Interfacing the MH-Z14A carbon dioxide sensor with OLED display and ToF Laser distance sensor on STM32

When it comes to indoor air quality, especially during the cold days, we usually think of trying to keep the humidity at an acceptable level to prevent irritation of the respiratory tract occurring from dry air caused by ventilation, which reduces the relative humidity of outside air (that was well-saturated with moisture while it was still cold) by heating it up indoors.

However generally avoiding ventilation will sooner or later result in poor air quality, which is way more difficult to measure than humidity itself: indoor pollutants, mostly from human metabolism (but also furniture and everything else in your house) can vary among countless groups of complex substances that would be impossible to quantify analytically by reasonable means.

The carbon dioxide level is thus often used as an indicator of how much the air is “used up” by human respiration, so you can get a good estimate about air quality in your room or office by just monitoring that single component:

While fresh air is close to the average outdoor level of around 400ppm CO2, you should try to keep your indoor level below 1000ppm to prevent fatigue and reduced cognitive performance.
Exceeding 4000ppm can show significant negative effects on concentration and feeling comfortable.

Devices that measure and display the CO2 concentration (just like we are used to from countless thermometers displaying humidity) are still rare and very expensive, also they would require a little more power than a humidity meter running for years on two AA batteries.

Among the first devices bringing a low-cost solution to the mass market was the weather station from netatmo: The pack contains several sensors connected to a base station that will collect all CO2 measurements and transmit them over WiFi – directly to the netatmo cloud servers. The only way to see your local air quality in the same room is by touching the top of the base, which will then illuminate in one of three colours to give a vague estimate of the situation. Everything else works exclusively through the cloud service: They will monitor human activity (and even sound levels!) in your house and show you fancy-looking graphs on the app or website in return.

If that does not sound like the solution you would want to be running and connected to the internet 24/7, you will quickly stumble upon those MH-Z14 / MH-Z14A sensors that sell on AliExpress for less than $20 USD, shipping included.

They come with their own microcontroller and several interface options like Analog, PWM and UART.

The UART interface is somewhat straightforward, especially if you keep sending the same command over and over to query the current value (so there’s no need to calculate the checksum but rather use the static one that matches the command, as given in the datasheet): Send 9 bytes at 9600 baud and receive 9 bytes with the measurement. There’s not even the need to verify the checksum of the received reply – if you’re lazy, you can just read the value from the third and fourth byte of the reply.

For the beginning, you can just wire it up to some USB CP2102 UART module – the sensor module is perfectly fine with using the 5V from your USB port.

Which results in this little proof-of-concpet Python script that will write the current value to the console each five seconds (make sure to pip install pyserial first and adjust your /dev/ttyUSB or COM port number):

import serial, time

z14 = serial.Serial(port="COM13",baudrate=9600)

while True:
	z14.write(bytearray.fromhex("FF 01 86 00 00 00 00 00 79"))
	result =
	print(result[2] * 256 + result[3])

You will notice it takes a few minutes for the sensor to warm up and deliver plausible results.

Also, there seems to be a difference between ZH14 and ZH-14A modules regarding calibration: The 14A variant performs automatic calibration within each interval of 24 hours – in other words, you have to make sure to let in some fresh air from outside at least once per day, so it can calibrate the lowest reading within the previous 24 hours to be around 400ppm! This seems quite annoying, but also makes using the sensor a lot easier on the other hand.

Now a console window scrolling the latest sensor reading may seem quite appealing to the average nerd, but if you want to go for a more netatmo-ish style of standalone device, how about adding a microcontroller with an oled display and a tocuh sensor? The display would show a tiny version of the CO2 graph, similar to the netatmo apps, on the device itself after you activate it with a touch.

And to make it even cooler, I used one of those new-fangled VL53L0X Time-of-flight laser distance sensors from ST – those are basically the successor of the tiny sensors you have in smartphones to detect user presence (to disable the touch screen while holding the phone close to the head) and allow for range measurements of up to two meters. So you don’t even need to touch the device itself, but could rather hover your hand over the device to switch on the measurements display, or use gesture recognition (upwards or downwards motion) for additional functions.

