ESP32-C3 (WiFi/Homeyduino) + ESP32-C6 (native Zigbee) — my experience as a beginner, surprisingly easy with Claude Code"

Wanted to share my recent experience testing two different approaches to integrate DIY hardware with Homey Pro, in case it helps others getting started:

1) ESP32-C3 Mini + WiFi + Homeyduino app
I set up an on/off LED using the Homeyduino app (see thread Homeyduino @ V2.0), which lets you program the ESP32-C3 from Arduino IDE and integrate it directly as a device in Homey. The process was straightforward: flash via USB, and Homey picked it up without any friction, automatically creating the device title and on/off flow cards, ready to use right away.

2) ESP32-C6 (Waveshare DEV-KIT-NX) + native/generic Zigbee, no ESPHome
This is where I was really surprised. Using Arduino IDE with the ESP32 core 3.x, which now has the Zigbee library built in (standard On/Off cluster from the Zigbee Cluster Library), I programmed the C6 as a Zigbee End Device. When pairing, Homey Pro (acting as native Zigbee coordinator) detected it as a generic Zigbee device, no driver or extra app needed — it got recognized on the first try, easier than some branded commercial devices I’d tried before. It also generated its own title and flow cards automatically, just like a commercial device would.

My takeaway: when you stick to standard clusters (not proprietary/manufacturer-specific ones), Homey doesn’t just recognize the device without friction — it builds the whole integration (name, flow cards, capabilities) automatically.

Worth mentioning: I wrote and set up both projects entirely with Claude Code (Anthropic’s AI coding assistant). It wrote the Arduino sketches, configured the Zigbee endpoint/cluster setup for the C6, and walked me through wiring, board settings, and pairing — I described what I wanted, and it produced working, uploadable code each time. What struck me most was the speed: both projects went from zero to working devices paired in Homey in just a handful of iterations, without me needing to read through ESP-IDF documentation or the Zigbee spec myself. It significantly lowered the barrier to entry for this kind of DIY Zigbee project — something that used to require real embedded/protocol expertise.

I’m now considering switching the C6 to Router mode (since it’s always USB-powered) to help strengthen the Zigbee mesh at home.

If anyone’s thinking about trying “pure” ESP32 + Zigbee (no ESPHome/Z2M) with Homey, I’d definitely recommend it — the learning curve was much lower than I expected, especially with AI-assisted coding doing the heavy lifting.

Interesting findings! I was actually planning on using some ESP32-C6’s to add Zigbee to some IKEA Ädellövskog candles powered by 2 AA batteries.

Very interesting, especially the battery angle - please share how it goes once you test it!

Also, this kind of experience makes me think Homey could really benefit from building more of a maker community around it, similar to what Home Assistant has. With tools like these, everything becomes so much more accessible - you don’t even need to know how to code anymore, which means we don’t really need ESPHome or MQTT in the middle to get a working device.

From here, I’d encourage Homey to keep developing further in this direction.

As for the ESP32-C6 those are also great to create Thread/Matter devices. One of them is a replacement for a Ring Intercom since my building has two doors and it integrates great into any ecosystem. More complex devices on Zigbee tend to need custom clusters which needs to be implemented per ecosystem integration which is why Matter is great since it has more standardized clusters and capabilities out of the box.

EDIT: I can recommend the Olimex ESP32-C5 EVB and ESP32-C6 EVB as they come with relays and opto-inputs ready to go.

I Can you provide a sample code for in the esp32-c3?

C3 doesn’t support Zigbee/Thread.

Thanks! I’m curious about your Homey integration. Did you expose the ESP32-C6 directly as a Matter device, or did you use another middleware? How has the overall experience been?

Also, are you using End Devices or Router Devices?

I’m working on an apartment intercom project for the same reason. Mine needs to interface with a Comelit Simplebus 2 system, so I’m currently evaluating whether Zigbee or Matter is the better approach.

And thanks for the Olimex board recommendation—I hadn’t come across those before.

Yes, exactly as @robertklep said, that board isn’t running Zigbee—it’s using Wi-Fi, and it works really well.

For my first project I’m actually using a standard ESP32 DevKit board. The only code I currently have for the ESP32-C3 is a simple LED on/off example.

I actually have two separate .ino files: one is the LED example for the ESP32-C3 Mini, and the other is for my humidifier project using a standard ESP32 board.

initially considered using a standard ESP32 DevKit board for the humidifier project, but I ultimately completed it using the ESP32-C3 Mini. The C3 provides everything required for this project while taking up considerably less space inside the enclosure.

The complete controller has now been tested and is working on the actual humidifier. It includes Homey control over Wi-Fi, USB Serial Monitor commands, ACS712 current-based state synchronization, automatic Wi-Fi reconnection and Arduino OTA updates.

The complete, tested ESP32-C3 humidifier code is included in a later reply below.

