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();
+ }
+}