News:

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

Main Menu

Web server for KID

Started by dgd, November 10, 2015, 02:29:53 PM

Previous topic - Next topic

dgd

I have been experimenting with this for a couple of months now between real life demands.
Its seems fairly straight forward to get an Arduino (mega2560 V3) with rs232 serial and ethernet/SD card connected to the KID then extract some running data values for simple web page displays and log reports.
Was also thinking about a modbus ethernet interface for the KID where an Arduino processes the KID serial data and provides a modbus stack that can be accessed via ethernet (and perhaps rs485/rs232)
I see from Arduino forums that others have hacked some existing modbus libs to achieve this with the goal to made a cheaper version of the Outback AXS box but perhaps limited to one OB device. This type of protocol inverter is just what the KID needs but i'm unsure how existing MN software such as the LocalApp interfaces to ethernet/modbus to identify devices it can extract data from or how the MyMN software extracts data it uses to identify Classics.

Before moving too far down this track I would be interested to know if MN are developing a web connecting device or even just a protocol converter for the KID?
I'm also assuming that MN will not release any info on localAPP/MyMN protocol interface to Classics/KIDS so having the localApp recognise a KID/Arduino modbus stack manager may be impractical.

I can (soon) post all the arduino code here for a complete simple KID web server if anyone is interested.

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

ClassicCrazy

Dear Midnite ,
Please consider giving  DGD some of the  stuff he needs to know and use  so he can make some cool add on's for us to play with !
And before he starts letting real life get in his way again .
( or contract him to make these new devices  ?)
Just a few thoughts.

Thank you ,
Larry
system 1
Classic 150 , 5s3p  Kyocera 135watt , 12s Soneil 2v 540amp lead crystal for 24v pack , Outback 3524 inverter
system 2
 5s 135w Kyocero , 3s3p 270w Kyocera  to Classic 150 ,   8s Kyocera 225w to Hawkes Bay Jakiper 48v 15kwh LiFePO4 , Outback VFX 3648 inverter
system 3
KID / Brat portable

dgd

OK, just about completed testing with a KID web server using an Arduino Mega 2560, ethernet/SD card shield and a small rs232 shield using max3232.
Initially I have used the same html5 index page with canvas gauges I used for the Classic web server. But I really want this to look different but still use gauge displays plus a nice transparent text box over a suitable retro background image.
So just a few more experiments with different gauge types (the steel gauges are so just right  8))
I like the Midnite 1930s car image too, makes a great background
Arduino IDE C++ code next postings with server images...

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

This has turned into an interesting and somewhat complex C++ code project. Mainly because there are only seven useful data items made available by the KID via its serial port. The rest in the enumed list provided by Mario in the KID forum seem to be for dual KID communications.
I have looked at a simplified list of packet values for data comms to the KID and avoiding the complexities of the xoring code.

The Arduino Mega interfaces to some additional hardware to provide some of the web page data.

With no WBjr data being made available from the Kid, an ACS758 current sensor is used to provide battery current. The particular version used is the bi-directional 100 amp. ($15 ebay)
http://www.ebay.com/itm/50A-100A-150A-200A-Bi-Uni-AC-DC-Current-Sensor-Module-arduino-compatible-/111689533182?var=&hash=item1a013706fe:m:msZpOTmntlaR-hYEFieqFgw

An RTC is connected to the Mega, this has been previously set up with correct time and date and is a ZS-043 card containing DS3231 rtc, AT23C32 eeprom and a temperature probe. The RTC provides all date/time info for logging info and web pages. ($4 ebay)
http://www.ebay.com/itm/5Pcs-For-Arduino-DS3231-AT24C32-IIC-Module-Precision-Real-Time-Clock-Memory-/262123920788?hash=item3d07cd9d94:g:--4AAOSwniRWNyWo

There is a lot of arduino info on interfacing and driving these devices
http://henrysbench.capnfatz.com/henrys-bench/arduino-current-measurements/acs758-arduino-current-sensor-tutorial/
http://tronixstuff.com/2014/12/01/tutorial-using-ds1307-and-ds3231-real-time-clock-modules-with-arduino/

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

I'm sort of debating if its worth posting all this Kid/Arduino web server stuff here as its probaby, at best, only of slight interest. Moderators please remove if its too much removed from the forum purpose of Midnite related postings...

The code is my latest test setup for Kid web server and is compiled using Adruino IDE1.6.6 for Mega2560,
Ethernet/SD card shield,max3232 rs232/tty serial card, ACS758 DC current sensor, several temperature probes ZC349400, ZS-042 rtc/eeprom card, and micro SD card containing web pages and a Kid info file.
There are a fair few gaps in the code where I have experimented with making this server almost completely independant of any data extracted from a solar controller.
This involves a very accurate DC battery voltage measurement using a 24bit ADC, LTC2400 voltmeter, additional current sensors based on ACS758 devices and a direct interface to a  Midnite WBjr/Deltec shunt combo (not connected to a Kid or Classic). This would make the server and associated sensors into a wrapper for any solar controller.  (I did this for an Outback MX60 and more successfully for an old Heliotrope CC120 PWM controller that I retired over 10 years ago)

dgd

Part 1......

/*
  KID Web Server

A web server that shows the values of several running data items from a Midnite KID mppt solar controller.
Loads web pages from SD card, the index.htm display uses the Mikhaus canvas gauges and svg panel to show data
This code uses AJAX to update gauges and svg items.
Data is loaded from the KID serial port (rs232) labelled 'mster port'.
David Dix  4 March 2016  dgd@kc.net.nz
release 0.2,  only shows battery volts, pv volts, output current, watts (calculated), kwh and temps
               awaiting Kid FW update to make more data items available, eg WBjr current etc..
               Static ID info data is contained in file kidinfo.txt on SD card in CSV format
               RTC is connected to Mega, for timestamping log report data and time in display
               Real battery current taken using  ACS759 device since WBjr data not yet available from Kid
         0.3   Added RTC ZN-042 for time/dates used in reports/displays etc.. This device includes an eeprom
               128by32 and temperature measurement (used at remt in code). EEprom code included but not yet
               used - intended for storing up to 128 daily stats records/
               
               
*/

#include <Ethernet.h>
#include <SD.h> 
#include <Wire.h>

// Define IP and MAC
byte mac[] = {
  0xDE, 0x9A, 0xBE, 0xEF, 0xDE, 0xCD    // just leave as is
};
IPAddress ip(192, 168, 1, 175);   // this depends on LAN IP, adjust to suit

// size of buffer used to capture HTTP requests
#define REQ_BUF_SZ   50

// serial port on Mega connecting to Kid serial port
#define Kidserial Serial1

// Initialize the Ethernet server library
// with the IP address and port (port 80 is default for HTTP):
EthernetServer server(80);
File webFile, KidFile;              // the web page file, Kid ID file, on the SD card
char HTTP_req[REQ_BUF_SZ] = {0}; // buffered HTTP request stored as null terminated string
char req_index = 0;              // index into HTTP_req buffer

#define baud 57600        // and let serial.begin default 8N1 apply

