ESPHome & Input Shift Registers

I started a project to control turning on and off 12 relays with an ESP32 & 2 shift registers (74HC595). I used the SN74HC595 component in ESPHome to make that happen, and it works great. In HA, I have the 12 switches controlling the relays - exactly what I wanted.

Now, I want to add physical buttons to the ESP32 to turn on/off those relays. The plan was to have 12 buttons - 1 for each relay, and I was hoping to use shift registers on the button input like I did with the output. From what I can tell, the 74HC595 isn’t the right component for that, and what I really need are 74HC165 shift registers.

Been poking around for a couple days, but haven’t found the right solution. I do have enough GPIOs on the ESP board where I could just wire them directly to the board, but where’s the fun in that?

Is it possible to use a 74HC595 as the shift register to accept the button inputs? I know I need to create a separate “hub” for the inputs, but it doesn’t look like the 74HC595 is the right component from what I’ve researched.

I don’t see a pre-built component for 74HC165 shift registers - anyone have any experience in using them with an ESP32 board?

Or, is there another component that I should be using for my use case?

Well today is your lucky day my friend , because after days of looking all over the internet and not finding anything , I just wrote and tested the code for custom 74HC165 binary sensors to use in my own project today and was going to share it in this site so that other people can use it in theirs works and check for any possible bugs , so I can fix them :sweat_smile:
So for adding a custom component to ESPHome , first you should add a file called “74HC165.h” to ESPHome configuration directory by File editor ADD-ON , then save this code inside that file :

#include "esphome.h"

class SN74HC165Component : public PollingComponent, public BinarySensor {
  byte pinNum;
  private:
    const byte LATCH = 12;  // ESP pin number that should be connected to pin 1 of 74hc165s
    const byte DATA  = 39;  // ESP pin number that should be connected to pin 9 of first 74hc165
    const byte CLOCK = 16; // ESP pin number that should be connected to pin 2 of 74hc165s
    const byte NUMBER_OF_CHIPS = 2; // Total number of daisy-chained 74HC165s

  bool getBit(unsigned long x, byte n) {
    bool value = bitRead(x, n);
    return value;
  }
  
  unsigned long shiftinput= 1;
  unsigned long oldshiftinput= 0;

 public:
  SN74HC165Component (byte pin) : PollingComponent(100) { pinNum = pin; }   // Change the number inside "PollingComponent(100)" to change update interval in miliseconds 
  float get_setup_priority() const override { return esphome::setup_priority::HARDWARE; }

  void setup() override {
    pinMode(LATCH, OUTPUT); 
    pinMode(CLOCK, OUTPUT); 
    pinMode(DATA, INPUT); 
    digitalWrite(CLOCK, LOW); 
    digitalWrite(LATCH, HIGH);
  }

  void update() override {
    digitalWrite(CLOCK, LOW); 
    digitalWrite(LATCH, LOW); 
    digitalWrite(LATCH, HIGH); 

    shiftinput = shiftIn(DATA, CLOCK, MSBFIRST);
    if (NUMBER_OF_CHIPS > 1) { 
      shiftinput |= shiftIn(DATA, CLOCK, MSBFIRST) << 8;
    }
    if (NUMBER_OF_CHIPS > 2) { 
      shiftinput |= shiftIn(DATA, CLOCK, MSBFIRST) << 16;
    }
    if (NUMBER_OF_CHIPS > 3) { 
      shiftinput |= shiftIn(DATA, CLOCK, MSBFIRST) << 24;
    }
    
    if (shiftinput != oldshiftinput) {
      publish_state(getBit(shiftinput, pinNum));
      oldshiftinput = shiftinput;
    }  
  }
};

and then add a include key to your ESPHome .yaml file :

and finally define your inputs by using a custom platform in the same .yaml file :

binary_sensor:
  - platform: custom
    lambda: |-
      auto X00 = new SN74HC165Component(0);
      auto X01 = new SN74HC165Component(1);
      auto X02 = new SN74HC165Component(2);
      auto X03 = new SN74HC165Component(3);
      auto X04 = new SN74HC165Component(4);
      auto X05 = new SN74HC165Component(5);
      auto X06 = new SN74HC165Component(6);
      auto X07 = new SN74HC165Component(7);
      auto X08 = new SN74HC165Component(8);
      auto X09 = new SN74HC165Component(9);
      auto X10 = new SN74HC165Component(10);
      auto X11 = new SN74HC165Component(11);
      auto X12 = new SN74HC165Component(12);
      auto X13 = new SN74HC165Component(13);
      auto X14 = new SN74HC165Component(14);
      auto X15 = new SN74HC165Component(15);
      App.register_component(X00);
      App.register_component(X01);
      App.register_component(X02);
      App.register_component(X03);
      App.register_component(X04);
      App.register_component(X05);
      App.register_component(X06);
      App.register_component(X07);
      App.register_component(X08);
      App.register_component(X09);
      App.register_component(X10);
      App.register_component(X11);
      App.register_component(X12);
      App.register_component(X13);
      App.register_component(X14);
      App.register_component(X15);
      return {X00,X01,X02,X03,X04,X05,X06,X07,X08,X09,X10,X11,X12,X13,X14,X15};

    binary_sensors:
     - name: "Input 1"
     - name: "Input 2"
     - name: "Input 3"
     - name: "Input 4"
     - name: "Input 5"
     - name: "Input 6"
     - name: "Input 7"
     - name: "Input 8"
     - name: "Input 9"
     - name: "Input 10"
     - name: "Input 11"
     - name: "Input 12"
     - name: "Input 13"
     - name: "Input 14"
     - name: "Input 15"
     - name: "Input 16"

Connect your chips to ESP like this and you should see all of 74HC165s pins as input entities in your ESPHome device :vulcan_salute:
http://domoticx.com/wp-content/uploads/2020/07/74HC165-daisy-chain-schematic.png

Just some notes , LATCH and CLOCK pins on your ESP should be OUTPUT capable , and DATA pin should be INPUT capable ! (some gpios are not)

Excellent instructions and nice clean code. Great job! I ordered some 74HC165’s and should have them in the next few days. Will post back with my results.

Got my shift registers, wired them up, setup my ESP, and everything works great. Exactly what I needed.

Thanks again. You should consider doing a PR and adding this as a component to core ESPHome.

I have a netgain hyper9 motor and controller. Controller is hooked up to CAN bus. I am using an esp32 with mcp2515 to read the data. The format is as in the image here. I can read a byte ok and get a number but can not correctly read the word (2bytes) nor can I get the correct bitmap. I am using esphome to read this through a lambda function. Are there any c++ / esphome lambda people that could help.

Did you make a PR to add this to the Component list?

Here is my conf for using 32Bit Outputs and 32BitInputs, using 4x74HC595 and 4x74HC165

Because of the channel-numbering (it used to start with 0, i want it to start with 1) i made a little change with the Clock toggeling.

Binswitch.h (for 74HC595):

#include "esphome.h"

//  global variable to store state of bits
uint64_t output_state = 0;
// pins of the Output shift register (74HC595)
const byte outputLatch_pin = 15;
const byte clock_pin = 14;
const byte mosi_pin = 13;

class binSwitch : public Component, public Switch
{
 byte pinNum;
 bool SetBit(bool pinValue, uint64_t number)
 {
   if (pinValue)
     bitSet(output_state, number);
   else
     bitClear(output_state, number);
   SetOutputs();
   return pinValue;
 }
 void SetOutputs()
 {
   digitalWrite(clock_pin, LOW);
   digitalWrite(outputLatch_pin, LOW);
   shiftOut(mosi_pin, clock_pin, LSBFIRST, output_state);
   shiftOut(mosi_pin, clock_pin, LSBFIRST, output_state >> 8);
   shiftOut(mosi_pin, clock_pin, LSBFIRST, output_state >> 16);
   shiftOut(mosi_pin, clock_pin, LSBFIRST, output_state >> 24);
   digitalWrite(outputLatch_pin, HIGH);
 }
public:
 // pin - output pin of the shift register (0..31)
 binSwitch(byte pin) { pinNum = pin; publish_state(false);}
 void setup() override
 {
   publish_state(false);
   pinMode(outputLatch_pin, OUTPUT);
   pinMode(clock_pin, OUTPUT);
   pinMode(mosi_pin, OUTPUT);
   digitalWrite(outputLatch_pin, HIGH);
   SetOutputs();
 }
 void write_state(bool state) override
 {
   publish_state(SetBit(state, pinNum));
 }
};

and 74HC165.h:

#include "esphome.h"

