Add support for 'door_lock' service

This commit is contained in:
Adrian Jagielak 2025-07-26 23:28:24 +02:00
parent 3939b37d88
commit 77096d6732
No known key found for this signature in database
GPG Key ID: 0818CF7AF6C62BFB
7 changed files with 739 additions and 11 deletions

View File

@ -35,8 +35,6 @@ Some services are more common than others; some are deprecated entirely.
<!-- <!--
todo: pairing todo: pairing
todo add info about factory reset hub to restore 30 day trial todo add info about factory reset hub to restore 30 day trial
todo: links to the .ts service implementations below
todo: service names and not just raw service identifiers?
--> -->
| Name | Service | Example device | Implementation status | Home Assistant entity | | Name | Service | Example device | Implementation status | Home Assistant entity |
@ -48,7 +46,7 @@ todo: service names and not just raw service identifiers?
| Chargepoint | [chargepoint](https://github.com/adrianjagielak/home-assistant-futurehome/blob/master/futurehome/src/services/chargepoint.ts) | [Futurehome Charge](https://www.futurehome.io/en_no/shop/charge) | ✅ | [Sensor](https://www.home-assistant.io/integrations/sensor/), [Switch](https://www.home-assistant.io/integrations/switch/), [Number](https://www.home-assistant.io/integrations/number/), [Select](https://www.home-assistant.io/integrations/select/) | | Chargepoint | [chargepoint](https://github.com/adrianjagielak/home-assistant-futurehome/blob/master/futurehome/src/services/chargepoint.ts) | [Futurehome Charge](https://www.futurehome.io/en_no/shop/charge) | ✅ | [Sensor](https://www.home-assistant.io/integrations/sensor/), [Switch](https://www.home-assistant.io/integrations/switch/), [Number](https://www.home-assistant.io/integrations/number/), [Select](https://www.home-assistant.io/integrations/select/) |
| Color control | [color_ctrl](https://github.com/adrianjagielak/home-assistant-futurehome/blob/master/futurehome/src/services/color_ctrl.ts) | | ✅ | [Light](https://www.home-assistant.io/integrations/light/) | | Color control | [color_ctrl](https://github.com/adrianjagielak/home-assistant-futurehome/blob/master/futurehome/src/services/color_ctrl.ts) | | ✅ | [Light](https://www.home-assistant.io/integrations/light/) |
| Complex Alarm System | [complex_alarm_system](https://github.com/adrianjagielak/home-assistant-futurehome/blob/master/futurehome/src/services/complex_alarm_system.ts) | | | | Complex Alarm System | [complex_alarm_system](https://github.com/adrianjagielak/home-assistant-futurehome/blob/master/futurehome/src/services/complex_alarm_system.ts) | | |
| Door lock| [door_lock](https://github.com/adrianjagielak/home-assistant-futurehome/blob/master/futurehome/src/services/door_lock.ts) | | | | Door lock | [door_lock](https://github.com/adrianjagielak/home-assistant-futurehome/blob/master/futurehome/src/services/door_lock.ts) | | ✅ | [Lock](https://www.home-assistant.io/integrations/lock/), [Binary sensor](https://www.home-assistant.io/integrations/binary_sensor/), [Switch](https://www.home-assistant.io/integrations/switch/), [Number](https://www.home-assistant.io/integrations/number/), [Button](https://www.home-assistant.io/integrations/button/), [Select](https://www.home-assistant.io/integrations/select/) |
| ??? | [doorman](https://github.com/adrianjagielak/home-assistant-futurehome/blob/master/futurehome/src/services/doorman.ts) | | | | ??? | [doorman](https://github.com/adrianjagielak/home-assistant-futurehome/blob/master/futurehome/src/services/doorman.ts) | | |
| Fan | [fan_ctrl](https://github.com/adrianjagielak/home-assistant-futurehome/blob/master/futurehome/src/services/fan_ctrl.ts) | | ✅ | [Fan](https://www.home-assistant.io/integrations/fan/) | | Fan | [fan_ctrl](https://github.com/adrianjagielak/home-assistant-futurehome/blob/master/futurehome/src/services/fan_ctrl.ts) | | ✅ | [Fan](https://www.home-assistant.io/integrations/fan/) |
| Light | [light](https://github.com/adrianjagielak/home-assistant-futurehome/blob/master/futurehome/src/services/light.ts) | | | | Light | [light](https://github.com/adrianjagielak/home-assistant-futurehome/blob/master/futurehome/src/services/light.ts) | | |

View File

@ -6,6 +6,7 @@
- Refactored sensors. - Refactored sensors.
- Added support for 'meter_*' services (electricity meters, gas meters, water meters, heating meters, cooling meters). - Added support for 'meter_*' services (electricity meters, gas meters, water meters, heating meters, cooling meters).
- Added support for 'sound_switch' service (sound emitters). - Added support for 'sound_switch' service (sound emitters).
- Added support for 'door_lock' service (door locks).
## 0.1.5 (25.07.2025) ## 0.1.5 (25.07.2025)

View File

@ -34,8 +34,6 @@ Some services are more common than others; some are deprecated entirely.
<!-- <!--
todo: pairing todo: pairing
todo add info about factory reset hub to restore 30 day trial todo add info about factory reset hub to restore 30 day trial
todo: links to the .ts service implementations below
todo: service names and not just raw service identifiers?
--> -->
| Name | Service | Example device | Implementation status | Home Assistant entity | | Name | Service | Example device | Implementation status | Home Assistant entity |
@ -47,7 +45,7 @@ todo: service names and not just raw service identifiers?
| Chargepoint | [chargepoint](https://github.com/adrianjagielak/home-assistant-futurehome/blob/master/futurehome/src/services/chargepoint.ts) | [Futurehome Charge](https://www.futurehome.io/en_no/shop/charge) | ✅ | [Sensor](https://www.home-assistant.io/integrations/sensor/), [Switch](https://www.home-assistant.io/integrations/switch/), [Number](https://www.home-assistant.io/integrations/number/), [Select](https://www.home-assistant.io/integrations/select/) | | Chargepoint | [chargepoint](https://github.com/adrianjagielak/home-assistant-futurehome/blob/master/futurehome/src/services/chargepoint.ts) | [Futurehome Charge](https://www.futurehome.io/en_no/shop/charge) | ✅ | [Sensor](https://www.home-assistant.io/integrations/sensor/), [Switch](https://www.home-assistant.io/integrations/switch/), [Number](https://www.home-assistant.io/integrations/number/), [Select](https://www.home-assistant.io/integrations/select/) |
| Color control | [color_ctrl](https://github.com/adrianjagielak/home-assistant-futurehome/blob/master/futurehome/src/services/color_ctrl.ts) | | ✅ | [Light](https://www.home-assistant.io/integrations/light/) | | Color control | [color_ctrl](https://github.com/adrianjagielak/home-assistant-futurehome/blob/master/futurehome/src/services/color_ctrl.ts) | | ✅ | [Light](https://www.home-assistant.io/integrations/light/) |
| Complex Alarm System | [complex_alarm_system](https://github.com/adrianjagielak/home-assistant-futurehome/blob/master/futurehome/src/services/complex_alarm_system.ts) | | | | Complex Alarm System | [complex_alarm_system](https://github.com/adrianjagielak/home-assistant-futurehome/blob/master/futurehome/src/services/complex_alarm_system.ts) | | |
| Door lock| [door_lock](https://github.com/adrianjagielak/home-assistant-futurehome/blob/master/futurehome/src/services/door_lock.ts) | | | | Door lock | [door_lock](https://github.com/adrianjagielak/home-assistant-futurehome/blob/master/futurehome/src/services/door_lock.ts) | | ✅ | [Lock](https://www.home-assistant.io/integrations/lock/), [Binary sensor](https://www.home-assistant.io/integrations/binary_sensor/), [Switch](https://www.home-assistant.io/integrations/switch/), [Number](https://www.home-assistant.io/integrations/number/), [Button](https://www.home-assistant.io/integrations/button/), [Select](https://www.home-assistant.io/integrations/select/) |
| ??? | [doorman](https://github.com/adrianjagielak/home-assistant-futurehome/blob/master/futurehome/src/services/doorman.ts) | | | | ??? | [doorman](https://github.com/adrianjagielak/home-assistant-futurehome/blob/master/futurehome/src/services/doorman.ts) | | |
| Fan | [fan_ctrl](https://github.com/adrianjagielak/home-assistant-futurehome/blob/master/futurehome/src/services/fan_ctrl.ts) | | ✅ | [Fan](https://www.home-assistant.io/integrations/fan/) | | Fan | [fan_ctrl](https://github.com/adrianjagielak/home-assistant-futurehome/blob/master/futurehome/src/services/fan_ctrl.ts) | | ✅ | [Fan](https://www.home-assistant.io/integrations/fan/) |
| Light | [light](https://github.com/adrianjagielak/home-assistant-futurehome/blob/master/futurehome/src/services/light.ts) | | | | Light | [light](https://github.com/adrianjagielak/home-assistant-futurehome/blob/master/futurehome/src/services/light.ts) | | |

View File

@ -13,6 +13,7 @@ import { basic__components } from '../services/basic';
import { battery__components } from '../services/battery'; import { battery__components } from '../services/battery';
import { chargepoint__components } from '../services/chargepoint'; import { chargepoint__components } from '../services/chargepoint';
import { color_ctrl__components } from '../services/color_ctrl'; import { color_ctrl__components } from '../services/color_ctrl';
import { door_lock__components } from '../services/door_lock';
import { fan_ctrl__components } from '../services/fan_ctrl'; import { fan_ctrl__components } from '../services/fan_ctrl';
import { indicator_ctrl__components } from '../services/indicator_ctrl'; import { indicator_ctrl__components } from '../services/indicator_ctrl';
import { media_player__components } from '../services/media_player'; import { media_player__components } from '../services/media_player';
@ -149,6 +150,7 @@ const serviceHandlers: {
battery: battery__components, battery: battery__components,
chargepoint: chargepoint__components, chargepoint: chargepoint__components,
color_ctrl: color_ctrl__components, color_ctrl: color_ctrl__components,
door_lock: door_lock__components,
fan_ctrl: fan_ctrl__components, fan_ctrl: fan_ctrl__components,
indicator_ctrl: indicator_ctrl__components, indicator_ctrl: indicator_ctrl__components,
media_player: media_player__components, media_player: media_player__components,

View File

@ -1947,7 +1947,7 @@
"client": { "client": {
"name": "Smart Speaker" "name": "Smart Speaker"
}, },
"id": 1001, "id": 1003,
"model": "zigbee - Futurehome - Smart Speaker", "model": "zigbee - Futurehome - Smart Speaker",
"modelAlias": "Smart Speaker", "modelAlias": "Smart Speaker",
"type": { "type": {
@ -1968,12 +1968,12 @@
"services": { "services": {
"media_player": { "media_player": {
"name": "media_player", "name": "media_player",
"addr": "/rt:dev/rn:zigbee/ad:1/sv:media_player/ad:1001_0", "addr": "/rt:dev/rn:zigbee/ad:1/sv:media_player/ad:1003_0",
"enabled": true, "enabled": true,
"props": { "props": {
"sup_playback": ["play", "pause", "next_track", "previous_track"], "sup_playback": ["play", "pause", "next_track", "previous_track"],
"sup_modes": ["repeat", "repeat_one", "shuffle", "crossfade"], "sup_modes": ["repeat", "repeat_one", "shuffle", "crossfade"],
"sup_metadata": ["album", "track", "artist", "image_url"] "sup_metadata": ["album", "track", "artist"]
}, },
"intf": [ "intf": [
"cmd.playback.set", "cmd.playback.set",
@ -2412,5 +2412,135 @@
} }
}, },
"metadata": null "metadata": null
},
{
"client": {
"name": "Door lock"
},
"fimp": {
"adapter": "zwave-ad",
"address": "92",
"group": "ch_0"
},
"functionality": "security",
"id": 74,
"lrn": true,
"model": "zw_560_3_1",
"modelAlias": "Door lock",
"param": {
"alarms": {
"lock": ["manual_lock", "manual_unlock"]
},
"autoLock": "on",
"batteryLevel": "ok",
"batteryPercentage": 100,
"lockState": "unlocked",
"openState": "open",
"presence": false,
"supportedAlarms": {
"lock": ["rf_not_locked"]
},
"timestamp": "2020-02-11 15:38:12 +0100",
"zwaveConfigParameters": [
{
"parameter": 1,
"size": 1,
"value": 1
}
]
},
"problem": false,
"room": null,
"services": {
"alarm_lock": {
"addr": "/rt:dev/rn:zw/ad:1/sv:alarm_lock/ad:92_0",
"enabled": true,
"intf": ["cmd.alarm.get_report", "evt.alarm.report"],
"props": {
"is_secure": true,
"is_unsecure": false,
"sup_events": ["rf_not_locked"]
}
},
"basic": {
"addr": "/rt:dev/rn:zw/ad:1/sv:basic/ad:92_0",
"enabled": true,
"intf": ["cmd.lvl.get_report", "cmd.lvl.set", "evt.lvl.report"],
"props": {
"is_secure": true,
"is_unsecure": true
}
},
"battery": {
"addr": "/rt:dev/rn:zw/ad:1/sv:battery/ad:92_0",
"enabled": true,
"intf": ["cmd.lvl.get_report", "evt.alarm.report", "evt.lvl.report"],
"props": {
"is_secure": false,
"is_unsecure": true
}
},
"dev_sys": {
"addr": "/rt:dev/rn:zw/ad:1/sv:dev_sys/ad:92_0",
"enabled": true,
"intf": [
"cmd.config.get_report",
"cmd.config.set",
"cmd.group.add_members",
"cmd.group.delete_members",
"cmd.group.get_members",
"cmd.ping.send",
"evt.config.report",
"evt.group.members_report",
"evt.ping.report"
],
"props": {
"is_secure": false,
"is_unsecure": true
}
},
"door_lock": {
"addr": "/rt:dev/rn:zw/ad:1/sv:door_lock/ad:92_0",
"enabled": true,
"intf": ["cmd.lock.get_report", "cmd.lock.set", "evt.lock.report"],
"props": {
"is_secure": true,
"is_unsecure": false
}
},
"sensor_presence": {
"addr": "/rt:dev/rn:zw/ad:1/sv:sensor_presence/ad:92_0",
"enabled": true,
"intf": ["cmd.presence.get_report", "evt.presence.report"],
"props": {
"is_secure": true,
"is_unsecure": false
}
},
"user_code": {
"addr": "/rt:dev/rn:zw/ad:1/sv:user_code/ad:92_0",
"enabled": true,
"intf": [
"cmd.usercode.clear",
"cmd.usercode.clear_all",
"cmd.usercode.get",
"cmd.usercode.set"
],
"props": {
"is_secure": true,
"is_unsecure": false,
"sup_users": 52
}
}
},
"supports": ["clear", "poll"],
"thing": 57,
"type": {
"subtype": null,
"supported": {
"door_lock": []
},
"type": null
}
} }
] ]

View File

@ -1245,10 +1245,10 @@
] ]
}, },
{ {
"id": 1001, "id": 1003,
"services": [ "services": [
{ {
"addr": "/rt:dev/rn:zigbee/ad:1/sv:media_player/ad:1001_0", "addr": "/rt:dev/rn:zigbee/ad:1/sv:media_player/ad:1003_0",
"attributes": [ "attributes": [
{ {
"name": "playback", "name": "playback",
@ -1772,5 +1772,65 @@
"name": "sound_switch" "name": "sound_switch"
} }
] ]
},
{
"id": 74,
"services": [
{
"addr": "/rt:dev/rn:zw/ad:1/sv:door_lock/ad:92_0",
"attributes": [
{
"name": "lock",
"values": [
{
"props": {
"timeout_s": "254",
"unsecured_desc": ""
},
"ts": "2020-02-11 15:38:12 +0100",
"val": {
"bolt_is_locked": false,
"door_is_closed": false,
"is_secured": false,
"latch_is_closed": false
},
"val_t": "bool_map"
}
]
}
],
"name": "door_lock"
},
{
"addr": "/rt:dev/rn:zw/ad:1/sv:dev_sys/ad:92_0",
"attributes": [
{
"name": "state",
"values": [
{
"ts": "2020-01-29 17:35:54 +0100",
"val": "DOWN",
"val_t": "string"
}
]
},
{
"name": "error",
"values": [
{
"props": {
"msg": "TRANSMIT_COMPLETE_NO_ACK",
"src": "nodeId=92_0;service=door_lock"
},
"ts": "2020-01-23 14:58:07 +0100",
"val": "TX_ERROR",
"val_t": "string"
}
]
}
],
"name": "dev_sys"
}
]
} }
] ]

View File

@ -0,0 +1,539 @@
import { sendFimpMsg } from '../fimp/fimp';
import {
VinculumPd7Device,
VinculumPd7Service,
} from '../fimp/vinculum_pd7_device';
import { HaMqttComponent } from '../ha/mqtt_components/_component';
import { BinarySensorDeviceClass } from '../ha/mqtt_components/_enums';
import {
CommandHandlers,
ServiceComponentsCreationResult,
} from '../ha/publish_device';
export function door_lock__components(
topicPrefix: string,
device: VinculumPd7Device,
svc: VinculumPd7Service,
_svcName: string,
): ServiceComponentsCreationResult | undefined {
const components: Record<string, HaMqttComponent> = {};
const commandHandlers: CommandHandlers = {};
const stateTopic = `${topicPrefix}/state`;
// Main lock component
if (
svc.intf?.includes('cmd.lock.set') &&
svc.intf?.includes('evt.lock.report')
) {
const lockCommandTopic = `${topicPrefix}${svc.addr}/lock/command`;
components[`${svc.addr}_lock`] = {
unique_id: `${svc.addr}_lock`,
platform: 'lock',
name: 'Lock',
command_topic: lockCommandTopic,
state_topic: stateTopic,
optimistic: false,
// Map the lock state based on is_secured component
value_template: `{{ 'LOCKED' if value_json['${svc.addr}'].lock.is_secured else 'UNLOCKED' }}`,
payload_lock: 'LOCK',
payload_unlock: 'UNLOCK',
};
commandHandlers[lockCommandTopic] = async (payload: string) => {
const isLock = payload === 'LOCK';
await sendFimpMsg({
address: svc.addr,
service: 'door_lock',
cmd: 'cmd.lock.set',
val_t: 'bool',
val: isLock,
});
};
}
// Individual lock components as binary sensors
const supComponents = svc.props?.sup_components || [];
for (const component of supComponents) {
let deviceClass: BinarySensorDeviceClass;
let name: string;
switch (component) {
case 'is_secured':
deviceClass = 'lock';
name = 'Secured';
break;
case 'door_is_closed':
deviceClass = 'door';
name = 'Door Closed';
break;
case 'bolt_is_locked':
deviceClass = 'lock';
name = 'Bolt Locked';
break;
case 'latch_is_closed':
deviceClass = 'lock';
name = 'Latch Closed';
break;
default:
deviceClass = 'lock';
name = component
.replace(/_/g, ' ')
.replace(/\b\w/g, (l: string) => l.toUpperCase());
}
components[`${svc.addr}_${component}`] = {
unique_id: `${svc.addr}_${component}`,
platform: 'binary_sensor',
name: name,
device_class: deviceClass,
entity_category: 'diagnostic',
state_topic: stateTopic,
value_template: `{{ 'ON' if value_json['${svc.addr}'].lock.${component} else 'OFF' }}`,
};
}
// Auto-lock switch
if (
svc.intf?.includes('cmd.auto_lock.set') &&
svc.intf?.includes('evt.auto_lock.report')
) {
const autoLockCommandTopic = `${topicPrefix}${svc.addr}/auto_lock/command`;
components[`${svc.addr}_auto_lock`] = {
unique_id: `${svc.addr}_auto_lock`,
platform: 'switch',
name: 'Auto Lock',
entity_category: 'config',
command_topic: autoLockCommandTopic,
state_topic: stateTopic,
optimistic: false,
value_template: `{{ 'ON' if value_json['${svc.addr}'].auto_lock else 'OFF' }}`,
payload_on: 'ON',
payload_off: 'OFF',
};
commandHandlers[autoLockCommandTopic] = async (payload: string) => {
const enabled = payload === 'ON';
await sendFimpMsg({
address: svc.addr,
service: 'door_lock',
cmd: 'cmd.auto_lock.set',
val_t: 'bool',
val: enabled,
});
};
}
// Volume control
if (
svc.intf?.includes('cmd.volume.set') &&
svc.intf?.includes('evt.volume.report')
) {
const volumeCommandTopic = `${topicPrefix}${svc.addr}/volume/command`;
const minVolume = svc.props?.min_volume ?? 0;
const maxVolume = svc.props?.max_volume ?? 10;
components[`${svc.addr}_volume`] = {
unique_id: `${svc.addr}_volume`,
platform: 'number',
name: 'Volume',
entity_category: 'config',
command_topic: volumeCommandTopic,
state_topic: stateTopic,
optimistic: false,
min: minVolume,
max: maxVolume,
step: 1,
value_template: `{{ value_json['${svc.addr}'].volume | default(${minVolume}) }}`,
};
commandHandlers[volumeCommandTopic] = async (payload: string) => {
const volume = parseInt(payload, 10);
if (Number.isNaN(volume) || volume < minVolume || volume > maxVolume) {
return;
}
await sendFimpMsg({
address: svc.addr,
service: 'door_lock',
cmd: 'cmd.volume.set',
val_t: 'int',
val: volume,
});
};
}
// Lock configuration button
if (svc.intf?.includes('cmd.lock.get_configuration')) {
const getConfigCommandTopic = `${topicPrefix}${svc.addr}/get_configuration/command`;
components[`${svc.addr}_get_config`] = {
unique_id: `${svc.addr}_get_config`,
platform: 'button',
name: 'Get Configuration',
entity_category: 'diagnostic',
command_topic: getConfigCommandTopic,
};
commandHandlers[getConfigCommandTopic] = async (_payload: string) => {
await sendFimpMsg({
address: svc.addr,
service: 'door_lock',
cmd: 'cmd.lock.get_configuration',
val_t: 'null',
val: null,
});
};
}
// Get lock report button
if (svc.intf?.includes('cmd.lock.get_report')) {
const getLockReportCommandTopic = `${topicPrefix}${svc.addr}/get_lock_report/command`;
components[`${svc.addr}_get_lock_report`] = {
unique_id: `${svc.addr}_get_lock_report`,
platform: 'button',
name: 'Get Lock Report',
entity_category: 'diagnostic',
command_topic: getLockReportCommandTopic,
};
commandHandlers[getLockReportCommandTopic] = async (_payload: string) => {
await sendFimpMsg({
address: svc.addr,
service: 'door_lock',
cmd: 'cmd.lock.get_report',
val_t: 'null',
val: null,
});
};
}
// Get auto-lock report button
if (svc.intf?.includes('cmd.auto_lock.get_report')) {
const getAutoLockReportCommandTopic = `${topicPrefix}${svc.addr}/get_auto_lock_report/command`;
components[`${svc.addr}_get_auto_lock_report`] = {
unique_id: `${svc.addr}_get_auto_lock_report`,
platform: 'button',
name: 'Get Auto Lock Report',
entity_category: 'diagnostic',
command_topic: getAutoLockReportCommandTopic,
};
commandHandlers[getAutoLockReportCommandTopic] = async (
_payload: string,
) => {
await sendFimpMsg({
address: svc.addr,
service: 'door_lock',
cmd: 'cmd.auto_lock.get_report',
val_t: 'null',
val: null,
});
};
}
// Get volume report button
if (svc.intf?.includes('cmd.volume.get_report')) {
const getVolumeReportCommandTopic = `${topicPrefix}${svc.addr}/get_volume_report/command`;
components[`${svc.addr}_get_volume_report`] = {
unique_id: `${svc.addr}_get_volume_report`,
platform: 'button',
name: 'Get Volume Report',
entity_category: 'diagnostic',
command_topic: getVolumeReportCommandTopic,
};
commandHandlers[getVolumeReportCommandTopic] = async (_payload: string) => {
await sendFimpMsg({
address: svc.addr,
service: 'door_lock',
cmd: 'cmd.volume.get_report',
val_t: 'null',
val: null,
});
};
}
// Operation type configuration
const supOpTypes = svc.props?.sup_op_types || [];
if (
supOpTypes.length > 0 &&
svc.intf?.includes('cmd.lock.set_configuration')
) {
const opTypeCommandTopic = `${topicPrefix}${svc.addr}/operation_type/command`;
components[`${svc.addr}_operation_type`] = {
unique_id: `${svc.addr}_operation_type`,
platform: 'select',
name: 'Operation Type',
entity_category: 'config',
command_topic: opTypeCommandTopic,
state_topic: stateTopic,
optimistic: false,
options: supOpTypes,
value_template: `{{ value_json['${svc.addr}'].configuration.operation_type | default('${supOpTypes[0]}') }}`,
};
commandHandlers[opTypeCommandTopic] = async (payload: string) => {
if (!supOpTypes.includes(payload)) {
return;
}
// Send a partial configuration update
await sendFimpMsg({
address: svc.addr,
service: 'door_lock',
cmd: 'cmd.lock.set_configuration',
val_t: 'object',
val: {
operation_type: payload,
},
});
};
}
// Auto-relock time configuration (if supported)
if (svc.props?.supports_auto_relock) {
const minAutoRelock = svc.props?.min_auto_relock_time ?? 0;
const maxAutoRelock = svc.props?.max_auto_relock_time ?? 65535;
const autoRelockTimeCommandTopic = `${topicPrefix}${svc.addr}/auto_relock_time/command`;
components[`${svc.addr}_auto_relock_time`] = {
unique_id: `${svc.addr}_auto_relock_time`,
platform: 'number',
name: 'Auto Relock Time',
entity_category: 'config',
command_topic: autoRelockTimeCommandTopic,
state_topic: stateTopic,
optimistic: false,
min: minAutoRelock,
max: maxAutoRelock,
step: 1,
unit_of_measurement: 's',
value_template: `{{ value_json['${svc.addr}'].configuration.auto_relock_time | default(0) }}`,
};
commandHandlers[autoRelockTimeCommandTopic] = async (payload: string) => {
const time = parseInt(payload, 10);
if (Number.isNaN(time) || time < minAutoRelock || time > maxAutoRelock) {
return;
}
await sendFimpMsg({
address: svc.addr,
service: 'door_lock',
cmd: 'cmd.lock.set_configuration',
val_t: 'object',
val: {
auto_relock_time: time,
},
});
};
}
// Hold and release time configuration (if supported)
if (svc.props?.supports_hold_and_release) {
const minHoldRelease = svc.props?.min_hold_and_release_time ?? 1;
const maxHoldRelease = svc.props?.max_hold_and_release_time ?? 65535;
const holdReleaseTimeCommandTopic = `${topicPrefix}${svc.addr}/hold_release_time/command`;
components[`${svc.addr}_hold_release_time`] = {
unique_id: `${svc.addr}_hold_release_time`,
platform: 'number',
name: 'Hold & Release Time',
entity_category: 'config',
command_topic: holdReleaseTimeCommandTopic,
state_topic: stateTopic,
optimistic: false,
min: minHoldRelease,
max: maxHoldRelease,
step: 1,
unit_of_measurement: 's',
value_template: `{{ value_json['${svc.addr}'].configuration.hold_and_release_time | default(0) }}`,
};
commandHandlers[holdReleaseTimeCommandTopic] = async (payload: string) => {
const time = parseInt(payload, 10);
if (
Number.isNaN(time) ||
time < minHoldRelease ||
time > maxHoldRelease
) {
return;
}
await sendFimpMsg({
address: svc.addr,
service: 'door_lock',
cmd: 'cmd.lock.set_configuration',
val_t: 'object',
val: {
hold_and_release_time: time,
},
});
};
}
// Block to block configuration (if supported)
if (svc.props?.supports_block_to_block) {
const blockToBlockCommandTopic = `${topicPrefix}${svc.addr}/block_to_block/command`;
components[`${svc.addr}_block_to_block`] = {
unique_id: `${svc.addr}_block_to_block`,
platform: 'switch',
name: 'Block to Block',
entity_category: 'config',
command_topic: blockToBlockCommandTopic,
state_topic: stateTopic,
optimistic: false,
value_template: `{{ 'ON' if value_json['${svc.addr}'].configuration.block_to_block else 'OFF' }}`,
payload_on: 'ON',
payload_off: 'OFF',
};
commandHandlers[blockToBlockCommandTopic] = async (payload: string) => {
const enabled = payload === 'ON';
await sendFimpMsg({
address: svc.addr,
service: 'door_lock',
cmd: 'cmd.lock.set_configuration',
val_t: 'object',
val: {
block_to_block: enabled,
},
});
};
}
// Twist assist configuration (if supported)
if (svc.props?.supports_twist_assist) {
const twistAssistCommandTopic = `${topicPrefix}${svc.addr}/twist_assist/command`;
components[`${svc.addr}_twist_assist`] = {
unique_id: `${svc.addr}_twist_assist`,
platform: 'switch',
name: 'Twist Assist',
entity_category: 'config',
command_topic: twistAssistCommandTopic,
state_topic: stateTopic,
optimistic: false,
value_template: `{{ 'ON' if value_json['${svc.addr}'].configuration.twist_assist else 'OFF' }}`,
payload_on: 'ON',
payload_off: 'OFF',
};
commandHandlers[twistAssistCommandTopic] = async (payload: string) => {
const enabled = payload === 'ON';
await sendFimpMsg({
address: svc.addr,
service: 'door_lock',
cmd: 'cmd.lock.set_configuration',
val_t: 'object',
val: {
twist_assist: enabled,
},
});
};
}
// Lock timeout configuration (if timed operation is supported)
if (supOpTypes.includes('timed')) {
const minTimeoutSeconds = svc.props?.min_lock_timeout_seconds ?? 0;
const maxTimeoutSeconds = svc.props?.max_lock_timeout_seconds ?? 59;
const minTimeoutMinutes = svc.props?.min_lock_timeout_minutes ?? 0;
const maxTimeoutMinutes = svc.props?.max_lock_timeout_minutes ?? 253;
// Lock timeout seconds
const timeoutSecondsCommandTopic = `${topicPrefix}${svc.addr}/lock_timeout_seconds/command`;
components[`${svc.addr}_lock_timeout_seconds`] = {
unique_id: `${svc.addr}_lock_timeout_seconds`,
platform: 'number',
name: 'Lock Timeout Seconds',
entity_category: 'config',
command_topic: timeoutSecondsCommandTopic,
state_topic: stateTopic,
optimistic: false,
min: minTimeoutSeconds,
max: maxTimeoutSeconds,
step: 1,
unit_of_measurement: 's',
value_template: `{{ value_json['${svc.addr}'].configuration.lock_timeout_seconds | default(0) }}`,
};
commandHandlers[timeoutSecondsCommandTopic] = async (payload: string) => {
const seconds = parseInt(payload, 10);
if (
Number.isNaN(seconds) ||
seconds < minTimeoutSeconds ||
seconds > maxTimeoutSeconds
) {
return;
}
await sendFimpMsg({
address: svc.addr,
service: 'door_lock',
cmd: 'cmd.lock.set_configuration',
val_t: 'object',
val: {
lock_timeout_seconds: seconds,
},
});
};
// Lock timeout minutes
const timeoutMinutesCommandTopic = `${topicPrefix}${svc.addr}/lock_timeout_minutes/command`;
components[`${svc.addr}_lock_timeout_minutes`] = {
unique_id: `${svc.addr}_lock_timeout_minutes`,
platform: 'number',
name: 'Lock Timeout Minutes',
entity_category: 'config',
command_topic: timeoutMinutesCommandTopic,
state_topic: stateTopic,
optimistic: false,
min: minTimeoutMinutes,
max: maxTimeoutMinutes,
step: 1,
unit_of_measurement: 'min',
value_template: `{{ value_json['${svc.addr}'].configuration.lock_timeout_minutes | default(0) }}`,
};
commandHandlers[timeoutMinutesCommandTopic] = async (payload: string) => {
const minutes = parseInt(payload, 10);
if (
Number.isNaN(minutes) ||
minutes < minTimeoutMinutes ||
minutes > maxTimeoutMinutes
) {
return;
}
await sendFimpMsg({
address: svc.addr,
service: 'door_lock',
cmd: 'cmd.lock.set_configuration',
val_t: 'object',
val: {
lock_timeout_minutes: minutes,
},
});
};
}
return {
components,
commandHandlers,
};
}