Schools resources in some parts of the world are very limited and health concerns does not take priority on local budgetary decisions.
Children in these schools could be dealing with a fever trigger by a viral infection and not being able to be identified after they share the virus with other children.
This device is a cheap, mobile, energy efficient way to take the vitals of children and transmit important information to a central command health for further action.
MotivationOn a more international note, "Doctors without borders" are global first responders to emergencies, epidemics, and natural disasters. They could benefit on a device that can link to a network that can deploy fast, and easy like SigFox. The fast sharing of information on an epidemic outbreak can be the difference on containment locally or a wider spread of the sickness.
We hope that the simplicity of the device will allow the gathering of spatial, temporal and vitals data, for prompt deploy of medical resources in affected schools.
Note that we are using the finger temperature to identify children with fever. This can be tricky and we have not found the correct parameters to trigger with confidence the fever alarm. We hope that further research and the interaction with the community might bring our settings closer to a reliable system.
MaterialsThe following is a snapshot of the materials we use for the device. Take into account that we use the MikroE boards without a proper interface stage board. The compact format of the board and the I2C interface facilitate the connection.
The SigFox network is a smart choice for areas where the construction of big scale infrastructure is not possible. The network deployment can be easily achieve by using a SigFox Access Station Micro SMBS-T4.
We use PyCom LoPy 4 boards to connect with the SigFox network. First, we have to register the devices with the network by following the instructions here.
Once register, you have to send the first payload to the network in order to activate the device. In our case was difficult since our local town does not have a SigFox gateway. Had to drive 80 miles to get to the closest gateway service area.
Now that the devices have being activated, you can get access to the messages and settings using the SigFox backend server. You can login at https://backend.sigfox.com/auth/login.
In order to get access thru the REST Api to your group of devices you need to set the proper privileges to allow access, You can do this by selecting the group in the backend server interface.
On the left right menu select the API Access option.
The page will update and show a New link on the upper right corner. Click on it.
The Api access creation form will show the Profiles that you can give access to your application. We allow access to the devices and messages thru the REST API.
After you press Ok, the form will refresh and show the access group settings. Inside you will see the assigned login and password, which differs from your backend server. These credentials will be require when using the SigFoxApi functions.
We get access to our device messages on the section SigFox -- Accessing Data section.
We are all set, now let us develop our device and front end access point.
THE DeviceThe device reads body temperature as heart beat rate, saves the data in the SD card, displays the data and sends the information to the SigFox network, in a compact and mobile enclosure. Currently, we are powering the device with a USB charging battery from 5 Below discount store.
The Pycom LoPy 4 is a quadruple bearer MicroPython enabled development board, supporting the SigFox network.
The use of the SigFox network requires to register the device as explained in the section SigFox Network. Once activated you can use the following class to create a communication socket.
The below class initializes the SigFox radio with the proper zone and frequency, then a communication socket is created and finally the transmission direction is set as uplink. The class includes functions for sending data and for closing the communication socket.
from network import Sigfox
import socket
import pycom
import time
class mySigFox():
def __init__(self):
self.loadSocket = False
def loadSigFox(self):
try:
# init Sigfox for RCZ1 (Europe) RCZ2 (America, Mexico and Brazil)
self.sigfox = Sigfox(mode=Sigfox.SIGFOX, rcz=Sigfox.RCZ2)
# create a Sigfox socket
self.s = socket.socket(socket.AF_SIGFOX, socket.SOCK_RAW)
# make the socket blocking
self.s.setblocking(True)
# configure it as uplink only
self.s.setsockopt(socket.SOL_SIGFOX, socket.SO_RX, False)
self.loadSocket = True
except Exception as e:
print(e)
def sendMsg(self, data):
self.s.send(data)
def closeSigFox(self):
self.s.close()
self.loadSocket = False
The
SigFox network allows 140, 12 byte messages per day. We transmit the GPS, number of patients, temperature, BPM, patient number and a message block code. The data is encoded in two consecutive messages with the following format:
Block 1, PACKAGE_REPORT
Latitude (float: 4 bytes) Longitude (float: 4 bytes) Number of patients (short: 2 bytes) Block code (short: 2 bytes)
Block 2, PACKAGE_PATIENT:
We use Python's struct library to format the data into a byte array to be sent as shown in the below code.
#Encode GPS and number of patients
ba = bytearray(struct.pack('f',float(0.0)) + struct.pack('f',float(0.0)) + struct.pack('h',patCounter) + struct.pack('h',PACKAGE_REPORT))
#Encode patient temperature and BPM
ba = bytearray(struct.pack("f", max.hr_avg)+struct.pack("f", float(txtTemp))+struct.pack("h", patCheck)+ struct.pack('h',PACKAGE_PATIENT))
There
is a 30 second delay between sending messages as per the SigFox recommendations.
The PyTrack is an expansion board for the Pycom multinetwork modules, with a GPS, accelerometer and a MicroSD card.
The PyTrack has an I2C bus that communicates with the internal sensors supported (GPS, accelerometer, MicroSD) and one more expose I2C bus defined in the external IO header using Pin 9 and 10 (P9, P10). The code below initializes the second I2C bus as a master and a baudrate of 400k (note that the sensors connected to this bus support this baudrate speed)
#Initialize the I2C bus
i2c = I2C(2, I2C.MASTER, baudrate=400000,pins=('P9','P10'))
We
updated the classes of the sensors connected to the board by allowing a parameter to initialize the internal property with the active I2C bus object. Also, a function to scan the I2C port and confirm that the device is found and connected. In the code below it happens to be the display.
def __init__(self, i2c):
if (i2c != None):
self.temperature = 0.00
self.i2c = i2c
self.isConnected()
def isConnected(self):
if self.i2c != None:
print("Scanning...")
# Check I2C devices
devices = self.i2c.scan() # returns list of slave addresses
print(devices)
for d in devices:
print(d)
if d == SSD1306_I2C_ADDRESS:
print("found display")
return True
print("Not found")
return False
else:
# No check for SPI
return True
The
extension board manages the MicroSD device. We initialize the SD object, mount it and open the log file, as shown below.
#Initialize the SD card and file
sd = SD()
os.mount(sd, '/sd')
f = open('/sd/gps-record.txt', 'a')
Finally, the extension board controls the GPS sensor. We initialize the sensor, inquire the coordinates, format them write them to the log file, as shown in the code below.
l76 = L76GNSS(timeout=30)
coord = l76.coordinates()
f.write("{} - {}\n".format(coord, rtc.now()))
Some of the code shown happen in different part of the program, we just gather together for having a sense of the process per sensor.
TemperatureWe use the python driver from Adafruit that supports the MAX30205 temperature sensor and updated to use the I2C functions from Pycom.
The changes shows like below.
readRaw = self.i2c.readfrom_mem(MAX30205_ADDRESS,MAX30205_TEMPERATURE,2)
#I2CreadBytes(MAX30205_ADDRESS,MAX30205_TEMPERATURE, &readRaw[0] ,2); //read two bytes
#I2CwriteByte(MAX30205_ADDRESS, MAX30205_CONFIGURATION, reg | 0x80);
self.i2c.writeto_mem(MAX30205_ADDRESS, MAX30205_CONFIGURATION, bytearray([(reg | 0x80)]))
The Adafruit library uses different functions to support I2C, we update the calls to use the Pycom I2C implementation as shown above. The function allows the setting of the base address of the register and then the offset value to write or read from the device register.
Then we collect in a loop the current read temperature with the below function call.
txtTemp = temp.getTemperature()
Heart RateWe took the Python library from https://github.com/zerynth/lib-maxim-max30101 and updated to use the Pycom I2C. Instead of changing each call to the write and read functions on the I2C library, we overload these functions and from within call the active I2C library.
def write_read(self, reg, nbytes):
self.i2c.writeto(MAX30101_I2CADDR, bytearray([reg]))
# data = self.i2c.readfrom_mem(MAX30101_I2CADDR, MAX30205_TEMPERATURE,1)
data = self.i2c.readfrom(MAX30101_I2CADDR, nbytes)
# write_read(MAX30101_REV_ID,1)
return data
def write_bytes(self, addr, reg):
self.i2c.writeto_mem(MAX30101_I2CADDR, addr, bytearray([reg]))
The following calls the function to check for a pulse signal and update of the class properties for the beats per minute (BPM) value.
detect_pulse(max)
DisplayThe starting code was taking from Adafruit python SSD1306, we created a class and update the code for the 128x64 pixels case. The class update the I2C calls to the use the Pycom interface. We also add the big font support for the original driver.
The class is used as the below code. The loop to display data consist on clearing the current buffer, add content (addString, addString2) and then draw the buffer to the display.
lopyLCD.clearBuffer()
lopyLCD.addString(0, 0, "GPS:{} VisteliLabs".format(txtGPS))
lopyLCD.addString(0, 1, "P Check {} P Count {}".format(patCheck,patCounter))
if (tryHeartRate):
lopyLCD.addString2(0,2,"{:0.2f} C".format(txtTemp))
lopyLCD.addString2(0,5,"{:0.2f} BPM".format(max.hr_avg))
else:
lopyLCD.addString2(0,3,"{:0.2f} C".format(txtTemp)) #2
lopyLCD.addString(0, 7, "Package: {}".format(packageCounter))
lopyLCD.drawBuffer()
The display format can be improved by displaying animated icons and other information from the heart rate sensor.
The EnclosureThe enclose was taken from Thingiverse: Simple case for Lopy. We did some alterations as you can see from the snapshots. Cuts on the upper left of the enclose were made to provide for the display mounting, and holes on the base to support the Pycom board and I2C hub. The cover was cut too for the MikroE pins to go thru. The modifications were made using a high speed drill.
Now the fun part begins. The snapshots below shows the steps taken to put together the MoBitals device.
The steps are
- Connect the cables to the PyTrack board with the external IO header (P9, P10, 3V3 Pymodule, GND) to enable the I2C bus 2 service.
- Mount the board and I2C hub into the enclosure.
- Attach the LoPy 4 board. Make sure you align the board correctly.
- Attach the temperature and heart rate 4 click boards to the enclosure cover. The pins should go thru the cuts made.
- Connect the corresponding cables as shown in the schematics to the click boards.
- Connect the I2C grove cables to the hub,
- Close the enclosure with the cover. On this prototype we use a rubber band to hold in place. The enclosure is not deep enough to handle the I2C grove cables we acquire. This can be fix by using shorter cables.
Your enclosure should look like the last two snapshots below.
Once the device is connected to a power source (we use a USB recharger battery), and the LED in the heart rate sensor lights position one finger over the LED and another finger over the temperature sensor (the little black square).
The display will show the following format:
The display shows
- Upper left corner first row: the status of the GPS sensor; if the sensor lock on a position it will display OK, if it has not shows NG (Not Good).
- Upper right corner first row: Trademark VisteliLabs :)
- Upper left corner second row: number of patients check (total with or without fever).
- Upper right corner second row: number of patients detected with fever or showing signs of sickness.
- Central first row: current read temperature in Celsius
- Central second row: current beats per minute read.
- Lower right corner: number of packages sent. A package in this screen means two payloads sent (Report and patient)
The screen is refresh on every read of the heart rate sensor.
Once the conditions for fever are reached (in our case 29 Celsius) a payload transmission to the SigFox network is started. The transmission consists of two packages (each 12 bytes) delayed by a 30 seconds wait time between them. The first payload contains the GPS coordinates, and sick patients counter; the second payload contains the current patient temperature and heart beat rate.
The device will continue measuring the vitals until the fingers are removed from the sensors; waiting for the next patient to come.
SigFoxApi -- Accessing DataThe first step is to install the SigFox libraries to access the REST API interface. Use the following command
pip install sigfoxapi
You can find the documentation here.
We use the sigfoxapi to communicate with the REST API server, Tk API for user interface, tkinterTable to display the messages data and webbrowser to link to the Google maps service for GPSlocation.
import tkinter as tk
from tkinter.ttk import *
from sigfoxapi import Sigfox
from webbrowser import *
from tkintertable.Tables import TableCanvas
from tkintertable.TableModels import TableModel
Then we connect to the SigFox backend server using the login and password obtained in the SigFox Network section. The function returns an object that serves as connector. Using this object we inquire the backend to return a list of registered devices to the group.
#Connect to sigfox backend server
s = Sigfox('login', 'password')
#get a list of the available devices
listDevices = s.devicetype_list()
We then use the id element of the devices listed to get the device unique name for the registered devices.
devices = {}
devCounter =0
#Extract the device id
for dev in listDevices:
devices[devCounter] = s.device_list(dev['id'])
devCounter+=1
We then retrieve all the messages received by first device registered. Our program only supports two devices since that was all our project budget. The program can be adapted for more adapters if necessary.
#Get the messages from first device
mes=s.device_messages(devices[0][0]['id'])
data,data1 = createDataSigFox(mes)
The
following code is not a complete version. It has being minimize for the purpose of showing the decoding of the received data.
This function receives the messages from the registered devices and decodes the payload. We use the struct package to help with the decoding. The received data is in string format and is converted to a byte array using the bytearray.fromhex function. Then we use the data format explained in the LoPy 4 section.
# PACKAGE_REPORT = 1
# PACKAGE_PATIENT = 2
def createDataSigFox(sfData):
data = {}
data1 = {}
b = bytearray.fromhex(c['data'])
dataCode = struct.unpack_from('h', b[10:12], 0)
if (dataCode[0] != 0):
for key, value in c.items():
if (key == 'data'):
a = bytearray.fromhex(value)
if (dataCode[0] == 1):
data[rowName]['Latitude']=struct.unpack_from('f', a[0:4], 0)
data[rowName]['Longitude'] = struct.unpack_from('f', a[4:8], 0)
data[rowName]['NumPat'] = struct.unpack_from('h', a[8:10], 0)
elif (dataCode[0] == 2):
data1[rowName]['BPM'] = struct.unpack_from('f', a[0:4], 0)
data1[rowName]['Temperature'] = struct.unpack_from('f', a[4:8],0)
data1[rowName]['Patient'] = struct.unpack_from('h', a[8:10], 0)
The
timestamp received was also challenging. The timestamp is received as a long integer contained in a string. We converted to integer the string value and use a datetime.fromtimestamp function to obtain a timestamp object. Then in order to display the value as a normal date/time format, we use the strftime function.
elif (key =='time'):
mydate = datetime.fromtimestamp(int(value))
if (dataCode[0] == 1):
data[rowName]['TImestamp'] = mydate.strftime("%Y-%m-%d %H:%M:%S")
elif (dataCode[0] == 2):
data1[rowName]['TImestamp'] = mydate.strftime("%Y-%m-%d %H:%M:%S")
Finally, we use the tkinterTable library to display the data on tables.
A challenge was to add a link to show in a map the GPS coordinates from the device. We use Google maps to show the location.
The following code shows how to attached a button to a table cell in order to execute the web link and display the map.
x1, y1, x2, y2 = table.getCellCoords(counter, 5)
btnText = "Btn{}".format(counter)
print(btnText)
linkGMaps[btnText] = tk.Button(table, text="Map IT!", fg="blue",cursor="hand2")
linkGMaps[btnText].pack()
print(linkGMaps)
linkText = "https://www.google.com/maps/?q="+mydata[c]['GPS']
print(linkText)
linkGMaps[btnText].bind("<Button-1>", lambda e,a=linkText: callback(a))
table.create_window(((x1 + x2) // 2, (y1 + y2) // 2), window=linkGMaps[btnText])
The time of true come, and when executing the program, we get an individual window per device with two data tables. The device name is displayed in the window title.
The first data table shows the timestamp, the GPS coordinates (note that we have remove the coordinates for privacy purpose), the number of patients recorded with fever at the time of the payload, the raw data received (payload) and a button that will triggered a web browser session and display the location on a Google map.
The second table shows the timestamp, the beats per minute, temperature and patient number recorded at the time of measurement, and the raw data received.
The snapshot below shows an example of location 0.0, 0.0 (latitude, longitude) on Google maps.
Although we hope for a clear path for development, we were not that lucky. Here is a couple of things we had to deal with.
On the pymakr.conf we at some point have issues uploading the scripts to the LoPy 4 board with a time out error. We found that changing the safe_boot_on_upload setting to true.
{
"address": "COM26",
"username": "",
"password": "",
"sync_folder": "",
"open_on_start": true,
"safe_boot_on_upload": true,
"py_ignore": [],
"fast_upload": false
}
Our initial design included LEDs and a cute bear paw to attract the attention of children meanwhile taking their vitals. But, the increase amount of power the LEDs require created a feedback on the LoPy device triggering I2C bus critical errors. We figure out that it was not only the power consumption but also the fact that we were using the RMT library to control the pulse for the LEDs was interfering with the I2C timing, or at least that is what we think.
Finally, the click boards require of special connections to work properly. The Fever click requires that the ground pin is connected on the same pin raw as the I2C connections. And, the Heart Rate 4 Click board needs the 5 V and 3V3 power pins connected, otherwise the LED will not light, that is the reason we use an I2C power connector to add the fulfill the extra needs.
Future WorkAdd pressure buttons to allow for personal settings, live temperature threshold and BPM refresh rate.
We have learned so much doing this project. From the needs first responders like Doctors without Borders are required to the intricate of connecting MikroE boards to non compliant boards.
We hope that this project can inspire more devices that emergency crews and local schools can use to help children.
Thanks for reading.
Comments