Skip to the content
2 layers37.8 × 54 mmPollutag node2, the printed circuit board in 3D

The Pollutag Node2 repo. Contain FW and HW. This LoRaWAN node designed for autonomous work above 10 years

Size
37.8 × 54 mm
Copper layers
2
Thickness
1.6 mm
Parts
198
Nets
43
Pads
293
Tracks, vias
436, 57
Pours
140
Routed
Yes
KiCad
KiCad 10.0 (file 20260206)
Created
5 Jul 2024
Updated
7 Nov 2026

Overview

Pollutag node2 is an open-source printed circuit board by WeSpeakEnglish, from the WeSpeakEnglish/pollutag_node2 repository on GitHub, published under the GPL-3.0 licence. It is a 2-layer board of 37.8 × 54 mm with 198 parts on 43 nets, routed with 436 tracks and 57 vias.

Its parts include 12 capacitors, 10 resistors, 4 diodes, 2 transistors and 2 connectors.

README

README.md

the Node2

Pollutag Node2

The LoraWAN node for air quatity monitoring based on STM32WLE and SEN54 Sensirion sensor 868 Mhz LoRaWAN node for continuous air quality monitoring. The node operates autonomously and support remote commands via downlinks. SEN54 connected via I2C2 interface.

  1. LaraWAN node based on STM32WLE MCU (LORA-E5), easy to integrate monitoring and control node (air quality monitoring, garden, intelligent home. etc.)
  2. You may connect load remotely by downlinks - via AO3400A transistor (on board)
  3. New Sensirion SEN54 sensor optimized
  4. MPPT and Li-Ion battery charger for power from 5V++ cheap solar panels.
  5. Sleep current as low as 0.9 mA
  6. 10+ years of autonomity with cheap 2W solar panel and standard 1850 Li-Ion battery
  7. More than 2 weeks work autonomity from fully charged 3500mA Li-Ion battery
  8. Ideal for sensor.community integration over LoRaWAN IoT, like TTN and Helium, no internet connection from your side needed!
  9. Full fledged with routed test points - easy to solder - easy to add functionality (inputs, outputs, interfaces etc.)

How the node works (firmware v1.4.1)

Measurement cycle (default every 30 minutes, set by downlink 02):

TimeAction
0 sSEN54 power on (skipped while the battery level byte is 143 or lower)
1 sSensor reset, VOC algorithm state restored, measurement started
31-60 sPM: 30 s start-up, then one sample per second averaged over 30 s (Sensirion "Reduced Power Operation SEN5x" recommendation). VOC, temperature and humidity are taken from the last sample
61 sResult ready; uplink sent if no uplink period is set
62 sVOC algorithm state saved, measurement stopped
63 sSEN54 power off

The sensor is powered for 63 s per cycle (about 70 mA while measuring). VOC note: the SEN54 VOC index learns its baseline during continuous operation. Because the sensor is powered off between measurements, the VOC value is only a rough, short-term indicator. The VOC algorithm state is saved before power-off and restored at power-up to reduce the effect.

Uplinks are unconfirmed, with ADR on. By default one uplink is sent after each measurement; with downlink 03 the node sends instead on its own timer (each uplink carries the latest measurement). If the duty-cycle limit does not allow sending yet, the LoRaWAN stack queues the uplink and sends it as soon as allowed. Settings made by downlink are not stored: after a reset the defaults apply again.

Downlink commands supported

All commands on Port 2, two bytes [command, value] (except reset):

DownlinkCommandValue
01 00 / 01 01LED / load output off / onThis LED is enhanced with a FET transistor, so you may connect some load to be switched ON/OFF (see the schematic in PCB folder)
02 XXMeasuring periodXX minutes (hex). 00 = measuring off, 01 is raised to the 2 minute minimum. Default 30 min (02 1E)
03 XXUplink periodXX minutes (hex), minimum 1. Uplinks are then sent only every XX minutes. 00 = one uplink after each measurement (default)
FF FF FF FF FF FFReset the device

Examples: 02 3C measure every 60 min, 03 05 send every 5 min, 01 01 LED on.

Downlink payload encoder example:

js
function Encoder(measurements, port) {
    // LED on/off
    if (measurements.LED_ON !== undefined) return [0x01, measurements.LED_ON.value ? 1 : 0];
    // measuring period, minutes (0 = off)
    if (measurements.MEASURE_MIN !== undefined) return [0x02, measurements.MEASURE_MIN.value & 0xFF];
    // uplink period, minutes (0 = after each measurement)
    if (measurements.UPLINK_MIN !== undefined) return [0x03, measurements.UPLINK_MIN.value & 0xFF];
    return [];
}

Uplink

The node uplink data packet (Port 2) contains 14 payload bytes with the following data: PM1 concentration, PM2.5 concentration, PM10 concentration (30 s averages), temperature, humidity, volatile organic compounds index (VOC), battery level related to volts, status of LED output.

BytesValueEncodingInvalid / no data
0-1PM1.0ug/m3 x10, big endianFF FF
2-3PM2.5ug/m3 x10, big endianFF FF
4-5PM10ug/m3 x10, big endianFF FF
6-7Temperaturebyte6 + byte7/100 - 100 [degC]FF FF
8-9Humiditybyte8 + byte9/100 [%RH]FF FF
10-11VOC indexx10, big endianFF FF
12Battery levelx 0.0238 = volts
13LED output0 = off, 1 = on

Invalid values are sent when the sensor did not answer or had no data (for example while the battery is too low to power it).

Decoder for payload on server side (uplink):

js
 function Decoder(bytes, port, uplink_info) {
    var decoded = {};
    function u16(i) { return bytes[i]*256 + bytes[i+1]; }
    function invalid(i) { return bytes[i] === 0xFF && bytes[i+1] === 0xFF; }
    decoded.PM1   = invalid(0) ? null : (u16(0)/10.0).toFixed(2);
    decoded.PM2_5 = invalid(2) ? null : (u16(2)/10.0).toFixed(2);
    decoded.PM10  = invalid(4) ? null : (u16(4)/10.0).toFixed(2);
    decoded.T     = invalid(6) ? null : (-100.0 + bytes[6] + bytes[7]/100.0).toFixed(2);
    decoded.RH    = invalid(8) ? null : (bytes[8] + bytes[9]/100).toFixed(2);
    decoded.VOC   = invalid(10) ? null : (u16(10)/10.0).toFixed(2);
    decoded.Battery = (bytes[12]*0.0238).toFixed(2);//battery , volts
    decoded.LEDON = bytes[13];
    decoded.RSSI = normalizedPayload.gateways[0].rssi.toFixed(0);
    decoded.SNR = normalizedPayload.gateways[0].snr.toFixed(0);
    decoded.FREQUENCY = normalizedPayload.frequency;
    return decoded;
 }

Open and build

  1. Install STM32CubeIDE (the .ioc was saved with STM32CubeMX 6.15, firmware package STM32Cube FW_WL V1.3.1).
  2. File > Import > General > Existing Projects into Workspace, select the FW folder. The project is called pollutag_node2.
  3. Build: Debug (no optimisation, for debugging) or Release (optimised, for deployment). All sources, include paths and the linker script are relative to the project folder, so no setup is needed.
  4. Flash / debug with the pollutag_node2 launch configuration (ST-LINK, connect under reset).

Notes:

  • The firmware package FW_WL V1.3.1 is only needed to regenerate code from the .ioc; building works without it.
  • Application code lives in FW/Core/Src/app_sensor.c (sensor and measurement cycle) and FW/LoRaWAN/App/lora_app.c (uplink payload, downlinks), inside the CubeMX USER CODE sections so regenerating keeps it.
  • The last 4 KB of flash (0x0803F000) hold the LoRaWAN session and are reserved in FW/STM32WLE5JCIX_FLASH.ld.
  • Enter the LoRaWAN credentials of your device in FW/LoRaWAN/App/lorawan_credentials.h before building, see LoRaWAN credentials.

