This repository has been archived on 2026-10-08. You can view files and clone it, but cannot push or open issues or pull requests.

118 lines
3.0 KiB
JavaScript

import config from "../config.js";
import { Device, Patch } from "../db/models/index.js";
import { getLogger } from "../logger.js";
import {
formatClientPatches,
updatePatchesFromUplinkPayload,
} from "./patches.js";
import { applyTankLevelFromPayload } from "./waterTank.js";
import { applyPowerFromPayload } from "./powerState.js";
const logger = getLogger("Devices");
export function toPlainDevice(doc, patchDocs = []) {
if (!doc) return null;
const o = doc.toObject ? doc.toObject() : doc;
o.id = o._id?.toString();
delete o._id;
o.__v = undefined;
o.patches = formatClientPatches(patchDocs);
return o;
}
/** Single TTN device from config — used for watering and lookups. */
export async function getConfiguredDevice() {
const { ttnApplicationId, ttnDeviceId } = config.device;
if (!ttnApplicationId?.trim() || !ttnDeviceId?.trim()) return null;
return Device.findOne({
ttnApplicationId: ttnApplicationId.trim(),
ttnDeviceId: ttnDeviceId.trim(),
});
}
/**
* Ensures exactly one Device row matches config.device; removes others and their patches.
*/
export async function syncDeviceFromConfig(cfg) {
const { name, ttnApplicationId, ttnDeviceId } = cfg.device || {};
const tid = ttnApplicationId?.trim();
const did = ttnDeviceId?.trim();
if (!tid || !did) {
logger.warn(
"config.device: set ttnApplicationId and ttnDeviceId (or DEMO_TTN_* / DEVICE_TTN_* env)",
);
return;
}
const displayName = (name && String(name).trim()) || "Allotment node";
const match = await Device.findOne({
ttnApplicationId: tid,
ttnDeviceId: did,
});
const toRemove = await Device.find(
match ? { _id: { $ne: match._id } } : {},
);
for (const d of toRemove) {
await Patch.deleteMany({ deviceId: d._id });
await Device.deleteOne({ _id: d._id });
}
if (!match) {
await Device.create({
name: displayName,
ttnApplicationId: tid,
ttnDeviceId: did,
});
logger.info("Created device from config.device");
return;
}
if (match.name !== displayName) {
match.name = displayName;
await match.save();
}
}
export async function applyTtnUplink({ applicationId, deviceId, decodedPayload }) {
if (!decodedPayload || typeof decodedPayload !== "object") return null;
const tid = config.device.ttnApplicationId?.trim();
const did = config.device.ttnDeviceId?.trim();
if (
!tid ||
!did ||
applicationId !== tid ||
deviceId !== did
) {
return { skipped: true, reason: "not_configured_device" };
}
const dev = await Device.findOne({
ttnApplicationId: tid,
ttnDeviceId: did,
});
if (!dev) {
return { skipped: true, reason: "no_matching_device" };
}
const payload = decodedPayload;
await updatePatchesFromUplinkPayload(dev, payload);
const tankLevel = await applyTankLevelFromPayload(payload);
const power = await applyPowerFromPayload(payload);
dev.online = true;
await dev.save();
const patchDocs = await Patch.find({ deviceId: dev._id }).lean();
return {
device: toPlainDevice(dev, patchDocs),
tankLevel,
power,
};
}