forked from iarv/meshtastic-cli-iobroker-mqtt
Compare commits
4 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
00c118ab4a | ||
|
|
aa562b8ab6 | ||
|
|
df58a950bd | ||
|
|
0b3c39b964 |
@@ -18,7 +18,7 @@ Ziel ist es, Meshtastic-Netzwerke in ioBroker sichtbar und steuerbar zu machen:
|
||||
send TraceRoute, Ping, Message, u.v.m.
|
||||
|
||||
- **Echtzeit-Chat** via MQTT
|
||||
Nachrichtenempfang (LoRa & MQTT) direkt in ioBroker-Datenpunkten
|
||||
Nachrichtenempfang und Sendung (LoRa & MQTT) direkt in ioBroker-Datenpunkten
|
||||
|
||||
- **Multi-Kanal Support**
|
||||
Primary/Secondary Channels werden unterstützt
|
||||
|
||||
@@ -9,8 +9,8 @@ var mqttPath = 'mqtt.3.msh.*.json.*';
|
||||
|
||||
var chats = [
|
||||
{ name: 'Default', id: 0 },
|
||||
{ name: 'Puig', id: 1 },
|
||||
{ name: 'Baleares', id: 2 }
|
||||
{ name: '<private channel>', id: 1 },
|
||||
{ name: '<public channel>', id: 2 }
|
||||
];
|
||||
|
||||
|
||||
@@ -57,9 +57,12 @@ on({id: /^mqtt\.3\.msh\..*\.json\..*$/, change: "any"}, function (obj) {
|
||||
}
|
||||
|
||||
// Save in Chat Channel
|
||||
const chatMsgPath = '0_userdata.0.Meshtastic.Chats.' + channelIdx + '.lastMessage';
|
||||
if (getObject('0_userdata.0.Meshtastic.Chats.' + channelIdx)) {
|
||||
setState(chatMsgPath, `${displayName}: ${text}`, true);
|
||||
const chatPath = '0_userdata.0.Meshtastic.Chats.' + channelIdx;
|
||||
if (getObject(chatPath)) {
|
||||
setState(chatPath + '.lastMessage', `${displayName}: ${text}`, true);
|
||||
|
||||
// --- add to history ---
|
||||
addToHistory(channelIdx, displayName, text);
|
||||
}
|
||||
|
||||
log(`Meshtastic Chat [${channelIdx}]: ${displayName} sagt "${text}"`);
|
||||
@@ -69,6 +72,49 @@ on({id: /^mqtt\.3\.msh\..*\.json\..*$/, change: "any"}, function (obj) {
|
||||
}
|
||||
});
|
||||
|
||||
// Trigger für Nachrichten an Kanäle (Chats)
|
||||
on({id: /^0_userdata\.0\.Meshtastic\.Chats\.\d+\.sendMessage$/, change: "any", ack: false}, function (obj) {
|
||||
const msg = obj.state.val;
|
||||
if (!msg || msg === "") return;
|
||||
|
||||
const parts = obj.id.split('.');
|
||||
// parseInt stellt sicher, dass channelId eine Nummer ist
|
||||
const channelId = parseInt(parts[parts.length - 2]);
|
||||
|
||||
log(`Meshtastic: Sende Nachricht an Kanal ${channelId}: ${msg}`);
|
||||
|
||||
// In der CLI-Exec wird channelId wieder zum String für den Befehl,
|
||||
// aber für addToHistory ist es nun die korrekte Nummer.
|
||||
exec(`/home/iobroker/.local/bin/meshtastic --host ${deviceIp} --ch-index ${channelId} --sendtext "${msg}"`, function (error, result, stderr) {
|
||||
if (error) {
|
||||
log(`Fehler beim Senden: ${stderr}`, 'error');
|
||||
} else {
|
||||
setState(obj.id, "", true);
|
||||
// Jetzt passt der Typ für den ersten Parameter (number)
|
||||
addToHistory(channelId, "ICH (ioBroker)", msg);
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
// Trigger für Direktnachrichten an einzelne Nodes
|
||||
on({id: /^0_userdata\.0\.Meshtastic\.Nodes\..*\.command\.sendMessage$/, change: "any", ack: false}, function (obj) {
|
||||
const msg = obj.state.val;
|
||||
if (!msg || msg === "") return;
|
||||
|
||||
const parts = obj.id.split('.');
|
||||
const nodeId = parts[parts.length - 3]; // Pfad ist Nodes.ID.command.sendMessage
|
||||
|
||||
log(`Meshtastic: Sende Direktnachricht an !${nodeId}: ${msg}`);
|
||||
|
||||
exec(`/home/iobroker/.local/bin/meshtastic --host ${deviceIp} --dest "!${nodeId}" --sendtext "${msg}"`, function (error, result, stderr) {
|
||||
if (error) {
|
||||
log(`Fehler beim Senden an Node: ${stderr}`, 'error');
|
||||
} else {
|
||||
setState(obj.id, "", true);
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
// ======================================================
|
||||
// 2. CLI Node Polling + Parsing
|
||||
// ======================================================
|
||||
@@ -283,9 +329,88 @@ function createChats() {
|
||||
},
|
||||
native: {}
|
||||
});
|
||||
|
||||
// message history as JSON (read-only)
|
||||
setObject('0_userdata.0.Meshtastic.Chats.' + chatObj.id + '.history', {
|
||||
type: 'state',
|
||||
common: {
|
||||
name: 'Chat Historie JSON',
|
||||
type: 'string',
|
||||
role: 'json',
|
||||
read: true,
|
||||
write: false
|
||||
},
|
||||
native: {}
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Fügt eine Nachricht zum JSON-Verlauf und HTML-String eines Kanals hinzu
|
||||
* @param {number} channelIdx - Index des Kanals (0, 1, 2...)
|
||||
* @param {string} senderName - Aufgelöster Name oder ID des Absenders
|
||||
* @param {string} messageText - Inhalt der Nachricht
|
||||
*/
|
||||
function addToHistory(channelIdx, senderName, messageText) {
|
||||
const basePath = '0_userdata.0.Meshtastic.Chats.' + channelIdx;
|
||||
const historyPath = basePath + '.history';
|
||||
const htmlPath = basePath + '.history_html';
|
||||
const maxEntries = 20; // Anzahl der gespeicherten Nachrichten
|
||||
|
||||
// --- 1. JSON HISTORY (für Jarvis JsonTable) ---
|
||||
let history = [];
|
||||
if (existsState(historyPath)) {
|
||||
let currentState = getState(historyPath).val;
|
||||
try {
|
||||
history = JSON.parse(currentState) || [];
|
||||
} catch (e) { history = []; }
|
||||
}
|
||||
|
||||
// Neues Objekt erstellen
|
||||
const newEntry = {
|
||||
ts: Date.now(),
|
||||
time: formatDate(new Date(), "HH:mm"),
|
||||
from: senderName,
|
||||
text: messageText
|
||||
};
|
||||
|
||||
// Oben einfügen & Kürzen
|
||||
history.unshift(newEntry);
|
||||
if (history.length > maxEntries) history = history.slice(0, maxEntries);
|
||||
|
||||
// JSON Speichern
|
||||
setState(historyPath, JSON.stringify(history), true);
|
||||
|
||||
|
||||
// --- 2. HTML HISTORY (für Jarvis StateHTML / Messenger-Look) ---
|
||||
let html = '<div style="display:flex; flex-direction:column; gap:10px; font-family:sans-serif;">';
|
||||
|
||||
history.forEach(m => {
|
||||
html += `
|
||||
<div style="background:rgba(128,128,128,0.1); padding:8px 12px; border-radius:12px; border-left:4px solid #009688; max-width:95%;">
|
||||
<div style="font-size:0.75em; opacity:0.6; margin-bottom:4px;">
|
||||
<span style="font-weight:bold; color:#009688;">${m.from}</span> • ${m.time}
|
||||
</div>
|
||||
<div style="font-size:0.95em; line-height:1.3; word-wrap:break-word;">
|
||||
${m.text}
|
||||
</div>
|
||||
</div>`;
|
||||
});
|
||||
|
||||
html += '</div>';
|
||||
|
||||
// HTML Datenpunkt prüfen und schreiben
|
||||
if (!existsState(htmlPath)) {
|
||||
setObject(htmlPath, {
|
||||
type: 'state',
|
||||
common: { name: 'Chat Historie HTML', type: 'string', role: 'html', read: true, write: false },
|
||||
native: {}
|
||||
});
|
||||
setTimeout(() => { setState(htmlPath, html, true); }, 200);
|
||||
} else {
|
||||
setState(htmlPath, html, true);
|
||||
}
|
||||
}
|
||||
|
||||
// ======================================================
|
||||
// 6. Command Listener + CLI Actions
|
||||
|
||||
Reference in New Issue
Block a user