mirror of
https://github.com/adrianjagielak/home-assistant-futurehome.git
synced 2025-09-13 07:37:09 +00:00
Add support for unpairing devices
This commit is contained in:
parent
b034197a93
commit
850fc7fc57
@ -12,7 +12,7 @@ This add-on:
|
|||||||
* Fetches all device metadata from the Futurehome hub and maps them to Home Assistant devices/entities.
|
* Fetches all device metadata from the Futurehome hub and maps them to Home Assistant devices/entities.
|
||||||
* Fetches and updates device states and availability in real time.
|
* Fetches and updates device states and availability in real time.
|
||||||
* Supports interaction with devices comparable to the official Futurehome app.
|
* Supports interaction with devices comparable to the official Futurehome app.
|
||||||
* Supports pairing and unpairing of devices.
|
* Supports pairing and unpairing devices.
|
||||||
|
|
||||||
## Installation
|
## Installation
|
||||||
|
|
||||||
|
@ -4,6 +4,7 @@
|
|||||||
|
|
||||||
- Added logo.
|
- Added logo.
|
||||||
- Added support for pairing new devices.
|
- Added support for pairing new devices.
|
||||||
|
- Added support for unpairing devices.
|
||||||
|
|
||||||
## 0.1.7 (26.07.2025)
|
## 0.1.7 (26.07.2025)
|
||||||
|
|
||||||
|
@ -11,7 +11,7 @@ This add-on:
|
|||||||
* Fetches all device metadata from the Futurehome hub and maps them to Home Assistant devices/entities.
|
* Fetches all device metadata from the Futurehome hub and maps them to Home Assistant devices/entities.
|
||||||
* Fetches and updates device states and availability in real time.
|
* Fetches and updates device states and availability in real time.
|
||||||
* Supports interaction with devices comparable to the official Futurehome app.
|
* Supports interaction with devices comparable to the official Futurehome app.
|
||||||
* Supports pairing and unpairing of devices.
|
* Supports pairing and unpairing devices.
|
||||||
|
|
||||||
## Installation
|
## Installation
|
||||||
|
|
||||||
|
@ -3,7 +3,10 @@ export type VinculumPd7Device = {
|
|||||||
// User-defined device name
|
// User-defined device name
|
||||||
name?: string | null;
|
name?: string | null;
|
||||||
} | null;
|
} | null;
|
||||||
|
// FIMP Device ID.
|
||||||
id: number;
|
id: number;
|
||||||
|
// FIMP Thing Address (ID).
|
||||||
|
thing?: number | null;
|
||||||
// "Model" string, e.g. "zb - _TZ3040_bb6xaihh - TS0202"
|
// "Model" string, e.g. "zb - _TZ3040_bb6xaihh - TS0202"
|
||||||
// The first one is the adapter, the second one is the manufacturer, the third one is the device model.
|
// The first one is the adapter, the second one is the manufacturer, the third one is the device model.
|
||||||
model?: string | null;
|
model?: string | null;
|
||||||
|
@ -1,5 +1,3 @@
|
|||||||
import { v4 as uuidv4 } from 'uuid';
|
|
||||||
import { IMqttClient } from '../mqtt/interface';
|
|
||||||
import { CommandHandlers } from './publish_device';
|
import { CommandHandlers } from './publish_device';
|
||||||
import { HaDeviceConfig } from './ha_device_config';
|
import { HaDeviceConfig } from './ha_device_config';
|
||||||
import { ha } from './globals';
|
import { ha } from './globals';
|
||||||
@ -10,9 +8,31 @@ import {
|
|||||||
connectThingsplexWebSocketAndSend,
|
connectThingsplexWebSocketAndSend,
|
||||||
loginToThingsplex,
|
loginToThingsplex,
|
||||||
} from '../thingsplex/thingsplex';
|
} from '../thingsplex/thingsplex';
|
||||||
import { delay } from '../utils';
|
|
||||||
import { pollVinculum } from '../fimp/vinculum';
|
import { pollVinculum } from '../fimp/vinculum';
|
||||||
|
|
||||||
|
const inclusionExclusionNotRunningValues = [
|
||||||
|
'Ready',
|
||||||
|
'Done',
|
||||||
|
'Ready to start inclusion',
|
||||||
|
'Failed trying to start inclusion.',
|
||||||
|
"Operation failed. The device can't be included.",
|
||||||
|
'Device added successfully!',
|
||||||
|
'Ready to start exclusion',
|
||||||
|
'Failed trying to start exclusion.',
|
||||||
|
"Operation failed. The device can't be excluded.",
|
||||||
|
'',
|
||||||
|
];
|
||||||
|
|
||||||
|
const inclusionExclusionStartingStoppingValues = [
|
||||||
|
'Demo mode, inclusion not supported',
|
||||||
|
'Demo mode, exclusion not supported',
|
||||||
|
'Starting ZigBee inclusion',
|
||||||
|
'Starting ZigBee exclusion',
|
||||||
|
'Starting Z-Wave inclusion',
|
||||||
|
'Starting Z-Wave exclusion',
|
||||||
|
'Stopping',
|
||||||
|
];
|
||||||
|
|
||||||
let initializedState = false;
|
let initializedState = false;
|
||||||
|
|
||||||
export function exposeSmarthubTools(parameters: {
|
export function exposeSmarthubTools(parameters: {
|
||||||
@ -28,14 +48,10 @@ export function exposeSmarthubTools(parameters: {
|
|||||||
const topicPrefix = `homeassistant/device/futurehome_${parameters.hubId}_hub`;
|
const topicPrefix = `homeassistant/device/futurehome_${parameters.hubId}_hub`;
|
||||||
|
|
||||||
if (!initializedState) {
|
if (!initializedState) {
|
||||||
ha?.publish(
|
ha?.publish(`${topicPrefix}/inclusion_exclusion_status/state`, 'Ready', {
|
||||||
`${topicPrefix}/inclusion_status/state`,
|
retain: true,
|
||||||
'Ready to start inclusion',
|
qos: 2,
|
||||||
{
|
});
|
||||||
retain: true,
|
|
||||||
qos: 2,
|
|
||||||
},
|
|
||||||
);
|
|
||||||
initializedState = true;
|
initializedState = true;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -57,6 +73,25 @@ export function exposeSmarthubTools(parameters: {
|
|||||||
'https://github.com/adrianjagielak/home-assistant-futurehome',
|
'https://github.com/adrianjagielak/home-assistant-futurehome',
|
||||||
},
|
},
|
||||||
components: {
|
components: {
|
||||||
|
[`${deviceId}_inclusion_exclusion_status`]: {
|
||||||
|
unique_id: `${deviceId}_inclusion_exclusion_status`,
|
||||||
|
platform: 'sensor',
|
||||||
|
entity_category: 'diagnostic',
|
||||||
|
device_class: 'enum',
|
||||||
|
name: 'Inclusion/exclusion status',
|
||||||
|
state_topic: `${topicPrefix}/inclusion_exclusion_status/state`,
|
||||||
|
},
|
||||||
|
[`${deviceId}_zwave_startExclusion`]: {
|
||||||
|
unique_id: `${deviceId}_zwave_startExclusion`,
|
||||||
|
platform: 'button',
|
||||||
|
entity_category: 'diagnostic',
|
||||||
|
name: 'Start Z-Wave exclusion',
|
||||||
|
icon: 'mdi:z-wave',
|
||||||
|
command_topic: `${topicPrefix}/start_exclusion/command`,
|
||||||
|
payload_press: 'zwave',
|
||||||
|
availability_topic: `${topicPrefix}/inclusion_exclusion_status/state`,
|
||||||
|
availability_template: `{% if ${inclusionExclusionNotRunningValues.map((v) => `value == "${v}"`).join(' or ')} %}online{% else %}offline{% endif %}`,
|
||||||
|
} as any,
|
||||||
[`${deviceId}_zwave_startInclusion`]: {
|
[`${deviceId}_zwave_startInclusion`]: {
|
||||||
unique_id: `${deviceId}_zwave_startInclusion`,
|
unique_id: `${deviceId}_zwave_startInclusion`,
|
||||||
platform: 'button',
|
platform: 'button',
|
||||||
@ -65,8 +100,8 @@ export function exposeSmarthubTools(parameters: {
|
|||||||
icon: 'mdi:z-wave',
|
icon: 'mdi:z-wave',
|
||||||
command_topic: `${topicPrefix}/start_inclusion/command`,
|
command_topic: `${topicPrefix}/start_inclusion/command`,
|
||||||
payload_press: 'zwave',
|
payload_press: 'zwave',
|
||||||
availability_topic: `${topicPrefix}/inclusion_status/state`,
|
availability_topic: `${topicPrefix}/inclusion_exclusion_status/state`,
|
||||||
availability_template: `{% if value == "Done" or value == "Ready to start inclusion" or value == "Failed trying to start inclusion." or value == "Operation failed. The device can't be included." or value == "Device added successfully!" or value == "" %}online{% else %}offline{% endif %}`,
|
availability_template: `{% if ${inclusionExclusionNotRunningValues.map((v) => `value == "${v}"`).join(' or ')} %}online{% else %}offline{% endif %}`,
|
||||||
} as any,
|
} as any,
|
||||||
[`${deviceId}_zigbee_startInclusion`]: {
|
[`${deviceId}_zigbee_startInclusion`]: {
|
||||||
unique_id: `${deviceId}_zigbee_startInclusion`,
|
unique_id: `${deviceId}_zigbee_startInclusion`,
|
||||||
@ -76,27 +111,19 @@ export function exposeSmarthubTools(parameters: {
|
|||||||
icon: 'mdi:zigbee',
|
icon: 'mdi:zigbee',
|
||||||
command_topic: `${topicPrefix}/start_inclusion/command`,
|
command_topic: `${topicPrefix}/start_inclusion/command`,
|
||||||
payload_press: 'zigbee',
|
payload_press: 'zigbee',
|
||||||
availability_topic: `${topicPrefix}/inclusion_status/state`,
|
availability_topic: `${topicPrefix}/inclusion_exclusion_status/state`,
|
||||||
availability_template: `{% if value == "Done" or value == "Ready to start inclusion" or value == "Failed trying to start inclusion." or value == "Operation failed. The device can't be included." or value == "Device added successfully!" or value == "" %}online{% else %}offline{% endif %}`,
|
availability_template: `{% if ${inclusionExclusionNotRunningValues.map((v) => `value == "${v}"`).join(' or ')} %}online{% else %}offline{% endif %}`,
|
||||||
} as any,
|
} as any,
|
||||||
[`${deviceId}_stopInclusion`]: {
|
[`${deviceId}_stopInclusionExclusion`]: {
|
||||||
unique_id: `${deviceId}_stopInclusion`,
|
unique_id: `${deviceId}_stopInclusionExclusion`,
|
||||||
platform: 'button',
|
platform: 'button',
|
||||||
entity_category: 'diagnostic',
|
entity_category: 'diagnostic',
|
||||||
name: 'Stop inclusion',
|
name: 'Stop inclusion/exclusion',
|
||||||
icon: 'mdi:cancel',
|
icon: 'mdi:cancel',
|
||||||
command_topic: `${topicPrefix}/stop_inclusion/command`,
|
command_topic: `${topicPrefix}/stop_inclusion_exclusion/command`,
|
||||||
availability_topic: `${topicPrefix}/inclusion_status/state`,
|
availability_topic: `${topicPrefix}/inclusion_exclusion_status/state`,
|
||||||
availability_template: `{% if value == "Done" or value == "Ready to start inclusion" or value == "Failed trying to start inclusion." or value == "Operation failed. The device can't be included." or value == "Device added successfully!" or value == "Starting" or value == "Stopping" or value == "" %}offline{% else %}online{% endif %}`,
|
availability_template: `{% if ${[...inclusionExclusionNotRunningValues, ...inclusionExclusionStartingStoppingValues].map((v) => `value == "${v}"`).join(' or ')} %}offline{% else %}online{% endif %}`,
|
||||||
} as any,
|
} as any,
|
||||||
[`${deviceId}_inclusion_status`]: {
|
|
||||||
unique_id: `${deviceId}_inclusion_status`,
|
|
||||||
platform: 'sensor',
|
|
||||||
entity_category: 'diagnostic',
|
|
||||||
device_class: 'enum',
|
|
||||||
name: 'Inclusion status',
|
|
||||||
state_topic: `${topicPrefix}/inclusion_status/state`,
|
|
||||||
},
|
|
||||||
},
|
},
|
||||||
qos: 2,
|
qos: 2,
|
||||||
};
|
};
|
||||||
@ -110,40 +137,27 @@ export function exposeSmarthubTools(parameters: {
|
|||||||
const handlers: CommandHandlers = {
|
const handlers: CommandHandlers = {
|
||||||
[`${topicPrefix}/start_inclusion/command`]: async (payload) => {
|
[`${topicPrefix}/start_inclusion/command`]: async (payload) => {
|
||||||
if (parameters.demoMode) {
|
if (parameters.demoMode) {
|
||||||
ha?.publish(`${topicPrefix}/inclusion_status/state`, 'Starting', {
|
|
||||||
retain: true,
|
|
||||||
qos: 2,
|
|
||||||
});
|
|
||||||
await delay(2000);
|
|
||||||
ha?.publish(
|
ha?.publish(
|
||||||
`${topicPrefix}/inclusion_status/state`,
|
`${topicPrefix}/inclusion_exclusion_status/state`,
|
||||||
'Looking for device',
|
|
||||||
{
|
|
||||||
retain: true,
|
|
||||||
qos: 2,
|
|
||||||
},
|
|
||||||
);
|
|
||||||
await delay(2000);
|
|
||||||
ha?.publish(
|
|
||||||
`${topicPrefix}/inclusion_status/state`,
|
|
||||||
'Demo mode, inclusion not supported',
|
'Demo mode, inclusion not supported',
|
||||||
{
|
{
|
||||||
retain: true,
|
retain: true,
|
||||||
qos: 2,
|
qos: 2,
|
||||||
},
|
},
|
||||||
);
|
);
|
||||||
await delay(2000);
|
|
||||||
ha?.publish(`${topicPrefix}/inclusion_status/state`, 'Done', {
|
|
||||||
retain: true,
|
|
||||||
qos: 2,
|
|
||||||
});
|
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
ha?.publish(`${topicPrefix}/inclusion_status/state`, 'Starting', {
|
ha?.publish(
|
||||||
retain: true,
|
`${topicPrefix}/inclusion_exclusion_status/state`,
|
||||||
qos: 2,
|
payload == 'zwave'
|
||||||
});
|
? 'Starting Z-Wave inclusion'
|
||||||
|
: 'Starting ZigBee inclusion',
|
||||||
|
{
|
||||||
|
retain: true,
|
||||||
|
qos: 2,
|
||||||
|
},
|
||||||
|
);
|
||||||
try {
|
try {
|
||||||
const token = await loginToThingsplex({
|
const token = await loginToThingsplex({
|
||||||
host: parameters.hubIp,
|
host: parameters.hubIp,
|
||||||
@ -170,7 +184,7 @@ export function exposeSmarthubTools(parameters: {
|
|||||||
);
|
);
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
ha?.publish(
|
ha?.publish(
|
||||||
`${topicPrefix}/inclusion_status/state`,
|
`${topicPrefix}/inclusion_exclusion_status/state`,
|
||||||
'Failed trying to start inclusion.',
|
'Failed trying to start inclusion.',
|
||||||
{
|
{
|
||||||
retain: true,
|
retain: true,
|
||||||
@ -179,15 +193,20 @@ export function exposeSmarthubTools(parameters: {
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
[`${topicPrefix}/stop_inclusion/command`]: async (_payload) => {
|
[`${topicPrefix}/stop_inclusion_exclusion/command`]: async (_payload) => {
|
||||||
ha?.publish(`${topicPrefix}/inclusion_status/state`, 'Stopping', {
|
|
||||||
retain: true,
|
|
||||||
qos: 2,
|
|
||||||
});
|
|
||||||
if (parameters.demoMode) {
|
if (parameters.demoMode) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
ha?.publish(
|
||||||
|
`${topicPrefix}/inclusion_exclusion_status/state`,
|
||||||
|
'Stopping',
|
||||||
|
{
|
||||||
|
retain: true,
|
||||||
|
qos: 2,
|
||||||
|
},
|
||||||
|
);
|
||||||
|
|
||||||
try {
|
try {
|
||||||
const token = await loginToThingsplex({
|
const token = await loginToThingsplex({
|
||||||
host: parameters.hubIp,
|
host: parameters.hubIp,
|
||||||
@ -214,15 +233,29 @@ export function exposeSmarthubTools(parameters: {
|
|||||||
val: false,
|
val: false,
|
||||||
val_t: 'bool',
|
val_t: 'bool',
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
address: 'pt:j1/mt:cmd/rt:ad/rn:zigbee/ad:1',
|
||||||
|
service: 'zigbee',
|
||||||
|
cmd: 'cmd.thing.exclusion',
|
||||||
|
val: false,
|
||||||
|
val_t: 'bool',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
address: 'pt:j1/mt:cmd/rt:ad/rn:zw/ad:1',
|
||||||
|
service: 'zwave-ad',
|
||||||
|
cmd: 'cmd.thing.exclusion',
|
||||||
|
val: false,
|
||||||
|
val_t: 'bool',
|
||||||
|
},
|
||||||
],
|
],
|
||||||
);
|
);
|
||||||
ha?.publish(`${topicPrefix}/inclusion_status/state`, 'Done', {
|
ha?.publish(`${topicPrefix}/inclusion_exclusion_status/state`, 'Done', {
|
||||||
retain: true,
|
retain: true,
|
||||||
qos: 2,
|
qos: 2,
|
||||||
});
|
});
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
ha?.publish(
|
ha?.publish(
|
||||||
`${topicPrefix}/inclusion_status/state`,
|
`${topicPrefix}/inclusion_exclusion_status/state`,
|
||||||
'Failed trying to stop inclusion.',
|
'Failed trying to stop inclusion.',
|
||||||
{
|
{
|
||||||
retain: true,
|
retain: true,
|
||||||
@ -231,6 +264,64 @@ export function exposeSmarthubTools(parameters: {
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
[`${topicPrefix}/start_exclusion/command`]: async (payload) => {
|
||||||
|
if (parameters.demoMode) {
|
||||||
|
ha?.publish(
|
||||||
|
`${topicPrefix}/inclusion_exclusion_status/state`,
|
||||||
|
'Demo mode, exclusion not supported',
|
||||||
|
{
|
||||||
|
retain: true,
|
||||||
|
qos: 2,
|
||||||
|
},
|
||||||
|
);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
ha?.publish(
|
||||||
|
`${topicPrefix}/inclusion_exclusion_status/state`,
|
||||||
|
payload == 'zwave'
|
||||||
|
? 'Starting Z-Wave exclusion'
|
||||||
|
: 'Starting ZigBee exclusion',
|
||||||
|
{
|
||||||
|
retain: true,
|
||||||
|
qos: 2,
|
||||||
|
},
|
||||||
|
);
|
||||||
|
try {
|
||||||
|
const token = await loginToThingsplex({
|
||||||
|
host: parameters.hubIp,
|
||||||
|
username: parameters.thingsplexUsername,
|
||||||
|
password: parameters.thingsplexPassword,
|
||||||
|
});
|
||||||
|
await connectThingsplexWebSocketAndSend(
|
||||||
|
{
|
||||||
|
host: parameters.hubIp,
|
||||||
|
token: token,
|
||||||
|
},
|
||||||
|
[
|
||||||
|
{
|
||||||
|
address:
|
||||||
|
payload == 'zwave'
|
||||||
|
? 'pt:j1/mt:cmd/rt:ad/rn:zw/ad:1'
|
||||||
|
: 'pt:j1/mt:cmd/rt:ad/rn:zigbee/ad:1',
|
||||||
|
service: payload == 'zwave' ? 'zwave-ad' : 'zigbee',
|
||||||
|
cmd: 'cmd.thing.exclusion',
|
||||||
|
val: true,
|
||||||
|
val_t: 'bool',
|
||||||
|
},
|
||||||
|
],
|
||||||
|
);
|
||||||
|
} catch (e) {
|
||||||
|
ha?.publish(
|
||||||
|
`${topicPrefix}/inclusion_exclusion_status/state`,
|
||||||
|
'Failed trying to start exclusion.',
|
||||||
|
{
|
||||||
|
retain: true,
|
||||||
|
qos: 2,
|
||||||
|
},
|
||||||
|
);
|
||||||
|
}
|
||||||
|
},
|
||||||
};
|
};
|
||||||
|
|
||||||
return { commandHandlers: handlers };
|
return { commandHandlers: handlers };
|
||||||
@ -249,13 +340,21 @@ export function handleInclusionStatusReport(hubId: string, msg: FimpResponse) {
|
|||||||
case 'ADD_NODE_GET_NODE_INFO':
|
case 'ADD_NODE_GET_NODE_INFO':
|
||||||
case 'ADD_NODE_PROTOCOL_DONE':
|
case 'ADD_NODE_PROTOCOL_DONE':
|
||||||
localizedStatus = 'Device added successfully!';
|
localizedStatus = 'Device added successfully!';
|
||||||
pollVinculum('device').catch((e) => log.warn('Failed to request devices', e));
|
pollVinculum('device').catch((e) =>
|
||||||
pollVinculum('state').catch((e) => log.warn('Failed to request state', e));
|
log.warn('Failed to request devices', e),
|
||||||
|
);
|
||||||
|
pollVinculum('state').catch((e) =>
|
||||||
|
log.warn('Failed to request state', e),
|
||||||
|
);
|
||||||
break;
|
break;
|
||||||
case 'ADD_NODE_DONE':
|
case 'ADD_NODE_DONE':
|
||||||
localizedStatus = 'Done';
|
localizedStatus = 'Done';
|
||||||
pollVinculum('device').catch((e) => log.warn('Failed to request devices', e));
|
pollVinculum('device').catch((e) =>
|
||||||
pollVinculum('state').catch((e) => log.warn('Failed to request state', e));
|
log.warn('Failed to request devices', e),
|
||||||
|
);
|
||||||
|
pollVinculum('state').catch((e) =>
|
||||||
|
log.warn('Failed to request state', e),
|
||||||
|
);
|
||||||
break;
|
break;
|
||||||
case 'NET_NODE_INCL_CTRL_OP_FAILED':
|
case 'NET_NODE_INCL_CTRL_OP_FAILED':
|
||||||
localizedStatus = "Operation failed. The device can't be included.";
|
localizedStatus = "Operation failed. The device can't be included.";
|
||||||
@ -266,11 +365,57 @@ export function handleInclusionStatusReport(hubId: string, msg: FimpResponse) {
|
|||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
|
||||||
ha?.publish(`${topicPrefix}/inclusion_status/state`, localizedStatus, {
|
ha?.publish(
|
||||||
retain: true,
|
`${topicPrefix}/inclusion_exclusion_status/state`,
|
||||||
qos: 2,
|
localizedStatus,
|
||||||
});
|
{
|
||||||
|
retain: true,
|
||||||
|
qos: 2,
|
||||||
|
},
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
// todo exclusion?
|
export function handleExclusionStatusReport(hubId: string, msg: FimpResponse) {
|
||||||
// NET_NODE_REMOVE_FAILED", "Device can't be deleted
|
const topicPrefix = `homeassistant/device/futurehome_${hubId}_hub`;
|
||||||
|
|
||||||
|
let localizedStatus: string;
|
||||||
|
switch (msg.val) {
|
||||||
|
case 'REMOVE_NODE_STARTING':
|
||||||
|
case 'REMOVE_NODE_STARTED':
|
||||||
|
localizedStatus = 'Looking for device in unpairing mode';
|
||||||
|
break;
|
||||||
|
case 'REMOVE_NODE_FOUND':
|
||||||
|
localizedStatus = 'Device found';
|
||||||
|
break;
|
||||||
|
case 'REMOVE_NODE_DONE':
|
||||||
|
localizedStatus = 'Done';
|
||||||
|
pollVinculum('device').catch((e) =>
|
||||||
|
log.warn('Failed to request devices', e),
|
||||||
|
);
|
||||||
|
pollVinculum('state').catch((e) =>
|
||||||
|
log.warn('Failed to request state', e),
|
||||||
|
);
|
||||||
|
break;
|
||||||
|
case 'NET_NODE_REMOVE_FAILED':
|
||||||
|
localizedStatus = "Operation failed. The device can't be excluded.";
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
localizedStatus = msg.val;
|
||||||
|
log.warn(`Unknown exclusion status: ${msg.val}`);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
ha?.publish(
|
||||||
|
`${topicPrefix}/inclusion_exclusion_status/state`,
|
||||||
|
localizedStatus,
|
||||||
|
{
|
||||||
|
retain: true,
|
||||||
|
qos: 2,
|
||||||
|
},
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
export function handleExclusionReport() {
|
||||||
|
pollVinculum('device').catch((e) => log.warn('Failed to request devices', e));
|
||||||
|
pollVinculum('state').catch((e) => log.warn('Failed to request state', e));
|
||||||
|
}
|
||||||
|
@ -1,3 +1,4 @@
|
|||||||
|
import { sendFimpMsg } from '../fimp/fimp';
|
||||||
import { InclusionReport } from '../fimp/inclusion_report';
|
import { InclusionReport } from '../fimp/inclusion_report';
|
||||||
import {
|
import {
|
||||||
VinculumPd7Device,
|
VinculumPd7Device,
|
||||||
@ -30,8 +31,9 @@ import { sound_switch__components } from '../services/sound_switch';
|
|||||||
import { thermostat__components } from '../services/thermostat';
|
import { thermostat__components } from '../services/thermostat';
|
||||||
import { user_code__components } from '../services/user_code';
|
import { user_code__components } from '../services/user_code';
|
||||||
import { water_heater__components } from '../services/water_heater';
|
import { water_heater__components } from '../services/water_heater';
|
||||||
|
import { connectThingsplexWebSocketAndSend, loginToThingsplex } from '../thingsplex/thingsplex';
|
||||||
import { abbreviateHaMqttKeys } from './abbreviate_ha_mqtt_keys';
|
import { abbreviateHaMqttKeys } from './abbreviate_ha_mqtt_keys';
|
||||||
import { ha } from './globals';
|
import { ha, haCommandHandlers } from './globals';
|
||||||
import { HaDeviceConfig } from './ha_device_config';
|
import { HaDeviceConfig } from './ha_device_config';
|
||||||
import { HaMqttComponent } from './mqtt_components/_component';
|
import { HaMqttComponent } from './mqtt_components/_component';
|
||||||
|
|
||||||
@ -199,8 +201,11 @@ function shouldPublishService(
|
|||||||
export function haPublishDevice(parameters: {
|
export function haPublishDevice(parameters: {
|
||||||
hubId: string;
|
hubId: string;
|
||||||
demoMode: boolean;
|
demoMode: boolean;
|
||||||
|
hubIp: string;
|
||||||
vinculumDeviceData: VinculumPd7Device;
|
vinculumDeviceData: VinculumPd7Device;
|
||||||
deviceInclusionReport: InclusionReport | undefined;
|
deviceInclusionReport: InclusionReport | undefined;
|
||||||
|
thingsplexUsername: string;
|
||||||
|
thingsplexPassword: string;
|
||||||
}): { commandHandlers: CommandHandlers } {
|
}): { commandHandlers: CommandHandlers } {
|
||||||
const components: { [key: string]: HaMqttComponent } = {};
|
const components: { [key: string]: HaMqttComponent } = {};
|
||||||
const handlers: CommandHandlers = {};
|
const handlers: CommandHandlers = {};
|
||||||
@ -253,6 +258,71 @@ export function haPublishDevice(parameters: {
|
|||||||
Object.assign(handlers, result.commandHandlers);
|
Object.assign(handlers, result.commandHandlers);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const firstSvcAddr =
|
||||||
|
Object.entries(parameters.vinculumDeviceData.services ?? {})?.[0]?.[1]
|
||||||
|
.addr ?? '';
|
||||||
|
if (
|
||||||
|
parameters.thingsplexUsername &&
|
||||||
|
parameters.thingsplexPassword &&
|
||||||
|
parameters.vinculumDeviceData.thing &&
|
||||||
|
(firstSvcAddr.includes('/rn:zigbee/ad:1/') ||
|
||||||
|
firstSvcAddr.includes('/rn:zw/ad:1/'))
|
||||||
|
) {
|
||||||
|
const deleteCommandTopic = `${topicPrefix}/delete/command`;
|
||||||
|
const availabilityTopic = `${topicPrefix}/delete/availability`;
|
||||||
|
components[`${topicPrefix}_delete_button`] = {
|
||||||
|
unique_id: `${topicPrefix}_delete_button`,
|
||||||
|
platform: 'button',
|
||||||
|
entity_category: 'diagnostic',
|
||||||
|
name: firstSvcAddr.includes('/rn:zigbee/ad:1/')
|
||||||
|
? 'ZigBee: Unpair Device'
|
||||||
|
: 'Z-Wave: Unpair Device',
|
||||||
|
icon: 'mdi:delete-forever',
|
||||||
|
command_topic: deleteCommandTopic,
|
||||||
|
availability_topic: availabilityTopic,
|
||||||
|
availability_template: `{% if value == "online" or value == "" %}online{% else %}offline{% endif %}`,
|
||||||
|
} as any;
|
||||||
|
handlers[deleteCommandTopic] = async (_payload: string) => {
|
||||||
|
ha?.publish(availabilityTopic, 'offline', {
|
||||||
|
retain: true,
|
||||||
|
qos: 2,
|
||||||
|
});
|
||||||
|
try {
|
||||||
|
const token = await loginToThingsplex({
|
||||||
|
host: parameters.hubIp,
|
||||||
|
username: parameters.thingsplexUsername,
|
||||||
|
password: parameters.thingsplexPassword,
|
||||||
|
});
|
||||||
|
await connectThingsplexWebSocketAndSend(
|
||||||
|
{
|
||||||
|
host: parameters.hubIp,
|
||||||
|
token: token,
|
||||||
|
},
|
||||||
|
[
|
||||||
|
{
|
||||||
|
address: firstSvcAddr.includes('/rn:zigbee/ad:1/')
|
||||||
|
? 'pt:j1/mt:cmd/rt:ad/rn:zigbee/ad:1'
|
||||||
|
: 'pt:j1/mt:evt/rt:ad/rn:zw/ad:1',
|
||||||
|
service: firstSvcAddr.includes('/rn:zigbee/ad:1/')
|
||||||
|
? 'zigbee'
|
||||||
|
: 'zwave-ad',
|
||||||
|
cmd: 'cmd.thing.delete',
|
||||||
|
val_t: 'str_map',
|
||||||
|
val: {
|
||||||
|
address: parameters.vinculumDeviceData.thing,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
],
|
||||||
|
);
|
||||||
|
} catch (e) {
|
||||||
|
ha?.publish(availabilityTopic, 'online', {
|
||||||
|
retain: true,
|
||||||
|
qos: 2,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
let vinculumManufacturer: string | undefined;
|
let vinculumManufacturer: string | undefined;
|
||||||
const parts = (parameters.vinculumDeviceData?.model ?? '').split(' - ');
|
const parts = (parameters.vinculumDeviceData?.model ?? '').split(' - ');
|
||||||
if (parts.length === 3) {
|
if (parts.length === 3) {
|
||||||
|
@ -7,7 +7,7 @@ import { haUpdateState, haUpdateStateValueReport } from './ha/update_state';
|
|||||||
import { VinculumPd7Device } from './fimp/vinculum_pd7_device';
|
import { VinculumPd7Device } from './fimp/vinculum_pd7_device';
|
||||||
import { haUpdateAvailability } from './ha/update_availability';
|
import { haUpdateAvailability } from './ha/update_availability';
|
||||||
import { delay } from './utils';
|
import { delay } from './utils';
|
||||||
import { exposeSmarthubTools, handleInclusionStatusReport } from './ha/admin';
|
import { exposeSmarthubTools, handleExclusionReport, handleExclusionStatusReport, handleInclusionStatusReport } from './ha/admin';
|
||||||
import { pollVinculum } from './fimp/vinculum';
|
import { pollVinculum } from './fimp/vinculum';
|
||||||
|
|
||||||
(async () => {
|
(async () => {
|
||||||
@ -146,8 +146,11 @@ import { pollVinculum } from './fimp/vinculum';
|
|||||||
const result = haPublishDevice({
|
const result = haPublishDevice({
|
||||||
hubId,
|
hubId,
|
||||||
demoMode,
|
demoMode,
|
||||||
|
hubIp,
|
||||||
vinculumDeviceData,
|
vinculumDeviceData,
|
||||||
deviceInclusionReport,
|
deviceInclusionReport,
|
||||||
|
thingsplexUsername,
|
||||||
|
thingsplexPassword,
|
||||||
});
|
});
|
||||||
await delay(50);
|
await delay(50);
|
||||||
|
|
||||||
@ -255,6 +258,16 @@ import { pollVinculum } from './fimp/vinculum';
|
|||||||
handleInclusionStatusReport(hubId, msg);
|
handleInclusionStatusReport(hubId, msg);
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
case 'evt.thing.exclusion_status_report': {
|
||||||
|
handleExclusionStatusReport(hubId, msg);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
case 'evt.thing.exclusion_report': {
|
||||||
|
handleExclusionReport();
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
default: {
|
default: {
|
||||||
// Handle any event that matches the pattern: evt.<something>.report
|
// Handle any event that matches the pattern: evt.<something>.report
|
||||||
|
Loading…
x
Reference in New Issue
Block a user