class SN74HC165Component : public PollingComponent, public BinarySensor 
{
  byte pinNum;
  private:
    const byte LATCH = 4;  // ESP pin number that should be connected to pin 1 of 74hc165s
    const byte DATA  = 5;  // ESP pin number that should be connected to pin 9 of first 74hc165
    const byte CLOCK = 2; // ESP pin number that should be connected to pin 2 of 74hc165s

  bool getBit(unsigned long x, byte n) 
  {
    bool value = bitRead(x, n);
    return value;
  }
  
  unsigned long shiftinput= 1;
  unsigned long oldshiftinput= 0;

 public:
  SN74HC165Component (byte pin) : PollingComponent(100) { pinNum = pin; }   // Change the number inside "PollingComponent(100)" to change update interval in miliseconds 
  float get_setup_priority() const override { return esphome::setup_priority::HARDWARE; }

  void setup() override 
  {
    pinMode(LATCH, OUTPUT); 
    pinMode(CLOCK, OUTPUT); 
    pinMode(DATA, INPUT); 
    digitalWrite(CLOCK, LOW); 
    digitalWrite(CLOCK, HIGH); 
    digitalWrite(LATCH, HIGH);
  }

  void update() override 
  {
    digitalWrite(CLOCK, LOW); 
    digitalWrite(CLOCK, HIGH); 
    digitalWrite(LATCH, LOW); 
    digitalWrite(LATCH, HIGH); 

    shiftinput = shiftIn(DATA, CLOCK, LSBFIRST);
    shiftinput |= shiftIn(DATA, CLOCK, LSBFIRST) << 8;
    shiftinput |= shiftIn(DATA, CLOCK, LSBFIRST) << 16;
    shiftinput |= shiftIn(DATA, CLOCK, LSBFIRST) << 24;
    
    if (shiftinput != oldshiftinput) 
    {
      publish_state(getBit(shiftinput, pinNum));
      oldshiftinput = shiftinput;
    }  
  }
};

and here the ESPhome yaml:

esphome:
  name: test_esp_shift
  platform: ESP8266
  board: d1_mini
  includes:
    - binSwitch.h
    - 74HC165.h
    
wifi:
  ssid: "yourSSID"
  password: "yourPW"
  
  manual_ip:
    static_ip: 192.168.2.244
    gateway: 192.168.2.1
    subnet: 255.255.252.0

# Enable Home Assistant API
api:
  password: "yourPW"

ota:
  password: "yourPW"

web_server:
  port: 80


switch:
- platform: custom
  lambda: |-
    auto switch1 = new binSwitch(0);
    auto switch2 = new binSwitch(1);
    auto switch3 = new binSwitch(2);
    auto switch4 = new binSwitch(3);
    auto switch5 = new binSwitch(4);
    auto switch6 = new binSwitch(5);
    auto switch7 = new binSwitch(6);
    auto switch8 = new binSwitch(7);
    auto switch9 = new binSwitch(8);
    auto switch10 = new binSwitch(9);
    auto switch11 = new binSwitch(10);
    auto switch12 = new binSwitch(11);
    auto switch13 = new binSwitch(12);
    auto switch14 = new binSwitch(13);
    auto switch15 = new binSwitch(14);
    auto switch16 = new binSwitch(15);
    auto switch17 = new binSwitch(16);
    auto switch18 = new binSwitch(17);
    auto switch19 = new binSwitch(18);
    auto switch20 = new binSwitch(19);
    auto switch21 = new binSwitch(20);
    auto switch22 = new binSwitch(21);
    auto switch23 = new binSwitch(22);
    auto switch24 = new binSwitch(23);
    auto switch25 = new binSwitch(24);
    auto switch26 = new binSwitch(25);
    auto switch27 = new binSwitch(26);
    auto switch28 = new binSwitch(27);
    auto switch29 = new binSwitch(28);
    auto switch30 = new binSwitch(29);
    auto switch31 = new binSwitch(30);
    auto switch32 = new binSwitch(31);
    App.register_component(switch1);
    App.register_component(switch2);
    App.register_component(switch3);
    App.register_component(switch4);
    App.register_component(switch5);
    App.register_component(switch6);
    App.register_component(switch7);
    App.register_component(switch8);
    App.register_component(switch9);
    App.register_component(switch10);
    App.register_component(switch11);
    App.register_component(switch12);
    App.register_component(switch13);
    App.register_component(switch14);
    App.register_component(switch15);
    App.register_component(switch16);
    App.register_component(switch17);
    App.register_component(switch18);
    App.register_component(switch19);
    App.register_component(switch20);
    App.register_component(switch21);
    App.register_component(switch22);
    App.register_component(switch23);
    App.register_component(switch24);
    App.register_component(switch25);
    App.register_component(switch26);
    App.register_component(switch27);
    App.register_component(switch28);
    App.register_component(switch29);
    App.register_component(switch30);
    App.register_component(switch31);
    App.register_component(switch32);
    return {switch1,switch2,switch3,switch4,switch5,switch6,switch7,switch8,switch9,switch10,switch11,switch12,switch13,switch14,switch15,switch16,switch17,switch18,switch19,switch20,switch21,switch22,switch23,switch24,switch25,switch26,switch27,switch28,switch29,switch30,switch31,switch32};

  switches:
    - name: "Switch 1"
      id: switch_1
    - name: "Switch 2"
      id: switch_2
    - name: "Switch 3"
      id: switch_3
    - name: "Switch 4"
      id: switch_4
    - name: "Switch 5"
      id: switch_5
    - name: "Switch 6"
      id: switch_6
    - name: "Switch 7"
      id: switch_7
    - name: "Switch 8"
      id: switch_8
    - name: "Switch 9"
      id: switch_9
    - name: "Switch 10"
      id: switch_10
    - name: "Switch 11"
      id: switch_11
    - name: "Switch 12"
      id: switch_12
    - name: "Switch 13"
      id: switch_13
    - name: "Switch 14"
      id: switch_14
    - name: "Switch 15"
      id: switch_15
    - name: "Switch 16"
      id: switch_16
    - name: "Switch 17"
      id: switch_17
    - name: "Switch 18"
      id: switch_18
    - name: "Switch 19"
      id: switch_19
    - name: "Switch 20"
      id: switch_20
    - name: "Switch 21"
      id: switch_21
    - name: "Switch 22"
      id: switch_22
    - name: "Switch 23"
      id: switch_23
    - name: "Switch 24"
      id: switch_24
    - name: "Switch 25"
      id: switch_25
    - name: "Switch 26"
      id: switch_26
    - name: "Switch 27"
      id: switch_27
    - name: "Switch 28"
      id: switch_28
    - name: "Switch 29"
      id: switch_29
    - name: "Switch 30"
      id: switch_30
    - name: "Switch 31"
      id: switch_31
    - name: "Switch 32"
      id: switch_32

binary_sensor:
  - platform: custom
    lambda: |-
      auto X01 = new SN74HC165Component(0);
      auto X02 = new SN74HC165Component(1);
      auto X03 = new SN74HC165Component(2);
      auto X04 = new SN74HC165Component(3);
      auto X05 = new SN74HC165Component(4);
      auto X06 = new SN74HC165Component(5);
      auto X07 = new SN74HC165Component(6);
      auto X08 = new SN74HC165Component(7);
      auto X09 = new SN74HC165Component(8);
      auto X10 = new SN74HC165Component(9);
      auto X11 = new SN74HC165Component(10);
      auto X12 = new SN74HC165Component(11);
      auto X13 = new SN74HC165Component(12);
      auto X14 = new SN74HC165Component(13);
      auto X15 = new SN74HC165Component(14);
      auto X16 = new SN74HC165Component(15);
      auto X17 = new SN74HC165Component(16);
      auto X18 = new SN74HC165Component(17);
      auto X19 = new SN74HC165Component(18);
      auto X20 = new SN74HC165Component(19);
      auto X21 = new SN74HC165Component(20);
      auto X22 = new SN74HC165Component(21);
      auto X23 = new SN74HC165Component(22);
      auto X24 = new SN74HC165Component(23);
      auto X25 = new SN74HC165Component(24);
      auto X26 = new SN74HC165Component(25);
      auto X27 = new SN74HC165Component(26);
      auto X28 = new SN74HC165Component(27);
      auto X29 = new SN74HC165Component(28);
      auto X30 = new SN74HC165Component(29);
      auto X31 = new SN74HC165Component(30);
      auto X32 = new SN74HC165Component(31);
      App.register_component(X01);
      App.register_component(X02);
      App.register_component(X03);
      App.register_component(X04);
      App.register_component(X05);
      App.register_component(X06);
      App.register_component(X07);
      App.register_component(X08);
      App.register_component(X09);
      App.register_component(X10);
      App.register_component(X11);
      App.register_component(X12);
      App.register_component(X13);
      App.register_component(X14);
      App.register_component(X15);
      App.register_component(X16);
      App.register_component(X17);
      App.register_component(X18);
      App.register_component(X19);
      App.register_component(X20);
      App.register_component(X21);
      App.register_component(X22);
      App.register_component(X23);
      App.register_component(X24);
      App.register_component(X25);
      App.register_component(X26);
      App.register_component(X27);
      App.register_component(X28);
      App.register_component(X29);
      App.register_component(X30);
      App.register_component(X31);
      App.register_component(X32);
      return {X01,X02,X03,X04,X05,X06,X07,X08,X09,X10,X11,X12,X13,X14,X15,X16,X17,X18,X19,X20,X21,X22,X23,X24,X25,X26,X27,X28,X29,X30,X31,X32};

    binary_sensors:
     - name: "Input 1"
     - name: "Input 2"
     - name: "Input 3"
     - name: "Input 4"
     - name: "Input 5"
     - name: "Input 6"
     - name: "Input 7"
     - name: "Input 8"
     - name: "Input 9"
     - name: "Input 10"
     - name: "Input 11"
     - name: "Input 12"
     - name: "Input 13"
     - name: "Input 14"
     - name: "Input 15"
     - name: "Input 16"
     - name: "Input 17"
     - name: "Input 18"
     - name: "Input 19"
     - name: "Input 20"
     - name: "Input 21"
     - name: "Input 22"
     - name: "Input 23"
     - name: "Input 24"
     - name: "Input 25"
     - name: "Input 26"
     - name: "Input 27"
     - name: "Input 28"
     - name: "Input 29"
     - name: "Input 30"
     - name: "Input 31"
     - name: "Input 32"     

