meross-iot 0.1.0

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.
Files changed (99) hide show
  1. package/CHANGELOG.md +30 -0
  2. package/LICENSE +21 -0
  3. package/README.md +153 -0
  4. package/index.d.ts +2344 -0
  5. package/index.js +131 -0
  6. package/lib/controller/device.js +1317 -0
  7. package/lib/controller/features/alarm-feature.js +89 -0
  8. package/lib/controller/features/child-lock-feature.js +61 -0
  9. package/lib/controller/features/config-feature.js +54 -0
  10. package/lib/controller/features/consumption-feature.js +210 -0
  11. package/lib/controller/features/control-feature.js +62 -0
  12. package/lib/controller/features/diffuser-feature.js +411 -0
  13. package/lib/controller/features/digest-timer-feature.js +22 -0
  14. package/lib/controller/features/digest-trigger-feature.js +22 -0
  15. package/lib/controller/features/dnd-feature.js +79 -0
  16. package/lib/controller/features/electricity-feature.js +144 -0
  17. package/lib/controller/features/encryption-feature.js +259 -0
  18. package/lib/controller/features/garage-feature.js +337 -0
  19. package/lib/controller/features/hub-feature.js +687 -0
  20. package/lib/controller/features/light-feature.js +408 -0
  21. package/lib/controller/features/presence-sensor-feature.js +297 -0
  22. package/lib/controller/features/roller-shutter-feature.js +456 -0
  23. package/lib/controller/features/runtime-feature.js +74 -0
  24. package/lib/controller/features/screen-feature.js +67 -0
  25. package/lib/controller/features/sensor-history-feature.js +47 -0
  26. package/lib/controller/features/smoke-config-feature.js +50 -0
  27. package/lib/controller/features/spray-feature.js +166 -0
  28. package/lib/controller/features/system-feature.js +269 -0
  29. package/lib/controller/features/temp-unit-feature.js +55 -0
  30. package/lib/controller/features/thermostat-feature.js +804 -0
  31. package/lib/controller/features/timer-feature.js +507 -0
  32. package/lib/controller/features/toggle-feature.js +223 -0
  33. package/lib/controller/features/trigger-feature.js +333 -0
  34. package/lib/controller/hub-device.js +185 -0
  35. package/lib/controller/subdevice.js +1537 -0
  36. package/lib/device-factory.js +463 -0
  37. package/lib/error-budget.js +138 -0
  38. package/lib/http-api.js +766 -0
  39. package/lib/manager.js +1609 -0
  40. package/lib/model/channel-info.js +79 -0
  41. package/lib/model/constants.js +119 -0
  42. package/lib/model/enums.js +819 -0
  43. package/lib/model/exception.js +363 -0
  44. package/lib/model/http/device.js +215 -0
  45. package/lib/model/http/error-codes.js +121 -0
  46. package/lib/model/http/exception.js +151 -0
  47. package/lib/model/http/subdevice.js +133 -0
  48. package/lib/model/push/alarm.js +112 -0
  49. package/lib/model/push/bind.js +97 -0
  50. package/lib/model/push/common.js +282 -0
  51. package/lib/model/push/diffuser-light.js +100 -0
  52. package/lib/model/push/diffuser-spray.js +83 -0
  53. package/lib/model/push/factory.js +229 -0
  54. package/lib/model/push/generic.js +115 -0
  55. package/lib/model/push/hub-battery.js +59 -0
  56. package/lib/model/push/hub-mts100-all.js +64 -0
  57. package/lib/model/push/hub-mts100-mode.js +59 -0
  58. package/lib/model/push/hub-mts100-temperature.js +62 -0
  59. package/lib/model/push/hub-online.js +59 -0
  60. package/lib/model/push/hub-sensor-alert.js +61 -0
  61. package/lib/model/push/hub-sensor-all.js +59 -0
  62. package/lib/model/push/hub-sensor-smoke.js +110 -0
  63. package/lib/model/push/hub-sensor-temphum.js +62 -0
  64. package/lib/model/push/hub-subdevicelist.js +50 -0
  65. package/lib/model/push/hub-togglex.js +60 -0
  66. package/lib/model/push/index.js +81 -0
  67. package/lib/model/push/online.js +53 -0
  68. package/lib/model/push/presence-study.js +61 -0
  69. package/lib/model/push/sensor-latestx.js +106 -0
  70. package/lib/model/push/timerx.js +63 -0
  71. package/lib/model/push/togglex.js +78 -0
  72. package/lib/model/push/triggerx.js +62 -0
  73. package/lib/model/push/unbind.js +34 -0
  74. package/lib/model/push/water-leak.js +107 -0
  75. package/lib/model/states/diffuser-light-state.js +119 -0
  76. package/lib/model/states/diffuser-spray-state.js +58 -0
  77. package/lib/model/states/garage-door-state.js +71 -0
  78. package/lib/model/states/index.js +38 -0
  79. package/lib/model/states/light-state.js +134 -0
  80. package/lib/model/states/presence-sensor-state.js +239 -0
  81. package/lib/model/states/roller-shutter-state.js +82 -0
  82. package/lib/model/states/spray-state.js +58 -0
  83. package/lib/model/states/thermostat-state.js +297 -0
  84. package/lib/model/states/timer-state.js +192 -0
  85. package/lib/model/states/toggle-state.js +105 -0
  86. package/lib/model/states/trigger-state.js +155 -0
  87. package/lib/subscription.js +587 -0
  88. package/lib/utilities/conversion.js +62 -0
  89. package/lib/utilities/debug.js +165 -0
  90. package/lib/utilities/mqtt.js +152 -0
  91. package/lib/utilities/network.js +53 -0
  92. package/lib/utilities/options.js +64 -0
  93. package/lib/utilities/request-queue.js +161 -0
  94. package/lib/utilities/ssid.js +37 -0
  95. package/lib/utilities/state-changes.js +66 -0
  96. package/lib/utilities/stats.js +687 -0
  97. package/lib/utilities/timer.js +310 -0
  98. package/lib/utilities/trigger.js +286 -0
  99. package/package.json +73 -0
