diff --git a/fhem/FHEM/98_ArduCounter.pm b/fhem/FHEM/98_ArduCounter.pm index b4230d98f..d3aa9a65c 100755 --- a/fhem/FHEM/98_ArduCounter.pm +++ b/fhem/FHEM/98_ArduCounter.pm @@ -39,7 +39,7 @@ # 2016-12-25 check for old firmware and log error, better logging, disable attribute # 2017-01-01 improved logging # 2017-01-02 modification for sketch 1.7, monitor clock drift difference between ardino and Fhem -# 2017-01-04 some more beatification in logging +# 2017-01-04 some more beautification in logging # 2017-01-06 avoid reopening when disable=0 is set during startup # 2017-02-06 Doku korrigiert # 2017-02-18 fixed a bug that caused a missing open when the device is defined while fhem is already initialized @@ -66,8 +66,20 @@ # 2018-04-10 many smaller fixes, new interpolation based on real boot time, counter etc. # 2018-05-13 send keepalive delay with k command, don't reset k timer when parsing a message # 2018-07-17 modify define / notify so connection is opened after Event Defined +# 2018-12-17 modifications to support analog ferraris counters with IR at analog input pin, some smaller bug fixes, +# new attribute pulsesPerKWh, analogThresholds +# reading names now follow the definition of the pin (end on D4 instead of 4 if the pin was defined as pinD4 and not pin4) +# attributes that modify a pin or its reading names also look for the pin name (like D4 instead of 4 or A7 instead of 21) +# 2019-01-12 fixed a small bug in logging +# 2019-01-18 better logging for disallowed pins +# 2019-01-29 changed handling of analog pins to better support future boards like ESP32 # # ideas / todo: +# +# - max time for interpolation as attribute +# +# - convert module to package +# # - OTA Flashing for ESP # # - parse sequence num of history entries -> reconstruct long history list in perl mem @@ -84,22 +96,52 @@ use strict; use warnings; use Time::HiRes qw(gettimeofday); +my $ArduCounter_Version = '6.07 - 27.1.2019'; + + my %ArduCounter_sets = ( "disable" => "", "enable" => "", "raw" => "", "reset" => "", "flash" => "", - "devVerbose" => "", "saveConfig" => "", "reconnect" => "" ); my %ArduCounter_gets = ( - "info" => "" + "info" => "", + "levels" => "" +); + + +my %AnalogPinMap = ( + "NANO" => { + "A0" => 14, + "A1" => 15, + "A2" => 16, + "A3" => 17, + "A4" => 18, + "A5" => 19, + "A6" => 20, + "A7" => 21 }, + "ESP8266" => { + "A0" => 17 } +); +my %rAnalogPinMap = ( + "NANO" => { + 14 => "A0", + 15 => "A1", + 16 => "A2", + 17 => "A3", + 18 => "A4", + 19 => "A5", + 20 => "A6", + 21 => "A7" }, + "ESP8266" => { + 17 => "A0" } ); -my $ArduCounter_Version = '5.94 - 13.5.2018'; # # FHEM module intitialisation @@ -120,28 +162,38 @@ sub ArduCounter_Initialize($) $hash->{AttrFn} = "ArduCounter_Attr"; $hash->{NotifyFn} = "ArduCounter_Notify"; $hash->{AttrList} = - 'pin.* ' . - "interval " . - "factor " . - "readingNameCount[0-9]+ " . - "readingNamePower[0-9]+ " . - "readingNameLongCount[0-9]+ " . - "readingNameInterpolatedCount[0-9]+ " . - "readingFactor[0-9]+ " . - "readingStartTime[0-9]+ " . - "verboseReadings[0-9]+ " . - "flashCommand " . - "helloSendDelay " . - "helloWaitTime " . - "keepAliveDelay " . - "keepAliveTimeout " . - "nextOpenDelay " . - "silentReconnect " . - "openTimeout " . + 'board:UNO,NANO,ESP8266 ' . + 'pin.[AD]?[0-9]+ ' . + 'interval ' . + 'factor ' . # legacy (should be removed, use pulsesPerKwh instead) + 'pulsesPerKWh ' . + 'devVerbose:0,5,10,20 ' . # verbose level of board + 'analogThresholds ' . + 'readingNameCount[AD]?[0-9]+ ' . # raw count for this running period + 'readingNamePower[AD]?[0-9]+ ' . + 'readingNameLongCount[AD]?[0-9]+ ' . # long term count + 'readingNameInterpolatedCount[AD]?[0-9]+ ' . # long term count including interpolation for offline times + 'readingNameCalcCount[AD]?[0-9]+ ' . # new to be implemented by using factor for the counter as well + 'readingFactor[AD]?[0-9]+ ' . + 'readingPulsesPerKWh[AD]?[0-9]+ ' . + 'readingStartTime[AD]?[0-9]+ ' . + 'verboseReadings[AD]?[0-9]+ ' . + 'flashCommand ' . + 'helloSendDelay ' . + 'helloWaitTime ' . + 'configDelay ' . # how many seconds to wait before sending config after reboot of board + 'keepAliveDelay ' . + 'keepAliveTimeout ' . + 'nextOpenDelay ' . + 'silentReconnect:0,1 ' . + 'openTimeout ' . - "disable:0,1 " . - "do_not_notify:1,0 " . + 'disable:0,1 ' . + 'do_not_notify:1,0 ' . $readingFnAttributes; + + # todo: create rAnalogPinMap hash from AnalogPinMap + } # @@ -183,11 +235,7 @@ sub ArduCounter_Define($$) } Log3 $name, 5, "$name: defined with $dev, Module version $ArduCounter_Version"; - #if ($init_done) { - # ArduCounter_Open($hash); - #} - # do open in notify - + # do open in notify after init_done or after a new defined device (also after init_done) return; } @@ -247,8 +295,17 @@ sub ArduCounter_OpenCB($$) } -######################################################## +########################################################################## # Open Device +# called from Notify after init_done or when a new device is defined later, +# or from Ready as reopen, +# from attr when disable is removed / set to 0, +# from set reconnect, reset or after flash, +# from delayed_open when a tcp connection was closed with "already busy" +# +# normally an open also resets the counter board, unless its hardware is modified +# to continue when opened. +# sub ArduCounter_Open($;$) { my ($hash, $reopen) = @_; @@ -286,7 +343,7 @@ sub ArduCounter_Open($;$) DevIo_OpenDev($hash, $reopen, 0, \&ArduCounter_OpenCB); delete $hash->{TIMEOUT}; if ($hash->{FD}) { - Log3 $name, 5, "$name: ArduCounter_Open succeeded immediatelay" if (!$reopen); + Log3 $name, 5, "$name: ArduCounter_Open succeeded immediately" if (!$reopen); } else { Log3 $name, 5, "$name: ArduCounter_Open waiting for callback" if (!$reopen); } @@ -323,8 +380,10 @@ sub ArduCounter_Ready($) } -####################################### -# Aufruf aus InternalTimer +####################################################### +# Aufruf aus InternalTimer +# falls in Parse TCP Connection wieder abgewiesen wird +# weil "already busy" sub ArduCounter_DelayedOpen($) { my $param = shift; @@ -353,6 +412,7 @@ sub ArduCounter_Notify($$) # Log3 $name, 5, "$name: Notify called for source $source->{NAME} with events: @{$events}"; return if (!grep(m/^INITIALIZED|REREADCFG|(MODIFIED $name)|(DEFINED $name)$/, @{$source->{CHANGED}})); + # DEFINED is not triggered if init is not done. if (IsDisabled($name)) { Log3 $name, 3, "$name: Notify / Init: device is disabled"; @@ -385,10 +445,22 @@ sub ArduCounter_Write ($$) } + +########################################################### +# return the name of the caling function for debug output +sub ArduCounter_Caller() +{ + my ($package, $filename, $line, $subroutine, $hasargs, $wantarray, $evaltext, $is_require, $hints, $bitmask, $hinthash) = caller 2; + return $1 if ($subroutine =~ /main::ArduCounter_(.*)/); + return $1 if ($subroutine =~ /main::(.*)/); + return "$subroutine"; +} + + ####################################### # Aufruf aus InternalTimer # send "h" to ask for "Hello" since device didn't say "Started" so far - maybe it's still counting ... -# called with timer from _open, _Ready and if count is read in _Parse +# called with timer from _openCB, _Ready and if count is read in _Parse but no hello was received sub ArduCounter_AskForHello($) { my $param = shift; @@ -481,60 +553,125 @@ sub ArduCounter_ConfigureDevice($) # todo: check if device got disconnected in the meantime! - # first check if device did send its config, then compare and send config if necessary - if ($hash->{runningCfg}) { + my @runningPins = sort grep (/[\d]/, keys %{$hash->{runningCfg}}); + Log3 $name, 5, "$name: ConfigureDevice: pins in running config: @runningPins"; + my @attrPins = sort grep (/pin([dDaA])?[\d]/, keys %{$attr{$name}}); + Log3 $name, 5, "$name: ConfigureDevice: pins from attrs: @attrPins"; + + CHECKS: { + # first check if device did send its config, then compare and send config if necessary + if (!$hash->{runningCfg}) { + Log3 $name, 5, "$name: ConfigureDevice: no running config received"; + last CHECKS; + } Log3 $name, 5, "$name: ConfigureDevice: got running config - comparing"; + my $iAttr = AttrVal($name, "interval", ""); if (!$iAttr) { $iAttr = "30 60 2 2"; Log3 $name, 5, "$name: ConfigureDevice: interval attr not set - take default $iAttr"; } if ($iAttr =~ /^(\d+) (\d+) ?(\d+)? ?(\d+)?$/) { - #Log3 $name, 5, "$name: ConfigureDevice: comparing interval"; - my $iACfg = "$1 $2 " . ($3 ? $3 : "0") . " " . ($4 ? $4 : "0"); - if ($hash->{runningCfg}{I} eq $iACfg) { - #Log3 $name, 5, "$name: ConfigureDevice: interval matches - now compare pins"; - # interval config matches - now check pins as well - my @runningPins = sort grep (/[\d]/, keys %{$hash->{runningCfg}}); - #Log3 $name, 5, "$name: ConfigureDevice: pins in running config: @runningPins"; - my @attrPins = sort grep (/pin([dD])?[\d]/, keys %{$attr{$name}}); - #Log3 $name, 5, "$name: ConfigureDevice: pins from attrs: @attrPins"; - if (@runningPins == @attrPins) { - my $match = 1; - for (my $i = 0; $i < @attrPins; $i++) { - #Log3 $name, 5, "$name: ConfigureDevice: compare pin $attrPins[$i] to $runningPins[$i]"; - $attrPins[$i] =~ /pin[dD]?([\d+]+)/; - my $pinNum = $1; - $runningPins[$i] =~ /pin[dD]?([\d]+)/; - $match = 0 if (!$1 || $1 ne $pinNum); - #Log3 $name, 5, "$name: ConfigureDevice: now compare pin $attrPins[$i] $attr{$name}{$attrPins[$i]} to $hash->{runningCfg}{$pinNum}"; - $match = 0 if (($attr{$name}{$attrPins[$i]}) ne $hash->{runningCfg}{$pinNum}); - } - if ($match) { # Config matches -> leave - Log3 $name, 5, "$name: ConfigureDevice: running config matches attributes"; - return; - } - Log3 $name, 5, "$name: ConfigureDevice: no match -> send config"; - } else { - Log3 $name, 5, "$name: ConfigureDevice: pin numbers don't match (@runningPins vs. @attrPins)"; - } - } else { - Log3 $name, 5, "$name: ConfigureDevice: interval does not match (>$hash->{runningCfg}{I}< vs >$iACfg< from attr)"; + my $iRCfg = ($hash->{runningCfg}{I} ? $hash->{runningCfg}{I} : ""); + my $iACfg = "$1 $2" . ($3 ? " $3" : " 0") . ($4 ? " $4" : " 0"); + Log3 $name, 5, "$name: ConfigureDevice: comparing intervals (>$iRCfg< vs >$iACfg< from attr)"; + if (!$iRCfg || $iRCfg ne $iACfg) { + Log3 $name, 5, "$name: ConfigureDevice: intervals don't match (>$iRCfg< vs >$iACfg< from attr)"; + last CHECKS; } } else { - Log3 $name, 5, "$name: ConfigureDevice: can not compare against interval attr"; + Log3 $name, 3, "$name: ConfigureDevice: can not compare against interval attr - wrong format"; + } + + my $tAttr = AttrVal($name, "analogThresholds", ""); + if (!$tAttr) { + Log3 $name, 3, "$name: ConfigureDevice: no analogThresholds attribute"; + } else { + if ($tAttr =~ /^(\d+) (\d+)/) { + my $tRCfg = ($hash->{runningCfg}{T} ? $hash->{runningCfg}{T} : ""); + my $tACfg = "$1 $2"; + Log3 $name, 5, "$name: ConfigureDevice: comparing analog Thresholds (>$tRCfg< vs >$tACfg< from attr)"; + if (!$tRCfg || ($tRCfg ne $tACfg)) { + Log3 $name, 5, "$name: ConfigureDevice: analog Thresholds don't match (>$tRCfg< vs >$tACfg< from attr)"; + last CHECKS; + } + } else { + Log3 $name, 3, "$name: ConfigureDevice: can not compare against analogThreshold attr - wrong format"; + } + + } + + Log3 $name, 5, "$name: ConfigureDevice: matches so far - now compare pins"; + # interval config matches - now check pins as well + if (@runningPins != @attrPins) { + Log3 $name, 5, "$name: ConfigureDevice: number of defined pins doesn't match (@runningPins vs. @attrPins)"; + last CHECKS; + } + for (my $i = 0; $i < @attrPins; $i++) { + Log3 $name, 5, "$name: ConfigureDevice: compare pin $attrPins[$i] to $runningPins[$i]"; + $attrPins[$i] =~ /pin([dDaA])?([\d+]+)/; + my $type = $1; + my $aPinNum = $2; # pin number from attr + + $aPinNum = ArduCounter_PinNumber($hash, $type.$aPinNum) if ($type eq 'A'); + if (!$aPinNum) { # should never happen, because board type is known and pin was allowed + Log3 $name, 5, "$name: ConfigureDevice can not compare pin config for $attrPins[$i], internal pin number can not be determined"; + last CHECKS; + } + + last CHECKS if (!$hash->{runningCfg}{$aPinNum}); + Log3 $name, 5, "$name: ConfigureDevice: now compare $attr{$name}{$attrPins[$i]} to $hash->{runningCfg}{$aPinNum}"; + + last CHECKS if ($attr{$name}{$attrPins[$i]} !~ /^(rising|falling) ?(pullup)? ?([0-9]+)?/); + my $aEdge = $1; + my $aPull = ($2 ? $2 : "nop"); + my $aMin = ($3 ? $3 : ""); + last CHECKS if ($hash->{runningCfg}{$aPinNum} !~ /^(rising|falling|-) ?(pullup|nop)? ?([0-9]+)?/); + my $cEdge = $1; + my $cPull = ($2 ? $2 : ""); + my $cMin = ($3 ? $3 : ""); + + last CHECKS if ($aEdge ne $cEdge || $aPull ne $cPull || $aMin ne $cMin); + + } + Log3 $name, 5, "$name: ConfigureDevice: running config matches attributes"; + return; + } + Log3 $name, 5, "$name: ConfigureDevice: now check for pins without attr in @runningPins"; + my %cPins; # get all pins from running config in a hash to find out if one is not defined on fhem side + for (my $i = 0; $i < @runningPins; $i++) { + $cPins{$runningPins[$i]} = 1; + #Log3 $name, 3, "$name: ConfigureDevice remember pin $runningPins[$i]"; + } + # send attributes to arduino device. Just call ArduCounter_Attr again + Log3 $name, 3, "$name: ConfigureDevice: no match -> send config"; + while (my ($aName, $val) = each(%{$attr{$name}})) { + if ($aName =~ /^(interval|analogThresholds)/) { + Log3 $name, 5, "$name: ConfigureDevice calls Attr with $aName $val"; + ArduCounter_Attr("set", $name, $aName, $val); + } elsif ($aName =~ /^pin([dDaA])?([\d+]+)/) { + my $type = $1; + my $num = $2; + my $aPinNum = ArduCounter_PinNumber($hash, "A$num") if ($type =~ /[aA]/); + if ($aPinNum) { + delete $cPins{$aPinNum}; + #Log3 $name, 5, "$name: ConfigureDevice ignore pin $aPinNum"; + Log3 $name, 5, "$name: ConfigureDevice calls Attr with $aName $val"; + ArduCounter_Attr("set", $name, $aName, $val); + } else { + Log3 $name, 5, "$name: ConfigureDevice can not send pin config for $aName, internal pin number can not be determined"; + } + } + } + if (%cPins) { + my $pins = join ",", keys %cPins; + Log3 $name, 5, "$name: ConfigureDevice: pins in running config without attribute in Fhem: $pins"; + foreach my $pin (keys %cPins) { + Log3 $name, 5, "$name: ConfigureDevice: removing pin $pin"; + ArduCounter_Write($hash, "${pin}d"); } } else { - Log3 $name, 5, "$name: ConfigureDevice: no running config received"; - } - - # send attributes to arduino device. Just call ArduCounter_Attr again - Log3 $name, 3, "$name: sending configuration from attributes to device"; - while (my ($aName, $val) = each(%{$attr{$name}})) { - if ($aName =~ "pin|interval") { - Log3 $name, 3, "$name: ConfigureDevice calls Attr with $aName $val"; - ArduCounter_Attr("set", $name, $aName, $val); - } + Log3 $name, 5, "$name: ConfigureDevice: no pins in running config without attribute in Fhem"; } } @@ -554,16 +691,27 @@ sub ArduCounter_Attr(@) #Log3 $name, 5, "$name: Attr called with @_"; if ($cmd eq "set") { - if ($aName =~ /^pin[dD]?(\d+)/) { - my $pin = $1; - my %pins; - if ($hash->{allowedPins}) { - %pins = map { $_ => 1 } split (",", $hash->{allowedPins}); + if ($aName =~ /^pin([DA]?)(\d+)/) { + if (!$hash->{Initialized}) { # no hello received yet + Log3 $name, 5, "$name: pin validation and communication postponed until device is initialized"; + return undef; # accept attribute but don't send it to the device yet. + } + # board did send hello already and therefore allowedPins and Board should be set ... + my $pinType = $1; + my $pin = $2; + if ($hash->{allowedPins}) { # list of allowed pins received with hello + my %pins = map { $_ => 1 } split (",", $hash->{allowedPins}); + if ($init_done && %pins && !$pins{$pin}) { + Log3 $name, 3, "$name: Invalid pin in attr $name $aName $aVal"; + return "Invalid / disallowed pin specification $aName. The board reports $hash->{allowedPins} as allowed."; + } + } + $pin = ArduCounter_PinNumber($hash, $pinType.$pin) if ($pinType eq 'A'); + if (!$pin) { + # this should never happen since Board is known and Pin was already verified to be allowed. + Log3 $name, 3, "$name: can not determine internal pin number for attr $name $aName $aVal"; + return "pin specification is not valid or something went wrong. Check the logs"; } - if ($init_done && $hash->{allowedPins} && %pins && !$pins{$pin}) { - Log3 $name, 3, "$name: Invalid pin in attr $name $aName $aVal"; - return "Invalid / disallowed pin specification $aName"; - } if ($aVal =~ /^(rising|falling) ?(pullup)? ?([0-9]+)?/) { my $opt = ""; if ($1 eq 'rising') {$opt = "3"} @@ -571,16 +719,55 @@ sub ArduCounter_Attr(@) $opt .= ($2 ? ",1" : ",0"); # pullup $opt .= ($3 ? ",$3" : ""); # min length - if ($hash->{Initialized}) { + if ($hash->{Initialized}) { # hello already received ArduCounter_Write($hash, "${pin},${opt}a"); } else { - Log3 $name, 5, "$name: communication postponed until device is initialized"; + } } else { Log3 $name, 3, "$name: Invalid value in attr $name $aName $aVal"; return "Invalid Value $aVal"; } + } elsif ($aName =~ /^pin.*/) { + Log3 $name, 3, "$name: Invalid pin specification in attr $name $aName $aVal. Use something like pinD4 or PinA7"; + return "Invalid pin specification in attr $name $aName $aVal. Use something like pinD4 or PinA7"; + + } elsif ($aName eq "devVerbose") { + if ($aVal =~ /^(\d+)\s*$/) { + my $t = $1; + if ($t > 100) { + Log3 $name, 3, "$name: Invalid value in attr $name $aName $aVal"; + return "Invalid Value $aVal"; + } + if ($hash->{Initialized}) { + ArduCounter_Write($hash, "${t}v"); + } else { + Log3 $name, 5, "$name: communication postponed until device is initialized"; + } + } else { + Log3 $name, 3, "$name: Invalid value in attr $name $aName $aVal"; + return "Invalid Value $aVal"; + } + + } elsif ($aName eq "analogThresholds") { + if ($aVal =~ /^(\d+) (\d+)\s*$/) { + my $min = $1; + my $max = $2; + if ($min < 1 || $min > 1023 || $max < $min || $max > 1023) { + Log3 $name, 3, "$name: Invalid value in attr $name $aName $aVal"; + return "Invalid Value $aVal"; + } + if ($hash->{Initialized}) { + ArduCounter_Write($hash, "${min},${max}t"); + } else { + Log3 $name, 5, "$name: communication postponed until device is initialized"; + } + } else { + Log3 $name, 3, "$name: Invalid value in attr $name $aName $aVal"; + return "Invalid Value $aVal"; + } + } elsif ($aName eq "interval") { if ($aVal =~ /^(\d+) (\d+) ?(\d+)? ?(\d+)?$/) { my $min = $1; @@ -667,11 +854,12 @@ sub ArduCounter_Attr(@) } elsif ($cmd eq "del") { if ($aName =~ 'pin.*') { - if ($aName !~ 'pin([dD]?\d+)') { + if ($aName !~ 'pin([aAdD]?\d+)') { Log3 $name, 3, "$name: Invalid pin name in attr $name $aName $aVal"; return "Invalid pin name $aName"; } my $pin = $1; + # todo: convert to internal value with AnalogPinMap if ($hash->{Initialized}) { # did device already report its version? ArduCounter_Write( $hash, "${pin}d"); @@ -817,9 +1005,10 @@ sub ArduCounter_Set($@) } } elsif ($attr eq "devVerbose") { - if ($arg =~ /^\d$/) { + if ($arg =~ /^\d+$/) { Log3 $name, 4, "$name: set devVerbose $arg called"; ArduCounter_Write($hash, "$arg"."v"); + delete $hash->{analogLevels} if ($arg eq "0"); } else { Log3 $name, 4, "$name: set devVerbose called with illegal value $arg"; } @@ -855,9 +1044,15 @@ sub ArduCounter_Get($@) if ($attr eq "info") { Log3 $name, 3, "$name: Sending info command to device"; ArduCounter_Write( $hash, "s"); - my ($err, $msg) = ArduCounter_ReadAnswer($hash, 'Next report in.*seconds'); - + my ($err, $msg) = ArduCounter_ReadAnswer($hash, 'Next report in.*seconds'); return ($err ? $err : $msg); + + } elsif ($attr eq "levels") { + my $msg = ""; + foreach my $level (sort {$a <=> $b} keys %{$hash->{analogLevels}}) { + $msg .= "$level: $hash->{analogLevels}{$level}\n"; + } + return "observed levels from analog input:\n$msg\n"; } return undef; @@ -903,134 +1098,247 @@ sub ArduCounter_ParseHello($$$) my ($hash, $line, $now) = @_; my $name = $hash->{NAME}; - if ($line =~ /^ArduCounter V([\d\.]+) on ([^\ ]+ ?[^\ ]*) compiled (.*) Hello(, pins ([0-9\,]+) available)? ?(T([\d]+),([\d]+) B([\d]+),([\d]+))?/) { # setup / hello message - $hash->{VersionFirmware} = ($1 ? $1 : "unknown"); - $hash->{Board} = ($2 ? $2 : "unknown"); - $hash->{SketchCompile} = ($3 ? $3 : "unknown"); - $hash->{allowedPins} = $5 if ($5); - my $mNow = ($7 ? $7 : 0); - my $mNowW = ($8 ? $8 : 0); - my $mBoot = ($9 ? $9 : 0); - my $mBootW = ($10 ? $10 : 0); + if ($line =~ /^ArduCounter V([\d\.]+) on ([^\ ]+)( ?[^\ ]*) compiled (.*) Hello(, pins ([0-9\,]+) available)? ?(T([\d]+),([\d]+) B([\d]+),([\d]+))?/) { # setup / hello message + $hash->{VersionFirmware} = ($1 ? $1 : 'unknown'); + $hash->{Board} = ($2 ? $2 : 'unknown'); + $hash->{BoardDet} = ($3 ? $3 : ''); + $hash->{SketchCompile} = ($4 ? $4 : 'unknown'); + $hash->{allowedPins} = $6 if ($6); + my $mNow = ($8 ? $8 : 0); + my $mNowW = ($9 ? $9 : 0); + my $mBoot = ($10 ? $10 : 0); + my $mBootW = ($11 ? $11 : 0); if ($hash->{VersionFirmware} < "2.36") { $hash->{VersionFirmware} .= " - not compatible with this Module version - please flash new sketch"; Log3 $name, 3, "$name: device reported outdated Arducounter Firmware ($hash->{VersionFirmware}) - please update!"; delete $hash->{Initialized}; } else { Log3 $name, 3, "$name: device sent hello: $line"; - $hash->{Initialized} = 1; # now device has finished its boot and reported its version + $hash->{Initialized} = 1; # now device has finished its boot and reported its version delete $hash->{runningCfg}; - my $cft = AttrVal($name, "ConfigDelay", 1); # wait for device to send cfg before reconf. + my $cft = AttrVal($name, "configDelay", 1); # wait for device to send cfg before reconf. RemoveInternalTimer ("cmpCfg:$name"); InternalTimer($now+$cft, "ArduCounter_ConfigureDevice", "cmpCfg:$name", 0); my $deviceNowSecs = ($mNow/1000) + ((0xFFFFFFFF / 1000) * $mNowW); my $deviceBootSecs = ($mBoot/1000) + ((0xFFFFFFFF / 1000) * $mBootW); my $bootTime = $now - ($deviceNowSecs - $deviceBootSecs); - $hash->{deviceBooted} = $bootTime; # for estimation of missed pulses up to now + $hash->{deviceBooted} = $bootTime; # for estimation of missed pulses up to now + + my $boardAttr = AttrVal($name, 'board', ''); + if ($hash->{Board} && $boardAttr && ($hash->{Board} ne $boardAttr)) { + Log3 $name, 3, "attribute board is set to $boardAttr and is overwriting board $hash->{Board} reported by device"; + $hash->{Board} = $boardAttr; + } + # now enrich $hash->{allowedPins} with $rAnalogPinMap{$hash->{Board}}{$pin} + if ($hash->{allowedPins} && $hash->{Board}) { + my $newAllowed; + my $first = 1; + foreach my $pin (split (",", $hash->{allowedPins})) { + $newAllowed .= ($first ? '' : ','); # separate by , if not empty anymore + $newAllowed .= $pin; + if ($rAnalogPinMap{$hash->{Board}}{$pin}) { + $newAllowed .= ",$rAnalogPinMap{$hash->{Board}}{$pin}"; + } + $first = 0; + } + $hash->{allowedPins} = $newAllowed; + } } delete $hash->{WaitForHello}; - RemoveInternalTimer ("hwait:$name"); # dont wait for hello reply if already sent - RemoveInternalTimer ("sendHello:$name"); # Hello not needed anymore if not sent yet + RemoveInternalTimer ("hwait:$name"); # dont wait for hello reply if already sent + RemoveInternalTimer ("sendHello:$name"); # Hello not needed anymore if not sent yet } else { Log3 $name, 4, "$name: probably wrong firmware version - cannot parse line $line"; } } +###################################### +# $hash->{Board} wird in parseHello gesetzt und ggf. dort gleich durch das Attribut Board überschrieben +# called from Attr and ConfigureDevice. +# in all cases Board and AllowedPins have been received with hello before +sub ArduCounter_PinNumber($$) +{ + my ($hash, $pinName) = @_; + my $name = $hash->{NAME}; + my $boardAttr = AttrVal($name, "board", ""); + my $board = ($boardAttr ? $boardAttr : $hash->{Board}); + my $pin; + + if (!$board) { # maybe no hello received yet and no Board-attr set (should never be the case) + my @boardOptions = keys %AnalogPinMap; + my $count = 0; + foreach my $candidate (@boardOptions) { + if ($AnalogPinMap{$candidate}{$pinName}) { + $board = $AnalogPinMap{$candidate}{$pinName}; + $count++; + } + } + if ($count > 1) { + Log3 $name, 3, "$name: PinNumber called from " . ArduCounter_Caller() . " can not determine internal pin number for $pinName, board type is not known (yet) and attribute Board is also not set"; + } elsif (!$count) { + Log3 $name, 3, "$name: PinNumber called from " . ArduCounter_Caller() . " can not determine internal pin number for $pinName. No known board seems to support it"; + } + } + $pin = $AnalogPinMap{$board}{$pinName} if ($board); + if ($pin) { + Log3 $name, 5, "$name: PinNumber called from " . ArduCounter_Caller() . " returns $pin for $pinName"; + } else { + Log3 $name, 5, "$name: PinNumber called from " . ArduCounter_Caller() . " returns unknown for $pinName"; + } + return $pin # might be undef +} + + +###################################### +sub ArduCounter_PinName($$) +{ + my ($hash, $pin) = @_; + my $name = $hash->{NAME}; + + my $pinName = $pin; # start assuming that attrs are set as pinX + if (!AttrVal($name, "pin$pinName", 0)) { # if not + if (AttrVal($name, "pinD$pin", 0)) { + $pinName = "D$pin"; # maybe pinDX? + #Log3 $name, 5, "$name: using attrs with pin name D$pin"; + } elsif ($hash->{Board}) { + my $aPin = $rAnalogPinMap{$hash->{Board}}{$pin}; + if ($aPin) { # or pinAX? + $pinName = "$aPin"; + #Log3 $name, 5, "$name: using attrs with pin name $pinName instead of $pin or D$pin (Board $hash->{Board})"; + } + } + } + return $pinName; +} + + +sub AduCounter_AttrVal($$$;$$$) +{ + my ($hash, $default, $a1, $a2, $a3, $a4) = @_; + my $name = $hash->{NAME}; + return AttrVal($name, $a1, undef) if (defined (AttrVal($name, $a1, undef))); + return AttrVal($name, $a2, undef) if (defined ($a2) && defined (AttrVal($name, $a2, undef))); + return AttrVal($name, $a3, undef) if (defined ($a3) && defined (AttrVal($name, $a3, undef))); + return AttrVal($name, $a4, undef) if (defined ($a4) && defined (AttrVal($name, $a4, undef))); + return $default; +} + + +###################################### +sub ArduCounter_LogPinDesc($$) +{ + my ($hash, $pin) = @_; + my $pinName = ArduCounter_PinName ($hash, $pin); + return AduCounter_AttrVal($hash, "pin$pin", "readingNameCount$pinName", "readingNameCount$pin", "readingNamePower$pinName", "readingNamePower$pin"); +} + + ######################################################################### sub ArduCounter_HandleCounters($$$$$$$$) { - my ($hash, $pin, $sequence, $count, $time, $diff, $rDiff, $now) = @_; + my ($hash, $pin, $seq, $count, $time, $diff, $rDiff, $now) = @_; my $name = $hash->{NAME}; - my $rcname = AttrVal($name, "readingNameCount$pin", "pin$pin"); # internal count reading - my $rlname = AttrVal($name, "readingNameLongCount$pin", "long$pin"); # long count - my $riname = AttrVal($name, "readingNameInterpolatedCount$pin", "interpolatedLong$pin"); - my $lName = AttrVal($name, "readingNamePower$pin", AttrVal($name, "readingNameCount$pin", "pin$pin")); # for logging + my $pinName = ArduCounter_PinName ($hash, $pin); - my $longCount = ReadingsVal($name, $rlname, 0); # alter long count Wert - my $intpCount = ReadingsVal($name, $riname, 0); # alter interpolated count Wert + my $rcname = AduCounter_AttrVal($hash, "pin$pinName", "readingNameCount$pinName", "readingNameCount$pin"); + my $rlname = AduCounter_AttrVal($hash, "long$pinName", "readingNameLongCount$pinName", "readingNameLongCount$pin"); + my $riname = AduCounter_AttrVal($hash, "interpolatedLong$pinName", "readingNameInterpolatedCount$pinName", "readingNameInterpolatedCount$pin"); + my $rccname = AduCounter_AttrVal($hash, "calcCounter$pinName", "readingNameCalcCount$pinName", "readingNameCalcCount$pin"); + my $ppk = AduCounter_AttrVal($hash, 0, "readingPulsesPerKWh$pin", "pulsesPerKWh"); + my $lName = ArduCounter_LogPinDesc($hash, $pin); + + my $longCount = ReadingsVal($name, $rlname, 0); # alter long count Wert + my $intpCount = ReadingsVal($name, $riname, 0); # alter interpolated count Wert my $lastCount = ReadingsVal($name, $rcname, 0); - my $lastSeq = ReadingsVal($name, "seq".$pin, 0); + my $cCounter = ReadingsVal($name, $rccname, 0); # calculated counter + my $iSum = ReadingsVal($name, $rccname . "_i", 0); # interpolation sum + my $lastSeq = ReadingsVal($name, "seq".$pinName, 0); + my $intrCount = 0; # interpolated count to be added - my $lastCountTS = ReadingsTimestamp ($name, $rlname, 0); # last time long count reading was set - my $lastCountTNum = time_str2num($lastCountTS); - my $fBootTim = ($hash->{deviceBooted} ? FmtTime($hash->{deviceBooted}) : "never"); # time device booted - my $fLastCTim = FmtTime($lastCountTNum); - my $pulseGap = $count - $lastCount - $rDiff; - my $seqGap = $sequence - ($lastSeq + 1); + my $lastCountTS = ReadingsTimestamp ($name, $rlname, 0); # last time long count reading was set as string + my $lastCountTNum = time_str2num($lastCountTS); # time as number + my $fLastCTim = FmtTime($lastCountTNum); # formatted for logging + my $pLog = "$name: pin $pinName ($lName)"; # start of log lines + + my $fBootTim; + my $deviceBooted; + if ($hash->{deviceBooted} && $lastCountTS && $hash->{deviceBooted} > $lastCountTNum) { + $deviceBooted = 1; # first report for this pin after a restart + $fBootTim = FmtTime($hash->{deviceBooted}) ; # time device booted + } # without old readings, interpolation makes no sense anyway + + my $countStart = $count - $rDiff; # count at start of this reported interval + $countStart = 0 if ($countStart < 0); - if (!$lastCountTS && !$longCount && !$intpCount) { - # new defined or deletereading done ... - Log3 $name, 3, "$name: pin $pin ($lName) first report, initializing counters to " . ($count - $rDiff); - $longCount = $count - $rDiff; - $intpCount = $count - $rDiff; - } - if ($lastCountTS && $hash->{deviceBooted} && $hash->{deviceBooted} > $lastCountTNum) { - # first report for this pin after a restart - # -> do interpolation for period between last report before boot and boot time. count after boot has to be added later - Log3 $name, 5, "$name: pin $pin ($lName) device restarted at $fBootTim, last reported at $fLastCTim, sequence for pin $pin changed from $lastSeq to $sequence and count from $lastCount to $count"; - $lastSeq = 0; - $seqGap = $sequence - 1; # $sequence should be 1 after restart - $pulseGap = $count - $rDiff; # + my $timeGap = ($now - $time/1000 - $lastCountTNum); # time between last report and start of currently reported interval + $timeGap = 0 if ($timeGap < 0 || !$lastCountTS); + + my $seqGap = $seq - ($lastSeq + 1); # gap of reporting sequences if any + $seqGap = 0 if (!$lastCountTS); # readings didn't exist yet + if ($seqGap < 0) { # new sequence number is smaller than last + $seqGap %= 256; # correct seq gap + Log3 $name, 5, "$pLog sequence wrapped from $lastSeq to $seq, set seqGap to $seqGap" if (!$deviceBooted); + } + + my $pulseGap = $countStart - $lastCount; # gap of missed pulses if any + $pulseGap = 0 if (!$lastCountTS); # readings didn't exist yet + if ($pulseGap < 0) { # pulseGap < 0 should not happen + $pulseGap = 0; + Log3 $name, 3, "$pLog seems to have missed $seqGap reports in $timeGap seconds. " . + "Last reported sequence was $lastSeq, now $seq. " . + "Device count before was $lastCount, now $count with rDiff $rDiff " . + "but pulseGap is $pulseGap. this is probably wrong and should not happen" if (!$deviceBooted); + } + + if ($deviceBooted) { # first report for this pin after a restart -> do interpolation + # interpolate for period between last report before boot and boot time. + Log3 $name, 5, "$pLog device restarted at $fBootTim, last reported at $fLastCTim, " . + "count changed from $lastCount to $count, sequence from $lastSeq to $seq"; + $seqGap = $seq - 1; # $seq should be 1 after restart + $pulseGap = $countStart; # we missed everything up to the count at start of the reported interval - my $lastInterval = ReadingsVal ($name, "timeDiff$pin", 0); - my $lastCDiff = ReadingsVal ($name, "countDiff$pin", 0); - my $offlTime = sprintf ("%.2f", $hash->{deviceBooted} - $lastCountTNum); + my $lastInterval = ReadingsVal ($name, "timeDiff$pinName", 0); # time diff of last interval (old reading) + my $lastCDiff = ReadingsVal ($name, "countDiff$pinName", 0); # count diff of last interval (old reading) + my $offlTime = sprintf ("%.2f", $hash->{deviceBooted} - $lastCountTNum); # estimated offline time (last report in readings until boot) - if ($lastCountTS && $lastInterval && ($offlTime > 0) && ($offlTime < 12*60*60)) { # > 0 and < 12h + if ($lastInterval && ($offlTime > 0) && ($offlTime < 12*60*60)) { # offline > 0 and < 12h my $lastRatio = $lastCDiff / $lastInterval; my $curRatio = $diff / $time; - my $intRatio = 1000 * ($lastRatio + $curRatio) / 2; - my $intrCount = int(($offlTime * $intRatio)+0.5); - - Log3 $name, 3, "$name: pin $pin ($lName) interpolating for $offlTime secs until boot, $intrCount estimated pulses (before $lastCDiff in $lastInterval ms, now $diff in $time ms, avg ratio $intRatio p/s)"; - Log3 $name, 5, "$name: pin $pin ($lName) adding interpolated $intrCount to interpolated count $intpCount"; - $intpCount += $intrCount; - + my $intRatio = 1000 * ($lastRatio + $curRatio) / 2; + $intrCount = int(($offlTime * $intRatio)+0.5); + Log3 $name, 3, "$pLog interpolating for $offlTime secs until boot, $intrCount estimated pulses (before $lastCDiff in $lastInterval ms, now $diff in $time ms, avg ratio $intRatio p/s)"; } else { - Log3 $name, 4, "$name: interpolation of missed pulses for pin $pin ($lName) not possible - no valid historic data."; - } - } elsif ($lastCountTS && $seqGap < 0) { - # new sequence number is smaller than last and we have old readings - # and this is not after a reboot of the device - $seqGap += 256; # correct seq gap - Log3 $name, 5, "$name: pin $pin ($lName) sequence wrapped from $lastSeq to $sequence, set seqGap to $seqGap"; + Log3 $name, 4, "$pLog interpolation of missed pulses for pin $pinName ($lName) not possible - no valid historic data."; + } } - - if ($lastCountTS && $seqGap > 0) { - # probably missed a report. Maybe even the first ones after a reboot (until reconnect) - # take last count, delta to new reported count as missed pulses to correct long counter - my $timeGap = ($now - $time/1000 - $lastCountTNum); - if ($pulseGap > 0) { - $longCount += $pulseGap; - $intpCount += $pulseGap; - Log3 $name, 3, "$name: pin $pin ($lName) missed $seqGap reports in $timeGap seconds. Last reported sequence was $lastSeq, now $sequence. Device count before was $lastCount, now $count with rDiff $rDiff. Adding $pulseGap to long count and intpolated count readings"; - } elsif ($pulseGap == 0) { - # outdated sketch? - Log3 $name, 5, "$name: pin $pin ($lName) missed $seqGap sequence numbers in $timeGap seconds. Last reported sequence was $lastSeq, now $sequence. Device count before was $lastCount, now $count with rDiff $rDiff. Nothing is missing - ignore"; - } else { - # strange ... - Log3 $name, 3, "$name: Pin $pin ($lName) missed $seqGap reports in $timeGap seconds. " . - "Last reported sequence was $lastSeq, now $sequence. " . - "Device count before was $lastCount, now $count with rDiff $rDiff " . - "but pulseGap is $pulseGap. this is wrong and should not happen"; - } - } - Log3 $name, 5, "$name: pin $pin ($lName) adding rDiff $rDiff to long count $longCount and interpolated count $intpCount"; + Log3 $name, 3, "$pLog missed $seqGap reports in $timeGap seconds. Last reported sequence was $lastSeq, " . + "now $seq. Device count before was $lastCount, now $count with rDiff $rDiff. " . + "Adding $pulseGap to long count and intpolated count readings" if ($pulseGap > 0); + Log3 $name, 5, "$pLog adding rDiff $rDiff to long count $longCount and interpolated count $intpCount"; + Log3 $name, 5, "$pLog adding interpolated $intrCount to interpolated count $intpCount" if ($intrCount); + + $intpCount += ($rDiff + $pulseGap + $intrCount); + $longCount += ($rDiff + $pulseGap); + if ($ppk) { + $cCounter += ($rDiff + $pulseGap + $intrCount) / $ppk; # add to calculated counter + $iSum += $intrCount / $ppk; # sum of interpolation kWh + } - $intpCount += $rDiff; - $longCount += $rDiff; - - readingsBulkUpdate($hash, $rcname, $count); - readingsBulkUpdate($hash, $rlname, $longCount); - readingsBulkUpdate($hash, $riname, $intpCount); - readingsBulkUpdate($hash, "seq".$pin, $sequence); + readingsBulkUpdate($hash, $rcname, $count); # device internal counter + readingsBulkUpdate($hash, $rlname, $longCount); # Fhem long counterr + readingsBulkUpdate($hash, $riname, $intpCount); # Fhem interpolated counter + if ($ppk) { + readingsBulkUpdate($hash, $rccname, $cCounter); # Fhem calculated / interpolated counter + readingsBulkUpdate($hash, $rccname . "_i", $iSum); # Fhem interpolation sum + } + readingsBulkUpdate($hash, "seq".$pinName, $seq); # Sequence number } - + ######################################################################### sub ArduCounter_ParseReport($$) @@ -1041,79 +1349,88 @@ sub ArduCounter_ParseReport($$) if ($line =~ '^R([\d]+) C([\d]+) D([\d]+) ?[\/R]([\d]+) T([\d]+) N([\d]+),([\d]+) X([\d]+)( S[\d]+)?( A[\d]+)?') { # new count is beeing reported - my $pin = $1; - my $count = $2; # internal counter at device - my $diff = $3; # delta during interval - my $rDiff = $4; # real delta including the first pulse after a restart - my $time = $5; # interval - my $deTime = $6; - my $deTiW = $7; - my $reject = $8; - my $seq = ($9 ? substr($9, 2) : ""); - my $avgLen = ($10 ? substr($10, 2) : ""); + my $pin = $1; + my $count = $2; # internal counter at device + my $diff = $3; # delta during interval + my $rDiff = $4; # real delta including the first pulse after a restart + my $time = $5; # interval in ms + my $deTime = $6; + my $deTiW = $7; + my $reject = $8; + my $seq = ($9 ? substr($9, 2) : ""); + my $avgLen = ($10 ? substr($10, 2) : ""); + my $pinName = ArduCounter_PinName($hash, $pin); - my $factor = AttrVal($name, "readingFactor$pin", AttrVal($name, "factor", 1000)); - my $rpname = AttrVal($name, "readingNamePower$pin", "power$pin"); # power reading name - my $lName = AttrVal($name, "readingNamePower$pin", AttrVal($name, "readingNameCount$pin", "pin$pin")); # for logging + # now get pin specific reading names and options - first try with pinName, then pin Number, then generic fallback for all pins + my $factor = AduCounter_AttrVal($hash, 1000, "readingFactor$pinName", "readingFactor$pin", "factor"); + my $ppk = AduCounter_AttrVal($hash, 0, "readingPulsesPerKWh$pinName", "readingPulsesPerKWh$pin", "pulsesPerKWh"); + my $rpname = AduCounter_AttrVal($hash, "power$pinName", "readingNamePower$pinName", "readingNamePower$pin"); + my $lName = ArduCounter_LogPinDesc($hash, $pin); + my $pLog = "$name: pin $pinName ($lName)"; # start of log lines - my $sTime = $now - $time/1000; # start of observation interval (~first pulse) - my $fSTime = FmtDateTime($sTime); # formatted - my $fSdTim = FmtTime($sTime); # only time formatted for logging - my $fEdTim = FmtTime($now); # end of Interval - only time formatted for logging + my $sTime = $now - $time/1000; # start of observation interval (~first pulse) in secs (floating point) + my $fSTime = FmtDateTime($sTime); # formatted + my $fSdTim = FmtTime($sTime); # only time formatted for logging + my $fEdTim = FmtTime($now); # end of Interval - only time formatted for logging ArduCounter_HandleDeviceTime($hash, $deTime, $deTiW, $now); if (!$time || !$factor) { - Log3 $name, 3, "$name: Pin $pin ($lName) skip line because time or factor is 0: $line"; + Log3 $name, 3, "$pLog skip line because time or factor is 0: $line"; return; + } + + my $power; + if ($ppk) { # new calculation with pulsee or rounds per unit (kWh) + $power = sprintf ("%.3f", ($time ? ($diff/$time) * (3600000 / $ppk) : 0)); + } else { # old calculation with a factor that is hard to understand + $power = sprintf ("%.3f", ($time ? $diff/$time/1000*3600*$factor : 0)); } - my $power = sprintf ("%.3f", ($time ? $diff/$time/1000*3600*$factor : 0)); - Log3 $name, 4, "$name: Pin $pin ($lName) Cnt $count " . + + Log3 $name, 4, "$pLog Cnt $count " . "(diff $diff/$rDiff) in " . sprintf("%.3f", $time/1000) . "s" . - " from $fSdTim until $fEdTim" . - ", seq $seq" . + " from $fSdTim until $fEdTim, seq $seq" . ((defined($reject) && $reject ne "") ? ", Rej $reject" : "") . (defined($avgLen) ? ", Avg ${avgLen}ms" : "") . ", result $power"; - if (AttrVal($name, "readingStartTime$pin", 0)) { - readingsBeginUpdate($hash); # special block with potentially manipulates times - # special way to set readings: use time of interval start as reading time - Log3 $name, 5, "$name: readingStartTime$pin specified: setting timestamp to $fSdTim"; + if (AttrVal($name, "readingStartTime$pinName", AttrVal($name, "readingStartTime$pin", 0))) { + readingsBeginUpdate($hash); # special block: use time of interval start as reading time + Log3 $name, 5, "$pLog readingStartTime$pinName specified: setting timestamp to $fSdTim"; my $chIdx = 0; $hash->{".updateTime"} = $sTime; $hash->{".updateTimestamp"} = $fSTime; readingsBulkUpdate($hash, $rpname, $power) if ($time); - $hash->{CHANGETIME}[$chIdx++] = $fSTime; # Intervall start - readingsEndUpdate($hash, 1); # end of special block - readingsBeginUpdate($hash); # start regular update block + $hash->{CHANGETIME}[$chIdx++] = $fSTime; # Intervall start + readingsEndUpdate($hash, 1); # end of special block + readingsBeginUpdate($hash); # start regular update block } else { # normal way to set readings - readingsBeginUpdate($hash); # start regular update block + readingsBeginUpdate($hash); # start regular update block readingsBulkUpdate($hash, $rpname, $power) if ($time); } if (defined($reject) && $reject ne "") { - my $rejCount = ReadingsVal($name, "reject$pin", 0); # alter reject count Wert - readingsBulkUpdate($hash, "reject$pin", $reject + $rejCount); + my $rejCount = ReadingsVal($name, "reject$pinName", 0); # alter reject count Wert + readingsBulkUpdate($hash, "reject$pinName", $reject + $rejCount); } - readingsBulkUpdate($hash, "timeDiff$pin", $time); - readingsBulkUpdate($hash, "countDiff$pin", $diff); + readingsBulkUpdate($hash, "timeDiff$pinName", $time); # these readings are used internally for calculations + readingsBulkUpdate($hash, "countDiff$pinName", $diff); # these readings are used internally for calculations - if (AttrVal($name, "verboseReadings$pin", 0)) { - readingsBulkUpdate($hash, "lastMsg$pin", $line); + if (AttrVal($name, "verboseReadings$pinName", AttrVal($name, "verboseReadings$pin", 0))) { + readingsBulkUpdate($hash, "lastMsg$pinName", $line); } ArduCounter_HandleCounters($hash, $pin, $seq, $count, $time, $diff, $rDiff, $now); readingsEndUpdate($hash, 1); - if (!$hash->{Initialized}) { # device has sent count but not Started / hello after reconnect + if (!$hash->{Initialized}) { # device has sent count but not Started / hello after reconnect Log3 $name, 3, "$name: device is still counting"; - if (!$hash->{WaitForHello}) { # if hello not already sent, send it now + if (!$hash->{WaitForHello}) { # if hello not already sent, send it now ArduCounter_AskForHello("direct:$name"); } - RemoveInternalTimer ("sendHello:$name"); # don't send hello again + RemoveInternalTimer ("sendHello:$name"); # don't send hello again } } } @@ -1131,14 +1448,14 @@ sub ArduCounter_Parse($) foreach my $line (@lines) { #Log3 $name, 5, "$name: Parse line: $line"; - if ($line =~ /^R([\d]+)/) - { + if ($line =~ /^R([\d]+)/) { ArduCounter_ParseReport($hash, $line); - } elsif ($line =~ /^H([\d+]) (.+)/) { # pin pulse history as separate line + } elsif ($line =~ /^H([\d]+) (.+)/) { # pin pulse history as separate line my $pin = $1; my $hist = $2; - if (AttrVal($name, "verboseReadings$pin", 0)) { + my $pinName = ArduCounter_PinName($hash, $pin); + if (AttrVal($name, "verboseReadings$pinName", AttrVal($name, "verboseReadings$pin", 0))) { readingsBeginUpdate($hash); readingsBulkUpdate($hash, "pinHistory$pin", $hist); readingsEndUpdate($hash, 1); @@ -1151,13 +1468,25 @@ sub ArduCounter_Parse($) } elsif ($line =~ /^I(.*)/) { # interval config report after show/hello $hash->{runningCfg}{I} = $1; # save for later compare $hash->{runningCfg}{I} =~ s/\s+$//; # remove spaces at end - $retStr .= ($retStr ? "\n" : "") . $line; + $retStr .= ($retStr ? "\n" : "") . $line; + Log3 $name, 4, "$name: device sent interval config $hash->{runningCfg}{I}"; - } elsif ($line =~ /^P([\d]+) (falling|rising|-) ?(pullup)? ?min ([\d]+)/) { # pin configuration at device - $hash->{runningCfg}{$1} = "$2 $3 $4"; # save for later compare + } elsif ($line =~ /^T(.*)/) { # analog threshold config report after show/hello + $hash->{runningCfg}{T} = $1; # save for later compare + $hash->{runningCfg}{T} =~ s/\s+$//; # remove spaces at end + $retStr .= ($retStr ? "\n" : "") . $line; + Log3 $name, 4, "$name: device sent analog threshold config $hash->{runningCfg}{T}"; + + } elsif ($line =~ /^V(.*)/) { # devVerbose + $hash->{runningCfg}{V} = $1; # save for later compare + $hash->{runningCfg}{V} =~ s/\s+$//; # remove spaces at end + $retStr .= ($retStr ? "\n" : "") . $line; + } elsif ($line =~ /^P([\d]+) (falling|rising|-) ?(pullup)? ?min ([\d]+)/) { # pin configuration at device + my $p = ($3 ? $3 : "nop"); + $hash->{runningCfg}{$1} = "$2 $p $4"; # save for later compare $retStr .= ($retStr ? "\n" : "") . $line; - Log3 $name, 4, "$name: device sent config for pin $1: $1 $2 min $3"; + Log3 $name, 4, "$name: device sent config for pin $1: $2 $p min $4"; } elsif ($line =~ /^alive/) { # alive response RemoveInternalTimer ("alive:$name"); @@ -1183,6 +1512,13 @@ sub ArduCounter_Parse($) $retStr .= ($retStr ? "\n" : "") . $line; Log3 $name, 4, "$name: device: $1"; + } elsif ($line =~ /^L([\d]+)/) { # analog level difference reported + if ($hash->{analogLevels}{$1}) { + $hash->{analogLevels}{$1}++; + } else { + $hash->{analogLevels}{$1} = 1; + } + } elsif ($line =~ /^M (.*)/) { # other Message from device $retStr .= ($retStr ? "\n" : "") . $line; Log3 $name, 3, "$name: device: $1"; @@ -1310,7 +1646,7 @@ sub ArduCounter_ReadAnswer($$)
@@ -1349,7 +1687,7 @@ sub ArduCounter_ReadAnswer($$)
- Configuration of ArduCounter counters

