48v MPPT charger >=250v panels

Announcement - Help us fight the BOTS! Please report all spam including stuff in your inbox!

powerhey

New member
Joined
Feb 28, 2020
Messages
15
I've been looking for an MPPT charger that can move ~2 kw from panels >=250v to charge a 48v lifepo4 bank.

Are there many units that do just this task? I'm shy of the all-in-one units since my 5048GK died for no clear reason.

I think I saw a Victron one for ~$1k, but are there any for ~$500?

Thanks
 

cak

Member
Joined
Mar 14, 2021
Messages
65
https://www.altestore.com/store/cha...rs/mppt-solar-charge-controllers-c474/?page=1 Is where I look for professional type solar equipment. Most of the stuff you can find on Amazon and the like are ether more geared towards RV type applications and everything is a lot more expensive for what you get vs AltE Store. I use Outback but for some power levels Victron is cheaper. The MPPT charge controllers are rated in Amps on the output side so to figure out if it is the right size for you just multiply that Amps by your battery voltage to get KW maximum. In my case I have significantly oversized solar so at peak sun my system is charge amperage limited but it means I can get max charging for more of the day. Not all of the Charge controllers are designed for LiIon or LifePo4 but usually there are settings that can be hacked to work. On my Outback controller I disabled the bulk charge stage so it effectively works like a CC/CV controller.
 

OffGridInTheCity

Active member
Joined
Dec 15, 2018
Messages
1,746
I can endorse Midnight Classics - I run 3 x 150s for over 2 years now with no problems. I like the old fashioned, regular internet (RJ45 internet cables) for remote tools. If you go that route, I have PHP code to access the data.
 
  • Like
Reactions: cak

cak

Member
Joined
Mar 14, 2021
Messages
65
I can endorse Midnight Classics - I run 3 x 150s for over 2 years now with no problems. I like the old fashioned, regular internet (RJ45 internet cables) for remote tools. If you go that route, I have PHP code to access the data.
I might have to hit you up for that code. I have a DIY BMS on the way and would eventually like to set up everything to talk to eachother.
 

OffGridInTheCity

Active member
Joined
Dec 15, 2018
Messages
1,746
I might have to hit you up for that code. I have a DIY BMS on the way and would eventually like to set up everything to talk to eachother.
To give you and idea - here's the PHP I wrote to retrieve a lot of the values from 3 different Classics.... You need a public domain php modbus include - I could share my copy if you get that far and can't find it. There's also a Midnite document download that specifies all the registers... I could share that as well. This is read-only but could be extended to set the registers as well.

I use it to store data in my database aligned with Batrium data and also to produce this dashboard on my desktop: I use Aux1 on/off to turn my inverters on/off - e.g. the green AUTO means the inverter is on. Its 7:40am/raining so no PV yet for the day.
1632753696633.png




include_once("modbus/ModbusMaster.php");
include_once("modbus/ModbusMasterTcp.php");
include_once("modbus/ModbusMasterUdp.php");

// Classic Network
define ("MIDNIT1_NAME","Midnite1");
define ("MIDNIT1_IP","192.168.x.xxx");
define ("MIDNIT2_NAME","Midnite2");
define ("MIDNIT2_IP","192.168.x.xxx");
define ("MIDNIT3_NAME","Midnite3");
define ("MIDNIT3_IP","192.168.x.xxx");

// Classic Simple registers
define ("PV_ARRAY_VOLTAGE_DIV_BY_10","4116");
define ("PV_ARRAY_OPEN_CURCUIT_VOLTAGE_DIV_BY_10","4122");
define ("PV_ARRAY_CURRENT_DIV_BY_10","4121");
define ("BAT_VOLTAGE_DIV_BY_10","4115");
define ("BAT_CURRENT_DIV_BY_10","4117");
define ("BAT_WATTS","4119");
define ("DAILY_KWH_DIV_BY_10","4118");
define ("DAILY_AH","4125");
define ("BAT_TEMP_DIV_BY_10","4132");
define ("FET_TEMP_DIV_BY_10","4133");
define ("PCB_TEMP_DIV_BY_10","4134");
define ("MINS_NO_POWER","4135");
define ("FLOAT_TIME_SECS","4138");
define ("ABSORB_TIME_SECS","4139");
define ("AUX1_VOLT_ON_DIV_BY_10","4172");
define ("AUX1_VOLT_OFF_DIV_BY_10","4166");
define ("ABSORB_SET_VOLT_DIV_BY_10","4149");
define ("FLOAT_SET_VOLT_DIV_BY_10","4150");

