PV Wechselrichter von MPP Solar eingebunden

Servus Andi
Inzwischen hab ich zwei PIP.
Einer mit RS232 und einen neueren mit USB. Der mit RS232 hängt direkt am IPS Rechner.
Für den mit USB hab ich mit einem uC (ESP8266) einen kleinen Umsetzer gebaut welcher mir die Anbindung per WLAN macht.
Softwareseitig verwenden beide das gleiche Protokoll.
Mein IPS läuft unter Windows, es sollte aber grundsätzlich keine nennenswerten Unterschied machen ob nun RASP oder WIN.
Und ja, ich kann die PIP komplett aus IPS raus fernsteuern bzw. natürlich die Daten loggen, plotten und weiterverarbeiten.
Die Scripte dazu sind nicht sonderlich kompliziert, allerdings ist vieles hardgecodet. Wenn du sie verwenden willst mußt zumindest alle ID’s überarbeiten.
Das WF ist sowiso individuell nach einzurichten.
Modul oder Installer hab ich keinen.

schöne Grüße
bb

hey bb, danke für die schnelle Antwort.

Ich habe nun das erste mal was von ESP8266 gelesen, sieht recht interessant aus! Ich habe gesehen, dass eine art Python unterstützt wird, hast Du evtl. damit was gemacht?
Ich habe mir geschworen und auch schon länger eingehalten, dass ich keine Projekte mehr anfangen werden, deren Aufwand ich nicht abschätzen kann :rolleyes:. In diesem Fall ist es die Schnittstelle… Mit dem oben genannten Link jedoch gibt es ein Download mit einem Perl Skript das ich evtl. schnell zum Laufen bekomme und nicht mehr sonderlich viel Aufwand reinstecken muss. Oder denkst Du das mit Deinem ESP8266 ist einfacher?

gruss Andi

Servus

Den ESP8266 programmiere ich in C mit der Arduino IDE. Die original Firmware verwende ich nicht.
Die original USB Platine wird vom PIP entfernt, und stattdessen der ESP8266 eingebaut.

Hier und auf den folgenden Seiten haben wir im PV Forum darüber diskutiert:
Erfahrungen mit Mpp-Solar: Taiwan rules! • Seite 325• Photovoltaikforum

gruß
bb

Hallo
Da nun schon mehrere Leute per PM nach den Scripten gefragt haben poste ich sie mal. Die Scipte waren nie zur Veröffentlichung gedacht waren, daher ist daher einiges an Handarbeit nötig um es zum Laufen zu bringen. Insbesondere müssen alle in den Scripten vorkommenden Variablen händisch angelegt und die ID entsprechend angepasst werden.
Es ist also mehr als cookbook dann als fertige Lösung zu betrachten.

OK, beginnen wir mit dem anlegen eines Serial Ports mit folgenden Settings:

Der Serial Port gibt seine Daten an einen Cutter mit diesen Settings weiter
serial.JPG

von dort landen sie in einer Registervariable welche ihrerseits das Script „PIPCommunication“ als Ziel definiert hat.

Hier ist dann auch schon die erste Baustelle, alle im Script verwendeten Variablen müssen händisch angelegt werden. Die ID der variablen dann im Script entsprechend einpflegen.
Je nach Gusto auch noch passende Variablenprofile zuweisen.

<?

Switch ($_IPS['SENDER'])
	{
	Default:
	   // PRINT "PW: ".$_IPS['SENDER'];
	   break;

	Case "RunScript":
	Case "Execute":
   #   $PipCommand ="QMOD";
    #  $PipCommand ="QPIGS";
	#	$PipCommand ="QPIWS";

	 #  $StringToSend=$PipCommand.calculate_common_crc16c($PipCommand).chr(0x0D);
	#	REGVar_SendText(29318 /*[PV\PIP2\PIPComunication\Register Variable]*/, $StringToSend);
	
	Case "TimerEvent":
  	   break;

	Case "Variable":
      $PipCommand = $_IPS['VALUE'];
	   $StringToSend=$PipCommand.calculate_common_crc16c($PipCommand).chr(0x0D);

	   # for any reason PIP does not accept CRC for this command.
		# CRC calculation verified with http://www.zorc.breitbandkatze.de/crc.html
		# correct CRC: 0xE2 0x0A
		# but PIP2424HS want to get 0xE2 0x0B

		if ($PipCommand == "POP02"){ 
			$StringToSend = chr(0x50).chr(0x4F).chr(0x50).chr(0x30).chr(0x32).chr(0xE2).chr(0x0B).chr(0x0D);
		}

			REGVar_SendText(29318 /*[PV\PIP2\PIPComunication\Register Variable]*/, $StringToSend);


	Case "WebFront": 	   
	Case "RegisterVariable":
    	$data = $_IPS['VALUE'];   #leading "(" and tailing "<CR>" is cut in Regvar Cutter
		$mycrc = calculate_common_crc16c(substr('('.$data,0,-2) ); # add tailing '(' and remove CRC
		IF ($mycrc == substr($data,-2)) {
		$datasets = explode(' ', $data);  // luckily datasets are already separated by " "
 		if (strlen($data) == 3) {
    		if(substr($datasets[0],0,1) == 'P') SetValue(43888 /*[PV\PIP2\PIP Daten\Status\Betriebmodus]*/,0);
			if(substr($datasets[0],0,1) == 'S') SetValue(43888 /*[PV\PIP2\PIP Daten\Status\Betriebmodus]*/,1);

			if(substr($datasets[0],0,1) == 'L') {
			   if (GetValue(34552 /*[PV\PIP2\PIP Daten\Status\Lädt vom Netz]*/)) {
			      SetValue(43888 /*[PV\PIP2\PIP Daten\Status\Betriebmodus]*/,7);
			      }
			   else {
					SetValue(43888 /*[PV\PIP2\PIP Daten\Status\Betriebmodus]*/,2);
					}
			}
			if(substr($datasets[0],0,1) == 'B') {
				if (GetValue(55123 /*[PV\PIP1\PIP Daten\Batterie\Batteriestrom (berechnet)]*/) >=0) {
					SetValue(43888 /*[PV\PIP2\PIP Daten\Status\Betriebmodus]*/,6);
					}
				else {
					SetValue(43888 /*[PV\PIP2\PIP Daten\Status\Betriebmodus]*/,3);
					}
				}
			if(substr($datasets[0],0,1) == 'F') SetValue(43888 /*[PV\PIP2\PIP Daten\Status\Betriebmodus]*/,4);
			if(substr($datasets[0],0,1) == 'H') SetValue(43888 /*[PV\PIP2\PIP Daten\Status\Betriebmodus]*/,5);
		
			if(floatval(substr($datasets[0],0,1)) == 0) {
				SetValue(59096 /*[PV\PIP2\PIP Daten\Status\Warning Status]*/,true);
				}
			else { SetValue(59096 /*[PV\PIP2\PIP Daten\Status\Warning Status]*/,false);
				};
					
		}

      if (strlen($data) == 108) {
		SetValueFloat(56990 /*[PV\PIP2\PIP Daten\Netz\Netzspannung]*/,floatval(substr($datasets[0],-5)));
      SetValue(57460 /*[PV\PIP2\PIP Daten\Netz\Netzfrequenz]*/,floatval($datasets[1]));
      SetValue(38633 /*[PV\PIP2\PIP Daten\Wechselrichter\Ausgangsspannung]*/,floatval($datasets[2]));
      SetValue(20583 /*[PV\PIP2\PIP Daten\Wechselrichter\Ausgangsfrequenz]*/,floatval($datasets[3]));
		SetValue(56087 /*[PV\PIP2\PIP Daten\Wechselrichter\Scheinleistung]*/,floatval($datasets[4]));
		SetValue(48107 /*[PV\PIP2\PIP Daten\Wechselrichter\Wirkleistung]*/,floatval($datasets[5]));
      SetValue(43847 /*[PV\PIP2\PIP Daten\Wechselrichter\Auslastung]*/,floatval($datasets[6]));

      SetValue(18842 /*[PV\PIP2\PIP Daten\Wechselrichter\Busspannung]*/,floatval($datasets[7]));
      SetValue(58966 /*[PV\PIP2\PIP Daten\Batterie\Batterie Spannung]*/,floatval($datasets[8])+0.1);
		SetValue(36077 /*[PV\PIP2\PIP Daten\Batterie\Batterie Ladestrom]*/,floatval($datasets[9]));
		SetValue(12611 /*[PV\PIP2\PIP Daten\Batterie\Batterie Ladezustand]*/,floatval($datasets[10]));

		SetValue(35455 /*[PV\PIP2\PIP Daten\Temperatur]*/,((floatval($datasets[11])/10)-32)/1.8);
		SetValue(22758 /*[PV\PIP2\PIP Daten\PV Daten\PV Strom]*/,floatval($datasets[12]));
      SetValue(41090 /*[PV\PIP2\PIP Daten\PV Daten\PV Spannung]*/,floatval($datasets[13]));
      SetValue(16415 /*[PV\PIP2\PIP Daten\Batterie\Batteriespannung SCC]*/,floatval($datasets[14]));
	   SetValue(15038 /*[PV\PIP2\PIP Daten\Batterie\Batterie Entladestrom]*/,floatval($datasets[15]));

		SetValue(34552 /*[PV\PIP2\PIP Daten\Status\Lädt vom Netz]*/,($datasets[16] & 1));
		SetValue(10814 /*[PV\PIP2\PIP Daten\Status\Lädt SCC]*/,($datasets[16] & 2));
		SetValue(54850 /*[PV\PIP2\PIP Daten\Batterie\Batterieladung]*/,($datasets[16] & 4));
		SetValue(33747 /*[PV\PIP2\PIP Daten\Status\Erhaltungsladung]*/,($datasets[16] & 8));
		SetValue(57147 /*[PV\PIP2\PIP Daten\Status\Last Status]*/,($datasets[16] & 16));
	}

 if (strlen($data) == 95) {
SetValue(13546 /*[PV\PIP2\ReChargeVoltage]*/,floatval($datasets[8]));
SetValue(12376 /*[PV\PIP2\CutoffVoltage]*/,floatval($datasets[9]));
SetValue(35632 /*[PV\PIP2\ChargeBulkVoltage]*/,floatval($datasets[10]));
SetValue(38205 /*[PV\PIP2\PipChargeFloatVoltage]*/,floatval($datasets[11]));


SetValue(44355 /*[PV\PIP2\PipOutputMode]*/,floatval($datasets[16]));
SetValue(21765 /*[PV\PIP2\PipChargeMode]*/,floatval($datasets[17]));

SetValue(42940 /*[PV\PIP2\ReDischargeVoltage]*/,floatval($datasets[22]));

#print $datasets[16];
}
} #end CRC check
 # for debug echo all datasets
/*for ($i = 0; $i < count($datasets) - 1; $i++)
        {
            echo "empfangener Datensatz: ".$datasets[$i]."
";
        }
*/
}

