UVR16x2 CAN Bus

Der chaotische Hauptfaden

Moderatoren: Heaterman, Finger, Sven, TDI, Marsupilami72, duese

Antworten
Benutzeravatar
Tjareson
Beiträge: 362
Registriert: Fr 3. Jul 2015, 05:01

UVR16x2 CAN Bus

Beitrag von Tjareson »

Ich habe aus dem UVR-Thema aus viewtopic.php?t=14374&start=75 mal einen eigenen thread gemacht, da es ggf. für andere UVR16x2 Nutzer auch interessant sein könnte. Ausser mir, scheint es da ja noch den einen oder anderen zu geben...
Hightech hat geschrieben: Di 19. Dez 2023, 21:31 Moin,
hat schon mal jemand an den CAN-Bus der UVR16x2 was dran gehäkelt, mir gehen die Ein- und Ausgänge aus und den DL-Bus hab ich schon voll.
Kann man da so CAN-BuS Koppler nehmen und wie werden die konfiguriert?
Hoffe, das ist nicht zu softwarelastig, aber irgendwie finde ich ansonsten auch nicht viel im Netz dazu. Die fhem-Lösung erfordert auch das CMI, sofern ich das richtig verstanden habe. Das ist mir allein dazu aber zu teuer.
Ich habe mich mal gestern abend zusammen mit chatGPT etwas an die Analyse von dem CAN Bus der UVR gemacht und bin dabei in recht kurzer Zeit ein Stück weiter gekommen.
Ich nutze übrigens den CAN-Adapter USBtin. Der ist relativ teuer, aber irgendwie scheint das die "amtliche Version" zu sein und es gibt auch ein bißchen Software dazu. :?
Meine UVR (derzeit übrigens noch nicht verbaut) läuft als Node 1, mit 50.000 baud auf'm bus.
Ich habe 2 PT1000 (an Eingang 1 und 2) und 2 Relais definiert (Ausgang 1 und 5).
Dann habe ich auf dem CAN-Bus der UVR testweise bei den Analogausgängen den Kanal 1 mit dem ersten Temperatursensor und den Kanal 9 mit dem zweiten Temperatursensor belegt.
Bei den digitalen Ausgängen habe ich ein Relais auf Kanal 1 und das zweite auf Kanal 17 gelegt.

Daraufhin sendet die UVR schon mal 3 zusätzliche IDs: 513 und 769 (darin stehen die Temperaturen für Tempsensor 1 und 2 auf den Kanälen 1 und 9) In der payload stehen jeweils 4 bytes für die Temperatur als Zehntelgrad.

Code: Alles auswählen

Raw Message: ID: 449, Data: bytearray(b'\x01\x06++++++')
Raw Message: ID: 513, Data: bytearray(b'\xf0\x00\x00\x00\x00\x00\x00\x00')
Analog Channel 1: 24.0°C
Raw Message: ID: 1793, Data: bytearray(b'\x05')
Raw Message: ID: 1793, Data: bytearray(b'\x05\x87\x01\x00\x9d\x9b\x9d\x91')
Raw Message: ID: 449, Data: bytearray(b'\x01\x08++++++')
Raw Message: ID: 769, Data: bytearray(b'\xe5\x00\x00\x00\x00\x00\x00\x00')
Analog Channel 9: 22.9°C
Bei den Digitalausgängen ist es etwas anders, da hier weniger Daten notwendig sind. Auch wenn ich das zweite Relais auf den digitalen CAN Bus Ausgang 17 gelegt habe, passt alles noch in die Payload der dritten ID385

Code: Alles auswählen

