Skip to content

Commit 18a249f

Browse files
committed
optimize pairing and panel
1 parent 302bda3 commit 18a249f

File tree

2 files changed

+118
-43
lines changed

2 files changed

+118
-43
lines changed

custom_components/bituopmd/config_flow.py

Lines changed: 2 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -80,8 +80,7 @@ async def async_step_zeroconf_confirm(self, user_input=None):
8080
)
8181

8282
# 在设备配对成功后,移除已配对的设备
83-
self.devices = [device for device in self.devices if device["ip"] != self.host]
84-
await self.async_remove_discovered_device(self.host)
83+
await self.hass.async_add_executor_job(self.async_remove_discovered_device, self.host)
8584

8685
return entry
8786

@@ -241,4 +240,4 @@ def _on_service_state_change(self, zeroconf, service_type, name, state_change):
241240
# 过滤掉已存在的设备
242241
if not any(entry.data.get(CONF_HOST_IP) == address for entry in existing_entries):
243242
if not any(device["ip"] == address for device in self.devices):
244-
self.devices.append({"ip": address, "name": name.split(".")[0]})
243+
self.devices.append({"ip": address, "name": name.split(".")[0]})

custom_components/bituopmd/www/bituo_panel.js

Lines changed: 116 additions & 40 deletions
Original file line numberDiff line numberDiff line change
@@ -374,58 +374,83 @@ class BituoPanel extends HTMLElement {
374374
}
375375
}
376376

377-
showConfirmationDialog(message, onConfirm) {
378-
const dialog = this.querySelector('#confirmation-dialog');
379-
const messageElement = this.querySelector('#confirmation-message');
380-
const yesButton = this.querySelector('#confirm-yes');
381-
const noButton = this.querySelector('#confirm-no');
382-
383-
messageElement.textContent = message;
384-
dialog.style.display = 'block';
385-
386-
yesButton.onclick = () => {
387-
dialog.style.display = 'none';
388-
onConfirm();
389-
};
390-
391-
noButton.onclick = () => {
392-
dialog.style.display = 'none';
393-
this.hideOtaOverlay();
394-
};
395-
}
396-
397377
async setDataRequestFrequency() {
398378
const frequency = parseInt(this.querySelector('#data-frequency').value);
399-
379+
400380
if (isNaN(frequency) || frequency < 5 || frequency > 3600) {
401381
this.showAlert('Polling Interval must be between 5 and 3600 seconds.');
402382
this.hideOtaOverlay();
403383
return;
404384
}
405385
const applyToAll = this.querySelector('#apply-to-all').checked;
406-
386+
387+
const offlineDevices = [];
388+
const onlineTasks = [];
389+
const deviceSelectElement = this.querySelector('#device-select');
390+
const options = Array.from(deviceSelectElement.options).filter(option => option.value);
391+
407392
if (applyToAll) {
408-
const deviceSelectElement = this.querySelector('#device-select');
409-
const options = Array.from(deviceSelectElement.options).filter(option => option.value);
410-
411-
// 逐个发送请求,确保每个设备的设置被正确更新
393+
// 收集任务并检查设备在线状态
412394
for (const option of options) {
413395
const deviceIp = option.value;
414-
try {
415-
await this.updateDeviceFrequency(deviceIp, frequency);
416-
} catch (error) {
417-
this.showAlert(`Error updating frequency for device ${deviceIp}: ${error.message}`);
418-
}
396+
const task = async () => {
397+
try {
398+
const isOnline = await this.checkDeviceStatus(deviceIp);
399+
if (isOnline) {
400+
await this.updateDeviceFrequency(deviceIp, frequency);
401+
} else {
402+
offlineDevices.push(deviceIp);
403+
}
404+
} catch (error) {
405+
offlineDevices.push(deviceIp);
406+
}
407+
};
408+
onlineTasks.push(task);
419409
}
420410
} else {
421411
const { deviceIp } = this.getSelectedDevice();
422412
if (!deviceIp) {
423413
this.showAlert('No device selected.');
424414
return;
425415
}
426-
await this.updateDeviceFrequency(deviceIp, frequency);
416+
const task = async () => {
417+
try {
418+
const isOnline = await this.checkDeviceStatus(deviceIp);
419+
if (isOnline) {
420+
await this.updateDeviceFrequency(deviceIp, frequency);
421+
} else {
422+
offlineDevices.push(deviceIp);
423+
}
424+
} catch (error) {
425+
offlineDevices.push(deviceIp);
426+
}
427+
};
428+
onlineTasks.push(task);
429+
}
430+
431+
// 限制并发数量
432+
await this.runWithConcurrencyLimit(onlineTasks, 10);
433+
434+
const totalDevices = options.length;
435+
const offlineCount = offlineDevices.length;
436+
this.showAlert(`Total devices: ${totalDevices}. Offline devices: ${offlineCount}. Polling interval has been successfully updated for all online devices.`);
437+
}
438+
439+
async runWithConcurrencyLimit(tasks, limit) {
440+
const results = [];
441+
const executing = [];
442+
for (const task of tasks) {
443+
const p = task().then(result => {
444+
executing.splice(executing.indexOf(p), 1);
445+
return result;
446+
});
447+
results.push(p);
448+
executing.push(p);
449+
if (executing.length >= limit) {
450+
await Promise.race(executing);
451+
}
427452
}
428-
this.showAlert(`Polling Interval set successfully.`);
453+
return Promise.all(results);
429454
}
430455

