Skip to content
  • Home
  • Recent
  • Tags
  • 0 Unread 0
  • Categories
  • Unreplied
  • Popular
  • GitHub
  • Docu
  • Hilfe
Skins
  • Light
  • Brite
  • Cerulean
  • Cosmo
  • Flatly
  • Journal
  • Litera
  • Lumen
  • Lux
  • Materia
  • Minty
  • Morph
  • Pulse
  • Sandstone
  • Simplex
  • Sketchy
  • Spacelab
  • United
  • Yeti
  • Zephyr
  • Dark
  • Cyborg
  • Darkly
  • Quartz
  • Slate
  • Solar
  • Superhero
  • Vapor

  • Default (No Skin)
  • No Skin
Collapse
ioBroker Logo

Community Forum

donate donate
  1. ioBroker Community Home
  2. Deutsch
  3. Hardware
  4. Buderus 4121 - Einbindung über EcoCan [läuft]

NEWS

  • Monatsrückblick Januar/Februar 2026 ist online!
    BluefoxB
    Bluefox
    18
    1
    756

  • Jahresrückblick 2025 – unser neuer Blogbeitrag ist online! ✨
    BluefoxB
    Bluefox
    18
    1
    6.1k

  • Neuer Blogbeitrag: Monatsrückblick - Dezember 2025 🎄
    BluefoxB
    Bluefox
    13
    1
    1.5k

Buderus 4121 - Einbindung über EcoCan [läuft]

Scheduled Pinned Locked Moved Hardware
2 Posts 2 Posters 198 Views 2 Watching
  • Oldest to Newest
  • Newest to Oldest
  • Most Votes
Reply
  • Reply as topic