Here’s some example using an STM32F103C8T6 board (mostly since those are less than $2 on AliExpress for an ARM Cortex MCU and also code for VL53L0x is readily available from ST; also there are Chinese clones of the ST-Link / v2 debugger availably for very little money, compared to e.g. JTAGICE debuggers for AVR controllers, or platforms like ESP8266 or Arduino where you have no live debugging by default at all). Also it has built-in USB that you can use for USB to serial or even mass storage (how about donwloading your measurements from a dynamically created .csv-file for example?).

Using STM32 CubeMX from ST you can graphically set your desired pin functions and generate all the hardware-related code, similar to the web-based tool availbale from Microchip / Atmel at for the AVR and ATSAM MCUs).

Here’s my CubeMX file (I actually used both I2C cores for separating the OLED and the ToF sensor for mere simplicity in wiring).

But after all, you should of course use the platform you feel most experienced with, considering that SSD1306 OLED drivers are available almost everywhere, and you can check the VL53L0x library from Pololu for a more lightweight alternative to ST’s bloated C library to control the laser distance sensor.

Next thing would be to make decent case for it: currently my prototype resides in a 3D-printed cylinder without any bottom plate, and I still need to figure out how to fit a battery in there and how to charge it (or probably just run a cable to the nearest usb port).

The final goal would be connecting it to the USB port of some OpenWRT router for data logging and network access, but then again you might as well go for ESP8266 in the first place… And after all, summer is coming closer, making this the ideal project for some spare time in next winter…

pictures will follow 🙂

Neue VR-Bankcards: girocard kontaktlos ohne V-PAY kontaktlos?

Nachdem bereits vor einigen Jahren bekannt wurde, dass die Volks- und Raiffeisenbanken die bundesweite Einführung von girocards mit kontaktlos-Schnittstelle beschlossen haben, zog vor kurzem auch meine Bank nach: Bereits in den Wochen vor der Auslieferung wurden Kunden über einen Hinweis im Online-Banking informiert, u.a. darüber, dass die Kontaktlos-Schnittstelle zunächst inaktiv sei.

Diese solle jedoch automatisch beim ersten Einsatz der Karte am POS oder Geldautomaten aktiviert werden – es geht hier also anscheinend tatsächlich eher um einen Schutz vor Erkennbarkeit des Inhalts der ansonsten neutralen Briefsendung auf dem Transportweg als um eine potentielle Paranoia-Linderungsmaßnahme gegenüber dem zukünftigen Karteninhaber. Dennoch soll dieser die Möglichkeit haben, die kontaktlos-Funktion am Geldautomat der eigenen Bank jederzeit zu de- bzw. reaktiveren.

Und tatsächlich antwortet die Karte im Auslieferungszustand in keinster Weise auf ein angelegtes ISO 14443-Feld – es ist nicht nur die Zahlungsfunktion deaktiviert, sondern wirklich die physische Kontaktlos-Schnittstelle.

Wenn nun die eigene Neugier nicht bis zum nächsten Geldautomaten- oder Supermarkt-Besuch warten kann, wird eben kurzerhand ein altes POS-Terminal aus der Bastelkiste gekramt, und siehe da: Es bedarf – wie erwartet – tatsächlich nicht einmal einer erfolgreich abgeschlossen Transaktion, um das Kontaktlos-Interface zu aktivieren; vielmehr geschieht dies bereits bei einer aufgrund nicht zustande gekommener Internetverbindung abgebrochenen Zahlung (die Kunst liegt am ehesten noch darin, das Terminal zunächst per DHCP-Lease davon zu überzeugen, dass theoretisch eine Online-Verbindung realisierbar wäre, um ein Fallback auf ELV-Offline mit Magnetstreifen zu verhindern)…

