Skip to content

Commit

Permalink
support of external temperature sensor via mqtt and fix error on conn…
Browse files Browse the repository at this point in the history
…ection timeout (maxnowack#5)

* replace current dummy implementation of current temperature with data provided from configurable MQTT topic

* fix unhandled promise rejection in case of thermostat timeout

* fixing unhanded promise rejection and adding readme section about external current temperature sensors configuration

* remove named links from README.md

* fixing unhanded promise rejection and adding readme section about external current temperature sensors configuration
  • Loading branch information
aleksandrsivanovs authored and maxnowack committed Nov 29, 2016
1 parent a57d961 commit 6f513dc
Show file tree
Hide file tree
Showing 3 changed files with 94 additions and 9 deletions.
37 changes: 36 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
@@ -1,5 +1,7 @@
## Homebridge EQ3BLE
Homebridge plugin to control EQ3 bluetooth thermostats
Homebridge plugin to control EQ3 bluetooth thermostats.

It's possible to report MQTT topic's messages as current temperature of this thermostat.

### Usage
````json
Expand Down Expand Up @@ -29,7 +31,40 @@ You can configure the homebridge integration for the thermostat with the followi
| `discoverTimeout` | `60000` | time in milliseconds before a timeout error will be triggered |
| `connectionTimeout` | `10000` | time in milliseconds before homebridge will disconnect from the device after last action |
| `disableBoostSwitch` | `false` | if set to true, the boost switch won't be published from homebridge |
| `currentTemperature` | | MQTT configuration for current temperature |

### External current temperature configuration options

| Option | Description |
| --- | --- |
| `url` *(required)* | MQTT URL |
| `topic` *(required)* | MQTT Topic name |
| `username` | Username for accessing MQTT server |
| `password` | Password for accessing MQTT server |

## Usage with external current temperature sensor
````json
{
"bridge": {
"name": "Homebridge",
"username": "CC:22:3D:E3:CE:30",
"port": 51826,
"pin": "031-45-154"
},

"accessories": [{
"accessory": "EQ3-Thermostat",
"name": "Bedroom Thermostat",
"address": "00:1a:22:07:48:77",
"currentTemperature": {
"url": "mqtt://localhost",
"topic": "/home/sensors/bedroom/temperature",
"username": "sensors",
"password": "Sensors!"
}
}]
}
````


## License
Expand Down
3 changes: 2 additions & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -30,7 +30,8 @@
},
"dependencies": {
"babel-core": "6.18.2",
"eq3ble": "1.0.0"
"eq3ble": "1.0.0",
"mqtt": "^1.12.0"
},
"devDependencies": {
"babel-cli": "6.18.0",
Expand Down
63 changes: 56 additions & 7 deletions src/thermostat.js
Original file line number Diff line number Diff line change
@@ -1,9 +1,11 @@
import EQ3BLE from 'eq3ble'
import mqtt from 'mqtt'

export default function createThermostat({ Service, Characteristic }) {
return class EQ3Thermostat {
constructor(log, config) {
this.log = log
this.name = config.name
this.address = config.address
this.discoverTimeout = config.discoverTimeout || (60 * 1000) // 1 minute
this.connectionTimeout = config.connectionTimeout || (10 * 1000) // 10 seconds
Expand All @@ -17,6 +19,41 @@ export default function createThermostat({ Service, Characteristic }) {
this.thermostatService = new Service.Thermostat(this.name)
this.informationService = new Service.AccessoryInformation()
this.boostService = new Service.Switch(`${this.name} boost mode`)
this.currentTemperature = null

if (config.currentTemperature) {
this.mqttClient = mqtt.connect(config.currentTemperature.url, {
keepalive: 10,
clientId: 'mqttjs_'.concat(Math.random().toString(16).substr(2, 8)),
protocolId: 'MQTT',
protocolVersion: 4,
clean: true,
reconnectPeriod: 1000,
connectTimeout: 30 * 1000,
will: {
topic: 'WillMsg',
payload: 'Connection Closed abnormally..!',
qos: 0,
retain: false,
},
username: config.currentTemperature.username,
password: config.currentTemperature.password,
rejectUnauthorized: false,
})

this.mqttClient.on('connect', () => {
this.mqttClient.subscribe(config.currentTemperature.topic)
})

this.mqttClient.on('message', (topic, message) => {
const mqttData = JSON.parse(message)
if (mqttData === null) { return null }
this.currentTemperature = parseFloat(mqttData)
this.thermostatService
.setCharacteristic(Characteristic.CurrentTemperature, this.currentTemperature)
return this.currentTemperature
})
}

this.boostService
.setCharacteristic(Characteristic.Name, 'Boost Mode')
Expand All @@ -37,9 +74,15 @@ export default function createThermostat({ Service, Characteristic }) {
.on('get', this.execAfterConnect.bind(this, this.getTargetHeatingCoolingState.bind(this)))
.on('set', this.execAfterConnect.bind(this, this.setTargetHeatingCoolingState.bind(this)))

this.thermostatService
.getCharacteristic(Characteristic.CurrentTemperature)
.on('get', this.execAfterConnect.bind(this, this.getTargetTemperature.bind(this)))
if (this.mqttClient) {
this.thermostatService
.getCharacteristic(Characteristic.CurrentTemperature)
.on('get', this.getCurrentTemperature.bind(this))
} else {
this.thermostatService
.getCharacteristic(Characteristic.CurrentTemperature)
.on('get', this.execAfterConnect.bind(this, this.getTargetTemperature.bind(this)))
}

this.thermostatService
.getCharacteristic(Characteristic.TargetTemperature)
Expand All @@ -56,15 +99,17 @@ export default function createThermostat({ Service, Characteristic }) {
.on('get', this.execAfterConnect.bind(this, this.getTargetTemperature.bind(this)))


this.discoverPromise = this.discover()
this.discoverPromise = this.discover().catch((err) => {
this.log(err)
})
}
discover() {
return new Promise((resolve, reject) => {
this.log(`discovering thermostat (${this.address})`)
const discoverTimeout = setTimeout(() => {
this.discoverPromise = null
this.log(`cannot discover thermostat (${this.address})`)
reject(`discovering thermostat timed out (${this.address})`)
reject(new Error(`discovering thermostat timed out (${this.address})`))
}, this.discoverTimeout)
EQ3BLE.discoverByAddress(this.address, (device) => {
clearTimeout(discoverTimeout)
Expand All @@ -74,7 +119,7 @@ export default function createThermostat({ Service, Characteristic }) {
}, (err) => {
this.discoverPromise = null
this.log(`cannot discover thermostat (${this.address}): ${err}`)
reject(`discovering thermostat (${this.address}) resulted in error ${err}`)
reject(new Error(`discovering thermostat (${this.address}) resulted in error ${err}`))
})
})
}
Expand Down Expand Up @@ -198,6 +243,10 @@ export default function createThermostat({ Service, Characteristic }) {
callback(null, targetTemperature < 10 ? 10 : targetTemperature)
}, deviceErr => callback(deviceErr))
}
getCurrentTemperature(callback) {
this.log(this.name, ' - MQTT : ', this.currentTemperature)
callback(null, this.currentTemperature)
}
getTemperatureDisplayUnits(callback, ...args) {
const lastArg = args && args[args.length - 1]
if (lastArg.isError) return callback(lastArg)
Expand Down Expand Up @@ -235,7 +284,7 @@ export default function createThermostat({ Service, Characteristic }) {
case Characteristic.TargetHeatingCoolingState.COOL:
return this.device.manualMode().then(() => callback(),
deviceErr => callback(deviceErr))
default: return callback('Unsupport mode')
default: return callback('Unsupported mode')
}
}

Expand Down

0 comments on commit 6f513dc

Please sign in to comment.