73_GardenaSmartBridge.pm 34 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901902903904905906907908909910911912913914915916917918919920921922923924925926927928929930931932933934935936937938939940941942943944945946947948949950951952953954955956957958959960961962963964965966967968969970971972973974975976977978979980981982983984985986987988989990991992993994995996997998999100010011002100310041005100610071008100910101011101210131014101510161017101810191020102110221023102410251026102710281029103010311032103310341035103610371038103910401041104210431044104510461047104810491050105110521053105410551056105710581059106010611062106310641065106610671068106910701071107210731074107510761077107810791080108110821083108410851086108710881089109010911092109310941095109610971098109911001101110211031104110511061107110811091110111111121113111411151116111711181119112011211122112311241125112611271128112911301131113211331134113511361137
  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. # Special thanks goes to comitters:
  9. # - Michael (mbrak) Thanks for Commandref
  10. # - Matthias (Kenneth) Thanks for Wiki entry
  11. # - BioS Thanks for predefined start points Code
  12. # - fettgu Thanks for Debugging Irrigation Control data flow
  13. #
  14. #
  15. # This script is free software; you can redistribute it and/or modify
  16. # it under the terms of the GNU General Public License as published by
  17. # the Free Software Foundation; either version 2 of the License, or
  18. # any later version.
  19. #
  20. # The GNU General Public License can be found at
  21. # http://www.gnu.org/copyleft/gpl.html.
  22. # A copy is found in the textfile GPL.txt and important notices to the license
  23. # from the author is found in LICENSE.txt distributed with these scripts.
  24. #
  25. # This script is distributed in the hope that it will be useful,
  26. # but WITHOUT ANY WARRANTY; without even the implied warranty of
  27. # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
  28. # GNU General Public License for more details.
  29. #
  30. #
  31. # $Id: 73_GardenaSmartBridge.pm 17555 2018-10-17 20:06:58Z CoolTux $
  32. #
  33. ###############################################################################
  34. ##
  35. ##
  36. ## Das JSON Modul immer in einem eval aufrufen
  37. # $data = eval{decode_json($data)};
  38. #
  39. # if($@){
  40. # Log3($SELF, 2, "$TYPE ($SELF) - error while request: $@");
  41. #
  42. # readingsSingleUpdate($hash, "state", "error", 1);
  43. #
  44. # return;
  45. # }
  46. #
  47. #
  48. ###### Wichtige Notizen
  49. #
  50. # apt-get install libio-socket-ssl-perl
  51. # http://www.dxsdata.com/de/2016/07/php-class-for-gardena-smart-system-api/
  52. #
  53. ##
  54. ##
  55. package main;
  56. use strict;
  57. use warnings;
  58. my $version = "1.4.0";
  59. sub GardenaSmartBridge_Initialize($) {
  60. my ($hash) = @_;
  61. # Provider
  62. $hash->{WriteFn} = "GardenaSmartBridge::Write";
  63. $hash->{Clients} = ":GardenaSmartDevice:";
  64. $hash->{MatchList} = { "1:GardenaSmartDevice" => '^{"id":".*' };
  65. # Consumer
  66. $hash->{SetFn} = "GardenaSmartBridge::Set";
  67. $hash->{DefFn} = "GardenaSmartBridge::Define";
  68. $hash->{UndefFn} = "GardenaSmartBridge::Undef";
  69. $hash->{DeleteFn} = "GardenaSmartBridge::Delete";
  70. $hash->{RenameFn} = "GardenaSmartBridge::Rename";
  71. $hash->{NotifyFn} = "GardenaSmartBridge::Notify";
  72. $hash->{AttrFn} = "GardenaSmartBridge::Attr";
  73. $hash->{AttrList} =
  74. "debugJSON:0,1 "
  75. . "disable:1 "
  76. . "interval "
  77. . "disabledForIntervals "
  78. . "gardenaAccountEmail "
  79. . $readingFnAttributes;
  80. foreach my $d ( sort keys %{ $modules{GardenaSmartBridge}{defptr} } ) {
  81. my $hash = $modules{GardenaSmartBridge}{defptr}{$d};
  82. $hash->{VERSION} = $version;
  83. }
  84. }
  85. package GardenaSmartBridge;
  86. use GPUtils qw(:all)
  87. ; # wird für den Import der FHEM Funktionen aus der fhem.pl benötigt
  88. my $missingModul = "";
  89. use strict;
  90. use warnings;
  91. use POSIX;
  92. use HttpUtils;
  93. eval "use Encode qw(encode encode_utf8 decode_utf8);1"
  94. or $missingModul .= "Encode ";
  95. eval "use JSON;1" or $missingModul .= "JSON ";
  96. eval "use IO::Socket::SSL;1" or $missingModul .= "IO::Socket::SSL ";
  97. BEGIN {
  98. GP_Import(
  99. qw(readingsSingleUpdate
  100. readingsBulkUpdate
  101. readingsBulkUpdateIfChanged
  102. readingsBeginUpdate
  103. readingsEndUpdate
  104. Log3
  105. CommandAttr
  106. AttrVal
  107. ReadingsVal
  108. CommandDefMod
  109. modules
  110. setKeyValue
  111. getKeyValue
  112. getUniqueId
  113. RemoveInternalTimer
  114. InternalTimer
  115. defs
  116. init_done
  117. IsDisabled
  118. deviceEvents
  119. HttpUtils_NonblockingGet
  120. gettimeofday
  121. Dispatch)
  122. );
  123. }
  124. sub Define($$) {
  125. my ( $hash, $def ) = @_;
  126. my @a = split( "[ \t][ \t]*", $def );
  127. return "too few parameters: define <NAME> GardenaSmartBridge"
  128. if ( @a != 2 );
  129. return
  130. "Cannot define Gardena Bridge device. Perl modul ${missingModul}is missing."
  131. if ($missingModul);
  132. my $name = $a[0];
  133. $hash->{BRIDGE} = 1;
  134. $hash->{URL} = 'https://sg-api.dss.husqvarnagroup.net/sg-1';
  135. $hash->{VERSION} = $version;
  136. $hash->{INTERVAL} = 300;
  137. $hash->{NOTIFYDEV} = "global,$name";
  138. CommandAttr( undef, $name . ' room GardenaSmart' )
  139. if ( AttrVal( $name, 'room', 'none' ) eq 'none' );
  140. readingsSingleUpdate( $hash, 'token', 'none', 1 );
  141. readingsSingleUpdate( $hash, 'state', 'initialized', 1 );
  142. Log3 $name, 3, "GardenaSmartBridge ($name) - defined GardenaSmartBridge";
  143. $modules{GardenaSmartBridge}{defptr}{BRIDGE} = $hash;
  144. return undef;
  145. }
  146. sub Undef($$) {
  147. my ( $hash, $name ) = @_;
  148. RemoveInternalTimer($hash);
  149. delete $modules{GardenaSmartBridge}{defptr}{BRIDGE}
  150. if ( defined( $modules{GardenaSmartBridge}{defptr}{BRIDGE} ) );
  151. return undef;
  152. }
  153. sub Delete($$) {
  154. my ( $hash, $name ) = @_;
  155. setKeyValue( $hash->{TYPE} . "_" . $name . "_passwd", undef );
  156. return undef;
  157. }
  158. sub Attr(@) {
  159. my ( $cmd, $name, $attrName, $attrVal ) = @_;
  160. my $hash = $defs{$name};
  161. if ( $attrName eq "disable" ) {
  162. if ( $cmd eq "set" and $attrVal eq "1" ) {
  163. RemoveInternalTimer($hash);
  164. readingsSingleUpdate( $hash, "state", "inactive", 1 );
  165. Log3 $name, 3, "GardenaSmartBridge ($name) - disabled";
  166. }
  167. elsif ( $cmd eq "del" ) {
  168. readingsSingleUpdate( $hash, "state", "active", 1 );
  169. Log3 $name, 3, "GardenaSmartBridge ($name) - enabled";
  170. }
  171. }
  172. elsif ( $attrName eq "disabledForIntervals" ) {
  173. if ( $cmd eq "set" ) {
  174. return
  175. "check disabledForIntervals Syntax HH:MM-HH:MM or 'HH:MM-HH:MM HH:MM-HH:MM ...'"
  176. unless ( $attrVal =~ /^((\d{2}:\d{2})-(\d{2}:\d{2})\s?)+$/ );
  177. Log3 $name, 3, "GardenaSmartBridge ($name) - disabledForIntervals";
  178. }
  179. elsif ( $cmd eq "del" ) {
  180. readingsSingleUpdate( $hash, "state", "active", 1 );
  181. Log3 $name, 3, "GardenaSmartBridge ($name) - enabled";
  182. }
  183. }
  184. elsif ( $attrName eq "interval" ) {
  185. if ( $cmd eq "set" ) {
  186. RemoveInternalTimer($hash);
  187. return "Interval must be greater than 0"
  188. unless ( $attrVal > 0 );
  189. $hash->{INTERVAL} = $attrVal;
  190. Log3 $name, 3,
  191. "GardenaSmartBridge ($name) - set interval: $attrVal";
  192. }
  193. elsif ( $cmd eq "del" ) {
  194. RemoveInternalTimer($hash);
  195. $hash->{INTERVAL} = 300;
  196. Log3 $name, 3,
  197. "GardenaSmartBridge ($name) - delete User interval and set default: 300";
  198. }
  199. }
  200. return undef;
  201. }
  202. sub Notify($$) {
  203. my ( $hash, $dev ) = @_;
  204. my $name = $hash->{NAME};
  205. return if ( IsDisabled($name) );
  206. my $devname = $dev->{NAME};
  207. my $devtype = $dev->{TYPE};
  208. my $events = deviceEvents( $dev, 1 );
  209. return if ( !$events );
  210. getToken($hash)
  211. if (
  212. (
  213. $devtype eq 'Global'
  214. and (
  215. grep /^INITIALIZED$/,
  216. @{$events} or grep /^REREADCFG$/,
  217. @{$events} or grep /^DEFINED.$name$/,
  218. @{$events} or grep /^MODIFIED.$name$/,
  219. @{$events} or grep /^ATTR.$name.gardenaAccountEmail.+/,
  220. @{$events}
  221. )
  222. )
  223. or (
  224. $devtype eq 'GardenaSmartBridge'
  225. and (
  226. grep /^gardenaAccountPassword.+/,
  227. @{$events} or ReadingsVal( '$devname', 'token', '' ) eq 'none'
  228. )
  229. )
  230. );
  231. getDevices($hash)
  232. if (
  233. $devtype eq 'Global'
  234. and (
  235. grep /^DELETEATTR.$name.disable$/,
  236. @{$events} or grep /^ATTR.$name.disable.0$/,
  237. @{$events} or grep /^DELETEATTR.$name.interval$/,
  238. @{$events} or grep /^ATTR.$name.interval.[0-9]+/,
  239. @{$events}
  240. )
  241. and $init_done
  242. );
  243. if (
  244. $devtype eq 'GardenaSmartBridge'
  245. and (
  246. grep /^state:.connected.to.cloud$/,
  247. @{$events} or grep /^lastRequestState:.request_error$/,
  248. @{$events}
  249. )
  250. )
  251. {
  252. InternalTimer( gettimeofday() + $hash->{INTERVAL},
  253. "GardenaSmartBridge::getDevices", $hash );
  254. Log3 $name, 4,
  255. "GardenaSmartBridge ($name) - set internal timer function for recall getDevices sub";
  256. }
  257. return;
  258. }
  259. sub Set($@) {
  260. my ( $hash, $name, $cmd, @args ) = @_;
  261. if ( lc $cmd eq 'getdevicesstate' ) {
  262. getDevices($hash);
  263. }
  264. elsif ( lc $cmd eq 'gettoken' ) {
  265. return "please set Attribut gardenaAccountEmail first"
  266. if ( AttrVal( $name, 'gardenaAccountEmail', 'none' ) eq 'none' );
  267. return "please set gardenaAccountPassword first"
  268. if ( not defined( ReadPassword($hash) ) );
  269. return "token is up to date"
  270. if ( defined( $hash->{helper}{session_id} ) );
  271. getToken($hash);
  272. }
  273. elsif ( lc $cmd eq 'gardenaaccountpassword' ) {
  274. return "please set Attribut gardenaAccountEmail first"
  275. if ( AttrVal( $name, 'gardenaAccountEmail', 'none' ) eq 'none' );
  276. return "usage: $cmd <password>" if ( @args != 1 );
  277. my $passwd = join( ' ', @args );
  278. StorePassword( $hash, $passwd );
  279. }
  280. elsif ( lc $cmd eq 'deleteaccountpassword' ) {
  281. return "usage: $cmd <password>" if ( @args != 0 );
  282. DeletePassword($hash);
  283. }
  284. else {
  285. my $list = "getDevicesState:noArg getToken:noArg"
  286. if ( defined( ReadPassword($hash) ) );
  287. $list .= " gardenaAccountPassword"
  288. if ( not defined( ReadPassword($hash) ) );
  289. $list .= " deleteAccountPassword:noArg"
  290. if ( defined( ReadPassword($hash) ) );
  291. return "Unknown argument $cmd, choose one of $list";
  292. }
  293. return undef;
  294. }
  295. sub Write($@) {
  296. my ( $hash, $payload, $deviceId, $abilities ) = @_;
  297. my $name = $hash->{NAME};
  298. my ( $session_id, $header, $uri, $method );
  299. ( $payload, $session_id, $header, $uri, $method, $deviceId, $abilities ) =
  300. createHttpValueStrings( $hash, $payload, $deviceId, $abilities );
  301. HttpUtils_NonblockingGet(
  302. {
  303. url => $hash->{URL} . $uri,
  304. timeout => 15,
  305. hash => $hash,
  306. device_id => $deviceId,
  307. data => $payload,
  308. method => $method,
  309. header => $header,
  310. doTrigger => 1,
  311. callback => \&ErrorHandling
  312. }
  313. );
  314. Log3 $name, 4,
  315. "GardenaSmartBridge ($name) - Send with URL: $hash->{URL}$uri, HEADER: secret!, DATA: secret!, METHOD: $method";
  316. }
  317. sub ErrorHandling($$$) {
  318. my ( $param, $err, $data ) = @_;
  319. my $hash = $param->{hash};
  320. my $name = $hash->{NAME};
  321. my $dhash = $hash;
  322. $dhash = $modules{GardenaSmartDevice}{defptr}{ $param->{'device_id'} }
  323. unless ( not defined( $param->{'device_id'} ) );
  324. my $dname = $dhash->{NAME};
  325. if ( defined($err) ) {
  326. if ( $err ne "" ) {
  327. readingsBeginUpdate($dhash);
  328. readingsBulkUpdate( $dhash, "state", "$err" )
  329. if ( ReadingsVal( $dname, "state", 1 ) ne "initialized" );
  330. readingsBulkUpdate( $dhash, "lastRequestState", "request_error",
  331. 1 );
  332. if ( $err =~ /timed out/ ) {
  333. Log3 $dname, 5,
  334. "GardenaSmartBridge ($dname) - RequestERROR: connect to gardena cloud is timed out. check network";
  335. }
  336. elsif ($err =~ /Keine Route zum Zielrechner/
  337. or $err =~ /no route to target/ )
  338. {
  339. Log3 $dname, 5,
  340. "GardenaSmartBridge ($dname) - RequestERROR: no route to target. bad network configuration or network is down";
  341. }
  342. else {
  343. Log3 $dname, 5,
  344. "GardenaSmartBridge ($dname) - RequestERROR: $err";
  345. }
  346. readingsEndUpdate( $dhash, 1 );
  347. Log3 $dname, 5,
  348. "GardenaSmartBridge ($dname) - RequestERROR: GardenaSmartBridge RequestErrorHandling: error while requesting gardena cloud: $err";
  349. delete $dhash->{helper}{deviceAction}
  350. if ( defined( $dhash->{helper}{deviceAction} ) );
  351. return;
  352. }
  353. }
  354. if ( $data eq "" and exists( $param->{code} ) and $param->{code} != 200 ) {
  355. readingsBeginUpdate($dhash);
  356. readingsBulkUpdate( $dhash, "state", $param->{code}, 1 )
  357. if ( ReadingsVal( $dname, "state", 1 ) ne "initialized" );
  358. readingsBulkUpdateIfChanged( $dhash, "lastRequestState",
  359. "request_error", 1 );
  360. if ( $param->{code} == 401 and $hash eq $dhash ) {
  361. if ( ReadingsVal( $dname, 'token', 'none' ) eq 'none' ) {
  362. readingsBulkUpdate( $dhash, "state", "no token available", 1 );
  363. readingsBulkUpdateIfChanged( $dhash, "lastRequestState",
  364. "no token available", 1 );
  365. }
  366. Log3 $dname, 5,
  367. "GardenaSmartBridge ($dname) - RequestERROR: " . $param->{code};
  368. }
  369. elsif ( $param->{code} == 204
  370. and $dhash ne $hash
  371. and defined( $dhash->{helper}{deviceAction} ) )
  372. {
  373. readingsBulkUpdate( $dhash, "state", "the command is processed",
  374. 1 );
  375. InternalTimer( gettimeofday() + 5, "GardenaSmartBridge::getDevices", $hash, 1 );
  376. }
  377. elsif ( $param->{code} != 200 ) {
  378. Log3 $dname, 5,
  379. "GardenaSmartBridge ($dname) - RequestERROR: " . $param->{code};
  380. }
  381. readingsEndUpdate( $dhash, 1 );
  382. Log3 $dname, 5,
  383. "GardenaSmartBridge ($dname) - RequestERROR: received http code "
  384. . $param->{code}
  385. . " without any data after requesting gardena cloud";
  386. delete $dhash->{helper}{deviceAction}
  387. if ( defined( $dhash->{helper}{deviceAction} ) );
  388. return;
  389. }
  390. if (
  391. (
  392. ( $data =~ /Error/ )
  393. or defined( eval { decode_json($data) }->{errors} )
  394. )
  395. and exists( $param->{code} )
  396. )
  397. {
  398. readingsBeginUpdate($dhash);
  399. readingsBulkUpdate( $dhash, "state", $param->{code}, 1 )
  400. if ( ReadingsVal( $dname, "state", 0 ) ne "initialized" );
  401. readingsBulkUpdate( $dhash, "lastRequestState", "request_error", 1 );
  402. if ( $param->{code} == 400 ) {
  403. if ( eval { decode_json($data) } ) {
  404. if ( ref( eval { decode_json($data) }->{errors} ) eq "ARRAY"
  405. and defined( eval { decode_json($data) }->{errors} ) )
  406. {
  407. readingsBulkUpdate(
  408. $dhash,
  409. "state",
  410. eval { decode_json($data) }->{errors}[0]{error} . ' '
  411. . eval { decode_json($data) }->{errors}[0]{attribute},
  412. 1
  413. );
  414. readingsBulkUpdate(
  415. $dhash,
  416. "lastRequestState",
  417. eval { decode_json($data) }->{errors}[0]{error} . ' '
  418. . eval { decode_json($data) }->{errors}[0]{attribute},
  419. 1
  420. );
  421. Log3 $dname, 5,
  422. "GardenaSmartBridge ($dname) - RequestERROR: "
  423. . eval { decode_json($data) }->{errors}[0]{error} . " "
  424. . eval { decode_json($data) }->{errors}[0]{attribute};
  425. }
  426. }
  427. else {
  428. readingsBulkUpdate( $dhash, "lastRequestState",
  429. "Error 400 Bad Request", 1 );
  430. Log3 $dname, 5,
  431. "GardenaSmartBridge ($dname) - RequestERROR: Error 400 Bad Request";
  432. }
  433. }
  434. elsif ( $param->{code} == 503 ) {
  435. Log3 $dname, 5,
  436. "GardenaSmartBridge ($dname) - RequestERROR: Error 503 Service Unavailable";
  437. readingsBulkUpdate( $dhash, "state", "Service Unavailable", 1 );
  438. readingsBulkUpdate( $dhash, "lastRequestState",
  439. "Error 503 Service Unavailable", 1 );
  440. }
  441. elsif ( $param->{code} == 404 ) {
  442. if ( defined( $dhash->{helper}{deviceAction} ) and $dhash ne $hash )
  443. {
  444. readingsBulkUpdate( $dhash, "state", "device Id not found", 1 );
  445. readingsBulkUpdate( $dhash, "lastRequestState",
  446. "device id not found", 1 );
  447. }
  448. Log3 $dname, 5,
  449. "GardenaSmartBridge ($dname) - RequestERROR: Error 404 Not Found";
  450. }
  451. elsif ( $param->{code} == 500 ) {
  452. Log3 $dname, 5,
  453. "GardenaSmartBridge ($dname) - RequestERROR: check the ???";
  454. }
  455. else {
  456. Log3 $dname, 5,
  457. "GardenaSmartBridge ($dname) - RequestERROR: http error "
  458. . $param->{code};
  459. }
  460. readingsEndUpdate( $dhash, 1 );
  461. Log3 $dname, 5,
  462. "GardenaSmartBridge ($dname) - RequestERROR: received http code "
  463. . $param->{code}
  464. . " receive Error after requesting gardena cloud";
  465. delete $dhash->{helper}{deviceAction}
  466. if ( defined( $dhash->{helper}{deviceAction} ) );
  467. return;
  468. }
  469. readingsSingleUpdate( $hash, 'state', 'connected to cloud', 1 )
  470. if ( defined( $hash->{helper}{locations_id} ) );
  471. ResponseProcessing( $hash, $data );
  472. }
  473. sub ResponseProcessing($$) {
  474. my ( $hash, $json ) = @_;
  475. my $name = $hash->{NAME};
  476. my $decode_json = eval { decode_json($json) };
  477. if ($@) {
  478. Log3 $name, 3,
  479. "GardenaSmartBridge ($name) - JSON error while request: $@";
  480. if ( AttrVal( $name, 'debugJSON', 0 ) == 1 ) {
  481. readingsBeginUpdate($hash);
  482. readingsBulkUpdate( $hash, 'JSON_ERROR', $@, 1 );
  483. readingsBulkUpdate( $hash, 'JSON_ERROR_STRING', $json, 1 );
  484. readingsEndUpdate( $hash, 1 );
  485. }
  486. }
  487. if ( defined( $decode_json->{sessions} ) and $decode_json->{sessions} ) {
  488. $hash->{helper}{session_id} = $decode_json->{sessions}{token};
  489. $hash->{helper}{user_id} = $decode_json->{sessions}{user_id};
  490. Write( $hash, undef, undef, undef );
  491. Log3 $name, 3, "GardenaSmartBridge ($name) - fetch locations id";
  492. readingsSingleUpdate( $hash, 'token', $hash->{helper}{session_id}, 1 );
  493. return;
  494. }
  495. elsif ( not defined( $hash->{helper}{locations_id} )
  496. and defined( $decode_json->{locations} )
  497. and ref( $decode_json->{locations} ) eq "ARRAY"
  498. and scalar( @{ $decode_json->{locations} } ) > 0 )
  499. {
  500. foreach my $location ( @{ $decode_json->{locations} } ) {
  501. $hash->{helper}{locations_id} = $location->{id};
  502. WriteReadings( $hash, $location );
  503. }
  504. Log3 $name, 3,
  505. "GardenaSmartBridge ($name) - processed locations id. ID is "
  506. . $hash->{helper}{locations_id};
  507. Write( $hash, undef, undef, undef );
  508. return;
  509. }
  510. elsif ( defined( $decode_json->{devices} )
  511. and ref( $decode_json->{devices} ) eq "ARRAY"
  512. and scalar( @{ $decode_json->{devices} } ) > 0 )
  513. {
  514. my @buffer = split( '"devices":\[', $json );
  515. my ( $json, $tail ) = ParseJSON( $hash, $buffer[1] );
  516. while ($json) {
  517. Log3 $name, 5,
  518. "GardenaSmartBridge ($name) - Decoding JSON message. Length: "
  519. . length($json)
  520. . " Content: "
  521. . $json;
  522. Log3 $name, 5,
  523. "GardenaSmartBridge ($name) - Vor Sub: Laenge JSON: "
  524. . length($json)
  525. . " Content: "
  526. . $json
  527. . " Tail: "
  528. . $tail;
  529. unless ( not defined($tail) and not($tail) ) {
  530. $decode_json = eval { decode_json($json) };
  531. if ($@) {
  532. Log3 $name, 3,
  533. "GardenaSmartBridge ($name) - JSON error while request: $@";
  534. }
  535. Dispatch( $hash, $json, undef )
  536. unless ( $decode_json->{category} eq 'gateway' );
  537. }
  538. ( $json, $tail ) = ParseJSON( $hash, $tail );
  539. Log3 $name, 5,
  540. "GardenaSmartBridge ($name) - Nach Sub: Laenge JSON: "
  541. . length($json)
  542. . " Content: "
  543. . $json
  544. . " Tail: "
  545. . $tail;
  546. }
  547. return;
  548. }
  549. Log3 $name, 3, "GardenaSmartBridge ($name) - no Match for processing data";
  550. }
  551. sub WriteReadings($$) {
  552. my ( $hash, $decode_json ) = @_;
  553. my $name = $hash->{NAME};
  554. if ( defined( $decode_json->{id} )
  555. and $decode_json->{id}
  556. and defined( $decode_json->{name} )
  557. and $decode_json->{name} )
  558. {
  559. readingsBeginUpdate($hash);
  560. readingsBulkUpdateIfChanged( $hash, 'name', $decode_json->{name} );
  561. readingsBulkUpdateIfChanged( $hash, 'authorized_user_ids',
  562. scalar( @{ $decode_json->{authorized_user_ids} } ) );
  563. readingsBulkUpdateIfChanged( $hash, 'devices',
  564. scalar( @{ $decode_json->{devices} } ) );
  565. while ( ( my ( $t, $v ) ) = each %{ $decode_json->{geo_position} } ) {
  566. $v = encode_utf8($v);
  567. readingsBulkUpdateIfChanged( $hash, $t, $v );
  568. }
  569. readingsBulkUpdateIfChanged( $hash, 'zones',
  570. scalar( @{ $decode_json->{zones} } ) );
  571. readingsEndUpdate( $hash, 1 );
  572. }
  573. Log3 $name, 3, "GardenaSmartBridge ($name) - readings would be written";
  574. }
  575. ####################################
  576. ####################################
  577. #### my little helpers Sub's #######
  578. sub getDevices($) {
  579. my $hash = shift;
  580. my $name = $hash->{NAME};
  581. RemoveInternalTimer($hash);
  582. if ( not IsDisabled($name) ) {
  583. Write( $hash, undef, undef, undef );
  584. Log3 $name, 4,
  585. "GardenaSmartBridge ($name) - fetch device list and device states";
  586. }
  587. else {
  588. readingsSingleUpdate( $hash, 'state', 'disabled', 1 );
  589. Log3 $name, 3, "GardenaSmartBridge ($name) - device is disabled";
  590. }
  591. }
  592. sub getToken($) {
  593. my $hash = shift;
  594. my $name = $hash->{NAME};
  595. return readingsSingleUpdate( $hash, 'state',
  596. 'please set Attribut gardenaAccountEmail first', 1 )
  597. if ( AttrVal( $name, 'gardenaAccountEmail', 'none' ) eq 'none' );
  598. return readingsSingleUpdate( $hash, 'state',
  599. 'please set gardena account password first', 1 )
  600. if ( not defined( ReadPassword($hash) ) );
  601. readingsSingleUpdate( $hash, 'state', 'get token', 1 );
  602. delete $hash->{helper}{session_id}
  603. if ( defined( $hash->{helper}{session_id} )
  604. and $hash->{helper}{session_id} );
  605. delete $hash->{helper}{user_id}
  606. if ( defined( $hash->{helper}{user_id} ) and $hash->{helper}{user_id} );
  607. delete $hash->{helper}{locations_id}
  608. if ( defined( $hash->{helper}{locations_id} )
  609. and $hash->{helper}{locations_id} );
  610. Write(
  611. $hash,
  612. '"sessions": {"email": "'
  613. . AttrVal( $name, 'gardenaAccountEmail', 'none' )
  614. . '","password": "'
  615. . ReadPassword($hash) . '"}',
  616. undef,
  617. undef
  618. );
  619. Log3 $name, 3,
  620. "GardenaSmartBridge ($name) - send credentials to fetch Token and locationId";
  621. }
  622. sub StorePassword($$) {
  623. my ( $hash, $password ) = @_;
  624. my $index = $hash->{TYPE} . "_" . $hash->{NAME} . "_passwd";
  625. my $key = getUniqueId() . $index;
  626. my $enc_pwd = "";
  627. if ( eval "use Digest::MD5;1" ) {
  628. $key = Digest::MD5::md5_hex( unpack "H*", $key );
  629. $key .= Digest::MD5::md5_hex($key);
  630. }
  631. for my $char ( split //, $password ) {
  632. my $encode = chop($key);
  633. $enc_pwd .= sprintf( "%.2x", ord($char) ^ ord($encode) );
  634. $key = $encode . $key;
  635. }
  636. my $err = setKeyValue( $index, $enc_pwd );
  637. return "error while saving the password - $err" if ( defined($err) );
  638. return "password successfully saved";
  639. }
  640. sub ReadPassword($) {
  641. my ($hash) = @_;
  642. my $name = $hash->{NAME};
  643. my $index = $hash->{TYPE} . "_" . $hash->{NAME} . "_passwd";
  644. my $key = getUniqueId() . $index;
  645. my ( $password, $err );
  646. Log3 $name, 4, "GardenaSmartBridge ($name) - Read password from file";
  647. ( $err, $password ) = getKeyValue($index);
  648. if ( defined($err) ) {
  649. Log3 $name, 3,
  650. "GardenaSmartBridge ($name) - unable to read password from file: $err";
  651. return undef;
  652. }
  653. if ( defined($password) ) {
  654. if ( eval "use Digest::MD5;1" ) {
  655. $key = Digest::MD5::md5_hex( unpack "H*", $key );
  656. $key .= Digest::MD5::md5_hex($key);
  657. }
  658. my $dec_pwd = '';
  659. for my $char ( map { pack( 'C', hex($_) ) } ( $password =~ /(..)/g ) ) {
  660. my $decode = chop($key);
  661. $dec_pwd .= chr( ord($char) ^ ord($decode) );
  662. $key = $decode . $key;
  663. }
  664. return $dec_pwd;
  665. }
  666. else {
  667. Log3 $name, 3, "GardenaSmartBridge ($name) - No password in file";
  668. return undef;
  669. }
  670. }
  671. sub Rename(@) {
  672. my ( $new, $old ) = @_;
  673. my $hash = $defs{$new};
  674. StorePassword( $hash, ReadPassword($hash) );
  675. setKeyValue( $hash->{TYPE} . "_" . $old . "_passwd", undef );
  676. return undef;
  677. }
  678. sub ParseJSON($$) {
  679. my ( $hash, $buffer ) = @_;
  680. my $name = $hash->{NAME};
  681. my $open = 0;
  682. my $close = 0;
  683. my $msg = '';
  684. my $tail = '';
  685. if ($buffer) {
  686. foreach my $c ( split //, $buffer ) {
  687. if ( $open == $close and $open > 0 ) {
  688. $tail .= $c;
  689. Log3 $name, 5,
  690. "GardenaSmartBridge ($name) - $open == $close and $open > 0";
  691. }
  692. elsif ( ( $open == $close ) and ( $c ne '{' ) ) {
  693. Log3 $name, 5,
  694. "GardenaSmartBridge ($name) - Garbage character before message: "
  695. . $c;
  696. }
  697. else {
  698. if ( $c eq '{' ) {
  699. $open++;
  700. }
  701. elsif ( $c eq '}' ) {
  702. $close++;
  703. }
  704. $msg .= $c;
  705. }
  706. }
  707. if ( $open != $close ) {
  708. $tail = $msg;
  709. $msg = '';
  710. }
  711. }
  712. Log3 $name, 5,
  713. "GardenaSmartBridge ($name) - return msg: $msg and tail: $tail";
  714. return ( $msg, $tail );
  715. }
  716. sub createHttpValueStrings($@) {
  717. my ( $hash, $payload, $deviceId, $abilities ) = @_;
  718. my $session_id = $hash->{helper}{session_id};
  719. my $header = "Content-Type: application/json";
  720. my $uri = '';
  721. my $method = 'POST';
  722. $header .= "\r\nX-Session: $session_id"
  723. if ( defined( $hash->{helper}{session_id} ) );
  724. $payload = '{' . $payload . '}' if ( defined($payload) );
  725. $payload = '{}' if ( not defined($payload) );
  726. if ( $payload eq '{}' ) {
  727. $method = 'GET';
  728. $uri .= '/locations/?user_id=' . $hash->{helper}{user_id}
  729. if ( not defined( $hash->{helper}{locations_id} ) );
  730. readingsSingleUpdate( $hash, 'state', 'fetch locationId', 1 )
  731. if ( not defined( $hash->{helper}{locations_id} ) );
  732. $uri .= '/sessions' if ( not defined( $hash->{helper}{session_id} ) );
  733. $uri .= '/devices'
  734. if ( not defined($abilities)
  735. and defined( $hash->{helper}{locations_id} ) );
  736. }
  737. $uri .= '/sessions' if ( not defined( $hash->{helper}{session_id} ) );
  738. if ( defined( $hash->{helper}{locations_id} ) ) {
  739. if ( defined($abilities) and $abilities eq 'mower_settings' ) {
  740. $method = 'PUT';
  741. my $dhash = $modules{GardenaSmartDevice}{defptr}{$deviceId};
  742. $uri .=
  743. '/devices/'
  744. . $deviceId
  745. . '/settings/'
  746. . $dhash->{helper}{STARTINGPOINTID}
  747. if ( defined($abilities)
  748. and defined($payload)
  749. and $abilities eq 'mower_settings' );
  750. }
  751. elsif ( defined($abilities)
  752. and defined($payload)
  753. and $abilities eq 'watering' )
  754. {
  755. my $valve_id;
  756. $method = 'PUT';
  757. if ( $payload =~ m#watering_timer_(\d)# ) {
  758. $valve_id = $1;
  759. }
  760. $uri .=
  761. '/devices/'
  762. . $deviceId
  763. . '/abilities/'
  764. . $abilities
  765. . '/properties/watering_timer_'
  766. . $valve_id;
  767. }
  768. else {
  769. $uri .=
  770. '/devices/' . $deviceId . '/abilities/' . $abilities . '/command'
  771. if ( defined($abilities) and defined($payload) );
  772. }
  773. $uri .= '?locationId=' . $hash->{helper}{locations_id};
  774. }
  775. return ( $payload, $session_id, $header, $uri, $method, $deviceId,
  776. $abilities );
  777. }
  778. sub DeletePassword($) {
  779. my $hash = shift;
  780. setKeyValue( $hash->{TYPE} . "_" . $hash->{NAME} . "_passwd", undef );
  781. return undef;
  782. }
  783. 1;
  784. =pod
  785. =item device
  786. =item summary Modul to communicate with the GardenaCloud
  787. =item summary_DE Modul zur Datenübertragung zur GardenaCloud
  788. =begin html
  789. <a name="GardenaSmartBridge"></a>
  790. <h3>GardenaSmartBridge</h3>
  791. <ul>
  792. <u><b>Prerequisite</b></u>
  793. <br><br>
  794. <li>In combination with GardenaSmartDevice this FHEM Module controls the communication between the GardenaCloud and connected Devices like Mover, Watering_Computer, Temperature_Sensors</li>
  795. <li>Installation of the following packages: apt-get install libio-socket-ssl-perl</li>
  796. <li>The Gardena-Gateway and all connected Devices must be correctly installed in the GardenaAPP</li>
  797. </ul>
  798. <br>
  799. <a name="GardenaSmartBridgedefine"></a>
  800. <b>Define</b>
  801. <ul><br>
  802. <code>define &lt;name&gt; GardenaSmartBridge</code>
  803. <br><br>
  804. Beispiel:
  805. <ul><br>
  806. <code>define Gardena_Bridge GardenaSmartBridge</code><br>
  807. </ul>
  808. <br>
  809. The GardenaSmartBridge device is created in the room GardenaSmart, then the devices of Your system are recognized automatically and created in FHEM. From now on the devices can be controlled and changes in the GardenaAPP are synchronized with the state and readings of the devices.
  810. <br><br>
  811. <a name="GardenaSmartBridgereadings"></a>
  812. <br><br>
  813. <b>Readings</b>
  814. <ul>
  815. <li>address - your Adress (Longversion)</li>
  816. <li>authorized_user_ids - </li>
  817. <li>city - Zip, City</li>
  818. <li>devices - Number of Devices in the Cloud (Gateway included)</li>
  819. <li>lastRequestState - Last Status Result</li>
  820. <li>latitude - Breitengrad des Grundstücks</li>
  821. <li>longitude - Längengrad des Grundstücks</li>
  822. <li>name - Name of your Garden – Default „My Garden“</li>
  823. <li>state - State of the Bridge</li>
  824. <li>token - SessionID</li>
  825. <li>zones - </li>
  826. </ul>
  827. <br><br>
  828. <a name="GardenaSmartBridgeset"></a>
  829. <b>set</b>
  830. <ul>
  831. <li>getDeviceState - Starts a Datarequest</li>
  832. <li>getToken - Gets a new Session-ID</li>
  833. <li>gardenaAccountPassword - Passwort which was used in the GardenaAPP</li>
  834. <li>deleteAccountPassword - delete the password from store</li>
  835. </ul>
  836. <br><br>
  837. <a name="GardenaSmartBridgeattributes"></a>
  838. <b>Attributes</b>
  839. <ul>
  840. <li>debugJSON - </li>
  841. <li>disable - Disables the Bridge</li>
  842. <li>interval - Interval in seconds (Default=300)</li>
  843. <li>gardenaAccountEmail - Email Adresse which was used in the GardenaAPP</li>
  844. </ul>
  845. </ul>
  846. =end html
  847. =begin html_DE
  848. <a name="GardenaSmartBridge"></a>
  849. <h3>GardenaSmartBridge</h3>
  850. <ul>
  851. <u><b>Voraussetzungen</b></u>
  852. <br><br>
  853. <li>Zusammen mit dem Device GardenaSmartDevice stellt dieses FHEM Modul die Kommunikation zwischen der GardenaCloud und Fhem her. Es k&ouml;nnen damit Rasenm&auml;her, Bew&auml;sserungscomputer und Bodensensoren überwacht und gesteuert werden</li>
  854. <li>Das Perl-Modul "SSL Packet" wird ben&ouml;tigt.</li>
  855. <li>Unter Debian (basierten) System, kann dies mittels "apt-get install libio-socket-ssl-perl" installiert werden.</li>
  856. <li>Das Gardena-Gateway und alle damit verbundenen Ger&auml;te und Sensoren m&uuml;ssen vorab in der GardenaApp eingerichtet sein.</li>
  857. </ul>
  858. <br>
  859. <a name="GardenaSmartBridgedefine"></a>
  860. <b>Define</b>
  861. <ul><br>
  862. <code>define &lt;name&gt; GardenaSmartBridge</code>
  863. <br><br>
  864. Beispiel:
  865. <ul><br>
  866. <code>define Gardena_Bridge GardenaSmartBridge</code><br>
  867. </ul>
  868. <br>
  869. Das Bridge Device wird im Raum GardenaSmart angelegt und danach erfolgt das Einlesen und automatische Anlegen der Ger&auml;te. Von nun an k&ouml;nnen die eingebundenen Ger&auml;te gesteuert werden. &Auml;nderungen in der APP werden mit den Readings und dem Status syncronisiert.
  870. <br><br>
  871. <a name="GardenaSmartBridgereadings"></a>
  872. <br><br>
  873. <b>Readings</b>
  874. <ul>
  875. <li>address - Adresse, welche in der App eingetragen wurde (Langversion)</li>
  876. <li>authorized_user_ids - </li>
  877. <li>city - PLZ, Stadt</li>
  878. <li>devices - Anzahl der Ger&auml;te, welche in der GardenaCloud angemeldet sind (Gateway z&auml;hlt mit)</li>
  879. <li>lastRequestState - Letzter abgefragter Status der Bridge</li>
  880. <li>latitude - Breitengrad des Grundst&uuml;cks</li>
  881. <li>longitude - Längengrad des Grundst&uuml;cks</li>
  882. <li>name - Name für das Grundst&uuml;ck – Default „My Garden“</li>
  883. <li>state - Status der Bridge</li>
  884. <li>token - SessionID</li>
  885. <li>zones - </li>
  886. </ul>
  887. <br><br>
  888. <a name="GardenaSmartBridgeset"></a>
  889. <b>set</b>
  890. <ul>
  891. <li>getDeviceState - Startet eine Abfrage der Daten.</li>
  892. <li>getToken - Holt eine neue Session-ID</li>
  893. <li>gardenaAccountPassword - Passwort, welches in der GardenaApp verwendet wurde</li>
  894. <li>deleteAccountPassword - l&oml;scht das Passwort aus dem Passwortstore</li>
  895. </ul>
  896. <br><br>
  897. <a name="GardenaSmartBridgeattributes"></a>
  898. <b>Attribute</b>
  899. <ul>
  900. <li>debugJSON - JSON Fehlermeldungen</li>
  901. <li>disable - Schaltet die Daten&uuml;bertragung der Bridge ab</li>
  902. <li>interval - Abfrageinterval in Sekunden (default: 300)</li>
  903. <li>gardenaAccountEmail - Email Adresse, die auch in der GardenaApp verwendet wurde</li>
  904. </ul>
  905. </ul>
  906. =end html_DE
  907. =cut