111 lines
3.8 KiB
TypeScript
111 lines
3.8 KiB
TypeScript
import type { QueryApi } from "@influxdata/influxdb-client";
|
|
import type { Env } from "../env.js";
|
|
import type { PowerSample } from "../types.js";
|
|
|
|
interface SeriesConfig {
|
|
bucket: string;
|
|
measurement: string;
|
|
field: string;
|
|
fieldUnit: "W" | "kW";
|
|
domain?: string;
|
|
entityId?: string;
|
|
}
|
|
|
|
/**
|
|
* Fetches raw samples for one series between `sinceMs` and now, converting
|
|
* the configured field to watts based on `fieldUnit`.
|
|
*/
|
|
async function fetchSeriesSince(
|
|
queryApi: QueryApi,
|
|
config: SeriesConfig,
|
|
sinceMs: number,
|
|
): Promise<PowerSample[]> {
|
|
const sinceIso = new Date(sinceMs).toISOString();
|
|
const domainFilter = config.domain
|
|
? `|> filter(fn: (r) => r["domain"] == "${config.domain}")\n `
|
|
: "";
|
|
const entityFilter = config.entityId
|
|
? `|> filter(fn: (r) => r["entity_id"] == "${config.entityId}")\n `
|
|
: "";
|
|
const flux = `
|
|
from(bucket: "${config.bucket}")
|
|
|> range(start: ${sinceIso})
|
|
|> filter(fn: (r) => r["_measurement"] == "${config.fieldUnit}")
|
|
|> filter(fn: (r) => r["_field"] == "${config.field}")
|
|
${domainFilter}${entityFilter}|> sort(columns: ["_time"])
|
|
`;
|
|
|
|
const unitMultiplier = config.fieldUnit === "kW" ? 1000 : 1;
|
|
const samples: PowerSample[] = [];
|
|
|
|
for await (const { values, tableMeta } of queryApi.iterateRows(flux)) {
|
|
const row = tableMeta.toObject(values) as { _time: string; _value: number };
|
|
samples.push({
|
|
timestampMs: new Date(row._time).getTime(),
|
|
watts: row._value * unitMultiplier,
|
|
});
|
|
}
|
|
|
|
return samples;
|
|
}
|
|
|
|
function housePowerConfig(env: Env): SeriesConfig {
|
|
return {
|
|
bucket: env.INFLUX_BUCKET,
|
|
measurement: env.INFLUX_MEASUREMENT,
|
|
field: env.INFLUX_FIELD,
|
|
fieldUnit: env.INFLUX_FIELD_UNIT,
|
|
domain: env.INFLUX_DOMAIN,
|
|
entityId: env.INFLUX_ENTITY_ID,
|
|
};
|
|
}
|
|
|
|
/**
|
|
* Fetches raw power samples between `sinceMs` and now.
|
|
* Converts the configured field to watts based on INFLUX_FIELD_UNIT.
|
|
*/
|
|
export async function fetchPowerSamplesSince(
|
|
queryApi: QueryApi,
|
|
env: Env,
|
|
sinceMs: number,
|
|
): Promise<PowerSample[]> {
|
|
return fetchSeriesSince(queryApi, housePowerConfig(env), sinceMs);
|
|
}
|
|
|
|
/** Fetches the single most recent power sample, if any exists within the lookback window. */
|
|
export async function fetchLatestPowerSample(
|
|
queryApi: QueryApi,
|
|
env: Env,
|
|
lookbackMs = 60_000,
|
|
): Promise<PowerSample | null> {
|
|
const samples = await fetchPowerSamplesSince(
|
|
queryApi,
|
|
env,
|
|
Date.now() - lookbackMs,
|
|
);
|
|
return samples.length > 0 ? samples[samples.length - 1] : null;
|
|
}
|
|
|
|
/**
|
|
* Fetches the most recent solar production sample from InfluxDB, if any
|
|
* exists within the lookback window. Returns null when no solar production
|
|
* series is configured (SOLAR_INFLUX_ENTITY_ID unset).
|
|
*/
|
|
export async function fetchLatestSolarProductionSample(
|
|
queryApi: QueryApi,
|
|
env: Env,
|
|
lookbackMs = 60_000,
|
|
): Promise<PowerSample | null> {
|
|
if (!env.SOLAR_INFLUX_ENTITY_ID) return null;
|
|
const config: SeriesConfig = {
|
|
bucket: env.INFLUX_BUCKET,
|
|
measurement: env.SOLAR_INFLUX_MEASUREMENT,
|
|
field: env.SOLAR_INFLUX_FIELD,
|
|
fieldUnit: env.SOLAR_INFLUX_FIELD_UNIT,
|
|
domain: env.SOLAR_INFLUX_DOMAIN,
|
|
entityId: env.SOLAR_INFLUX_ENTITY_ID,
|
|
};
|
|
const samples = await fetchSeriesSince(queryApi, config, Date.now() - lookbackMs);
|
|
return samples.length > 0 ? samples[samples.length - 1] : null;
|
|
}
|