// Kid data types enum list
typedef enum{             // there are 6 bits for this list, could be 64 entries, right now most don't work
                          // this server only uses seven of these marked ** below
   null = 0,              /// Not Used
   special_key = 3,       /// Special key for different Comm modes.
   COMM_MODE = 5,         /// Not for PC Comm Mode for internal use
   COMM_status,           /// Not for PC. Comm Status
   COMM_RAWiBATT,         /// Not for PC. to be able to get the offset of the slave unit
   COMM_iBATT ,           /// 8 ** Not for PC  (actually should be as it seems to be KID output current)
   COMM_vPV,              /// 9 ** PV Voltage displayed on the kid (is this differnt to actual PV voltage?)
   COMM_vBATT,            /// 10 ** Battery voltage displayed on the kid (is it different to actual BV?)
   COMM_fetTEMP,          /// 11 * Internal FET temperature
   COMM_loadSTATE,        /// 12 ** Load state On, off, idle, overcurrent
   COMM_KWH,              /// 13 ** displayed KWH daily
   COMM_PWMERROR,         /// Not for PC Twin mode Only
   COMM_wimpf,            /// Not for PC Twin mode Only
   COMM_batteryStage,     /// 16 ** Battery charge stage Absorb, Bulk, Float, EQ (same values as Clssic?)
   COMM_batt_setpoint,    /// for test Do not use, invalid parameter
   COMM_absorbV,          /// Absorb battery voltage setpoint value
   COMM_floatV,           /// Float  battery voltage setpoint value
   COMM_eqV,              /// Eq  battery voltage setpoint value
   COMM_absorbT,          /// Absorb battery time setpoint in minutes
   COMM_batteryTemp,      /// 22 ** Battery Temperature as read from the BTS
   COMM_TempCompV,        /// Battery temperature compensation setpoint in mV
   Comm_SystemSet,        /// For Twin mode. set the other unit system
   Comm_BattNominal,      /// battery nominal 12, 24, 36,48 v
   Comm_EqT,              /// Eq battery time setpoint in minutes
} comm_var;

// using default absorb time 2 hours in seconds
#define Absorb_Time 7200

#define LOG_INTERVAL 594970    // millis time between saving 10 minute log entry 600,000 minus fine tuning

// maximum number of log records to store in memory
// keep <=48 for Mega memory (8k)
// not yet writing these to SD file as its a bit slow
#define MAX_LOG_COUNT 48
// Daily log, keep deault 20 days in memory arrays
#define MAX_DLOG_COUNT 20

#define AT24C32_ADDRESS    0x57  // On-board 32k byte EEPROM; 128 pages of 32 bytes each
#define DS3231_ADDRESS     0x68 // Device address when ADO = 0

// RTC variables
 
float temperature;

// useful data variables (some not used but for future upgrades)
  float batt_volts, batt_amps, batt_temp, endamps, lowvolts, highvolts, in_volts , out_amps, load_amps;
  char  invstr [8], oastr [8], kwhstr [6], statestr [30], load_statestr[10];
  char  lastkwhr[6];
  int   cstate = 0, loadstate = 0, watts, wbjr_ahr, wbjr_ra, wbjr_soc, vers , model , remt;
  int   high_watts , ctime_secs , ctime_mins , ctime_hrs , last_min;
  int   ctime_day , ctime_mth ;
  int   last_day, last_month;
  int   ctime_weekday , last_weekday ,highwatts ;
  char  ftpstr [8], ptpstr [8], peakpstr [6];
  char  KID_name[10], bvstr[8], wbastr[8], btpstr[8], rtpstr[8], ctimestr[10];
  int   eq_hrs , eq_mins , eq_secs ;
  int   fl_hrs , fl_mins , fl_secs ;
  int   abs_hrs , abs_mins , abs_secs ;
  int   serno ;
  unsigned long start_count, timenow;
  int   log_index=0, last_index=0, next_mins=0;
 
  // 10 minute log file arrays (should eventually write to SD card)
  int   log_mins[MAX_LOG_COUNT], log_hrs[MAX_LOG_COUNT];   // for record time stamp, no date yet
  float log_battvolts[MAX_LOG_COUNT], log_battamps[MAX_LOG_COUNT];     
  int   log_watts[MAX_LOG_COUNT], log_state[MAX_LOG_COUNT];       
  float log_involts[MAX_LOG_COUNT], log_outamps[MAX_LOG_COUNT];
  int   log_ahremain[MAX_LOG_COUNT];
  char  log_kwh[MAX_LOG_COUNT][6];
  int   log_ahr[MAX_LOG_COUNT], log_soc[MAX_LOG_COUNT];   // Soc to 100%
 
  int   dlog_index=0, dlast_index=0;
  // Day log file in arrays max 20 entries
  int   dlog_mth[MAX_DLOG_COUNT], dlog_day[MAX_DLOG_COUNT];
  char  dlog_kwh[MAX_DLOG_COUNT][6];
  int   dlog_abhr[MAX_DLOG_COUNT], dlog_abmin[MAX_DLOG_COUNT], dlog_flhr[MAX_DLOG_COUNT];
  int   dlog_flmin[MAX_DLOG_COUNT];
  float dlog_lbv[MAX_DLOG_COUNT], dlog_hbv[MAX_DLOG_COUNT], dlog_ea[MAX_DLOG_COUNT];
  int   dlog_hwts[MAX_DLOG_COUNT];
  int   dlog_hsoc[MAX_DLOG_COUNT], dlog_lsoc[MAX_DLOG_COUNT];
  int   dlog_ham[MAX_DLOG_COUNT], dlog_lam[MAX_DLOG_COUNT];
  int   dlog_hahr[MAX_DLOG_COUNT], dlog_lahr[MAX_DLOG_COUNT];
 
  int   high_soc=0, low_soc=0;       
  int   high_ahr=0, low_ahr=0;     
  int   high_ra=0, low_ra=0;
  int   float_hrs, float_mins, absorb_hrs, absorb_mins, absorb_left;

  // ACS758 setup data using 100amp bi-directional version
  const int analogIn = A0;
  int mVperAmp = 20;          // for 100A bi-directional
  int ACSoffset = 2500;       // for bi-directional

  // for KID data
  byte   rcbuff[5];  // input buffer for KID packet input/output
  int    kid_data;   // int for data value from data bytes rcbuff[1] and rcbuff[2]

// get time and date  from RTC
void get_time()
{
   // If device is not busy, read time, date, and temperature
   byte c = readByte(DS3231_ADDRESS, 0x0F) & 0x04;
   if(!c) // device is not busy
   {   c = readByte(DS3231_ADDRESS, 0x0F);           // Read STATUS register of DS3231 RTC
       // get time/date     
       ctime_secs = readSeconds();
       ctime_mins = readMinutes();
       ctime_hrs  = readHours();     // 24 hour clock? 
       ctime_day  = readDate();
       ctime_mth  = readMonth();
   
       // get temperature   use this as remote temperature normally from WBjr
       remt = readTempData();  // Read the temperature store as remt
       itoa (remt, rtpstr, 10);  // save as string
   }
   // otherwise if device is busy then just leave ctime and temperature values from last successful read
}

// read battery current from ACS758 hall device, reads high side (+ve line) current at battery
void get_BattAmps()
{
  int RawValue = 0;
  double Voltage = 0;

  RawValue = analogRead(analogIn);
  Voltage = (RawValue / 1023.0) * 5000;    // in mV
  batt_amps = (float) ((Voltage - ACSoffset) / mVperAmp);  // there that was easy
   
}

// sends SET packet to KID then receives GET packet from KID containing requested data item
// uses Serial1 on Mega

void get_Kdata (byte kid_cmd)
{
     int i,j;
     byte check_xor = 0, check_get, check_cmd, check_xor2;
     byte xorbits;
     
     rcbuff[0] = 0xB0;              // B11000000,  the 2 msbits are for data request from master
     rcbuff[0] = rcbuff[0] | kid_cmd ;    // lower 6 bits of CMD have code for data item requested
     rcbuff[1] = rcbuff[2] = rcbuff[3] = '\0';    // other 3 bytes are nulled

     // rcbuff[3] is xor'ed result from xor'ing bit pairs in rcbuff's 4 bytes
     for (i=0; i<4; i++)
     {
        for (j=0; j<4; j++)  // for four bit pairs in each byte
        {
                xorbits = (rcbuff[i] >> (j*2)) & 3; // xor each pair of bits in each rcbuff byte
                check_xor = check_xor ^ xorbits;
        }
        rcbuff[3] = check_xor;
     }
     Kidserial.write (rcbuff, 4);       // write buffer string to serial port
     delay(2);                        // delay to let GET response arrive
     
     if (Kidserial.available())
         Kidserial.readBytes (rcbuff, 4);   // rcbuff[1] and [2] contain 2 bytes of data
     // check valid data was returned, rcbuff[0] top 2 bits should be b10 and kid_cmd in lower 6 bits
     check_get = rcbuff[0] >> 6;
     check_cmd = rcbuff[0] | 0xbf;
     for (i=0; i<4; i++)
     {
        for (j=0; j<4; j++)  // for four bit pairs in each byte
        {
                xorbits = (rcbuff[i] >> (j*2)) & 3; // xor each pair of bits in each rcbuff byte
                check_xor2 = check_xor2 ^ xorbits;
        }
     }
         
     if (check_get != 0x02 || check_cmd != kid_cmd || check_xor2 != 0 ) // nonsense back from kid
         kid_data=0;  // so just return zero value for data
     else
     {
         kid_data = rcbuff[1] << 8;
         kid_data = kid_data | rcbuff[2];   // now have data as integer value in kid_data
     }
      return;
}     

 
void setup()
{
  int  i=0,j, k;
  char  idbuff[40];  // data buffer for SD card file kid.txt containing ID data.
 
    // disable Ethernet chip
    pinMode(10, OUTPUT);
    digitalWrite(10, HIGH);

    Kidserial.begin(baud);       // for data input from KID
   
    // initialize SD card
//    Serial.println("Initializing SD card...");
    if (!SD.begin(4)) {
//      Serial.println("ERROR - SD card initialization failed!");
        return;    // init failed
    }
//    Serial.println("SUCCESS - SD card initialized.");


    // check for index.htm file

    if (!SD.exists("index.htm")) {
//        Serial.println("ERROR - Can't find index.htm file!");
        return;  // can't find index file
    }
//    Serial.println("SUCCESS - Found index.htm file.");
   
     // start the Ethernet connection and the server:
    Ethernet.begin(mac, ip);
    server.begin();

     // Read KID id info, version from IDfile stored on SD card
     // Initially data is
     // Name, 8 characters user defined
     // Model, 0 or 1 for relese 0, original, or release 2 that has extra AUX terminal
     // Serial number, from label on Kid

       strcpy (KID_name, "MNKID");  // set defaults in case SD kid.info file missing/not readable
       serno = 0;
       model = 0;

       if (SD.exists("kid.txt"))
       {
             KidFile = SD.open("kid.txt");
             while(KidFile.available())
             {
                  idbuff[i] = webFile.read();
                  i++;
             }
             idbuff[i] = '\0'; 
             KidFile.close();

             i=0;
             // now parse idbuff to extract Kid static info (could have IP number defined in here too, later!)
             // nnnnnnnn,m,ssssss  where nnnnnnnn is 8 char name, M is either 0 or 1 and ssssss is serial number
             // up to 6 numeric digits
             while (idbuff[i] != ',' )
             {    KID_name[i] = idbuff[i];
                  if (++i > 7)  break;     // only want 1st 8 characters of Kid name
             }
             KID_name[i] = '\0';
             i++;  // skip comma
             if (idbuff[i] == '1') model=1;  // one character
             i++;  // skip comma
             serno = atoi(&idbuff[i]); 
       }

       Wire.begin();    // for RTC reading time, date and temperature data,
}

