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 f524c53
Show file tree
Hide file tree
Showing 13 changed files with 283 additions and 7 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"
}
}
}
14 changes: 13 additions & 1 deletion app.ts
Original file line number Diff line number Diff line change
@@ -1,12 +1,14 @@
import Homey from 'homey';
import { HomeyAPI, ExtendedHomeyAPIV3Local } from 'homey-api';
import { HomeyAPI, ExtendedHomeyAPIV3Local, ExtendedZone } from 'homey-api';
import { Log } from 'homey-log';
import ConditionCardAnyDeviceTurnedOn from './lib/ConditionCardAnyDeviceTurnedOn';
import ConditionCardZoneInactiveForMinutes from './lib/ConditionCardZoneInactiveForMinutes';
import ConditionCardEvaluateSensorCapabilities from './lib/ConditionCardEvaluateSensorCapabilities';
import ConditionCardZoneActiveForMinutes from './lib/ConditionCardZoneActiveForMinutes';
import TriggerCardAnyDeviceTurnedOn from './lib/TriggerCardAnyDeviceOnOff';
import ZonesDb from './lib/ZonesDb';
import WidgetZoneActivityState from './lib/WidgetZoneActivityState';
import getIconForZone from './utils/getIconForZone';

class ZoneActivity extends Homey.App {
/**
Expand Down Expand Up @@ -34,6 +36,16 @@ 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);
await WidgetZoneActivityState.initialize(this.homey, this.homeyApi, zonesDb, this.log, this.error);
}

//TODO: SYNC WITH WidgetZoneActivityState
public async isZoneActive(zoneId: string): Promise<ExtendedZone | null> {
const zone = await this.homeyApi.zones.getZone({ id: zoneId });
if (zone === null)
return null;
zone.icon = getIconForZone(zone.icon);
return zone;
}
}

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

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) => {
zone.icon = getIconForZone(zone.icon);
this.homey.api.realtime('zone-update', zone);
});
}
}

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.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.
Binary file added widgets/zone-activity-state/public/homey-logo.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
102 changes: 102 additions & 0 deletions widgets/zone-activity-state/public/index.html
Original file line number Diff line number Diff line change
@@ -0,0 +1,102 @@
<html>

<head>
<style>
/* Example of a custom CSS class. */
.zone-icon {
margin-right: var(--homey-su-2);
width: 20px;
height: 20px;
}

.zone-info-container {
display: flex;
flex-direction: column;
align-items: flex-start;
}

.zone-info-row {
display: flex;
align-items: center;
}

.zone-name {
font-size: 1.2em;
font-weight: bold;
}

.zone-status {
margin-left: var(--homey-su-2);
font-size: 1em;
color: gray;
}

.zone-status.active {
color: royalblue;
}

.last-state-change {
margin-top: var(--homey-su-2);
font-size: 0.9em;
color: darkgray;
}

</style>
</head>

<body class="homey-widget">
<div id="zone-info" class="zone-info-container"></div>

<script type="text/javascript">
function onHomeyReady(Homey) {
Homey.ready();

// View the settings the user provided if your widget has settings.
const settings = Homey.getSettings();
console.log('Widget settings:', settings);

// Update the zone info
const zoneInfo = document.getElementById('zone-info');
zoneInfo.innerHTML = `
<div class="zone-info-row">
<img id="zone-icon" src="${settings.zone.image}" alt="${settings.zone.name}" class="zone-icon" />
<div class="zone-name">${settings.zone.name}</div>
<div id="zone-status" class="zone-status">(Loading...)</div>
</div>
<div id="last-state-change" class="last-state-change"></div>
`;

function updateZoneStatus(result) {
console.log('Zone status:', result);
if (result.id === settings.zone.id) {
const statusText = result.active ? 'Active' : 'Inactive';
document.getElementById('zone-status').textContent = `(${statusText})`;
document.getElementById('zone-icon').src = result.icon;
if (result.active) {
document.getElementById('zone-status').classList.add('active');
} else {
document.getElementById('zone-status').classList.remove('active');
}

// Update the last state change information
const lastStateChange = new Date(result.activeLastUpdated).toLocaleString();
document.getElementById('last-state-change').textContent = `Last state change: ${lastStateChange}`;
}
}

if (settings.zone.id) {
// Fetch something from your app.
Homey.api('GET', `/?zoneId=${settings.zone.id}`, {})
.then((result) => updateZoneStatus(result))
.catch((error) => {
console.error(error);
document.getElementById('zone-status').textContent = '(Error)';
});
}

Homey.on('zone-update', (result) => updateZoneStatus(result));
}
</script>
</body>

</html>
34 changes: 34 additions & 0 deletions widgets/zone-activity-state/widget.compose.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
{
"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"
}
}
}

0 comments on commit f524c53

Please sign in to comment.