Making a Benfei USB switch smart with a ESP32 S3

Summary

 I am documenting my (successful) attempt to make a Benfei USB switch smart by adding an embedded ESP32 S3 zero board. This is because I found zero information about this device on the web, and it might help someone in the future.

Background

I wanted to be able to use Home Assistant to switch my keyboard and mouse between 2 pc's. 

But I wanted to be able to control this via Home  Assistant so I could make it switch at the same time as I used it to switch my monitor input between the same 2 PCs, so I could have a smart KVM without spending the money on a proper one, and without losing my lovely 4k 144Hz capability.

I did some googling and I saw that folk had successfully added a ESP32 to generic, cheap USB switches.

So I took a punt on a Benfi one, ensuring that it had a remote control and optional external power, should they be needed for the project (they werent).

The one I bought was

https://amazon.co.uk/dp/B0DGFC4MWK

It was less than £20 so even if I broke it, and failed, it wouldnt be a huge loss, and I would have learnt something.

Investigation

I could find no teardowns or documentation on the web for this device (or any similar device),so I popped it open (after testing it worked), and got my multimeter out:


I was searching for a way to 

a) toggle inputs

b) detect current input

c) power the ESP32 S3

so I could reliably tell home assistant to "select input 1" or "select input 2"

So I needed a way to detect which LED was lit up, and to press the button.

What I found

a) toggle inputs

The button was surface mount, but had nice big pads, so soldering to them was easy. A bit of probing proved that the pads on the bottom of the image above were ground, and the ones at the top were the input to the controller on board (u4). (there are 2 pairs of joined together pins on these switches)

It operated by normally having +5v on the signal side, which was pulled to ground when the button was pressed.

Easy.

b) detect current input

I initially planned to tap the LEDs themselves (or one of them, since if 1 is on, 2 is off), but when I saw the surface mount pads, I no'ped right out, they are tiny, and beyond my soldering ability.

So I probed the main microcontroller, and played, and discovered that the pins already provided what I needed. I found the 8 pins on the chip are

1. Unsure, +5v all the time, probably the chip power input
2. Unsure, +5v all the time, probably the power for the actual switching chip
3. The currently selected output (output 1, high +5v, output 2, low, 0v or the other way round). I think this is what goes to the actual switching chip to select output. This was the one to tap :)
4. Connected to the push button via a resistor. Easier to solder to the actual button
5. Ground
6. LED output 1
7. LED output 2
8. Ground

c) power for the ESP32

This turned out to be the hardest part. I was hoping to tap off one of the large capacitors, but one only had 5v if port 1 was connected to a PC and the other never had 5v at all (maybe for the external power input?).

I considered tapping from the +5v on the U4 chip, but there were 2 of them, and I was unsure if it had enough capability to power a wifi esp32

So I ended up tapping the 2 PC usb connections, via a pair of diodes to get my +5v

The design

I used Claude to help design the actual circuit to do this, and it came up with. I didnt include the cap.


The build

I dont have the capability to fab my own PCBs so it was a bit of a bodge job. I didnt want to build it as a rats nest, with diodes etc inline with the wires, but that might have been a better choice.

I instead build the 3 component parts onto tiny 3x3 pieces of stripboard, then used wires to join it all up.

I used velcro sticky pads to position and hold the ESP32 board itself to the space on the devices PCB, and off-cuts of these to insulate the PCB to stick my 3 bodge boards to it using hot glue.

I then soldered it all together.














The reassembly

It was a very tight fit. If it was doing this again I would take more care about vertical space within the enclosure :)

But I did get it back together and it worked.

The software

I used ESPHome to drive it, and it was a simple YAML that published the following:

Here is the ESPHome YAML code:

# Board: Waveshare ESP32-S3-Zero (Waveshare)
# Definition: definitions/boards/waveshare_esp32_s3_zero/manifest.yaml

esphome:
  name: benfei
  friendly_name: Benfei

esp32:
  variant: esp32s3
  flash_size: 4MB
  framework:
    type: esp-idf
    advanced:
      enable_ota_rollback: true

logger:

api:
  encryption:
    key: "(REDACTED)"

ota:
  - platform: esphome

wifi:
  ssid: !secret wifi_ssid
  password: !secret wifi_password
  ap:
    ssid: Benfei Fallback Hotspot
    password: "(REDACTED)"

captive_portal:

# GPIO2 -> 1N5819 (band towards ESP) -> K1 top pad. Open-drain: driving low presses the button,
# released (high-Z) leaves it alone. Inverted so "on" = pressed.
output:
  - platform: gpio
    id: press_output
    pin:
      number: GPIO2
      inverted: true
      mode:
        output: true
        open_drain: true

button:
  - platform: output
    id: toggle_input
    name: "Toggle Input"
    output: press_output
    duration: 200ms
  - platform: template
    name: "Select Input 1"
    on_press:
      - script.execute:
          id: select_input
          want_input1: true
  - platform: template
    name: "Select Input 2"
    on_press:
      - script.execute:
          id: select_input
          want_input1: false

# Only toggles if the switch isn't already on the wanted input. Mode single plus the settle
# delay stops back-to-back calls from double-toggling before GPIO1 has caught up.
script:
  - id: select_input
    mode: single
    parameters:
      want_input1: bool
    then:
      - if:
          condition:
            lambda: return id(input1_selected).state != want_input1;
          then:
            - button.press: toggle_input
            - delay: 500ms

# GPIO1 <- 3k <- 1N5819 (band towards U4) <- U4 select line. High = input 1, low = input 2.
binary_sensor:
  - platform: gpio
    id: input1_selected
    name: "Input 1 Selected"
    internal: true
    pin:
      number: GPIO1
      mode:
        input: true
        pullup: true

text_sensor:
  - platform: template
    name: "Active Input"
    update_interval: 1s
    lambda: |-
      return std::string(id(input1_selected).state ? "Input 1" : "Input 2");


Comments

Popular posts from this blog

Emergency safe mode on ESPHome and ESP32 (re-using EN button on GPIO0)

Introducing Easynow - an ESPHome component to make sensor proxying via ESPNow easy in ESPHome.

SSD Upgrade on Lenovo Legion Go - Part 1 - Backup the original drive using Veeam