// Sketch for ESP32-C3 mini with Homeyduino
// Built-in LED on GPIO 8, controllable from the Homey app (WiFi)
// and from the Serial Monitor (USB) by typing "on", "off" or "toggle" + Enter

#include <WiFi.h>
#include <WiFiClient.h>
#include <Homey.h>

// --- Network configuration ---
// Replace these values with your own WiFi network's
const char* WIFI_SSID = "MOVISTAR_4A08";
const char* WIFI_PASSWORD = "TU_CONTRASEÑA_WIFI";

const int LED_PIN = 8;

// Test pin used to check which GPIOs we'll use in the humidifier
// project. First we're testing pin 5.
const int TEST_LED_PIN = 5;

// Current LED state (true = on, false = off)
bool ledState = false;

// Current state of the test LED on pin 5
bool testLedState = false;

// Buffer that accumulates characters received over the Serial Monitor
String serialBuffer = "";

// Applies the state to the LED's physical pin
// (the built-in LED is usually active-LOW, hence the inverted signal)
void applyLedState() {
digitalWrite(LED_PIN, ledState ? LOW : HIGH);
}

// Applies the state to the test LED on pin 5
// (standard external LED: active-HIGH, signal not inverted)
void applyTestLedState() {
digitalWrite(TEST_LED_PIN, testLedState ? HIGH : LOW);
}

// Callback called by Homey when the secondary switch ("onoff.led5") is pressed in the app
// (controls the built-in LED; the capability name is kept for compatibility with Homey,
// even though it no longer controls pin 5)
void onHomeyOnOff() {
ledState = Homey.value.toInt(); // Converts "1"/"0" received from Homey to bool
// (assigning Homey.value directly to a bool
// always evaluates to true, which is why "off" wasn't working)
applyLedState();
Serial.println(ledState ? "Homey: LED on" : "Homey: LED off");
}

// Callback called by Homey when the main switch ("onoff") is pressed in the app
// (controls the test LED on pin 5)
void onHomeyTestLedOnOff() {
testLedState = Homey.value.toInt(); // Same as with the built-in LED: needs toInt() conversion
applyTestLedState();
Serial.println(testLedState ? "Homey: LED pin 5 on" : "Homey: LED pin 5 off");
}

// Changes the LED state and syncs the switch in the Homey app
void setLedState(bool newState) {
ledState = newState;
applyLedState();
Homey.setCapabilityValue("onoff.led5", ledState); // Updates the secondary switch in the app
Serial.println(ledState ? "Serial: LED on" : "Serial: LED off");
}

// Changes the state of the test LED on pin 5 and syncs its own switch in Homey
void setTestLedState(bool newState) {
testLedState = newState;
applyTestLedState();
Homey.setCapabilityValue("onoff", testLedState); // Updates the main switch in the app
Serial.println(testLedState ? "Serial: LED pin 5 on" : "Serial: LED pin 5 off");
}

// Processes a text command received over the Serial Monitor
void processSerialCommand(String command) {
command.trim();
command.toLowerCase();

if (command == "on") {
setLedState(true);
} else if (command == "off") {
setLedState(false);
} else if (command == "toggle") {
setLedState(!ledState);
} else if (command == "led5 on") {
setTestLedState(true);
} else if (command == "led5 off") {
setTestLedState(false);
} else if (command == "led5 toggle") {
setTestLedState(!testLedState);
} else if (command.length() > 0) {
Serial.println("Unrecognized command. Use: on / off / toggle / led5 on / led5 off / led5 toggle");
}
}

// Reads characters from the Serial Monitor without blocking the main loop
void handleSerialInput() {
while (Serial.available() > 0) {
char c = Serial.read();
if (c == '\n' || c == '\r') {
if (serialBuffer.length() > 0) {
processSerialCommand(serialBuffer);
serialBuffer = "";
}
} else {
serialBuffer += c;
}
}
}

void setup() {
Serial.begin(115200);
delay(500); // gives the native USB Serial time to finish connecting before printing

pinMode(LED_PIN, OUTPUT);
applyLedState(); // LED off at startup

pinMode(TEST_LED_PIN, OUTPUT);
applyTestLedState(); // Test LED (pin 5) off at startup

// WiFi connection
WiFi.begin(WIFI_SSID, WIFI_PASSWORD);
Serial.print("Connecting to WiFi");
while (WiFi.status() != WL_CONNECTED) {
delay(500); // Only blocks during the initial connection, not in the loop
Serial.print(".");
}
Serial.println();
Serial.print("Connected. IP: ");
Serial.println(WiFi.localIP());

// Register the device with Homey
Homey.begin("LedTest");
Homey.setClass("light");

// Main capability "onoff": the one shown as a direct button on the Homey
// card/dashboard. Controls the test LED on pin 5.
Homey.addCapability("onoff", onHomeyTestLedOnOff);

// Second capability "onoff.led5": secondary switch, only visible inside the
// device detail view (Homey doesn't give it its own dashboard button). Controls the built-in LED.
Homey.addCapability("onoff.led5", onHomeyOnOff);

Serial.println("Ready. Type on / off / toggle in the Serial Monitor.");
Serial.println("To test the LED on pin 5: led5 on / led5 off / led5 toggle");
}