/*  This function reads the KID data
    It then unpacks the register unsigned int values into appropriate global floats, ints and character
    arrays. In character arrays decimal points are inserted where needed and leading zero prepended on
    fraction numbers in preparation for displaying in html page.
*/
void read_KID_data()
{
     int  i,j,k, amps;
     char temp[7];
         
  // Battery volts in float and string 
     get_Kdata(COMM_vBATT); 
     batt_volts = kid_data;
     batt_volts /= 10;   
     // record high/low battery voltages for day log
     if (batt_volts > highvolts ) highvolts = batt_volts;
     if (lowvolts == 0 || batt_volts < lowvolts ) lowvolts = batt_volts;
     
     itoa (rcbuff[1], bvstr, 10);
     i=strlen(bvstr);
     bvstr[i] = bvstr[i-1];
     bvstr[i-1] = '.';
     bvstr[i+1] = '\0';

  // Input volts in float and string
     get_Kdata(COMM_vPV); 
     in_volts = kid_data;
     in_volts /= 10;   
     itoa (kid_data, invstr, 10);   // input voltage
     i = strlen (invstr  );
     invstr  [i] = invstr  [i-1];
     invstr  [i-1] = '.';
     invstr  [i+1] = '\0';

  // Output amps in float and string (note NOT wbjr amps but amps from Kid)
     get_Kdata(COMM_iBATT);     // iBATT is really Kid output current, true battery current is WBjr current   
     out_amps = kid_data;
     out_amps  /=10;
     itoa (kid_data, oastr, 10);    // output amps
     i = strlen (oastr);
     if ( i==1 )                          // insert leading zero if value < 1
     {
       oastr  [1]=oastr  [0];
       oastr  [0] = '0';
       i++;
     }
     oastr  [i] = oastr  [i-1];
     oastr  [i-1] = '.';
     oastr  [i+1] = '\0';

  // Kwh as string
     get_Kdata(COMM_KWH);
     itoa (kid_data, kwhstr  , 10);   // kwh
     i = strlen (kwhstr);
     if (i==1)              // insert leading zero if < 0
     {     kwhstr  [1] = kwhstr  [0];
           kwhstr  [0] = '0';
           i++;
     }
     kwhstr  [i] = kwhstr  [i-1];
     kwhstr  [i-1] = '.';
     kwhstr  [i+1] = '\0';
 
  // Create string output line with day's peak Watts reading and timestamp it.
  //   get_Kdata(COMM_wATTS);  // doesn't exist in enum list so calculate vBATT * iBATT
  //   watts   = rcbuff[2];
     watts = out_amps * batt_volts;
     if (highwatts < watts   )
     {
        highwatts = watts;
        itoa (highwatts  , peakpstr  , 10);    // peak wattage from Classic today
        // can't save peak power time as no clock :-\
       
        itoa(ctime_hrs , temp, 10);            // save time of peak power
        strcpy ( ctimestr , temp );            // build string ctimestr by writing ctime hours then
                                               // ctime minutes then ctime seconds separted by a :
        itoa (ctime_mins, temp, 10);           // if mins or secs re single digit then insert leading zero
        i = strlen(temp);                      // to get  hh:mm:ss or h:mm:ss (not fussed about leading
        if (i==1)                              // zero on hours part
        {
            temp[2] = temp[0];
            temp[0] = ':';
            temp[1] = '0';
         }
         else
         {
            temp[2] = temp[1];
            temp[1] = temp[0];
            temp[0] = ':';
          }
          temp[3] = '\0';
          strcat (ctimestr  , temp);   
         
         itoa (ctime_secs  , temp, 10);
         i = strlen(temp);
         if (i==1)
         {
            temp[2] = temp[0];
            temp[0] = ':';
            temp[1] = '0';
         }
         else
         {
            temp[2] = temp[1];
            temp[1] = temp[0];
            temp[0] = ':';
          }
          temp[3] = '\0';
          strcat (ctimestr, temp);     // now have string ctimestr for displaying
     }

  // Charge state
     get_Kdata(COMM_batteryStage);   
     cstate = kid_data;

  // Load state (on, off, IDLE, OVERCURRENT)
     get_Kdata(COMM_loadSTATE);
     loadstate = kid_data;

  // get battery amps from ACS758 into batt_amps
     get_BattAmps();
     
  // Battery temperature string in degrees C
     get_Kdata(COMM_batteryTemp);
     batt_temp = kid_data;   
     if (kid_data > 100 || kid_data == 0) // if its a crazy over 100 deg C measurement then likely no BTS present
     {
         btpstr[0] = '2';   // set to default 25c
         btpstr[1] = '5';
         btpstr[2] = '\0'; 
     }
     else
     {
         itoa (kid_data, btpstr, 10);    // need to properly test if BTS present
         i = strlen(btpstr);
         btpstr[i] = btpstr[i-1];
         btpstr[i-1] = '.';
         btpstr[i+1] = '\0';
     }
 
  // FET temperature string
     get_Kdata(COMM_fetTEMP); 
     itoa (kid_data, ftpstr  , 10);
     i = strlen (ftpstr  );
     ftpstr  [i] = ftpstr  [i-1];
     ftpstr  [i-1] = '.';
     ftpstr  [i+1] = '\0';



... continued in part 2
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

#5
Kid webserver part 2


/*     
  // PCB temperature string
     itoa (kid_data, ptpstr  , 10);
     i = strlen (ptpstr  );
     ptpstr  [i] = ptpstr  [i-1];
     ptpstr  [i-1] = '.';
     ptpstr  [i+1] = '\0';
*/
/*   
  // Float times
     fl_secs = readRegs[23];      //4137 floatTimeofdayseconds
     if (fl_secs   >= 3600)
     {
         fl_hrs   = (int)fl_secs  /3600;
         fl_secs   -= (int) fl_hrs  *3600;
     }
     else fl_hrs   = 0;
     if (fl_secs   >= 60 )
     { 
         fl_mins   = (int)fl_secs  /60;
         fl_secs   -= (int) fl_mins  *60;
     }
     else fl_mins  =0;
     // Absorb times   
     absorb_left = abs_secs   = readRegs[24];      // 4138 AbsorbTime seconds left (downcounter)
     if (abs_secs   >= 3600)
     {
         abs_hrs   = (int)abs_secs  /3600;
         abs_secs   -= (int) abs_hrs  *3600;
     }
     else abs_hrs   = 0;
     if (abs_secs   >= 60 )
     {
         abs_mins   = (int)abs_secs  /60;
         abs_secs   -= (int) abs_mins  *60;
     }
     else abs_mins   = 0;
*/
   
   // set up state string  includes abs/fl/eq time elapsed/to go time
   
     switch (cstate)  // cstate is battery charging state KID is in
     {
            case 0:
              strcpy( statestr  , "RESTING");
              break;
            case 3:
              strcpy ( statestr  , "ABSORB - time left ");
              // string for Absorb time left hh:mm:ss or h:mm:ss inserting leading zero in
              // mm and ss if needed
              itoa(abs_hrs  , temp, 10);
              strcat ( statestr  , temp );             
              itoa (abs_mins  , temp, 10);
              i = strlen(temp);
              if (i==1)
              {
                temp[2] = temp[0];
                temp[0] = ':';
                temp[1] = '0';
              }
              else
              {
                temp[2] = temp[1];
                temp[1] = temp[0];
                temp[0] = ':';
              }
              temp[3] = '\0';
              strcat (statestr  , temp);     
           
              itoa(abs_secs  , temp, 10);
              i = strlen(temp);
              if (i==1)
              {
                temp[2] = temp[0];
                temp[0] = ':';
                temp[1] = '0';
              }
              else
              {
                temp[2] = temp[1];
                temp[1] = temp[0];
                temp[0] = ':';
              }
              temp[3] = '\0';

              strcat (statestr  , temp);
              break;
            case 4:
              strcpy (statestr  , "BULKMPPT");
              break;
            case 5:
              strcpy ( statestr  , "FLOAT - Total time ");
             // string for total float time in hh:mm:ss or h:mm:ss  inserting leading
             // zero in mm and ss if needed
              itoa(fl_hrs  , temp, 10);
              strcat ( statestr  , temp );
              itoa (fl_mins  , temp, 10);
              i = strlen(temp);
              if (i==1)
              {
                temp[2] = temp[0];
                temp[0] = ':';
                temp[1] = '0';
              }
              else
              {
                temp[2] = temp[1];
                temp[1] = temp[0];
                temp[0] = ':';
              }
              temp[3] = '\0';
              strcat (statestr  , temp);     
           
              itoa(fl_secs  , temp, 10);
              i = strlen(temp);
              if (i==1)
              {
                temp[2] = temp[0];
                temp[0] = ':';
                temp[1] = '0';
              }
              else
              {
                temp[2] = temp[1];
                temp[1] = temp[0];
                temp[0] = ':';
              }
              temp[3] = '\0';
              strcat (statestr  , temp);
              break;
 
            case 6:
              strcpy ( statestr  , "FLOAT MPPT");
              break;
            case 7:             
              // string for total Equalise time hh:mm:ss or h:mm:ss   
              // inserting leading zero in mm and ss if needed       
/*              eq_secs   = readRegs[28];      // eq time in seconds
              if (eq_secs   >= 3600)
              {
                  eq_hrs   = (int)eq_secs  /3600;
                  eq_secs   -= (int) eq_hrs  *3600;
              }
              else eq_hrs   = 0;
              if (eq_secs   >= 60 )
              {   
                  eq_mins   = (int)eq_secs  /60;
                  eq_secs   -= (int) eq_mins  *60;
              }
              else eq_mins  =0;
*/   
              strcpy ( statestr  , "EQUALIZE - Time left ");
              itoa(eq_hrs  , temp, 10);
              strcat ( statestr  , temp );
                     
              itoa (eq_mins  , temp, 10);
              i = strlen(temp);
              if (i==1)
              {
                temp[2] = temp[0];
                temp[0] = ':';
                temp[1] = '0';
              }
              else
              {
                temp[2] = temp[1];
                temp[1] = temp[0];
                temp[0] = ':';
              }
              temp[3] = '\0';
             
              strcat (statestr  , temp);     
           
              itoa(eq_secs  , temp, 10);
              i = strlen(temp);
              if (i==1)
              {
                temp[2] = temp[0];
                temp[0] = ':';
                temp[1] = '0';
              }
              else
              {
                temp[2] = temp[1];
                temp[1] = temp[0];
                temp[0] = ':';
              }
              temp[3] = '\0';

              strcat (statestr, temp);
              break;
             case 10:
              strcpy ( statestr,"HYPERVOC");
              break;
            case 18:
              strcpy ( statestr,"EQ MPPT");
              break;
            default:
              strcpy ( statestr,"RESTING");
              break;
     }

     // setup load state string
          switch (loadstate)  // loadstate is load output state KID is in
     {
            case 0:
              strcpy( load_statestr, "ON");
              break;
            case 1:
               strcpy( load_statestr, "OFF");
              break;
            case 2:
               strcpy( load_statestr, "IDLE");
              break;
            case 3:
               strcpy( load_statestr, "OVERAMPS");
              break;
            default:
              strcpy ( load_statestr,"OFF");
              break;
     }
     

     if (wbjr_soc == 0) wbjr_soc = 100;  // defaut to 100% SOC if no value can be obtained
     // need to do some simple SOC% calculation here since no WBjr data available
     // wait for Float charge stage to set SOC to 100% then process +/- Ahr by charge efficiency for +
     // (its not an exact science anyway!)
/*
  // WBjr registers

  // wbjr remote temperature
     get_Kdata(COMM_remTemp
     remt = kid_data;
     remt -=50;
     if  (remt != -50 )  // there is WBjr connected
     {
        itoa (remt, rtpstr, 10);
       
    // wbjr battery amps minus value = discharging, positive = charging, save as float and string
        get_Kdata(COMM_wbjrAmps)
        amps = kid_data;   // cast doesn't seem to work
        // wbjrRegs is unsigned int so need to make signed int before itoa conversion
        if (amps > 60000) amps -=65535;
        batt_amps = (float) amps/10;      // float  battery amps
        if (cstate == 3 ) endamps = batt_amps;  // ABSORB so set end amps for day report
        itoa (amps, wbastr, 10);          // string battery amps
        i = strlen(wbastr);
          // need to insert leading zero if <1 and > -1,  eg .3 becomes 0.3, -.2 becomes -0.2
        if (i==1 || (i==2 && wbastr[0] == '-') )
        { 
            wbastr[i] = wbastr[i-1];
            wbastr[i-1] = '0';
            i++;
        }   
        wbastr[i] = wbastr[i-1];   // now divide by 10 by inserting decimal point
        wbastr[i-1] = '.';     
        wbastr[i+1] = '\0';
       
      // wbjr battery state of charge %
        get_Kdata(COMM_battSoc);
        wbjr_soc = kid_data;
       
        // record high/low SOC % for day log
       if (wbjr_soc > high_soc ) high_soc = wbjr_soc;
       if (low_soc == 0 || wbjr_soc < low_soc ) low_soc = wbjr_soc;
       
      // wbjr net Amp hours charge
        get_Kdata(COMM_battAhr);
        wbjr_ahr = kid_data;           // Net Ah
        // record high/low net amp  hours for day log
       if (wbjr_ahr > high_ahr ) high_ahr = wbjr_ahr;
       if (low_ahr == 0 || wbjr_ahr < low_ahr ) low_ahr = wbjr_ahr;
       
      // wbjr Amp hours left in battery
        get_Kdata(COMM_ahRemain);
        wbjr_ra = kid_data;           // Remaining Ahr
        // record high/low remaining amp hours for day log
        if (wbjr_ra > high_ra ) high_ra = wbjr_ra;
        if (low_ra == 0 || wbjr_ra < low_ra ) low_ra = wbjr_ra;

     }
*/
     
     //  save a new 10 minute log file record in memory arrays 
     //  log records are stored in a set of memory arrays, enough for MAX_LOG_COUNT entries with time
     //  of 10 minutes between each  The arrays are cyclic and a log_index is
     //  kept of where the last log record is stored. They are eventually display starting at latest and
     //  going backwards down dispay screen 
     timenow = millis();
     // millis may have cycled back to zero after 50 days so reset start_count as well
     if (timenow < start_count) start_count = 0;  // so there may be one 10min+ time gap
     if ((timenow - start_count) > (unsigned long)LOG_INTERVAL)
     {
         start_count = timenow;
         if (log_index >= MAX_LOG_COUNT ) log_index=0;  // back to start of log array
         log_mins[log_index] = ctime_mins  ;         
         log_hrs[log_index] = ctime_hrs  ;
         
         // now store all current data items
         log_battvolts[log_index] = batt_volts;
         log_battamps[log_index] = batt_amps;     
         log_watts[log_index] = watts;
         log_state[log_index] = cstate  ;       
         log_involts[log_index] = in_volts  ;
         log_outamps[log_index] = out_amps  ;
         log_soc[log_index] = wbjr_soc;
         log_ahremain[log_index] = wbjr_ra;
         strcpy (log_kwh[log_index], kwhstr  );
         log_ahr[log_index] = wbjr_ahr;
         last_index = log_index;
         log_index++;
       // end of writing new log record
       // now save last KWHr string, day and month, for Day log record
       // (because Classic clears Kwh to zero at midnite and day/month moves forward
         if (last_day == 0 )last_day = ctime_day  ;
         if (last_month == 0) last_month = ctime_mth  ;
         if (ctime_hrs   > 0 ) // so as not to clear data when Classic reset times at midnie
         {                      // saved float/absorb times will reset here at 1am
             float_hrs = fl_hrs  ;        // save float time for day log
             float_mins = fl_mins  ;
             i = Absorb_Time - absorb_left;     // get abs time seconds elapsed
             i = (int) i/60;                    // in minutes
             absorb_hrs = (int) i/60;                   
             absorb_mins = (int) i - (absorb_hrs*60);    // need to revisit this for EA terminated absorb
             strcpy (lastkwhr, kwhstr  );      // for the day log report
         }

     // daylog time testing done within 10 minute log time logic to reduce unnecessary
     // testing in main loop     
     // save a day log entry for yesterday just after midnight and reset highwatts
     // value/timestamps etc.
         if ( ctime_hrs  == 0 && ctime_mins   < 10 )   // rolled over to next day
         {       
            dlog_day[dlog_index] = last_day;     // the day of the month 1-31   
            dlog_mth[dlog_index] = last_month;     // the month of the year 1-12   
         // now store day data items
            dlog_ea[dlog_index] = endamps;            // the EA vale of last ABSORB state
            strcpy (dlog_kwh[dlog_index], lastkwhr);   // day's kwh reading
            dlog_hwts[dlog_index] = highwatts;
            dlog_hbv[dlog_index] = highvolts;
            dlog_lbv[dlog_index] = lowvolts;
            dlog_hsoc[dlog_index] = high_soc;
            dlog_lsoc[dlog_index] = low_soc;       
            dlog_ham[dlog_index] = high_ra;
            dlog_lam[dlog_index] = low_ra;     
            dlog_hahr[dlog_index] = high_ahr;
            dlog_lahr[dlog_index] = low_ahr;
            dlog_flhr[dlog_index] = float_hrs;        // save float/absorb times for day log           
            dlog_flmin[dlog_index] = float_mins;
            dlog_abhr[dlog_index] = absorb_hrs;                 
            dlog_abmin[dlog_index] = absorb_mins;

         // so clear highwatts value and its datestamp
            last_weekday   = ctime_weekday  ;
            highwatts   = 0;
            ctimestr  [0] = '\0';                  // clear by nulling the string 
         // move array pointer dlog_index forward, roll back to zero if too large and reset variables
            dlast_index = dlog_index;
            dlog_index++;                             // for next dlog entry in array
            if (dlog_index >= MAX_DLOG_COUNT ) dlog_index=0;  // loop back to start of day log array
            endamps = 0;
            lowvolts = 0;
            highvolts = 0;
            low_soc = high_soc = 0;       
            high_ahr = low_ahr = 0;     
            high_ra = low_ra = 0;
            last_day = last_month = 0;           
         } // end of day logging
     }
     //end of 10min and day logging
     
}



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