Weiterhin wird seitens der Volksbank darauf hingewiesen, dass die neue Karte nun nicht mehr über die GeldKarte-Funktion verfügt. Dies erklärt wohl auch den missglückten Ladeversuch meiner bisherigen Karte am Geldautomat noch vor einigen Wochen (“Sie sind zu dieser Funktion nicht berechtigt”), und auch die Online-Aufladung über unterstützt nun explizit nicht mehr die Aufladung von Karten der genossenschaftlichen Institute. Inzwischen ist sogar die erfolgreiche Durchführung einer kostenlosen Testladung in Höhe von einem Cent Voraussetzung, um den Service nutzen zu können. Anscheinend hat man hier bereits großflächig begonnen, die technische Infrastruktur der GeldKarte im Hintergrund soweit zurückzubauen, dass man sich auch bei EURO Kartensysteme (als technischem Dienstleister der Deutschen Kreditwirtschaft) nicht mehr ganz sicher ist, was nun noch funktioniert und was nicht. Immerhin: Von der Frankfurter Sparkasse ausgegebene kontaktlose girogo-Karten sind weiterhin über erhältnlich und können nach wie vor online geladen sowie an allen GeldKarte- und girogo-Akzeptanzstellen eingesetzt werden.

Möglicherweise ging es den Volksbanken auch darum, keine Verwirrung am POS zu stiften, wenn das Terminal bei kontaktloser Zahlung ungefragt das geladene GeldKarte-Guthaben verwendet bzw. girogo als erstbeste kontaktlose Zahlmethode erkennt, dann aber aufgrund fehlenden Guthabens den Vorgang abbricht.

So ist DF_BOERSE kontaktlos nicht vorhanden, jedoch lässt sich kontaktbehaftet immernoch ein EF_BETRAG in Höhe von 0,00 Euro auslesen. Das maximale Guthaben ist wie üblich beschränkt auf 200 Euro, die maximale Höhe eines Ladebetrags jedoch auf 0,00 – womit die GeldKarte technisch zwar nicht entfernt aber zumindest effektiv deaktiviert ist.

Es finden sich somit insgesamt nur zwei Akzeptanzlogos auf der Karte: girocard und V-PAY.

Gerade einmal neun Jahre nach Ausgabe der ersten Debitkarten mit kontaktloser V-PAY-Funktion durch eine italienische Bank scheint man nun also auch in Deutschland bereit zu sein…

Wie dem auch sei, funktioniert hat die erste Zahlung bei Aldi Nord jedenfalls nicht.

Das Terminal akzeptiert die Karte (ohne PIN) und baut zunächst ordnungsgemäß eine online-Verbindung auf, worauf die Zahlung mit “Systemfehler” abbricht. Kontaktbehaftet funktionierte es dann problemlos (die erste Vermutung bestand somit darin, dass die AID für girocard in der kontaktlosen Variante zwar vom Terminal, aber noch nicht vom Acquirer akzeptiert würde).

Tatsächlich wunderte ich mich schon im voraus, ob auf dem Kundenbeleg hinterher wohl “girocard” stehen würde, oder stattdessen ein Fallback auf V-PAY stattfindet. Die Karte unterstützt laut Aufdruck schließlich V-Pay, und die AID A0000000032020 lässt sich auch über die kontaktlose Schnittstelle problemlos selektieren.

Schaut man sich dagegen den Inhalt des PPSE an, tauchen dort nur 3 AIDs auf:

A00000005945430100 // Girocard Electronic Cash
A0000003591010028001 // Girocard EAPS
D27600002547410100 // Girocard ATM

Grundsätzlich steht es einem Terminal natürlich frei, unabhängig vom PPSE auch wahllos andere AIDs durchzuprobieren, dennoch frage ich mich, warum V-PAY hier nicht enthalten ist. Somit wäre fraglich, ob die kontaktlose Zahlung im Ausland überhaupt funktionieren würde, oder die Karte weiterhin gesteckt werden muss (sofern man denn überhaupt einen Händler gefunden hat, der international nicht nur Maestro akzeptiert).

