Flexible Serial to Ethernet for less than 20 Euro

Nano v3 with Ethernet Shield

Wired connections have always been my favorite for connecting hardware to my Home Automation System. This week I found a new way of doing that – costing only 17 Euros, purchased at AliExpress.

What you see in the image above is a ENC28J60 based Ethernet shield with a Arduino Nano 3 on top of it. Measuring about 70 x 20 x 37 mm (with the lower pins cut to half their length). Very small and programmable 😉

Last Friday 2 sets (of shield & Nano) arrived and I just couldn’t resist giving them a try and since I’ve still got a Raspberry Pi near the smart meter for the sole purpose of collecting the data it produces, it looked like a good idea to see if I could replace the RPi with this Ethernet-Nano.

Finding a library for the ENC28J60 based shield wasn’t hard – I had already worked with Jean-Claude Wipplers Ethercard before and which is still in use as our doorbell controller, so that wouldn’t be any problem. But first I had to solve the problem I had when I connected the Nano to my PC- ‘Device enumeration failed’ is what USBView told me. It took a while before I got the idea to place a powered USB hub in between the two…. problem solved.

This Ethernet shield uses pin 10 for CS (Clock Select) but this was easy to change in the code; instead of

if (ether.begin(sizeof Ethernet::buffer, mymac) == 0)

all I had to do was supply an extra parameter and it all worked instantly:

if (ether.begin(sizeof Ethernet::buffer, mymac, 10) == 0) 

There’s one small problem I still have to fix, which is doing a DNS query on the Nano. It times out and I have no clue why; for now I added a ‘fallback’ IP address in the code until it’s fixed.

EthernetNano test setupI used a 2nd Arduino to play the smart meter ‘role’ by sending a P1 datagram to the Nano over a serial connection with an interval of 10 seconds – that would be as close to reality as it could get. I connected the TX pin of the Arduino Ethernet (the bottom one)  to the RX pin on the EthernetNano and all that was left to do was writing a sketch.

Sometimes it looks like everything has been developed before already, the only thing you have to do is find it or remembering where you saw it – well, that was the case here also. With the Ethercard library come a couple of examples which I could use for the Ethernet part of the sketch. And I knew that Jean-Claude Wippler had blogged about P1 data a couple of times – strip the RF12 code and replace it with Ethernet code and I would be ready to go… and that’s exactly what I did 😉

Here’s the code for my first EthernetNano handling the smart meter data and uploading it (with HTTP) to my HA system! Bye bye RPi…

/// @dir p1scanner
/// Parse P1 data from smart meter and send as compressed packet over RF12.
/// @see http://jeelabs.org/2013/01/02/encoding-p1-data/
// 2012-12-31 <jc@wippler.nl> http://opensource.org/licenses/mit-license.php

// Changed to work with Ethernet shield, 2014-06 Robert Hekkers

#include <SoftwareSerial.h>
#include <EtherCard.h>

#define DEBUG 1   // set to 1 to use fake data instead of SoftwareSerial
#define LED   0   // set to 0 to disable LED blinking

SoftwareSerial mySerial (7,17 /*, true*/); // rx, tx, inverted logic
#define NTYPES (sizeof typeMap / sizeof *typeMap)
// list of codes to be sent out (only compares lower byte!)
const byte typeMap [] = {181, 182, 281, 282, 96140, 170, 270, 2410, 2420, 2440};

byte Ethernet::buffer[700];
static byte mymac[] = { 0x74,0x69,0x69,0x2D,0x00,0x01 };
const char website[] PROGMEM = "devbox.hekkers.lan";
uint8_t hisip[] = { 192,168,10,179 };
Stash stash;

byte type;
uint32_t value;
uint32_t readings[NTYPES+1];

static bool p1_scanner (char c) {
  switch (c) {
    case ':':
      type = 0;
      value = 0;
      break;
    case '(':
      if (type == 0)
        type = value; // truncates to lower byte
      value = 0;
    case '.':
      break;
    case ')':
      if (type)
        return true;
      break;
    default:
      if ('0' <= c && c <= '9')
        value = 10 * value + (c - '0');
  }
  return false;
}

static void collectData (bool empty =false) {
  if (!empty) {
    for (byte i = 0; i < NTYPES; ++i) {
      Serial.print("@ ");
      Serial.print(typeMap[i]);
      Serial.print('=');
      Serial.println(readings[i]);
    }
    byte sd = stash.create();
    stash.print("0,");
    stash.println(millis() / 1000);
    for (byte i = 0; i < NTYPES; ++i) {
      stash.print(typeMap[i]);
      stash.print(",");
      stash.println(readings[i]);
    }
    stash.save();
    // generate the header with payload - note that the stash size is used,
    // and that a "stash descriptor" is passed in as argument using "$H"
    Stash::prepare(PSTR("GET http://$F/p1/ HTTP/1.0" "rn"
                        "Host: $F" "rn"
                        "Content-Length: $D" "rn"
                        "rn"
                        "$H"),
            website, website, stash.size(), sd);
    // send the packet - this also releases all stash buffers once done
    ether.tcpSend();
  }
}

void setup () {
  if (DEBUG) {
    Serial.begin(115200);
    Serial.println("n[p1poster]");
  }
  mySerial.begin(9600);
  // digitalWrite(7, 1); // enable pull-up
  collectData(true); // empty packet on power-up

  delay(2000);
  if (ether.begin(sizeof Ethernet::buffer, mymac, 10) == 0)
    Serial.println( "Failed to access Ethernet controller");
  if (!ether.dhcpSetup())
    Serial.println("DHCP failed");

  ether.printIp("IP:  ", ether.myip);
  ether.printIp("GW:  ", ether.gwip);
  ether.printIp("DNS: ", ether.dnsip);

  if (!ether.dnsLookup(website)) {
    Serial.println("DNS failed");
    ether.copyIp(ether.hisip, hisip);
  }
  ether.hisport = 80;
  ether.printIp("SRV: ", ether.hisip);
}

void serialLoop() {
  byte c;
  while(mySerial.available()){

    c = mySerial.read();
    if (c > 0) {
      //c &= 0x7F;
    }
    switch (c) {
      case '/':
        break;
      case '!':
        collectData();
        memset(readings, 0, sizeof readings);
        break;
      default:
        if (p1_scanner(c)) {
          for (byte i = 0; i < NTYPES; ++i)
            if (type == typeMap[i]) {
              readings[i] = value;
              break;
            }
        }
    }
  }
}

void loop () {
  ether.packetLoop(ether.packetReceive());
  serialLoop();
}

I’m sure I’m going to use this EthernetNano more in the future – the very small size, price, built-in flexibility make this a great solution for a lot of things!

 

 

 

 

 

Tagged , . Bookmark the permalink.

9 Responses to Flexible Serial to Ethernet for less than 20 Euro

  1. Henk van Hulst says:

    Leuk gedaan, ja ik gebruik de Arduino ook vaak zijn voor i/o en sensoren.
    Ik zou graag weten hoe je nu koppelt met de HC2 of HCL van Fibaro. Ik heb een Arduino screencontroller gemaakt (lichtsensor,windmolentje,regensensor) maar koppelt deze nu met een Fibaro binaire i/o (€ 40) maar wil graag dit via Ethernet koppelen naar een virtueel device in de HCL.

    groet, Henk

    • Hallo Henk,
      Mijn HC2 ervaring is om preies te zijn 2 avonden, dus misschien kan het op een nog betere manier, maar ik heb het als volgt gedaan. Net als dat je de Arduino HTTP calls kunt laten uitvoeren, kun je hem ook als HTTP server dienst laten doen. Als er een pagina index.html wordt opgevraagd, wordt er een response teruggestuurd met daarin een JSON met de uptime van de Nano (de millis()). Voeg daarbij een Virtual Device in de HC2 met in de main loop een lua script:
      --[[
      %% properties
      %% globals
      --]]
      local hms = 0;
      fibaro:debug("start")
      if (nano == nil) then
      nano = Net.FHttp("192.168.10.191", 80)
      end
      res, status, errorCode = nano:GET("/index.html")
      fibaro:debug(res)
      fibaro:sleep(10*1000)

      En je ziet elke 10 seconden de uptime in de debug window verschijnen 😉
      Verder ben ik nog niet, want het is nog niet stabiel genoeg; ik ben nu aan het uitzoeken of dat aan de Fibaro zijde (JSON encoding?) ligt of de Nano…

  2. Pingback: Adding a smart meter to the Fibaro HC2 - Digits Domotica Blog

  3. Falcon says:

    Zeker een interessante post. Echter lijkt mij dat je nu een gevoedde USB poort aansluit en als je hem aan je slimme meter hangt en dus geen stroom is.

    De slimme meter moet juist stroom ontvangen op de P1 poort en dus moet je weer stroom aansluiten op je arduino.

    Of zie ik dat verkeerd? Op dit moment loopt het via een RPi maar die wil ik eigenlijk vervangen door dit project.

    • Robert Hekkers says:

      Klopt, de P1 poort moet je voeden met 5V als ik me niet vergis. Daar kun je ook de Arduino mee voeden of je pakt de 5V van een pin af (heb ik ooit ook zo gedaan).

  4. Falcon says:

    Maar hoe kan ik die voeden? Alle pins verdwijnen in jouw foto in de Ethernet shield.

  5. Robert Hekkers says:

    En komen er aan de onderzijde net zo hard weer uit tevoorschijn.

  6. Falcon says:

    Ok, heb de libraries toegevoegd EtherCard en SPI

    Script van deze pagina (en de volgende) gecombineerd maar krijg een foutmelding:
    /// @dir p1scanner
    /// Parse P1 data from smart meter and send as compressed packet over RF12.
    /// @see http://jeelabs.org/2013/01/02/encoding-p1-data/
    // 2012-12-31 http://opensource.org/licenses/mit-license.php

    // Changed to work with Ethernet shield, 2014-06 Robert Hekkers

    #include
    #include

    #define DEBUG 1 // set to 1 to use fake data instead of SoftwareSerial
    #define LED 0 // set to 0 to disable LED blinking

    SoftwareSerial mySerial (7,17 /*, true*/); // rx, tx, inverted logic
    #define NTYPES (sizeof typeMap / sizeof *typeMap)
    // list of codes to be sent out (only compares lower byte!)
    const byte typeMap [] = {181, 182, 281, 282, 96140, 170, 270, 2410, 2420, 2440};

    byte Ethernet::buffer[700];
    static byte mymac[] = { 0x74,0x69,0x69,0x2D,0x00,0x01 };
    const char website[] PROGMEM = "devbox.hekkers.lan";
    uint8_t hisip[] = { 192,168,10,179 };
    Stash stash;

    byte type;
    uint32_t value;
    uint32_t readings[NTYPES+1];

    static bool p1_scanner (char c) {
    switch (c) {
    case ':':
    type = 0;
    value = 0;
    break;
    case '(':
    if (type == 0)
    type = value; // truncates to lower byte
    value = 0;
    case '.':
    break;
    case ')':
    if (type)
    return true;
    break;
    default:
    if ('0' <= c && c <= '9')
    value = 10 * value + (c - '0');
    }
    return false;
    }

    static void collectData (bool empty =false) {
    if (!empty) {
    for (byte i = 0; i < NTYPES; ++i) {
    Serial.print("@ ");
    Serial.print(typeMap[i]);
    Serial.print('=');
    Serial.println(readings[i]);
    }
    byte sd = stash.create();
    stash.print("0,");
    stash.println(millis() / 1000);
    for (byte i = 0; i 0) {
    //c &= 0x7F;
    }
    switch (c) {
    case '/':
    break;
    case '!':
    collectData();
    memset(readings, 0, sizeof readings);
    break;
    default:
    if (p1_scanner(c)) {
    for (byte i = 0; i < NTYPES; ++i)
    if (type == typeMap[i]) {
    readings[i] = value;
    break;
    }
    }
    }
    }
    }

    void loop() {
    c = mySerial.read();
    switch (c) {
    case '/':
    receiving = true;
    ...
    break;
    case '!':
    receiving = false;
    ...
    break;
    }
    ...
    if(!receiving) {
    if (ether.packetLoop(ether.packetReceive())) {
    ...
    ether.httpServerReply(readingsPage());
    }
    }
    }

    Foutmelding:

    hekkers_smart_meter.ino: In function 'void loop()':
    hekkers_smart_meter.ino:137:3: error: 'c' was not declared in this scope
    hekkers_smart_meter.ino:140:7: error: 'receiving' was not declared in this scope
    hekkers_smart_meter.ino:141:7: error: expected primary-expression before '...' token
    hekkers_smart_meter.ino:141:7: error: expected ';' before '...' token
    hekkers_smart_meter.ino:145:7: error: expected primary-expression before '...' token
    hekkers_smart_meter.ino:145:7: error: expected ';' before '...' token
    hekkers_smart_meter.ino:148:3: error: expected primary-expression before '...' token
    hekkers_smart_meter.ino:148:3: error: expected ';' before '...' token
    hekkers_smart_meter.ino:155:1: error: expected '}' at end of input
    Error compiling.

    • Robert Hekkers says:

      Je gebruikt niet de originele code (en die van de volgende is maar een snippet) en vraagt mij te debuggen? Vooruit dan maar…
      Ik mis onder andere een “byte c;” regel.

Leave a Reply

Your email address will not be published. Required fields are marked *