Skip to content

Commit

Permalink
Add a widget that indicates activity state for a given zone
Browse files Browse the repository at this point in the history
  • Loading branch information
Erikvl87 committed Nov 2, 2024
1 parent 2dce2da commit df39e32
Show file tree
Hide file tree
Showing 14 changed files with 351 additions and 8 deletions.
2 changes: 1 addition & 1 deletion .homeycompose/app.json
Original file line number Diff line number Diff line change
Expand Up @@ -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"
Expand Down
41 changes: 39 additions & 2 deletions app.json
Original file line number Diff line number Diff line change
Expand Up @@ -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"
Expand Down Expand Up @@ -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": "My Title"
}
}
],
"api": {
"getSomething": {
"method": "GET",
"path": "/"
},
"addSomething": {
"method": "POST",
"path": "/"
},
"updateSomething": {
"method": "PUT",
"path": "/:id"
},
"deleteSomething": {
"method": "DELETE",
"path": "/:id"
}
},
"id": "zone-activity-state"
}
}
}
4 changes: 4 additions & 0 deletions app.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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 {
/**
Expand All @@ -16,6 +17,8 @@ class ZoneActivity extends Homey.App {
homeyApi!: ExtendedHomeyAPIV3Local;
homeyLog?: Log;

private widget!: WidgetZoneActivityState;

/**
* Initialize the Zone Activity app.
* @returns {Promise<void>} A promise that resolves when the app has been initialized.
Expand All @@ -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);
}
}

Expand Down
73 changes: 73 additions & 0 deletions lib/WidgetZoneActivityState.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,73 @@
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<string, ExtendedDeviceCapability> = 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<WidgetZoneActivityState> {
if (WidgetZoneActivityState.instance === null) {
WidgetZoneActivityState.instance = new WidgetZoneActivityState(homey, homeyApi, zonesDb, log, error);
await WidgetZoneActivityState.instance.setup();
}
return WidgetZoneActivityState.instance;
}

private async setup(): Promise<void> {
// @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<unknown> {
const zone = await this.homeyApi.zones.getZone({ id: zoneId });
if (zone === null)
return null;

return this.transformZone(zone);
}

private transformZone(zone: ExtendedZone): unknown {
const date = new Date(zone.activeLastUpdated).toLocaleString(this.homey.i18n.getLanguage());
const result = {
id: zone.id,
icon: getIconForZone(zone.icon),
name: zone.name,
subtitle: `${this.homey.__('last_change')}: ${date}`,
active: zone.active,
activeLastUpdated: zone.activeLastUpdated,
}

return result;
}
}

3 changes: 2 additions & 1 deletion locales/en.json
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
{
"any_type": "Any type",
"any_sensor": "Any sensor"
"any_sensor": "Any sensor",
"last_change": "Last change"
}
4 changes: 3 additions & 1 deletion locales/nl.json
Original file line number Diff line number Diff line change
@@ -1,4 +1,6 @@
{
"any_type": "Elk type",
"any_sensor": "Elke sensor"
"any_sensor": "Elke sensor",
"last_change": "Laatste wijziging"

}
2 changes: 1 addition & 1 deletion package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

2 changes: 1 addition & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -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",
Expand Down
3 changes: 2 additions & 1 deletion tsconfig.json
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,8 @@
"sourceMap": true
},
"include": [
"./**/*.ts"
"./**/*.ts",
"./widgets/zone-activity-state/api.js"
],
"exclude": [
"node_modules",
Expand Down
39 changes: 39 additions & 0 deletions widgets/zone-activity-state/api.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,39 @@

import Homey from "homey/lib/Homey";

class ZoneActivityStateWidget {
public async getSomething({ homey, query }: ApiRequest) : Promise<string> {
// 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<string> {
// // 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<string> {
// return homey.app.setSomething(body);
// }

// public async deleteSomething({ homey, params } : ApiRequest) : Promise<string> {
// return homey.app.deleteSomething(params.id);
// }
}

module.exports = new ZoneActivityStateWidget();

type ApiRequest = { homey: Homey; query: string, body: unknown, params: unknown };
Binary file added widgets/zone-activity-state/preview-dark.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added widgets/zone-activity-state/preview-light.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading

0 comments on commit df39e32

Please sign in to comment.