The most important and critical factor to concern is maintaining the appropriate temperature throughout the pharmaceutical transportation cycle. Even a small temperature variation can be enough to spoil the entire lot, which could result in huge losses. This issue is worsened when shipping pharmaceuticals between locations with extreme temperature variances. On such occasions, keeping a track of both the location and the temperature will always be important.
MedBox is a smart device which can control the temperature of the medical containers and allow the shipper/dealer to monitor the temperature inside the container along with the GPS location of it throughout the journey through a dashboard. This device can be used in
- pharmaceutical deliveries
- medical sample deliveries
- ready to transplant organs
For mass pharmaceutical dealers, the number of containers that should be monitored simultaneously is higher. In such cases, a centralized system where all these can be handled will be more effective.
Mass pharmaceutical dealers who have more customers will have more containers that should be managed online. Apart from that, For each of the drugs, the temperature which should be maintained constant will also differ. Given that there is an active wifi connection, both the GPS location and current temperature of the container will be uploaded to the cloud. This system has a dashboard where a dealer can monitor a large number of containers in terms of location and temperature distribution over time, register new devices and update device details such as the temperature to be made constant, more effectively and conveniently.
Implemented SystemThe system mainly consists of two parts; hardware device and the cloud connected dashboard. The device is powered through a DC jack. The AC voltage driving section should be connected to the main AC power and to the cooling element of the container. Then the device starts working and controls the temperature. Upon providing an active WiFi connection, it will connect to the cloud service and the device can be controlled through the dashboard.
Dashboard
The dashboard allows the admin to register multiple devices and control them in one place. Further, the dashboard has provided a separate field to display the customer name and the drug type inside the container. This makes it easier to identify each device by the customer name rather than identifying the device with the device ID. The target temperature which should be maintained inside the container can be defined from the dashboard and change if required. The dashboard admin can delete a particular device when needed.
The device logs its GPS location and measured temperature inside the container in the cloud database. When a device is selected from the dashboard, it will display the location tags and temperature values during the past 30 days from today. A filter can be applied by selecting different time intervals. When the user clicks on a specific geotag, the dashboard will show the corresponding timestamp, making it's easy to identify the location of the container at a specific time.
Now let's go through the implementation details of this project.
Getting StartedTips to work with AVR IOT WG
Getting started tutorial series provided by Microchip has given clear information about installing MPLAB X and uploading firmware. Here I'm giving some additional tips to make the coding experience easier with MPLAB X and AVR IOT board.
1. If you are an Arduino guy and heavily using the serial monitor for debugging you could use the printf function to send the data to the UART port. This can be viewed using any serial console or the Arduino serial monitor.
/*print temperature values*/
rawTemperature = SENSORS_getTempValue();
printf("Temp:%2d.%2d",rawTemperature/100,rawTemperature%100 );
2. The pin map of the board is shown in the following diagram.
(source - https://github.com/MCUdude/MegaCoreX)
The used pins of the board can be found in the pin_manager.h file. Predefined functions in the same file can be used to control GPIO pins. But if you want to control an undefined pin in this file, the following method can be used. Here, I'm writing a function to blink an LED connected to PD7. To see it working replace the code in the main.c file with the following.
#define F_CPU 10000000
#include <avr/io.h>
#include <avr/delay.h>
int main(void){
PORTD.DIR |= PIN7_bm;
while (1){
PORTD.OUT |= PIN7_bm;
_delay_ms(500);
PORTD.OUT &= ~PIN7_bm;
_delay_ms(500);
}
}
3. Try "clean and build" if you are confident about the code but still getting build errors.
Hardware designAs shown in the above system block diagram we have two sensors and one AC driving section in the circuit.
The AVR IOT board comes with an onboard temperature sensor. But for this application, an external temperature sensor is ideal because of two reasons.
1. The onboard temperature sensor captures the heat generated by other components of the board.
2. If going with the onboard sensor, the whole device should be placed inside the cooler to get readings.
But for the PoC, we are moving forward with the onboard temperature sensor.
To get the coordinates of the location of the device, I'm using a Neo-7m GPS module. Following is the pin connection between the AVR-IOT board and the GPS module.
- (GPS) Vcc -> 5v (AVR-IOT)
- (GPS) Gnd -> Gnd (AVR-IOT)
- (GPS) Tx -> Rx (AVR-IOT)
The cooling element driving part can be easily done using a mechanical relay. But due to frequent operation, it can be failed within a short time. Thus, a solid state relay is chosen for the task. The following is the diagram for the AC driving part. The transistor is controlled through the PD7 pin.
The enclosure should have enough space to place the AVR-IOT board, Solid State Relay and the 5v regulator. The GPS module is mounted outside the enclosure to capture satellite data without any interference. The 3D printable STL file is attached under the attachment section.
Now let's focus on implementing the firmware for the board.
Getting GPS ReadingsThe GPS module comes with a UART interface. When the module is powered up, it will transmit GPS data through the tx pin.
The Tx and Rx pins of the board are connected to the UART1 port. The original AVR IOT WG firmware has an implemented library only for the UART2. By observing the datasheet of the Atmega4808, we can implement functions to handle UART1. I have included a library for the UART1 in the firmware.
The baud rate of the GPS module is 9600. We have to define this in the usart1.c file.
USART1.BAUD = (uint16_t)USART1_BAUD_RATE(9600);
The next challenge is to extract the location coordinates from the series of dataflow coming from the GPS module.
The latitude and longitude data are in the line starts with $GPGGA. Following is a sample line that starts with $GPGGA.
$GPGGA,123519, 4807.0388,N,01131.0000,E,1,08,0.9,545.4,M,46.9,M,,*47
The first number which is separated by commas contains the UTC time in which the measurement is taken. Here, it is 12:35:19 UTC.
The range of the latitude is from -90 to 90 and longitude is from -180 to 180.
The second number shows the latitude of 48.070388N. If we use the numerical convention for the coordinates 'N' means positive and 'E' means negative. The third number shows the longitude of 11.310000E. The convention is positive for 'E' and negative for 'W'.
This GPS_Read() function has been implemented in the GPS.c file.
Temperature ControllerThe temperature controller activates and deactivates the cooling element by comparing the current temperature with the target temperature. The target temperature is sent to the device from the dashboard. Once it's received, the device stores the value in the EEPROM. The following line stores the received payload in the particular address.
eeprom_write_word (0x1400, payload);
setTemp = atoi(payload);
When the AVR IOT board initializes its' functions, this value is retrieved and converted into an integer value.
setTemp = atoi(eeprom_read_word(0x1400));
Adding the complementary filter
The temperature controller turns on the cooler if the current temperature is higher than the target temperature and vice versa. Usually raw sensor readings give spikes. This will result in turning on and off the cooler several times within a short time period which is not good for the cooler. To avoid that we need to filter out spikes from the temperature readings. While there are several filters, I have selected the complementary filter to avoid the temperature controller react to sudden temperature spikes which is not necessary to drug cooler boxes. The temperature reading function inside the sensor_handling.c is modified in the following way.
int16_t SENSORS_getTempValue (void){
int32_t temperature;
for (uint8_t i=0; i<10; i++){
temperature = i2c_read2ByteRegister(MCP9809_ADDR, MCP9808_REG_TA);
temperature = temperature << 19;
temperature = temperature >> 19;
temperature *= 100;
temperature /= 16;
curr_temp = (curr_temp*0.95) + (temperature*0.05); //filter
DELAY_milliseconds(10);
}
return curr_temp;
}
The main.c file is the first script to run after powering up the device. This contains two functions; application_init() and runScheduler(). The runScheduler() function runs within an infinite loop. This function gives priority to establish the WiFi connection. Thus, it is ideal to run the temperature control function separately.
The tempController() function is implemented inside application_manager.c. The function changes the state of the pin 7 to HIGH if the current temperature > target temperature. Finally, it is added to the main.c file.
while (1){
runScheduler();
tempController();
}
Let's test this function with an LED when the device is not connected to a WiFi network. Here I use 50C as the target temperature and use a hairdryer to increase the temperature of the sensor.
After all these changes the json object is modified to include the location and temperature data.
sprintf(json,"{\"Loc\":\"%s\",\"Temp\":%d.%d}",location,rawTemperature/100,rawTemperature%100 );
Setting the project with Google CloudUnderstand the basic structure
If you observe the mqtt_config.h file, you could identify two topics that the device publishes data and subscribes to receive data.
#define CFG_PUBTOPIC "mchp/iot/events"
#define CFG_SUBTOPIC "mchp/iot/config"
When the device is connected to the Google IoT Core, it publishes the data inside the "json" char array to the events topic. This function is implemented as sendToCloud() inside the application_manager.c file.
If we publish something to the config topic from the cloud, the device will receive the data and store it in the "payload" variable. This function is implemented as receivedFromCloud() inside the application_manager.c file.
This is all we need to know at the beginning to publish the data to the cloud and get the data from the cloud.
Setting up Firebase
Firebase is chosen for two reasons.
1. The data can be viewed manually in a tree structure.
2. Google IoT Core supports running Node.js scripts to handle data stored in Firebase.
To setup the Firebase, we use the scripts implemented by Leverege. First open the gcloud shell and authorize it. dependency
If the gcloud shell is not showing the project name as in the below image type $gcloud config set project <project id>
in the shell. Otherwise, the script will not run correctly.
Then follow all the steps provided in https://github.com/Leverege/microchip-avr-iot/tree/master except the firmware updating part.
Figured out insights
1. Keep the registry name as AVR-IOT (default name). Otherwise you may have difficulties when connecting the board with the cloud service.
2. This script has used node 8. Since google cloud is moving with node 10 and 12, it is better to update the node version. For that you can update the node version to 10 in the setup > functions > package.json file just after cloning the repo. You may use the $cloudshell edit <file name>
to do this inside the cloud service.
"engines": {
"node": "10"
}
Adding Configurations to the Device
After creating the cloud project, we need to enter the project id and the registry id in the device firmware inside the cloud_config.h file.
To get the project ID, click on the project list on the top bar. This will popup a window with all the available projects and project IDs. Registry ID is the ID, which we have given when running the firebase setup script. The default ID is "AVR-IOT". Otherwise, the ID can be found at this link https://console.cloud.google.com/iot/registries.
#define CFG_PROJECT_ID "medical-cooler-box"
#define CFG_PROJECT_REGION "us-central1"
#define CFG_REGISTRY_ID "AVR-IOT"
Tip - If you are trying this for the first time, I would suggest keeping the ID as "AVR-IOT". Otherwise you might have to change some other dependency variables, which are not covered here.
Logging published dataAfter initializing the Leverage scripts, the published data to the relevant topic is logged inside the Firebase database. We will see how it happens and how to modify this process to get the preferred data structure.
Inside the Leverege repo, setup > functions you may find two major JS files; index.js and FirebaseWriter.js. Inside the index.js file, we can find the main function which gets triggered when we publish something to the 'avr-iot' topic.
exports.recordMessage =
functions.pubsub.topic('avr-iot').onPublish((message)=>{ }
As mentioned earlier you might want to change the topic name here if you choose a different registry id. Inside this script, it creates a message object by adding the received json from the device and the current timestamp. For our task we need to create the datalog structure as shown in the following image.
We need to edit the index.js file by removing the timestamp and keeping the original json message by replacing the following two lines with the shown single line.
//original script
const time = Date.now();
msg = Object.assign({},msg,{time});
//modified script
msg = Object.assign({}, msg);
The msg object contains the GPS coordinates and temperature readings. Those should be logged inside datalogs > {device id} > {timestamp}. For this, we need to edit the FirebaseWriter.js file.
// original script
const ref = this.admin.database().ref( '/avr-iot/' );
const newMsgRef = ref.push();
const newMsgKey = newMsgRef.key;
const updatedDeviceData = {};
updatedDeviceData[`data/${deviceId}/${newMsgKey}`] = msg;
updatedDeviceData[`lastUpdated/${deviceId}`] = msg.time;
//modified script
const ref = this.admin.database().ref( '/avr-iot/' );
const time = Math.round(Date.now() / 1000);
const updatedDeviceData = {};
updatedDeviceData[`datalogs/${deviceId}/${time}`] = msg;
Both scripts can be edited within the Google Cloud Console. Go to Cloud Functions and select the "recordMessage" function. Click Edit > Next and then you will get a script editor. Deploy the function again after making the changes mentioned above.
Send target temperature values to the deviceWhen we publish data to the "config" topic, it will be automatically retrieved by the device. The "Quality of Service" of this process is 1. This means the cloud service will make sure that the device gets the published message when it's connected. The advantage of this is, you don't need to check whether the device is online before publishing data.
To test this, first add the prinf function at the end of the receivedFromCloud function inside the application_manager.c file.
debug_printIoTAppMsg("topic: %s", topic);
debug_printIoTAppMsg("payload: %s", payload);
printf("\npayload: %s\n", payload);
To publish a test message go to the Registries, select the registry id, go to the Devices tab and select the device id. On top of the page you can find the Update Config function. Type the message that you want to send and click send to device. You may notice that the same message will get printed on the serial monitor.
Now we need to integrate this functionality in our backend so that when the target temperature is changed from the dashboard, the backend script will send the value to the device.
Google has provided clear documentation about this process and required Node JS scripts. Go to the "Updating and reverting device configuration" section of the following link and check API > Node js for the script.
https://cloud.google.com/iot/docs/how-tos/config/configuring-devices
Uncomment and set corresponding values to cloudRegion, deviceId, projectId and registryId. Make sure the add a "d" infront of the actual device id. Now we can call the modifyCloudToDeviceConfig()
function to send the message contains inside the constant variable "data", to the device.
Now let's test the implemented functions with the device. I have connected a table fan to the AC section to demonstrate the function of the cooling element. The temperature reading is 33 Celcius at the neutral state.
1. Set the target temperature to 20C. The device should turn on the fan.
2. Set the target temperature to 40C. The device should turn off the fan.
3. Heat the board to > 40C. The device should turn on the fan.
4. Let the board cooldown. The device should turn off the fan.
A quick explanation about dashboard functionsThe front end is built using React JS. It is not expected to deeply explain the front end components here. Following is a quick guide for some of the important functions of the front end.
1. Backend functions are exposed to the dashboard environment through src\utils\API.js This is where all the front end APIs are implemented
2. In src\store\actions\dashboard.js these pre-defined APIs are used to serve the functionalities of the dashboard
3. The first screen that a user gets in contact with is the Login screen. All the implementations of the methods can be found in src\containers\auth\Auth.js
4. The login authorization process is handled in src\store\actions\auth.js
5. After a successful login, the user then directed to the dashboard. This dashboard has three main parts. Widget structure and the method implementations of the following part can be found in the below files.
a. Add new Device - src\containers\RegisterDevices.js
b. Show device list - src\containers\DeviceSwitch.js
c. Manage and monitoring devices - src\containers\ManageDevices.js
6. The customized widget components that are used in this dashboard can be found in the below location: src\components\UI
7. The Layout of the Dashboard App is defined and implemented in files in src\hoc\Layout & src\containers\AppWrapper.js
8. Basic configuration of the dashboard is in src\shared\config.js
There are only 3 backend functions implemented with Node JS.
1. dashboard - Retrieve data from db and pass to the front end.
2. deleteDevice - delete the selected device from db
3. toggleDeviceStatus - Creating device records for new devices and update temperature of existing devices
Hosting the dashboard backendFirst clone the backend repo to the local storage and open using a Coding IDE.
Then navigate into the functions folder and run $npm i
to install all the required packages. After this step, we need to add keys and configurations to the project.
1. Generating the service account key - In the firebase project go to the Settings > Service Accounts and click on "Generate new private key". A key file will be downloaded to your local machine.
2. Copy the firebase Realtime database URL
Open the config.js file and replace the db URL at the "databaseUrl" field.
{
"serviceAccount": {
"type": ,
"project_id": ,
"private_key_id": ,
"private_key": ,
"client_email":,
"client_id": ,
"auth_uri": ,
"token_uri": ,
"auth_provider_x509_cert_url": ,
"client_x509_cert_url":
},
"databaseUrl": "xxxx"
}
Open the downloaded private key file with a text editor and replace the "serviceAccount" field with the content.
Run $firebase login
complete the required login process.
Run $firebase init
, go to functions, press space and then Enter. Select "Existing Projects" and select the correct project. Then select Javascript. For the Y/N questions related to replacing files enter "No". Enter "Yes" for installing dependencies.
Finally run $firebase deploy
. This will take a moment and end after generating API endpoints to each function of the backend service. Save any of these API endpoints to use when setting up the front end.
If you go to the "Cloud Functions" in the google cloud console, you can notice that all functions are up and running on the cloud.
Clone the repo and open with the IDE.
Run $npm i
in the root directory to install node packages. Go to medical cooler box\front end\node_modules\google-maps-react\dist\GoogleApiComponent.js and add this line after line 224.
{style: {height: '100%', width:'100%'}},
Then we need to set the environment variables. Create a new file in the root directory and name it as ".env" (make sure to add the dot before env). Then place the following variables inside the env file.
REACT_APP_GOOGLE_MAPS_API_KEY = ""
REACT_APP_FIREBASE_API_KEY = ""
REACT_APP_FIREBASE_PROJECT_ID = ""
REACT_APP_FIREBASE_APP_ID = ""
REACT_APP_SERVER_API = "https://xxxxxxxx.cloudfunctions.net"
The server API can be made from the API endpoints that are generated when deploying the backend. Replace the first part before the ".cloudfunctions.net" in the REACT_APP_SERVER_API. To get the rest of the keys, we need to register a new app. Go to Settings > General and Select </> icon under "Your apps". This will create a new app inside firebase. Add a nickname to the app and enable Firebase Hosting. Add a domain name to host the project. If you type "my-test-app", the dashboard will be hosted in my-test-app.web.app. Then register the app and complete the next 3 steps. In the "Deploy to Firebase Hosting" step, you may find a json script that needs to be placed inside the firebase.json file. Save that to use in a future step.
Now go to the Settings of the firebase project and select the "General" tab. You may find a section called "Firebase SDK snippet" under "Your apps".
From here you can find the FIREBASE_API_KEY, FIREBASE_PROJECT_ID and FIREBASE_APP_ID by selecting CDN or Config radio buttons.
To generate the API key for google maps, go to the APIs and Services > Credentials in the google cloud console. Click on "CREATE CREDENTIALS" to generate a new API key and place the key in the env file.
Now the project is ready to launch. Make sure to enable "Google Maps API" in the google cloud console. Select "Maps JavaScript API" and enable.
Run $npm run build
and $firebase init
.
Select "host" > use an existing project. Use the following answer for the next popup questions.
- What do you want to use as your public directory? build
- Configure as a single-page app (rewrite all urls to /index.html)? Yes
- Set up automatic builds and deploys with GitHub? No
- File build/index.html already exists. Overwrite? No
The initialization script will create .firebaserc and firebase.json files in the root directory. Place the previously saved json script inside the firebase.json file.
Run $firebase deploy
.
Visit the <chosen domain name>.web.app to check the hosted dashboard.
Adding Users to the DashboardTo register a new user to login via the dashboard, we need to add credentials from the Firebase. Go to the Authentication tab in Firebase and click "Get Started". Select "Email/Password" and enable the service. Then you can add login emails and passwords by adding users.
The AVR-IOT board comes with the WINC1510 WiFi module. This module has an 8MB flash memory. The next step is to implement a data logging system to store GPS locations and temperature values inside the flash memory when an active WiFi connection is not available. The board has provided SMD pads giving access to this module.
Comments