Skip to content

Instantly share code, notes, and snippets.

@helxsz
Created February 17, 2012 10:14
Show Gist options
  • Save helxsz/1852444 to your computer and use it in GitHub Desktop.
Save helxsz/1852444 to your computer and use it in GitHub Desktop.
Currentcost to Pachube for Nanode
// Home monitoring CurrentCost monitors such as CC128 and EnviR
//
// Version specifically for Nanode, www.nanode.eu
// I am using with Arduino/Freeduino and EtherShield (ENC28J60 based)
// CC128 connected to pin 0 - UART Serial in and ground
//
// If using regular Arduino or clode with FTDI chip:
// PCB track between FTTDI chip and R11 is cut to disable USB/SERIAL.
// Just short gap to re-program
//
// all other pins currently unused
// Ethernet accepts ping only, no other requests implemented.
//
// Original code by Written Dec 2009 - Jan 2010, John Crouchley
// Updates for Ethernet by Andrew Lindsay Jan 2010
// Conversion to Ethercard and more improvements, DEC 2011
//#define DEBUG
//#define USE_LCD
#include <TinyXML.h>
#include <EtherCard.h>
#include <NanodeMAC.h>
#ifdef USE_LCD
#include <nokia_3310_lcd.h>
#endif
// Rx/Tx Enable pins
#define RXEN 4
#define TXEN 6
#define USE_RGB
#ifdef USE_RGB
// Define where the RGB LED is connected - this is a common cathode LED.
#define BLUEPIN 9 // Blue LED, connected to digital pin 3
#define REDPIN 5 // Red LED, connected to digital pin 5
#define GREENPIN 3 // Green LED, connected to digital pin 6
// If using a Common Anode RGB LED (i.e. has connection to +5V
// Then leave this uncommented this
#define COMMON_ANODE
// If using Common Cathode RGB LED (i.e. has common connection to GND)
// then comment out the above line or change to:
//#undef COMMON_ANODE
#ifdef COMMON_ANODE
#define LOW_LIMIT 255
#define HIGH_LIMIT 0
#else
#define LOW_LIMIT 0
#define HIGH_LIMIT 255
#endif
#endif
// CC128 XML status
#define XMLSTATUS_NULL 0x00 // waiting ...
#define XMLSTATUS_MSG 0x01 // got <msg>
#define XMLSTATUS_TIM 0x02 // got <msg><time>value
#define XMLSTATUS_TMP 0x03 // got <msg><tmpr>value
#define XMLSTATUS_SNSR 0x04 // got <msg><sensor>value
#define XMLSTATUS_WATT 0x05 // got <msg><ch1><watts>value
#define XMLSTATUS_DAYSNSR 0x06 // got <msg><hist><data><sensor>value
#define XMLSTATUS_DAYWATT 0x07 // got <msg><hist><data><d0001>value
static uint8_t mymac[6] = { 0,0,0,0,0,0 };
static uint8_t myip[4] = { 0, 0, 0, 0};
static uint8_t mynetmask[4] = { 0,0,0,0 };
// Default gateway, dns server and dhcp server.
// These are found using DHCP
static uint8_t gwip[4] = { 0,0,0,0 };
static uint8_t dnsip[4] = { 0,0,0,0 };
static uint8_t dhcpsvrip[4] = { 0,0,0,0 };
// ** pachube.com setup **
// IP address of the www.pachube.com server to contact (IP of the first portion of the URL):
static uint8_t pachubeip[4] = { 0,0,0,0};
// The name of the virtual host which you want to contact at pachubeip (hostname of the first portion of the URL):
#define PACHUBE_VHOST "api.pachube.com"
// setup yor feed url and API keys here.
//#define WEBSERVER_VHOST "www.pachube.com"
#define PACHUBEAPIKEY "X-PachubeApiKey: --------------your api key---------------"
#define PACHUBEAPIURL "/v2/feeds/NNNNNN.csv?_method=put"
// Put data: /v2/feeds/<feed_id>
/*
1,123\n
2,456\n
<stream_id>,<value>
*/
static uint8_t resend=0;
//static int8_t dns_state=DNS_STATE_INIT;
// End of configuration
// listen port for tcp/www:
#define MYWWWPORT 80
static volatile uint8_t start_web_client=0; // 0=off but enabled, 1=send update, 2=sending initiated, 3=update was sent OK, 4=diable updates
// Reading types
#define READING 0
#define PULSECOUNT 1
// Define the data structure for each channel - 11 in all,
// 0-9 for CC channels, 10 for temperature
struct CCData {
uint32_t lastReading; // timestamp for last reading
uint32_t wattsTotal; //watts data
uint16_t wattsAvg;
uint8_t sampleCount;
uint8_t pachubeStreamId;
uint8_t divFactor; // 1 for normal, 10 for temperature readings
uint8_t type; // Reading or pulse count
};
CCData ccData[11] = {
{ 0L, 0L, 0, 0, 0, 1, READING },
{ 0L, 0L, 0, 0, 1, 1, PULSECOUNT },
{ 0L, 0L, 0, 0, 2, 1, READING },
{ 0L, 0L, 0, 0, 3, 1, READING },
{ 0L, 0L, 0, 0, 4, 1, READING },
{ 0L, 0L, 0, 0, 5, 1, READING },
{ 0L, 0L, 0, 0, -1, 1, READING },
{ 0L, 0L, 0, 0, -1, 1, READING },
{ 0L, 0L, 0, 0, -1, 1, READING },
{ 0L, 0L, 0, 0, -1, 1, READING },
{ 0L, 0L, 0, 0, 10, 10, READING }
};
//const char iphdr[] PROGMEM ={
// 0x45,0,0,0x82,0,0,0x40,0,0x20}; // 0x82 is the total
// Temperature is in slot 10
uint32_t wattsTotal[11];
uint8_t wattsCount[11];
uint16_t wattsAvg[11];
uint8_t pachubeStreamId[11] = { 0,1,2,3,4,5,-1,-1,-1,-1,10};
uint16_t errorCount; // count of errors in the XML.
uint16_t timeoutCount; // count of all protocol lockups
uint32_t time;
// TODO - Keep track of last time reading recieved and check to make sure
// We arent sending same reading over and over again
#define NUMBER_SAMPLES 4
#define BUFFER_SIZE 500
byte Ethernet::buffer[BUFFER_SIZE];
NanodeMAC mac( mymac );
#ifdef USE_LCD
Nokia_3310_lcd lcd=Nokia_3310_lcd();
#endif
// XML processing variables
TinyXML xml;
uint8_t buffer[100];
uint8_t XMLstatus;
char XMLtime[9];
char XMLtmpr[5];
char XMLsensor;
char XMLwatts[6];
// Used by timeout code - used to detect a protocol lockup
// (e.g. we didn't sucessfully receive an end tag and so never see the end of a message)
// can happen when other CPU demands cause Serial buffer overflow.
// assumption here is that we should receive three msgs every 6 seconds
// - so if x seconds have elapsed after the last message then TinyXML needs a reset
#define XML_TIMEOUT 15000 // 15 seconds
uint32_t timeout_millis; // holds the time of the last received msg
uint32_t lastUpdate; // When we last send something
#ifdef USE_RGB
int currentRed = 0;
int currentGreen = 0;
int currentBlue = 0;
// start of code
void setRGBWatts( int watts) {
int r,g,b;
#ifdef COMMON_ANODE
r = 255;
g = 255;
b = 255;
#else
r = 0;
g = 0;
b = 0;
#endif
if( watts > 700 )
r = map( watts, 700,2000, LOW_LIMIT, HIGH_LIMIT);
if( watts <1200 )
g = map( watts, 200,1200, LOW_LIMIT, HIGH_LIMIT);
fadeToColour( REDPIN, currentRed, r );
fadeToColour( GREENPIN, currentGreen, g );
fadeToColour( BLUEPIN, currentBlue, b );
currentRed = r;
currentGreen = g;
currentBlue = b;
}
// Fade a single colour
void fadeToColour( int pin, int fromValue, int toValue ) {
int increment = (fromValue > toValue ? -1 : 1 );
int startValue = (fromValue > toValue ? : 1 );
if( fromValue == toValue )
return; // Nothing to do!
if( fromValue > toValue ) {
// Fade down
for( int i = fromValue; i >= toValue; i += increment ) {
analogWrite( pin, i );
delay(10);
}
} else {
// Fade up
for( int i = fromValue; i <= toValue; i += increment ) {
analogWrite( pin, i );
delay(10);
}
}
}
//function holds RGB values for time t milliseconds, mainly for demo
void solid(int r, int g, int b, int t)
{
//map values
r = map(r, 0, 255, LOW_LIMIT, HIGH_LIMIT);
g = map(g, 0, 255, LOW_LIMIT, HIGH_LIMIT);
b = map(b, 0, 255, LOW_LIMIT, HIGH_LIMIT);
//output
analogWrite(REDPIN,r);
analogWrite(GREENPIN,g);
analogWrite(BLUEPIN,b);
currentRed = r;
currentGreen = g;
currentBlue = b;
//hold at this colour set for t ms
if( delay > 0 )
delay(t);
}
#endif
void setup()
{
// Set Nanode Tx & Rx Enable
pinMode( RXEN, OUTPUT );
pinMode( TXEN, OUTPUT );
digitalWrite( RXEN, LOW );
digitalWrite( TXEN, HIGH );
#ifdef USE_LCD
lcd.init();
lcd.clear();
lcd.gotoXY( 0, 0 );
lcd.print( "CCEth V0.9" );
#endif
Serial.begin(57600);
#ifdef DEBUG
Serial.println("CurrentCost to Pachube for Nanode");
#endif
if (ether.begin(sizeof Ethernet::buffer, mymac) == 0) {
#ifdef DEBUG
Serial.println( "Failed to access Ethernet controller");
#endif
while(1);
}
#ifdef USE_RGB
// Set RGB Pins
pinMode(REDPIN, OUTPUT); // sets the pins as output
pinMode(GREENPIN, OUTPUT);
pinMode(BLUEPIN, OUTPUT);
// Set the RGB LEDs off
solid(255, 0, 0, 500 );
solid(0, 255, 0, 500 );
solid(0, 0, 255, 500 );
solid(0, 0, 0, 0 );
#endif
memset(wattsTotal, 0, 44);
memset(wattsCount, 0, 11 );
memset(wattsAvg, 0, 22 );
// temperature = 0;
errorCount = 0; // count of errors in the XML.
timeoutCount = 0;
time = 0L;
xml.init((uint8_t*)&buffer,sizeof(buffer),&XML_callback);
lastUpdate = millis();
#ifdef USE_LCD
pinMode( LCD_BL, OUTPUT );
digitalWrite( LCD_BL, LOW );
#endif
}
#ifdef DEBUG
// Output a ip address from buffer from startByte
void printIP( uint8_t *buf ) {
for( int i = 0; i < 4; i++ ) {
Serial.print( buf[i], DEC );
if( i<3 )
Serial.print( "." );
}
}
#endif
void loop()
{
int ch;
uint16_t dat_p;
// Do network init stuff, get IP address and DNS name
static uint32_t timetosend;
int sec = 0;
long lastDnsRequest = 0L;
int plen = 0;
long lastDhcpRequest = millis();
uint8_t dhcpState = 0;
boolean gotIp = false;
// Get IP Address details
if (!ether.dhcpSetup()) {
#ifdef DEBUG
Serial.println( "DHCP failed");
#endif
while(1);
} else {
#ifdef DEBUG
ether.printIp("IP: ", ether.myip);
ether.printIp("GW: ", ether.gwip);
ether.printIp("DNS: ", ether.dnsip);
#endif
// Perform DNS Lookup for host name
Serial.println("resolving hostname");
if (!ether.dnsLookup(PSTR( PACHUBE_VHOST ) )) {
#ifdef DEBUG
Serial.println("DNS failed");
#endif
while(1);
}
}
// Main processing loop now we have our addresses
while(1) {
// If it changes then it drops out and forces a renewal of details
// Read serial
while (Serial.available()) {
ch = Serial.read();
xml.processChar((char)(ch & 0xff));
}
dat_p = ether.packetLoop(ether.packetReceive());
//timeout code
if (millis()-timeout_millis > XML_TIMEOUT)
{
xml.reset();
timeout_millis=millis(); // don't re-trigger for another XML_TIMEOUT
}
if( millis() - lastUpdate > 60000 ) {
// Send what we have
#ifdef DEBUG
Serial.println("Send Update");
#endif
// Send pachube update once a minute
sendPachubeUpdate();
lastUpdate = millis();
}
}
}
// CC128 call back events
void XML_callback( uint8_t statusflags, char* tagName, uint16_t tagNameLen, char* data, uint16_t dataLen )
{
if (statusflags & STATUS_ERROR)
{
XMLstatus = XMLSTATUS_NULL;
errorCount++;
}
else if ((statusflags & STATUS_END_TAG) && (tagNameLen == 4) && (XMLstatus!=XMLSTATUS_WATT))
{
if ( strcmp(tagName,"/msg") == 0 )
{
//we have a message that didn't have all the data (e.g. history)
XMLstatus = XMLSTATUS_NULL; // just set to null status
}
}
switch (XMLstatus)
{
case XMLSTATUS_NULL:
if (statusflags & STATUS_START_TAG)
{
if ( tagNameLen )
{
if ( strcmp(tagName,"/msg") == 0 ) {
XMLstatus = XMLSTATUS_MSG;
}
}
}
break;
case XMLSTATUS_MSG:
if (statusflags & STATUS_TAG_TEXT)
{
if ( strcmp(tagName,"/msg/time") == 0 )
{
strncpy(XMLtime,data,8);
XMLtime[8]=0;
XMLstatus = XMLSTATUS_TIM;
}
}
break;
case XMLSTATUS_TIM:
if (statusflags & STATUS_TAG_TEXT)
{
if ( strcmp(tagName,"/msg/tmpr") == 0 )
{
strncpy(XMLtmpr,data,4);
XMLtmpr[4]=0;
XMLstatus = XMLSTATUS_TMP;
}
}
break;
case XMLSTATUS_TMP:
if (statusflags & STATUS_TAG_TEXT)
{
if ( strcmp(tagName,"/msg/sensor") == 0 )
{
XMLsensor = data[0];
XMLstatus = XMLSTATUS_SNSR;
}
}
break;
case XMLSTATUS_SNSR:
if (statusflags & STATUS_TAG_TEXT)
{
if ( strcmp(tagName,"/msg/ch1/watts") == 0 )
{
strncpy(XMLwatts,data,5);
XMLwatts[5]=0;
XMLstatus = XMLSTATUS_WATT;
}
}
break;
case XMLSTATUS_WATT:
if (statusflags & STATUS_END_TAG)
{
if ( strcmp(tagName,"/msg") == 0 )
{
XMLstatus = XMLSTATUS_NULL;
processMsg();
}
}
break;
}
}
//
// got a CC128 <msg> so process it
//
void processMsg()
{
timeout_millis = millis(); // reset timeout
time = ((uint32_t)atoi(&XMLtime[0])*60+(uint32_t)atoi(&XMLtime[3]))*60+(uint32_t)atoi(&XMLtime[6]);
wattsAvg[10] = uint16_t(atof(&XMLtmpr[0])*10); // Temperature
// Need to determine if history or not
uint16_t tmpConv = atoi(&XMLwatts[0]);
uint8_t sensorNum = XMLsensor - '0';
wattsTotal[sensorNum] += tmpConv;
wattsCount[sensorNum]++;
#ifdef DEBUG
Serial.print( "Sensor " );
Serial.print( sensorNum, DEC );
Serial.print( " value " );
Serial.println( tmpConv, DEC );
#endif
// Create average reading
if( wattsCount[sensorNum] > NUMBER_SAMPLES ) {
// TODO: Deal with pulse counting
wattsAvg[sensorNum] = wattsTotal[sensorNum] / wattsCount[sensorNum];
wattsCount[sensorNum] = 0;
wattsTotal[sensorNum] = 0;
#ifdef DEBUG
Serial.print("Calc Average: Sensor ");
Serial.print( sensorNum, DEC );
Serial.print( " value " );
Serial.println( wattsAvg[sensorNum], DEC );
#endif
}
#ifdef USE_RGB
if( sensorNum == 0 ) {
setRGBWatts(tmpConv);
}
#endif
#ifdef USE_LCD
switch(sensorNum) {
case '0': // Electricity
lcd.gotoXY( 0, 1 );
lcd.print("CH0: ");
case '3': //
lcd.gotoXY( 0, 2 );
lcd.print("CH3: ");
break;
case '4': // fridge
lcd.gotoXY( 0, 3 );
lcd.print("CH4: ");
break;
case '5': //
lcd.gotoXY( 0, 4 );
lcd.print("CH5: ");
break;
default:
break;
}
switch(sensorNum) {
case '0': // Electricity
case '3': //
case '4': // fridge
case '5': //
lcd.print( &XMLwatts[0] );
lcd.print( "W" );
break;
default:
break;
}
#endif
}
// Browser callback, where we get to after receiving a reply to an update, should really
// do somthing here to check all was OK.
void browserresult_callback(uint8_t statuscode,uint16_t datapos, uint16_t dlen){
// TODO: What to do on response? Check its OK
#ifdef DEBUG
Serial.print("callback pos: ");
Serial.print(datapos,DEC);
Serial.print(" status: ");
Serial.print(statuscode,DEC);
Serial.print(" Data: ");
Serial.println((char *)&(Ethernet::buffer[datapos]));
#endif
// clear pending state at sucessful contact with the
// web server even if account is expired:
// if (start_web_client==2) start_web_client=3;
}
// Send pachube update as follows:
// 0 = temperature
// 1 = ch 0
// 2 = ch 1 etc
void sendPachubeUpdate( void ) {
uint16_t dat_p;
int8_t cmd;
int plen = 0;
int sec = 0;
// global string buffer for twitter message:
char statusstr[150];
char *statusPtr = statusstr;
char numBuf[10];
for( int i=0; i<11; i++ ) {
if( pachubeStreamId[i] != 0xff ) {
// Serial.print( pachubeStreamId[i], DEC );
// Serial.print( "," );
// Serial.println( wattsAvg[i], DEC );
itoa( i, numBuf, 10 );
strcpy(statusPtr, numBuf );
statusPtr += strlen( numBuf );
*statusPtr++ = ',';
if( i == 10 )
itoa( wattsAvg[i]/10, numBuf, 10 );
else
itoa( wattsAvg[i], numBuf, 10 );
strcpy(statusPtr, numBuf );
statusPtr += strlen( numBuf );
*statusPtr++ = '\n';
}
}
*statusPtr++ = '\0';
#ifdef DEBUG
Serial.print("Pachube update ");
Serial.println( statusstr );
#endif
ether.httpPost( PSTR(PACHUBEAPIURL),PSTR(PACHUBE_VHOST),PSTR(PACHUBEAPIKEY), statusstr ,&browserresult_callback);
start_web_client=2;
}
// End
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment