diff --git a/fhem/FHEM/98_ArduCounter.pm b/fhem/FHEM/98_ArduCounter.pm index 07e46ff31..b4230d98f 100755 --- a/fhem/FHEM/98_ArduCounter.pm +++ b/fhem/FHEM/98_ArduCounter.pm @@ -55,13 +55,27 @@ # 2018-01-01 little fixes # 2018-01-02 extend reporting line with history H.*, create new reading pinHistory if received from device and verboseReadings is set to 1 # create long count readings always, not only if attr verboseReadings is set to 1 -# 2018-01-03 little docu fix -# 2018-01-13 little docu addon +# 2018-01-03 little docu fix +# 2018-01-13 little docu addon +# 2018-02-04 modifications for ArduCounter on ESP8266 connected via TCP +# remove "change" as option (only rising and falling allowed now) +# TCP connection handling, keepalive, +# many changes more ... +# 2018-03-07 fix pinHistory when verboseReadings is not set +# 2018-03-08 parse board name in setup / hello message +# 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 # # ideas / todo: -# - parse error messages from sketch and show it in a message box? async output? +# - OTA Flashing for ESP +# +# - parse sequence num of history entries -> reconstruct long history list in perl mem +# and display with get history instead of readings incl. individual time +# # - timeMissed # +# package main; @@ -71,16 +85,21 @@ use warnings; use Time::HiRes qw(gettimeofday); my %ArduCounter_sets = ( - "raw" => "", - "reset" => "", - "flash" => "" + "disable" => "", + "enable" => "", + "raw" => "", + "reset" => "", + "flash" => "", + "devVerbose" => "", + "saveConfig" => "", + "reconnect" => "" ); my %ArduCounter_gets = ( - "info" => "" + "info" => "" ); -my $ArduCounter_Version = '5.7 - 2.1.2018'; +my $ArduCounter_Version = '5.94 - 13.5.2018'; # # FHEM module intitialisation @@ -113,7 +132,13 @@ sub ArduCounter_Initialize($) "verboseReadings[0-9]+ " . "flashCommand " . "helloSendDelay " . - "helloWaitTime " . + "helloWaitTime " . + "keepAliveDelay " . + "keepAliveTimeout " . + "nextOpenDelay " . + "silentReconnect " . + "openTimeout " . + "disable:0,1 " . "do_not_notify:1,0 " . $readingFnAttributes; @@ -134,51 +159,39 @@ sub ArduCounter_Define($$) my $name = $a[0]; my $dev = $a[2]; - if ($dev !~ /.+@([0-9]+)/) { - $dev .= '@38400'; + if ($dev =~ m/^(.+):([0-9]+)$/) { + # tcp conection + $hash->{TCP} = 1; } else { - Log3 $name, 3, "$name: Warning: connection speed $1 is not the default for the ArduCounter firmware" - if ($1 != 38400); + if ($dev !~ /.+@([0-9]+)/) { + $dev .= '@38400'; + } else { + Log3 $name, 3, "$name: Warning: connection speed $1 is not the default for the ArduCounter firmware" + if ($1 != 38400); + } } - $hash->{buffer} = ""; $hash->{DeviceName} = $dev; $hash->{VersionModule} = $ArduCounter_Version; $hash->{NOTIFYDEV} = "global"; # NotifyFn nur aufrufen wenn global events (INITIALIZED) $hash->{STATE} = "disconnected"; - delete $hash->{Initialized}; + delete $hash->{Initialized}; # device might not be initialized - wait for hello / setup before cmds if(!defined($attr{$name}{'flashCommand'})) { - #$attr{$name}{'flashCommand'} = 'avrdude -p atmega328P -b 57600 -c arduino -P [PORT] -D -U flash:w:[HEXFILE] 2>[LOGFILE]'; # for nano - $attr{$name}{'flashCommand'} = 'avrdude -p atmega328P -c arduino -P [PORT] -D -U flash:w:[HEXFILE] 2>[LOGFILE]'; # for uno + #$attr{$name}{'flashCommand'} = 'avrdude -p atmega328P -b 57600 -c arduino -P [PORT] -D -U flash:w:[HEXFILE] 2>[LOGFILE]'; # for nano + $attr{$name}{'flashCommand'} = 'avrdude -p atmega328P -c arduino -P [PORT] -D -U flash:w:[HEXFILE] 2>[LOGFILE]'; # for uno } - if ($init_done) { - ArduCounter_Open($hash); - } + Log3 $name, 5, "$name: defined with $dev, Module version $ArduCounter_Version"; + #if ($init_done) { + # ArduCounter_Open($hash); + #} + # do open in notify + return; } -# -# Send config commands after Board reported it is ready or still counting -########################################################################## -sub ArduCounter_ConfigureDevice($) -{ - my ($hash) = @_; - my $name = $hash->{NAME}; - - # send attributes to arduino device. Just call ArduCounter_Attr again - #Log3 $name, 3, "$name: sending configuration from attributes to device"; - while (my ($attr, $val) = each(%{$attr{$name}})) { - if ($attr =~ "pin|interval") { - Log3 $name, 3, "$name: ConfigureDevice calls Attr with $attr $val"; - ArduCounter_Attr("set", $name, $attr, $val); - } - } -} - - # # undefine command when device is deleted ######################################################################### @@ -189,21 +202,139 @@ sub ArduCounter_Undef($$) } +# remove timers, call DevIo_Disconnected +# to set state and add to readyFnList +##################################################### +sub ArduCounter_Disconnected($) +{ + my $hash = shift; + my $name = $hash->{NAME}; + + RemoveInternalTimer ("alive:$name"); # no timeout if waiting for keepalive response + RemoveInternalTimer ("keepAlive:$name"); # don't send keepalive messages anymore + RemoveInternalTimer ("sendHello:$name"); + DevIo_Disconnected($hash); # close, add to readyFnList so _Ready is called to reopen + delete $hash->{WaitForAlive}; +} + + +##################################### +sub ArduCounter_OpenCB($$) +{ + my ($hash, $msg) = @_; + my $name = $hash->{NAME}; + my $now = gettimeofday(); + if ($msg) { + Log3 $name, 5, "$name: Open callback: $msg" if ($msg); + } + delete $hash->{BUSY_OPENDEV}; + if ($hash->{FD}) { + Log3 $name, 5, "$name: ArduCounter_Open succeeded in callback"; + my $hdl = AttrVal($name, "helloSendDelay", 15); + # send hello if device doesn't say "Started" withing $hdl seconds + RemoveInternalTimer ("sendHello:$name"); + InternalTimer($now+$hdl, "ArduCounter_AskForHello", "sendHello:$name", 0); + + if ($hash->{TCP}) { + # send first keepalive immediately to turn on tcp mode in device + ArduCounter_KeepAlive("keepAlive:$name"); + } + } else { + #Log3 $name, 5, "$name: ArduCounter_Open failed - open callback called from DevIO without FD"; + } + + return; +} + + ######################################################## # Open Device -sub ArduCounter_Open($) +sub ArduCounter_Open($;$) +{ + my ($hash, $reopen) = @_; + my $name = $hash->{NAME}; + my $now = gettimeofday(); + $reopen = 0 if (!$reopen); + + if ($hash->{BUSY_OPENDEV}) { # still waiting for callback to last open + if ($hash->{LASTOPEN} && $now > $hash->{LASTOPEN} + (AttrVal($name, "openTimeout", 3) * 2) + && $now > $hash->{LASTOPEN} + 15) { + Log3 $name, 5, "$name: _Open - still waiting for open callback, timeout is over twice - this should never happen"; + Log3 $name, 5, "$name: _Open - stop waiting and reset the flag."; + $hash->{BUSY_OPENDEV} = 0; + } else { + Log3 $name, 5, "$name: _Open - still waiting for open callback"; + return; + } + } + + if (!$reopen) { # not called from _Ready + DevIo_CloseDev($hash); + delete $hash->{NEXT_OPEN}; + delete $hash->{DevIoJustClosed}; + } + + Log3 $name, 4, "$name: trying to open connection to $hash->{DeviceName}" if (!$reopen); + + $hash->{BUSY_OPENDEV} = 1; + $hash->{LASTOPEN} = $now; + $hash->{nextOpenDelay} = AttrVal($name, "nextOpenDelay", 60); + $hash->{devioLoglevel} = (AttrVal($name, "silentReconnect", 0) ? 4 : 3); + $hash->{TIMEOUT} = AttrVal($name, "openTimeout", 3); + $hash->{buffer} = ""; # clear Buffer for reception + + DevIo_OpenDev($hash, $reopen, 0, \&ArduCounter_OpenCB); + delete $hash->{TIMEOUT}; + if ($hash->{FD}) { + Log3 $name, 5, "$name: ArduCounter_Open succeeded immediatelay" if (!$reopen); + } else { + Log3 $name, 5, "$name: ArduCounter_Open waiting for callback" if (!$reopen); + } + +} + + +######################################################################### +sub ArduCounter_Ready($) { my ($hash) = @_; my $name = $hash->{NAME}; - - DevIo_OpenDev($hash, 0, 0); - if ($hash->{FD}) { - my $now = gettimeofday(); - my $hdl = AttrVal($name, "helloSendDelay", 3); - # send hello if device doesn't say "Started" withing $hdl seconds - RemoveInternalTimer ("sendHello:$name"); - InternalTimer($now+$hdl, "ArduCounter_SendHello", "sendHello:$name", 0); + + if($hash->{STATE} eq "disconnected") { + RemoveInternalTimer ("alive:$name"); # no timeout if waiting for keepalive response + RemoveInternalTimer ("keepAlive:$name"); # don't send keepalive messages anymore + delete $hash->{WaitForAlive}; + delete $hash->{Initialized}; # when reconnecting wait for setup / hello before further action + if (IsDisabled($name)) { + Log3 $name, 3, "$name: _Ready: $name is disabled - don't try to reconnect"; + DevIo_CloseDev($hash); # close, remove from readyfnlist so _ready is not called again + return; + } + ArduCounter_Open($hash, 1); # reopen, don't call DevIoClose before reopening + return; # a return value triggers direct read for win } + # This is relevant for windows/USB only + my $po = $hash->{USBDev}; + if ($po) { + my ($BlockingFlags, $InBytes, $OutBytes, $ErrorFlags) = $po->status; + return ($InBytes>0); # tell fhem.pl to read when we return + } + return; +} + + +####################################### +# Aufruf aus InternalTimer +sub ArduCounter_DelayedOpen($) +{ + my $param = shift; + my (undef,$name) = split(':',$param); + my $hash = $defs{$name}; + + Log3 $name, 4, "$name: try to reopen connection after delay"; + RemoveInternalTimer ("delayedopen:$name"); + delete $hash->{DevIoJustClosed}; # otherwise open returns without doing anything this time and we are not on the readyFnList ... + ArduCounter_Open($hash, 1); # reopen } @@ -221,14 +352,14 @@ sub ArduCounter_Notify($$) my $name = $hash->{NAME}; # Log3 $name, 5, "$name: Notify called for source $source->{NAME} with events: @{$events}"; - return if (!grep(m/^INITIALIZED|REREADCFG|(MODIFIED $name)$/, @{$source->{CHANGED}})); + return if (!grep(m/^INITIALIZED|REREADCFG|(MODIFIED $name)|(DEFINED $name)$/, @{$source->{CHANGED}})); - if (AttrVal($name, "disable", undef)) { - Log3 $name, 4, "$name: device is disabled - don't set timer to send hello"; + if (IsDisabled($name)) { + Log3 $name, 3, "$name: Notify / Init: device is disabled"; return; } - Log3 $name, 5, "$name: Notify called with events: @{$events}, open device and set timer to send hello to device"; + Log3 $name, 3, "$name: Notify called with events: @{$events}, open device and set timer to send hello to device"; ArduCounter_Open($hash); } @@ -240,23 +371,25 @@ sub ArduCounter_Write ($$) my ($hash, $line) = @_; my $name = $hash->{NAME}; if ($hash->{STATE} eq "disconnected" || !$hash->{FD}) { - Log3 $name, 4, "$name: Write: device is disconnected, dropping line to write"; + Log3 $name, 5, "$name: Write: device is disconnected, dropping line to write"; return 0; } - if (AttrVal($name, "disable", undef)) { - Log3 $name, 4, "$name: Write called but device is disabled, dropping line to send"; + if (IsDisabled($name)) { + Log3 $name, 5, "$name: Write called but device is disabled, dropping line to send"; return 0; } - Log3 $name, 4, "$name: Write: $line"; - DevIo_SimpleWrite( $hash, "$line\n", 2); + #Log3 $name, 5, "$name: Write: $line"; # devio will already log the write + #DevIo_SimpleWrite($hash, "\n", 2); + DevIo_SimpleWrite($hash, "$line.", 2); return 1; } ####################################### # Aufruf aus InternalTimer -# send "h" to ask for "Hello" since device didn't say "Started" so fae - maybe it's still counting ... -sub ArduCounter_SendHello($) +# 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 +sub ArduCounter_AskForHello($) { my $param = shift; my (undef,$name) = split(':',$param); @@ -266,7 +399,7 @@ sub ArduCounter_SendHello($) return if (!ArduCounter_Write( $hash, "h")); my $now = gettimeofday(); - my $hwt = AttrVal($name, "helloWaitTime ", 3); + my $hwt = AttrVal($name, "helloWaitTime", 3); RemoveInternalTimer ("hwait:$name"); InternalTimer($now+$hwt, "ArduCounter_HelloTimeout", "hwait:$name", 0); $hash->{WaitForHello} = 1; @@ -282,6 +415,127 @@ sub ArduCounter_HelloTimeout($) my $hash = $defs{$name}; Log3 $name, 3, "$name: device didn't reply to h(ello). Is the right sketch flashed? Is speed set to 38400?"; delete $hash->{WaitForHello}; + RemoveInternalTimer ("hwait:$name"); +} + + +############################################ +# Aufruf aus Open / Ready und InternalTimer +# send "1k" to ask for "alive" +sub ArduCounter_KeepAlive($) +{ + my $param = shift; + my (undef,$name) = split(':',$param); + my $hash = $defs{$name}; + my $now = gettimeofday(); + + if (IsDisabled($name)) { + return; + } + + my $kdl = AttrVal($name, "keepAliveDelay", 10); # next keepalive as timer + my $kto = AttrVal($name, "keepAliveTimeout", 2); # timeout waiting for response + + Log3 $name, 5, "$name: sending k(eepAlive) to device"; + ArduCounter_Write( $hash, "1,${kdl}k"); + + RemoveInternalTimer ("alive:$name"); + InternalTimer($now+$kto, "ArduCounter_AliveTimeout", "alive:$name", 0); + $hash->{WaitForAlive} = 1; + + if ($hash->{TCP}) { + RemoveInternalTimer ("keepAlive:$name"); + InternalTimer($now+$kdl, "ArduCounter_KeepAlive", "keepAlive:$name", 0); # next keepalive + } +} + + +####################################### +# Aufruf aus InternalTimer +sub ArduCounter_AliveTimeout($) +{ + my $param = shift; + my (undef,$name) = split(':',$param); + my $hash = $defs{$name}; + Log3 $name, 3, "$name: device didn't reply to k(eeepAlive), setting to disconnected and try to reopen"; + delete $hash->{WaitForAlive}; + + $hash->{KeepAliveRetries} = 0 if (!$hash->{KeepAliveRetries}); + + if (++$hash->{KeepAliveRetries} > AttrVal($name, "keepAliveRetries", 1)) { + Log3 $name, 3, "$name: no retries left, setting device to disconnected"; + ArduCounter_Disconnected($hash); # set to Disconnected but let _Ready try to Reopen + } +} + + +# +# Send config commands after Board reported it is ready or still counting +# called from internal timer to give device the time to report its config first +########################################################################## +sub ArduCounter_ConfigureDevice($) +{ + my $param = shift; + my (undef,$name) = split(':',$param); + my $hash = $defs{$name}; + + # 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}) { + 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)"; + } + } else { + Log3 $name, 5, "$name: ConfigureDevice: can not compare against interval attr"; + } + } 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); + } + } } @@ -300,22 +554,25 @@ sub ArduCounter_Attr(@) #Log3 $name, 5, "$name: Attr called with @_"; if ($cmd eq "set") { - if ($aName =~ 'pin.*') { - if ($aName !~ 'pin[dD]?(\d+)') { - Log3 $name, 3, "$name: Invalid pin name in attr $name $aName $aVal"; - return "Invalid pin name $aName"; - } + if ($aName =~ /^pin[dD]?(\d+)/) { my $pin = $1; - if ($aVal =~ /^(rising|falling|change) ?(pullup)? ?([0-9]+)?/) { + my %pins; + if ($hash->{allowedPins}) { + %pins = map { $_ => 1 } split (",", $hash->{allowedPins}); + } + 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"} elsif ($1 eq 'falling') {$opt = "2"} - elsif ($1 eq 'change') {$opt = "1"} $opt .= ($2 ? ",1" : ",0"); # pullup $opt .= ($3 ? ",$3" : ""); # min length - if ($hash->{Initialized}) { - ArduCounter_Write( $hash, "${pin},${opt}a"); + if ($hash->{Initialized}) { + ArduCounter_Write($hash, "${pin},${opt}a"); } else { Log3 $name, 5, "$name: communication postponed until device is initialized"; } @@ -325,7 +582,7 @@ sub ArduCounter_Attr(@) return "Invalid Value $aVal"; } } elsif ($aName eq "interval") { - if ($aVal =~ '^(\d+) (\d+) ?(\d+)? ?(\d+)?$') { + if ($aVal =~ /^(\d+) (\d+) ?(\d+)? ?(\d+)?$/) { my $min = $1; my $max = $2; my $sml = $3; @@ -351,10 +608,21 @@ sub ArduCounter_Attr(@) Log3 $name, 3, "$name: Invalid value in attr $name $aName $aVal"; return "Invalid Value $aVal"; } + } elsif ($aName eq "keepAliveDelay") { + if ($aVal =~ '^(\d+)$') { + if ($aVal > 300) { + Log3 $name, 3, "$name: value too big in attr $name $aName $aVal"; + return "Value too big: $aVal"; + } + } else { + Log3 $name, 3, "$name: Invalid value in attr $name $aName $aVal"; + return "Invalid Value $aVal"; + } } elsif ($aName eq 'disable') { if ($aVal) { - Log3 $name, 5, "$name: disable attribute set"; - DevIo_CloseDev($hash); + Log3 $name, 5, "$name: disable attribute set"; + ArduCounter_Disconnected($hash); # set to disconnected and remove timers + DevIo_CloseDev($hash); # really close and remove from readyFnList again return; } else { Log3 $name, 3, "$name: disable attribute cleared"; @@ -414,18 +682,82 @@ sub ArduCounter_Attr(@) } elsif ($aName eq 'disable') { Log3 $name, 3, "$name: disable attribute removed"; - ArduCounter_Open($hash) if ($hash->{$init_done}); # if fhem is initialized + ArduCounter_Open($hash) if ($init_done); # if fhem is initialized } } return undef; } +# SET command +######################################################################### +sub ArduCounter_Flash($$) +{ + my ($hash, @args) = @_; + my $name = $hash->{NAME}; + my $log = ""; + my @deviceName = split('@', $hash->{DeviceName}); + my $port = $deviceName[0]; + my $firmwareFolder = "./FHEM/firmware/"; + my $logFile = AttrVal("global", "logdir", "./log") . "/ArduCounterFlash.log"; + + return "Flashing ESP8266 not supported yet" if ($hash->{Board} =~ /ESP8266/); + + my $hexFile = $firmwareFolder . "ArduCounter.hex"; + return "The file '$hexFile' does not exist" if(!-e $hexFile); + + Log3 $name, 3, "$name: Flashing Aduino at $port with $hexFile. See $logFile for details"; + + $log .= "flashing device as ArduCounter for $name\n"; + $log .= "hex file: $hexFile\n"; + + $log .= "port: $port\n"; + $log .= "log file: $logFile\n"; + + my $flashCommand = AttrVal($name, "flashCommand", ""); + + if($flashCommand ne "") { + if (-e $logFile) { + unlink $logFile; + } + + ArduCounter_Disconnected($hash); + DevIo_CloseDev($hash); + $log .= "$name closed\n"; + + my $avrdude = $flashCommand; + $avrdude =~ s/\Q[PORT]\E/$port/g; + $avrdude =~ s/\Q[HEXFILE]\E/$hexFile/g; + $avrdude =~ s/\Q[LOGFILE]\E/$logFile/g; + + $log .= "command: $avrdude\n\n"; + `$avrdude`; + + local $/=undef; + if (-e $logFile) { + open FILE, $logFile; + my $logText = ; + close FILE; + $log .= "--- AVRDUDE ---------------------------------------------------------------------------------\n"; + $log .= $logText; + $log .= "--- AVRDUDE ---------------------------------------------------------------------------------\n\n"; + } + else { + $log .= "WARNING: avrdude created no log file\n\n"; + } + ArduCounter_Open($hash, 0); # new open + $log .= "$name open called.\n"; + delete $hash->{Initialized}; + } + return $log; +} + + # SET command ######################################################################### sub ArduCounter_Set($@) { - my ( $hash, @a ) = @_; + my ($hash, @a) = @_; return "\"set ArduCounter\" needs at least one argument" if ( @a < 2 ); # @a is an array with DeviceName, SetName, Rest of Set Line @@ -438,84 +770,59 @@ sub ArduCounter_Set($@) return "Unknown argument $attr, choose one of " . join(" ", @cList); } - if(!$hash->{FD}) { - Log3 $name, 4, "$name: Set called but device is disconnected"; - return ("Set called but device is disconnected", undef); + if ($attr eq "disable") { + Log3 $name, 4, "$name: set disable called"; + CommandAttr(undef, "$name disable 1"); + return; + + } elsif ($attr eq "enable") { + Log3 $name, 4, "$name: set enable called"; + CommandAttr(undef, "$name disable 0"); + return; + + } elsif ($attr eq "reconnect") { + Log3 $name, 4, "$name: set reconnect called"; + DevIo_CloseDev($hash); + ArduCounter_Open($hash); + return; + + } elsif ($attr eq "flash") { + return ArduCounter_Flash($hash, @a); } - if (AttrVal($name, "disable", undef)) { - Log3 $name, 4, "$name: set called but device is disabled"; + if(!$hash->{FD}) { + Log3 $name, 4, "$name: Set $attr $arg called but device is disconnected"; + return ("Set called but device is disconnected", undef); + } + if (IsDisabled($name)) { + Log3 $name, 4, "$name: set $attr $arg called but device is disabled"; return; } - if ($attr eq "raw") { + Log3 $name, 4, "$name: set raw $arg called"; ArduCounter_Write($hash, "$arg"); + + } elsif ($attr eq "saveConfig") { + Log3 $name, 4, "$name: set saveConfig called"; + ArduCounter_Write($hash, "e"); } elsif ($attr eq "reset") { - DevIo_CloseDev($hash); - $hash->{buffer} = ""; - DevIo_OpenDev( $hash, 0, 0); + Log3 $name, 4, "$name: set reset called"; + DevIo_CloseDev($hash); + ArduCounter_Open($hash); if (ArduCounter_Write($hash, "r")) { delete $hash->{Initialized}; return "sent (r)eset command to device - waiting for its setup message"; } - } elsif ($attr eq "flash") { - my @args = split(' ', $arg); - my $log = ""; - my @deviceName = split('@', $hash->{DeviceName}); - my $port = $deviceName[0]; - my $firmwareFolder = "./FHEM/firmware/"; - my $logFile = AttrVal("global", "logdir", "./log") . "/ArduCounterFlash.log"; - my $hexFile = $firmwareFolder . "ArduCounter.hex"; - - return "The file '$hexFile' does not exist" if(!-e $hexFile); - - Log3 $name, 4, "$name: Flashing Aduino at $port with $hexFile. See $logFile for details"; - - $log .= "flashing device as ArduCounter for $name\n"; - $log .= "hex file: $hexFile\n"; - - $log .= "port: $port\n"; - $log .= "log file: $logFile\n"; - - my $flashCommand = AttrVal($name, "flashCommand", ""); - - if($flashCommand ne "") { - if (-e $logFile) { - unlink $logFile; - } - - DevIo_CloseDev($hash); - readingsSingleUpdate($hash, "state", "disconnected", 1); - $log .= "$name closed\n"; - - my $avrdude = $flashCommand; - $avrdude =~ s/\Q[PORT]\E/$port/g; - $avrdude =~ s/\Q[HEXFILE]\E/$hexFile/g; - $avrdude =~ s/\Q[LOGFILE]\E/$logFile/g; - - $log .= "command: $avrdude\n\n"; - `$avrdude`; - - local $/=undef; - if (-e $logFile) { - open FILE, $logFile; - my $logText = ; - close FILE; - $log .= "--- AVRDUDE ---------------------------------------------------------------------------------\n"; - $log .= $logText; - $log .= "--- AVRDUDE ---------------------------------------------------------------------------------\n\n"; - } - else { - $log .= "WARNING: avrdude created no log file\n\n"; - } - DevIo_OpenDev($hash, 0, 0); - $log .= "$name opened\n"; - delete $hash->{Initialized}; - } - return $log; + } elsif ($attr eq "devVerbose") { + if ($arg =~ /^\d$/) { + Log3 $name, 4, "$name: set devVerbose $arg called"; + ArduCounter_Write($hash, "$arg"."v"); + } else { + Log3 $name, 4, "$name: set devVerbose called with illegal value $arg"; + } } return undef; } @@ -540,7 +847,7 @@ sub ArduCounter_Get($@) return ("Get called but device is disconnected", undef); } - if (AttrVal($name, "disable", undef)) { + if (IsDisabled($name)) { Log3 $name, 4, "$name: get called but device is disabled"; return; } @@ -548,8 +855,7 @@ 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 [0-9]+ Milliseconds'); - # todo: test adding \n to regex to make sure we got the whole respose string + my ($err, $msg) = ArduCounter_ReadAnswer($hash, 'Next report in.*seconds'); return ($err ? $err : $msg); } @@ -559,21 +865,256 @@ sub ArduCounter_Get($@) ###################################### -sub ArduCounter_HandleVersion($$) +sub ArduCounter_HandleDeviceTime($$$$) { - my ($hash, $line) = @_; + my ($hash, $deTi, $deTiW, $now) = @_; my $name = $hash->{NAME}; - if ($line =~ / V([\d\.]+)/) { - my $version = $1; - if ($version < "1.9") { - $version .= " - not compatible with this Module version - please flash new sketch"; - Log3 $name, 3, "$name: device reported outdated Arducounter Firmware - please update!"; + + my $deviceNowSecs = ($deTi/1000) + ((0xFFFFFFFF / 1000) * $deTiW); + Log3 $name, 5, "$name: Device Time $deviceNowSecs"; + + if (defined ($hash->{'.DeTOff'}) && $hash->{'.LastDeT'}) { + if ($deviceNowSecs >= $hash->{'.LastDeT'}) { + $hash->{'.Drift2'} = ($now - $hash->{'.DeTOff'}) - $deviceNowSecs; + } else { + $hash->{'.DeTOff'} = $now - $deviceNowSecs; + Log3 $name, 4, "$name: device did reset (now $deviceNowSecs, before $hash->{'.LastDeT'}). New offset is $hash->{'.DeTOff'}"; + } + } else { + $hash->{'.DeTOff'} = $now - $deviceNowSecs; + $hash->{'.Drift2'} = 0; + $hash->{'.DriftStart'} = $now; + Log3 $name, 5, "$name: Initialize device clock offset to $hash->{'.DeTOff'}"; + } + $hash->{'.LastDeT'} = $deviceNowSecs; + + my $drTime = ($now - $hash->{'.DriftStart'}); + #Log3 $name, 5, "$name: Device Time $deviceNowSecs" . + #", Offset " . sprintf("%.3f", $hash->{'.DeTOff'}/1000) . + ", Drift " . sprintf("%.3f", $hash->{'.Drift2'}) . + "s in " . sprintf("%.3f", $drTime) . "s" . + ($drTime > 0 ? ", " . sprintf("%.2f", $hash->{'.Drift2'} / $drTime * 100) . "%" : ""); +} + + +###################################### +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 ($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 { - $hash->{Initialized} = 1; # now device is initialized + Log3 $name, 3, "$name: device sent hello: $line"; + $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. + 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 + } + 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 + } else { + Log3 $name, 4, "$name: probably wrong firmware version - cannot parse line $line"; + } +} + + +######################################################################### +sub ArduCounter_HandleCounters($$$$$$$$) +{ + my ($hash, $pin, $sequence, $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 $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 $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); + + 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 $lastInterval = ReadingsVal ($name, "timeDiff$pin", 0); + my $lastCDiff = ReadingsVal ($name, "countDiff$pin", 0); + my $offlTime = sprintf ("%.2f", $hash->{deviceBooted} - $lastCountTNum); + + if ($lastCountTS && $lastInterval && ($offlTime > 0) && ($offlTime < 12*60*60)) { # > 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; + + } 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"; + } + + 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"; + + $intpCount += $rDiff; + $longCount += $rDiff; + + readingsBulkUpdate($hash, $rcname, $count); + readingsBulkUpdate($hash, $rlname, $longCount); + readingsBulkUpdate($hash, $riname, $intpCount); + readingsBulkUpdate($hash, "seq".$pin, $sequence); +} + + +######################################################################### +sub ArduCounter_ParseReport($$) +{ + my ($hash, $line) = @_; + my $name = $hash->{NAME}; + my $now = gettimeofday(); + 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 $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 + + 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 + + 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"; + return; + } + my $power = sprintf ("%.3f", ($time ? $diff/$time/1000*3600*$factor : 0)); + Log3 $name, 4, "$name: Pin $pin ($lName) Cnt $count " . + "(diff $diff/$rDiff) in " . sprintf("%.3f", $time/1000) . "s" . + " 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"; + 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 + } else { + # normal way to set readings + 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); + } + readingsBulkUpdate($hash, "timeDiff$pin", $time); + readingsBulkUpdate($hash, "countDiff$pin", $diff); + + if (AttrVal($name, "verboseReadings$pin", 0)) { + readingsBulkUpdate($hash, "lastMsg$pin", $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 + Log3 $name, 3, "$name: device is still counting"; + if (!$hash->{WaitForHello}) { # if hello not already sent, send it now + ArduCounter_AskForHello("direct:$name"); + } + RemoveInternalTimer ("sendHello:$name"); # don't send hello again } - $hash->{VersionFirmware} = $version; - Log3 $name, 4, "$name: device reported firmware $version"; } } @@ -590,231 +1131,62 @@ sub ArduCounter_Parse($) foreach my $line (@lines) { #Log3 $name, 5, "$name: Parse line: $line"; - if ($line =~ 'R([\d]+) C([\d]+) D([\d]+) R([\d]+) T([\d]+)( N[\d]+)?( X[\d]+)?( F[\d]+)?( L[\d]+)?( A[\d]+)?( H.*)?') + if ($line =~ /^R([\d]+)/) { - # new count is beeing reported - my $pin = $1; - my $count = $2; - my $diff = $3; - my $rDiff = $4; - my $time = $5; - my $deTime = ($6 ? substr($6, 2) / 1000 : ""); - my $reject = ($7 ? substr($7, 2) : ""); - my $first = ($8 ? substr($8, 2) : ""); - my $last = ($9 ? substr($9, 2) : ""); - my $avgLen = ($10 ? substr($10, 2) : ""); - my $hist = ($11 ? substr($11, 2) : ""); + ArduCounter_ParseReport($hash, $line); - my $factor = AttrVal($name, "readingFactor$pin", AttrVal($name, "factor", 1000)); - my $rcname = AttrVal($name, "readingNameCount$pin", "pin$pin"); # internal count reading name - my $rlname = AttrVal($name, "readingNameLongCount$pin", "long$pin"); # long count - continues after reset - my $riname = AttrVal($name, "readingNameInterpolatedCount$pin", "interpolatedLong$pin"); # interpol. count - continues after reset, interpolates - 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 - - my $chIdx = 0; - my $sTime = $now - $time/1000; # start of observation interval (~first pulse) - my $fSTime = FmtDateTime($sTime); # formatted - my $fSdTim = FmtTime($sTime); # only time formatted - - my $eTime = $now; # now / end of observation interval - my $fETime = FmtDateTime($eTime); # formatted - my $fEdTim = FmtTime($eTime); # only time formatted - - if (!$time || !$factor) { - Log3 $name, 3, "$name: Pin $pin ($lName) skip line because time or factor is 0: $line"; - next; - } - my $power = sprintf ("%.3f", ($time ? $diff/$time/1000*3600*$factor : 0)); - - my $intrCount = 0; - my $offlTime = 0; - my $longCount = ReadingsVal($name, $rlname, 0); # alter long count Wert im Reading - my $intpCount = ReadingsVal($name, $riname, 0); # alter interpolated count Wert im Reading - if (!$hash->{CounterInterpolated}{$pin} && $hash->{CounterResetTime}) { - # arduino reboot -> try to interpolate - my $lastCountTime = ReadingsTimestamp ($name, $rlname, 0); # last time long count reading was set as string - my $lastCountTNum = time_str2num($lastCountTime); # ... as number - - my $lastInterval = ReadingsVal ($name, "timeDiff$pin", 0); - my $lastCDiff = ReadingsVal ($name, "countDiff$pin", 0); - - Log3 $name, 4, "$name: arduino was restarted so some impulses might have got lost for $pin ($lName)"; - $offlTime = sprintf ("%.2f", $hash->{CounterResetTime} - $lastCountTNum); - if ($lastCountTime && $lastInterval && ($offlTime > 0) && ($offlTime < 1000*60*60*12)) { - # > 0 and < 12h - my $lastRatio = $lastCDiff / $lastInterval; - my $curRatio = $diff / $time; - my $intRatio = 1000 * ($lastRatio + $curRatio) / 2; - $intrCount = int(($offlTime * $intRatio)+0.5); - - Log3 $name, 3, "$name: pin $pin ($lName): interpolation after counter reset, offline $offlTime secs, $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; - - } else { - Log3 $name, 4, "$name: interpolation of missed pulses for pin $pin ($lName) not possible - no valid historic data."; - } - $hash->{CounterInterpolated}{$pin} = 1; - } - Log3 $name, 5, "$name: Pin $pin debug: adding $rDiff to long count $longCount and interpolated count $intpCount"; - $intpCount += $rDiff; - $longCount += $rDiff; - - Log3 $name, 4, "$name: Pin $pin ($lName) count $count " . - ($longCount ? "longCount $longCount " : "") . - ($intpCount ? "interpCount $intpCount " : "") . - "(diff $diff) in " . sprintf("%.3f", $time/1000) . "s" . - ((defined($reject) && $reject ne "") ? ", reject $reject" : "") . - (defined($avgLen) ? ", Avg Len ${avgLen}ms" : "") . - ", result $power"; - Log3 $name, 4, "$name: interval $fSdTim until $fEdTim" . - (defined($first) ? ", First at $first" : "") . - (defined($last) ? ", Last at $last" : ""); - - - readingsBeginUpdate($hash); - if (AttrVal($name, "readingStartTime$pin", 0)) { - # special way to set readings: use time of interval start as reading time - Log3 $name, 5, "$name: readingStartTime$pin specified: setting reading timestamp to $fSdTim"; - Log3 $name, 5, "$name: set readings $rpname to $power, timeDiff$pin to $time and countDiff$pin to $diff"; - - $hash->{".updateTime"} = $sTime; - $hash->{".updateTimestamp"} = $fSTime; - readingsBulkUpdate($hash, $rpname, $power) if ($time); - $hash->{CHANGETIME}[$chIdx++] = $fSTime; # Intervall start - - $hash->{".updateTime"} = $eTime; - $hash->{".updateTimestamp"} = $fETime; - readingsBulkUpdate($hash, $rcname, $count); - $hash->{CHANGETIME}[$chIdx++] = $fETime; - - readingsBulkUpdate($hash, $rlname, $longCount); - $hash->{CHANGETIME}[$chIdx++] = $fETime; - - readingsBulkUpdate($hash, $riname, $intpCount); - $hash->{CHANGETIME}[$chIdx++] = $fETime; - - if (defined($reject)) { - my $rejCount = ReadingsVal($name, "reject$pin", 0); # alter reject count Wert im Reading - readingsBulkUpdate($hash, "reject$pin", $reject + $rejCount); - $hash->{CHANGETIME}[$chIdx++] = $fETime; - } - if (AttrVal($name, "verboseReadings$pin", 0)) { - - readingsBulkUpdate($hash, "timeDiff$pin", $time); - $hash->{CHANGETIME}[$chIdx++] = $fETime; - - readingsBulkUpdate($hash, "countDiff$pin", $diff); - $hash->{CHANGETIME}[$chIdx++] = $fETime; - - readingsBulkUpdate($hash, "lastMsg$pin", $line); - $hash->{CHANGETIME}[$chIdx++] = $fETime; - - if ($hist) { - readingsBulkUpdate($hash, "pinHistory$pin", $hist); - $hash->{CHANGETIME}[$chIdx++] = $fETime; - } - } - } else { - # normal way to set readings - Log3 $name, 5, "$name: set readings $rpname to $power, timeDiff$pin to $time and countDiff$pin to $diff"; - readingsBulkUpdate($hash, $rpname, $power) if ($time); - #$eTime = time_str2num(ReadingsTimestamp ($name, $rpname, 0)); - readingsBulkUpdate($hash, $rcname, $count); - readingsBulkUpdate($hash, $rlname, $longCount); - readingsBulkUpdate($hash, $riname, $intpCount); - if (defined($reject)) { - my $rejCount = ReadingsVal($name, "reject$pin", 0); # alter reject count Wert im Reading - readingsBulkUpdate($hash, "reject$pin", $reject + $rejCount); - } - if (AttrVal($name, "verboseReadings$pin", 0)) { - readingsBulkUpdate($hash, "timeDiff$pin", $time); - readingsBulkUpdate($hash, "countDiff$pin", $diff); - readingsBulkUpdate($hash, "lastMsg$pin", $line); - readingsBulkUpdate($hash, "pinHistory$pin", $hist) if ($hist); - } - } - readingsEndUpdate($hash, 1); - - if ($deTime) { - if (defined ($hash->{'.DeTOff'}) && $hash->{'.LastDeT'}) { - if ($deTime >= $hash->{'.LastDeT'}) { - $hash->{'.Drift2'} = ($now - $hash->{'.DeTOff'}) - $deTime; - } else { - $hash->{'.DeTOff'} = $now - $deTime; - Log3 $name, 4, "$name: device clock wrapped or reset (now $deTime, before $hash->{'.LastDeT'}). New offset is $hash->{'.DeTOff'}"; - } - } else { - $hash->{'.DeTOff'} = $now - $deTime; - $hash->{'.Drift2'} = 0; - $hash->{'.DriftStart'} = $now; - Log3 $name, 5, "$name: Initialize clock offset to $hash->{'.DeTOff'}"; - } - $hash->{'.LastDeT'} = $deTime; + } elsif ($line =~ /^H([\d+]) (.+)/) { # pin pulse history as separate line + my $pin = $1; + my $hist = $2; + if (AttrVal($name, "verboseReadings$pin", 0)) { + readingsBeginUpdate($hash); + readingsBulkUpdate($hash, "pinHistory$pin", $hist); + readingsEndUpdate($hash, 1); } - my $drTime = ($now - $hash->{'.DriftStart'}); - Log3 $name, 5, "$name: Device Time $deTime" . - #", Offset " . sprintf("%.3f", $hash->{'.DeTOff'}/1000) . - ", Drift " . sprintf("%.3f", $hash->{'.Drift2'}) . - "s in " . sprintf("%.3f", $drTime) . "s" . - ($drTime > 0 ? ", " . sprintf("%.2f", $hash->{'.Drift2'} / $drTime * 100) . "%" : ""); - - if (!$hash->{Initialized}) { # device has not sent Started / hello yet - Log3 $name, 3, "$name: device is still counting"; - if (!$hash->{WaitForHello}) { # if hello has not already been sent, send it now - ArduCounter_SendHello("direct:$name"); - } - RemoveInternalTimer ("sendHello:$name"); # don't send hello again - } - - } elsif ($line =~ /ArduCounter V([\d\.]+).?Hello/) { # response to h(ello) - Log3 $name, 3, "$name: device replied to hello, V$1"; - ArduCounter_HandleVersion($hash, $line); - if ($hash->{Initialized}) { - ArduCounter_ConfigureDevice($hash) # send pin configuration - } - delete $hash->{WaitForHello}; - RemoveInternalTimer ("hwait:$name"); - RemoveInternalTimer ("sendHello:$name"); - - } elsif ($line =~ /Status: ArduCounter V([\d\.]+)/) { # response to s(how) - $retStr .= "\n" if ($retStr); - $retStr .= $line; - ArduCounter_HandleVersion($hash, $line); - - 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 - - - } elsif ($line =~ /ArduCounter V([\d\.]+).?Started/) { # setup message - Log3 $name, 3, "$name: device sent setup message, V$1"; - ArduCounter_HandleVersion($hash, $line); - if ($hash->{Initialized}) { - ArduCounter_ConfigureDevice($hash) # send pin configuration - } - 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 + } elsif ($line =~ /^M Next report in ([\d]+)/) { # end of report tells when next + $retStr .= ($retStr ? "\n" : "") . $line; + Log3 $name, 4, "$name: device: $line"; - $hash->{CounterResetTime} = $now; - delete $hash->{CounterInterpolated}; - - } elsif ($line =~ /V([\d\.]+).?Setup done/) { # old setup message - Log3 $name, 3, "$name: device is flashed with an old and incompatible firmware : $1"; - Log3 $name, 3, "$name: please use set $name flash to update"; - ArduCounter_HandleVersion($hash, $line); + } 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; + + } elsif ($line =~ /^P([\d]+) (falling|rising|-) ?(pullup)? ?min ([\d]+)/) { # pin configuration at device + $hash->{runningCfg}{$1} = "$2 $3 $4"; # save for later compare + + $retStr .= ($retStr ? "\n" : "") . $line; + Log3 $name, 4, "$name: device sent config for pin $1: $1 $2 min $3"; + + } elsif ($line =~ /^alive/) { # alive response + RemoveInternalTimer ("alive:$name"); + $hash->{WaitForAlive} = 0; + delete $hash->{KeepAliveRetries}; + + } elsif ($line =~ /^ArduCounter V([\d\.]+).*(Started|Hello)/) { # setup message + ArduCounter_ParseHello($hash, $line, $now); + + } elsif ($line =~ /^Status: ArduCounter V([\d\.]+)/) { # response to s(how) + $retStr .= ($retStr ? "\n" : "") . $line; + + } elsif ($line =~ /connection already busy/) { + my $now = gettimeofday(); + my $delay = AttrVal($name, "nextOpenDelay", 60); + Log3 $name, 4, "$name: _Parse: primary tcp connection seems busy - delay next open"; + ArduCounter_Disconnected($hash); # set to disconnected (state), remove timers + DevIo_CloseDev($hash); # close, remove from readyfnlist so _ready is not called again + RemoveInternalTimer ("delayedopen:$name"); + InternalTimer($now+$delay, "ArduCounter_DelayedOpen", "delayedopen:$name", 0); } elsif ($line =~ /^D (.*)/) { # debug / info Message from device - $retStr .= "\n" if ($retStr); - $retStr .= $1; + $retStr .= ($retStr ? "\n" : "") . $line; Log3 $name, 4, "$name: device: $1"; + } elsif ($line =~ /^M (.*)/) { # other Message from device - $retStr .= "\n" if ($retStr); - $retStr .= $1; + $retStr .= ($retStr ? "\n" : "") . $line; Log3 $name, 3, "$name: device: $1"; + } elsif ($line =~ /^[\s\n]*$/) { # blank line - ignore } else { @@ -853,8 +1225,7 @@ sub ArduCounter_Read($) ##################################### # Called from get / set to get a direct answer # called with logical device hash -sub -ArduCounter_ReadAnswer($$) +sub ArduCounter_ReadAnswer($$) { my ($hash, $expect) = @_; my $name = $hash->{NAME}; @@ -885,7 +1256,7 @@ ArduCounter_ReadAnswer($$) if($nfound < 0) { next if ($! == EAGAIN() || $! == EINTR() || $! == 0); my $err = $!; - DevIo_Disconnected($hash); + ArduCounter_Disconnected($hash); # set to disconnected, remove timers, let _ready try to reopen Log3 $name, 3, "$name: ReadAnswer error: $err"; return("ReadAnswer error: $err", undef); } @@ -925,67 +1296,31 @@ ArduCounter_ReadAnswer($$) -# -# copied from other FHEM modules -######################################################################### -sub ArduCounter_Ready($) -{ - my ($hash) = @_; - my $name = $hash->{NAME}; - - if (AttrVal($name, "disable", undef)) { - return; - } - - # try to reopen if state is disconnected - if ( $hash->{STATE} eq "disconnected" ) { - #Log3 $name, 3, "$name: ReadyFN tries to open"; # debug - delete $hash->{Initialized}; - DevIo_OpenDev( $hash, 1, undef ); - if ($hash->{FD}) { - Log3 $name, 3, "$name: device maybe not initialized yet, set timer to send h(ello"; - my $now = gettimeofday(); - my $hdl = AttrVal($name, "helloSendDelay", 3); - RemoveInternalTimer ("sendHello:$name"); - InternalTimer($now+$hdl, "ArduCounter_SendHello", "sendHello:$name", 0); - } - return; - } - - # This is relevant for windows/USB only - my $po = $hash->{USBDev}; - if ($po) { - my ( $BlockingFlags, $InBytes, $OutBytes, $ErrorFlags ) = $po->status; - return ( $InBytes > 0 ); - } -} - - 1; =pod =item device -=item summary Module for consumption counter based on an arduino with the ArduCounter sketch -=item summary_DE Modul für Strom / Wasserzähler auf Arduino-Basis mit ArduCounter Sketch +=item summary Module for counters based on arduino / ESP8266 board +=item summary_DE Modul für Strom / Wasserzähler mit Arduino- oder ESP8266 =begin html