void loop() {
Homey.loop(); // Must be called constantly, no delay() in this loop
handleSerialInput();
}

Please use Preformatted Text for code, otherwise your code is unreadable due to Discourse formatting

Hi Eladio,

I have modified “Chip.h” to get it through the compiler.

Now it its working.

(Attachment chip.h is missing)

Thanks for looking into the problem.

I did expose the ESP32-C6 directly as a matter device with two locks nodes and one switch node. I soldered the relays to my existing intercoms buttons, so it does the same as manually pressing the buttons and then connected the external bell port to one of the opto inputs which triggers a Matter switch/button press event. This works great on basically any eco system, in contrast to Zigbee where door locks are not that well standardized.

The whole implementation was done with esp-matter and Claude.

I’m using it as a router device as I have it constantly powered. Initially I wanted to just grab some 27V from my intercom, but that caused some static on the headset. So for now I’m just powering it via usb, but I think in theory battery powering it should also be possible.

Hi Steef,

Glad to hear you got it working. I didn’t encounter that compilation problem on my setup, so I didn’t need to modify Chip.h. It may be related to a different Homeyduino library version, ESP32 core version, or board configuration.

The chip.h attachment seems to be missing. Could you attach it again or explain what you changed? It could be useful for anyone encountering the same issue.

Update — complete ESP32-C3 humidifier controller, tested and working

I’ve now completed and tested the full humidifier controller on the actual hardware.

It uses an ESP32-C3 SuperMini with Homeyduino over Wi-Fi and supports:

  • On/off control from Homey.

  • Control and diagnostics through the USB Serial Monitor.

  • Arduino OTA updates over Wi-Fi.

  • A MOSFET that briefly interrupts the 24 V supply to switch the humidifier off and reset it to standby.

  • A PC817 optocoupler that simulates pressing the humidifier’s physical button to switch it on.

  • An ACS712 current sensor that detects manual operation and synchronizes the actual state back to Homey.

  • Non-blocking switching sequences.

  • Automatic Wi-Fi reconnection and recovery of the Homey and OTA services.

  • Built-in status LED.

Hardware connections:

  • GPIO10 → MOSFET trigger.

  • GPIO7 → PC817 optocoupler.

  • GPIO3 → ACS712 output.

  • GPIO8 → built-in status LED.

The attached .ino file is the complete version currently running on my humidifier.

Before uploading, replace these placeholders with your own values:

  • YOUR_2G4_WIFI

  • YOUR_WIFI_PASSWORD

  • YOUR_OTA_PASSWORD

The ACS712 thresholds were calibrated for my particular humidifier and may need adjustment for different hardware.

[Attach: humidifier_c3_homeyduino_ota.ino]

// ===========================================================================
// ESP32-C3 HUMIDIFIER CONTROLLER — PUBLIC FORUM VERSION
// ===========================================================================
// Board: ESP32-C3 SuperMini (HW-466AB)
// Control: Homey over Wi-Fi, USB Serial Monitor and Arduino OTA updates.
//
// Hardware connections:
//   GPIO10 -> MOSFET trigger. Briefly interrupts the humidifier's 24 V supply.
//   GPIO7  -> bare PC817 optocoupler. Simulates pressing the physical button.
//   GPIO3  -> ACS712 5 A current-sensor output (ADC1 pin required with Wi-Fi).
//   GPIO8  -> built-in status LED (active LOW).
//
// Operation:
//   OFF briefly interrupts and automatically restores the 24 V supply. This
//   resets the humidifier to standby while leaving its physical button usable.
//   ON leaves the power supply connected and pulses the optocoupler to emulate
//   a button press. Both sequences are non-blocking.
//
// The ACS712 detects manual operation and keeps Homey's on/off state in sync.
// Current thresholds were measured for this particular humidifier and may need
// adjustment for other hardware. Use the Serial commands shown at startup.
//
// Before uploading, replace YOUR_2G4_WIFI, YOUR_WIFI_PASSWORD and
// YOUR_OTA_PASSWORD with your own values.
// Never publish the resulting private copy.
// ===========================================================================

#include <WiFi.h>
#include <WiFiClient.h>
#include <ArduinoOTA.h>
#include <Homey.h>

const char* WIFI_SSID = "YOUR_2G4_WIFI";
const char* WIFI_PASSWORD = "YOUR_WIFI_PASSWORD";

