@jakobgoerke/ac-infinity-client 0.2.1 → 0.3.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/README.md +259 -1
- package/build/src/AcInfinityClient.d.ts +20 -6
- package/build/src/AcInfinityClient.d.ts.map +1 -1
- package/build/src/AcInfinityClient.js +58 -14
- package/build/src/errors/index.d.ts +6 -1
- package/build/src/errors/index.d.ts.map +1 -1
- package/build/src/errors/index.js +14 -1
- package/build/src/index.d.ts +3 -0
- package/build/src/index.d.ts.map +1 -1
- package/build/src/index.js +8 -0
- package/build/src/types/Controller.d.ts +208 -17
- package/build/src/types/Controller.d.ts.map +1 -1
- package/build/src/types/Controller.js +115 -5
- package/build/src/types/Device.d.ts +401 -4
- package/build/src/types/Device.d.ts.map +1 -1
- package/build/src/types/Device.js +216 -5
- package/build/src/types/index.d.ts +4 -4
- package/build/src/types/index.d.ts.map +1 -1
- package/build/src/types/index.js +6 -1
- package/package.json +15 -11
package/README.md
CHANGED
|
@@ -1 +1,259 @@
|
|
|
1
|
-
# ac-infinity-client
|
|
1
|
+
# ac-infinity-client
|
|
2
|
+
|
|
3
|
+
[](https://www.npmjs.com/package/@jakobgoerke/ac-infinity-client)
|
|
4
|
+
[](https://github.com/jakobgoerke/ac-infinity-client/actions/workflows/build.yml)
|
|
5
|
+
[](LICENSE)
|
|
6
|
+
|
|
7
|
+
A typed Node.js client for the [AC Infinity](https://acinfinity.com) cloud API, the same backend the AC Infinity app uses. Use it to read your
|
|
8
|
+
UIS controllers, their ports and sensors, and to change device and mode settings from your own code.
|
|
9
|
+
|
|
10
|
+
- **Typed**: responses are validated with [zod](https://zod.dev) and turned into readable objects (`levelWhileOn` instead of `onSpead`).
|
|
11
|
+
- **Decoded values**: temperatures, humidity, VPD and sensor readings come back in real units, not vendor-scaled integers.
|
|
12
|
+
- **Partial updates**: send only the fields you want to change. The client fetches the current settings and merges your changes in.
|
|
13
|
+
- **Device detection**: ports without an assigned device type are identified by the ID resistor in the plug.
|
|
14
|
+
|
|
15
|
+
> [!NOTE]
|
|
16
|
+
> This is an unofficial project and is not affiliated with AC Infinity. The API is undocumented and may change without notice.
|
|
17
|
+
|
|
18
|
+
## Installation
|
|
19
|
+
|
|
20
|
+
```sh
|
|
21
|
+
npm install @jakobgoerke/ac-infinity-client
|
|
22
|
+
# or
|
|
23
|
+
yarn add @jakobgoerke/ac-infinity-client
|
|
24
|
+
```
|
|
25
|
+
|
|
26
|
+
Requires Node.js 24 or newer.
|
|
27
|
+
|
|
28
|
+
## Quick start
|
|
29
|
+
|
|
30
|
+
```ts
|
|
31
|
+
import { AcInfinityClient, DeviceMode } from '@jakobgoerke/ac-infinity-client';
|
|
32
|
+
|
|
33
|
+
// build() creates the client and logs in
|
|
34
|
+
const client = await AcInfinityClient.build({
|
|
35
|
+
email: 'you@example.com',
|
|
36
|
+
password: 'your-password',
|
|
37
|
+
});
|
|
38
|
+
|
|
39
|
+
const [controller] = await client.getControllers();
|
|
40
|
+
console.log(`${controller.name}: ${controller.deviceInfo.temperature} °C, ${controller.deviceInfo.humidity} %`);
|
|
41
|
+
|
|
42
|
+
for (const port of controller.deviceInfo.ports) {
|
|
43
|
+
console.log(`Port ${port.port} (${port.deviceName}) is at level ${port.currentLevel}`);
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
// turn the fan on port 1 on at level 6
|
|
47
|
+
await client.setDeviceModeSettings({
|
|
48
|
+
controllerId: controller.id,
|
|
49
|
+
port: 1,
|
|
50
|
+
settings: { mode: DeviceMode.ON, levelWhileOn: 6 },
|
|
51
|
+
});
|
|
52
|
+
```
|
|
53
|
+
|
|
54
|
+
## Usage
|
|
55
|
+
|
|
56
|
+
### Authentication
|
|
57
|
+
|
|
58
|
+
`AcInfinityClient.build()` creates a client and logs in right away. If you want to log in later, create the client with `new` and call
|
|
59
|
+
`authenticate()` yourself:
|
|
60
|
+
|
|
61
|
+
```ts
|
|
62
|
+
const client = new AcInfinityClient({ email, password });
|
|
63
|
+
const user = await client.authenticate(); // { id, username, email }
|
|
64
|
+
```
|
|
65
|
+
|
|
66
|
+
If the credentials are rejected, an `AuthenticationError` is thrown.
|
|
67
|
+
|
|
68
|
+
> [!WARNING]
|
|
69
|
+
> The user `id` returned by `authenticate()` is also the API token: it is sent with every request, and anyone who has it can control your
|
|
70
|
+
> devices. Treat it like your password and keep it out of logs, issues and screenshots. The vendor API is served over plain HTTP, so the
|
|
71
|
+
> token travels unencrypted.
|
|
72
|
+
|
|
73
|
+
### Controllers
|
|
74
|
+
|
|
75
|
+
```ts
|
|
76
|
+
const controllers = await client.getControllers(); // controllers on your account
|
|
77
|
+
const shared = await client.getSharedControllers(); // controllers others shared with you
|
|
78
|
+
const controller = await client.getController({ controllerId: controllers[0].id });
|
|
79
|
+
```
|
|
80
|
+
|
|
81
|
+
A controller looks like this (abridged):
|
|
82
|
+
|
|
83
|
+
```ts
|
|
84
|
+
{
|
|
85
|
+
id: '1234567890123456789',
|
|
86
|
+
name: 'Grow Tent',
|
|
87
|
+
type: ControllerDeviceType.UIS_CONTROLLER_69_PRO,
|
|
88
|
+
totalPorts: 4,
|
|
89
|
+
online: true,
|
|
90
|
+
firmwareVersion: '3.2.25',
|
|
91
|
+
deviceInfo: {
|
|
92
|
+
temperature: 24.5, // °C
|
|
93
|
+
temperatureF: 76.1, // °F, null if not reported
|
|
94
|
+
humidity: 58.3, // %
|
|
95
|
+
vpd: 1.28, // kPa
|
|
96
|
+
ports: [
|
|
97
|
+
{
|
|
98
|
+
port: 1,
|
|
99
|
+
deviceName: 'Exhaust Fan',
|
|
100
|
+
deviceType: DeviceType.FAN,
|
|
101
|
+
currentMode: DeviceMode.AUTO,
|
|
102
|
+
currentLevel: 4,
|
|
103
|
+
online: true,
|
|
104
|
+
remainingTime: null, // seconds until the next timer / cycle / schedule switch
|
|
105
|
+
},
|
|
106
|
+
],
|
|
107
|
+
sensors: [
|
|
108
|
+
{ port: 1, type: SensorType.CO2, value: 812 }, // value is null when the sensor has no reading
|
|
109
|
+
],
|
|
110
|
+
},
|
|
111
|
+
}
|
|
112
|
+
```
|
|
113
|
+
|
|
114
|
+
### Ports
|
|
115
|
+
|
|
116
|
+
Settings are addressed by `controllerId` and `port`. Port `0` is the controller itself, ports `1` to `n` are the devices plugged into it.
|
|
117
|
+
|
|
118
|
+
### Mode settings
|
|
119
|
+
|
|
120
|
+
Mode settings control what a device does: its mode, its levels, and the triggers for timer, cycle, schedule, auto and VPD modes.
|
|
121
|
+
|
|
122
|
+
```ts
|
|
123
|
+
const modes = await client.getDeviceModeSettings({ controllerId, port: 1 });
|
|
124
|
+
|
|
125
|
+
modes.mode; // DeviceMode.AUTO
|
|
126
|
+
modes.levelWhileOn; // 0–10
|
|
127
|
+
modes.auto.highTemperature; // { enabled: true, celsius: 28, fahrenheit: 82 }
|
|
128
|
+
modes.vpd.target; // { enabled: false, value: 1.2 } (kPa)
|
|
129
|
+
modes.schedule; // { start: 480, end: 1200 } (minutes after midnight)
|
|
130
|
+
```
|
|
131
|
+
|
|
132
|
+
Only the fields you pass to `setDeviceModeSettings()` are changed:
|
|
133
|
+
|
|
134
|
+
```ts
|
|
135
|
+
// run the fan in auto mode when it gets hotter than 28 °C or more humid than 65 %
|
|
136
|
+
await client.setDeviceModeSettings({
|
|
137
|
+
controllerId,
|
|
138
|
+
port: 1,
|
|
139
|
+
settings: {
|
|
140
|
+
mode: DeviceMode.AUTO,
|
|
141
|
+
levelWhileOn: 8,
|
|
142
|
+
levelWhileOff: 1,
|
|
143
|
+
auto: {
|
|
144
|
+
highTemperature: { enabled: true, celsius: 28 },
|
|
145
|
+
highHumidity: { enabled: true, value: 65 },
|
|
146
|
+
},
|
|
147
|
+
},
|
|
148
|
+
});
|
|
149
|
+
|
|
150
|
+
// switch the light on at 08:00 and off at 20:00
|
|
151
|
+
await client.setDeviceModeSettings({
|
|
152
|
+
controllerId,
|
|
153
|
+
port: 2,
|
|
154
|
+
settings: {
|
|
155
|
+
mode: DeviceMode.SCHEDULE,
|
|
156
|
+
schedule: { start: 8 * 60, end: 20 * 60 },
|
|
157
|
+
},
|
|
158
|
+
});
|
|
159
|
+
|
|
160
|
+
// 15 minutes on, 45 minutes off
|
|
161
|
+
await client.setDeviceModeSettings({
|
|
162
|
+
controllerId,
|
|
163
|
+
port: 3,
|
|
164
|
+
settings: {
|
|
165
|
+
mode: DeviceMode.CYCLE,
|
|
166
|
+
cycle: { on: 15 * 60, off: 45 * 60 },
|
|
167
|
+
},
|
|
168
|
+
});
|
|
169
|
+
```
|
|
170
|
+
|
|
171
|
+
| Setting | Unit / range |
|
|
172
|
+
| ---------------------------------------------------- | -------------------------------------------------------------- |
|
|
173
|
+
| `levelWhileOn`, `levelWhileOff` | 0–10 |
|
|
174
|
+
| `timer.toOn`, `timer.toOff`, `cycle.on`, `cycle.off` | seconds |
|
|
175
|
+
| `schedule.start`, `schedule.end` | minutes after midnight (0–1439), `null` to clear |
|
|
176
|
+
| temperature triggers | `celsius` (0–90) **or** `fahrenheit` (32–194), the other is computed |
|
|
177
|
+
| humidity triggers | percent (0–100) |
|
|
178
|
+
| VPD triggers | kPa (0–9.9) |
|
|
179
|
+
|
|
180
|
+
### Device settings
|
|
181
|
+
|
|
182
|
+
Device settings are the general settings of a port or the controller: name, temperature unit, display brightness, backlight and sensor
|
|
183
|
+
calibration.
|
|
184
|
+
|
|
185
|
+
```ts
|
|
186
|
+
const settings = await client.getDeviceSettings({ controllerId, port: 0 });
|
|
187
|
+
|
|
188
|
+
settings.name; // 'Grow Tent'
|
|
189
|
+
settings.temperatureUnit; // TemperatureUnit.CELSIUS
|
|
190
|
+
settings.calibration; // { temperatureC, temperatureF, humidity, leafTemperatureC, leafTemperatureF }
|
|
191
|
+
|
|
192
|
+
await client.setDeviceSettings({
|
|
193
|
+
controllerId,
|
|
194
|
+
port: 0,
|
|
195
|
+
settings: {
|
|
196
|
+
name: 'Grow Tent',
|
|
197
|
+
temperatureUnit: TemperatureUnit.CELSIUS,
|
|
198
|
+
backlight: true,
|
|
199
|
+
calibration: { temperatureC: -1, humidity: 2 },
|
|
200
|
+
},
|
|
201
|
+
});
|
|
202
|
+
```
|
|
203
|
+
|
|
204
|
+
As with temperature triggers, give the temperature calibration in either `temperatureC` or `temperatureF`. The other value is computed for you.
|
|
205
|
+
|
|
206
|
+
### Errors
|
|
207
|
+
|
|
208
|
+
| Error | When |
|
|
209
|
+
| --------------------- | ---------------------------------------------------------------------------------------- |
|
|
210
|
+
| `AuthenticationError` | The login was rejected. |
|
|
211
|
+
| `ApiError` | The API answered with a non-200 code. The error carries the vendor's `code` and `msg`. |
|
|
212
|
+
| `ZodError` | A response didn't match the expected shape, or the changes you passed are out of range. |
|
|
213
|
+
|
|
214
|
+
```ts
|
|
215
|
+
import { ApiError } from '@jakobgoerke/ac-infinity-client';
|
|
216
|
+
|
|
217
|
+
try {
|
|
218
|
+
await client.getController({ controllerId: 'unknown' });
|
|
219
|
+
} catch (error) {
|
|
220
|
+
if (error instanceof ApiError) {
|
|
221
|
+
console.error(error.code, error.msg);
|
|
222
|
+
}
|
|
223
|
+
}
|
|
224
|
+
```
|
|
225
|
+
|
|
226
|
+
## API reference
|
|
227
|
+
|
|
228
|
+
| Method | Returns |
|
|
229
|
+
| -------------------------------------------------------- | ----------------------------- |
|
|
230
|
+
| `AcInfinityClient.build({ email, password })` | `Promise<AcInfinityClient>` |
|
|
231
|
+
| `authenticate()` | `Promise<User>` |
|
|
232
|
+
| `getControllers()` | `Promise<Controller[]>` |
|
|
233
|
+
| `getSharedControllers()` | `Promise<Controller[]>` |
|
|
234
|
+
| `getController({ controllerId })` | `Promise<Controller>` |
|
|
235
|
+
| `getDeviceSettings({ controllerId, port })` | `Promise<DeviceSettings>` |
|
|
236
|
+
| `setDeviceSettings({ controllerId, port, settings })` | `Promise<void>` |
|
|
237
|
+
| `getDeviceModeSettings({ controllerId, port })` | `Promise<DeviceModeSettings>` |
|
|
238
|
+
| `setDeviceModeSettings({ controllerId, port, settings })`| `Promise<void>` |
|
|
239
|
+
|
|
240
|
+
The package also exports the enums `ControllerDeviceType`, `DeviceType`, `DeviceMode`, `SensorType` and `TemperatureUnit`, and the types
|
|
241
|
+
`Controller`, `ControllerDeviceInfo`, `ControllerPort`, `ControllerSensor`, `DeviceSettings`, `DeviceSettingsChanges`, `DeviceModeSettings`,
|
|
242
|
+
`DeviceModeSettingsChanges` and `User`.
|
|
243
|
+
|
|
244
|
+
## Development
|
|
245
|
+
|
|
246
|
+
```sh
|
|
247
|
+
corepack enable
|
|
248
|
+
yarn install
|
|
249
|
+
yarn test # run the tests
|
|
250
|
+
yarn coverage # run the tests with coverage
|
|
251
|
+
yarn lint # check formatting and lint rules with Biome
|
|
252
|
+
yarn build # compile to ./build
|
|
253
|
+
```
|
|
254
|
+
|
|
255
|
+
New versions are published to npm by CI when a tag is pushed.
|
|
256
|
+
|
|
257
|
+
## License
|
|
258
|
+
|
|
259
|
+
[GPL-3.0-or-later](LICENSE)
|
|
@@ -1,25 +1,35 @@
|
|
|
1
|
-
import type { Controller, DeviceModeSettings, DeviceSettings, User } from './types';
|
|
1
|
+
import type { Controller, DeviceModeSettings, DeviceModeSettingsChanges, DeviceSettings, DeviceSettingsChanges, User } from './types';
|
|
2
2
|
export declare enum Url {
|
|
3
3
|
AUTH = "/user/appUserLogin",
|
|
4
4
|
GET_CONTROLLERS = "/user/devInfoListAll",
|
|
5
|
+
GET_SHARED_CONTROLLERS = "/user/shareDevList",
|
|
6
|
+
GET_CONTROLLER = "/dev/getDeviceInfo",
|
|
5
7
|
GET_DEVICE_SETTINGS = "/dev/getDevSetting",
|
|
8
|
+
SET_DEVICE_SETTINGS = "/dev/updateAdvSetting",
|
|
6
9
|
GET_DEVICE_MODE_SETTINGS = "/dev/getdevModeSettingList",
|
|
7
10
|
SET_DEVICE_MODE_SETTINGS = "/dev/addDevMode"
|
|
8
11
|
}
|
|
9
12
|
export interface Response<T> {
|
|
10
13
|
code: number;
|
|
14
|
+
msg?: string;
|
|
11
15
|
data: T;
|
|
12
16
|
}
|
|
13
17
|
export interface AuthParams {
|
|
14
18
|
email: string;
|
|
15
19
|
password: string;
|
|
16
20
|
}
|
|
17
|
-
interface
|
|
21
|
+
export interface ControllerParams {
|
|
18
22
|
controllerId: string;
|
|
23
|
+
}
|
|
24
|
+
export interface GetDeviceParams extends ControllerParams {
|
|
25
|
+
/** 0 addresses the controller itself, 1..n the devices plugged into its ports */
|
|
19
26
|
port: number;
|
|
20
27
|
}
|
|
21
|
-
interface SetDeviceModeSettingsParams extends GetDeviceParams {
|
|
22
|
-
settings:
|
|
28
|
+
export interface SetDeviceModeSettingsParams extends GetDeviceParams {
|
|
29
|
+
settings: DeviceModeSettingsChanges;
|
|
30
|
+
}
|
|
31
|
+
export interface SetDeviceSettingsParams extends GetDeviceParams {
|
|
32
|
+
settings: DeviceSettingsChanges;
|
|
23
33
|
}
|
|
24
34
|
declare class AcInfinityClient {
|
|
25
35
|
constructor(args: AuthParams);
|
|
@@ -28,13 +38,17 @@ declare class AcInfinityClient {
|
|
|
28
38
|
private email;
|
|
29
39
|
private password;
|
|
30
40
|
authenticate(): Promise<User>;
|
|
31
|
-
private
|
|
41
|
+
private request;
|
|
32
42
|
getControllers(): Promise<Controller[]>;
|
|
43
|
+
getSharedControllers(): Promise<Controller[]>;
|
|
44
|
+
getController({ controllerId }: ControllerParams): Promise<Controller>;
|
|
33
45
|
getDeviceSettings({ controllerId, port }: GetDeviceParams): Promise<DeviceSettings>;
|
|
46
|
+
setDeviceSettings({ controllerId, port, settings }: SetDeviceSettingsParams): Promise<void>;
|
|
47
|
+
private getDeviceName;
|
|
34
48
|
getDeviceModeSettings({ controllerId, port }: GetDeviceParams): Promise<DeviceModeSettings>;
|
|
35
49
|
setDeviceModeSettings({ controllerId, port, settings }: SetDeviceModeSettingsParams): Promise<void>;
|
|
36
50
|
static build(args: AuthParams): Promise<AcInfinityClient>;
|
|
37
51
|
}
|
|
52
|
+
export type { Controller, DeviceModeSettings, DeviceModeSettingsChanges, DeviceSettings, DeviceSettingsChanges, User };
|
|
38
53
|
export { AcInfinityClient };
|
|
39
|
-
export type { Controller, DeviceModeSettings, DeviceSettings, User };
|
|
40
54
|
//# sourceMappingURL=AcInfinityClient.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"AcInfinityClient.d.ts","sourceRoot":"","sources":["../../src/AcInfinityClient.ts"],"names":[],"mappings":"AAGA,OAAO,KAAK,EAAE,UAAU,EAAE,kBAAkB,
|
|
1
|
+
{"version":3,"file":"AcInfinityClient.d.ts","sourceRoot":"","sources":["../../src/AcInfinityClient.ts"],"names":[],"mappings":"AAGA,OAAO,KAAK,EAAE,UAAU,EAAE,kBAAkB,EAAE,yBAAyB,EAAE,cAAc,EAAE,qBAAqB,EAAE,IAAI,EAAE,MAAM,SAAS,CAAC;AAUtI,oBAAY,GAAG;IACb,IAAI,uBAAuB;IAC3B,eAAe,yBAAyB;IACxC,sBAAsB,uBAAuB;IAC7C,cAAc,uBAAuB;IACrC,mBAAmB,uBAAuB;IAC1C,mBAAmB,0BAA0B;IAC7C,wBAAwB,+BAA+B;IACvD,wBAAwB,oBAAoB;CAC7C;AAED,MAAM,WAAW,QAAQ,CAAC,CAAC;IACzB,IAAI,EAAE,MAAM,CAAC;IACb,GAAG,CAAC,EAAE,MAAM,CAAC;IACb,IAAI,EAAE,CAAC,CAAC;CACT;AAED,MAAM,WAAW,UAAU;IACzB,KAAK,EAAE,MAAM,CAAC;IACd,QAAQ,EAAE,MAAM,CAAC;CAClB;AAED,MAAM,WAAW,gBAAgB;IAC/B,YAAY,EAAE,MAAM,CAAC;CACtB;AAED,MAAM,WAAW,eAAgB,SAAQ,gBAAgB;IACvD,iFAAiF;IACjF,IAAI,EAAE,MAAM,CAAC;CACd;AAED,MAAM,WAAW,2BAA4B,SAAQ,eAAe;IAClE,QAAQ,EAAE,yBAAyB,CAAC;CACrC;AAED,MAAM,WAAW,uBAAwB,SAAQ,eAAe;IAC9D,QAAQ,EAAE,qBAAqB,CAAC;CACjC;AAqBD,cAAM,gBAAgB;gBACR,IAAI,EAAE,UAAU;IAiB5B,OAAO,CAAC,GAAG,CAAgB;IAC3B,OAAO,CAAC,KAAK,CAAS;IACtB,OAAO,CAAC,KAAK,CAAS;IACtB,OAAO,CAAC,QAAQ,CAAS;IAEZ,YAAY,IAAI,OAAO,CAAC,IAAI,CAAC;YAiB5B,OAAO;IAUR,cAAc,IAAI,OAAO,CAAC,UAAU,EAAE,CAAC;IAQvC,oBAAoB,IAAI,OAAO,CAAC,UAAU,EAAE,CAAC;IAQ7C,aAAa,CAAC,EAAE,YAAY,EAAE,EAAE,gBAAgB,GAAG,OAAO,CAAC,UAAU,CAAC;IAQtE,iBAAiB,CAAC,EAAE,YAAY,EAAE,IAAI,EAAE,EAAE,eAAe,GAAG,OAAO,CAAC,cAAc,CAAC;IASnF,iBAAiB,CAAC,EAAE,YAAY,EAAE,IAAI,EAAE,QAAQ,EAAE,EAAE,uBAAuB,GAAG,OAAO,CAAC,IAAI,CAAC;YAe1F,aAAa;IAMd,qBAAqB,CAAC,EAAE,YAAY,EAAE,IAAI,EAAE,EAAE,eAAe,GAAG,OAAO,CAAC,kBAAkB,CAAC;IAS3F,qBAAqB,CAAC,EAAE,YAAY,EAAE,IAAI,EAAE,QAAQ,EAAE,EAAE,2BAA2B,GAAG,OAAO,CAAC,IAAI,CAAC;WAYnG,KAAK,CAAC,IAAI,EAAE,UAAU;CAKpC;AAED,YAAY,EAAE,UAAU,EAAE,kBAAkB,EAAE,yBAAyB,EAAE,cAAc,EAAE,qBAAqB,EAAE,IAAI,EAAE,CAAC;AACvH,OAAO,EAAE,gBAAgB,EAAE,CAAC"}
|
|
@@ -11,10 +11,26 @@ var Url;
|
|
|
11
11
|
(function (Url) {
|
|
12
12
|
Url["AUTH"] = "/user/appUserLogin";
|
|
13
13
|
Url["GET_CONTROLLERS"] = "/user/devInfoListAll";
|
|
14
|
+
Url["GET_SHARED_CONTROLLERS"] = "/user/shareDevList";
|
|
15
|
+
Url["GET_CONTROLLER"] = "/dev/getDeviceInfo";
|
|
14
16
|
Url["GET_DEVICE_SETTINGS"] = "/dev/getDevSetting";
|
|
17
|
+
Url["SET_DEVICE_SETTINGS"] = "/dev/updateAdvSetting";
|
|
15
18
|
Url["GET_DEVICE_MODE_SETTINGS"] = "/dev/getdevModeSettingList";
|
|
16
19
|
Url["SET_DEVICE_MODE_SETTINGS"] = "/dev/addDevMode";
|
|
17
20
|
})(Url || (exports.Url = Url = {}));
|
|
21
|
+
// fahrenheit values below freezing are raised to 32 before sending mode settings
|
|
22
|
+
const FAHRENHEIT_FIELDS = ['devHtf', 'devLtf', 'targetTempF', 'waterTempHighValueF', 'waterTempLowValueF', 'waterTempTargetValueF'];
|
|
23
|
+
// only send flat values: nested objects and empty fields are left out
|
|
24
|
+
const toFormPayload = (values) => Object.fromEntries(Object.entries(values).filter(([, value]) => value !== null && value !== undefined && typeof value !== 'object'));
|
|
25
|
+
const clampFahrenheit = (payload) => {
|
|
26
|
+
for (const field of FAHRENHEIT_FIELDS) {
|
|
27
|
+
const value = payload[field];
|
|
28
|
+
if (typeof value === 'number' && value < 32) {
|
|
29
|
+
payload[field] = 32;
|
|
30
|
+
}
|
|
31
|
+
}
|
|
32
|
+
return payload;
|
|
33
|
+
};
|
|
18
34
|
class AcInfinityClient {
|
|
19
35
|
constructor(args) {
|
|
20
36
|
const { email, password } = args;
|
|
@@ -46,40 +62,68 @@ class AcInfinityClient {
|
|
|
46
62
|
this.api.defaults.headers.common.token = user.id;
|
|
47
63
|
return user;
|
|
48
64
|
}
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
65
|
+
async request(url, params) {
|
|
66
|
+
const response = await this.api.post(url, params);
|
|
67
|
+
if (response.data.code !== 200) {
|
|
68
|
+
throw new errors_1.ApiError(response.data.code, response.data.msg);
|
|
52
69
|
}
|
|
70
|
+
return response.data.data;
|
|
53
71
|
}
|
|
54
72
|
async getControllers() {
|
|
55
|
-
const
|
|
73
|
+
const controllers = await this.request(Url.GET_CONTROLLERS, {
|
|
56
74
|
userId: this.token,
|
|
57
75
|
});
|
|
58
|
-
return
|
|
76
|
+
return controllers.map((device) => types_1.ControllerSchema.parse(device));
|
|
77
|
+
}
|
|
78
|
+
async getSharedControllers() {
|
|
79
|
+
const controllers = await this.request(Url.GET_SHARED_CONTROLLERS, {
|
|
80
|
+
userId: this.token,
|
|
81
|
+
});
|
|
82
|
+
return controllers.map((device) => types_1.ControllerSchema.parse(device));
|
|
83
|
+
}
|
|
84
|
+
async getController({ controllerId }) {
|
|
85
|
+
const controller = await this.request(Url.GET_CONTROLLER, {
|
|
86
|
+
devId: controllerId,
|
|
87
|
+
});
|
|
88
|
+
return types_1.ControllerSchema.parse(controller);
|
|
59
89
|
}
|
|
60
90
|
async getDeviceSettings({ controllerId, port }) {
|
|
61
|
-
const
|
|
91
|
+
const settings = await this.request(Url.GET_DEVICE_SETTINGS, {
|
|
92
|
+
devId: controllerId,
|
|
93
|
+
port,
|
|
94
|
+
});
|
|
95
|
+
return types_1.DeviceSettingsSchema.parse(settings);
|
|
96
|
+
}
|
|
97
|
+
async setDeviceSettings({ controllerId, port, settings }) {
|
|
98
|
+
const vendorChanges = (0, types_1.encodeDeviceSettings)(settings);
|
|
99
|
+
const currentSettings = await this.request(Url.GET_DEVICE_SETTINGS, {
|
|
62
100
|
devId: controllerId,
|
|
63
101
|
port,
|
|
64
102
|
});
|
|
65
|
-
|
|
103
|
+
// the name is required, but often not stored in the settings themselves
|
|
104
|
+
const devName = vendorChanges.devName ?? currentSettings.devName ?? (await this.getDeviceName({ controllerId, port }));
|
|
105
|
+
const payload = toFormPayload({ ...currentSettings, ...vendorChanges, devName });
|
|
106
|
+
await this.request(Url.SET_DEVICE_SETTINGS, payload);
|
|
107
|
+
}
|
|
108
|
+
async getDeviceName({ controllerId, port }) {
|
|
109
|
+
const controller = await this.getController({ controllerId });
|
|
110
|
+
return port === 0 ? controller.name : controller.deviceInfo.ports.find((p) => p.port === port)?.deviceName;
|
|
66
111
|
}
|
|
67
112
|
async getDeviceModeSettings({ controllerId, port }) {
|
|
68
|
-
const
|
|
113
|
+
const settings = await this.request(Url.GET_DEVICE_MODE_SETTINGS, {
|
|
69
114
|
devId: controllerId,
|
|
70
115
|
port,
|
|
71
116
|
});
|
|
72
|
-
return types_1.DeviceModeSettingsSchema.parse(
|
|
117
|
+
return types_1.DeviceModeSettingsSchema.parse(settings);
|
|
73
118
|
}
|
|
74
119
|
async setDeviceModeSettings({ controllerId, port, settings }) {
|
|
75
|
-
const
|
|
120
|
+
const vendorChanges = (0, types_1.encodeDeviceModeSettings)(settings);
|
|
121
|
+
const currentSettings = await this.request(Url.GET_DEVICE_MODE_SETTINGS, {
|
|
76
122
|
devId: controllerId,
|
|
77
123
|
port,
|
|
78
124
|
});
|
|
79
|
-
const
|
|
80
|
-
|
|
81
|
-
const response = await this.api.post(Url.SET_DEVICE_MODE_SETTINGS, payload);
|
|
82
|
-
this.ensureSuccess(response.data);
|
|
125
|
+
const payload = clampFahrenheit(toFormPayload({ ...currentSettings, ...vendorChanges, onlyUpdateSpeed: 0 }));
|
|
126
|
+
await this.request(Url.SET_DEVICE_MODE_SETTINGS, payload);
|
|
83
127
|
}
|
|
84
128
|
static async build(args) {
|
|
85
129
|
const instance = new AcInfinityClient(args);
|
|
@@ -1,5 +1,10 @@
|
|
|
1
1
|
declare class AuthenticationError extends Error {
|
|
2
2
|
constructor(message?: string);
|
|
3
3
|
}
|
|
4
|
-
|
|
4
|
+
declare class ApiError extends Error {
|
|
5
|
+
readonly code: number;
|
|
6
|
+
readonly msg?: string | undefined;
|
|
7
|
+
constructor(code: number, msg?: string | undefined);
|
|
8
|
+
}
|
|
9
|
+
export { ApiError, AuthenticationError };
|
|
5
10
|
//# sourceMappingURL=index.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../../src/errors/index.ts"],"names":[],"mappings":"AAAA,cAAM,mBAAoB,SAAQ,KAAK;gBACzB,OAAO,CAAC,EAAE,MAAM;CAQ7B;AAED,OAAO,EAAE,mBAAmB,EAAE,CAAC"}
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../../src/errors/index.ts"],"names":[],"mappings":"AAAA,cAAM,mBAAoB,SAAQ,KAAK;gBACzB,OAAO,CAAC,EAAE,MAAM;CAQ7B;AAED,cAAM,QAAS,SAAQ,KAAK;aAER,IAAI,EAAE,MAAM;aACZ,GAAG,CAAC,EAAE,MAAM;gBADZ,IAAI,EAAE,MAAM,EACZ,GAAG,CAAC,EAAE,MAAM,YAAA;CAS/B;AAED,OAAO,EAAE,QAAQ,EAAE,mBAAmB,EAAE,CAAC"}
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
"use strict";
|
|
2
2
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
-
exports.AuthenticationError = void 0;
|
|
3
|
+
exports.AuthenticationError = exports.ApiError = void 0;
|
|
4
4
|
class AuthenticationError extends Error {
|
|
5
5
|
constructor(message) {
|
|
6
6
|
super(message);
|
|
@@ -10,3 +10,16 @@ class AuthenticationError extends Error {
|
|
|
10
10
|
}
|
|
11
11
|
}
|
|
12
12
|
exports.AuthenticationError = AuthenticationError;
|
|
13
|
+
class ApiError extends Error {
|
|
14
|
+
code;
|
|
15
|
+
msg;
|
|
16
|
+
constructor(code, msg) {
|
|
17
|
+
super(`API request failed [code="${code}", msg="${msg}"]`);
|
|
18
|
+
this.code = code;
|
|
19
|
+
this.msg = msg;
|
|
20
|
+
this.name = this.constructor.name;
|
|
21
|
+
Error.captureStackTrace(this, this.constructor);
|
|
22
|
+
Object.setPrototypeOf(this, ApiError.prototype);
|
|
23
|
+
}
|
|
24
|
+
}
|
|
25
|
+
exports.ApiError = ApiError;
|
package/build/src/index.d.ts
CHANGED
|
@@ -1,2 +1,5 @@
|
|
|
1
1
|
export * from './AcInfinityClient';
|
|
2
|
+
export * from './errors';
|
|
3
|
+
export type { ControllerDeviceInfo, ControllerPort, ControllerSensor } from './types';
|
|
4
|
+
export { ControllerDeviceType, DeviceMode, DeviceType, SensorType, TemperatureUnit } from './types';
|
|
2
5
|
//# sourceMappingURL=index.d.ts.map
|
package/build/src/index.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/index.ts"],"names":[],"mappings":"AAAA,cAAc,oBAAoB,CAAC"}
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/index.ts"],"names":[],"mappings":"AAAA,cAAc,oBAAoB,CAAC;AACnC,cAAc,UAAU,CAAC;AACzB,YAAY,EAAE,oBAAoB,EAAE,cAAc,EAAE,gBAAgB,EAAE,MAAM,SAAS,CAAC;AACtF,OAAO,EAAE,oBAAoB,EAAE,UAAU,EAAE,UAAU,EAAE,UAAU,EAAE,eAAe,EAAE,MAAM,SAAS,CAAC"}
|
package/build/src/index.js
CHANGED
|
@@ -14,4 +14,12 @@ var __exportStar = (this && this.__exportStar) || function(m, exports) {
|
|
|
14
14
|
for (var p in m) if (p !== "default" && !Object.prototype.hasOwnProperty.call(exports, p)) __createBinding(exports, m, p);
|
|
15
15
|
};
|
|
16
16
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
17
|
+
exports.TemperatureUnit = exports.SensorType = exports.DeviceType = exports.DeviceMode = exports.ControllerDeviceType = void 0;
|
|
17
18
|
__exportStar(require("./AcInfinityClient"), exports);
|
|
19
|
+
__exportStar(require("./errors"), exports);
|
|
20
|
+
var types_1 = require("./types");
|
|
21
|
+
Object.defineProperty(exports, "ControllerDeviceType", { enumerable: true, get: function () { return types_1.ControllerDeviceType; } });
|
|
22
|
+
Object.defineProperty(exports, "DeviceMode", { enumerable: true, get: function () { return types_1.DeviceMode; } });
|
|
23
|
+
Object.defineProperty(exports, "DeviceType", { enumerable: true, get: function () { return types_1.DeviceType; } });
|
|
24
|
+
Object.defineProperty(exports, "SensorType", { enumerable: true, get: function () { return types_1.SensorType; } });
|
|
25
|
+
Object.defineProperty(exports, "TemperatureUnit", { enumerable: true, get: function () { return types_1.TemperatureUnit; } });
|