Skip to content

Commit

Permalink
Merge pull request #272 from bjoernrennfanz/feature/dect_smoke_sensors
Browse files Browse the repository at this point in the history
Implemented support to use an Telekom smoke detector with homebridge.
  • Loading branch information
seydx authored Jun 12, 2022
2 parents e0cf11d + 3c2ad53 commit 6d7b91e
Show file tree
Hide file tree
Showing 8 changed files with 174 additions and 6 deletions.
8 changes: 7 additions & 1 deletion config.schema.json
Original file line number Diff line number Diff line change
Expand Up @@ -650,7 +650,7 @@
"title": "Battery",
"type": "boolean",
"condition": {
"functionBody": "return model.smarthome[arrayIndices[0]].accType === 'thermostat' || model.smarthome[arrayIndices[0]].accType === 'window' || model.smarthome[arrayIndices[0]].accType === 'temperature' || model.smarthome[arrayIndices[0]].accType === 'contact' || model.smarthome[arrayIndices[0]].accTypeGroup === 'thermostat' || model.smarthome[arrayIndices[0]].accType === 'button';"
"functionBody": "return model.smarthome[arrayIndices[0]].accType === 'thermostat' || model.smarthome[arrayIndices[0]].accType === 'window' || model.smarthome[arrayIndices[0]].accType === 'temperature' || model.smarthome[arrayIndices[0]].accType === 'contact' || model.smarthome[arrayIndices[0]].accTypeGroup === 'thermostat' || model.smarthome[arrayIndices[0]].accType === 'smoke' || model.smarthome[arrayIndices[0]].accType === 'button';"
},
"description": "Enable this, if the device has a built in battery sensor. This shows additional battery information within the accessory."
},
Expand Down Expand Up @@ -773,6 +773,12 @@
"enum": [
"window"
]
},
{
"title": "Smoke Detector",
"enum": [
"smoke"
]
}
],
"required": true,
Expand Down
4 changes: 2 additions & 2 deletions docs/Supported.md
Original file line number Diff line number Diff line change
Expand Up @@ -37,7 +37,7 @@
- [x] Fritz!DECT 210
- [x] Fritz!DECT 200
- [x] HAN-FUN Contact sensor


- [x] Telekom smoke detector
- [x] Telekom light bulb

