import * as dgram from 'dgram'; import * as os from 'os'; import { parseResponse } from './response.js'; import type { DiscoveryDevice } from './types.js'; const UDP_SRC_PORT = 30000; const UDP_DST_PORT = 30050; const GRACE_SECONDS = 1; const DISCOVERY_MSG = 'DAIKIN_UDP/common/basic_info'; interface NetworkInterfaceInfoIPv4 extends os.NetworkInterfaceInfoIPv4 { broadcast?: string; } export class Discovery { private socket: dgram.Socket; private devices: Map = new Map(); constructor() { this.socket = dgram.createSocket({ type: 'udp4', reuseAddr: true }); } poll(stopIfFound?: string, targetIp?: string): Promise { return new Promise((resolve, reject) => { const broadcastIps = this.getBroadcastIps(targetIp); const timeout = setTimeout(() => { this.socket.close(); resolve(Array.from(this.devices.values())); }, GRACE_SECONDS * 1000); this.socket.on('message', (msg, rinfo) => { try { const data = parseResponse(msg.toString('utf-8')); if (!data.mac) { throw new Error('No MAC found for device'); } const device: DiscoveryDevice = { name: data.name || '', ip: rinfo.address, mac: data.mac, port: rinfo.port, }; this.devices.set(data.mac, device); if (stopIfFound && data.name?.toLowerCase() === stopIfFound.toLowerCase()) { clearTimeout(timeout); this.socket.close(); resolve(Array.from(this.devices.values())); } } catch { // Invalid message, ignore } }); this.socket.on('error', (err) => { clearTimeout(timeout); this.socket.close(); reject(err); }); this.socket.bind(UDP_SRC_PORT, () => { this.socket.setBroadcast(true); for (const ip of broadcastIps) { const message = Buffer.from(DISCOVERY_MSG, 'utf-8'); this.socket.send(message, 0, message.length, UDP_DST_PORT, ip); } }); }); } private getBroadcastIps(targetIp?: string): string[] { if (targetIp) { return [targetIp]; } const interfaces = os.networkInterfaces(); const broadcastIps: string[] = []; for (const name in interfaces) { const iface = interfaces[name]; if (!iface) continue; for (const info of iface) { const ipv4Info = info as NetworkInterfaceInfoIPv4; if (ipv4Info.family === 'IPv4' && ipv4Info.broadcast) { broadcastIps.push(ipv4Info.broadcast); } } } return broadcastIps; } } export function getDevices(): DiscoveryDevice[] { const discovery = new Discovery(); return [] as DiscoveryDevice[]; } export function getName(name: string): DiscoveryDevice | null { return null; }