const char* DEVICE_NAME = "HumidificadorC3";
const char* OTA_PASSWORD = "YOUR_OTA_PASSWORD";

const unsigned long WIFI_TIMEOUT_MS = 20000;
const unsigned long WIFI_RETRY_MS = 30000;
const unsigned long WIFI_CHECK_MS = 5000;
const int WIFI_MISSED_CHECKS = 2;
const unsigned long WIFI_RADIO_RESET_MS = 600000;

bool networkStarted = false;
bool capabilitiesRegistered = false;

unsigned long lastWifiRetry = 0;
unsigned long lastWifiCheck = 0;
unsigned long wifiDownSince = 0;

int wifiMissedChecks = 0;
uint16_t wifiDropCount = 0;

IPAddress lastIp;

const int MOSFET_PIN = 10;
const bool MOSFET_ACTIVE_LOW = true;

const int OPTO1_PIN = 7;
const bool OPTO1_ACTIVE_LOW = false;

const int LED_PIN = 8;
const bool LED_ACTIVE_LOW = true;

const unsigned long LED_BLINK_MS = 500;

unsigned long lastLedToggle = 0;
bool ledBlinkState = false;

const unsigned long CUT_PULSE_MS = 500;
const unsigned long OPTO_SETTLE_MS = 300;
const unsigned long OPTO_PULSE_MS = 250;

bool humidifierOn = false;

const int CURRENT_PIN = 3;
const int CURRENT_SAMPLES = 400;
const unsigned long CURRENT_POLL_MS = 500;

float CURRENT_ON_MV = 20.0;
float CURRENT_OFF_MV = 8.0;

const int CURRENT_CONFIRM_ON = 3;
const int CURRENT_CONFIRM_OFF = 10;

const float ADC_SATURATION_WARN_MV = 2900.0;
const float ACS712_MV_PER_A = 185.0;

float currentOffsetMv = 0;
bool currentOffsetValid = false;
bool sensorEnabled = true;
float lastCurrentMv = 0;

unsigned long lastCurrentPoll = 0;
unsigned long lastSequenceEnd = 0;

int detectedStreak = 0;
bool lastDetectedOn = false;
bool powerIsCut = false;

void setPowerCut(bool cut) {
  bool pinLevel = MOSFET_ACTIVE_LOW ? !cut : cut;
  digitalWrite(MOSFET_PIN, pinLevel ? HIGH : LOW);
  powerIsCut = cut;
}

void setOptoTap(bool active) {
  bool pinLevel = OPTO1_ACTIVE_LOW ? !active : active;
  digitalWrite(OPTO1_PIN, pinLevel ? HIGH : LOW);
}

void setLed(bool on) {
  digitalWrite(
    LED_PIN,
    (LED_ACTIVE_LOW ? !on : on) ? HIGH : LOW
  );
}

float readCurrentMv() {
  uint32_t total = 0;

  for (int i = 0; i < CURRENT_SAMPLES; i++) {
    total += analogReadMilliVolts(CURRENT_PIN);
  }

  return (float)total / CURRENT_SAMPLES;
}

void calibrateCurrentOffset(const char* reason) {
  currentOffsetMv = readCurrentMv();
  currentOffsetValid = true;

  Serial.print("Sensor: 0 A reference recorded (");
  Serial.print(reason);
  Serial.print("): ");
  Serial.print(currentOffsetMv, 1);
  Serial.println(" mV");

  if (currentOffsetMv > ADC_SATURATION_WARN_MV) {
    Serial.println(
      "  WARNING: very high reading; the C3 ADC may be saturated."
    );
    Serial.println(
      "  Use 'amp' and check whether the value changes when switching on."
    );
  }
}

enum ActionStage {
  STAGE_IDLE,
  STAGE_OFF_CUTTING,
  STAGE_ON_SETTLING,
  STAGE_ON_TAPPING
};

ActionStage stage = STAGE_IDLE;
unsigned long stageStartMillis = 0;
bool offsetTakenThisCut = false;

void startTurnOffSequence() {
  stage = STAGE_OFF_CUTTING;
  stageStartMillis = millis();
  offsetTakenThisCut = false;

  setPowerCut(true);

  Serial.println(
    "OFF: cutting power for 0.5 s (automatic restoration)..."
  );
}

void startTurnOnSequence() {
  if (powerIsCut) {
    setPowerCut(false);
  }

  stage = STAGE_ON_SETTLING;
  stageStartMillis = millis();

  Serial.println(
    "ON: waiting for stabilization before pressing the button..."
  );
}

void updateActionSequence() {
  if (stage == STAGE_IDLE) {
    return;
  }

  unsigned long elapsed = millis() - stageStartMillis;

  if (
    stage == STAGE_OFF_CUTTING &&
    !offsetTakenThisCut &&
    elapsed >= 200
  ) {
    offsetTakenThisCut = true;
    calibrateCurrentOffset("during the OFF power cut");
  }

  if (
    stage == STAGE_OFF_CUTTING &&
    elapsed >= CUT_PULSE_MS
  ) {
    setPowerCut(false);
    stage = STAGE_IDLE;
    lastSequenceEnd = millis();

    Serial.println(
      "OFF: power restored; humidifier is in standby."
    );
  } else if (
    stage == STAGE_ON_SETTLING &&
    elapsed >= OPTO_SETTLE_MS
  ) {
    setOptoTap(true);

    stageStartMillis = millis();
    stage = STAGE_ON_TAPPING;

    Serial.println(
      "ON: pressing the button (optocoupler)..."
    );
  } else if (
    stage == STAGE_ON_TAPPING &&
    elapsed >= OPTO_PULSE_MS
  ) {
    setOptoTap(false);

    stage = STAGE_IDLE;
    lastSequenceEnd = millis();

    Serial.println("ON: sequence complete.");
  }
}

void updateCurrentSensor() {
  if (millis() - lastCurrentPoll < CURRENT_POLL_MS) {
    return;
  }

  lastCurrentPoll = millis();
  lastCurrentMv = readCurrentMv();

  if (!sensorEnabled) {
    return;
  }

  if (stage != STAGE_IDLE) {
    return;
  }

  if (millis() - lastSequenceEnd < 3000) {
    return;
  }

  if (!currentOffsetValid) {
    return;
  }

  float deviation = fabs(lastCurrentMv - currentOffsetMv);

  if (!humidifierOn && deviation < CURRENT_ON_MV) {
    currentOffsetMv += 0.01 * (lastCurrentMv - currentOffsetMv);
  }

  bool detected;

  if (deviation > CURRENT_ON_MV) {
    detected = true;
  } else if (deviation < CURRENT_OFF_MV) {
    detected = false;
  } else {
    return;
  }

  if (detected == lastDetectedOn) {
    detectedStreak++;
  } else {
    lastDetectedOn = detected;
    detectedStreak = 1;
  }

  int requiredReadings =
    detected ? CURRENT_CONFIRM_ON : CURRENT_CONFIRM_OFF;

  if (
    detectedStreak >= requiredReadings &&
    detected != humidifierOn
  ) {
    humidifierOn = detected;

    Serial.print("Sensor: humidifier is ");
    Serial.print(humidifierOn ? "ON" : "OFF");
    Serial.println(
      " (detected by current draw). Updating Homey."
    );

    if (networkStarted) {
      Homey.setCapabilityValue("onoff", humidifierOn);
    }
  }
}

void updateStatusLed() {
  if (!networkStarted) {
    if (millis() - lastLedToggle >= LED_BLINK_MS) {
      lastLedToggle = millis();
      ledBlinkState = !ledBlinkState;
      setLed(ledBlinkState);
    }

    return;
  }

  setLed(humidifierOn);
}

void turnOff() {
  setOptoTap(false);
  humidifierOn = false;

  if (networkStarted) {
    Homey.setCapabilityValue("onoff", humidifierOn);
  }

  startTurnOffSequence();
}

void turnOn() {
  humidifierOn = true;

  if (networkStarted) {
    Homey.setCapabilityValue("onoff", humidifierOn);
  }

  startTurnOnSequence();
}

void onHomeyOnOff() {
  bool wantOn = Homey.value.toInt();

  if (wantOn) {
    turnOn();
  } else {
    turnOff();
  }
}

