forked from tatemazer/AcaiaArduinoBLE
-
Notifications
You must be signed in to change notification settings - Fork 0
Expand file tree
/
Copy pathAcaiaArduinoBLE.cpp
More file actions
381 lines (333 loc) · 13.1 KB
/
AcaiaArduinoBLE.cpp
File metadata and controls
381 lines (333 loc) · 13.1 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
/*
AcaiaArduinoBLE.cpp - Library for connecting to
an Acaia Scale using the ArduinoBLE library.
Created by Tate Mazer, December 13, 2023.
Released into the public domain.
Adding Generic Scale Support, Pio Baettig
*/
#include "Arduino.h"
#include "AcaiaArduinoBLE.h"
#include <ArduinoBLE.h>
byte IDENTIFY[20] = { 0xef, 0xdd, 0x0b, 0x30, 0x31, 0x32, 0x33, 0x34, 0x35, 0x36, 0x37, 0x38, 0x39, 0x30, 0x31, 0x32, 0x33, 0x34, 0x9a, 0x6d };
byte HEARTBEAT[7] = { 0xef, 0xdd, 0x00, 0x02, 0x00, 0x02, 0x00 };
byte NOTIFICATION_REQUEST[14] = { 0xef, 0xdd, 0x0c, 0x09, 0x00, 0x01, 0x01, 0x02, 0x02, 0x05, 0x03, 0x04, 0x15, 0x06 };
byte START_TIMER[7] = { 0xef, 0xdd, 0x0d, 0x00, 0x00, 0x00, 0x00 };
byte STOP_TIMER[7] = { 0xef, 0xdd, 0x0d, 0x00, 0x02, 0x00, 0x02 };
byte RESET_TIMER[7] = { 0xef, 0xdd, 0x0d, 0x00, 0x01, 0x00, 0x01 };
byte TARE_ACAIA[6] = { 0xef, 0xdd, 0x04, 0x00, 0x00, 0x00 };
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 };
/* Generic commands from
https://github.com/graphefruit/Beanconqueror/blob/master/src/classes/devices/felicita/constants.ts
*/
AcaiaArduinoBLE::AcaiaArduinoBLE(bool debug){
_debug = debug;
_currentWeight = 0;
_connected = false;
_packetPeriod = 0;
}
bool AcaiaArduinoBLE::init(String mac){
unsigned long start = millis();
_lastPacket = 0;
if (mac == ""){
BLE.scan();
}else if (!BLE.scanForAddress(mac)){
Serial.print("Failed to find ");
Serial.println(mac);
return false;
}
do{
BLEDevice peripheral = BLE.available();
if(_debug && peripheral){
// discovered a peripheral, print out address, local name, and advertised service
Serial.print("Found ");
Serial.print(peripheral.address());
Serial.print(" '");
Serial.print(peripheral.localName());
Serial.print("' ");
Serial.print(peripheral.advertisedServiceUuid());
Serial.println();
}
if (peripheral && isScaleName(peripheral.localName())) {
BLE.stopScan();
Serial.println("Connecting ...");
if (peripheral.connect()) {
Serial.println("Connected");
_peripheral = peripheral;
} else {
Serial.println("Failed to connect!");
return false;
}
Serial.println("Discovering attributes ...");
if (peripheral.discoverAttributes()) {
Serial.println("Attributes discovered");;
} else {
Serial.println("Attribute discovery failed!");
peripheral.disconnect();
return false;
}
if(_debug){
// read and print device name of peripheral
Serial.println();
Serial.print("Device name: ");
Serial.println(peripheral.deviceName());
Serial.print("Appearance: 0x");
Serial.println(peripheral.appearance(), HEX);
Serial.println();
// loop the services of the peripheral and explore each
for (int i = 0; i < peripheral.serviceCount(); i++) {
BLEService service = peripheral.service(i);
exploreService(service);
}
}
// Determine type of scale
if(peripheral.characteristic(READ_CHAR_OLD_VERSION).canSubscribe()){
Serial.println("Old version Acaia Detected");
_type = OLD;
_write = peripheral.characteristic(WRITE_CHAR_OLD_VERSION);
_read = peripheral.characteristic(READ_CHAR_OLD_VERSION);
}else if(peripheral.characteristic(READ_CHAR_NEW_VERSION).canSubscribe()){
Serial.println("New version Acaia Detected");
_type = NEW;
_write = peripheral.characteristic(WRITE_CHAR_NEW_VERSION);
_read = peripheral.characteristic(READ_CHAR_NEW_VERSION);
} else if(peripheral.characteristic(READ_CHAR_GENERIC).canSubscribe()){
Serial.println("Generic Scale Detected");
_type = GENERIC;
_write = peripheral.characteristic(WRITE_CHAR_GENERIC);
_read = peripheral.characteristic(READ_CHAR_GENERIC);
}
else{
Serial.println("unable to determine scale type");
return false;
}
if(!_read.canSubscribe()){
Serial.println("unable to subscribe to READ");
return false;
}else if(!_read.subscribe()){
Serial.println("subscription failed");
return false;
}else {
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");
return false;
}
_connected = true;
_packetPeriod = 0;
return true;
}
}while(millis() - start < 10000);
Serial.println("failed to find scale");
return false;
}
bool AcaiaArduinoBLE::tare(){
if(_write.writeValue((_type == GENERIC ? TARE_GENERIC : TARE_ACAIA), 6)){
Serial.println("tare write successful");
return true;
}else{
_connected = false;
Serial.println("tare write failed");
return false;
}
}
bool AcaiaArduinoBLE::startTimer(){
if(_write.writeValue((_type == GENERIC ? START_TIMER_GENERIC : START_TIMER),
(_type == GENERIC ? 6 : 7))){
Serial.println("start timer write successful");
return true;
}else{
_connected = false;
Serial.println("start timer write failed");
return false;
}
}
bool AcaiaArduinoBLE::stopTimer(){
if(_write.writeValue((_type == GENERIC ? STOP_TIMER_GENERIC : STOP_TIMER),
(_type == GENERIC ? 6 : 7 ))){
Serial.println("stop timer write successful");
return true;
}else{
_connected = false;
Serial.println("stop timer write failed");
return false;
}
}
bool AcaiaArduinoBLE::resetTimer(){
if(_write.writeValue((_type == GENERIC ? RESET_TIMER_GENERIC : RESET_TIMER),
(_type == GENERIC ? 6 : 7 ))){
Serial.println("reset timer write successful");
return true;
}else{
_connected = false;
Serial.println("reset timer write failed");
return false;
}
}
bool AcaiaArduinoBLE::heartbeat(){
if(_write.writeValue(HEARTBEAT, 7)){
_lastHeartBeat = millis();
return true;
}else{
_connected = false;
return false;
}
}
float AcaiaArduinoBLE::getWeight(){
return _currentWeight;
}
bool AcaiaArduinoBLE::heartbeatRequired(){
if(_type == OLD || _type == NEW){
return (millis() - _lastHeartBeat) > HEARTBEAT_PERIOD_MS;
}else{
return 0;
}
}
bool AcaiaArduinoBLE::isConnected(){
return _connected;
}
bool AcaiaArduinoBLE::newWeightAvailable(){
bool newWeightPacket = false;
//check how long its been since we last got a response
if(_lastPacket && millis()-_lastPacket > MAX_PACKET_PERIOD_MS){
Serial.println("timeout!");
//reset connection
_connected = false;
_peripheral.disconnect();
return false;
}else if(_read.valueUpdated()){
byte input[] = {0,0,0,0,0,0,0,0,0,0,0,0,0};
int l = _read.valueLength();
// Get packet
if(10 >= l || //10 byte packets for pre-2021 lunar
(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
){
_read.readValue(input, (l > 13) ? 13 : l); // readValue() seems to crash whenever l > weight packet (10, 13 or 18)
if(_debug){
Serial.print(l);
Serial.print(": 0x");
printData(input, l);
Serial.println();
}
}
// Parse New style data packet
if (NEW == _type && (13 == l || 17 == l) && input[4] == 0x05)
{
//Grab weight bytes (5 and 6)
// apply scaling based on the unit byte (9)
// get sign byte (10)
_currentWeight = (((input[6] & 0xff) << 8) + (input[5] & 0xff))
/ pow(10,input[9])
* ((input[10] & 0x02) ? -1 : 1);
newWeightPacket = true;
// Parse old style data packet
}else if( OLD == _type && (l == 10 || l == 14)){
//Grab weight bytes (2 and 3),
// apply scaling based on the unit byte (6)
// get sign byte (7)
_currentWeight = (((input[3] & 0xff) << 8) + (input[2] & 0xff))
/ pow(10, input[6])
* ((input[7] & 0x02) ? -1 : 1);
newWeightPacket = true;
}else if( GENERIC == _type && l == 20){
//Grab weight bytes (3-8),
// get sign byte (2)
_currentWeight = (( input[7] << 16) | (input[8] << 8) | input[9]);
if (input[6] == 45) { // Check if the value is negative
_currentWeight = -_currentWeight;
}
_currentWeight = _currentWeight / 100;
newWeightPacket = true;
}
if(newWeightPacket){
if(_lastPacket){
_packetPeriod = millis() - _lastPacket;
}
_lastPacket = millis();
}
return newWeightPacket;
}
else{
return false;
}
}
bool AcaiaArduinoBLE::isScaleName(String name){
String nameShort = name.substring(0,5);
return nameShort == "CINCO"
|| nameShort == "ACAIA"
|| nameShort == "PYXIS"
|| nameShort == "LUNAR"
|| nameShort == "PEARL"
|| nameShort == "PROCH"
|| nameShort == "BOOKO";
}
void AcaiaArduinoBLE::exploreService(BLEService service) {
// print the UUID of the service
Serial.print("Service ");
Serial.println(service.uuid());
// loop the characteristics of the service and explore each
for (int i = 0; i < service.characteristicCount(); i++) {
BLECharacteristic characteristic = service.characteristic(i);
exploreCharacteristic(characteristic);
}
}
void AcaiaArduinoBLE::exploreCharacteristic(BLECharacteristic characteristic) {
// print the UUID and properties of the characteristic
Serial.print("\tCharacteristic ");
Serial.print(characteristic.uuid());
Serial.print(", properties 0x");
Serial.print(characteristic.properties(), HEX);
// check if the characteristic is readable
if (characteristic.canRead()) {
// read the characteristic value
characteristic.read();
if (characteristic.valueLength() > 0) {
// print out the value of the characteristic
Serial.print(", value 0x");
printData(characteristic.value(), characteristic.valueLength());
}
}
Serial.println();
// loop the descriptors of the characteristic and explore each
for (int i = 0; i < characteristic.descriptorCount(); i++) {
BLEDescriptor descriptor = characteristic.descriptor(i);
exploreDescriptor(descriptor);
}
}
void AcaiaArduinoBLE::exploreDescriptor(BLEDescriptor descriptor) {
// print the UUID of the descriptor
Serial.print("\t\tDescriptor ");
Serial.print(descriptor.uuid());
// read the descriptor value
descriptor.read();
// print out the value of the descriptor
Serial.print(", value 0x");
printData(descriptor.value(), descriptor.valueLength());
Serial.println();
}
void AcaiaArduinoBLE::printData(const unsigned char data[], int length) {
for (int i = 0; i < length; i++) {
unsigned char b = data[i];
if (b < 16) {
Serial.print("0");
}
Serial.print(b, HEX);
}
}
void AcaiaArduinoBLE::disconnect() {
_peripheral.disconnect();
}