dgd'

I don't see posting  this Kid Webserver stuff  as a problem. It IS related to the Midnite products and is interesting, too.

Not my call in the end, however.

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

ClassicCrazy

I think it is great - someday I hope to have time to hook up the arduino stuff and play with it.

Larry
system 1
Classic 150 , 5s3p  Kyocera 135watt , 12s Soneil 2v 540amp lead crystal for 24v pack , Outback 3524 inverter
system 2
 5s 135w Kyocero , 3s3p 270w Kyocera  to Classic 150 ,   8s Kyocera 225w to Hawkes Bay Jakiper 48v 15kwh LiFePO4 , Outback VFX 3648 inverter
system 3
KID / Brat portable

dgd

... and part 3 which is the loop(), the actual web server code and XML file creation..., and a couple of support functions


void loop()
{
  char  c;
  int   i,j,k, amps;

  get_time();        // from RTC, various time date and temperature data
  read_KID_data();
   
  // listen for incoming clients
      EthernetClient client = server.available();  // try to get client

    if (client)
    {  // got client?
        boolean currentLineIsBlank = true;
        while (client.connected())
        {
            read_KID_data();
            if (client.available())
            {   // client data available to read
                char c = client.read(); // read 1 byte (character) from client
                // buffer first part of HTTP request in HTTP_req array (string)
                // leave last element in array as 0 to null terminate string (REQ_BUF_SZ - 1)
                if (req_index < (REQ_BUF_SZ - 1)) {
                    HTTP_req[req_index] = c;          // save HTTP request character
                    req_index++;
                }
                // last line of client request is blank and ends with \n
                // respond to client only after last line received
                if (c == '\n' && currentLineIsBlank)
                {
                    // send a standard http response header
                    client.println(F("HTTP/1.1 200 OK"));
                    // remainder of header follows below, depending on if
                    // web page or XML page is requested
                    // Ajax request - send appropriate XML file
                   
                    if (StrContains(HTTP_req, "kid_inputs") || StrContains(HTTP_req, "log"))
                    {
                        // send rest of HTTP header
                        client.println(F("Content-Type: text/xml"));
                        client.println(F("Connection: keep-alive"));
                        client.println(F("<?xml version = \"1.0\" ?>"));
                        client.println();
                        if (StrContains(HTTP_req, "kid_inputs"))
                           XML_response(client);  // 1sec data XML for Canv gauges
                        else
                        {
                          if (StrContains(HTTP_req, "CLlog")) // send 10min log XML file
                             XML_CLlog(client);
                          else  // send day log XML file
                             XML_Daylog(client);
                        }
                    }
                    else
                    {  // web page or image request
                           if (StrContains(HTTP_req, "GET / ")
                                 || StrContains(HTTP_req, "GET /index.htm")
                                 || StrContains(HTTP_req, "node")
                                 || StrContains(HTTP_req, "hchart")
                                 || StrContains(HTTP_req, "hc1" ))
                           {
                             // send rest of HTTP header
                             client.println(F("Content-Type: text/html"));
                             client.println(F("Connection: keep-alive"));
                             client.println();
                             // select web page
                             if (StrContains(HTTP_req, "GET / ") ||
                                 StrContains(HTTP_req, "GET /index.htm") )
                                webFile = SD.open("index.htm");
                           // 10 minute log web page
                             if (StrContains(HTTP_req, "node14.htm")) webFile = SD.open("node14.htm");
                           // Day log web page
                             if (StrContains(HTTP_req, "node17.htm")) webFile = SD.open("node17.htm");
                           // HighChart display pages for Day Log
                             if (StrContains(HTTP_req, "hc109.htm")) webFile = SD.open("hc109.htm");
                             if (StrContains(HTTP_req, "hc110.htm")) webFile = SD.open("hc110.htm");
                             if (StrContains(HTTP_req, "hc111.htm")) webFile = SD.open("hc111.htm");
                             if (StrContains(HTTP_req, "hc112.htm")) webFile = SD.open("hc112.htm");
                             if (StrContains(HTTP_req, "hc113.htm")) webFile = SD.open("hc113.htm");
                             if (StrContains(HTTP_req, "hc114.htm")) webFile = SD.open("hc114.htm");
                           // HighChart live web page
                             if (StrContains(HTTP_req, "hchart.htm")) webFile = SD.open("hchart.htm");

                             //send web page
                             if (webFile)
                             {
                               while(webFile.available())
                               {
                                client.write(webFile.read()); // send web page to client
                               }
                              webFile.close();
                             }
                           }
                           else
                           {
                             // image file needs buffering else wy slow loading
                             if (StrContains(HTTP_req, "GET /kid.jpg"))
                             {
                               client.println(F("Content-Type: image/jpeg"));
                               client.println();
                               webFile = SD.open("kid.jpg");
                               if (webFile)
                               {
                                 byte clientBuf[64];
                                 int clientCount = 0;

                                 while(webFile.available())
                                 {
                                   clientBuf[clientCount] = webFile.read();
                                   clientCount++;
                                   if(clientCount > 63)
                                   {
                                     // Serial.println("Packet");
                                     client.write(clientBuf,64);
                                     clientCount = 0;
                                   }
                                 }  // webfile available
                               //final <64 byte cleanup packet
                                 if(clientCount > 0) client.write(clientBuf,clientCount);           
                               // close the file:
                                 webFile.close();
                               } // if webfile
                             } // if sasa.jpg
                           } //else
                       
                    }
                    // display received HTTP request on serial port (debugging only)
//                    Serial.print(HTTP_req);
                    // reset buffer index and all buffer elements to 0
                    req_index = 0;
                    StrClear(HTTP_req, REQ_BUF_SZ);
                    break;
                }
                // every line of text received from the client ends with \r\n
                if (c == '\n')
                {
                    // last character on line of received text
                    // starting new line with next character read
                    currentLineIsBlank = true;
                }
                else if (c != '\r') {
                    // a text character was received from client
                    currentLineIsBlank = false;
                }
            } // end if (client.available())
        } // end while (client.connected())
        delay(1);      // give the web browser time to receive the data
        client.stop(); // close the connection
    } // end if (client)
}

