This commit is contained in:
sebseb7
2025-12-25 05:30:06 +01:00
parent 8f01f35470
commit ab89cbc97f
4 changed files with 195 additions and 2 deletions

View File

@@ -229,6 +229,115 @@ export class ACInfinityClient {
return readings;
}
async getDeviceModeSettings(devId, port) {
if (!this.isLoggedIn()) {
await this.login();
}
try {
const response = await fetch(`${this.host}/api/dev/getdevModeSettingList`, {
method: 'POST',
headers: {
'User-Agent': 'ACController/1.9.7 (com.acinfinity.humiture; build:533; iOS 18.5.0) Alamofire/5.10.2',
'Content-Type': 'application/x-www-form-urlencoded; charset=utf-8',
...this.getAuthHeaders(),
},
body: new URLSearchParams({
devId: devId,
port: port.toString()
})
});
const data = await response.json();
return data.data;
} catch (err) {
console.error('[AC] Error getting device settings:', err);
throw err;
}
}
/**
* Set device port mode/level
* @param {string} devId - Device ID
* @param {number} port - Port number (1-4)
* @param {number} level - Level 0-10
*/
async setDevicePort(devId, port, level) {
if (!this.isLoggedIn()) {
await this.login();
}
try {
// 1. Get existing settings
const settings = await this.getDeviceModeSettings(devId, port);
if (!settings) throw new Error('Could not fetch existing settings');
// 2. Prepare updates
// Constrain level 0-10
const safeLevel = Math.max(0, Math.min(10, Math.round(level)));
// Mode 1 = ON (Manual), 0 = OFF
const mode = safeLevel === 0 ? 0 : 1;
// Merge with existing settings
// We need to send back mostly specific keys.
// Based on reference usage, we can try merging into a new object using existing keys
// but 'mode' and 'speak' are overrides.
const params = new URLSearchParams();
// Add required base params
params.append('userId', this.userId);
params.append('devId', devId);
params.append('port', port.toString());
// Add mode/speak
params.append('mode', mode.toString());
params.append('speak', safeLevel.toString());
// Copy other relevant fields from settings if they exist to maintain state
// Common fields seen in other implementations:
// transitionType, surplus, backup, trigger related fields...
// For addDevMode, usually just the basics + what we want to change is enough IF the server merges?
// But the error 999999 suggests missing fields.
// Let's copy everything from settings that looks like a config parameter
const keyBlocklist = ['devId', 'port', 'mode', 'speak', 'devName', 'deviceInfo', 'devType', 'macAddr'];
for (const [key, val] of Object.entries(settings)) {
if (!keyBlocklist.includes(key) && typeof val !== 'object') {
params.append(key, val);
}
}
// Ensure defaults if missing
if (!params.has('surplus')) params.append('surplus', '0');
if (!params.has('backup')) params.append('backup', '0');
if (!params.has('transitionType')) params.append('transitionType', '0');
// 3. Send update
const response = await fetch(`${this.host}/api/dev/addDevMode?${params.toString()}`, {
method: 'POST',
headers: {
'User-Agent': 'ACController/1.9.7 (com.acinfinity.humiture; build:533; iOS 18.5.0) Alamofire/5.10.2',
'Content-Type': 'application/x-www-form-urlencoded; charset=utf-8',
...this.getAuthHeaders(),
},
});
const data = await response.json();
if (data.code !== 200) {
throw new ACInfinityClientError(`Set mode failed: ${JSON.stringify(data)}`);
}
console.log(`[AC] Set device ${devId} port ${port} to level ${safeLevel} (mode ${mode})`);
return true;
} catch (error) {
console.error('[AC] Error setting device port:', error);
return false;
}
}
}
export default ACInfinityClient;

View File

@@ -60,6 +60,68 @@ async function main() {
// Connect to WebSocket server
await wsClient.connect();
// Handle commands from server
wsClient.onCommand(async (cmd) => {
console.log('[Main] Received command:', cmd);
const { device, value } = cmd; // e.g. device="tent:fan"
if (!device) return;
try {
// Fetch latest device list to get IDs and port mapping
const devices = await acClient.getDevicesListAll();
// Parse "tent:fan" -> devName="tent", portName="fan"
// If just "tent", assume port 1 or device level
const parts = device.split(':');
const targetDevName = parts[0];
const targetPortName = parts[1];
// Find matching device by name
const dev = devices.find(d => {
const name = (d.devName || `device-${d.devId}`).toLowerCase();
return name.includes(targetDevName.toLowerCase());
});
if (!dev) {
console.error(`[Main] Device not found: ${targetDevName}`);
return;
}
// Find port index
// Structure varies: dev.deviceInfo.ports OR dev.devPortList
const info = dev.deviceInfo || dev;
const ports = info.ports || dev.devPortList || [];
let portId = 0; // 0 usually means "All" or "Device"? But setDevicePort expects 1-4.
// If explicit port set
if (targetPortName) {
const port = ports.find(p => {
const pName = (p.portName || `port${p.port || p.portId}`).toLowerCase();
return pName.includes(targetPortName.toLowerCase());
});
if (port) {
portId = port.port || port.portId;
} else {
// Check if it's a number
const pNum = parseInt(targetPortName);
if (!isNaN(pNum)) portId = pNum;
}
} else {
// Default to first port if available, or 0?
// Let's assume port 1 if no specific port requested but ports exist
if (ports.length > 0) portId = ports[0].port || ports[0].portId;
}
console.log(`[Main] Setting ${dev.devName} (${dev.devId}) port ${portId} to ${value}`);
await acClient.setDevicePort(dev.devId, portId, value);
} catch (err) {
console.error('[Main] Error handling command:', err);
}
});
// Start polling
console.log(`[Main] Starting polling every ${config.pollIntervalMs / 1000}s`);

View File

@@ -22,6 +22,11 @@ export class WSClient {
this.pingTimer = null;
this.messageQueue = [];
this.onReadyCallback = null;
this.onCommandCallback = null;
}
onCommand(callback) {
this.onCommandCallback = callback;
}
/**
@@ -104,6 +109,12 @@ export class WSClient {
console.error('[WS] Server error:', message.error);
break;
case 'command':
if (this.onCommandCallback) {
this.onCommandCallback(message);
}
break;
default:
console.log('[WS] Unknown message type:', message.type);
}