Author Archives: Marc

Trinket Air Quality Sensor

Source code is on GitHub. Ceramic cap is 104M, base resistor is 500 ohms, switch resistor is 1k. Electrolytic is 47 micro-farads

With the recent wildfires here in California and the on-going Covid crisis making us all stay indoors, indoor air quality has become a very important consideration. Working in a back bedroom all day can subject you to some bad air. Mostly volatile organic compounds — you know the stuff that smells — can build up and make a room unhealthy.

What I want, ultimately, is to monitor the indoor and outdoor air quality and generate alerts and run automation to deal with bad air. This sensor project is a key component for indoor air quality monitoring. But I wanted the sensor to provide some limited stand-alone functionality. By using the trinket’s dot star, a piezo buzzer and a button some basic functionality can be implemented.

The dot star on the Trinket serves as a visual indicator of the 10-minute rolling average of both pm 2.5 and eCo2 levels. Steady on is the current level of co2 — green, yellow, orange, red, purple, maroon. Flashing on is the current level of pm 2.5.

The levels are set as follows:

eCo2

WARNING COLORLEVEL ECO2
green< 1000 ppm
yellow< 1500 ppm
orange< 2000 ppm
red< 3000 ppm
purple< 5000 ppm
maroon> 5000 ppm

If enabled the alert will sound on orange and above. Solid on led displays eCO2 level.

pm 2.5

WARNING LEVELPM2.5 PPB
green< 12.1
yellow< 35.4
orange< 55.4
red< 150.4
purple< 250.4
maroon> 250.4

Blinking dot star is showing the pm25 warning level.

The button has 3 functions. If the alert is sounding 1 press will cancel it. Double click will toggle the alert buzzer on or off. Default is off. There is an increasing or decreasing tone to denote which happened. So, a hi to low tone means the alert is off. A long press will set the brightness of the dotstar. Default brightness is 90%. These settings revert to defaults on reset/power on.

A buzzer provides audible feedback of button presses and alerts. If enabled an alert tone will sound for 1 minute when a level goes above level 2 for either monitored compound.

The CCS811 measures VOCs that are normally found in indoor environments and provides an equivalent CO2 level. VOC levels vary by the atomic weight of the compound in question, so it is hard to pin down levels that require actions to be taken. But CO2 has well defined levels (such as this) that can be used for triggers.

For pm 2.5 measurement I chose the PMSA003I sensor. Adafruit sells this sensor on a breakout board which would be the way to go if you can get them. Otherwise you will need to either solder some wires on the very fine pitch connector Adafruit includes or you can use a swd breakout connector and a cable like I did. You will need to use a dremel to cut the connector to fit the PMSA003I. It’s really easy:

Start with this

Grind it down to this. The key has to go.

Ready for test. Can be glued and mounted as is or moved to another board.

A BME280 completes the package. The temperature and humidity are passed to the CCS811 to compensate for the measurements.

This is sort of a stew — the project takes advantage of components I had on hand as much as possible. If I were buying all the parts for this I would probably go with a BME680 and reduce the component count.

To utilize the project as a sensor for an HVAC system, we connect to it via the USB serial port. Command Messenger is used to pass messages to the device and retrieve data from the device. A python app is included that can help set measurement offsets and monitor data. It requires PyCmdMessenger. Data can be retrieved from the device in json format:

See the python source to see how offsets can be set for sensor readings. These are stored in flash when changed and will be valid after restarts.

The Arduino code is set up as a Platform.io project. Change trinket_aqs.cpp to trinket_aqs.ino to use in Arduino ide. See the source for required libraries.

Using Ohmite Force sensitive potentiometers.

Force sensing potentiometers offer an alternative to mechanical potentiometers and rotary encoders. These devices do away the mechanical action of the switch and instead offer a touch sensitive alternative. After looking around the web I was not able to find a suitable library for implementing these devices so I ended up creating my own for Arduino.

