26_tahoma.pm 60 KB

1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556575859606162636465666768697071727374757677787980818283848586878889909192939495969798991001011021031041051061071081091101111121131141151161171181191201211221231241251261271281291301311321331341351361371381391401411421431441451461471481491501511521531541551561571581591601611621631641651661671681691701711721731741751761771781791801811821831841851861871881891901911921931941951961971981992002012022032042052062072082092102112122132142152162172182192202212222232242252262272282292302312322332342352362372382392402412422432442452462472482492502512522532542552562572582592602612622632642652662672682692702712722732742752762772782792802812822832842852862872882892902912922932942952962972982993003013023033043053063073083093103113123133143153163173183193203213223233243253263273283293303313323333343353363373383393403413423433443453463473483493503513523533543553563573583593603613623633643653663673683693703713723733743753763773783793803813823833843853863873883893903913923933943953963973983994004014024034044054064074084094104114124134144154164174184194204214224234244254264274284294304314324334344354364374384394404414424434444454464474484494504514524534544554564574584594604614624634644654664674684694704714724734744754764774784794804814824834844854864874884894904914924934944954964974984995005015025035045055065075085095105115125135145155165175185195205215225235245255265275285295305315325335345355365375385395405415425435445455465475485495505515525535545555565575585595605615625635645655665675685695705715725735745755765775785795805815825835845855865875885895905915925935945955965975985996006016026036046056066076086096106116126136146156166176186196206216226236246256266276286296306316326336346356366376386396406416426436446456466476486496506516526536546556566576586596606616626636646656666676686696706716726736746756766776786796806816826836846856866876886896906916926936946956966976986997007017027037047057067077087097107117127137147157167177187197207217227237247257267277287297307317327337347357367377387397407417427437447457467477487497507517527537547557567577587597607617627637647657667677687697707717727737747757767777787797807817827837847857867877887897907917927937947957967977987998008018028038048058068078088098108118128138148158168178188198208218228238248258268278288298308318328338348358368378388398408418428438448458468478488498508518528538548558568578588598608618628638648658668678688698708718728738748758768778788798808818828838848858868878888898908918928938948958968978988999009019029039049059069079089099109119129139149159169179189199209219229239249259269279289299309319329339349359369379389399409419429439449459469479489499509519529539549559569579589599609619629639649659669679689699709719729739749759769779789799809819829839849859869879889899909919929939949959969979989991000100110021003100410051006100710081009101010111012101310141015101610171018101910201021102210231024102510261027102810291030103110321033103410351036103710381039104010411042104310441045104610471048104910501051105210531054105510561057105810591060106110621063106410651066106710681069107010711072107310741075107610771078107910801081108210831084108510861087108810891090109110921093109410951096109710981099110011011102110311041105110611071108110911101111111211131114111511161117111811191120112111221123112411251126112711281129113011311132113311341135113611371138113911401141114211431144114511461147114811491150115111521153115411551156115711581159116011611162116311641165116611671168116911701171117211731174117511761177117811791180118111821183118411851186118711881189119011911192119311941195119611971198119912001201120212031204120512061207120812091210121112121213121412151216121712181219122012211222122312241225122612271228122912301231123212331234123512361237123812391240124112421243124412451246124712481249125012511252125312541255125612571258125912601261126212631264126512661267126812691270127112721273127412751276127712781279128012811282128312841285128612871288128912901291129212931294129512961297129812991300130113021303130413051306130713081309131013111312131313141315131613171318131913201321132213231324132513261327132813291330133113321333133413351336133713381339134013411342134313441345134613471348134913501351135213531354135513561357135813591360136113621363136413651366136713681369137013711372137313741375137613771378137913801381138213831384138513861387138813891390139113921393139413951396139713981399140014011402140314041405140614071408140914101411141214131414141514161417141814191420142114221423142414251426142714281429143014311432143314341435143614371438143914401441144214431444144514461447144814491450145114521453145414551456145714581459146014611462146314641465146614671468146914701471147214731474147514761477147814791480148114821483148414851486148714881489149014911492149314941495149614971498149915001501150215031504150515061507150815091510151115121513151415151516151715181519152015211522152315241525152615271528152915301531153215331534153515361537153815391540154115421543154415451546154715481549155015511552155315541555155615571558155915601561156215631564156515661567156815691570157115721573157415751576157715781579158015811582158315841585158615871588158915901591159215931594159515961597159815991600160116021603160416051606160716081609161016111612161316141615161616171618161916201621162216231624162516261627162816291630163116321633163416351636163716381639164016411642164316441645164616471648164916501651165216531654165516561657165816591660166116621663166416651666166716681669167016711672167316741675167616771678167916801681168216831684168516861687168816891690169116921693169416951696169716981699170017011702170317041705170617071708170917101711171217131714171517161717171817191720172117221723172417251726172717281729173017311732173317341735173617371738173917401741174217431744174517461747174817491750175117521753175417551756175717581759176017611762176317641765176617671768176917701771177217731774177517761777177817791780178117821783178417851786178717881789179017911792179317941795179617971798179918001801180218031804180518061807180818091810181118121813181418151816181718181819182018211822182318241825182618271828182918301831183218331834183518361837183818391840184118421843184418451846184718481849185018511852185318541855
  1. # $Id: 26_tahoma.pm 16851 2018-06-11 21:30:34Z mike3436 $
  2. ################################################################
  3. #
  4. # Copyright notice
  5. #
  6. # (c) 2015 mike3436 (mike3436@online.de)
  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. # (at your option) any later version.
  12. #
  13. # The GNU General Public License can be found at
  14. # http://www.gnu.org/copyleft/gpl.html.
  15. # A copy is found in the textfile GPL.txt and important notices to the license
  16. # from the author is found in LICENSE.txt distributed with these scripts.
  17. #
  18. # This script is distributed in the hope that it will be useful,
  19. # but WITHOUT ANY WARRANTY; without even the implied warranty of
  20. # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
  21. # GNU General Public License for more details.
  22. #
  23. # This copyright notice MUST APPEAR in all copies of the script!
  24. #
  25. ################################################################
  26. # $Id: 26_tahoma.pm
  27. #
  28. # 2014-08-01 V 0100 first Version using XML Interface
  29. # 2015-08-16 V 0200 communication to server changes from xml to json
  30. # 2015-08-16 V 0201 some standard requests after login which are not neccessary disabled (so the actual requests are not equal to flow of iphone app)
  31. # 2016-02-14 V 0202 bugs forcing some startup warning messages fixed
  32. # 2016-02-20 V 0203 perl exception while parsing json string captured
  33. # 2016-02-24 V 0204 commands open,close,my,stop and setClosure added
  34. # 2016-04-24 V 0205 commands taken from setup
  35. # 2016-06-16 V 0206 updateDevices called for devices created before setup has been read
  36. # 2016-11-15 V 0207 BLOCKING=0 can be used, all calls asynchron, attribut levelInvert inverts RollerShutter position
  37. # 2016-11-29 V 0208 HttpUtils used instead of LWP::UserAgent, BLOCKING=0 set as default, umlaut can be used in Tahoma names
  38. # 2016-12-15 V 0209 perl warnings during startup and login eliminated
  39. # 2017-01-08 V 0210 tahoma_cancelExecutions: cancel command added
  40. # 2017-01-10 V 0211 tahoma_getStates: read all states based on table {setup}{devices}[n]{definition}{states}
  41. # 2017-01-24 V 0212 tahoma_getStates: read all states recovered
  42. # 2017-01-24 V 0212 start scene with launchActionGroup so cancel is working on scenes now
  43. # 2017-01-24 V 0212 Attribut interval used to disable or enable refreshAllstates
  44. # 2017-01-24 V 0212 Setup changes recognized for reading places
  45. # 2017-03-23 V 0213 username and password stored encrypted
  46. # 2017-05-07 V 0214 encryption can be disabled by new attribut cryptLoginData
  47. # 2017-05-07 V 0214 correct parameters of setClosureAndLinearSpeed caused syntax error
  48. # 2017-07-01 V 0215 creation of fid and device names for first autocreate extended
  49. # 2017-07-08 V 0215 login delay increased automatically up to 160s if login failed
  50. # 2017-07-08 V 0215 default set commands on devices without commands deleted
  51. # 2017-10-08 V 0216 group definition added
  52. # 2018-05-10 V 0217 disable activated on devices
  53. # 2018-05-25 V 0218 keepalive of http connection corrected
  54. # 2018-06-01 V 0219 new Attributes for time interval of getEvents, getStates and refreshAllStates
  55. # 2018-06-11 V 0220 HttpUtils_Close before login, some newer Debug outputs deleted, Apply command separated, command responds verified
  56. package main;
  57. use strict;
  58. use warnings;
  59. use utf8;
  60. use Encode qw(decode_utf8);
  61. use JSON;
  62. #use Data::Dumper;
  63. use Time::HiRes qw(time);
  64. use HttpUtils;
  65. sub tahoma_parseGetSetupPlaces($$);
  66. sub tahoma_UserAgent_NonblockingGet($);
  67. sub tahoma_encode_utf8($);
  68. my $hash_;
  69. sub tahoma_Initialize($)
  70. {
  71. my ($hash) = @_;
  72. $hash->{DefFn} = "tahoma_Define";
  73. $hash->{NOTIFYDEV} = "global";
  74. $hash->{NotifyFn} = "tahoma_Notify";
  75. $hash->{UndefFn} = "tahoma_Undefine";
  76. $hash->{SetFn} = "tahoma_Set";
  77. $hash->{GetFn} = "tahoma_Get";
  78. $hash->{AttrFn} = "tahoma_Attr";
  79. $hash->{AttrList} = "IODev ".
  80. "blocking ".
  81. "debug:1 ".
  82. "disable:1 ".
  83. "interval ".
  84. "intervalRefresh ".
  85. "intervalEvents ".
  86. "intervalStates ".
  87. "logfile ".
  88. "url ".
  89. "placeClasses ".
  90. "levelInvert ".
  91. "cryptLoginData ".
  92. "userAgent ";
  93. $hash->{AttrList} .= $readingFnAttributes;
  94. }
  95. #####################################
  96. sub tahoma_fhemIdFromDevice($)
  97. {
  98. my @device = split "/", shift;
  99. $device[-1] =~ s/\W/_/g;
  100. return $device[-1] if (@device <= 4);
  101. $device[-2] =~ s/\W/_/g;
  102. return $device[-2].'_'.$device[-1] if (@device <= 5);;
  103. $device[-3] =~ s/\W/_/g;
  104. return $device[-3].'_'.$device[-2].'_'.$device[-1];
  105. }
  106. sub tahoma_fhemIdFromOid($)
  107. {
  108. my @oid = split "-", shift;
  109. $oid[0] =~ s/\W/_/g;
  110. return $oid[0];
  111. }
  112. my $groupId = 123001;
  113. sub tahoma_Define($$)
  114. {
  115. my ($hash, $def) = @_;
  116. my @a = split("[ \t][ \t]*", $def);
  117. my $ModuleVersion = "0220";
  118. my $subtype;
  119. my $name = $a[0];
  120. if( $a[2] eq "DEVICE" && @a == 4 ) {
  121. $subtype = "DEVICE";
  122. my $device = $a[3];
  123. my $fid = tahoma_fhemIdFromDevice($device);
  124. $hash->{device} = $device;
  125. $hash->{fid} = $fid;
  126. #$hash->{INTERVAL} = 0;
  127. my $d = $modules{$hash->{TYPE}}{defptr}{"$fid"};
  128. return "device $device already defined as $d->{NAME}" if( defined($d) && $d->{NAME} ne $name );
  129. $modules{$hash->{TYPE}}{defptr}{"$fid"} = $hash;
  130. } elsif( $a[2] eq "PLACE" && @a == 4 ) {
  131. $subtype = "PLACE";
  132. my $oid = $a[@a-1];
  133. my $fid = tahoma_fhemIdFromOid($oid);
  134. $hash->{oid} = $oid;
  135. $hash->{fid} = $fid;
  136. #$hash->{INTERVAL} = 0;
  137. my $d = $modules{$hash->{TYPE}}{defptr}{"$fid"};
  138. return "place oid $oid already defined as $d->{NAME}" if( defined($d) && $d->{NAME} ne $name );
  139. $modules{$hash->{TYPE}}{defptr}{"$fid"} = $hash;
  140. } elsif( $a[2] eq "GROUP" && @a == 4 ) {
  141. $subtype = "GROUP";
  142. my $oid = $a[@a-1];
  143. my $fid = 'group' . "$groupId";
  144. $groupId++;
  145. $hash->{oid} = $oid;
  146. $hash->{fid} = $fid;
  147. #$hash->{INTERVAL} = 0;
  148. my $d = $modules{$hash->{TYPE}}{defptr}{"$fid"};
  149. return "group oid $oid already defined as $d->{NAME}" if( defined($d) && $d->{NAME} ne $name );
  150. $modules{$hash->{TYPE}}{defptr}{"$fid"} = $hash;
  151. } elsif( $a[2] eq "SCENE" && @a == 4 ) {
  152. $subtype = "SCENE";
  153. my $oid = $a[@a-1];
  154. my $fid = tahoma_fhemIdFromOid($oid);
  155. $hash->{oid} = $oid;
  156. $hash->{fid} = $fid;
  157. #$hash->{INTERVAL} = 0;
  158. my $d = $modules{$hash->{TYPE}}{defptr}{"$fid"};
  159. return "scene oid $oid already defined as $d->{NAME}" if( defined($d) && $d->{NAME} ne $name );
  160. $modules{$hash->{TYPE}}{defptr}{"$fid"} = $hash;
  161. } elsif( $a[2] eq "ACCOUNT" && @a == 5 ) {
  162. $subtype = "ACCOUNT";
  163. my $username = $a[@a-2];
  164. my $password = $a[@a-1];
  165. $hash->{Clients} = ":tahoma:";
  166. $hash->{helper}{username} = $username;
  167. $hash->{helper}{password} = $password;
  168. $hash->{BLOCKING} = 0;
  169. $hash->{VERSION} = $ModuleVersion;
  170. $hash->{getEventsInterval} = 2;
  171. $hash->{refreshStatesInterval} = 120;
  172. $hash->{getStatesInterval} = 0;
  173. } else {
  174. return "Usage: define <name> tahoma device\
  175. define <name> tahoma ACCOUNT username password\
  176. define <name> tahoma DEVICE id\
  177. define <name> tahoma SCENE oid username password\
  178. define <name> tahoma PLACE oid" if(@a < 4 || @a > 5);
  179. }
  180. $hash->{NAME} = $name;
  181. $hash->{SUBTYPE} = $subtype;
  182. $hash->{STATE} = "Initialized";
  183. if( $init_done ) {
  184. tahoma_connect($hash) if( $hash->{SUBTYPE} eq "ACCOUNT" );
  185. tahoma_initDevice($hash) if( $hash->{SUBTYPE} eq "DEVICE" );
  186. tahoma_initDevice($hash) if( $hash->{SUBTYPE} eq "PLACE" );
  187. tahoma_initDevice($hash) if( $hash->{SUBTYPE} eq "GROUP" );
  188. tahoma_initDevice($hash) if( $hash->{SUBTYPE} eq "SCENE" );
  189. }
  190. return undef;
  191. }
  192. sub tahoma_Notify($$)
  193. {
  194. my ($hash,$dev) = @_;
  195. return if($dev->{NAME} ne "global");
  196. return if(!grep(m/^INITIALIZED|REREADCFG$/, @{$dev->{CHANGED}}));
  197. if( $hash->{SUBTYPE} eq "ACCOUNT" )
  198. {
  199. my $name = $hash->{NAME};
  200. my $username = $hash->{helper}{username};
  201. my $password = $hash->{helper}{password};
  202. if ((defined $attr{$name}{cryptLoginData}) && (not $attr{$name}{cryptLoginData}))
  203. {
  204. $username = tahoma_decrypt($username);
  205. $password = tahoma_decrypt($password);
  206. }
  207. else
  208. {
  209. $username = tahoma_encrypt($username);
  210. $password = tahoma_encrypt($password);
  211. }
  212. $hash->{DEF} = "$hash->{SUBTYPE} $username $password";
  213. }
  214. tahoma_connect($hash) if( $hash->{SUBTYPE} eq "ACCOUNT" );
  215. tahoma_initDevice($hash) if( $hash->{SUBTYPE} eq "DEVICE" );
  216. tahoma_initDevice($hash) if( $hash->{SUBTYPE} eq "PLACE" );
  217. tahoma_initDevice($hash) if( $hash->{SUBTYPE} eq "GROUP" );
  218. tahoma_initDevice($hash) if( $hash->{SUBTYPE} eq "SCENE" );
  219. }
  220. sub tahoma_Undefine($$)
  221. {
  222. my ($hash, $arg) = @_;
  223. delete( $modules{$hash->{TYPE}}{defptr}{"$hash->{fid}"} ) if( $hash->{SUBTYPE} eq "DEVICE" );
  224. delete( $modules{$hash->{TYPE}}{defptr}{"$hash->{fid}"} ) if( $hash->{SUBTYPE} eq "PLACE" );
  225. delete( $modules{$hash->{TYPE}}{defptr}{"$hash->{fid}"} ) if( $hash->{SUBTYPE} eq "GROUP" );
  226. delete( $modules{$hash->{TYPE}}{defptr}{"$hash->{fid}"} ) if( $hash->{SUBTYPE} eq "SCENE" );
  227. return undef;
  228. }
  229. sub tahoma_login($)
  230. {
  231. my ($hash) = @_;
  232. my $name = $hash->{NAME};
  233. Log3 $name, 3, "$name: tahoma_login";
  234. HttpUtils_Close($hash);
  235. $hash->{logged_in} = undef;
  236. $hash->{startup_run} = undef;
  237. $hash->{startup_done} = undef;
  238. $hash->{url} = "https://www.tahomalink.com/enduser-mobile-web/externalAPI/json/";
  239. $hash->{url} = $attr{$name}{url} if (defined $attr{$name}{url});
  240. $hash->{userAgent} = "TaHoma/7980 CFNetwork/758.5.3 Darwin/15.6.0";
  241. $hash->{userAgent} = $attr{$name}{userAgent} if (defined $attr{$name}{userAgent});
  242. $hash->{timeout} = 10;
  243. $hash->{HTTPCookies} = undef;
  244. $hash->{loginRetryTimer} = 5 if (!defined $hash->{loginRetryTimer});
  245. $hash->{loginRetryTimer} *= 2 if ($hash->{loginRetryTimer} < 160);
  246. Log3 $name, 2, "$name: login start";
  247. tahoma_UserAgent_NonblockingGet({
  248. timeout => 10,
  249. noshutdown => 1,
  250. hash => $hash,
  251. page => 'login',
  252. data => {'userId' => tahoma_decrypt($hash->{helper}{username}) , 'userPassword' => tahoma_decrypt($hash->{helper}{password})},
  253. callback => \&tahoma_dispatch,
  254. nonblocking => 1,
  255. });
  256. }
  257. my @startup_pages = ( 'getEndUser',
  258. 'getSetup',
  259. 'getActionGroups',
  260. #'/../../enduserAPI/setup/interactiveNotifications',
  261. #'/../../enduserAPI/setup/interactiveNotifications/history',
  262. #'getCalendarDayList',
  263. #'getCalendarRuleList',
  264. #'/../../enduserAPI/conditionGroups',
  265. #'getScheduledExecutions',
  266. #'getHistory',
  267. #'getSetupTriggers',
  268. #'getUserPreferences',
  269. #'getSetupOptions',
  270. #'getAvailableProtocolsType',
  271. #'getActiveProtocolsType',
  272. #'getSetupQuota?quotaId=smsCredit',
  273. #'getSetupDawnAndDuskTimes',
  274. '../../enduserAPI/setup/gateways',
  275. #'../../enduserAPI/setup/gateways',
  276. #'../../enduserAPI/setup/subscribe/notification/apple/com.somfy.iPhoneTaHoma',
  277. #'../../enduserAPI/setup/subscribe/notification/devices/tahoma',
  278. #'/../../enduserAPI/setup/subscribe/notification/apple/com.somfy.iPhoneTaHoma',
  279. #'../../enduserAPI/setup/subscribe/notification/devices/tahoma',
  280. 'getCurrentExecutions',
  281. 'refreshAllStates' );
  282. sub tahoma_startup($)
  283. {
  284. my ($hash) = @_;
  285. my $name = $hash->{NAME};
  286. Log3 $name, 4, "$name: tahoma_startup";
  287. return if (!$hash->{logged_in});
  288. return if ($hash->{startup_done});
  289. if (!defined($hash->{startup_run}))
  290. {
  291. $hash->{startup_run} = 0;
  292. }
  293. else
  294. {
  295. $hash->{startup_run}++;
  296. if ($hash->{startup_run} >= scalar @startup_pages)
  297. {
  298. $hash->{startup_done} = 1;
  299. return;
  300. }
  301. }
  302. my $page = $startup_pages[$hash->{startup_run}];
  303. my $subpage = "";
  304. $subpage = '?gatewayId='.$hash->{gatewayId} if (substr($page, -13) eq 'ProtocolsType');
  305. $subpage = '?quotaId=smsCredit' if ($page eq 'getSetupQuota');
  306. $subpage = '/'.$hash->{gatewayId}.'/version' if (substr($page, -8) eq 'gateways');
  307. tahoma_UserAgent_NonblockingGet({
  308. timeout => 10,
  309. noshutdown => 1,
  310. hash => $hash,
  311. page => $page,
  312. subpage => $subpage,
  313. callback => \&tahoma_dispatch,
  314. nonblocking => 1,
  315. });
  316. }
  317. sub tahoma_refreshAllStates($)
  318. {
  319. my ($hash) = @_;
  320. my $name = $hash->{NAME};
  321. Log3 $name, 4, "$name: tahoma_refreshAllStates";
  322. tahoma_UserAgent_NonblockingGet({
  323. timeout => 10,
  324. noshutdown => 1,
  325. hash => $hash,
  326. page => 'refreshAllStates',
  327. callback => \&tahoma_dispatch,
  328. nonblocking => 1,
  329. });
  330. }
  331. sub tahoma_getEvents($)
  332. {
  333. my ($hash) = @_;
  334. my $name = $hash->{NAME};
  335. Log3 $name, 4, "$name: tahoma_getEvents";
  336. return if(!$hash->{logged_in} && !$hash->{startup_done});
  337. tahoma_UserAgent_NonblockingGet({
  338. timeout => 10,
  339. noshutdown => 1,
  340. hash => $hash,
  341. page => 'getEvents',
  342. method => 'PUSH',
  343. callback => \&tahoma_dispatch,
  344. nonblocking => 1,
  345. });
  346. }
  347. sub tahoma_readStatusTimer($)
  348. {
  349. my $timestart = time;
  350. my $timeinfo = "tahoma_readStatusTimer";
  351. my ($hash) = @_;
  352. my $name = $hash->{NAME};
  353. RemoveInternalTimer($hash);
  354. my ($seconds) = gettimeofday();
  355. $hash->{refreshStatesTimer} = $seconds + 10 if ( (!defined($hash->{refreshStatesTimer})) || (!$hash->{logged_in}) );
  356. if( $hash->{request_active} ) {
  357. Log3 $name, 3, "$name: request active";
  358. if( ($timestart - $hash->{request_time}) > 10)
  359. {
  360. Log3 $name, 2, "$name: timeout, close ua";
  361. $hash->{socket} = undef;
  362. $hash->{request_active} = 0;
  363. $hash->{logged_in} = 0;
  364. $hash->{startup_done} = 0;
  365. }
  366. }
  367. elsif( !$hash->{logged_in} ) {
  368. tahoma_login($hash) if (!(defined $hash->{loginRetryTimer}) || !(defined $hash->{request_time}) || (($timestart - $hash->{request_time}) >= $hash->{loginRetryTimer}));
  369. $timeinfo = "tahoma_login";
  370. }
  371. elsif( !$hash->{startup_done} ) {
  372. tahoma_startup($hash);
  373. $timeinfo = "tahoma_startup";
  374. if ( $hash->{startup_done} ) {
  375. tahoma_getStates($hash) ;
  376. $hash->{refreshStatesTimer} = $seconds + $hash->{refreshStatesInterval};
  377. $hash->{getStatesTimer} = $seconds + $hash->{getStatesInterval};
  378. $hash->{getEventsTimer} = $seconds + $hash->{getEventsInterval};
  379. $timeinfo = "tahoma_getStates";
  380. }
  381. }
  382. elsif( ($seconds >= $hash->{refreshStatesTimer}) && ($hash->{refreshStatesInterval} > 0) )
  383. {
  384. Log3 $name, 4, "$name: refreshing all states";
  385. tahoma_refreshAllStates($hash);
  386. $hash->{refreshStatesTimer} = $seconds + $hash->{refreshStatesInterval};
  387. $timeinfo = "tahoma_refreshAllStates";
  388. }
  389. elsif( ($seconds >= $hash->{getStatesTimer}) && ($hash->{getStatesInterval} > 0) )
  390. {
  391. Log3 $name, 4, "$name: get all states";
  392. tahoma_getStates($hash);
  393. $hash->{getStatesTimer} = $seconds + $hash->{getStatesInterval};
  394. $timeinfo = "tahoma_getStates";
  395. }
  396. elsif( ($seconds >= $hash->{getEventsTimer}) || ($hash->{getEventsInterval} <= 0) )
  397. {
  398. Log3 $name, 4, "$name: refreshing event";
  399. tahoma_getEvents($hash);
  400. $hash->{getEventsTimer} = $seconds + $hash->{getEventsInterval};
  401. $timeinfo = "tahoma_getEvents";
  402. }
  403. my $timedelta = time -$timestart;
  404. if ($timedelta > 0.5)
  405. {
  406. $timedelta *= 1000;
  407. Log3 $name, 3, "$name: $timeinfo took $timedelta ms"
  408. }
  409. InternalTimer(gettimeofday()+2, "tahoma_readStatusTimer", $hash, 0);
  410. }
  411. sub tahoma_connect($)
  412. {
  413. my ($hash) = @_;
  414. my $name = $hash->{NAME};
  415. Log3 $name, 3, "$name: tahoma_connect";
  416. RemoveInternalTimer($hash);
  417. tahoma_login($hash);
  418. my ($seconds) = gettimeofday();
  419. $hash->{refreshStatesTimer} = $seconds + 10;
  420. tahoma_readStatusTimer($hash);
  421. }
  422. sub tahoma_initDevice($)
  423. {
  424. my ($hash) = @_;
  425. my $name = $hash->{NAME};
  426. my $subtype = $hash->{SUBTYPE};
  427. AssignIoPort($hash);
  428. if(defined($hash->{IODev}->{NAME})) {
  429. Log3 $name, 3, "$name: I/O device is " . $hash->{IODev}->{NAME};
  430. } else {
  431. Log3 $name, 1, "$name: no I/O device";
  432. }
  433. my $device;
  434. if( defined($hash->{device}) ) {
  435. $device = tahoma_getDeviceDetail( $hash, $hash->{device} );
  436. #Log3 $name, 4, Dumper($device);
  437. } elsif( defined($hash->{oid}) ) {
  438. $device = tahoma_getDeviceDetail( $hash, $hash->{oid} );
  439. #Log3 $name, 4, Dumper($device);
  440. }
  441. if( defined($device) && ($subtype eq 'DEVICE') ) {
  442. Log3 $name, 4, "$name: I/O device is label=".$device->{label};
  443. $hash->{inType} = $device->{type};
  444. $hash->{inLabel} = $device->{label};
  445. $hash->{inControllable} = $device->{controllableName};
  446. $hash->{inPlaceOID} = $device->{placeOID};
  447. $hash->{inClass} = $device->{uiClass};
  448. $device->{levelInvert} = $attr{$hash->{NAME}}{levelInvert} if (defined $attr{$hash->{NAME}}{levelInvert});
  449. }
  450. elsif( defined($device) && ($subtype eq 'PLACE') ) {
  451. Log3 $name, 4, "$name: I/O device is label=".$device->{label};
  452. $hash->{inType} = $device->{type};
  453. $hash->{inLabel} = $device->{label};
  454. $hash->{inOID} = $device->{oid};
  455. $hash->{inClass} = 'RollerShutter';
  456. $hash->{inClass} = $attr{$hash->{NAME}}{placeClasses} if (defined $attr{$hash->{NAME}}{placeClasses});
  457. }
  458. elsif( defined($device) && ($subtype eq 'SCENE') ) {
  459. Log3 $name, 4, "$name: I/O device is label=".$device->{label};
  460. $hash->{inLabel} = $device->{label};
  461. $hash->{inOID} = $device->{oid};
  462. }
  463. elsif($subtype eq 'GROUP' ) {
  464. $hash->{inType} = '';
  465. $hash->{inLabel} = '';
  466. $hash->{inLabel} = $attr{$hash->{NAME}}{alias} if (defined $attr{$hash->{NAME}}{alias});
  467. $hash->{inOID} = '';
  468. $hash->{inClass} = '';
  469. }
  470. else
  471. {
  472. my $device=$hash->{device};
  473. $device ||= 'undefined';
  474. $subtype ||= 'undefined';
  475. Log3 $name, 3, "$name: unknown device=$device, subtype=$subtype";
  476. }
  477. }
  478. sub tahoma_updateDevices($)
  479. {
  480. my ($hash) = @_;
  481. my $name = $hash->{NAME};
  482. Log3 $name, 3, "$name: tahoma_updateDevices";
  483. return undef if( !$hash->{helper}{devices} ) ;
  484. $hash = $hash->{IODev} if( defined($hash->{IODev}) );
  485. foreach my $module (keys %{$modules{$hash->{TYPE}}{defptr}}) {
  486. my $def = $modules{$hash->{TYPE}}{defptr}{"$module"};
  487. my $subtype = $def->{SUBTYPE};
  488. if (defined($def->{oid}) && !defined($def->{inType}))
  489. {
  490. Log3 $name, 3, "$name: updateDevices oid=$def->{oid}";
  491. my $device = tahoma_getDeviceDetail( $hash, $def->{oid} );
  492. if( defined($device) && ($subtype eq 'PLACE') ) {
  493. Log3 $name, 4, "$name: I/O device is label=".$device->{label};
  494. $def->{inType} = $device->{type};
  495. $def->{inLabel} = $device->{label};
  496. $def->{inOID} = $device->{oid};
  497. $def->{inClass} = 'RollerShutter';
  498. $def->{inClass} = $attr{$def->{NAME}}{placeClasses} if (defined $attr{$def->{NAME}}{placeClasses});
  499. $device->{NAME} = $def->{NAME};
  500. }
  501. elsif( defined($device) && ($subtype eq 'SCENE') ) {
  502. Log3 $name, 4, "$name: I/O device is label=".$device->{label};
  503. $def->{inLabel} = $device->{label};
  504. $def->{inOID} = $device->{oid};
  505. $device->{NAME} = $def->{NAME};
  506. }
  507. }
  508. elsif (defined($def->{device}) && !defined($def->{inType}))
  509. {
  510. Log3 $name, 3, "$name: updateDevices device=$def->{device}";
  511. my $device = tahoma_getDeviceDetail( $hash, $def->{device} );
  512. if( defined($device) && ($subtype eq 'DEVICE') ) {
  513. Log3 $name, 4, "$name: I/O device is label=".$device->{label};
  514. $def->{inType} = $device->{type};
  515. $def->{inLabel} = $device->{label};
  516. $def->{inControllable} = $device->{controllableName};
  517. $def->{inPlaceOID} = $device->{placeOID};
  518. $def->{inClass} = $device->{uiClass};
  519. $device->{levelInvert} = $attr{$def->{NAME}}{levelInvert} if (defined $attr{$def->{NAME}}{levelInvert});
  520. $device->{NAME} = $def->{NAME};
  521. }
  522. }
  523. }
  524. return undef;
  525. }
  526. sub tahoma_getDevices($$)
  527. {
  528. my ($hash,$nonblocking) = @_;
  529. my $name = $hash->{NAME};
  530. Log3 $name, 4, "$name: tahoma_getDevices";
  531. tahoma_UserAgent_NonblockingGet({
  532. timeout => 10,
  533. noshutdown => 1,
  534. hash => $hash,
  535. page => 'getSetup',
  536. callback => \&tahoma_dispatch,
  537. nonblocking => $nonblocking,
  538. });
  539. return $hash->{helper}{devices};
  540. }
  541. sub tahoma_getDeviceDetail($$)
  542. {
  543. my ($hash,$id) = @_;
  544. my $name = $hash->{NAME};
  545. Log3 $name, 4, "$name: tahoma_getDeviceDetails $id";
  546. $hash = $hash->{IODev} if( defined($hash->{IODev}) );
  547. foreach my $device (@{$hash->{helper}{devices}}) {
  548. return $device if( defined($device->{deviceURL}) && ($device->{deviceURL} eq $id) );
  549. return $device if( defined($device->{oid}) && ($device->{oid} eq $id) );
  550. }
  551. Log3 $name, 4, "$name: getDeviceDetails $id not found";
  552. return undef;
  553. }
  554. sub tahoma_getStates($)
  555. {
  556. my ($hash) = @_;
  557. my $name = $hash->{NAME};
  558. Log3 $name, 4, "$name: tahoma_getStates";
  559. my $data = '[';
  560. foreach my $device (@{$hash->{helper}{devices}}) {
  561. if( defined($device->{deviceURL}) && defined($device->{states}) )
  562. {
  563. $data .= ',' if (substr($data, -1) eq '}');
  564. $data .= '{"deviceURL":"'.$device->{deviceURL}.'","states":[';
  565. foreach my $state (@{$device->{states}}) {
  566. $data .= ',' if (substr($data, -1) eq '}');
  567. $data .= '{"name":"' . $state->{name} . '"}';
  568. }
  569. $data .= ']}';
  570. }
  571. }
  572. $data .= ']';
  573. Log3 $name, 5, "$name: tahoma_getStates data=".$data;
  574. tahoma_UserAgent_NonblockingGet({
  575. timeout => 10,
  576. noshutdown => 1,
  577. hash => $hash,
  578. page => 'getStates',
  579. data => tahoma_encode_utf8($data),
  580. callback => \&tahoma_dispatch,
  581. nonblocking => 1,
  582. });
  583. }
  584. sub tahoma_getDeviceList($$$$);
  585. sub tahoma_getDeviceList($$$$)
  586. {
  587. my ($hash,$oid,$placeClasses,$deviceList) = @_;
  588. #print "tahoma_getDeviceList oid=$oid devices=".scalar @{$deviceList}."\n";
  589. my @classes = split(' ',$placeClasses);
  590. my $devices = $hash->{helper}{devices};
  591. foreach my $device (@{$devices}) {
  592. if ( defined($device->{deviceURL}) && defined($device->{placeOID}) && defined($device->{uiClass}) ) {
  593. if (( grep { $_ eq $device->{uiClass}} @classes ) && ($device->{placeOID} eq $oid)) {
  594. push ( @{$deviceList}, { device => $device->{deviceURL}, class => $device->{uiClass}, levelInvert => $device->{levelInvert} } ) if !($attr{$device->{NAME}}{disable});
  595. #print "tahoma_getDeviceList url=$device->{deviceURL} devices=".scalar @{$deviceList}."\n";
  596. }
  597. } elsif ( defined($device->{oid}) && defined($device->{subPlaces}) ) {
  598. if ($device->{oid} eq $oid)
  599. {
  600. foreach my $place (@{$device->{subPlaces}}) {
  601. tahoma_getDeviceList($hash,$place->{oid},$placeClasses,$deviceList);
  602. }
  603. }
  604. }
  605. }
  606. }
  607. sub tahoma_getGroupList($$$)
  608. {
  609. my ($hash,$oid,$deviceList) = @_;
  610. #print "tahoma_getGroupList oid=$oid devices=".scalar @{$deviceList}."\n";
  611. my @groupDevices = split(',',$oid);
  612. foreach my $module (@groupDevices) {
  613. if (defined($defs{$module}) && defined($defs{$module}{device}) && defined($defs{$module}{inClass})) {
  614. push ( @{$deviceList}, { device => $defs{$module}{device}, class => $defs{$module}{inClass}, levelInvert => $attr{$module}{levelInvert} } ) if !($attr{$module}{disable});
  615. }
  616. }
  617. }
  618. sub tahoma_checkCommand($$$$)
  619. {
  620. my ($hash,$device,$command,$value) = @_;
  621. my $name = $hash->{NAME};
  622. Log3 $name, 4, "$name: tahoma_checkCommand";
  623. if (($command eq 'setClosure') && (defined ($device->{levelInvert})))
  624. {
  625. $value = 100 - $value if ($device->{levelInvert} && ($value >= 0) && ($value <= 100));
  626. }
  627. if (($command eq 'setClosure') && ($value == 100) && (index($hash->{COMMANDS}," close:") > 0))
  628. {
  629. $command = 'close';
  630. $value = '';
  631. }
  632. if (($command eq 'setClosure') && ($value == 0) && (index($hash->{COMMANDS}," open:") > 0))
  633. {
  634. $command = 'open';
  635. $value = '';
  636. }
  637. return ($command,$value);
  638. }
  639. sub tahoma_applyRequest($$$)
  640. {
  641. my ($hash,$command,$value) = @_;
  642. my $name = $hash->{NAME};
  643. Log3 $name, 4, "$name: tahoma_applyRequest";
  644. if ( !defined($hash->{IODev}) || !(defined($hash->{device}) || defined($hash->{oid})) || !defined($hash->{inLabel}) || !defined($hash->{inClass}) ) {
  645. Log3 $name, 3, "$name: tahoma_applyRequest failed - define error";
  646. return;
  647. }
  648. my @devices = ();
  649. if ( defined($hash->{device}) ) {
  650. push ( @devices, { device => $hash->{device}, class => $hash->{inClass}, commands => $hash->{COMMANDS}, levelInvert => $attr{$hash->{NAME}}{levelInvert} } );
  651. } elsif ($hash->{SUBTYPE} eq 'GROUP') {
  652. tahoma_getGroupList($hash->{IODev},$hash->{oid},\@devices);
  653. } else {
  654. tahoma_getDeviceList($hash->{IODev},$hash->{oid},$hash->{inClass},\@devices);
  655. }
  656. Log3 $name, 4, "$name: tahoma_applyRequest devices=".scalar @devices;
  657. foreach my $dev (@devices) {
  658. Log3 $name, 4, "$name: tahoma_applyRequest devices=$dev->{device} class=$dev->{class}";
  659. }
  660. return if (scalar @devices < 1);
  661. my $data = '';
  662. $value = '' if (!defined($value));
  663. my $commandChecked = $command;
  664. my $valueChecked = $value;
  665. foreach my $device (@devices) {
  666. ($commandChecked, $valueChecked) = tahoma_checkCommand($hash,$device,$command,$value);
  667. if (defined($commandChecked) && defined($valueChecked))
  668. {
  669. $data .= ',' if substr($data, -1) eq '}';
  670. $data .= '{"deviceURL":"'.$device->{device}.'",';
  671. $data .= '"commands":[{"name":"'.$commandChecked.'","parameters":['.$valueChecked.']}]}';
  672. }
  673. }
  674. return if (length $data < 20);
  675. my $dataHead = '{"label":"' . $hash->{inLabel};
  676. if ($commandChecked eq 'setClosure') {
  677. $dataHead .= ' - Positionieren auf '.$valueChecked.' % - iPhone","actions":[';
  678. } elsif ($commandChecked eq 'close') {
  679. $dataHead .= ' - Schliessen - iPhone","actions":[';
  680. } elsif ($commandChecked eq 'open') {
  681. $dataHead .= ' - Oeffnen - iPhone","actions":[';
  682. } elsif ($commandChecked eq 'setClosureAndLinearSpeed') { #neu fuer setClosureAndLinearSpeed
  683. $dataHead .= ' - Positionieren auf '.(split(',',$valueChecked))[0].' % - iPhone","actions":['; #neu fuer setClosureAndLinearSpeed
  684. } else {
  685. $dataHead .= " - $commandChecked $valueChecked".' - iPhone","actions":[';
  686. }
  687. $data = $dataHead . $data . ']}';
  688. Log3 $name, 3, "$name: tahoma_applyRequest data=".$data;
  689. tahoma_UserAgent_NonblockingGet({
  690. timeout => 10,
  691. noshutdown => 1,
  692. hash => $hash,
  693. page => 'apply',
  694. data => tahoma_encode_utf8($data),
  695. callback => \&tahoma_dispatch,
  696. nonblocking => 1,
  697. });
  698. }
  699. sub tahoma_scheduleActionGroup($$)
  700. {
  701. my ($hash,$delay) = @_;
  702. my $name = $hash->{NAME};
  703. Log3 $name, 4, "$name: tahoma_scheduleActionGroup";
  704. if ( !defined($hash->{IODev}) || !defined($hash->{oid}) ) {
  705. Log3 $name, 3, "$name: tahoma_scheduleActionGroup failed - define error";
  706. return;
  707. }
  708. $delay = 0 if(!defined($delay));
  709. tahoma_UserAgent_NonblockingGet({
  710. timeout => 10,
  711. noshutdown => 1,
  712. hash => $hash,
  713. page => 'scheduleActionGroup',
  714. subpage => '?oid='.$hash->{oid}.'&delay='.$delay,
  715. callback => \&tahoma_dispatch,
  716. nonblocking => 1,
  717. });
  718. }
  719. sub tahoma_launchActionGroup($)
  720. {
  721. my ($hash) = @_;
  722. my $name = $hash->{NAME};
  723. Log3 $name, 4, "$name: tahoma_launchActionGroup";
  724. if ( !defined($hash->{IODev}) || !defined($hash->{oid}) ) {
  725. Log3 $name, 3, "$name: tahoma_launchActionGroup failed - define error";
  726. return;
  727. }
  728. tahoma_UserAgent_NonblockingGet({
  729. timeout => 10,
  730. noshutdown => 1,
  731. hash => $hash,
  732. page => 'launchActionGroup',
  733. subpage => '?oid='.$hash->{oid},
  734. callback => \&tahoma_dispatch,
  735. nonblocking => 1,
  736. });
  737. }
  738. sub tahoma_cancelExecutions($)
  739. {
  740. my ($hash) = @_;
  741. my $name = $hash->{NAME};
  742. Log3 $name, 4, "$name: tahoma_cancelExecutions";
  743. my $subpage = '';
  744. if (defined($hash->{IODev}))
  745. {
  746. if (defined($hash->{inExecId}) && (length $hash->{inExecId} > 20))
  747. {
  748. $subpage = '?execId='.$hash->{inExecId};
  749. }
  750. elsif (defined($hash->{inTriggerId}) && (length $hash->{inTriggerId} > 20))
  751. {
  752. $subpage = '?triggerId='.$hash->{inTriggerId};
  753. }
  754. else
  755. {
  756. Log3 $name, 3, "$name: tahoma_cancelExecutions failed - no valid execId or triggerId found";
  757. return;
  758. }
  759. }
  760. Log3 $name, 3, "$name: tahoma_cancelExecutions subpage=$subpage";
  761. tahoma_UserAgent_NonblockingGet({
  762. timeout => 10,
  763. noshutdown => 1,
  764. hash => $hash,
  765. page => 'cancelExecutions',
  766. subpage => $subpage,
  767. callback => \&tahoma_dispatch,
  768. nonblocking => 1,
  769. });
  770. }
  771. sub tahoma_dispatch($$$)
  772. {
  773. my ($param, $err, $data) = @_;
  774. my $hash = $param->{hash};
  775. my $name = $hash->{NAME};
  776. my $hashIn = $hash;
  777. $hash = $hash->{IODev} if (defined($hash->{IODev}));
  778. $hash->{request_active} = 0;
  779. if( $err ) {
  780. Log3 $name, 2, "$name: tahoma_dispatch http request failed: $err";
  781. $hash->{logged_in} = 0;
  782. return;
  783. }
  784. if( $data ) {
  785. tahoma_GetCookies($hash,$param->{httpheader}) if (!$hash->{logged_in});
  786. $data =~ tr/\r\n//d;
  787. $data =~ s/\h+/ /g;
  788. $data =~ s/\\\//\//g;
  789. Log3 $name, (length $data > 120)?4:5, "$name: tahoma_dispatch data=".decode_utf8($data);
  790. # perl exception while parsing json string captured
  791. my $json = {};
  792. eval { $json = JSON->new->utf8(0)->decode($data); };
  793. if ($@ || ((ref $json ne 'HASH') && (ref $json ne 'ARRAY')) ) {
  794. Log3 $name, 3, "$name: tahoma_dispatch json string is faulty" . substr($data,0,40) . ' ...';
  795. $hash->{lastError} = 'json string is faulty: ' . substr($data,0,40) . ' ...';
  796. $hash->{logged_in} = 0;
  797. return;
  798. }
  799. if( (ref $json eq 'HASH') && ($json->{errorResponse}) ) {
  800. $hash->{lastError} = $json->{errorResponse}{message};
  801. $hash->{logged_in} = 0;
  802. Log3 $name, 3, "$name: tahoma_dispatch error: $hash->{lastError}";
  803. return;
  804. }
  805. if( (ref $json eq 'HASH') && ($json->{error}) ) {
  806. $hash->{lastError} = $json->{error};
  807. $hash->{logged_in} = 0;
  808. Log3 $name, 3, "$name: tahoma_dispatch error: $hash->{lastError}";
  809. return;
  810. }
  811. if( $param->{page} eq 'getEvents' ) {
  812. tahoma_parseGetEvents($hash,$json);
  813. } elsif( $param->{page} eq 'apply' ) {
  814. tahoma_parseApplyRequest($hashIn,$json);
  815. } elsif( $param->{page} eq 'getSetup' ) {
  816. tahoma_parseGetSetup($hash,$json);
  817. } elsif( $param->{page} eq 'refreshAllStates' ) {
  818. tahoma_parseRefreshAllStates($hash,$json);
  819. } elsif( $param->{page} eq 'getStates' ) {
  820. tahoma_parseGetStates($hash,$json);
  821. } elsif( $param->{page} eq 'login' ) {
  822. tahoma_parseLogin($hash,$json);
  823. } elsif( $param->{page} eq 'getActionGroups' ) {
  824. tahoma_parseGetActionGroups($hash,$json);
  825. } elsif( $param->{page} eq '../../enduserAPI/setup/gateways' ) {
  826. tahoma_parseEnduserAPISetupGateways($hash,$json);
  827. } elsif( $param->{page} eq 'getCurrentExecutions' ) {
  828. tahoma_parseGetCurrentExecutions($hash,$json);
  829. } elsif( $param->{page} eq 'scheduleActionGroup' ) {
  830. tahoma_parseScheduleActionGroup($hashIn,$json);
  831. } elsif( $param->{page} eq 'launchActionGroup' ) {
  832. tahoma_parseLaunchActionGroup($hashIn,$json);
  833. } elsif( $param->{page} eq 'cancelExecutions' ) {
  834. tahoma_parseCancelExecutions($hash,$json);
  835. }
  836. }
  837. }
  838. sub tahoma_autocreate($)
  839. {
  840. my($hash) = @_;
  841. my $name = $hash->{NAME};
  842. if( !$hash->{helper}{devices} ) {
  843. tahoma_getDevices($hash,1);
  844. return undef;
  845. }
  846. foreach my $d (keys %defs) {
  847. next if($defs{$d}{TYPE} ne "autocreate");
  848. return undef if(AttrVal($defs{$d}{NAME},"disable",undef));
  849. }
  850. Log3 $name, 2, "$name: tahoma_autocreate begin";
  851. my $autocreated = 0;
  852. my $devices = $hash->{helper}{devices};
  853. foreach my $device (@{$devices}) {
  854. my ($id, $fid, $devname, $define);
  855. if ($device->{deviceURL}) {
  856. $id = $device->{deviceURL};
  857. $fid = tahoma_fhemIdFromDevice($id);
  858. $devname = "tahoma_". $fid;
  859. $define = "$devname tahoma DEVICE $id";
  860. if( defined($modules{$hash->{TYPE}}{defptr}{"$fid"}) ) {
  861. Log3 $name, 4, "$name: device '$fid' already defined";
  862. next;
  863. }
  864. } elsif ( $device->{oid} ) {
  865. $id = $device->{oid};
  866. my $fid = tahoma_fhemIdFromOid($id);
  867. $devname = "tahoma_". $fid;
  868. $define = "$devname tahoma PLACE $id" if (!defined $device->{actions});
  869. $define = "$devname tahoma SCENE $id" if (defined $device->{actions});
  870. if( defined($modules{$hash->{TYPE}}{defptr}{"$fid"}) ) {
  871. Log3 $name, 4, "$name: device '$fid' already defined";
  872. next;
  873. }
  874. }
  875. Log3 $name, 3, "$name: create new device '$devname' for device '$id'";
  876. my $cmdret= CommandDefine(undef,$define);
  877. if($cmdret) {
  878. Log3 $name, 1, "$name: Autocreate: An error occurred while creating device for id '$id': $cmdret";
  879. } else {
  880. $cmdret= CommandAttr(undef,"$devname alias room ".$device->{label}) if( defined($device->{label}) && defined($device->{oid}) && !defined($device->{actions}) );
  881. $cmdret= CommandAttr(undef,"$devname alias scene ".$device->{label}) if( defined($device->{label}) && defined($device->{oid}) && defined($device->{actions}) );
  882. $cmdret= CommandAttr(undef,"$devname alias $device->{uiClass} ".$device->{label}) if( defined($device->{label}) && defined($device->{states}) );
  883. $cmdret= CommandAttr(undef,"$devname room tahoma");
  884. $cmdret= CommandAttr(undef,"$devname IODev $name");
  885. $cmdret= CommandAttr(undef,"$devname webCmd dim") if( defined($device->{uiClass}) && ($device->{uiClass} eq "RollerShutter") );
  886. $autocreated++;
  887. }
  888. }
  889. CommandSave(undef,undef) if( $autocreated && AttrVal( "autocreate", "autosave", 1 ) );
  890. Log3 $name, 2, "$name: tahoma_autocreate end, new=$autocreated";
  891. }
  892. sub tahoma_defineCommands($)
  893. {
  894. my($hash) = @_;
  895. my $name = $hash->{NAME};
  896. Log3 $name, 4, "$name: tahoma_defineCommands";
  897. my $devices = $hash->{helper}{devices};
  898. foreach my $device (@{$devices}) {
  899. my ($id, $fid, $devname, $define);
  900. if ($device->{deviceURL}) {
  901. $id = $device->{deviceURL};
  902. $fid = tahoma_fhemIdFromDevice($id);
  903. $devname = "tahoma_". $fid;
  904. $define = "$devname tahoma DEVICE $id";
  905. my $commandlist = "";
  906. if( defined $device->{definition}{commands}[0]{commandName} ) {
  907. $commandlist = "dim:slider,0,1,100 cancel:noArg";
  908. foreach my $command (@{$device->{definition}{commands}}) {
  909. $commandlist .= " " . $command->{commandName};
  910. $commandlist .= ":noArg" if ($command->{nparams} == 0);
  911. }
  912. }
  913. if( defined($modules{$hash->{TYPE}}{defptr}{"$fid"}) ) {
  914. $modules{$hash->{TYPE}}{defptr}{"$fid"}{COMMANDS} = $commandlist;
  915. Log3 $name, 4, "$name: tahoma_defineCommands fid=$fid commandlist=$commandlist";
  916. }
  917. }
  918. }
  919. }
  920. sub tahoma_parseLogin($$)
  921. {
  922. my($hash, $json) = @_;
  923. my $name = $hash->{NAME};
  924. Log3 $name, 4, "$name: tahoma_parseLogin";
  925. if (ref($json) ne 'HASH')
  926. {
  927. Log3 $name, 3, "$name: tahoma_parseLogin response is not a valid hash";
  928. return;
  929. }
  930. if (defined $json->{errorResponse}) {
  931. $hash->{logged_in} = 0;
  932. $hash->{STATE} = $json->{errorResponse}{message};
  933. } else {
  934. $hash->{inVersion} = $json->{version};
  935. $hash->{logged_in} = 1;
  936. $hash->{loginRetryTimer} = 5;
  937. }
  938. Log3 $name, 2, "$name: login end, logged_in=".$hash->{logged_in};
  939. }
  940. sub tahoma_parseGetEvents($$)
  941. {
  942. my($hash, $json) = @_;
  943. my $name = $hash->{NAME};
  944. Log3 $name, 5, "$name: tahoma_parseGetEvents";
  945. $hash->{refresh_event} = $json;
  946. if( $hash->{logged_in} ) {
  947. $hash->{STATE} = "Connected";
  948. } else {
  949. $hash->{STATE} = "Disconnected";
  950. }
  951. if( ref $json eq 'ARRAY' ) {
  952. #print Dumper($json);
  953. foreach my $devices ( @{$json} ) {
  954. if( defined($devices->{deviceURL}) ) {
  955. #print "\nDevice=$devices->{deviceURL} found\n";
  956. my $id = $devices->{deviceURL};
  957. my $fid = tahoma_fhemIdFromDevice($id);
  958. my $devname = "tahoma_". $fid;
  959. my $d = $modules{$hash->{TYPE}}{defptr}{"$fid"};
  960. if( defined($d) )# && $d->{NAME} eq $devname )
  961. {
  962. #print "\nDevice=$devices->{deviceURL} updated\n";
  963. readingsBeginUpdate($d);
  964. foreach my $state (@{$devices->{deviceStates}}) {
  965. #print "$devname $state->{name} = $state->{value}\n";
  966. next if (!defined($state->{name}) || !defined($state->{value}));
  967. if ($state->{name} eq "core:ClosureState") {
  968. $state->{value} = 100 - $state->{value} if ($attr{$d->{NAME}}{levelInvert});
  969. readingsBulkUpdate($d, "state", "dim".$state->{value});
  970. } elsif ($state->{name} eq "core:OpenClosedState") {
  971. readingsBulkUpdate($d, "devicestate", $state->{value});
  972. }
  973. readingsBulkUpdate($d, (split(":",$state->{name}))[-1], $state->{value});
  974. }
  975. my ($seconds) = gettimeofday();
  976. readingsBulkUpdate( $d, ".lastupdate", $seconds, 0 );
  977. readingsEndUpdate($d,1);
  978. }
  979. }
  980. elsif( defined($devices->{name}) && (defined($devices->{execId}) || defined($devices->{triggerId})) )
  981. {
  982. foreach my $module (keys %{$modules{$hash->{TYPE}}{defptr}})
  983. {
  984. my $def = $modules{$hash->{TYPE}}{defptr}{"$module"};
  985. if (defined($def->{inExecId}) && ($def->{inExecId} eq $devices->{execId}))
  986. {
  987. if ($devices->{name} eq 'ExecutionStateChangedEvent')
  988. {
  989. $def->{inExecState} = $devices->{newState};
  990. $def->{inExecId} = 'finished' if ($devices->{newState} == 4);
  991. $def->{inExecId} = 'canceled' if ($devices->{newState} == 5);
  992. }
  993. }
  994. elsif (defined($def->{inTriggerId}) && ($def->{inTriggerId} eq $devices->{triggerId}))
  995. {
  996. $def->{inTriggerState} = $devices->{name};
  997. $def->{inTriggerId} = 'finished' if ($devices->{name} eq '4');
  998. $def->{inTriggerId} = 'canceled' if ($devices->{name} eq '5');
  999. }
  1000. }
  1001. }
  1002. }
  1003. }
  1004. }
  1005. sub tahoma_parseApplyRequest($$)
  1006. {
  1007. my($hash, $json) = @_;
  1008. my $name = $hash->{NAME};
  1009. Log3 $name, 4, "$name: tahoma_parseApplyRequest";
  1010. $hash->{inExecState} = 0;
  1011. if (ref($json) ne 'HASH') {
  1012. Log3 $name, 3, "$name: tahoma_parseApplyRequest response is not a valid hash";
  1013. return;
  1014. }
  1015. if (defined($json->{execId})) {
  1016. $hash->{inExecId} = $json->{execId};
  1017. } else {
  1018. $hash->{inExecId} = "undefined";
  1019. }
  1020. if (defined($json->{events}) && defined($hash->{IODev}))
  1021. {
  1022. tahoma_parseGetEvents($hash->{IODev},$json->{events})
  1023. }
  1024. }
  1025. sub tahoma_parseGetSetup($$)
  1026. {
  1027. my($hash, $json) = @_;
  1028. my $name = $hash->{NAME};
  1029. if (ref($json) ne 'HASH') {
  1030. Log3 $name, 3, "$name: tahoma_parseGetSetup response is not a valid hash";
  1031. return;
  1032. }
  1033. $hash->{gatewayId} = $json->{setup}{gateways}[0]{gatewayId};
  1034. my @devices = ();
  1035. foreach my $device (@{$json->{setup}{devices}}) {
  1036. Log3 $name, 4, "$name: tahoma_parseGetSetup device = $device->{label}";
  1037. push( @devices, $device );
  1038. }
  1039. $hash->{helper}{devices} = \@devices;
  1040. if ($json->{setup}{rootPlace}) {
  1041. my $places = $json->{setup}{rootPlace};
  1042. #Log3 $name, 4, "$name: tahoma_parseGetSetup places= " . Dumper($places);
  1043. tahoma_parseGetSetupPlaces($hash, $places);
  1044. }
  1045. tahoma_autocreate($hash);
  1046. tahoma_updateDevices($hash);
  1047. tahoma_defineCommands($hash);
  1048. }
  1049. sub tahoma_parseGetSetupPlaces($$)
  1050. {
  1051. my($hash, $places) = @_;
  1052. my $name = $hash->{NAME};
  1053. #Log3 $name, 4, "$name: tahoma_parseGetSetupPlaces " . Dumper($places);
  1054. my $devices = $hash->{helper}{devices};
  1055. if (ref $places eq 'ARRAY') {
  1056. #Log3 $name, 4, "$name: tahoma_parseGetSetupPlaces isArray";
  1057. foreach my $place (@{$places}) {
  1058. push( @{$devices}, $place );
  1059. my $placesNext = $place->{subPlaces};
  1060. tahoma_parseGetSetupPlaces($hash, $placesNext ) if ($placesNext);
  1061. }
  1062. }
  1063. else {
  1064. #Log3 $name, 4, "$name: tahoma_parseGetSetupPlaces isScalar";
  1065. push( @{$devices}, $places );
  1066. my $placesNext = $places->{subPlaces};
  1067. tahoma_parseGetSetupPlaces($hash, $placesNext) if ($placesNext);
  1068. }
  1069. }
  1070. sub tahoma_parseGetActionGroups($$)
  1071. {
  1072. my($hash, $json) = @_;
  1073. my $name = $hash->{NAME};
  1074. Log3 $name, 4, "$name: tahoma_parseGetActionGroups";
  1075. if (ref($json) ne 'HASH') {
  1076. Log3 $name, 3, "$name: tahoma_parseGetActionGroups response is not a valid hash";
  1077. return;
  1078. }
  1079. my $devices = $hash->{helper}{devices};
  1080. foreach my $action (@{$json->{actionGroups}}) {
  1081. push( @{$devices}, $action );
  1082. }
  1083. tahoma_autocreate($hash);
  1084. tahoma_updateDevices($hash);
  1085. tahoma_defineCommands($hash);
  1086. }
  1087. sub tahoma_parseRefreshAllStates($$)
  1088. {
  1089. my($hash, $json) = @_;
  1090. my $name = $hash->{NAME};
  1091. Log3 $name, 4, "$name: tahoma_parseRefreshAllStates";
  1092. }
  1093. sub tahoma_parseGetStates($$)
  1094. {
  1095. my($hash, $states) = @_;
  1096. my $name = $hash->{NAME};
  1097. Log3 $name, 4, "$name: tahoma_parseGetStates";
  1098. if (ref($states) ne 'HASH') {
  1099. Log3 $name, 3, "$name: tahoma_parseGetStates response is not a valid hash";
  1100. return;
  1101. }
  1102. if( defined($states->{devices}) ) {
  1103. foreach my $devices ( @{$states->{devices}} ) {
  1104. if( defined($devices->{deviceURL}) ) {
  1105. my $id = $devices->{deviceURL};
  1106. my $fid = tahoma_fhemIdFromDevice($id);
  1107. my $devname = "tahoma_". $fid;
  1108. my $d = $modules{$hash->{TYPE}}{defptr}{"$fid"};
  1109. if( defined($d) )# && $d->{NAME} eq $devname )
  1110. {
  1111. readingsBeginUpdate($d);
  1112. foreach my $state (@{$devices->{states}}) {
  1113. next if (!defined($state->{name}) || !defined($state->{value}));
  1114. if ($state->{name} eq "core:ClosureState") {
  1115. $state->{value} = 100 - $state->{value} if ($attr{$d->{NAME}}{levelInvert});
  1116. readingsBulkUpdate($d, "state", "dim".$state->{value});
  1117. } elsif ($state->{name} eq "core:OpenClosedState") {
  1118. readingsBulkUpdate($d, "devicestate", $state->{value});
  1119. }
  1120. readingsBulkUpdate($d, (split(":",$state->{name}))[-1], $state->{value});
  1121. }
  1122. my ($seconds) = gettimeofday();
  1123. readingsBulkUpdate( $d, ".lastupdate", $seconds, 0 );
  1124. readingsEndUpdate($d,1);
  1125. }
  1126. }
  1127. }
  1128. }
  1129. }
  1130. sub tahoma_parseEnduserAPISetupGateways($$)
  1131. {
  1132. my($hash, $json) = @_;
  1133. my $name = $hash->{NAME};
  1134. Log3 $name, 4, "$name: tahoma_parseEnduserAPISetupGateways";
  1135. if (ref($json) ne 'HASH') {
  1136. Log3 $name, 3, "$name: tahoma_parseEnduserAPISetupGateways response is not a valid hash";
  1137. return;
  1138. }
  1139. eval { $hash->{inGateway} = $json->{result}; };
  1140. eval { $hash->{inGateway} = $json->[0]{gatewayId}; };
  1141. }
  1142. sub tahoma_parseGetCurrentExecutions($$)
  1143. {
  1144. my($hash, $json) = @_;
  1145. my $name = $hash->{NAME};
  1146. Log3 $name, 4, "$name: tahoma_parseGetCurrentExecutions";
  1147. if (ref($json) ne 'HASH') {
  1148. Log3 $name, 3, "$name: tahoma_parseGetCurrentExecutions response is not a valid hash";
  1149. return;
  1150. }
  1151. }
  1152. sub tahoma_parseScheduleActionGroup($$)
  1153. {
  1154. my($hash, $json) = @_;
  1155. my $name = $hash->{NAME};
  1156. Log3 $name, 4, "$name: tahoma_parseScheduleActionGroup";
  1157. if (ref($json) ne 'HASH') {
  1158. Log3 $name, 3, "$name: tahoma_parseScheduleActionGroup response is not a valid hash";
  1159. return;
  1160. }
  1161. if (defined $json->{actionGroup})
  1162. {
  1163. $hash->{inTriggerState} = 0;
  1164. if (defined($json->{actionGroup}[0]{triggerId})) {
  1165. $hash->{inTriggerId} = $json->{actionGroup}[0]{triggerId};
  1166. } else {
  1167. $hash->{inTriggerId} = "undefined";
  1168. }
  1169. }
  1170. if (defined($json->{events}) && defined($hash->{IODev}))
  1171. {
  1172. tahoma_parseGetEvents($hash->{IODev},$json->{events})
  1173. }
  1174. }
  1175. sub tahoma_parseLaunchActionGroup($$)
  1176. {
  1177. my($hash, $json) = @_;
  1178. my $name = $hash->{NAME};
  1179. Log3 $name, 4, "$name: tahoma_parseLaunchActionGroup";
  1180. if (ref($json) ne 'HASH') {
  1181. Log3 $name, 3, "$name: tahoma_parseLaunchActionGroup response is not a valid hash";
  1182. return;
  1183. }
  1184. if (defined $json->{actionGroup})
  1185. {
  1186. $hash->{inExecState} = 0;
  1187. if (defined($json->{actionGroup}[0]{execId})) {
  1188. $hash->{inExecId} = $json->{actionGroup}[0]{execId};
  1189. } else {
  1190. $hash->{inExecId} = "undefined";
  1191. }
  1192. }
  1193. if (defined($json->{events}) && defined($hash->{IODev}))
  1194. {
  1195. tahoma_parseGetEvents($hash->{IODev},$json->{events})
  1196. }
  1197. }
  1198. sub tahoma_parseCancelExecutions($$)
  1199. {
  1200. my($hash, $json) = @_;
  1201. my $name = $hash->{NAME};
  1202. Log3 $name, 4, "$name: tahoma_parseCancelExecutions";
  1203. }
  1204. sub tahoma_Get($$@)
  1205. {
  1206. my ($hash, $name, $cmd) = @_;
  1207. my $list;
  1208. if( $hash->{SUBTYPE} eq "DEVICE" ) {
  1209. $list = "updateAll:noArg";
  1210. if( $cmd eq "updateAll" ) {
  1211. my ($seconds) = gettimeofday();
  1212. $hash = $hash->{IODev} if (defined ($hash->{IODev}));
  1213. $hash->{refreshStatesTimer} = $seconds;
  1214. return undef;
  1215. }
  1216. } elsif( $hash->{SUBTYPE} eq "SCENE"
  1217. || $hash->{SUBTYPE} eq "GROUP"
  1218. || $hash->{SUBTYPE} eq "PLACE" ) {
  1219. $list = "";
  1220. } elsif( $hash->{SUBTYPE} eq "ACCOUNT" ) {
  1221. $list = "devices:noArg reset:noArg";
  1222. if( $cmd eq "devices" ) {
  1223. my $devices = tahoma_getDevices($hash,0);
  1224. my $ret;
  1225. foreach my $device (@{$devices}) {
  1226. $ret .= "$device->{deviceURL}\t".$device->{label}."\t$device->{uiClass}\t$device->{controllable}\t\n" if ($device->{deviceURL});
  1227. $ret .= "$device->{oid}\t".$device->{label}."\n" if ($device->{oid});
  1228. }
  1229. $ret = "id\t\t\t\tname\t\t\tuiClass\t\tcontrollable\n" . $ret if( $ret );
  1230. $ret = "no devices found" if( !$ret );
  1231. return $ret;
  1232. }
  1233. elsif( $cmd eq "reset" ) {
  1234. HttpUtils_Close($hash);
  1235. $hash->{logged_in} = undef;
  1236. $hash->{loginRetryTimer} = undef;
  1237. return "connection closed";
  1238. }
  1239. }
  1240. return "Unknown argument $cmd, choose one of $list";
  1241. }
  1242. sub tahoma_Set($$@)
  1243. {
  1244. my ($hash, $name, $cmd, $val) = @_;
  1245. #Log3 $name, 3, "$name: tahoma_Set $cmd $val $hash->{SUBTYPE} $hash->{COMMANDS}";
  1246. my $list = "";
  1247. if( $hash->{SUBTYPE} eq "DEVICE" ||
  1248. $hash->{SUBTYPE} eq "GROUP" ||
  1249. $hash->{SUBTYPE} eq "PLACE" ) {
  1250. $list = "dim:slider,0,1,100 setClosure open:noArg close:noArg my:noArg stop:noArg cancel:noArg";
  1251. $list = $hash->{COMMANDS} if (defined $hash->{COMMANDS});
  1252. if( $cmd eq "cancel" ) {
  1253. tahoma_cancelExecutions($hash);
  1254. return undef;
  1255. }
  1256. $cmd = "setClosure" if( $cmd eq "dim" );
  1257. my @commands = split(" ",$list);
  1258. foreach my $command (@commands)
  1259. {
  1260. if( $cmd eq (split(":",$command))[0])
  1261. {
  1262. tahoma_applyRequest($hash,$cmd,$val) if !($attr{$hash->{NAME}}{disable});
  1263. return undef;
  1264. }
  1265. }
  1266. }
  1267. if( $hash->{SUBTYPE} eq "SCENE") {
  1268. $list = "start:noArg startAt cancel:noArg";
  1269. if( $cmd eq "start" ) {
  1270. tahoma_launchActionGroup($hash) if !($attr{$hash->{NAME}}{disable});
  1271. return undef;
  1272. }
  1273. if( $cmd eq "startAt" ) {
  1274. tahoma_scheduleActionGroup($hash,$val) if !($attr{$hash->{NAME}}{disable});
  1275. return undef;
  1276. }
  1277. if( $cmd eq "cancel" ) {
  1278. tahoma_cancelExecutions($hash);
  1279. return undef;
  1280. }
  1281. }
  1282. if( $hash->{SUBTYPE} eq "ACCOUNT") {
  1283. $list = "cancel:noArg reset:noArg refreshAllStates:noArg getStates:noArg";
  1284. if( $cmd eq "cancel" ) {
  1285. tahoma_cancelExecutions($hash);
  1286. return undef;
  1287. }
  1288. elsif( $cmd eq "reset" ) {
  1289. HttpUtils_Close($hash);
  1290. $hash->{logged_in} = undef;
  1291. $hash->{loginRetryTimer} = undef;
  1292. return undef;
  1293. }
  1294. elsif( $cmd eq "refreshAllStates" ) {
  1295. tahoma_refreshAllStates($hash);
  1296. return undef;
  1297. }
  1298. elsif( $cmd eq "getStates" ) {
  1299. tahoma_getStates($hash);
  1300. return undef;
  1301. }
  1302. }
  1303. return "Unknown argument $cmd, choose one of $list";
  1304. }
  1305. sub tahoma_Attr($$$)
  1306. {
  1307. my ($cmd, $name, $attrName, $attrVal) = @_;
  1308. Log3 $name, 3, "$name: tahoma_Attr $cmd $attrName $attrVal";
  1309. my $orig = $attrVal;
  1310. if( $attrName eq "interval" ) {
  1311. my $hash = $defs{$name};
  1312. return "Attribut 'interval' only usable for type ACCOUNT" if $hash->{SUBTYPE} ne "ACCOUNT";
  1313. $attrVal = defined $attrVal ? int($attrVal) : 0;
  1314. $attrVal = int($attrVal);
  1315. $attrVal = 120 if ($attrVal < 120 && $attrVal != 0);
  1316. $hash->{refreshStatesInterval} = $attrVal ? $attrVal / 2 : 120;
  1317. $hash->{getStatesInterval} = $attrVal;
  1318. } elsif( $attrName eq "intervalRefresh" ) {
  1319. my $hash = $defs{$name};
  1320. return "Attribut 'intervalRefresh' only usable for type ACCOUNT" if $hash->{SUBTYPE} ne "ACCOUNT";
  1321. $attrVal = defined $attrVal ? int($attrVal) : 120;
  1322. $attrVal = 60 if ($attrVal < 60 && $attrVal != 0);
  1323. $hash->{refreshStatesInterval} = $attrVal;
  1324. } elsif( $attrName eq "intervalStates" ) {
  1325. my $hash = $defs{$name};
  1326. return "Attribut 'intervalStates' only usable for type ACCOUNT" if $hash->{SUBTYPE} ne "ACCOUNT";
  1327. $attrVal = defined $attrVal ? int($attrVal) : 0;
  1328. $attrVal = int($attrVal);
  1329. $attrVal = 120 if ($attrVal < 120 && $attrVal != 0);
  1330. $hash->{getStatesInterval} = $attrVal;
  1331. } elsif( $attrName eq "intervalEvents" ) {
  1332. my $hash = $defs{$name};
  1333. return "Attribut 'intervalEvents' only usable for type ACCOUNT" if $hash->{SUBTYPE} ne "ACCOUNT";
  1334. $attrVal = defined $attrVal ? int($attrVal) : 2;
  1335. $attrVal = int($attrVal);
  1336. $attrVal = 2 if ($attrVal < 2 && $attrVal != 0);
  1337. $hash->{getEventsInterval} = $attrVal;
  1338. } elsif( $attrName eq "disable" ) {
  1339. my $hash = $defs{$name};
  1340. RemoveInternalTimer($hash);
  1341. if( $cmd eq "set" && $attrVal ne "0" ) {
  1342. } else {
  1343. $attr{$name}{$attrName} = 0;
  1344. }
  1345. } elsif( $attrName eq "blocking" ) {
  1346. my $hash = $defs{$name};
  1347. return "Attribut 'blocking' only usable for type ACCOUNT" if $hash->{SUBTYPE} ne "ACCOUNT";
  1348. $attrVal = defined $attrVal ? int($attrVal) : 0;
  1349. $hash->{BLOCKING} = $attrVal;
  1350. } elsif( $attrName eq "placeClasses" ) {
  1351. my $hash = $defs{$name};
  1352. return "Attribut 'placeClasses' only usable for type PLACE" if $hash->{SUBTYPE} ne "PLACE";
  1353. $attrVal = defined $attrVal ? $attrVal : 'RollerShutter';
  1354. $hash->{inClass} = $attrVal;
  1355. }
  1356. elsif ( $attrName eq "levelInvert" ) {
  1357. my $hash = $defs{$name};
  1358. return "Attribut 'placeClasses' only usable for type DEVICE" if $hash->{SUBTYPE} ne "DEVICE";
  1359. $attrVal = defined $attrVal ? int($attrVal) : 0;
  1360. my $device = tahoma_getDeviceDetail( $hash, $hash->{device} );
  1361. $device->{levelInvert} = $attrVal if (defined $device);
  1362. }
  1363. if( $cmd eq "set" ) {
  1364. if( $orig ne $attrVal ) {
  1365. $attr{$name}{$attrName} = $attrVal;
  1366. return $attrName ." set to ". $attrVal;
  1367. }
  1368. }
  1369. return;
  1370. }
  1371. sub tahoma_UserAgent_NonblockingGet($)
  1372. {
  1373. my ($param) = @_;
  1374. my ($hash) = $param->{hash};
  1375. $hash = $hash->{IODev} if (defined ($hash->{IODev}));
  1376. # restore parameter from last HttpUtils call
  1377. if (defined $hash->{paramHash} && $hash->{paramHash}{keepalive} && !$hash->{request_active})
  1378. {
  1379. my $paramHash = $hash->{paramHash};
  1380. if (defined $paramHash)
  1381. {
  1382. delete $paramHash->{timeout};
  1383. delete $paramHash->{noshutdown};
  1384. delete $paramHash->{hash};
  1385. delete $paramHash->{page};
  1386. delete $paramHash->{subpage};
  1387. delete $paramHash->{data};
  1388. delete $paramHash->{method};
  1389. delete $paramHash->{callback};
  1390. delete $paramHash->{nonblocking};
  1391. }
  1392. $paramHash = {} if !(defined $paramHash);
  1393. $paramHash->{$_} = $param->{$_} for (keys %$param);
  1394. $param = $paramHash;
  1395. }
  1396. $hash->{paramHash} = $param;
  1397. my $name = $hash->{NAME};
  1398. Log3 $name, 5, "$name: tahoma_UserAgent_NonblockingGet page=$param->{page}";
  1399. #"User-Agent":"TaHoma/7980 CFNetwork/758.5.3 Darwin/15.6.0","Proxy-Connection":"keep-alive","Accept":"*/*","Connection":"keep-alive","Content-Length":"49","Accept-Encoding":"gzip, deflate","Content-Type":"application/x-www-form-urlencoded","Accept-Language":"de-de","Host":"www.tahomalink.com"
  1400. $param->{header} = {'User-Agent' => $hash->{userAgent} }; #, 'Accept-Language' => "de-de", 'Accept-Encoding' => "gzip, deflate"};
  1401. $param->{header}{Cookie} = $hash->{HTTPCookies} if ($hash->{HTTPCookies});
  1402. $param->{compress} = 1;
  1403. $param->{keepalive} = 1;
  1404. if (index($hash->{url},'file:') == 0)
  1405. {
  1406. $param->{url} = $hash->{url} . $param->{page} . '.json';
  1407. my $find = "../";
  1408. $find = quotemeta $find; # escape regex metachars if present
  1409. $param->{url} =~ s/$find//g;
  1410. }
  1411. else
  1412. {
  1413. $param->{url} = $hash->{url} . $param->{page};
  1414. $param->{url} .= $param->{subpage} if ($param->{subpage});
  1415. }
  1416. $hash->{request_active} = 1;
  1417. $hash->{request_time} = time;
  1418. if ($param->{blocking})
  1419. {
  1420. my($err,$data) = HttpUtils_BlockingGet($param);
  1421. $param->{callback}($param, $err, $data, length $data) if($param->{callback});
  1422. }
  1423. else
  1424. {
  1425. my($err,$data) = HttpUtils_NonblockingGet($param);
  1426. }
  1427. }
  1428. sub tahoma_encode_utf8($)
  1429. {
  1430. my ($text) = @_;
  1431. $text =~ s/Ä/Ae/g;
  1432. $text =~ s/Ö/Oe/g;
  1433. $text =~ s/Ü/Ue/g;
  1434. $text =~ s/ä/ae/g;
  1435. $text =~ s/ö/oe/g;
  1436. $text =~ s/ü/ue/g;
  1437. $text =~ s/ß/ss/g;
  1438. return $text;
  1439. }
  1440. sub tahoma_GetCookies($$)
  1441. {
  1442. my ($hash, $header) = @_;
  1443. my $name = $hash->{NAME};
  1444. Log3 $name, 5, "$name: tahoma_GetCookies looking for Cookies in header";
  1445. foreach my $cookie ($header =~ m/set-cookie: ?(.*)/gi) {
  1446. Log3 $name, 5, "$name: Set-Cookie: $cookie";
  1447. $cookie =~ /([^,; ]+)=([^,; ]+)[;, ]*(.*)/;
  1448. Log3 $name, 4, "$name: Cookie: $1 Wert $2 Rest $3";
  1449. $hash->{HTTPCookieHash}{$1}{Value} = $2;
  1450. $hash->{HTTPCookieHash}{$1}{Options} = ($3 ? $3 : "");
  1451. }
  1452. $hash->{HTTPCookies} = join ("; ", map ($_ . "=".$hash->{HTTPCookieHash}{$_}{Value},
  1453. sort keys %{$hash->{HTTPCookieHash}}));
  1454. }
  1455. sub tahoma_encrypt($)
  1456. {
  1457. my ($decoded) = @_;
  1458. my $key = getUniqueId();
  1459. my $encoded;
  1460. return $decoded if( $decoded =~ /^crypt:(.*)/ );
  1461. for my $char (split //, $decoded) {
  1462. my $encode = chop($key);
  1463. $encoded .= sprintf("%.2x",ord($char)^ord($encode));
  1464. $key = $encode.$key;
  1465. }
  1466. return 'crypt:'. $encoded;
  1467. }
  1468. sub tahoma_decrypt($)
  1469. {
  1470. my ($encoded) = @_;
  1471. my $key = getUniqueId();
  1472. my $decoded;
  1473. return $encoded if not ( $encoded =~ /^crypt:(.*)/ );
  1474. $encoded = $1 if( $encoded =~ /^crypt:(.*)/ );
  1475. for my $char (map { pack('C', hex($_)) } ($encoded =~ /(..)/g)) {
  1476. my $decode = chop($key);
  1477. $decoded .= chr(ord($char)^ord($decode));
  1478. $key = $decode.$key;
  1479. }
  1480. return $decoded;
  1481. }
  1482. 1;
  1483. =pod
  1484. =item summary commumication modul for io-homecontrol&reg gateway TaHoma&reg
  1485. =item summary_DE Kommunicationsmodul f&uuml;er io-homecontrol&reg Gateway TaHoma&reg
  1486. =begin html
  1487. <a name="tahoma"></a>
  1488. <h3>tahoma</h3>
  1489. <ul>
  1490. The module realizes the communication with io-homecontrol&reg; Devices e.g. from Somfy&reg; or Velux&reg;<br>
  1491. A registered TaHoma&reg; Connect gateway from Overkiz&reg; sold by Somfy&reg; which is continuously connected to the internet is necessary for the module.<br>
  1492. <br><br>
  1493. Notes:
  1494. <ul>
  1495. <li>JSON has to be installed on the FHEM host.</li>
  1496. <li>on problems refer also the fhem forum <a href="https://forum.fhem.de/index.php/topic,28045.0.html">IO-Homecontrol Devices &uuml;ber Tahoma Box einbinden</a></li>
  1497. </ul><br>
  1498. <a name="tahoma_Define"></a>
  1499. <b>Define</b>
  1500. <ul>
  1501. <code>define &lt;name&gt; tahoma ACCOUNT &lt;username&gt; &lt;password&gt;</code><br>
  1502. <code>define &lt;name&gt; tahoma DEVICE &lt;DeviceURL&gt;</code><br>
  1503. <code>define &lt;name&gt; tahoma PLACE &lt;oid&gt;</code><br>
  1504. <code>define &lt;name&gt; tahoma SCENE &lt;oid&gt;</code><br>
  1505. <code>define &lt;name&gt; tahoma GROUP &lt;tahoma_device1&gt;,&lt;tahoma_device2&gt;,&lt;tahoma_device3&gt;</code><br>
  1506. <br>
  1507. <br>
  1508. A definition is only necessary for a tahoma device:<br>
  1509. <code>define &lt;name&gt; tahoma ACCOUNT &lt;username&gt; &lt;password&gt;</code><br>
  1510. <b>If a tahoma device of the type ACCOUNT is created, all other devices accessible by the tahoma gateway are automatically created!</b><br>
  1511. If the account is valid, the setup will be read from the server.<br>
  1512. All registered devices are automatically created with name tahoma_12345 (device number 12345 is used from setup)<br>
  1513. All defined rooms will be are automatically created.<br>
  1514. Also all defined scenes will be automatically created.<br>
  1515. Groups of devices can be manually added to send out one group command for all attached devices<br>
  1516. <br>
  1517. <br>
  1518. <b>global Attributes for ACCOUNT:</b>
  1519. <ul>
  1520. If autocreate is disabled, no devices, places and scenes will be created automatically:<br>
  1521. <code>attr autocreate disable</code><br>
  1522. </ul>
  1523. <br>
  1524. <b>local Attributes for ACCOUNT:</b>
  1525. <ul>
  1526. Normally, the web commands will be send asynchronous, and this can be forced to wait of the result by blocking=1<br>
  1527. <code>attr tahoma1 blocking 1</code><br><br>
  1528. </ul>
  1529. <ul>
  1530. Normally, the login data is stored encrypted after the first start, but this functionality can be disabled by cryptLoginData=0<br>
  1531. <code>attr tahoma1 cryptLoginData 0</code><br><br>
  1532. </ul>
  1533. <ul>
  1534. The account can be completely disabled, so no communication to server is running:<br>
  1535. <code>attr tahoma1 disable 1</code><br><br>
  1536. </ul>
  1537. <ul>
  1538. The interval [seconds] for refreshing and fetching all states can be set:<br>
  1539. This is an old attribute, the default is 0 = off.<br>
  1540. <code>attr tahoma1 interval 300</code><br><br>
  1541. </ul>
  1542. <ul>
  1543. The interval [seconds] for refreshing states can be changed:<br>
  1544. The default is 120s, allowed minimum is 60s, 0 = off.<br>
  1545. This command actualizes the states of devices, which will be externally changed e.g. by controller.<br>
  1546. <code>attr tahoma1 intervalRefresh 120</code><br><br>
  1547. </ul>
  1548. <ul>
  1549. The interval [seconds] for fetching all states can be set:<br>
  1550. The default is 0 = off, allowed minimum is 120s.<br>
  1551. Normally this isn't necessary to set, because all states will be actualized by events.<br>
  1552. <code>attr tahoma1 intervalStates 300</code><br><br>
  1553. </ul>
  1554. <ul>
  1555. The interval [seconds] for fetching new events can be changed:<br>
  1556. The default is 2s, allowed minimum is 2s.<br>
  1557. <code>attr tahoma1 intervalEvents 300</code><br>
  1558. </ul>
  1559. <br>
  1560. <b>local Attributes for DEVICE:</b>
  1561. <ul>
  1562. If the closure value 0..100 should be 100..0, the level can be inverted:<br>
  1563. <code>attr tahoma_23234545 levelInvert 1</code><br><br>
  1564. </ul>
  1565. <ul>
  1566. The device can be disabled:<br>
  1567. The device is disabled then for direct access and indirect access by GROUP and PLACE, but not by SCENE.<br>
  1568. <code>attr tahoma_23234545 disable 1</code><br>
  1569. </ul>
  1570. <br>
  1571. <b>local Attributes for PLACE:</b>
  1572. <ul>
  1573. The commands in a room will only affect the devices in the room with inClass=RollerShutter.<br>
  1574. This can be extend or changed by setting the placeClasses attribute:<br>
  1575. <code>attr tahoma_abc12345 placeClasses RollerShutter ExteriorScreen Window</code><br><br>
  1576. </ul>
  1577. <ul>
  1578. The place and its sub places can be disabled:<br>
  1579. <code>attr tahoma_abc12345 disable 1</code><br>
  1580. </ul>
  1581. <br>
  1582. <b>local Attributes for SCENE:</b>
  1583. <ul>
  1584. The scene can be disabled:<br>
  1585. <code>attr tahoma_4ef30a23 disable 1</code><br>
  1586. </ul>
  1587. <br>
  1588. <b>local Attributes for GROUP:</b>
  1589. <ul>
  1590. The group can be disabled:<br>
  1591. <code>attr tahoma_group1 disable 1</code><br>
  1592. </ul>
  1593. <br>
  1594. <b>Examples:</b>
  1595. <ul>
  1596. <code>define tahoma1 tahoma ACCOUNT abc@test.com myPassword</code><br>
  1597. <code>attr tahoma1 blocking 0</code><br>
  1598. <code>attr tahoma1 room tahoma</code><br>
  1599. <br>
  1600. <br>Automatic created device e.g.:<br>
  1601. <code>define tahoma_23234545 tahoma DEVICE io://0234-5678-9012/23234545</code><br>
  1602. <code>attr tahoma_23234545 IODev tahoma1</code><br>
  1603. <code>attr tahoma_23234545 alias RollerShutter Badezimmer</code><br>
  1604. <code>attr tahoma_23234545 room tahoma</code><br>
  1605. <code>attr tahoma_23234545 webCmd dim</code><br>
  1606. <br>
  1607. <br>Automatic created place e.g.:<br>
  1608. <code>define tahoma_abc12345 tahoma PLACE abc12345-0a23-0b45-0c67-d5e6f7a1b2c3</code><br>
  1609. <code>attr tahoma_abc12345 IODev tahoma1</code><br>
  1610. <code>attr tahoma_abc12345 alias room Wohnzimmer</code><br>
  1611. <code>attr tahoma_abc12345 room tahoma</code><br>
  1612. <br>
  1613. <br>Automatic created scene e.g.:<br>
  1614. <code>define tahoma_4ef30a23 tahoma SCENE 4ef30a23-0b45-0c67-d5e6-f7a1b2c32e3f</code><br>
  1615. <code>attr tahoma_4ef30a23 IODev tahoma1</code><br>
  1616. <code>attr tahoma_4ef30a23 alias scene Rolladen S&uuml;dfenster zu</code><br>
  1617. <code>attr tahoma_4ef30a23 room tahoma</code><br>
  1618. <br>
  1619. <br>manual created group e.g.:<br>
  1620. <code>define tahoma_group1 tahoma GROUP tahoma_23234545,tahoma_23234546,tahoma_23234547</code><br>
  1621. <code>attr tahoma_group1 IODev tahoma1</code><br>
  1622. <code>attr tahoma_group1 alias Gruppe Rolladen Westen</code><br>
  1623. <code>attr tahoma_group1 room tahoma</code><br>
  1624. </ul>
  1625. </ul><br>
  1626. </ul>
  1627. =end html
  1628. =cut