├── .gitignore ├── README.md ├── package.json ├── LICENSE └── index.js /.gitignore: -------------------------------------------------------------------------------- 1 | node_modules/ 2 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Homebridge Kettle 2 | This is a simple Homebridge plugin for the [Fellow Stagg EKG+]() kettle so that it can be controlled over WiFi using HomeKit. 3 | 4 | This is intended to be used with my [`stagg-ekg-plus` Python program](https://github.com/calvinmclean/stagg-ekg-plus) which handles the actual BLE communication with the kettle. 5 | 6 | ## Config Example 7 | ```json 8 | "accessories": [ 9 | { 10 | "accessory": "MyKettle", 11 | "room": "Kitchen", 12 | "name": "Kettle", 13 | "url": "http://localhost:8000" 14 | } 15 | ], 16 | ``` 17 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "homebridge-kettle", 3 | "version": "0.0.1", 4 | "description": "This is a Homebridge plugin for connecting the Fellow Stagg EKG+", 5 | "license": "ISC", 6 | "keywords": [ 7 | "homebridge-plugin" 8 | ], 9 | "repository": { 10 | "type": "git", 11 | "url": "git://github.com/calvinmclean/homebridge-kettle.git" 12 | }, 13 | "bugs": { 14 | "url": "http://github.com/calvinmclean/homebridge-kettle/issues" 15 | }, 16 | "engines": { 17 | "node": ">=0.12.0", 18 | "homebridge": ">=0.2.0" 19 | }, 20 | "dependencies": { 21 | "request": "^2.65.0" 22 | } 23 | } 24 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2020 Calvin McLean 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /index.js: -------------------------------------------------------------------------------- 1 | 'use strict' 2 | 3 | const request = require('request') 4 | const url = require('url') 5 | 6 | let Service, Characteristic 7 | 8 | module.exports = (homebridge) => { 9 | Service = homebridge.hap.Service 10 | Characteristic = homebridge.hap.Characteristic 11 | homebridge.registerAccessory("homebridge-kettle", "MyKettle", StaggEKGAccessory) 12 | } 13 | 14 | class StaggEKGAccessory { 15 | constructor (log, config) { 16 | this.log = log; 17 | this.config = config; 18 | this.service = new Service.Thermostat(this.config.name); 19 | this.url = this.config.url; 20 | this.tempDisplayUnits = 1; 21 | 22 | this.maxTemp = 100; 23 | this.minTemp = 40; 24 | } 25 | 26 | getServices () { 27 | const informationService = new Service.AccessoryInformation() 28 | informationService 29 | .setCharacteristic(Characteristic.Manufacturer, "Fellow") 30 | .setCharacteristic(Characteristic.Model, "Stagg EKG+") 31 | .setCharacteristic(Characteristic.SerialNumber, "123-456-789") 32 | 33 | this.service.getCharacteristic(Characteristic.TargetHeatingCoolingState) 34 | .on('get', this.getTargetHeatingCoolingStateCharacteristicHandler.bind(this)) 35 | .on('set', this.setTargetHeatingCoolingStateCharacteristicHandler.bind(this)) 36 | 37 | this.service.getCharacteristic(Characteristic.TargetTemperature) 38 | .on('get', this.getTargetTemperatureHandler.bind(this)) 39 | .on('set', this.setTargetTemperatureHandler.bind(this)) 40 | 41 | this.service.getCharacteristic(Characteristic.TargetTemperature) 42 | .setProps({ 43 | maxValue: 100, 44 | minValue: 40, 45 | unit: 1 46 | }) 47 | 48 | this.service.getCharacteristic(Characteristic.CurrentTemperature) 49 | .setProps({ 50 | maxValue: 100, 51 | minValue: 0, 52 | unit: 1 53 | }) 54 | 55 | this.service.getCharacteristic(Characteristic.TemperatureDisplayUnits) 56 | .setProps({value: 1}) 57 | 58 | this.service.getCharacteristic(Characteristic.CurrentTemperature) 59 | .on('get', this.getCurrentTemperatureHandler.bind(this)) 60 | 61 | this.service.getCharacteristic(Characteristic.TemperatureDisplayUnits) 62 | .on('get', this.getTemperatureDisplayUnitsHandler.bind(this)) 63 | .on('set', this.setTemperatureDisplayUnitsHandler.bind(this)) 64 | 65 | this.service.getCharacteristic(Characteristic.TargetHeatingCoolingState) 66 | .setProps({validValues: [0, 1]}) 67 | 68 | this.service.getCharacteristic(Characteristic.CurrentHeatingCoolingState) 69 | .setProps({validValues: [0, 1]}) 70 | 71 | return [informationService, this.service] 72 | } 73 | 74 | getTargetHeatingCoolingStateCharacteristicHandler (callback) { 75 | this.log(`calling getTargetHeatingCoolingStateCharacteristicHandler`) 76 | var self = this; 77 | request({ 78 | url: self.url + "/state", 79 | method: "GET" 80 | }, function (error, response, body) { 81 | self.log(`getTargetHeatingCoolingState result:`, body) 82 | self.service.updateCharacteristic(Characteristic.TargetHeatingCoolingState, body) 83 | }); 84 | callback(null, this.service.getCharacteristic(Characteristic.TargetHeatingCoolingState).value) 85 | } 86 | 87 | setTargetHeatingCoolingStateCharacteristicHandler (value, callback) { 88 | this.service.updateCharacteristic(Characteristic.TargetHeatingCoolingState, value) 89 | this.log(`calling setTargetHeatingCoolingStateCharacteristicHandler`, value) 90 | var self = this; 91 | request({ 92 | url: self.url + "/state", 93 | method: "POST", 94 | json: false, 95 | body: "value=" + value, 96 | headers: {"Content-Length": 7} 97 | }, function (error, response, body){}); 98 | callback(null, value) 99 | } 100 | 101 | getTargetTemperatureHandler (callback) { 102 | this.log(`calling getTargetTemperatureHandler`) 103 | var self = this; 104 | request({ 105 | url: self.url + "/target_temp", 106 | method: "GET" 107 | }, function (error, response, body) { 108 | self.log(`getTargetTemperatureHandler result:`, body) 109 | self.service.updateCharacteristic(Characteristic.TargetTemperature, (body - 32)/1.8000) 110 | }); 111 | callback(null, this.service.getCharacteristic(Characteristic.TargetTemperature).value) 112 | } 113 | 114 | setTargetTemperatureHandler (value, callback) { 115 | this.service.updateCharacteristic(Characteristic.TargetTemperature, value) 116 | this.log(`calling setTargetTemperatureHandler`, value) 117 | var self = this; 118 | request({ 119 | url: self.url + "/target_temp", 120 | method: "POST", 121 | json: false, 122 | body: "value=" + value.toString(), 123 | headers: {"Content-Length": 6 + value.toString().length} 124 | }, function (error, response, body){}); 125 | callback(null, value) 126 | } 127 | 128 | getCurrentTemperatureHandler (callback) { 129 | this.log(`calling getCurrentTemperatureHandler`) 130 | var self = this; 131 | request({ 132 | url: self.url + "/current_temp", 133 | method: "GET" 134 | }, function (error, response, body) { 135 | self.log(`getCurrentTemperatureHandler result:`, body) 136 | self.service.updateCharacteristic(Characteristic.CurrentTemperature, (body - 32)/1.8000) 137 | }); 138 | callback(null, this.service.getCharacteristic(Characteristic.CurrentTemperature).value) 139 | } 140 | 141 | getTemperatureDisplayUnitsHandler (callback) { 142 | this.log(`calling getTemperatureDisplayUnitsHandler`, this.tempDisplayUnits) 143 | callback(null, this.tempDisplayUnits) 144 | } 145 | 146 | setTemperatureDisplayUnitsHandler (value, callback) { 147 | this.log(`calling setTemperatureDisplayUnitsHandler`, value) 148 | callback(null, this.tempDisplayUnits) 149 | } 150 | } 151 | --------------------------------------------------------------------------------