The libraries implement functionality for Ohmite’s FSP series of devices. The devices include one round sensor (FPS03CE), and two linear devices (FSP01CE and FSP02CE). The linear devices come in two lengths.

All devices are single touch. This means that you can only detect one touch location on the device at a time. If we tried to detect a finger on each end of a linear device it would register as being in the middle.

For all devices the force is reported as voltage. Generally 3.2 volts is the highest we will see on a 3.3 volt system. The position is reported as an integer. For the FPS03CE the integer represents 0 to 360 degrees from the tail (connector) of the device. For the linear devices it is 0 to 100 mm for the FSP01CE and 0 to 55 mm for the FSP02CE. Default for the two linear devices is for zero at the tail, but the call to position command can take a parameter to return with 0 at the head end of the device.

Integration Guide:

I was only able to find one source for the integration guide — this pdf at Mouser. It is pretty comprehensive, but contains a couple of errors that an integrator should be aware of.

First: In the section for FSP0(1/2)CE on page 4

This section from the integration guide specifies V2 as an analog input. It does not need to be.

The document specifies that V2 should be an ADC pin. In measuring the force and position I have not read an Analog value from there. Not a show stopper, but there is no need to tie up an analog input unnecessarily. I changed this to a digital input and these sensors work correctly.

Second: In the section for FSP03CE on page 7:

This table for the FSP03CE pinout is not correct.

Using Figure 10: Pin 4 is actually the wiper pin, while pin 1 is the third drive electrode. Plus the Pin name for pin 2 is incorrect. Luckily this diagram makes it clear where the connections go.

From the integration guide.

Hook up:

Refer to this diagram when hooking up one the FSP devices.

Use this schematic when wiring up one of the FSP devices. VRef will use a digital I/O

Each device requires one analog input and multiple digital I/O lines. I used 22K for the FSP03CE voltage divider and 18K for both the linear variants. All are 1/4 watt 5%. VRef will use a digital I/O line. These resistor values worked for my application. The sensitivity of response to touch is affected by these values. This is detailed in the integration guide.

Arduino:
#include "M2aglabs_Ohmite.h"

//Set this to the one the Arduino uses
#define ANALOG_RESOLUTION 12
#define LINEAR_THRESHOLD  0.3
#define ROUND_THRESHOLD  0.3

/*
     Round sensor
     WIPER, VREF, D0, D120, D240
     Linear Sensor
     WIPER, VREF, V1, V2, true/false (Short or Long)
    WIPER and VREF are on two sides of a resistor. VREF floats for position measurements,
     PULLS low for force.

*/

M2aglabs_Ohmite roundSensor(A5, 0, 2, 1, 3);
M2aglabs_Ohmite lLinear(A2, 7, 6, 10, true);  //true means short
M2aglabs_Ohmite sLinear(A4, 5, 4, 9, false);  //false is long sensor

void setup() {
     Serial.begin(115200);
     /*
          The lib is set for a default of 10 for analog resolution and     3.3. for voltage. If the voltage is 5.0 set it here.

     */

     analogReadResolution(ANALOG_RESOLUTION);
     roundSensor.begin(ANALOG_RESOLUTION);
     sLinear.begin(ANALOG_RESOLUTION);
     lLinear.begin(ANALOG_RESOLUTION);

     //Set options --
     /*
     Serial.println(roundSensor.readRange());
     Serial.println(roundSensor.readRange(1500));
     Serial.println(roundSensor.zeroOffset());
     Serial.println(roundSensor.zeroOffset(500));
     */

}

void loop() {
     roundSensorActions();
     linearSensorActions();
}

