Skip to content

Commit

Permalink
Merge pull request #12 from ronnyf/ronnyf/statefixes
Browse files Browse the repository at this point in the history
state fixes and qos and other stuff
  • Loading branch information
ronnyf authored Sep 30, 2024
2 parents 46aae20 + a6f728c commit 7b0e77e
Show file tree
Hide file tree
Showing 7 changed files with 91 additions and 53 deletions.
3 changes: 2 additions & 1 deletion package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

5 changes: 3 additions & 2 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@
"private": false,
"displayName": "MQTT garage door opener",
"name": "homebridge-garagedoor-mqtt",
"version": "2.0.2",
"version": "2.0.3",
"description": "A Homebridge plugin for MQTT and a custom (RFxLabs) esp8266 firmware",
"license": "Apache-2.0",
"repository": {
Expand Down Expand Up @@ -31,7 +31,8 @@
"door"
],
"dependencies": {
"mqtt": "^5.8.1"
"mqtt": "^5.8.1",
"mqtt-packet": "^9.0.0"
},
"devDependencies": {
"@types/node": "^20.10.0",
Expand Down
20 changes: 13 additions & 7 deletions src/garageclient.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,6 @@
import mqtt, { IClientOptions, IClientSubscribeOptions, ISubscriptionGrant, MqttClient, OnMessageCallback } from 'mqtt';
// eslint-disable-next-line max-len
import mqtt, { IClientOptions, IClientPublishOptions, IClientSubscribeOptions, ISubscriptionGrant, MqttClient, OnMessageCallback } from 'mqtt';
import { QoS } from 'mqtt-packet';

export class GarageMQTT {

Expand Down Expand Up @@ -29,8 +31,8 @@ export class GarageMQTT {
return this.client;
}

async addSubscription(topic: string | string[]): Promise<ISubscriptionGrant[]> {
const opts: IClientSubscribeOptions = { qos: 0 }; // at most once (1 would be ok would be multiple times)
async addSubscription(topic: string | string[], qos: QoS = 0): Promise<ISubscriptionGrant[]> {
const opts: IClientSubscribeOptions = { qos: qos }; // at most once (1 would be ok would be multiple times)
// qos: 1 means we want the message to arrive at least once but don't care if it arrives twice (or more
return this.client.subscribeAsync(topic, opts);
}
Expand All @@ -39,14 +41,18 @@ export class GarageMQTT {
this.client.on('message', callback);
}

publish(topic: string, message: string) {
this.client.publish(topic, message);
publish(topic: string, message: string, opts: IClientPublishOptions) {
this.client.publish(topic, message, opts);
}

publishValue(topic: string, value: number) {
publishValue(topic: string, value: number, qos: QoS = 0, retain: boolean = false) {
const stringValue = String(value);
if (stringValue !== undefined) {
this.publish(topic, stringValue);
const opts: IClientPublishOptions = {
qos: qos,
retain: retain,
};
this.publish(topic, stringValue, opts);
}
}

Expand Down
35 changes: 29 additions & 6 deletions src/garagestate.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,20 +7,22 @@ export class GarageState extends EventEmitter {
private readonly Characteristic: typeof Characteristic;
private readonly emitter: EventEmitter;

constructor(current: number, api: API) {
constructor(api: API) {
super();
this.Characteristic = api.hap.Characteristic;
this.emitter = new EventEmitter();
this.current = current;
this.target = this.targetDoorStateForCurrent(current);
this.current = -1;
this.target = -1;
}

public updateCurrentState(current: number) {
public updateCurrentState(current: number, emit: boolean = true) {
if (this.current === current) {
return;
}
this.current = current;
this.emit('current', current);
if (emit === true) {
this.emit('current', current);
}
}

public updateTargetState(target: number, emit: boolean = true) {
Expand All @@ -41,6 +43,27 @@ export class GarageState extends EventEmitter {
return this.target;
}

public getCurrentDescription(): string {
switch (this.current) {
case this.Characteristic.CurrentDoorState.OPEN:
return 'Open';

case this.Characteristic.CurrentDoorState.CLOSED:
return 'Closed';

case this.Characteristic.CurrentDoorState.OPENING:
return 'Opening';

case this.Characteristic.CurrentDoorState.CLOSING:
return 'Closing';

case this.Characteristic.CurrentDoorState.STOPPED:
return 'Stopped';
}

return 'unknown';
}

public targetDoorStateForCurrent(value: number): number {
switch (value) {
case this.Characteristic.CurrentDoorState.OPEN:
Expand All @@ -61,6 +84,6 @@ export class GarageState extends EventEmitter {
}

public description(): string {
return 'current: ' + this.current + ', target: ' + this.target;
return 'current: ' + this.getCurrentDescription() + ', target: ' + this.target;
}
}
47 changes: 23 additions & 24 deletions src/platform.ts
Original file line number Diff line number Diff line change
Expand Up @@ -31,7 +31,7 @@ export class GarageDoorOpenerPlatform implements DynamicPlatformPlugin {
this.garageClient = null;

// start with closed state
this.garageState = new GarageState(api.hap.Characteristic.CurrentDoorState.CLOSED, api);
this.garageState = new GarageState(api);

this.log.debug('Finished initializing platform:', this.config.name);

Expand Down Expand Up @@ -88,6 +88,8 @@ export class GarageDoorOpenerPlatform implements DynamicPlatformPlugin {
}

async discoverDevices() {
this.log.debug('discovering devices...');

const mqttUsername = this.config['mqttUsername'];
const mqttPassword = this.config['mqttPassword'];
const clientID = this.config['mqttClientID'] ?? 'GarageMQTT';
Expand All @@ -96,13 +98,6 @@ export class GarageDoorOpenerPlatform implements DynamicPlatformPlugin {
const client = await this.connectAndSubscribe(clientID, mqttUsername, mqttPassword, mqttHost);
// at this point we should be connected and should have established a connection...
this.garageClient = client;

//for closed, target and current point to the same value (1)
this.garageState.updateCurrentState(this.getCurrentDoorStateClosed());
this.garageState.updateTargetState(this.getCurrentDoorStateClosed());

// let's assume closed as the initial state
this.initializeAccessory();
}

async connectAndSubscribe(clientID: string, username: string, password: string, host: string): Promise<GarageMQTT> {
Expand All @@ -118,26 +113,25 @@ export class GarageDoorOpenerPlatform implements DynamicPlatformPlugin {
}

async receiveMessage(topic: string, payload: Buffer) {
this.log.debug('received topic: ', topic, 'payload: ', payload);
if (this.garageAccessory === null) {
this.log.debug('garageAccessory is null, initializing...');
this.initializeAccessory();
}

this.handleTopic(topic, payload);
}

handleTopic(topic: string, payload: Buffer) {
const stringValue = payload.toString('ascii');

this.log.debug('received topic: ', topic, 'payload: ', stringValue);
switch (topic) {
case this.getCurrentTopic():
{
const value = this.mapCurrentDoorState(stringValue);
if (value > -1) {
const old = this.garageAccessory?.getCurrentDoorState();
this.garageState.updateCurrentState(value);
this.log.debug('did update from current state: ', old, ' to:', value);
this.log.debug('did update to current state: ', value);

if (this.garageState.getTargetState() < 0) {
this.log.debug('quietly updating target state');
const mappedTargetState = this.garageState.targetDoorStateForCurrent(value);
this.garageState.updateTargetState(mappedTargetState, false);
if (this.garageAccessory === null) {
this.initializeAccessory();
}
}

} else {
this.log.error('unknown door state value ', value, ' for payload: ', stringValue);
}
Expand All @@ -164,14 +158,14 @@ export class GarageDoorOpenerPlatform implements DynamicPlatformPlugin {

initializeAccessory() {
if (this.garageAccessory !== null) {
this.log.error('Accessory already initialized');
this.log.debug('Accessory already initialized');
return;
}

this.log.debug('initializing Garage Door Opener Accessory...');

const deviceID = 'GD01';
const deviceDisplayName = 'Garage Door Opener';
const deviceID = 'GDC01';
const deviceDisplayName = 'Garage Door';

// generate a unique id for the accessory this should be generated from
// something globally unique, but constant, for example, the device serial
Expand Down Expand Up @@ -252,4 +246,9 @@ export class GarageDoorOpenerPlatform implements DynamicPlatformPlugin {
return -1;
}
}

publishTargetDoorState(value: number) {
this.log.debug('publishing target door state: ', value, ' to topic: ', this.getTargetTopic());
this.garageClient?.publishValue(this.getTargetTopic(), value);
}
}
32 changes: 20 additions & 12 deletions src/platformAccessory.ts
Original file line number Diff line number Diff line change
Expand Up @@ -15,9 +15,9 @@ export class GarageDoorOpenerAccessory {

// set accessory information
this.accessory.getService(this.platform.Service.AccessoryInformation)!
.setCharacteristic(this.platform.Characteristic.Manufacturer, 'Default-Manufacturer')
.setCharacteristic(this.platform.Characteristic.Model, 'Default-Model')
.setCharacteristic(this.platform.Characteristic.SerialNumber, 'Default-Serial');
.setCharacteristic(this.platform.Characteristic.Manufacturer, 'RFx Software Inc.')
.setCharacteristic(this.platform.Characteristic.Model, 'GDC-1')
.setCharacteristic(this.platform.Characteristic.SerialNumber, '100001');

// get the LightBulb service if it exists, otherwise create a new LightBulb service
// you can create multiple services for each accessory
Expand All @@ -42,28 +42,32 @@ export class GarageDoorOpenerAccessory {


garageState.on('current', (currentValue) => {
if (this.currentDoorStateCharacteristic()?.value === currentValue) {
return;
this.platform.log.debug('emitted [current] value: ', currentValue);
if (currentValue >= 0 && currentValue < 5) {
this.currentDoorStateCharacteristic().updateValue(currentValue);
} else {
this.platform.log.warn('ignoring current value update: ', currentValue);
}
this.currentDoorStateCharacteristic()?.updateValue(currentValue);
});

garageState.on('target', (targetValue) => {
if (this.targetDoorStateCharacteristic()?.value === targetValue) {
return;
this.platform.log.debug('emitted [target] value: ', targetValue);
if (targetValue >= 0 && targetValue < 2) {
this.targetDoorStateCharacteristic()?.updateValue(targetValue);
}else {
this.platform.log.warn('ignoring target value update: ', targetValue);
}
this.targetDoorStateCharacteristic()?.updateValue(targetValue);
});

this.platform.log.debug('initial door states: ', this.garageState.description());
this.garageState = garageState;
}

currentDoorStateCharacteristic(): Characteristic | undefined {
currentDoorStateCharacteristic(): Characteristic {
return this.service.getCharacteristic(this.platform.Characteristic.CurrentDoorState);
}

targetDoorStateCharacteristic(): Characteristic | undefined {
targetDoorStateCharacteristic(): Characteristic {
return this.service.getCharacteristic(this.platform.Characteristic.TargetDoorState);
}

Expand All @@ -72,9 +76,13 @@ export class GarageDoorOpenerAccessory {
}

async setTargetDoorState(value: CharacteristicValue) {
// publish an mqtt message if necessary
if (this.garageState.getTargetState() === value) {
return;
}

this.garageState.updateTargetState(value as number);
this.platform.log.debug('Set TargetDoorState ->', this.garageState.description());
this.platform.publishTargetDoorState(value as number);
}

async getTargetDoorState(): Promise<CharacteristicValue> {
Expand Down
2 changes: 1 addition & 1 deletion src/version.ts
Original file line number Diff line number Diff line change
@@ -1 +1 @@
export const PLUGIN_VERSION = '2.0.2';
export const PLUGIN_VERSION = '2.0.3';

0 comments on commit 7b0e77e

Please sign in to comment.