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
|
|
@ -4,6 +4,7 @@ import type { ZoneStatus, ActivityEntry } from '../types'
|
|||
import { ROOM_STATE_LABELS, can } from '../types'
|
||||
import { useAuth } from './AuthGate'
|
||||
import { updateZone, overrideZone, fetchActivity } from '../api'
|
||||
import MhiControlPanel from './MhiControlPanel'
|
||||
|
||||
export default function ZoneDetailModal({ zone, onClose, onSaved }: {
|
||||
zone: ZoneStatus
|
||||
|
|
@ -88,15 +89,18 @@ export default function ZoneDetailModal({ zone, onClose, onSaved }: {
|
|||
<div className="section-title">Devices</div>
|
||||
{zone.devices.length === 0 && <p className="muted">No devices mapped to this zone yet — assign some on the Devices page.</p>}
|
||||
{zone.devices.map(d => (
|
||||
<div key={d.id} className="card" style={{ display: 'flex', justifyContent: 'space-between', alignItems: 'center' }}>
|
||||
<div>
|
||||
<div style={{ fontWeight: 600, fontSize: 13 }}>{d.location || d.discovered_name || d.external_ref}</div>
|
||||
<div className="muted" style={{ fontSize: 12 }}>
|
||||
{d.current_target_temp != null ? `${Number(d.current_target_temp).toFixed(1)}°C` : '—'}
|
||||
{d.battery_pct != null && ` · ${d.battery_pct}% battery`}
|
||||
<div key={d.id} className="card">
|
||||
<div style={{ display: 'flex', justifyContent: 'space-between', alignItems: 'center' }}>
|
||||
<div>
|
||||
<div style={{ fontWeight: 600, fontSize: 13 }}>{d.location || d.discovered_name || d.external_ref}</div>
|
||||
<div className="muted" style={{ fontSize: 12 }}>
|
||||
{d.current_target_temp != null ? `${Number(d.current_target_temp).toFixed(1)}°C` : '—'}
|
||||
{d.battery_pct != null && ` · ${d.battery_pct}% battery`}
|
||||
</div>
|
||||
</div>
|
||||
<span className={`badge badge-health-${d.health_state}`}>{d.health_state.replace('_', ' ')}</span>
|
||||
</div>
|
||||
<span className={`badge badge-health-${d.health_state}`}>{d.health_state.replace('_', ' ')}</span>
|
||||
{d.device_type === 'mhi_modbus' && <MhiControlPanel deviceId={d.id} canControl={canControl} />}
|
||||
</div>
|
||||
))}
|
||||
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue