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,5 +1,6 @@
|
|||
import type {
|
||||
Zone, ZoneStatus, Device, DevicePhoto, ActivityEntry, NewbookSite, AppConfig,
|
||||
MhiGateway, MhiStatus,
|
||||
} from './types'
|
||||
|
||||
const BASE = '/hvac/api'
|
||||
|
|
@ -96,6 +97,41 @@ export function photoUrl(filePath: string): string {
|
|||
return `${BASE}/uploads${filePath}`
|
||||
}
|
||||
|
||||
// MHI Modbus gateways (manual aircon control)
|
||||
export function fetchMhiGateways(): Promise<MhiGateway[]> {
|
||||
return request('/mhi-gateways')
|
||||
}
|
||||
export function createMhiGateway(body: { name: string; host: string; port?: number; slaveId?: number; addressBase?: number }): Promise<MhiGateway> {
|
||||
return request('/mhi-gateways', { method: 'POST', body: JSON.stringify(body) })
|
||||
}
|
||||
export function updateMhiGateway(id: number, body: Partial<{ name: string; host: string; port: number; slaveId: number; addressBase: number }>): Promise<MhiGateway> {
|
||||
return request(`/mhi-gateways/${id}`, { method: 'PATCH', body: JSON.stringify(body) })
|
||||
}
|
||||
export function testMhiGatewayConnection(id: number): Promise<{ ok: boolean; note?: string; error?: string }> {
|
||||
return request(`/mhi-gateways/${id}/test-connection`, { method: 'POST', body: JSON.stringify({}) })
|
||||
}
|
||||
export async function importMhiRegisterMap(id: number, file: File): Promise<MhiGateway> {
|
||||
const form = new FormData()
|
||||
form.append('file', file)
|
||||
const res = await fetch(`${BASE}/mhi-gateways/${id}/import`, { method: 'POST', credentials: 'include', body: form })
|
||||
if (!res.ok) {
|
||||
const err = await res.json().catch(() => ({ error: res.statusText }))
|
||||
throw new Error(err.error || `Import failed: ${res.status}`)
|
||||
}
|
||||
return res.json()
|
||||
}
|
||||
export function assignMhiUnit(gatewayId: number, unitIndex: number, body: { zone_id: number | null; location: string }): Promise<Device> {
|
||||
return request(`/mhi-gateways/${gatewayId}/units/${unitIndex}/assign`, { method: 'POST', body: JSON.stringify(body) })
|
||||
}
|
||||
|
||||
// MHI manual control (device-level — mhi_modbus rows only)
|
||||
export function fetchMhiStatus(deviceId: number): Promise<MhiStatus> {
|
||||
return request(`/devices/${deviceId}/mhi-status`)
|
||||
}
|
||||
export function sendMhiControl(deviceId: number, body: { onOff?: boolean; mode?: string; tempC?: number; fanSpeed?: string }): Promise<{ ok: boolean }> {
|
||||
return request(`/devices/${deviceId}/mhi-control`, { method: 'POST', body: JSON.stringify(body) })
|
||||
}
|
||||
|
||||
// Config / settings
|
||||
export function fetchConfig(): Promise<AppConfig> {
|
||||
return request('/config')
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue