Add Daikin local-API driver — MAC-matched subnet sweep instead of UDP broadcast
Daikin BRP069/BRP072-class units are WiFi/DHCP with no fixed IP. hvac's backend runs as a Docker container on its own bridge network (not directly on the LXC's admin-VLAN NIC), so UDP broadcast discovery risked silently failing to egress — instead, every unit is identified by MAC (external_ref), and a background poller periodically HTTP-sweeps the configured subnet CIDR and matches replies by MAC, self-healing zone_devices.device_ip whenever a lease changes. Only needs plain outbound HTTP, same path already proven by the MHI Modbus TCP driver. - lib/drivers/daikin.js: discover()/getStatus()/setTarget() against the local /common/basic_info + /aircon/get_control_info + /aircon/set_control_info API (confirmed live against firmware 4_2_303, 2026-08-25). Mode/fan-rate encoding taken from community reverse-engineering, not yet verified against a live setTarget() call — flagged in comments for first real control test. - lib/daikin-poller.js: 60s status poll (cached IP) + 5min subnet resweep (self-heal IP + auto-register new units, never auto-assigned to a zone). - lib/device-registry.js: shared discover-upsert, extracted so both the manual Discover button and the background sweep record device_ip the same way. - ac-control.js / routes/override.js: daikin-status/daikin-control endpoints and commandAcDevice() branch, mirroring the existing MHI control path. - Settings page: new "Daikin subnet (CIDR)" field (daikin_subnet_cidr config key) — sweeps do nothing until this is set. - Frontend: DaikinControlPanel.tsx (on/off/mode/setpoint/fan), wired into ZoneDetailModal; daikin moved from NOT_YET_IMPLEMENTED to a real driver. Backend node --check and frontend tsc --noEmit both clean. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
This commit is contained in:
parent
5fc576aabd
commit
373f66ef5c
12 changed files with 559 additions and 27 deletions
|
|
@ -94,4 +94,50 @@ export async function overrideRoutes(app) {
|
|||
|
||||
return { ok }
|
||||
})
|
||||
|
||||
// GET /api/devices/:id/daikin-status — served from the poll-populated cache
|
||||
// (lib/daikin-poller.js), same "read the row, don't do a live round trip" shape as
|
||||
// the mhi_mqtt branch above — Daikin has no push mechanism either, but a live HTTP
|
||||
// call on every panel open would be slower and the cache is at most POLL_MS stale.
|
||||
app.get('/api/devices/:id/daikin-status', { preHandler: requireCap('view') }, async (req, reply) => {
|
||||
const { rows } = await pool.query(
|
||||
`SELECT * FROM zone_devices WHERE id = $1 AND device_type = 'daikin'`,
|
||||
[req.params.id]
|
||||
)
|
||||
if (!rows.length) return reply.status(404).send({ error: 'Daikin device not found' })
|
||||
const device = rows[0]
|
||||
|
||||
return {
|
||||
external_ref: device.external_ref,
|
||||
onOff: device.power_state ?? undefined,
|
||||
mode: device.current_mode ?? undefined,
|
||||
setpoint: device.current_target_temp != null ? Number(device.current_target_temp) : undefined,
|
||||
fanSpeed: device.fan_speed ?? undefined,
|
||||
roomTemp: device.current_room_temp != null ? Number(device.current_room_temp) : undefined,
|
||||
status_updated_at: device.status_updated_at,
|
||||
}
|
||||
})
|
||||
|
||||
// POST /api/devices/:id/daikin-control — explicit staff control from DaikinControlPanel.
|
||||
// Body: { onOff?, mode?, tempC?, fanSpeed? }. Shares commandAcDevice() with the mhi
|
||||
// routes above and with the scheduler's automatic occupied/vacant AC control.
|
||||
app.post('/api/devices/:id/daikin-control', { preHandler: requireCap('control') }, async (req, reply) => {
|
||||
const { rows } = await pool.query(
|
||||
`SELECT * FROM zone_devices WHERE id = $1 AND device_type = 'daikin'`,
|
||||
[req.params.id]
|
||||
)
|
||||
if (!rows.length) return reply.status(404).send({ error: 'Daikin device not found' })
|
||||
const device = rows[0]
|
||||
const { onOff, mode, tempC, fanSpeed } = req.body || {}
|
||||
|
||||
const { ok } = await commandAcDevice(device, { onOff, mode, tempC, fanSpeed }, { origin: 'manual', log: req.log })
|
||||
|
||||
await logActivity(device.zone_id, 'device_command', {
|
||||
note: `Daikin 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