Fork me on GitHub

Project Notes

#812 ESP8266 (ESP-12E) IoT Temperature Sensor

Using MicroPython on an ESP-12E (4Mib ESP8266) to capture DS18S20 temperature measurements and log continuous readings to a cloud data collection service (Ubidots).

Build

Notes

This project expands on LEAP#811 ESP8266 (ESP-01) IoT Temperature Sensor by porting the concept to an ESP-12E ESP8266 module, mounted on an adapter board.

Critically, the ESP-12E has 4Mib of flash so we get a full featured MicroPython environment. This allows me to address key limitations in the ESP-01 demo:

  • install the python script to automatically run after boot
  • use HTTPS/TLS for web requests
  • access to more convenient libraries such as urequests

NB: I purchased the ESP-12E module for US$1.92 in Mar-2016, currently listing for SG$1.39 in Jan-2026. The adapters were US$1.25 for 10 in Oct-2017, currently listing for SG$1.70 for 10 in Jan-2026.

esp-12e-board

Loading MicroPython

For the initial programming, I am using the LEAP#540 ESP-12 DIY Dev Board:

programming

Testing the Connection

First find the device the CH340G-based USB to UART adapter is connected on, and set that as a variable for later use:

$ export ESP_PORT=$(ls /dev/tty.wchusb*)
$ echo ${ESP_PORT}
/dev/tty.wchusbserial2420

Verify the esptool install and connection by getting the chip_id. This requires a switch into flash mode and reset on the dev board before it responds…

$ esptool.py --port ${ESP_PORT} chip_id
esptool.py v4.8.1
Serial port /dev/tty.wchusbserial2420
Connecting...
Detecting chip type... Unsupported detection protocol, switching and trying again...
Connecting...
Detecting chip type... ESP8266
Chip is ESP8266EX
Features: WiFi
Crystal is 26MHz
MAC: 18:fe:34:d4:7a:a5
Stub is already running. No upload is necessary.
Chip ID: 0x00d47aa5
Hard resetting via RTS pin...
$ esptool.py --port ${ESP_PORT} flash_id
esptool.py v4.8.1
Serial port /dev/tty.wchusbserial2420
Connecting....
Detecting chip type... Unsupported detection protocol, switching and trying again...
Connecting...
Detecting chip type... ESP8266
Chip is ESP8266EX
Features: WiFi
Crystal is 26MHz
MAC: 18:fe:34:d4:7a:a5
Uploading stub...
Running stub...
Stub running...
Manufacturer: e0
Device: 4016
Detected flash size: 4MB
Hard resetting via RTS pin...

Flashing with MicroPython

Downloading the latest generic build:

wget https://micropython.org/resources/firmware/ESP8266_GENERIC-20251209-v1.27.0.bin

Erasing then flash first (as recommended, probably not really necessary). Again, this requires a switch into flash mode and reset on the dev board before it responds…

$ esptool.py --port ${ESP_PORT} erase_flash
esptool.py v4.8.1
Serial port /dev/tty.wchusbserial2420
Connecting...
Detecting chip type... Unsupported detection protocol, switching and trying again...
Connecting...
Detecting chip type... ESP8266
Chip is ESP8266EX
Features: WiFi
Crystal is 26MHz
MAC: 18:fe:34:d4:7a:a5
Stub is already running. No upload is necessary.
Erasing flash (this may take a while)...
Chip erase completed successfully in 11.0s
Hard resetting via RTS pin...
$ esptool.py --port ${ESP_PORT} --baud 115200 write_flash --flash_size=detect 0 ESP8266_GENERIC-20251209-v1.27.0.bin
esptool.py v4.8.1
Serial port /dev/tty.wchusbserial2420
Connecting....
Detecting chip type... Unsupported detection protocol, switching and trying again...
Connecting...
Detecting chip type... ESP8266
Chip is ESP8266EX
Features: WiFi
Crystal is 26MHz
MAC: 68:c6:3a:85:3a:41
Uploading stub...
Running stub...
Stub running...
Configuring flash size...
Auto-detected Flash size: 4MB
Flash will be erased from 0x00000000 to 0x0009cfff...
Flash params set to 0x0040
Compressed 640832 bytes to 428824...
Wrote 640832 bytes (428824 compressed) at 0x00000000 in 37.7 seconds (effective 135.9 kbit/s)...
Hash of data verified.

Leaving...
Hard resetting via RTS pin...

Testing the MicroPython REPL

Connecting with screen and trying some python commands… all looking good!

$ screen ${ESP_PORT} 115200
>>> import os
>>> os.uname()
(sysname='esp8266', nodename='esp8266', release='2.2.0-dev(9422289)', version='v1.27.0 on 2025-12-09', machine='ESP module with ESP8266')
>>>

Ubidots

