ESP32 Audio Kit with squeezelite-esp32

AI Thinker ESP32-A1S Audio Kit v 2.2 board.

No-name SSD1322 display, with 0-Ohm resistors set to 4-wire SPI mode.

KY040 rotary encoder.

squeezelite-esp32 firmware version ESP32-A1S.552.master-cmake

Much experimenting required to get a working permutation of GPIO selections. With assistance from the forums, I have eventually found that the following configuration:







DIP switch positions, reading left to right: Off/Off/Off/On/On.

Wired as follows:

LCDESP32 Audio Kitsqueezelite-esp32 parameter
1 – GroundGND
2 – 3V33V3
4 – SCLKIO13 (labelled MTCK)spi_config: clk=13
5 – SDINIO22spi_config: data=22
14 – DCIO5spi_config: dc=5
15 – ResetIO19*display_config: reset=19
16 – CSIO0display_config: cs=0
Rot EncESP32 Audio Kitsqueezelite-esp32 parameter
CLKIO18rotary_config: B=18
DTIO23rotary_config: A=23
SWIO15 (labelled MTDO)rotary_config: SW=15

gives a working display + rotary encoder.

When relocating Reset, I noticed that the display does not seem to require it.

Why is there some electrical tape on it? Because the LEDs on the board, particularly D1, are obnoxiously bright.

Amplification is provided by 2x NS1450, “a Low EMI, Filterless, 3W Mono Class D Audio Amplifier”, with the caveat being that it’s only filterless if the wire to the speakers is less than 100mm.

Todo: build an enclosure, acquire some suitable speakers, have a look at what types of batteries this thing can manage.

Bill of materials so far:

ESP32-A1S Audio Kit – £10

3.12″ SSD1322 OLED display – £16

3x JST XH 2-pin connectors – £1.8

12x DuPont jumpers – £2

1x HW040 rotary encoder – £0.8

Took quite a while to find a permutation of GPIOs that let the amp, rotary encoder and the display work simultaneously.

set_GPIO 21=amp,39=jack:0

It seems like there is some interaction between GPIOs that isn’t what you’d expect from the actual GPIO numbers.

Thoughts on this so far

First, the negatives:

Audio quality isn’t great. Higher frequencies are a bit mushy. Fine for building a little portable streaming speaker, though.

Documentation for this board is sparse. The manufacturer’s pages are all now 404s.

Green LED D1 is needlessly bright.

The GPIO assignments are bit mysterious [although to be fair, this is the first ESP32 project I’ve done]. Changing one thing causes others to mysteriously stop working. The 5 DIP switches on the board are confusing labelled – they are all labelled IO13 or IO15, but MTDI is IO12.

The firmware is not “done” yet. I have had the occasional core dump/reboot, but I expect this to improve with time.

The positives:

It’s very cheap. £10 delivered to the UK.

It’s all in one – buttons, DAC, amp and charge controller – but with the caveat that the GPIOs are what they are, and that’s that.

What are the alternatives?

Raspberry Pi Zero W [£10] + HiFiBerry Pi Zero MiniAMP [£15.5] + an SD card. Identical power output specs, probably better audio quality. Would need to do something for battery management. More than twice the price. piCorePlayer would be the obvious OS to run on this.

ESP32 WROVER board + DAC + amp. Bit more work selecting the components to use but no fussing with GPIO confusion.

Olimex ESP32-ADF – very similar specs to the ESP32 Audio Kit. Can’t confirm that it works with squeezelite-esp32. Note that Espressif’s software framework is called “ESP-ADF” and this piece of hardware from Olimex is called “ESP32-ADF”, so have fun googling that!

What else could be done with an ESP32 Audio Kit?

Wifi intercom – a pair of these could work back-to-back with some kind of SIP stack running on each. Press a button, a call is set up to the other unit and audio starts flowing between them immediately. More than two and you’d need an external server to do the audio mixing [eg, Freeswitch]. Call setup times would have to be pretty rapid for this to work.

Power consumption

Figures are per minute. Tested with a cheap UT-25 type USB tester.

  • Playing through the speakers, volume level 24: 18mWh
  • Through the headphone jack at volume level 30: 18mWh
  • Idle, or “off”: 8mWh
  • Display off in “off” mode: same!
  • Changed clock mode to hh:mm [ie, no seconds]: 6mWh

Two surprises here: the OLED being off or on seemed to make no difference to the power consumption, and neither did running through the speakers vs. headphones. Waking up regularly to process display updates seems to be relatively expensive.

PMS5003, Pi Zero W and Zabbix

