21_OWTEMP.pm.fork 14 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532
  1. ################################################################
  2. #
  3. # Copyright notice
  4. #
  5. # (c) 2009 Copyright: Martin Fischer (m_fischer at gmx dot de)
  6. # All rights reserved
  7. #
  8. # This script free software; you can redistribute it and/or modify
  9. # it under the terms of the GNU General Public License as published by
  10. # the Free Software Foundation; either version 2 of the License, or
  11. # (at your option) any later version.
  12. #
  13. # The GNU General Public License can be found at
  14. # http://www.gnu.org/copyleft/gpl.html.
  15. # A copy is found in the textfile GPL.txt and important notices to the license
  16. # from the author is found in LICENSE.txt distributed with these scripts.
  17. #
  18. # This script is distributed in the hope that it will be useful,
  19. # but WITHOUT ANY WARRANTY; without even the implied warranty of
  20. # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
  21. # GNU General Public License for more details.
  22. #
  23. ################################################################
  24. package main;
  25. use strict;
  26. use warnings;
  27. use Time::HiRes qw(gettimeofday);
  28. use OW;
  29. my %gets = (
  30. "address" => "",
  31. "alias" => "",
  32. "crc8" => "",
  33. "family" => "10",
  34. "id" => "",
  35. "locator" => "",
  36. "power" => "",
  37. "present" => "",
  38. # "r_address" => "",
  39. # "r_id" => "",
  40. # "r_locator" => "",
  41. "temperature" => "",
  42. "temphigh" => "",
  43. "templow" => "",
  44. "type" => "",
  45. );
  46. my %sets = (
  47. "alias" => "",
  48. "temphigh" => "",
  49. "templow" => "",
  50. "interval" => "",
  51. "alarminterval" => "",
  52. );
  53. my %updates = (
  54. "present" => "",
  55. "temperature" => "",
  56. "templow" => "",
  57. "temphigh" => "",
  58. );
  59. my %dummy = (
  60. "crc8" => "4D",
  61. "alias" => "dummy",
  62. "locator" => "FFFFFFFFFFFFFFFF",
  63. "power" => "0",
  64. "present" => "1",
  65. "temphigh" => "75",
  66. "templow" => "10",
  67. "type" => "DS18S20",
  68. "warnings" => "none",
  69. );
  70. #####################################
  71. sub
  72. OWTEMP_Initialize($)
  73. {
  74. my ($hash) = @_;
  75. $hash->{DefFn} = "OWTEMP_Define";
  76. $hash->{UndefFn} = "OWTEMP_Undef";
  77. $hash->{GetFn} = "OWTEMP_Get";
  78. $hash->{SetFn} = "OWTEMP_Set";
  79. $hash->{AttrList}= "IODev do_not_notify:0,1 showtime:0,1 model:DS18S20 loglevel:0,1,2,3,4,5";
  80. }
  81. #####################################
  82. sub
  83. OWTEMP_UpdateReading($$$$)
  84. {
  85. my ($hash,$reading,$now,$value) = @_;
  86. # define vars
  87. my $temp;
  88. # exit if empty value
  89. return 0
  90. if(!defined($value) || $value eq "");
  91. # trim value
  92. $value =~ s/\s//g
  93. if($reading ne "warnings");
  94. if($reading eq "temperature") {
  95. $value = sprintf("%.4f",$value);
  96. $temp = $value;
  97. $value = $value . " (".$hash->{OW_SCALE}.")";
  98. }
  99. # update readings
  100. $hash->{READINGS}{$reading}{TIME} = $now;
  101. $hash->{READINGS}{$reading}{VAL} = $value;
  102. Log 4, "OWTEMP $hash->{NAME} $reading: $value";
  103. return $value;
  104. }
  105. #####################################
  106. sub
  107. OWTEMP_GetUpdate($$)
  108. {
  109. my ($hash, $a) = @_;
  110. # define vars
  111. my $name = $hash->{NAME};
  112. my $now = TimeNow();
  113. my $value = "";
  114. my $temp = "";
  115. my $ret = "";
  116. my $count = 0;
  117. # define warnings
  118. my $warn = "none";
  119. $hash->{ALARM} = "0";
  120. # check for real sensor
  121. if($hash->{OW_ID} ne "none") {
  122. # real sensor
  123. if(!$hash->{LOCAL} || $a eq "") {
  124. #####################
  125. # OW::Get is too slow: do it in the background by fork. After receiving
  126. # the data from the OW module, the child contacts the parent, and calls
  127. # "set <NAME> childupdate <data>", which in turn will call this function
  128. # again with a filled CHILDDATA
  129. if(!$hash->{CHILDDATA}) {
  130. if($hash->{CHILDPID}) {
  131. Log 2, "OWTEMP: Child already forked: timeout too short?";
  132. return;
  133. }
  134. return if(($hash->{CHILDPID} = fork));
  135. my @ret;
  136. foreach my $r (sort keys %updates) {
  137. my $ret = OW::get("/uncached/".$hash->{OW_PATH}."/".$r);
  138. $ret = "" if(!defined($ret));
  139. push(@ret, $ret);
  140. last if($ret eq "");
  141. }
  142. my @port = split(" ", $attr{global}{port});
  143. my $server = IO::Socket::INET->new(PeerAddr => "localhost:$port[0]");
  144. Log 0, "OWTEMP: Can't connect to parent\n" if(!$server);
  145. syswrite($server, "set $hash->{NAME} childupdate ".join(":",@ret)."\n");
  146. exit(0);
  147. } else {
  148. #####################
  149. # Digest the data sent by the CHILD.
  150. my @ret = split(":", $hash->{CHILDDATA});
  151. delete($hash->{CHILDPID});
  152. delete($hash->{CHILDDATA});
  153. foreach my $r (sort keys %updates) {
  154. $ret = shift(@ret);
  155. if($ret eq "") {
  156. #
  157. $hash->{PRESENT} = "0";
  158. $r = "present";
  159. $value = "0";
  160. $ret = OWTEMP_UpdateReading($hash,$r,$now,$value);
  161. $hash->{CHANGED}[$count] = "present: ".$value
  162. } else {
  163. $hash->{PRESENT} = "1";
  164. $value = $ret;
  165. if($r eq "temperature") {
  166. $temp = sprintf("%.4f",$value);
  167. $temp =~ s/\s//g;
  168. }
  169. $ret = OWTEMP_UpdateReading($hash,$r,$now,$value);
  170. }
  171. last if($hash->{PRESENT} eq "0");
  172. }
  173. }
  174. } else {
  175. $ret = "";
  176. $ret = OW::get("/uncached/".$hash->{OW_PATH}."/".$a);
  177. if(!defined($ret)) {
  178. $hash->{PRESENT} = "0";
  179. $a = "present";
  180. $value = "0";
  181. $ret = OWTEMP_UpdateReading($hash,$a,$now,$value);
  182. } else {
  183. $hash->{PRESENT} = "1";
  184. $value = $ret;
  185. if($a eq "temperature") {
  186. $temp = sprintf("%.4f",$value);
  187. $temp =~ s/\s//g;
  188. $value = $temp;
  189. }
  190. $ret = OWTEMP_UpdateReading($hash,$a,$now,$value);
  191. }
  192. }
  193. } else {
  194. # dummy sensor
  195. $temp = sprintf("%.4f",rand(85));
  196. $dummy{temperature} = $temp;
  197. $dummy{present} = "1";
  198. $hash->{PRESENT} = $dummy{present};
  199. if(!$hash->{LOCAL} || $a eq "") {
  200. foreach my $r (sort keys %updates) {
  201. $ret = OWTEMP_UpdateReading($hash,$r,$now,$dummy{$r});
  202. }
  203. } else {
  204. $ret = "";
  205. $ret = $dummy{$a};
  206. if($ret ne "") {
  207. $value = $ret;
  208. if($a eq "temperature") {
  209. $temp = sprintf("%.4f",$value);
  210. $temp =~ s/\s//g;
  211. }
  212. $ret = OWTEMP_UpdateReading($hash,$a,$now,$value);
  213. }
  214. }
  215. }
  216. return 1
  217. if($hash->{LOCAL} && $a eq "" && $hash->{PRESENT} eq "0");
  218. # check for warnings
  219. my $templow = $hash->{READINGS}{templow}{VAL};
  220. my $temphigh = $hash->{READINGS}{temphigh}{VAL};
  221. if($hash->{PRESENT} eq "1") {
  222. if($temp <= $templow) {
  223. # low temperature
  224. $hash->{ALARM} = "1";
  225. $warn = "templow";
  226. } elsif($temp >= $temphigh) {
  227. # high temperature
  228. $hash->{ALARM} = "1";
  229. $warn = "temphigh";
  230. }
  231. } else {
  232. # set old state
  233. $temp = $hash->{READINGS}{temperature}{VAL};
  234. ($temp,undef) = split(" ",$temp);
  235. # sensor is missing
  236. $hash->{ALARM} = "1";
  237. $warn = "not present";
  238. }
  239. if(!$hash->{LOCAL} || $a eq "") {
  240. $ret = OWTEMP_UpdateReading($hash,"warnings",$now,$warn);
  241. }
  242. $hash->{STATE} = "T: ".$temp." ".
  243. "L: ".$templow." ".
  244. "H: ".$temphigh." ".
  245. "P: ".$hash->{PRESENT}." ".
  246. "A: ".$hash->{ALARM}." ".
  247. "W: ".$warn;
  248. # inform changes
  249. # state
  250. $hash->{CHANGED}[$count++] = $hash->{STATE};
  251. # present
  252. $hash->{CHANGED}[$count++] = "present: ".$hash->{PRESENT}
  253. if(defined($hash->{PRESENT}) && $hash->{PRESENT} ne "");
  254. # temperature
  255. $hash->{CHANGED}[$count++] = "temperature: ".$temp." (".$hash->{OW_SCALE}.")"
  256. if(defined($temp) && $temp ne "");
  257. # temperature raw
  258. $hash->{CHANGED}[$count++] = "tempraw: ".$temp
  259. if(defined($temp) && $temp ne "");
  260. # low temperature
  261. $hash->{CHANGED}[$count++] = "templow: ".$templow
  262. if(defined($templow) && $templow ne "");
  263. # high temperature
  264. $hash->{CHANGED}[$count++] = "temphigh: ".$temphigh
  265. if(defined($temphigh) && $temphigh ne "");
  266. # warnings
  267. $hash->{CHANGED}[$count++] = "warnings: ".$warn
  268. if(defined($warn) && $warn ne "");
  269. if(!$hash->{LOCAL}) {
  270. # update timer
  271. RemoveInternalTimer($hash);
  272. # check alarm
  273. if($hash->{ALARM} eq "0") {
  274. $hash->{INTERVAL} = $hash->{INTV_CHECK};
  275. } else {
  276. $hash->{INTERVAL} = $hash->{INTV_ALARM};
  277. }
  278. InternalTimer(gettimeofday()+$hash->{INTERVAL}, "OWTEMP_GetUpdate", $hash, 1);
  279. } else {
  280. return $value;
  281. }
  282. if(!$hash->{LOCAL}) {
  283. DoTrigger($name, undef) if($init_done);
  284. }
  285. return $hash->{STATE};
  286. }
  287. #####################################
  288. sub
  289. OWTEMP_Get($@)
  290. {
  291. my ($hash, @a) = @_;
  292. # check syntax
  293. return "argument is missing @a"
  294. if(int(@a) != 2);
  295. # check argument
  296. return "Unknown argument $a[1], choose one of ".join(",", sort keys %gets)
  297. if(!defined($gets{$a[1]}));
  298. # define vars
  299. my $value;
  300. # get value
  301. $hash->{LOCAL} = 1;
  302. $value = OWTEMP_GetUpdate($hash,$a[1]);
  303. delete $hash->{LOCAL};
  304. my $reading = $a[1];
  305. if(defined($hash->{READINGS}{$reading})) {
  306. $value = $hash->{READINGS}{$reading}{VAL};
  307. }
  308. return "$a[0] $reading => $value";
  309. }
  310. #####################################
  311. sub
  312. OWTEMP_Set($@)
  313. {
  314. my ($hash, @a) = @_;
  315. # check syntax
  316. return "set needs one parameter"
  317. if(int(@a) != 3);
  318. # check arguments
  319. return "Unknown argument $a[1], choose one of ".join(",", sort keys %sets)
  320. if(!defined($sets{$a[1]}) && $a[1] ne "childupdate");
  321. # define vars
  322. my $key = $a[1];
  323. my $value = $a[2];
  324. my $ret;
  325. if($key eq "childupdate") {
  326. $hash->{CHILDDATA} = $value;
  327. OWTEMP_GetUpdate($hash,undef);
  328. return undef;
  329. }
  330. # set new timer
  331. if($key eq "interval" || $key eq "alarminterval") {
  332. $key = "INTV_CHECK"
  333. if($key eq "interval");
  334. $key = "INTV_ALARM"
  335. if($key eq "alarminterval");
  336. # update timer
  337. $hash->{$key} = $value;
  338. RemoveInternalTimer($hash);
  339. # check alarm
  340. if($hash->{ALARM} eq "0") {
  341. $hash->{INTERVAL} = $hash->{INTV_CHECK};
  342. } else {
  343. $hash->{INTERVAL} = $hash->{INTV_ALARM};
  344. }
  345. InternalTimer(gettimeofday()+$hash->{INTERVAL}, "OWTEMP_GetUpdate", $hash, 1);
  346. }
  347. # set warnings
  348. if($key eq "templow" || $key eq "temphigh") {
  349. # check range
  350. return "wrong value: range -55°C - 125°C"
  351. if(int($value) < -55 || int($value) > 125);
  352. }
  353. # set value
  354. Log 4, "OWTEMP set $hash->{NAME} $key $value";
  355. # check for real sensor
  356. if($hash->{OW_ID} ne "none") {
  357. # real senson
  358. $ret = OW::put($hash->{OW_PATH}."/$key",$value);
  359. } else {
  360. # dummy sensor
  361. $dummy{$key} = $value;
  362. }
  363. # update readings
  364. if($key ne "interval" || $key ne "alarminterval") {
  365. $hash->{LOCAL} = 1;
  366. $ret = OWTEMP_GetUpdate($hash,$key);
  367. delete $hash->{LOCAL};
  368. }
  369. return undef;
  370. }
  371. #####################################
  372. sub
  373. OWTEMP_Define($$)
  374. {
  375. my ($hash, $def) = @_;
  376. # define <name> OWTEMP <id> [interval] [alarminterval]
  377. # e.g.: define flow OWTEMP 332670010800 300
  378. my @a = split("[ \t][ \t]*", $def);
  379. # check syntax
  380. return "wrong syntax: define <name> OWTEMP <id> [interval] [alarminterval]"
  381. if(int(@a) < 2 && int(@a) > 5);
  382. # check ID format
  383. return "Define $a[0]: missing ID or wrong ID format: specify a 12 digit value or set it to none for demo mode"
  384. if(lc($a[2]) ne "none" && lc($a[2]) !~ m/^[0-9|a-f]{12}$/);
  385. # define vars
  386. my $name = $a[0];
  387. my $id = $a[2];
  388. my $interval = 300;
  389. my $alarminterval = 300;
  390. my $scale = "";
  391. my $ret = "";
  392. # overwrite default intervals if set by define
  393. if(int(@a)==4) { $interval = $a[3]; }
  394. if(int(@a)==5) { $interval = $a[3]; $alarminterval = $a[4] }
  395. # define device internals
  396. $hash->{ALARM} = 0;
  397. $hash->{INTERVAL} = $interval;
  398. $hash->{INTV_CHECK} = $interval;
  399. $hash->{INTV_ALARM} = $alarminterval;
  400. $hash->{OW_ID} = $id;
  401. $hash->{OW_FAMILY} = $gets{family};
  402. $hash->{OW_PATH} = $hash->{OW_FAMILY}.".".$hash->{OW_ID};
  403. $hash->{PRESENT} = 0;
  404. $modules{OWTEMP}{defptr}{$a[2]} = $hash;
  405. # assign IO port
  406. AssignIoPort($hash);
  407. return "No I/O device found. Please define a OWFS device first."
  408. if(!defined($hash->{IODev}->{NAME}));
  409. # get scale from I/O device
  410. $scale = $attr{$hash->{IODev}->{NAME}}{"temp-scale"};
  411. # define scale for temperature values
  412. $scale = "Celsius" if ($scale eq "C");
  413. $scale = "Fahrenheit" if ($scale eq "F");
  414. $scale = "Kelvin" if ($scale eq "K");
  415. $scale = "Rankine" if ($scale eq "R");
  416. $hash->{OW_SCALE} = $scale;
  417. $hash->{STATE} = "Defined";
  418. # define dummy values for testing
  419. if($hash->{OW_ID} eq "none") {
  420. my $now = TimeNow();
  421. $dummy{address} = $hash->{OW_FAMILY}.$hash->{OW_ID}.$dummy{crc8};
  422. $dummy{family} = $hash->{OW_FAMILY};
  423. $dummy{id} = $hash->{OW_ID};
  424. $dummy{temperature} = "80.0000 (".$hash->{OW_SCALE}.")";
  425. foreach my $r (sort keys %gets) {
  426. $hash->{READINGS}{$r}{TIME} = $now;
  427. $hash->{READINGS}{$r}{VAL} = $dummy{$r};
  428. Log 4, "OWTEMP $hash->{NAME} $r: ".$dummy{$r};
  429. }
  430. }
  431. $hash->{STATE} = "Initialized";
  432. # initalize
  433. $hash->{LOCAL} = 1;
  434. $ret = OWTEMP_GetUpdate($hash,"");
  435. delete $hash->{LOCAL};
  436. # exit if sensor is not present
  437. return "Define $hash->{NAME}: Sensor is not reachable. Check first your 1-wire connection."
  438. if(defined($ret) && $ret eq 1);
  439. if(!$hash->{LOCAL}) {
  440. if($hash->{ALARM} eq "0") {
  441. $hash->{INTERVAL} = $hash->{INTV_CHECK};
  442. } else {
  443. $hash->{INTERVAL} = $hash->{INTV_ALARM};
  444. }
  445. InternalTimer(gettimeofday()+$hash->{INTERVAL}, "OWTEMP_GetUpdate", $hash, 0);
  446. }
  447. return undef;
  448. }
  449. #####################################
  450. sub
  451. OWTEMP_Undef($$)
  452. {
  453. my ($hash, $name) = @_;
  454. delete($modules{OWTEMP}{defptr}{$hash->{NAME}});
  455. RemoveInternalTimer($hash);
  456. return undef;
  457. }
  458. 1;