homebridge-deconz 1.0.26 → 1.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/lib/Deconz/Resource.js +62 -1
- package/lib/DeconzAccessory/Gateway.js +9 -1
- package/lib/DeconzAccessory/index.js +9 -0
- package/lib/DeconzService/AlarmSystem.js +87 -0
- package/lib/DeconzService/Daylight.js +1 -1
- package/lib/DeconzService/Flag.js +1 -1
- package/lib/DeconzService/Label.js +8 -0
- package/lib/DeconzService/Status.js +1 -1
- package/package.json +3 -3
    
        package/lib/Deconz/Resource.js
    CHANGED
    
    | @@ -16,7 +16,7 @@ import '../DeconzService/Button.js' | |
| 16 16 | 
             
            const { toInstance, toInt, toObject, toString } = OptionParser
         | 
| 17 17 | 
             
            const { buttonEvent } = ApiClient
         | 
| 18 18 | 
             
            const { SINGLE, DOUBLE, LONG } = DeconzService.Button
         | 
| 19 | 
            -
            const rtypes = ['lights', 'sensors', 'groups']
         | 
| 19 | 
            +
            const rtypes = ['lights', 'sensors', 'groups', 'alarmsystems']
         | 
| 20 20 |  | 
| 21 21 | 
             
            const patterns = {
         | 
| 22 22 | 
             
              clipId: /^(S[0-9]{1,3})-([0-9a-z]{2})-([0-9a-z]{4})$/i,
         | 
| @@ -49,6 +49,19 @@ const hueTapMap = { | |
| 49 49 | 
             
              99: 0 // release 3 and 4
         | 
| 50 50 | 
             
            }
         | 
| 51 51 |  | 
| 52 | 
            +
            const ancillaryControlMap = {
         | 
| 53 | 
            +
              disarmed: 1002,
         | 
| 54 | 
            +
              already_disarmed: 1002,
         | 
| 55 | 
            +
              armed_stay: 2002,
         | 
| 56 | 
            +
              armed_night: 3002,
         | 
| 57 | 
            +
              armed_away: 4002,
         | 
| 58 | 
            +
              invalid_code: 5002,
         | 
| 59 | 
            +
              not_ready: 0,
         | 
| 60 | 
            +
              emergency: 6002,
         | 
| 61 | 
            +
              fire: 7002,
         | 
| 62 | 
            +
              panic: 8002
         | 
| 63 | 
            +
            }
         | 
| 64 | 
            +
             | 
| 52 65 | 
             
            /** Delegate class for a resource on a deCONZ gateway.
         | 
| 53 66 | 
             
              *
         | 
| 54 67 | 
             
              * @memberof Deconz
         | 
| @@ -115,6 +128,9 @@ class Resource { | |
| 115 128 | 
             
                body.name = body.name.replace(/[^\p{L}\p{N} ']/ug, ' ')
         | 
| 116 129 | 
             
                  .replace(/^[ ']*/, '')
         | 
