OK, I've finally grown up.
No more proprietary equipment used or needed.
Job done.
1 x home built inverter, 3kW, based on the nanoverter controller and Madness's 6kW power board.
(and I have designed a compatible PCB for the 6kW board. Yet to be built and tested to destruction....that could be fun)
2 x home built MPPT charge controllers. These have calibrated thermistors on the battery terminals, to trim the charge voltage exactly as they warm up or cool down.
I saw no need for the inductor temp sensor but I could use a battery temp...
I can rebuild and repair the lot now. Don't even need schematics, it's all
too easy.
No more $2,700 AU inverters that last 2 years but not one month longer.
No more MPPT controllers that if you send a "Reset" command via the MODBUS interface
while the unit is connected to battery and solar, the unit will blow.
No more expensive components that provide zero support such as schematics and
product assembeld/designed to be user repairable.
(In the old days, Tektronix built oscilloscopes that were user repairable
by design and they give excellent repair information including schematics.)
The control of the 2 MPPT and the inverter is done with a Raspberry Pi.
In addition to this, there is another Arduino Nano giving the controller
the DC Load current data, streep power, inverter real power,
and a few digital I/O pins that can drive 20mA at 5V
one which is used to run the pool pump.
The control software when started up, first connects to all USB devices that are plugged into it's 4 ports. There is no requirement for a device to be in a certain USB port. It is automatically determined which device is located on what port.
The control program main loop is:
send "$" to a open USB port.
Get the result. ("i1 ...." means it's inverter #1, "m0 ..." means MPPT unit #0
"d1 ..." means it's the data-I/0 nano)
parse the rest of the response according to what device it is.
every 2 minutes send web requests to a few database servers to insert
a new row of data.
One local at home, one at work and one located in Sydney
Here is the monitor web page now. It looks the same but it's different.
Maybe a bit simpler.
The control program is written in C and uses no special drivers or libraries.
Straight C, compiles without modification under the latest and a 4 year old
version of R-Pi operating systems.
(too bad the forum CODE tags munges the tab stops..)
/*
cc m12.c -o m12
mysql> describe nudat;
+----------------------+-------------+------+-----+-------------------+-----------------------------+
| Field | Type | Null | Key | Default | Extra |
+----------------------+-------------+------+-----+-------------------+-----------------------------+
| id | int(11) | NO | PRI | NULL | auto_increment |
| vin1 | float | YES | | NULL | |
| vin2 | float | YES | | NULL | |
| vout1 | float | YES | | NULL | |
| vout2 | float | YES | | NULL | |
| iin1 | float | YES | | NULL | |
| iin2 | float | YES | | NULL | |
| hs_temp1 | float | YES | | NULL | |
| hs_temp2 | float | YES | | NULL | |
| bat_temp1 | float | YES | | NULL | |
| bat_temp2 | float | YES | | NULL | |
| total_energy1 | float | YES | | NULL | |
| total_energy2 | float | YES | | NULL | |
| hs_throttle_current1 | float | YES | | NULL | |
| hs_throttle_current2 | float | YES | | NULL | |
| inv_dcv | float | YES | | NULL | |
| inv_ac_output | float | YES | | NULL | |
| inv_oen | smallint(6) | YES | | NULL | |
| inv_pinb | smallint(6) | YES | | NULL | |
| inv_hs_temp | float | YES | | NULL | |
| inv_tor_temp | float | YES | | NULL | |
| streetpower | float | YES | | NULL | |
| loadamps | float | YES | | NULL | |
| target_volts | float | YES | | NULL | |
| battery_temp | float | YES | | NULL | |
| ts | timestamp | NO | | CURRENT_TIMESTAMP | on update CURRENT_TIMESTAMP |
| ddate | datetime | YES | | NULL | |
| loadamps_peak | float | YES | | NULL | |
| realpower | float | YES | | NULL | |
| inv_stopreason | smallint(6) | YES | | NULL | |
+----------------------+-------------+------+-----+-------------------+-----------------------------+
30 rows in set (0.01 sec)
*/
#include <stdio.h>
#include <string.h>
#include "stdio.h"
#include "stdlib.h"
#include "strings.h"
#include <signal.h>
#include <time.h>
#include <unistd.h> //Used for UART
#include <fcntl.h> //Used for UART
#include <termios.h> //Used for UART
typedef struct
{
int usb_fs;
int dev_type;
int dev_id;
} _USB_SP;
_USB_SP usb_sp[4];
typedef struct
{
float vin,vout,iin,iout,hs_temp,target_volts;
float bat_temp,total_energy,hs_throttle_current;
int id, track_mode,got_data;
} _MPPT;
_MPPT mppt[3]; // for id = 1 and id = 2 and id = 0
typedef struct
{
float dcv,hs_temp,tor_temp,ac_output,dci;
int oen,stop_reason,fan_ctl,got_data,pinb;
} _INV;
_INV inv;
typedef struct
{
float a0,a1,a2,a3,max_a0,c0,c1;
int got_data;
} _DATALOGGER;
_DATALOGGER dlog;
typedef struct
{
float vin1,vout1,iin1,iout1,hs_temp1, bat_temp1,total_energy1,hs_throttle_current1;
float vin2,vout2,iin2,iout2,hs_temp2, bat_temp2,total_energy2,hs_throttle_current2;
float inv_dcv,inv_ac_output,inv_hs_temp,inv_tor_temp;
int inv_oen, inv_stopreason, inv_pinb;
float realpower,streetpower, loadamps, target_volts, battery_temp,loadamps_peak,inv_cutoff;
} _NUDATA;
_NUDATA nudata;
typedef struct
{
float loadamps;
float invpeakcurrent;
float array2_amps;
float realpower;
float streetpower;
float bv,tv,cc,av,ac,op,svmp,swoc,swpm,bt,hst,chargestate,ledstate, vcutoff, vcutin,tscc,tsbv,tshst,tsbt,tspwm;
int whr;
} _datah;
typedef struct
{
float vcutoff,vcutin;
int i; // read current switch status ON=1 OFF=0
float co_v1 ;
float co_a1 ;
float co_v2 ;
float co_a2 ;
int ioverride;
int icmd;
int got_info;
int c;
} _invctl;
_invctl invctl;
_datah datah;
char dstr[1000];
char tdstr[1000];
time_t rawtime;
struct tm tm;
int d_nano;
void serial_write(char *s,int i)
{
int count;
if (i == -1) return;
if (usb_sp[i].usb_fs != -1)
{
count = write(usb_sp[i].usb_fs, s,strlen(s));
if (count < 0)
{
printf("UART TX error\n");
usb_sp[i].usb_fs = -1;
}
}
}
void init_usb(int i)
{
// for all 4 USB ports, try to open as a file stream
// dev_type = -1 when not used, = 1 for inverter, = 2 for mppt
// usb_fs = file descriptor, -1 if not used.
// with the r-pi, the device names are /dev/ttyUSBx when x = 0 to 3
//
char portname[20];
struct termios options;
if (usb_sp[i].usb_fs != -1)
close(usb_sp[i].usb_fs);
usb_sp[i].usb_fs = -1;
usb_sp[i].dev_type = -1;
//
// try to open it
//
sprintf(portname,"/dev/ttyUSB%1d",i);
//printf(" trying to open dev %s\n",portname);
usb_sp[i].usb_fs = open(portname, O_RDWR | O_NOCTTY | O_NDELAY); //Open in non blocking read/wr$
if (usb_sp[i].usb_fs == -1)
{
printf("\nError - Unable to open %s",portname);
}
else
{
tcgetattr(usb_sp[i].usb_fs, &options);
options.c_cflag = B9600 | CS8 | CLOCAL | CREAD; // baud rate
options.c_iflag = IGNPAR | ICRNL;
//options.c_cflag |= CRTSCTS; // RTS/CTS Flow Control
options.c_oflag = 0;
options.c_lflag = 0;
tcflush(usb_sp[i].usb_fs, TCIFLUSH);
tcsetattr(usb_sp[i].usb_fs, TCSANOW, &options);
printf("\n%s opened OK",portname);
}
}
void get_data(int i)
{
// determine what type of device, get id, then get data for that device
char buf[100],*ptr;
int n,j,dev_type,dev_id;
n=-1;
j=0;
while((n < 5) && (j++ < 20))
{
serial_write("$\n\r",i); // send '$' <cr,lf> to get the running data from a device from the USB tty port
usleep(500000);
memset(buf,0,100);
n = read(usb_sp[i].usb_fs,buf,99);
}
if (n > 0)
{
//printf("\n%s",buf);fflush(stdout);
if (buf[0] == 'd')
{
ptr = strtok(buf," "); // go past "d1" datalogger, id=1 by default
ptr = strtok(NULL," "); if(ptr != NULL) dlog.a0 = atof(ptr);
ptr = strtok(NULL," "); if(ptr != NULL) dlog.a1 = atof(ptr);
ptr = strtok(NULL," "); if(ptr != NULL) dlog.a2 = atof(ptr);
ptr = strtok(NULL," "); if(ptr != NULL) dlog.a3 = atof(ptr);
ptr = strtok(NULL," "); if(ptr != NULL) dlog.c0 = atof(ptr);
ptr = strtok(NULL," "); if(ptr != NULL) dlog.c1 = atof(ptr);
ptr = strtok(NULL," "); if(ptr != NULL) dlog.max_a0 = atof(ptr);
dlog.got_data = 1;
d_nano = i;
}
if (buf[0] == 'i')
{
ptr = strtok(buf," "); // go past "i1" inverter, id=1 by default
ptr = strtok(NULL," "); if(ptr != NULL) inv.oen = atoi(ptr);
ptr = strtok(NULL," "); if(ptr != NULL) inv.stop_reason = atoi(ptr);
ptr = strtok(NULL," "); if(ptr != NULL) inv.pinb = atoi(ptr);
ptr = strtok(NULL," "); if(ptr != NULL) inv.dcv = atof(ptr);
ptr = strtok(NULL," "); if(ptr != NULL) inv.ac_output = atof(ptr);
ptr = strtok(NULL," "); if(ptr != NULL) inv.tor_temp = atof(ptr);
ptr = strtok(NULL," "); if(ptr != NULL) inv.hs_temp = atof(ptr);
inv.got_data = 1;
}
if (buf[0] == 'm')
{
j = buf[1] - '0'; // permit mmpt id from 0 to 2 inclusive
printf("\nj=%d",j); fflush(stdout);
ptr = strtok(buf," "); // go past "m1"
ptr = strtok(NULL," "); if(ptr != NULL) mppt[j].track_mode = atoi(ptr);
ptr = strtok(NULL," "); if(ptr != NULL) mppt[j].vin = atof(ptr);
ptr = strtok(NULL," "); if(ptr != NULL) mppt[j].vout = atof(ptr);
ptr = strtok(NULL," "); if(ptr != NULL) mppt[j].iin = atof(ptr);
ptr = strtok(NULL," "); if(ptr != NULL) mppt[j].iout = atof(ptr);
ptr = strtok(NULL," "); if(ptr != NULL) mppt[j].hs_throttle_current = atof(ptr);
ptr = strtok(NULL," "); if(ptr != NULL) mppt[j].hs_temp = atof(ptr);
ptr = strtok(NULL," "); if(ptr != NULL) mppt[j].bat_temp = atof(ptr);
ptr = strtok(NULL," "); if(ptr != NULL) mppt[j].total_energy = atof(ptr);
ptr = strtok(NULL," "); if(ptr != NULL) mppt[j].target_volts = atof(ptr);
mppt[j].got_data = 1;
}
}
}
void zero_datah()
{
datah.tsbv = datah.tscc = datah.tshst = 0.0;
datah.tsbt = datah.tspwm = datah.tscc = 0.0;
datah.bv = datah.tv = datah.cc = datah.av = 0.0;
datah.ac = datah.svmp = datah.swoc = datah.swpm = 0.0;
datah.bt = datah.hst = 0.0;
datah.chargestate = 0.0;
datah.ledstate = 0.0;
}
void zero_mppt(int i)
{
mppt[i].track_mode=0;
mppt[i].vin=mppt[i].vout=mppt[i].iin=mppt[i].iout=0.0;
mppt[i].hs_temp=mppt[i].bat_temp=mppt[i].target_volts = 0.0;
mppt[i].hs_throttle_current=mppt[i].total_energy=0.0;
}
void massage_data()
{
float bt;
// obtain calibrated values, assign to particular mysql fields..
if (dlog.got_data == 1)
{
// calc current sensor values, streetpower
datah.loadamps = nudata.loadamps = -(float)(dlog.a0 - 507) * 0.281; // was 0.1176, 0.132, 511 offset
datah.invpeakcurrent = nudata.loadamps_peak = -(float)(dlog.max_a0 - 507) * 0.281; // was 0.1176, 0.132, 511 offset
datah.realpower = nudata.realpower = dlog.c1 * 30.0;
datah.streetpower = nudata.streetpower = dlog.c0 * 30.0;
datah.whr = dlog.c0;
}
if (mppt[0].got_data == 1) // mppt id=0 is th eleft hand one, no bat temp
{ // so don't copy into datah.bt
datah.bv = nudata.vout1 = mppt[0].vout;
datah.cc = nudata.iout1 = mppt[0].iout;
datah.hst = nudata.hs_temp1 = mppt[0].hs_temp;
datah.bt = nudata.bat_temp1 = mppt[0].bat_temp;
nudata.total_energy1 = mppt[0].total_energy;
nudata.hs_throttle_current1 = mppt[0].hs_throttle_current;
nudata.vin1 = mppt[0].vin;
nudata.iin1 = mppt[0].iin;
}
if (mppt[1].got_data == 1)
{
datah.tsbv = nudata.vout2 = mppt[1].vout;
datah.tscc = nudata.iout2 = mppt[1].iout;
datah.tshst = nudata.hs_temp2 = mppt[1].hs_temp;
datah.bt = nudata.bat_temp2 = mppt[1].bat_temp;
nudata.total_energy2 = mppt[1].total_energy;
nudata.hs_throttle_current2 = mppt[1].hs_throttle_current;
nudata.vin2 = mppt[1].vin;
nudata.iin2 = mppt[1].iin;
nudata.battery_temp = mppt[1].bat_temp;
nudata.target_volts = mppt[1].target_volts;
}
if (inv.got_data == 1)
{
nudata.inv_dcv = inv.dcv;
nudata.inv_ac_output = inv.ac_output;
nudata.inv_hs_temp = inv.hs_temp;
nudata.inv_tor_temp = inv.tor_temp;
nudata.inv_pinb = inv.pinb;
nudata.inv_oen = inv.oen;
nudata.inv_stopreason = inv.stop_reason;
}
}
void make_time_strings()
{
dstr[0] = 0;
tdstr[0] = 0;
tm = *localtime(&rawtime);
sprintf(dstr,"%4d%02d%02d",1900+tm.tm_year,1+tm.tm_mon,tm.tm_mday);
sprintf(tdstr,"%4d%02d%02d%02d%02d%02d",1900+tm.tm_year,1+tm.tm_mon,tm.tm_mday,tm.tm_hour, tm.tm_min,tm.tm_sec);
}
void insert_rows()
{
make_url_and_insert_row_old("139.180.165.109");
make_url_and_insert_row_old("smtp.bqdesign.com.au");
make_url_and_insert_row_old("localhost");
make_url_and_insert_row("139.180.165.109");
make_url_and_insert_row("smtp.bqdesign.com.au");
make_url_and_insert_row("localhost");
}
void make_url_and_insert_row_old(char *host)
{
char url[1000];
sprintf(url,"wget -q -T 10 -t 1 -O wget1 \"http://%s/mppt/nu_row.php?bv=%f&tv=%f&ts=%s&cc=%f&av=%f&ac=%f\
&svmp=%f&swoc=%f&swpm=%f&bt=%f&hst=%f&chargestate=%f&ddate=%s&dtime=%s&loadamps=%f&streetpower=%f&loadampspeak=%f&tscc=%f&realpower=%f&meterwhr=%d\""
,host,datah.bv,datah.tv,tdstr,datah.cc,datah.av,datah.ac,datah.svmp,datah.swoc,datah.swpm,datah.bt,datah.hst,
datah.chargestate,dstr,tdstr,datah.loadamps,datah.streetpower,datah.invpeakcurrent,datah.tscc,datah.realpower,datah.whr);
system(url);
}
void make_url_and_insert_row(char *host)
{
char url[1000];
sprintf(url,"wget -q -T 10 -t 1 -O wget1 \"http://%s/mppt/nudata_row.php?\
ddate=%s&vin1=%f&vout1=%f&iin1=%f&iout1=%f&hs_temp1=%f&bat_temp1=%f&total_energy1=%f&hs_throttle_current1=%f\
&vin2=%f&vout2=%f&iin2=%f&iout2=%f&hs_temp2=%f&bat_temp2=%f&total_energy2=%f&hs_throttle_current2=%f\
&inv_dcv=%f&inv_ac_output=%f&inv_hs_temp=%f&inv_tor_temp=%f&inv_oen=%d&inv_stopreason=%d&inv_pinb=%d\
&realpower=%f&streetpower=%f&loadamps=%f&target_volts=%f&battery_temp=%f&loadamps_peak=%f&inv_cutoff=%f\""
,host,tdstr,nudata.vin1,nudata.vout1,nudata.iin1,nudata.iout1,nudata.hs_temp1,nudata.bat_temp1,nudata.total_energy1,nudata.hs_throttle_current1,
nudata.vin2,nudata.vout2,nudata.iin2,nudata.iout2,nudata.hs_temp2,nudata.bat_temp2,nudata.total_energy2,nudata.hs_throttle_current2,
nudata.inv_dcv,nudata.inv_ac_output,nudata.inv_hs_temp,nudata.inv_tor_temp,nudata.inv_oen,nudata.inv_stopreason,nudata.inv_pinb,
nudata.realpower,nudata.streetpower,nudata.loadamps,nudata.target_volts,nudata.battery_temp,nudata.loadamps_peak,nudata.inv_cutoff);
system(url);
}
void get_inv_control()
{
char buf[100];
int j;
FILE *fp;
sprintf(buf,"wget -q -T 10 -t 1 -O wgetinfo \"http://localhost/mppt/get-dinfo.php\"");
system(buf);
fp = fopen("wgetinfo","r");
if (fp != NULL) fgets(buf,20,fp); invctl.vcutoff = atof(buf);
if (fp != NULL) fgets(buf,20,fp); invctl.vcutin = atof(buf);
if (fp != NULL) fgets(buf,20,fp); invctl.i = atoi(buf);
if (fp != NULL) fgets(buf,20,fp); invctl.co_v1 = atof(buf);
if (fp != NULL) fgets(buf,20,fp); invctl.co_a1 = atof(buf);
if (fp != NULL) fgets(buf,20,fp); invctl.co_v2 = atof(buf);
if (fp != NULL) fgets(buf,20,fp); invctl.co_a2 = atof(buf);
if (fp != NULL) fgets(buf,20,fp); invctl.ioverride = atoi(buf);
if (fp != NULL) fgets(buf,20,fp); invctl.icmd = atoi(buf);
if (fp != NULL) fgets(buf,20,fp); invctl.c = atoi(buf);
invctl.got_info = 1;
fclose(fp);
printf("\nvcutoff = %f, vcutin = %f i = %d\n",invctl.vcutoff,invctl.vcutin,invctl.i);
printf("v1=%f,a1=%f,v2=%f,a2 = %f\n",invctl.co_v1,invctl.co_a1,invctl.co_v2,invctl.co_a2);
printf("ioverride=%d,icmd = %d, c = %d",invctl.ioverride,invctl.icmd,invctl.c);
printf("\nget inv control() completed\n"); fflush(stdout);
}
void do_inv_control()
{
char buf[1000];
int j;
// parse control word for output pins D6, d7
if ((invctl.c & 0x02) == 0x02)
serial_write("+",d_nano); // HIGH output ..
else
serial_write("-",d_nano); // LOW output please..
serial_write("6",d_nano); // pin D6
if ((invctl.c & 0x01) == 0x01)
serial_write("+",d_nano);
else
serial_write("-",d_nano);
serial_write("5",d_nano); // pin d5
printf(" ioverride = %d",invctl.ioverride); fflush(stdout);
if (invctl.ioverride == 1)
{
printf("\n inverter override in control, set to %d ",invctl.icmd);
invctl.i = invctl.icmd;
if (invctl.icmd == 1)
printf("\n override: inverter ON"); // switch inverter ON
else
printf("\n override: inverter OFF"); // switch inverter OFF
}
else
{
printf("\n since ioverride = 0 we are here: ");
// if (bv < vcutoff)
// linear interpolate cut off voltage using inverter current from co_a1 to co_a2
// if current is less than co_a1, clamp it to co_v1
// if more than co_a2, clamp it to co_v2
// ensure v1 > v2 and a1 < a2
//
invctl.vcutoff = invctl.co_v1;
if (datah.loadamps > invctl.co_a1 && datah.loadamps < invctl.co_a2)
invctl.vcutoff = invctl.co_v1 - (invctl.co_v1 - invctl.co_v2)*((datah.loadamps - invctl.co_a1)/(invctl.co_a2 - invctl.co_a1));
if (datah.loadamps > invctl.co_a2)
invctl.vcutoff = invctl.co_v2;
printf("\ncutoff = %f, battery voltage = %f",invctl.vcutoff,datah.bv);
nudata.inv_cutoff = invctl.vcutoff;
if (datah.bv < invctl.vcutoff)
{
invctl.i = 0;
printf("\n LV - stop inverter");
}
if (datah.bv > invctl.vcutin)
{
if (invctl.i == 0)
printf("\n restart inverter");
else
printf("\n run inverter");
// else it was = 1 already so it's been running
invctl.i = 1;
}
}
// write inverter switch status
if(invctl.i == 1)
serial_write("-",d_nano); // nano D7 LOW = inverter ON
else
serial_write("+",d_nano); // D7 HIGH = inverter OFF
serial_write("7",d_nano); // pin D7
sprintf(buf,"wget -q -T 10 -t 1 -O wget1 \"http://localhost/mppt/dinfo-update.php?istatus=%d&vcutoff=%f\"",invctl.i,invctl.vcutoff);
system(buf);
fflush(stdout);
}
int main()
{
int i;
long sleeptime; // must be signed. must be.
d_nano = -1;
for(i=0; i < 4; i++)
{
usb_sp[i].usb_fs = -1;
printf("\n init ttyUSB%d",i);
init_usb(i);
sleep(5);
}
invctl.got_info = 0; // must be set = 1 for inv control code to run
while(1)
{
rawtime = time(NULL);
make_time_strings();
system("wget -q -T 10 -t 1 -O wget1 \"https://freedns.afraid.org/dynamic/update.php?R1JiNzJ4UlpCUm9iT1BSSXdwUVM6MTYwNDk5MzA=\"");
dlog.a0=dlog.a1=dlog.a2=dlog.a3=dlog.max_a0=dlog.c0=dlog.c1=0.0;
zero_mppt(0);
zero_mppt(1);
inv.dcv=inv.hs_temp=inv.tor_temp=inv.ac_output=inv.dci = 0.0;
inv.oen=inv.stop_reason=inv.fan_ctl=inv.pinb=0;
zero_datah();
for(i=0; i < 4; i++)
get_data(i);
massage_data();
get_inv_control();
do_inv_control();
make_url_and_insert_row();
sleeptime = 120 - (time(NULL) - rawtime);
if (sleeptime < 1)
{
printf("\nNO sleep needed! sleeptime = %ld",sleeptime); fflush(stdout);
}
else
{
printf("\nneed %ld seconds sleep()",sleeptime); fflush(stdout);
sleep(120 - (time(NULL) - rawtime) );
}
}
}
wronger than a phone book full of wrong phone numbers