74_XiaomiBTLESens.pm 40 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901902903904905906907908909910911912913914915916917918919920921922923924925926927928929930931932933934935936937938939940941942943944945946947948949950951952953954955956957958959960961962963964965966967968969970971972973974975976977978979980981982983984985986987988989990991992993994995996997998999100010011002100310041005100610071008100910101011101210131014101510161017101810191020102110221023102410251026102710281029103010311032103310341035103610371038103910401041104210431044104510461047104810491050105110521053105410551056105710581059106010611062106310641065106610671068106910701071107210731074107510761077107810791080108110821083108410851086108710881089109010911092109310941095109610971098
  1. ###############################################################################
  2. #
  3. # Developed with Kate
  4. #
  5. # (c) 2017-2018 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_XiaomiBTLESens.pm 17328 2018-09-12 06:40:06Z 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. my $missingModul = "";
  33. use strict;
  34. use warnings;
  35. use POSIX;
  36. eval "use JSON;1" or $missingModul .= "JSON ";
  37. eval "use Blocking;1" or $missingModul .= "Blocking ";
  38. #use Data::Dumper; only for Debugging
  39. my $version = "2.2.2";
  40. my %XiaomiModels = (
  41. flowerSens => {'rdata' => '0x35' ,'wdata' => '0x33' ,'wdataValue' => 'A01F' ,'wdatalisten' => 0 ,'battery' => '0x38' ,'firmware' => '0x38'},
  42. thermoHygroSens => {'wdata' => '0x10' ,'wdataValue' => '0100' ,'wdatalisten' => 1 ,'battery' => '0x18' ,'firmware' => '0x24' ,'devicename' => '0x3'},
  43. );
  44. my %CallBatteryAge = ( '8h' => 28800,
  45. '16h' => 57600,
  46. '24h' => 86400,
  47. '32h' => 115200,
  48. '40h' => 144000,
  49. '48h' => 172800
  50. );
  51. # Declare functions
  52. sub XiaomiBTLESens_Initialize($);
  53. sub XiaomiBTLESens_Define($$);
  54. sub XiaomiBTLESens_Undef($$);
  55. sub XiaomiBTLESens_Attr(@);
  56. sub XiaomiBTLESens_stateRequest($);
  57. sub XiaomiBTLESens_stateRequestTimer($);
  58. sub XiaomiBTLESens_Set($$@);
  59. sub XiaomiBTLESens_Get($$@);
  60. sub XiaomiBTLESens_Notify($$);
  61. sub XiaomiBTLESens_CreateParamGatttool($@);
  62. sub XiaomiBTLESens_ExecGatttool_Run($);
  63. sub XiaomiBTLESens_ExecGatttool_Done($);
  64. sub XiaomiBTLESens_ExecGatttool_Aborted($);
  65. sub XiaomiBTLESens_ProcessingNotification($@);
  66. sub XiaomiBTLESens_WriteReadings($$);
  67. sub XiaomiBTLESens_ProcessingErrors($$);
  68. sub XiaomiBTLESens_encodeJSON($);
  69. sub CometBlueBTLE_CmdlinePreventGrepFalsePositive($);
  70. sub XiaomiBTLESens_CallBattery_IsUpdateTimeAgeToOld($$);
  71. sub XiaomiBTLESens_CallBattery_Timestamp($);
  72. sub XiaomiBTLESens_CallBattery_UpdateTimeAge($);
  73. sub XiaomiBTLESens_CreateDevicenameHEX($);
  74. sub XiaomiBTLESens_FlowerSensHandle0x35($$);
  75. sub XiaomiBTLESens_FlowerSensHandle0x38($$);
  76. sub XiaomiBTLESens_ThermoHygroSensHandle0x18($$);
  77. sub XiaomiBTLESens_ThermoHygroSensHandle0x10($$);
  78. sub XiaomiBTLESens_ThermoHygroSensHandle0x24($$);
  79. sub XiaomiBTLESens_ThermoHygroSensHandle0x3($$);
  80. sub XiaomiBTLESens_Initialize($) {
  81. my ($hash) = @_;
  82. $hash->{SetFn} = "XiaomiBTLESens_Set";
  83. $hash->{GetFn} = "XiaomiBTLESens_Get";
  84. $hash->{DefFn} = "XiaomiBTLESens_Define";
  85. $hash->{NotifyFn} = "XiaomiBTLESens_Notify";
  86. $hash->{UndefFn} = "XiaomiBTLESens_Undef";
  87. $hash->{AttrFn} = "XiaomiBTLESens_Attr";
  88. $hash->{AttrList} = "interval ".
  89. "disable:1 ".
  90. "disabledForIntervals ".
  91. "hciDevice:hci0,hci1,hci2 ".
  92. "batteryFirmwareAge:8h,16h,24h,32h,40h,48h ".
  93. "minFertility ".
  94. "maxFertility ".
  95. "minTemp ".
  96. "maxTemp ".
  97. "minMoisture ".
  98. "maxMoisture ".
  99. "minLux ".
  100. "maxLux ".
  101. "sshHost ".
  102. "model:flowerSens,thermoHygroSens ".
  103. "blockingCallLoglevel:2,3,4,5 ".
  104. $readingFnAttributes;
  105. foreach my $d(sort keys %{$modules{XiaomiBTLESens}{defptr}}) {
  106. my $hash = $modules{XiaomiBTLESens}{defptr}{$d};
  107. $hash->{VERSION} = $version;
  108. }
  109. }
  110. sub XiaomiBTLESens_Define($$) {
  111. my ( $hash, $def ) = @_;
  112. my @a = split( "[ \t][ \t]*", $def );
  113. return "too few parameters: define <name> XiaomiBTLESens <BTMAC>" if( @a != 3 );
  114. return "Cannot define XiaomiBTLESens device. Perl modul ${missingModul}is missing." if ( $missingModul );
  115. my $name = $a[0];
  116. my $mac = $a[2];
  117. $hash->{BTMAC} = $mac;
  118. $hash->{VERSION} = $version;
  119. $hash->{INTERVAL} = 300;
  120. $hash->{helper}{CallSensDataCounter} = 0;
  121. $hash->{helper}{CallBattery} = 0;
  122. $hash->{NOTIFYDEV} = "global,$name";
  123. $hash->{loglevel} = 4;
  124. readingsSingleUpdate($hash,"state","initialized", 0);
  125. CommandAttr(undef,$name . ' room XiaomiBTLESens') if( AttrVal($name,'room','none') eq 'none' );
  126. Log3 $name, 3, "XiaomiBTLESens ($name) - defined with BTMAC $hash->{BTMAC}";
  127. $modules{XiaomiBTLESens}{defptr}{$hash->{BTMAC}} = $hash;
  128. return undef;
  129. }
  130. sub XiaomiBTLESens_Undef($$) {
  131. my ( $hash, $arg ) = @_;
  132. my $mac = $hash->{BTMAC};
  133. my $name = $hash->{NAME};
  134. RemoveInternalTimer($hash);
  135. BlockingKill($hash->{helper}{RUNNING_PID}) if(defined($hash->{helper}{RUNNING_PID}));
  136. delete($modules{XiaomiBTLESens}{defptr}{$mac});
  137. Log3 $name, 3, "Sub XiaomiBTLESens_Undef ($name) - delete device $name";
  138. return undef;
  139. }
  140. sub XiaomiBTLESens_Attr(@) {
  141. my ( $cmd, $name, $attrName, $attrVal ) = @_;
  142. my $hash = $defs{$name};
  143. if( $attrName eq "disable" ) {
  144. if( $cmd eq "set" and $attrVal eq "1" ) {
  145. RemoveInternalTimer($hash);
  146. readingsSingleUpdate ( $hash, "state", "disabled", 1 );
  147. Log3 $name, 3, "XiaomiBTLESens ($name) - disabled";
  148. }
  149. elsif( $cmd eq "del" ) {
  150. Log3 $name, 3, "XiaomiBTLESens ($name) - enabled";
  151. }
  152. }
  153. elsif( $attrName eq "disabledForIntervals" ) {
  154. if( $cmd eq "set" ) {
  155. return "check disabledForIntervals Syntax HH:MM-HH:MM or 'HH:MM-HH:MM HH:MM-HH:MM ...'"
  156. unless($attrVal =~ /^((\d{2}:\d{2})-(\d{2}:\d{2})\s?)+$/);
  157. Log3 $name, 3, "XiaomiBTLESens ($name) - disabledForIntervals";
  158. XiaomiBTLESens_stateRequest($hash);
  159. }
  160. elsif( $cmd eq "del" ) {
  161. Log3 $name, 3, "XiaomiBTLESens ($name) - enabled";
  162. readingsSingleUpdate ( $hash, "state", "active", 1 );
  163. }
  164. }
  165. elsif( $attrName eq "interval" ) {
  166. RemoveInternalTimer($hash);
  167. if( $cmd eq "set" ) {
  168. if( $attrVal < 120 ) {
  169. Log3 $name, 3, "XiaomiBTLESens ($name) - interval too small, please use something >= 120 (sec), default is 300 (sec)";
  170. return "interval too small, please use something >= 120 (sec), default is 300 (sec)";
  171. } else {
  172. $hash->{INTERVAL} = $attrVal;
  173. Log3 $name, 3, "XiaomiBTLESens ($name) - set interval to $attrVal";
  174. }
  175. }
  176. elsif( $cmd eq "del" ) {
  177. $hash->{INTERVAL} = 300;
  178. Log3 $name, 3, "XiaomiBTLESens ($name) - set interval to default";
  179. }
  180. }
  181. elsif( $attrName eq "blockingCallLoglevel" ) {
  182. if( $cmd eq "set" ) {
  183. $hash->{loglevel} = $attrVal;
  184. Log3 $name, 3, "XiaomiBTLESens ($name) - set blockingCallLoglevel to $attrVal";
  185. }
  186. elsif( $cmd eq "del" ) {
  187. $hash->{loglevel} = 4;
  188. Log3 $name, 3, "XiaomiBTLESens ($name) - set blockingCallLoglevel to default";
  189. }
  190. }
  191. return undef;
  192. }
  193. sub XiaomiBTLESens_Notify($$) {
  194. my ($hash,$dev) = @_;
  195. my $name = $hash->{NAME};
  196. return if (IsDisabled($name));
  197. my $devname = $dev->{NAME};
  198. my $devtype = $dev->{TYPE};
  199. my $events = deviceEvents($dev,1);
  200. return if (!$events);
  201. XiaomiBTLESens_stateRequestTimer($hash) if( (((grep /^DEFINED.$name$/,@{$events}
  202. or grep /^DELETEATTR.$name.disable$/,@{$events}
  203. or grep /^ATTR.$name.disable.0$/,@{$events}
  204. or grep /^DELETEATTR.$name.interval$/,@{$events}
  205. or grep /^DELETEATTR.$name.model$/,@{$events}
  206. or grep /^ATTR.$name.model.+/,@{$events}
  207. or grep /^ATTR.$name.interval.[0-9]+/,@{$events}) and $devname eq 'global')
  208. or grep /^resetBatteryTimestamp$/,@{$events}) and $init_done
  209. or ((grep /^INITIALIZED$/,@{$events}
  210. or grep /^REREADCFG$/,@{$events}
  211. or grep /^MODIFIED.$name$/,@{$events}) and $devname eq 'global') );
  212. XiaomiBTLESens_CreateParamGatttool($hash,'read',$XiaomiModels{AttrVal($name,'model','')}{devicename}) if( AttrVal($name,'model','thermoHygroSens') eq 'thermoHygroSens'
  213. and $devname eq $name
  214. and grep /^$name.firmware.+/,@{$events} );
  215. return;
  216. }
  217. sub XiaomiBTLESens_stateRequest($) {
  218. my ($hash) = @_;
  219. my $name = $hash->{NAME};
  220. my %readings;
  221. if( AttrVal($name,'model','none') eq 'none') {
  222. readingsSingleUpdate($hash,"state","set attribute model first",1);
  223. } elsif( !IsDisabled($name) ) {
  224. if( ReadingsVal($name,'firmware','none') ne 'none' ) {
  225. return XiaomiBTLESens_CreateParamGatttool($hash,'read',$XiaomiModels{AttrVal($name,'model','')}{battery})
  226. if( XiaomiBTLESens_CallBattery_IsUpdateTimeAgeToOld($hash,$CallBatteryAge{AttrVal($name,'BatteryFirmwareAge','24h')}) );
  227. if( $hash->{helper}{CallSensDataCounter} < 1 ) {
  228. XiaomiBTLESens_CreateParamGatttool($hash,'write',$XiaomiModels{AttrVal($name,'model','')}{wdata},$XiaomiModels{AttrVal($name,'model','')}{wdataValue});
  229. $hash->{helper}{CallSensDataCounter} = $hash->{helper}{CallSensDataCounter} + 1;
  230. } else {
  231. $readings{'lastGattError'} = 'charWrite faild';
  232. XiaomiBTLESens_WriteReadings($hash,\%readings);
  233. $hash->{helper}{CallSensDataCounter} = 0;
  234. return;
  235. }
  236. } else {
  237. XiaomiBTLESens_CreateParamGatttool($hash,'read',$XiaomiModels{AttrVal($name,'model','')}{firmware});
  238. }
  239. } else {
  240. readingsSingleUpdate($hash,"state","disabled",1);
  241. }
  242. }
  243. sub XiaomiBTLESens_stateRequestTimer($) {
  244. my ($hash) = @_;
  245. my $name = $hash->{NAME};
  246. RemoveInternalTimer($hash);
  247. XiaomiBTLESens_stateRequest($hash);
  248. InternalTimer( gettimeofday()+$hash->{INTERVAL}+int(rand(300)), "XiaomiBTLESens_stateRequestTimer", $hash );
  249. Log3 $name, 4, "XiaomiBTLESens ($name) - stateRequestTimer: Call Request Timer";
  250. }
  251. sub XiaomiBTLESens_Set($$@) {
  252. my ($hash, $name, @aa) = @_;
  253. my ($cmd, @args) = @aa;
  254. my $mod;
  255. my $handle;
  256. my $value = 'write';
  257. if( $cmd eq 'devicename' ) {
  258. return "usage: devicename <name>" if( @args < 1 );
  259. my $devicename = join( " ", @args );
  260. $mod = 'write'; $handle = $XiaomiModels{AttrVal($name,'model','')}{devicename}; $value = XiaomiBTLESens_CreateDevicenameHEX(makeDeviceName($devicename));
  261. } elsif( $cmd eq 'resetBatteryTimestamp' ) {
  262. return "usage: resetBatteryTimestamp" if( @args != 0 );
  263. $hash->{helper}{updateTimeCallBattery} = 0;
  264. return;
  265. } else {
  266. my $list = "resetBatteryTimestamp:noArg";
  267. $list .= " devicename" if( AttrVal($name,'model','thermoHygroSens') eq 'thermoHygroSens' );
  268. return "Unknown argument $cmd, choose one of $list";
  269. }
  270. XiaomiBTLESens_CreateParamGatttool($hash,$mod,$handle,$value);
  271. return undef;
  272. }
  273. sub XiaomiBTLESens_Get($$@) {
  274. my ($hash, $name, @aa) = @_;
  275. my ($cmd, @args) = @aa;
  276. my $mod = 'read';
  277. my $handle;
  278. if( $cmd eq 'sensorData' ) {
  279. return "usage: sensorData" if( @args != 0 );
  280. XiaomiBTLESens_stateRequest($hash);
  281. } elsif( $cmd eq 'firmware' ) {
  282. return "usage: firmware" if( @args != 0 );
  283. $mod = 'read'; $handle = $XiaomiModels{AttrVal($name,'model','')}{firmware};
  284. } elsif( $cmd eq 'devicename' ) {
  285. return "usage: devicename" if( @args != 0 );
  286. $mod = 'read'; $handle = $XiaomiModels{AttrVal($name,'model','')}{devicename};
  287. } else {
  288. my $list = "sensorData:noArg firmware:noArg";
  289. $list .= " devicename:noArg" if( AttrVal($name,'model','thermoHygroSens') eq 'thermoHygroSens' );
  290. return "Unknown argument $cmd, choose one of $list";
  291. }
  292. XiaomiBTLESens_CreateParamGatttool($hash,$mod,$handle) if( $cmd ne 'sensorData' );
  293. return undef;
  294. }
  295. sub XiaomiBTLESens_CreateParamGatttool($@) {
  296. my ($hash,$mod,$handle,$value) = @_;
  297. my $name = $hash->{NAME};
  298. my $mac = $hash->{BTMAC};
  299. Log3 $name, 4, "XiaomiBTLESens ($name) - Run CreateParamGatttool with mod: $mod";
  300. if( $mod eq 'read' ) {
  301. $hash->{helper}{RUNNING_PID} = BlockingCall("XiaomiBTLESens_ExecGatttool_Run", $name."|".$mac."|".$mod."|".$handle, "XiaomiBTLESens_ExecGatttool_Done", 90, "XiaomiBTLESens_ExecGatttool_Aborted", $hash) unless( exists($hash->{helper}{RUNNING_PID}) );
  302. readingsSingleUpdate($hash,"state","read sensor data",1);
  303. Log3 $name, 5, "XiaomiBTLESens ($name) - Read XiaomiBTLESens_ExecGatttool_Run $name|$mac|$mod|$handle";
  304. } elsif( $mod eq 'write' ) {
  305. $hash->{helper}{RUNNING_PID} = BlockingCall("XiaomiBTLESens_ExecGatttool_Run", $name."|".$mac."|".$mod."|".$handle."|".$value."|".$XiaomiModels{AttrVal($name,'model','')}{wdatalisten}, "XiaomiBTLESens_ExecGatttool_Done", 90, "XiaomiBTLESens_ExecGatttool_Aborted", $hash) unless( exists($hash->{helper}{RUNNING_PID}) );
  306. readingsSingleUpdate($hash,"state","write sensor data",1);
  307. Log3 $name, 5, "XiaomiBTLESens ($name) - Write XiaomiBTLESens_ExecGatttool_Run $name|$mac|$mod|$handle|$value";
  308. }
  309. }
  310. sub XiaomiBTLESens_ExecGatttool_Run($) {
  311. my $string = shift;
  312. my ($name,$mac,$gattCmd,$handle,$value,$listen) = split("\\|", $string);
  313. my $sshHost = AttrVal($name,"sshHost","none");
  314. my $gatttool;
  315. my $json_notification;
  316. $gatttool = qx(which gatttool) if($sshHost eq 'none');
  317. $gatttool = qx(ssh $sshHost 'which gatttool') if($sshHost ne 'none');
  318. chomp $gatttool;
  319. if(defined($gatttool) and ($gatttool)) {
  320. my $cmd;
  321. my $loop;
  322. my @gtResult;
  323. my $wait = 1;
  324. my $sshHost = AttrVal($name,"sshHost","none");
  325. my $hci = AttrVal($name,"hciDevice","hci0");
  326. $cmd = "ssh $sshHost '" if($sshHost ne 'none');
  327. $cmd .= "timeout 10 " if($listen);
  328. $cmd .= "gatttool -i $hci -b $mac ";
  329. $cmd .= "--char-read -a $handle" if($gattCmd eq 'read');
  330. $cmd .= "--char-write-req -a $handle -n $value" if($gattCmd eq 'write');
  331. $cmd .= " --listen" if($listen);
  332. $cmd .= " 2>&1 /dev/null";
  333. $cmd .= "'" if($sshHost ne 'none');
  334. $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' and AttrVal($name,"model","none") eq 'flowerSens');
  335. while($wait) {
  336. my $grepGatttool;
  337. my $gatttoolCmdlineStaticEscaped = CometBlueBTLE_CmdlinePreventGrepFalsePositive("gatttool -i $hci -b $mac");
  338. $grepGatttool = qx(ps ax| grep -E \'$gatttoolCmdlineStaticEscaped\') if($sshHost eq 'none');
  339. $grepGatttool = qx(ssh $sshHost 'ps ax| grep -E "$gatttoolCmdlineStaticEscaped"') if($sshHost ne 'none');
  340. if(not $grepGatttool =~ /^\s*$/) {
  341. Log3 $name, 3, "XiaomiBTLESens ($name) - ExecGatttool_Run: another gatttool process is running. waiting...";
  342. sleep(1);
  343. } else {
  344. $wait = 0;
  345. }
  346. }
  347. $loop = 0;
  348. do {
  349. Log3 $name, 5, "XiaomiBTLESens ($name) - ExecGatttool_Run: call gatttool with command: $cmd and loop $loop";
  350. @gtResult = split(": ",qx($cmd));
  351. Log3 $name, 5, "XiaomiBTLESens ($name) - ExecGatttool_Run: gatttool loop result ".join(",", @gtResult);
  352. $loop++;
  353. $gtResult[0] = 'connect error'
  354. unless( defined($gtResult[0]) );
  355. } while( $loop < 5 and $gtResult[0] eq 'connect error' );
  356. Log3 $name, 4, "XiaomiBTLESens ($name) - ExecGatttool_Run: gatttool result ".join(",", @gtResult);
  357. $handle = '0x35' if($sshHost ne 'none' and $gattCmd eq 'write' and AttrVal($name,'model','none') eq 'flowerSens');
  358. $gattCmd = 'read' if($sshHost ne 'none' and $gattCmd eq 'write' and AttrVal($name,'model','none') eq 'flowerSens');
  359. $gtResult[1] = 'no data response'
  360. unless( defined($gtResult[1]) );
  361. if( $gtResult[1] ne 'no data response' and $listen ) {
  362. ($gtResult[1]) = split("\n",$gtResult[1]);
  363. $gtResult[1] =~ s/\\n//g;
  364. }
  365. $json_notification = XiaomiBTLESens_encodeJSON($gtResult[1]);
  366. if($gtResult[1] =~ /^([0-9a-f]{2}(\s?))*$/) {
  367. return "$name|$mac|ok|$gattCmd|$handle|$json_notification";
  368. } elsif($gtResult[0] ne 'connect error' and $gattCmd eq 'write') {
  369. if( $sshHost ne 'none' ) {
  370. XiaomiBTLESens_ExecGatttool_Run($name."|".$mac."|read|0x35");
  371. } else {
  372. return "$name|$mac|ok|$gattCmd|$handle|$json_notification";
  373. }
  374. } else {
  375. return "$name|$mac|error|$gattCmd|$handle|$json_notification";
  376. }
  377. } else {
  378. $json_notification = XiaomiBTLESens_encodeJSON('no gatttool binary found. Please check if bluez-package is properly installed');
  379. return "$name|$mac|error|$gattCmd|$handle|$json_notification";
  380. }
  381. }
  382. sub XiaomiBTLESens_ExecGatttool_Done($) {
  383. my $string = shift;
  384. my ($name,$mac,$respstate,$gattCmd,$handle,$json_notification) = split("\\|", $string);
  385. my $hash = $defs{$name};
  386. delete($hash->{helper}{RUNNING_PID});
  387. Log3 $name, 5, "XiaomiBTLESens ($name) - ExecGatttool_Done: Helper is disabled. Stop processing" if($hash->{helper}{DISABLED});
  388. return if($hash->{helper}{DISABLED});
  389. Log3 $name, 5, "XiaomiBTLESens ($name) - ExecGatttool_Done: gatttool return string: $string";
  390. my $decode_json = eval{decode_json($json_notification)};
  391. if($@){
  392. Log3 $name, 4, "XiaomiBTLESens ($name) - ExecGatttool_Done: JSON error while request: $@";
  393. }
  394. if( $respstate eq 'ok' and $gattCmd eq 'write' and AttrVal($name,'model','none') eq 'flowerSens' ) {
  395. XiaomiBTLESens_CreateParamGatttool($hash,'read',$XiaomiModels{AttrVal($name,'model','')}{rdata});
  396. } elsif( $respstate eq 'ok' ) {
  397. XiaomiBTLESens_ProcessingNotification($hash,$gattCmd,$handle,$decode_json->{gtResult});
  398. } else {
  399. XiaomiBTLESens_ProcessingErrors($hash,$decode_json->{gtResult});
  400. }
  401. }
  402. sub XiaomiBTLESens_ExecGatttool_Aborted($) {
  403. my ($hash) = @_;
  404. my $name = $hash->{NAME};
  405. my %readings;
  406. delete($hash->{helper}{RUNNING_PID});
  407. readingsSingleUpdate($hash,"state","unreachable", 1);
  408. $readings{'lastGattError'} = 'The BlockingCall Process terminated unexpectedly. Timedout';
  409. XiaomiBTLESens_WriteReadings($hash,\%readings);
  410. Log3 $name, 4, "XiaomiBTLESens ($name) - ExecGatttool_Aborted: The BlockingCall Process terminated unexpectedly. Timedout";
  411. }
  412. sub XiaomiBTLESens_ProcessingNotification($@) {
  413. my ($hash,$gattCmd,$handle,$notification) = @_;
  414. my $name = $hash->{NAME};
  415. my $readings;
  416. Log3 $name, 4, "XiaomiBTLESens ($name) - ProcessingNotification";
  417. if( AttrVal($name,'model','none') eq 'flowerSens' ) {
  418. if( $handle eq '0x38' ) {
  419. ### Flower Sens - Read Firmware and Battery Data
  420. Log3 $name, 4, "XiaomiBTLESens ($name) - ProcessingNotification: handle 0x38";
  421. $readings = XiaomiBTLESens_FlowerSensHandle0x38($hash,$notification);
  422. } elsif( $handle eq '0x35' ) {
  423. ### Flower Sens - Read Sensor Data
  424. Log3 $name, 4, "XiaomiBTLESens ($name) - ProcessingNotification: handle 0x35";
  425. $readings = XiaomiBTLESens_FlowerSensHandle0x35($hash,$notification);
  426. }
  427. } elsif( AttrVal($name,'model','none') eq 'thermoHygroSens') {
  428. if( $handle eq '0x18' ) {
  429. ### Thermo/Hygro Sens - Read Battery Data
  430. Log3 $name, 4, "XiaomiBTLESens ($name) - ProcessingNotification: handle 0x18";
  431. $readings = XiaomiBTLESens_ThermoHygroSensHandle0x18($hash,$notification);
  432. }
  433. elsif( $handle eq '0x10' ) {
  434. ### Thermo/Hygro Sens - Read Sensor Data
  435. Log3 $name, 4, "XiaomiBTLESens ($name) - ProcessingNotification: handle 0x10";
  436. $readings = XiaomiBTLESens_ThermoHygroSensHandle0x10($hash,$notification);
  437. }
  438. elsif( $handle eq '0x24' ) {
  439. ### Thermo/Hygro Sens - Read Firmware Data
  440. Log3 $name, 4, "XiaomiBTLESens ($name) - ProcessingNotification: handle 0x24";
  441. $readings = XiaomiBTLESens_ThermoHygroSensHandle0x24($hash,$notification)
  442. }
  443. elsif( $handle eq '0x3' ) {
  444. ### Thermo/Hygro Sens - Read and Write Devicename
  445. Log3 $name, 4, "XiaomiBTLESens ($name) - ProcessingNotification: handle 0x3";
  446. return XiaomiBTLESens_CreateParamGatttool($hash,'read',$XiaomiModels{AttrVal($name,'model','')}{devicename}) unless($gattCmd eq 'read');
  447. $readings = XiaomiBTLESens_ThermoHygroSensHandle0x3($hash,$notification)
  448. }
  449. }
  450. XiaomiBTLESens_WriteReadings($hash,$readings);
  451. }
  452. sub XiaomiBTLESens_FlowerSensHandle0x38($$) {
  453. ### FlowerSens - Read Firmware and Battery Data
  454. my ($hash,$notification) = @_;
  455. my $name = $hash->{NAME};
  456. my %readings;
  457. Log3 $name, 4, "XiaomiBTLESens ($name) - FlowerSens Handle0x38";
  458. my @dataBatFw = split(" ",$notification);
  459. ### neue Vereinheitlichung für Batteriereadings Forum #800017
  460. $readings{'batteryPercent'} = hex("0x".$dataBatFw[0]);
  461. $readings{'batteryState'} = (hex("0x".$dataBatFw[0]) > 15 ? "ok" : "low");
  462. $readings{'firmware'} = ($dataBatFw[2]-30).".".($dataBatFw[4]-30).".".($dataBatFw[6]-30);
  463. $hash->{helper}{CallBattery} = 1;
  464. XiaomiBTLESens_CallBattery_Timestamp($hash);
  465. return \%readings;
  466. }
  467. sub XiaomiBTLESens_FlowerSensHandle0x35($$) {
  468. ### Flower Sens - Read Sensor Data
  469. my ($hash,$notification) = @_;
  470. my $name = $hash->{NAME};
  471. my %readings;
  472. Log3 $name, 4, "XiaomiBTLESens ($name) - FlowerSens Handle0x35";
  473. my @dataSensor = split(" ",$notification);
  474. return XiaomiBTLESens_stateRequest($hash)
  475. 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");
  476. if( $dataSensor[1] eq "ff" ) {
  477. $readings{'temperature'} = (hex("0x".$dataSensor[1].$dataSensor[0]) - hex("0xffff")) / 10;
  478. } else {
  479. $readings{'temperature'} = hex("0x".$dataSensor[1].$dataSensor[0]) / 10;
  480. }
  481. $readings{'lux'} = hex("0x".$dataSensor[4].$dataSensor[3]);
  482. $readings{'moisture'} = hex("0x".$dataSensor[7]);
  483. $readings{'fertility'} = hex("0x".$dataSensor[9].$dataSensor[8]);
  484. Log3 $name, 4, "XiaomiBTLESens ($name) - FlowerSens Handle0x35 - lux: " . $readings{lux} . ", moisture: " . $readings{moisture} . ", fertility: " . $readings{fertility} ;
  485. $hash->{helper}{CallBattery} = 0;
  486. return \%readings;
  487. }
  488. sub XiaomiBTLESens_ThermoHygroSensHandle0x18($$) {
  489. ### Thermo/Hygro Sens - Battery Data
  490. my ($hash,$notification) = @_;
  491. my $name = $hash->{NAME};
  492. my %readings;
  493. Log3 $name, 4, "XiaomiBTLESens ($name) - Thermo/Hygro Sens Handle0x18";
  494. chomp($notification);
  495. ### neue Vereinheitlichung für Batteriereadings Forum #800017
  496. $readings{'batteryPercent'} = hex("0x".$notification);
  497. $readings{'batteryState'} = (hex("0x".$notification) > 15 ? "ok" : "low");
  498. $hash->{helper}{CallBattery} = 1;
  499. XiaomiBTLESens_CallBattery_Timestamp($hash);
  500. return \%readings;
  501. }
  502. sub XiaomiBTLESens_ThermoHygroSensHandle0x10($$) {
  503. ### Thermo/Hygro Sens - Read Sensor Data
  504. my ($hash,$notification) = @_;
  505. my $name = $hash->{NAME};
  506. my %readings;
  507. Log3 $name, 4, "XiaomiBTLESens ($name) - Thermo/Hygro Sens Handle0x10";
  508. return XiaomiBTLESens_stateRequest($hash)
  509. unless($notification =~ /^([0-9a-f]{2}(\s?))*$/);
  510. my @numberOfHex = split(' ',$notification);
  511. $notification =~ s/\s+//g;
  512. $readings{'temperature'} = pack('H*',substr($notification,4,8));
  513. if( scalar(@numberOfHex) < 14 ) {
  514. $readings{'humidity'} = pack('H*',substr($notification,16,8));
  515. } else {
  516. $readings{'humidity'} = pack('H*',substr($notification,18,8));
  517. }
  518. $hash->{helper}{CallBattery} = 0;
  519. return \%readings;
  520. }
  521. sub XiaomiBTLESens_ThermoHygroSensHandle0x24($$) {
  522. ### Thermo/Hygro Sens - Read Firmware Data
  523. my ($hash,$notification) = @_;
  524. my $name = $hash->{NAME};
  525. my %readings;
  526. Log3 $name, 4, "XiaomiBTLESens ($name) - Thermo/Hygro Sens Handle0x24";
  527. $notification =~ s/\s+//g;
  528. $readings{'firmware'} = pack('H*',$notification);
  529. $hash->{helper}{CallBattery} = 0;
  530. return \%readings;
  531. }
  532. sub XiaomiBTLESens_ThermoHygroSensHandle0x3($$) {
  533. ### Thermo/Hygro Sens - Read and Write Devicename
  534. my ($hash,$notification) = @_;
  535. my $name = $hash->{NAME};
  536. my %readings;
  537. Log3 $name, 4, "XiaomiBTLESens ($name) - Thermo/Hygro Sens Handle0x3";
  538. $notification =~ s/\s+//g;
  539. $readings{'devicename'} = pack('H*',$notification);
  540. $hash->{helper}{CallBattery} = 0;
  541. return \%readings;
  542. }
  543. sub XiaomiBTLESens_WriteReadings($$) {
  544. my ($hash,$readings) = @_;
  545. my $name = $hash->{NAME};
  546. readingsBeginUpdate($hash);
  547. while( my ($r,$v) = each %{$readings} ) {
  548. readingsBulkUpdate($hash,$r,$v);
  549. }
  550. readingsBulkUpdateIfChanged($hash, "state", ($readings->{'lastGattError'}?'error':'active')) if( AttrVal($name,'model','none') eq 'flowerSens' );
  551. readingsBulkUpdateIfChanged($hash, "state", ($readings->{'lastGattError'}?'error':'T: '.ReadingsVal($name,'temperature',0).' H: '.ReadingsVal($name,'humidity',0)) ) if( AttrVal($name,'model','none') eq 'thermoHygroSens' );
  552. readingsEndUpdate($hash,1);
  553. if( AttrVal($name,'model','none') eq 'flowerSens') {
  554. if( defined($readings->{temperature}) ) {
  555. DoTrigger($name, 'minFertility ' . ($readings->{fertility}<AttrVal($name,'minFertility',0)?'low':'ok')) if( AttrVal($name,'minFertility','none') ne 'none' );
  556. DoTrigger($name, 'maxFertility ' . ($readings->{fertility}>AttrVal($name,'maxFertility',0)?'high':'ok')) if( AttrVal($name,'maxFertility','none') ne 'none' );
  557. DoTrigger($name, 'minMoisture ' . ($readings->{moisture}<AttrVal($name,'minMoisture',0)?'low':'ok')) if( AttrVal($name,'minMoisture','none') ne 'none' );
  558. DoTrigger($name, 'maxMoisture ' . ($readings->{moisture}>AttrVal($name,'maxMoisture',0)?'high':'ok')) if( AttrVal($name,'maxMoisture','none') ne 'none' );
  559. DoTrigger($name, 'minLux ' . ($readings->{lux}<AttrVal($name,'minLux',0)?'low':'ok')) if( AttrVal($name,'minLux','none') ne 'none' );
  560. DoTrigger($name, 'maxLux ' . ($readings->{lux}>AttrVal($name,'maxLux',0)?'high':'ok')) if( AttrVal($name,'maxLux','none') ne 'none' );
  561. }
  562. }
  563. if( defined($readings->{temperature}) ) {
  564. DoTrigger($name, 'minTemp ' . ($readings->{temperature}<AttrVal($name,'minTemp',0)?'low':'ok')) if( AttrVal($name,'minTemp','none') ne 'none' );
  565. DoTrigger($name, 'maxTemp ' . ($readings->{temperature}>AttrVal($name,'maxTemp',0)?'high':'ok')) if( AttrVal($name,'maxTemp','none') ne 'none' );
  566. }
  567. Log3 $name, 4, "XiaomiBTLESens ($name) - WriteReadings: Readings were written";
  568. $hash->{helper}{CallSensDataCounter} = 0;
  569. XiaomiBTLESens_stateRequest($hash) if( $hash->{helper}{CallBattery} == 1 );
  570. }
  571. sub XiaomiBTLESens_ProcessingErrors($$) {
  572. my ($hash,$notification) = @_;
  573. my $name = $hash->{NAME};
  574. my %readings;
  575. Log3 $name, 4, "XiaomiBTLESens ($name) - ProcessingErrors";
  576. $readings{'lastGattError'} = $notification;
  577. XiaomiBTLESens_WriteReadings($hash,\%readings);
  578. }
  579. #### my little Helper
  580. sub XiaomiBTLESens_encodeJSON($) {
  581. my $gtResult = shift;
  582. chomp($gtResult);
  583. my %response = (
  584. 'gtResult' => $gtResult
  585. );
  586. return encode_json(\%response);
  587. }
  588. ## Routinen damit Firmware und Batterie nur alle X male statt immer aufgerufen wird
  589. sub XiaomiBTLESens_CallBattery_Timestamp($) {
  590. my $hash = shift;
  591. # get timestamp
  592. $hash->{helper}{updateTimeCallBattery} = gettimeofday(); # in seconds since the epoch
  593. $hash->{helper}{updateTimestampCallBattery} = FmtDateTime(gettimeofday());
  594. }
  595. sub XiaomiBTLESens_CallBattery_UpdateTimeAge($) {
  596. my $hash = shift;
  597. $hash->{helper}{updateTimeCallBattery} = 0 if( not defined($hash->{helper}{updateTimeCallBattery}) );
  598. my $UpdateTimeAge = gettimeofday() - $hash->{helper}{updateTimeCallBattery};
  599. return $UpdateTimeAge;
  600. }
  601. sub XiaomiBTLESens_CallBattery_IsUpdateTimeAgeToOld($$) {
  602. my ($hash,$maxAge) = @_;;
  603. return (XiaomiBTLESens_CallBattery_UpdateTimeAge($hash)>$maxAge ? 1:0);
  604. }
  605. sub XiaomiBTLESens_CreateDevicenameHEX($) {
  606. my $devicename = shift;
  607. my $devicenameHex = unpack("H*", $devicename);
  608. return $devicenameHex;
  609. }
  610. sub CometBlueBTLE_CmdlinePreventGrepFalsePositive($) {
  611. # https://stackoverflow.com/questions/9375711/more-elegant-ps-aux-grep-v-grep
  612. # Given abysmal (since external-command-based) performance in the first place, we'd better
  613. # avoid an *additional* grep process plus pipe...
  614. my $cmdline = shift;
  615. $cmdline =~ s/(.)(.*)/[$1]$2/;
  616. return $cmdline;
  617. }
  618. 1;
  619. =pod
  620. =item device
  621. =item summary Modul to retrieves data from a Xiaomi BTLE Sensors
  622. =item summary_DE Modul um Daten vom Xiaomi BTLE Sensoren aus zu lesen
  623. =begin html
  624. <a name="XiaomiBTLESens"></a>
  625. <h3>Xiaomi BTLE Sensor</h3>
  626. <ul>
  627. <u><b>XiaomiBTLESens - Retrieves data from a Xiaomi BTLE Sensor</b></u>
  628. <br>
  629. With this module it is possible to read the data from a sensor and to set it as reading.</br>
  630. Gatttool and hcitool is required to use this modul. (apt-get install bluez)
  631. <br><br>
  632. <a name="XiaomiBTLESensdefine"></a>
  633. <b>Define</b>
  634. <ul><br>
  635. <code>define &lt;name&gt; XiaomiBTLESens &lt;BT-MAC&gt;</code>
  636. <br><br>
  637. Example:
  638. <ul><br>
  639. <code>define Weihnachtskaktus XiaomiBTLESens C4:7C:8D:62:42:6F</code><br>
  640. </ul>
  641. <br>
  642. This statement creates a XiaomiBTLESens with the name Weihnachtskaktus and the Bluetooth Mac C4:7C:8D:62:42:6F.<br>
  643. After the device has been created and the model attribut is set, the current data of the Xiaomi BTLE Sensor is automatically read from the device.
  644. </ul>
  645. <br><br>
  646. <a name="XiaomiBTLESensreadings"></a>
  647. <b>Readings</b>
  648. <ul>
  649. <li>state - Status of the flower sensor or error message if any errors.</li>
  650. <li>batteryState - current battery state dependent on batteryLevel.</li>
  651. <li>batteryPercent - current battery level in percent.</li>
  652. <li>fertility - Values for the fertilizer content</li>
  653. <li>firmware - current device firmware</li>
  654. <li>lux - current light intensity</li>
  655. <li>moisture - current moisture content</li>
  656. <li>temperature - current temperature</li>
  657. </ul>
  658. <br><br>
  659. <a name="XiaomiBTLESensset"></a>
  660. <b>Set</b>
  661. <ul>
  662. <li>devicename - set a devicename</li>
  663. <li>resetBatteryTimestamp - when the battery was changed</li>
  664. <br>
  665. </ul>
  666. <br><br>
  667. <a name="XiaomiBTLESensget"></a>
  668. <b>Get</b>
  669. <ul>
  670. <li>sensorData - retrieves the current data of the Xiaomi sensor</li>
  671. <li>devicename - fetch devicename</li>
  672. <li>firmware - fetch firmware</li>
  673. <br>
  674. </ul>
  675. <br><br>
  676. <a name="XiaomiBTLESensattribut"></a>
  677. <b>Attributes</b>
  678. <ul>
  679. <li>disable - disables the device</li>
  680. <li>disabledForIntervals - disable device for interval time (13:00-18:30 or 13:00-18:30 22:00-23:00)</li>
  681. <li>interval - interval in seconds for statusRequest</li>
  682. <li>minFertility - min fertility value for low warn event</li>
  683. <li>hciDevice - select bluetooth dongle device</li>
  684. <li>model - set model type</li>
  685. <li>maxFertility - max fertility value for High warn event</li>
  686. <li>minMoisture - min moisture value for low warn event</li>
  687. <li>maxMoisture - max moisture value for High warn event</li>
  688. <li>minTemp - min temperature value for low warn event</li>
  689. <li>maxTemp - max temperature value for high warn event</li>
  690. <li>minlux - min lux value for low warn event</li>
  691. <li>maxlux - max lux value for high warn event
  692. <br>
  693. Event Example for min/max Value's: 2017-03-16 11:08:05 XiaomiBTLESens Dracaena minMoisture low<br>
  694. Event Example for min/max Value's: 2017-03-16 11:08:06 XiaomiBTLESens Dracaena maxTemp high</li>
  695. <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>
  696. <li>batteryFirmwareAge - how old can the reading befor fetch new data</li>
  697. <li>blockingCallLoglevel - Blocking.pm Loglevel for BlockingCall Logoutput</li>
  698. </ul>
  699. </ul>
  700. =end html
  701. =begin html_DE
  702. <a name="XiaomiBTLESens"></a>
  703. <h3>Xiaomi BTLE Sensor</h3>
  704. <ul>
  705. <u><b>XiaomiBTLESens - liest Daten von einem Xiaomi BTLE Sensor</b></u>
  706. <br />
  707. Dieser Modul liest Daten von einem Sensor und legt sie in den Readings ab.<br />
  708. Auf dem (Linux) FHEM-Server werden gatttool und hcitool vorausgesetzt. (sudo apt install bluez)
  709. <br /><br />
  710. <a name="XiaomiBTLESensdefine"></a>
  711. <b>Define</b>
  712. <ul><br />
  713. <code>define &lt;name&gt; XiaomiBTLESens &lt;BT-MAC&gt;</code>
  714. <br /><br />
  715. Beispiel:
  716. <ul><br />
  717. <code>define Weihnachtskaktus XiaomiBTLESens C4:7C:8D:62:42:6F</code><br />
  718. </ul>
  719. <br />
  720. Der Befehl legt ein Device vom Typ XiaomiBTLESens an mit dem Namen Weihnachtskaktus und der Bluetooth MAC C4:7C:8D:62:42:6F.<br />
  721. Nach dem Anlegen des Device und setzen des korrekten model Attributes werden umgehend und automatisch die aktuellen Daten vom betroffenen Xiaomi BTLE Sensor gelesen.
  722. </ul>
  723. <br /><br />
  724. <a name="XiaomiBTLESensreadings"></a>
  725. <b>Readings</b>
  726. <ul>
  727. <li>state - Status des BTLE Sensor oder eine Fehlermeldung falls Fehler beim letzten Kontakt auftraten.</li>
  728. <li>batteryState - aktueller Batterie-Status in Abhängigkeit vom Wert batteryLevel.</li>
  729. <li>batteryPercent - aktueller Ladestand der Batterie in Prozent.</li>
  730. <li>fertility - Wert des Fruchtbarkeitssensors (Bodenleitf&auml;higkeit)</li>
  731. <li>firmware - aktuelle Firmware-Version des BTLE Sensor</li>
  732. <li>lastGattError - Fehlermeldungen vom gatttool</li>
  733. <li>lux - aktuelle Lichtintensit&auml;t</li>
  734. <li>moisture - aktueller Feuchtigkeitswert</li>
  735. <li>temperature - aktuelle Temperatur</li>
  736. </ul>
  737. <br /><br />
  738. <a name="XiaomiBTLESensset"></a>
  739. <b>Set</b>
  740. <ul>
  741. <li>resetBatteryTimestamp - wenn die Batterie gewechselt wurde</li>
  742. <li>devicename - setzt einen Devicenamen</li>
  743. <br />
  744. </ul>
  745. <br /><br />
  746. <a name="XiaomiBTLESensGet"></a>
  747. <b>Get</b>
  748. <ul>
  749. <li>sensorData - aktive Abfrage der Sensors Werte</li>
  750. <li>devicename - liest den Devicenamen aus</li>
  751. <li>firmware - liest die Firmeware aus</li>
  752. <br />
  753. </ul>
  754. <br /><br />
  755. <a name="XiaomiBTLESensattribut"></a>
  756. <b>Attribute</b>
  757. <ul>
  758. <li>disable - deaktiviert das Device</li>
  759. <li>interval - Interval in Sekunden zwischen zwei Abfragen</li>
  760. <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>
  761. <li>minFertility - min Fruchtbarkeits-Grenzwert f&uuml;r ein Ereignis minFertility low </li>
  762. <li>hciDevice - Auswahl bei mehreren Bluetooth Dongeln</li>
  763. <li>model - setzt das Model</li>
  764. <li>maxFertility - max Fruchtbarkeits-Grenzwert f&uuml;r ein Ereignis maxFertility high </li>
  765. <li>minMoisture - min Feuchtigkeits-Grenzwert f&uuml;r ein Ereignis minMoisture low </li>
  766. <li>maxMoisture - max Feuchtigkeits-Grenzwert f&uuml;r ein Ereignis maxMoisture high </li>
  767. <li>minTemp - min Temperatur-Grenzwert f&uuml;r ein Ereignis minTemp low </li>
  768. <li>maxTemp - max Temperatur-Grenzwert f&uuml;r ein Ereignis maxTemp high </li>
  769. <li>minlux - min Helligkeits-Grenzwert f&uuml;r ein Ereignis minlux low </li>
  770. <li>maxlux - max Helligkeits-Grenzwert f&uuml;r ein Ereignis maxlux high
  771. <br /><br />Beispiele f&uuml;r min/max-Ereignisse:<br />
  772. 2017-03-16 11:08:05 XiaomiBTLESens Dracaena minMoisture low<br />
  773. 2017-03-16 11:08:06 XiaomiBTLESens Dracaena maxTemp high<br /><br /></li>
  774. <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>
  775. <li>batteryFirmwareAge - wie alt soll der Timestamp des Readings sein bevor eine Aktuallisierung statt findet</li>
  776. <li>blockingCallLoglevel - Blocking.pm Loglevel für BlockingCall Logausgaben</li>
  777. </ul>
  778. </ul>
  779. =end html_DE
  780. =cut