MCP23017 chip settings

Thanks very much @Peerke, I’ll give this a try and let you know.

I am using MCP23017 for several months with no issue.
Using “standard” integration included with HA, which unfortunately does not use interrupts so my inputs are read once every second. This is so far OK as the input I have on it are not time critical.
However I am planing some changes and fast reaction for input will be more needed. Also with interrupt sensed by RPi it logically is more “economic” design.

@Jardiamj

  1. can you confirm that your component is working fine with interrupts ?
  2. any hint to install it over the “standard” one ? (should I somehow disable the “standard” one ?)

Thanks

Hi,

1: The int routine is unstable. more details are on the repo. I am also looking into the working of the code vs why the int does not work as stable as one would expect

2: Create the component in the custom_component folder use the config as documented and you are ready to go.

Kind regards

Thank you @Peerke
2: I have created subfolder for MCP23017 integration: \custom_components\mcp23017 and placed files from github repo.
Unfortunately I have the following error in log:

Invalid config for [binary_sensor.mcp23017]: [interrupt_port] is an invalid option for [binary_sensor.mcp23017].Check: binary_sensor.mcp23017->interrupt_port.
(See /config/configuration.yaml, line 120).
Please check the docs at https://www.home-assistant.io/integrations/mcp23017

I wonder how HA knows that it should use custom integration rather then “built-in” ? Especially that the name in the configuration.yaml are the same… ?

EDIT:
After reverting configuration.yaml to previous structure (and with custom component installed) my sensor is working fine. And in the detailed log I have the following entry:

2020-10-14 19:38:44 ERROR (SyncWorker_0) [homeassistant.loader] Error parsing manifest.json file at /config/custom_components/mcp23017/manifest.json: Expecting value: line 6 column 1 (char 5)

Hi @Maco65,

Totally forgot, i changed the manifest.json as some components used where too old.

{
  "domain": "mcp23017",
  "name": "MCP23017 I/O Expander",
  "documentation": "https://www.home-assistant.io/components/mcp23017",
  "requirements": [
    "RPi.GPIO==0.7.0",
    "adafruit-blinka==4.4.0",
    "adafruit-circuitpython-mcp230xx==2.3.1"
    ],
  "dependencies": [],
  "codeowners": ["@jardiamj"]
} 

i use an include for my sensors to keep the configuration.yaml clean

# Binary sensors
binary_sensor: !include binary_sensors.yaml

The in binary_sensors.yaml i have this:
Note that:
active_high is not in the original code. My schematic had no support to change to opendrain so i modified the code of binary_sensor.py to sent some the correct value to the register in the MCP chip. after this it loads correctly. I never new there was a default integration of the MCP23017.

pull_mode DOWN also does not exists, there is no pull-down resistor in the IC, you need to put them in yourselves. So same here i modified the code a bit to discard the ‘UP’ and make sure its not pulled_up at all.

- platform: mcp23017
  pull_mode: DOWN
  invert_logic: false
  interrupt_port: 13
  interrupt_mode: activehigh
  chips:
    - i2c_address: 0x27
      pins:
        0: mcp1-1
        1: mcp1-2
        2: mcp1-3
        3: mcp1-4
        4: mcp1-5
        5: mcp1-6
        6: mcp1-7
        7: mcp1-8
        8: mcp1-9
        9: mcp1-10
        10: mcp1-11
       11: mcp1-12
       12: mcp1-13
        13: mcp1-14
        14: mcp1-15
       15: mcp1-16
    - i2c_address: 0x25
      pins:
        0: mcp2-1
        1: mcp2-2
        2: mcp2-3
        3: mcp2-4
        4: mcp2-5
        5: mcp2-6
        6: mcp2-7
        7: mcp2-8
        8: mcp2-9
        9: mcp2-10
        10: mcp2-11
        11: mcp2-12
        12: mcp2-13
        13: mcp2-14
        14: mcp2-15