Kontaktbehaftet lief die Transaktion jedenfalls über die AID A0000003591010028001.

Zweiter Versuch bei Rewe: Hier sind die notwendigen Terminals (Ingenico IPP 350) schon seit Jahren vorhanden, irgendwann wurde die kontaktlos-Funktion dann aber abgeschaltet (vermutlich weil zuviele gleichgesinnte damals ebenfalls erfolglos versucht hatten, mit einer Mastercard zu zahlen, die kontaktbehaftet problemlos akzeptiert wird).
Ich bitte also darum, “mit Karte” zu zahlen (die Diskussion darüber welche Zahlmöglichkeiten denn genau unterstützt würden spare ich mir aufgrund entsprechender Erfahrungen aus der Vergangenheit) – und werde um meine Karte gebeten.
Ich stelle fest, dass ich gerade ebensowenig Interesse daran habe, vor Ort auszudiskutieren inwieweit das denn genau “nur zu meinem Schutz” diene, während die Banken doch gerade das nicht-aus-der-Hand-geben als zusätzliche Verbesserung der Sicherheit und Hygiene propagieren. Die Dame an der Kasse betrachtet jedenfalls die Unterschrift auf der Rückseite und drückt schließlich die Karte mit mehreren Fingern fest auf das Display, als würde sie diese fotokopieren wollen. Das Terminal bittet mich trotz des Betrags unterhalb von 25 Euro um Eingabe der PIN.
Auf dem Beleg steht dann auch girocard kontaktlos.

Dritter Versuch: Nun klappt es auch bei Aldi, allerdings ebenfalls mit PIN-Eingabe.
Auf dem Beleg ist wieder die AID A0000003591010028001 angegeben.

Die Bank verlangt laut Info-Flyer auch unter 25 Euro hin und wieder eine PIN-Eingabe (angeblich erst ab 100 Euro Umsatz bzw. einer bestimmten Anzahl Transaktionen ohne PIN-Prüfung), vermutlich war dies dann auch der Grund, warum es beim ersten Versuch nicht klappte (bei der ersten erfolgreichen Transaktion muss es wohl auch ein CDOL-Update gegeben haben!?), oder es bedurfte auch seitens der Bank erst irgendeiner Initialisierung, die durch das einseitige Aktivieren der Kontaktlos-Schnittstelle nicht erfolgte.

Zusammenfassend lässt sich also feststellen: Die Notwendigkeit einer PIN-Eingabe bei Aldi werde ich zunächst noch weiter beobachten, im Zweifelsfall bleibt es eben wie bisher bei der nicht-genossenschaftlichen Mastercard, wenn meine Bank das so möchte. Bei anderen Supermärkten (die durch gewohnt inkompetentes Personal auffallen) bleibt es ohnehin bei Bargeld, was auch immernoch schneller ist, wenn ich ohnehin erst etwas aus dem Portemonnaie herauspulen muss.

Immerhin: Vielleicht sorgt die steigende Akzeptanz in der Bevölkerung ja zukünftig für weniger irritierte Blicke an den Kassen.

Wozu die Karte übrigens einen Magnetstreifen hat, erschließt sich mir so langsam wirklich nicht mehr. Wahrscheinlich liegt es an den Geldautomaten, die nach wie vor keine Karten mit beschädigtem Magnetstreifen akzeptieren!?
(Wer dieses Vergnügen bereits hatte, oder z.B. auch nur spaßeshalber das Guthaben einer kontoungebundenen GeldKarte prüfen möchte, wird sich unter Umständen wundern, diese nur wenige Millimeter tief hineinschieben zu können…)

Yealink IP phones and Sennheiser DW Pro wireless headsets – what’s the magic within the EHS36 box?

If you’ve ever come across wireless headsets for office phones, you might have noticed that “wireless” in this case actually results in a lot more wires on your desk than using a regular wired headset: At least the charging / base station will need an additional power supply, and maybe also an adapter cable to fit the particular phone’s headset jack, since those base stations are rarely sold in multiple variants, tailored to a specific phone model, unlike the most simple wired headsets.

Now there’s one more advantage to wireless than just reducing the chance of accidentally pulling on the cable with an arm (or office chair): You can even leave the desk during calls. Thus it would be great to pickup and hangup calls remotely with a button on the headset – but how should the base station then signal this to the phone?

In mobile phones, this kind of button was considered for wired headsets right from the beginning: The de-facto standard means simply shorting the MIC+ line to GND (directly or with a certain resistor value to distinguish between additional functions like track skipping or volume control).

However, this was never a requirement for desk phones, so the information about ringing, accepting a call or hanging up usually needs to be transmitted out-of-band: Back in the days of analog phones, the most common approach involved mechanical handset lifters that would actually physically move the handset upwards from the phone, so the hook switch would be released or closed when the user presses the button on the headset, thus accepting the call or hanging up.

While this “standard” used to be widely compatible (it just needed to fit the gap underneath the handset), it seemed reasonable for manufacturers of modern ISDN and IP phones to come up with some less hardware-intensive solution – the “Electronic Hook Switch” (EHS).

Obviously, there could not be a common global standard for EHS functionality, but you will probably deal with one of the following (mostly country- or vendor-specific) flavours:

  • DHSG (“Drahtlose Hör-/Sprech-Garnitur”, formal German term for “wireless headset”) – probably the most widely deployed standard worldwide by now
  • AEI (“Additional Equipment Interface”) – by Avaya
  • HHC (“Headset Hookswitch Control”) – for Cisco IP phones
  • MSH (“Microphone Short Hook”, as mentioned above for mobile phones, will short the MIC line to GND to signal pickup / hangup) – available in some Alcatel phones
  • RHL (“Remote Handset Lifter”, apparently standardized interface for mechanical lifters as mentioned above)

I recently had the chance to play with some Sennheiser DW Pro DECT Headsets at the office – they offer DHSG, MSH and a (proprietary?) Handset Lifter. Our phones are Yealink T46G that do not come with EHS functionality out of the box, but you are supposed to purchase the $40 “EHS36” adapter box by Yealink, which is meant to be compatible with several wireless headset manufacturers (it even comes with a bunch of cables), including Sennheiser (in DHSG mode).

While there seems to be nothing wrong with purchasing one of those adapters for each desk, users report they would crash every once in a while and need to be re-plugged. Also it just made me curious that there is absolutely no information about pinouts or even protocols out there yet, not even a picture of the PCB… So here it is:

Yealink EHS36 PCB
Yealink EHS36 PCB

The 6P6C (RJ-12) connector on the right goes into the EXT port of the phone through a 1:1 modular cable.
For Sennheiser, a Y-Cable is included that connects the 8P8C port on the base with the 8P8C (RJ-45) port on the EHS36 (left) and the 4P4C (RJ-9) analog headset connector on the phone. Basically, Pins 3-6 for analog audio go into the Yealink phone in reverse order, while Pins 1, 2, 7 and 8 (DHSG Control Pins) go into Pins 3-6 (in this order) of the EHS36 8P8C connector.

For the detailed DHSG pinout, please see the innovaphone wiki:
The specifications are also available for download from there:

There’s also some info (in German) about DHSG Y-Cables here, the one for Sennheiser should be similar to this one:

I soldered some 0.1″ pin headers to the modular jack pins on the back of the PCB to capture the signalling between phone and headset base while making some test calls.

The DHSG part looks quite straightforward and can also be found in the specifications above:

DHSG / EHS wireless headset serial bus protocol signalling / commands specification
DHSG / EHS wireless headset serial bus protocol signalling / commands specification

8P8C pinout at EHS36 (only 3-6 seem to be connected):

Pin 3: GND
Pin 4: DHSG RX (“Bus Send”: headset -> EHS36)
Pin 5: +3.3V
Pin 6: DHSG TX (“Bus Receive”: EHS36 -> headset)