void linearSensorActions() {

     int spos, lpos; //Position is an integer
     float fsp, flp; //Force is a float
     fsp = sLinear.getForce();
     if (fsp > LINEAR_THRESHOLD) {
          //False reads from tail to tip.
          spos = sLinear.getPosition(false);
          Serial.print("s: ");
          Serial.print(fsp);
          Serial.print(" : ");
          Serial.println(spos);
     }

     flp = lLinear.getForce();

     if (flp > LINEAR_THRESHOLD) {
          lpos = lLinear.getPosition(false);
          Serial.print("l: ");
          Serial.print(flp);
          Serial.print(" : ");
          Serial.println(lpos);

     }

}

void roundSensorActions() {

     //Get the force from the round sensor
     float force = roundSensor.getForce();
     //If it looks like we are touching it, calculate the position.

     if (force > ROUND_THRESHOLD) {
          Serial.print("force: ");
          Serial.print(force);
          int angle = roundSensor.getPosition();
          Serial.print(" raw angle: ");
          Serial.print(angle);
          angle = constrain(angle, 0, 360);
          Serial.print(" adjusted: ");
          Serial.println(angle);

     }

     return;

}

The function calls are documented in the header for the library.

Using the library:

Usage is fairly straightforward. The general steps are:

  • Instantiate the object
  • Call begin
  • Poll for force
  • If there is force applied read the position

The library has only been tested on a Metro M4, ItsyBitsy M4 and Raspberry Pi Pico to date. There is nothing that is SAMD specific so the Arduino library should work on other devices.

Two settings to be aware of are the _ZERO_OFFSET and _READ_RANGE. These affect each sensor’s overall range. The _ZERO_OFFSET specifies the normal zero reading of the ADC. With a finger at the 0 position of the sensor there is still a voltage present. Depending on the sensor, the voltage will be in the 200 to 800 millivolt range. If the sensor will not go to zero try adjusting this. For round sensors the 0’s are at 0 degrees (at the tail) then clockwise to 120 degrees, then 240. This is detailed in the integration guide.

Read range sets the maximum value of the voltage at the max end of the sensor. So if the lengths come out short, or max is hit before the end reached try adjusting this setting.

The library is available on github.

https://github.com/m2ag-labs/m2aglabs_ohmite

Securing Local IoT Devices

The goal of my development efforts has been the implementation of an IoT framework for  embedded devices that requires no connection to the cloud. I want a framework that I can use within my firewall that does not need to connect anywhere. I can then allow only the data I want to make it to the cloud.  This makes security a little tricky for me. I intend to use web interfaces to access the devices, but I still want to ensure secure access to the device. I will be targeting Google Chrome as the web browser

Stack overflow has several discussions on the topic of securing devices on an internal network.

This one is pretty good ->

https://security.stackexchange.com/questions/121163/how-do-i-run-proper-https-on-an-internal-network

When it comes down to it there are only two ways to proceed:

  1. Self signed certificates
  2. Public certificates for internal addresses

The stack overflow discussion mentioned previously can give a overview of some of the options.

For this project’s needs option 2 is not attractive because the need to involve an outside entity for certificate management. Option 2 also requires a more advanced approach to network routing and a hard requirement for at least occasional internet access for certificate validation. It has the benefit of effortless compatibility with web browsers. 

Self signed certificates  can provide a complete disconnect from outside entities if desired and still allow for web interfaces on the IoT devices. The downside is that the self generated root certificate will need to be installed on every device that is used to access the systems via SSL. Steps to do so on each O/S varies but is just a Google search away.

There is an excellent writeup here on how to  generate self signed certificates. The focus is for local software development, but the result generates certificates that will meet the projects needs.

There are two things to keep in mind here. First: server SSL certificates can only have a life span of 2 years. Trying to generate one with a longer life will succeed but Chrome will reject it.  Secondly — the root certificate will need to be imported in each O/S that will be used to access the IoT device. This is actually kind of a benefit as we an control release of the root certificate authority according to our security situation.

Instructions to create a trusted certificate:

https://www.freecodecamp.org/news/how-to-get-https-working-on-your-local-development-environment-in-5-minutes-7af615770eec/

https://github.com/dakshshah96/local-cert-generator/