Hopes this helps you on your way, it took me hours and hours to figer this one out.
I wonder how your setup runs. I have a PI4 and the mcp23017 registers are not written as expected meaning that the interupt is not configured and therefor does not work.
My guess is that the mcp object within the script is not used correctly messing things up.
I need some time to play with the code and test it out.

Regards,
Peter

Thank you @Peerke !
I have modified manifest.json the same way as you and after that I am able to restart HA with no errors except warning: BinarySensorDevice is deprecated, modify MCP23017BinarySensor to extend BinarySensorEntity - which at this point seem to have no negative effect.
So far my switches seem to work fine and my binary.sensor also works. So this is good starting point. Tomorrow will try to check/measure the response time. But it works without scan_interval: parameter (which is part of default MCP23017 integration).

I have removed interrupt_mode: - probably becasue I did not modify binary_sensor.py
At this point I have pull_mode: DOWN and seems to work fine.

It does help me a lot however what worries me is your statement: :frowning:

If I can help here let me know. Will do some test tomorrow with measurements of interrupt levels.

Yes, there is (Removed integration - Home Assistant) and I was using it so far with no issues (both sensor and switches). The challenge was when I wanted sensors to react instantly rather then 1-1,5 seconds (reality). This “default” integration does not use interrupts and instead you can set “scan_interval” (min. 1s) - in my view this means that default integration can only be used for switches or sensor which can detect state with few seconds delay.

My part of binary.sensor looks like that:

  - platform: mcp23017
    pull_mode: DOWN
    invert_logic: false
    interrupt_port: 23
    chips:
      - i2c_address: 0x20
        pins:
          0: sensA0

Hi @Maco65,

The error is because the this used methode is depricated, it can be found on line 9 of the binary_sensor.py. I made the change localy and was planning (as soon as the int worked as it should) to push an update with all i had done. For now its working, in the future it might not anymore.

The interupt_mode has an default in the binary_sensor.py, its it set as ‘opendrain’.
also does the pull_mode.

If you can provide soem feedback around your findings that would be great.
MY sensors showed up with the correct state, as soon as i change one HA did not respond. After a lot of googling and trying i found out how to alter the code so i had debug level logs and i added some ectra lines. It looked like the routine did a good setup of the i2c chip with the right registers. At the end of the setup i pulled the config out of the chip (at least i think it works like that when i look at the examples provided by adafruit) and the registers where not set at all.
So either my circuit mailfunctions or the routine doen’t do it’s job.

One other thing i first tried is to have 2 chips installed, and 1 of the 2 had correct registers and worked but not all ports corresponded as i expected. So i decided to remove 1 chip and start testing just with 1. and that is where i ended with the log lines that the registers where not set.

So some strange things are going on that i cant get my head around.
One thing i noticed is that in most examples the mcp object is created and then updated. In the chips setup the mcp object is stored in a dict and then pulled out. in don’t see any action after a change to the object and then be overwritten in the dict. Dont kwon yet if that is good or not. My first plan was to always overwirte the mcp object in teh dict after updates on ports or settings or even remove teh use of the dict and recreate the object every time (just to see what happens then).

Hope your findings are fine, that would mean both my chips are fried or the board has some sort of wierd behaviour.

Kind regards,

Peter

Good info @Peerke !
I did not have much time today but done a test with disconnected interrupt from MC chip and RPi (using RPi4B and MCP23017) and binary sensor did not work. Connecting to another INT works fine.
This means to me that interrupt is working OK. Next testing will be to check precisely the speed of reaction as observing it for last 2 days it does not look like faster than “default” integration (appr. 1-2 seconds).

I am also planning connecting second chip to RPi and what you write worries me… When I look at config I wonder if the interrupt_port: is in the right “section”. What I mean is that each MCP chip should be connected to separate RPi GPIO ? If not that means that several MCP chips interrupts connected to one RPi input would result in checking all MCP chips binary sensors. Maybe this is the logic. Or maybe not and this might be a problem you have experienced.