I had selected Ubidots as a decent service to use for IoT in 2026. In the previous tests using an ESP-01, I was constrained to HTTP only.

Let’s first revise some simple Ubidots tests with HTTPS. The ubidots.py script is an updated implementation that:

  • uses the Ubidits HTTPS/TLS API endpoints
  • uses urequests for a simpler high-level interaction
  • demonstrates using the permanent API key to generate temporary API tokens
    • if a request returns 401, it indicates a new token is required
  • demonstrates posting data to the API
import urequests
import ujson

UBIDOTS_API_KEY="%{UBIDOTS_API_KEY}%"

def get_new_token():
    url = "https://industrial.api.ubidots.com/api/v1.6/auth/token"
    headers = {
        "x-ubidots-apikey": UBIDOTS_API_KEY,
        "Content-Type": "application/json"
    }
    resp = urequests.post(
        url,
        headers=headers
    )
    print(resp.status_code)
    print(resp.text)
    try:
        token = ujson.loads(resp.text).get("token")
    except Exception:
        token = None
    resp.close()
    return token

def post_temperature(token, temperature):
    url = "https://industrial.api.ubidots.com/api/v1.6/devices/test-device"
    payload = {"temperature":{"value": temperature, "units": "C"}}

    headers = {
        "X-Auth-Token": token,
        "Content-Type": "application/json"
    }

    resp = urequests.post(
        url,
        data=ujson.dumps(payload),
        headers=headers
    )

    print(resp.status_code)
    print(resp.text)
    resp.close()
    return resp.status_code

token = get_new_token()
if token:
    post_temperature(token, 23.4)

I make the appropriate %{UBIDOTS_API_KEY}% substitution to a private file that cannot accidentally be checked into the repository. The resulting ubidots-private.py file contents are pasted directly into the ESP MicroPython REPL to execute.

sed "s|%{UBIDOTS_API_KEY}%|${UBIDOTS_API_KEY}|g" ubidots.py > ubidots-private.py

And now I’m seeing the data appear correctly in the Ubidots dashboard:

ubidots-1

Full Implementation

Now let’s put it all together:

  • measuring temperature
  • posting data to Ubidots
    • getting a fresh API token when required

Circuit Design

It’s quite straight-forward:

  • an LD1117-based 3.3V linear power supply
  • an ESP-12E on an adapter board
  • the DS18S20 temperature sensor

Designed with Fritzing: see esp-12-temperature-logger.fzz.

bb

schematic

I’m using the LEAP#540 ESP-12 DIY Dev Board for programming, but broken out onto a breadboard to easily allow the wiring of the DS18S20 on the 1-wire bus.

bb_build

The Program

Finally, we can do the thing properly. See log-temperature-to-ubidots.py for the full source. Key points:

  • defines a class UbidotsApi to wrap the API interactions
  • just requires the main API key to run
  • handles API token regeneration and expiry transparently
  • read_and_log_temps is the main method
  • I used demo_read_and_log_temps for verifying the API interaction without a sensor attached
import urequests
import ujson
import time
import onewire
import ds18x20
from machine import Pin

UBIDOTS_API_KEY="%{UBIDOTS_API_KEY}%"
ONEWIRE_PIN = 2

class UbidotsApi():

    @property
    def token(self):
        if not hasattr(self, '_token') or self._token is None:
            self._token = self.get_token()
        return self._token

    def get_token(self):
        url = "https://industrial.api.ubidots.com/api/v1.6/auth/token"
        headers = {
            "x-ubidots-apikey": UBIDOTS_API_KEY,
            "Content-Type": "application/json"
        }
        resp = urequests.post(
            url,
            headers=headers
        )
        print(resp.status_code)
        print(resp.text)
        try:
            token = ujson.loads(resp.text).get("token")
        except Exception:
            token = None
        resp.close()
        return token

    def _post_request(self, url, payload):
        headers = {
            "X-Auth-Token": self.token,
            "Content-Type": "application/json"
        }
        resp = urequests.post(
            url,
            data=ujson.dumps(payload),
            headers=headers
        )
        print(resp.status_code)
        print(resp.text)
        resp.close()
        if resp.status_code == 401:
            print("Unauthorized - token may have expired")
            self._token = None
        return resp.status_code

    def post_request(self, url, payload):
        status_code = self._post_request(url, payload)
        if status_code == 401:
            # retry once with new token
            print("Retrying with new token...")
            status_code = self._post_request(url, payload)
        return status_code

    def post_temperature(self, device_name, temperature):
        url = "https://industrial.api.ubidots.com/api/v1.6/devices/{}".format(device_name)
        payload = {
            "temperature": {
                "value": temperature, "units": "C"
            }
        }
        self.post_request(url, payload)

