NEU! Insider Preview IP3 verfügbar - Fettes Connectivity Upgrade zu den Feiertagen!
Jetzt könnt ihr tausende Geräte mehr anschließen, mit wenigen Klicks: Alexa, Netatmo, Aquara, Bosch, NEFF, Siemens, Gaggenau, Thermador, Constructa, SONOS, hue, somfy, Spotify, NUKI, tado, blink, ring, Gardena, neato, iRobot, Husquarna, Apple Watch, Go-e Charger, fitbit, Google, Weather Underground, telegram, Twitter (wer noch mag) und 700 weitere.
Soviel Connectivity gab es noch nie auf einen Schlag.
Damit Euch nicht langweilig wird. Weitere Info im Wiki: https://elabnet.atlassian.net/l/cp/1yDzrSQU

----------------------------------------------------------------------------------------------------------------------------------------------------------
Hauptversion 3.5.1 verfügbar - Jetzt mit HTTP-API und hunderten Verbesserungen
Info im Forum: viewtopic.php?f=8&t=3831

[Beantwortet] V-Bus Anbindung an den TWS

Wissen, Planung & Diskussion zur Modbus Unterstützung im Timberwolf Server.
Stellt uns hier Eure Modbus Projekte und Ideen vor.
Forumsregeln
  • Denke bitte an aussagekräftige Titel und gebe dort auch die [Firmware] an. Wenn ETS oder CometVisu beteiligt sind, dann auch deren Version
  • Bitte mache vollständige Angaben zu Deinem Server, dessen ID und dem Online-Status in Deiner Signatur. Hilfreich ist oft auch die Beschreibung der angeschlossener Hardware sowie die verwendeten Protokolle
  • Beschreibe Dein Projekt und Dein Problem bitte vollständig. Achte bitte darauf, dass auf Screenshots die Statusleiste sichtbar ist
  • Bitte sei stets freundlich und wohlwollend, bleibe beim Thema und unterschreibe mit deinem Vornamen. Bitte lese alle Regeln, die Du hier findest: https://wiki.timberwolf.io/Forenregeln

Ersteller
Mibr85
Reactions:
Beiträge: 146
Registriert: Mo Dez 02, 2019 5:38 am
Hat sich bedankt: 117 Mal
Danksagung erhalten: 45 Mal

V-Bus Anbindung an den TWS

#1

Beitrag von Mibr85 »

Hallo zusammen
Bei mir im Haus arbeitet eine Erdwärmepumpe Viessmann Vitocal 300 mit Vitocell 200 Steuerung und eine Frischwasserstation Vitotrans 353. leider ist das Modbusmodul von Viessmann mit 1000€ ganz schön teuer und aus der Frischwasserstation bekommt man die Werte nur über den VBus (Viessmann interner Bus)
http://hobbyelektronik.org/w/index.php/VBus-Decoder
Hier gibt es auch die Spezifikationen für den VBus.
Hat sich damit evtl schon mal jemand auseinander gesetzt bzw. Hat eine Idee das ganze Inman den TWS abzubinden?
Der ioBroker Adapter läuft dieser gibt aber leider keine Werte der Frischwasserstation aus.

Danke für eure Ideen
Grüße Micha :-)

TWS 2600 #528 + PBM #972,
VPN offen, Reboot nach Rücksprache
PLZ 01...

gndlff
Reactions:
Beiträge: 4
Registriert: Do Feb 03, 2022 8:36 pm
Danksagung erhalten: 11 Mal

#2

Beitrag von gndlff »

Hallo Micha,

