mirror of
https://github.com/bourquep/mysa-js-sdk.git
synced 2026-02-04 09:41:07 +00:00
Compare commits
21 Commits
v1.4.0
...
dependabot
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
05c7a439af | ||
|
|
85c123d2aa | ||
|
|
0524bcea73 | ||
|
|
bb10ba4616 | ||
|
|
5366ea6fc9 | ||
|
|
baa7941cfc | ||
|
|
ef60db37d5 | ||
|
|
f1525cd1f1 | ||
|
|
3b2a020ac7 | ||
|
|
cbac285b1e | ||
|
|
ca127483c1 | ||
|
|
e320d658e8 | ||
|
|
c8dac38563 | ||
|
|
94acdede23 | ||
|
|
d007c2d745 | ||
|
|
5d9981f9e0 | ||
|
|
2f2cdef0ee | ||
|
|
193f67226b | ||
|
|
ef8d787e05 | ||
|
|
0c71ed95ce | ||
|
|
d861a50136 |
2
.github/dependabot.yml
vendored
2
.github/dependabot.yml
vendored
@@ -8,7 +8,7 @@ updates:
|
|||||||
- package-ecosystem: 'npm' # See documentation for possible values
|
- package-ecosystem: 'npm' # See documentation for possible values
|
||||||
directory: '/' # Location of package manifests
|
directory: '/' # Location of package manifests
|
||||||
schedule:
|
schedule:
|
||||||
interval: 'daily'
|
interval: 'weekly'
|
||||||
labels:
|
labels:
|
||||||
- 'dependencies'
|
- 'dependencies'
|
||||||
commit-message:
|
commit-message:
|
||||||
|
|||||||
@@ -65,9 +65,10 @@ async function main() {
|
|||||||
client.emitter.on('statusChanged', (status) => {
|
client.emitter.on('statusChanged', (status) => {
|
||||||
try {
|
try {
|
||||||
const device = devices.DevicesObj[status.deviceId];
|
const device = devices.DevicesObj[status.deviceId];
|
||||||
const watts = status.current !== undefined ? status.current * device.Voltage : undefined;
|
const watts =
|
||||||
|
status.current !== undefined && device.Voltage !== undefined ? status.current * device.Voltage : undefined;
|
||||||
rootLogger.info(
|
rootLogger.info(
|
||||||
`[${status.deviceId}] '${device.Name}' status changed: ${status.temperature}°C, ${status.humidity}%, ${watts ?? 'na'}W`
|
`[${status.deviceId}] '${device.Name ?? 'Unknown'}' status changed: ${status.temperature}°C, ${status.humidity}%, ${watts ?? 'na'}W`
|
||||||
);
|
);
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
rootLogger.error(error, `Error processing status update for device '${status.deviceId}'`);
|
rootLogger.error(error, `Error processing status update for device '${status.deviceId}'`);
|
||||||
@@ -77,7 +78,9 @@ async function main() {
|
|||||||
client.emitter.on('setPointChanged', (change) => {
|
client.emitter.on('setPointChanged', (change) => {
|
||||||
try {
|
try {
|
||||||
const device = devices.DevicesObj[change.deviceId];
|
const device = devices.DevicesObj[change.deviceId];
|
||||||
rootLogger.info(`'${device.Name}' setpoint changed from ${change.previousSetPoint} to ${change.newSetPoint}`);
|
rootLogger.info(
|
||||||
|
`'${device.Name ?? 'Unknown'}' setpoint changed from ${change.previousSetPoint} to ${change.newSetPoint}`
|
||||||
|
);
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
rootLogger.error(error, `Error processing setpoint update for device '${change.deviceId}'`);
|
rootLogger.error(error, `Error processing setpoint update for device '${change.deviceId}'`);
|
||||||
}
|
}
|
||||||
@@ -86,7 +89,7 @@ async function main() {
|
|||||||
client.emitter.on('stateChanged', (change) => {
|
client.emitter.on('stateChanged', (change) => {
|
||||||
try {
|
try {
|
||||||
const device = devices.DevicesObj[change.deviceId];
|
const device = devices.DevicesObj[change.deviceId];
|
||||||
rootLogger.info(change, `'${device.Name}' state changed.`);
|
rootLogger.info(change, `'${device.Name ?? 'Unknown'}' state changed.`);
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
rootLogger.error(error, `Error processing state update for device '${change.deviceId}'`);
|
rootLogger.error(error, `Error processing state update for device '${change.deviceId}'`);
|
||||||
}
|
}
|
||||||
@@ -96,7 +99,7 @@ async function main() {
|
|||||||
await Promise.all(
|
await Promise.all(
|
||||||
Object.entries(devices.DevicesObj).map(async ([deviceId, device]) => {
|
Object.entries(devices.DevicesObj).map(async ([deviceId, device]) => {
|
||||||
const serial = await client.getDeviceSerialNumber(deviceId);
|
const serial = await client.getDeviceSerialNumber(deviceId);
|
||||||
rootLogger.info(`Serial number for device '${deviceId}' (${device.Name}): ${serial}`);
|
rootLogger.info(`Serial number for device '${deviceId}' (${device.Name ?? 'Unknown'}): ${serial}`);
|
||||||
|
|
||||||
await client.startRealtimeUpdates(deviceId);
|
await client.startRealtimeUpdates(deviceId);
|
||||||
})
|
})
|
||||||
|
|||||||
2817
package-lock.json
generated
2817
package-lock.json
generated
File diff suppressed because it is too large
Load Diff
41
package.json
41
package.json
@@ -50,34 +50,35 @@
|
|||||||
"brace-expansion": "^2.0.2"
|
"brace-expansion": "^2.0.2"
|
||||||
},
|
},
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@aws-sdk/client-iot": "3.920.0",
|
"@aws-sdk/client-iot": "3.936.0",
|
||||||
"@aws-sdk/credential-providers": "3.901.0",
|
"@aws-sdk/credential-providers": "3.940.0",
|
||||||
"amazon-cognito-identity-js": "6.3.15",
|
"amazon-cognito-identity-js": "6.3.16",
|
||||||
"aws-iot-device-sdk-v2": "1.22.0",
|
"aws-iot-device-sdk-v2": "1.23.1",
|
||||||
"dayjs": "1.11.18",
|
"dayjs": "1.11.19",
|
||||||
"lodash": "4.17.21"
|
"lodash": "4.17.21",
|
||||||
|
"nanoid": "5.1.6"
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"@eslint/js": "9.38.0",
|
"@eslint/js": "9.39.1",
|
||||||
"@semantic-release/npm": "13.1.1",
|
"@semantic-release/npm": "13.1.2",
|
||||||
"@types/lodash": "4.17.20",
|
"@types/lodash": "4.17.21",
|
||||||
"@types/node": "24.9.2",
|
"@types/node": "24.10.1",
|
||||||
"conventional-changelog-conventionalcommits": "9.1.0",
|
"conventional-changelog-conventionalcommits": "9.1.0",
|
||||||
"dotenv": "17.2.3",
|
"dotenv": "17.2.3",
|
||||||
"eslint": "9.38.0",
|
"eslint": "9.39.1",
|
||||||
"eslint-plugin-jsdoc": "61.1.11",
|
"eslint-plugin-jsdoc": "61.4.1",
|
||||||
"eslint-plugin-tsdoc": "0.4.0",
|
"eslint-plugin-tsdoc": "0.5.0",
|
||||||
"pino": "9.13.0",
|
"pino": "10.1.0",
|
||||||
"pino-pretty": "13.0.0",
|
"pino-pretty": "13.1.2",
|
||||||
"prettier": "3.6.2",
|
"prettier": "3.6.2",
|
||||||
"prettier-plugin-jsdoc": "1.5.0",
|
"prettier-plugin-jsdoc": "1.5.0",
|
||||||
"prettier-plugin-organize-imports": "4.3.0",
|
"prettier-plugin-organize-imports": "4.3.0",
|
||||||
"semantic-release": "25.0.1",
|
"semantic-release": "25.0.2",
|
||||||
"tsup": "8.5.0",
|
"tsup": "8.5.0",
|
||||||
"tsx": "4.20.6",
|
"tsx": "4.21.0",
|
||||||
"typedoc": "0.28.13",
|
"typedoc": "0.28.14",
|
||||||
"typedoc-material-theme": "1.4.0",
|
"typedoc-material-theme": "1.4.1",
|
||||||
"typescript": "5.9.3",
|
"typescript": "5.9.3",
|
||||||
"typescript-eslint": "8.41.0"
|
"typescript-eslint": "8.46.2"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -19,8 +19,10 @@ import {
|
|||||||
CognitoUserSession
|
CognitoUserSession
|
||||||
} from 'amazon-cognito-identity-js';
|
} from 'amazon-cognito-identity-js';
|
||||||
import { iot, mqtt } from 'aws-iot-device-sdk-v2';
|
import { iot, mqtt } from 'aws-iot-device-sdk-v2';
|
||||||
import dayjs from 'dayjs';
|
import { hash } from 'crypto';
|
||||||
|
import dayjs, { Dayjs } from 'dayjs';
|
||||||
import duration from 'dayjs/plugin/duration.js';
|
import duration from 'dayjs/plugin/duration.js';
|
||||||
|
import { customAlphabet } from 'nanoid';
|
||||||
import { MqttPublishError, MysaApiError, UnauthenticatedError } from './Errors';
|
import { MqttPublishError, MysaApiError, UnauthenticatedError } from './Errors';
|
||||||
import { Logger, VoidLogger } from './Logger';
|
import { Logger, VoidLogger } from './Logger';
|
||||||
import { MysaApiClientEventTypes } from './MysaApiClientEventTypes';
|
import { MysaApiClientEventTypes } from './MysaApiClientEventTypes';
|
||||||
@@ -29,6 +31,8 @@ import { MysaDeviceMode, MysaFanSpeedMode } from './MysaDeviceMode';
|
|||||||
|
|
||||||
dayjs.extend(duration);
|
dayjs.extend(duration);
|
||||||
|
|
||||||
|
const getRandomClientId = customAlphabet('1234567890abcdefghijklmnopqrstuvwxyz', 8);
|
||||||
|
|
||||||
/** Options for MQTT publish operations. */
|
/** Options for MQTT publish operations. */
|
||||||
export interface MqttPublishOptions {
|
export interface MqttPublishOptions {
|
||||||
/** Maximum number of publish attempts before failing (default: 5). */
|
/** Maximum number of publish attempts before failing (default: 5). */
|
||||||
@@ -86,6 +90,18 @@ export class MysaApiClient {
|
|||||||
/** A promise that resolves to the MQTT connection used for real-time updates. */
|
/** A promise that resolves to the MQTT connection used for real-time updates. */
|
||||||
private _mqttConnectionPromise?: Promise<mqtt.MqttClientConnection>;
|
private _mqttConnectionPromise?: Promise<mqtt.MqttClientConnection>;
|
||||||
|
|
||||||
|
/** Stable per-process MQTT client id (prevents collisions between multiple processes). */
|
||||||
|
private _mqttClientId?: string;
|
||||||
|
|
||||||
|
/** Expiration time of the credentials currently in use by the MQTT client. */
|
||||||
|
private _mqttCredentialsExpiration?: Dayjs;
|
||||||
|
|
||||||
|
/** Interrupt timestamps for storm / collision detection. */
|
||||||
|
private _mqttInterrupts: number[] = [];
|
||||||
|
|
||||||
|
/** Whether a forced MQTT reset is currently in progress (guards against re-entrancy). */
|
||||||
|
private _mqttResetInProgress = false;
|
||||||
|
|
||||||
/** The device IDs that are currently being updated in real-time, mapped to their respective timeouts. */
|
/** The device IDs that are currently being updated in real-time, mapped to their respective timeouts. */
|
||||||
private _realtimeDeviceIds: Map<string, NodeJS.Timeout> = new Map();
|
private _realtimeDeviceIds: Map<string, NodeJS.Timeout> = new Map();
|
||||||
|
|
||||||
@@ -173,6 +189,9 @@ export class MysaApiClient {
|
|||||||
async login(emailAddress: string, password: string): Promise<void> {
|
async login(emailAddress: string, password: string): Promise<void> {
|
||||||
this._cognitoUser = undefined;
|
this._cognitoUser = undefined;
|
||||||
this._cognitoUserSession = undefined;
|
this._cognitoUserSession = undefined;
|
||||||
|
this._mqttClientId = undefined;
|
||||||
|
this._mqttInterrupts = [];
|
||||||
|
|
||||||
this.emitter.emit('sessionChanged', this.session);
|
this.emitter.emit('sessionChanged', this.session);
|
||||||
|
|
||||||
return new Promise((resolve, reject) => {
|
return new Promise((resolve, reject) => {
|
||||||
@@ -481,13 +500,15 @@ export class MysaApiClient {
|
|||||||
|
|
||||||
const timer = setInterval(async () => {
|
const timer = setInterval(async () => {
|
||||||
this._logger.debug(`Sending request to keep-alive publishing device status for '${deviceId}'...`);
|
this._logger.debug(`Sending request to keep-alive publishing device status for '${deviceId}'...`);
|
||||||
|
|
||||||
|
const connection = await this._getMqttConnection();
|
||||||
const payload = serializeMqttPayload<StartPublishingDeviceStatus>({
|
const payload = serializeMqttPayload<StartPublishingDeviceStatus>({
|
||||||
Device: deviceId,
|
Device: deviceId,
|
||||||
MsgType: InMessageType.START_PUBLISHING_DEVICE_STATUS,
|
MsgType: InMessageType.START_PUBLISHING_DEVICE_STATUS,
|
||||||
Timestamp: dayjs().unix(),
|
Timestamp: dayjs().unix(),
|
||||||
Timeout: RealtimeKeepAliveInterval.asSeconds()
|
Timeout: RealtimeKeepAliveInterval.asSeconds()
|
||||||
});
|
});
|
||||||
await this._publishWithRetry(mqttConnection, `/v1/dev/${deviceId}/in`, payload, mqtt.QoS.AtLeastOnce);
|
await this._publishWithRetry(connection, `/v1/dev/${deviceId}/in`, payload, mqtt.QoS.AtLeastOnce);
|
||||||
}, RealtimeKeepAliveInterval.subtract(10, 'seconds').asMilliseconds());
|
}, RealtimeKeepAliveInterval.subtract(10, 'seconds').asMilliseconds());
|
||||||
|
|
||||||
this._realtimeDeviceIds.set(deviceId, timer);
|
this._realtimeDeviceIds.set(deviceId, timer);
|
||||||
@@ -601,6 +622,9 @@ export class MysaApiClient {
|
|||||||
const transientMarkers = [
|
const transientMarkers = [
|
||||||
'AWS_ERROR_MQTT_TIMEOUT',
|
'AWS_ERROR_MQTT_TIMEOUT',
|
||||||
'AWS_ERROR_MQTT_NO_CONNECTION',
|
'AWS_ERROR_MQTT_NO_CONNECTION',
|
||||||
|
'AWS_ERROR_MQTT_UNEXPECTED_HANGUP',
|
||||||
|
'UNEXPECTED_HANGUP',
|
||||||
|
'AWS_ERROR_MQTT_CONNECTION_DESTROYED',
|
||||||
'Time limit between request and response',
|
'Time limit between request and response',
|
||||||
'timeout'
|
'timeout'
|
||||||
];
|
];
|
||||||
@@ -691,40 +715,119 @@ export class MysaApiClient {
|
|||||||
});
|
});
|
||||||
const credentials = await credentialsProvider();
|
const credentials = await credentialsProvider();
|
||||||
|
|
||||||
// Stable client id + persistent session to retain QoS1 queue & subscriptions across reconnects.
|
if (!credentials.expiration) {
|
||||||
const stableClientId = `mysa-js-sdk-${this.session?.username ?? ''}`;
|
throw new Error('MQTT credentials do not have an expiration time.');
|
||||||
|
}
|
||||||
|
|
||||||
|
this._mqttCredentialsExpiration = dayjs(credentials.expiration);
|
||||||
|
|
||||||
|
this._logger.debug(`MQTT credentials expiration: ${this._mqttCredentialsExpiration.format()}`);
|
||||||
|
|
||||||
|
if (!this._mqttCredentialsExpiration.isAfter(dayjs())) {
|
||||||
|
this._mqttCredentialsExpiration = undefined;
|
||||||
|
throw new Error('MQTT credentials are already expired.');
|
||||||
|
}
|
||||||
|
|
||||||
|
// Per-process stable client id. Random suffix avoids collisions with other running processes.
|
||||||
|
if (!this._mqttClientId) {
|
||||||
|
const username = this.session?.username ?? 'anon';
|
||||||
|
const usernameHash = hash('sha1', username);
|
||||||
|
this._mqttClientId = `mysa-js-sdk-${usernameHash}-${process.pid}-${getRandomClientId()}`;
|
||||||
|
}
|
||||||
|
|
||||||
const builder = iot.AwsIotMqttConnectionConfigBuilder.new_with_websockets()
|
const builder = iot.AwsIotMqttConnectionConfigBuilder.new_with_websockets()
|
||||||
.with_credentials(AwsRegion, credentials.accessKeyId, credentials.secretAccessKey, credentials.sessionToken)
|
.with_credentials(AwsRegion, credentials.accessKeyId, credentials.secretAccessKey, credentials.sessionToken)
|
||||||
.with_endpoint(MqttEndpoint)
|
.with_endpoint(MqttEndpoint)
|
||||||
.with_client_id(stableClientId)
|
.with_client_id(this._mqttClientId)
|
||||||
.with_clean_session(false)
|
.with_clean_session(false)
|
||||||
.with_keep_alive_seconds(30)
|
.with_keep_alive_seconds(30)
|
||||||
.with_ping_timeout_ms(3000)
|
.with_ping_timeout_ms(3000)
|
||||||
.with_protocol_operation_timeout_ms(60000);
|
.with_protocol_operation_timeout_ms(60000)
|
||||||
|
.with_reconnect_min_sec(1)
|
||||||
|
.with_reconnect_max_sec(30);
|
||||||
|
|
||||||
const config = builder.build();
|
const config = builder.build();
|
||||||
const client = new mqtt.MqttClient();
|
const client = new mqtt.MqttClient();
|
||||||
const connection = client.new_connection(config);
|
const connection = client.new_connection(config);
|
||||||
|
|
||||||
connection.on('connect', () => {
|
connection.on('connect', () => {
|
||||||
this._logger.debug('MQTT connect');
|
this._logger.debug(`MQTT connect (clientId=${this._mqttClientId})`);
|
||||||
});
|
});
|
||||||
|
|
||||||
connection.on('connection_success', () => {
|
connection.on('connection_success', () => {
|
||||||
this._logger.debug('MQTT connection_success');
|
this._logger.debug(`MQTT connection_success (clientId=${this._mqttClientId})`);
|
||||||
});
|
});
|
||||||
|
|
||||||
connection.on('connection_failure', (e) => {
|
connection.on('connection_failure', (e) => {
|
||||||
this._logger.error('MQTT connection_failure', e);
|
this._logger.error(`MQTT connection_failure (clientId=${this._mqttClientId})`, e);
|
||||||
});
|
});
|
||||||
|
|
||||||
connection.on('interrupt', (e) => {
|
connection.on('interrupt', async (e) => {
|
||||||
this._logger.warn('MQTT interrupt', e);
|
this._logger.warn(`MQTT interrupt (clientId=${this._mqttClientId})`, e);
|
||||||
|
|
||||||
|
// Track recent interrupts
|
||||||
|
const now = Date.now();
|
||||||
|
|
||||||
|
// Keep only last 60s
|
||||||
|
this._mqttInterrupts = this._mqttInterrupts.filter((t) => now - t < 60000);
|
||||||
|
this._mqttInterrupts.push(now);
|
||||||
|
|
||||||
|
const areCredentialsExpired = !(this._mqttCredentialsExpiration?.isAfter(dayjs()) ?? false);
|
||||||
|
|
||||||
|
if ((this._mqttInterrupts.length > 5 || areCredentialsExpired) && !this._mqttResetInProgress) {
|
||||||
|
this._mqttResetInProgress = true;
|
||||||
|
|
||||||
|
if (this._mqttInterrupts.length > 5) {
|
||||||
|
this._logger.warn(
|
||||||
|
`High interrupt rate (${this._mqttInterrupts.length}/60s). Possible clientId collision. Regenerating clientId and resetting connection...`
|
||||||
|
);
|
||||||
|
} else {
|
||||||
|
this._logger.warn(`Credentials expired. Regenerating clientId and resetting connection...`);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Force new client id to escape collision; close current connection
|
||||||
|
this._mqttClientId = undefined;
|
||||||
|
|
||||||
|
this._mqttCredentialsExpiration = undefined;
|
||||||
|
|
||||||
|
// Clear interrupts
|
||||||
|
this._mqttInterrupts = [];
|
||||||
|
|
||||||
|
// Explicitly clear promise first to prevent reuse while disconnecting
|
||||||
|
// (publishers calling _getMqttConnection() will create a new one)
|
||||||
|
this._mqttConnectionPromise = undefined;
|
||||||
|
|
||||||
|
try {
|
||||||
|
await connection.disconnect();
|
||||||
|
|
||||||
|
try {
|
||||||
|
this._logger.debug('Old MQTT connection disconnected; establishing new connection...');
|
||||||
|
const newConnection = await this._getMqttConnection();
|
||||||
|
|
||||||
|
for (const deviceId of Array.from(this._realtimeDeviceIds.keys())) {
|
||||||
|
const topic = `/v1/dev/${deviceId}/out`;
|
||||||
|
this._logger.debug(`Re-subscribing to ${topic}`);
|
||||||
|
await newConnection.subscribe(topic, mqtt.QoS.AtLeastOnce, (_topic, payload) => {
|
||||||
|
this._processMqttMessage(payload);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
this._logger.info('MQTT connection rebuilt successfully after interrupt storm or credentials expiration');
|
||||||
|
} catch (err) {
|
||||||
|
this._logger.error('Failed to re-subscribe after interrupt storm or credentials expiration', err);
|
||||||
|
}
|
||||||
|
} catch (error) {
|
||||||
|
this._logger.error('Error during MQTT reset', error);
|
||||||
|
} finally {
|
||||||
|
this._mqttResetInProgress = false;
|
||||||
|
}
|
||||||
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
connection.on('resume', async (returnCode, sessionPresent) => {
|
connection.on('resume', async (returnCode, sessionPresent) => {
|
||||||
this._logger.info(`MQTT resume returnCode=${returnCode} sessionPresent=${sessionPresent}`);
|
this._logger.info(
|
||||||
|
`MQTT resume returnCode=${returnCode} sessionPresent=${sessionPresent} clientId=${this._mqttClientId}`
|
||||||
|
);
|
||||||
|
|
||||||
if (!sessionPresent) {
|
if (!sessionPresent) {
|
||||||
this._logger.info('No session present, re-subscribing each device');
|
this._logger.info('No session present, re-subscribing each device');
|
||||||
@@ -743,12 +846,13 @@ export class MysaApiClient {
|
|||||||
});
|
});
|
||||||
|
|
||||||
connection.on('error', (e) => {
|
connection.on('error', (e) => {
|
||||||
this._logger.error('MQTT error', e);
|
this._logger.error(`MQTT error (clientId=${this._mqttClientId})`, e);
|
||||||
});
|
});
|
||||||
|
|
||||||
connection.on('closed', () => {
|
connection.on('closed', () => {
|
||||||
this._logger.info('MQTT connection closed');
|
this._logger.info('MQTT connection closed');
|
||||||
this._mqttConnectionPromise = undefined;
|
this._mqttConnectionPromise = undefined;
|
||||||
|
this._mqttCredentialsExpiration = undefined;
|
||||||
});
|
});
|
||||||
|
|
||||||
await connection.connect();
|
await connection.connect();
|
||||||
|
|||||||
@@ -10,9 +10,9 @@ export interface BrandInfo {
|
|||||||
/** Unique identifier for the brand */
|
/** Unique identifier for the brand */
|
||||||
Id: number;
|
Id: number;
|
||||||
/** Remote control model number for the AC device */
|
/** Remote control model number for the AC device */
|
||||||
remoteModelNumber: string;
|
remoteModelNumber?: string;
|
||||||
/** Original Equipment Manufacturer brand name */
|
/** Original Equipment Manufacturer brand name */
|
||||||
OEMBrand: string;
|
OEMBrand?: string;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -56,55 +56,55 @@ export interface ModeObj {
|
|||||||
*/
|
*/
|
||||||
export interface DeviceBase {
|
export interface DeviceBase {
|
||||||
/** Button digital input configuration value */
|
/** Button digital input configuration value */
|
||||||
ButtonDI: number;
|
ButtonDI?: number;
|
||||||
/** Maximum current rating as a string value */
|
/** Maximum current rating as a string value */
|
||||||
MaxCurrent: string;
|
MaxCurrent?: string;
|
||||||
/** Device model identifier string */
|
/** Device model identifier string */
|
||||||
Model: string;
|
Model: string;
|
||||||
/** Button average value configuration */
|
/** Button average value configuration */
|
||||||
ButtonAVE: number;
|
ButtonAVE?: number;
|
||||||
/** Operating voltage of the device */
|
/** Operating voltage of the device */
|
||||||
Voltage: number;
|
Voltage?: number;
|
||||||
/** Button polling interval configuration */
|
/** Button polling interval configuration */
|
||||||
ButtonPolling: number;
|
ButtonPolling?: number;
|
||||||
/** Minimum brightness level (0-100) */
|
/** Minimum brightness level (0-100) */
|
||||||
MinBrightness: number;
|
MinBrightness?: number;
|
||||||
/** User-assigned device name */
|
/** User-assigned device name */
|
||||||
Name: string;
|
Name?: string;
|
||||||
/** Button low power mode configuration */
|
/** Button low power mode configuration */
|
||||||
ButtonLowPower: number;
|
ButtonLowPower?: number;
|
||||||
/** Type of heater controlled by the device */
|
/** Type of heater controlled by the device */
|
||||||
HeaterType: string;
|
HeaterType?: string;
|
||||||
/** Button repeat delay configuration in milliseconds */
|
/** Button repeat delay configuration in milliseconds */
|
||||||
ButtonRepeatDelay: number;
|
ButtonRepeatDelay?: number;
|
||||||
/** Button repeat start delay configuration in milliseconds */
|
/** Button repeat start delay configuration in milliseconds */
|
||||||
ButtonRepeatStart: number;
|
ButtonRepeatStart?: number;
|
||||||
/** Display animation style setting */
|
/** Display animation style setting */
|
||||||
Animation: string;
|
Animation?: string;
|
||||||
/** Maximum brightness level (0-100) */
|
/** Maximum brightness level (0-100) */
|
||||||
MaxBrightness: number;
|
MaxBrightness?: number;
|
||||||
/** Array of user IDs allowed to control this device */
|
/** Array of user IDs allowed to control this device */
|
||||||
AllowedUsers: string[];
|
AllowedUsers?: string[];
|
||||||
/** Current button state indicator */
|
/** Current button state indicator */
|
||||||
ButtonState: string;
|
ButtonState?: string;
|
||||||
/** Home identifier that this device belongs to */
|
/** Home identifier that this device belongs to */
|
||||||
Home: string;
|
Home?: string;
|
||||||
/** Button sensitivity threshold configuration */
|
/** Button sensitivity threshold configuration */
|
||||||
ButtonThreshold: number;
|
ButtonThreshold?: number;
|
||||||
/** Data format version used by the device */
|
/** Data format version used by the device */
|
||||||
Format: string;
|
Format?: string;
|
||||||
/** Time zone setting for the device */
|
/** Time zone setting for the device */
|
||||||
TimeZone: string;
|
TimeZone?: string;
|
||||||
/** Unix timestamp of when device was last paired */
|
/** Unix timestamp of when device was last paired */
|
||||||
LastPaired: number;
|
LastPaired?: number;
|
||||||
/** Minimum temperature setpoint allowed */
|
/** Minimum temperature setpoint allowed */
|
||||||
MinSetpoint: number;
|
MinSetpoint?: number;
|
||||||
/** Current operating mode of the device */
|
/** Current operating mode of the device */
|
||||||
Mode: ModeObj;
|
Mode?: ModeObj;
|
||||||
/** User ID of the device owner */
|
/** User ID of the device owner */
|
||||||
Owner: string;
|
Owner?: string;
|
||||||
/** Maximum temperature setpoint allowed */
|
/** Maximum temperature setpoint allowed */
|
||||||
MaxSetpoint: number;
|
MaxSetpoint?: number;
|
||||||
/** Unique device identifier */
|
/** Unique device identifier */
|
||||||
Id: string;
|
Id: string;
|
||||||
/** Optional zone assignment for the device */
|
/** Optional zone assignment for the device */
|
||||||
|
|||||||
@@ -13,43 +13,43 @@ export interface DeviceState {
|
|||||||
/** Overall timestamp for the device state */
|
/** Overall timestamp for the device state */
|
||||||
Timestamp: number;
|
Timestamp: number;
|
||||||
/** Time the device has been on */
|
/** Time the device has been on */
|
||||||
OnTime: TimestampedValue<number>;
|
OnTime?: TimestampedValue<number>;
|
||||||
/** Temperature set point */
|
/** Temperature set point */
|
||||||
SetPoint: TimestampedValue<number>;
|
SetPoint?: TimestampedValue<number>;
|
||||||
/** Display brightness level */
|
/** Display brightness level */
|
||||||
Brightness: TimestampedValue<number>;
|
Brightness?: TimestampedValue<number>;
|
||||||
/** Schedule mode setting */
|
/** Schedule mode setting */
|
||||||
ScheduleMode: TimestampedValue<number>;
|
ScheduleMode?: TimestampedValue<number>;
|
||||||
/** Hold time setting */
|
/** Hold time setting */
|
||||||
HoldTime: TimestampedValue<number>;
|
HoldTime?: TimestampedValue<number>;
|
||||||
/** Wi-Fi signal strength */
|
/** Wi-Fi signal strength */
|
||||||
Rssi: TimestampedValue<number>;
|
Rssi?: TimestampedValue<number>;
|
||||||
/** Thermostat mode */
|
/** Thermostat mode */
|
||||||
TstatMode: TimestampedValue<number>;
|
TstatMode?: TimestampedValue<number>;
|
||||||
/** Available heap memory */
|
/** Available heap memory */
|
||||||
FreeHeap: TimestampedValue<number>;
|
FreeHeap?: TimestampedValue<number>;
|
||||||
/** Sensor temperature reading */
|
/** Sensor temperature reading */
|
||||||
SensorTemp: TimestampedValue<number>;
|
SensorTemp?: TimestampedValue<number>;
|
||||||
/** Current mode */
|
/** Current mode */
|
||||||
Mode: TimestampedValue<number>;
|
Mode?: TimestampedValue<number>;
|
||||||
/** Voltage measurement */
|
/** Voltage measurement */
|
||||||
Voltage: TimestampedValue<number>;
|
Voltage?: TimestampedValue<number>;
|
||||||
/** Temperature corrected for calibration */
|
/** Temperature corrected for calibration */
|
||||||
CorrectedTemp: TimestampedValue<number>;
|
CorrectedTemp?: TimestampedValue<number>;
|
||||||
/** Duty cycle percentage */
|
/** Duty cycle percentage */
|
||||||
Duty: TimestampedValue<number>;
|
Duty?: TimestampedValue<number>;
|
||||||
/** Heat sink temperature */
|
/** Heat sink temperature */
|
||||||
HeatSink: TimestampedValue<number>;
|
HeatSink?: TimestampedValue<number>;
|
||||||
/** Time the device has been off */
|
/** Time the device has been off */
|
||||||
OffTime: TimestampedValue<number>;
|
OffTime?: TimestampedValue<number>;
|
||||||
/** Connection status */
|
/** Connection status */
|
||||||
Connected: TimestampedValue<boolean>;
|
Connected?: TimestampedValue<boolean>;
|
||||||
/** Current consumption */
|
/** Current consumption */
|
||||||
Current: TimestampedValue<number>;
|
Current?: TimestampedValue<number>;
|
||||||
/** Humidity reading */
|
/** Humidity reading */
|
||||||
Humidity: TimestampedValue<number>;
|
Humidity?: TimestampedValue<number>;
|
||||||
/** Lock status */
|
/** Lock status */
|
||||||
Lock: TimestampedValue<number>;
|
Lock?: TimestampedValue<number>;
|
||||||
/** Fan speed */
|
/** Fan speed */
|
||||||
FanSpeed?: TimestampedValue<number>;
|
FanSpeed?: TimestampedValue<number>;
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user