From 648c8187fb415e21b3f3aa81c9ade1f0c4ec6e78 Mon Sep 17 00:00:00 2001 From: pahenning <> Date: Mon, 13 Feb 2012 15:49:57 +0000 Subject: [PATCH] Module for solar inverter Sunways NT5000 git-svn-id: https://svn.fhem.de/fhem/trunk@1257 2b470e98-0d58-463d-a4d8-8e2adae1ed80 --- fhem/contrib/70_NT5000.pm | 1094 +++++++++++++++++++++++++++++++++++++ 1 file changed, 1094 insertions(+) create mode 100755 fhem/contrib/70_NT5000.pm diff --git a/fhem/contrib/70_NT5000.pm b/fhem/contrib/70_NT5000.pm new file mode 100755 index 000000000..7b772d062 --- /dev/null +++ b/fhem/contrib/70_NT5000.pm @@ -0,0 +1,1094 @@ +######################################################################################## +# +# NT5000.pm +# +# An FHEM module to read the data from a Sunways NT5000 solar inverter +# +# Version 0.96 - January 2, 2012 +# +# Prof. Dr. Peter A. Henning, 2011 +# +# still some beta functionality +# +# Setup as: +# define nt5000 NT5000 +# +# where nt5000 may be replaced by any name string and +# is a serial (USB) device or the keyword "emulator". +# In the latter case, a 4.5 kWP solar installation is simulated +# +# Attributes are set as (examples !) +# +# attr nt5000 room Solaranlage +# Area of solar installation +# attr nt5000 Area 32.75 +# Peak Solar Power +# attr nt5000 PSP 4.5 +# Months with erroneous readings - see line 83 ff +# attr nt5000 MERR +# Expected yields per month / year +# attr nt5000 Wx_M1 150 +# attr nt5000 Wx_M2 250 +# attr nt5000 Wx_M3 350 +# attr nt5000 Wx_M4 450 +# attr nt5000 Wx_M5 600 +# attr nt5000 Wx_M6 600 +# attr nt5000 Wx_M7 600 +# attr nt5000 Wx_M8 600 +# attr nt5000 Wx_M9 450 +# attr nt5000 Wx_M10 350 +# attr nt5000 Wx_M11 250 +# attr nt5000 Wx_M12 150 +# attr nt5000 Wx_Y 4800 +# +# TODO: +# Anzeige month, year im State ist falsch (Juli: Month M:30, year Y:6) +# Anzeige cmd aus dem state heraus. +# +######################################################################################## +package main; + +use strict; +use warnings; +use Device::SerialPort; + +# Prototypes to make komodo happy +use vars qw{%attr %defs}; +sub Log($$); + +# Line counter +my $cline=0; + +# These we may get on request +my %gets = ( + "reading" => "R", + "month" => "M", + "year" => "Y", + "serial" => "S", + "proto" => "P" +); + +# These occur in a pulldown menu as settable values +my %sets = ( + "time" => "T" +); + +# These we may get on request +my %attrs = ( + "Wyx" => "R", +); + + +######################################################################################## +# +# NT5000_Initialize +# +# Parameter hash +# +######################################################################################## + +sub NT5000_Initialize ($) { + my ($hash) = @_; + + $hash->{DefFn} = "NT5000_Define"; + $hash->{GetFn} = "NT5000_Get"; + $hash->{SetFn} = "NT5000_Set"; + # Area = Area of solar panels, to calculate expected output from solar irradiation + # PSP = Peak Solar Power of installation + # MERR = List of month entries that failed. Reason: Defective loggers in the NT5000 itself + # sometimes "jump" ahead in the monthly setting. Maybe singular problem of author ? + # Every pseudo-month-entry in this list means that its yield is ADDED to the FOLLOWING month + # e.g. MERR = 4 => Month value for April is wrong and should be added to the value from May, + # which is the following one. + # WxM1 .. WxM12 = Expected yield from January .. December + # WxY = Expected yield per year + $hash->{AttrList}= "Area PSP MERR Wx_M1 Wx_M2 Wx_M3 Wx_M4 Wx_M5 Wx_M6 Wx_M7 Wx_M8 Wx_M9 Wx_M10 Wx_M11 Wx_M12 Wx_Y loglevel:0,1,2,3,4,5,6"; +} + +####################################################################################### +######################################################################################## +# +# NT5000_Define - Implements DefFn function +# +# Parameter hash, definition string +# +######################################################################################## + +sub NT5000_Define($$) { + my ($hash, $def) = @_; + my @a = split("[ \t][ \t]*", $def); + + return "Define the serial device as a parameter, use none or emulator for a fake device" + if(@a != 3); + $hash->{STATE} = "Initialized"; + + my $dev = $a[2]; + + Log 1, "NT5000 device is none, commands will be echoed only" + if($dev eq "none"); + + Log 1, "NT5000 with emulator mode" + if($dev eq "emulator"); + + if( ($dev ne "none") && ($dev ne "emulator")) { + Log 3, "NT5000 opening device $dev"; + my $nt5000_serport = new Device::SerialPort ($dev); + return "NT5000 Can't open $dev: $!" if(!$nt5000_serport); + Log 2, "NT5000 opened device $dev"; + $hash->{USBDev} = $nt5000_serport; + sleep(1); + $nt5000_serport->close(); + + } + + $hash->{DeviceName} = $dev; + $hash->{Timer} = 60; # call every 60 seconds + $hash->{Cmd} = "reading"; # get all data, min/max unchange + $hash->{SerialNumber} = ""; + $hash->{Protocol} = ""; + $hash->{Firmware} = ""; + $hash->{STATE} = "offline"; + my $tn = TimeNow(); + #$hash->{READINGS}{"freq"}{TIME} = $tn; + #$hash->{READINGS}{"freq"}{VAL} = $hash->{Timer}; + #$hash->{READINGS}{"cmd"}{TIME} = $tn; + #$hash->{READINGS}{"cmd"}{VAL} = $hash->{Cmd}; + #$hash->{CHANGED}[$main::cline++] = "freq: $hash->{Timer}"; + #$hash->{CHANGED}[$main::cline++] = "cmd: $hash->{Cmd}"; + + # InternalTimer blocks if init_done is not true + my $oid = $init_done; + $init_done = 1; + NT5000_GetStatus($hash); + $init_done = $oid; + return undef; +} + +######################################################################################## +# +# NT5000_Get - Implements GetFn function +# +# Parameter hash, argument array +# +######################################################################################## + +sub NT5000_Get ($@) { +my ($hash, @a) = @_; + +return "NT5000_Get needs exactly one parameter" if(@a != 2); +my $name = $hash->{NAME}; +my $v; + +if($a[1] eq "reading") + { + $v = NT5000_GetLine($hash,"reading"); + if(!defined($v)) + { + Log GetLogLevel($name,2), "NT5000_Get $a[1] error"; + return "$a[0] $a[1] => Error"; + } + $v =~ s/[\r\n]//g; # Delete the NewLine + $hash->{READINGS}{$a[1]}{VAL} = $v; + $hash->{READINGS}{$a[1]}{TIME} = TimeNow(); + } +elsif($a[1] eq "month") + { + $v = NT5000_GetLine($hash,"month"); + if(!defined($v)) + { + Log GetLogLevel($name,2), "NT5000_Get $a[1] error"; + return "$a[0] $a[1] => Error"; + } + $v =~ s/[\r\n]//g; # Delete the NewLine + $hash->{READINGS}{$a[1]}{VAL} = $v; + $hash->{READINGS}{$a[1]}{TIME} = TimeNow(); + } + elsif($a[1] eq "year") + { + $v = NT5000_GetLine($hash,"year"); + if(!defined($v)) + { + Log GetLogLevel($name,2), "NT5000_Get $a[1] error"; + return "$a[0] $a[1] => Error"; + } + $v =~ s/[\r\n]//g; # Delete the NewLine + $hash->{READINGS}{$a[1]}{VAL} = $v; + $hash->{READINGS}{$a[1]}{TIME} = TimeNow(); + } +else + { + return "NT5000_Get with unknown argument $a[1], choose one of " . join(",", sort keys %gets); + } + +Log GetLogLevel($name,3), "NT5000_Get $a[1] $v"; +return "$a[0] $a[1] => $v"; +} + +######################################################################################## +# +# NT5000_Set - Implements SetFn function +# +# Parameter hash, a = argument array +# +######################################################################################## + +sub NT5000_Set ($@) { + my ($hash, @a) = @_; + my $name = shift @a; + my $res; + + #-- for the selector: which values are possible + return join(" ", sort keys %sets) if(@a != 2); + return "NT5000_Set: With unknown argument $a[0], choose one of " . join(" ", sort keys %sets) + if(!defined($sets{$a[0]})); + + #-- Set time value + if( $a[0] eq "time" ){ + #-- only values >= 15 secs allowed + if( $a[1] >= 15){ + $res = "not yet implemented"; + } else { + $res = "not yet implemented"; + } + Log GetLogLevel($name,3), "NT5000_Set $name ".join(" ",@a)." => $res"; + DoTrigger($name, undef) if($init_done); + return "NT5000_Set => $name ".join(" ",@a)." => $res"; + } +} + +######################################################################################## +# +# NT5000 - GetStatus - Called in regular intervals to obtain current reading +# +# Parameter hash +# +######################################################################################## + +sub NT5000_GetStatus ($) { + my ($hash) = @_; + my $name = $hash->{NAME}; + + # Call us in n minutes again. + InternalTimer(gettimeofday()+ $hash->{Timer}, "NT5000_GetStatus", $hash,1); + + # Obtain the current reading + my $result = NT5000_GetLine($hash, "reading"); + + # If one of these applies we must assume that the inverter is offline (no retry !) + # Logging only if this is a change from the previous state + if( !defined($result) ) { + Log GetLogLevel($name,1), "NT5000 cannot be read, inverter offline" if( $hash->{STATE} ne "offline" ); + #Log 3, "NT5000 cannot be read, inverter offline"; + $hash->{STATE} = "offline"; + return $hash->{STATE}; + } elsif( length($result) < 13 ){ + Log GetLogLevel($name,1), "NT5000 returns incomplete line, inverter offline" if( $hash->{STATE} ne "starting" ); + #Log 3, "NT5000 returns incomplete line"; + $hash->{STATE} = "starting"; + return $hash->{STATE}; + }else { + # we have obtained a reading: inverter is online + #Log 3, "NT5000 has answered 13 bytes"; + my $tn = TimeNow(); + my @names = ("Udc", "Idc", "Pdc", "Uac", "Iac", "Pac", "Temp", "S", "Wd", "Wtot", "Eta"); + + if( $hash->{STATE} ne "online" ) { + # we have turned online recently + Log GetLogLevel($name,2), "NT5000 inverter is online"; + $hash->{STATE} = "starting"; + # Obtain the serial number and protocol + my $serial = NT5000_GetLine($hash, "serial"); + $serial =~ s/^.*S://; + $serial =~ s/[\r\n ]//g; + $hash->{SerialNumber} = "$serial"; + my $proto = NT5000_GetLine($hash, "proto"); + $proto =~ s/^.*P://; + $proto =~ s/[\r\n ]//g; + $hash->{Protocol} = substr($proto,0,1).".".substr($proto,1,1); + $hash->{Firmware} = substr($proto,2,1).".".substr($proto,4,2); + + # Obtain monthly readings in 70 seconds - only once + InternalTimer(gettimeofday()+ 20, "NT5000_GetMonth", $hash,1); + + # Obtain yearly readings in 10 seconds - only once + InternalTimer(gettimeofday()+ 40, "NT5000_GetYear", $hash,1); + + my $resmod ="header: "; + #Put a header line into the log file + for(my $i = 0; $i < int(@names); $i++) { + $resmod .= $names[$i]." "; + } + $hash->{CHANGED}[$main::cline++] = "$resmod"; + }; + + $hash->{STATE} = "online"; + #$result =~ s/^.*R://; + #$result =~ s/[\r\n ]//g; + #$result=~ s/,/./g; + #-- Log level 5 + Log GetLogLevel($name,5), "NT5000 online result = $result"; + + # All data items in one line saves a lot of place + my $resmod = $result; + #$resmod =~ s/;/ /g; + $hash->{CHANGED}[$main::cline++] = "reading: $resmod"; + + #-- split result for writing into hash + my @data = split(' ',$result); + for(my $i = 0; $i < int(@names); $i++) { + # This puts individual pairs into the tabular view + $hash->{READINGS}{$names[$i]}{TIME} = $tn; + $hash->{READINGS}{$names[$i]}{VAL} = $data[$i]; + } + + DoTrigger($name, undef) if($init_done); + + $result =~ s/;/ /g; + } + + return $hash->{STATE}; +} + +######################################################################################## +# +# NT5000_GetMonth - Read monthly data from inverter +# +# Parameter hash +# +######################################################################################## + +sub NT5000_GetMonth ($) { +my ($hash) = @_; +my $name = $hash->{NAME}; + + #-- Obtain the monthly reading + my $result = NT5000_GetLine($hash, "month"); + $result =~ s/^.*M://; + $result =~ s/[\r\n ]//g; + Log GetLogLevel($name,3), "NT5000 monthly result = $result"; + $result=~ s/,/./g; + my @data = split(";", $result); + my $day = $data[0]; + + #-- Expected yield for month + my ($sec,$min,$hour,$dayn,$month,$year,$wday,$yday,$isdst) = localtime(time); + my $mex = "Wx_M".($month+1); + my $wex = $attr{$name}{$mex}; + my $wac = 0; + my $wre; + + my @names = ("W_D1","W_D2","W_D3","W_D4","W_D5","W_D6","W_D7","W_D8","W_D9","W_D10", + "W_D11","W_D12","W_D13","W_D14","W_D15","W_D16","W_D17","W_D18","W_D19","W_D20", + "W_D21","W_D22","W_D23","W_D24","W_D25","W_D26","W_D27","W_D28","W_D29","W_D30","W_D31"); + + my $yearn = $year+1900; + my $monn = $month+1; + + #-- we are not sure, if this is logged now or in the next minute, hence an "or" in the regexp + $hash->{LASTM}=sprintf("%4d-%02d-%02d_%02d:(%02d|%02d)",$yearn,$monn,$day,$hour,$min,$min+1); + + for(my $i = 0; $i < $day; $i++) + { + + my $dayn = $i+1; + my $daten = $yearn."-".$monn."-".$dayn."_23:59:59"; + $wac += $data[$day-$i]; + if( $wex ) + { + $wre = int(1000*$wac/$wex)/10 if ($wex>0 ); + }; + # Put one item per line into the log file + # +1 necessary - otherwise will be overridden by the changes in the daily readings + $main::cline++; + $hash->{CHANGED}[$main::cline++] = "$names[$i]: $daten $data[$day-$i] $wac $wre"; + # This puts individual pairs into the tabular view + #$hash->{READINGS}{$names[$i]}{TIME} = $tn; + #$hash->{READINGS}{$names[$i]}{VAL} = $data[$day-$i]; + }; +} + +######################################################################################## +# +# NT5000_GetYear - Read yearly data from inverter +# +# Parameter hash +# +######################################################################################## + +sub NT5000_GetYear ($) { +my ($hash) = @_; +my $name = $hash->{NAME}; + +#-- Obtain the yearly reading + my $result = NT5000_GetLine($hash, "year"); + $result =~ s/^.*Y://; + $result =~ s/[\r\n ]//g; + Log GetLogLevel($name,3), "NT5000 yearly result = $result"; + $result=~ s/,/./g; + my @data = split(";", $result); + + #-- Expected yield for year + my ($sec,$min,$hour,$dayn,$month,$year,$wday,$yday,$isdst) = localtime(time); + my $wex = $attr{$name}{Wx_Y}; + my $wac = 0; + my $wre; + + my @names = ("W_M01","W_M02","W_M03","W_M04","W_M05","W_M06","W_M07","W_M08","W_M09","W_M10", + "W_M11","W_M12"); + + my $yearn = $year+1900; + #-- we are not sure, if this is logged now or in the next minute, hence an "or" in the regexp + $hash->{LASTY}=sprintf("%4d-%02d-%02d_%02d:(%02d|%02d)",$yearn,$month+1,$dayn,$hour,$min,$min+1); + + for(my $i = 0; $i <= $month; $i++) { + my $monn = $i+1; + my $daten = $yearn."-".$monn."-28_23:59:59"; + my $mex = "Wx_M".($monn); + my $mmex = $attr{$name}{$mex}; + $wac += $data[$month+1-$i]; + if( $wex ) + { + $wre = int(1000.0*$wac/$wex)/10 if ($wex > 0); + }; + #-- Put one item per line into the log file + # +1 necessary - otherwise will be overridden by the changes in the daily readings + $hash->{CHANGED}[$main::cline++] = "$names[$i]: $daten $data[$month+1-$i] $mmex $wac $wre"; + #-- This puts individual pairs into the tabular view + # $hash->{READINGS}{$names[$i]}{TIME} = $tn; + # $hash->{READINGS}{$names[$i]}{VAL} = $data[$i]; + }; + } + +######################################################################################## +# +# NT5000_GetLine - Read data from inverter +# +# Parameter hash, a = string parameter to define what will be asked +# +######################################################################################## + +sub NT5000_GetLine ($$) { + my ($hash,$a) = @_; + my $name = $hash->{NAME}; + + return "NT5000_GetLine: Unknown argument $a, choose one of " . join(",", sort keys %gets) + if(!defined($gets{$a})); + + my $dev = $hash->{DeviceName}; + + #-- Inverter data + my $rError = "\x00\x01\x01\x01"; + my $rOnline1 = "\x00\x01\x02\x01"; + my $rMon1 = "\x00\x01\x03\x01"; + my $rYear1 = "\x00\x01\x04\x01"; + my $rTime = "\x00\x01\x06\x01"; + my $rSerial = "\x00\x01\x08\x01"; + my $rProFW = "\x00\x01\x09\x01"; + my $sYY = "\x00\x01\x50"; + my $sLL = "\x00\x01\x51"; + my $sDD = "\x00\x01\x52"; + my $sHH = "\x00\x01\x53"; + my $sMM = "\x00\x01\x54"; + + my @invBuffer; + + #------------------------ current readings ------------------------------------- + if( $a eq "reading" ) + { + #Log 3, "Asking for online data"; + my $invReturn = NT5000_5to13($hash,$dev,$rOnline1); + #-- test if this is an offline case + if( !($invReturn) ) + { + return undef; + } + @invBuffer=split(//,$invReturn); + #-- test again if this is an offline case + if( @invBuffer < 13 ) + { + return undef; + } + #-- Process data + my $udc = ord($invBuffer[0])*2.8+100; + my $idc = ord($invBuffer[1])*0.08; + my $uac = ord($invBuffer[2])+100.0; + my $iac = ord($invBuffer[3])*0.120; + my $t = ord($invBuffer[4])-40.0; + my $s = ord($invBuffer[5])*6.0; + my $pdc = int($udc*$idc)/1000; + my $pac = int($uac*$iac)/1000; + my $wd = (ord($invBuffer[6])* 256 + ord($invBuffer[7]))/1000; + my $wtot= ord($invBuffer[8])* 256 + ord($invBuffer[9]); + #-- Calculate eta + my $name= $hash->{NAME}; + my $a = $attr{$name}{Area}; + my $eta; + if ( $s && $a ) { + if( ($a>0)&&($s>0) ) { + $eta = int(100000*$pac/($s*$a))/100; + } else { + $eta = 0; + }; + } else { + $eta=0; + } + return sprintf "%3.1f %2.2f %1.3f %3.1f %2.2f %1.3f %2.0f %4.0f %2.3f %5.1f %1.2f", + $udc,$idc,$pdc,$uac,$iac,$pac,$t,$s,$wd,$wtot,$eta; +#------------------------ montly readings ------------------------------------- + } elsif( $a eq "month" ) { + my $i=1; + #-- Get the first block anyhow + my $ica=$i%256; + my $cmd2=sprintf("%s%c",substr($rMon1,0,3),$ica); + my $invReturn = NT5000_5to13($hash,$dev,$cmd2); + #-- test if this is an offline case + if( !($invReturn) ){ + return undef; + } + @invBuffer=split(//,$invReturn); + #-- test again if this is an offline case + if( @invBuffer < 13 ) { + return undef; + } + #-- Process data + my $day = ord($invBuffer[1]); + my $result="M:$day;"; + for( my $j=0; $j<3; $j++ ) { + $result .= ((ord($invBuffer[2+4*$j])* 256 + ord($invBuffer[3+4*$j]))/1000).";" if( ($day-$j) > 0); + } + #-- Get further blocks if necessary + for( $i=2; $i<=($day+2)/3; $i++) { + $ica=$i%256; + $cmd2=sprintf("%s%c",substr($rMon1,0,3),$ica); + $invReturn = NT5000_5to13($hash,$dev,$cmd2); + #-- test if this is an offline case + if( !($invReturn) ) { + return undef; + } + @invBuffer=split(//,$invReturn); + #-- test again if this is an offline case + if( @invBuffer < 13 ) { + return undef; + } + for( my $j=0; $j<3; $j++ ) { + $result .= ((ord($invBuffer[2+4*$j])* 256 + ord($invBuffer[3+4*$j]))/1000).";" if( ($day-($i-1)*3-$j) > 0); + } + }; + return "$result\n"; +#------------------------ yearly readings ------------------------------------- + } elsif( $a eq "year" ) { + my $i=1; + #-- We read the full data, e.g. current month (cm) .. cm-12 + my @pmval; + #-- Get the first block + my $ica=$i%256; + my $cmd2=sprintf("%s%c",substr($rYear1,0,3),$ica); + my $invReturn = NT5000_5to13($hash,$dev,$cmd2); + #-- test if this is an offline case + if( !($invReturn) ) { + return undef; + } + @invBuffer=split(//,$invReturn); + #-- test again if this is an offline case + if( @invBuffer < 13 ) { + return undef; + } + #-- Process data for current month (cm) .. cm-4 + my $month = ord($invBuffer[1]); + for( my $j=0; $j<5; $j++ ) { + #-- value for pseudo-month + push(@pmval,(ord($invBuffer[2+2*$j])* 256 + ord($invBuffer[3+2*$j]))/10); + } + #-- Get the second block + $i++; + $ica=$i%256; + $cmd2=sprintf("%s%c",substr($rYear1,0,3),$ica); + $invReturn = NT5000_5to13($hash,$dev,$cmd2); + #-- test if this is an offline case + if( !($invReturn) ) { + return undef; + } + @invBuffer=split(//,$invReturn); + #-- test again if this is an offline case + if( @invBuffer < 13 ) { + return undef; + } + #-- Process data for cm-5 .. cm-10 + for( my $j=0; $j<6; $j++ ) { + #-- value for pseudo-month + push(@pmval,(ord($invBuffer[2*$j])* 256 + ord($invBuffer[1+2*$j]))/10); + }; + #-- Get the third block + $i++; + $ica=$i%256; + $cmd2=sprintf("%s%c",substr($rYear1,0,3),$ica); + $invReturn = NT5000_5to13($hash,$dev,$cmd2); + #-- test if this is an offline case + if( !($invReturn) ) { + return undef; + } + @invBuffer=split(//,$invReturn); + #-- test again if this is an offline case + if( @invBuffer < 13 ) { + return undef; + } + #-- Process data for cm-11 .. cm-12 + for( my $j=0; $j<2; $j++ ) { + #-- value for pseudo-month + push(@pmval,(ord($invBuffer[2*$j])* 256 + ord($invBuffer[1+2*$j]))/10); + }; + #-- Now we have to correct for those erroneous jumps of the internal data logger + # of the NT5000 + # The first one is never wrong, belongs to the current month + my @val = ($pmval[0]); + my $result; + # which and how many pseudo-month-entries do we have ? + my $merr = $attr{$name}{MERR}; + # none, we my return HERE + if( !defined($merr) ) { + @val= ($pmval[0]); + for( my $j=1; $j<=$month; $j++ ){ + push(@val,$pmval[$j]); + } + $result = "Y:$month;".join(';',@val); + return $result; + }; + # oops, correction has to be done + my @merrs = split(',',$merr); + my $merrno = @merrs; + #-- For the year we therefore need the first $month+$merrno entries + + my $pm; + my $mlim = $month+$merrno; + for( my $j=1; $j<$mlim; $j++ ) { + $pm = $month-$j; + my $listed = 0; + #-- check if this is in the list + for( my $k=0; $k<$merrno; $k++ ) { + # yes, it is + if( $merrs[$k]==$pm ) { + $listed = 1; + } + } + # yes, it is indeed + if( $listed==1) { + # add data to the last entry in @val + $val[@val-1]+=$pmval[$j]; + # no, it is not + } else { + # append value to array + push(@val,$pmval[$j]); + } + }; + #-- Compare the results + #Log 3, "YEAR PSEUDO ".join(';',@pmval); + #Log 3, "YEAR CORR ".join(';',@val); + $result = "Y:$month;".join(';',@val); + return $result; + }elsif( $a eq "serial" ) { + my $r1 = NT5000_5to13($hash,$dev,$rSerial); + return "S:".substr($r1,0,12)."\n" + }elsif( $a eq "proto" ){ + my $r1 = NT5000_5to13($hash,$dev,$rProFW); + return "P:".substr($r1,0,6)."\n" + }else { + print "OHOH => NT5000_GetLine mit Argument $a\n"; + } +} + +######################################################################################## +# +# NT5000_5to13 - Read 13 bytes from the inverter or the emulator +# +# Parameter: hash,dev = none,emulator or a serial port definition +# cmd = 5 byte parameter to query the device properly +# +######################################################################################## +sub +NT5000_5to13($$$) +{ + +my $retry = 0; +my ($hash,$dev,$cmd) = @_; + +my $result; +my ($i,$j,$k); + +if( $dev eq "none" ) #no inverter attached + { + return "\x00\x01\x02\x03\x04\x05\x06\x07\x07\x09\x0a\x0b\x0c"; + + } elsif ( $dev eq "emulator" ) #emulator attached + { + #-- read from emulator + + #-- calculate checksum + my $CS = unpack("%32C*", $cmd); + $CS=$CS%256; + my $cmd2=sprintf("%s%c",$cmd,$CS); + #-- control + #print "Sending out:\n"; + #for(my $i=0;$i<5;$i++) + # { my $j=int(ord(substr($cmd2,$i,1))/16); + # my $k=ord(substr($cmd2,$i,1))%16; + # print "byte $i = 0x$j$k\n"; + # } + + my $result = NT5000_emu(5,$cmd); + #print "[I] Answer 13 bytes received\n"; + #for($i=0;$i<13;$i++) + # { $j=int(ord($invBuffer[$i])/16); + # $k=ord($invBuffer[$i])%16; + # print "byte $i = 0x$j$k\n"; + # } + return($result); + + } else # here we do the real thing + { + #Just opening the old device does not reaaly work. + #my $serport = $hash->{USBDev}; + my $serport = new Device::SerialPort ($dev); + if(!$serport) { + Log 1, "NT5000: Can't open $dev: $!"; + return undef; + } + #Log 3, "NT5000 opened"; + $serport->reset_error(); + $serport->baudrate(9600) || die "failed setting baudrate"; + $serport->databits(8) || die "failed setting databits"; + $serport->parity('none') || die "failed setting parity"; + $serport->stopbits(1) || die "failed setting stopbits"; + $serport->handshake('none') || die "failed setting handshake"; + $serport->write_settings || die "no settings"; + + #my $rm = "NT5000 timeout reading the answer"; + + #-- calculate checksum + my $CS = unpack("%32C*", $cmd); + $CS=$CS%256; + my $cmd2=sprintf("%s%c",$cmd,$CS); + #-- control + #print "Sending out:\n"; + #for(my $i=0;$i<5;$i++) + # { my $j=int(ord(substr($cmd2,$i,1))/16); + # my $k=ord(substr($cmd2,$i,1))%16; + # print "byte $i = 0x$j$k\n"; + # } + + my $count_out = $serport->write($cmd2); + Log 3, "NT5000 write failed\n" unless ($count_out); + Log 3, "NT5000 write incomplete $count_out ne ".(length($cmd2))."\n" if ( $count_out != 5 ); + #Log 3, "write complete $count_out \n" if ( $count_out == 5 ); + #-- sleeping 0.03 seconds + select(undef,undef,undef,0.05); + my ($count_in, $string_in) = $serport->read(13); + #Log 3, "NT5000 read unsuccessful, $count_in bytes \n" unless ($count_in == 13); + #Log 3, "read complete $count_in \n" if ( $count_in == 13 ); + #-- sleeping 0.03 seconds + select(undef,undef,undef,0.05); + $serport->close(); + return($string_in); + } + +} + +######################################################################################## +# +# NT5000_emu - Emulator section - to be used, if the real solar inverter is not attached. +# +######################################################################################## + +sub NT5000_emu { +#-- For configuration purpose: when does the sun come up, and when does it go down +my $start = 6 + 55.0/60; +my $stop = 21 + 31.0/60; + +#-- Inverter data + +my $rError = "\x00\x01\x01\x01"; +my $rOnline1 = "\x00\x01\x02\x01"; +my $rMon1 = "\x00\x01\x03\x01"; +my $rYear1 = "\x00\x01\x04\x01"; +my $rTime = "\x00\x01\x06\x01"; +my $rSerial = "\x00\x01\x08\x01"; +my $rProFW = "\x00\x01\x09\x01"; +my $sYY = "\x00\x01\x50"; +my $sLL = "\x00\x01\x51"; +my $sDD = "\x00\x01\x52"; +my $sHH = "\x00\x01\x53"; +my $sMM = "\x00\x01\x54"; + +#-- Timer data +my ($sec,$min,$hour,$day,$month,$year,$wday,$yday,$isdst) = localtime(time); + +#-- parse incoming parameters +my ($count,$buf1)=@_; + +#-- default: do not send data +my $senddata=0; +my ($i,$j,$k); +my (@buf3,@buf3a); + +#-- No bytes received +if( $count == 0) + { + Log 3, "[NT5000 emulator] Zero bytes received, count=0"; + return undef; + } +#-- no sun yet +if( (($hour+$min/60.0-$start)<0) || (($stop-$hour-$min/60.0)<0) ) + { + Log 3, "[NT5000 emulator] No Sun !"; + return undef; + } +#-- 5 bytes received +elsif( $count == 5) + { + my $buf2 = substr($buf1,0,4); + my $buf4 = substr($buf1,0,3); + + #---- Error + if( $buf2 eq $rError ) + { + Log 3, "[NT5000 emulator] Request for error list received"; + my @buf3=($year,$month,$day,$hour, + $min,0,0,0,0,0,0,0); + $senddata=1; + } + + #---- Online block 1 + elsif( $buf2 eq $rOnline1 ) + { + #Log 3 "[NT5000 mulator] Request for online data block 1 received"; + my ($wd,$wdl,$wdh,$wtot,$wtotl,$wtoth,$uac,$udc,$iac,$idc,$pac,$t,$s)=(0,0,0,0,0,0,0,0,0,0,0,0,0); + #-- shift into full day + my $q = ($hour+$min/60.0-$start)/($stop-$start); + if( ($q>0) && ($q<1.0) ) + { + #-- produce fake data + $wd= int( 4500/3.14*($stop-$start)*(1-cos($q*3.14)) ); + $wdl=$wd%256; + $wdh=int(($wd-$wdl)/256); + $wtot=1000+int($wd/1000); + $wtotl=$wtot%256; + $wtoth=int(($wtot-$wtotl)/256); + + $uac=int( 230-100 + 0.5 ); + $udc=int( (600-100)/2.8 + 0.5 ); + $iac=int( 4500/230*sin($q * 3.14)/0.12 + 0.5 ); + $idc=int( 4550/600*sin($q * 3.14)/0.08 + 0.5 ); + + $pac=int(4500*sin($q*3.14))/1000.0; + + $t=10+40; + $s=int(100/6); + } + @buf3=($udc,$idc,$uac,$iac,$t,$s,$wdh,$wdl,$wtoth,$wtotl,0,0); + $senddata=1; + } + + #---- Monthly data block + elsif( substr($buf2,0,3) eq substr($rMon1,0,3) ) + { + my $ica = substr($buf2,3,1); + my $blocknr = ord($ica)%16; + # Log 3, "[NT5000 emulator] Request for monthly data block $blocknr received"; + my ($mon1,$day1,$wdh1,$wdl1,$mon2,$day2,$wdh2,$wdl2,$mon3,$day3,$wdh3,$wdl3)=(0,0,0,0,0,0,0,0,0,0,0,0,0); + + #-- produce fake data + $mon1 = $month; + $day1 = $day+3-$blocknr*3; + my $wd = (19.001 + 2*($day1 % 2))*1000.0; + $wdl1 = $wd%256; + $wdh1 = int(($wd-$wdl1)/256); + $mon2 = $mon1; + $day2 = $day1-1; + if( $day2 < 1 ) + { + $mon2-- if($mon2 > 0); + $day2 = 31; + } + $wd = (19.001 + 2*($day2 % 2))*1000.0; + $wdl2 = ($wd)%256; + $wdh2 = int(($wd-$wdl2)/256); + $mon3 = $mon2; + $day3 = $day2-1; + if( $day3 < 1 ) + { + $mon3-- if($mon3 > 0); + $day3 = 31; + } + $wd = (19.001 + 2*($day3 % 2))*1000.0; + $wdl3=$wd%256; + $wdh3=int(($wd-$wdl3)/256); + + @buf3=($mon1,$day1,$wdh1,$wdl1,$mon2,$day2,$wdh2,$wdl2,$mon3,$day3,$wdh3,$wdl3); + $senddata=1; + } + + #---- Yearly data block + elsif( substr($buf2,0,3) eq substr($rYear1,0,3) ) + { + my $ica = substr($buf2,3,1); + my $blocknr = ord($ica)%16; + Log 3, "[NT5000 emulator] Request for yearly data block $blocknr received"; + my ($wmh1,$wml1,$wmh2,$wml2,$wmh3,$wml3,$wmh4,$wml4,$wmh5,$wml5)=(0,0,0,0,0,0,0,0,0,0); + my @pwm=(1500,2500,3500,4500,6000,6000,6000,6000,4500,3500,2500,1500); + + #-- produce fake data + my $ip; + @buf3=(0,0,0,0,0,0,0,0,0,0,0,0); + if( $blocknr==1){ + $buf3[1]=$month; + for( my $i=0;$i<5;$i++) + { + my $ip=$month-$i; + if( $ip<0 ) + { + $ip += 12; + }; + my $wd=$pwm[$ip]; + my $wdl = ($wd)%256; + my $wdh = int(($wd-$wdl)/256); + $buf3[2*$i+2]=$wdh; + $buf3[2*$i+3]=$wdl; + } + }elsif( $blocknr==2){ + for( my $i=0;$i<6;$i++) + { + my $ip=$month-5-$i; + if( $ip<0 ) + { + $ip += 12; + }; + my $wd=$pwm[$ip]; + my $wdl = ($wd)%256; + my $wdh = int(($wd-$wdl)/256); + $buf3[2*$i]=$wdh; + $buf3[2*$i+1]=$wdl; + } + }else{ + for( my $i=0;$i<2;$i++) + { + my $ip=$month-11-$i; + if( $ip<0 ) + { + $ip += 12; + }; + my $wd=$pwm[$ip]; + my $wdl = ($wd)%256; + my $wdh = int(($wd-$wdl)/256); + $buf3[2*$i]=$wdh; + $buf3[2*$i+1]=$wdl; + } + } + $senddata=1; + } + + #---- Time data + elsif( $buf2 eq $rTime ) + { + Log 3, "[NT5000 emulator] Request for time data received"; + @buf3=($year,$month,$day,$hour, + $min,0,0,0,0,0,0,0,0); + $senddata=1; + } + + #---- Serial number + elsif( $buf2 eq $rSerial ) + { + Log 3, "[NT5000 emulator] Request for serial number received"; + @buf3 = (ord('1'),ord('5'),ord('3'),ord('3'),ord('A'),ord('5'),ord('0'),ord('1'),ord('2'),ord('3'),ord('4'),ord('5')); + $senddata=1; + } + + #---- Protocol and Firmware + elsif( $buf2 eq $rProFW ) + { + Log 3, "[NT5000 emulator] Request for protocol version received"; + @buf3=(ord('1'),ord('1'),ord('1'),ord('-'),ord('2'),ord('3'),0,0,0,0,0,0); + $senddata=1; + } + #---- Set year + elsif( $buf4 eq $sYY ) + { + $year=ord(substr($buf2,3,1)); + Log 3, "[NT5000 eulator] Setting year to $year"; + } + #---- Set month + elsif( $buf4 eq $sLL ) + { + $month=ord(substr($buf2,3,1)); + Log 3, "[NT5000 emulator] Setting month to $month"; + } + #---- Set day + elsif( $buf4 eq $sDD ) + { + $day=ord(substr($buf2,3,1)); + Log 3, "[NT5000 emulator] Setting day to $day"; + } + #---- Set hour + elsif( $buf4 eq $sHH ) + { + $hour=ord(substr($buf2,3,1))-1; + Log 3, "[NT5000 emulator] Setting hour to $hour"; + } + #---- Set minute + elsif( $buf4 eq $sMM ) + { + $min=ord(substr($buf2,3,1))-1; + Log 3, "[NT5000 emulator] Setting minute to $min"; + } + #---- show content + else + { + Log 3, "[NT5000 emulator] Unknown request of 5 bytes received"; + for($i=0;$i<5;$i++) + { $j=int(ord(substr($buf2,$i,1))/16); + $k=ord(substr($buf2,$i,1))%16; + print "byte $i = 0x$j$k\n"; + } + } + #---- Other number of bytes received + } else + { + Log 3, "[NT5000 emulator] $count bytes received"; + for($i=0;$i<$count;$i++) + { $j=int(ord(substr($buf1,$i,1))/16); + $k=ord(substr($buf1,$i,1))%16; + print "byte $i = 0x$j$k\n"; + } + } + #-- Here we are really sending data back to the main program + if( $senddata==1 ) { + #-- calculate checksum + my $CS=0; + for($i=0,$i<=11,$i++) + { + $CS+=$buf3[$i]; + } + $CS=$CS%256; + my $data = sprintf("%c%c%c%c%c%c%c%c%c%c%c%c%c", + $buf3[0],$buf3[1],$buf3[2],$buf3[3], + $buf3[4],$buf3[5],$buf3[6],$buf3[7], + $buf3[8],$buf3[9],$buf3[10],$buf3[11],$CS); + + #-- control + #print "Sending out:"; + #for($i=0;$i<13;$i++) + # { $j=int(ord(substr($data,$i,1))/16); + # $k=ord(substr($data,$i,1))%16; + # print "byte $i = 0x$j$k\n"; + # } + $senddata=0; + return($data); + } + else + { + return undef; + } +} + + + +1;