// send the XML file containing KID data values for canv gauge updates (about once per second)
void XML_response(EthernetClient cl)
{   
//    cl.println(F("<?xml version = \"1.0\" ?>"));
    cl.print("<inputs>");   
    // output stored modbus registers
    cl.print("<bamps>");    cl.print(batt_amps);    cl.print("</bamps>");
    cl.print("<bvolts>");   cl.print(batt_volts);   cl.print("</bvolts>");   
    cl.print("<cvin>");     cl.print(in_volts);  cl.print("</cvin>");
    cl.print("<cwatts>");   cl.print(watts);     cl.print("</cwatts>");
    cl.print("<camps>");    cl.print(out_amps);  cl.print("</camps>");
   
    cl.print("<clname>");   cl.print (KID_name);
    cl.print("</clname>");
    cl.print("<clmodel>");  cl.print (model);        cl.print("</clmodel>");
    cl.print("<clserno>");  cl.print (serno);        cl.print("</clserno>");
    cl.print("<clhrs>");    cl.print (ctime_hrs);    cl.print("</clhrs>");
    cl.print("<clmins>");   cl.print (ctime_mins);   cl.print("</clmins>");
   
    cl.print("<clstate>");  cl.print (statestr); cl.print("</clstate>");
    cl.print("<clkwh>");    cl.print (kwhstr);   cl.print("</clkwh>");
    cl.print("<bah>");      cl.print (wbjr_ahr);    cl.print("</bah>");
    cl.print("<bahr>");     cl.print (wbjr_ra);     cl.print("</bahr>");
    cl.print("<bsoc>");     cl.print (wbjr_soc);    cl.print("</bsoc>");
   
    cl.print("<fettemp>");  cl.print (ftpstr); cl.print("</fettemp>");
//    cl.print("<pcbtemp>");  cl.print (ptpstr); cl.print("</pcbtemp>");
    cl.print("<battemp>");  cl.print (btpstr);    cl.print("</battemp>");
    cl.print("<remtemp>");  cl.print (rtpstr);    cl.print("</remtemp>");
   
    cl.print("<loadstate>");  cl.print (load_statestr);    cl.print("</loadstate>");
    cl.print("<loadamps>");  cl.print (load_amps);    cl.print("</loadamps>");
     
    cl.print("<peakpwr>");  cl.print (peakpstr);  cl.print("</peakpwr>");
    cl.print("<peaktime>"); cl.print (ctimestr);  cl.print("</peaktime>");
 
    cl.print("</inputs>");
}

// send the XML file containing 10 minute log records
void XML_CLlog(EthernetClient cl)
{
    int i,j; 

    cl.print("<CLlog>");   
    // output stored log file values
    cl.print("<CLdata>");   
    cl.print("<clname>");   cl.print (KID_name);   cl.print("</clname>");
    cl.print("<clmodel>");  cl.print (model);    cl.print("</clmodel>");
    cl.print("<clserno>");  cl.print (serno);    cl.print("</clserno>");
    cl.print("</CLdata>");
   
    for (i=last_index,j=0; j < MAX_LOG_COUNT; i--,j++)  // for Mega2560 keep this to 48 maximum
    {
        if (i < 0 ) i = MAX_LOG_COUNT-1;
        if (log_ahr[i] == 0) break;
        cl.print("<CLdata>");
        cl.print("<HRS>");    cl.print(log_hrs[i]);       cl.print("</HRS>");
        cl.print("<MINS>");   cl.print(log_mins[i]);      cl.print("</MINS>");
        cl.print("<STATE>");  cl.print(log_state[i]);     cl.print("</STATE>");
        cl.print("<INV>");    cl.print(log_involts[i]);   cl.print("</INV>");
        cl.print("<KWH>");    cl.print(log_kwh[i]);       cl.print("</KWH>");
        cl.print("<WATTS>");  cl.print(log_watts[i]);     cl.print("</WATTS>");
        cl.print("<OUTA>");   cl.print(log_outamps[i]);   cl.print("</OUTA>");       
        cl.print("<BATTV>");  cl.print(log_battvolts[i]); cl.print("</BATTV>");
        cl.print("<BATTA>");  cl.print(log_battamps[i]);  cl.print("</BATTA>");   
        cl.print("<SOC>");    cl.print(log_soc[i]);       cl.print("</SOC>");
        cl.print("<AHREM>");  cl.print(log_ahremain[i]);  cl.print("</AHREM>");         
        cl.print("<AHR>");    cl.print(log_ahr[i]);       cl.print("</AHR>");               
        cl.print("</CLdata>");
    }
    cl.print("</CLlog>");
    delay(1);  // give browser time to receive data
}

// send the XML file containing Day log records
void XML_Daylog(EthernetClient cl)
{
     int i,j; 
    cl.print("<Daylog>");   
    // output stored log file values
    cl.print("<Daydata>");   
    cl.print("<clname>");   cl.print (KID_name);   cl.print("</clname>");
    cl.print("<clmodel>");  cl.print (model);    cl.print("</clmodel>");
    cl.print("<clserno>");  cl.print (serno);    cl.print("</clserno>");
    cl.print("</Daydata>");
       
    for (i=dlast_index,j=0; j < MAX_DLOG_COUNT; i--,j++)  // for Mega2560 keep this to 20 maximum
    {
        if (i < 0 ) i = MAX_DLOG_COUNT-1;
        if (dlog_mth[i] == 0) break;  // dont bother with empty log entries1
        cl.print("<Daydata>");
        cl.print("<MTH>");    cl.print(dlog_mth[i]);    cl.print("</MTH>");
        cl.print("<DAY>");    cl.print(dlog_day[i]);    cl.print("</DAY>");
        cl.print("<KWH>");    cl.print(dlog_kwh[i]);    cl.print("</KWH>");
        cl.print("<ABHR>");   cl.print(dlog_abhr[i]);   cl.print("</ABHR>");
        cl.print("<ABMIN>");  cl.print(dlog_abmin[i]);  cl.print("</ABMIN>");
        cl.print("<FLHR>");   cl.print(dlog_flhr[i]);   cl.print("</FLHR>");
        cl.print("<FLMIN>");  cl.print(dlog_flmin[i]);  cl.print("</FLMIN>");
        cl.print("<EA>");     cl.print(dlog_ea[i]);     cl.print("</EA>");       
        cl.print("<HBV>");    cl.print(dlog_hbv[i]);    cl.print("</HBV>");   
        cl.print("<LBV>");    cl.print(dlog_lbv[i]);    cl.print("</LBV>");
        cl.print("<HWTS>");   cl.print(dlog_hwts[i]);   cl.print("</HWTS>");         
        cl.print("<HSOC>");   cl.print(dlog_hsoc[i]);   cl.print("</HSOC>");
        cl.print("<LSOC>");   cl.print(dlog_lsoc[i]);   cl.print("</LSOC>");               
        cl.print("<HAM>");    cl.print(dlog_ham[i]);    cl.print("</HAM>");
        cl.print("<LAM>");    cl.print(dlog_lam[i]);    cl.print("</LAM>");               
        cl.print("<HAHR>");   cl.print(dlog_hahr[i]);   cl.print("</HAHR>");
        cl.print("<LAHR>");   cl.print(dlog_lahr[i]);   cl.print("</LAHR>");                     
        cl.print("</Daydata>");
    }
    cl.print("</Daylog>");
    delay(1);   // give browser time to receive data
}

// sets every element of str to 0 (clears array)
void StrClear(char *str, char length)
{
    for (int i = 0; i < length; i++) {
        str[i] = 0;
    }
}

