influxdb implementation

This commit is contained in:
2026-06-22 18:55:32 +03:00
parent 60277dfbd7
commit ff7d0d4ee1
4 changed files with 88 additions and 14 deletions
+15 -1
View File
@@ -18,6 +18,12 @@ interface Config {
port: number; port: number;
password: string; password: string;
}; };
influx: {
url: string;
token: string;
org: string;
bucket: string;
};
core: { core: {
hysteresis: number; hysteresis: number;
candidateHysteresis: number; candidateHysteresis: number;
@@ -49,13 +55,21 @@ const config: Config = {
port: parseInt(process.env.REDIS_PORT ?? "6379"), port: parseInt(process.env.REDIS_PORT ?? "6379"),
password: process.env.REDIS_PASSWORD ?? "redispassword", password: process.env.REDIS_PASSWORD ?? "redispassword",
}, },
influx: {
url: process.env.INFLUX_URL ?? "http://10.24.4.13:8086",
token: process.env.INFLUX_TOKEN ?? "",
org: process.env.INFLUX_ORG ?? "officesense",
bucket: process.env.INFLUX_BUCKET ?? "officesense",
},
core: { core: {
hysteresis: parseInt(process.env.CORE_HYSTERESIS ?? "6"), hysteresis: parseInt(process.env.CORE_HYSTERESIS ?? "6"),
candidateHysteresis: parseInt(process.env.CORE_CANDIDATE_HYSTERESIS ?? "5"), candidateHysteresis: parseInt(process.env.CORE_CANDIDATE_HYSTERESIS ?? "5"),
debounceMS: parseInt(process.env.CORE_DEBOUNCE_MS ?? String(3 * 1000)), debounceMS: parseInt(process.env.CORE_DEBOUNCE_MS ?? String(3 * 1000)),
minSamples: parseInt(process.env.CORE_MIN_SAMPLES ?? "4"), minSamples: parseInt(process.env.CORE_MIN_SAMPLES ?? "4"),
transitionTTL: parseInt(process.env.CORE_TRANSITION_TTL ?? String(5 * 60 * 1000)), transitionTTL: parseInt(process.env.CORE_TRANSITION_TTL ?? String(5 * 60 * 1000)),
transitionCleanupInterval: parseInt(process.env.CORE_TRANSITION_CLEANUP_INTERVAL ?? String(60 * 1000)), transitionCleanupInterval: parseInt(
process.env.CORE_TRANSITION_CLEANUP_INTERVAL ?? String(60 * 1000)
),
lossThreshold: parseInt(process.env.CORE_LOSS_THRESHOLD ?? String(8 * 1000)), lossThreshold: parseInt(process.env.CORE_LOSS_THRESHOLD ?? String(8 * 1000)),
userTTL: parseInt(process.env.CORE_USER_TTL ?? String(3 * 60 * 1000)), userTTL: parseInt(process.env.CORE_USER_TTL ?? String(3 * 60 * 1000)),
verifyTimeout: parseInt(process.env.CORE_VERIFY_TIMEOUT ?? String(5 * 60 * 1000)), verifyTimeout: parseInt(process.env.CORE_VERIFY_TIMEOUT ?? String(5 * 60 * 1000)),
+47 -12
View File
@@ -3,15 +3,45 @@ import { prisma } from "../lib/prisma.js";
import { RoomTransition, transitions } from "./transition.js"; import { RoomTransition, transitions } from "./transition.js";
import config from "../config/config.js"; import config from "../config/config.js";
import { type RedisUserData } from "../api/livedata/livedata.repository.js"; import { type RedisUserData } from "../api/livedata/livedata.repository.js";
import { getInflux } from "../influx/influx.js";
import { Point } from "@influxdata/influxdb-client";
const adjectives = ["Crazy", "Silent", "Dark", "Fast", "Lucky", "Wild", "Epic"]; const adjectives = ["Crazy", "Silent", "Dark", "Fast", "Lucky", "Wild", "Epic"];
const nouns = ["Tiger", "Wolf", "Falcon", "Shadow", "Ninja", "Dragon", "Phoenix"]; const nouns = ["Tiger", "Wolf", "Falcon", "Shadow", "Ninja", "Dragon", "Phoenix"];
const running = new Set<string>(); const running = new Set<string>();
const userRooms = new Map<string, string>(); const userRooms = new Map<string, string>();
const userPseudo = new Map<string, string>(); const userPseudo = new Map<string, string>();
async function writeRoomEvent(
event: "enter" | "leave",
pseudoID: string,
roomID: string,
rssi: number
) {
try {
const room = await prisma.room.findUnique({
where: { id: roomID },
select: { name: true },
});
const roomName = room?.name ?? roomID;
const point = new Point("room_events")
.tag("pseudo_id", pseudoID)
.tag("room_id", roomID)
.tag("room_name", roomName)
.tag("event", event)
.floatField("rssi", rssi);
getInflux().writePoint(point);
console.log(`[InfluxDB] Wrote '${event}' event for pseudo ${pseudoID} in room ${roomName}`);
} catch (err) {
console.error("[InfluxDB] Failed to write event:", err);
}
}
export async function updateRoomOccupancyListener(message: string, channel: string) { export async function updateRoomOccupancyListener(message: string, channel: string) {
console.log(`[Redis] Key ${message} expired.`); console.log(`[Redis] Key ${message} expired.`);
@@ -21,10 +51,11 @@ export async function updateRoomOccupancyListener(message: string, channel: stri
const redis = getRedis(); const redis = getRedis();
if (!lastRoomID) { if (!lastRoomID) console.log("[!] Key not found in local map");
console.log("[!] Key not found in local map"); else {
} else {
await redis.decr(`room:${lastRoomID}`); await redis.decr(`room:${lastRoomID}`);
if (userPseudoID) await writeRoomEvent("leave", userPseudoID, lastRoomID, 0);
} }
await redis.del(`_user:${userPseudoID}`); await redis.del(`_user:${userPseudoID}`);
@@ -35,11 +66,8 @@ export async function updateRoomOccupancyListener(message: string, channel: stri
function generateNickname() { function generateNickname() {
const adjective = adjectives[Math.floor(Math.random() * adjectives.length)]; const adjective = adjectives[Math.floor(Math.random() * adjectives.length)];
const noun = nouns[Math.floor(Math.random() * nouns.length)]; const noun = nouns[Math.floor(Math.random() * nouns.length)];
const number = Math.floor(Math.random() * 1000); const number = Math.floor(Math.random() * 1000);
return `${adjective}${noun}${number}`; return `${adjective}${noun}${number}`;
} }
@@ -103,14 +131,14 @@ export async function analyzeData(
); );
await redis.set(`_user:${user.pseudoID}`, userID); await redis.set(`_user:${user.pseudoID}`, userID);
await redis.incr(`room:${roomID}`); await redis.incr(`room:${roomID}`);
userRooms.set(userID, roomID); userRooms.set(userID, roomID);
userPseudo.set(userID, user.pseudoID); userPseudo.set(userID, user.pseudoID);
console.log("[Redis] Stored new user in redis."); await writeRoomEvent("enter", user.pseudoID, roomID, metrics.rssi);
console.log("[Redis] Stored new user in redis.");
return; return;
} }
@@ -123,7 +151,6 @@ export async function analyzeData(
resObj["timestamp"] = Date.now(); resObj["timestamp"] = Date.now();
await redis.set(`user:${userID}`, JSON.stringify(resObj), { PX: config.core.userTTL }); await redis.set(`user:${userID}`, JSON.stringify(resObj), { PX: config.core.userTTL });
return; return;
} }
@@ -140,7 +167,10 @@ export async function analyzeData(
) { ) {
console.log(`[!] Transition done ${resObj["room"]} -> ${roomID}`); console.log(`[!] Transition done ${resObj["room"]} -> ${roomID}`);
await redis.decr(`room:${resObj["room"]}`); const previousRoomID = resObj["room"];
const pseudoID = resObj["userID"];
await redis.decr(`room:${previousRoomID}`);
await redis.incr(`room:${roomID}`); await redis.incr(`room:${roomID}`);
userRooms.set(userID, roomID); userRooms.set(userID, roomID);
@@ -151,7 +181,12 @@ export async function analyzeData(
resObj["verified"] = false; resObj["verified"] = false;
await redis.set(`user:${userID}`, JSON.stringify(resObj), { PX: config.core.userTTL }); await redis.set(`user:${userID}`, JSON.stringify(resObj), { PX: config.core.userTTL });
} else console.log("[!] Transition declined");
await writeRoomEvent("leave", pseudoID, previousRoomID, metrics.rssi);
await writeRoomEvent("enter", pseudoID, roomID, metrics.rssi);
} else {
console.log("[!] Transition declined");
}
} finally { } finally {
running.delete(metrics.tagID); running.delete(metrics.tagID);
} }
+3
View File
@@ -3,6 +3,7 @@ import { initRedis, initSubRedis } from "../redis/redis.js";
import { initCamera } from "./camera.js"; import { initCamera } from "./camera.js";
import * as mqtt from "./mqtt.js"; import * as mqtt from "./mqtt.js";
import { cleanupWorker } from "./transition.js"; import { cleanupWorker } from "./transition.js";
import { initInflux } from "../influx/influx.js";
export async function bootstrap() { export async function bootstrap() {
try { try {
@@ -17,6 +18,8 @@ export async function bootstrap() {
await initSubRedis(); await initSubRedis();
await mqtt.start(); await mqtt.start();
initInflux();
cleanupWorker(); cleanupWorker();
await initCamera(); await initCamera();
+22
View File
@@ -0,0 +1,22 @@
import { InfluxDB, type WriteApi } from "@influxdata/influxdb-client";
import config from "../config/config.js";
let writeApi: WriteApi | null = null;
export function initInflux(): void {
const client = new InfluxDB({
url: config.influx.url,
token: config.influx.token,
});
writeApi = client.getWriteApi(config.influx.org, config.influx.bucket, "ms");
writeApi.useDefaultTags({ host: "officesense-pi" });
console.log("[InfluxDB] Write API initialized.");
}
export function getInflux(): WriteApi {
if (!writeApi) throw new Error("[InfluxDB] Not initialized. Call initInflux() first.");
return writeApi;
}