Add MHI aircon manual control (Phase 2, Modbus only)
Manual on/off/mode/setpoint/fan control for MHI indoor units via the already-verified Intesis Modbus TCP gateway, deliberately NOT wired into the NewBook-driven scheduler yet — that stays deferred. Modbus doesn't depend on the MQTT broker (separate infra, still unbuilt), so this can be deployed and tested standalone. - mhi_gateways table (connection config: host/port/slave id/address base) + zone_devices columns for MHI devices (gateway id, unit index, IU hint, and the per-device register map staged from a MAPS import — ground truth, never re-derived from room/IU at runtime) - drivers/mhi-modbus.js: discover/getStatus/setTarget over modbus-serial, same interface as trv.js/homeassistant.js; per-gateway request queue since Modbus TCP requires serialised requests; falls back to the profile's stride formula (with a loud warning) only if a unit has never been through an import - routes/mhi-gateways.js: gateway CRUD, live test-connection, xlsx import (stages the parsed result for review — never auto-creates devices), per-unit 'assign to zone' as the explicit commit step - routes/override.js: GET .../mhi-status, POST .../mhi-control (control cap) - frontend: GatewaysPanel (add gateway, test connection, import + preview, assign units) and MhiControlPanel (on/off, mode, setpoint, fan) wired into Devices.tsx / ZoneDetailModal.tsx Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
This commit is contained in:
parent
a9b5703c57
commit
e5c40bbb8c
14 changed files with 935 additions and 20 deletions
|
|
@ -1,6 +1,7 @@
|
|||
import { requireAuth, requireCap } from '../auth.js'
|
||||
import { pool, logActivity } from '../db.js'
|
||||
import { batchSetZoneTemperature } from '../lib/mqtt.js'
|
||||
import * as mhiDriver from '../lib/drivers/mhi-modbus.js'
|
||||
|
||||
// Manual force-temperature — the equivalent of the old integration's
|
||||
// newbook.force_room_temperature service. Disables auto mode for the zone so the
|
||||
|
|
@ -38,4 +39,49 @@ export async function overrideRoutes(app) {
|
|||
|
||||
return { ok: successful > 0, successful, total: devices.length, auto_mode: false }
|
||||
})
|
||||
|
||||
// GET /api/devices/:id/mhi-status — live Modbus read (onOff/mode/setpoint/
|
||||
// fanSpeed/roomTemp/errorCode/etc) for one assigned MHI aircon unit.
|
||||
app.get('/api/devices/:id/mhi-status', { preHandler: requireCap('view') }, async (req, reply) => {
|
||||
const { rows } = await pool.query(
|
||||
`SELECT * FROM zone_devices WHERE id = $1 AND device_type = 'mhi_modbus'`,
|
||||
[req.params.id]
|
||||
)
|
||||
if (!rows.length) return reply.status(404).send({ error: 'MHI device not found' })
|
||||
const status = await mhiDriver.getStatus(rows[0].external_ref)
|
||||
if (!status || status.error) {
|
||||
return reply.status(502).send({ error: status?.error || 'Failed to read device status' })
|
||||
}
|
||||
return status
|
||||
})
|
||||
|
||||
// POST /api/devices/:id/mhi-control — manual aircon control only (no
|
||||
// scheduler wiring). Body: { onOff?, mode?, tempC?, fanSpeed? } — only the
|
||||
// fields present are written, via the mhi-modbus driver's setTarget().
|
||||
app.post('/api/devices/:id/mhi-control', { preHandler: requireCap('control') }, async (req, reply) => {
|
||||
const { rows } = await pool.query(
|
||||
`SELECT * FROM zone_devices WHERE id = $1 AND device_type = 'mhi_modbus'`,
|
||||
[req.params.id]
|
||||
)
|
||||
if (!rows.length) return reply.status(404).send({ error: 'MHI device not found' })
|
||||
const device = rows[0]
|
||||
const { onOff, mode, tempC, fanSpeed } = req.body || {}
|
||||
|
||||
const ok = await mhiDriver.setTarget(device.external_ref, { onOff, mode, tempC, fanSpeed })
|
||||
|
||||
if (ok && tempC !== undefined && tempC !== null) {
|
||||
await pool.query(
|
||||
`UPDATE zone_devices SET current_target_temp = $1, target_origin = 'manual', updated_at = NOW() WHERE id = $2`,
|
||||
[tempC, device.id]
|
||||
)
|
||||
}
|
||||
|
||||
await logActivity(device.zone_id, 'device_command', {
|
||||
note: `MHI manual control on ${device.location || device.discovered_name || device.external_ref}: ${JSON.stringify({ onOff, mode, tempC, fanSpeed })}${ok ? '' : ' — FAILED'}`,
|
||||
source: 'manual',
|
||||
userEmail: req.user.email,
|
||||
})
|
||||
|
||||
return { ok }
|
||||
})
|
||||
}
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue