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.
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);
}
}