// Classic Register - 2 byte, each with value
define ("CHARGE_STATE","4120");
// Classic Register -> binary string -> 2nd or 3rd bit
define ("AUX1_ON","4130");
define ("AUX1_DIVERSION_ON_OFF_AUTO","4165");
// Classic Whizbang Jr
define ("WBJR_AH_NET","4110");
define ("WBJR_RAW_CURRENT","4362");
define ("WBJR_AMPS","4371");


function get_midnite_values($ip,&$retMsg,$debug=false) {
$ret = array();

$ret['SnapshotId'] = 0; // for MSSQL Classic table insert

if($ip == MIDNIT1_IP) $ret['ClassicName'] = MIDNIT1_NAME;
if($ip == MIDNIT2_IP) $ret['ClassicName'] = MIDNIT2_NAME;
if($ip == MIDNIT3_IP) $ret['ClassicName'] = MIDNIT3_NAME;
$ret['ClassicIP'] = $ip;

$ret['ChargeState'] = get_midnite_register($ip,CHARGE_STATE,2,$retMsg);
$ret['PVArrayVoltage'] = get_midnite_register($ip,PV_ARRAY_VOLTAGE_DIV_BY_10,1,$retMsg);
$ret['PVArrayCurrent'] = get_midnite_register($ip,PV_ARRAY_CURRENT_DIV_BY_10,1,$retMsg);
$ret['BatteryVoltage'] = get_midnite_register($ip,BAT_VOLTAGE_DIV_BY_10,1,$retMsg);
$ret['BatteryCurrent'] = get_midnite_register($ip,BAT_CURRENT_DIV_BY_10,1,$retMsg);
$ret['BatteryWatts'] = get_midnite_register($ip,BAT_WATTS,0 ,$retMsg);
$ret['DailyKwh'] = get_midnite_register($ip,DAILY_KWH_DIV_BY_10,1,$retMsg);
$ret['DailyAh'] = get_midnite_register($ip,DAILY_AH,0,$retMsg);
$ret['BatteryTempC'] = get_midnite_register($ip,BAT_TEMP_DIV_BY_10,1,$retMsg);
$ret['FETTempC'] = get_midnite_register($ip,FET_TEMP_DIV_BY_10,1,$retMsg);
$ret['PCBTempC'] = get_midnite_register($ip,PCB_TEMP_DIV_BY_10,1,$retMsg);
$ret['MinsNoPower'] = get_midnite_register($ip,MINS_NO_POWER,0,$retMsg);
$ret['FloatTime'] = get_midnite_register($ip,MINS_NO_POWER,0,$retMsg);
$ret['AbsorbTime'] = get_midnite_register($ip,MINS_NO_POWER,0,$retMsg);
$ret['Aux1On'] = get_midnite_register($ip,AUX1_ON,3,$retMsg);
$ret['Aux1VoltOn'] = get_midnite_register($ip,AUX1_VOLT_ON_DIV_BY_10,1,$retMsg);
$ret['Aux1VoltOff'] = get_midnite_register($ip,AUX1_VOLT_OFF_DIV_BY_10,1,$retMsg);
$ret['AbsorbSetVolt'] = get_midnite_register($ip,ABSORB_SET_VOLT_DIV_BY_10,1,$retMsg);
$ret['FloatSetVolt'] = get_midnite_register($ip,FLOAT_SET_VOLT_DIV_BY_10,1,$retMsg);
$ret['Aux1DivOnOffAuto'] = get_midnite_register($ip,AUX1_DIVERSION_ON_OFF_AUTO,4,$retMsg);
$ret['WzbjNetAh'] = get_midnite_register($ip,WBJR_AH_NET,5,$retMsg);
$ret['WzbjAmps'] = get_midnite_register($ip,WBJR_AMPS,6,$retMsg);
return($ret);
}





// Return the register's value. On error, return -1 and $retMsg != "Success";
// $mode = 0 - return simple value
// $mode = 1 - return simple value / 10
// $mode = 2 - return custom charge state string from 2 byte
// $mode = 3 - return custom binary bit for Aux1 ON/OFF
function get_midnite_register($ip,$regNum,$mode,&$retMsg) {
$retMsg = "Success";
try {

$modbus = new ModbusMaster($ip, "TCP");
$recData = $modbus->readMultipleRegisters(0, ($regNum - 1), 1); // $regNum + 1, so we minus a 1
// simple value
if($mode == 0) {
$ret = ($recData[0] <<8) + $recData[1];
return($ret);
// simple value / 10
} else if ($mode == 1) {
$ret = ($recData[0] <<8) + $recData[1];
$ret = substr($ret,0,strlen($ret)-1).".".substr($ret,strlen($ret)-1,1); // insert a decimal point '.' 1 digit from the right
// "6505.1" occurs when sensor is not hooked up to classic - e.g. Midnite2 as an example.
if(($regNum == BAT_TEMP_DIV_BY_10) && ($ret == "6505.1")) { $ret = 0; }
// charge state
} else if ($mode == 2) {
if( $recData[0] == 0) { $ret1 = "(Low Power)"; }
else if($recData[0] == 3) { $ret1 = "ABSORB"; }
else if($recData[0] == 4) { $ret1 = "BULK"; }
else if($recData[0] == 5) { $ret1 = "FLOAT"; }
else if($recData[0] == 6) { $ret1 = "FLOATMPPT"; }
else if($recData[0] == 7) { $ret1 = "EQUALIZE"; }
else if($recData[0] == 10) { $ret1 = "(HyperVoc)"; }
else if($recData[0] == 18) { $ret1 = "(EqMppt)"; }
else { $ret2 = "(Unknown)"; $retMsg = "Unknown charge state high byte=".$recData[0]; return(-1); }

if( $recData[1] == 0) { $ret2 = "Resting"; }
else if($recData[1] == 1) { $ret2 = "Waking"; }
else if($recData[1] == 2) { $ret2 = "Waking"; }
else if($recData[1] == 3) { $ret2 = "MPPT"; }
else if($recData[1] == 4) { $ret2 = "MPPT"; }
else if($recData[1] == 6) { $ret2 = "MPPT"; }
else { $ret = "Unknown"; $retMsg = "Unknown charge state low byte=".$recData[1]; return(-1); }

return($ret1." ".$ret2);
} else if ($mode == 3) {
// Aux1 ON = 0x00004000 = 0100 0000 0000 0000
$ret = 0; // off
$bh = str_pad(decbin($recData[0]), 8, 0, STR_PAD_LEFT);
$bl = str_pad(decbin($recData[1]), 8, 0, STR_PAD_LEFT);
$hilow=$bh.$bl;
if($hilow[1] == 1) $ret = 1;
return($ret);

} else if ($mode == 4) {
// AUX 1 Off – Auto – On (Extracted/Encoded as Aux12Function bits 6,7)
// Aux1 Off = 0 // Aux 1 output is forced OFF (0 Volts)
// Aux1 Auto = 1 // Aux 1 operates automatically as defined in Aux1Funtion
// Aux1 On = 2 // Aux 1 output is forced ON (~14 Volts)
// Example in this function (Off): 0100000000000001
// (Auto): 0100000001000001
// (On): 0100000010000001
// ----------------
// Bit #: 76543210
// Str Index: 0123456789

$ret = "Error"; // off
$bh = str_pad(decbin($recData[0]), 8, 0, STR_PAD_LEFT);
$bl = str_pad(decbin($recData[1]), 8, 0, STR_PAD_LEFT);
$hilow=$bh.$bl;
if(($hilow[8] == 0) && ($hilow[9] == 0)) { return("off"); }
if(($hilow[8] == 0) && ($hilow[9] == 1)) { return("auto"); }
if(($hilow[8] == 1) && ($hilow[9] == 0)) { return("on"); }
$retMst = "Unknown status at bits 6,7 from the right in binary string: $hilow";
return("Error");
} else if ($mode == 5) { // WbJrAmpHourNET (([4370] << 16) + [4369]) Amp Hours NET (signed) Whizbang Jr. Net amp hours.
$anum = ($recData[0] <<8) + $recData[1];
$anum = $anum << 16;

$recDataPrevReg = $modbus->readMultipleRegisters(0, (4109 - 1), 1); // read the previous reg number
$ret = $anum + (($recDataPrevReg[0] <<8) + $recDataPrevReg[1]);

return($ret);
} else if ($mode == 6) {
// Left hand 8 bits
$bh = str_pad(decbin($recData[0]), 8, 0, STR_PAD_LEFT);
// Right hand 8 bits
$bl = str_pad(decbin($recData[1]), 8, 0, STR_PAD_LEFT);

// Create a number from the 2 bytes
$anum = ($recData[0] <<8) + $recData[1];

// Convert the number from unsigned to signed
if($bh[0] == 1) {
$anum = - (0x010000 - $anum);
}

// Divide by 10
$ret = $anum / 10;
return($ret);

} else {
$retMsg = "mode=$mode is not recognized - cannot proceed to process register recData[0]=".$recData[0].", recData[1]=".$recData[1]." information.";
return(-1);
}
return($ret);

}
catch (Exception $e) {
$retMsg = $e;
return(-1);
}
}
 
Last edited:
Top