Initial commit

This commit is contained in:
2022-07-12 16:34:41 +02:00
commit e7947dc769
14 changed files with 1287 additions and 0 deletions
+13
View File
@@ -0,0 +1,13 @@
export abstract class accessory {
public type: string
public typeId: number
constructor(type: string, typeId: number) {
this.type = type
this.typeId = typeId
}
abstract setName(name)
abstract setValue(key, value)
abstract update()
}
+12
View File
@@ -0,0 +1,12 @@
export interface device {
// --- Database Parameters ---
type: string
typeId: number
// --- HomeBridge Parameters ---
uniqueId: string
deviceName: string
// --- Extra Parameters ---
switchable: string
dimmable: string
detailedType: string
}
+134
View File
@@ -0,0 +1,134 @@
import { Service, PlatformAccessory, CharacteristicValue } from 'homebridge'
import { accessory } from './accessory'
import { device } from './device'
import { httpRequest, response } from './httpPromise'
import { domoticaPlatform } from './platform'
export class doorWindowSensorAccessory extends accessory {
private service: Service
private state = {
Position: 0
}
static async discoverDevices(platform: domoticaPlatform): Promise<device[]> {
return new Promise((resolve, reject) => {
let doorWindowSensorDevices: device[] = []
httpRequest("http://" + platform.config.hostname + ':' + platform.config.port + '/API/inventory/doorwindowsensor')
.then((response) => {
let devices = JSON.parse(response.body)
for (const device of devices) {
if (device.enabled == "true") {
let object: device = {
type: "DoorWindowSensor",
typeId: device.id,
uniqueId: "DoorWindowSensor." + device.id,
deviceName: device.name,
switchable: "false",
dimmable: "false",
detailedType: device.type
}
doorWindowSensorDevices.push(object)
}
}
resolve(doorWindowSensorDevices)
})
.catch((error) => {
reject('doorWindowSensorAccessory::discoverDevices Error ->' + error)
})
})
}
constructor(
private readonly platform: domoticaPlatform,
private readonly accessory: PlatformAccessory,
) {
super("DoorWindowSensor", accessory.context.device.typeId)
this.accessory.getService(this.platform.Service.AccessoryInformation)!
.setCharacteristic(this.platform.Characteristic.Manufacturer, 'Xiaomi')
.setCharacteristic(this.platform.Characteristic.Model, 'MCCGQ01LM')
.setCharacteristic(this.platform.Characteristic.SerialNumber, '04000' + accessory.context.device.typeId)
if (accessory.context.device.detailedType == "door") {
this.service = this.accessory.getService(this.platform.Service.Door) || this.accessory.addService(this.platform.Service.Door)
} else { // window
this.service = this.accessory.getService(this.platform.Service.Window) || this.accessory.addService(this.platform.Service.Window)
}
this.service.getCharacteristic(this.platform.Characteristic.CurrentPosition)
.onGet(this.getCurrentPosition.bind(this));
this.service.getCharacteristic(this.platform.Characteristic.PositionState)
.onGet(this.getPositionState.bind(this));
this.service.getCharacteristic(this.platform.Characteristic.TargetPosition)
.onGet(this.getTargetPosition.bind(this))
this.setName(accessory.context.device.deviceName)
this.update()
}
setName(name) {
this.service.setCharacteristic(this.platform.Characteristic.Name, name)
}
setValue(key, value) {
if (key == "state" && value != "") {
if (value == "Off") {
this.state.Position = 0
} else {
this.state.Position = 100
}
this.service.updateCharacteristic(this.platform.Characteristic.CurrentPosition, this.state.Position)
this.service.updateCharacteristic(this.platform.Characteristic.TargetPosition, this.state.Position)
}
}
update() {
const data = JSON.stringify([{
type: 'doorwindow',
query: 'state'
}])
httpRequest("http://" + this.platform.config.hostname + ':' + this.platform.config.port + '/API/Status', data)
.then((response) => {
const devices = JSON.parse(response.body)
for (const device of devices.doorwindow) {
if (device.id == this.accessory.context.device.typeId) {
if (device.state == 'closed') {
this.state.Position = 0
} else { // open
this.state.Position = 100
}
this.service.updateCharacteristic(this.platform.Characteristic.CurrentPosition, this.state.Position)
this.service.updateCharacteristic(this.platform.Characteristic.TargetPosition, this.state.Position)
break
}
}
})
.catch((error) => {
this.platform.log.debug('doorWindowSensorAccessory::update Error ->' + error)
})
}
getCurrentPosition(): CharacteristicValue {
return this.state.Position
}
getPositionState(): CharacteristicValue {
return this.platform.Characteristic.PositionState.STOPPED;
}
getTargetPosition(): CharacteristicValue {
return this.state.Position
}
getPosition(): CharacteristicValue {
return this.state.Position
}
}
+118
View File
@@ -0,0 +1,118 @@
import { Service, PlatformAccessory, CharacteristicValue } from 'homebridge'
import { accessory } from './accessory'
import { device } from './device'
import { httpRequest, response } from './httpPromise'
import { domoticaPlatform } from './platform'
export class environmentSensorAccessory extends accessory {
private serviceTemperature: Service
private serviceHumidity: Service
private state = {
Temperature: 0,
Humidity: 0
}
static async discoverDevices(platform: domoticaPlatform): Promise<device[]> {
return new Promise((resolve, reject) => {
let environmentSensorDevices: device[] = []
httpRequest("http://" + platform.config.hostname + ':' + platform.config.port + '/API/inventory/environmentsensor')
.then((response) => {
let devices = JSON.parse(response.body)
for (const device of devices) {
if (device.enabled == "true") {
let object: device = {
type: "EnvironmentSensor",
typeId: device.id,
uniqueId: "EnvironmentSensor." + device.id,
deviceName: device.name,
switchable: "false",
dimmable: "false",
detailedType: ""
}
environmentSensorDevices.push(object)
}
}
resolve(environmentSensorDevices)
})
.catch((error) => {
reject('environmentSensorAccessory::discoverDevices Error ->' + error)
})
})
}
constructor(
private readonly platform: domoticaPlatform,
private readonly accessory: PlatformAccessory,
) {
super("EnvironmentSensor", accessory.context.device.typeId)
this.accessory.getService(this.platform.Service.AccessoryInformation)!
.setCharacteristic(this.platform.Characteristic.Manufacturer, 'Xiaomi')
.setCharacteristic(this.platform.Characteristic.Model, 'WSDCGQ01LM')
.setCharacteristic(this.platform.Characteristic.SerialNumber, '03000' + accessory.context.device.typeId)
this.serviceTemperature = this.accessory.getService(this.platform.Service.TemperatureSensor) || this.accessory.addService(this.platform.Service.TemperatureSensor)
this.serviceTemperature.getCharacteristic(this.platform.Characteristic.CurrentTemperature)
.onGet(this.getTemperature.bind(this))
this.serviceHumidity = this.accessory.getService(this.platform.Service.HumiditySensor) || this.accessory.addService(this.platform.Service.HumiditySensor)
this.serviceHumidity.getCharacteristic(this.platform.Characteristic.CurrentRelativeHumidity)
.onGet(this.getHumidity.bind(this))
this.setName(accessory.context.device.deviceName)
this.update()
}
setName(name) {
this.serviceTemperature.setCharacteristic(this.platform.Characteristic.Name, name)
this.serviceHumidity.setCharacteristic(this.platform.Characteristic.Name, name)
}
setValue(key, value) {
if (key == "temperature" && value != "") {
this.state.Temperature = value / 100
this.serviceTemperature.updateCharacteristic(this.platform.Characteristic.CurrentTemperature, this.state.Temperature)
}
if (key == "humidity" && value != "") {
this.state.Humidity = value / 100
this.serviceHumidity.updateCharacteristic(this.platform.Characteristic.CurrentRelativeHumidity, this.state.Humidity)
}
}
update() {
const data = JSON.stringify([{
type: 'environment',
query: 'state'
}])
httpRequest("http://" + this.platform.config.hostname + ':' + this.platform.config.port + '/API/Status', data)
.then((response) => {
const devices = JSON.parse(response.body)
for (const device of devices.environment) {
if (device.id == this.accessory.context.device.typeId) {
this.state.Temperature = device.temperature
this.state.Humidity = device.humidity
this.serviceTemperature.updateCharacteristic(this.platform.Characteristic.CurrentTemperature, this.state.Temperature)
this.serviceHumidity.updateCharacteristic(this.platform.Characteristic.CurrentRelativeHumidity, this.state.Humidity)
break
}
}
})
.catch((error) => {
this.platform.log.debug('environmentSensorAccessory::getTemperature Error ->' + error)
})
}
getTemperature(): CharacteristicValue {
return this.state.Temperature
}
getHumidity(): CharacteristicValue {
return this.state.Humidity
}
}
+35
View File
@@ -0,0 +1,35 @@
'use strict'
const http = require('http')
const https = require('https')
export interface response {
statusCode: number,
headers: object,
body: string
}
export function httpRequest(urlOptions, data = ''): Promise<response> {
let operator = http
if (urlOptions.startsWith('https'))
operator = https
return new Promise((resolve, reject) => {
const request = operator.request(urlOptions,
(result) => {
let body = ''
result.on('data', (chunk = '') => (body += chunk))
result.on('error', reject)
result.on('end', () => {
if (result.statusCode >= 200 && result.statusCode <= 299) {
resolve({statusCode: result.statusCode, headers: result.headers, body: body})
} else {
reject('Request failed. status: ' + result.statusCode + ', body: ' + body)
}
})
})
request.on('error', reject)
request.write(data, 'binary')
request.end()
})
}
+98
View File
@@ -0,0 +1,98 @@
import { PlatformConfig } from 'homebridge'
import { domoticaPlatform } from './platform'
const request = require("request")
const http = require('http')
const url = require('url')
export class httpServer {
private address
private port
constructor(
private readonly platform: domoticaPlatform,
private readonly config: PlatformConfig
) {
this.address = "0.0.0.0"
this.port = 8080
}
serverCallback (request, response) {
var urlObject = url.parse(request.url, true)
var urlQuery = urlObject.query
var body: Uint8Array[] = []
var log = this.platform.log
request.on('error', (err) => {
log.debug("[ERROR Homebridge Domotica HttpServer] Reason: %s.", err)
}).on('data', (chunk) => {
body.push(chunk)
}).on('end', () => {
var bodyString = Buffer.concat(body).toString()
response.on('error', function(err) {
log.debug("[ERROR Homebridge Domotica HttpServer] Reason: %s.", err)
})
response.statusCode = 200
response.setHeader('Content-Type', 'application/json')
if (!urlQuery.type || !urlQuery.typeId) {
response.statusCode = 404
response.setHeader("Content-Type", "text/plain")
var errorText = "[ERROR Homebridge Domotica HttpServer] Type or TypeId missing in request."
response.write(errorText)
response.end()
} else {
var type = urlQuery.type
var typeId = urlQuery.typeId
if (type != "PowerSocket" && type != "PowerSwitch" && type != "Xiaomi") {
log.debug(request.url)
}
if (type == "Xiaomi") {
type = urlQuery.model
if (type == "TemperatureHumiditySensor") {
type = "EnvironmentSensor"
}
}
var foundAccessory
for (const accessory of this.platform.accessories) {
if (accessory.type == type && accessory.typeId == typeId) {
foundAccessory = accessory
break
}
}
if (foundAccessory) {
for (const key of Object.keys(urlQuery)) {
if (key != "type" && key != "typeId") {
foundAccessory.setValue(key, urlQuery[key])
}
}
var responseBody = { "success" : true };
response.write(JSON.stringify(responseBody))
response.end()
} else {
response.setHeader("Content-Type", "text/plain")
errorText = "[ERROR Homebridge Domotica HttpServer] " + type + " " + typeId + " not found."
if (type != "PowerSocket") {
log.debug(errorText + '(' + request.url + ')')
}
response.write(errorText)
response.end()
}
}
})
}
start() {
http.createServer(this.serverCallback.bind(this)).listen(this.port, this.address)
}
}
+8
View File
@@ -0,0 +1,8 @@
import { API } from 'homebridge'
import { PLATFORM_NAME } from './settings'
import { domoticaPlatform } from './platform'
export = (api: API) => {
api.registerPlatform(PLATFORM_NAME, domoticaPlatform)
}
+147
View File
@@ -0,0 +1,147 @@
import { Service, PlatformAccessory, CharacteristicValue } from 'homebridge'
import { accessory } from './accessory'
import { device } from './device'
import { httpRequest, response } from './httpPromise'
import { domoticaPlatform } from './platform'
export class klikAanKlikUitAccessory extends accessory {
private service: Service
private state = {
On: false,
Brightness: 100
}
static async discoverDevices(platform: domoticaPlatform): Promise<device[]> {
return new Promise((resolve, reject) => {
let klikaanklikuitDevices: device[] = []
httpRequest("http://" + platform.config.hostname + ':' + platform.config.port + '/API/inventory/klikaanklikuit')
.then((response) => {
let devices = JSON.parse(response.body)
for (const device of devices) {
if (device.enabled == "true") {
let object: device = {
type: "KlikAanKlikUit",
typeId: device.id,
uniqueId: "KlikAanKlikUit." + device.id,
deviceName: device.name,
switchable: "true",
dimmable: device.dimmable,
detailedType: ""
}
klikaanklikuitDevices.push(object)
}
}
resolve(klikaanklikuitDevices)
})
.catch((error) => {
reject('klikAanKlikUitAccessory::discoverDevices Error ->' + error)
})
})
}
constructor(
private readonly platform: domoticaPlatform,
private readonly accessory: PlatformAccessory,
) {
super("KlikAanKlikUit", accessory.context.device.typeId)
this.accessory.getService(this.platform.Service.AccessoryInformation)!
.setCharacteristic(this.platform.Characteristic.Manufacturer, 'KlikAanKlikUit')
.setCharacteristic(this.platform.Characteristic.Model, 'AC-1000')
.setCharacteristic(this.platform.Characteristic.SerialNumber, '01000' + accessory.context.device.typeId)
this.service = this.accessory.getService(this.platform.Service.Lightbulb) || this.accessory.addService(this.platform.Service.Lightbulb)
this.service.getCharacteristic(this.platform.Characteristic.On)
.onSet(this.setOn.bind(this))
.onGet(this.getOn.bind(this))
if (accessory.context.device.dimmable != "false") { // WTF?
this.service.getCharacteristic(this.platform.Characteristic.Brightness)
.onSet(this.setBrightness.bind(this))
.onGet(this.getBrightness.bind(this))
}
this.setName(accessory.context.device.deviceName)
this.update()
}
setName(name) {
this.service.setCharacteristic(this.platform.Characteristic.Name, name)
}
setValue(key, value) {
if (key == "state" && value != "") {
this.state.On = (value == "on")
this.service.updateCharacteristic(this.platform.Characteristic.On, this.state.On)
} else if (key == "dimLevel" && value!= "") {
this.state.Brightness = value
this.service.updateCharacteristic(this.platform.Characteristic.Brightness, this.state.Brightness)
}
}
update() {
const data = JSON.stringify([{
type: 'klikaanklikuit',
query: 'state'
}])
httpRequest("http://" + this.platform.config.hostname + ':' + this.platform.config.port + '/API/Status', data)
.then((response) => {
const devices = JSON.parse(response.body)
for (const device of devices.klikaanklikuit) {
if (device.id == this.accessory.context.device.typeId) {
if (device.state == 'on') {
this.state.On = true
} else {
this.state.On = false
}
this.service.updateCharacteristic(this.platform.Characteristic.On, this.state.On)
if (device.dimmable != 'false') { // WTF?
this.state.Brightness = device.dimLevel;
this.service.updateCharacteristic(this.platform.Characteristic.Brightness, this.state.Brightness)
}
break
}
}
})
.catch((error) => {
this.platform.log.debug('klikAanKlikUitAccessory::update Error ->' + error)
})
}
async setOn(value: CharacteristicValue) {
this.state.On = value as boolean
// http://$domoticaIP/external/WebUpdate.php?data=KlikAanKlikUit/$id/$state/
httpRequest("http://" + this.platform.config.hostname + '/external/WebUpdate.php?data=KlikAanKlikUit/' + this.accessory.context.device.typeId + '/' + (this.state.On? '1': '0') + '/')
.catch((error) => {
this.platform.log.debug('klikAanKlikUitAccessory::setOn Error ->' + error)
})
}
getOn(): CharacteristicValue {
return this.state.On
}
async setBrightness(value: CharacteristicValue) {
this.state.Brightness = value as number
// http://$domoticaIP/external/WebUpdate.php?data=KlikAanKlikUit/$id/$state/$dimLevel
httpRequest("http://" + this.platform.config.hostname + '/external/WebUpdate.php?data=KlikAanKlikUit/' + this.accessory.context.device.typeId + '//' + this.state.Brightness)
.catch((error) => {
this.platform.log.debug('klikAanKlikUitAccessory::setBrightness Error ->' + error)
})
}
getBrightness(): CharacteristicValue {
return this.state.Brightness
}
}
+111
View File
@@ -0,0 +1,111 @@
import { Service, PlatformAccessory, CharacteristicValue } from 'homebridge'
import { accessory } from './accessory'
import { device } from './device'
import { httpRequest, response } from './httpPromise'
import { domoticaPlatform } from './platform'
export class motionSensorAccessory extends accessory {
private service: Service
private state = {
Motion: false
}
static async discoverDevices(platform: domoticaPlatform): Promise<device[]> {
return new Promise((resolve, reject) => {
let motionSensorDevices: device[] = []
httpRequest("http://" + platform.config.hostname + ':' + platform.config.port + '/API/inventory/motionsensor')
.then((response) => {
let devices = JSON.parse(response.body)
for (const device of devices) {
if (device.enabled == "true") {
let object: device = {
type: "MotionSensor",
typeId: device.id,
uniqueId: "MotionSensor." + device.id,
deviceName: device.name,
switchable: "false",
dimmable: "false",
detailedType: device.type
}
motionSensorDevices.push(object)
}
}
resolve(motionSensorDevices)
})
.catch((error) => {
reject('motionSensorAccessory::discoverDevices Error ->' + error)
})
})
}
constructor(
private readonly platform: domoticaPlatform,
private readonly accessory: PlatformAccessory,
) {
super("MotionSensor", accessory.context.device.typeId)
this.accessory.getService(this.platform.Service.AccessoryInformation)!
.setCharacteristic(this.platform.Characteristic.Manufacturer, 'Xiaomi')
.setCharacteristic(this.platform.Characteristic.Model, 'RTCGQ01LM')
.setCharacteristic(this.platform.Characteristic.SerialNumber, '05000' + accessory.context.device.typeId)
this.service = this.accessory.getService(this.platform.Service.MotionSensor) || this.accessory.addService(this.platform.Service.MotionSensor)
this.service.getCharacteristic(this.platform.Characteristic.MotionDetected)
.onGet(this.getState.bind(this));
this.setName(accessory.context.device.deviceName)
this.update()
}
setName(name) {
this.service.setCharacteristic(this.platform.Characteristic.Name, name)
}
setValue(key, value) {
if (key == "state" && value != "") {
if (value == "On") {
this.state.Motion = true
} else {
this.state.Motion = false
}
this.service.updateCharacteristic(this.platform.Characteristic.MotionDetected, this.state.Motion)
}
}
update() {
const data = JSON.stringify([{
type: 'motion',
query: 'state'
}])
httpRequest("http://" + this.platform.config.hostname + ':' + this.platform.config.port + '/API/Status', data)
.then((response) => {
const devices = JSON.parse(response.body)
for (const device of devices.motion) {
if (device.id == this.accessory.context.device.typeId) {
if (device.state == 'absent') {
this.state.Motion = false
} else { // present
this.state.Motion = true
}
return this.state.Motion
this.service.updateCharacteristic(this.platform.Characteristic.MotionDetected, this.state.Motion)
break
}
}
})
.catch((error) => {
this.platform.log.debug('motionSensorAccessory::update Error ->' + error)
})
}
getState(): CharacteristicValue {
return this.state.Motion
}
}
+189
View File
@@ -0,0 +1,189 @@
import { API, DynamicPlatformPlugin, Logger, PlatformAccessory, PlatformConfig, Service, Characteristic } from 'homebridge'
import { PLATFORM_NAME, PLUGIN_NAME } from './settings'
import { accessory } from './accessory'
import { device } from './device'
import { klikAanKlikUitAccessory } from './klikAanKlikUitAccessory'
import { powerSocketAccessory } from './powerSocketAccessory'
import { powerSwitchAccessory } from './powerSwitchAccessory'
import { environmentSensorAccessory } from './environmentSensorAccessory'
import { doorWindowSensorAccessory } from './doorWindowSensorAccessory'
import { motionSensorAccessory } from './motionSensorAccessory'
import { httpServer } from './httpServer'
export class domoticaPlatform implements DynamicPlatformPlugin {
public readonly Service: typeof Service = this.api.hap.Service
public readonly Characteristic: typeof Characteristic = this.api.hap.Characteristic
public readonly accessories: accessory[] = []
public readonly cachedAccessories: PlatformAccessory[] = []
public readonly presentAccessories: PlatformAccessory[] = []
private readonly discoveries: String[] = []
private readonly completedDiscoveries: String[] = []
private httpServer: httpServer = new httpServer(this, this.config)
constructor(
public readonly log: Logger,
public readonly config: PlatformConfig,
public readonly api: API
) {
this.api.on('didFinishLaunching', () => {
this.discoverDevices()
this.httpServer.start()
})
}
configureAccessory(accessory: PlatformAccessory) {
this.cachedAccessories.push(accessory)
}
discoverDevices() {
this.discoveryStarted('klikAanKlikUitAccessory')
klikAanKlikUitAccessory.discoverDevices(this)
.then((devices) => {
this.processDiscoveredDevices(devices)
this.discoveryCompleted('klikAanKlikUitAccessory')
})
.catch((error) => {
this.log.debug('klikAanKlikUitAccessory.discoverDevices Error ->' + error)
})
this.discoveryStarted('powerSocketAccessory')
powerSocketAccessory.discoverDevices(this)
.then((devices) => {
this.processDiscoveredDevices(devices)
this.discoveryCompleted('powerSocketAccessory')
})
.catch((error) => {
this.log.debug('powerSocketAccessory.discoverDevices Error ->' + error)
})
this.discoveryStarted('powerSwitchAccessory')
powerSwitchAccessory.discoverDevices(this)
.then((devices) => {
this.processDiscoveredDevices(devices)
this.discoveryCompleted('powerSwitchAccessory')
})
.catch((error) => {
this.log.debug('powerSwitchAccessory.discoverDevices Error ->' + error)
})
this.discoveryStarted('environmentSensorAccessory')
environmentSensorAccessory.discoverDevices(this)
.then((devices) => {
this.processDiscoveredDevices(devices)
this.discoveryCompleted('environmentSensorAccessory')
})
.catch((error) => {
this.log.debug('environmentSensorAccessory.discoverDevices Error ->' + error)
})
this.discoveryStarted('doorWindowSensorAccessory')
doorWindowSensorAccessory.discoverDevices(this)
.then((devices) => {
this.processDiscoveredDevices(devices)
this.discoveryCompleted('doorWindowSensorAccessory')
})
.catch((error) => {
this.log.debug('doorWindowSensorAccessory.discoverDevices Error ->' + error)
})
this.discoveryStarted('motionSensorAccessory')
motionSensorAccessory.discoverDevices(this)
.then((devices) => {
this.processDiscoveredDevices(devices)
this.discoveryCompleted('motionSensorAccessory')
})
.catch((error) => {
this.log.debug('motionSensorAccessory.discoverDevices Error ->' + error)
})
}
processDiscoveredDevices(domoticaDevices: device[]) {
for (const device of domoticaDevices) {
const uuid = this.api.hap.uuid.generate(device.uniqueId)
const existingAccessory = this.cachedAccessories.find(accessory => accessory.UUID === uuid)
if (existingAccessory) {
this.log.info('| ' + existingAccessory.context.device.type + ': ' + existingAccessory.displayName)
this.presentAccessories.push(existingAccessory)
if (existingAccessory.displayName != device.deviceName) {
this.log.info('! ' + existingAccessory.displayName + ' => ' + device.deviceName)
existingAccessory.context.device = device
this.api.updatePlatformAccessories([existingAccessory])
}
if (existingAccessory.context.device.type == 'KlikAanKlikUit') {
this.accessories.push(new klikAanKlikUitAccessory(this, existingAccessory))
} else if (existingAccessory.context.device.type == 'PowerSocket') {
this.accessories.push(new powerSocketAccessory(this, existingAccessory))
} else if (existingAccessory.context.device.type == 'PowerSwitch') {
this.accessories.push(new powerSwitchAccessory(this, existingAccessory))
} else if (existingAccessory.context.device.type == 'EnvironmentSensor') {
this.accessories.push(new environmentSensorAccessory(this, existingAccessory))
} else if (existingAccessory.context.device.type == 'DoorWindowSensor') {
this.accessories.push(new doorWindowSensorAccessory(this, existingAccessory))
} else if (existingAccessory.context.device.type == 'MotionSensor') {
this.accessories.push(new motionSensorAccessory(this, existingAccessory))
}
} else {
this.log.info('+ ' + device.type + ': ' + device.deviceName)
const accessory = new this.api.platformAccessory(device.deviceName, uuid)
accessory.context.device = device
if (accessory.context.device.type == 'KlikAanKlikUit') {
this.accessories.push(new klikAanKlikUitAccessory(this, accessory))
} else if (accessory.context.device.type == 'PowerSocket') {
this.accessories.push(new powerSocketAccessory(this, accessory))
} else if (accessory.context.device.type == 'PowerSwitch') {
this.accessories.push(new powerSwitchAccessory(this, accessory))
} else if (accessory.context.device.type == 'EnvironmentSensor') {
this.accessories.push(new environmentSensorAccessory(this, accessory))
} else if (accessory.context.device.type == 'DoorWindowSensor') {
this.accessories.push(new doorWindowSensorAccessory(this, accessory))
} else if (accessory.context.device.type == 'MotionSensor') {
this.accessories.push(new motionSensorAccessory(this, accessory))
}
this.api.registerPlatformAccessories(PLUGIN_NAME, PLATFORM_NAME, [accessory])
}
}
}
discoveryStarted(deviceType) {
this.discoveries.push(deviceType)
}
discoveryCompleted(deviceType) {
this.completedDiscoveries.push(deviceType)
if (this.completedDiscoveries.length == this.discoveries.length) {
this.cleanupRemovedDevices()
}
//setInterval(this.updateAccessories.bind(this), 60 * 1000); // Poll every minute
}
updateAccessories() {
for (const accessory of this.accessories) {
accessory.update()
}
}
cleanupRemovedDevices() {
for (const existingAccessory of this.cachedAccessories) {
const presentAccessory = this.presentAccessories.find(accessory => accessory.UUID === existingAccessory.UUID)
if (!presentAccessory) {
this.log.info('- ' + existingAccessory.context.device.type + ': ' + existingAccessory.displayName)
this.api.unregisterPlatformAccessories(PLUGIN_NAME, PLATFORM_NAME, [existingAccessory])
}
}
}
}
+141
View File
@@ -0,0 +1,141 @@
import { Service, PlatformAccessory, CharacteristicValue } from 'homebridge'
import { ExampleHomebridgePlatform } from './platform'
/**
* Platform Accessory
* An instance of this class is created for each accessory your platform registers
* Each accessory may expose multiple services of different service types.
*/
export class ExamplePlatformAccessory {
private service: Service
/**
* These are just used to create a working example
* You should implement your own code to track the state of your accessory
*/
private exampleStates = {
On: false,
Brightness: 100,
}
constructor(
private readonly platform: ExampleHomebridgePlatform,
private readonly accessory: PlatformAccessory,
) {
// set accessory information
this.accessory.getService(this.platform.Service.AccessoryInformation)!
.setCharacteristic(this.platform.Characteristic.Manufacturer, 'Default-Manufacturer')
.setCharacteristic(this.platform.Characteristic.Model, 'Default-Model')
.setCharacteristic(this.platform.Characteristic.SerialNumber, 'Default-Serial')
// get the LightBulb service if it exists, otherwise create a new LightBulb service
// you can create multiple services for each accessory
this.service = this.accessory.getService(this.platform.Service.Lightbulb) || this.accessory.addService(this.platform.Service.Lightbulb)
// set the service name, this is what is displayed as the default name on the Home app
// in this example we are using the name we stored in the `accessory.context` in the `discoverDevices` method.
this.service.setCharacteristic(this.platform.Characteristic.Name, accessory.context.device.exampleDisplayName)
// each service must implement at-minimum the "required characteristics" for the given service type
// see https://developers.homebridge.io/#/service/Lightbulb
// register handlers for the On/Off Characteristic
this.service.getCharacteristic(this.platform.Characteristic.On)
.onSet(this.setOn.bind(this)) // SET - bind to the `setOn` method below
.onGet(this.getOn.bind(this)) // GET - bind to the `getOn` method below
// register handlers for the Brightness Characteristic
this.service.getCharacteristic(this.platform.Characteristic.Brightness)
.onSet(this.setBrightness.bind(this)) // SET - bind to the 'setBrightness` method below
/**
* Creating multiple services of the same type.
*
* To avoid "Cannot add a Service with the same UUID another Service without also defining a unique 'subtype' property." error,
* when creating multiple services of the same type, you need to use the following syntax to specify a name and subtype id:
* this.accessory.getService('NAME') || this.accessory.addService(this.platform.Service.Lightbulb, 'NAME', 'USER_DEFINED_SUBTYPE_ID')
*
* The USER_DEFINED_SUBTYPE must be unique to the platform accessory (if you platform exposes multiple accessories, each accessory
* can use the same sub type id.)
*/
// Example: add two "motion sensor" services to the accessory
const motionSensorOneService = this.accessory.getService('Motion Sensor One Name') ||
this.accessory.addService(this.platform.Service.MotionSensor, 'Motion Sensor One Name', 'YourUniqueIdentifier-1')
const motionSensorTwoService = this.accessory.getService('Motion Sensor Two Name') ||
this.accessory.addService(this.platform.Service.MotionSensor, 'Motion Sensor Two Name', 'YourUniqueIdentifier-2')
/**
* Updating characteristics values asynchronously.
*
* Example showing how to update the state of a Characteristic asynchronously instead
* of using the `on('get')` handlers.
* Here we change update the motion sensor trigger states on and off every 10 seconds
* the `updateCharacteristic` method.
*
*/
let motionDetected = false
setInterval(() => {
// EXAMPLE - inverse the trigger
motionDetected = !motionDetected
// push the new value to HomeKit
motionSensorOneService.updateCharacteristic(this.platform.Characteristic.MotionDetected, motionDetected)
motionSensorTwoService.updateCharacteristic(this.platform.Characteristic.MotionDetected, !motionDetected)
this.platform.log.debug('Triggering motionSensorOneService:', motionDetected)
this.platform.log.debug('Triggering motionSensorTwoService:', !motionDetected)
}, 10000)
}
/**
* Handle "SET" requests from HomeKit
* These are sent when the user changes the state of an accessory, for example, turning on a Light bulb.
*/
async setOn(value: CharacteristicValue) {
// implement your own code to turn your device on/off
this.exampleStates.On = value as boolean
this.platform.log.debug('Set Characteristic On ->', value)
}
/**
* Handle the "GET" requests from HomeKit
* These are sent when HomeKit wants to know the current state of the accessory, for example, checking if a Light bulb is on.
*
* GET requests should return as fast as possbile. A long delay here will result in
* HomeKit being unresponsive and a bad user experience in general.
*
* If your device takes time to respond you should update the status of your device
* asynchronously instead using the `updateCharacteristic` method instead.
* @example
* this.service.updateCharacteristic(this.platform.Characteristic.On, true)
*/
async getOn(): Promise<CharacteristicValue> {
// implement your own code to check if the device is on
const isOn = this.exampleStates.On
this.platform.log.debug('Get Characteristic On ->', isOn)
// if you need to return an error to show the device as "Not Responding" in the Home app:
// throw new this.platform.api.hap.HapStatusError(this.platform.api.hap.HAPStatus.SERVICE_COMMUNICATION_FAILURE)
return isOn
}
/**
* Handle "SET" requests from HomeKit
* These are sent when the user changes the state of an accessory, for example, changing the Brightness
*/
async setBrightness(value: CharacteristicValue) {
// implement your own code to set the brightness
this.exampleStates.Brightness = value as number
this.platform.log.debug('Set Characteristic Brightness -> ', value)
}
}
+127
View File
@@ -0,0 +1,127 @@
import { Service, PlatformAccessory, CharacteristicValue } from 'homebridge'
import { accessory } from './accessory'
import { device } from './device'
import { httpRequest, response } from './httpPromise'
import { domoticaPlatform } from './platform'
export class powerSocketAccessory extends accessory {
private service: Service
private state = {
On: false
}
static async discoverDevices(platform: domoticaPlatform): Promise<device[]> {
return new Promise((resolve, reject) => {
let powersocketDevices: device[] = []
httpRequest("http://" + platform.config.hostname + ':' + platform.config.port + '/API/inventory/powersocket')
.then((response) => {
let devices = JSON.parse(response.body)
for (const device of devices) {
if (device.enabled == "true" &&
device.switchable == "true") {
let object: device = {
type: "PowerSocket",
typeId: device.id,
uniqueId: "PowerSocket." + device.id,
deviceName: device.name,
switchable: device.switchable,
dimmable: "false",
detailedType: device.type
}
powersocketDevices.push(object)
}
}
resolve(powersocketDevices)
})
.catch((error) => {
reject('powerSocketAccessory::discoverDevices Error ->' + error)
})
})
}
constructor(
private readonly platform: domoticaPlatform,
private readonly accessory: PlatformAccessory,
) {
super("PowerSocket", accessory.context.device.typeId)
this.accessory.getService(this.platform.Service.AccessoryInformation)!
.setCharacteristic(this.platform.Characteristic.Manufacturer, 'PowerSocket')
.setCharacteristic(this.platform.Characteristic.Model, 'BW-SHP6')
.setCharacteristic(this.platform.Characteristic.SerialNumber, '02000' + accessory.context.device.typeId)
if (accessory.context.device.detailedType == "light") {
this.service = this.accessory.getService(this.platform.Service.Lightbulb) || this.accessory.addService(this.platform.Service.Lightbulb)
} else { // powersocket
this.service = this.accessory.getService(this.platform.Service.Outlet) || this.accessory.addService(this.platform.Service.Outlet)
}
if (accessory.context.device.switchable == "true") {
this.service.getCharacteristic(this.platform.Characteristic.On)
.onSet(this.setOn.bind(this))
.onGet(this.getOn.bind(this))
} else {
this.service.getCharacteristic(this.platform.Characteristic.On)
.onGet(this.getOn.bind(this))
}
this.setName(accessory.context.device.deviceName)
this.update()
}
setName(name) {
this.service.setCharacteristic(this.platform.Characteristic.Name, name)
}
setValue(key, value) {
if (key == "state" && value != "") {
this.state.On = (value == "1")
this.service.updateCharacteristic(this.platform.Characteristic.On, this.state.On)
}
}
update() {
const data = JSON.stringify([{
type: 'powersocket',
query: 'state'
}])
httpRequest("http://" + this.platform.config.hostname + ':' + this.platform.config.port + '/API/Status', data)
.then((response) => {
const devices = JSON.parse(response.body)
for (const device of devices.powersocket) {
if (device.id == this.accessory.context.device.typeId) {
if (device.state == 'on') {
this.state.On = true
} else {
this.state.On = false
}
this.service.updateCharacteristic(this.platform.Characteristic.On, this.state.On)
break
}
}
})
.catch((error) => {
this.platform.log.debug('powerSocketAccessory::update Error ->' + error)
})
}
async setOn(value: CharacteristicValue) {
this.state.On = value as boolean
// http://$domoticaIP/external/WebUpdate.php?data=PowerSocket/$id/$state/
httpRequest("http://" + this.platform.config.hostname + '/external/WebUpdate.php?data=PowerSocket/' + this.accessory.context.device.typeId + '/' + (this.state.On? '1': '0') + '/')
.catch((error) => {
this.platform.log.debug('powerSocketAccessory::setOn Error ->' + error)
})
}
getOn(): CharacteristicValue {
return this.state.On
}
}
+152
View File
@@ -0,0 +1,152 @@
import { Service, PlatformAccessory, CharacteristicValue } from 'homebridge'
import { accessory } from './accessory'
import { device } from './device'
import { httpRequest, response } from './httpPromise'
import { domoticaPlatform } from './platform'
export class powerSwitchAccessory extends accessory {
private service: Service
private state = {
On: false,
Brightness: 100
}
static async discoverDevices(platform: domoticaPlatform): Promise<device[]> {
return new Promise((resolve, reject) => {
let powerswitchDevices: device[] = []
httpRequest("http://" + platform.config.hostname + ':' + platform.config.port + '/API/inventory/powerswitch')
.then((response) => {
let devices = JSON.parse(response.body)
for (const device of devices) {
if (device.enabled == "true") {
let object: device = {
type: "PowerSwitch",
typeId: device.id,
uniqueId: "PowerSwitch." + device.id,
deviceName: device.name,
switchable: "true",
dimmable: device.dimmable,
detailedType: device.type
}
powerswitchDevices.push(object)
}
}
resolve(powerswitchDevices)
})
.catch((error) => {
reject('powerSwitchAccessory::discoverDevices Error ->' + error)
})
})
}
constructor(
private readonly platform: domoticaPlatform,
private readonly accessory: PlatformAccessory,
) {
super("PowerSwitch", accessory.context.device.typeId)
this.accessory.getService(this.platform.Service.AccessoryInformation)!
.setCharacteristic(this.platform.Characteristic.Manufacturer, 'PowerSwitch')
.setCharacteristic(this.platform.Characteristic.Model, 'Shelly')
.setCharacteristic(this.platform.Characteristic.SerialNumber, '06000' + accessory.context.device.typeId)
if (accessory.context.device.detailedType == "light") {
this.service = this.accessory.getService(this.platform.Service.Lightbulb) || this.accessory.addService(this.platform.Service.Lightbulb)
} else if (accessory.context.device.detailedType == "fan") {
this.service = this.accessory.getService(this.platform.Service.Fan) || this.accessory.addService(this.platform.Service.Fan)
} else { // other
this.service = this.accessory.getService(this.platform.Service.Outlet) || this.accessory.addService(this.platform.Service.Outlet)
}
this.service.getCharacteristic(this.platform.Characteristic.On)
.onSet(this.setOn.bind(this))
.onGet(this.getOn.bind(this))
if (accessory.context.device.dimmable != "false") { // WTF?
this.service.getCharacteristic(this.platform.Characteristic.Brightness)
.onSet(this.setBrightness.bind(this))
.onGet(this.getBrightness.bind(this))
}
this.setName(accessory.context.device.deviceName)
this.update()
}
setName(name) {
this.service.setCharacteristic(this.platform.Characteristic.Name, name)
}
setValue(key, value) {
if (key == "state" && value != "") {
this.state.On = (value == "1")
this.service.updateCharacteristic(this.platform.Characteristic.On, this.state.On)
} else if (key == "dimLevel" && value != "") {
this.state.Brightness = value
this.service.updateCharacteristic(this.platform.Characteristic.Brightness, this.state.Brightness)
}
}
update() {
const data = JSON.stringify([{
type: 'powerswitch',
query: 'state'
}])
httpRequest("http://" + this.platform.config.hostname + ':' + this.platform.config.port + '/API/Status', data)
.then((response) => {
const devices = JSON.parse(response.body)
for (const device of devices.powerswitch) {
if (device.id == this.accessory.context.device.typeId) {
if (device.state == 'on') {
this.state.On = true
} else {
this.state.On = false
}
this.service.updateCharacteristic(this.platform.Characteristic.On, this.state.On)
if (device.dimmable != 'false') { // WTF?
this.state.Brightness = device.dimlevel;
this.service.updateCharacteristic(this.platform.Characteristic.Brightness, this.state.Brightness)
}
break
}
}
})
.catch((error) => {
this.platform.log.debug('powerSwitchAccessory::update Error ->' + error)
})
}
async setOn(value: CharacteristicValue) {
this.state.On = value as boolean
// http://$domoticaIP/external/WebUpdate.php?data=PowerSwitch/$id/$state/
httpRequest("http://" + this.platform.config.hostname + '/external/WebUpdate.php?data=PowerSwitch/' + this.accessory.context.device.typeId + '/' + (this.state.On? '1': '0') + '/')
.catch((error) => {
this.platform.log.debug('powerSwitchAccessory::setOn Error ->' + error)
})
}
getOn(): CharacteristicValue {
return this.state.On
}
async setBrightness(value: CharacteristicValue) {
this.state.Brightness = value as number
// http://$domoticaIP/external/WebUpdate.php?data=PowerSwitch/$id/$state/$dimLevel
httpRequest("http://" + this.platform.config.hostname + '/external/WebUpdate.php?data=PowerSwitch/' + this.accessory.context.device.typeId + '//' + this.state.Brightness)
.catch((error) => {
this.platform.log.debug('powerSwitchAccessory::setBrightness Error ->' + error)
})
}
getBrightness(): CharacteristicValue {
return this.state.Brightness
}
}
+2
View File
@@ -0,0 +1,2 @@
export const PLATFORM_NAME = 'Domotica'
export const PLUGIN_NAME = 'homebridge-domotica'