News:

To visit MidNite Solar click this link www.midnitesolar.com

Main Menu

Web server for Classic

Started by dgd, February 11, 2015, 07:57:05 AM

Previous topic - Next topic

dgd

I have made some progress with a simple Arduino based web server for the Classic. First cut, its bare minimum but does look ok
It uses an Arduino UNO with an ethernet shield (Wiznet W5100 with micro SD card interface)  and an RS232 shield.
The connection to the Classic is via rs232 to the middle serial port MNGP/SLAVE and uses Modbus RTU to extract the values of various modbus registers.
The Uno then formats this data into a very simple html table and with webserver makes a page available to incoming web browser connections.
This does not affect the builtin ethernet connection in the Classic which can still have an active connection to the Local App or other applications.
The webserver on the UNO seems to accept several TCP connections, I have had three active at one time. I have tested it on W8 laptop with firefox web browser and Ipad running Safari and on my Iphone 4s.

Again, as with the 2 line LCD display in another arduino thread, I will make the program (sketch) available to anyone who wants it.
Not sure if posting it here is ok

One of my goals here is to eventually make an MNIP plug on front to replace the MNLP front on the Lite Classics. The very tiny sized Arduino Nano ( the one for $2 US without USB serial but ttl serial instead) and the almost microscopic MAX232 converter plus the ethernet wifi from Sainsmart means all the bits will easily fit the space

dgd
Classic 250, 150,  20 140w, 6 250w PVs, 2Kw turbine, MN ac Clipper, Epanel/MNdc, Trace SW3024E (1997), Century 1050Ah 24V FLA (1999). Arduino power monitoring and web server.  Off grid since 4/2000
West Auckland, New Zealand

TomW

#1
Quote from: dgd on February 11, 2015, 07:57:05 AM
Again, as with the 2 line LCD display in another arduino thread, I will make the program (sketch) available to anyone who wants it.
Not sure if posting it here is ok

dgd

dgd;

I put on my Administrators hat and I can see no reason you can't post it here.

Please do so,  in fact.

Thanks.

Tom
Do NOT mistake me for any kind of "expert".

( ͡° ͜ʖ ͡°)


24 Trina 310 watt modules, SMA SunnyBoy 7.7 KW Grid Tie inverter.

I thought that they were angels, but much to my surprise, We climbed aboard their starship and headed for the skies

dgd

#2
thanks Tom.
I will upload the sketch here in a few minutes.
I have been running this simple server now for about 30 hours and it seems quite stable. Once when I moved the cabling and unplugged the serial and ethernet then reconnected them it would not respond until I reset the UNO. I think this was the modbus connection timeout which stops the program.
From the screenshot in previous posting I don't understand the impossible high input voltage, thats from register 4123 HighestVinputLog and is divided by 10 to get volts
I also need to figure out from which register, if any, the BTS presence is known, I have no BTS hence weird value.
Same with REM, remote or WBjr temperature, I need to know how to detect, from Register?, if WBjr is present.

Any suggestions are welcome.
dgd
Classic 250, 150,  20 140w, 6 250w PVs, 2Kw turbine, MN ac Clipper, Epanel/MNdc, Trace SW3024E (1997), Century 1050Ah 24V FLA (1999). Arduino power monitoring and web server.  Off grid since 4/2000
West Auckland, New Zealand

dgd

#3
Sketch below
Note the IP number definition which should be set to whatever you need. Do NOT use the same IP number as your Classic.
The modbus_construct second parameter, 10, is the Classic id. Change this if yours is not 10, eg 11,12 etc..
Leave the MAC address.
This sketch should be cut and pasted into the Arduino IDE window running on a windows computer, then verified (compiled).
To use this sketch you will need an Arduino UNO, an rs232 shield and an ethernet shield, WIznet 5100 with micro SD card interface.
Future versions of this sketch will use the SD card to store and retrieve Classic log data for display in web page.
You need n ethernet connection to your LAN and a serial cable rj11 to DB9 male to connect Classic MNGP/SLAVE port to rs232 shield.
This sketch has been tested with an UNO with Atmel 328 cpu,  a NANO clone with 328, a micro max232 ttl<>rs232 serial converter s well as the serial rs232 shield.

dgd

---

Sketch removed 20Feb, later version in posting below

Classic 250, 150,  20 140w, 6 250w PVs, 2Kw turbine, MN ac Clipper, Epanel/MNdc, Trace SW3024E (1997), Century 1050Ah 24V FLA (1999). Arduino power monitoring and web server.  Off grid since 4/2000
West Auckland, New Zealand

mtdoc

#4
Good stuff dgd.  Unfortunately, I'm having the same issue compiling this code with the same 'packetPointer' does not name a type error.

I wish I wasn't such a programming dummy - I am just recently trying to learn c.

Of course the simplified Arduino version of C has its own ideosyncrasies,  but doesn't that error imply that the 'packetPointer' data type needs to be defined?

I still can't find it defined in the v22 Simplemodbusmaster library or in your code.  I must be missing something obvious.  :-[
Array 1: Sanyo HIT225 X 8 on Wattsun tracker. Array 2: Evergreen ES-E-225 X 12 on shed roof. Midnite e-panel with Outback GVFX3648, FNDC and Classic 150 X 2. 436 AH AGMs. Honda eu2000i X 2.

mtdoc

Success with this one as well after changing to the V12 library with the packetPointer defined.

One small correction needed to be made on line 252:

If (bat_temp > 90) 

'If' needs a lowercase i -----> if (bat_temp > 90)

Yeah!

Thanks much.
Array 1: Sanyo HIT225 X 8 on Wattsun tracker. Array 2: Evergreen ES-E-225 X 12 on shed roof. Midnite e-panel with Outback GVFX3648, FNDC and Classic 150 X 2. 436 AH AGMs. Honda eu2000i X 2.

dgd

I have tidied up and included more Classic data in the simple webpage, including eq, abs and float timers, the Wbjr temp,current and soc.
Many timings have been fine tuned to allow a page refresh every 12 seconds.
The next posting will have the updated sketch. This has been tested and running for a week and appears very stable.
I will remove the previously posted sketch to avoid confusion (which state I'm usually in)
According to the docs for the ethernet shield's Wiznet 5100 controller it provides the ethernet stack and can support up to four TCP/UDP connections.
Nice  :)
This simple sketch is reaching the limit of resources for the Arduino UNO. I have a sketch version that displays the current dat and five more 10 minute apart sets of data but the UNO has very little of its 2k ram left and sometimes gets confused as its memory fades.
This problem does not happen if an Arduino Mega 2560 is used as it has 8k ram.
If anyone is interested I can post Mega sketch with extended web page displays - in colour too  :)

dgd
Classic 250, 150,  20 140w, 6 250w PVs, 2Kw turbine, MN ac Clipper, Epanel/MNdc, Trace SW3024E (1997), Century 1050Ah 24V FLA (1999). Arduino power monitoring and web server.  Off grid since 4/2000
West Auckland, New Zealand

dgd

#7
The Arduino UNO simple web server for Midnite Classic
Cut and paste this into the Arduino IDE then check for broken/truncated lines. This has been compiled and loaded to a UNO
Note you need to have installed SimpleModbusMaster library version 1.10 or 1.12   You can use 2.23 but you will have to make changes to the sketch as outlined in the sample sketch provided with that library.
Set the IP number as required and the second parameter in the modbus_construct lines is the Classic ID number, You will need to change this if your is not 10 (usually the default).

dgd

Updated 2 March 2014



/*
  Web Server

A simple web server that shows the values of several modbus registers from a Midnite
Classic
David Dix  2 March 2015  dgd@kc.net.nz
*/

#include <SimtpleModbusMaster.h> 
#include <Ethernet.h>
#include <SPI.h>

// Enter a MAC address and IP address for your controller below.
// The IP address will be dependent on your local network:
byte mac[] = {
  0xDE, 0xAD, 0xBE, 0xEF, 0xFE, 0xED
};
IPAddress ip(192, 168, 1, 177);

// Initialize the Ethernet server library
// with the IP address and port you want to use
// (port 80 is default for HTTP):
EthernetServer server(80);

// setup modbus port information
#define baud 19200
#define timeout 1000
#define polling 200 // the scan rate
#define retry_count 10

// used to toggle the receive/transmit pin on the driver, lib needs it but really only for rs485
#define TxEnablePin 2

enum
{
  PACKET1,
  PACKET2,
  PACKET3,
  PACKET4,
  PACKET5,
  TOTAL_NO_OF_PACKETS // leave this last entry
};

// Create an array of Packets to be configured
Packet packets[TOTAL_NO_OF_PACKETS];
packetPointer packet1 = &packets[PACKET1];  // pointer to access each packet
packetPointer packet2 = &packets[PACKET2];
packetPointer packet3 = &packets[PACKET3];
packetPointer packet4 = &packets[PACKET4];
packetPointer packet5 = &packets[PACKET5];

// Data read from the Classic will be stored in these arrays
// if the array is initialized to the packet.
unsigned int readRegs[30];
unsigned int wbjrRegs[15];
unsigned int vRegs[2];
unsigned int idRegs[9];       // name 4209 to 4212, and time/date 4213 to 4216
unsigned int sernoRegs[3];

void setup()
{
  // start the Ethernet connection and the server:
  Ethernet.begin(mac, ip);
  server.begin();
 
  // to read 20 register starting at address 4114  and 14 starting 4360 
  modbus_construct(packet1, 10, READ_HOLDING_REGISTERS, 4114, 29, readRegs);
  modbus_construct(packet2, 10, READ_HOLDING_REGISTERS, 4360, 14, wbjrRegs);
  modbus_construct(packet3, 10, READ_HOLDING_REGISTERS, 4100, 1, vRegs);
  modbus_construct(packet4, 10, READ_HOLDING_REGISTERS, 4209, 8, idRegs);
  modbus_construct(packet5, 10, READ_HOLDING_REGISTERS, 0x7000, 2, sernoRegs);

  // initialise modbus communications settings
  modbus_configure(&Serial, baud, SERIAL_8N1, timeout, polling, retry_count, TxEnablePin, packets, TOTAL_NO_OF_PACKETS);

}