| 117 130 | 
             
                  .replace(/[ ']*$/, '')
         | 
| 131 | 
            +
                if (rtype === 'alarmsystems') {
         | 
| 132 | 
            +
                  body.type = 'AlarmSystem'
         | 
| 133 | 
            +
                }
         | 
| 118 134 | 
             
                toString('body.type', body.type, true)
         | 
| 119 135 |  | 
| 120 136 | 
             
                let realDevice = false
         | 
| @@ -297,12 +313,15 @@ class Resource { | |
| 297 313 | 
             
                    case 'Window covering device': return 'WindowCovering'
         | 
| 298 314 | 
             
                    default: return null
         | 
| 299 315 | 
             
                  }
         | 
| 316 | 
            +
                } else if (this.rtype === 'alarmsystems') {
         | 
| 317 | 
            +
                  return 'AlarmSystem'
         | 
| 300 318 | 
             
                } else { // (this.rtype === 'sensors')
         | 
| 301 319 | 
             
                  const type = /^(CLIP|ZHA|ZGP)?([A-Za-z]*)$/.exec(this.body.type)?.[2]
         | 
| 302 320 | 
             
                  switch (type) {
         | 
| 303 321 | 
             
                    case 'AirPurifier': return 'AirPurifier'
         | 
| 304 322 | 
             
                    case 'AirQuality': return 'AirQuality'
         | 
| 305 323 | 
             
                    case 'Alarm': return 'Alarm'
         | 
| 324 | 
            +
                    case 'AncillaryControl': return 'Label'
         | 
| 306 325 | 
             
                    case 'Battery': return 'Battery'
         | 
| 307 326 | 
             
                    case 'CarbonMonoxide': return 'CarbonMonoxide'
         | 
| 308 327 | 
             
                    case 'Consumption': return 'Consumption'
         | 
| @@ -421,6 +440,16 @@ class Resource { | |
| 421 440 | 
             
                const buttons = []
         | 
| 422 441 | 
             
                let dots = false
         | 
| 423 442 | 
             
                switch (this.manufacturer) {
         | 
| 443 | 
            +
                  case 'Aqara':
         | 
| 444 | 
            +
                    switch (this.model) {
         | 
| 445 | 
            +
                      case 'lumi.switch.acn047': // Aqara T2 Module
         | 
| 446 | 
            +
                        buttons.push(1, 'S1', SINGLE)
         | 
| 447 | 
            +
                        buttons.push(2, 'S2', SINGLE)
         | 
| 448 | 
            +
                        break
         | 
| 449 | 
            +
                      default:
         | 
| 450 | 
            +
                        break
         | 
| 451 | 
            +
                    }
         | 
| 452 | 
            +
                    break
         | 
| 424 453 | 
             
                  case 'Bitron Home':
         | 
| 425 454 | 
             
                    switch (this.model) {
         | 
| 426 455 | 
             
                      case '902010/23': // Bitron remote, see #639.
         | 
| @@ -1117,6 +1146,25 @@ class Resource { | |
| 1117 1146 | 
             
                        break
         | 
| 1118 1147 | 
             
                    }
         | 
| 1119 1148 | 
             
                    break
         | 
| 1149 | 
            +
                  case 'frient AS':
         | 
| 1150 | 
            +
                    switch (this.model) {
         | 
| 1151 | 
            +
                      case 'KEPZB-110':
         | 
| 1152 | 
            +
                        buttons.push([1, 'Disarm', SINGLE])
         | 
| 1153 | 
            +
                        buttons.push([2, 'Armed Stay', SINGLE])
         | 
| 1154 | 
            +
                        buttons.push([3, 'Armed Night', SINGLE])
         | 
| 1155 | 
            +
                        buttons.push([4, 'Armed Away', SINGLE])
         | 
| 1156 | 
            +
                        buttons.push([5, 'Invalid Code', SINGLE])
         | 
| 1157 | 
            +
                        buttons.push([6, 'SOS', SINGLE])
         | 
| 1158 | 
            +
                        // buttons.push([7, 'Fire', SINGLE])
         | 
| 1159 | 
            +
                        // buttons.push([8, 'Panic', SINGLE])
         | 
| 1160 | 
            +
                        this.capabilities.toButtonEvent = (v) => {
         | 
| 1161 | 
            +
                          return ancillaryControlMap[v]
         | 
| 1162 | 
            +
                        }
         | 
| 1163 | 
            +
                        break
         | 
| 1164 | 
            +
                      default:
         | 
| 1165 | 
            +
                        break
         | 
| 1166 | 
            +
                    }
         | 
| 1167 | 
            +
                    break
         | 
| 1120 1168 | 
             
                  case 'icasa':
         | 
| 1121 1169 | 
             
                    switch (this.model) {
         | 
| 1122 1170 | 
             
                      case 'ICZB-KPD12':
         | 
| @@ -1211,6 +1259,19 @@ class Resource { | |
| 1211 1259 | 
             
                    }
         | 
| 1212 1260 | 
             
                    break
         | 
| 1213 1261 | 
             
                  default:
         | 
| 1262 | 
            +
                    if (this.body.type === 'ZHAAncillaryControl') {
         | 
| 1263 | 
            +
                      buttons.push([1, 'Disarm', SINGLE])
         | 
| 1264 | 
            +
                      buttons.push([2, 'Armed Stay', SINGLE])
         | 
| 1265 | 
            +
                      buttons.push([3, 'Armed Night', SINGLE])
         | 
| 1266 | 
            +
                      buttons.push([4, 'Armed Away', SINGLE])
         | 
| 1267 | 
            +
                      buttons.push([5, 'Invalid Code', SINGLE])
         | 
| 1268 | 
            +
                      buttons.push([6, 'Emergency', SINGLE])
         | 
| 1269 | 
            +
                      buttons.push([7, 'Fire', SINGLE])
         | 
| 1270 | 
            +
                      buttons.push([8, 'Panic', SINGLE])
         | 
| 1271 | 
            +
                      this.capabilities.toButtonEvent = (v) => {
         | 
| 1272 | 
            +
                        return ancillaryControlMap[v]
         | 
| 1273 | 
            +
                      }
         | 
| 1274 | 
            +
                    }
         | 
| 1214 1275 | 
             
                    break
         | 
| 1215 1276 | 
             
                }
         | 
| 1216 1277 | 
             
                if (buttons.length > 0) {
         | 
| @@ -29,7 +29,7 @@ const migration = { | |
| 29 29 | 
             
              classid: 1
         | 
| 30 30 | 
             
            }
         | 
| 31 31 |  | 
| 32 | 
            -
            const rtypes = ['lights', 'sensors', 'groups']
         | 
| 32 | 
            +
            const rtypes = ['lights', 'sensors', 'groups', 'alarmsystems']
         | 
| 33 33 |  | 
| 34 34 | 
             
            const periodicEvents = [
         | 
| 35 35 | 
             
              { rate: 60, event: 1002 },
         | 
| @@ -992,6 +992,11 @@ class Gateway extends AccessoryDelegate { | |
| 992 992 | 
             
                    try {
         | 
| 993 993 | 
             
                      fullState.groups[0] = await this.client.get('/groups/0')
         | 
| 994 994 | 
             
                    } catch (error) {}
         | 
| 995 | 
            +
                    try {
         | 
| 996 | 
            +
                      fullState.alarmsystems = await this.client.get('/alarmsystems')
         | 
| 997 | 
            +
                    } catch (error) {
         | 
| 998 | 
            +
                      fullState.alarmsystems = {}
         | 
| 999 | 
            +
                    }
         | 
| 995 1000 | 
             
                    this.context.fullState = fullState
         | 
| 996 1001 | 
             
                    this.pollFullState = false
         | 
| 997 1002 | 
             
                  } else {
         | 
| @@ -1016,6 +1021,9 @@ class Gateway extends AccessoryDelegate { | |
| 1016 1021 | 
             
                        this.context.fullState.groups[0] = await this.client.get('/groups/0')
         | 
| 1017 1022 | 
             
                      } catch (error) {}
         | 
| 1018 1023 | 
             
                    }
         | 
| 1024 | 
            +
                    if (this.nDevicesByRtype.alarmsystems > 0) {
         | 
| 1025 | 
            +
                      this.context.fullState.alarmsystems = await this.client.get('/alarmsystems')
         | 
| 1026 | 
            +
                    }
         | 
| 1019 1027 | 
             
                    if (this.values.exposeSchedules) {
         | 
| 1020 1028 | 
             
                      this.context.fullState.schedules = await this.client.get('/schedules')
         | 
| 1021 1029 | 
             
                    }
         | 
| @@ -235,6 +235,7 @@ class DeconzAccessory extends AccessoryDelegate { | |
| 235 235 | 
             
                        logLevel: this.values.logLevel,
         | 
| 236 236 | 
             
                        lowBatteryThreshold: this.servicesByServiceName?.Battery?.[0].values.lowBatteryThreshold,
         | 
| 237 237 | 
             
                        // offset: this.servicesByServiceName?.Temperature?.[0].values.offset,
         | 
| 238 | 
            +
                        pin: this.service.values.pin,
         | 
| 238 239 | 
             
                        serviceName: this.values.serviceName,
         | 
| 239 240 | 
             
                        venetianBlind: this.service.values.venetianBlind,
         | 
| 240 241 | 
             
                        useExternalTemperature: this.service.values.useExternalTemperature,
         | 
| @@ -289,6 +290,14 @@ class DeconzAccessory extends AccessoryDelegate { | |
| 289 290 | 
             
                          continue
         | 
| 290 291 | 
             
                        }
         | 
| 291 292 | 
             
                        break
         | 
| 293 | 
            +
                      case 'pin':
         | 
| 294 | 
            +
                        if (this.service.values[key] != null) {
         | 
| 295 | 
            +
                          value = OptionParser.toString(key, body[key])
         | 
| 296 | 
            +
                          this.service.values[key] = value
         | 
| 297 | 
            +
                          responseBody[key] = value
         | 
| 298 | 
            +
                          continue
         | 
| 299 | 
            +
                        }
         | 
| 300 | 
            +
                        break
         | 
| 292 301 | 
             
                      case 'serviceName':
         | 
| 293 302 | 
             
                        if (this.values.serviceName != null) {
         | 
| 294 303 | 
             
                          value = OptionParser.toString(key, body[key])
         | 
| @@ -0,0 +1,87 @@ | |
| 1 | 
            +
            // homebridge-deconz/lib/DeconzService/AlarmSystem.js
         | 
| 2 | 
            +
            // Copyright © 2022-2025 Erik Baauw. All rights reserved.
         | 
| 3 | 
            +
            //
         | 
| 4 | 
            +
            // Homebridge plugin for deCONZ.
         | 
| 5 | 
            +
             | 
| 6 | 
            +
            import { DeconzService } from '../DeconzService/index.js'
         | 
| 7 | 
            +
             | 
| 8 | 
            +
            let mapsInitialised = false
         | 
| 9 | 
            +
            const armModeMap = {}
         | 
| 10 | 
            +
            const armStateMap = {}
         | 
| 11 | 
            +
            const targetStateMap = {}
         | 
| 12 | 
            +
             | 
| 13 | 
            +
            function initMaps (currentState, targetState) {
         | 
| 14 | 
            +
              if (mapsInitialised) {
         | 
| 15 | 
            +
                return
         | 
| 16 | 
            +
              }
         | 
| 17 | 
            +
              armStateMap.disarmed = currentState.DISARMED
         | 
| 18 | 
            +
              armStateMap.armed_away = currentState.AWAY_ARM
         | 
| 19 | 
            +
              armStateMap.armed_stay = currentState.STAY_ARM
         | 
| 20 | 
            +
              armStateMap.armed_night = currentState.NIGHT_ARM
         | 
| 21 | 
            +
              armStateMap.in_alarm = currentState.ALARM_TRIGGERED
         | 
| 22 | 
            +
              armModeMap.disarmed = targetState.DISARM
         | 
| 23 | 
            +
              armModeMap.armed_away = targetState.AWAY_ARM
         | 
| 24 | 
            +
              armModeMap.armed_stay = targetState.STAY_ARM
         | 
| 25 | 
            +
              armModeMap.armed_night = targetState.NIGHT_ARM
         | 
| 26 | 
            +
              targetStateMap[targetState.DISARM] = 'disarm'
         | 
| 27 | 
            +
              targetStateMap[targetState.AWAY_ARM] = 'arm_away'
         | 
| 28 | 
            +
              targetStateMap[targetState.STAY_ARM] = 'arm_stay'
         | 
| 29 | 
            +
              targetStateMap[targetState.NIGHT_ARM] = 'arm_night'
         | 
| 30 | 
            +
              mapsInitialised = true
         | 
| 31 | 
            +
            }
         | 
| 32 | 
            +
             | 
| 33 | 
            +
            /**
         | 
| 34 | 
            +
              * @memberof DeconzService
         | 
| 35 | 
            +
              */
         | 
| 36 | 
            +
            class AlarmSystem extends DeconzService {
         | 
| 37 | 
            +
              constructor (accessory, resource, params = {}) {
         | 
| 38 | 
            +
                params.Service = accessory.Services.hap.SecuritySystem
         | 
| 39 | 
            +
                super(accessory, resource, params)
         | 
| 40 | 
            +
             | 
| 41 | 
            +
                initMaps(
         | 
| 42 | 
            +
                  this.Characteristics.hap.SecuritySystemCurrentState,
         | 
| 43 | 
            +
                  this.Characteristics.hap.SecuritySystemTargetState
         | 
| 44 | 
            +
                )
         | 
| 45 | 
            +
                this.addCharacteristicDelegate({
         | 
| 46 | 
            +
                  key: 'currentState',
         | 
| 47 | 
            +
                  Characteristic: this.Characteristics.hap.SecuritySystemCurrentState
         | 
| 48 | 
            +
                })
         | 
| 49 | 
            +
                this.addCharacteristicDelegate({
         | 
| 50 | 
            +
                  key: 'targetState',
         | 
| 51 | 
            +
                  Characteristic: this.Characteristics.hap.SecuritySystemTargetState
         | 
| 52 | 
            +
                }).on('didSet', async (value, fromHomeKit) => {
         | 
| 53 | 
            +
                  if (fromHomeKit) {
         | 
| 54 | 
            +
                    await this.put(`/${targetStateMap[value]}`, {
         | 
| 55 | 
            +
                      code0: this.values.pin
         | 
| 56 | 
            +
                    })
         | 
| 57 | 
            +
                  }
         | 
| 58 | 
            +
                })
         | 
| 59 | 
            +
                this.addCharacteristicDelegate({
         | 
| 60 | 
            +
                  key: 'alarmType',
         | 
| 61 | 
            +
                  Characteristic: this.Characteristics.hap.SecuritySystemAlarmType
         | 
| 62 | 
            +
                })
         | 
| 63 | 
            +
                this.addCharacteristicDelegate({
         | 
| 64 | 
            +
                  key: 'pin',
         | 
| 65 | 
            +
                  value: '0000'
         | 
| 66 | 
            +
                })
         | 
| 67 | 
            +
             | 
| 68 | 
            +
                this.update(resource.body, resource.rpath)
         | 
| 69 | 
            +
              }
         | 
| 70 | 
            +
             | 
| 71 | 
            +
              updateState (state) {
         | 
| 72 | 
            +
                if (armStateMap[state.armstate] != null) {
         | 
| 73 | 
            +
                  this.values.currentState = armStateMap[state.armstate]
         | 
| 74 | 
            +
                }
         | 
| 75 | 
            +
                this.values.alarmType = state.armstate === 'in_alarm'
         | 
| 76 | 
            +
                  ? 1 // this.Characteristics.hap.SecuritySystemAlarmType.UNKNOWN
         | 
| 77 | 
            +
                  : 0 // this.Characteristics.hap.SecuritySystemAlarmType.NO_ALARM
         | 
| 78 | 
            +
              }
         | 
| 79 | 
            +
             | 
| 80 | 
            +
              updateConfig (config) {
         | 
| 81 | 
            +
                if (armModeMap[config.armmode] != null) {
         | 
| 82 | 
            +
                  this.values.targetState = armModeMap[config.armmode]
         | 
| 83 | 
            +
                }
         | 
| 84 | 
            +
              }
         | 
| 85 | 
            +
            }
         | 
| 86 | 
            +
             | 
| 87 | 
            +
            DeconzService.AlarmSystem = AlarmSystem
         | 
| @@ -20,7 +20,7 @@ class Flag extends DeconzService.SensorsResource { | |
| 20 20 | 
             
                    Characteristic: this.Characteristics.hap.On,
         | 
| 21 21 | 
             
                    props: {
         | 
| 22 22 | 
             
                      perms: [
         | 
| 23 | 
            -
                        this.Characteristic.Perms. | 
| 23 | 
            +
                        this.Characteristic.Perms.PAIRED_READ, this.Characteristic.Perms.NOTIFY
         | 
| 24 24 | 
             
                      ]
         | 
| 25 25 | 
             
                    }
         | 
| 26 26 | 
             
                  })
         | 
| @@ -42,6 +42,12 @@ class Label extends DeconzService.SensorsResource { | |
| 42 42 | 
             
                    right: keys[0],
         | 
| 43 43 | 
             
                    left: keys[1]
         | 
| 44 44 | 
             
                  }
         | 
| 45 | 
            +
                } else if (resource.body.type.endsWith('AncillaryControl')) {
         | 
| 46 | 
            +
                  this.buttonResources[resource.rpath] = {
         | 
| 47 | 
            +
                    buttonEvent: resource.body.state.action,
         | 
| 48 | 
            +
                    lastUpdated: new Date(),
         | 
| 49 | 
            +
                    toButtonEvent: resource.capabilities.toButtonEvent
         | 
| 50 | 
            +
                  }
         | 
| 45 51 | 
             
                }
         | 
| 46 52 | 
             
                for (const i in resource.capabilities.buttons) {
         | 
| 47 53 | 
             
                  const { label, events, hasRepeat } = resource.capabilities.buttons[i]
         | 
| @@ -73,6 +79,8 @@ class Label extends DeconzService.SensorsResource { | |
| 73 79 | 
             
                      buttonResource.buttonEvent = buttonResource.toButtonEvent == null
         | 
| 74 80 | 
             
                        ? state.buttonevent
         | 
| 75 81 | 
             
                        : buttonResource.toButtonEvent(state.buttonevent)
         | 
| 82 | 
            +
                    } else if (state.action != null) {
         | 
| 83 | 
            +
                      buttonResource.buttonEvent = buttonResource.toButtonEvent(state.action)
         | 
| 76 84 | 
             
                    }
         | 
| 77 85 | 
             
                    // TODO handle repeat
         | 
| 78 86 | 
             
                    const i = Math.floor(buttonResource.buttonEvent / 1000)
         | 
| @@ -44,7 +44,7 @@ class Status extends DeconzService.SensorsResource { | |
| 44 44 | 
             
                    Characteristic: this.Characteristics.my.Status,
         | 
| 45 45 | 
             
                    props: {
         | 
| 46 46 | 
             
                      perms: [
         | 
| 47 | 
            -
                        this.Characteristic.Perms. | 
| 47 | 
            +
                        this.Characteristic.Perms.PAIRED_READ, this.Characteristic.Perms.NOTIFY
         | 
| 48 48 | 
             
                      ]
         | 
| 49 49 | 
             
                    }
         | 
| 50 50 | 
             
                  })
         | 
    
        package/package.json
    CHANGED
    
    | @@ -7,7 +7,7 @@ | |
| 7 7 | 
             
                "ebaauw"
         | 
| 8 8 | 
             
              ],
         | 
| 9 9 | 
             
              "license": "Apache-2.0",
         | 
| 10 | 
            -
              "version": "1.0 | 
| 10 | 
            +
              "version": "1.1.0",
         | 
| 11 11 | 
             
              "keywords": [
         | 
| 12 12 | 
             
                "homebridge-plugin",
         | 
| 13 13 | 
             
                "homekit",
         | 
| @@ -26,12 +26,12 @@ | |
| 26 26 | 
             
                "ui": "cli/ui.js"
         | 
| 27 27 | 
             
              },
         | 
| 28 28 | 
             
              "engines": {
         | 
| 29 | 
            -
                "deCONZ": "2. | 
| 29 | 
            +
                "deCONZ": "2.29.5",
         | 
| 30 30 | 
             
                "homebridge": "^1.9.0||^2.0.0-beta",
         | 
| 31 31 | 
             
                "node": "^22||^20||^18"
         | 
| 32 32 | 
             
              },
         | 
| 33 33 | 
             
              "dependencies": {
         | 
| 34 | 
            -
                "hb-deconz-tools": "~2.0. | 
| 34 | 
            +
                "hb-deconz-tools": "~2.0.9",
         | 
| 35 35 | 
             
                "homebridge-lib": "~7.1.4"
         | 
| 36 36 | 
             
              },
         | 
| 37 37 | 
             
              "scripts": {
         |