98_ArduCounter.pm 68 KB

12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152535455565758596061626364656667686970717273747576777879808182838485868788899091929394959697989910010110210310410510610710810911011111211311411511611711811912012112212312412512612712812913013113213313413513613713813914014114214314414514614714814915015115215315415515615715815916016116216316416516616716816917017117217317417517617717817918018118218318418518618718818919019119219319419519619719819920020120220320420520620720820921021121221321421521621721821922022122222322422522622722822923023123223323423523623723823924024124224324424524624724824925025125225325425525625725825926026126226326426526626726826927027127227327427527627727827928028128228328428528628728828929029129229329429529629729829930030130230330430530630730830931031131231331431531631731831932032132232332432532632732832933033133233333433533633733833934034134234334434534634734834935035135235335435535635735835936036136236336436536636736836937037137237337437537637737837938038138238338438538638738838939039139239339439539639739839940040140240340440540640740840941041141241341441541641741841942042142242342442542642742842943043143243343443543643743843944044144244344444544644744844945045145245345445545645745845946046146246346446546646746846947047147247347447547647747847948048148248348448548648748848949049149249349449549649749849950050150250350450550650750850951051151251351451551651751851952052152252352452552652752852953053153253353453553653753853954054154254354454554654754854955055155255355455555655755855956056156256356456556656756856957057157257357457557657757857958058158258358458558658758858959059159259359459559659759859960060160260360460560660760860961061161261361461561661761861962062162262362462562662762862963063163263363463563663763863964064164264364464564664764864965065165265365465565665765865966066166266366466566666766866967067167267367467567667767867968068168268368468568668768868969069169269369469569669769869970070170270370470570670770870971071171271371471571671771871972072172272372472572672772872973073173273373473573673773873974074174274374474574674774874975075175275375475575675775875976076176276376476576676776876977077177277377477577677777877978078178278378478578678778878979079179279379479579679779879980080180280380480580680780880981081181281381481581681781881982082182282382482582682782882983083183283383483583683783883984084184284384484584684784884985085185285385485585685785885986086186286386486586686786886987087187287387487587687787887988088188288388488588688788888989089189289389489589689789889990090190290390490590690790890991091191291391491591691791891992092192292392492592692792892993093193293393493593693793893994094194294394494594694794894995095195295395495595695795895996096196296396496596696796896997097197297397497597697797897998098198298398498598698798898999099199299399499599699799899910001001100210031004100510061007100810091010101110121013101410151016101710181019102010211022102310241025102610271028102910301031103210331034103510361037103810391040104110421043104410451046104710481049105010511052105310541055105610571058105910601061106210631064106510661067106810691070107110721073107410751076107710781079108010811082108310841085108610871088108910901091109210931094109510961097109810991100110111021103110411051106110711081109111011111112111311141115111611171118111911201121112211231124112511261127112811291130113111321133113411351136113711381139114011411142114311441145114611471148114911501151115211531154115511561157115811591160116111621163116411651166116711681169117011711172117311741175117611771178117911801181118211831184118511861187118811891190119111921193119411951196119711981199120012011202120312041205120612071208120912101211121212131214121512161217121812191220122112221223122412251226122712281229123012311232123312341235123612371238123912401241124212431244124512461247124812491250125112521253125412551256125712581259126012611262126312641265126612671268126912701271127212731274127512761277127812791280128112821283128412851286128712881289129012911292129312941295129612971298129913001301130213031304130513061307130813091310131113121313131413151316131713181319132013211322132313241325132613271328132913301331133213331334133513361337133813391340134113421343134413451346134713481349135013511352135313541355135613571358135913601361136213631364136513661367136813691370137113721373137413751376137713781379138013811382138313841385138613871388138913901391139213931394139513961397139813991400140114021403140414051406140714081409141014111412141314141415141614171418141914201421142214231424142514261427142814291430143114321433143414351436143714381439144014411442144314441445144614471448144914501451145214531454145514561457145814591460146114621463146414651466146714681469147014711472147314741475147614771478147914801481148214831484148514861487148814891490149114921493149414951496149714981499150015011502150315041505150615071508150915101511151215131514151515161517151815191520152115221523
  1. ############################################################################
  2. # $Id: 98_ArduCounter.pm 17270 2018-09-04 16:40:46Z StefanStrobel $
  3. # fhem Modul für Impulszähler auf Basis von Arduino mit ArduCounter Sketch
  4. #
  5. # This file is part of fhem.
  6. #
  7. # Fhem is free software: you can redistribute it and/or modify
  8. # it under the terms of the GNU General Public License as published by
  9. # the Free Software Foundation, either version 2 of the License, or
  10. # (at your option) any later version.
  11. #
  12. # Fhem is distributed in the hope that it will be useful,
  13. # but WITHOUT ANY WARRANTY; without even the implied warranty of
  14. # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
  15. # GNU General Public License for more details.
  16. #
  17. # You should have received a copy of the GNU General Public License
  18. # along with fhem. If not, see <http://www.gnu.org/licenses/>.
  19. #
  20. ##############################################################################
  21. # Changelog:
  22. #
  23. # 2014-2-4 initial version
  24. # 2014-3-12 added documentation
  25. # 2015-02-08 renamed ACNT to ArduCounter
  26. # 2016-01-01 added attributes for reading names
  27. # 2016-10-15 fixed bug in handling Initialized / STATE
  28. # added attribute for individual factor for each pin
  29. # 2016-10-29 added option to receive additional Message vom sketch and log it at level 4
  30. # added documentation, changed logging timestamp for power to begin of interval
  31. # 2016-11-02 Attribute to control timestamp backdating
  32. # 2016-11-04 allow number instead of rising etc. as change with min pulse length
  33. # 2016-11-10 finish parsing new messages
  34. # 2016-11-12 added attributes verboseReadings, readingStartTime
  35. # add readAnswer for get info
  36. # 2016-12-13 better logging, ignore empty lines from Ardiuno
  37. # change to new communication syntax of sketch version 1.6
  38. # 2016-12-24 add -b 57600 to flashCommand
  39. # 2016-12-25 check for old firmware and log error, better logging, disable attribute
  40. # 2017-01-01 improved logging
  41. # 2017-01-02 modification for sketch 1.7, monitor clock drift difference between ardino and Fhem
  42. # 2017-01-04 some more beatification in logging
  43. # 2017-01-06 avoid reopening when disable=0 is set during startup
  44. # 2017-02-06 Doku korrigiert
  45. # 2017-02-18 fixed a bug that caused a missing open when the device is defined while fhem is already initialized
  46. # 2017-05-09 fixed character encoding for documentation text
  47. # 2017-09-24 interpolation of lost impulses during fhem restart / arduino reset
  48. # 2017-10-03 bug fix
  49. # 2017-10-06 more optimisations regarding longCount
  50. # 2017-10-08 more little bug fixes (parsing debug messages)
  51. # 2017-10-14 modifications for new sketch version 1.9
  52. # 2017-11-26 minor modifications of log levels
  53. # 2017-12-02 fixed adding up reject count reading
  54. # 2017-12-27 modified logging levels
  55. # 2018-01-01 little fixes
  56. # 2018-01-02 extend reporting line with history H.*, create new reading pinHistory if received from device and verboseReadings is set to 1
  57. # create long count readings always, not only if attr verboseReadings is set to 1
  58. # 2018-01-03 little docu fix
  59. # 2018-01-13 little docu addon
  60. # 2018-02-04 modifications for ArduCounter on ESP8266 connected via TCP
  61. # remove "change" as option (only rising and falling allowed now)
  62. # TCP connection handling, keepalive,
  63. # many changes more ...
  64. # 2018-03-07 fix pinHistory when verboseReadings is not set
  65. # 2018-03-08 parse board name in setup / hello message
  66. # 2018-04-10 many smaller fixes, new interpolation based on real boot time, counter etc.
  67. # 2018-05-13 send keepalive delay with k command, don't reset k timer when parsing a message
  68. # 2018-07-17 modify define / notify so connection is opened after Event Defined
  69. #
  70. # ideas / todo:
  71. # - OTA Flashing for ESP
  72. #
  73. # - parse sequence num of history entries -> reconstruct long history list in perl mem
  74. # and display with get history instead of readings incl. individual time
  75. #
  76. # - timeMissed
  77. #
  78. #
  79. package main;
  80. use strict;
  81. use warnings;
  82. use Time::HiRes qw(gettimeofday);
  83. my %ArduCounter_sets = (
  84. "disable" => "",
  85. "enable" => "",
  86. "raw" => "",
  87. "reset" => "",
  88. "flash" => "",
  89. "devVerbose" => "",
  90. "saveConfig" => "",
  91. "reconnect" => ""
  92. );
  93. my %ArduCounter_gets = (
  94. "info" => ""
  95. );
  96. my $ArduCounter_Version = '5.94 - 13.5.2018';
  97. #
  98. # FHEM module intitialisation
  99. # defines the functions to be called from FHEM
  100. #########################################################################
  101. sub ArduCounter_Initialize($)
  102. {
  103. my ($hash) = @_;
  104. require "$attr{global}{modpath}/FHEM/DevIo.pm";
  105. $hash->{ReadFn} = "ArduCounter_Read";
  106. $hash->{ReadyFn} = "ArduCounter_Ready";
  107. $hash->{DefFn} = "ArduCounter_Define";
  108. $hash->{UndefFn} = "ArduCounter_Undef";
  109. $hash->{GetFn} = "ArduCounter_Get";
  110. $hash->{SetFn} = "ArduCounter_Set";
  111. $hash->{AttrFn} = "ArduCounter_Attr";
  112. $hash->{NotifyFn} = "ArduCounter_Notify";
  113. $hash->{AttrList} =
  114. 'pin.* ' .
  115. "interval " .
  116. "factor " .
  117. "readingNameCount[0-9]+ " .
  118. "readingNamePower[0-9]+ " .
  119. "readingNameLongCount[0-9]+ " .
  120. "readingNameInterpolatedCount[0-9]+ " .
  121. "readingFactor[0-9]+ " .
  122. "readingStartTime[0-9]+ " .
  123. "verboseReadings[0-9]+ " .
  124. "flashCommand " .
  125. "helloSendDelay " .
  126. "helloWaitTime " .
  127. "keepAliveDelay " .
  128. "keepAliveTimeout " .
  129. "nextOpenDelay " .
  130. "silentReconnect " .
  131. "openTimeout " .
  132. "disable:0,1 " .
  133. "do_not_notify:1,0 " .
  134. $readingFnAttributes;
  135. }
  136. #
  137. # Define command
  138. ##########################################################################
  139. sub ArduCounter_Define($$)
  140. {
  141. my ($hash, $def) = @_;
  142. my @a = split( "[ \t\n]+", $def );
  143. return "wrong syntax: define <name> ArduCounter devicename\@speed"
  144. if ( @a < 3 );
  145. DevIo_CloseDev($hash);
  146. my $name = $a[0];
  147. my $dev = $a[2];
  148. if ($dev =~ m/^(.+):([0-9]+)$/) {
  149. # tcp conection
  150. $hash->{TCP} = 1;
  151. } else {
  152. if ($dev !~ /.+@([0-9]+)/) {
  153. $dev .= '@38400';
  154. } else {
  155. Log3 $name, 3, "$name: Warning: connection speed $1 is not the default for the ArduCounter firmware"
  156. if ($1 != 38400);
  157. }
  158. }
  159. $hash->{DeviceName} = $dev;
  160. $hash->{VersionModule} = $ArduCounter_Version;
  161. $hash->{NOTIFYDEV} = "global"; # NotifyFn nur aufrufen wenn global events (INITIALIZED)
  162. $hash->{STATE} = "disconnected";
  163. delete $hash->{Initialized}; # device might not be initialized - wait for hello / setup before cmds
  164. if(!defined($attr{$name}{'flashCommand'})) {
  165. #$attr{$name}{'flashCommand'} = 'avrdude -p atmega328P -b 57600 -c arduino -P [PORT] -D -U flash:w:[HEXFILE] 2>[LOGFILE]'; # for nano
  166. $attr{$name}{'flashCommand'} = 'avrdude -p atmega328P -c arduino -P [PORT] -D -U flash:w:[HEXFILE] 2>[LOGFILE]'; # for uno
  167. }
  168. Log3 $name, 5, "$name: defined with $dev, Module version $ArduCounter_Version";
  169. #if ($init_done) {
  170. # ArduCounter_Open($hash);
  171. #}
  172. # do open in notify
  173. return;
  174. }
  175. #
  176. # undefine command when device is deleted
  177. #########################################################################
  178. sub ArduCounter_Undef($$)
  179. {
  180. my ( $hash, $arg ) = @_;
  181. DevIo_CloseDev($hash);
  182. }
  183. # remove timers, call DevIo_Disconnected
  184. # to set state and add to readyFnList
  185. #####################################################
  186. sub ArduCounter_Disconnected($)
  187. {
  188. my $hash = shift;
  189. my $name = $hash->{NAME};
  190. RemoveInternalTimer ("alive:$name"); # no timeout if waiting for keepalive response
  191. RemoveInternalTimer ("keepAlive:$name"); # don't send keepalive messages anymore
  192. RemoveInternalTimer ("sendHello:$name");
  193. DevIo_Disconnected($hash); # close, add to readyFnList so _Ready is called to reopen
  194. delete $hash->{WaitForAlive};
  195. }
  196. #####################################
  197. sub ArduCounter_OpenCB($$)
  198. {
  199. my ($hash, $msg) = @_;
  200. my $name = $hash->{NAME};
  201. my $now = gettimeofday();
  202. if ($msg) {
  203. Log3 $name, 5, "$name: Open callback: $msg" if ($msg);
  204. }
  205. delete $hash->{BUSY_OPENDEV};
  206. if ($hash->{FD}) {
  207. Log3 $name, 5, "$name: ArduCounter_Open succeeded in callback";
  208. my $hdl = AttrVal($name, "helloSendDelay", 15);
  209. # send hello if device doesn't say "Started" withing $hdl seconds
  210. RemoveInternalTimer ("sendHello:$name");
  211. InternalTimer($now+$hdl, "ArduCounter_AskForHello", "sendHello:$name", 0);
  212. if ($hash->{TCP}) {
  213. # send first keepalive immediately to turn on tcp mode in device
  214. ArduCounter_KeepAlive("keepAlive:$name");
  215. }
  216. } else {
  217. #Log3 $name, 5, "$name: ArduCounter_Open failed - open callback called from DevIO without FD";
  218. }
  219. return;
  220. }
  221. ########################################################
  222. # Open Device
  223. sub ArduCounter_Open($;$)
  224. {
  225. my ($hash, $reopen) = @_;
  226. my $name = $hash->{NAME};
  227. my $now = gettimeofday();
  228. $reopen = 0 if (!$reopen);
  229. if ($hash->{BUSY_OPENDEV}) { # still waiting for callback to last open
  230. if ($hash->{LASTOPEN} && $now > $hash->{LASTOPEN} + (AttrVal($name, "openTimeout", 3) * 2)
  231. && $now > $hash->{LASTOPEN} + 15) {
  232. Log3 $name, 5, "$name: _Open - still waiting for open callback, timeout is over twice - this should never happen";
  233. Log3 $name, 5, "$name: _Open - stop waiting and reset the flag.";
  234. $hash->{BUSY_OPENDEV} = 0;
  235. } else {
  236. Log3 $name, 5, "$name: _Open - still waiting for open callback";
  237. return;
  238. }
  239. }
  240. if (!$reopen) { # not called from _Ready
  241. DevIo_CloseDev($hash);
  242. delete $hash->{NEXT_OPEN};
  243. delete $hash->{DevIoJustClosed};
  244. }
  245. Log3 $name, 4, "$name: trying to open connection to $hash->{DeviceName}" if (!$reopen);
  246. $hash->{BUSY_OPENDEV} = 1;
  247. $hash->{LASTOPEN} = $now;
  248. $hash->{nextOpenDelay} = AttrVal($name, "nextOpenDelay", 60);
  249. $hash->{devioLoglevel} = (AttrVal($name, "silentReconnect", 0) ? 4 : 3);
  250. $hash->{TIMEOUT} = AttrVal($name, "openTimeout", 3);
  251. $hash->{buffer} = ""; # clear Buffer for reception
  252. DevIo_OpenDev($hash, $reopen, 0, \&ArduCounter_OpenCB);
  253. delete $hash->{TIMEOUT};
  254. if ($hash->{FD}) {
  255. Log3 $name, 5, "$name: ArduCounter_Open succeeded immediatelay" if (!$reopen);
  256. } else {
  257. Log3 $name, 5, "$name: ArduCounter_Open waiting for callback" if (!$reopen);
  258. }
  259. }
  260. #########################################################################
  261. sub ArduCounter_Ready($)
  262. {
  263. my ($hash) = @_;
  264. my $name = $hash->{NAME};
  265. if($hash->{STATE} eq "disconnected") {
  266. RemoveInternalTimer ("alive:$name"); # no timeout if waiting for keepalive response
  267. RemoveInternalTimer ("keepAlive:$name"); # don't send keepalive messages anymore
  268. delete $hash->{WaitForAlive};
  269. delete $hash->{Initialized}; # when reconnecting wait for setup / hello before further action
  270. if (IsDisabled($name)) {
  271. Log3 $name, 3, "$name: _Ready: $name is disabled - don't try to reconnect";
  272. DevIo_CloseDev($hash); # close, remove from readyfnlist so _ready is not called again
  273. return;
  274. }
  275. ArduCounter_Open($hash, 1); # reopen, don't call DevIoClose before reopening
  276. return; # a return value triggers direct read for win
  277. }
  278. # This is relevant for windows/USB only
  279. my $po = $hash->{USBDev};
  280. if ($po) {
  281. my ($BlockingFlags, $InBytes, $OutBytes, $ErrorFlags) = $po->status;
  282. return ($InBytes>0); # tell fhem.pl to read when we return
  283. }
  284. return;
  285. }
  286. #######################################
  287. # Aufruf aus InternalTimer
  288. sub ArduCounter_DelayedOpen($)
  289. {
  290. my $param = shift;
  291. my (undef,$name) = split(':',$param);
  292. my $hash = $defs{$name};
  293. Log3 $name, 4, "$name: try to reopen connection after delay";
  294. RemoveInternalTimer ("delayedopen:$name");
  295. delete $hash->{DevIoJustClosed}; # otherwise open returns without doing anything this time and we are not on the readyFnList ...
  296. ArduCounter_Open($hash, 1); # reopen
  297. }
  298. ########################################################
  299. # Notify for INITIALIZED or Modified
  300. # -> Open connection to device
  301. sub ArduCounter_Notify($$)
  302. {
  303. my ($hash, $source) = @_;
  304. return if($source->{NAME} ne "global");
  305. my $events = deviceEvents($source, 1);
  306. return if(!$events);
  307. my $name = $hash->{NAME};
  308. # Log3 $name, 5, "$name: Notify called for source $source->{NAME} with events: @{$events}";
  309. return if (!grep(m/^INITIALIZED|REREADCFG|(MODIFIED $name)|(DEFINED $name)$/, @{$source->{CHANGED}}));
  310. if (IsDisabled($name)) {
  311. Log3 $name, 3, "$name: Notify / Init: device is disabled";
  312. return;
  313. }
  314. Log3 $name, 3, "$name: Notify called with events: @{$events}, open device and set timer to send hello to device";
  315. ArduCounter_Open($hash);
  316. }
  317. ######################################
  318. # wrapper for DevIo write
  319. sub ArduCounter_Write ($$)
  320. {
  321. my ($hash, $line) = @_;
  322. my $name = $hash->{NAME};
  323. if ($hash->{STATE} eq "disconnected" || !$hash->{FD}) {
  324. Log3 $name, 5, "$name: Write: device is disconnected, dropping line to write";
  325. return 0;
  326. }
  327. if (IsDisabled($name)) {
  328. Log3 $name, 5, "$name: Write called but device is disabled, dropping line to send";
  329. return 0;
  330. }
  331. #Log3 $name, 5, "$name: Write: $line"; # devio will already log the write
  332. #DevIo_SimpleWrite($hash, "\n", 2);
  333. DevIo_SimpleWrite($hash, "$line.", 2);
  334. return 1;
  335. }
  336. #######################################
  337. # Aufruf aus InternalTimer
  338. # send "h" to ask for "Hello" since device didn't say "Started" so far - maybe it's still counting ...
  339. # called with timer from _open, _Ready and if count is read in _Parse
  340. sub ArduCounter_AskForHello($)
  341. {
  342. my $param = shift;
  343. my (undef,$name) = split(':',$param);
  344. my $hash = $defs{$name};
  345. Log3 $name, 3, "$name: sending h(ello) to device to ask for version";
  346. return if (!ArduCounter_Write( $hash, "h"));
  347. my $now = gettimeofday();
  348. my $hwt = AttrVal($name, "helloWaitTime", 3);
  349. RemoveInternalTimer ("hwait:$name");
  350. InternalTimer($now+$hwt, "ArduCounter_HelloTimeout", "hwait:$name", 0);
  351. $hash->{WaitForHello} = 1;
  352. }
  353. #######################################
  354. # Aufruf aus InternalTimer
  355. sub ArduCounter_HelloTimeout($)
  356. {
  357. my $param = shift;
  358. my (undef,$name) = split(':',$param);
  359. my $hash = $defs{$name};
  360. Log3 $name, 3, "$name: device didn't reply to h(ello). Is the right sketch flashed? Is speed set to 38400?";
  361. delete $hash->{WaitForHello};
  362. RemoveInternalTimer ("hwait:$name");
  363. }
  364. ############################################
  365. # Aufruf aus Open / Ready und InternalTimer
  366. # send "1k" to ask for "alive"
  367. sub ArduCounter_KeepAlive($)
  368. {
  369. my $param = shift;
  370. my (undef,$name) = split(':',$param);
  371. my $hash = $defs{$name};
  372. my $now = gettimeofday();
  373. if (IsDisabled($name)) {
  374. return;
  375. }
  376. my $kdl = AttrVal($name, "keepAliveDelay", 10); # next keepalive as timer
  377. my $kto = AttrVal($name, "keepAliveTimeout", 2); # timeout waiting for response
  378. Log3 $name, 5, "$name: sending k(eepAlive) to device";
  379. ArduCounter_Write( $hash, "1,${kdl}k");
  380. RemoveInternalTimer ("alive:$name");
  381. InternalTimer($now+$kto, "ArduCounter_AliveTimeout", "alive:$name", 0);
  382. $hash->{WaitForAlive} = 1;
  383. if ($hash->{TCP}) {
  384. RemoveInternalTimer ("keepAlive:$name");
  385. InternalTimer($now+$kdl, "ArduCounter_KeepAlive", "keepAlive:$name", 0); # next keepalive
  386. }
  387. }
  388. #######################################
  389. # Aufruf aus InternalTimer
  390. sub ArduCounter_AliveTimeout($)
  391. {
  392. my $param = shift;
  393. my (undef,$name) = split(':',$param);
  394. my $hash = $defs{$name};
  395. Log3 $name, 3, "$name: device didn't reply to k(eeepAlive), setting to disconnected and try to reopen";
  396. delete $hash->{WaitForAlive};
  397. $hash->{KeepAliveRetries} = 0 if (!$hash->{KeepAliveRetries});
  398. if (++$hash->{KeepAliveRetries} > AttrVal($name, "keepAliveRetries", 1)) {
  399. Log3 $name, 3, "$name: no retries left, setting device to disconnected";
  400. ArduCounter_Disconnected($hash); # set to Disconnected but let _Ready try to Reopen
  401. }
  402. }
  403. #
  404. # Send config commands after Board reported it is ready or still counting
  405. # called from internal timer to give device the time to report its config first
  406. ##########################################################################
  407. sub ArduCounter_ConfigureDevice($)
  408. {
  409. my $param = shift;
  410. my (undef,$name) = split(':',$param);
  411. my $hash = $defs{$name};
  412. # todo: check if device got disconnected in the meantime!
  413. # first check if device did send its config, then compare and send config if necessary
  414. if ($hash->{runningCfg}) {
  415. Log3 $name, 5, "$name: ConfigureDevice: got running config - comparing";
  416. my $iAttr = AttrVal($name, "interval", "");
  417. if (!$iAttr) {
  418. $iAttr = "30 60 2 2";
  419. Log3 $name, 5, "$name: ConfigureDevice: interval attr not set - take default $iAttr";
  420. }
  421. if ($iAttr =~ /^(\d+) (\d+) ?(\d+)? ?(\d+)?$/) {
  422. #Log3 $name, 5, "$name: ConfigureDevice: comparing interval";
  423. my $iACfg = "$1 $2 " . ($3 ? $3 : "0") . " " . ($4 ? $4 : "0");
  424. if ($hash->{runningCfg}{I} eq $iACfg) {
  425. #Log3 $name, 5, "$name: ConfigureDevice: interval matches - now compare pins";
  426. # interval config matches - now check pins as well
  427. my @runningPins = sort grep (/[\d]/, keys %{$hash->{runningCfg}});
  428. #Log3 $name, 5, "$name: ConfigureDevice: pins in running config: @runningPins";
  429. my @attrPins = sort grep (/pin([dD])?[\d]/, keys %{$attr{$name}});
  430. #Log3 $name, 5, "$name: ConfigureDevice: pins from attrs: @attrPins";
  431. if (@runningPins == @attrPins) {
  432. my $match = 1;
  433. for (my $i = 0; $i < @attrPins; $i++) {
  434. #Log3 $name, 5, "$name: ConfigureDevice: compare pin $attrPins[$i] to $runningPins[$i]";
  435. $attrPins[$i] =~ /pin[dD]?([\d+]+)/;
  436. my $pinNum = $1;
  437. $runningPins[$i] =~ /pin[dD]?([\d]+)/;
  438. $match = 0 if (!$1 || $1 ne $pinNum);
  439. #Log3 $name, 5, "$name: ConfigureDevice: now compare pin $attrPins[$i] $attr{$name}{$attrPins[$i]} to $hash->{runningCfg}{$pinNum}";
  440. $match = 0 if (($attr{$name}{$attrPins[$i]}) ne $hash->{runningCfg}{$pinNum});
  441. }
  442. if ($match) { # Config matches -> leave
  443. Log3 $name, 5, "$name: ConfigureDevice: running config matches attributes";
  444. return;
  445. }
  446. Log3 $name, 5, "$name: ConfigureDevice: no match -> send config";
  447. } else {
  448. Log3 $name, 5, "$name: ConfigureDevice: pin numbers don't match (@runningPins vs. @attrPins)";
  449. }
  450. } else {
  451. Log3 $name, 5, "$name: ConfigureDevice: interval does not match (>$hash->{runningCfg}{I}< vs >$iACfg< from attr)";
  452. }
  453. } else {
  454. Log3 $name, 5, "$name: ConfigureDevice: can not compare against interval attr";
  455. }
  456. } else {
  457. Log3 $name, 5, "$name: ConfigureDevice: no running config received";
  458. }
  459. # send attributes to arduino device. Just call ArduCounter_Attr again
  460. Log3 $name, 3, "$name: sending configuration from attributes to device";
  461. while (my ($aName, $val) = each(%{$attr{$name}})) {
  462. if ($aName =~ "pin|interval") {
  463. Log3 $name, 3, "$name: ConfigureDevice calls Attr with $aName $val";
  464. ArduCounter_Attr("set", $name, $aName, $val);
  465. }
  466. }
  467. }
  468. # Attr command
  469. #########################################################################
  470. sub ArduCounter_Attr(@)
  471. {
  472. my ($cmd,$name,$aName,$aVal) = @_;
  473. # $cmd can be "del" or "set"
  474. # $name is device name
  475. # aName and aVal are Attribute name and value
  476. my $hash = $defs{$name};
  477. my $modHash = $modules{$hash->{TYPE}};
  478. #Log3 $name, 5, "$name: Attr called with @_";
  479. if ($cmd eq "set") {
  480. if ($aName =~ /^pin[dD]?(\d+)/) {
  481. my $pin = $1;
  482. my %pins;
  483. if ($hash->{allowedPins}) {
  484. %pins = map { $_ => 1 } split (",", $hash->{allowedPins});
  485. }
  486. if ($init_done && $hash->{allowedPins} && %pins && !$pins{$pin}) {
  487. Log3 $name, 3, "$name: Invalid pin in attr $name $aName $aVal";
  488. return "Invalid / disallowed pin specification $aName";
  489. }
  490. if ($aVal =~ /^(rising|falling) ?(pullup)? ?([0-9]+)?/) {
  491. my $opt = "";
  492. if ($1 eq 'rising') {$opt = "3"}
  493. elsif ($1 eq 'falling') {$opt = "2"}
  494. $opt .= ($2 ? ",1" : ",0"); # pullup
  495. $opt .= ($3 ? ",$3" : ""); # min length
  496. if ($hash->{Initialized}) {
  497. ArduCounter_Write($hash, "${pin},${opt}a");
  498. } else {
  499. Log3 $name, 5, "$name: communication postponed until device is initialized";
  500. }
  501. } else {
  502. Log3 $name, 3, "$name: Invalid value in attr $name $aName $aVal";
  503. return "Invalid Value $aVal";
  504. }
  505. } elsif ($aName eq "interval") {
  506. if ($aVal =~ /^(\d+) (\d+) ?(\d+)? ?(\d+)?$/) {
  507. my $min = $1;
  508. my $max = $2;
  509. my $sml = $3;
  510. my $cnt = $4;
  511. if ($min < 1 || $min > 3600 || $max < $min || $max > 3600) {
  512. Log3 $name, 3, "$name: Invalid value in attr $name $aName $aVal";
  513. return "Invalid Value $aVal";
  514. }
  515. if ($hash->{Initialized}) {
  516. $sml = 0 if (!$sml);
  517. $cnt = 0 if (!$cnt);
  518. ArduCounter_Write($hash, "${min},${max},${sml},${cnt}i");
  519. } else {
  520. Log3 $name, 5, "$name: communication postponed until device is initialized";
  521. }
  522. } else {
  523. Log3 $name, 3, "$name: Invalid value in attr $name $aName $aVal";
  524. return "Invalid Value $aVal";
  525. }
  526. } elsif ($aName eq "factor") {
  527. if ($aVal =~ '^(\d+)$') {
  528. } else {
  529. Log3 $name, 3, "$name: Invalid value in attr $name $aName $aVal";
  530. return "Invalid Value $aVal";
  531. }
  532. } elsif ($aName eq "keepAliveDelay") {
  533. if ($aVal =~ '^(\d+)$') {
  534. if ($aVal > 300) {
  535. Log3 $name, 3, "$name: value too big in attr $name $aName $aVal";
  536. return "Value too big: $aVal";
  537. }
  538. } else {
  539. Log3 $name, 3, "$name: Invalid value in attr $name $aName $aVal";
  540. return "Invalid Value $aVal";
  541. }
  542. } elsif ($aName eq 'disable') {
  543. if ($aVal) {
  544. Log3 $name, 5, "$name: disable attribute set";
  545. ArduCounter_Disconnected($hash); # set to disconnected and remove timers
  546. DevIo_CloseDev($hash); # really close and remove from readyFnList again
  547. return;
  548. } else {
  549. Log3 $name, 3, "$name: disable attribute cleared";
  550. ArduCounter_Open($hash) if ($init_done); # only if fhem is initialized
  551. }
  552. }
  553. # handle wild card attributes -> Add to userattr to allow modification in fhemweb
  554. #Log3 $name, 3, "$name: attribute $aName checking ";
  555. if (" $modHash->{AttrList} " !~ m/ ${aName}[ :;]/) {
  556. # nicht direkt in der Liste -> evt. wildcard attr in AttrList
  557. foreach my $la (split " ", $modHash->{AttrList}) {
  558. $la =~ /([^:;]+)(:?.*)/;
  559. my $vgl = $1; # attribute name in list - probably a regex
  560. my $opt = $2; # attribute hint in list
  561. if ($aName =~ $vgl) { # yes - the name in the list now matches as regex
  562. # $aName ist eine Ausprägung eines wildcard attrs
  563. addToDevAttrList($name, "$aName" . $opt); # create userattr with hint to allow changing by click in fhemweb
  564. if ($opt) {
  565. # remove old entries without hint
  566. my $ualist = $attr{$name}{userattr};
  567. $ualist = "" if(!$ualist);
  568. my %uahash;
  569. foreach my $a (split(" ", $ualist)) {
  570. if ($a !~ /^${aName}$/) { # entry in userattr list is attribute without hint
  571. $uahash{$a} = 1;
  572. } else {
  573. Log3 $name, 3, "$name: added hint $opt to attr $a in userattr list";
  574. }
  575. }
  576. $attr{$name}{userattr} = join(" ", sort keys %uahash);
  577. }
  578. }
  579. }
  580. } else {
  581. # exakt in Liste enthalten -> sicherstellen, dass keine +* etc. drin sind.
  582. if ($aName =~ /\|\*\+\[/) {
  583. Log3 $name, 3, "$name: Atribute $aName is not valid. It still contains wildcard symbols";
  584. return "$name: Atribute $aName is not valid. It still contains wildcard symbols";
  585. }
  586. }
  587. } elsif ($cmd eq "del") {
  588. if ($aName =~ 'pin.*') {
  589. if ($aName !~ 'pin([dD]?\d+)') {
  590. Log3 $name, 3, "$name: Invalid pin name in attr $name $aName $aVal";
  591. return "Invalid pin name $aName";
  592. }
  593. my $pin = $1;
  594. if ($hash->{Initialized}) { # did device already report its version?
  595. ArduCounter_Write( $hash, "${pin}d");
  596. } else {
  597. Log3 $name, 5, "$name: pin config can not be deleted since device is not initialized yet";
  598. return "device is not initialized yet";
  599. }
  600. } elsif ($aName eq 'disable') {
  601. Log3 $name, 3, "$name: disable attribute removed";
  602. ArduCounter_Open($hash) if ($init_done); # if fhem is initialized
  603. }
  604. }
  605. return undef;
  606. }
  607. # SET command
  608. #########################################################################
  609. sub ArduCounter_Flash($$)
  610. {
  611. my ($hash, @args) = @_;
  612. my $name = $hash->{NAME};
  613. my $log = "";
  614. my @deviceName = split('@', $hash->{DeviceName});
  615. my $port = $deviceName[0];
  616. my $firmwareFolder = "./FHEM/firmware/";
  617. my $logFile = AttrVal("global", "logdir", "./log") . "/ArduCounterFlash.log";
  618. return "Flashing ESP8266 not supported yet" if ($hash->{Board} =~ /ESP8266/);
  619. my $hexFile = $firmwareFolder . "ArduCounter.hex";
  620. return "The file '$hexFile' does not exist" if(!-e $hexFile);
  621. Log3 $name, 3, "$name: Flashing Aduino at $port with $hexFile. See $logFile for details";
  622. $log .= "flashing device as ArduCounter for $name\n";
  623. $log .= "hex file: $hexFile\n";
  624. $log .= "port: $port\n";
  625. $log .= "log file: $logFile\n";
  626. my $flashCommand = AttrVal($name, "flashCommand", "");
  627. if($flashCommand ne "") {
  628. if (-e $logFile) {
  629. unlink $logFile;
  630. }
  631. ArduCounter_Disconnected($hash);
  632. DevIo_CloseDev($hash);
  633. $log .= "$name closed\n";
  634. my $avrdude = $flashCommand;
  635. $avrdude =~ s/\Q[PORT]\E/$port/g;
  636. $avrdude =~ s/\Q[HEXFILE]\E/$hexFile/g;
  637. $avrdude =~ s/\Q[LOGFILE]\E/$logFile/g;
  638. $log .= "command: $avrdude\n\n";
  639. `$avrdude`;
  640. local $/=undef;
  641. if (-e $logFile) {
  642. open FILE, $logFile;
  643. my $logText = <FILE>;
  644. close FILE;
  645. $log .= "--- AVRDUDE ---------------------------------------------------------------------------------\n";
  646. $log .= $logText;
  647. $log .= "--- AVRDUDE ---------------------------------------------------------------------------------\n\n";
  648. }
  649. else {
  650. $log .= "WARNING: avrdude created no log file\n\n";
  651. }
  652. ArduCounter_Open($hash, 0); # new open
  653. $log .= "$name open called.\n";
  654. delete $hash->{Initialized};
  655. }
  656. return $log;
  657. }
  658. # SET command
  659. #########################################################################
  660. sub ArduCounter_Set($@)
  661. {
  662. my ($hash, @a) = @_;
  663. return "\"set ArduCounter\" needs at least one argument" if ( @a < 2 );
  664. # @a is an array with DeviceName, SetName, Rest of Set Line
  665. my $name = shift @a;
  666. my $attr = shift @a;
  667. my $arg = join(" ", @a);
  668. if(!defined($ArduCounter_sets{$attr})) {
  669. my @cList = keys %ArduCounter_sets;
  670. return "Unknown argument $attr, choose one of " . join(" ", @cList);
  671. }
  672. if ($attr eq "disable") {
  673. Log3 $name, 4, "$name: set disable called";
  674. CommandAttr(undef, "$name disable 1");
  675. return;
  676. } elsif ($attr eq "enable") {
  677. Log3 $name, 4, "$name: set enable called";
  678. CommandAttr(undef, "$name disable 0");
  679. return;
  680. } elsif ($attr eq "reconnect") {
  681. Log3 $name, 4, "$name: set reconnect called";
  682. DevIo_CloseDev($hash);
  683. ArduCounter_Open($hash);
  684. return;
  685. } elsif ($attr eq "flash") {
  686. return ArduCounter_Flash($hash, @a);
  687. }
  688. if(!$hash->{FD}) {
  689. Log3 $name, 4, "$name: Set $attr $arg called but device is disconnected";
  690. return ("Set called but device is disconnected", undef);
  691. }
  692. if (IsDisabled($name)) {
  693. Log3 $name, 4, "$name: set $attr $arg called but device is disabled";
  694. return;
  695. }
  696. if ($attr eq "raw") {
  697. Log3 $name, 4, "$name: set raw $arg called";
  698. ArduCounter_Write($hash, "$arg");
  699. } elsif ($attr eq "saveConfig") {
  700. Log3 $name, 4, "$name: set saveConfig called";
  701. ArduCounter_Write($hash, "e");
  702. } elsif ($attr eq "reset") {
  703. Log3 $name, 4, "$name: set reset called";
  704. DevIo_CloseDev($hash);
  705. ArduCounter_Open($hash);
  706. if (ArduCounter_Write($hash, "r")) {
  707. delete $hash->{Initialized};
  708. return "sent (r)eset command to device - waiting for its setup message";
  709. }
  710. } elsif ($attr eq "devVerbose") {
  711. if ($arg =~ /^\d$/) {
  712. Log3 $name, 4, "$name: set devVerbose $arg called";
  713. ArduCounter_Write($hash, "$arg"."v");
  714. } else {
  715. Log3 $name, 4, "$name: set devVerbose called with illegal value $arg";
  716. }
  717. }
  718. return undef;
  719. }
  720. # GET command
  721. #########################################################################
  722. sub ArduCounter_Get($@)
  723. {
  724. my ( $hash, @a ) = @_;
  725. return "\"set ArduCounter\" needs at least one argument" if ( @a < 2 );
  726. my $name = shift @a;
  727. my $attr = shift @a;
  728. if(!defined($ArduCounter_gets{$attr})) {
  729. my @cList = keys %ArduCounter_gets;
  730. return "Unknown argument $attr, choose one of " . join(" ", @cList);
  731. }
  732. if(!$hash->{FD}) {
  733. Log3 $name, 4, "$name: Get called but device is disconnected";
  734. return ("Get called but device is disconnected", undef);
  735. }
  736. if (IsDisabled($name)) {
  737. Log3 $name, 4, "$name: get called but device is disabled";
  738. return;
  739. }
  740. if ($attr eq "info") {
  741. Log3 $name, 3, "$name: Sending info command to device";
  742. ArduCounter_Write( $hash, "s");
  743. my ($err, $msg) = ArduCounter_ReadAnswer($hash, 'Next report in.*seconds');
  744. return ($err ? $err : $msg);
  745. }
  746. return undef;
  747. }
  748. ######################################
  749. sub ArduCounter_HandleDeviceTime($$$$)
  750. {
  751. my ($hash, $deTi, $deTiW, $now) = @_;
  752. my $name = $hash->{NAME};
  753. my $deviceNowSecs = ($deTi/1000) + ((0xFFFFFFFF / 1000) * $deTiW);
  754. Log3 $name, 5, "$name: Device Time $deviceNowSecs";
  755. if (defined ($hash->{'.DeTOff'}) && $hash->{'.LastDeT'}) {
  756. if ($deviceNowSecs >= $hash->{'.LastDeT'}) {
  757. $hash->{'.Drift2'} = ($now - $hash->{'.DeTOff'}) - $deviceNowSecs;
  758. } else {
  759. $hash->{'.DeTOff'} = $now - $deviceNowSecs;
  760. Log3 $name, 4, "$name: device did reset (now $deviceNowSecs, before $hash->{'.LastDeT'}). New offset is $hash->{'.DeTOff'}";
  761. }
  762. } else {
  763. $hash->{'.DeTOff'} = $now - $deviceNowSecs;
  764. $hash->{'.Drift2'} = 0;
  765. $hash->{'.DriftStart'} = $now;
  766. Log3 $name, 5, "$name: Initialize device clock offset to $hash->{'.DeTOff'}";
  767. }
  768. $hash->{'.LastDeT'} = $deviceNowSecs;
  769. my $drTime = ($now - $hash->{'.DriftStart'});
  770. #Log3 $name, 5, "$name: Device Time $deviceNowSecs" .
  771. #", Offset " . sprintf("%.3f", $hash->{'.DeTOff'}/1000) .
  772. ", Drift " . sprintf("%.3f", $hash->{'.Drift2'}) .
  773. "s in " . sprintf("%.3f", $drTime) . "s" .
  774. ($drTime > 0 ? ", " . sprintf("%.2f", $hash->{'.Drift2'} / $drTime * 100) . "%" : "");
  775. }
  776. ######################################
  777. sub ArduCounter_ParseHello($$$)
  778. {
  779. my ($hash, $line, $now) = @_;
  780. my $name = $hash->{NAME};
  781. if ($line =~ /^ArduCounter V([\d\.]+) on ([^\ ]+ ?[^\ ]*) compiled (.*) Hello(, pins ([0-9\,]+) available)? ?(T([\d]+),([\d]+) B([\d]+),([\d]+))?/) { # setup / hello message
  782. $hash->{VersionFirmware} = ($1 ? $1 : "unknown");
  783. $hash->{Board} = ($2 ? $2 : "unknown");
  784. $hash->{SketchCompile} = ($3 ? $3 : "unknown");
  785. $hash->{allowedPins} = $5 if ($5);
  786. my $mNow = ($7 ? $7 : 0);
  787. my $mNowW = ($8 ? $8 : 0);
  788. my $mBoot = ($9 ? $9 : 0);
  789. my $mBootW = ($10 ? $10 : 0);
  790. if ($hash->{VersionFirmware} < "2.36") {
  791. $hash->{VersionFirmware} .= " - not compatible with this Module version - please flash new sketch";
  792. Log3 $name, 3, "$name: device reported outdated Arducounter Firmware ($hash->{VersionFirmware}) - please update!";
  793. delete $hash->{Initialized};
  794. } else {
  795. Log3 $name, 3, "$name: device sent hello: $line";
  796. $hash->{Initialized} = 1; # now device has finished its boot and reported its version
  797. delete $hash->{runningCfg};
  798. my $cft = AttrVal($name, "ConfigDelay", 1); # wait for device to send cfg before reconf.
  799. RemoveInternalTimer ("cmpCfg:$name");
  800. InternalTimer($now+$cft, "ArduCounter_ConfigureDevice", "cmpCfg:$name", 0);
  801. my $deviceNowSecs = ($mNow/1000) + ((0xFFFFFFFF / 1000) * $mNowW);
  802. my $deviceBootSecs = ($mBoot/1000) + ((0xFFFFFFFF / 1000) * $mBootW);
  803. my $bootTime = $now - ($deviceNowSecs - $deviceBootSecs);
  804. $hash->{deviceBooted} = $bootTime; # for estimation of missed pulses up to now
  805. }
  806. delete $hash->{WaitForHello};
  807. RemoveInternalTimer ("hwait:$name"); # dont wait for hello reply if already sent
  808. RemoveInternalTimer ("sendHello:$name"); # Hello not needed anymore if not sent yet
  809. } else {
  810. Log3 $name, 4, "$name: probably wrong firmware version - cannot parse line $line";
  811. }
  812. }
  813. #########################################################################
  814. sub ArduCounter_HandleCounters($$$$$$$$)
  815. {
  816. my ($hash, $pin, $sequence, $count, $time, $diff, $rDiff, $now) = @_;
  817. my $name = $hash->{NAME};
  818. my $rcname = AttrVal($name, "readingNameCount$pin", "pin$pin"); # internal count reading
  819. my $rlname = AttrVal($name, "readingNameLongCount$pin", "long$pin"); # long count
  820. my $riname = AttrVal($name, "readingNameInterpolatedCount$pin", "interpolatedLong$pin");
  821. my $lName = AttrVal($name, "readingNamePower$pin", AttrVal($name, "readingNameCount$pin", "pin$pin")); # for logging
  822. my $longCount = ReadingsVal($name, $rlname, 0); # alter long count Wert
  823. my $intpCount = ReadingsVal($name, $riname, 0); # alter interpolated count Wert
  824. my $lastCount = ReadingsVal($name, $rcname, 0);
  825. my $lastSeq = ReadingsVal($name, "seq".$pin, 0);
  826. my $lastCountTS = ReadingsTimestamp ($name, $rlname, 0); # last time long count reading was set
  827. my $lastCountTNum = time_str2num($lastCountTS);
  828. my $fBootTim = ($hash->{deviceBooted} ? FmtTime($hash->{deviceBooted}) : "never"); # time device booted
  829. my $fLastCTim = FmtTime($lastCountTNum);
  830. my $pulseGap = $count - $lastCount - $rDiff;
  831. my $seqGap = $sequence - ($lastSeq + 1);
  832. if (!$lastCountTS && !$longCount && !$intpCount) {
  833. # new defined or deletereading done ...
  834. Log3 $name, 3, "$name: pin $pin ($lName) first report, initializing counters to " . ($count - $rDiff);
  835. $longCount = $count - $rDiff;
  836. $intpCount = $count - $rDiff;
  837. }
  838. if ($lastCountTS && $hash->{deviceBooted} && $hash->{deviceBooted} > $lastCountTNum) {
  839. # first report for this pin after a restart
  840. # -> do interpolation for period between last report before boot and boot time. count after boot has to be added later
  841. Log3 $name, 5, "$name: pin $pin ($lName) device restarted at $fBootTim, last reported at $fLastCTim, sequence for pin $pin changed from $lastSeq to $sequence and count from $lastCount to $count";
  842. $lastSeq = 0;
  843. $seqGap = $sequence - 1; # $sequence should be 1 after restart
  844. $pulseGap = $count - $rDiff; #
  845. my $lastInterval = ReadingsVal ($name, "timeDiff$pin", 0);
  846. my $lastCDiff = ReadingsVal ($name, "countDiff$pin", 0);
  847. my $offlTime = sprintf ("%.2f", $hash->{deviceBooted} - $lastCountTNum);
  848. if ($lastCountTS && $lastInterval && ($offlTime > 0) && ($offlTime < 12*60*60)) { # > 0 and < 12h
  849. my $lastRatio = $lastCDiff / $lastInterval;
  850. my $curRatio = $diff / $time;
  851. my $intRatio = 1000 * ($lastRatio + $curRatio) / 2;
  852. my $intrCount = int(($offlTime * $intRatio)+0.5);
  853. Log3 $name, 3, "$name: pin $pin ($lName) interpolating for $offlTime secs until boot, $intrCount estimated pulses (before $lastCDiff in $lastInterval ms, now $diff in $time ms, avg ratio $intRatio p/s)";
  854. Log3 $name, 5, "$name: pin $pin ($lName) adding interpolated $intrCount to interpolated count $intpCount";
  855. $intpCount += $intrCount;
  856. } else {
  857. Log3 $name, 4, "$name: interpolation of missed pulses for pin $pin ($lName) not possible - no valid historic data.";
  858. }
  859. } elsif ($lastCountTS && $seqGap < 0) {
  860. # new sequence number is smaller than last and we have old readings
  861. # and this is not after a reboot of the device
  862. $seqGap += 256; # correct seq gap
  863. Log3 $name, 5, "$name: pin $pin ($lName) sequence wrapped from $lastSeq to $sequence, set seqGap to $seqGap";
  864. }
  865. if ($lastCountTS && $seqGap > 0) {
  866. # probably missed a report. Maybe even the first ones after a reboot (until reconnect)
  867. # take last count, delta to new reported count as missed pulses to correct long counter
  868. my $timeGap = ($now - $time/1000 - $lastCountTNum);
  869. if ($pulseGap > 0) {
  870. $longCount += $pulseGap;
  871. $intpCount += $pulseGap;
  872. Log3 $name, 3, "$name: pin $pin ($lName) missed $seqGap reports in $timeGap seconds. Last reported sequence was $lastSeq, now $sequence. Device count before was $lastCount, now $count with rDiff $rDiff. Adding $pulseGap to long count and intpolated count readings";
  873. } elsif ($pulseGap == 0) {
  874. # outdated sketch?
  875. Log3 $name, 5, "$name: pin $pin ($lName) missed $seqGap sequence numbers in $timeGap seconds. Last reported sequence was $lastSeq, now $sequence. Device count before was $lastCount, now $count with rDiff $rDiff. Nothing is missing - ignore";
  876. } else {
  877. # strange ...
  878. Log3 $name, 3, "$name: Pin $pin ($lName) missed $seqGap reports in $timeGap seconds. " .
  879. "Last reported sequence was $lastSeq, now $sequence. " .
  880. "Device count before was $lastCount, now $count with rDiff $rDiff " .
  881. "but pulseGap is $pulseGap. this is wrong and should not happen";
  882. }
  883. }
  884. Log3 $name, 5, "$name: pin $pin ($lName) adding rDiff $rDiff to long count $longCount and interpolated count $intpCount";
  885. $intpCount += $rDiff;
  886. $longCount += $rDiff;
  887. readingsBulkUpdate($hash, $rcname, $count);
  888. readingsBulkUpdate($hash, $rlname, $longCount);
  889. readingsBulkUpdate($hash, $riname, $intpCount);
  890. readingsBulkUpdate($hash, "seq".$pin, $sequence);
  891. }
  892. #########################################################################
  893. sub ArduCounter_ParseReport($$)
  894. {
  895. my ($hash, $line) = @_;
  896. my $name = $hash->{NAME};
  897. my $now = gettimeofday();
  898. if ($line =~ '^R([\d]+) C([\d]+) D([\d]+) ?[\/R]([\d]+) T([\d]+) N([\d]+),([\d]+) X([\d]+)( S[\d]+)?( A[\d]+)?')
  899. {
  900. # new count is beeing reported
  901. my $pin = $1;
  902. my $count = $2; # internal counter at device
  903. my $diff = $3; # delta during interval
  904. my $rDiff = $4; # real delta including the first pulse after a restart
  905. my $time = $5; # interval
  906. my $deTime = $6;
  907. my $deTiW = $7;
  908. my $reject = $8;
  909. my $seq = ($9 ? substr($9, 2) : "");
  910. my $avgLen = ($10 ? substr($10, 2) : "");
  911. my $factor = AttrVal($name, "readingFactor$pin", AttrVal($name, "factor", 1000));
  912. my $rpname = AttrVal($name, "readingNamePower$pin", "power$pin"); # power reading name
  913. my $lName = AttrVal($name, "readingNamePower$pin", AttrVal($name, "readingNameCount$pin", "pin$pin")); # for logging
  914. my $sTime = $now - $time/1000; # start of observation interval (~first pulse)
  915. my $fSTime = FmtDateTime($sTime); # formatted
  916. my $fSdTim = FmtTime($sTime); # only time formatted for logging
  917. my $fEdTim = FmtTime($now); # end of Interval - only time formatted for logging
  918. ArduCounter_HandleDeviceTime($hash, $deTime, $deTiW, $now);
  919. if (!$time || !$factor) {
  920. Log3 $name, 3, "$name: Pin $pin ($lName) skip line because time or factor is 0: $line";
  921. return;
  922. }
  923. my $power = sprintf ("%.3f", ($time ? $diff/$time/1000*3600*$factor : 0));
  924. Log3 $name, 4, "$name: Pin $pin ($lName) Cnt $count " .
  925. "(diff $diff/$rDiff) in " . sprintf("%.3f", $time/1000) . "s" .
  926. " from $fSdTim until $fEdTim" .
  927. ", seq $seq" .
  928. ((defined($reject) && $reject ne "") ? ", Rej $reject" : "") .
  929. (defined($avgLen) ? ", Avg ${avgLen}ms" : "") .
  930. ", result $power";
  931. if (AttrVal($name, "readingStartTime$pin", 0)) {
  932. readingsBeginUpdate($hash); # special block with potentially manipulates times
  933. # special way to set readings: use time of interval start as reading time
  934. Log3 $name, 5, "$name: readingStartTime$pin specified: setting timestamp to $fSdTim";
  935. my $chIdx = 0;
  936. $hash->{".updateTime"} = $sTime;
  937. $hash->{".updateTimestamp"} = $fSTime;
  938. readingsBulkUpdate($hash, $rpname, $power) if ($time);
  939. $hash->{CHANGETIME}[$chIdx++] = $fSTime; # Intervall start
  940. readingsEndUpdate($hash, 1); # end of special block
  941. readingsBeginUpdate($hash); # start regular update block
  942. } else {
  943. # normal way to set readings
  944. readingsBeginUpdate($hash); # start regular update block
  945. readingsBulkUpdate($hash, $rpname, $power) if ($time);
  946. }
  947. if (defined($reject) && $reject ne "") {
  948. my $rejCount = ReadingsVal($name, "reject$pin", 0); # alter reject count Wert
  949. readingsBulkUpdate($hash, "reject$pin", $reject + $rejCount);
  950. }
  951. readingsBulkUpdate($hash, "timeDiff$pin", $time);
  952. readingsBulkUpdate($hash, "countDiff$pin", $diff);
  953. if (AttrVal($name, "verboseReadings$pin", 0)) {
  954. readingsBulkUpdate($hash, "lastMsg$pin", $line);
  955. }
  956. ArduCounter_HandleCounters($hash, $pin, $seq, $count, $time, $diff, $rDiff, $now);
  957. readingsEndUpdate($hash, 1);
  958. if (!$hash->{Initialized}) { # device has sent count but not Started / hello after reconnect
  959. Log3 $name, 3, "$name: device is still counting";
  960. if (!$hash->{WaitForHello}) { # if hello not already sent, send it now
  961. ArduCounter_AskForHello("direct:$name");
  962. }
  963. RemoveInternalTimer ("sendHello:$name"); # don't send hello again
  964. }
  965. }
  966. }
  967. #########################################################################
  968. sub ArduCounter_Parse($)
  969. {
  970. my ($hash) = @_;
  971. my $name = $hash->{NAME};
  972. my $retStr = "";
  973. my @lines = split /\n/, $hash->{buffer};
  974. my $now = gettimeofday();
  975. foreach my $line (@lines) {
  976. #Log3 $name, 5, "$name: Parse line: $line";
  977. if ($line =~ /^R([\d]+)/)
  978. {
  979. ArduCounter_ParseReport($hash, $line);
  980. } elsif ($line =~ /^H([\d+]) (.+)/) { # pin pulse history as separate line
  981. my $pin = $1;
  982. my $hist = $2;
  983. if (AttrVal($name, "verboseReadings$pin", 0)) {
  984. readingsBeginUpdate($hash);
  985. readingsBulkUpdate($hash, "pinHistory$pin", $hist);
  986. readingsEndUpdate($hash, 1);
  987. }
  988. } elsif ($line =~ /^M Next report in ([\d]+)/) { # end of report tells when next
  989. $retStr .= ($retStr ? "\n" : "") . $line;
  990. Log3 $name, 4, "$name: device: $line";
  991. } elsif ($line =~ /^I(.*)/) { # interval config report after show/hello
  992. $hash->{runningCfg}{I} = $1; # save for later compare
  993. $hash->{runningCfg}{I} =~ s/\s+$//; # remove spaces at end
  994. $retStr .= ($retStr ? "\n" : "") . $line;
  995. } elsif ($line =~ /^P([\d]+) (falling|rising|-) ?(pullup)? ?min ([\d]+)/) { # pin configuration at device
  996. $hash->{runningCfg}{$1} = "$2 $3 $4"; # save for later compare
  997. $retStr .= ($retStr ? "\n" : "") . $line;
  998. Log3 $name, 4, "$name: device sent config for pin $1: $1 $2 min $3";
  999. } elsif ($line =~ /^alive/) { # alive response
  1000. RemoveInternalTimer ("alive:$name");
  1001. $hash->{WaitForAlive} = 0;
  1002. delete $hash->{KeepAliveRetries};
  1003. } elsif ($line =~ /^ArduCounter V([\d\.]+).*(Started|Hello)/) { # setup message
  1004. ArduCounter_ParseHello($hash, $line, $now);
  1005. } elsif ($line =~ /^Status: ArduCounter V([\d\.]+)/) { # response to s(how)
  1006. $retStr .= ($retStr ? "\n" : "") . $line;
  1007. } elsif ($line =~ /connection already busy/) {
  1008. my $now = gettimeofday();
  1009. my $delay = AttrVal($name, "nextOpenDelay", 60);
  1010. Log3 $name, 4, "$name: _Parse: primary tcp connection seems busy - delay next open";
  1011. ArduCounter_Disconnected($hash); # set to disconnected (state), remove timers
  1012. DevIo_CloseDev($hash); # close, remove from readyfnlist so _ready is not called again
  1013. RemoveInternalTimer ("delayedopen:$name");
  1014. InternalTimer($now+$delay, "ArduCounter_DelayedOpen", "delayedopen:$name", 0);
  1015. } elsif ($line =~ /^D (.*)/) { # debug / info Message from device
  1016. $retStr .= ($retStr ? "\n" : "") . $line;
  1017. Log3 $name, 4, "$name: device: $1";
  1018. } elsif ($line =~ /^M (.*)/) { # other Message from device
  1019. $retStr .= ($retStr ? "\n" : "") . $line;
  1020. Log3 $name, 3, "$name: device: $1";
  1021. } elsif ($line =~ /^[\s\n]*$/) {
  1022. # blank line - ignore
  1023. } else {
  1024. Log3 $name, 3, "$name: unparseable message from device: $line";
  1025. }
  1026. }
  1027. $hash->{buffer} = "";
  1028. return $retStr;
  1029. }
  1030. #########################################################################
  1031. # called from the global loop, when the select for hash->{FD} reports data
  1032. sub ArduCounter_Read($)
  1033. {
  1034. my ($hash) = @_;
  1035. my $name = $hash->{NAME};
  1036. my ($pin, $count, $diff, $power, $time, $reject, $msg);
  1037. # read from serial device
  1038. my $buf = DevIo_SimpleRead($hash);
  1039. return if (!defined($buf) );
  1040. $hash->{buffer} .= $buf;
  1041. my $end = chop $buf;
  1042. #Log3 $name, 5, "$name: Read: current buffer content: " . $hash->{buffer};
  1043. # did we already get a full frame?
  1044. return if ($end ne "\n");
  1045. ArduCounter_Parse($hash);
  1046. }
  1047. #####################################
  1048. # Called from get / set to get a direct answer
  1049. # called with logical device hash
  1050. sub ArduCounter_ReadAnswer($$)
  1051. {
  1052. my ($hash, $expect) = @_;
  1053. my $name = $hash->{NAME};
  1054. my $rin = '';
  1055. my $msgBuf = '';
  1056. my $to = AttrVal($name, "timeout", 2);
  1057. my $buf;
  1058. Log3 $name, 5, "$name: ReadAnswer called";
  1059. for(;;) {
  1060. if($^O =~ m/Win/ && $hash->{USBDev}) {
  1061. $hash->{USBDev}->read_const_time($to*1000); # set timeout (ms)
  1062. $buf = $hash->{USBDev}->read(999);
  1063. if(length($buf) == 0) {
  1064. Log3 $name, 3, "$name: Timeout in ReadAnswer";
  1065. return ("Timeout reading answer", undef)
  1066. }
  1067. } else {
  1068. if(!$hash->{FD}) {
  1069. Log3 $name, 3, "$name: Device lost in ReadAnswer";
  1070. return ("Device lost when reading answer", undef);
  1071. }
  1072. vec($rin, $hash->{FD}, 1) = 1; # setze entsprechendes Bit in rin
  1073. my $nfound = select($rin, undef, undef, $to);
  1074. if($nfound < 0) {
  1075. next if ($! == EAGAIN() || $! == EINTR() || $! == 0);
  1076. my $err = $!;
  1077. ArduCounter_Disconnected($hash); # set to disconnected, remove timers, let _ready try to reopen
  1078. Log3 $name, 3, "$name: ReadAnswer error: $err";
  1079. return("ReadAnswer error: $err", undef);
  1080. }
  1081. if($nfound == 0) {
  1082. Log3 $name, 3, "$name: Timeout2 in ReadAnswer";
  1083. return ("Timeout reading answer", undef);
  1084. }
  1085. $buf = DevIo_SimpleRead($hash);
  1086. if(!defined($buf)) {
  1087. Log3 $name, 3, "$name: ReadAnswer got no data";
  1088. return ("No data", undef);
  1089. }
  1090. }
  1091. if($buf) {
  1092. #Log3 $name, 5, "$name: ReadAnswer got: $buf";
  1093. $hash->{buffer} .= $buf;
  1094. }
  1095. my $end = chop $buf;
  1096. #Log3 $name, 5, "$name: Current buffer content: " . $hash->{buffer};
  1097. next if ($end ne "\n");
  1098. $msgBuf .= "\n" if ($msgBuf);
  1099. $msgBuf .= ArduCounter_Parse($hash);
  1100. #Log3 $name, 5, "$name: ReadAnswer msgBuf: " . $msgBuf;
  1101. if ($msgBuf =~ $expect) {
  1102. Log3 $name, 5, "$name: ReadAnswer matched $expect";
  1103. return (undef, $msgBuf);
  1104. }
  1105. }
  1106. return ("no Data", undef);
  1107. }
  1108. 1;
  1109. =pod
  1110. =item device
  1111. =item summary Module for counters based on arduino / ESP8266 board
  1112. =item summary_DE Modul für Strom / Wasserzähler mit Arduino- oder ESP8266
  1113. =begin html
  1114. <a name="ArduCounter"></a>
  1115. <h3>ArduCounter</h3>
  1116. <ul>
  1117. This module implements an Interface to an Arduino or ESP8266 based counter for pulses on any input pin of an Arduino Uno, Nano, Jeenode, NodeMCU, Wemos D1 or similar device. The device connects to Fhem either through USB / serial or via tcp if an ESP board is used.<br>
  1118. The typical use case is an S0-Interface on an energy meter or water meter<br>
  1119. Counters are configured with attributes that define which Arduino pins should count pulses and in which intervals the Arduino board should report the current counts.<br>
  1120. The Arduino sketch that works with this module uses pin change interrupts so it can efficiently count pulses on all available input pins.<br>
  1121. The module creates readings for pulse counts, consumption and optionally also a pulse history with pulse lengths and gaps of the last pulses.
  1122. <br><br>
  1123. <b>Prerequisites</b>
  1124. <ul>
  1125. <br>
  1126. <li>
  1127. This module requires an Arduino Uno, Nano, Jeenode, NodeMCU, Wemos D1 or similar device based on an Atmel 328p or ESP8266 running the ArduCounter sketch provided with this module<br>
  1128. In order to flash an arduino board with the corresponding ArduCounter firmware from within Fhem, avrdude needs to be installed.
  1129. </li>
  1130. </ul>
  1131. <br>
  1132. <a name="ArduCounterdefine"></a>
  1133. <b>Define</b>
  1134. <ul>
  1135. <br>
  1136. <code>define &lt;name&gt; ArduCounter &lt;device&gt;</code><br>
  1137. or<br>
  1138. <code>define &lt;name&gt; ArduCounter &lt;ip:port&gt;</code><br>
  1139. <br>
  1140. &lt;device&gt; specifies the serial port to communicate with the Arduino.<br>
  1141. &lt;ip:port&gt; specifies the ip address and tcp port to communicate with an esp8266 where port is typically 80.<br>
  1142. The name of the serial-device depends on your distribution.
  1143. You can also specify a baudrate for serial connections if the device name contains the @
  1144. character, e.g.: /dev/ttyUSB0@38400<br>
  1145. The default baudrate of the ArduCounter firmware is 38400 since Version 1.4
  1146. <br>
  1147. Example:<br>
  1148. <br>
  1149. <ul><code>define AC ArduCounter /dev/ttyUSB2@38400</code></ul>
  1150. <ul><code>define AC ArduCounter 192.168.1.134:80</code></ul>
  1151. </ul>
  1152. <br>
  1153. <a name="ArduCounterconfiguration"></a>
  1154. <b>Configuration of ArduCounter counters</b><br><br>
  1155. <ul>
  1156. Specify the pins where impulses should be counted e.g. as <code>attr AC pinX falling pullup 30</code> <br>
  1157. The X in pinX can be an Arduino / ESP pin number with or without the letter D e.g. pin4, pinD5, pin6, pinD7 ...<br>
  1158. After the pin you can use the keywords falling or rising to define if a logical one / 5V (rising) or a logical zero / 0V (falling) should be treated as pulse.<br>
  1159. The optional keyword pullup activates the pullup resistor for the given Pin. <br>
  1160. The last argument is also optional but recommended and specifies a minimal pulse length in milliseconds.<br>
  1161. An energy meter with S0 interface is typically connected to GND and an input pin like D4. <br>
  1162. The S0 pulse then pulls the input to 0V.<br>
  1163. Since the minimal pulse lenght of the s0 interface is specified to be 30ms, the typical configuration for an s0 interface is <br>
  1164. <code>attr AC pinX falling pullup 30</code><br>
  1165. Specifying a minimal pulse length is recommended since it filters bouncing of reed contacts or other noise.
  1166. <br><br>
  1167. Example:<br>
  1168. <pre>
  1169. define AC ArduCounter /dev/ttyUSB2
  1170. attr AC factor 1000
  1171. attr AC interval 60 300
  1172. attr AC pinD4 falling pullup 5
  1173. attr AC pinD5 falling pullup 30
  1174. attr AC verboseReadings5
  1175. attr AC pinD6 rising
  1176. </pre>
  1177. This defines three counters connected to the pins D4, D5 and D5. <br>
  1178. D4 and D5 have their pullup resistors activated and the impulse draws the pins to zero. <br>
  1179. For D4 and D5 the arduino measures the time in milliseconds between the falling edge and the rising edge. If this time is longer than the specified 5 or 30 milliseconds
  1180. then the impulse is counted. If the time is shorter then this impulse is regarded as noise and added to a separate reject counter.<br>
  1181. verboseReadings5 causes the module to create additional readings like the pulse history which shows length and gaps between the last pulses.<br>
  1182. For pin D6 the arduino does not check pulse lengths and counts every time when the signal changes from 0 to 1.<br>
  1183. The ArduCounter sketch which must be loaded on the Arduino implements this using pin change interrupts,
  1184. so all avilable input pins can be used, not only the ones that support normal interrupts. <br>
  1185. The module has been tested with 14 inputs of an Arduino Uno counting in parallel and pulses as short as 3 milliseconds.
  1186. </ul>
  1187. <br>
  1188. <a name="ArduCounterset"></a>
  1189. <b>Set-Commands</b><br>
  1190. <ul>
  1191. <li><b>raw</b></li>
  1192. send the value to the board so you can directly talk to the sketch using its commands.<br>
  1193. This is not needed for normal operation but might be useful sometimes for debugging<br>
  1194. <li><b>flash</b></li>
  1195. flashes the ArduCounter firmware ArduCounter.hex from the fhem subdirectory FHEM/firmware
  1196. onto the device. This command needs avrdude to be installed. The attribute flashCommand specidies how avrdude is called. If it is not modifed then the module sets it to avrdude -p atmega328P -c arduino -P [PORT] -D -U flash:w:[HEXFILE] 2>[LOGFILE]<br>
  1197. This setting should work for a standard installation and the placeholders are automatically replaced when
  1198. the command is used. So normally there is no need to modify this attribute.<br>
  1199. Depending on your specific Arduino board however, you might need to insert <code>-b 57600</code> in the flash Command. (e.g. for an Arduino Nano)<br>
  1200. <br>
  1201. ESP boards so far have to be fashed from the Arduino IDE. In a future version flashing over the air sould be supported.
  1202. <li><b>reset</b></li>
  1203. reopens the arduino device and sends a command to it which causes a reinitialize and reset of the counters. Then the module resends the attribute configuration / definition of the pins to the device.
  1204. <li><b>saveConfig</b></li>
  1205. stores the current interval and pin configuration to be stored in the EEPROM of the counter device so it can be retrieved after a reset.
  1206. </ul>
  1207. <br>
  1208. <a name="ArduCounterget"></a>
  1209. <b>Get-Commands</b><br>
  1210. <ul>
  1211. <li><b>info</b></li>
  1212. send a command to the Arduino board to get current counts.<br>
  1213. This is not needed for normal operation but might be useful sometimes for debugging<br>
  1214. </ul>
  1215. <br>
  1216. <a name="ArduCounterattr"></a>
  1217. <b>Attributes</b><br><br>
  1218. <ul>
  1219. <li><a href="#do_not_notify">do_not_notify</a></li>
  1220. <li><a href="#readingFnAttributes">readingFnAttributes</a></li>
  1221. <br>
  1222. <li><b>pin.*</b></li>
  1223. Define a pin of the Arduino or ESP board as input. This attribute expects either
  1224. <code>rising</code>, <code>falling</code> or <code>change</code>, followed by an optional <code>pullup</code> and an optional number as value.<br>
  1225. If a number is specified, the arduino will track rising and falling edges of each impulse and measure the length of a pulse in milliseconds. The number specified here is the minimal length of a pulse and a pause before a pulse. If one is too small, the pulse is not counted but added to a separate reject counter.
  1226. <li><b>interval</b> normal max min mincout</li>
  1227. Defines the parameters that affect the way counting and reporting works.
  1228. This Attribute expects at least two and a maximum of four numbers as value. The first is the normal interval, the second the maximal interval, the third is a minimal interval and the fourth is a minimal pulse count.
  1229. In the usual operation mode (when the normal interval is smaller than the maximum interval),
  1230. the Arduino board just counts and remembers the time between the first impulse and the last impulse for each pin.<br>
  1231. After the normal interval is elapsed the Arduino board reports the count and time for those pins where impulses were encountered.<br>
  1232. This means that even though the normal interval might be 10 seconds, the reported time difference can be
  1233. something different because it observed impulses as starting and ending point.<br>
  1234. The Power (e.g. for energy meters) is then calculated based of the counted impulses and the time between the first and the last impulse. <br>
  1235. For the next interval, the starting time will be the time of the last impulse in the previous
  1236. reporting period and the time difference will be taken up to the last impulse before the reporting
  1237. interval has elapsed.
  1238. <br><br>
  1239. The second, third and fourth numbers (maximum, minimal interval and minimal count) exist for the special case when the pulse frequency is very low and the reporting time is comparatively short.<br>
  1240. For example if the normal interval (first number) is 60 seconds and the device counts only one impulse in 90 seconds, the the calculated power reading will jump up and down and will give ugly numbers.<br>
  1241. By adjusting the other numbers of this attribute this can be avoided.<br>
  1242. In case in the normal interval the observed impulses are encountered in a time difference that is smaller than the third number (minimal interval) or if the number of impulses counted is smaller than the fourth number (minimal count) then the reporting is delayed until the maximum interval has elapsed or the above conditions have changed after another normal interval.<br>
  1243. This way the counter will report a higher number of pulses counted and a larger time difference back to fhem.
  1244. <br><br>
  1245. If this is seems too complicated and you prefer a simple and constant reporting interval, then you can set the normal interval and the mximum interval to the same number. This changes the operation mode of the counter to just count during this normal and maximum interval and report the count. In this case the reported time difference is always the reporting interval and not the measured time between the real impulses.
  1246. <li><b>factor</b></li>
  1247. Define a multiplicator for calculating the power from the impulse count and the time between the first and the last impulse
  1248. <li><b>readingNameCount[0-9]+</b></li>
  1249. Change the name of the counter reading pinX to something more meaningful.
  1250. <li><b>readingNameLongCount[0-9]+</b></li>
  1251. Change the name of the long counter reading longX to something more meaningful.
  1252. <li><b>readingNameInterpolatedCount[0-9]+</b></li>
  1253. Change the name of the interpolated long counter reading InterpolatedlongX to something more meaningful.
  1254. <li><b>readingNamePower[0-9]+</b></li>
  1255. Change the name of the power reading powerX to something more meaningful.
  1256. <li><b>readingFactor[0-9]+</b></li>
  1257. Override the factor attribute for this individual pin.
  1258. <li><b>readingStartTime[0-9]+</b></li>
  1259. Allow the reading time stamp to be set to the beginning of measuring intervals.
  1260. <li><b>verboseReadings[0-9]+</b></li>
  1261. create readings timeDiff, countDiff and lastMsg for each pin <br>
  1262. <li><b>flashCommand</b></li>
  1263. sets the command to call avrdude and flash the onnected arduino with an updated hex file (by default it looks for ArduCounter.hex in the FHEM/firmware subdirectory.<br>
  1264. This attribute contains <code>avrdude -p atmega328P -c arduino -P [PORT] -D -U flash:w:[HEXFILE] 2>[LOGFILE]</code> by default.<br>
  1265. For an Arduino Nano based counter you should add <code>-b 57600</code> e.g. between the -P and -D options.
  1266. <li><b>keepAliveDelay</b></li>
  1267. defines an interval in which the module sends keepalive messages to a counter device that is conected via tcp.<br>
  1268. This attribute is ignored if the device is connected via serial port.<br>
  1269. If the device doesn't reply within a defined timeout then the module closes and tries to reopen the connection.<br>
  1270. The module tells the device when to expect the next keepalive message and the device will also close the tcp connection if it doesn't see a keepalive message within the delay multiplied by 2.5<br>
  1271. The delay defaults to 10 seconds.
  1272. <li><b>keepAliveTimeout</b></li>
  1273. defines the timeout when wainting for a keealive reply (see keepAliveDelay)
  1274. The timeout defaults to 2 seconds.
  1275. <li><b>nextOpenDelay</b></li>
  1276. defines the time that the module waits before retrying to open a disconnected tcp connection. <br>
  1277. This defaults to 60 seconds.
  1278. <li><b>openTimeout</b></li>
  1279. defines the timeout after which tcp open gives up trying to establish a connection to the counter device.
  1280. This timeout defaults to 3 seconds.
  1281. <li><b>silentReconnect</b></li>
  1282. if set to 1, then it will set the loglevel for "disconnected" and "reappeared" messages to 4 instead of 3
  1283. <li><b>disable</b></li>
  1284. if set to 1 then the module closes the connection to a counter device.<br>
  1285. </ul>
  1286. <br>
  1287. <b>Readings / Events</b><br>
  1288. <ul>
  1289. The module creates at least the following readings and events for each defined pin:
  1290. <li><b>pin.*</b></li>
  1291. the current count at this pin
  1292. <li><b>long.*</b></li>
  1293. long count which keeps on counting up after fhem restarts whereas the pin.* count is only a temporary internal count that starts at 0 when the arduino board starts.
  1294. <li><b>interpolatedLong.*</b></li>
  1295. like long.* but when the Arduino restarts the potentially missed pulses are interpolated based on the pulse rate before the restart and after the restart.
  1296. <li><b>reject.*</b></li>
  1297. counts rejected pulses that are shorter than the specified minimal pulse length.
  1298. <li><b>power.*</b></li>
  1299. the current calculated power at this pin
  1300. <li><b>pinHistory.*</b></li>
  1301. shows detailed information of the last pulses. This is only available when a minimal pulse length is specified for this pin. Also the total number of impulses recorded here is limited to 20 for all pins together. The output looks like -36/7:0C, -29/7:1G, -22/8:0C, -14/7:1G, -7/7:0C, 0/7:1G<br>
  1302. The first number is the relative time in milliseconds when the input level changed, followed by the length in milliseconds, the level and the internal action.<br>
  1303. -36/7:0C for example means that 36 milliseconds before the reporting started, the input changed to 0V, stayed there for 7 milliseconds and this was counted.<br>
  1304. <li><b>countDiff.*</b></li>
  1305. delta of the current count to the last reported one. This is used together with timeDiff.* to calculate the power consumption.
  1306. <li><b>timeDiff.*</b></li>
  1307. time difference between the first pulse in the current observation interval and the last one. Used togehter with countDiff to calculate the power consumption.
  1308. <li><b>seq.*</b></li>
  1309. internal sequence number of the last report from the board to fhem.
  1310. </ul>
  1311. <br>
  1312. </ul>
  1313. =end html
  1314. =cut