Repository: vojtamolda/homebridge-bluetooth Branch: master Commit: fa3c44748900 Files: 65 Total size: 369.6 KB Directory structure: gitextract_nhq2hgc9/ ├── config.json ├── examples/ │ ├── humidity/ │ │ ├── arduino101/ │ │ │ ├── Si7021.cpp │ │ │ ├── Si7021.h │ │ │ ├── arduino101.fzz │ │ │ └── arduino101.ino │ │ ├── bluefruit/ │ │ │ ├── Si7021.cpp │ │ │ ├── Si7021.h │ │ │ ├── bluefruit.fzz │ │ │ └── bluefruit.ino │ │ ├── config.json │ │ ├── images/ │ │ │ └── humidity.psd │ │ └── readme.md │ ├── lightbulb/ │ │ ├── arduino101/ │ │ │ ├── arduino101.fzz │ │ │ └── arduino101.ino │ │ ├── config.json │ │ ├── images/ │ │ │ └── lightbulb.psd │ │ └── readme.md │ ├── lightbulb-rgb/ │ │ ├── arduino101/ │ │ │ ├── arduino101.fzz │ │ │ └── arduino101.ino │ │ ├── config.json │ │ ├── images/ │ │ │ └── lightbulb-rgb.psd │ │ └── readme.md │ ├── lock/ │ │ ├── arduino101/ │ │ │ ├── arduino101.ino │ │ │ ├── arduino101.sch │ │ │ ├── dummy/ │ │ │ │ └── dummy.ino │ │ │ ├── lock.cpp │ │ │ ├── lock.h │ │ │ ├── motor.cpp │ │ │ ├── motor.h │ │ │ ├── sensor.cpp │ │ │ └── sensor.h │ │ ├── config.json │ │ ├── images/ │ │ │ └── lock.psd │ │ ├── readme.md │ │ └── rfduino/ │ │ ├── lock.cpp │ │ ├── lock.h │ │ ├── motor.cpp │ │ ├── motor.h │ │ ├── rfduino.ino │ │ ├── rfduino.sch │ │ ├── sensor.cpp │ │ └── sensor.h │ ├── readme.md │ ├── switch/ │ │ ├── arduino101/ │ │ │ ├── arduino101.fzz │ │ │ └── arduino101.ino │ │ ├── config.json │ │ ├── images/ │ │ │ └── switch.psd │ │ └── readme.md │ └── thermometer/ │ ├── arduino101/ │ │ ├── arduino101.fzz │ │ └── arduino101.ino │ ├── bluefruit/ │ │ ├── bluefruit.fzz │ │ └── bluefruit.ino │ ├── config.json │ ├── images/ │ │ └── thermometer.psd │ └── readme.md ├── images/ │ ├── examples.psd │ └── overview.psd ├── index.js ├── license.txt ├── package.json ├── readme.md └── source/ ├── accessory.js ├── characteristic.js ├── platform.js └── service.js ================================================ FILE CONTENTS ================================================ ================================================ FILE: config.json ================================================ { "bridge": { "name": "Raspberry Pi 3", "username": "CC:22:3D:E3:CE:30", "port": 51826, "pin": "031-45-154" }, "description": "Raspberry Pi 3 Homebridge-Bluetooth Example", "platforms": [ { "platform": "Bluetooth", "accessories": [ { "name": "Arduino 101", "name_note": "Name of the accessory as shown in the Home app on iOS.", "address": "01:23:45:67:89:AB", "address_note": "Bluetooth address of the accessory. Non-matching devices are ignored.", "services": [ { "name": "LED 13", "name_note": "Name of the service as shown in the Home app on iOS.", "type": "Lightbulb", "type_note1": "Type of the service - i.e. Lightbulb, Switch, Lock, HumiditySensor, ...", "type_note2": "Must match this list - https://github.com/KhaosT/HAP-NodeJS/blob/master/lib/gen/HomeKitTypes.js", "type_note3": "Service.Lightbulb has a mandatory On characteristic and Brightness, Hue and Saturation are optional.", "UUID": "A7B10010-EEEE-5377-FF6C-D104768A1214", "UUID_note": "Bluetooth UUID of the service. Capitalization and dashes doesn't matter.", "characteristics": [ { "type": "On", "type_note1": "Type of the characteristic - i.e. On, Brightness, CurrentHumidity, CurrentTemperature, ...", "type_note2": "Must match this list - https://github.com/KhaosT/HAP-NodeJS/blob/master/lib/gen/HomeKitTypes.js", "type_note3": "Characteristic.On is a BOOL value and expects READ, WRITE and NOTIFY permissions.", "UUID": "A7B10010-EEEE-5377-FF6C-D104768A1214", "UUID_note": "Bluetooth UUID of the characteristic. Capitalization and dashes doesn't matter." }, { "type": "Brightness", "type_note1": "Type of the characteristic - i.e. On, Brightness, CurrentHumidity, CurrentTemperature, ...", "type_note2": "Must match this list - https://github.com/KhaosT/HAP-NodeJS/blob/master/lib/gen/HomeKitTypes.js", "type_note3": "Characteristic.Brightness is an INT value and expects READ, WRITE and NOTIFY permissions.", "UUID": "A7B10012-EEEE-5377-FF6C-D104768A1214", "UUID_note": "Bluetooth UUID of the characteristic. Capitalization and dashes doesn't matter." } ], "characteristics_note1": "List of Bluetooth characteristics that will be exposed to HomeKit.", "characteristics_note2": "Characteristics with non-matching UUIDs are ignored." } ], "services_note1": "List of Bluetooth services that will be exposed to HomeKit.", "services_note2": "Services with non-matching UUIDs are ignored." } ] } ] } ================================================ FILE: examples/humidity/arduino101/Si7021.cpp ================================================ /************************************************************************** @file Si7021.cpp @author Limor Fried (Adafruit Industries), Vojta Molda @license BSD (see license.txt) This is a library for the Adafruit Si7021 breakout board ----> https://www.adafruit.com/products/3251 Adafruit invests time and resources providing this open source code, please support Adafruit and open-source hardware by purchasing products from Adafruit! @section HISTORY v1.0 - First release v1.1 - Modified to use **************************************************************************/ #include "Si7021.h" Si7021::Si7021(SoftwareWire& Wire_) : sernum_a(0), sernum_b(0), Wire(Wire_) { } bool Si7021::begin(void) { Wire.begin(); reset(); if (readRegister8(Si7021::Cmd.READRHT_REG) != 0x3A) return false; readSerialNumber(); return true; } float Si7021::readHumidity(void) { Wire.beginTransmission(Si7021::Cmd.ADDRESS); Wire.write(Si7021::Cmd.MEASRH_NOHOLD); Wire.endTransmission(false); delay(25); Wire.requestFrom(Si7021::Cmd.ADDRESS, (uint8_t)3); uint16_t hum = Wire.read(); hum <<= 8; hum |= Wire.read(); uint8_t chxsum = Wire.read(); float humidity = hum; humidity *= 125; humidity /= 65536; humidity -= 6; return humidity; } float Si7021::readTemperature(void) { Wire.beginTransmission(Si7021::Cmd.ADDRESS); Wire.write(Si7021::Cmd.MEASTEMP_NOHOLD); Wire.endTransmission(false); delay(25); Wire.requestFrom(Si7021::Cmd.ADDRESS, (uint8_t)3); uint16_t temp = Wire.read(); temp <<= 8; temp |= Wire.read(); uint8_t chxsum = Wire.read(); float temperature = temp; temperature *= 175.72; temperature /= 65536; temperature -= 46.85; return temperature; } void Si7021::reset(void) { Wire.beginTransmission(Si7021::Cmd.ADDRESS); Wire.write(Si7021::Cmd.RESET); Wire.endTransmission(); delay(50); } void Si7021::readSerialNumber(void) { Wire.beginTransmission(Si7021::Cmd.ADDRESS); Wire.write(Si7021::Cmd.ID1 >> 8); Wire.write(Si7021::Cmd.ID1 & 0xFF); Wire.endTransmission(); Wire.requestFrom(Si7021::Cmd.ADDRESS, (uint8_t)8); sernum_a = Wire.read(); Wire.read(); sernum_a <<= 8; sernum_a |= Wire.read(); Wire.read(); sernum_a <<= 8; sernum_a |= Wire.read(); Wire.read(); sernum_a <<= 8; sernum_a |= Wire.read(); Wire.read(); Wire.beginTransmission(Si7021::Cmd.ADDRESS); Wire.write(Si7021::Cmd.ID2 >> 8); Wire.write(Si7021::Cmd.ID2 & 0xFF); Wire.endTransmission(); Wire.requestFrom(Si7021::Cmd.ADDRESS, (uint8_t)8); sernum_b = Wire.read(); Wire.read(); sernum_b <<= 8; sernum_b |= Wire.read(); Wire.read(); sernum_b <<= 8; sernum_b |= Wire.read(); Wire.read(); sernum_b <<= 8; sernum_b |= Wire.read(); Wire.read(); } void Si7021::writeRegister8(uint8_t reg, uint8_t value) { Wire.beginTransmission(Si7021::Cmd.ADDRESS); Wire.write(reg); Wire.write(value); Wire.endTransmission(); } uint8_t Si7021::readRegister8(uint8_t reg) { uint8_t value; Wire.beginTransmission(Si7021::Cmd.ADDRESS); Wire.write(reg); Wire.endTransmission(false); Wire.requestFrom(Si7021::Cmd.ADDRESS, (uint8_t)1); value = Wire.read(); return value; } uint16_t Si7021::readRegister16(uint8_t reg) { uint16_t value; Wire.beginTransmission(Si7021::Cmd.ADDRESS); Wire.write(reg); Wire.endTransmission(); Wire.requestFrom(Si7021::Cmd.ADDRESS, (uint8_t)2); value = Wire.read(); value <<= 8; value |= Wire.read(); return value; } ================================================ FILE: examples/humidity/arduino101/Si7021.h ================================================ #ifndef _SI7021_H_ #define _SI7021_H_ /************************************************************************** @file Si7021.cpp @author Limor Fried (Adafruit Industries), Vojta Molda @license BSD (see license.txt) This is a library for the Adafruit Si7021 breakout board ----> https://www.adafruit.com/products/3251 Adafruit invests time and resources providing this open source code, please support Adafruit and open-source hardware by purchasing products from Adafruit! @section HISTORY v1.0 - First release v1.1 - Modified to use **************************************************************************/ #include #include class Si7021 { public: Si7021(SoftwareWire& Wire_); bool begin(void); void reset(void); void readSerialNumber(void); float readTemperature(void); float readHumidity(void); uint32_t sernum_a, sernum_b; private: uint8_t readRegister8(uint8_t reg); uint16_t readRegister16(uint8_t reg); void writeRegister8(uint8_t reg, uint8_t value); SoftwareWire& Wire; const static struct WireCommands { static const uint8_t ADDRESS = 0x40; static const uint8_t MEASRH_HOLD = 0xE5; static const uint8_t MEASRH_NOHOLD = 0xF5; static const uint8_t MEASTEMP_HOLD = 0xE3; static const uint8_t MEASTEMP_NOHOLD = 0xF3; static const uint8_t READPREVTEMP = 0xE0; static const uint8_t RESET = 0xFE; static const uint8_t WRITERHT_REG = 0xE6; static const uint8_t READRHT_REG = 0xE7; static const uint8_t WRITEHEATER_REG = 0x51; static const uint8_t READHEATER_REG = 0x11; static const uint16_t ID1 = 0xFA0F; static const uint16_t ID2 = 0xFCC9; static const uint16_t FIRMVERS = 0x84B8; } Cmd; }; #endif ================================================ FILE: examples/humidity/arduino101/arduino101.ino ================================================ #include #include #include #include "Si7021.h" static struct BoardPins { const uint8_t VIN = A0; const uint8_t VO3 = A1; const uint8_t GND = A2; const uint8_t SCL = A3; const uint8_t SDA = A4; } Pin; SoftwareWire wire = SoftwareWire(Pin.SDA, Pin.SCL); Si7021 sensor = Si7021(wire); BLEPeripheral ble; BLEService informationService("180A"); BLECharacteristic modelCharacteristic("2A24", BLERead, "101"); BLECharacteristic manufacturerCharacteristic("2A29", BLERead, "Arduino"); BLECharacteristic serialNumberCharacteristic("2A25", BLERead, "2.71828"); BLEService humidityService("10525F60-CF73-11E6-9598-F8E2251C9A69"); BLEFloatCharacteristic humidityCharacteristic("10525F61-CF73-11E6-9598-F8E2251C9A69", BLERead | BLENotify); BLEService thermometerService("F489CB00-C177-11E6-9598-9854249C9A66"); BLEFloatCharacteristic temperatureCharacteristic("F489CB01-C177-11E6-9598-9854249C9A66", BLERead | BLENotify); void setup() { Serial.begin(115200); pinMode(Pin.VIN, OUTPUT); digitalWrite(Pin.VIN, HIGH); pinMode(Pin.GND, OUTPUT); digitalWrite(Pin.GND, LOW); sensor.begin(); ble.setLocalName("Humidity"); ble.setAdvertisedServiceUuid(thermometerService.uuid()); ble.addAttribute(informationService); ble.addAttribute(modelCharacteristic); ble.addAttribute(manufacturerCharacteristic); ble.addAttribute(serialNumberCharacteristic); ble.addAttribute(humidityService); ble.addAttribute(humidityCharacteristic); humidityCharacteristic.setValueLE(0.0); ble.addAttribute(thermometerService); ble.addAttribute(temperatureCharacteristic); temperatureCharacteristic.setValueLE(0.0); ble.setEventHandler(BLEConnected, centralConnect); ble.setEventHandler(BLEDisconnected, centralDisconnect); ble.begin(); Serial.println("Bluetooth on"); } void loop() { ble.poll(); static float averageHumidity = sensor.readHumidity(); averageHumidity += (sensor.readTemperature() - averageHumidity) / 30.0; float previousHumidity = humidityCharacteristic.valueLE(); if (abs(averageHumidity - previousHumidity) > 1.50) { humidityCharacteristic.setValueLE(averageHumidity); Serial.print("Update temperature | "); Serial.println(averageHumidity, 0); } else { Serial.print("Temperature | "); Serial.println(averageHumidity, 0); } static float averageCelsius = sensor.readTemperature(); averageCelsius += (sensor.readTemperature() - averageCelsius) / 30.0; float previousCelsius = temperatureCharacteristic.valueLE(); if (abs(averageCelsius - previousCelsius) > 0.20) { temperatureCharacteristic.setValueLE(averageCelsius); Serial.print("Update temperature | "); Serial.println(averageCelsius, 2); } else { Serial.print("Temperature | "); Serial.println(averageCelsius, 2); } delay(1000); } void centralConnect(BLECentral& central) { Serial.print("Central connected | "); Serial.println(central.address()); } void centralDisconnect(BLECentral& central) { Serial.print("Central disconnected | "); Serial.println(central.address()); } ================================================ FILE: examples/humidity/bluefruit/Si7021.cpp ================================================ /************************************************************************** @file Si7021.cpp @author Limor Fried (Adafruit Industries), Vojta Molda @license BSD (see license.txt) This is a library for the Adafruit Si7021 breakout board ----> https://www.adafruit.com/products/3251 Adafruit invests time and resources providing this open source code, please support Adafruit and open-source hardware by purchasing products from Adafruit! @section HISTORY v1.0 - First release v1.1 - Modified to use **************************************************************************/ #include "Si7021.h" Si7021::Si7021(SoftwareWire& Wire_) : sernum_a(0), sernum_b(0), Wire(Wire_) { } bool Si7021::begin(void) { Wire.begin(); reset(); if (readRegister8(Si7021::Cmd.READRHT_REG) != 0x3A) return false; readSerialNumber(); return true; } float Si7021::readHumidity(void) { Wire.beginTransmission(Si7021::Cmd.ADDRESS); Wire.write(Si7021::Cmd.MEASRH_NOHOLD); Wire.endTransmission(false); delay(25); Wire.requestFrom(Si7021::Cmd.ADDRESS, (uint8_t)3); uint16_t hum = Wire.read(); hum <<= 8; hum |= Wire.read(); uint8_t chxsum = Wire.read(); float humidity = hum; humidity *= 125; humidity /= 65536; humidity -= 6; return humidity; } float Si7021::readTemperature(void) { Wire.beginTransmission(Si7021::Cmd.ADDRESS); Wire.write(Si7021::Cmd.MEASTEMP_NOHOLD); Wire.endTransmission(false); delay(25); Wire.requestFrom(Si7021::Cmd.ADDRESS, (uint8_t)3); uint16_t temp = Wire.read(); temp <<= 8; temp |= Wire.read(); uint8_t chxsum = Wire.read(); float temperature = temp; temperature *= 175.72; temperature /= 65536; temperature -= 46.85; return temperature; } void Si7021::reset(void) { Wire.beginTransmission(Si7021::Cmd.ADDRESS); Wire.write(Si7021::Cmd.RESET); Wire.endTransmission(); delay(50); } void Si7021::readSerialNumber(void) { Wire.beginTransmission(Si7021::Cmd.ADDRESS); Wire.write(Si7021::Cmd.ID1 >> 8); Wire.write(Si7021::Cmd.ID1 & 0xFF); Wire.endTransmission(); Wire.requestFrom(Si7021::Cmd.ADDRESS, (uint8_t)8); sernum_a = Wire.read(); Wire.read(); sernum_a <<= 8; sernum_a |= Wire.read(); Wire.read(); sernum_a <<= 8; sernum_a |= Wire.read(); Wire.read(); sernum_a <<= 8; sernum_a |= Wire.read(); Wire.read(); Wire.beginTransmission(Si7021::Cmd.ADDRESS); Wire.write(Si7021::Cmd.ID2 >> 8); Wire.write(Si7021::Cmd.ID2 & 0xFF); Wire.endTransmission(); Wire.requestFrom(Si7021::Cmd.ADDRESS, (uint8_t)8); sernum_b = Wire.read(); Wire.read(); sernum_b <<= 8; sernum_b |= Wire.read(); Wire.read(); sernum_b <<= 8; sernum_b |= Wire.read(); Wire.read(); sernum_b <<= 8; sernum_b |= Wire.read(); Wire.read(); } void Si7021::writeRegister8(uint8_t reg, uint8_t value) { Wire.beginTransmission(Si7021::Cmd.ADDRESS); Wire.write(reg); Wire.write(value); Wire.endTransmission(); } uint8_t Si7021::readRegister8(uint8_t reg) { uint8_t value; Wire.beginTransmission(Si7021::Cmd.ADDRESS); Wire.write(reg); Wire.endTransmission(false); Wire.requestFrom(Si7021::Cmd.ADDRESS, (uint8_t)1); value = Wire.read(); return value; } uint16_t Si7021::readRegister16(uint8_t reg) { uint16_t value; Wire.beginTransmission(Si7021::Cmd.ADDRESS); Wire.write(reg); Wire.endTransmission(); Wire.requestFrom(Si7021::Cmd.ADDRESS, (uint8_t)2); value = Wire.read(); value <<= 8; value |= Wire.read(); return value; } ================================================ FILE: examples/humidity/bluefruit/Si7021.h ================================================ #ifndef _SI7021_H_ #define _SI7021_H_ /************************************************************************** @file Si7021.cpp @author Limor Fried (Adafruit Industries), Vojta Molda @license BSD (see license.txt) This is a library for the Adafruit Si7021 breakout board ----> https://www.adafruit.com/products/3251 Adafruit invests time and resources providing this open source code, please support Adafruit and open-source hardware by purchasing products from Adafruit! @section HISTORY v1.0 - First release v1.1 - Modified to use **************************************************************************/ #include #include class Si7021 { public: Si7021(SoftwareWire& Wire_); bool begin(void); void reset(void); void readSerialNumber(void); float readTemperature(void); float readHumidity(void); uint32_t sernum_a, sernum_b; private: uint8_t readRegister8(uint8_t reg); uint16_t readRegister16(uint8_t reg); void writeRegister8(uint8_t reg, uint8_t value); SoftwareWire& Wire; const static struct WireCommands { static const uint8_t ADDRESS = 0x40; static const uint8_t MEASRH_HOLD = 0xE5; static const uint8_t MEASRH_NOHOLD = 0xF5; static const uint8_t MEASTEMP_HOLD = 0xE3; static const uint8_t MEASTEMP_NOHOLD = 0xF3; static const uint8_t READPREVTEMP = 0xE0; static const uint8_t RESET = 0xFE; static const uint8_t WRITERHT_REG = 0xE6; static const uint8_t READRHT_REG = 0xE7; static const uint8_t WRITEHEATER_REG = 0x51; static const uint8_t READHEATER_REG = 0x11; static const uint16_t ID1 = 0xFA0F; static const uint16_t ID2 = 0xFCC9; static const uint16_t FIRMVERS = 0x84B8; } Cmd; }; #endif ================================================ FILE: examples/humidity/bluefruit/bluefruit.ino ================================================ #include #include #include #include #include #include "Si7021.h" static struct BoardPins { const uint8_t VIN = A0; const uint8_t VO3 = A1; const uint8_t GND = A2; const uint8_t SCL = A3; const uint8_t SDA = A4; } Pin; SoftwareWire wire = SoftwareWire(Pin.SDA, Pin.SCL); Si7021 sensor = Si7021(wire); Adafruit_BluefruitLE_SPI ble(8, 7, 4); // Firmware > 0.7.0 Adafruit_BLEGatt gatt(ble); int32_t humidityService; int32_t humidityCharacteristic; int32_t thermometerService; int32_t temperatureCharacteristic; union float_bytes { float value; uint8_t bytes[sizeof(float)]; }; void setup(void) { Serial.begin(115200); pinMode(Pin.VIN, OUTPUT); digitalWrite(Pin.VIN, HIGH); pinMode(Pin.GND, OUTPUT); digitalWrite(Pin.GND, LOW); sensor.begin(); ble.begin(/* true - useful for debugging */); ble.factoryReset(); ble.info(); uint8_t humidityServiceUUID[] = {0x10,0x52,0x5F,0x60,0xCF,0x73,0x11,0xE6,0x95,0x98,0xF8,0xE2,0x25,0x1C,0x9A,0x69}; humidityService = gatt.addService(humidityServiceUUID); uint8_t humidityCharacteristicUUID[] = {0x10,0x52,0x5F,0x61,0xCF,0x73,0x11,0xE6,0x95,0x98,0xF8,0xE2,0x25,0x1C,0x9A,0x69}; humidityCharacteristic = gatt.addCharacteristic(humidityCharacteristicUUID, GATT_CHARS_PROPERTIES_READ | GATT_CHARS_PROPERTIES_NOTIFY, sizeof(float), sizeof(float), BLE_DATATYPE_BYTEARRAY); uint8_t thermometerServiceUUID[] = {0xF4,0x89,0xCB,0x00,0xC1,0x77,0x11,0xE6,0x95,0x98,0x98,0x54,0x24,0x9C,0x9A,0x66}; thermometerService = gatt.addService(thermometerServiceUUID); uint8_t thermometerCharacteristicUUID[] = {0xF4,0x89,0xCB,0x01,0xC1,0x77,0x11,0xE6,0x95,0x98,0x98,0x54,0x24,0x9C,0x9A,0x66}; temperatureCharacteristic = gatt.addCharacteristic(thermometerCharacteristicUUID, GATT_CHARS_PROPERTIES_READ | GATT_CHARS_PROPERTIES_NOTIFY, sizeof(float), sizeof(float), BLE_DATATYPE_BYTEARRAY); ble.reset(); ble.setConnectCallback(centralConnect); ble.setDisconnectCallback(centralDisconnect); Serial.println("Bluetooth on"); } /** Send randomized heart rate data continuously **/ void loop(void) { ble.update(); static union float_bytes averageHumidity = { .value = sensor.readHumidity() }; averageHumidity.value += (sensor.readHumidity() - averageHumidity.value) / 30.0; static union float_bytes previousHumidity = { .value = 0.0 }; gatt.getChar(humidityCharacteristic, previousHumidity.bytes, sizeof(previousHumidity)); if (abs(averageHumidity.value - previousHumidity.value) > 1.5) { gatt.setChar(humidityCharacteristic, averageHumidity.bytes, sizeof(averageHumidity)); Serial.print("Update humidity | "); Serial.println(averageHumidity.value, 0); } else { Serial.print("Humidity | "); Serial.println(averageHumidity.value, 0); } static union float_bytes averageCelsius = { .value = sensor.readTemperature()}; averageCelsius.value += (sensor.readTemperature() - averageCelsius.value) / 30.0; static union float_bytes previousCelsius = { .value = 0.0 }; gatt.getChar(temperatureCharacteristic, previousCelsius.bytes, sizeof(previousCelsius)); if (abs(averageCelsius.value - previousCelsius.value) > 0.20) { gatt.setChar(temperatureCharacteristic, averageCelsius.bytes, sizeof(averageCelsius)); Serial.print("Update temperature | "); Serial.println(averageCelsius.value, 2); } else { Serial.print("Temperature | "); Serial.println(averageCelsius.value, 2); } delay(1000); } void centralConnect(void) { Serial.print("Central connected | "); if (ble.sendCommandCheckOK("AT+BLEGETPEERADDR")) { Serial.println(ble.buffer); } } void centralDisconnect(void) { Serial.print("Central disconnected | "); if (ble.sendCommandCheckOK("AT+BLEGETPEERADDR")) { Serial.println(ble.buffer); } } ================================================ FILE: examples/humidity/config.json ================================================ { "bridge": { "name": "Raspberry Pi 3", "username": "CC:22:3D:E3:CE:30", "port": 51826, "pin": "314-15-926" }, "description": "Raspberry Pi 3 Homebridge-Bluetooth Humidity Sensor Example", "platforms": [ { "platform": "Bluetooth", "accessories": [ { "name": "Arduino", "address": "01:23:45:67:89:AB", "services": [ { "name": "Humidity Sensor", "type": "HumiditySensor", "UUID": "10525F60-CF73-11E6-9598-F8E2251C9A69", "characteristics": [ { "type": "CurrentRelativeHumidity", "UUID": "10525F61-CF73-11E6-9598-F8E2251C9A69" } ] }, { "name": "Temperature Sensor", "type": "TemperatureSensor", "UUID": "F489CB00-C177-11E6-9598-9854249C9A66", "characteristics": [ { "type": "CurrentTemperature", "UUID": "F489CB01-C177-11E6-9598-9854249C9A66" } ] } ] } ] } ] } ================================================ FILE: examples/humidity/readme.md ================================================ # Humidity and Temperature Sensor Turn a BLE capable microprocessor and the [Si7021](https://www.silabs.com/Support%20Documents/TechnicalDocs/Si7021-A20.pdf) humidity and temperature sensor into a wireless HomeKit weather station. Temperature and Humidity readings can be displayed in the Home app on your Apple device and used to setup automation rules for your thermostat and humidifier. This example uses [Arduino 101](https://www.arduino.cc/en/Main/ArduinoBoard101) or [Bluefruit Micro LE](https://www.adafruit.com/products/2661) and [Raspberry Pi 3](https://www.raspberrypi.org/). Generally, any programmable BLE peripheral and a box capable of running [Node.js](https://nodejs.org) with [Noble](https://github.com/sandeepmistry/noble) will work. You'll also need the Si7021 humidity and temperature sensor. There is a nice breakout done by [Adafruit](https://www.adafruit.com/products/3251) and you can also use another breakout done by [Sparkfun](https://www.sparkfun.com/products/13763). ## BLE Periphral (Arduino 101, Bluefruit Micro LE or Other BLE Board) Download and install the latest version of the [Arduino IDE](https://www.arduino.cc/en/Main/Software). If you're totally new to microcontrollers take some time to go through an introductory tutorial and learn how to make a LED blink. This will help you to understand how to use the IDE, how to upload a sketch and what is the code actually doing. ### Wiring The Si7021 breakout is wired to ports `A0`-`A4`. `A0` and `A2` are set as outputs `HIGH`/`LOW` and serve as `VCC` and `GND` to provide power to the sensor. Ports `A3` and `A4` serve as two wire interface data `SDA` and clock `SCL` lines. `A3` is connected to optional `3VO` outptu of the voltage regulator built into the Adafruit's breakout and is not used at all. This pin setup was chosen so the breakout can be soldered directly onto the Bluefruit board to create a small and compact package. **Note** _Alternatively, you can use any of the many BLE boards available on the market ([BlueBean](https://punchthrough.com/bean/), [RedBearLabs BLE Nano](http://redbearlab.com/blenano), ...) as long as you keep UUIDs of the services and characteristics in sync with your `config.json` file, everything will work just fine._ ### Running the Sketch Compile, run and upload the [arduino101.ino](arduino101/arduino101.ino) or [bluefruit.ino](bluefruit/bluefruit.ino) sketch using the [Arduino IDE](https://www.arduino.cc/en/Main/Software). The sketch creates two BLE services with a readable and notifiable characteristic for the current temperature and humidity. Both values are a `float` type. ```cpp BLEService humidityService("10525F60-CF73-11E6-9598-F8E2251C9A69"); BLEFloatCharacteristic humidityCharacteristic("10525F61-CF73-11E6-9598-F8E2251C9A69", BLERead | BLENotify); BLEService thermometerService("F489CB00-C177-11E6-9598-9854249C9A66"); BLEFloatCharacteristic temperatureCharacteristic("F489CB01-C177-11E6-9598-9854249C9A66", BLERead | BLENotify); ``` Take a look into [this file](https://github.com/KhaosT/HAP-NodeJS/blob/master/lib/gen/HomeKitTypes.js#L1147) to see the full definition of the _CurrentRelativeHumidity_ characteristic used in the _HumiditySensor_ service. _TemperatureSensor_ service has _CurrentTemperature_ characteristic. Once the BLE central device is setup, it connects to these characteristics and exposes them via Homebridge as a HomeKit accessory of type _HumiditySensor_ and _TemperatureSensor_. The code also calculates (pseudo-moving) average to prevent noise in the measurements to trigger frequent mew value notifications. Duration of the averaging window is about 30 s. Once the moving average accumulates change above a certain threshold a new value of the characteristic is set which triggers the notify mechanism and propagates the measurement to HomeKit. The threshold is approximately half of the sensor accuracy (±0.4°C and ±3%). Leave the device powered on and the sketch running while you setup the Homebridge server. The sketch has some built-in logging, so keeping the Serial monitor open may be helpful for debugging. ## BLE Central & Homebridge Server (Raspberry Pi 3 or Other Compatible Box) For help installing an operating system on your new Pi, the official documentation contains a couple of [nice videos](https://www.raspberrypi.org/help/videos/). ### Wiring No wiring except for the micro-USB cable providing power is needed. The Pi needs to be connected to the same router (subnet) as the Apple device you plan to use. It doesn't matter whether via Wifi or Ethernet. Otherwise, you won't be able discover and connect to the Homebridge server running on the Pi. **Note** _Alternatively, you can use a Raspberry Pi 2 with a supported USB BLE dongle instead of the Pi 3._ ### Running Homebridge Running Homebridge on a Raspberry Pi is straightforward. Follow [this guide](https://github.com/nfarina/homebridge/wiki/Running-HomeBridge-on-a-Raspberry-Pi) to install Homebridge server and then run the following command to install the homebridge-bluetooth plugin: ```sh [sudo] npm install -g homebridge-bluetooth ``` Edit the `~/.homebridge/config.json`, name your Homebridge server and add a new accessory to allow the plugin to connect to the BLE service running on the Arduino: ```js "name": "Arduino", "address": "01:23:45:67:89:AB", "services": [ { "name": "Humidity Sensor", "type": "HumiditySensor", "UUID": "10525F60-CF73-11E6-9598-F8E2251C9A69", "characteristics": [ { "type": "CurrentRelativeHumidity", "UUID": "10525F61-CF73-11E6-9598-F8E2251C9A69" } ] }, { "name": "Temperature Sensor", "type": "TemperatureSensor", "UUID": "F489CB00-C177-11E6-9598-9854249C9A66", "characteristics": [ { "type": "CurrentTemperature", "UUID": "F489CB01-C177-11E6-9598-9854249C9A66" } ] } ] ``` Finally, start the Homebridge server. If you use Linux you may need to run with higher privileges in order to have access to the BLE hardware layer. See [this link](https://github.com/sandeepmistry/noble#running-without-rootsudo) for more details about running without `sudo`. ```sh [sudo] homebridge -D ``` **Note** _Running with `-D` turns on additional debugging output that is very helpful for getting addresses and UUIDs of your BLE devices that needs to match with the `config.json` file._ **Note** _Homebridge server doesn't run only on Linux. MacOS and Windows machines are also supported given they have a built-in BLE adapter or an USB dongle. For more details see supported platforms of [Homebridge](https://github.com/nfarina/homebridge) and [Noble](https://github.com/sandeepmistry/noble)._ ## Apple Device (iOS 10 or newer) ### Pairing Open Home app and tap the '+' button to add new accessory. When you attempt to add the 'Raspberry Pi 3' bridge, it will ask for a "PIN" from the `config.json` file. Once you are paired with your new Rapsberry, Homebridge server all the connected BLE accesories can be added the same way as the bridge. ### Interacting Once your BLE accessory has been added to HomeKit database, besides using the Home app or Control Center at the bottom of the screen, you should be able to tell Siri to get the reading from any HomeKit accessory. Try _"Hey Siri, what's the humidity of the Arduino?"_. However, Siri is a cloud service and iOS may need some time to synchronize your HomeKit database to iCloud. ================================================ FILE: examples/lightbulb/arduino101/arduino101.ino ================================================ #include #include const int pinLED = 13; BLEPeripheral ble; BLEService informationService("180A"); BLECharacteristic modelCharacteristic("2A24", BLERead, "101"); BLECharacteristic manufacturerCharacteristic("2A29", BLERead, "Arduino"); BLECharacteristic serialNumberCharacteristic("2A25", BLERead, "2.71828"); BLEService lightbulbService("8E76F000-690E-472E-88C3-051277686A73"); BLECharCharacteristic onCharacteristic("8E76F001-690E-472E-88C3-051277686A73", BLEWrite | BLERead | BLENotify); void setup() { Serial.begin(115200); pinMode(pinLED, OUTPUT); ble.setLocalName("Light"); ble.setAdvertisedServiceUuid(lightbulbService.uuid()); ble.addAttribute(informationService); ble.addAttribute(modelCharacteristic); ble.addAttribute(manufacturerCharacteristic); ble.addAttribute(serialNumberCharacteristic); ble.addAttribute(lightbulbService); ble.addAttribute(onCharacteristic); bool on = true; onCharacteristic.setValueLE(on); setLED(on); ble.setEventHandler(BLEConnected, centralConnect); ble.setEventHandler(BLEDisconnected, centralDisconnect); onCharacteristic.setEventHandler(BLEWritten, characteristicWrite); ble.begin(); Serial.println("Bluetooth on"); } void loop() { ble.poll(); } void centralConnect(BLECentral& central) { Serial.print("Central connected | "); Serial.println(central.address()); } void centralDisconnect(BLECentral& central) { Serial.print("Central disconnected | "); Serial.println(central.address()); } void characteristicWrite(BLECentral& central, BLECharacteristic& characteristic) { Serial.print("Characteristic written | "); Serial.println(characteristic.uuid()); bool on = (bool) onCharacteristic.valueLE(); setLED(on); } void setLED(bool on) { Serial.print("LED | "); Serial.print(on); digitalWrite(pinLED, on); } ================================================ FILE: examples/lightbulb/config.json ================================================ { "bridge": { "name": "Raspberry Pi 3", "username": "CC:22:3D:E3:CE:30", "port": 51826, "pin": "314-15-926" }, "description": "Raspberry Pi 3 Homebridge-Bluetooth Lightbulb Example", "platforms": [ { "platform": "Bluetooth", "accessories": [ { "name": "Arduino", "address": "01:23:45:67:89:AB", "services": [ { "name": "LED", "type": "Lightbulb", "UUID": "8E76F000-690E-472E-88C3-051277686A73", "characteristics": [ { "type": "On", "UUID": "8E76F001-690E-472E-88C3-051277686A73" } ] } ] } ] } ] } ================================================ FILE: examples/lightbulb/readme.md ================================================ # Lightbulb Turn an LED connected to a BLE capable microprocessor into a wireless HomeKit lightbulb. Use the Home app or Siri on your Apple device to switch it on and off. This example uses [Arduino 101](https://www.arduino.cc/en/Main/ArduinoBoard101) and [Raspberry Pi 3](https://www.raspberrypi.org/). Generally, any programmable BLE peripheral and a box capable of running [Node.js](https://nodejs.org) with [Noble](https://github.com/sandeepmistry/noble) will work. ## BLE Peripheral (Arduino 101 or Other BLE Board) Download and install the latest version of the [Arduino IDE](https://www.arduino.cc/en/Main/Software). If you're totally new to microcontrollers take some time to go through an introductory tutorial and learn how to make a LED blink. This will help you to understand how to use the IDE, how to upload a sketch and what is the code actually doing. ### Wiring Connect the LED to pin 13. The LED has to have a resistor in series to limit the current passing through - max current per I/O pin is 20 mA. Generally, anything between 100 and 1k Ohms will do. If you're lazy you can also skip the wiring and use the onboard LED connected to pin 13. **Note** _Alternatively, you can use any of the many BLE boards available on the market ([BlueBean](https://punchthrough.com/bean/), [RedBearLabs BLE Nano](http://redbearlab.com/blenano), ...) as long as you keep UUIDs of the services and characteristics in sync with your `config.json` file, everything will work just fine._ ### Running the Sketch Compile, run and upload the [arduino101.ino sketch](arduino101/arduino101.ino) using the [Arduino IDE](https://www.arduino.cc/en/Main/Software). The sketch creates a BLE service with a readable, writable and notifiable characteristic for on/off state. ```cpp BLEService lightbulbService("8E76F000-690E-472E-88C3-051277686A73"); BLECharCharacteristic onCharacteristic("8E76F001-690E-472E-88C3-051277686A73", BLEWrite | BLERead | BLENotify); ``` Take a look into [this file](https://github.com/KhaosT/HAP-NodeJS/blob/master/lib/gen/HomeKitTypes.js#L1147) to see the full definition of the _On_ characteristic used in the _Lightbulb_ service. Once the BLE central device is setup, it connects to this characteristic and exposes it via Homebridge as a HomeKit accessory of type _Lightbulb_. Leave the device powered on and the sketch running while you setup the Homebridge server. The sketch has some built-in logging, so keeping the Serial monitor open may be helpful for debugging. ## BLE Central & Homebridge Server (Raspberry Pi 3 or Other Compatible Box) For help installing an operating system on your new Pi, the official documentation contains a couple of [nice videos](https://www.raspberrypi.org/help/videos/). ### Wiring No wiring except for the micro-USB cable providing power is needed. The Pi needs to be connected to the same router (subnet) as the Apple device you plan to use. It doesn't matter whether via Wifi or Ethernet. Otherwise, you won't be able discover and connect to the Homebridge server running on the Pi. **Note** _Alternatively, you can use a Raspberry Pi 2 with a supported USB BLE dongle instead of the Pi 3._ ### Running Homebridge Running Homebridge on a Raspberry Pi is straightforward. Follow [this guide](https://github.com/nfarina/homebridge/wiki/Running-HomeBridge-on-a-Raspberry-Pi) to install Homebridge server and then run the following command to install the homebridge-bluetooth plugin: ```sh [sudo] npm install -g homebridge-bluetooth ``` Edit the `~/.homebridge/config.json`, name your Homebridge server and add a new accessory to allow the plugin to connect to the BLE service running on the Arduino: ```js "name": "Arduino", "address": "01:23:45:67:89:AB", "services": [ { "name": "LED", "type": "Lightbulb", "UUID": "8E76F000-690E-472E-88C3-051277686A73", "characteristics": [ { "type": "On", "UUID": "8E76F001-690E-472E-88C3-051277686A73" } ] } ] ``` Finally, start the Homebridge server. If you use Linux you may need to run with higher privileges in order to have access to the BLE hardware layer. See [this link](https://github.com/sandeepmistry/noble#running-without-rootsudo) for more details about running without `sudo`. ```sh [sudo] homebridge -D ``` **Note** _Running with `-D` turns on additional debugging output that is very helpful for getting addresses and UUIDs of your BLE devices that needs to match with the `config.json` file._ **Note** _Homebridge server doesn't run only on Linux. MacOS and Windows machines are also supported given they have a built-in BLE adapter or an USB dongle. For more details see supported platforms of [Homebridge](https://github.com/nfarina/homebridge) and [Noble](https://github.com/sandeepmistry/noble)._ ## Apple Device (iOS 10 or newer) ### Pairing Open Home app and tap the '+' button to add new accessory. When you attempt to add the 'Raspberry Pi 3' bridge, it will ask for a "PIN" from the `config.json` file. Once you are paired with your new Rapsberry, Homebridge server all the connected BLE accesories can be added the same way as the bridge. ### Interacting Once your BLE accessory has been added to HomeKit database, besides using the Home app or Control Center at the bottom of the screen, you should be able to tell Siri to control any HomeKit accessory. Try _"Hey Siri, turn on LED"_. However, Siri is a cloud service and iOS may need some time to synchronize your HomeKit database to iCloud. ================================================ FILE: examples/lightbulb-rgb/arduino101/arduino101.ino ================================================ #include #include #include const int pinRedLED = 5; const int pinGreenLED = 6; const int pinBlueLED = 9; const int pinSwitch = 2; BLEPeripheral ble; BLEService informationService("180A"); BLECharacteristic modelCharacteristic("2A24", BLERead, "101"); BLECharacteristic manufacturerCharacteristic("2A29", BLERead, "Arduino"); BLECharacteristic serialNumberCharacteristic("2A25", BLERead, "2.71828"); BLEService lightbulbService("57E54BF0-8574-47BE-9C1D-A0DBFC8FA183"); BLECharCharacteristic onCharacteristic("57E54BF1-8574-47BE-9C1D-A0DBFC8FA183", BLERead | BLEWrite | BLENotify); BLEFloatCharacteristic hueCharacteristic("57E54BF2-8574-47BE-9C1D-A0DBFC8FA183", BLERead | BLEWrite | BLENotify); BLEFloatCharacteristic saturationCharacteristic("57E54BF3-8574-47BE-9C1D-A0DBFC8FA183", BLERead | BLEWrite | BLENotify); BLEIntCharacteristic brightnessCharacteristic("57E54BF4-8574-47BE-9C1D-A0DBFC8FA183", BLERead | BLEWrite | BLENotify); bool lastSwitch = HIGH; void setup() { Serial.begin(115200); pinMode(pinRedLED, OUTPUT); pinMode(pinGreenLED, OUTPUT); pinMode(pinBlueLED, OUTPUT); pinMode(pinSwitch, INPUT_PULLUP); ble.setLocalName("Light"); ble.setAdvertisedServiceUuid(lightbulbService.uuid()); ble.addAttribute(informationService); ble.addAttribute(modelCharacteristic); ble.addAttribute(manufacturerCharacteristic); ble.addAttribute(serialNumberCharacteristic); ble.addAttribute(lightbulbService); ble.addAttribute(onCharacteristic); ble.addAttribute(hueCharacteristic); ble.addAttribute(saturationCharacteristic); ble.addAttribute(brightnessCharacteristic); bool on = true; float hue = 0.0; float saturation = 0.0; int brightness = 100; onCharacteristic.setValueLE(on); hueCharacteristic.setValueLE(hue); saturationCharacteristic.setValueLE(saturation); brightnessCharacteristic.setValueLE(brightness); setLED(on, hue, saturation, brightness); ble.setEventHandler(BLEConnected, centralConnect); ble.setEventHandler(BLEDisconnected, centralDisconnect); onCharacteristic.setEventHandler(BLEWritten, characteristicWrite); hueCharacteristic.setEventHandler(BLEWritten, characteristicWrite); saturationCharacteristic.setEventHandler(BLEWritten, characteristicWrite); brightnessCharacteristic.setEventHandler(BLEWritten, characteristicWrite); ble.begin(); Serial.println("Bluetooth on"); lastSwitch = digitalRead(pinSwitch); } void loop() { ble.poll(); if ((digitalRead(pinSwitch) == LOW) && (lastSwitch == HIGH)) { bool on = (bool) onCharacteristic.valueLE(); float hue = (float) hueCharacteristic.valueLE(); float saturation = (float) saturationCharacteristic.valueLE(); int brightness = (int) brightnessCharacteristic.valueLE(); setLED(!on, hue, saturation, brightness); onCharacteristic.setValue(!on); ble.poll(); lastSwitch = LOW; delay(1); } else if ((digitalRead(pinSwitch) == HIGH) && (lastSwitch == LOW)) { lastSwitch = HIGH; delay(1); } } void centralConnect(BLECentral& central) { Serial.print("Central connected | "); Serial.println(central.address()); } void centralDisconnect(BLECentral& central) { Serial.print("Central disconnected | "); Serial.println(central.address()); } void characteristicWrite(BLECentral& central, BLECharacteristic& characteristic) { Serial.print("Characteristic written | "); Serial.println(characteristic.uuid()); bool on = (bool) onCharacteristic.valueLE(); float hue = (float) hueCharacteristic.valueLE(); float saturation = (float) saturationCharacteristic.valueLE(); int brightness = (int) brightnessCharacteristic.valueLE(); setLED(on, hue, saturation, brightness); } void setLED(bool on, float hue, float saturation, int brightness) { hue = max(0.0, min(360.0, hue)); saturation = max(0.0, min(100.0, saturation)); brightness = max(0, min(100, brightness)); unsigned int red, green, blue; hsv2rgb(hue, saturation/100.0, brightness/100.0, &red, &green, &blue); analogWrite(pinRedLED, on * red); analogWrite(pinGreenLED, on * green); analogWrite(pinBlueLED, on * blue); Serial.print("RGB LED | "); Serial.print(on); Serial.print(" HSB("); Serial.print(hue); Serial.print(","); Serial.print(saturation); Serial.print(","); Serial.print(brightness); Serial.print(") -> RGB("); Serial.print(red); Serial.print(","); Serial.print(green); Serial.print(","); Serial.print(blue); Serial.println(")"); } void hsv2rgb(float hue, float saturation, float value, unsigned int* red, unsigned int* green, unsigned int* blue) { if (saturation == 0.0) { *red = (unsigned int) round(value * 255.0); *green = (unsigned int) round(value * 255.0); *blue = (unsigned int) round(value * 255.0); } else { int h = ((int) floor(hue / 60.0) % 6); float f = (hue / 60.0) - floor(hue / 60.0); float p = value * (1.0 - saturation); float q = value * (1.0 - saturation * f); float t = value * (1.0 - (saturation * (1.0 - f))); switch (h) { case 0: *red = (unsigned int) round(value * 255.0); *green = (unsigned int) round(t * 255.0); *blue = (unsigned int) round(p * 255.0); break; case 1: *red = (unsigned int) round(q * 255.0); *green = (unsigned int) round(value * 255.0); *blue = (unsigned int) round(p * 255.0); break; case 2: *red = (unsigned int) round(p * 255.0); *green = (unsigned int) round(value * 255.0); *blue = (unsigned int) round(t * 255.0); break; case 3: *red = (unsigned int) round(p * 255.0); *green = (unsigned int) round(q * 255.0); *blue = (unsigned int) round(value * 255.0); break; case 4: *red = (unsigned int) round(t * 255.0); *green = (unsigned int) round(p * 255.0); *blue = (unsigned int) round(value * 255.0); break; case 5: *red = (unsigned int) round(value * 255.0); *green = (unsigned int) round(p * 255.0); *blue = (unsigned int) round(q * 255.0); break; } } } ================================================ FILE: examples/lightbulb-rgb/config.json ================================================ { "bridge": { "name": "Raspberry Pi 3", "username": "CC:22:3D:E3:CE:30", "port": 51826, "pin": "314-15-926" }, "description": "Raspberry Pi 3 Homebridge-Bluetooth RGB Lightbulb Example", "platforms": [ { "platform": "Bluetooth", "accessories": [ { "name": "Arduino", "address": "01:23:45:67:89:AB", "services": [ { "name": "RGB LED", "type": "Lightbulb", "UUID": "57E54BF0-8574-47BE-9C1D-A0DBFC8FA183", "characteristics": [ { "type": "On", "UUID": "57E54BF1-8574-47BE-9C1D-A0DBFC8FA183" }, { "type": "Hue", "UUID": "57E54BF2-8574-47BE-9C1D-A0DBFC8FA183" }, { "type": "Saturation", "UUID": "57E54BF3-8574-47BE-9C1D-A0DBFC8FA183" }, { "type": "Brightness", "UUID": "57E54BF4-8574-47BE-9C1D-A0DBFC8FA183" } ] } ] } ] } ] } ================================================ FILE: examples/lightbulb-rgb/readme.md ================================================ # RGB Lightbulb Turn a bunch of LEDs connected to a BLE capable microprocessor into a wireless HomeKit light. Use the Home app or Siri on your Apple device to switch it on, change color or reduce brightness. This example uses [Arduino 101](https://www.arduino.cc/en/Main/ArduinoBoard101) and [Raspberry Pi 3](https://www.raspberrypi.org/). Generally, any programmable BLE peripheral and a box capable of running [Node.js](https://nodejs.org) with [Noble](https://github.com/sandeepmistry/noble) will work. ## BLE Peripheral (Arduino 101 or Other BLE Board) Download and install the latest version of the [Arduino IDE](https://www.arduino.cc/en/Main/Software). If you're totally new to microcontrollers take some time to go through an introductory tutorial and learn how to make a LED blink. This will help you to understand how to use the IDE, how to upload a sketch and what is the code actually doing. ### Wiring Connect 3 LEDs to pins 9, 6 and 5. These pins support PWM and therefore can be programmed to dim the LEDs. Each LED needs to have a resistor to limit the current passing through - max current per I/O pin is 20 mA. Anything between 100 Ohms and 1k Ohms will do. The tactile switch is connected to pin 2 and when pushed connects the pin to the ground. The sketch code activates the internal 10k Ohm pull-up resitor to keep the pin high when the switch isn't pressed. **Note** _Alternatively, you can use any of the many BLE boards available on the market ([BlueBean](https://punchthrough.com/bean/), [RedBearLabs BLE Nano](http://redbearlab.com/blenano), ...) as long as you keep UUIDs of the services and characteristics in sync with your `config.json` file, everything will work just fine._ ### Running the Sketch Compile, run and upload the [arduino101.ino sketch](arduino101/arduino101.ino) using the [Arduino IDE](https://www.arduino.cc/en/Main/Software). The sketch creates a BLE service with 4 characteristics. There's one characteristic for on/off characteristic (type `BOOL`), two for hue & saturation (type `FLOAT`) and the fourth one for brightness (type `INT`). All charactersitis have read, write and notify permissions. ```cpp BLEService lightbulbService("57E54BF0-8574-47BE-9C1D-A0DBFC8FA183"); BLECharCharacteristic onCharacteristic("57E54BF1-8574-47BE-9C1D-A0DBFC8FA183", BLERead | BLEWrite | BLENotify); BLEFloatCharacteristic hueCharacteristic("57E54BF2-8574-47BE-9C1D-A0DBFC8FA183", BLERead | BLEWrite | BLENotify); BLEFloatCharacteristic saturationCharacteristic("57E54BF3-8574-47BE-9C1D-A0DBFC8FA183", BLERead | BLEWrite | BLENotify); BLEIntCharacteristic brightnessCharacteristic("57E54BF4-8574-47BE-9C1D-A0DBFC8FA183", BLERead | BLEWrite | BLENotify); ``` When the tactile switch is toggled the LEDs turn on (or turn off if they were on) and the BLE subscribe-notification mechanism cases the an update update on the Homebridge. This way the information about switching propagates through callbacks to the Apple device without any polling. Take a look into [this file](https://github.com/KhaosT/HAP-NodeJS/blob/master/lib/gen/HomeKitTypes.js#L1147) to see the full definition of the _Lightbulb_ service. Once the BLE central device is setup, it connects to this characteristic and exposes it via Homebridge as a HomeKit accessory of type _Lightbulb_. The sketch also contains some logic to convert HSV colors to RGB values for each LED. Leave the device powered on and the sketch running while you setup the Homebridge server. The sketch has some built-in logging, so keeping the Serial monitor open may be helpful for debugging. ## BLE Central & Homebridge Server (Raspberry Pi 3 or Other Compatible Box) For help installing an operating system on your new Pi, the official documentation contains a couple of [nice videos](https://www.raspberrypi.org/help/videos/). ### Wiring No wiring except for the micro-USB cable providing power is needed. The Pi needs to be connected to the same router (subnet) as the Apple device you plan to use. It doesn't matter whether via Wifi or Ethernet. Otherwise, you won't be able discover and connect to the Homebridge server running on the Pi. **Note** _Alternatively, you can use a Raspberry Pi 2 with a supported USB BLE dongle instead of the Pi 3._ ### Running Homebridge Running Homebridge on a Raspberry Pi is straightforward. Follow [this guide](https://github.com/nfarina/homebridge/wiki/Running-HomeBridge-on-a-Raspberry-Pi) to install Homebridge server and then run the following command to install the homebridge-bluetooth plugin: ```sh [sudo] npm install -g homebridge-bluetooth ``` Edit the `~/.homebridge/config.json`, name your Homebridge server and add a new accessory to allow the plugin to connect to the BLE service running on the Arduino: ```js "name": "Arduino", "address": "01:23:45:67:89:AB", "services": [ { "name": "RGB LED", "type": "Lightbulb", "UUID": "57E54BF0-8574-47BE-9C1D-A0DBFC8FA183", "characteristics": [ { "type": "On", "UUID": "57E54BF1-8574-47BE-9C1D-A0DBFC8FA183" }, { "type": "Brightness", "UUID": "57E54BF2-8574-47BE-9C1D-A0DBFC8FA183" }, { "type": "Saturation", "UUID": "857E54BF3-8574-47BE-9C1D-A0DBFC8FA183" }, { "type": "Hue", "UUID": "857E54BF4-8574-47BE-9C1D-A0DBFC8FA183" } ] } ] ``` Finally, start the Homebridge server. If you use Linux you may need to run with higher privileges in order to have access to the BLE hardware layer. See [this link](https://github.com/sandeepmistry/noble#running-without-rootsudo) for more details about running without `sudo`. ``` [sudo] homebridge -D ``` **Note** _Running with `-D` turns on additional debugging output that is very helpful for getting addresses and UUIDs of your BLE devices that needs to match with the `config.json` file._ **Note** _Homebridge server doesn't run only on Linux. MacOS and Windows machines are also supported given they have a built-in BLE adapter or an USB dongle. For more details see supported platforms of [Homebridge](https://github.com/nfarina/homebridge) and [Noble](https://github.com/sandeepmistry/noble)._ ## Apple Device ### Pairing Open Home app and tap the '+' button to add new accessory. When you attempt to add the 'Raspberry Pi 3' bridge, it will ask for a "PIN" from the `config.json` file. Once you are paired with your new Rapsberry Homebridge server all the Arduino accesory can be added the same way as the bridge. ### Interacting Once your BLE accessory has been added to HomeKit database, besides using the Home app or Control Center at the bottom of the screen, you should be able to tell Siri to control any HomeKit accessory. Try _"Hey Siri, dim RGB LED to 50%"_. However, Siri is a cloud service and iOS may need some time to synchronize your HomeKit database to iCloud. ================================================ FILE: examples/lock/arduino101/arduino101.ino ================================================ #include #include #include "sensor.h" #include "motor.h" #include "lock.h" static struct BoardPins { const uint8_t Motor1 = 1; const uint8_t Motor2 = 2; const uint8_t MotorPWM = 3; const uint8_t Sensor1A = 4; const uint8_t Sensor1B = 5; const uint8_t Sensor2A = 6; const uint8_t Sensor2B = 7; } Pin; BLEPeripheral ble; BLEService informationService("180A"); BLECharacteristic modelCharacteristic("2A24", BLERead, "Arduino 101"); BLECharacteristic manufacturerCharacteristic("2A29", BLERead, "Lockitron"); BLECharacteristic serialNumberCharacteristic("2A25", BLERead, "V1"); BLEService lockMechanismService("43AAF900-5FF0-4633-95A2-FE4189EE103B"); BLEUnsignedCharCharacteristic targetStateCharacteristic("43AAF901-5FF0-4633-95A2-FE4189EE103B", BLEWrite | BLERead | BLENotify); BLEUnsignedCharCharacteristic currentStateCharacteristic("43AAF902-5FF0-4633-95A2-FE4189EE103B", BLERead | BLENotify); Sensor sensor1A(Pin.Sensor1A); Sensor sensor1B(Pin.Sensor1B); Sensor sensor2A(Pin.Sensor2A); Sensor sensor2B(Pin.Sensor2B); Motor motor(Pin.Motor1, Pin.Motor2, Pin.MotorPWM, 128); Lock lock = Lock(sensor1A, sensor1B, sensor2A, sensor2B, motor, true); volatile bool targetStateUpdate = false; void targetStateWritten(BLECentral& /*central*/, BLECharacteristic& /*characteristic*/) { targetStateUpdate = true; } void setup() { Serial.begin(115200); ble.setLocalName("Lock"); ble.addAttribute(informationService); ble.addAttribute(modelCharacteristic); ble.addAttribute(manufacturerCharacteristic); ble.addAttribute(serialNumberCharacteristic); ble.addAttribute(lockMechanismService); ble.addAttribute(targetStateCharacteristic); ble.addAttribute(currentStateCharacteristic); targetStateCharacteristic.setEventHandler(BLEWritten, targetStateWritten); ble.begin(); lock.begin(); } void loop() { ble.poll(); delay(200); if (targetStateUpdate) { lock.set(targetStateCharacteristic.valueLE()); currentStateCharacteristic.setValueLE(lock.state()); targetStateUpdate = false; } if (lock.state() != currentStateCharacteristic.valueLE()) { targetStateCharacteristic.setValueLE(lock.state()); currentStateCharacteristic.setValueLE(lock.state()); } } ================================================ FILE: examples/lock/arduino101/arduino101.sch ================================================ <b>Lithium Batteries and NC Accus</b><p> <author>Created by librarian@cadsoft.de</author> <b>LI BATTERY</b> Varta >NAME Lithium 3V CR-AA >VALUE >NAME >VALUE <b>LI BATTERY</b> Varta <b>Supply Symbols</b><p> GND, VCC, 0V, +5V, -5V, etc.<p> Please keep in mind, that these devices are necessary for the automatic wiring of the supply signals.<p> The pin name defined in the symbol is identical to the net which is to be wired automatically.<p> In this library the device names are the same as the pin names of the symbols, therefore the correct signal names appear next to the supply symbols in the schematic.<p> <author>Created by librarian@cadsoft.de</author> >VALUE >VALUE <b>SUPPLY SYMBOL</b> <b>SUPPLY SYMBOL</b> <h3>SparkFun Electronics' preferred foot prints</h3> In this library you'll find anything that moves- switches, relays, buttons, potentiometers. Also, anything that goes on a board but isn't electrical in nature- screws, standoffs, etc.<br><br> We've spent an enormous amount of time creating and checking these footprints and parts, but it is the end user's responsibility to ensure correctness and suitablity for a given componet or application. If you enjoy using this library, please buy one of our products at www.sparkfun.com. <br><br> <b>Licensing:</b> Creative Commons ShareAlike 4.0 International - https://creativecommons.org/licenses/by-sa/4.0/ <br><br> You are welcome to use this library for commercial purposes. For attribution, we ask that when you begin to sell your device using our footprint, you email us with a link to the product being sold. We want bragging rights that we helped (in a very small part) to create your 8th world wonder. We would like the opportunity to feature your device on our homepage. >NAME >VALUE <b>DPDT Slide Switch SMD</b> www.SparkFun.com SKU : Comp-SMDS >Name >Value >Name >Value >NAME >VALUE <h3>SWITCH-SPDT_KIT</h3> Through-hole SPDT Switch<br> <br> <b>Warning:</b> This is the KIT version of this package. This package has a smaller diameter top stop mask, which doesn't cover the diameter of the pad. This means only the bottom side of the pads' copper will be exposed. You'll only be able to solder to the bottom side. >NAME >VALUE >Name >Value - + <b>BUZZER</b> >NAME >VALUE >NAME >VALUE + <h3>BUZZER-12MM-NS-KIT</h3> Through-hole buzzer<br> <br> <b>Warning:</b> This is the KIT version of this package. This package has a smaller diameter top stop mask, which doesn't cover the diameter of the pad. This means only the bottom side of the pads' copper will be exposed. You'll only be able to solder to the bottom side. >NAME >VALUE + <h3>BUZZER-CCV-KIT</h3> SMD Buzzer<br> <br> <b>Warning:</b> This is the KIT version of this package. This package has longer pads to aid in hand soldering. >NAME >VALUE + >NAME >VALUE >NAME >VALUE >NAME >VALUE <b>SPDT Switch</b><br> Simple slide switch, Spark Fun Electronics SKU : COM-00102<br> DPDT SMT slide switch, AYZ0202, SWCH-08179 <b>Vibration Motor- ROB-08449</b> Physical dimension and wire connections for this powerful vibration motor. Motor has a self adhesive backing. <b>Buzzer 12mm</b> Spark Fun Electronics SKU : Comp-Buzzer <h3>SparkFun Electronics' preferred foot prints</h3> In this library you'll find drivers, regulators, and amplifiers.<br><br> We've spent an enormous amount of time creating and checking these footprints and parts, but it is the end user's responsibility to ensure correctness and suitablity for a given componet or application. If you enjoy using this library, please buy one of our products at www.sparkfun.com. <br><br> <b>Licensing:</b> Creative Commons ShareAlike 4.0 International - https://creativecommons.org/licenses/by-sa/4.0/ <br><br> You are welcome to use this library for commercial purposes. For attribution, we ask that when you begin to sell your device using our footprint, you email us with a link to the product being sold. We want bragging rights that we helped (in a very small part) to create your 8th world wonder. We would like the opportunity to feature your device on our homepage. >NAME >VALUE >NAME >VALUE Toshiba 1A dual motor driver IC-09363 <b>COTO TECHNOLOGY</b><p> Reed switch<br> <author>Created by librarian@cadsoft.de</author> <b>CT10 Series Molded Switch</b><p> Source: www.cotorelay.com .. Coto_Technology__CT10-1530-G1.pdf >NAME >VALUE <b>CT10 Series Molded Switch</b><p> Source: www.cotorelay.com .. Coto_Technology__CT10-1530-G1.pdf >NAME >VALUE <b>CT10 Series Molded Switch</b><p> Source: www.cotorelay.com .. Coto_Technology__CT10-1530-G1.pdf >NAME >VALUE >NAME >VALUE <b>CT10 Series Molded Switch</b><p> Source: www.cotorelay.com .. Coto_Technology__CT10-1530-G1.pdf <h3>SparkFun Electronics' preferred foot prints</h3> In this library you'll find resistors, capacitors, inductors, test points, jumper pads, etc.<br><br> We've spent an enormous amount of time creating and checking these footprints and parts, but it is the end user's responsibility to ensure correctness and suitablity for a given componet or application. If you enjoy using this library, please buy one of our products at www.sparkfun.com. <br><br> <b>Licensing:</b> Creative Commons ShareAlike 4.0 International - https://creativecommons.org/licenses/by-sa/4.0/ <br><br> You are welcome to use this library for commercial purposes. For attribution, we ask that when you begin to sell your device using our footprint, you email us with a link to the product being sold. We want bragging rights that we helped (in a very small part) to create your 8th world wonder. We would like the opportunity to feature your device on our homepage. >NAME >VALUE >NAME >VALUE RES-09333 <h3>SparkFun Electronics' preferred foot prints</h3> In this library you'll find boards and modules: Arduino footprints, breadboards, non-RF modules, etc.<br><br> We've spent an enormous amount of time creating and checking these footprints and parts, but it is the end user's responsibility to ensure correctness and suitablity for a given componet or application. If you enjoy using this library, please buy one of our products at www.sparkfun.com. <br><br> <b>Licensing:</b> Creative Commons ShareAlike 4.0 International - https://creativecommons.org/licenses/by-sa/4.0/ <br><br> You are welcome to use this library for commercial purposes. For attribution, we ask that when you begin to sell your device using our footprint, you email us with a link to the product being sold. We want bragging rights that we helped (in a very small part) to create your 8th world wonder. We would like the opportunity to feature your device on our homepage. Arduino Uno Footprint R3 SCL SDA AREF GND D13 D12 D11 D10 D9 D8 D7 D6 D5 D4 D3 D2 D0/RXI D1/TXO !RESET! 3.3V 5V GND GND VIN A0 A1 A2 A3 A4 A5 IOREF MISO SCK RST GND MOSI 5V SCL SDA AREF GND D13 D12 D11 D10 D9 D8 D7 D6 D5 D4 D3 D2 D0/RXI D1/TXO !RESET! 3.3V 5V GND GND VIN A0 A1 A2 A3 A4 A5 IOREF MISO SCK RST GND MOSI 5V >Name 101 <h3>Arduino R3 Footprint (w/ SPI header)</h3> ORANGE RING BLACK RING BLUE GREEN YELLOW ORANGE BROWN RED GREY WHITE BLACK RED BLACK BLACK RED BATTERY WHITE GREY GREY WHITE GREY BLACK WHITE BLACK RED ================================================ FILE: examples/lock/arduino101/dummy/dummy.ino ================================================ #include #include const int pinStateLED = 13; BLEPeripheral ble; BLEService informationService("180A"); BLECharacteristic modelCharacteristic("2A24", BLERead, "101"); BLECharacteristic manufacturerCharacteristic("2A29", BLERead, "Arduino"); BLECharacteristic serialNumberCharacteristic("2A25", BLERead, "Dummy Testing Lock"); BLEService lockMechanismService("43AAF900-5FF0-4633-95A2-FE4189EE103B"); BLEUnsignedCharCharacteristic targetStateCharacteristic("43AAF901-5FF0-4633-95A2-FE4189EE103B", BLEWrite | BLERead | BLENotify); BLEUnsignedCharCharacteristic currentStateCharacteristic("43AAF902-5FF0-4633-95A2-FE4189EE103B", BLERead | BLENotify); void setup() { Serial.begin(115200); pinMode(pinStateLED, OUTPUT); ble.setLocalName("Lock"); ble.setAdvertisedServiceUuid(lockMechanismService.uuid()); ble.addAttribute(lockMechanismService); ble.addAttribute(targetStateCharacteristic); ble.addAttribute(currentStateCharacteristic); targetStateCharacteristic.setValueLE(0); currentStateCharacteristic.setValueLE(0); digitalWrite(pinStateLED, 0); ble.setEventHandler(BLEConnected, centralConnect); ble.setEventHandler(BLEDisconnected, centralDisconnect); ble.begin(); Serial.println("Bluetooth on"); } void loop() { ble.poll(); if (targetStateCharacteristic.valueLE() != currentStateCharacteristic.valueLE()) { unsigned char state = targetStateCharacteristic.valueLE(); delay(500); digitalWrite(pinStateLED, (bool) state); currentStateCharacteristic.setValueLE(state); Serial.print("Lock state | "); Serial.println(state); } } void centralConnect(BLECentral& central) { Serial.print("Central connected | "); Serial.println(central.address()); } void centralDisconnect(BLECentral& central) { Serial.print("Central disconnected | "); Serial.println(central.address()); } ================================================ FILE: examples/lock/arduino101/lock.cpp ================================================ #include "lock.h" //#define DEBUG(message) Serial.print(message) #define DEBUG(message) Lock::Lock(Sensor& sensor1A_, Sensor& sensor1B_, Sensor& sensor2A_, Sensor& sensor2B_, Motor& motor_, const bool lockClockwise_) : working(false), sensor1A(sensor1A_), sensor1B(sensor1B_), sensor2A(sensor2A_), sensor2B(sensor2B_), motor(motor_), lockClockwise(lockClockwise_) { } void Lock::begin() { DEBUG("Lock::begin()\n"); motor.begin(); rotate(Position.Neutral, Position.Any, false, false); lastState = Position.Unlocked; } uint8_t Lock::state() { switch (orange()) { case Position.Jammed: lastState = Position.Jammed; return State.Jammed; case Position.Unlocked: if (lastState != Position.Unlocked) { DEBUG("Lock::state() = Unsecured (Manually)\n"); lastState = Position.Unlocked; } return State.Unsecured; case Position.Locked: if (lastState != Position.Locked) { DEBUG("Lock::state() = Secured (Manually)\n"); lastState = Position.Locked; } return State.Secured; case Position.Unknown: return lastState; } } bool Lock::set(uint8_t state) { switch (state) { case State.Secured: DEBUG("Lock::set(Secured)\n"); if (!rotate(Position.Any, Position.Locked, lockClockwise)) { DEBUG("Full lock failed\n"); return false; } delay(Delay.DirectionChange); if (!rotate(Position.Neutral, Position.Any, !lockClockwise)) { DEBUG("Parking back to neutral failed\n"); return false; } lastState = state; return true; case State.Unsecured: DEBUG("Lock::set(Unsecured)\n"); if (!rotate(Position.Any, Position.Unlocked, !lockClockwise)) { DEBUG("Full unlock failed\n"); return false; } delay(Delay.DirectionChange); if (!rotate(Position.Neutral, Position.Any, lockClockwise)) { DEBUG("Parking back to neutral failed\n"); return false; } lastState = state; return true; default: DEBUG("Lock::set(Neutral)\n"); rotate(Position.Neutral, Position.Any, false, false); lastState = state; return true; } } bool Lock::rotate(uint8_t blck, uint8_t orng, bool clockwise, bool sure) { DEBUG("Lock::rotate(blck="); DEBUG(blck); DEBUG(", orng="); DEBUG(orng); DEBUG(", clkws="); DEBUG(clockwise); DEBUG(")\n"); static bool recursion = false; working = true; timeout = millis(); motor.start(clockwise); do { if ((!sure) && (blck == Position.Neutral)) { if ((black() == Position.Locked) || (black() == Position.Unlocked)) { /* Wrong direction while trying to reach mid-point, back out */ DEBUG("Reverse\n"); motor.stop(); working = false; delay(Delay.DirectionChange); if (!recursion) { bool direction = (black() == Position.Unlocked); bool retval = rotate(Position.Neutral, Position.Any, direction); return retval; } } } if (black() == Position.Jammed) { /* Ooooppss... */ DEBUG("Jammed!!!\n"); motor.stop(); delay(Delay.DirectionChange); if (!recursion) { recursion = true; uint32_t keep = timeout; rotate(Position.Neutral, Position.Any, !clockwise); recursion = false; timeout = keep; working = true; } return false; } /* Keep cranking... */ } while ( ((black() != blck) || (blck == Position.Any)) && ((orange() != orng) || (orng == Position.Any)) ); if ((orng == Position.Locked) || (orng == Position.Unlocked)) { /* Keep cranking for a bit longer when locking/unlocking to fully extend/retract the bolt */ delay(Delay.Override); } motor.stop(); working = false; return true; } uint8_t Lock::black() { DEBUG("Lock:black(2A="); DEBUG(sensor2A); DEBUG(", 2B="); DEBUG(sensor2B); DEBUG(")"); if (working && ((millis() - timeout) > Delay.Timeout)) { DEBUG(" = Jammed\n"); return Position.Jammed; } if (lockClockwise) { if ((sensor2A == HIGH) && (sensor2B == HIGH)) { /* Black ring parked at neutral mid-point */ DEBUG(" = Neutral\n"); return Position.Neutral; } if ((sensor2A == LOW ) && (sensor2B == HIGH)) { /* Black ring rotated all the way clockwise */ DEBUG(" = Locked\n"); return Position.Locked; } if ((sensor2A == HIGH) && (sensor2B == LOW )) { /* Black ring rotated all the way counter-clockwise */ DEBUG(" = Unlocked\n"); return Position.Unlocked; } DEBUG(" = Unknown\n"); return Position.Unknown; } else { if ((sensor2A == HIGH) && (sensor2B == HIGH)) { /* Black ring parked at neutral mid-point */ DEBUG(" = Neutral\n"); return Position.Neutral; } if ((sensor2A == LOW ) && (sensor2B == HIGH)) { /* Black ring rotated all the way clockwise */ DEBUG(" = Unlocked\n"); return Position.Unlocked; } if ((sensor2A == HIGH) && (sensor2B == LOW )) { /* Black ring rotated all the way counter-clockwise */ DEBUG(" = Locked\n"); return Position.Locked; } DEBUG(" = Unknown\n"); return Position.Unknown; } } uint8_t Lock::orange() { if (working && ((millis() - timeout) > Delay.Timeout)) { return Position.Jammed; } if (lockClockwise) { if ((sensor1A == HIGH) && (sensor1B == LOW )) { /* Orange ring rotated all the way clockwise relative to the black ring */ return Position.Unlocked; } if ((sensor1A == LOW ) && (sensor1B == HIGH)) { /* Orange ring rotated all the way counter-clockwise relative to the black ring */ return Position.Locked; } return Position.Unknown; } else { if ((sensor1A == HIGH) && (sensor1B == LOW )) { /* Orange ring rotated all the way clockwise relative to the black ring */ return Position.Locked; } if ((sensor1A == LOW ) && (sensor1B == HIGH)) { /* Orange ring rotated all the way counter-clockwise relative to the black ring */ return Position.Unlocked; } return Position.Unknown; } } ================================================ FILE: examples/lock/arduino101/lock.h ================================================ #ifndef _LOCK_H_ #define _LOCK_H_ #include #include "sensor.h" #include "motor.h" class Lock { public: Lock(Sensor& sensor1A_, Sensor& sensor1B_, Sensor& sensor2A_, Sensor& sensor2B_, Motor& motor_, const bool lockClockwise_); void begin(); uint8_t state(void); bool set(uint8_t state); static struct LockState { // HAP-NodeJS/lib/gen/HomeKitTypes.js #897 static const uint8_t Unsecured = 0; static const uint8_t Secured = 1; static const uint8_t Jammed = 2; static const uint8_t Unknown = 3; } State; private: bool rotate(uint8_t blck, uint8_t orng, bool clockwise = true, bool sure = true); uint8_t orange(); uint8_t black(); static struct RingPosition { static const uint8_t Unlocked = 0; static const uint8_t Locked = 1; static const uint8_t Jammed = 2; static const uint8_t Unknown = 3; static const uint8_t Neutral = 4; static const uint8_t Any = ~0; } Position; static struct LockTimeDelay { static const uint32_t Timeout = 3000; // milliseconds static const uint32_t DirectionChange = 300; // milliseconds static const uint32_t Override = 500; // milliseconds - needs to be tuned for each lock } Delay; bool working; uint32_t timeout; // milliseconds - overflows after ~50 days uint8_t lastState; Sensor& sensor1A; Sensor& sensor1B; Sensor& sensor2A; Sensor& sensor2B; Motor& motor; const bool lockClockwise; }; #endif ================================================ FILE: examples/lock/arduino101/motor.cpp ================================================ #include "motor.h" // #define DEBUG(message) Serial.print(message) #define DEBUG(message) Motor::Motor(const uint32_t minusPin_, const uint32_t plusPin_, const uint32_t pwmPin_, const uint8_t pwm_) : minusPin(minusPin_), plusPin(plusPin_), pwmPin(pwmPin_), pwm(pwm_) { } void Motor::begin() { pinMode(minusPin, OUTPUT); pinMode(plusPin, OUTPUT); pinMode(pwmPin, OUTPUT); } void Motor::start(bool clockwise) { analogWrite(pwmPin, pwm); if (clockwise) { digitalWrite(minusPin, HIGH); digitalWrite(plusPin, LOW); } else { digitalWrite(minusPin, LOW); digitalWrite(plusPin, HIGH); } DEBUG("Motor::start(clkws="); DEBUG(clockwise); DEBUG(")\n"); } void Motor::stop() { analogWrite(pwmPin, 0); digitalWrite(minusPin, LOW); digitalWrite(plusPin, LOW); DEBUG("Motor::stop()\n"); } Motor::~Motor() { stop(); DEBUG("Motor::destructor()\n"); } ================================================ FILE: examples/lock/arduino101/motor.h ================================================ #ifndef _MOTOR_H_ #define _MOTOR_H_ #include class Motor { public: Motor(const uint32_t minusPin_, const uint32_t plusPin_, const uint32_t pwmPin_, const uint8_t pwm_); ~Motor(); void begin(); void start(bool clockwise = true); void stop(); private: const uint32_t minusPin; const uint32_t plusPin; const uint32_t pwmPin; const uint8_t pwm; }; #endif ================================================ FILE: examples/lock/arduino101/sensor.cpp ================================================ #include "sensor.h" // #define DEBUG(message) Serial.print(message) #define DEBUG(message) Sensor::Sensor(uint32_t pin_) : pin(pin_) { pinMode(pin, INPUT); } Sensor::operator int() { int retval = digitalRead(pin); DEBUG("Sensor::int(pin="); DEBUG(pin); DEBUG(", val="); DEBUG(retval); DEBUG(")\n"); return retval; } ================================================ FILE: examples/lock/arduino101/sensor.h ================================================ #ifndef _SENSOR_H_ #define _SENSOR_H_ #include class Sensor { public: Sensor(uint32_t pin_); operator int(); private: const uint32_t pin; }; #endif ================================================ FILE: examples/lock/config.json ================================================ { "bridge": { "name": "Raspberry Pi 3", "username": "CC:22:3D:E3:CE:30", "port": 51826, "pin": "314-15-926" }, "description": "Raspberry Pi 3 Homebridge-Bluetooth RGB Lightbulb Example", "platforms": [ { "platform": "Bluetooth", "accessories": [ { "name": "Arduino", "address": "01:23:45:67:89:AB", "services": [ { "name": "Lock", "type": "LockMechanism", "UUID": "43AAF900-5FF0-4633-95A2-FE4189EE103B", "characteristics": [ { "type": "LockTargetState", "UUID": "43AAF901-5FF0-4633-95A2-FE4189EE103B" }, { "type": "LockCurrentState", "UUID": "43AAF902-5FF0-4633-95A2-FE4189EE103B" } ] } ] } ] } ] } ================================================ FILE: examples/lock/readme.md ================================================ # Lock Turn an old _Lockitron V1_ body connected to a BLE capable microprocessor into a wireless HomeKit lock. Use the Home app or Siri on your Apple device to secure and unsecure the lock. This example uses [Arduino 101](https://www.arduino.cc/en/Main/ArduinoBoard101) or [RFduino](http://www.rfduino.com/) and [Raspberry Pi 3](https://www.raspberrypi.org/). Generally, any programmable BLE peripheral and a box capable of running [Node.js](https://nodejs.org) with [Noble](https://github.com/sandeepmistry/noble) will work. You'll also need a [H-Bridge breakout](https://www.adafruit.com/product/2448) to power the motor and an old _Lockitron V1_ body . It's sold by [Sparkfun](https://www.sparkfun.com/products/retired/13648), [Adafruit](https://www.adafruit.com/products/2579) or [eBay](http://www.ebay.com/sch/i.html?_from=R40&_trksid=p2050601.m570.l1313.TR12.TRC2.A0.H0.Xlockitron.TRS0&_nkw=lockitron&_sacat=0) :). ## Lock Mechanism ### Installation Here's the official [Installation Manual](https://cdn.sparkfun.com/datasheets/Components/General/installation_manual_lockitron.pdf). Or if you preffer here's the [Installation Video](https://www.youtube.com/watch?v=ZOJhfsSCoYs). [![Installation Video](https://img.youtube.com/vi/ZOJhfsSCoYs/0.jpg)](https://www.youtube.com/watch?v=ZOJhfsSCoYs) ### Wiring Here's how the lock looks inside, when you take off the black cover. The lock uses tactile switch sensors to detect position of the orange and black rings. Sparkfun has a nice article about the hookup [here](https://learn.sparkfun.com/tutorials/lockitron-hardware-hookup-guide?_ga=1.268388430.1924023418.1474989120). I've borrowed some pics from them. Generally, you want to solder wire to each sensor and possibly to extend the battery and motor wires to reach outside of the case.       If you use _RFDuino_ and want to power the whole thing from the batteries, you'll want add a voltage regulator. Theoretically, you could add another wire to the battery pack and just use the voltage of two AA batteries, but the motor is quite hungry and eats about 2 A when running. The voltage of the two batteries drop below 1.9 V and the poor _RFduino_ resets. This doesn't happen with the regulator. I've managed to put the voltage regulator on the [proto shield](http://www.rfduino.com/product/rfd22125-proto-shield-for-rfduino/index.html). If you cut and throw away some plastics, it's possible to squeeze the _RFDuino_ inside of the original case. ## BLE Accessory (Arduino 101, RFDuino or Other BLE Board) Download and install the latest version of the [Arduino IDE](https://www.arduino.cc/en/Main/Software). If you're totally new to microcontrollers take some time to go through an introductory tutorial and learn how to make a LED blink. This will help you to understand how to use the IDE, how to upload a sketch and what is the code actually doing. ### Wiring Connect your board to the H-Bridge breakout, voltage regulator and lock according the the following schematics. This may, again, require some soldering.       **Note** _Alternatively, you can use any of the many BLE boards available on the market ([BlueBean](https://punchthrough.com/bean/), [RedBearLabs BLE Nano](http://redbearlab.com/blenano), ...) as long as you keep UUIDs of the services and characteristics in sync with your `config.json` file, everything will work just fine._ ### Running the Sketch Compile, run and upload the [arduino101.ino](arduino101/arduino101.ino) or [rfduino.ino](rfduino/rfduino.ino) sketch using the [Arduino IDE](https://www.arduino.cc/en/Main/Software). The sketch creates a BLE service with 2 characteristics. There's one characteristic for the lock target state (`Unsecured/Secured`) and the other for the lock current state (`Unsecured/Secured/Jammed/Unknown`). Only the target characteristic is writeable ans is used to trigger the locking mechanism. ```cpp BLEService lockMechanismService("43AAF900-5FF0-4633-95A2-FE4189EE103B"); BLEUnsignedCharCharacteristic targetStateCharacteristic("43AAF901-5FF0-4633-95A2-FE4189EE103B", BLEWrite | BLERead | BLENotify); BLEUnsignedCharCharacteristic currentStateCharacteristic("43AAF902-5FF0-4633-95A2-FE4189EE103B", BLERead | BLENotify); ``` When the tactile switch is toggled the LEDs turn on (or turn off if they were on) and the BLE subscribe-notification mechanism cases the an update update on the Homebridge. This way the information about switching propagates through callbacks to the Apple device without any polling. Take a look into [this file](https://github.com/KhaosT/HAP-NodeJS/blob/master/lib/gen/HomeKitTypes.js#L2896) to see the full definition of the _LockMechanism_ service. Once the BLE central device is setup, it connects to this characteristic and exposes it via Homebridge as a HomeKit accessory of type _LockMechanism_. The sketch also contains some logic to handle jamming and other corner cases. Leave the device powered on and the sketch running while you setup the Homebridge server. The sketch has some built-in logging, so keeping the Serial monitor open may be helpful for debugging. ## BLE Central & Homebridge Server (Raspberry Pi 3 or Other Compatible Box) For help installing an operating system on your new Pi, the official documentation contains a couple of [nice videos](https://www.raspberrypi.org/help/videos/). ### Wiring No wiring except for the micro-USB cable providing power is needed. The Pi needs to be connected to the same router (subnet) as the Apple device you plan to use. It doesn't matter whether via Wifi or Ethernet. Otherwise, you won't be able discover and connect to the Homebridge server running on the Pi. **Note** _Alternatively, you can use a Raspberry Pi 2 with a supported USB BLE dongle instead of the Pi 3._ ### Running Homebridge Running Homebridge on a Raspberry Pi is straightforward. Follow [this guide](https://github.com/nfarina/homebridge/wiki/Running-HomeBridge-on-a-Raspberry-Pi) to install Homebridge server and then run the following command to install the homebridge-bluetooth plugin: ```sh [sudo] npm install -g homebridge-bluetooth ``` Edit the `~/.homebridge/config.json`, name your Homebridge server and add a new accessory to allow the plugin to connect to the BLE service running on the Arduino: ```js "name": "Arduino", "address": "01:23:45:67:89:AB", "services": [ { "name": "Lock", "type": "LockMechanism", "UUID": "43AAF900-5FF0-4633-95A2-FE4189EE103B", "characteristics": [ { "type": "LockTargetState", "UUID": "43AAF901-5FF0-4633-95A2-FE4189EE103B" }, { "type": "LockCurrentState", "UUID": "43AAF902-5FF0-4633-95A2-FE4189EE103B" } ] } ] ``` Finally, start the Homebridge server. If you use Linux you may need to run with higher privileges in order to have access to the BLE hardware layer. See [this link](https://github.com/sandeepmistry/noble#running-without-rootsudo) for more details about running without `sudo`. ``` [sudo] homebridge -D ``` **Note** _Running with `-D` turns on additional debugging output that is very helpful for getting addresses and UUIDs of your BLE devices that needs to match with the `config.json` file._ **Note** _Homebridge server doesn't run only on Linux. MacOS and Windows machines are also supported given they have a built-in BLE adapter or an USB dongle. For more details see supported platforms of [Homebridge](https://github.com/nfarina/homebridge) and [Noble](https://github.com/sandeepmistry/noble)._ ## Apple Device ### Pairing Open Home app and tap the '+' button to add new accessory. When you attempt to add the 'Raspberry Pi 3' bridge, it will ask for a "PIN" from the `config.json` file. Once you are paired with your new Rapsberry Homebridge server all the Arduino accesories are added at the same time as the bridge. ### Interacting Once your BLE accessory has been added to HomeKit database, besides using the Home app or Control Center at the bottom of the screen, you should be able to tell Siri to control any HomeKit accessory. Try _"Hey Siri, lock the Arduino"_. However, Siri is a cloud service and iOS may need some time to synchronize your HomeKit database to iCloud. ================================================ FILE: examples/lock/rfduino/lock.cpp ================================================ #include "lock.h" // #define DEBUG(message) Serial.print(message) #define DEBUG(message) Lock::Lock(Sensor& sensor1A_, Sensor& sensor1B_, Sensor& sensor2A_, Sensor& sensor2B_, Motor& motor_, const bool lockClockwise_) : working(false), sensor1A(sensor1A_), sensor1B(sensor1B_), sensor2A(sensor2A_), sensor2B(sensor2B_), motor(motor_), lockClockwise(lockClockwise_) { } void Lock::begin() { DEBUG("Lock::begin()\n"); motor.begin(); rotate(Position.Neutral, Position.Any, false, false); lastState = Position.Unlocked; } uint8_t Lock::state() { uint8_t orng = orange(); if (orng == Position.Jammed) { lastState = Position.Jammed; return State.Jammed; } else if (orng == Position.Unlocked) { if (lastState != Position.Unlocked) { DEBUG("Lock::state() = Unsecured (Manually)\n"); lastState = Position.Unlocked; } return State.Unsecured; } else if (orng == Position.Locked) { if (lastState != Position.Locked) { DEBUG("Lock::state() = Secured (Manually)\n"); lastState = Position.Locked; } return State.Secured; } else if (orng == Position.Unknown) { return lastState; } } bool Lock::set(uint8_t state) { if (state == State.Secured) { DEBUG("Lock::set(Secured)\n"); if (!rotate(Position.Any, Position.Locked, lockClockwise)) { DEBUG("Full lock failed\n"); return false; } delay(Delay.DirectionChange); if (!rotate(Position.Neutral, Position.Any, !lockClockwise)) { DEBUG("Parking back to neutral failed\n"); return false; } lastState = state; return true; } else if (state == State.Unsecured) { DEBUG("Lock::set(Unsecured)\n"); if (!rotate(Position.Any, Position.Unlocked, !lockClockwise)) { DEBUG("Full unlock failed\n"); return false; } delay(Delay.DirectionChange); if (!rotate(Position.Neutral, Position.Any, lockClockwise)) { DEBUG("Parking back to neutral failed\n"); return false; } lastState = state; return true; } else { DEBUG("Lock::set(Neutral)\n"); rotate(Position.Neutral, Position.Any, false, false); lastState = state; return true; } } bool Lock::rotate(uint8_t blck, uint8_t orng, bool clockwise, bool sure) { DEBUG("Lock::rotate(blck="); DEBUG(blck); DEBUG(", orng="); DEBUG(orng); DEBUG(", clkws="); DEBUG(clockwise); DEBUG(")\n"); static bool recursion = false; working = true; timeout = millis(); motor.start(clockwise); do { if ((!sure) && (blck == Position.Neutral)) { if ((black() == Position.Locked) || (black() == Position.Unlocked)) { /* Wrong direction while trying to reach mid-point, back out */ DEBUG("Reverse\n"); motor.stop(); working = false; delay(Delay.DirectionChange); if (!recursion) { bool direction = (black() == Position.Unlocked); bool retval = rotate(Position.Neutral, Position.Any, direction); return retval; } } } if (black() == Position.Jammed) { /* Ooooppss... */ DEBUG("Jammed!!!\n"); motor.stop(); delay(Delay.DirectionChange); if (!recursion) { recursion = true; uint32_t keep = timeout; rotate(Position.Neutral, Position.Any, !clockwise); recursion = false; timeout = keep; working = true; } return false; } /* Keep cranking... */ } while ( ((black() != blck) || (blck == Position.Any)) && ((orange() != orng) || (orng == Position.Any)) ); if ((orng == Position.Locked) || (orng == Position.Unlocked)) { /* Keep cranking for a bit longer when locking/unlocking to fully extend/retract the bolt */ delay(Delay.Override); } motor.stop(); working = false; return true; } uint8_t Lock::black() { DEBUG("Lock:black(2A="); DEBUG(sensor2A); DEBUG(", 2B="); DEBUG(sensor2B); DEBUG(")"); if (working && ((millis() - timeout) > Delay.Timeout)) { DEBUG(" = Jammed\n"); return Position.Jammed; } if (lockClockwise) { if ((sensor2A == HIGH) && (sensor2B == HIGH)) { /* Black ring parked at neutral mid-point */ DEBUG(" = Neutral\n"); return Position.Neutral; } if ((sensor2A == LOW ) && (sensor2B == HIGH)) { /* Black ring rotated all the way clockwise */ DEBUG(" = Locked\n"); return Position.Locked; } if ((sensor2A == HIGH) && (sensor2B == LOW )) { /* Black ring rotated all the way counter-clockwise */ DEBUG(" = Unlocked\n"); return Position.Unlocked; } DEBUG(" = Unknown\n"); return Position.Unknown; } else { if ((sensor2A == HIGH) && (sensor2B == HIGH)) { /* Black ring parked at neutral mid-point */ DEBUG(" = Neutral\n"); return Position.Neutral; } if ((sensor2A == LOW ) && (sensor2B == HIGH)) { /* Black ring rotated all the way clockwise */ DEBUG(" = Unlocked\n"); return Position.Unlocked; } if ((sensor2A == HIGH) && (sensor2B == LOW )) { /* Black ring rotated all the way counter-clockwise */ DEBUG(" = Locked\n"); return Position.Locked; } DEBUG(" = Unknown\n"); return Position.Unknown; } } uint8_t Lock::orange() { if (working && ((millis() - timeout) > Delay.Timeout)) { return Position.Jammed; } if (lockClockwise) { if ((sensor1A == HIGH) && (sensor1B == LOW )) { /* Orange ring rotated all the way clockwise relative to the black ring */ return Position.Unlocked; } if ((sensor1A == LOW ) && (sensor1B == HIGH)) { /* Orange ring rotated all the way counter-clockwise relative to the black ring */ return Position.Locked; } return Position.Unknown; } else { if ((sensor1A == HIGH) && (sensor1B == LOW )) { /* Orange ring rotated all the way clockwise relative to the black ring */ return Position.Locked; } if ((sensor1A == LOW ) && (sensor1B == HIGH)) { /* Orange ring rotated all the way counter-clockwise relative to the black ring */ return Position.Unlocked; } return Position.Unknown; } } ================================================ FILE: examples/lock/rfduino/lock.h ================================================ #ifndef _LOCK_H_ #define _LOCK_H_ #include #include "sensor.h" #include "motor.h" class Lock { public: Lock(Sensor& sensor1A_, Sensor& sensor1B_, Sensor& sensor2A_, Sensor& sensor2B_, Motor& motor_, const bool lockClockwise_); void begin(); uint8_t state(void); bool set(uint8_t state); const static struct LockState { // HAP-NodeJS/lib/gen/HomeKitTypes.js #897 static const uint8_t Unsecured = 0; static const uint8_t Secured = 1; static const uint8_t Jammed = 2; static const uint8_t Unknown = 3; } State; private: bool rotate(uint8_t blck, uint8_t orng, bool clockwise = true, bool sure = true); uint8_t orange(); uint8_t black(); const static struct RingPosition { static const uint8_t Unlocked = 0; static const uint8_t Locked = 1; static const uint8_t Jammed = 2; static const uint8_t Unknown = 3; static const uint8_t Neutral = 4; static const uint8_t Any = ~0; } Position; const static struct LockTimeDelay { static const uint32_t Timeout = 3000; // milliseconds static const uint32_t DirectionChange = 300; // milliseconds static const uint32_t Override = 500; // milliseconds - needs to be tuned for each lock } Delay; bool working; uint32_t timeout; // milliseconds - overflows after ~50 days uint8_t lastState; Sensor& sensor1A; Sensor& sensor1B; Sensor& sensor2A; Sensor& sensor2B; Motor& motor; const bool lockClockwise; }; #endif ================================================ FILE: examples/lock/rfduino/motor.cpp ================================================ #include "motor.h" // #define DEBUG(message) Serial.print(message) #define DEBUG(message) Motor::Motor(const uint32_t minusPin_, const uint32_t plusPin_, const uint32_t pwmPin_, const uint8_t pwm_) : minusPin(minusPin_), plusPin(plusPin_), pwmPin(pwmPin_), pwm(pwm_) { } void Motor::begin() { pinMode(minusPin, OUTPUT); pinMode(plusPin, OUTPUT); pinMode(pwmPin, OUTPUT); } void Motor::start(bool clockwise) { analogWrite(pwmPin, pwm); if (clockwise) { digitalWrite(minusPin, HIGH); digitalWrite(plusPin, LOW); } else { digitalWrite(minusPin, LOW); digitalWrite(plusPin, HIGH); } DEBUG("Motor::start(clkws="); DEBUG(clockwise); DEBUG(")\n"); } void Motor::stop() { analogWrite(pwmPin, 0); digitalWrite(minusPin, LOW); digitalWrite(plusPin, LOW); DEBUG("Motor::stop()\n"); } Motor::~Motor() { stop(); DEBUG("Motor::destructor()\n"); } ================================================ FILE: examples/lock/rfduino/motor.h ================================================ #ifndef _MOTOR_H_ #define _MOTOR_H_ #include class Motor { public: Motor(const uint32_t minusPin_, const uint32_t plusPin_, const uint32_t pwmPin_, const uint8_t pwm_); ~Motor(); void begin(); void start(bool clockwise = true); void stop(); private: const uint32_t minusPin; const uint32_t plusPin; const uint32_t pwmPin; const uint8_t pwm; }; #endif ================================================ FILE: examples/lock/rfduino/rfduino.ino ================================================ #include #include #include "sensor.h" #include "motor.h" #include "lock.h" static struct BoardPins { const uint8_t Motor1 = 0; const uint8_t Motor2 = 1; const uint8_t MotorPWM = 2; const uint8_t Sensor1A = 3; const uint8_t Sensor1B = 4; const uint8_t Sensor2A = 5; const uint8_t Sensor2B = 6; } Pin; BLEPeripheral ble; BLEService informationService("180A"); BLECharacteristic modelCharacteristic("2A24", BLERead, "RFduino"); BLECharacteristic manufacturerCharacteristic("2A29", BLERead, "Lockitron"); BLECharacteristic serialNumberCharacteristic("2A25", BLERead, "V1"); BLEService lockMechanismService("43AAF900-5FF0-4633-95A2-FE4189EE103B"); BLEUnsignedCharCharacteristic targetStateCharacteristic("43AAF901-5FF0-4633-95A2-FE4189EE103B", BLEWrite | BLERead | BLENotify); BLEUnsignedCharCharacteristic currentStateCharacteristic("43AAF902-5FF0-4633-95A2-FE4189EE103B", BLERead | BLENotify); Sensor sensor1A(Pin.Sensor1A); Sensor sensor1B(Pin.Sensor1B); Sensor sensor2A(Pin.Sensor2A); Sensor sensor2B(Pin.Sensor2B); Motor motor(Pin.Motor1, Pin.Motor2, Pin.MotorPWM, 128); Lock lock = Lock(sensor1A, sensor1B, sensor2A, sensor2B, motor, true); volatile bool targetStateUpdate = false; void targetStateWritten(BLECentral& /*central*/, BLECharacteristic& /*characteristic*/) { targetStateUpdate = true; } void setup() { // DEBUG Serial.begin(115200); ble.setLocalName("Lock"); ble.addAttribute(informationService); ble.addAttribute(modelCharacteristic); ble.addAttribute(manufacturerCharacteristic); ble.addAttribute(serialNumberCharacteristic); ble.addAttribute(lockMechanismService); ble.addAttribute(targetStateCharacteristic); ble.addAttribute(currentStateCharacteristic); targetStateCharacteristic.setEventHandler(BLEWritten, targetStateWritten); ble.begin(); lock.begin(); } void loop() { ble.poll(); RFduino_ULPDelay(200); if (targetStateUpdate) { lock.set(targetStateCharacteristic.value()); currentStateCharacteristic.setValue(lock.state()); targetStateUpdate = false; } if (lock.state() != currentStateCharacteristic.value()) { targetStateCharacteristic.setValue(lock.state()); currentStateCharacteristic.setValue(lock.state()); } } ================================================ FILE: examples/lock/rfduino/rfduino.sch ================================================ <b>Lithium Batteries and NC Accus</b><p> <author>Created by librarian@cadsoft.de</author> <b>LI BATTERY</b> Varta >NAME Lithium 3V CR-AA >VALUE >NAME >VALUE <b>LI BATTERY</b> Varta <b>Supply Symbols</b><p> GND, VCC, 0V, +5V, -5V, etc.<p> Please keep in mind, that these devices are necessary for the automatic wiring of the supply signals.<p> The pin name defined in the symbol is identical to the net which is to be wired automatically.<p> In this library the device names are the same as the pin names of the symbols, therefore the correct signal names appear next to the supply symbols in the schematic.<p> <author>Created by librarian@cadsoft.de</author> >VALUE >VALUE >VALUE <b>SUPPLY SYMBOL</b> <b>SUPPLY SYMBOL</b> <b>SUPPLY SYMBOL</b> ANT AREA >NAME >VALUE <b>RFD22102</b> <p> RFduino DIP Module </p> RFduino Datasheet:<br> http://www.rfdigital.com/wp-content/uploads/2014/03/rfduino.datasheet.pdf >NAME >VALUE <b>RFD22102</b> <p> RFduino DIP module </p> RFduino Datasheet: <br> http://www.rfdigital.com/wp-content/uploads/2014/03/rfduino.datasheet.pdf <h3>SparkFun Electronics' preferred foot prints</h3> In this library you'll find anything that moves- switches, relays, buttons, potentiometers. Also, anything that goes on a board but isn't electrical in nature- screws, standoffs, etc.<br><br> We've spent an enormous amount of time creating and checking these footprints and parts, but it is the end user's responsibility to ensure correctness and suitablity for a given componet or application. If you enjoy using this library, please buy one of our products at www.sparkfun.com. <br><br> <b>Licensing:</b> Creative Commons ShareAlike 4.0 International - https://creativecommons.org/licenses/by-sa/4.0/ <br><br> You are welcome to use this library for commercial purposes. For attribution, we ask that when you begin to sell your device using our footprint, you email us with a link to the product being sold. We want bragging rights that we helped (in a very small part) to create your 8th world wonder. We would like the opportunity to feature your device on our homepage. >NAME >VALUE <b>DPDT Slide Switch SMD</b> www.SparkFun.com SKU : Comp-SMDS >Name >Value >Name >Value >NAME >VALUE <h3>SWITCH-SPDT_KIT</h3> Through-hole SPDT Switch<br> <br> <b>Warning:</b> This is the KIT version of this package. This package has a smaller diameter top stop mask, which doesn't cover the diameter of the pad. This means only the bottom side of the pads' copper will be exposed. You'll only be able to solder to the bottom side. >NAME >VALUE >Name >Value - + <b>BUZZER</b> >NAME >VALUE >NAME >VALUE + <h3>BUZZER-12MM-NS-KIT</h3> Through-hole buzzer<br> <br> <b>Warning:</b> This is the KIT version of this package. This package has a smaller diameter top stop mask, which doesn't cover the diameter of the pad. This means only the bottom side of the pads' copper will be exposed. You'll only be able to solder to the bottom side. >NAME >VALUE + <h3>BUZZER-CCV-KIT</h3> SMD Buzzer<br> <br> <b>Warning:</b> This is the KIT version of this package. This package has longer pads to aid in hand soldering. >NAME >VALUE + >NAME >VALUE >NAME >VALUE >NAME >VALUE <b>SPDT Switch</b><br> Simple slide switch, Spark Fun Electronics SKU : COM-00102<br> DPDT SMT slide switch, AYZ0202, SWCH-08179 <b>Vibration Motor- ROB-08449</b> Physical dimension and wire connections for this powerful vibration motor. Motor has a self adhesive backing. <b>Buzzer 12mm</b> Spark Fun Electronics SKU : Comp-Buzzer <h3>SparkFun Electronics' preferred foot prints</h3> In this library you'll find drivers, regulators, and amplifiers.<br><br> We've spent an enormous amount of time creating and checking these footprints and parts, but it is the end user's responsibility to ensure correctness and suitablity for a given componet or application. If you enjoy using this library, please buy one of our products at www.sparkfun.com. <br><br> <b>Licensing:</b> Creative Commons ShareAlike 4.0 International - https://creativecommons.org/licenses/by-sa/4.0/ <br><br> You are welcome to use this library for commercial purposes. For attribution, we ask that when you begin to sell your device using our footprint, you email us with a link to the product being sold. We want bragging rights that we helped (in a very small part) to create your 8th world wonder. We would like the opportunity to feature your device on our homepage. >NAME >VALUE >NAME >VALUE Toshiba 1A dual motor driver IC-09363 <b>COTO TECHNOLOGY</b><p> Reed switch<br> <author>Created by librarian@cadsoft.de</author> <b>CT10 Series Molded Switch</b><p> Source: www.cotorelay.com .. Coto_Technology__CT10-1530-G1.pdf >NAME >VALUE <b>CT10 Series Molded Switch</b><p> Source: www.cotorelay.com .. Coto_Technology__CT10-1530-G1.pdf >NAME >VALUE <b>CT10 Series Molded Switch</b><p> Source: www.cotorelay.com .. Coto_Technology__CT10-1530-G1.pdf >NAME >VALUE >NAME >VALUE <b>CT10 Series Molded Switch</b><p> Source: www.cotorelay.com .. Coto_Technology__CT10-1530-G1.pdf <h3>SparkFun Electronics' preferred foot prints</h3> In this library you'll find resistors, capacitors, inductors, test points, jumper pads, etc.<br><br> We've spent an enormous amount of time creating and checking these footprints and parts, but it is the end user's responsibility to ensure correctness and suitablity for a given componet or application. If you enjoy using this library, please buy one of our products at www.sparkfun.com. <br><br> <b>Licensing:</b> Creative Commons ShareAlike 4.0 International - https://creativecommons.org/licenses/by-sa/4.0/ <br><br> You are welcome to use this library for commercial purposes. For attribution, we ask that when you begin to sell your device using our footprint, you email us with a link to the product being sold. We want bragging rights that we helped (in a very small part) to create your 8th world wonder. We would like the opportunity to feature your device on our homepage. >NAME >VALUE >NAME >VALUE RES-09333 <b>Linear Devices</b><p> Operational amplifiers, comparators, voltage regulators, ADCs, DACs, etc.<p> <author>Created by librarian@cadsoft.de</author> <b>TO-92</b> >NAME >VALUE <b>TO-220</b> >NAME >VALUE <b>Small Outline Transistor</b> 3 4 1 2 >NAME >VALUE >NAME >VALUE ADJ Positive <b>VOLTAGE REGULATOR</b> <h3>SparkFun Electronics' preferred foot prints</h3> In this library you'll find resistors, capacitors, inductors, test points, jumper pads, etc.<br><br> We've spent an enormous amount of time creating and checking these footprints and parts, but it is the end user's responsibility to ensure correctness and suitablity for a given componet or application. If you enjoy using this library, please buy one of our products at www.sparkfun.com. <br><br> <b>Licensing:</b> Creative Commons ShareAlike 4.0 International - https://creativecommons.org/licenses/by-sa/4.0/ <br><br> You are welcome to use this library for commercial purposes. For attribution, we ask that when you begin to sell your device using our footprint, you email us with a link to the product being sold. We want bragging rights that we helped (in a very small part) to create your 8th world wonder. We would like the opportunity to feature your device on our homepage. >NAME >VALUE >Name >Value <b>RESISTOR</b><p> chip >NAME >VALUE >NAME >VALUE >NAME >VALUE <b>CAPACITOR</b><p> chip >NAME >VALUE 1/6W Thru-hole Resistor - *UNPROVEN* >NAME >VALUE >NAME >VALUE 1/4W Resistor, 0.4" wide<p> Yageo CFR series <a href="http://www.yageo.com/pdf/yageo/Leaded-R_CFR_2008.pdf">http://www.yageo.com/pdf/yageo/Leaded-R_CFR_2008.pdf</a> >Name >Value 1/2W Resistor, 0.5" wide<p> Yageo CFR series <a href="http://www.yageo.com/pdf/yageo/Leaded-R_CFR_2008.pdf">http://www.yageo.com/pdf/yageo/Leaded-R_CFR_2008.pdf</a> >Name >Value 1W Resistor, 0.6" wide<p> Yageo CFR series <a href="http://www.yageo.com/pdf/yageo/Leaded-R_CFR_2008.pdf">http://www.yageo.com/pdf/yageo/Leaded-R_CFR_2008.pdf</a> >Name >Value 2W Resistor, 0.8" wide<p> Yageo CFR series <a href="http://www.yageo.com/pdf/yageo/Leaded-R_CFR_2008.pdf">http://www.yageo.com/pdf/yageo/Leaded-R_CFR_2008.pdf</a> >Name >Value <h3>AXIAL-0.3-KIT</h3> Commonly used for 1/4W through-hole resistors. 0.3" pitch between holes.<br> <br> <b>Warning:</b> This is the KIT version of the AXIAL-0.3 package. This package has a smaller diameter top stop mask, which doesn't cover the diameter of the pad. This means only the bottom side of the pads' copper will be exposed. You'll only be able to solder to the bottom side. >Name >Value This is the "EZ" version of the standard .3" spaced resistor package.<br> It has a reduced top mask to make it harder to install upside-down. >Name >Value >Name >Value >Name >Value >Name >Value >NAME >VALUE >NAME >VALUE <b>CAPACITOR</b><p> chip >NAME >VALUE >Name >Value >Name >Value >NAME >VALUE CTZ3 Series land pattern for variable capacitor - CTZ3E-50C-W1-PF >NAME >VALUE <h3>CAP-PTH-SMALL-KIT</h3> Commonly used for small ceramic capacitors. Like our 0.1uF (http://www.sparkfun.com/products/8375) or 22pF caps (http://www.sparkfun.com/products/8571).<br> <br> <b>Warning:</b> This is the KIT version of this package. This package has a smaller diameter top stop mask, which doesn't cover the diameter of the pad. This means only the bottom side of the pads' copper will be exposed. You'll only be able to solder to the bottom side. This is the "EZ" version of the .1" spaced ceramic thru-hole cap.<br> It has reduced top mask to make it harder to put the component on the wrong side of the board. >Name >Value >NAME >VALUE >NAME >VALUE <b>Resistor</b> Basic schematic elements and footprints for 0603, 1206, and PTH resistors. <b>Capacitor</b> Standard 0603 ceramic capacitor, and 0.1" leaded capacitor. ORANGE RING BLACK RING REGULATOR BLUE GREEN YELLOW ORANGE BROWN RED GREY WHITE BLACK RED BLACK BLACK RED BATTERY VIOLET WHITE GREY GREY WHITE GREY BLACK WHITE BLACK RED ================================================ FILE: examples/lock/rfduino/sensor.cpp ================================================ #include "sensor.h" // #define DEBUG(message) Serial.print(message) #define DEBUG(message) Sensor::Sensor(uint32_t pin_) : pin(pin_) { pinMode(pin, INPUT); } Sensor::operator int() { int retval = digitalRead(pin); DEBUG("Sensor::int(pin="); DEBUG(pin); DEBUG(", val="); DEBUG(retval); DEBUG(")\n"); return retval; } ================================================ FILE: examples/lock/rfduino/sensor.h ================================================ #ifndef _SENSOR_H_ #define _SENSOR_H_ #include class Sensor { public: Sensor(uint32_t pin_); operator int(); private: const uint32_t pin; }; #endif ================================================ FILE: examples/readme.md ================================================ # Examples Here's couple of example how to use this plugin. Each example also contains sketches/code for selected BLE enabled microcontrollers. ## Humidity and Temperature Sensor [`humidity/`](humidity) Turn a BLE capable microprocessor and a Si7021 humidity and temperature sensor into a wireless HomeKit weather station. Humidity and temperature reading can be displayed in the Home app on your Apple device and used to setup your home automation rules. ## Lightbulb [`lightbulb/`](lightbulb) Turn an LED connected to a BLE capable microprocessor into a wireless HomeKit lightbulb. Use the Home app or Siri on your Apple device to switch it on and off. ## Lightbulb RGB [`lightbulb-rgb/`](lightbulb-rgb) Turn three LEDs connected to a BLE capable microprocessor into a wireless HomeKit lightbulb. Use the Home app or Siri on your Apple device to switch it on and change it's brightness or color. BLE subscribe capability allows the microprocessor to sent events back to the HomeKit whenever an external event changes the state of the light. ## Lock [`lock/`](lock) Turn an old Lockitron V1 body connected to a BLE capable microprocessor into a wireless HomeKit lock. Use the Home app or Siri on your Apple device to secure and unsecure the lock. ## Switch [`switch/`](switch) Turn a tactile push button connected to a BLE capable microprocessor into a wireless HomeKit switch. Use the Home app or Siri on your Apple device to trigger events based on the switch state. ## Thermometer [`thermometer/`](thermometer) Turn a BLE capable microprocessor into a wireless HomeKit thermometer. Temperature reading can be displayed in the Home app on your Apple device. ================================================ FILE: examples/switch/arduino101/arduino101.ino ================================================ #include #include const int pinSwitch = 2; BLEPeripheral ble; BLEService informationService("180A"); BLECharacteristic modelCharacteristic("2A24", BLERead, "101"); BLECharacteristic manufacturerCharacteristic("2A29", BLERead, "Arduino"); BLECharacteristic serialNumberCharacteristic("2A25", BLERead, "2.71828"); BLEService switchService("5ECEC5A0-7F71-41DA-9B8C-6252FE7EFB7E"); BLECharCharacteristic onCharacteristic("5ECEC5A1-7F71-41DA-9B8C-6252FE7EFB7E", BLEWrite | BLERead | BLENotify); bool lastSwitch = HIGH; void setup() { Serial.begin(115200); pinMode(pinSwitch, INPUT_PULLUP); ble.setLocalName("Switch"); ble.setAdvertisedServiceUuid(switchService.uuid()); ble.addAttribute(informationService); ble.addAttribute(modelCharacteristic); ble.addAttribute(manufacturerCharacteristic); ble.addAttribute(serialNumberCharacteristic); ble.addAttribute(informationService); ble.addAttribute(modelCharacteristic); ble.addAttribute(manufacturerCharacteristic); ble.addAttribute(serialNumberCharacteristic); ble.addAttribute(switchService); ble.addAttribute(onCharacteristic); onCharacteristic.setValueLE(false); ble.setEventHandler(BLEConnected, centralConnect); ble.setEventHandler(BLEDisconnected, centralDisconnect); onCharacteristic.setEventHandler(BLEWritten, characteristicWrite); ble.begin(); Serial.println("Bluetooth on"); lastSwitch = digitalRead(pinSwitch); } void loop() { ble.poll(); if ((digitalRead(pinSwitch) == LOW) && (lastSwitch == HIGH)) { bool on = (bool) onCharacteristic.valueLE(); onCharacteristic.setValue(!on); Serial.print("Switch | "); Serial.println(!on); ble.poll(); lastSwitch = LOW; delay(1); } else if ((digitalRead(pinSwitch) == HIGH) && (lastSwitch == LOW)) { lastSwitch = HIGH; delay(1); } } void centralConnect(BLECentral& central) { Serial.print("Central connected | "); Serial.println(central.address()); } void centralDisconnect(BLECentral& central) { Serial.print("Central disconnected | "); Serial.println(central.address()); } void characteristicWrite(BLECentral& central, BLECharacteristic& characteristic) { Serial.print("Characteristic written | "); Serial.println(characteristic.uuid()); bool on = (bool) onCharacteristic.valueLE(); Serial.print("Switch | "); Serial.println(on); } ================================================ FILE: examples/switch/config.json ================================================ { "bridge": { "name": "Raspberry Pi 3", "username": "CC:22:3D:E3:CE:30", "port": 51826, "pin": "314-15-926" }, "description": "Raspberry Pi 3 Homebridge-Bluetooth Switch Example", "platforms": [ { "platform": "Bluetooth", "accessories": [ { "name": "Arduino", "address": "01:23:45:67:89:AB", "services": [ { "name": "Button", "type": "Switch", "UUID": "5ECEC5A0-7F71-41DA-9B8C-6252FE7EFB7E", "characteristics": [ { "type": "On", "UUID": "5ECEC5A1-7F71-41DA-9B8C-6252FE7EFB7E" } ] } ] } ] } ] } ================================================ FILE: examples/switch/readme.md ================================================ # Switch Turn a tactile push button connected to a BLE capable microprocessor into a wireless HomeKit switch. Use the Home app or Siri on your Apple device to trigger events based on the switch state. This example uses [Arduino 101](https://www.arduino.cc/en/Main/ArduinoBoard101) and [Raspberry Pi 3](https://www.raspberrypi.org/). Generally, any programmable BLE peripheral and a box capable of running [Node.js](https://nodejs.org) with [Noble](https://github.com/sandeepmistry/noble) will work. ## BLE Peripheral (Arduino 101 or Other BLE Board) Download and install the latest version of the [Arduino IDE](https://www.arduino.cc/en/Main/Software). If you're totally new to microcontrollers take some time to go through an introductory tutorial and learn how to make a LED blink. This will help you to understand how to use the IDE, how to upload a sketch and what is the code actually doing. ### Wiring Connect the tactile switch to pin 2 and ground. When the button is pushed it connects the pin to the ground. The sketch code activates the internal 10k Ohm pull-up resitor to keep the pin high when the switch isn't pressed. **Note** _Alternatively, you can use any of the many BLE boards available on the market ([BlueBean](https://punchthrough.com/bean/), [RedBearLabs BLE Nano](http://redbearlab.com/blenano), ...) as long as you keep UUIDs of the services and characteristics in sync with your `config.json` file, everything will work just fine._ ### Running the Sketch Compile, run and upload the [arduino101.ino sketch](arduino101/arduino101.ino) using the [Arduino IDE](https://www.arduino.cc/en/Main/Software). The sketch creates a BLE service with a readable, writable and notifiable characteristic for on/off state. ```cpp BLEService switchService("5ECEC5A0-7F71-41DA-9B8C-6252FE7EFB7E"); BLECharCharacteristic onCharacteristic("5ECEC5A1-7F71-41DA-9B8C-6252FE7EFB7E", BLEWrite | BLERead | BLENotify); ``` Take a look into [this file](https://github.com/KhaosT/HAP-NodeJS/blob/master/lib/gen/HomeKitTypes.js#L1147) to see the full definition of the _On_ characteristic used in the _Switch_ service. Once the BLE central device is setup, it connects to this characteristic and exposes it via Homebridge as a HomeKit accessory of type _Switch_. Leave the device powered on and the sketch running while you setup the Homebridge server. The sketch has some built-in logging, so keeping the Serial monitor open may be helpful for debugging. ## BLE Central & Homebridge Server (Raspberry Pi 3 or Other Compatible Box) For help installing an operating system on your new Pi, the official documentation contains a couple of [nice videos](https://www.raspberrypi.org/help/videos/). ### Wiring No wiring except for the micro-USB cable providing power is needed. The Pi needs to be connected to the same router (subnet) as the Apple device you plan to use. It doesn't matter whether via Wifi or Ethernet. Otherwise, you won't be able discover and connect to the Homebridge server running on the Pi. **Note** _Alternatively, you can use a Raspberry Pi 2 with a supported USB BLE dongle instead of the Pi 3._ ### Running Homebridge Running Homebridge on a Raspberry Pi is straightforward. Follow [this guide](https://github.com/nfarina/homebridge/wiki/Running-HomeBridge-on-a-Raspberry-Pi) to install Homebridge server and then run the following command to install the homebridge-bluetooth plugin: ```sh [sudo] npm install -g homebridge-bluetooth ``` Edit the `~/.homebridge/config.json`, name your Homebridge server and add a new accessory to allow the plugin to connect to the BLE service running on the Arduino: ```js "name": "Arduino", "address": "01:23:45:67:89:AB", "services": [ { "name": "Button", "type": "Switch", "UUID": "5ECEC5A0-7F71-41DA-9B8C-6252FE7EFB7E", "characteristics": [ { "type": "On", "UUID": "5ECEC5A1-7F71-41DA-9B8C-6252FE7EFB7E" } ] } ] ``` Finally, start the Homebridge server. If you use Linux you may need to run with higher privileges in order to have access to the BLE hardware layer. See [this link](https://github.com/sandeepmistry/noble#running-without-rootsudo) for more details about running without `sudo`. ```sh [sudo] homebridge -D ``` **Note** _Running with `-D` turns on additional debugging output that is very helpful for getting addresses and UUIDs of your BLE devices that needs to match with the `config.json` file._ **Note** _Homebridge server doesn't run only on Linux. MacOS and Windows machines are also supported given they have a built-in BLE adapter or an USB dongle. For more details see supported platforms of [Homebridge](https://github.com/nfarina/homebridge) and [Noble](https://github.com/sandeepmistry/noble)._ ## Apple Device (iOS 10 or newer) ### Pairing Open Home app and tap the '+' button to add new accessory. When you attempt to add the 'Raspberry Pi 3' bridge, it will ask for a "PIN" from the `config.json` file. Once you are paired with your new Rapsberry, Homebridge server all the connected BLE accesories can be added the same way as the bridge. ### Interacting Once your BLE accessory has been added to HomeKit database, besides using the Home app or Control Center at the bottom of the screen, you should be able to tell Siri to control any HomeKit accessory. Try _"Hey Siri, turn switch off"_. However, Siri is a cloud service and iOS may need some time to synchronize your HomeKit database to iCloud. ================================================ FILE: examples/thermometer/arduino101/arduino101.ino ================================================ #include #include #include BLEPeripheral ble; BLEService informationService("180A"); BLECharacteristic modelCharacteristic("2A24", BLERead, "101"); BLECharacteristic manufacturerCharacteristic("2A29", BLERead, "Arduino"); BLECharacteristic serialNumberCharacteristic("2A25", BLERead, "2.71828"); BLEService thermometerService("1D8A68E0-E68E-4FED-943E-369099F5B499"); BLEFloatCharacteristic temperatureCharacteristic("1D8A68E1-E68E-4FED-943E-369099F5B499", BLERead | BLENotify); void setup() { Serial.begin(115200); ble.setLocalName("Thermo"); ble.setAdvertisedServiceUuid(thermometerService.uuid()); ble.addAttribute(informationService); ble.addAttribute(modelCharacteristic); ble.addAttribute(manufacturerCharacteristic); ble.addAttribute(serialNumberCharacteristic); ble.addAttribute(thermometerService); ble.addAttribute(temperatureCharacteristic); temperatureCharacteristic.setValueLE(0.0); ble.setEventHandler(BLEConnected, centralConnect); ble.setEventHandler(BLEDisconnected, centralDisconnect); ble.begin(); CurieIMU.begin(); Serial.println("Bluetooth on"); } void loop() { ble.poll(); float currentCelsius = (float) CurieIMU.readTemperature() / 32767.0 + 23.0; static float averageCelsius = currentCelsius; averageCelsius += (currentCelsius - averageCelsius) / 30.0; float previousCelsius = temperatureCharacteristic.valueLE(); if (abs(averageCelsius - previousCelsius) > 0.50) { temperatureCharacteristic.setValueLE(currentCelsius); Serial.print("Update temperature | "); Serial.println(averageCelsius, 2); } else { Serial.print("Temperature | "); Serial.println(averageCelsius, 2); } delay(1000); } void centralConnect(BLECentral& central) { Serial.print("Central connected | "); Serial.println(central.address()); } void centralDisconnect(BLECentral& central) { Serial.print("Central disconnected | "); Serial.println(central.address()); } ================================================ FILE: examples/thermometer/bluefruit/bluefruit.ino ================================================ #include #include #include #include #include Adafruit_BluefruitLE_SPI ble(8, 7, 4); // Firmware > 0.7.0 Adafruit_BLEGatt gatt(ble); int32_t informationService; int32_t modelCharacteristic; int32_t manufacturerCharacteristic; int32_t serialNumberCharacteristic; int32_t thermometerService; int32_t temperatureCharacteristic; union float_bytes { float value; uint8_t bytes[sizeof(float)]; }; void setup(void) { Serial.begin(115200); ble.begin(/* true - useful for debugging */); ble.factoryReset(); ble.info(); informationService = gatt.addService(0x180A); char model[] = "Micro LE"; modelCharacteristic = gatt.addCharacteristic(0x2A24, GATT_CHARS_PROPERTIES_READ, sizeof(model)+1, sizeof(model)+1, BLE_DATATYPE_STRING); gatt.setChar(modelCharacteristic, model); char manufacturer[] = "Bluefruit"; manufacturerCharacteristic = gatt.addCharacteristic(0x2A29, GATT_CHARS_PROPERTIES_READ, sizeof(manufacturer), sizeof(manufacturer), BLE_DATATYPE_STRING); gatt.setChar(manufacturerCharacteristic, manufacturer); char serialNumber[] = "2.71828"; serialNumberCharacteristic = gatt.addCharacteristic(0x2A25, GATT_CHARS_PROPERTIES_READ, sizeof(serialNumber), sizeof(serialNumber), BLE_DATATYPE_STRING); gatt.setChar(serialNumberCharacteristic, serialNumber); uint8_t thermometerServiceUUID[] = {0x1D,0x8A,0x68,0xE0,0xE6,0x8E,0x4F,0xED,0x94,0x3E,0x36,0x90,0x99,0xF5,0xB4,0x99}; thermometerService = gatt.addService(thermometerServiceUUID); uint8_t thermometerCharacteristicUUID[] = {0x1D,0x8A,0x68,0xE1,0xE6,0x8E,0x4F,0xED,0x94,0x3E,0x36,0x90,0x99,0xF5,0xB4,0x99}; temperatureCharacteristic = gatt.addCharacteristic(thermometerCharacteristicUUID, GATT_CHARS_PROPERTIES_READ | GATT_CHARS_PROPERTIES_NOTIFY, sizeof(float), sizeof(float), BLE_DATATYPE_BYTEARRAY); ble.reset(); ble.setConnectCallback(centralConnect); ble.setDisconnectCallback(centralDisconnect); Serial.println("Bluetooth on"); } /** Send randomized heart rate data continuously **/ void loop(void) { ble.update(); static union float_bytes averageCelsius = { .value = 0.0 }; if (ble.sendCommandCheckOK("AT+HWGETDIETEMP")) { float currentCelsius = atof(ble.buffer); averageCelsius.value += (currentCelsius - averageCelsius.value) / 30.0; } union float_bytes previousCelsius = { .value = 0.0 }; gatt.getChar(temperatureCharacteristic, previousCelsius.bytes, sizeof(previousCelsius)); if (abs(averageCelsius.value - previousCelsius.value) > 0.50) { gatt.setChar(temperatureCharacteristic, averageCelsius.bytes, sizeof(averageCelsius)); Serial.print("Update temperature | "); Serial.println(averageCelsius.value); } else { Serial.print("Temperature | "); Serial.println(averageCelsius.value); } delay(1000); } void centralConnect(void) { Serial.print("Central connected | "); if (ble.sendCommandCheckOK("AT+BLEGETPEERADDR")) { Serial.println(ble.buffer); } } void centralDisconnect(void) { Serial.print("Central disconnected | "); if (ble.sendCommandCheckOK("AT+BLEGETPEERADDR")) { Serial.println(ble.buffer); } } ================================================ FILE: examples/thermometer/config.json ================================================ { "bridge": { "name": "Raspberry Pi 3", "username": "CC:22:3D:E3:CE:30", "port": 51826, "pin": "314-15-926" }, "description": "Raspberry Pi 3 Homebridge-Bluetooth Thermometer Example", "platforms": [ { "platform": "Bluetooth", "accessories": [ { "name": "Arduino", "address": "01:23:45:67:89:AB", "services": [ { "name": "Thermometer", "type": "TemperatureSensor", "UUID": "1D8A68E0-E68E-4FED-943E-369099F5B499", "characteristics": [ { "type": "CurrentTemperature", "UUID": "1D8A68E1-E68E-4FED-943E-369099F5B499" } ] } ] } ] } ] } ================================================ FILE: examples/thermometer/readme.md ================================================ # Thermometer Turn a BLE capable microprocessor into a wireless HomeKit thermometer. Temperature reading can be displayed in the Home app on your Apple device. This example uses [Arduino 101](https://www.arduino.cc/en/Main/ArduinoBoard101) or [Bluefruit Micro LE](https://www.adafruit.com/products/2661) and [Raspberry Pi 3](https://www.raspberrypi.org/). Generally, any programmable BLE peripheral and a box capable of running [Node.js](https://nodejs.org) with [Noble](https://github.com/sandeepmistry/noble) will work. ## BLE Peripheral (Arduino 101, Bluefruit Micro LE or Other BLE Board) Download and install the latest version of the [Arduino IDE](https://www.arduino.cc/en/Main/Software). If you're totally new to microcontrollers take some time to go through an introductory tutorial and learn how to make a LED blink. This will help you to understand how to use the IDE, how to upload a sketch and what is the code actually doing. ### Wiring No wiring is needed, since we use the internal thermometer attached to the IMU or a thermometer inside of the nRF51 chip die in case of the Bluefruit Micro LE. **Note** _Alternatively, you can use any of the many BLE boards available on the market ([BlueBean](https://punchthrough.com/bean/), [RedBearLabs BLE Nano](http://redbearlab.com/blenano), ...) as long as you keep UUIDs of the services and characteristics in sync with your `config.json` file, everything will work just fine._ ### Running the Sketch Compile, run and upload the [arduino101.ino](arduino101/arduino101.ino) or [bluefruit.ino](bluefruit/bluefruit.ino) sketch using the [Arduino IDE](https://www.arduino.cc/en/Main/Software). The sketch creates a BLE service with a readable and notifiable characteristic for the current temperature `float` value. ```cpp BLEService thermometerService("1D8A68E0-E68E-4FED-943E-369099F5B499"); BLEFloatCharacteristic temperatureCharacteristic("1D8A68E1-E68E-4FED-943E-369099F5B499", BLERead | BLENotify); ``` Take a look into [this file](https://github.com/KhaosT/HAP-NodeJS/blob/master/lib/gen/HomeKitTypes.js#L1147) to see the full definition of the _CurrentTemperature_ characteristic used in the _TemperatureSensor_ service. Once the BLE central device is setup, it connects to this characteristic and exposes it via Homebridge as a HomeKit accessory of type _TemperatureSensor_. The code also calculates (pseudo-moving) average to prevent noise in the temperature measurement to trigger frequent mew value notifications. Duration of the averaging window is about 30 s. Once the moving average accumulates change above a certain threshold a new value of the characteristic is set which triggers the notify mechanism and propagates the measurement to HomeKit. The threshold is approximately half of the thermometer accuracy (±1°C). The built-ine sensor in fact measures is the temperature if the silicon die, which might be different from the room temperature (usually higher). The die temperature also depends on the load of the processor and might change even when the outside room temperature is stable. Leave the device powered on and the sketch running while you setup the Homebridge server. The sketch has some built-in logging, so keeping the Serial monitor open may be helpful for debugging. ## BLE Central & Homebridge Server (Raspberry Pi 3 or Other Compatible Box) For help installing an operating system on your new Pi, the official documentation contains a couple of [nice videos](https://www.raspberrypi.org/help/videos/). ### Wiring No wiring except for the micro-USB cable providing power is needed. The Pi needs to be connected to the same router (subnet) as the Apple device you plan to use. It doesn't matter whether via Wifi or Ethernet. Otherwise, you won't be able discover and connect to the Homebridge server running on the Pi. **Note** _Alternatively, you can use a Raspberry Pi 2 with a supported USB BLE dongle instead of the Pi 3._ ### Running Homebridge Running Homebridge on a Raspberry Pi is straightforward. Follow [this guide](https://github.com/nfarina/homebridge/wiki/Running-HomeBridge-on-a-Raspberry-Pi) to install Homebridge server and then run the following command to install the homebridge-bluetooth plugin: ```sh [sudo] npm install -g homebridge-bluetooth ``` Edit the `~/.homebridge/config.json`, name your Homebridge server and add a new accessory to allow the plugin to connect to the BLE service running on the Arduino: ```js "name": "Arduino", "address": "01:23:45:67:89:AB", "services": [ "name": "Thermometer", "type": "TemperatureSensor", "UUID": "1D8A68E0-E68E-4FED-943E-369099F5B499", "characteristics": [ { "type": "CurrentTemperature", "UUID": "1D8A68E1-E68E-4FED-943E-369099F5B499" } ] } ] ``` Finally, start the Homebridge server. If you use Linux you may need to run with higher privileges in order to have access to the BLE hardware layer. See [this link](https://github.com/sandeepmistry/noble#running-without-rootsudo) for more details about running without `sudo`. ```sh [sudo] homebridge -D ``` **Note** _Running with `-D` turns on additional debugging output that is very helpful for getting addresses and UUIDs of your BLE devices that needs to match with the `config.json` file._ **Note** _Homebridge server doesn't run only on Linux. MacOS and Windows machines are also supported given they have a built-in BLE adapter or an USB dongle. For more details see supported platforms of [Homebridge](https://github.com/nfarina/homebridge) and [Noble](https://github.com/sandeepmistry/noble)._ ## Apple Device (iOS 10 or newer) ### Pairing Open Home app and tap the '+' button to add new accessory. When you attempt to add the 'Raspberry Pi 3' bridge, it will ask for a "PIN" from the `config.json` file. Once you are paired with your new Rapsberry, Homebridge server all the connected BLE accesories can be added the same way as the bridge. ### Interacting Once your BLE accessory has been added to HomeKit database, besides using the Home app or Control Center at the bottom of the screen, you should be able to tell Siri to get the reading from any HomeKit accessory. Try _"Hey Siri, what's the temperature of the Arduino?"_. However, Siri is a cloud service and iOS may need some time to synchronize your HomeKit database to iCloud. ================================================ FILE: index.js ================================================ var Noble, Accessory, Service, Characteristic, UUIDGen; module.exports = function (homebridge) { console.log("Homebridge API version: " + homebridge.version); Noble = require('noble'); Accessory = homebridge.platformAccessory; Service = homebridge.hap.Service; Characteristic = homebridge.hap.Characteristic; UUIDGen = homebridge.hap.uuid; BluetoothCharacteristic = require("./source/characteristic.js")(Characteristic); BluetoothService = require("./source/service.js")(Service, BluetoothCharacteristic); BluetoothAccessory = require("./source/accessory.js")(Accessory, BluetoothService); BluetoothPlatform = require("./source/platform.js")(Noble, UUIDGen, Accessory, BluetoothAccessory); homebridge.registerPlatform("homebridge-bluetooth", "Bluetooth", BluetoothPlatform, true); }; ================================================ FILE: license.txt ================================================ MIT License Copyright (c) 2016 Vojta Molda Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. ================================================ FILE: package.json ================================================ { "name": "homebridge-bluetooth", "description": "Homebridge plugin that exposes Bluetooth peripherals as HomeKit accessories", "author": "Vojta Molda", "version": "0.1.7", "license": "MIT", "keywords": [ "homebridge-plugin", "bluetooth", "bluetooth low energy" ], "repository": { "type": "git", "url": "git://github.com/vojtamolda/homebridge-bluetooth.git" }, "bugs": { "url": "https://github.com/vojtamolda/homebridge-bluetooth/issues" }, "engines": { "node": ">=4.3.2", "homebridge": ">=0.4.6" }, "dependencies": { "noble": ">=1.7.0", "chalk": ">=1.1.1" } } ================================================ FILE: readme.md ================================================ # homebridge-bluetooth [![NPM version](https://badge.fury.io/js/homebridge-bluetooth.svg)](https://badge.fury.io/js/homebridge-bluetooth) [Homebridge](https://github.com/nfarina/homebridge) plugin for exposing services and characteristics of nearby [Bluetooth Low Energy](https://www.bluetooth.com/what-is-bluetooth-technology/bluetooth-technology-basics/low-energy) (BLE) peripherals as [HomeKit](https://www.apple.com/ios/home/) accesories. Ideal for wireless DIY home automation projects if you'd like to control them comfortably with Siri on any Apple device. Homebridge runs on top of [Node.js](https://nodejs.org) server and is an open-source implementation of the Apple HomeKit protocol. HomeKit provides the API between your Apple device (i.e. Watch) and your home automation server (i.e. Raspberry Pi). This Homebridge [plugin](https://www.npmjs.com/package/homebridge-bluetooth) relays the communication from the home automation server to the BLE peripheral device (i.e. Arduino 101). Take a peek into the [examples](/examples/) folder for inspiration. ## Installation Make sure your systems matches the [prerequisites](#what-are-the-prerequisites-for-installation). You need to have a C compiler, [Node.js](https://nodejs.org) server and if you're running on Linux the [`libbluez-dev`](http://www.bluez.org/download/) library. ### Install Homebridge & Noble [Homebridge](https://github.com/nfarina/homebridge) is a lightweight framework built on top of [Node.js](https://nodejs.org/) server that provides the HomeKit bridge for your Apple devices to connect to. [Noble](https://github.com/sandeepmistry/noble) is BLE central module library for [Node.js](https://nodejs.org/) that abstracts away intricacies of each OS BLE stack implementation and provides a nice universal high-level API. ```sh [sudo] npm install -g noble [sudo] npm install -g --unsafe-perm homebridge node-gyp [sudo] npm install -g homebridge-bluetooth ``` **Note** _Depending on your privileges `-g` flag may need root permissions to install to the global `npm` module directory._ ### Configure Homebridge Homebridge is setup via `config.json` file sitting in the `~/.homebridge/` directory. The [example config](config.json) included in the repository has lots of comments and is a good starting point. Each BLE peripheral device is uniquely identified by it's address. Services and characteristics are identified by UUID. Also, each of the [examples](/examples/) comes with it's own `config.json` file. The easiest way to find the address of a BLE device is to start the plugin with a random/default address and then check the output for `Ignored | MyDevice - 01:23:45:67:89:ab` message. Alternatively you can run `[sudo] hcitool lescan` to discover available BLE devices. Mapping of services and characteristics from Bluetooth to HomeKit relies on two things - matching read/write/nofity permissions and matching data type. To see what is available, take a look at the very long list [here](https://github.com/KhaosT/HAP-NodeJS/blob/master/lib/gen/HomeKitTypes.js). Services are towards the bottom of the file. Each characteristic has pre-defined permissions that represent the expected way to use it - i.e. it doesn't make sense to write (set) the current temperature. These permissions must match the corresponding permissions of the Bluetooth characteristic. All data are assumed to use little endian encoding for multi-byte values. The following table summarizes permission matching for a very popular [BLEPeripheral](https://github.com/sandeepmistry/arduino-BLEPeripheral) library that can run on most Arduino boards: | HomeKit `Characteristic.Perms[...]` | BLEPeripheral `BLECharacteristic(...)` | | :---------------------------------- | :------------------------------------- | | `READ` | `BLEWrite` | | `WRITE` | `BLERead` | | `NOTIFY` | `BLENotify` | Similar to permissions, matching of the exchanged data type for [BLEPeripheral](https://github.com/sandeepmistry/arduino-BLEPeripheral) is summarized here: | HomeKit `Characteristic.Formats[...]` | BLEPeripheral `BLETypedCharacteristic<...>` | | :------------------------------------ | :------------------------------------------ | | `BOOL` | `BLECharCharacteristic` | | `INT` | `BLEIntCharacteristic` | | `FLOAT` | `BLEFloatCharacteristic` | | `STRING` | `BLECharacteristic` | | `UINT8` | `BLEUnsignedCharCharacteristic` | | `UINT16` | `BLEUnsignedShortCharacteristic` | | `UINT32` | `BLEUnsignedIntCharacteristic` | | `UINT64` | `BLEUnsignedLongCharacteristic` | For instance if you'd like create a HomeKit thermometer, you have to implement the `TemperatureSensor` service. According to the [list of available services and characteristics]() the service has one mandatory characteristic - `CurrentTemperature` and a handful of optional ones. The `CurrentTemperature` characteristic communicates a `FLOAT` value and needs `READ` and `NOTIFY` permissions. Using the [BLEPeripheral](https://github.com/sandeepmistry/arduino-BLEPeripheral) library, the characteristic would be implemented as: ```cpp BLEService temperatureSensorService("SERVICE-UUID-HERE"); BLEFloatCharacteristic currentTemperatureCharacteristic("CHARACTERISTIC-UUID-HERE", BLERead | BLENotify); ``` The corresponding entry in the `config.json` file that connects to the characteristic above is: ```js "services" : [ { "name": "The Best HomeKit Thermometer", "type": "TemperatureSensor", "UUID": "SERVICE-UUID-HERE", "characteristics": [ { "type": "CurrentTemperature", "UUID": "CHARACTERISTIC-UUID-HERE" } ] } ] ``` **Note** _All [UUIDs](https://en.wikipedia.org/wiki/Universally_unique_identifier) should be randomly generated to prevent collisions. [This page](https://www.uuidgenerator.net) is a good place to get your own._ ### Run Homebridge Depending on your privileges, accessing the BLE kernel subsystem may need root permissions. ```sh [sudo] homebridge -D ``` **Note** _Running with `-D` turns on additional debugging output that is very helpful for getting addresses and UUIDs of your BLE devices that needs to match with the `config.json` file._ **Note** _See [this section of Noble readme](https://github.com/sandeepmistry/noble#running-without-rootsudo) for more details about running without `sudo`._ ## Troubleshooting If you encouter a different problem, please, open an [issue](https://github.com/vojtamolda/homebridge-bluetooth/issues). ### Home app can't discover any nearby accessories Make sure the Apple device and the Homebridge server are on the same subnet and connected to the same wifi router. Sometimes, homebridge server might think that, it has successfully paired with iOS, but iOS doesn't agree. Try to delete the `persist/` directory in the `~/.homebridge/` configuration folder. This removes all pairings that normally persist from session to session. ```sh rm -rf ~/.homebridge/persist/ ``` From time to time it looks like iOS ignores HomeKit bridges with `username` that it has already paired with. Try to change the `username` in the `bridge` section of `config.json` to a new value never used before. ```js "username": "CC:22:3D:E3:CE:30" -> "username": "DD:33:4E:F4:DF:41" ``` ### BLE peripheral is discovered, but immediately disconnects This isssue seems to be happening on some versions of Raspbian running on the Pi 3. Resetting the BLE adapter seems to resolve the issue: ```sh [sudo] hciconfig hci0 reset ``` Obviously, the solution is not really addressing the core of the problem, but it seems to help. ## FAQ ### Can I contribute my own example or a new feature? Sure thing! All contributions are welcome. Just do a pull-request or open a new issue if you see something broken. ### Is this thing secure? **No.** Not even close. Connection from your Apple device to Homebridge server is encrypted. However, all BLE communication is currently unencrypted and anyone with the right spoofing equipment can listen to it. Moreover, once the attacker has figured out what devices you have, he can also connect to any of your peripherals and control them directly. So don't use this if you're paranoid or if NSA is after you. You wouldn't sleep well. [MFi](https://developer.apple.com/programs/mfi/) certified HomeKit BLE encryption uses quite-strong Ed25519 elliptic cypher. BLE has built-in support for encryption, but it seems that Apple has decided to build their own thing as usual. It makes some sense in this case since a lot is at stake - once HomeKit becomes widespread a security bug literally means open doors to your house. Hooray - let's connect billions of unsecured IoT devices to the internet! What could possibly go wrong?? Moreover, from bits and pieces available on the Internet, it seems that Apple has changed the specs several times and caused a lot of trouble for the manufacturers, since slower microprocessors tend to have a hard time doing all the involved encryption math. Initial pairing can take literally minutes. Pairing procedure uses SRP (Secure Remote Password (3072-bit) protocol with an 8-digit code (the number you have to type in when first pairing with Homebridge). After pairing, per session communication always uses unique keys derived by HKDF-SHA-512 and encrypted by the ChaCha20-Poly1305. Theoretically, one should be able to get rid of the Homebridge 'middle-man' since HomeKit over BLE allows direct connection to any Apple device. While there are many good [HomeKit IP stacks around](https://github.com/KhaosT/HAP-NodeJS) BLE implementations are few and far between. There's only one implementation I'm aware of [here](https://github.com/aanon4/HomeKit), but it can't be easily ported to other boards. BTW, I think it might be possible to re-write the BLE implementation above with [BLEPeripheral](https://github.com/sandeepmistry/arduino-BLEPeripheral) library instead of Nordic Semi's SoftDevice to make it run essentially everywhere. If anyone is interested in this project, please, let me know and maybe we can figure a plan and come up with something useful and fun to use. Implementing a HomeKit over BLE stack correctly requires access to [MFi](https://developer.apple.com/programs/mfi/) internal documentation, which isn't publicly available, unless you're a registered developer at a company with big $$$. Making BLE accessories is simple, but making them secure seems to be very, very hard. ### What are the prerequisites for installation? #### Linux (Debian Based, Kernel 3.6 or newer) A supported BLE (Bluetooth 4.0) USB dongle is required, if your device doesn't have it built-in. - Install [Node.js](https://nodejs.org/en/download/) [Node.js](https://nodejs.org) is an asynchronous event driven JavaScript server, ideal for building scalable, low-latency network applications. [Homebridge](https://www.npmjs.com/package/homebridge) is built on top of this server. It is being developed so quickly that package repositories of most distributions contain a very old version. Getting latest from the official website is recommended. - Install `libbluetooth-dev` and `libavahi-compat-libdnssd-dev` These libraries and their dependencies are required by [Noble](https://www.npmjs.com/package/noble) package and provide access to the kernel Bluetooth subsystem. ```sh sudo apt-get install bluetooth bluez libbluetooth-dev libudev-dev libavahi-compat-libdnssd-dev ``` #### macOS (10.10 or newer) Check [this link](http://www.imore.com/how-tell-if-your-mac-has-bluetooth-40) to see if your mac has built-in BLE (Bluetooth 4.0) support. All macs from 2012 and newer are generally fine. - Install [Node.js](https://nodejs.org/en/download/) [Node.js](https://nodejs.org) is an asynchronous event driven JavaScript server, ideal for building scalable, low-latency network applications. [Homebridge](https://www.npmjs.com/package/homebridge) is built on top of this server. It is being developed so quickly that package repositories of most distributions contain a very old version. Getting latest from the official website is recommended. - Install [XCode](https://itunes.apple.com/ca/app/xcode/id497799835?mt=12) [XCode](https://developer.apple.com/xcode/) comes with a C compiler that is needed to compile the JavaScript to C bindings required by [Noble](https://www.npmjs.com/package/noble) package. #### Windows (8.1 or newer) Pull request is welcomed here... Both [Homebridge](https://www.npmjs.com/package/homebridge) and [Noble](https://www.npmjs.com/package/noble) should run on Windows, but I don't have a machine to test. ### On what devices was this plugin tested? Here's a list of testing devices. The list is by no means exhaustive and the plugin will work with many more. - Apple Device - [iPhone](https://en.wikipedia.org/wiki/IPhone) 5S & 6 running [iOS](https://en.wikipedia.org/wiki/IOS) 10 - Homebridge Server - [Raspberry Pi](https://en.wikipedia.org/wiki/Raspberry_Pi) 3 & 2 (with USB dongle) running [Raspbian](https://www.raspberrypi.org/downloads/raspbian/) Jessie Lite - [Macbook Air](https://en.wikipedia.org/wiki/MacBook_Air) (2015) & [iMac](https://en.wikipedia.org/wiki/IMac) (2012) running [macOS](https://en.wikipedia.org/wiki/MacOS) 10.12 - Bluetooth Peripheral - nRF51 Based Boards - [Arduino 101](https://www.arduino.cc/en/Main/ArduinoBoard101), [RFDuino](http://www.rfduino.com/), [Bluefruit Micro](https://www.adafruit.com/product/2661) - nRF8001 Based Boards - [Arduino UNO](https://www.arduino.cc/en/Main/ArduinoBoardUno) + [nRF8001 Breakout](https://www.adafruit.com/products/1697), ## License This work is licensed under the MIT license. See [license](license.txt) for more details. ================================================ FILE: source/accessory.js ================================================ var Accessory, BluetoothService; var Chalk = require('chalk'); module.exports = function (accessory, bluetoothService) { Accessory = accessory; BluetoothService = bluetoothService; return BluetoothAccessory; }; function BluetoothAccessory(log, config) { this.log = log; if (!config.name) { throw new Error("Missing mandatory config 'name'"); } this.name = config.name; this.prefix = Chalk.blue("[" + config.name + "]"); if (!config.address) { throw new Error(this.prefix + " Missing mandatory config 'address'"); } this.address = config.address; if (!config.services || !(config.services instanceof Array)) { throw new Error(this.prefix + " Missing mandatory config 'services'"); } this.log.debug(this.prefix, "Initialized | " + this.name + " (" + this.address + ")"); this.bluetoothServices = {}; for (var serviceConfig of config.services) { var serviceUUID = trimUUID(serviceConfig.UUID); this.bluetoothServices[serviceUUID] = new BluetoothService(this.log, serviceConfig, this.prefix); } var informationServiceUUID = trimUUID('180A') if (!(informationServiceUUID in this.bluetoothServices)) { informationServiceConfig = { "name": "Information", "type": "AccessoryInformation", "UUID": "180A", "characteristics": [ {"type": "Manufacturer", "UUID": "2A29"}, {"type": "Model", "UUID": "2A24"}, {"type": "SerialNumber", "UUID": "2A25"} ] }; this.bluetoothServices[informationServiceUUID] = new BluetoothService(this.log, informationServiceConfig, this.prefix); } this.homebridgeAccessory = null; this.nobleAccessory = null; } BluetoothAccessory.prototype.connect = function (nobleAccessory, homebridgeAccessory) { this.log.info(this.prefix, "Connected | " + this.name + " (" + this.address + ")"); this.homebridgeAccessory = homebridgeAccessory; this.homebridgeAccessory.on('identify', this.identification.bind(this)); this.homebridgeAccessory.updateReachability(true); this.nobleAccessory = nobleAccessory; this.nobleAccessory.once('disconnect', this.disconnect.bind(this)); this.nobleAccessory.discoverServices([], this.discoverServices.bind(this)); }; BluetoothAccessory.prototype.discoverServices = function (error, nobleServices) { if (error) { this.log.error(this.prefix, "Discover services failed | " + error); return; } if (nobleServices.length == 0) { this.log.warn(this.prefix, "No services discovered"); return; } for (var nobleService of nobleServices) { var serviceUUID = trimUUID(nobleService.uuid); var bluetoothService = this.bluetoothServices[serviceUUID]; if (!bluetoothService) { if (nobleService.uuid != '1800' && nobleService.uuid != '1801') { this.log.debug(this.prefix, "Ignored | Service (" + nobleService.uuid + ")"); } continue; } var homebridgeService = this.homebridgeAccessory.getService(bluetoothService.class); if (!homebridgeService) { homebridgeService = this.homebridgeAccessory.addService(bluetoothService.class, bluetoothService.name); } bluetoothService.connect(nobleService, homebridgeService); } }; BluetoothAccessory.prototype.identification = function (paired, callback) { this.log.info(this.prefix, "Identify"); callback(); }; BluetoothAccessory.prototype.disconnect = function (error) { if (error) { this.log.error("Disconnecting failed | " + this.name + " (" + this.address + ") | " + error); } for (var serviceUUID in this.bluetoothServices) { this.bluetoothServices[serviceUUID].disconnect(); } if (this.nobleAccessory && this.homebridgeAccessory) { this.homebridgeAccessory.removeAllListeners('identify'); this.homebridgeAccessory.updateReachability(false); this.homebridgeAccessory = null; this.nobleAccessory.removeAllListeners(); this.nobleAccessory = null; this.log.info(this.prefix, "Disconnected"); } }; function trimUUID(uuid) { return uuid.toLowerCase().replace(/:/g, "").replace(/-/g, ""); } ================================================ FILE: source/characteristic.js ================================================ var Characteristic; var Chalk = require('chalk'); module.exports = function (characteristic) { Characteristic = characteristic; return BluetoothCharacteristic; }; function BluetoothCharacteristic(log, config, prefix) { this.log = log; if (!config.type) { throw new Error(this.prefix + " Missing mandatory config 'type'"); } this.type = config.type; this.prefix = prefix + " " + Chalk.green("[" + this.type + "]"); if (!Characteristic[this.type]) { throw new Error(this.prefix + " Characteristic type '" + this.type + "' is not defined. " + "See 'HAP-NodeJS/lib/gen/HomeKitType.js' for options.") } this.class = Characteristic[this.type]; // For example - Characteristic.Brightness if (!config.UUID) { throw new Error(this.prefix + " Missing mandatory config 'UUID'"); } this.UUID = config.UUID; this.log.debug(this.prefix, "Initialized | Characteristic." + this.type + " (" + this.UUID + ")"); this.homebridgeCharacteristic = null; this.nobleCharacteristic = null; } BluetoothCharacteristic.prototype.connect = function (nobleCharacteristic, homebridgeCharacteristic) { this.log.info(this.prefix, "Connected"); this.log.debug(this.prefix, "Characteristic." + this.type + " (" + this.UUID + ")"); this.homebridgeCharacteristic = homebridgeCharacteristic; this.nobleCharacteristic = nobleCharacteristic; for (var permission of this.homebridgeCharacteristic.props['perms']) { switch (permission) { case Characteristic.Perms.READ: if (this.nobleCharacteristic.properties.indexOf('read') >= 0) { this.homebridgeCharacteristic.on('get', this.get.bind(this)); } else { this.log.warn(this.prefix, "Read from bluetooth characteristic not permitted"); } break; case Characteristic.Perms.WRITE: if (this.nobleCharacteristic.properties.indexOf('write') >= 0) { this.homebridgeCharacteristic.on('set', this.set.bind(this)); } else { this.log.warn(this.prefix, "Write to bluetooth characteristic not permitted"); } break; case Characteristic.Perms.NOTIFY: if (this.nobleCharacteristic.properties.indexOf('notify') >= 0) { this.nobleCharacteristic.on('read', this.notify.bind(this)); this.nobleCharacteristic.subscribe(function (error) { if (error) { this.log.warn(this.prefix, "Subscribe to bluetooth characteristic failed"); } }.bind(this)); } else { this.log.warn(this.prefix, "Subscribe to bluetooth characteristic not permitted"); } break; } } }; BluetoothCharacteristic.prototype.get = function (callback) { this.nobleCharacteristic.read(function (error, buffer) { if (error) { this.log.warn(this.prefix, "Read from bluetooth characteristic failed | " + error); callback(error, null); return } var value = this.fromBuffer(buffer); this.log.info(this.prefix, "Get | " + value); callback(null, value); }.bind(this)); }; BluetoothCharacteristic.prototype.set = function (value, callback) { this.log.info(this.prefix, "Set | " + value); var buffer = this.toBuffer(value); this.nobleCharacteristic.write(buffer, false); callback(); }; BluetoothCharacteristic.prototype.notify = function (buffer, notification) { if (notification) { var value = this.fromBuffer(buffer); this.log.info(this.prefix, "Notify | " + value); this.homebridgeCharacteristic.updateValue(value, null, this); } }; BluetoothCharacteristic.prototype.toBuffer = function (value) { var buffer; switch (this.homebridgeCharacteristic.props['format']) { case Characteristic.Formats.BOOL: // BLECharCharacteristic buffer = Buffer.alloc(1); buffer.writeInt8(value ? 1 : 0, 0); break; case Characteristic.Formats.INT: // BLEIntCharacteristic buffer = Buffer.alloc(4); buffer.writeInt32LE(value, 0); break; case Characteristic.Formats.FLOAT: // BLEFloatCharacteristic buffer = Buffer.alloc(4); buffer.writeFloatLE(value, 0); break; case Characteristic.Formats.STRING: // BLECharacteristic buffer = Buffer.from(value, 'utf8'); break; case Characteristic.Formats.UINT8: // BLEUnsignedCharCharacteristic buffer = Buffer.alloc(1); buffer.writeUInt8(value, 0); break; case Characteristic.Formats.UINT16: // BLEUnsignedShortCharacteristic buffer = Buffer.alloc(2); buffer.writeUInt16(value, 0); break; case Characteristic.Formats.UINT32: // BLEUnsignedIntCharacteristic buffer = Buffer.alloc(4); buffer.writeUInt32(value, 0); break; case Characteristic.Formats.UINT64: // BLEUnsignedLongCharacteristic buffer = Buffer.alloc(8); buffer.writeUIntLE(value, 0, 8); break; default: this.log.error(this.prefix, "Unsupported data conversion | " + this.homebridgeCharacteristic.props['format']); buffer = Buffer.alloc(1); buffer.writeInt8(0, 0); break; } return buffer; }; BluetoothCharacteristic.prototype.fromBuffer = function (buffer) { var value; switch (this.homebridgeCharacteristic.props['format']) { case Characteristic.Formats.BOOL: // BLECharCharacteristic value = buffer.readInt8(0); break; case Characteristic.Formats.INT: // BLEIntCharacteristic value = buffer.readInt32LE(0); break; case Characteristic.Formats.FLOAT: // BLEFloatCharacteristic value = buffer.readFloatLE(0); break; case Characteristic.Formats.STRING: // BLECharacteristic value = buffer.toString('utf8', 0); break; case Characteristic.Formats.UINT8: // BLEUnsignedCharCharacteristic value = buffer.readUInt8(0); break; case Characteristic.Formats.UINT16: // BLEUnsignedShortCharacteristic value = buffer.readUInt16LE(0); break; case Characteristic.Formats.UINT32: // BLEUnsignedIntCharacteristic value = buffer.readUInt32LE(0); break; case Characteristic.Formats.UINT64: // BLEUnsignedLongCharacteristic value = buffer.readUIntLE(0, 8); break; default: value = 0; this.log.error(this.prefix, "Unsupported data conversion | " + this.homebridgeCharacteristic.props['format']); } return value; }; BluetoothCharacteristic.prototype.disconnect = function () { if (this.nobleCharacteristic && this.homebridgeCharacteristic) { if (this.nobleCharacteristic.properties.indexOf('read') >= 0) { this.homebridgeCharacteristic.removeAllListeners('get'); } if (this.nobleCharacteristic.properties.indexOf('write') >= 0) { this.homebridgeCharacteristic.removeAllListeners('set'); } if (this.nobleCharacteristic.properties.indexOf('notify') >= 0) { this.nobleCharacteristic.unsubscribe(null); this.nobleCharacteristic.removeAllListeners('read'); } this.homebridgeCharacteristic = null; this.nobleCharacteristic = null; this.log.info(this.prefix, "Disconnected"); } }; ================================================ FILE: source/platform.js ================================================ var Noble, UUIDGen, Accessory, BluetoothAccessory; module.exports = function (noble, uuidGen, accessory, bluetoothAccessory) { Noble = noble; UUIDGen = uuidGen Accessory = accessory; BluetoothAccessory = bluetoothAccessory; return BluetoothPlatform; }; function BluetoothPlatform(log, config, homebridgeAPI) { this.log = log; if (!config) { this.log.warn("Missing mandatory platform config named 'Bluetooth'"); return; } if (!config.accessories || !(config.accessories instanceof Array)) { this.log.warn("Missing mandatory config 'accessories'"); return; } this.bluetoothAccessories = {}; for (var accessoryConfig of config.accessories) { var accessoryAddress = trimAddress(accessoryConfig.address); var bluetoothAccessory = new BluetoothAccessory(this.log, accessoryConfig); this.bluetoothAccessories[accessoryAddress] = bluetoothAccessory; } this.cachedHomebridgeAccessories = {}; this.homebridgeAPI = homebridgeAPI; this.homebridgeAPI.on('didFinishLaunching', this.didFinishLaunching.bind(this)); } BluetoothPlatform.prototype.configureAccessory = function (homebridgeAccessory) { var accessoryAddress = homebridgeAccessory.context['address']; var bluetoothAccessory = this.bluetoothAccessories[accessoryAddress]; if (!bluetoothAccessory) { this.log.debug("Removed | " + homebridgeAccessory.displayName + " (" + accessoryAddress + ")"); this.homebridgeAPI.unregisterPlatformAccessories("homebridge-bluetooth", "Bluetooth", [homebridgeAccessory]); return; } this.log.debug("Persist | " + homebridgeAccessory.displayName + " (" + accessoryAddress + ")"); this.cachedHomebridgeAccessories[accessoryAddress] = homebridgeAccessory; }; BluetoothPlatform.prototype.didFinishLaunching = function () { Noble.on('stateChange', this.stateChange.bind(this)); }; BluetoothPlatform.prototype.stateChange = function (state) { if (state != 'poweredOn') { this.log.info("Stopped | " + state); Noble.stopScanning(); } this.log.info("Started | " + state); Noble.startScanning([], false); Noble.on('discover', this.discover.bind(this)); }; BluetoothPlatform.prototype.discover = function (nobleAccessory) { var accessoryAddress = trimAddress(nobleAccessory.address); var bluetoothAccessory = this.bluetoothAccessories[accessoryAddress]; if (!bluetoothAccessory) { this.log.debug("Ignored | " + nobleAccessory.advertisement.localName + " (" + nobleAccessory.address + ") | RSSI " + nobleAccessory.rssi + "dB"); return; } this.log.debug("Discovered | " + nobleAccessory.advertisement.localName + " (" + nobleAccessory.address + ") | RSSI " + nobleAccessory.rssi + "dB"); nobleAccessory.connect(function (error) { this.connect(error, nobleAccessory) }.bind(this)); }; BluetoothPlatform.prototype.connect = function (error, nobleAccessory) { if (error) { this.log.error("Connecting failed | " + nobleAccessory.advertisement.localName + " (" + nobleAccessory.address + ") | " + error); return; } var accessoryAddress = trimAddress(nobleAccessory.address); var bluetoothAccessory = this.bluetoothAccessories[accessoryAddress]; var homebridgeAccessory = this.cachedHomebridgeAccessories[accessoryAddress]; if (!homebridgeAccessory) { homebridgeAccessory = new Accessory(bluetoothAccessory.name, UUIDGen.generate(bluetoothAccessory.name)); homebridgeAccessory.context['address'] = accessoryAddress; this.homebridgeAPI.registerPlatformAccessories("homebridge-bluetooth", "Bluetooth", [homebridgeAccessory]); } else { delete this.cachedHomebridgeAccessories[accessoryAddress]; } bluetoothAccessory.connect(nobleAccessory, homebridgeAccessory); nobleAccessory.once('disconnect', function (error) { this.disconnect(nobleAccessory, homebridgeAccessory, error); }.bind(this)); if (Object.keys(this.cachedHomebridgeAccessories).length > 0) { Noble.startScanning([], false); } }; BluetoothPlatform.prototype.disconnect = function (nobleAccessory, homebridgeAccessory, error) { var accessoryAddress = trimAddress(nobleAccessory.address); this.cachedHomebridgeAccessories[accessoryAddress] = homebridgeAccessory; Noble.startScanning([], false); }; function trimAddress(address) { return address.toLowerCase().replace(/:/g, ""); } ================================================ FILE: source/service.js ================================================ var Service, BluetoothCharacteristic; var Chalk = require('chalk'); module.exports = function (service, bluetoothCharacteristic) { Service = service; BluetoothCharacteristic = bluetoothCharacteristic; return BluetoothService; }; function BluetoothService(log, config, prefix) { this.log = log; if (!config.name) { throw new Error("Missing mandatory config 'name'"); } this.name = config.name; this.prefix = prefix + " " + Chalk.magenta("[" + this.name + "]"); if (!config.type) { throw new Error(this.prefix + " Missing mandatory config 'type'"); } this.type = config.type; if (!Service[this.type]) { throw new Error(this.prefix + " Service type '" + this.type + "' is not defined. " + "See 'HAP-NodeJS/lib/gen/HomeKitType.js' for options.") } this.class = Service[this.type]; // For example - Service.Lightbulb if (!config.UUID) { throw new Error(this.prefix + " Missing mandatory config 'UUID'"); } this.UUID = config.UUID; if (!config.characteristics || !(config.characteristics instanceof Array)) { throw new Error(this.prefix + " Missing mandatory config 'characteristics'"); } this.log.debug(this.prefix, "Initialized | Service." + this.type + " (" + this.UUID + ")"); this.bluetoothCharacteristics = {}; for (var characteristicConfig of config.characteristics) { var characteristicUUID = trimUUID(characteristicConfig.UUID); this.bluetoothCharacteristics[characteristicUUID] = new BluetoothCharacteristic(this.log, characteristicConfig, this.prefix); } this.homebridgeService = null; this.nobleService = null; } BluetoothService.prototype.connect = function (nobleService, homebridgeService) { this.log.info(this.prefix, "Connected"); this.log.debug(this.prefix, "Service." + this.type + " (" + this.UUID + ")"); this.homebridgeService = homebridgeService; this.nobleService = nobleService; this.nobleService.discoverCharacteristics([], this.discoverCharacteristics.bind(this)); }; BluetoothService.prototype.discoverCharacteristics = function (error, nobleCharacteristics) { if (error) { this.log.error(this.prefix, "Discover characteristics failed | " + error); return; } if (nobleCharacteristics.length == 0) { this.log.warn(this.prefix, "No characteristics discovered"); return; } for (var nobleCharacteristic of nobleCharacteristics) { var characteristicUUID = trimUUID(nobleCharacteristic.uuid); var bluetoothCharacteristic = this.bluetoothCharacteristics[characteristicUUID]; if (!bluetoothCharacteristic) { this.log.debug(this.prefix, "Ignored | Characteristic (" + nobleCharacteristic.uuid + ")"); continue; } var homebridgeCharacteristic = this.homebridgeService.getCharacteristic(bluetoothCharacteristic.class); bluetoothCharacteristic.connect(nobleCharacteristic, homebridgeCharacteristic); } }; BluetoothService.prototype.disconnect = function () { for (var characteristicUUID in this.bluetoothCharacteristics) { this.bluetoothCharacteristics[characteristicUUID].disconnect(); } if (this.nobleCharacteristic && this.homebridgeCharacteristic) { this.homebridgeService = null; this.nobleService = null; this.log.info(this.prefix, "Disconnected"); } }; function trimUUID(uuid) { return uuid.toLowerCase().replace(/:/g, "").replace(/-/g, ""); }