Add support for 'doorman' service

This commit is contained in:
Adrian Jagielak 2025-07-26 23:59:57 +02:00
parent f01494caa8
commit ac435719a5
No known key found for this signature in database
GPG Key ID: 0818CF7AF6C62BFB
5 changed files with 433 additions and 4 deletions

View File

@ -46,8 +46,7 @@ todo add info about factory reset hub to restore 30 day trial
| 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/) |
| 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) | | ✅ | [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) | | |
| Door lock | [door_lock](https://github.com/adrianjagielak/home-assistant-futurehome/blob/master/futurehome/src/services/door_lock.ts) | [Yale Doorman](https://www.assaabloy.com/ee/en/solutions/products/smart-locks/yale-doorman), [doorman](https://github.com/adrianjagielak/home-assistant-futurehome/blob/master/futurehome/src/services/doorman.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/), [Sensor](https://www.home-assistant.io/integrations/sensor/), [Text](https://www.home-assistant.io/integrations/text/) |
| 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) | | |
| Media player | [media_player](https://github.com/adrianjagielak/home-assistant-futurehome/blob/master/futurehome/src/services/media_player.ts) | | ✅ | [Select](https://www.home-assistant.io/integrations/select/), [Number](https://www.home-assistant.io/integrations/number/), [Switch](https://www.home-assistant.io/integrations/switch/), [Image](https://www.home-assistant.io/integrations/image/), [Sensor](https://www.home-assistant.io/integrations/sensor/) |

View File

@ -9,6 +9,7 @@
- Added support for 'door_lock' service (door locks).
- Added support for 'user_code' service (keypads).
- Added support for 'schedule_entry' service (for scheduling access).
- Added support for 'doorman' service (Yale door locks).
## 0.1.5 (25.07.2025)

View File

@ -45,8 +45,7 @@ todo add info about factory reset hub to restore 30 day trial
| 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/) |
| 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) | | ✅ | [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) | | |
| Door lock | [door_lock](https://github.com/adrianjagielak/home-assistant-futurehome/blob/master/futurehome/src/services/door_lock.ts) | [Yale Doorman](https://www.assaabloy.com/ee/en/solutions/products/smart-locks/yale-doorman), [doorman](https://github.com/adrianjagielak/home-assistant-futurehome/blob/master/futurehome/src/services/doorman.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/), [Sensor](https://www.home-assistant.io/integrations/sensor/), [Text](https://www.home-assistant.io/integrations/text/) |
| 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) | | |
| Media player | [media_player](https://github.com/adrianjagielak/home-assistant-futurehome/blob/master/futurehome/src/services/media_player.ts) | | ✅ | [Select](https://www.home-assistant.io/integrations/select/), [Number](https://www.home-assistant.io/integrations/number/), [Switch](https://www.home-assistant.io/integrations/switch/), [Image](https://www.home-assistant.io/integrations/image/), [Sensor](https://www.home-assistant.io/integrations/sensor/) |

View File

@ -14,6 +14,7 @@ import { battery__components } from '../services/battery';
import { chargepoint__components } from '../services/chargepoint';
import { color_ctrl__components } from '../services/color_ctrl';
import { door_lock__components } from '../services/door_lock';
import { doorman__components } from '../services/doorman';
import { fan_ctrl__components } from '../services/fan_ctrl';
import { indicator_ctrl__components } from '../services/indicator_ctrl';
import { media_player__components } from '../services/media_player';
@ -153,6 +154,7 @@ const serviceHandlers: {
chargepoint: chargepoint__components,
color_ctrl: color_ctrl__components,
door_lock: door_lock__components,
doorman: doorman__components,
fan_ctrl: fan_ctrl__components,
indicator_ctrl: indicator_ctrl__components,
media_player: media_player__components,

View File

@ -0,0 +1,428 @@
import { sendFimpMsg } from '../fimp/fimp';
import {
VinculumPd7Device,
VinculumPd7Service,
} from '../fimp/vinculum_pd7_device';
import { HaMqttComponent } from '../ha/mqtt_components/_component';
import {
CommandHandlers,
ServiceComponentsCreationResult,
} from '../ha/publish_device';
export function doorman__components(
topicPrefix: string,
device: VinculumPd7Device,
svc: VinculumPd7Service,
_svcName: string,
): ServiceComponentsCreationResult | undefined {
const components: Record<string, HaMqttComponent> = {};
const commandHandlers: CommandHandlers = {};
// Integration session sensor - shows when the lock is in integration mode
if (svc.intf?.includes('evt.doorman_session.report')) {
components[`${svc.addr}_session`] = {
unique_id: `${svc.addr}_session`,
platform: 'binary_sensor',
name: 'Integration Session',
device_class: 'running',
entity_category: 'diagnostic',
value_template: `{{ value_json['${svc.addr}'].session_active | default(false) | iif('ON', 'OFF') }}`,
};
}
// User management buttons and sensors
if (svc.intf?.includes('cmd.doorman_user.get_all')) {
// Button to refresh user list
const getUsersCommandTopic = `${topicPrefix}${svc.addr}/get_users/command`;
components[`${svc.addr}_get_users`] = {
unique_id: `${svc.addr}_get_users`,
platform: 'button',
name: 'Refresh Users',
entity_category: 'config',
command_topic: getUsersCommandTopic,
};
commandHandlers[getUsersCommandTopic] = async (_payload: string) => {
await sendFimpMsg({
address: svc.addr,
service: 'doorman',
cmd: 'cmd.doorman_user.get_all',
val_t: 'null',
val: null,
});
};
}
// User count sensor
components[`${svc.addr}_user_count`] = {
unique_id: `${svc.addr}_user_count`,
platform: 'sensor',
name: 'User Count',
entity_category: 'diagnostic',
icon: 'mdi:account-multiple',
value_template: `{{ value_json['${svc.addr}'].users.slots | length if value_json['${svc.addr}'].users.slots is defined else 0 }}`,
};
// Parameter management
if (svc.intf?.includes('cmd.doorman_param.get_report')) {
// Button to refresh parameters
const getParamsCommandTopic = `${topicPrefix}${svc.addr}/get_params/command`;
components[`${svc.addr}_get_params`] = {
unique_id: `${svc.addr}_get_params`,
platform: 'button',
name: 'Refresh Parameters',
entity_category: 'config',
command_topic: getParamsCommandTopic,
};
commandHandlers[getParamsCommandTopic] = async (_payload: string) => {
await sendFimpMsg({
address: svc.addr,
service: 'doorman',
cmd: 'cmd.doorman_param.get_report',
val_t: 'null',
val: null,
});
};
}
// Configuration parameters as select entities
if (svc.intf?.includes('cmd.doorman_param.set')) {
// Silent mode (Parameter ID 1)
const silentModeCommandTopic = `${topicPrefix}${svc.addr}/silent_mode/command`;
components[`${svc.addr}_silent_mode`] = {
unique_id: `${svc.addr}_silent_mode`,
platform: 'select',
name: 'Silent Mode',
entity_category: 'config',
options: ['Silent', 'Volume 1 (Low)', 'Volume 2 (High)'],
command_topic: silentModeCommandTopic,
value_template: `{% set val = value_json['${svc.addr}'].params['1'] | default('3') %}{{ {'1': 'Silent', '2': 'Volume 1 (Low)', '3': 'Volume 2 (High)'}.get(val, 'Volume 2 (High)') }}`,
};
commandHandlers[silentModeCommandTopic] = async (payload: string) => {
const valueMap: Record<string, string> = {
Silent: '1',
'Volume 1 (Low)': '2',
'Volume 2 (High)': '3',
};
const value = valueMap[payload];
if (!value) return;
await sendFimpMsg({
address: svc.addr,
service: 'doorman',
cmd: 'cmd.doorman_param.set',
val_t: 'str_map',
val: {
parameter_id: '1',
value: value,
},
});
};
// Auto Relock (Parameter ID 2)
const autoRelockCommandTopic = `${topicPrefix}${svc.addr}/auto_relock/command`;
components[`${svc.addr}_auto_relock`] = {
unique_id: `${svc.addr}_auto_relock`,
platform: 'switch',
name: 'Auto Relock',
entity_category: 'config',
command_topic: autoRelockCommandTopic,
value_template: `{{ (value_json['${svc.addr}'].params['2'] | default('0') == '255') | iif('ON', 'OFF') }}`,
};
commandHandlers[autoRelockCommandTopic] = async (payload: string) => {
const value = payload === 'ON' ? '255' : '0';
await sendFimpMsg({
address: svc.addr,
service: 'doorman',
cmd: 'cmd.doorman_param.set',
val_t: 'str_map',
val: {
parameter_id: '2',
value: value,
},
});
};
// Language (Parameter ID 5)
const languageCommandTopic = `${topicPrefix}${svc.addr}/language/command`;
components[`${svc.addr}_language`] = {
unique_id: `${svc.addr}_language`,
platform: 'select',
name: 'Language',
entity_category: 'config',
options: ['English', 'Danish', 'Norwegian', 'Swedish'],
command_topic: languageCommandTopic,
value_template: `{% set val = value_json['${svc.addr}'].params['5'] | default('1') %}{{ {'1': 'English', '4': 'Danish', '5': 'Norwegian', '6': 'Swedish'}.get(val, 'English') }}`,
};
commandHandlers[languageCommandTopic] = async (payload: string) => {
const valueMap: Record<string, string> = {
English: '1',
Danish: '4',
Norwegian: '5',
Swedish: '6',
};
const value = valueMap[payload];
if (!value) return;
await sendFimpMsg({
address: svc.addr,
service: 'doorman',
cmd: 'cmd.doorman_param.set',
val_t: 'str_map',
val: {
parameter_id: '5',
value: value,
},
});
};
// Home/Away Alarm Mode (Parameter ID 17)
const alarmModeCommandTopic = `${topicPrefix}${svc.addr}/alarm_mode/command`;
components[`${svc.addr}_alarm_mode`] = {
unique_id: `${svc.addr}_alarm_mode`,
platform: 'select',
name: 'Alarm Mode',
entity_category: 'config',
options: ['Off', 'Home Alarm Mode', 'Away Alarm Mode'],
command_topic: alarmModeCommandTopic,
value_template: `{% set val = value_json['${svc.addr}'].params['17'] | default('0') %}{{ {'0': 'Off', '1': 'Home Alarm Mode', '2': 'Away Alarm Mode'}.get(val, 'Off') }}`,
};
commandHandlers[alarmModeCommandTopic] = async (payload: string) => {
const valueMap: Record<string, string> = {
Off: '0',
'Home Alarm Mode': '1',
'Away Alarm Mode': '2',
};
const value = valueMap[payload];
if (!value) return;
await sendFimpMsg({
address: svc.addr,
service: 'doorman',
cmd: 'cmd.doorman_param.set',
val_t: 'str_map',
val: {
parameter_id: '17',
value: value,
},
});
};
// Part of Alarm System (Parameter ID 18)
const alarmSystemCommandTopic = `${topicPrefix}${svc.addr}/alarm_system/command`;
components[`${svc.addr}_alarm_system`] = {
unique_id: `${svc.addr}_alarm_system`,
platform: 'switch',
name: 'Part of Alarm System',
entity_category: 'config',
command_topic: alarmSystemCommandTopic,
value_template: `{{ (value_json['${svc.addr}'].params['18'] | default('1') == '1') | iif('ON', 'OFF') }}`,
};
commandHandlers[alarmSystemCommandTopic] = async (payload: string) => {
const value = payload === 'ON' ? '1' : '0';
await sendFimpMsg({
address: svc.addr,
service: 'doorman',
cmd: 'cmd.doorman_param.set',
val_t: 'str_map',
val: {
parameter_id: '18',
value: value,
},
});
};
// User Code Blocking (Parameter ID 19)
const codeBlockingCommandTopic = `${topicPrefix}${svc.addr}/code_blocking/command`;
components[`${svc.addr}_code_blocking`] = {
unique_id: `${svc.addr}_code_blocking`,
platform: 'switch',
name: 'User Code Blocking',
entity_category: 'config',
command_topic: codeBlockingCommandTopic,
value_template: `{{ (value_json['${svc.addr}'].params['19'] | default('0') == '1') | iif('ON', 'OFF') }}`,
};
commandHandlers[codeBlockingCommandTopic] = async (payload: string) => {
const value = payload === 'ON' ? '1' : '0';
await sendFimpMsg({
address: svc.addr,
service: 'doorman',
cmd: 'cmd.doorman_param.set',
val_t: 'str_map',
val: {
parameter_id: '19',
value: value,
},
});
};
// System Arm Hold Time (Parameter ID 16) - Number input in milliseconds
const armHoldTimeCommandTopic = `${topicPrefix}${svc.addr}/arm_hold_time/command`;
components[`${svc.addr}_arm_hold_time`] = {
unique_id: `${svc.addr}_arm_hold_time`,
platform: 'number',
name: 'System Arm Hold Time',
entity_category: 'config',
unit_of_measurement: 'ms',
min: 1000,
max: 20000,
step: 100,
command_topic: armHoldTimeCommandTopic,
value_template: `{{ value_json['${svc.addr}'].params['16'] | default(3000) | int }}`,
};
commandHandlers[armHoldTimeCommandTopic] = async (payload: string) => {
const value = parseInt(payload, 10);
if (isNaN(value) || value < 1000 || value > 20000) return;
await sendFimpMsg({
address: svc.addr,
service: 'doorman',
cmd: 'cmd.doorman_param.set',
val_t: 'str_map',
val: {
parameter_id: '16',
value: value.toString(),
},
});
};
}
// Activity sensor - shows last activity
if (svc.intf?.includes('evt.doorman_activity.report')) {
components[`${svc.addr}_last_activity`] = {
unique_id: `${svc.addr}_last_activity`,
platform: 'sensor',
name: 'Last Activity',
entity_category: 'diagnostic',
icon: 'mdi:history',
value_template: `{% set activity = value_json['${svc.addr}'].activity %}{% if activity %}{{ activity.event_type | default('unknown') }}{% else %}unknown{% endif %}`,
};
// Activity details as attributes sensor
components[`${svc.addr}_activity_details`] = {
unique_id: `${svc.addr}_activity_details`,
platform: 'sensor',
name: 'Activity Details',
entity_category: 'diagnostic',
icon: 'mdi:information',
value_template: `{% set activity = value_json['${svc.addr}'].activity %}{% if activity %}{{ activity | tojson }}{% else %}{}{% endif %}`,
};
}
// User management functions - these would typically be handled through automations or scripts
// But we can expose basic clear user functionality
if (svc.intf?.includes('cmd.doorman_user.clear')) {
// We'll create a text input for slot number to clear
const clearUserCommandTopic = `${topicPrefix}${svc.addr}/clear_user/command`;
components[`${svc.addr}_clear_user_slot`] = {
unique_id: `${svc.addr}_clear_user_slot`,
platform: 'text',
name: 'Clear User Slot',
entity_category: 'config',
command_topic: clearUserCommandTopic,
min: 1,
max: 2,
pattern: '^[0-9]{1,2}$',
};
commandHandlers[clearUserCommandTopic] = async (payload: string) => {
const slotNumber = parseInt(payload, 10);
if (isNaN(slotNumber) || slotNumber < 0 || slotNumber > 20) return;
await sendFimpMsg({
address: svc.addr,
service: 'doorman',
cmd: 'cmd.doorman_user.clear',
val_t: 'str_map',
val: {
slot_number: slotNumber.toString(),
},
});
};
}
// Integration command support
if (svc.intf?.includes('cmd.doorman.integration')) {
// This is typically handled automatically by the hub, but we can expose manual trigger
const integrationCommandTopic = `${topicPrefix}${svc.addr}/start_integration/command`;
components[`${svc.addr}_start_integration`] = {
unique_id: `${svc.addr}_start_integration`,
platform: 'button',
name: 'Start Integration',
entity_category: 'config',
device_class: 'restart',
command_topic: integrationCommandTopic,
};
commandHandlers[integrationCommandTopic] = async (_payload: string) => {
// Start integration with default PIN code (this should be customized)
await sendFimpMsg({
address: svc.addr,
service: 'doorman',
cmd: 'cmd.doorman.integration',
val_t: 'str_map',
val: {
slot_number: '0',
code_type: 'pin',
code: '123456', // Default code - should be configurable
},
});
};
}
// Arm confirmation support
if (svc.intf?.includes('cmd.doorman.arm_confirm')) {
const armConfirmCommandTopic = `${topicPrefix}${svc.addr}/arm_confirm/command`;
components[`${svc.addr}_arm_confirm`] = {
unique_id: `${svc.addr}_arm_confirm`,
platform: 'button',
name: 'Arm Confirm',
entity_category: 'config',
command_topic: armConfirmCommandTopic,
};
commandHandlers[armConfirmCommandTopic] = async (_payload: string) => {
await sendFimpMsg({
address: svc.addr,
service: 'doorman',
cmd: 'cmd.doorman.arm_confirm',
val_t: 'str_map',
val: {
sequence_number: '0',
operating_parameter: '0',
},
});
};
}
return {
components,
commandHandlers,
};
}