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
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
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
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 (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 (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://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/ (http://tronixstuff.com/2014/12/01/tutorial-using-ds1307-and-ds3231-real-time-clock-modules-with-arduino/)
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
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
}
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
I think it is great - someday I hope to have time to hook up the arduino stuff and play with it.
Larry
... 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;
}
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
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
Lots of us really appreciate work others have done. They help us on our own projects.