rewrite typeysation
This commit is contained in:
@@ -1,14 +1,8 @@
|
||||
import type { HomeAssistantEntity } from "@dpu/shared";
|
||||
import { BaseClient } from "@dpu/shared/dist/fastify";
|
||||
import { printNetworkError } from "@dpu/shared/dist/logger.js";
|
||||
import type { AxiosInstance } from "axios";
|
||||
|
||||
export class HomeAssistantClient {
|
||||
private axiosInstance: AxiosInstance;
|
||||
|
||||
constructor(axiosInstance: AxiosInstance) {
|
||||
this.axiosInstance = axiosInstance;
|
||||
}
|
||||
|
||||
export class HomeAssistantClient extends BaseClient {
|
||||
async getEntityStates(entityIds: string[]): Promise<HomeAssistantEntity[]> {
|
||||
try {
|
||||
const promises = entityIds.map((id) => this.getEntityState(id));
|
||||
@@ -21,7 +15,7 @@ export class HomeAssistantClient {
|
||||
|
||||
async getEntityState(entityId: string): Promise<HomeAssistantEntity> {
|
||||
try {
|
||||
const response = await this.axiosInstance.get<HomeAssistantEntity>(
|
||||
const response = await this.getAxios().get<HomeAssistantEntity>(
|
||||
`states/${entityId}`,
|
||||
);
|
||||
return response.data;
|
||||
@@ -33,7 +27,7 @@ export class HomeAssistantClient {
|
||||
|
||||
async triggerWebhook(webhookId: string): Promise<unknown> {
|
||||
try {
|
||||
const response = await this.axiosInstance.post<HomeAssistantEntity>(
|
||||
const response = await this.getAxios().post<HomeAssistantEntity>(
|
||||
`webhook/${webhookId}`,
|
||||
);
|
||||
return response.data;
|
||||
|
||||
@@ -2,26 +2,23 @@ import type {
|
||||
HomeAssistantDeskPositionResult,
|
||||
HomeAssistantEntity,
|
||||
} from "@dpu/shared";
|
||||
import { BaseService, type ServiceResult } from "@dpu/shared/dist/fastify.js";
|
||||
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";
|
||||
|
||||
export class HomeAssistantService {
|
||||
private client: HomeAssistantClient;
|
||||
|
||||
constructor(client: HomeAssistantClient) {
|
||||
this.client = client;
|
||||
}
|
||||
|
||||
async startStandingAutomation(): Promise<unknown | null> {
|
||||
export class HomeAssistantService extends BaseService<HomeAssistantClient> {
|
||||
async startStandingAutomation(): Promise<ServiceResult<unknown | string>> {
|
||||
try {
|
||||
const position = await this.getDeskPosition();
|
||||
const positionResult = await this.getDeskPosition();
|
||||
|
||||
if (position === null) {
|
||||
throw Error("error accessing desk api");
|
||||
if (!positionResult.successful) {
|
||||
throw Error(positionResult.result as string);
|
||||
}
|
||||
|
||||
const position = positionResult.result as HomeAssistantDeskPositionResult;
|
||||
|
||||
if (position.as_boolean) {
|
||||
throw Error("desk is already in standing position");
|
||||
}
|
||||
@@ -30,24 +27,29 @@ export class HomeAssistantService {
|
||||
throw Error("desk has moved too recently");
|
||||
}
|
||||
|
||||
return await this.client.triggerWebhook(
|
||||
const result = await this.getClient().triggerWebhook(
|
||||
Config.homeassistant.id_webhook_stand,
|
||||
);
|
||||
|
||||
return this.getSuccessfulResult(result);
|
||||
} catch (error) {
|
||||
logWarning("Error starting stand automation:", error);
|
||||
return null;
|
||||
const error_message = "error starting stand automation";
|
||||
logWarning(error_message, error);
|
||||
return this.getErrorResult(error_message);
|
||||
}
|
||||
}
|
||||
|
||||
async getDeskPosition(): Promise<HomeAssistantDeskPositionResult | null> {
|
||||
async getDeskPosition(): Promise<
|
||||
ServiceResult<HomeAssistantDeskPositionResult | string>
|
||||
> {
|
||||
try {
|
||||
const raw = await this.client.getEntityState(
|
||||
const raw = await this.getClient().getEntityState(
|
||||
Config.homeassistant.id_desk_sensor_binary,
|
||||
);
|
||||
|
||||
const position = Number(raw.state);
|
||||
|
||||
return {
|
||||
const result = {
|
||||
raw,
|
||||
as_boolean: position === 1,
|
||||
as_text: () => {
|
||||
@@ -60,13 +62,16 @@ export class HomeAssistantService {
|
||||
Date.now(),
|
||||
),
|
||||
};
|
||||
|
||||
return this.getSuccessfulResult(result);
|
||||
} catch (error) {
|
||||
logWarning("Error getting desk position:", error);
|
||||
return null;
|
||||
const error_message = "error getting desk position";
|
||||
logWarning(error_message, error);
|
||||
return this.getErrorResult(error_message);
|
||||
}
|
||||
}
|
||||
|
||||
async getTemperatureText(): Promise<string | null> {
|
||||
async getTemperatureText(): Promise<ServiceResult<string>> {
|
||||
try {
|
||||
const entities = await this.getTemperatures();
|
||||
const values = entities
|
||||
@@ -76,30 +81,23 @@ export class HomeAssistantService {
|
||||
values.length > 0
|
||||
? values.reduce((sum, value) => sum + value, 0) / values.length
|
||||
: 0;
|
||||
return average.toFixed(2);
|
||||
const result = average.toFixed(2);
|
||||
return this.getSuccessfulResult(result);
|
||||
} catch (error) {
|
||||
logWarning(`Error getting temperature as text`, error);
|
||||
return null;
|
||||
const error_message = "error getting temperature as text";
|
||||
logWarning(error_message, error);
|
||||
return this.getErrorResult(error_message);
|
||||
}
|
||||
}
|
||||
|
||||
async getTemperatures(): Promise<HomeAssistantEntity[]> {
|
||||
private async getTemperatures(): Promise<HomeAssistantEntity[]> {
|
||||
try {
|
||||
return await this.client.getEntityStates(
|
||||
return await this.getClient().getEntityStates(
|
||||
Config.homeassistant.id_room_sensors,
|
||||
);
|
||||
} catch (error) {
|
||||
logWarning("Error getting temperatures:", error);
|
||||
logWarning("error getting temperatures:", error);
|
||||
return [];
|
||||
}
|
||||
}
|
||||
|
||||
async getTemperature(entityId: string): Promise<HomeAssistantEntity | null> {
|
||||
try {
|
||||
return await this.client.getEntityState(entityId);
|
||||
} catch (error) {
|
||||
logWarning(`Error getting temperature for ${entityId}:`, error);
|
||||
return null;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
78
src/index.ts
78
src/index.ts
@@ -1,3 +1,4 @@
|
||||
import type { HomeAssistantDeskPositionResult } from "@dpu/shared";
|
||||
import { logInfo } from "@dpu/shared/dist/logger.js";
|
||||
import type { FastifyReply, FastifyRequest } from "fastify";
|
||||
import fastify from "fastify";
|
||||
@@ -44,17 +45,20 @@ const tidalService = new TidalService(tidalClient);
|
||||
|
||||
// HOME ASSISTANT
|
||||
server.get("/homeassistant/desk/position", async (_request, reply) => {
|
||||
const result = await haService.getDeskPosition();
|
||||
const service_result = await haService.getDeskPosition();
|
||||
|
||||
if (!result) {
|
||||
reply.code(500);
|
||||
return { error: "Failed to get desk position" };
|
||||
if (!service_result.successful) {
|
||||
reply.code(418);
|
||||
return { error: service_result.result };
|
||||
}
|
||||
|
||||
const position_result =
|
||||
service_result.result as HomeAssistantDeskPositionResult;
|
||||
|
||||
return {
|
||||
position: result.as_text(),
|
||||
is_standing: result.as_boolean,
|
||||
last_changed: result.last_changed.toReadable(true),
|
||||
position: position_result.as_text(),
|
||||
is_standing: position_result.as_boolean,
|
||||
last_changed: position_result.last_changed.toReadable(true),
|
||||
};
|
||||
});
|
||||
|
||||
@@ -62,60 +66,60 @@ server.post(
|
||||
"/homeassistant/desk/stand",
|
||||
{ preHandler: verifyAPIKey },
|
||||
async (_request, reply) => {
|
||||
const result = await haService.startStandingAutomation();
|
||||
const service_result = await haService.startStandingAutomation();
|
||||
|
||||
if (!result) {
|
||||
reply.code(500);
|
||||
return { error: "Failed to start stand automation" };
|
||||
if (!service_result.successful) {
|
||||
reply.code(418);
|
||||
return { error: service_result.result };
|
||||
}
|
||||
|
||||
return result;
|
||||
return service_result.result;
|
||||
},
|
||||
);
|
||||
|
||||
server.get("/homeassistant/temperature", async (_request, reply) => {
|
||||
const result = await haService.getTemperatureText();
|
||||
const service_result = await haService.getTemperatureText();
|
||||
|
||||
if (!result) {
|
||||
reply.code(500);
|
||||
return { error: "Failed to get temperature" };
|
||||
if (!service_result.successful) {
|
||||
reply.code(418);
|
||||
return { error: service_result.result };
|
||||
}
|
||||
|
||||
return result;
|
||||
return service_result.result;
|
||||
});
|
||||
|
||||
// TIDAL
|
||||
server.get("/tidal/song", async (_request, reply) => {
|
||||
const result = await tidalService.getSong();
|
||||
const service_result = await tidalService.getSong();
|
||||
|
||||
if (!result) {
|
||||
reply.code(500);
|
||||
return { error: "Failed to get song" };
|
||||
if (!service_result.successful) {
|
||||
reply.code(418);
|
||||
return { error: service_result.result };
|
||||
}
|
||||
|
||||
return result;
|
||||
return service_result.result;
|
||||
});
|
||||
|
||||
server.get("/tidal/songFormatted", async (_request, reply) => {
|
||||
const result = await tidalService.getSongFormatted();
|
||||
const service_result = await tidalService.getSongFormatted();
|
||||
|
||||
if (!result) {
|
||||
reply.code(500);
|
||||
return { error: "Failed to get song" };
|
||||
if (!service_result.successful) {
|
||||
reply.code(418);
|
||||
return { error: service_result.result };
|
||||
}
|
||||
|
||||
return result;
|
||||
return service_result.result;
|
||||
});
|
||||
|
||||
server.get("/tidal/volume", async (_request, reply) => {
|
||||
const result = await tidalService.getVolume();
|
||||
const service_result = await tidalService.getVolume();
|
||||
|
||||
if (!result) {
|
||||
reply.code(500);
|
||||
return { error: "Failed to get volume" };
|
||||
if (!service_result.successful) {
|
||||
reply.code(418);
|
||||
return { error: service_result.result };
|
||||
}
|
||||
|
||||
return result;
|
||||
return service_result.result;
|
||||
});
|
||||
|
||||
server.post(
|
||||
@@ -123,14 +127,14 @@ server.post(
|
||||
{ preHandler: verifyAPIKey },
|
||||
async (request, reply) => {
|
||||
const volume = request.body as string;
|
||||
const result = await tidalService.setVolume(volume);
|
||||
const service_result = await tidalService.setVolume(volume);
|
||||
|
||||
if (!result) {
|
||||
reply.code(500);
|
||||
return { error: "Failed to set volume" };
|
||||
if (!service_result.successful) {
|
||||
reply.code(418);
|
||||
return { error: service_result.result };
|
||||
}
|
||||
|
||||
return result;
|
||||
return service_result.result;
|
||||
},
|
||||
);
|
||||
|
||||
|
||||
@@ -1,16 +1,10 @@
|
||||
import { BaseClient } from "@dpu/shared/dist/fastify";
|
||||
import { printNetworkError } from "@dpu/shared/dist/logger.js";
|
||||
import type { AxiosInstance } from "axios";
|
||||
|
||||
export class TidalClient {
|
||||
private axiosInstance: AxiosInstance;
|
||||
|
||||
constructor(axiosInstance: AxiosInstance) {
|
||||
this.axiosInstance = axiosInstance;
|
||||
}
|
||||
|
||||
export class TidalClient extends BaseClient {
|
||||
async get<T>(endpoint: string): Promise<T> {
|
||||
try {
|
||||
const response = await this.axiosInstance.get<T>(`/${endpoint}`);
|
||||
const response = await this.getAxios().get<T>(`/${endpoint}`);
|
||||
|
||||
return response.data;
|
||||
} catch (error) {
|
||||
@@ -21,7 +15,7 @@ export class TidalClient {
|
||||
|
||||
async post<T>(endpoint: string, data: unknown): Promise<T> {
|
||||
try {
|
||||
const response = await this.axiosInstance.post<T>(`/${endpoint}`, data);
|
||||
const response = await this.getAxios().post<T>(`/${endpoint}`, data);
|
||||
return response.data;
|
||||
} catch (error) {
|
||||
printNetworkError(error);
|
||||
|
||||
@@ -1,43 +1,43 @@
|
||||
import type { TidalSong, TidalVolume } from "@dpu/shared";
|
||||
import { BaseService, type ServiceResult } from "@dpu/shared/dist/fastify.js";
|
||||
import { logWarning } from "@dpu/shared/dist/logger.js";
|
||||
import type { TidalClient } from "./client.js";
|
||||
import type { TidalClient } from "./client";
|
||||
|
||||
export class TidalService {
|
||||
private client: TidalClient;
|
||||
|
||||
constructor(client: TidalClient) {
|
||||
this.client = client;
|
||||
}
|
||||
|
||||
async getSongFormatted(): Promise<string> {
|
||||
const song = await this.getSong();
|
||||
if (song) {
|
||||
export class TidalService extends BaseService<TidalClient> {
|
||||
async getSongFormatted(): Promise<ServiceResult<string>> {
|
||||
const req = await this.getSong();
|
||||
if (req.successful) {
|
||||
const song = req.result as TidalSong;
|
||||
const status = song.status === "playing" ? "▶️" : "⏸️";
|
||||
return `listening to ${song.title} by ${song.artists}. ${status} ${song.current}/${song.duration}. link: ${song.url}`;
|
||||
return this.getSuccessfulResult(
|
||||
`listening to ${song.title} by ${song.artists}. ${status} ${song.current}/${song.duration}. link: ${song.url}`,
|
||||
);
|
||||
} else {
|
||||
return `no song found`;
|
||||
return this.getErrorResult(req.result as string);
|
||||
}
|
||||
}
|
||||
|
||||
async getSong(): Promise<TidalSong | null> {
|
||||
async getSong(): Promise<ServiceResult<TidalSong | string>> {
|
||||
try {
|
||||
const response = this.client.get<TidalSong>("current");
|
||||
const response = await this.getClient().get<TidalSong>("current");
|
||||
|
||||
return response;
|
||||
return this.getSuccessfulResult(response);
|
||||
} catch {
|
||||
logWarning("error getting song from tidal");
|
||||
return null;
|
||||
const error_message = "error getting song from tidal";
|
||||
logWarning(error_message);
|
||||
return this.getErrorResult(error_message);
|
||||
}
|
||||
}
|
||||
|
||||
async getVolume(): Promise<TidalVolume | null> {
|
||||
async getVolume(): Promise<ServiceResult<TidalVolume | string>> {
|
||||
try {
|
||||
const response = await this.client.get<TidalVolume>("volume");
|
||||
const response = await this.getClient().get<TidalVolume>("volume");
|
||||
|
||||
return response;
|
||||
return this.getSuccessfulResult(response);
|
||||
} catch {
|
||||
logWarning("error getting volume from tidal");
|
||||
return null;
|
||||
const error_message = "error getting volume from tidal";
|
||||
logWarning(error_message);
|
||||
return this.getErrorResult(error_message);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -45,13 +45,16 @@ export class TidalService {
|
||||
return Math.min(Math.max(value, 0), 100);
|
||||
}
|
||||
|
||||
async setVolume(argument: string): Promise<TidalVolume | null> {
|
||||
async setVolume(
|
||||
argument: string,
|
||||
): Promise<ServiceResult<TidalVolume | string>> {
|
||||
const value = parseInt(argument, 10);
|
||||
// relative
|
||||
const adjustMatch = argument.match(/^([+-])(\d+)$/);
|
||||
if (adjustMatch) {
|
||||
const volume = await this.getVolume();
|
||||
if (volume) {
|
||||
const req = await this.getVolume();
|
||||
if (req.successful) {
|
||||
const volume = req.result as TidalVolume;
|
||||
const wantedVolume = volume.volume + value;
|
||||
const clampWantedVolume = this.clamp(wantedVolume);
|
||||
return await this.setVolumeToTidal(clampWantedVolume);
|
||||
@@ -65,19 +68,24 @@ export class TidalService {
|
||||
return await this.setVolumeToTidal(clampValue);
|
||||
}
|
||||
|
||||
return null;
|
||||
const error_message = "error parsing volume to set";
|
||||
logWarning(error_message);
|
||||
return this.getErrorResult(error_message);
|
||||
}
|
||||
|
||||
async setVolumeToTidal(volume: number): Promise<TidalVolume | null> {
|
||||
async setVolumeToTidal(
|
||||
volume: number,
|
||||
): Promise<ServiceResult<TidalVolume | string>> {
|
||||
try {
|
||||
const response = await this.client.post<TidalVolume>("volume", {
|
||||
const response = await this.getClient().post<TidalVolume>("volume", {
|
||||
volume,
|
||||
});
|
||||
|
||||
return response;
|
||||
return this.getSuccessfulResult(response);
|
||||
} catch {
|
||||
logWarning("error setting volume from tidal");
|
||||
return null;
|
||||
const error_message = "error setting volume from tidal";
|
||||
logWarning(error_message);
|
||||
return this.getErrorResult(error_message);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user