homebridge-solaxcloud-api 2.4.2 → 2.5.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +16 -0
- package/README.md +30 -3
- package/config.schema.json +2 -2
- package/dist/batteryAccessory.d.ts +1 -1
- package/dist/batteryAccessory.d.ts.map +1 -1
- package/dist/batteryAccessory.js +17 -17
- package/dist/batteryAccessory.js.map +1 -1
- package/dist/index.d.ts +2 -2
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +7 -5
- package/dist/index.js.map +1 -1
- package/dist/lightSensorAccessory.d.ts +23 -16
- package/dist/lightSensorAccessory.d.ts.map +1 -1
- package/dist/lightSensorAccessory.js +109 -32
- package/dist/lightSensorAccessory.js.map +1 -1
- package/dist/motionAccessory.d.ts +1 -2
- package/dist/motionAccessory.d.ts.map +1 -1
- package/dist/motionAccessory.js +17 -20
- package/dist/motionAccessory.js.map +1 -1
- package/dist/outletAccessory.d.ts +1 -1
- package/dist/outletAccessory.d.ts.map +1 -1
- package/dist/outletAccessory.js +47 -33
- package/dist/outletAccessory.js.map +1 -1
- package/dist/platform.d.ts +1 -1
- package/dist/platform.d.ts.map +1 -1
- package/dist/platform.js +30 -27
- package/dist/platform.js.map +1 -1
- package/dist/platformAccessory.d.ts.map +1 -1
- package/dist/platformAccessory.js +40 -18
- package/dist/platformAccessory.js.map +1 -1
- package/dist/platformInverter.d.ts +5 -0
- package/dist/platformInverter.d.ts.map +1 -1
- package/dist/platformInverter.js +148 -75
- package/dist/platformInverter.js.map +1 -1
- package/dist/platformMeters.d.ts +2 -2
- package/dist/platformMeters.d.ts.map +1 -1
- package/dist/platformMeters.js +1 -2
- package/dist/settings.js +2 -5
- package/dist/settings.js.map +1 -1
- package/dist/solaxcloudfetch.js +14 -19
- package/dist/solaxcloudfetch.js.map +1 -1
- package/dist/statistics.js +1 -5
- package/dist/statistics.js.map +1 -1
- package/dist/util.js +1 -5
- package/dist/util.js.map +1 -1
- package/package.json +21 -16
- package/test/batteryAccessory.test.ts +3 -3
- package/test/helpers.ts +49 -5
- package/test/lightSensorAccessory.test.ts +48 -3
- package/test/motionAccessory.test.ts +3 -3
- package/test/outletAccessory.test.ts +3 -3
- package/test/platformAccessory.test.ts +3 -3
- package/test/platformConfig.test.ts +2 -2
- package/test/platformTotalizer.test.ts +64 -4
- package/test/statistics.test.ts +2 -2
package/package.json
CHANGED
|
@@ -2,23 +2,24 @@
|
|
|
2
2
|
"private": false,
|
|
3
3
|
"displayName": "Homebridge Solax Cloud API",
|
|
4
4
|
"name": "homebridge-solaxcloud-api",
|
|
5
|
-
"
|
|
5
|
+
"type": "module",
|
|
6
|
+
"version": "2.5.1",
|
|
6
7
|
"description": "Integrates solar production info using public Solax/QCells Cloud API into Homebridge accessories.",
|
|
7
8
|
"license": "Apache-2.0",
|
|
8
9
|
"repository": {
|
|
9
10
|
"type": "git",
|
|
10
|
-
"url": "git://github.com/AllMightySauron/
|
|
11
|
+
"url": "git://github.com/AllMightySauron/homebridge-solaxcloud-api.git"
|
|
11
12
|
},
|
|
12
13
|
"bugs": {
|
|
13
|
-
"url": "https://github.com/AllMightySauron/
|
|
14
|
+
"url": "https://github.com/AllMightySauron/homebridge-solaxcloud-api/issues"
|
|
14
15
|
},
|
|
15
16
|
"engines": {
|
|
16
|
-
"node": "
|
|
17
|
-
"homebridge": ">=
|
|
17
|
+
"node": "^22 || ^24 || ^26",
|
|
18
|
+
"homebridge": ">=2.4.0"
|
|
18
19
|
},
|
|
19
20
|
"main": "dist/index.js",
|
|
20
21
|
"scripts": {
|
|
21
|
-
"test": "mocha --exit
|
|
22
|
+
"test": "tsx ./node_modules/mocha/bin/mocha.js --exit test/**/*.test.ts",
|
|
22
23
|
"lint": "eslint src/**.ts --max-warnings=0",
|
|
23
24
|
"watch": "npm run build && npm link && nodemon",
|
|
24
25
|
"build": "rimraf ./dist && tsc",
|
|
@@ -27,29 +28,33 @@
|
|
|
27
28
|
"keywords": [
|
|
28
29
|
"homebridge",
|
|
29
30
|
"homebridge-plugin",
|
|
31
|
+
"supports-matter",
|
|
32
|
+
"supports-hap",
|
|
30
33
|
"solax",
|
|
31
34
|
"qcells",
|
|
32
35
|
"solaxcloud"
|
|
33
36
|
],
|
|
37
|
+
"peerDependencies": {
|
|
38
|
+
"homebridge": "^2.4.0"
|
|
39
|
+
},
|
|
34
40
|
"dependencies": {
|
|
35
41
|
"fakegato-history": "^0.6.7",
|
|
36
42
|
"homebridge-lib": "^8.1.0",
|
|
37
|
-
"jshint": "^2.13.6",
|
|
38
43
|
"string-hash-64": "^1.0.3",
|
|
39
44
|
"sync-fetch": "^0.3.1",
|
|
40
45
|
"solax-cloud-api": "^0.2.0"
|
|
41
46
|
},
|
|
42
47
|
"devDependencies": {
|
|
43
|
-
"@types/mocha": "^
|
|
44
|
-
"@types/node": "^
|
|
45
|
-
"@typescript-eslint/eslint-plugin": "^
|
|
46
|
-
"@typescript-eslint/parser": "^
|
|
47
|
-
"eslint": "^8.
|
|
48
|
-
"homebridge": "^
|
|
49
|
-
"mocha": "^
|
|
48
|
+
"@types/mocha": "^10.0.10",
|
|
49
|
+
"@types/node": "^24.19.2",
|
|
50
|
+
"@typescript-eslint/eslint-plugin": "^8.72.1",
|
|
51
|
+
"@typescript-eslint/parser": "^8.72.1",
|
|
52
|
+
"eslint": "^8.57.1",
|
|
53
|
+
"homebridge": "^2.4.0",
|
|
54
|
+
"mocha": "^12.0.3",
|
|
50
55
|
"nodemon": "^3.1.0",
|
|
51
56
|
"rimraf": "^3.0.2",
|
|
52
|
-
"
|
|
53
|
-
"typescript": "^
|
|
57
|
+
"tsx": "^4.23.15",
|
|
58
|
+
"typescript": "^5.9.3"
|
|
54
59
|
}
|
|
55
60
|
}
|
|
@@ -1,7 +1,7 @@
|
|
|
1
|
-
import assert
|
|
1
|
+
import assert from 'assert';
|
|
2
2
|
|
|
3
|
-
import { BATTERY_CHARGING_STATE, SolaxBatteryAccessory } from '../src/batteryAccessory';
|
|
4
|
-
import { createApi, createLog } from './helpers';
|
|
3
|
+
import { BATTERY_CHARGING_STATE, SolaxBatteryAccessory } from '../src/batteryAccessory.js';
|
|
4
|
+
import { createApi, createLog } from './helpers.js';
|
|
5
5
|
|
|
6
6
|
describe('SolaxBatteryAccessory', () => {
|
|
7
7
|
it('keeps charge state getter in sync after updates', () => {
|
package/test/helpers.ts
CHANGED
|
@@ -1,10 +1,21 @@
|
|
|
1
|
-
import { API, Logging } from 'homebridge';
|
|
2
|
-
import { Characteristic, Service } from 'hap-nodejs';
|
|
1
|
+
import { API, Logging, MatterAccessory, MatterAPI } from 'homebridge';
|
|
2
|
+
import { Characteristic, Service, uuid } from '@homebridge/hap-nodejs';
|
|
3
|
+
import { createRequire } from 'module';
|
|
3
4
|
import os from 'os';
|
|
4
5
|
import path from 'path';
|
|
5
6
|
|
|
6
|
-
|
|
7
|
-
const { CharacteristicEventTypes, Formats, Perms } = require('hap-nodejs');
|
|
7
|
+
const require = createRequire(import.meta.url);
|
|
8
|
+
const { CharacteristicEventTypes, Formats, Perms } = require('@homebridge/hap-nodejs');
|
|
9
|
+
|
|
10
|
+
export interface MatterApiHarness {
|
|
11
|
+
api: MatterAPI;
|
|
12
|
+
registrations: MatterAccessory[][];
|
|
13
|
+
updates: Array<{
|
|
14
|
+
uuid: string;
|
|
15
|
+
cluster: string;
|
|
16
|
+
attributes: Record<string, unknown>;
|
|
17
|
+
}>;
|
|
18
|
+
}
|
|
8
19
|
|
|
9
20
|
export function createLog(): Logging {
|
|
10
21
|
const noop = () => undefined;
|
|
@@ -19,7 +30,7 @@ export function createLog(): Logging {
|
|
|
19
30
|
}) as unknown as Logging;
|
|
20
31
|
}
|
|
21
32
|
|
|
22
|
-
export function createApi(): API {
|
|
33
|
+
export function createApi(matter?: MatterAPI): API {
|
|
23
34
|
return {
|
|
24
35
|
hap: {
|
|
25
36
|
Characteristic,
|
|
@@ -27,10 +38,43 @@ export function createApi(): API {
|
|
|
27
38
|
Formats,
|
|
28
39
|
Perms,
|
|
29
40
|
Service,
|
|
41
|
+
uuid,
|
|
30
42
|
},
|
|
31
43
|
on: () => undefined,
|
|
32
44
|
user: {
|
|
33
45
|
storagePath: () => path.join(os.tmpdir(), 'homebridge-solaxcloud-api-test'),
|
|
34
46
|
},
|
|
47
|
+
matter,
|
|
35
48
|
} as unknown as API;
|
|
36
49
|
}
|
|
50
|
+
|
|
51
|
+
export function createMatterApi(): MatterApiHarness {
|
|
52
|
+
const registrations: MatterAccessory[][] = [];
|
|
53
|
+
const updates: MatterApiHarness['updates'] = [];
|
|
54
|
+
|
|
55
|
+
const api = {
|
|
56
|
+
deviceTypes: {
|
|
57
|
+
ElectricalSensor: { name: 'ElectricalSensor' },
|
|
58
|
+
},
|
|
59
|
+
clusterNames: {
|
|
60
|
+
ElectricalPowerMeasurement: 'electricalPowerMeasurement',
|
|
61
|
+
ElectricalEnergyMeasurement: 'electricalEnergyMeasurement',
|
|
62
|
+
},
|
|
63
|
+
registerPlatformAccessories: async (
|
|
64
|
+
_pluginIdentifier: string,
|
|
65
|
+
_platformName: string,
|
|
66
|
+
accessories: MatterAccessory[],
|
|
67
|
+
) => {
|
|
68
|
+
registrations.push(accessories);
|
|
69
|
+
},
|
|
70
|
+
updateAccessoryState: async (
|
|
71
|
+
uuid: string,
|
|
72
|
+
cluster: string,
|
|
73
|
+
attributes: Record<string, unknown>,
|
|
74
|
+
) => {
|
|
75
|
+
updates.push({ uuid, cluster, attributes });
|
|
76
|
+
},
|
|
77
|
+
} as unknown as MatterAPI;
|
|
78
|
+
|
|
79
|
+
return { api, registrations, updates };
|
|
80
|
+
}
|
|
@@ -1,7 +1,7 @@
|
|
|
1
|
-
import assert
|
|
1
|
+
import assert from 'assert';
|
|
2
2
|
|
|
3
|
-
import { SolaxLightSensorAccessory } from '../src/lightSensorAccessory';
|
|
4
|
-
import { createApi, createLog } from './helpers';
|
|
3
|
+
import { SolaxLightSensorAccessory } from '../src/lightSensorAccessory.js';
|
|
4
|
+
import { createApi, createLog, createMatterApi } from './helpers.js';
|
|
5
5
|
|
|
6
6
|
describe('SolaxLightSensorAccessory', () => {
|
|
7
7
|
it('stores ambient light values and applies the HomeKit minimum', () => {
|
|
@@ -24,5 +24,50 @@ describe('SolaxLightSensorAccessory', () => {
|
|
|
24
24
|
const sensor = new SolaxLightSensorAccessory(createLog(), createApi(), 'Light sensor', 'light-1', 'Model');
|
|
25
25
|
|
|
26
26
|
assert.equal(sensor.getServices().length, 2);
|
|
27
|
+
assert.equal(sensor.isMatterAccessory(), false);
|
|
28
|
+
});
|
|
29
|
+
|
|
30
|
+
it('registers and updates a Matter electrical sensor', async () => {
|
|
31
|
+
const matter = createMatterApi();
|
|
32
|
+
const sensor = new SolaxLightSensorAccessory(
|
|
33
|
+
createLog(),
|
|
34
|
+
createApi(matter.api),
|
|
35
|
+
'Power sensor',
|
|
36
|
+
'power-1',
|
|
37
|
+
'Model',
|
|
38
|
+
true,
|
|
39
|
+
);
|
|
40
|
+
|
|
41
|
+
assert.equal(sensor.getServices().length, 1);
|
|
42
|
+
assert.equal(sensor.isMatterAccessory(), true);
|
|
43
|
+
assert.equal(matter.registrations.length, 1);
|
|
44
|
+
|
|
45
|
+
const accessory = matter.registrations[0][0];
|
|
46
|
+
assert.equal(accessory.deviceType, matter.api.deviceTypes.ElectricalSensor);
|
|
47
|
+
assert.deepEqual(accessory.clusters, {
|
|
48
|
+
electricalPowerMeasurement: { activePower: 0 },
|
|
49
|
+
electricalEnergyMeasurement: {
|
|
50
|
+
cumulativeEnergyExported: { energy: 0 },
|
|
51
|
+
},
|
|
52
|
+
});
|
|
53
|
+
|
|
54
|
+
sensor.setAmbientLightLevel(123.456);
|
|
55
|
+
sensor.setTotalEnergyConsumption(12.345);
|
|
56
|
+
await new Promise(resolve => setImmediate(resolve));
|
|
57
|
+
|
|
58
|
+
assert.deepEqual(matter.updates, [
|
|
59
|
+
{
|
|
60
|
+
uuid: accessory.UUID,
|
|
61
|
+
cluster: 'electricalPowerMeasurement',
|
|
62
|
+
attributes: { activePower: 123456 },
|
|
63
|
+
},
|
|
64
|
+
{
|
|
65
|
+
uuid: accessory.UUID,
|
|
66
|
+
cluster: 'electricalEnergyMeasurement',
|
|
67
|
+
attributes: {
|
|
68
|
+
cumulativeEnergyExported: { energy: 12345 },
|
|
69
|
+
},
|
|
70
|
+
},
|
|
71
|
+
]);
|
|
27
72
|
});
|
|
28
73
|
});
|
|
@@ -1,7 +1,7 @@
|
|
|
1
|
-
import assert
|
|
1
|
+
import assert from 'assert';
|
|
2
2
|
|
|
3
|
-
import { SolaxMotionAccessory } from '../src/motionAccessory';
|
|
4
|
-
import { createApi, createLog } from './helpers';
|
|
3
|
+
import { SolaxMotionAccessory } from '../src/motionAccessory.js';
|
|
4
|
+
import { createApi, createLog } from './helpers.js';
|
|
5
5
|
|
|
6
6
|
function sleep(millis: number): Promise<void> {
|
|
7
7
|
return new Promise(resolve => setTimeout(resolve, millis));
|
|
@@ -1,6 +1,6 @@
|
|
|
1
|
-
import assert
|
|
2
|
-
import { SolaxOutletAccessory } from '../src/outletAccessory';
|
|
3
|
-
import { createApi, createLog } from './helpers';
|
|
1
|
+
import assert from 'assert';
|
|
2
|
+
import { SolaxOutletAccessory } from '../src/outletAccessory.js';
|
|
3
|
+
import { createApi, createLog } from './helpers.js';
|
|
4
4
|
|
|
5
5
|
describe('SolaxOutletAccessory', () => {
|
|
6
6
|
function withOutlet(test: (outlet: SolaxOutletAccessory) => void): void {
|
|
@@ -1,7 +1,7 @@
|
|
|
1
|
-
import assert
|
|
1
|
+
import assert from 'assert';
|
|
2
2
|
|
|
3
|
-
import { SolaxPlatformAccessory } from '../src/platformAccessory';
|
|
4
|
-
import { createApi, createLog } from './helpers';
|
|
3
|
+
import { SolaxPlatformAccessory } from '../src/platformAccessory.js';
|
|
4
|
+
import { createApi, createLog } from './helpers.js';
|
|
5
5
|
|
|
6
6
|
describe('SolaxPlatformAccessory', () => {
|
|
7
7
|
it('exposes normalized model and stable hashed serial information', () => {
|
|
@@ -1,8 +1,8 @@
|
|
|
1
1
|
import { Logging, PlatformConfig } from 'homebridge';
|
|
2
2
|
|
|
3
|
-
import assert
|
|
3
|
+
import assert from 'assert';
|
|
4
4
|
|
|
5
|
-
import { SolaxCloudAPIPlatform } from '../src/platform';
|
|
5
|
+
import { SolaxCloudAPIPlatform } from '../src/platform.js';
|
|
6
6
|
|
|
7
7
|
interface TestLog extends Logging {
|
|
8
8
|
errors: string[];
|
|
@@ -1,10 +1,10 @@
|
|
|
1
1
|
import { API, Logging, PlatformConfig } from 'homebridge';
|
|
2
2
|
|
|
3
|
-
import assert
|
|
3
|
+
import assert from 'assert';
|
|
4
4
|
|
|
5
|
-
import { SolaxCloudAPIPlatform } from '../src/platform';
|
|
6
|
-
import { SolaxCloudAPIPlatformInverter } from '../src/platformInverter';
|
|
7
|
-
import { createApi, createLog } from './helpers';
|
|
5
|
+
import { SolaxCloudAPIPlatform } from '../src/platform.js';
|
|
6
|
+
import { SolaxCloudAPIPlatformInverter } from '../src/platformInverter.js';
|
|
7
|
+
import { createApi, createLog, createMatterApi } from './helpers.js';
|
|
8
8
|
|
|
9
9
|
interface InverterCreation {
|
|
10
10
|
brand: number;
|
|
@@ -73,6 +73,66 @@ describe('SolaxCloudAPIPlatform totalizer inverter creation', () => {
|
|
|
73
73
|
assert.equal(totalizer.hasBattery, true);
|
|
74
74
|
});
|
|
75
75
|
|
|
76
|
+
it('publishes and updates applicable meters through Matter without requiring pureHomeApp', async () => {
|
|
77
|
+
const matter = createMatterApi();
|
|
78
|
+
const config = {
|
|
79
|
+
enableHistory: false,
|
|
80
|
+
pureHomeApp: false,
|
|
81
|
+
smoothMeters: true,
|
|
82
|
+
smoothingMethod: 'sma',
|
|
83
|
+
} as PlatformConfig;
|
|
84
|
+
|
|
85
|
+
const inverter = new SolaxCloudAPIPlatformInverter(
|
|
86
|
+
createLog(),
|
|
87
|
+
config,
|
|
88
|
+
createApi(matter.api),
|
|
89
|
+
0,
|
|
90
|
+
'',
|
|
91
|
+
'virtual',
|
|
92
|
+
'Virtual inverter',
|
|
93
|
+
false,
|
|
94
|
+
3,
|
|
95
|
+
true,
|
|
96
|
+
);
|
|
97
|
+
|
|
98
|
+
assert.equal(matter.registrations.length, 10);
|
|
99
|
+
assert.equal(inverter.getAccessories().length, 6);
|
|
100
|
+
|
|
101
|
+
inverter.setRawPower('inverterAC', 123.456);
|
|
102
|
+
inverter.setSmoothPower('inverterAC');
|
|
103
|
+
inverter.setRawEnergy('inverterAC', 12.345);
|
|
104
|
+
await new Promise(resolve => setImmediate(resolve));
|
|
105
|
+
|
|
106
|
+
assert.equal(matter.updates.filter(update =>
|
|
107
|
+
update.cluster === 'electricalPowerMeasurement' && update.attributes.activePower === 123456).length, 2);
|
|
108
|
+
assert.equal(matter.updates.some(update =>
|
|
109
|
+
update.cluster === 'electricalEnergyMeasurement'), true);
|
|
110
|
+
});
|
|
111
|
+
|
|
112
|
+
it('retains pureHomeApp as a non-Matter legacy fallback', () => {
|
|
113
|
+
const config = {
|
|
114
|
+
enableHistory: false,
|
|
115
|
+
pureHomeApp: true,
|
|
116
|
+
smoothMeters: false,
|
|
117
|
+
smoothingMethod: 'sma',
|
|
118
|
+
} as PlatformConfig;
|
|
119
|
+
|
|
120
|
+
const inverter = new SolaxCloudAPIPlatformInverter(
|
|
121
|
+
createLog(),
|
|
122
|
+
config,
|
|
123
|
+
createApi(),
|
|
124
|
+
0,
|
|
125
|
+
'',
|
|
126
|
+
'virtual',
|
|
127
|
+
'Virtual inverter',
|
|
128
|
+
false,
|
|
129
|
+
3,
|
|
130
|
+
true,
|
|
131
|
+
);
|
|
132
|
+
|
|
133
|
+
assert.equal(inverter.getAccessories().length, 11);
|
|
134
|
+
});
|
|
135
|
+
|
|
76
136
|
it('creates a virtual totalizer with QCells as brand when all inverters use QCells', () => {
|
|
77
137
|
createPlatform(createConfig(['qcells', 'qcells']));
|
|
78
138
|
|
package/test/statistics.test.ts
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
|
-
import { Statistics } from '../src/statistics';
|
|
1
|
+
import { Statistics } from '../src/statistics.js';
|
|
2
2
|
|
|
3
|
-
import assert
|
|
3
|
+
import assert from 'assert';
|
|
4
4
|
|
|
5
5
|
describe('Statistics: Smoothing methods', () => {
|
|
6
6
|
const sampleSeries = [1, 3, 5, 3, 7, 6, 8, 5, 8, 8, 4, 5];
|