@@ -0,0 +1,151 @@
1
+ 'use strict';
2
+
3
+ const { MerossError, AuthenticationError } = require('../exception');
4
+
5
+ /**
6
+ * Base class for HTTP API errors
7
+ *
8
+ * Extends MerossError with HTTP status code information to distinguish between
9
+ * different types of HTTP failures (client errors, server errors, etc.).
10
+ *
11
+ * @class
12
+ * @extends MerossError
13
+ * @property {number|null} httpStatusCode - HTTP status code (e.g., 400, 500)
14
+ */
15
+ class HttpApiError extends MerossError {
16
+ constructor(message, errorCode = null, httpStatusCode = null) {
17
+ super(message || 'HTTP API error occurred', errorCode);
18
+ this.httpStatusCode = httpStatusCode;
19
+ }
20
+ }
21
+
22
+ /**
23
+ * Unauthorized access error (HTTP 401 or similar)
24
+ *
25
+ * Thrown when an API request is made without proper authentication or with invalid
26
+ * credentials. Typically indicates token expiration or an invalid token that
27
+ * cannot be used for authorization.
28
+ *
29
+ * @class
30
+ * @extends HttpApiError
31
+ * @property {number} httpStatusCode - HTTP status code (typically 401)
32
+ */
33
+ class UnauthorizedError extends HttpApiError {
34
+ constructor(message, errorCode = null, httpStatusCode = 401) {
35
+ super(message || 'Unauthorized access', errorCode, httpStatusCode);
36
+ }
37
+ }
38
+
39
+ /**
40
+ * Bad domain / region redirect error (error code 1030)
41
+ *
42
+ * Thrown when Meross API redirects to a different regional domain. This occurs
43
+ * when the account is registered in a different region than the one being used
44
+ * for API requests. The error response includes the correct regional endpoints
45
+ * that should be used for subsequent requests.
46
+ *
47
+ * @class
48
+ * @extends MerossError
49
+ * @property {number} errorCode - Always 1030
50
+ * @property {string|null} apiDomain - The correct API domain for this account
51
+ * @property {string|null} mqttDomain - The correct MQTT domain for this account
52
+ */
53
+ class BadDomainError extends MerossError {
54
+ constructor(message, apiDomain = null, mqttDomain = null) {
55
+ super(message || 'Redirect app to login other than this region', 1030);
56
+ this.apiDomain = apiDomain;
57
+ this.mqttDomain = mqttDomain;
58
+ }
59
+ }
60
+
61
+ /**
62
+ * Token expired or invalid errors (error codes 1019, 1022, 1200)
63
+ *
64
+ * Thrown when the authentication token has expired or is invalid. Tokens
65
+ * expire after a period of inactivity or when explicitly invalidated by the
66
+ * authentication service.
67
+ *
68
+ * @class
69
+ * @extends MerossError
70
+ * @property {number} errorCode - API error code (1019, 1022, or 1200)
71
+ */
72
+ class TokenExpiredError extends MerossError {
73
+ constructor(message, errorCode) {
74
+ super(message || 'Token has expired or is invalid', errorCode);
75
+ }
76
+ }
77
+
78
+ /**
79
+ * Too many tokens error (error code 1301)
80
+ *
81
+ * Thrown when too many authentication tokens have been issued without logging out.
82
+ * Meross enforces a limit on concurrent sessions per account, and exceeding this
83
+ * limit results in account-level restrictions until tokens are revoked.
84
+ *
85
+ * @class
86
+ * @extends MerossError
87
+ * @property {number} errorCode - Always 1301
88
+ */
89
+ class TooManyTokensError extends MerossError {
90
+ constructor(message) {
91
+ super(message || 'You have issued too many tokens without logging out and your account might have been temporarily disabled.', 1301);
92
+ }
93
+ }
94
+
95
+ /**
96
+ * MFA code required error (error code 1033)
97
+ *
98
+ * Thrown when MFA (Multi-Factor Authentication) is enabled for the account but
99
+ * no MFA code was provided during login. The login process requires an additional
100
+ * authentication step that was not completed.
101
+ *
102
+ * @class
103
+ * @extends AuthenticationError
104
+ * @property {number} errorCode - Always 1033
105
+ */
106
+ class MFARequiredError extends AuthenticationError {
107
+ constructor(message) {
108
+ super(message || 'MFA is activated for the account but MFA code not provided. Please provide a current MFA code.', 1033);
109
+ }
110
+ }
111
+
112
+ /**
113
+ * Alias for MFARequiredError
114
+ *
115
+ * Provided for backward compatibility and clearer naming in contexts where
116
+ * the error represents a missing MFA code rather than a general MFA requirement.
117
+ *
118
+ * @class
119
+ * @extends MFARequiredError
120
+ */
121
+ class MissingMFAError extends MFARequiredError {
122
+ }
123
+
124
+ /**
125
+ * Wrong MFA code error (error code 1032)
126
+ *
127
+ * Thrown when an invalid or expired MFA code is provided during login.
128
+ * The code provided does not match the current valid code from the
129
+ * authenticator application.
130
+ *
131
+ * @class
132
+ * @extends AuthenticationError
133
+ * @property {number} errorCode - Always 1032
134
+ */
135
+ class WrongMFAError extends AuthenticationError {
136
+ constructor(message) {
137
+ super(message || 'Invalid MFA code. Please use a current MFA code.', 1032);
138
+ }
139
+ }
140
+
141
+ module.exports = {
142
+ HttpApiError,
143
+ UnauthorizedError,
144
+ BadDomainError,
145
+ TokenExpiredError,
146
+ TooManyTokensError,
147
+ MFARequiredError,
148
+ MissingMFAError,
149
+ WrongMFAError
150
+ };
151
+
@@ -0,0 +1,133 @@
1
+ 'use strict';
2
+
3
+ /**
4
+ * Represents subdevice information from the HTTP API
5
+ *
6
+ * This class encapsulates subdevice metadata retrieved from the Meross HTTP API,
7
+ * including subdevice identification, type, vendor, and icon information.
8
+ *
9
+ * @class
10
+ * @example
11
+ * // Use fromDict() to create instances
12
+ * const subdeviceInfo = HttpSubdeviceInfo.fromDict({
13
+ * sub_device_id: '12345',
14
+ * true_id: '67890',
15
+ * sub_device_type: 'ms130',
16
+ * sub_device_vendor: 'meross',
17
+ * sub_device_name: 'Temperature Sensor',
18
+ * sub_device_icon_id: 'icon123'
19
+ * });
20
+ */
21
+ class HttpSubdeviceInfo {
22
+ /**
23
+ * Creates an HttpSubdeviceInfo instance from a dictionary object.
24
+ * Normalizes incoming keys (handles camelCase, snake_case, and generic keys) to camelCase.
25
+ * This is the only way to create instances.
26
+ *
27
+ * @static
28
+ * @param {Object} jsonDict - Raw API response object with any key format
29
+ * @param {string} [jsonDict.subDeviceId] - Subdevice ID (camelCase)
30
+ * @param {string} [jsonDict.sub_device_id] - Subdevice ID (snake_case)
31
+ * @param {string} [jsonDict.id] - Generic ID key (mapped to subDeviceId)
32
+ * @param {string} [jsonDict.trueId] - True ID (camelCase)
33
+ * @param {string} [jsonDict.true_id] - True ID (snake_case)
34
+ * @param {string} [jsonDict.subDeviceType] - Subdevice type (camelCase)
35
+ * @param {string} [jsonDict.sub_device_type] - Subdevice type (snake_case)
36
+ * @param {string} [jsonDict.type] - Generic type key (mapped to subDeviceType)
37
+ * @param {string} [jsonDict.subDeviceVendor] - Subdevice vendor (camelCase)
38
+ * @param {string} [jsonDict.sub_device_vendor] - Subdevice vendor (snake_case)
39
+ * @param {string} [jsonDict.subDeviceName] - Subdevice name (camelCase)
40
+ * @param {string} [jsonDict.sub_device_name] - Subdevice name (snake_case)
41
+ * @param {string} [jsonDict.name] - Generic name key (mapped to subDeviceName)
42
+ * @param {string} [jsonDict.subDeviceIconId] - Subdevice icon ID (camelCase)
43
+ * @param {string} [jsonDict.sub_device_icon_id] - Subdevice icon ID (snake_case)
44
+ * @returns {HttpSubdeviceInfo} New HttpSubdeviceInfo instance
45
+ */
46
+ static fromDict(jsonDict) {
47
+ if (!jsonDict || typeof jsonDict !== 'object') {
48
+ throw new Error('Subdevice info dictionary is required');
49
+ }
50
+
51
+ // The Meross API returns properties in inconsistent formats (camelCase, snake_case, or generic keys).
52
+ // This mapping allows us to accept any format and normalize to camelCase for internal use.
53
+ // Generic keys like 'id', 'type', and 'name' are included as fallbacks for API variations.
54
+ const propertyMappings = {
55
+ subDeviceId: ['subDeviceId', 'sub_device_id', 'id'],
56
+ trueId: ['trueId', 'true_id'],
57
+ subDeviceType: ['subDeviceType', 'sub_device_type', 'type'],
58
+ subDeviceVendor: ['subDeviceVendor', 'sub_device_vendor'],
59
+ subDeviceName: ['subDeviceName', 'sub_device_name', 'name'],
60
+ subDeviceIconId: ['subDeviceIconId', 'sub_device_icon_id']
61
+ };
62
+
63
+ // Normalize properties by checking alternatives in priority order (camelCase, snake_case, then generic).
64
+ // This ensures consistent property names regardless of API response format.
65
+ const normalized = {};
66
+ for (const [camelKey, alternatives] of Object.entries(propertyMappings)) {
67
+ for (const key of alternatives) {
68
+ if (jsonDict[key] !== null && jsonDict[key] !== undefined) {
69
+ normalized[camelKey] = jsonDict[key];
70
+ break;
71
+ }
72
+ }
73
+ }
74
+
75
+ // Use Object.create() instead of a constructor to allow static factory pattern.
76
+ // This prevents accidental instantiation via 'new' and enforces use of fromDict().
77
+ const instance = Object.create(HttpSubdeviceInfo.prototype);
78
+ instance.subDeviceId = normalized.subDeviceId || null;
79
+ instance.trueId = normalized.trueId || null;
80
+ instance.subDeviceType = normalized.subDeviceType || null;
81
+ instance.subDeviceVendor = normalized.subDeviceVendor || null;
82
+ instance.subDeviceName = normalized.subDeviceName || null;
83
+ instance.subDeviceIconId = normalized.subDeviceIconId || null;
84
+
85
+ return instance;
86
+ }
87
+
88
+ /**
89
+ * Converts the instance to a plain object dictionary with camelCase keys
90
+ *
91
+ * @returns {Object} Plain object with camelCase property keys
92
+ */
93
+ toDict() {
94
+ return {
95
+ subDeviceId: this.subDeviceId,
96
+ trueId: this.trueId,
97
+ subDeviceType: this.subDeviceType,
98
+ subDeviceVendor: this.subDeviceVendor,
99
+ subDeviceName: this.subDeviceName,
100
+ subDeviceIconId: this.subDeviceIconId
101
+ };
102
+ }
103
+
104
+ /**
105
+ * Returns a string representation of the subdevice info
106
+ *
107
+ * Provides a human-readable format for logging and debugging. Includes
108
+ * name, type, and both device IDs to uniquely identify the subdevice.
109
+ *
110
+ * @returns {string} String representation in the format "Name (type, ID x, TRUE-ID y)"
111
+ */
112
+ toString() {
113
+ const name = this.subDeviceName || 'Unknown';
114
+ const type = this.subDeviceType || 'unknown';
115
+ const id = this.subDeviceId || 'N/A';
116
+ const trueId = this.trueId || 'N/A';
117
+ return `${name} (${type}, ID ${id}, TRUE-ID ${trueId})`;
118
+ }
119
+
120
+ /**
121
+ * Returns a JSON representation of the subdevice info
122
+ *
123
+ * Serializes the subdevice data to JSON format. Uses toDict() to ensure
124
+ * consistent camelCase property names in the output.
125
+ *
126
+ * @returns {string} JSON string representation of the subdevice data
127
+ */
128
+ toJSON() {
129
+ return JSON.stringify(this.toDict());
130
+ }
131
+ }
132
+
133
+ module.exports = HttpSubdeviceInfo;
@@ -0,0 +1,112 @@
1
+ 'use strict';
2
+
3
+ const GenericPushNotification = require('./generic');
4
+
5
+ /**
6
+ * Push notification for device alarm events.
7
+ *
8
+ * Handles alarm notifications from both standalone devices and hub subdevices.
9
+ * The alarm data structure varies: standalone devices send alarm data directly,
10
+ * while hub sensors include interconnection data with source subdevice information.
11
+ *
12
+ * @class
13
+ * @extends GenericPushNotification
14
+ * @example
15
+ * device.on('pushNotification', (notification) => {
16
+ * if (notification instanceof AlarmPushNotification) {
17
+ * console.log('Alarm triggered on channel:', notification.channel);
18
+ * console.log('Alarm value:', notification.value);
19
+ * console.log('Timestamp:', notification.timestamp);
20
+ * if (notification.subdevice_id) {
21
+ * console.log('From subdevice:', notification.subdevice_id);
22
+ * }
23
+ * }
24
+ * });
25
+ */
26
+ class AlarmPushNotification extends GenericPushNotification {
27
+ /**
28
+ * Creates a new AlarmPushNotification instance.
29
+ *
30
+ * @param {string} originatingDeviceUuid - UUID of the device that sent the notification
31
+ * @param {Object} rawData - Raw notification data from the device
32
+ * @param {Object|Array} [rawData.alarm] - Alarm event data (single object or array)
33
+ * @param {number} [rawData.alarm.channel] - Channel number where alarm occurred
34
+ * @param {Object} [rawData.alarm.event] - Alarm event details
35
+ * @param {Object} [rawData.alarm.event.interConn] - Interconnection data
36
+ * @param {*} [rawData.alarm.event.interConn.value] - Alarm value
37
+ * @param {number} [rawData.alarm.event.interConn.timestamp] - Alarm timestamp
38
+ * @param {Object|Array} [rawData.alarm.event.interConn.source] - Source device data
39
+ * @param {string|number} [rawData.alarm.event.interConn.source.subId] - Subdevice ID if from hub sensor
40
+ */
41
+ constructor(originatingDeviceUuid, rawData) {
42
+ super('Appliance.Control.Alarm', originatingDeviceUuid, rawData);
43
+
44
+ // Devices may send single objects or arrays; normalize to array for consistent processing
45
+ const alarmRaw = rawData?.alarm;
46
+ const alarm = GenericPushNotification.normalizeToArray(alarmRaw);
47
+
48
+ // Update rawData so routing logic receives normalized structure
49
+ if (rawData && alarmRaw !== alarm) {
50
+ rawData.alarm = alarm;
51
+ }
52
+
53
+ if (alarm && alarm.length > 0) {
54
+ const alarmEvent = alarm[0];
55
+ this._channel = alarmEvent?.channel;
56
+
57
+ const interConn = alarmEvent?.event?.interConn;
58
+ if (interConn) {
59
+ this._value = interConn.value;
60
+ this._timestamp = interConn.timestamp;
61
+
62
+ const { source } = interConn;
63
+ const sourceArray = GenericPushNotification.normalizeToArray(source);
64
+ if (sourceArray && sourceArray.length > 0) {
65
+ this._subDeviceId = sourceArray[0]?.subId;
66
+ }
67
+ }
68
+ }
69
+ }
70
+
71
+ /**
72
+ * Gets the alarm value.
73
+ *
74
+ * @returns {*} Alarm value or undefined if not available
75
+ */
76
+ get value() {
77
+ return this._value;
78
+ }
79
+
80
+ /**
81
+ * Gets the alarm timestamp.
82
+ *
83
+ * @returns {number|undefined} Timestamp when alarm occurred or undefined if not available
84
+ */
85
+ get timestamp() {
86
+ return this._timestamp;
87
+ }
88
+
89
+ /**
90
+ * Gets the channel number where the alarm occurred.
91
+ *
92
+ * @returns {number|undefined} Channel number or undefined if not available
93
+ */
94
+ get channel() {
95
+ return this._channel;
96
+ }
97
+
98
+ /**
99
+ * Gets the subdevice ID if alarm originated from a hub subdevice.
100
+ *
101
+ * Only present when the alarm comes from a hub sensor subdevice, not standalone devices.
102
+ *
103
+ * @returns {string|number|undefined} Subdevice ID or undefined if not from a subdevice
104
+ */
105
+ // eslint-disable-next-line camelcase
106
+ get subdevice_id() {
107
+ return this._subDeviceId;
108
+ }
109
+ }
110
+
111
+ module.exports = AlarmPushNotification;
112
+
@@ -0,0 +1,97 @@
1
+ 'use strict';
2
+
3
+ const GenericPushNotification = require('./generic');
4
+ const { HardwareInfo, FirmwareInfo, TimeInfo } = require('./common');
5
+
6
+ /**
7
+ * Push notification for device binding events.
8
+ *
9
+ * Emitted when a device is bound to a user account or when binding information is updated.
10
+ * The binding data includes hardware and firmware metadata that uniquely identifies the device
11
+ * and its capabilities, which is used for device registration and feature detection.
12
+ *
13
+ * @class
14
+ * @extends GenericPushNotification
15
+ * @example
16
+ * device.on('pushNotification', (notification) => {
17
+ * if (notification instanceof BindPushNotification) {
18
+ * const timeInfo = notification.time;
19
+ * if (timeInfo) {
20
+ * console.log('Device bound at:', timeInfo.timestamp, 'timezone:', timeInfo.timezone);
21
+ * }
22
+ * const hwInfo = notification.hwinfo;
23
+ * if (hwInfo) {
24
+ * console.log('Hardware version:', hwInfo.version, 'MAC:', hwInfo.macAddress);
25
+ * }
26
+ * const fwInfo = notification.fwinfo;
27
+ * if (fwInfo) {
28
+ * console.log('Firmware version:', fwInfo.version, 'WiFi MAC:', fwInfo.wifiMac);
29
+ * }
30
+ * }
31
+ * });
32
+ */
33
+ class BindPushNotification extends GenericPushNotification {
34
+ /**
35
+ * Creates a new BindPushNotification instance.
36
+ *
37
+ * @param {string} originatingDeviceUuid - UUID of the device that sent the notification
38
+ * @param {Object} rawData - Raw notification data from the device
39
+ * @param {Object} [rawData.bind] - Binding data
40
+ * @param {Object} [rawData.bind.time] - Time information object (will be converted to TimeInfo instance)
41
+ * @param {Object} [rawData.bind.hardware] - Hardware information object (will be converted to HardwareInfo instance)
42
+ * @param {Object} [rawData.bind.firmware] - Firmware information object (will be converted to FirmwareInfo instance)
43
+ */
44
+ constructor(originatingDeviceUuid, rawData) {
45
+ super('Appliance.Control.Bind', originatingDeviceUuid, rawData);
46
+ }
47
+
48
+ /**
49
+ * Gets the binding timestamp information.
50
+ *
51
+ * Returns a TimeInfo instance that normalizes timezone and timestamp data from the raw payload.
52
+ *
53
+ * @returns {TimeInfo|null} TimeInfo instance or null if not available
54
+ */
55
+ get time() {
56
+ const timeData = this._rawData?.bind?.time;
57
+ if (!timeData) {
58
+ return null;
59
+ }
60
+ return TimeInfo.fromDict(timeData);
61
+ }
62
+
63
+ /**
64
+ * Gets the hardware information.
65
+ *
66
+ * Returns a HardwareInfo instance that normalizes hardware metadata (version, UUID, MAC, chip type)
67
+ * from the raw payload, handling both camelCase and snake_case property names.
68
+ *
69
+ * @returns {HardwareInfo|null} HardwareInfo instance or null if not available
70
+ */
71
+ get hwinfo() {
72
+ const hardwareData = this._rawData?.bind?.hardware;
73
+ if (!hardwareData) {
74
+ return null;
75
+ }
76
+ return HardwareInfo.fromDict(hardwareData);
77
+ }
78
+
79
+ /**
80
+ * Gets the firmware information.
81
+ *
82
+ * Returns a FirmwareInfo instance that normalizes firmware metadata (version, WiFi MAC, server info)
83
+ * from the raw payload, handling both camelCase and snake_case property names.
84
+ *
85
+ * @returns {FirmwareInfo|null} FirmwareInfo instance or null if not available
86
+ */
87
+ get fwinfo() {
88
+ const firmwareData = this._rawData?.bind?.firmware;
89
+ if (!firmwareData) {
90
+ return null;
91
+ }
92
+ return FirmwareInfo.fromDict(firmwareData);
93
+ }
94
+ }
95
+
96
+ module.exports = BindPushNotification;
97
+