Preparation

Originally LORA-E5 (Wio) modules came pre-programmed with AT-command enabled stack, working like a LoRaWAN modem. To use the core MCU for this node it is needed first to erase the chip and prepare the project for proper comissioning. You may use commissioning identificators already pre-programmed into the chip by Seeed studio, or generate the new via TTN/ Helium console. To erase chip connect it via ST-LINK under reset (you need to short RESET pin (J1, pin 5) of the MCU to the ground before you press "Connect" button in STM32CubeProgrammer). to compile please edit comissioning first

After you successfully connected the MCU, it need to be reprogrammed Read protection Option Byte to AA (no protection) to compile please edit comissioning first

After completing this process, the chip should be successfully cleaned. to compile please edit comissioning first

LoRaWAN credentials

The keys are not taken from the .ioc (it keeps zeros). Each device has its own small file FW/LoRaWAN/App/lorawan_credentials.h, which overrides the CubeMX values and survives code regeneration:

c
#define LORAWAN_DEVICE_EUI    00,00,00,00,00,00,00,00                          // 8 bytes
#define LORAWAN_JOIN_EUI      00,00,00,00,00,00,00,00                          // 8 bytes (AppEUI)
#define LORAWAN_APP_KEY       00,00,00,00,00,00,00,00,00,00,00,00,00,00,00,00  // 16 bytes
  1. Copy FW/LoRaWAN/App/lorawan_credentials_template.h to FW/LoRaWAN/App/lorawan_credentials.h (if the file is missing the build stops with a message saying so).
  2. Enter the values from your network console (TTN, Helium, ChirpStack...) as comma separated hex bytes, MSB first, exactly as the console shows them.
  3. Build and flash. At boot the node prints the DevEUI and JoinEUI it uses on the debug UART (115200 baud).

Options:

  • LORAWAN_DEVICE_EUI all 00: the DevEUI is derived from the MCU unique ID. Read it from the boot log and register the device with that DevEUI in the network console.
  • LORAWAN_JOIN_EUI may be left all 00 for networks that accept it (useful if you plan to move to a different LoRaWAN network later: register the node on the new network console, reset the node, then delete it on the old console and reset again).
  • You may also reuse the identifiers pre-programmed by Seeed Studio (read them out with the AT commands before erasing the chip).

lorawan_credentials.h is device specific and secret: do not share or commit it (it is listed in FW/.gitignore); share lorawan_credentials_template.h instead.

Hackaday project: LINK

OSHWLab STD project: LINK

OSHWLab PRO poject: LINK

Pollutants.eu: LINK

Parts

198
QtyValueFootprintReferences
27PollutagNo-easyedapro:S1751-46R3V3, BAT-, BAT+, DONE, GND, LOAD, PA0, PA2, PA3, PA10, PA15, PB0, PB10, PB13, PB14, PB15 +11
12PollutagNo-easyedapro:CAP0603C1, C2, C3, C4, C5, C6, C7, C8, C9, C10, C11, C12
10PollutagNo-easyedapro:R0603R1, R2, R4, R5, R6, R7, R8, R9, R10, R11
2PollutagNo-easyedapro:LEDC2012X90NLED1, LED2
2PollutagNo-easyedapro:SOT-23-3_L2.9-W1.6-P1.90-LS2.8-BRQ1, Q2
2PollutagNo-easyedapro:IND-SMD_L3.0-W3.0_SRN3015L1, L2
2PollutagNo-easyedapro:SOD-123_L2.6-W1.7-LS3.5-RDD1, D2
1Pad_e59
1Pad_e164
1Pad_e99
1Pad_e107
1Pad_e116
1Pad_e69
1Pad_e92
1Pad_e122
1Pad_e183
1Pad_e79
1Pad_e159
1Pad_e143
1Pad_e86
1Pad_e182
1Pad_e94
1Pad_e115
1Pad_e108
1Pad_e158
1Pad_e161
1Pad_e167
1Pad_e173
1Pad_e101
1Pad_e104
1Pad_e136
1Pad_e190
1Pad_e95
1Pad_e160
1Pad_e102
1Pad_e100
1Pad_e186
1Pad_e77
1Pad_e129
1Pad_e124
1Pad_e172
1PollutagNo-easyedapro:SM06B-GHS-TBJ3
1Pad_e62
1Pad_e103
1Pad_e175
1PollutagNo-easyedapro:142-0701-801J2
1Pad_e163
1Pad_e137
1Pad_e177
1Pad_e134
1Pad_e133
1Pad_e91
1Pad_e117
1Pad_e126
1Pad_e68
1Pad_e142
1Pad_e65
1Pad_e141
1Pad_e148
1Pad_e170
1Pad_e74
1Pad_e73
1Pad_e165
1Pad_e166
1Pad_e138
1Pad_e80
1Pad_e106
1Pad_e87
1Pad_e187
1Pad_e121
1Pad_e181
1Pad_e188
1Pad_e109
1Pad_e112
1Pad_e125
1Pad_e96
1Pad_e179
1Pad_e66
1Pad_e110
1Pad_e185

The 130 most common of 198 parts.

Similar boards

  • 2 layers327 × 160.5 mmAnalog keyboard, the printed circuit board in 3D

    Analog keyboard

    hackclub/OnBoard

    💡 Join 1,000 teenagers and make your first PCB with a $100 grant!

    198 parts915
  • 2 layers53.3 × 22.9 mmBluePillF4, the printed circuit board in 3D

    BluePillF4

    rogerclarkmelbourne/STM32_Pill_Boards

    Various STM32 boards in a form factor similar to the Maple Mini or Blue Pill board

    198 parts51
  • 2 layers183.9 × 155.8 mmHermod left, the printed circuit board in 3D

    Hermod left

    modern-hobbyist/aesir

    Collection of KiCad PCB designs for custom keyboards, including full-sized and split ergonomic layouts with STM32F072CBT6 microcontrollers.

    198 parts27
  • 2 layers85 × 170 mmCore64 LB, the printed circuit board in 3D

    Core64 LB

    ageppert/Core-64-Interactive-Core-Memory-Badge

    64 bits of core memory and LEDs to interact with directly using a magnetic wand.

    198 parts17GPL-3.0
  • 2 layers283 × 94.2 mmDoddle60, the printed circuit board in 3D

    Doddle60

    AcheronProject/Doddle60

    Doddle60 is a 60% ISO layout PCB with a minimal layout number support intended for ISO users.

    198 parts10
  • 2 layers155 × 114 mmIOExpansion, the printed circuit board in 3D

    IOExpansion

    ja1umi/MUTIF09Project

    Various design materials for building my 8-bit-microprocessor-based single board computer(s)

    198 parts10MIT
  • 2 layers272.1 × 112.3 mmPanel, the printed circuit board in 3D

    Panel

    ericrlau/SP56

    Bluetooth low engery keyboard. Layout based on Sofle keyboard with some modifications. NRF52840 powered. Designed around Kaihl Choc switches and spacing.

    198 parts9CC-BY-SA-4.0

Questions

How big is the Pollutag node2 PCB?

It measures 37.8 × 54 mm and has 2 copper layers, 1.6 mm thick.

What parts does the Pollutag node2 use?

198 parts. The full list of its parts is on this page, with links to each part's symbol and footprint where PCBKit's library has them.

Where can I download the Pollutag node2 KiCad files?

From the WeSpeakEnglish/pollutag_node2 repository on GitHub, at the commit this page was made from: the board is PCB/kicad/pollutag_node2.kicad_pcb and its schematic PCB/kicad/pollutag_node2.kicad_sch. PCBKit opens the board in the browser, with nothing to install.

Can I use the Pollutag node2 design in my own project?

Its repository publishes it under the GPL-3.0 licence: you may reuse it on that licence's terms (keep the author's notice, and for a copyleft licence share your changes alike).