**Note:** The page is constantly updated. If your device is also supported, please contact me
2 changes: 1 addition & 1 deletion package.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "homebridge-fritz-platform",
"version": "6.0.18",
"version": "6.0.20",
"description": "Homebridge Plugin to control FritzBox router, smarthome devices and more.",
"main": "index.js",
"funding": [
Expand Down
2 changes: 1 addition & 1 deletion src/accessories/smarthome/smarthome.config.js
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
'use strict';

const Config = (smarthomeConfig) => {
const validTypes = ['switch', 'contact', 'thermostat', 'lightbulb', 'temperature', 'window', 'blind', 'button'];
const validTypes = ['switch', 'contact', 'thermostat', 'lightbulb', 'temperature', 'window', 'blind', 'button', 'smoke'];
const validTypesGroup = ['switch', 'lightbulb', 'thermostat', 'switch-lightbulb'];
const validButtons = [1, 4];

Expand Down
73 changes: 73 additions & 0 deletions src/accessories/smarthome/smarthome.handler.js
Original file line number Diff line number Diff line change
Expand Up @@ -203,6 +203,12 @@ class Handler {
}
case 'smarthome-blind':
break;
case 'smarthome-smoke': {
if (historyService) {
historyService.addEntry({ time: moment().unix(), status: context.newValue ? 1 : 0 });
}
break;
}
default:
logger.warn(
`Can not handle CHANGE event. Unknown accessory subtype (${subtype})`,
Expand Down Expand Up @@ -1175,6 +1181,70 @@ class Handler {

return state;
}
case 'smarthome-smoke': {
let state = accessory
.getService(this.api.hap.Service.SmokeSensor)
.getCharacteristic(this.api.hap.Characteristic.SmokeDetected).value;

try {
let device = this.smarthomeList.devices.find((device) => device.ain.includes(accessory.context.config.ain));
logger.debug(device, `${accessory.displayName} (${subtype})`);

if (device) {
accessory.context.config.ain = device.ain;

if (device.online) {
if (device.alert) {
state = device.alert.state || 0;
} else {
logger.warn(
'Can not find alert data - "accType" and/or options correct?',
`${accessory.displayName} (${subtype})`
);
}

if (accessory.context.config.battery) {
if (device.battery) {
let batteryLevel = device.battery.value || 0;
let lowBattery = device.battery.low || 0;

accessory
.getService(this.api.hap.Service.BatteryService)
.getCharacteristic(this.api.hap.Characteristic.BatteryLevel)
.updateValue(batteryLevel);

accessory
.getService(this.api.hap.Service.BatteryService)
.getCharacteristic(this.api.hap.Characteristic.StatusLowBattery)
.updateValue(lowBattery);
} else {
logger.warn(
'Can not find battery data - "accType" and/or options correct?',
`${accessory.displayName} (${subtype})`
);
}
}
} else {
logger.warn('Device offline!', `${accessory.displayName} (${subtype})`);
}
} else {
logger.warn(
`Can not find device with AIN: ${accessory.context.config.ain}`,
`${accessory.displayName} (${subtype})`
);
}
} catch (err) {
logger.warn('An error occured during getting state!', `${accessory.displayName} (${subtype})`);
logger.error(err, `${accessory.displayName} (${subtype})`);
}

accessory
.getService(this.api.hap.Service.SmokeSensor)
.getCharacteristic(this.api.hap.Characteristic.SmokeDetected)
.updateValue(state);

return state;
}
default:
logger.warn(
`Can not handle GET event. Unknown accessory subtype (${subtype})`,
Expand Down Expand Up @@ -1741,6 +1811,9 @@ class Handler {
}
}
break;
case 'smarthome-smoke':
// no SET event
break;
default:
logger.warn(
`Can not handle SET event. Unknown accessory subtype (${subtype})`,
Expand Down
1 change: 1 addition & 0 deletions src/accessories/smarthome/smarthome.js
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ module.exports = {
SHLightbulbAccessory: require('./lightbulb/lightbulb.accessory'),
SHOutletAccessory: require('./outlet/outlet.accessory'),
SHOutletLightbulbAccessory: require('./outlet-lightbulb/outlet-lightbulb.accessory'),
SHSmokeAccessory: require('./smoke/smoke.accessory'),
SHSwitchAccessory: require('./switch/switch.accessory'),
SHSwitchLightbulbAccessory: require('./switch-lightbulb/switch-lightbulb.accessory'),
SHTemperatureAccessory: require('./temperature/temperature.accessory'),
Expand Down
85 changes: 85 additions & 0 deletions src/accessories/smarthome/smoke/smoke.accessory.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,85 @@
'use strict';

const moment = require('moment');
const logger = require('../../../utils/logger');
const Handler = require('../smarthome.handler');

const timeout = (ms) => new Promise((res) => setTimeout(res, ms));

class Accessory {
constructor(api, accessory, accessories, meshMaster, HistoryService) {
this.api = api;
this.accessory = accessory;
this.HistoryService = HistoryService;

this.handler = Handler.configure(api, accessories, accessory.context.config.polling, meshMaster);
this.getService();
}

//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~//
// Services
//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~//

async getService() {
let service = this.accessory.getService(this.api.hap.Service.SmokeSensor);

if (!service) {
logger.info('Adding Smoke service', `${this.accessory.displayName} (${this.accessory.context.config.subtype})`);
service = this.accessory.addService(
this.api.hap.Service.SmokeSensor,
this.accessory.displayName,
this.accessory.context.config.subtype
);
}

if (this.accessory.context.config.battery) {
let batteryService = this.accessory.getService(this.api.hap.Service.BatteryService);

if (!batteryService) {
logger.info(
'Adding Battery service',
`${this.accessory.displayName} (${this.accessory.context.config.subtype})`
);
batteryService = this.accessory.addService(this.api.hap.Service.BatteryService);
}

batteryService.setCharacteristic(
this.api.hap.Characteristic.ChargingState,
this.api.hap.Characteristic.ChargingState.NOT_CHARGEABLE
);
} else {
if (this.accessory.getService(this.api.hap.Service.BatteryService)) {
this.accessory.removeService(this.accessory.getService(this.api.hap.Service.BatteryService));
}
}

this.historyService = new this.HistoryService('door', this.accessory, {
storage: 'fs',
path: this.api.user.storagePath() + '/fritzbox/',
disableTimer: true,
});

await timeout(250); //wait for historyService to load

service
.getCharacteristic(this.api.hap.Characteristic.SmokeDetected)
.on('change', (context) => this.handler.change(context, this.accessory, null, this.historyService));

this.refreshHistory(service);
}

async refreshHistory(service) {
let state = service.getCharacteristic(this.api.hap.Characteristic.SmokeDetected).value;

this.historyService.addEntry({
time: moment().unix(),
status: state ? 1 : 0,
});

setTimeout(() => {
this.refreshHistory(service);
}, 10 * 60 * 1000);
}
}

module.exports = Accessory;
5 changes: 4 additions & 1 deletion src/platform.js
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,7 @@ const {
SHLightbulbAccessory,
SHOutletAccessory,
SHOutletLightbulbAccessory,
SHSmokeAccessory,
SHSwitchAccessory,
SHSwitchLightbulbAccessory,
SHTemperatureAccessory,
Expand Down Expand Up @@ -212,7 +213,9 @@ FritzPlatform.prototype = {
else if (device.subtype === 'smarthome-window-switch')
new SHWindowSwitchAccessory(this.api, accessory, this.accessories, this.meshMaster);
else if (device.subtype === 'smarthome-lightbulb')
new SHLightbulbAccessory(this.api, accessory, this.accessories, this.meshMaster);
new SHLightbulbAccessory(this.api, accessory, this.accessories, this.meshMaster);
else if (device.subtype === 'smarthome-smoke')
new SHSmokeAccessory(this.api, accessory, this.accessories, this.meshMaster, HistoryService);
else if (device.subtype === 'smarthome-switch-lightbulb' && device.energy)
new SHOutletLightbulbAccessory(this.api, accessory, this.accessories, this.meshMaster, HistoryService);
else if (device.subtype === 'smarthome-switch-lightbulb' && !device.energy)
Expand Down

0 comments on commit 6d7b91e

Please sign in to comment.