From 5d4f7674fa5a5bccbedaf1106631ff2247457b8a Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?Lo=C3=AFc?= <1536036+zoic21@users.noreply.github.com>
Date: Sun, 19 Jan 2025 23:55:43 +0100
Subject: [PATCH] Support VR ipx800V4 + poll interval + url force refresh (#1)
* begin get vr pos
* add poll interval
* add webhook handler
* add install script
* bugfix
* fix
* test
* test
* same
* test
* add more log
* fix
* add log
* add log
* add log
* test
* test
* test
* add log
* typoi
* test
* fix
* remove log
* try
* typo
* remove log
* add log
* fix log
* test
* test
* bugfix
* test
* test real time update position
* typo
* bugfix
* test
* same
* test
* add log
* test
* add log
* bugfix
* bugfix
* bugfix
* bugfix
* test
* test
* test
* test
* test
* test
* improvement
* improvement
* update readme
* improve chache restore
* add more log
* test
* test
* bugfix
* test
* add more log
* test
* test
* add log
* test
* test
* remove log
* test
* test
* revert
* remove const
* add more config
* test
* test
* fix
* typo
* add log
* change log level
* remove log
* improve code
* add name in place of model
* Update gradual.ts
* Update package.json
* Update install.sh
* Update install.sh
* Update install.sh
* Update install.sh
* bugfix
* bugfix
* test
* test
* test
* remove get (not usefull)
* test
* test verify result from ipx
* bugfix
* same
* bugfix
* test
* test simulate error
* typo debug log
* bugfix
* remove simulation of error
* Update ipxV4.ts
* add more log
* Update ipxV4.ts
* Update ipxV4.ts
* typo
* add more log
* Update ipxV4.ts
* test
* test
* bugfix
* test
* test
* test
* fix
* typo
* fix
* test
* test
* test
* add more log
* add more delay
* test
* test
* test
* test
* bugfix
* test
* same
* test
* test
* test
* test
* typo
* improvements
* test
* add log
* reduce ipx call
* test
* test
* test
* test
* fix
* test
* test
* test
* fix
* typo
* test
* improve code
* update readme
* test
* add verfiy on vr
* test
* bugfix
* improve timming
---
.vscode/settings.json | 2 +-
README.md | 47 ++++---
config.schema.json | 11 ++
install.sh | 8 ++
package.json | 2 +-
src/conf-definition/api.d.ts | 4 +-
src/conf-definition/relays.d.ts | 3 +-
src/config.d.ts | 6 +
src/device/analogInput.ts | 14 +--
src/device/gradual.ts | 30 +++--
src/device/input.ts | 8 +-
src/device/relay.ts | 18 ++-
src/ipx/api.ts | 3 +-
src/ipx/ipxV4.ts | 209 +++++++++++++++++++++++++-------
src/ipx/ipxV5.ts | 49 ++++----
src/platform.ts | 90 ++++++--------
src/webhookServer.ts | 35 ++++++
17 files changed, 364 insertions(+), 175 deletions(-)
create mode 100644 install.sh
create mode 100644 src/webhookServer.ts
diff --git a/.vscode/settings.json b/.vscode/settings.json
index 31b20d18..89d2e29c 100644
--- a/.vscode/settings.json
+++ b/.vscode/settings.json
@@ -1,7 +1,7 @@
{
"files.eol": "\n",
"editor.codeActionsOnSave": {
- "source.fixAll.eslint": true
+ "source.fixAll.eslint": "explicit"
},
"editor.rulers": [ 140 ],
"eslint.enable": true
diff --git a/README.md b/README.md
index 5fe5ea17..23ce9959 100644
--- a/README.md
+++ b/README.md
@@ -1,12 +1,3 @@
-
-
-
-
-
-
-
-
-
# Homebridge IPX800 Plugin
This plugin brings support of IPX800 to homekit.
@@ -18,14 +9,12 @@ As now it support different devices :
* analog inputs (ipx, x-thl)
2. ipx v4
* relays
- * gradual (x-dimmer, x4Vr) without state update
+ * gradual (x-dimmer, x4Vr)
* analog inputs (ipx, x-thl)
This is heavily based on the hombridge plateform template it may let you control your ipx800 relays.
-
-
## Install Development Dependencies
Using a terminal on the computer running homebridge:
@@ -34,39 +23,38 @@ Using a terminal on the computer running homebridge:
#clone plugin
git clone https://github.com/Adrien-B/ipx800.git
-
# install dependency
cd ipx800
-npm install
-sudo npm install -g typescript rimraf
-
-# build and link plugin
-npm run build
-npm link #or sudo npm link
+Chmod +x install.sh;./install.sh
-
-#(re)start homebridge if not done already
homebridge -D
```
-
## Configure the plugin
In homebridge set the ipx api settings
* ip
* api-key
* version
+* pollInterval
+* webhookPort
+* webhookPath
See the following json snippet exemple:
```
"api": {
- "ip": "*.*.*.*",
- "key": "*",
- "version": "v5"
- },
+ "ip": "*.*.*.*",
+ "key": "*",
+ "version": "v5",
+ "pollInterval": 60,
+ "webhookPort": 58698,
+ "webhookPath": "TODO"
+ },
```
+After this configuration you can trigger a refresh of state by calling https://IP_HOMEBRIDGE:webhookPort/webhookPath, you can for exemple add this in ipx800v4 push to trigger an update when relay state change
+
### Configure v5 devices
Than add all your devices (relays, dimmer, inputs).
See the following json snippet exemple for v5:
@@ -128,6 +116,13 @@ See the following json snippet exemple for v5:
"index": "r3"
}
],
+ "graduals": [
+ {
+ "displayName": "corridor",
+ "type": "covering",
+ "anaIndex": "VR02"
+ }
+ ],
"analogInputs": [
{
"displayName": "séjour",
diff --git a/config.schema.json b/config.schema.json
index bed0d5ac..09330cb5 100644
--- a/config.schema.json
+++ b/config.schema.json
@@ -18,6 +18,17 @@
"default": "v4",
"enum": ["v4", "v5"],
"required": true
+ },
+ "pollInterval": {
+ "type": "number",
+ "default": 60
+ },
+ "webhookPort": {
+ "type": "number"
+ },
+ "webhookPath": {
+ "type": "string",
+ "default": ""
}
}
},
diff --git a/install.sh b/install.sh
new file mode 100644
index 00000000..2bc869e4
--- /dev/null
+++ b/install.sh
@@ -0,0 +1,8 @@
+git pull
+npm install
+npm run build
+npm link
+npm pack
+mv homebridge-ipx800-1.0.0.tgz /homebridge
+cd /homebridge
+npm install homebridge-ipx800-1.0.0.tgz
diff --git a/package.json b/package.json
index 5b76d5b5..3f4fbd7d 100644
--- a/package.json
+++ b/package.json
@@ -7,7 +7,7 @@
"license": "Apache-2.0",
"repository": {
"type": "git",
- "url": "https://github.com/Adrien-B/ipx800"
+ "url": "https://github.com/zoic21/homebridge-ipx800.git"
},
"bugs": {
"url": "https://github.com/Adrien-B/ipx800/issues"
diff --git a/src/conf-definition/api.d.ts b/src/conf-definition/api.d.ts
index d1955d5b..daa95f19 100644
--- a/src/conf-definition/api.d.ts
+++ b/src/conf-definition/api.d.ts
@@ -9,5 +9,7 @@ export interface Api {
ip: string;
key: string;
version: "v4" | "v5";
-
+ pollInterval: number;
+ webhookPort: number;
+ webhookPath: string;
}
diff --git a/src/conf-definition/relays.d.ts b/src/conf-definition/relays.d.ts
index ac28ac0d..c476fa3b 100644
--- a/src/conf-definition/relays.d.ts
+++ b/src/conf-definition/relays.d.ts
@@ -10,10 +10,11 @@ export type OutletFrPrise = "outlet";
export type FanFrVentilation = "fan";
export type SwitchFrInterrupteur = "switch";
export type ButtonFrPoussoir = "bswitch";
+export type ToggleFrPoussoir = "toggle";
export type Valve = "valve";
export type Relays = {
displayName: string;
- type: (LightFrLumiere | OutletFrPrise | FanFrVentilation | SwitchFrInterrupteur | ButtonFrPoussoir | Valve) & string;
+ type: (LightFrLumiere | OutletFrPrise | FanFrVentilation | SwitchFrInterrupteur | ButtonFrPoussoir | Valve | ToggleFrPoussoir) & string;
index: string;
};
diff --git a/src/config.d.ts b/src/config.d.ts
index a7b4dcba..8cd5869a 100644
--- a/src/config.d.ts
+++ b/src/config.d.ts
@@ -7,6 +7,9 @@
export type IpxIp = string;
export type IpxApiKey = string;
+export type IpxPollInterval = number;
+export type IpxWebhookPort = number;
+export type IpxWebhookPath = string;
export type IpxVersion = IpxVersion1 & IpxVersion2;
export type IpxVersion1 = V4 | V5;
export type V4 = "v4";
@@ -17,5 +20,8 @@ export interface IpxApiConfiguration {
ip?: IpxIp;
key?: IpxApiKey;
version?: IpxVersion;
+ pollInterval?: IpxPollInterval;
+ webhookPort?: IpxWebhookPort;
+ webhookPath?: IpxWebhookPath;
[k: string]: unknown;
}
diff --git a/src/device/analogInput.ts b/src/device/analogInput.ts
index 1bf696be..e05dd4ca 100644
--- a/src/device/analogInput.ts
+++ b/src/device/analogInput.ts
@@ -15,7 +15,7 @@ export class AnalogInputHandler {
// set accessory information
this.accessory.getService(this.platform.Service.AccessoryInformation)!
.setCharacteristic(this.platform.Characteristic.Manufacturer, 'GCE-Electronic')
- .setCharacteristic(this.platform.Characteristic.Model, this.platform.model);
+ .setCharacteristic(this.platform.Characteristic.Model, accessory.context.device.displayName);
switch(accessory.context.device.type) {
case 'humidity': {
@@ -39,12 +39,12 @@ export class AnalogInputHandler {
this.service.setCharacteristic(this.platform.Characteristic.Name, accessory.context.device.displayName);
}
- async updateAnaValue(state: number){
+ async updateAnaValue(value: number){
if (this.characteristic === this.platform.Characteristic.CurrentAmbientLightLevel) {
- state = Math.max(state, 0.1);
- }// else if (this.index == "THL1-TEMP") {
- // state = (state - 1);
- // }
- this.service.updateCharacteristic(this.characteristic, state);
+ value = Math.max(value, 0.1);
+ }
+ if(this.service.getCharacteristic(this.characteristic).value != value){
+ this.service.updateCharacteristic(this.characteristic, value);
+ }
}
}
diff --git a/src/device/gradual.ts b/src/device/gradual.ts
index 26ad2171..74ffc0e7 100644
--- a/src/device/gradual.ts
+++ b/src/device/gradual.ts
@@ -9,6 +9,7 @@ export class GradualHandler {
public readonly anaIndex: string = this.accessory.context.device.anaIndex;
private service: Service;
private readonly characteristic;
+ private state;
constructor(
private readonly platform: IPXPlatform,
@@ -18,15 +19,13 @@ export class GradualHandler {
// set accessory information
this.accessory.getService(this.platform.Service.AccessoryInformation)!
.setCharacteristic(this.platform.Characteristic.Manufacturer, 'GCE-Electronic')
- .setCharacteristic(this.platform.Characteristic.Model, this.platform.model);
+ .setCharacteristic(this.platform.Characteristic.Model, accessory.context.device.displayName);
switch(accessory.context.device.type) {
case 'covering': {
- this.service = this.accessory.getService(this.platform.Service.WindowCovering) ||
- this.accessory.addService(this.platform.Service.WindowCovering);
+ this.service = this.accessory.getService(this.platform.Service.WindowCovering) || this.accessory.addService(this.platform.Service.WindowCovering);
this.service.setCharacteristic(this.platform.Characteristic.Name, accessory.context.device.displayName);
- this.service.getCharacteristic(this.platform.Characteristic.TargetPosition)
- .onSet((v) => ipx.setVRPosition(v, this.platform, this.accessory));
+ this.service.getCharacteristic(this.platform.Characteristic.TargetPosition).onSet((v) => ipx.setVRPosition(v, this.platform, this.accessory));
this.characteristic = this.platform.Characteristic.CurrentPosition; //but onSet is TargetPosition
break;
}
@@ -51,14 +50,27 @@ export class GradualHandler {
async updateAnaValue(value: number){
if (this.characteristic === this.platform.Characteristic.CurrentPosition) {
- //if curtain (xv4r) revert position
- this.service.updateCharacteristic(this.characteristic, 100 - value);
+ this.state = 100 - value;
+ if(this.service.getCharacteristic(this.characteristic).value != (100 - value)){
+ this.service.updateCharacteristic(this.characteristic, 100 - value);
+ this.service.updateCharacteristic(this.platform.Characteristic.PositionState, this.platform.Characteristic.PositionState.STOPPED);
+ this.service.updateCharacteristic(this.platform.Characteristic.TargetPosition, 100 - value);
+ }
+ if(this.service.getCharacteristic(this.platform.Characteristic.PositionState).value != this.platform.Characteristic.PositionState.STOPPED){
+ this.service.updateCharacteristic(this.platform.Characteristic.PositionState, this.platform.Characteristic.PositionState.STOPPED);
+ }
} else {
- this.service.updateCharacteristic(this.characteristic, value);
+ this.state = value;
+ if(this.service.getCharacteristic(this.characteristic).value != value){
+ this.service.updateCharacteristic(this.characteristic, value);
+ }
}
}
async updateIO(value: boolean){
- this.service.updateCharacteristic(this.platform.Characteristic.On, value);
+ this.state = value;
+ if(this.service.getCharacteristic(this.platform.Characteristic.On).value != value){
+ this.service.updateCharacteristic(this.platform.Characteristic.On, value);
+ }
}
}
diff --git a/src/device/input.ts b/src/device/input.ts
index 8b5add3b..ec14076a 100644
--- a/src/device/input.ts
+++ b/src/device/input.ts
@@ -14,7 +14,7 @@ export class InputHandler {
// set accessory information
this.accessory.getService(this.platform.Service.AccessoryInformation)!
.setCharacteristic(this.platform.Characteristic.Manufacturer, 'GCE-Electronic')
- .setCharacteristic(this.platform.Characteristic.Model, 'IPX-800');
+ .setCharacteristic(this.platform.Characteristic.Model, accessory.context.device.displayName);
switch(accessory.context.device.type) {
case 'motion': {
@@ -38,7 +38,9 @@ export class InputHandler {
this.service.setCharacteristic(this.platform.Characteristic.Name, accessory.context.device.displayName);
}
- public updateIO(state: boolean){
- this.service.updateCharacteristic(this.characteristic, state);
+ public updateIO(value: boolean){
+ if(this.service.getCharacteristic(this.characteristic).value != value){
+ this.service.updateCharacteristic(this.characteristic, value);
+ }
}
}
diff --git a/src/device/relay.ts b/src/device/relay.ts
index 768aee94..57484429 100644
--- a/src/device/relay.ts
+++ b/src/device/relay.ts
@@ -7,6 +7,7 @@ import { IpxApiCaller } from '../ipx/api';
export class RelayHandler {
public readonly index: string = this.accessory.context.device.index;
private readonly service: Service;
+ private state;
constructor(
private readonly platform: IPXPlatform,
@@ -16,13 +17,17 @@ export class RelayHandler {
// set accessory information
this.accessory.getService(this.platform.Service.AccessoryInformation)!
.setCharacteristic(this.platform.Characteristic.Manufacturer, 'GCE-Electronic')
- .setCharacteristic(this.platform.Characteristic.Model, 'IPX-800');
+ .setCharacteristic(this.platform.Characteristic.Model, accessory.context.device.displayName);
switch(accessory.context.device.type) {
case 'bswitch': {
this.service = this.accessory.getService(this.platform.Service.Switch) || this.accessory.addService(this.platform.Service.Switch);
break;
}
+ case 'toggle': {
+ this.service = this.accessory.getService(this.platform.Service.Switch) || this.accessory.addService(this.platform.Service.Switch);
+ break;
+ }
case 'switch': {
this.service = this.accessory.getService(this.platform.Service.Switch) || this.accessory.addService(this.platform.Service.Switch);
break;
@@ -62,7 +67,14 @@ export class RelayHandler {
}
}
- public updateIO(state: boolean){
- this.service.updateCharacteristic(this.platform.Characteristic.On, state);
+ public updateIO(value: boolean){
+ if(this.accessory.context.device.type == 'toggle'){
+ this.service.updateCharacteristic(this.platform.Characteristic.On, false);
+ }else{
+ this.state = value;
+ if(this.service.getCharacteristic(this.platform.Characteristic.On).value != value){
+ this.service.updateCharacteristic(this.platform.Characteristic.On, value);
+ }
+ }
}
}
diff --git a/src/ipx/api.ts b/src/ipx/api.ts
index 7db1cee5..aa082301 100644
--- a/src/ipx/api.ts
+++ b/src/ipx/api.ts
@@ -8,7 +8,6 @@ export abstract class IpxApiCaller {
//public abstract setAnaPosition(value: CharacteristicValue, platform: IPXPlatform, accessory: PlatformAccessory): void;
public abstract setVRPosition(value: CharacteristicValue, platform: IPXPlatform, accessory: PlatformAccessory): void;
public abstract setDimmerPosition(value: CharacteristicValue, platform: IPXPlatform, accessory: PlatformAccessory): void;
- public abstract getStateByDeviceIndex(platform: IPXPlatform): Promise