Created
February 17, 2012 10:14
-
-
Save helxsz/1852444 to your computer and use it in GitHub Desktop.
Currentcost to Pachube for Nanode
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
// 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