From 98ef631530d57ab6e1ff22db2ff8980b9755daf1 Mon Sep 17 00:00:00 2001 From: TWJ Date: Sun, 14 Jun 2026 21:34:41 +0300 Subject: [PATCH 1/2] Add back the support for Felicita Arc scale and fix related bugs --- AcaiaArduinoBLE.cpp | 101 +++++++++++++++++++++++++++++++++++--------- AcaiaArduinoBLE.h | 14 +++--- 2 files changed, 91 insertions(+), 24 deletions(-) diff --git a/AcaiaArduinoBLE.cpp b/AcaiaArduinoBLE.cpp index 2bf3112..9d78f19 100644 --- a/AcaiaArduinoBLE.cpp +++ b/AcaiaArduinoBLE.cpp @@ -5,6 +5,8 @@ Released into the public domain. Adding Generic Scale Support, Pio Baettig + Fixing Felicita Arc Bug, A-TWJ + */ #include "Arduino.h" #include "AcaiaArduinoBLE.h" @@ -21,6 +23,11 @@ byte TARE_GENERIC[6] = { 0x03, 0x0a, 0x01, 0x00, 0x00, 0x08 }; byte START_TIMER_GENERIC[6] = { 0x03, 0x0a, 0x04, 0x00, 0x00, 0x0a }; byte STOP_TIMER_GENERIC[6] = { 0x03, 0x0a, 0x05, 0x00, 0x00, 0x0d }; byte RESET_TIMER_GENERIC[6] = { 0x03, 0x0a, 0x06, 0x00, 0x00, 0x0c }; +byte TARE_FELICITA[1] = { 0x54 }; // 'Tare' +byte START_TIMER_FELICITA[1] = { 0x52 }; // 'Start_Timer' +byte STOP_TIMER_FELICITA[1] = { 0x53 }; // 'Stop_Timer' +byte RESET_TIMER_FELICITA[1] = { 0x43 }; // 'Reset_Timer' +byte WEIGHT_TIMER_MODE_FELICITA[1] = { 0x32 }; // 'Weight+timer mode (known-good wake state) /* Generic commands from https://github.com/graphefruit/Beanconqueror/blob/master/src/classes/devices/felicita/constants.ts @@ -116,6 +123,11 @@ bool AcaiaArduinoBLE::init(String mac){ _type = GENERIC; _write = peripheral.characteristic(WRITE_CHAR_GENERIC); _read = peripheral.characteristic(READ_CHAR_GENERIC); + } else if(peripheral.characteristic(READ_CHAR_FELICITA).canSubscribe()){ + Serial.println("Felicita Arc Detected"); + _type = FELICITA; + _write = peripheral.characteristic(WRITE_CHAR_FELICITA); + _read = peripheral.characteristic(READ_CHAR_FELICITA); } else{ Serial.println("unable to determine scale type"); @@ -132,17 +144,28 @@ bool AcaiaArduinoBLE::init(String mac){ Serial.println("subscribed!"); } - if(_write.writeValue(IDENTIFY, 20)){ - Serial.println("identify write successful"); - }else{ - Serial.println("identify write failed"); - return false; - } - if(_write.writeValue(NOTIFICATION_REQUEST,14)){ - Serial.println("notification request write successful"); - }else{ - Serial.println("notification request write failed"); + if(_type == OLD || _type == NEW){ + if(_write.writeValue(IDENTIFY, sizeof(IDENTIFY))){ + Serial.println("identify write successful"); + }else{ + Serial.println("identify write failed"); + return false; + } + if(_write.writeValue(NOTIFICATION_REQUEST, sizeof(NOTIFICATION_REQUEST))){ + Serial.println("notification request write successful"); + }else{ + Serial.println("notification request write failed"); return false; + } + }else if(_type == FELICITA){ + // Wake the Felicita into a deterministic, known-good mode + // (weight + timer) instead of whatever IDENTIFY used to leave it in. + if(_write.writeValue(WEIGHT_TIMER_MODE_FELICITA, sizeof(WEIGHT_TIMER_MODE_FELICITA))){ + Serial.println("felicita weight+timer mode set"); + }else{ + Serial.println("felicita mode set failed"); + return false; + } } _connected = true; _packetPeriod = 0; @@ -154,8 +177,18 @@ bool AcaiaArduinoBLE::init(String mac){ return false; } +// All command writes now use sizeof() so the byte count always matches the +// array. Felicita commands are single ASCII bytes; sending the right length +// (1) is essential -- the old code wrote 20/7 bytes from these 1-byte arrays, +// leaking adjacent memory to the scale as extra commands. bool AcaiaArduinoBLE::tare(){ - if(_write.writeValue((_type == GENERIC ? TARE_GENERIC : TARE_ACAIA), 6)){ + bool ok; + switch(_type){ + case GENERIC: ok = _write.writeValue(TARE_GENERIC, sizeof(TARE_GENERIC)); break; + case FELICITA: ok = _write.writeValue(TARE_FELICITA, sizeof(TARE_FELICITA)); break; + default: ok = _write.writeValue(TARE_ACAIA, sizeof(TARE_ACAIA)); break; + } + if(ok){ Serial.println("tare write successful"); return true; }else{ @@ -166,8 +199,13 @@ bool AcaiaArduinoBLE::tare(){ } bool AcaiaArduinoBLE::startTimer(){ - if(_write.writeValue((_type == GENERIC ? START_TIMER_GENERIC : START_TIMER), - (_type == GENERIC ? 6 : 7))){ + bool ok; + switch(_type){ + case GENERIC: ok = _write.writeValue(START_TIMER_GENERIC, sizeof(START_TIMER_GENERIC)); break; + case FELICITA: ok = _write.writeValue(START_TIMER_FELICITA, sizeof(START_TIMER_FELICITA)); break; + default: ok = _write.writeValue(START_TIMER, sizeof(START_TIMER)); break; + } + if(ok){ Serial.println("start timer write successful"); return true; }else{ @@ -178,8 +216,13 @@ bool AcaiaArduinoBLE::startTimer(){ } bool AcaiaArduinoBLE::stopTimer(){ - if(_write.writeValue((_type == GENERIC ? STOP_TIMER_GENERIC : STOP_TIMER), - (_type == GENERIC ? 6 : 7 ))){ + bool ok; + switch(_type){ + case GENERIC: ok = _write.writeValue(STOP_TIMER_GENERIC, sizeof(STOP_TIMER_GENERIC)); break; + case FELICITA: ok = _write.writeValue(STOP_TIMER_FELICITA, sizeof(STOP_TIMER_FELICITA)); break; + default: ok = _write.writeValue(STOP_TIMER, sizeof(STOP_TIMER)); break; + } + if(ok){ Serial.println("stop timer write successful"); return true; }else{ @@ -190,8 +233,13 @@ bool AcaiaArduinoBLE::stopTimer(){ } bool AcaiaArduinoBLE::resetTimer(){ - if(_write.writeValue((_type == GENERIC ? RESET_TIMER_GENERIC : RESET_TIMER), - (_type == GENERIC ? 6 : 7 ))){ + bool ok; + switch(_type){ + case GENERIC: ok = _write.writeValue(RESET_TIMER_GENERIC, sizeof(RESET_TIMER_GENERIC)); break; + case FELICITA: ok = _write.writeValue(RESET_TIMER_FELICITA, sizeof(RESET_TIMER_FELICITA)); break; + default: ok = _write.writeValue(RESET_TIMER, sizeof(RESET_TIMER)); break; + } + if(ok){ Serial.println("reset timer write successful"); return true; }else{ @@ -243,7 +291,8 @@ bool AcaiaArduinoBLE::newWeightAvailable(){ (13 >= l && OLD != _type) || //13 byte packets for pyxis and older lunar 2021 fw (14 == l && OLD == _type) || //14 byte packets for lunar 2021 AL008 (17 == l && NEW == _type) || //17 byte packets for newer lunar 2021 fw - (20 == l && GENERIC == _type) //18 byte packets for generic scales + (20 == l && GENERIC == _type) || //20 byte packets for generic scales + (18 == l && FELICITA == _type) //18 byte packets for Felicita Arc ){ _read.readValue(input, (l > 13) ? 13 : l); // readValue() seems to crash whenever l > weight packet (10, 13 or 18) @@ -287,6 +336,19 @@ bool AcaiaArduinoBLE::newWeightAvailable(){ } _currentWeight = _currentWeight / 100; newWeightPacket = true; + + }else if( FELICITA == _type && l == 18){ + // Felicita Arc 18-byte ASCII packet (matches pyfelicita lib): + // byte 2 = sign ('-'/0x2D = negative) + // bytes 3..8 = six ASCII weight digits, value / 100 = grams + _currentWeight = (input[2] == 0x2D ? -1 : 1) + * ( (input[3] - 0x30) * 1000 + + (input[4] - 0x30) * 100 + + (input[5] - 0x30) * 10 + + (input[6] - 0x30) * 1 + + (input[7] - 0x30) * 0.1 + + (input[8] - 0x30) * 0.01 ); + newWeightPacket = true; } if(newWeightPacket){ if(_lastPacket){ @@ -309,7 +371,8 @@ bool AcaiaArduinoBLE::isScaleName(String name){ || nameShort == "LUNAR" || nameShort == "PEARL" || nameShort == "PROCH" - || nameShort == "BOOKO"; + || nameShort == "BOOKO" + || nameShort == "FELIC"; } void AcaiaArduinoBLE::exploreService(BLEService service) { diff --git a/AcaiaArduinoBLE.h b/AcaiaArduinoBLE.h index 91b98ef..8b4744b 100644 --- a/AcaiaArduinoBLE.h +++ b/AcaiaArduinoBLE.h @@ -5,20 +5,23 @@ Released into the public domain. Pio Baettig: Adding Felicita Arc support - + A-TWJ: Fixing Felicita Arc bug (issue #15) + Known Bugs: * Only supports Grams */ #ifndef AcaiaArduinoBLE_h #define AcaiaArduinoBLE_h -#define LIBRARY_VERSION "3.3.0" +#define LIBRARY_VERSION "3.3.0-felicita-fix" #define WRITE_CHAR_OLD_VERSION "2a80" #define READ_CHAR_OLD_VERSION "2a80" #define WRITE_CHAR_NEW_VERSION "49535343-8841-43f4-a8d4-ecbe34729bb3" #define READ_CHAR_NEW_VERSION "49535343-1e4d-4bd9-ba61-23c647249616" #define WRITE_CHAR_GENERIC "ff12" #define READ_CHAR_GENERIC "ff11" +#define WRITE_CHAR_FELICITA "ffe1" // Felicita Arc: single ffe1 char (service ffe0) for both +#define READ_CHAR_FELICITA "ffe1" // notify (weight) and write (commands) #define HEARTBEAT_PERIOD_MS 2750 #define MAX_PACKET_PERIOD_MS 5000 @@ -26,9 +29,10 @@ #include enum scale_type{ - OLD, // Lunar (pre-2021) - NEW, // Lunar (2021), Pyxis - GENERIC // Felicita Arc, etc + OLD, // Lunar (pre-2021) + NEW, // Lunar (2021), Pyxis + GENERIC, // BooKoo Themis, Decent, etc (ff11/ff12, binary protocol) + FELICITA // Felicita Arc (ffe1, single-byte ASCII protocol) }; class AcaiaArduinoBLE{ From 1443bc9fcaa4596aa515e2903b1816ffb6fe0445 Mon Sep 17 00:00:00 2001 From: A-TWJ <150237906+A-TWJ@users.noreply.github.com> Date: Tue, 16 Jun 2026 19:42:37 +0300 Subject: [PATCH 2/2] Update README with project details and compatibility --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index 320db39..bfbb93e 100644 --- a/README.md +++ b/README.md @@ -118,7 +118,7 @@ Scale Compatibility: ☑ Pearl S -❌ Felicita Arc (buggy, see bug report) +☑ Felicita Arc (under-testing) ☑ Bookoo