#----------------------------------------------------------------------------------------------
// split buffer in byte and concatenate after CRC had been calculated
function calculate_common_crc16c($buffer)
{
    $crc16c = 0x0;  // the crc initial value
    $buffer_length = strlen($buffer);
    for ($i = 0; $i < $buffer_length; $i++)
    {
        $ch = ord($buffer[$i]);
        $crc16c = update_common_crc16c($ch, $crc16c);
    }
    
    $data = (string) dechex($crc16c);
    $len = strlen($data);
    if($len % 2) {
        $data = "0".$data; //substr($data, 0, $len -1);
    }
	 return hex2bin($data);
}

// calculate the crc16c byte by byte
// $ch is the next byte and $crc16c is the result from the last call, or 0xffff initially
function update_common_crc16c($ch, $crc16c)
{
    $crc16c_polynomial = 0x1021;
    // This comment was in the code from
    // http://www.joegeluso.com/software/articles/ccitt.htm
    // Why are they shifting this byte left by 8 bits??
    // How do the low bits of the poly ever see it?
    $ch <<= 8;
    for($i = 0; $i < 8; $i++)
    {
        if (($crc16c ^ $ch) & 0x8000)
        {
            $xor_flag = true;
        }
        else
        {
            $xor_flag = false;
        }
        $crc16c = $crc16c << 1;
        if ($xor_flag)
        {
            $crc16c = $crc16c ^ $crc16c_polynomial;
        }
        $ch = $ch << 1;
    }
    // mask off (zero out) the upper two bytes
    $crc16c = $crc16c & 0x0000ffff;
    return $crc16c;
}
?>

