change from poll to update from hooks
This commit is contained in:
@@ -19,7 +19,7 @@ export const Config = {
|
||||
api_token: process.env.HA_API_TOKEN || "",
|
||||
|
||||
id_sensor_desk_binary: process.env.HA_SENSOR_DESK_BINARY || "",
|
||||
id_sensors_roomtemp: process.env.HA_SENSORS_ROOMTEMP?.split(",") || [],
|
||||
id_sensor_roomtemp: process.env.HA_SENSOR_ROOMTEMP || "",
|
||||
|
||||
id_webhook_stand: process.env.HA_STANDING_WEBHOOK || "",
|
||||
},
|
||||
|
||||
@@ -1,7 +1,6 @@
|
||||
import {
|
||||
BaseService,
|
||||
type GristRecord_PersonalGoals,
|
||||
logWarning,
|
||||
type ServiceResult,
|
||||
} from "@dpu/shared";
|
||||
import { DateTime } from "luxon";
|
||||
@@ -25,14 +24,12 @@ export class GristService extends BaseService<GristClient> {
|
||||
this.transformToPersonalGoals(response.records[0].fields),
|
||||
);
|
||||
} else {
|
||||
const error_message = "error finding record from grist";
|
||||
logWarning(error_message);
|
||||
return this.getErrorResult(error_message);
|
||||
return this.getErrorResult(
|
||||
"error getting record from grist. record not found.",
|
||||
);
|
||||
}
|
||||
} catch {
|
||||
const error_message = "error getting record from grist";
|
||||
logWarning(error_message);
|
||||
return this.getErrorResult(error_message);
|
||||
} catch (error) {
|
||||
return this.getErrorResult("error getting record from grist.", error);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -2,16 +2,6 @@ import { BaseClient, type HomeAssistantEntity } from "@dpu/shared";
|
||||
import { printNetworkError } from "@dpu/shared/dist/logger.js";
|
||||
|
||||
export class HomeAssistantClient extends BaseClient {
|
||||
async getEntityStates(entityIds: string[]): Promise<HomeAssistantEntity[]> {
|
||||
try {
|
||||
const promises = entityIds.map((id) => this.getEntityState(id));
|
||||
return await Promise.all(promises);
|
||||
} catch (error) {
|
||||
printNetworkError(error);
|
||||
throw error;
|
||||
}
|
||||
}
|
||||
|
||||
async getEntityState(entityId: string): Promise<HomeAssistantEntity> {
|
||||
try {
|
||||
const response = await this.getAxios().get<HomeAssistantEntity>(
|
||||
|
||||
@@ -93,7 +93,7 @@ export async function homeAssistantRoutes(
|
||||
},
|
||||
},
|
||||
async (_request, reply) => {
|
||||
const service_result = await haService.getTemperatureText();
|
||||
const service_result = await haService.getTemperature();
|
||||
|
||||
if (!service_result.successful) {
|
||||
reply.code(418);
|
||||
|
||||
@@ -5,7 +5,6 @@ import {
|
||||
type HomeAssistantEntity,
|
||||
type ServiceResult,
|
||||
} from "@dpu/shared";
|
||||
import { logWarning } from "@dpu/shared/dist/logger.js";
|
||||
import { calculateSecondsBetween } from "@dpu/shared/dist/timehelper.js";
|
||||
import { Config } from "../config.js";
|
||||
import type { HomeAssistantClient } from "./client.js";
|
||||
@@ -37,9 +36,7 @@ export class HomeAssistantService extends BaseService<HomeAssistantClient> {
|
||||
|
||||
return this.getSuccessfulResult(result);
|
||||
} catch (error) {
|
||||
const error_message = `error starting stand automation. ${error instanceof Error ? error.message : error}`;
|
||||
logWarning(error_message);
|
||||
return this.getErrorResult(error_message);
|
||||
return this.getErrorResult("error starting stand automation.", error);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -51,25 +48,27 @@ export class HomeAssistantService extends BaseService<HomeAssistantClient> {
|
||||
Config.homeassistant.id_sensor_desk_binary,
|
||||
);
|
||||
|
||||
const position = Number(raw.state);
|
||||
|
||||
const result = {
|
||||
raw,
|
||||
as_boolean: position === 1,
|
||||
last_changed: calculateSecondsBetween(
|
||||
new Date(raw.last_changed).getTime(),
|
||||
Date.now(),
|
||||
),
|
||||
};
|
||||
|
||||
return this.getSuccessfulResult(result);
|
||||
return this.getSuccessfulResult(this.convertHaEntityToPosResult(raw));
|
||||
} catch (error) {
|
||||
const error_message = "error getting desk position";
|
||||
logWarning(error_message, error);
|
||||
return this.getErrorResult(error_message);
|
||||
return this.getErrorResult("error getting desk position.", error);
|
||||
}
|
||||
}
|
||||
|
||||
convertHaEntityToPosResult(
|
||||
raw: HomeAssistantEntity,
|
||||
): HomeAssistantDeskPositionResult {
|
||||
const position = Number(raw.state);
|
||||
|
||||
return {
|
||||
raw,
|
||||
as_boolean: position === 1,
|
||||
last_changed: calculateSecondsBetween(
|
||||
new Date(raw.last_changed).getTime(),
|
||||
Date.now(),
|
||||
),
|
||||
};
|
||||
}
|
||||
|
||||
convertPosResultToApiAnswer(
|
||||
position: HomeAssistantDeskPositionResult,
|
||||
): API_HA_DeskPosition {
|
||||
@@ -80,33 +79,15 @@ export class HomeAssistantService extends BaseService<HomeAssistantClient> {
|
||||
};
|
||||
}
|
||||
|
||||
async getTemperatureText(): Promise<ServiceResult<string>> {
|
||||
async getTemperature(): Promise<ServiceResult<string>> {
|
||||
try {
|
||||
const entities = await this.getTemperatures();
|
||||
const values = entities
|
||||
.map((entity) => parseFloat(entity.state))
|
||||
.filter((value) => !Number.isNaN(value));
|
||||
const average =
|
||||
values.length > 0
|
||||
? values.reduce((sum, value) => sum + value, 0) / values.length
|
||||
: 0;
|
||||
const result = average.toFixed(2);
|
||||
return this.getSuccessfulResult(result);
|
||||
} catch (error) {
|
||||
const error_message = "error getting temperature as text";
|
||||
logWarning(error_message, error);
|
||||
return this.getErrorResult(error_message);
|
||||
}
|
||||
}
|
||||
|
||||
private async getTemperatures(): Promise<HomeAssistantEntity[]> {
|
||||
try {
|
||||
return await this.getClient().getEntityStates(
|
||||
Config.homeassistant.id_sensors_roomtemp,
|
||||
const entity = await this.getClient().getEntityState(
|
||||
Config.homeassistant.id_sensor_roomtemp,
|
||||
);
|
||||
|
||||
return this.getSuccessfulResult(entity.state);
|
||||
} catch (error) {
|
||||
logWarning("error getting temperatures:", error);
|
||||
return [];
|
||||
return this.getErrorResult("error getting temperature.", error);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,18 +1,31 @@
|
||||
import type {
|
||||
API_HA_DeskPosition,
|
||||
ComponentUpdate,
|
||||
GristRecord_PersonalGoals,
|
||||
HA_Update,
|
||||
HomeAssistantEntity,
|
||||
TidalGetCurrent,
|
||||
} from "@dpu/shared";
|
||||
import type { FastifyInstance } from "fastify";
|
||||
import type { FastifyInstance, FastifyReply } from "fastify";
|
||||
import type { FastifyRequest } from "fastify/types/request.js";
|
||||
import { z } from "zod";
|
||||
import { Config } from "../config.js";
|
||||
import type { HomeAssistantService } from "../homeassistant/service.js";
|
||||
import type { HomepageService } from "./service.js";
|
||||
|
||||
export async function homepageRoutes(
|
||||
fastify: FastifyInstance,
|
||||
{
|
||||
hpService,
|
||||
haService,
|
||||
verifyAPIKey,
|
||||
}: {
|
||||
hpService: HomepageService;
|
||||
haService: HomeAssistantService;
|
||||
verifyAPIKey: (
|
||||
request: FastifyRequest,
|
||||
reply: FastifyReply,
|
||||
) => Promise<void>;
|
||||
},
|
||||
) {
|
||||
fastify.get(
|
||||
@@ -47,4 +60,96 @@ export async function homepageRoutes(
|
||||
return service_result.result;
|
||||
},
|
||||
);
|
||||
|
||||
fastify.post(
|
||||
"/homepage/update/homeassistant",
|
||||
{
|
||||
preHandler: verifyAPIKey,
|
||||
schema: {
|
||||
description: "Update information for component on dpu status page",
|
||||
tags: ["homepage"],
|
||||
body: z.custom<unknown>(),
|
||||
response: {
|
||||
200: z.string(),
|
||||
418: z.object({
|
||||
error: z.string(),
|
||||
}),
|
||||
},
|
||||
hide: true,
|
||||
},
|
||||
},
|
||||
async (request, reply) => {
|
||||
const ha_update = request.body as HA_Update;
|
||||
const ha_entity: HomeAssistantEntity = {
|
||||
entity_id: ha_update.entity_id,
|
||||
state: ha_update.state,
|
||||
attributes: ha_update.attributes,
|
||||
last_changed: ha_update.timestamp,
|
||||
};
|
||||
const updates: ComponentUpdate[] = [];
|
||||
|
||||
switch (ha_update.entity_id) {
|
||||
case Config.homeassistant.id_sensor_desk_binary: {
|
||||
updates.push({
|
||||
component: "desk",
|
||||
data: haService.convertPosResultToApiAnswer(
|
||||
haService.convertHaEntityToPosResult(ha_entity),
|
||||
),
|
||||
});
|
||||
break;
|
||||
}
|
||||
case Config.homeassistant.id_sensor_roomtemp:
|
||||
updates.push({
|
||||
component: "desk",
|
||||
data: ha_entity.state,
|
||||
});
|
||||
break;
|
||||
default:
|
||||
}
|
||||
|
||||
const service_result = await hpService.updatePartial(updates);
|
||||
|
||||
if (!service_result.successful) {
|
||||
reply.code(418);
|
||||
return { error: service_result.result };
|
||||
}
|
||||
|
||||
return service_result.result;
|
||||
},
|
||||
);
|
||||
|
||||
fastify.post(
|
||||
"/homepage/update/tidal",
|
||||
{
|
||||
preHandler: verifyAPIKey,
|
||||
schema: {
|
||||
description: "Update information for component on dpu status page",
|
||||
tags: ["homepage"],
|
||||
body: z.custom<TidalGetCurrent>(),
|
||||
response: {
|
||||
200: z.string(),
|
||||
418: z.object({
|
||||
error: z.string(),
|
||||
}),
|
||||
},
|
||||
hide: true,
|
||||
},
|
||||
},
|
||||
async (request, reply) => {
|
||||
const update = request.body as TidalGetCurrent;
|
||||
const service_result = await hpService.updatePartial([
|
||||
{
|
||||
component: "tidal",
|
||||
data: update,
|
||||
},
|
||||
]);
|
||||
|
||||
if (!service_result.successful) {
|
||||
reply.code(418);
|
||||
return { error: service_result.result };
|
||||
}
|
||||
|
||||
return service_result.result;
|
||||
},
|
||||
);
|
||||
}
|
||||
|
||||
@@ -9,7 +9,7 @@ import {
|
||||
type TidalGetCurrent,
|
||||
type WsService,
|
||||
} from "@dpu/shared";
|
||||
import { logInfo, logWarning } from "@dpu/shared/dist/logger.js";
|
||||
import { logInfo } from "@dpu/shared/dist/logger.js";
|
||||
import type { GristService } from "../grist/service";
|
||||
import type { HomeAssistantService } from "../homeassistant/service";
|
||||
import type { TidalService } from "../tidal/service";
|
||||
@@ -58,10 +58,8 @@ export class HomepageService extends BaseService<null> {
|
||||
grist_personal_goals: personal_goals,
|
||||
}),
|
||||
);
|
||||
} catch {
|
||||
const error_message = "error getting all information";
|
||||
logWarning(error_message);
|
||||
return this.getErrorResult(error_message);
|
||||
} catch (error) {
|
||||
return this.getErrorResult("error getting all information.", error);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -75,7 +73,7 @@ export class HomepageService extends BaseService<null> {
|
||||
}
|
||||
|
||||
private async _getTemp(): Promise<string | null> {
|
||||
const temp = await this.haService.getTemperatureText();
|
||||
const temp = await this.haService.getTemperature();
|
||||
return temp.successful ? temp.result : null;
|
||||
}
|
||||
|
||||
@@ -110,22 +108,37 @@ export class HomepageService extends BaseService<null> {
|
||||
return newPoll;
|
||||
}
|
||||
|
||||
async updatePartial(components: string[]): Promise<void> {
|
||||
async updatePartial(
|
||||
components: ComponentUpdate[],
|
||||
): Promise<ServiceResult<string>> {
|
||||
const updates: ComponentUpdate[] = [];
|
||||
for (const component of components) {
|
||||
switch (component) {
|
||||
switch (component.component) {
|
||||
case "desk": {
|
||||
this.updateHaDesk(await this._getDesk(), updates);
|
||||
this.updateHaDesk(
|
||||
(component.data as API_HA_DeskPosition) ?? (await this._getDesk()),
|
||||
updates,
|
||||
);
|
||||
break;
|
||||
}
|
||||
case "temp":
|
||||
this.updateHaTemp(await this._getTemp(), updates);
|
||||
this.updateHaTemp(
|
||||
(component.data as string) ?? (await this._getTemp()),
|
||||
updates,
|
||||
);
|
||||
break;
|
||||
case "tidal":
|
||||
this.updateTidal(await this._getTidal(), updates);
|
||||
this.updateTidal(
|
||||
(component.data as TidalGetCurrent) ?? (await this._getTidal()),
|
||||
updates,
|
||||
);
|
||||
break;
|
||||
case "grist":
|
||||
this.updateGristPG(await this._getGristPG(), updates);
|
||||
this.updateGristPG(
|
||||
(component.data as GristRecord_PersonalGoals) ??
|
||||
(await this._getGristPG()),
|
||||
updates,
|
||||
);
|
||||
break;
|
||||
default:
|
||||
}
|
||||
@@ -135,9 +148,11 @@ export class HomepageService extends BaseService<null> {
|
||||
type: "update",
|
||||
data: updates,
|
||||
});
|
||||
|
||||
return this.getSuccessfulResult("update broadcasted");
|
||||
}
|
||||
|
||||
private updateHaDesk(
|
||||
updateHaDesk(
|
||||
new_ha_desk_position: API_HA_DeskPosition | null,
|
||||
updates: ComponentUpdate[],
|
||||
): void {
|
||||
@@ -153,10 +168,7 @@ export class HomepageService extends BaseService<null> {
|
||||
}
|
||||
}
|
||||
|
||||
private updateHaTemp(
|
||||
new_ha_temp: string | null,
|
||||
updates: ComponentUpdate[],
|
||||
): void {
|
||||
updateHaTemp(new_ha_temp: string | null, updates: ComponentUpdate[]): void {
|
||||
if (this.lastPoll.ha_temp !== new_ha_temp) {
|
||||
this.lastPoll.ha_temp = new_ha_temp;
|
||||
updates.push({
|
||||
@@ -166,7 +178,7 @@ export class HomepageService extends BaseService<null> {
|
||||
}
|
||||
}
|
||||
|
||||
private updateTidal(
|
||||
updateTidal(
|
||||
new_tidal_current: TidalGetCurrent | null,
|
||||
updates: ComponentUpdate[],
|
||||
): void {
|
||||
@@ -184,7 +196,7 @@ export class HomepageService extends BaseService<null> {
|
||||
}
|
||||
}
|
||||
|
||||
private updateGristPG(
|
||||
updateGristPG(
|
||||
new_grist_personal_goals: GristRecord_PersonalGoals | null,
|
||||
updates: ComponentUpdate[],
|
||||
): void {
|
||||
@@ -227,10 +239,16 @@ export class HomepageService extends BaseService<null> {
|
||||
startPolling(): void {
|
||||
logInfo("Polling started");
|
||||
|
||||
const config: [string[], number][] = [
|
||||
[["tidal"], 20_000],
|
||||
[["desk"], 60_000],
|
||||
[["temp", "grist"], 600_000],
|
||||
const config: [ComponentUpdate[], number][] = [
|
||||
//[[{ component: "tidal", data: null }], 20_000],
|
||||
//[[{ component: "desk", data: null }], 60_000],
|
||||
[
|
||||
[
|
||||
//{ component: "temp", data: null },
|
||||
{ component: "grist", data: null },
|
||||
],
|
||||
600_000,
|
||||
],
|
||||
];
|
||||
|
||||
this.pollingIntervals = config.map(([components, interval]) =>
|
||||
|
||||
10
src/index.ts
10
src/index.ts
@@ -8,10 +8,10 @@ import type { FastifyReply, FastifyRequest } from "fastify";
|
||||
import Fastify from "fastify";
|
||||
import fastifyAxios from "fastify-axios";
|
||||
import {
|
||||
jsonSchemaTransform,
|
||||
serializerCompiler,
|
||||
validatorCompiler,
|
||||
type ZodTypeProvider,
|
||||
jsonSchemaTransform,
|
||||
serializerCompiler,
|
||||
validatorCompiler,
|
||||
type ZodTypeProvider,
|
||||
} from "fastify-type-provider-zod";
|
||||
import { SwaggerTheme, SwaggerThemeNameEnum } from "swagger-themes";
|
||||
import { z } from "zod";
|
||||
@@ -129,7 +129,7 @@ await fastify.register(gristRoutes, { gristService });
|
||||
await fastify.register(homeAssistantRoutes, { haService, verifyAPIKey });
|
||||
await fastify.register(tidalRoutes, { tidalService, verifyAPIKey });
|
||||
await fastify.register(wsRoutes, { wsService });
|
||||
await fastify.register(homepageRoutes, { hpService });
|
||||
await fastify.register(homepageRoutes, { hpService, haService, verifyAPIKey });
|
||||
|
||||
fastify.get(
|
||||
"/ping",
|
||||
|
||||
@@ -3,7 +3,6 @@ import {
|
||||
type ServiceResult,
|
||||
type TidalGetCurrent,
|
||||
} from "@dpu/shared";
|
||||
import { logWarning } from "@dpu/shared/dist/logger.js";
|
||||
import type { TidalClient } from "./client.js";
|
||||
|
||||
export class TidalService extends BaseService<TidalClient> {
|
||||
@@ -27,10 +26,8 @@ export class TidalService extends BaseService<TidalClient> {
|
||||
const response = await this.getClient().get<TidalGetCurrent>("current");
|
||||
|
||||
return this.getSuccessfulResult(response);
|
||||
} catch {
|
||||
const error_message = "error getting song from tidal";
|
||||
logWarning(error_message);
|
||||
return this.getErrorResult(error_message);
|
||||
} catch (error) {
|
||||
return this.getErrorResult("error getting song from tidal.", error);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -41,10 +38,8 @@ export class TidalService extends BaseService<TidalClient> {
|
||||
return this.getSuccessfulResult(
|
||||
Math.round(response.volume * 100), // * 100 because it's a decimal and we want a percentage
|
||||
);
|
||||
} catch {
|
||||
const error_message = "error getting volume from tidal";
|
||||
logWarning(error_message);
|
||||
return this.getErrorResult(error_message);
|
||||
} catch (error) {
|
||||
return this.getErrorResult("error getting volume from tidal.", error);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -73,9 +68,7 @@ export class TidalService extends BaseService<TidalClient> {
|
||||
return await this.setVolumeToTidal(clampValue);
|
||||
}
|
||||
|
||||
const error_message = "error parsing volume to set";
|
||||
logWarning(error_message);
|
||||
return this.getErrorResult(error_message);
|
||||
return this.getErrorResult("error parsing volume to set.");
|
||||
}
|
||||
|
||||
async setVolumeToTidal(
|
||||
@@ -88,10 +81,8 @@ export class TidalService extends BaseService<TidalClient> {
|
||||
);
|
||||
|
||||
return this.getSuccessfulResult(Math.round(volume));
|
||||
} catch {
|
||||
const error_message = "error setting volume from tidal";
|
||||
logWarning(error_message);
|
||||
return this.getErrorResult(error_message);
|
||||
} catch (error) {
|
||||
return this.getErrorResult("error setting volume from tidal.", error);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user