431456
async updateDeviceFrequency(deviceIp, frequency) {
@@ -456,21 +481,51 @@ class BituoPanel extends HTMLElement {
456481
clearInterval(this.intervalId);
457482
}
458483

484+
async limitConcurrency(tasks, limit) {
485+
const results = [];
486+
const executing = [];
487+
488+
for (const task of tasks) {
489+
const p = task(); // 执行任务
490+
results.push(p);
491+
492+
if (limit <= tasks.length) {
493+
const e = p.then(() => executing.splice(executing.indexOf(e), 1));
494+
executing.push(e);
495+
if (executing.length >= limit) {
496+
await Promise.race(executing);
497+
}
498+
}
499+
}
500+
return Promise.all(results);
501+
}
502+
459503
async loadDevices() {
460504
const deviceSelectElement = this.querySelector('#device-select');
461505
const selectedDeviceIp = deviceSelectElement.value; // 保存当前选中的设备IP
462506
deviceSelectElement.innerHTML = '<option value="" disabled selected>Loading devices...</option>';
507+
463508
try {
464509
const response = await this._hass.callApi('GET', 'bituopmd/devices');
465510
deviceSelectElement.innerHTML = ''; // 清空之前的内容
511+
466512
if (response && response.length) {
467-
for (const device of response) {
513+
const tasks = response.map(device => async () => {
468514
const option = this.findOrCreateOption(deviceSelectElement, device.ip);
469-
const isOnline = await this.checkDeviceStatus(device.ip);
515+
let isOnline = false;
516+
try {
517+
isOnline = await this.checkDeviceStatus(device.ip);
518+
} catch (error) {
519+
isOnline = false; // 超时或错误,视为离线
520+
}
470521
option.text = isOnline ? device.name : `${device.name} (offline)`;
471522
option.disabled = !isOnline;
472523
deviceSelectElement.add(option);
473-
}
524+
});
525+
526+
// 使用并发限制执行任务,每次最多执行10个
527+
await this.limitConcurrency(tasks, 10);
528+
474529
// 恢复之前选中的设备
475530
deviceSelectElement.value = selectedDeviceIp;
476531
} else {
@@ -491,12 +546,33 @@ class BituoPanel extends HTMLElement {
491546
return option;
492547
}
493548

494-
async checkDeviceStatus(ip) {
549+
async checkDeviceStatus(ip, timeout = 3000) {
550+
const url = `bituopmd/proxy/${ip}/data`;
551+
552+
// 创建超时的 Promise
553+
const timeoutPromise = new Promise((_, reject) =>
554+
setTimeout(() => reject(new Error('Request timed out')), timeout)
555+
);
556+
557+
// 创建 API 请求的 Promise
558+
const apiPromise = this._hass.callApi('GET', url)
559+
.then(response => {
560+
// 确保有有效的响应数据
561+
if (response && response.response) {
562+
// 根据实际数据来判断设备状态
563+
return response.response !== "404: Not Found";
564+
} else {
565+
// 没有有效数据时视为离线
566+
return false;
567+
}
568+
})
569+
.catch(() => false); // 处理请求错误
570+
495571
try {
496-
const response = await this._hass.callApi('GET', `bituopmd/proxy/${ip}/data`);
497-
return response.response && response.response !== "404: Not Found";
572+
// 使用 Promise.race 来处理超时和 API 请求
573+
return await Promise.race([apiPromise, timeoutPromise]);
498574
} catch {
499-
return false;
575+
return false; // 返回 false 表示设备离线或请求超时
500576
}
501577
}
502578

0 commit comments

Comments
 (0)