Scaffold Phase 1 hvac app — NewBook-driven room TRV heating scheduler
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>
This commit is contained in:
commit
276c04f8c6
42 changed files with 5461 additions and 0 deletions
41
backend/src/routes/override.js
Normal file
41
backend/src/routes/override.js
Normal file
|
|
@ -0,0 +1,41 @@
|
|||
import { requireAuth, requireCap } from '../auth.js'
|
||||
import { pool, logActivity } from '../db.js'
|
||||
import { batchSetZoneTemperature } from '../lib/mqtt.js'
|
||||
|
||||
// Manual force-temperature — the equivalent of the old integration's
|
||||
// newbook.force_room_temperature service. Disables auto mode for the zone so the
|
||||
// scheduler leaves it alone until staff switch auto mode back on (zones.js PATCH).
|
||||
export async function overrideRoutes(app) {
|
||||
app.addHook('preHandler', requireAuth)
|
||||
|
||||
app.post('/api/zones/:id/override', { preHandler: requireCap('control') }, async (req, reply) => {
|
||||
const { temp_c } = req.body || {}
|
||||
const tempC = Number(temp_c)
|
||||
if (!Number.isFinite(tempC)) return reply.status(400).send({ error: 'temp_c must be a number' })
|
||||
|
||||
const { rows } = await pool.query('SELECT * FROM zones WHERE id = $1', [req.params.id])
|
||||
if (!rows.length) return reply.status(404).send({ error: 'Zone not found' })
|
||||
const zone = rows[0]
|
||||
|
||||
await pool.query('UPDATE zones SET auto_mode = FALSE WHERE id = $1', [zone.id])
|
||||
|
||||
const { rows: devices } = await pool.query(
|
||||
`SELECT external_ref, device_ip FROM zone_devices WHERE zone_id = $1 AND device_type = 'shelly_trv'`,
|
||||
[zone.id]
|
||||
)
|
||||
if (!devices.length) {
|
||||
return reply.status(200).send({ ok: false, error: 'No TRVs mapped to this zone', auto_mode: false })
|
||||
}
|
||||
|
||||
const results = await batchSetZoneTemperature(devices, tempC)
|
||||
const successful = Object.values(results).filter(Boolean).length
|
||||
|
||||
await logActivity(zone.id, 'override', {
|
||||
note: `Manual override to ${tempC}°C (${successful}/${devices.length} devices, auto mode disabled)`,
|
||||
source: 'manual',
|
||||
userEmail: req.user.email,
|
||||
})
|
||||
|
||||
return { ok: successful > 0, successful, total: devices.length, auto_mode: false }
|
||||
})
|
||||
}
|
||||
Loading…
Add table
Add a link
Reference in a new issue