├── Buffer.cpp ├── Buffer.h ├── Device.h ├── Device_Curie.cpp ├── Device_Curie.h ├── Device_HM10.cpp ├── Device_HM10.h ├── LICENSE ├── Scale.cpp ├── Scale.h └── Scale.ino /Buffer.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | #include "Buffer.h" 3 | 4 | #include 5 | 6 | unsigned char * Buffer::getPayload() { 7 | 8 | return data; 9 | } 10 | 11 | 12 | 13 | int Buffer::getLen() { 14 | 15 | return len; 16 | } 17 | 18 | 19 | 20 | int Buffer::getFreeLen() { 21 | 22 | return dlen - len; 23 | } 24 | 25 | 26 | bool Buffer::hasBytes(unsigned int bytes) { 27 | 28 | if (len < bytes) { 29 | return false; 30 | } 31 | 32 | return true; 33 | } 34 | 35 | 36 | unsigned char Buffer::getByte(unsigned int pos) { 37 | 38 | if (pos >= len) { 39 | return 0; 40 | } 41 | 42 | return data[pos]; 43 | } 44 | 45 | 46 | void Buffer::addBytes(const unsigned char * bytes, int bLen) { 47 | 48 | if ((bLen < 0) || ((len + bLen) < 0) || ((len + bLen) > dlen)) { 49 | return; 50 | } 51 | 52 | memcpy(data + len, bytes, bLen); 53 | len += bLen; 54 | } 55 | 56 | 57 | 58 | void Buffer::addByteCount(int bLen) { 59 | 60 | len += bLen; 61 | } 62 | 63 | void Buffer::removeBytes(int bLen) { 64 | 65 | if (bLen <= 0) { 66 | return; 67 | } 68 | 69 | if (bLen >= len) { 70 | len = 0; 71 | return; 72 | } 73 | 74 | for (int i = 0; i < len - bLen; i++) { 75 | data[i] = data[bLen + i]; 76 | } 77 | 78 | len = len - bLen; 79 | } 80 | 81 | 82 | void Buffer::reset() { 83 | 84 | len = 0; 85 | } 86 | 87 | 88 | Buffer::Buffer() { 89 | 90 | len = 0; 91 | dlen = sizeof(data); 92 | } 93 | 94 | 95 | Buffer::~Buffer() { 96 | 97 | } 98 | 99 | 100 | -------------------------------------------------------------------------------- /Buffer.h: -------------------------------------------------------------------------------- 1 | #ifndef BUFFER_H 2 | #define BUFFER_H 3 | 4 | class Buffer { 5 | 6 | unsigned char data[40]; 7 | int len; 8 | int dlen; 9 | 10 | public: 11 | Buffer(); 12 | ~Buffer(); 13 | unsigned char * getPayload(); 14 | int getLen(); 15 | int getFreeLen(); 16 | unsigned char getByte(unsigned int pos); 17 | bool hasBytes(unsigned int bytes); 18 | void addBytes(const unsigned char * bytes, int bLen); 19 | void addByteCount(int bLen); 20 | void removeBytes(int bLen); 21 | void reset(); 22 | }; 23 | 24 | #endif 25 | 26 | -------------------------------------------------------------------------------- /Device.h: -------------------------------------------------------------------------------- 1 | #ifndef DEVICE_H_ 2 | #define DEVICE_H_ 3 | 4 | #include "Buffer.h" 5 | 6 | class Device { 7 | 8 | public: 9 | const char *service = "1820"; 10 | const char *characteristic = "2a80"; 11 | 12 | virtual bool isNewConnection() = 0; 13 | virtual bool isConnected() = 0; 14 | virtual void connect() = 0; 15 | virtual void disconnect() = 0; 16 | virtual void init() = 0; 17 | virtual void removeBytes(int bLen) = 0; 18 | virtual unsigned char getByte(unsigned int pos) = 0; 19 | virtual unsigned char * getPayload() = 0; 20 | virtual bool hasBytes(unsigned int bytes) = 0; 21 | virtual void write(const unsigned char *payload, int len) = 0; 22 | }; 23 | 24 | #endif 25 | 26 | -------------------------------------------------------------------------------- /Device_Curie.cpp: -------------------------------------------------------------------------------- 1 | #if defined(__arc__) 2 | 3 | #include "Device_Curie.h" 4 | 5 | void DeviceCurie::removeBytes(int bLen) { 6 | 7 | buffer->removeBytes(bLen); 8 | } 9 | 10 | 11 | unsigned char DeviceCurie::getByte(unsigned int pos) { 12 | 13 | return buffer->getByte(pos); 14 | } 15 | 16 | 17 | unsigned char * DeviceCurie::getPayload() { 18 | 19 | return buffer->getPayload(); 20 | } 21 | 22 | 23 | bool DeviceCurie::hasBytes(unsigned int bytes) { 24 | 25 | if (buffer->hasBytes(bytes)) { 26 | return true; 27 | } 28 | 29 | // do not loop - try to keep the buffer data queue small 30 | if (characteristic.valueUpdated()) { 31 | buffer->addBytes(characteristic.value(), characteristic.valueLength()); 32 | if (buffer->hasBytes(bytes)) { 33 | return true; 34 | } 35 | } 36 | 37 | return false; 38 | } 39 | 40 | 41 | void DeviceCurie::write(const unsigned char *payload, int len) { 42 | 43 | if (!connected) { 44 | return; 45 | } 46 | 47 | characteristic.writeValue(payload, len); 48 | } 49 | 50 | 51 | bool DeviceCurie::reset(const char * message) { 52 | 53 | Serial.println(message); 54 | 55 | if (peripheral.connected()) { 56 | peripheral.disconnect(); 57 | } 58 | 59 | connected = false; 60 | newConnection = false; 61 | buffer->reset(); 62 | BLE.scanForUuid(service); 63 | 64 | return false; 65 | } 66 | 67 | 68 | bool DeviceCurie::isNewConnection() { 69 | 70 | if (newConnection == true) { 71 | newConnection = false; 72 | return true; 73 | } 74 | 75 | return false; 76 | } 77 | 78 | 79 | bool DeviceCurie::isConnected() { 80 | 81 | if (connected && peripheral.connected()) { 82 | return true; 83 | } 84 | 85 | if (connected) { 86 | return reset("device disconnected"); 87 | } 88 | 89 | peripheral = BLE.available(); 90 | if (!peripheral) { 91 | return false; 92 | } 93 | 94 | BLE.stopScan(); 95 | 96 | if (!peripheral.connect()) { 97 | return reset("failed to connect"); 98 | } 99 | 100 | if (!peripheral.discoverAttributes()) { 101 | return reset("failed to discover attributes"); 102 | } 103 | 104 | characteristic = peripheral.characteristic(characteristic); 105 | if (!characteristic) { 106 | return reset("failed to get characteristic"); 107 | } 108 | 109 | characteristic.subscribe(); 110 | connected = true; 111 | newConnection = true; 112 | 113 | return true; 114 | } 115 | 116 | 117 | void DeviceCurie::connect() { 118 | 119 | if (connected) { 120 | return; 121 | } 122 | 123 | BLE.scanForUuid(service); 124 | } 125 | 126 | 127 | void DeviceCurie::disconnect() { 128 | 129 | if (!this->connected) { 130 | return; 131 | } 132 | 133 | reset("disconnect device"); 134 | BLE.stopScan(); 135 | } 136 | 137 | 138 | void DeviceCurie::init() { 139 | 140 | BLE.begin(); 141 | } 142 | 143 | 144 | DeviceCurie::DeviceCurie() { 145 | 146 | buffer = new Buffer(); 147 | connected = false; 148 | newConnection = false; 149 | } 150 | 151 | 152 | DeviceCurie::~DeviceCurie() { 153 | 154 | delete(buffer); 155 | } 156 | 157 | #endif 158 | 159 | -------------------------------------------------------------------------------- /Device_Curie.h: -------------------------------------------------------------------------------- 1 | #ifndef DEVICE_CURIE_H_ 2 | #define DEVICE_CURIE_H_ 3 | 4 | 5 | #include "Device.h" 6 | #include 7 | 8 | class DeviceCurie : public Device { 9 | 10 | Buffer * buffer; 11 | bool connected = false; 12 | bool newConnection = false; 13 | 14 | BLEDevice peripheral; 15 | BLECharacteristic characteristic; 16 | 17 | bool reset(const char * message); 18 | 19 | public: 20 | bool isNewConnection(); 21 | bool isConnected(); 22 | void connect(); 23 | void disconnect(); 24 | void init(); 25 | void removeBytes(int bLen); 26 | unsigned char getByte(unsigned int pos); 27 | unsigned char * getPayload(); 28 | bool hasBytes(unsigned int bytes); 29 | void write(const unsigned char *payload, int len); 30 | DeviceCurie(); 31 | ~DeviceCurie(); 32 | }; 33 | 34 | #endif 35 | 36 | -------------------------------------------------------------------------------- /Device_HM10.cpp: -------------------------------------------------------------------------------- 1 | #include "Device_HM10.h" 2 | 3 | void DeviceHM10::serialPrintf(const char *format, ...) { 4 | 5 | va_list args; 6 | char buffer[100]; 7 | 8 | va_start (args, format); 9 | vsnprintf (buffer, sizeof(buffer), format, args); 10 | buffer[sizeof(buffer) - 1] = '\0'; 11 | 12 | serial->print(buffer); 13 | } 14 | 15 | 16 | void DeviceHM10::removeBytes(int bLen) { 17 | 18 | buffer->removeBytes(bLen); 19 | checkConnectionStatus(); 20 | } 21 | 22 | 23 | unsigned char DeviceHM10::getByte(unsigned int pos) { 24 | 25 | return buffer->getByte(pos); 26 | } 27 | 28 | 29 | unsigned char * DeviceHM10::getPayload() { 30 | 31 | return buffer->getPayload(); 32 | } 33 | 34 | 35 | void DeviceHM10::dump(const char * msg, const unsigned char * payload, size_t len) { 36 | 37 | Serial.print(msg); 38 | 39 | for (unsigned int i = 0; i < len; i++) { 40 | char buf[4]; 41 | snprintf(buf, sizeof(buf), "%.2X", payload[i]); 42 | Serial.print(buf); 43 | } 44 | 45 | Serial.println(""); 46 | } 47 | 48 | 49 | bool DeviceHM10::isDeviceConnected() { 50 | 51 | return status == STATUS_CONNECTED || status == STATUS_DISCONNECTING; 52 | } 53 | 54 | 55 | bool DeviceHM10::checkConnectionStatus() { 56 | 57 | bool connectionStatus = DeviceHM10::isDeviceConnected(); 58 | 59 | if (buffer->getLen() < 3) { 60 | int count = serial->readBytes(buffer->getPayload() + buffer->getLen(), 3); 61 | buffer->addByteCount(count); 62 | if (count < 3) { 63 | return connectionStatus; 64 | } 65 | } 66 | 67 | unsigned char * payload = buffer->getPayload(); 68 | // OK+CONNX / OK+LOST 69 | if (payload[0] != 'O' || payload[1] != 'K' || payload[2] != '+') { 70 | return connectionStatus; 71 | } 72 | 73 | 74 | if (buffer->getLen() < 7) { 75 | int need = 8 - buffer->getLen(); 76 | int count = serial->readBytes(buffer->getPayload() + buffer->getLen(), need); 77 | buffer->addByteCount(count); 78 | if (buffer->getLen() < 7) { 79 | return connectionStatus; 80 | } 81 | } 82 | 83 | if (buffer->getLen() > 7) { 84 | if ( memcmp(buffer->getPayload(), "OK+CONNE", 8) == 0 || 85 | memcmp(buffer->getPayload(), "OK+CONNF", 8) == 0 86 | ) { 87 | 88 | if (status == STATUS_CONNECTING) { 89 | status = STATUS_INITIALIZING; 90 | } 91 | else { 92 | status = STATUS_DISCONNECTED; 93 | } 94 | buffer->removeBytes(8); 95 | return connectionStatus; 96 | } 97 | 98 | if (memcmp(buffer->getPayload(), "OK+CONNA", 8) == 0) { 99 | buffer->removeBytes(8); 100 | return connectionStatus; 101 | } 102 | } 103 | 104 | if (memcmp(buffer->getPayload(), "OK+CONN", 7) == 0) { 105 | buffer->removeBytes(7); 106 | newConnection = true; 107 | status = STATUS_CONNECTED; 108 | Serial.println("device connected"); 109 | return true; 110 | } 111 | 112 | // BAD: assuming it is OK+LOST 113 | return reset("device disconnected"); 114 | } 115 | 116 | 117 | bool DeviceHM10::hasBytes(unsigned int bytes) { 118 | 119 | if (buffer->hasBytes(bytes)) { 120 | return true; 121 | } 122 | 123 | int bAvailable = bytes - buffer->getLen(); 124 | 125 | 126 | // do not loop - try to keep the buffer data queue small 127 | if (serial->available()) { 128 | if (bAvailable > buffer->getFreeLen()) { 129 | bAvailable = buffer->getFreeLen(); 130 | } 131 | 132 | int total = serial->readBytes(buffer->getPayload() + buffer->getLen(), bAvailable); 133 | if (total > 0) { 134 | buffer->addByteCount(total); 135 | } 136 | } 137 | 138 | if (!checkConnectionStatus()) { 139 | return false; 140 | } 141 | 142 | if (buffer->hasBytes(bytes)) { 143 | return true; 144 | } 145 | 146 | return false; 147 | } 148 | 149 | 150 | void DeviceHM10::write(const unsigned char *payload, int len) { 151 | 152 | if (status != STATUS_CONNECTED) { 153 | return; 154 | } 155 | 156 | serial->write(payload, len); 157 | } 158 | 159 | 160 | bool DeviceHM10::reset(const char * message) { 161 | 162 | Serial.println(message); 163 | 164 | if (status == STATUS_DISCONNECTING) { 165 | status = STATUS_DISCONNECTED; 166 | } 167 | else { 168 | status = STATUS_INITIALIZING; 169 | } 170 | 171 | newConnection = false; 172 | serial->flush(); 173 | buffer->reset(); 174 | 175 | // purge serial buffer 176 | while (serial->available()) { 177 | int val = serial->read(); 178 | } 179 | 180 | return false; 181 | } 182 | 183 | 184 | bool DeviceHM10::isNewConnection() { 185 | 186 | if (newConnection == true) { 187 | newConnection = false; 188 | return true; 189 | } 190 | 191 | return false; 192 | } 193 | 194 | 195 | bool DeviceHM10::isConnected() { 196 | 197 | if (status == STATUS_CONNECTED) { 198 | return true; 199 | } 200 | else if (status == STATUS_DISCONNECTED) { 201 | return false; 202 | } 203 | 204 | if (status == STATUS_INITIALIZING) { 205 | if (!sendCommand("CON", mac)) return false; 206 | status = STATUS_CONNECTING; 207 | } 208 | 209 | if (status == STATUS_DISCONNECTING) { 210 | // Empty AT command disconnects the device 211 | serial->print("AT"); 212 | } 213 | 214 | return checkConnectionStatus(); 215 | } 216 | 217 | 218 | bool DeviceHM10::sendCommand(const char *cmd, const char *value) { 219 | 220 | char buffer[40]; 221 | char expected[40]; 222 | int count = 0; 223 | 224 | while (count < 5) { 225 | delay(count * 100); 226 | count++; 227 | 228 | // purge serial buffer 229 | while (serial->available()) { 230 | int val = serial->read(); 231 | } 232 | 233 | if (cmd == NULL || strlen(cmd) == 0) { 234 | serialPrintf("AT"); 235 | snprintf(expected, sizeof(expected), "OK"); 236 | } 237 | else { 238 | serialPrintf("AT+%s%s", cmd, value); 239 | 240 | if (value == NULL || strlen(value) == 0) { 241 | snprintf(expected, sizeof(expected), "OK+%s", cmd); 242 | } 243 | else if (cmd == "CON") { 244 | return true; 245 | //snprintf(expected, sizeof(expected), "OK+CONNA"); 246 | } 247 | else { 248 | snprintf(expected, sizeof(expected), "OK+Set:%s", value); 249 | } 250 | } 251 | 252 | int total = serial->readBytes(buffer, strlen(expected)); 253 | 254 | if (total >= 0) { 255 | // read extra bytes still in buffer 256 | while (serial->available() && (total < sizeof(buffer))) { 257 | buffer[total++] = serial->read(); 258 | } 259 | 260 | buffer[total] = '\0'; 261 | } 262 | 263 | if (total <= 0) { 264 | continue; 265 | } 266 | 267 | if (strcmp(buffer, expected) != 0) { 268 | Serial.print("command "); 269 | Serial.print(cmd); 270 | Serial.print(" failed: "); 271 | Serial.println(buffer); 272 | Serial.print("expected: "); 273 | Serial.println(expected); 274 | 275 | return false; 276 | } 277 | 278 | return true; 279 | } 280 | 281 | Serial.print("failed to get answer for command "); 282 | Serial.println(cmd); 283 | 284 | return false; 285 | } 286 | 287 | 288 | void DeviceHM10::connect() { 289 | 290 | if (status == STATUS_CONNECTED || status == STATUS_INITIALIZING || status == STATUS_CONNECTING) { 291 | return; 292 | } 293 | 294 | status = STATUS_INITIALIZING; 295 | // TODO: Do discovery 296 | } 297 | 298 | 299 | void DeviceHM10::disconnect() { 300 | 301 | if (status == STATUS_DISCONNECTED) { 302 | return; 303 | } 304 | 305 | if (status == STATUS_INITIALIZING) { 306 | status = STATUS_DISCONNECTED; 307 | return; 308 | } 309 | 310 | status = STATUS_DISCONNECTING; 311 | } 312 | 313 | 314 | boolean DeviceHM10::initDevice() { 315 | if (!sendCommand("", "")) return false; 316 | if (!sendCommand("RENEW", "")) return false; 317 | if (!sendCommand("IMME", "1")) return false; 318 | if (!sendCommand("MODE", "1")) return false; 319 | if (!sendCommand("COMP", "1")) return false; 320 | if (!sendCommand("NOTI", "1")) return false; 321 | if (!sendCommand("UUID", "0x1800")) return false; 322 | if (!sendCommand("CHAR", "0x2A80")) return false; 323 | if (!sendCommand("ROLE", "1")) return false; 324 | delay(1000); 325 | 326 | return true; 327 | } 328 | 329 | 330 | void DeviceHM10::init() { 331 | 332 | #if defined(HAVE_HWSERIAL1) 333 | serial = &Serial1; 334 | #else 335 | //serial = new SoftwareSerial(TX_PIN, RX_PIN); 336 | serial = new AltSoftSerial(TX_PIN, RX_PIN); 337 | #endif 338 | 339 | serial->begin(9600); 340 | 341 | while (!initDevice()) { 342 | } 343 | } 344 | 345 | 346 | DeviceHM10::DeviceHM10() { 347 | 348 | buffer = new Buffer(); 349 | newConnection = false; 350 | status = STATUS_DISCONNECTED; 351 | } 352 | 353 | 354 | DeviceHM10::~DeviceHM10() { 355 | 356 | delete(buffer); 357 | } 358 | 359 | 360 | -------------------------------------------------------------------------------- /Device_HM10.h: -------------------------------------------------------------------------------- 1 | #ifndef DEVICE_HM10_H_ 2 | #define DEVICE_HM10_H_ 3 | 4 | #include 5 | #include "Device.h" 6 | 7 | enum ConnectionStatus { 8 | STATUS_DISCONNECTED, 9 | STATUS_INITIALIZING, 10 | STATUS_CONNECTING, 11 | STATUS_CONNECTED, 12 | STATUS_DISCONNECTING 13 | }; 14 | 15 | #if defined(HAVE_HWSERIAL1) 16 | #include 17 | #else 18 | // NOTE: The SoftwareSerial is too slow and the notifications too fast 19 | // every now and then the internal circular buffer (64bytes max) is 20 | // full and some data is overwritten/lost 21 | // we end up with invalid packet. 22 | // The DeviceHM10 implementation can recover from that - 1 byte is removed 23 | // at a time until the correct header is found. 24 | // #include 25 | 26 | // AltSoftSerial uses two separate (non circular) buffer or RX (80 bytes) and RX 27 | // It is faster than SoftwareSerial and does not seem to lose packets 28 | 29 | #include 30 | 31 | #define TX_PIN 8 32 | #define RX_PIN 9 33 | #endif 34 | 35 | class DeviceHM10 : public Device { 36 | 37 | Buffer * buffer; 38 | bool connected = false; 39 | bool newConnection = false; 40 | 41 | int status; 42 | 43 | const char * mac = "001C9714F68E"; 44 | 45 | #if defined(HAVE_HWSERIAL1) 46 | HardwareSerial * serial = NULL; 47 | #else 48 | //SoftwareSerial * serial = NULL; 49 | AltSoftSerial * serial = NULL; 50 | #endif 51 | 52 | bool reset(const char * message); 53 | void serialPrintf(const char *format, ...); 54 | bool sendCommand(const char *cmd, const char *value); 55 | bool isDeviceConnected(); 56 | bool checkConnectionStatus(); 57 | boolean initDevice(); 58 | 59 | public: 60 | bool isNewConnection(); 61 | bool isConnected(); 62 | void connect(); 63 | void disconnect(); 64 | void init(); 65 | void dump(const char * msg, const unsigned char * payload, size_t len); 66 | void removeBytes(int bLen); 67 | unsigned char getByte(unsigned int pos); 68 | unsigned char * getPayload(); 69 | bool hasBytes(unsigned int bytes); 70 | void write(const unsigned char *payload, int len); 71 | DeviceHM10(); 72 | ~DeviceHM10(); 73 | }; 74 | 75 | #endif 76 | 77 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | Copyright (c) 2017 Nicolas Pouvesle 2 | 3 | Permission is hereby granted, free of charge, to any person obtaining a copy 4 | of this software and associated documentation files (the "Software"), to deal 5 | in the Software without restriction, including without limitation the rights 6 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 7 | copies of the Software, and to permit persons to whom the Software is 8 | furnished to do so, subject to the following conditions: 9 | 10 | The above copyright notice and this permission notice shall be included in all 11 | copies or substantial portions of the Software. 12 | 13 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 14 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 15 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 16 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 17 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 18 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 19 | SOFTWARE. -------------------------------------------------------------------------------- /Scale.cpp: -------------------------------------------------------------------------------- 1 | #include "Scale.h" 2 | 3 | #include 4 | 5 | #if defined (__arc__) 6 | #include "Device_Curie.h" 7 | #else 8 | #include "Device_HM10.h" 9 | #endif 10 | 11 | #define HEADER1 0xef 12 | #define HEADER2 0xdd 13 | 14 | #define MSG_SYSTEM 0 15 | #define MSG_TARE 4 16 | #define MSG_INFO 7 17 | #define MSG_STATUS 8 18 | #define MSG_IDENTIFY 11 19 | #define MSG_EVENT 12 20 | #define MSG_TIMER 13 21 | 22 | #define EVENT_WEIGHT 5 23 | #define EVENT_BATTERY 6 24 | #define EVENT_TIMER 7 25 | #define EVENT_KEY 8 26 | #define EVENT_ACK 11 27 | 28 | 29 | #define EVENT_WEIGHT_LEN 6 30 | #define EVENT_BATTERY_LEN 1 31 | #define EVENT_TIMER_LEN 3 32 | #define EVENT_KEY_LEN 1 33 | #define EVENT_ACK_LEN 2 34 | 35 | #define TIMER_START 0 36 | #define TIMER_STOP 1 37 | #define TIMER_PAUSE 2 38 | 39 | #define READ_HEADER 0 40 | #define READ_DATA 1 41 | 42 | #define HEADER_SIZE 3 43 | 44 | #define TIMER_STATE_STOPPED 0 45 | #define TIMER_STATE_STARTED 1 46 | #define TIMER_STATE_PAUSED 2 47 | 48 | void Scale::printf(const char *format, ...) { 49 | 50 | va_list args; 51 | char buffer[100]; 52 | 53 | va_start (args, format); 54 | vsnprintf (buffer, sizeof(buffer), format, args); 55 | buffer[sizeof(buffer) - 1] = '\0'; 56 | 57 | Serial.print(buffer); 58 | } 59 | 60 | 61 | void Scale::sendMessage(char msgType, const unsigned char *payload, size_t len) { 62 | 63 | // TODO: malloc can return NULL 64 | unsigned char *bytes = (unsigned char *)malloc(5 + len); 65 | unsigned char cksum1 = 0; 66 | unsigned char cksum2 = 0; 67 | unsigned int i; 68 | 69 | bytes[0] = HEADER1; 70 | bytes[1] = HEADER2; 71 | bytes[2] = msgType; 72 | 73 | 74 | for (i = 0; i < len; i++) { 75 | unsigned char val = payload[i] & 0xff; 76 | bytes[3+i] = val; 77 | if (i % 2 == 0) { 78 | cksum1 += val; 79 | } 80 | else { 81 | cksum2 += val; 82 | } 83 | } 84 | 85 | bytes[len + 3] = (cksum1 & 0xFF); 86 | bytes[len + 4] = (cksum2 & 0xFF); 87 | 88 | 89 | device->write(bytes, len + 5); 90 | free(bytes); 91 | } 92 | 93 | 94 | void Scale::sendEvent(unsigned char *payload, size_t len) { 95 | 96 | unsigned int i; 97 | // TODO: malloc can return NULL 98 | unsigned char *bytes = (unsigned char*)malloc(len + 1); 99 | bytes[0] = len + 1; 100 | 101 | for (i = 0; i < len; i++) { 102 | bytes[i+1] = payload[i] & 0xff; 103 | } 104 | 105 | sendMessage(MSG_EVENT, bytes, len+1); 106 | free(bytes); 107 | } 108 | 109 | 110 | void Scale::sendHeartbeat() { 111 | 112 | unsigned long now = millis(); 113 | 114 | if (lastHeartbeat + 3000 > now) { 115 | return; 116 | } 117 | 118 | unsigned char payload[] = {0x02,0x00}; 119 | sendMessage(MSG_SYSTEM, payload, sizeof(payload)); 120 | this->lastHeartbeat = now; 121 | } 122 | 123 | 124 | void Scale::sendTare() { 125 | 126 | unsigned char payload[] = {0x00}; 127 | sendMessage(MSG_TARE, payload, sizeof(payload)); 128 | } 129 | 130 | 131 | void Scale::sendTimerCommand(unsigned char command) { 132 | 133 | unsigned char payload[] = {0x00, command}; 134 | sendMessage(MSG_TIMER, payload, sizeof(payload)); 135 | } 136 | 137 | 138 | void Scale::sendId() { 139 | 140 | unsigned char payload[] = {0x2d,0x2d,0x2d,0x2d,0x2d,0x2d,0x2d,0x2d,0x2d,0x2d,0x2d,0x2d,0x2d,0x2d,0x2d}; 141 | sendMessage(MSG_IDENTIFY, payload, sizeof(payload)); 142 | } 143 | 144 | 145 | void Scale::sendNotificationRequest() { 146 | 147 | unsigned char payload[] = { 148 | 0, // weight 149 | 1, // weight argument 150 | 1, // battery 151 | 2, // battery argument 152 | 2, // timer 153 | 5, // timer argument 154 | 3, // key 155 | 4 // setting 156 | }; 157 | 158 | sendEvent(payload, sizeof(payload)); 159 | this->ready = true; 160 | } 161 | 162 | 163 | void dump(const char * msg, const unsigned char * payload, size_t len) { 164 | 165 | Serial.print(msg); 166 | 167 | for (unsigned int i = 0; i < len; i++) { 168 | char buf[4]; 169 | snprintf(buf, sizeof(buf), "%.2X", payload[i]); 170 | Serial.print(buf); 171 | } 172 | 173 | Serial.println(""); 174 | } 175 | 176 | 177 | int Scale::parseWeightEvent(unsigned char *payload, size_t len) { 178 | 179 | if (len < EVENT_WEIGHT_LEN) { 180 | dump("Invalid weight event length: ", payload, len); 181 | return -1; 182 | } 183 | 184 | float value = (unsigned int) (((payload[1] & 0xff) << 8) + (payload[0] & 0xff)); 185 | int unit = payload[4] & 0xFF; 186 | 187 | if (unit == 1) { 188 | value /= 10; 189 | } 190 | else if (unit == 2) { 191 | value /= 100; 192 | } 193 | else if (unit == 3) { 194 | value /= 1000; 195 | } 196 | else if (unit == 4) { 197 | value /= 10000; 198 | } 199 | 200 | if ((payload[5] & 0x02) == 0x02) { 201 | value *= -1; 202 | } 203 | 204 | this->weight = value; 205 | this->weightHasChanged = true; 206 | 207 | return EVENT_WEIGHT_LEN; 208 | } 209 | 210 | 211 | int Scale::parseAckEvent(unsigned char *payload, size_t len) { 212 | 213 | if (len < EVENT_ACK_LEN) { 214 | dump("Invalid ack event length: ", payload, len); 215 | return -1; 216 | } 217 | 218 | // ignore ack 219 | 220 | return EVENT_ACK_LEN; 221 | } 222 | 223 | 224 | int Scale::parseKeyEvent(unsigned char *payload, size_t len) { 225 | 226 | if (len < EVENT_KEY_LEN) { 227 | dump("Invalid ack event length: ", payload, len); 228 | return -1; 229 | } 230 | 231 | // ignore key event 232 | 233 | return EVENT_KEY_LEN; 234 | } 235 | 236 | 237 | int Scale::parseBatteryEvent(unsigned char *payload, size_t len) { 238 | 239 | if (len < EVENT_BATTERY_LEN) { 240 | dump("Invalid battery event length: ", payload, len); 241 | return -1; 242 | } 243 | 244 | this->battery = payload[0]; 245 | 246 | return EVENT_BATTERY_LEN; 247 | } 248 | 249 | 250 | int Scale::parseTimerEvent(unsigned char *payload, size_t len) { 251 | 252 | if (len < EVENT_TIMER_LEN) { 253 | dump("Invalid timer event length: ", payload, len); 254 | return -1; 255 | } 256 | 257 | this->minutes = payload[0]; 258 | this->seconds = payload[1]; 259 | this->mseconds = payload[2]; 260 | 261 | return EVENT_TIMER_LEN; 262 | } 263 | 264 | 265 | // returns last position in payload 266 | int Scale::parseScaleEvent(unsigned char *payload, size_t len) { 267 | 268 | int event = payload[0]; 269 | unsigned char *ptr = payload + 1; 270 | size_t ptrLen = len - 1; 271 | int val = 0; 272 | 273 | switch(event) { 274 | 275 | case EVENT_WEIGHT: 276 | val = parseWeightEvent(ptr, ptrLen); 277 | break; 278 | 279 | case EVENT_BATTERY: 280 | val = parseBatteryEvent(ptr, ptrLen); 281 | break; 282 | 283 | case EVENT_TIMER: 284 | val = parseTimerEvent(ptr, ptrLen); 285 | break; 286 | 287 | case EVENT_ACK: 288 | val = parseAckEvent(ptr, ptrLen); 289 | break; 290 | 291 | case EVENT_KEY: 292 | val = parseKeyEvent(ptr, ptrLen); 293 | break; 294 | 295 | default: 296 | dump("Unknown event: ", payload, len); 297 | return -1; 298 | } 299 | 300 | if (val < 0) { 301 | return -1; 302 | } 303 | 304 | return val + 1; 305 | } 306 | 307 | 308 | int Scale::parseScaleEvents(unsigned char *payload, size_t len) { 309 | 310 | unsigned int lastPos = 0; 311 | while (lastPos < len) { 312 | int pos = parseScaleEvent(payload + lastPos, len - lastPos); 313 | if (pos < 0) { 314 | return -1; 315 | } 316 | 317 | lastPos += pos; 318 | } 319 | 320 | return 0; 321 | } 322 | 323 | 324 | int Scale::parseInfo(unsigned char *payload, size_t len) { 325 | 326 | this->battery = payload[4]; 327 | // TODO parse other infos 328 | 329 | return 0; 330 | } 331 | 332 | 333 | int Scale::parseScaleData(unsigned char *payload, size_t len) { 334 | 335 | int ret = 0; 336 | 337 | switch(msgType) { 338 | case MSG_INFO: 339 | ret = parseInfo(payload, len); 340 | sendId(); 341 | break; 342 | 343 | case MSG_STATUS: 344 | if (device->isNewConnection()) { 345 | sendNotificationRequest(); 346 | } 347 | break; 348 | 349 | case MSG_EVENT: 350 | ret = parseScaleEvents(payload, len); 351 | break; 352 | 353 | default: 354 | break; 355 | } 356 | 357 | return ret; 358 | } 359 | 360 | 361 | void Scale::update() { 362 | 363 | unsigned char * header = NULL; 364 | 365 | if (!device->isConnected()) { 366 | return; 367 | } 368 | 369 | sendHeartbeat(); 370 | 371 | while (1) { 372 | 373 | if (state == READ_HEADER) { 374 | if (!device->hasBytes(HEADER_SIZE)) { 375 | return; 376 | } 377 | 378 | header = device->getPayload(); 379 | if (header[0] != HEADER1 || header[1] != HEADER2) { 380 | dump("invalid header: ", header, HEADER_SIZE); 381 | device->removeBytes(1); 382 | continue; 383 | } 384 | 385 | msgType = header[2]; 386 | device->removeBytes(HEADER_SIZE); 387 | state = READ_DATA; 388 | } 389 | else { 390 | if (!device->hasBytes(1)) { 391 | return; 392 | } 393 | 394 | unsigned char len = 0; 395 | unsigned char offset = 0; 396 | 397 | if (msgType == MSG_STATUS || msgType == MSG_EVENT || msgType == MSG_INFO) { 398 | len = device->getByte(0); 399 | if (len == 0) { 400 | len = 1; 401 | } 402 | offset = 1; 403 | } 404 | else { 405 | switch(msgType) { 406 | case 0: 407 | len = 2; 408 | break; 409 | 410 | default: 411 | len = 0; 412 | } 413 | } 414 | 415 | if (!device->hasBytes(len + 2)) { 416 | return; 417 | } 418 | 419 | parseScaleData(device->getPayload() + offset, len - offset); 420 | device->removeBytes(len + 2); 421 | state = READ_HEADER; 422 | } 423 | } 424 | } 425 | 426 | 427 | void Scale::connect() { 428 | 429 | device->connect(); 430 | } 431 | 432 | 433 | void Scale::disconnect() { 434 | 435 | device->disconnect(); 436 | } 437 | 438 | 439 | bool Scale::tare() { 440 | 441 | if (!ready) { 442 | return false; 443 | } 444 | 445 | sendTare(); 446 | return true; 447 | } 448 | 449 | 450 | // TODO:: track internal scale state to prevent sending 451 | // multiple start/stop commands. That seems to cause some 452 | // issues with the packets received from the scale (BAD) 453 | 454 | bool Scale::startTimer() { 455 | 456 | if (!ready) { 457 | return false; 458 | } 459 | 460 | if (timerState == TIMER_STATE_STARTED) { 461 | return false; 462 | } 463 | 464 | sendTimerCommand(TIMER_START); 465 | timerState = TIMER_STATE_STARTED; 466 | 467 | return true; 468 | } 469 | 470 | 471 | bool Scale::stopTimer() { 472 | 473 | if (!ready) { 474 | return false; 475 | } 476 | 477 | if (timerState == TIMER_STATE_STOPPED) { 478 | return false; 479 | } 480 | 481 | sendTimerCommand(TIMER_STOP); 482 | timerState = TIMER_STATE_STOPPED; 483 | 484 | return true; 485 | } 486 | 487 | 488 | bool Scale::pauseTimer() { 489 | 490 | if (!ready) { 491 | return false; 492 | } 493 | 494 | 495 | if (timerState == TIMER_STATE_PAUSED) { 496 | return false; 497 | } 498 | 499 | sendTimerCommand(TIMER_PAUSE); 500 | timerState = TIMER_STATE_PAUSED; 501 | 502 | return true; 503 | } 504 | 505 | 506 | bool Scale::hasWeightChanged() { 507 | 508 | return weightHasChanged; 509 | } 510 | 511 | 512 | float Scale::getWeight() { 513 | 514 | weightHasChanged = false; 515 | return this->weight; 516 | } 517 | 518 | 519 | unsigned char Scale::getBattery() { 520 | 521 | return this->battery; 522 | } 523 | 524 | 525 | unsigned char Scale::getSeconds() { 526 | 527 | return this->seconds; 528 | } 529 | 530 | 531 | Scale::Scale() { 532 | 533 | this->state = READ_HEADER; 534 | this->ready = false; 535 | this->weight = 0; 536 | this->weightHasChanged = false; 537 | this->battery = 0; 538 | this->lastHeartbeat = 0; 539 | this->timerState = TIMER_STATE_STOPPED; 540 | 541 | this->minutes = 0; 542 | this->seconds = 0; 543 | this->mseconds = 0; 544 | 545 | #if defined (__arc__) 546 | device = (Device *)new DeviceCurie(); 547 | #else 548 | device = (Device *)new DeviceHM10(); 549 | #endif 550 | 551 | device->init(); 552 | } 553 | 554 | 555 | -------------------------------------------------------------------------------- /Scale.h: -------------------------------------------------------------------------------- 1 | #ifndef SCALE_H 2 | #define SCALE_H 3 | 4 | #include "Device.h" 5 | #include 6 | 7 | class Scale { 8 | 9 | float weight; 10 | bool weightHasChanged; 11 | 12 | Device *device; 13 | int state; 14 | int msgType; 15 | 16 | unsigned char battery; 17 | unsigned char minutes; 18 | unsigned char seconds; 19 | unsigned char mseconds; 20 | 21 | int timerState; 22 | 23 | bool connected; 24 | bool ready; 25 | unsigned long lastHeartbeat; 26 | 27 | void printf(const char *format, ...); 28 | void sendMessage(char msgType, const unsigned char *payload, size_t len); 29 | void sendEvent(unsigned char *payload, size_t len); 30 | void sendHeartbeat(); 31 | void sendTare(); 32 | void sendId(); 33 | void sendNotificationRequest(); 34 | void sendTimerCommand(unsigned char command); 35 | int parseAckEvent(unsigned char *payload, size_t len); 36 | int parseWeightEvent(unsigned char *payload, size_t len); 37 | int parseBatteryEvent(unsigned char *payload, size_t len); 38 | int parseTimerEvent(unsigned char *payload, size_t len); 39 | int parseKeyEvent(unsigned char *payload, size_t len); 40 | int parseScaleEvent(unsigned char *payload, size_t len); 41 | int parseScaleEvents(unsigned char *payload, size_t len); 42 | int parseInfo(unsigned char *payload, size_t len); 43 | int parseScaleData(unsigned char *payload, size_t len); 44 | bool isConnected(); 45 | bool reset(const char * message); 46 | bool hasBytes(unsigned int bytes); 47 | 48 | public: 49 | bool hasWeightChanged(); 50 | float getWeight(); 51 | unsigned char getBattery(); 52 | unsigned char getSeconds(); 53 | void update(); 54 | void connect(); 55 | void disconnect(); 56 | bool tare(); 57 | bool startTimer(); 58 | bool pauseTimer(); 59 | bool stopTimer(); 60 | Scale(); 61 | }; 62 | 63 | #endif 64 | 65 | 66 | -------------------------------------------------------------------------------- /Scale.ino: -------------------------------------------------------------------------------- 1 | #include "Scale.h" 2 | 3 | Scale *scale = NULL; 4 | unsigned long startTime; 5 | int state = 0; 6 | bool isRunning = false; 7 | 8 | void setup() 9 | { 10 | Serial.begin(9600); 11 | scale = new Scale(); 12 | scale->connect(); 13 | } 14 | 15 | 16 | void loop() { 17 | 18 | scale->update(); 19 | if (scale->hasWeightChanged()) { 20 | Serial.print("battery: "); 21 | Serial.println(scale->getBattery()); 22 | Serial.print("weight: "); 23 | Serial.println(scale->getWeight()); 24 | Serial.print("seconds: "); 25 | Serial.println(scale->getSeconds()); 26 | 27 | if (state == 0) { 28 | Serial.println("starting shot"); 29 | if (scale->tare()) { 30 | scale->startTimer(); 31 | state++; 32 | } 33 | } 34 | 35 | if (state == 1 && scale->getSeconds() >= 5) { 36 | Serial.println("stopping shot"); 37 | scale->pauseTimer(); 38 | state++; 39 | startTime = millis(); 40 | } 41 | 42 | if (state == 2 && (startTime + 5000) < millis()) { 43 | scale->stopTimer(); 44 | //scale->disconnect(); 45 | state++; 46 | } 47 | } 48 | 49 | delay(1); 50 | } 51 | 52 | 53 | --------------------------------------------------------------------------------