When I set up my certificates I gave the root a lifetime of 20 years. I don’t want to have to touch a machine more than once. The server certificates had to be set to 2. The certs can be used for the Mosquitto server as well. This site has a good explanation of how to set up a Mosquitto server  for SSL

Managing users:

Each O/S has a specific way this must be done.

Mac O/S:

The original article details how to import the root certificate into the Mac keychain.

Windows/Linux:

This article details the steps: How to import CA root certificates on Linux and Windows. The Windows instructions worked without issue. The Linux instructions are iffy.

I0S:

The root ca must be emailed to an IOS device and imported. This article details the steps –> https://medium.com/collaborne-engineering/self-signed-certificates-in-ios-apps-ff489bf8b96e . These instructions had no issues.

Android:

This site has instructions on how to install self signed certificates. The steps were not tested.

Useful links:

Enable SSH for PAHO JavaScript client:

https://stackoverflow.com/questions/53051679/how-can-i-use-tls-with-paho-mqtt-over-javascript. The PAHO client will not use client certificates (which are the same as the sever certificates) and will support SSL only rather than full encryption. To do this in a browser mqtt.js will need to be used. 

Securing raspberry pi:

https://www.raspberrypi.org/documentation/configuration/security.md

https://www.raspberrypi.org/documentation/configuration/

PI running indicator and shutdown button

Aug 2021

One of the things I want for my IoT devices is a smart power button. When off or in low power I would like to be able to press the button and have the device power on. If the device is on I want to press the power button and have the device shutdown safely and either power off or indicate that power can be shut off.

The PI has built in support for the seed of my solution in the form of device tree overlays  (dto) that can activate kernel support for a shut down signal and a powered down signal. I will be using these two features to implement a simple shutdown button and running indicator led. Not quite the feature set I am looking for, but this may be useful to pull out for some circumstances. Besides, if I can control an led at shut down I could send a signal to put the PI in low power or have it’s UPS shutdown.

There are many sources on the web that detail these two dto’s . The two I found most helpful were:

For shutdown signal:

https://www.raspberrypi.org/forums/viewtopic.php?t=217442

For toggling GPIO at shutdown:

https://www.raspberrypi.org/forums/viewtopic.php?t=223157

It should be noted that specifying the gpio pin to use is different in these two overlays. My implementation looks like this (in /boot/config.txt):

dtoverlay=gpio-shutdown,gpio_pin=17,active_low=1,gpio_pull=up

dtoverlay=gpio-poweroff,gpiopin=19,active_low=1

With this configuration I am monitoring pin 17 for a low signal to initiate system shut down. When the system has halted, pin 19 will go low. Pin 19 will go from low to high on startup.

When pin 19 signals shut down there are two low to high transitions that look like this:

So — using this line to control a running led would have the led on while running. When the system shuts down the led will blink once and then go out indicating it is safe to power off. Keeping the led lit adds 10 ma to the measured current draw of the PI. If this is a concern the logic could be reversed and the led could be made to illuminate at system shutdown. I’m going to be using the led as a running indicator, so it will be on while running.

This simple circuit is all that we need to implement our running indicator and shutdown switch.

I2C Interfacing on the Raspberry PI

updated 30 July, 2021 

In this post I want to demonstrate some techniques for figuring out how to interface with an i2c chip and some basic verification techniques. I will be using a Raspberry PI 3 B, but these techniques will apply to most Linux based embedded systems with an i2c bus.

I2C is short for Inter-Integrated Circuit, a two wire bus used for connecting components to each other on electronic devices. I2C is just one option used for interconnecting devices. Other options  include. SPI, 1-wire, and Serial. When using devices for an embedded project we generally find SPI or I2C on devices. I tend to go for I2C devices when I can because they allow me to get away with using less wires to hook up.

