Hi there,
since the SolarMax Binding for OpenHab is not released, yet - more information about that project can be found here - I’ve made a simple script for reading out the data provided by the inverters.
Usually, this solution should also work with other SolarMax Inverters than the S6000, which I am working with.
After capturing a Wireshark capture of the communication with the provided software MaxTalk and the inverter I recognized that the parameters are transferred via a TCP connection with plain ASCII data.
A quick research led me to the following page, which confirmed my assumption.
SolarMax MaxTalk protocol reverse engineered
So I decided to script the usage of netcat on my Linux machine for polling the inverter data and calculating the checksum for the communication automatically.
For this approach, netcat has to be installed on your machine as well.
Bash-Script:
#!/bin/bash
# Solar Max Request script
# Script parameters
# $Source: Linux server
# $Destination: Inverter module
# $Cmd: Semicolan seperated command(s) (PAC, PDC)
# $IpAddress: Ip address of your inverter
# $PortNumber: Portnumber assigned to your inverter
Source=$1 # e.g "FF"
Destination=$2 # e.g. "01" depending on your inverter address
Cmd=$3 # e.g. "PAC;KDY" for the total Power (AC) and the total energy this year
IpAddress=$4
PortNumber=$5
# Calculate lenght of the command; Lenght of Source, Destination, Lenght and CheckSum is fixed
printf -v Length "%02X" "$((${#Cmd} + 19))"
# Calculate TCP command without checksum
TcpCmd="$Source;$Destination;$Length|64:$Cmd|"
# Calculate checksum - Sum up all the bytes of string "$Source;$Destination;$Length|64:$Cmd|" and keep the lowest 16 bits of the sum.
for i in $(seq 1 ${#TcpCmd})
do
# Get charackter bytes
printf -v AsciiByte "%02X" "'${TcpCmd:i-1:1}"
# Calclulate checksum
CheckSum=$(( $CheckSum + 16#$AsciiByte ))
done
# Get lower bytes of checksum
printf -v CheckSum "%04X" "${CheckSum}"
# Append checksum to command
TcpCmd="{${TcpCmd}$CheckSum}"
# Get solar max properties according to TCP command using netcat; The first response is taken; Timeout is set to 5 seconds
echo -n $TcpCmd | netcat -W1 -w5 $IpAddress $PortNumber
echo ""
This script can be easily used with the parameters:
SolarMax_GetData.sh “SourceAddress” “DestinationAddress” “SemicolonSeperatedParameters” “InverterIpAddress” “InverterPortNumber”
For using the script automatically within OpenHab, I am using the Exec Binding and saved the script into the script’s directory.
Make sure for adding the command using the script to the whitelist, as described within the Exec Binding configuration more thoroughly.
Now a simple rule can be used to cyclically call the script and pull the inverter data. I also used the rule to convert the returning hexadecimal value into decimal values for a better interpretation.
Things-File:
//Using exec binding to execute script
//Keep aware of listing the executed conmmand line at $OpenHAB_config/misc/exec.whitelist
//Keep aware of setting propper folder/file permissions
// Solar Max Thing
Thing exec:command:SolarMaxUpdate "SolarMax" [command="/openhab/conf/scripts/SolarMax_GetData.sh %2$s", interval=60, timeout=5, autorun=false]
Items-File:
// Solar Max Script items
Switch SolarMaxUpdate_update "SolarMax update" {channel="exec:command:SolarMaxUpdate:run"}
String SolarMaxUpdate_output "SolarMax output string" {channel="exec:command:SolarMaxUpdate:output"}
String SolarMaxUpdate_input "SolarMax input string" {channel="exec:command:SolarMaxUpdate:input"}
Number SolarMaxUpdate_return "SolarMax return value" {channel="exec:command:SolarMaxUpdate:exit"}
DateTime SolarMaxUpdate_executed "SolarMax executed" {channel="exec:command:SolarMaxUpdate:lastexecution"}
// Solar Max items
Switch SolarMaxUpdate_manualUpdate "SolarMax update"
Number SolarMax_PAC "SolarMax AC power [%.0f W]"
Number SolarMax_PDC "SolarMax DC power [%.0f W]"
Number SolarMax_KHR "SolarMax operating hours [%d h]"
Number SolarMax_KDY "SolarMax energy today [%d Wh]"
Number SolarMax_KYR "SolarMax energy this year [%d kWh]"
Number SolarMax_UDC "SolarMax DC voltage [%.2f V]"
Number SolarMax_UL1 "SolarMax AC voltage L1 [%.2f V]"
Number SolarMax_IDC "SolarMax DC current [%.2f A]"
Number SolarMax_IL1 "SolarMax AC current L1 [%.2f A]"
Number SolarMax_PIN "SolarMax installed power [%.0f W]"
Number SolarMax_PRL "SolarMax used power [%d %%]"
Rules-File:
val String Source = "FF"
val String Destination = "01"
val String Prperties = "PAC;PDC;KHR;KDY;KYR;UDC;UL1;IDC;IL1;PIN;PRL"
val String IpAddresse = "xxx.xxx.xxx.xxx"
val String PortNumber = "12345"
rule "SolarMax update parameters"
when
Time cron "0 0/5 * * * ?" or// every 5 Minute
Item SolarMaxUpdate_manualUpdate changed to ON
then
logInfo("SolarMax", "Request Solar Max stats")
// Set input string for command
var String Command = String.format("'%s' '%s' '%s' '%s' '%s'", Source, Destination, Prperties, IpAddresse, PortNumber);
SolarMaxUpdate_input.sendCommand(Command)
//Executing Solar Max updates script
logInfo("SolarMax", "Requestion command: {}", Command)
SolarMaxUpdate_update.sendCommand(ON)
var String Output = SolarMaxUpdate_output.state.toString()
logInfo("SolarMax", "Returning command: {}", Output)
var int StartIndex = Output.indexOf(':') + 1
var int EndIndex = Output.length() - 6
var String Result = Output.substring(StartIndex, EndIndex);
var String[] SplittedProperties = Result.split(";")
// Loop through respond properties
SplittedProperties.forEach [Prop |
// Split values
// logDebug("SolarMax", "Returning property: {}", Prop)
var String[] temp = Prop.split("=")
var String PropName = temp.get(0)
var String PropValue_hex = temp.get(1) // Hexadecimal
// Convert hex to dec
var int PropValue_dec = Integer.parseInt(PropValue_hex, 16)
//logDebug("SolarMax", "Returning property: {}, {}", PropName, PropValue_dec)
// Assign items
switch (PropName) {
case "PAC": SolarMax_PAC.sendCommand(PropValue_dec)
case "PDC": SolarMax_PDC.sendCommand(PropValue_dec)
case "KHR": SolarMax_KHR.sendCommand(PropValue_dec)
case "KDY": SolarMax_KDY.sendCommand(PropValue_dec)
case "KYR": SolarMax_KYR.sendCommand(PropValue_dec)
case "UDC": SolarMax_UDC.sendCommand(PropValue_dec.doubleValue()/10)
case "UL1": SolarMax_UL1.sendCommand(PropValue_dec.doubleValue()/10)
case "IDC": SolarMax_IDC.sendCommand(PropValue_dec.doubleValue()/100)
case "IL1": SolarMax_IL1.sendCommand(PropValue_dec.doubleValue()/100)
case "PIN": SolarMax_PIN.sendCommand(PropValue_dec)
case "PRL": SolarMax_PRL.sendCommand(PropValue_dec)
}
]
//Set items to initial state
createTimer(now.plusSeconds(1))[|SolarMaxUpdate_update.postUpdate(OFF)]
createTimer(now.plusSeconds(1))[|SolarMaxUpdate_manualUpdate.postUpdate(OFF)]
end
I hope, this slight workaround will fit your needs until the SolarMax Binding is finished.
Best regards