Hi @Maco65,

Good to know it works as expected. Need to look at the i2c bus why its seems unstable.

The interupt works with opendrain, those can be connected together so the Pi reacts to the same RPI port. The code does a scan on both (or better all) chips and updates the state in HA.

The routine to check the interupt flags in each chip is to buggy and therefore the creator choose this way to do it until a more stable reading can be achieved.
I guess it takes longer time to do a full scan then just read te chip and get the updated port.

If the interupt way takes as long as the default then the need for this complexity is not needed.
I wanted the interupt way for the same reason as you, instant reaction in HA on a pin change.

Next steps in testing are positive. Connected PIR sensor which should turn on the lights in the hall. The challenge was that from sensing presence by PIR sensor, light should be turned on instantly. With previous integration, taking sometimes more than 1 second delay it did not work as person would be half way in the hall (or more) when the lights turned on.
With this integration the lights goes on practically instantly! So I am happy !
(Small note, that internal pull-up resistor either does not seem to work or is too small for my PIR sensor or maybe is not enabled ? Anyhow connecting external pull-up resistor does the work and all work fine.)

For interrupt and several chips I understand open drain. So the logic is that in case of interrupt RPi goes to all defined chips and all defined binary sensor to check the state and reflect in HA, correct ?
Based on MCP23017 documentation there is “INTF register, which reflects the interrupt condition on the port pins of any pin that is enabled for interrupts via the GPINTEN register. A ‘set’ bit indicates that the associated pin caused the interrupt.” But using this logic the software should first know which chip is it (than each chip should have it own interrupt connected to RPi) and then would read only the indicated input - this would be super optimal and fastest.

I am trying (with my limited programming knowledge) understand this logic in binary_sensor.py but need some time to do it. The challenge is probably also that some (or most) of the communication between chip and RPi is done in those Adafruit/Arduino components - and this is another step in my education/investigation. :slight_smile:

Yeah, the orriginal code (in a prevoius version) did do that. They had some issues reading the register from the chip and reverted to this method.

The owner is lookig for help to get that sorted. That is why i tried to modify the code to see what is does.
I got stuck at other issues and was put on the wrong track regarding the stabilty, glad that you used teh same code and all is working fine. I will first get my i2c bus to do what it should and then continue on the code.

Since the release of the component some major includes have been upgraded, so maybe it now works as intended. The original lines that use the scan are still in de binary_sensor.py.

There a 2 parts that sort of run side by side, the setup in python and the correlation of the sensors in HA. Then the int-gpio change detection that reads the chip and updates the corresponding sensors in HA.

It’s straight foreward code and with a bt of help from the docs of HA custom components i figured out how to read it and make some changes including the debug line in de HA log to see what the script is doing at what point in time.

If you need help with the code let me know.

OK.
Now not so good news… :frowning:
So far I had 1 input and 9 outputs defined. Few days ago I have added one more input and this created some problems observed on HA card:

  1. This new input (pulled up) did not respond properly - there was delay after connecting to ground.
  2. The state was not changed either way (after putting it on ground or putting high)
  3. I think that also my Outputs has been “toughed” and started to not work as they should.

It could potentially be that the line to this input was long (I mean cable) so putting it to ground could make still some voltage on the input. But I believe I have also done some test exactly on the chip so there was ground for sure and still behaviour was the same.
As I did not have much time to test (the connection I made was sort of emergency) I gave up and went other solution. At this point I have suspicion that more then 1 GPIO defined as input may create a problem for this integration ?
@Peerke - thank you for help in understanding the code, I hope end of this week or next one will have some time to contact you. Should we exchange info here or maybe more “privately”?