When we integrate a device it is handy to ensure the device is recognized by the system before we try to write code for it. There are tools available for this called  I2C tools. Formerly a part of the LM-Sensors project, I2C tools allow low level access to I2C busses and the devices connected to them. These tools are not installed on the PI by default but are available via apt:

sudo apt install i2c-tools

Let’s get started. The four commands we are interested in are summarized in this table:

Bus scanningi2cdetect
Device register dumpingi2cdump
Device register readingi2cget
Device register settingi2cset

Bus scanning:  

We use the i2cdetect command to both find the busses available as well as the devices on the bus. If we execute

i2cdetect -l

we can get a list of the currently installed I2C busses. The output is as as follows:

We can see we have 1 I2C bus on the PI.  If we wanted to see the devices on bus 1, we would enter:

i2cdetect -r 1

and get something like the following output:

The result of i2cdetect -r 1. This gives us a dump listing the devices on i2c bus 1.

When executing this command we get the standard warning that executing this command may jack up our device. It’s true, we can cause errors if we run this command on some busses on other devices (like the obsolete Intel Edison), but it has always cleared up for me with a power cycle of the Device. Usually the device just hangs.

Looking at the output we can see we have four devices on bus 1. At 0x18 we have a MCP9808 temperature sensor, 0x3C is a SSD1306 display, 0x48 is an ADS1819 ADC , and 0x77 is a BMP085 barometric pressure and temperature sensor.

Device register dumping:

Now that we know what devices are on the board (or more accurately all the devices we wired to the PI are recognized by our system) we can start working with the devices. Each device has internal registers that control the device and provide output from the device. We use the i2cdump command to take a look at these registers. Let’s take a look at the MCP9808

i2cdump 1 0x18 w

The form of the command is 12cdump (bus | device address | option). The ‘w’ option give us word output which makes it easier (for me at least) to read the registers. Executing the command gives us output that looks like this:

i2cdump of our MCP9808 on i2c bus 1 in word format. There is a lot of register space for devices, but we see we only have 13 registers in our device.

To understand what we are seeing in the dump we need to study the data sheet for our MCP9808.

If we study the data sheet for the MCP9808 we can decipher the registers we dumped.

Keeping in mind that the MCP9808 is a little endian device, we see the 16 bit data registers storing their values with the least significant  byte (LSB) first and most significant byte (MSB) second. We just need to do a quick byte swap to look at register 0 to see the value is 0x001d. According to the spec sheet the MSB should always be 0x00 as well as the LSB bits 7-4. Bits 3-0 in the LSB are the pointer used to select which register we read and write to when communicating to the device. The MCP9808 is a surprisingly complex device, so we won’t look at all the registers. But a thorough explanation of the registers and their function begins on page 16 of the data sheet.

Read a specific register:

We could certainly read the registers from the output of  i2cdump but there is a specific command for reading a single register. If we wanted to read the the current temperature the data sheet tells us the register containing this value is 0x05. To read this register we would issue this command:

i2cget 1 0x18 0x05 w

The command is in the form i2cget (bus | device address | register | option) . This gives is the following output:

Use i2cget to read the current temperature from our MCP9808.

We get the requisite warning and then the little endian contents of the register. Swapping the bytes to 0xC152 and referencing page 25 of the data sheet we can get the current temperature in Celsius. Briefly —   Bits 15-13 are flags that are used when we set the device to monitor a high and low set point. They can tell us if a threshold was passed, and which one. We don’t have those set so the values are not significant to us in this case. Bit 12 of the word is the sign of the two’s compliment value, since it is 0 our value is positive. Stripping those four bits out we are left with 0x0152 which is the temperature value. The rest is a base conversion and a little math — the MSB is 1 decimal, the LSB is 82. So using the data sheet formula  –> 1 * 16 + 82 / 16  = 21 degrees Celsius. Thats a cool 69.8 F in the office today.

Set a register:

If we want to manually control the MCP9808 we can set its registers with the i2cset command. Before we use the command a little back ground.

