An ESP32-Based Doorbel using LoRa (1) - Setup

Introduction

Ever since I started out with poking around with ESP32's trying to do something with LoRa ¹ has been on my list. And because my doorbell kept breaking CONSTANTLY, I thought that I would just make my own... and if it could have kilometres of range, well, why shouldn't it?! So I went on AliExpress and bought some parts...

I have gone through a lot of trial and error to get to where I am at the moment. The biggest problem was that I bought the LoRa modules before I stopped using Arduino and switched over to MicroPython for development on the ESP32. I did so because of a few reasons: 1) I never actually learned C++ so Arduino wasn't HUGELY familiar, albeit some secondary-school C and some JavaScript mostly got me there, but there came a point where even writing to an SD Card seemed to be such a huge challenge that I just gave up on it; and 2) I was learning Python anyway and my good friend Iohannes could help me with it, as he is fluent in French, Latin, and (u)Python; 3) It's so much faster to work with uPython; it's very rewarding.

The biggest problem I think I faced was that I ordered the wrong chipset tho for uPython + ESP32. Arduino supports the RFM95W cards I bought, but MicroPython, as I discovered, likes working with SX127x cards most. This really did cause quite a lot of problems, because there were SO many times when I thought I hooked up everything correctly and the code ought to have worked, but didn't. I should have been more suspicious really: for example there were a number of boards where I saw mysterious things like "DIO0" pins and "NSS" pins, but at times the code made no mention of these at all... I went through about 4-5 libraries that promised to make it function but I had no success with any of them. Until I discovered that there's an "official" uPython project called "micropython-lib" ² that has a "lora" library. But micropython-lib/lora only really supports SX126x and SX127x chipsets; so I had to go online and buy some again... Fortunately they were not very expensive at all.

Another slightly frustrating thing was working with cheap led-free solder. Gracious me, it was HORRIBLE. It just kept crumbling and crumbling -- what ought to have taken 3-4 minutes to connect up often took more like 10. There were times when I had to entirely re-do things because I was getting failed solder joints, etc. So I ordered some 60/40 solder from China, and, while I do think I was sold more like a 40/60 (the tin content is probably much higher than it ought to be), it's still an upgrade to work with it.

It's also a work in progress. I am learning as I go!

  1. Before you start

anything, check on what frequencies you are allowed to use. LoRa is available over 3 frequencies: 915mHz, 863mHz, and 433mHz. There is a list on TheThingsNetwork's website of what country allows which frequencies. ³ Where I am, 863 and 433 are available for the public, with some caveats.

  1. Parts list

ESP32 Boards:

I chose Wemos' Lolin ESP32 boards beacuse they are great value for money, plus there are also so many little add-ons for the Mini, they seem quite useful (like prototype shields).

