feat: restructure backend with Elysia framework and add MQTT adapter

- Updated .gitignore to exclude generated files and database
- Modified package.json to change dev script and add new dependencies
- Removed src/index.ts and created app.ts for Elysia server initialization
- Added environment variable configuration in config/env.ts
- Implemented MQTT adapter in adapter/mqtt.ts for message handling
- Created Prisma client in prisma/client.ts and defined schema in prisma/schema.prisma
- Added seeding script in prisma/seed.ts for measurement points
- Established logging utility in utils/logger.ts for structured logging
- Created bed router in routes/bed.ts for handling bed-related routes
This commit is contained in:
Siwat Sirichai 2025-06-21 18:24:54 +07:00
parent b76d6b99ee
commit a767dc3635
14 changed files with 801 additions and 14 deletions

5
prisma/client.ts Normal file
View file

@ -0,0 +1,5 @@
import { PrismaClient } from "~/generated/prisma";
const db = new PrismaClient()
export default db;

81
prisma/schema.prisma Normal file
View file

@ -0,0 +1,81 @@
// This is your Prisma schema file,
// learn more about it in the docs: https://pris.ly/d/prisma-schema
generator client {
provider = "prisma-client-js"
output = "../generated/prisma"
}
datasource db {
provider = "sqlite"
url = env("DATABASE_URL")
}
model MeasurementPoint {
id String @id @default(cuid())
sensorId String @unique // e.g., "head-1", "back-2"
label String // e.g., "Head Left", "Upper Back Center"
zone String // e.g., "head", "back", "shoulders"
x Int // X coordinate on bed layout
y Int // Y coordinate on bed layout
pin Int // Hardware pin number
// Threshold configuration
warningThreshold Int // Pressure value that triggers warning
alarmThreshold Int // Pressure value that triggers alarm
warningDelayMs Int // Delay before warning escalates to alarm
// Timestamps
createdAt DateTime @default(now())
updatedAt DateTime @updatedAt
// Relations
data MeasurementPointData[]
alerts Alert[]
}
model MeasurementPointData {
id String @id @default(cuid())
measurementPointId String
// Sensor reading data
value Int // Analog sensor value (0-4095)
// Timestamps
timestamp DateTime @default(now())
time String // Formatted time string
// Relations
measurementPoint MeasurementPoint @relation(fields: [measurementPointId], references: [id], onDelete: Cascade)
@@index([measurementPointId, timestamp])
}
enum AlertType {
WARNING
ALARM
}
model Alert {
id String @id @default(cuid())
measurementPointId String
// Alert details
type AlertType
value Int // Sensor value that triggered alert
threshold Int // Threshold that was exceeded
// Alert state
acknowledged Boolean @default(false)
silenced Boolean @default(false)
// Timing
startTime DateTime @default(now())
endTime DateTime?
// Relations
measurementPoint MeasurementPoint @relation(fields: [measurementPointId], references: [id], onDelete: Cascade)
@@index([measurementPointId, startTime])
@@index([type, acknowledged])
}

219
prisma/seed.ts Normal file
View file

