74_XiaomiFlowerSens.pm 31 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888
  1. ###############################################################################
  2. #
  3. # Developed with Kate
  4. #
  5. # (c) 2016-2017 Copyright: Marko Oldenburg (leongaultier at gmail dot com)
  6. # All rights reserved
  7. #
  8. # This script is 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. # 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. # $Id: 74_XiaomiFlowerSens.pm 15805 2018-01-06 20:11:24Z CoolTux $
  25. #
  26. ###############################################################################
  27. #$cmd = "qx(gatttool -i $hci -b $mac --char-write-req -a 0x33 -n A01F";
  28. #$cmd = "qx(gatttool -i $hci -b $mac --char-read -a 0x35"; # Sensor Daten
  29. #$cmd = "qx(gatttool -i $hci -b $mac --char-read -a 0x38"; # Firmware und Batterie
  30. # e8 00 00 58 0f 00 00 34 f1 02 02 3c 00 fb 34 9b
  31. package main;
  32. use strict;
  33. use warnings;
  34. use POSIX;
  35. use JSON;
  36. use Blocking;
  37. my $version = "1.4.1";
  38. my %CallBatteryFirmwareAge = ( '8h' => 28800,
  39. '16h' => 57600,
  40. '24h' => 86400,
  41. '32h' => 115200,
  42. '40h' => 144000,
  43. '48h' => 172800
  44. );
  45. # Declare functions
  46. sub XiaomiFlowerSens_Initialize($);
  47. sub XiaomiFlowerSens_Define($$);
  48. sub XiaomiFlowerSens_Undef($$);
  49. sub XiaomiFlowerSens_Attr(@);
  50. sub XiaomiFlowerSens_stateRequest($);
  51. sub XiaomiFlowerSens_stateRequestTimer($);
  52. sub XiaomiFlowerSens_Set($$@);
  53. sub XiaomiFlowerSens_Get($$@);
  54. sub XiaomiFlowerSens_Notify($$);
  55. sub XiaomiFlowerSens_CallBatteryFirmware($);
  56. sub XiaomiFlowerSens_CallSensData($);
  57. sub XiaomiFlowerSens_WriteSensData($);
  58. sub XiaomiFlowerSens_ExecGatttool_Run($);
  59. sub XiaomiFlowerSens_ExecGatttool_Done($);
  60. sub XiaomiFlowerSens_ExecGatttool_Aborted($);
  61. sub XiaomiFlowerSens_ProcessingNotification($@);
  62. sub XiaomiFlowerSens_WriteReadings($$);
  63. sub XiaomiFlowerSens_ProcessingErrors($$);
  64. sub XiaomiFlowerSens_CallBatteryFirmware_IsUpdateTimeAgeToOld($$);
  65. sub XiaomiFlowerSens_CallBatteryFirmware_Timestamp($);
  66. sub XiaomiFlowerSens_CallBatteryFirmware_UpdateTimeAge($);
  67. sub XiaomiFlowerSens_encodeJSON($);
  68. sub XiaomiFlowerSens_Handle0x35($$);
  69. sub XiaomiFlowerSens_Handle0x38($$);
  70. sub XiaomiFlowerSens_Initialize($) {
  71. my ($hash) = @_;
  72. $hash->{SetFn} = "XiaomiFlowerSens_Set";
  73. $hash->{GetFn} = "XiaomiFlowerSens_Get";
  74. $hash->{DefFn} = "XiaomiFlowerSens_Define";
  75. $hash->{NotifyFn} = "XiaomiFlowerSens_Notify";
  76. $hash->{UndefFn} = "XiaomiFlowerSens_Undef";
  77. $hash->{AttrFn} = "XiaomiFlowerSens_Attr";
  78. $hash->{AttrList} = "interval ".
  79. "disable:1 ".
  80. "disabledForIntervals ".
  81. "hciDevice:hci0,hci1,hci2 ".
  82. "batteryFirmwareAge:8h,16h,24h,32h,40h,48h ".
  83. "minFertility ".
  84. "maxFertility ".
  85. "minTemp ".
  86. "maxTemp ".
  87. "minMoisture ".
  88. "maxMoisture ".
  89. "minLux ".
  90. "maxLux ".
  91. "sshHost ".
  92. "blockingCallLoglevel:2,3,4,5 ".
  93. $readingFnAttributes;
  94. foreach my $d(sort keys %{$modules{XiaomiFlowerSens}{defptr}}) {
  95. my $hash = $modules{XiaomiFlowerSens}{defptr}{$d};
  96. $hash->{VERSION} = $version;
  97. }
  98. }
  99. sub XiaomiFlowerSens_Define($$) {
  100. my ( $hash, $def ) = @_;
  101. my @a = split( "[ \t][ \t]*", $def );
  102. return "too few parameters: define <name> XiaomiFlowerSens <BTMAC>" if( @a != 3 );
  103. my $name = $a[0];
  104. my $mac = $a[2];
  105. $hash->{BTMAC} = $mac;
  106. $hash->{VERSION} = $version;
  107. $hash->{INTERVAL} = 300;
  108. $hash->{helper}{CallSensDataCounter} = 0;
  109. $hash->{helper}{CallBatteryFirmware} = 0;
  110. $hash->{NOTIFYDEV} = "global";
  111. $hash->{loglevel} = 4;
  112. readingsSingleUpdate($hash,"state","initialized", 0);
  113. $attr{$name}{room} = "FlowerSens" if( !defined($attr{$name}{room}) );
  114. Log3 $name, 3, "XiaomiFlowerSens ($name) - defined with BTMAC $hash->{BTMAC}";
  115. $modules{XiaomiFlowerSens}{defptr}{$hash->{BTMAC}} = $hash;
  116. return undef;
  117. }
  118. sub XiaomiFlowerSens_Undef($$) {
  119. my ( $hash, $arg ) = @_;
  120. my $mac = $hash->{BTMAC};
  121. my $name = $hash->{NAME};
  122. RemoveInternalTimer($hash);
  123. BlockingKill($hash->{helper}{RUNNING_PID}) if(defined($hash->{helper}{RUNNING_PID}));
  124. delete($modules{XiaomiFlowerSens}{defptr}{$mac});
  125. Log3 $name, 3, "Sub XiaomiFlowerSens_Undef ($name) - delete device $name";
  126. return undef;
  127. }
  128. sub XiaomiFlowerSens_Attr(@) {
  129. my ( $cmd, $name, $attrName, $attrVal ) = @_;
  130. my $hash = $defs{$name};
  131. if( $attrName eq "disable" ) {
  132. if( $cmd eq "set" and $attrVal eq "1" ) {
  133. RemoveInternalTimer($hash);
  134. readingsSingleUpdate ( $hash, "state", "disabled", 1 );
  135. Log3 $name, 3, "XiaomiFlowerSens ($name) - disabled";
  136. }
  137. elsif( $cmd eq "del" ) {
  138. Log3 $name, 3, "XiaomiFlowerSens ($name) - enabled";
  139. }
  140. }
  141. elsif( $attrName eq "disabledForIntervals" ) {
  142. if( $cmd eq "set" ) {
  143. return "check disabledForIntervals Syntax HH:MM-HH:MM or 'HH:MM-HH:MM HH:MM-HH:MM ...'"
  144. unless($attrVal =~ /^((\d{2}:\d{2})-(\d{2}:\d{2})\s?)+$/);
  145. Log3 $name, 3, "XiaomiFlowerSens ($name) - disabledForIntervals";
  146. readingsSingleUpdate ( $hash, "state", "disabled", 1 );
  147. }
  148. elsif( $cmd eq "del" ) {
  149. Log3 $name, 3, "XiaomiFlowerSens ($name) - enabled";
  150. readingsSingleUpdate ( $hash, "state", "active", 1 );
  151. }
  152. }
  153. elsif( $attrName eq "interval" ) {
  154. if( $cmd eq "set" ) {
  155. if( $attrVal < 300 ) {
  156. Log3 $name, 3, "XiaomiFlowerSens ($name) - interval too small, please use something >= 300 (sec), default is 3600 (sec)";
  157. return "interval too small, please use something >= 300 (sec), default is 3600 (sec)";
  158. } else {
  159. $hash->{INTERVAL} = $attrVal;
  160. Log3 $name, 3, "XiaomiFlowerSens ($name) - set interval to $attrVal";
  161. }
  162. }
  163. elsif( $cmd eq "del" ) {
  164. $hash->{INTERVAL} = 300;
  165. Log3 $name, 3, "XiaomiFlowerSens ($name) - set interval to default";
  166. }
  167. }
  168. elsif( $attrName eq "blockingCallLoglevel" ) {
  169. if( $cmd eq "set" ) {
  170. $hash->{loglevel} = $attrVal;
  171. Log3 $name, 3, "XiaomiFlowerSens ($name) - set blockingCallLoglevel to $attrVal";
  172. }
  173. elsif( $cmd eq "del" ) {
  174. $hash->{loglevel} = 4;
  175. Log3 $name, 3, "XiaomiFlowerSens ($name) - set blockingCallLoglevel to default";
  176. }
  177. }
  178. return undef;
  179. }
  180. sub XiaomiFlowerSens_Notify($$) {
  181. my ($hash,$dev) = @_;
  182. my $name = $hash->{NAME};
  183. return if (IsDisabled($name));
  184. my $devname = $dev->{NAME};
  185. my $devtype = $dev->{TYPE};
  186. my $events = deviceEvents($dev,1);
  187. return if (!$events);
  188. XiaomiFlowerSens_stateRequestTimer($hash) if( (grep /^DEFINED.$name$/,@{$events}
  189. or grep /^INITIALIZED$/,@{$events}
  190. or grep /^MODIFIED.$name$/,@{$events}
  191. or grep /^DELETEATTR.$name.disable$/,@{$events}
  192. or grep /^ATTR.$name.disable.0$/,@{$events}
  193. or grep /^DELETEATTR.$name.interval$/,@{$events}
  194. or grep /^ATTR.$name.interval.[0-9]+/,@{$events} ) and $init_done );
  195. return;
  196. }
  197. sub XiaomiFlowerSens_stateRequest($) {
  198. my ($hash) = @_;
  199. my $name = $hash->{NAME};
  200. my %readings;
  201. if( !IsDisabled($name) ) {
  202. if( ReadingsVal($name,'firmware','none') ne 'none') {
  203. return XiaomiFlowerSens_CallBatteryFirmware($hash)
  204. if( XiaomiFlowerSens_CallBatteryFirmware_IsUpdateTimeAgeToOld($hash,$CallBatteryFirmwareAge{AttrVal($name,'BatteryFirmwareAge','24h')}) );
  205. if( ReadingsVal($name, 'firmware', '') eq '2.6.2') {
  206. XiaomiFlowerSens_CallSensData($hash);
  207. } else {
  208. if( $hash->{helper}{CallSensDataCounter} < 1 ) {
  209. XiaomiFlowerSens_WriteSensData($hash);
  210. $hash->{helper}{CallSensDataCounter} = $hash->{helper}{CallSensDataCounter} + 1;
  211. } else {
  212. $readings{'lastGattError'} = 'charWrite faild';
  213. XiaomiFlowerSens_WriteReadings($hash,\%readings);
  214. $hash->{helper}{CallSensDataCounter} = 0;
  215. return;
  216. }
  217. }
  218. } else {
  219. XiaomiFlowerSens_CallBatteryFirmware($hash);
  220. }
  221. readingsSingleUpdate($hash,"state","fetch sensor data",1);
  222. } else {
  223. readingsSingleUpdate($hash,"state","disabled",1);
  224. }
  225. }
  226. sub XiaomiFlowerSens_stateRequestTimer($) {
  227. my ($hash) = @_;
  228. my $name = $hash->{NAME};
  229. RemoveInternalTimer($hash);
  230. if( $init_done and not IsDisabled($name) ) {
  231. XiaomiFlowerSens_stateRequest($hash);
  232. } else {
  233. readingsSingleUpdate ( $hash, "state", "disabled", 1 );
  234. }
  235. InternalTimer( gettimeofday()+$hash->{INTERVAL}+int(rand(300)), "XiaomiFlowerSens_stateRequestTimer", $hash, 1 );
  236. Log3 $name, 4, "XiaomiFlowerSens ($name) - stateRequestTimer: Call Request Timer";
  237. }
  238. sub XiaomiFlowerSens_CallBatteryFirmware($) {
  239. my $hash = shift;
  240. my $name = $hash->{NAME};
  241. my $mac = $hash->{BTMAC};
  242. $hash->{helper}{RUNNING_PID} = BlockingCall("XiaomiFlowerSens_ExecGatttool_Run", $name."|".$mac."|read|0x38", "XiaomiFlowerSens_ExecGatttool_Done", 60, "XiaomiFlowerSens_ExecGatttool_Aborted", $hash) unless(exists($hash->{helper}{RUNNING_PID}));
  243. Log3 $name, 4, "XiaomiFlowerSens ($name) - CallBatteryFirmware: call function ExecGatttool_Run";
  244. }
  245. sub XiaomiFlowerSens_CallSensData($) {
  246. my $hash = shift;
  247. my $name = $hash->{NAME};
  248. my $mac = $hash->{BTMAC};
  249. $hash->{helper}{RUNNING_PID} = BlockingCall("XiaomiFlowerSens_ExecGatttool_Run", $name."|".$mac."|read|0x35", "XiaomiFlowerSens_ExecGatttool_Done", 60, "XiaomiFlowerSens_ExecGatttool_Aborted", $hash) unless(exists($hash->{helper}{RUNNING_PID}));
  250. Log3 $name, 4, "XiaomiFlowerSens ($name) - CallSensData: call function ExecGatttool_Run";
  251. }
  252. sub XiaomiFlowerSens_WriteSensData($) {
  253. my $hash = shift;
  254. my $name = $hash->{NAME};
  255. my $mac = $hash->{BTMAC};
  256. $hash->{helper}{RUNNING_PID} = BlockingCall("XiaomiFlowerSens_ExecGatttool_Run", $name."|".$mac."|write|0x33|A01F", "XiaomiFlowerSens_ExecGatttool_Done", 60, "XiaomiFlowerSens_ExecGatttool_Aborted", $hash) unless(exists($hash->{helper}{RUNNING_PID}));
  257. Log3 $name, 4, "XiaomiFlowerSens ($name) - WriteSensData: call function ExecGatttool_Run";
  258. }
  259. sub XiaomiFlowerSens_Set($$@) {
  260. my ($hash, $name, @aa) = @_;
  261. my ($cmd, @args) = @aa;
  262. if( $cmd eq 'clearFirmwareReading' ) {
  263. return "usage: clearFirmwareReading" if( @args != 0 );
  264. readingsSingleUpdate($hash,'firmware','',0);
  265. } else {
  266. my $list = "clearFirmwareReading:noArg";
  267. return "Unknown argument $cmd, choose one of $list";
  268. }
  269. return undef;
  270. }
  271. sub XiaomiFlowerSens_Get($$@) {
  272. my ($hash, $name, @aa) = @_;
  273. my ($cmd, @args) = @aa;
  274. if( $cmd eq 'statusRequest' ) {
  275. return "usage: statusRequest" if( @args != 0 );
  276. XiaomiFlowerSens_stateRequest($hash);
  277. } else {
  278. my $list = "statusRequest:noArg";
  279. return "Unknown argument $cmd, choose one of $list";
  280. }
  281. return undef;
  282. }
  283. sub XiaomiFlowerSens_ExecGatttool_Run($) {
  284. my $string = shift;
  285. my ($name,$mac,$gattCmd,$handle,$value) = split("\\|", $string);
  286. my $sshHost = AttrVal($name,"sshHost","none");
  287. my $gatttool;
  288. $gatttool = qx(which gatttool) if($sshHost eq 'none');
  289. $gatttool = qx(ssh $sshHost 'which gatttool') if($sshHost ne 'none');
  290. chomp $gatttool;
  291. if(-x $gatttool) {
  292. my $cmd;
  293. my $loop;
  294. my @gtResult;
  295. my $wait = 1;
  296. my $sshHost = AttrVal($name,"sshHost","none");
  297. my $hci = AttrVal($name,"hciDevice","hci0");
  298. while($wait) {
  299. my $grepGatttool;
  300. $grepGatttool = qx(ps ax| grep -E \'gatttool -i $hci -b $mac\' | grep -v grep) if($sshHost eq 'none');
  301. $grepGatttool = qx(ssh $sshHost 'ps ax| grep -E "gatttool -i $hci -b $mac" | grep -v grep') if($sshHost ne 'none');
  302. if(not $grepGatttool =~ /^\s*$/) {
  303. Log3 $name, 5, "XiaomiFlowerSens ($name) - ExecGatttool_Run: another gatttool process is running. waiting...";
  304. sleep(1);
  305. } else {
  306. $wait = 0;
  307. }
  308. }
  309. $cmd .= "ssh $sshHost '" if($sshHost ne 'none');
  310. $cmd .= "gatttool -i $hci -b $mac ";
  311. $cmd .= "--char-read -a $handle" if($gattCmd eq 'read');
  312. $cmd .= "--char-write-req -a $handle -n $value" if($gattCmd eq 'write');
  313. $cmd .= " 2>&1 /dev/null";
  314. $cmd .= "'" if($sshHost ne 'none');
  315. $cmd = "ssh $sshHost 'gatttool -i $hci -b $mac --char-write-req -a 0x33 -n A01F && gatttool -i $hci -b $mac --char-read -a 0x35 2>&1 /dev/null'" if($sshHost ne 'none' and $gattCmd eq 'write');
  316. $loop = 0;
  317. do {
  318. Log3 $name, 5, "XiaomiFlowerSens ($name) - ExecGatttool_Run: call gatttool with command $cmd and loop $loop";
  319. @gtResult = split(": ",qx($cmd));
  320. Log3 $name, 5, "XiaomiFlowerSens ($name) - ExecGatttool_Run: gatttool loop result ".join(",", @gtResult);
  321. $loop++;
  322. $gtResult[0] = 'connect error'
  323. unless( defined($gtResult[0]) );
  324. } while( $loop < 5 and $gtResult[0] eq 'connect error' );
  325. Log3 $name, 4, "XiaomiFlowerSens ($name) - ExecGatttool_Run: gatttool result ".join(",", @gtResult);
  326. $handle = '0x35' if($sshHost ne 'none' and $gattCmd eq 'write');
  327. $gattCmd = 'read' if($sshHost ne 'none' and $gattCmd eq 'write');
  328. $gtResult[1] = 'no data response'
  329. unless( defined($gtResult[1]) );
  330. my $json_notification = XiaomiFlowerSens_encodeJSON($gtResult[1]);
  331. if($gtResult[1] =~ /^([0-9a-f]{2}(\s?))*$/) {
  332. return "$name|$mac|ok|$gattCmd|$handle|$json_notification";
  333. } elsif($gtResult[0] ne 'connect error' and $gattCmd eq 'write') {
  334. if( $sshHost ne 'none' ) {
  335. XiaomiFlowerSens_ExecGatttool_Run($name."|".$mac."|read|0x35");
  336. } else {
  337. return "$name|$mac|ok|$gattCmd|$handle|$json_notification";
  338. }
  339. } else {
  340. return "$name|$mac|error|$gattCmd|$handle|$json_notification";
  341. }
  342. } else {
  343. return "$name|$mac|error|$gattCmd|$handle|no gatttool binary found. Please check if bluez-package is properly installed";
  344. }
  345. }
  346. sub XiaomiFlowerSens_ExecGatttool_Done($) {
  347. my $string = shift;
  348. my ($name,$mac,$respstate,$gattCmd,$handle,$json_notification) = split("\\|", $string);
  349. my $hash = $defs{$name};
  350. delete($hash->{helper}{RUNNING_PID});
  351. Log3 $name, 5, "XiaomiFlowerSens ($name) - ExecGatttool_Done: Helper is disabled. Stop processing" if($hash->{helper}{DISABLED});
  352. return if($hash->{helper}{DISABLED});
  353. Log3 $name, 4, "XiaomiFlowerSens ($name) - ExecGatttool_Done: gatttool return string: $string";
  354. my $decode_json = eval{decode_json($json_notification)};
  355. if($@){
  356. Log3 $name, 5, "XiaomiFlowerSens ($name) - ExecGatttool_Done: JSON error while request: $@";
  357. }
  358. if( $respstate eq 'ok' and $gattCmd eq 'read' ) {
  359. XiaomiFlowerSens_ProcessingNotification($hash,$handle,$decode_json->{gtResult});
  360. } elsif( $respstate eq 'ok' and $gattCmd eq 'write' ) {
  361. XiaomiFlowerSens_CallSensData($hash);
  362. } else {
  363. XiaomiFlowerSens_ProcessingErrors($hash,$decode_json->{gtResult});
  364. }
  365. }
  366. sub XiaomiFlowerSens_ExecGatttool_Aborted($) {
  367. my ($hash) = @_;
  368. my $name = $hash->{NAME};
  369. my %readings;
  370. delete($hash->{helper}{RUNNING_PID});
  371. readingsSingleUpdate($hash,"state","unreachable", 1);
  372. $readings{'lastGattError'} = 'The BlockingCall Process terminated unexpectedly. Timedout';
  373. XiaomiFlowerSens_WriteReadings($hash,\%readings);
  374. Log3 $name, 4, "XiaomiFlowerSens ($name) - ExecGatttool_Aborted: The BlockingCall Process terminated unexpectedly. Timedout";
  375. }
  376. sub XiaomiFlowerSens_ProcessingNotification($@) {
  377. my ($hash,$handle,$notification) = @_;
  378. my $name = $hash->{NAME};
  379. my $readings;
  380. Log3 $name, 5, "XiaomiFlowerSens ($name) - ProcessingNotification";
  381. if( $handle eq '0x38' ) {
  382. ### Read Firmware and Battery Data
  383. Log3 $name, 4, "XiaomiFlowerSens ($name) - ProcessingNotification: handle 0x38";
  384. $readings = XiaomiFlowerSens_Handle0x38($hash,$notification);
  385. } elsif( $handle eq '0x35' ) {
  386. ### Read Sensor Data
  387. Log3 $name, 4, "XiaomiFlowerSens ($name) - ProcessingNotification: handle 0x35";
  388. $readings = XiaomiFlowerSens_Handle0x35($hash,$notification);
  389. }
  390. XiaomiFlowerSens_WriteReadings($hash,$readings);
  391. }
  392. sub XiaomiFlowerSens_Handle0x38($$) {
  393. ### Read Firmware and Battery Data
  394. my ($hash,$notification) = @_;
  395. my $name = $hash->{NAME};
  396. my %readings;
  397. Log3 $name, 5, "XiaomiFlowerSens ($name) - Handle0x38";
  398. my @dataBatFw = split(" ",$notification);
  399. my $blevel = hex("0x".$dataBatFw[0]);
  400. my $fw = ($dataBatFw[2]-30).".".($dataBatFw[4]-30).".".($dataBatFw[6]-30);
  401. $readings{'batteryLevel'} = $blevel;
  402. $readings{'battery'} = ($blevel > 20?"ok":"low");
  403. $readings{'firmware'} = $fw;
  404. $hash->{helper}{CallBatteryFirmware} = 1;
  405. XiaomiFlowerSens_CallBatteryFirmware_Timestamp($hash);
  406. return \%readings;
  407. }
  408. sub XiaomiFlowerSens_Handle0x35($$) {
  409. ### Read Sensor Data
  410. my ($hash,$notification) = @_;
  411. my $name = $hash->{NAME};
  412. my %readings;
  413. Log3 $name, 5, "XiaomiFlowerSens ($name) - Handle0x35";
  414. my @dataSensor = split(" ",$notification);
  415. return XiaomiFlowerSens_stateRequest($hash)
  416. unless( $dataSensor[0] ne "aa" and $dataSensor[1] ne "bb" and $dataSensor[2] ne "cc" and $dataSensor[3] ne "dd" and $dataSensor[4] ne "ee" and $dataSensor[5] ne "ff");
  417. my $temp;
  418. if( $dataSensor[1] eq "ff" ) {
  419. $temp = hex("0x".$dataSensor[1].$dataSensor[0]) - hex("0xffff");
  420. } else {
  421. $temp = hex("0x".$dataSensor[1].$dataSensor[0]);
  422. }
  423. my $lux = hex("0x".$dataSensor[4].$dataSensor[3]);
  424. my $moisture = hex("0x".$dataSensor[7]);
  425. my $fertility = hex("0x".$dataSensor[9].$dataSensor[8]);
  426. $readings{'temperature'} = $temp/10;
  427. $readings{'lux'} = $lux;
  428. $readings{'moisture'} = $moisture;
  429. $readings{'fertility'} = $fertility;
  430. $hash->{helper}{CallBatteryFirmware} = 0;
  431. return \%readings;
  432. }
  433. sub XiaomiFlowerSens_WriteReadings($$) {
  434. my ($hash,$readings) = @_;
  435. my $name = $hash->{NAME};
  436. readingsBeginUpdate($hash);
  437. while( my ($r,$v) = each %{$readings} ) {
  438. readingsBulkUpdate($hash,$r,$v);
  439. }
  440. readingsBulkUpdateIfChanged($hash, "state", ($readings->{'lastGattError'}?'error':'active'));
  441. readingsEndUpdate($hash,1);
  442. if( defined($readings->{temperature}) ) {
  443. DoTrigger($name, 'minFertility ' . ($readings->{fertility}<AttrVal($name,'minFertility',0)?'low':'ok')) if( AttrVal($name,'minFertility','none') ne 'none' );
  444. DoTrigger($name, 'maxFertility ' . ($readings->{fertility}>AttrVal($name,'maxFertility',0)?'high':'ok')) if( AttrVal($name,'maxFertility','none') ne 'none' );
  445. DoTrigger($name, 'minTemp ' . ($readings->{temperature}<AttrVal($name,'minTemp',0)?'low':'ok')) if( AttrVal($name,'minTemp','none') ne 'none' );
  446. DoTrigger($name, 'maxTemp ' . ($readings->{temperature}>AttrVal($name,'maxTemp',0)?'high':'ok')) if( AttrVal($name,'maxTemp','none') ne 'none' );
  447. DoTrigger($name, 'minMoisture ' . ($readings->{moisture}<AttrVal($name,'minMoisture',0)?'low':'ok')) if( AttrVal($name,'minMoisture','none') ne 'none' );
  448. DoTrigger($name, 'maxMoisture ' . ($readings->{moisture}>AttrVal($name,'maxMoisture',0)?'high':'ok')) if( AttrVal($name,'maxMoisture','none') ne 'none' );
  449. DoTrigger($name, 'minLux ' . ($readings->{lux}<AttrVal($name,'minLux',0)?'low':'ok')) if( AttrVal($name,'minLux','none') ne 'none' );
  450. DoTrigger($name, 'maxLux ' . ($readings->{lux}>AttrVal($name,'maxLux',0)?'high':'ok')) if( AttrVal($name,'maxLux','none') ne 'none' );
  451. }
  452. Log3 $name, 4, "XiaomiFlowerSens ($name) - WriteReadings: Readings were written";
  453. $hash->{helper}{CallSensDataCounter} = 0;
  454. XiaomiFlowerSens_stateRequest($hash) if( $hash->{helper}{CallBatteryFirmware} == 1 );
  455. }
  456. sub XiaomiFlowerSens_ProcessingErrors($$) {
  457. my ($hash,$notification) = @_;
  458. my $name = $hash->{NAME};
  459. my %readings;
  460. Log3 $name, 5, "XiaomiFlowerSens ($name) - ProcessingErrors";
  461. $readings{'lastGattError'} = $notification;
  462. XiaomiFlowerSens_WriteReadings($hash,\%readings);
  463. }
  464. #### my little Helper
  465. sub XiaomiFlowerSens_encodeJSON($) {
  466. my $gtResult = shift;
  467. chomp($gtResult);
  468. my %response = (
  469. 'gtResult' => $gtResult
  470. );
  471. return encode_json \%response;
  472. }
  473. ## Routinen damit Firmware und Batterie nur alle X male statt immer aufgerufen wird
  474. sub XiaomiFlowerSens_CallBatteryFirmware_Timestamp($) {
  475. my $hash = shift;
  476. # get timestamp
  477. $hash->{helper}{updateTimeCallBatteryFirmware} = gettimeofday(); # in seconds since the epoch
  478. $hash->{helper}{updateTimestampCallBatteryFirmware} = FmtDateTime(gettimeofday());
  479. }
  480. sub XiaomiFlowerSens_CallBatteryFirmware_UpdateTimeAge($) {
  481. my $hash = shift;
  482. $hash->{helper}{updateTimeCallBatteryFirmware} = 0 if( not defined($hash->{helper}{updateTimeCallBatteryFirmware}) );
  483. my $UpdateTimeAge = gettimeofday() - $hash->{helper}{updateTimeCallBatteryFirmware};
  484. return $UpdateTimeAge;
  485. }
  486. sub XiaomiFlowerSens_CallBatteryFirmware_IsUpdateTimeAgeToOld($$) {
  487. my ($hash,$maxAge) = @_;;
  488. return (XiaomiFlowerSens_CallBatteryFirmware_UpdateTimeAge($hash)>$maxAge ? 1:0);
  489. }
  490. 1;
  491. =pod
  492. =item device
  493. =item summary Modul to retrieves data from a Xiaomi Flower Monitor
  494. =item summary_DE Modul um Daten vom Xiaomi Flower Monitor aus zu lesen
  495. =begin html
  496. <a name="XiaomiFlowerSens"></a>
  497. <h3>Xiaomi Flower Monitor</h3>
  498. <ul>
  499. <u><b>XiaomiFlowerSens - Retrieves data from a Xiaomi Flower Monitor</b></u>
  500. <br>
  501. With this module it is possible to read the data from a sensor and to set it as reading.</br>
  502. Gatttool and hcitool is required to use this modul. (apt-get install bluez)
  503. <br><br>
  504. <a name="XiaomiFlowerSensdefine"></a>
  505. <b>Define</b>
  506. <ul><br>
  507. <code>define &lt;name&gt; XiaomiFlowerSens &lt;BT-MAC&gt;</code>
  508. <br><br>
  509. Example:
  510. <ul><br>
  511. <code>define Weihnachtskaktus XiaomiFlowerSens C4:7C:8D:62:42:6F</code><br>
  512. </ul>
  513. <br>
  514. This statement creates a XiaomiFlowerSens with the name Weihnachtskaktus and the Bluetooth Mac C4:7C:8D:62:42:6F.<br>
  515. After the device has been created, the current data of the Xiaomi Flower Monitor is automatically read from the device.
  516. </ul>
  517. <br><br>
  518. <a name="XiaomiFlowerSensreadings"></a>
  519. <b>Readings</b>
  520. <ul>
  521. <li>state - Status of the flower sensor or error message if any errors.</li>
  522. <li>battery - current battery state dependent on batteryLevel.</li>
  523. <li>batteryLevel - current battery level in percent.</li>
  524. <li>fertility - Values for the fertilizer content</li>
  525. <li>firmware - current device firmware</li>
  526. <li>lux - current light intensity</li>
  527. <li>moisture - current moisture content</li>
  528. <li>temperature - current temperature</li>
  529. </ul>
  530. <br><br>
  531. <a name="XiaomiFlowerSensset"></a>
  532. <b>Set</b>
  533. <ul>
  534. <li>clearFirmwareReading - clear firmware reading for new begin.</li>
  535. <br>
  536. </ul>
  537. <br><br>
  538. <a name="XiaomiFlowerSensget"></a>
  539. <b>Get</b>
  540. <ul>
  541. <li>statusRequest - retrieves the current state of the Xiaomi Flower Monitor.</li>
  542. <br>
  543. </ul>
  544. <br><br>
  545. <a name="XiaomiFlowerSensattribut"></a>
  546. <b>Attributes</b>
  547. <ul>
  548. <li>disable - disables the device</li>
  549. <li>disabledForIntervals - disable device for interval time (13:00-18:30 or 13:00-18:30 22:00-23:00)</li>
  550. <li>interval - interval in seconds for statusRequest</li>
  551. <li>minFertility - min fertility value for low warn event</li>
  552. <li>maxFertility - max fertility value for High warn event</li>
  553. <li>minMoisture - min moisture value for low warn event</li>
  554. <li>maxMoisture - max moisture value for High warn event</li>
  555. <li>minTemp - min temperature value for low warn event</li>
  556. <li>maxTemp - max temperature value for high warn event</li>
  557. <li>minlux - min lux value for low warn event</li>
  558. <li>maxlux - max lux value for high warn event
  559. <br>
  560. Event Example for min/max Value's: 2017-03-16 11:08:05 XiaomiFlowerSens Dracaena minMoisture low<br>
  561. Event Example for min/max Value's: 2017-03-16 11:08:06 XiaomiFlowerSens Dracaena maxTemp high</li>
  562. <li>sshHost - FQD-Name or IP of ssh remote system / you must configure your ssh system for certificate authentication. For better handling you can config ssh Client with .ssh/config file</li>
  563. <li>batteryFirmwareAge - how old can the reading befor fetch new data</li>
  564. <li>blockingCallLoglevel - Blocking.pm Loglevel for BlockingCall Logoutput</li>
  565. </ul>
  566. </ul>
  567. =end html
  568. =begin html_DE
  569. <a name="XiaomiFlowerSens"></a>
  570. <h3>Xiaomi Flower Monitor</h3>
  571. <ul>
  572. <u><b>XiaomiFlowerSens - liest Daten von einem Xiaomi Flower Monitor</b></u>
  573. <br />
  574. Dieser Modul liest Daten von einem Sensor und legt sie in den Readings ab.<br />
  575. Auf dem (Linux) FHEM-Server werden gatttool und hcitool vorausgesetzt. (sudo apt install bluez)
  576. <br /><br />
  577. <a name="XiaomiFlowerSensdefine"></a>
  578. <b>Define</b>
  579. <ul><br />
  580. <code>define &lt;name&gt; XiaomiFlowerSens &lt;BT-MAC&gt;</code>
  581. <br /><br />
  582. Beispiel:
  583. <ul><br />
  584. <code>define Weihnachtskaktus XiaomiFlowerSens C4:7C:8D:62:42:6F</code><br />
  585. </ul>
  586. <br />
  587. Der Befehl legt ein Device vom Typ XiaomiFlowerSens an mit dem Namen Weihnachtskaktus und der Bluetooth MAC C4:7C:8D:62:42:6F.<br />
  588. Nach dem Anlegen des Device werden umgehend und automatisch die aktuellen Daten vom betroffenen Xiaomi Flower Monitor gelesen.
  589. </ul>
  590. <br /><br />
  591. <a name="XiaomiFlowerSensreadings"></a>
  592. <b>Readings</b>
  593. <ul>
  594. <li>state - Status des Flower Monitor oder eine Fehlermeldung falls Fehler beim letzten Kontakt auftraten.</li>
  595. <li>battery - aktueller Batterie-Status in Abhängigkeit vom Wert batteryLevel.</li>
  596. <li>batteryLevel - aktueller Ladestand der Batterie in Prozent.</li>
  597. <li>fertility - Wert des Fruchtbarkeitssensors (Bodenleitf&auml;higkeit)</li>
  598. <li>firmware - aktuelle Firmware-Version des Flower Monitor</li>
  599. <li>lux - aktuelle Lichtintensit&auml;t</li>
  600. <li>moisture - aktueller Feuchtigkeitswert</li>
  601. <li>temperature - aktuelle Temperatur</li>
  602. </ul>
  603. <br /><br />
  604. <a name="XiaomiFlowerSensset"></a>
  605. <b>Set</b>
  606. <ul>
  607. <li>clearFirmwareReading - l&ouml;scht das Reading firmware f&uuml;r/nach Upgrade</li>
  608. <br />
  609. </ul>
  610. <br /><br />
  611. <a name="XiaomiFlowerSensGet"></a>
  612. <b>Get</b>
  613. <ul>
  614. <li>statusRequest - aktive Abfrage des aktuellen Status des Xiaomi Flower Monitor und seiner Werte</li>
  615. <br />
  616. </ul>
  617. <br /><br />
  618. <a name="XiaomiFlowerSensattribut"></a>
  619. <b>Attribute</b>
  620. <ul>
  621. <li>disable - deaktiviert das Device</li>
  622. <li>interval - Interval in Sekunden zwischen zwei Abfragen</li>
  623. <li>disabledForIntervals - deaktiviert das Gerät für den angegebenen Zeitinterval (13:00-18:30 or 13:00-18:30 22:00-23:00)</li>
  624. <li>minFertility - min Fruchtbarkeits-Grenzwert f&uuml;r ein Ereignis minFertility low </li>
  625. <li>maxFertility - max Fruchtbarkeits-Grenzwert f&uuml;r ein Ereignis maxFertility high </li>
  626. <li>minMoisture - min Feuchtigkeits-Grenzwert f&uuml;r ein Ereignis minMoisture low </li>
  627. <li>maxMoisture - max Feuchtigkeits-Grenzwert f&uuml;r ein Ereignis maxMoisture high </li>
  628. <li>minTemp - min Temperatur-Grenzwert f&uuml;r ein Ereignis minTemp low </li>
  629. <li>maxTemp - max Temperatur-Grenzwert f&uuml;r ein Ereignis maxTemp high </li>
  630. <li>minlux - min Helligkeits-Grenzwert f&uuml;r ein Ereignis minlux low </li>
  631. <li>maxlux - max Helligkeits-Grenzwert f&uuml;r ein Ereignis maxlux high
  632. <br /><br />Beispiele f&uuml;r min/max-Ereignisse:<br />
  633. 2017-03-16 11:08:05 XiaomiFlowerSens Dracaena minMoisture low<br />
  634. 2017-03-16 11:08:06 XiaomiFlowerSens Dracaena maxTemp high<br /><br /></li>
  635. <li>sshHost - FQDN oder IP-Adresse eines entfernten SSH-Systems. Das SSH-System ist auf eine Zertifikat basierte Authentifizierung zu konfigurieren. Am elegantesten geschieht das mit einer .ssh/config Datei auf dem SSH-Client.</li>
  636. <li>batteryFirmwareAge - wie alt soll der Timestamp des Readings sein bevor eine Aktuallisierung statt findet</li>
  637. <li>blockingCallLoglevel - Blocking.pm Loglevel für BlockingCall Logausgaben</li>
  638. </ul>
  639. </ul>
  640. =end html_DE
  641. =cut