poida
Guru
Joined: 02/02/2017 Location: AustraliaPosts: 1418 |
Posted: 01:44am 06 Apr 2021 |
Copy link to clipboard |
Print this post |
|
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 |