in HA there are two Cards:
Outputs:

type: entities
entities:
  - entity: switch.switch_1
  - entity: switch.switch_2
  - entity: switch.switch_3
  - entity: switch.switch_4
  - entity: switch.switch_5
  - entity: switch.switch_6
  - entity: switch.switch_7
  - entity: switch.switch_8
  - entity: switch.switch_9
  - entity: switch.switch_10
  - entity: switch.switch_11
  - entity: switch.switch_12
  - entity: switch.switch_13
  - entity: switch.switch_14
  - entity: switch.switch_15
  - entity: switch.switch_16
  - entity: switch.switch_17
  - entity: switch.switch_18
  - entity: switch.switch_19
  - entity: switch.switch_20
  - entity: switch.switch_21
  - entity: switch.switch_22
  - entity: switch.switch_23
  - entity: switch.switch_24
  - entity: switch.switch_25
  - entity: switch.switch_26
  - entity: switch.switch_27
  - entity: switch.switch_28
  - entity: switch.switch_29
  - entity: switch.switch_30
  - entity: switch.switch_31
  - entity: switch.switch_32
state_color: true
title: ESP Shiftboard Outputs

and for Inputs:

type: entities
entities:
  - entity: binary_sensor.input_1
  - entity: binary_sensor.input_2
  - entity: binary_sensor.input_3
  - entity: binary_sensor.input_4
  - entity: binary_sensor.input_5
  - entity: binary_sensor.input_6
  - entity: binary_sensor.input_7
  - entity: binary_sensor.input_8
  - entity: binary_sensor.input_9
  - entity: binary_sensor.input_10
  - entity: binary_sensor.input_11
  - entity: binary_sensor.input_12
  - entity: binary_sensor.input_13
  - entity: binary_sensor.input_14
  - entity: binary_sensor.input_15
  - entity: binary_sensor.input_16
  - entity: binary_sensor.input_17
  - entity: binary_sensor.input_18
  - entity: binary_sensor.input_19
  - entity: binary_sensor.input_20
  - entity: binary_sensor.input_21
  - entity: binary_sensor.input_22
  - entity: binary_sensor.input_23
  - entity: binary_sensor.input_24
  - entity: binary_sensor.input_25
  - entity: binary_sensor.input_26
  - entity: binary_sensor.input_27
  - entity: binary_sensor.input_28
  - entity: binary_sensor.input_29
  - entity: binary_sensor.input_30
  - entity: binary_sensor.input_31
  - entity: binary_sensor.input_32
state_color: true
title: ESP Shiftboard Inputs

I hope this helps someone!

@mega-hz Do you have a schematic how you’ve connected the shift registers?