=> Wemos ESP32 S3 Pro (I was planning for this to become the receiver, as it's more advanced and has things like an SD card slot) | S3 Pro documentation

=> Wemos ESP32 C3 Pico (This is nice and small and thus really quite convenient for a small doorbell) | C3 Pico documentation

LoRa modules:

=> SX1276 modules (check if these are correct for you frequency-wise, you may need another SX127x board)

Other bits and pieces

=> Prototype boards
=> Some t̶i̶n̶y̶ ̶r̶o̶b̶o̶t̶ ̶p̶u̶p̶p̶i̶e̶s̶ tactile momentary buttons

  1. Wiring schematics

These LoRa modules communicate over SPI, so you need to consult your board's schematics to see which pin is what exactly. Another lesson hard learned: always go to your manufacturer's website and DO NOT JUST GOOGLE ABOUT, because you will get wrong information. Wiring up your board to the wrong pins can sometimes have hilarious effects, like, when I accidentally hooked one data line to the LED, that was quite interesting. Sometimes if you end up just using random GPIO pins, you can use SoftSPI to still make it work; but it's much much better to use the actual SPI pins.

If you do not know the SPI pins, you can write a little script in MicroPython to help:

from machine import Pin, SPI

###
# Enumerate pins and print their state
###

for i in range(50):
    try:
        currentpin = Pin(i)
        print(f"{currentpin}: {currentpin.value()}")
    except:
        pass

###
# Enumerate SPI interfaces and print their pins
###

for i in range(0,3):
    try:
        print(SPI(i))
    except:
        pass

For reference, my SPI pins are:

ESP32C3 Pico:

ESP32S3 Pro:

On top of these, you will need to connect the following LoRa pins to some available GPIO pins:

These can be connected to any GPIO.

=> Here are some wiring diagrams. (Look at it, it has ASCII art and all).

  1. Software

Tools

!!  IF YOU ARE ON MACOS, then there is a set of drivers
    you will need to install to be able to access the 
    serial ports over USB. I don't know much more. 

!!  IF YOU ARE ON WINDOWS, then you are on your own, sorry.
    I just haven't used Windows in over a decade.

(i) On Linux / Unix systems, make sure that your user is a
    member of the `dialout` (Linux) or `dialer` (BSD) group.

First and foremost you will need esptool.py for flashing, ⁴ and the correct firmware images.

Esptool.py is a pretty well-known tool and so it's often available in standard package managers.

$ doas pkg_add py3-esptool

But failing that, it's also available in pip / pipx:

$ pipx install esptool

You will also want to use something to copy files across to your board. There are various choices: from the MicroPython-specific Thonny IDE ⁵. I use rshell and I will be using that in any examples. ⁶

Finally, you will also want to have mpremote ⁷ installed because it helps with installing libraries onto the board later:

$ pipx install mpremote

Firmware

On first try I downloaded the "stock" ESP32C3 / S3 firmware .bin files from the MicroPython website ⁸ but I discovered that Wemos has published their own MicroPython flash binaries on GitHub, which is amazing. ⁹ So get the latter -- go specific if you can avoid going generic. At time of writing the filenames are:

firmware-LOLIN_C3_PICO-v1.23.0-220-g692763989.bin
firmware-LOLIN_S3_PRO-v1.23.0-220-g692763989.bin

Libraries

Micropython-lib has a specific LoRa library. Which is absolutely fantastic news, because honestly, it is HARD to find reliable stuff for uPython and LoRa, as I already hinted above. You can get the LoRa library on GitHub; ¹⁰ but if you have mpremote installed, then it is easier to directly install the libraries on the board using mip:

$ mpremote mip install lora lora-sync lora-sx127x

Of course you can also install lora-async if that is how you want to go.

  1. Putting it all together!

  1. Wire up the board. Correctly. Unlike I did SO many times. If using a Protoboard, one mistake I kept running iinto was accidentally blocking ports. I managed to mount the ESP32S3 Pro in such a way that the LoRa module is kindof in the way of the USB-C port. This is a stupid mistake and easily avoided if you place the ESP32 on the bottom of the board, with USB connector facing out, and mount the LoRa module ABOVE the ESP. The other problem that is easy to run into is to forget to mount the antenna... Do that first, before you would connect an wires.

  1. Erase the stock firmware on the ESP32.

It's a good idea to start monitoring your USB devices at this stage. You will see changes to the device tree as the ESP32 enters flashing mode.

$ tail -f /var/log/messages

To start messing with the flash memory and firmware and stuff, put the board in DFU / flashing mode. To do this, unplug the board, hold down the BOOT button, plug in the board while holding BOOT, hold down RESET briefly alongside BOOT, release RESET, release BOOT. Your messages should give you something like this:

Oct 16 22:31:59 sul4co /bsd: umodem0 detached
Oct 16 22:32:04 sul4co /bsd: umodem0 at uhub1 port 3 configuration 1 interface 0 "Espressif USB JTAG/serial debug unit" rev 2.00/1.01 addr 5
Oct 16 22:32:04 sul4co /bsd: umodem0: data interface 1, has CM over data, has no break
Oct 16 22:32:04 sul4co /bsd: umodem0: status change notification available
Oct 16 22:32:04 sul4co /bsd: ucom0 at umodem0: usb0.1.00003.1
Oct 16 22:32:04 sul4co /bsd: ugen2 at uhub1 port 3 configuration 1 "Espressif USB JTAG/serial debug unit" rev 2.00/1.01 addr 5
Oct 16 22:32:05 sul4co /bsd: ucom0 detached
Oct 16 22:32:05 sul4co /bsd: umodem0 detached
Oct 16 22:32:05 sul4co /bsd: ugen2 detached
Oct 16 22:32:07 sul4co /bsd: umodem0 at uhub1 port 3 configuration 1 interface 0 "Espressif USB JTAG/serial debug unit" rev 2.00/1.01 addr 5
Oct 16 22:32:07 sul4co /bsd: umodem0: data interface 1, has CM over data, has no break
Oct 16 22:32:07 sul4co /bsd: umodem0: status change notification available
Oct 16 22:32:07 sul4co /bsd: ucom0 at umodem0: usb0.1.00003.1
Oct 16 22:32:07 sul4co /bsd: ugen2 at uhub1 port 3 configuration 1 "Espressif USB JTAG/serial debug unit" rev 2.00/1.01 addr 5

If you can see "serial debug unit" rather than just "Espressif Systems Espressif Device", you're golden.

Then run:

$ esptool.py --chip esp32s3 --port /dev/ttyU0 erase_flash

See the help page of esptool.py for details and chipsets and stuff.

  1. Flash the micropython firmware:

You should still be in DFU mode after successfully erasing the flash, but if not, repeat the above boot procedure. Then you want to run esptool.py again and tell it to write_flash:

$ esptool.py --chip esp32s3 --port /dev/ttyU0 write_flash -z 0x0 ~/path/to/firmware-LOLIN_S3_PRO-v1.23.0-220-g692763989.bin

Don't forget the -z 0x0 part; that specifies that we want to start writing the firmware from the 0th block -- replacing the bootloader.

  1. Connect to your MicroPython board!

Finally!

$ rshell --port /dev/ttyU0
[...snip...]
Welcome to rshell. Use Control-D (or the exit command) to exit rshell.

Here you can issue various commands. Try running repl and issue some Python commands for fun. Use Ctrl-X to exit the Python prompt.

In rshell the prompt is showing you the local directory on your computer that you are working in; to copy stuff over to the ESP you need to copy them to /pyboard/:

/home/kls> cp ~/foo.py /pyboard/bar.py

  1. Install uPython libraries

Press reset if need be on the board, then install the libraries you will need using mip and mpremote running the command above. Mpremote will then download the needed libaries for you and copy them over to the board.

  1. Test

Among the MicroPython-lib files on GitHub there is an example file, called simple_rxtx.py. ¹¹ Give it a go. Uncomment the stuff in the get_modem() function, comment out the raise line, specify correct Pin numbers abd SPI interface.

Copy the file over to your ESP32 using rshell. (See above).

For testing I like to run things directly in the repl:

/home/kls> repl
Entering REPL. Use Control-X to exit.
>
MicroPython v1.23.0-220.g6927639898 on 2024-07-11; LOLIN S3 PRO with ESP32S3
Type "help()" for more information.
>>> 
>>> import simple_rxtx
>>> simple_rxtx.main()

If you get to "Sending..." and it doesn't crash after "Initializing" then you should be happy!

Now, if you wire up ANOTHER board and install stuff the same way, then you will be able to send messages from one to another!

=> Like so!

As you can see from the image above, it WORKS, but it's sending a lot of garbage. It is recognisably trying to send "Hello world from MicroPython #42" but there seems to be quite a lot of noise.

Go and read up a little in the GitHub page for the micropython-lib LoRa library. There are some really good parameters to tweak here, such as bandwidth, SF, etc. I tried to optimise for range and reliability over transmission speed, so here's what I came up with:

def get_modem():
    from lora import SX1276
    lora_cfg = {
            "freq_khz": 868000,
            "sf": 12,
            "bw": "62.5",  # kHz
            "coding_rate": 8,
            "preamble_len": 12,
            "output_power": 20,  # dBm
            "auto_image_cal" : True,
            "syncword": 0x12,
            "tx_ant": "PA_BOOST",
            "rx_boost": True,
    }

As you can see, I set the frequency to 868, because that's what the law says over here.

I set "SF" to the maximum because

Setting sf to maximum (higher "Spreading Factor") means each LoRa "chirp" takes longer, for more range but lower data rate.

BandWidth was dropped from initial 500 to 62.5 as:

Setting bw bandwidth setting lower makes the signal less susceptible to noise, but again slower. 62.5kHz is the lowest setting recommended by Semtech unless the modem uses a TCXO for high frequency stability, rather than a cheaper crystal.

NB: when specifying "floats" here the devs recommend specifying them as strings and not as floats / numbers! (You know why...) ¹

So, here we are at the moment. Some further work will be necessary to make sure transmission doesn't randomly fail. But I can send packets over the air now, and hopefully, so can you!

TBC as and when...


=> ² LoRa Alliance | ³ micropython-lib on GitHub | ⁴ The Things Network: Frequency Plans by Country | ⁵ Esptool.py Installation | ⁶ Thonny | ⁷ rshell on GitHub | ⁸ mpremote | ⁹ Generic MicroPython firmware files for the ESP32 | ¹⁰ WeMos Lolin ESP32 MicroPython firmware files on GitHub | Micropython-lib: LoRa on GitHub | simple_rxtx.py | ¹¹ The famous Python Floating Point weirdness

Proxy Information
Original URL
gemini://vigilia.cc/?p=gemlog%2F2024%2F2024-10-16+An+ESP32-based+doorbell+using+LoRa.gmi
Status Code
Success (20)
Meta
text/gemini; charset=utf-8; lang=en-GB
Capsule Response Time
546.84828 milliseconds
Gemini-to-HTML Time
6.924127 milliseconds

This content has been proxied by September (3851b).