Raw Message: ID: 385, Data: bytearray(b'\x01\x00\x00\x00\x00\x00\x00\x00')
Digital Channel 1: ON
Raw Message: ID: 1793, Data: bytearray(b'\x05')
Raw Message: ID: 1793, Data: bytearray(b'\x05\x87\x01\x00\x9d\x9b\x9d\x91')
Raw Message: ID: 1793, Data: bytearray(b'\x05')
Raw Message: ID: 1793, Data: bytearray(b'\x05\x87\x01\x00\x9d\x9b\x9d\x91')
Raw Message: ID: 449, Data: bytearray(b'\x01\x00\x01\x00\x00\x00\x00\x00')
Raw Message: ID: 385, Data: bytearray(b'\x00\x00\x00\x00\x00\x00\x00\x00')
Digital Channel 1: OFF
Raw Message: ID: 385, Data: bytearray(b'\x00\x00\x01\x00\x00\x00\x00\x00')
Digital Channel 17: ON
Raw Message: ID: 1793, Data: bytearray(b'\x05')
Raw Message: ID: 1793, Data: bytearray(b'\x05\x87\x01\x00\x9d\x9b\x9d\x91')
Raw Message: ID: 449, Data: bytearray(b'\x01\x01\x00\x00\x01\x00\x00\x00')
Raw Message: ID: 385, Data: bytearray(b'\x00\x00\x00\x00\x00\x00\x00\x00')
Digital Channel 17: OFF
Was mich interessiert: kann mal jemand anderes ein Relaisausgang auf einen CAN-Ausgang bei seiner UVR legen und sehen, ob die dann durch UVR gesendet ID auf dem CAN Bus auch die 385 ist?
Ich habe chatGPT dann gebeten, mir das Python script so zu schreiben, dass ich die CAN-Bus IDs für analoge und digitale Werte festlegen kann. Vermutlich kann man das auch direkt an der Payload der ID 449 erkennen, aber da habe ich bisher nichts schlüssiges erkennen können. Das Script hat auch noch den Fehler, das nur Temperaturen ungleich 0 gültig sind, das muss ich noch ändern. Ungültige Werte sind da glaube ich 9999 oder sowas.

Das python script anbei. Falls das mal jemand mit seiner UVR ausprobieren und mitteilen kann, was da für IDs gesendet werden, wäre das ganz aufschlussreich.

Code: Alles auswählen

import can

def decode_temperature(data):
    """Decode temperature from CAN data."""
    temp_raw = int.from_bytes(data, byteorder='little')
    return temp_raw / 10

def decode_digital(data):
    """Decode digital data from CAN data."""
    states = []
    for byte in data[:4]:  # Limit to the first 4 bytes (32 channels)
        for bit in range(8):
            states.append(bool(byte & (1 << bit)))
    return states

