@lucavb/shellies-ds9
Version:
Handles communication with the next generation of Shelly devices
1 lines • 190 kB
Source Map (JSON)
{"version":3,"sources":["../src/components/base.ts","../src/components/bluetooth-low-energy.ts","../src/components/cloud.ts","../src/components/cover.ts","../src/components/device-power.ts","../src/components/ethernet.ts","../src/components/ht-ui.ts","../src/components/humidity.ts","../src/components/input.ts","../src/components/light.ts","../src/components/mqtt.ts","../src/components/outbound-websocket.ts","../src/components/script.ts","../src/components/switch.ts","../src/components/system.ts","../src/components/temperature.ts","../src/components/ui.ts","../src/components/wifi.ts","../src/components/pm1.ts","../src/devices/base.ts","../src/services/base.ts","../src/services/http.ts","../src/services/kvs.ts","../src/services/schedule.ts","../src/services/shelly.ts","../src/services/webhook.ts","../src/devices/shelly-plus-1.ts","../src/devices/shelly-plus-pm.ts","../src/devices/shelly-plus-1-pm.ts","../src/devices/shelly-plus-2-pm.ts","../src/devices/shelly-plus-ht.ts","../src/devices/shelly-plus-i4.ts","../src/devices/shelly-plus-plug.ts","../src/devices/shelly-pro-1.ts","../src/devices/shelly-pro-1-pm.ts","../src/devices/shelly-pro-2.ts","../src/devices/shelly-pro-2-pm.ts","../src/devices/shelly-pro-3.ts","../src/devices/shelly-pro-4-pm.ts","../src/devices/shelly-pro-dimmer-1-pm.ts","../src/devices/shelly-pro-dimmer-2-pm.ts","../src/devices/shelly-pro-dual-cover-pm.ts","../src/devices/shelly-plus-dimmer-pm.ts","../src/devices/shelly-plus-dimmer.ts","../src/devices/shelly-gen3-2-pm.ts","../src/discovery/base.ts","../src/discovery/mdns.ts","../src/rpc/base.ts","../src/rpc/auth.ts","../src/rpc/websocket.ts","../src/shellies.ts"],"sourcesContent":["import equal from 'fast-deep-equal';\nimport EventEmitter from 'eventemitter3';\n\nimport { Device } from '../devices';\nimport { RpcEvent, RpcParams } from '../rpc';\n\nexport type PrimitiveTypes = boolean | number | string;\nexport type CharacteristicValue =\n | PrimitiveTypes\n | PrimitiveTypes[]\n | null\n | { [key: string]: PrimitiveTypes | PrimitiveTypes[] | null };\n\n/**\n * Interface used when passing around components, since the Component class is\n * generic.\n */\nexport interface ComponentLike {\n name: string;\n key: string;\n device: Device;\n\n update(data: Record<string, unknown>);\n handleEvent(event: RpcEvent);\n}\n\n/**\n * Property decorator used to label properties as characteristics.\n * @param target - The prototype of the component class that the property belongs to.\n * @param propertyName - The name of the property.\n */\n// eslint-disable-next-line @typescript-eslint/no-explicit-any\nexport const characteristic = (target: any, propertyName: string) => {\n // make sure the given prototype has an array of properties\n if (!Object.prototype.hasOwnProperty.call(target, '_characteristicProps')) {\n target._characteristicProps = new Array<string>();\n }\n\n // get the array of properties\n const props: string[] = target._characteristicProps;\n\n // add this property to the array\n props.push(propertyName);\n};\n\n/**\n * Base class for all device components.\n */\nexport abstract class ComponentBase extends EventEmitter implements ComponentLike {\n /**\n * The key used to identify the component in status updates etc.\n */\n readonly key: string;\n\n /**\n * @param name - The name of this component. Used when making RPCs.\n * @param device - The device that owns this component.\n * @param key - The key used to identify the component in status updates etc. If `null`, the component name\n * in lower case letters will be used.\n */\n constructor(\n readonly name: string,\n readonly device: Device,\n key: string | null = null,\n ) {\n super();\n\n this.key = key !== null ? key : name.toLowerCase();\n }\n\n private _characteristics: Set<string> | null = null;\n\n /**\n * A list of all characteristics.\n */\n protected get characteristics(): Set<string> {\n if (this._characteristics === null) {\n // construct an array of properties stored by the @characteristic property decorator\n const props = new Array<string>();\n let proto = Object.getPrototypeOf(this);\n\n // traverse the prototype chain and collect all properties\n while (proto !== null) {\n if (Object.prototype.hasOwnProperty.call(proto, '_characteristicProps')) {\n props.push(...proto._characteristicProps);\n }\n\n proto = Object.getPrototypeOf(proto);\n }\n\n // store the list\n this._characteristics = new Set(props);\n }\n\n return this._characteristics;\n }\n\n /**\n * Updates the characteristics of the component.\n * This method will emit `change` events for all characteristics that are\n * updated.\n * @param data - A data object that contains characteristics and their values.\n */\n update(data: Record<string | number | symbol, unknown>) {\n const cs = this.characteristics;\n const changed = new Set<string>();\n\n if (!cs) {\n // abort if we don't have any characteristics\n return;\n }\n\n // loop through each of our characteristics\n for (const c of cs) {\n if (!Object.prototype.hasOwnProperty.call(data, c)) {\n // ignore if this characteristic is not in the data set\n continue;\n }\n\n if (typeof data[c] === 'object' && this[c]) {\n // if this is an object, we need to check for deep equality\n if (equal(data[c], this[c])) {\n // skip if nothing has changed\n continue;\n }\n\n // copy the attributes of the characteristic\n Object.assign(this[c], data[c]);\n } else {\n if (data[c] === this[c]) {\n // skip if the value is unchanged\n continue;\n }\n\n // assign the new value to our characteristic\n this[c] = data[c];\n }\n\n // add it to the list of changed characteristics\n changed.add(c);\n }\n\n // emit all change events after the characteristics have been updated\n for (const c of changed) {\n this.emit('change', c, this[c]);\n this.emit('change:' + c, this[c]);\n }\n }\n\n /**\n * Handles events received from the device RPC handler.\n * Subclasses should override this method to handle their specific events.\n * @param event - The event that has occurred.\n */\n handleEvent(event: RpcEvent) {\n if (event.event === 'config_changed') {\n this.emit('configChange', event.cfg_rev as number, event.restart_required as boolean);\n }\n }\n\n /**\n * Shorthand method for making an RPC.\n */\n protected rpc<T>(method: string, params?: RpcParams): PromiseLike<T> {\n return this.device.rpcHandler.request<T>(`${this.name}.${method}`, params);\n }\n}\n\n/**\n * Defines the default response from a component's SetConfig RPC method.\n */\nexport interface DefaultConfigResponse {\n restart_required: boolean;\n}\n\n/**\n * Defines a set of methods common for (almost) all device components.\n */\nexport abstract class Component<Attributes, Config, ConfigResponse = DefaultConfigResponse> extends ComponentBase {\n /**\n * The confoguration options for this component.\n * Use the `getConfig()` method to load these options.\n * This property is automatically populated by the `Device.loadConfig()` method.\n */\n config?: Config;\n\n /**\n * Retrieves the status of this component.\n */\n getStatus(): PromiseLike<Attributes> {\n return this.rpc<Attributes>('GetStatus');\n }\n\n /**\n * Retrieves the configuration of this component.\n */\n getConfig(): PromiseLike<Config> {\n return this.rpc<Config>('GetConfig');\n }\n\n /**\n * Requests changes in the configuration of this component.\n * @param config - The configuration options to set.\n */\n setConfig(config: Partial<Config>): PromiseLike<ConfigResponse> {\n return this.rpc<ConfigResponse>('SetConfig', {\n config,\n });\n }\n}\n\n/**\n * Base class for components with an ID.\n */\nexport abstract class ComponentWithId<Attributes, Config, ConfigResponse = DefaultConfigResponse> extends Component<\n Attributes,\n Config,\n ConfigResponse\n> {\n /**\n * @param name - The name of this component. Used when making RPCs.\n * @param device - The device that owns this component.\n * @param id - ID of this component.\n * @param key - The key used to identify the component in status updates etc. If `null`, the component name\n * in lower case letters will be used. The component ID will be appended to this key.\n */\n constructor(\n name: string,\n device: Device,\n readonly id: number = 0,\n key: string | null = null,\n ) {\n super(name, device, (key !== null ? key : name.toLowerCase()) + ':' + id);\n }\n\n /**\n * Retrieves the status of this component.\n */\n getStatus(): PromiseLike<Attributes> {\n return this.rpc<Attributes>('GetStatus', {\n id: this.id,\n });\n }\n\n /**\n * Retrieves the configuration of this component.\n */\n getConfig(): PromiseLike<Config> {\n return this.rpc<Config>('GetConfig', {\n id: this.id,\n });\n }\n\n /**\n * Requests changes in the configuration of this component.\n * @param config - The configuration options to set.\n */\n setConfig(config: Partial<Config>): PromiseLike<ConfigResponse> {\n return this.rpc<ConfigResponse>('SetConfig', {\n id: this.id,\n config,\n });\n }\n}\n","import { Component } from './base';\nimport { Device } from '../devices';\n\nexport interface BluetoothLowEnergyAttributes {}\n\nexport interface BluetoothLowEnergyConfig {\n enable: boolean;\n}\n\n/**\n * Handles the Bluetooth services of a device.\n */\nexport class BluetoothLowEnergy\n extends Component<BluetoothLowEnergyAttributes, BluetoothLowEnergyConfig>\n implements BluetoothLowEnergyAttributes\n{\n constructor(device: Device) {\n super('BLE', device);\n }\n}\n","import { characteristic, Component } from './base';\nimport { Device } from '../devices';\n\nexport interface CloudAttributes {\n connected: boolean;\n}\n\nexport interface CloudConfig {\n enable: boolean;\n server: string | null;\n}\n\n/**\n * Handles the Cloud services of a device.\n */\nexport class Cloud extends Component<CloudAttributes, CloudConfig> implements CloudAttributes {\n /**\n * Whether the device is connected to the Shelly cloud.\n */\n @characteristic\n readonly connected: boolean = false;\n\n constructor(device: Device) {\n super('Cloud', device);\n }\n}\n","import { characteristic, ComponentWithId } from './base';\nimport { Device } from '../devices';\n\nexport interface CoverEnergyCounterAttributes {\n total: number;\n by_minute: number[];\n minute_ts: number;\n}\n\nexport interface CoverTemperatureAttributes {\n tC: number | null;\n tF: number | null;\n}\n\nexport interface CoverAttributes {\n id: number;\n source: string;\n state: 'open' | 'closed' | 'opening' | 'closing' | 'stopped' | 'calibrating';\n apower: number;\n voltage: number;\n current: number;\n pf: number;\n aenergy: CoverEnergyCounterAttributes;\n current_pos: number | null;\n target_pos: number | null;\n move_timeout?: number;\n move_started_at?: number;\n pos_control: boolean;\n temperature?: CoverTemperatureAttributes;\n errors?: string[];\n}\n\nexport interface CoverAcMotorConfig {\n idle_power_thr: number;\n}\n\nexport interface CoverObstructionDetectionConfig {\n enable: boolean;\n direction: 'open' | 'close' | 'both';\n action: 'stop' | 'reverse';\n power_thr: number;\n holdoff: number;\n}\n\nexport interface CoverSafetySwitchConfig {\n enable: boolean;\n direction: 'open' | 'close' | 'both';\n action: 'stop' | 'reverse' | 'pause';\n allowed_move: 'reverse' | null;\n}\n\nexport interface CoverConfig {\n id: number;\n name: string | null;\n in_mode?: 'single' | 'dual' | 'detached';\n initial_state: 'open' | 'closed' | 'stopped';\n power_limit: number;\n voltage_limit: number;\n current_limit: number;\n motor?: CoverAcMotorConfig;\n maxtime_open: number;\n maxtime_close: number;\n swap_inputs?: boolean;\n invert_directions: boolean;\n obstruction_detection: CoverObstructionDetectionConfig;\n safety_switch?: CoverSafetySwitchConfig;\n}\n\n/**\n * Handles the operation of moorized garage doors, window blinds, roof skylights etc.\n */\nexport class Cover extends ComponentWithId<CoverAttributes, CoverConfig> implements CoverAttributes {\n /**\n * Source of the last command.\n */\n @characteristic\n readonly source: string = '';\n\n /**\n * The current state.\n */\n @characteristic\n readonly state: 'open' | 'closed' | 'opening' | 'closing' | 'stopped' | 'calibrating' = 'stopped';\n\n /**\n * The current (last measured) instantaneous power delivered to the attached\n * load.\n */\n @characteristic\n readonly apower: number = 0;\n\n /**\n * Last measured voltage (in Volts).\n */\n @characteristic\n readonly voltage: number = 0;\n\n /**\n * Last measured current (in Amperes).\n */\n @characteristic\n readonly current: number = 0;\n\n /**\n * Last measured power factor.\n */\n @characteristic\n readonly pf: number = 0;\n\n /**\n * Information about the energy counter.\n */\n @characteristic\n readonly aenergy: CoverEnergyCounterAttributes = {\n total: 0,\n by_minute: [],\n minute_ts: 0,\n };\n\n /**\n * The current position in percent, from `0` (fully closed) to `100` (fully open); or `null` if not calibrated.\n */\n @characteristic\n readonly current_pos: number | null = null;\n\n /**\n * The requested target position in percent, from `0` (fully closed) to `100` (fully open); or `null` if not calibrated\n * or not actively moving.\n */\n @characteristic\n readonly target_pos: number | null = null;\n\n /**\n * A timeout (in seconds) after which the cover will automatically stop moving; or `undefined` if not actively moving.\n */\n @characteristic\n readonly move_timeout: number | undefined;\n\n /**\n * The time at which the movement began; or `undefined` if not actively moving.\n */\n @characteristic\n readonly move_started_at: number | undefined;\n\n /**\n * Whether the cover has been calibrated.\n */\n @characteristic\n readonly pos_control: boolean = false;\n\n /**\n * Information about the temperature sensor (if applicable).\n */\n @characteristic\n readonly temperature: CoverTemperatureAttributes | undefined;\n\n /**\n * Any error conditions that have occurred.\n */\n @characteristic\n readonly errors: string[] | undefined;\n\n constructor(device: Device, id = 0) {\n super('Cover', device, id);\n }\n\n /**\n * Opens the cover.\n * @param duration - Move in open direction for the specified time (in seconds).\n */\n open(duration?: number): PromiseLike<null> {\n return this.rpc<null>('Open', {\n id: this.id,\n duration,\n });\n }\n\n /**\n * Closes the cover.\n * @param duration - Move in close direction for the specified time (in seconds).\n */\n close(duration?: number): PromiseLike<null> {\n return this.rpc<null>('Close', {\n id: this.id,\n duration,\n });\n }\n\n /**\n * Stops any ongoing movement.\n */\n stop(): PromiseLike<null> {\n return this.rpc<null>('Stop', {\n id: this.id,\n });\n }\n\n /**\n * Moves the cover to the given position.\n * One, but not both, of `pos` and `rel` must be specified.\n * @param pos - An absolute position (in percent).\n * @param rel - A relative position (in percent).\n */\n goToPosition(pos?: number, rel?: number): PromiseLike<null> {\n return this.rpc<null>('GoToPosition', {\n id: this.id,\n pos,\n rel,\n });\n }\n\n /**\n * Starts the calibration procedure.\n */\n calibrate(): PromiseLike<null> {\n return this.rpc<null>('Calibrate', {\n id: this.id,\n });\n }\n}\n","import { characteristic, ComponentWithId } from './base';\nimport { Device } from '../devices';\n\nexport interface DevicePowerBatteryStatus {\n V: number | null;\n percent: number | null;\n}\n\nexport interface DevicePowerExternalSource {\n present: boolean;\n}\n\nexport interface DevicePowerAttributes {\n id: number;\n battery: DevicePowerBatteryStatus;\n external?: DevicePowerExternalSource;\n errors?: string[];\n}\n\nexport interface DevicePowerConfig {}\n\n/**\n * Handles the monitoring of a device's battery charge.\n */\nexport class DevicePower\n extends ComponentWithId<DevicePowerAttributes, DevicePowerConfig>\n implements DevicePowerAttributes\n{\n /**\n * Information about the battery charge.\n */\n @characteristic\n readonly battery: DevicePowerBatteryStatus = {\n V: null,\n percent: null,\n };\n\n /**\n * Information about the external power source.\n */\n @characteristic\n readonly external: DevicePowerExternalSource | undefined;\n\n /**\n * Any error conditions that have occurred.\n */\n @characteristic\n readonly errors: string[] | undefined;\n\n constructor(device: Device, id = 0) {\n super('DevicePower', device, id);\n }\n}\n","import { characteristic, Component } from './base';\nimport { Device } from '../devices';\n\nexport interface EthernetAttributes {\n ip: string | null;\n}\n\nexport interface EthernetConfig {\n enable: boolean;\n ipv4mode: 'dhcp' | 'static';\n ip: string | null;\n netmask: string | null;\n gw: string | null;\n nameserver: string | null;\n}\n\n/**\n * Handles the Ethernet services of a device.\n */\nexport class Ethernet extends Component<EthernetAttributes, EthernetConfig> implements EthernetAttributes {\n /**\n * IP address of the device.\n */\n @characteristic\n readonly ip: string | null = null;\n\n constructor(device: Device) {\n super('Eth', device);\n }\n}\n","import { Component } from './base';\nimport { Device } from '../devices';\n\nexport interface HtUiAttributes {}\n\nexport interface HtUiConfig {\n temperature_unit: 'C' | 'F';\n}\n\n/**\n * Handles the settings of a Plus H&T device's screen.\n */\nexport class HtUi extends Component<HtUiAttributes, HtUiConfig> implements HtUiAttributes {\n constructor(device: Device) {\n super('HT_UI', device);\n }\n}\n","import { characteristic, ComponentWithId } from './base';\nimport { Device } from '../devices';\n\nexport interface HumidityAttributes {\n id: number;\n rh: number | null;\n errors?: string[];\n}\n\nexport interface HumidityConfig {\n id: number;\n name: string | null;\n report_thr: number;\n}\n\n/**\n * Handles the monitoring of a device's humidity sensor.\n */\nexport class Humidity extends ComponentWithId<HumidityAttributes, HumidityConfig> implements HumidityAttributes {\n /**\n * Relative humidity, in percent.\n */\n @characteristic\n readonly rh: number | null = null;\n\n /**\n * Any error conditions that have occurred.\n */\n @characteristic\n readonly errors: string[] | undefined;\n\n constructor(device: Device, id = 0) {\n super('Humidity', device, id);\n }\n}\n","import { characteristic, ComponentWithId } from './base';\nimport { Device } from '../devices';\nimport { RpcEvent } from '../rpc';\n\nexport interface InputAttributes {\n id: number;\n state: boolean | null;\n}\n\nexport interface InputConfig {\n id: number;\n name: string | null;\n type: 'switch' | 'button';\n invert: boolean;\n factory_reset?: boolean;\n}\n\n/**\n * Handles the input of a device.\n */\nexport class Input extends ComponentWithId<InputAttributes, InputConfig> implements InputAttributes {\n /**\n * State of the input (null if stateless).\n */\n @characteristic\n readonly state: boolean | null = null;\n\n constructor(device: Device, id = 0) {\n super('Input', device, id);\n }\n\n handleEvent(event: RpcEvent) {\n switch (event.event) {\n case 'btn_down':\n this.emit('buttonDown');\n break;\n\n case 'btn_up':\n this.emit('buttonUp');\n break;\n\n case 'single_push':\n this.emit('singlePush');\n break;\n\n case 'double_push':\n this.emit('doublePush');\n break;\n\n case 'long_push':\n this.emit('longPush');\n break;\n\n default:\n super.handleEvent(event);\n }\n }\n}\n","import { characteristic, ComponentWithId } from './base';\nimport { Device } from '../devices';\n\nexport interface LightAttributes {\n id: number;\n source: string;\n output: boolean;\n brightness: number;\n timer_started_at?: number;\n timer_duration?: number;\n}\n\nexport interface LightConfig {\n id: number;\n name: string | null;\n initial_state: 'off' | 'on' | 'restore_last' | 'match_input';\n auto_on: boolean;\n auto_on_delay: number;\n auto_off: boolean;\n auto_off_delay: number;\n default: {\n brightness: number;\n };\n night_mode: {\n enable: boolean;\n brightness: number;\n active_between?: string[];\n };\n}\n\n/**\n * Handles a dimmable light output with additional on/off control.\n */\nexport class Light extends ComponentWithId<LightAttributes, LightConfig> implements LightAttributes {\n /**\n * Source of the last command.\n */\n @characteristic\n readonly source: string = '';\n\n /**\n * true if the output channel is currently on, false otherwise.\n */\n @characteristic\n readonly output: boolean = false;\n\n /**\n * Current brightness level, in percent.\n */\n @characteristic\n readonly brightness: number = 0;\n\n /**\n * Start time of the timer (as a UNIX timestamp, in UTC).\n */\n @characteristic\n readonly timer_started_at: number | undefined;\n\n /**\n * Duration of the timer, in seconds.\n */\n @characteristic\n readonly timer_duration: number | undefined;\n\n constructor(device: Device, id = 0) {\n super('Light', device, id);\n }\n\n /**\n * Toggles the output state.\n */\n toggle(): PromiseLike<null> {\n return this.rpc<null>('Toggle', {\n id: this.id,\n });\n }\n\n /**\n * Sets the output and brightness level of the light.\n * At least one of `on` and `brightness` must be specified.\n * @param on - Whether to switch on or off.\n * @param brightness - Brightness level.\n * @param toggle_after - Flip-back timer, in seconds.\n */\n set(on?: boolean, brightness?: number, toggle_after?: number): PromiseLike<null> {\n return this.rpc<null>('Set', {\n id: this.id,\n on,\n brightness,\n toggle_after,\n });\n }\n}\n","import { characteristic, Component } from './base';\nimport { Device } from '../devices';\n\nexport interface MqttAttributes {\n connected: boolean;\n}\n\nexport interface MqttConfig {\n enable: boolean;\n server: string | null;\n client_id: string | null;\n user: string | null;\n pass?: string | null;\n ssl_ca: '*' | 'user_ca.pem' | 'ca.pem' | null;\n topic_prefix: string | null;\n rpc_ntf: boolean;\n status_ntf: boolean;\n}\n\n/**\n * Handles configuration and status of the device's outbound MQTT connection.\n */\nexport class Mqtt extends Component<MqttAttributes, MqttConfig> implements MqttAttributes {\n /**\n * Whether the device is connected to an MQTT server.\n */\n @characteristic\n readonly connected: boolean = false;\n\n constructor(device: Device) {\n super('MQTT', device);\n }\n}\n","import { characteristic, Component } from './base';\nimport { Device } from '../devices';\n\nexport interface OutboundWebSocketAttributes {\n connected: boolean;\n}\n\nexport interface OutboundWebSocketConfig {\n enable: boolean;\n server: string;\n ssl_ca: '*' | 'user_ca.pem' | 'ca.pem' | null;\n}\n\n/**\n * Makes it possible to configure a device to establish and maintain an outbound WebSocket connection.\n */\nexport class OutboundWebSocket\n extends Component<OutboundWebSocketAttributes, OutboundWebSocketConfig>\n implements OutboundWebSocketAttributes\n{\n /**\n * Whether an outbound WebSocket connection is established.\n */\n @characteristic\n readonly connected: boolean = false;\n\n constructor(device: Device) {\n super('Ws', device);\n }\n}\n","import { ComponentBase } from './base';\nimport { Device } from '../devices';\n\nexport interface ScriptAttributes {\n id: number;\n running: boolean;\n errors?: string[];\n}\n\nexport interface ScriptConfig {\n id: number;\n name: string;\n enable: boolean;\n}\n\nexport interface ScriptConfigResponse {\n restart_required: boolean;\n}\n\nexport interface ScriptList {\n scripts: Array<{\n id: number;\n name: string;\n enable: boolean;\n running: boolean;\n }>;\n}\n\nexport interface ScriptCreateResponse {\n id: number;\n}\n\nexport interface ScriptStartStopResponse {\n was_running: boolean;\n}\n\nexport interface ScriptPutCodeResponse {\n len: number;\n}\n\nexport interface ScriptGetCodeResponse {\n data: string;\n left: number;\n}\n\nexport interface ScriptEvalResponse {\n result: string;\n}\n\n/**\n * Handles scripts on a device.\n */\nexport class Script extends ComponentBase {\n constructor(device: Device) {\n super('Script', device);\n }\n\n /**\n * Retrieves the status of a script.\n * @param id - The script ID.\n */\n getStatus(id: number): PromiseLike<ScriptAttributes> {\n return this.rpc<ScriptAttributes>('GetStatus', {\n id,\n });\n }\n\n /**\n * Retrieves the configuration of a script.\n * @param id - The script ID.\n */\n getConfig(id: number): PromiseLike<ScriptConfig> {\n return this.rpc<ScriptConfig>('GetConfig', {\n id,\n });\n }\n\n /**\n * Requests changes in the configuration of a script.\n * @param id - The script ID.\n * @param config - The configuration options to set.\n */\n setConfig(id: number, config: Partial<ScriptConfig>): PromiseLike<ScriptConfigResponse> {\n return this.rpc<ScriptConfigResponse>('SetConfig', {\n id,\n config,\n });\n }\n\n /**\n * Lists all scripts.\n */\n list(): PromiseLike<ScriptList> {\n return this.rpc<ScriptList>('List');\n }\n\n /**\n * Creates a new script.\n * @param name - The name of the script.\n */\n create(name: string): PromiseLike<ScriptCreateResponse> {\n return this.rpc<ScriptCreateResponse>('Create', {\n name,\n });\n }\n\n /**\n * Removes a script.\n * @param id - The script ID.\n */\n delete(id: number): PromiseLike<null> {\n return this.rpc<null>('Delete', {\n id,\n });\n }\n\n /**\n * Runs a script.\n * @param id - The script ID.\n */\n start(id: number): PromiseLike<ScriptStartStopResponse> {\n return this.rpc<ScriptStartStopResponse>('Start', {\n id,\n });\n }\n\n /**\n * Stops the execution of a script.\n * @param id - The script ID.\n */\n stop(id: number): PromiseLike<ScriptStartStopResponse> {\n return this.rpc<ScriptStartStopResponse>('Stop', {\n id,\n });\n }\n\n /**\n * Uploads code to a script.\n * @param id - The script ID.\n * @param code - The code to upload.\n * @param append - Whether the code should be appended to the script or overwrite any existing code.\n */\n putCode(id: number, code: string, append = false): PromiseLike<ScriptPutCodeResponse> {\n return this.rpc<ScriptPutCodeResponse>('PutCode', {\n id,\n code,\n append,\n });\n }\n\n /**\n * Downloads code from a script.\n * @param id - The script ID.\n * @param offset - The byte offset from the beginning.\n * @param len - The number of bytes to download.\n */\n getCode(id: number, offset = 0, len?: number): PromiseLike<ScriptGetCodeResponse> {\n return this.rpc<ScriptGetCodeResponse>('GetCode', {\n id,\n offset,\n len,\n });\n }\n\n /**\n * Evaluates or executes code inside of a script.\n * @param id - The script ID.\n * @param code - The code to evaluate.\n */\n eval(id: number, code: string): PromiseLike<ScriptEvalResponse> {\n return this.rpc<ScriptEvalResponse>('Eval', {\n id,\n code,\n });\n }\n}\n","import { characteristic, ComponentWithId } from './base';\nimport { Device } from '../devices';\n\nexport interface SwitchEnergyCounterAttributes {\n total: number;\n by_minute: number[];\n minute_ts: number;\n}\n\nexport interface SwitchTemperatureAttributes {\n tC: number | null;\n tF: number | null;\n}\n\nexport interface SwitchAttributes {\n id: number;\n source: string;\n output: boolean;\n timer_started_at?: number;\n timer_duration?: number;\n apower?: number;\n voltage?: number;\n current?: number;\n pf?: number;\n aenergy?: SwitchEnergyCounterAttributes;\n temperature: SwitchTemperatureAttributes;\n errors?: string[];\n}\n\nexport interface SwitchConfig {\n id: number;\n name: string | null;\n in_mode: 'momentary' | 'follow' | 'flip' | 'detached';\n initial_state: 'off' | 'on' | 'restore_last' | 'match_input';\n auto_on: boolean;\n auto_on_delay: number;\n auto_off: boolean;\n auto_off_delay: number;\n input_id: number;\n power_limit?: number | null;\n voltage_limit?: number | null;\n current_limit?: number | null;\n}\n\nexport interface SwitchSetResponse {\n was_on: boolean;\n}\n\n/**\n * Represents a switch (relay) of a device.\n */\nexport class Switch extends ComponentWithId<SwitchAttributes, SwitchConfig> implements SwitchAttributes {\n /**\n * Source of the last command.\n */\n @characteristic\n readonly source: string = '';\n\n /**\n * true if the output channel is currently on, false otherwise.\n */\n @characteristic\n readonly output: boolean = false;\n\n /**\n * Start time of the timer (as a UNIX timestamp, in UTC).\n */\n @characteristic\n readonly timer_started_at: number | undefined;\n\n /**\n * Duration of the timer, in seconds;\n */\n @characteristic\n readonly timer_duration: number | undefined;\n\n /**\n * The current (last measured) instantaneous power delivered to the attached\n * load (if applicable).\n */\n @characteristic\n readonly apower: number | undefined;\n\n /**\n * Last measured voltage (in Volts, if applicable).\n */\n @characteristic\n readonly voltage: number | undefined;\n\n /**\n * Last measured current (in Amperes, if applicable).\n */\n @characteristic\n readonly current: number | undefined;\n\n /**\n * Last measured power factor (if applicable).\n */\n @characteristic\n readonly pf: number | undefined;\n\n /**\n * Information about the energy counter (if applicable).\n */\n @characteristic\n readonly aenergy: SwitchEnergyCounterAttributes | undefined;\n\n /**\n * Information about the temperature.\n */\n @characteristic\n readonly temperature: SwitchTemperatureAttributes = {\n tC: null,\n tF: null,\n };\n\n /**\n * Any error conditions that have occurred.\n */\n @characteristic\n readonly errors: string[] | undefined;\n\n constructor(device: Device, id = 0) {\n super('Switch', device, id);\n }\n\n /**\n * Toggles the switch.\n */\n toggle(): PromiseLike<SwitchSetResponse> {\n return this.rpc<SwitchSetResponse>('Toggle', {\n id: this.id,\n });\n }\n\n /**\n * Sets the output of the switch.\n * @param on - Whether to switch on or off.\n * @param toggle_after - Flip-back timer, in seconds.\n */\n set(on: boolean, toggle_after?: number): PromiseLike<SwitchSetResponse> {\n return this.rpc<SwitchSetResponse>('Set', {\n id: this.id,\n on,\n toggle_after,\n });\n }\n}\n","import { characteristic, Component } from './base';\nimport { Device } from '../devices';\nimport { RpcEvent } from '../rpc';\n\nexport interface SystemFirmwareUpdate {\n stable?: {\n version: string;\n };\n beta?: {\n version: string;\n };\n}\n\nexport interface SystemWakeupReason {\n boot: 'poweron' | 'software_restart' | 'deepsleep_wake' | 'internal' | 'unknown';\n cause: 'button' | 'usb' | 'periodic' | 'status_update' | 'undefined';\n}\n\nexport interface SystemAttributes {\n mac: string;\n restart_required: boolean;\n time: string;\n unixtime: number;\n uptime: number;\n ram_size: number;\n ram_free: number;\n fs_size: number;\n fs_free: number;\n cfg_rev: number;\n kvs_rev: number;\n schedule_rev?: number;\n webhook_rev?: number;\n available_updates: SystemFirmwareUpdate;\n wakeup_reason?: SystemWakeupReason;\n}\n\nexport interface SystemConfig {\n device: {\n name: string;\n eco_mode: boolean;\n mac: string;\n fw_id: string;\n profile?: string;\n discoverable: boolean;\n };\n location: {\n tz: string | null;\n lat: number | null;\n lon: number | null;\n };\n debug: {\n mqtt: {\n enable: boolean;\n };\n websocket: {\n enable: boolean;\n };\n udp: {\n addr: string | null;\n };\n };\n ui_data: Record<string, unknown>;\n rpc_udp: {\n dst_addr: string | null;\n listen_port: number | null;\n };\n sntp: {\n server: string;\n };\n sleep?: {\n wakeup_period: number;\n };\n cfg_rev: number;\n}\n\n/**\n * Handles the system services of a device.\n */\nexport class System extends Component<SystemAttributes, SystemConfig> implements SystemAttributes {\n /**\n * MAC address of the device.\n */\n @characteristic\n readonly mac: string = '';\n\n /**\n * true if a restart is required, false otherwise.\n */\n @characteristic\n readonly restart_required: boolean = false;\n\n /**\n * Local time in the current timezone (HH:MM).\n */\n @characteristic\n readonly time: string = '';\n\n /**\n * Current time in UTC as a UNIX timestamp.\n */\n @characteristic\n readonly unixtime: number = 0;\n\n /**\n * Time in seconds since last reboot.\n */\n @characteristic\n readonly uptime: number = 0;\n\n /**\n * Total RAM, in bytes.\n */\n @characteristic\n readonly ram_size: number = 0;\n\n /**\n * Available RAM, in bytes.\n */\n @characteristic\n readonly ram_free: number = 0;\n\n /**\n * File system total size, in bytes.\n */\n @characteristic\n readonly fs_size: number = 0;\n\n /**\n * File system available size, in bytes.\n */\n @characteristic\n readonly fs_free: number = 0;\n\n /**\n * Configuration revision number.\n */\n @characteristic\n readonly cfg_rev: number = 0;\n\n /**\n * KVS (Key-Value Store) revision number.\n */\n @characteristic\n readonly kvs_rev: number = 0;\n\n /**\n * Schedule revision number (present if schedules are enabled).\n */\n @characteristic\n readonly schedule_rev: number | undefined;\n\n /**\n * Webhook revision number (present if schedules are enabled).\n */\n @characteristic\n readonly webhook_rev: number | undefined;\n\n /**\n * Available firmware updates, if any.\n */\n @characteristic\n readonly available_updates: SystemFirmwareUpdate = {};\n\n /**\n * Information about boot type and cause (only for battery-operated devices).\n */\n @characteristic\n readonly wakeup_reason: SystemWakeupReason | undefined;\n\n constructor(device: Device) {\n super('Sys', device);\n }\n\n handleEvent(event: RpcEvent) {\n switch (event.event) {\n case 'ota_begin':\n this.emit('otaBegin', event.msg);\n break;\n\n case 'ota_progress':\n this.emit('otaProgress', event.progress_percent, event.msg);\n break;\n\n case 'ota_success':\n this.emit('otaSuccess', event.msg);\n break;\n\n case 'ota_error':\n this.emit('otaError', event.msg);\n break;\n\n case 'sleep':\n this.emit('sleep');\n break;\n\n default:\n super.handleEvent(event);\n }\n }\n}\n","import { characteristic, ComponentWithId } from './base';\nimport { Device } from '../devices';\n\nexport interface TemperatureAttributes {\n id: number;\n tC: number | null;\n tF: number | null;\n errors?: string[];\n}\n\nexport interface TemperatureConfig {\n id: number;\n name: string | null;\n report_thr_C: number;\n}\n\n/**\n * Handles the monitoring of a device's temperature sensor.\n */\nexport class Temperature\n extends ComponentWithId<TemperatureAttributes, TemperatureConfig>\n implements TemperatureAttributes\n{\n /**\n * Current temperature, in Celsius.\n */\n @characteristic\n readonly tC: number | null = null;\n\n /**\n * Current temperature, in Fahrenheit.\n */\n @characteristic\n readonly tF: number | null = null;\n\n /**\n * Any error conditions that have occurred.\n */\n @characteristic\n readonly errors: string[] | undefined;\n\n constructor(device: Device, id = 0) {\n super('Temperature', device, id);\n }\n}\n","import { Component } from './base';\nimport { Device } from '../devices';\n\nexport interface UiAttributes {}\n\nexport interface UiConfig {\n idle_brightness: number;\n}\n\n/**\n * Handles the settings of a Pro4PM device's screen.\n */\nexport class Ui extends Component<UiAttributes, UiConfig> implements UiAttributes {\n constructor(device: Device) {\n super('UI', device);\n }\n}\n","import { characteristic, Component } from './base';\nimport { Device } from '../devices';\nimport { RpcEvent } from '../rpc';\n\nexport interface WiFiAttributes {\n sta_ip: string | null;\n status: 'disconnected' | 'connecting' | 'connected' | 'got ip';\n ssid: string | null;\n rssi: number;\n ap_client_count?: number;\n}\n\nexport interface WiFiStationConfig {\n ssid: string | null;\n pass?: string | null;\n is_open: boolean;\n enable: boolean;\n ipv4mode: 'dhcp' | 'static';\n ip: string | null;\n netmask: string | null;\n gw: string | null;\n nameserver: string | null;\n}\n\nexport interface WiFiConfig {\n ap: {\n ssid: string | null;\n is_open: boolean;\n enable: boolean;\n range_extender?: {\n enable: boolean;\n };\n };\n sta: WiFiStationConfig;\n sta1: WiFiStationConfig;\n roam: {\n rssi_thr: number;\n interval: number;\n };\n}\n\nexport interface WiFiScanResponse {\n results: Array<{\n ssid: string | null;\n bssid: string;\n auth: 0 | 1 | 2 | 3 | 4 | 5;\n channel: number;\n rssi: number;\n }>;\n}\n\nexport interface WiFiListApClientsResponse {\n ts: number | null;\n ap_clients: Array<{\n mac: string;\n ip: string;\n ip_static: boolean;\n mport: number;\n since: number;\n }>;\n}\n\n/**\n * Handles the WiFi services of a device.\n */\nexport class WiFi extends Component<WiFiAttributes, WiFiConfig> implements WiFiAttributes {\n /**\n * IP address of the device.\n */\n @characteristic\n readonly sta_ip: string | null = null;\n\n /**\n * Status of the connection.\n */\n @characteristic\n readonly status: 'disconnected' | 'connecting' | 'connected' | 'got ip' = 'disconnected';\n\n /**\n * SSID of the network.\n */\n @characteristic\n readonly ssid: string | null = null;\n\n /**\n * Signal strength, in dBms.\n */\n @characteristic\n readonly rssi: number = 0;\n\n /**\n * Number of clients connected to the access point.\n */\n @characteristic\n readonly ap_client_count: number | undefined;\n\n constructor(device: Device) {\n super('WiFi', device);\n }\n\n /**\n * Retrieves a list of available networks.\n */\n scan(): PromiseLike<WiFiScanResponse> {\n return this.rpc<WiFiScanResponse>('Scan');\n }\n\n /**\n * Returns a list of clients currently connected to the device's access point.\n */\n listApClients(): PromiseLike<WiFiListApClientsResponse> {\n return this.rpc<WiFiListApClientsResponse>('ListAPClients');\n }\n\n handleEvent(event: RpcEvent) {\n switch (event.event) {\n case 'sta_connect_fail':\n this.emit('connectionError', event.reason);\n break;\n\n case 'sta_disconnected':\n this.emit('disconnect', event.reason, event.ssid, event.sta_ip);\n break;\n\n default:\n super.handleEvent(event);\n }\n }\n}\n","import { characteristic, ComponentWithId } from './base';\nimport { Device } from '../devices';\n\nexport interface Pm1AenergyStatus {\n total: number;\n by_minute?: number[];\n minute_ts: number;\n}\n\nexport interface Pm1Attributes {\n id: number;\n output: boolean;\n voltage?: number;\n current?: number;\n apower?: number;\n aprtpower?: number;\n pf?: number;\n freq?: number;\n aenergy?: Pm1AenergyStatus;\n ret_aenergy?: Pm1AenergyStatus;\n errors?: string[];\n}\n\nexport interface Pm1Config {\n id: number;\n name: string | null;\n}\n\n/**\n * Handles the monitoring of a device's temperature sensor.\n */\nexport class Pm1 extends ComponentWithId<Pm1Attributes, Pm1Config> implements Pm1Attributes {\n /**\n * true if the output channel is currently on, false otherwise.\n */\n @characteristic\n readonly output: boolean = false;\n\n /**\n * Last measured voltage in Volts\n */\n @characteristic\n readonly voltage: number | undefined;\n\n /**\n * Last measured current in Amperes\n */\n @characteristic\n readonly current: number | undefined;\n\n /**\n * Last measured instantaneous active power (in Watts) delivered to the attached load\n */\n @characteristic\n readonly apower: number | undefined;\n\n /**\n * Last measured instantaneous apparent power (in Volt-Amperes) delivered to the attached load (shown if applicable)\n */\n @characteristic\n readonly aprtpower: number | undefined;\n\n /**\n * Last measured power factor (shown if applicable)\n */\n @characteristic\n readonly pf: number | undefined;\n /**\n * Last measured network frequency (shown if applicable)\n */\n @characteristic\n readonly freq: number | undefined;\n /**\n * Information about the active energy counter\n */\n @characteristic\n readonly aenergy: Pm1AenergyStatus | undefined;\n\n /**\n * Information about the returned active energy counter\n */\n @characteristic\n readonly ret_aenergy: Pm1AenergyStatus | undefined;\n\n /**\n * Any error conditions that have occurred.\n */\n @characteristic\n readonly errors: string[] | undefined;\n\n constructor(device: Device, id = 0) {\n super('Pm1', device, id);\n }\n}\n","import EventEmitter from 'eventemitter3';\n\nimport { Component, ComponentLike, System } from '../components';\nimport { HttpService, KvsService, ScheduleService, ShellyService, WebhookService } from '../services';\nimport { RpcEventNotification, RpcHandler, RpcStatusNotification } from '../rpc';\n\nexport type DeviceId = string;\n\n/**\n * Information about a device.\n */\nexport interface DeviceInfo {\n /**\n * The device ID.\n */\n id: DeviceId;\n /**\n * The MAC address of the device.\n */\n mac: string;\n /**\n * The model designation of the device.\n */\n model?: string;\n /**\n * Current firmware ID.\n */\n fw_id?: string;\n /**\n * Current firmware version.\n */\n ver?: string;\n /**\n * Current device profile.\n */\n profile?: string;\n}\n\n/**\n * Information about the firmware that a device is running.\n */\nexport interface DeviceFirmwareInfo {\n /**\n * The firmware ID.\n */\n id?: string;\n /**\n * The firmware version.\n */\n version?: string;\n}\n\n/**\n * Describes a device class and its static properties.\n */\nexport interface DeviceClass {\n new (info: DeviceInfo, rpcHandler: RpcHandler): Device;\n\n /**\n * The model designation of the device.\n */\n model: string;\n /**\n * A human-friendly name of the device model.\n */\n modelName: string;\n}\n\n/**\n * Property decorator used to label properties as components.\n * @param target - The prototype of the device class that the property belongs to.\n * @param propertyName - The name of the property.\n */\n// eslint-disable-next-line @typescript-eslint/no-explicit-any\nexport const component = (target: any, propertyName: string) => {\n // make sure the given prototype has an array of properties\n if (!Object.prototype.hasOwnProperty.call(target, '_componentProps')) {\n target._componentProps = new Array<string>();\n }\n\n // get the array of properties\n const props: string[] = target._componentProps;\n\n // add this property to the array\n props.push(propertyName);\n};\n\n/**\n * Base class for all devices.\n */\nexport abstract class Device extends EventEmitter {\n /**\n * Holds all registered subclasses.\n */\n private static readonly subclasses = new Map<string, DeviceClass>();\n\n /**\n * Registers a device class, so that it can later be found based on its device model\n * using the `Device.getClass()` method.\n * @param cls - A subclass of `Device`.\n */\n static registerClass(cls: DeviceClass) {\n const model = cls.model.toUpperCase();\n // make sure it's not already registered\n if (Device.subclasses.has(model)) {\n throw new Error(`A device class for ${model} has already been registered`);\n }\n\n // add it to the list\n Device.subclasses.set(model, cls);\n }\n\n /**\n * Returns the device class for the given device model, if one has been registered.\n * @param model - The model designation to lookup.\n */\n static getClass(model: string): DeviceClass | undefined {\n return Device.subclasses.get(model.toUpperCase());\n }\n\n /**\n * The ID of this device.\n */\n readonly id: DeviceId;\n\n /**\n * The MAC address of this device.\n */\n readonly macAddress: string;\n\n /**\n * Information about the firmware that the device is running.\n */\n readonly firmware: DeviceFirmwareInfo;\n\n /**\n * This device's Shelly service.\n */\n readonly shelly = new ShellyService(this);\n\n /**\n * This device's Schedule service.\n */\n readonly schedule = new ScheduleService(this);\n\n /**\n * This device's Webhook service.\n */\n readonly webhook = new WebhookService(this);\n\n /**\n * This device's HTTP service.\n */\n readonly http = new HttpService(this);\n\n /**\n * This device's KVS service.\n */\n readonly kvs = new KvsService(this);\n\n @component\n readonly system = new System(this);\n\n /**\n * @param info - Information about this device.\n * @param rpcHandler - Used to make remote procedure calls.\n */\n constructor(\n info: DeviceInfo,\n readonly rpcHandler: RpcHandler,\n ) {\n super();\n\n this.id = info.id;\n this.macAddress = info.mac;\n this.firmware = {\n id: info.fw_id,\n version: info.ver,\n };\n this._model = info.model;\n\n // handle status updates\n rpcHandler.on('statusUpdate', this.statusUpdateHandler, this);\n\n // handle events\n