169 lines
4.6 KiB
TypeScript
169 lines
4.6 KiB
TypeScript
import Bonjour from 'bonjour-service';
|
|
import { WledClient } from '../wled/client';
|
|
|
|
export interface DiscoveredDevice {
|
|
name: string;
|
|
ipAddress: string;
|
|
port: number;
|
|
host: string;
|
|
type: string;
|
|
}
|
|
|
|
export class DiscoveryService {
|
|
private bonjour: Bonjour;
|
|
|
|
constructor() {
|
|
this.bonjour = new Bonjour();
|
|
}
|
|
|
|
/**
|
|
* Discover WLED devices on the network using mDNS
|
|
* @param timeout - How long to search for devices (in milliseconds)
|
|
* @returns Array of discovered devices
|
|
*/
|
|
async discoverDevices(timeout: number = 5000): Promise<DiscoveredDevice[]> {
|
|
return new Promise((resolve) => {
|
|
const discoveredDevices: DiscoveredDevice[] = [];
|
|
const seenAddresses = new Set<string>();
|
|
|
|
// Search for WLED devices using mDNS service discovery
|
|
// WLED devices advertise themselves as _http._tcp
|
|
const browser = this.bonjour.find({ type: 'http' });
|
|
|
|
browser.on('up', (service) => {
|
|
// WLED devices typically have "wled" in their name or txt records
|
|
const isWled =
|
|
service.name?.toLowerCase().includes('wled') ||
|
|
service.txt?.ver?.startsWith('0.') || // WLED version format
|
|
service.txt?.name !== undefined;
|
|
|
|
if (isWled && service.addresses && service.addresses.length > 0) {
|
|
// Use the first IPv4 address
|
|
const ipv4Address = service.addresses.find((addr: string) =>
|
|
addr.includes('.') && !addr.startsWith('169.254')
|
|
);
|
|
|
|
if (ipv4Address && !seenAddresses.has(ipv4Address)) {
|
|
seenAddresses.add(ipv4Address);
|
|
|
|
discoveredDevices.push({
|
|
name: service.name || service.host || 'Unknown WLED Device',
|
|
ipAddress: ipv4Address,
|
|
port: service.port || 80,
|
|
host: service.host || ipv4Address,
|
|
type: service.type || 'http',
|
|
});
|
|
}
|
|
}
|
|
});
|
|
|
|
// Stop searching after timeout
|
|
setTimeout(() => {
|
|
browser.stop();
|
|
resolve(discoveredDevices);
|
|
}, timeout);
|
|
});
|
|
}
|
|
|
|
/**
|
|
* Scan a range of IP addresses for WLED devices
|
|
* This is a fallback method when mDNS doesn't work
|
|
* @param baseIp - Base IP address (e.g., "192.168.1")
|
|
* @param startRange - Start of range (e.g., 1)
|
|
* @param endRange - End of range (e.g., 254)
|
|
* @returns Array of discovered devices
|
|
*/
|
|
async scanIpRange(
|
|
baseIp: string,
|
|
startRange: number = 1,
|
|
endRange: number = 254
|
|
): Promise<DiscoveredDevice[]> {
|
|
const discoveredDevices: DiscoveredDevice[] = [];
|
|
const promises: Promise<void>[] = [];
|
|
|
|
for (let i = startRange; i <= endRange; i++) {
|
|
const ipAddress = `${baseIp}.${i}`;
|
|
|
|
promises.push(
|
|
(async () => {
|
|
try {
|
|
const client = new WledClient(ipAddress, 80);
|
|
const info = await Promise.race([
|
|
client.getInfo(),
|
|
new Promise((_, reject) =>
|
|
setTimeout(() => reject(new Error('Timeout')), 2000)
|
|
),
|
|
]) as any;
|
|
|
|
if (info && info.name) {
|
|
discoveredDevices.push({
|
|
name: info.name,
|
|
ipAddress,
|
|
port: 80,
|
|
host: ipAddress,
|
|
type: 'wled',
|
|
});
|
|
}
|
|
} catch (err) {
|
|
// Device not found or not responding, ignore
|
|
}
|
|
})()
|
|
);
|
|
|
|
// Process in batches of 10 to avoid overwhelming the network
|
|
if (promises.length >= 10) {
|
|
await Promise.all(promises);
|
|
promises.length = 0;
|
|
}
|
|
}
|
|
|
|
// Wait for remaining promises
|
|
if (promises.length > 0) {
|
|
await Promise.all(promises);
|
|
}
|
|
|
|
return discoveredDevices;
|
|
}
|
|
|
|
/**
|
|
* Verify a device at a specific IP address is a WLED device
|
|
* @param ipAddress - IP address to check
|
|
* @param port - Port number (default: 80)
|
|
* @returns Device info if it's a WLED device, null otherwise
|
|
*/
|
|
async verifyDevice(
|
|
ipAddress: string,
|
|
port: number = 80
|
|
): Promise<DiscoveredDevice | null> {
|
|
try {
|
|
const client = new WledClient(ipAddress, port);
|
|
const info = await Promise.race([
|
|
client.getInfo(),
|
|
new Promise((_, reject) =>
|
|
setTimeout(() => reject(new Error('Timeout')), 3000)
|
|
),
|
|
]) as any;
|
|
|
|
if (info && info.name) {
|
|
return {
|
|
name: info.name,
|
|
ipAddress,
|
|
port,
|
|
host: ipAddress,
|
|
type: 'wled',
|
|
};
|
|
}
|
|
|
|
return null;
|
|
} catch (err) {
|
|
return null;
|
|
}
|
|
}
|
|
|
|
destroy() {
|
|
this.bonjour.destroy();
|
|
}
|
|
}
|
|
|
|
export const discoveryService = new DiscoveryService();
|