ich habe meine Oventrop Regumaq Frischwasserstation, die ebenfalls Resol V-Bus spricht über den Resol USB-Schnittstellenadapter an den TWS angebunden.
Darauf läuft im Docker Container das Python-Script von rellit (https://github.com/rellit/resol-vbus-python), welches ich um eine MQTT-Anbindung in Python (paho-mqtt) erweitert habe. Damit werden die Daten des V-Bus ausgelesen und über MQTT an den TWS in die Timeseries gesendet.

Das Vorlage-Script muss angepasst werden, da vermutlich auch die Viessmann-spezifischen Paket-Header (wie auch bei Oventrop) nicht standardmäßig interpretiert werden können. Über den Schnittstellenadapter am PC lässt sich mit der Resol Service Center Software (https://www.resol.de/de/software) allerdings der Datenstrom in Rohform ausgeben und die von deiner WP/FriWa genutzten Paketheader mit der V-Bus Spezifikation (http://danielwippermann.github.io/resol-vbus/#/vsf) abgeglichen werden. Siehe dazu auch die von dir bereits verlinkte Seite von Hobbyelektronik. Mit diesen Infos kann dann das Python Script angepasst werden.

Viel Erfolg.

Gruß Moritz


Hier die angepassten Scripte als Lösungsansatz

config.py

Code: Alles auswählen

import os

# configure kind of connection "lan", "serial" or "stdin"
#connection = "lan"
connection = "serial"
#connection = "stdin"

# only used for "lan"
address = ("192.168.1.253", 7053)
vbus_pass = "vbus"

# only used for "serial"
port = "/dev/ttyACM0"
baudrate = 9600

spec_file = os.path.dirname(__file__) + '/spec/Regumaq-X45.json'
# expected amount of different source packets (see spec_file)
expected_packets = 1

debug = False

# MQTT Broker config
mqtt_host = "192.168.3.11"
#MQTT_PORT = 1883
#MQTT_KEEPALIVE_INTERVAL = 45
mqtt_topic = "regumaq"
resol3mqtt.py

Code: Alles auswählen

#!/usr/bin/env python3

#
# Talk with Resol VBUS over LAN or serial UART
#

import socket
import sys
import json
from decimal import *
import paho.mqtt.client as mqtt
import time

# Load settings
try:
    import config
except:
    sys.exit("config.py not found!")

if config.connection == "serial":
    # import serial (pyserial) only if it's configured to not force installing it without needing
    import serial

# Load Message specification
try:
    import spec
except:
    sys.exit("Could not load Message Specification")


# Logs in onto the DeltaSol BS Plus over LAN. Also starts (and maintains) the
# actual stream of data.
def login():
    dat = recv()

    #Check if device answered
    if dat != "+HELLO\n":
        return False

    #Send Password
    send("PASS %s\n" % config.vbus_pass)

    dat = recv()

    return dat.startswith("+OK")


def load_data():
    if config.connection == "lan":
        #Request Data
        send("DATA\n")

        dat = recv()

        #Check if device is ready to send Data
        if not dat.startswith("+OK"):
            return

    while len(result) < config.expected_packets:
        buf = readstream()
        msgs = splitmsg(buf)
        if config.debug:
            print(str(len(msgs))+" Messages, "+str(len(result))+" Resultlen")
        for msg in msgs:
            if config.debug: print(get_protocolversion(msg))
            if "PV1" == get_protocolversion(msg):
                if config.debug: print(format_message_pv1(msg))
                parse_payload(msg)
            elif "PV2" == get_protocolversion(msg):
                if config.debug: print(format_message_pv2(msg))

# Receive 1024 bytes from stream
def recv():
    if config.connection == "serial":
        # timeout needs to be set to 0 (see init), or it will
        # block until the requested number of bytes is read
        dat = sock.read(1024)
    elif config.connection == "lan":
        dat = sock.recv(1024)
    elif config.connection == "stdin":
        dat = sock.buffer.read(1024)
    else:
        sys.exit('Unknown connection type. Please check config.')
    return dat


# Sends given bytes over the stream. Adds debug
def send(dat):
    sock.send(dat)


# Read data until minimum 1 message is received
# We cyclic get:
# '0xaa' '0x00' '0x00' '0x21' '0x74' '0x20' ...
# '0xaa' '0x15' '0x00' '0x21' '0x74' '0x10' ...
# '0xaa' '0x10' '0x00' '0x21' '0x74' '0x10' ...
# Only the last one is needed, so we need 4 times '0xaa'!
def readstream():
    data = bytearray(b'')
    data.extend(recv())
    while data.count(0xAA) < 4:
        data.extend(recv())
    return data


#Split Messages on Sync Byte
def splitmsg(buf):
    return buf.split(b'\xAA')[1:-1]


# Format 1 byte as String Hex string
def format_byte(byte):
    return '0x' + ('0' if byte < 0x10 else '') + format(byte, 'x')


# Extract protocol Version from msg
def get_protocolversion(msg):
    if msg[4] == 0x10: return "PV1"
    if msg[4] == 0x20: return "PV2"
    if msg[4] == 0x30: return "PV3"
    return "UNKNOWN"


# Extract Destination from msg
def get_destination(msg):
    return format_byte(msg[1]) + format_byte(msg[0])[2:]


#Extract source from msg
def get_source(msg):
    return format_byte(msg[3]) + format_byte(msg[2])[2:]


# Extract command from msg
def get_command(msg):
    return format_byte(msg[6]) + format_byte(msg[5])[2:]


# Get count of frames in msg
def get_frame_count(msg):
    return gb(msg, 7, 8)


# Extract payload from msg
def get_payload(msg):
    payload = bytearray(b'')
    for i in range(get_frame_count(msg)):
        payload.extend(integrate_septett(msg[9+(i*6):15+(i*6)]))
    return payload


# parse payload and put result in result
def parse_payload(msg):
    payload = get_payload(msg)

    if config.debug:
         print('ParsePacket Payload '+str(len(payload)))

    for packet in spec.spec['packet']:
        if packet['source'].lower() == get_source(msg).lower() and \
                packet['destination'].lower() == get_destination(msg).lower() and \
                packet['command'].lower() == get_command(msg).lower():
            result[get_source_name(msg)] = {}
            for field in packet['field']:
                result[get_source_name(msg)][field['name'][0]] = str(
                    gb(payload, field['offset'], int(field['offset'])+((int(field['bitSize'])+1) / 8)) *
                    (Decimal(field['factor']) if 'factor' in field else 1)) + \
                    (field['unit'] if 'unit' in field else '')


def format_message_pv1(msg):
    parsed = "PARSED: \n"
    parsed += "    ZIEL".ljust(15,'.')+": " + get_destination(msg) + "\n"
    parsed += "    QUELLE".ljust(15,'.')+": " + get_source(msg) + " " + get_source_name(msg) + "\n"
    parsed += "    PROTOKOLL".ljust(15,'.')+": " + get_protocolversion(msg) + "\n"
    parsed += "    BEFEHL".ljust(15,'.')+": " + get_command(msg) + "\n"
    parsed += "    ANZ_FRAMES".ljust(15,'.')+": " + str(get_frame_count(msg)) + "\n"
    parsed += "    CHECKSUM".ljust(15,'.')+": " + format_byte(msg[8]) + "\n"
    for i in range(get_frame_count(msg)):
        integrated = integrate_septett(msg[9+(i*6):15+(i*6)])
        parsed += ("    NB"+str(i*4+1)).ljust(15,'.')+": " + format_byte(msg[9+(i*6)]) + " - " + format_byte(integrated[0]) + "\n"
        parsed += ("    NB"+str(i*4+2)).ljust(15,'.')+": " + format_byte(msg[10+(i*6)]) + " - " + format_byte(integrated[1]) + "\n"
        parsed += ("    NB"+str(i*4+3)).ljust(15,'.')+": " + format_byte(msg[11+(i*6)]) + " - " + format_byte(integrated[2]) + "\n"
        parsed += ("    NB"+str(i*4+4)).ljust(15,'.')+": " + format_byte(msg[12+(i*6)]) + " - " + format_byte(integrated[3]) + "\n"
        parsed += ("    SEPTETT"+str(i+1)).ljust(15,'.')+": " + format_byte(msg[13+(i*6)]) + "\n"
        parsed += ("    CHECKSUM"+str(i+1)).ljust(15,'.')+": " + format_byte(msg[14+(i*6)]) + "\n"
    parsed += "    PAYLOAD".ljust(15,'.')+": " + (" ".join(format_byte(b) for b in get_payload(msg)))+"\n"
    return parsed


def format_message_pv2(msg):
    parsed = "PARSED: \n"
    parsed += "    ZIEL1".ljust(15,'.')+": " + format_byte(msg[0]) + "\n"
    parsed += "    ZIEL2".ljust(15,'.')+": " + format_byte(msg[1]) + "\n"
    parsed += "    QUELLE1".ljust(15,'.')+": " + format_byte(msg[2]) + "\n"
    parsed += "    QUELLE2".ljust(15,'.')+": " + format_byte(msg[3]) + "\n"
    parsed += "    PROTOKOLL".ljust(15,'.')+": " + format_byte(msg[4]) + "\n"
    parsed += "    BEFEHL1".ljust(15,'.')+": " + format_byte(msg[5]) + "\n"
    parsed += "    BEFEHL2".ljust(15,'.')+": " + format_byte(msg[6]) + "\n"
    parsed += "    ID1".ljust(15,'.')+": " + format_byte(msg[7]) + "\n"
    parsed += "    ID2".ljust(15,'.')+": " + format_byte(msg[8]) + "\n"
    parsed += "    WERT1".ljust(15,'.')+": " + format_byte(msg[9]) + "\n"
    parsed += "    WERT2".ljust(15,'.')+": " + format_byte(msg[10]) + "\n"
    parsed += "    WERT3".ljust(15,'.')+": " + format_byte(msg[11]) + "\n"
    parsed += "    WERT4".ljust(15,'.')+": " + format_byte(msg[12]) + "\n"
    parsed += "    SEPTETT".ljust(15,'.')+": " + format_byte(msg[13]) + "\n"
    parsed += "    CHECKSUM".ljust(15,'.')+": " + format_byte(msg[14]) + "\n"
    return parsed


def get_compare_length(mask):
    i = 1
    while i < 6 and mask[i] != '0':
        i += 1
    return i+1


def get_source_name(msg):
    src = format_byte(msg[3]) + format_byte(msg[2])[2:]
    for device in spec.spec['device']:
        if src[:get_compare_length(device['mask'])].lower() == device['address'][:get_compare_length(device['mask'])].lower():
            return device['name'] if get_compare_length(device['mask']) == 7 else str(device['name']).replace('#',device['address'][get_compare_length(device['mask'])-1:],1)
    return ""


def integrate_septett(frame):
    data = bytearray(b'')
    septet = frame[4]

    for j in range(4):
        if septet & (1 << j):
            data.append(frame[j] | 0x80)
        else:
            data.append(frame[j])
    return data


# Gets the numerical value of a set of bytes (respect Two's complement by value Range)
def gb(data, begin, end):  # GetBytes
    # convert begin and end to int whatever was passed to make enumerate work
    begin = int(begin)
    end = int(end)
    wbg = sum([0xff << (i * 8) for i, b in enumerate(data[begin:end])])
    s = sum([b << (i * 8) for i, b in enumerate(data[begin:end])])
    if s >= wbg/2:
        s = -1 * (wbg - s)
    return s


if __name__ == '__main__':
    if config.connection == "serial":
        sock = serial.Serial(config.port, baudrate=config.baudrate, timeout=0)
    elif config.connection == "lan":
        sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
        sock.connect(config.address)
        login()
    elif config.connection == "stdin":
        sock = sys.stdin
    else:
        sys.exit('Unknown connection type. Please check config.')

    while True:

        result = dict()
        load_data()

        client = mqtt.Client("resol")
   #    client.connect(mqtt_host)
        client.connect("192.168.3.11")

    #print(json.dumps(result))
#    client.publish(mqtt_topic,json.dumps(result))
        client.publish("regumaq",json.dumps(result))

        client.publish("regumaq/temperaturen/S1",str(result["Regumaq-X45"]["Temperatur Sensor 1"].split(" ",1)[0]))
        client.publish("regumaq/temperaturen/s2",str(result["Regumaq-X45"]["Temperatur Sensor 2"].split(" ",1)[0]))
        client.publish("regumaq/temperaturen/s3",str(result["Regumaq-X45"]["Temperatur Sensor 3"].split(" ",1)[0]))
        client.publish("regumaq/temperaturen/s4",str(result["Regumaq-X45"]["Temperatur Sensor 4"].split(" ",1)[0]))
        client.publish("regumaq/temperaturen/s5",str(result["Regumaq-X45"]["Temperatur Sensor 5"].split(" ",1)[0]))
        client.publish("regumaq/temperaturen/s6",str(result["Regumaq-X45"]["Temperatur Sensor 6"].split(" ",1)[0]))
        client.publish("regumaq/temperaturen/s7",str(result["Regumaq-X45"]["Temperatur Sensor 7"].split(" ",1)[0]))
        client.publish("regumaq/temperaturen/s8",str(result["Regumaq-X45"]["Temperatur Sensor 8"].split(" ",1)[0]))
        client.publish("regumaq/volumentstrom/trinkwasser",str(result["Regumaq-X45"]["Durchflusssensor"].split(" ",1)[0]))
        client.publish("regumaq/pwm/pwm1",str(result["Regumaq-X45"]["Spannung PWM 1.1"].split(" ",1)[0]))
        client.publish("regumaq/pwm/pwm2",str(result["Regumaq-X45"]["Spannung PWM 1.2"].split(" ",1)[0]))
        client.publish("regumaq/pwm/pwm3",str(result["Regumaq-X45"]["Spannung PWM 2.1"].split(" ",1)[0]))
        client.publish("regumaq/pwm/pwm4",str(result["Regumaq-X45"]["Spannung PWM 2.2"].split(" ",1)[0]))
        client.publish("regumaq/relais/R1",str(result["Regumaq-X45"]["Relais 1"].split(" ",1)[0]))
        client.publish("regumaq/relais/R2",str(result["Regumaq-X45"]["Relais 2"].split(" ",1)[0]))
        client.publish("regumaq/relais/R3",str(result["Regumaq-X45"]["Relais 3"].split(" ",1)[0]))
        client.publish("regumaq/relais/R4",str(result["Regumaq-X45"]["Relais 4"].split(" ",1)[0]))
        client.publish("regumaq/ausgaenge/A1",str(result["Regumaq-X45"]["Ausgang 1"].split(" ",1)[0]))
        client.publish("regumaq/ausgaenge/A2",str(result["Regumaq-X45"]["Ausgang 2"].split(" ",1)[0]))
        client.publish("regumaq/ausgaenge/A3",str(result["Regumaq-X45"]["Ausgang 3"].split(" ",1)[0]))
        client.publish("regumaq/ausgaenge/A4",str(result["Regumaq-X45"]["Ausgang 4"].split(" ",1)[0]))
        client.publish("regumaq/ausgaenge/LinOut",str(result["Regumaq-X45"]["Lin Out"].split(" ",1)[0]))
        client.publish("regumaq/fehler/s1",str(result["Regumaq-X45"]["Fehler S1"]))
        client.publish("regumaq/fehler/s2",str(result["Regumaq-X45"]["Fehler S2"]))
        client.publish("regumaq/fehler/s3",str(result["Regumaq-X45"]["Fehler S3"]))
        client.publish("regumaq/fehler/s4",str(result["Regumaq-X45"]["Fehler S4"]))
        client.publish("regumaq/fehler/s5",str(result["Regumaq-X45"]["Fehler S5"]))
        client.publish("regumaq/fehler/s6",str(result["Regumaq-X45"]["Fehler S6"]))
        client.publish("regumaq/fehler/s7",str(result["Regumaq-X45"]["Fehler S7"]))
        client.publish("regumaq/fehler/s8",str(result["Regumaq-X45"]["Fehler S8"]))

        client.disconnect()

        time.sleep(10)

    if config.connection == "lan":
        try:
            sock.shutdown(0)
        except:
            pass
    sock.close()
    sock = None

spec/Regumaq-X45.json

Code: Alles auswählen

 
{
  "vbusSpecification": {
    "device": [
      {
        "address": "0x1632",
        "mask": "0xFFFF",
        "name": "Regumaq-X45",
        "isMaster": "true"
      }
    ],
    "packet": [
      {
      "destination": "0x0010",
      "source": "0x1632",
      "command": "0x0100",
      "field": [
        {
          "offset": "0",
          "name": [
            "Temperatur Sensor 1",
            {
              "-lang": "en",
              "#text": "Temperature sensor 1"
            }
          ],
          "bitSize": "15",
          "factor": "0.1",
          "unit": " °C"
        },
        {
          "offset": "2",
          "name": [
            "Temperatur Sensor 2",
            {
              "-lang": "en",
              "#text": "Temperature sensor 2"
            }
          ],
          "bitSize": "15",
          "factor": "0.1",
          "unit": " °C"
        },
        {
          "offset": "4",
          "name": [
            "Temperatur Sensor 3",
            {
              "-lang": "en",
              "#text": "Temperature sensor 3"
            }
          ],
          "bitSize": "15",
          "factor": "0.1",
          "unit": " °C"
        },
        {
          "offset": "6",
          "name": [
            "Temperatur Sensor 4",
            {
              "-lang": "en",
              "#text": "Temperature sensor 4"
            }
          ],
          "bitSize": "15",
          "factor": "0.1",
          "unit": " °C"
        },
        {
          "offset": "8",
          "name": [
            "Temperatur Sensor 5",
            {
              "-lang": "en",
              "#text": "Temperature sensor 5"
            }
          ],
          "bitSize": "15",
          "factor": "0.1",
          "unit": " °C"
        },
        {
          "offset": "10",
          "name": [
            "Temperatur Sensor 6",
            {
              "-lang": "en",
              "#text": "Temperature sensor 6"
            }
          ],
          "bitSize": "15",
          "factor": "0.1",
          "unit": " °C"
        },
        {
          "offset": "12",
          "name": [
            "Temperatur Sensor 7",
            {
              "-lang": "en",
              "#text": "Temperature sensor 7"
            }
          ],
          "bitSize": "15",
          "factor": "0.1",
          "unit": " °C"
        },
        {
          "offset": "14",
          "name": [
            "Temperatur Sensor 8",
            {
              "-lang": "en",
              "#text": "Temperature sensor 8"
            }
          ],
          "bitSize": "15",
          "factor": "0.1",
          "unit": " °C"
        },
        {
          "offset": "16",
          "name": [
            "Spannung PWM 1.1",
            {
              "-lang": "en",
              "#text": "Voltage / GFA 1.1"
            }
          ],
          "bitSize": "32",
          "factor": "0.000001",
          "unit": " V"
        },
        {
          "offset": "20",
          "name": [
            "Spannung PWM 1.2",
            {
              "-lang": "en",
              "#text": "Voltage / GFA 1.2"
            }
          ],
          "bitSize": "32",
          "factor": "0.000001",
          "unit": " V"
        },
        {
          "offset": "24",
          "name": [
            "Spannung PWM 2.1",
            {
              "-lang": "en",
              "#text": "Voltage / GFA 2.1"
            }
          ],
          "bitSize": "32",
          "factor": "0.000001",
          "unit": " V"
        },
        {
          "offset": "28",
          "name": [
            "Spannung PWM 2.2",
            {
              "-lang": "en",
              "#text": "Voltage / GFA 2.2"
            }
          ],
          "bitSize": "32",
          "factor": "0.000001",
          "unit": " V"
        },
        {
          "offset": "32",
          "name": [
            "Durchflusssensor",
            {
              "-lang": "en",
              "#text": "Frequency Sensor"
            }
          ],
          "bitSize": "15",
          "unit": " l/h"
        },
        {
          "offset": "35",
          "name": [
            "Relais 1",
            {
              "-lang": "en",
              "#text": "Relay 1"
            }
          ],
          "bitSize": "8",
          "unit": " %"
        },
        {
          "offset": "36",
          "name": [
            "Relais 2",
            {
              "-lang": "en",
              "#text": "Relay 2"
            }
          ],
          "bitSize": "8",
          "unit": " %"
        },
        {
          "offset": "37",
          "name": [
            "Relais 3",
            {
              "-lang": "en",
              "#text": "Relay 3"
            }
          ],
          "bitSize": "8",
          "unit": " %"
        },
        {
          "offset": "38",
          "name": [
            "Relais 4",
            {
              "-lang": "en",
              "#text": "Relay 4"
            }
          ],
          "bitSize": "8",
          "unit": " %"
        },
        {
          "offset": "39",
          "name": [
            "Relais 5",
            {
              "-lang": "en",
              "#text": "Relay 5"
            }
          ],
          "bitSize": "8",
          "unit": " %"
        },
        {
          "offset": "40",
          "name": [
            "Ausgang 1",
            {
              "-lang": "en",
              "#text": "Output 1"
            }
          ],
          "bitSize": "16",
          "unit": " %"
        },
        {
          "offset": "42",
          "name": [
            "Ausgang 2",
            {
              "-lang": "en",
              "#text": "Output 2"
            }
          ],
          "bitSize": "16",
          "unit": " %"
        },
        {
          "offset": "44",
          "name": [
           "Ausgang 3",
                {
                "-lang": "en",
                "#text": "Output 3"
                }
         ],
          "bitSize": "16",
          "unit": " %"
        },
        {
          "offset": "46",
          "name": [
            "Ausgang 4",
            {
              "-lang": "en",
              "#text": "Output 4"
            }
          ],
          "bitSize": "16",
          "unit": " %"
        },
        {
          "offset": "50",
          "name": [
            "Lin Out",
            {
              "-lang": "en",
              "#text": "Lin Out"
            }
          ],
          "bitSize": "16",
          "factor": "0.1",
          "unit": " %"
        },
        {
          "offset": "60",
          "name": [
            "Fehler",
            {
              "-lang": "en",
              "#text": "Error"
            }
          ],
          "bitSize": "16"
        },
        {
          "offset": "60",
          "name": ["Fehler S1"],
          "bitSize": "1",
          "bitPos": "1"
        },
        {
          "offset": "60",
          "name": ["Fehler S2"],
          "bitSize": "1",
          "bitPos": "2"
        },
        {
          "offset": "60",
          "name": ["Fehler S3"],
          "bitSize": "1",
          "bitPos": "3"
        },
        {
          "offset": "60",
          "name": ["Fehler S4"],
          "bitSize": "1",
          "bitPos": "4"
        },
        {
          "offset": "60",
          "name": ["Fehler S5"],
          "bitSize": "1",
          "bitPos": "5"
        },
        {
          "offset": "60",
          "name": ["Fehler S6"],
          "bitSize": "1",
          "bitPos": "6"
        },
        {
          "offset": "60",
          "name": ["Fehler S7"],
          "bitSize": "1",
          "bitPos": "7"
        },
        {
          "offset": "60",
          "name": ["Fehler S8"],
          "bitSize": "1",
          "bitPos": "8"
        }
      ]
    }
    ]
  }
}

Hier noch der Docker Bauplan für das Image, basierend auf python-slim

die pyhton-scripts sind als persitenters Volume unter /app gemountet

Code: Alles auswählen

CMD ["bash"]
ENV PATH=/usr/local/bin:/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin
ENV LANG=C.UTF-8
RUN set -eux; apt-get update; apt-get install -y --no-install-recommends ca-certificates netbase tzdata ; rm -rf /var/lib/apt/lists/*
ENV GPG_KEY=A035C8C19219BA821ECEA86B64E628F8D684696D
ENV PYTHON_VERSION=3.10.2
RUN set -eux; savedAptMark="$(apt-mark showmanual)"; apt-get update; apt-get install -y --no-install-recommends dpkg-dev gcc gnupg dirmngr libbluetooth-dev libbz2-dev libc6-dev libexpat1-dev libffi-dev libgdbm-dev liblzma-dev libncursesw5-dev libreadline-dev libsqlite3-dev libssl-dev make tk-dev uuid-dev wget xz-utils zlib1g-dev ; wget -O python.tar.xz "https://www.python.org/ftp/python/${PYTHON_VERSION%%[a-z]*}/Python-$PYTHON_VERSION.tar.xz"; wget -O python.tar.xz.asc "https://www.python.org/ftp/python/${PYTHON_VERSION%%[a-z]*}/Python-$PYTHON_VERSION.tar.xz.asc"; GNUPGHOME="$(mktemp -d)"; export GNUPGHOME; gpg --batch --keyserver hkps://keys.openpgp.org --recv-keys "$GPG_KEY"; gpg --batch --verify python.tar.xz.asc python.tar.xz; command -v gpgconf > /dev/null && gpgconf --kill all || :; rm -rf "$GNUPGHOME" python.tar.xz.asc; mkdir -p /usr/src/python; tar --extract --directory /usr/src/python --strip-components=1 --file python.tar.xz; rm python.tar.xz; cd /usr/src/python; gnuArch="$(dpkg-architecture --query DEB_BUILD_GNU_TYPE)"; ./configure --build="$gnuArch" --enable-loadable-sqlite-extensions --enable-optimizations --enable-option-checking=fatal --enable-shared --with-lto --with-system-expat --with-system-ffi --without-ensurepip ; nproc="$(nproc)"; make -j "$nproc" LDFLAGS="-Wl,--strip-all" ; make install; cd /; rm -rf /usr/src/python; find /usr/local -depth \( \( -type d -a \( -name test -o -name tests -o -name idle_test \) \) -o \( -type f -a \( -name '*.pyc' -o -name '*.pyo' -o -name '*.a' \) \) \) -exec rm -rf '{}' + ; ldconfig; apt-mark auto '.*' > /dev/null; apt-mark manual $savedAptMark; find /usr/local -type f -executable -not \( -name '*tkinter*' \) -exec ldd '{}' ';' | awk '/=>/ { print $(NF-1) }' | sort -u | xargs -r dpkg-query --search | cut -d: -f1 | sort -u | xargs -r apt-mark manual ; apt-get purge -y --auto-remove -o APT::AutoRemove::RecommendsImportant=false; rm -rf /var/lib/apt/lists/*; python3 --version
RUN set -eux; for src in idle3 pydoc3 python3 python3-config; do dst="$(echo "$src" | tr -d 3)"; [ -s "/usr/local/bin/$src" ]; [ ! -e "/usr/local/bin/$dst" ]; ln -svT "/usr/local/bin/$src" "/usr/local/bin/$dst"; done
ENV PYTHON_PIP_VERSION=21.2.4
ENV PYTHON_SETUPTOOLS_VERSION=58.1.0
ENV PYTHON_GET_PIP_URL=https://github.com/pypa/get-pip/raw/2caf84b14febcda8077e59e9b8a6ef9a680aa392/public/get-pip.py
ENV PYTHON_GET_PIP_SHA256=7c5239cea323cadae36083079a5ee6b2b3d56f25762a0c060d2867b89e5e06c5	
RUN set -eux; savedAptMark="$(apt-mark showmanual)"; apt-get update; apt-get install -y --no-install-recommends wget; wget -O get-pip.py "$PYTHON_GET_PIP_URL"; echo "$PYTHON_GET_PIP_SHA256 *get-pip.py" | sha256sum -c -; apt-mark auto '.*' > /dev/null; [ -z "$savedAptMark" ] || apt-mark manual $savedAptMark > /dev/null; apt-get purge -y --auto-remove -o APT::AutoRemove::RecommendsImportant=false; rm -rf /var/lib/apt/lists/*; python get-pip.py --disable-pip-version-check --no-cache-dir "pip==$PYTHON_PIP_VERSION" "setuptools==$PYTHON_SETUPTOOLS_VERSION" ; pip --version; find /usr/local -depth \( \( -type d -a \( -name test -o -name tests -o -name idle_test \) \) -o \( -type f -a \( -name '*.pyc' -o -name '*.pyo' \) \) \) -exec rm -rf '{}' + ; rm -f get-pip.py
CMD ["python3"]
VOLUME [/app]
WORKDIR /app
RUN pip install pyserial paho-mqtt
ENTRYPOINT ["/bin/sh" "-c" "python3 /app/resol-vbus-python/resol3mqtt.py\"]"]
timberwolf714 (TWS3500) | VPN offen | Reboot tagsüber erlaubt

StefanW
Elaborated Networks
Reactions:
Beiträge: 7922
Registriert: So Aug 12, 2018 9:27 am
Wohnort: Grafing
Hat sich bedankt: 3899 Mal
Danksagung erhalten: 5445 Mal
Kontaktdaten:

#3

Beitrag von StefanW »

Hallo Moriz,

willkommen hier im Forum und dann gleich so ein Expertenbeitrag. Toll. ich bin begeistert.

ich hoffe mehr von Dir zu lesen.

lg

Stefan
Stefan Werner
Product Owner für Timberwolf Server, 1-Wire und BlitzART der Elaborated Networks GmbH
Bitte immer zuerst im WIKI / Handbuch lesen. Support nur über dieses Forum. Keine PN.
Link zu Impressum und Datenschutzerklärung oben

Ersteller
Mibr85
Reactions:
Beiträge: 146
Registriert: Mo Dez 02, 2019 5:38 am
Hat sich bedankt: 117 Mal
Danksagung erhalten: 45 Mal

#4

Beitrag von Mibr85 »

Vielen Dank Moriz 👍👍👍
Werde mir das bei Gelegenheit mal ansehen.
Welche Werte bekommst du aus der Frischwasserstation?
Kannst du diese auch steuern zB Zirkulationspumpe An/Aus?
Grüße Micha :-)

TWS 2600 #528 + PBM #972,
VPN offen, Reboot nach Rücksprache
PLZ 01...

Ersteller
Mibr85
Reactions:
Beiträge: 146
Registriert: Mo Dez 02, 2019 5:38 am
Hat sich bedankt: 117 Mal
Danksagung erhalten: 45 Mal

#5

Beitrag von Mibr85 »

Kannst du mir dein Docker Container zur Verfügung stellen? Dann muss ich den nur noch auf Viessmann anpassen.
Ich muss bei mir wahrscheinlich einen VBus zu LAN Adapter verwenden da die Heizung zuweist vom TWS entfernt ist.
Grüße Micha :-)

TWS 2600 #528 + PBM #972,
VPN offen, Reboot nach Rücksprache
PLZ 01...

gndlff
Reactions:
Beiträge: 4
Registriert: Do Feb 03, 2022 8:36 pm
Danksagung erhalten: 11 Mal

#6

Beitrag von gndlff »

Leider habe ich auf die Schnelle die Export-Funktion in Portainer nicht gefunden. @StefanW: Gibt es einen Weg, den Container oder das Image aus dem Timberwolf zu exportieren?

Aktuell habe ich bisher nur Lesen implementiert. Ich lesen folgende Werte (wie in Regumaq-X45.json definiert):

- Alle Temperaturen (S1 - S8),
bei mir: Speichertemperatur Oben, Mitte, Unten, Speichervorlauf, Speicherrücklauf, Kaltwassertemperatur, Warmwassertemperatur
- Schaltzustände der Ausgangsrelais (R1-R5),
bei mir: Ladepumpe an, Zirkulation an, Anforderung Zonenladung an WP, Motorkugelhahn Rücklaufeinschichtung
- Fehlermeldungen der Sensoren
- PWM-Ausgangszustände,
bei mir: Drehzahl der Ladepumpe
- Volumenstrom Warmwasser

Zusätzlich wären noch die Systemzeit und die Infos zur thermischen Desinfektion verfügbar, die habe ich bei mir allerdings nicht implementiert, da nicht genutzt.

Laut der Doku von Resol sind für meine Station keine Schreibbefehle vorgesehen. Die Zirkulationspumpe steuere ich über einen KNX-Ausgang an, der auf den Sensor-Eingang der Frischwasserstation für Zirkulationsanforderung wirkt. (Lässt sich im Controller der Frischwasserstation so konfigurieren).

Wenn ich nochmal kaufen würde, würde ich auch das zusätzliche Geld für den VBUS Lan Adapter investieren. Die USB-Verbindung über den Timberwolf in den Docker-Container läuft bei mir nicht immer stabil, sodass gelegentlich ein Neustart des TWS nötig ist, damit das USB-Gerät wieder im Portainer verfügbar ist.
timberwolf714 (TWS3500) | VPN offen | Reboot tagsüber erlaubt

Ersteller
Mibr85
Reactions:
Beiträge: 146
Registriert: Mo Dez 02, 2019 5:38 am
Hat sich bedankt: 117 Mal
Danksagung erhalten: 45 Mal

#7

Beitrag von Mibr85 »

Alles klar, vielen Dank für die Infos 👍
Grüße Micha :-)

TWS 2600 #528 + PBM #972,
VPN offen, Reboot nach Rücksprache
PLZ 01...

Dragonos2000
Reactions:
Beiträge: 2059
Registriert: So Aug 12, 2018 1:38 pm
Wohnort: Karlsruher Raum
Hat sich bedankt: 459 Mal
Danksagung erhalten: 842 Mal

#8

Beitrag von Dragonos2000 »

Hi Micha @Mibr85 ,
ist das Thema bei Dir noch aktuell? Ich hab die letzten Tage auch an meiner Heizung rumgebastelt, bin aber über die Optolink-Schnittstelle gegangen.
Lg
Jochen
____________________________________________________________
TW 2600 #188
VPN offen, Zugriff jederzeit, Experimente jederzeit, Reboot jederzeit

Ersteller
Mibr85
Reactions:
Beiträge: 146
Registriert: Mo Dez 02, 2019 5:38 am
Hat sich bedankt: 117 Mal
Danksagung erhalten: 45 Mal

#9

Beitrag von Mibr85 »

Hallo Jochen
Ja das Thema ist noch aktuell und wird es immer mehr da Viessmann die API immer weiter beschneidet ä z.B. einige Temperaturen in 1K Auflösung statt 0,1K. Das macht die API für zB Warmwassersteuerung unbrauchbar.
Leider hatte ich noch keine Zeit mich damit weiter zu beschäftigen.
Wie hast du es denn gelöst?
Grüße Micha :-)

TWS 2600 #528 + PBM #972,
VPN offen, Reboot nach Rücksprache
PLZ 01...

alexbeer
Reactions:
Beiträge: 364
Registriert: Mi Sep 12, 2018 1:11 am
Wohnort: NRW
Hat sich bedankt: 186 Mal
Danksagung erhalten: 236 Mal

#10

Beitrag von alexbeer »

da Viessmann die API immer weiter beschneidet ä z.B. einige Temperaturen in 1K Auflösung statt 0,1K
für meine Anlage habe ich das Gegenteil - nach Inbetriebnahme in 09/2022 habe ich kürzlich noch weitere Datenpunkte hinzubekommen.
Bei mir werden auch alle Temperaturen mit 0,1K aufgelöst.

Langfristig reizt mich der VBus aber auch 😉
VG Alex
Timberwolf122 (TWS 2500) // Wartungs-VPN: offen // Reboot: jederzeit
Antworten

Zurück zu „Modbus“