def main():
    # Configurable section for analog temperature data IDs
    analog_temp_ids = [513, 769]  # Each ID corresponds to a block of 8 channels

    # Configurable section for digital data IDs
    digital_ids = [385]

    # Initial states for digital channels
    last_digital_states = [False] * 32

    # Configure the connection to the CAN interface
    bus = can.interface.Bus(bustype='slcan', channel='/dev/ttyACM0', bitrate=50000)

    try:
        print("Listening for CAN messages...")
        while True:
            message = bus.recv()  # Block and wait for a message

            if message is not None:
                print(f"Raw Message: ID: {message.arbitration_id}, Data: {message.data}")

                # Handle analog temperature data
                if message.arbitration_id in analog_temp_ids:
                    base_channel_offset = 8 * analog_temp_ids.index(message.arbitration_id)
                    for i in range(0, len(message.data), 4):
                        temp = decode_temperature(message.data[i:i+4])
                        if temp != 0:  # Assuming 0 is not a valid temperature
                            channel = base_channel_offset + (i // 4) + 1
                            print(f"Analog Channel {channel}: {temp}°C")

                # Handle digital data
                elif message.arbitration_id in digital_ids:
                    states = decode_digital(message.data)
                    for i, state in enumerate(states):
                        if i < len(last_digital_states):
                            if state != last_digital_states[i]:
                                last_digital_states[i] = state
                                print(f"Digital Channel {i + 1}: {'ON' if state else 'OFF'}")

    except KeyboardInterrupt:
        print("Interrupted, closing connection.")
        bus.shutdown()

if __name__ == "__main__":
    main()
Was der nächste Schritt ist, wäre dann Daten an die UVR zu senden, in dem man einfach vorgibt eine zweite UVR zu sein und Daten im gleichen Format aber mit anderer Node-ID sendet. Dann könnte man die UVR auch von aussen einstellen. Ich bin mir nur nicht sicher, wie sich die IDs ergeben. Gerade bei den beiden Analogwerten ist da ein ziemlicher Sprung dazwischen, der irgendwie nach zufälliger Zuweisung aussieht. Und dann ist da noch die ID449, welche irgendwie 3 mal gesendet wird, mit unterschiedlichen Payloads, die irgendwie im Zusammenhang mit den genutzten digitalen und analogen CAN-Kanälen stehen.

Noch ein Nachtrag: es hat sich herausgestellt, dass es bei Änderungen der CAN-Bus Ausgänge es sinnvoll sein kann, dass man die UVR danach neu startet. Zumindest wenn ich Ausgänge am CAN-Bus wieder auf unbenutzt stelle, sendet die UVR danach ab und an Datenmüll auf dem CAN. Nach Neustart ist dann aber alles wieder entsprechend der Config.
Benutzeravatar
Tjareson
Beiträge: 362
Registriert: Fr 3. Jul 2015, 05:01

Re: UVR16x2 CAN Bus

Beitrag von Tjareson »

...anbei ein paar Korrekturen. Temperaturen sind nur als 2 Byte-Werte geliefert, wie es aussieht.
Ausserdem scheint diese CAN-ID (arbitration ID) aus Node ID (hier 1 in der UVR konfiguriert) und Function Code zusammengesetzt zu sein. Dann sind die CAN-IDs auch nicht mehr ganz so erratisch in ihren Abständen.
Nur senden an die UVR scheint noch schwieriger zu sein. Einfach ein "Replay" der CAN-Bus Nachrichten der UVR mit neuer Node-ID reicht nicht - man sieht nix auf den CAN-Eingängen.

Code: Alles auswählen

import can

def decode_temperature(data):
    """Decode temperature from CAN data assuming 2 bytes per reading."""
    temp_raw = int.from_bytes(data, byteorder='little', signed=True)
    return temp_raw / 10

def decode_digital(data):
    """Decode digital data from CAN data."""
    states = []
    for byte in data[:4]:  # Limit to the first 4 bytes (32 channels)
        for bit in range(8):
            states.append(bool(byte & (1 << bit)))
    return states

def extract_node_id(arbitration_id):
    """Extract node ID from arbitration ID."""
    return arbitration_id & 0x7F

def extract_function_code(arbitration_id):
    """Extract function code from arbitration ID."""
    return arbitration_id >> 7

def main():
    analog_temp_ids = [513, 769]  # Each ID corresponds to a block of 8 channels
    digital_ids = [385]
    last_digital_states = [None] * 32  # Initialize with None to track changes

    bus = can.interface.Bus(bustype='slcan', channel='/dev/ttyACM0', bitrate=50000)

    try:
        print("Listening for CAN messages...")
        while True:
            message = bus.recv()

            if message is not None:
                node_id = extract_node_id(message.arbitration_id)
                function_code = extract_function_code(message.arbitration_id)
                byte_values = [f"{b:02x}" for b in message.data]  # Convert each byte to hex string
                print(f"Raw Message: ID: {message.arbitration_id} (Node ID: {node_id}, Function Code: {function_code}), Data: {byte_values}")

                if message.arbitration_id in analog_temp_ids:
                    base_channel_offset = 8 * analog_temp_ids.index(message.arbitration_id)
                    for i in range(0, len(message.data), 2):
                        temp = decode_temperature(message.data[i:i+2])
                        if temp != 0:  # Assuming 0 is not a valid temperature
                            channel = base_channel_offset + (i // 2) + 1
                            print(f"Analog Channel {channel}: {temp}°C")
                
                elif message.arbitration_id in digital_ids:
                    states = decode_digital(message.data)
                    for i, state in enumerate(states):
                        if last_digital_states[i] is not None and state != last_digital_states[i]:
                            print(f"Digital Channel {i + 1}: {'ON' if state else 'OFF'}")
                        last_digital_states[i] = state

    except KeyboardInterrupt:
        print("Interrupted, closing connection.")
        bus.shutdown()

if __name__ == "__main__":
    main()
jodurino
Beiträge: 2109
Registriert: So 17. Nov 2013, 20:43

Re: UVR16x2 CAN Bus

Beitrag von jodurino »

Hi
geben die in der Dokumentation den kein .dbc File oder wenigstens eine Excel Liste mit den Adressen mit?

Wenn Du alles per Hand raussuchen musst und du keine weiteren CAN Geräte hast ist es ja einfacher einen eigenen Prozessor zu nehmen und die Steuerung selbst zu bauen

cu
jodurino
Benutzeravatar
Tjareson
Beiträge: 362
Registriert: Fr 3. Jul 2015, 05:01

Re: UVR16x2 CAN Bus

Beitrag von Tjareson »

Das ist sowieso einfacher, aber halt kein Standard, den noch irgendjemand anders warten könnte, falls man mal ausfällt.
jodurino
Beiträge: 2109
Registriert: So 17. Nov 2013, 20:43

Re: UVR16x2 CAN Bus

Beitrag von jodurino »

Tjareson hat geschrieben: Sa 30. Dez 2023, 14:41 Das ist sowieso einfacher, aber halt kein Standard, den noch irgendjemand anders warten könnte, falls man mal ausfällt.
Ok solche Gedanken habe ich da gar nicht. Bin ich zu egoistisch für, oder sehr egal mir.

Aber ist Deine Lösung nicht dann auch so speziell das ein anderer gar nicht was ändern könnte?
egal ist offtopic
Benutzeravatar
Tjareson
Beiträge: 362
Registriert: Fr 3. Jul 2015, 05:01

Re: UVR16x2 CAN Bus

Beitrag von Tjareson »

jodurino hat geschrieben: Sa 30. Dez 2023, 14:54 Ok solche Gedanken habe ich da gar nicht. Bin ich zu egoistisch für, oder sehr egal mir.
Aber ist Deine Lösung nicht dann auch so speziell das ein anderer gar nicht was ändern könnte?
Nein, wir wollen schon sicherstellen, dass es die Trauergesellschaft schön warm hat, während ich von der Straße gekratzt werde oder was auch immer... :D

Aber im Ernst: klar, dass Hausautomation dann über die UVR in Sachen Einzelraumanforderung usw. hinausgeht und damit wieder spezieller wird - aber genau das kann ich an der UVR abschaltbar machen, wodurch es sozusagen auf einfachen Heizungsbetrieb zurückfällt. Die Kesselsteuerung ist auch das einzige Ersatzteil was mir betreffend meiner Ölheizung noch fehlt. Die notwendige Eurocontrol KO findet sich bei Hausnummern zwischen 200-400 Euro in etwas dubiosem "generalüberholten" Zustand im Netz. Da kann ich dann auch gleich was neues Einbauen, was dazu noch einen etwas besseren Bastelfaktor hat. Ich möchte sowas auch nicht erst lösen, wenn das Ding ausgefallen ist - was, wenn, dann natürlich im Winter passieren wird.
runni
Beiträge: 114
Registriert: Do 18. Okt 2018, 09:54
Wohnort: München

Re: UVR16x2 CAN Bus

Beitrag von runni »

Wieso eine extra Kesselsteuerung? Die UVR macht genau das bei mir.
Benutzeravatar
sukram
Beiträge: 3119
Registriert: Sa 10. Mär 2018, 18:27
Wohnort: Leibzsch

Re: UVR16x2 CAN Bus

Beitrag von sukram »

Was ich mich gerade wundere: Kann die UVR nicht auch Modbus? Darüber sollten sich doch beliebig viele I/O z.b. mit fertigen Feldbuskopplern (Wago/Beckhoff z.b.) anschließen lassen.

Oder spricht der sogar CanOpen auf dem Interface? Dafür gibt es auch passende Koppler...
Benutzeravatar
Mechatronk
Beiträge: 452
Registriert: So 28. Feb 2021, 23:47

Re: UVR16x2 CAN Bus

Beitrag von Mechatronk »

Meine Strategie: Gebrauchte UVR1610/11 Sonstwas für wenig geld bei den kleinanzeigen kaufen und als Netzwerkein/Ausgang nutzen
Konsole
Beiträge: 1125
Registriert: Fr 16. Aug 2019, 20:52

Re: UVR16x2 CAN Bus

Beitrag von Konsole »

Es ist zwar keine UVR, die Meßwerte sendet, aber würde Dir ein leihweise zur Verfügung gestelltes CMI helfen?

Ich bin grade dabei, unsere Anlage von einer UVR1611 auf eine x2 zu migrieren, aber das geht alles ned so schnell wie geplant.

Beim Rest kann ich Dir leider nicht helfen, damit CAN ich mich nicht aus ;)
Benutzeravatar
Hightech
Beiträge: 11497
Registriert: So 11. Aug 2013, 18:37

Re: UVR16x2 CAN Bus

Beitrag von Hightech »

Ich hab ein Komplett System mit UVR und CMI
Das ist in Betrieb und muss nur mal einen Adapter dran häkeln.
Benutzeravatar
Tjareson
Beiträge: 362
Registriert: Fr 3. Jul 2015, 05:01

Re: UVR16x2 CAN Bus

Beitrag von Tjareson »

So, nur um das Thema noch abzuschließen und zur Doku, falls es jemand anderes auch noch suchen sollte: Ansteuerung der UVR16x2 mit einfachem USB CAN-Adapter (ohne CMI!) geht jetzt auch.
Ansteuerung heisst in meinem Fall: Setzen der 16 digitalen und analogen Eingänge auf dem CAN-Bus, so daß man von "aussen" in das Verhalten eingreifen kann.
Da HomeAutomation bei mir daheim mit NodeRed läuft, habe ich dazu etwas JavaScript für diese Umgebung zusammengebastelt und in einem Bsp. Flow eingebettet:
UVR-NodeRed.png
Versand der CAN Nachrichten erfolgt mit cansend tool unter linux. Es gibt dazu auch auch ein paar direkte CAN-Nodes die man in NodeRed installieren kann, aber da erst rauszufinden, welche Parameter die jeweilige Lösung braucht, war mir zu umständlich. Im Zweifel ist Linux CLI sowieso langlebiger.
Falls der USB-Can-Adapter auf /dev/ttyACM0 läuft sieht ein mögliches Initialisierungsscript dazu so aus:

Code: Alles auswählen

#!/bin/bash
modprobe can
modprobe can-raw
modprobe slcan
apt-get install -y can-utils net-tools
slcan_attach -f -s2 -o /dev/ttyACM0 
slcand ttyACM0 slcan0
ifconfig slcan0 up
Der JavaScipt-Node nimmt als msg.topic A1-A16 (UVR Analogeingänge) oder D1-D16 (UVR Digitaleingänge) und als msg.payload dann entsprechend einen Temperaturwert oder 0/1 für digital.
Anbei mal die Node-Red-Flow-Daten zum Import.
Vielleicht kann es ausser mir sonst noch jemand gebrauchen.

Code: Alles auswählen

[
    {
        "id": "e8b2614ac141d1fb",
        "type": "function",
        "z": "a3706963805671b4",
        "name": "UVR16x2",
        "func": "const NODE_ID = 2; // Node ID\nconst CAN_INTERFACE = 'slcan0'; // CAN interface\n\n// Function to create a digital message\nfunction createDigitalMessage(channel, state) {\n    const arbitrationId = 0x180 + NODE_ID;\n    const data = Buffer.alloc(8);\n    if (state) {\n        data[Math.floor((channel - 1) / 8)] |= 1 << ((channel - 1) % 8);\n    }\n    return { arbitrationId, data };\n}\n\n// Function to create an analog message\nfunction createAnalogMessage(channel, temperature) {\n    const offset = 0x200 + (Math.floor((channel - 1) / 4) * 0x80);\n    const arbitrationId = offset + NODE_ID;\n    const data = Buffer.alloc(8);\n    const channelIndex = (channel - 1) % 4;\n    const tempRaw = Math.round(temperature * 10); // Convert temperature to tenths of degrees\n    data.writeUInt16LE(tempRaw, channelIndex * 2);\n    return { arbitrationId, data };\n}\n\n// Function to prepare a CAN message for the exec node\nfunction prepareCanMessage(arbitrationId, data) {\n    const canId = arbitrationId.toString(16).padStart(3, '0');\n    const dataHex = data.toString('hex');\n    const command = ` ${CAN_INTERFACE} ${canId}#${dataHex}`;\n    return { payload: command };\n}\n\n// Initialize or retrieve global arrays\nconst UVRdigital = global.get('UVRdigital') || new Array(16).fill(0);\nconst UVRanalog = global.get('UVRanalog') || new Array(16).fill(0.0);\n\n// Process incoming message\nconst topic = msg.topic.toUpperCase();\nconst payload = msg.payload;\nconst channelMatch = topic.match(/([DA])(\\d+)/);\nif (channelMatch) {\n    const type = channelMatch[1];\n    const channel = parseInt(channelMatch[2], 10);\n\n    if (type === 'D' && channel >= 1 && channel <= 16) {\n        UVRdigital[channel - 1] = payload ? 1 : 0;\n        global.set('UVRdigital', UVRdigital);\n        // Prepare and send updated digital message\n        const message = createDigitalMessage(channel, UVRdigital[channel - 1]);\n        const canMsg = prepareCanMessage(message.arbitrationId, message.data);\n        node.send(canMsg);\n    } else if (type === 'A' && channel >= 1 && channel <= 16) {\n        UVRanalog[channel - 1] = parseFloat(payload);\n        global.set('UVRanalog', UVRanalog);\n        // Prepare and send updated analog message\n        const message = createAnalogMessage(channel, UVRanalog[channel - 1]);\n        const canMsg = prepareCanMessage(message.arbitrationId, message.data);\n        node.send(canMsg);\n    }\n}\n",
        "outputs": 1,
        "timeout": 0,
        "noerr": 0,
        "initialize": "",
        "finalize": "",
        "libs": [],
        "x": 480,
        "y": 240,
        "wires": [
            [
                "cb92b3eec0e9bd8d"
            ]
        ]
    },
    {
        "id": "5d6685a392cbc0f9",
        "type": "inject",
        "z": "a3706963805671b4",
        "name": "",
        "props": [
            {
                "p": "payload"
            },
            {
                "p": "topic",
                "vt": "str"
            }
        ],
        "repeat": "",
        "crontab": "",
        "once": false,
        "onceDelay": 0.1,
        "topic": "D2",
        "payload": "1",
        "payloadType": "num",
        "x": 230,
        "y": 240,
        "wires": [
            [
                "e8b2614ac141d1fb"
            ]
        ]
    },
    {
        "id": "3bf0d7dba394e16f",
        "type": "inject",
        "z": "a3706963805671b4",
        "name": "",
        "props": [
            {
                "p": "payload"
            },
            {
                "p": "topic",
                "vt": "str"
            }
        ],
        "repeat": "",
        "crontab": "",
        "once": false,
        "onceDelay": 0.1,
        "topic": "D2",
        "payload": "0",
        "payloadType": "num",
        "x": 230,
        "y": 280,
        "wires": [
            [
                "e8b2614ac141d1fb"
            ]
        ]
    },
    {
        "id": "cb92b3eec0e9bd8d",
        "type": "exec",
        "z": "a3706963805671b4",
        "command": "cansend",
        "addpay": "payload",
        "append": "",
        "useSpawn": "false",
        "timer": "",
        "winHide": false,
        "oldrc": false,
        "name": "",
        "x": 720,
        "y": 240,
        "wires": [
            [],
            [],
            []
        ]
    },
    {
        "id": "73ba05e9ce71ead4",
        "type": "inject",
        "z": "a3706963805671b4",
        "name": "",
        "props": [
            {
                "p": "payload"
            },
            {
                "p": "topic",
                "vt": "str"
            }
        ],
        "repeat": "",
        "crontab": "",
        "once": false,
        "onceDelay": 0.1,
        "topic": "A1",
        "payload": "10",
        "payloadType": "num",
        "x": 230,
        "y": 360,
        "wires": [
            [
                "e8b2614ac141d1fb"
            ]
        ]
    },
    {
        "id": "e94efee9d5557a42",
        "type": "inject",
        "z": "a3706963805671b4",
        "name": "",
        "props": [
            {
                "p": "payload"
            },
            {
                "p": "topic",
                "vt": "str"
            }
        ],
        "repeat": "",
        "crontab": "",
        "once": false,
        "onceDelay": 0.1,
        "topic": "A1",
        "payload": "20",
        "payloadType": "num",
        "x": 230,
        "y": 400,
        "wires": [
            [
                "e8b2614ac141d1fb"
            ]
        ]
    }
]
Konsole hat geschrieben: So 31. Dez 2023, 12:54 Es ist zwar keine UVR, die Meßwerte sendet, aber würde Dir ein leihweise zur Verfügung gestelltes CMI helfen?
Und Danke für das Angebot! :-)
Antworten