From 677f29aa488be8557c6f1866d4ac8d7468b040cd Mon Sep 17 00:00:00 2001 From: S222em <68066476+S222em@users.noreply.github.com> Date: Wed, 28 Jun 2023 10:43:58 +0200 Subject: [PATCH] V0.6 (#61) * refactor(GroupedLight): changed .owner to ResourceIdentifier removed(GroupedLight): .ownerIdentifier refactor(Room): changed .children to ResourceIdentifier[] removed(Room): .childIdentifiers refactor(Room): changed .services to ResourceIdentifier[] removed(Room): .serviceIdentifiers refactor(Zone): changed .children to ResourceIdentifier[] removed(Zone): .childIdentifiers refactor(Zone): changed .services to ResourceIdentifier[] removed(Zone): .serviceIdentifiers refactor(Scene): changed .group to ResourceIdentifier removed(Scene): .groupIdentifier types(ResourceManager): moved function overloads to an interface feat(BridgeHome) * chore(deps): update andrew-chen-wang/github-wiki-action action to v4 (#50) Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com> * chore(deps): update dependency tslib to v2.5.1 (#49) Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com> * chore(deps): update dependency tslib to v2.5.2 (#51) Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com> * chore(deps): update dependency typedoc to v0.24.8 (#56) Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com> * chore(deps): update typescript-eslint monorepo to v5.59.11 (#53) Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com> * chore(deps): update dependency tslib to v2.5.3 (#55) Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com> * chore(deps): update dependency typescript to v5.1.3 (#54) Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com> * chore(deps): update dependency eslint to v8.43.0 (#52) Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com> * DevicePower & ResourceManager feat(DevicePower) fix(ResourceManager) overloads not recognised by webstorm * refactor(ResourceManager): merged into Bridge feat(Cache): better functions to retrieve resources from cache refactor(Device): .services is now of type ResourceIdentifier[] fix(DevicePower): add exports * Caching & Motion refactor(Cache): add cache managers for each resource feat(Motion): added motion resource Signed-off-by: S222em * Many changes feat(ArcheType): add support for archeTypes refactor(Resources): move request methods to manager feat(Room & Zone): create fix(Ratelimit): incorrect route parsing let to breaking the limit refactor(ApiResourceType): renamed to ResourceType refactor(ResourceIdentifier): remove as return value e.g. .services -> .serviceIds refactor(Light): removed DimmableLight, MirekLight, XyLight, XysLight refactor(Light & GroupedLight & Scene): renamed xy & xys to color & gradient feat(Scene): added action editing refactor(Zone & Room): removeChildren and addChildren now only accept an array with id's refactor(Manager): removed .find And some more minor changes.. * feat(Scene): create Signed-off-by: s222em * refactor(Managers): move .create, .edit and .delete to respective managers Signed-off-by: s222em * feat(Transformers): reuse code that transforms input to API acceptable format Signed-off-by: s222em * refactor(API types): reorganize types Signed-off-by: s222em * feat(ZigbeeConnectivity): added support for zigbeeconnectivity resource Signed-off-by: s222em * Changes refactor(Bridge): renamed to Hue feat(Bridge): added support for bridge resource feat(ZigbeeDeviceDiscovery): added support for device discovery feat(Enums): added missing enums for options Signed-off-by: s222em * Changes bump(Version): 0.6 refactor(Readme): update for latest changes refactor(mirek): change to colorTemperature fix(hex): fixed issue with hex parsing Signed-off-by: s222em --------- Signed-off-by: S222em Signed-off-by: s222em Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com> --- .eslintrc.json | 4 +- .github/workflows/wiki.yml | 2 +- README.md | 289 ++++++------------ package-lock.json | 167 +++++----- package.json | 18 +- src/api/ApiResourceType.ts | 129 -------- src/api/ArcheType.ts | 91 ++++++ src/api/ResourceIdentifier.ts | 4 +- src/api/ResourceType.ts | 141 +++++++++ src/api/device/put.ts | 12 - src/api/get/BridgeGet.ts | 12 + src/api/get/BridgeHomeGet.ts | 9 + src/api/{device/get.ts => get/DeviceGet.ts} | 11 +- src/api/get/DevicePowerGet.ts | 12 + .../get.ts => get/GroupedLightGet.ts} | 6 +- src/api/{light/get.ts => get/LightGet.ts} | 13 +- src/api/get/MotionGet.ts | 13 + src/api/{room/get.ts => get/RoomGet.ts} | 9 +- src/api/{scene/get.ts => get/SceneGet.ts} | 6 +- src/api/get/ZigbeeConnectivityGet.ts | 10 + src/api/get/ZigbeeDeviceDiscoveryGet.ts | 9 + src/api/{zone/get.ts => get/ZoneGet.ts} | 9 +- src/api/post/RoomPost.ts | 12 + src/api/{scene/post.ts => post/ScenePost.ts} | 6 +- src/api/post/ZonePost.ts | 12 + src/api/put/DevicePut.ts | 13 + .../put.ts => put/GroupedLightPut.ts} | 9 +- src/api/{light/put.ts => put/LightPut.ts} | 9 +- src/api/put/MotionPut.ts | 6 + src/api/put/RoomPut.ts | 12 + src/api/{scene/put.ts => put/ScenePut.ts} | 6 +- src/api/put/ZigbeeDeviceDiscoveryPut.ts | 11 + src/api/put/ZonePut.ts | 12 + src/api/room/post.ts | 11 - src/api/room/put.ts | 11 - src/api/zone/post.ts | 11 - src/api/zone/put.ts | 11 - src/bridge/Bridge.ts | 74 ----- src/{util => }/color/gamut.ts | 0 src/{util => }/color/hex.ts | 10 +- src/{util => }/color/rgb.ts | 0 src/{util => }/color/xy.ts | 0 src/connections/Limit.ts | 2 +- src/connections/Rest.ts | 36 +-- src/connections/Sse.ts | 116 ++----- src/connections/events/add/BridgeAdd.ts | 9 + src/connections/events/add/DeviceAdd.ts | 9 + src/connections/events/add/DevicePowerAdd.ts | 9 + src/connections/events/add/GroupedLightAdd.ts | 9 + src/connections/events/add/LightAdd.ts | 9 + src/connections/events/add/MotionAdd.ts | 9 + src/connections/events/add/RoomAdd.ts | 9 + src/connections/events/add/SceneAdd.ts | 9 + .../events/add/ZigbeeConnectivityAdd.ts | 9 + .../events/add/ZigbeeDeviceDiscoveryAdd.ts | 9 + src/connections/events/add/ZoneAdd.ts | 9 + src/connections/events/delete/BridgeDelete.ts | 13 + src/connections/events/delete/DeviceDelete.ts | 13 + .../events/delete/DevicePowerDelete.ts | 13 + .../events/delete/GroupedLightDelete.ts | 13 + src/connections/events/delete/LightDelete.ts | 13 + src/connections/events/delete/MotionDelete.ts | 13 + src/connections/events/delete/RoomDelete.ts | 13 + src/connections/events/delete/SceneDelete.ts | 13 + .../events/delete/ZigbeeConnectivityDelete.ts | 13 + .../delete/ZigbeeDeviceDiscoveryDelete.ts | 13 + src/connections/events/delete/ZoneDelete.ts | 13 + src/connections/events/index.ts | 77 +++++ src/connections/events/update/BridgeUpdate.ts | 11 + .../events/update/DevicePowerUpdate.ts | 11 + src/connections/events/update/DeviceUpdate.ts | 11 + .../events/update/GroupedLightUpdate.ts | 11 + src/connections/events/update/LightUpdate.ts | 11 + src/connections/events/update/MotionUpdate.ts | 11 + src/connections/events/update/RoomUpdate.ts | 11 + src/connections/events/update/SceneUpdate.ts | 11 + .../events/update/ZigbeeConnectivityUpdate.ts | 11 + .../update/ZigbeeDeviceDiscoveryUpdate.ts | 11 + src/connections/events/update/ZoneUpdate.ts | 11 + src/hue/Hue.ts | 109 +++++++ .../BridgeEvents.ts => hue/HueEvents.ts} | 49 ++- src/index.ts | 55 ++-- src/managers/BridgeManager.ts | 8 + src/managers/DeviceManager.ts | 23 ++ src/managers/DevicePowerManager.ts | 8 + src/managers/GroupedLightManager.ts | 18 ++ src/managers/LightManager.ts | 33 ++ src/managers/Manager.ts | 48 +++ src/managers/MotionManager.ts | 14 + src/managers/ResourceManager.ts | 169 ---------- src/managers/RoomManager.ts | 29 ++ src/managers/SceneManager.ts | 36 +++ src/managers/ZigbeeConnectivityManager.ts | 8 + src/managers/ZigbeeDeviceDiscoveryManager.ts | 19 ++ src/managers/ZoneManager.ts | 29 ++ src/structures/ArcheTypeResource.ts | 29 ++ src/structures/Bridge.ts | 23 ++ src/structures/Device.ts | 35 +-- src/structures/DevicePower.ts | 28 ++ src/structures/DimmableLight.ts | 32 -- src/structures/GroupedLight.ts | 35 +-- src/structures/Light.ts | 204 +++++++++---- src/structures/MirekLight.ts | 39 --- src/structures/Motion.ts | 39 +++ src/structures/NamedResource.ts | 27 +- src/structures/Resource.ts | 94 +++--- src/structures/Room.ts | 59 ++-- src/structures/Scene.ts | 98 +++--- src/structures/XyLight.ts | 59 ---- src/structures/XysLight.ts | 41 --- src/structures/ZigbeeConnectivity.ts | 30 ++ src/structures/ZigbeeDeviceDiscovery.ts | 36 +++ src/structures/Zone.ts | 56 ++-- src/util/Transformers.ts | 133 ++++++++ src/util/clone.ts | 2 +- src/util/ifNotNull.ts | 3 + src/util/resourceIdentifier.ts | 8 +- src/util/util.ts | 16 - 118 files changed, 2245 insertions(+), 1342 deletions(-) delete mode 100644 src/api/ApiResourceType.ts create mode 100644 src/api/ArcheType.ts create mode 100644 src/api/ResourceType.ts delete mode 100644 src/api/device/put.ts create mode 100644 src/api/get/BridgeGet.ts create mode 100644 src/api/get/BridgeHomeGet.ts rename src/api/{device/get.ts => get/DeviceGet.ts} (61%) create mode 100644 src/api/get/DevicePowerGet.ts rename src/api/{grouped_light/get.ts => get/GroupedLightGet.ts} (63%) rename src/api/{light/get.ts => get/LightGet.ts} (80%) create mode 100644 src/api/get/MotionGet.ts rename src/api/{room/get.ts => get/RoomGet.ts} (52%) rename src/api/{scene/get.ts => get/SceneGet.ts} (87%) create mode 100644 src/api/get/ZigbeeConnectivityGet.ts create mode 100644 src/api/get/ZigbeeDeviceDiscoveryGet.ts rename src/api/{zone/get.ts => get/ZoneGet.ts} (52%) create mode 100644 src/api/post/RoomPost.ts rename src/api/{scene/post.ts => post/ScenePost.ts} (87%) create mode 100644 src/api/post/ZonePost.ts create mode 100644 src/api/put/DevicePut.ts rename src/api/{grouped_light/put.ts => put/GroupedLightPut.ts} (71%) rename src/api/{light/put.ts => put/LightPut.ts} (80%) create mode 100644 src/api/put/MotionPut.ts create mode 100644 src/api/put/RoomPut.ts rename src/api/{scene/put.ts => put/ScenePut.ts} (88%) create mode 100644 src/api/put/ZigbeeDeviceDiscoveryPut.ts create mode 100644 src/api/put/ZonePut.ts delete mode 100644 src/api/room/post.ts delete mode 100644 src/api/room/put.ts delete mode 100644 src/api/zone/post.ts delete mode 100644 src/api/zone/put.ts delete mode 100644 src/bridge/Bridge.ts rename src/{util => }/color/gamut.ts (100%) rename src/{util => }/color/hex.ts (61%) rename src/{util => }/color/rgb.ts (100%) rename src/{util => }/color/xy.ts (100%) create mode 100644 src/connections/events/add/BridgeAdd.ts create mode 100644 src/connections/events/add/DeviceAdd.ts create mode 100644 src/connections/events/add/DevicePowerAdd.ts create mode 100644 src/connections/events/add/GroupedLightAdd.ts create mode 100644 src/connections/events/add/LightAdd.ts create mode 100644 src/connections/events/add/MotionAdd.ts create mode 100644 src/connections/events/add/RoomAdd.ts create mode 100644 src/connections/events/add/SceneAdd.ts create mode 100644 src/connections/events/add/ZigbeeConnectivityAdd.ts create mode 100644 src/connections/events/add/ZigbeeDeviceDiscoveryAdd.ts create mode 100644 src/connections/events/add/ZoneAdd.ts create mode 100644 src/connections/events/delete/BridgeDelete.ts create mode 100644 src/connections/events/delete/DeviceDelete.ts create mode 100644 src/connections/events/delete/DevicePowerDelete.ts create mode 100644 src/connections/events/delete/GroupedLightDelete.ts create mode 100644 src/connections/events/delete/LightDelete.ts create mode 100644 src/connections/events/delete/MotionDelete.ts create mode 100644 src/connections/events/delete/RoomDelete.ts create mode 100644 src/connections/events/delete/SceneDelete.ts create mode 100644 src/connections/events/delete/ZigbeeConnectivityDelete.ts create mode 100644 src/connections/events/delete/ZigbeeDeviceDiscoveryDelete.ts create mode 100644 src/connections/events/delete/ZoneDelete.ts create mode 100644 src/connections/events/index.ts create mode 100644 src/connections/events/update/BridgeUpdate.ts create mode 100644 src/connections/events/update/DevicePowerUpdate.ts create mode 100644 src/connections/events/update/DeviceUpdate.ts create mode 100644 src/connections/events/update/GroupedLightUpdate.ts create mode 100644 src/connections/events/update/LightUpdate.ts create mode 100644 src/connections/events/update/MotionUpdate.ts create mode 100644 src/connections/events/update/RoomUpdate.ts create mode 100644 src/connections/events/update/SceneUpdate.ts create mode 100644 src/connections/events/update/ZigbeeConnectivityUpdate.ts create mode 100644 src/connections/events/update/ZigbeeDeviceDiscoveryUpdate.ts create mode 100644 src/connections/events/update/ZoneUpdate.ts create mode 100644 src/hue/Hue.ts rename src/{bridge/BridgeEvents.ts => hue/HueEvents.ts} (51%) create mode 100644 src/managers/BridgeManager.ts create mode 100644 src/managers/DeviceManager.ts create mode 100644 src/managers/DevicePowerManager.ts create mode 100644 src/managers/GroupedLightManager.ts create mode 100644 src/managers/LightManager.ts create mode 100644 src/managers/Manager.ts create mode 100644 src/managers/MotionManager.ts delete mode 100644 src/managers/ResourceManager.ts create mode 100644 src/managers/RoomManager.ts create mode 100644 src/managers/SceneManager.ts create mode 100644 src/managers/ZigbeeConnectivityManager.ts create mode 100644 src/managers/ZigbeeDeviceDiscoveryManager.ts create mode 100644 src/managers/ZoneManager.ts create mode 100644 src/structures/ArcheTypeResource.ts create mode 100644 src/structures/Bridge.ts create mode 100644 src/structures/DevicePower.ts delete mode 100644 src/structures/DimmableLight.ts delete mode 100644 src/structures/MirekLight.ts create mode 100644 src/structures/Motion.ts delete mode 100644 src/structures/XyLight.ts delete mode 100644 src/structures/XysLight.ts create mode 100644 src/structures/ZigbeeConnectivity.ts create mode 100644 src/structures/ZigbeeDeviceDiscovery.ts create mode 100644 src/util/Transformers.ts create mode 100644 src/util/ifNotNull.ts delete mode 100644 src/util/util.ts diff --git a/.eslintrc.json b/.eslintrc.json index a59fcda..07e05be 100644 --- a/.eslintrc.json +++ b/.eslintrc.json @@ -13,7 +13,9 @@ "prettier" ], "rules": { - "prettier/prettier": "error", + "prettier/prettier": ["error", { + "endOfLine": "auto" + }], "@typescript-eslint/no-explicit-any": "off", "@typescript-eslint/no-namespace": "off", "@typescript-eslint/no-non-null-assertion": "off", diff --git a/.github/workflows/wiki.yml b/.github/workflows/wiki.yml index 9dffbf4..33fd042 100644 --- a/.github/workflows/wiki.yml +++ b/.github/workflows/wiki.yml @@ -15,7 +15,7 @@ jobs: - run: npm ci - run: npm run wiki - name: Push Wiki Changes - uses: Andrew-Chen-Wang/github-wiki-action@v3 + uses: Andrew-Chen-Wang/github-wiki-action@v4 env: WIKI_DIR: wiki/ GH_TOKEN: ${{ secrets.GITHUB_TOKEN }} diff --git a/README.md b/README.md index faa2ca3..91567c2 100644 --- a/README.md +++ b/README.md @@ -4,25 +4,23 @@ [![Issues][issues-shield]][issues-url] [![MIT License][license-shield]][license-url] -**WARNING: this library is still under heavy construction** -
Table of Contents
  1. - About + About
  2. - Example usage + Installation
  3. - Installation + Example usage
  4. - Docs + Colors
  5. - Guides + Other
  6. Roadmap @@ -30,43 +28,15 @@
+> ⚠️ Library is under construction and may not work as expected + # About hue.ts is a node module that allows you to easily interact with the hue API (V2). - Object-oriented - Written in TypeScript -- Future 100%-coverage of the hue API - -# Docs - -Docs can be found on our wiki [here][wiki-url]. - -# Example usage - -```js -const { Bridge, ApiResourceType } = require('hue.ts'); - -const bridge = new Bridge({ - connection: { - ip: 'some-ip', - applicationKey: 'some-key', - }, -}); - -bridge.on('ready', async () => { - console.log('Ready!'); - - const scene = bridge.resources.getByName('Best Scene Ever', { - type: ApiResourceType.Scene, - force: true, - }); - - await scene.recall({ duration: 5000 }); -}); - -bridge.connect(); -``` +- Future 100%-coverage of the hue API (v2) # Installation @@ -77,25 +47,17 @@ the hue app, Settings -> My Hue system -> Select your bridge -> Software npm install hue.ts ``` -# Guides - -## Connecting to a bridge +# Example usage -```ts -import { Bridge } from 'hue.ts'; +This examples goal is to create a new zone, and adding a scene to this zone. -const bridge = new Bridge({ - connection: { - ip: 'some-ip', - applicationKey: 'some-key', - }, -}); +```shell +npm install hue.ts ``` -The **some-ip** can be found in the Hue app at Settings -> My hue system -> Select your bridge -> IP - -You can get **some-key** by following the steps below: - +Before connecting to your hue bridge, its ip address and an application key are required. +The ip address can be found in the Hue app at Settings -> My hue system -> Select your bridge -> IP +After, an application key can be acquired by the following method: 1. Open your browser and go to `https:///debug/clip.html` 2. Next fill in the options below: URL: /api @@ -107,192 +69,120 @@ You can get **some-key** by following the steps below: For more information on retrieving this key visit: https://developers.meethue.com/develop/hue-api-v2/getting-started/ -Finally, connect to your bridge by calling `bridge.connect()`. - -After the bridge has successfully connected and cached all resources the **ready** event is emitted. You can also listen to other events which can be found [here](https://github.com/S222em/hue.ts/wiki/BridgeEvents). - ```ts -import { Bridge, Events } from 'hue.ts'; +import { Hue, ArcheType, SceneAction } from 'hue.ts'; -const bridge = new Bridge({ +// Create new Hue, with the ip address and application key +const hue = new Hue({ connection: { ip: 'some-ip', applicationKey: 'some-key', }, }); -bridge.on(Events.Ready, () => { - // Do stuff +// Listen to the 'ready' event, which is emitted when the socket has connected and cached all resources +hue.on('ready', async () => { + // Get the lights we want in the new zone + const light1 = hue.lights.cache.find((light) => light.name == 'Demo Light 1')!; + const light2 = hue.lights.cache.find((light) => light.name == 'Demo Light 2')!; + + // Create the zone + await hue.zones.create({ + name: 'Demo', + archeType: ArcheType.ManCave, + children: [light1.id, light2.id], + }); }); -bridge.connect(); -``` - -### Connecting via Cloud2Cloud +// Listen to the 'zoneAdd' event, emitted on creation of a new zone +hue.on('zoneAdd', async (zone) => { + // Ignore if the zone is not the one created above + if (zone.name !== 'Demo') return; -> ⚠️ **It is not advised to use Cloud2Cloud at this time** + // Find the lights belonging to the zone again, in this case these will be Demo Light 1 & Demo Light 2 + const lights = hue.lights.cache.filter((light) => zone.childIds.includes(light.id)); -```ts -const bridge = new Bridge({ - connection: { - accessToken: 'some-token', - applicationKey: 'some-key', - }, -}); -``` + // Make the actions + const actions = lights.map((light) => { + const action: SceneAction = { + id: light.id, + on: true, + }; -To find the **accessToken** and **applicationKey** visit https://developers.meethue.com/develop/hue-api-v2/cloud2cloud-getting-started/ + // Check if the light can do dimming, and if so, set the brightness of the light to 50% + if (light.isCapableOfDimming()) action.brightness = 50; -## Resources + // Check if the light can display color, and if so, set the color to #eb403 + if (light.isCapableOfColor()) action.color = fromHex('#eb403'); -All the resources belonging to the connect bridge are cached after calling `bridge.connect()`. -You can access resources in the cache with the following methods. - -Can be used to find a resource by its unique ID. -```ts -const resource = bridge.resources.getById('some-id'); -``` + return action; + }); -Can be used to find a resource by its identifier, similar to an ID, but an identifier also includes the type of the resource. -```ts -const resource = bridge.resources.getByIdentifier({ - rid: 'some-id', - rtype: 'some-type', + // Create the scene + await zone.createScene({ + name: 'Awesome scene', + actions, + }); }); -``` - -Find multiple resources by identifier. -```ts -const resource = bridge.resources.getByIdentifiers([ - { - rid: 'some-id', rtype: 'some-type', - }, - { - rid: 'some-id', rtype: 'some-type', - } -]); -``` -> ⚠️ **.getByName will return the first occurrence** +// Listen to the sceneAdd event, emitted on creation of a new scene +hue.on('sceneAdd', async (scene) => { + // Ignore if not the scene just created above + if (scene.name !== 'Awesome scene') return; -Find a resource by its name. -```ts -const resource = bridge.resources.getByName('some-name'); -``` - -These methods include additional options as second parameter: -- **force**, if true, error is thrown if resource does not exist. -- **type**, the type of the resource, will not return resources of other types. - -Here is an example: -```ts -const resource = bridge.resources.getById('some-id', { - type: ApiResourceType.Light, - force: true, + // Recall the scene + await scene.recall(); }); ``` +# Colors -## Lights +Colors... get more complicated. The hue system uses the C.I.E. color representation. This representation is a 2D-colored diagram. +This also means, to get a color of this diagram, a coordinate (position on the horizontal and vertical axes) is needed. +A color is therefor represented as `{ x: number; y: number }`. Where x is the horizontal placement and y the vertical. -### On/Off +![cie-url] +As this is a sort of 'non-standard' color representation, utility functions are provided to convert a rgb/hex value to C.I.E. ```ts -light.isOn(); +const xy = fromHex('#eb4034'); -await light.off(); -await light.on(); -await light.toggle(); -await light.edit({ - on: true, -}); +await light.setColor(xy); ``` - -### Brightness - ```ts -// Number between 1%-100% -light.brightness - -await light.setBrightness(50); -await light.edit({ - brightness: 50, -}); -``` - -### Color temperature - -```ts -// Number between 153-500 -light.mirek - -await light.setMirek(300); -await light.edit({ - mirek: 300, +const xy = fromRGB({ + red: 235, + green: 64, + blue: 52, }); -``` -### Color - -> ⚠️ **Given color is always transpiled to the closest point in the lights display range** - -```ts -// Number between 153-500 -light.xy - -await light.setXy({ - x: 0.3, - y: 0.5, -}) -await light.edit({ - xy: { - x: 0.3, - y: 0.5, - }, -}); +await light.setColor(xy); ``` - -To convert hex or rgb to a xy value the following utility functions can be used: ```ts -const xy = fromHex('#e62d20'); -const hex = toHex(xy); - -await light.setXy(fromHex('#e62d20')); +const hex = toHex(light.color); ``` ```ts -const xy = fromRGB({ - red: 230, - green: 45, - blue: 32, -}); -const rgb = toRGB(xy); - -await light.setXy(fromRGB({ - red: 230, - green: 45, - blue: 32, -})); +const rgb = toRGB(light.color); ``` -### Type Guards - -Not all lights support color or color temperature. To make sure a light supports these, type guards are available - +As also visible in the C.I.E. above, defined by the triangles, +there is a limit to what the light is able to display. +Because of that, there might be a difference of your input and the color of the light. +In order to calculate the color the light is currently showing, the following method can be used. ```ts -light.isCapableOfDimming() -light.isCapableOfMirek() -light.isCapableOfXy() -light.isCapableOfXys() +const xy = light.colorToRange(light.color); + +const hex = toHex(xy); ``` -## Other resources +# Links -Not all resources are currently supported. Although most important ones are. Guides on these resources will be added later. For now reference to [documentation](https://github.com/S222em/hue.ts/wiki). -For further assistance, feel free to open up an issue on our GitHub. +- [Documentation](documentation-url) +- [GitHub](github-url) +- [npm](npm-url) -# Roadmap +# Help -This library is still a work in progress, and I plan to add support for all API features, currently focusing on the structure of the library itself. The Hue API V2 is currently -not in a finished state yet, so many features **can't** be implemented yet. +For questions or issues, please open an issue on our [github](issues-url) page. [contributors-shield]: https://img.shields.io/github/contributors/S222em/hue.js.svg?style=for-the-badge [contributors-url]: https://github.com/S222em/hue.js/graphs/contributors @@ -304,4 +194,7 @@ not in a finished state yet, so many features **can't** be implemented yet. [issues-url]: https://github.com/S222em/hue.js/issues [license-shield]: https://img.shields.io/github/license/S222em/hue.js.svg?style=for-the-badge [license-url]: https://github.com/S222em/hue.js/blob/master/LICENSE.txt -[wiki-url]: https://github.com/S222em/hue.ts/wiki +[cie-url]: https://developers.meethue.com/wp-content/uploads/2018/02/color.png +[documentation-url]: https://github.com/S222em/hue.ts/wiki/Exports +[github-url]: https://github.com/S222em/hue.ts +[npm-url]: https://www.npmjs.com/package/hue.ts diff --git a/package-lock.json b/package-lock.json index 45a821b..7cf2606 100644 --- a/package-lock.json +++ b/package-lock.json @@ -15,18 +15,18 @@ }, "devDependencies": { "@types/node": "20.1.4", - "@typescript-eslint/eslint-plugin": "5.59.6", - "@typescript-eslint/parser": "5.59.6", - "eslint": "8.40.0", + "@typescript-eslint/eslint-plugin": "5.59.11", + "@typescript-eslint/parser": "5.59.11", + "eslint": "8.43.0", "eslint-config-prettier": "8.8.0", "eslint-plugin-prettier": "4.2.1", "prettier": "2.8.8", "ts-node": "10.9.1", - "tslib": "2.5.0", - "typedoc": "0.24.7", + "tslib": "2.5.3", + "typedoc": "0.24.8", "typedoc-github-wiki-theme": "1.1.0", "typedoc-plugin-markdown": "3.15.3", - "typescript": "5.0.4" + "typescript": "5.1.3" }, "engines": { "node": ">=16.6.0", @@ -101,18 +101,18 @@ } }, "node_modules/@eslint/js": { - "version": "8.40.0", - "resolved": "https://registry.npmjs.org/@eslint/js/-/js-8.40.0.tgz", - "integrity": "sha512-ElyB54bJIhXQYVKjDSvCkPO1iU1tSAeVQJbllWJq1XQSmmA4dgFk8CbiBGpiOPxleE48vDogxCtmMYku4HSVLA==", + "version": "8.43.0", + "resolved": "https://registry.npmjs.org/@eslint/js/-/js-8.43.0.tgz", + "integrity": "sha512-s2UHCoiXfxMvmfzqoN+vrQ84ahUSYde9qNO1MdxmoEhyHWsfmwOpFlwYV+ePJEVc7gFnATGUi376WowX1N7tFg==", "dev": true, "engines": { "node": "^12.22.0 || ^14.17.0 || >=16.0.0" } }, "node_modules/@humanwhocodes/config-array": { - "version": "0.11.8", - "resolved": "https://registry.npmjs.org/@humanwhocodes/config-array/-/config-array-0.11.8.tgz", - "integrity": "sha512-UybHIJzJnR5Qc/MsD9Kr+RpO2h+/P1GhOwdiLPXK5TWk5sgTdu88bTD9UP+CKbPPh5Rni1u0GjAdYQLemG8g+g==", + "version": "0.11.10", + "resolved": "https://registry.npmjs.org/@humanwhocodes/config-array/-/config-array-0.11.10.tgz", + "integrity": "sha512-KVVjQmNUepDVGXNuoRRdmmEjruj0KfiGSbS8LVc12LMsWDQzRXJ0qdhN8L8uUigKpfEHRhlaQFY0ib1tnUbNeQ==", "dev": true, "dependencies": { "@humanwhocodes/object-schema": "^1.2.1", @@ -236,9 +236,9 @@ "dev": true }, "node_modules/@types/json-schema": { - "version": "7.0.11", - "resolved": "https://registry.npmjs.org/@types/json-schema/-/json-schema-7.0.11.tgz", - "integrity": "sha512-wOuvG1SN4Us4rez+tylwwwCV1psiNVOkJeM3AUWUNWg/jDQY2+HE/444y5gc+jBmRqASOm2Oeh5c1axHobwRKQ==", + "version": "7.0.12", + "resolved": "https://registry.npmjs.org/@types/json-schema/-/json-schema-7.0.12.tgz", + "integrity": "sha512-Hr5Jfhc9eYOQNPYO5WLDq/n4jqijdHNlDXjuAQkkt+mWdQR+XJToOHrsD4cPaMXpn6KO7y2+wM8AZEs8VpBLVA==", "dev": true }, "node_modules/@types/node": { @@ -254,15 +254,15 @@ "dev": true }, "node_modules/@typescript-eslint/eslint-plugin": { - "version": "5.59.6", - "resolved": "https://registry.npmjs.org/@typescript-eslint/eslint-plugin/-/eslint-plugin-5.59.6.tgz", - "integrity": "sha512-sXtOgJNEuRU5RLwPUb1jxtToZbgvq3M6FPpY4QENxoOggK+UpTxUBpj6tD8+Qh2g46Pi9We87E+eHnUw8YcGsw==", + "version": "5.59.11", + "resolved": "https://registry.npmjs.org/@typescript-eslint/eslint-plugin/-/eslint-plugin-5.59.11.tgz", + "integrity": "sha512-XxuOfTkCUiOSyBWIvHlUraLw/JT/6Io1365RO6ZuI88STKMavJZPNMU0lFcUTeQXEhHiv64CbxYxBNoDVSmghg==", "dev": true, "dependencies": { "@eslint-community/regexpp": "^4.4.0", - "@typescript-eslint/scope-manager": "5.59.6", - "@typescript-eslint/type-utils": "5.59.6", - "@typescript-eslint/utils": "5.59.6", + "@typescript-eslint/scope-manager": "5.59.11", + "@typescript-eslint/type-utils": "5.59.11", + "@typescript-eslint/utils": "5.59.11", "debug": "^4.3.4", "grapheme-splitter": "^1.0.4", "ignore": "^5.2.0", @@ -288,14 +288,14 @@ } }, "node_modules/@typescript-eslint/parser": { - "version": "5.59.6", - "resolved": "https://registry.npmjs.org/@typescript-eslint/parser/-/parser-5.59.6.tgz", - "integrity": "sha512-7pCa6al03Pv1yf/dUg/s1pXz/yGMUBAw5EeWqNTFiSueKvRNonze3hma3lhdsOrQcaOXhbk5gKu2Fludiho9VA==", + "version": "5.59.11", + "resolved": "https://registry.npmjs.org/@typescript-eslint/parser/-/parser-5.59.11.tgz", + "integrity": "sha512-s9ZF3M+Nym6CAZEkJJeO2TFHHDsKAM3ecNkLuH4i4s8/RCPnF5JRip2GyviYkeEAcwGMJxkqG9h2dAsnA1nZpA==", "dev": true, "dependencies": { - "@typescript-eslint/scope-manager": "5.59.6", - "@typescript-eslint/types": "5.59.6", - "@typescript-eslint/typescript-estree": "5.59.6", + "@typescript-eslint/scope-manager": "5.59.11", + "@typescript-eslint/types": "5.59.11", + "@typescript-eslint/typescript-estree": "5.59.11", "debug": "^4.3.4" }, "engines": { @@ -315,13 +315,13 @@ } }, "node_modules/@typescript-eslint/scope-manager": { - "version": "5.59.6", - "resolved": "https://registry.npmjs.org/@typescript-eslint/scope-manager/-/scope-manager-5.59.6.tgz", - "integrity": "sha512-gLbY3Le9Dxcb8KdpF0+SJr6EQ+hFGYFl6tVY8VxLPFDfUZC7BHFw+Vq7bM5lE9DwWPfx4vMWWTLGXgpc0mAYyQ==", + "version": "5.59.11", + "resolved": "https://registry.npmjs.org/@typescript-eslint/scope-manager/-/scope-manager-5.59.11.tgz", + "integrity": "sha512-dHFOsxoLFtrIcSj5h0QoBT/89hxQONwmn3FOQ0GOQcLOOXm+MIrS8zEAhs4tWl5MraxCY3ZJpaXQQdFMc2Tu+Q==", "dev": true, "dependencies": { - "@typescript-eslint/types": "5.59.6", - "@typescript-eslint/visitor-keys": "5.59.6" + "@typescript-eslint/types": "5.59.11", + "@typescript-eslint/visitor-keys": "5.59.11" }, "engines": { "node": "^12.22.0 || ^14.17.0 || >=16.0.0" @@ -332,13 +332,13 @@ } }, "node_modules/@typescript-eslint/type-utils": { - "version": "5.59.6", - "resolved": "https://registry.npmjs.org/@typescript-eslint/type-utils/-/type-utils-5.59.6.tgz", - "integrity": "sha512-A4tms2Mp5yNvLDlySF+kAThV9VTBPCvGf0Rp8nl/eoDX9Okun8byTKoj3fJ52IJitjWOk0fKPNQhXEB++eNozQ==", + "version": "5.59.11", + "resolved": "https://registry.npmjs.org/@typescript-eslint/type-utils/-/type-utils-5.59.11.tgz", + "integrity": "sha512-LZqVY8hMiVRF2a7/swmkStMYSoXMFlzL6sXV6U/2gL5cwnLWQgLEG8tjWPpaE4rMIdZ6VKWwcffPlo1jPfk43g==", "dev": true, "dependencies": { - "@typescript-eslint/typescript-estree": "5.59.6", - "@typescript-eslint/utils": "5.59.6", + "@typescript-eslint/typescript-estree": "5.59.11", + "@typescript-eslint/utils": "5.59.11", "debug": "^4.3.4", "tsutils": "^3.21.0" }, @@ -359,9 +359,9 @@ } }, "node_modules/@typescript-eslint/types": { - "version": "5.59.6", - "resolved": "https://registry.npmjs.org/@typescript-eslint/types/-/types-5.59.6.tgz", - "integrity": "sha512-tH5lBXZI7T2MOUgOWFdVNUILsI02shyQvfzG9EJkoONWugCG77NDDa1EeDGw7oJ5IvsTAAGVV8I3Tk2PNu9QfA==", + "version": "5.59.11", + "resolved": "https://registry.npmjs.org/@typescript-eslint/types/-/types-5.59.11.tgz", + "integrity": "sha512-epoN6R6tkvBYSc+cllrz+c2sOFWkbisJZWkOE+y3xHtvYaOE6Wk6B8e114McRJwFRjGvYdJwLXQH5c9osME/AA==", "dev": true, "engines": { "node": "^12.22.0 || ^14.17.0 || >=16.0.0" @@ -372,13 +372,13 @@ } }, "node_modules/@typescript-eslint/typescript-estree": { - "version": "5.59.6", - "resolved": "https://registry.npmjs.org/@typescript-eslint/typescript-estree/-/typescript-estree-5.59.6.tgz", - "integrity": "sha512-vW6JP3lMAs/Tq4KjdI/RiHaaJSO7IUsbkz17it/Rl9Q+WkQ77EOuOnlbaU8kKfVIOJxMhnRiBG+olE7f3M16DA==", + "version": "5.59.11", + "resolved": "https://registry.npmjs.org/@typescript-eslint/typescript-estree/-/typescript-estree-5.59.11.tgz", + "integrity": "sha512-YupOpot5hJO0maupJXixi6l5ETdrITxeo5eBOeuV7RSKgYdU3G5cxO49/9WRnJq9EMrB7AuTSLH/bqOsXi7wPA==", "dev": true, "dependencies": { - "@typescript-eslint/types": "5.59.6", - "@typescript-eslint/visitor-keys": "5.59.6", + "@typescript-eslint/types": "5.59.11", + "@typescript-eslint/visitor-keys": "5.59.11", "debug": "^4.3.4", "globby": "^11.1.0", "is-glob": "^4.0.3", @@ -399,17 +399,17 @@ } }, "node_modules/@typescript-eslint/utils": { - "version": "5.59.6", - "resolved": "https://registry.npmjs.org/@typescript-eslint/utils/-/utils-5.59.6.tgz", - "integrity": "sha512-vzaaD6EXbTS29cVH0JjXBdzMt6VBlv+hE31XktDRMX1j3462wZCJa7VzO2AxXEXcIl8GQqZPcOPuW/Z1tZVogg==", + "version": "5.59.11", + "resolved": "https://registry.npmjs.org/@typescript-eslint/utils/-/utils-5.59.11.tgz", + "integrity": "sha512-didu2rHSOMUdJThLk4aZ1Or8IcO3HzCw/ZvEjTTIfjIrcdd5cvSIwwDy2AOlE7htSNp7QIZ10fLMyRCveesMLg==", "dev": true, "dependencies": { "@eslint-community/eslint-utils": "^4.2.0", "@types/json-schema": "^7.0.9", "@types/semver": "^7.3.12", - "@typescript-eslint/scope-manager": "5.59.6", - "@typescript-eslint/types": "5.59.6", - "@typescript-eslint/typescript-estree": "5.59.6", + "@typescript-eslint/scope-manager": "5.59.11", + "@typescript-eslint/types": "5.59.11", + "@typescript-eslint/typescript-estree": "5.59.11", "eslint-scope": "^5.1.1", "semver": "^7.3.7" }, @@ -425,12 +425,12 @@ } }, "node_modules/@typescript-eslint/visitor-keys": { - "version": "5.59.6", - "resolved": "https://registry.npmjs.org/@typescript-eslint/visitor-keys/-/visitor-keys-5.59.6.tgz", - "integrity": "sha512-zEfbFLzB9ETcEJ4HZEEsCR9HHeNku5/Qw1jSS5McYJv5BR+ftYXwFFAH5Al+xkGaZEqowMwl7uoJjQb1YSPF8Q==", + "version": "5.59.11", + "resolved": "https://registry.npmjs.org/@typescript-eslint/visitor-keys/-/visitor-keys-5.59.11.tgz", + "integrity": "sha512-KGYniTGG3AMTuKF9QBD7EIrvufkB6O6uX3knP73xbKLMpH+QRPcgnCxjWXSHjMRuOxFLovljqQgQpR0c7GvjoA==", "dev": true, "dependencies": { - "@typescript-eslint/types": "5.59.6", + "@typescript-eslint/types": "5.59.11", "eslint-visitor-keys": "^3.3.0" }, "engines": { @@ -715,16 +715,16 @@ } }, "node_modules/eslint": { - "version": "8.40.0", - "resolved": "https://registry.npmjs.org/eslint/-/eslint-8.40.0.tgz", - "integrity": "sha512-bvR+TsP9EHL3TqNtj9sCNJVAFK3fBN8Q7g5waghxyRsPLIMwL73XSKnZFK0hk/O2ANC+iAoq6PWMQ+IfBAJIiQ==", + "version": "8.43.0", + "resolved": "https://registry.npmjs.org/eslint/-/eslint-8.43.0.tgz", + "integrity": "sha512-aaCpf2JqqKesMFGgmRPessmVKjcGXqdlAYLLC3THM8t5nBRZRQ+st5WM/hoJXkdioEXLLbXgclUpM0TXo5HX5Q==", "dev": true, "dependencies": { "@eslint-community/eslint-utils": "^4.2.0", "@eslint-community/regexpp": "^4.4.0", "@eslint/eslintrc": "^2.0.3", - "@eslint/js": "8.40.0", - "@humanwhocodes/config-array": "^0.11.8", + "@eslint/js": "8.43.0", + "@humanwhocodes/config-array": "^0.11.10", "@humanwhocodes/module-importer": "^1.0.1", "@nodelib/fs.walk": "^1.2.8", "ajv": "^6.10.0", @@ -743,13 +743,12 @@ "find-up": "^5.0.0", "glob-parent": "^6.0.2", "globals": "^13.19.0", - "grapheme-splitter": "^1.0.4", + "graphemer": "^1.4.0", "ignore": "^5.2.0", "import-fresh": "^3.0.0", "imurmurhash": "^0.1.4", "is-glob": "^4.0.0", "is-path-inside": "^3.0.3", - "js-sdsl": "^4.1.4", "js-yaml": "^4.1.0", "json-stable-stringify-without-jsonify": "^1.0.1", "levn": "^0.4.1", @@ -1130,6 +1129,12 @@ "integrity": "sha512-bzh50DW9kTPM00T8y4o8vQg89Di9oLJVLW/KaOGIXJWP/iqCN6WKYkbNOF04vFLJhwcpYUh9ydh/+5vpOqV4YQ==", "dev": true }, + "node_modules/graphemer": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/graphemer/-/graphemer-1.4.0.tgz", + "integrity": "sha512-EtKwoO6kxCL9WO5xipiHTZlSzBm7WLT627TqC/uVRd0HKmq8NXyebnNYxDoBi7wt8eTWrUrKXCOVaFq9x1kgag==", + "dev": true + }, "node_modules/handlebars": { "version": "4.7.7", "resolved": "https://registry.npmjs.org/handlebars/-/handlebars-4.7.7.tgz", @@ -1255,16 +1260,6 @@ "integrity": "sha1-6PvzdNxVb/iUehDcsFctYz8s+hA=", "dev": true }, - "node_modules/js-sdsl": { - "version": "4.4.0", - "resolved": "https://registry.npmjs.org/js-sdsl/-/js-sdsl-4.4.0.tgz", - "integrity": "sha512-FfVSdx6pJ41Oa+CF7RDaFmTnCaFhua+SNYQX74riGOpl96x+2jQCqEfQ2bnXu/5DPCqlRuiqyvTJM0Qjz26IVg==", - "dev": true, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/js-sdsl" - } - }, "node_modules/js-yaml": { "version": "4.1.0", "resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-4.1.0.tgz", @@ -1842,9 +1837,9 @@ } }, "node_modules/tslib": { - "version": "2.5.0", - "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.5.0.tgz", - "integrity": "sha512-336iVw3rtn2BUK7ORdIAHTyxHGRIHVReokCR3XjbckJMK7ms8FysBfhLR8IXnAgy7T0PTPNBWKiH514FOW/WSg==", + "version": "2.5.3", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.5.3.tgz", + "integrity": "sha512-mSxlJJwl3BMEQCUNnxXBU9jP4JBktcEGhURcPR6VQVlnP0FdDEsIaz0C35dXNGLyRfrATNofF0F5p2KPxQgB+w==", "dev": true }, "node_modules/tsutils": { @@ -1893,9 +1888,9 @@ } }, "node_modules/typedoc": { - "version": "0.24.7", - "resolved": "https://registry.npmjs.org/typedoc/-/typedoc-0.24.7.tgz", - "integrity": "sha512-zzfKDFIZADA+XRIp2rMzLe9xZ6pt12yQOhCr7cD7/PBTjhPmMyMvGrkZ2lPNJitg3Hj1SeiYFNzCsSDrlpxpKw==", + "version": "0.24.8", + "resolved": "https://registry.npmjs.org/typedoc/-/typedoc-0.24.8.tgz", + "integrity": "sha512-ahJ6Cpcvxwaxfu4KtjA8qZNqS43wYt6JL27wYiIgl1vd38WW/KWX11YuAeZhuz9v+ttrutSsgK+XO1CjL1kA3w==", "dev": true, "dependencies": { "lunr": "^2.3.9", @@ -1910,7 +1905,7 @@ "node": ">= 14.14" }, "peerDependencies": { - "typescript": "4.6.x || 4.7.x || 4.8.x || 4.9.x || 5.0.x" + "typescript": "4.6.x || 4.7.x || 4.8.x || 4.9.x || 5.0.x || 5.1.x" } }, "node_modules/typedoc-github-wiki-theme": { @@ -1945,9 +1940,9 @@ } }, "node_modules/typedoc/node_modules/minimatch": { - "version": "9.0.0", - "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-9.0.0.tgz", - "integrity": "sha512-0jJj8AvgKqWN05mrwuqi8QYKx1WmYSUoKSxu5Qhs9prezTz10sxAHGNZe9J9cqIJzta8DWsleh2KaVaLl6Ru2w==", + "version": "9.0.1", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-9.0.1.tgz", + "integrity": "sha512-0jWhJpD/MdhPXwPuiRkCbfYfSKp2qnn2eOc279qI7f+osl/l+prKSrvhg157zSYvx/1nmgn2NqdT6k2Z7zSH9w==", "dev": true, "dependencies": { "brace-expansion": "^2.0.1" @@ -1960,16 +1955,16 @@ } }, "node_modules/typescript": { - "version": "5.0.4", - "resolved": "https://registry.npmjs.org/typescript/-/typescript-5.0.4.tgz", - "integrity": "sha512-cW9T5W9xY37cc+jfEnaUvX91foxtHkza3Nw3wkoF4sSlKn0MONdkdEndig/qPBWXNkmplh3NzayQzCiHM4/hqw==", + "version": "5.1.3", + "resolved": "https://registry.npmjs.org/typescript/-/typescript-5.1.3.tgz", + "integrity": "sha512-XH627E9vkeqhlZFQuL+UsyAXEnibT0kWR2FWONlr4sTjvxyJYnyefgrkyECLzM5NenmKzRAy2rR/OlYLA1HkZw==", "dev": true, "bin": { "tsc": "bin/tsc", "tsserver": "bin/tsserver" }, "engines": { - "node": ">=12.20" + "node": ">=14.17" } }, "node_modules/uglify-js": { diff --git a/package.json b/package.json index 74d49d5..5eaa5cb 100644 --- a/package.json +++ b/package.json @@ -1,11 +1,11 @@ { "name": "hue.ts", - "version": "0.5.0", + "version": "0.6.0", "description": "A powerful library to interact with the Hue API", "main": "dist/index.js", "types": "dist/index.d.ts", "repository": "https://github.com/S222em/hue.ts", - "homepage": "https://github.com/S222em/hue.ts/wiki", + "homepage": "https://github.com/S222em/hue.ts#readme", "bugs": { "url": "https://github.com/S222em/hue.ts/issues" }, @@ -20,7 +20,7 @@ "Hue" ], "author": "S222em", - "license": "ISC", + "license": "MIT", "dependencies": { "@discordjs/collection": "1.5.1", "@sapphire/async-queue": "1.5.0", @@ -28,18 +28,18 @@ }, "devDependencies": { "@types/node": "20.1.4", - "@typescript-eslint/eslint-plugin": "5.59.6", - "@typescript-eslint/parser": "5.59.6", - "eslint": "8.40.0", + "@typescript-eslint/eslint-plugin": "5.59.11", + "@typescript-eslint/parser": "5.59.11", + "eslint": "8.43.0", "eslint-config-prettier": "8.8.0", "eslint-plugin-prettier": "4.2.1", "prettier": "2.8.8", "ts-node": "10.9.1", - "tslib": "2.5.0", - "typedoc": "0.24.7", + "tslib": "2.5.3", + "typedoc": "0.24.8", "typedoc-github-wiki-theme": "1.1.0", "typedoc-plugin-markdown": "3.15.3", - "typescript": "5.0.4" + "typescript": "5.1.3" }, "engines": { "node": ">=16.6.0", diff --git a/src/api/ApiResourceType.ts b/src/api/ApiResourceType.ts deleted file mode 100644 index 7428eff..0000000 --- a/src/api/ApiResourceType.ts +++ /dev/null @@ -1,129 +0,0 @@ -import { ApiLightGet } from './light/get'; -import { ApiLightPut } from './light/put'; -import { ApiSceneGet } from './scene/get'; -import { ApiScenePut } from './scene/put'; -import { ApiScenePost } from './scene/post'; -import { ApiRoomPost } from './room/post'; -import { ApiZonePost } from './zone/post'; -import { ApiRoomPut } from './room/put'; -import { ApiZonePut } from './zone/put'; -import { ApiRoomGet } from './room/get'; -import { ApiZoneGet } from './zone/get'; -import { ApiDeviceGet } from './device/get'; -import { ApiDevicePut } from './device/put'; -import { ApiGroupedLightGet } from './grouped_light/get'; -import { ApiGroupedLightPut } from './grouped_light/put'; - -export enum ApiResourceType { - Device = 'device', - BridgeHome = 'bridge_home', - Room = 'room', - Zone = 'zone', - Light = 'light', - Button = 'button', - Temperature = 'temperature', - LightLevel = 'light_level', - Motion = 'motion', - Entertainment = 'entertainment', - GroupedLight = 'grouped_light', - DevicePower = 'device_power', - ZigbeeBridgeConnectivity = 'zigbee_bridge_connectivity', - ZgpConnectivity = 'zgp_connectivity', - Bridge = 'bridge', - Homekit = 'homekit', - Scene = 'scene', - EntertainmentConfiguration = 'entertainment_configuration', - PublicImage = 'public_image', - BehaviourScript = 'behaviour_script', - BehaviourInstance = 'behaviour_instance', - Geofence = 'geofence', - GeofenceClient = 'geofence_client', - Geolocation = 'geolocation', -} - -export interface ApiResourceTypesGet { - [ApiResourceType.Device]: ApiDeviceGet; - [ApiResourceType.BridgeHome]: never; - [ApiResourceType.Room]: ApiRoomGet; - [ApiResourceType.Zone]: ApiZoneGet; - [ApiResourceType.Light]: ApiLightGet; - [ApiResourceType.Button]: never; - [ApiResourceType.Temperature]: never; - [ApiResourceType.LightLevel]: never; - [ApiResourceType.Motion]: never; - [ApiResourceType.Entertainment]: never; - [ApiResourceType.GroupedLight]: ApiGroupedLightGet; - [ApiResourceType.DevicePower]: never; - [ApiResourceType.ZigbeeBridgeConnectivity]: never; - [ApiResourceType.ZgpConnectivity]: never; - [ApiResourceType.Bridge]: never; - [ApiResourceType.Homekit]: never; - [ApiResourceType.Scene]: ApiSceneGet; - [ApiResourceType.EntertainmentConfiguration]: never; - [ApiResourceType.PublicImage]: never; - [ApiResourceType.BehaviourScript]: never; - [ApiResourceType.BehaviourInstance]: never; - [ApiResourceType.Geofence]: never; - [ApiResourceType.GeofenceClient]: never; - [ApiResourceType.Geolocation]: never; -} - -export type ApiResourceTypeGet = ApiResourceTypesGet[T]; - -export interface ApiResourceTypesPut { - [ApiResourceType.Device]: ApiDevicePut; - [ApiResourceType.BridgeHome]: never; - [ApiResourceType.Room]: ApiRoomPut; - [ApiResourceType.Zone]: ApiZonePut; - [ApiResourceType.Light]: ApiLightPut; - [ApiResourceType.Button]: never; - [ApiResourceType.Temperature]: never; - [ApiResourceType.LightLevel]: never; - [ApiResourceType.Motion]: never; - [ApiResourceType.Entertainment]: never; - [ApiResourceType.GroupedLight]: ApiGroupedLightPut; - [ApiResourceType.DevicePower]: never; - [ApiResourceType.ZigbeeBridgeConnectivity]: never; - [ApiResourceType.ZgpConnectivity]: never; - [ApiResourceType.Bridge]: never; - [ApiResourceType.Homekit]: never; - [ApiResourceType.Scene]: ApiScenePut; - [ApiResourceType.EntertainmentConfiguration]: never; - [ApiResourceType.PublicImage]: never; - [ApiResourceType.BehaviourScript]: never; - [ApiResourceType.BehaviourInstance]: never; - [ApiResourceType.Geofence]: never; - [ApiResourceType.GeofenceClient]: never; - [ApiResourceType.Geolocation]: never; -} - -export type ApiResourceTypePut = ApiResourceTypesPut[T]; - -export interface ApiResourceTypesPost { - [ApiResourceType.Device]: never; - [ApiResourceType.BridgeHome]: never; - [ApiResourceType.Room]: ApiRoomPost; - [ApiResourceType.Zone]: ApiZonePost; - [ApiResourceType.Light]: never; - [ApiResourceType.Button]: never; - [ApiResourceType.Temperature]: never; - [ApiResourceType.LightLevel]: never; - [ApiResourceType.Motion]: never; - [ApiResourceType.Entertainment]: never; - [ApiResourceType.GroupedLight]: never; - [ApiResourceType.DevicePower]: never; - [ApiResourceType.ZigbeeBridgeConnectivity]: never; - [ApiResourceType.ZgpConnectivity]: never; - [ApiResourceType.Bridge]: never; - [ApiResourceType.Homekit]: never; - [ApiResourceType.Scene]: ApiScenePost; - [ApiResourceType.EntertainmentConfiguration]: never; - [ApiResourceType.PublicImage]: never; - [ApiResourceType.BehaviourScript]: never; - [ApiResourceType.BehaviourInstance]: never; - [ApiResourceType.Geofence]: never; - [ApiResourceType.GeofenceClient]: never; - [ApiResourceType.Geolocation]: never; -} - -export type ApiResourceTypePost = ApiResourceTypesPost[T]; diff --git a/src/api/ArcheType.ts b/src/api/ArcheType.ts new file mode 100644 index 0000000..cc686ba --- /dev/null +++ b/src/api/ArcheType.ts @@ -0,0 +1,91 @@ +export enum ArcheType { + Unknown = 'unknown_archetype', + ClassicBulb = 'classic_bulb', + SultanBulb = 'sultan_bulb', + FloodBulb = 'flood_bulb', + SpotBulb = 'spot_bulb', + CandleBulb = 'candle_bulb', + LusterBulb = 'luster_bulb', + PendantRound = 'pendant_round', + PendantLong = 'pendant_long', + CeilingRound = 'ceiling_round', + CeilingSquare = 'ceiling_square', + FloorShade = 'floor_shade', + FloorLantern = 'floor_lantern', + TableShade = 'table_shade', + RecessedCeiling = 'recessed_ceiling', + RecessedFloor = 'recessed_floor', + SingleSpot = 'single_spot', + DoubleSpot = 'double_spot', + TableWash = 'table_wash', + WallLantern = 'wall_lantern', + WallShade = 'wall_shade', + FlexibleLamp = 'flexible_lamp', + GroundSpot = 'ground_spot', + WallSpot = 'wall_spot', + Plug = 'plug', + HueGo = 'hue_go', + HueLightstrip = 'hue_lightstrip', + HueIris = 'hue_iris', + HueBloom = 'hue_bloom', + Bollard = 'bollard', + WallWasher = 'wall_washer', + HuePlay = 'hue_play', + VintageBulb = 'vintage_bulb', + VintageCandleBulb = 'vintage_candle_bulb', + EllipseBulb = 'ellipse_bulb', + TriangleBulb = 'triangle_bulb', + SmallGlobeBulb = 'small_globe_bulb', + LargeGlobeBulb = 'large_globe_bulb', + EdisonBulb = 'edison_bulb', + ChristmasTree = 'christmas_tree', + StringLight = 'string_light', + HueCentris = 'hue_centris', + HueLightstripTV = 'hue_lightstrip_tv', + HueLightStripPC = 'hue_lightstrip_pc', + HueTube = 'hue_tube', + HueSigne = 'hue_signe', + PendantSpot = 'pendant_spot', + CeilingHorizontal = 'ceiling_horizontal', + CeilingTube = 'ceiling_tube', + LivingRoom = 'living_room', + Kitchen = 'kitchen', + Dining = 'dining', + Bedroom = 'bedroom', + KidsBedroom = 'kids_bedroom', + Bathroom = 'bathroom', + Nursery = 'nursery', + Recreation = 'recreation', + Office = 'office', + Gym = 'gym', + Hallway = 'hallway', + Toilet = 'toilet', + FrontDoor = 'front_door', + Garage = 'garage', + Terrace = 'terrace', + Garden = 'garden', + Driveway = 'driveway', + Carport = 'carport', + Home = 'home', + Downstairs = 'downstairs', + Upstairs = 'upstairs', + TopFloor = 'top_floor', + Attic = 'attic', + GuestRoom = 'guest_room', + Staircase = 'staircase', + Lounge = 'lounge', + ManCave = 'man_cave', + Computer = 'computer', + Studio = 'studio', + Music = 'music', + TV = 'tv', + Reading = 'reading', + Closet = 'closet', + Storage = 'storage', + LaundryRoom = 'laundry_room', + Balcony = 'balcony', + Porch = 'porch', + Barbecue = 'barbecue', + Pool = 'pool', + Other = 'other', +} diff --git a/src/api/ResourceIdentifier.ts b/src/api/ResourceIdentifier.ts index 6753c0d..6374d7c 100644 --- a/src/api/ResourceIdentifier.ts +++ b/src/api/ResourceIdentifier.ts @@ -1,6 +1,6 @@ -import { ApiResourceType } from './ApiResourceType'; +import { ResourceType } from './ResourceType'; -export interface ResourceIdentifier { +export interface ResourceIdentifier { rid: string; rtype: T; } diff --git a/src/api/ResourceType.ts b/src/api/ResourceType.ts new file mode 100644 index 0000000..f71fa0a --- /dev/null +++ b/src/api/ResourceType.ts @@ -0,0 +1,141 @@ +import { DeviceGet } from './get/DeviceGet'; +import { BridgeHomeGet } from './get/BridgeHomeGet'; +import { RoomGet } from './get/RoomGet'; +import { ZoneGet } from './get/ZoneGet'; +import { LightGet } from './get/LightGet'; +import { MotionGet } from './get/MotionGet'; +import { GroupedLightGet } from './get/GroupedLightGet'; +import { DevicePowerGet } from './get/DevicePowerGet'; +import { SceneGet } from './get/SceneGet'; +import { DevicePut } from './put/DevicePut'; +import { RoomPut } from './put/RoomPut'; +import { ZonePut } from './put/ZonePut'; +import { LightPut } from './put/LightPut'; +import { MotionPut } from './put/MotionPut'; +import { GroupedLightPut } from './put/GroupedLightPut'; +import { ScenePut } from './put/ScenePut'; +import { RoomPost } from './post/RoomPost'; +import { ZonePost } from './post/ZonePost'; +import { ScenePost } from './post/ScenePost'; +import { ZigbeeConnectivityGet } from './get/ZigbeeConnectivityGet'; +import { ZigbeeDeviceDiscoveryGet } from './get/ZigbeeDeviceDiscoveryGet'; +import { ZigbeeDeviceDiscoveryPut } from './put/ZigbeeDeviceDiscoveryPut'; +import { BridgeGet } from './get/BridgeGet'; + +export enum ResourceType { + Device = 'device', + BridgeHome = 'bridge_home', + Room = 'room', + Zone = 'zone', + Light = 'light', + Button = 'button', + Temperature = 'temperature', + LightLevel = 'light_level', + Motion = 'motion', + Entertainment = 'entertainment', + GroupedLight = 'grouped_light', + DevicePower = 'device_power', + ZigbeeConnectivity = 'zigbee_connectivity', + ZgpConnectivity = 'zgp_connectivity', + ZigbeeDeviceDiscovery = 'zigbee_device_discovery', + Bridge = 'bridge', + Homekit = 'homekit', + Scene = 'scene', + EntertainmentConfiguration = 'entertainment_configuration', + PublicImage = 'public_image', + BehaviourScript = 'behaviour_script', + BehaviourInstance = 'behaviour_instance', + Geofence = 'geofence', + GeofenceClient = 'geofence_client', + Geolocation = 'geolocation', +} + +export interface ResourceTypesGet { + [ResourceType.Device]: DeviceGet; + [ResourceType.BridgeHome]: BridgeHomeGet; + [ResourceType.Room]: RoomGet; + [ResourceType.Zone]: ZoneGet; + [ResourceType.Light]: LightGet; + [ResourceType.Button]: never; + [ResourceType.Temperature]: never; + [ResourceType.LightLevel]: never; + [ResourceType.Motion]: MotionGet; + [ResourceType.Entertainment]: never; + [ResourceType.GroupedLight]: GroupedLightGet; + [ResourceType.DevicePower]: DevicePowerGet; + [ResourceType.ZigbeeConnectivity]: ZigbeeConnectivityGet; + [ResourceType.ZgpConnectivity]: never; + [ResourceType.ZigbeeDeviceDiscovery]: ZigbeeDeviceDiscoveryGet; + [ResourceType.Bridge]: BridgeGet; + [ResourceType.Homekit]: never; + [ResourceType.Scene]: SceneGet; + [ResourceType.EntertainmentConfiguration]: never; + [ResourceType.PublicImage]: never; + [ResourceType.BehaviourScript]: never; + [ResourceType.BehaviourInstance]: never; + [ResourceType.Geofence]: never; + [ResourceType.GeofenceClient]: never; + [ResourceType.Geolocation]: never; +} + +export type ResourceTypeGet = ResourceTypesGet[T]; + +export interface ResourceTypesPut { + [ResourceType.Device]: DevicePut; + [ResourceType.BridgeHome]: never; + [ResourceType.Room]: RoomPut; + [ResourceType.Zone]: ZonePut; + [ResourceType.Light]: LightPut; + [ResourceType.Button]: never; + [ResourceType.Temperature]: never; + [ResourceType.LightLevel]: never; + [ResourceType.Motion]: MotionPut; + [ResourceType.Entertainment]: never; + [ResourceType.GroupedLight]: GroupedLightPut; + [ResourceType.DevicePower]: never; + [ResourceType.ZigbeeConnectivity]: never; + [ResourceType.ZgpConnectivity]: never; + [ResourceType.ZigbeeDeviceDiscovery]: ZigbeeDeviceDiscoveryPut; + [ResourceType.Bridge]: never; + [ResourceType.Homekit]: never; + [ResourceType.Scene]: ScenePut; + [ResourceType.EntertainmentConfiguration]: never; + [ResourceType.PublicImage]: never; + [ResourceType.BehaviourScript]: never; + [ResourceType.BehaviourInstance]: never; + [ResourceType.Geofence]: never; + [ResourceType.GeofenceClient]: never; + [ResourceType.Geolocation]: never; +} + +export type ResourceTypePut = ResourceTypesPut[T]; + +export interface ResourceTypesPost { + [ResourceType.Device]: never; + [ResourceType.BridgeHome]: never; + [ResourceType.Room]: RoomPost; + [ResourceType.Zone]: ZonePost; + [ResourceType.Light]: never; + [ResourceType.Button]: never; + [ResourceType.Temperature]: never; + [ResourceType.LightLevel]: never; + [ResourceType.Motion]: never; + [ResourceType.Entertainment]: never; + [ResourceType.GroupedLight]: never; + [ResourceType.DevicePower]: never; + [ResourceType.ZigbeeConnectivity]: never; + [ResourceType.ZgpConnectivity]: never; + [ResourceType.ZigbeeDeviceDiscovery]: never; + [ResourceType.Bridge]: never; + [ResourceType.Homekit]: never; + [ResourceType.Scene]: ScenePost; + [ResourceType.EntertainmentConfiguration]: never; + [ResourceType.PublicImage]: never; + [ResourceType.BehaviourScript]: never; + [ResourceType.BehaviourInstance]: never; + [ResourceType.Geofence]: never; + [ResourceType.GeofenceClient]: never; + [ResourceType.Geolocation]: never; +} + +export type ResourceTypePost = ResourceTypesPost[T]; diff --git a/src/api/device/put.ts b/src/api/device/put.ts deleted file mode 100644 index f41bc5f..0000000 --- a/src/api/device/put.ts +++ /dev/null @@ -1,12 +0,0 @@ -import { ApiResourceType } from '../ApiResourceType'; - -export interface ApiDevicePut { - type?: ApiResourceType.Device; - metadata?: { - archetype?: string; - name?: string; - }; - identify?: { - action: 'identify'; - }; -} diff --git a/src/api/get/BridgeGet.ts b/src/api/get/BridgeGet.ts new file mode 100644 index 0000000..85bd6b1 --- /dev/null +++ b/src/api/get/BridgeGet.ts @@ -0,0 +1,12 @@ +import { ResourceType } from '../ResourceType'; +import { ResourceIdentifier } from '../ResourceIdentifier'; + +export interface BridgeGet { + type?: ResourceType.Bridge; + id: string; + owner: ResourceIdentifier; + bridge_id: string; + time_zone: { + time_zone: string; + }; +} diff --git a/src/api/get/BridgeHomeGet.ts b/src/api/get/BridgeHomeGet.ts new file mode 100644 index 0000000..5ab2988 --- /dev/null +++ b/src/api/get/BridgeHomeGet.ts @@ -0,0 +1,9 @@ +import { ResourceType } from '../ResourceType'; +import { ResourceIdentifier } from '../ResourceIdentifier'; + +export interface BridgeHomeGet { + type?: ResourceType.BridgeHome; + id: string; + children: ResourceIdentifier[]; + services: ResourceIdentifier[]; +} diff --git a/src/api/device/get.ts b/src/api/get/DeviceGet.ts similarity index 61% rename from src/api/device/get.ts rename to src/api/get/DeviceGet.ts index 914f735..e9a72cd 100644 --- a/src/api/device/get.ts +++ b/src/api/get/DeviceGet.ts @@ -1,20 +1,21 @@ -import { ApiResourceType } from '../ApiResourceType'; +import { ResourceType } from '../ResourceType'; import { ResourceIdentifier } from '../ResourceIdentifier'; +import { ArcheType } from '../ArcheType'; -export interface ApiDeviceGet { - type?: ApiResourceType.Device; +export interface DeviceGet { + type?: ResourceType.Device; id: string; product_data: { model_id: string; manufacturer_name: string; product_name: string; - product_archetype: string; + product_archetype: ArcheType; certified: boolean; software_version: string; hardware_platform_type?: string; }; metadata: { - archetype: string; + archetype: ArcheType; name: string; }; services: ResourceIdentifier[]; diff --git a/src/api/get/DevicePowerGet.ts b/src/api/get/DevicePowerGet.ts new file mode 100644 index 0000000..c65d374 --- /dev/null +++ b/src/api/get/DevicePowerGet.ts @@ -0,0 +1,12 @@ +import { ResourceType } from '../ResourceType'; +import { ResourceIdentifier } from '../ResourceIdentifier'; + +export interface DevicePowerGet { + type?: ResourceType.DevicePower; + id: string; + owner: ResourceIdentifier; + power_state: { + battery_state: 'normal' | 'low' | 'critical'; + battery_level: number; + }; +} diff --git a/src/api/grouped_light/get.ts b/src/api/get/GroupedLightGet.ts similarity index 63% rename from src/api/grouped_light/get.ts rename to src/api/get/GroupedLightGet.ts index a062454..e20df0b 100644 --- a/src/api/grouped_light/get.ts +++ b/src/api/get/GroupedLightGet.ts @@ -1,8 +1,8 @@ -import { ApiResourceType } from '../ApiResourceType'; +import { ResourceType } from '../ResourceType'; import { ResourceIdentifier } from '../ResourceIdentifier'; -export interface ApiGroupedLightGet { - type?: ApiResourceType.GroupedLight; +export interface GroupedLightGet { + type?: ResourceType.GroupedLight; id: string; owner: ResourceIdentifier; on?: { on: boolean }; diff --git a/src/api/light/get.ts b/src/api/get/LightGet.ts similarity index 80% rename from src/api/light/get.ts rename to src/api/get/LightGet.ts index 054caa1..63174b4 100644 --- a/src/api/light/get.ts +++ b/src/api/get/LightGet.ts @@ -1,12 +1,13 @@ -import { ApiResourceType } from '../ApiResourceType'; +import { ResourceType } from '../ResourceType'; import { ResourceIdentifier } from '../ResourceIdentifier'; +import { ArcheType } from '../ArcheType'; -export interface ApiLightGet { - type?: ApiResourceType.Light; +export interface LightGet { + type?: ResourceType.Light; id: string; owner: ResourceIdentifier; metadata: { - archetype: string; + archetype: ArcheType; name: string; }; on: { @@ -65,15 +66,19 @@ export interface ApiLightGet { }; effects?: { effect: 'fire' | 'candle' | 'no_effect'; + // TODO any[] not correct type status_value: any[]; status: 'fire' | 'candle' | 'no_effect'; + // TODO any[] not correct type effect_values: any[]; }; timed_effects?: { effect: 'sunrise' | 'no_effect'; duration: number; + // TODO any[] not correct type status_values: any[]; status: 'sunrise' | 'no_effect'; + // TODO any[] not correct type effect_values: any[]; }; } diff --git a/src/api/get/MotionGet.ts b/src/api/get/MotionGet.ts new file mode 100644 index 0000000..47f5e9c --- /dev/null +++ b/src/api/get/MotionGet.ts @@ -0,0 +1,13 @@ +import { ResourceType } from '../ResourceType'; +import { ResourceIdentifier } from '../ResourceIdentifier'; + +export interface MotionGet { + type?: ResourceType.Motion; + id: string; + owner: ResourceIdentifier; + enabled: boolean; + motion: { + motion: boolean; + motion_valid: boolean; + }; +} diff --git a/src/api/room/get.ts b/src/api/get/RoomGet.ts similarity index 52% rename from src/api/room/get.ts rename to src/api/get/RoomGet.ts index a66f363..c3a6430 100644 --- a/src/api/room/get.ts +++ b/src/api/get/RoomGet.ts @@ -1,13 +1,14 @@ -import { ApiResourceType } from '../ApiResourceType'; +import { ResourceType } from '../ResourceType'; import { ResourceIdentifier } from '../ResourceIdentifier'; +import { ArcheType } from '../ArcheType'; -export interface ApiRoomGet { - type?: ApiResourceType.Room; +export interface RoomGet { + type?: ResourceType.Room; id: string; children: Array; services: Array; metadata: { name: string; - archetype: string; + archetype: ArcheType; }; } diff --git a/src/api/scene/get.ts b/src/api/get/SceneGet.ts similarity index 87% rename from src/api/scene/get.ts rename to src/api/get/SceneGet.ts index ff61134..bcd2913 100644 --- a/src/api/scene/get.ts +++ b/src/api/get/SceneGet.ts @@ -1,8 +1,8 @@ -import { ApiResourceType } from '../ApiResourceType'; +import { ResourceType } from '../ResourceType'; import { ResourceIdentifier } from '../ResourceIdentifier'; -export interface ApiSceneGet { - type?: ApiResourceType.Scene; +export interface SceneGet { + type?: ResourceType.Scene; id: string; metadata: { name: string; diff --git a/src/api/get/ZigbeeConnectivityGet.ts b/src/api/get/ZigbeeConnectivityGet.ts new file mode 100644 index 0000000..2f0056f --- /dev/null +++ b/src/api/get/ZigbeeConnectivityGet.ts @@ -0,0 +1,10 @@ +import { ResourceType } from '../ResourceType'; +import { ResourceIdentifier } from '../ResourceIdentifier'; + +export interface ZigbeeConnectivityGet { + type?: ResourceType.ZigbeeConnectivity; + id: string; + owner: ResourceIdentifier; + status: 'connected' | 'disconnected' | 'connectivity_issue' | 'unidirectional_incoming'; + mac_address: string; +} diff --git a/src/api/get/ZigbeeDeviceDiscoveryGet.ts b/src/api/get/ZigbeeDeviceDiscoveryGet.ts new file mode 100644 index 0000000..dedfb82 --- /dev/null +++ b/src/api/get/ZigbeeDeviceDiscoveryGet.ts @@ -0,0 +1,9 @@ +import { ResourceType } from '../ResourceType'; +import { ResourceIdentifier } from '../ResourceIdentifier'; + +export interface ZigbeeDeviceDiscoveryGet { + type?: ResourceType.ZigbeeDeviceDiscovery; + id: string; + owner: ResourceIdentifier; + status: 'active' | 'ready'; +} diff --git a/src/api/zone/get.ts b/src/api/get/ZoneGet.ts similarity index 52% rename from src/api/zone/get.ts rename to src/api/get/ZoneGet.ts index 655bbf3..671dc9b 100644 --- a/src/api/zone/get.ts +++ b/src/api/get/ZoneGet.ts @@ -1,13 +1,14 @@ -import { ApiResourceType } from '../ApiResourceType'; +import { ResourceType } from '../ResourceType'; import { ResourceIdentifier } from '../ResourceIdentifier'; +import { ArcheType } from '../ArcheType'; -export interface ApiZoneGet { - type?: ApiResourceType.Zone; +export interface ZoneGet { + type?: ResourceType.Zone; id: string; children: Array; services: Array; metadata: { name: string; - archetype: string; + archetype: ArcheType; }; } diff --git a/src/api/post/RoomPost.ts b/src/api/post/RoomPost.ts new file mode 100644 index 0000000..d228f64 --- /dev/null +++ b/src/api/post/RoomPost.ts @@ -0,0 +1,12 @@ +import { ResourceType } from '../ResourceType'; +import { ResourceIdentifier } from '../ResourceIdentifier'; +import { ArcheType } from '../ArcheType'; + +export interface RoomPost { + type?: ResourceType.Room; + children: Array; + metadata: { + name: string; + archetype: ArcheType; + }; +} diff --git a/src/api/scene/post.ts b/src/api/post/ScenePost.ts similarity index 87% rename from src/api/scene/post.ts rename to src/api/post/ScenePost.ts index 33fe33d..7d07c49 100644 --- a/src/api/scene/post.ts +++ b/src/api/post/ScenePost.ts @@ -1,8 +1,8 @@ -import { ApiResourceType } from '../ApiResourceType'; +import { ResourceType } from '../ResourceType'; import { ResourceIdentifier } from '../ResourceIdentifier'; -export interface ApiScenePost { - type?: ApiResourceType.Scene; +export interface ScenePost { + type?: ResourceType.Scene; metadata?: { name: string; image?: ResourceIdentifier }; group: ResourceIdentifier; actions: Array<{ diff --git a/src/api/post/ZonePost.ts b/src/api/post/ZonePost.ts new file mode 100644 index 0000000..c099e4b --- /dev/null +++ b/src/api/post/ZonePost.ts @@ -0,0 +1,12 @@ +import { ResourceType } from '../ResourceType'; +import { ResourceIdentifier } from '../ResourceIdentifier'; +import { ArcheType } from '../ArcheType'; + +export interface ZonePost { + type?: ResourceType.Zone; + children: Array; + metadata: { + name: string; + archetype: ArcheType; + }; +} diff --git a/src/api/put/DevicePut.ts b/src/api/put/DevicePut.ts new file mode 100644 index 0000000..fb744f8 --- /dev/null +++ b/src/api/put/DevicePut.ts @@ -0,0 +1,13 @@ +import { ResourceType } from '../ResourceType'; +import { ArcheType } from '../ArcheType'; + +export interface DevicePut { + type?: ResourceType.Device; + metadata?: { + archetype?: ArcheType; + name?: string; + }; + identify?: { + action: 'identify'; + }; +} diff --git a/src/api/grouped_light/put.ts b/src/api/put/GroupedLightPut.ts similarity index 71% rename from src/api/grouped_light/put.ts rename to src/api/put/GroupedLightPut.ts index 92da34e..e874ccf 100644 --- a/src/api/grouped_light/put.ts +++ b/src/api/put/GroupedLightPut.ts @@ -1,9 +1,10 @@ -import { ApiResourceType } from '../ApiResourceType'; +import { ResourceType } from '../ResourceType'; +import { ArcheType } from '../ArcheType'; -export interface ApiGroupedLightPut { - type?: ApiResourceType.GroupedLight; +export interface GroupedLightPut { + type?: ResourceType.GroupedLight; metadata?: { - archetype?: string; + archetype?: ArcheType; name?: string; }; on?: { diff --git a/src/api/light/put.ts b/src/api/put/LightPut.ts similarity index 80% rename from src/api/light/put.ts rename to src/api/put/LightPut.ts index 06cfc96..75bcec3 100644 --- a/src/api/light/put.ts +++ b/src/api/put/LightPut.ts @@ -1,9 +1,10 @@ -import { ApiResourceType } from '../ApiResourceType'; +import { ResourceType } from '../ResourceType'; +import { ArcheType } from '../ArcheType'; -export interface ApiLightPut { - type?: ApiResourceType.Light; +export interface LightPut { + type?: ResourceType.Light; metadata?: { - archetype?: string; + archetype?: ArcheType; name?: string; }; on?: { diff --git a/src/api/put/MotionPut.ts b/src/api/put/MotionPut.ts new file mode 100644 index 0000000..7172776 --- /dev/null +++ b/src/api/put/MotionPut.ts @@ -0,0 +1,6 @@ +import { ResourceType } from '../ResourceType'; + +export interface MotionPut { + type?: ResourceType.Motion; + enabled?: boolean; +} diff --git a/src/api/put/RoomPut.ts b/src/api/put/RoomPut.ts new file mode 100644 index 0000000..dbe3220 --- /dev/null +++ b/src/api/put/RoomPut.ts @@ -0,0 +1,12 @@ +import { ResourceType } from '../ResourceType'; +import { ResourceIdentifier } from '../ResourceIdentifier'; +import { ArcheType } from '../ArcheType'; + +export interface RoomPut { + type?: ResourceType.Room; + children?: Array; + metadata?: { + name?: string; + archetype?: ArcheType; + }; +} diff --git a/src/api/scene/put.ts b/src/api/put/ScenePut.ts similarity index 88% rename from src/api/scene/put.ts rename to src/api/put/ScenePut.ts index d9d85b6..7588c60 100644 --- a/src/api/scene/put.ts +++ b/src/api/put/ScenePut.ts @@ -1,8 +1,8 @@ -import { ApiResourceType } from '../ApiResourceType'; +import { ResourceType } from '../ResourceType'; import { ResourceIdentifier } from '../ResourceIdentifier'; -export interface ApiScenePut { - type?: ApiResourceType.Scene; +export interface ScenePut { + type?: ResourceType.Scene; metadata?: { name: string }; actions?: Array<{ target: ResourceIdentifier; diff --git a/src/api/put/ZigbeeDeviceDiscoveryPut.ts b/src/api/put/ZigbeeDeviceDiscoveryPut.ts new file mode 100644 index 0000000..d35c56f --- /dev/null +++ b/src/api/put/ZigbeeDeviceDiscoveryPut.ts @@ -0,0 +1,11 @@ +import { ResourceType } from '../ResourceType'; + +export interface ZigbeeDeviceDiscoveryPut { + type?: ResourceType.ZigbeeDeviceDiscovery; + action: { + action_type: 'search'; + // TODO not properly documented, type is unknown + search_codes: Array; + install_codes: Array; + }; +} diff --git a/src/api/put/ZonePut.ts b/src/api/put/ZonePut.ts new file mode 100644 index 0000000..a9a3b1f --- /dev/null +++ b/src/api/put/ZonePut.ts @@ -0,0 +1,12 @@ +import { ResourceType } from '../ResourceType'; +import { ResourceIdentifier } from '../ResourceIdentifier'; +import { ArcheType } from '../ArcheType'; + +export interface ZonePut { + type?: ResourceType.Zone; + children?: Array; + metadata?: { + name?: string; + archetype?: ArcheType; + }; +} diff --git a/src/api/room/post.ts b/src/api/room/post.ts deleted file mode 100644 index 6e9c5dc..0000000 --- a/src/api/room/post.ts +++ /dev/null @@ -1,11 +0,0 @@ -import { ApiResourceType } from '../ApiResourceType'; -import { ResourceIdentifier } from '../ResourceIdentifier'; - -export interface ApiRoomPost { - type?: ApiResourceType.Room; - children: Array; - metadata: { - name: string; - archetype: string; - }; -} diff --git a/src/api/room/put.ts b/src/api/room/put.ts deleted file mode 100644 index 4a40888..0000000 --- a/src/api/room/put.ts +++ /dev/null @@ -1,11 +0,0 @@ -import { ApiResourceType } from '../ApiResourceType'; -import { ResourceIdentifier } from '../ResourceIdentifier'; - -export interface ApiRoomPut { - type?: ApiResourceType.Room; - children?: Array; - metadata?: { - name?: string; - archetype?: string; - }; -} diff --git a/src/api/zone/post.ts b/src/api/zone/post.ts deleted file mode 100644 index 45a9a67..0000000 --- a/src/api/zone/post.ts +++ /dev/null @@ -1,11 +0,0 @@ -import { ApiResourceType } from '../ApiResourceType'; -import { ResourceIdentifier } from '../ResourceIdentifier'; - -export interface ApiZonePost { - type?: ApiResourceType.Zone; - children: Array; - metadata: { - name: string; - archetype: string; - }; -} diff --git a/src/api/zone/put.ts b/src/api/zone/put.ts deleted file mode 100644 index 800cdfe..0000000 --- a/src/api/zone/put.ts +++ /dev/null @@ -1,11 +0,0 @@ -import { ApiResourceType } from '../ApiResourceType'; -import { ResourceIdentifier } from '../ResourceIdentifier'; - -export interface ApiZonePut { - type?: ApiResourceType.Zone; - children?: Array; - metadata?: { - name?: string; - archetype?: string; - }; -} diff --git a/src/bridge/Bridge.ts b/src/bridge/Bridge.ts deleted file mode 100644 index 0509e31..0000000 --- a/src/bridge/Bridge.ts +++ /dev/null @@ -1,74 +0,0 @@ -import { EventEmitter } from 'node:events'; -import { BridgeEvents, Events } from './BridgeEvents'; -import { Rest } from '../connections/Rest'; -import { ResourceManager } from '../managers/ResourceManager'; -import { Sse } from '../connections/Sse'; - -export const CA = - '-----BEGIN CERTIFICATE-----\n' + - 'MIICMjCCAdigAwIBAgIUO7FSLbaxikuXAljzVaurLXWmFw4wCgYIKoZIzj0EAwIw\n' + - 'OTELMAkGA1UEBhMCTkwxFDASBgNVBAoMC1BoaWxpcHMgSHVlMRQwEgYDVQQDDAty\n' + - 'b290LWJyaWRnZTAiGA8yMDE3MDEwMTAwMDAwMFoYDzIwMzgwMTE5MDMxNDA3WjA5\n' + - 'MQswCQYDVQQGEwJOTDEUMBIGA1UECgwLUGhpbGlwcyBIdWUxFDASBgNVBAMMC3Jv\n' + - 'b3QtYnJpZGdlMFkwEwYHKoZIzj0CAQYIKoZIzj0DAQcDQgAEjNw2tx2AplOf9x86\n' + - 'aTdvEcL1FU65QDxziKvBpW9XXSIcibAeQiKxegpq8Exbr9v6LBnYbna2VcaK0G22\n' + - 'jOKkTqOBuTCBtjAPBgNVHRMBAf8EBTADAQH/MA4GA1UdDwEB/wQEAwIBhjAdBgNV\n' + - 'HQ4EFgQUZ2ONTFrDT6o8ItRnKfqWKnHFGmQwdAYDVR0jBG0wa4AUZ2ONTFrDT6o8\n' + - 'ItRnKfqWKnHFGmShPaQ7MDkxCzAJBgNVBAYTAk5MMRQwEgYDVQQKDAtQaGlsaXBz\n' + - 'IEh1ZTEUMBIGA1UEAwwLcm9vdC1icmlkZ2WCFDuxUi22sYpLlwJY81Wrqy11phcO\n' + - 'MAoGCCqGSM49BAMCA0gAMEUCIEBYYEOsa07TH7E5MJnGw557lVkORgit2Rm1h3B2\n' + - 'sFgDAiEA1Fj/C3AN5psFMjo0//mrQebo0eKd3aWRx+pQY08mk48=\n' + - '-----END CERTIFICATE-----'; - -export interface BridgeConnectionOptions { - applicationKey: string; -} - -export interface LocalBridgeConnectionsOptions extends BridgeConnectionOptions { - ip: string; -} - -export interface ExternalBridgeConnectionOptions extends BridgeConnectionOptions { - accessToken: string; -} - -export interface BridgeOptions { - connection: LocalBridgeConnectionsOptions | ExternalBridgeConnectionOptions; -} - -export interface Bridge { - on: (event: T, listener: (...args: BridgeEvents[T]) => any) => this; - once: (event: T, listener: (...args: BridgeEvents[T]) => any) => this; - emit: (event: T, ...args: BridgeEvents[T]) => boolean; - off: (event: T, listener: (...args: BridgeEvents[T]) => any) => this; - removeAllListeners: (event?: T) => this; -} - -export class Bridge extends EventEmitter { - public readonly options: BridgeOptions; - public readonly rest = new Rest(this); - public readonly sse = new Sse(this); - public readonly resources = new ResourceManager(this); - - public constructor(options: BridgeOptions) { - super(); - this.options = options; - } - - get _url(): string { - if ('ip' in this.options.connection) return `https://${this.options.connection.ip}:443`; - else return `https://api.meethue.com/route`; - } - - public async connect(): Promise { - const data = await this.rest.get('/resource'); - - for (const resource of data) { - this.resources._create(resource); - } - - await this.sse.connect(); - - this.emit(Events.Ready, this); - } -} diff --git a/src/util/color/gamut.ts b/src/color/gamut.ts similarity index 100% rename from src/util/color/gamut.ts rename to src/color/gamut.ts diff --git a/src/util/color/hex.ts b/src/color/hex.ts similarity index 61% rename from src/util/color/hex.ts rename to src/color/hex.ts index a4aa500..a64f50e 100644 --- a/src/util/color/hex.ts +++ b/src/color/hex.ts @@ -6,13 +6,13 @@ export function rgbToHex({ red, green, blue }: RGB) { } export function hexToRgb(hex: string): RGB { - const result = /^#?([a-f\d]{2})([a-f\d]{2})([a-f\d]{2})$/i.exec(hex); - if (!result) throw new TypeError('Invalid hex'); + const parsed = hex.replace(/^#?([a-f\d])([a-f\d])([a-f\d])$/i, (m, r, g, b) => '#' + r + r + g + g + b + b); + if (!parsed) throw new TypeError('Invalid hex'); return { - red: parseInt(result[1], 16), - green: parseInt(result[2], 16), - blue: parseInt(result[3], 16), + red: parseInt(parsed.substring(1, 3), 16), + green: parseInt(parsed.substring(3, 5), 16), + blue: parseInt(parsed.substring(5, 7), 16), }; } diff --git a/src/util/color/rgb.ts b/src/color/rgb.ts similarity index 100% rename from src/util/color/rgb.ts rename to src/color/rgb.ts diff --git a/src/util/color/xy.ts b/src/color/xy.ts similarity index 100% rename from src/util/color/xy.ts rename to src/color/xy.ts diff --git a/src/connections/Limit.ts b/src/connections/Limit.ts index 0dbacc8..d17a36c 100644 --- a/src/connections/Limit.ts +++ b/src/connections/Limit.ts @@ -7,7 +7,7 @@ export interface RateLimits { } export const RateLimits: RateLimits = { - '/resources/lights': 100, + '/resource/light/': 100, } as const; export class Limit extends AsyncQueue { diff --git a/src/connections/Rest.ts b/src/connections/Rest.ts index ae874a2..1ec5be7 100644 --- a/src/connections/Rest.ts +++ b/src/connections/Rest.ts @@ -1,8 +1,8 @@ -import { Bridge, CA } from '../bridge/Bridge'; +import { CA, Hue } from '../hue/Hue'; import { Agent, request } from 'undici'; import { Collection } from '@discordjs/collection'; import { Limit } from './Limit'; -import { Events } from '../bridge/BridgeEvents'; +import { Events } from '../hue/HueEvents'; export interface Request { route: string; @@ -23,12 +23,12 @@ export enum RestRequestType { } export class Rest { - public readonly bridge: Bridge; + public readonly hue: Hue; public readonly dispatcher: Agent; public readonly limits: Collection; - constructor(bridge: Bridge) { - this.bridge = bridge; + constructor(hue: Hue) { + this.hue = hue; this.dispatcher = new Agent({ connect: { ca: CA, @@ -56,8 +56,8 @@ export class Rest { return await this._queue(route, RestRequestType.Delete); } - private async _queue(route: string, method: RestRequestType, data?: Record) { - this.bridge.emit(Events.Request, { + public async _queue(route: string, method: RestRequestType, data?: Record) { + this.hue.emit(Events.Request, { route, method, body: data, @@ -68,15 +68,15 @@ export class Rest { await limit.wait(); try { - const { body, statusCode } = await request(`${this.bridge._url}/clip/v2${route}`, { + const { body, statusCode } = await request(`${this.hue._url}/clip/v2${route}`, { method, body: data ? JSON.stringify(data) : null, headers: { Authorization: - 'accessToken' in this.bridge.options.connection - ? `Bearer ${this.bridge.options.connection.accessToken}` + 'accessToken' in this.hue.options.connection + ? `Bearer ${this.hue.options.connection.accessToken}` : undefined, - 'hue-application-key': this.bridge.options.connection.applicationKey, + 'hue-application-key': this.hue.options.connection.applicationKey, 'Content-Type': 'application/json', Accept: 'application/json', }, @@ -85,9 +85,10 @@ export class Rest { const responseData = await body.json(); - if (statusCode !== 200) throw new Error(`${responseData?.errors?.[0]?.description ?? 'unknown'}`); + if (statusCode !== 200 && statusCode !== 201) + throw new Error(`${responseData?.errors?.[0]?.description ?? 'unknown'}`); - this.bridge.emit(Events.Response, { + this.hue.emit(Events.Response, { route, method, body: responseData, @@ -100,13 +101,12 @@ export class Rest { } } - private _getLimit(route: string) { + public _getLimit(route: string) { return this.limits.ensure(route, () => new Limit(this, route)); } - private _sanitizeRoute(route: string) { - route.replace(/\/[0-9a-fA-F]{8}\b-[0-9a-fA-F]{4}\b-[0-9a-fA-F]{4}\b-[0-9a-fA-F]{4}\b-[0-9a-fA-F]{12}$/, ''); - - return route; + public _sanitizeRoute(route: string) { + // Remove possible resource ID from the route + return route.replace(/[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}/, ''); } } diff --git a/src/connections/Sse.ts b/src/connections/Sse.ts index 4a5bd31..a476e32 100644 --- a/src/connections/Sse.ts +++ b/src/connections/Sse.ts @@ -1,43 +1,16 @@ -import { Bridge } from '../bridge/Bridge'; +import { Hue } from '../hue/Hue'; import { Agent, Dispatcher, request } from 'undici'; import BodyReadable from 'undici/types/readable'; -import { Events } from '../bridge/BridgeEvents'; -import { ApiResourceType } from '../api/ApiResourceType'; - -export const RESOURCES_EVENTS = { - [ApiResourceType.Device]: [Events.DeviceAdd, Events.DeviceUpdate, Events.DeviceDelete], - [ApiResourceType.BridgeHome]: undefined, - [ApiResourceType.Room]: [Events.RoomAdd, Events.RoomUpdate, Events.RoomDelete], - [ApiResourceType.Zone]: [Events.ZoneAdd, Events.ZoneUpdate, Events.ZoneDelete], - [ApiResourceType.Light]: [Events.LightAdd, Events.LightUpdate, Events.LightDelete], - [ApiResourceType.Button]: undefined, - [ApiResourceType.Temperature]: undefined, - [ApiResourceType.LightLevel]: undefined, - [ApiResourceType.Motion]: undefined, - [ApiResourceType.Entertainment]: undefined, - [ApiResourceType.GroupedLight]: [Events.GroupedLightAdd, Events.GroupedLightUpdate, Events.GroupedLightDelete], - [ApiResourceType.DevicePower]: undefined, - [ApiResourceType.ZigbeeBridgeConnectivity]: undefined, - [ApiResourceType.ZgpConnectivity]: undefined, - [ApiResourceType.Bridge]: undefined, - [ApiResourceType.Homekit]: undefined, - [ApiResourceType.Scene]: [Events.SceneAdd, Events.SceneUpdate, Events.SceneDelete], - [ApiResourceType.EntertainmentConfiguration]: undefined, - [ApiResourceType.PublicImage]: undefined, - [ApiResourceType.BehaviourScript]: undefined, - [ApiResourceType.BehaviourInstance]: undefined, - [ApiResourceType.Geofence]: undefined, - [ApiResourceType.GeofenceClient]: undefined, - [ApiResourceType.Geolocation]: undefined, -}; +import { Events } from '../hue/HueEvents'; +import { RESOURCE_ADD, RESOURCE_DELETE, RESOURCE_UPDATE } from './events'; export class Sse { - public readonly bridge: Bridge; + public readonly hue: Hue; public readonly dispatcher: Agent; public connection?: BodyReadable & Dispatcher.BodyMixin; - constructor(bridge: Bridge) { - this.bridge = bridge; + constructor(hue: Hue) { + this.hue = hue; this.dispatcher = new Agent({ connect: { // ca: CA, @@ -51,20 +24,20 @@ export class Sse { } public debug(message: string) { - this.bridge.emit(Events.Debug, `[SSE] ${message}`); + this.hue.emit(Events.Debug, `[SSE] ${message}`); } public async connect(): Promise { this.connection = undefined; - const { body, statusCode } = await request(`${this.bridge._url}/eventstream/clip/v2`, { + const { body, statusCode } = await request(`${this.hue._url}/eventstream/clip/v2`, { method: 'GET', headers: { Authorization: - 'accessToken' in this.bridge.options.connection - ? `Bearer ${this.bridge.options.connection.accessToken}` + 'accessToken' in this.hue.options.connection + ? `Bearer ${this.hue.options.connection.accessToken}` : undefined, - 'hue-application-key': this.bridge.options.connection.applicationKey, + 'hue-application-key': this.hue.options.connection.applicationKey, Accept: 'text/event-stream', }, bodyTimeout: 0, @@ -81,67 +54,32 @@ export class Sse { public async event(raw: string) { if (raw === ': hi\n' + '\n') return this.debug('Hi'); - const events = this.parse(raw); + const events = this._parse(raw); + + const queue: Array<(() => boolean) | undefined> = []; for (const event of events) { this.debug(`Received ${event.data.length} ${event.type} event(s)`); - for (const data of event.data) { - switch (event.type) { - case 'add': { - this.add(data); - break; - } - case 'update': { - this.update(data); - break; - } - case 'delete': { - this.delete(data); - break; - } - } - } - } - this.bridge.emit(Events.Raw, events); - } - - public add(data: Record): void { - const resource = this.bridge.resources._create(data); - if (!resource) return; - - this.bridge.emit(Events.Add, resource); - - const event = RESOURCES_EVENTS[resource.type as ApiResourceType]; - if (event) this.bridge.emit(event[0], resource as any); - } - public update(data: Record): void { - const resource = this.bridge.resources.cache.get(data.id); - if (!resource) return; - - const clone = resource._update(data); - - this.bridge.emit(Events.Update, resource, clone); - - const event = RESOURCES_EVENTS[resource.type as ApiResourceType]; - if (event) this.bridge.emit(event[1], resource as any, clone as any); - } - - public delete(data: Record) { - const resource = this.bridge.resources.cache.get(data.id); - if (!resource) return; + for (const data of event.data) { + let handler: ((data: any, hue: Hue) => (() => boolean) | undefined) | undefined; - const clone = resource._clone(); + if (event.type == 'add') handler = RESOURCE_ADD[data.type]; + else if (event.type == 'delete') handler = RESOURCE_DELETE[data.type]; + else if (event.type == 'update') handler = RESOURCE_UPDATE[data.type]; - this.bridge.resources.cache.delete(data.id); + if (handler) queue.push(handler(data, this.hue)); + } + } - this.bridge.emit(Events.Delete, clone); + for (const emitter of queue) { + if (typeof emitter === 'function') emitter(); + } - const event = RESOURCES_EVENTS[clone.type as ApiResourceType]; - if (event) this.bridge.emit(event[2], clone as any); + this.hue.emit(Events.Raw, events); } - public parse(raw: string): Array> { + public _parse(raw: string): Array> { const regex = /id: \d+:\d+\ndata: /; const replaced = raw.replace(regex, ''); diff --git a/src/connections/events/add/BridgeAdd.ts b/src/connections/events/add/BridgeAdd.ts new file mode 100644 index 0000000..99e75ef --- /dev/null +++ b/src/connections/events/add/BridgeAdd.ts @@ -0,0 +1,9 @@ +import { Hue } from '../../../hue/Hue'; +import { Events } from '../../../hue/HueEvents'; + +export default function bridgeAdd(data: any, hue: Hue) { + const bridge = hue.bridges._add(data); + if (!bridge) return; + + return () => hue.emit(Events.BridgeAdd, bridge); +} diff --git a/src/connections/events/add/DeviceAdd.ts b/src/connections/events/add/DeviceAdd.ts new file mode 100644 index 0000000..5c1a4ed --- /dev/null +++ b/src/connections/events/add/DeviceAdd.ts @@ -0,0 +1,9 @@ +import { Hue } from '../../../hue/Hue'; +import { Events } from '../../../hue/HueEvents'; + +export default function deviceAdd(data: any, hue: Hue) { + const device = hue.devices._add(data); + if (!device) return; + + return () => hue.emit(Events.DeviceAdd, device); +} diff --git a/src/connections/events/add/DevicePowerAdd.ts b/src/connections/events/add/DevicePowerAdd.ts new file mode 100644 index 0000000..4b19d8a --- /dev/null +++ b/src/connections/events/add/DevicePowerAdd.ts @@ -0,0 +1,9 @@ +import { Hue } from '../../../hue/Hue'; +import { Events } from '../../../hue/HueEvents'; + +export default function devicePowerAdd(data: any, hue: Hue) { + const devicePower = hue.devicePowers._add(data); + if (!devicePower) return; + + return () => hue.emit(Events.DevicePowerAdd, devicePower); +} diff --git a/src/connections/events/add/GroupedLightAdd.ts b/src/connections/events/add/GroupedLightAdd.ts new file mode 100644 index 0000000..457910d --- /dev/null +++ b/src/connections/events/add/GroupedLightAdd.ts @@ -0,0 +1,9 @@ +import { Hue } from '../../../hue/Hue'; +import { Events } from '../../../hue/HueEvents'; + +export default function groupedLightAdd(data: any, hue: Hue) { + const groupedLight = hue.groupedLights._add(data); + if (!groupedLight) return; + + return () => hue.emit(Events.GroupedLightAdd, groupedLight); +} diff --git a/src/connections/events/add/LightAdd.ts b/src/connections/events/add/LightAdd.ts new file mode 100644 index 0000000..ca15534 --- /dev/null +++ b/src/connections/events/add/LightAdd.ts @@ -0,0 +1,9 @@ +import { Hue } from '../../../hue/Hue'; +import { Events } from '../../../hue/HueEvents'; + +export default function lightAdd(data: any, hue: Hue) { + const light = hue.lights._add(data); + if (!light) return; + + return () => hue.emit(Events.LightAdd, light); +} diff --git a/src/connections/events/add/MotionAdd.ts b/src/connections/events/add/MotionAdd.ts new file mode 100644 index 0000000..4bfc2fc --- /dev/null +++ b/src/connections/events/add/MotionAdd.ts @@ -0,0 +1,9 @@ +import { Hue } from '../../../hue/Hue'; +import { Events } from '../../../hue/HueEvents'; + +export default function motionAdd(data: any, hue: Hue) { + const motion = hue.motions._add(data); + if (!motion) return; + + return () => hue.emit(Events.MotionAdd, motion); +} diff --git a/src/connections/events/add/RoomAdd.ts b/src/connections/events/add/RoomAdd.ts new file mode 100644 index 0000000..4aee1e4 --- /dev/null +++ b/src/connections/events/add/RoomAdd.ts @@ -0,0 +1,9 @@ +import { Hue } from '../../../hue/Hue'; +import { Events } from '../../../hue/HueEvents'; + +export default function roomAdd(data: any, hue: Hue) { + const room = hue.rooms._add(data); + if (!room) return; + + return () => hue.emit(Events.RoomAdd, room); +} diff --git a/src/connections/events/add/SceneAdd.ts b/src/connections/events/add/SceneAdd.ts new file mode 100644 index 0000000..5007489 --- /dev/null +++ b/src/connections/events/add/SceneAdd.ts @@ -0,0 +1,9 @@ +import { Hue } from '../../../hue/Hue'; +import { Events } from '../../../hue/HueEvents'; + +export default function sceneAdd(data: any, hue: Hue) { + const scene = hue.scenes._add(data); + if (!scene) return; + + return () => hue.emit(Events.SceneAdd, scene); +} diff --git a/src/connections/events/add/ZigbeeConnectivityAdd.ts b/src/connections/events/add/ZigbeeConnectivityAdd.ts new file mode 100644 index 0000000..115baf9 --- /dev/null +++ b/src/connections/events/add/ZigbeeConnectivityAdd.ts @@ -0,0 +1,9 @@ +import { Hue } from '../../../hue/Hue'; +import { Events } from '../../../hue/HueEvents'; + +export default function zigbeeConnectivityAdd(data: any, hue: Hue) { + const zigbeeConnectivity = hue.zigbeeConnectivities._add(data); + if (!zigbeeConnectivity) return; + + return () => hue.emit(Events.ZigbeeConnectivityAdd, zigbeeConnectivity); +} diff --git a/src/connections/events/add/ZigbeeDeviceDiscoveryAdd.ts b/src/connections/events/add/ZigbeeDeviceDiscoveryAdd.ts new file mode 100644 index 0000000..bce1666 --- /dev/null +++ b/src/connections/events/add/ZigbeeDeviceDiscoveryAdd.ts @@ -0,0 +1,9 @@ +import { Hue } from '../../../hue/Hue'; +import { Events } from '../../../hue/HueEvents'; + +export default function zigbeeDeviceDiscoveryAdd(data: any, hue: Hue) { + const zigbeeDeviceDiscovery = hue.zigbeeDeviceDiscoveries._add(data); + if (!zigbeeDeviceDiscovery) return; + + return () => hue.emit(Events.ZigbeeDeviceDiscoveryAdd, zigbeeDeviceDiscovery); +} diff --git a/src/connections/events/add/ZoneAdd.ts b/src/connections/events/add/ZoneAdd.ts new file mode 100644 index 0000000..e3f2f1c --- /dev/null +++ b/src/connections/events/add/ZoneAdd.ts @@ -0,0 +1,9 @@ +import { Hue } from '../../../hue/Hue'; +import { Events } from '../../../hue/HueEvents'; + +export default function zoneAdd(data: any, hue: Hue) { + const zone = hue.zones._add(data); + if (!zone) return; + + return () => hue.emit(Events.ZoneAdd, zone); +} diff --git a/src/connections/events/delete/BridgeDelete.ts b/src/connections/events/delete/BridgeDelete.ts new file mode 100644 index 0000000..e2261c3 --- /dev/null +++ b/src/connections/events/delete/BridgeDelete.ts @@ -0,0 +1,13 @@ +import { Hue } from '../../../hue/Hue'; +import { Events } from '../../../hue/HueEvents'; + +export default function bridgeDelete(data: any, hue: Hue) { + const bridge = hue.bridges.cache.get(data.id); + if (!bridge) return; + + const clone = bridge._clone(); + + hue.bridges.cache.delete(data.id); + + return () => hue.emit(Events.BridgeDelete, clone); +} diff --git a/src/connections/events/delete/DeviceDelete.ts b/src/connections/events/delete/DeviceDelete.ts new file mode 100644 index 0000000..04bc916 --- /dev/null +++ b/src/connections/events/delete/DeviceDelete.ts @@ -0,0 +1,13 @@ +import { Hue } from '../../../hue/Hue'; +import { Events } from '../../../hue/HueEvents'; + +export default function deviceDelete(data: any, hue: Hue) { + const device = hue.devices.cache.get(data.id); + if (!device) return; + + const clone = device._clone(); + + hue.devices.cache.delete(data.id); + + return () => hue.emit(Events.DeviceDelete, clone); +} diff --git a/src/connections/events/delete/DevicePowerDelete.ts b/src/connections/events/delete/DevicePowerDelete.ts new file mode 100644 index 0000000..b84f5d5 --- /dev/null +++ b/src/connections/events/delete/DevicePowerDelete.ts @@ -0,0 +1,13 @@ +import { Hue } from '../../../hue/Hue'; +import { Events } from '../../../hue/HueEvents'; + +export default function devicePowerDelete(data: any, hue: Hue) { + const devicePower = hue.devicePowers.cache.get(data.id); + if (!devicePower) return; + + const clone = devicePower._clone(); + + hue.devicePowers.cache.delete(data.id); + + return () => hue.emit(Events.DevicePowerDelete, clone); +} diff --git a/src/connections/events/delete/GroupedLightDelete.ts b/src/connections/events/delete/GroupedLightDelete.ts new file mode 100644 index 0000000..24d1b12 --- /dev/null +++ b/src/connections/events/delete/GroupedLightDelete.ts @@ -0,0 +1,13 @@ +import { Hue } from '../../../hue/Hue'; +import { Events } from '../../../hue/HueEvents'; + +export default function groupedLightDelete(data: any, hue: Hue) { + const groupedLight = hue.groupedLights.cache.get(data.id); + if (!groupedLight) return; + + const clone = groupedLight._clone(); + + hue.groupedLights.cache.delete(data.id); + + return () => hue.emit(Events.GroupedLightDelete, clone); +} diff --git a/src/connections/events/delete/LightDelete.ts b/src/connections/events/delete/LightDelete.ts new file mode 100644 index 0000000..e403cca --- /dev/null +++ b/src/connections/events/delete/LightDelete.ts @@ -0,0 +1,13 @@ +import { Hue } from '../../../hue/Hue'; +import { Events } from '../../../hue/HueEvents'; + +export default function lightDelete(data: any, hue: Hue) { + const light = hue.lights.cache.get(data.id); + if (!light) return; + + const clone = light._clone(); + + hue.devices.cache.delete(data.id); + + return () => hue.emit(Events.LightDelete, clone); +} diff --git a/src/connections/events/delete/MotionDelete.ts b/src/connections/events/delete/MotionDelete.ts new file mode 100644 index 0000000..a5c0c3a --- /dev/null +++ b/src/connections/events/delete/MotionDelete.ts @@ -0,0 +1,13 @@ +import { Hue } from '../../../hue/Hue'; +import { Events } from '../../../hue/HueEvents'; + +export default function motionDelete(data: any, hue: Hue) { + const motion = hue.motions.cache.get(data.id); + if (!motion) return; + + const clone = motion._clone(); + + hue.devices.cache.delete(data.id); + + return () => hue.emit(Events.MotionDelete, clone); +} diff --git a/src/connections/events/delete/RoomDelete.ts b/src/connections/events/delete/RoomDelete.ts new file mode 100644 index 0000000..9a14360 --- /dev/null +++ b/src/connections/events/delete/RoomDelete.ts @@ -0,0 +1,13 @@ +import { Hue } from '../../../hue/Hue'; +import { Events } from '../../../hue/HueEvents'; + +export default function roomDelete(data: any, hue: Hue) { + const room = hue.rooms.cache.get(data.id); + if (!room) return; + + const clone = room._clone(); + + hue.rooms.cache.delete(data.id); + + return () => hue.emit(Events.RoomDelete, clone); +} diff --git a/src/connections/events/delete/SceneDelete.ts b/src/connections/events/delete/SceneDelete.ts new file mode 100644 index 0000000..4cd8d9f --- /dev/null +++ b/src/connections/events/delete/SceneDelete.ts @@ -0,0 +1,13 @@ +import { Hue } from '../../../hue/Hue'; +import { Events } from '../../../hue/HueEvents'; + +export default function sceneDelete(data: any, hue: Hue) { + const scene = hue.scenes.cache.get(data.id); + if (!scene) return; + + const clone = scene._clone(); + + hue.scenes.cache.delete(data.id); + + return () => hue.emit(Events.SceneDelete, clone); +} diff --git a/src/connections/events/delete/ZigbeeConnectivityDelete.ts b/src/connections/events/delete/ZigbeeConnectivityDelete.ts new file mode 100644 index 0000000..b0a86e0 --- /dev/null +++ b/src/connections/events/delete/ZigbeeConnectivityDelete.ts @@ -0,0 +1,13 @@ +import { Hue } from '../../../hue/Hue'; +import { Events } from '../../../hue/HueEvents'; + +export default function zigbeeConnectivityDelete(data: any, hue: Hue) { + const zigbeeConnectivity = hue.zigbeeConnectivities.cache.get(data.id); + if (!zigbeeConnectivity) return; + + const clone = zigbeeConnectivity._clone(); + + hue.zigbeeConnectivities.cache.delete(data.id); + + return () => hue.emit(Events.ZigbeeConnectivityDelete, clone); +} diff --git a/src/connections/events/delete/ZigbeeDeviceDiscoveryDelete.ts b/src/connections/events/delete/ZigbeeDeviceDiscoveryDelete.ts new file mode 100644 index 0000000..1d43ade --- /dev/null +++ b/src/connections/events/delete/ZigbeeDeviceDiscoveryDelete.ts @@ -0,0 +1,13 @@ +import { Hue } from '../../../hue/Hue'; +import { Events } from '../../../hue/HueEvents'; + +export default function zigbeeDeviceDiscoveryDelete(data: any, hue: Hue) { + const zigbeeDeviceDiscovery = hue.zigbeeDeviceDiscoveries.cache.get(data.id); + if (!zigbeeDeviceDiscovery) return; + + const clone = zigbeeDeviceDiscovery._clone(); + + hue.zigbeeDeviceDiscoveries.cache.delete(data.id); + + return () => hue.emit(Events.ZigbeeDeviceDiscoveryUpdate, clone); +} diff --git a/src/connections/events/delete/ZoneDelete.ts b/src/connections/events/delete/ZoneDelete.ts new file mode 100644 index 0000000..f60fd08 --- /dev/null +++ b/src/connections/events/delete/ZoneDelete.ts @@ -0,0 +1,13 @@ +import { Hue } from '../../../hue/Hue'; +import { Events } from '../../../hue/HueEvents'; + +export default function zoneDelete(data: any, hue: Hue) { + const zone = hue.zones.cache.get(data.id); + if (!zone) return; + + const clone = zone._clone(); + + hue.zones.cache.delete(data.id); + + return () => hue.emit(Events.ZoneDelete, clone); +} diff --git a/src/connections/events/index.ts b/src/connections/events/index.ts new file mode 100644 index 0000000..b8f135e --- /dev/null +++ b/src/connections/events/index.ts @@ -0,0 +1,77 @@ +import { ResourceType } from '../../api/ResourceType'; +import deviceAdd from './add/DeviceAdd'; +import devicePowerAdd from './add/DevicePowerAdd'; +import groupedLightAdd from './add/GroupedLightAdd'; +import lightAdd from './add/LightAdd'; +import motionAdd from './add/MotionAdd'; +import roomAdd from './add/RoomAdd'; +import sceneAdd from './add/SceneAdd'; +import zoneAdd from './add/ZoneAdd'; +import deviceDelete from './delete/DeviceDelete'; +import devicePowerDelete from './delete/DevicePowerDelete'; +import groupedLightDelete from './delete/GroupedLightDelete'; +import lightDelete from './delete/LightDelete'; +import motionDelete from './delete/MotionDelete'; +import roomDelete from './delete/RoomDelete'; +import sceneDelete from './delete/SceneDelete'; +import zoneDelete from './delete/ZoneDelete'; +import deviceUpdate from './update/DeviceUpdate'; +import devicePowerUpdate from './update/DevicePowerUpdate'; +import groupedLightUpdate from './update/GroupedLightUpdate'; +import lightUpdate from './update/LightUpdate'; +import motionUpdate from './update/MotionUpdate'; +import roomUpdate from './update/RoomUpdate'; +import sceneUpdate from './update/SceneUpdate'; +import zoneUpdate from './update/ZoneUpdate'; +import { Hue } from '../../hue/Hue'; +import zigbeeConnectivityAdd from './add/ZigbeeConnectivityAdd'; +import zigbeeConnectivityDelete from './delete/ZigbeeConnectivityDelete'; +import zigbeeConnectivityUpdate from './update/ZigbeeConnectivityUpdate'; +import zigbeeDeviceDiscoveryAdd from './add/ZigbeeDeviceDiscoveryAdd'; +import zigbeeDeviceDiscoveryDelete from './delete/ZigbeeDeviceDiscoveryDelete'; +import zigbeeDeviceDiscoveryUpdate from './update/ZigbeeDeviceDiscoveryUpdate'; +import bridgeAdd from './add/BridgeAdd'; +import bridgeDelete from './delete/BridgeDelete'; +import bridgeUpdate from './update/BridgeUpdate'; + +export const RESOURCE_ADD: { [key: string]: (data: any, hue: Hue) => (() => boolean) | undefined } = { + [ResourceType.Device]: deviceAdd, + [ResourceType.DevicePower]: devicePowerAdd, + [ResourceType.GroupedLight]: groupedLightAdd, + [ResourceType.Light]: lightAdd, + [ResourceType.Motion]: motionAdd, + [ResourceType.Room]: roomAdd, + [ResourceType.Scene]: sceneAdd, + [ResourceType.Zone]: zoneAdd, + [ResourceType.ZigbeeConnectivity]: zigbeeConnectivityAdd, + [ResourceType.ZigbeeDeviceDiscovery]: zigbeeDeviceDiscoveryAdd, + [ResourceType.Bridge]: bridgeAdd, +}; + +export const RESOURCE_DELETE: { [key: string]: (data: any, hue: Hue) => (() => boolean) | undefined } = { + [ResourceType.Device]: deviceDelete, + [ResourceType.DevicePower]: devicePowerDelete, + [ResourceType.GroupedLight]: groupedLightDelete, + [ResourceType.Light]: lightDelete, + [ResourceType.Motion]: motionDelete, + [ResourceType.Room]: roomDelete, + [ResourceType.Scene]: sceneDelete, + [ResourceType.Zone]: zoneDelete, + [ResourceType.ZigbeeConnectivity]: zigbeeConnectivityDelete, + [ResourceType.ZigbeeDeviceDiscovery]: zigbeeDeviceDiscoveryDelete, + [ResourceType.Bridge]: bridgeDelete, +}; + +export const RESOURCE_UPDATE: { [key: string]: (data: any, hue: Hue) => (() => boolean) | undefined } = { + [ResourceType.Device]: deviceUpdate, + [ResourceType.DevicePower]: devicePowerUpdate, + [ResourceType.GroupedLight]: groupedLightUpdate, + [ResourceType.Light]: lightUpdate, + [ResourceType.Motion]: motionUpdate, + [ResourceType.Room]: roomUpdate, + [ResourceType.Scene]: sceneUpdate, + [ResourceType.Zone]: zoneUpdate, + [ResourceType.ZigbeeConnectivity]: zigbeeConnectivityUpdate, + [ResourceType.ZigbeeDeviceDiscovery]: zigbeeDeviceDiscoveryUpdate, + [ResourceType.Bridge]: bridgeUpdate, +}; diff --git a/src/connections/events/update/BridgeUpdate.ts b/src/connections/events/update/BridgeUpdate.ts new file mode 100644 index 0000000..3f5d2bf --- /dev/null +++ b/src/connections/events/update/BridgeUpdate.ts @@ -0,0 +1,11 @@ +import { Hue } from '../../../hue/Hue'; +import { Events } from '../../../hue/HueEvents'; + +export default function bridgeUpdate(data: any, hue: Hue) { + const bridge = hue.bridges.cache.get(data.id); + if (!bridge) return; + + const clone = bridge._update(data); + + return () => hue.emit(Events.BridgeUpdate, bridge, clone); +} diff --git a/src/connections/events/update/DevicePowerUpdate.ts b/src/connections/events/update/DevicePowerUpdate.ts new file mode 100644 index 0000000..e3b1c2b --- /dev/null +++ b/src/connections/events/update/DevicePowerUpdate.ts @@ -0,0 +1,11 @@ +import { Hue } from '../../../hue/Hue'; +import { Events } from '../../../hue/HueEvents'; + +export default function devicePowerUpdate(data: any, hue: Hue) { + const devicePower = hue.devicePowers.cache.get(data.id); + if (!devicePower) return; + + const clone = devicePower._update(data); + + return () => hue.emit(Events.DevicePowerUpdate, devicePower, clone); +} diff --git a/src/connections/events/update/DeviceUpdate.ts b/src/connections/events/update/DeviceUpdate.ts new file mode 100644 index 0000000..c1affb2 --- /dev/null +++ b/src/connections/events/update/DeviceUpdate.ts @@ -0,0 +1,11 @@ +import { Hue } from '../../../hue/Hue'; +import { Events } from '../../../hue/HueEvents'; + +export default function deviceUpdate(data: any, hue: Hue) { + const device = hue.devices.cache.get(data.id); + if (!device) return; + + const clone = device._update(data); + + return () => hue.emit(Events.DeviceUpdate, device, clone); +} diff --git a/src/connections/events/update/GroupedLightUpdate.ts b/src/connections/events/update/GroupedLightUpdate.ts new file mode 100644 index 0000000..f057818 --- /dev/null +++ b/src/connections/events/update/GroupedLightUpdate.ts @@ -0,0 +1,11 @@ +import { Hue } from '../../../hue/Hue'; +import { Events } from '../../../hue/HueEvents'; + +export default function groupedLightUpdate(data: any, hue: Hue) { + const groupedLight = hue.groupedLights.cache.get(data.id); + if (!groupedLight) return; + + const clone = groupedLight._update(data); + + return () => hue.emit(Events.GroupedLightUpdate, groupedLight, clone); +} diff --git a/src/connections/events/update/LightUpdate.ts b/src/connections/events/update/LightUpdate.ts new file mode 100644 index 0000000..f8e256c --- /dev/null +++ b/src/connections/events/update/LightUpdate.ts @@ -0,0 +1,11 @@ +import { Hue } from '../../../hue/Hue'; +import { Events } from '../../../hue/HueEvents'; + +export default function lightUpdate(data: any, hue: Hue) { + const light = hue.lights.cache.get(data.id); + if (!light) return; + + const clone = light._update(data); + + return () => hue.emit(Events.LightUpdate, light, clone); +} diff --git a/src/connections/events/update/MotionUpdate.ts b/src/connections/events/update/MotionUpdate.ts new file mode 100644 index 0000000..a701387 --- /dev/null +++ b/src/connections/events/update/MotionUpdate.ts @@ -0,0 +1,11 @@ +import { Hue } from '../../../hue/Hue'; +import { Events } from '../../../hue/HueEvents'; + +export default function motionUpdate(data: any, hue: Hue) { + const motion = hue.motions.cache.get(data.id); + if (!motion) return; + + const clone = motion._update(data); + + return () => hue.emit(Events.MotionUpdate, motion, clone); +} diff --git a/src/connections/events/update/RoomUpdate.ts b/src/connections/events/update/RoomUpdate.ts new file mode 100644 index 0000000..4be7f4b --- /dev/null +++ b/src/connections/events/update/RoomUpdate.ts @@ -0,0 +1,11 @@ +import { Hue } from '../../../hue/Hue'; +import { Events } from '../../../hue/HueEvents'; + +export default function roomUpdate(data: any, hue: Hue) { + const room = hue.rooms.cache.get(data.id); + if (!room) return; + + const clone = room._update(data); + + return () => hue.emit(Events.RoomUpdate, room, clone); +} diff --git a/src/connections/events/update/SceneUpdate.ts b/src/connections/events/update/SceneUpdate.ts new file mode 100644 index 0000000..10652ac --- /dev/null +++ b/src/connections/events/update/SceneUpdate.ts @@ -0,0 +1,11 @@ +import { Hue } from '../../../hue/Hue'; +import { Events } from '../../../hue/HueEvents'; + +export default function sceneUpdate(data: any, hue: Hue) { + const scene = hue.scenes.cache.get(data.id); + if (!scene) return; + + const clone = scene._update(data); + + return () => hue.emit(Events.SceneUpdate, scene, clone); +} diff --git a/src/connections/events/update/ZigbeeConnectivityUpdate.ts b/src/connections/events/update/ZigbeeConnectivityUpdate.ts new file mode 100644 index 0000000..212a73f --- /dev/null +++ b/src/connections/events/update/ZigbeeConnectivityUpdate.ts @@ -0,0 +1,11 @@ +import { Hue } from '../../../hue/Hue'; +import { Events } from '../../../hue/HueEvents'; + +export default function zigbeeConnectivityUpdate(data: any, hue: Hue) { + const zigbeeConnectivity = hue.zigbeeConnectivities.cache.get(data.id); + if (!zigbeeConnectivity) return; + + const clone = zigbeeConnectivity._update(data); + + return () => hue.emit(Events.ZigbeeConnectivityUpdate, zigbeeConnectivity, clone); +} diff --git a/src/connections/events/update/ZigbeeDeviceDiscoveryUpdate.ts b/src/connections/events/update/ZigbeeDeviceDiscoveryUpdate.ts new file mode 100644 index 0000000..52df37a --- /dev/null +++ b/src/connections/events/update/ZigbeeDeviceDiscoveryUpdate.ts @@ -0,0 +1,11 @@ +import { Hue } from '../../../hue/Hue'; +import { Events } from '../../../hue/HueEvents'; + +export default function zigbeeDeviceDiscoveryUpdate(data: any, hue: Hue) { + const zigbeeDeviceDiscovery = hue.zigbeeDeviceDiscoveries.cache.get(data.id); + if (!zigbeeDeviceDiscovery) return; + + const clone = zigbeeDeviceDiscovery._update(data); + + return () => hue.emit(Events.ZigbeeDeviceDiscoveryUpdate, zigbeeDeviceDiscovery, clone); +} diff --git a/src/connections/events/update/ZoneUpdate.ts b/src/connections/events/update/ZoneUpdate.ts new file mode 100644 index 0000000..b4e8469 --- /dev/null +++ b/src/connections/events/update/ZoneUpdate.ts @@ -0,0 +1,11 @@ +import { Hue } from '../../../hue/Hue'; +import { Events } from '../../../hue/HueEvents'; + +export default function zoneUpdate(data: any, hue: Hue) { + const zone = hue.zones.cache.get(data.id); + if (!zone) return; + + const clone = zone._update(data); + + return () => hue.emit(Events.ZoneUpdate, zone, clone); +} diff --git a/src/hue/Hue.ts b/src/hue/Hue.ts new file mode 100644 index 0000000..5f0bc19 --- /dev/null +++ b/src/hue/Hue.ts @@ -0,0 +1,109 @@ +import { EventEmitter } from 'node:events'; +import { Events, HueEvents } from './HueEvents'; +import { Rest } from '../connections/Rest'; +import { Sse } from '../connections/Sse'; +import { ResourceType } from '../api/ResourceType'; +import { NarrowResource } from '../structures/Resource'; +import { LightManager } from '../managers/LightManager'; +import { DeviceManager } from '../managers/DeviceManager'; +import { RoomManager } from '../managers/RoomManager'; +import { ZoneManager } from '../managers/ZoneManager'; +import { DevicePowerManager } from '../managers/DevicePowerManager'; +import { GroupedLightManager } from '../managers/GroupedLightManager'; +import { SceneManager } from '../managers/SceneManager'; +import { MotionManager } from '../managers/MotionManager'; +import { ZigbeeConnectivityManager } from '../managers/ZigbeeConnectivityManager'; +import { ZigbeeDeviceDiscoveryManager } from '../managers/ZigbeeDeviceDiscoveryManager'; +import { BridgeManager } from '../managers/BridgeManager'; + +export const CA = + '-----BEGIN CERTIFICATE-----\n' + + 'MIICMjCCAdigAwIBAgIUO7FSLbaxikuXAljzVaurLXWmFw4wCgYIKoZIzj0EAwIw\n' + + 'OTELMAkGA1UEBhMCTkwxFDASBgNVBAoMC1BoaWxpcHMgSHVlMRQwEgYDVQQDDAty\n' + + 'b290LWJyaWRnZTAiGA8yMDE3MDEwMTAwMDAwMFoYDzIwMzgwMTE5MDMxNDA3WjA5\n' + + 'MQswCQYDVQQGEwJOTDEUMBIGA1UECgwLUGhpbGlwcyBIdWUxFDASBgNVBAMMC3Jv\n' + + 'b3QtYnJpZGdlMFkwEwYHKoZIzj0CAQYIKoZIzj0DAQcDQgAEjNw2tx2AplOf9x86\n' + + 'aTdvEcL1FU65QDxziKvBpW9XXSIcibAeQiKxegpq8Exbr9v6LBnYbna2VcaK0G22\n' + + 'jOKkTqOBuTCBtjAPBgNVHRMBAf8EBTADAQH/MA4GA1UdDwEB/wQEAwIBhjAdBgNV\n' + + 'HQ4EFgQUZ2ONTFrDT6o8ItRnKfqWKnHFGmQwdAYDVR0jBG0wa4AUZ2ONTFrDT6o8\n' + + 'ItRnKfqWKnHFGmShPaQ7MDkxCzAJBgNVBAYTAk5MMRQwEgYDVQQKDAtQaGlsaXBz\n' + + 'IEh1ZTEUMBIGA1UEAwwLcm9vdC1icmlkZ2WCFDuxUi22sYpLlwJY81Wrqy11phcO\n' + + 'MAoGCCqGSM49BAMCA0gAMEUCIEBYYEOsa07TH7E5MJnGw557lVkORgit2Rm1h3B2\n' + + 'sFgDAiEA1Fj/C3AN5psFMjo0//mrQebo0eKd3aWRx+pQY08mk48=\n' + + '-----END CERTIFICATE-----'; + +export interface HueConnectionOptions { + applicationKey: string; +} + +export interface LocalHueConnectionsOptions extends HueConnectionOptions { + ip: string; +} + +export interface ExternalHueConnectionOptions extends HueConnectionOptions { + accessToken: string; +} + +export interface HueOptions { + connection: LocalHueConnectionsOptions | ExternalHueConnectionOptions; +} + +export interface Hue { + on: (event: T, listener: (...args: HueEvents[T]) => any) => this; + once: (event: T, listener: (...args: HueEvents[T]) => any) => this; + off: (event: T, listener: (...args: HueEvents[T]) => any) => this; + removeAllListeners: (event?: T) => this; +} + +export class Hue extends EventEmitter { + public readonly options: HueOptions; + public readonly lights = new LightManager(this); + public readonly devices = new DeviceManager(this); + public readonly rooms = new RoomManager(this); + public readonly zones = new ZoneManager(this); + public readonly groupedLights = new GroupedLightManager(this); + public readonly devicePowers = new DevicePowerManager(this); + public readonly scenes = new SceneManager(this); + public readonly motions = new MotionManager(this); + public readonly zigbeeConnectivities = new ZigbeeConnectivityManager(this); + public readonly zigbeeDeviceDiscoveries = new ZigbeeDeviceDiscoveryManager(this); + public readonly bridges = new BridgeManager(this); + public readonly _rest = new Rest(this); + public readonly _sse = new Sse(this); + + public constructor(options: HueOptions) { + super(); + this.options = options; + } + + get _url(): string { + if ('ip' in this.options.connection) return `https://${this.options.connection.ip}:443`; + else return `https://api.meethue.com/route`; + } + + public async connect(): Promise { + const data = await this._rest.get('/resource'); + + for (const resource of data) { + this._create(resource); + } + + await this._sse.connect(); + + this.emit(Events.Ready, this); + } + + public _create(data: any): NarrowResource | undefined { + if (data.type === ResourceType.Light) return this.lights._add(data); + else if (data.type === ResourceType.Device) return this.devices._add(data); + else if (data.type === ResourceType.Room) return this.rooms._add(data); + else if (data.type === ResourceType.Zone) return this.zones._add(data); + else if (data.type === ResourceType.GroupedLight) return this.groupedLights._add(data); + else if (data.type === ResourceType.DevicePower) return this.devicePowers._add(data); + else if (data.type === ResourceType.Scene) return this.scenes._add(data); + else if (data.type === ResourceType.Motion) return this.motions._add(data); + else if (data.type === ResourceType.ZigbeeConnectivity) return this.zigbeeConnectivities._add(data); + else if (data.type === ResourceType.ZigbeeDeviceDiscovery) return this.zigbeeDeviceDiscoveries._add(data); + else if (data.type === ResourceType.Bridge) return this.bridges._add(data); + } +} diff --git a/src/bridge/BridgeEvents.ts b/src/hue/HueEvents.ts similarity index 51% rename from src/bridge/BridgeEvents.ts rename to src/hue/HueEvents.ts index 4aab7c4..8b30307 100644 --- a/src/bridge/BridgeEvents.ts +++ b/src/hue/HueEvents.ts @@ -1,4 +1,4 @@ -import { Bridge } from './Bridge'; +import { Hue } from './Hue'; import { Request, Response } from '../connections/Rest'; import { Light } from '../structures/Light'; import { NarrowResource } from '../structures/Resource'; @@ -7,6 +7,11 @@ import { Room } from '../structures/Room'; import { Zone } from '../structures/Zone'; import { Scene } from '../structures/Scene'; import { GroupedLight } from '../structures/GroupedLight'; +import { DevicePower } from '../structures/DevicePower'; +import { Motion } from '../structures/Motion'; +import { ZigbeeConnectivity } from '../structures/ZigbeeConnectivity'; +import { ZigbeeDeviceDiscovery } from '../structures/ZigbeeDeviceDiscovery'; +import { Bridge } from '../structures/Bridge'; export const Events = { Ready: 'ready', @@ -36,10 +41,25 @@ export const Events = { ZoneAdd: 'zoneAdd', ZoneUpdate: 'zoneUpdate', ZoneDelete: 'zoneDelete', + DevicePowerAdd: 'devicePowerAdd', + DevicePowerUpdate: 'devicePowerUpdate', + DevicePowerDelete: 'devicePowerDelete', + MotionAdd: 'motionAdd', + MotionUpdate: 'motionUpdate', + MotionDelete: 'motionDelete', + ZigbeeConnectivityAdd: 'zigbeeConnectivityAdd', + ZigbeeConnectivityUpdate: 'zigbeeConnectivityUpdate', + ZigbeeConnectivityDelete: 'zigbeeConnectivityDelete', + ZigbeeDeviceDiscoveryAdd: 'zigbeeDeviceDiscoveryAdd', + ZigbeeDeviceDiscoveryUpdate: 'zigbeeDeviceDiscoveryUpdate', + ZigbeeDeviceDiscoveryDelete: 'zigbeeDeviceDiscoveryDelete', + BridgeAdd: 'bridgeAdd', + BridgeUpdate: 'bridgeUpdate', + BridgeDelete: 'bridgeDelete', } as const; -export interface BridgeEvents { - [Events.Ready]: [bridge: Bridge]; +export interface HueEvents { + [Events.Ready]: [bridge: Hue]; [Events.Error]: [error: Error]; [Events.Raw]: [data: Record]; [Events.Debug]: [debug: string]; @@ -64,6 +84,27 @@ export interface BridgeEvents { [Events.SceneUpdate]: [newScene: Scene, oldScene: Scene]; [Events.SceneDelete]: [scene: Scene]; [Events.ZoneAdd]: [zone: Zone]; - [Events.ZoneUpdate]: [newZone: Room, oldZone: Zone]; + [Events.ZoneUpdate]: [newZone: Zone, oldZone: Zone]; [Events.ZoneDelete]: [zone: Zone]; + [Events.DevicePowerAdd]: [devicePower: DevicePower]; + [Events.DevicePowerUpdate]: [newDevicePower: DevicePower, oldDevicePower: DevicePower]; + [Events.DevicePowerDelete]: [devicePower: DevicePower]; + [Events.MotionAdd]: [motion: Motion]; + [Events.MotionUpdate]: [newMotion: Motion, oldMotion: Motion]; + [Events.MotionDelete]: [motion: Motion]; + [Events.ZigbeeConnectivityAdd]: [zigbeeConnectivity: ZigbeeConnectivity]; + [Events.ZigbeeConnectivityUpdate]: [ + newZigbeeConnectivity: ZigbeeConnectivity, + oldZigbeeConnectivity: ZigbeeConnectivity, + ]; + [Events.ZigbeeConnectivityDelete]: [zigbeeConnectivity: ZigbeeConnectivity]; + [Events.ZigbeeDeviceDiscoveryAdd]: [zigbeeDeviceDiscovery: ZigbeeDeviceDiscovery]; + [Events.ZigbeeDeviceDiscoveryUpdate]: [ + newZigbeeDeviceDiscovery: ZigbeeDeviceDiscovery, + oldZigbeeDeviceDiscovery: ZigbeeDeviceDiscovery, + ]; + [Events.ZigbeeDeviceDiscoveryDelete]: [zigbeeDeviceDiscovery: ZigbeeDeviceDiscovery]; + [Events.BridgeAdd]: [bridge: Bridge]; + [Events.BridgeUpdate]: [newBridge: Bridge, oldBridge: Bridge]; + [Events.BridgeDelete]: [bridge: Bridge]; } diff --git a/src/index.ts b/src/index.ts index d28093a..165f163 100644 --- a/src/index.ts +++ b/src/index.ts @@ -1,6 +1,6 @@ -// Bridge -export * from './bridge/Bridge'; -export * from './bridge/BridgeEvents'; +// Hue +export * from './hue/Hue'; +export * from './hue/HueEvents'; // Connections export * from './connections/Rest'; @@ -8,53 +8,40 @@ export * from './connections/Limit'; export * from './connections/Sse'; // Managers -export * from './managers/ResourceManager'; +export * from './managers/Manager'; +export * from './managers/LightManager'; +export * from './managers/DeviceManager'; +export * from './managers/RoomManager'; +export * from './managers/ZoneManager'; +export * from './managers/GroupedLightManager'; +export * from './managers/DevicePowerManager'; +export * from './managers/MotionManager'; +export * from './managers/SceneManager'; // Resources export * from './structures/Device'; -export * from './structures/DimmableLight'; +export * from './structures/DevicePower'; export * from './structures/GroupedLight'; export * from './structures/Light'; -export * from './structures/MirekLight'; export * from './structures/NamedResource'; export * from './structures/Resource'; export * from './structures/Room'; export * from './structures/Scene'; -export * from './structures/XyLight'; -export * from './structures/XysLight'; export * from './structures/Zone'; +export * from './structures/Motion'; // API -export * from './api/ApiResourceType'; +export * from './api/ResourceType'; export * from './api/ResourceIdentifier'; - -export * from './api/device/get'; -export * from './api/device/put'; -export * from './api/grouped_light/get'; -export * from './api/grouped_light/put'; -export * from './api/light/get'; -export * from './api/light/put'; -export * from './api/room/get'; -export * from './api/room/post'; -export * from './api/room/put'; -export * from './api/scene/get'; -export * from './api/scene/post'; -export * from './api/scene/put'; -export * from './api/zone/get'; -export * from './api/zone/post'; -export * from './api/zone/put'; +export * from './api/ArcheType'; // Util -export * from './util/util'; export * from './util/clone'; export * from './util/merge'; export * from './util/resourceIdentifier'; -export * from './util/color/xy'; -export * from './util/color/gamut'; -export * from './util/color/rgb'; -export * from './util/color/hex'; - -// TODO dimming_delta & color_temperature_delta -// TODO archetypes -// TODO delete resources +// Color +export * from './color/xy'; +export * from './color/gamut'; +export * from './color/rgb'; +export * from './color/hex'; diff --git a/src/managers/BridgeManager.ts b/src/managers/BridgeManager.ts new file mode 100644 index 0000000..dc35264 --- /dev/null +++ b/src/managers/BridgeManager.ts @@ -0,0 +1,8 @@ +import { Manager } from './Manager'; +import { ResourceType } from '../api/ResourceType'; +import { Bridge } from '../structures/Bridge'; + +export class BridgeManager extends Manager { + type = ResourceType.Bridge; + holds = Bridge; +} diff --git a/src/managers/DeviceManager.ts b/src/managers/DeviceManager.ts new file mode 100644 index 0000000..a0ad852 --- /dev/null +++ b/src/managers/DeviceManager.ts @@ -0,0 +1,23 @@ +import { Manager } from './Manager'; +import { ResourceType } from '../api/ResourceType'; +import { Device, DeviceEditOptions } from '../structures/Device'; +import { transformMetadataWithArcheType } from '../util/Transformers'; + +export class DeviceManager extends Manager { + type = ResourceType.Device; + holds = Device; + + public async identify(id: string): Promise { + await this._put(id, { identify: { action: 'identify' } }); + } + + public async edit(id: string, options: DeviceEditOptions): Promise { + await this._put(id, { + metadata: transformMetadataWithArcheType(options), + }); + } + + public async delete(id: string): Promise { + await this._delete(id); + } +} diff --git a/src/managers/DevicePowerManager.ts b/src/managers/DevicePowerManager.ts new file mode 100644 index 0000000..1504057 --- /dev/null +++ b/src/managers/DevicePowerManager.ts @@ -0,0 +1,8 @@ +import { Manager } from './Manager'; +import { ResourceType } from '../api/ResourceType'; +import { DevicePower } from '../structures/DevicePower'; + +export class DevicePowerManager extends Manager { + type = ResourceType.DevicePower; + holds = DevicePower; +} diff --git a/src/managers/GroupedLightManager.ts b/src/managers/GroupedLightManager.ts new file mode 100644 index 0000000..4a30fd0 --- /dev/null +++ b/src/managers/GroupedLightManager.ts @@ -0,0 +1,18 @@ +import { Manager } from './Manager'; +import { ResourceType } from '../api/ResourceType'; +import { GroupedLight, GroupedLightEditOptions } from '../structures/GroupedLight'; +import { transformColor, transformColorTemperature, transformDimming, transformOn } from '../util/Transformers'; + +export class GroupedLightManager extends Manager { + type = ResourceType.GroupedLight; + holds = GroupedLight; + + public async edit(id: string, options: GroupedLightEditOptions): Promise { + await this._put(id, { + on: transformOn(options.on), + dimming: transformDimming(options.brightness), + color_temperature: transformColorTemperature(options.mirek), + color: transformColor(options.color), + }); + } +} diff --git a/src/managers/LightManager.ts b/src/managers/LightManager.ts new file mode 100644 index 0000000..c1b4eea --- /dev/null +++ b/src/managers/LightManager.ts @@ -0,0 +1,33 @@ +import { Manager } from './Manager'; +import { ResourceType } from '../api/ResourceType'; +import { Light, LightEditOptions } from '../structures/Light'; +import { + transformColor, + transformColorTemperature, + transformDimming, + transformDynamics, + transformEffects, + transformGradient, + transformMetadataWithArcheType, + transformOn, + transformTimedEffects, +} from '../util/Transformers'; + +export class LightManager extends Manager { + type = ResourceType.Light; + holds = Light; + + public async edit(id: string, options: LightEditOptions): Promise { + await this._put(id, { + metadata: transformMetadataWithArcheType(options), + on: transformOn(options.on), + dynamics: transformDynamics(options.dynamics), + effects: transformEffects(options.effect), + timed_effects: transformTimedEffects(options.timedEffects), + dimming: transformDimming(options.brightness ?? options.color?.z), + color_temperature: transformColorTemperature(options.colorTemperature), + color: transformColor(options.color), + gradient: transformGradient(options.gradient), + }); + } +} diff --git a/src/managers/Manager.ts b/src/managers/Manager.ts new file mode 100644 index 0000000..6de4aa7 --- /dev/null +++ b/src/managers/Manager.ts @@ -0,0 +1,48 @@ +import { Collection } from '@discordjs/collection'; +import { ResourceType, ResourceTypeGet, ResourceTypePost, ResourceTypePut } from '../api/ResourceType'; +import { NarrowResource } from '../structures/Resource'; +import { ResourceIdentifier } from '../api/ResourceIdentifier'; +import { Hue } from '../hue/Hue'; + +export type By = string | ResourceIdentifier; + +export type Force = B extends true + ? NarrowResource + : NarrowResource | undefined; + +export type ResourceConstructorSignature = new (bridge: Hue, data: any) => NarrowResource; + +export abstract class Manager { + public readonly hue: Hue; + public readonly cache = new Collection>(); + public abstract type: ResourceType; + public abstract holds: ResourceConstructorSignature; + + public constructor(hue: Hue) { + this.hue = hue; + } + + public _add(data: any): NarrowResource { + const resource = new this.holds(this.hue, data); + + this.cache.set(data.id, resource); + + return resource; + } + + public async _get(id: string): Promise> { + return await this.hue._rest.get(`/resource/${this.type}/${id}`); + } + + public async _put(id: string, data: ResourceTypePut): Promise { + return await this.hue._rest.put(`/resource/${this.type}/${id}`, data); + } + + public async _post(data: ResourceTypePost): Promise { + return await this.hue._rest.post(`/resource/${this.type}`, data); + } + + public async _delete(id: string): Promise { + return await this.hue._rest.delete(`/resource/${this.type}/${id}`); + } +} diff --git a/src/managers/MotionManager.ts b/src/managers/MotionManager.ts new file mode 100644 index 0000000..65fe9d3 --- /dev/null +++ b/src/managers/MotionManager.ts @@ -0,0 +1,14 @@ +import { Manager } from './Manager'; +import { ResourceType } from '../api/ResourceType'; +import { Motion, MotionEditOptions } from '../structures/Motion'; + +export class MotionManager extends Manager { + type = ResourceType.Motion; + holds = Motion; + + public async edit(id: string, options: MotionEditOptions): Promise { + await this._put(id, { + enabled: options.enabled, + }); + } +} diff --git a/src/managers/ResourceManager.ts b/src/managers/ResourceManager.ts deleted file mode 100644 index 5e7b3cd..0000000 --- a/src/managers/ResourceManager.ts +++ /dev/null @@ -1,169 +0,0 @@ -import { Bridge } from '../bridge/Bridge'; -import { Collection } from '@discordjs/collection'; -import { NarrowResource } from '../structures/Resource'; -import { ApiResourceType, ApiResourceTypeGet } from '../api/ApiResourceType'; -import { Light, LightCapabilities, NarrowLight } from '../structures/Light'; -import { XyLight } from '../structures/XyLight'; -import { MirekLight } from '../structures/MirekLight'; -import { DimmableLight } from '../structures/DimmableLight'; -import { Scene } from '../structures/Scene'; -import { XysLight } from '../structures/XysLight'; -import { ResourceIdentifier } from '../api/ResourceIdentifier'; -import { Room } from '../structures/Room'; -import { Zone } from '../structures/Zone'; -import { Device } from '../structures/Device'; -import { GroupedLight } from '../structures/GroupedLight'; - -export const RESOURCES = { - [ApiResourceType.Light]: { - [LightCapabilities.None]: Light, - [LightCapabilities.Dimming]: DimmableLight, - [LightCapabilities.Mirek]: MirekLight, - [LightCapabilities.Xy]: XyLight, - [LightCapabilities.Xys]: XysLight, - }, - [ApiResourceType.Device]: Device, - [ApiResourceType.BridgeHome]: undefined, - [ApiResourceType.Room]: Room, - [ApiResourceType.Zone]: Zone, - [ApiResourceType.Button]: undefined, - [ApiResourceType.Temperature]: undefined, - [ApiResourceType.LightLevel]: undefined, - [ApiResourceType.Motion]: undefined, - [ApiResourceType.Entertainment]: undefined, - [ApiResourceType.GroupedLight]: GroupedLight, - [ApiResourceType.DevicePower]: undefined, - [ApiResourceType.ZigbeeBridgeConnectivity]: undefined, - [ApiResourceType.ZgpConnectivity]: undefined, - [ApiResourceType.Bridge]: undefined, - [ApiResourceType.Homekit]: undefined, - [ApiResourceType.Scene]: Scene, - [ApiResourceType.EntertainmentConfiguration]: undefined, - [ApiResourceType.PublicImage]: undefined, - [ApiResourceType.BehaviourScript]: undefined, - [ApiResourceType.BehaviourInstance]: undefined, - [ApiResourceType.Geofence]: undefined, - [ApiResourceType.GeofenceClient]: undefined, - [ApiResourceType.Geolocation]: undefined, -}; - -export type Resolvable = - | NarrowResource - | string - | ResourceIdentifier; - -export interface ResolveOptions< - T extends ApiResourceType = ApiResourceType, - L extends LightCapabilities | undefined = undefined, -> { - type?: T; - light?: { - capableOf?: L; - }; - force?: boolean; -} - -export type Resolved = T extends ApiResourceType.Light - ? NarrowLight - : NarrowResource; - -export class ResourceManager { - public readonly bridge: Bridge; - public readonly cache = new Collection(); - - constructor(bridge: Bridge) { - this.bridge = bridge; - } - - public getById(id?: string, options?: { force: true; type?: T }): NarrowResource; - public getById( - id?: string, - options?: { force?: boolean; type?: T }, - ): NarrowResource | undefined; - public getById(id?: string, options: { force?: boolean; type?: T } = {}) { - const resource = this.cache.get(id ?? ''); - - if (!resource && options.force) throw new Error(`Nonexistent ${options.type ?? 'unknown type'}: ${id}`); - if (resource && resource.type !== options.type) - throw new Error(`${resource.type}: ${id} rtype mismatch (requested: ${options.type}, actual: ${options.type})`); - - return resource; - } - - public getByIdentifier( - identifier?: ResourceIdentifier, - options?: { force: true; type?: U }, - ): NarrowResource; - public getByIdentifier( - identifier?: ResourceIdentifier, - options?: { force?: boolean; type?: U }, - ): NarrowResource | undefined; - public getByIdentifier( - identifier?: ResourceIdentifier, - options: { force?: boolean; type?: U } = {}, - ) { - return this.getById(identifier?.rid, { type: options.type ?? identifier?.rtype, force: options.force }); - } - - public getByIdentifiers(identifiers: ResourceIdentifier[]): NarrowResource[] { - return identifiers - .map((identifier) => this.getByIdentifier(identifier)) - .filter((resource) => resource !== null && resource !== undefined); - } - - public getByName(name?: string, options?: { force: true; type?: T }): NarrowResource; - public getByName( - name?: string, - options?: { force?: boolean; type?: T }, - ): NarrowResource | undefined; - public getByName(name?: string, options: { force?: boolean; type?: T } = {}) { - const resource = this.cache.find( - (resource) => 'name' in resource && resource.name === name && resource.type === options.type, - ); - - if (!resource && options.force) throw new Error(`Nonexistent ${name}`); - - return resource; - } - - public getIdentifierByName( - name?: string, - options?: { force: true; type?: T }, - ): ResourceIdentifier; - public getIdentifierByName( - name?: string, - options?: { force?: boolean; type?: T }, - ): ResourceIdentifier | undefined; - public getIdentifierByName(name?: string, options: { force?: boolean; type?: T } = {}) { - const resource = this.getByName(name, options); - - return resource?.identifier; - } - - public _create(data: ApiResourceTypeGet): NarrowResource | undefined { - const resourceClass = this._resolveResource(data); - if (!resourceClass) return; - - const resource = new resourceClass(this.bridge, data); - this.cache.set(resource.id, resource); - - return resource; - } - - private _resolveResource( - data: ApiResourceTypeGet, - ): (new (bridge: Bridge, data: any) => NarrowResource) | undefined { - if (data.type && data.type === ApiResourceType.Light) - return RESOURCES[ApiResourceType.Light][this._resolveLightCapabilities(data)]; - else if (data.type) return RESOURCES[data.type]; - } - - private _resolveLightCapabilities(data: ApiResourceTypeGet): LightCapabilities { - if (data.gradient) return LightCapabilities.Xys; - if (data.color) return LightCapabilities.Xy; - if (data.color_temperature) return LightCapabilities.Mirek; - if (data.dimming) return LightCapabilities.Dimming; - - return LightCapabilities.None; - } -} diff --git a/src/managers/RoomManager.ts b/src/managers/RoomManager.ts new file mode 100644 index 0000000..df001ec --- /dev/null +++ b/src/managers/RoomManager.ts @@ -0,0 +1,29 @@ +import { Manager } from './Manager'; +import { ResourceType } from '../api/ResourceType'; +import { Room, RoomCreateOptions, RoomEditOptions } from '../structures/Room'; +import { transformChildren, transformMetadataWithArcheType } from '../util/Transformers'; + +export class RoomManager extends Manager { + type = ResourceType.Room; + holds = Room; + + public async create(options: RoomCreateOptions): Promise { + const identifiers = await this._post({ + metadata: transformMetadataWithArcheType(options)!, + children: transformChildren(options.children)!, + }); + + return identifiers?.[0]?.rid; + } + + public async edit(id: string, options: RoomEditOptions): Promise { + await this._put(id, { + metadata: transformMetadataWithArcheType(options), + children: transformChildren(options.children), + }); + } + + public async delete(id: string): Promise { + await this._delete(id); + } +} diff --git a/src/managers/SceneManager.ts b/src/managers/SceneManager.ts new file mode 100644 index 0000000..6d23d55 --- /dev/null +++ b/src/managers/SceneManager.ts @@ -0,0 +1,36 @@ +import { Manager } from './Manager'; +import { ResourceType } from '../api/ResourceType'; +import { Scene, SceneCreateOptions, SceneEditOptions } from '../structures/Scene'; +import { transformMetadata, transformRecall, transformSceneActions } from '../util/Transformers'; +import { createResourceIdentifier } from '../util/resourceIdentifier'; +import { ifNotNull } from '../util/ifNotNull'; + +export class SceneManager extends Manager { + type = ResourceType.Scene; + holds = Scene; + + public async create(groupId: string, options: SceneCreateOptions): Promise { + const group = this.hue.zones.cache.get(groupId) ?? this.hue.rooms.cache.get(groupId); + if (!group) return; + + const identifiers = await this._post({ + group: createResourceIdentifier(groupId, group.type), + metadata: transformMetadata(options)!, + actions: transformSceneActions(options.actions)!, + }); + + return identifiers?.[0]?.rid; + } + public async edit(id: string, options: SceneEditOptions): Promise { + await this._put(id, { + metadata: transformMetadata(options), + speed: ifNotNull(options.speed, () => options.speed), + recall: transformRecall(options.recall), + actions: transformSceneActions(options.actions), + }); + } + + public async delete(id: string): Promise { + await this._delete(id); + } +} diff --git a/src/managers/ZigbeeConnectivityManager.ts b/src/managers/ZigbeeConnectivityManager.ts new file mode 100644 index 0000000..37d7d54 --- /dev/null +++ b/src/managers/ZigbeeConnectivityManager.ts @@ -0,0 +1,8 @@ +import { Manager } from './Manager'; +import { ResourceType } from '../api/ResourceType'; +import { ZigbeeConnectivity } from '../structures/ZigbeeConnectivity'; + +export class ZigbeeConnectivityManager extends Manager { + type = ResourceType.ZigbeeConnectivity; + holds = ZigbeeConnectivity; +} diff --git a/src/managers/ZigbeeDeviceDiscoveryManager.ts b/src/managers/ZigbeeDeviceDiscoveryManager.ts new file mode 100644 index 0000000..712ad81 --- /dev/null +++ b/src/managers/ZigbeeDeviceDiscoveryManager.ts @@ -0,0 +1,19 @@ +import { Manager } from './Manager'; +import { ResourceType } from '../api/ResourceType'; +import { + ZigbeeDeviceDiscovery, + ZigbeeDeviceDiscoveryActionType, + ZigbeeDeviceDiscoveryEdit, +} from '../structures/ZigbeeDeviceDiscovery'; +import { transformAction } from '../util/Transformers'; + +export class ZigbeeDeviceDiscoveryManager extends Manager { + type = ResourceType.ZigbeeDeviceDiscovery; + holds = ZigbeeDeviceDiscovery; + + public async edit(id: string, options: ZigbeeDeviceDiscoveryEdit) { + await this._put(id, { + action: transformAction({ ...options, actionType: ZigbeeDeviceDiscoveryActionType.Search }), + }); + } +} diff --git a/src/managers/ZoneManager.ts b/src/managers/ZoneManager.ts new file mode 100644 index 0000000..eaf0733 --- /dev/null +++ b/src/managers/ZoneManager.ts @@ -0,0 +1,29 @@ +import { Manager } from './Manager'; +import { ResourceType } from '../api/ResourceType'; +import { Zone, ZoneCreateOptions, ZoneEditOptions } from '../structures/Zone'; +import { transformChildren, transformMetadataWithArcheType } from '../util/Transformers'; + +export class ZoneManager extends Manager { + type = ResourceType.Zone; + holds = Zone; + + public async create(options: ZoneCreateOptions): Promise { + const identifiers = await this._post({ + metadata: transformMetadataWithArcheType(options)!, + children: transformChildren(options.children)!, + }); + + return identifiers?.[0]?.rid; + } + + public async edit(id: string, options: ZoneEditOptions): Promise { + await this._put(id, { + metadata: transformMetadataWithArcheType(options), + children: transformChildren(options.children), + }); + } + + public async delete(id: string): Promise { + await this._delete(id); + } +} diff --git a/src/structures/ArcheTypeResource.ts b/src/structures/ArcheTypeResource.ts new file mode 100644 index 0000000..cdacae9 --- /dev/null +++ b/src/structures/ArcheTypeResource.ts @@ -0,0 +1,29 @@ +import { NamedResource, NamedResourceEditOptions } from './NamedResource'; +import { ResourceType, ResourceTypeGet } from '../api/ResourceType'; +import { ArcheType } from '../api/ArcheType'; +import { Hue } from '../hue/Hue'; + +export interface ArcheTypeResourceEditOptions extends NamedResourceEditOptions { + archeType?: ArcheType; +} + +export type ArcheTypeResourceCreateOptions = Required; + +export abstract class ArcheTypeResource extends NamedResource { + public data: ResourceTypeGet & { metadata: { name: string; archetype: ArcheType } }; + + constructor(bridge: Hue, data: ResourceTypeGet & { metadata: { name: string; archetype: ArcheType } }) { + super(bridge, data); + this.data = data; + } + + get archeType(): ArcheType { + return this.data.metadata.archetype; + } + + public async setArcheType(archeType: ArcheType): Promise { + await this.edit({ archeType }); + } + + public abstract edit(options: { name?: string; archeType?: ArcheType }): Promise; +} diff --git a/src/structures/Bridge.ts b/src/structures/Bridge.ts new file mode 100644 index 0000000..60fee47 --- /dev/null +++ b/src/structures/Bridge.ts @@ -0,0 +1,23 @@ +import { Resource } from './Resource'; +import { ResourceType } from '../api/ResourceType'; +import { BridgeManager } from '../managers/BridgeManager'; + +export class Bridge extends Resource { + type = ResourceType.Bridge; + + get manager(): BridgeManager { + return this.hue.bridges; + } + + get ownerId(): string { + return this.data.owner.rid; + } + + get bridgeId(): string { + return this.data.bridge_id; + } + + get timeZone(): string { + return this.data.time_zone.time_zone; + } +} diff --git a/src/structures/Device.ts b/src/structures/Device.ts index ee2596d..3e407f9 100644 --- a/src/structures/Device.ts +++ b/src/structures/Device.ts @@ -1,14 +1,15 @@ -import { NamedResource } from './NamedResource'; -import { ApiResourceType } from '../api/ApiResourceType'; -import { ResourceIdentifier } from '../api/ResourceIdentifier'; -import { NarrowResource } from './Resource'; +import { ResourceType } from '../api/ResourceType'; +import { ArcheTypeResource, ArcheTypeResourceEditOptions } from './ArcheTypeResource'; +import { DeviceManager } from '../managers/DeviceManager'; -export interface DeviceEditOptions { - name: string; -} +export type DeviceEditOptions = ArcheTypeResourceEditOptions; + +export class Device extends ArcheTypeResource { + type = ResourceType.Device; -export class Device extends NamedResource { - type = ApiResourceType.Device; + get manager(): DeviceManager { + return this.hue.devices; + } get modelId(): string { return this.data.product_data.model_id; @@ -34,19 +35,19 @@ export class Device extends NamedResource { return this.data.product_data.hardware_platform_type; } - get services(): NarrowResource[] { - return this.bridge.resources.getByIdentifiers(this.serviceIdentifiers); - } - - get serviceIdentifiers(): ResourceIdentifier[] { - return this.data.services; + get serviceIds(): string[] { + return this.data.services.map((service) => service.rid); } public async identify(): Promise { - await this._put({ identify: { action: 'identify' } }); + await this.manager.identify(this.id); } public async edit(options: DeviceEditOptions): Promise { - await this._put({ metadata: options.name ? { name: options.name } : undefined }); + await this.manager.edit(this.id, options); + } + + public async delete(): Promise { + await this.manager.delete(this.id); } } diff --git a/src/structures/DevicePower.ts b/src/structures/DevicePower.ts new file mode 100644 index 0000000..f2d08e6 --- /dev/null +++ b/src/structures/DevicePower.ts @@ -0,0 +1,28 @@ +import { Resource } from './Resource'; +import { ResourceType } from '../api/ResourceType'; +import { DevicePowerManager } from '../managers/DevicePowerManager'; + +export enum DevicePowerBatteryState { + Normal = 'normal', + Low = 'low', + Critical = 'critical', +} + +export class DevicePower extends Resource { + type = ResourceType.DevicePower; + + get manager(): DevicePowerManager { + return this.hue.devicePowers; + } + get ownerId(): string { + return this.data.owner.rid; + } + + get batteryState(): DevicePowerBatteryState { + return this.data.power_state.battery_state as DevicePowerBatteryState; + } + + get batteryLevel(): number { + return this.data.power_state.battery_level; + } +} diff --git a/src/structures/DimmableLight.ts b/src/structures/DimmableLight.ts deleted file mode 100644 index e939488..0000000 --- a/src/structures/DimmableLight.ts +++ /dev/null @@ -1,32 +0,0 @@ -import { Light, LightCapabilities, LightEditOptions } from './Light'; -import { ApiResourceType, ApiResourceTypePut } from '../api/ApiResourceType'; - -export interface DimmableLightEditOptions extends LightEditOptions { - brightness?: number; -} - -export class DimmableLight extends Light { - public capabilities = LightCapabilities.Dimming; - - get brightness(): number { - return this.data.dimming!.brightness; - } - - get minBrightnessLevel(): number | undefined { - return this.data.dimming!.min_dim_level; - } - - public async setBrightness(brightness: DimmableLightEditOptions['brightness'], duration?: number): Promise { - await this.edit({ brightness, dynamics: { duration } }); - } - - public async edit( - options: DimmableLightEditOptions, - _inject?: ApiResourceTypePut, - ): Promise { - await super.edit(options, { - dimming: { brightness: options.brightness }, - ..._inject, - }); - } -} diff --git a/src/structures/GroupedLight.ts b/src/structures/GroupedLight.ts index 64a4231..eaceda0 100644 --- a/src/structures/GroupedLight.ts +++ b/src/structures/GroupedLight.ts @@ -1,24 +1,24 @@ -import { NarrowResource, Resource } from './Resource'; -import { ApiResourceType } from '../api/ApiResourceType'; -import { ResourceIdentifier } from '../api/ResourceIdentifier'; -import { XyPoint } from '../util/color/xy'; +import { Resource } from './Resource'; +import { ResourceType } from '../api/ResourceType'; +import { XyPoint } from '../color/xy'; +import { GroupedLightManager } from '../managers/GroupedLightManager'; export interface GroupedLightEditOptions { on?: boolean; brightness?: number; mirek?: number; - xy?: XyPoint; + color?: XyPoint; } -export class GroupedLight extends Resource { - type = ApiResourceType.GroupedLight; +export class GroupedLight extends Resource { + type = ResourceType.GroupedLight; - get owner(): NarrowResource { - return this.bridge.resources.getByIdentifier(this.ownerIdentifier); + get manager(): GroupedLightManager { + return this.hue.groupedLights; } - get ownerIdentifier(): ResourceIdentifier { - return this.data.owner; + get ownerId(): string { + return this.data.owner.rid; } public isOn(): boolean | undefined { @@ -49,18 +49,11 @@ export class GroupedLight extends Resource { await this.edit({ mirek }); } - public async setXy(xy: GroupedLightEditOptions['xy']): Promise { - await this.edit({ xy }); + public async setColor(color: GroupedLightEditOptions['color']): Promise { + await this.edit({ color }); } public async edit(options: GroupedLightEditOptions): Promise { - await this._put({ - on: { on: options.on ?? true }, - dimming: options.brightness ? { brightness: options.brightness } : undefined, - color_temperature: { - mirek: options.mirek, - }, - color: { xy: options.xy ? { x: options.xy.x, y: options.xy.y } : undefined }, - }); + await this.manager.edit(this.id, options); } } diff --git a/src/structures/Light.ts b/src/structures/Light.ts index 3c21424..819b63e 100644 --- a/src/structures/Light.ts +++ b/src/structures/Light.ts @@ -1,9 +1,8 @@ -import { NamedResource } from './NamedResource'; -import { ApiResourceType, ApiResourceTypePut } from '../api/ApiResourceType'; -import { DimmableLight } from './DimmableLight'; -import { MirekLight } from './MirekLight'; -import { XyLight } from './XyLight'; -import { XysLight } from './XysLight'; +import { ResourceType } from '../api/ResourceType'; +import { ArcheTypeResource, ArcheTypeResourceEditOptions } from './ArcheTypeResource'; +import { LightManager } from '../managers/LightManager'; +import { checkXyInReach, createXy, getClosestXy, XyPoint } from '../color/xy'; +import { createGamut, Gamut, resolveGamutByType } from '../color/gamut'; export enum LightCapabilities { None = 'none', @@ -13,78 +12,133 @@ export enum LightCapabilities { Xys = 'xys', } -export interface Lights { - [LightCapabilities.None]: Light | DimmableLight | MirekLight | XyLight | XysLight; - [LightCapabilities.Dimming]: DimmableLight | MirekLight | XyLight | XysLight; - [LightCapabilities.Mirek]: MirekLight | XyLight | XysLight; - [LightCapabilities.Xy]: XyLight | XysLight; - [LightCapabilities.Xys]: XysLight; +export interface LightIsCapableOfDimming { + brightness: number; + minBrightnessLevel: number; } -export type NarrowLight = Lights[T]; +export interface LightIsCapableOfColorTemperature extends LightIsCapableOfDimming { + colorTemperature: number; + minColorTemperature: number; + maxColorTemperature: number; +} -export interface LightEditOptions { - name?: string; - on?: boolean; - dynamics?: { - duration?: number; - speed?: number; - }; - effect?: 'fire' | 'candle' | 'no_effect'; - timedEffects?: { - effect?: 'sunrise' | 'no_effect'; - duration?: number; - }; +export interface LightIsCapableOfColor extends LightIsCapableOfColorTemperature { + color: XyPoint; + maxGamutRed: number; + maxGamutGreen: number; + maxGamutBlue: number; + gamut: Gamut; + colorInRange: (xy: XyPoint) => boolean; + colorToRange: (xy: XyPoint) => XyPoint; +} + +export interface LightIsCapableOfGradient extends LightIsCapableOfColor { + gradient: XyPoint[]; } -export interface LightStateOptions { +export interface LightEditOptions extends ArcheTypeResourceEditOptions { on?: boolean; dynamics?: { duration?: number; speed?: number; }; - effect?: 'fire' | 'candle' | 'no_effect'; + effect?: LightEffect; timedEffects?: { - effect?: 'sunrise' | 'no_effect'; + effect?: LightTimedEffect; duration?: number; }; + brightness?: number; + colorTemperature?: number; + color?: XyPoint; + gradient?: XyPoint[]; } -export class Light extends NamedResource { - public capabilities: LightCapabilities = LightCapabilities.None; - type = ApiResourceType.Light; +export enum LightEffect { + Fire = 'fire', + Candle = 'candle', + NoEffect = 'no_effect', +} + +export enum LightTimedEffect { + Sunrise = 'sunrise', + NoEffect = 'no_effect', +} + +export enum LightMode { + Normal = 'normal', + Streaming = 'streaming', +} + +// TODO add effects and timed_effects getters +// TODO dimming_delta & color_temperature_delta +export class Light extends ArcheTypeResource { + type = ResourceType.Light; + + get manager(): LightManager { + return this.hue.lights; + } public isOn(): boolean { return this.data.on.on; } - get mode(): 'normal' | 'streaming' { - return this.data.mode; + get brightness(): number | undefined { + return this.data.dimming?.brightness; } - public isCapableOf(capability: T): this is NarrowLight { - const order = [LightCapabilities.Dimming, LightCapabilities.Mirek, LightCapabilities.Xy, LightCapabilities.Xys]; + get minBrightnessLevel(): number | undefined { + return this.data.dimming?.min_dim_level; + } + + get colorTemperature(): number | undefined { + return this.data.color_temperature?.mirek; + } - const index = order.indexOf(capability); - const left = order.slice(index, order.length); + get minColorTemperature(): number | undefined { + return this.data.color_temperature?.mirek_schema?.mirek_minimum; + } + + get maxColorTemperature(): number | undefined { + return this.data.color_temperature?.mirek_schema?.mirek_maximum; + } - return left.includes(this.capabilities); + get color(): XyPoint | undefined { + return this.data.color + ? createXy(this.data.color.xy.x, this.data.color!.xy.y, this.data.dimming!.brightness) + : undefined; } - public isCapableOfDimming(): this is NarrowLight { - return this.isCapableOf(LightCapabilities.Dimming); + get maxGamutRed(): XyPoint | undefined { + return this.data.color + ? this.data.color.gamut?.red ?? resolveGamutByType(this.data.color.gamut_type).red + : undefined; } - public isCapableOfMirek(): this is NarrowLight { - return this.isCapableOf(LightCapabilities.Mirek); + get maxGamutGreen(): XyPoint | undefined { + return this.data.color + ? this.data.color.gamut?.green ?? resolveGamutByType(this.data.color.gamut_type).green + : undefined; } - public isCapableOfXy(): this is NarrowLight { - return this.isCapableOf(LightCapabilities.Xy); + get maxGamutBlue(): XyPoint | undefined { + return this.data.color + ? this.data.color.gamut?.blue ?? resolveGamutByType(this.data.color.gamut_type).blue + : undefined; } - public isCapableOfXys(): this is NarrowLight { - return this.isCapableOf(LightCapabilities.Xys); + get gamut(): Gamut | undefined { + return this.maxGamutRed && this.maxGamutGreen && this.maxGamutBlue + ? createGamut(this.maxGamutRed, this.maxGamutGreen, this.maxGamutBlue) + : undefined; + } + + get gradient(): XyPoint[] | undefined { + return this.data.gradient?.points?.map((point) => point.color.xy); + } + + get mode(): LightMode { + return this.data.mode as LightMode; } public async on(duration?: number): Promise { @@ -99,25 +153,55 @@ export class Light extends NamedResource { await this.edit({ on: !this.isOn(), dynamics: { duration } }); } - public async effect(effect: LightStateOptions['effect']): Promise { + public async effect(effect: LightEditOptions['effect']): Promise { await this.edit({ effect }); } - public async timedEffect(timedEffects: LightStateOptions['timedEffects']): Promise { + public async timedEffect(timedEffects: LightEditOptions['timedEffects']): Promise { await this.edit({ timedEffects }); } - public async edit(options: LightEditOptions, _inject?: ApiResourceTypePut): Promise { - await this._put({ - metadata: options.name ? { name: options.name } : undefined, - on: { on: options.on ?? true }, - dynamics: { duration: options.dynamics?.duration, speed: options.dynamics?.speed }, - effects: { effect: options.effect }, - timed_effects: { - effect: options.timedEffects?.effect, - duration: options.timedEffects?.duration, - }, - ..._inject, - }); + public async setBrightness(brightness: LightEditOptions['brightness'], duration?: number): Promise { + await this.edit({ brightness, on: true, dynamics: { duration } }); + } + + public async setMirek(mirek: LightEditOptions['colorTemperature'], duration?: number): Promise { + await this.edit({ colorTemperature: mirek, on: true, dynamics: { duration } }); + } + + public colorInRange(color: XyPoint): boolean | undefined { + return this.gamut ? checkXyInReach(color, this.gamut) : undefined; + } + + public colorToRange(color: XyPoint): XyPoint | undefined { + return this.gamut ? getClosestXy(color, this.gamut) : undefined; + } + + public async setColor(color: LightEditOptions['color'], duration?: number): Promise { + await this.edit({ color, on: true, dynamics: { duration } }); + } + + public async setGradient(gradient: LightEditOptions['gradient'], duration?: number): Promise { + await this.edit({ gradient, on: true, dynamics: { duration } }); + } + + public async edit(options: LightEditOptions): Promise { + await this.manager.edit(this.id, options); + } + + public isCapableOfDimming(): this is this & LightIsCapableOfDimming { + return typeof this.data.dimming != 'undefined'; + } + + public isCapableOfColorTemperature(): this is this & LightIsCapableOfColorTemperature { + return typeof this.data.color_temperature != 'undefined'; + } + + public isCapableOfColor(): this is this & LightIsCapableOfColor { + return typeof this.data.color != 'undefined'; + } + + public isCapableOfGradient(): this is this & LightIsCapableOfGradient { + return typeof this.data.gradient != 'undefined'; } } diff --git a/src/structures/MirekLight.ts b/src/structures/MirekLight.ts deleted file mode 100644 index dee7f86..0000000 --- a/src/structures/MirekLight.ts +++ /dev/null @@ -1,39 +0,0 @@ -import { DimmableLight, DimmableLightEditOptions } from './DimmableLight'; -import { LightCapabilities } from './Light'; -import { ApiResourceType, ApiResourceTypePut } from '../api/ApiResourceType'; - -export interface MirekLightEditOptions extends DimmableLightEditOptions { - mirek?: number; -} - -export class MirekLight extends DimmableLight { - public capabilities = LightCapabilities.Mirek; - - get mirek(): number { - return this.data.color_temperature!.mirek; - } - - get minMirek(): number { - return this.data.color_temperature!.mirek_schema.mirek_minimum; - } - - get maxMirek(): number { - return this.data.color_temperature!.mirek_schema.mirek_maximum; - } - - public async setMirek(mirek: MirekLightEditOptions['mirek'], duration?: number): Promise { - await this.edit({ mirek, dynamics: { duration } }); - } - - public async edit( - options: MirekLightEditOptions, - _inject?: ApiResourceTypePut, - ): Promise { - await super.edit(options, { - color_temperature: { - mirek: options.mirek, - }, - ..._inject, - }); - } -} diff --git a/src/structures/Motion.ts b/src/structures/Motion.ts new file mode 100644 index 0000000..208b29c --- /dev/null +++ b/src/structures/Motion.ts @@ -0,0 +1,39 @@ +import { Resource } from './Resource'; +import { ResourceType } from '../api/ResourceType'; +import { MotionManager } from '../managers/MotionManager'; + +export interface MotionEditOptions { + enabled?: boolean; +} + +export class Motion extends Resource { + type = ResourceType.Motion; + + get manager(): MotionManager { + return this.hue.motions; + } + + get enabled(): boolean { + return this.data.enabled; + } + + get motionDetected(): boolean { + return this.data.motion.motion; + } + + get motionValid(): boolean { + return this.data.motion.motion_valid; + } + + public async disable(): Promise { + return await this.edit({ enabled: false }); + } + + public async enable(): Promise { + return await this.edit({ enabled: true }); + } + + public async edit(options: MotionEditOptions): Promise { + await this.manager.edit(this.id, options); + } +} diff --git a/src/structures/NamedResource.ts b/src/structures/NamedResource.ts index c3f9a79..9250dc1 100644 --- a/src/structures/NamedResource.ts +++ b/src/structures/NamedResource.ts @@ -1,11 +1,18 @@ +import { ResourceType, ResourceTypeGet } from '../api/ResourceType'; +import { Hue } from '../hue/Hue'; +import { ArcheType } from '../api/ArcheType'; import { Resource } from './Resource'; -import { ApiResourceType, ApiResourceTypeGet } from '../api/ApiResourceType'; -import { Bridge } from '../bridge/Bridge'; -export abstract class NamedResource extends Resource { - public data: ApiResourceTypeGet & { metadata: { name: string } }; +export interface NamedResourceEditOptions { + name?: string; +} + +export type NamedResourceCreateOptions = Required; - constructor(bridge: Bridge, data: ApiResourceTypeGet & { metadata: { name: string } }) { +export abstract class NamedResource extends Resource { + public data: ResourceTypeGet & { metadata: { name: string; archetype: ArcheType } }; + + constructor(bridge: Hue, data: ResourceTypeGet & { metadata: { name: string; archetype: ArcheType } }) { super(bridge, data); this.data = data; } @@ -14,9 +21,17 @@ export abstract class NamedResource extends Resource< return this.data.metadata.name; } + get archeType(): ArcheType { + return this.data.metadata.archetype; + } + public async setName(name: string): Promise { await this.edit({ name }); } - public abstract edit(options: { name?: string }): Promise; + public async setArcheType(archeType: ArcheType): Promise { + await this.edit({ archeType }); + } + + public abstract edit(options: { name?: string; archeType?: ArcheType }): Promise; } diff --git a/src/structures/Resource.ts b/src/structures/Resource.ts index a359429..d7b6906 100644 --- a/src/structures/Resource.ts +++ b/src/structures/Resource.ts @@ -1,5 +1,5 @@ -import { Bridge } from '../bridge/Bridge'; -import { ApiResourceType, ApiResourceTypeGet, ApiResourceTypePut } from '../api/ApiResourceType'; +import { Hue } from '../hue/Hue'; +import { ResourceType, ResourceTypeGet } from '../api/ResourceType'; import { Light } from './Light'; import { clone } from '../util/clone'; import { merge } from '../util/merge'; @@ -10,44 +10,48 @@ import { Room } from './Room'; import { Zone } from './Zone'; import { Device } from './Device'; import { GroupedLight } from './GroupedLight'; +import { DevicePower } from './DevicePower'; +import { Motion } from './Motion'; +import { Manager } from '../managers/Manager'; +import { ZigbeeConnectivity } from './ZigbeeConnectivity'; +import { ZigbeeDeviceDiscovery } from './ZigbeeDeviceDiscovery'; +import { Bridge } from './Bridge'; export interface Resources { - [ApiResourceType.Device]: Device; - [ApiResourceType.BridgeHome]: Resource; - [ApiResourceType.Room]: Room; - [ApiResourceType.Zone]: Zone; - [ApiResourceType.Light]: Light; - [ApiResourceType.Button]: Resource; - [ApiResourceType.Temperature]: Resource; - [ApiResourceType.LightLevel]: Resource; - [ApiResourceType.Motion]: Resource; - [ApiResourceType.Entertainment]: Resource; - [ApiResourceType.GroupedLight]: GroupedLight; - [ApiResourceType.DevicePower]: Resource; - [ApiResourceType.ZigbeeBridgeConnectivity]: Resource; - [ApiResourceType.ZgpConnectivity]: Resource; - [ApiResourceType.Bridge]: Resource; - [ApiResourceType.Homekit]: Resource; - [ApiResourceType.Scene]: Scene; - [ApiResourceType.EntertainmentConfiguration]: Resource; - [ApiResourceType.PublicImage]: Resource; - [ApiResourceType.BehaviourScript]: Resource; - [ApiResourceType.BehaviourInstance]: Resource; - [ApiResourceType.Geofence]: Resource; - [ApiResourceType.GeofenceClient]: Resource; - [ApiResourceType.Geolocation]: Resource; + [ResourceType.Device]: Device; + [ResourceType.BridgeHome]: Resource; + [ResourceType.Room]: Room; + [ResourceType.Zone]: Zone; + [ResourceType.Light]: Light; + [ResourceType.Button]: Resource; + [ResourceType.Temperature]: Resource; + [ResourceType.LightLevel]: Resource; + [ResourceType.Motion]: Motion; + [ResourceType.Entertainment]: Resource; + [ResourceType.GroupedLight]: GroupedLight; + [ResourceType.DevicePower]: DevicePower; + [ResourceType.ZigbeeConnectivity]: ZigbeeConnectivity; + [ResourceType.ZgpConnectivity]: Resource; + [ResourceType.ZigbeeDeviceDiscovery]: ZigbeeDeviceDiscovery; + [ResourceType.Bridge]: Bridge; + [ResourceType.Homekit]: Resource; + [ResourceType.Scene]: Scene; + [ResourceType.EntertainmentConfiguration]: Resource; + [ResourceType.PublicImage]: Resource; + [ResourceType.BehaviourScript]: Resource; + [ResourceType.BehaviourInstance]: Resource; + [ResourceType.Geofence]: Resource; + [ResourceType.GeofenceClient]: Resource; + [ResourceType.Geolocation]: Resource; } -/** -export type NarrowResource = T extends ApiResourceType - ? Resources[T] - : Resource | NamedResource; - */ -export type NarrowResource = Resources[T]; -export abstract class Resource { - public readonly bridge: Bridge; - public abstract readonly type: ApiResourceType; - public data: ApiResourceTypeGet; +export type NarrowResource = Resources[T]; + +export abstract class Resource { + public readonly hue: Hue; + public abstract readonly type: ResourceType; + public abstract readonly manager: Manager; + public data: ResourceTypeGet; get id(): string { return this.data.id; @@ -57,32 +61,26 @@ export abstract class Resource { return createResourceIdentifier(this.id, this.type); } - constructor(bridge: Bridge, data: ApiResourceTypeGet) { - this.bridge = bridge; + constructor(hue: Hue, data: ResourceTypeGet) { + this.hue = hue; this.data = data; } - public isType(type: T): this is NarrowResource { + public isType(type: T): this is NarrowResource { return this.type === type; } - public _patch(data: Partial>) { - this.data = merge>(clone(this.data), data); + public _patch(data: Partial>) { + this.data = merge>(clone(this.data), data); } public _clone(): this { return clone(this); } - public _update(data: Partial>): this { + public _update(data: Partial>): this { const clone = this._clone(); this._patch(data); return clone; } - - protected async _put(data: ApiResourceTypePut): Promise { - await this.bridge.rest.put(`/resource/${this.type}/${this.id}`, data); - } - - public abstract edit(options: Record): Promise; } diff --git a/src/structures/Room.ts b/src/structures/Room.ts index 049bc7e..c106933 100644 --- a/src/structures/Room.ts +++ b/src/structures/Room.ts @@ -1,39 +1,54 @@ -import { NamedResource } from './NamedResource'; -import { ApiResourceType } from '../api/ApiResourceType'; -import { ResourceIdentifier } from '../api/ResourceIdentifier'; -import { NarrowResource } from './Resource'; - -export interface RoomEditOptions { - name?: string; - children?: ResourceIdentifier[]; +import { ResourceType } from '../api/ResourceType'; +import { ArcheTypeResource, ArcheTypeResourceEditOptions } from './ArcheTypeResource'; +import { RoomManager } from '../managers/RoomManager'; +import { ZoneEditOptions } from './Zone'; +import { SceneCreateOptions } from './Scene'; + +export interface RoomEditOptions extends ArcheTypeResourceEditOptions { + children?: string[]; } -export class Room extends NamedResource { - type = ApiResourceType.Room; +export type RoomCreateOptions = Required; - get children(): NarrowResource[] { - return this.bridge.resources.getByIdentifiers(this.childIdentifiers); +export class Room extends ArcheTypeResource { + type = ResourceType.Room; + + get manager(): RoomManager { + return this.hue.rooms; + } + + get childIds(): string[] { + return this.data.children.map((child) => child.rid); } - get childIdentifiers(): ResourceIdentifier[] { - return this.data.children; + get serviceIds(): string[] { + return this.data.services.map((service) => service.rid); } - get services(): NarrowResource[] { - return this.bridge.resources.getByIdentifiers(this.serviceIdentifiers); + public async createScene(options: SceneCreateOptions): Promise { + return await this.hue.scenes.create(this.id, options); } - get serviceIdentifiers(): ResourceIdentifier[] { - return this.data.services; + public async addChildren(children: Required['children']): Promise { + const newChildren = [...this.childIds, ...children]; + + await this.setChildren(newChildren); } - public async addChildren(...children: ResourceIdentifier[]) { - const newChildren = [...this.childIdentifiers, ...children]; + public async removeChildren(children: Required['children']): Promise { + const newChildren = this.childIds.filter((id) => !children.includes(id)); - await this.edit({ children: newChildren }); + await this.setChildren(newChildren); } + public async setChildren(children: Required['children']): Promise { + await this.edit({ children }); + } public async edit(options: RoomEditOptions): Promise { - await this._put({ metadata: options.name ? { name: options.name } : undefined, children: options.children }); + await this.manager.edit(this.id, options); + } + + public async delete(): Promise { + await this.manager.delete(this.id); } } diff --git a/src/structures/Scene.ts b/src/structures/Scene.ts index b7f2b0a..ce9dd00 100644 --- a/src/structures/Scene.ts +++ b/src/structures/Scene.ts @@ -1,23 +1,23 @@ import { NamedResource } from './NamedResource'; -import { ApiResourceType } from '../api/ApiResourceType'; -import { XyPoint } from '../util/color/xy'; -import { ResourceIdentifier } from '../api/ResourceIdentifier'; -import { NarrowResource } from './Resource'; +import { ResourceType } from '../api/ResourceType'; +import { XyPoint } from '../color/xy'; +import { SceneManager } from '../managers/SceneManager'; +import { LightEffect } from './Light'; export interface SceneAction { - target: ResourceIdentifier; + id: string; on?: boolean; brightness?: number; - mirek?: number; - xy?: XyPoint; - xys?: XyPoint[]; - effects?: 'fire' | 'candle' | 'no_effect'; + colorTemperature?: number; + color?: XyPoint; + gradient?: XyPoint[]; + effects?: LightEffect; dynamics?: { duration?: number }; } export interface ScenePalette { color: Array<{ - xy: XyPoint; + color: XyPoint; brightness: number; }>; brightness: number; @@ -30,7 +30,7 @@ export interface ScenePalette { export interface SceneEditOptions { name?: string; actions?: Array; - palette?: ScenePalette; + // palette?: ScenePalette; speed?: number; recall?: { action?: 'active' | 'dynamic_palette'; @@ -39,55 +39,77 @@ export interface SceneEditOptions { }; } -// TODO palette and actions edit -export class Scene extends NamedResource { - type = ApiResourceType.Scene; +export type SceneCreateOptions = Pick, 'name' | 'actions'>; - get group(): NarrowResource { - return this.bridge.resources.getByIdentifier(this.groupIdentifier); +export enum SceneRecallAction { + Active = 'active', + DynamicPalette = 'dynamic_palette', +} + +// TODO scene palette + +export class Scene extends NamedResource { + type = ResourceType.Scene; + + get manager(): SceneManager { + return this.hue.scenes; } - get groupIdentifier(): ResourceIdentifier { - return this.data.group; + get groupId(): string { + return this.data.group.rid; } get actions(): SceneAction[] { return this.data.actions.map(({ target, action }) => { return { - target: target, + id: target.rid, on: action.on?.on, brightness: action.dimming?.brightness, - mirek: action.color_temperature?.mirek, - xy: action.color?.xy, - xys: action.gradient ? action.gradient.points.map((p) => p.color.xy) : undefined, - effects: action.effects, + colorTemperature: action.color_temperature?.mirek, + color: action.color?.xy, + gradient: action.gradient ? action.gradient.points.map((p) => p.color.xy) : undefined, + effects: action.effects as LightEffect, dynamics: action.dynamics, }; }); } - public actionFor(identifier: ResourceIdentifier): SceneAction | undefined { - return this.actions.find((action) => action.target === identifier); - } - get speed(): number { return this.data.speed; } + public actionFor(id: string): SceneAction | undefined { + return this.actions.find((action) => action.id == id); + } + public async recall(options?: SceneEditOptions['recall']): Promise { - await this._put({ recall: { action: 'active', ...options } }); + await this.edit({ recall: { ...options } }); + } + + public async createActionFor(id: string, options: Omit): Promise { + const newAction = { ...options, id }; + + await this.setActions([...this.actions, newAction]); + } + + public async editActionFor(id: string, options: Omit): Promise { + const action = this.actionFor(id); + if (!action) return; + + const newAction = { ...action, ...options }; + + await this.setActions([...this.actions.filter((action) => action.id != id), newAction]); + } + + public async setActions(actions: SceneAction[]): Promise { + await this.edit({ actions }); } public async edit(options: SceneEditOptions): Promise { - await this._put({ - metadata: options.name ? { name: options.name } : undefined, - recall: options.recall - ? { - action: options.recall?.action ?? 'active', - duration: options.recall?.duration, - dimming: { brightness: options.recall?.brightness }, - } - : undefined, - }); + await this.manager.edit(this.id, options); + } + + public async delete(): Promise { + await this.manager.delete(this.id); } } diff --git a/src/structures/XyLight.ts b/src/structures/XyLight.ts deleted file mode 100644 index 1f2c79c..0000000 --- a/src/structures/XyLight.ts +++ /dev/null @@ -1,59 +0,0 @@ -import { MirekLight, MirekLightEditOptions } from './MirekLight'; -import { LightCapabilities } from './Light'; -import { ApiResourceType, ApiResourceTypePut } from '../api/ApiResourceType'; -import { createGamut, Gamut, resolveGamutByType } from '../util/color/gamut'; -import { checkXyInReach, createXy, getClosestXy, XyPoint } from '../util/color/xy'; - -export interface XyLightEditOptions extends MirekLightEditOptions { - xy?: XyPoint; -} - -export class XyLight extends MirekLight { - public capabilities = LightCapabilities.Xy; - - get xy(): XyPoint { - return createXy(this.data.color!.xy.x, this.data.color!.xy.y, this.data.dimming!.brightness); - } - - get maxGamutRed(): XyPoint { - return this.data.color!.gamut?.red ?? resolveGamutByType(this.data.color!.gamut_type).red; - } - - get maxGamutGreen(): XyPoint { - return this.data.color!.gamut?.green ?? resolveGamutByType(this.data.color!.gamut_type).green; - } - - get maxGamutBlue(): XyPoint { - return this.data.color!.gamut?.blue ?? resolveGamutByType(this.data.color!.gamut_type).blue; - } - - get gamut(): Gamut { - return createGamut(this.maxGamutRed, this.maxGamutGreen, this.maxGamutBlue); - } - - public xyInRange(xy: XyPoint): boolean { - return checkXyInReach(xy, this.gamut); - } - - public xyToRange(xy: XyPoint): XyPoint { - return getClosestXy(xy, this.gamut); - } - - public async setXy(xy: XyLightEditOptions['xy'], duration?: number): Promise { - await this.edit({ xy, dynamics: { duration } }); - } - - public async edit(options: XyLightEditOptions, _inject?: ApiResourceTypePut): Promise { - if (options.xy && !this.xyInRange(options.xy)) { - options.xy = this.xyToRange(options.xy); - } - - await super.edit( - { brightness: options.xy?.z, ...options }, - { - color: { xy: options.xy ? { x: options.xy.x, y: options.xy.y } : undefined }, - ..._inject, - }, - ); - } -} diff --git a/src/structures/XysLight.ts b/src/structures/XysLight.ts deleted file mode 100644 index 0282794..0000000 --- a/src/structures/XysLight.ts +++ /dev/null @@ -1,41 +0,0 @@ -import { XyLight, XyLightEditOptions } from './XyLight'; -import { LightCapabilities } from './Light'; -import { XyPoint } from '../util/color/xy'; -import { ApiResourceType, ApiResourceTypePut } from '../api/ApiResourceType'; - -export interface XysLightEditOptions extends XyLightEditOptions { - xys?: XyPoint[]; -} - -export class XysLight extends XyLight { - public capabilities = LightCapabilities.Xys; - - get xys(): XyPoint[] { - return this.data.gradient!.points.map((point) => point.color.xy); - } - - public async setXys(xys: XyPoint[], duration: number): Promise { - await this.edit({ xys, dynamics: { duration } }); - } - - public async edit(options: XysLightEditOptions, _inject?: ApiResourceTypePut): Promise { - if (options.xys) - options.xys = options.xys.map((xy) => { - if (!this.xyInRange(xy)) return this.xyToRange(xy); - return xy; - }); - - await super.edit(options, { - gradient: options.xys - ? { - points: options.xys.map((xy) => { - return { - color: { xy }, - }; - }), - } - : undefined, - ..._inject, - }); - } -} diff --git a/src/structures/ZigbeeConnectivity.ts b/src/structures/ZigbeeConnectivity.ts new file mode 100644 index 0000000..45c4fe8 --- /dev/null +++ b/src/structures/ZigbeeConnectivity.ts @@ -0,0 +1,30 @@ +import { Resource } from './Resource'; +import { ResourceType } from '../api/ResourceType'; +import { ZigbeeConnectivityManager } from '../managers/ZigbeeConnectivityManager'; + +export enum ZigbeeConnectivityStatus { + Connected = 'connected', + Disconnected = 'disconnected', + Connectivity_issue = 'connectivity_issue', + UnidirectionalIncoming = 'unidirectional_incoming', +} + +export class ZigbeeConnectivity extends Resource { + type = ResourceType.ZigbeeConnectivity; + + get manager(): ZigbeeConnectivityManager { + return this.hue.zigbeeConnectivities; + } + + get ownerId(): string { + return this.data.owner.rid; + } + + get status(): ZigbeeConnectivityStatus { + return this.data.status as ZigbeeConnectivityStatus; + } + + get macAddress(): string { + return this.data.mac_address; + } +} diff --git a/src/structures/ZigbeeDeviceDiscovery.ts b/src/structures/ZigbeeDeviceDiscovery.ts new file mode 100644 index 0000000..9b1e745 --- /dev/null +++ b/src/structures/ZigbeeDeviceDiscovery.ts @@ -0,0 +1,36 @@ +import { Resource } from './Resource'; +import { ResourceType } from '../api/ResourceType'; +import { ZigbeeDeviceDiscoveryManager } from '../managers/ZigbeeDeviceDiscoveryManager'; + +export enum ZigbeeDeviceDiscoveryStatus { + Active = 'active', + Ready = 'ready', +} + +export enum ZigbeeDeviceDiscoveryActionType { + Search = 'search', +} + +export interface ZigbeeDeviceDiscoveryEdit { + actionType: ZigbeeDeviceDiscoveryActionType; +} + +export class ZigbeeDeviceDiscovery extends Resource { + type = ResourceType.ZigbeeDeviceDiscovery; + + get manager(): ZigbeeDeviceDiscoveryManager { + return this.hue.zigbeeDeviceDiscoveries; + } + + get ownerId(): string { + return this.data.owner.rid; + } + + get status(): ZigbeeDeviceDiscoveryStatus { + return this.data.status as ZigbeeDeviceDiscoveryStatus; + } + + public async edit(options: ZigbeeDeviceDiscoveryEdit) { + await this.manager.edit(this.id, options); + } +} diff --git a/src/structures/Zone.ts b/src/structures/Zone.ts index 7e0e581..1f5f7bd 100644 --- a/src/structures/Zone.ts +++ b/src/structures/Zone.ts @@ -1,45 +1,55 @@ import { NamedResource } from './NamedResource'; -import { ApiResourceType } from '../api/ApiResourceType'; -import { ResourceIdentifier } from '../api/ResourceIdentifier'; -import { NarrowResource } from './Resource'; +import { ResourceType } from '../api/ResourceType'; +import { ArcheTypeResourceEditOptions } from './ArcheTypeResource'; +import { ZoneManager } from '../managers/ZoneManager'; +import { SceneCreateOptions } from './Scene'; -export interface ZoneEditOptions { - name?: string; - children?: ResourceIdentifier[]; +export interface ZoneEditOptions extends ArcheTypeResourceEditOptions { + children?: string[]; } -export class Zone extends NamedResource { - type = ApiResourceType.Zone; +export type ZoneCreateOptions = Required; - get children(): NarrowResource[] { - return this.bridge.resources.getByIdentifiers(this.childIdentifiers); +export class Zone extends NamedResource { + type = ResourceType.Zone; + + get manager(): ZoneManager { + return this.hue.zones; } - get childIdentifiers(): ResourceIdentifier[] { - return this.data.children; + get childIds(): string[] { + return this.data.children.map((child) => child.rid); } - get services(): NarrowResource[] { - return this.bridge.resources.getByIdentifiers(this.serviceIdentifiers); + get serviceIds(): string[] { + return this.data.services.map((service) => service.rid); } - get serviceIdentifiers(): ResourceIdentifier[] { - return this.data.services; + public async createScene(options: SceneCreateOptions): Promise { + return await this.hue.scenes.create(this.id, options); } - public async removeChildren(...children: ResourceIdentifier[]): Promise { - const newChildren = this.childIdentifiers.filter((identifier) => !children.includes(identifier)); + public async addChildren(children: Required['children']): Promise { + const newChildren = [...this.childIds, ...children]; - await this.edit({ children: newChildren }); + await this.setChildren(newChildren); } - public async addChildren(...children: ResourceIdentifier[]): Promise { - const newChildren = [...this.childIdentifiers, ...children]; + public async removeChildren(children: Required['children']): Promise { + const newChildren = this.childIds.filter((id) => !children.includes(id)); + + await this.setChildren(newChildren); + } - await this.edit({ children: newChildren }); + public async setChildren(children: Required['children']): Promise { + await this.edit({ children }); } public async edit(options: ZoneEditOptions): Promise { - await this._put({ metadata: options.name ? { name: options.name } : undefined, children: options.children }); + await this.manager.edit(this.id, options); + } + + public async delete(): Promise { + await this.manager.delete(this.id); } } diff --git a/src/util/Transformers.ts b/src/util/Transformers.ts new file mode 100644 index 0000000..baecd34 --- /dev/null +++ b/src/util/Transformers.ts @@ -0,0 +1,133 @@ +import { ifNotNull } from './ifNotNull'; +import { createResourceIdentifier } from './resourceIdentifier'; +import { ResourceType } from '../api/ResourceType'; +import { XyPoint } from '../color/xy'; +import { SceneAction, SceneRecallAction } from '../structures/Scene'; +import { NamedResourceEditOptions } from '../structures/NamedResource'; +import { ArcheTypeResourceEditOptions } from '../structures/ArcheTypeResource'; +import { LightEffect, LightTimedEffect } from '../structures/Light'; +import { ZigbeeDeviceDiscoveryActionType } from '../structures/ZigbeeDeviceDiscovery'; + +export function transformMetadata(options: NamedResourceEditOptions) { + return ifNotNull(options.name, () => + Object({ + name: options.name, + }), + ); +} + +export function transformMetadataWithArcheType(options: ArcheTypeResourceEditOptions) { + return ifNotNull(options.name ?? options.archeType, () => + Object({ + name: options.name, + archetype: options.archeType, + }), + ); +} + +export function transformChildren(children?: string[]) { + return ifNotNull(children, () => children!.map((child) => createResourceIdentifier(child, ResourceType.Light))); +} + +export function transformOn(on?: boolean) { + return ifNotNull(on, () => Object({ on })); +} + +export function transformDynamics(dynamics?: { duration?: number; speed?: number }) { + return ifNotNull(dynamics, () => + Object({ + duration: dynamics!.duration, + speed: dynamics!.speed, + }), + ); +} + +export function transformEffects(effect?: LightEffect) { + return ifNotNull(effect, () => + Object({ + effect: effect, + }), + ); +} + +export function transformTimedEffects(timedEffects?: { effect?: LightTimedEffect; duration?: number }) { + return ifNotNull(timedEffects, () => + Object({ + effect: timedEffects!.effect, + duration: timedEffects!.duration, + }), + ); +} + +export function transformDimming(brightness?: number) { + return ifNotNull(brightness, () => + Object({ + brightness, + }), + ); +} + +export function transformColorTemperature(colorTemperature?: number) { + return ifNotNull(colorTemperature, () => + Object({ + mirek: colorTemperature, + }), + ); +} + +export function transformColor(color?: XyPoint) { + return ifNotNull(color, () => + Object({ + xy: { x: color!.x, y: color!.y }, + }), + ); +} + +export function transformGradient(gradient?: XyPoint[]) { + return ifNotNull(gradient, () => + Object({ + points: gradient!.map((point) => + Object({ + color: transformColor(point), + }), + ), + }), + ); +} + +export function transformRecall(recall?: { action?: string; duration?: number; brightness?: number }) { + return ifNotNull(recall, () => + Object({ + action: recall!.action ?? SceneRecallAction.Active, + duration: recall!.duration, + dimming: { brightness: recall!.brightness }, + }), + ); +} + +export function transformSceneActions(actions?: SceneAction[]) { + return ifNotNull(actions, () => + actions!.map((action) => + Object({ + target: createResourceIdentifier(action.id, ResourceType.Light), + action: { + on: transformOn(action.on), + dimming: transformDimming(action.brightness), + color_temperature: transformColorTemperature(action.colorTemperature), + color: transformColor(action.color), + gradient: transformGradient(action.gradient), + effects: transformEffects(action.effects), + dynamics: transformDynamics(action.dynamics), + }, + }), + ), + ); +} + +export function transformAction(action: { actionType: ZigbeeDeviceDiscoveryActionType }) { + return ifNotNull(action, () => + Object({ + action_type: action.actionType, + }), + ); +} diff --git a/src/util/clone.ts b/src/util/clone.ts index b4b1ce1..4fa920a 100644 --- a/src/util/clone.ts +++ b/src/util/clone.ts @@ -1,3 +1,3 @@ export function clone>(object: T): T { - return Object.assign(Object.create(object), object); + return Object.assign(Object.create(Object.getPrototypeOf(object)), object); } diff --git a/src/util/ifNotNull.ts b/src/util/ifNotNull.ts new file mode 100644 index 0000000..a080f37 --- /dev/null +++ b/src/util/ifNotNull.ts @@ -0,0 +1,3 @@ +export function ifNotNull(predicate: any, func: () => T): T | undefined { + if (predicate != null) return func(); +} diff --git a/src/util/resourceIdentifier.ts b/src/util/resourceIdentifier.ts index 962b072..dc28830 100644 --- a/src/util/resourceIdentifier.ts +++ b/src/util/resourceIdentifier.ts @@ -1,10 +1,6 @@ -import { ApiResourceType } from '../api/ApiResourceType'; +import { ResourceType } from '../api/ResourceType'; import { ResourceIdentifier } from '../api/ResourceIdentifier'; -export function createResourceIdentifier(id: string, type: T): ResourceIdentifier { +export function createResourceIdentifier(id: string, type: T): ResourceIdentifier { return { rid: id, rtype: type }; } - -export function areIdentifiersEqual(identifier1?: ResourceIdentifier, identifier2?: ResourceIdentifier): boolean { - return identifier1?.rid === identifier2?.rid && identifier1?.rtype === identifier2?.rtype; -} diff --git a/src/util/util.ts b/src/util/util.ts deleted file mode 100644 index 09f815f..0000000 --- a/src/util/util.ts +++ /dev/null @@ -1,16 +0,0 @@ -import { NarrowResource } from '../structures/Resource'; -import { ApiResourceType } from '../api/ApiResourceType'; - -export function findResourceByTypeInArray( - resources: NarrowResource[], - type: T, - force?: boolean, -): NarrowResource | undefined; -export function findResourceByTypeInArray( - resources: NarrowResource[], - type: T, - force: true, -): NarrowResource; -export function findResourceByTypeInArray(resources: NarrowResource[], type: T) { - return resources.find((resource) => resource.isType(type)); -}