Due to the pause after each bit, it will not work with standard 8 bit hardware UART though.

However I will focus on the interface between phone and EHS36 here:

Here we have SPI with CPOL = 1, CPHA = 0 (“Mode 2”) and a variable bit count, so you probably cannot use most of the hardware implementations available in microcontrollers for this part either, since most of them can only handle fixed 8 bit data chunks.
There’s also an LDO voltage regulator on board that generates 3.3V for the logic levels from the phone’s 5V supply.

6P6C Pinout at EHS36 (conntected to phone’s EXT port):

Pin 1: MISO
Pin 2: MOSI
Pin 3: SS
Pin 4: GND
Pin 5: Vin (+5V)
Pin 6: SCLK

Let’s start with the most frequent signal:
Exactly once per second, the phone will send a 15bit poll message to the EHS box:

15 bit poll:
MOSI: 101100001100000
MISO: 000000000000101

(it seems that somewhere between bit 9 and 11 the EHS36 eventually decides to reply with …101)

Periodic “poll” signal (1 Hz) between Yealink T46G phone and EHS36

The same “poll” also occurs in between the 1Hz interval, directly before any other command will be sent.

So let’s ask the EHS36 to signal an incoming call to the headset: On SPI we will have the above “poll” followed by another message (/SS released shortly in between!) that is 21bits in length:

21 bits ring:
MOSI: 101000001000000000010
MISO: 000000000000000000000

The EHS36 then signals DHSG Code 1 to the headset base, and you can hear the ringing indicator in the earpiece.

Now what if you press pickup on the headset?
Upon receiving DHSG Code 2, EHS36 will set MISO high, which makes the phone immediately pull /SS low and start generating the clock for a transfer:

At first we see the same “poll” command as above, however MISO is still high during the first 12 bits:

MOSI: 101100001100000
MISO: 111111111111101 (apparently, only the last 3 bits are considered as acknowledge from EHS36 to the polling?)

After that, /SS will be released shortly and a new 19bits transfer is started – the phone will read the command from EHS:

19 bits pickup:
MOSI: 0111111111111111111
MISO: 1110100000100011001

EHS36 now confirms with DHSG Code 2 and the call is being accepted by the phone; curiously this is followed by two more “poll” commands with sometimes very delayed SPI clock (phone seems to be busy handling the call). After several hundred milliseconds to establish the call, the phone notifies the headset about the successfull connection again with DHSG Code 2 (this will also happen when you press accept on the phone rather than headset, or after you have dialled a number and the remote party accepts):

Again, we see a “poll” command followed by a signal that is 21 bits in length:

21 bits call established:
MOSI: 101000001000000000000
MISO: 000000000000000000000
EHS36 logic signals when picking up a call by pressing the headset button
EHS36 logic signals when picking up a call by pressing the headset button

What’s missing? Hanging up by pressing the headset button!

If the headset base sends DHSG Code 1 during a call, exactly the same will happen on SPI side as for pickup:
A “poll” with first 12 bits MISO high, followed by 19 bits pickup command. This is followed by two “regular poll” commands.
The base then receives DHSG Code 3 as an indicator that the call has been terminated.

Where to go from here?

Using the information above, it should be theoretically possible to build your own EHS-adapter for Yealink <-> DHSG, though this would most probably not be cheaper than buying the original one in the small scale.

However, you could add some nice features like Busy Light Indication – think of a little LED sign on your desk that shows clearly whether you are available or not?

There seems to be no busy light on the market yet that natively uses the EXT port on Yealink phones – all that money can buy at the moment will use workarounds like monitoring the state of the microphone audio line between phone and headset – through another little black plastic box on your desk… *sigh*

For the kuando Busylight Combi, for example, there’s even a dual version that allows you to use *either* handset or external headset, by looping both microphone lines through the box…

So stay tuned for my future attempts of putting EHS, Sennheiser Y-Cable and Busy Light Ddetection all into one little box 🙂