Skip to content
  • Home
  • Aktuell
  • Tags
  • 0 Ungelesen 0
  • Kategorien
  • Unreplied
  • Beliebt
  • 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

  • Standard: (Kein Skin)
  • Kein Skin
Einklappen
ioBroker Logo

Community Forum

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

NEWS

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

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

  • Weihnachtsangebot 2025! 🎄
    BluefoxB
    Bluefox
    25
    1
    2.1k

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

Geplant Angeheftet Gesperrt Verschoben Hardware
2 Beiträge 2 Kommentatoren 182 Aufrufe 2 Watching
  • Älteste zuerst
  • Neuste zuerst
  • Meiste Stimmen
Antworten
  • In einem neuen Thema antworten
Anmelden zum Antworten
Dieses Thema wurde gelöscht. Nur Nutzer mit entsprechenden Rechten können es sehen.
  • D Offline
    D Offline
    DaBibo
    schrieb am zuletzt editiert von
    #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 Antwort Letzte Antwort
    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
      schrieb am zuletzt editiert von
      #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 Antwort Letzte Antwort
      0
      Antworten
      • In einem neuen Thema antworten
      Anmelden zum Antworten
      • Älteste zuerst
      • Neuste zuerst
      • Meiste Stimmen


      Support us

      ioBroker
      Community Adapters
      Donate

      768

      Online

      32.6k

      Benutzer

      82.1k

      Themen

      1.3m

      Beiträge
      Community
      Impressum | Datenschutz-Bestimmungen | Nutzungsbedingungen | Einwilligungseinstellungen
      ioBroker Community 2014-2025
      logo
      • Anmelden

      • Du hast noch kein Konto? Registrieren

      • Anmelden oder registrieren, um zu suchen
      • Erster Beitrag
        Letzter Beitrag
      0
      • Home
      • Aktuell
      • Tags
      • Ungelesen 0
      • Kategorien
      • Unreplied
      • Beliebt
      • GitHub
      • Docu
      • Hilfe