From afd9e4a3f71b439d71d55db04ac016a4ad197f71 Mon Sep 17 00:00:00 2001 From: StefanStrobel <> Date: Tue, 9 Jul 2019 18:20:01 +0000 Subject: [PATCH] 98_Arducounter.pm: bug fixes and firmware update git-svn-id: https://svn.fhem.de/fhem/trunk@19808 2b470e98-0d58-463d-a4d8-8e2adae1ed80 --- fhem/FHEM/98_ArduCounter.pm | 18 +- fhem/FHEM/firmware/ArduCounter.hex | 1650 ++++----- fhem/contrib/arduino/ArduCounter1.8.ino | 804 ----- fhem/contrib/arduino/ArduCounter2.00.ino | 899 ----- ...rduCounter2.36.ino => ArduCounter3.20.ino} | 3135 +++++++++-------- 5 files changed, 2571 insertions(+), 3935 deletions(-) delete mode 100755 fhem/contrib/arduino/ArduCounter1.8.ino delete mode 100755 fhem/contrib/arduino/ArduCounter2.00.ino rename fhem/contrib/arduino/{ArduCounter2.36.ino => ArduCounter3.20.ino} (71%) diff --git a/fhem/FHEM/98_ArduCounter.pm b/fhem/FHEM/98_ArduCounter.pm index 751c55bfb..0ef8761f2 100755 --- a/fhem/FHEM/98_ArduCounter.pm +++ b/fhem/FHEM/98_ArduCounter.pm @@ -80,6 +80,7 @@ # 2019-02-23 added maxHist attribute # 2019-02-24 added documentation and better return value when get history has no data, option to pass a pinName to get history # query new running config after configuring device +# 2019-06-17 fix log messages and expose logRetries attribute # # ideas / todo: # @@ -103,7 +104,7 @@ use strict; use warnings; use Time::HiRes qw(gettimeofday); -my $ArduCounter_Version = '6.14 - 24.2.2019'; +my $ArduCounter_Version = '6.15 - 17.6.2019'; my %ArduCounter_sets = ( @@ -192,6 +193,7 @@ sub ArduCounter_Initialize($) 'configDelay ' . # how many seconds to wait before sending config after reboot of board 'keepAliveDelay ' . 'keepAliveTimeout ' . + 'keepAliveRetries ' . 'nextOpenDelay ' . 'silentReconnect:0,1 ' . 'openTimeout ' . @@ -538,14 +540,14 @@ 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"; + Log3 $name, 3, "$name: device didn't reply to k(eeepAlive), no retries left, setting device to disconnected"; ArduCounter_Disconnected($hash); # set to Disconnected but let _Ready try to Reopen + } else { + Log3 $name, 3, "$name: device didn't reply to k(eeepAlive), count=$hash->{KeepAliveRetries}"; } } @@ -2070,7 +2072,13 @@ sub ArduCounter_ReadAnswer($$) attr myCounter keepAliveTimeout 3 - +
  • keepAliveRetries
  • + defines how often sending a keepalive is retried before the connection is closed and reopened.
    + It defaults to 2.
    + Example: + + attr myCounter keepAliveRetries 3 +
  • nextOpenDelay
  • defines the time that the module waits before retrying to open a disconnected tcp connection.
    This defaults to 60 seconds.
    diff --git a/fhem/FHEM/firmware/ArduCounter.hex b/fhem/FHEM/firmware/ArduCounter.hex index f6930087e..d06404feb 100755 --- a/fhem/FHEM/firmware/ArduCounter.hex +++ b/fhem/FHEM/firmware/ArduCounter.hex @@ -1,827 +1,839 @@ -:100000000C941C020C9444020C9444020C941B0D9E -:100010000C94F60C0C94D10C0C9444020C944402F5 -:100020000C9444020C9444020C9444020C94440238 -:100030000C9444020C9444020C9444020C94440228 -:100040000C94CA110C9444020C94D2030C94AC038B -:100050000C9444020C9444020C9444020C94440208 -:100060000C9444020C944402AE13A915A915C013B4 -:100070001F14A915A915DA14DD14A915EF14A91573 -:10008000A915A915A915A915A9150B150E157715EB -:10009000A91589154572726F723A20004A616E2067 -:1000A000203620323031392031373A31313A343745 +:100000000C943B020C9463020C9463020C94460D16 +:100010000C94210D0C94FC0C0C9463020C94630260 +:100020000C9463020C9463020C9463020C946302BC +:100030000C9463020C9463020C9463020C946302AC +:100040000C94B9110C9463020C94F1030C94CB033F +:100050000C9463020C9463020C9463020C9463028C +:100060000C9463020C946302A713A315A315B91390 +:100070001914A315A315D414D714A315E914A315A3 +:10008000A315A315A315A315A3150515081571151B +:10009000A31583154572726F723A20004A756C2061 +:1000A000203920323031392031383A34353A32353E :1000B0000020636F6D70696C656420004E414E4F87 :1000C00000206F6E200041726475436F756E746519 -:1000D000722056332E3130002000200020004900CD +:1000D000722056332E3230002000200020004900CC :1000E00020005400206D696E20002070756C6C75C6 :1000F0007000202D002066616C6C696E67002072B4 :100100006973696E6700500040002F0073002C2057 :1001100000200048002041002053002058002C00FF :10012000204E002054002F002044002043005200A5 -:100130002C002042002C0020540020617661696C64 -:1001400061626C65002C002048656C6C6F2C20701F -:10015000696E7320004D20726573746F72696E67EB -:1001600020636F6E6669672066726F6D204545502B -:10017000524F4D004D20696C6C6567616C20636F58 -:100180006E66696720696E20454550524F4D004D9F -:10019000206E6F20636F6E66696720696E2045452B -:1001A00050524F4D002C002C002C0020004D2053AD -:1001B0006C6F743A200020536C6F74733A20004DBA -:1001C00020454550524F4D20436F6E6669673A2077 -:1001D000004D20696C6C6567616C20636F6E6669A9 -:1001E0006720696E20454550524F4D004D206E6F7F -:1001F00020636F6E66696720696E20454550524FD7 -:100200004D00206D696C6C697365636F6E6473007B -:100210004D204E657874207265706F727420696E1F -:1002200020002C200056004D205374617475733AE1 -:100230002000616C697665002C2000636F6E666932 -:10024000672073617665642C20004D2072656D6FA8 -:10025000766564200052656D496E7400496C6C656A -:1002600067616C2070696E2073706563696669638D -:100270006174696F6E20004D20696C6C6567616CFC -:100280002076616C75652070617373656420666F9C -:100290007220646576566572626F7365004D2064E6 -:1002A0006576566572626F73652073657420746F2E -:1002B00020002073697A6520002C004420676F7449 -:1002C0002000202072656A65637420002020636F1F -:1002D000756E74200020206869737449647820006A -:1002E00020746F200020292000202820696E74656A -:1002F000726E616C20004D2070696E2000202072AB -:10030000656A65637420002020636F756E74200039 -:100310002020686973744964782000206368616EE6 -:1003200067656420746F2000202900202820696EF2 -:100330007465726E616C20004D2070696E20004CF7 -:100340000000000000240027002A00000000002513 -:100350000028002B00000000080002010000030438 -:100360000700000000000000000102040810204007 -:10037000800102040810200102040810204D2064AE -:100380006566696E65642000416464496E74004965 -:100390006C6C6567616C2070756C7365206C65763C -:1003A000656C2073706563696669636174696F6EFB -:1003B00020666F722070696E20000000000023002C -:1003C00026002900040404040404040402020202B6 -:1003D0000202030303030303496C6C6567616C202D -:1003E00070696E2073706563696669636174696FB3 -:1003F0006E200020004D20616E616C6F6720746874 -:10040000726573686F6C64732073657420746F20F9 -:100410000073697A65002000200020004D20696E7D -:1004200074657276616C732073657420746F20003C -:1004300073697A650000141211241FBECFEFD8E053 -:10044000DEBFCDBF11E0A0E0B1E0EAEFF2E302C011 -:1004500005900D92A639B107D9F726E0A6E9B1E0DB -:1004600001C01D92A938B207E1F712E0CCE1D2E059 -:1004700004C02197FE010E944D19CB31D107C9F765 -:100480000E9441120C947B190C940000CF92DF92D1 -:10049000EF92FF920F931F93CF93DF936C017A013A -:1004A0008B01C0E0D0E0CE15DF0581F0D8016D9161 -:1004B0008D01D601ED91FC910190F081E02DC601F6 -:1004C0000995892B11F02196EECF7E01C701DF91AE -:1004D000CF911F910F91FF90EF90DF90CF900895F3 -:1004E000089580E090E00895FC01538D448D252F00 -:1004F00030E0842F90E0821B930B541710F0CF96BE -:10050000089501970895FC01918D828D981761F0EF -:10051000828DDF01A80FB11D5D968C91928D9F5F3A -:100520009F73928F90E008958FEF9FEF0895FC01E5 -:10053000918D828D981731F0828DE80FF11D858D98 -:1005400090E008958FEF9FEF0895FC01918D228D2B -:10055000892F90E0805C9F4F821B91098F739927B0 -:10056000089585E595E00E94A50221E0892B09F414 -:1005700020E0822F0895FC01848DDF01A80FB11DBA -:10058000A35ABF4F2C91848D90E001968F739927C9 -:10059000848FA689B7892C93A089B1898C918370A7 -:1005A00080648C93938D848D981306C00288F389A0 -:1005B000E02D80818F7D80830895EF92FF920F93CD -:1005C0001F93CF93DF93EC0181E0888F9B8D8C8DFF -:1005D000981305C0E889F989808185FD26C0F62E2B -:1005E0000B8D10E00F5F1F4F0F731127E02E8C8DC6 -:1005F000E8120CC00FB607FCFACFE889F9898081B0 -:1006000085FFF5CFCE010E94BB02F1CF8B8DFE019D -:10061000E80FF11DE35AFF4FF0829FB7F8940B8F5C -:10062000EA89FB89808180620AC09FB7F894EE89CD -:10063000FF896083E889F989808183708064808381 -:100640009FBF81E090E0DF91CF911F910F91FF90CC -:10065000EF900895CF93DF93EC01888D8823C9F044 -:10066000EA89FB89808185FD05C0A889B9898C91BB -:1006700086FD0FC00FB607FCF5CF808185FFF2CF56 -:10068000A889B9898C9185FFEDCFCE010E94BB026C -:10069000E7CFDF91CF91089580E090E0892B29F09A -:1006A0000E94B10281110C9400000895DC01ED91CB -:1006B000FC910190F081E02D0994EF92FF920F934D -:1006C0001F93CF93DF938C017B01C0E0D0E0F70153 -:1006D000EC0FFD1F6491662361F0D801ED91FC9150 -:1006E0000190F081E02DC8010995892B11F0219628 -:1006F000EECFCE01DF91CF911F910F91FF90EF9040 -:1007000008956115710579F0FB0101900020E9F76A -:100710003197AF01461B570BDC01ED91FC91028034 -:10072000F381E02D099480E090E0089562E971E0A2 -:100730000C9481030F931F93CF93DF93EC010E94DE -:100740005D038C01CE010E949603800F911FDF9103 -:10075000CF911F910F9108951F920F920FB60F9294 -:1007600011242F933F934F935F936F937F938F93B6 -:100770009F93AF93BF93EF93FF9385E595E00E941E -:10078000BB02FF91EF91BF91AF919F918F917F91AC -:100790006F915F914F913F912F910F900FBE0F90EE -:1007A0001F9018951F920F920FB60F9211242F933E -:1007B0008F939F93EF93FF93E0916505F09166050A -:1007C0008081E0916B05F0916C0582FD12C09081F3 -:1007D00080916E058F5F8F7320916F05821751F0A6 -:1007E000E0916E05F0E0EB5AFA4F958F80936E051D -:1007F00001C08081FF91EF919F918F912F910F9078 -:100800000FBE0F901F9018950C940119CF93DF9392 -:10081000EC019C01220F331FF901EA5BF94F8FEFC6 -:100820009FEF91838083FE01E059FB4F1082F90115 -:10083000E550FA4F11821082CE01880F991F880F60 -:10084000991FFC01EF54FB4F108211821282138218 -:10085000FE01E25AFB4F1082FC01EA5EFB4F108260 -:10086000118212821382F901E357FB4F1182108229 -:10087000F901EE50FC4F11821082FC01E655FC4F4D -:100880004083518362837383FC01EE59FC4F4083A4 -:10089000518362837383FC01E65EFC4F4083518386 -:1008A00062837383FC01E45FFA4F40835183628368 -:1008B0007383FE01E85FFC4F1082F901EE5BFE4F8F -:1008C00080810E94D00EFE01EA50FD4F8083C65009 -:1008D000DB4F8883DF91CF910895CF92DF92EF9223 -:1008E000FF920F931F93CF93DF930E949E0F6B0194 -:1008F0007C01C0E0D0E0B701A601CE010E94060451 -:100900002196C231D105B9F780910E0190910F0166 -:10091000A0911001B0911101C80ED91EEA1EFB1E54 -:10092000C0927A06D0927B06E0927C06F0927D0619 -:1009300010925405EEEBF3E0A591B4918C91809365 -:100940004306E0ECF3E0A591B4918C9180934406CA -:10095000E2ECF3E0A591B4918C918093450680E0A0 -:1009600090E00E940404833461F481E090E00E94EE -:100970000404863631F482E090E00E940404873655 -:1009800009F16FE871E085E595E00E949A030E9405 -:100990009E0F6093840470938504809386049093E3 -:1009A00087048091880490918904909383048093B4 -:1009B0008204DF91CF911F910F91FF90EF90DF9014 -:1009C000CF90089583E090E00E940404F82E8FEF0A -:1009D0008F0D833118F064E771E0D5CF65E571E0E4 -:1009E00085E595E00E949A03C4E0D0E010E064E061 -:1009F000E62ECE010E940404082FCE0101960E942B -:100A00000404D82ECE0102960E94040490E0982F90 -:100A100088278D0D911D90936B0680936A06CE01F9 -:100A200003960E940404D82ECE0104960E9404046A -:100A300090E0982F88278D0D911D90936D068093DF -:100A40006C06CE0105960E940404D82ECE010696AF -:100A50000E94040490E0982F88278D0D911D90939B -:100A60006F0680936E06CE0107960E940404D82E6E -:100A7000CE0108960E94040490E0982F88278D0DDF -:100A8000911D90937106809370062996E0928C04D4 -:100A9000093421F484E00E94AA0F0BC0043521F42C -:100AA00084E00E94450E05C0013419F484E00E94E0 -:100AB000A8101F5FF1129DCF10928C0410928B042E -:100AC00010928A04EAE6F6E08AE796E01192119223 -:100AD0008E179F07D9F75BCFFF920F931F93CF938A -:100AE000DF93EC01F62E08811981C8010E940404ED -:100AF0008F1521F06F2DC8010E94091988819981F5 -:100B0000019699838883DF91CF911F910F91FF9078 -:100B10000895AF92BF92CF92DF92EF92FF920F9320 -:100B20001F93CF93DF93EC015A0169010E946C057A -:100B30006A2DCE010E946C056B2DCE010E946C05C2 -:100B40006C2DCE010E946C056D2DCE010E946C05AE -:100B5000602FCE010E946C05612FCE010E946C05B2 -:100B60006E2DCE010E946C056F2DCE01DF91CF91CD -:100B70001F910F91FF90EF90DF90CF90BF90AF90BB -:100B80000C946C052F923F924F925F926F927F92DE -:100B90008F929F92AF92BF92CF92DF92EF92FF928D -:100BA0000F931F93CF93DF93CDB7DEB7A1970FB607 -:100BB000F894DEBF0FBECDBF1C016D8BF8942C01E5 -:100BC000440C551C440C551C5D8E4C8E8201065500 -:100BD0001C4FD801CD90DD90ED90FC90CD82DE824F -:100BE000EF82F886F201EE59FC4F4080518062801E -:100BF00073804E8E5F8E68A279A2EC8DFD8DEF54CE -:100C0000FB4FC080D180E280F380C982DA82EB8220 -:100C1000FC822C01440C551CF201E357FB4F608110 -:100C20007181FC01E25AFB4F7080EC8DFD8DE25525 -:100C3000FD4FC080D180E280F380CE8ADF8AE88ECB -:100C4000F98E78948E8C9F8CA8A0B9A08D819E81FE -:100C5000AF81B885881A990AAA0ABB0AEC8DFD8D66 -:100C6000EA5EFB4FC080D180E280F38089819A8167 -:100C7000AB81BC818C199D09AE09BF0989879A8710 -:100C8000AB87BC876C017D01C718D108E108F1086A -:100C9000F201EE50FC4F808191812B01481A590AD4 -:100CA0005B8E4A8E5D885110DFC0EC8DFD8DE45F58 -:100CB000FA4F80819181A281B3818D879E87AF8712 -:100CC000B88B40900A0150900B0160900C0170901D -:100CD0000D01498A5A8A6B8A7C8ADA01C901841912 -:100CE0009509A609B7094D845E846F8478888419B4 -:100CF0009509A609B709B7FD47C0809104019091F5 -:100D00000501A0E0B0E0C816D906EA06FB06F8F037 -:100D10008091060190910701A0910801B09109010D -:100D200088159905AA05BB0590F480910E01909154 -:100D30000F01A0911001B091110149885A886B8868 -:100D40007C88481659066A067B0609F051C0F8945B -:100D5000EC8DFD8DE655FC4F208331834283538318 -:100D6000EC8DFD8DEE59FC4F2083318342835383FC -:100D7000789449015A014D805E806F807884841890 -:100D80009508A608B7083FC080910E0190910F0109 -:100D9000A0911001B091110129013A01481A590A94 -:100DA0006A0A7B0AD301C2014D845E846F8478880D -:100DB00084199509A609B709B7FD37C180910401C7 -:100DC00090910501A0E0B0E0C816D906EA06FB063E -:100DD00008F42BC18091060190910701A0910801B0 -:100DE000B091090188159905AA05BB0508F01DC138 -:100DF000F8948E8D9F8DA8A1B9A1F801808391836D -:100E0000A283B3837894F894F101E25AFB4F1082E5 -:100E1000EC8DFD8DE255FD4F1082118212821382FE -:100E20007894EC8DFD8DEA5EFB4F49805A806B8093 -:100E30007C804082518262827382F101EE0FFF1F3B -:100E4000EE50FC4F718360838C8D9D8D845F9A4F33 -:100E5000DC012D933D934D935C931397F101E85F73 -:100E6000FC4F80818F5F808329013A011D893E827A -:100E70002D826EE271E080917E0690917F060E9445 -:100E80005D03ED81FE81EE0FFF1FEA5BF94F60818C -:100E9000718180917E0690917F060E94260E6BE202 -:100EA00071E080917E0690917F060E945D034981EA -:100EB0005A816B817C8180917E0690917F060E9491 -:100EC000D90D68E271E080917E0690917F060E94C4 -:100ED0005D03B701A60180917E0690917F060E9476 -:100EE000D90D66E271E080917E0690917F060E94A6 -:100EF0005D0349855A856B857C8580917E0690913E -:100F00007F060E94D90D63E271E080917E06909188 -:100F10007F060E945D03B501A40180917E06909139 -:100F20007F060E94D90D60E271E080917E0690916B -:100F30007F060E945D03B301A20180917E0690911D -:100F40007F060E94F40D6EE171E080917E06909123 -:100F50007F060E945D0360918804709189048091EE -:100F60007E0690917F060E94DC0D6BE171E080911E -:100F70007E0690917F060E945D036A8D7B8D809135 -:100F80007E0690917F060E94DC0D111113C068E16E -:100F900071E080917E0690917F060E945D03ED8155 -:100FA000FE81E85FFC4F608180917E0690917F0614 -:100FB0000E943E0EC114D104E104F104C1F065E1C8 -:100FC00071E080917E0690917F060E945D036E899C -:100FD0007F89888D998DA70196010E942B19BA01EE -:100FE000A90180917E0690917F060E94D90D809183 -:100FF0007E0690917F06A1960FB6F894DEBF0FBED5 -:10100000CDBFDF91CF911F910F91FF90EF90DF90B7 -:10101000CF90BF90AF909F908F907F906F905F9098 -:101020004F903F902F900C949603A1960FB6F89492 -:10103000DEBF0FBECDBFDF91CF911F910F91FF900B -:10104000EF90DF90CF90BF90AF909F908F907F9068 -:101050006F905F904F903F902F9008952F923F9206 -:101060004F925F926F927F928F929F92AF92BF92B8 -:10107000CF92DF92EF92FF920F931F93CF93DF9364 -:101080006C014A015B018091F90490E0029664E1F1 -:1010900070E00E941719FC0120E030E040E0C4E15C -:1010A000D0E0CF01820F931FBE010E941719DC010F -:1010B000A656BD4F8C9190E08C159D0509F44F5FAD -:1010C0002F5F3F4F2431310561F7442309F4D7C026 -:1010D0001F0163E171E080917E0690917F060E947E -:1010E0005D03F601EE0FFF1FEA5BF94F608171812E -:1010F00080917E0690917F060E94260E61E171E04C -:1011000080917E0690917F060E945D03E12CF12C78 -:1011100021E0C701820D931D64E170E00E94171960 -:101120008C01FC01E656FD4F808190E0C816D9067F -:1011300009F085C0E801CC0FDD1FCC0FDD1F2111A8 -:1011400020C0FE01E65BFD4F80819181A281B381C9 -:10115000FE01E650FE4F4081518162817381840F10 -:10116000951FA61FB71F84159505A605B70508F49A -:101170005FC06EE071E080917E0690917F060E94D4 -:101180005D03F801EE0FFF1FEE52FE4F608171818B -:1011900080917E0690917F060E94DC0D6CE071E0EC -:1011A00080917E0690917F060E945D03FE01E65BC2 -:1011B000FD4F4081518162817381481959096A0943 -:1011C0007B0980917E0690917F060E94F40D6AE073 -:1011D00071E080917E0690917F060E945D03FE0182 -:1011E000E650FE4F408151816281738180917E067D -:1011F00090917F060E94D90D68E071E080917E0693 -:1012000090917F060E945D03F801E254FE4F6081D9 -:1012100080917E0690917F060E943E0EF801E65571 -:10122000FE4F608180917E0690917F060E9456035A -:10123000C65BDD4F488059806A807B8020E08FEF5D -:10124000E81AF80A84E1E816F10409F062CF809107 -:101250007E0690917F06DF91CF911F910F91FF90B5 -:10126000EF90DF90CF90BF90AF909F908F907F9046 -:101270006F905F904F903F902F900C949603DF916A -:10128000CF911F910F91FF90EF90DF90CF90BF9083 -:10129000AF909F908F907F906F905F904F903F9016 -:1012A0002F9008950F931F93CF93DF93EC0166E087 -:1012B00071E080917E0690917F060E945D038E0111 -:1012C000000F111FF801EA5BF94F60817181809175 -:1012D0007E0690917F060E94260EFE01EF5CF94F7C -:1012E000208180917E0690917F06222329F0213073 -:1012F00031F46EEF70E005C065EF70E002C062EFA0 -:1013000070E00E945D03C15ED94F8881882341F05F -:101310006AEE70E080917E0690917F060E945D03E8 -:1013200064EE70E080917E0690917F060E945D03DE -:10133000F801E550FA4F6081718180917E069091AD -:101340007F06DF91CF911F910F910C94DC0D62EE1F -:1013500070E080917E0690917F060E945D0360910F -:1013600002017091030180917E0690917F060E9498 -:10137000260E60EE70E080917E0690917F060E94BE -:101380005D03609100017091010180917E06909152 -:101390007F060C942C0ECF92DF92EF92FF926EEDAF -:1013A00070E080917E0690917F060E945D036091BF -:1013B0000E0170910F01809110019091110128EEA2 -:1013C000C22E23E0D22EE12CF12CA70196010E941F -:1013D0002B19BA01A90180917E0690917F060E9487 -:1013E000D90D6CED70E080917E0690917F060E9491 -:1013F0005D0360910A0170910B0180910C01909145 -:101400000D01A70196010E942B19BA01A901809133 -:101410007E0690917F060E94D90D6AED70E0809162 -:101420007E0690917F060E945D0360910601709197 -:1014300007018091080190910901A70196010E947E -:101440002B19BA01A90180917E0690917F060E9416 -:10145000D90D68ED70E080917E0690917F060E9424 -:101460005D03609104017091050180917E06909169 -:101470007F06FF90EF90DF90CF900C94E20D0F93DA -:101480001F93CF93DF93C6ECD0E0FE01649180916F -:101490007E0690917F060E9456032196F0E0C73D9C -:1014A000DF0799F761EC70E080917E0690917F06EE -:1014B0000E945D03CCEBD0E0FE01649180917E063A -:1014C00090917F060E9456032196F0E0C03CDF0712 -:1014D00099F761EB70E080917E0690917F060E9403 -:1014E0005D03CCE9D0E000EB10E0FE016491809157 -:1014F0007E0690917F060E94560321960C171D07C9 -:10150000A1F7DF91CF911F910F910895CF92DF92B4 -:10151000EF92FF920F931F93CF93DF930E949E0F42 -:101520006B017C0180917E0690917F060E9496035C -:101530000E943F0A67E471E080917E0690917F06E9 -:101540000E945D0306E611E0C0E081E0F801219110 -:1015500031918F0137FD12C0811108C065E471E03F -:1015600080917E0690917F060E945D036C2F809192 -:101570007E0690917F060E943E0E80E0CF5FC631CE -:1015800029F76AE371E080917E0690917F060E94C0 -:101590005D0367E371E080917E0690917F060E9473 -:1015A0005D03B701A60180917E0690917F060E949F -:1015B000D90D65E371E080917E0690917F060E94CF -:1015C0005D03609188047091890480917E069091FA -:1015D0007F060E94DC0D62E371E080917E069091AF -:1015E0007F060E945D0340918404509185046091C0 -:1015F00086047091870480917E0690917F060E94F8 -:10160000D90D60E371E080917E0690917F060E9483 -:101610005D03609182047091830480917E069091B5 -:101620007F060E94DC0D80917E0690917F060E94CD -:1016300096030E94CB090E94A70906E416E0C0E0C9 -:10164000D0E0F801819191918F0197FD09C0CE0101 -:101650000E94520980917E0690917F060E94960317 -:101660002196C231D10569F7DF91CF911F910F917A -:10167000FF90EF90DF90CF900895CF93DF93C4E970 -:10168000D0E0FE01649180917E0690917F060E94D9 -:1016900056032196F0E0CB39DF0799F7DF91CF9120 -:1016A00008954F925F926F927F928F929F92AF9226 -:1016B000BF92DF92EF92FF920F931F93CF93DF932E -:1016C000D62E682F70E08B01000F111F000F111F25 -:1016D000F801E65EFC4F80809180A280B3802901F2 -:1016E0003A01481859086A087B0853014201909151 -:1016F000F9049F5F9093F9049091F904943110F0EC -:101700001092F904E091F904F0E0A091A801B091E1 -:10171000A901ED012196D093A901C093A801EE0F74 -:10172000FF1FEE52FE4FB183A083E091F904F0E079 -:10173000E656FD4F8083E091F904C801865E9C4F18 -:101740007C01EC0188819981AA81BB81D4E0ED9F65 -:10175000F0011124E65BFD4F80839183A283B38364 -:10176000E091F90484E0E89FF0011124E650FE4F77 -:1017700080829182A282B382A091F904B0E0FB0141 -:10178000EA50FD4F8081A254BE4F8C93CB01880F4D -:10179000991FDC01A550BA4F0D90BC91A02D2D01D1 -:1017A000612C712CEB01CF5CD94F84149504A604F5 -:1017B000B704B0F4D7012D933D934D935C931397E9 -:1017C00070816881761372C0DC01A357BB4F8D9185 -:1017D0009C911197019611969C938E9362E567C038 -:1017E000A081B881CB018A569E4FAB135AC0EC0141 -:1017F000B881AB1709F442C0D801AF54BB4F4D902C -:101800005D906D907C901397DFEF4D1A5D0A6D0A25 -:101810007D0A4D925D926D927C921397D801AE59DC -:10182000BC4F2D933D934D935C931397DB01A059CF -:10183000BB4FCC91C1110FC0E801C655DC4F2883C6 -:1018400039834A835B83C1E0CC93DB01A25ABB4F4F -:101850006C916F5F6C93D801A255BD4F4D905D9018 -:101860006D907C901397840C951CA61CB71C8D92D0 -:101870009D92AD92BC92139763E414C0D801A25517 -:10188000BD4F4D905D906D907C901397840C951C8E -:10189000A61CB71C8D929D92AD92BC92139760E5E9 -:1018A00001C067E47081DC017C9301C068E5A09110 -:1018B000F904B0E0A655BE4F6C93D801A65EBC4FAC -:1018C0002D933D934D935C931397D082DF91CF91ED -:1018D0001F910F91FF90EF90DF90BF90AF909F907E -:1018E0008F907F906F905F904F900895BF92CF92AE -:1018F000DF92EF92FF920F931F93CF93DF93C82F46 -:101900000E949E0F6B017C018C2F90E0FC01EE0F7A -:10191000FF1FE254FC4FA591B4910C91DC01AD5B2B -:10192000B94F1C91FC01EE0FFF1FE65DFE4F0190C9 -:10193000F081E02D2081102712230C93112339F11F -:10194000FC01EB5EFE4FC081D1E0FC01EE5EFE4F7C -:10195000B080BC16E0F08D2F8123B1F0EC2FF0E0C9 -:10196000EE0FFF1FEA59FE4F0190F081E02D1E1689 -:101970001F0654F461E09D2F902309F460E0A70155 -:1019800096018E2F0E94510BDD0FCF5FE2CFDF91CA -:10199000CF911F910F91FF90EF90DF90CF90BF906C -:1019A00008951F920F920FB60F9211242F933F9319 -:1019B0004F935F936F937F938F939F93AF93BF9357 -:1019C000EF93FF9382E00E94760CFF91EF91BF911D -:1019D000AF919F918F917F916F915F914F913F91C7 -:1019E0002F910F900FBE0F901F9018951F920F927E -:1019F0000FB60F9211242F933F934F935F936F93E2 -:101A00007F938F939F93AF93BF93EF93FF9381E067 -:101A10000E94760CFF91EF91BF91AF919F918F91B2 -:101A20007F916F915F914F913F912F910F900FBEDA -:101A30000F901F9018951F920F920FB60F921124BE -:101A40002F933F934F935F936F937F938F939F93C6 -:101A5000AF93BF93EF93FF9380E00E94760CFF91CA -:101A6000EF91BF91AF919F918F917F916F915F9116 -:101A70004F913F912F910F900FBE0F901F9018958F -:101A80000E9476198F929F92AF92BF92EF92FF922F -:101A90000F931F93CF93DF93CDB7DEB7A1970FB608 -:101AA000F894DEBF0FBECDBF7C01FA01CB0119A2B5 -:101AB000223008F42AE08E010F5D1F4F822E912CF8 -:101AC000A12CB12CBF01A50194010E942B19F90191 -:101AD000CA01015011096A3014F4605D01C0695CEB -:101AE000D8016C93232B242B252B61F7B801C70158 -:101AF0000E948103A1960FB6F894DEBF0FBECDBF42 -:101B0000DF91CF911F910F91FF90EF90BF90AF9019 -:101B10009F908F900895833081F028F4813099F060 -:101B20008230A1F008958730A9F08830B9F0843070 -:101B3000D1F4809180008F7D03C0809180008F77E9 -:101B400080938000089584B58F7702C084B58F7D1F -:101B500084BD08958091B0008F7703C08091B0005C -:101B60008F7D8093B00008953FB7F89480918506EB -:101B700090918606A0918706B091880626B5A89B0D -:101B800005C02F3F19F00196A11DB11D3FBFBA2F0F -:101B9000A92F982F8827820F911DA11DB11DBC016F -:101BA000CD0142E0660F771F881F991F4A95D1F734 -:101BB00008952AE00C94420DAB0160E070E02AE049 -:101BC0000C94420D0F931F93CF93DF93EC010E946F -:101BD000DC0D8C01CE010E949603800F911FDF91D6 -:101BE000CF911F910F910895CF92DF92EF92FF92C4 -:101BF0000F931F93CF93DF9377FF1DC06A017B0183 -:101C0000EC016DE20E9456038C0144275527BA016E -:101C10004C195D096E097F092AE0CE010E94420D30 -:101C2000800F911FDF91CF911F910F91FF90EF9047 -:101C3000DF90CF9008952AE0DF91CF911F910F910F -:101C4000FF90EF90DF90CF900C94420DAB01770F97 -:101C5000660B770B0C94F40D0F931F93CF93DF93C8 -:101C6000EC010E94260E8C01CE010E949603800F8B -:101C7000911FDF91CF911F910F910895462F50E052 -:101C800060E070E02AE00C94420D823058F40E942B -:101C90003D0B61E174E080917E0690917F060E9489 -:101CA0005D0353C080916A0690916B069C012150A0 -:101CB00031092F3F334038F00E943D0B60916A0696 -:101CC00070916B0614C09093030180930201809180 -:101CD0006C0690916D069C01215031092F3F3340D5 -:101CE00060F00E943D0B60916C0670916D068091D2 -:101CF0007E0690917F060C94E20D909301018093F3 -:101D0000000165EF73E080917E0690917F060E944E -:101D10005D0360916A0670916B0680917E069091DA -:101D20007F060E94DC0D63EF73E080917E06909148 -:101D30007F060E945D0360916C0670916D06809134 -:101D40007E0690917F060E94DC0D80917E06909128 -:101D50007F060C9496030F931F93CF93DF93EC01B0 -:101D60000E943E0E8C01CE010E949603800F911FAF -:101D7000DF91CF911F910F91089587E480937C00AC -:101D800080917A00806480937A0080917A0086FD49 -:101D9000FCCF809178002091790090E0922B0895FB -:101DA000CF93DF93282F30E0F901EB5AFC4F849159 -:101DB000F901E759FC4FD491F901EC53FC4FC49160 -:101DC000CC2391F081110E948B0DEC2FF0E0EE0FEF -:101DD000FF1FE654FC4FA591B491EC91ED2381E0F7 -:101DE00090E021F480E002C080E090E0DF91CF91AC -:101DF00008951F93CF93DF93282F30E0F901EB5A1A -:101E0000FC4F8491F901E759FC4FD491F901EC534F -:101E1000FC4FC491CC23C1F0162F81110E948B0D71 -:101E2000EC2FF0E0EE0FFF1FE55BFC4FA591B491A6 -:101E30009FB7F894111104C08C91D095D82302C09B -:101E4000EC91DE2BDC939FBFDF91CF911F91089522 -:101E5000CF93DF9390E0FC01E759FC4F2491FC0104 -:101E6000EC53FC4F8491882361F190E0880F991F17 -:101E7000FC01EF5BFC4FC591D491FC01E55BFC4F8D -:101E8000A591B491611109C09FB7F89488812095FC -:101E900082238883EC912E230BC0623061F49FB7BC -:101EA000F8948881322F309583238883EC912E2BF0 -:101EB0002C939FBF06C08FB7F894E8812E2B288300 -:101EC0008FBFDF91CF9108958F929F92AF92BF9273 -:101ED000CF92DF92EF92FF926B017C010E94B40DD2 -:101EE0004B015C01C114D104E104F104F1F00E9442 -:101EF000B40DDC01CB0188199909AA09BB09883EF8 -:101F00009340A105B10570F321E0C21AD108E108A0 -:101F1000F10888EE880E83E0981EA11CB11CC11444 -:101F2000D104E104F10419F7DDCFFF90EF90DF90C9 -:101F3000CF90BF90AF909F908F9008952FB7F89457 -:101F4000609181067091820680918306909184064B -:101F50002FBF0895CF92DF92EF92FF920F931F93BE -:101F6000843058F40E943D0B60E374E080917E065B -:101F700090917F060E945D03DFC020916A06309138 -:101F80006B06C901019780319E4038F00E943D0BDD -:101F900060916A0670916B0677C0A8EEB3E00E946C -:101FA00053196B017C0160930E0170930F018093B4 -:101FB0001001909311010E949E0FDC01CB018C0D4A -:101FC0009D1DAE1DBF1D00917A0610917B062091CC -:101FD0007C0630917D0680179107A207B30780F435 -:101FE0000E949E0FDC01CB018C0D9D1DAE1DBF1DFF -:101FF00080937A0690937B06A0937C06B0937D062F -:1020000020916C0630916D06C901019780319E4088 -:1020100038F00E943D0B60916C0670916D0634C0E3 -:10202000A8EEB3E00E94531960930A0170930B016C -:1020300080930C0190930D0120916E0630916F06F4 -:1020400021318EE0380738F00E943D0B60916E061A -:1020500070916F0619C0A8EEB3E00E945319609307 -:102060000601709307018093080190930901809104 -:102070007006909171068536910590F00E943D0B97 -:10208000609170067091710680917E0690917F0636 -:102090001F910F91FF90EF90DF90CF900C94E20D85 -:1020A00090930501809304016CE174E080917E06B9 -:1020B00090917F060E945D0360916A0670916B06A5 -:1020C00080917E0690917F060E94DC0D6AE174E0AB -:1020D00080917E0690917F060E945D0360916C0660 -:1020E00070916D0680917E0690917F060E94DC0DB6 -:1020F00068E174E080917E0690917F060E945D0306 -:1021000060916E0670916F0680917E0690917F06B9 -:102110000E94DC0D66E174E080917E0690917F065E -:102120000E945D03609170067091710680917E0639 -:1021300090917F060E94DC0D80917E0690917F0633 -:102140001F910F91FF90EF90DF90CF900C9496032A -:102150007F928F929F92AF92BF92CF92DF92EF9237 -:10216000FF920F931F93CF93DF93782E0E949E0FC1 -:10217000AB01BC01C0906A06D0906B0685E18C155E -:1021800058F05601BB24F501EE0FFF1FEA59FE4F30 -:102190000081118117FF1EC00E943D0B68ED73E0A6 -:1021A00080917E0690917F060E945D036C2D809148 -:1021B0007E0690917F06DF91CF911F910F91FF9046 -:1021C000EF90DF90CF90BF90AF909F908F907F90D7 -:1021D0000C94AB0E11277801EE0CFF1CF701EE5B9F -:1021E000FE4FC081D181F701EA5BF94F4F01808139 -:1021F0009181A816B906D1F0FE01FF27EC53FC4FE0 -:10220000E491E2508E2F90E0FC01EE0FFF1FE254AC -:10221000FC4FA591B4912C91FC01ED5BF94F20830B -:10222000C8010E940604F401B182A08280916C066C -:1022300090916D060297029788F00E943D0B6FE81F -:1022400073E080917E0690917F060E945D036C2D65 -:1022500080917E0690917F060E94AB0EF801EF5CA4 -:10226000F94F81E020916C0630916D0623303105E5 -:1022700009F080E08083C801805D9E4F6C01F80109 -:10228000E15EF94F5F01F2E0F71590F480916E0680 -:1022900090916F06892B61F0F6018081811108C051 -:1022A00062E08C2F0E94280F81E0F501808306C038 -:1022B00060E08C2F0E94280FF5011082F3E0F715E3 -:1022C00030F48091700690917106009711F482E0CD -:1022D00090E0F701E550FA4F91838083F601808109 -:1022E000811123C0DD27FE01E759FC4F8491FE01D7 -:1022F000EC53FC4FC491CC23B1F1C250AC2FB0E0F1 -:10230000AA0FBB1FA65DBE4F0D90BC91A02DEC91F6 -:10231000E82BEC932091680081E090E001C0880FE9 -:10232000CA95EAF7822B809368006DE773E080918D -:102330007E0690917F060E945D03C8010E945209AB -:1023400080917E0690917F06DF91CF911F910F9132 -:10235000FF90EF90DF90CF90BF90AF909F908F90C5 -:102360007F900C9496030E943D0B68E873E0809187 -:102370007E0690917F06DF91CF911F910F91FF9084 -:10238000EF90DF90CF90BF90AF909F908F907F9015 -:102390000C949A031F920F920FB60F9211242F9351 -:1023A0003F938F939F93AF93BF93809181069091BA -:1023B0008206A0918306B09184063091800623E0C6 -:1023C000230F2D3720F40196A11DB11D05C026E86D -:1023D000230F0296A11DB11D2093800680938106D4 -:1023E00090938206A0938306B0938406809185061D -:1023F00090918606A0918706B09188060196A11D4E -:10240000B11D8093850690938606A0938706B093AE -:102410008806BF91AF919F918F913F912F910F901F -:102420000FBE0F901F901895E5E5F5E0138212821C -:1024300088EE93E0A0E0B0E084839583A683B78321 -:102440008CE191E09183808385EC90E0958784878F -:1024500084EC90E09787868780EC90E0918B808B6E -:1024600081EC90E0938B828B82EC90E0958B848B57 -:1024700086EC90E0978B868B118E128E138E148EC5 -:102480000895CF93DF93CDB7DEB727970FB6F894B3 -:10249000DEBF0FBECDBF789484B5826084BD84B5A5 -:1024A000816084BD85B5826085BD85B5816085BD4F -:1024B00080916E00816080936E0010928100809107 -:1024C000810082608093810080918100816080938F -:1024D0008100809180008160809380008091B100B4 -:1024E00084608093B1008091B00081608093B000DF -:1024F00080917A00846080937A0080917A00826073 -:1025000080937A0080917A00816080937A00809134 -:102510007A00806880937A001092C100E09165058E -:10252000F091660582E08083E0916105F09162059B -:102530001082E0916305F091640583E3808310923B -:102540006D05E0916905F0916A0586E08083E09170 -:102550006705F0916805808180618083E09167055F -:10256000F0916805808188608083E0916705F09133 -:102570006805808180688083E0916705F091680537 -:1025800080818F7D808364EF71E080E090E00E9425 -:10259000640F789485E595E00E94960385E595E0C3 -:1025A00090937F0680937E0610928904109288048F -:1025B0000E949E0F6093F2057093F3058093F405DB -:1025C0009093F5050E946D0461E082E00E94280F5F -:1025D00061E08CE00E94280F0E94860A28EE222EDD -:1025E00023E0322E412C512C0E949E0F0091F205C7 -:1025F0001091F3052091F4053091F50560177107EE -:102600008207930750F420918804309189042F5F4A -:102610003F4F30938904209388046093F2057093B0 -:10262000F3058093F4059093F50585E595E00E9408 -:10263000A502892B09F49BC285E595E00E948302DF -:10264000D82E9CE2C92EDC1019C0E0918C04E73032 -:1026500008F08DC281E08E0F80938C04F0E0EE0FC5 -:10266000FF1FE659F94F80918A0490918B04918362 -:10267000808310928B0410928A0479C280ED8D0DB4 -:102680008A30A8F420918A0430918B040AE0E02E6D -:10269000E29EC001E39E900D1124C0978D0D911D07 -:1026A000D7FC9A9590938B0480938A0460C28FE93B -:1026B0008D0D8A3108F05BC2809154058823F1F1B9 -:1026C0006BEB72E085E595E00E945D031AE6E12E72 -:1026D00016E0F12E00E010E080918C0490E080176D -:1026E0009107A4F00115110531F069EB72E085E561 -:1026F00095E00E945D03F701619171917F0185E58D -:1027000095E00E94DC0D0F5F1F4FE6CF6D2D85E534 -:1027100095E00E94560362EB72E085E595E00E9429 -:102720005D0360918C0470E06F5F7F4F85E595E0FD -:102730000E94260E85E595E00E9496038D2DDD0C06 -:10274000990BAA0BBB0BFC01E156F109E631F1052F -:1027500008F0FFC1EC5CFF4F0C944D1980918C0484 -:10276000E82FF0E0EE0FFF1FE659F94F20918A04A1 -:1027700030918B04318320838F5F0E94A810E9C1C0 -:1027800080918C04E82FF0E0EE0FFF1FE659F94F1F -:1027900020918A0430918B043183208300916A0652 -:1027A00010916B068F3F61F0063150F4F801FF275E -:1027B000EE0FFF1FEA59FE4F8081918197FF05C000 -:1027C0000E943D0B6CE572E0EDC39927FC01E05DD2 -:1027D000FE4F20812111DFC3FC01EE0FFF1FEE5BD6 -:1027E000FE4F208131813327F901E759FC4F449195 -:1027F000F901EC53FC4F2491222309F4C3C3225066 -:10280000E22FF0E0EE0FFF1FE65DFE4FA081B181E9 -:102810003C91409543234C933C913111BCC3609152 -:10282000680041E050E06A0102C0CC0CDD1C2A9532 -:10283000E2F796012095262320936800ACC31A8204 -:10284000198263E4CE0101960E946C0566E6CE0112 -:1028500001960E946C0567E6CE0101960E946C0508 -:1028600036E4632E36E0732EF30181E0219131913D -:1028700037FF8F5F06E0EA36F007C1F78F5F8B8383 -:10288000682FCE0101960E946C05E0900401F09043 -:1028900005016091060170910701809108019091F6 -:1028A0000901A20191010E942B192C833D834E83C3 -:1028B0005F8360910A0170910B0180910C019091EE -:1028C0000D01A20191010E942B1949015A01609149 -:1028D0000E0170910F018091100190911101A201E0 -:1028E00091010E942B19BA01A9010C811D8194014B -:1028F00069E4CE0101960E94890520910001309182 -:1029000001014091020150910301E12CF12C00E002 -:1029100010E064E5CE0101960E9489058BEF882EB8 -:1029200085E0982E9FE1A92E96E0B92E21E3C22ED4 -:1029300026E0D22EF301419151913F0157FD14C081 -:10294000F401E080F180F501008110E0F601808162 -:10295000811103C022E030E002C023E030E061E4F6 -:10296000CE0101960E948905F2E08F0E911C0FEFB7 -:10297000A01AB00A1FEFC11AD10A2AE6621626E091 -:102980007206C1F66BE372E085E595E00E945D0397 -:102990006B8185E595E00E943E0E68E372E085E577 -:1029A00095E00E945D0369817A8185E595E00E944A -:1029B0002C0ECFC00E94860ACCC080918C04E82FD8 -:1029C000F0E0EE0FFF1FE659F94F20918A04309195 -:1029D0008B04318320838F5F0E94AA0FBAC080913D -:1029E0008C04E82FF0E0EE0FFF1FE659F94F20911D -:1029F0008A0430918B043183208320916A063091C0 -:102A00006B062130310509F0A4C08F3F09F4A1C045 -:102A100062E372E0BBC20E946D049BC00E949E0FE5 -:102A20004B015C0167E272E080917E0690917F0627 -:102A30000E945D030E943F0A80917E0690917F066E -:102A40000E9496030E94CB090E94A70965E272E0EA -:102A500080917E0690917F060E945D0360915405EF -:102A600080917E0690917F060E94AB0EB6E4EB2E1D -:102A7000B6E0FB2E00E010E0F701819191917F011B -:102A800097FD16C0C8010E94520962E272E080916F -:102A90007E0690917F060E945D03A501940161E08E -:102AA000C8010E94C205B501A401C8010E942E08F8 -:102AB0000F5F1F4F02311105F9F680E090E00E9490 -:102AC000040400917E0610917F06833409F065C3EB -:102AD00081E090E00E940404863609F05EC382E043 -:102AE00090E00E940404873609F057C369C28091C0 -:102AF0008C04E82FF0E0EE0FFF1FE659F94F20910C -:102B00008A0430918B04318320838F5F0E94450EAD -:102B100020C020918A0430918B0480917E06909190 -:102B20007F062F3F310588F4209354056DE972E04C -:102B30000E945D0360918A0470918B0480917E06EF -:102B400090917F060E94E20D04C067E772E00E9448 -:102B50009A0310928C0410928B0410928A04EAE675 -:102B6000F6E01192119216E0EA37F107D1F7809161 -:102B700068069091690697FDDEC060E082E00E94E1 -:102B8000F90E6AE070E080E090E00E94640F0E941D -:102B9000BD0E8C019093FA058093F90561E082E007 -:102BA0000E94F90E6AE070E080E090E00E94640FFD -:102BB0000E94BD0E9093F8058093F705801B910B42 -:102BC00020910001309101012817390754F020911C -:102BD000020130910301821793070CF090C010E0BE -:102BE00001C011E08091F605811709F488C01093A7 -:102BF000F605612F8CE00E94F90E0E949E0F9B014A -:102C0000AC01612F81E10E94510B809154058A3003 -:102C100008F475C066EF72E080917E0690917F06A1 -:102C20000E945D0365E170E080917E0690917F06D1 -:102C30000E94260E69EE72E080917E0690917F06DA -:102C40000E945D0365E170E080917E0690917F06B1 -:102C50000E94260E65EE72E080917E0690917F06BE -:102C60000E945D0360EE72E080917E0690917F0687 -:102C70000E945D03612F80917E0690917F060E94E5 -:102C80003E0E65ED72E080917E0690917F060E9477 -:102C90005D036091F90480917E0690917F060E9409 -:102CA0003E0E6CEC72E080917E0690917F060E9451 -:102CB0005D034091F5045091F6046091F704709122 -:102CC000F80480917E0690917F060E94D90D62ECF7 -:102CD00072E080917E0690917F060E945D03609174 -:102CE000AF047091B00480917E0690917F060E949F -:102CF000DC0D80917E0690917F060E949603809164 -:102D000054058431C0F06FE373E080917E069091AA -:102D10007F060E945D036091F7057091F805809130 -:102D2000F9059091FA05681B790B80917E069091C8 -:102D30007F060E942C0E809154058A3030F50E9447 -:102D40009E0F00917A0610917B0620917C063091AF -:102D50007D066B017C01C01AD10AE20AF30AF7FE74 -:102D6000D6C000910A0110910B0120910C01309105 -:102D70000D016B017C01C01AD10AE20AF30AE6E4F4 -:102D8000F6E0ACE0B5E040E0BBC036E4A32E36E0B0 -:102D9000B32E4AEFE42E44E0F42E52E4C52E51E067 -:102DA000D52E00E010E0F501819091905F0118149C -:102DB00019040CF085C0F6016080862D0E94D00EAB -:102DC000782EF7018081781609F47AC0708268E362 -:102DD00073E080917E0690917F060E945D03B401AE -:102DE00080917E0690917F060E94260E6BE273E032 -:102DF00080917E0690917F060E945D03662D8091F2 -:102E00007E0690917F060E943E0E68E273E08091FC -:102E10007E0690917F060E945D036BE173E08091D6 -:102E20007E0690917F060E945D03672D80917E064D -:102E300090917F060E943E0E60E173E080917E06D5 -:102E400090917F060E945D036091F90480917E0657 -:102E500090917F060E943E0E67E073E080917E06AF -:102E600090917F060E945D03F801EE0FFF1FEE0FA9 -:102E7000FF1FEF54FB4F408151816281738180912C -:102E80007E0690917F060E94D90D6DEF72E08091D1 -:102E90007E0690917F060E945D03F801EE0FFF1FF2 -:102EA000E357FB4F6081718180917E0690917F0690 -:102EB0000E94DC0D80917E0690917F060E94960311 -:102EC0000F5F1F4FFFEFEF1AFF0A22E0C20ED11C67 -:102ED0000231110509F067CF32CF0D911D912D916F -:102EE0003C911397C701B601601B710B820B930BCA -:102EF00097FF41E014968AE696E08E179F0729F027 -:102F00002191319137FDF6CFE8CF4423C1F10E94E2 -:102F10009E0F4B015C0186E4E82E86E0F82E00E06F -:102F200010E0F701819191917F0197FD0FC0A501FC -:102F3000940160E0C8010E94C2058091540585306B -:102F400028F0B501A401C8010E942E080F5F1F4F91 -:102F50000231110531F780910E0190910F01A0917E -:102F60001001B0911101880E991EAA1EBB1E8092FD -:102F70007A0690927B06A0927C06B0927D060E9413 -:102F80004C0332CB0E943D0B65E572E080917E06DA -:102F900090917F06DCCD40E050E0BA010E9406042B -:102FA0006AE472E080917E0690917F060E945D0344 -:102FB000602F80917E0690917F060E94AB0EC9CD56 -:102FC00083E090E00E940404682EF4E1F81718F4FE -:102FD00061ED71E0E4C0C8010E9496036FEB71E0FF -:102FE00080917E0690917F060E945D0380E090E0D4 -:102FF0000E940404682F80917E0690917F060E94B3 -:10300000560381E090E00E940404682F80917E06C0 -:1030100090917F060E94560382E090E00E94040493 -:10302000682F80917E0690917F060E94560366EB82 -:1030300071E080917E0690917F060E945D0383E09F -:1030400090E00E940404682F70E080917E069091C9 -:103050007F060E94260E80917E0690917F060E9438 -:103060009603712C04E010E0671409F49BC0C801BA -:103070000E9404048B83C80101960E940404F82E68 -:10308000C80102960E940404882E912C982C882452 -:103090008F0C911CC80103960E940404F82EC801ED -:1030A00004960E940404A82EB12CBA2CAA24AF0CBA -:1030B000B11CC80105960E940404F82EC8010696AA -:1030C0000E940404C82ED12CDC2CCC24CF0CD11CA3 -:1030D000C80107960E940404F82EC80108960E94B1 -:1030E000040490E0982F88279C012F0D311D790151 -:1030F000075F1F4F6DEA71E080917E0690917F0619 -:103100000E945D036B8180917E0690917F060E94F4 -:1031100056036BEA71E080917E0690917F060E94D3 -:103120005D03B40180917E0690917F060E94DC0DC4 -:1031300069EA71E080917E0690917F060E945D03AE -:10314000B50180917E0690917F060E94DC0D67EAB2 -:1031500071E080917E0690917F060E945D03B6012A -:1031600080917E0690917F060E94DC0D65EA71E0F9 -:1031700080917E0690917F060E945D03B701809149 -:103180007E0690917F060E94DC0D80917E069091D4 -:103190007F060E949603739467CF6CEE71E0C801BE -:1031A0000E949A0360E172E080917E0690917F0612 -:1031B0000E945D03C0907A06D0907B06E0907C066A -:1031C000F0907D060E949E0F97018601061B170B4B -:1031D000280B390BB901A80180917E0690917F06DA -:1031E0000E94D90D62E072E080917E0690917F0688 -:1031F0000E945D0380917E0690917F060E94960357 -:10320000A8CCF999FECF92BD81BDF89A992780B5D7 -:103210000895262FF999FECF1FBA92BD81BD20BD1A -:103220000FB6F894FA9AF99A0FBE0196089597FB93 -:10323000072E16F4009407D077FD09D00E9462197A -:1032400007FC05D03EF4909581959F4F08957095A9 -:1032500061957F4F0895A1E21A2EAA1BBB1BFD01A9 -:103260000DC0AA1FBB1FEE1FFF1FA217B307E40765 -:10327000F50720F0A21BB30BE40BF50B661F771FBD -:10328000881F991F1A9469F760957095809590959D -:103290009B01AC01BD01CF010895EE0FFF1F05900A -:1032A000F491E02D0994A29FB001B39FC001A39FA8 -:1032B000700D811D1124911DB29F700D811D11246F -:1032C000911D0895AA1BBB1B51E107C0AA1FBB1F7C -:1032D000A617B70710F0A61BB70B881F991F5A95A2 -:1032E000A9F780959095BC01CD01089581E090E00B -:0A32F000F8940C947B19F894FFCFBA -:1032FA006E0064000200D007000060EA000030752A -:10330A0000000D1307080E0000000000DD0246024F -:10331A0074022A03A502830297026B006C006D00F7 -:10332A000000000000000000000000000000000093 -:10333A000001030004000500060007000800090058 -:10334A000A000B000C000E000F0010001100120002 -:10335A00130014001500FFFFFFFFFFFF000001002C -:10336A000200030004000500060007000800FFFF32 -:10337A0009000A000B000C000D000E000F001000DF -:06338A0011000D0A000015 +:1001300056002C002042002C0020540020617661E3 +:10014000696C61626C65002C002048656C6C6F2CDA +:100150002070696E7320004D20726573746F726930 +:100160006E6720636F6E6669672066726F6D2045EB +:100170004550524F4D004D20696C6C6567616C2095 +:10018000636F6E66696720696E20454550524F4D1A +:10019000004D206E6F20636F6E66696720696E2068 +:1001A000454550524F4D002C002C002C0020004D96 +:1001B00020536C6F743A200020536C6F74733A2094 +:1001C000004D20454550524F4D20436F6E66696784 +:1001D0003A20004D20696C6C6567616C20636F6E1E +:1001E00066696720696E20454550524F4D004D208D +:1001F0006E6F20636F6E66696720696E204545509B +:10020000524F4D00206D696C6C697365636F6E644D +:1002100073004D204E657874207265706F72742083 +:10022000696E20002C200056004D205374617475B7 +:10023000733A2000616C697665002C2000636F6E54 +:100240006669672073617665642C20004D207265B5 +:100250006D6F766564200052656D496E7400496C5F +:100260006C6567616C2070696E2073706563696688 +:1002700069636174696F6E20004D20696C6C6567FD +:10028000616C2076616C75652070617373656420A4 +:10029000666F7220646576566572626F7365004D95 +:1002A00020646576566572626F736520736574208D +:1002B000746F20002073697A6520002C0044206749 +:1002C0006F742000202072656A656374200020200E +:1002D000636F756E742000202068697374496478B8 +:1002E000200020746F2000202920002C20696E74CB +:1002F00065726E616C20002028696E64657820004C +:100300004D2070696E200020616E642074726967F0 +:100310006765725374617465004D2063616C6C6530 +:100320006420646F436F756E742077697468207001 +:10033000696E496E64657820002C2072656A656379 +:100340007420002C20636F756E7420002C20686967 +:1003500073744964782000206368616E6765642067 +:10036000746F200029002028696E7465726E616CBC +:1003700020004D2070696E20002D3E002C004C00A6 +:1003800000000000240027002A00000000002500D3 +:1003900028002B00000000080002010000030407F1 +:1003A000000000000000000001020408102040804E +:1003B0000102040810200102040810204D20646589 +:1003C00066696E65642000416464496E7400496C1E +:1003D0006C6567616C2070756C7365206C65766503 +:1003E0006C2073706563696669636174696F6E2000 +:1003F000666F722070696E200000000000230026E6 +:100400000029000404040404040404020202020299 +:1004100002030303030303496C6C6567616C20707E +:10042000696E2073706563696669636174696F6E74 +:10043000200020004D20616E616C6F67207468722F +:100440006573686F6C64732073657420746F20002B +:1004500073697A65002000200020004D20696E74C9 +:10046000657276616C732073657420746F200073FD +:10047000697A6500031211241FBECFEFD8E0DEBFFA +:10048000CDBF11E0A0E0B1E0E6EBF3E302C00590E0 +:100490000D92A639B107D9F726E0A6E9B1E001C06F +:1004A0001D92A938B207E1F712E0CBE3D2E004C015 +:1004B0002197FE010E94AB19CA33D107C9F70E94E8 +:1004C00030120C94D9190C940000CF92DF92EF9265 +:1004D000FF920F931F93CF93DF936C017A018B01EF +:1004E000C0E0D0E0CE15DF0581F0D8016D918D011F +:1004F000D601ED91FC910190F081E02DC6010995A6 +:10050000892B11F02196EECF7E01C701DF91CF91AB +:100510001F910F91FF90EF90DF90CF900895089575 +:1005200080E090E00895FC01538D448D252F30E04C +:10053000842F90E0821B930B541710F0CF960895F0 +:1005400001970895FC01918D828D981761F0828D3D +:10055000DF01A80FB11D5D968C91928D9F5F9F73F7 +:10056000928F90E008958FEF9FEF0895FC01918D99 +:10057000828D981731F0828DE80FF11D858D90E006 +:1005800008958FEF9FEF0895FC01918D228D892FA3 +:1005900090E0805C9F4F821B91098F73992708958B +:1005A00089E595E00E94C40221E0892B09F420E04E +:1005B000822F0895FC01848DDF01A80FB11DA35A7D +:1005C000BF4F2C91848D90E001968F739927848F73 +:1005D000A689B7892C93A089B1898C918370806496 +:1005E0008C93938D848D981306C00288F389E02D37 +:1005F00080818F7D80830895EF92FF920F931F93E8 +:10060000CF93DF93EC0181E0888F9B8D8C8D9813C5 +:1006100005C0E889F989808185FD26C0F62E0B8DFD +:1006200010E00F5F1F4F0F731127E02E8C8DE81223 +:100630000CC00FB607FCFACFE889F989808185FFE5 +:10064000F5CFCE010E94DA02F1CF8B8DFE01E80FCB +:10065000F11DE35AFF4FF0829FB7F8940B8FEA89A0 +:10066000FB89808180620AC09FB7F894EE89FF8978 +:100670006083E889F98980818370806480839FBF6B +:1006800081E090E0DF91CF911F910F91FF90EF906B +:100690000895CF93DF93EC01888D8823C9F0EA8910 +:1006A000FB89808185FD05C0A889B9898C9186FD6B +:1006B0000FC00FB607FCF5CF808185FFF2CFA88968 +:1006C000B9898C9185FFEDCFCE010E94DA02E7CF88 +:1006D000DF91CF91089580E090E0892B29F00E946E +:1006E000D00281110C9400000895DC01ED91FC9181 +:1006F0000190F081E02D0994EF92FF920F931F93E8 +:10070000CF93DF938C017B01C0E0D0E0F701EC0FC9 +:10071000FD1F6491662361F0D801ED91FC91019079 +:10072000F081E02DC8010995892B11F02196EECFBB +:10073000CE01DF91CF911F910F91FF90EF9008951F +:100740006115710579F0FB0101900020E9F73197FF +:10075000AF01461B570BDC01ED91FC910280F38148 +:10076000E02D099480E090E0089562E971E00C9436 +:10077000A0030F931F93CF93DF93EC010E947C03A0 +:100780008C01CE010E94B503800F911FDF91CF91A4 +:100790001F910F9108951F920F920FB60F9211247F +:1007A0002F933F934F935F936F937F938F939F9379 +:1007B000AF93BF93EF93FF9389E595E00E94DA0230 +:1007C000FF91EF91BF91AF919F918F917F916F9129 +:1007D0005F914F913F912F910F900FBE0F901F90FF +:1007E00018951F920F920FB60F9211242F938F938B +:1007F0009F93EF93FF93E0916905F0916A058081E3 +:10080000E0916F05F091700582FD12C0908180919A +:1008100072058F5F8F7320917305821751F0E091FD +:100820007205F0E0E75AFA4F958F8093720501C088 +:100830008081FF91EF919F918F912F910F900FBE2B +:100840000F901F9018950C945F19CF93DF93EC01D4 +:100850009C01220F331FF901E65BF94F8FEF9FEFE9 +:1008600091838083FE01E059FB4F1082F901E15032 +:10087000FA4F11821082CE01880F991F880F991F9D +:10088000FC01EF54FB4F1082118212821382FE0191 +:10089000E25AFB4F1082FC01EA5EFB4F108211828C +:1008A00012821382F901E357FB4F11821082F90182 +:1008B000EE50FC4F11821082FC01E655FC4F408344 +:1008C000518362837383FC01EE59FC4F4083518353 +:1008D00062837383FC01E65EFC4F40835183628335 +:1008E0007383FC01E45FFA4F408351836283738317 +:1008F000FE01E85FFC4F1082FE01E05DFE4F80814B +:10090000811107C0F901EE5BFE4F80810E94EC0F60 +:1009100001C080E0FE01EA50FD4F8083C650DB4FEE +:100920008883DF91CF910895CF92DF92EF92FF926B +:100930000F931F93CF93DF930E948B106B017C0169 +:10094000C0E0D0E0B701A601CE010E9425042196A7 +:10095000C231D105B9F7C0925405D0925505E09245 +:100960005605F092570510925805EDEFF3E0A5916A +:10097000B4918C9180934706EFEFF3E0A591B49189 +:100980008C9180934806E1E0F4E0A591B4918C91BC +:100990008093490680E090E00E942304833461F450 +:1009A00081E090E00E942304863631F482E090E0FA +:1009B0000E942304873609F161E971E089E595E039 +:1009C0000E94B9030E948B10609384047093850485 +:1009D0008093860490938704809188049091890481 +:1009E0009093830480938204DF91CF911F910F91A4 +:1009F000FF90EF90DF90CF90089583E090E00E9409 +:100A00002304F82E8FEF8F0D833118F066E771E025 +:100A1000D5CF67E571E089E595E00E94B903C4E0B0 +:100A2000D0E010E064E0E62ECE010E942304082FFF +:100A3000CE0101960E942304D82ECE0102960E9478 +:100A4000230490E0982F88278D0D911D90936F06B9 +:100A500080936E06CE0103960E942304D82ECE0109 +:100A600004960E94230490E0982F88278D0D911DF5 +:100A70009093710680937006CE0105960E94230420 +:100A8000D82ECE0106960E94230490E0982F882746 +:100A90008D0D911D9093730680937206CE0107967B +:100AA0000E942304D82ECE0108960E94230490E0D1 +:100AB000982F88278D0D911D90937506809374064D +:100AC0002996E0928C04093421F484E00E94820E7D +:100AD0000BC0043521F484E00E94470F05C00134A7 +:100AE00019F484E00E9497101F5FF1129DCF1092BD +:100AF0008C0410928B0410928A04EEE6F6E08EE7E6 +:100B000096E0119211928E179F07D9F75BCFFF9253 +:100B10000F931F93CF93DF93EC01F62E0881198179 +:100B2000C8010E9423048F1521F06F2DC8010E9477 +:100B3000671988819981019699838883DF91CF9184 +:100B40001F910F91FF900895AF92BF92CF92DF92C5 +:100B5000EF92FF920F931F93CF93DF93EC015A0113 +:100B600069010E9487056A2DCE010E9487056B2DC1 +:100B7000CE010E9487056C2DCE010E9487056D2D48 +:100B8000CE010E948705602FCE010E948705612F4C +:100B9000CE010E9487056E2DCE010E9487056F2D24 +:100BA000CE01DF91CF911F910F91FF90EF90DF90D9 +:100BB000CF90BF90AF900C9487052F923F924F92A9 +:100BC0005F926F927F928F929F92AF92BF92CF92DD +:100BD000DF92EF92FF920F931F93CF93DF93CDB7E6 +:100BE000DEB7A1970FB6F894DEBF0FBECDBF1C01D4 +:100BF0006D8BF8942C01440C551C440C551C5D8ED7 +:100C00004C8E820106551C4FD801CD90DD90ED90A1 +:100C1000FC90CD82DE82EF82F886F201EE59FC4F25 +:100C200040805180628073804E8E5F8E68A279A270 +:100C3000EC8DFD8DEF54FB4FC080D180E280F380BE +:100C4000C982DA82EB82FC822C01440C551CF20131 +:100C5000E357FB4F60817181FC01E25AFB4F7080CA +:100C6000EC8DFD8DE255FD4FC080D180E280F38098 +:100C7000CE8ADF8AE88EF98E78948E8C9F8CA8A01D +:100C8000B9A08D819E81AF81B885881A990AAA0A78 +:100C9000BB0AEC8DFD8DEA5EFB4FC080D180E28007 +:100CA000F38089819A81AB81BC818C199D09AE0941 +:100CB000BF0989879A87AB87BC876C017D01C718FC +:100CC000D108E108F108F201EE50FC4F80819181DA +:100CD0002B01481A590A5B8E4A8E5D885110DFC07D +:100CE000EC8DFD8DE45FFA4F80819181A281B3810B +:100CF0008D879E87AF87B88B40900A0150900B017B +:100D000060900C0170900D01498A5A8A6B8A7C8A26 +:100D1000DA01C90184199509A609B7094D845E84D1 +:100D20006F84788884199509A609B709B7FD47C06B +:100D30008091040190910501A0E0B0E0C816D906A9 +:100D4000EA06FB06F8F08091060190910701A09158 +:100D50000801B091090188159905AA05BB0590F411 +:100D600080910E0190910F01A0911001B09111019D +:100D700049885A886B887C88481659066A067B061B +:100D800009F051C0F894EC8DFD8DE655FC4F2083A1 +:100D9000318342835383EC8DFD8DEE59FC4F2083CC +:100DA000318342835383789449015A014D805E8098 +:100DB0006F80788484189508A608B7083FC0809192 +:100DC0000E0190910F01A0911001B0911101290124 +:100DD0003A01481A590A6A0A7B0AD301C2014D84B2 +:100DE0005E846F84788884199509A609B709B7FDD0 +:100DF00037C18091040190910501A0E0B0E0C816D0 +:100E0000D906EA06FB0608F42BC1809106019091F1 +:100E10000701A0910801B091090188159905AA055B +:100E2000BB0508F01DC1F8948E8D9F8DA8A1B9A1B6 +:100E3000F80180839183A283B3837894F894F101BD +:100E4000E25AFB4F1082EC8DFD8DE255FD4F108272 +:100E50001182128213827894EC8DFD8DEA5EFB4F35 +:100E600049805A806B807C8040825182628273828A +:100E7000F101EE0FFF1FEE50FC4F718360838C8DEC +:100E80009D8D845F9A4FDC012D933D934D935C9330 +:100E90001397F101E85FFC4F80818F5F8083290108 +:100EA0003A011D893E822D826EE271E080917E06BC +:100EB00090917F060E947C03ED81FE81EE0FFF1F63 +:100EC000E65BF94F6081718180917E0690917F068B +:100ED0000E94510E6BE271E080917E0690917F0638 +:100EE0000E947C0349815A816B817C8180917E06BE +:100EF00090917F060E94040E68E271E080917E0668 +:100F000090917F060E947C03B701A60180917E0626 +:100F100090917F060E94040E66E271E080917E0649 +:100F200090917F060E947C0349855A856B857C855C +:100F300080917E0690917F060E94040E63E271E02C +:100F400080917E0690917F060E947C03B501A401EA +:100F500080917E0690917F060E94040E60E271E00F +:100F600080917E0690917F060E947C03B301A201CE +:100F700080917E0690917F060E941F0E6EE171E0C7 +:100F800080917E0690917F060E947C036091880488 +:100F90007091890480917E0690917F060E94070ED1 +:100FA0006BE171E080917E0690917F060E947C0348 +:100FB0006A8D7B8D80917E0690917F060E94070E40 +:100FC000111113C068E171E080917E0690917F0657 +:100FD0000E947C03ED81FE81E85FFC4F608180917F +:100FE0007E0690917F060E94690EC114D104E1042F +:100FF000F104C1F065E171E080917E0690917F0679 +:101000000E947C036E897F89888D998DA701960146 +:101010000E948919BA01A90180917E0690917F06EC +:101020000E94040E80917E0690917F06A1960FB6D5 +:10103000F894DEBF0FBECDBFDF91CF911F910F910E +:10104000FF90EF90DF90CF90BF90AF909F908F90E8 +:101050007F906F905F904F903F902F900C94B503CE +:10106000A1960FB6F894DEBF0FBECDBFDF91CF9132 +:101070001F910F91FF90EF90DF90CF90BF90AF90B6 +:101080009F908F907F906F905F904F903F902F90A8 +:1010900008952F923F924F925F926F927F928F921C +:1010A0009F92AF92BF92CF92DF92EF92FF920F93F7 +:1010B0001F93CF93DF936C014A015B018091F90488 +:1010C00090E0029664E170E00E947519FC0120E056 +:1010D00030E040E0C4E1D0E0CF01820F931FBE01B9 +:1010E0000E947519DC01A656BD4F8C9190E08C15BD +:1010F0009D0509F44F5F2F5F3F4F2431310561F7A4 +:10110000442309F4D7C01F0163E171E080917E069A +:1011100090917F060E947C03F601EE0FFF1FE65BB5 +:10112000F94F6081718180917E0690917F060E94C7 +:10113000510E61E171E080917E0690917F060E94E0 +:101140007C03E12CF12C21E0C701820D931D64E1A9 +:1011500070E00E9475198C01FC01E656FD4F8081FC +:1011600090E0C816D90609F085C0E801CC0FDD1F54 +:10117000CC0FDD1F211120C0FE01E65BFD4F8081F9 +:101180009181A281B381FE01E650FE4F40815181E1 +:1011900062817381840F951FA61FB71F8415950563 +:1011A000A605B70508F45FC06EE071E080917E0689 +:1011B00090917F060E947C03F801EE0FFF1FEE5214 +:1011C000FE4F6081718180917E0690917F060E9422 +:1011D000070E6CE071E080917E0690917F060E9480 +:1011E0007C03FE01E65BFD4F40815181628173818A +:1011F000481959096A097B0980917E0690917F06FA +:101200000E941F0E6AE071E080917E0690917F0639 +:101210000E947C03FE01E650FE4F408151816281B5 +:10122000738180917E0690917F060E94040E68E093 +:1012300071E080917E0690917F060E947C03F80108 +:10124000E254FE4F608180917E0690917F060E945D +:10125000690EF801E655FE4F608180917E069091FF +:101260007F060E947503C65BDD4F488059806A8007 +:101270007B8020E08FEFE81AF80A84E1E816F10499 +:1012800009F062CF80917E0690917F06DF91CF9129 +:101290001F910F91FF90EF90DF90CF90BF90AF9094 +:1012A0009F908F907F906F905F904F903F902F9086 +:1012B0000C94B503DF91CF911F910F91FF90EF90A8 +:1012C000DF90CF90BF90AF909F908F907F906F9066 +:1012D0005F904F903F902F9008950F931F93CF935F +:1012E000DF93EC0166E071E080917E0690917F06CD +:1012F0000E947C038E01000F111FF801E65BF94F7D +:101300006081718180917E0690917F060E94510ECE +:10131000FE01EB5CF94F208180917E0690917F0663 +:10132000222329F0213031F46EEF70E005C065EF23 +:1013300070E002C062EF70E00E947C03CD5DD94F87 +:101340008881882341F06AEE70E080917E0690915A +:101350007F060E947C0364EE70E080917E0690918F +:101360007F060E947C03F801E150FA4F6081718191 +:1013700080917E0690917F06DF91CF911F910F9112 +:101380000C94070E62EE70E080917E0690917F06CD +:101390000E947C03609102017091030180917E069E +:1013A00090917F060E94510E60EE70E080917E0663 +:1013B00090917F060E947C03609100017091010171 +:1013C00080917E0690917F060C94570ECF92DF920B +:1013D000EF92FF926EED70E080917E0690917F0615 +:1013E0000E947C0360910E0170910F0180911001A9 +:1013F0009091110128EEC22E23E0D22EE12CF12C87 +:10140000A70196010E948919BA01A90180917E065F +:1014100090917F060E94040E6CED70E080917E0634 +:1014200090917F060E947C0360910A0170910B01EC +:1014300080910C0190910D01A70196010E948919DC +:10144000BA01A90180917E0690917F060E94040E48 +:101450006AED70E080917E0690917F060E947C0389 +:101460006091060170910701809108019091090136 +:10147000A70196010E948919BA01A90180917E06EF +:1014800090917F060E94040E68ED70E080917E06C8 +:1014900090917F060E947C03609104017091050188 +:1014A00080917E0690917F06FF90EF90DF90CF9025 +:1014B0000C940D0E0F931F93CF93DF93C6ECD0E0E7 +:1014C000FE01649180917E0690917F060E947503D3 +:1014D0002196F0E0C73DDF0799F761EC70E080915D +:1014E0007E0690917F060E947C03CCEBD0E0FE014B +:1014F000649180917E0690917F060E9475032196EB +:10150000F0E0C03CDF0799F761EB70E080917E0668 +:1015100090917F060E947C03CCE9D0E000EB10E0C4 +:10152000FE01649180917E0690917F060E94750372 +:1015300021960C171D07A1F7DF91CF911F910F91F5 +:101540000895CF92DF92EF92FF920F931F93CF9364 +:10155000DF930E948B106B017C0180917E0690913D +:101560007F060E94B5030E945A0A69E471E08091E7 +:101570007E0690917F060E947C0306E611E0C0E0A3 +:1015800081E0F801219131918F0137FD12C0811165 +:1015900008C067E471E080917E0690917F060E940A +:1015A0007C036C2F80917E0690917F060E94690ECD +:1015B00080E0CF5FC63129F76CE371E080917E0651 +:1015C00090917F060E947C0369E371E080917E0622 +:1015D00090917F060E947C03B701A60180917E0650 +:1015E00090917F060E94040E67E371E080917E0671 +:1015F00090917F060E947C03609188047091890419 +:1016000080917E0690917F060E94070E64E371E050 +:1016100080917E0690917F060E947C034091840415 +:1016200050918504609186047091870480917E06B4 +:1016300090917F060E94040E62E371E080917E0625 +:1016400090917F060E947C036091820470918304D4 +:1016500080917E0690917F060E94070E80917E0603 +:1016600090917F060E94B5030E94E6090E94C2097C +:1016700060E371E080917E0690917F060E947C037A +:101680006091580580917E0690917F060E94700EB1 +:101690000AE416E0C0E0D0E0F801819191918F0159 +:1016A00097FD09C0CE010E946D0980917E06909140 +:1016B0007F060E94B5032196C231D10569F7DF91FB +:1016C000CF911F910F91FF90EF90DF90CF900895F1 +:1016D000CF93DF93C4E9D0E0FE01649180917E0650 +:1016E00090917F060E9475032196F0E0CB39DF07C9 +:1016F00099F7DF91CF9108954F925F926F927F9209 +:101700008F929F92AF92BF92DF92EF92FF920F93D0 +:101710001F93CF93DF93D62E682F70E08B01000FBD +:10172000111F000F111FF801E65EFC4F80809180B1 +:10173000A280B38029013A01481859086A087B0839 +:10174000530142019091F9049F5F9093F9049091A5 +:10175000F904943110F01092F904E091F904F0E0EA +:10176000A091A801B091A901ED012196D093A90102 +:10177000C093A801EE0FFF1FEE52FE4FB183A0836E +:10178000E091F904F0E0E656FD4F8083E091F90422 +:10179000C801865E9C4F7C01EC0188819981AA81F9 +:1017A000BB81D4E0ED9FF0011124E65BFD4F808307 +:1017B0009183A283B383E091F90484E0E89FF00170 +:1017C0001124E650FE4F80829182A282B382A091C2 +:1017D000F904B0E0FB01EA50FD4F8081A254BE4FF6 +:1017E0008C93CB01880F991FDC01A150BA4F0D904B +:1017F000BC91A02D2D01612C712CEB01CB5CD94F3C +:1018000084149504A604B704B0F4D7012D933D9336 +:101810004D935C93139770816881761372C0DC01DD +:10182000A357BB4F8D919C911197019611969C9354 +:101830008E9362E567C0A081B881CB018A569E4F26 +:10184000AB135AC0EC01B881AB1709F442C0D80100 +:10185000AF54BB4F4D905D906D907C901397DFEF30 +:101860004D1A5D0A6D0A7D0A4D925D926D927C92D1 +:101870001397D801AE59BC4F2D933D934D935C9374 +:101880001397DB01A059BB4FCC91C1110FC0E801E8 +:10189000C655DC4F288339834A835B83C1E0CC93F0 +:1018A000DB01A25ABB4F6C916F5F6C93D801A255BC +:1018B000BD4F4D905D906D907C901397840C951C5E +:1018C000A61CB71C8D929D92AD92BC92139763E4B7 +:1018D00014C0D801A255BD4F4D905D906D907C9085 +:1018E0001397840C951CA61CB71C8D929D92AD92EB +:1018F000BC92139760E501C067E47081DC017C93C2 +:1019000001C068E5A091F904B0E0A655BE4F6C9304 +:10191000D801A65EBC4F2D933D934D935C931397D6 +:10192000D082DF91CF911F910F91FF90EF90DF90C8 +:10193000BF90AF909F908F907F906F905F904F90EF +:101940000895BF92CF92DF92EF92FF920F931F9371 +:10195000CF93DF93C82F0E948B106B017C018C2FDB +:1019600090E0FC01EE0FFF1FE350FC4FA591B491F6 +:101970000C91DC01A95BB94F1C91FC01EE0FFF1F1C +:10198000E65DFE4F0190F081E02D208110271223AB +:101990000C93112339F1FC01EB5EFE4FC081D1E0C5 +:1019A000FC01EE5EFE4FB080BC16E0F08D2F81236F +:1019B000B1F0EC2FF0E0EE0FFF1FEA59FE4F01905F +:1019C000F081E02D1E161F0654F461E09D2F902338 +:1019D00009F460E0A70196018E2F0E947C0BDD0FB9 +:1019E000CF5FE2CFDF91CF911F910F91FF90EF90EA +:1019F000DF90CF90BF9008951F920F920FB60F9275 +:101A000011242F933F934F935F936F937F938F9303 +:101A10009F93AF93BF93EF93FF9382E00E94A10C3B +:101A2000FF91EF91BF91AF919F918F917F916F91B6 +:101A30005F914F913F912F910F900FBE0F901F908C +:101A400018951F920F920FB60F9211242F933F9368 +:101A50004F935F936F937F938F939F93AF93BF93B6 +:101A6000EF93FF9381E00E94A10CFF91EF91BF9152 +:101A7000AF919F918F917F916F915F914F913F9126 +:101A80002F910F900FBE0F901F9018951F920F92DD +:101A90000FB60F9211242F933F934F935F936F9341 +:101AA0007F938F939F93AF93BF93EF93FF9380E0C8 +:101AB0000E94A10CFF91EF91BF91AF919F918F91E7 +:101AC0007F916F915F914F913F912F910F900FBE3A +:101AD0000F901F9018950E94D4198F929F92AF92E9 +:101AE000BF92EF92FF920F931F93CF93DF93CDB7E7 +:101AF000DEB7A1970FB6F894DEBF0FBECDBF7C0155 +:101B0000FA01CB0119A2223008F42AE08E010F5D00 +:101B10001F4F822E912CA12CB12CBF01A501940145 +:101B20000E948919F901CA01015011096A3014F49F +:101B3000605D01C0695CD8016C93232B242B252B9D +:101B400061F7B801C7010E94A003A1960FB6F894EF +:101B5000DEBF0FBECDBFDF91CF911F910F91FF90E0 +:101B6000EF90BF90AF909F908F900895833081F059 +:101B700028F4813099F08230A1F008958730A9F0DF +:101B80008830B9F08430D1F4809180008F7D03C01B +:101B9000809180008F7780938000089584B58F773F +:101BA00002C084B58F7D84BD08958091B0008F7789 +:101BB00003C08091B0008F7D8093B00008953FB73F +:101BC000F8948091850690918606A0918706B09141 +:101BD000880626B5A89B05C02F3F19F00196A11DC8 +:101BE000B11D3FBFBA2FA92F982F8827820F911DB3 +:101BF000A11DB11DBC01CD0142E0660F771F881FFA +:101C0000991F4A95D1F708952AE00C946D0DAB0108 +:101C100060E070E02AE00C946D0D0F931F93CF935A +:101C2000DF93EC010E94070E8C01CE010E94B503E8 +:101C3000800F911FDF91CF911F910F910895CF9247 +:101C4000DF92EF92FF920F931F93CF93DF9377FF73 +:101C50001DC06A017B01EC016DE20E9475038C01DD +:101C600044275527BA014C195D096E097F092AE0FE +:101C7000CE010E946D0D800F911FDF91CF911F91BA +:101C80000F91FF90EF90DF90CF9008952AE0DF91C1 +:101C9000CF911F910F91FF90EF90DF90CF900C9418 +:101CA0006D0DAB01770F660B770B0C941F0E0F9326 +:101CB0001F93CF93DF93EC010E94510E8C01CE0154 +:101CC0000E94B503800F911FDF91CF911F910F915B +:101CD0000895462F50E060E070E02AE00C946D0D0E +:101CE0000F931F93CF93DF93EC010E94690E8C0139 +:101CF000CE010E94B503800F911FDF91CF911F91FC +:101D00000F910895843058F40E94680B6FE674E0D8 +:101D100080917E0690917F060E947C03B2C0209144 +:101D20006E0630916F06C901019780319E4038F0F0 +:101D30000E94680B60916E0670916F0650C0A8EE0D +:101D4000B3E00E94B11960930E0170930F0180936C +:101D50001001909311012091700630917106C90114 +:101D6000019780319E4038F00E94680B60917006A8 +:101D70007091710634C0A8EEB3E00E94B11960936F +:101D80000A0170930B0180930C0190930D01209137 +:101D900072063091730621318EE0380738F00E94C8 +:101DA000680B609172067091730619C0A8EEB3E0DB +:101DB0000E94B11960930601709307018093080196 +:101DC000909309018091740690917506853691056E +:101DD00060F00E94680B60917406709175068091A6 +:101DE0007E0690917F060C940D0E909305018093D2 +:101DF00004016BE574E080917E0690917F060E945D +:101E00007C0360916E0670916F0680917E069091C2 +:101E10007F060E94070E69E574E080917E0690912E +:101E20007F060E947C03609170067091710680911C +:101E30007E0690917F060E94070E67E574E0809110 +:101E40007E0690917F060E947C03609172067091DD +:101E5000730680917E0690917F060E94070E65E5CD +:101E600074E080917E0690917F060E947C036091D1 +:101E700074067091750680917E0690917F060E948F +:101E8000070E80917E0690917F060C94B5038230F8 +:101E900058F40E94680B60E574E080917E06909192 +:101EA0007F060E947C0353C080916E0690916F065E +:101EB0009C01215031092F3F334038F00E94680BBC +:101EC00060916E0670916F0614C090930301809329 +:101ED000020180917006909171069C012150310998 +:101EE0002F3F334060F00E94680B60917006709144 +:101EF000710680917E0690917F060C940D0E909352 +:101F000001018093000164E374E080917E0690916A +:101F10007F060E947C0360916E0670916F0680912F +:101F20007E0690917F060E94070E62E374E0809126 +:101F30007E0690917F060E947C03609170067091EE +:101F4000710680917E0690917F060E94070E809117 +:101F50007E0690917F060C94B50387E480937C0005 +:101F600080917A00806480937A0080917A0086FD67 +:101F7000FCCF809178002091790090E0922B089519 +:101F80001F93CF93DF93E6E9F3E09491EAEAF3E05D +:101F9000D491E5E0F4E0C491CC23D1F0182F99233B +:101FA00019F0892F0E94B60DEC2FF0E0EE0FFF1F05 +:101FB000E657FC4FA591B4919FB7F894111104C056 +:101FC0008C91D095D82302C0EC91DE2BDC939FBF7F +:101FD000DF91CF911F910895CF93DF93282F30E0A9 +:101FE000F901EC56FC4F8491F901E855FC4FD4916E +:101FF000F901ED5FFB4FC491CC2391F081110E9458 +:10200000B60DEC2FF0E0EE0FFF1FE750FC4FA5914F +:10201000B491EC91ED2381E090E021F480E002C0E6 +:1020200080E090E0DF91CF910895CF93DF9390E02F +:10203000FC01E855FC4F2491FC01ED5FFB4F8491BE +:10204000882361F190E0880F991FFC01E058FC4F54 +:10205000C591D491FC01E657FC4FA591B491611153 +:1020600009C09FB7F8948881209582238883EC91DA +:102070002E230BC0623061F49FB7F8948881322F11 +:10208000309583238883EC912E2B2C939FBF06C021 +:102090008FB7F894E8812E2B28838FBFDF91CF91E3 +:1020A00008958F929F92AF92BF92CF92DF92EF925C +:1020B000FF926B017C010E94DF0D4B015C01C1149A +:1020C000D104E104F104F1F00E94DF0DDC01CB0149 +:1020D00088199909AA09BB09883E9340A105B10551 +:1020E00070F321E0C21AD108E108F10888EE880EE9 +:1020F00083E0981EA11CB11CC114D104E104F104B9 +:1021000019F7DDCFFF90EF90DF90CF90BF90AF90A9 +:102110009F908F9008952FB7F894609181067091E9 +:10212000820680918306909184062FBF08957F9246 +:102130008F929F92AF92BF92CF92DF92EF92FF92D7 +:102140000F931F93CF93DF93782E0E948B10AB01D8 +:10215000BC01C0906E06D0906F0685E18C1558F0DA +:102160005601BB24F501EE0FFF1FEA59FE4F008117 +:10217000118117FF1EC00E94680B67E174E0809117 +:102180007E0690917F060E947C036C2D80917E06D6 +:1021900090917F06DF91CF911F910F91FF90EF906B +:1021A000DF90CF90BF90AF909F908F907F900C94D6 +:1021B000700E11277801EE0CFF1CF701EE5BFE4F4D +:1021C000C081D181F701E65BF94F4F018081918198 +:1021D000A816B906D1F0FE01FF27ED5FFB4FE49191 +:1021E000E2508E2F90E0FC01EE0FFF1FE350FC4FFA +:1021F000A591B4912C91FC01E95BF94F2083C801B2 +:102200000E942504F401B182A08280917006909111 +:1022100071060297029788F00E94680B6EEC73E0DB +:1022200080917E0690917F060E947C036C2D8091A8 +:102230007E0690917F060E94700EF801EB5CF94FCC +:1022400081E020917006309171062330310509F04C +:1022500080E08083C801805D9E4F6C01F801ED5DD8 +:10226000F94F5F01F2E0F71590F4809172069091BA +:102270007306892B61F0F6018081811108C062E04C +:102280008C2F0E94151081E0F501808306C060E06C +:102290008C2F0E941510F5011082F3E0F71530F431 +:1022A0008091740690917506009711F482E090E099 +:1022B000F701E150FA4F91838083F601808181110B +:1022C00023C0DD27FE01E855FC4F8491FE01ED5F40 +:1022D000FB4FC491CC23B1F1C250AC2FB0E0AA0F98 +:1022E000BB1FA65DBE4F0D90BC91A02DEC91E82BBD +:1022F000EC932091680081E090E001C0880FCA95BE +:10230000EAF7822B809368006CEB73E080917E0685 +:1023100090917F060E947C03C8010E946D09809104 +:102320007E0690917F06DF91CF911F910F91FF90D4 +:10233000EF90DF90CF90BF90AF909F908F907F9065 +:102340000C94B5030E94680B67EC73E080917E06E5 +:1023500090917F06DF91CF911F910F91FF90EF90A9 +:10236000DF90CF90BF90AF909F908F907F900C9414 +:10237000B9031F920F920FB60F9211242F933F9320 +:102380008F939F93AF93BF93809181069091820624 +:10239000A0918306B09184063091800623E0230F3C +:1023A0002D3720F40196A11DB11D05C026E8230F8D +:1023B0000296A11DB11D2093800680938106909303 +:1023C0008206A0938306B09384068091850690913F +:1023D0008606A0918706B09188060196A11DB11DC1 +:1023E0008093850690938606A0938706B09388060F +:1023F000BF91AF919F918F913F912F910F900FBE01 +:102400000F901F901895E9E5F5E01382128288EE8F +:1024100093E0A0E0B0E084839583A683B7838CE14A +:1024200091E09183808385EC90E09587848784ECAC +:1024300090E09787868780EC90E0918B808B81EC91 +:1024400090E0938B828B82EC90E0958B848B86EC72 +:1024500090E0978B868B118E128E138E148E0895BA +:10246000CF93DF93CDB7DEB727970FB6F894DEBFD3 +:102470000FBECDBF789484B5826084BD84B5816081 +:1024800084BD85B5826085BD85B5816085BD80913F +:102490006E00816080936E001092810080918100B7 +:1024A00082608093810080918100816080938100AF +:1024B000809180008160809380008091B100846071 +:1024C0008093B1008091B00081608093B0008091D2 +:1024D0007A00846080937A0080917A008260809391 +:1024E0007A0080917A00816080937A0080917A00EE +:1024F000806880937A001092C100E0916905F091A4 +:102500006A0582E08083E0916505F091660510829E +:10251000E0916705F091680583E38083109271056F +:10252000E0916D05F0916E0586E08083E0916B058A +:10253000F0916C05808180618083E0916B05F09162 +:102540006C05808188608083E0916B05F0916C055B +:10255000808180688083E0916B05F0916C058081BB +:102560008F7D808364EF71E080E090E00E945110E5 +:10257000789489E595E00E94B50389E595E090930C +:102580007F0680937E0610928904109288040E9430 +:102590008B106093F6057093F7058093F805909380 +:1025A000F9050E94940461E082E00E9415100E94E7 +:1025B000A10A18EE212E13E0312E412C512C0E943D +:1025C0008B100091F6051091F7052091F8053091D8 +:1025D000F905601771078207930750F4209188046A +:1025E000309189042F5F3F4F3093890420938804F2 +:1025F0006093F6057093F7058093F8059093F905BD +:1026000089E595E00E94C402892B09F4AAC289E5F4 +:1026100095E00E94A202D82EBCE28B2E881119C030 +:10262000E0918C04E73008F09CC281E08E0F80932B +:102630008C04F0E0EE0FFF1FE259F94F80918A04FD +:1026400090918B049183808310928B0410928A0462 +:1026500088C280ED8D0D8A30A8F420918A043091D3 +:102660008B04EAE09E2E929EC001939E900D112451 +:10267000C0978D0D911DD7FC9A9590938B048093F4 +:102680008A046FC28FE98D0D8A3108F06AC2809189 +:102690005805882309F44BC0E0918C04F0E0EE0F5C +:1026A000FF1FE259F94F80918A0490918B04918326 +:1026B00080836DEB72E089E595E00E947C037EE605 +:1026C000E72E76E0F72E00E010E080918C0490E099 +:1026D00080179107A4F00115110531F06BEB72E042 +:1026E00089E595E00E947C03F701619171917F017A +:1026F00089E595E00E94070E0F5F1F4FE6CF6D2D15 +:1027000089E595E00E94750364EB72E089E595E048 +:102710000E947C0360918C0470E06F5F7F4F89E5BD +:1027200095E00E94510E89E595E00E94B5038D2D3C +:10273000DD0C990BAA0BBB0BFC01E156F109E6314C +:10274000F10508F000C2EC5CFF4F0C94AB198091CE +:102750008C04E82FF0E0EE0FFF1FE259F94F2091B3 +:102760008A0430918B04318320838F5F0E949710FD +:10277000EAC180918C04E82FF0E0EE0FFF1FE259D0 +:10278000F94F20918A0430918B043183208300918A +:102790006E0610916F068F3F61F0063150F4F8011C +:1027A000FF27EE0FFF1FEA59FE4F8081918197FFAF +:1027B00005C00E94680B6EE572E046C49927FC01D3 +:1027C000E05DFE4F2081211138C4FC01EE0FFF1F98 +:1027D000EE5BFE4F208131813327F901E855FC4F34 +:1027E0004491F901ED5FFB4F2491222309F41CC4AD +:1027F0002250A22FB0E0AA0FBB1FA65DBE4F0D90C6 +:10280000BC91A02D3C91409543234C933C913111B8 +:1028100014C46091680041E050E04A0102C0880C95 +:10282000991C2A95E2F794012095262320936800AD +:1028300004C41A82198263E4CE0101960E948705BE +:1028400066E6CE0101960E94870567E6CE010196F5 +:102850000E948705FAE46F2EF6E07F2EF30181E0F7 +:102860002191319137FF8F5F06E0EE36F007C1F717 +:102870008F5F8B83682FCE0101960E948705E090C1 +:102880000401F090050160910601709107018091AB +:10289000080190910901A20191010E9489192C83DC +:1028A0003D834E835F8360910A0170910B0180919B +:1028B0000C0190910D01A20191010E948919490119 +:1028C0005A0160910E0170910F0180911001909159 +:1028D0001101A20191010E948919BA01A9010C817B +:1028E0001D81940169E4CE0101960E94A405209106 +:1028F0000001309101014091020150910301E12C4E +:10290000F12C00E010E064E5CE0101960E94A405E0 +:102910006FEF862E65E0962E73E2A72E76E0B72E37 +:10292000E5E3CE2EE6E0DE2EF301419151913F0129 +:1029300057FD14C0F401E080F180F501008110E042 +:10294000F6018081811103C022E030E002C023E063 +:1029500030E061E4CE0101960E94A405F2E08F0E02 +:10296000911C0FEFA01AB00A1FEFC11AD10A2EE670 +:10297000621626E07206C1F66DE372E089E595E025 +:102980000E947C036B8189E595E00E94690E6AE3F1 +:1029900072E089E595E00E947C0369817A8189E58E +:1029A00095E00E94570ECFC00E94A10ACCC0809132 +:1029B0008C04E82FF0E0EE0FFF1FE259F94F209151 +:1029C0008A0430918B04318320838F5F0E94820EB2 +:1029D000BAC080918C04E82FF0E0EE0FFF1FE2599F +:1029E000F94F20918A0430918B0431832083209108 +:1029F0006E0630916F062130310509F0A4C08F3F7B +:102A000009F4A1C064E372E013C30E9494049BC064 +:102A10000E948B104B015C0169E272E080917E069E +:102A200090917F060E947C030E945A0A80917E0644 +:102A300090917F060E94B5030E94E6090E94C20998 +:102A400067E272E080917E0690917F060E947C038F +:102A50006091580580917E0690917F060E94700ECD +:102A60003AE4E32E36E0F32E00E010E0F701819126 +:102A700091917F0197FD16C0C8010E946D0964E223 +:102A800072E080917E0690917F060E947C03A501F2 +:102A9000940161E0C8010E94DD05B501A401C801EF +:102AA0000E9449080F5F1F4F02311105F9F680E0BF +:102AB00090E00E94230400917E0610917F068334EB +:102AC00009F0BDC381E090E00E942304863609F03E +:102AD000B6C382E090E00E942304873609F0AFC3BA +:102AE000C1C280918C04E82FF0E0EE0FFF1FE25985 +:102AF000F94F20918A0430918B04318320838F5FBA +:102B00000E94470F20C020918A0430918B0480914D +:102B10007E0690917F062F3F310588F4209358055B +:102B20006FE972E00E947C0360918A0470918B04CB +:102B300080917E0690917F060E940D0E04C069E789 +:102B400072E00E94B90310928C0410928B041092D0 +:102B50008A04EEE6F6E01192119206E0EE37F007F5 +:102B6000D1F780916C0690916D0697FD36C180E09B +:102B70000E94C00F6AE070E080E090E00E94511077 +:102B80000E94AD0F8C019093FE058093FD0581E0BE +:102B90000E94C00F6AE070E080E090E00E94511057 +:102BA0000E94AD0F9093FC058093FB05801B910B59 +:102BB00020910001309101012817390754F020912C +:102BC000020130910301821793070CF0C6C010E098 +:102BD00001C011E08091FA05811709F4BEC010937D +:102BE000FA050E948B109B01AC01612F81E10E94CC +:102BF0007C0B809158058A3030F169E173E0809157 +:102C00007E0690917F060E947C0361E170E08091D6 +:102C10007E0690917F060E94510E67E073E08091DE +:102C20007E0690917F060E947C036091FA05809158 +:102C30007E0690917F060E94690E80917E0690919B +:102C40007F060E94B503809158058A3008F485C03C +:102C500060E073E080917E0690917F060E947C0385 +:102C600065E170E080917E0690917F060E94510E92 +:102C700067EF72E080917E0690917F060E947C0350 +:102C800061E170E080917E0690917F060E94510E76 +:102C90006BEE72E080917E0690917F060E947C032D +:102CA00065E170E080917E0690917F060E94510E52 +:102CB00067EE72E080917E0690917F060E947C0311 +:102CC00062EE72E080917E0690917F060E947C0306 +:102CD000612F80917E0690917F060E94690E67EDBC +:102CE00072E080917E0690917F060E947C03609145 +:102CF000F90480917E0690917F060E94690E6EEC29 +:102D000072E080917E0690917F060E947C03409144 +:102D1000F5045091F6046091F7047091F8048091E5 +:102D20007E0690917F060E94040E64EC72E0809112 +:102D30007E0690917F060E947C036091AF047091A3 +:102D4000B00480917E0690917F060E94070E8091CC +:102D50007E0690917F060E94B503809158058431CC +:102D6000E0F16EE773E080917E0690917F060E940D +:102D70007C036091FB057091FC0580917E0690912B +:102D80007F060E94510E6CE773E080917E06909161 +:102D90007F060E947C036091FD057091FE05809185 +:102DA0007E0690917F060E94510E69E773E0809144 +:102DB0007E0690917F060E947C036091FB057091D6 +:102DC000FC058091FD059091FE05681B790B8091B3 +:102DD0007E0690917F060E94570E809158058A309A +:102DE00060F50E948B1000915405109155052091BB +:102DF0005605309157054B015C01801A910AA20AD1 +:102E0000B30AC0900E01D0900F01E0901001F09035 +:102E100011018C149D04AE04BF0408F0D6C0C0900C +:102E20000A01D0900B01E0900C01F0900D01EAE452 +:102E3000F6E0ACE0B5E040E0C1C05AE4A52E56E0B3 +:102E4000B52E6AEFE62E64E0F62E72E4C72E71E02E +:102E5000D72E00E010E0F501819091905F011814E9 +:102E600019040CF085C0F6016080862D0E94EC0FDD +:102E7000782EF7018081781609F47AC0708262E7B3 +:102E800073E080917E0690917F060E947C03B401DE +:102E900080917E0690917F060E94510E66E673E057 +:102EA00080917E0690917F060E947C03662D809122 +:102EB0007E0690917F060E94690E64E673E0809121 +:102EC0007E0690917F060E947C0367E573E0809107 +:102ED0007E0690917F060E947C03672D80917E067E +:102EE00090917F060E94690E6CE473E080917E06EB +:102EF00090917F060E947C036091F90480917E0688 +:102F000090917F060E94690E63E473E080917E06D3 +:102F100090917F060E947C03F801EE0FFF1FEE0FD9 +:102F2000FF1FEF54FB4F408151816281738180917B +:102F30007E0690917F060E94040E69E373E0809103 +:102F40007E0690917F060E947C03F801EE0FFF1F22 +:102F5000E357FB4F6081718180917E0690917F06DF +:102F60000E94070E80917E0690917F060E94B50315 +:102F70000F5F1F4FFFEFEF1AFF0A22E0C20ED11CB6 +:102F80000231110509F067CF2CCF0D911D912D91C4 +:102F90003C9113974B015C01801A910AA20AB30A73 +:102FA0008C149D04AE04BF0408F041E014962EE694 +:102FB000A22E26E0B22EAE16BF0629F02191319145 +:102FC00037FDF4CFE2CF442361F10E948B104B0117 +:102FD0005C018AE4E82E86E0F82E00E010E0F701BC +:102FE000819191917F0197FD0FC0A501940160E04F +:102FF000C8010E94DD0580915805853028F0B50193 +:10300000A401C8010E9449080F5F1F4F023111053A +:1030100031F78092540590925505A0925605B092D2 +:1030200057050E946B03CBCA0E94680B67E572E0EC +:1030300080917E0690917F0684CD40E050E0BA01F9 +:103040000E9425046CE472E080917E0690917F06D8 +:103050000E947C03602F80917E0690917F060E94E3 +:10306000700E71CD83E090E00E942304682EF4E19D +:10307000F81718F463ED71E0E4C0C8010E94B503CD +:1030800061EC71E080917E0690917F060E947C0346 +:1030900080E090E00E942304682F80917E0690914A +:1030A0007F060E94750381E090E00E942304682F50 +:1030B00080917E0690917F060E94750382E090E0E9 +:1030C0000E942304682F80917E0690917F060E94C3 +:1030D000750368EB71E080917E0690917F060E94F7 +:1030E0007C0383E090E00E942304682F70E08091CD +:1030F0007E0690917F060E94510E80917E069091EF +:103100007F060E94B503712C04E010E0671409F4F7 +:103110009BC0C8010E9423048B83C80101960E94B2 +:103120002304F82EC80102960E942304882E912CB5 +:10313000982C88248F0C911CC80103960E942304AC +:10314000F82EC80104960E942304A82EB12CBA2C94 +:10315000AA24AF0CB11CC80105960E942304F82EC6 +:10316000C80106960E942304C82ED12CDC2CCC2446 +:10317000CF0CD11CC80107960E942304F82EC80169 +:1031800008960E94230490E0982F88279C012F0D19 +:10319000311D7901075F1F4F6FEA71E080917E0654 +:1031A00090917F060E947C036B8180917E069091B6 +:1031B0007F060E9475036DEA71E080917E06909112 +:1031C0007F060E947C03B40180917E0690917F0669 +:1031D0000E94070E6BEA71E080917E0690917F0657 +:1031E0000E947C03B50180917E0690917F060E942B +:1031F000070E69EA71E080917E0690917F060E9439 +:103200007C03B60180917E0690917F060E94070E96 +:1032100067EA71E080917E0690917F060E947C03B0 +:10322000B70180917E0690917F060E94070E8091E3 +:103230007E0690917F060E94B503739467CF6EEE71 +:1032400071E0C8010E94B90362E172E080917E06DC +:1032500090917F060E947C03C0905405D090550544 +:10326000E0905605F090570580910E0190910F0166 +:10327000A0911001B0911101C80ED91EEA1EFB1ECB +:103280000E948B1046015701861A970AA80AB90AAC +:10329000B501A40180917E0690917F060E94040EE4 +:1032A00064E072E080917E0690917F060E947C032C +:1032B00080917E0690917F060E94B50344CCF999D7 +:1032C000FECF92BD81BDF89A992780B50895262F2B +:1032D000F999FECF1FBA92BD81BD20BD0FB6F894FB +:1032E000FA9AF99A0FBE0196089597FB072E16F4E5 +:1032F000009407D077FD09D00E94C01907FC05D0C3 +:103300003EF4909581959F4F0895709561957F4FFC +:103310000895A1E21A2EAA1BBB1BFD010DC0AA1F16 +:10332000BB1FEE1FFF1FA217B307E407F50720F02E +:10333000A21BB30BE40BF50B661F771F881F991FA9 +:103340001A9469F760957095809590959B01AC01F2 +:10335000BD01CF010895EE0FFF1F0590F491E02D00 +:103360000994A29FB001B39FC001A39F700D811D5E +:103370001124911DB29F700D811D1124911D08957E +:10338000AA1BBB1B51E107C0AA1FBB1FA617B7078B +:1033900010F0A61BB70B881F991F5A95A9F78095A7 +:1033A0009095BC01CD01089581E090E0F8940C94D3 +:0633B000D919F894FFCFCB +:1033B6006E0064000200D007000060EA000030756D +:1033C60000000D1307080E0000000000FC02650255 +:1033D60093024903C402A202B6026B006C006D00A0 +:1033E60000000000000000000000000000000000D7 +:1033F600000103000400050006000700080009009C +:103406000A000B000C000E000F0010001100120045 +:10341600130014001500FFFFFFFFFFFF000001006F +:103426000200030004000500060007000800FFFF75 +:1034360009000A000B000C000D000E000F00100022 +:0634460011000D0A000058 :00000001FF diff --git a/fhem/contrib/arduino/ArduCounter1.8.ino b/fhem/contrib/arduino/ArduCounter1.8.ino deleted file mode 100755 index 3563a1b48..000000000 --- a/fhem/contrib/arduino/ArduCounter1.8.ino +++ /dev/null @@ -1,804 +0,0 @@ -/* - * 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 board - * - * the sketch uses pin change interrupts which can be anabled - * for any of the inputs on e.g. an arduino uno or a jeenode - * - * the pin change Interrupt handling used here - * is based on the arduino playground example on PCINT: - * http://playground.arduino.cc/Main/PcInt - * - * Refer to avr-gcc header files, arduino source and atmega datasheet. - */ - -/* 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 byte 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 - - ToDo / Ideas: - - new index scheme to save memory: - array to map from pcintPin to new index, limit allowed pins. - unused pcintpins point to -1 and vomment states arduino pin number - insread of allowedPins array use new function from aPin to pcintPin - and then look up in new array for index or -1 -*/ - -#include "pins_arduino.h" - -const char versionStr[] PROGMEM = "ArduCounter V1.8"; -const char errorStr[] PROGMEM = "Error: "; - -#define enablePulseLenChecking 1 - -#define SERIAL_SPEED 38400 -#define MAX_ARDUINO_PIN 24 -#define MAX_PCINT_PIN 24 -#define MAX_INPUT_NUM 8 - -/* arduino pins that are typically ok to use - * (some are left out because they are used - * as reset, serial, led or other things on most boards) */ -byte allowedPins[MAX_ARDUINO_PIN] = - { 0, 0, 0, 3, 4, 5, 6, 7, - 0, 9, 10, 11, 12, 0, - 14, 15, 16, 17, 0, 0}; - - -/* Pin change mask for each chip port */ -volatile uint8_t *port_to_pcmask[] = { - &PCMSK0, - &PCMSK1, - &PCMSK2 -}; - -/* last PIN States to detect individual pin changes in ISR */ -volatile static uint8_t PCintLast[3]; - -unsigned long intervalMin = 30000; // default 30 sec - report after this time if nothing else delays it -unsigned long intervalMax = 60000; // default 60 sec - report after this time if it didin't happen before -unsigned long intervalSml = 2000; // default 2 secs - continue count if timeDiff is less and intervalMax not over -unsigned int countMin = 1; // continue counting if count is less than this and intervalMax not over - -unsigned long timeNextReport; - -/* index to the following arrays is the internal PCINT pin number, not the arduino - * pin number because the PCINT pin number corresponds to the physical ports - * and this saves time for mapping to the arduino numbers - */ - -/* pin change mode (RISING etc.) as parameter for ISR */ -byte PCintMode[MAX_PCINT_PIN]; -/* mode for timing pulse length - derived from PCintMode (RISING etc. */ -byte PulseMode[MAX_PCINT_PIN]; - -/* pin number for PCINT number if active - otherwise -1 */ -char PCintActivePin[MAX_PCINT_PIN]; - -/* did we get first interrupt yet? */ -volatile boolean initialized[MAX_PCINT_PIN]; - -/* individual counter for each real pin */ -volatile unsigned long counter[MAX_PCINT_PIN]; -/* count at last report to get difference */ -unsigned long lastCount[MAX_PCINT_PIN]; - -#ifdef enablePulseLenChecking -/* individual reject counter for each real pin */ -volatile unsigned int rejectCounter[MAX_PCINT_PIN]; -unsigned int lastRejCount[MAX_PCINT_PIN]; - -/* millis at last interrupt when signal was rising (for filtering with min pulse length) */ -volatile unsigned long lastPulseStart[MAX_PCINT_PIN]; - -/* millis at last interrupt when signal was falling (for filtering with min pulse length) */ -volatile unsigned long lastPulseEnd[MAX_PCINT_PIN]; - -/* minimal pulse length in millis */ -/* specified instead of rising or falling. isr needs to check change anyway */ -unsigned int pulseWidthMin[MAX_PCINT_PIN]; - -/* sum of pulse lengths for average output */ -volatile unsigned long pulseWidthSum[MAX_PCINT_PIN]; - -/* start of pulse for measuring length */ -byte pulseWidthStart[MAX_PCINT_PIN]; - -#endif - -/* millis at first interrupt for current calculation - * (is also last interrupt of old interval) */ -volatile unsigned long startTime[MAX_PCINT_PIN]; - -/* millis at last interrupt */ -volatile unsigned long lastTime[MAX_PCINT_PIN]; - -/* millis at first interrupt in a reporting cycle */ -volatile unsigned long startTimeRepInt[MAX_PCINT_PIN]; - - -/* millis at last report - * to find out when maxInterval is over - * and report has to be done even if - * no impulses were counted */ -unsigned long lastReport[MAX_PCINT_PIN]; - -unsigned int commandData[MAX_INPUT_NUM]; -byte commandDataPointer = 0; - - -int digitalPinToPcIntPin(uint8_t aPin) { - uint8_t pcintPin; // PCINT pin number for the pin to be added (index for most arrays) - uint8_t port = digitalPinToPort(aPin) - 2; // port that this arduno pin belongs to for enabling interrupts - - if (port == 1) { // now calculate the PCINT pin number that corresponds to the arduino pin number - pcintPin = aPin - 6; // port 1: PC0-PC5 (A0-A5 or D14-D19) is PCINT 8-13 (PC6 is reset) - } else { // arduino numbering continues at D14 since PB6/PB7 are used for other things - pcintPin = port * 8 + (aPin % 8); // port 0: PB0-PB5 (D8-D13) is PCINT 0-5 (PB6/PB7 is crystal) - } // port 2: PD0-PD7 (D0-D7) is PCINT 16-23 - return pcintPin; -} - - -/* Add a pin to be handled */ -byte AddPinChangeInterrupt(uint8_t aPin) { - uint8_t pcintPin; // PCINT pin number for the pin to be added (used as index for most arrays) - volatile uint8_t *pcmask; // pointer to PCMSK0 or 1 or 2 depending on the port corresponding to the pin - - uint8_t bit = digitalPinToBitMask(aPin); // bit in PCMSK to enable pin change interrupt for this arduino pin - uint8_t port = digitalPinToPort(aPin); // port that this arduno pin belongs to for enabling interrupts - - if (port == NOT_A_PORT) - return 0; - - port -= 2; - pcmask = port_to_pcmask[port]; // point to PCMSK0 or 1 or 2 depending on the port corresponding to the pin - *pcmask |= bit; // 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 */ -byte RemovePinChangeInterrupt(uint8_t aPin) { - uint8_t pcintPin; - volatile uint8_t *pcmask; - - uint8_t bit = digitalPinToBitMask(aPin); - uint8_t port = digitalPinToPort(aPin); - - if (port == NOT_A_PORT) - return 0; - - port -= 2; - pcmask = port_to_pcmask[port]; - *pcmask &= ~bit; // disable the mask. - if (*pcmask == 0) { // if that's the last one, disable the interrupt. - PCICR &= ~(0x01 << port); - } - return 1; -} - - - -void PrintErrorMsg() { - int len = strlen_P(errorStr); - char myChar; - for (unsigned char k = 0; k < len; k++) { - myChar = pgm_read_byte_near(errorStr + k); - Serial.print(myChar); - } -} - -void printVersion() { - int len = strlen_P(versionStr); - char myChar; - for (unsigned char k = 0; k < len; k++) { - myChar = pgm_read_byte_near(versionStr + k); - Serial.print(myChar); - } -} - - -/* - common interrupt handler. "port" is the PCINT port number (0-2) - - do counting and set start / end time of interval. - reporting is not triggered from here. - - only here counter[] is modified - lastTime[] is set here and in report - startTime[] is set in case a pin was not initialized yet and in report -*/ -static void PCint(uint8_t port) { - uint8_t bit; - uint8_t curr; - uint8_t mask; - uint8_t pcintPin; - unsigned long now = millis(); -#ifdef enablePulseLenChecking - unsigned long len, gap; -#endif - // get the pin states for the indicated port. - curr = *portInputRegister(port+2); // current pin states at port - mask = curr ^ PCintLast[port]; // xor gets bits that are different - PCintLast[port] = curr; // store new pin state for next interrupt - - if ((mask &= *port_to_pcmask[port]) == 0) // mask is pins that have changed. screen out non pcint pins. - return; /* no handled pin changed */ - - for (uint8_t i=0; i < 8; i++) { - bit = 0x01 << i; // loop over each pin that changed - if (bit & mask) { // did this pin change? - pcintPin = port * 8 + i; // pcint pin numbers follow the bits, only arduino pin nums are special - - // count if mode is CHANGE, or if RISING and bit is high, or if mode is FALLING and bit is low. - if ((PCintMode[pcintPin] == CHANGE - || ((PCintMode[pcintPin] == RISING) && (curr & bit)) - || ((PCintMode[pcintPin] == FALLING) && !(curr & bit)))) { -#ifdef enablePulseLenChecking - if (pulseWidthMin[pcintPin]) { // check minimal pulse length and gap - if ( ( (curr & bit) && pulseWidthStart[pcintPin] == RISING) - || (!(curr & bit) && pulseWidthStart[pcintPin] == FALLING)) { // edge does fit defined start - lastPulseStart[pcintPin] = now; - continue; - } else { // End of defined pulse - gap = lastPulseStart[pcintPin] - lastPulseEnd[pcintPin]; - len = now - lastPulseStart[pcintPin]; - lastPulseEnd[pcintPin] = now; - if (len < pulseWidthMin[pcintPin] || gap < pulseWidthMin[pcintPin]) { - rejectCounter[pcintPin]++; // pulse too short - continue; - } - pulseWidthSum[pcintPin] += len; // for average calculation - } - } -#endif - lastTime[pcintPin] = now; // remember time of in case pulse will be the last in the interval - if (!startTimeRepInt[pcintPin]) startTimeRepInt[pcintPin] = now; // time of first impulse in this reporting interval - if (initialized[pcintPin]) { - counter[pcintPin]++; // count - } else { - startTime[pcintPin] = lastTime[pcintPin]; // if this is the very first impulse on this pin -> start interval now - initialized[pcintPin] = true; // and start counting the next impulse (so far counter is 0) - } - } - } - } -} - - -/* - report count and time for pins that are between min and max interval - - lastCount[] is only modified here (count at time of last reporting) - lastTime[] is modified here and in ISR - disable interrupts in critcal moments to avoid garbage in var - startTime[] is modified only here (or for very first Interrupt in ISR) -> no problem. -*/ -void report() { - int aPin; - unsigned long count, countDiff; - unsigned long timeDiff, now; - unsigned long startT, endT; - unsigned long avgLen; - now = millis(); - for (int pcintPin=0; pcintPin < MAX_PCINT_PIN; pcintPin++) { // go through all observed pins as PCINT pin number - aPin = PCintActivePin[pcintPin]; // take saved arduino pin number - if (aPin < 0) continue; // -1 means pin is not active for reporting - noInterrupts(); - startT = startTime[pcintPin]; - endT = lastTime[pcintPin]; - count = counter[pcintPin]; // get current counter - interrupts(); - - timeDiff = endT - startT; // time between first and last impulse during interval - countDiff = count - lastCount[pcintPin]; // how many impulses since last report? (works with wrapping) - - if((long)(now - (lastReport[pcintPin] + intervalMax)) >= 0) { // intervalMax is over - if ((countDiff >= countMin) && (timeDiff > intervalSml) && (intervalMin != intervalMax)) { - // normal procedure - lastCount[pcintPin] = count; // remember current count for next interval - noInterrupts(); - startTime[pcintPin] = endT; // time of last impulse in this interval becomes also time of first impulse in next - interrupts(); - } else { - // nothing counted or counts happened during a fraction of intervalMin only - noInterrupts(); - lastTime[pcintPin] = now; // don't calculate with last impulse, use now instead - startTime[pcintPin] = now; // start a new interval for next report now - interrupts(); - lastCount[pcintPin] = count; // remember current count for next interval - timeDiff = now - startT; // special handling - calculation ends now instead of last impulse - } - } else if((long)(now - (lastReport[pcintPin] + intervalMin)) >= 0) { // minInterval has elapsed - if ((countDiff >= countMin) && (timeDiff > intervalSml)) { - // normal procedure - lastCount[pcintPin] = count; // remember current count for next interval - noInterrupts(); - startTime[pcintPin] = endT; // time of last impulse in this interval becomes also time of first impulse in next - interrupts(); - } else continue; // not enough counted - wait - } else continue; // intervalMin not over - wait - - Serial.print(F("R")); // R Report - Serial.print(aPin); - Serial.print(F(" C")); // C - Count - Serial.print(count); - Serial.print(F(" D")); // D - Count Diff - Serial.print(countDiff); - Serial.print(F(" T")); // T - Time - Serial.print(timeDiff); - Serial.print(F(" N")); // N - now - Serial.print((long)now); - -#ifdef enablePulseLenChecking - // rejected count ausgeben - // evt auch noch average pulse len und gap len - if (pulseWidthMin[pcintPin]) { // check minimal pulse length and gap - Serial.print(F(" X")); // X Reject - Serial.print(rejectCounter[pcintPin] - lastRejCount[pcintPin]); - noInterrupts(); - lastRejCount[pcintPin] = rejectCounter[pcintPin]; - interrupts(); - } -#endif - - if (countDiff) { - Serial.print(F(" F")); // F - first impulse after the one that started the interval - Serial.print((long)startTimeRepInt[pcintPin] - startT); - Serial.print(F(" L")); // L - last impulse - marking the end of this interval - Serial.print((long)endT - startT); - startTimeRepInt[pcintPin] = 0; - -#ifdef enablePulseLenChecking - if (pulseWidthMin[pcintPin]) {// check minimal pulse length and gap - noInterrupts(); - avgLen = pulseWidthSum[pcintPin] / countDiff; - pulseWidthSum[pcintPin] = 0; - interrupts(); - Serial.print(F(" A")); - Serial.print(avgLen); - } -#endif - } - Serial.println(); - lastReport[pcintPin] = now; // remember when we reported - } -} - - -/* print status for one pin */ -void showPin(byte pcintPin) { - unsigned long newCount; - unsigned long countDiff; - unsigned long timeDiff; - unsigned long avgLen; - - timeDiff = lastTime[pcintPin] - startTime[pcintPin]; - newCount = counter[pcintPin]; - countDiff = newCount - lastCount[pcintPin]; - if (!timeDiff) - timeDiff = millis() - startTime[pcintPin]; - - Serial.print(F("PCInt pin ")); - Serial.print(pcintPin); - - Serial.print(F(", iMode ")); - switch (PCintMode[pcintPin]) { - case RISING: Serial.print(F("rising")); break; - case FALLING: Serial.print(F("falling")); break; - case CHANGE: Serial.print(F("change")); break; - } -#ifdef enablePulseLenChecking - if (pulseWidthMin[pcintPin] > 0) { - Serial.print(F(", min len ")); - Serial.print(pulseWidthMin[pcintPin]); - Serial.print(F(" ms")); - switch (pulseWidthStart[pcintPin]) { - case RISING: Serial.print(F(" rising")); break; - case FALLING: Serial.print(F(" falling")); break; - } - } else { - Serial.print(F(", no min len")); - } -#endif - Serial.print(F(", count ")); - Serial.print(newCount); - Serial.print(F(" (+")); - Serial.print(countDiff); - Serial.print(F(") in ")); - Serial.print(timeDiff); - Serial.print(F(" ms")); -#ifdef enablePulseLenChecking - // rejected count ausgeben - // evt auch noch average pulse len und gap len - if (pulseWidthMin[pcintPin]) { // check minimal pulse length and gap - Serial.print(F(" Rej ")); - Serial.print(rejectCounter[pcintPin] - lastRejCount[pcintPin]); - } -#endif - if (countDiff) { - Serial.println(); - Serial.print(F("M first at ")); - Serial.print((long)startTimeRepInt[pcintPin] - lastReport[pcintPin]); - Serial.print(F(", last at ")); - Serial.print((long)lastTime[pcintPin] - lastReport[pcintPin]); -#ifdef enablePulseLenChecking - noInterrupts(); - avgLen = pulseWidthSum[pcintPin] / countDiff; - interrupts(); - Serial.print(F(", avg len ")); - Serial.print(avgLen); -#endif - } -} - - -/* give status report in between if requested over serial input */ -void showCmd() { - unsigned long newCount; - unsigned long countDiff; - unsigned long timeDiff; - unsigned long avgLen; - char myChar; - - Serial.print(F("M Status: ")); - printVersion(); - Serial.println(); - Serial.print(F("M normal interval ")); - Serial.println(intervalMin); - Serial.print(F("M max interval ")); - Serial.println(intervalMax); - Serial.print(F("M min interval ")); - Serial.println(intervalSml); - Serial.print(F("M min count ")); - Serial.println(countMin); - - for (byte pcintPin=0; pcintPin < MAX_PCINT_PIN; pcintPin++) { - int aPin = PCintActivePin[pcintPin]; - if (aPin != -1) { - timeDiff = lastTime[pcintPin] - startTime[pcintPin]; - newCount = counter[pcintPin]; - countDiff = newCount - lastCount[pcintPin]; - if (!timeDiff) - timeDiff = millis() - startTime[pcintPin]; - Serial.print(F("M pin ")); - Serial.print(aPin); - Serial.print(F(" ")); - showPin(pcintPin); - Serial.println(); - } - } - Serial.print(F("M Next report in ")); - Serial.print(timeNextReport - millis()); - Serial.print(F(" Milliseconds")); - Serial.println(); -} - - -/* - handle add command. -*/ -void addCmd(unsigned int *values, byte size) { - uint8_t pcintPin; // PCINT pin number for the pin to be added (used as index for most arrays) - byte mode; - unsigned int pw; - unsigned long now = millis(); - - //Serial.println(F("M Add called")); - int aPin = values[0]; - pcintPin = digitalPinToPcIntPin(aPin); - if (aPin >= MAX_ARDUINO_PIN || aPin < 1 - || allowedPins[aPin] == 0 || pcintPin > MAX_PCINT_PIN) { - PrintErrorMsg(); - Serial.print(F("Illegal pin specification ")); - Serial.println(aPin); - return; - }; - - switch (values[1]) { - case 2: - mode = FALLING; - pulseWidthStart[pcintPin] = FALLING; - break; - case 3: - mode = RISING; - pulseWidthStart[pcintPin] = RISING; - break; - case 1: - mode = CHANGE; - break; - default: - PrintErrorMsg(); - Serial.print(F("Illegal pin specification ")); - Serial.println(aPin); - } - - pinMode (aPin, INPUT); - if (values[2]) { - digitalWrite (aPin, HIGH); // enable pullup resistor - } - -#ifdef enablePulseLenChecking - PulseMode[pcintPin] = mode; // specified mode also defines pulse level in this case - if (values[3] > 0) { - pw = values[3]; - mode = CHANGE; - } else { - pw = 0; - } -#endif - - if (!AddPinChangeInterrupt(aPin)) { // add Pin Change Interrupt - PrintErrorMsg(); Serial.println(F("AddInt")); - return; - } - PCintMode[pcintPin] = mode; // save mode for ISR which uses the pcintPin as index - -#ifdef enablePulseLenChecking - pulseWidthMin[pcintPin] = pw; // minimal pulse width in millis, 3 if not specified n add cmd -#endif - - if (PCintActivePin[pcintPin] != aPin) { // in case this pin is already active counting - PCintActivePin[pcintPin] = aPin; // save real arduino pin number and flag this pin as active for reporting - initialized[pcintPin] = false; // initialize arrays for this pin - counter[pcintPin] = 0; - lastCount[pcintPin] = 0; - startTime[pcintPin] = now; - lastTime[pcintPin] = now; - lastReport[pcintPin] = now; - } - Serial.print(F("M defined pin ")); - Serial.print(aPin); - Serial.print(F(" ")); - showPin(pcintPin); - Serial.println(); - -} - - -/* - handle rem command. -*/ -void removeCmd(unsigned int *values, byte size) { - uint8_t pcintPin; // PCINT pin number for the pin to be added (used as index for most arrays) - int aPin = values[0]; - pcintPin = digitalPinToPcIntPin(aPin); - if (aPin >= MAX_ARDUINO_PIN || aPin < 1 - || allowedPins[aPin] == 0 || pcintPin > MAX_PCINT_PIN) { - PrintErrorMsg(); - Serial.print(F("Illegal pin specification ")); - Serial.println(aPin); - return; - }; - - if (!RemovePinChangeInterrupt(aPin)) { - PrintErrorMsg(); Serial.println(F("RemInt")); - return; - } - - PCintActivePin[pcintPin] = -1; - initialized[pcintPin] = false; // reset for next add - counter[pcintPin] = 0; - lastCount[pcintPin] = 0; -#ifdef enablePulseLenChecking - pulseWidthMin[pcintPin] = 0; - lastRejCount[pcintPin] = 0; - rejectCounter[pcintPin] = 0; -#endif - - Serial.print(F("M removed ")); - Serial.println(aPin); -} - - - -void intervalCmd(unsigned int *values, byte size) { - if (size < 4) { - PrintErrorMsg(); - Serial.print(F("size")); - Serial.println(); - return; - } - if (values[0] < 1 || values[0] > 3600) { - PrintErrorMsg(); Serial.println(values[0]); - return; - } - intervalMin = (long)values[0] * 1000; - if (millis() + intervalMin < timeNextReport) - timeNextReport = millis() + intervalMin; - - if (values[1] < 1 || values[1] > 3600) { - PrintErrorMsg(); Serial.println(values[1]); - return; - } - intervalMax = (long)values[1]* 1000; - - if (values[2] > 3600) { - PrintErrorMsg(); Serial.println(values[2]); - return; - } - if (values[2] > 0) { - intervalSml = (long)values[2] * 1000; - } - - if (values[3]> 0) { - countMin = values[3]; - } - Serial.print(F("M intervals set to ")); - Serial.print(values[0]); - Serial.print(F(" ")); - Serial.print(values[1]); - Serial.print(F(" ")); - Serial.print(values[2]); - Serial.print(F(" ")); - Serial.print(values[3]); - Serial.println(); -} - - -void helloCmd() { - Serial.println(); - printVersion(); - Serial.println(F("Hello")); -} - - -static void HandleSerialPort(char c) { - static unsigned int value; - - if (c == ',') { - if (commandDataPointer + 1 < MAX_INPUT_NUM) { - commandData[commandDataPointer++] = value; - value = 0; - } - } - else if ('0' <= c && c <= '9') { - value = 10 * value + c - '0'; - } - else if ('a' <= c && c <= 'z') { - switch (c) { - case 'a': - commandData[commandDataPointer] = value; - addCmd(commandData, ++commandDataPointer); - commandDataPointer = 0; - break; - - case 'd': - commandData[commandDataPointer] = value; - removeCmd(commandData, ++commandDataPointer); - commandDataPointer = 0; - break; - - case 'i': - commandData[commandDataPointer] = value; - intervalCmd(commandData, ++commandDataPointer); - commandDataPointer = 0; - break; - - case 'r': - setup(); - commandDataPointer = 0; - break; - - case 's': - showCmd(); - commandDataPointer = 0; - break; - - case 'h': - helloCmd(); - commandDataPointer = 0; - break; - - default: - commandDataPointer = 0; - //PrintErrorMsg(); Serial.println(); - break; - } - value = 0; - } -} - - - -SIGNAL(PCINT0_vect) { - PCint(0); -} -SIGNAL(PCINT1_vect) { - PCint(1); -} -SIGNAL(PCINT2_vect) { - PCint(2); -} - - -void setup() { - unsigned long now = millis(); - - for (int pcintPin=0; pcintPin < MAX_PCINT_PIN; pcintPin++) { - PCintActivePin[pcintPin] = -1; // set all pins to inactive (-1) - initialized[pcintPin] = false; // initialize arrays for this pin - counter[pcintPin] = 0; - lastCount[pcintPin] = 0; - startTime[pcintPin] = now; - lastTime[pcintPin] = now; -#ifdef enablePulseLenChecking - lastPulseStart[pcintPin] = now; - lastPulseEnd[pcintPin] = now; - pulseWidthMin[pcintPin] = 0; - rejectCounter[pcintPin] = 0; - lastRejCount[pcintPin] = 0; -#endif - lastReport[pcintPin] = now; - } - - timeNextReport = millis() + intervalMin; // time for first output - Serial.begin(SERIAL_SPEED); // initialize serial - delay (500); - interrupts(); - Serial.println(); - printVersion(); - Serial.println(F("Started")); -} - - -/* - 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() { - unsigned long now = millis(); - - if (Serial.available()) { - HandleSerialPort(Serial.read()); - } - boolean doReport = false; // check if report nedds to be called - if((long)(now - timeNextReport) >= 0) // works fine when millis wraps. - doReport = true; // intervalMin is over - else - for (byte pcintPin=0; pcintPin < MAX_PCINT_PIN; pcintPin++) - if (PCintActivePin[pcintPin] >= 0) - if((long)(now - (lastReport[pcintPin] + intervalMax)) >= 0) - doReport = true; // active pin has not been reported for langer than intervalMax - if (doReport) { - report(); - timeNextReport = now + intervalMin; // do it again after intervalMin millis - } -} - diff --git a/fhem/contrib/arduino/ArduCounter2.00.ino b/fhem/contrib/arduino/ArduCounter2.00.ino deleted file mode 100755 index 0cba95fcd..000000000 --- a/fhem/contrib/arduino/ArduCounter2.00.ino +++ /dev/null @@ -1,899 +0,0 @@ -/* - * 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 board - * - * the sketch uses pin change interrupts which can be anabled - * for any of the inputs on e.g. an arduino uno or a jeenode - * - * the pin change Interrupt handling used here - * is based on the arduino playground example on PCINT: - * http://playground.arduino.cc/Main/PcInt - * - * Refer to avr-gcc header files, arduino source and atmega datasheet. - */ - -/* 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 - */ - -/* to test pin 4 with interval 10-20 sec do - * 4,2,1,30a - * 10,20,2,0i - */ - -/* - 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 byte 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) - - ToDo / Ideas: - - new index scheme to save memory: - define new array to map from pcintPin to new index, limit allowed pins. - unused pcintpins point to -1 (or some other unused number < 0) and comment states arduino pin number - instead of allowedPins array use new function from aPin to pcintPin - and then look up in new array for index or -1 -*/ - -#include "pins_arduino.h" - -const char versionStr[] PROGMEM = "ArduCounter V2.05"; -const char errorStr[] PROGMEM = "Error: "; - -#define SERIAL_SPEED 38400 -#define MAX_ARDUINO_PIN 24 -#define MAX_PCINT_PIN 24 -#define MAX_INPUT_NUM 8 -#define MAX_HIST 20 - -/* arduino pins that are typically ok to use - * (some are left out because they are used - * as reset, serial, led or other things on most boards) */ -byte allowedPins[MAX_ARDUINO_PIN] = - { 0, 0, 0, 3, 4, 5, 6, 7, - 8, 9, 10, 11, 12, 13, - 14, 15, 16, 17, 0, 0}; - - -/* Pin change mask for each chip port */ -volatile uint8_t *port_to_pcmask[] = { - &PCMSK0, - &PCMSK1, - &PCMSK2 -}; - -/* last PIN States to detect individual pin changes in ISR */ -volatile static uint8_t PCintLast[3]; - -unsigned long intervalMin = 30000; // default 30 sec - report after this time if nothing else delays it -unsigned long intervalMax = 60000; // default 60 sec - report after this time if it didin't happen before -unsigned long intervalSml = 2000; // default 2 secs - continue count if timeDiff is less and intervalMax not over -unsigned int countMin = 1; // continue counting if count is less than this and intervalMax not over - -unsigned long timeNextReport; - -/* index to the following arrays is the internal PCINT pin number, not the arduino - * pin number because the PCINT pin number corresponds to the physical ports - * and this saves time for mapping to the arduino numbers - */ - -/* did we get first interrupt yet? */ -volatile boolean initialized[MAX_PCINT_PIN]; - -/* individual counters for each real pin */ -volatile unsigned long counter[MAX_PCINT_PIN]; -volatile uint8_t counterIgn[MAX_PCINT_PIN]; // ignored first pulse after init -volatile unsigned int rejectCounter[MAX_PCINT_PIN]; - -/* millis at last level change (for measuring pulse length) */ -volatile unsigned long lastChange[MAX_PCINT_PIN]; - -/* last valid level */ -volatile uint8_t lastLevel[MAX_PCINT_PIN]; - -/* sum of pulse lengths for average output */ -volatile unsigned long pulseWidthSum[MAX_PCINT_PIN]; - - -/* count at last report to get difference */ -unsigned long lastCount[MAX_PCINT_PIN]; -unsigned int lastRejCount[MAX_PCINT_PIN]; - -/* history ring */ -volatile uint8_t histIndex; -volatile uint8_t histPin[MAX_HIST]; -volatile uint8_t histLevel[MAX_HIST]; -volatile unsigned long histTime[MAX_HIST]; -volatile unsigned long histLen[MAX_HIST]; -volatile char histAct[MAX_HIST]; -//volatile uint8_t histI1[MAX_HIST]; - - -/* real arduino pin number for PCINT number if active - otherwise 0 */ -uint8_t PCintActivePin[MAX_PCINT_PIN]; - -/* pin change mode (RISING etc.) as parameter for ISR */ -uint8_t PCintMode[MAX_PCINT_PIN]; - -/* minimal pulse length in millis for filtering */ -unsigned int pulseWidthMin[MAX_PCINT_PIN]; - -/* start of pulse for measuring length */ -uint8_t pulseWidthStart[MAX_PCINT_PIN]; // FALLING or RISING as defined for each pin - -/* start and end of an interval - typically set by first / last pulse */ -volatile unsigned long intervalStart[MAX_PCINT_PIN]; -volatile unsigned long intervalEnd[MAX_PCINT_PIN]; - -/* millis at first interrupt in a reporting cycle */ -volatile unsigned long firstPulse[MAX_PCINT_PIN]; - -/* millis at last report - * to find out when maxInterval is over - * and report has to be done even if - * no impulses were counted */ -unsigned long lastReport[MAX_PCINT_PIN]; - -/* input data over serial port */ -unsigned int commandData[MAX_INPUT_NUM]; -uint8_t commandDataPointer = 0; - - - -int digitalPinToPcIntPin(uint8_t aPin) { - uint8_t pcintPin; // PCINT pin number for the pin to be added (index for most arrays) - uint8_t portIdx = digitalPinToPort(aPin)-2; // index of port that this arduno pin belongs to for enabling interrupts - // since the macro maps to defines PB(=2), PC(=3) and PD(=4), we subtract 2 - // to use the result as array index in this sketch - - if (portIdx == 1) { // now calculate the PCINT pin number that corresponds to the arduino pin number - pcintPin = aPin - 6; // portIdx 1: PC0-PC5 (A0-A5 or D14-D19) is PCINT 8-13 (PC6 is reset) - } else { // arduino numbering continues at D14 since PB6/PB7 are used for other things - pcintPin = portIdx*8 + (aPin % 8); // portIdx 0: PB0-PB5 (D8-D13) is PCINT 0-5 (PB6/PB7 is crystal) - } // portIdx 2: PD0-PD7 (D0-D7) is PCINT 16-23 - return pcintPin; -} - - -/* Add a pin to be handled */ -byte AddPinChangeInterrupt(uint8_t aPin) { - volatile uint8_t *pcmask; // pointer to PCMSK0 or 1 or 2 depending on the port corresponding to the pin - - uint8_t bitM = digitalPinToBitMask(aPin); // mask to bit in PCMSK to enable pin change interrupt for this arduino pin - uint8_t port = digitalPinToPort(aPin); // 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 */ -byte RemovePinChangeInterrupt(uint8_t aPin) { - volatile uint8_t *pcmask; - - uint8_t bitM = digitalPinToBitMask(aPin); - uint8_t port = digitalPinToPort(aPin); - - 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; -} - - - -void PrintErrorMsg() { - int len = strlen_P(errorStr); - char myChar; - for (unsigned char k = 0; k < len; k++) { - myChar = pgm_read_byte_near(errorStr + k); - Serial.print(myChar); - } -} - -void printVersion() { - int len = strlen_P(versionStr); - char myChar; - for (unsigned char k = 0; k < len; k++) { - myChar = pgm_read_byte_near(versionStr + k); - Serial.print(myChar); - } -} - - -/* - common interrupt handler. "port" is the PCINT port index (0-2), not PB, PC or PD which are mapped to 2-4 - - 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 PCint(uint8_t port) { - uint8_t bit; - uint8_t curr; - uint8_t delta; - uint8_t level; - uint8_t pulseLevel; - uint8_t pcintPin; - unsigned long len; - unsigned long 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]; // xor gets bits that are different - PCintLast[port] = curr; // store new pin state for next interrupt - - if ((delta &= *port_to_pcmask[port]) == 0) // delta is pins that have changed. screen out non pcint pins. - return; /* no handled pin changed */ - - for (uint8_t i=0; i < 8; i++) { // loop over each pin on the given port that changed - bit = 0x01 << i; - if (delta & bit) { // did this pin change? - pcintPin = port * 8 + i; // pcint pin numbers follow the bits, only arduino pin nums are special - level = ((curr & bit) > 0); - pulseLevel = (pulseWidthStart[pcintPin] == RISING); // RISING means that pulse is at high level - - len = now - lastChange[pcintPin]; - histIndex++; - if (histIndex >= MAX_HIST) histIndex = 0; - histPin[histIndex] = pcintPin; - histTime[histIndex] = lastChange[pcintPin]; - histLen[histIndex] = len; - histLevel[histIndex] = !level; // before it changed - histAct[histIndex] = ' '; - //histI1[histIndex] = lastLevel[pcintPin]; - - // go on if mode is CHANGE, or if RISING and bit is high, or if mode is FALLING and bit is low. - if (PCintMode[pcintPin] == CHANGE || level == pulseLevel) { - - if (pulseWidthMin[pcintPin]) { // if minimal pulse length defined then check minimal pulse length and gap - - if (len < pulseWidthMin[pcintPin]) { - lastChange[pcintPin] = now; - if (level != pulseLevel) { // if change to gap level - rejectCounter[pcintPin]++; // pulse too short - histAct[histIndex] = 'R'; - } else { - histAct[histIndex] = 'X'; - } - } else { - - if (level == pulseLevel) { // edge does fit defined start, level is now pulse - // potential end of a valid gap, now we are at pulse level - if (lastLevel[pcintPin] == pulseLevel) { // last remembered valid level was also pulse - // last remembered valid level was pulse, now the gap was confirmed. - histAct[histIndex] = 'G'; - } else { - // last remembered valid level was a gap -> now we had another valid gap -> inbetween was only a spike -> ignore - histAct[histIndex] = 'G'; - } - - } else { // edge is a change to gap, level is now gap - // potential end of a valid pulse, now we are at gap level - if (lastLevel[pcintPin] != pulseLevel) { // last remembered valid level was also gap - // last remembered valid level was a gap -> now we had valid new pulse -> count - - intervalEnd[pcintPin] = now; // remember time of in case pulse will be the last in the interval - if (!firstPulse[pcintPin]) firstPulse[pcintPin] = now; // time of first impulse in this reporting interval - if (initialized[pcintPin]) { - counter[pcintPin]++; // count - } else { - counter[pcintPin]++; // count - counterIgn[pcintPin]++; // count as to be ignored for diff because it defines the start of the interval - intervalStart[pcintPin] = now; // if this is the very first impulse on this pin -> start interval now - initialized[pcintPin] = true; // and start counting the next impulse (so far counter is 0) - } - pulseWidthSum[pcintPin] += len; // for average calculation - histAct[histIndex] = 'C'; - } else { - // last remembered valid level was a pulse -> now we had another valid pulse - // inbetween was an invalid drop so pulse is already counted. - pulseWidthSum[pcintPin] += len; // for average calculation - histAct[histIndex] = 'P'; - } - } // change to gap level - - // remember this valid level as lastLevel - lastLevel[pcintPin] = !level; // before it changed - - } // if pulse is not too short - - } // if pulseWidth checking - } - lastChange[pcintPin] = now; - } // if bit changed - } // for -} - - -/* show pulse history ring */ -void showHistory() { - uint8_t hi; - Serial.println (F("D pulse history: ")); - unsigned long now = millis(); - unsigned long last; - uint8_t start = (histIndex + 2) % MAX_HIST; - for (uint8_t i = 0; i < MAX_HIST; i++) { - hi = (start + i) % MAX_HIST; - if (i == 0 || (last <= histTime[hi]+histLen[hi])) { - Serial.print (F("D pin ")); - Serial.print (PCintActivePin[histPin[hi]]); - Serial.print (F(" start ")); - Serial.print ((long) (histTime[hi] - now)); - Serial.print (F(" len ")); - Serial.print (histLen[hi]); - Serial.print (F(" at ")); - Serial.print (histLevel[hi]); - Serial.print (F(" ")); - Serial.print (histAct[hi]); - Serial.println(); - } - last = histTime[hi]; - } -} - - -/* - report count and time for pins that are between min and max interval - - 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) -> no problem. -*/ -void report() { - int aPin; - unsigned long count, countIgn, countDiff, realDiff; - unsigned long timeDiff, now; - unsigned long startT, endT; - unsigned long avgLen; - now = millis(); - - for (int pcintPin=0; pcintPin < MAX_PCINT_PIN; pcintPin++) { // go through all observed pins as PCINT pin number - aPin = PCintActivePin[pcintPin]; // take saved arduino pin number - if (aPin < 1) continue; // 0 means pin is not active for reporting - noInterrupts(); - startT = intervalStart[pcintPin]; - endT = intervalEnd[pcintPin]; - count = counter[pcintPin]; // get current counter (counts all pulses - countIgn = counterIgn[pcintPin]; // pulses that mark the beginning of an interval and should not be taken into calculation (happens after restart) - interrupts(); - - timeDiff = endT - startT; // time between first and last impulse during interval - countDiff = count - countIgn - lastCount[pcintPin]; // how many pulses during intervall since last report? (ignore forst pulse after device restart) - realDiff = count - lastCount[pcintPin]; // (works with wrapping) - if((long)(now - (lastReport[pcintPin] + intervalMax)) >= 0) { // intervalMax is over - if ((countDiff >= countMin) && (timeDiff > intervalSml) && (intervalMin != intervalMax)) { - // normal procedure - lastCount[pcintPin] = count; // remember current count for next interval - noInterrupts(); - intervalStart[pcintPin] = endT; // time of last impulse in this interval becomes also time of first impulse in next - counterIgn[pcintPin] = 0; - interrupts(); - } else { - // nothing counted or counts happened during a fraction of intervalMin only - noInterrupts(); - intervalEnd[pcintPin] = now; // don't calculate with last impulse, use now instead - intervalStart[pcintPin] = now; // start a new interval for next report now - counterIgn[pcintPin] = 0; - interrupts(); - lastCount[pcintPin] = count; // remember current count for next interval - timeDiff = now - startT; // special handling - calculation ends now instead of last impulse - } - } else if((long)(now - (lastReport[pcintPin] + intervalMin)) >= 0) { // minInterval has elapsed - if ((countDiff >= countMin) && (timeDiff > intervalSml)) { - // normal procedure - lastCount[pcintPin] = count; // remember current count for next interval - noInterrupts(); - intervalStart[pcintPin] = endT; // time of last impulse in this interval becomes also time of first impulse in next - counterIgn[pcintPin] = 0; - interrupts(); - } else continue; // not enough counted - wait - } else continue; // intervalMin not over - wait - - Serial.print(F("R")); // R Report - Serial.print(aPin); - Serial.print(F(" C")); // C - Count - Serial.print(count); - Serial.print(F(" D")); // D - Count Diff (without pulse that marks the begin of an interval) - Serial.print(countDiff); - Serial.print(F(" R")); // R - real Diff for incrementing long counter in Fhem - includes even the first pulse after restart - Serial.print(realDiff); - Serial.print(F(" T")); // T - Time - Serial.print(timeDiff); - Serial.print(F(" N")); // N - now - Serial.print((long)now); - - // rejected count ausgeben - // evt auch noch average pulse len und gap len - if (pulseWidthMin[pcintPin]) { // check minimal pulse length and gap - Serial.print(F(" X")); // X Reject - Serial.print(rejectCounter[pcintPin] - lastRejCount[pcintPin]); - noInterrupts(); - lastRejCount[pcintPin] = rejectCounter[pcintPin]; - interrupts(); - } - - if (realDiff) { - Serial.print(F(" F")); // F - first impulse after the one that started the interval - Serial.print((long)firstPulse[pcintPin] - startT); - Serial.print(F(" L")); // L - last impulse - marking the end of this interval - Serial.print((long)endT - startT); - firstPulse[pcintPin] = 0; - - if (pulseWidthMin[pcintPin]) {// check minimal pulse length and gap - noInterrupts(); - avgLen = pulseWidthSum[pcintPin] / countDiff; - pulseWidthSum[pcintPin] = 0; - interrupts(); - Serial.print(F(" A")); - Serial.print(avgLen); - } - } - - uint8_t hi; - boolean first = true; - uint8_t start = (histIndex + 2) % MAX_HIST; - unsigned long last; - Serial.print (F(" H")); - for (uint8_t i = 0; i < MAX_HIST; i++) { - hi = (start + i) % MAX_HIST; - if (histPin[hi] == pcintPin) { - if (first || (last <= histTime[hi]+histLen[hi])) { - if (!first) - Serial.print (F(", ")); - //Serial.print (F("")); - Serial.print ((long) (histTime[hi] - now)); - Serial.print (F("/")); - Serial.print (histLen[hi]); - Serial.print (F(":")); - Serial.print (histLevel[hi]); - //Serial.print (F(" ")); - Serial.print (histAct[hi]); - first = false; - } - last = histTime[hi]; - } - } - - Serial.println(); - lastReport[pcintPin] = now; // remember when we reported - } -} - - -/* print status for one pin */ -void showPin(byte pcintPin) { - unsigned long newCount, countIgn, countDiff; - unsigned long timeDiff; - unsigned long avgLen; - - timeDiff = intervalEnd[pcintPin] - intervalStart[pcintPin]; - newCount = counter[pcintPin]; - countIgn = counterIgn[pcintPin]; - countDiff = newCount - countIgn - lastCount[pcintPin]; - if (!timeDiff) - timeDiff = millis() - intervalStart[pcintPin]; - - Serial.print(F("PCInt pin ")); - Serial.print(pcintPin); - - Serial.print(F(", iMode ")); - switch (PCintMode[pcintPin]) { - case RISING: Serial.print(F("rising")); break; - case FALLING: Serial.print(F("falling")); break; - case CHANGE: Serial.print(F("change")); break; - } - if (pulseWidthMin[pcintPin] > 0) { - Serial.print(F(", min len ")); - Serial.print(pulseWidthMin[pcintPin]); - Serial.print(F(" ms")); - switch (pulseWidthStart[pcintPin]) { - case RISING: Serial.print(F(" rising")); break; - case FALLING: Serial.print(F(" falling")); break; - } - } else { - Serial.print(F(", no min len")); - } - Serial.print(F(", count ")); - Serial.print(newCount); - Serial.print(F(" (+")); - Serial.print(countDiff); - Serial.print(F(") in ")); - Serial.print(timeDiff); - Serial.print(F(" ms")); - - // rejected count ausgeben - // evt auch noch average pulse len und gap len - if (pulseWidthMin[pcintPin]) { // check minimal pulse length and gap - Serial.print(F(" Rej ")); - Serial.print(rejectCounter[pcintPin] - lastRejCount[pcintPin]); - } - if (countDiff) { - Serial.println(); - Serial.print(F("M first at ")); - Serial.print((long)firstPulse[pcintPin] - lastReport[pcintPin]); - Serial.print(F(", last at ")); - Serial.print((long)intervalEnd[pcintPin] - lastReport[pcintPin]); - noInterrupts(); - avgLen = pulseWidthSum[pcintPin] / countDiff; - interrupts(); - Serial.print(F(", avg len ")); - Serial.print(avgLen); - } -} - - -/* give status report in between if requested over serial input */ -void showCmd() { - Serial.print(F("M Status: ")); - printVersion(); - Serial.println(); - Serial.print(F("M normal interval ")); - Serial.println(intervalMin); - Serial.print(F("M max interval ")); - Serial.println(intervalMax); - Serial.print(F("M min interval ")); - Serial.println(intervalSml); - Serial.print(F("M min count ")); - Serial.println(countMin); - - for (byte pcintPin=0; pcintPin < MAX_PCINT_PIN; pcintPin++) { - int aPin = PCintActivePin[pcintPin]; - if (aPin > 0) { - Serial.print(F("M pin ")); - Serial.print(aPin); - Serial.print(F(" ")); - showPin(pcintPin); - Serial.println(); - } - } - Serial.print(F("M Next report in ")); - Serial.print(timeNextReport - millis()); - Serial.print(F(" Milliseconds")); - Serial.println(); - showHistory(); -} - - - -/* - handle add command. - todo: check size and clear options not passed -*/ -void addCmd(unsigned int *values, byte size) { - uint8_t pcintPin; // PCINT pin number for the pin to be added (used as index for most arrays) - byte mode = 2; - uint8_t port; - unsigned int pw; - unsigned long now = millis(); - - - //Serial.println(F("M Add called")); - int aPin = values[0]; // value 0 is pin number - pcintPin = digitalPinToPcIntPin(aPin); - if (aPin >= MAX_ARDUINO_PIN || aPin < 1 - || allowedPins[aPin] == 0 || pcintPin > MAX_PCINT_PIN) { - PrintErrorMsg(); - Serial.print(F("Illegal pin specification ")); - Serial.println(aPin); - return; - }; - port = digitalPinToPort(aPin) - 2; - - switch (values[1]) { // value 1 is rising / falling etc. - case 2: - mode = FALLING; - pulseWidthStart[pcintPin] = FALLING; - break; - case 3: - mode = RISING; - pulseWidthStart[pcintPin] = RISING; - break; - case 1: - mode = CHANGE; - break; - default: - PrintErrorMsg(); - Serial.print(F("Illegal mode for pin specification ")); - Serial.println(aPin); - } - - pinMode (aPin, INPUT); - if (size > 2 && values[2]) { // value 2 is pullup - digitalWrite (aPin, HIGH); // enable pullup resistor - } - - if (size > 3 && values[3] > 0) { // value 3 is min length (if given) - pw = values[3]; - mode = CHANGE; - } else { - pw = 0; - } - - if (!AddPinChangeInterrupt(aPin)) { // add Pin Change Interrupt - PrintErrorMsg(); Serial.println(F("AddInt")); - return; - } - PCintMode[pcintPin] = mode; // save mode for ISR which uses the pcintPin as index - - pulseWidthMin[pcintPin] = pw; // minimal pulse width in millis, 0 if not specified in add cmd todo: needs fixing! values[3] might contain data from last command - - if (PCintActivePin[pcintPin] != aPin) { // in case this pin is not already active counting - PCintLast[port] = *portInputRegister(port+2); // current pin states at port - PCintActivePin[pcintPin] = aPin; // save real arduino pin number and flag this pin as active for reporting - initialized[pcintPin] = false; // initialize arrays for this pin - counter[pcintPin] = 0; - counterIgn[pcintPin] = 0; - lastCount[pcintPin] = 0; - intervalStart[pcintPin] = now; - intervalEnd[pcintPin] = now; - lastReport[pcintPin] = now; // next reporting cycle is probably earlier than now+intervalMin (already started) so report will be later than next interval - lastChange[pcintPin] = now; - rejectCounter[pcintPin] = 0; - lastRejCount[pcintPin] = 0; - } - Serial.print(F("M defined pin ")); - Serial.print(aPin); - Serial.print(F(" ")); - showPin(pcintPin); - Serial.println(); -} - - -/* - handle rem command. -*/ -void removeCmd(unsigned int *values, byte size) { - uint8_t pcintPin; // PCINT pin number for the pin to be added (used as index for most arrays) - int aPin = values[0]; - pcintPin = digitalPinToPcIntPin(aPin); - if (size < 1 || aPin >= MAX_ARDUINO_PIN || aPin < 1 - || allowedPins[aPin] == 0 || pcintPin > MAX_PCINT_PIN) { - PrintErrorMsg(); - Serial.print(F("Illegal pin specification ")); - Serial.println(aPin); - return; - }; - - if (!RemovePinChangeInterrupt(aPin)) { - PrintErrorMsg(); Serial.println(F("RemInt")); - return; - } - - PCintActivePin[pcintPin] = 0; - initialized[pcintPin] = false; // reset for next add - counter[pcintPin] = 0; - counterIgn[pcintPin] = 0; - lastCount[pcintPin] = 0; - pulseWidthMin[pcintPin] = 0; - lastRejCount[pcintPin] = 0; - rejectCounter[pcintPin] = 0; - - Serial.print(F("M removed ")); - Serial.println(aPin); -} - - - -void intervalCmd(unsigned int *values, byte size) { - if (size < 4) { // i command always gets 4 values: min, max, sml, cntMin - PrintErrorMsg(); - Serial.print(F("size")); - Serial.println(); - return; - } - if (values[0] < 1 || values[0] > 3600) { - PrintErrorMsg(); Serial.println(values[0]); - return; - } - intervalMin = (long)values[0] * 1000; - if (millis() + intervalMin < timeNextReport) - timeNextReport = millis() + intervalMin; - - if (values[1] < 1 || values[1] > 3600) { - PrintErrorMsg(); Serial.println(values[1]); - return; - } - intervalMax = (long)values[1]* 1000; - - if (values[2] > 3600) { - PrintErrorMsg(); Serial.println(values[2]); - return; - } - intervalSml = (long)values[2] * 1000; - countMin = values[3]; - - Serial.print(F("M intervals set to ")); - Serial.print(values[0]); - Serial.print(F(" ")); - Serial.print(values[1]); - Serial.print(F(" ")); - Serial.print(values[2]); - Serial.print(F(" ")); - Serial.print(values[3]); - Serial.println(); -} - - -void helloCmd() { - Serial.println(); - printVersion(); - Serial.println(F("Hello")); -} - - -static void HandleSerialPort(char c) { - static unsigned int value; - - if (c == ',') { - if (commandDataPointer < (MAX_INPUT_NUM - 1)) { - commandData[commandDataPointer] = value; - commandDataPointer++; - value = 0; - } - } - else if ('0' <= c && c <= '9') { - value = 10 * value + c - '0'; - } - else if ('a' <= c && c <= 'z') { - switch (c) { - case 'a': - commandData[commandDataPointer] = value; - addCmd(commandData, ++commandDataPointer); - break; - case 'd': - commandData[commandDataPointer] = value; - removeCmd(commandData, ++commandDataPointer); - break; - case 'i': - commandData[commandDataPointer] = value; - intervalCmd(commandData, ++commandDataPointer); - break; - case 'r': - initialize(); - break; - case 's': - showCmd(); - break; - case 'h': - helloCmd(); - break; - default: - //PrintErrorMsg(); Serial.println(); - break; - } - commandDataPointer = 0; - value = 0; - for (byte i=0; i < MAX_INPUT_NUM; i++) - commandData[i] = 0; - } -} - - - -SIGNAL(PCINT0_vect) { - PCint(0); -} -SIGNAL(PCINT1_vect) { - PCint(1); -} -SIGNAL(PCINT2_vect) { - PCint(2); -} - - - -void initialize() { - unsigned long now = millis(); - - Serial.println(); - printVersion(); - Serial.println(F(" Started")); - - for (int pcintPin=0; pcintPin < MAX_PCINT_PIN; pcintPin++) { - PCintActivePin[pcintPin] = 0; // set all pins to inactive (0) - initialized[pcintPin] = false; // initialize arrays for this pin - counter[pcintPin] = 0; - counterIgn[pcintPin] = 0; - lastCount[pcintPin] = 0; - intervalStart[pcintPin] = now; - intervalEnd[pcintPin] = now; - lastChange[pcintPin] = now; - pulseWidthMin[pcintPin] = 0; - rejectCounter[pcintPin] = 0; - lastRejCount[pcintPin] = 0; - lastReport[pcintPin] = now; - } - - for (unsigned int port=0; port <= 2; port++) { - PCintLast[port] = *portInputRegister(port+2); // current pin states at port - } - - timeNextReport = millis() + intervalMin; // time for first output -} - - -void setup() { - Serial.begin(SERIAL_SPEED); // initialize serial - delay (500); - interrupts(); - initialize(); -} - - -/* - 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() { - unsigned long now = millis(); - - if (Serial.available()) { - HandleSerialPort(Serial.read()); - } - boolean doReport = false; // check if report nedds to be called - if((long)(now - timeNextReport) >= 0) // works fine when millis wraps. - doReport = true; // intervalMin is over - else - for (byte pcintPin=0; pcintPin < MAX_PCINT_PIN; pcintPin++) - if (PCintActivePin[pcintPin] > 0) - if((long)(now - (lastReport[pcintPin] + intervalMax)) >= 0) - doReport = true; // active pin has not been reported for langer than intervalMax - - if (doReport) { - report(); - timeNextReport = now + intervalMin; // do it again after intervalMin millis - } -} - diff --git a/fhem/contrib/arduino/ArduCounter2.36.ino b/fhem/contrib/arduino/ArduCounter3.20.ino similarity index 71% rename from fhem/contrib/arduino/ArduCounter2.36.ino rename to fhem/contrib/arduino/ArduCounter3.20.ino index 96a217a8b..88a336a09 100755 --- a/fhem/contrib/arduino/ArduCounter2.36.ino +++ b/fhem/contrib/arduino/ArduCounter3.20.ino @@ -1,1409 +1,1728 @@ -/* - * 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(); - } -} +/* + * Sketch for counting impulses in a defined interval + * e.g. for power meters with an s0 interface that can be + * connected to an input of an arduino or esp8266 board + * + * the sketch uses pin change interrupts which can be anabled + * for any of the inputs on e.g. an arduino uno, jeenode, wemos d1 etc. + * + * the pin change Interrupt handling for arduinos used here + * is based on the arduino playground example on PCINT: + * http://playground.arduino.cc/Main/PcInt which is outdated. + * + * see https://github.com/GreyGnome/EnableInterrupt for a newer library (not used here) + * and also + * https://playground.arduino.cc/Main/PinChangeInterrupt + * http://www.avrfreaks.net/forum/difference-between-signal-and-isr + * + * Refer to avr-gcc header files, arduino source and atmega datasheet. + */ +/* Arduino Uno / Nano Pin to interrupt map: + * D0-D7 = PCINT 16-23 = PCIR2 = PD = PCIE2 = pcmsk2 + * D8-D13 = PCINT 0-5 = PCIR0 = PB = PCIE0 = pcmsk0 + * A0-A5 (D14-D19) = PCINT 8-13 = PCIR1 = PC = PCIE1 = pcmsk1 + */ + +/* test cmds analog ESP: + * 20v Verbose + * 17,3,0,50a A0, rising, no Pullup, MinLen 50 + * 15,25t Level Diff Thresholds + * + * for ESP with D5 falling pullup 30 + * 5,2,1,30a + * 20v + * 10,20,1,1i + */ + +/* + Changes: + V1.2 + 27.10.16 - use noInterrupts in report() + - avoid reporting very short timeDiff in case of very slow impulses after a report + - now reporting is delayed if impulses happened only within in intervalSml + - reporting is also delayed if less than countMin pulses counted + - extend command "int" for optional intervalSml and countMin + 29.10.16 - allow interval Min >= Max or Sml > Min + which changes behavior to take fixed calculation interval instead of timeDiff between pulses + -> if intervalMin = intervalMax, counting will allways follow the reporting interval + 3.11.16 - more noInterrupt blocks when accessing the non uint8_t volatiles in report + V1.3 + 4.11.16 - check min pulse width and add more output, + - prefix show output with M + V1.4 + 10.11.16 - restructure add Cmd + - change syntax for specifying minPulseLengh + - res (reset) command + V1.6 + 13.12.16 - new startup message logic?, newline before first communication? + 18.12.16 - replace all code containing Strings, new communication syntax and parsing from Jeelink code + V1.7 + 2.1.17 - change message syntax again, report time as well, first and last impulse are reported + relative to start of intervall not start of reporting intervall + V1.8 + 4.1.17 - fixed a missing break in the case statement for pin definition + 5.1.17 - cleanup debug logging + 14.10.17 - fix a bug where last port state was not initialized after interrupt attached but this is necessary there + 23.11.17 - beautify code, add comments, more debugging for users with problematic pulse creation devices + 28.12.17 - better reportung of first pulse (even if only one pulse and countdiff is 0 but realdiff is 1) + 30.12.17 - rewrite PCInt, new handling of min pulse length, pulse history ring + 1.1.18 - check len in add command, allow pin 8 and 13 + 2.1.18 - add history per pin to report line, show negative starting times in show history + 3.1.18 - little reporting fix (start pos of history report) + + V2.0 + 17.1.18 - rewrite many things - use pin number instead of pcIntPinNumber as index, split interrupt handler for easier porting to ESP8266, ... + V2.23 + 10.2.18 - new commands for check alive and quit, send setup message after reboot also over tcp + remove reporting time of first pulse (now we hava history) + remove pcIntMode (is always change now) + pulse min interval is now always checked and defaults to 2 if not set + march 2018 many changes more to support ESP8266 + 7.3.18 - change pin config output, fix pullup (V2.26), store config in eeprom and read it back after boot + 22.4.18 - many changes, delay report if tcp mode and disconnected, verbose levels, ... + 13.5.18 - V2.36 Keepalive also on Arduino side + 9.12.18 - V3.0 start implementing analog input for old ferraris counters + 6.1.19 - V3.1 showIntervals in hello + 19.1.19 - V3.12 support for ESP with analog + 24.2.19 - V3.13 fix internal pin to GPIO mapping (must match ISR functions) when ESP8266 and analog support + - V3.14 added return of devVerbose upon startup + 27.6.19 - V3.20 replace timeNextReport with lastReportCall to avoid problem with data tyoes on ESP + fix a bug with analog counting on the ESP + + ToDo / Ideas: + + +*/ + +/* Remove this before compiling */ +/* #define TestConfig */ + + +/* allow printing of every pin change to Serial */ +#define debugPins 1 + +/* allow tracking of pulse lengths */ +#define pulseHistory 1 + +/* support analog input for ferraris counters with IR light hardware */ +#define analogIR 1 + +/* use a sample config at boot */ +// #define debugCfg 1 + +#include "pins_arduino.h" +#include + +const char versionStr[] PROGMEM = "ArduCounter V3.20"; +const char compile_date[] PROGMEM = __DATE__ " " __TIME__; +const char errorStr[] PROGMEM = "Error: "; + +#ifdef ARDUINO_BOARD +const char boardName1[] PROGMEM = ARDUINO_BOARD; +#endif + +#if defined(__AVR_ATmega328P__) || defined(__AVR_ATmega168__) +#ifdef ARDUINO_AVR_NANO +const char boardName[] PROGMEM = "NANO"; +#else +const char boardName[] PROGMEM = "UNO"; +#endif +#elif defined(__AVR_ATmega32U4__) || defined(__AVR_ATmega16U4__) +const char boardName[] PROGMEM = "Leonardo"; +#elif defined(__AVR_ATmega1280__) || defined(__AVR_ATmega2560__) +const char boardName[] PROGMEM = "Mega"; +#elif defined(ESP8266) +const char boardName[] PROGMEM = "ESP8266"; +#else +const char boardName[] PROGMEM = "UNKNOWN"; +#endif + +#define SERIAL_SPEED 38400 +#define MAX_INPUT_NUM 8 + +#ifdef analogIR +int sensorValueOff = 0; // value read from the photo transistor when ir LED is off +int sensorValueOn = 0; // value read from the photo transistor when ir LED is on +int analogThresholdMin = 100; // min value of analog input +int analogThresholdMax = 110; // max value of analog input + +uint8_t triggerState; // todo: use existing arrays instead + +// save measurement during same level as sum and count to get average and then put in history when doCount is called +// but how do we do this before we can detect the levels? + +#endif + +#ifdef ESP8266 // ESP variables and definitions +#include // ============================= + +#ifdef TestConfig +#include "ArduCounterTestConfig.h" +#else +const char* ssid = "MySSID"; +const char* password = "secret"; +#endif + +WiFiServer Server(80); // For ESP WiFi connection +WiFiClient Client1; // active TCP connection +WiFiClient Client2; // secound TCP connection to send reject message +boolean Client1Connected; // remember state of TCP connection +boolean Client2Connected; // remember state of TCP connection + +boolean tcpMode = false; +uint8_t delayedTcpReports = 0; // how often did we already delay reporting because tcp disconnected +uint32_t lastDelayedTcpReports = 0; // last time we delayed + +#define MAX_HIST 20 // 20 history entries for ESP boards (can be increased) + +#ifdef analogIR // code for ESP with analog pin and reflection light barrier support (test) + +#define MAX_APIN 18 +#define MAX_PIN 9 + +/* 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 (aPin) to sketch internal index numbers */ +short allowedPins[MAX_APIN] = // ESP 8266 with analog: + { 0, 1, 2, -1, // printed pin numbers 0,1,2 are ok to be used + -1, 5, -1, -1, // printed pin number 5 is ok to be used + -1, -1, -1, -1, // 8-11 not avaliable + -1, -1, -1, -1, // 12-15 not avaliable + -1, 8 }; // 16 not available, 17 is analog + +/* Wemos / NodeMCU Pins 3,4 and 8 (GPIO 0,2 and 15) define boot mode and therefore + * can not be used to connect to signal */ + +/* Map from sketch internal pin index to real chip IO pin number (not aPin, e.g. for ESP) + Note that the internal numbers might be different from the printed + pin numbers (e.g. pin 0 is in index 0 but real chip pin number 16! */ +short internalPins[MAX_PIN] = + { D0, D1, D2, D3, // map from internal pin Index to + D4, D5, D6, D7, // real GPIO pin numbers / defines + A0 }; // D0=16, D1=5, D2=4, D5=14, A0=17 + + + +uint8_t analogPins[MAX_PIN] = + { 0,0,0,0,0,0,0,0,1 }; // ESP pin A0 (pinIndex 8, internal 17) is analog + +const int analogInPin = A0; // Analog input pin that the photo transistor is attached to (internally number 17) +const int irOutPin = D6; // Digital output pin that the IR-LED is attached to +const int ledOutPin = D7; // Signal LED output pin + +#else // code for ESP without analog pin and reflection light barrier support + + +#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] = // ESP 8266 without analog: + { 0, 1, 2, -1, // printed pin numbers 0,1,2 are ok to be used, 3 not + -1, 5, 6, 7}; // printed pin numbers 5-7 are ok to be used, 4 not, >8 not + +/* Wemos / NodeMCU Pins 3,4 and 8 (GPIO 0,2 and 15) define boot mode and therefore + * can not be used to connect to signal */ + +/* Map from sketch internal pin index to real chip IO pin number (not aPin, e.g. for ESP) + Note that the internal numbers might be different from the printed + pin numbers (e.g. pin 0 is in index 0 but real chip pin number 16! */ +short internalPins[MAX_PIN] = + { D0, D1, D2, D3, // printed pin numbers 0, 1, 2, 3 (3 should not be used and could be removed here) + D5, D5, D6, D7}; // printed pin numbers 4, 5, 6, 7 (4 should not be used and could be removed here) + // D0=16, D1=5, D2=4, D5=14, A0=17, ... + +#endif // end of ESP section without analog reading + + + + +#else // Arduino Uno or Nano variables and definitions + // ============================================= + +#define MAX_HIST 20 // 20 history entries for arduino boards + +/* arduino pins that are typically ok to use + * (some might be set to -1 (disallowed) because they are used + * as reset, serial, led or other things on most boards) + * maps printed pin numbers to sketch internal index numbers */ + +#ifdef analogIR + +/* 2 is used for IR out, 12 for signal, A7 for In */ +#define MAX_APIN 22 +#define MAX_PIN 18 + +short allowedPins[MAX_APIN] = + {-1, -1, -1, 0, /* arduino pin 0 - 3 to internal index or -1 if pin is reserved */ + 1, 2, 3, 4, /* arduino pin 4 - 7 to internal index */ + 5, 6, 7, 8, /* arduino pin 8 - 11 to internal index */ + -1, 9, 10, 11, /* arduino pin 12, 13, A0, A1 / 14, 15 to internal index or -1 if pin is reserved*/ + 12, 13, 14, 15, /* arduino pin A2 - A5 / 16 - 19 to internal index */ + 16, 17 }; /* arduino pin A6, A7 to internal index */ + +/* Map from sketch internal pin index to real chip IO pin number */ +short internalPins[MAX_PIN] = + { 3, 4, 5, 6, /* index 0 - 3 map to pins 3 - 6 */ + 7, 8, 9, 10, /* index 4 - 7 map to pins 7 - 10 */ + 11, 12, 14, 15, /* index 8 - 11 map to pins 11,13, A0 - A1 */ + 16, 17, 18, 19, /* index 12 - 15 map to pins A2 - A5 */ + 20, 21 }; /* index 16 - 17 map to pin A6, A7 */ + +uint8_t analogPins[MAX_PIN] = + { 0,0,0,0, /* everything except Arduino A7 (pinIndex 17, internal 21) is digital by default */ + 0,0,0,0, + 0,0,0,0, + 0,0,0,0, + 0,1 }; + +const int analogInPin = A7; // Arduino analog input pin that the photo transistor is attached to (internal 21) +const int irOutPin = 2; // Digital output pin that the IR-LED is attached to +const int ledOutPin = 12; // Signal LED output pin + + +#else +/* no analog IR support -> all Nano pins including analog available für digital counting */ + +#define MAX_APIN 22 +#define MAX_PIN 20 +short allowedPins[MAX_APIN] = + {-1, -1, 0, 1, /* arduino pin 0 - 3 to internal Pin index or -1 if pin is reserved */ + 2, 3, 4, 5, /* arduino pin 4 - 7 to internal Pin index or -1 if pin is reserved */ + 6, 7, 8, 9, /* arduino pin 8 - 11 to internal Pin index or -1 if pin is reserved */ + 10, 11, 12, 13, /* arduino pin 12, 13, A0, A1 to internal Pin index or -1 if pin is reserved */ + 14, 15, 16, 17, /* arduino pin A2 - A5 / 16 - 19 to internal Pin index or -1 if pin is reserved */ + 18, 19 }; /* arduino pin A6, A7 to internal Pin index or -1 if pin is reserved */ + +/* Map from sketch internal pin index to real chip IO pin number */ +short internalPins[MAX_PIN] = + { 2, 3, 4, 5, /* index 0 - 3 map to pins 2 - 5 */ + 6, 7, 8, 9, /* index 4 - 7 map to pins 6 - 9 */ + 10, 11, 12, 13, /* index 8 - 11 map to pins 10 - 13 */ + 14, 15, 16, 17, /* index 12 - 15 map to pins A0 - A3 */ + 18, 19, 20, 21 }; /* index 16 - 19 map to pins A4 - A7 */ + +uint8_t analogPins[MAX_PIN] = + { 0,0,0,0, /* everything is digital by default */ + 0,0,0,0, + 0,0,0,0, + 0,0,0,0, + 0,0,0,0 }; + +#endif + +/* first and last pin at port PB, PC and PD for arduino uno/nano */ +uint8_t firstPin[] = {8, 14, 0}; // aPin -> allowedPins[] -> pinIndex +uint8_t lastPin[] = {13, 19, 7}; + +/* Pin change mask for each chip port on the arduino platform */ +volatile uint8_t *port_to_pcmask[] = { + &PCMSK0, + &PCMSK1, + &PCMSK2 +}; + +/* last PIN States at io port to detect individual pin changes in arduino ISR */ +volatile static uint8_t PCintLast[3]; + +#endif + + +Print *Output; // Pointer to output device (Serial / TCP connection with ESP8266) +uint32_t bootTime; +uint16_t bootWraps; // counter for millis wraps at last reset +uint16_t millisWraps; // counter to track when millis counter wraps +uint32_t lastMillis; // milis at last main loop iteration +uint8_t devVerbose; // >=10 shows pin changes, >=5 shows pin history + +#ifdef debugPins +uint8_t lastState[MAX_PIN]; // for debug output when a pin state changes +#endif + +uint32_t intervalMin = 30000; // default 30 sec - report after this time if nothing else delays it +uint32_t intervalMax = 60000; // default 60 sec - report after this time if it didin't happen before +uint32_t intervalSml = 2000; // default 2 secs - continue count if timeDiff is less and intervalMax not over +uint16_t countMin = 2; // continue counting if count is less than this and intervalMax not over + +uint32_t lastReportCall; +#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()); + // called with pinIndex, level, now +} + +void ESPISR5() { // ISR for real pin GPIO 5 / pinIndex 1 + doCount(1, digitalRead(5), millis()); +} + +void ESPISR12() { // ISR for real pin GPIO 12 / pinIndex 6 + doCount(6, digitalRead(12), millis()); +} + +void ESPISR13() { // ISR for real pin GPIO 13 / pinIndex 7 + doCount(7, digitalRead(13), millis()); +} + +void ESPISR14() {// ISR for real pin GPIO 14 / pinIndex 5 + doCount(5, digitalRead(14), millis()); +} + +void ESPISR16() { // ISR for real pin GPIO 16 / pinIndex 0 + doCount(0, digitalRead(16), millis()); +} +#endif + + +void PrintErrorMsg() { + uint8_t len = strlen_P(errorStr); + char myChar; + for (unsigned char k = 0; k < len; k++) { + myChar = pgm_read_byte_near(errorStr + k); + Output->print(myChar); + } +} + + +void printVersionMsg() { + uint8_t len = strlen_P(versionStr); + char myChar; + for (unsigned char k = 0; k < len; k++) { + myChar = pgm_read_byte_near(versionStr + k); + Output->print(myChar); + } + Output->print(F(" on ")); + len = strlen_P(boardName); + for (unsigned char k = 0; k < len; k++) { + myChar = pgm_read_byte_near(boardName + k); + Output->print(myChar); + } + +#ifdef ARDUINO_BOARD + Output->print(F(" ")); + len = strlen_P(boardName1); + for (unsigned char k = 0; k < len; k++) { + myChar = pgm_read_byte_near(boardName1 + k); + Output->print(myChar); + } +#endif + + Output->print(F(" compiled ")); + len = strlen_P(compile_date); + for (unsigned char k = 0; k < len; k++) { + myChar = pgm_read_byte_near(compile_date + k); + Output->print(myChar); + } +} + + +void showIntervals() { + Output->print(F("I")); + Output->print(intervalMin / 1000); + Output->print(F(" ")); + Output->print(intervalMax / 1000); + Output->print(F(" ")); + Output->print(intervalSml / 1000); + Output->print(F(" ")); + Output->println(countMin); +} + + +#ifdef analogIR +void showThresholds() { + Output->print(F("T")); + Output->print(analogThresholdMin); + Output->print(F(" ")); + Output->println(analogThresholdMax); +} +#endif + + +void showPinConfig(short pinIndex) { + Output->print(F("P")); + Output->print(activePin[pinIndex]); + switch (pulseLevel[pinIndex]) { + case 1: Output->print(F(" rising")); break; + case 0: Output->print(F(" falling")); break; + default: Output->print(F(" -")); break; + } + if (pullup[pinIndex]) + Output->print(F(" pullup")); + Output->print(F(" min ")); + Output->print(pulseWidthMin[pinIndex]); +} + +#ifdef pulseHistory +void showPinHistory(short pinIndex, uint32_t now) { + uint8_t hi; + uint8_t start = (histIndex + 2) % MAX_HIST; + uint8_t count = 0; + uint32_t last; + boolean first = true; + + for (uint8_t i = 0; i < MAX_HIST; i++) { + hi = (start + i) % MAX_HIST; + if (histPin[hi] == pinIndex) + if (first || (last <= histTime[hi]+histLen[hi])) count++; + } + if (!count) { + // Output->println (F("M No Pin History")); + 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((now - lastReportCall) >= intervalMin) // 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((now - lastReport[pinIndex]) >= intervalMax) + 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 + } + } + lastReportCall = now; // check again after intervalMin or if intervalMax is over for a pin +} + + +/* give status report in between if requested over serial input */ +void showCmd() { + uint32_t now = millis(); + Output->print(F("M Status: ")); + printVersionMsg(); + Output->println(); + + showIntervals(); +#ifdef analogIR + showThresholds(); +#endif + Output->print(F("V")); + Output->println(devVerbose); + + for (uint8_t pinIndex=0; pinIndex < MAX_PIN; pinIndex++) { + if (activePin[pinIndex] >= 0) { + showPinConfig(pinIndex); + Output->print(F(", ")); + showPinCounter(pinIndex, true, now); +#ifdef pulseHistory + showPinHistory(pinIndex, now); +#endif + } + } + readFromEEPROM(); + Output->print(F("M Next report in ")); + Output->print(lastReportCall + intervalMin - millis()); + Output->print(F(" milliseconds")); + Output->println(); +} + + +void helloCmd() { + uint32_t now = millis(); + Output->println(); + printVersionMsg(); + Output->print(F(" Hello, pins ")); + boolean first = true; + for (uint8_t aPin=0; aPin < MAX_APIN; aPin++) { + if (allowedPins[aPin] >= 0) { + if (!first) { + Output->print(F(",")); + } else { + first = false; + } + Output->print(aPin); + } + } + Output->print(F(" available")); + Output->print(F(" T")); + Output->print(now); + Output->print(F(",")); + Output->print(millisWraps); + Output->print(F(" B")); + Output->print(bootTime); + Output->print(F(",")); + Output->print(bootWraps); + + Output->println(); + showIntervals(); +#ifdef analogIR + showThresholds(); +#endif + Output->print(F("V")); + Output->println(devVerbose); + + for (uint8_t pinIndex=0; pinIndex < MAX_PIN; pinIndex++) { // go through all observed pins as pinIndex + if (activePin[pinIndex] >= 0) { + showPinConfig(pinIndex); + Output->println(); + } + } +} + + + +/* + handle add command. +*/ +void addCmd(uint16_t *values, uint8_t size) { + uint16_t pulseWidth; + uint32_t now = millis(); + + uint8_t aPin = values[0]; // values[0] is pin number + if (aPin >= MAX_APIN || allowedPins[aPin] < 0) { + PrintErrorMsg(); + Output->print(F("Illegal pin specification ")); + Output->println(aPin); + return; + }; + uint8_t pinIndex = allowedPins[aPin]; + uint8_t rPin = internalPins[pinIndex]; + + if (activePin[pinIndex] != aPin) { // in case this pin is not already active counting + #ifndef ESP8266 + uint8_t port = digitalPinToPort(rPin) - 2; + PCintLast[port] = *portInputRegister(port+2); + #endif + initPinVars(pinIndex, now); + activePin[pinIndex] = aPin; // save arduino pin number and flag this pin as active for reporting + } + + if (values[1] < 2 || values[1] > 3) { // values[1] is level (3->rising / 2->falling) + PrintErrorMsg(); + Output->print(F("Illegal pulse level specification for pin ")); + Output->println(aPin); + } + pulseLevel[pinIndex] = (values[1] == 3); // 2 = falling -> pulseLevel 0, 3 = rising -> pulseLevel 1 + +#ifdef analogIR + if (size > 2 && values[2] && !analogPins[pinIndex]) { +#else + if (size > 2 && values[2]) { +#endif + pinMode (rPin, INPUT_PULLUP); // values[2] is pullup flag + pullup[pinIndex] = 1; + } else { + pinMode (rPin, INPUT); + pullup[pinIndex] = 0; + } + + if (size > 3 && values[3] > 0) { // value 3 is min length + pulseWidth = values[3]; + } else { + pulseWidth = 2; + } + + /* todo: add upper and lower limits for analog pins as option here and in Fhem module */ + + pulseWidthMin[pinIndex] = pulseWidth; + +#ifdef analogIR + if (!analogPins[pinIndex]) { +#endif + if (!AddPinChangeInterrupt(rPin)) { // add Pin Change Interrupt + PrintErrorMsg(); + Output->println(F("AddInt")); + return; + } +#ifdef analogIR + } +#endif + Output->print(F("M defined ")); + showPinConfig(pinIndex); + Output->println(); +} + + +/* + handle rem command. +*/ +void removeCmd(uint16_t *values, uint8_t size) { + uint8_t aPin = values[0]; + if (size < 1 || aPin >= MAX_APIN || allowedPins[aPin] < 0) { + PrintErrorMsg(); + Output->print(F("Illegal pin specification ")); + Output->println(aPin); + return; + }; + uint8_t pinIndex = allowedPins[aPin]; + +#ifdef analogIR + if (!analogPins[pinIndex]) { +#endif +#ifdef ESP8266 + detachInterrupt(digitalPinToInterrupt(internalPins[pinIndex])); +#else + if (!RemovePinChangeInterrupt(internalPins[pinIndex])) { + PrintErrorMsg(); Output->println(F("RemInt")); + return; + } +#endif +#ifdef analogIR + } +#endif + initPinVars(pinIndex, 0); + Output->print(F("M removed ")); + Output->println(aPin); +} + + + +void intervalCmd(uint16_t *values, uint8_t size) { + /*Serial.print(F("D int ptr is ")); + Serial.println(size);*/ + if (size < 4) { // i command always gets 4 values: min, max, sml, cntMin + PrintErrorMsg(); + Output->print(F("size")); + Output->println(); + return; + } + if (values[0] < 1 || values[0] > 3600) { + PrintErrorMsg(); Output->println(values[0]); + return; + } + intervalMin = (long)values[0] * 1000; + + if (values[1] < 1 || values[1] > 3600) { + PrintErrorMsg(); Output->println(values[1]); + return; + } + intervalMax = (long)values[1]* 1000; + + if (values[2] > 3600) { + PrintErrorMsg(); Output->println(values[2]); + return; + } + intervalSml = (long)values[2] * 1000; + + if (values[3] > 100) { + PrintErrorMsg(); Output->println(values[3]); + return; + } + countMin = values[3]; + + Output->print(F("M intervals set to ")); + Output->print(values[0]); + Output->print(F(" ")); + Output->print(values[1]); + Output->print(F(" ")); + Output->print(values[2]); + Output->print(F(" ")); + Output->print(values[3]); + Output->println(); +} + +#ifdef analogIR +void thresholdCmd(uint16_t *values, uint8_t size) { + /*Serial.print(F("D threshold size ")); + Serial.print(size); + Serial.print(F(" v0 ")); + Serial.print(values[0]); + Serial.print(F(" v1 ")); + Serial.print(values[1]); + Serial.println();*/ + + if (size < 2) { // t command gets 2 values: min, max + PrintErrorMsg(); + Output->print(F("size")); + Output->println(); + return; + } + if (values[0] < 1 || values[0] > 1023) { + PrintErrorMsg(); Output->println(values[0]); + return; + } + analogThresholdMin = (int)values[0]; + + if (values[1] < 1 || values[1] > 1023) { + PrintErrorMsg(); Output->println(values[1]); + return; + } + analogThresholdMax = (int)values[1]; + + Output->print(F("M analog thresholds set to ")); + Output->print(values[0]); + Output->print(F(" ")); + Output->print(values[1]); + Output->println(); +} +#endif + + +void keepAliveCmd(uint16_t *values, uint8_t size) { + if (values[0] == 1 && size > 0) { + Output->println(F("alive")); + } +#ifdef ESP8266 + if (values[0] == 1 && size > 0 && size < 3 && Client1.connected()) { + tcpMode = true; + if (size == 2) { + expectK = millis() + values[1] * 2500; + } else { + expectK = millis() + 600000; // 10 Minutes if nothing sent (should not happen) + } + } +#endif +} + + +#ifdef ESP8266 +void quitCmd() { + if (Client1.connected()) { + Client1.println(F("closing connection")); + Client1.stop(); + tcpMode = false; + Serial.println(F("M TCP connection closed")); + } else { + Serial.println(F("M TCP not connected")); + } +} +#endif + + + +void updateEEPROM(int &address, byte value) { + if( EEPROM.read(address) != value){ + EEPROM.write(address, value); + } + address++; +} + + +void updateEEPROMSlot(int &address, char cmd, int v1, int v2, int v3, int v4) { + updateEEPROM(address, cmd); // I / A + updateEEPROM(address, v1 & 0xff); + updateEEPROM(address, v1 >> 8); + updateEEPROM(address, v2 & 0xff); + updateEEPROM(address, v2 >> 8); + updateEEPROM(address, v3 & 0xff); + updateEEPROM(address, v3 >> 8); + updateEEPROM(address, v4 & 0xff); + updateEEPROM(address, v4 >> 8); +} + +/* todo: include analogPins as well as analog limits in save / restore */ + +void saveToEEPROMCmd() { + int address = 0; + uint8_t slots = 1; + updateEEPROM(address, 'C'); + updateEEPROM(address, 'f'); + updateEEPROM(address, 'g'); + for (uint8_t pinIndex=0; pinIndex < MAX_PIN; pinIndex++) + if (activePin[pinIndex] >= 0) slots ++; +#ifdef analogIR + slots ++; +#endif + updateEEPROM(address, slots); // number of defined pins + intervall definition + updateEEPROMSlot(address, 'I', (uint16_t)(intervalMin / 1000), (uint16_t)(intervalMax / 1000), + (uint16_t)(intervalSml / 1000), (uint16_t)countMin); +#ifdef analogIR + updateEEPROMSlot(address, 'T', (uint16_t)analogThresholdMin, (uint16_t)analogThresholdMax, 0, 0); +#endif + for (uint8_t pinIndex=0; pinIndex < MAX_PIN; pinIndex++) + if (activePin[pinIndex] >= 0) + updateEEPROMSlot(address, 'A', (uint16_t)activePin[pinIndex], (uint16_t)(pulseLevel[pinIndex] ? 3:2), + (uint16_t)pullup[pinIndex], (uint16_t)pulseWidthMin[pinIndex]); +#ifdef ESP8266 + EEPROM.commit(); +#endif + Serial.print(F("config saved, ")); + Serial.print(slots); + Serial.print(F(", ")); + Serial.println(address); +} + + +void readFromEEPROM() { + int address = 0; + uint16_t v1, v2, v3, v4; + char cmd; + if (EEPROM.read(address) != 'C' || EEPROM.read(address+1) != 'f' || EEPROM.read(address+2) != 'g') { + Output->println(F("M no config in EEPROM")); + return; + } + address = 3; + uint8_t slots = EEPROM.read(address++); + if (slots > MAX_PIN + 2) { + Output->println(F("M illegal config in EEPROM")); + return; + } + Output->println(); + Output->print(F("M EEPROM Config: ")); + Output->print((char) EEPROM.read(0)); + Output->print((char) EEPROM.read(1)); + Output->print((char) EEPROM.read(2)); + Output->print(F(" Slots: ")); + Output->print((int) EEPROM.read(3)); + Output->println(); + for (uint8_t slot=0; slot < slots; slot++) { + cmd = EEPROM.read(address); + v1 = EEPROM.read(address+1) + (((uint16_t)EEPROM.read(address+2)) << 8); + v2 = EEPROM.read(address+3) + (((uint16_t)EEPROM.read(address+4)) << 8); + v3 = EEPROM.read(address+5) + (((uint16_t)EEPROM.read(address+6)) << 8); + v4 = EEPROM.read(address+7) + (((uint16_t)EEPROM.read(address+8)) << 8); + address = address + 9; + Output->print(F("M Slot: ")); + Output->print(cmd); + Output->print(F(" ")); + Output->print(v1); + Output->print(F(",")); + Output->print(v2); + Output->print(F(",")); + Output->print(v3); + Output->print(F(",")); + Output->print(v4); + Output->println(); + } +} + + +void restoreFromEEPROM() { + int address = 0; + if (EEPROM.read(address) != 'C' || EEPROM.read(address+1) != 'f' || EEPROM.read(address+2) != 'g') { + Serial.println(F("M no config in EEPROM")); + return; + } + address = 3; + uint8_t slots = EEPROM.read(address++); + if (slots > MAX_PIN + 1 || slots < 1) { + Serial.println(F("M illegal config in EEPROM")); + return; + } + Serial.println(F("M restoring config from EEPROM")); + char cmd; + for (uint8_t slot=0; slot < slots; slot++) { + cmd = EEPROM.read(address); + commandData[0] = EEPROM.read(address+1) + (((uint16_t)EEPROM.read(address+2)) << 8); + commandData[1] = EEPROM.read(address+3) + (((uint16_t)EEPROM.read(address+4)) << 8); + commandData[2] = EEPROM.read(address+5) + (((uint16_t)EEPROM.read(address+6)) << 8); + commandData[3] = EEPROM.read(address+7) + (((uint16_t)EEPROM.read(address+8)) << 8); + address = address + 9; + commandDataPointer = 4; + if (cmd == 'I') intervalCmd(commandData, commandDataPointer); +#ifdef analogIR + if (cmd == 'T') thresholdCmd(commandData, commandDataPointer); +#endif + if (cmd == 'A') addCmd(commandData, commandDataPointer); + } + commandDataPointer = 0; + value = 0; + for (uint8_t i=0; i < MAX_INPUT_NUM; i++) + commandData[i] = 0; + +} + + +void handleInput(char c) { + if (c == ',') { // Komma input, last value is finished + if (commandDataPointer < (MAX_INPUT_NUM - 1)) { + commandData[commandDataPointer++] = value; + value = 0; + } + } + else if ('0' <= c && c <= '9') { // digit input + value = 10 * value + c - '0'; + } + else if ('a' <= c && c <= 'z') { // letter input is command + + if (devVerbose > 0) { + commandData[commandDataPointer] = value; + Serial.print(F("D got ")); + for (short v = 0; v <= commandDataPointer; v++) { + if (v > 0) Serial.print(F(",")); + Serial.print(commandData[v]); + } + Serial.print(c); + Serial.print(F(" size ")); + Serial.print(commandDataPointer+1); + Serial.println(); + } + + switch (c) { + case 'a': // add a pin + commandData[commandDataPointer] = value; + addCmd(commandData, commandDataPointer+1); + break; + case 'd': // delete a pin + commandData[commandDataPointer] = value; + removeCmd(commandData, commandDataPointer+1); + break; + case 'e': // save to EEPROM + saveToEEPROMCmd(); + break; + case 'f': // flash ota + // OTA flash from HTTP Server + break; + case 'h': // hello + helloCmd(); + break; + case 'i': // interval + commandData[commandDataPointer] = value; + intervalCmd(commandData, commandDataPointer+1); + break; + case 'k': // keep alive + commandData[commandDataPointer] = value; + keepAliveCmd(commandData, commandDataPointer+1); + break; +#ifdef ESP8266 + case 'q': // quit + quitCmd(); + break; +#endif + case 'r': // reset + initialize(); + break; + case 's': // show + showCmd(); + break; +#ifdef analogIR + case 't': // thresholds for analog pin + commandData[commandDataPointer] = value; + thresholdCmd(commandData, commandDataPointer+1); + break; +#endif + case 'v': // dev verbose + if (value < 255) { + devVerbose = value; + Output->print(F("M devVerbose set to ")); + Output->println(value); + } else { + Output->println(F("M illegal value passed for devVerbose")); + } + break; + default: + break; + } + commandDataPointer = 0; + value = 0; + for (uint8_t i=0; i < MAX_INPUT_NUM; i++) + commandData[i] = 0; + //Serial.println(F("D End of command")); + } +} + +#ifdef debugCfg +/* do sample config so we don't need to configure pins after each reboot */ +void debugSetup() { + commandData[0] = 10; + commandData[1] = 20; + commandData[2] = 3; + commandData[3] = 0; + commandDataPointer = 4; + intervalCmd(commandData, commandDataPointer); + + commandData[0] = 1; // pin 1 + commandData[1] = 2; // falling + commandData[2] = 1; // pullup + commandData[3] = 30; // min Length + commandDataPointer = 4; + addCmd(commandData, commandDataPointer); + + commandData[0] = 2; // pin 2 + addCmd(commandData, commandDataPointer); + +/* + commandData[0] = 5; // pin 5 + addCmd(commandData, commandDataPointer); + + commandData[0] = 6; // pin 6 + addCmd(commandData, commandDataPointer); +*/ +} +#endif + + +#ifdef debugPins +void debugPinChanges() { + for (uint8_t pinIndex=0; pinIndex < MAX_PIN; pinIndex++) { + short aPin = activePin[pinIndex]; + if (aPin > 0) { + uint8_t rPin = internalPins[pinIndex]; + uint8_t pinState = digitalRead(rPin); + + if (pinState != lastState[pinIndex]) { + lastState[pinIndex] = pinState; + Output->print(F("M pin ")); + Output->print(aPin); + Output->print(F(" (internal ")); + Output->print(rPin); + Output->print(F(")")); + Output->print(F(" changed to ")); + Output->print(pinState); +#ifdef pulseHistory + Output->print(F(", histIdx ")); + Output->print(histIndex); +#endif + Output->print(F(", count ")); + Output->print(counter[pinIndex]); + Output->print(F(", reject ")); + Output->print(rejectCounter[pinIndex]); + Output->println(); + } + } + } +} +#endif + + +#ifdef ESP8266 +void connectWiFi() { + Client1Connected = false; + Client2Connected = false; + + // Connect to WiFi network + WiFi.mode(WIFI_STA); + delay (1000); + if (WiFi.status() != WL_CONNECTED) { + Serial.print(F("M Connecting WiFi to ")); + Serial.println(ssid); + WiFi.begin(ssid, password); // authenticate + while (WiFi.status() != WL_CONNECTED) { + Serial.print(F("M Status is ")); + switch (WiFi.status()) { + case WL_CONNECT_FAILED: + Serial.println(F("Connect Failed")); + break; + case WL_CONNECTION_LOST: + Serial.println(F("Connection Lost")); + break; + case WL_DISCONNECTED: + Serial.println(F("Disconnected")); + break; + case WL_CONNECTED: + Serial.println(F("Connected")); + break; + default: + Serial.println(WiFi.status()); + } + delay(1000); + } + Serial.println(); + Serial.print(F("M WiFi connected to ")); + Serial.println(WiFi.SSID()); + } else { + Serial.print(F("M WiFi already connected to ")); + Serial.println(WiFi.SSID()); + } + + // Start the server + Server.begin(); + Serial.println(F("M Server started")); + + // Print the IP address + Serial.print(F("M Use this IP: ")); + Serial.println(WiFi.localIP()); +} + + +void handleConnections() { + IPAddress remote; + uint32_t now = millis(); + + if (Client1Connected) { + if((long)(now - expectK) >= 0) { + Serial.println(F("M no keepalive from Client - disconnecting")); + Client1.stop(); + } + } + if (Client1.available()) { + handleInput(Client1.read()); + //Serial.println(F("M new Input over TCP")); + } + if (Client1.connected()) { + Client2 = Server.available(); + if (Client2) { + Client2.println(F("connection already busy")); + remote = Client2.remoteIP(); + Client2.stop(); + Serial.print(F("M second connection from ")); + Serial.print(remote); + Serial.println(F(" rejected")); + } + } else { + if (Client1Connected) { // client used to be connected, now disconnected + Client1Connected = false; + Output = &Serial; + Serial.println(F("M connection to client lost")); + } + Client1 = Server.available(); + if (Client1) { // accepting new connection + remote = Client1.remoteIP(); + Serial.print(F("M new connection from ")); + Serial.print(remote); + Serial.println(F(" accepted")); + Client1Connected = true; + Output = &Client1; + expectK = now + 600000; // max 10 Minutes (to be checked on Fhem module side as well + helloCmd(); // say hello to client + } + } +} +#endif + + +void handleTime() { + uint32_t now = millis(); + if (now < lastMillis) millisWraps++; + lastMillis = now; +} + + +#ifdef analogIR +void detectTrigger(int val) { + uint8_t nextState = triggerState; + if (val > analogThresholdMax) { + nextState = 1; + } else if (val < analogThresholdMin) { + nextState = 0; + } + if (nextState != triggerState) { + triggerState = nextState; +#ifdef ledOutPin + digitalWrite(ledOutPin, triggerState); +#endif + short pinIndex = allowedPins[analogInPin]; // ESP pin A0 (pinIndex 8, internal 17) or Arduino A7 (pinIndex 17, internal 21) + uint32_t now = millis(); + doCount (pinIndex, triggerState, now); // do the counting, history and so on + + if (devVerbose >= 10) { + Output->print(F("M called doCount with pinIndex ")); + Output->print(pinIndex); + Output->print(F(" and triggerState")); + Output->print(triggerState); + Output->println(); + } + +#ifdef debugPins + if (devVerbose >= 10) { + short rPin = internalPins[pinIndex]; + Output->print(F("M pin ")); + Output->print(analogInPin); + Output->print(F(" (index ")); + Output->print(pinIndex); + Output->print(F(", internal ")); + Output->print(rPin); + Output->print(F(" ) ")); + Output->print(F(" to ")); + Output->print(nextState); +#ifdef pulseHistory + Output->print(F(" histIdx ")); + Output->print(histIndex); +#endif + Output->print(F(" count ")); + Output->print(counter[pinIndex]); + Output->print(F(" reject ")); + Output->print(rejectCounter[pinIndex]); + Output->println(); + } +#endif + } +} +#endif + +void initPinVars(short pinIndex, uint32_t now) { + uint8_t level = 0; + activePin[pinIndex] = -1; // inactive (-1) + initialized[pinIndex] = false; // no pulse seen yet + pulseWidthMin[pinIndex] = 0; // min pulse length + counter[pinIndex] = 0; // counter to 0 + counterIgn[pinIndex] = 0; + lastCount[pinIndex] = 0; + rejectCounter[pinIndex] = 0; + lastRejCount[pinIndex] = 0; + intervalStart[pinIndex] = now; // time vars + intervalEnd[pinIndex] = now; + lastChange[pinIndex] = now; + lastReport[pinIndex] = now; + reportSequence[pinIndex] = 0; +#ifdef analogIR + if (!analogPins[pinIndex]) { + level = digitalRead(internalPins[pinIndex]); + } +#else + level = digitalRead(internalPins[pinIndex]); +#endif + lastLevel[pinIndex] = level; +#ifdef debugPins + lastState[pinIndex] = level; // for debug output +#endif + /* todo: add analogPins, upper and lower limits for analog */ +} + + +void initialize() { + uint32_t now = millis(); + for (uint8_t pinIndex=0; pinIndex < MAX_PIN; pinIndex++) { + initPinVars(pinIndex, now); + } + lastReportCall = now; // time for first output after intervalMin from now + devVerbose = 0; +#ifndef ESP8266 + for (uint8_t port=0; port <= 2; port++) { + PCintLast[port] = *portInputRegister(port+2); // current pin states at port for PCInt handler + } +#endif +#ifdef debugCfg + debugSetup(); +#endif + restoreFromEEPROM(); + bootTime = millis(); // with boot / reset time + bootWraps = millisWraps; +#ifdef ESP8266 + expectK = now + 600000; // max 10 Minutes (to be checked on Fhem module side as well +#endif +} + + +void setup() { + Serial.begin(SERIAL_SPEED); // initialize serial +#ifdef ESP8266 + EEPROM.begin(100); +#endif + delay (500); + interrupts(); + Serial.println(); + Output = &Serial; + millisWraps = 0; + lastMillis = millis(); + initialize(); +#ifdef analogIR + pinMode(irOutPin, OUTPUT); +#ifdef ledOutPin + pinMode(ledOutPin, OUTPUT); +#endif +#endif + helloCmd(); // started message to serial +#ifdef ESP8266 + connectWiFi(); +#endif +} + + +/* Main Loop */ +void loop() { + handleTime(); + if (Serial.available()) handleInput(Serial.read()); +#ifdef ESP8266 + handleConnections(); +#endif + +#ifdef analogIR + short AIndex = allowedPins[analogInPin]; + if (AIndex >= 0 && activePin[AIndex] >= 0) { + digitalWrite(irOutPin, LOW); + // wait 10 milliseconds + delay(10); + // read the analog in value: + sensorValueOff = analogRead(analogInPin); + // turn IR LED on + digitalWrite(irOutPin, HIGH); + delay(10); + // read the analog in value: + sensorValueOn = analogRead(analogInPin); + detectTrigger (sensorValueOn - sensorValueOff); + if (devVerbose >= 20) { + Output->print(F("L")); + + Output->print(sensorValueOn); + Output->print(F(",")); + Output->print(sensorValueOff); + Output->print(F("->")); + + Output->println(sensorValueOn - sensorValueOff); + } + } +#endif + +#ifdef debugPins + if (devVerbose >= 10) { + debugPinChanges(); + } +#endif + + if (reportDue()) { + report(); + } +}