On top of interrup I also see a challenge with using “inverted” option for Input. It seems that current integration can only allow to do it for ALL inputs not per individual. When I look for MCP23017 integration with ESPHome it is done they was I would love to see it in HA on RPi, which woudl have possiblity to invert individual inputs and also define each whether it is internally pulled-up or pulled-down.

# Individual inputs
binary_sensor:
  - platform: gpio
    name: "MCP23017 Pin #1"
    pin:
      mcp23017: mcp23017_hub
      # Use pin number 1
      number: 1
      # One of INPUT or INPUT_PULLUP
      mode: INPUT_PULLUP
      inverted: False

Will work on it more as few days ago I got more MC23017 chips which I can do tests with - so also multiple chips on one IC2 line will be tested.

Hi @Maco65,

I sent you a PM.

Interesting find around the 1 input, my chip only serves inputs, i wil see what happens if i just enable 1 in the config of HA.
Also good test to see if i change the rest to output in HA.
Just to see where in the integration stuff breaks.

from the datasheet:
16-Bit Remote Bidirectional I/O Port:

  • I/O pins default to input

Kind regards,
Peter

I will comment the below info from another topic:

Your experience is strange. I have one chip connected to RPi4 since May 2020 and generally it works OK. There is a challenge with response time for binary sensors (discussed above) but other then that it is OK. However it happened 2-3 times during last month that I had to reboot HA server as the switches did not work. The other chip I have is connected to Esp8266 and configured only as switches - did not noticed any issues so far.

@bigu1975 - related to your input in another topic:

it is a very good point on retaining the values during restart and “reseting” the chip in case there is instability. Maybe your script can be added to local (or official) integration to help others ?
I wonder how this is made when MCP23017 is used on ESPHome or TASMOTA ? Particulary on Tasmota there seem to be more options…

To be more clear:
Now the reset of switch states of MCP23017 is doing HA during every restart - this is something, which I don’t like but I can survive :slight_smile:

The critical problem for me is fact that from time to time my switches or/and sensors are become unresponsive after HA restart.

And here is my magic :slight_smile:
Every HA restart I am calling external python “script” (script it is too much said) - few lines to make the init without resetting MCP states:

#!/usr/bin/env python
from IOPi import IOPi

bus = IOPi(0x20)
bus.set_port_direction(0, 0x00)
bus.set_port_direction(1, 0x00)

bus3 = IOPi(0x23)
bus3.set_port_direction(0, 0xFF)
bus3.set_port_pullups(0, 0xFF)
bus3.set_port_direction(1, 0xFF)
bus3.set_port_pullups(1, 0xFF)
bus3.set_interrupt_defaults(0, 0x00)
bus3.set_interrupt_defaults(1, 0x00)
bus3.set_interrupt_type(0, 0x00)
bus3.set_interrupt_type(1, 0x00)
bus3.set_interrupt_on_port(0, 0xFF)
bus3.set_interrupt_on_port(1, 0xFF)
bus3.reset_interrupts()

exit

The hugest added value of my “script” is fact that for sensors I do activate of Interrupts signals connected to RPI GPIO - so having simple automation I can check sensors only on real state change.

And final disclaimer - I am not a programmer, I know that my workaround is very ‘dirty’ solution but only in that way I can work with HA and MCP2017 quite stable.
And as I am not programmer I was not able to make this as Custom Addon so to be able to implement my script I have HA installed in docker.

Hope it helps

I edit this post with the latest results of the various tests.
My HA runs in Hassio in RP4. The MCP23017 integration always works well only with the pins set as inputs (binary_sensor) and therefore does not create any problems for me.
There are problems with the “switch” configuration. In fact, if I reset the chip with the appropriate pin, the functionality of the switches is lost: they do not respond to the command.
Furthermore, if I leave the outputs active, I often find them turned off without HA being, without a power failure or other. It seems that integration is unstable.
To reactivate the switches I must necessarily restart HA.
I ask you if this happens to you too and if you have any suggestions for this problem. I am currently porting the switches to esphome, which is much more stable and reliable, and I only use the integration for inputs. Thanks