def read_and_log_temps():
    ds = ds18x20.DS18X20(onewire.OneWire( Pin(ONEWIRE_PIN)))
    roms = ds.scan()
    api = UbidotsApi()
    while True:
        ds.convert_temp()
        time.sleep(1)
        for rom in roms:
            device_name = 'esp8266-' + ''.join('{:02x}'.format(b) for b in rom)
            temperature = ds.read_temp(rom)
            print('Device:', device_name, 'Latest reading:', temperature)
            api.post_temperature(device_name, temperature)
        time.sleep(9)

def demo_read_and_log_temps():
    import random

    roms = [b'\xff\xff\xff\xff\x12\x34\x56\x78']
    api = UbidotsApi()
    while True:
        time.sleep(1)
        for rom in roms:
            device_name = 'demo-' + ''.join('{:02x}'.format(b) for b in rom)
            temperature = 20.0 + random.getrandbits(5) / 10.0
            print('Device:', device_name, 'Latest reading:', temperature)
            api.post_temperature(device_name, temperature)
        time.sleep(9)

read_and_log_temps()

I make the appropriate %{UBIDOTS_API_KEY}% substitution to a private file that cannot accidentally be checked into the repository. The resulting log-temperature-to-ubidots-private.py file contents are pasted directly into the ESP MicroPython REPL to execute.

sed "s|%{UBIDOTS_API_KEY}%|${UBIDOTS_API_KEY}|g" log-temperature-to-ubidots.py > log-temperature-to-ubidots-private.py

And I’m seeing the data appear correctly in the Ubidots dashboard:

ubidots-1

Using mpremote to install startup script

We don’t want to paste the python code into the ESP8266 repl every time it boots. Thankfully, the ESP-12E has enough memory that it supports a filesystem. If we load a file called main.py, it will be executed automatically after boot.

The mpremote tool provides a convenient way of managing the boot script.

First, install mpremote:

pip install mpremote

The connect command opens a console to the ESP8266. You will see whatever it is running…

mpremote connect

Finally, to copy the boot script from a local main.py file to :main.py on the ESP8266. The : signifies the remote file system.

mpremote fs cp log-temperature-to-ubidots.py :main.py

To remove the startup script later on:

mpremote fs rm :main.py

Deploying the Ubidots Script

Now that the script is proven to work OK, I can install it as the default program to run on boot for the ESP device.

I used the following command to do the required key substitution, send the file to the ESP-12, and cleanup:

sed "s|%{UBIDOTS_API_KEY}%|${UBIDOTS_API_KEY}|g" log-temperature-to-ubidots.py > log-temperature-to-ubidots-private.py
mpremote fs cp log-temperature-to-ubidots-private.py :main.py
rm log-temperature-to-ubidots-private.py

Now when the ESP is booted, it immediately starts logging…

ubidots-3

Making a Custom Protoboard

Transferring the circuit to a 3x7cm protoboard:

  • I’ve added an LD1117 linear regulator to take any supply above ~4V to 3.3V
  • headers to accept an ESP-12E module on adapter board
  • plug pins for a DS18S20

protoboard

protoboard-build

And we have results being logged continuously:

ubidots-4

A “Long” Running Test

So I left it running for a few hours and it kept on capturing nicely. Some explanations of the readings:

  • the monitor is running in my work area, which is often air-conditioned during the day
  • this is Singapore, but a relatively cool period

ubidots-5

Conclusions

I must admit I was somewhat underwhelmed when I first tried MicroPython back in 2016, but now I realise that was largely due to the fact that I was still trying to squeeze it all into the original ESP-01 512Kib form-factor.

That was unrealistic, and unfairly detracted from the MicroPython possibilities if you give it just a little more memory.

With an ESP-12E, I can totally see how this is a fantastic platform for embedded development. Minimal hardware, but we get many of the goodies normally associated with “normal” software development, in particular:

  • a higher lever language (python)
  • a repl actually on the device
  • mpremote for remote device abstraction and interaction

Credits and References

About LEAP#812 ESP8266MicroPythonDS18S20SensorsUbidots

This page is a web-friendly rendering of my project notes shared in the LEAP GitHub repository.

Project Source on GitHub Return to the LEAP Catalog
About LEAP

LEAP is my personal collection of electronics projects - usually involving an Arduino or other microprocessor in one way or another. Some are full-blown projects, while many are trivial breadboard experiments, intended to learn and explore something interesting.

Projects are often inspired by things found wild on the net, or ideas from the many great electronics podcasts and YouTube channels. Feel free to borrow liberally, and if you spot any issues do let me know or send a pull-request.

NOTE: For a while I included various scale modelling projects here too, but I've now split them off into a new repository: check out LittleModelArt if you are looking for these projects.

Project Gallery view the projects as an image gallery Notebook reference materials and other notes Follow the Blog follow projects and notes as they are published in your favourite feed reader