Living near a busy road, I have recently taken an interest in air quality. To that end, I have acquired a PMS5003 from Pimoroni to connect to a Pi Zero W that I had been auditioning Pi Core Player on. 1

The PMS5003

The PMS5003 is matchbox-sized and works by means of a fan that draws air over a laser which shines at an optical sensor. Airborne particles interrupt the laser beam and thus the device can count them. I am not sure how it determines the sizes of the particles. If you’re worried about noise, don’t be – the fan is quiet to the point of being inaudible. The PMS5003 is powered from the Pi Zero W so in hardware terms this is a fair simple project to put together.

Pimoroni have written a Python library for the PMS5003 so that seems like the sensible place to start. The examples/ script that comes with the library seemed like a good starting point, but by default it outputs values once per second which is far more frequent than I have any use for, so I added a time.sleep statement to make the script pause for 30 seconds after each output:

while True:
    data =

This is what the output of looks like:

PM1.0 ug/m3 (ultrafine particles):                             1
PM2.5 ug/m3 (combustion particles, organic compounds, metals): 1
PM10 ug/m3  (dust, pollen, mould spores):                      3
PM1.0 ug/m3 (atmos env):                                       1
PM2.5 ug/m3 (atmos env):                                       1
PM10 ug/m3 (atmos env):                                        3
>0.3um in 0.1L air:                                            318
>0.5um in 0.1L air:                                            94
>1.0um in 0.1L air:                                            6
>2.5um in 0.1L air:                                            4
>5.0um in 0.1L air:                                            2
>10um in 0.1L air:                                             1

Getting data from Pi to Zabbix

Why Zabbix? It collects time-series and textual data, presents it in different ways and carries out actions when certain thresholds are met. I have extensive experience of Zabbix through using it at work and I already have it installed at home for monitoring my small network, so it seems like a good place to start.

Approach 1 – send values with zabbix_sender

The first approach I tried with Zabbix was to use zabbix_sender to send each value to Zabbix as it arrived from This consists of ~50 lines of Perl to convert the output of to the form expected by zabbix_sender, and joins the two processes together with pipes. The advantage of this method is that the raw data does not need to be written to disk, which is a worthwhile consideration on an I/O-constrained platform running off a cheap MicroSD card.2 If zabbix_sender loses contact with the server, or dies, the whole thing aborts. I use a systemd unit file to keep the script running, but I found that systemd was preventing from exiting when either child dies, in contrast to what happens when running interactively. After throwing out this question to the collective genius of the #a&a IRC channel, TC dug up the IgnoreSIGPIPE option in systemd, which defaults to true. When set to false in the unit file, it all now behaves as expected!

Approach 2 – read values from a logfile

The second approach I tried is to write the output of to a log file, via the ‘ts’ command from moreutils. The Zabbix agent then reads this log file and sends interesting values to the server. Outputting the values every 30s results in about 2MB/day of data. The advantage to this approach is that it simplifies the pipeline – our data source and sink operate independently of each other and don’t need to know what state the other is in. The disadvantage is the additional I/O as mentioned above, and now I also have a growing log file to manage somehow. It’s not enough just to truncate the file; you have to stop the process, clear it down and restart it.

Handling data, server-side

I created nine Zabbix items to handle the 12 parameters output by What about the other three? The (atmos env) versions always appear to be the same as the first three parameters, so I am not bothering to store them separately. The zabbix_sender items are “trapper” type, and they match the arbitrarily chosen item names in

The output

Here are six hours of data, as plotted by Zabbix:

Hope and change, Bolton style

This came through the letterbox yesterday:


There is much amusement to be found in this leaflet, from the black-on-dark-green text to the ingenious new abbreviation for litre, ‘lr’. Who is it published by? Surely a link to a website, an email address or god forbid a Facebook page would make sense for some sort of campaign in this day and age? A look at the bottom [which is difficult to see because of the shit picture I took] and all is clear:

“What can you do about this change?

Nothing because you live in Bolton”

The campaign has been abandoned already and this leaflet is just to let everybody know.

Woodgate Reserve West Country Vintage 2014

Found this in Lidl this evening. £1.49/568ml, 7.3% ABV. A bit sweet for my liking, bears more than a passing resemblance to Henry Westons Vintage, although about 1% less ABV. Much nicer than their regular cider which seems to have gone off the boil (in my opinion) since a recent packaging change, although it could be my imagination.


3.5 out of 5? Doesn’t sound particularly enthusiastic, but then I’m a hard man to please. I will probably end up buying it again.

Been a while…

It’s certainly been a long time since I updated this page.

Here are some things I’ve added to the gallery in the past three years: