CANBus / Spline / Innoxel interface with HA

Hi

As it might interest other users in Swiss Germany or Germany to unlock their home automation system, here is a link on how to interface Spline / Innoxel / Zidatech systems with Home-Assistant:
https://www.domedia.net/?p=1761&lang=en

Vincèn

2 Likes

Interesting if it’s as easy with car’s canbus. I have esphome device in car for voltage and temperature readings, I’ll order this adapter and update in few weeks.

I don’t think it’s as easy for cars as there are some standardised protocols for cars but you can have a look at that that works (I use it in my own traditional car not electric): WiCAN Pro | Crowd Supply

I have exactly the same Innoxel system, the same age, and the same need to connect it to Home Assistant. Until now, I had solved this via Innoxel Master - RTI XP6s - MQTT Driver - Home Assistant. Your post here is like Christmas for me. I’ve already done a few projects with ESP32, so I immediately ordered the CAN bus module :slight_smile: I hope everything works out. I’ve only skimmed the article, but I’ll try to implement it as soon as possible. Thank you.
Do you speak german?

Happy the article can help :wink: For the interface you can use that that is nicer and better to fit in electrical panel: Industrial ESP32 IoT Programmable Controller | RS485, CAN, Ethernet - DFRobot and it’s also ESP based and same hw for can interface than the one used in article :wink:
I don’t speak german, only english and French !

Thank you very much. I’ll give it a try. What I have in my Innoxel installation is also a weather station plus lots of buttons with status LEDs, IR receivers, and a NoxIn module. Was none of that available when you implemented it during installation? That makes it difficult to completely omit the NoxMaster.

The Inoxel installation we worked on had of course a weather station linked to it through a serial adapter and data is quite easy to read on the CAN bus, same for IR receiver.
For the Noxin module what is it ? have a picture of it or documentation ?

Nice. Its this one. Only Inputs.

Yes, my weather station is also connected to the CAN bus via an RS485 CAN module from Innoxel. I’ll see how far I can get. I’ve just ordered the industrial CAN device you mentioned :slight_smile:

And something like this for every button with a temperature sensor, some with an IR receiver and connected room controller for the heating.

ah yeah the input boxes you can handle them without problem with codes in the blog article :wink:

Yeah exactly same configuration as we had, we handled the buttons without problem. The only thing we didn’t find out at that time was how to convert temperature (sensor returns a weirdo value but should not be a big deal to find out how to “interpret” it :wink:

I am excited and will report back. Is all this information on website from you free of charge, or is there more that can be purchased? I saw that you have a company. If I hadn’t found your information, I would have considered replacing the Noxmaster controller with the latest Innoxel Master 3, which would have had a REST API.

All the information is in the blog post and work has been sponsored by one of our customer here in Switzarland that wanted to find out if it could be interfaced with HA, and eventually in future replace the Inoxel system fully by HA without changing straight all CAN modules. He wanted also to get control on his home and automation system (get rid of the fully closed and over engineered Inoxel system not even speaking overpriced).
If you don’t succeed to get hands on your system or needs assistance, we would be happy to help you as a paying job.

I understand but then it would keep you in a fully closed system that you have no control on it and if you need to interface systems better to interface straight with CAN devices from HA then pass through a closed system out of control :wink:

The DFRobot Edge101 Industrial ESP32 arrived today, which is good. Do you have a device like this too?
I’m not sure about the GPIOS. I have this part for canbus in ESPHome Builder, similar to your example, but with different GPIOS according to the circuit diagram.

As soon as I connect the CAN bus to it, the device freezes :slight_smile: But it’s just enough to display logs from the CAN. Strange.

canbus:
  - platform: esp32_can
    tx_pin: GPIO32
    rx_pin: GPIO35
    can_id: 4
    bit_rate: 100kbps
    use_extended_id: false
    on_frame:   
    - can_id: 0x000
      can_id_mask: 0x000
      then:
      - lambda: |-
          std::string b(x.begin(), x.end());
          ESP_LOGD(“can id 0x42B ”, “%s”, &b[0] );

I think the problem is that too much is happening on the CAN bus and too much has to be logged.

Your code looks all good for me. You’ll get a lot of noise all the time as the Inoxel system is very badly managed and the weather station floods bus permanently (you’ll see in ESPHome logs from time to time a message stating some messages have been lost but it doesn’t impair it to work :wink: To help debug I strongly advise to disconnect temporarily the weather station (you can do that easily on the RS-484/CAN interface). You’ll discover suddenly the bus is lot more quiet :smiley:
Be careful when you connect the dfrobot to the can bus to respect polarity so H on H and L on L or you are going to sort of shortcut the CAN bus.

I found the first address, still with the weather station connected.
This is the status feedback from a NOXin-0800h 8-channel module.
It only works fast enough if you omit logging on the dfrobot edge101.
It works very well, but I don’t know yet if it’s the right thing to do.

Example:

switch:
  - platform: template
    id: innoxel_relais_217_k0
    name: “Innoxel Relay 0x217 Channel 0”
    optimistic: true
    turn_on_action: []
    turn_off_action: []

  - platform: template
    id: innoxel_relais_217_k1
    name: “Innoxel Relay 0x217 Channel 1”
    optimistic: true
    turn_on_action: []
    turn_off_action: []

  - platform: template
    id: innoxel_relay_217_k2
    name: “Innoxel Relay 0x217 Channel 2”
    optimistic: true
    turn_on_action: []
    turn_off_action: []

  - platform: template
    id: innoxel_relay_217_k3
    name: “Innoxel Relay 0x217 Channel 3”
    optimistic: true
    turn_on_action: []
    turn_off_action: []

  - platform: template
    id: innoxel_relay_217_k4
    name: “Innoxel Relay 0x217 Channel 4”
    optimistic: true
    turn_on_action: []
    turn_off_action: []

  - platform: template
    id: innoxel_relais_217_k5
    name: “Innoxel Relay 0x217 Channel 5”
    optimistic: true
    turn_on_action: []
    turn_off_action: []

 - platform: template
    id: innoxel_relay_217_k6
    name: “Innoxel Relay 0x217 Channel 6”
    optimistic: true
    turn_on_action: []
    turn_off_action: []

  - platform: template
    id: innoxel_relay_217_k7
    name: “Innoxel Relay 0x217 Channel 7”
    optimistic: true
    turn_on_action: []
    turn_off_action: []

# CAN bus
canbus:
  - platform: esp32_can
    tx_pin: GPIO32
    rx_pin: GPIO35
    can_id: 4
    bit_rate: 100kbps
    use_extended_id: false
	
	on_frame:
		  - can_id: 0x217
			can_id_mask: 0x7FF
			then:
			  - lambda: |-
				  if (x.size() < 2) return;

				  static uint8_t last_b0 = 0xFF;
				  static uint8_t last_b1 = 0xFF;

				  const uint8_t b0 = x[0];
				  const uint8_t b1 = x[1];

				  if (b0 != 0x03) return;

				  if (b0 == last_b0 && b1 == last_b1) return;
				  last_b0 = b0;
				  last_b1 = b1;

				  const bool k0_on = (b1 & 0x01) != 0;
				  const bool k1_on = (b1 & 0x02) != 0;
				  const bool k2_on = (b1 & 0x04) != 0;
				  const bool k3_on = (b1 & 0x08) != 0;
				  const bool k4_on = (b1 & 0x10) != 0;
				  const bool k5_on = (b1 & 0x20) != 0;
				  const bool k6_on = (b1 & 0x40) != 0;
				  const bool k7_on = (b1 & 0x80) != 0;

				  id(innoxel_relais_217_k0).publish_state(k0_on);
				  id(innoxel_relais_217_k1).publish_state(k1_on);
				  id(innoxel_relais_217_k2).publish_state(k2_on);
				  id(innoxel_relais_217_k3).publish_state(k3_on);
				  id(innoxel_relais_217_k4).publish_state(k4_on);
				  id(innoxel_relais_217_k5).publish_state(k5_on);
				  id(innoxel_relais_217_k6).publish_state(k6_on);
				  id(innoxel_relais_217_k7).publish_state(k7_on);

type or paste code here

I am now able to switch all Innoxel Switch modules with Home Assistant and ESPHome via a DFRobot Edge 101. However, it works differently than in your description.
The CAN addresses of the modules match your description. O1 = 0x203, O2 = 0x205, always with an address in between that is not used. However, these addresses are only for status in my case.

b1 & 0x01 → checks bit 0 (0000 0001) → channel 0
b1 & 0x02 → checks bit 1 (0000 0010) → channel 1
b1 & 0x04 → checks bit 2 (0000 0100) → channel 2
b1 & 0x08 → checks bit 3 (0000 1000) → channel 3
b1 & 0x10 → checks bit 4 (0001 0000) → channel 4
b1 & 0x20 → checks bit 5 (0010 0000) → channel 5
b1 & 0x40 → checks bit 6 (0100 0000) → channel 6
b1 & 0x80 → checks bit 7 (1000 0000) → channel 7

However, a channel of a module is switched via a different address. Switching a channel then works as follows, for example:

- id: o11_k0_toggle
    then:
      - canbus.send: { can_id: 0x588, data: [0x00, 0x0B] }
      - delay: 5ms
      - canbus.send: { can_id: 0x588, data: [0x00, 0x0F] }

However, the address for switching is individual; no pattern can be identified.
Nevertheless, I was able to find them all by looking at what happens around the module status address when switching.

I therefore think that your description of switching using an S and C command works in conjunction with an AMX controller that responds to it,
but not in a pure Innoxel setup. I can see these S and C commands even when I trigger a command directly from the Innoxel programming software,
but when I send it to the module’s status address using ESPHome, it does not respond.

I’m going to take a look at the dimmers now.

Here is the Example YAML file for the DFRobot Edge 101 for ESPHome with LAN and CAN 100kbit, including the status and switch for Innoxel (individual).

esphome:
  name: edge101-can
  friendly_name: Edge101-CAN

  # Beim Boot Schaltbefehle erst nach 10s erlauben
  on_boot:
    priority: -10
    then:
      - delay: 10s
      - lambda: |-
          id(can_commands_enabled) = true;

esp32:
  board: esp32dev
  framework:
    type: arduino

logger:
  level: INFO
  logs:
    canbus: WARN   # internen CAN-Debug-Spam dämpfen

api:
  encryption:
    key: "XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX"

ota:
  - platform: esphome
    password: "XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX"

# ---------- Start-Sperre für Schaltbefehle ----------
globals:
  - id: can_commands_enabled
    type: bool
    restore_value: no
    initial_value: 'false'

# ---------- ETHERNET (Edge101) ----------
ethernet:
  type: IP101
  mdc_pin: GPIO4
  mdio_pin: GPIO13
  clk:
    pin: GPIO0
    mode: CLK_EXT_IN
  phy_addr: 1
  power_pin: GPIO2

# ---------- CAN-Bus ----------
canbus:
  - platform: esp32_can
    tx_pin: GPIO32
    rx_pin: GPIO35
    can_id: 4
    bit_rate: 100kbps
    use_extended_id: false
    
    on_frame:	  
	  # Status-Frames O0 (0x201)
      - can_id: 0x201
        can_id_mask: 0x7FF
        then:
          - lambda: |-
              if (x.size() < 2) return;
              const uint8_t b0 = x[0];
              const uint8_t b1 = x[1];
              if (b0 != 0x03) return;
              id(o0_k0).publish_state(b1 & 0x01);
              id(o0_k1).publish_state(b1 & 0x02);
              id(o0_k2).publish_state(b1 & 0x04);
              id(o0_k3).publish_state(b1 & 0x08);
              id(o0_k4).publish_state(b1 & 0x10);
              id(o0_k5).publish_state(b1 & 0x20);
              id(o0_k6).publish_state(b1 & 0x40);
              id(o0_k7).publish_state(b1 & 0x80);
			  
	  # Status-Frames O10 (0x215)
      - can_id: 0x215
        can_id_mask: 0x7FF
        then:
          - lambda: |-
              if (x.size() < 2) return;
              const uint8_t b0 = x[0];
              const uint8_t b1 = x[1];
              if (b0 != 0x03) return;
              id(o10_k0).publish_state(b1 & 0x01);
              id(o10_k1).publish_state(b1 & 0x02);
              id(o10_k2).publish_state(b1 & 0x04);
              id(o10_k3).publish_state(b1 & 0x08);
              id(o10_k4).publish_state(b1 & 0x10);
              id(o10_k5).publish_state(b1 & 0x20);
              id(o10_k6).publish_state(b1 & 0x40);
              id(o10_k7).publish_state(b1 & 0x80);
	  
# ---------- SCRIPTS: Toggle-Pulse ----------
script:

  # ------- O0 -------
  # o0_k0: (noch kein Schaltframe bekannt)
  
  - id: o0_k1_toggle   
    then:
      - canbus.send: { can_id: 0x59E, data: [0x00, 0x07] }
      - delay: 5ms
      - canbus.send: { can_id: 0x59E, data: [0x00, 0x0F] }

  # o0_k2: (noch kein Schaltframe bekannt)
  
  - id: o0_k3_toggle
    then:
      - canbus.send: { can_id: 0x53A, data: [0x00, 0x0E] }
      - delay: 5ms
      - canbus.send: { can_id: 0x53A, data: [0x00, 0x0F] }

  # o0_k4..k7: (noch kein Schaltframe bekannt)

  # ------- O10 -------
  - id: o10_k0_toggle  
    then:
      - canbus.send: { can_id: 0x52C, data: [0x00, 0x0D] }
      - delay: 5ms
      - canbus.send: { can_id: 0x52C, data: [0x00, 0x0F] }

  - id: o10_k1_toggle  
    then:
      - canbus.send: { can_id: 0x52C, data: [0x00, 0x0B] }
      - delay: 5ms
      - canbus.send: { can_id: 0x52C, data: [0x00, 0x0F] }

  - id: o10_k2_toggle  
    then:
      - canbus.send: { can_id: 0x52C, data: [0x00, 0x07] }
      - delay: 5ms
      - canbus.send: { can_id: 0x52C, data: [0x00, 0x0F] }

  # o10_k3: (noch kein Schaltframe bekannt)

  - id: o10_k4_toggle  
    then:
      - canbus.send: { can_id: 0x52E, data: [0x00, 0x0D] }
      - delay: 5ms
      - canbus.send: { can_id: 0x52E, data: [0x00, 0x0F] }

  # o10_k5: (noch kein Schaltframe bekannt)

  - id: o10_k6_toggle   
    then:
      - canbus.send: { can_id: 0x52E, data: [0x00, 0x0B] }
      - delay: 5ms
      - canbus.send: { can_id: 0x52E, data: [0x00, 0x0F] }

  - id: o10_k7_toggle 
    then:
      - canbus.send: { can_id: 0x52C, data: [0x00, 0x0E] }
      - delay: 5ms
      - canbus.send: { can_id: 0x52C, data: [0x00, 0x0F] }

# ---------- SWITCHES ----------
switch:
# ------- O0 -------
  - platform: template
    id: o0_k0
    name: "o0_k0"
    optimistic: false
    turn_on_action: []
    turn_off_action: []

  - platform: template
    id: o0_k1
    name: "o0_k1"
    optimistic: false
    turn_on_action:
      - if:
          condition:
            lambda: 'return id(can_commands_enabled);'
          then:
            - script.execute: o0_k1_toggle
            - lambda: |-
                id(o0_k1).publish_state(true);
    turn_off_action:
      - if:
          condition:
            lambda: 'return id(can_commands_enabled);'
          then:
            - script.execute: o0_k1_toggle
            - lambda: |-
                id(o0_k1).publish_state(false);

  - platform: template
    id: o0_k2
    name: "o0_k2"
    optimistic: false
    turn_on_action: []
    turn_off_action: []

  - platform: template
    id: o0_k3
    name: "o0_k3"
    optimistic: false
    turn_on_action:
      - if:
          condition:
            lambda: 'return id(can_commands_enabled);'
          then:
            - script.execute: o0_k3_toggle
            - lambda: |-
                id(o0_k3).publish_state(true);
    turn_off_action:
      - if:
          condition:
            lambda: 'return id(can_commands_enabled);'
          then:
            - script.execute: o0_k3_toggle
            - lambda: |-
                id(o0_k3).publish_state(false);

  - platform: template
    id: o0_k4
    name: "o0_k4" # Nur Status
    optimistic: false
    turn_on_action: []
    turn_off_action: []

  - platform: template
    id: o0_k5
    name: "o0_k5" # Nur Status
    optimistic: false
    turn_on_action: []
    turn_off_action: []

  - platform: template
    id: o0_k6
    name: "o0_k6" # Nur Status
    optimistic: false
    turn_on_action: []
    turn_off_action: []

  - platform: template
    id: o0_k7
    name: "o0_k7" # Nur Status
    optimistic: false
    turn_on_action: []
    turn_off_action: []

 # ------- O10 -------
  - platform: template
    id: o10_k0
    name: "o10_k0"
    optimistic: false
    turn_on_action:
      - if:
          condition:
            lambda: 'return id(can_commands_enabled);'
          then:
            - script.execute: o10_k0_toggle
            - lambda: |-
                id(o10_k0).publish_state(true);
    turn_off_action:
      - if:
          condition:
            lambda: 'return id(can_commands_enabled);'
          then:
            - script.execute: o10_k0_toggle
            - lambda: |-
                id(o10_k0).publish_state(false);

  - platform: template
    id: o10_k1
    name: "o10_k1"
    optimistic: false
    turn_on_action:
      - if:
          condition:
            lambda: 'return id(can_commands_enabled);'
          then:
            - script.execute: o10_k1_toggle
            - lambda: |-
                id(o10_k1).publish_state(true);
    turn_off_action:
      - if:
          condition:
            lambda: 'return id(can_commands_enabled);'
          then:
            - script.execute: o10_k1_toggle
            - lambda: |-
                id(o10_k1).publish_state(false);

  - platform: template
    id: o10_k2
    name: "o10_k2"
    optimistic: false
    turn_on_action:
      - if:
          condition:
            lambda: 'return id(can_commands_enabled);'
          then:
            - script.execute: o10_k2_toggle
            - lambda: |-
                id(o10_k2).publish_state(true);
    turn_off_action:
      - if:
          condition:
            lambda: 'return id(can_commands_enabled);'
          then:
            - script.execute: o10_k2_toggle
            - lambda: |-
                id(o10_k2).publish_state(false);

  - platform: template
    id: o10_k3
    name: "o10_k3" # Nur Status 
    optimistic: false
    turn_on_action: []
    turn_off_action: []

  - platform: template
    id: o10_k4
    name: "o10_k4"
    optimistic: false
    turn_on_action:
      - if:
          condition:
            lambda: 'return id(can_commands_enabled);'
          then:
            - script.execute: o10_k4_toggle
            - lambda: |-
                id(o10_k4).publish_state(true);
    turn_off_action:
      - if:
          condition:
            lambda: 'return id(can_commands_enabled);'
          then:
            - script.execute: o10_k4_toggle
            - lambda: |-
                id(o10_k4).publish_state(false);

  - platform: template
    id: o10_k5
    name: "o10_k5" # Nur Status 
    optimistic: false
    turn_on_action: []
    turn_off_action: []

  - platform: template
    id: o10_k6
    name: "o10_k6"
    optimistic: false
    turn_on_action:
      - if:
          condition:
            lambda: 'return id(can_commands_enabled);'
          then:
            - script.execute: o10_k6_toggle
            - lambda: |-
                id(o10_k6).publish_state(true);
    turn_off_action:
      - if:
          condition:
            lambda: 'return id(can_commands_enabled);'
          then:
            - script.execute: o10_k6_toggle
            - lambda: |-
                id(o10_k6).publish_state(false);

  - platform: template
    id: o10_k7
    name: "o10_k7"
    optimistic: false
    turn_on_action:
      - if:
          condition:
            lambda: 'return id(can_commands_enabled);'
          then:
            - script.execute: o10_k7_toggle
            - lambda: |-
                id(o10_k7).publish_state(true);
    turn_off_action:
      - if:
          condition:
            lambda: 'return id(can_commands_enabled);'
          then:
            - script.execute: o10_k7_toggle
            - lambda: |-
                id(o10_k7).publish_state(false);	

Hi

Sorry for the late answer and nice to hear you have been able to do nice progress on your system to take over control with HA.
For code I guess you have done it with some AI as it’s over complicated and quite few non sense stuffs in it :frowning: Why not use the one we supply on the blog ? It’s simple and efficient :slight_smile:

Vincèn

Hi Tinu1, I’m Clément, i worked with Vincèn decoding the can bus commands and I wrote a large part of esphome sketch in Vincèn’s blog article.
To help you efficiently, can you show the logs of your DFrobot so we can see together what go through the Can bus ?
I also have to know witch Inoxel device your are working on. Input module ? Relay module ? What is your goal (command via HA ? Feedback ? Control everything via HA or keeping Inoxel gateway by side ?).
Inoxel syteme may have few different command in the bus but it don’t think they are different regarding the gateway (innoxel or amx). The commands we suggest you in Vincèn blog should work everywhere. Especially if you saw them in the logs of your DFrobot.

Yes, that’s right, I created it together with AI. Everything is now working (dimmers, blinds, relays), although perhaps not quite as you described. Your example: Control of ON/OFF circuits, where you send an S6 for turn_on_action and a C6 for turn_off_action to 0x21D. I also have a NoxSwitch-0806 at address 0x21D. When I use the example with S6 and C6, nothing happens at all. Instead, I have to send this, which works:

  • canbus.send: { can_id: 0x554, data: [0x00, 0x0E] }
  • delay: 5ms
  • canbus.send: { can_id: 0x554, data: [0x00, 0x0F] }.
    I’m not sure why yet. Maybe I’m using the wrong address.
    However, I can get the status of the switched output at 0x21D.