// searches for the string sfind in the string str
// returns 1 if string found
// returns 0 if string not found
char StrContains(char *str, char *sfind)
{
    char found = 0;
    char index = 0;
    char len;

    len = strlen(str);
   
    if (strlen(sfind) > len) {
        return 0;
    }
    while (index < len) {
        if (str[index] == sfind[found]) {
            found++;
            if (strlen(sfind) == found) {
                return 1;
            }
        }
        else {
            found = 0;
        }
        index++;
    }
    return 0;
}

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

and last part 4 containing support functions for RTC, eeprom etc.



// Write one byte to the EEPROM
  void writeEEPROM(uint8_t EEPROMaddress, uint8_t page, uint8_t entry, uint8_t data)
  {
    // Construct EEPROM address from page and entry input
    // There are 128 pages and 32 entries (bytes) per page
    // EEPROM address are 16-bit (2 byte) address where the MS four bits are zero (or don't care)
    // the next seven MS bits are the page and the last five LS bits are the entry location on the page
    uint16_t pageEntryAddress = (uint16_t) ((uint16_t) page << 5) | entry;
    uint8_t  highAddressByte  = (uint8_t) (pageEntryAddress >> 8);  // byte with the four MSBits of the address
    uint8_t  lowAddressByte   = (uint8_t) (pageEntryAddress - ((uint16_t) highAddressByte << 8)); // byte with the eight LSbits of the address
   
    Wire.beginTransmission(EEPROMaddress);    // Initialize the Tx buffer
    Wire.write(highAddressByte);              // Put slave register address 1 in Tx buffer
    Wire.write(lowAddressByte);               // Put slave register address 2 in Tx buffer
    Wire.write(data);                         // Put data in Tx buffer
    delay(10);                                // maximum write cycle time per data sheet
    Wire.endTransmission();                   // Send the Tx buffer
    delay(10);
  }

// ZS-042 RTC card, eeprom read/write byte routines

// Read one byte from the EEPROM
    uint8_t readEEPROM(uint8_t EEPROMaddress, uint8_t page, uint8_t entry)
{
    uint16_t pageEntryAddress = (uint16_t) ((uint16_t) page << 5) | entry;
    uint8_t  highAddressByte  = (uint8_t) (pageEntryAddress >> 8);  // byte with the four MSBits of the address
    uint8_t  lowAddressByte   = (uint8_t) (pageEntryAddress - ((uint16_t)highAddressByte << 8)); // byte with the eight LSbits of the address

    uint8_t data;                                    // `data` will store the register data   
    Wire.beginTransmission(EEPROMaddress);           // Initialize the Tx buffer
    Wire.write(highAddressByte);                     // Put slave register address 1 in Tx buffer
    Wire.write(lowAddressByte);                      // Put slave register address 2 in Tx buffer
    Wire.endTransmission(false);                     // Send the Tx buffer, but send a restart to keep connection alive
    Wire.requestFrom(EEPROMaddress, (uint8_t) 1);    // Read one byte from slave register address
    delay(10);                                       // maximum write cycle time per data sheet
    data = Wire.read();                              // Fill Rx buffer with result
    delay(10);
    return data;                                     // Return data read from slave register
}

  uint8_t readSeconds()
  {
    uint8_t data;
    data = readByte(DS3231_ADDRESS, 0x00);
    return ((data >> 4) * 10) + (data & 0x0F);
  }
   
  uint8_t readMinutes()
  {
    uint8_t data;
    data = readByte(DS3231_ADDRESS, 0x01);
    return ((data >> 4) * 10) + (data & 0x0F);
  }
 
  uint8_t readHours()
  {
    uint8_t data;
    data = readByte(DS3231_ADDRESS, 0x02);
    return (((data & 0x10) >> 4) * 10) + (data & 0x0F);
  }
 
  boolean readPM()
  {
    uint8_t data;
    data = readByte(DS3231_ADDRESS, 0x02);
    return (data & 0x20);
  }
 
  uint8_t readDay()
  {
    uint8_t data;
    data = readByte(DS3231_ADDRESS, 0x03);
    return data;
  }
 
  uint8_t readDate()
  {
    uint8_t data;
    data = readByte(DS3231_ADDRESS, 0x04);
    return ((data >> 4) * 10) + (data & 0x0F);
  }
  uint8_t readMonth()
  {
    uint8_t data;
    data = readByte(DS3231_ADDRESS, 0x05);
    return (((data & 0x10) >> 4) * 10) + (data & 0x0F);
  }

  uint8_t readYear()
  {
    uint8_t data;
    data = readByte(DS3231_ADDRESS, 0x06);
    return ((data >> 4) * 10) + (data & 0x0F);
  }

   
float readTempData()
{
     uint8_t rawData[2];  // x/y/z gyro register data stored here
     readBytes(DS3231_ADDRESS, 0x11, 2, &rawData[0]);  // Read the two raw data registers 0x11/0x12 sequentially into data array
     return ((float) ((int8_t)rawData[0])) + ((float)((int8_t)rawData[1] >> 6) * 0.25) ;  // Construct the temperature in degrees C
}


void writeByte(uint8_t address, uint8_t subAddress, uint8_t data)
{
  Wire.beginTransmission(address);  // Initialize the Tx buffer
  Wire.write(subAddress);           // Put slave register address in Tx buffer
  Wire.write(data);                 // Put data in Tx buffer
  Wire.endTransmission();           // Send the Tx buffer
}

uint8_t readByte(uint8_t address, uint8_t subAddress)
{
  uint8_t data; // `data` will store the register data   
  Wire.beginTransmission(address);         // Initialize the Tx buffer
  Wire.write(subAddress);                  // Put slave register address in Tx buffer
  Wire.endTransmission(false);             // Send the Tx buffer, but send a restart to keep connection alive
  Wire.requestFrom(address, (uint8_t) 1);  // Read one byte from slave register address
  data = Wire.read();                      // Fill Rx buffer with result
  return data;                             // Return data read from slave register
}

void readBytes(uint8_t address, uint8_t subAddress, uint8_t count, uint8_t * dest)

  Wire.beginTransmission(address);   // Initialize the Tx buffer
  Wire.write(subAddress);            // Put slave register address in Tx buffer
  Wire.endTransmission(false);       // Send the Tx buffer, but send a restart to keep connection alive
  uint8_t i = 0;
        Wire.requestFrom(address, count);  // Read bytes from slave register address
  while (Wire.available()) {
        dest[i++] = Wire.read(); }         // Put read results in the Rx buffer

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

#10
In all the above code there is not a mention of Modbus anywhere - as surprisingly the Kid does not support a Modbus stack for internal running data or providing data to external devices/processors via Modbus RTU
(modbus over serial port).
So one further experiment with an Arduino was to simulate a Kid modbus stack and provide a modbus RTU interface as well as ethernet modbus TCP.
This would then allow other networked reporting systems to in effect connect to the Kid for data reporting.
Most of this C++ arduino code is also now migrated over to an rPi3 system although Libmodbus is proving more challenging to configure

The existing Arduino library SimpleModbusSlave will do nicely and initial tests have shown it works well
Soon I would like to get this interfacing to Midnite's Local App and possibly, eventually,  the MyMidnite system.

Code for an Arduino NANO with ethernet wireless and serial rs232 interface that does the modbus slave, posted here soon.. still playing with getting this squeezed into the small wiring space on Kid support bracket.  This should be more feasible with the new large base that has the wider wiring/terminal compartment.

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

headgeek

Lots of us really appreciate work others have done. They help us on our own projects.