void processSerialCommand(String command) {
  command.trim();
  command.toLowerCase();

  if (command == "on") {
    turnOn();
  } else if (command == "off") {
    turnOff();
  } else if (
    command == "mosfet on" ||
    command == "rele on"
  ) {
    setPowerCut(true);
    Serial.println("Manual power cut (no sequence)");
  } else if (
    command == "mosfet off" ||
    command == "rele off"
  ) {
    setPowerCut(false);
    Serial.println("Power restored manually (no sequence)");
  } else if (command == "opto on") {
    setOptoTap(true);
    Serial.println("Optocoupler: manually activated");
  } else if (command == "opto off") {
    setOptoTap(false);
    Serial.println("Optocoupler: manually deactivated");
  } else if (command == "amp") {
    float mv = readCurrentMv();
    lastCurrentMv = mv;

    Serial.print("Sensor: ");
    Serial.print(mv, 1);
    Serial.print(" mV | 0 A reference: ");

    if (currentOffsetValid) {
      Serial.print(currentOffsetMv, 1);
      Serial.print(" mV | difference: ");

      float delta = mv - currentOffsetMv;

      Serial.print(delta, 1);
      Serial.print(" mV (~");
      Serial.print(delta / ACS712_MV_PER_A, 3);
      Serial.print(" A) | threshold: ");
      Serial.print(CURRENT_ON_MV, 1);
      Serial.print(" mV -> ");

      Serial.println(
        fabs(delta) > CURRENT_ON_MV
          ? "DRAWING CURRENT"
          : "standby"
      );
    } else {
      Serial.println(
        "NOT CALIBRATED yet "
        "(use 'cal' while the humidifier is off)"
      );
    }

    if (!sensorEnabled) {
      Serial.println(
        "  (sensor DISABLED: it only measures "
        "and does not update Homey."
      );
      Serial.println(
        "   Once wired: switch the humidifier off "
        "and send 'cal'.)"
      );
    }

    if (mv > ADC_SATURATION_WARN_MV) {
      Serial.println(
        "  WARNING: above the expected C3 ADC limit (~2500 mV)."
      );
      Serial.println(
        "  If this value does NOT change when the humidifier starts,"
      );
      Serial.println(
        "  the ADC is saturated: lower the MP1584 to 4.5 V."
      );
    }
  } else if (command == "scan") {
    const int pins[] = {0, 1, 2, 3, 4};

    Serial.println(
      "ADC1 scan (GPIO0-4). "
      "Find the pin with a high, stable reading:"
    );

    for (int i = 0; i < 5; i++) {
      analogSetPinAttenuation(pins[i], ADC_11db);

      uint32_t total = 0;

      for (int j = 0; j < 100; j++) {
        total += analogReadMilliVolts(pins[i]);
      }

      Serial.print("  GPIO");
      Serial.print(pins[i]);
      Serial.print(": ");
      Serial.print(total / 100.0, 1);
      Serial.println(" mV");
    }
  } else if (command == "cal") {
    calibrateCurrentOffset(
      "manually using the cal command"
    );

    sensorEnabled = true;

    Serial.println(
      "Sensor: ENABLED; it now updates Homey until the next restart."
    );
  } else if (command == "estado") {
    Serial.print("Logical state: ");
    Serial.print(humidifierOn ? "ON" : "off");

    Serial.print(" | power: ");
    Serial.print(powerIsCut ? "CUT" : "restored");

    Serial.print(" | sensor: ");
    Serial.print(
      sensorEnabled
        ? "active"
        : "DISABLED (use 'cal' once wired)"
    );

    Serial.print(" | WiFi: ");

    if (networkStarted) {
      Serial.print("connected, IP ");
      Serial.print(WiFi.localIP());
      Serial.print(", signal ");
      Serial.print(WiFi.RSSI());
      Serial.print(" dBm");
    } else if (wifiDownSince != 0) {
      Serial.print("DOWN for ");
      Serial.print((millis() - wifiDownSince) / 1000);
      Serial.print(" s");
    } else {
      Serial.print("NOT connected");
    }

    Serial.print(" | drops since startup: ");
    Serial.print(wifiDropCount);

    Serial.print(" | uptime: ");
    Serial.print(millis() / 60000);
    Serial.println(" min");
  } else if (command.startsWith("umbral ")) {
    CURRENT_ON_MV = command.substring(7).toFloat();

    Serial.print("Detection threshold set to ");
    Serial.print(CURRENT_ON_MV, 1);
    Serial.println(
      " mV (until restart; edit the code to make it permanent)"
    );
  } else if (command.length() > 0) {
    Serial.println("Unknown command. Use:");
    Serial.println(
      "  on / off / mosfet on / mosfet off / "
      "opto on / opto off"
    );
    Serial.println(
      "  amp / cal / scan / estado / umbral <mV>"
    );
  }
}

void connectWifi() {
  WiFi.mode(WIFI_STA);
  WiFi.begin(WIFI_SSID, WIFI_PASSWORD);
  WiFi.setSleep(false);
}

void stopNetworkServices() {
  ArduinoOTA.end();
  Homey.stop();
  networkStarted = false;
}

void startNetworkServices() {
  lastIp = WiFi.localIP();

  Serial.print("Connected. IP: ");
  Serial.println(lastIp);

  ArduinoOTA.setHostname(DEVICE_NAME);
  ArduinoOTA.setPassword(OTA_PASSWORD);

  ArduinoOTA.onStart([]() {
    Serial.println("OTA: update started.");
  });

  ArduinoOTA.onEnd([]() {
    Serial.println("OTA: update complete; restarting.");
  });

  ArduinoOTA.onError([](ota_error_t error) {
    Serial.printf("OTA: error [%u]\n", error);
  });

  ArduinoOTA.begin();

  Serial.println(
    "OTA ready. In Arduino IDE: "
    "Tools > Port > network port."
  );

  Homey.begin(DEVICE_NAME);
  Homey.setClass("light");

  if (!capabilitiesRegistered) {
    Homey.addCapability("onoff", onHomeyOnOff);
    capabilitiesRegistered = true;
  }

  Homey.setCapabilityValue("onoff", humidifierOn);

  networkStarted = true;
  wifiMissedChecks = 0;

  if (wifiDownSince != 0) {
    Serial.print("Wi-Fi: recovered after ");
    Serial.print((millis() - wifiDownSince) / 1000);
    Serial.println(
      " s offline. Services restarted and state sent to Homey."
    );

    wifiDownSince = 0;
  }
}

void updateNetwork() {
  if (millis() - lastWifiCheck < WIFI_CHECK_MS) {
    return;
  }

  lastWifiCheck = millis();

  bool connected =
    WiFi.status() == WL_CONNECTED &&
    WiFi.localIP()[0] != 0;

  if (networkStarted) {
    if (connected) {
      wifiMissedChecks = 0;

      if (WiFi.localIP() != lastIp) {
        Serial.print(
          "Wi-Fi: IP address changed from "
        );
        Serial.print(lastIp);
        Serial.print(" to ");
        Serial.println(WiFi.localIP());

        Serial.println(
          "  If Homey loses the device, reserve its IP address"
        );
        Serial.println(
          "  in the router (DHCP) and pair it again."
        );

        stopNetworkServices();
        startNetworkServices();
      }

      return;
    }

    wifiMissedChecks++;

    if (wifiMissedChecks < WIFI_MISSED_CHECKS) {
      return;
    }

    wifiDropCount++;
    wifiDownSince = millis();
    lastWifiRetry = millis();

    Serial.println(
      "Wi-Fi: connection LOST. "
      "Stopping OTA and Homey, then reconnecting."
    );
    Serial.println(
      "  The humidifier still works from its physical button."
    );

    stopNetworkServices();
    return;
  }

  if (connected) {
    startNetworkServices();
    return;
  }

  if (millis() - lastWifiRetry < WIFI_RETRY_MS) {
    return;
  }

  lastWifiRetry = millis();

  if (
    wifiDownSince != 0 &&
    millis() - wifiDownSince >= WIFI_RADIO_RESET_MS
  ) {
    Serial.println(
      "Wi-Fi: offline for 10 min; fully restarting the radio..."
    );

    WiFi.disconnect(true);
    WiFi.mode(WIFI_OFF);
    delay(200);

    connectWifi();
    wifiDownSince = millis();

    return;
  }

  Serial.println("Wi-Fi: still offline; retrying...");

  WiFi.disconnect();
  connectWifi();
}

void setup() {
  Serial.begin(115200);
  delay(500);

  pinMode(MOSFET_PIN, OUTPUT);
  setPowerCut(false);

  pinMode(OPTO1_PIN, OUTPUT);
  setOptoTap(false);

  pinMode(LED_PIN, OUTPUT);
  setLed(false);

  analogSetPinAttenuation(CURRENT_PIN, ADC_11db);

  Serial.println();
  Serial.println(
    "Board started. ESP32-C3 SuperMini - C3 Humidifier."
  );

  calibrateCurrentOffset(
    "provisional at startup; 0 A is not guaranteed"
  );

  connectWifi();

  Serial.print("Connecting to Wi-Fi (max. 20 s)");

  unsigned long wifiStart = millis();

  while (
    WiFi.status() != WL_CONNECTED &&
    millis() - wifiStart < WIFI_TIMEOUT_MS
  ) {
    delay(500);
    Serial.print(".");
  }

  Serial.println();

  if (WiFi.status() == WL_CONNECTED) {
    startNetworkServices();
  } else {
    Serial.println(
      "NO Wi-Fi after 20 s. Continuing startup: "
      "Serial control and the"
    );
    Serial.println(
      "physical humidifier button still work. "
      "Retrying every 30 s."
    );

    lastWifiRetry = millis();
    wifiDownSince = millis();
  }

  Serial.println(
    "Ready. Use the on/off switch in Homey "
    "or Serial commands:"
  );
  Serial.println(
    "  on / off / mosfet on / mosfet off / "
    "opto on / opto off"
  );
  Serial.println(
    "  amp (read sensor) / cal (0 A reference) / "
    "scan / estado"
  );
}

void loop() {
  if (networkStarted) {
    ArduinoOTA.handle();
    Homey.loop();
  }

  updateNetwork();
  updateActionSequence();
  updateCurrentSensor();
  updateStatusLed();

  if (Serial.available() > 0) {
    String command = Serial.readStringUntil('\n');
    processSerialCommand(command);
  }
}

Thanks for pointing that out. I wasn’t familiar with Discourse’s Preformatted Text option.

I’ve now corrected the original ESP32-C3 example and reposted the complete, tested humidifier code as Preformatted Text. The new version also includes Arduino OTA updates and automatic Wi-Fi reconnection.

It should now be properly formatted and readable.

Hi,

attached the modified chip.h

(Attachment chip.h is missing)

Hi,

I can not send you the file as a file but here it is printed.

// 26-07-26 Modified to accept ESP32-C3

// SdK

#include “Arduino.h”

#include “pins_arduino.h”

#ifndef CHIP

#define CHIP

#if defined(ARDUINO_ARCH_ESP8266) //ESP8266

static const char* arduino_arch = “esp8266”;

#elif defined(ARDUINO_ARCH_ESP32) //ESP32

static const char* arduino_arch = “esp32”;

#elif defined(ARDUINO_ARCH_AVR)

static const char* arduino_arch = “avr”;

#elif defined(ARDUINO_ARCH_SAM) //Arduino Due

static const char* arduino_arch = “sam”;

#else

static const char* arduino_arch = “unknown”;

#endif

// Changed based on the number of input define the Analog inputs

static const uint8_t analog_input_map = {

#if (NUM_ANALOG_INPUTS>0)

A0

#endif

#if (NUM_ANALOG_INPUTS>1)

,A1

#endif

#if (NUM_ANALOG_INPUTS>2)

,A2

#endif

#if (NUM_ANALOG_INPUTS>3)

,A3

#endif

#if (NUM_ANALOG_INPUTS>4)

,A4

#endif

#if (NUM_ANALOG_INPUTS>5)

,A5

#endif

#if (NUM_ANALOG_INPUTS>6)

,A6

#endif

#if (NUM_ANALOG_INPUTS>7)

,A7

#endif

#if (NUM_ANALOG_INPUTS>8)

,A8

#endif

#if (NUM_ANALOG_INPUTS>9)

,A9

#endif

#if (NUM_ANALOG_INPUTS>10)

,A10

#endif

#if (NUM_ANALOG_INPUTS>11)

,A11

#endif

#if (NUM_ANALOG_INPUTS>12)

,A12

#endif

#if (NUM_ANALOG_INPUTS>13)

,A13

#endif

#if (NUM_ANALOG_INPUTS>14)

,A14

#endif

#if (NUM_ANALOG_INPUTS>15)

,A15

#endif

#if (NUM_ANALOG_INPUTS>16)

,A16

#endif

#if (NUM_ANALOG_INPUTS>17)

,A17

#endif

#if (NUM_ANALOG_INPUTS>18)

,A18

#endif

#if (NUM_ANALOG_INPUTS>19)

,A19

#endif

#if (NUM_ANALOG_INPUTS>20)

,A20

#endif

#if (NUM_ANALOG_INPUTS>21)

,A21

#endif

#if (NUM_ANALOG_INPUTS>22)

,A22

#endif

#if (NUM_ANALOG_INPUTS>23)

,A23

#endif

#if (NUM_ANALOG_INPUTS>24)

,A24

#endif

#if (NUM_ANALOG_INPUTS>25)

,A25

#endif

#if (NUM_ANALOG_INPUTS>26)

,A26

#endif

#if (NUM_ANALOG_INPUTS>27)

,A27

#endif

#if (NUM_ANALOG_INPUTS>28)

,A28

#endif

#if (NUM_ANALOG_INPUTS>29)

,A29

#endif

#if (NUM_ANALOG_INPUTS>30)

,A30

#endif

#if (NUM_ANALOG_INPUTS>31)

,A31

#endif

};

static const uint8_t digital_pin_map = {

#if defined(ARDUINO_ESP8266_WEMOS_D1MINI)

D0, D1, D2, D3, D4, D5, D6, D7, D8

#elif defined(ARDUINO_ESP8266_NODEMCU)

D0, D1, D2, D3, D4, D5, D6, D7, D8, D9, D10

#else

#ifdef D0

D0

#endif

#ifdef D1

,D1

#endif

#ifdef D2

,D2

#endif

#ifdef D3

,D3

#endif

#ifdef D4

,D4

#endif

#ifdef D5

,D5

#endif

#ifdef D6

,D6

#endif

#ifdef D7

,D7

#endif

#ifdef D8

,D8

#endif

#ifdef D9

,D9

#endif

#ifdef D10

,D10

#endif

#ifdef D11

,D11

#endif

#ifdef D12

,D12

#endif

#ifdef D13

,D13

#endif

#ifdef D14

,D14

#endif

#ifdef D15

,D15

#endif

#ifdef D16

,D16

#endif

#ifdef D17

,D17

#endif

#ifdef D18

,D18

#endif

#ifdef D19

,D19

#endif

#ifdef D20

,D20

#endif

#ifdef D21

,D21

#endif

#ifdef D22

,D22

#endif

#ifdef D23

,D23

#endif

#ifdef D24

,D24

#endif

#ifdef D25

,D25

#endif

#ifdef D26

,D26

#endif

#ifdef D27

,D27

#endif

#ifdef D28

,D28

#endif

#ifdef D29

,D29

#endif

#ifdef D30

,D30

#endif

#ifdef D31

,D31

#endif

#endif

};

#endif