The MCP9808 has two modes of operation — continuous conversion and shutdown mode. Shutdown mode puts the device into a low power state and , as you might have guessed, stops updating the temperature measurement. If we read the temperature register while the device is in shutdown we will retrieve the last measurement made. In a battery powered device it might be a good idea to shutdown the device and only power it up when we need to update the temperature. This is controlled by bit 8 of the config register located at 0x01.

We can use the following command to enter into shutdown mode:

 i2cset 1 0x18 0x01 0x0001 w

Remember that we need to swap the bytes in the word due to endianness. This can be tested by reading the temperature while heating up the sensor (I just put my finger on it). You can use i2cget or i2cdump to read register 0x05 of the device. To set the sensor to continuous conversion mode use this command:

i2cset 1 0x18 0x01 0x0000 w

This post covered the basics of verifying a device is detected on an I2C bus and determining if the device is functioning properly. We used the four I2C tools commands to detect the busses and devices installed, dump a device registers, read a specific register, and to write a register. With these four commands we can certainly see if our devices are working correctly as we implement an embedded system.

The i2c tools do have other modes of operation that I did not touch on here. These modes can be read about on the I2C tools docs page. Armed with these tools and the data sheet for a device we are ready to get coding.

Using the ZS-042 module

July 2021

The cheapest clocks around

The ZS-042 module is a very cheap DS3231 breakout board. Common on eBay and  Amazon units can be had for less than a dollar. It is a bonus that the module comes with a 4K eprom as well.  In my experience these units work well as a clock but damage the LIRC2032 battery that it is supposed to keep charged.  I used one with the charger enabled for about a month and found the LIRC2032 had swollen and would not take a charge. So to use these ZS-042s the charging circuit needs to be disabled and a standard CR2032 battery  used instead.

There are a lot of discussions on the net about this module. The best discussions can be found at the Arduino forum, the Cave Perl Project and One Transistor . All three sites discuss the charging circuit issue but the Cave Perl Project offers advice on how to make the board more power efficient. One Transistor gives a detailed explanation as to why the charging circuit is a problem on this board.

Basically — the built in battery charging circuit has a good chance of destroying LIRC2302’s because the voltage must be kept at a specific level to charge them. The charging circuit on the ZS-042 is not up to the task for this.

Certainly a standard CR2302 can not be used on an unmodified ZS-042. A non rechargeable battery should not be charged, at any voltage.

As far as I am concerned — the charging circuit must be disabled on this board. I don’t have confidence that I will be able to keep the supply voltage at just the right voltage to charge the battery correctly. Plus trickle charging a LIR2302 continuously  is not recommended (see the links for more info).

Removing the charging circuit also has the benefit of allowing us to run the RTC at 3.3 volts. You have to have 5 volts to charge, but the DS3231 has a very wide voltage range.

32k 
SQW 
SCL 
SDA 
vcc 
GND 
SCL 
N.C 
SDA 
N.C 
vcc 
N.C 
GND 
N.C 
ZS-042 Module 
Jumper 
4X4 7k 
4X4 7k 
GND 
IOOnF 
vcc 
SCL 
SD 
24C32 
16 
SCL 
15 
SDA 
N.C. 
N.C. 
N.C. 
N.C. 
> 
DS3231 
INT/SQW 
32kHz 
14 
V bat 
12 
11 
10 
200 
IN4148 
2032 
IOOnF 
1k 
POWER

To disable the charging circuit remove  the 1N4148 diode, 200 ohm resistor or both from the board. About two minutes work with a soldering iron. Easy to follow instructions can be found here:

https://www.onetransistor.eu/2019/07/zs042-ds3231-battery-charging-circuit.html

If removing a SMD from your board seems daunting this Youtube video shows you all about it in less than two minutes –> How to Remove SMD Resistors & Capacitors Using a Regular Soldering Iron

A modified board looks like this:

With a simple mod these dirt cheap and highly available modules can be used in all our projects.

Datasheets: