In case you are wondering if this post might be interesting for you, here is what we have achieved and what we will show in it:
- I2C communication between CY8CKIT-048 and Raspberrypi with EZI2C and quick2wire modules respectively.
- Real-time, multi-platform, free-software visualization of CY8CKIT-048 using Python, Qt and ZeroMQ.
- Integration of an external sensor (LY330ALH gyroscope) with CY8CKIT-048.
- Data logging using the
logging
Python module on the Raspberry Pi.
- Data analysis and results visualization on PC with Pandas, Jupyter and Bokeh.
- Results visualization and sharing with cloud-based ThingSpeak IoT platform.
All our code is freely available on GitHub, do not forget to check our repositories and give us some feedback! ^^
The environment control inside intermodal containers or trailers is key to deliver the goods in optimal conditions. Besides well-known cold chains for food and drugs, the levels of humidity should be also checked to avoid phenomenons as container rain, cargo sweat or corrosion. Thefts and negligent manipulation are also typical concerns.
Analog Coprocessor Pioner Kit together along any of the inertial measurement units from Mouser could detect typical problems. While Cypress device already includes sensors to guarantee cold chain (temperature sensor), humidity levels (humidity sensor) and thefts detection (motion and ambient light sensors), Mouser inertial measurement unit would help to identify careless manipulation.
With all this data, would only remain to inform stakeholders (carriers and customers) via instant alarms or historical reports.
The stowaway project is composed by:
- A CY8CKIT-048 board with a temperature, an ambient light and an humidity sensor to guarantee cold chain, humidity levels and detect thefts. It is also used to convert the output of LY330ALH gyroscope sensor from analog to digital . The sensors data is sent to the external world through I2C.
- A LY330ALH gyroscope sensor that provides a measured angular rate on z-axis to identify careless manipulation of the container.
- A Raspberry Pi that collects information from CY8CKIT-048 through I2C. It saves sensors data on a convenient format to posterior analysis. It also sends real-time measurements that can be received by any PC connected over wifi or ethernet.
- A PC to analyze the hystorical reports and deduce the origin of the defective goods. It also shows data graphics in real time and will instantly warn stakeholders about dangerous situations.
To start with the CY8CKIT-048 board, we programmed it with the examples provided by Cypress on the kit web page. There are so many examples as sensors integrated in the board and besides specific blocks needed for each type of sensor, each example includes the component EZI2C from Cypress Catalog to connect the board as slave with I2C .
Although the pack includes PSoC Creator version 3.3, the examples are also fully compatible with last version 4.0 after updating ADC component.
Raspberry Pi 3 initial configurationDownload Raspbian minimal image, extract it and burn it to the micro-SD card (make sure you select the appropriate output device!):
dd bs=4M if=2017-01-11-raspbian-jessie.img of=/dev/sdd
sync
Connect a keyboard and a display to the Raspberry and boot from the micro-SD card. Login as `pi` with password `raspberry`. Then:
sudo raspi-config
- Select the
Expand Filesystem
option.
- Under
Advanced Options
: Enable SSH and Enable I2C.
Reboot.
Make sure to change your default password with `passwd` and add your public key to `.ssh/authorized_keys`. Check that you are able to log in through SSH without needing to enter the password and then disable SSH login with password:
sudo sed '/PasswordAuthentication yes/s/.*/PasswordAuthentication no/' /etc/ssh/sshd_config | grep PasswordAuthentication
sudo systemctl restart ssh.service
Upgrade and install dependencies:
sudo apt-get update
sudo apt-get upgrade
sudo apt-get install git i2c-tools libzmq3-dev
Download and install Miniconda:
wget http://repo.continuum.io/miniconda/Miniconda3-latest-Linux-armv7l.sh
# Make sure you verify the checksum at https://repo.continuum.io/miniconda/
chmod +x chmod +x Miniconda3-latest-Linux-armv7l.sh
./Miniconda3-latest-Linux-armv7l.sh
Log out and back in (close and reopen SSH session). You may also want to delete the script you just downloaded.
Then, allow `pi` user to access I2C:
sudo adduser pi i2c
Log out and back in (close and reopen SSH session).
Verify that the user is able to detect the I2C interface. First list the available I2C interfaces and then take the I2C interface name and test it:
i2cdetect -y $(i2cdetect -l | awk '{ print $3 }')
If everything went well, the output should look like this:
0 1 2 3 4 5 6 7 8 9 a b c d e f
00: -- -- -- -- -- -- -- -- -- -- -- -- --
10: -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- --
20: -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- --
30: -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- --
40: -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- --
50: -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- --
60: -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- --
70: -- -- -- -- -- -- -- --
Clone the Stowaway project and create a virtual environment:
git clone https://github.com/CojoCompany/stowaway.git
cd stowaway
conda create -n stowaway python=3
source activate stowaway
Then, within the virtual environment, install the required packages:
pip install --upgrade pip
pip install -r requirements.txt
Optionally, Stowaway can send data in real time over the network using ZeroMQ. To activate that functionality, just make sure to install `pyzmq`:
conda install pyzmq
If you followed all the steps and you have the CY8CKIT-048 connected to the Raspberry and both powered on, then you should be able to run the main program with:
python stowaway.py
It will stuck in an infinite loop logging the data read by the CY8CKIT-048.
I2C connectionWe implemented communication between the Raspberry and CKIT using I2C, being the Raspberry the master and the CY8CKIT-048 the slave.
With the EZI2C module from Cypress Catalog included in the design of CKIT, the python module quick2wire and the initial configuration (Section above) on Raspberry, only remains take care of connection schema.
Different pins could be selected on CKIT board for I2C, but we used the ones configured on web examples. For the Raspberry we also used the ones by default.
Although not mandatory, it may be convenient to connect not only the I2C SDA and SCL pins, but also GND pins (specially if the boards are powered from different sources).
To finish, let's explain the dependencies between the configuration and code of the module EZI2C from the CY8CKIT-048 board, and the python module quick2wire on Raspberry Pi code.
The slave address is configured on the EZI2C module and included on the Raspberry code to find the CKIT board.
data = bus.transaction(i2c.reading(8, format_chain_size))
The data structure exchanged within I2C, is included on both CKIT and Raspberry code.
C code on CKIT:
typedef struct PROCESS_VALUES
{
int16 Temperature; /* Measured temperature */
uint16 LightIntensity; /* Light Intensity */
uint16 Humidity; /* Relative Humidity */
int16 Voutz; /* Gyroscope Output Voltage */
} PROCESS_VALUES;
Python code with struct module on Raspberry Pi:
gyroscope = [
('temperature','h'),
('light','H'),
('humidity','H'),
('gyroscope','h'),
]
Gyroscope integrationWe wanted to integrate an external sensor in our prototype. Ideally, this would have been a 3-axis gyroscope or accelerometer. However, we only had a 1-axis gyroscope at hand: the LY330ALH gyroscope. It has an SMD package, so we had to solder a couple of cables with care:
Then we put it in a breadboard with the manufacturer's recommended application set-up (the extra capacitors in the breadboard are due to lack of capacitors with the recommended capacity):
Here is the schematic for the gyroscope circuit:
The schematic was created using Upverter and it can be found online for you to use it or edit at will.
For simplicity, we will be representing this basic setup circuit diagram as:
This circuit has two inputs (GND and VCC) and two outputs (VREF and VOUT), which can be connected to the CY8CKIT-048 as follows:
Or, in pictures:
For our project, we will be working with both the absolute position and the angular rate. The gyroscope actually provides angular rate so to get absolute position we just integrate the angular rate over time.
Knowing the parameters of the gyroscope (3.752 mV/dps) we can define the variation of the position (degrees) between t1
and t2
as the following approximation:
Where V(t)
is the difference between the gyroscope's VREF and VOUT:
The LY330ALH gyroscope provides a measured angular rate to the external world through an analog output voltage. With the CY8CKIT-048 board, this output can be transformed to an I2C signal to be consumed by the raspberry pi or pc.
The gyroscope should be powered with a Vdd between 2.7 and 3.6 V and the outputs are basically two: Vref with the reference voltage and OUTZ with the Z axis output voltage.
With this information, and after taking a look to the temperature sensor project provided by Cypress, we decided to use the Scan ADC from Cypress library with a signed differential channel to transform analog voltage information into digital, the Prog Vref and Opamp components to power the gyroscope with 3.3 V and the module EZI2C to send the voltage digital output through I2C.
To configure the ADC and pins mapping, we took into account the future integration with temperature and ALS sensors provided by Cypress. For that, and after checking the range of gyroscope output, we used Vref 1.200 V and unused pins on example projects.
Besides, to better understand Scan ADC operation and test the design we started attaching to the Cypress board a voltage divider.
Once we learned how the ADC worked we attached the gyroscope to the board and started to read angular rate results.
To finish, we integrated the sensors needed for our project: temperature, humidity, ALS and gyroscope. First, we deleted from the Environment Sensor Hub Demo project the board unused sensors and last, we included gyroscope. The code and schematics are available on attachments.
In order to visualize the sensors, we made a multi-platform free-software graphical user interface with Python, the code is freely available on GitHub, so do not forget to check it out, fork it an improve it!
Here we can see a video of the interface monitoring the light sensor (yellow line), the temperature sensor (red line), the humidity sensor (blue bar) and the gyroscope sensor (3D view).
This is the configuration file used to get that interface:
gui:
fullscreen: True
refresh:
data_period: 10
display_period: 50
server:
host: '192.168.0.39'
port: 5000
sensors:
position:
reference: 'G'
type: "euclidean"
temperature:
reference: 'T'
type: "line"
magnitude: "Temperature"
unit: "°C"
color: "#dc381f"
seconds: 4.
min_y_range: 5.
humidity:
reference: 'H'
type: "bar"
magnitude: "Humidity"
unit: "%"
color: "#add8e6"
y_range: [0, 100]
light:
reference: 'L'
type: "line"
magnitude: "Light"
unit: "lux"
color: "y"
seconds: 4.
min_y_range: 10.
And the corresponding main.py file (we are still working on letting the user define the Qt splitters directly from the configuration file as well):
from Qt import QtGui
from Qt import QtCore
from spinete.gui import run
def splitter(self):
hbox = QtGui.QHBoxLayout(self)
splitter1 = QtGui.QSplitter(QtCore.Qt.Horizontal)
splitter1.addWidget(self.sensors['position'])
splitter1.addWidget(self.sensors['humidity'])
splitter1.setSizes([800, 200])
splitter2 = QtGui.QSplitter(QtCore.Qt.Vertical)
splitter2.addWidget(splitter1)
splitter2.addWidget(self.sensors['temperature'])
splitter2.addWidget(self.sensors['light'])
splitter2.setSizes([400, 200, 200])
hbox.addWidget(splitter2)
self.setLayout(hbox)
if __name__ == "__main__":
run(splitter)
Of course, you will need to change the `host
` and `port
` parameters in the configuration file to match your Raspberry Pi's IP address.
The best thing about using a TCP-based PUB-SUB communication pattern is that it is very easy to connect multiple devices to the real-time data stream to visualize it from them simultaneously. To do so, simply run the Spinete interface from another computer in the network!
The data from the CY8CKIT-048 is stored on the Raspberry Pi on a text file using a CSV format.
During development, MySQL and SQLite databases were also considered and implemented. These were discarded due to the higher consumption of raspberry CPU resources (MySQL: 19-28 %, SQLite: 16-21%, logging-CSV: 7-8%), and also because of the extra configuration and management complexity.
The python module logging is used to convert sensors data to CSV file. With a very simple configuration instruction we can define the output filename ('sensors.log
') and the format in which we want to save the data.
logging.basicConfig(format='%(levelname)s,%(message)s',
filename='sensors.log', level=logging.INFO)
The instructions below are included on the code where we want to save the sensor values. The sensor id ('T' for temperature, etc), the time and the value of the sensor are separated with a comma to get the CSV file.
logging.info('T,{},{}'.format(now, temp))
logging.info('H,{},{}'.format(now, hum))
logging.info('L,{},{}'.format(now, light))
logging.info('G,{},{}'.format(now, dps))
The resulting file looks like this:
To represent and analyze the sensors data stored on the Raspberry Pi, we use a Jupyter notebook together along Pandas library. You can find it on the stowaway repository.
First, we copy the log file from the Raspberry to our PC.
scp pi@raspberrypi.local:/home/pi/sensors.log sensors.log
We copy the log file on the same folder than Jupyter notebook to match with current import path. As we used csv format on logging, the sensors data can be easily read with the function read_csv.
df = pandas.read_csv('sensors.log',names = ["Level", "Sensor", "Time", "Value"],parse_dates=['Time'])
Once with sensors data in a data frame, to extract the values from a specific sensor we only need to know the sensors identifier ('T' for temperature for example).
temp = df.loc[df['Sensor'] == 'T']
A graphic with historical data is shown with Bokeh and with interactive buttons the admissible thresholds or ranges can be configured. These thresholds are shown on the graphic and are the reference to detect warning time intervals and calculate some relevant data about them as duration, maximum or average value.
To mitigate the oscillations over the thresholds, the minimum time interval detected is one minute.
This notebook could be reused for the rest of sensors changing the identifier filter and thresholds ranges.
Report with cloud IoT platformsAlthough Jupyter gives you a lot of flexibility and control over the generated reports, sometimes using a cloud-based IoT platform can be very useful. We tried ThingSpeak to upload the logged data, visualize it and share it through a public URL, allowing visitors to export and download the displayed data.
In order to upload our data to ThingSpeak we had to perform a simple transformation (a table pivot):
import pandas
# Load logged data
df = pandas.read_csv('sensors.log_20170219',
names=['level', 'sensor', 'timestamp', 'value'],
parse_dates=['timestamp'])
# Perform a simple transformation
df = df.pivot(index='timestamp', columns='sensor', values='value')
# Save results to a new file for uploading
df.to_csv('thingspeak.csv')
The new generated CSV can then be imported directly to ThingSpeak for visualization and sharing:
You can also visit our public link to see the visualization for yourself and download the data.
Note that each of those graphics can be embedded as an IFrame in other websites, which can be very convenient for sharing results with interested parties.
CAD DesignWe made an enclosure for the prototype which consist in two parts: top and bottom.
The bottom enclosure has a space for the battery and power circuit, mounts for the CKIT and Raspberry and a hole for the Raspberry connectors in one side. It also has holes in the corner for mounting it with the bottom part:
The top simply has a hole mesh over the CKIT to let some light in and to let air (temperature and humidity) flow. It also has holes in the corner for mounting it with the top part:
Both parts were designed using CadQuery with a very simple source code, which is included here for reference (the code can also be found at GitHub):
from cadquery import Workplane
from Helpers import show
# Enclosure dimensions
WIDTH = 180
LENGTH = 180
BOTTOM_HEIGHT = 30
TOP_HEIGHT = 10
THICK = 3
# Passthrough holes
PASS_INNER = 3
PASS_OUTTER = 8
PASS_CBORE = 6
PASS_CBORE_DEPTH = THICK / 2.
PASS_WIDTH = WIDTH - PASS_OUTTER
PASS_LENGTH = LENGTH - PASS_OUTTER
# Mesh dimensions
MESH_SPACE = 10
MESH_WIDTH = 6
MESH_LENGTH = 4
# CKIT dimensions
CKIT = dict(
WIDTH=99,
LENGTH=63,
CLEAR=15,
INNER=2,
OUTTER=5,
HEIGHT=20 # TODO: calculate this,
)
CKIT['CENTER_WIDTH'] = (WIDTH - CKIT['WIDTH']) / 2. - CKIT['CLEAR']
CKIT['CENTER_LENGTH'] = (LENGTH - CKIT['LENGTH']) / 2. - CKIT['CLEAR']
# Raspberry Pi dimensions
RBPI_HOLE_WIDTH = 56
RBPI_HOLE_HEIGHT = 20
RBPI = dict(
WIDTH=85,
LENGTH=49,
CLEAR=27,
INNER=2.75,
OUTTER=6,
HEIGHT=BOTTOM_HEIGHT-RBPI_HOLE_HEIGHT
)
RBPI['CENTER_WIDTH'] = (WIDTH - RBPI['WIDTH']) / 2. - RBPI['CLEAR']
RBPI['CENTER_LENGTH'] = -((LENGTH - RBPI['LENGTH']) / 2. - RBPI['CLEAR'])
# Wall dimensions
WALL_THICK = THICK
WALL_CENTER_WIDTH = min(
RBPI['CENTER_WIDTH'] - RBPI['WIDTH'] / 2. - RBPI['CLEAR'],
CKIT['CENTER_WIDTH'] - CKIT['WIDTH'] / 2. - CKIT['CLEAR']
)
WALL_HEIGHT = BOTTOM_HEIGHT - 5
def enclosure_passthrough_vertices(face):
workplane = face.workplane()
rect = workplane.rect(PASS_WIDTH, PASS_LENGTH, forConstruction=True)
return rect.vertices()
def enclosure(height):
# Bottom enclosure part
part = Workplane('XY').box(WIDTH, LENGTH, height)\
.faces('+Z').shell(THICK)
part = enclosure_passthrough_vertices(part.faces('>Z'))\
.rect(PASS_OUTTER, PASS_OUTTER).extrude(-height)\
.edges('|Z').fillet(2)
part = enclosure_passthrough_vertices(part.faces('<Z'))\
.cboreHole(PASS_INNER, PASS_CBORE, PASS_CBORE_DEPTH)
return part
def mount_vertices(face, dimm):
return face.workplane()\
.center(dimm['CENTER_WIDTH'], dimm['CENTER_LENGTH'])\
.rect(dimm['WIDTH'], dimm['LENGTH'], forConstruction=True).vertices()
def mount(part, dimm):
vertices = mount_vertices(part.faces('<Z'), dimm)
part = vertices\
.circle(dimm['OUTTER'] / 2.).extrude(-THICK - dimm['HEIGHT'])
part = vertices\
.cboreHole(dimm['INNER'], dimm['OUTTER'], PASS_CBORE_DEPTH)
return part
# Bottom enclosure
bottom = enclosure(BOTTOM_HEIGHT)
# CKIT and Raspberry Pi mounts
bottom = mount(bottom, CKIT)
bottom = mount(bottom, RBPI)
# Raspberry Pi hole
bottom = bottom.faces('>X').workplane()\
.center(-RBPI['CENTER_LENGTH'], (BOTTOM_HEIGHT - RBPI_HOLE_HEIGHT) / 2.)\
.rect(RBPI_HOLE_WIDTH, RBPI_HOLE_HEIGHT).cutBlind(-THICK)
# Wall
bottom = bottom.faces('<Z').workplane()\
.center(WALL_CENTER_WIDTH, 0)\
.rect(WALL_THICK, LENGTH)\
.extrude(-WALL_HEIGHT - THICK)
# Top enclosure
top = enclosure(TOP_HEIGHT)
top = top.mirror('XY')
# CKIT mesh
top = top.faces('<Z').workplane()\
.center(CKIT['CENTER_WIDTH'], CKIT['CENTER_LENGTH'])\
.rarray(MESH_SPACE, MESH_SPACE, MESH_WIDTH, MESH_LENGTH, center=False)\
.rect(8, 8).cutThruAll()
top = top.translate((WIDTH + 20, 0, 0))
Scroll down to visualize an interactive 3D version of the enclosure parts with a, perhaps, nicer rendering. Both parts can be downloaded from Sketchfab, where they were uploaded.
Future workThere is still a lot of work to do including:
- ALS sensor calibration.
- Using a 3-axis gyroscope (we only had a 1-axis gyroscope available at the time), which should be easy to integrate.
- Substituting the Raspberry Pi with a low-consumption micro-controller. This requires to migrate Python code to C as well.
- Use batteries to power the system.
- Design the schematics and PCB of a final prototype that includes all the components required for the application and excludes those dispensable.
Comments