Although it's years since I have used Arduino code (as I currently use Micro-Python or Tasmota) - I managed to find an old script for a Sonoff S20 using an ESP8266 so you can compare YOUR script against it and hopefully find where the discrepancy is located.
Apart from the above and this listing - I'm out of ideas for now - sorry.
/*
* Sonoff Basic / S20 โ MQTT + Node-RED Controller
* Board : Generic ESP8266 (or "ITEAD Sonoff" if installed)
* Flash : 1 MB, DOUT mode
*
* Wiring / Pins (Sonoff Basic):
* GPIO0 โ Button (active-LOW, with internal pull-up)
* GPIO12 โ Relay (HIGH = ON)
* GPIO13 โ LED (active-LOW)
*
* Dependencies (install via Library Manager):
* - PubSubClient by Nick O'Leary
* - ArduinoJson v6.x (optional, for richer payloads)
*
* MQTT Topics:
* Subscribe : cmnd/<DEVICE_ID>/power โ payload "ON" / "OFF" / "TOGGLE"
* Publish : stat/<DEVICE_ID>/power โ "ON" / "OFF"
* Publish : tele/<DEVICE_ID>/state โ JSON heartbeat every 30 s
*
* Node-RED flow hint:
* [inject "ON"] โ [mqtt out: cmnd/sonoff01/power]
* [mqtt in: stat/sonoff01/power] โ [debug / dashboard switch]
*/
#include <ESP8266WiFi.h>
#include <PubSubClient.h>
// โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ
// USER CONFIG โ edit these
// โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ
const char* WIFI_SSID = "YourSSID";
const char* WIFI_PASSWORD = "YourWiFiPassword";
const char* MQTT_SERVER = "192.168.1.100"; // broker IP or hostname
const int MQTT_PORT = 1883;
const char* MQTT_USER = ""; // leave blank if not required
const char* MQTT_PASS = "";
const char* DEVICE_ID = "sonoff01"; // unique per device
// โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ
// PIN DEFINITIONS
// โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ
#define PIN_RELAY 12
#define PIN_LED 13
#define PIN_BUTTON 0
// โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ
// TOPIC HELPERS
// โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ
String topicCommand; // cmnd/<id>/power
String topicStat; // stat/<id>/power
String topicTele; // tele/<id>/state
// โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ
// GLOBALS
// โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ
WiFiClient wifiClient;
PubSubClient mqtt(wifiClient);
bool relayState = false;
bool lastButtonState = HIGH;
unsigned long lastTele = 0;
const unsigned long TELE_INTERVAL = 30000; // ms
// โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ
// HELPERS
// โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ
void setRelay(bool on) {
relayState = on;
digitalWrite(PIN_RELAY, on ? HIGH : LOW);
digitalWrite(PIN_LED, on ? LOW : HIGH); // LED active-LOW
mqtt.publish(topicStat.c_str(), on ? "ON" : "OFF", true); // retained
Serial.printf("[RELAY] %s\n", on ? "ON" : "OFF");
}
void publishTelemetry() {
char buf[128];
snprintf(buf, sizeof(buf),
"{\"device\":\"%s\",\"power\":\"%s\",\"rssi\":%d,\"uptime\":%lu}",
DEVICE_ID,
relayState ? "ON" : "OFF",
WiFi.RSSI(),
millis() / 1000
);
mqtt.publish(topicTele.c_str(), buf);
Serial.printf("[TELE] %s\n", buf);
}
// โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ
// MQTT CALLBACK
// โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ
void mqttCallback(char* topic, byte* payload, unsigned int length) {
String msg;
for (unsigned int i = 0; i < length; i++) msg += (char)payload[i];
msg.trim();
Serial.printf("[MQTT IN] %s โ %s\n", topic, msg.c_str());
if (String(topic) == topicCommand) {
if (msg.equalsIgnoreCase("ON")) setRelay(true);
else if (msg.equalsIgnoreCase("OFF")) setRelay(false);
else if (msg.equalsIgnoreCase("TOGGLE")) setRelay(!relayState);
}
}
// โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ
// Wi-Fi
// โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ
void connectWiFi() {
Serial.printf("\n[WiFi] Connecting to %s", WIFI_SSID);
WiFi.mode(WIFI_STA);
WiFi.begin(WIFI_SSID, WIFI_PASSWORD);
while (WiFi.status() != WL_CONNECTED) {
delay(500);
Serial.print(".");
// blink LED while connecting
digitalWrite(PIN_LED, !digitalRead(PIN_LED));
}
digitalWrite(PIN_LED, HIGH); // LED off (active-LOW)
Serial.printf("\n[WiFi] Connected โ IP: %s\n", WiFi.localIP().toString().c_str());
}
// โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ
// MQTT
// โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ
void connectMQTT() {
while (!mqtt.connected()) {
Serial.printf("[MQTT] Connecting to %s:%d โฆ ", MQTT_SERVER, MQTT_PORT);
String clientId = String("sonoff-") + DEVICE_ID;
// Last-will message so Node-RED knows if the device drops off
bool ok = mqtt.connect(
clientId.c_str(),
MQTT_USER, MQTT_PASS,
topicStat.c_str(), 1, true, "OFFLINE"
);
if (ok) {
Serial.println("connected");
mqtt.subscribe(topicCommand.c_str());
// Announce current state
mqtt.publish(topicStat.c_str(), relayState ? "ON" : "OFF", true);
} else {
Serial.printf("failed (rc=%d) โ retry in 5 s\n", mqtt.state());
delay(5000);
}
}
}
// โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ
// SETUP
// โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ
void setup() {
Serial.begin(115200);
// Build topic strings once
topicCommand = String("cmnd/") + DEVICE_ID + "/power";
topicStat = String("stat/") + DEVICE_ID + "/power";
topicTele = String("tele/") + DEVICE_ID + "/state";
pinMode(PIN_RELAY, OUTPUT);
pinMode(PIN_LED, OUTPUT);
pinMode(PIN_BUTTON, INPUT_PULLUP);
digitalWrite(PIN_RELAY, LOW);
digitalWrite(PIN_LED, HIGH); // off
connectWiFi();
mqtt.setServer(MQTT_SERVER, MQTT_PORT);
mqtt.setCallback(mqttCallback);
mqtt.setKeepAlive(60);
connectMQTT();
publishTelemetry();
}
// โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ
// LOOP
// โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ
void loop() {
// Maintain connections
if (WiFi.status() != WL_CONNECTED) connectWiFi();
if (!mqtt.connected()) connectMQTT();
mqtt.loop();
// Physical button โ toggle on falling edge
bool buttonNow = digitalRead(PIN_BUTTON);
if (lastButtonState == HIGH && buttonNow == LOW) {
delay(50); // debounce
if (digitalRead(PIN_BUTTON) == LOW) {
setRelay(!relayState);
}
}
lastButtonState = buttonNow;
// Periodic telemetry
if (millis() - lastTele >= TELE_INTERVAL) {
lastTele = millis();
publishTelemetry();
}
}