@rushmed
here we go
Javascript (wie gesagt da kommen Fehlermeldungen, ich warte da mal ein paar Tage ob sich der Owner meldet):
const DEBUG = false
let filePath = '/opt/iobroker/iobroker-data/ElsterTable.inc';
let elsterJsObject = convertToJsObject(filePath);
const CanScnifferId = "mqtt.0.cansniffer"
//const CanScnifferId = "mqtt.0.cansniffer2.142"
let scanAllElsterIdsTimerVar, scanAllStatestimerVar, scanShortStatestimerVar
let GlobscanAllElsterIds = {
lastPosition: 0,
lastkey: "0",
//geraet: 0x0180
}
// Das muss einmal pro Zieladresse ausgeführt werden um zu scannen welche Datenpunke abfragbar sind
// Jeweils einmal pro Adresse
//scanAllElsterIds(0x0700,0)
let GlobscanAllStates = {
lastPosition: 0,
lastkey: "0",
intervall: 10 * 60 * 1000,
}
scanAllStates()
// Die Hier angegebenen Datenpunkte werden in einem unter Intervall angegebenen Intervall in Millisekunden regelmäßig aktiv abgefragt
let GlobshortScan = {
lastPosition: 0,
lastkey: "0",
intervall: 30000,
iDs: [
// { geraet: 0x0180, elsterName: "WPVORLAUFIST" },
// { geraet: 0x0180, elsterName: "RUECKLAUFISTTEMP" },
// { geraet: 0x0180, elsterName: "MASCHINENDRUCK" },
// { geraet: 0x0301, elsterName: "VORLAUFSOLLTEMP" },
// { geraet: 0x0180, elsterName: "PUFFERSOLL" },
]
}
scanShortStates()
on({ id: CanScnifferId + '.pload', change: "ne" }, function (obj) {
if (DEBUG || false) log(obj.state.val)
const telegramObj = parseCanTelegram(JSON.parse(obj.state.val))
if (Number(telegramObj.elsterIndex) == Number(GlobscanAllElsterIds.lastkey)) {
//log("gleich")
//log(Number(telegramObj.elsterIndex) + " - " + Number(GlobscanAllElsterIds.lastkey))
clearTimeout(scanAllElsterIdsTimerVar)
scanAllElsterIds(GlobscanAllElsterIds.geraet, GlobscanAllElsterIds.lastPosition + 1)
}
if (Number(telegramObj.elsterIndex) == Number(GlobscanAllStates.lastkey)) {
//log("gleich")
//log(Number(telegramObj.elsterIndex) + " - " + Number(GlobscanAllElsterIds.lastkey))
clearTimeout(scanAllStatestimerVar)
scanAllStates(GlobscanAllStates.lastPosition + 1)
}
if (Number(telegramObj.elsterIndex) == Number(GlobshortScan.lastkey)) {
//log("gleich")
//log(Number(telegramObj.elsterIndex) + " - " + Number(GlobscanAllElsterIds.lastkey))
clearTimeout(scanShortStatestimerVar)
scanShortStates(GlobshortScan.lastPosition + 1)
}
//evaluateMessages(JSON.parse(obj.state.val))
//log(JSON.stringify(telegramObj));
//writeLog(JSON.stringify(telegramObj)), 'CanLog.csv').catch(error => console.error(JSON.stringify(error)));
});
function convertToJsObject(filePath) {
// In einer echten JavaScript-Umgebung müsste hier die fs-Bibliothek verwendet werden,
// um die Datei zu lesen. Der folgende Code ist eine vereinfachte Darstellung.
const fs = require('fs');
let elsterTable = {};
// Lies die Datei synchron, dies ist in einem realen Szenario zu vermeiden
let fileContent = fs.readFileSync(filePath, 'utf-8');
let lines = fileContent.split('\n');
lines.forEach((line) => {
// Ignorieren von Kommentaren und leeren Zeilen
if (line.startsWith('//') || line.trim() === '' || line.startsWith('{') || line.startsWith('}')) {
return;
}
// Entfernen von Anführungszeichen und geschweiften Klammern, dann Aufspaltung der Zeile in Teile
let parts = line.replace('{', '').replace('}', '').replace(/"/g, '').trim().split(',');
parts = parts.map(part => part.trim()).filter(part => part);
// Überprüfen, ob die Zeile drei Teile enthält: Name, Index und Typ
if (parts.length === 3) {
let [name, index, type_] = parts;
try {
// Versuche, den Index von Hex zu Dezimal zu konvertieren
let indexKey = parseInt(index, 16);
// Hinzufügen zum JavaScript-Objekt
elsterTable[indexKey] = { "name": name, "type": type_ };
} catch (error) {
// Wenn der Index nicht in einen Integer umgewandelt werden kann, ignorieren wir diese Zeile
console.error('Konvertierungsfehler:', error);
}
}
});
return elsterTable;
}
// Das ElsterTable-Objekt, das zuvor erstellt wurde
let elsterTable = elsterJsObject
// Funktion zum Konvertieren der Hex-Daten in lesbare Werte
function convertData(hexString) {
let bytes = [];
for (let i = 0; i < hexString.length; i += 2) {
bytes.push(parseInt(hexString.substr(i, 2), 16));
}
let rawValue = (bytes[1] << 8) | bytes[2]; // Korrigierte Byte-Position
return rawValue / 10; // Annahme: Der Wert ist durch 10 zu teilen
}
// Funktion zum Auswerten der Nachrichten
function evaluateMessages(message) {
//messages.forEach((message) => {
let id = message.ID;
let data = message.Data.replace(/\s/g, "");
// Der Index ist das dritte Byte in der Nachricht, hier als Hex-String
let indexHex = data.substr(4, 2);
log(indexHex)
let index = parseInt(indexHex, 16);
let valueHEX = data.substr(6, 4);
let value = parseInt(valueHEX, 16)
log("Index:" + index)
log("Data:" + data)
log("Value Hex:" + valueHEX)
log("Value Dezi:" + value)
// Hole den Eintrag aus dem ElsterTable
let entry = elsterTable[index];
if (entry) {
log(`Gerät: ${id}, Parameter: ${entry.name}, Wert: ${value / 10}`);
} else {
log(`Unbekannter Index: ${indexHex}. Message: ${message}`);
}
}
function parseMessage(str) {
// Zerlege den String in Teile und extrahiere relevante Teile
const parts = str.trim().split(/\s+/);
const idPart = parts[1].substring(2); // Entferne '0x'
const dataPart = parts.slice(4).join('').replace("Data:", ""); // Entferne 'Data:'
// Erstelle das messages-Objekt
return {
id: idPart, // Entfernt '0x'
data: dataPart // Entfernt 'Data:'
};
}
// Beispiel für die Verwendung der Funktion
//const messageStr = {"ID": "0x301", "Len": "7", "Data": "C0 01 11 00 DF 00 00"};
//evaluateMessages(messageStr)
// ****************************************************************
// :
function parseCanTelegram(telegram) {
// Entferne alle Leerzeichen und prüfe das Format
let dataString = telegram.Data.replace(/\s/g, '');
if (dataString.length % 2 !== 0) {
throw new Error('Ungültige Datenlänge');
}
// Zerlege die Daten in ihre Teile
let parts = dataString.match(/.{1,2}/g).map(byte => parseInt(byte, 16));
// Berechne die CAN-ID
let calculatedId = (8 * (parts[0] & 0xF0)) + (parts[1] & 0x0F);
let calculatedIdHex = calculatedId.toString(16).toUpperCase();
calculatedIdHex = "0x" + ("000" + calculatedIdHex).slice(-4);
// Überprüfe den Typ des Telegramms
let typID = (parts[0] & 0x0F)
let type
switch (typID) {
case 0:
type = "write"
break;
case 1:
type = "read"
break;
case 2:
type = "response"
break;
case 3:
type = "ack"
break;
case 4:
type = "write ack"
break;
case 5:
type = "write respond"
break;
case 6:
type = "system"
break;
case 7:
type = "system respond"
break;
default:
type = "Unbekannt"
}
// Bestimme den Elster-Index und erhöhe ihn um 1
let elsterIndex = parts[2] === 0xFA ? (((parts[3] << 8) + parts[4])) : (parts[2]);
// Konvertiere den Elster-Index in vierstellige Hexadezimalzahl und entferne das zuvor hinzugefügte +1
let elsterIndexHex = elsterIndex.toString(16).toUpperCase();
let elsterFunktion = elsterTable[elsterIndex];
elsterIndexHex = "0x" + ("000" + elsterIndexHex).slice(-4);
let data = parts.slice(3).map(byte => byte.toString(16).padStart(2, '0')).join(' ').toUpperCase();
// Nutzdaten, beginnend nach dem Elster-Index
let dataIndexStart = parts[2] === 0xFA ? 5 : 3;
let dataBytes = parts.slice(dataIndexStart, dataIndexStart + 2);
let dataHex = dataBytes.map(byte => byte.toString(16).padStart(2, '0')).join('').toUpperCase();
let dataDecimal = (dataBytes[0] << 8) + dataBytes[1];
let dataDecimalCon = AllValuesConverter(dataDecimal, elsterFunktion.type, true)
let StateToSet = "0_userdata.0.WP13." + telegram.ID.replace("0x", "") + "." + elsterFunktion.name
log(StateToSet)
log("state:" + StateToSet + " dataDecimalCon: " + dataDecimalCon)
if (dataDecimal != 32768) {
createState(StateToSet, dataDecimalCon, false)
setState(StateToSet, dataDecimalCon, true)
}
return {
raw: dataString,
calculatedId: calculatedIdHex,
type,
elsterIndex: elsterIndexHex,
elsterFunktion,
dataHex,
dataDecimal,
FromID: telegram.ID,
Len: telegram.Len
};
}
function AllValuesConverter(value, TypeString, read = true) {
//log("value to String: " + value.toString().toLowerCase() )
switch (TypeString) {
case "et_datum":
if (read) {
return convertBytesToDatum(Number(value))
} else {
return convertDatumToBytes(value)
}
break;
case "et_dec_val":
if (read) {
return Number((interpretAsSigned16(value) / 10).toFixed(1))
} else {
return Number((interpretAsSigned16(value) * 10).toFixed(0))
}
break;
case "et_time_domain":
if (read) {
if (value == 0xFFFF) return "NA"
if (value == 0x8080) return "-----"
return convertBytesToTime(Number(value))
} else {
if (value.toString() == "NA") return 0xFFFF
if (value.toString() == "-----") return 0x8080
log("Zeitrückgabe: " + value + " zu: " + Number(convertTimeRangeToBytes(value.toString())))
return Number(convertTimeRangeToBytes(value.toString()))
}
break;
case "et_betriebsart":
if (read) {
if (value == 0x0000) return "Notbetrieb"
if (value == 0x0100) return "Bereitschaft"
if (value == 0x0200) return "Automatik"
if (value == 0x0300) return "Tagbetrieb"
if (value == 0x0400) return "Absenkbetrieb"
if (value == 0x0500) return "Warmwasser"
if (value == 0x0B00) return "FEKAuto"
return value
} else {
//log("value to String: " + value.toString().toLowerCase() )
if (value.toString().toLowerCase() == "notbetrieb") return 0x0000
if (value.toString().toLowerCase() == "bereitschaft") return 0x0100
if (value.toString().toLowerCase() == "automatik") return 0x0200
if (value.toString().toLowerCase() == "tagbetrieb") return 0x0300
if (value.toString().toLowerCase() == "absenkbetrieb") return 0x0400
if (value.toString().toLowerCase() == "warmwasser") return 0x0500
if (value.toString().toLowerCase() == "fekauto") return 0x0B00
return Number(value)
}
break;
case "et_little_endian":
case "et_little_bool":
return littleEndianHexConvert(value)
break;
default:
return value
}
}
function interpretAsSigned16(uint16) {
// Prüfen, ob die Zahl größer als der maximal positive Wert für einen vorzeichenbehafteten 16-Bit-Integer ist
if (uint16 > 32767) {
return uint16 - 65536;
} else {
return uint16;
}
}
// Von Little Endian Hex zu Dezimal
function littleEndianHexConvert(value) {
// Verschieben Sie die Bytes und kombinieren Sie sie
let lowByte = value & 0xFF; // Extrahieren Sie das niedrigwertige Byte
let highByte = (value & 0xFF00) >> 8; // Extrahieren und verschieben Sie das hochwertige Byte
return (highByte | (lowByte << 8));
}
function convertBytesToTime(value) {
let startByte = value >> 8;
let endByte = value & 0xFF;
return convertToTime(startByte) + ' - ' + convertToTime(endByte);
}
function convertBytesToDatum(value) {
let startByte = value >> 8;
let endByte = value & 0xFF;
return (startByte) + '.' + (endByte);
}
function convertToTime(value) {
let hours = Math.floor(value / 4);
let minutes = (value % 4) * 15;
return hours.toString().padStart(2, '0') + ':' + minutes.toString().padStart(2, '0');
}
function convertTimeToBytes(startTime, endTime) {
let startUnits = convertToUnits(startTime);
let endUnits = convertToUnits(endTime);
return (startUnits << 8) + endUnits;
}
function convertToUnits(time) {
let [hours, minutes] = time.split(':').map(Number);
return hours * 4 + Math.floor(minutes / 15);
}
function convertTimeRangeToBytes(timeRange) {
// Entfernen Sie alle Leerzeichen aus dem String
let cleanedTimeRange = timeRange.replace(/\s/g, '');
// Teilen Sie den bereinigten String in Start- und Endzeit
let [startTime, endTime] = cleanedTimeRange.split('-');
if (!endTime) return timeRange
return convertTimeToBytes(startTime, endTime);
}
function convertDatumToBytes(datum) {
let cleaneddatum = datum.replace(/\s/g, '');
let [tag, monat] = cleaneddatum.split('.').map(Number);
//log(tag + " " + monat + " " + typeof(monat))
if (!monat) return datum
return ((tag << 8) + (monat));
}
//console.log(convertBytesToTime(0x8080)); // Gibt "00:00 - 01:15" aus
//console.log(convertTimeToBytes("15:30", "01:15").toString(16)); // Gibt "105" aus
//console.log(convertTimeRangeToBytes("32:00 - 32:00").toString(16)); // Gibt "105" aus
//log(convertBytesToDatum(3851)) // Gibt "15.11" aus
//log(convertDatumToBytes("15.11"))
// Beispiel-Telegramm
let telegram = {
"ID": "0x680",
"Len": "6",
"Data": "61010112340000"
}
//console.log(parseCanTelegram(telegram));
function buildCanIdWithTypeId(canId, typeId, elsterIndexHex, HexDaten) {
let hexbuffer = Buffer.from("0000000000", "hex")
hexbuffer[0] = Math.floor(parseInt(canId, 16) / 8) + parseInt(typeId, 16)
hexbuffer[1] = (parseInt(canId, 16) % 8)
let elsterBytes = Buffer.from(elsterIndexHex, "hex");
hexbuffer[2] = elsterBytes[1];
//hexbuffer[3] = elsterBytes[1];
let DatenBytes = Buffer.from(HexDaten, "hex");
hexbuffer[3] = DatenBytes[0];
hexbuffer[4] = DatenBytes[1];
return hexbuffer.toString('hex')
}
/***************************************
********** LOG FILE ERSTELLEN ************
****************************************/
const logpath = '/opt/iobroker/log/';
const fs = require('fs').promises;
async function writeLog(logEntry, filename = "ECOTest2.csv") {
// Get the current date and time
const now = new Date();
const dateTime = now.toLocaleString();
{1}
// Format the log entry
const formattedEntry = `${dateTime}\t${logEntry}\n`;
{1}
try {
// Check if the file already exists
await fs.stat(logpath + filename);
} catch (error) {
if (error.code === 'ENOENT') {
// If the file does not exist, we need to add the header
const header = 'DateTime\tLog Entry\n';
await fs.writeFile(logpath + filename, header);
log("neue datei erstellt");
} else {
// Some other error occurred
throw error;
}
}
// Append the log entry to the file
try {
await fs.appendFile(logpath + filename, formattedEntry);
//console.log('Log entry saved!');
} catch (error) {
throw error;
}
}
// Anwendung:
//writeLog('This is a test entry.', 'Filename.csv').catch(error => console.error(error));
//***************************************
//***************************************
function initElsterFrame(senderId, receiverId, elsterIdx, pdaten, typ = 1) {
senderId = Number(senderId)
const address = (Number(receiverId) & 0x780) >> 3;
let data = new Array(7); // Erstellt ein Array mit 5 Elementen für die Datenbytes
// Die Id und die Länge des Frames werden nicht direkt gesetzt, da in JavaScript
// üblicherweise Objekte anstelle von direkt manipulierten Byte-Arrays verwendet werden.
data[0] = (Number(address) & 0xF0) + Number(typ);
data[1] = Number(receiverId) & 0x07;
data[2] = 0xFA;
data[3] = (Number(elsterIdx) >> 8) & 0xFF; // Höherwertiges Byte von ElsterIdx
data[4] = Number(elsterIdx) & 0xFF; // Niederwertiges Byte von ElsterIdx
data[5] = (Number(pdaten) >> 8) & 0xFF; // Niederwertiges Byte von ElsterIdx
data[6] = Number(pdaten) & 0xFF; // Niederwertiges Byte von ElsterIdx
return {
ID: "0x" + senderId.toString(16),
Len: "7",
Data: data.map(byte => byte.toString(16).padStart(2, '0')).join('').toUpperCase()
};
}
// Änderungen senden:
on({ id: new RegExp("0_userdata.0.WP13\..*"), change: "any", ack: false }, function (obj) {
log("was geändert:" + obj.id + " ack:" + obj.state.ack)
readDataFromObject(obj, 'write')
});
function readDataFromObject(obj, art = 'readonly') {
let ID, elstername, mtelegram
if (obj.id) {
ID = "0x" + ((obj.id).split('.').slice(-2, -1)[0])
elstername = ((obj.id).split('.').slice(-1)[0])
} else {
ID = "0x" + ((obj._id).split('.').slice(-2, -1)[0])
elstername = ((obj._id).split('.').slice(-1)[0])
}
let elsterObjekt = Object.entries(elsterJsObject).find(([key, value]) => value.name === elstername);
{1}
let elsterID = elsterObjekt[0]; // Der Schlüssel
let gesuchtesObjekt = elsterObjekt[1]; // Das gesuchte Objekt
// Verwenden Sie hier elsterID und gesuchtesObjekt wie benötigt
// log (JSON.stringify(gesuchtesObjekt) + " : " + obj.state.val + " Konvertiert: " + AllValuesConverter(obj.state.val, gesuchtesObjekt.type ,true))
{1}
//let elsterID = Object.keys(elsterJsObject).find(key => elsterJsObject[key].name === elstername);
/*
log("Gerät: " + ID)
log("elstername: " + elstername)
log("elsterIDHex: " + Number(elsterID).toString(16))
log("value: " + obj.state.val)
log("art: " + art)
*/
if (art == 'write') {
let dataDecimalCon = AllValuesConverter((obj.state.val), gesuchtesObjekt.type, false)
mtelegram = JSON.stringify(initElsterFrame("0x680", ID, elsterID, dataDecimalCon, 2))
log(" Wert Setzten: " + mtelegram)
setState(CanScnifferId + '.canSend', mtelegram)
setTimeout(function () {
mtelegram = JSON.stringify(initElsterFrame("0x680", ID, elsterID, 0, 1))
//log(" Wert abrufen: " + mtelegram)
setState(CanScnifferId + '.canSend', mtelegram)
}, 2 * 1000);
} else if (art == 'readonly') {
mtelegram = JSON.stringify(initElsterFrame("0x680", ID, elsterID, 0, 1))
//log(" Wert abrufen: " + mtelegram)
setState(CanScnifferId + '.canSend', mtelegram)
}
}
//Zum Scannen aller verfügbaren Parameter eines gerätes
function scanAllElsterIds(geraet = 0x180, lastPosition = 0) {
GlobscanAllElsterIds.geraet = geraet
let canId, mtelegram
let maxScan
let keys = Object.keys(elsterJsObject);
maxScan = keys.length
if (lastPosition >= maxScan) {
let Zeitkompl = Math.floor((Date.now() - GlobscanAllStates.Starttime) / 1000)
if (DEBUG) log("Scan all ElsterIds beendet. Scandauer: " + Zeitkompl + " Sekunden für " + lastPosition + " Scans")
GlobscanAllElsterIds.lastPosition = 0
GlobscanAllElsterIds.lastkey = "0"
GlobscanAllElsterIds.Starttime = 0
return
} else if (lastPosition == 0) {
if (DEBUG) log("Scan all States gestartet")
GlobscanAllElsterIds.Starttime = Date.now()
}
let key = keys[lastPosition]
if (DEBUG) if (lastPosition % 20 == 0) log("(" + ((100 / maxScan) * lastPosition).toFixed(0) + "%) Scanposition: " + lastPosition + " von " + maxScan + " elsterID:" + key + " elstername:" + elsterJsObject[key].name)
//log(key)
mtelegram = JSON.stringify(initElsterFrame("0x680", geraet, key, 0, 1))
//log(lastPosition + " von " + maxScan + " Wert abrufen: " + mtelegram)
setState(CanScnifferId + '.canSend', mtelegram)
GlobscanAllElsterIds.lastPosition = lastPosition
GlobscanAllElsterIds.lastkey = key
scanAllElsterIdsTimerVar = setTimeout(function () {
scanAllElsterIds(geraet, lastPosition + 1); // Setzt den nächsten Aufruf auf 1000 ms später
}, 5000)
}
//Zum Scannen aller Angelegten States
function scanAllStates(lastPosition = 0) {
let mSelector = $("0_userdata.0.WP13.*");
let maxScan = mSelector.length
if (lastPosition >= maxScan - 1) {
let Zeitkompl = Math.floor((Date.now() - GlobscanAllStates.Starttime) / 1000)
if (DEBUG) log("Scan all States beendet. Scandauer: " + Zeitkompl + " Sekunden für " + lastPosition + " Scans")
GlobscanAllStates.lastPosition = 0
GlobscanAllStates.lastkey = "0"
GlobscanAllStates.Starttime = 0
setTimeout(function () {
scanAllStates(0); // Setzt den nächsten Aufruf auf 1000 ms später
}, GlobscanAllStates.intervall)
return
} else if (lastPosition == 0) {
if (DEBUG) log("Scan all States gestartet")
GlobscanAllStates.Starttime = Date.now()
}
//log("mSelector " + mSelector[lastPosition])
let elstername = ((mSelector[lastPosition]).split('.').slice(-1)[0])
let geraeteId = (mSelector[lastPosition]).split('.').slice(-2)[0]
//log("GeraeteID:" + geraeteId )
let elsterID = Object.keys(elsterJsObject).find(key => elsterJsObject[key].name === elstername);
GlobscanAllStates.lastPosition = lastPosition
GlobscanAllStates.lastkey = elsterID
if (DEBUG) if (lastPosition % 20 == 0) log("ScanAllStates (" + ((100 / maxScan) * lastPosition).toFixed(0) + "%) Positiom:" + lastPosition + " von " + maxScan + " geraeteId:" + geraeteId + " elsterID:" + elsterID + " elstername:" + elstername)
let myObj = getObject(mSelector[lastPosition])
myObj.state = getState(mSelector[lastPosition])
myObj.id = mSelector[lastPosition]
//log(JSON.stringify(myObj))
readDataFromObject(myObj)
scanAllStatestimerVar = setTimeout(function () {
if (elstername != "DATUM") log("Timeout position:" + lastPosition + " geraeteId:" + geraeteId + " elsterID:" + elsterID + " elstername:" + elstername)
scanAllStates(lastPosition + 1); // Setzt den nächsten Aufruf auf 1000 ms später
}, 5000)
}
function scanShortStates(lastPosition = 0) {
let maxScan = GlobshortScan.iDs.length
//log("Anzahl IDs Short:" + maxScan)
if (lastPosition >= maxScan) {
let Zeitkompl = Math.floor((Date.now() - GlobshortScan.Starttime) / 1000)
if (DEBUG) log("Scan Short States beendet. Scandauer: " + Zeitkompl + " Sekunden für " + lastPosition + " Scans")
GlobshortScan.lastPosition = 0
GlobshortScan.lastkey = "0"
GlobshortScan.Starttime = 0
setTimeout(function () {
scanShortStates(0); // Setzt den nächsten Aufruf auf 1000 ms später
}, GlobshortScan.intervall)
return
} else if (lastPosition == 0) {
if (DEBUG) log("Scan Short States gestartet")
GlobshortScan.Starttime = Date.now()
}
//log("mSelector " + mSelector[lastPosition])
let elstername = GlobshortScan.iDs[lastPosition].elsterName
let elsterID = Object.keys(elsterJsObject).find(key => elsterJsObject[key].name === elstername);
GlobshortScan.lastPosition = lastPosition
GlobshortScan.lastkey = elsterID
let esterState = "0_userdata.0.WP13." + GlobshortScan.iDs[lastPosition].geraet.toString(16).replace("tzt0x", "") + "." + elstername
//log(esterState)
if (DEBUG) if (lastPosition % 20 == 0) log("ScanShortStates (" + ((100 / maxScan) * lastPosition).toFixed(0) + "%) Positiom:" + lastPosition + " von " + maxScan + " elsterID:" + elsterID + " elstername:" + elstername)
let myObj = getObject(esterState)
myObj.state = getState(esterState)
myObj.id = esterState
//log(JSON.stringify(myObj))
readDataFromObject(myObj)
scanShortStatestimerVar = setTimeout(function () {
log("Timeout Shortscan position:" + lastPosition + " elsterID:" + elsterID + " elstername:" + elstername)
scanShortStates(lastPosition + 1); // Setzt den nächsten Aufruf auf 1000 ms später
}, 5000)
}
/*
//Objekte löschen:
$('0_userdata.0.WP13.180.EL_*').each(function (id, i) {
log(i + " - " + id + " existsObject: " + existsObject(id))
//deleteObject(id,true);
});
*/
// Beispielaufruf
let canId, mtelegram
let Senderid = "0x680"
canId = "0x180"; // 384 in dezimal
//canId = "0x601"; // 384 in dezimal
let typeId = 1; // 1 in dezimal
let elsterIndexHex = "0x17a0"
let pHexDaten = 0x0000
mtelegram = JSON.stringify(initElsterFrame(Senderid, canId, elsterIndexHex, pHexDaten, typeId))
//console.log("Generiertes Telegramm:" + mtelegram);
//setState( CanScnifferId +'.canSend', mtelegram)
/*
setState( CanScnifferId +'.canSend', mtelegram, function () {
setTimeout(function () {
elsterIndexHex = "0x1700"
pHexDaten = 0x8080
mtelegram = JSON.stringify(initElsterFrame(Senderid, canId, elsterIndexHex, pHexDaten, typeId))
console.log("Generiertes Telegramm:" + mtelegram);
setState( CanScnifferId +'.canSend', mtelegram, function () {
setTimeout(function () {
elsterIndexHex = "0x1722"
pHexDaten = 0x8080
mtelegram = JSON.stringify(initElsterFrame(Senderid, canId, elsterIndexHex, pHexDaten, typeId))
console.log("Generiertes Telegramm:" + mtelegram);
setState( CanScnifferId +'.canSend', mtelegram)
}, 200);
})
}, 200);
})
*/
//console.log(mtelegram);
setTimeout(function () {
typeId = 1;
//elsterIndexHex = "0x1720"
//HexDaten = "0000"
mtelegram = JSON.stringify(initElsterFrame(Senderid, canId, elsterIndexHex, pHexDaten, typeId))
//console.log("Generiertes Telegramm:" + mtelegram);
//setState( CanScnifferId +'.canSend', mtelegram)
}, 2 * 1000);
//
EIgentlich habe ich hier kaum etwas geändert mit Ausnahme des MQTT Teils und dem Auskommentieren in der Zeile 27-31
Und das Arduino Skript:
#include <Arduino.h>
#include <mcp2515.h>
#include <WiFi.h>
#include <ArduinoOTA.h>
#include <PubSubClient.h>
#include <ArduinoJson.h>
const char* ssid = "DeineSSID"; // Ersetze mit deinem WiFi-Namen
const char* password = "Not for you"; // Ersetze mit deinem WiFi-Passwort
const char* mqtt_server = "192.168.10.99"; // Dein MQTT Broker Server
const int mqtt_port = 1887;
const char* mqtt_user = "Musst Du selbst wissen";
const char* mqtt_password = "Not for you";
const char* mqtt_topic = "cansniffer";
WiFiClient espClient;
PubSubClient client(espClient);
MCP2515 mcp2515(5); // Der CS-Pin muss entsprechend deiner Schaltung angepasst werden.
// Diese Funktion wird aufgerufen, wenn eine Nachricht auf einem abonnierten Thema ankommt
void callback(char* topic, byte* payload, unsigned int length) {
Serial.println("Callback aufgerufen: " + String(topic));
char canSendTopic[64];
snprintf(canSendTopic, sizeof(canSendTopic), "%s/canRawGesendet", mqtt_topic);
String topicString = String(topic);
// Überprüfe, ob das Topic stimmt
if (topicString.equals(String(mqtt_topic) + "/canSend")) {
// Konvertiere payload in einen String zur Verarbeitung
String payloadString;
for (int i = 0; i < length; i++) {
payloadString += (char)payload[i];
}
if (length == 0 || payloadString == "0" || payloadString == "OK" || payloadString == "INIT" ) {
Serial.println("Leere Nachricht ");
return;
}
// Parse die JSON-Nachricht
StaticJsonDocument<200> doc;
DeserializationError error = deserializeJson(doc, payloadString);
if (error || !doc.is<JsonObject>()) {
Serial.println("Fehler beim Parsen von JSON");
client.publish(canSendTopic, ("JSON PARS ERROR: " + payloadString).c_str());
return;
}
String tempDataString = doc["Data"].as<String>(); // Konvertiert den Wert zu einem String
tempDataString.replace(" ", ""); // Entfernt alle Leerzeichen
doc["Data"] = tempDataString.c_str(); // Speichert den geänderten Wert zurück im JSON-Objekt
// Extrahiere ID und Daten aus dem JSON
const char* idString = doc["ID"];
const char* dataString = doc["Data"];
// Umwandeln der ID in einen Integer
uint16_t id = strtol(idString, NULL, 16);
// Umwandeln des Datenstrings in ein Byte-Array
uint8_t data[8];
size_t dataLength = strlen(dataString) / 2;
for (size_t i = 0; i < dataLength; i++) {
char byteString[] = {dataString[i*2], dataString[i*2 + 1], 0};
data[i] = strtol(byteString, NULL, 16);
}
// Erstelle CAN-Nachricht und sende sie
can_frame canMsg;
canMsg.can_id = id;
canMsg.can_dlc = dataLength;
memcpy(canMsg.data, data, dataLength);
// Erstelle einen String, um die Hex-Darstellung zu speichern
char msgString[35]; // Länge = Anzahl der Bytes * 2 (für Hex) + 1 (für '\0')
// Konvertiere jeden Byte der Daten in einen Hex-String
for (int i = 0; i < canMsg.can_dlc; i++) {
sprintf(&msgString[i * 2], "%02X", canMsg.data[i]);
}
// Sende CAN-Nachricht
if (mcp2515.sendMessage(&canMsg) != MCP2515::ERROR_OK) {
Serial.println("Fehler beim Senden der CAN-Nachricht");
client.loop();
delay(100);
client.publish(canSendTopic, "SEND ERROR");
}else{
// Gib den Hex-String aus
Serial.print("Raw CAN Data: ");
Serial.println(msgString);
client.publish(canSendTopic, msgString); // Sende die Daten zum MQTT-Broker
client.loop();
Serial.println("Erfolgreich gesendet");
client.publish(topicString.c_str(), "OK",false);
}
}else{
Serial.println("Falsches Topic");
return;
}
}
void setup() {
// Initialisiere die serielle Kommunikation
Serial.begin(115200);
pinMode(2, OUTPUT); // Setze GPIO 26 als Ausgang für die LED
// Warte auf die serielle Verbindung, falls nötig
while (!Serial) {
; // warte auf die Verbindung der seriellen Schnittstelle
}
// Verbinde mit WiFi-Netzwerk
WiFi.begin(ssid, password);
Serial.print("Verbinde mit WiFi");
while (WiFi.status() != WL_CONNECTED) {
delay(500);
Serial.print(".");
}
Serial.println("\nWiFi verbunden!");
Serial.print("IP-Adresse: ");
Serial.println(WiFi.localIP());
connectMQTT();
// Initialisiere den MCP2515 CAN Controller
mcp2515.reset();
mcp2515.setBitrate(CAN_20KBPS, MCP_8MHZ);
// Setze die Masken und Filter
mcp2515.setFilterMask(MCP2515::MASK0, true, 0x000); // Maske 0 auf 0 setzen (alle Nachrichten akzeptieren)
mcp2515.setFilterMask(MCP2515::MASK1, true, 0x000); // Maske 1 auf 0 setzen (alle Nachrichten akzeptieren)
// Setze alle Filter auf 0, um alle Nachrichten zu akzeptieren
//for (int filter = 0; filter < 6; filter++) {
//mcp2515.setFilter((MCP2515::RXF)filter, true, 0x000);
//}
mcp2515.setNormalMode();
Serial.println("CAN-Bus Monitor gestartet!");
// Starte OTA
ArduinoOTA.setHostname("esp32-ota");
ArduinoOTA.setPassword("XXXXXXXX");
ArduinoOTA.onStart([]() {
String type;
if (ArduinoOTA.getCommand() == U_FLASH) {
type = "sketch";
} else { // U_SPIFFS
type = "filesystem";
}
// Hinweis: if updating SPIFFS this would be the place to unmount SPIFFS using SPIFFS.end()
Serial.println("Start updating " + type);
});
ArduinoOTA.onEnd([]() {
Serial.println("\nEnd");
});
ArduinoOTA.onProgress([](unsigned int progress, unsigned int total) {
Serial.printf("Progress: %u%%\r", (progress / (total / 100)));
});
ArduinoOTA.onError([](ota_error_t error) {
Serial.printf("Error[%u]: ", error);
if (error == OTA_AUTH_ERROR) {
Serial.println("Auth Failed");
} else if (error == OTA_BEGIN_ERROR) {
Serial.println("Begin Failed");
} else if (error == OTA_CONNECT_ERROR) {
Serial.println("Connect Failed");
} else if (error == OTA_RECEIVE_ERROR) {
Serial.println("Receive Failed");
} else if (error == OTA_END_ERROR) {
Serial.println("End Failed");
}
});
ArduinoOTA.begin();
}
void loop() {
static unsigned long lastMillis = 0;
char aliveTopic[64];
char timestampString[32]; // Genug Platz für die Darstellung des Zeitstempels
connectMQTT();
// Sende alle 5 Sekunden eine Nachricht an das Thema "alive"
if (millis() - lastMillis > 5000) {
lastMillis = millis();
snprintf(aliveTopic, sizeof(aliveTopic), "%s/alive", mqtt_topic); // Kombiniere das Hauptthema mit "/alive"
snprintf(timestampString, sizeof(timestampString), "%lu", millis()); // Konvertiere den Zeitstempel in einen String
client.publish(aliveTopic, timestampString); // Sende das Zeichen "!" an das Unterthema "alive"
Serial.println("Alive-Nachricht gesendet!");
digitalWrite(2, HIGH); // LED einschalten
//delay(10); // Kurz warten
digitalWrite(2, LOW); // LED ausschalten
Serial.println(timestampString);
}
can_frame canMsg;
if (mcp2515.readMessage(&canMsg) == MCP2515::ERROR_OK) {
digitalWrite(2, HIGH); // LED einschalten
delay(100); // Kurz warten
digitalWrite(2, LOW); // LED ausschalten
char msgString[128]; // Ein Array, um die Daten zu speichern
sprintf(msgString, "{\"ID\": \"0x%03X\", \"Len\": \"%1d\", \"Data\": \"", canMsg.can_id, canMsg.can_dlc);
for (int i = 0; i < canMsg.can_dlc; i++) {
sprintf(msgString + strlen(msgString), "%02X ", canMsg.data[i]);
}
sprintf(msgString + strlen(msgString)-1, "\"}");
snprintf(aliveTopic, sizeof(aliveTopic), "%s/pload", mqtt_topic); // Kombiniere das Hauptthema mit "/alive"
client.publish(aliveTopic, msgString); // Sende die Daten zum MQTT-Broker
Serial.println(msgString);
}
// Kurze Verzögerung, um den CAN-Bus nicht zu überlasten
//Serial.print(".");
//delay(10);
ArduinoOTA.handle();
client.loop();
}
void connectMQTT(){
// Verbinde mit MQTT Broker
while (!client.connected()) {
client.setServer(mqtt_server, mqtt_port);
Serial.print("Verbinde mit MQTT...");
if (client.connect("CAN_MQTT_Client", mqtt_user, mqtt_password)) {
Serial.println("verbunden");
// Setze MQTT-Callback-Funktion
client.setCallback(callback);
// Erstelle das Topic für das Senden von CAN-Nachrichten
char canSendTopic[64];
snprintf(canSendTopic, sizeof(canSendTopic), "%s/canSend", mqtt_topic);
client.publish(canSendTopic, "INIT"); // Sende die Daten zum MQTT-Broker
delay(200);
Serial.print("Abonniere Topic: ");
Serial.println(canSendTopic);
if (client.subscribe(canSendTopic)) {
Serial.println("Abonnement erfolgreich!");
} else {
Serial.println("Abonnement fehlgeschlagen!");
}
delay(200);
} else {
Serial.print("Fehler, rc=");
Serial.print(client.state());
Serial.println(" Versuche es erneut in 5 Sekunden");
delay(5000);
}
}
};
Mein MQTT Port ist nicht Standard, da ich den Standard für den Sonoff Adpater verwende.
Einfach nach delay(10) suchen nur die befinden sich in der loop.
Ich überlege mir auch noch das Arduino OTA herauszunehmen. Sehe hier nicht wirklich einen Vorteil.