Using Sonoff basic switches with own firmware

15 Mar 2019 - tsp
Last update 18 Jul 2020
Reading time 10 mins

Since I’ve been using them now for a while I’ve decided to collect that information somwhere …

The Sonoff basic is an ESP8285 based 230V 10A AC switching device that includes an local 3.3V power supply. The ESP8285 offers WiFi connectivity (currently only WPA-PSK modes are supported, some 802.1x setups using EAP-PEAP with MS-CHAP v2 do also work; some people even claim to have got connections to EAP-TLS based 802.1x networks but there seem to be problems with the currently available SDKs and EAP in general). Additionally the devices bring an LED as well as an push button and a single useable GPIO (revision 1 boards allowed soldering a pin header there, revision 2 devices only allow soldering to an surface pad). Mains current is switched via an classical mechanical relais which limits inrush current and reactive loads (if not compensated) that the switch is capable to operate.

These switches are readily available (you can easily get them from Amazon in small, medium and large quantities )

Unfortunately not the whole SDK is open sourced - but most parts are. One requires some binary blobs to interact with the ESP8266 / ESP8285 - and the SDK also uses some proprietary ROM routines. Nevertheless it’s currently one of the most easily accessible and most open embedded WiFi controllers available.

It provides an easy way of switching mains loads in existing locations when someone is not capable of wiring up a wired network (though it’s possible - and I’m using them sometimes that way - to attach them to an network like 1-wire or an RS485 network. The latter one requires external drivers and the first one a 3.3V instead of a 5V 1wire network or some external level shifting mechanism though).

Device as delivered

Note that it is of course not possible to use the switches for other applications than switching AC mains current - if one wants to use the builtin power supply. If one doesn’T require this PSU there are of course cheaper or more easily useable relais solutions.

The ESP8285 uses an 32 Bit Xtensa LX106 core (by Tensilica) which is clocked (at the Sonoff basic clocked at 80 MHz). The main difference between the ESP8285 and the well known ESP8266 is the 1 MByte on chip flash - which allows devices to be even smaller than ESP8266 based ones. Both devices boot from an integrated boot-ROM that also includes some functions used by the SDK.


Because the switch is a mains current switch one should ahere to the standard safety rules when manipulating mains current devices:

Here strain relief is missing - and an external case or enclosure

Sonoff basic used for plant lighting using LEDs (lower switch and left power supply) and fluorescent lamps (upper switch) in an small geenhouse. Additional 5V power supply for an RaspberryPi and sensor boards is installed.

GPIO usage

The sonoff basic only directly exposes four functional GPIO pins:

Flashing firmware

Flashing firmware is done via esptool. This tool allows flashing via a serial connection (the ROM bootloader of the ESP8285 allows flashing any firmware via a serial protocol) or via IP (OTA updates). Using OTA requires OTA capable firmware. Note that using OTA is of course a good idea - but cuts the available flash memory into halve it’s size. Even though 1 MByte of flash seems to be large there is a complete WiFi stack that has to be embedded and in many cases people also want so support local webpages, hotspot functionality, etc.

To flash firmware connect the TX, RX, GND and VCC pins to any serial adapter that support CMOS levels (i.e. runs on 3.3V). Do never ever attach any part of the circuit to 5V logic, powersupply or even RS232 ports.

Using jumper wires without pogo pins may work. Sometimes

To attach the pins one can use:

To enter programming mode press the button during powering up the device. The devices does not show any indication of entering programming mode but will respond to esptool.

One can then flash the firmware. Depending on the development environment one uses one has to call esptool or use for example the Arduino IDE (the latter one is nice to get started with the ESP8285 and ESP8266 when one doesn’t care about the internal workings of the SDK and doesn’t want to produce the smallest possible firmware / doesn’t need build automation).

Using esp-open-sdk and esptool

This is the preferred way when not using an learning / tutorial IDE like ArduinoIDE (for example when requiring full and direct control over the used code, not wanting any abstraction above the SDK, having licensing reasons, wanting build automation, etc.). One flashes the firmware with esptool and is capable of compiling software using the esp-open-sdk (which is the preferred way of developing using the most open SDK that’s currently possible). The SDK (along with Linux deployment scripts) is available at

Compilation is done using xtensa-lx106-elf-g++, the gcc variant targeted at the Xtensa LX106 processor core. Compiling (without linking) requires a bunch of compiler options. These are basically

-D__ets__ -DICACHE_FLASH -U__STRICT_ANSI__ -c -w  -Os -g -mlongcalls -mtext-section-literals -fno-exceptions -fno-rtti -falign-functions=4 -std=c++11  -ffunction-sections -fdata-sections -w -x c++ -E -CC -DF_CPU=80000000L -DLWIP_OPEN_SRC -DTCP_MSS=536 -DLED_BUILTIN=2 -DESP8266

Note that one also has to specify the include directory of the SDK.

