feat: initial myprimal MQTT bridge service
Dynamic MySolem/MyIndygo IoT bridge with MQTT, Home Assistant autodiscovery, and Homebridge EasyMQTT compatibility. - Device discovery via MySolem API on startup (all modules/inputs/outputs) - Configurable poll engine (fast/normal/slow buckets, per-input overrides) - MQTT retained state publishing + HA autodiscovery payloads - Unified manual command routing (linesControl vs sendManualModuleCommand) - Runtime config via MQTT $config/set, persisted to config.json - Expression override system (API-provided > hardcoded > passthrough) - Minimal REST API: /health, /state, /control, /rediscover - Docker deployment (Dockerfile + docker-compose.yml) - 38 unit tests Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
@@ -0,0 +1,56 @@
|
||||
'use strict';
|
||||
const { getClient, getPrefix, publish } = require('./client');
|
||||
const { allOutputs } = require('../registry');
|
||||
const { sendCommand } = require('../control');
|
||||
|
||||
function subscribeAll() {
|
||||
const client = getClient();
|
||||
if (!client) return;
|
||||
const prefix = getPrefix();
|
||||
|
||||
for (const out of allOutputs()) {
|
||||
const topic = `${prefix}/${out.moduleSlug}/output/${out.index}/set`;
|
||||
client.subscribe(topic);
|
||||
}
|
||||
|
||||
client.on('message', async (topic, buf) => {
|
||||
const prefix_ = `${prefix}/`;
|
||||
if (!topic.startsWith(prefix_)) return;
|
||||
|
||||
// Match: {prefix}/{slug}/output/{index}/set
|
||||
const parts = topic.slice(prefix_.length).split('/');
|
||||
if (parts.length !== 4 || parts[1] !== 'output' || parts[3] !== 'set') return;
|
||||
|
||||
const [slug, , indexStr] = parts;
|
||||
const outputIndex = parseInt(indexStr, 10);
|
||||
|
||||
let cmd;
|
||||
try {
|
||||
cmd = JSON.parse(buf.toString());
|
||||
} catch {
|
||||
console.warn(`[commands] Invalid JSON on ${topic}`);
|
||||
return;
|
||||
}
|
||||
|
||||
// Find module by slug
|
||||
const output = allOutputs().find(o => o.moduleSlug === slug && o.index === outputIndex);
|
||||
if (!output) {
|
||||
console.warn(`[commands] No output found for ${slug}/output/${outputIndex}`);
|
||||
return;
|
||||
}
|
||||
|
||||
try {
|
||||
await sendCommand(output.moduleId, outputIndex, cmd);
|
||||
// Reflect running state optimistically
|
||||
const stateValue = (cmd.action === 'run' || cmd.action === 'boost') ? 1 : 0;
|
||||
publish(`${slug}/output/${outputIndex}/state`, { value: stateValue, ts: new Date().toISOString() });
|
||||
console.log(`[commands] ${slug}/output/${outputIndex} → ${cmd.action}`);
|
||||
} catch (e) {
|
||||
console.error(`[commands] Command failed for ${slug}/output/${outputIndex}:`, e.message);
|
||||
}
|
||||
});
|
||||
|
||||
console.log(`[commands] Subscribed to ${allOutputs().length} output set topics`);
|
||||
}
|
||||
|
||||
module.exports = { subscribeAll };
|
||||
Reference in New Issue
Block a user