@switchbot/homebridge-switchbot
Version:
The SwitchBot plugin allows you to access your SwitchBot device(s) from HomeKit.
675 lines • 32.5 kB
JavaScript
import { DEVICE_TYPES } from './constants.js';
import { uiLog } from './logger.js';
export async function importDiscoveredDevice(device) {
// --- OpenAPI Polling Interval (refreshRate) ---
const openApiRefreshLabel = document.createElement('label');
openApiRefreshLabel.textContent = 'OpenAPI Polling Interval (seconds)';
openApiRefreshLabel.style.display = 'block';
openApiRefreshLabel.style.marginBottom = '6px';
openApiRefreshLabel.style.fontWeight = '500';
openApiRefreshLabel.style.fontSize = '12px';
openApiRefreshLabel.style.color = '#6b7280';
openApiRefreshLabel.title = 'How often to poll this device via OpenAPI for status (in seconds). Overrides platform value if set. Default: 300 (5 minutes). Minimum: 30.';
const openApiRefreshInput = document.createElement('input');
openApiRefreshInput.type = 'number';
openApiRefreshInput.value = device.refreshRate || 300;
openApiRefreshInput.min = '30';
openApiRefreshInput.step = '1';
openApiRefreshInput.style.width = '100%';
openApiRefreshInput.style.marginBottom = '12px';
openApiRefreshInput.style.padding = '8px 10px';
openApiRefreshInput.style.borderRadius = '6px';
openApiRefreshInput.style.fontSize = '14px';
openApiRefreshInput.style.boxSizing = 'border-box';
return new Promise((resolve) => {
const div = document.createElement('div');
div.style.position = 'fixed';
div.style.top = '0';
div.style.left = '0';
div.style.width = '100%';
div.style.height = '100%';
div.style.background = 'rgba(0,0,0,0.7)';
div.style.display = 'flex';
div.style.alignItems = 'center';
div.style.justifyContent = 'center';
div.style.zIndex = '9999';
const modal = document.createElement('div');
modal.style.background = getComputedStyle(document.body).backgroundColor;
modal.style.color = getComputedStyle(document.body).color;
modal.style.padding = '0';
modal.style.borderRadius = '10px';
modal.style.minWidth = '440px';
modal.style.maxWidth = '90vw';
modal.style.boxShadow = '0 8px 32px rgba(0,0,0,0.35)';
modal.style.overflow = 'hidden';
modal.style.borderTop = '3px solid var(--switchbot-red, #ef4444)';
const title = document.createElement('h3');
title.textContent = 'Import Discovered Device';
title.style.marginTop = '0';
title.style.marginBottom = '16px';
title.style.padding = '20px 20px 0';
title.style.fontSize = '18px';
title.style.fontWeight = '600';
title.style.color = 'var(--switchbot-red, #ef4444)';
title.style.letterSpacing = '-0.02em';
const contentDiv = document.createElement('div');
contentDiv.style.padding = '0 20px 20px';
const nameLabel = document.createElement('label');
nameLabel.textContent = 'Device Name';
nameLabel.style.display = 'block';
nameLabel.style.marginBottom = '6px';
nameLabel.style.fontWeight = '500';
nameLabel.style.fontSize = '12px';
nameLabel.style.color = '#6b7280';
const nameInput = document.createElement('input');
nameInput.type = 'text';
// Never allow 'undefined' as a name
let safeName = device.name;
if (!safeName || safeName === 'undefined') {
safeName = device.id || '';
}
nameInput.value = safeName;
nameInput.style.width = '100%';
nameInput.style.marginBottom = '12px';
nameInput.style.padding = '8px 10px';
nameInput.style.borderRadius = '6px';
nameInput.style.fontSize = '14px';
nameInput.style.boxSizing = 'border-box';
// --- Device Type Select ---
const typeLabel = document.createElement('label');
typeLabel.textContent = 'Config Device Type';
typeLabel.style.display = 'block';
typeLabel.style.marginBottom = '6px';
typeLabel.style.fontWeight = '500';
typeLabel.style.fontSize = '12px';
typeLabel.style.color = '#6b7280';
const typeSelect = document.createElement('select');
typeSelect.style.width = '100%';
typeSelect.style.padding = '8px 10px';
typeSelect.style.marginBottom = '12px';
typeSelect.style.borderRadius = '6px';
typeSelect.style.fontSize = '14px';
typeSelect.style.background = getComputedStyle(nameInput).background;
typeSelect.style.color = getComputedStyle(nameInput).color;
typeSelect.style.border = getComputedStyle(nameInput).border;
typeSelect.style.boxSizing = 'border-box';
Object.keys(DEVICE_TYPES).forEach((categoryName) => {
const optgroup = document.createElement('optgroup');
optgroup.label = categoryName;
DEVICE_TYPES[categoryName].forEach((deviceType) => {
const opt = document.createElement('option');
opt.value = deviceType;
opt.text = deviceType;
const detectedType = (device.type || '').toLowerCase();
opt.selected = deviceType.toLowerCase() === detectedType;
optgroup.appendChild(opt);
});
typeSelect.appendChild(optgroup);
});
// --- Connection Preference ---
const connectionPrefLabel = document.createElement('label');
connectionPrefLabel.textContent = 'Connection Preference';
connectionPrefLabel.style.display = 'block';
connectionPrefLabel.style.marginBottom = '6px';
connectionPrefLabel.style.fontWeight = '500';
connectionPrefLabel.style.fontSize = '12px';
connectionPrefLabel.style.color = '#6b7280';
const connectionPrefSelect = document.createElement('select');
connectionPrefSelect.style.width = '100%';
connectionPrefSelect.style.marginBottom = '12px';
connectionPrefSelect.style.padding = '8px 10px';
connectionPrefSelect.style.borderRadius = '6px';
connectionPrefSelect.style.fontSize = '14px';
connectionPrefSelect.style.boxSizing = 'border-box';
['auto', 'ble', 'openapi'].forEach((val) => {
const opt = document.createElement('option');
opt.value = val;
opt.text = val.charAt(0).toUpperCase() + val.slice(1);
opt.selected = (device.connectionPreference || 'auto') === val;
connectionPrefSelect.appendChild(opt);
});
// --- Room ---
const roomLabel = document.createElement('label');
roomLabel.textContent = 'Room';
roomLabel.style.display = 'block';
roomLabel.style.marginBottom = '6px';
roomLabel.style.fontWeight = '500';
roomLabel.style.fontSize = '12px';
roomLabel.style.color = '#6b7280';
const roomInput = document.createElement('input');
roomInput.type = 'text';
roomInput.value = device.room || '';
roomInput.placeholder = 'Optional room/location metadata';
roomInput.style.width = '100%';
roomInput.style.marginBottom = '12px';
roomInput.style.padding = '8px 10px';
roomInput.style.borderRadius = '6px';
roomInput.style.fontSize = '14px';
roomInput.style.boxSizing = 'border-box';
// --- BLE MAC Address ---
const macLabel = document.createElement('label');
macLabel.textContent = 'BLE MAC Address (optional)';
macLabel.style.display = 'block';
macLabel.style.marginBottom = '6px';
macLabel.style.fontWeight = '500';
macLabel.style.fontSize = '12px';
macLabel.style.color = '#6b7280';
const macInput = document.createElement('input');
macInput.type = 'text';
macInput.value = device.address || '';
macInput.placeholder = 'AA:BB:CC:DD:EE:FF';
macInput.style.width = '100%';
macInput.style.marginBottom = '12px';
macInput.style.padding = '8px 10px';
macInput.style.borderRadius = '6px';
macInput.style.fontSize = '14px';
macInput.style.boxSizing = 'border-box';
// --- Encryption Key ---
const encryptionKeyLabel = document.createElement('label');
encryptionKeyLabel.textContent = 'BLE Encryption Key (optional)';
encryptionKeyLabel.style.display = 'block';
encryptionKeyLabel.style.marginBottom = '6px';
encryptionKeyLabel.style.fontWeight = '500';
encryptionKeyLabel.style.fontSize = '12px';
encryptionKeyLabel.style.color = '#6b7280';
const encryptionKeyInput = document.createElement('input');
encryptionKeyInput.type = 'password';
encryptionKeyInput.value = device.encryptionKey || '';
encryptionKeyInput.placeholder = 'Paste device BLE encryption key';
encryptionKeyInput.style.width = '100%';
encryptionKeyInput.style.marginBottom = '12px';
encryptionKeyInput.style.padding = '8px 10px';
encryptionKeyInput.style.borderRadius = '6px';
encryptionKeyInput.style.fontSize = '14px';
encryptionKeyInput.style.boxSizing = 'border-box';
// --- Key ID ---
const keyIdLabel = document.createElement('label');
keyIdLabel.textContent = 'BLE Key ID (optional)';
keyIdLabel.style.display = 'block';
keyIdLabel.style.marginBottom = '6px';
keyIdLabel.style.fontWeight = '500';
keyIdLabel.style.fontSize = '12px';
keyIdLabel.style.color = '#6b7280';
const keyIdInput = document.createElement('input');
keyIdInput.type = 'text';
keyIdInput.value = device.keyId || '';
keyIdInput.placeholder = 'e.g. ff';
keyIdInput.style.width = '100%';
keyIdInput.style.marginBottom = '12px';
keyIdInput.style.padding = '8px 10px';
keyIdInput.style.borderRadius = '6px';
keyIdInput.style.fontSize = '14px';
keyIdInput.style.boxSizing = 'border-box';
// --- BLE Polling Enabled ---
const blePollingEnabledLabel = document.createElement('label');
blePollingEnabledLabel.textContent = 'Enable BLE Polling Fallback';
blePollingEnabledLabel.style.display = 'block';
blePollingEnabledLabel.style.marginBottom = '6px';
blePollingEnabledLabel.style.fontWeight = '500';
blePollingEnabledLabel.style.fontSize = '12px';
blePollingEnabledLabel.style.color = '#6b7280';
const blePollingEnabledInput = document.createElement('input');
blePollingEnabledInput.type = 'checkbox';
blePollingEnabledInput.checked = device.blePollingEnabled !== false; // default true
blePollingEnabledInput.style.marginRight = '8px';
blePollingEnabledInput.style.marginBottom = '12px';
// --- BLE Poll Interval ---
const blePollIntervalLabel = document.createElement('label');
blePollIntervalLabel.textContent = 'BLE Polling Interval (ms)';
blePollIntervalLabel.style.display = 'block';
blePollIntervalLabel.style.marginBottom = '6px';
blePollIntervalLabel.style.fontWeight = '500';
blePollIntervalLabel.style.fontSize = '12px';
blePollIntervalLabel.style.color = '#6b7280';
const blePollIntervalInput = document.createElement('input');
blePollIntervalInput.type = 'number';
blePollIntervalInput.value = device.blePollIntervalMs || 600000;
blePollIntervalInput.min = '60000';
blePollIntervalInput.step = '1000';
blePollIntervalInput.style.width = '100%';
blePollIntervalInput.style.marginBottom = '12px';
blePollIntervalInput.style.padding = '8px 10px';
blePollIntervalInput.style.borderRadius = '6px';
blePollIntervalInput.style.fontSize = '14px';
blePollIntervalInput.style.boxSizing = 'border-box';
const buttons = document.createElement('div');
buttons.style.display = 'flex';
buttons.style.gap = '10px';
buttons.style.justifyContent = 'flex-end';
buttons.style.marginTop = '18px';
buttons.style.paddingTop = '18px';
buttons.style.borderTop = '1px solid rgba(0, 0, 0, 0.08)';
const cancelBtn = document.createElement('button');
cancelBtn.textContent = 'Cancel';
cancelBtn.className = 'secondary';
cancelBtn.style.background = '#6b7280';
cancelBtn.style.padding = '8px 16px';
cancelBtn.style.fontSize = '13px';
const importBtn = document.createElement('button');
importBtn.textContent = 'Add to Config';
importBtn.style.background = 'var(--switchbot-red, #ef4444)';
importBtn.style.padding = '8px 20px';
importBtn.style.fontSize = '13px';
const cleanup = (result) => {
div.remove();
resolve(result);
};
cancelBtn.onclick = () => cleanup(null);
importBtn.onclick = () => {
// Never allow 'undefined' as a name
let finalName = nameInput.value;
if (!finalName || finalName === 'undefined') {
finalName = device.id || '';
}
cleanup({
configDeviceName: finalName,
configDeviceType: typeSelect.value || device.type,
address: macInput.value || undefined,
connectionPreference: connectionPrefSelect.value || undefined,
room: roomInput.value || undefined,
encryptionKey: encryptionKeyInput.value || undefined,
keyId: keyIdInput.value || undefined,
refreshRate: Number(openApiRefreshInput.value) || 300,
blePollingEnabled: blePollingEnabledInput.checked,
blePollIntervalMs: Number(blePollIntervalInput.value) || 600000,
});
};
div.addEventListener('click', (event) => {
if (event.target === div) {
cleanup(null);
}
});
buttons.appendChild(cancelBtn);
buttons.appendChild(importBtn);
contentDiv.appendChild(nameLabel);
contentDiv.appendChild(nameInput);
contentDiv.appendChild(typeLabel);
contentDiv.appendChild(typeSelect);
contentDiv.appendChild(connectionPrefLabel);
contentDiv.appendChild(connectionPrefSelect);
contentDiv.appendChild(roomLabel);
contentDiv.appendChild(roomInput);
contentDiv.appendChild(macLabel);
contentDiv.appendChild(macInput);
contentDiv.appendChild(encryptionKeyLabel);
contentDiv.appendChild(encryptionKeyInput);
contentDiv.appendChild(keyIdLabel);
contentDiv.appendChild(keyIdInput);
contentDiv.appendChild(openApiRefreshLabel);
contentDiv.appendChild(openApiRefreshInput);
contentDiv.appendChild(blePollingEnabledLabel);
contentDiv.appendChild(blePollingEnabledInput);
contentDiv.appendChild(blePollIntervalLabel);
contentDiv.appendChild(blePollIntervalInput);
contentDiv.appendChild(buttons);
modal.appendChild(title);
modal.appendChild(contentDiv);
div.appendChild(modal);
document.body.appendChild(div);
nameInput.focus();
});
}
export async function editDevice(device) {
// --- OpenAPI Polling Interval (refreshRate) ---
const openApiRefreshLabel = document.createElement('label');
openApiRefreshLabel.textContent = 'OpenAPI Polling Interval (seconds)';
openApiRefreshLabel.style.display = 'block';
openApiRefreshLabel.style.marginBottom = '6px';
openApiRefreshLabel.style.fontWeight = '500';
openApiRefreshLabel.style.fontSize = '12px';
openApiRefreshLabel.style.color = '#6b7280';
openApiRefreshLabel.title = 'How often to poll this device via OpenAPI for status (in seconds). Overrides platform value if set. Default: 300 (5 minutes). Minimum: 30.';
const openApiRefreshInput = document.createElement('input');
openApiRefreshInput.type = 'number';
openApiRefreshInput.value = device.refreshRate || 300;
openApiRefreshInput.min = '30';
openApiRefreshInput.step = '1';
openApiRefreshInput.style.width = '100%';
openApiRefreshInput.style.marginBottom = '12px';
openApiRefreshInput.style.padding = '8px 10px';
openApiRefreshInput.style.borderRadius = '6px';
openApiRefreshInput.style.fontSize = '14px';
openApiRefreshInput.style.boxSizing = 'border-box';
// --- Device Type Select ---
// --- BLE Polling Enabled ---
const blePollingEnabledLabel = document.createElement('label');
blePollingEnabledLabel.textContent = 'Enable BLE Polling Fallback';
blePollingEnabledLabel.style.display = 'block';
blePollingEnabledLabel.style.marginBottom = '6px';
blePollingEnabledLabel.style.fontWeight = '500';
blePollingEnabledLabel.style.fontSize = '12px';
blePollingEnabledLabel.style.color = '#6b7280';
const blePollingEnabledInput = document.createElement('input');
blePollingEnabledInput.type = 'checkbox';
blePollingEnabledInput.checked = device.blePollingEnabled !== false; // default true
blePollingEnabledInput.style.marginRight = '8px';
blePollingEnabledInput.style.marginBottom = '12px';
// --- BLE Poll Interval ---
const blePollIntervalLabel = document.createElement('label');
blePollIntervalLabel.textContent = 'BLE Polling Interval (ms)';
blePollIntervalLabel.style.display = 'block';
blePollIntervalLabel.style.marginBottom = '6px';
blePollIntervalLabel.style.fontWeight = '500';
blePollIntervalLabel.style.fontSize = '12px';
blePollIntervalLabel.style.color = '#6b7280';
const blePollIntervalInput = document.createElement('input');
blePollIntervalInput.type = 'number';
blePollIntervalInput.value = device.blePollIntervalMs || 600000;
blePollIntervalInput.min = '60000';
blePollIntervalInput.step = '1000';
blePollIntervalInput.style.width = '100%';
blePollIntervalInput.style.marginBottom = '12px';
blePollIntervalInput.style.padding = '8px 10px';
blePollIntervalInput.style.borderRadius = '6px';
blePollIntervalInput.style.fontSize = '14px';
blePollIntervalInput.style.boxSizing = 'border-box';
const typeLabel = document.createElement('label');
typeLabel.textContent = 'Config Device Type';
typeLabel.style.display = 'block';
typeLabel.style.marginBottom = '6px';
typeLabel.style.fontWeight = '500';
typeLabel.style.fontSize = '12px';
typeLabel.style.color = '#6b7280';
const typeSelect = document.createElement('select');
typeSelect.style.width = '100%';
typeSelect.style.padding = '8px 10px';
typeSelect.style.marginBottom = '12px';
typeSelect.style.borderRadius = '6px';
typeSelect.style.fontSize = '14px';
typeSelect.style.background = getComputedStyle(document.body).backgroundColor;
typeSelect.style.color = getComputedStyle(document.body).color;
typeSelect.style.border = '1px solid #ccc';
typeSelect.style.boxSizing = 'border-box';
// Add option groups with all API device types
Object.keys(DEVICE_TYPES).forEach((categoryName) => {
const optgroup = document.createElement('optgroup');
optgroup.label = categoryName;
DEVICE_TYPES[categoryName].forEach((deviceType) => {
const opt = document.createElement('option');
opt.value = deviceType;
opt.text = deviceType;
// Select current configDeviceType if set, otherwise match API deviceType
const currentType = device.configDeviceType || device.deviceType || device.type || '';
opt.selected = currentType === deviceType;
optgroup.appendChild(opt);
});
typeSelect.appendChild(optgroup);
});
// Create modal dialog for editing
const div = document.createElement('div');
div.style.position = 'fixed';
div.style.top = '0';
div.style.left = '0';
div.style.width = '100%';
div.style.height = '100%';
div.style.background = 'rgba(0,0,0,0.7)';
div.style.display = 'flex';
div.style.alignItems = 'center';
div.style.justifyContent = 'center';
div.style.zIndex = '9999';
const modal = document.createElement('div');
modal.style.background = getComputedStyle(document.body).backgroundColor;
modal.style.color = getComputedStyle(document.body).color;
modal.style.padding = '0';
modal.style.borderRadius = '10px';
modal.style.minWidth = '440px';
modal.style.maxWidth = '90vw';
modal.style.boxShadow = '0 8px 32px rgba(0,0,0,0.35)';
modal.style.overflow = 'hidden';
modal.style.borderTop = '3px solid var(--switchbot-red, #ef4444)';
const title = document.createElement('h3');
title.textContent = 'Edit Device';
title.style.marginTop = '0';
title.style.marginBottom = '16px';
title.style.padding = '20px 20px 0';
title.style.fontSize = '18px';
title.style.fontWeight = '600';
title.style.color = 'var(--switchbot-red, #ef4444)';
title.style.letterSpacing = '-0.02em';
const contentDiv = document.createElement('div');
contentDiv.style.padding = '0 20px 20px';
const nameLabel = document.createElement('label');
nameLabel.textContent = 'Device Name';
nameLabel.style.display = 'block';
nameLabel.style.marginBottom = '6px';
nameLabel.style.fontWeight = '500';
nameLabel.style.fontSize = '12px';
nameLabel.style.color = '#6b7280';
const nameInput = document.createElement('input');
nameInput.type = 'text';
nameInput.value = device.name || device.id;
nameInput.style.width = '100%';
nameInput.style.marginBottom = '12px';
nameInput.style.padding = '8px 10px';
nameInput.style.borderRadius = '6px';
nameInput.style.fontSize = '14px';
nameInput.style.boxSizing = 'border-box';
nameInput.style.transition = 'border-color 0.2s ease';
// Read-only API device type field
const apiTypeLabel = document.createElement('label');
apiTypeLabel.textContent = 'Device Type (API - Read Only)';
apiTypeLabel.style.display = 'block';
apiTypeLabel.style.marginBottom = '6px';
apiTypeLabel.style.fontWeight = '500';
apiTypeLabel.style.fontSize = '12px';
apiTypeLabel.style.color = '#6b7280';
const apiTypeInput = document.createElement('input');
apiTypeInput.type = 'text';
apiTypeInput.value = device.deviceType || device.type || 'Unknown';
apiTypeInput.readOnly = true;
apiTypeInput.style.width = '100%';
apiTypeInput.style.marginBottom = '12px';
apiTypeInput.style.padding = '8px 10px';
apiTypeInput.style.borderRadius = '6px';
apiTypeInput.style.fontSize = '13px';
apiTypeInput.style.opacity = '0.6';
apiTypeInput.style.cursor = 'not-allowed';
apiTypeInput.style.boxSizing = 'border-box';
apiTypeInput.style.backgroundColor = '#f9fafb';
// Editable config device type dropdown
// Add option groups with all API device types
Object.keys(DEVICE_TYPES).forEach((categoryName) => {
const optgroup = document.createElement('optgroup');
optgroup.label = categoryName;
DEVICE_TYPES[categoryName].forEach((deviceType) => {
const opt = document.createElement('option');
opt.value = deviceType;
opt.text = deviceType;
// Select current configDeviceType if set, otherwise match API deviceType
const currentType = device.configDeviceType || device.deviceType || device.type || '';
opt.selected = currentType === deviceType;
optgroup.appendChild(opt);
});
typeSelect.appendChild(optgroup);
});
// --- Connection Preference ---
const connectionPrefLabel = document.createElement('label');
connectionPrefLabel.textContent = 'Connection Preference';
connectionPrefLabel.style.display = 'block';
connectionPrefLabel.style.marginBottom = '6px';
connectionPrefLabel.style.fontWeight = '500';
connectionPrefLabel.style.fontSize = '12px';
connectionPrefLabel.style.color = '#6b7280';
const connectionPrefSelect = document.createElement('select');
connectionPrefSelect.style.width = '100%';
connectionPrefSelect.style.marginBottom = '12px';
connectionPrefSelect.style.padding = '8px 10px';
connectionPrefSelect.style.borderRadius = '6px';
connectionPrefSelect.style.fontSize = '14px';
connectionPrefSelect.style.boxSizing = 'border-box';
['auto', 'ble', 'openapi'].forEach((val) => {
const opt = document.createElement('option');
opt.value = val;
opt.text = val.charAt(0).toUpperCase() + val.slice(1);
opt.selected = (device.connectionPreference || 'auto') === val;
connectionPrefSelect.appendChild(opt);
});
// --- Room ---
const roomLabel = document.createElement('label');
roomLabel.textContent = 'Room';
roomLabel.style.display = 'block';
roomLabel.style.marginBottom = '6px';
roomLabel.style.fontWeight = '500';
roomLabel.style.fontSize = '12px';
roomLabel.style.color = '#6b7280';
const roomInput = document.createElement('input');
roomInput.type = 'text';
roomInput.value = device.room || '';
roomInput.placeholder = 'Optional room/location metadata';
roomInput.style.width = '100%';
roomInput.style.marginBottom = '12px';
roomInput.style.padding = '8px 10px';
roomInput.style.borderRadius = '6px';
roomInput.style.fontSize = '14px';
roomInput.style.boxSizing = 'border-box';
// --- Encryption Key ---
const encryptionKeyLabel = document.createElement('label');
encryptionKeyLabel.textContent = 'BLE Encryption Key (optional)';
encryptionKeyLabel.style.display = 'block';
encryptionKeyLabel.style.marginBottom = '6px';
encryptionKeyLabel.style.fontWeight = '500';
encryptionKeyLabel.style.fontSize = '12px';
encryptionKeyLabel.style.color = '#6b7280';
const encryptionKeyInput = document.createElement('input');
encryptionKeyInput.type = 'password';
encryptionKeyInput.value = device.encryptionKey || '';
encryptionKeyInput.placeholder = 'Paste device BLE encryption key';
encryptionKeyInput.style.width = '100%';
encryptionKeyInput.style.marginBottom = '12px';
encryptionKeyInput.style.padding = '8px 10px';
encryptionKeyInput.style.borderRadius = '6px';
encryptionKeyInput.style.fontSize = '14px';
encryptionKeyInput.style.boxSizing = 'border-box';
// --- Key ID ---
const keyIdLabel = document.createElement('label');
keyIdLabel.textContent = 'BLE Key ID (optional)';
keyIdLabel.style.display = 'block';
keyIdLabel.style.marginBottom = '6px';
keyIdLabel.style.fontWeight = '500';
keyIdLabel.style.fontSize = '12px';
keyIdLabel.style.color = '#6b7280';
const keyIdInput = document.createElement('input');
keyIdInput.type = 'text';
keyIdInput.value = device.keyId || '';
keyIdInput.placeholder = 'e.g. ff';
keyIdInput.style.width = '100%';
keyIdInput.style.marginBottom = '12px';
keyIdInput.style.padding = '8px 10px';
keyIdInput.style.borderRadius = '6px';
keyIdInput.style.fontSize = '14px';
keyIdInput.style.boxSizing = 'border-box';
const errorMessage = document.createElement('div');
errorMessage.style.color = 'var(--switchbot-red, #ef4444)';
errorMessage.style.marginBottom = '12px';
errorMessage.style.fontSize = '12px';
errorMessage.style.display = 'none';
errorMessage.style.padding = '8px 10px';
errorMessage.style.background = 'var(--switchbot-red-light, #fee2e2)';
errorMessage.style.borderRadius = '6px';
errorMessage.style.fontWeight = '500';
const buttons = document.createElement('div');
buttons.style.display = 'flex';
buttons.style.gap = '10px';
buttons.style.justifyContent = 'flex-end';
buttons.style.marginTop = '18px';
buttons.style.paddingTop = '18px';
buttons.style.borderTop = '1px solid rgba(0, 0, 0, 0.08)';
const cancelBtn = document.createElement('button');
cancelBtn.textContent = 'Cancel';
cancelBtn.className = 'secondary';
cancelBtn.style.background = '#6b7280';
cancelBtn.style.padding = '8px 16px';
cancelBtn.style.fontSize = '13px';
cancelBtn.onclick = () => div.remove();
const saveBtn = document.createElement('button');
saveBtn.textContent = 'Save';
saveBtn.style.background = 'var(--switchbot-red, #ef4444)';
saveBtn.style.padding = '8px 20px';
saveBtn.style.fontSize = '13px';
saveBtn.onclick = async () => {
try {
const { updateDevice, syncParentPluginConfigFromDisk, fetchDevices } = await import('./api.js');
const { renderDeviceList } = await import('./render.js');
const params = {
deviceId: device.id,
configDeviceName: nameInput.value || undefined,
configDeviceType: typeSelect.value,
connectionPreference: connectionPrefSelect.value,
room: roomInput.value || undefined,
encryptionKey: encryptionKeyInput.value || undefined,
keyId: keyIdInput.value || undefined,
refreshRate: Number(openApiRefreshInput.value) || 300,
blePollingEnabled: blePollingEnabledInput.checked,
blePollIntervalMs: Number(blePollIntervalInput.value) || 600000,
};
// Only include defined properties in the options object
const options = {};
if (params.connectionPreference !== undefined) {
options.connectionPreference = params.connectionPreference;
}
if (params.room !== undefined) {
options.room = params.room;
}
if (params.encryptionKey !== undefined) {
options.encryptionKey = params.encryptionKey;
}
if (params.keyId !== undefined) {
options.keyId = params.keyId;
}
if (params.refreshRate !== undefined) {
options.refreshRate = params.refreshRate;
}
if (params.blePollingEnabled !== undefined) {
options.blePollingEnabled = params.blePollingEnabled;
}
if (params.blePollIntervalMs !== undefined) {
options.blePollIntervalMs = params.blePollIntervalMs;
}
await updateDevice(params.deviceId, params.configDeviceName, params.configDeviceType, options);
await syncParentPluginConfigFromDisk();
contentDiv.appendChild(openApiRefreshLabel);
contentDiv.appendChild(openApiRefreshInput);
// Refresh device list
uiLog.info('[Edit Device] Refreshing device list after update');
const list = await fetchDevices();
renderDeviceList(list);
div.remove();
}
catch (e) {
uiLog.error('Update error:', e);
errorMessage.textContent = `Error: ${e instanceof Error ? e.message : 'Failed to update device'}`;
errorMessage.style.display = 'block';
}
};
buttons.appendChild(cancelBtn);
buttons.appendChild(saveBtn);
contentDiv.appendChild(nameLabel);
contentDiv.appendChild(nameInput);
contentDiv.appendChild(apiTypeLabel);
contentDiv.appendChild(apiTypeInput);
contentDiv.appendChild(typeLabel);
contentDiv.appendChild(typeSelect);
contentDiv.appendChild(connectionPrefLabel);
contentDiv.appendChild(connectionPrefSelect);
contentDiv.appendChild(roomLabel);
contentDiv.appendChild(roomInput);
contentDiv.appendChild(encryptionKeyLabel);
contentDiv.appendChild(encryptionKeyInput);
contentDiv.appendChild(keyIdLabel);
contentDiv.appendChild(keyIdInput);
// Insert BLE polling fields before error/buttons
contentDiv.appendChild(openApiRefreshLabel);
contentDiv.appendChild(openApiRefreshInput);
contentDiv.appendChild(blePollingEnabledLabel);
contentDiv.appendChild(blePollingEnabledInput);
contentDiv.appendChild(blePollIntervalLabel);
contentDiv.appendChild(blePollIntervalInput);
contentDiv.appendChild(errorMessage);
contentDiv.appendChild(buttons);
modal.appendChild(title);
modal.appendChild(contentDiv);
div.appendChild(modal);
document.body.appendChild(div);
nameInput.focus();
}
//# sourceMappingURL=modals.js.map