diff --git a/.homeycompose/app.json b/.homeycompose/app.json index aa211c2..00d9354 100644 --- a/.homeycompose/app.json +++ b/.homeycompose/app.json @@ -7,7 +7,7 @@ "url": "https://github.com/Erikvl87/nl.erikvl87.zoneactivity/issues" }, "source": "https://github.com/Erikvl87/nl.erikvl87.zoneactivity", - "compatibility": ">=5.0.0", + "compatibility": ">=12.1.2", "sdk": 3, "platforms": [ "local" diff --git a/app.json b/app.json index b00bcab..26faa78 100644 --- a/app.json +++ b/app.json @@ -8,7 +8,7 @@ "url": "https://github.com/Erikvl87/nl.erikvl87.zoneactivity/issues" }, "source": "https://github.com/Erikvl87/nl.erikvl87.zoneactivity", - "compatibility": ">=5.0.0", + "compatibility": ">=12.1.2", "sdk": 3, "platforms": [ "local" @@ -628,5 +628,42 @@ } ] } - ] + ], + "widgets": { + "zone-activity-state": { + "name": { + "en": "Zone activity state", + "nl": "Zone activiteit status" + }, + "height": 75, + "settings": [ + { + "id": "zone", + "type": "autocomplete", + "title": { + "en": "Zone" + } + } + ], + "api": { + "getSomething": { + "method": "GET", + "path": "/" + }, + "addSomething": { + "method": "POST", + "path": "/" + }, + "updateSomething": { + "method": "PUT", + "path": "/:id" + }, + "deleteSomething": { + "method": "DELETE", + "path": "/:id" + } + }, + "id": "zone-activity-state" + } + } } \ No newline at end of file diff --git a/app.ts b/app.ts index b4ec133..e819655 100644 --- a/app.ts +++ b/app.ts @@ -7,6 +7,7 @@ import ConditionCardEvaluateSensorCapabilities from './lib/ConditionCardEvaluate import ConditionCardZoneActiveForMinutes from './lib/ConditionCardZoneActiveForMinutes'; import TriggerCardAnyDeviceTurnedOn from './lib/TriggerCardAnyDeviceOnOff'; import ZonesDb from './lib/ZonesDb'; +import WidgetZoneActivityState from './lib/WidgetZoneActivityState'; class ZoneActivity extends Homey.App { /** @@ -16,6 +17,8 @@ class ZoneActivity extends Homey.App { homeyApi!: ExtendedHomeyAPIV3Local; homeyLog?: Log; + private widget!: WidgetZoneActivityState; + /** * Initialize the Zone Activity app. * @returns {Promise} A promise that resolves when the app has been initialized. @@ -34,6 +37,7 @@ class ZoneActivity extends Homey.App { await ConditionCardZoneActiveForMinutes.initialize(this.homey, this.homeyApi, zonesDb, this.log); await ConditionCardEvaluateSensorCapabilities.initialize(this.homey, this.homeyApi, zonesDb, this.log); await TriggerCardAnyDeviceTurnedOn.initialize(this.homey, this.homeyApi, zonesDb, this.log, this.error); + this.widget = await WidgetZoneActivityState.initialize(this.homey, this.homeyApi, zonesDb, this.log, this.error); } } diff --git a/lib/WidgetZoneActivityState.ts b/lib/WidgetZoneActivityState.ts new file mode 100644 index 0000000..42adb0b --- /dev/null +++ b/lib/WidgetZoneActivityState.ts @@ -0,0 +1,71 @@ +import { ExtendedDeviceCapability, ExtendedHomeyAPIV3Local, ExtendedZone } from "homey-api"; +import Homey from "homey/lib/Homey"; +import handleZoneAutocomplete from "../utils/handleZoneAutocomplete"; +import ZonesDb from "./ZonesDb"; +import getIconForZone from "./../utils/getIconForZone"; + +export default class WidgetZoneActivityState { + + private static instance: WidgetZoneActivityState | null = null; + + widget: unknown; + + syncDevicesTimeoutId: NodeJS.Timeout | undefined; + + capabilityInstances: Map = new Map(); + + private constructor(private homey: Homey, private homeyApi: ExtendedHomeyAPIV3Local, private zonesDb: ZonesDb, private log: (...args: unknown[]) => void, private error: (...args: unknown[]) => void) { + + // @ts-expect-error Ignore the error for the non-existing method / property + this.widget = this.homey.dashboards.getWidget('zone-activity-state'); + } + + public static async initialize(homey: Homey, homeyApi: ExtendedHomeyAPIV3Local, zonesDb: ZonesDb, log: (...args: unknown[]) => void, error: (...args: unknown[]) => void): Promise { + if (WidgetZoneActivityState.instance === null) { + WidgetZoneActivityState.instance = new WidgetZoneActivityState(homey, homeyApi, zonesDb, log, error); + await WidgetZoneActivityState.instance.setup(); + } + return WidgetZoneActivityState.instance; + } + + private async setup(): Promise { + // @ts-expect-error Ignore the error for the non-existing method / property + this.widget.registerSettingAutocompleteListener('zone', async (query: string) => { + const result = (await handleZoneAutocomplete(query, this.zonesDb)) + // Remap icon to image for all results + .map((zone) => { + zone.image = zone.icon; + delete zone.icon; + return zone; + }); + + this.log('Zone autocomplete results:', result); + return result; + }); + + this.homeyApi.zones.on('zone.update', async (zone: ExtendedZone) => { + this.homey.api.realtime('zone-update', this.transformZone(zone)); + }); + } + + public async isZoneActive(zoneId: string): Promise { + const zone = await this.zonesDb.getZone(zoneId); + if (zone === null) + return null; + + return this.transformZone(zone); + } + + private transformZone(zone: ExtendedZone): unknown { + const result = { + id: zone.id, + icon: getIconForZone(zone.icon), + name: zone.name, + active: zone.active, + activeLastUpdated: zone.activeLastUpdated, + } + + return result; + } +} + diff --git a/locales/en.json b/locales/en.json index a597a7e..78e632d 100644 --- a/locales/en.json +++ b/locales/en.json @@ -1,4 +1,12 @@ { "any_type": "Any type", - "any_sensor": "Any sensor" + "any_sensor": "Any sensor", + "active": "Active", + "last_active": "Last active", + "hour_ago": "hour ago", + "hours_ago": "hours ago", + "minute_ago": "minute ago", + "minutes_ago": "minutes ago", + "second_ago": "second ago", + "seconds_ago": "seconds ago" } \ No newline at end of file diff --git a/locales/nl.json b/locales/nl.json index a322cef..317fb87 100644 --- a/locales/nl.json +++ b/locales/nl.json @@ -1,4 +1,12 @@ { "any_type": "Elk type", - "any_sensor": "Elke sensor" + "any_sensor": "Elke sensor", + "active": "Actief", + "last_active": "Laatst actief", + "hours_ago": "uur geleden", + "hour_ago": "uur geleden", + "minutes_ago": "minuten geleden", + "minute_ago": "minuut geleden", + "second_ago": "seconde geleden", + "seconds_ago": "seconden geleden" } \ No newline at end of file diff --git a/package-lock.json b/package-lock.json index 188b671..1e92725 100644 --- a/package-lock.json +++ b/package-lock.json @@ -15,7 +15,7 @@ "devDependencies": { "@eslint-recommended/eslint-config": "^26.1.1", "@tsconfig/node16": "^16.1.3", - "@types/homey": "npm:homey-apps-sdk-v3-types@^0.3.7", + "@types/homey": "npm:homey-apps-sdk-v3-types@^0.3.9", "@types/node": "^22.5.1", "@typescript-eslint/eslint-plugin": "^8.3.0", "@typescript-eslint/parser": "^8.3.0", diff --git a/package.json b/package.json index 39fb068..429dbf9 100644 --- a/package.json +++ b/package.json @@ -8,7 +8,7 @@ "devDependencies": { "@eslint-recommended/eslint-config": "^26.1.1", "@tsconfig/node16": "^16.1.3", - "@types/homey": "npm:homey-apps-sdk-v3-types@^0.3.7", + "@types/homey": "npm:homey-apps-sdk-v3-types@^0.3.9", "@types/node": "^22.5.1", "@typescript-eslint/eslint-plugin": "^8.3.0", "@typescript-eslint/parser": "^8.3.0", diff --git a/tsconfig.json b/tsconfig.json index 712faca..0c4882a 100644 --- a/tsconfig.json +++ b/tsconfig.json @@ -10,7 +10,8 @@ "sourceMap": true }, "include": [ - "./**/*.ts" + "./**/*.ts", + "./widgets/zone-activity-state/api.js" ], "exclude": [ "node_modules", diff --git a/widgets/zone-activity-state/api.ts b/widgets/zone-activity-state/api.ts new file mode 100644 index 0000000..d19567e --- /dev/null +++ b/widgets/zone-activity-state/api.ts @@ -0,0 +1,39 @@ + +import Homey from "homey/lib/Homey"; + +class ZoneActivityStateWidget { + public async getSomething({ homey, query }: ApiRequest) : Promise { + // you can access query parameters like "/?foo=bar" through `query.foo` + + console.log('query', query); + + //@ts-expect-error: Ignore the error for the non-existing method 'test' + const result = await homey.app.widget.isZoneActive(query.zoneId); + + console.log('result', result); + + + + + // perform other logic like mapping result data + return result; + } + + // public async addSomething({ homey, body }: ApiRequest) : Promise { + // // access the post body and perform some action on it. + // return homey.app.addSomething(body); + // } + + // // eslint-disable-next-line @typescript-eslint/no-unused-vars + // public async updateSomething({ homey, params, body }: ApiRequest) : Promise { + // return homey.app.setSomething(body); + // } + + // public async deleteSomething({ homey, params } : ApiRequest) : Promise { + // return homey.app.deleteSomething(params.id); + // } +} + +module.exports = new ZoneActivityStateWidget(); + +type ApiRequest = { homey: Homey; query: string, body: unknown, params: unknown }; \ No newline at end of file diff --git a/widgets/zone-activity-state/preview-dark.png b/widgets/zone-activity-state/preview-dark.png new file mode 100644 index 0000000..888755d Binary files /dev/null and b/widgets/zone-activity-state/preview-dark.png differ diff --git a/widgets/zone-activity-state/preview-light.png b/widgets/zone-activity-state/preview-light.png new file mode 100644 index 0000000..f7cff26 Binary files /dev/null and b/widgets/zone-activity-state/preview-light.png differ diff --git a/widgets/zone-activity-state/public/index.html b/widgets/zone-activity-state/public/index.html new file mode 100644 index 0000000..bc8b966 --- /dev/null +++ b/widgets/zone-activity-state/public/index.html @@ -0,0 +1,176 @@ + + + + + + + + + + + + + \ No newline at end of file diff --git a/widgets/zone-activity-state/widget.compose.json b/widgets/zone-activity-state/widget.compose.json new file mode 100644 index 0000000..13d2740 --- /dev/null +++ b/widgets/zone-activity-state/widget.compose.json @@ -0,0 +1,34 @@ +{ + "name": { + "en": "Zone activity state", + "nl": "Zone activiteit status" + }, + "height": 75, + "settings": [ + { + "id": "zone", + "type": "autocomplete", + "title": { + "en": "Zone" + } + } + ], + "api": { + "getSomething": { + "method": "GET", + "path": "/" + }, + "addSomething": { + "method": "POST", + "path": "/" + }, + "updateSomething": { + "method": "PUT", + "path": "/:id" + }, + "deleteSomething": { + "method": "DELETE", + "path": "/:id" + } + } +} \ No newline at end of file