void loop()
{

  float batt_volts, in_volts, batt_amps, kwh, high_in_volts;
  int state, watts, wbjr_soc, vers, model;
  float fet_temp, pcb_temp, rem_temp, bat_temp, wbjr_amps;
  char  c;
  char  Classic_name[10];
  int   eq_hrs=0, eq_mins=0, eq_secs;
  int   fl_hrs=0, fl_mins=0, fl_secs;
  int   abs_hrs=0, abs_mins=0, abs_secs;
  unsigned int serno;
 
  // get the modbus registers 
  modbus_update();
   
  // make Classic register values ready for web page
     model = (unsigned int)lowByte(vRegs[0]);
     Classic_name[0] = lowByte (idRegs[0]);
     Classic_name[1] = highByte(idRegs[0]);
     Classic_name[2] = lowByte (idRegs[1]);
     Classic_name[3] = highByte(idRegs[1]);
     Classic_name[4] = lowByte (idRegs[2]);
     Classic_name[5] = highByte(idRegs[2]);
     Classic_name[6] = lowByte (idRegs[3]);
     Classic_name[7] = highByte(idRegs[3]);
     Classic_name[8] = 0x00;

     serno =  sernoRegs[1];
     batt_volts = readRegs[0];
     batt_volts /=10;
     in_volts = readRegs[1];
     in_volts /=10;
     batt_amps = readRegs[2];
     batt_amps /=10;
     kwh= readRegs[3];
     kwh /=10;
     watts = readRegs[4];
     state = (unsigned int)readRegs[5] >> 8;  // high byte contains charge state code 
   
     high_in_volts = readRegs[7];
     high_in_volts /=10;
     bat_temp = readRegs[17];    // need to test if BTS present how?
     bat_temp /=10;
     fet_temp = readRegs[18];
     fet_temp /=10;
     pcb_temp = readRegs[19];
     pcb_temp /=10;
     
     fl_secs = readRegs[23];      //4137 floatTimeodayseconds
     if (fl_secs >= 3600) fl_hrs = (int)fl_secs/3600;
     fl_secs -= fl_hrs*3600;
     if (fl_secs >= 60 ) fl_mins = (int)fl_secs/60;
     fl_secs -= fl_mins*60;
     
     eq_secs = readRegs[28];      //4142 EqualizeTime
     if (eq_secs >= 3600) eq_hrs = (int)eq_secs/3600;
     eq_secs -= eq_hrs*3600;
     if (eq_secs >= 60 ) eq_mins = (int)eq_secs/60;
     eq_secs -= eq_mins*60;
     
     abs_secs = readRegs[24];      //4138 AbsorbTime
     if (abs_secs >= 3600) abs_hrs = (int)abs_secs/3600;
     abs_secs -= abs_hrs*3600;
     if (abs_secs >= 60 ) abs_mins = (int)abs_secs/60;
     abs_secs -= abs_mins*60;
     
     rem_temp = (unsigned int) lowByte (wbjrRegs[11]);
     rem_temp -=50;
     wbjr_amps = (int) wbjrRegs[10];
     wbjr_amps /=10;
     wbjr_soc = wbjrRegs[12];          // 4372
     
  // listen for incoming clients
  EthernetClient client = server.available();
  if (client)
  {
    // an http request ends with a blank line
    boolean currentLineIsBlank = true;
    while (client.connected())
    {
      if (client.available())
      {
        char c = client.read();
        // if you've gotten to the end of the line (received a newline
        // character) and the line is blank, the http request has ended,
        // so you can send a reply
        if (c == '\n' && currentLineIsBlank)
        {
          // send a standard http response header
          client.println("HTTP/1.1 200 OK");
          client.println("Content-Type: text/html");
          client.println("Connection: close");  // the connection will close after Completing response
          client.println("Refresh: 5");  // refresh the page automatically every 5 sec
          client.println();
          client.println("<!DOCTYPE HTML>");

          client.println("<html><body>");
         
          // simple page title
          client.println ("<h2>Midnite Classic</h2> ");
         
          client.println ("<table border=\"1\" cellpadding=\"10\" style=\"width\:60%\"\>");
          client.println ("<tr>");
          client.println ("<td>Name</td>");
          client.print ("<td>");
          client.print (Classic_name);
          client.print ("  C");
          client.print (model);
          client.print ("  #");
          client.print (serno);
          client.println("</td>");
          client.println("</tr>");
         
          client.println("<tr>");         
          client.print ("<td>Charge State</td>");
          switch (state)
          {
            case 0:
              client.print ("<td>Resting</td>");
              break;
            case 3:
              client.print ("<td>Absorb - time left ");
              client.print (abs_hrs);
              client.print (":");
              client.print (abs_mins);
              client.print (":");
              client.print (abs_secs);
              client.print ("</td>");
              break;
            case 4:
              client.print ("<td>BulkMppt</td>");
              break;
            case 5:
              client.print ("<td>Float - total time ");
              client.print (fl_hrs);
              client.print (":");
              client.print (fl_mins);
              client.print (":");
              client.print (fl_secs);
              client.print ("</td>");
              break;
            case 6:
              client.print ("<td>FloatMppt</td>");
              break;
            case 7:
              client.print ("<td>Equalize - time left ");
              client.print (eq_hrs);
              client.print (":");
              client.print (eq_mins);
              client.print (":");
              client.print (eq_secs);
              client.print ("</td>");
              break; 
            case 10:
              client.print ("<td>HyperVoc</td>");
              break;
            case 18:
              client.print ("<td>EqMppt</td>");
              break;
          }
          client.println("</tr>");
         
          // output the values of several Classic modbus registers
          client.println("<tr>");
          client.print("<td>Battery volts</td>");
          client.print("<td>");
          client.print(batt_volts);
          client.println("</td>");
          client.println ("</tr>");

          client.println("<tr>");
          client.print ("<td>Output Amps</td>");
          client.print ("<td>");
          client.print(batt_amps);
          client.println("</td>");
          client.println ("</tr>");

          client.println("<tr>");
          client.print ("<td>Power Watts</td>");
          client.print ("<td>");
          client.print(watts);
          client.println("</td>");
          client.println("</tr>");
 
          client.println("<tr>");
          client.print ("<td>Energy Kwhr</td>");
          client.print ("<td>");
          client.print(kwh);
          client.println("</td>");
          client.println("</tr>");
         
          client.println("<tr>");
          client.print("<td>InputPV volts</td>");
          client.print ("<td>");
          client.print(in_volts);
          client.println("</td>");
          client.println("</tr>");
         
          client.println("<tr>");
          client.print("<td>High PV volts</td>");
          client.print ("<td>");
          client.print(high_in_volts);
          client.println("</td>");
          client.println("</tr>");
         
          client.println("<tr>");
          client.print("<td>PCB temp oC</td>");
          client.print ("<td>");
          client.print(pcb_temp);
          client.println("</td>");
          client.println("</tr>");

          client.print("<tr>"); 
          client.print("<td>FET temp oC</td>");
          client.print ("<td>");
          client.print(fet_temp);
          client.println("</td>");
          client.println("</tr>");
         
          client.println("<tr>");
          client.print("<td>BAT temp oC</td>");
          client.print ("<td>");
          if (bat_temp > 100 )  // not a good test, should see if bts present
          client.print ("No BTS, default 25c");
          else
          client.print(bat_temp);
          client.println("</td>");
          client.println("</tr>");
         
          client.println("<tr>");
          client.print("<td>Remote temp oC</td>");
          client.print ("<td>");
          client.print(rem_temp);
          client.println("</td>");
          client.println("</tr>");
 
          client.println("<tr>");
          client.print ("<td>WBjr Amps</td>");
          client.print ("<td>");
          client.print(wbjr_amps);
          client.println ("</td>");
          client.println("</tr>");
     
          client.println("<tr>");
          client.print ("<td>Battery SOC%</td>");
          client.print ("<td>");
          client.print(wbjr_soc);
          client.println ("</td>");
          client.println("</tr>");
           
          client.println ("</table>");

          client.println("</body></html>");
          break;
        }
        if (c == '\n') {
          // you're starting a new line
          currentLineIsBlank = true; 
         
        }
        else if (c != '\r') {
          // you've gotten a character on the current line
          currentLineIsBlank = false;
        }
      }
    }
    // give the web browser time to receive the data
    delay(10);
    // close the connection:
    client.stop();
  }
}