@ -0,0 +1,219 @@
import db from "./client";
async function main() {
console.log('Seeding MeasurementPoints...');
// Delete existing measurement points
await db.measurementPoint.deleteMany();
// Create measurement points based on API sensor configuration
const measurementPoints = [
// Head area - Higher thresholds due to critical nature, faster escalation
{
sensorId: "head-1",
label: "Head Left",
zone: "head",
x: 45,
y: 15,
pin: 2,
warningThreshold: 3000,
alarmThreshold: 3500,
warningDelayMs: 30000 // 30 seconds
},
{
sensorId: "head-2",
label: "Head Right",
zone: "head",
x: 55,
y: 15,
pin: 3,
warningThreshold: 3000,
alarmThreshold: 3500,
warningDelayMs: 30000 // 30 seconds
},
// Shoulder area - Moderate thresholds, medium escalation time
{
sensorId: "shoulder-1",
label: "Left Shoulder",
zone: "shoulders",
x: 35,
y: 25,
pin: 4,
warningThreshold: 2800,
alarmThreshold: 3200,
warningDelayMs: 45000 // 45 seconds
},
{
sensorId: "shoulder-2",
label: "Right Shoulder",
zone: "shoulders",
x: 65,
y: 25,
pin: 5,
warningThreshold: 2800,
alarmThreshold: 3200,
warningDelayMs: 45000 // 45 seconds
},
// Upper back - Moderate thresholds, 1 minute escalation
{
sensorId: "back-1",
label: "Upper Back Left",
zone: "back",
x: 40,
y: 35,
pin: 6,
warningThreshold: 2500,
alarmThreshold: 3000,
warningDelayMs: 60000 // 1 minute
},
{
sensorId: "back-2",
label: "Upper Back Center",
zone: "back",
x: 50,
y: 35,
pin: 7,
warningThreshold: 2500,
alarmThreshold: 3000,
warningDelayMs: 60000 // 1 minute
},
{
sensorId: "back-3",
label: "Upper Back Right",
zone: "back",
x: 60,
y: 35,
pin: 8,
warningThreshold: 2500,
alarmThreshold: 3000,
warningDelayMs: 60000 // 1 minute
},
// Lower back/Hip area - Lower thresholds, 90 second escalation
{
sensorId: "hip-1",
label: "Left Hip",
zone: "hips",
x: 35,
y: 50,
pin: 9,
warningThreshold: 2200,
alarmThreshold: 2800,
warningDelayMs: 90000 // 90 seconds
},
{
sensorId: "hip-2",
label: "Lower Back",
zone: "hips",
x: 50,
y: 50,
pin: 10,
warningThreshold: 2200,
alarmThreshold: 2800,
warningDelayMs: 90000 // 90 seconds
},
{
sensorId: "hip-3",
label: "Right Hip",
zone: "hips",
x: 65,
y: 50,
pin: 11,
warningThreshold: 2200,
alarmThreshold: 2800,
warningDelayMs: 90000 // 90 seconds
},
// Thigh area - Lower thresholds, 2 minute escalation
{
sensorId: "thigh-1",
label: "Left Thigh",
zone: "legs",
x: 40,
y: 65,
pin: 12,
warningThreshold: 2000,
alarmThreshold: 2500,
warningDelayMs: 120000 // 2 minutes
},
{
sensorId: "thigh-2",
label: "Right Thigh",
zone: "legs",
x: 60,
y: 65,
pin: 13,
warningThreshold: 2000,
alarmThreshold: 2500,
warningDelayMs: 120000 // 2 minutes
},
// Calf area (mock sensors - no pin) - Lower thresholds, 2.5 minute escalation
{
sensorId: "calf-1",
label: "Left Calf",
zone: "legs",
x: 40,
y: 75,
pin: null,
warningThreshold: 1800,
alarmThreshold: 2200,
warningDelayMs: 150000 // 2.5 minutes
},
{
sensorId: "calf-2",
label: "Right Calf",
zone: "legs",
x: 60,
y: 75,
pin: null,
warningThreshold: 1800,
alarmThreshold: 2200,
warningDelayMs: 150000 // 2.5 minutes
},
// Feet (mock sensors - no pin) - Lowest thresholds, 3 minute escalation
{
sensorId: "feet-1",
label: "Left Foot",
zone: "feet",
x: 45,
y: 85,
pin: null,
warningThreshold: 1500,
alarmThreshold: 1800,
warningDelayMs: 180000 // 3 minutes
},
{
sensorId: "feet-2",
label: "Right Foot",
zone: "feet",
x: 55,
y: 85,
pin: null,
warningThreshold: 1500,
alarmThreshold: 1800,
warningDelayMs: 180000 // 3 minutes
}
];
// Insert all measurement points
for (const point of measurementPoints) {
await db.measurementPoint.create({
data: point
});
}
console.log(`Created ${measurementPoints.length} measurement points`);
}
main()
.catch((e) => {
console.error(e);
process.exit(1);
})
.finally(async () => {
await db.$disconnect();
});