From 764a3ca2236457d3fa675fb11a31570100f26385 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Victor=20Hagelb=C3=A4ck?= Date: Mon, 29 Mar 2021 12:48:27 +0200 Subject: [PATCH] Add typings and jsdoc comments to improve developer experience --- plejd/Configuration.js | 8 +- plejd/PlejdBLEHandler.js | 4 +- plejd/README.md | 5 + plejd/jsconfig.json | 10 + plejd/types/ApiSite.d.ts | 655 ++++++++++++++++++++++++++++++++ plejd/types/Configuration.d.ts | 50 +++ plejd/types/DeviceRegistry.d.ts | 21 + plejd/types/PlejdApi.d.ts | 11 + plejd/typings.json | 5 + 9 files changed, 765 insertions(+), 4 deletions(-) create mode 100644 plejd/jsconfig.json create mode 100644 plejd/types/ApiSite.d.ts create mode 100644 plejd/types/Configuration.d.ts create mode 100644 plejd/types/DeviceRegistry.d.ts create mode 100644 plejd/types/PlejdApi.d.ts create mode 100644 plejd/typings.json diff --git a/plejd/Configuration.js b/plejd/Configuration.js index 88a02b3..4b62689 100644 --- a/plejd/Configuration.js +++ b/plejd/Configuration.js @@ -1,9 +1,12 @@ const fs = require('fs'); class Configuration { + /** @type {import('types/Configuration').Options} */ static _options = null; + /** @type {import('types/Configuration').AddonInfo} */ static _addonInfo = null; + /** @returns Options */ static getOptions() { if (!Configuration._options) { Configuration._hydrateCache(); @@ -11,6 +14,7 @@ class Configuration { return Configuration._options; } + /** @returns AddonInfo */ static getAddonInfo() { if (!Configuration._addonInfo) { Configuration._hydrateCache(); @@ -20,10 +24,10 @@ class Configuration { static _hydrateCache() { const rawData = fs.readFileSync('/data/options.json'); - const config = JSON.parse(rawData); + const config = JSON.parse(rawData.toString()); const defaultRawData = fs.readFileSync('/plejd/config.json'); - const defaultConfig = JSON.parse(defaultRawData); + const defaultConfig = JSON.parse(defaultRawData.toString()); Configuration._options = { ...defaultConfig.options, ...config }; Configuration._addonInfo = { diff --git a/plejd/PlejdBLEHandler.js b/plejd/PlejdBLEHandler.js index 9256324..84f1329 100644 --- a/plejd/PlejdBLEHandler.js +++ b/plejd/PlejdBLEHandler.js @@ -1,7 +1,7 @@ const dbus = require('dbus-next'); const crypto = require('crypto'); const xor = require('buffer-xor'); -const EventEmitter = require('events'); +const { EventEmitter } = require('events'); const Configuration = require('./Configuration'); const constants = require('./constants'); @@ -874,7 +874,7 @@ class PlejBLEHandler extends EventEmitter { (pl) => pl.writeInt32LE(Math.trunc(newLocalTimestamp), 5), ); try { - this.write(payload); + this._write(payload); } catch (err) { logger.error( 'Failed writing new time to Plejd. Will try again in one hour or at restart.', diff --git a/plejd/README.md b/plejd/README.md index dec014d..336ea57 100644 --- a/plejd/README.md +++ b/plejd/README.md @@ -189,6 +189,11 @@ The code in this project follows the [Airbnb JavaScript guide](https://github.co For a nice developer experience it is very convenient to have `eslint` and `prettier` installed in your favorite editor (such as VS Code) and use the "format on save" option (or invoke formatting by Alt+Shift+F in VS Code). Any code issues should appear in the problems window inside the editor, as well as when running the command above. +For partial type hinting you can run + +- `npm install --global typings` +- `typings install` + When contributing, please do so by forking the repo and then using pull requests towards the dev branch. ### Logs diff --git a/plejd/jsconfig.json b/plejd/jsconfig.json new file mode 100644 index 0000000..9599f8f --- /dev/null +++ b/plejd/jsconfig.json @@ -0,0 +1,10 @@ +{ + "compilerOptions": { + "baseUrl": ".", + "checkJs": true, + "module": "commonjs", + "moduleResolution": "node", + "target": "es6" + }, + "exclude": ["node_modules", "rootfs"] +} diff --git a/plejd/types/ApiSite.d.ts b/plejd/types/ApiSite.d.ts new file mode 100644 index 0000000..856d056 --- /dev/null +++ b/plejd/types/ApiSite.d.ts @@ -0,0 +1,655 @@ +/* eslint-disable camelcase */ +/* eslint-disable no-use-before-define */ + +export interface CachedSite { + siteId: string; + siteDetails: ApiSite; + sessionToken: string; + dtCache: string; +} + +export interface ApiSite { + site: SiteDetailsSite; + plejdMesh: PlejdMesh; + rooms: Room[]; + scenes: Scene[]; + devices: Device[]; + plejdDevices: PlejdDevice[]; + gateways: Gateway[]; + resourceSets: ResourceSet[]; + timeEvents: TimeEvent[]; + sceneSteps: SceneStep[]; + astroEvents: AstroEvent[]; + inputSettings: InputSetting[]; + outputSettings: OutputSetting[]; + stateTimers: StateTimers; + sitePermission: SitePermission; + inputAddress: { [key: string]: { [key: string]: number } }; + outputAddress: { [key: string]: OutputAddress }; + deviceAddress: { [key: string]: number }; + outputGroups: { [key: string]: OutputGroup }; + roomAddress: { [key: string]: number }; + sceneIndex: { [key: string]: number }; + images: Images; + deviceLimit: number; +} + +export interface AstroEvent { + dirtyDevices?: any[]; + dirtyRemovedDevices?: any[]; + deviceId: string; + siteId: string; + sceneId: string; + fadeTime: number; + activated: boolean; + astroEventId: string; + index: number; + sunriseOffset: number; + sunsetOffset: number; + pauseStart: string; + pauseEnd: string; + createdAt: Date; + updatedAt: Date; + dirtyRemove?: boolean; + ACL: AstroEventACL; + targetDevices: AstroEventTargetDevice[]; + objectId: string; + __type: AstroEventType; + className: string; +} + +export interface AstroEventACL {} + +export enum AstroEventType { + Object = 'Object', +} + +export interface AstroEventTargetDevice { + deviceId: string; + index: number; +} + +export interface Device { + deviceId: string; + siteId: string; + roomId: string; + title: string; + traits: number; + hardware?: Hardware; + hiddenFromRoomList: boolean; + createdAt: Date; + updatedAt: Date; + outputType: OutputType; + ACL: AstroEventACL; + objectId: string; + __type: AstroEventType; + className: DeviceClassName; + hiddenFromIntegrations?: boolean; +} + +export enum DeviceClassName { + Device = 'Device', +} + +export interface Hardware { + createdAt: Date; + updatedAt: Date; + name: Name; + hardwareId: string; + minSupportedFirmware: PlejdMeshClass; + latestFirmware: PlejdMeshClass; + brand: Brand; + type: Type; + image: Image; + requiredAccountType: RequiredAccountType[]; + numberOfDevices: number; + predefinedLoad: PredefinedLoad; + supportedFirmware: PredefinedLoad; + ACL: AstroEventACL; + objectId: HardwareObjectID; + __type: AstroEventType; + className: HardwareClassName; +} + +export enum Brand { + PlejdLight = 'Plejd Light', +} + +export enum HardwareClassName { + Hardware = 'Hardware', +} + +export interface Image { + __type: ImageType; + name: string; + url: string; +} + +export enum ImageType { + File = 'File', +} + +export interface PlejdMeshClass { + __type: InstallerType; + className: SiteClassName; + objectId: ObjectID; +} + +export enum InstallerType { + Pointer = 'Pointer', +} + +export enum SiteClassName { + DimCurve = 'DimCurve', + Firmware = 'Firmware', + PlejdMesh = 'PlejdMesh', + Site = 'Site', + User = '_User', + UserProfile = 'UserProfile', +} + +export enum ObjectID { + BBBJO2Cufm = 'BBBJO2cufm', + D4Dw87Hq21 = 'D4DW87HQ21', + FCrrS1NJHH = 'FCrrS1nJHH', + GX1W4P06QS = 'gX1W4p06QS', + Ndlvzgh4Df = 'ndlvzgh4df', + UHoKQLuXqZ = 'uHoKQLuXqZ', + VfHiawBPA8 = 'vfHiawBPA8', + WgAFPloWjK = 'wgAfPloWjK', + YkyNDotBNa = 'YkyNDotBNa', +} + +export enum Name { + Ctr01 = 'CTR-01', + Dim01 = 'DIM-01', +} + +export enum HardwareObjectID { + R3Gfd6ACAu = 'R3gfd6ACAu', + XjslOltgvi = 'xjslOltgvi', +} + +export interface PredefinedLoad { + __type: SupportedFirmwareType; + className: PredefinedLoadClassName; +} + +export enum SupportedFirmwareType { + Relation = 'Relation', +} + +export enum PredefinedLoadClassName { + DimCurve = 'DimCurve', + Firmware = 'Firmware', + PredefinedLoad = 'PredefinedLoad', +} + +export enum RequiredAccountType { + Installer = 'installer', +} + +export enum Type { + Controller = 'Controller', + LEDDimmer = 'LED Dimmer', +} + +export enum OutputType { + Light = 'LIGHT', + Relay = 'RELAY', +} + +export interface Gateway { + title: string; + deviceId: string; + siteId: string; + hardwareId: string; + installer: ObjectID; + firmware: number; + firmwareObject: Firmware; + dirtyInstall: boolean; + dirtyUpdate: boolean; + createdAt: Date; + updatedAt: Date; + factoryKey: string; + resourceSetId: string; + ACL: AstroEventACL; + objectId: string; + __type: AstroEventType; + className: string; +} + +export interface Firmware { + notes: Notes; + createdAt: Date; + updatedAt: Date; + data: Image; + metaData: Image; + version: Version; + buildTime: number; + firmwareApi: string; + ACL: AstroEventACL; + objectId: FirmwareObjectObjectID; + __type: AstroEventType; + className: SiteClassName; +} + +export enum Notes { + Ctr01 = 'CTR-01', + Ctr20ReleaseCandidate1 = 'Ctr 2.0 Release candidate 1', + Dim20ReleaseCandidate1 = 'Dim 2.0 Release candidate 1', + Dim221ReleaseCandidate = 'Dim 2.2.1 Release Candidate', + GWY10ReleaseCandidate = 'GWY 1.0 Release Candidate', +} + +export enum FirmwareObjectObjectID { + BBBJO2Cufm = 'BBBJO2cufm', + E6YxfREDuF = 'E6yxfREDuF', + JYSZ0EvyCU = 'JYSZ0EvyCU', + Ndlvzgh4Df = 'ndlvzgh4df', + RlglTfVHDe = 'rlglTfVHDe', +} + +export enum Version { + The12 = '1.2', + The20 = '2.0', + The221 = '2.2.1', + The304 = '3.0.4', +} + +export interface Images { + '2afc6c6e-7a26-466a-b8ec-febbca90f5f7': string; +} + +export interface InputSetting { + deviceId: string; + input: number; + siteId: string; + dimSpeed: number; + buttonType: ButtonType; + createdAt: Date; + updatedAt: Date; + ACL: AstroEventACL; + objectId: string; + __type: AstroEventType; + className: InputSettingClassName; + doubleClick?: string; + singleClick?: null; + doubleSidedDirectionButton?: boolean; +} + +export enum ButtonType { + PushButton = 'PushButton', + RotateMesh = 'RotateMesh', + Scene = 'Scene', +} + +export enum InputSettingClassName { + PlejdDeviceInputSetting = 'PlejdDeviceInputSetting', +} + +export interface OutputAddress { + '0': number; +} + +export interface OutputGroup { + '0': number[]; +} + +export interface OutputSetting { + deviceId: string; + output: number; + deviceParseId: string; + siteId: string; + predefinedLoad: OutputSettingPredefinedLoad; + createdAt: Date; + updatedAt: Date; + dimMin: number; + dimMax: number; + dimStart: number; + outputStartTime: number; + outputSpeed: number; + bootState: BootState; + dimCurve: DimCurve; + curveLogarithm: number; + curveSinusCompensation: number; + curveRectification: boolean; + output_0_10V_Mode?: Output0_10_VMode; + zeroCrossing?: Output0_10_VMode; + minimumRelayOffTime?: number; + ACL: AstroEventACL; + objectId: string; + __type: AstroEventType; + className: OutputSettingClassName; + ledCurrent?: number; + ledVoltage?: number; + relayConfig?: Output0_10_VMode; +} + +export enum BootState { + UseLast = 'UseLast', +} + +export enum OutputSettingClassName { + PlejdDeviceOutputSetting = 'PlejdDeviceOutputSetting', +} + +export enum DimCurve { + LinearLogarithmicSlidingProportion = 'LinearLogarithmicSlidingProportion', + NonDimmable = 'NonDimmable', +} + +export enum Output0_10_VMode { + Unknown = 'Unknown', +} + +export interface OutputSettingPredefinedLoad { + updatedAt: Date; + createdAt: Date; + loadType: string; + predefinedLoadData: string; + defaultDimCurve: PlejdMeshClass; + description_en?: DescriptionEn; + title_en?: TitleEn; + title_sv?: TitleSv; + description_sv?: DescriptionSv; + titleKey: string; + descriptionKey: string; + allowedDimCurves: PredefinedLoad; + ACL: PredefinedLoadACL; + objectId: string; + __type: AstroEventType; + className: PredefinedLoadClassName; + supportMessage?: SupportMessage; + filters?: Filters; +} + +export interface PredefinedLoadACL { + '*': Empty; +} + +export interface Empty { + read: boolean; +} + +export enum DescriptionEn { + OnOff = 'On / Off', + OnlySwitchingOffOn = 'Only switching off/on', + The230VDimmableLEDLightSourceMax100VA = '230V dimmable LED light source - Max 100VA', + The230VIncandescentHalogenElectronicTransformatorMax300W = '230V Incandescent / Halogen, Electronic transformator - Max 300W', + WithoutRelay = 'Without relay', +} + +export enum DescriptionSv { + EndastBrytningAVPå = 'Endast brytning av/på', + ReläbrytningAVPå = 'Reläbrytning av/på', + The230VDimbarLEDLjuskällaMax100VA = '230V dimbar LED ljuskälla - Max 100VA', + The230VDimbarLEDLjuskällaMax200VA = '230V dimbar LED ljuskälla - Max 200VA', + The230VHalogenGlödljusElektroniskTransformatorMax300W = '230V Halogen / Glödljus, Elektronisk transformator - Max 300W', + UtanReläbrytning = 'Utan reläbrytning', +} + +export interface Filters { + allowedCountriesFilter: AllowedCountriesFilter; +} + +export interface AllowedCountriesFilter { + countryCodes: CountryCode[]; +} + +export enum CountryCode { + Fi = 'FI', + No = 'NO', + SE = 'SE', +} + +export enum SupportMessage { + PredefinedLoadNonDimmableSupportMessageHTML = 'PredefinedLoadNonDimmableSupportMessageHTML', +} + +export enum TitleEn { + IncandescentHalogen = 'Incandescent / Halogen', + LEDTrailingEdgeCommon = 'LED Trailing Edge (Common)', + LeadingEdge = 'Leading edge', + NonDimmableLEDLightSourceMax200VA = 'Non-dimmable LED light source (Max 200VA)', + RelayOnly = 'Relay only', + The010V = '0-10V', +} + +export enum TitleSv { + EjDimbarLEDLjuskällaMax200VA = 'Ej dimbar LED-ljuskälla (Max 200VA)', + HalogenGlödljus = 'Halogen / Glödljus', + LEDBakkantVanligast = 'LED Bakkant (Vanligast)', + LEDFramkant = 'LED Framkant', + Reläfunktion = 'Reläfunktion', + The010V = '0-10V', +} + +export interface PlejdDevice { + deviceId: string; + installer: PlejdMeshClass; + dirtyInstall: boolean; + dirtyUpdate: boolean; + dirtyClock: boolean; + hardwareId: string; + faceplateId: string; + firmware: Firmware; + createdAt: Date; + updatedAt: Date; + coordinates: Coordinates; + dirtySettings: boolean; + diagnostics: string; + siteId: string; + predefinedLoad: OutputSettingPredefinedLoad; + ACL: AstroEventACL; + objectId: string; + __type: AstroEventType; + className: PlejdDeviceClassName; +} + +export enum PlejdDeviceClassName { + PlejdDevice = 'PlejdDevice', +} + +export interface Coordinates { + __type: CoordinatesType; + latitude: number; + longitude: number; +} + +export enum CoordinatesType { + GeoPoint = 'GeoPoint', +} + +export interface PlejdMesh { + siteId: string; + plejdMeshId: string; + meshKey: string; + cryptoKey: string; + createdAt: Date; + updatedAt: Date; + site: PlejdMeshClass; + ACL: AstroEventACL; + objectId: ObjectID; + __type: AstroEventType; + className: SiteClassName; +} + +export interface ResourceSet { + scopes: string[]; + remoteAccessUsers: string[]; + name: string; + type: string; + createdAt: Date; + updatedAt: Date; + ACL: AstroEventACL; + objectId: string; + __type: AstroEventType; + className: string; +} + +export interface Room { + siteId: string; + roomId: string; + title: string; + category: string; + imageHash: number; + createdAt: Date; + updatedAt: Date; + ACL: AstroEventACL; + objectId: string; + __type: AstroEventType; + className: RoomClassName; +} + +export enum RoomClassName { + Room = 'Room', +} + +export interface SceneStep { + sceneId: string; + siteId: string; + deviceId: string; + state: State; + value: number; + output: number; + createdAt: Date; + updatedAt: Date; + ACL: AstroEventACL; + objectId: string; + __type: AstroEventType; + className: SceneStepClassName; + dirty?: boolean; + dirtyRemoved?: boolean; +} + +export enum SceneStepClassName { + SceneStep = 'SceneStep', +} + +export enum State { + Off = 'Off', + On = 'On', +} + +export interface Scene { + title: string; + sceneId: string; + siteId: string; + hiddenFromSceneList: boolean; + settings: string; + createdAt: Date; + updatedAt: Date; + ACL: AstroEventACL; + objectId: string; + __type: AstroEventType; + className: ButtonType; +} + +export interface SiteDetailsSite { + installers: ObjectID[]; + title: string; + siteId: string; + version: number; + createdAt: Date; + updatedAt: Date; + plejdMesh: PlejdMeshClass; + coordinates: Coordinates; + astroTable: AstroTable; + deviceAstroTable: DeviceAstroTable; + zipCode: string; + city: string; + country: string; + previousOwners: string[]; + ACL: AstroEventACL; + objectId: ObjectID; + __type: AstroEventType; + className: SiteClassName; +} + +export interface AstroTable { + sunrise: string[]; + sunset: string[]; +} + +export interface DeviceAstroTable { + sunrise: number[]; + sunset: number[]; +} + +export interface SitePermission { + siteId: string; + userId: ObjectID; + user: User; + isOwner: boolean; + isInstaller: boolean; + isUser: boolean; + site: SiteDetailsSite; + createdAt: Date; + updatedAt: Date; + ACL: AstroEventACL; + objectId: string; + __type: AstroEventType; + className: string; +} + +export interface User { + profileName: string; + isInstaller: boolean; + email: string; + locale: string; + username: string; + emailVerified: boolean; + createdAt: Date; + updatedAt: Date; + profile: PlejdMeshClass; + _failed_login_count: number; + hasIntegration: boolean; + ACL: UserACL; + objectId: ObjectID; + __type: AstroEventType; + className: SiteClassName; +} + +export interface UserACL { + gX1W4p06QS: GX1W4P06QS; +} + +export interface GX1W4P06QS { + read: boolean; + write: boolean; +} + +export interface StateTimers { + SafetyTimer: any[]; +} + +export interface TimeEvent { + dirtyDevices?: any[]; + dirtyRemovedDevices?: any[]; + scheduledDays: number[]; + deviceId: string; + siteId: string; + sceneId: string; + fadeTime: number; + activated: boolean; + timeEventId: string; + startTimeIndex: number; + endTimeIndex: number; + startTime: string; + endTime: string; + createdAt: Date; + updatedAt: Date; + dirtyRemove?: boolean; + ACL: AstroEventACL; + targetDevices: TimeEventTargetDevice[]; + objectId: string; + __type: AstroEventType; + className: string; +} + +export interface TimeEventTargetDevice { + deviceId: string; + startTimeIndex: number; + endTimeIndex: number; +} diff --git a/plejd/types/Configuration.d.ts b/plejd/types/Configuration.d.ts new file mode 100644 index 0000000..016d19b --- /dev/null +++ b/plejd/types/Configuration.d.ts @@ -0,0 +1,50 @@ +/* eslint-disable no-use-before-define */ + +export interface AddonInfo { + name: string; + version: string; + slug: string; + description: string; + url: string; + arch: string[]; + startup: string; + boot: string; + host_network: boolean; + host_dbus: boolean; + apparmor: boolean; +} + +export interface Configuration extends AddonInfo { + options: Options; + schema: Schema; +} + +export interface Options { + site: string; + username: string; + password: string; + mqttBroker: string; + mqttUsername: string; + mqttPassword: string; + includeRoomsAsLights: boolean; + preferCachedApiResponse: boolean; + updatePlejdClock: boolean; + logLevel: string; + connectionTimeout: number; + writeQueueWaitTime: number; +} + +export interface Schema { + site: string; + username: string; + password: string; + mqttBroker: string; + mqttUsername: string; + mqttPassword: string; + includeRoomsAsLights: string; + preferCachedApiResponse: string; + updatePlejdClock: string; + logLevel: string; + connectionTimeout: string; + writeQueueWaitTime: string; +} diff --git a/plejd/types/DeviceRegistry.d.ts b/plejd/types/DeviceRegistry.d.ts new file mode 100644 index 0000000..4dc373f --- /dev/null +++ b/plejd/types/DeviceRegistry.d.ts @@ -0,0 +1,21 @@ +/* eslint-disable no-use-before-define */ + +export type OutputDevices = { [deviceIdAndOutput: string]: OutputDevice }; + +export interface OutputDevice { + bleDeviceIndex: number; + deviceId: string; + dim?: number; + dimmable: boolean; + hiddenFromRoomList?: boolean; + hiddenFromIntegrations?: boolean; + hiddenFromSceneList?: boolean; + name: string; + output: number; + roomId: string; + state: number | undefined; + type: string; + typeName: string; + version: string; + uniqueId: string; +} diff --git a/plejd/types/PlejdApi.d.ts b/plejd/types/PlejdApi.d.ts new file mode 100644 index 0000000..1cbdbe8 --- /dev/null +++ b/plejd/types/PlejdApi.d.ts @@ -0,0 +1,11 @@ +/* eslint-disable no-use-before-define */ + +import { ApiSite } from './ApiSite'; + +export type PlejdApi = { + config: any; + deviceRegistry: any; + sessionToken: string; + siteId: string; + siteDetails: ApiSite; +}; diff --git a/plejd/typings.json b/plejd/typings.json new file mode 100644 index 0000000..0fd4334 --- /dev/null +++ b/plejd/typings.json @@ -0,0 +1,5 @@ +{ + "globalDependencies": { + "node": "registry:dt/node#7.0.0+20170322231424" + } +}