Compare commits
5 Commits
1.1.0
...
5dc28b2409
Author | SHA1 | Date | |
---|---|---|---|
5dc28b2409 | |||
cdab6327f5 | |||
1121dbc52c | |||
bc2208b5cb | |||
845071c274 |
36
.drone.yml
36
.drone.yml
@ -18,7 +18,39 @@ steps:
|
||||
- npm install
|
||||
- npm run build
|
||||
|
||||
- name: publish
|
||||
- name: version
|
||||
image: node
|
||||
commands:
|
||||
- export version=`node -p "require('./package.json').version"`
|
||||
- export commit=`echo $DRONE_COMMIT | cut -c1-5`
|
||||
- npm version prerelease --preid=$commit --git-tag-version=false --allow-same-version=true
|
||||
when:
|
||||
event:
|
||||
exclude:
|
||||
- tag
|
||||
- pull_request
|
||||
branch:
|
||||
include:
|
||||
- master
|
||||
|
||||
- name: publish pre
|
||||
image: plugins/npm
|
||||
settings:
|
||||
username: admin
|
||||
password:
|
||||
from_secret: npm_password
|
||||
email: brandon@watsonlabs.net
|
||||
registry: "http://linuxhost.me:4873/"
|
||||
when:
|
||||
event:
|
||||
exclude:
|
||||
- tag
|
||||
- pull_request
|
||||
branch:
|
||||
include:
|
||||
- master
|
||||
|
||||
- name: publish tagged version
|
||||
image: plugins/npm
|
||||
settings:
|
||||
username: admin
|
||||
@ -29,6 +61,8 @@ steps:
|
||||
when:
|
||||
event:
|
||||
- tag
|
||||
exclude:
|
||||
- pull_request
|
||||
|
||||
notify:
|
||||
image: drillster/drone-email
|
||||
|
22
deploy.sh
22
deploy.sh
@ -1,22 +0,0 @@
|
||||
#!/bin/bash
|
||||
remote_user="bmw"
|
||||
remote_server="linuxhost.me"
|
||||
deploy_location="/home/bmw/homebridge-harmony-control"
|
||||
homebridge_location="/var/lib/homebridge/"
|
||||
|
||||
#build
|
||||
tsc --build
|
||||
#copy files to remote machine
|
||||
scp -r bin $remote_user@$remote_server:$deploy_location
|
||||
scp package.json $remote_user@$remote_server:$deploy_location
|
||||
|
||||
#install package
|
||||
ssh -t $remote_user@$remote_server "sudo npm install -g --unsafe-perm $deploy_location"
|
||||
|
||||
#restart service
|
||||
ssh -t
|
||||
ssh -t $remote_user@$remote_server "sudo systemctl restart homebridge.service"
|
||||
ssh -t $remote_user@$remote_server "sudo systemctl status homebridge.service"
|
||||
|
||||
echo done
|
||||
exit
|
@ -3,5 +3,8 @@
|
||||
{
|
||||
"path": "."
|
||||
}
|
||||
]
|
||||
],
|
||||
"settings": {
|
||||
"editor.tabSize": 2
|
||||
}
|
||||
}
|
@ -75,23 +75,28 @@ export class DeviceButton {
|
||||
}
|
||||
|
||||
//Execute command
|
||||
if (this._device) {
|
||||
//change state if stateful
|
||||
if (this._deviceInfo.IsStateful && this._buttonState != newState) {
|
||||
this._buttonState = newState;
|
||||
await this._device.sendCommand(this._deviceInfo.ButtonName);
|
||||
} else if (!this._deviceInfo.IsStateful) {
|
||||
//Send the number of configured key presses
|
||||
for (let i = 0; i < this._deviceInfo.NumberOfKeyPresses; i++) {
|
||||
await this._device.sendCommand(this._deviceInfo.ButtonName);
|
||||
}
|
||||
this._switchService
|
||||
.getCharacteristic(this._platform.Characteristic.On)
|
||||
.updateValue(false);
|
||||
return callback(new Error("Normal Response"));
|
||||
}
|
||||
if (!this._device) {
|
||||
return callback();
|
||||
}
|
||||
|
||||
//change state if stateful
|
||||
if (this._deviceInfo.IsStateful && this._buttonState != newState) {
|
||||
this._buttonState = newState;
|
||||
await this._device.sendCommand(this._deviceInfo.ButtonName);
|
||||
return callback();
|
||||
} else if (!this._deviceInfo.IsStateful) {
|
||||
//Send the number of configured key presses
|
||||
for (let i = 0; i < this._deviceInfo.NumberOfKeyPresses; i++) {
|
||||
await this._device.sendCommand(this._deviceInfo.ButtonName);
|
||||
}
|
||||
|
||||
this._switchService
|
||||
.getCharacteristic(this._platform.Characteristic.On)
|
||||
.updateValue(false);
|
||||
|
||||
this._buttonState = false;
|
||||
return callback();
|
||||
}
|
||||
return callback();
|
||||
};
|
||||
|
||||
/**
|
||||
|
105
src/Accessories/Sequence.ts
Normal file
105
src/Accessories/Sequence.ts
Normal file
@ -0,0 +1,105 @@
|
||||
import {
|
||||
CharacteristicGetCallback,
|
||||
CharacteristicSetCallback,
|
||||
CharacteristicValue,
|
||||
PlatformAccessory,
|
||||
Service,
|
||||
} from "homebridge";
|
||||
import HarmonyDataProvider from "../DataProviders/HarmonyDataProvider";
|
||||
import { ISequence } from "../Models/Config/ISequence";
|
||||
import { HarmonyDevice } from "../Models/HarmonyDevice";
|
||||
import { Platform } from "../platform";
|
||||
import { sleep } from "../Util";
|
||||
|
||||
export class Sequence {
|
||||
private _devices: { [deviceName: string]: HarmonyDevice };
|
||||
private _switchService: Service;
|
||||
|
||||
constructor(
|
||||
private readonly _platform: Platform,
|
||||
private readonly _accessory: PlatformAccessory,
|
||||
private _dataProvider: HarmonyDataProvider,
|
||||
private _sequence: ISequence
|
||||
) {
|
||||
this._accessory
|
||||
.getService(this._platform.Service.AccessoryInformation)!
|
||||
.setCharacteristic(
|
||||
this._platform.Characteristic.Manufacturer,
|
||||
"Brandon Watson"
|
||||
)
|
||||
.setCharacteristic(this._platform.Characteristic.Model, "Sequence Button")
|
||||
.setCharacteristic(
|
||||
this._platform.Characteristic.SerialNumber,
|
||||
"123-456-789"
|
||||
);
|
||||
|
||||
const switchUUID = this._platform.api.hap.uuid.generate(
|
||||
`${this._accessory.displayName} Switch`
|
||||
);
|
||||
|
||||
this._switchService =
|
||||
this._accessory.getService(this._platform.Service.Switch) ||
|
||||
this._accessory.addService(
|
||||
this._platform.Service.Switch,
|
||||
this._accessory.displayName,
|
||||
switchUUID
|
||||
);
|
||||
|
||||
this._switchService
|
||||
.getCharacteristic(this._platform.Characteristic.On)
|
||||
.on("set", this.onSwitchSet)
|
||||
.updateValue(false)
|
||||
.on("get", (callback: CharacteristicGetCallback): void => {
|
||||
return callback(null);
|
||||
});
|
||||
|
||||
this._devices = {};
|
||||
// Get devices in sequence
|
||||
for (const deviceName of _sequence.Steps.map((e) => e.DeviceName)) {
|
||||
if (!deviceName) {
|
||||
continue;
|
||||
}
|
||||
const device = this._dataProvider.getDeviceFromName(deviceName);
|
||||
if (device) {
|
||||
this._devices[deviceName] = device;
|
||||
} else {
|
||||
this._platform.log.warn(
|
||||
`Device ${deviceName} was not found in harmony configuration`
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Handler for switchSet command
|
||||
* @param callback
|
||||
*/
|
||||
public onSwitchSet = async (
|
||||
_value: CharacteristicValue,
|
||||
callback: CharacteristicSetCallback
|
||||
): Promise<void> => {
|
||||
// Execute sequence
|
||||
for (const step of this._sequence.Steps) {
|
||||
await sleep(step.Delay);
|
||||
const device: HarmonyDevice = this._devices[step.DeviceName ?? ""];
|
||||
if (
|
||||
device &&
|
||||
step.DeviceCommand &&
|
||||
device.supportsCommand(step.DeviceCommand)
|
||||
) {
|
||||
await device.sendCommand(step.DeviceCommand);
|
||||
} else {
|
||||
this._platform.log.warn(
|
||||
`Attempted to execute command ${step.DeviceCommand} on device ${step.DeviceName} but the device or command was not found`
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
// Deactivate button
|
||||
this._switchService
|
||||
.getCharacteristic(this._platform.Characteristic.On)
|
||||
.updateValue(false);
|
||||
|
||||
callback(null);
|
||||
};
|
||||
}
|
@ -2,19 +2,21 @@ import { IMatrix } from "./IMatrix";
|
||||
import { IActivity } from "./IActivity";
|
||||
import { IDeviceButton } from "./IDeviceButton";
|
||||
import { IDeviceConfig } from "./IDeviceConfig";
|
||||
import { IHub } from './IHub';
|
||||
import { IHub } from "./IHub";
|
||||
import { ISequence } from "./ISequence";
|
||||
|
||||
export interface IControlUnit {
|
||||
DisplayName: string;
|
||||
Activities: Array<IActivity>;
|
||||
DisplayName: string;
|
||||
Activities: Array<IActivity>;
|
||||
}
|
||||
|
||||
export interface IConfig {
|
||||
hubIp: string;
|
||||
EmitDevicesOnStartup: boolean;
|
||||
Matrix: IMatrix;
|
||||
ControlUnits: Array<IControlUnit>;
|
||||
DeviceButtons: Array<IDeviceButton>;
|
||||
Devices: Array<IDeviceConfig>;
|
||||
Hubs: Array<IHub>;
|
||||
}
|
||||
hubIp: string;
|
||||
EmitDevicesOnStartup: boolean;
|
||||
Matrix: IMatrix;
|
||||
ControlUnits: Array<IControlUnit>;
|
||||
DeviceButtons: Array<IDeviceButton>;
|
||||
Sequences: Array<ISequence>;
|
||||
Devices: Array<IDeviceConfig>;
|
||||
Hubs: Array<IHub>;
|
||||
}
|
||||
|
10
src/Models/Config/ISequence.ts
Normal file
10
src/Models/Config/ISequence.ts
Normal file
@ -0,0 +1,10 @@
|
||||
export interface ISequence {
|
||||
DisplayName: string;
|
||||
Steps: Array<IStep>;
|
||||
}
|
||||
|
||||
export interface IStep {
|
||||
DeviceName?: string;
|
||||
DeviceCommand?: string;
|
||||
Delay: number;
|
||||
}
|
@ -8,11 +8,14 @@ import {
|
||||
Service,
|
||||
} from "homebridge";
|
||||
import { ControlUnit, DeviceButton } from "./Accessories";
|
||||
import { Sequence } from "./Accessories/Sequence";
|
||||
import HarmonyDataProvider from "./DataProviders/HarmonyDataProvider";
|
||||
import { IConfig, IControlUnit, IDeviceButton } from "./Models/Config";
|
||||
import { ISequence } from "./Models/Config/ISequence";
|
||||
import { HarmonyDevice } from "./Models/HarmonyDevice";
|
||||
import { HarmonyHub } from "./Models/HarmonyHub";
|
||||
import { PLATFORM_NAME, PLUGIN_NAME } from "./settings";
|
||||
import { sleep } from "./Util";
|
||||
|
||||
export class Platform implements DynamicPlatformPlugin {
|
||||
constructor(
|
||||
@ -22,7 +25,7 @@ export class Platform implements DynamicPlatformPlugin {
|
||||
) {
|
||||
this.log.debug("Finished initializing platform:", config.name);
|
||||
|
||||
this.config = (config as unknown) as IConfig;
|
||||
this.config = config as unknown as IConfig;
|
||||
//construct data provider
|
||||
const dataProvider = new HarmonyDataProvider({
|
||||
hubs: this.config.Hubs,
|
||||
@ -31,9 +34,10 @@ export class Platform implements DynamicPlatformPlugin {
|
||||
log: this.log,
|
||||
});
|
||||
|
||||
let didFinishLaunching = false;
|
||||
this.api.on("didFinishLaunching", async () => {
|
||||
log.debug("Executed didFinishLaunching callback");
|
||||
this.discoverDevices(dataProvider);
|
||||
didFinishLaunching = true;
|
||||
});
|
||||
|
||||
this.dataProvider = null;
|
||||
@ -51,6 +55,9 @@ export class Platform implements DynamicPlatformPlugin {
|
||||
|
||||
this.dataProvider.on("Ready", () => {
|
||||
this.log.info("All hubs connected");
|
||||
this.discoverControlUnitAccessories(dataProvider);
|
||||
this.discoverDeviceButtonAccessories(dataProvider);
|
||||
this.discoverSequenceAccessories(dataProvider);
|
||||
if (this.config.EmitDevicesOnStartup) {
|
||||
const hubs = this.dataProvider!.hubs;
|
||||
Object.values(hubs).forEach((hub: HarmonyHub) => {
|
||||
@ -70,18 +77,25 @@ export class Platform implements DynamicPlatformPlugin {
|
||||
}
|
||||
|
||||
public readonly Service: typeof Service = this.api.hap.Service;
|
||||
public readonly Characteristic: typeof Characteristic = this.api.hap
|
||||
.Characteristic;
|
||||
public readonly Characteristic: typeof Characteristic =
|
||||
this.api.hap.Characteristic;
|
||||
|
||||
// this is used to track restored cached accessories
|
||||
public readonly accessories: PlatformAccessory[] = [];
|
||||
public config: IConfig;
|
||||
public dataProvider: HarmonyDataProvider | null;
|
||||
|
||||
public discoverDevices(dataProvider: HarmonyDataProvider) {
|
||||
/**
|
||||
* Discover new control unit accessories
|
||||
* @param dataProvider
|
||||
*/
|
||||
private discoverControlUnitAccessories(
|
||||
dataProvider: HarmonyDataProvider
|
||||
): void {
|
||||
this.config.ControlUnits.forEach((unit: IControlUnit) => {
|
||||
const uuid = this.api.hap.uuid.generate(unit.DisplayName);
|
||||
const existingAccessory = this.accessories.find((e) => e.UUID === uuid);
|
||||
|
||||
if (existingAccessory) {
|
||||
this.log.info(
|
||||
"Restoring existing accessory from cache: " +
|
||||
@ -106,7 +120,15 @@ export class Platform implements DynamicPlatformPlugin {
|
||||
console.log("Publishing external accessory: " + uuid);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Discover new device button accessories
|
||||
* @param dataProvider
|
||||
*/
|
||||
private discoverDeviceButtonAccessories(
|
||||
dataProvider: HarmonyDataProvider
|
||||
): void {
|
||||
this.config.DeviceButtons.forEach((button: IDeviceButton) => {
|
||||
const uuid = this.api.hap.uuid.generate(button.DisplayName);
|
||||
const existingAccessory = this.accessories.find((e) => e.UUID === uuid);
|
||||
@ -133,6 +155,65 @@ export class Platform implements DynamicPlatformPlugin {
|
||||
]);
|
||||
}
|
||||
});
|
||||
|
||||
// Remove old device buttons
|
||||
for (const accessory of this.accessories) {
|
||||
if (
|
||||
this.config.DeviceButtons.filter(
|
||||
(button) => button.DisplayName === accessory.displayName
|
||||
).length === 0
|
||||
) {
|
||||
this.api.unregisterPlatformAccessories(PLUGIN_NAME, PLATFORM_NAME, [
|
||||
accessory,
|
||||
]);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Discover new sequence accessories
|
||||
* @param dataProvider
|
||||
*/
|
||||
public discoverSequenceAccessories(dataProvider: HarmonyDataProvider): void {
|
||||
this.config.Sequences.forEach((sequence: ISequence) => {
|
||||
const uuid = this.api.hap.uuid.generate(sequence.DisplayName);
|
||||
const existingAccessory = this.accessories.find((e) => e.UUID === uuid);
|
||||
if (existingAccessory) {
|
||||
this.log.info(
|
||||
"Restoring existing accessory from cache: " +
|
||||
existingAccessory.displayName
|
||||
);
|
||||
|
||||
new Sequence(this, existingAccessory, dataProvider, sequence);
|
||||
this.api.updatePlatformAccessories([existingAccessory]);
|
||||
} else {
|
||||
this.log.info("Adding new accessory: " + sequence.DisplayName);
|
||||
const accessory = new this.api.platformAccessory(
|
||||
sequence.DisplayName,
|
||||
uuid
|
||||
);
|
||||
accessory.context["DeviceName"] = sequence.DisplayName;
|
||||
|
||||
new Sequence(this, accessory, dataProvider, sequence);
|
||||
|
||||
this.api.registerPlatformAccessories(PLUGIN_NAME, PLATFORM_NAME, [
|
||||
accessory,
|
||||
]);
|
||||
}
|
||||
});
|
||||
|
||||
// Remove old device buttons
|
||||
for (const accessory of this.accessories) {
|
||||
if (
|
||||
this.config.Sequences.filter(
|
||||
(sequence) => sequence.DisplayName === accessory.displayName
|
||||
).length === 0
|
||||
) {
|
||||
this.api.unregisterPlatformAccessories(PLUGIN_NAME, PLATFORM_NAME, [
|
||||
accessory,
|
||||
]);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
configureAccessory(accessory: PlatformAccessory<Record<string, any>>): void {
|
||||
|
Reference in New Issue
Block a user