00_OWX_ASYNC.pm 48 KB

1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556575859606162636465666768697071727374757677787980818283848586878889909192939495969798991001011021031041051061071081091101111121131141151161171181191201211221231241251261271281291301311321331341351361371381391401411421431441451461471481491501511521531541551561571581591601611621631641651661671681691701711721731741751761771781791801811821831841851861871881891901911921931941951961971981992002012022032042052062072082092102112122132142152162172182192202212222232242252262272282292302312322332342352362372382392402412422432442452462472482492502512522532542552562572582592602612622632642652662672682692702712722732742752762772782792802812822832842852862872882892902912922932942952962972982993003013023033043053063073083093103113123133143153163173183193203213223233243253263273283293303313323333343353363373383393403413423433443453463473483493503513523533543553563573583593603613623633643653663673683693703713723733743753763773783793803813823833843853863873883893903913923933943953963973983994004014024034044054064074084094104114124134144154164174184194204214224234244254264274284294304314324334344354364374384394404414424434444454464474484494504514524534544554564574584594604614624634644654664674684694704714724734744754764774784794804814824834844854864874884894904914924934944954964974984995005015025035045055065075085095105115125135145155165175185195205215225235245255265275285295305315325335345355365375385395405415425435445455465475485495505515525535545555565575585595605615625635645655665675685695705715725735745755765775785795805815825835845855865875885895905915925935945955965975985996006016026036046056066076086096106116126136146156166176186196206216226236246256266276286296306316326336346356366376386396406416426436446456466476486496506516526536546556566576586596606616626636646656666676686696706716726736746756766776786796806816826836846856866876886896906916926936946956966976986997007017027037047057067077087097107117127137147157167177187197207217227237247257267277287297307317327337347357367377387397407417427437447457467477487497507517527537547557567577587597607617627637647657667677687697707717727737747757767777787797807817827837847857867877887897907917927937947957967977987998008018028038048058068078088098108118128138148158168178188198208218228238248258268278288298308318328338348358368378388398408418428438448458468478488498508518528538548558568578588598608618628638648658668678688698708718728738748758768778788798808818828838848858868878888898908918928938948958968978988999009019029039049059069079089099109119129139149159169179189199209219229239249259269279289299309319329339349359369379389399409419429439449459469479489499509519529539549559569579589599609619629639649659669679689699709719729739749759769779789799809819829839849859869879889899909919929939949959969979989991000100110021003100410051006100710081009101010111012101310141015101610171018101910201021102210231024102510261027102810291030103110321033103410351036103710381039104010411042104310441045104610471048104910501051105210531054105510561057105810591060106110621063106410651066106710681069107010711072107310741075107610771078107910801081108210831084108510861087108810891090109110921093109410951096109710981099110011011102110311041105110611071108110911101111111211131114111511161117111811191120112111221123112411251126112711281129113011311132113311341135113611371138113911401141114211431144114511461147114811491150115111521153115411551156115711581159116011611162116311641165116611671168116911701171117211731174117511761177117811791180118111821183118411851186118711881189119011911192119311941195119611971198119912001201120212031204120512061207120812091210121112121213121412151216121712181219122012211222122312241225122612271228122912301231123212331234123512361237123812391240124112421243124412451246124712481249125012511252125312541255125612571258125912601261126212631264126512661267126812691270127112721273127412751276127712781279128012811282128312841285
  1. ########################################################################################
  2. #
  3. # OWX_ASYNC.pm
  4. #
  5. # FHEM module to commmunicate with 1-Wire bus devices
  6. # * via an active DS2480 bus master interface attached to a serial port, USB or Ethernet<->Serial interface
  7. # * via a passive DS9097 bus master interface attached to a serial port
  8. # * via an Arduino running ConfigurableFirmata attached to USB
  9. # * via an Arduino running ConfigurableFirmata connecting to FHEM via Ethernet
  10. #
  11. # Norbert Truchsess
  12. # based on 00_OWX.pm written by Prof. Dr. Peter A. Henning
  13. #
  14. # $Id: 00_OWX_ASYNC.pm 6378 2014-08-07 22:01:18Z ntruchsess $
  15. #
  16. ########################################################################################
  17. #
  18. # define <name> OWX_ASYNC <serial-device> for serial or USB interfaces (both DS2480 and DS9097)
  19. # define <name> OWX_ASYNC <ip:port> for DS2480 over Ethernet
  20. # define <name> OWX_ASYNC <arduino-pin> for a Arduino/Firmata (10_FRM.pm) interface
  21. #
  22. # where <name> may be replaced by any name string
  23. # <serial-device> is a serial (USB) device
  24. # <arduino-pin> is an Arduino pin
  25. #
  26. # get <name> alarms => find alarmed 1-Wire devices (not with CUNO)
  27. # get <name> devices => find all 1-Wire devices
  28. # get <name> version => OWX_ASYNC version number
  29. #
  30. # set <name> interval <seconds> => set period for temperature conversion and alarm testing
  31. # set <name> followAlarms on/off => determine whether an alarm is followed by a search for
  32. # alarmed devices
  33. #
  34. # attr <name> buspower real/parasitic => for devices that steal power from data-line
  35. #
  36. # attr <name> dokick 0/1 => 1 if the interface regularly kicks thermometers on the
  37. # bus to do a temperature conversion,
  38. # and to make an alarm check
  39. # 0 if not
  40. #
  41. # attr <name> interval <seconds> => set period for temperature conversion and alarm testing
  42. #
  43. # attr <name> IODev <frm-device> => required when there's more than a single frm-device defined.
  44. #
  45. ########################################################################################
  46. #
  47. # This programm is free software; you can redistribute it and/or modify
  48. # it under the terms of the GNU General Public License as published by
  49. # the Free Software Foundation; either version 2 of the License, or
  50. # (at your option) any later version.
  51. #
  52. # The GNU General Public License can be found at
  53. # http://www.gnu.org/copyleft/gpl.html.
  54. # A copy is found in the textfile GPL.txt and important notices to the license
  55. # from the author is found in LICENSE.txt distributed with these scripts.
  56. #
  57. # This script is distributed in the hope that it will be useful,
  58. # but WITHOUT ANY WARRANTY; without even the implied warranty of
  59. # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
  60. # GNU General Public License for more details.
  61. #
  62. ########################################################################################
  63. package main;
  64. use strict;
  65. use warnings;
  66. use GPUtils qw(:all);
  67. #add FHEM/lib to @INC if it's not allready included. Should rather be in fhem.pl than here though...
  68. BEGIN {
  69. if (!grep(/FHEM\/lib$/,@INC)) {
  70. foreach my $inc (grep(/FHEM$/,@INC)) {
  71. push @INC,$inc."/lib";
  72. };
  73. };
  74. };
  75. use ProtoThreads;
  76. no warnings 'deprecated';
  77. #-- unfortunately some things OS-dependent
  78. my $SER_regexp;
  79. if( $^O =~ /Win/ ) {
  80. require Win32::SerialPort;
  81. $SER_regexp= "com";
  82. } else {
  83. require Device::SerialPort;
  84. $SER_regexp= "/dev/";
  85. }
  86. use Time::HiRes qw( gettimeofday tv_interval );
  87. sub Log3($$$);
  88. use vars qw{%owg_family %gets %sets $owx_async_version $owx_async_debug};
  89. # 1-Wire devices
  90. # http://owfs.sourceforge.net/family.html
  91. %owg_family = (
  92. "01" => ["DS2401/DS1990A","OWID DS2401"],
  93. "05" => ["DS2405","OWID 05"],
  94. "10" => ["DS18S20/DS1920","OWTHERM DS1820"],
  95. "12" => ["DS2406/DS2507","OWSWITCH DS2406"],
  96. "1B" => ["DS2436","OWID 1B"],
  97. "1D" => ["DS2423","OWCOUNT DS2423"],
  98. "20" => ["DS2450","OWAD DS2450"],
  99. "22" => ["DS1822","OWTHERM DS1822"],
  100. "24" => ["DS2415/DS1904","OWID 24"],
  101. "26" => ["DS2438","OWMULTI DS2438"],
  102. "27" => ["DS2417","OWID 27"],
  103. "28" => ["DS18B20","OWTHERM DS18B20"],
  104. "29" => ["DS2408","OWSWITCH DS2408"],
  105. "3A" => ["DS2413","OWSWITCH DS2413"],
  106. "3B" => ["DS1825","OWID 3B"],
  107. "81" => ["DS1420","OWID 81"],
  108. "FF" => ["LCD","OWLCD"]
  109. );
  110. #-- These we may get on request
  111. %gets = (
  112. "alarms" => "A",
  113. "devices" => "D",
  114. "version" => "V"
  115. );
  116. #-- These occur in a pulldown menu as settable values for the bus master
  117. %sets = (
  118. "interval" => "T",
  119. "followAlarms" => "F"
  120. );
  121. #-- These are attributes
  122. my %attrs = (
  123. );
  124. #-- some globals needed for the 1-Wire module
  125. $owx_async_version=5.14;
  126. #-- Debugging 0,1,2,3
  127. $owx_async_debug=0;
  128. ########################################################################################
  129. #
  130. # The following subroutines are independent of the bus interface
  131. #
  132. ########################################################################################
  133. #
  134. # OWX_ASYNC_Initialize
  135. #
  136. # Parameter hash = hash of device addressed
  137. #
  138. ########################################################################################
  139. sub OWX_ASYNC_Initialize ($) {
  140. my ($hash) = @_;
  141. #-- Provider
  142. $hash->{Clients} = ":OWAD:OWCOUNT:OWID:OWLCD:OWMULTI:OWSWITCH:OWTHERM:";
  143. #-- Normal Devices
  144. $hash->{DefFn} = "OWX_ASYNC_Define";
  145. $hash->{UndefFn} = "OWX_ASYNC_Undef";
  146. $hash->{GetFn} = "OWX_ASYNC_Get";
  147. $hash->{SetFn} = "OWX_ASYNC_Set";
  148. $hash->{AttrFn} = "OWX_ASYNC_Attr";
  149. $hash->{NotifyFn} = "OWX_ASYNC_Notify";
  150. $hash->{ReadFn} = "OWX_ASYNC_Read";
  151. $hash->{ReadyFn} = "OWX_ASYNC_Ready";
  152. $hash->{InitFn} = "OWX_ASYNC_Init";
  153. $hash->{AttrList} = "dokick:0,1 interval buspower:real,parasitic IODev timeout maxtimeouts";
  154. main::LoadModule("OWX");
  155. }
  156. ########################################################################################
  157. #
  158. # OWX_ASYNC_Define - Implements DefFn function
  159. #
  160. # Parameter hash = hash of device addressed, def = definition string
  161. #
  162. ########################################################################################
  163. sub OWX_ASYNC_Define ($$) {
  164. my ($hash, $def) = @_;
  165. my @a = split("[ \t][ \t]*", $def);
  166. #-- check syntax
  167. return "OWX: Syntax error - must be define <name> OWX <serial-device>|<arduino-pin>" if(int(@a) < 3);
  168. Log3 ($hash->{NAME},2,"OWX: Warning - Some parameter(s) ignored, must be define <name> OWX <serial-device>|<arduino-pin>") if( int(@a)>3 );
  169. my $dev = $a[2];
  170. $hash->{NOTIFYDEV} = "global";
  171. #-- Dummy 1-Wire ROM identifier, empty device lists
  172. $hash->{ROM_ID} = "FF";
  173. $hash->{DEVS} = [];
  174. $hash->{ALARMDEVS} = [];
  175. $hash->{tasks} = {};
  176. my $owx;
  177. #-- First step - different methods
  178. #-- check if we have a serial device attached
  179. if ( $dev =~ m|$SER_regexp|i or $dev =~ m/^(.+):([0-9]+)$/ ){
  180. require "$main::attr{global}{modpath}/FHEM/OWX_SER.pm";
  181. $owx = OWX_SER->new();
  182. #-- check if we have a COC/CUNO interface attached
  183. }elsif( (defined $main::defs{$dev} && (defined( $main::defs{$dev}->{VERSION} ) ? $main::defs{$dev}->{VERSION} : "") =~ m/CSM|CUNO/ )){
  184. require "$main::attr{global}{modpath}/FHEM/OWX_CCC.pm";
  185. $owx = OWX_CCC->new();
  186. #-- check if we are connecting to Arduino (via FRM):
  187. } elsif ($dev =~ /^\d{1,2}$/) {
  188. require "$main::attr{global}{modpath}/FHEM/OWX_FRM.pm";
  189. $owx = OWX_FRM->new();
  190. } else {
  191. return "OWX: Define failed, unable to identify interface type $dev"
  192. };
  193. my $ret = $owx->Define($hash,$def);
  194. #-- cancel definition of OWX if interface define fails
  195. return $ret if $ret;
  196. $hash->{OWX} = $owx;
  197. $hash->{INTERFACE} = $owx->{interface};
  198. $hash->{STATE} = "Defined";
  199. if ($main::init_done) {
  200. return OWX_ASYNC_Init($hash);
  201. }
  202. return undef;
  203. }
  204. #######################################################################################
  205. #
  206. # OWTX_Attr - Set one attribute value for device
  207. #
  208. # Parameter hash = hash of device addressed
  209. # a = argument array
  210. #
  211. ########################################################################################
  212. sub OWX_ASYNC_Attr(@) {
  213. my ($do,$name,$key,$value) = @_;
  214. my $hash = $main::defs{$name};
  215. my $ret;
  216. if ( $do eq "set") {
  217. SET_HANDLER: {
  218. $key eq "interval" and do {
  219. $hash->{interval} = $value;
  220. if ($main::init_done) {
  221. OWX_ASYNC_Kick($hash);
  222. }
  223. last;
  224. };
  225. $key eq "buspower" and do {
  226. if ($value eq "parasitic" and (defined $hash->{dokick}) and $hash->{dokick} ne "ignored") {
  227. $hash->{dokick} = "ignored";
  228. Log3($name,3,"OWX_ASYNC: ignoring attribute dokick because buspower is parasitic");
  229. } elsif ($value eq "real" and (defined $hash->{dokick}) and $hash->{dokick} eq "ignored") {
  230. $hash->{dokick} = $main::attr{$name}{dokick};
  231. }
  232. last;
  233. };
  234. $key eq "dokick" and do {
  235. if ($main::attr{$name}{"buspower"} and $main::attr{$name}{"buspower"} eq "parasitic" and ((!defined $hash->{dokick}) or $hash->{dokick} ne "ignored")) {
  236. $hash->{dokick} = "ignored";
  237. Log3($name,3,"OWX_ASYNC: ignoring attribute dokick because buspower is parasitic");
  238. } else {
  239. $hash->{dokick} = $value;
  240. }
  241. last;
  242. };
  243. }
  244. } elsif ( $do eq "del" ) {
  245. DEL_HANDLER: {
  246. $key eq "interval" and do {
  247. $hash->{interval} = 300;
  248. if ($main::init_done) {
  249. OWX_ASYNC_Kick($hash);
  250. }
  251. last;
  252. };
  253. $key eq "buspower" and do {
  254. if ((defined $hash->{dokick}) and $hash->{dokick} eq "ignored") {
  255. $hash->{dokick} = $main::attr{$name}{dokick};
  256. }
  257. last;
  258. };
  259. $key eq "dokick" and do {
  260. delete $hash->{dokick};
  261. last;
  262. };
  263. }
  264. }
  265. return $ret;
  266. }
  267. sub OWX_ASYNC_Notify ($$) {
  268. my ($hash,$dev) = @_;
  269. my $name = $hash->{NAME};
  270. my $type = $hash->{TYPE};
  271. if( grep(m/^(INITIALIZED|REREADCFG)$/, @{$dev->{CHANGED}}) ) {
  272. OWX_ASYNC_Init($hash);
  273. } elsif( grep(m/^SAVE$/, @{$dev->{CHANGED}}) ) {
  274. }
  275. }
  276. sub OWX_ASYNC_Ready ($) {
  277. my $hash = shift;
  278. unless ( $hash->{STATE} eq "Active" ) {
  279. my $ret = OWX_ASYNC_Init($hash);
  280. if ($ret) {
  281. Log3 ($hash->{NAME},2,"OWX: Error initializing ".$hash->{NAME}.": ".$ret);
  282. return undef;
  283. }
  284. }
  285. return 1;
  286. };
  287. sub OWX_ASYNC_Read ($) {
  288. my ($hash) = @_;
  289. Log3 ($hash->{NAME},5,"OWX_ASYNC_Read") if ($owx_async_debug > 2);
  290. if (defined $hash->{ASYNC}) {
  291. $hash->{ASYNC}->poll();
  292. };
  293. OWX_ASYNC_RunTasks($hash);
  294. };
  295. sub OWX_ASYNC_Disconnect($) {
  296. my ($hash) = @_;
  297. my $async = $hash->{ASYNC};
  298. Log3 ($hash->{NAME},3, "OWX_ASYNC_Disconnect");
  299. if (defined $async) {
  300. $async->exit($hash);
  301. delete $hash->{ASYNC};
  302. };
  303. $hash->{STATE} = "disconnected" if $hash->{STATE} eq "Active";
  304. $hash->{PRESENT} = 0;
  305. GP_ForallClients($hash,sub {
  306. my ($client) = @_;
  307. RemoveInternalTimer($client);
  308. readingsSingleUpdate($client,"present",0,$client->{PRESENT});
  309. $client->{PRESENT} = 0;
  310. },undef);
  311. };
  312. ########################################################################################
  313. #
  314. # OWX_ASYNC_Alarms - Initiate search for devices on the 1-Wire bus which have the alarm flag set
  315. #
  316. # Parameter hash = hash of bus master
  317. #
  318. # Return: 1 if search could be successfully initiated. Message or list of alarmed devices
  319. # undef otherwise
  320. #TODO fix OWX_ASYNC_Alarms return value on failure
  321. ########################################################################################
  322. sub OWX_ASYNC_PT_Alarms ($) {
  323. my ($hash) = @_;
  324. #-- get the interface
  325. my $async = $hash->{ASYNC};
  326. #-- Discover all devices on the 1-Wire bus, they will be found in $hash->{DEVS}
  327. if (defined $async) {
  328. return PT_THREAD(sub {
  329. my ($thread) = @_;
  330. PT_BEGIN($thread);
  331. $thread->{pt_alarms} = $async->get_pt_alarms();
  332. PT_WAIT_THREAD($thread->{pt_alarms});
  333. die $thread->{pt_alarms}->PT_CAUSE() if ($thread->{pt_alarms}->PT_STATE() == PT_ERROR);
  334. if (defined (my $alarmed_devs = $thread->{pt_alarms}->PT_RETVAL())) {
  335. OWX_ASYNC_AfterAlarms($hash,$alarmed_devs);
  336. };
  337. PT_END;
  338. });
  339. } else {
  340. my $owx_interface = $hash->{INTERFACE};
  341. if( !defined($owx_interface) ) {
  342. die "OWX: Alarms called with undefined interface on bus $hash->{NAME}";
  343. } else {
  344. die "OWX: Alarms called with unknown interface $owx_interface on bus $hash->{NAME}";
  345. }
  346. }
  347. }
  348. ########################################################################################
  349. #
  350. # OWX_ASYNC_AfterAlarms - is called when the search for alarmed devices that was initiated by OWX_ASYNC_Alarms successfully returns
  351. #
  352. # stores device-addresses found in $hash->{ALARMDEVS}
  353. #
  354. # Attention: this function is not intendet to be called directly!
  355. #
  356. # Parameter hash = hash of bus master
  357. # alarmed_devs = Reference to Array of device-address-strings
  358. #
  359. # Returns: nothing
  360. #
  361. ########################################################################################
  362. sub OWX_ASYNC_AfterAlarms($$) {
  363. my ($hash,$alarmed_devs) = @_;
  364. my @alarmed_devnames = ();
  365. GP_ForallClients($hash,sub {
  366. my ($client) = @_;
  367. my $romid = $client->{ROM_ID};
  368. Log3 ($client->{IODev}->{NAME},5,"OWX_ASYNC_AfterAlarms client NAME: $client->{NAME}, ROM_ID: $romid, ALARM: $client->{ALARM}, alarmed_devs: [".join(",",@$alarmed_devs)."]") if ($owx_async_debug>2);
  369. if (grep {$romid eq $_} @$alarmed_devs) {
  370. readingsSingleUpdate($client,"alarm",1,!$client->{ALARM});
  371. $client->{ALARM}=1;
  372. push (@alarmed_devnames,$client->{NAME});
  373. } else {
  374. readingsSingleUpdate($client,"alarm",0, $client->{ALARM});
  375. $client->{ALARM}=0;
  376. }
  377. });
  378. $hash->{ALARMDEVS} = \@alarmed_devnames;
  379. Log3 ($hash->{NAME},5,"OWX_ASYNC_AfterAlarms: ALARMDEVS = [".join(",",@alarmed_devnames)."]") if ($owx_async_debug>2);
  380. };
  381. ########################################################################################
  382. #
  383. # OWX_ASYNC_Discover - Discover devices on the 1-Wire bus,
  384. # autocreate devices if not already present
  385. #
  386. # Parameter hash = hash of bus master
  387. #
  388. # Return: List of devices in table format or undef
  389. #
  390. ########################################################################################
  391. sub OWX_ASYNC_PT_Discover ($) {
  392. my ($hash) = @_;
  393. #-- get the interface
  394. my $async = $hash->{ASYNC};
  395. #-- Discover all devices on the 1-Wire bus, they will be found in $hash->{DEVS}
  396. if (defined $async) {
  397. return PT_THREAD(sub {
  398. my ($thread) = @_;
  399. PT_BEGIN($thread);
  400. $thread->{pt_discover} = $async->get_pt_discover();
  401. PT_WAIT_THREAD($thread->{pt_discover});
  402. die $thread->{pt_discover}->PT_CAUSE() if ($thread->{pt_discover}->PT_STATE() == PT_ERROR);
  403. if (my $owx_devices = $thread->{pt_discover}->PT_RETVAL()) {
  404. PT_EXIT(OWX_ASYNC_AutoCreate($hash,$owx_devices));
  405. };
  406. PT_END;
  407. });
  408. } else {
  409. my $owx_interface = $hash->{INTERFACE};
  410. if( !defined($owx_interface) ) {
  411. die "OWX: Discover called with undefined interface on bus $hash->{NAME}";
  412. } else {
  413. die "OWX: Discover called with unknown interface $owx_interface on bus $hash->{NAME}";
  414. }
  415. }
  416. }
  417. #######################################################################################
  418. #
  419. # OWX_ASYNC_Search - Initiate Search for devices on the 1-Wire bus
  420. #
  421. # Parameter hash = hash of bus master
  422. #
  423. # Return: 1, if initiation of search could be startet, undef if not
  424. #
  425. ########################################################################################
  426. sub OWX_ASYNC_PT_Search($) {
  427. my ($hash) = @_;
  428. #-- get the interface
  429. my $async = $hash->{ASYNC};
  430. #-- Discover all devices on the 1-Wire bus, they will be found in $hash->{DEVS}
  431. if (defined $async) {
  432. return PT_THREAD(sub {
  433. my ($thread) = @_;
  434. PT_BEGIN($thread);
  435. $thread->{pt_discover} = $async->get_pt_discover();
  436. PT_WAIT_THREAD($thread->{pt_discover});
  437. die $thread->{pt_discover}->PT_CAUSE() if ($thread->{pt_discover}->PT_STATE() == PT_ERROR);
  438. if (defined (my $owx_devs = $thread->{pt_discover}->PT_RETVAL())) {
  439. OWX_ASYNC_AfterSearch($hash,$owx_devs);
  440. }
  441. PT_END;
  442. });
  443. } else {
  444. my $owx_interface = $hash->{INTERFACE};
  445. if( !defined($owx_interface) ) {
  446. die "OWX: Search called with undefined interface on bus $hash->{NAME}";
  447. } else {
  448. die "OWX: Search called with unknown interface $owx_interface on bus $hash->{NAME}";
  449. }
  450. }
  451. }
  452. ########################################################################################
  453. #
  454. # OWX_ASYNC_AfterSearch - is called when the search initiated by OWX_ASYNC_Search successfully returns
  455. #
  456. # stores device-addresses found in $hash->{DEVS}
  457. #
  458. # Attention: this function is not intendet to be called directly!
  459. #
  460. # Parameter hash = hash of bus master
  461. # owx_devs = Reference to Array of device-address-strings
  462. #
  463. # Returns: nothing
  464. #
  465. ########################################################################################
  466. sub OWX_ASYNC_AfterSearch($$) {
  467. my ($hash,$owx_devs) = @_;
  468. # if (defined $owx_devs and (ref($owx_devs) eq "ARRAY")) {
  469. my @devnames = ();
  470. GP_ForallClients($hash,sub {
  471. my ($client) = @_;
  472. my $romid = $client->{ROM_ID};
  473. Log3 ($client->{IODev}->{NAME},5,"OWX_ASYNC_AfterSearch client NAME: $client->{NAME}, ROM_ID: $romid, PRESENT: $client->{PRESENT}, devs: [".join(",",@$owx_devs)."]") if ($owx_async_debug>2);
  474. if (grep {$romid eq $_} @$owx_devs) {
  475. readingsSingleUpdate($client,"present",1,!$client->{PRESENT});
  476. $client->{PRESENT} = 1;
  477. push (@devnames,$client->{NAME});
  478. } else {
  479. readingsSingleUpdate($client,"present",0,$client->{PRESENT});
  480. $client->{PRESENT} = 0;
  481. }
  482. });
  483. $hash->{DEVS} = \@devnames;
  484. Log3 ($hash->{NAME},5,"OWX_ASYNC_AfterSearch: DEVS = [".join(",",@devnames)."]") if ($owx_async_debug>2);
  485. # }
  486. }
  487. ########################################################################################
  488. #
  489. # OWX_ASYNC_Autocreate - autocreate devices if not already present
  490. #
  491. # Parameter hash = hash of bus master
  492. # owx_devs = Reference to Array of device-address-strings as OWX_ASYNC_AfterSearch stores in $hash->{DEVS}
  493. #
  494. # Return: List of devices in table format or undef
  495. #
  496. ########################################################################################
  497. sub OWX_ASYNC_AutoCreate($$) {
  498. my ($hash,$owx_devs) = @_;
  499. my $name = $hash->{NAME};
  500. my ($chip,$acstring,$acname,$exname);
  501. my $ret= "";
  502. my @owx_names=();
  503. if (defined $owx_devs and (ref($owx_devs) eq "ARRAY")) {
  504. #-- Go through all devices found on this bus
  505. foreach my $owx_dev (@{$owx_devs}) {
  506. #-- ignore those which do not have the proper pattern
  507. if( !($owx_dev =~ m/[0-9A-F]{2}\.[0-9A-F]{12}\.[0-9A-F]{2}/) ){
  508. Log3 ($hash->{NAME},3,"OWX: Invalid 1-Wire device ID $owx_dev, ignoring it");
  509. next;
  510. }
  511. #-- three pieces of the ROM ID found on the bus
  512. my $owx_rnf = substr($owx_dev,3,12);
  513. my $owx_f = substr($owx_dev,0,2);
  514. my $owx_crc = substr($owx_dev,16,2);
  515. my $id_owx = $owx_f.".".$owx_rnf;
  516. my $match = 0;
  517. #-- Check against all existing devices
  518. foreach my $fhem_dev (sort keys %main::defs) {
  519. #-- skip if busmaster
  520. # next if( $hash->{NAME} eq $main::defs{$fhem_dev}{NAME} );
  521. #-- all OW types start with OW
  522. next if( !defined($main::defs{$fhem_dev}{TYPE}));
  523. next if( substr($main::defs{$fhem_dev}{TYPE},0,2) ne "OW");
  524. my $id_fhem = substr($main::defs{$fhem_dev}{ROM_ID},0,15);
  525. #-- skip interface device
  526. next if( length($id_fhem) != 15 );
  527. #-- testing if equal to the one found here
  528. # even with improper family
  529. # Log 1, " FHEM-Device = ".substr($id_fhem,3,12)." OWX discovered device ".substr($id_owx,3,12);
  530. if( substr($id_fhem,3,12) eq substr($id_owx,3,12) ) {
  531. #-- warn if improper family id
  532. if( substr($id_fhem,0,2) ne substr($id_owx,0,2) ){
  533. Log3 ($hash->{NAME},3, "OWX: Warning, $fhem_dev is defined with improper family id ".substr($id_fhem,0,2).
  534. ", must enter correct model in configuration");
  535. #$main::defs{$fhem_dev}{OW_FAMILY} = substr($id_owx,0,2);
  536. }
  537. $exname=$main::defs{$fhem_dev}{NAME};
  538. push(@owx_names,$exname);
  539. #-- replace the ROM ID by the proper value including CRC
  540. $main::defs{$fhem_dev}{ROM_ID}=$owx_dev;
  541. readingsSingleUpdate($main::defs{$fhem_dev},"present",1,!$main::defs{$fhem_dev}->{PRESENT});
  542. $main::defs{$fhem_dev}{PRESENT}=1;
  543. $match = 1;
  544. last;
  545. }
  546. #
  547. }
  548. #-- Determine the device type
  549. if(exists $owg_family{$owx_f}) {
  550. $chip = $owg_family{$owx_f}[0];
  551. $acstring = $owg_family{$owx_f}[1];
  552. }else{
  553. Log3 ($hash->{NAME},3, "OWX: Unknown family code '$owx_f' found");
  554. #-- All unknown families are ID only
  555. $chip = "unknown";
  556. $acstring = "OWID $owx_f";
  557. }
  558. #Log 1,"###\nfor the following device match=$match, chip=$chip name=$name acstring=$acstring";
  559. #-- device exists
  560. if( $match==1 ){
  561. $ret .= sprintf("%s.%s %-14s %s\n", $owx_f,$owx_rnf, $chip, $exname);
  562. #-- device unknown, autocreate
  563. }else{
  564. #-- example code for checking global autocreate - do we want this ?
  565. #foreach my $d (keys %defs) {
  566. #next if($defs{$d}{TYPE} ne "autocreate");
  567. #return undef if(AttrVal($defs{$d}{NAME},"disable",undef));
  568. $acname = sprintf "OWX_%s_%s",$owx_f,$owx_rnf;
  569. #Log 1, "to define $acname $acstring $owx_rnf";
  570. my $res = CommandDefine(undef,"$acname $acstring $owx_rnf");
  571. if($res) {
  572. $ret.= "OWX: Error autocreating with $acname $acstring $owx_rnf: $res\n";
  573. } else{
  574. select(undef,undef,undef,0.1);
  575. push(@owx_names,$acname);
  576. readingsSingleUpdate($main::defs{$acname},"present",1,!$main::defs{$acname}->{PRESENT});
  577. $main::defs{$acname}{PRESENT}=1;
  578. #-- THIS IODev, default room (model is set in the device module)
  579. CommandAttr (undef,"$acname IODev $hash->{NAME}");
  580. CommandAttr (undef,"$acname room OWX");
  581. #-- replace the ROM ID by the proper value
  582. $main::defs{$acname}{ROM_ID}=$owx_dev;
  583. $ret .= sprintf("%s.%s %-10s %s\n", $owx_f,$owx_rnf, $chip, $acname);
  584. }
  585. }
  586. }
  587. }
  588. #-- final step: Undefine all 1-Wire devices which
  589. # are autocreated and
  590. # not discovered on this bus
  591. # but have this IODev
  592. foreach my $fhem_dev (sort keys %main::defs) {
  593. #-- skip if malformed device
  594. #next if( !defined($main::defs{$fhem_dev}{NAME}) );
  595. #-- all OW types start with OW, but safeguard against deletion of other devices
  596. #next if( !defined($main::defs{$fhem_dev}{TYPE}));
  597. next if( substr($main::defs{$fhem_dev}{TYPE},0,2) ne "OW");
  598. next if( uc($main::defs{$fhem_dev}{TYPE}) eq "OWX");
  599. next if( uc($main::defs{$fhem_dev}{TYPE}) eq "OWFS");
  600. next if( uc($main::defs{$fhem_dev}{TYPE}) eq "OWSERVER");
  601. next if( uc($main::defs{$fhem_dev}{TYPE}) eq "OWDEVICE");
  602. #-- restrict to autocreated devices
  603. next if( $main::defs{$fhem_dev}{NAME} !~ m/OWX_[0-9a-fA-F]{2}_/);
  604. #-- skip if the device is present.
  605. next if( $main::defs{$fhem_dev}{PRESENT} == 1);
  606. #-- skip if different IODev, but only if other IODev exists
  607. if ( $main::defs{$fhem_dev}{IODev} ){
  608. next if( $main::defs{$fhem_dev}{IODev}{NAME} ne $hash->{NAME} );
  609. }
  610. Log3 ($hash->{NAME},3, "OWX: Deleting unused 1-Wire device $main::defs{$fhem_dev}{NAME} of type $main::defs{$fhem_dev}{TYPE}");
  611. CommandDelete(undef,$main::defs{$fhem_dev}{NAME});
  612. #Log 1, "present= ".$main::defs{$fhem_dev}{PRESENT}." iodev=".$main::defs{$fhem_dev}{IODev}{NAME};
  613. }
  614. #-- Log the discovered devices
  615. Log3 ($hash->{NAME},2, "OWX: 1-Wire devices found on bus $name (".join(",",@owx_names).")");
  616. #-- tabular view as return value
  617. return "OWX: 1-Wire devices found on bus $name \n".$ret;
  618. }
  619. ########################################################################################
  620. #
  621. # OWX_ASYNC_Get - Implements GetFn function
  622. #
  623. # Parameter hash = hash of the bus master a = argument array
  624. #
  625. ########################################################################################
  626. sub OWX_ASYNC_Get($@) {
  627. my ($hash, @a) = @_;
  628. return "OWX: Get needs exactly one parameter" if(@a != 2);
  629. my $name = $hash->{NAME};
  630. my $owx_dev = $hash->{ROM_ID};
  631. my ($task,$task_state);
  632. if( $a[1] eq "alarms") {
  633. eval {
  634. OWX_ASYNC_RunToCompletion($hash,OWX_ASYNC_PT_Alarms($hash));
  635. };
  636. return $@ if $@;
  637. unless ( defined $hash->{ALARMDEVS} and @{$hash->{ALARMDEVS}}) {
  638. return "OWX: No alarmed 1-Wire devices found on bus $name";
  639. }
  640. return "OWX: ".scalar(@{$hash->{ALARMDEVS}})." alarmed 1-Wire devices found on bus $name (".join(",",@{$hash->{ALARMDEVS}}).")";
  641. } elsif( $a[1] eq "devices") {
  642. eval {
  643. $task_state = OWX_ASYNC_RunToCompletion($hash,OWX_ASYNC_PT_Discover($hash));
  644. };
  645. return $@ if $@;
  646. return $task_state;
  647. } elsif( $a[1] eq "version") {
  648. return $owx_async_version;
  649. } else {
  650. return "OWX: Get with unknown argument $a[1], choose one of ".
  651. join(" ", sort keys %gets);
  652. }
  653. }
  654. #######################################################################################
  655. #
  656. # OWX_ASYNC_Init - Re-Initialize the device
  657. #
  658. # Parameter hash = hash of bus master
  659. #
  660. # Return 0 or undef : OK
  661. # 1 or Errormessage : not OK
  662. #
  663. ########################################################################################
  664. sub OWX_ASYNC_Init ($) {
  665. my ($hash)=@_;
  666. RemoveInternalTimer($hash);
  667. if (defined ($hash->{ASNYC})) {
  668. $hash->{ASYNC}->exit($hash);
  669. delete $hash->{ASYNC}; #TODO should we call delete on $hash->{ASYNC}?
  670. }
  671. #-- get the interface
  672. my $owx = $hash->{OWX};
  673. if (defined $owx) {
  674. $hash->{INTERFACE} = $owx->{interface};
  675. my $ret;
  676. #-- Third step: see, if a bus interface is detected
  677. eval {
  678. $ret = $owx->initialize($hash);
  679. };
  680. Log3 ($hash->{NAME},4,"OWX_ASYNC_Init failed: $@") if $@;
  681. if (my $err = GP_Catch($@)) {
  682. $hash->{PRESENT} = 0;
  683. $hash->{STATE} = "Init Failed: $err";
  684. return "OWX_ASYNC_Init failed: $err";
  685. };
  686. return undef unless $ret;
  687. $hash->{ASYNC} = $ret ;
  688. $hash->{ASYNC}->{debug} = $owx_async_debug;
  689. $hash->{INTERFACE} = $owx->{interface};
  690. } else {
  691. return "OWX: Init called with undefined interface";
  692. }
  693. $hash->{STATE} = "Active";
  694. #-- Fourth step: discovering devices on the bus
  695. # in 10 seconds discover all devices on the 1-Wire bus
  696. my $pt_discover = OWX_ASYNC_PT_Discover($hash);
  697. $pt_discover->{ExecuteTime} = gettimeofday()+10;
  698. eval {
  699. OWX_ASYNC_Schedule($hash,$pt_discover);
  700. };
  701. return GP_Catch($@) if $@;
  702. #-- Default settings
  703. $hash->{interval} = AttrVal($hash->{NAME},"interval",300); # kick every 5 minutes
  704. $hash->{followAlarms} = "off";
  705. $hash->{ALARMED} = "no";
  706. #-- InternalTimer blocks if init_done is not true
  707. $hash->{PRESENT} = 1;
  708. #readingsSingleUpdate($hash,"state","defined",1);
  709. #-- Intiate first alarm detection and eventually conversion in a minute or so
  710. InternalTimer(gettimeofday() + $hash->{interval}, "OWX_ASYNC_Kick", $hash,0);
  711. GP_ForallClients($hash,\&OWX_ASYNC_InitClient,undef);
  712. return undef;
  713. }
  714. sub OWX_ASYNC_InitClient {
  715. my ($hash) = @_;
  716. my $name = $hash->{NAME};
  717. #return undef unless (defined $hash->{InitFn});
  718. my $ret = CallFn($name,"InitFn",$hash);
  719. if ($ret) {
  720. Log3 $name,2,"error initializing '".$hash->{NAME}."': ".$ret;
  721. }
  722. }
  723. ########################################################################################
  724. #
  725. # OWX_ASYNC_Kick - Initiate some processes in all devices
  726. #
  727. # Parameter hash = hash of bus master
  728. #
  729. # Return 1 : OK
  730. # 0 : Not OK
  731. #
  732. ########################################################################################
  733. sub OWX_ASYNC_Kick($) {
  734. my($hash) = @_;
  735. my $ret;
  736. #-- Call us in n seconds again.
  737. InternalTimer(gettimeofday()+ $hash->{interval}, "OWX_ASYNC_Kick", $hash,0);
  738. unless ($hash->{".kickrunning"}) {
  739. $hash->{".kickrunning"} = 1;
  740. eval {
  741. OWX_ASYNC_Schedule( $hash, PT_THREAD(sub {
  742. my ($thread) = @_;
  743. PT_BEGIN($thread);
  744. #-- Only if we have the dokick attribute set to 1
  745. if ((defined $hash->{dokick}) and $hash->{dokick} eq "1") {
  746. Log3 $hash->{NAME},5,"OWX_ASYNC_PT_Kick: kicking DS14B20 temperature conversion";
  747. #-- issue the skip ROM command \xCC followed by start conversion command \x44
  748. $thread->{pt_execute} = OWX_ASYNC_PT_Execute($hash,1,undef,"\x44",0);
  749. PT_WAIT_THREAD($thread->{pt_execute});
  750. if ($thread->{pt_execute}->PT_STATE() == PT_ERROR) {
  751. Log3 ($hash->{NAME},4,"OWX_ASYNC_PT_Kick: Failure in temperature conversion: ".$thread->{pt_execute}->PT_CAUSE());
  752. } else {
  753. $thread->{ExecuteTime} = gettimeofday()+1;
  754. PT_YIELD_UNTIL(gettimeofday() >= $thread->{ExecuteTime});
  755. delete $thread->{ExecuteTime};
  756. GP_ForallClients($hash,sub {
  757. my ($client) = @_;
  758. if ($client->{TYPE} eq "OWTHERM" and AttrVal($client->{NAME},"tempConv","") eq "onkick" ) {
  759. Log3 $client->{NAME},5,"OWX_ASYNC_PT_Kick: doing tempConv for $client->{NAME}";
  760. OWX_ASYNC_Schedule($client, OWXTHERM_PT_GetValues($client) );
  761. }
  762. },undef);
  763. }
  764. }
  765. $thread->{pt_search} = OWX_ASYNC_PT_Search($hash);
  766. PT_WAIT_THREAD($thread->{pt_search});
  767. if ($thread->{pt_search}->PT_STATE() == PT_ERROR) {
  768. Log3 ($hash->{NAME},4,"OWX_ASYNC_PT_Kick: Failure in search: ".$thread->{pt_search}->PT_CAUSE());
  769. } else {
  770. $thread->{pt_alarms} = OWX_ASYNC_PT_Alarms($hash);
  771. PT_WAIT_THREAD($thread->{pt_alarms});
  772. if ($thread->{pt_alarms}->PT_STATE() == PT_ERROR) {
  773. Log3 ($hash->{NAME},4,"OWX_ASYNC_PT_Kick: Failure in alarm-search: ".$thread->{pt_alarms}->PT_CAUSE());
  774. };
  775. }
  776. delete $hash->{".kickrunning"};
  777. PT_END;
  778. }));
  779. };
  780. Log3 ($hash->{NAME},4,"OWX_ASYNC_PT_Kick".GP_Catch($@)) if ($@);
  781. }
  782. return 1;
  783. }
  784. ########################################################################################
  785. #
  786. # OWX_ASYNC_Set - Implements SetFn function
  787. #
  788. # Parameter hash , a = argument array
  789. #
  790. ########################################################################################
  791. sub OWX_ASYNC_Set($@) {
  792. my ($hash, @a) = @_;
  793. my $name = shift @a;
  794. my $res;
  795. #-- First we need to find the ROM ID corresponding to the device name
  796. my $owx_romid = $hash->{ROM_ID};
  797. Log3 ($hash->{NAME},5, "OWX_ASYNC_Set request $name $owx_romid ".join(" ",@a));
  798. #-- for the selector: which values are possible
  799. return join(" ", sort keys %sets) if(@a != 2);
  800. return "OWX_ASYNC_Set: With unknown argument $a[0], choose one of " . join(" ", sort keys %sets)
  801. if(!defined($sets{$a[0]}));
  802. #-- Set timer value
  803. if( $a[0] eq "interval" ){
  804. #-- only values >= 15 secs allowed
  805. if( $a[1] >= 15){
  806. $hash->{interval} = $a[1];
  807. $res = 1;
  808. } else {
  809. $res = 0;
  810. }
  811. }
  812. #-- Set alarm behaviour
  813. if( $a[0] eq "followAlarms" ){
  814. #-- only values >= 15 secs allowed
  815. if( (lc($a[1]) eq "off") && ($hash->{followAlarms} eq "on") ){
  816. $hash->{followAlarms} = "off";
  817. $res = 1;
  818. }elsif( (lc($a[1]) eq "on") && ($hash->{followAlarms} eq "off") ){
  819. $hash->{followAlarms} = "on";
  820. $res = 1;
  821. } else {
  822. $res = 0;
  823. }
  824. }
  825. Log3 ($name,3, "OWX_ASYNC_Set $name ".join(" ",@a)." => $res");
  826. DoTrigger($name, undef) if($main::init_done);
  827. return "OWX_ASYNC_Set => $name ".join(" ",@a)." => $res";
  828. }
  829. ########################################################################################
  830. #
  831. # OWX_ASYNC_Undef - Implements UndefFn function
  832. #
  833. # Parameter hash = hash of the bus master, name
  834. #
  835. ########################################################################################
  836. sub OWX_ASYNC_Undef ($$) {
  837. my ($hash, $name) = @_;
  838. RemoveInternalTimer($hash);
  839. OWX_ASYNC_Disconnect($hash);
  840. return undef;
  841. }
  842. ########################################################################################
  843. #
  844. # OWX_ASYNC_Verify - Verify a particular device on the 1-Wire bus
  845. #
  846. # Parameter hash = hash of bus master, dev = 8 Byte ROM ID of device to be tested
  847. #
  848. # Return 1 : device found
  849. # 0 : device not found
  850. #
  851. ########################################################################################
  852. sub OWX_ASYNC_PT_Verify($) {
  853. my ($hash) = @_;
  854. #-- get the interface
  855. my $async = $hash->{IODev}->{ASYNC};
  856. my $romid = $hash->{ROM_ID};
  857. #-- Verify a devices is present on the 1-Wire bus
  858. return PT_THREAD(sub {
  859. my ($thread) = @_;
  860. PT_BEGIN($thread);
  861. if (defined $async) {
  862. $thread->{pt_verify} = $async->get_pt_verify($romid);
  863. PT_WAIT_THREAD($thread->{pt_verify});
  864. die $thread->{pt_verify}->PT_CAUSE() if ($thread->{pt_verify}->PT_STATE() == PT_ERROR);
  865. my $value = $thread->{pt_verify}->PT_RETVAL();
  866. if( $value == 0 ){
  867. readingsSingleUpdate($hash,"present",0,$hash->{PRESENT});
  868. } else {
  869. readingsSingleUpdate($hash,"present",1,!$hash->{PRESENT});
  870. }
  871. $hash->{PRESENT} = $value;
  872. } else {
  873. my $owx_interface = $hash->{IODev}->{INTERFACE};
  874. if( !defined($owx_interface) ) {
  875. die "OWX: Verify called with undefined interface on bus $hash->{IODev}->{NAME}";
  876. } else {
  877. die "OWX: Verify called with unknown interface $owx_interface on bus $hash->{IODev}->{NAME}";
  878. }
  879. }
  880. PT_END;
  881. });
  882. }
  883. ########################################################################################
  884. #
  885. # OWX_Execute - # similar to OWX_Complex, but asynchronous
  886. # executes a sequence of 'reset','skip/match ROM','write','read','delay' on the bus
  887. #
  888. # Parameter hash = hash of bus master,
  889. # context = anything that can be sent as a hash-member through a thread-safe queue
  890. # see http://perldoc.perl.org/Thread/Queue.html#DESCRIPTION
  891. # reset = 1/0 if 1 reset the bus first
  892. # owx_dev = 8 Byte ROM ID of device to be tested, if undef do a 'skip ROM' instead
  893. # data = bytes to write (string)
  894. # numread = number of bytes to read after write
  895. # delay = optional delay (in ms) to wait after executing the next command
  896. # for the same device
  897. #
  898. # Returns : 1 if OK
  899. # 0 if not OK
  900. #
  901. ########################################################################################
  902. sub OWX_ASYNC_PT_Execute($$$$$) {
  903. my ( $hash, $reset, $owx_dev, $data, $numread ) = @_;
  904. if (my $executor = $hash->{ASYNC}) {
  905. return $executor->get_pt_execute($reset,$owx_dev,$data,$numread);
  906. } else {
  907. die "OWX_ASYNC_PT_Execute: no async device assigned";
  908. }
  909. }
  910. sub OWX_ASYNC_Schedule($$) {
  911. my ( $hash, $task ) = @_;
  912. my $master = $hash->{TYPE} eq "OWX_ASYNC" ? $hash : $hash->{IODev};
  913. my $name = $hash->{NAME};
  914. Log3 ($master->{NAME},5,"OWX_ASYNC_Schedule master: ".$master->{NAME}.", task: ".$name);
  915. die "OWX_ASYNC_Schedule: Master not Active" unless $master->{STATE} eq "Active";
  916. $task->{ExecuteTime} = gettimeofday() unless (defined $task->{ExecuteTime});
  917. #if buspower is parasitic serialize all tasks by scheduling everything to master queue.
  918. $name = $master->{NAME} if (AttrVal($master->{NAME},"buspower","real") eq "parasitic");
  919. if (defined $master->{tasks}->{$name}) {
  920. push @{$master->{tasks}->{$name}}, $task;
  921. $hash->{NUMTASKS} = @{$master->{tasks}->{$name}};
  922. } else {
  923. $master->{tasks}->{$name} = [$task];
  924. $hash->{NUMTASKS} = 1;
  925. }
  926. #TODO make use of $master->{".nexttasktime"}
  927. InternalTimer($task->{ExecuteTime}, "OWX_ASYNC_RunTasks", $master,0);
  928. };
  929. sub OWX_ASYNC_RunToCompletion($$) {
  930. my ($hash,$task) = @_;
  931. my $task_state;
  932. eval {
  933. OWX_ASYNC_Schedule($hash,$task);
  934. my $master = $hash->{TYPE} eq "OWX_ASYNC" ? $hash : $hash->{IODev};
  935. do {
  936. die "interface $master->{INTERFACE} not active" unless defined $master->{ASYNC};
  937. $master->{ASYNC}->poll();
  938. OWX_ASYNC_RunTasks($master);
  939. $task_state = $task->PT_STATE();
  940. } while ($task_state == PT_INITIAL or $task_state == PT_WAITING or $task_state == PT_YIELDED);
  941. };
  942. die $@ if $@;
  943. die $task->PT_CAUSE() if ($task_state == PT_ERROR or $task_state == PT_CANCELED);
  944. return $task->PT_RETVAL();
  945. }
  946. sub OWX_ASYNC_TaskTimeout($$) {
  947. my ( $master, $timeout ) = @_;
  948. die "OWX_ASYNC_TaskTimeout: no task running" unless defined $master->{".runningtask"};
  949. $master->{".runningtask"}->{TimeoutTime} = $timeout;
  950. }
  951. sub OWX_ASYNC_RunTasks($) {
  952. my ( $master ) = @_;
  953. if ($master->{STATE} eq "Active") {
  954. Log3 ($master->{NAME},5,"OWX_ASYNC_RunTasks: called") if ($owx_async_debug>2);
  955. my $now = gettimeofday();
  956. while(1) {
  957. my @queue_waiting = ();
  958. my @queue_ready = ();
  959. my @queue_sleeping = ();
  960. my @queue_initial = ();
  961. foreach my $name (keys %{$master->{tasks}}) {
  962. my $queue = $master->{tasks}->{$name};
  963. while (@$queue) {
  964. my $state = $queue->[0]->PT_STATE();
  965. if ($state == PT_WAITING) {
  966. push @queue_waiting,{ device => $name, queue => $queue};
  967. last;
  968. } elsif ($state == PT_YIELDED) {
  969. if ($now >= $queue->[0]->{ExecuteTime}) {
  970. push @queue_ready, { device => $name, queue => $queue};
  971. } else {
  972. push @queue_sleeping, { device => $name, queue => $queue};
  973. }
  974. last;
  975. } elsif ($state == PT_INITIAL) {
  976. push @queue_initial, { device => $name, queue => $queue};
  977. last;
  978. } else {
  979. shift @$queue;
  980. $main::defs{$name}->{NUMTASKS} = @$queue;
  981. }
  982. };
  983. delete $master->{tasks}->{$name} unless (@$queue);
  984. }
  985. if (defined (my $current = @queue_waiting ? shift @queue_waiting : @queue_ready ? shift @queue_ready : @queue_initial ? shift @queue_initial : undef)) {
  986. my $task = $current->{queue}->[0];
  987. $master->{".runningtask"} = $task;
  988. my $timeout = $task->{TimeoutTime};
  989. if ($task->PT_SCHEDULE()) {
  990. my $state = $task->PT_STATE();
  991. # waiting for ExecuteResponse:
  992. if ($state == PT_WAITING) {
  993. if (defined $task->{TimeoutTime}) {
  994. #task timed out:
  995. if ($now >= $task->{TimeoutTime}) {
  996. Log3 ($master->{NAME},4,"OWX_ASYNC_RunTasks: $current->{device} task timed out");
  997. Log3 ($master->{NAME},5,sprintf("OWX_ASYNC_RunTasks: TimeoutTime: %.6f, now: %.6f",$task->{TimeoutTime},$now)) if ($owx_async_debug>1);
  998. $task->PT_CANCEL("Timeout");
  999. shift @{$current->{queue}};
  1000. $main::defs{$current->{device}}->{NUMTASKS} = @{$current->{queue}};
  1001. $master->{TIMEOUTS}++;
  1002. if ($master->{TIMEOUTS} > AttrVal($master->{NAME},"maxtimeouts",5)) {
  1003. Log3 ($master->{NAME},3,"OWX_ASYNC_RunTasks: $master->{NAME} maximum number of timeouts exceedet ($master->{TIMEOUTS}), trying to reconnect");
  1004. OWX_ASYNC_Disconnect($master);
  1005. $master->{TIMEOUTS} = 0;
  1006. }
  1007. next;
  1008. } else {
  1009. Log3 $master->{NAME},5,"OWX_ASYNC_RunTasks: $current->{device} task waiting for data or timeout" if ($owx_async_debug>2);
  1010. #new timeout or timeout did change:
  1011. if (!defined $timeout or $timeout != $task->{TimeoutTime}) {
  1012. Log3 $master->{NAME},5,sprintf("OWX_ASYNC_RunTasks: $current->{device} task schedule for timeout at %.6f",$task->{TimeoutTime}) if ($owx_async_debug>1);
  1013. InternalTimer($task->{TimeoutTime}, "OWX_ASYNC_RunTasks", $master,0);
  1014. }
  1015. last;
  1016. }
  1017. } else {
  1018. Log3 ($master->{NAME},4,"$current->{device} unexpected thread state PT_WAITING without TimeoutTime");
  1019. $task->{TimeoutTime} = $now + 2; #TODO implement attribute based timeout
  1020. }
  1021. # sleeping:
  1022. } elsif ($state == PT_YIELDED) {
  1023. next;
  1024. } else {
  1025. Log3 ($master->{NAME},4,"$current->{device} unexpected thread state while running: $state");
  1026. }
  1027. } else {
  1028. my $state = $task->PT_STATE();
  1029. if ($state == PT_ENDED) {
  1030. Log3 ($master->{NAME},5,"OWX_ASYNC_RunTasks: $current->{device} task finished");
  1031. $master->{TIMEOUTS} = 0;
  1032. } elsif ($state == PT_EXITED) {
  1033. Log3 ($master->{NAME},4,"OWX_ASYNC_RunTasks: $current->{device} task exited: ".(defined $task->PT_RETVAL() ? $task->PT_RETVAL : "- no retval -"));
  1034. } elsif ($state == PT_ERROR) {
  1035. Log3 ($master->{NAME},4,"OWX_ASYNC_RunTasks: $current->{device} task Error: ".$task->PT_CAUSE());
  1036. $main::defs{$current->{device}}->{PRESENT} = 0;
  1037. } else {
  1038. Log3 ($master->{NAME},4,"$current->{device} unexpected thread state after termination: $state");
  1039. }
  1040. shift @{$current->{queue}};
  1041. $main::defs{$current->{device}}->{NUMTASKS} = @{$current->{queue}};
  1042. next;
  1043. }
  1044. } else {
  1045. my $nexttime;
  1046. my $nextdevice;
  1047. foreach my $current (@queue_sleeping) {
  1048. # if task is scheduled for future:
  1049. if (!defined $nexttime or ($nexttime > $current->{queue}->[0]->{ExecuteTime})) {
  1050. $nexttime = $current->{queue}->[0]->{ExecuteTime};
  1051. $nextdevice = $current->{device};
  1052. }
  1053. }
  1054. if (defined $nexttime) {
  1055. if ($nexttime > $now) {
  1056. if (!defined $master->{".nexttasktime"} or $nexttime < $master->{".nexttasktime"} or $now >= $master->{".nexttasktime"}) {
  1057. Log3 $master->{NAME},5,sprintf("OWX_ASYNC_RunTasks: $nextdevice schedule next at %.6f",$nexttime) if ($owx_async_debug);
  1058. main::InternalTimer($nexttime, "OWX_ASYNC_RunTasks", $master,0);
  1059. $master->{".nexttasktime"} = $nexttime;
  1060. } else {
  1061. Log3 $master->{NAME},5,sprintf("OWX_ASYNC_RunTasks: $nextdevice skip %.6f, allready scheduled at %.6f",$nexttime,$master->{".nexttasktime"}) if ($owx_async_debug>2);
  1062. }
  1063. } else {
  1064. Log3 $master->{NAME},5,sprintf("OWX_ASYNC_RunTasks: $nextdevice nexttime at %.6f allready passed",$nexttime) if ($owx_async_debug>2);
  1065. }
  1066. } else {
  1067. Log3 $master->{NAME},5,sprintf("OWX_ASYNC_RunTasks: -undefined- no nexttime") if ($owx_async_debug>2);
  1068. }
  1069. Log3 $master->{NAME},5,sprintf("OWX_ASYNC_RunTasks: -undefined- exit loop") if ($owx_async_debug>2);
  1070. last;
  1071. }
  1072. };
  1073. }
  1074. };
  1075. 1;
  1076. =pod
  1077. =begin html
  1078. <a name="OWX_ASYNC"></a>
  1079. <h3>OWX_ASYNC</h3>
  1080. <p> FHEM module to commmunicate with 1-Wire bus devices</p>
  1081. <ul>
  1082. <li>via an active DS2480 bus master interface attached to an USB port or </li>
  1083. <li>via an Arduino running ConfigurableFirmata attached to USB</li>
  1084. <li>via an Arduino running ConfigurableFirmata connecting to FHEM via Ethernet</li>
  1085. </ul>
  1086. <p>Internally these interfaces are vastly different, read the corresponding <a
  1087. href="http://fhemwiki.de/wiki/Interfaces_f%C3%BCr_1-Wire"> Wiki pages </a></p>
  1088. <p>OWX_ASYNC does pretty much the same job as <a href="#OWX">OWX</a> does, but using
  1089. an asynchronous mode of communication</p>
  1090. <br />
  1091. <br />
  1092. <h4>Example</h4><br />
  1093. <p>
  1094. <code>define OWio1 OWX_ASYNC /dev/ttyUSB1</code>
  1095. <br />
  1096. <code>define OWio3 OWX_ASYNC 10</code>
  1097. <br />
  1098. </p>
  1099. <br />
  1100. <a name="OWX_ASYNCdefine"></a>
  1101. <h4>Define</h4>
  1102. <p>
  1103. <code>define &lt;name&gt; OWX_ASYNC &lt;serial-device&gt;</code> or <br />
  1104. <code>define &lt;name&gt; OWX_ASYNC &lt;cuno/coc-device&gt;</code> or <br />
  1105. <code>define &lt;name&gt; OWX_ASYNC &lt;arduino-pin&gt;</code>
  1106. <br /><br /> Define a 1-Wire interface to communicate with a 1-Wire bus.<br />
  1107. <br />
  1108. </p>
  1109. <ul>
  1110. <li>
  1111. <code>&lt;serial-device&gt;</code> The serial device (e.g. USB port) to which the
  1112. 1-Wire bus is attached.</li>
  1113. <li>
  1114. <code>&lt;cuno-device&gt;</code> The previously defined CUNO to which the 1-Wire bus
  1115. is attached. </li>
  1116. <li>
  1117. <code>&lt;arduino-pin&gt;</code> The pin of the previous defined <a href="#FRM">FRM</a>
  1118. to which the 1-Wire bus is attached. If there is more than one FRM device defined
  1119. use <a href="#IODev">IODev</a> attribute to select which FRM device to use.</li>
  1120. </ul>
  1121. <br />
  1122. <a name="OWX_ASYNCset"></a>
  1123. <h4>Set</h4>
  1124. <ul>
  1125. <li><a name="owx_async_interval">
  1126. <code>set &lt;name&gt; interval &lt;value&gt;</code>
  1127. </a>
  1128. <br />sets the time period in seconds for "kicking" the 1-Wire bus when the <a href="#OWX_ASYNCdokick">dokick attribute</a> is set (default
  1129. is 300 seconds).
  1130. </li>
  1131. <li><a name="owx_async_followAlarms">
  1132. <code>set &lt;name&gt; followAlarms on|off</code>
  1133. </a>
  1134. <br /><br /> instructs the module to start an alarm search in case a reset pulse
  1135. discovers any 1-Wire device which has the alarm flag set. </li>
  1136. </ul>
  1137. <br />
  1138. <a name="OWX_ASYNCget"></a>
  1139. <h4>Get</h4>
  1140. <ul>
  1141. <li><a name="owx_async_alarms"></a>
  1142. <code>get &lt;name&gt; alarms</code>
  1143. <br /><br /> performs an "alarm search" for devices on the 1-Wire bus and, if found,
  1144. generates an event in the log (not with CUNO). </li>
  1145. <li><a name="owx_async_devices"></a>
  1146. <code>get &lt;name&gt; devices</code>
  1147. <br /><br /> redicovers all devices on the 1-Wire bus. If a device found has a
  1148. previous definition, this is automatically used. If a device is found but has no
  1149. definition, it is autocreated. If a defined device is not on the 1-Wire bus, it is
  1150. autodeleted. </li>
  1151. </ul>
  1152. <br />
  1153. <a name="OWX_ASYNCattr"></a>
  1154. <h4>Attributes</h4>
  1155. <ul>
  1156. <li><a name="OWX_ASYNCdokick"><code>attr &lt;name&gt; dokick 0|1</code></a>
  1157. <br />1 if the interface regularly kicks thermometers on the bus to do a temperature conversion,
  1158. and to perform an alarm check, 0 if not</li>
  1159. <li><a name="OWX_ASYNCbuspower"><code>attr &lt;name&gt; buspower real|parasitic</code></a>
  1160. <br />parasitic if there are any devices on the bus that steal power from the data line.
  1161. <br />Ensures that never more than a single device on the bus is talked to (throughput is throttled noticable!)
  1162. <br />Automatically disables attribute 'dokick'.</li>
  1163. <li><a name="OWX_ASYNCIODev"><code>attr &lt;name&gt; IODev &lt;FRM-device&gt;</code></a>
  1164. <br />assignes a specific FRM-device to OWX_ASYNC when working through an Arduino.
  1165. <br />Required only if there is more than one FRM defined.</li>
  1166. <li><a name="OWX_ASYNCmaxtimeouts"><code>attr &lt;name&gt; maxtimeouts &lt;number&gt;</code></a>
  1167. <br />maximum number of timeouts (in a row) before OWX_ASYNC disconnects itself from the
  1168. busmaster and tries to establish a new connection</li>
  1169. <li>Standard attributes <a href="#alias">alias</a>, <a href="#comment">comment</a>, <a
  1170. href="#event-on-update-reading">event-on-update-reading</a>, <a
  1171. href="#event-on-change-reading">event-on-change-reading</a>, <a href="#room"
  1172. >room</a>, <a href="#eventMap">eventMap</a>,
  1173. <a href="#webCmd">webCmd</a></li>
  1174. </ul>
  1175. =end html
  1176. =cut