Pet Food Supply Monitor Using Load Cells and Phidgets - Part 1
by Lucas
Source Code
Introduction
My sister recently asked me to watch her cat while she is on vacation for the next few weeks. I would like to minimize the number of trips to her house, so I decided to create a
pet monitoring system using Phidgets. The goal of this project will be to create a system that will send me a text message when the cat's food or water supply is getting low. There should also
be a web interface that will allow me to view data in real time, as well as modify any settings.
Part 1 will cover monitoring the water and food supply. In part 2 we will delve into logging the animals weight.
Hardware
Software
Libraries and Drivers
This project assumes that you are somewhat familiar with the basic operation of Phidgets (i.e. attaching and opening devices, reading data, etc). If this is your first time building a Phidgets project with JavaScript or a Phidget SBC, please refer to the JavaScript page or SBC page for instructions on how to setup your environment and get started.
Overview
A simple C program will run on the Phidget SBC and perform the following tasks:
- Create two VoltageRatioInput objects and map them to the PhidgetBridge 4-Input.
- Connect to a PhidgetDictionary. Update two key-value pairs in the dictionary with information about the current state of the food/water bowls. Also read the current phone numbers that are stored on the dictionary.
- Monitor weight of food/water supply. Send a text message via SSMTP if supply gets too low.
The web page will perform the following tasks:
- Connect to a PhidgetDictionary and display food/water supply and current phone numbers.
- Update PhidgetDictionary when user inputs a new phone number.
If you would like to learn more about SSMTP and the Phidget SBC, check out this project
Step 1: Server Configuration
The server configuration was basic, and added only a single dictionary channel. To create a new dictionary, open the SBC Web Interface and navigate to:
Phidgets > phidget22NetworkServer > Phidget Dictionaries
This dictionary will store some key-value pairs that will be used throughout the program. These are described below:
- waterSupply - the C program will update this value with the current water level. The value will be shown to users via a web page
- foodSupply - the C program will update this value with the current food level. The value will be shown to users via a web page
- phonenumbercount - This value will be updated via the web page when the user adds a new phone number.
- phonenumberX - We will being sending SMS messages using the email to SMS function that most cellphone providers support. The phone number will be stored in the following format: phoneNumber@carrier.specific.domain
Step 2: Create simple C program on Phidget SBC
Note: you should create a project on the SBC that looks like this:
After some minor changes to petmonitor.c (serial numbers, email information for SSMTP, calibration values for scale), you can compile the program like so:
Logging and remote connections
As always, we want to create a log file that will let us know what is happening in our project. We write this line at the top of our main loop:
PhidgetLog_enable(PHIDGET_LOG_INFO, "/path/to/log/file");
We also need to connect to the PhidgetDictionary that we defined. This will be a remote connection so we need to add the following line at the top of our main loop:
PhidgetNet_enableServerDiscovery(PHIDGETSERVER_DEVICE);
Interface with load cells
The PhidgetBridge 4-Input will connect to the load cells and allow us to measure the weight of the food and water bowls. In order for our C program to interface with the PhidgetBridge 4-Input, we must create two PhidgetVoltageRatioInput channels. One for food, one for water. Below is the code to create a water PhidgetVoltageRatioInput object.
#define BRIDGE_SERIALNUM 141009
#define WATER_BRIDGE_CHANNEL 0
...
PhidgetVoltageRatioInputHandle water;
PhidgetReturnCode result;
...
//This PhidgetVoltageRatio object will monitor the weight of the water bowls
result = PhidgetVoltageRatioInput_create(&water);
if (result != EPHIDGET_OK) {
Phidget_log(PHIDGET_LOG_ERROR, "failed to create voltage ratio object");
return 1;
}
result = Phidget_setDeviceSerialNumber((PhidgetHandle)water, BRIDGE_SERIALNUM);
if (result != EPHIDGET_OK) {
Phidget_log(PHIDGET_LOG_ERROR, "failed to set device serial number");
return 1;
}
result = Phidget_setChannel((PhidgetHandle)water, WATER_BRIDGE_CHANNEL);
if (result != EPHIDGET_OK) {
Phidget_log(PHIDGET_LOG_ERROR, "failed to set channel");
return 1;
}
result = Phidget_openWaitForAttachment((PhidgetHandle)water, 2000);
if (result != EPHIDGET_OK) {
Phidget_log(PHIDGET_LOG_ERROR, "failed to open water channel");
return 1;
}
result = PhidgetVoltageRatioInput_setBridgeEnabled(water, 1);
if (result != EPHIDGET_OK) {
Phidget_log(PHIDGET_LOG_ERROR, "failed to enable bridge");
return 1;
}
result = PhidgetVoltageRatioInput_setBridgeGain(water, BRIDGE_GAIN_1);
if (result != EPHIDGET_OK) {
PhidgetLog_log(PHIDGET_LOG_ERROR, "failed to enable bridge");
return 1;
}
Note: All code is available in petmonitor.c
Create and initialize a PhidgetDictionary
We want to be able to access the PhidgetDictionary that is running on our server, so we need to create and configure a PhidgetDictionary in our C program.
Note the use of PhidgetDictionary_setOnUpdateHandler(dict, onDictionaryUpdate, NULL)
This will allow our program to be notified when the user changes a setting via the web page.
#define DICTIONARY_SERIALNUM 54321
...
PhidgetDictionaryHandle dict;
PhidgetReturnCode result;
...
//create dictionary
result = PhidgetDictionary_create(&dict);
if (result != EPHIDGET_OK) {
Phidget_log(PHIDGET_LOG_ERROR, "failed to create dictionary object");
return 1;
}
result = Phidget_setDeviceSerialNumber((PhidgetHandle)dict, DICTIONARY_SERIALNUM);
if (result != EPHIDGET_OK) {
Phidget_log(PHIDGET_LOG_ERROR, "failed to set device serial number");
return 1;
}
result = PhidgetDictionary_setOnUpdateHandler(dict, onDictionaryUpdate, NULL);
if (result != EPHIDGET_OK) {
Phidget_log(PHIDGET_LOG_ERROR, "failed to set dictionary update handler\n");
return 1;
}
result = Phidget_openWaitForAttachment((PhidgetHandle)dict, 2000);
if (result != EPHIDGET_OK) {
Phidget_log(PHIDGET_LOG_ERROR, "failed to open dictionary channel");
return 1;
}
Calibrate load cell data
I would like each scale to return a value between 0-1. This will make it easy to calculate a "percent full" value that I can show the user via the web page and also use in my main loop. In order to do this, I must calibrate the data coming back from the PhidgetBridge 4-Input. Check out this article for more information.
double m[2] = {value, value};
double b[2] = {value , value};
...
double getVoltageRatio(PhidgetVoltageRatioInputHandle ch, int channel) {
double value;
PhidgetVoltageRatioInput_getVoltageRatio(ch, &value);
return m[channel]*value + b[channel];
}
Main loop
Here is a breakdown of the main loop:
- Update the waterSupply and foodSupply key-value pairs in the dictionary every 5 seconds.
- If dictionary has been updated, update our
phonenumberinfo
struct with new information. - Check to make sure supplies are low for at least 10 minutes before attempting to send a text message. This will prevent us from sending a message if the owner is quickly changing the food or cleaning the bowl.
- If the supplies have been low for 10 minutes, try to send a message. The program will only send a message once every 8 hours.
#define SLEEP_TIME 5
#define DELAY 600/SLEEP_TIME //10 minutes
#define SUPPLIES_LOW 0.05 //5% left
#define HOURS_8 28800
struct Supplies {
double food;
double water;
};
struct PhoneNumberInfo{
int count;
char phonenumbers[5][50];
};
initialMessage = 1; //send text message initially without waiting
dictionaryUpdate = 1;//read dictionary initially
...
//In main function
struct Supplies supplies;
struct PhoneNumberInfo phonenumberinfo;
char value[50];
int wait = 0;
int i;
...
while (1) {
//Update dictionary with current values for water and food (runs every 5 seconds)
supplies.water = getVoltageRatio(water, WATER_BRIDGE_CHANNEL);
supplies.food = getVoltageRatio(food, FOOD_BRIDGE_CHANNEL);
snprintf(value, 50, "%f", supplies.water*100.0);
PhidgetDictionary_set(dict, "waterSupply", value);
snprintf(value, 50, "%f", supplies.food*100.0);
PhidgetDictionary_set(dict, "foodSupply", value);
if (dictionaryUpdate) {
Phidget_log(PHIDGET_LOG_INFO, "dictionary update");
dictionaryUpdate = 0;
int count = 0;
PhidgetDictionary_get(dict, "phonenumbercount", value, 50);
count = strtol(value, NULL, 0);
phonenumberinfo.count = count;
for (i = 0; i < count; i++) {
snprintf(value, 50, "phonenumber%d:number", i);
PhidgetDictionary_get(dict, value, value, 50);
strcpy(phonenumberinfo.phonenumbers[i], value);
}
}
if (supplies.food < SUPPLIES_LOW || supplies.water < SUPPLIES_LOW) {
if (wait++ == DELAY) { //if supplies have been low for DELAY seconds we should try to send a text message
wait = 0;
time_t currentTime;
time(& currentTime);
if(initialMessage || difftime(currentTime,lastTime) > HOURS_8){ //only send text messages once every 8 hours
Phidget_log(PHIDGET_LOG_INFO, "supplies are low, sending text message");
initialMessage = 0;
time(&lastTime);
sendMessage(phonenumberinfo);//send message to all phone numbers currently on list
}
}
}
else
wait = 0;
sleep(SLEEP_TIME);
}
Step 3: Create web page
The web page uses the Phidgets JavaScript library to connect to the dictionary we previously created on the network server.
Our web page will provide a few basic functions:
- Allow user to view the water/food bowl state in real time.
- Allow user to view which phone numbers are currently being notified when the water/food supply is low.
- Allow user to add new phone numbers and remove old phone numbers.
$(document).ready(function () {
var conn = new jPhidgets.Connection('ws://' + window.location.host + '/phidgets', { name: window.location.host });
conn.connect()
.then(runCode);
});
First we need to connect to the Phidget server which can be seen in the code above. Next, the runCode
function will run, which will
connect to the PhidgetDictionary we created earlier. It will add attach/detach handlers for the dictionary, and also build a table for the stored phone numbers.
function runCode() {
ch = new jPhidgets.Dictionary();
ch.setDeviceSerialNumber(54321);
ch.onAttach = attach;
ch.onUpdate = update;
ch.open().then(function() {
//nothing to configure
}).catch(function (err) {
alert("Error!\Could not open Dictionary!");
return;
});
buildtable();
}
The code for the attach handler is shown below. You can see that we grab some initial values for the food/water supply labels. We also go through the phone numbers stored in the PhidgetDictionary and display them.
function attach(ch) {
console.log(ch + ' attached');
j = 0;//used below to keep track dictionary key-value pairs
//get initial values for water/food supply.
ch.get("waterSupply", "key not found").then(function(val){
document.getElementById('waterSupply').innerHTML = val +"%";
});
ch.get("foodSupply", "key not found").then(function(val){
document.getElementById('foodSupply').innerHTML = val + "%";
});
//Get and display current phone numbers
ch.get("phonenumbercount", "key not found").then(function(val){
if(val == "key not found")
count = 0;
else
count = Number(val);
document.getElementById('phonenumbercount').innerHTML = "Current Phone Numbers: " + val + "/5 spots used";
});
for(var i = 0; i < 5; i ++){
ch.get("phonenumber" + i, "This phone number slot is free").then(function(phonenum){
document.getElementById("phonenum" + j++).innerHTML = phonenum; //reference id that buildtable() created (located below)
console.log(phonenum);
});
}
}
You can view the whole html file in the project downloaded located at the top of this page.
Conclusion
I am now able to receive text messages when my sister's cat is running out of food and/or water! Here are some things that I would like to add to this project in order to make it that much better:
- Enclosure for the PhidgetBridge 4-Input - Currently my board is sitting underneath the scale exposed.
- Waterproof enclosure for the Phidget SBC. Currently the electronics are sitting underneath a large plastic container that won't provide much protection if there is a spill. Something like this would work well.
- Access to the web page from anywhere. This project explains how this can be done.
- Implement some averaging on data coming from PhidgetBridge 4-Input. By reading only one sample every
SLEEP_TIME
seconds, our data can move around too much.
The next step will be to incorporate a scale that logs the weight of the cat. The saved data can then be shown on the web page using a graph.
Update
The Pet Monitoring system got an update this weekend when I moved the PhidgetSBC from the plastic container to a new enclosure from Phidgets. I ended up using this enclosure because it let me see the state of the Phidget SBC without having to take it apart. I drilled a hole in the back of the enclsoure and simply fed the cables through in order to access them.