fix: publish availability as JSON to fix EasyMQTT parse error

Plain string 'online'/'offline' caused SyntaxError in EasyMQTT which
wildcard-subscribes and JSON.parses all messages. Switch to
{"state":"online"} payload with availability_template in HA discovery.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
2026-06-28 01:25:30 +02:00
parent 15ab63c3e5
commit 15ed8f8723
+45 -21
View File
@@ -3,6 +3,12 @@ const { getClient, getPrefix } = require('./client');
const { allInputs, allOutputs } = require('../registry'); const { allInputs, allOutputs } = require('../registry');
const { getInputType } = require('../input-types'); const { getInputType } = require('../input-types');
// Availability payload is JSON so EasyMQTT (wildcard subscriber) can parse it.
// HA uses availability_template to extract the state field.
const AVAIL_ONLINE = JSON.stringify({ state: 'online' });
const AVAIL_OFFLINE = JSON.stringify({ state: 'offline' });
const AVAIL_TEMPLATE = '{{ value_json.state }}';
function publishAll() { function publishAll() {
const client = getClient(); const client = getClient();
if (!client) return; if (!client) return;
@@ -14,11 +20,14 @@ function publishAll() {
const availTopic = `${prefix}/${inp.moduleSlug}/availability`; const availTopic = `${prefix}/${inp.moduleSlug}/availability`;
const payload = { const payload = {
name: inp.name || inp.metric, name: inp.name || inp.metric,
unique_id: `myprimal_${inp.id}`, unique_id: `myprimal_${inp.id}`,
state_topic: stateTopic, state_topic: stateTopic,
value_template: '{{ value_json.value }}', value_template: '{{ value_json.value }}',
availability_topic: availTopic, availability_topic: availTopic,
availability_template: AVAIL_TEMPLATE,
payload_available: 'online',
payload_not_available: 'offline',
device: { device: {
identifiers: [`myprimal_${inp.moduleId}`], identifiers: [`myprimal_${inp.moduleId}`],
name: inp.moduleName, name: inp.moduleName,
@@ -35,21 +44,24 @@ function publishAll() {
} }
for (const out of allOutputs()) { for (const out of allOutputs()) {
const stateTopic = `${prefix}/${out.moduleSlug}/output/${out.index}/state`; const stateTopic = `${prefix}/${out.moduleSlug}/output/${out.index}/state`;
const cmdTopic = `${prefix}/${out.moduleSlug}/output/${out.index}/set`; const cmdTopic = `${prefix}/${out.moduleSlug}/output/${out.index}/set`;
const availTopic = `${prefix}/${out.moduleSlug}/availability`; const availTopic = `${prefix}/${out.moduleSlug}/availability`;
const payload = { const payload = {
name: out.name, name: out.name,
unique_id: `myprimal_${out.moduleId}_${out.index}`, unique_id: `myprimal_${out.moduleId}_${out.index}`,
state_topic: stateTopic, state_topic: stateTopic,
command_topic: cmdTopic, command_topic: cmdTopic,
payload_on: JSON.stringify({ action: 'run', duration: 30 }), payload_on: JSON.stringify({ action: 'run', duration: 30 }),
payload_off: JSON.stringify({ action: 'stop' }), payload_off: JSON.stringify({ action: 'stop' }),
value_template: '{{ value_json.value }}', value_template: '{{ value_json.value }}',
state_on: '1', state_on: '1',
state_off: '0', state_off: '0',
availability_topic: availTopic, availability_topic: availTopic,
availability_template: AVAIL_TEMPLATE,
payload_available: 'online',
payload_not_available: 'offline',
device: { device: {
identifiers: [`myprimal_${out.moduleId}`], identifiers: [`myprimal_${out.moduleId}`],
name: out.moduleName, name: out.moduleName,
@@ -61,16 +73,28 @@ function publishAll() {
client.publish(configTopic, JSON.stringify(payload), { retain: true }); client.publish(configTopic, JSON.stringify(payload), { retain: true });
} }
// Publish availability online for all modules // Publish availability for all modules (JSON payload)
const seen = new Set(); const seen = new Set();
for (const inp of allInputs()) { for (const inp of allInputs()) {
if (!seen.has(inp.moduleSlug)) { if (!seen.has(inp.moduleSlug)) {
seen.add(inp.moduleSlug); seen.add(inp.moduleSlug);
getClient().publish(`${prefix}/${inp.moduleSlug}/availability`, 'online', { retain: true }); client.publish(`${prefix}/${inp.moduleSlug}/availability`, AVAIL_ONLINE, { retain: true });
}
}
for (const out of allOutputs()) {
if (!seen.has(out.moduleSlug)) {
seen.add(out.moduleSlug);
client.publish(`${prefix}/${out.moduleSlug}/availability`, AVAIL_ONLINE, { retain: true });
} }
} }
console.log('[discovery] HA autodiscovery published'); console.log('[discovery] HA autodiscovery published');
} }
module.exports = { publishAll }; function publishOffline(moduleSlug) {
const client = getClient();
if (!client) return;
client.publish(`${getPrefix()}/${moduleSlug}/availability`, AVAIL_OFFLINE, { retain: true });
}
module.exports = { publishAll, publishOffline };