+ Configuration of ArduCounter digital counters


+ + Configuration of ArduCounter analog counters

+ +
Set-Commands

@@ -1410,7 +1821,9 @@ sub ArduCounter_ReadAnswer($$)
@@ -1419,72 +1832,172 @@ sub ArduCounter_ReadAnswer($$)
  • do_not_notify
  • readingFnAttributes

  • -
  • pin.*
  • +
  • pin[AD]?[0-9]+
  • Define a pin of the Arduino or ESP board as input. This attribute expects either rising, falling or change, followed by an optional pullup and an optional number as value.
    - If a number is specified, the arduino will track rising and falling edges of each impulse and measure the length of a pulse in milliseconds. The number specified here is the minimal length of a pulse and a pause before a pulse. If one is too small, the pulse is not counted but added to a separate reject counter. + If a number is specified, the arduino will track rising and falling edges of each impulse and measure the length of a pulse in milliseconds. The number specified here is the minimal length of a pulse and a pause before a pulse. If one is too small, the pulse is not counted but added to a separate reject counter.
    + Example:
    + + attr MyCounter pinD4 falling pullup 30 + +
  • interval normal max min mincout
  • Defines the parameters that affect the way counting and reporting works. This Attribute expects at least two and a maximum of four numbers as value. The first is the normal interval, the second the maximal interval, the third is a minimal interval and the fourth is a minimal pulse count. - +

    In the usual operation mode (when the normal interval is smaller than the maximum interval), the Arduino board just counts and remembers the time between the first impulse and the last impulse for each pin.
    After the normal interval is elapsed the Arduino board reports the count and time for those pins where impulses were encountered.
    This means that even though the normal interval might be 10 seconds, the reported time difference can be something different because it observed impulses as starting and ending point.
    The Power (e.g. for energy meters) is then calculated based of the counted impulses and the time between the first and the last impulse.
    - For the next interval, the starting time will be the time of the last impulse in the previous - reporting period and the time difference will be taken up to the last impulse before the reporting - interval has elapsed. + For the next interval, the starting time will be the time of the last impulse in the previous reporting period and the time difference will be taken up to the last impulse before the reporting interval has elapsed.

    The second, third and fourth numbers (maximum, minimal interval and minimal count) exist for the special case when the pulse frequency is very low and the reporting time is comparatively short.
    For example if the normal interval (first number) is 60 seconds and the device counts only one impulse in 90 seconds, the the calculated power reading will jump up and down and will give ugly numbers.
    By adjusting the other numbers of this attribute this can be avoided.
    In case in the normal interval the observed impulses are encountered in a time difference that is smaller than the third number (minimal interval) or if the number of impulses counted is smaller than the fourth number (minimal count) then the reporting is delayed until the maximum interval has elapsed or the above conditions have changed after another normal interval.
    - This way the counter will report a higher number of pulses counted and a larger time difference back to fhem. -

    + This way the counter will report a higher number of pulses counted and a larger time difference back to fhem.
    + Example:
    + + attr myCounter interval 60 600 5 2 +
    If this is seems too complicated and you prefer a simple and constant reporting interval, then you can set the normal interval and the mximum interval to the same number. This changes the operation mode of the counter to just count during this normal and maximum interval and report the count. In this case the reported time difference is always the reporting interval and not the measured time between the real impulses. +
  • factor
  • - Define a multiplicator for calculating the power from the impulse count and the time between the first and the last impulse - -
  • readingNameCount[0-9]+
  • - Change the name of the counter reading pinX to something more meaningful. -
  • readingNameLongCount[0-9]+
  • - Change the name of the long counter reading longX to something more meaningful. - -
  • readingNameInterpolatedCount[0-9]+
  • - Change the name of the interpolated long counter reading InterpolatedlongX to something more meaningful. - -
  • readingNamePower[0-9]+
  • - Change the name of the power reading powerX to something more meaningful. + Define a multiplicator for calculating the power from the impulse count and the time between the first and the last impulse.
    + This attribute is outdated and unintuitive so you should avoid it.
    + Instead you should specify the attribute pulsesPerKWh or readingPulsesPerKWh[0-9]+ (where [0-9]+ stands for the pin number). +
  • readingFactor[0-9]+
  • - Override the factor attribute for this individual pin. -
  • readingStartTime[0-9]+
  • + Override the factor attribute for this individual pin.
    + Just like the attribute factor, this is a rather cumbersome way to specify the pulses per kWh.
    + Instaed it is advised to use the attribute pulsesPerKWh or readingPulsesPerKWh[0-9]+ (where [0-9]+ stands for the pin number). + +
  • pulsesPerKWh
  • + specify the number of pulses that the meter is giving out per unit that sould be displayed (e.g. per kWh energy consumed). For many S0 counters this is 1000, for old ferraris counters this is 75 (rounds per kWh).
    + Example: + + attr myCounter pulsesPerKWh 75 + +
  • readingPulsesPerKWh[0-9]+
  • + is the same as pulsesPerKWh but specified per pin individually in case you have multiple counters with different settings at the same time +
    + Example:
    + + attr myCounter readingPulsesPerKWhA7 75
    + attr myCounter readingPulsesPerKWhD4 1000 +
    + +
  • readingNameCount[AD]?[0-9]+
  • + Change the name of the counter reading pinX to something more meaningful.
    + Example: + + attr myCounter readingNameCountD4 CounterHaus_internal + +
  • readingNameLongCount[AD]?[0-9]+
  • + Change the name of the long counter reading longX to something more meaningful.
    + Example: + + attr myCounter readingNameLongCountD4 CounterHaus_long + + +
  • readingNameInterpolatedCount[AD]?[0-9]+
  • + Change the name of the interpolated long counter reading InterpolatedlongX to something more meaningful.
    + Example: + + attr myCounter readingNameInterpolatedCountD4 CounterHaus_interpolated + + +
  • readingNameCalcCount[AD]?[0-9]+
  • + Change the name of the real unit counter reading CalcCounterX to something more meaningful.
    + Example: + + attr myCounter readingNameCalcCountD4 CounterHaus_kWh + + +
  • readingNamePower[AD]?[0-9]+
  • + Change the name of the power reading powerX to something more meaningful.
    + Example: + + attr myCounter readingNamePowerD4 PowerHaus + + +
  • readingStartTime[AD]?[0-9]+
  • Allow the reading time stamp to be set to the beginning of measuring intervals. -
  • verboseReadings[0-9]+
  • + +
  • verboseReadings[AD]?[0-9]+
  • create readings timeDiff, countDiff and lastMsg for each pin
    + Example: + + attr myCounter verboseReadingsD4 1 + + +
  • devVerbose
  • + set the verbose level in the counting board. This defaults to 0.
    + If the value is >0, then the firmware will echo all commands sent to it by the Fhem module.
    + If the value is >=5, then the firmware will report the pulse history (assuming that the firmware has been compiled with this feature enabled)
    + If the value is >=10, then the firmware will report every level change of a pin
    + If the value is >=20, then the firmware will report every analog measurement (assuming that the firmware has been compiled with analog measurements for old ferraris counters or similar). + +
  • analogThresholds
  • + this Attribute is necessary when you use an arduino nano with connected reflection light barrier (photo transistor and led) to detect the red mark of an old ferraris energy counter. In this case the firmware uses an upper and lower threshold which can be set here.
    + Example: + + attr myCounter analogThresholds 90 110 +
    + In order to find out the right threshold values you can set devVerbose to 20, wait for several turns of the ferraris disc and then use get levels to see the typical measurements for the red mark and the blank disc. +
  • flashCommand
  • sets the command to call avrdude and flash the onnected arduino with an updated hex file (by default it looks for ArduCounter.hex in the FHEM/firmware subdirectory.
    This attribute contains avrdude -p atmega328P -c arduino -P [PORT] -D -U flash:w:[HEXFILE] 2>[LOGFILE] by default.
    - For an Arduino Nano based counter you should add -b 57600 e.g. between the -P and -D options. + For an Arduino Nano based counter you should add -b 57600 e.g. between the -P and -D options.
    + Example: + + attr myCounter flashCommand avrdude -p atmega328P -c arduino -b 57600 -P [PORT] -D -U flash:w:[HEXFILE] 2>[LOGFILE] +
  • keepAliveDelay
  • defines an interval in which the module sends keepalive messages to a counter device that is conected via tcp.
    This attribute is ignored if the device is connected via serial port.
    If the device doesn't reply within a defined timeout then the module closes and tries to reopen the connection.
    The module tells the device when to expect the next keepalive message and the device will also close the tcp connection if it doesn't see a keepalive message within the delay multiplied by 2.5
    - The delay defaults to 10 seconds. + The delay defaults to 10 seconds.
    + Example: + + attr myCounter keepAliveDelay 30 + +
  • keepAliveTimeout
  • defines the timeout when wainting for a keealive reply (see keepAliveDelay) - The timeout defaults to 2 seconds. + The timeout defaults to 2 seconds.
    + Example: + + attr myCounter keepAliveTimeout 3 + +
  • nextOpenDelay
  • defines the time that the module waits before retrying to open a disconnected tcp connection.
    - This defaults to 60 seconds. + This defaults to 60 seconds.
    + Example: + + attr myCounter nextOpenDelay 20 + +
  • openTimeout
  • defines the timeout after which tcp open gives up trying to establish a connection to the counter device. - This timeout defaults to 3 seconds. + This timeout defaults to 3 seconds.
    + Example: + + attr myCounter openTimeout 5 + +
  • silentReconnect
  • - if set to 1, then it will set the loglevel for "disconnected" and "reappeared" messages to 4 instead of 3 + if set to 1, then it will set the loglevel for "disconnected" and "reappeared" messages to 4 instead of 3
    + Example: + + attr myCounter silentReconnect 1 +
  • disable
  • if set to 1 then the module closes the connection to a counter device.
    @@ -1493,26 +2006,42 @@ sub ArduCounter_ReadAnswer($$) Readings / Events

    diff --git a/fhem/FHEM/firmware/ArduCounter.hex b/fhem/FHEM/firmware/ArduCounter.hex index 9fc321541..f6930087e 100755 --- a/fhem/FHEM/firmware/ArduCounter.hex +++ b/fhem/FHEM/firmware/ArduCounter.hex @@ -1,757 +1,827 @@ -:100000000C94E5010C940D020C940D020C94960DC9 -:100010000C94710D0C944C0D0C940D020C940D026B -:100020000C940D020C940D020C940D020C940D0214 -:100030000C940D020C940D020C940D020C940D0204 -:100040000C9496050C940D020C9478030C945203B6 -:100050000C940D020C940D020C940D020C940D02E4 -:100060000C940D020C940D028F129A149A14A11282 -:10007000D1139A149A14CE13FB129A1485149A145D -:100080009A149A149A149A149A140D1310139A1419 -:100090009A14AF134572726F723A20004D61792045 -:1000A000313420323031382031393A31313A303937 -:1000B0000020636F6D70696C65642000554E4F00C1 -:1000C000206F6E200041726475436F756E746572A7 -:1000D0002056322E33360020002000200049002018 -:1000E0006D696E20002070756C6C757000202D009D -:1000F0002066616C6C696E670020726973696E6757 -:1001000000500040002F0073002C20002000480009 -:100110002041002053002058002C00204E00205485 -:10012000002F0020440020430052002C00204200F9 -:100130002C0020540020617661696C61626C65005E -:100140002C002048656C6C6F2C2070696E73200049 -:100150004D20726573746F72696E6720636F6E668F -:1001600069672066726F6D20454550524F4D004DB6 -:1001700020696C6C6567616C20636F6E66696720CF -:10018000696E20454550524F4D004D206E6F2063E3 -:100190006F6E66696720696E20454550524F4D006D -:1001A0000000000800020100000304070000000036 -:1001B00000000000616C697665002C2000636F6EA2 -:1001C0006669672073617665642C20002C002C0022 -:1001D0002C0020004D20536C6F743A20004D206994 -:1001E0006C6C6567616C20636F6E66696720696E11 -:1001F00020454550524F4D004D206E6F20636F6E6D -:1002000066696720696E20454550524F4D00205366 -:100210006C6F74733A20004D20454550524F4D206D -:10022000436F6E6669673A2000206D696C6C697374 -:1002300065636F6E6473004D204E6578742072653F -:10024000706F727420696E20002C20004D20537452 -:10025000617475733A20004D2072656D6F76656428 -:10026000200052656D496E7400496C6C6567616C65 -:100270002070696E20737065636966696361746973 -:100280006F6E20004D20696C6C6567616C20766133 -:100290006C75652070617373656420666F7220648D -:1002A0006576566572626F7365004D20646576569B -:1002B0006572626F73652073657420746F2000200F -:1002C00073697A6520002C004420676F7420002039 -:1002D0002072656A65637420002020636F756E74F8 -:1002E0002000202068697374496478200020746FAE -:1002F000200020292000202820696E7465726E611C -:100300006C20004D2070696E200000000000250068 -:1003100028002B0000000000240027002A00010212 -:100320000408102040800102040810200102040883 -:1003300010204D20646566696E6564200041646428 -:10034000496E7400496C6C6567616C2070756C73E4 -:1003500065206C6576656C2073706563696669639A -:100360006174696F6E20666F722070696E20000084 -:1003700000000023002600290004040404040404EF -:1003800004020202020202030303030303496C6C2A -:100390006567616C2070696E20737065636966695A -:1003A000636174696F6E20002000200020004D20E2 -:1003B000696E74657276616C732073657420746FF6 -:1003C000200073697A650000D91011241FBECFEF99 -:1003D000D8E0DEBFCDBF11E0A0E0B1E0E4EAFEE28C -:1003E00002C005900D92A438B107D9F726E0A4E821 -:1003F000B1E001C01D92AA3CB207E1F711E0C5EEE1 -:10040000D1E004C02197FE010E942217C43ED1070B -:10041000C9F70E9406110C9450170C940000CF925B -:10042000DF92EF92FF920F931F93CF93DF936C01B4 -:100430007A018B01C0E0D0E0CE15DF0589F0D8014C -:100440006D918D01D601ED91FC910190F081E02D2F -:10045000C6010995892B11F47E0102C02196ECCFCB -:10046000C701DF91CF911F910F91FF90EF90DF9027 -:10047000CF900895089580E090E00895FC01538D99 -:10048000448D252F30E0842F90E0821B930B54176E -:1004900010F0CF96089501970895FC01918D828DFB -:1004A000981761F0828DDF01A80FB11D5D968C91C8 -:1004B000928D9F5F9F73928F90E008958FEF9FEFD3 -:1004C0000895FC01918D828D981731F0828DE80F8F -:1004D000F11D858D90E008958FEF9FEF0895FC0149 -:1004E000918D228D892F90E0805C9F4F821B910916 -:1004F0008F739927089588E995E00E946F0221E0A3 -:10050000892B09F420E0822F0895FC01848DDF01FE -:10051000A80FB11DA35ABF4F2C91848D90E0019676 -:100520008F739927848FA689B7892C93A089B18965 -:100530008C9180648C93938D848D981306C002886F -:10054000F389E02D80818F7D80830895EF92FF9263 -:100550000F931F93CF93DF93EC0181E0888F9B8DE6 -:100560008C8D981305C0E889F989808185FD24C0A8 -:10057000F62E0B8D10E00F5F1F4F0F731127E02E2B -:100580008C8DE8120CC00FB607FCFACFE889F98908 -:10059000808185FFF5CFCE010E948502F1CF8B8D42 -:1005A000FE01E80FF11DE35AFF4FF0820B8FEA893D -:1005B000FB898081806207C0EE89FF896083E889BA -:1005C000F98980818064808381E090E0DF91CF9120 -:1005D0001F910F91FF90EF900895CF93DF93EC015F -:1005E000888D8823C9F0EA89FB89808185FD05C053 -:1005F000A889B9898C9186FD0FC00FB607FCF5CF8D -:10060000808185FFF2CFA889B9898C9185FFEDCFD4 -:10061000CE010E948502E7CFDF91CF91089580E05F -:1006200090E0892B29F00E947B0281110C9400003C -:100630000895EF92FF920F931F93CF93DF938C0156 -:100640007B01C0E0D0E0F701EC0FFD1F6491662351 -:1006500061F0D801ED91FC910190F081E02DC8018D -:100660000995892B11F02196EECFCE01DF91CF9124 -:100670001F910F91FF90EF9008956115710579F02A -:10068000FB0101900020E9F73197AF01461B570BA2 -:10069000DC01ED91FC910280F381E02D099480E072 -:1006A00090E008951F920F920FB60F9211242F938E -:1006B0003F934F935F936F937F938F939F93AF93EA -:1006C000BF93EF93FF9388E995E00E948502FF9125 -:1006D000EF91BF91AF919F918F917F916F915F91BA -:1006E0004F913F912F910F900FBE0F901F90189533 -:1006F0001F920F920FB60F9211242F938F939F93F7 -:10070000EF93FF93E091A805F091A9058081E09116 -:10071000AE05F091AF0582FD12C090818091B105C8 -:100720008F5F8F732091B205821751F0E091B10570 -:10073000F0E0E856FA4F958F8093B10501C08081B3 -:10074000FF91EF919F918F912F910F900FBE0F907E -:100750001F90189590E0FC01E056FE4F2491FC019B -:10076000E25EFC4F3491FC01E758FC4FE491EE232C -:10077000B9F1222339F1233091F038F42130A9F076 -:10078000223001F584B58F7D12C0273091F02830DA -:10079000A1F02430B9F4809180008F7D03C0809156 -:1007A00080008F77809380000DC084B58F7784BDE3 -:1007B00009C08091B0008F7703C08091B0008F7D19 -:1007C0008093B000F0E0EE0FFF1FE159FC4FA591C0 -:1007D000B491EC913E2381E090E021F480E0089513 -:1007E00080E090E008953FB7F8948091B104909133 -:1007F000B204A091B304B091B40426B5A89B05C07F -:100800002F3F19F00196A11DB11D3FBFBA2FA92F8F -:10081000982F8827820F911DA11DB11DBC01CD010C -:1008200042E0660F771F881F991F4A95D1F70895F8 -:100830002FB7F8946091AD047091AE048091AF042D -:100840009091B0042FBF08952F923F924F925F92E4 -:100850006F927F928F929F92AF92BF92CF92DF92D0 -:10086000EF92FF920F931F93CF93DF93C82F0E94B5 -:100870001804AB01BC018C2F90E0FC01EE0FFF1FB0 -:10088000ED58FC4FA591B4919C90DC01A757B94F4E -:100890003C90FC01EE0FFF1FEA5DFE4F0190F081DE -:1008A000E02D2081392432229C92332009F42BC17F -:1008B000FC01EF5EFE4FA080FC01E25FFE4F208056 -:1008C000BB24B3942A1408F41EC18B2D832109F490 -:1008D00017C1EA2DF0E0EE0FFF1FEC5AFE4FA0818A -:1008E000B1811A161B060CF00BC1882483948B2D42 -:1008F000892109F4812C9D013327C901880F991F93 -:10090000880F991FFC01EA5EFC4FC080D180E28015 -:10091000F3802A013B014C185D086E087F087301C3 -:100920006201E0913205EF5FE0933205E09132051C -:10093000E43110F010923205E0913205F0E0C09100 -:100940009801D09199018E010F5F1F4F109399016B -:1009500000939801EE0FFF1FEE53FE4FD183C0832B -:10096000E0913205F0E0E657FD4FA083E0913205BB -:10097000DC01AA5EBC4F4D905D906D907C9013970A -:10098000C4E0EC9FF0011124E65CFD4F40825182EF -:1009900062827382E0913205D4E0ED9FF001112470 -:1009A000E651FE4FC082D182E282F382E0913205AD -:1009B000F0E0890102511D4FE8017880E255FE4FB9 -:1009C0007082E901CC0FDD1FFE01E75CF94F019059 -:1009D000F081E02D2F01612C712CF901EB58F94FBA -:1009E000C414D504E604F704A0F44D935D936D930D -:1009F0007C931397D8013C912081321371C0C65467 -:100A0000DB4F288139812F5F3F4F3983288322E5CF -:100A100068C0E801A881B081F901EC57FE4FAB1323 -:100A20005AC0B081BA1709F443C0DC01AE51BB4FC4 -:100A30004D905D906D907C901397DFEF4D1A5D0A9D -:100A40006D0A7D0A4D925D926D927C921397DC0146 -:100A5000AA59BC4F4D935D936D937C931397D90125 -:100A6000AE56BB4FCC91C11110C0EC01CA54DC4F43 -:100A7000488359836A837B83442443944C92D901ED -:100A8000A258BB4F2C912F5F2C93DC01A256BD4F77 -:100A90004D905D906D907C901397C40CD51CE61C16 -:100AA000F71CCD92DD92ED92FC92139723E414C0D3 -:100AB000DC01A256BD4F4D905D906D907C901397D8 -:100AC000C40CD51CE61CF71CCD92DD92ED92FC9275 -:100AD000139720E501C027E4D8013C91308301C081 -:100AE00028E5E0913205F0E0E656FE4F2083FC0158 -:100AF000EA5EFC4F4083518362837383E8018882FE -:100B0000BB0CA394DFCEDF91CF911F910F91FF908B -:100B1000EF90DF90CF90BF90AF909F908F907F909D -:100B20006F905F904F903F902F9008951F920F927B -:100B30000FB60F9211242F933F938F939F93AF93F0 -:100B4000BF938091AD049091AE04A091AF04B09199 -:100B5000B0043091AC0423E0230F2D3720F401962C -:100B6000A11DB11D05C026E8230F0296A11DB11DD0 -:100B70002093AC048093AD049093AE04A093AF0493 -:100B8000B093B0048091B1049091B204A091B304E9 -:100B9000B091B4040196A11DB11D8093B10490934E -:100BA000B204A093B304B093B404BF91AF919F91EA -:100BB0008F913F912F910F900FBE0F901F9018951E -:100BC0000C94D616CF93DF93EC019C01220F331FB8 -:100BD000F901E457F94F8FEF9FEF91838083FE0176 -:100BE000EE56FB4F1082F901E75CF94F118210823B -:100BF000CE01880F991F880F991FFC01EE51FB4F02 -:100C00001082118212821382FE01E258FB4F108281 -:100C1000FC01E25DFB4F1082118212821382F90106 -:100C2000E654FB4F11821082F901EA5FFB4F1182FB -:100C30001082FC01EA54FC4F40835183628373832A -:100C4000FC01EA59FC4F4083518362837383FC01AA -:100C5000EA5EFC4F4083518362837383FC01E95B4E -:100C6000FA4F4083518362837383FE01EE5FFC4F32 -:100C70001082F901E45DFE4F80810E94AA03FE010B -:100C8000E251FD4F8083CD5CDA4F8883DF91CF91B5 -:100C90000895CF92DF92EF92FF920F931F93CF931D -:100CA000DF931F92CDB7DEB70E9418046B017C0161 -:100CB00000E010E0B701A601C8010E94E2050F5F45 -:100CC0001F4F04311105B1F780910A0190910B017A -:100CD000A0910C01B0910D018C0D9D1DAE1DBF1D8D -:100CE0008093C4069093C506A093C606B093C7062A -:100CF00010929705E3E7F3E0A591B4918C9180936E -:100D00008906E5E7F3E0A591B4918C9180938A067A -:100D1000E7E7F3E0A591B4918C9180938B0680E096 -:100D200090E00E94E005833461F481E090E00E944D -:100D3000E005863631F482E090E00E94E0058736D7 -:100D400011F16AE871E088E995E00E940A0E0E94BC -:100D500018046093A8047093A9048093AA04909344 -:100D6000AB048091B5049091B6049093A70480934E -:100D7000A6040F90DF91CF911F910F91FF90EF90FC -:100D8000DF90CF90089583E090E00E94E005082F67 -:100D90008FEF800F853118F06FE671E0D4CF60E5FA -:100DA00071E088E995E00E940A0E10E064E0C62E2A -:100DB000D12C74E0F72EC6010E94E005482FC60131 -:100DC000019649830E94E005E82EC60102960E9422 -:100DD000E005282F30E0322F2227C9018E0D911D0A -:100DE0009093B5068093B406C60103960E94E00571 -:100DF000E82EC60104960E94E005282F30E0322F2D -:100E000022272E0D311D3093B7062093B606C6015A -:100E100005960E94E005E82EC60106960E94E005B0 -:100E2000282F30E0322F22272E0D311D3093B906A6 -:100E30002093B806C60107960E94E005E82EC60179 -:100E400008960E94E005282F30E0322F22272E0D31 -:100E5000311D3093BB062093BA0689E0C80ED11C21 -:100E6000F092B9044981493421F484E00E94420E91 -:100E700005C0413419F484E00E94870F1F5F1013EE -:100E80009ACF1092B9041092B8041092B704E4EB10 -:100E9000F6E01192119286E0E43CF807D1F757CFC3 -:100EA000EF92FF920F931F93CF93DF931F92CDB7D3 -:100EB000DEB77C01FC0100811181C80169830E94B9 -:100EC000E0056981861719F0C8010E94DE16F70156 -:100ED000808191810196918380830F90DF91CF91E2 -:100EE0001F910F91FF90EF900895CF92DF92EF92B4 -:100EF000FF920F931F93CF93DF9300D000D0CDB715 -:100F0000DEB76C012B833C8349835A830E945007D0 -:100F10004981642FC6010E9450075A81652FC6017E -:100F20000E9450072B81622FC6010E9450073C810E -:100F3000632FC6010E945007602FC6010E94500710 -:100F4000612FC6010E9450076E2DC6010E945007F6 -:100F50006F2DC6010F900F900F900F90DF91CF91E2 -:100F60001F910F91FF90EF90DF90CF900C9450075E -:100F70002F923F924F925F926F927F928F929F92A9 -:100F8000AF92BF92CF92DF92EF92FF920F931F9397 -:100F9000CF93DF93CDB7DEB7A1970FB6F894DEBF3E -:100FA0000FBECDBF1C016D8BDA01C901F894710130 -:100FB000EE0CFF1CEE0CFF1CF9A2E8A287010A54FC -:100FC0001C4F1E830D83F8012081318142815381A2 -:100FD0002F83388749875A87F701EA59FC4F4080A9 -:100FE000518062807380B7016E517B4FFB01E080BE -:100FF000F18002811381E982FA820B831C839101C3 -:10100000220F331FB90166547B4FFB010190F08121 -:10101000E02DFC87EB87F101E258FB4FE080E98A85 -:1010200048A159A142565D4FFA01E080F18002814A -:101030001381EE8AFF8A088F198F78945301420139 -:10104000EF80F88409851A858E189F08A00AB10AD6 -:10105000E8A1F9A1E25DFB4FC080D180E280F3807E -:1010600049815A816B817C814C195D096E097F0928 -:101070004D875E876F87788B6A017B015989C51A16 -:10108000D108E108F108F901EA5FFB4F20813181C5 -:101090000B851C85021B130B1B8F0A8F1D891111D9 -:1010A000E3C0E8A1F9A1E95BFA4F208131814281D7 -:1010B00053812C8F3D8F4E8F5F8F40910601509151 -:1010C00007016091080170910901498B5A8B6B8B64 -:1010D0007C8BAC01BD0109891A892B893C89401B95 -:1010E000510B620B730B0C8D1D8D2E8D3F8D401B94 -:1010F000510B620B730B77FD47C04091000150917B -:10110000010160E070E0C416D506E606F706F8F0C7 -:101110004091020150910301609104017091050119 -:10112000481559056A057B0590F440910A015091D4 -:101130000B0160910C0170910D0109891A892B89AD -:101140003C89401751076207730709F051C0F894B2 -:10115000E8A1F9A1EA54FC4F80839183A283B38371 -:10116000E8A1F9A1EA59FC4F80839183A283B3835C -:1011700078944C015D010F81188529853A85801A84 -:10118000910AA20AB30A3CC040910A0150910B0196 -:1011900060910C0170910D018C019D01041B150BD8 -:1011A000260B370BB901A8010C8D1D8D2E8D3F8D9F -:1011B000401B510B620B730B77FD3BC1409100014B -:1011C0005091010160E070E0C416D506E606F7060E -:1011D00008F42FC140910201509103016091040174 -:1011E00070910501481559056A057B0508F021C174 -:1011F000F894ED81FE8140825182628273827894FC -:10120000F894F101E258FB4F1082E8A1F9A1E256EF -:10121000FD4F1082118212821382789468A179A105 -:10122000625D7B4F29813A814B815C81FB01208388 -:10123000318342835383F101EE0FFF1FAF014A5FF9 -:101240005B4F0B851C85FA0111830083E8A1F9A18E -:10125000E95BFA4F80839183A283B383F101EE5F50 -:10126000FC4F20812F5F20832C013D011D8969E205 -:1012700071E08091C8069091C9060E941903F1019E -:10128000EE0FFF1FE457F94F40815181052E000CEE -:10129000660B770B8091C8069091C9060E943E0F9D -:1012A00066E271E08091C8069091C9060E94190318 -:1012B00049815A816B817C818091C8069091C906D1 -:1012C0000E94230E63E271E08091C8069091C906E6 -:1012D0000E941903B701A6018091C8069091C90622 -:1012E0000E94230E61E271E08091C8069091C906C8 -:1012F0000E9419034D855E856F8578898091C806A7 -:101300009091C9060E94230E6EE171E08091C8069B -:101310009091C9060E941903B501A4018091C806E5 -:101320009091C9060E94230E6BE171E08091C8067E -:101330009091C9060E941903B301A2018091C806C9 -:101340009091C9060E943E0F69E171E08091C80644 -:101350009091C9060E9419036091B5047091B6047A -:101360008091C8069091C9060E943C0E66E171E02A -:101370008091C8069091C9060E9419036A8D7B8DE1 -:101380008091C8069091C9060E943C0E111116C0AA -:1013900063E171E08091C8069091C9060E9419032B -:1013A000F101EE5FFC4F408150E060E070E02AE028 -:1013B0008091C8069091C9060E94BD0DC114D10448 -:1013C000E104F104C1F060E171E08091C806909100 -:1013D000C9060E9419036E897F89888D998DA7019E -:1013E00096010E940017BA01A9018091C806909148 -:1013F000C9060E94230E8091C8069091C906A19645 -:101400000FB6F894DEBF0FBECDBFDF91CF911F9115 -:101410000F91FF90EF90DF90CF90BF90AF909F9093 -:101420008F907F906F905F904F903F902F900C9493 -:10143000060EA1960FB6F894DEBF0FBECDBFDF91AA -:10144000CF911F910F91FF90EF90DF90CF90BF90C1 -:10145000AF909F908F907F906F905F904F903F9054 -:101460002F9008952F923F924F925F926F927F92AA -:101470008F929F92AF92BF92CF92DF92EF92FF92A4 -:101480000F931F93CF93DF937C012A013B0180913F -:10149000320590E0029664E170E00E94EC16EC01E7 -:1014A00020E030E040E004E110E0C9018C0F9D1F16 -:1014B000B8010E94EC16FC01E657FD4FE081F0E018 -:1014C000EE15FF0509F44F5F2F5F3F4F24313105C3 -:1014D00061F7442309F4DFC01E016EE071E08091E2 -:1014E000C8069091C9060E941903F701EE0FFF1F6D -:1014F000E457F94F40815181052E000C660B770BA4 -:101500008091C8069091C9060E943E0F6CE071E080 -:101510008091C8069091C9060E941903C12CD12C54 -:1015200021E0C601820D931D64E170E00E94EC167B -:101530008C01FC01E657FD4F808190E08E159F05E0 -:1015400009F089C0E801CC0FDD1FCC0FDD1F211190 -:1015500020C0FE01E65CFD4F80819181A281B381B4 -:10156000FE01E651FE4F4081518162817381840FFB -:10157000951FA61FB71F88159905AA05BB0508F476 -:1015800063C069E071E08091C8069091C9060E942D -:101590001903F801EE0FFF1FEE53FE4F60817181BA -:1015A0008091C8069091C9060E943C0E67E071E0E8 -:1015B0008091C8069091C9060E941903FE01E65C5D -:1015C000FD4F40815181628173814419550966093B -:1015D00077098091C8069091C9060E943E0F65E088 -:1015E00071E08091C8069091C9060E941903FE011E -:1015F000E651FE4F40815181628173818091C8061E -:101600009091C9060E94230E63E071E08091C806A4 -:101610009091C9060E941903F801E255FE4F4081DE -:1016200050E060E070E02AE08091C8069091C90621 -:101630000E94BD0DF801E656FE4F60818091C806FC -:101640009091C9060E941C0EC65CDD4F888099806F -:10165000AA80BB8020E08FEFC81AD80A84E1C816A0 -:10166000D10409F05ECF8091C8069091C906DF9140 -:10167000CF911F910F91FF90EF90DF90CF90BF908F -:10168000AF909F908F907F906F905F904F903F9022 -:101690002F900C94060EDF91CF911F910F91FF9028 -:1016A000EF90DF90CF90BF90AF909F908F907F9002 -:1016B0006F905F904F903F902F9008950F931F93DE -:1016C000CF93DF93EC0161E071E08091C8069091C7 -:1016D000C9060E9419038E01000F111FF801E4577B -:1016E000F94F40815181052E000C660B770B8091DC -:1016F000C8069091C9060E943E0FFE01EB58F94FB3 -:1017000020818091C8069091C906222329F02130BA -:1017100031F469EF70E005C060EF70E002C06DEE7B -:1017200070E00E941903CF59D94F8881882341F076 -:1017300065EE70E08091C8069091C9060E94190379 -:101740006FED70E08091C8069091C9060E94190360 -:10175000F801E75CF94F608171818091C806909132 -:10176000C906DF91CF911F910F910C943C0ECF923F -:10177000DF92EF92FF926DED70E08091C80690913C -:10178000C9060E94190360910A0170910B018091B2 -:101790000C0190910D0128EEC22E23E0D22EE12CF7 -:1017A000F12CA70196010E940017BA01A9018091AE -:1017B000C8069091C9060E94230E6BED70E08091DF -:1017C000C8069091C9060E941903609106017091A4 -:1017D00007018091080190910901A70196010E94DB -:1017E0000017BA01A9018091C8069091C9060E940C -:1017F000230E69ED70E08091C8069091C9060E94A1 -:10180000190360910201709103018091040190918C -:101810000501A70196010E940017BA01A901809154 -:10182000C8069091C9060E94230E67ED70E0809172 -:10183000C8069091C9060E94190360910001709139 -:1018400001018091C8069091C906FF90EF90DF904A -:10185000CF900C94260E1F93CF93DF93C5ECD0E06E -:101860001C2F1F5EFE0164918091C8069091C906ED -:101870000E941C0E21961C13F5CF60EC70E0809145 -:10188000C8069091C9060E941903CCEBD0E01C2F2A -:101890001D5FFE0164918091C8069091C9060E9467 -:1018A0001C0E21961C13F5CF61EB70E08091C806E9 -:1018B0009091C9060E941903CCE9D0E01C2F1C5E50 -:1018C000FE0164918091C8069091C9060E941C0E89 -:1018D00021961C13F5CFDF91CF911F9108958F9220 -:1018E0009F92AF92BF92CF92DF92EF92FF920F93AF -:1018F0001F93CF93DF930E9418044B015C018091EA -:10190000C8069091C9060E94060E0E942B0C62E444 -:1019100071E08091C8069091C9060E941903C4E540 -:10192000D1E0C12CD12C760181E02991399137FD8C -:1019300014C0811108C060E471E08091C8069091E4 -:10194000C9060E9419032AE0B701A6018091C806C2 -:101950009091C9060E94BD0D80E09FEFC91AD90A77 -:10196000E90AF90AE6E1CE16D104E104F104E9F648 -:1019700065E371E08091C8069091C9060E94190341 -:1019800062E371E08091C8069091C9060E94190334 -:10199000B501A4018091C8069091C9060E94230E4A -:1019A00060E371E08091C8069091C9060E94190316 -:1019B0006091B5047091B6048091C8069091C906F3 -:1019C0000E943C0E6DE271E08091C8069091C906BC -:1019D0000E9419034091A8045091A9046091AA049F -:1019E0007091AB048091C8069091C9060E94230EA5 -:1019F0006BE271E08091C8069091C9060E941903BC -:101A00006091A6047091A7048091C8069091C906C0 -:101A10000E943C0E8091C8069091C9060E94060E55 -:101A20000E94B70B0CE816E0C0E0D0E0F80181910D -:101A300091918F0197FD09C0CE010E945E0B8091AC -:101A4000C8069091C9060E94060E2196C431D105A0 -:101A500069F7DF91CF911F910F91FF90EF90DF9089 -:101A6000CF90BF90AF909F908F9008951F93CF938A -:101A7000DF93C4E9D0E01C2F195FFE0164918091CF -:101A8000C8069091C9060E941C0E21961C13F5CF22 -:101A9000DF91CF911F9108951F920F920FB60F9271 -:101AA00011242F933F934F935F936F937F938F9363 -:101AB0009F93AF93BF93EF93FF9382E00E94240420 -:101AC000FF91EF91BF91AF919F918F917F916F9116 -:101AD0005F914F913F912F910F900FBE0F901F90EC -:101AE00018951F920F920FB60F9211242F933F93C8 -:101AF0004F935F936F937F938F939F93AF93BF9316 -:101B0000EF93FF9381E00E942404FF91EF91BF9136 -:101B1000AF919F918F917F916F915F914F913F9185 -:101B20002F910F900FBE0F901F9018951F920F923C -:101B30000FB60F9211242F933F934F935F936F93A0 -:101B40007F938F939F93AF93BF93EF93FF9380E027 -:101B50000E942404FF91EF91BF91AF919F918F91CB -:101B60007F916F915F914F913F912F910F900FBE99 -:101B70000F901F9018950E944B174F925F926F9293 -:101B80007F92EF92FF920F931F93CF93DF93CDB786 -:101B9000DEB7A1970FB6F894DEBF0FBECDBF7C01B4 -:101BA000FA01CB0119A2223008F42AE08E010F5D60 -:101BB0001F4F422E512C612C712CBF01A3019201A9 -:101BC0000E940017F901CA01015011096A3014F48A -:101BD000605D01C0695CD8016C93232B242B252BFD -:101BE00061F7B801C7010E943D03A1960FB6F894B2 -:101BF000DEBF0FBECDBFDF91CF911F910F91FF9040 -:101C0000EF907F906F905F904F90089560E871E043 -:101C10000C943D030F931F93CF93DF93EC010E942D -:101C200019038C01CE010E94060E800F911FDF91D7 -:101C3000CF911F910F910895DC01ED91FC910190DE -:101C4000F081E02D09942AE00C94BD0D0F931F93B1 -:101C5000CF93DF93EC01AB0160E070E02AE00E94DB -:101C6000BD0D8C01CE010E94060E800F911FDF91E9 -:101C7000CF911F910F910895AB0160E070E02AE0D1 -:101C80000C94BD0DCF92DF92EF92FF920F931F93B2 -:101C9000843058F40E94360D62EC73E08091C806DF -:101CA0009091C9060E941903DDC02091B4063091BD -:101CB000B506C901019780319E4038F00E94360D6B -:101CC0006091B4067091B50675C0A8EEB3E00E94AD -:101CD00028176B017C0160930A0170930B018093BC -:101CE0000C0190930D010E941804DC01CB018C0DB6 -:101CF0009D1DAE1DBF1D0091C4061091C50620910B -:101D0000C6063091C70680179107A207B30770F483 -:101D10000E941804C60ED71EE81EF91EC092C40603 -:101D2000D092C506E092C606F092C7062091B6068C -:101D30003091B706C901019780319E4038F00E946A -:101D4000360D6091B6067091B70634C0A8EEB3E0C8 -:101D50000E94281760930601709307018093080181 -:101D6000909309012091B8063091B90621318EE097 -:101D7000380738F00E94360D6091B8067091B906A8 -:101D800019C0A8EEB3E00E94281760930201709377 -:101D9000030180930401909305018091BA0690910C -:101DA000BB068536910590F00E94360D6091BA060B -:101DB0007091BB068091C8069091C9061F910F9142 -:101DC000FF90EF90DF90CF900C94260E909301013E -:101DD000809300016EEA73E08091C8069091C90675 -:101DE0000E9419036091B4067091B5068091C806EF -:101DF0009091C9060E943C0E6CEA73E08091C8067F -:101E00009091C9060E9419036091B6067091B706B9 -:101E10008091C8069091C9060E943C0E6AEA73E060 -:101E20008091C8069091C9060E9419036091B80676 -:101E30007091B9068091C8069091C9060E943C0E27 -:101E400068EA73E08091C8069091C9060E94190360 -:101E50006091BA067091BB068091C8069091C90640 -:101E60000E943C0E8091C8069091C9061F910F9167 -:101E7000FF90EF90DF90CF900C94060ECF92DF9200 -:101E8000EF92FF920F931F93CF93DF9377FF1DC0C5 -:101E90006A017B01EC016DE20E941C0E8C0144275B -:101EA0005527BA014C195D096E097F092AE0CE0158 -:101EB0000E94BD0D800F911FDF91CF911F910F9157 -:101EC000FF90EF90DF90CF9008952AE0DF91CF91BF -:101ED0001F910F91FF90EF90DF90CF900C94BD0D6C -:101EE0000F931F93CF93DF93EC01462F50E060E0F8 -:101EF00070E02AE00E94BD0D8C01CE010E94060E0A -:101F0000800F911FDF91CF911F910F9108956F92D4 -:101F10007F928F929F92AF92BF92DF92EF92FF9249 -:101F20000F931F93CF93DF93D82E0E941804AB0119 -:101F3000BC01A090B406B090B50685E18A1558F0B2 -:101F400045019924F401EE0FFF1FEC5AFE4F00816A -:101F5000118117FF1EC00E94360D6DE873E080915D -:101F6000C8069091C9060E9419036A2D8091C8067F -:101F70009091C906DF91CF911F910F91FF90EF9043 -:101F8000DF90BF90AF909F908F907F906F900C9458 -:101F9000700F11277801EE0CFF1CF701E45DFE4F76 -:101FA000C081D181F701E457F94F3F0180819181D0 -:101FB000DD2788159905C9F0FE01E758FC4FE4912B -:101FC000E2508E2F90E0FC01EE0FFF1FED58FC4F0A -:101FD000A591B4912C91FC01E757F94F2083C801DA -:101FE0000E94E205F301918280828091B606909171 -:101FF000B7060297029788F00E94360D64E473E0FA -:102000008091C8069091C9060E9419036A2D80919B -:10201000C8069091C9060E94700FF801EB58F94F5D -:1020200081E02091B6063091B7062330310509F0E2 -:1020300080E08083D801AF59B94FF2E0FD1568F513 -:102040008091B8069091B906892B39F1FE01E25EC4 -:10205000FC4F2491FE01E758FC4F84918823D1F076 -:1020600090E0880F991FFC01EC5EFC4F4591549164 -:10207000FC01E65FFC4F659174919FB7F894FA01FB -:102080003081822F809583238083FB018081282BE0 -:1020900020839FBF81E08C9326C0FE01E25EFC4F4F -:1020A0004491FE01E758FC4F24912223D9F030E0FF -:1020B000220F331FF901EC5EFC4F85919491F901D9 -:1020C000E65FFC4F259134916FB7F894FC01508185 -:1020D000742F7095452F47234083F9018081872312 -:1020E00080836FBF1C92F3E0FD1530F48091BA0637 -:1020F0009091BB06009711F482E090E0F701E75C55 -:10210000F94F91838083FE01E25EFC4F8491FE01D2 -:10211000E758FC4FC491CC23A9F1C250EC2FF0E05A -:10212000EE0FFF1FEA5DFE4FA081B181EC91E82B1D -:10213000EC932091680081E090E001C0880FCA957F -:10214000EAF7822B8093680062E373E08091C8060F -:102150009091C9060E941903C8010E945E0B8091EC -:10216000C8069091C906DF91CF911F910F91FF9002 -:10217000EF90DF90BF90AF909F908F907F906F9087 -:102180000C94060E0E94360D6DE373E08091C80634 -:102190009091C906DF91CF911F910F91FF90EF9021 -:1021A000DF90BF90AF909F908F907F906F900C9436 -:1021B0000A0EE8E9F5E01382128288EE93E0A0E0CF -:1021C000B0E084839583A683B78388E191E091830F -:1021D000808385EC90E09587848784EC90E09787F6 -:1021E000868780EC90E0918B808B81EC90E0938BE4 -:1021F000828B82EC90E0958B848B86EC90E0978BC1 -:10220000868B118E128E138E148E0895CF93DF93CA -:10221000CDB7DEB72A970FB6F894DEBF0FBECDBF9D -:10222000789484B5826084BD84B5816084BD85B5B1 -:10223000826085BD85B5816085BD80916E008160BD -:1022400080936E0010928100809181008260809363 -:102250008100809181008160809381008091800065 -:102260008160809380008091B10084608093B10090 -:102270008091B00081608093B00080917A0084608A -:1022800080937A0080917A00826080937A008091B6 -:102290007A00816080937A0080917A0080688093D0 -:1022A0007A001092C100E091A805F091A90582E0A2 -:1022B0008083E091A405F091A5051082E091A60528 -:1022C000F091A70583E380831092B005E091AC05FF -:1022D000F091AD0586E08083E091AA05F091AB0511 -:1022E000808180618083E091AA05F091AB058081B7 -:1022F00088608083E091AA05F091AB0580818068B9 -:102300008083E091AA05F091AB0580818F7D808369 -:102310000E94F3034B015C0114EFC12EDD24D39422 -:10232000E12CF12C0E94F303DC01CB0188199909FF -:10233000AA09BB09883E9340A105B10558F001E008 -:10234000C01AD108E108F10818EE810E13E0911EC1 -:10235000A11CB11CC114D104E104F10419F7789453 -:1023600088E995E00E94060E88E995E09093C906F9 -:102370008093C8061092B6041092B5040E94180407 -:10238000609335067093360680933706909338062F -:102390000E9449060E946F0CB8EE4B2EB3E05B2EF4 -:1023A000612C712C0E94180400913506109136069C -:1023B000209137063091380660177107820793071E -:1023C00050F42091B5043091B6042F5F3F4F309305 -:1023D000B6042093B5046093350670933606809357 -:1023E00037069093380688E995E00E946F02892BA2 -:1023F00009F4AEC288E995E00E944D02182F8C3294 -:10240000C9F4E091B904E73008F0A2C281E08E0F70 -:102410008093B904F0E0EE0FFF1FEC54F94F809168 -:10242000B7049091B804918380831092B8041092FD -:10243000B7048EC280ED810F8A30A0F42091B704DA -:102440003091B8044AE0429FC001439F900D11248F -:10245000C097810F911D17FD9A959093B8048093B2 -:10246000B70476C28FE9810F8A3108F071C280917A -:102470009705882309F443C068EC72E088E995E089 -:102480000E941903E4EBCE2EE6E0DE2EE12CF12CC7 -:102490008091B90490E08E159F05ACF0E114F10431 -:1024A00031F066EC72E088E995E00E941903F601CC -:1024B000619171916F0188E995E00E943C0EFFEFF8 -:1024C000EF1AFF0AE5CF612F88E995E00E941C0E04 -:1024D0006FEB72E088E995E00E9419034091B9041E -:1024E00050E04F5F5F4F052E000C660B770B88E9BD -:1024F00095E00E943E0F88E995E00E94060E812F2C -:10250000110F990BAA0BBB0BFC01E156F109E63147 -:10251000F10508F00FC2EC5CFF4F0C94221780917C -:10252000B904E82FF0E0EE0FFF1FEC54F94F2091B3 -:10253000B7043091B804318320838F5F0E94870FE6 -:10254000F9C18091B904E82FF0E0EE0FFF1FEC54C1 -:10255000F94F2091B7043091B80431832083009162 -:10256000B4061091B5068F3F61F0063150F4F801C2 -:10257000FF27EE0FFF1FEC5AFE4F8081918197FFDE -:1025800005C00E94360D69E672E024C39927FC015C -:10259000EE0FFF1FE45DFE4F208131813327F901EB -:1025A000E25EFC4F4491F901E758FC4F249122234D -:1025B00009F404C32250E22FF0E0EE0FFF1FEA5DA2 -:1025C000FE4FA081B1813C91E42FE095E323EC9391 -:1025D0003C913111F8C26091680041E050E06A011D -:1025E00002C0CC0CDD1C2A95E2F79601209526232B -:1025F00020936800E8C28091B904E82FF0E0EE0F64 -:10260000FF1FEC54F94F2091B7043091B804318387 -:1026100020838F5F0E94420E8DC10E9449068AC1AD -:102620000E9418044B015C016CE472E08091C806C2 -:102630009091C9060E9419030E942B0C8091C80634 -:102640009091C9060E94060E0E94B70B1CE8E12E6D -:1026500016E0F12E00E010E0F701819191917F01E9 -:1026600018161906B4F4C8010E945E0B69E472E002 -:102670008091C8069091C9060E941903A501940192 -:1026800061E0C8010E94B807B501A401C8010E9419 -:10269000320A0F5F1F4F04311105F1F68091C80611 -:1026A0009091C9060E94060E67E172E08091C8060B -:1026B0009091C9060E94190380E090E00E94E00515 -:1026C000682F8091C8069091C9060E941C0E81E077 -:1026D00090E00E94E005682F8091C8069091C9069D -:1026E0000E941C0E82E090E00E94E005682F80911D -:1026F000C8069091C9060E941C0E6EE072E080919F -:10270000C8069091C9060E94190383E090E00E94D8 -:10271000E005482F50E060E070E08091C80690919D -:10272000C9060E943E0F8091C8069091C9060E947A -:10273000060E80E090E00E94E005833409F058C264 -:1027400081E090E00E94E005863609F051C282E007 -:1027500090E00E94E005873609F04AC27EC22091CF -:10276000B7043091B8048091C8069091C9062F3FF4 -:10277000310588F4209397056AEA72E00E941903F4 -:102780006091B7047091B8048091C8069091C90611 -:102790000E94260ECFC064E872E0CAC00E946F0C8F -:1027A000C9C01A82198263E4CE0101960E945007C3 -:1027B00066E6CE0101960E94500767E6CE010196BB -:1027C0000E9450072CE836E03C832B83F901332428 -:1027D000339481919191181619060CF4339436E0D4 -:1027E000E43BF307B1F7632DCE0101960E94500739 -:1027F000E0900001F09001016091020170910301ED -:102800008091040190910501A30192010E9400179B -:102810002D833E834F835887609106017091070195 -:102820008091080190910901A30192010E94001773 -:1028300049015A0160910A0170910B0180910C01CC -:1028400090910D01A30192010E940017BA01A90104 -:102850000D811E81940169E4CE0101960E947507E5 -:1028600079E3A72E76E0B72EC12CD12CEB81FC8129 -:1028700041915191FC83EB8314161506C4F4F501C4 -:10288000E080F180F601EF59F94F008110E0F60188 -:10289000EB58F94F8081811103C022E030E002C083 -:1028A00023E030E061E4CE0101960E947507FFEF5E -:1028B000CF1ADF0A02E0A00EB11C14E1C116D10448 -:1028C000A9F66DEB71E088E995E00E941903432DAC -:1028D00050E060E070E02AE088E995E00E94BD0DDC -:1028E0006AEB71E088E995E00E94190349815A81F9 -:1028F000052E000C660B770B88E995E00E943E0FD1 -:1029000088E995E00E94060E15C0E091B904F0E058 -:10291000EE0FFF1FEC54F94F8091B7049091B8046B -:102920009183808364EB71E08091C8069091C90621 -:102930000E940A0E1092B9041092B8041092B704C3 -:10294000E4EBF6E01192119226E0E43CF207D1F7B5 -:10295000809197058A3030F50E9418040091C406D2 -:102960001091C5062091C6063091C7066B017C0107 -:10297000C01AD10AE20AF30AF7FEE5C00091060187 -:102980001091070120910801309109014B015C0170 -:10299000801A910AA20AB30AECE8F6E0A7E4B5E0CF -:1029A00020E0C9C03CE8832E36E0932E43E3E42EBA -:1029B00045E0F42E5CE2A52E51E0B52E00E010E0DB -:1029C000F401C190D1904F011C141D040CF094C06F -:1029D000F5012080822D0E94AA03382EF701808104 -:1029E000381609F489C0308263E073E08091C8062C -:1029F0009091C9060E941903A601DD0C660B770BA6 -:102A00008091C8069091C9060E943E0F66EF72E061 -:102A10008091C8069091C9060E941903422D50E08A -:102A200060E070E02AE08091C8069091C9060E949B -:102A3000BD0D62EF72E08091C8069091C9060E94B8 -:102A400019036DEE72E08091C8069091C9060E944C -:102A50001903432D50E060E070E02AE08091C80641 -:102A60009091C9060E94BD0D62EE72E08091C80689 -:102A70009091C9060E9419034091320550E060E030 -:102A800070E02AE08091C8069091C9060E94BD0DB1 -:102A900069ED72E08091C8069091C9060E94190301 -:102AA000F801EE0FFF1FEE0FFF1FEE51FB4F4081AD -:102AB0005181628173818091C8069091C9060E94FC -:102AC000230E6FEC72E08091C8069091C9060E94B7 -:102AD0001903F801EE0FFF1FE654FB4F608171816F -:102AE0008091C8069091C9060E943C0E8091C8064C -:102AF0009091C9060E94060E0F5F1F4FFFEFEF1A5D -:102B0000FF0A22E0A20EB11C0431110509F058CFD2 -:102B100023CFCD90DD90ED90FC901397B501A401EB -:102B20004C195D096E097F0977FF21E0149656E084 -:102B3000E43BF50731F041915191141615064CF321 -:102B4000F5CF2223C1F10E9418044B015C018CE8EF -:102B5000E82E86E0F82E00E010E0F70181919191D7 -:102B60007F0197FD0FC0A501940160E0C8010E949C -:102B7000B80780919705853028F0B501A401C801F8 -:102B80000E94320A0F5F1F4F0431110531F7409147 -:102B90000A0150910B0160910C0170910D01480DDB -:102BA000591D6A1D7B1D4093C4065093C506609352 -:102BB000C6067093C7060E940F03F4CB0E94360D21 -:102BC00062E672E0B1CE40E050E0BA010E94E20558 -:102BD00067E572E08091C8069091C9060E941903CA -:102BE000602F8091C8069091C9060E94700FA2CEF6 -:102BF00068EF71E08091C8069091C9060E940A0EA4 -:102C000067E372E08091C8069091C9060E9419039B -:102C1000C090C406D090C506E090C606F090C706E6 -:102C20000E94180497018601061B170B280B390B0D -:102C3000B901A8018091C8069091C9060E94230E8F -:102C400069E272E08091C8069091C9060E9419035A -:102C50008091C8069091C90655CE83E090E00E940D -:102C6000E005282EF5E1F81718F46DED71E0C2CFFC -:102C7000312C04E010E0321419F2C8010E94E00582 -:102C8000282FC80101962A870E94E005F82EC80166 -:102C900002960E94E005C82ED12CDC2CCC24CF0C4F -:102CA000D11CC80103960E94E005F82EC8010496C5 -:102CB0000E94E005A82EB12CBA2CAA24AF0CB11C9E -:102CC000C80105960E94E005F82EC80106960E94EC -:102CD000E005882E912C982C88248F0C911CC8011B -:102CE00007960E94E005382FC801089639870E9490 -:102CF000E005E82EF12CFE2CEE243985E30EF11CC4 -:102D0000075F1F4F64ED71E08091C8069091C9067E -:102D10000E9419032A85622F8091C8069091C906E6 -:102D20000E941C0E62ED71E08091C8069091C90668 -:102D30000E941903B6018091C8069091C9060E94AD -:102D40003C0E60ED71E08091C8069091C9060E942A -:102D50001903B5018091C8069091C9060E943C0EE6 -:102D60006EEC71E08091C8069091C9060E9419032B -:102D7000B4018091C8069091C9060E943C0E6CEC8B -:102D800071E08091C8069091C9060E941903B701AD -:102D90008091C8069091C9060E943C0E8091C80699 -:102DA0009091C9060E94060E339465CFF999FECF23 -:102DB00092BD81BDF89A992780B50895262FF9997B -:102DC000FECF1FBA92BD81BD20BD0FB6F894FA9A0E -:102DD000F99A0FBE0196089597FB072E16F40094FA -:102DE00007D077FD09D00E94371707FC05D03EF4C5 -:102DF000909581959F4F0895709561957F4F0895A7 -:102E0000A1E21A2EAA1BBB1BFD010DC0AA1FBB1FEE -:102E1000EE1FFF1FA217B307E407F50720F0A21B60 -:102E2000B30BE40BF50B661F771F881F991F1A94CD -:102E300069F760957095809590959B01AC01BD01F7 -:102E4000CF010895EE0FFF1F0590F491E02D099436 -:102E5000A29FB001B39FC001A39F700D811D1124DB -:102E6000911DB29F700D811D1124911D0895AA1B03 -:102E7000BB1B51E107C0AA1FBB1FA617B70710F065 -:102E8000A61BB70B881F991F5A95A9F78095909597 -:102E9000BC01CD01089581E090E0F8940C945017A6 -:042EA000F894FFCFD4 -:102EA4000200D007000060EA0000307500000D1336 -:102EB40007080E0000000000A6020F023E02ED0209 -:102EC4006F024D0261026B006C006D000200030092 -:102ED4000400050006000700080009000A000B00B2 -:102EE4000C000D000E000F00100011001200130062 -:102EF40000000000FFFFFFFF0000010002000300CC -:102F04000400050006000700080009000A000B0081 -:102F14000C000D000E000F00100011001200130031 -:042F24000D0A000092 +:100000000C941C020C9444020C9444020C941B0D9E +:100010000C94F60C0C94D10C0C9444020C944402F5 +:100020000C9444020C9444020C9444020C94440238 +:100030000C9444020C9444020C9444020C94440228 +:100040000C94CA110C9444020C94D2030C94AC038B +:100050000C9444020C9444020C9444020C94440208 +:100060000C9444020C944402AE13A915A915C013B4 +:100070001F14A915A915DA14DD14A915EF14A91573 +:10008000A915A915A915A915A9150B150E157715EB +:10009000A91589154572726F723A20004A616E2067 +:1000A000203620323031392031373A31313A343745 +:1000B0000020636F6D70696C656420004E414E4F87 +:1000C00000206F6E200041726475436F756E746519 +:1000D000722056332E3130002000200020004900CD +:1000E00020005400206D696E20002070756C6C75C6 +:1000F0007000202D002066616C6C696E67002072B4 +:100100006973696E6700500040002F0073002C2057 +:1001100000200048002041002053002058002C00FF +:10012000204E002054002F002044002043005200A5 +:100130002C002042002C0020540020617661696C64 +:1001400061626C65002C002048656C6C6F2C20701F +:10015000696E7320004D20726573746F72696E67EB +:1001600020636F6E6669672066726F6D204545502B +:10017000524F4D004D20696C6C6567616C20636F58 +:100180006E66696720696E20454550524F4D004D9F +:10019000206E6F20636F6E66696720696E2045452B +:1001A00050524F4D002C002C002C0020004D2053AD +:1001B0006C6F743A200020536C6F74733A20004DBA +:1001C00020454550524F4D20436F6E6669673A2077 +:1001D000004D20696C6C6567616C20636F6E6669A9 +:1001E0006720696E20454550524F4D004D206E6F7F +:1001F00020636F6E66696720696E20454550524FD7 +:100200004D00206D696C6C697365636F6E6473007B +:100210004D204E657874207265706F727420696E1F +:1002200020002C200056004D205374617475733AE1 +:100230002000616C697665002C2000636F6E666932 +:10024000672073617665642C20004D2072656D6FA8 +:10025000766564200052656D496E7400496C6C656A +:1002600067616C2070696E2073706563696669638D +:100270006174696F6E20004D20696C6C6567616CFC +:100280002076616C75652070617373656420666F9C +:100290007220646576566572626F7365004D2064E6 +:1002A0006576566572626F73652073657420746F2E +:1002B00020002073697A6520002C004420676F7449 +:1002C0002000202072656A65637420002020636F1F +:1002D000756E74200020206869737449647820006A +:1002E00020746F200020292000202820696E74656A +:1002F000726E616C20004D2070696E2000202072AB +:10030000656A65637420002020636F756E74200039 +:100310002020686973744964782000206368616EE6 +:1003200067656420746F2000202900202820696EF2 +:100330007465726E616C20004D2070696E20004CF7 +:100340000000000000240027002A00000000002513 +:100350000028002B00000000080002010000030438 +:100360000700000000000000000102040810204007 +:10037000800102040810200102040810204D2064AE +:100380006566696E65642000416464496E74004965 +:100390006C6C6567616C2070756C7365206C65763C +:1003A000656C2073706563696669636174696F6EFB +:1003B00020666F722070696E20000000000023002C +:1003C00026002900040404040404040402020202B6 +:1003D0000202030303030303496C6C6567616C202D +:1003E00070696E2073706563696669636174696FB3 +:1003F0006E200020004D20616E616C6F6720746874 +:10040000726573686F6C64732073657420746F20F9 +:100410000073697A65002000200020004D20696E7D +:1004200074657276616C732073657420746F20003C +:1004300073697A650000141211241FBECFEFD8E053 +:10044000DEBFCDBF11E0A0E0B1E0EAEFF2E302C011 +:1004500005900D92A639B107D9F726E0A6E9B1E0DB +:1004600001C01D92A938B207E1F712E0CCE1D2E059 +:1004700004C02197FE010E944D19CB31D107C9F765 +:100480000E9441120C947B190C940000CF92DF92D1 +:10049000EF92FF920F931F93CF93DF936C017A013A +:1004A0008B01C0E0D0E0CE15DF0581F0D8016D9161 +:1004B0008D01D601ED91FC910190F081E02DC601F6 +:1004C0000995892B11F02196EECF7E01C701DF91AE +:1004D000CF911F910F91FF90EF90DF90CF900895F3 +:1004E000089580E090E00895FC01538D448D252F00 +:1004F00030E0842F90E0821B930B541710F0CF96BE +:10050000089501970895FC01918D828D981761F0EF +:10051000828DDF01A80FB11D5D968C91928D9F5F3A +:100520009F73928F90E008958FEF9FEF0895FC01E5 +:10053000918D828D981731F0828DE80FF11D858D98 +:1005400090E008958FEF9FEF0895FC01918D228D2B +:10055000892F90E0805C9F4F821B91098F739927B0 +:10056000089585E595E00E94A50221E0892B09F414 +:1005700020E0822F0895FC01848DDF01A80FB11DBA +:10058000A35ABF4F2C91848D90E001968F739927C9 +:10059000848FA689B7892C93A089B1898C918370A7 +:1005A00080648C93938D848D981306C00288F389A0 +:1005B000E02D80818F7D80830895EF92FF920F93CD +:1005C0001F93CF93DF93EC0181E0888F9B8D8C8DFF +:1005D000981305C0E889F989808185FD26C0F62E2B +:1005E0000B8D10E00F5F1F4F0F731127E02E8C8DC6 +:1005F000E8120CC00FB607FCFACFE889F9898081B0 +:1006000085FFF5CFCE010E94BB02F1CF8B8DFE019D +:10061000E80FF11DE35AFF4FF0829FB7F8940B8F5C +:10062000EA89FB89808180620AC09FB7F894EE89CD +:10063000FF896083E889F989808183708064808381 +:100640009FBF81E090E0DF91CF911F910F91FF90CC +:10065000EF900895CF93DF93EC01888D8823C9F044 +:10066000EA89FB89808185FD05C0A889B9898C91BB +:1006700086FD0FC00FB607FCF5CF808185FFF2CF56 +:10068000A889B9898C9185FFEDCFCE010E94BB026C +:10069000E7CFDF91CF91089580E090E0892B29F09A +:1006A0000E94B10281110C9400000895DC01ED91CB +:1006B000FC910190F081E02D0994EF92FF920F934D +:1006C0001F93CF93DF938C017B01C0E0D0E0F70153 +:1006D000EC0FFD1F6491662361F0D801ED91FC9150 +:1006E0000190F081E02DC8010995892B11F0219628 +:1006F000EECFCE01DF91CF911F910F91FF90EF9040 +:1007000008956115710579F0FB0101900020E9F76A +:100710003197AF01461B570BDC01ED91FC91028034 +:10072000F381E02D099480E090E0089562E971E0A2 +:100730000C9481030F931F93CF93DF93EC010E94DE +:100740005D038C01CE010E949603800F911FDF9103 +:10075000CF911F910F9108951F920F920FB60F9294 +:1007600011242F933F934F935F936F937F938F93B6 +:100770009F93AF93BF93EF93FF9385E595E00E941E +:10078000BB02FF91EF91BF91AF919F918F917F91AC +:100790006F915F914F913F912F910F900FBE0F90EE +:1007A0001F9018951F920F920FB60F9211242F933E +:1007B0008F939F93EF93FF93E0916505F09166050A +:1007C0008081E0916B05F0916C0582FD12C09081F3 +:1007D00080916E058F5F8F7320916F05821751F0A6 +:1007E000E0916E05F0E0EB5AFA4F958F80936E051D +:1007F00001C08081FF91EF919F918F912F910F9078 +:100800000FBE0F901F9018950C940119CF93DF9392 +:10081000EC019C01220F331FF901EA5BF94F8FEFC6 +:100820009FEF91838083FE01E059FB4F1082F90115 +:10083000E550FA4F11821082CE01880F991F880F60 +:10084000991FFC01EF54FB4F108211821282138218 +:10085000FE01E25AFB4F1082FC01EA5EFB4F108260 +:10086000118212821382F901E357FB4F1182108229 +:10087000F901EE50FC4F11821082FC01E655FC4F4D +:100880004083518362837383FC01EE59FC4F4083A4 +:10089000518362837383FC01E65EFC4F4083518386 +:1008A00062837383FC01E45FFA4F40835183628368 +:1008B0007383FE01E85FFC4F1082F901EE5BFE4F8F +:1008C00080810E94D00EFE01EA50FD4F8083C65009 +:1008D000DB4F8883DF91CF910895CF92DF92EF9223 +:1008E000FF920F931F93CF93DF930E949E0F6B0194 +:1008F0007C01C0E0D0E0B701A601CE010E94060451 +:100900002196C231D105B9F780910E0190910F0166 +:10091000A0911001B0911101C80ED91EEA1EFB1E54 +:10092000C0927A06D0927B06E0927C06F0927D0619 +:1009300010925405EEEBF3E0A591B4918C91809365 +:100940004306E0ECF3E0A591B4918C9180934406CA +:10095000E2ECF3E0A591B4918C918093450680E0A0 +:1009600090E00E940404833461F481E090E00E94EE +:100970000404863631F482E090E00E940404873655 +:1009800009F16FE871E085E595E00E949A030E9405 +:100990009E0F6093840470938504809386049093E3 +:1009A00087048091880490918904909383048093B4 +:1009B0008204DF91CF911F910F91FF90EF90DF9014 +:1009C000CF90089583E090E00E940404F82E8FEF0A +:1009D0008F0D833118F064E771E0D5CF65E571E0E4 +:1009E00085E595E00E949A03C4E0D0E010E064E061 +:1009F000E62ECE010E940404082FCE0101960E942B +:100A00000404D82ECE0102960E94040490E0982F90 +:100A100088278D0D911D90936B0680936A06CE01F9 +:100A200003960E940404D82ECE0104960E9404046A +:100A300090E0982F88278D0D911D90936D068093DF +:100A40006C06CE0105960E940404D82ECE010696AF +:100A50000E94040490E0982F88278D0D911D90939B +:100A60006F0680936E06CE0107960E940404D82E6E +:100A7000CE0108960E94040490E0982F88278D0DDF +:100A8000911D90937106809370062996E0928C04D4 +:100A9000093421F484E00E94AA0F0BC0043521F42C +:100AA00084E00E94450E05C0013419F484E00E94E0 +:100AB000A8101F5FF1129DCF10928C0410928B042E +:100AC00010928A04EAE6F6E08AE796E01192119223 +:100AD0008E179F07D9F75BCFFF920F931F93CF938A +:100AE000DF93EC01F62E08811981C8010E940404ED +:100AF0008F1521F06F2DC8010E94091988819981F5 +:100B0000019699838883DF91CF911F910F91FF9078 +:100B10000895AF92BF92CF92DF92EF92FF920F9320 +:100B20001F93CF93DF93EC015A0169010E946C057A +:100B30006A2DCE010E946C056B2DCE010E946C05C2 +:100B40006C2DCE010E946C056D2DCE010E946C05AE +:100B5000602FCE010E946C05612FCE010E946C05B2 +:100B60006E2DCE010E946C056F2DCE01DF91CF91CD +:100B70001F910F91FF90EF90DF90CF90BF90AF90BB +:100B80000C946C052F923F924F925F926F927F92DE +:100B90008F929F92AF92BF92CF92DF92EF92FF928D +:100BA0000F931F93CF93DF93CDB7DEB7A1970FB607 +:100BB000F894DEBF0FBECDBF1C016D8BF8942C01E5 +:100BC000440C551C440C551C5D8E4C8E8201065500 +:100BD0001C4FD801CD90DD90ED90FC90CD82DE824F +:100BE000EF82F886F201EE59FC4F4080518062801E +:100BF00073804E8E5F8E68A279A2EC8DFD8DEF54CE +:100C0000FB4FC080D180E280F380C982DA82EB8220 +:100C1000FC822C01440C551CF201E357FB4F608110 +:100C20007181FC01E25AFB4F7080EC8DFD8DE25525 +:100C3000FD4FC080D180E280F380CE8ADF8AE88ECB +:100C4000F98E78948E8C9F8CA8A0B9A08D819E81FE +:100C5000AF81B885881A990AAA0ABB0AEC8DFD8D66 +:100C6000EA5EFB4FC080D180E280F38089819A8167 +:100C7000AB81BC818C199D09AE09BF0989879A8710 +:100C8000AB87BC876C017D01C718D108E108F1086A +:100C9000F201EE50FC4F808191812B01481A590AD4 +:100CA0005B8E4A8E5D885110DFC0EC8DFD8DE45F58 +:100CB000FA4F80819181A281B3818D879E87AF8712 +:100CC000B88B40900A0150900B0160900C0170901D +:100CD0000D01498A5A8A6B8A7C8ADA01C901841912 +:100CE0009509A609B7094D845E846F8478888419B4 +:100CF0009509A609B709B7FD47C0809104019091F5 +:100D00000501A0E0B0E0C816D906EA06FB06F8F037 +:100D10008091060190910701A0910801B09109010D +:100D200088159905AA05BB0590F480910E01909154 +:100D30000F01A0911001B091110149885A886B8868 +:100D40007C88481659066A067B0609F051C0F8945B +:100D5000EC8DFD8DE655FC4F208331834283538318 +:100D6000EC8DFD8DEE59FC4F2083318342835383FC +:100D7000789449015A014D805E806F807884841890 +:100D80009508A608B7083FC080910E0190910F0109 +:100D9000A0911001B091110129013A01481A590A94 +:100DA0006A0A7B0AD301C2014D845E846F8478880D +:100DB00084199509A609B709B7FD37C180910401C7 +:100DC00090910501A0E0B0E0C816D906EA06FB063E +:100DD00008F42BC18091060190910701A0910801B0 +:100DE000B091090188159905AA05BB0508F01DC138 +:100DF000F8948E8D9F8DA8A1B9A1F801808391836D +:100E0000A283B3837894F894F101E25AFB4F1082E5 +:100E1000EC8DFD8DE255FD4F1082118212821382FE +:100E20007894EC8DFD8DEA5EFB4F49805A806B8093 +:100E30007C804082518262827382F101EE0FFF1F3B +:100E4000EE50FC4F718360838C8D9D8D845F9A4F33 +:100E5000DC012D933D934D935C931397F101E85F73 +:100E6000FC4F80818F5F808329013A011D893E827A +:100E70002D826EE271E080917E0690917F060E9445 +:100E80005D03ED81FE81EE0FFF1FEA5BF94F60818C +:100E9000718180917E0690917F060E94260E6BE202 +:100EA00071E080917E0690917F060E945D034981EA +:100EB0005A816B817C8180917E0690917F060E9491 +:100EC000D90D68E271E080917E0690917F060E94C4 +:100ED0005D03B701A60180917E0690917F060E9476 +:100EE000D90D66E271E080917E0690917F060E94A6 +:100EF0005D0349855A856B857C8580917E0690913E +:100F00007F060E94D90D63E271E080917E06909188 +:100F10007F060E945D03B501A40180917E06909139 +:100F20007F060E94D90D60E271E080917E0690916B +:100F30007F060E945D03B301A20180917E0690911D +:100F40007F060E94F40D6EE171E080917E06909123 +:100F50007F060E945D0360918804709189048091EE +:100F60007E0690917F060E94DC0D6BE171E080911E +:100F70007E0690917F060E945D036A8D7B8D809135 +:100F80007E0690917F060E94DC0D111113C068E16E +:100F900071E080917E0690917F060E945D03ED8155 +:100FA000FE81E85FFC4F608180917E0690917F0614 +:100FB0000E943E0EC114D104E104F104C1F065E1C8 +:100FC00071E080917E0690917F060E945D036E899C +:100FD0007F89888D998DA70196010E942B19BA01EE +:100FE000A90180917E0690917F060E94D90D809183 +:100FF0007E0690917F06A1960FB6F894DEBF0FBED5 +:10100000CDBFDF91CF911F910F91FF90EF90DF90B7 +:10101000CF90BF90AF909F908F907F906F905F9098 +:101020004F903F902F900C949603A1960FB6F89492 +:10103000DEBF0FBECDBFDF91CF911F910F91FF900B +:10104000EF90DF90CF90BF90AF909F908F907F9068 +:101050006F905F904F903F902F9008952F923F9206 +:101060004F925F926F927F928F929F92AF92BF92B8 +:10107000CF92DF92EF92FF920F931F93CF93DF9364 +:101080006C014A015B018091F90490E0029664E1F1 +:1010900070E00E941719FC0120E030E040E0C4E15C +:1010A000D0E0CF01820F931FBE010E941719DC010F +:1010B000A656BD4F8C9190E08C159D0509F44F5FAD +:1010C0002F5F3F4F2431310561F7442309F4D7C026 +:1010D0001F0163E171E080917E0690917F060E947E +:1010E0005D03F601EE0FFF1FEA5BF94F608171812E +:1010F00080917E0690917F060E94260E61E171E04C +:1011000080917E0690917F060E945D03E12CF12C78 +:1011100021E0C701820D931D64E170E00E94171960 +:101120008C01FC01E656FD4F808190E0C816D9067F +:1011300009F085C0E801CC0FDD1FCC0FDD1F2111A8 +:1011400020C0FE01E65BFD4F80819181A281B381C9 +:10115000FE01E650FE4F4081518162817381840F10 +:10116000951FA61FB71F84159505A605B70508F49A +:101170005FC06EE071E080917E0690917F060E94D4 +:101180005D03F801EE0FFF1FEE52FE4F608171818B +:1011900080917E0690917F060E94DC0D6CE071E0EC +:1011A00080917E0690917F060E945D03FE01E65BC2 +:1011B000FD4F4081518162817381481959096A0943 +:1011C0007B0980917E0690917F060E94F40D6AE073 +:1011D00071E080917E0690917F060E945D03FE0182 +:1011E000E650FE4F408151816281738180917E067D +:1011F00090917F060E94D90D68E071E080917E0693 +:1012000090917F060E945D03F801E254FE4F6081D9 +:1012100080917E0690917F060E943E0EF801E65571 +:10122000FE4F608180917E0690917F060E9456035A +:10123000C65BDD4F488059806A807B8020E08FEF5D +:10124000E81AF80A84E1E816F10409F062CF809107 +:101250007E0690917F06DF91CF911F910F91FF90B5 +:10126000EF90DF90CF90BF90AF909F908F907F9046 +:101270006F905F904F903F902F900C949603DF916A +:10128000CF911F910F91FF90EF90DF90CF90BF9083 +:10129000AF909F908F907F906F905F904F903F9016 +:1012A0002F9008950F931F93CF93DF93EC0166E087 +:1012B00071E080917E0690917F060E945D038E0111 +:1012C000000F111FF801EA5BF94F60817181809175 +:1012D0007E0690917F060E94260EFE01EF5CF94F7C +:1012E000208180917E0690917F06222329F0213073 +:1012F00031F46EEF70E005C065EF70E002C062EFA0 +:1013000070E00E945D03C15ED94F8881882341F05F +:101310006AEE70E080917E0690917F060E945D03E8 +:1013200064EE70E080917E0690917F060E945D03DE +:10133000F801E550FA4F6081718180917E069091AD +:101340007F06DF91CF911F910F910C94DC0D62EE1F +:1013500070E080917E0690917F060E945D0360910F +:1013600002017091030180917E0690917F060E9498 +:10137000260E60EE70E080917E0690917F060E94BE +:101380005D03609100017091010180917E06909152 +:101390007F060C942C0ECF92DF92EF92FF926EEDAF +:1013A00070E080917E0690917F060E945D036091BF +:1013B0000E0170910F01809110019091110128EEA2 +:1013C000C22E23E0D22EE12CF12CA70196010E941F +:1013D0002B19BA01A90180917E0690917F060E9487 +:1013E000D90D6CED70E080917E0690917F060E9491 +:1013F0005D0360910A0170910B0180910C01909145 +:101400000D01A70196010E942B19BA01A901809133 +:101410007E0690917F060E94D90D6AED70E0809162 +:101420007E0690917F060E945D0360910601709197 +:1014300007018091080190910901A70196010E947E +:101440002B19BA01A90180917E0690917F060E9416 +:10145000D90D68ED70E080917E0690917F060E9424 +:101460005D03609104017091050180917E06909169 +:101470007F06FF90EF90DF90CF900C94E20D0F93DA +:101480001F93CF93DF93C6ECD0E0FE01649180916F +:101490007E0690917F060E9456032196F0E0C73D9C +:1014A000DF0799F761EC70E080917E0690917F06EE +:1014B0000E945D03CCEBD0E0FE01649180917E063A +:1014C00090917F060E9456032196F0E0C03CDF0712 +:1014D00099F761EB70E080917E0690917F060E9403 +:1014E0005D03CCE9D0E000EB10E0FE016491809157 +:1014F0007E0690917F060E94560321960C171D07C9 +:10150000A1F7DF91CF911F910F910895CF92DF92B4 +:10151000EF92FF920F931F93CF93DF930E949E0F42 +:101520006B017C0180917E0690917F060E9496035C +:101530000E943F0A67E471E080917E0690917F06E9 +:101540000E945D0306E611E0C0E081E0F801219110 +:1015500031918F0137FD12C0811108C065E471E03F +:1015600080917E0690917F060E945D036C2F809192 +:101570007E0690917F060E943E0E80E0CF5FC631CE +:1015800029F76AE371E080917E0690917F060E94C0 +:101590005D0367E371E080917E0690917F060E9473 +:1015A0005D03B701A60180917E0690917F060E949F +:1015B000D90D65E371E080917E0690917F060E94CF +:1015C0005D03609188047091890480917E069091FA +:1015D0007F060E94DC0D62E371E080917E069091AF +:1015E0007F060E945D0340918404509185046091C0 +:1015F00086047091870480917E0690917F060E94F8 +:10160000D90D60E371E080917E0690917F060E9483 +:101610005D03609182047091830480917E069091B5 +:101620007F060E94DC0D80917E0690917F060E94CD +:1016300096030E94CB090E94A70906E416E0C0E0C9 +:10164000D0E0F801819191918F0197FD09C0CE0101 +:101650000E94520980917E0690917F060E94960317 +:101660002196C231D10569F7DF91CF911F910F917A +:10167000FF90EF90DF90CF900895CF93DF93C4E970 +:10168000D0E0FE01649180917E0690917F060E94D9 +:1016900056032196F0E0CB39DF0799F7DF91CF9120 +:1016A00008954F925F926F927F928F929F92AF9226 +:1016B000BF92DF92EF92FF920F931F93CF93DF932E +:1016C000D62E682F70E08B01000F111F000F111F25 +:1016D000F801E65EFC4F80809180A280B3802901F2 +:1016E0003A01481859086A087B0853014201909151 +:1016F000F9049F5F9093F9049091F904943110F0EC +:101700001092F904E091F904F0E0A091A801B091E1 +:10171000A901ED012196D093A901C093A801EE0F74 +:10172000FF1FEE52FE4FB183A083E091F904F0E079 +:10173000E656FD4F8083E091F904C801865E9C4F18 +:101740007C01EC0188819981AA81BB81D4E0ED9F65 +:10175000F0011124E65BFD4F80839183A283B38364 +:10176000E091F90484E0E89FF0011124E650FE4F77 +:1017700080829182A282B382A091F904B0E0FB0141 +:10178000EA50FD4F8081A254BE4F8C93CB01880F4D +:10179000991FDC01A550BA4F0D90BC91A02D2D01D1 +:1017A000612C712CEB01CF5CD94F84149504A604F5 +:1017B000B704B0F4D7012D933D934D935C931397E9 +:1017C00070816881761372C0DC01A357BB4F8D9185 +:1017D0009C911197019611969C938E9362E567C038 +:1017E000A081B881CB018A569E4FAB135AC0EC0141 +:1017F000B881AB1709F442C0D801AF54BB4F4D902C +:101800005D906D907C901397DFEF4D1A5D0A6D0A25 +:101810007D0A4D925D926D927C921397D801AE59DC +:10182000BC4F2D933D934D935C931397DB01A059CF +:10183000BB4FCC91C1110FC0E801C655DC4F2883C6 +:1018400039834A835B83C1E0CC93DB01A25ABB4F4F +:101850006C916F5F6C93D801A255BD4F4D905D9018 +:101860006D907C901397840C951CA61CB71C8D92D0 +:101870009D92AD92BC92139763E414C0D801A25517 +:10188000BD4F4D905D906D907C901397840C951C8E +:10189000A61CB71C8D929D92AD92BC92139760E5E9 +:1018A00001C067E47081DC017C9301C068E5A09110 +:1018B000F904B0E0A655BE4F6C93D801A65EBC4FAC +:1018C0002D933D934D935C931397D082DF91CF91ED +:1018D0001F910F91FF90EF90DF90BF90AF909F907E +:1018E0008F907F906F905F904F900895BF92CF92AE +:1018F000DF92EF92FF920F931F93CF93DF93C82F46 +:101900000E949E0F6B017C018C2F90E0FC01EE0F7A +:10191000FF1FE254FC4FA591B4910C91DC01AD5B2B +:10192000B94F1C91FC01EE0FFF1FE65DFE4F0190C9 +:10193000F081E02D2081102712230C93112339F11F +:10194000FC01EB5EFE4FC081D1E0FC01EE5EFE4F7C +:10195000B080BC16E0F08D2F8123B1F0EC2FF0E0C9 +:10196000EE0FFF1FEA59FE4F0190F081E02D1E1689 +:101970001F0654F461E09D2F902309F460E0A70155 +:1019800096018E2F0E94510BDD0FCF5FE2CFDF91CA +:10199000CF911F910F91FF90EF90DF90CF90BF906C +:1019A00008951F920F920FB60F9211242F933F9319 +:1019B0004F935F936F937F938F939F93AF93BF9357 +:1019C000EF93FF9382E00E94760CFF91EF91BF911D +:1019D000AF919F918F917F916F915F914F913F91C7 +:1019E0002F910F900FBE0F901F9018951F920F927E +:1019F0000FB60F9211242F933F934F935F936F93E2 +:101A00007F938F939F93AF93BF93EF93FF9381E067 +:101A10000E94760CFF91EF91BF91AF919F918F91B2 +:101A20007F916F915F914F913F912F910F900FBEDA +:101A30000F901F9018951F920F920FB60F921124BE +:101A40002F933F934F935F936F937F938F939F93C6 +:101A5000AF93BF93EF93FF9380E00E94760CFF91CA +:101A6000EF91BF91AF919F918F917F916F915F9116 +:101A70004F913F912F910F900FBE0F901F9018958F +:101A80000E9476198F929F92AF92BF92EF92FF922F +:101A90000F931F93CF93DF93CDB7DEB7A1970FB608 +:101AA000F894DEBF0FBECDBF7C01FA01CB0119A2B5 +:101AB000223008F42AE08E010F5D1F4F822E912CF8 +:101AC000A12CB12CBF01A50194010E942B19F90191 +:101AD000CA01015011096A3014F4605D01C0695CEB +:101AE000D8016C93232B242B252B61F7B801C70158 +:101AF0000E948103A1960FB6F894DEBF0FBECDBF42 +:101B0000DF91CF911F910F91FF90EF90BF90AF9019 +:101B10009F908F900895833081F028F4813099F060 +:101B20008230A1F008958730A9F08830B9F0843070 +:101B3000D1F4809180008F7D03C0809180008F77E9 +:101B400080938000089584B58F7702C084B58F7D1F +:101B500084BD08958091B0008F7703C08091B0005C +:101B60008F7D8093B00008953FB7F89480918506EB +:101B700090918606A0918706B091880626B5A89B0D +:101B800005C02F3F19F00196A11DB11D3FBFBA2F0F +:101B9000A92F982F8827820F911DA11DB11DBC016F +:101BA000CD0142E0660F771F881F991F4A95D1F734 +:101BB00008952AE00C94420DAB0160E070E02AE049 +:101BC0000C94420D0F931F93CF93DF93EC010E946F +:101BD000DC0D8C01CE010E949603800F911FDF91D6 +:101BE000CF911F910F910895CF92DF92EF92FF92C4 +:101BF0000F931F93CF93DF9377FF1DC06A017B0183 +:101C0000EC016DE20E9456038C0144275527BA016E +:101C10004C195D096E097F092AE0CE010E94420D30 +:101C2000800F911FDF91CF911F910F91FF90EF9047 +:101C3000DF90CF9008952AE0DF91CF911F910F910F +:101C4000FF90EF90DF90CF900C94420DAB01770F97 +:101C5000660B770B0C94F40D0F931F93CF93DF93C8 +:101C6000EC010E94260E8C01CE010E949603800F8B +:101C7000911FDF91CF911F910F910895462F50E052 +:101C800060E070E02AE00C94420D823058F40E942B +:101C90003D0B61E174E080917E0690917F060E9489 +:101CA0005D0353C080916A0690916B069C012150A0 +:101CB00031092F3F334038F00E943D0B60916A0696 +:101CC00070916B0614C09093030180930201809180 +:101CD0006C0690916D069C01215031092F3F3340D5 +:101CE00060F00E943D0B60916C0670916D068091D2 +:101CF0007E0690917F060C94E20D909301018093F3 +:101D0000000165EF73E080917E0690917F060E944E +:101D10005D0360916A0670916B0680917E069091DA +:101D20007F060E94DC0D63EF73E080917E06909148 +:101D30007F060E945D0360916C0670916D06809134 +:101D40007E0690917F060E94DC0D80917E06909128 +:101D50007F060C9496030F931F93CF93DF93EC01B0 +:101D60000E943E0E8C01CE010E949603800F911FAF +:101D7000DF91CF911F910F91089587E480937C00AC +:101D800080917A00806480937A0080917A0086FD49 +:101D9000FCCF809178002091790090E0922B0895FB +:101DA000CF93DF93282F30E0F901EB5AFC4F849159 +:101DB000F901E759FC4FD491F901EC53FC4FC49160 +:101DC000CC2391F081110E948B0DEC2FF0E0EE0FEF +:101DD000FF1FE654FC4FA591B491EC91ED2381E0F7 +:101DE00090E021F480E002C080E090E0DF91CF91AC +:101DF00008951F93CF93DF93282F30E0F901EB5A1A +:101E0000FC4F8491F901E759FC4FD491F901EC534F +:101E1000FC4FC491CC23C1F0162F81110E948B0D71 +:101E2000EC2FF0E0EE0FFF1FE55BFC4FA591B491A6 +:101E30009FB7F894111104C08C91D095D82302C09B +:101E4000EC91DE2BDC939FBFDF91CF911F91089522 +:101E5000CF93DF9390E0FC01E759FC4F2491FC0104 +:101E6000EC53FC4F8491882361F190E0880F991F17 +:101E7000FC01EF5BFC4FC591D491FC01E55BFC4F8D +:101E8000A591B491611109C09FB7F89488812095FC +:101E900082238883EC912E230BC0623061F49FB7BC +:101EA000F8948881322F309583238883EC912E2BF0 +:101EB0002C939FBF06C08FB7F894E8812E2B288300 +:101EC0008FBFDF91CF9108958F929F92AF92BF9273 +:101ED000CF92DF92EF92FF926B017C010E94B40DD2 +:101EE0004B015C01C114D104E104F104F1F00E9442 +:101EF000B40DDC01CB0188199909AA09BB09883EF8 +:101F00009340A105B10570F321E0C21AD108E108A0 +:101F1000F10888EE880E83E0981EA11CB11CC11444 +:101F2000D104E104F10419F7DDCFFF90EF90DF90C9 +:101F3000CF90BF90AF909F908F9008952FB7F89457 +:101F4000609181067091820680918306909184064B +:101F50002FBF0895CF92DF92EF92FF920F931F93BE +:101F6000843058F40E943D0B60E374E080917E065B +:101F700090917F060E945D03DFC020916A06309138 +:101F80006B06C901019780319E4038F00E943D0BDD +:101F900060916A0670916B0677C0A8EEB3E00E946C +:101FA00053196B017C0160930E0170930F018093B4 +:101FB0001001909311010E949E0FDC01CB018C0D4A +:101FC0009D1DAE1DBF1D00917A0610917B062091CC +:101FD0007C0630917D0680179107A207B30780F435 +:101FE0000E949E0FDC01CB018C0D9D1DAE1DBF1DFF +:101FF00080937A0690937B06A0937C06B0937D062F +:1020000020916C0630916D06C901019780319E4088 +:1020100038F00E943D0B60916C0670916D0634C0E3 +:10202000A8EEB3E00E94531960930A0170930B016C +:1020300080930C0190930D0120916E0630916F06F4 +:1020400021318EE0380738F00E943D0B60916E061A +:1020500070916F0619C0A8EEB3E00E945319609307 +:102060000601709307018093080190930901809104 +:102070007006909171068536910590F00E943D0B97 +:10208000609170067091710680917E0690917F0636 +:102090001F910F91FF90EF90DF90CF900C94E20D85 +:1020A00090930501809304016CE174E080917E06B9 +:1020B00090917F060E945D0360916A0670916B06A5 +:1020C00080917E0690917F060E94DC0D6AE174E0AB +:1020D00080917E0690917F060E945D0360916C0660 +:1020E00070916D0680917E0690917F060E94DC0DB6 +:1020F00068E174E080917E0690917F060E945D0306 +:1021000060916E0670916F0680917E0690917F06B9 +:102110000E94DC0D66E174E080917E0690917F065E +:102120000E945D03609170067091710680917E0639 +:1021300090917F060E94DC0D80917E0690917F0633 +:102140001F910F91FF90EF90DF90CF900C9496032A +:102150007F928F929F92AF92BF92CF92DF92EF9237 +:10216000FF920F931F93CF93DF93782E0E949E0FC1 +:10217000AB01BC01C0906A06D0906B0685E18C155E +:1021800058F05601BB24F501EE0FFF1FEA59FE4F30 +:102190000081118117FF1EC00E943D0B68ED73E0A6 +:1021A00080917E0690917F060E945D036C2D809148 +:1021B0007E0690917F06DF91CF911F910F91FF9046 +:1021C000EF90DF90CF90BF90AF909F908F907F90D7 +:1021D0000C94AB0E11277801EE0CFF1CF701EE5B9F +:1021E000FE4FC081D181F701EA5BF94F4F01808139 +:1021F0009181A816B906D1F0FE01FF27EC53FC4FE0 +:10220000E491E2508E2F90E0FC01EE0FFF1FE254AC +:10221000FC4FA591B4912C91FC01ED5BF94F20830B +:10222000C8010E940604F401B182A08280916C066C +:1022300090916D060297029788F00E943D0B6FE81F +:1022400073E080917E0690917F060E945D036C2D65 +:1022500080917E0690917F060E94AB0EF801EF5CA4 +:10226000F94F81E020916C0630916D0623303105E5 +:1022700009F080E08083C801805D9E4F6C01F80109 +:10228000E15EF94F5F01F2E0F71590F480916E0680 +:1022900090916F06892B61F0F6018081811108C051 +:1022A00062E08C2F0E94280F81E0F501808306C038 +:1022B00060E08C2F0E94280FF5011082F3E0F715E3 +:1022C00030F48091700690917106009711F482E0CD +:1022D00090E0F701E550FA4F91838083F601808109 +:1022E000811123C0DD27FE01E759FC4F8491FE01D7 +:1022F000EC53FC4FC491CC23B1F1C250AC2FB0E0F1 +:10230000AA0FBB1FA65DBE4F0D90BC91A02DEC91F6 +:10231000E82BEC932091680081E090E001C0880FE9 +:10232000CA95EAF7822B809368006DE773E080918D +:102330007E0690917F060E945D03C8010E945209AB +:1023400080917E0690917F06DF91CF911F910F9132 +:10235000FF90EF90DF90CF90BF90AF909F908F90C5 +:102360007F900C9496030E943D0B68E873E0809187 +:102370007E0690917F06DF91CF911F910F91FF9084 +:10238000EF90DF90CF90BF90AF909F908F907F9015 +:102390000C949A031F920F920FB60F9211242F9351 +:1023A0003F938F939F93AF93BF93809181069091BA +:1023B0008206A0918306B09184063091800623E0C6 +:1023C000230F2D3720F40196A11DB11D05C026E86D +:1023D000230F0296A11DB11D2093800680938106D4 +:1023E00090938206A0938306B0938406809185061D +:1023F00090918606A0918706B09188060196A11D4E +:10240000B11D8093850690938606A0938706B093AE +:102410008806BF91AF919F918F913F912F910F901F +:102420000FBE0F901F901895E5E5F5E0138212821C +:1024300088EE93E0A0E0B0E084839583A683B78321 +:102440008CE191E09183808385EC90E0958784878F +:1024500084EC90E09787868780EC90E0918B808B6E +:1024600081EC90E0938B828B82EC90E0958B848B57 +:1024700086EC90E0978B868B118E128E138E148EC5 +:102480000895CF93DF93CDB7DEB727970FB6F894B3 +:10249000DEBF0FBECDBF789484B5826084BD84B5A5 +:1024A000816084BD85B5826085BD85B5816085BD4F +:1024B00080916E00816080936E0010928100809107 +:1024C000810082608093810080918100816080938F +:1024D0008100809180008160809380008091B100B4 +:1024E00084608093B1008091B00081608093B000DF +:1024F00080917A00846080937A0080917A00826073 +:1025000080937A0080917A00816080937A00809134 +:102510007A00806880937A001092C100E09165058E +:10252000F091660582E08083E0916105F09162059B +:102530001082E0916305F091640583E3808310923B +:102540006D05E0916905F0916A0586E08083E09170 +:102550006705F0916805808180618083E09167055F +:10256000F0916805808188608083E0916705F09133 +:102570006805808180688083E0916705F091680537 +:1025800080818F7D808364EF71E080E090E00E9425 +:10259000640F789485E595E00E94960385E595E0C3 +:1025A00090937F0680937E0610928904109288048F +:1025B0000E949E0F6093F2057093F3058093F405DB +:1025C0009093F5050E946D0461E082E00E94280F5F +:1025D00061E08CE00E94280F0E94860A28EE222EDD +:1025E00023E0322E412C512C0E949E0F0091F205C7 +:1025F0001091F3052091F4053091F50560177107EE +:102600008207930750F420918804309189042F5F4A +:102610003F4F30938904209388046093F2057093B0 +:10262000F3058093F4059093F50585E595E00E9408 +:10263000A502892B09F49BC285E595E00E948302DF +:10264000D82E9CE2C92EDC1019C0E0918C04E73032 +:1026500008F08DC281E08E0F80938C04F0E0EE0FC5 +:10266000FF1FE659F94F80918A0490918B04918362 +:10267000808310928B0410928A0479C280ED8D0DB4 +:102680008A30A8F420918A0430918B040AE0E02E6D +:10269000E29EC001E39E900D1124C0978D0D911D07 +:1026A000D7FC9A9590938B0480938A0460C28FE93B +:1026B0008D0D8A3108F05BC2809154058823F1F1B9 +:1026C0006BEB72E085E595E00E945D031AE6E12E72 +:1026D00016E0F12E00E010E080918C0490E080176D +:1026E0009107A4F00115110531F069EB72E085E561 +:1026F00095E00E945D03F701619171917F0185E58D +:1027000095E00E94DC0D0F5F1F4FE6CF6D2D85E534 +:1027100095E00E94560362EB72E085E595E00E9429 +:102720005D0360918C0470E06F5F7F4F85E595E0FD +:102730000E94260E85E595E00E9496038D2DDD0C06 +:10274000990BAA0BBB0BFC01E156F109E631F1052F +:1027500008F0FFC1EC5CFF4F0C944D1980918C0484 +:10276000E82FF0E0EE0FFF1FE659F94F20918A04A1 +:1027700030918B04318320838F5F0E94A810E9C1C0 +:1027800080918C04E82FF0E0EE0FFF1FE659F94F1F +:1027900020918A0430918B043183208300916A0652 +:1027A00010916B068F3F61F0063150F4F801FF275E +:1027B000EE0FFF1FEA59FE4F8081918197FF05C000 +:1027C0000E943D0B6CE572E0EDC39927FC01E05DD2 +:1027D000FE4F20812111DFC3FC01EE0FFF1FEE5BD6 +:1027E000FE4F208131813327F901E759FC4F449195 +:1027F000F901EC53FC4F2491222309F4C3C3225066 +:10280000E22FF0E0EE0FFF1FE65DFE4FA081B181E9 +:102810003C91409543234C933C913111BCC3609152 +:10282000680041E050E06A0102C0CC0CDD1C2A9532 +:10283000E2F796012095262320936800ACC31A8204 +:10284000198263E4CE0101960E946C0566E6CE0112 +:1028500001960E946C0567E6CE0101960E946C0508 +:1028600036E4632E36E0732EF30181E0219131913D +:1028700037FF8F5F06E0EA36F007C1F78F5F8B8383 +:10288000682FCE0101960E946C05E0900401F09043 +:1028900005016091060170910701809108019091F6 +:1028A0000901A20191010E942B192C833D834E83C3 +:1028B0005F8360910A0170910B0180910C019091EE +:1028C0000D01A20191010E942B1949015A01609149 +:1028D0000E0170910F018091100190911101A201E0 +:1028E00091010E942B19BA01A9010C811D8194014B +:1028F00069E4CE0101960E94890520910001309182 +:1029000001014091020150910301E12CF12C00E002 +:1029100010E064E5CE0101960E9489058BEF882EB8 +:1029200085E0982E9FE1A92E96E0B92E21E3C22ED4 +:1029300026E0D22EF301419151913F0157FD14C081 +:10294000F401E080F180F501008110E0F601808162 +:10295000811103C022E030E002C023E030E061E4F6 +:10296000CE0101960E948905F2E08F0E911C0FEFB7 +:10297000A01AB00A1FEFC11AD10A2AE6621626E091 +:102980007206C1F66BE372E085E595E00E945D0397 +:102990006B8185E595E00E943E0E68E372E085E577 +:1029A00095E00E945D0369817A8185E595E00E944A +:1029B0002C0ECFC00E94860ACCC080918C04E82FD8 +:1029C000F0E0EE0FFF1FE659F94F20918A04309195 +:1029D0008B04318320838F5F0E94AA0FBAC080913D +:1029E0008C04E82FF0E0EE0FFF1FE659F94F20911D +:1029F0008A0430918B043183208320916A063091C0 +:102A00006B062130310509F0A4C08F3F09F4A1C045 +:102A100062E372E0BBC20E946D049BC00E949E0FE5 +:102A20004B015C0167E272E080917E0690917F0627 +:102A30000E945D030E943F0A80917E0690917F066E +:102A40000E9496030E94CB090E94A70965E272E0EA +:102A500080917E0690917F060E945D0360915405EF +:102A600080917E0690917F060E94AB0EB6E4EB2E1D +:102A7000B6E0FB2E00E010E0F701819191917F011B +:102A800097FD16C0C8010E94520962E272E080916F +:102A90007E0690917F060E945D03A501940161E08E +:102AA000C8010E94C205B501A401C8010E942E08F8 +:102AB0000F5F1F4F02311105F9F680E090E00E9490 +:102AC000040400917E0610917F06833409F065C3EB +:102AD00081E090E00E940404863609F05EC382E043 +:102AE00090E00E940404873609F057C369C28091C0 +:102AF0008C04E82FF0E0EE0FFF1FE659F94F20910C +:102B00008A0430918B04318320838F5F0E94450EAD +:102B100020C020918A0430918B0480917E06909190 +:102B20007F062F3F310588F4209354056DE972E04C +:102B30000E945D0360918A0470918B0480917E06EF +:102B400090917F060E94E20D04C067E772E00E9448 +:102B50009A0310928C0410928B0410928A04EAE675 +:102B6000F6E01192119216E0EA37F107D1F7809161 +:102B700068069091690697FDDEC060E082E00E94E1 +:102B8000F90E6AE070E080E090E00E94640F0E941D +:102B9000BD0E8C019093FA058093F90561E082E007 +:102BA0000E94F90E6AE070E080E090E00E94640FFD +:102BB0000E94BD0E9093F8058093F705801B910B42 +:102BC00020910001309101012817390754F020911C +:102BD000020130910301821793070CF090C010E0BE +:102BE00001C011E08091F605811709F488C01093A7 +:102BF000F605612F8CE00E94F90E0E949E0F9B014A +:102C0000AC01612F81E10E94510B809154058A3003 +:102C100008F475C066EF72E080917E0690917F06A1 +:102C20000E945D0365E170E080917E0690917F06D1 +:102C30000E94260E69EE72E080917E0690917F06DA +:102C40000E945D0365E170E080917E0690917F06B1 +:102C50000E94260E65EE72E080917E0690917F06BE +:102C60000E945D0360EE72E080917E0690917F0687 +:102C70000E945D03612F80917E0690917F060E94E5 +:102C80003E0E65ED72E080917E0690917F060E9477 +:102C90005D036091F90480917E0690917F060E9409 +:102CA0003E0E6CEC72E080917E0690917F060E9451 +:102CB0005D034091F5045091F6046091F704709122 +:102CC000F80480917E0690917F060E94D90D62ECF7 +:102CD00072E080917E0690917F060E945D03609174 +:102CE000AF047091B00480917E0690917F060E949F +:102CF000DC0D80917E0690917F060E949603809164 +:102D000054058431C0F06FE373E080917E069091AA +:102D10007F060E945D036091F7057091F805809130 +:102D2000F9059091FA05681B790B80917E069091C8 +:102D30007F060E942C0E809154058A3030F50E9447 +:102D40009E0F00917A0610917B0620917C063091AF +:102D50007D066B017C01C01AD10AE20AF30AF7FE74 +:102D6000D6C000910A0110910B0120910C01309105 +:102D70000D016B017C01C01AD10AE20AF30AE6E4F4 +:102D8000F6E0ACE0B5E040E0BBC036E4A32E36E0B0 +:102D9000B32E4AEFE42E44E0F42E52E4C52E51E067 +:102DA000D52E00E010E0F501819091905F0118149C +:102DB00019040CF085C0F6016080862D0E94D00EAB +:102DC000782EF7018081781609F47AC0708268E362 +:102DD00073E080917E0690917F060E945D03B401AE +:102DE00080917E0690917F060E94260E6BE273E032 +:102DF00080917E0690917F060E945D03662D8091F2 +:102E00007E0690917F060E943E0E68E273E08091FC +:102E10007E0690917F060E945D036BE173E08091D6 +:102E20007E0690917F060E945D03672D80917E064D +:102E300090917F060E943E0E60E173E080917E06D5 +:102E400090917F060E945D036091F90480917E0657 +:102E500090917F060E943E0E67E073E080917E06AF +:102E600090917F060E945D03F801EE0FFF1FEE0FA9 +:102E7000FF1FEF54FB4F408151816281738180912C +:102E80007E0690917F060E94D90D6DEF72E08091D1 +:102E90007E0690917F060E945D03F801EE0FFF1FF2 +:102EA000E357FB4F6081718180917E0690917F0690 +:102EB0000E94DC0D80917E0690917F060E94960311 +:102EC0000F5F1F4FFFEFEF1AFF0A22E0C20ED11C67 +:102ED0000231110509F067CF32CF0D911D912D916F +:102EE0003C911397C701B601601B710B820B930BCA +:102EF00097FF41E014968AE696E08E179F0729F027 +:102F00002191319137FDF6CFE8CF4423C1F10E94E2 +:102F10009E0F4B015C0186E4E82E86E0F82E00E06F +:102F200010E0F701819191917F0197FD0FC0A501FC +:102F3000940160E0C8010E94C2058091540585306B +:102F400028F0B501A401C8010E942E080F5F1F4F91 +:102F50000231110531F780910E0190910F01A0917E +:102F60001001B0911101880E991EAA1EBB1E8092FD +:102F70007A0690927B06A0927C06B0927D060E9413 +:102F80004C0332CB0E943D0B65E572E080917E06DA +:102F900090917F06DCCD40E050E0BA010E9406042B +:102FA0006AE472E080917E0690917F060E945D0344 +:102FB000602F80917E0690917F060E94AB0EC9CD56 +:102FC00083E090E00E940404682EF4E1F81718F4FE +:102FD00061ED71E0E4C0C8010E9496036FEB71E0FF +:102FE00080917E0690917F060E945D0380E090E0D4 +:102FF0000E940404682F80917E0690917F060E94B3 +:10300000560381E090E00E940404682F80917E06C0 +:1030100090917F060E94560382E090E00E94040493 +:10302000682F80917E0690917F060E94560366EB82 +:1030300071E080917E0690917F060E945D0383E09F +:1030400090E00E940404682F70E080917E069091C9 +:103050007F060E94260E80917E0690917F060E9438 +:103060009603712C04E010E0671409F49BC0C801BA +:103070000E9404048B83C80101960E940404F82E68 +:10308000C80102960E940404882E912C982C882452 +:103090008F0C911CC80103960E940404F82EC801ED +:1030A00004960E940404A82EB12CBA2CAA24AF0CBA +:1030B000B11CC80105960E940404F82EC8010696AA +:1030C0000E940404C82ED12CDC2CCC24CF0CD11CA3 +:1030D000C80107960E940404F82EC80108960E94B1 +:1030E000040490E0982F88279C012F0D311D790151 +:1030F000075F1F4F6DEA71E080917E0690917F0619 +:103100000E945D036B8180917E0690917F060E94F4 +:1031100056036BEA71E080917E0690917F060E94D3 +:103120005D03B40180917E0690917F060E94DC0DC4 +:1031300069EA71E080917E0690917F060E945D03AE +:10314000B50180917E0690917F060E94DC0D67EAB2 +:1031500071E080917E0690917F060E945D03B6012A +:1031600080917E0690917F060E94DC0D65EA71E0F9 +:1031700080917E0690917F060E945D03B701809149 +:103180007E0690917F060E94DC0D80917E069091D4 +:103190007F060E949603739467CF6CEE71E0C801BE +:1031A0000E949A0360E172E080917E0690917F0612 +:1031B0000E945D03C0907A06D0907B06E0907C066A +:1031C000F0907D060E949E0F97018601061B170B4B +:1031D000280B390BB901A80180917E0690917F06DA +:1031E0000E94D90D62E072E080917E0690917F0688 +:1031F0000E945D0380917E0690917F060E94960357 +:10320000A8CCF999FECF92BD81BDF89A992780B5D7 +:103210000895262FF999FECF1FBA92BD81BD20BD1A +:103220000FB6F894FA9AF99A0FBE0196089597FB93 +:10323000072E16F4009407D077FD09D00E9462197A +:1032400007FC05D03EF4909581959F4F08957095A9 +:1032500061957F4F0895A1E21A2EAA1BBB1BFD01A9 +:103260000DC0AA1FBB1FEE1FFF1FA217B307E40765 +:10327000F50720F0A21BB30BE40BF50B661F771FBD +:10328000881F991F1A9469F760957095809590959D +:103290009B01AC01BD01CF010895EE0FFF1F05900A +:1032A000F491E02D0994A29FB001B39FC001A39FA8 +:1032B000700D811D1124911DB29F700D811D11246F +:1032C000911D0895AA1BBB1B51E107C0AA1FBB1F7C +:1032D000A617B70710F0A61BB70B881F991F5A95A2 +:1032E000A9F780959095BC01CD01089581E090E00B +:0A32F000F8940C947B19F894FFCFBA +:1032FA006E0064000200D007000060EA000030752A +:10330A0000000D1307080E0000000000DD0246024F +:10331A0074022A03A502830297026B006C006D00F7 +:10332A000000000000000000000000000000000093 +:10333A000001030004000500060007000800090058 +:10334A000A000B000C000E000F0010001100120002 +:10335A00130014001500FFFFFFFFFFFF000001002C +:10336A000200030004000500060007000800FFFF32 +:10337A0009000A000B000C000D000E000F001000DF +:06338A0011000D0A000015 :00000001FF diff --git a/fhem/contrib/arduino/ArduCounter3.00.ino b/fhem/contrib/arduino/ArduCounter3.00.ino new file mode 100755 index 000000000..f5e2059d3 --- /dev/null +++ b/fhem/contrib/arduino/ArduCounter3.00.ino @@ -0,0 +1,1708 @@ +/* + * Sketch for counting impulses in a defined interval + * e.g. for power meters with an s0 interface that can be + * connected to an input of an arduino or esp8266 board + * + * the sketch uses pin change interrupts which can be anabled + * for any of the inputs on e.g. an arduino uno, jeenode, wemos d1 etc. + * + * the pin change Interrupt handling for arduinos used here + * is based on the arduino playground example on PCINT: + * http://playground.arduino.cc/Main/PcInt which is outdated. + * + * see https://github.com/GreyGnome/EnableInterrupt for a newer library (not used here) + * and also + * https://playground.arduino.cc/Main/PinChangeInterrupt + * http://www.avrfreaks.net/forum/difference-between-signal-and-isr + * + * Refer to avr-gcc header files, arduino source and atmega datasheet. + */ + +/* Arduino Uno / Nano Pin to interrupt map: + * D0-D7 = PCINT 16-23 = PCIR2 = PD = PCIE2 = pcmsk2 + * D8-D13 = PCINT 0-5 = PCIR0 = PB = PCIE0 = pcmsk0 + * A0-A5 (D14-D19) = PCINT 8-13 = PCIR1 = PC = PCIE1 = pcmsk1 + */ + +/* test cmds analog ESP: + * 20v + * 17,3,0,50a + * 15,25t + * + */ + +/* + Changes: + V1.2 + 27.10.16 - use noInterrupts in report() + - avoid reporting very short timeDiff in case of very slow impulses after a report + - now reporting is delayed if impulses happened only within in intervalSml + - reporting is also delayed if less than countMin pulses counted + - extend command "int" for optional intervalSml and countMin + 29.10.16 - allow interval Min >= Max or Sml > Min + which changes behavior to take fixed calculation interval instead of timeDiff between pulses + -> if intervalMin = intervalMax, counting will allways follow the reporting interval + 3.11.16 - more noInterrupt blocks when accessing the non uint8_t volatiles in report + V1.3 + 4.11.16 - check min pulse width and add more output, + - prefix show output with M + V1.4 + 10.11.16 - restructure add Cmd + - change syntax for specifying minPulseLengh + - res (reset) command + V1.6 + 13.12.16 - new startup message logic?, newline before first communication? + 18.12.16 - replace all code containing Strings, new communication syntax and parsing from Jeelink code + V1.7 + 2.1.17 - change message syntax again, report time as well, first and last impulse are reported + relative to start of intervall not start of reporting intervall + V1.8 + 4.1.17 - fixed a missing break in the case statement for pin definition + 5.1.17 - cleanup debug logging + 14.10.17 - fix a bug where last port state was not initialized after interrupt attached but this is necessary there + 23.11.17 - beautify code, add comments, more debugging for users with problematic pulse creation devices + 28.12.17 - better reportung of first pulse (even if only one pulse and countdiff is 0 but realdiff is 1) + 30.12.17 - rewrite PCInt, new handling of min pulse length, pulse history ring + 1.1.18 - check len in add command, allow pin 8 and 13 + 2.1.18 - add history per pin to report line, show negative starting times in show history + 3.1.18 - little reporting fix (start pos of history report) + + V2.0 + 17.1.18 - rewrite many things - use pin number instead of pcIntPinNumber as index, split interrupt handler for easier porting to ESP8266, ... + V2.23 + 10.2.18 - new commands for check alive and quit, send setup message after reboot also over tcp + remove reporting time of first pulse (now we hava history) + remove pcIntMode (is always change now) + pulse min interval is now always checked and defaults to 2 if not set + march 2018 many changes more to support ESP8266 + 7.3.18 - change pin config output, fix pullup (V2.26), store config in eeprom and read it back after boot + 22.4.18 - many changes, delay report if tcp mode and disconnected, verbose levels, ... + 13.5.18 - V2.36 Keepalive also on Arduino side + 9.12.18 - V3.0 start implementing analog input for old ferraris counters + 6.1.19 - V3.1 showIntervals in hello + 19.1.19 - V3.12 support for ESP with analog + + + ToDo / Ideas: + + +*/ + +/* allow printing of every pin change to Serial */ +#define debugPins 1 + +/* allow tracking of pulse lengths */ +#define pulseHistory 1 + +/* support analog input for ferraris counters with IR light hardware */ +#define analogIR 1 + +/* use a sample config at boot */ +// #define debugCfg 1 + +#include "pins_arduino.h" +#include + +const char versionStr[] PROGMEM = "ArduCounter V3.12"; +const char compile_date[] PROGMEM = __DATE__ " " __TIME__; +const char errorStr[] PROGMEM = "Error: "; + +#ifdef ARDUINO_BOARD +const char boardName1[] PROGMEM = ARDUINO_BOARD; +#endif + +#if defined(__AVR_ATmega328P__) || defined(__AVR_ATmega168__) +#ifdef ARDUINO_AVR_NANO +const char boardName[] PROGMEM = "NANO"; +#else +const char boardName[] PROGMEM = "UNO"; +#endif +#elif defined(__AVR_ATmega32U4__) || defined(__AVR_ATmega16U4__) +const char boardName[] PROGMEM = "Leonardo"; +#elif defined(__AVR_ATmega1280__) || defined(__AVR_ATmega2560__) +const char boardName[] PROGMEM = "Mega"; +#elif defined(ESP8266) +const char boardName[] PROGMEM = "ESP8266"; +#else +const char boardName[] PROGMEM = "UNKNOWN"; +#endif + +#define SERIAL_SPEED 38400 +#define MAX_INPUT_NUM 8 + +#ifdef analogIR +int sensorValueOff = 0; // value read from the photo transistor when ir LED is off +int sensorValueOn = 0; // value read from the photo transistor when ir LED is on +int analogThresholdMin = 100; // min value of analog input +int analogThresholdMax = 110; // max value of analog input + +uint8_t triggerState; // todo: use existing arrays instead + +// save measurement during same level as sum and count to get average and then put in history when doCount is called +// but how do we do this before we can detect the levels? + +#endif + +#ifdef ESP8266 // ESP variables and definitions +#include // ============================= + +const char* ssid = "MySSID"; +const char* password = "secret"; + +WiFiServer Server(80); // For ESP WiFi connection +WiFiClient Client1; // active TCP connection +WiFiClient Client2; // secound TCP connection to send reject message +boolean Client1Connected; // remember state of TCP connection +boolean Client2Connected; // remember state of TCP connection + +boolean tcpMode = false; +uint8_t delayedTcpReports = 0; // how often did we already delay reporting because tcp disconnected +uint32_t lastDelayedTcpReports = 0; // last time we delayed + +#define MAX_HIST 20 // 20 history entries for ESP boards (can be increased) + +#ifdef analogIR // code for ESP with analog pin and reflection light barrier support (test) + +#define MAX_APIN 18 +#define MAX_PIN 10 + +/* ESP8266 pins that are typically ok to use + * (some might be set to -1 (disallowed) because they are used + * as reset, serial, led or other things on most boards) + * maps printed pin numbers to sketch internal index numbers */ +short allowedPins[MAX_APIN] = + { 0, 1, 2, -1, // printed pin numbers 0,1,2 are ok to be used + -1, 3, -1, -1, // printed pin number 5 is ok to be used + -1, -1, -1, -1, // 8-11 not avaliable + -1, -1, -1, -1, // 12-15 not avaliable + -1, 4 }; // 16 not available, 17 is analog + +/* Wemos / NodeMCU Pins 3,4 and 8 (GPIO 0,2 and 15) define boot mode and therefore + * can not be used to connect to signal */ + +/* Map from sketch internal pin index to real chip IO pin number (not aPin, e.g. for ESP) + Note that the internal numbers might be different from the printed + pin numbers (e.g. pin 0 is in index 0 but real chip pin number 16! */ +short internalPins[MAX_PIN] = + { D0, D1, D2, D5, A0 }; // only the allowed pins in the internal array, + // D0=16, D1=5, D2=4, D5=14, A0=17 + +uint8_t analogPins[MAX_PIN] = + { 0,0,0,0,1 }; // internal index 4 is analog + +const int analogInPin = A0; // Analog input pin that the photo transistor is attached to (internally number 17) +const int irOutPin = D6; // Digital output pin that the IR-LED is attached to +const int ledOutPin = D7; // Signal LED output pin + +#else // code for ESP without analog pin and reflection light barrier support (test) + + +#define MAX_APIN 8 +#define MAX_PIN 8 + +/* ESP8266 pins that are typically ok to use + * (some might be set to -1 (disallowed) because they are used + * as reset, serial, led or other things on most boards) + * maps printed pin numbers to sketch internal index numbers */ +short allowedPins[MAX_APIN] = + { 0, 1, 2, -1, // printed pin numbers 0,1,2 are ok to be used, 3 not + -1, 5, 6, 7}; // printed pin numbers 5-7 are ok to be used, 4 not, >8 not + +/* Wemos / NodeMCU Pins 3,4 and 8 (GPIO 0,2 and 15) define boot mode and therefore + * can not be used to connect to signal */ + +/* Map from sketch internal pin index to real chip IO pin number (not aPin, e.g. for ESP) + Note that the internal numbers might be different from the printed + pin numbers (e.g. pin 0 is in index 0 but real chip pin number 16! */ +short internalPins[MAX_PIN] = + { D0, D1, D2, D3, // printed pin numbers 0, 1, 2, 3 (3 should not be used and could be removed here) + D5, D5, D6, D7}; // printed pin numbers 4, 5, 6, 7 (4 should not be used and could be removed here) + // D0=16, D1=5, D2=4, D5=14, A0=17, ... + +#endif // end of ESP section without analog reading + + + + +#else // Arduino Uno or Nano variables and definitions + // ============================================= + +#define MAX_HIST 20 // 20 history entries for arduino boards + +/* arduino pins that are typically ok to use + * (some might be set to -1 (disallowed) because they are used + * as reset, serial, led or other things on most boards) + * maps printed pin numbers to sketch internal index numbers */ + +#ifdef analogIR + +/* 2 is used for IR out, 12 for signal, A7 for In */ +#define MAX_APIN 22 +#define MAX_PIN 18 + +short allowedPins[MAX_APIN] = + {-1, -1, -1, 0, /* arduino pin 0 - 3 to internal index or -1 if pin is reserved */ + 1, 2, 3, 4, /* arduino pin 4 - 7 to internal index */ + 5, 6, 7, 8, /* arduino pin 8 - 11 to internal index */ + -1, 9, 10, 11, /* arduino pin 12, 13, A0, A1 / 14, 15 to internal index or -1 if pin is reserved*/ + 12, 13, 14, 15, /* arduino pin A2 - A5 / 16 - 19 to internal index */ + 16, 17 }; /* arduino pin A6, A7 to internal index */ + +/* Map from sketch internal pin index to real chip IO pin number */ +short internalPins[MAX_PIN] = + { 3, 4, 5, 6, /* index 0 - 3 map to pins 3 - 6 */ + 7, 8, 9, 10, /* index 4 - 7 map to pins 7 - 10 */ + 11, 12, 14, 15, /* index 8 - 11 map to pins 11,13, A0 - A1 */ + 16, 17, 18, 19, /* index 12 - 15 map to pins A2 - A5 */ + 20, 21 }; /* index 16 - 17 map to pin A6, A7 */ + +uint8_t analogPins[MAX_PIN] = + { 0,0,0,0, /* everything except A7 is digital by default */ + 0,0,0,0, + 0,0,0,0, + 0,0,0,0, + 0,1 }; + +const int analogInPin = A7; // Analog input pin that the photo transistor is attached to +const int irOutPin = 2; // Digital output pin that the IR-LED is attached to +const int ledOutPin = 12; // Signal LED output pin + + +#else +/* no analog IR support -> all Nano pins including analog available für digital counting */ + +#define MAX_APIN 22 +#define MAX_PIN 20 +short allowedPins[MAX_APIN] = + {-1, -1, 0, 1, /* arduino pin 0 - 3 to internal Pin index or -1 if pin is reserved */ + 2, 3, 4, 5, /* arduino pin 4 - 7 to internal Pin index or -1 if pin is reserved */ + 6, 7, 8, 9, /* arduino pin 8 - 11 to internal Pin index or -1 if pin is reserved */ + 10, 11, 12, 13, /* arduino pin 12, 13, A0, A1 to internal Pin index or -1 if pin is reserved */ + 14, 15, 16, 17, /* arduino pin A2 - A5 / 16 - 19 to internal Pin index or -1 if pin is reserved */ + 18, 19 }; /* arduino pin A6, A7 to internal Pin index or -1 if pin is reserved */ + +/* Map from sketch internal pin index to real chip IO pin number */ +short internalPins[MAX_PIN] = + { 2, 3, 4, 5, /* index 0 - 3 map to pins 2 - 5 */ + 6, 7, 8, 9, /* index 4 - 7 map to pins 6 - 9 */ + 10, 11, 12, 13, /* index 8 - 11 map to pins 10 - 13 */ + 14, 15, 16, 17, /* index 12 - 15 map to pins A0 - A3 */ + 18, 19, 20, 21 }; /* index 16 - 19 map to pins A4 - A7 */ + +uint8_t analogPins[MAX_PIN] = + { 0,0,0,0, /* everything is digital by default */ + 0,0,0,0, + 0,0,0,0, + 0,0,0,0, + 0,0,0,0 }; + +#endif + +/* first and last pin at port PB, PC and PD for arduino uno/nano */ +uint8_t firstPin[] = {8, 14, 0}; // aPin -> allowedPins[] -> pinIndex +uint8_t lastPin[] = {13, 19, 7}; + +/* Pin change mask for each chip port on the arduino platform */ +volatile uint8_t *port_to_pcmask[] = { + &PCMSK0, + &PCMSK1, + &PCMSK2 +}; + +/* last PIN States at io port to detect individual pin changes in arduino ISR */ +volatile static uint8_t PCintLast[3]; + +#endif + + +Print *Output; // Pointer to output device (Serial / TCP connection with ESP8266) +uint32_t bootTime; +uint16_t bootWraps; // counter for millis wraps at last reset +uint16_t millisWraps; // counter to track when millis counter wraps +uint32_t lastMillis; // milis at last main loop iteration +uint8_t devVerbose; // >=10 shows pin changes, >=5 shows pin history + +#ifdef debugPins +uint8_t lastState[MAX_PIN]; // for debug output when a pin state changes +#endif + +uint32_t intervalMin = 30000; // default 30 sec - report after this time if nothing else delays it +uint32_t intervalMax = 60000; // default 60 sec - report after this time if it didin't happen before +uint32_t intervalSml = 2000; // default 2 secs - continue count if timeDiff is less and intervalMax not over +uint16_t countMin = 2; // continue counting if count is less than this and intervalMax not over + +uint32_t timeNextReport; +#ifdef ESP8266 +uint32_t expectK; +#endif + +/* index to the following arrays is the internal pin index number */ + +volatile boolean initialized[MAX_PIN]; // did we get first interrupt yet? +short activePin[MAX_PIN]; // printed arduino pin number for index if active - otherwise -1 +uint16_t pulseWidthMin[MAX_PIN]; // minimal pulse length in millis for filtering +uint8_t pulseLevel[MAX_PIN]; // start of pulse for measuring length - 0 / 1 as defined for each pin +uint8_t pullup[MAX_PIN]; // pullup configuration state + +volatile uint32_t counter[MAX_PIN]; // real pulse counter +volatile uint8_t counterIgn[MAX_PIN]; // ignored first pulse after init +volatile uint16_t rejectCounter[MAX_PIN]; // counter for rejected pulses that are shorter than pulseWidthMin +uint32_t lastCount[MAX_PIN]; // counter at last report (to get the delta count) +uint16_t lastRejCount[MAX_PIN]; // reject counter at last report (to get the delta count) + +volatile uint32_t lastChange[MAX_PIN]; // millis at last level change (for measuring pulse length) +volatile uint8_t lastLevel[MAX_PIN]; // level of input at last interrupt +volatile uint8_t lastLongLevel[MAX_PIN]; // last level that was longer than pulseWidthMin + +volatile uint32_t pulseWidthSum[MAX_PIN]; // sum of pulse lengths for average calculation +uint8_t reportSequence[MAX_PIN]; // sequence number for reports + + +#ifdef pulseHistory +volatile uint8_t histIndex; // pointer to next entry in history ring +volatile uint16_t histNextSeq; // next seq number to use +volatile uint16_t histSeq[MAX_HIST]; // history sequence number +volatile uint8_t histPin[MAX_HIST]; // pin for this entry +volatile uint8_t histLevel[MAX_HIST]; // level for this entry +volatile uint32_t histTime[MAX_HIST]; // time for this entry +volatile uint32_t histLen[MAX_HIST]; // time that this level was held +volatile char histAct[MAX_HIST]; // action (count, reject, ...) as one char +#endif + +volatile uint32_t intervalStart[MAX_PIN]; // start of an interval - typically set by first / last pulse +volatile uint32_t intervalEnd[MAX_PIN]; // end of an interval - typically set by first / last pulse +uint32_t lastReport[MAX_PIN]; // millis at last report to find out when maxInterval is over + +uint16_t commandData[MAX_INPUT_NUM]; // input data over serial port or network +uint8_t commandDataPointer = 0; // index pointer to next input value +uint16_t value; // the current value for input function + + +/* + do counting and set start / end time of interval. + reporting is not triggered from here. + + only here counter[] is modified + intervalEnd[] is set here and in report + intervalStart[] is set in case a pin was not initialized yet and in report +*/ +static void inline doCount(uint8_t pinIndex, uint8_t level, uint32_t now) { + uint32_t len = now - lastChange[pinIndex]; + char act = ' '; + +#ifdef pulseHistory + histIndex++; + if (histIndex >= MAX_HIST) histIndex = 0; + histSeq[histIndex] = histNextSeq++; + histPin[histIndex] = pinIndex; + histTime[histIndex] = lastChange[pinIndex]; + histLen[histIndex] = len; + histLevel[histIndex] = lastLevel[pinIndex]; +#endif + if (len < pulseWidthMin[pinIndex]) { // pulse was too short + lastChange[pinIndex] = now; + if (lastLevel[pinIndex] == pulseLevel[pinIndex]) { // if change to gap level + rejectCounter[pinIndex]++; // inc reject counter and set action to R (pulse too short) + act = 'R'; + } else { + act = 'X'; // set action to X (gap too short) + } + } else { + if (lastLevel[pinIndex] != pulseLevel[pinIndex]) { // edge does fit defined pulse start, level is now pulse, before it was gap + act = 'G'; // now the gap is confirmed (even if inbetween was a spike that we ignored) + } else { // edge is a change to gap, level is now gap + if (lastLongLevel[pinIndex] != pulseLevel[pinIndex]) { // last remembered valid level was also gap -> now we had valid new pulse -> count + counter[pinIndex]++; // count + intervalEnd[pinIndex] = now; // remember time of in case pulse will be the last in the interval + if (!initialized[pinIndex]) { + intervalStart[pinIndex] = now; // if this is the very first impulse on this pin -> start interval now + initialized[pinIndex] = true; // and start counting the next impulse (so far counter is 0) + counterIgn[pinIndex]++; // count as to be ignored for diff because it defines the start of the interval + } + pulseWidthSum[pinIndex] += len; // for average calculation + act = 'C'; + } else { // last remembered valid level was a pulse -> now we had another valid pulse + pulseWidthSum[pinIndex] += len; // for average calculation + act = 'P'; // pulse was already counted, only short drop inbetween + } + } + lastLongLevel[pinIndex] = lastLevel[pinIndex]; // remember this valid level as lastLongLevel + } +#ifdef pulseHistory + histAct[histIndex] = act; +#endif + lastChange[pinIndex] = now; + lastLevel[pinIndex] = level; +} + + +/* Interrupt handlers and their installation + * on Arduino and ESP8266 platforms + */ + +#ifndef ESP8266 +/* Add a pin to be handled (Arduino code) */ +uint8_t AddPinChangeInterrupt(uint8_t rPin) { + volatile uint8_t *pcmask; // pointer to PCMSK0 or 1 or 2 depending on the port corresponding to the pin + uint8_t bitM = digitalPinToBitMask(rPin); // mask to bit in PCMSK to enable pin change interrupt for this arduino pin + uint8_t port = digitalPinToPort(rPin); // port that this arduno pin belongs to for enabling interrupts + if (port == NOT_A_PORT) + return 0; + port -= 2; // from port (PB, PC, PD) to index in our array + pcmask = port_to_pcmask[port]; // point to PCMSK0 or 1 or 2 depending on the port corresponding to the pin + *pcmask |= bitM; // set the pin change interrupt mask through a pointer to PCMSK0 or 1 or 2 + PCICR |= 0x01 << port; // enable the interrupt + return 1; +} + + +/* Remove a pin to be handled (Arduino code) */ +uint8_t RemovePinChangeInterrupt(uint8_t rPin) { + volatile uint8_t *pcmask; + uint8_t bitM = digitalPinToBitMask(rPin); + uint8_t port = digitalPinToPort(rPin); + if (port == NOT_A_PORT) + return 0; + port -= 2; // from port (PB, PC, PD) to index in our array + pcmask = port_to_pcmask[port]; + *pcmask &= ~bitM; // clear the bit in the mask. + if (*pcmask == 0) { // if that's the last one, disable the interrupt. + PCICR &= ~(0x01 << port); + } + return 1; +} + + +// now set the arduino interrupt service routines and call the common handler with the port index number +ISR(PCINT0_vect) { + PCint(0); +} +ISR(PCINT1_vect) { + PCint(1); +} +ISR(PCINT2_vect) { + PCint(2); +} + +/* + common function for arduino pin change interrupt handlers. "port" is the PCINT port index (0-2) as passed from above, not PB, PC or PD which are mapped to 2-4 +*/ +static void PCint(uint8_t port) { + uint8_t bit; + uint8_t curr; + uint8_t delta; + short pinIndex; + uint32_t now = millis(); + + // get the pin states for the indicated port. + curr = *portInputRegister(port+2); // current pin states at port (add 2 to get from index to PB, PC or PD) + delta = (curr ^ PCintLast[port]) & *port_to_pcmask[port]; // xor gets bits that are different and & screens out non pcint pins + PCintLast[port] = curr; // store new pin state for next interrupt + + if (delta == 0) return; // no handled pin changed + + bit = 0x01; // start mit rightmost (least significant) bit in a port + for (uint8_t aPin = firstPin[port]; aPin <= lastPin[port]; aPin++) { // loop over each pin on the given port that changed + if (delta & bit) { // did this pin change? + pinIndex = allowedPins[aPin]; + if (pinIndex > 0) { // shound not be necessary but test anyway + doCount (pinIndex, ((curr & bit) > 0), now); // do the counting, history and so on + } + } + bit = bit << 1; // shift mask to go to next bit + } +} + + +#else +/* Add a pin to be handled (ESP8266 code) */ + +/* attachInterrupt needs to be given an individual function for each interrrupt . + * since we cant pass the pin value into the ISR or we need to use an + * internal function __attachInnterruptArg ... but then we need a fixed reference for the pin numbers ... +*/ +uint8_t AddPinChangeInterrupt(uint8_t rPin) { + switch(rPin) { + case 4: + attachInterrupt(digitalPinToInterrupt(rPin), ESPISR4, CHANGE); + break; + case 5: + attachInterrupt(digitalPinToInterrupt(rPin), ESPISR5, CHANGE); + break; + case 12: + attachInterrupt(digitalPinToInterrupt(rPin), ESPISR12, CHANGE); + break; + case 13: + attachInterrupt(digitalPinToInterrupt(rPin), ESPISR13, CHANGE); + break; + case 14: + attachInterrupt(digitalPinToInterrupt(rPin), ESPISR14, CHANGE); + break; + case 16: + attachInterrupt(digitalPinToInterrupt(rPin), ESPISR16, CHANGE); + break; + default: + PrintErrorMsg(); Output->println(F("attachInterrupt")); + } + return 1; +} + +void ESPISR4() { // ISR for real pin GPIO 4 / pinIndex 2 + doCount(2, digitalRead(4), millis()); +} + +void ESPISR5() { // ISR for real pin GPIO 5 / pinIndex 1 + doCount(1, digitalRead(5), millis()); +} + +void ESPISR12() { // ISR for real pin GPIO 12 / pinIndex 6 + doCount(6, digitalRead(12), millis()); +} + +void ESPISR13() { // ISR for real pin GPIO 13 / pinIndex 7 + doCount(7, digitalRead(13), millis()); +} + +void ESPISR14() {// ISR for real pin GPIO 14 / pinIndex 5 + doCount(5, digitalRead(14), millis()); +} + +void ESPISR16() { // ISR for real pin GPIO 16 / pinIndex 0 + doCount(0, digitalRead(16), millis()); +} +#endif + + +void PrintErrorMsg() { + uint8_t len = strlen_P(errorStr); + char myChar; + for (unsigned char k = 0; k < len; k++) { + myChar = pgm_read_byte_near(errorStr + k); + Output->print(myChar); + } +} + + +void printVersionMsg() { + uint8_t len = strlen_P(versionStr); + char myChar; + for (unsigned char k = 0; k < len; k++) { + myChar = pgm_read_byte_near(versionStr + k); + Output->print(myChar); + } + Output->print(F(" on ")); + len = strlen_P(boardName); + for (unsigned char k = 0; k < len; k++) { + myChar = pgm_read_byte_near(boardName + k); + Output->print(myChar); + } + +#ifdef ARDUINO_BOARD + Output->print(F(" ")); + len = strlen_P(boardName1); + for (unsigned char k = 0; k < len; k++) { + myChar = pgm_read_byte_near(boardName1 + k); + Output->print(myChar); + } +#endif + + Output->print(F(" compiled ")); + len = strlen_P(compile_date); + for (unsigned char k = 0; k < len; k++) { + myChar = pgm_read_byte_near(compile_date + k); + Output->print(myChar); + } +} + + +void showIntervals() { + Output->print(F("I")); + Output->print(intervalMin / 1000); + Output->print(F(" ")); + Output->print(intervalMax / 1000); + Output->print(F(" ")); + Output->print(intervalSml / 1000); + Output->print(F(" ")); + Output->println(countMin); +} + + +#ifdef analogIR +void showThresholds() { + Output->print(F("T")); + Output->print(analogThresholdMin); + Output->print(F(" ")); + Output->println(analogThresholdMax); +} +#endif + + +void showPinConfig(short pinIndex) { + Output->print(F("P")); + Output->print(activePin[pinIndex]); + switch (pulseLevel[pinIndex]) { + case 1: Output->print(F(" rising")); break; + case 0: Output->print(F(" falling")); break; + default: Output->print(F(" -")); break; + } + if (pullup[pinIndex]) + Output->print(F(" pullup")); + Output->print(F(" min ")); + Output->print(pulseWidthMin[pinIndex]); +} + +#ifdef pulseHistory +void showPinHistory(short pinIndex, uint32_t now) { + uint8_t hi; + uint8_t start = (histIndex + 2) % MAX_HIST; + uint8_t count = 0; + uint32_t last; + boolean first = true; + + for (uint8_t i = 0; i < MAX_HIST; i++) { + hi = (start + i) % MAX_HIST; + if (histPin[hi] == pinIndex) + if (first || (last <= histTime[hi]+histLen[hi])) count++; + } + if (!count) return; + + Output->print (F("H")); // start with H + Output->print (activePin[pinIndex]); // printed pin number + Output->print (F(" ")); + for (uint8_t i = 0; i < MAX_HIST; i++) { + hi = (start + i) % MAX_HIST; + if (histPin[hi] == pinIndex) { + if (first || (last <= histTime[hi]+histLen[hi])) { + if (!first) Output->print (F(", ")); + Output->print (histSeq[hi]); // sequence + Output->print (F("s")); + Output->print ((long) (histTime[hi] - now)); // time when level started + Output->print (F("/")); + Output->print (histLen[hi]); // length + Output->print (F("@")); + Output->print (histLevel[hi]); // level (0/1) + Output->print (histAct[hi]); // action + first = false; + } + last = histTime[hi]; + } + } + Output->println(); +} +#endif + +/* + lastCount[] is only modified here (count at time of last reporting) + intervalEnd[] is modified here and in ISR - disable interrupts in critcal moments to avoid garbage in var + intervalStart[] is modified only here or for very first Interrupt in ISR +*/ +void showPinCounter(short pinIndex, boolean showOnly, uint32_t now) { + uint32_t count, countDiff, realDiff; + uint32_t startT, endT, timeDiff, widthSum; + uint16_t rejCount, rejDiff; + uint8_t countIgn; + + noInterrupts(); // copy counters while they cant be changed in isr + startT = intervalStart[pinIndex]; // start of interval (typically first pulse) + endT = intervalEnd[pinIndex]; // end of interval (last unless not enough) + count = counter[pinIndex]; // get current counter (counts all pulses + rejCount = rejectCounter[pinIndex]; + countIgn = counterIgn[pinIndex]; // pulses that mark the beginning of an interval + widthSum = pulseWidthSum[pinIndex]; + interrupts(); + + timeDiff = endT - startT; // time between first and last impulse + realDiff = count - lastCount[pinIndex]; // pulses during intervall + countDiff = realDiff - countIgn; // ignore forst pulse after device restart + rejDiff = rejCount - lastRejCount[pinIndex]; + + if (!showOnly) { // real reporting sets the interval borders new + if((long)(now - (lastReport[pinIndex] + intervalMax)) >= 0) { + // intervalMax is over + if ((countDiff >= countMin) && (timeDiff > intervalSml) && (intervalMin != intervalMax)) { + // normal procedure + noInterrupts(); // vars could be modified in ISR as well + intervalStart[pinIndex] = endT; // time of last impulse becomes first in next + interrupts(); + } else { + // nothing counted or counts happened during a fraction of intervalMin only + noInterrupts(); // vars could be modified in ISR as well + intervalStart[pinIndex] = now; // start a new interval for next report now + intervalEnd[pinIndex] = now; // no last impulse, use now instead + interrupts(); + timeDiff = now - startT; // special handling - calculation ends now + } + } else if( ((long)(now - (lastReport[pinIndex] + intervalMin)) >= 0) + && (countDiff >= countMin) && (timeDiff > intervalSml)) { + // minInterval has elapsed and other conditions are ok + noInterrupts(); // vars could be modified in ISR as well + intervalStart[pinIndex] = endT; // time of last also time of first in next + interrupts(); + } else { + return; // intervalMin and Max not over - dont report yet + } + noInterrupts(); + counterIgn[pinIndex] = 0; + pulseWidthSum[pinIndex] = 0; + interrupts(); + lastCount[pinIndex] = count; // remember current count for next interval + lastRejCount[pinIndex] = rejCount; + lastReport[pinIndex] = now; // remember when we reported +#ifdef ESP8266 + delayedTcpReports = 0; +#endif + reportSequence[pinIndex]++; + } + Output->print(F("R")); // R Report + Output->print(activePin[pinIndex]); + Output->print(F(" C")); // C - Count + Output->print(count); + Output->print(F(" D")); // D - Count Diff (without pulse that marks the begin) + Output->print(countDiff); + Output->print(F("/")); // R - real Diff for long counter - includes first after restart + Output->print(realDiff); + Output->print(F(" T")); // T - Time + Output->print(timeDiff); + Output->print(F(" N")); // N - now + Output->print((long)now); + Output->print(F(",")); + Output->print(millisWraps); + Output->print(F(" X")); // X Reject + Output->print(rejDiff); + + if (!showOnly) { + Output->print(F(" S")); // S - Sequence number + Output->print(reportSequence[pinIndex]); + } + if (countDiff > 0) { + Output->print(F(" A")); + Output->print(widthSum / countDiff); + } + Output->println(); +#ifdef ESP8266 + if (tcpMode && !showOnly) { + Serial.print(F("D reported pin ")); + Serial.print(activePin[pinIndex]); + Serial.print(F(" sequence ")); + Serial.print(reportSequence[pinIndex]); + Serial.println(F(" over tcp ")); + } +#endif + +} + + +/* + report count and time for pins that are between min and max interval +*/ + +boolean reportDue() { + uint32_t now = millis(); + boolean doReport = false; // check if report needs to be called + if((long)(now - timeNextReport) >= 0) // works fine when millis wraps. + doReport = true; // intervalMin is over + else + for (uint8_t pinIndex=0; pinIndex < MAX_PIN; pinIndex++) + if (activePin[pinIndex] >= 0) + if((long)(now - (lastReport[pinIndex] + intervalMax)) >= 0) + doReport = true; // active pin has not been reported for langer than intervalMax + return doReport; +} + + + +void report() { + uint32_t now = millis(); +#ifdef ESP8266 + if (tcpMode && !Client1Connected && (delayedTcpReports < 3)) { + if(delayedTcpReports == 0 || ((long)(now - (lastDelayedTcpReports + (1 * 30 * 1000))) > 0)) { + Serial.print(F("D report called but tcp is disconnected - delaying (")); + Serial.print(delayedTcpReports); + Serial.print(F(")")); + Serial.print(F(" now ")); + Serial.print(now); + Serial.print(F(" last ")); + Serial.print(lastDelayedTcpReports); + Serial.print(F(" diff ")); + Serial.println(now - lastDelayedTcpReports); + delayedTcpReports++; + lastDelayedTcpReports = now; + return; + } else return; + } +#endif + + for (uint8_t pinIndex=0; pinIndex < MAX_PIN; pinIndex++) { // go through all observed pins as pinIndex + if (activePin[pinIndex] >= 0) { + showPinCounter (pinIndex, false, now); // report pin counters if necessary +#ifdef pulseHistory + if (devVerbose >= 5) + showPinHistory(pinIndex, now); // show pin history if verbose >= 5 +#endif + } + } + timeNextReport = now + intervalMin; // check again after intervalMin or if intervalMax is over for a pin +} + + +/* give status report in between if requested over serial input */ +void showCmd() { + uint32_t now = millis(); + Output->print(F("M Status: ")); + printVersionMsg(); + Output->println(); + + showIntervals(); +#ifdef analogIR + showThresholds(); +#endif + Output->print(F("V")); + Output->println(devVerbose); + + for (uint8_t pinIndex=0; pinIndex < MAX_PIN; pinIndex++) { + if (activePin[pinIndex] >= 0) { + showPinConfig(pinIndex); + Output->print(F(", ")); + showPinCounter(pinIndex, true, now); +#ifdef pulseHistory + showPinHistory(pinIndex, now); +#endif + } + } + readFromEEPROM(); + Output->print(F("M Next report in ")); + Output->print(timeNextReport - millis()); + Output->print(F(" milliseconds")); + Output->println(); + //Output->println(F("M #end#")); +} + + +void helloCmd() { + uint32_t now = millis(); + Output->println(); + printVersionMsg(); + Output->print(F(" Hello, pins ")); + boolean first = true; + for (uint8_t aPin=0; aPin < MAX_APIN; aPin++) { + if (allowedPins[aPin] >= 0) { + if (!first) { + Output->print(F(",")); + } else { + first = false; + } + Output->print(aPin); + } + } + Output->print(F(" available")); + Output->print(F(" T")); + Output->print(now); + Output->print(F(",")); + Output->print(millisWraps); + Output->print(F(" B")); + Output->print(bootTime); + Output->print(F(",")); + Output->print(bootWraps); + + Output->println(); + showIntervals(); +#ifdef analogIR + showThresholds(); +#endif + for (uint8_t pinIndex=0; pinIndex < MAX_PIN; pinIndex++) { // go through all observed pins as pinIndex + if (activePin[pinIndex] >= 0) { + showPinConfig(pinIndex); + Output->println(); + } + } +} + + + +/* + handle add command. +*/ +void addCmd(uint16_t *values, uint8_t size) { + uint16_t pulseWidth; + uint32_t now = millis(); + + uint8_t aPin = values[0]; // values[0] is pin number + if (aPin >= MAX_APIN || allowedPins[aPin] < 0) { + PrintErrorMsg(); + Output->print(F("Illegal pin specification ")); + Output->println(aPin); + return; + }; + uint8_t pinIndex = allowedPins[aPin]; + uint8_t rPin = internalPins[pinIndex]; + + if (activePin[pinIndex] != aPin) { // in case this pin is not already active counting + #ifndef ESP8266 + uint8_t port = digitalPinToPort(rPin) - 2; + PCintLast[port] = *portInputRegister(port+2); + #endif + initPinVars(pinIndex, now); + activePin[pinIndex] = aPin; // save arduino pin number and flag this pin as active for reporting + } + + if (values[1] < 2 || values[1] > 3) { // values[1] is level (3->rising / 2->falling) + PrintErrorMsg(); + Output->print(F("Illegal pulse level specification for pin ")); + Output->println(aPin); + } + pulseLevel[pinIndex] = (values[1] == 3); // 2 = falling -> pulseLevel 0, 3 = rising -> pulseLevel 1 + +#ifdef analogIR + if (size > 2 && values[2] && !analogPins[pinIndex]) { +#else + if (size > 2 && values[2]) { +#endif + pinMode (rPin, INPUT_PULLUP); // values[2] is pullup flag + pullup[pinIndex] = 1; + } else { + pinMode (rPin, INPUT); + pullup[pinIndex] = 0; + } + + if (size > 3 && values[3] > 0) { // value 3 is min length + pulseWidth = values[3]; + } else { + pulseWidth = 2; + } + + /* todo: add upper and lower limits for analog pins as option here and in Fhem module */ + + pulseWidthMin[pinIndex] = pulseWidth; + +#ifdef analogIR + if (!analogPins[pinIndex]) { +#endif + if (!AddPinChangeInterrupt(rPin)) { // add Pin Change Interrupt + PrintErrorMsg(); + Output->println(F("AddInt")); + return; + } +#ifdef analogIR + } +#endif + Output->print(F("M defined ")); + showPinConfig(pinIndex); + Output->println(); +} + + +/* + handle rem command. +*/ +void removeCmd(uint16_t *values, uint8_t size) { + uint8_t aPin = values[0]; + if (size < 1 || aPin >= MAX_APIN || allowedPins[aPin] < 0) { + PrintErrorMsg(); + Output->print(F("Illegal pin specification ")); + Output->println(aPin); + return; + }; + uint8_t pinIndex = allowedPins[aPin]; + +#ifdef analogIR + if (!analogPins[pinIndex]) { +#endif +#ifdef ESP8266 + detachInterrupt(digitalPinToInterrupt(internalPins[pinIndex])); +#else + if (!RemovePinChangeInterrupt(internalPins[pinIndex])) { + PrintErrorMsg(); Output->println(F("RemInt")); + return; + } +#endif +#ifdef analogIR + } +#endif + initPinVars(pinIndex, 0); + Output->print(F("M removed ")); + Output->println(aPin); +} + + + +void intervalCmd(uint16_t *values, uint8_t size) { + /*Serial.print(F("D int ptr is ")); + Serial.println(size);*/ + if (size < 4) { // i command always gets 4 values: min, max, sml, cntMin + PrintErrorMsg(); + Output->print(F("size")); + Output->println(); + return; + } + if (values[0] < 1 || values[0] > 3600) { + PrintErrorMsg(); Output->println(values[0]); + return; + } + intervalMin = (long)values[0] * 1000; + if (millis() + intervalMin < timeNextReport) + timeNextReport = millis() + intervalMin; + + if (values[1] < 1 || values[1] > 3600) { + PrintErrorMsg(); Output->println(values[1]); + return; + } + intervalMax = (long)values[1]* 1000; + + if (values[2] > 3600) { + PrintErrorMsg(); Output->println(values[2]); + return; + } + intervalSml = (long)values[2] * 1000; + + if (values[3] > 100) { + PrintErrorMsg(); Output->println(values[3]); + return; + } + countMin = values[3]; + + Output->print(F("M intervals set to ")); + Output->print(values[0]); + Output->print(F(" ")); + Output->print(values[1]); + Output->print(F(" ")); + Output->print(values[2]); + Output->print(F(" ")); + Output->print(values[3]); + Output->println(); +} + +#ifdef analogIR +void thresholdCmd(uint16_t *values, uint8_t size) { + /*Serial.print(F("D threshold size ")); + Serial.print(size); + Serial.print(F(" v0 ")); + Serial.print(values[0]); + Serial.print(F(" v1 ")); + Serial.print(values[1]); + Serial.println();*/ + + if (size < 2) { // t command gets 2 values: min, max + PrintErrorMsg(); + Output->print(F("size")); + Output->println(); + return; + } + if (values[0] < 1 || values[0] > 1023) { + PrintErrorMsg(); Output->println(values[0]); + return; + } + analogThresholdMin = (int)values[0]; + + if (values[1] < 1 || values[1] > 1023) { + PrintErrorMsg(); Output->println(values[1]); + return; + } + analogThresholdMax = (int)values[1]; + + Output->print(F("M analog thresholds set to ")); + Output->print(values[0]); + Output->print(F(" ")); + Output->print(values[1]); + Output->println(); +} +#endif + + +void keepAliveCmd(uint16_t *values, uint8_t size) { + if (values[0] == 1 && size > 0) { + Output->println(F("alive")); + } +#ifdef ESP8266 + if (values[0] == 1 && size > 0 && size < 3 && Client1.connected()) { + tcpMode = true; + if (size == 2) { + expectK = millis() + values[1] * 2500; + } else { + expectK = millis() + 600000; // 10 Minutes if nothing sent (should not happen) + } + } +#endif +} + + +#ifdef ESP8266 +void quitCmd() { + if (Client1.connected()) { + Client1.println(F("closing connection")); + Client1.stop(); + tcpMode = false; + Serial.println(F("M TCP connection closed")); + } else { + Serial.println(F("M TCP not connected")); + } +} +#endif + + + +void updateEEPROM(int &address, byte value) { + if( EEPROM.read(address) != value){ + EEPROM.write(address, value); + } + address++; +} + + +void updateEEPROMSlot(int &address, char cmd, int v1, int v2, int v3, int v4) { + updateEEPROM(address, cmd); // I / A + updateEEPROM(address, v1 & 0xff); + updateEEPROM(address, v1 >> 8); + updateEEPROM(address, v2 & 0xff); + updateEEPROM(address, v2 >> 8); + updateEEPROM(address, v3 & 0xff); + updateEEPROM(address, v3 >> 8); + updateEEPROM(address, v4 & 0xff); + updateEEPROM(address, v4 >> 8); +} + +/* todo: include analogPins as well as analog limits in save / restore */ + +void saveToEEPROMCmd() { + int address = 0; + uint8_t slots = 1; + updateEEPROM(address, 'C'); + updateEEPROM(address, 'f'); + updateEEPROM(address, 'g'); + for (uint8_t pinIndex=0; pinIndex < MAX_PIN; pinIndex++) + if (activePin[pinIndex] >= 0) slots ++; +#ifdef analogIR + slots ++; +#endif + updateEEPROM(address, slots); // number of defined pins + intervall definition + updateEEPROMSlot(address, 'I', (uint16_t)(intervalMin / 1000), (uint16_t)(intervalMax / 1000), + (uint16_t)(intervalSml / 1000), (uint16_t)countMin); +#ifdef analogIR + updateEEPROMSlot(address, 'T', (uint16_t)analogThresholdMin, (uint16_t)analogThresholdMax, 0, 0); +#endif + for (uint8_t pinIndex=0; pinIndex < MAX_PIN; pinIndex++) + if (activePin[pinIndex] >= 0) + updateEEPROMSlot(address, 'A', (uint16_t)activePin[pinIndex], (uint16_t)(pulseLevel[pinIndex] ? 3:2), + (uint16_t)pullup[pinIndex], (uint16_t)pulseWidthMin[pinIndex]); +#ifdef ESP8266 + EEPROM.commit(); +#endif + Serial.print(F("config saved, ")); + Serial.print(slots); + Serial.print(F(", ")); + Serial.println(address); +} + + +void readFromEEPROM() { + int address = 0; + uint16_t v1, v2, v3, v4; + char cmd; + if (EEPROM.read(address) != 'C' || EEPROM.read(address+1) != 'f' || EEPROM.read(address+2) != 'g') { + Output->println(F("M no config in EEPROM")); + return; + } + address = 3; + uint8_t slots = EEPROM.read(address++); + if (slots > MAX_PIN + 2) { + Output->println(F("M illegal config in EEPROM")); + return; + } + Output->println(); + Output->print(F("M EEPROM Config: ")); + Output->print((char) EEPROM.read(0)); + Output->print((char) EEPROM.read(1)); + Output->print((char) EEPROM.read(2)); + Output->print(F(" Slots: ")); + Output->print((int) EEPROM.read(3)); + Output->println(); + for (uint8_t slot=0; slot < slots; slot++) { + cmd = EEPROM.read(address); + v1 = EEPROM.read(address+1) + (((uint16_t)EEPROM.read(address+2)) << 8); + v2 = EEPROM.read(address+3) + (((uint16_t)EEPROM.read(address+4)) << 8); + v3 = EEPROM.read(address+5) + (((uint16_t)EEPROM.read(address+6)) << 8); + v4 = EEPROM.read(address+7) + (((uint16_t)EEPROM.read(address+8)) << 8); + address = address + 9; + Output->print(F("M Slot: ")); + Output->print(cmd); + Output->print(F(" ")); + Output->print(v1); + Output->print(F(",")); + Output->print(v2); + Output->print(F(",")); + Output->print(v3); + Output->print(F(",")); + Output->print(v4); + Output->println(); + } +} + + +void restoreFromEEPROM() { + int address = 0; + if (EEPROM.read(address) != 'C' || EEPROM.read(address+1) != 'f' || EEPROM.read(address+2) != 'g') { + Serial.println(F("M no config in EEPROM")); + return; + } + address = 3; + uint8_t slots = EEPROM.read(address++); + if (slots > MAX_PIN + 1 || slots < 1) { + Serial.println(F("M illegal config in EEPROM")); + return; + } + Serial.println(F("M restoring config from EEPROM")); + char cmd; + for (uint8_t slot=0; slot < slots; slot++) { + cmd = EEPROM.read(address); + commandData[0] = EEPROM.read(address+1) + (((uint16_t)EEPROM.read(address+2)) << 8); + commandData[1] = EEPROM.read(address+3) + (((uint16_t)EEPROM.read(address+4)) << 8); + commandData[2] = EEPROM.read(address+5) + (((uint16_t)EEPROM.read(address+6)) << 8); + commandData[3] = EEPROM.read(address+7) + (((uint16_t)EEPROM.read(address+8)) << 8); + address = address + 9; + commandDataPointer = 4; + if (cmd == 'I') intervalCmd(commandData, commandDataPointer); +#ifdef analogIR + if (cmd == 'T') thresholdCmd(commandData, commandDataPointer); +#endif + if (cmd == 'A') addCmd(commandData, commandDataPointer); + } + commandDataPointer = 0; + value = 0; + for (uint8_t i=0; i < MAX_INPUT_NUM; i++) + commandData[i] = 0; + +} + + +void handleInput(char c) { + if (c == ',') { // Komma input, last value is finished + if (commandDataPointer < (MAX_INPUT_NUM - 1)) { + commandData[commandDataPointer++] = value; + value = 0; + } + } + else if ('0' <= c && c <= '9') { // digit input + value = 10 * value + c - '0'; + } + else if ('a' <= c && c <= 'z') { // letter input is command + + if (devVerbose > 0) { + commandData[commandDataPointer] = value; + Serial.print(F("D got ")); + for (short v = 0; v <= commandDataPointer; v++) { + if (v > 0) Serial.print(F(",")); + Serial.print(commandData[v]); + } + Serial.print(c); + Serial.print(F(" size ")); + Serial.print(commandDataPointer+1); + Serial.println(); + } + + switch (c) { + case 'a': // add a pin + commandData[commandDataPointer] = value; + addCmd(commandData, commandDataPointer+1); + break; + case 'd': // delete a pin + commandData[commandDataPointer] = value; + removeCmd(commandData, commandDataPointer+1); + break; + case 'e': // save to EEPROM + saveToEEPROMCmd(); + break; + case 'f': // flash ota + // OTA flash from HTTP Server + break; + case 'h': // hello + helloCmd(); + break; + case 'i': // interval + commandData[commandDataPointer] = value; + intervalCmd(commandData, commandDataPointer+1); + break; + case 'k': // keep alive + commandData[commandDataPointer] = value; + keepAliveCmd(commandData, commandDataPointer+1); + break; +#ifdef ESP8266 + case 'q': // quit + quitCmd(); + break; +#endif + case 'r': // reset + initialize(); + break; + case 's': // show + showCmd(); + break; +#ifdef analogIR + case 't': // thresholds for analog pin + commandData[commandDataPointer] = value; + thresholdCmd(commandData, commandDataPointer+1); + break; +#endif + case 'v': // dev verbose + if (value < 255) { + devVerbose = value; + Output->print(F("M devVerbose set to ")); + Output->println(value); + } else { + Output->println(F("M illegal value passed for devVerbose")); + } + break; + default: + break; + } + commandDataPointer = 0; + value = 0; + for (uint8_t i=0; i < MAX_INPUT_NUM; i++) + commandData[i] = 0; + //Serial.println(F("D End of command")); + } +} + +#ifdef debugCfg +/* do sample config so we don't need to configure pins after each reboot */ +void debugSetup() { + commandData[0] = 10; + commandData[1] = 20; + commandData[2] = 3; + commandData[3] = 0; + commandDataPointer = 4; + intervalCmd(commandData, commandDataPointer); + + commandData[0] = 1; // pin 1 + commandData[1] = 2; // falling + commandData[2] = 1; // pullup + commandData[3] = 30; // min Length + commandDataPointer = 4; + addCmd(commandData, commandDataPointer); + + commandData[0] = 2; // pin 2 + addCmd(commandData, commandDataPointer); + +/* + commandData[0] = 5; // pin 5 + addCmd(commandData, commandDataPointer); + + commandData[0] = 6; // pin 6 + addCmd(commandData, commandDataPointer); +*/ +} +#endif + + +#ifdef debugPins +void debugPinChanges() { + for (uint8_t pinIndex=0; pinIndex < MAX_PIN; pinIndex++) { + short aPin = activePin[pinIndex]; + if (aPin > 0) { + uint8_t rPin = internalPins[pinIndex]; + uint8_t pinState = digitalRead(rPin); + + if (pinState != lastState[pinIndex]) { + lastState[pinIndex] = pinState; + Output->print(F("M pin ")); + Output->print(aPin); + Output->print(F(" ( internal ")); + Output->print(rPin); + Output->print(F(" )")); + Output->print(F(" changed to ")); + Output->print(pinState); +#ifdef pulseHistory + Output->print(F(" histIdx ")); + Output->print(histIndex); +#endif + Output->print(F(" count ")); + Output->print(counter[pinIndex]); + Output->print(F(" reject ")); + Output->print(rejectCounter[pinIndex]); + Output->println(); + } + } + } +} +#endif + + +#ifdef ESP8266 +void connectWiFi() { + Client1Connected = false; + Client2Connected = false; + + // Connect to WiFi network + WiFi.mode(WIFI_STA); + delay (1000); + if (WiFi.status() != WL_CONNECTED) { + Serial.print(F("M Connecting WiFi to ")); + Serial.println(ssid); + WiFi.begin(ssid, password); // authenticate + while (WiFi.status() != WL_CONNECTED) { + Serial.print(F("M Status is ")); + switch (WiFi.status()) { + case WL_CONNECT_FAILED: + Serial.println(F("Connect Failed")); + break; + case WL_CONNECTION_LOST: + Serial.println(F("Connection Lost")); + break; + case WL_DISCONNECTED: + Serial.println(F("Disconnected")); + break; + case WL_CONNECTED: + Serial.println(F("Connected")); + break; + default: + Serial.println(WiFi.status()); + } + delay(1000); + } + Serial.println(); + Serial.print(F("M WiFi connected to ")); + Serial.println(WiFi.SSID()); + } else { + Serial.print(F("M WiFi already connected to ")); + Serial.println(WiFi.SSID()); + } + + // Start the server + Server.begin(); + Serial.println(F("M Server started")); + + // Print the IP address + Serial.print(F("M Use this IP: ")); + Serial.println(WiFi.localIP()); +} + + +void handleConnections() { + IPAddress remote; + uint32_t now = millis(); + + if (Client1Connected) { + if((long)(now - expectK) >= 0) { + Serial.println(F("M no keepalive from Client - disconnecting")); + Client1.stop(); + } + } + if (Client1.available()) { + handleInput(Client1.read()); + //Serial.println(F("M new Input over TCP")); + } + if (Client1.connected()) { + Client2 = Server.available(); + if (Client2) { + Client2.println(F("connection already busy")); + remote = Client2.remoteIP(); + Client2.stop(); + Serial.print(F("M second connection from ")); + Serial.print(remote); + Serial.println(F(" rejected")); + } + } else { + if (Client1Connected) { // client used to be connected, now disconnected + Client1Connected = false; + Output = &Serial; + Serial.println(F("M connection to client lost")); + } + Client1 = Server.available(); + if (Client1) { // accepting new connection + remote = Client1.remoteIP(); + Serial.print(F("M new connection from ")); + Serial.print(remote); + Serial.println(F(" accepted")); + Client1Connected = true; + Output = &Client1; + expectK = now + 600000; // max 10 Minutes (to be checked on Fhem module side as well + helloCmd(); // say hello to client + } + } +} +#endif + + +void handleTime() { + uint32_t now = millis(); + if (now < lastMillis) millisWraps++; + lastMillis = now; +} + + +#ifdef analogIR +void detectTrigger(int val) { + uint8_t nextState = triggerState; + if (val > analogThresholdMax) { + nextState = 1; + } else if (val < analogThresholdMin) { + nextState = 0; + } + if (nextState != triggerState) { + triggerState = nextState; +#ifdef ledOutPin + digitalWrite(ledOutPin, triggerState); +#endif +#ifdef ESP8266 + short pinIndex = 4; // a0 +#else + short pinIndex = 17; // a7 +#endif + uint32_t now = millis(); + doCount (pinIndex, triggerState, now); // do the counting, history and so on + +#ifdef debugPins + if (devVerbose >= 10) { + short pinIndex = allowedPins[analogInPin]; + short rPin = internalPins[pinIndex]; + Output->print(F("M pin ")); + Output->print(analogInPin); + Output->print(F(" ( internal ")); + Output->print(rPin); + Output->print(F(" ) ")); + Output->print(F(" to ")); + Output->print(nextState); +#ifdef pulseHistory + Output->print(F(" histIdx ")); + Output->print(histIndex); +#endif + Output->print(F(" count ")); + Output->print(counter[pinIndex]); + Output->print(F(" reject ")); + Output->print(rejectCounter[pinIndex]); + Output->println(); + } +#endif + } +} +#endif + +void initPinVars(short pinIndex, uint32_t now) { + uint8_t level = 0; + activePin[pinIndex] = -1; // inactive (-1) + initialized[pinIndex] = false; // no pulse seen yet + pulseWidthMin[pinIndex] = 0; // min pulse length + counter[pinIndex] = 0; // counter to 0 + counterIgn[pinIndex] = 0; + lastCount[pinIndex] = 0; + rejectCounter[pinIndex] = 0; + lastRejCount[pinIndex] = 0; + intervalStart[pinIndex] = now; // time vars + intervalEnd[pinIndex] = now; + lastChange[pinIndex] = now; + lastReport[pinIndex] = now; + reportSequence[pinIndex] = 0; +#ifdef analogIR + if (!analogPins[pinIndex]) { + level = digitalRead(internalPins[pinIndex]); + } +#else + level = digitalRead(internalPins[pinIndex]); +#endif + lastLevel[pinIndex] = level; +#ifdef debugPins + lastState[pinIndex] = level; // for debug output +#endif + /* todo: add analogPins, upper and lower limits for analog */ +} + + +void initialize() { + uint32_t now = millis(); + for (uint8_t pinIndex=0; pinIndex < MAX_PIN; pinIndex++) { + initPinVars(pinIndex, now); + } + timeNextReport = now + intervalMin; // time for first output + devVerbose = 0; +#ifndef ESP8266 + for (uint8_t port=0; port <= 2; port++) { + PCintLast[port] = *portInputRegister(port+2); // current pin states at port for PCInt handler + } +#endif +#ifdef debugCfg + debugSetup(); +#endif + restoreFromEEPROM(); + bootTime = millis(); // with boot / reset time + bootWraps = millisWraps; +#ifdef ESP8266 + expectK = now + 600000; // max 10 Minutes (to be checked on Fhem module side as well +#endif +} + + +void setup() { + Serial.begin(SERIAL_SPEED); // initialize serial +#ifdef ESP8266 + EEPROM.begin(100); +#endif + delay (500); + interrupts(); + Serial.println(); + Output = &Serial; + millisWraps = 0; + lastMillis = millis(); + initialize(); +#ifdef analogIR + pinMode(irOutPin, OUTPUT); +#ifdef ledOutPin + pinMode(ledOutPin, OUTPUT); +#endif +#endif + helloCmd(); // started message to serial +#ifdef ESP8266 + connectWiFi(); +#endif +} + + +/* + Main Loop + checks if report should be called because timeNextReport is reached + or lastReport for one pin is older than intervalMax + timeNextReport is only set here (and when interval is changed / at setup) +*/ +void loop() { + handleTime(); + if (Serial.available()) { + handleInput(Serial.read()); + } +#ifdef ESP8266 + handleConnections(); +#endif + +#ifdef analogIR + short AIndex = allowedPins[analogInPin]; + if (AIndex >= 0 && activePin[AIndex] >= 0) { + digitalWrite(irOutPin, LOW); + // wait 10 milliseconds + delay(10); + // read the analog in value: + sensorValueOff = analogRead(analogInPin); + // turn IR LED on + digitalWrite(irOutPin, HIGH); + delay(10); + // read the analog in value: + sensorValueOn = analogRead(analogInPin); + detectTrigger (sensorValueOn - sensorValueOff); + if (devVerbose >= 20) { + Output->print(F("L")); + + Output->print(sensorValueOn); + Output->print(F(",")); + Output->print(sensorValueOff); + Output->print(F("->")); + + Output->println(sensorValueOn - sensorValueOff); + } + } +#endif + +#ifdef debugPins + if (devVerbose >= 10) { + debugPinChanges(); + } +#endif + + if (reportDue()) { + report(); + } +}