Das obige Script erledigt zwei Dinge. Einerseits nimmt es die Daten welche der Wechselrichter sendet aus der Registervariable entgegen, zerlegt diese und schreibt die Werte in entsprechende Variablen.

Andererseits wird es von der Variable „CommandtoPip“ getriggert. Diese „CommandtoPip“ Variable wird ihrerseits zyklisch durch ein weiteres Script „PIP Command“ oder per Webfront mit Steuerkommandos für den Wechselrichter beschrieben.
Das „PipCommunication“ Script nimmt diese Klartextkommandos entgegen, berechnet den CRC und schickt sie an den Wechselrichter.

Das war es dann im wesentlichen auch schon wieder.
Das „PIPCommand“ Script sieht bei mir so aus:

<?

Switch ($_IPS['SENDER'])

	{
	Default:
	   break;

	Case "RunScript":

	
	Case "Execute":
#SetValueString (53394 /*[PV\PIP1\PIPCommand\CommandToPip]*/,"QVFW2");  # Mode
#      $PipCommand ="QVFW";
    #  $PipCommand ="QPIGS";
	#	$PipCommand ="QPIWS";

SetValueString (23017 /*[PV\PIP2\PIPCommand\CommandToPip]*/,"QFLAG");  # Mode
      IPS_Sleep(400);
 #     SetValueString (23017 /*[PV\PIP2\PIPCommand\CommandToPip]*/,"QPIWS"); #Warning
 #     IPS_Sleep(400);
#		SetValueString (23017 /*[PV\PIP2\PIPCommand\CommandToPip]*/,"QPIGS"); #Parameter




break;

	Case "TimerEvent":
      SetValueString (23017 /*[PV\PIP2\PIPCommand\CommandToPip]*/,"QMOD");  # Mode
      IPS_Sleep(300);
      SetValueString (23017 /*[PV\PIP2\PIPCommand\CommandToPip]*/,"QPIWS"); #Warning
      IPS_Sleep(300);
		SetValueString (23017 /*[PV\PIP2\PIPCommand\CommandToPip]*/,"QPIGS"); #Parameter
  	  break;

	Case "Variable":
	
	
    	
	Case "WebFront":
  #     SetValue($_IPS['VARIABLE'], $_IPS['VALUE']);
			   if ($_IPS['VARIABLE'] == 44355 /*[PV\PIP2\PipOutputMode]*/) {
	   switch ($_IPS['VALUE'])
			{
			case 0:SetValueString (23017 /*[PV\PIP2\PIPCommand\CommandToPip]*/,"POP00");
				break;
	      case 1:SetValueString (23017 /*[PV\PIP2\PIPCommand\CommandToPip]*/,"POP01");
				break;
         case 2:SetValueString (23017 /*[PV\PIP2\PIPCommand\CommandToPip]*/,"POP02");
				break;
  			 }
    	}

    	 if ($_IPS['VARIABLE'] == 21765 /*[PV\PIP2\PipChargeMode]*/) {
	   	switch ($_IPS['VALUE'])
			{
			case 0:SetValueString (23017 /*[PV\PIP2\PIPCommand\CommandToPip]*/,"PCP00");
				break;
	      case 1:SetValueString (23017 /*[PV\PIP2\PIPCommand\CommandToPip]*/,"PCP01");
				break;
         case 2:SetValueString (23017 /*[PV\PIP2\PIPCommand\CommandToPip]*/,"PCP02");
				break;
         case 3:SetValueString (23017 /*[PV\PIP2\PIPCommand\CommandToPip]*/,"PCP03");
				break;
			 }
    	}

    	if ($_IPS['VARIABLE'] == 13546 /*[PV\PIP2\ReChargeVoltage]*/) {
			$setpoint=number_format($_IPS['VALUE'],1);
	#		print "PBCV".$setpoint;
         SetValueString (23017 /*[PV\PIP2\PIPCommand\CommandToPip]*/,"PBCV".$setpoint);
		}

    	if ($_IPS['VARIABLE'] == 42940 /*[PV\PIP2\ReDischargeVoltage]*/) {
			$setpoint=number_format($_IPS['VALUE'],1);
#$setpoint = "00.0";
		 #	print "PBDV".$setpoint;
       
         SetValueString (23017 /*[PV\PIP2\PIPCommand\CommandToPip]*/,"PBDV".$setpoint);
		}

		if ($_IPS['VARIABLE'] == 12376 /*[PV\PIP2\CutoffVoltage]*/) {

			$setpoint=number_format($_IPS['VALUE'],1);
		#	print "PSDV".$setpoint;
         SetValueString (23017 /*[PV\PIP2\PIPCommand\CommandToPip]*/,"PSDV".$setpoint);
		}



    	if ($_IPS['VARIABLE'] == 38205 /*[PV\PIP2\PipChargeFloatVoltage]*/) {
			$setpoint=number_format($_IPS['VALUE'],1);
#echo $setpoint;
         SetValueString (23017 /*[PV\PIP2\PIPCommand\CommandToPip]*/,"PBFT".$setpoint);
		}

		if ($_IPS['VARIABLE'] == 35632 /*[PV\PIP2\ChargeBulkVoltage]*/) {
			$setpoint=number_format($_IPS['VALUE'],1);
         SetValueString (23017 /*[PV\PIP2\PIPCommand\CommandToPip]*/,"PCVV".$setpoint);
		}
# Read back setting to see accepted
       IPS_Sleep(500);
  		 SetValueString (23017 /*[PV\PIP2\PIPCommand\CommandToPip]*/,"QPIRI"); #Parameter
}





?>

Auch dieses ist an die jeweiligen Bedürfnisse, Bspw. welche Daten nun wann abgefragt, oder welche Settings übers
Webfront eingestellt werden sollen anzupassen.

Die Objekthirachie sieht so aus:


Es müssen nur die markierten Objekte angelegt werden.

viele Erfolg
bb

Wie das Webfront dazu aussiehen kann hab ich schon ein paar Post weiter vorne mal gezeigt.

Inzwischen hab ich noch um einen weiteren Wechselrichter aufgerüstet und folgende Live Darstellung dazugebaut:

dann gibt es noch einige Scripte zur Lastoptimierung:

und ein BMS mit Einzelzellenüberwachung

gruß
bb

Morgen bb,

tolle Sache die du da gebaut hast!
Und vielen Dank dafür, das du dies der Algemeinheit zur Verfügung stellst.

Ich habe auch seit ein paar Wochen einen PIP, den neuen 5048.
Deine Scripte und Variablen hab ich alle angelegt.

Mein PIP hängt über USB an IPS und ich bekomme einfach kein Verbindung hin, hast du vielleicht ein Tip :confused:
Statt einem Serial Port habe ich einen HID Port angelegt.
Ich find den PIP aber nicht mal im Gerätemanager als USB Gerät.
In WatchPower kommt aber alles perfekt an.

Super Sache,

ich rechne mir auch gerade die Wirtschaftlichkeit so einer Anlage durch oder schön. :slight_smile:
Der Link des Photovoltaikform war super. Da gibt es viel Lesestoff.

@bbernhard

könntest Du mal kurz erklären, wie Du das dynamische Live Schaubild Deiner Anlage programmiert hast?

@jossel
Das hilft dir jetzt nicht weiter, aber einer meiner PIP kahm mit serieller, der andere mit USB Schnittstelle.
Wegen großer Entfernung zum IPS Rechner hab ich das USB Modul rausgenommen und stattdessen mit einem ESP8266 eine UART->WLAN Bridge gebaut. d.h. vom zweiten PIP kommen die Daten nun per WLAN.

Ob der original als COM Port oder HID anzusprechen war kann ich mich jetzt nicht mehr erinneren. Ich denke die Scripte funktionieren nur mit einem COM Port, oder nach kleiner Anpassung über TCP (ServerSocket). Mit HID hab ich mich noch nie beschäftigt, keine Ahnung ob und wie das geht.

@Heimgeist
Die Darstellung ist mit der schon öfter beschriebenen „Floorplanmethode“ gemacht. Suche nach Floorplan, dann findest du alles was du brauchst.

greez
bb

Morgen bb,

danke für deine Hinweise, da werd ich mal noch etwas rumprobieren. :wink: