@@ -28,14 +28,35 @@ byte BEEP_LEVEL_1_BOOKOO[6] = { 0x03, 0x0a, 0x02, 0x00, 0x01, 0x0a };
2828byte TARE_FELICITA [1 ] = { 0x54 }; // 'Tare'
2929byte START_TIMER_FELICITA [1 ] = { 0x52 }; // 'Start_Timer'
3030byte STOP_TIMER_FELICITA [1 ] = { 0x53 }; // 'Stop_Timer'
31- byte RESET_TIMER_FELICITA [1 ] = { 0x43 }; // 'Reset_Timer'
32- byte WEIGHT_TIMER_MODE_FELICITA [1 ] = { 0x32 }; // 'Weight+timer mode (known-good wake state)
31+ byte RESET_TIMER_FELICITA [1 ] = { 0x43 }; // 'Reset_Timer'
32+ byte WEIGHT_TIMER_MODE_FELICITA [1 ] = { 0x32 }; // 'Weight+timer mode (known-good wake state)
33+ byte TARE_ECLAIR [3 ] = { 0x54 , 0x01 , 0x01 };
34+ byte START_TIMER_ECLAIR [3 ] = { 0x53 , 0x01 , 0x01 };
35+ byte STOP_TIMER_ECLAIR [3 ] = { 0x45 , 0x01 , 0x01 };
36+ byte RESET_TIMER_ECLAIR [3 ] = { 0x52 , 0x01 , 0x01 };
3337
3438/* Generic commands from
3539 https://github.com/graphefruit/Beanconqueror/blob/master/src/classes/devices/felicita/constants.ts
36- */
37-
38- AcaiaArduinoBLE::AcaiaArduinoBLE (bool debug){
40+ */
41+
42+ uint8_t calculateXOR (const byte data[], int length){
43+ uint8_t result = 0 ;
44+ for (int i = 0 ; i < length; i++){
45+ result ^= data[i];
46+ }
47+ return result;
48+ }
49+
50+ int32_t readInt32LittleEndian (const byte data[]){
51+ return (int32_t )(
52+ ((uint32_t )data[0 ]) |
53+ ((uint32_t )data[1 ] << 8 ) |
54+ ((uint32_t )data[2 ] << 16 ) |
55+ ((uint32_t )data[3 ] << 24 )
56+ );
57+ }
58+
59+ AcaiaArduinoBLE::AcaiaArduinoBLE (bool debug){
3960 _debug = debug;
4061 _currentWeight = 0 ;
4162 _connected = false ;
@@ -125,12 +146,17 @@ bool AcaiaArduinoBLE::init(String mac){
125146 _type = GENERIC ;
126147 _write = peripheral.characteristic (WRITE_CHAR_GENERIC );
127148 _read = peripheral.characteristic (READ_CHAR_GENERIC );
128- } else if (peripheral.characteristic (READ_CHAR_FELICITA ).canSubscribe ()){
129- Serial.println (" Felicita Arc Detected" );
130- _type = FELICITA ;
131- _write = peripheral.characteristic (WRITE_CHAR_FELICITA );
132- _read = peripheral.characteristic (READ_CHAR_FELICITA );
133- }
149+ } else if (peripheral.characteristic (READ_CHAR_FELICITA ).canSubscribe ()){
150+ Serial.println (" Felicita Arc Detected" );
151+ _type = FELICITA ;
152+ _write = peripheral.characteristic (WRITE_CHAR_FELICITA );
153+ _read = peripheral.characteristic (READ_CHAR_FELICITA );
154+ } else if (peripheral.characteristic (READ_CHAR_ECLAIR ).canSubscribe ()){
155+ Serial.println (" Eclair Scale Detected" );
156+ _type = ECLAIR ;
157+ _write = peripheral.characteristic (WRITE_CHAR_ECLAIR );
158+ _read = peripheral.characteristic (READ_CHAR_ECLAIR );
159+ }
134160 else {
135161 Serial.println (" unable to determine scale type" );
136162 return false ;
@@ -159,16 +185,16 @@ bool AcaiaArduinoBLE::init(String mac){
159185 Serial.println (" notification request write failed" );
160186 return false ;
161187 }
162- }else if (_type == FELICITA ){
163- // Wake the Felicita into a deterministic, known-good mode
164- // (weight + timer) instead of whatever IDENTIFY used to leave it in.
165- if (_write.writeValue (WEIGHT_TIMER_MODE_FELICITA , sizeof (WEIGHT_TIMER_MODE_FELICITA ))){
166- Serial.println (" felicita weight+timer mode set" );
167- }else {
168- Serial.println (" felicita mode set failed" );
169- return false ;
170- }
171- }
188+ }else if (_type == FELICITA ){
189+ // Wake the Felicita into a deterministic, known-good mode
190+ // (weight + timer) instead of whatever IDENTIFY used to leave it in.
191+ if (_write.writeValue (WEIGHT_TIMER_MODE_FELICITA , sizeof (WEIGHT_TIMER_MODE_FELICITA ))){
192+ Serial.println (" felicita weight+timer mode set" );
193+ }else {
194+ Serial.println (" felicita mode set failed" );
195+ return false ;
196+ }
197+ }
172198 _connected = true ;
173199 _packetPeriod = 0 ;
174200 return true ;
@@ -185,11 +211,12 @@ bool AcaiaArduinoBLE::init(String mac){
185211// leaking adjacent memory to the scale as extra commands.
186212bool AcaiaArduinoBLE::tare (){
187213 bool ok;
188- switch (_type){
189- case GENERIC : ok = _write.writeValue (TARE_GENERIC , sizeof (TARE_GENERIC )); break ;
190- case FELICITA : ok = _write.writeValue (TARE_FELICITA , sizeof (TARE_FELICITA )); break ;
191- default : ok = _write.writeValue (TARE_ACAIA , sizeof (TARE_ACAIA )); break ;
192- }
214+ switch (_type){
215+ case GENERIC : ok = _write.writeValue (TARE_GENERIC , sizeof (TARE_GENERIC )); break ;
216+ case FELICITA : ok = _write.writeValue (TARE_FELICITA , sizeof (TARE_FELICITA )); break ;
217+ case ECLAIR : ok = _write.writeValue (TARE_ECLAIR , sizeof (TARE_ECLAIR )); break ;
218+ default : ok = _write.writeValue (TARE_ACAIA , sizeof (TARE_ACAIA )); break ;
219+ }
193220 if (ok){
194221 Serial.println (" tare write successful" );
195222 return true ;
@@ -202,11 +229,12 @@ bool AcaiaArduinoBLE::tare(){
202229
203230bool AcaiaArduinoBLE::startTimer (){
204231 bool ok;
205- switch (_type){
206- case GENERIC : ok = _write.writeValue (START_TIMER_GENERIC , sizeof (START_TIMER_GENERIC )); break ;
207- case FELICITA : ok = _write.writeValue (START_TIMER_FELICITA , sizeof (START_TIMER_FELICITA )); break ;
208- default : ok = _write.writeValue (START_TIMER , sizeof (START_TIMER )); break ;
209- }
232+ switch (_type){
233+ case GENERIC : ok = _write.writeValue (START_TIMER_GENERIC , sizeof (START_TIMER_GENERIC )); break ;
234+ case FELICITA : ok = _write.writeValue (START_TIMER_FELICITA , sizeof (START_TIMER_FELICITA )); break ;
235+ case ECLAIR : ok = _write.writeValue (START_TIMER_ECLAIR , sizeof (START_TIMER_ECLAIR )); break ;
236+ default : ok = _write.writeValue (START_TIMER , sizeof (START_TIMER )); break ;
237+ }
210238 if (ok){
211239 Serial.println (" start timer write successful" );
212240 return true ;
@@ -219,11 +247,12 @@ bool AcaiaArduinoBLE::startTimer(){
219247
220248bool AcaiaArduinoBLE::stopTimer (){
221249 bool ok;
222- switch (_type){
223- case GENERIC : ok = _write.writeValue (STOP_TIMER_GENERIC , sizeof (STOP_TIMER_GENERIC )); break ;
224- case FELICITA : ok = _write.writeValue (STOP_TIMER_FELICITA , sizeof (STOP_TIMER_FELICITA )); break ;
225- default : ok = _write.writeValue (STOP_TIMER , sizeof (STOP_TIMER )); break ;
226- }
250+ switch (_type){
251+ case GENERIC : ok = _write.writeValue (STOP_TIMER_GENERIC , sizeof (STOP_TIMER_GENERIC )); break ;
252+ case FELICITA : ok = _write.writeValue (STOP_TIMER_FELICITA , sizeof (STOP_TIMER_FELICITA )); break ;
253+ case ECLAIR : ok = _write.writeValue (STOP_TIMER_ECLAIR , sizeof (STOP_TIMER_ECLAIR )); break ;
254+ default : ok = _write.writeValue (STOP_TIMER , sizeof (STOP_TIMER )); break ;
255+ }
227256 if (ok){
228257 Serial.println (" stop timer write successful" );
229258 return true ;
@@ -236,11 +265,12 @@ bool AcaiaArduinoBLE::stopTimer(){
236265
237266bool AcaiaArduinoBLE::resetTimer (){
238267 bool ok;
239- switch (_type){
240- case GENERIC : ok = _write.writeValue (RESET_TIMER_GENERIC , sizeof (RESET_TIMER_GENERIC )); break ;
241- case FELICITA : ok = _write.writeValue (RESET_TIMER_FELICITA , sizeof (RESET_TIMER_FELICITA )); break ;
242- default : ok = _write.writeValue (RESET_TIMER , sizeof (RESET_TIMER )); break ;
243- }
268+ switch (_type){
269+ case GENERIC : ok = _write.writeValue (RESET_TIMER_GENERIC , sizeof (RESET_TIMER_GENERIC )); break ;
270+ case FELICITA : ok = _write.writeValue (RESET_TIMER_FELICITA , sizeof (RESET_TIMER_FELICITA )); break ;
271+ case ECLAIR : ok = _write.writeValue (RESET_TIMER_ECLAIR , sizeof (RESET_TIMER_ECLAIR )); break ;
272+ default : ok = _write.writeValue (RESET_TIMER , sizeof (RESET_TIMER )); break ;
273+ }
244274 if (ok){
245275 Serial.println (" reset timer write successful" );
246276 return true ;
@@ -251,32 +281,46 @@ bool AcaiaArduinoBLE::resetTimer(){
251281 }
252282}
253283
254- bool AcaiaArduinoBLE::tareStartTimer (){
255- if (_write.writeValue (TARE_START_TIMER_BOOKOO , 6 )){
256- Serial.println (" tare and start timer write successful" );
257- return true ;
284+ bool AcaiaArduinoBLE::tareStartTimer (){
285+ bool ok;
286+ if (_type == ECLAIR ){
287+ ok = _write.writeValue (START_TIMER_ECLAIR , sizeof (START_TIMER_ECLAIR ));
288+ }else {
289+ ok = _write.writeValue (TARE_START_TIMER_BOOKOO , sizeof (TARE_START_TIMER_BOOKOO ));
290+ }
291+ if (ok){
292+ Serial.println (" tare and start timer write successful" );
293+ return true ;
258294 }else {
259295 _connected = false ;
260296 Serial.println (" tare and start timer write failed" );
261297 return false ;
262298 }
263299}
264300
265- bool AcaiaArduinoBLE::beep (){
266-
267- // for acaia's, use the tare command to generate the beep
268- if (_write.writeValue ((_type == GENERIC ? BEEP_LEVEL_1_BOOKOO : TARE_ACAIA ), 6 )){
269- Serial.println (" beep write successful" );
270- return true ;
301+ bool AcaiaArduinoBLE::beep (){
302+ bool ok;
303+ switch (_type){
304+ case GENERIC : ok = _write.writeValue (BEEP_LEVEL_1_BOOKOO , sizeof (BEEP_LEVEL_1_BOOKOO )); break ;
305+ case ECLAIR : ok = _write.writeValue (TARE_ECLAIR , sizeof (TARE_ECLAIR )); break ;
306+ default : ok = _write.writeValue (TARE_ACAIA , sizeof (TARE_ACAIA )); break ;
307+ }
308+ if (ok){
309+ Serial.println (" beep write successful" );
310+ return true ;
271311 }else {
272312 _connected = false ;
273313 Serial.println (" beep level write failed" );
274314 return false ;
275315 }
276316}
277317
278- bool AcaiaArduinoBLE::heartbeat (){
279- if (_write.writeValue (HEARTBEAT , 7 )){
318+ bool AcaiaArduinoBLE::heartbeat (){
319+ if (_type == ECLAIR ){
320+ return true ;
321+ }
322+
323+ if (_write.writeValue (HEARTBEAT , 7 )){
280324 _lastHeartBeat = millis ();
281325 return true ;
282326 }else {
@@ -313,7 +357,7 @@ bool AcaiaArduinoBLE::newWeightAvailable(){
313357 int l = _read.valueLength ();
314358
315359 // Get packet
316- if (10 >= l || // 10 byte packets for pre-2021 lunar
360+ if (10 >= l || // 10 byte packets for pre-2021 lunar and Eclair
317361 (13 >= l && OLD != _type) || // 13 byte packets for pyxis and older lunar 2021 fw
318362 (14 == l && OLD == _type) || // 14 byte packets for lunar 2021 AL008
319363 (17 == l && NEW == _type) || // 17 byte packets for newer lunar 2021 fw
@@ -363,19 +407,27 @@ bool AcaiaArduinoBLE::newWeightAvailable(){
363407 _currentWeight = _currentWeight / 100 ;
364408 newWeightPacket = true ;
365409
366- }else if ( FELICITA == _type && l == 18 ){
367- // Felicita Arc 18-byte ASCII packet (matches pyfelicita lib):
368- // byte 2 = sign ('-'/0x2D = negative)
369- // bytes 3..8 = six ASCII weight digits, value / 100 = grams
370- _currentWeight = (input[2 ] == 0x2D ? -1 : 1 )
371- * ( (input[3 ] - 0x30 ) * 1000
372- + (input[4 ] - 0x30 ) * 100
373- + (input[5 ] - 0x30 ) * 10
374- + (input[6 ] - 0x30 ) * 1
375- + (input[7 ] - 0x30 ) * 0.1
376- + (input[8 ] - 0x30 ) * 0.01 );
377- newWeightPacket = true ;
378- }
410+ }else if ( FELICITA == _type && l == 18 ){
411+ // Felicita Arc 18-byte ASCII packet (matches pyfelicita lib):
412+ // byte 2 = sign ('-'/0x2D = negative)
413+ // bytes 3..8 = six ASCII weight digits, value / 100 = grams
414+ _currentWeight = (input[2 ] == 0x2D ? -1 : 1 )
415+ * ( (input[3 ] - 0x30 ) * 1000
416+ + (input[4 ] - 0x30 ) * 100
417+ + (input[5 ] - 0x30 ) * 10
418+ + (input[6 ] - 0x30 ) * 1
419+ + (input[7 ] - 0x30 ) * 0.1
420+ + (input[8 ] - 0x30 ) * 0.01 );
421+ newWeightPacket = true ;
422+
423+ }else if ( ECLAIR == _type && l == 10 && input[0 ] == ' W' ){
424+ if (calculateXOR (input + 1 , 8 ) == input[9 ]){
425+ _currentWeight = readInt32LittleEndian (input + 1 ) / 1000.0 ;
426+ newWeightPacket = true ;
427+ }else if (_debug){
428+ Serial.println (" Eclair packet XOR failed" );
429+ }
430+ }
379431 if (newWeightPacket){
380432 if (_lastPacket){
381433 _packetPeriod = millis () - _lastPacket;
@@ -396,10 +448,11 @@ bool AcaiaArduinoBLE::isScaleName(String name){
396448 || nameShort == " PYXIS"
397449 || nameShort == " LUNAR"
398450 || nameShort == " PEARL"
399- || nameShort == " PROCH"
400- || nameShort == " BOOKO"
401- || nameShort == " FELIC" ;
402- }
451+ || nameShort == " PROCH"
452+ || nameShort == " BOOKO"
453+ || nameShort == " FELIC"
454+ || nameShort == " ECLAI" ;
455+ }
403456
404457void AcaiaArduinoBLE::exploreService (BLEService service) {
405458 // print the UUID of the service
0 commit comments