I am sorry, but I cannot spare time for this topic at the moment…

is there some progress on this?
I’m using the “standard” integration but i have to restart HA multiple times per day
i have 2 chips: 1 input and 1 output

the error i get is the next:

2021-01-30 07:35:19 ERROR (MainThread) [homeassistant.components.websocket_api.http.connection] [2881418840] [Errno 121] Remote I/O error
Traceback (most recent call last):
File "/usr/src/homeassistant/homeassistant/components/websocket_api/commands.py", line 135, in handle_call_service
await hass.services.async_call(
File "/usr/src/homeassistant/homeassistant/core.py", line 1445, in async_call
task.result()
File "/usr/src/homeassistant/homeassistant/core.py", line 1480, in _execute_service
await handler.job.target(service_call)
File "/usr/src/homeassistant/homeassistant/helpers/entity_component.py", line 204, in handle_service
await self.hass.helpers.service.entity_service_call(
File "/usr/src/homeassistant/homeassistant/helpers/service.py", line 593, in entity_service_call
future.result() # pop exception if have
File "/usr/src/homeassistant/homeassistant/helpers/entity.py", line 664, in async_request_call
await coro
File "/usr/src/homeassistant/homeassistant/helpers/service.py", line 630, in _handle_entity_call
await result
File "/usr/src/homeassistant/homeassistant/helpers/entity.py", line 699, in async_turn_off
await self.hass.async_add_executor_job(ft.partial(self.turn_off, **kwargs))
File "/usr/local/lib/python3.8/concurrent/futures/thread.py", line 57, in run
result = self.fn(*self.args, **self.kwargs)
File "/usr/src/homeassistant/homeassistant/components/mcp23017/switch.py", line 89, in turn_off
self._pin.value = self._invert_logic
File "/usr/local/lib/python3.8/site-packages/adafruit_mcp230xx/digital_inout.py", line 94, in value
self._mcp.gpio = _enable_bit(self._mcp.gpio, self._pin)
File "/usr/local/lib/python3.8/site-packages/adafruit_mcp230xx/mcp23017.py", line 77, in gpio
return self._read_u16le(_MCP23017_GPIOA)
File "/usr/local/lib/python3.8/site-packages/adafruit_mcp230xx/mcp230xx.py", line 56, in _read_u16le
i2c.write_then_readinto(_BUFFER, _BUFFER, out_end=1, in_start=1, in_end=3)
File "/usr/local/lib/python3.8/site-packages/adafruit_bus_device/i2c_device.py", line 124, in write_then_readinto
self.i2c.writeto_then_readfrom(
File "/usr/local/lib/python3.8/site-packages/busio.py", line 133, in writeto_then_readfrom
return self._i2c.writeto_then_readfrom(
File "/usr/local/lib/python3.8/site-packages/adafruit_blinka/microcontroller/generic_linux/i2c.py", line 87, in writeto_then_readfrom
readin = self._i2c_bus.read_i2c_block_data(
File "/usr/local/lib/python3.8/site-packages/Adafruit_PureIO/smbus.py", line 273, in read_i2c_block_data
ioctl(self._device.fileno(), I2C_RDWR, request)
OSError: [Errno 121] Remote I/O error

Hi Niels,

Looks like your error is related to the I2C communication with the MCP’s

I stopped working on it and moved to Arduino as I was getting different behaviour all the time.
My setup now has an Arduino Mega with an ethernetShield, 2 MCP’s, 2 ADS1115 that do a publish to the MQTT broker in HA. All is working fine even the interrupt from the MCP is now working as it should.

From what I have seen in creating the Arduino code is that the MCP’s need specific read order to get the port status and that HA (python containers) are not friends with the I2C communications of the RaspberryPi. I do not have a development setup so for me troubleshooting is hard and time-consuming.