Log in to reply
This topic has been deleted. Only users with topic management privileges can see it.
  • D Offline
    D Offline
    DaBibo
    wrote on last edited by
    #1

    Hallo zusammen,

    ich habe meine Buderus 4121 selbst über einen Raspberry zero 2 W und einem CAN-Hat (Habe den von waveshare genutzt, würde aber im erneuten Fall ein anderes nehmen, mit 8/16 Mhz - das von waveshare hat 12MHz und die Abfragen auf auf dem PI zeigen was anderes an.. war ein wenig Fehlersuche..) angebunden über zusammen mit chatgpt ein JavaScript geschrieben um die Daten per MQTT zu übertragen.

    #!/usr/bin/env node
    /**
     * Buderus System 4000 CAN -> MQTT Decoder (ALL MODULES, FULL BITS, RAW)
     * ---------------------------------------------------------------------
     * - SocketCAN (ID 0x400 Monitor), 60-Byte-Reassembly je Typ 0x80..0x9E
     * - Sprechende Felder + vollständige Bit-Dekodierung gemäß Doku
     * - Zusätzlich immer: Block-JSON, RAW-Bytes b00..b59, Wörter w00..w58 (u16be),
     *   Triplets t00..t57 (u24). Keine Wertebegrenzung/Clamps.
     *
     * ENV:
     *   CAN_IF=can0
     *   MQTT_URL=mqtt://USER:PASS@<BROKER_IP>:1883
     *   MQTT_BASE=buderus/eco
     *   PUBLISH_ONLY_SEEN=1   # nur publizieren, wenn Block wirklich empfangen (Default 1)
     *   STALE_MS=0            # >0: unterdrückt Veröffentlichung je Typ nach Inaktivität
     *   PUBLISH_BLOCK_JSON=1  # Block-JSON an/aus
     *   PUBLISH_RAW_BYTES=1   # b00..b59
     *   PUBLISH_WORDS=1       # w00,w02,...,w58
     *   PUBLISH_TRIPLETS=1    # t00..t57
     */
    
    var mqtt = require('mqtt');
    var can  = require('socketcan');
    
    // ---------- Konfig ----------
    var CAN_IF   = process.env.CAN_IF   || 'can0';
    var MQTT_URL = process.env.MQTT_URL || 'mqtt://127.0.0.1:1883';
    var MQTT_BASE = (process.env.MQTT_BASE || 'buderus/eco').replace(/\/+$/,'');
    var RETAIN = true, QOS = 0;
    
    var PUBLISH_ONLY_SEEN   = (process.env.PUBLISH_ONLY_SEEN   || '1') === '1';
    var STALE_MS            = parseInt(process.env.STALE_MS || '0', 10);
    var PUBLISH_BLOCK_JSON  = (process.env.PUBLISH_BLOCK_JSON  || '1') === '1';
    var PUBLISH_RAW_BYTES   = (process.env.PUBLISH_RAW_BYTES   || '1') === '1';
    var PUBLISH_WORDS       = (process.env.PUBLISH_WORDS       || '1') === '1';
    var PUBLISH_TRIPLETS    = (process.env.PUBLISH_TRIPLETS    || '1') === '1';
    
    // ---------- Helper ----------
    function u8(buf,i){ return (buf[i]!==undefined)?buf[i]:0; }
    function i8(buf,i){ var v=u8(buf,i); return v>=128?(v-256):v; }
    function u16be(buf,i){ return ((u8(buf,i)<<8)|u8(buf,i+1))>>>0; }
    function i16be(buf,i){ var v=u16be(buf,i); return (v&0x8000)?(v-0x10000):v; }
    function u24(b3,b2,b1){ return (((b3&0xFF)<<16)|((b2&0xFF)<<8)|(b1&0xFF))>>>0; }
    function bits(byte, names){
      var o={}, b;
      for(b=0;b<8;b++){
        var key = names && names[b] ? names[b] : ('b'+(b+1));
        o[key] = !!(byte & (1<<b));
      }
      return o;
    }
    function put(obj, path, val){
      var p=path.split('/'),cur=obj;
      for (var i=0;i<p.length-1;i++){ cur[p[i]]=cur[p[i]]||{}; cur=cur[p[i]]; }
      cur[p[p.length-1]] = val;
    }
    function tname(t){
      var map = {
        0x80:'hk1',0x81:'hk2',0x82:'hk3',0x83:'hk4',0x8A:'hk5',0x8B:'hk6',0x8C:'hk7',0x8D:'hk8',0x8E:'hk9',
        0x84:'warmwasser',0x88:'kessel',0x89:'konfig',0x8F:'strategie_boden',0x90:'lap',
        0x92:'uba1',0x93:'uba2',0x94:'uba3',0x95:'uba4',0x96:'uba5',0x97:'uba6',0x98:'uba7',0x99:'uba8',
        0x9B:'waermemenge',0x9C:'stoermodul',0x9D:'unterstation',0x9E:'solar'
      };
      return map[t] || ('type_'+t.toString(16));
    }
    function ts(){ return new Date().toISOString(); }
    
    // ---------- Bit-Labels aus Doku ----------
    var HK_BW1 = ['ausschaltopt','einschaltopt','automatik','ww_vorrang','estrichtrocknung','ferien','frostschutz','manuell'];
    var HK_BW2 = ['sommer','tag','fb_keine_kommunikation','fb_fehlerhaft','fehler_vorlauf','max_vorlauf','ext_stoereingang','party_pause'];
    var HK_EIN = ['eingang_wf2','eingang_wf3',null,null,null,'schalter_0','schalter_hand','schalter_aut'];
    
    var WW_BW1 = ['automatik','desinfektion','nachladung','ferien','fehler_desinfektion','fehler_fuehler','ww_bleibt_kalt','fehler_anode'];
    var WW_BW2 = ['laden','manuell','nachladen','ausschaltopt','einschaltopt','tag','warm','vorrang'];
    var WW_PUMP = ['ladepumpe','zirkulationspumpe','absenkung_solar',null,null,null,null,null];
    var WW_ZIRK = ['tag','automatik','ferien','einmallauf_3min',null,null,null,null];
    
    var SOL_BW1 = ['fehler_einst_hysterese','sp2_auf_max_temp','sp1_auf_max_temp','kollektor_auf_max_temp',null,null,null,null];
    var SOL_BW2 = ['fehler_fuehler_arl_bypass','fehler_fuehler_sp_mitte_bypass','fehler_volumenstrom_wz','fehler_fuehler_rl_wz','fehler_fuehler_vl_wz','fehler_fuehler_sp2_unten','fehler_fuehler_sp1_unten','fehler_fuehler_kollektor'];
    var SOL_BW3 = ['usv_sp2_zu','usv_sp2_auf_oder_ladepumpe2','usv_bypass_zu','usv_bypass_auf','sekundaerpumpe_sp2_betrieb',null,null,null];
    
    // UBA/EMS Bits (wandhängend)
    var UBA_STATUS = ['untergruppe_b0','untergruppe_b1','untergruppe_b2','hauptgruppe_b0','hauptgruppe_b1','hauptgruppe_b2','hauptgruppe_b3','blockierender_fehler_uba'];
    var UBA_HD     = ['ww_anforderung','ein_aus_raumthermostat','anforderung_schnittstelle','frostschutz','pumpennachlauf_wegen_ww','ww_anforderung_fuehler','ww_anforderung_durchfluss','brenner_an'];
    var UBA_BETRZ  = ['heizanforderung','ww_anforderung','jumper_11kw_entfernt','betriebstemperatur_betrieb','kesselschutz_taupunkt','verriegelt_service','blockiert','servicemeldung'];
    var UBA_REL1   = ['magnetventil_stufe1','magnetventil_stufe2','geblaese','zuendung','oelvorwaermung_abgasklappe','kesselkreispumpe_heizkreispumpe','3_wegeventil','ww_zirkulationspumpe'];
    var UBA_REL2   = ['ww_ladepumpe','fluessiggasventil','gwp_umwaelzpumpe',null,null,null,null,null];
    var UBA_EMS_FEHLER = ['luftfuehler_fa_defekt','betriebstemperatur_nicht_erreicht','oelvorwaermer_dauersignal','oelvorwaermer_ohne_signal',null,null,null,null];
    var UBA_FEHL_EINST = ['jumper_11kw_kaskade','bc10_notbetrieb','ww_poti_nicht_aut','kesselpoti_nicht_aut_90c','anforderung_klemme_wa',null,'kommunikation_vorhanden_fm458','keine_kommunikation_fm458'];
    
    // Kessel bodenstehend (0x88) – Brennerstatusbits (34)
    var KES_BRENNERSTATUS = ['abgastest','brenner_runter_aus','brenner_auto','brenner_1_stufe','unter_betrieb','leistung_frei','leistung_hoch','betriebsstunden_2_stufe'];
    
    // ---------- Schema-Bausteine ----------
    function F(name, byte, type, unit, scale){ return {name:name, byte:byte, type:type, unit:unit, scale:scale}; }
    function Fb(name, byte, names){ return {name:name, byte:byte, type:'u8_bits', bitnames:names}; }
    function Fu24(name, b3,b2,b1, unit){ return {name:name, bytes:[b3,b2,b1], type:'u24', unit:unit}; }
    function Fu16be(name, b, unit, scale){ return {name:name, bytes:[b,b+1], type:'u16be', unit:unit, scale:scale}; }
    
    // ---------- Schemata ----------
    // Heizkreise 0x80..0x83,0x8A..0x8E
    function HK_SCHEMA(){
      return [
        Fb('betriebswerte1',0,HK_BW1),
        Fb('betriebswerte2',1,HK_BW2),
        F('vorlauf_soll',2,'u8','°C'),
        F('vorlauf_ist',3,'u8','°C'),
        F('raum_soll',4,'u8','°C',0.5),
        F('raum_ist',5,'u8','°C',0.5),
        F('einschaltopt_min',6,'u8','min'),
        F('ausschaltopt_min',7,'u8','min'),
        F('pumpe',8,'u8','%'),
        F('stellglied',9,'u8','%'),
        Fb('hk_eingang',10,HK_EIN),
        // 11..17 „FREI/Heizkennlinie“ -> RAW deckt ab
      ];
    }
    
    // Warmwasser 0x84
    var SCHEMA_84 = [
      Fb('betriebswerte1',0,WW_BW1),
      Fb('betriebswerte2',1,WW_BW2),
      F('ww_soll',2,'u8','°C'),
      F('ww_ist',3,'u8','°C'),
      F('einschaltopt_min',4,'u8','min'),
      Fb('pumpen_bits',5,WW_PUMP),
      // 6..7 reserviert/RAW
      Fb('zirkulationspumpe',8,WW_ZIRK),
      Fb('wf_eingaenge',9,['eingang2','eingang3',null,null,null,'handschalter_0','handschalter_hand','handschalter_aut']),
      F('start_ladung',10,'u8','°C'),
      F('ende_ladung',11,'u8','°C'),
      F('t_speicher_unten',12,'u8','°C'),
      F('t_waermetauscher',13,'u8','°C'),
      F('mischer_soll',14,'u8','%'),
      F('mischer_ist',15,'u8','%'),
      F('prim_pumpe',16,'u8','%'),
      F('sek_pumpe',17,'u8','%')
    ];
    
    // Kessel bodenstehend 0x88 (repräsentative/benannte Felder + Brenner-Statusbits)
    var SCHEMA_88 = [
      F('kessel_vl_soll',0,'u8','°C'),
      F('kessel_vl_ist',1,'u8','°C'),
      // 2..5 RAW / spezifisch pro ZM
      F('brenner_ansteuerung',8,'u8'),
      F('abgastemperatur',9,'u8','°C'),
      F('mod_brenner_stellglied',10,'u8','%'),
      F('mod_brenner_ist_leistung',11,'u8','%'),
      // 12..33 diverse Laufzeiten/Starts – RAW deckt Zahlen vollständig ab
      Fb('brenner_statusbits',34,KES_BRENNERSTATUS)
    ];
    
    // Strategie Boden 0x8F (Auszug + RAW)
    var SCHEMA_8F = [
      F('anlagen_vl_soll',0,'u8','°C'),
      F('vl_ist',1,'u8','°C'),
      F('rl_soll',2,'u8','°C'),
      F('rl_ist',3,'u8','°C')
    ];
    
    // Konfiguration 0x89 (vollständig laut Tabelle)
    var SCHEMA_89 = [
      F('aussentemperatur',0,'i8','°C'),
      F('aussentemperatur_gedaempft',1,'i8','°C'),
      // 2,3 Versionen (Direktmodus) -> RAW
      F('slot1',6,'u8'),
      F('slot2',7,'u8'),
      F('slot3',8,'u8'),
      F('slot4',9,'u8'),
      F('slotA',10,'u8'),
      // 12..16 Fehlermeldungen je Slot -> RAW zusätzlich vorhanden
      F('anlagen_vl_soll',18,'u8','°C'),
      F('anlagen_vl_ist',19,'u8','°C'),
      Fb('anlagen_flags',20,['puffer_bleibt_kalt','fuehler_ust_fk_defekt','wartezeit_laeuft',null,null,null,null,null]),
      F('max_pumpen_ansteuerung',21,'u8','%'),
      F('max_stellglied',22,'u8','%'),
      F('rg_vorlauf_ist',23,'u8','°C')
    ];
    
    // LAP 0x90
    var SCHEMA_90 = [
      F('ww_soll',4,'u8','°C'),
      F('ww_ist',6,'u8','°C'),
      F('einschaltopt_min',8,'u8','min'),
      F('start_ladung',20,'u8','°C'),
      F('ende_ladung',21,'u8','°C'),
      F('ist_speicher_unten',24,'u8','°C'),
      F('ist_waermetauscher',26,'u8','°C'),
      F('mischer_soll',28,'u8','%'),
      F('mischer_ist',30,'u8','%'),
      F('primaerpumpe',32,'u8','%'),
      F('sekundaerpumpe',34,'u8','%')
    ];
    
    // UBA (wandhängend) 0x92..0x99
    var SCHEMA_UBA = [
      F('soll_modulation',0,'u8','%'),
      F('ist_modulation',1,'u8','%'),
      Fu24('brennerstunden_h',2,3,4,'h'),
      F('brennerminuten',5,'u8','min'),
      F('kessel_vl_soll',6,'u8','°C'),
      F('kessel_vl_ist',7,'u8','°C'),
      F('ww_soll',8,'u8','°C'),
      F('ww_ist',9,'u8','°C'),
      F('antipendel_min',10,'u8','min'),
      Fb('betriebsflag_regelgeraet',11,['antipendel','keine_komm_kse',null,null,null,null,null,null]),
      Fb('betriebsflags_uba_kse',12,['umwaelzpumpe','schornsteinfeger','keine_komm_uba','keine_komm_kse','antipendel','umschaltventil_ww','abgaswaechter','pumpenschalter']),
      Fb('status_uba',13,UBA_STATUS),
      Fb('hd_mode_uba',14,UBA_HD),
      Fu24('brennerstarts',15,16,17,'count'),
      F('version_uba',18,'u8'),
      F('kim_nummer',19,'u8'),
      F('ruecklauf',20,'u8','°C'),
      F('pumpen_mod_uba',21,'u8','%'),
      Fb('anlagenfehler_ems_kessel',22,UBA_EMS_FEHLER),
      // 23: „ems ww“ in älterer Tabelle – als RAW ohnehin da
      {name:'ems_code1', byte:24, type:'ascii'},
      {name:'ems_code2', byte:25, type:'ascii'},
      F('fehlernummer_hi',26,'u8'),
      F('fehlernummer_lo',27,'u8'),
      Fb('brennertyp',28,['bit1','bit2','bit3','bit4','bit5','bit6','bit7','bit8']),
      // 29 zusammen mit 54 als u16be
      {name:'max_leistung_kw', bytes:[54,29], type:'u16be', unit:'kW'},
    
      F('min_leistung_pct',30,'u8','%'),
      F('flammenstrom_uA',31,'u8','µA'),
      F('abgas_temp',32,'u8','°C'),
      F('ansaugluft',33,'i8','°C'),
      F('wasserdruck',34,'u8','bar'),
      Fb('brennerzustand',35,UBA_BETRZ),
      Fb('relais1',36,UBA_REL1),
      Fb('relais2',37,UBA_REL2),
      F('vl_soll_feuerungsautomat',38,'u8','°C'),
      F('ww_ladeart',39,'u8'),
      Fb('fehleinstellungen_ems_kessel',40,UBA_FEHL_EINST),
      Fb('ems_servicemeldungen',41,['keine_meldung',null,null,null,null,null,null,null]),
      // 42..53 Versions-/Kennbytes -> RAW
      F('betriebstemperatur_konst',55,'u8','°C')
    ];
    
    // Wärmemenge 0x9B (Triplets/Zähler kommen zusätzlich als RAW/Triplets)
    var SCHEMA_9B = [
      // einzelne Byte-Zähler exemplarisch – RAW (t..) liefert komplette Zahlen
      F('impulse_heute_hi',5,'u8'),
      F('impulse_heute_lo',6,'u8'),
      F('impulse_woche_hi',11,'u8'),
      F('impulse_woche_lo',12,'u8')
    ];
    
    // Störmodul 0x9C / Unterstation 0x9D – Grundfelder + RAW
    var SCHEMA_9C = [ Fu16be('vl_soll_0_10v',0,'°C') ];
    var SCHEMA_9D = [ Fu16be('anlagen_vorlauf',0,'°C'), Fu16be('vl_soll_0_10v',2,'°C'), F('zubringerpumpe',3,'u8','%') ];
    
    // Solar 0x9E
    var SCHEMA_9E = [
      Fb('betriebswerte1',0,SOL_BW1),
      Fb('betriebswerte2',1,SOL_BW2),
      Fb('betriebswerte3',2,SOL_BW3),
      {name:'kollektor_temp', bytes:[3,4], type:'u16be', unit:'°C', scale:0.1},
      F('pumpe_speicher_mod',5,'u8','%'),
      F('t_sp1_unten',6,'u8','°C'),
      F('status_sp1',7,'u8'),
      F('t_sp2_unten',8,'u8','°C'),
      F('status_sp2',9,'u8'),
      F('t_sp_mitte_bypass',10,'u8','°C'),
      F('t_anl_rl_bypass',11,'u8','°C'),
      F('t_wmz_vorlauf',12,'u8','°C'),
      F('t_wmz_ruecklauf',13,'u8','°C'),
      Fu16be('volumenstrom_lh',14,'l/h'),
      Fu16be('leistung_w',16,'W'),
      Fu24('wm_sp1_100wh',18,19,20,'100Wh'),
      Fu24('wm_sp2_100wh',21,22,23,'100Wh'),
      Fu24('bh_sp1_min',24,25,26,'min'),
      F('ww_sollabsenkung_solar',27,'u8','K'),
      F('ww_sollabsenkung_kapaz',28,'u8','K'),
      F('kollektor_temp_1C',29,'u8','°C'),
      Fu24('bh_sp2_min',30,31,32,'min')
    ];
    
    // Zuordnung Typ -> (Prefix, Schema)
    var TYPE_SCHEMAS = new Map([
      [0x80,{prefix:'hk1', schema:HK_SCHEMA()}],
      [0x81,{prefix:'hk2', schema:HK_SCHEMA()}],
      [0x82,{prefix:'hk3', schema:HK_SCHEMA()}],
      [0x83,{prefix:'hk4', schema:HK_SCHEMA()}],
      [0x8A,{prefix:'hk5', schema:HK_SCHEMA()}],
      [0x8B,{prefix:'hk6', schema:HK_SCHEMA()}],
      [0x8C,{prefix:'hk7', schema:HK_SCHEMA()}],
      [0x8D,{prefix:'hk8', schema:HK_SCHEMA()}],
      [0x8E,{prefix:'hk9', schema:HK_SCHEMA()}],
      [0x84,{prefix:'warmwasser', schema:SCHEMA_84}],
      [0x88,{prefix:'kessel',      schema:SCHEMA_88}],
      [0x8F,{prefix:'strategie_boden', schema:SCHEMA_8F}],
      [0x89,{prefix:'konfig',      schema:SCHEMA_89}],
      [0x90,{prefix:'lap',         schema:SCHEMA_90}],
      [0x92,{prefix:'uba1', schema:SCHEMA_UBA}],
      [0x93,{prefix:'uba2', schema:SCHEMA_UBA}],
      [0x94,{prefix:'uba3', schema:SCHEMA_UBA}],
      [0x95,{prefix:'uba4', schema:SCHEMA_UBA}],
      [0x96,{prefix:'uba5', schema:SCHEMA_UBA}],
      [0x97,{prefix:'uba6', schema:SCHEMA_UBA}],
      [0x98,{prefix:'uba7', schema:SCHEMA_UBA}],
      [0x99,{prefix:'uba8', schema:SCHEMA_UBA}],
      [0x9B,{prefix:'waermemenge', schema:SCHEMA_9B}],
      [0x9C,{prefix:'stoermodul',  schema:SCHEMA_9C}],
      [0x9D,{prefix:'unterstation',schema:SCHEMA_9D}],
      [0x9E,{prefix:'solar',       schema:SCHEMA_9E}]
    ]);
    
    // ---------- Decoder ----------
    function decodeByField(buf, f){
      var val;
      if (f.bytes){
        if (f.type==='u24'){
          val = u24(u8(buf,f.bytes[0]), u8(buf,f.bytes[1]), u8(buf,f.bytes[2]));
        } else if (f.type==='u16be'){
          val = u16be(buf, f.bytes[0]);
        } else { val = 0; }
      } else {
        if (f.type==='u8')      val = u8(buf,f.byte);
        else if (f.type==='i8') val = i8(buf,f.byte);
        else if (f.type==='ascii'){ var b=u8(buf,f.byte); val=(b>=32&&b<=126)?String.fromCharCode(b):''; }
        else if (f.type==='u8_bits'){ val = bits(u8(buf,f.byte), f.bitnames); }
        else if (f.type==='i16be'){ val = i16be(buf,f.byte); }
        else if (f.type==='u16be'){ val = u16be(buf,f.byte); }
        else val = u8(buf,f.byte);
      }
      if (typeof f.scale==='number') val = Number((val * f.scale).toFixed(2));
      if (f.unit) return {v:val, unit:f.unit};
      return val;
    }
    function decodeBlock(typ, buf){
      var t = TYPE_SCHEMAS.get(typ); if (!t) return {};
      var out = {}, i, f, decoded, key;
      for (i=0;i<t.schema.length;i++){
        f = t.schema[i]; decoded = decodeByField(buf, f); key = f.name;
        if (f.type==='u8_bits')      put(out, t.prefix+'/'+key, decoded);
        else if (decoded && typeof decoded==='object' && decoded.v!==undefined && f.unit){
          put(out, t.prefix+'/'+key, decoded.v); put(out, t.prefix+'/'+key+'@unit', f.unit);
        } else {
          put(out, t.prefix+'/'+key, decoded); if (f.unit) put(out, t.prefix+'/'+key+'@unit', f.unit);
        }
      }
      return out;
    }
    
    // ---------- Reassembly ----------
    var blocks = new Map();
    var seen = new Map(); // typ -> lastTs
    TYPE_SCHEMAS.forEach(function(_, typ){ blocks.set(typ, new Uint8Array(60)); });
    
    // ---------- CAN ----------
    var channel;
    try{
      channel = can.createRawChannel(CAN_IF, true);
      channel.addListener('onMessage', function (msg){
        if ((msg.id & 0x7FF) !== 0x400) return;
        var d = msg.data; if (!d || d.length<2) return;
        var typ = d[0], off = d[1], buf = blocks.get(typ);
        if (!buf) return;
        for (var i=2, j=0; i<d.length && (off+j)<buf.length; i++, j++) buf[off+j]=d[i];
        seen.set(typ, Date.now());
      });
      channel.start();
      console.error('[CAN] listening on '+CAN_IF+' (ID 0x400)');
    }catch(e){
      console.error('[CAN] open failed: '+e.message);
      process.exit(1);
    }
    
    // ---------- MQTT ----------
    var client = mqtt.connect(MQTT_URL, { reconnectPeriod:3000 });
    client.on('connect', function(){ console.error('[MQTT] connected '+MQTT_URL); });
    client.on('error',   function(err){ console.error('[MQTT] error: '+err.message); });
    
    setInterval(function(){
      TYPE_SCHEMAS.forEach(function(tdef, typ){
        if (PUBLISH_ONLY_SEEN){
          var last = seen.get(typ);
          if (!last) return;
          if (STALE_MS>0 && (Date.now()-last)>STALE_MS) return;
        }
        var buf = blocks.get(typ); if (!buf) return;
    
        // 1) Sprechende Felder
        publishTree(decodeBlock(typ, buf));
    
        var name = tname(typ);
    
        // 2) Block JSON
        if (PUBLISH_BLOCK_JSON){
          var payload = {
            ts: ts(),
            type: typ, type_name: name,
            length: buf.length,
            bytes: Array.from(buf)
          };
          client.publish(MQTT_BASE+'/blocks/'+typ.toString(16), JSON.stringify(payload), {retain:RETAIN, qos:QOS});
        }
    
        // 3) RAW bytes
        if (PUBLISH_RAW_BYTES){
          for (var b=0;b<60;b++){
            client.publish(MQTT_BASE+'/raw/'+name+'/b'+String(b).padStart(2,'0'), String(buf[b]), {retain:RETAIN, qos:QOS});
          }
        }
    
        // 4) Wörter u16be
        if (PUBLISH_WORDS){
          for (var w=0; w<=58; w+=2){
            client.publish(MQTT_BASE+'/raw/'+name+'/w'+String(w).padStart(2,'0'), String(u16be(buf,w)), {retain:RETAIN, qos:QOS});
          }
        }
    
        // 5) Triplets u24
        if (PUBLISH_TRIPLETS){
          for (var t=0; t<=57; t++){
            client.publish(MQTT_BASE+'/raw/'+name+'/t'+String(t).padStart(2,'0'), String(u24(buf[t],buf[t+1],buf[t+2])), {retain:RETAIN, qos:QOS});
          }
        }
      });
    
      client.publish(MQTT_BASE+'/heartbeat', String(Date.now()), {retain:RETAIN, qos:QOS});
    }, 2000);
    
    function publishTree(tree, base){
      base = base || MQTT_BASE;
      for (var k in tree){
        if (!Object.prototype.hasOwnProperty.call(tree,k)) continue;
        var v = tree[k];
        if (v && typeof v==='object' && !Array.isArray(v)) publishTree(v, base+'/'+k);
        else client.publish(base+'/'+k, String(v), {retain:RETAIN, qos:QOS});
      }
    }
    
    process.on('SIGINT', function(){
      try{ channel.stop(); }catch(e){}
      try{ client.end(); }catch(e){}
      process.exit(0);
    });
    
    

    Funkioniert, wenn man die Can Verbindung zum EcoCan Anschluss unter Can0 hergestellt hat.

    K 1 Reply Last reply
    0
    • D DaBibo

      Hallo zusammen,

      ich habe meine Buderus 4121 selbst über einen Raspberry zero 2 W und einem CAN-Hat (Habe den von waveshare genutzt, würde aber im erneuten Fall ein anderes nehmen, mit 8/16 Mhz - das von waveshare hat 12MHz und die Abfragen auf auf dem PI zeigen was anderes an.. war ein wenig Fehlersuche..) angebunden über zusammen mit chatgpt ein JavaScript geschrieben um die Daten per MQTT zu übertragen.

      #!/usr/bin/env node
      /**
       * Buderus System 4000 CAN -> MQTT Decoder (ALL MODULES, FULL BITS, RAW)
       * ---------------------------------------------------------------------
       * - SocketCAN (ID 0x400 Monitor), 60-Byte-Reassembly je Typ 0x80..0x9E
       * - Sprechende Felder + vollständige Bit-Dekodierung gemäß Doku
       * - Zusätzlich immer: Block-JSON, RAW-Bytes b00..b59, Wörter w00..w58 (u16be),
       *   Triplets t00..t57 (u24). Keine Wertebegrenzung/Clamps.
       *
       * ENV:
       *   CAN_IF=can0
       *   MQTT_URL=mqtt://USER:PASS@<BROKER_IP>:1883
       *   MQTT_BASE=buderus/eco
       *   PUBLISH_ONLY_SEEN=1   # nur publizieren, wenn Block wirklich empfangen (Default 1)
       *   STALE_MS=0            # >0: unterdrückt Veröffentlichung je Typ nach Inaktivität
       *   PUBLISH_BLOCK_JSON=1  # Block-JSON an/aus
       *   PUBLISH_RAW_BYTES=1   # b00..b59
       *   PUBLISH_WORDS=1       # w00,w02,...,w58
       *   PUBLISH_TRIPLETS=1    # t00..t57
       */
      
      var mqtt = require('mqtt');
      var can  = require('socketcan');
      
      // ---------- Konfig ----------
      var CAN_IF   = process.env.CAN_IF   || 'can0';
      var MQTT_URL = process.env.MQTT_URL || 'mqtt://127.0.0.1:1883';
      var MQTT_BASE = (process.env.MQTT_BASE || 'buderus/eco').replace(/\/+$/,'');
      var RETAIN = true, QOS = 0;
      
      var PUBLISH_ONLY_SEEN   = (process.env.PUBLISH_ONLY_SEEN   || '1') === '1';
      var STALE_MS            = parseInt(process.env.STALE_MS || '0', 10);
      var PUBLISH_BLOCK_JSON  = (process.env.PUBLISH_BLOCK_JSON  || '1') === '1';
      var PUBLISH_RAW_BYTES   = (process.env.PUBLISH_RAW_BYTES   || '1') === '1';
      var PUBLISH_WORDS       = (process.env.PUBLISH_WORDS       || '1') === '1';
      var PUBLISH_TRIPLETS    = (process.env.PUBLISH_TRIPLETS    || '1') === '1';
      
      // ---------- Helper ----------
      function u8(buf,i){ return (buf[i]!==undefined)?buf[i]:0; }
      function i8(buf,i){ var v=u8(buf,i); return v>=128?(v-256):v; }
      function u16be(buf,i){ return ((u8(buf,i)<<8)|u8(buf,i+1))>>>0; }
      function i16be(buf,i){ var v=u16be(buf,i); return (v&0x8000)?(v-0x10000):v; }
      function u24(b3,b2,b1){ return (((b3&0xFF)<<16)|((b2&0xFF)<<8)|(b1&0xFF))>>>0; }
      function bits(byte, names){
        var o={}, b;
        for(b=0;b<8;b++){
          var key = names && names[b] ? names[b] : ('b'+(b+1));
          o[key] = !!(byte & (1<<b));
        }
        return o;
      }
      function put(obj, path, val){
        var p=path.split('/'),cur=obj;
        for (var i=0;i<p.length-1;i++){ cur[p[i]]=cur[p[i]]||{}; cur=cur[p[i]]; }
        cur[p[p.length-1]] = val;
      }
      function tname(t){
        var map = {
          0x80:'hk1',0x81:'hk2',0x82:'hk3',0x83:'hk4',0x8A:'hk5',0x8B:'hk6',0x8C:'hk7',0x8D:'hk8',0x8E:'hk9',
          0x84:'warmwasser',0x88:'kessel',0x89:'konfig',0x8F:'strategie_boden',0x90:'lap',
          0x92:'uba1',0x93:'uba2',0x94:'uba3',0x95:'uba4',0x96:'uba5',0x97:'uba6',0x98:'uba7',0x99:'uba8',
          0x9B:'waermemenge',0x9C:'stoermodul',0x9D:'unterstation',0x9E:'solar'
        };
        return map[t] || ('type_'+t.toString(16));
      }
      function ts(){ return new Date().toISOString(); }
      
      // ---------- Bit-Labels aus Doku ----------
      var HK_BW1 = ['ausschaltopt','einschaltopt','automatik','ww_vorrang','estrichtrocknung','ferien','frostschutz','manuell'];
      var HK_BW2 = ['sommer','tag','fb_keine_kommunikation','fb_fehlerhaft','fehler_vorlauf','max_vorlauf','ext_stoereingang','party_pause'];
      var HK_EIN = ['eingang_wf2','eingang_wf3',null,null,null,'schalter_0','schalter_hand','schalter_aut'];
      
      var WW_BW1 = ['automatik','desinfektion','nachladung','ferien','fehler_desinfektion','fehler_fuehler','ww_bleibt_kalt','fehler_anode'];
      var WW_BW2 = ['laden','manuell','nachladen','ausschaltopt','einschaltopt','tag','warm','vorrang'];
      var WW_PUMP = ['ladepumpe','zirkulationspumpe','absenkung_solar',null,null,null,null,null];
      var WW_ZIRK = ['tag','automatik','ferien','einmallauf_3min',null,null,null,null];
      
      var SOL_BW1 = ['fehler_einst_hysterese','sp2_auf_max_temp','sp1_auf_max_temp','kollektor_auf_max_temp',null,null,null,null];
      var SOL_BW2 = ['fehler_fuehler_arl_bypass','fehler_fuehler_sp_mitte_bypass','fehler_volumenstrom_wz','fehler_fuehler_rl_wz','fehler_fuehler_vl_wz','fehler_fuehler_sp2_unten','fehler_fuehler_sp1_unten','fehler_fuehler_kollektor'];
      var SOL_BW3 = ['usv_sp2_zu','usv_sp2_auf_oder_ladepumpe2','usv_bypass_zu','usv_bypass_auf','sekundaerpumpe_sp2_betrieb',null,null,null];
      
      // UBA/EMS Bits (wandhängend)
      var UBA_STATUS = ['untergruppe_b0','untergruppe_b1','untergruppe_b2','hauptgruppe_b0','hauptgruppe_b1','hauptgruppe_b2','hauptgruppe_b3','blockierender_fehler_uba'];
      var UBA_HD     = ['ww_anforderung','ein_aus_raumthermostat','anforderung_schnittstelle','frostschutz','pumpennachlauf_wegen_ww','ww_anforderung_fuehler','ww_anforderung_durchfluss','brenner_an'];
      var UBA_BETRZ  = ['heizanforderung','ww_anforderung','jumper_11kw_entfernt','betriebstemperatur_betrieb','kesselschutz_taupunkt','verriegelt_service','blockiert','servicemeldung'];
      var UBA_REL1   = ['magnetventil_stufe1','magnetventil_stufe2','geblaese','zuendung','oelvorwaermung_abgasklappe','kesselkreispumpe_heizkreispumpe','3_wegeventil','ww_zirkulationspumpe'];
      var UBA_REL2   = ['ww_ladepumpe','fluessiggasventil','gwp_umwaelzpumpe',null,null,null,null,null];
      var UBA_EMS_FEHLER = ['luftfuehler_fa_defekt','betriebstemperatur_nicht_erreicht','oelvorwaermer_dauersignal','oelvorwaermer_ohne_signal',null,null,null,null];
      var UBA_FEHL_EINST = ['jumper_11kw_kaskade','bc10_notbetrieb','ww_poti_nicht_aut','kesselpoti_nicht_aut_90c','anforderung_klemme_wa',null,'kommunikation_vorhanden_fm458','keine_kommunikation_fm458'];
      
      // Kessel bodenstehend (0x88) – Brennerstatusbits (34)
      var KES_BRENNERSTATUS = ['abgastest','brenner_runter_aus','brenner_auto','brenner_1_stufe','unter_betrieb','leistung_frei','leistung_hoch','betriebsstunden_2_stufe'];
      
      // ---------- Schema-Bausteine ----------
      function F(name, byte, type, unit, scale){ return {name:name, byte:byte, type:type, unit:unit, scale:scale}; }
      function Fb(name, byte, names){ return {name:name, byte:byte, type:'u8_bits', bitnames:names}; }
      function Fu24(name, b3,b2,b1, unit){ return {name:name, bytes:[b3,b2,b1], type:'u24', unit:unit}; }
      function Fu16be(name, b, unit, scale){ return {name:name, bytes:[b,b+1], type:'u16be', unit:unit, scale:scale}; }
      
      // ---------- Schemata ----------
      // Heizkreise 0x80..0x83,0x8A..0x8E
      function HK_SCHEMA(){
        return [
          Fb('betriebswerte1',0,HK_BW1),
          Fb('betriebswerte2',1,HK_BW2),
          F('vorlauf_soll',2,'u8','°C'),
          F('vorlauf_ist',3,'u8','°C'),
          F('raum_soll',4,'u8','°C',0.5),
          F('raum_ist',5,'u8','°C',0.5),
          F('einschaltopt_min',6,'u8','min'),
          F('ausschaltopt_min',7,'u8','min'),
          F('pumpe',8,'u8','%'),
          F('stellglied',9,'u8','%'),
          Fb('hk_eingang',10,HK_EIN),
          // 11..17 „FREI/Heizkennlinie“ -> RAW deckt ab
        ];
      }
      
      // Warmwasser 0x84
      var SCHEMA_84 = [
        Fb('betriebswerte1',0,WW_BW1),
        Fb('betriebswerte2',1,WW_BW2),
        F('ww_soll',2,'u8','°C'),
        F('ww_ist',3,'u8','°C'),
        F('einschaltopt_min',4,'u8','min'),
        Fb('pumpen_bits',5,WW_PUMP),
        // 6..7 reserviert/RAW
        Fb('zirkulationspumpe',8,WW_ZIRK),
        Fb('wf_eingaenge',9,['eingang2','eingang3',null,null,null,'handschalter_0','handschalter_hand','handschalter_aut']),
        F('start_ladung',10,'u8','°C'),
        F('ende_ladung',11,'u8','°C'),
        F('t_speicher_unten',12,'u8','°C'),
        F('t_waermetauscher',13,'u8','°C'),
        F('mischer_soll',14,'u8','%'),
        F('mischer_ist',15,'u8','%'),
        F('prim_pumpe',16,'u8','%'),
        F('sek_pumpe',17,'u8','%')
      ];
      
      // Kessel bodenstehend 0x88 (repräsentative/benannte Felder + Brenner-Statusbits)
      var SCHEMA_88 = [
        F('kessel_vl_soll',0,'u8','°C'),
        F('kessel_vl_ist',1,'u8','°C'),
        // 2..5 RAW / spezifisch pro ZM
        F('brenner_ansteuerung',8,'u8'),
        F('abgastemperatur',9,'u8','°C'),
        F('mod_brenner_stellglied',10,'u8','%'),
        F('mod_brenner_ist_leistung',11,'u8','%'),
        // 12..33 diverse Laufzeiten/Starts – RAW deckt Zahlen vollständig ab
        Fb('brenner_statusbits',34,KES_BRENNERSTATUS)
      ];
      
      // Strategie Boden 0x8F (Auszug + RAW)
      var SCHEMA_8F = [
        F('anlagen_vl_soll',0,'u8','°C'),
        F('vl_ist',1,'u8','°C'),
        F('rl_soll',2,'u8','°C'),
        F('rl_ist',3,'u8','°C')
      ];
      
      // Konfiguration 0x89 (vollständig laut Tabelle)
      var SCHEMA_89 = [
        F('aussentemperatur',0,'i8','°C'),
        F('aussentemperatur_gedaempft',1,'i8','°C'),
        // 2,3 Versionen (Direktmodus) -> RAW
        F('slot1',6,'u8'),
        F('slot2',7,'u8'),
        F('slot3',8,'u8'),
        F('slot4',9,'u8'),
        F('slotA',10,'u8'),
        // 12..16 Fehlermeldungen je Slot -> RAW zusätzlich vorhanden
        F('anlagen_vl_soll',18,'u8','°C'),
        F('anlagen_vl_ist',19,'u8','°C'),
        Fb('anlagen_flags',20,['puffer_bleibt_kalt','fuehler_ust_fk_defekt','wartezeit_laeuft',null,null,null,null,null]),
        F('max_pumpen_ansteuerung',21,'u8','%'),
        F('max_stellglied',22,'u8','%'),
        F('rg_vorlauf_ist',23,'u8','°C')
      ];
      
      // LAP 0x90
      var SCHEMA_90 = [
        F('ww_soll',4,'u8','°C'),
        F('ww_ist',6,'u8','°C'),
        F('einschaltopt_min',8,'u8','min'),
        F('start_ladung',20,'u8','°C'),
        F('ende_ladung',21,'u8','°C'),
        F('ist_speicher_unten',24,'u8','°C'),
        F('ist_waermetauscher',26,'u8','°C'),
        F('mischer_soll',28,'u8','%'),
        F('mischer_ist',30,'u8','%'),
        F('primaerpumpe',32,'u8','%'),
        F('sekundaerpumpe',34,'u8','%')
      ];
      
      // UBA (wandhängend) 0x92..0x99
      var SCHEMA_UBA = [
        F('soll_modulation',0,'u8','%'),
        F('ist_modulation',1,'u8','%'),
        Fu24('brennerstunden_h',2,3,4,'h'),
        F('brennerminuten',5,'u8','min'),
        F('kessel_vl_soll',6,'u8','°C'),
        F('kessel_vl_ist',7,'u8','°C'),
        F('ww_soll',8,'u8','°C'),
        F('ww_ist',9,'u8','°C'),
        F('antipendel_min',10,'u8','min'),
        Fb('betriebsflag_regelgeraet',11,['antipendel','keine_komm_kse',null,null,null,null,null,null]),
        Fb('betriebsflags_uba_kse',12,['umwaelzpumpe','schornsteinfeger','keine_komm_uba','keine_komm_kse','antipendel','umschaltventil_ww','abgaswaechter','pumpenschalter']),
        Fb('status_uba',13,UBA_STATUS),
        Fb('hd_mode_uba',14,UBA_HD),
        Fu24('brennerstarts',15,16,17,'count'),
        F('version_uba',18,'u8'),
        F('kim_nummer',19,'u8'),
        F('ruecklauf',20,'u8','°C'),
        F('pumpen_mod_uba',21,'u8','%'),
        Fb('anlagenfehler_ems_kessel',22,UBA_EMS_FEHLER),
        // 23: „ems ww“ in älterer Tabelle – als RAW ohnehin da
        {name:'ems_code1', byte:24, type:'ascii'},
        {name:'ems_code2', byte:25, type:'ascii'},
        F('fehlernummer_hi',26,'u8'),
        F('fehlernummer_lo',27,'u8'),
        Fb('brennertyp',28,['bit1','bit2','bit3','bit4','bit5','bit6','bit7','bit8']),
        // 29 zusammen mit 54 als u16be
        {name:'max_leistung_kw', bytes:[54,29], type:'u16be', unit:'kW'},
      
        F('min_leistung_pct',30,'u8','%'),
        F('flammenstrom_uA',31,'u8','µA'),
        F('abgas_temp',32,'u8','°C'),
        F('ansaugluft',33,'i8','°C'),
        F('wasserdruck',34,'u8','bar'),
        Fb('brennerzustand',35,UBA_BETRZ),
        Fb('relais1',36,UBA_REL1),
        Fb('relais2',37,UBA_REL2),
        F('vl_soll_feuerungsautomat',38,'u8','°C'),
        F('ww_ladeart',39,'u8'),
        Fb('fehleinstellungen_ems_kessel',40,UBA_FEHL_EINST),
        Fb('ems_servicemeldungen',41,['keine_meldung',null,null,null,null,null,null,null]),
        // 42..53 Versions-/Kennbytes -> RAW
        F('betriebstemperatur_konst',55,'u8','°C')
      ];
      
      // Wärmemenge 0x9B (Triplets/Zähler kommen zusätzlich als RAW/Triplets)
      var SCHEMA_9B = [
        // einzelne Byte-Zähler exemplarisch – RAW (t..) liefert komplette Zahlen
        F('impulse_heute_hi',5,'u8'),
        F('impulse_heute_lo',6,'u8'),
        F('impulse_woche_hi',11,'u8'),
        F('impulse_woche_lo',12,'u8')
      ];
      
      // Störmodul 0x9C / Unterstation 0x9D – Grundfelder + RAW
      var SCHEMA_9C = [ Fu16be('vl_soll_0_10v',0,'°C') ];
      var SCHEMA_9D = [ Fu16be('anlagen_vorlauf',0,'°C'), Fu16be('vl_soll_0_10v',2,'°C'), F('zubringerpumpe',3,'u8','%') ];
      
      // Solar 0x9E
      var SCHEMA_9E = [
        Fb('betriebswerte1',0,SOL_BW1),
        Fb('betriebswerte2',1,SOL_BW2),
        Fb('betriebswerte3',2,SOL_BW3),
        {name:'kollektor_temp', bytes:[3,4], type:'u16be', unit:'°C', scale:0.1},
        F('pumpe_speicher_mod',5,'u8','%'),
        F('t_sp1_unten',6,'u8','°C'),
        F('status_sp1',7,'u8'),
        F('t_sp2_unten',8,'u8','°C'),
        F('status_sp2',9,'u8'),
        F('t_sp_mitte_bypass',10,'u8','°C'),
        F('t_anl_rl_bypass',11,'u8','°C'),
        F('t_wmz_vorlauf',12,'u8','°C'),
        F('t_wmz_ruecklauf',13,'u8','°C'),
        Fu16be('volumenstrom_lh',14,'l/h'),
        Fu16be('leistung_w',16,'W'),
        Fu24('wm_sp1_100wh',18,19,20,'100Wh'),
        Fu24('wm_sp2_100wh',21,22,23,'100Wh'),
        Fu24('bh_sp1_min',24,25,26,'min'),
        F('ww_sollabsenkung_solar',27,'u8','K'),
        F('ww_sollabsenkung_kapaz',28,'u8','K'),
        F('kollektor_temp_1C',29,'u8','°C'),
        Fu24('bh_sp2_min',30,31,32,'min')
      ];
      
      // Zuordnung Typ -> (Prefix, Schema)
      var TYPE_SCHEMAS = new Map([
        [0x80,{prefix:'hk1', schema:HK_SCHEMA()}],
        [0x81,{prefix:'hk2', schema:HK_SCHEMA()}],
        [0x82,{prefix:'hk3', schema:HK_SCHEMA()}],
        [0x83,{prefix:'hk4', schema:HK_SCHEMA()}],
        [0x8A,{prefix:'hk5', schema:HK_SCHEMA()}],
        [0x8B,{prefix:'hk6', schema:HK_SCHEMA()}],
        [0x8C,{prefix:'hk7', schema:HK_SCHEMA()}],
        [0x8D,{prefix:'hk8', schema:HK_SCHEMA()}],
        [0x8E,{prefix:'hk9', schema:HK_SCHEMA()}],
        [0x84,{prefix:'warmwasser', schema:SCHEMA_84}],
        [0x88,{prefix:'kessel',      schema:SCHEMA_88}],
        [0x8F,{prefix:'strategie_boden', schema:SCHEMA_8F}],
        [0x89,{prefix:'konfig',      schema:SCHEMA_89}],
        [0x90,{prefix:'lap',         schema:SCHEMA_90}],
        [0x92,{prefix:'uba1', schema:SCHEMA_UBA}],
        [0x93,{prefix:'uba2', schema:SCHEMA_UBA}],
        [0x94,{prefix:'uba3', schema:SCHEMA_UBA}],
        [0x95,{prefix:'uba4', schema:SCHEMA_UBA}],
        [0x96,{prefix:'uba5', schema:SCHEMA_UBA}],
        [0x97,{prefix:'uba6', schema:SCHEMA_UBA}],
        [0x98,{prefix:'uba7', schema:SCHEMA_UBA}],
        [0x99,{prefix:'uba8', schema:SCHEMA_UBA}],
        [0x9B,{prefix:'waermemenge', schema:SCHEMA_9B}],
        [0x9C,{prefix:'stoermodul',  schema:SCHEMA_9C}],
        [0x9D,{prefix:'unterstation',schema:SCHEMA_9D}],
        [0x9E,{prefix:'solar',       schema:SCHEMA_9E}]
      ]);
      
      // ---------- Decoder ----------
      function decodeByField(buf, f){
        var val;
        if (f.bytes){
          if (f.type==='u24'){
            val = u24(u8(buf,f.bytes[0]), u8(buf,f.bytes[1]), u8(buf,f.bytes[2]));
          } else if (f.type==='u16be'){
            val = u16be(buf, f.bytes[0]);
          } else { val = 0; }
        } else {
          if (f.type==='u8')      val = u8(buf,f.byte);
          else if (f.type==='i8') val = i8(buf,f.byte);
          else if (f.type==='ascii'){ var b=u8(buf,f.byte); val=(b>=32&&b<=126)?String.fromCharCode(b):''; }
          else if (f.type==='u8_bits'){ val = bits(u8(buf,f.byte), f.bitnames); }
          else if (f.type==='i16be'){ val = i16be(buf,f.byte); }
          else if (f.type==='u16be'){ val = u16be(buf,f.byte); }
          else val = u8(buf,f.byte);
        }
        if (typeof f.scale==='number') val = Number((val * f.scale).toFixed(2));
        if (f.unit) return {v:val, unit:f.unit};
        return val;
      }
      function decodeBlock(typ, buf){
        var t = TYPE_SCHEMAS.get(typ); if (!t) return {};
        var out = {}, i, f, decoded, key;
        for (i=0;i<t.schema.length;i++){
          f = t.schema[i]; decoded = decodeByField(buf, f); key = f.name;
          if (f.type==='u8_bits')      put(out, t.prefix+'/'+key, decoded);
          else if (decoded && typeof decoded==='object' && decoded.v!==undefined && f.unit){
            put(out, t.prefix+'/'+key, decoded.v); put(out, t.prefix+'/'+key+'@unit', f.unit);
          } else {
            put(out, t.prefix+'/'+key, decoded); if (f.unit) put(out, t.prefix+'/'+key+'@unit', f.unit);
          }
        }
        return out;
      }
      
      // ---------- Reassembly ----------
      var blocks = new Map();
      var seen = new Map(); // typ -> lastTs
      TYPE_SCHEMAS.forEach(function(_, typ){ blocks.set(typ, new Uint8Array(60)); });
      
      // ---------- CAN ----------
      var channel;
      try{
        channel = can.createRawChannel(CAN_IF, true);
        channel.addListener('onMessage', function (msg){
          if ((msg.id & 0x7FF) !== 0x400) return;
          var d = msg.data; if (!d || d.length<2) return;
          var typ = d[0], off = d[1], buf = blocks.get(typ);
          if (!buf) return;
          for (var i=2, j=0; i<d.length && (off+j)<buf.length; i++, j++) buf[off+j]=d[i];
          seen.set(typ, Date.now());
        });
        channel.start();
        console.error('[CAN] listening on '+CAN_IF+' (ID 0x400)');
      }catch(e){
        console.error('[CAN] open failed: '+e.message);
        process.exit(1);
      }
      
      // ---------- MQTT ----------
      var client = mqtt.connect(MQTT_URL, { reconnectPeriod:3000 });
      client.on('connect', function(){ console.error('[MQTT] connected '+MQTT_URL); });
      client.on('error',   function(err){ console.error('[MQTT] error: '+err.message); });
      
      setInterval(function(){
        TYPE_SCHEMAS.forEach(function(tdef, typ){
          if (PUBLISH_ONLY_SEEN){
            var last = seen.get(typ);
            if (!last) return;
            if (STALE_MS>0 && (Date.now()-last)>STALE_MS) return;
          }
          var buf = blocks.get(typ); if (!buf) return;
      
          // 1) Sprechende Felder
          publishTree(decodeBlock(typ, buf));
      
          var name = tname(typ);
      
          // 2) Block JSON
          if (PUBLISH_BLOCK_JSON){
            var payload = {
              ts: ts(),
              type: typ, type_name: name,
              length: buf.length,
              bytes: Array.from(buf)
            };
            client.publish(MQTT_BASE+'/blocks/'+typ.toString(16), JSON.stringify(payload), {retain:RETAIN, qos:QOS});
          }
      
          // 3) RAW bytes
          if (PUBLISH_RAW_BYTES){
            for (var b=0;b<60;b++){
              client.publish(MQTT_BASE+'/raw/'+name+'/b'+String(b).padStart(2,'0'), String(buf[b]), {retain:RETAIN, qos:QOS});
            }
          }
      
          // 4) Wörter u16be
          if (PUBLISH_WORDS){
            for (var w=0; w<=58; w+=2){
              client.publish(MQTT_BASE+'/raw/'+name+'/w'+String(w).padStart(2,'0'), String(u16be(buf,w)), {retain:RETAIN, qos:QOS});
            }
          }
      
          // 5) Triplets u24
          if (PUBLISH_TRIPLETS){
            for (var t=0; t<=57; t++){
              client.publish(MQTT_BASE+'/raw/'+name+'/t'+String(t).padStart(2,'0'), String(u24(buf[t],buf[t+1],buf[t+2])), {retain:RETAIN, qos:QOS});
            }
          }
        });
      
        client.publish(MQTT_BASE+'/heartbeat', String(Date.now()), {retain:RETAIN, qos:QOS});
      }, 2000);
      
      function publishTree(tree, base){
        base = base || MQTT_BASE;
        for (var k in tree){
          if (!Object.prototype.hasOwnProperty.call(tree,k)) continue;
          var v = tree[k];
          if (v && typeof v==='object' && !Array.isArray(v)) publishTree(v, base+'/'+k);
          else client.publish(base+'/'+k, String(v), {retain:RETAIN, qos:QOS});
        }
      }
      
      process.on('SIGINT', function(){
        try{ channel.stop(); }catch(e){}
        try{ client.end(); }catch(e){}
        process.exit(0);
      });
      
      

      Funkioniert, wenn man die Can Verbindung zum EcoCan Anschluss unter Can0 hergestellt hat.

      K Offline
      K Offline
      Kelrob
      wrote on last edited by
      #2

      @dabibo Hi DaBibo,
      dein Script deckt sich gut mit meinen Logs, habe einen Peak PCAN-USB mit 2 Leitungen an den CAN-Anschluss meiner Logamatic 4121 gehängt und die CAN-Bitrate auf 50kHz gesenkt, dann ging's.

      Woher hast du denn die Doku der CAN-IDs? Ich suche nämlich noch nach dem umgekehrten Weg, also Daten in die Steuerung zu übertragen wie z.B. den aktuellen Modus "Tag/Nacht/Auto".

      Danke für's teilen des Scripts.

      1 Reply Last reply
      0

      Hello! It looks like you're interested in this conversation, but you don't have an account yet.

      Getting fed up of having to scroll through the same posts each visit? When you register for an account, you'll always come back to exactly where you were before, and choose to be notified of new replies (either via email, or push notification). You'll also be able to save bookmarks and upvote posts to show your appreciation to other community members.

      With your input, this post could be even better 💗

      Register Login
      Reply
      • Reply as topic
      Log in to reply
      • Oldest to Newest
      • Newest to Oldest
      • Most Votes


      Support us

      ioBroker
      Community Adapters
      Donate

      528

      Online

      32.8k

      Users

      82.7k

      Topics

      1.3m

      Posts
      Community
      Impressum | Datenschutz-Bestimmungen | Nutzungsbedingungen | Einwilligungseinstellungen
      ioBroker Community 2014-2025
      logo
      • Login

      • Don't have an account? Register

      • Login or register to search.
      • First post
        Last post
      0
      • Home
      • Recent
      • Tags
      • Unread 0
      • Categories
      • Unreplied
      • Popular
      • GitHub
      • Docu
      • Hilfe