Classic 250, 150,  20 140w, 6 250w PVs, 2Kw turbine, MN ac Clipper, Epanel/MNdc, Trace SW3024E (1997), Century 1050Ah 24V FLA (1999). Arduino power monitoring and web server.  Off grid since 4/2000
West Auckland, New Zealand

Resthome

John

10 x Kyocera KC140, Classic 150 w/WBJr, Link10 Battery Monitor, 850 AH @ 12v Solar One 2v cells, Xantrex PROwatt SW2000
Off Grid on Houseboat Lake Don Pedro, CA

dgd

Quote from: Resthome on February 21, 2015, 01:35:16 AM
Nice work dgd !

thanks John
I enjoyed playing with these simple processors but I'm impatiently waiting on my Cubie to arrive so that I can do some proper reporting from my Classics and Clipper

dgd
Classic 250, 150,  20 140w, 6 250w PVs, 2Kw turbine, MN ac Clipper, Epanel/MNdc, Trace SW3024E (1997), Century 1050Ah 24V FLA (1999). Arduino power monitoring and web server.  Off grid since 4/2000
West Auckland, New Zealand

boB

K7IQ 🌛  He/She/Me

paul alting

#11
David,

Just looking over your program and I see the following code:
// Data read from the Classic will be stored in these arrays
// if the array is initialized to the packet.
unsigned int readRegs[30];
Now, this array is going to hold 30 registers, correct?
There is a problem with this, in that the hardware serial buffer in the Arduino is only 64 bytes deep, which is equates to 32 registers.
But, with Modbus, you have an overhead of some bytes, thus only allowing you 58 bytes, which means 29 registers.
Form the SimpleModbusMaster manual, page 4:
(https://drive.google.com/folderview?id=0B0B286tJkafVYnBhNGo4N3poQ2c&usp=drive_web&tid=0B0B286tJkafVSENVcU1RQVBfSzg#list)
QuoteNote:
The Arduino serial ring buffer is 64 bytes or 32 registers. This means the amount of data requested form
a slave in a single packet is limited to the amount of bytes available:

  • Function 1 & 2 â€" maximum number of points are limited to 472 (59 bytes)
    Function 3 & 4 â€" maximum number of registers are limited to 29 (58 bytes)
    Function 15 â€" maximum number of points are limited to 440 (55 bytes)
    Function 16 â€" maximum number of registers are limited to 27 (54 bytes)
It will be function code 3, where you read multiple holding registers.
I'm not sure what happens if you specifiy for a request to ask for 30 registers.

Yes, we have code tags in this forum, let's use them :)
____
Paul
6 x 200W PV into home-brew 6 stage MOSFET charge controller : Microhydro 220Vac 3 phase IMAG
8 x 400Ah LiFeYPO4 Winston : Latronics LS2412 inverter
QuadlogSCADA control and monitoring system : Tasmania, Australia : http://paulalting.com

dgd

Quote from: paul alting on March 22, 2015, 03:23:06 AM
Just looking over your program and I see the following code:
// Data read from the Classic will be stored in these arrays
// if the array is initialized to the packet.
unsigned int readRegs[30];
Now, this array is going to hold 30 registers, correct?
;
Yes that array is intended for register values but the 30 size is only the array declaration
Further down the code the statement that actually sets up the number of registers to read is:

  modbus_construct(packet1, 10, READ_HOLDING_REGISTERS, 4114, 29, readRegs);

The second to last parameter is the number of registers to read, ie 29 registers starting at address 4114
So 29, which as you pointed out is the maximum for read_holding_registers

This one already caught me last month when I did this simple web server  :D

dgd
Classic 250, 150,  20 140w, 6 250w PVs, 2Kw turbine, MN ac Clipper, Epanel/MNdc, Trace SW3024E (1997), Century 1050Ah 24V FLA (1999). Arduino power monitoring and web server.  Off grid since 4/2000
West Auckland, New Zealand