Products for USB Sensing and Control Canada flag
Products for USB Sensing and Control

sales inquiries

quotes, distributor information, purchase orders
sales@phidgets.com

technical inquiries

support, advice, warranty, returns, misshipment
support@phidgets.com

website inquiries

corrections or suggestions
web@phidgets.com

Address

Unit 1 - 6115 4 St SE
Calgary AB  T2H 2H9
Canada

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

Create a dictionary like the one shown above.

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

Preview of getting a text from our C program running on the 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);
	});

	}
}

Basic web page written with Phidgets JavaScript library

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.

Installed system!


Webpage working!

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.

Enclosure update