ArduCounter


Readings / Events
@@ -1131,19 +1495,25 @@ sub ArduCounter_Ready($) The module creates at least the following readings and events for each defined pin:
  • pin.*
  • the current count at this pin -
  • long.*
  • - long count which keeps on counting up after fhem restarts whereas the pin.* count is only a temporary internal count that starts at 0 when the arduino board starts. -
  • interpolatedLong.*
  • - like long.* but when the Arduino restarts the potentially missed pulses are interpolated based on the pulse rate before the restart and after the restart. -
  • reject.*
  • - counts rejected pulses that are shorter than the specified minimal pulse length. +
  • long.*
  • + long count which keeps on counting up after fhem restarts whereas the pin.* count is only a temporary internal count that starts at 0 when the arduino board starts. +
  • interpolatedLong.*
  • + like long.* but when the Arduino restarts the potentially missed pulses are interpolated based on the pulse rate before the restart and after the restart. +
  • reject.*
  • + counts rejected pulses that are shorter than the specified minimal pulse length.
  • power.*
  • the current calculated power at this pin -
  • pinHistory.*
  • - shows detailed information of the last pulses. This is only available when a minimal pulse length is specified for this pin. Also the total number of impulses recorded here is limited to 20 for all pins together. The output looks like -36/7:0C, -29/7:1G, -22/8:0C, -14/7:1G, -7/7:0C, 0/7:1G
    - The first number is the relative time in milliseconds when the input level changed, followed by the length in milliseconds, the level and the internal action.
    - -36/7:0C for example means that 36 milliseconds before the reporting started, the input changed to 0V, stayed there for 7 milliseconds and this was counted.
    - +
  • pinHistory.*
  • + shows detailed information of the last pulses. This is only available when a minimal pulse length is specified for this pin. Also the total number of impulses recorded here is limited to 20 for all pins together. The output looks like -36/7:0C, -29/7:1G, -22/8:0C, -14/7:1G, -7/7:0C, 0/7:1G
    + The first number is the relative time in milliseconds when the input level changed, followed by the length in milliseconds, the level and the internal action.
    + -36/7:0C for example means that 36 milliseconds before the reporting started, the input changed to 0V, stayed there for 7 milliseconds and this was counted.
    +
  • countDiff.*
  • + delta of the current count to the last reported one. This is used together with timeDiff.* to calculate the power consumption. +
  • timeDiff.*
  • + time difference between the first pulse in the current observation interval and the last one. Used togehter with countDiff to calculate the power consumption. +
  • seq.*
  • + internal sequence number of the last report from the board to fhem. +
    diff --git a/fhem/FHEM/firmware/ArduCounter.bin b/fhem/FHEM/firmware/ArduCounter.bin new file mode 100755 index 000000000..ac33fd177 Binary files /dev/null and b/fhem/FHEM/firmware/ArduCounter.bin differ diff --git a/fhem/FHEM/firmware/ArduCounter.hex b/fhem/FHEM/firmware/ArduCounter.hex index 8a2f858da..9fc321541 100755 --- a/fhem/FHEM/firmware/ArduCounter.hex +++ b/fhem/FHEM/firmware/ArduCounter.hex @@ -1,570 +1,757 @@ -:100000000C9479010C94A1010C94A1010C94C607E5 -:100010000C94A1070C947C070C94A1010C94A101F1 -:100020000C94A1010C94A1010C94A1010C94A101C8 -:100030000C94A1010C94A1010C94A1010C94A101B8 -:100040000C9432070C94A1010C9400070C94DA066E -:100050000C94A1010C94A1010C94A1010C94A10198 -:100060000C94A1010C94A1014572726F723A2000A8 -:1000700041726475436F756E7465722056322E300E -:1000800035002C20617667206C656E20002C206C7A -:1000900061737420617420004D20202066697273A2 -:1000A0007420617420002052656A2000206D730066 -:1000B0002920696E200020282B002C20636F756E8C -:1000C0007420002C206E6F206D696E206C656E00B0 -:1000D0002066616C6C696E670020726973696E6777 -:1000E00000206D73002C206D696E206C656E200001 -:1000F0006368616E67650066616C6C696E6700724B -:100100006973696E67002C20694D6F64652000502B -:1001100043496E742070696E2000205374617274BC -:1001200065640000000008000201000003040700ED -:100130000000000000000000000000250028002B47 -:100140000000000000240027002A0020004D206449 -:100150006566696E65642070696E200000000000AD -:10016000230026002900416464496E7400496C6CC8 -:100170006567616C206D6F646520666F72207069C1 -:100180006E2073706563696669636174696F6E2060 -:1001900000496C6C6567616C2070696E20737065D6 -:1001A00063696669636174696F6E20000404040406 -:1001B0000404040402020202020203030303030311 -:1001C00001020408102040800102040810200102EE -:1001D000040810204D2072656D6F76656420005212 -:1001E000656D496E7400496C6C6567616C2070695F -:1001F0006E2073706563696669636174696F6E20F0 -:10020000002000200020004D20696E746572766128 -:100210006C732073657420746F200073697A6500B5 -:1002200020002061742000206C656E200020737413 -:100230006172742000442070696E20004420707543 -:100240006C736520686973746F72793A2000204D71 -:10025000696C6C697365636F6E6473004D204E65E5 -:100260007874207265706F727420696E20002000AF -:100270004D2070696E20004D206D696E20636F7592 -:100280006E7420004D206D696E20696E7465727603 -:10029000616C20004D206D617820696E7465727606 -:1002A000616C20004D206E6F726D616C20696E7400 -:1002B000657276616C20004D205374617475733AD9 -:1002C000200048656C6C6F003A002F002C20002045 -:1002D0004800204100204C00204600205800204EBD -:1002E000002054002052002044002043005200000F -:1002F000111111241FBECFEFD8E0DEBFCDBF11E03A -:10030000A0E0B1E0E8E4F3E202C005900D92A03471 -:10031000B107D9F726E0A0E4B1E001C01D92AD3FDE -:10032000B207E1F711E0C9E7D1E004C02197FE016F -:100330000E949711C837D107C9F70E94D6080C94BC -:10034000A2110C940000CF92DF92EF92FF920F93D4 -:100350001F93CF93DF936C017A018B01C0E0D0E053 -:10036000CE15DF0589F0D8016D918D01D601ED9193 -:10037000FC910190F081E02DC6010995892B11F4C3 -:100380007E0102C02196ECCFC701DF91CF911F9172 -:100390000F91FF90EF90DF90CF900895FC01918D29 -:1003A000828D981761F0828DDF01A80FB11D5D96D7 -:1003B0008C91928D9F5F9F73928F90E008958FEF45 -:1003C0009FEF0895FC01918D828D981731F0828DF9 -:1003D000E80FF11D858D90E008958FEF9FEF089550 -:1003E000FC01918D228D892F90E0805C9F4F821BB4 -:1003F00091098F739927089580E696E00E94F00195 -:1004000021E0892B09F420E0822F0895FC01848DDE -:10041000DF01A80FB11DA35ABF4F2C91848D90E02E -:1004200001968F739927848FA689B7892C93A08909 -:10043000B1898C9180648C93938D848D981306C0C0 -:100440000288F389E02D80818F7D80830895EF926B -:10045000FF920F931F93CF93DF93EC0181E0888F7E -:100460009B8D8C8D981305C0E889F989808185FD65 -:1004700024C0F62E0B8D10E00F5F1F4F0F73112756 -:10048000E02E8C8DE8120CC00FB607FCFACFE8897D -:10049000F989808185FFF5CFCE010E940602F1CF58 -:1004A0008B8DFE01E80FF11DE35AFF4FF0820B8F99 -:1004B000EA89FB898081806207C0EE89FF896083B9 -:1004C000E889F98980818064808381E090E0DF9110 -:1004D000CF911F910F91FF90EF900895CF93DF93ED -:1004E000EC01888D8823C9F0EA89FB89808185FD2C -:1004F00005C0A889B9898C9186FD0FC00FB607FC8D -:10050000F5CF808185FFF2CFA889B9898C9185FFCD -:10051000EDCFCE010E940602E7CFDF91CF91089583 -:1005200080E090E0892B29F00E94FC0181110C945D -:10053000000008953FB7F89480915D0190915E01AD -:10054000A0915F01B091600126B5A89B05C02F3F27 -:1005500019F00196A11DB11D3FBFBA2FA92F982FE9 -:100560008827820F911DA11DB11DBC01CD0142E064 -:10057000660F771F881F991F4A95D1F708952FB7E7 -:10058000F8946091590170915A0180915B019091AA -:100590005C012FBF08952F923F924F925F926F920E -:1005A0007F928F929F92AF92BF92CF92DF92EF9203 -:1005B000FF920F931F93CF93DF9300D000D0CDB75E -:1005C000DEB7882E0E94BF02AB01BC01282D30E0AF -:1005D000F901EE0FFF1FE05AFE4FA591B4914C9028 -:1005E0004C82F901EF53FE4F908094244082220FF9 -:1005F000331FF901E25EFE4F0190F081E02D808112 -:10060000982209F460C1880C880C880C212C312CAC -:10061000AA24A39487E4B82E81E090E0022C01C0C4 -:10062000880F0A94EAF7292D282309F444C1182DCC -:10063000120D5C808521E1E009F4E0E0212F30E03B -:10064000D901A452BE4FF1E08C91833009F0F0E063 -:10065000C901880F991F880F991FDC01AF59BE4F40 -:10066000CD90DD90ED90FC902A013B014C185D0887 -:100670006E087F0873016201A091FB02AF5FA09337 -:10068000FB02A091FB02A43110F01092FB02A0919A -:10069000FB02B0E0A951BD4F1C931091FB02DC019D -:1006A000AF59BE4F4D905D906D907C9004E0109FCF -:1006B000D0011124A956BD4F4D925D926D927C924E -:1006C0001397A091FB0214E0A19FD0011124A95B14 -:1006D000BD4FCD92DD92ED92FC921397A091FB025B -:1006E0006A2E712CAE2FAA25A98383010D5C1D4FA4 -:1006F0004A2ED8014C92A091FB02B0E0A15EBD4F02 -:1007000010E2512E5C92D901AC53BE4FAC91A13096 -:1007100011F0EF13C9C03901660C771C7B826A8225 -:100720008301045E1B4FD8014D905C90411451042D -:1007300009F4BAC0612C712CC414D504E604F70482 -:1007400040F59C012F593E4FD9014D935D936D9318 -:100750007C931397EF17A1F0AA81BB81A451BC4FE2 -:100760002D913C9111972F5F3F4F11963C932E9303 -:10077000E091FB02F0E0E15EFD4FB2E5B08394C092 -:10078000E091FB02F0E0E15EFD4FA8E54A2E4082D9 -:100790008BC0D901A05CBE4FEF1308C02C91E09133 -:1007A000FB02F0E0E15EFD4FB0827CC0EC91EF1700 -:1007B00009F461C0FC01EC5DFA4F40835183628310 -:1007C0007383FC01E45AFC4F408051806280738047 -:1007D00045284628472821F4408351836283738348 -:1007E00089010C501E4F1B830A83F8011081FC0104 -:1007F000EC53FB4F4080518062807380112351F095 -:100800000FEF401A500A600A700A408251826282D9 -:10081000738219C01FEF411A510A610A710A40829E -:10082000518262827382F901E455FB4F20812F5F70 -:100830002083FC01EC57FA4F40835183628373831A -:10084000EA81FB81A082FC01E450FD4F4080518091 -:1008500062807380C40CD51CE61CF71CC082D18258 -:10086000E282F382E091FB02F0E0E15EFD4F33E4CF -:10087000432E408217C0FC01E450FD4F4080518060 -:1008800062807380C40CD51CE61CF71CC082D18228 -:10089000E282F382E091FB02F0E0E15EFD4F20E5B1 -:1008A000522E508269806C92FC01EF59FE4F4083BA -:1008B0005183628373830FEF201A300A18E02116E8 -:1008C000310409F0A9CE0F900F900F900F90DF9197 -:1008D000CF911F910F91FF90EF90DF90CF90BF903D -:1008E000AF909F908F907F906F905F904F903F90D0 -:1008F0002F900895E82FF0E0E455FE4FE491E25088 -:10090000E13011F4865005C0877098E0E99F800DB2 -:10091000112490E00895CF93DF93C0E7D0E0F0E09A -:10092000C138DF0731F0FE0184910E94EB07219668 -:10093000F6CFDF91CF910895CF93DF93C8E6D0E053 -:10094000F0E0CF36DF0731F0FE0184910E94EB0723 -:100950002196F6CFDF91CF9108952F923F924F923B -:100960005F926F927F928F929F92AF92BF92CF923F -:10097000DF92EF92FF920F931F93CF93DF93CDB748 -:10098000DEB727970FB6F894DEBF0FBECDBF8983C1 -:10099000282E312CC101880F991F880F991F9F8322 -:1009A0008E83FC01EC5DFA4FC080D180E280F38041 -:1009B0008C010C571A4FF80180819181A281B3817B -:1009C000C81AD90AEA0AFB0AEE81FF81EC53FB4FF1 -:1009D0004080518062807380F101E455FB4F80813B -:1009E00053014201881A9108A108B108EE81FF81E4 -:1009F000E45BFB4F80819181A281B381881A990ABF -:100A0000AA0ABB0AC114D104E104F10469F40E94EA -:100A1000BF02F80100811181228133816B017C01C9 -:100A2000C01AD10AE20AF30A8FE091E00E9462083C -:100A30002981622F70E080E090E04AE00E94FE078A -:100A400086E091E00E946208F101EC53FE4F808144 -:100A5000823051F0833029F0813051F480EF90E002 -:100A600005C08FEF90E002C087EF90E00E9462081F -:100A70008101000F111F9801245E3B4FF901808115 -:100A80009181892B11F185EE90E02C833D830E94AA -:100A900062082C813D81F9016081718180E090E0E4 -:100AA0004AE00E94FE0781EE90E00E946208F10198 -:100AB000E452FE4F8081823029F0833051F489ED79 -:100AC00090E005C080ED90E002C083EC90E00E94D1 -:100AD00062088AEB90E00E946208C301B2010E94A2 -:100AE0004F0886EB90E00E946208C501B4010E94A5 -:100AF0004F0880EB90E00E946208C701B6010E9497 -:100B00004F088CEA90E00E946208F801E45EFB4F17 -:100B100080819181892BA9F086EA90E00E94620889 -:100B2000F801E451FC4F60817181F801E454FC4FFD -:100B300080819181681B790B80E090E04AE00E94FF -:100B4000FE0781149104A104B10409F46EC00E944F -:100B5000F60788E990E00E946208EE81FF81E45A7E -:100B6000FC4F80819181A281B3810E811F810C5144 -:100B70001A4FF801C080D180E280F380BC01CD0122 -:100B80006C197D098E099F090E944F088DE890E03D -:100B90000E946208EE81FF81EC5DFA4F80819181B5 -:100BA000A281B381F8010081118122813381BC01CE -:100BB000CD01601B710B820B930B0E944F08F894C0 -:100BC000EE81FF81E450FD4F6081718182819381CC -:100BD000A50194010E945211789482E890E02C8340 -:100BE0003D834A835B830E9462082C813D814A8158 -:100BF0005B81CA01B90127960FB6F894DEBF0FBE1C -:100C0000CDBFDF91CF911F910F91FF90EF90DF90BB -:100C1000CF90BF90AF909F908F907F906F905F909C -:100C20004F903F902F900C944F0827960FB6F89452 -:100C3000DEBF0FBECDBFDF91CF911F910F91FF900F -:100C4000EF90DF90CF90BF90AF909F908F907F906C -:100C50006F905F904F903F902F900895AF92BF920A -:100C6000CF92DF92EF92FF920F931F93CF93DF9378 -:100C70000E94BF026B017C010E94F6070E948B0458 -:100C80008AE191E00E94AD0844E456E0CCE4D4E06F -:100C9000ACE1B4E0ECEBAE2EE3E0BE2E04EE15E0EA -:100CA00080E090E0FA011192AF019C012C503E4F80 -:100CB000F90110829C01220F331F220F331FB9014B -:100CC0006C537B4FFB011082118212821382BC0194 -:100CD00064557B4FFB011082199219921992199257 -:100CE000B9016C577A4FFB01C082D182E282F38254 -:100CF000B9016C5D7A4FFB01C082D182E282F3823E -:100D00002F593E4FF901C082D182E282F3821D92B7 -:100D10001D929C01220F331F24513C4FF901118277 -:100D20001082F501119211925F01F801C192D192E6 -:100D3000E192F1928F0101968831910509F0B2CFCD -:100D4000E0E6F1E0A591B4918C918093C101E2E6D7 -:100D5000F1E0A591B4918C918093C201E4E6F1E0B9 -:100D6000A591B4918C918093C3010E94BF02009120 -:100D70000601109107012091080130910901DC0161 -:100D8000CB01800F911FA21FB31F80935C0690932D -:100D90005D06A0935E06B0935F06DF91CF911F9131 -:100DA0000F91FF90EF90DF90CF90BF90AF9008959C -:100DB0000E949D111F920F920FB60F9211242F9334 -:100DC0003F934F935F936F937F938F939F93AF93D3 -:100DD000BF93EF93FF9380E696E00E940602FF9197 -:100DE000EF91BF91AF919F918F917F916F915F91A3 -:100DF0004F913F912F910F900FBE0F901F9018951C -:100E00001F920F920FB60F9211242F938F939F93DF -:100E1000EF93FF93E0917006F09171068081E0916D -:100E20007606F091770682FD12C090818091790656 -:100E30008F5F8F7320917A06821751F0E0917906C7 -:100E4000F0E0E05AF94F958F8093790601C08081D8 -:100E5000FF91EF919F918F912F910F900FBE0F9067 -:100E60001F9018951F920F920FB60F9211242F9377 -:100E70003F938F939F93AF93BF938091590190912C -:100E80005A01A0915B01B0915C013091580123E0BF -:100E9000230F2D3720F40196A11DB11D05C026E8B2 -:100EA000230F0296A11DB11D209358018093590173 -:100EB00090935A01A0935B01B0935C0180915D0116 -:100EC00090915E01A0915F01B09160010196A11D1A -:100ED000B11D80935D0190935E01A0935F01B0937B -:100EE0006001BF91AF919F918F913F912F910F9092 -:100EF0000FBE0F901F9018951F920F920FB60F9272 -:100F000011242F933F934F935F936F937F938F930E -:100F10009F93AF93BF93EF93FF9382E00E94CB0226 -:100F2000FF91EF91BF91AF919F918F917F916F91C1 -:100F30005F914F913F912F910F900FBE0F901F9097 -:100F400018951F920F920FB60F9211242F933F9373 -:100F50004F935F936F937F938F939F93AF93BF93C1 -:100F6000EF93FF9381E00E94CB02FF91EF91BF913D -:100F7000AF919F918F917F916F915F914F913F9131 -:100F80002F910F900FBE0F901F9018951F920F92E8 -:100F90000FB60F9211242F933F934F935F936F934C -:100FA0007F938F939F93AF93BF93EF93FF9380E0D3 -:100FB0000E94CB02FF91EF91BF91AF919F918F91D2 -:100FC0007F916F915F914F913F912F910F900FBE45 -:100FD0000F901F901895E0916006F09161060190C6 -:100FE000F081E02D682F80E696E0099442E050E021 -:100FF0006CE371E080E696E00C94A3018F929F92DF -:10100000AF92BF920F931F93CF93DF93CDB7DEB70D -:10101000A1970FB6F894DEBF0FBECDBF19A2423024 -:1010200008F44AE08E010F5D1F4F842E912CA12CF5 -:10103000B12CA50194010E945211E62FB901CA01F9 -:1010400001501109EA3014F4E05D01C0E95CD801F7 -:10105000EC93232B242B252B61F70115110571F03F -:10106000F80101900020E9F73197AF01401B510BC7 -:10107000B80180E696E00E94A30102C080E090E003 -:10108000A1960FB6F894DEBF0FBECDBFDF91CF9112 -:101090001F910F91BF90AF909F908F9008954AE05D -:1010A0000C94FE07CF93DF93BC0180E090E04AE010 -:1010B0000E94FE07EC010E94F6078C0F9D1FDF9136 -:1010C000CF9108950F931F93CF93DF938C01C0E0CE -:1010D000D0E0F801EC0FFD1F6491662371F0E09100 -:1010E0006006F09161060190F081E02D80E696E0C7 -:1010F0000995892B11F02196ECCFCE01DF91CF918C -:101100001F910F910895CF92DF92EF92FF92CF93AC -:10111000DF9397FF19C06B017C018DE20E94EB0702 -:10112000EC0166277727CB016C197D098E099F0991 -:101130004AE00E94FE078C0F9D1FDF91CF91FF9028 -:10114000EF90DF90CF9008954AE0DF91CF91FF902C -:10115000EF90DF90CF900C94FE07CF93DF930E9427 -:101160006208EC010E94F6078C0F9D1FDF91CF9162 -:101170000895CF93DF934AE00E94FE07EC010E949E -:10118000F6078C0F9D1FDF91CF910895CF93DF93CA -:10119000BC01990F880B990B0E948308EC010E94F7 -:1011A000F6078C0F9D1FDF91CF910895CF93DF93AA -:1011B000CDB7DEB7E2970FB6F894DEBF0FBECDBF56 -:1011C000789484B5826084BD84B5816084BD85B522 -:1011D000826085BD85B5816085BD80916E0081602E -:1011E00080936E00109281008091810082608093D4 -:1011F00081008091810081608093810080918000D6 -:101200008160809380008091B10084608093B10000 -:101210008091B00081608093B00080917A008460FA -:1012200080937A0080917A00826080937A00809126 -:101230007A00816080937A0080917A008068809340 -:101240007A001092C100E0917006F091710682E080 -:101250008083E0916C06F0916D061082E0916E063D -:10126000F0916F0683E3808310927806E091740614 -:10127000F091750686E08083E0917206F091730626 -:10128000808180618083E0917206F0917306808195 -:1012900088608083E0917206F09173068081806897 -:1012A0008083E0917206F091730680818F7D808348 -:1012B0000E949A024B015C01F4EFCF2EDD24D394FF -:1012C000E12CF12C0E949A02DC01CB0188199909CA -:1012D000AA09BB09883E9340A105B10558F001E079 -:1012E000C01AD108E108F10818EE810E13E0911E32 -:1012F000A11CB11CC114D104E104F10419F77894C4 -:101300000E942E0684E496E0E8E1CE2EC80EC8A620 -:101310000E94BF026D877E878F87988B80E696E05C -:101320000E94F001892B09F4D4C380E696E00E9464 -:10133000CE018C32C9F480911E02873008F0C9C3F7 -:10134000E82FF0E0EE0FFF1FE45FFD4F20911C023D -:1013500030911D02318320838F5F80931E02109293 -:101360001D0210921C02B5C390ED980F9A30B0F494 -:1013700040911C0250911D029AE0949F9001959F0C -:10138000300D1124205331097901E80EF11C87FD3D -:10139000FA94F0921D02E0921C029BC39FE9980F01 -:1013A0009A3108F096C3883609F47DC334F4813647 -:1013B00071F0843609F483C17EC3823709F4A9C26F -:1013C000833709F4A9C2893609F075C3CFC1C0902B -:1013D0001E02EC2DF0E0EE0FFF1FE45FFD4F809149 -:1013E0001C0290911D0291838083C394C0921E02BF -:1013F0000E94BF024B015C0100910C0210910D0292 -:10140000802F0E947A047C01C8010197479748F415 -:10141000F801EC5DFE4F8081882319F028E12E153C -:1014200040F40E949C0481E991E00E946208C80196 -:10143000A8C6A80144555E4F3A01FA0124915EEF17 -:10144000D52ED20E80910E0290910F02823091051E -:1014500079F08330910599F00197D9F00E949C04AE -:101460008DE691E00E946208C8010E94C6080FC084 -:10147000F701FF27E452FE4F82E0808308C0F701A6 -:10148000FF27E452FE4F83E0808363E003C062E005 -:1014900001C061E0C80180549E4FFC017491F301CA -:1014A00024912223C1F030E0220F331FF901EF5BBA -:1014B000FE4FA591B491F901E95CFE4F459154911D -:1014C0003FB7F8942C91709527232C93DA012C9137 -:1014D00072237C933FBFB2E0BC1508F054C020914A -:1014E000100230911102232B09F441C0F801ED5D87 -:1014F000FE4F3491FC012491F30144914423B9F14E -:10150000332339F1333091F038F43130A9F03230EF -:1015100001F534B53F7D12C0373091F03830A1F07D -:101520003430B9F4309180003F7D03C030918000A9 -:101530003F77309380000DC034B53F7734BD09C08C -:101540003091B0003F7703C03091B0003F7D3093C1 -:10155000B000E42FF0E0EE0FFF1FE95CFE4FA59115 -:10156000B4913FB7F8944C91242B2C933FBFF3E0F8 -:10157000CF1649F020911202309113022115310546 -:1015800021F061E002C020E030E0FC018491F30131 -:101590006490662009F4B4C56A946A94E62DF0E07C -:1015A000EE0FFF1FE25EFE4F0190F081E02D908173 -:1015B000892B80834091680081E090E001C0880F12 -:1015C0006A94EAF7842B80936800A7015527FA01F3 -:1015D000EC53FE4F6083CA01880F991FFC01E45E43 -:1015E000FB4F31832083DA01AC5BB94F2C9130E0A3 -:1015F0002017310709F451C02D2D30E0F901EE0F0D -:10160000FF1FE05AFE4F65917491FB016081F90163 -:10161000EF53FE4F60830C93FA01EC50FE4F1082A3 -:101620009A01220F331F220F331FF901EC53FB4F96 -:101630001082118212821382FA01E455FB4F10824C -:10164000F901E45BFB4F1082118212821382F901CF -:10165000EC57FA4F80829182A282B382F901EC5D4D -:10166000FA4F80829182A282B382F901EC51FA4F43 -:1016700080829182A282B382F901EF59FE4F80826B -:101680009182A282B382FC01E451FC4F118210824C -:10169000FC01E454FC4F118210828DE491E00E9421 -:1016A0006208B801110F880B990B0E9483088BE424 -:1016B00091E00E9462088E2D0E94AD0427C1109116 -:1016C0001E02E12FF0E0EE0FFF1FE45FFD4F80915F -:1016D0001C0290911D02918380831F5F10931E0254 -:1016E000E0900C02F0900D028E2D0E947A041123DE -:1016F00071F09701215031092731310540F4F7018C -:10170000EC5DFE4F2081222311F0893128F00E94E8 -:101710009C0486EE91E032C5F701E054FE4F24911F -:10172000F701E455FE4FE491EE2309F4EEC4E250D4 -:10173000AE2FB0E0AA0FBB1FA25EBE4F0D90BC91B2 -:10174000A02D3C91209523232C932C912111E2C4B0 -:101750003091680041E050E001C0440FEA95EAF79B -:10176000242F2095232320936800D4C480911E0247 -:10177000E82FF0E0EE0FFF1FE45FFD4F20911C0209 -:1017800030911D02318320838F5F80931E0284304D -:1017900038F40E949C048BE192E00E946208B6C07B -:1017A00020910C0230910D02C901019780319E40B9 -:1017B00038F00E949C0480910C0290910D025BC055 -:1017C000A8EEB3E00E9474116B017C0160930601E6 -:1017D0007093070180930801909309010E94BF0252 -:1017E000DC01CB018C0D9D1DAE1DBF1D00915C0663 -:1017F00010915D0620915E0630915F06801791077B -:10180000A207B30770F40E94BF02C60ED71EE81EDF -:10181000F91EC0925C06D0925D06E0925E06F092E0 -:101820005F0620910E0230910F02C90101978031AD -:101830009E4038F00E949C0480910E0290910F020D -:101840001AC0A8EEB3E00E94741160930A0170936D -:101850000B0180930C0190930D01209110023091A7 -:10186000110221311EE0310748F00E949C04809152 -:101870001002909111020E9452081DC1A8EEB3E01F -:101880000E94741160930001709301018093020122 -:10189000909303018091120290911302909305019D -:1018A0008093040187E092E00E94620860910C023C -:1018B00070910D0280E090E04AE00E94FE0785E012 -:1018C00092E00E94620860910E0270910F0280E027 -:1018D00090E04AE00E94FE0783E092E00E946208E6 -:1018E000609110027091110280E090E04AE00E9445 -:1018F000FE0781E092E00E946208609112027091FE -:10190000130280E090E04AE00E94FE070E94F60782 -:10191000D2C00E942E06CFC087EB92E00E946208E0 -:101920000E948B040E94F60784EA92E00E946208FB -:101930006091060170910701809108019091090161 -:101940000E94B90884E992E00E94620860910A014D -:1019500070910B0180910C0190910D010E94B908CA -:1019600084E892E00E946208609100017091010198 -:1019700080910201909103010E94B90887E792E0EB -:101980000E94620880910401909105010E94520812 -:1019900004E416E0F12CD801ED908D01EE2099F0D1 -:1019A00080E792E00E9462086E2D70E080E090E097 -:1019B0000E9483088EE692E00E9462088F2D0E94AA -:1019C000AD040E94F607F394B8E1FB12E4CF8CE576 -:1019D00092E00E946208C0905C06D0905D06E090A4 -:1019E0005E06F0905F060E94BF0297018601061B0B -:1019F000170B280B390BC901B8010E944F088EE460 -:101A000092E00E9462080E94F6078CE392E00E9436 -:101A1000AD080E94BF022B013C018091FB0290E0C7 -:101A2000029664E170E00E943E116C01812C912CC1 -:101A30005401E12CF12CC7018C0D9D1D64E170E077 -:101A40000E943E118C011C01220C331C220C331C01 -:101A5000E114F10409F498C3F101E956FD4F8081C6 -:101A60009181A281B381F101E95BFD4F40815181F8 -:101A700062817381840F951FA61FB71F8815990572 -:101A8000AA05BB0508F080C3F101E956FD4F80802F -:101A90009180A280B3801FEFE11AF10A24E1E216DF -:101AA000F10449F608C00E94F6070E948B0482ECFC -:101AB00092E00E94AD0810921E0210921D02109238 -:101AC0001C02ECE0F2E01192119232E0EC31F307EB -:101AD000D1F780915C0690915D06A0915E06B09171 -:101AE0005F06CD84DE84EF84F888C81AD90AEA0A32 -:101AF000FB0AF7FE2CC080910A0190910B01A09186 -:101B00000C01B0910D014D855E856F857889481B6C -:101B1000590B6A0B7B0BA4EEB5E0E4E4F6E080E041 -:101B20009191992369F00D911D912D913C911397FD -:101B30006A017B01C01AD10AE20AF30AF7FE81E0CA -:101B40001496D8A4DE12ECCF882309F4D6C20E94E2 -:101B5000BF022B013C0104E416E01A8F098F04EE4A -:101B600015E01C870B870CE114E01A8709870CEB42 -:101B700013E018870F830CE414E01A8309831D8A8D -:101B80001E8A1F8A188E212C312CA98DBA8DCD90DA -:101B9000BA8FA98FCB8ECC2009F477C2F89471014B -:101BA000EE0CFF1CEE0CFF1CFEA6EDA687010C57E9 -:101BB0001A4F18AB0FA7D8012D913D914D915C9113 -:101BC000298B3A8B4B8B5C8BF701EC5DFA4F408194 -:101BD000518162817381F701EC53FB4F80809180CA -:101BE000A280B380F101E455FB4FFAABE9AB208151 -:101BF00078946A017B0189899A89AB89BC89C81A62 -:101C0000D90AEA0AFB0ACB82DC82ED82FE82A98134 -:101C1000BA81CD90DD90ED90FC90CC8EDD8EEE8E75 -:101C2000FF8E75016401C21AD108E108F1080C8D1C -:101C30001D8D2E8D3F8DC01AD10AE20AF30AD501FF -:101C4000C401801B910BA20BB30B8C8F9D8FAE8FA9 -:101C5000BF8FAB85BC850D911D912D913C9108A343 -:101C600019A32AA33BA300910A0110910B01209113 -:101C70000C0130910D010CA31DA32EA33FA3D30192 -:101C8000C201801B910BA20BB30B08A119A12AA1C1 -:101C90003BA1801B910BA20BB30BB7FD55C08091EC -:101CA000040190910501A0E0B0E0C816D906EA064B -:101CB000FB0618F18091000190910101A0910201B1 -:101CC000B09103010B811C812D813E81801791070A -:101CD000A207B30790F48091060190910701A091AB -:101CE0000801B09109010CA11DA12EA13FA18017EF -:101CF0009107A207B30709F05FC0F894EDA5FEA510 -:101D0000EC5DFA4F4082518262827382EDA5FEA59E -:101D1000EC57FA4F4082518262827382F101E4559E -:101D2000FB4F10827894E981FA8180829182A282AD -:101D3000B382A301920189899A89AB89BC89281B46 -:101D4000390B4A0B5B0B4EC0809106019091070145 -:101D5000A0910801B091090193018201081B190BA0 -:101D60002A0B3B0BD901C80108A119A12AA13BA14B -:101D7000801B910BA20BB30BB7FD87C180910401AF -:101D800090910501A0E0B0E0C816D906EA06FB066E -:101D900008F47BC18091000190910101A0910201A2 -:101DA000B09103010B811C812D813E818017910729 -:101DB000A207B30708F069C1A981BA818D929D92EB -:101DC000AD92BC921397F894EFA5F8A94083518384 -:101DD00062837383A9A9BAA91C9278942B813C8150 -:101DE0004D815E818DEE92E029A73AA74BA75CA7B3 -:101DF0000E9462080B8D602F70E080E090E00E94EE -:101E000083088AEE92E00E946208C501B4010E9434 -:101E10004F0887EE92E00E946208C701B6010E9457 -:101E20004F0884EE92E00E9462086C8D7D8D8E8D4D -:101E30009F8D0E944F0881EE92E00E94620829A5C2 -:101E40003AA54BA55CA5CA01B9010E944F088EEDC9 -:101E500092E00E946208C301B2010E948308A98532 -:101E6000BA858D919C91892B09F18BED92E00E94AE -:101E70006208C101880F991F8C0104511C4FF801A1 -:101E800060817181AF81B8858D919C91681B790BC0 -:101E900080E090E04AE00E94FE07F894F80180811B -:101EA0009181AF81B8858D939C9378942C8D3D8DD5 -:101EB0004E8D5F8D232B242B252B09F453C088EDE9 -:101EC00092E00E9462080DA51EA5045A1C4FF8015D -:101ED00080819181A281B381BC01CD0129893A8998 -:101EE0004B895C89621B730B840B950B0E944F0816 -:101EF00085ED92E00E9462086B817C818D819E81DC -:101F00000E944F08D8011D921D921D921C9213979A -:101F1000E985FA8580819181892B21F1F8940DA5BD -:101F20001EA504501D4FD8016D917D918D919C91FE -:101F3000A70196010E945211F801108211821282AB -:101F40001382789482ED92E029A73AA74BA75CA769 -:101F50000E94620829A53AA54BA55CA5CA01B90152 -:101F60000E944F088091FB0290E0029664E170E0CD -:101F70000E943E116C018FEC92E00E946208E12CFD -:101F8000F12C21E0C7018C0D9D1D64E170E00E94E1 -:101F90003E115C01FC01E951FD4F808190E082150A -:101FA000930509F062C08501000F111F000F111F7A -:101FB00021111FC0F801E956FD4F80819181A28156 -:101FC000B381F801E95BFD4F4081518162817381EA -:101FD000840F951FA61FB71F2D893E894F89588DE5 -:101FE00082179307A407B507A0F18CEC92E00E943A -:101FF0006208F801E956FD4F80819181A281B38189 -:10200000BC01CD0164197509860997090E948308EE -:102010008AEC92E00E946208F801E95BFD4F608162 -:102020007181828193810E944F0888EC92E00E9426 -:102030006208F501ED5CFD4F608170E080E090E0AA -:102040004AE00E94FE07F501E15EFD4F80810E949B -:10205000EB07F801E956FD4F80819181A281B381A0 -:102060008D8B9E8BAF8BB88F20E09FEFE91AF90A1A -:10207000A4E1EA16F10409F085CF0E94F607EB858A -:10208000FC854082518262827382FFEF2F1A3F0AE1 -:102090000B851C850C5F1F4F1C870B8709851A85D4 -:1020A0000E5F1F4F1A8709870F8118850E5F1F4F1C -:1020B00018870F8309811A810C5F1F4F1A830983C8 -:1020C00018E12116310409F060CD8091060190914C -:1020D0000701A0910801B0910901CD84DE84EF844D -:1020E000F8888C0D9D1DAE1DBF1D80935C069093DE -:1020F0005D06A0935E06B0935F060E94900208C939 -:102100000E949C0486E691E0D4CC0E949C048FED52 -:1021100091E0CFCC9C013327F901EC5BF94F1082A1 -:10212000F901EC50FE4F1082C901880F991F880FEA -:10213000991FFC01EC53FB4F108211821282138213 -:10214000F901E455FB4F1082FC01E45BFB4F108268 -:10215000118212821382220F331FF901E45EFB4FBA -:1021600011821082F901E454FC4F11821082F901AE -:10217000E451FC4F1182108284ED91E00E946208CC -:10218000C7010E94C60897CC85E392E00E946208CE -:10219000F801E951FD4FE081F0E0EC5BF94F60811F -:1021A00070E080E090E04AE00E94FE078DE292E05D -:1021B0000E946208F101E956FD4F80819181A28160 -:1021C000B381BC01CD0164197509860997090E9484 -:1021D000830887E292E00E946208F101E95BFD4F0B -:1021E00060817181828193810E944F0882E292E036 -:1021F0000E946208F801ED5CFD4F608170E080E0B4 -:1022000090E04AE00E94FE0780E292E00E946208AD -:10221000F801E15EFD4F80810E94EB070E94F60706 -:1022200033CCE0E6F6E01382128288EE93E0A0E081 -:10223000B0E084839583A683B78382E191E09183A4 -:10224000808385EC90E09587848784EC90E0978785 -:10225000868780EC90E0918B808B81EC90E0938B73 -:10226000828B82EC90E0958B848B86EC90E0978B50 -:10227000868B118E128E138E148E089597FB072E67 -:1022800016F4009407D077FD09D00E94831107FC53 -:1022900005D03EF4909581959F4F08957095619576 -:1022A0007F4F0895A1E21A2EAA1BBB1BFD010DC092 -:1022B000AA1FBB1FEE1FFF1FA217B307E407F507F6 -:1022C00020F0A21BB30BE40BF50B661F771F881FD2 -:1022D000991F1A9469F760957095809590959B0168 -:1022E000AC01BD01CF010895A29FB001B39FC00111 -:1022F000A39F700D811D1124911DB29F700D811D32 -:102300001124911D0895AA1BBB1B51E107C0AA1FF0 -:10231000BB1FA617B70710F0A61BB70B881F991F86 -:102320005A95A9F780959095BC01CD010895EE0FBF -:10233000FF1F0590F491E02D099481E090E0F8945E -:082340000C94A211F894FFCFE8 -:10234800D007000001003075000060EA00000000BE -:1023580000002702A301F001CE01E2016E026B002A -:102368006C006D00000000030405060708090A0B4D -:102378000C0D0E0F10110000000000000D0A0000E7 +: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 :00000001FF diff --git a/fhem/contrib/arduino/ArduCounter2.36.ino b/fhem/contrib/arduino/ArduCounter2.36.ino new file mode 100755 index 000000000..96a217a8b --- /dev/null +++ b/fhem/contrib/arduino/ArduCounter2.36.ino @@ -0,0 +1,1409 @@ +/* + * 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 + */ + + +/* + 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 + + + ToDo / Ideas: + + +*/ + +/* allow printing of every pin change to Serial */ +#define debugPins 1 + +/* allow tracking of pulse lengths */ +#define pulseHistory 1 + +/* use a sample config at boot */ +// #define debugCfg 1 + +#include "pins_arduino.h" +#include + +const char versionStr[] PROGMEM = "ArduCounter V2.36"; +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__) +const char boardName[] PROGMEM = "UNO"; +#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 +#define MAX_HIST 20 + + +#ifdef ESP8266 +// varibales / definitions for ESP 8266 based boards +#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_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, + -1, 5, 6, 7}; +/* 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 */ +short internalPins[MAX_PIN] = + { 16, 5, 4, 0, + 2, 14, 12, 13}; + +#else +// variables / definitions for arduino / 328p based boards +#define MAX_APIN 22 +#define MAX_PIN 20 + +/* 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 */ +short allowedPins[MAX_APIN] = + {-1, -1, 0, 1, + 2, 3, 4, 5, + 6, 7, 8, 9, + 10, 11, 12, 13, + 14, 15, 16, 17, + 18, 19 }; + +/* Map from sketch internal pin index to real chip IO pin number */ +short internalPins[MAX_PIN] = + { 2, 3, 4, 5, + 6, 7, 8, 9, + 10, 11, 12, 13, + 14, 15, 16, 17, + 18, 19 }; + +/* 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); +} + + +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(); + 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(); + 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]; // value 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) { // value 1 is level (rising / falling -> 0/1 + 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 + + + if (size > 2 && values[2]) { // value 2 is pullup + pinMode (rPin, INPUT_PULLUP); + pullup[pinIndex] = 1; + // digitalWrite (rPin, HIGH); // old way to enable pullup resistor + } else { + pinMode (rPin, INPUT); + pullup[pinIndex] = 0; + } + + if (size > 3 && values[3] > 0) { // value 3 is min length + pulseWidth = values[3]; + } else { + pulseWidth = 2; + } + pulseWidthMin[pinIndex] = pulseWidth; + + if (!AddPinChangeInterrupt(rPin)) { // add Pin Change Interrupt + PrintErrorMsg(); + Output->println(F("AddInt")); + return; + } + + 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 ESP8266 + detachInterrupt(digitalPinToInterrupt(internalPins[pinIndex])); +#else + if (!RemovePinChangeInterrupt(internalPins[pinIndex])) { + PrintErrorMsg(); Output->println(F("RemInt")); + return; + } +#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(); +} + + +void keepAliveCmd(uint16_t *values, uint8_t size) { + 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); +} + + +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 ++; + 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); + 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; + 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(); + 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 + 1) { + Output->println(F("M illegal config in EEPROM")); + return; + } + uint16_t v1, v2, v3, v4; + char cmd; + 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); + 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) { + 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': + commandData[commandDataPointer] = value; + addCmd(commandData, commandDataPointer+1); + break; + case 'd': + commandData[commandDataPointer] = value; + removeCmd(commandData, commandDataPointer+1); + break; + case 'i': + commandData[commandDataPointer] = value; + intervalCmd(commandData, commandDataPointer+1); + break; + case 'r': + initialize(); + break; + case 's': + showCmd(); + break; + case 'v': + 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; + case 'h': + helloCmd(); + break; + case 'e': + saveToEEPROMCmd(); + break; + case 'f': + // OTA flash from HTTP Server + break; +#ifdef ESP8266 + case 'q': + quitCmd(); + break; +#endif + case 'k': + commandData[commandDataPointer] = value; + keepAliveCmd(commandData, commandDataPointer+1); + 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(" 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; +} + + +void initPinVars(short pinIndex, uint32_t now) { + 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; + uint8_t level = digitalRead(internalPins[pinIndex]); + lastLevel[pinIndex] = level; +#ifdef debugPins + lastState[pinIndex] = level; // for debug output +#endif +} + + +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(); + 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 debugPins + if (devVerbose >= 10) { + debugPinChanges(); + } +#endif + + if (reportDue()) { + report(); + } +} +