Linking is done using xtensa-lx106-elf-gcc. One has to supply at least settings like

-w -Os -nostdlib -Wl,--no-check-sections -Wl,-static -Wl,--gc-sections -Wl,-wrap,system_restart_local -Wl,-wrap,spi_flash_read

and depending on the libraries used link against some of them - for example:

-lhal -lphy -lpp -lnet80211 -llwip2 -lwpa -lcrypto -lmain -lwps -lbearssl -laxtls -lespnow -lsmartconfig -lairkiss -lwpa2 -lstdc++ -lm -lc -lgcc

After that esptool can be used to build the binary image that will be flashed later on - for example when using eboot:

esptool -eo eboot.elf -bo output.bin -bm dout -bf 40 -bz 1M -bs .text -bp 4096 -ec -eo input.bin -bs .irom0.text -bs .text -bs .data -bs .rodata -bc -ec

To upload the generated image one uses esptool again. Upload can be done either via a serial port

esptool -vv -cd ck -cb 115200 -cp /dev/ttyU0 -ca 0x00000 -cf output.bin

or when one uses a mechanism like ArduinoOTA or a compatible mechanism for example via the script: -i IPADRESS -p 8266 -auth=PASSWORD -f output.bin

Using ArduinoIDE

First add the esp8266 boards package to the additional boards manager URLs (File > Preferences). It’s located at Then one can install the required package from Tools > Board > Boards Manager. It’s simply called esp8266.

After that settings to be used can be selected also from the tools menu:

Additional examples can be found under File > Examples (they’ve been added by the boards manager). It’s a good idea to flash an simple blink example together with enabled OTA functionality to be capable to flash the device via wireless. A simple basic example using ArduinoOTA is shown below:

#include <ESP8266WiFi.h>
#include <ESP8266mDNS.h>
#include <WiFiUdp.h>
#include <ArduinoOTA.h>

#define LEDPORT 13
#define RELAISPORT 12
#define SWITCHPORT 0

const char* ssid = "{FILLIN_WIFI_SSID}";
const char* password = "{FILLIN_WIFI_PASSWORD}";

void setup() {

  WiFi.begin(ssid, password);
  while (WiFi.waitForConnectResult() != WL_CONNECTED) {
    Serial.println("Connection Failed! Rebooting...");

  // Port defaults to 8266
  // ArduinoOTA.setPort(8266);

  // Hostname defaults to esp8266-[ChipID]
  // ArduinoOTA.setHostname("{FILLIN_HOSTNAME}");

  // No authentication by default
  // ArduinoOTA.setPassword("{FILLIN_OTAPASSWORD}");

  ArduinoOTA.onStart([]() { Serial.println("Start updating"); });
  ArduinoOTA.onEnd([]() { Serial.println("\nEnd"); });

  ArduinoOTA.onProgress([](unsigned int progress, unsigned int total) {
    Serial.printf("Progress: %u%%\r", (progress / (total / 100)));

  ArduinoOTA.onError([](ota_error_t error) {
    Serial.printf("Error[%u]: ", error);
    if (error == OTA_AUTH_ERROR) {
      Serial.println("Auth Failed");
    } else if (error == OTA_BEGIN_ERROR) {
      Serial.println("Begin Failed");
    } else if (error == OTA_CONNECT_ERROR) {
      Serial.println("Connect Failed");
    } else if (error == OTA_RECEIVE_ERROR) {
      Serial.println("Receive Failed");
    } else if (error == OTA_END_ERROR) {
      Serial.println("End Failed");

  Serial.print("IP address: ");

static boolean ledStatus = false;

void loop() {
  unsigned long currentMillis = millis();
  if((currentMillis % 1000) > 500) {
    if(!ledStatus) {
      ledStatus = true;
      digitalWrite(LEDPORT, LOW);
  } else {
    if(ledStatus) {
      ledStatus = false;
      digitalWrite(LEDPORT, HIGH);


Note that there hsould be no delays inside setup or loop (except really small delays not solveable by any other means) because that would trigger the watchdog timer to timeout, wireless not working correctly, etc.

To use OTA one can (after initial flash and a power cycle of the target!) select the Board from the tools menue - it will appear under the network ports section (this only works when the device is up and running in the same network segment when the ArduinoIDE is starting up! It does not update itself during runtime).

Just a word of caution when you’re using OTA in an automated fashion like me (for example when using automated build and deployment via Jenkins or any other pipeline) - if you implemented automatic updating do not forget to implement an test that checks if the devices are flashable twice (once from the old and once from the new firmware) to not lock yourself out of hundreds of deployed devices …

This article is tagged:

Data protection policy

Dipl.-Ing. Thomas Spielauer, Wien (

This webpage is also available via TOR at http://rh6v563nt2dnxd5h2vhhqkudmyvjaevgiv77c62xflas52d5omtkxuid.onion/

Valid HTML 4.01 Strict Powered by FreeBSD IPv6 support