Skip to content

Commit

Permalink
Controller Rework (#118)
Browse files Browse the repository at this point in the history
* Split storage

... because controller persisted data are many states

* Update matter.js

* We can use more current version to publish (es2022 is Node.js 16+)

* DeviceManagement use classes

... instead of states. wy better that way and also allows to directly execute logic on instances

* Make state change callbacks async

* StateObject Bi-Directional and Unit conversion

Introducing a flag "isIoBrokerDevice" defined if the device originates in ioBroker vs "outside device".
SOme changes are not the nicest, here I need to cleanup later.

Additionally added unit conversion logic when provided.

Also includes some refactoring

* Update Devicetype classes

Mainly adding "update*" methods that are used if the device is no ioBroekr device and value normally is not writable but a new value is pushed from the outside.

Additionally I started to splitup "actual" states into own types because the double usage made funny issues.

* Needed adjustment

... class unused and will be removed soon anyway, but needed change

* Renamed/Updated "To Matter" classes

Renamed structure and classes and needed updated from former changes.

* Introduce "To Iobroker" device classes

Logic to map Matter devi es for the current types into ioBroker direction.

ALso includes Lock (which is yet missing for the other direction. Will get in Readme next.
ALso includes a "UtilityOnly" type which for now uses a socket device as basis, needs to be discussed or added on iobroker side.

* Matter.js related changes and logging

* Rework GeneralMatterNode

Enhance the GeneralMatterNode Logic.

* Adjust Controller Node and expose some inforation for DeviceManagement

* Adjust main logic

* ENhance controller config type

* Adjust tests

... we still miss tests for "non ioBroker devices".

* Update matter.js

* address review feedback
  • Loading branch information
Apollon77 authored Nov 5, 2024
1 parent b827693 commit 7733ae6
Show file tree
Hide file tree
Showing 42 changed files with 8,965 additions and 18,545 deletions.
25,192 changes: 6,997 additions & 18,195 deletions package-lock.json

Large diffs are not rendered by default.

29 changes: 15 additions & 14 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -19,14 +19,15 @@
"url": "https://github.com/ioBroker/ioBroker.matter"
},
"optionalDependencies": {
"@matter/nodejs-ble": "0.11.0-alpha.0-20241018-34fe42e99"
"@matter/nodejs-ble": "0.11.2"
},
"dependencies": {
"@iobroker/adapter-core": "^3.1.6",
"@iobroker/dm-utils": "^0.3.1",
"@iobroker/adapter-core": "^3.2.2",
"@iobroker/dm-utils": "^0.5.0",
"@iobroker/type-detector": "^4.0.1",
"@matter/main": "0.11.0-alpha.0-20241018-34fe42e99",
"@project-chip/matter.js": "0.11.0-alpha.0-20241018-34fe42e99",
"@matter/main": "0.11.2",
"@matter/nodejs": "0.11.2",
"@project-chip/matter.js": "0.11.2",
"axios": "^1.7.7",
"jsonwebtoken": "^9.0.2"
},
Expand All @@ -37,26 +38,26 @@
"@iobroker/dev-server": "^0.7.3",
"@iobroker/legacy-testing": "^1.0.13",
"@iobroker/types": "^6.0.11",
"@types/jsonwebtoken": "^9.0.6",
"@types/node": "^22.5.5",
"@types/jsonwebtoken": "^9.0.7",
"@types/node": "^22.8.0",
"@typescript-eslint/eslint-plugin": "^7.18.0",
"chai": "^4.5.0",
"colorette": "^2.0.20",
"eslint": "^8.57.0",
"eslint": "^8.57.1",
"eslint-config-airbnb": "^19.0.4",
"eslint-config-prettier": "^9.1.0",
"eslint-import-resolver-typescript": "^3.6.1",
"eslint-import-resolver-typescript": "^3.6.3",
"eslint-plugin-eqeqeq-fix": "^1.0.3",
"eslint-plugin-import": "^2.29.1",
"eslint-plugin-n": "^17.10.1",
"eslint-plugin-import": "^2.31.0",
"eslint-plugin-n": "^17.11.1",
"eslint-plugin-prettier": "^5.2.1",
"eslint-plugin-promise": "^7.1.0",
"gulp": "^4.0.2",
"mocha": "^10.7.0",
"mocha": "^10.7.3",
"prettier": "^3.3.3",
"prettier-plugin-organize-imports": "^4.0.0",
"prettier-plugin-organize-imports": "^4.1.0",
"puppeteer": "^22.15.0",
"typescript": "~5.5.4"
"typescript": "~5.6.3"
},
"bugs": {
"url": "https://github.com/ioBroker/ioBroker.matter/issues"
Expand Down
2 changes: 2 additions & 0 deletions src-admin/src/types.d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -69,6 +69,8 @@ export interface MatterControllerConfig {
wifiPassword?: string;
threadNetworkName?: string;
threadOperationalDataSet?: string;
defaultExposeMatterApplicationClusterData?: boolean;
defaultExposeMatterSystemClusterData?: boolean;
}

export interface MatterConfig {
Expand Down
132 changes: 84 additions & 48 deletions src/lib/DeviceManagement.ts
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,8 @@ import {
DeviceStatus,
ErrorResponse,
} from '@iobroker/dm-utils';
import { GeneralMatterNode } from '../matter/GeneralMatterNode';
import { GenericDeviceToIoBroker } from '../matter/to-iobroker/GenericDeviceToIoBroker';
import { getText, t } from './i18n';

const demoDevice = {
Expand All @@ -18,64 +20,30 @@ const demoDevice = {
};

class MatterAdapterDeviceManagement extends DeviceManagement<MatterAdapter> {
#adapter: MatterAdapter;
#demoState: ioBroker.State | null | undefined;

constructor(adapter: MatterAdapter) {
super(adapter);
this.#adapter = adapter;
}

// contents see in the next chapters
async listDevices(): Promise<DeviceInfo[]> {
if (!this.#demoState) {
this.#demoState = await this.adapter.getForeignStateAsync('javascript.0.RGB.on');
}

const devices = await this.adapter.getDevicesAsync();
// const devices = await this.adapter.getObjectView('system', 'device', { startkey: `system.adapter.matter.${this.instance}.`, endkey: 'system.adapter.matter.999' });
const arrDevices: DeviceInfo[] = [];
for (const device of devices) {
let status: DeviceStatus = 'disconnected';
if (!this.#adapter.controllerNode) {
return []; // TODO How to return that no controller is started?
}

const alive = await this.adapter.getStateAsync(`${device._id}.info.connection`);
if (alive !== null && alive !== undefined) {
status = alive.val ? 'connected' : 'disconnected';
}
const nodes = this.#adapter.controllerNode.nodes;

const manufacturer = await this.adapter.getStateAsync(`${device._id}._info.brand`);
const product = await this.adapter.getStateAsync(`${device._id}._info.product`);
const arch = await this.adapter.getStateAsync(`${device._id}._info.arch`);
const model = `${product?.val ?? ''} ${arch?.val ?? ''}`.trim();

const res: DeviceInfo = {
id: device._id,
name: device.common.name,
icon: device.common.icon || undefined,
manufacturer: (manufacturer?.val as string) || undefined,
model: model || undefined,
status,
hasDetails: true,
actions: [
{
id: 'delete',
icon: 'fa-solid fa-trash-can',
description: t('Delete this device'),
handler: this.handleDeleteDevice.bind(this),
},
{
id: 'rename',
icon: 'fa-solid fa-pen',
description: t('Rename this device'),
handler: this.handleRenameDevice.bind(this),
},
{
id: 'pairingCode',
icon: 'fa-solid fa-qrcode',
description: t('Generate new pairing code'),
handler: this.handlePairingCode.bind(this),
},
],
};
// if id contains gateway remove res.actions
if (device._id.includes('localhost')) {
res.actions = [];
}
arrDevices.push(res);
const arrDevices: DeviceInfo[] = [];
for (const ioNode of nodes.values()) {
const devices = this.#getNodeEntry(ioNode);
arrDevices.push(...devices);
}

arrDevices.push({
Expand Down Expand Up @@ -116,6 +84,74 @@ class MatterAdapterDeviceManagement extends DeviceManagement<MatterAdapter> {
return arrDevices;
}

/**
* Create the "Node" device entry and also add all Endpoint-"Devices" for Device-Manager
*/
#getNodeEntry(ioNode: GeneralMatterNode): DeviceInfo[] {
const status: DeviceStatus = ioNode.node.isConnected ? 'connected' : 'disconnected';

const res = new Array<DeviceInfo>();
res.push({
id: ioNode.nodeBaseId,
name: `Node ${ioNode.nodeId}`,
icon: undefined, // TODO
manufacturer: undefined, // TODO
model: undefined, // TODO
status,
hasDetails: true,
actions: [
{
id: 'delete',
icon: 'fa-solid fa-trash-can',
description: t('Delete this device'),
handler: this.handleDeleteDevice.bind(this),
},
{
id: 'rename',
icon: 'fa-solid fa-pen',
description: t('Rename this device'),
handler: this.handleRenameDevice.bind(this),
},
{
id: 'pairingCode',
icon: 'fa-solid fa-qrcode',
description: t('Generate new pairing code'),
handler: this.handlePairingCode.bind(this),
},
],
});

for (const device of ioNode.devices.values()) {
const deviceInfo = this.#getNodeDeviceEntries(device, status);
res.push(deviceInfo);
}

return res;
}

/**
* Create one Endpoint-"Device" for Device-Manager
*/
#getNodeDeviceEntries(device: GenericDeviceToIoBroker, status: DeviceStatus): DeviceInfo {
return {
id: device.baseId,
name: `Device ${device.name}`,
icon: undefined, // TODO
manufacturer: undefined, // TODO
model: undefined, // TODO
status, // TODO
hasDetails: true,
actions: [
{
id: 'rename',
icon: 'fa-solid fa-pen',
description: t('Rename this device'),
handler: this.handleRenameDevice.bind(this),
},
],
};
}

async handleControlDevice(
_deviceId: string,
_actionId: string,
Expand Down
8 changes: 5 additions & 3 deletions src/lib/SubscribeManager.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
export type SubscribeCallback = (state: ioBroker.State) => void;
export type SubscribeCallback = (state: ioBroker.State) => Promise<void>;

class SubscribeManager {
/** List of all registered subscribed state ids. */
Expand All @@ -12,10 +12,12 @@ class SubscribeManager {
SubscribeManager.adapter = adapter;
}

static observer(id: string, state: ioBroker.State | null | undefined): void {
static async observer(id: string, state: ioBroker.State | null | undefined): Promise<void> {
const callbacks = SubscribeManager.subscribes.get(id);
if (callbacks !== undefined && state) {
callbacks.forEach(callback => callback(state));
for (const callback of callbacks) {
await callback(state);
}
}
}

Expand Down
Loading

0 comments on commit 7733ae6

Please sign in to comment.