Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Feature/restructure ble #167

Merged
merged 25 commits into from
Mar 8, 2021
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
25 commits
Select commit Hold shift + click to select a range
2694c67
Break out and structure creation of BLE command payloads
SweVictor Feb 18, 2021
3fdb9ba
Split device communication and BLE handling into separate files
SweVictor Feb 20, 2021
53e67fb
Break out and structure creation of BLE command payloads
SweVictor Feb 18, 2021
436ed6b
Split device communication and BLE handling into separate files
SweVictor Feb 20, 2021
70059da
Merge branch 'feature/restructure-ble' of https://github.com/SweVicto…
SweVictor Feb 20, 2021
e528e2b
Fix turn off and dim commands
SweVictor Feb 20, 2021
0dbe0bb
Move BLE states to DeviceRegistry and improve logging
SweVictor Feb 20, 2021
50d664c
Clean up events and subscriptions
SweVictor Feb 20, 2021
e8ab13a
Remove redundant log row
SweVictor Feb 20, 2021
4d5a4dd
Stop write queue when BLE is not connected to avoid loosing commands
SweVictor Feb 22, 2021
925ccba
Stop recreating the dbus.systemBus on retries (old instances are not …
SweVictor Feb 27, 2021
3804c63
Merge pull request #166 from icanos/develop
SweVictor Feb 27, 2021
6855e03
Invoke power cycling of the BLE adapter every 10th reconnect attempt
SweVictor Feb 27, 2021
55f0f60
Fix setting state for created Plejd room devices
SweVictor Feb 27, 2021
e986c8b
Bump version to 0.6.2
SweVictor Feb 27, 2021
b3adf22
Break out and structure creation of BLE command payloads
SweVictor Feb 18, 2021
fb48b1f
Split device communication and BLE handling into separate files
SweVictor Feb 20, 2021
8d259a7
Fix turn off and dim commands
SweVictor Feb 20, 2021
4d7de61
Move BLE states to DeviceRegistry and improve logging
SweVictor Feb 20, 2021
ca7a5cd
Clean up events and subscriptions
SweVictor Feb 20, 2021
4591af5
Remove redundant log row
SweVictor Feb 20, 2021
e7b8a5a
Stop write queue when BLE is not connected to avoid loosing commands
SweVictor Feb 22, 2021
042447d
Stop recreating the dbus.systemBus on retries (old instances are not …
SweVictor Feb 27, 2021
517ab75
Invoke power cycling of the BLE adapter every 10th reconnect attempt
SweVictor Feb 27, 2021
0f9ad41
Merge remote-tracking branch 'origin/feature/restructure-ble' into fe…
SweVictor Feb 28, 2021
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
8 changes: 8 additions & 0 deletions plejd/CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,5 +1,13 @@
# Changelog hassio-plejd Home Assistant Plejd addon

### [0.6.2](https://github.com/icanos/hassio-plejd/tree/0.6.2) (2021-02-27)

[Full Changelog](https://github.com/icanos/hassio-plejd/compare/0.6.1...0.6.2)

**Closed issues:**

- Include rooms as lights does not work in 0.6.1 [\#169](https://github.com/icanos/hassio-plejd/issues/169)

### [0.6.1](https://github.com/icanos/hassio-plejd/tree/0.6.1) (2021-02-20)

[Full Changelog](https://github.com/icanos/hassio-plejd/compare/0.6.0...0.6.1)
Expand Down
111 changes: 96 additions & 15 deletions plejd/DeviceRegistry.js
Original file line number Diff line number Diff line change
@@ -1,3 +1,6 @@
const Logger = require('./Logger');

const logger = Logger.getLogger('device-registry');
class DeviceRegistry {
apiSite;
cryptoKey = null;
Expand All @@ -19,20 +22,70 @@ class DeviceRegistry {
}

addPlejdDevice(device) {
this.plejdDevices[device.id] = device;
this.deviceIdsBySerial[device.serialNumber] = device.id;
if (!this.deviceIdsByRoom[device.roomId]) {
this.deviceIdsByRoom[device.roomId] = [];
const added = {
...this.plejdDevices[device.id],
...device,
};

this.plejdDevices = {
...this.plejdDevices,
[added.id]: added,
};

this.deviceIdsBySerial[added.serialNumber] = added.id;

logger.verbose(
`Added/updated device: ${JSON.stringify(added)}. ${
Object.keys(this.plejdDevices).length
} plejd devices in total.`,
);

if (added.roomId) {
const room = this.deviceIdsByRoom[added.roomId] || [];
if (!room.includes(added.roomId)) {
this.deviceIdsByRoom[added.roomId] = [...room, added.roomId];
}
logger.verbose(
`Added to room #${added.roomId}: ${JSON.stringify(this.deviceIdsByRoom[added.roomId])}`,
);
}
this.deviceIdsByRoom[device.roomId].push(device.id);

return added;
}

addScene(scene) {
this.sceneDevices[scene.id] = scene;
addRoomDevice(device) {
const added = {
...this.roomDevices[device.id],
...device,
};
this.roomDevices = {
...this.roomDevices,
[added.id]: added,
};

logger.verbose(
`Added/updated room device: ${JSON.stringify(added)}. ${
Object.keys(this.roomDevices).length
} room devices total.`,
);
return added;
}

setApiSite(siteDetails) {
this.apiSite = siteDetails;
addScene(scene) {
const added = {
...this.sceneDevices[scene.id],
...scene,
};
this.sceneDevices = {
...this.sceneDevices,
[added.id]: added,
};
logger.verbose(
`Added/updated scene: ${JSON.stringify(added)}. ${
Object.keys(this.sceneDevices).length
} scenes in total.`,
);
return added;
}

clearPlejdDevices() {
Expand All @@ -41,10 +94,6 @@ class DeviceRegistry {
this.deviceIdsBySerial = {};
}

addRoomDevice(device) {
this.roomDevices[device.id] = device;
}

clearRoomDevices() {
this.roomDevices = {};
}
Expand All @@ -54,11 +103,15 @@ class DeviceRegistry {
}

getDevice(deviceId) {
return this.plejdDevices[deviceId];
return this.plejdDevices[deviceId] || this.roomDevices[deviceId];
}

getDeviceIdsByRoom(roomId) {
return this.deviceIdsByRoom[roomId];
}

getDeviceBySerialNumber(serialNumber) {
return this.plejdDevices[this.deviceIdsBySerial[serialNumber]];
return this.getDevice(this.deviceIdsBySerial[serialNumber]);
}

getDeviceName(deviceId) {
Expand All @@ -72,6 +125,34 @@ class DeviceRegistry {
getSceneName(sceneId) {
return (this.sceneDevices[sceneId] || {}).name;
}

getState(deviceId) {
const device = this.getDevice(deviceId) || {};
if (device.dimmable) {
return {
state: device.state,
dim: device.dim,
};
}
return {
state: device.state,
};
}

setApiSite(siteDetails) {
this.apiSite = siteDetails;
}

setState(deviceId, state, dim) {
const device = this.getDevice(deviceId) || this.addPlejdDevice({ id: deviceId });
device.state = state;
if (dim && device.dimmable) {
device.dim = dim;
}
if (Logger.shouldLog('silly')) {
logger.silly(`Updated state: ${JSON.stringify(device)}`);
}
}
}

module.exports = DeviceRegistry;
12 changes: 1 addition & 11 deletions plejd/Dockerfile
Original file line number Diff line number Diff line change
Expand Up @@ -7,19 +7,9 @@ ENV LANG C.UTF-8
SHELL ["/bin/bash", "-o", "pipefail", "-c"]

# Copy data for add-on
COPY ./*.js /plejd/
COPY ./config.json /plejd/
COPY ./Configuration.js /plejd/
COPY ./DeviceRegistry.js /plejd/
COPY ./Logger.js /plejd/
COPY ./main.js /plejd/
COPY ./MqttClient.js /plejd/
COPY ./package.json /plejd/
COPY ./PlejdAddon.js /plejd/
COPY ./PlejdApi.js /plejd/
COPY ./PlejdBLEHandler.js /plejd/
COPY ./Scene.js /plejd/
COPY ./SceneManager.js /plejd/
COPY ./SceneStep.js /plejd/

ARG BUILD_ARCH

Expand Down
127 changes: 70 additions & 57 deletions plejd/MqttClient.js
Original file line number Diff line number Diff line change
Expand Up @@ -70,6 +70,11 @@ const getSwitchPayload = (device) => ({
class MqttClient extends EventEmitter {
deviceRegistry;

static EVENTS = {
connected: 'connected',
stateChanged: 'stateChanged',
};

constructor(deviceRegistry) {
super();

Expand All @@ -94,10 +99,10 @@ class MqttClient extends EventEmitter {

this.client.subscribe(startTopics, (err) => {
if (err) {
logger.error('Unable to subscribe to status topics');
logger.error('Unable to subscribe to status topics', err);
}

this.emit('connected');
this.emit(MqttClient.EVENTS.connected);
});

this.client.subscribe(getSubscribePath(), (err) => {
Expand All @@ -113,66 +118,70 @@ class MqttClient extends EventEmitter {
});

this.client.on('message', (topic, message) => {
if (startTopics.includes(topic)) {
logger.info('Home Assistant has started. lets do discovery.');
this.emit('connected');
} else {
const decodedTopic = decodeTopic(topic);
if (decodedTopic) {
let device = this.deviceRegistry.getDevice(decodedTopic.id);

const messageString = message.toString();
const isJsonMessage = messageString.startsWith('{');
const command = isJsonMessage ? JSON.parse(messageString) : messageString;

if (
!isJsonMessage
&& messageString === 'ON'
&& this.deviceRegistry.getScene(decodedTopic.id)
) {
// Guess that id that got state command without dim value belongs to Scene, not Device
// This guess could very well be wrong depending on the installation...
logger.warn(
`Device id ${decodedTopic.id} belongs to both scene and device, guessing Scene is what should be set to ON.`
+ 'OFF commands still sent to device.',
);
device = this.deviceRegistry.getScene(decodedTopic.id);
}
const deviceName = device ? device.name : '';

switch (decodedTopic.command) {
case 'set':
logger.verbose(
`Got mqtt SET command for ${decodedTopic.type}, ${deviceName} (${decodedTopic.id}): ${messageString}`,
try {
if (startTopics.includes(topic)) {
logger.info('Home Assistant has started. lets do discovery.');
this.emit(MqttClient.EVENTS.connected);
} else {
const decodedTopic = decodeTopic(topic);
if (decodedTopic) {
let device = this.deviceRegistry.getDevice(decodedTopic.id);

const messageString = message.toString();
const isJsonMessage = messageString.startsWith('{');
const command = isJsonMessage ? JSON.parse(messageString) : messageString;

if (
!isJsonMessage
&& messageString === 'ON'
&& this.deviceRegistry.getScene(decodedTopic.id)
) {
// Guess that id that got state command without dim value belongs to Scene, not Device
// This guess could very well be wrong depending on the installation...
logger.warn(
`Device id ${decodedTopic.id} belongs to both scene and device, guessing Scene is what should be set to ON. `
+ 'OFF commands still sent to device.',
);
device = this.deviceRegistry.getScene(decodedTopic.id);
}
const deviceName = device ? device.name : '';

switch (decodedTopic.command) {
case 'set':
logger.verbose(
`Got mqtt SET command for ${decodedTopic.type}, ${deviceName} (${decodedTopic.id}): ${messageString}`,
);

if (device) {
this.emit('stateChanged', device, command);
} else {
logger.warn(
`Device for topic ${topic} not found! Can happen if HA calls previously existing devices.`,
if (device) {
this.emit(MqttClient.EVENTS.stateChanged, device, command);
} else {
logger.warn(
`Device for topic ${topic} not found! Can happen if HA calls previously existing devices.`,
);
}
break;
case 'state':
case 'config':
case 'availability':
logger.verbose(
`Sent mqtt ${decodedTopic.command} command for ${
decodedTopic.type
}, ${deviceName} (${decodedTopic.id}). ${
decodedTopic.command === 'availability' ? messageString : ''
}`,
);
}
break;
case 'state':
case 'config':
case 'availability':
logger.verbose(
`Sent mqtt ${decodedTopic.command} command for ${
decodedTopic.type
}, ${deviceName} (${decodedTopic.id}). ${
decodedTopic.command === 'availability' ? messageString : ''
}`,
);
break;
default:
logger.verbose(`Warning: Unknown command ${decodedTopic.command} in decoded topic`);
break;
default:
logger.verbose(`Warning: Unknown command ${decodedTopic.command} in decoded topic`);
}
} else {
logger.verbose(
`Warning: Got unrecognized mqtt command on '${topic}': ${message.toString()}`,
);
}
} else {
logger.verbose(
`Warning: Got unrecognized mqtt command on '${topic}': ${message.toString()}`,
);
}
} catch (err) {
logger.error(`Error processing mqtt message on topic ${topic}`, err);
}
});
}
Expand All @@ -181,6 +190,10 @@ class MqttClient extends EventEmitter {
this.client.reconnect();
}

cleanup() {
this.client.removeAllListeners();
}

disconnect(callback) {
this.deviceRegistry.allDevices.forEach((device) => {
this.client.publish(getAvailabilityTopic(device), 'offline');
Expand Down
Loading