98_ArduCounter.pm 46 KB

1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556575859606162636465666768697071727374757677787980818283848586878889909192939495969798991001011021031041051061071081091101111121131141151161171181191201211221231241251261271281291301311321331341351361371381391401411421431441451461471481491501511521531541551561571581591601611621631641651661671681691701711721731741751761771781791801811821831841851861871881891901911921931941951961971981992002012022032042052062072082092102112122132142152162172182192202212222232242252262272282292302312322332342352362372382392402412422432442452462472482492502512522532542552562572582592602612622632642652662672682692702712722732742752762772782792802812822832842852862872882892902912922932942952962972982993003013023033043053063073083093103113123133143153163173183193203213223233243253263273283293303313323333343353363373383393403413423433443453463473483493503513523533543553563573583593603613623633643653663673683693703713723733743753763773783793803813823833843853863873883893903913923933943953963973983994004014024034044054064074084094104114124134144154164174184194204214224234244254264274284294304314324334344354364374384394404414424434444454464474484494504514524534544554564574584594604614624634644654664674684694704714724734744754764774784794804814824834844854864874884894904914924934944954964974984995005015025035045055065075085095105115125135145155165175185195205215225235245255265275285295305315325335345355365375385395405415425435445455465475485495505515525535545555565575585595605615625635645655665675685695705715725735745755765775785795805815825835845855865875885895905915925935945955965975985996006016026036046056066076086096106116126136146156166176186196206216226236246256266276286296306316326336346356366376386396406416426436446456466476486496506516526536546556566576586596606616626636646656666676686696706716726736746756766776786796806816826836846856866876886896906916926936946956966976986997007017027037047057067077087097107117127137147157167177187197207217227237247257267277287297307317327337347357367377387397407417427437447457467477487497507517527537547557567577587597607617627637647657667677687697707717727737747757767777787797807817827837847857867877887897907917927937947957967977987998008018028038048058068078088098108118128138148158168178188198208218228238248258268278288298308318328338348358368378388398408418428438448458468478488498508518528538548558568578588598608618628638648658668678688698708718728738748758768778788798808818828838848858868878888898908918928938948958968978988999009019029039049059069079089099109119129139149159169179189199209219229239249259269279289299309319329339349359369379389399409419429439449459469479489499509519529539549559569579589599609619629639649659669679689699709719729739749759769779789799809819829839849859869879889899909919929939949959969979989991000100110021003100410051006100710081009101010111012101310141015101610171018101910201021102210231024102510261027102810291030103110321033103410351036103710381039104010411042104310441045104610471048104910501051105210531054105510561057105810591060106110621063106410651066106710681069107010711072107310741075107610771078107910801081
  1. ############################################################################
  2. # $Id: 98_ArduCounter.pm 15148 2017-09-28 18:10:35Z 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. #
  49. # ideas / todo:
  50. #
  51. package main;
  52. use strict;
  53. use warnings;
  54. use Time::HiRes qw(gettimeofday);
  55. my %ArduCounter_sets = (
  56. "raw" => "",
  57. "reset" => "",
  58. "flash" => ""
  59. );
  60. my %ArduCounter_gets = (
  61. "info" => ""
  62. );
  63. my $ArduCounter_Version = '4.8 - 24.9.2017';
  64. #
  65. # FHEM module intitialisation
  66. # defines the functions to be called from FHEM
  67. #########################################################################
  68. sub ArduCounter_Initialize($)
  69. {
  70. my ($hash) = @_;
  71. require "$attr{global}{modpath}/FHEM/DevIo.pm";
  72. $hash->{ReadFn} = "ArduCounter_Read";
  73. $hash->{ReadyFn} = "ArduCounter_Ready";
  74. $hash->{DefFn} = "ArduCounter_Define";
  75. $hash->{UndefFn} = "ArduCounter_Undef";
  76. $hash->{GetFn} = "ArduCounter_Get";
  77. $hash->{SetFn} = "ArduCounter_Set";
  78. $hash->{AttrFn} = "ArduCounter_Attr";
  79. $hash->{NotifyFn} = "ArduCounter_Notify";
  80. $hash->{AttrList} =
  81. 'pin.* ' .
  82. "interval " .
  83. "factor " .
  84. "readingNameCount[0-9]+ " .
  85. "readingNamePower[0-9]+ " .
  86. "readingNameLongCount[0-9]+ " .
  87. "readingFactor[0-9]+ " .
  88. "readingStartTime[0-9]+ " .
  89. "verboseReadings[0-9]+ " .
  90. "flashCommand " .
  91. "helloSendDelay " .
  92. "helloWaitTime " .
  93. "disable:0,1 " .
  94. "do_not_notify:1,0 " .
  95. $readingFnAttributes;
  96. }
  97. #
  98. # Define command
  99. ##########################################################################
  100. sub ArduCounter_Define($$)
  101. {
  102. my ($hash, $def) = @_;
  103. my @a = split( "[ \t\n]+", $def );
  104. return "wrong syntax: define <name> ArduCounter devicename\@speed"
  105. if ( @a < 3 );
  106. DevIo_CloseDev($hash);
  107. my $name = $a[0];
  108. my $dev = $a[2];
  109. if ($dev !~ /.+@([0-9]+)/) {
  110. $dev .= '@38400';
  111. } else {
  112. Log3 $name, 3, "$name: Warning: connection speed $1 is not the default for the ArduCounter firmware"
  113. if ($1 != 38400);
  114. }
  115. $hash->{buffer} = "";
  116. $hash->{DeviceName} = $dev;
  117. $hash->{VersionModule} = $ArduCounter_Version;
  118. $hash->{NOTIFYDEV} = "global"; # NotifyFn nur aufrufen wenn global events (INITIALIZED)
  119. $hash->{STATE} = "disconnected";
  120. delete $hash->{Initialized};
  121. if(!defined($attr{$name}{'flashCommand'})) {
  122. #$attr{$name}{'flashCommand'} = 'avrdude -p atmega328P -b 57600 -c arduino -P [PORT] -D -U flash:w:[HEXFILE] 2>[LOGFILE]';
  123. $attr{$name}{'flashCommand'} = 'avrdude -p atmega328P -c arduino -P [PORT] -D -U flash:w:[HEXFILE] 2>[LOGFILE]';
  124. }
  125. if ($init_done) {
  126. ArduCounter_Open($hash);
  127. }
  128. return;
  129. }
  130. #
  131. # Send config commands after Board reported it is ready or still counting
  132. ##########################################################################
  133. sub ArduCounter_ConfigureDevice($)
  134. {
  135. my ($hash) = @_;
  136. my $name = $hash->{NAME};
  137. # send attributes to arduino device. Just call ArduCounter_Attr again
  138. #Log3 $name, 3, "$name: sending configuration from attributes to device";
  139. while (my ($attr, $val) = each(%{$attr{$name}})) {
  140. if ($attr =~ "pin|interval") {
  141. Log3 $name, 3, "$name: ConfigureDevice calls Attr with $attr $val";
  142. ArduCounter_Attr("set", $name, $attr, $val);
  143. }
  144. }
  145. }
  146. #
  147. # undefine command when device is deleted
  148. #########################################################################
  149. sub ArduCounter_Undef($$)
  150. {
  151. my ( $hash, $arg ) = @_;
  152. DevIo_CloseDev($hash);
  153. }
  154. ########################################################
  155. # Open Device
  156. sub ArduCounter_Open($)
  157. {
  158. my ($hash) = @_;
  159. my $name = $hash->{NAME};
  160. DevIo_OpenDev($hash, 0, 0);
  161. if ($hash->{FD}) {
  162. my $now = gettimeofday();
  163. my $hdl = AttrVal($name, "helloSendDelay", 3);
  164. # send hello if device doesn't say "Started" withing $hdl seconds
  165. RemoveInternalTimer ("sendHello:$name");
  166. InternalTimer($now+$hdl, "ArduCounter_SendHello", "sendHello:$name", 0);
  167. }
  168. }
  169. ########################################################
  170. # Notify for INITIALIZED or Modified
  171. # -> Open connection to device
  172. sub ArduCounter_Notify($$)
  173. {
  174. my ($hash, $source) = @_;
  175. return if($source->{NAME} ne "global");
  176. my $events = deviceEvents($source, 1);
  177. return if(!$events);
  178. my $name = $hash->{NAME};
  179. # Log3 $name, 5, "$name: Notify called for source $source->{NAME} with events: @{$events}";
  180. return if (!grep(m/^INITIALIZED|REREADCFG|(MODIFIED $name)$/, @{$source->{CHANGED}}));
  181. if (AttrVal($name, "disable", undef)) {
  182. Log3 $name, 4, "$name: device is disabled - don't set timer to send hello";
  183. return;
  184. }
  185. Log3 $name, 5, "$name: Notify called with events: @{$events}, open device and set timer to send hello to device";
  186. ArduCounter_Open($hash);
  187. }
  188. ######################################
  189. # wrapper for DevIo write
  190. sub ArduCounter_Write ($$)
  191. {
  192. my ($hash, $line) = @_;
  193. my $name = $hash->{NAME};
  194. if ($hash->{STATE} eq "disconnected" || !$hash->{FD}) {
  195. Log3 $name, 4, "$name: Write: device is disconnected, dropping line to write";
  196. return 0;
  197. }
  198. if (AttrVal($name, "disable", undef)) {
  199. Log3 $name, 4, "$name: Write called but device is disabled, dropping line to send";
  200. return 0;
  201. }
  202. DevIo_SimpleWrite( $hash, "$line\n", 2);
  203. return 1;
  204. }
  205. #######################################
  206. # Aufruf aus InternalTimer
  207. # send "h" to ask for "Hello" since device didn't say "Started" so fae - maybe it's still counting ...
  208. sub ArduCounter_SendHello($)
  209. {
  210. my $param = shift;
  211. my (undef,$name) = split(':',$param);
  212. my $hash = $defs{$name};
  213. Log3 $name, 3, "$name: sending h(ello) to device to ask for version";
  214. return if (!ArduCounter_Write( $hash, "h"));
  215. my $now = gettimeofday();
  216. my $hwt = AttrVal($name, "helloWaitTime ", 3);
  217. RemoveInternalTimer ("hwait:$name");
  218. InternalTimer($now+$hwt, "ArduCounter_HelloTimeout", "hwait:$name", 0);
  219. $hash->{WaitForHello} = 1;
  220. }
  221. #######################################
  222. # Aufruf aus InternalTimer
  223. sub ArduCounter_HelloTimeout($)
  224. {
  225. my $param = shift;
  226. my (undef,$name) = split(':',$param);
  227. my $hash = $defs{$name};
  228. Log3 $name, 3, "$name: device didn't reply to h(ello). Is the right sketch flashed? Is speed set to 38400?";
  229. delete $hash->{WaitForHello};
  230. }
  231. # Attr command
  232. #########################################################################
  233. sub ArduCounter_Attr(@)
  234. {
  235. my ($cmd,$name,$aName,$aVal) = @_;
  236. # $cmd can be "del" or "set"
  237. # $name is device name
  238. # aName and aVal are Attribute name and value
  239. my $hash = $defs{$name};
  240. my $modHash = $modules{$hash->{TYPE}};
  241. #Log3 $name, 5, "$name: Attr called with @_";
  242. if ($cmd eq "set") {
  243. if ($aName =~ 'pin.*') {
  244. if ($aName !~ 'pin[dD]?(\d+)') {
  245. Log3 $name, 3, "$name: Invalid pin name in attr $name $aName $aVal";
  246. return "Invalid pin name $aName";
  247. }
  248. my $pin = $1;
  249. if ($aVal =~ /^(rising|falling|change) ?(pullup)? ?([0-9]+)?/) {
  250. my $opt = "";
  251. if ($1 eq 'rising') {$opt = "3"}
  252. elsif ($1 eq 'falling') {$opt = "2"}
  253. elsif ($1 eq 'change') {$opt = "1"}
  254. $opt .= ($2 ? ",1" : ",0"); # pullup
  255. $opt .= ($3 ? ",$3" : ""); # min length
  256. if ($hash->{Initialized}) {
  257. ArduCounter_Write( $hash, "${pin},${opt}a");
  258. } else {
  259. Log3 $name, 5, "$name: communication postponed until device is initialized";
  260. }
  261. } else {
  262. Log3 $name, 3, "$name: Invalid value in attr $name $aName $aVal";
  263. return "Invalid Value $aVal";
  264. }
  265. } elsif ($aName eq "interval") {
  266. if ($aVal =~ '^(\d+) (\d+) ?(\d+)? ?(\d+)?$') {
  267. my $min = $1;
  268. my $max = $2;
  269. my $sml = $3;
  270. my $cnt = $4;
  271. if ($min < 1 || $min > 3600 || $max < $min || $max > 3600) {
  272. Log3 $name, 3, "$name: Invalid value in attr $name $aName $aVal";
  273. return "Invalid Value $aVal";
  274. }
  275. if ($hash->{Initialized}) {
  276. $sml = 0 if (!$sml);
  277. $cnt = 0 if (!$cnt);
  278. ArduCounter_Write($hash, "${min},${max},${sml},${cnt}i");
  279. } else {
  280. Log3 $name, 5, "$name: communication postponed until device is initialized";
  281. }
  282. } else {
  283. Log3 $name, 3, "$name: Invalid value in attr $name $aName $aVal";
  284. return "Invalid Value $aVal";
  285. }
  286. } elsif ($aName eq "factor") {
  287. if ($aVal =~ '^(\d+)$') {
  288. } else {
  289. Log3 $name, 3, "$name: Invalid value in attr $name $aName $aVal";
  290. return "Invalid Value $aVal";
  291. }
  292. } elsif ($aName eq 'disable') {
  293. if ($aVal) {
  294. Log3 $name, 5, "$name: disable attribute set";
  295. DevIo_CloseDev($hash);
  296. return;
  297. } else {
  298. Log3 $name, 3, "$name: disable attribute cleared";
  299. ArduCounter_Open($hash) if ($init_done); # only if fhem is initialized
  300. }
  301. }
  302. # handle wild card attributes -> Add to userattr to allow modification in fhemweb
  303. #Log3 $name, 3, "$name: attribute $aName checking ";
  304. if (" $modHash->{AttrList} " !~ m/ ${aName}[ :;]/) {
  305. # nicht direkt in der Liste -> evt. wildcard attr in AttrList
  306. foreach my $la (split " ", $modHash->{AttrList}) {
  307. $la =~ /([^:;]+)(:?.*)/;
  308. my $vgl = $1; # attribute name in list - probably a regex
  309. my $opt = $2; # attribute hint in list
  310. if ($aName =~ $vgl) { # yes - the name in the list now matches as regex
  311. # $aName ist eine Ausprägung eines wildcard attrs
  312. addToDevAttrList($name, "$aName" . $opt); # create userattr with hint to allow changing by click in fhemweb
  313. if ($opt) {
  314. # remove old entries without hint
  315. my $ualist = $attr{$name}{userattr};
  316. $ualist = "" if(!$ualist);
  317. my %uahash;
  318. foreach my $a (split(" ", $ualist)) {
  319. if ($a !~ /^${aName}$/) { # entry in userattr list is attribute without hint
  320. $uahash{$a} = 1;
  321. } else {
  322. Log3 $name, 3, "$name: added hint $opt to attr $a in userattr list";
  323. }
  324. }
  325. $attr{$name}{userattr} = join(" ", sort keys %uahash);
  326. }
  327. }
  328. }
  329. } else {
  330. # exakt in Liste enthalten -> sicherstellen, dass keine +* etc. drin sind.
  331. if ($aName =~ /\|\*\+\[/) {
  332. Log3 $name, 3, "$name: Atribute $aName is not valid. It still contains wildcard symbols";
  333. return "$name: Atribute $aName is not valid. It still contains wildcard symbols";
  334. }
  335. }
  336. } elsif ($cmd eq "del") {
  337. if ($aName =~ 'pin.*') {
  338. if ($aName !~ 'pin([dD]?\d+)') {
  339. Log3 $name, 3, "$name: Invalid pin name in attr $name $aName $aVal";
  340. return "Invalid pin name $aName";
  341. }
  342. my $pin = $1;
  343. if ($hash->{Initialized}) { # did device already report its version?
  344. ArduCounter_Write( $hash, "${pin}d");
  345. } else {
  346. Log3 $name, 5, "$name: pin config can not be deleted since device is not initialized yet";
  347. return "device is not initialized yet";
  348. }
  349. } elsif ($aName eq 'disable') {
  350. Log3 $name, 3, "$name: disable attribute removed";
  351. ArduCounter_Open($hash) if ($hash->{$init_done}); # if fhem is initialized
  352. }
  353. }
  354. return undef;
  355. }
  356. # SET command
  357. #########################################################################
  358. sub ArduCounter_Set($@)
  359. {
  360. my ( $hash, @a ) = @_;
  361. return "\"set ArduCounter\" needs at least one argument" if ( @a < 2 );
  362. # @a is an array with DeviceName, SetName, Rest of Set Line
  363. my $name = shift @a;
  364. my $attr = shift @a;
  365. my $arg = join(" ", @a);
  366. if(!defined($ArduCounter_sets{$attr})) {
  367. my @cList = keys %ArduCounter_sets;
  368. return "Unknown argument $attr, choose one of " . join(" ", @cList);
  369. }
  370. if(!$hash->{FD}) {
  371. Log3 $name, 4, "$name: Set called but device is disconnected";
  372. return ("Set called but device is disconnected", undef);
  373. }
  374. if (AttrVal($name, "disable", undef)) {
  375. Log3 $name, 4, "$name: set called but device is disabled";
  376. return;
  377. }
  378. if ($attr eq "raw") {
  379. ArduCounter_Write($hash, "$arg");
  380. } elsif ($attr eq "reset") {
  381. DevIo_CloseDev($hash);
  382. $hash->{buffer} = "";
  383. DevIo_OpenDev( $hash, 0, 0);
  384. if (ArduCounter_Write($hash, "r")) {
  385. delete $hash->{Initialized};
  386. return "sent (r)eset command to device - waiting for its setup message";
  387. }
  388. } elsif ($attr eq "flash") {
  389. my @args = split(' ', $arg);
  390. my $log = "";
  391. my @deviceName = split('@', $hash->{DeviceName});
  392. my $port = $deviceName[0];
  393. my $firmwareFolder = "./FHEM/firmware/";
  394. my $logFile = AttrVal("global", "logdir", "./log") . "/ArduCounterFlash.log";
  395. my $hexFile = $firmwareFolder . "ArduCounter.hex";
  396. return "The file '$hexFile' does not exist" if(!-e $hexFile);
  397. Log3 $name, 4, "$name: Flashing Aduino at $port with $hexFile. See $logFile for details";
  398. $log .= "flashing device as ArduCounter for $name\n";
  399. $log .= "hex file: $hexFile\n";
  400. $log .= "port: $port\n";
  401. $log .= "log file: $logFile\n";
  402. my $flashCommand = AttrVal($name, "flashCommand", "");
  403. if($flashCommand ne "") {
  404. if (-e $logFile) {
  405. unlink $logFile;
  406. }
  407. DevIo_CloseDev($hash);
  408. readingsSingleUpdate($hash, "state", "disconnected", 1);
  409. $log .= "$name closed\n";
  410. my $avrdude = $flashCommand;
  411. $avrdude =~ s/\Q[PORT]\E/$port/g;
  412. $avrdude =~ s/\Q[HEXFILE]\E/$hexFile/g;
  413. $avrdude =~ s/\Q[LOGFILE]\E/$logFile/g;
  414. $log .= "command: $avrdude\n\n";
  415. `$avrdude`;
  416. local $/=undef;
  417. if (-e $logFile) {
  418. open FILE, $logFile;
  419. my $logText = <FILE>;
  420. close FILE;
  421. $log .= "--- AVRDUDE ---------------------------------------------------------------------------------\n";
  422. $log .= $logText;
  423. $log .= "--- AVRDUDE ---------------------------------------------------------------------------------\n\n";
  424. }
  425. else {
  426. $log .= "WARNING: avrdude created no log file\n\n";
  427. }
  428. DevIo_OpenDev($hash, 0, 0);
  429. $log .= "$name opened\n";
  430. delete $hash->{Initialized};
  431. }
  432. return $log;
  433. }
  434. return undef;
  435. }
  436. # GET command
  437. #########################################################################
  438. sub ArduCounter_Get($@)
  439. {
  440. my ( $hash, @a ) = @_;
  441. return "\"set ArduCounter\" needs at least one argument" if ( @a < 2 );
  442. my $name = shift @a;
  443. my $attr = shift @a;
  444. if(!defined($ArduCounter_gets{$attr})) {
  445. my @cList = keys %ArduCounter_gets;
  446. return "Unknown argument $attr, choose one of " . join(" ", @cList);
  447. }
  448. if(!$hash->{FD}) {
  449. Log3 $name, 4, "$name: Get called but device is disconnected";
  450. return ("Get called but device is disconnected", undef);
  451. }
  452. if (AttrVal($name, "disable", undef)) {
  453. Log3 $name, 4, "$name: get called but device is disabled";
  454. return;
  455. }
  456. if ($attr eq "info") {
  457. Log3 $name, 3, "$name: Sending info command to device";
  458. ArduCounter_Write( $hash, "s");
  459. my ($err, $msg) = ArduCounter_ReadAnswer($hash, 'Next report in [0-9]+ Milliseconds');
  460. # todo: test adding \n to regex to make sure we got the whole respose string
  461. return ($err ? $err : $msg);
  462. }
  463. return undef;
  464. }
  465. ######################################
  466. sub ArduCounter_HandleVersion($$)
  467. {
  468. my ($hash, $line) = @_;
  469. my $name = $hash->{NAME};
  470. if ($line =~ / V([\d\.]+)/) {
  471. my $version = $1;
  472. if ($version < "1.7") {
  473. $version .= " - not compatible with this Module version - please flash new sketch";
  474. Log3 $name, 3, "$name: device reported outdated Arducounter Firmware - please update!";
  475. delete $hash->{Initialized};
  476. } else {
  477. $hash->{Initialized} = 1; # now device is initialized
  478. }
  479. $hash->{VersionFirmware} = $version;
  480. Log3 $name, 4, "$name: device reported firmware $version";
  481. }
  482. }
  483. #########################################################################
  484. sub ArduCounter_Parse($)
  485. {
  486. my ($hash) = @_;
  487. my $name = $hash->{NAME};
  488. my $retStr = "";
  489. my @lines = split /\n/, $hash->{buffer};
  490. my $now = gettimeofday();
  491. foreach my $line (@lines) {
  492. # Log3 $name, 5, "$name: Parse line: $line";
  493. if ($line =~ 'R([\d]+) C([\d]+) D([\d]+) T([\d]+)( N[\d]+)?( X[\d]+)?( F[\d]+)?(L [\d]+)?( A[\d]+)?')
  494. {
  495. # new count is beeing reported
  496. my $pin = $1;
  497. my $count = $2;
  498. my $diff = $3;
  499. my $time = $4;
  500. my $deTime = ($5 ? substr($5, 2) / 1000 : "");
  501. my $reject = ($6 ? substr($6, 2) : "");
  502. my $first = ($7 ? substr($7, 2) : "");
  503. my $last = ($8 ? substr($8, 2) : "");
  504. my $avgLen = ($9 ? substr($9, 2) : "");
  505. my $factor = AttrVal($name, "readingFactor$pin", AttrVal($name, "factor", 1000));
  506. my $rcname = AttrVal($name, "readingNameCount$pin", "pin$pin"); # internal count reading name
  507. my $rlname = AttrVal($name, "readingNameLongCount$pin", "long$pin"); # long count - continues after reset, interpolates
  508. my $rpname = AttrVal($name, "readingNamePower$pin", "power$pin"); # power reading name
  509. my $lName = AttrVal($name, "readingNamePower$pin", AttrVal($name, "readingNameCount$pin", "pin$pin")); # for logging
  510. my $chIdx = 0;
  511. my $sTime = $now - $time/1000; # start of observation interval (~first pulse)
  512. my $fSTime = FmtDateTime($sTime); # formatted
  513. my $fSdTim = FmtTime($sTime); # only time formatted
  514. my $eTime = $now; # now / end of observation interval
  515. my $fETime = FmtDateTime($eTime); # formatted
  516. my $fEdTim = FmtTime($eTime); # only time formatted
  517. if (!$time || !$factor) {
  518. Log3 $name, 3, "$name: Pin $pin ($lName) skip line because time or factor is 0: $line";
  519. next;
  520. }
  521. my $power = sprintf ("%.3f", ($time ? $diff/$time/1000*3600*$factor : 0));
  522. my $longCount;
  523. if (AttrVal($name, "verboseReadings$pin", 0)) {
  524. $longCount = ReadingsVal($name, $rlname, 0); # alter long count Wert im Reading
  525. if (!$hash->{CounterInterpolated}{$pin} && $hash->{CounterResetTime}) {
  526. my $lastCountTime = ReadingsTimestamp ($name, $rlname, 0); # last time this reading was set
  527. my $lastCountTNum = time_str2num($lastCountTime);
  528. my $offlineTime = sprintf ("%.2f", $hash->{CounterResetTime} - $lastCountTNum);
  529. my $lastInterval = ReadingsVal ($name, "timeDiff$pin", 0);
  530. my $lastCDiff = ReadingsVal ($name, "countDiff$pin", 0);
  531. my $lastRatio = $lastCDiff / $lastInterval;
  532. my $curRatio = $diff / $time;
  533. my $intRatio = 1000 * ($lastRatio + $curRatio) / 2;
  534. my $interpolationCount = int($offlineTime * $intRatio) + 1; # add one that gets lost as start of new interval
  535. if ($lastCountTime && $lastInterval && $lastCDiff) {
  536. Log3 $name, 4, "$name: interpolation after counter reset for pin $pin ($lName): offline $offlineTime secs, $interpolationCount estimated pulses (before $lastCDiff in $lastInterval ms, now $diff in $time ms, avg ratio $intRatio p/s)";
  537. Log3 $name, 4, "$name: adding interpolated $interpolationCount and $diff to long count $longCount";
  538. $longCount += $interpolationCount;
  539. } else {
  540. Log3 $name, 4, "$name: interpolation for pin $pin ($lName) not possible - no historic data";
  541. }
  542. $hash->{CounterInterpolated}{$pin} = 1;
  543. }
  544. $longCount += $diff;
  545. }
  546. Log3 $name, 4, "$name: Pin $pin ($lName) count $count longCount $longCount (diff $diff) in " .
  547. sprintf("%.3f", $time/1000) . "s" .
  548. ((defined($reject) && $reject ne "") ? ", reject $reject" : "") .
  549. ($avgLen ? ", Avg Len ${avgLen}ms" : "") .
  550. ", result $power";
  551. Log3 $name, 5, "$name: interval $fSdTim until $fEdTim" .
  552. ($first ? ", First at $first" : "") .
  553. ($last ? ", Last at $last" : "");
  554. readingsBeginUpdate($hash);
  555. if (AttrVal($name, "readingStartTime$pin", 0)) {
  556. Log3 $name, 5, "$name: readingStartTime$pin specified: setting reading timestamp to $fSdTim";
  557. Log3 $name, 5, "$name: set readings $rpname to $power, timeDiff$pin to $time and countDiff$pin to $diff";
  558. $hash->{".updateTime"} = $sTime;
  559. $hash->{".updateTimestamp"} = $fSTime;
  560. readingsBulkUpdate($hash, $rpname, $power) if ($time);
  561. $hash->{CHANGETIME}[$chIdx++] = $fSTime; # Intervall start
  562. $hash->{".updateTime"} = $eTime;
  563. $hash->{".updateTimestamp"} = $fETime;
  564. readingsBulkUpdate($hash, $rcname, $count);
  565. $hash->{CHANGETIME}[$chIdx++] = $fETime;
  566. if (AttrVal($name, "verboseReadings$pin", 0)) {
  567. readingsBulkUpdate($hash, $rlname, $longCount);
  568. $hash->{CHANGETIME}[$chIdx++] = $fETime;
  569. readingsBulkUpdate($hash, "timeDiff$pin", $time);
  570. $hash->{CHANGETIME}[$chIdx++] = $fETime;
  571. readingsBulkUpdate($hash, "countDiff$pin", $diff);
  572. $hash->{CHANGETIME}[$chIdx++] = $fETime;
  573. readingsBulkUpdate($hash, "lastMsg$pin", $line);
  574. $hash->{CHANGETIME}[$chIdx++] = $fETime;
  575. if (defined($reject)) {
  576. readingsBulkUpdate($hash, "reject$pin", $reject);
  577. $hash->{CHANGETIME}[$chIdx++] = $fETime;
  578. }
  579. }
  580. } else {
  581. Log3 $name, 5, "$name: set readings $rpname to $power, timeDiff$pin to $time and countDiff$pin to $diff";
  582. readingsBulkUpdate($hash, $rpname, $power) if ($time);
  583. #$eTime = time_str2num(ReadingsTimestamp ($name, $rpname, 0));
  584. readingsBulkUpdate($hash, $rcname, $count);
  585. if (AttrVal($name, "verboseReadings$pin", 0)) {
  586. readingsBulkUpdate($hash, $rlname, $longCount);
  587. readingsBulkUpdate($hash, "timeDiff$pin", $time);
  588. readingsBulkUpdate($hash, "countDiff$pin", $diff);
  589. readingsBulkUpdate($hash, "lastMsg$pin", $line);
  590. if (defined($reject)) {
  591. readingsBulkUpdate($hash, "reject$pin", $reject);
  592. }
  593. }
  594. }
  595. readingsEndUpdate($hash, 1);
  596. if ($deTime) {
  597. if (defined ($hash->{'.DeTOff'}) && $hash->{'.LastDeT'}) {
  598. if ($deTime >= $hash->{'.LastDeT'}) {
  599. $hash->{'.Drift2'} = ($now - $hash->{'.DeTOff'}) - $deTime;
  600. } else {
  601. $hash->{'.DeTOff'} = $now - $deTime;
  602. Log3 $name, 5, "$name: device clock wrapped (now $deTime, before $hash->{'.LastDeT'}). New offset is $hash->{'.DeTOff'}";
  603. }
  604. } else {
  605. $hash->{'.DeTOff'} = $now - $deTime;
  606. $hash->{'.Drift2'} = 0;
  607. $hash->{'.DriftStart'} = $now;
  608. Log3 $name, 5, "$name: Initialize clock offset to $hash->{'.DeTOff'}";
  609. }
  610. $hash->{'.LastDeT'} = $deTime;
  611. }
  612. my $drTime = ($now - $hash->{'.DriftStart'});
  613. Log3 $name, 5, "$name: Device Time $deTime" .
  614. #", Offset " . sprintf("%.3f", $hash->{'.DeTOff'}/1000) .
  615. ", Drift " . sprintf("%.3f", $hash->{'.Drift2'}) .
  616. "s in " . sprintf("%.3f", $drTime) . "s" .
  617. ($drTime > 0 ? ", " . sprintf("%.2f", $hash->{'.Drift2'} / $drTime * 100) . "%" : "");
  618. if (!$hash->{Initialized}) { # device has not sent Started / hello yet
  619. Log3 $name, 3, "$name: device is still counting";
  620. if (!$hash->{WaitForHello}) { # if hello has not already been sent, send it now
  621. ArduCounter_SendHello("direct:$name");
  622. }
  623. RemoveInternalTimer ("sendHello:$name"); # don't send hello again
  624. }
  625. } elsif ($line =~ /ArduCounter V([\d\.]+).?Hello/) { # response to h(ello)
  626. Log3 $name, 3, "$name: device replied to hello, V$1";
  627. ArduCounter_HandleVersion($hash, $line);
  628. if ($hash->{Initialized}) {
  629. ArduCounter_ConfigureDevice($hash) # send pin configuration
  630. }
  631. delete $hash->{WaitForHello};
  632. RemoveInternalTimer ("hwait:$name");
  633. RemoveInternalTimer ("sendHello:$name");
  634. } elsif ($line =~ /Status: ArduCounter V([\d\.]+)/) { # response to s(how)
  635. $retStr .= "\n" if ($retStr);
  636. $retStr .= $line;
  637. ArduCounter_HandleVersion($hash, $line);
  638. delete $hash->{WaitForHello};
  639. RemoveInternalTimer ("hwait:$name"); # dont wait for hello reply if already sent
  640. RemoveInternalTimer ("sendHello:$name"); # Hello not needed anymore if not sent yet
  641. } elsif ($line =~ /ArduCounter V([\d\.]+).?Started/) { # setup message
  642. Log3 $name, 3, "$name: device sent setup message, V$1";
  643. ArduCounter_HandleVersion($hash, $line);
  644. if ($hash->{Initialized}) {
  645. ArduCounter_ConfigureDevice($hash) # send pin configuration
  646. }
  647. delete $hash->{WaitForHello};
  648. RemoveInternalTimer ("hwait:$name"); # dont wait for hello reply if already sent
  649. RemoveInternalTimer ("sendHello:$name"); # Hello not needed anymore if not sent yet
  650. $hash->{CounterResetTime} = $now;
  651. delete $hash->{CounterInterpolated};
  652. } elsif ($line =~ /V([\d\.]+).?Setup done/) { # old setup message
  653. Log3 $name, 3, "$name: device is flashed with an old and incompatible firmware : $1";
  654. Log3 $name, 3, "$name: please use set $name flash to update";
  655. ArduCounter_HandleVersion($hash, $line);
  656. } elsif ($line =~ /^M (.*)/) {
  657. $retStr .= "\n" if ($retStr);
  658. $retStr .= $1;
  659. Log3 $name, 3, "$name: device: $1";
  660. } elsif ($line =~ /^[\s\n]*$/) {
  661. # blank line - ignore
  662. } else {
  663. Log3 $name, 3, "$name: unparseable message from device: $line";
  664. }
  665. }
  666. $hash->{buffer} = "";
  667. return $retStr;
  668. }
  669. #########################################################################
  670. # called from the global loop, when the select for hash->{FD} reports data
  671. sub ArduCounter_Read($)
  672. {
  673. my ($hash) = @_;
  674. my $name = $hash->{NAME};
  675. my ($pin, $count, $diff, $power, $time, $reject, $msg);
  676. # read from serial device
  677. my $buf = DevIo_SimpleRead($hash);
  678. return if (!defined($buf) );
  679. $hash->{buffer} .= $buf;
  680. my $end = chop $buf;
  681. #Log3 $name, 5, "$name: Read: current buffer content: " . $hash->{buffer};
  682. # did we already get a full frame?
  683. return if ($end ne "\n");
  684. ArduCounter_Parse($hash);
  685. }
  686. #####################################
  687. # Called from get / set to get a direct answer
  688. # called with logical device hash
  689. sub
  690. ArduCounter_ReadAnswer($$)
  691. {
  692. my ($hash, $expect) = @_;
  693. my $name = $hash->{NAME};
  694. my $rin = '';
  695. my $msgBuf = '';
  696. my $to = AttrVal($name, "timeout", 2);
  697. my $buf;
  698. Log3 $name, 5, "$name: ReadAnswer called";
  699. for(;;) {
  700. if($^O =~ m/Win/ && $hash->{USBDev}) {
  701. $hash->{USBDev}->read_const_time($to*1000); # set timeout (ms)
  702. $buf = $hash->{USBDev}->read(999);
  703. if(length($buf) == 0) {
  704. Log3 $name, 3, "$name: Timeout in ReadAnswer";
  705. return ("Timeout reading answer", undef)
  706. }
  707. } else {
  708. if(!$hash->{FD}) {
  709. Log3 $name, 3, "$name: Device lost in ReadAnswer";
  710. return ("Device lost when reading answer", undef);
  711. }
  712. vec($rin, $hash->{FD}, 1) = 1; # setze entsprechendes Bit in rin
  713. my $nfound = select($rin, undef, undef, $to);
  714. if($nfound < 0) {
  715. next if ($! == EAGAIN() || $! == EINTR() || $! == 0);
  716. my $err = $!;
  717. DevIo_Disconnected($hash);
  718. Log3 $name, 3, "$name: ReadAnswer error: $err";
  719. return("ReadAnswer error: $err", undef);
  720. }
  721. if($nfound == 0) {
  722. Log3 $name, 3, "$name: Timeout2 in ReadAnswer";
  723. return ("Timeout reading answer", undef);
  724. }
  725. $buf = DevIo_SimpleRead($hash);
  726. if(!defined($buf)) {
  727. Log3 $name, 3, "$name: ReadAnswer got no data";
  728. return ("No data", undef);
  729. }
  730. }
  731. if($buf) {
  732. #Log3 $name, 5, "$name: ReadAnswer got: $buf";
  733. $hash->{buffer} .= $buf;
  734. }
  735. my $end = chop $buf;
  736. #Log3 $name, 5, "$name: Current buffer content: " . $hash->{buffer};
  737. next if ($end ne "\n");
  738. $msgBuf .= "\n" if ($msgBuf);
  739. $msgBuf .= ArduCounter_Parse($hash);
  740. #Log3 $name, 5, "$name: ReadAnswer msgBuf: " . $msgBuf;
  741. if ($msgBuf =~ $expect) {
  742. Log3 $name, 5, "$name: ReadAnswer matched $expect";
  743. return (undef, $msgBuf);
  744. }
  745. }
  746. return ("no Data", undef);
  747. }
  748. #
  749. # copied from other FHEM modules
  750. #########################################################################
  751. sub ArduCounter_Ready($)
  752. {
  753. my ($hash) = @_;
  754. my $name = $hash->{NAME};
  755. if (AttrVal($name, "disable", undef)) {
  756. return;
  757. }
  758. # try to reopen if state is disconnected
  759. if ( $hash->{STATE} eq "disconnected" ) {
  760. #Log3 $name, 3, "$name: ReadyFN tries to open"; # debug
  761. delete $hash->{Initialized};
  762. DevIo_OpenDev( $hash, 1, undef );
  763. if ($hash->{FD}) {
  764. Log3 $name, 3, "$name: device maybe not initialized yet, set timer to send h(ello";
  765. my $now = gettimeofday();
  766. my $hdl = AttrVal($name, "helloSendDelay", 3);
  767. RemoveInternalTimer ("sendHello:$name");
  768. InternalTimer($now+$hdl, "ArduCounter_SendHello", "sendHello:$name", 0);
  769. }
  770. return;
  771. }
  772. # This is relevant for windows/USB only
  773. my $po = $hash->{USBDev};
  774. if ($po) {
  775. my ( $BlockingFlags, $InBytes, $OutBytes, $ErrorFlags ) = $po->status;
  776. return ( $InBytes > 0 );
  777. }
  778. }
  779. 1;
  780. =pod
  781. =item device
  782. =item summary Module for consumption counter based on an arduino with the ArduCounter sketch
  783. =item summary_DE Modul für Strom / Wasserzähler auf Arduino-Basis mit ArduCounter Sketch
  784. =begin html
  785. <a name="ArduCounter"></a>
  786. <h3>ArduCounter</h3>
  787. <ul>
  788. This module implements an Interface to an Arduino based counter for pulses on any input pin of an Arduino Uno, Nano or similar device like a Jeenode. The typical use case is an S0-Interface on an energy meter<br>
  789. 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>
  790. The Arduino sketch that works with this module uses pin change interrupts so it can efficiently count pulses on all available input pins.
  791. <br><br>
  792. <b>Prerequisites</b>
  793. <ul>
  794. <br>
  795. <li>
  796. This module requires an Arduino uno, nano, Jeenode or similar device running the ArduCounter sketch provided with this module<br>
  797. In order to flash an arduino board with the corresponding ArduCounter firmware, avrdude needs to be installed.
  798. </li>
  799. </ul>
  800. <br>
  801. <a name="ArduCounterdefine"></a>
  802. <b>Define</b>
  803. <ul>
  804. <br>
  805. <code>define &lt;name&gt; ArduCounter &lt;device&gt;</code>
  806. <br>
  807. &lt;device&gt; specifies the serial port to communicate with the Arduino.<br>
  808. The name of the serial-device depends on your distribution.
  809. You can also specify a baudrate if the device name contains the @
  810. character, e.g.: /dev/ttyUSB0@38400<br>
  811. The default baudrate of the ArduCounter firmware is 38400 since Version 1.4
  812. <br>
  813. Example:<br>
  814. <br>
  815. <ul><code>define AC ArduCounter /dev/ttyUSB2@38400</code></ul>
  816. </ul>
  817. <br>
  818. <a name="ArduCounterconfiguration"></a>
  819. <b>Configuration of ArduCounter counters</b><br><br>
  820. <ul>
  821. Specify the pins where impulses should be counted e.g. as <code>attr AC pinX falling pullup 30</code> <br>
  822. The X in pinX can be an Arduino pin number with or without the letter D e.g. pin4, pinD5, pin6, pinD7 ...<br>
  823. After the pin you can define if rising or falling edges of the signals should be counted. The optional keyword pullup activates the pullup resistor for the given Arduino Pin.
  824. The last argument is also optional and specifies a minimal pulse length in milliseconds. In this case the first argument (e.g. falling) means that an impulse starts with a falling edge from 1 to 0 and ends when the signal changes back from 0 to 1.
  825. <br><br>
  826. Example:<br>
  827. <pre>
  828. define AC ArduCounter /dev/ttyUSB2
  829. attr AC factor 1000
  830. attr AC interval 60 300
  831. attr AC pinD4 falling pullup
  832. attr AC pinD5 falling pullup 30
  833. attr AC pinD6 rising
  834. </pre>
  835. this defines three counters connected to the pins D4, D5 and D5. <br>
  836. D4 and D5 have their pullup resistors activated and the impulse draws the pins to zero. <br>
  837. For D4 every falling edge of the signal (when the input changes from 1 to 0) is counted.<br>
  838. For D5 the arduino measures the time in milliseconds between the falling edge and the rising edge. If this time is longer than the specified 30 milliseconds 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>
  839. For pin D6 the ardiono counts every time when the signal changes from 0 to 1. <br>
  840. The ArduCounter sketch which must be loaded on the Arduino implements this using pin change interrupts,
  841. so all avilable input pins can be used, not only the ones that support normal interrupts.
  842. </ul>
  843. <br>
  844. <a name="ArduCounterset"></a>
  845. <b>Set-Commands</b><br>
  846. <ul>
  847. <li><b>raw</b></li>
  848. send the value to the Arduino board so you can directly talk to the sketch using its commands.<br>
  849. This is not needed for normal operation but might be useful sometimes for debugging<br>
  850. <li><b>flash</b></li>
  851. flashes the ArduCounter firmware ArduCounter.hex from the fhem subdirectory FHEM/firmware
  852. 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>
  853. This setting should work for a standard installation and the placeholders are automatically replaced when
  854. the command is used. So normally there is no need to modify this attribute.<br>
  855. Depending on your specific Arduino board however, you might need to insert <code>-b 57600</code> in the flash Command.<br>
  856. <br>
  857. <li><b>reset</b></li>
  858. 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.
  859. </ul>
  860. <br>
  861. <a name="ArduCounterget"></a>
  862. <b>Get-Commands</b><br>
  863. <ul>
  864. <li><b>info</b></li>
  865. send a command to the Arduino board to get current counts.<br>
  866. This is not needed for normal operation but might be useful sometimes for debugging<br>
  867. </ul>
  868. <br>
  869. <a name="ArduCounterattr"></a>
  870. <b>Attributes</b><br><br>
  871. <ul>
  872. <li><a href="#do_not_notify">do_not_notify</a></li>
  873. <li><a href="#readingFnAttributes">readingFnAttributes</a></li>
  874. <br>
  875. <li><b>pin.*</b></li>
  876. Define a pin of the Arduino board as input. This attribute expects either
  877. <code>rising</code>, <code>falling</code> or <code>change</code>, followed by an optional <code>pullup</code> and an optional number as value.<br>
  878. 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.
  879. <li><b>interval</b> normal max min mincout</li>
  880. Defines the parameters that affect the way counting and reporting works.
  881. 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.
  882. In the usual operation mode (when the normal interval is smaller than the maximum interval),
  883. the Arduino board just counts and remembers the time between the first impulse and the last impulse for each pin.<br>
  884. After the normal interval is elapsed the Arduino board reports the count and time for those pins where impulses were encountered.<br>
  885. This means that even though the normal interval might be 10 seconds, the reported time difference can be
  886. something different because it observed impulses as starting and ending point.<br>
  887. The Power (e.g. for energy meters) is the calculated based of the counted impulses and the time between the first and the last impulse. <br>
  888. For the next interval, the starting time will be the time of the last impulse in the previous
  889. reporting period and the time difference will be taken up to the last impulse before the reporting
  890. interval has elapsed.
  891. <br><br>
  892. 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>
  893. 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.
  894. By adjusting the other numbers of this attribute this can be avoided.<br>
  895. 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
  896. 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>
  897. This way the counter will report a higher number of pulses counted and a larger time difference back to fhem.
  898. <br><br>
  899. 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.
  900. <li><b>factor</b></li>
  901. Define a multiplicator for calculating the power from the impulse count and the time between the first and the last impulse
  902. <li><b>readingNameCount[0-9]+</b></li>
  903. Change the name of the counter reading pinX to something more meaningful.
  904. <li><b>readingNameLongCount[0-9]+</b></li>
  905. Change the name of the long counter reading longX (only created when verboseReadingsX is set to 1) to something more meaningful.
  906. <li><b>readingNamePower[0-9]+</b></li>
  907. Change the name of the power reading powerX to something more meaningful.
  908. <li><b>readingFactor[0-9]+</b></li>
  909. Override the factor attribute for this individual pin.
  910. <li><b>readingStartTime[0-9]+</b></li>
  911. Allow the reading time stamp to be set to the beginning of measuring intervals
  912. <li><b>verboseReadings[0-9]+</b></li>
  913. create readings timeDiff, countDiff and lastMsg for each pin
  914. </ul>
  915. <br>
  916. <b>Readings / Events</b><br>
  917. <ul>
  918. The module creates at least the following readings and events for each defined pin:
  919. <li><b>pin.*</b></li>
  920. the current count at this pin
  921. <li><b>power.*</b></li>
  922. the current calculated power at this pin
  923. Most reading names can be customized with attribues and many more readings can be generated by setting the attribute verboseReadings[0-9]+ to 1.<br>
  924. This includes the "long count" reading 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.
  925. </ul>
  926. <br>
  927. </ul>
  928. =end html
  929. =cut