even cooler now

This commit is contained in:
Darius
2026-02-09 20:02:47 +01:00
parent ff66061c32
commit c98304fe8e
6 changed files with 80 additions and 116 deletions

10
package-lock.json generated
View File

@@ -31,8 +31,8 @@
} }
}, },
"node_modules/@dpu/shared": { "node_modules/@dpu/shared": {
"version": "1.9.6", "version": "1.9.9",
"resolved": "git+https://git.dariusbag.dev/DarDarBinks/dpu-shared.git#872d3755d06c8e8c6452d8ab7212b6426d84943b", "resolved": "git+https://git.dariusbag.dev/DarDarBinks/dpu-shared.git#d73d98068a779569e3be375f077de1e63f9efa9c",
"dependencies": { "dependencies": {
"@types/ws": "^8.18.1", "@types/ws": "^8.18.1",
"axios": "^1.7.9", "axios": "^1.7.9",
@@ -1725,9 +1725,9 @@
} }
}, },
"node_modules/pino": { "node_modules/pino": {
"version": "10.3.0", "version": "10.3.1",
"resolved": "https://registry.npmjs.org/pino/-/pino-10.3.0.tgz", "resolved": "https://registry.npmjs.org/pino/-/pino-10.3.1.tgz",
"integrity": "sha512-0GNPNzHXBKw6U/InGe79A3Crzyk9bcSyObF9/Gfo9DLEf5qj5RF50RSjsu0W1rZ6ZqRGdzDFCRBQvi9/rSGPtA==", "integrity": "sha512-r34yH/GlQpKZbU1BvFFqOjhISRo1MNx1tWYsYvmj6KIRHSPMT2+yHOEb1SG6NMvRoHRF0a07kCOox/9yakl1vg==",
"license": "MIT", "license": "MIT",
"dependencies": { "dependencies": {
"@pinojs/redact": "^0.4.0", "@pinojs/redact": "^0.4.0",

View File

@@ -1,9 +1,10 @@
import type { import {
ComponentUpdate, StatusComponent,
GristRecord_PersonalGoals, type ComponentUpdate,
HA_Update, type GristRecord_PersonalGoals,
HomeAssistantEntity, type HA_Update,
TidalGetCurrent, type HomeAssistantEntity,
type TidalGetCurrent,
} from "@dpu/shared"; } from "@dpu/shared";
import type { FastifyInstance, FastifyReply } from "fastify"; import type { FastifyInstance, FastifyReply } from "fastify";
import type { FastifyRequest } from "fastify/types/request.js"; import type { FastifyRequest } from "fastify/types/request.js";
@@ -55,7 +56,7 @@ export async function privateHomepageRoutes(
} }
const service_result = await hpService.updatePartial([ const service_result = await hpService.updatePartial([
{ {
component: "grist", component: StatusComponent.GRIST_PERSONAL_GOALS,
data: gristService.transformToPersonalGoals(record), data: gristService.transformToPersonalGoals(record),
}, },
]); ]);
@@ -99,7 +100,7 @@ export async function privateHomepageRoutes(
switch (ha_update.entity_id) { switch (ha_update.entity_id) {
case Config.homeassistant.id_sensor_desk_binary: { case Config.homeassistant.id_sensor_desk_binary: {
updates.push({ updates.push({
component: "desk", component: StatusComponent.HA_DESK_POSITION,
data: haService.convertPosResultToApiAnswer( data: haService.convertPosResultToApiAnswer(
haService.convertHaEntityToPosResult(ha_entity), haService.convertHaEntityToPosResult(ha_entity),
), ),
@@ -108,7 +109,7 @@ export async function privateHomepageRoutes(
} }
case Config.homeassistant.id_sensor_roomtemp: case Config.homeassistant.id_sensor_roomtemp:
updates.push({ updates.push({
component: "desk", component: StatusComponent.HA_TEMP,
data: ha_entity.state, data: ha_entity.state,
}); });
break; break;
@@ -147,7 +148,7 @@ export async function privateHomepageRoutes(
const update = request.body as TidalGetCurrent; const update = request.body as TidalGetCurrent;
const service_result = await hpService.updatePartial([ const service_result = await hpService.updatePartial([
{ {
component: "tidal", component: StatusComponent.TIDAL_CURRENT,
data: update, data: update,
}, },
]); ]);

View File

@@ -1,50 +0,0 @@
import type {
API_HA_DeskPosition,
GristRecord_PersonalGoals,
TidalGetCurrent,
} from "@dpu/shared";
import type { FastifyInstance } from "fastify";
import { z } from "zod";
import type { HomepageService } from "./service.js";
export async function publicHomepageRoutes(
fastify: FastifyInstance,
{
hpService,
}: {
hpService: HomepageService;
},
) {
fastify.get(
"/homepage/status",
{
schema: {
description: "Get all current information for dpu status page",
tags: ["homepage"],
response: {
200: z.object({
ha_desk_position: z.custom<API_HA_DeskPosition>().nullable(),
ha_temp: z.string().nullable(),
tidal_current: z.custom<TidalGetCurrent>().nullable(),
grist_personal_goals: z
.custom<GristRecord_PersonalGoals>()
.nullable(),
}),
418: z.object({
error: z.string(),
}),
},
},
},
async (_request, reply) => {
const service_result = await hpService.getFullInformation();
if (!service_result.successful) {
reply.code(418);
return { error: service_result.result };
}
return service_result.result;
},
);
}

View File

@@ -1,18 +1,21 @@
import { import {
type API_HA_DeskPosition, type API_HA_DeskPosition,
BaseService, BaseService,
type ComponentUpdate, type ComponentUpdate,
type FullInformation, type FullInformation,
type GristRecord_PersonalGoals, type GristRecord_PersonalGoals,
type HomeAssistantDeskPositionResult, type HomeAssistantDeskPositionResult,
type ServiceResult, type ServiceResult,
type TidalGetCurrent, type TidalGetCurrent,
type WsService, type WsService,
createComponentUpdate,
StatusComponent,
} from "@dpu/shared"; } from "@dpu/shared";
import { logInfo } from "@dpu/shared/dist/logger.js"; import { logInfo, logWarning } from "@dpu/shared/dist/logger.js";
import type { GristService } from "../grist/service"; import type { GristService } from "../grist/service";
import type { HomeAssistantService } from "../homeassistant/service"; import type { HomeAssistantService } from "../homeassistant/service";
import type { TidalService } from "../tidal/service"; import type { TidalService } from "../tidal/service";
import type { WebSocket } from "ws";
export class HomepageService extends BaseService<null> { export class HomepageService extends BaseService<null> {
private gristService: GristService; private gristService: GristService;
@@ -39,21 +42,34 @@ export class HomepageService extends BaseService<null> {
this.wsService = wsService; this.wsService = wsService;
} }
async sendFullInformationToSocket(socket: WebSocket): Promise<void> {
try {
const [desk, temp, tidal, personal_goals] = await this._getAll();
const updates: ComponentUpdate[] = [
createComponentUpdate(StatusComponent.HA_DESK_POSITION, desk),
createComponentUpdate(StatusComponent.HA_TEMP, temp),
createComponentUpdate(StatusComponent.TIDAL_CURRENT, tidal),
createComponentUpdate(StatusComponent.GRIST_PERSONAL_GOALS, personal_goals)
];
socket.send(JSON.stringify({
type: "update",
data: updates
}))
} catch (error) {
logWarning("error getting all information for socket update.", error);
}
}
async getFullInformation(): Promise<ServiceResult<FullInformation | string>> { async getFullInformation(): Promise<ServiceResult<FullInformation | string>> {
try { try {
const [desk, temp, tidal, personal_goals] = await Promise.all([ const [desk, temp, tidal, personal_goals] = await this._getAll();
this._getDesk(),
this._getTemp(),
this._getTidal(),
this._getGristPG(),
]);
return this.getSuccessfulResult( return this.getSuccessfulResult(
this.updateFull({ this.updateFull({
ha_desk_position: desk, ha_desk_position: desk as API_HA_DeskPosition,
ha_temp: temp, ha_temp: temp as string,
tidal_current: tidal, tidal_current: tidal as TidalGetCurrent,
grist_personal_goals: personal_goals, grist_personal_goals: personal_goals as GristRecord_PersonalGoals,
}), }),
); );
} catch (error) { } catch (error) {
@@ -61,6 +77,15 @@ export class HomepageService extends BaseService<null> {
} }
} }
private async _getAll(): Promise<Iterable<unknown>> {
return await Promise.all([
this._getDesk(),
this._getTemp(),
this._getTidal(),
this._getGristPG(),
]);
}
private async _getDesk(): Promise<API_HA_DeskPosition | null> { private async _getDesk(): Promise<API_HA_DeskPosition | null> {
const desk = await this.haService.getDeskPosition(); const desk = await this.haService.getDeskPosition();
return desk.successful return desk.successful
@@ -112,26 +137,26 @@ export class HomepageService extends BaseService<null> {
const updates: ComponentUpdate[] = []; const updates: ComponentUpdate[] = [];
for (const component of components) { for (const component of components) {
switch (component.component) { switch (component.component) {
case "desk": { case StatusComponent.HA_DESK_POSITION: {
this.updateHaDesk( this.updateHaDesk(
(component.data as API_HA_DeskPosition) ?? (await this._getDesk()), (component.data as API_HA_DeskPosition) ?? (await this._getDesk()),
updates, updates,
); );
break; break;
} }
case "temp": case StatusComponent.HA_TEMP:
this.updateHaTemp( this.updateHaTemp(
(component.data as string) ?? (await this._getTemp()), (component.data as string) ?? (await this._getTemp()),
updates, updates,
); );
break; break;
case "tidal": case StatusComponent.TIDAL_CURRENT:
this.updateTidal( this.updateTidal(
(component.data as TidalGetCurrent) ?? (await this._getTidal()), (component.data as TidalGetCurrent) ?? (await this._getTidal()),
updates, updates,
); );
break; break;
case "grist": case StatusComponent.GRIST_PERSONAL_GOALS:
this.updateGristPG( this.updateGristPG(
(component.data as GristRecord_PersonalGoals) ?? (component.data as GristRecord_PersonalGoals) ??
(await this._getGristPG()), (await this._getGristPG()),
@@ -159,20 +184,14 @@ export class HomepageService extends BaseService<null> {
new_ha_desk_position?.is_standing new_ha_desk_position?.is_standing
) { ) {
this.lastPoll.ha_desk_position = new_ha_desk_position; this.lastPoll.ha_desk_position = new_ha_desk_position;
updates.push({ updates.push(createComponentUpdate(StatusComponent.HA_DESK_POSITION, new_ha_desk_position));
component: "ha_desk_position",
data: new_ha_desk_position,
});
} }
} }
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) { if (this.lastPoll.ha_temp !== new_ha_temp) {
this.lastPoll.ha_temp = new_ha_temp; this.lastPoll.ha_temp = new_ha_temp;
updates.push({ updates.push(createComponentUpdate(StatusComponent.HA_TEMP, new_ha_temp));
component: "ha_temp",
data: new_ha_temp,
});
} }
} }
@@ -187,10 +206,7 @@ export class HomepageService extends BaseService<null> {
this.lastPoll.tidal_current?.volume !== new_tidal_current?.volume this.lastPoll.tidal_current?.volume !== new_tidal_current?.volume
) { ) {
this.lastPoll.tidal_current = new_tidal_current; this.lastPoll.tidal_current = new_tidal_current;
updates.push({ updates.push(createComponentUpdate(StatusComponent.TIDAL_CURRENT, new_tidal_current));
component: "tidal_current",
data: new_tidal_current,
});
} }
} }
@@ -215,10 +231,7 @@ export class HomepageService extends BaseService<null> {
new_grist_personal_goals?.stairs new_grist_personal_goals?.stairs
) { ) {
this.lastPoll.grist_personal_goals = new_grist_personal_goals; this.lastPoll.grist_personal_goals = new_grist_personal_goals;
updates.push({ updates.push(createComponentUpdate(StatusComponent.GRIST_PERSONAL_GOALS, new_grist_personal_goals));
component: "grist_personal_goals",
data: new_grist_personal_goals,
});
} }
} }
} }

View File

@@ -3,17 +3,17 @@ import { logError } from "@dpu/shared/dist/logger.js";
import fastifyCors, { type FastifyCorsOptions } from "@fastify/cors"; import fastifyCors, { type FastifyCorsOptions } from "@fastify/cors";
import fastifySwagger, { type SwaggerOptions } from "@fastify/swagger"; import fastifySwagger, { type SwaggerOptions } from "@fastify/swagger";
import fastifySwaggerUi, { import fastifySwaggerUi, {
type FastifySwaggerUiOptions, type FastifySwaggerUiOptions,
} from "@fastify/swagger-ui"; } from "@fastify/swagger-ui";
import fastifyWebsocket from "@fastify/websocket"; import fastifyWebsocket from "@fastify/websocket";
import type { FastifyReply, FastifyRequest } from "fastify"; import type { FastifyReply, FastifyRequest } from "fastify";
import Fastify from "fastify"; import Fastify from "fastify";
import fastifyAxios, { type FastifyAxiosOptions } from "fastify-axios"; import fastifyAxios, { type FastifyAxiosOptions } from "fastify-axios";
import { import {
jsonSchemaTransform, jsonSchemaTransform,
serializerCompiler, serializerCompiler,
validatorCompiler, validatorCompiler,
type ZodTypeProvider, type ZodTypeProvider,
} from "fastify-type-provider-zod"; } from "fastify-type-provider-zod";
import type { OpenAPIV3 } from "openapi-types"; import type { OpenAPIV3 } from "openapi-types";
import { SwaggerTheme, SwaggerThemeNameEnum } from "swagger-themes"; import { SwaggerTheme, SwaggerThemeNameEnum } from "swagger-themes";
@@ -26,7 +26,6 @@ import { HomeAssistantClient } from "./homeassistant/client.js";
import { privateHomeAssistantRoutes } from "./homeassistant/private-routes.js"; import { privateHomeAssistantRoutes } from "./homeassistant/private-routes.js";
import { HomeAssistantService } from "./homeassistant/service.js"; import { HomeAssistantService } from "./homeassistant/service.js";
import { privateHomepageRoutes } from "./homepage/private-routes.js"; import { privateHomepageRoutes } from "./homepage/private-routes.js";
import { publicHomepageRoutes } from "./homepage/public-routes.js";
import { HomepageService } from "./homepage/service.js"; import { HomepageService } from "./homepage/service.js";
import { TidalClient } from "./tidal/client.js"; import { TidalClient } from "./tidal/client.js";
import { privateTidalRoutes } from "./tidal/private-routes.js"; import { privateTidalRoutes } from "./tidal/private-routes.js";
@@ -168,10 +167,7 @@ async function verifyAPIKey(
} }
// Register routes // Register routes
await publicServer.register(publicWsRoutes, { wsService }); await publicServer.register(publicWsRoutes, { wsService, hpService });
await publicServer.register(publicHomepageRoutes, {
hpService,
});
await privateServer.register(privateGristRoutes, { gristService }); await privateServer.register(privateGristRoutes, { gristService });
await privateServer.register(privateHomeAssistantRoutes, { await privateServer.register(privateHomeAssistantRoutes, {

View File

@@ -1,16 +1,19 @@
import { logInfo, type WsService } from "@dpu/shared"; import { logInfo, type WsService } from "@dpu/shared";
import type { FastifyInstance } from "fastify"; import type { FastifyInstance } from "fastify";
import type { HomepageService } from "../homepage/service";
export async function publicWsRoutes( export async function publicWsRoutes(
fastify: FastifyInstance, fastify: FastifyInstance,
{ {
wsService, wsService,
hpService,
}: { }: {
wsService: WsService; wsService: WsService;
hpService: HomepageService;
}, },
) { ) {
fastify.get( fastify.get(
"/dpu/events", "/dpu/status/events",
{ {
schema: { schema: {
description: "Register for WebSocket events", description: "Register for WebSocket events",
@@ -22,6 +25,7 @@ export async function publicWsRoutes(
(socket, _request) => { (socket, _request) => {
wsService.addClient(socket); wsService.addClient(socket);
socket.send(JSON.stringify({ type: "message", data: "Connected" })); socket.send(JSON.stringify({ type: "message", data: "Connected" }));
hpService.sendFullInformationToSocket(socket)
logInfo(`Connection for client established`); logInfo(`Connection for client established`);
socket.on("close", () => { socket.on("close", () => {