Created
April 3, 2011 22:29
-
-
Save knolleary/900885 to your computer and use it in GitHub Desktop.
CurrentCost MQTT Bridge
This file contains hidden or 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
/* | |
CurrentCost MQTT Bridge | |
Example sketch for the CurrentCost Bridge device that causes | |
readings to be published over MQTT. | |
Requires: | |
- Arduino 0022 | |
- PubSubClient v1.6+ | |
http://knolleary.net/arduino-client-for-mqtt/ | |
- Ethernet DHCP/DNS | |
http://www.mcqn.com/files/Ethernet-DHCP-DNS.zip | |
This must be compiled with the board set to: | |
Arduino Pro or Pro Mini (3.3V, 8MHz) w/ ATmega328 | |
Nicholas O'Leary, 04-2011 | |
http://knolleary.net/?p=1024 | |
This code is in the public domain | |
*/ | |
#include <SPI.h> | |
#include <Ethernet.h> | |
#include <Dhcp.h> | |
#include <dns.h> | |
#include <PubSubClient.h> | |
#include <EEPROM.h> | |
// Use either SERVER_HOST_NAME or SERVER_IP_ADDRESS - not both | |
#define _SERVER_HOST_NAME "example.com" | |
#define SERVER_IP_ADDRESS { 172, 16, 0, 2 } | |
// The port to connect to | |
#define SERVER_PORT 1883 | |
// The template Cliend ID. The blanks are filled with the first | |
// eight bytes of the uuid. | |
#define CLIENTID "cc " | |
// The template topic. The second topic-level is filled with the | |
// uuid of the bridge. The third/fourth levels are populated depending | |
// what is being published | |
#define TOPIC "cc/ / / " | |
// The char position at which the third topic level starts | |
#define TOPIC_ROOT_LENGTH 36 | |
// Should the temperature be published | |
#define PUBLISH_TEMPERATURE | |
// Should birth/will messages be used | |
#define PUBLISH_CONNECTION_STATE | |
// Ethernet reset line attached to pin 7 | |
#define ETHERNET_RESET 7 | |
// Where in EEPROM to start the uuid | |
#define UUID_START_BYTE 4 | |
char hexChars[] = "0123456789ABCDEF"; | |
#define HEX_MSB(v) hexChars[(v & 0xf0) >> 4] | |
#define HEX_LSB(v) hexChars[v & 0x0f] | |
// Struct to store a reading in | |
typedef struct { | |
char sensor; | |
char channel; | |
char value[10]; | |
} Reading; | |
// A single message my contain up to three channels | |
Reading readings[3]; | |
byte readingCount = 0; | |
char powertopic[] = TOPIC; | |
char clientId[] = CLIENTID; | |
byte mac[6]; | |
byte ip[4]; | |
#ifdef SERVER_IP_ADDRESS | |
byte server[] = SERVER_IP_ADDRESS; | |
#else | |
byte server[4]; | |
#endif | |
byte uuidBytes[16]; | |
char uuid[33]; | |
#define STATE_INITIAL 0 | |
#define STATE_IN_MSG 1 | |
byte state = STATE_INITIAL; | |
char temperature[5]; | |
int lastPublishedTemperature; | |
PubSubClient mqttClient(server, SERVER_PORT,cb); | |
// Subscription callback; unused | |
void cb(char* topic, byte* payload,int length) { | |
} | |
// Returns the next byte from the meter | |
// Blocks until there is something to return | |
char get_byte() { | |
int a = -1; | |
while((a = Serial.read()) == -1) {}; | |
return a; | |
} | |
// Handle a reading; causes it to be published | |
// to the approprite topic | |
void handleReading(char sensor, char channel, char* reading) { | |
if (sensor != '\0' && channel != '\0') { | |
char readingLength = 5; | |
if (channel == '0') { | |
readingLength = 10; | |
} | |
boolean validReading = true; | |
for (int i=0;i<readingLength;i++) { | |
validReading = (validReading && isDigit(reading[i])); | |
} | |
if (validReading) { | |
powertopic[TOPIC_ROOT_LENGTH] = sensor; | |
powertopic[TOPIC_ROOT_LENGTH+1] = '/'; | |
powertopic[TOPIC_ROOT_LENGTH+2] = channel; | |
publish(powertopic,reading,readingLength,0); | |
} | |
} | |
} | |
void publish(char* topic, char* value, int len, int retained) { | |
mqttClient.publish(topic,(uint8_t*)value,len,retained); | |
} | |
void setup() { | |
// Read EEPROM to get the uuid | |
byte i; | |
uuid[32] = '\0'; | |
byte b0 = EEPROM.read(0+UUID_START_BYTE); | |
if (b0 == 0xFF) { | |
// Generate a UUID | |
randomSeed(analogRead(5)); | |
for (i=0;i<16;i++) { | |
EEPROM.write(i+UUID_START_BYTE,(byte)random(256)); | |
} | |
} | |
for (i=0;i<16;i++) { | |
byte v = EEPROM.read(i+UUID_START_BYTE); | |
uuid[2*i] = HEX_MSB(v); | |
uuid[(2*i)+1] = HEX_LSB(v); | |
if (i > 9) { | |
mac[i-10] = v; | |
} | |
uuidBytes[i] = v; | |
} | |
// The LSB(it) of the MSB(yte) must be even to be a valid MAC Address | |
if ((mac[0]&1) == 1) { | |
mac[0] ^= 1; | |
} | |
// Fill in the Topic & Client ID templates | |
for (i=0;i<32;i++) { | |
powertopic[3+i] = uuid[i]; | |
if (i<8) { | |
clientId[2+i] = uuid[i]; | |
} | |
} | |
delay(500); | |
Serial.begin(57600); | |
} | |
int resetConnection() { | |
// Reset Ethernet shield in a controlled manner | |
pinMode(ETHERNET_RESET,OUTPUT); | |
digitalWrite(ETHERNET_RESET,LOW); | |
delay(250); | |
digitalWrite(ETHERNET_RESET,HIGH); | |
pinMode(ETHERNET_RESET,INPUT); | |
delay(500); | |
for (int i=0;i<16;i++) { | |
byte v = EEPROM.read(i+UUID_START_BYTE); | |
if (i > 9) { | |
mac[i-10] = v; | |
} | |
} | |
// The LSB(it) of the MSB(yte) must be even to be a valid MAC Address | |
if ((mac[0]&1) == 1) { | |
mac[0] ^= 1; | |
} | |
// Get an IP address - will timeout after 1 minute | |
int attempts = 0; | |
while (Dhcp.beginWithDHCP(mac) != 1) { | |
attempts++; | |
if (attempts == 2) { | |
return 0; | |
} | |
delay(15000); | |
} | |
delay(1000); | |
#ifdef SERVER_HOST_NAME | |
// Look-up server address if set | |
DNSClient dns; | |
Dhcp.getDnsServerIp(server); | |
dns.begin(server); | |
attempts = 0; | |
while (dns.gethostbyname(SERVER_HOST_NAME, server) != 1) { | |
attempts++; | |
if (attempts == 2) { | |
return 0; | |
} | |
delay(15000); | |
} | |
#endif | |
return 1; | |
} | |
void loop() | |
{ | |
// Check we're still connected | |
if (!mqttClient.connected()) { | |
while (resetConnection() != 1) { | |
delay(5000); | |
} | |
#ifdef PUBLISH_CONNECTION_STATE | |
// Setup the WILL message on connect | |
powertopic[TOPIC_ROOT_LENGTH] = 's'; | |
powertopic[TOPIC_ROOT_LENGTH+1] = 0; | |
if (mqttClient.connect(clientId,powertopic,0,1,"0")) { | |
mqttClient.publish(powertopic,(uint8_t*)"1",1,1); | |
} | |
#else | |
mqttClient.connect(clientId); | |
#endif | |
} | |
readingCount = 0; | |
state = STATE_INITIAL; | |
while (state == STATE_INITIAL) { | |
// Scan for </time | |
while (get_byte() != '<') {} | |
if (get_byte() == '/' && get_byte() == 't' && | |
get_byte() == 'i' && get_byte() == 'm' && | |
get_byte() == 'e') { | |
state = STATE_IN_MSG; | |
} | |
mqttClient.loop(); | |
} | |
get_byte(); // '>' | |
get_byte(); // '<' | |
if (get_byte() == 'h') { | |
// skip history messages | |
state = STATE_INITIAL; | |
} | |
else { | |
for (int i=0;i<4;i++) { // 'mpr>' | |
get_byte(); | |
} | |
for (int i=0;i<4;i++) { | |
temperature[i]=get_byte(); | |
} | |
temperature[4] = '\0'; | |
char sensor = 0; | |
while (state == STATE_IN_MSG) { | |
while (get_byte() != '<') {} | |
char c = get_byte(); | |
if (c == 's' && get_byte() == 'e' && get_byte() == 'n' && | |
get_byte() == 's' && get_byte() == 'o' && get_byte() == 'r') { | |
get_byte(); | |
sensor = get_byte(); | |
} | |
else if (c == 'c' && get_byte() == 'h') { | |
memset(&(readings[readingCount]),0,sizeof(Reading)); | |
readings[readingCount].sensor = sensor; | |
readings[readingCount].channel = get_byte(); | |
for (int i=0;i<8;i++) { // '><watts>' | |
get_byte(); | |
} | |
for (int i=0;i<5;i++) { | |
readings[readingCount].value[i] = get_byte(); | |
} | |
readingCount++; | |
} | |
else if (c == 'i' && get_byte() == 'm' && get_byte() == 'p') { | |
get_byte(); // '>' | |
memset(&(readings[readingCount]),0,sizeof(Reading)); | |
readings[readingCount].sensor = sensor; | |
readings[readingCount].channel = '0'; | |
for (int i=0;i<10;i++) { | |
readings[readingCount].value[i] = get_byte(); | |
} | |
readingCount++; | |
} | |
else if (c == '/' && get_byte() == 'm') { | |
for (int i=0;i<readingCount;i++) { | |
handleReading(readings[i].sensor,readings[i].channel,readings[i].value); | |
} | |
readingCount = 0; | |
#ifdef PUBLISH_TEMPERATURE | |
if (isDigit(temperature[0]) && | |
isDigit(temperature[1]) && | |
temperature[2]=='.' && | |
isDigit(temperature[3])) { | |
int temperatureValue = atoi(temperature)*10+atoi(&(temperature[3])); | |
if (abs(temperatureValue-lastPublishedTemperature) > 4) { | |
powertopic[TOPIC_ROOT_LENGTH] = 't'; | |
powertopic[TOPIC_ROOT_LENGTH+1] = 0; | |
publish(powertopic,temperature,4,1); | |
lastPublishedTemperature = temperatureValue; | |
} | |
} | |
#endif | |
state = STATE_INITIAL; | |
} | |
} | |
} | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
To revise an old topic, I just reprogrammed a Current Cost Bridge using Arduino 0023 and a modified copy of the original sketch. Now it publishes to topic emon/cc/x where x it the sensor number or "t" for temperature. I chose this topic because it works with Open Energy Monitor which is only subscribed to a single base topic. I have only one bridge so I've traded the unique ID for a more useful topic. I left the original code commented out so it should be obvious to anyone how to revert it to Nicks original. Arduino 0023 spits an error at startup saying The library "pubsubclient-2.6" cannot be used, but despite this it DOES compile, and the file you want ends in .cpp.hex and appears in a folder of the Windows Temp Directory. I programmed the Bridge using a USBASP programmer, and the jumper next to the button on the board needs shorting before pressing the button to enter programming mode. Further notes are at the start of the sketch. I'm not a programmer so there are likely more elegant ways of getting this working. I can also provide versions of the libraries used if anyone can tell me how to get to them in Arduino 0023 as it doesn't have a library manager.
Good luck to anyone following on