Ports the state machine, retry/backoff, and guest-override detection from the retired homeassistant-newbook-heating-component, without depending on Home Assistant. Backend (Fastify/pg) + frontend (React/Vite/TS) following standard stack conventions; LXC 128 (127 was already taken by utilities). MHI/Midea/Daikin/boiler drivers and the shared MQTT broker (LXC 104) are later phases/infra, not included here. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
59 lines
2.2 KiB
JavaScript
59 lines
2.2 KiB
JavaScript
import { requireAuth, requireCap } from '../auth.js'
|
|
import { pool } from '../db.js'
|
|
import { isConnected as mqttConnected } from '../lib/mqtt.js'
|
|
|
|
export async function statusRoutes(app) {
|
|
app.addHook('preHandler', requireAuth)
|
|
|
|
// GET /api/status — live status per zone: room state, target temp, device health/battery
|
|
app.get('/api/status', { preHandler: requireCap('view') }, async () => {
|
|
const { rows: zones } = await pool.query(`
|
|
SELECT z.*, zs.room_state, zs.last_transition_at, zs.last_booking_status
|
|
FROM zones z
|
|
LEFT JOIN zone_state zs ON zs.zone_id = z.id
|
|
WHERE z.active = TRUE
|
|
ORDER BY z.zone_type, z.name
|
|
`)
|
|
|
|
const { rows: devices } = await pool.query(`
|
|
SELECT id, zone_id, device_type, external_ref, discovered_name, location,
|
|
health_state, battery_pct, wifi_rssi, current_target_temp, target_origin, last_seen
|
|
FROM zone_devices WHERE zone_id IS NOT NULL
|
|
`)
|
|
const devicesByZone = new Map()
|
|
for (const d of devices) {
|
|
if (!devicesByZone.has(d.zone_id)) devicesByZone.set(d.zone_id, [])
|
|
devicesByZone.get(d.zone_id).push(d)
|
|
}
|
|
|
|
return {
|
|
mqtt_connected: mqttConnected(),
|
|
zones: zones.map(z => ({
|
|
...z,
|
|
room_state: z.room_state || 'vacant',
|
|
target_temp: ['heating_up', 'occupied'].includes(z.room_state) ? z.occupied_temp : z.vacant_temp,
|
|
devices: devicesByZone.get(z.id) || [],
|
|
})),
|
|
}
|
|
})
|
|
|
|
// GET /api/status/activity — recent activity log, optionally filtered by zone
|
|
app.get('/api/status/activity', { preHandler: requireCap('view') }, async (req) => {
|
|
const { zone_id, limit } = req.query || {}
|
|
const lim = Math.min(parseInt(limit) || 100, 500)
|
|
if (zone_id) {
|
|
const { rows } = await pool.query(
|
|
`SELECT al.*, z.name AS zone_name FROM activity_log al LEFT JOIN zones z ON z.id = al.zone_id
|
|
WHERE al.zone_id = $1 ORDER BY al.created_at DESC LIMIT $2`,
|
|
[zone_id, lim]
|
|
)
|
|
return rows
|
|
}
|
|
const { rows } = await pool.query(
|
|
`SELECT al.*, z.name AS zone_name FROM activity_log al LEFT JOIN zones z ON z.id = al.zone_id
|
|
ORDER BY al.created_at DESC LIMIT $1`,
|
|
[lim]
|
|
)
|
|
return rows
|
|
})
|
|
}
|