39_alexa.pm 24 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767
  1. # $Id: 39_alexa.pm 16299 2018-03-01 08:06:55Z justme1968 $
  2. package main;
  3. use strict;
  4. use warnings;
  5. use JSON;
  6. use Data::Dumper;
  7. use vars qw(%modules);
  8. use vars qw(%defs);
  9. use vars qw(%attr);
  10. use vars qw($readingFnAttributes);
  11. sub Log($$);
  12. sub Log3($$$);
  13. sub
  14. alexa_Initialize($)
  15. {
  16. my ($hash) = @_;
  17. #$hash->{ReadFn} = "alexa_Read";
  18. $hash->{DefFn} = "alexa_Define";
  19. #$hash->{NOTIFYDEV} = "global";
  20. #$hash->{NotifyFn} = "alexa_Notify";
  21. $hash->{UndefFn} = "alexa_Undefine";
  22. $hash->{SetFn} = "alexa_Set";
  23. $hash->{GetFn} = "alexa_Get";
  24. $hash->{AttrFn} = "alexa_Attr";
  25. $hash->{AttrList} = "alexaMapping:textField-long alexaTypes:textField-long fhemIntents:textField-long ".
  26. "articles prepositions ".
  27. "echoRooms:textField-long ".
  28. "alexaConfirmationLevel:2,1,0 alexaStatusLevel:2,1 ".
  29. "skillId:textField ".
  30. $readingFnAttributes;
  31. }
  32. #####################################
  33. sub
  34. alexa_AttrDefaults($)
  35. {
  36. my ($hash) = @_;
  37. my $name = $hash->{NAME};
  38. if( !AttrVal( $name, 'alexaMapping', undef ) ) {
  39. CommandAttr(undef,"$name alexaMapping #Characteristic=<name>=<value>,...\n".
  40. "On=verb=schalte,valueOn=an;ein,valueOff=aus,valueToggle=um\n\n".
  41. "Brightness=verb=stelle,property=helligkeit,valuePrefix=auf,values=AMAZON.NUMBER,valueSuffix=prozent\n\n".
  42. "Hue=verb=stelle,valuePrefix=auf,values=rot:0;grün:128;blau:200\n".
  43. "Hue=verb=färbe,values=rot:0;grün:120;blau:220\n\n".
  44. "Saturation=verb=stelle,property=sättigung,valuePrefix=auf,values=AMAZON.NUMBER\n".
  45. "Saturation=verb=sättige,values=AMAZON.NUMBER\n\n".
  46. "TargetPosition=verb=mach,articles=den;die,values=auf:100;zu:0\n".
  47. "TargetPosition=verb=stelle,valuePrefix=auf,values=AMAZON.NUMBER,valueSuffix=prozent\n\n".
  48. "TargetTemperature=verb=stelle,valuePrefix=auf,values=AMAZON.NUMBER,valueSuffix=grad\n\n".
  49. "Volume:verb=stelle,valuePrefix=auf,values=AMAZON.NUMBER,valueSuffix=prozent\n\n".
  50. "#Weckzeit=verb=stelle,valuePrefix=auf;für,values=AMAZON.TIME,valueSuffix=uhr" );
  51. }
  52. if( !AttrVal( $name, 'alexaTypes', undef ) ) {
  53. CommandAttr(undef,"$name alexaTypes #Type=<alias>[,<alias2>[,...]]\n".
  54. "light=licht,lampen\n".
  55. "blind=rolladen,rolläden,jalousie,jalousien,rollo,rollos" );
  56. }
  57. if( !AttrVal( $name, 'echoRooms', undef ) ) {
  58. CommandAttr(undef,"$name echoRooms #<deviceId>=<room>\n" );
  59. }
  60. if( !AttrVal( $name, 'fhemIntents', undef ) ) {
  61. CommandAttr(undef,"$name fhemIntents #IntentName=<sample utterance>\n".
  62. "gutenMorgen=guten morgen\n".
  63. "guteNacht=gute nacht" );
  64. }
  65. }
  66. sub
  67. alexa_Define($$)
  68. {
  69. my ($hash, $def) = @_;
  70. my @a = split("[ \t][ \t]*", $def);
  71. return "Usage: define <name> alexa" if(@a != 2);
  72. my $name = $a[0];
  73. $hash->{NAME} = $name;
  74. my $d = $modules{$hash->{TYPE}}{defptr};
  75. return "$hash->{TYPE} device already defined as $d->{NAME}." if( defined($d) && $name ne $d->{NAME} );
  76. $modules{$hash->{TYPE}}{defptr} = $hash;
  77. addToAttrList("$hash->{TYPE}Name");
  78. addToAttrList("$hash->{TYPE}Room");
  79. alexa_AttrDefaults($hash);
  80. $hash->{STATE} = 'active';
  81. return undef;
  82. }
  83. sub
  84. alexa_Notify($$)
  85. {
  86. my ($hash,$dev) = @_;
  87. return if($dev->{NAME} ne "global");
  88. return if(!grep(m/^INITIALIZED|REREADCFG$/, @{$dev->{CHANGED}}));
  89. return undef;
  90. }
  91. sub
  92. alexa_Undefine($$)
  93. {
  94. my ($hash, $arg) = @_;
  95. delete $modules{$hash->{TYPE}}{defptr};
  96. return undef;
  97. }
  98. sub
  99. alexa_Set($$@)
  100. {
  101. my ($hash, $name, $cmd, @args) = @_;
  102. my $list = "reload:noArg skillId";
  103. if( $cmd eq 'reload' ) {
  104. $hash->{".triggerUsed"} = 1;
  105. if( @args ) {
  106. FW_directNotify($name, "reload $args[0]");
  107. } else {
  108. FW_directNotify($name, 'reload');
  109. }
  110. return undef;
  111. } elsif( $cmd eq 'execute' ) {
  112. my ($intent,$applicationId) = split(':', shift @args, 2 );
  113. return 'usage $cmd execute <intent> [json]' if( !$intent );
  114. my $json = join(' ',@args);
  115. my $decoded = eval { decode_json($json) };
  116. if( $@ ) {
  117. my $msg = "json error: $@ in $json";
  118. Log3 $name, 2, "$name: $msg";
  119. return $msg;
  120. }
  121. Log3 $name, 5, "$name: \"$json\" -> ". Dumper $decoded;
  122. my $cmd = '{Log 1, "test"; return "result";}';
  123. Log3 $name, 5, "$name: cmd: $cmd";
  124. if( ref($decoded->{slots}) eq 'HASH' ) {
  125. $hash->{active} = 1;
  126. my $intent = $intent;
  127. $intent = "$intent:$applicationId" if( $applicationId );
  128. readingsSingleUpdate($hash, 'fhemIntent', $intent, 1 );
  129. my $exec = EvalSpecials($cmd, %{$decoded->{slots}});
  130. Log3 $name, 5, "$name: exec: $exec";
  131. my $ret = AnalyzeCommandChain($hash, $exec);
  132. Log3 $name, 5, "$name: ret ". ($ret?$ret:"undefined");
  133. $hash->{active} = 0;
  134. return $ret;
  135. }
  136. return undef;
  137. } elsif( $cmd eq 'skillId' ) {
  138. return CommandAttr(undef,"$name skillId $args[0]" );
  139. }
  140. return "Unknown argument $cmd, choose one of $list";
  141. }
  142. sub
  143. alexa_Get($$@)
  144. {
  145. my ($hash, $name, $cmd) = @_;
  146. my $list = "customSlotTypes:noArg interactionModel:noArg skillId:noArg";
  147. if( lc($cmd) eq 'customslottypes' ) {
  148. if( $hash->{CL} ) {
  149. FW_directNotify($name, "customSlotTypes $hash->{CL}{NAME}");
  150. } else {
  151. FW_directNotify($name, 'customSlotTypes');
  152. }
  153. return undef;
  154. } elsif( lc($cmd) eq 'interactionmodel' ) {
  155. my %mappings;
  156. if( my $mappings = AttrVal( $name, 'alexaMapping', undef ) ) {
  157. foreach my $mapping ( split( / |\n/, $mappings ) ) {
  158. next if( !$mapping );
  159. next if( $mapping =~ /^#/ );
  160. my %characteristic;
  161. my ($characteristic, $remainder) = split( /:|=/, $mapping, 2 );
  162. if( $characteristic =~ m/([^.]+)\.([^.]+)/ ) {
  163. $characteristic = $1;
  164. $characteristic{device} = $2;
  165. }
  166. my @parts = split( /,/, $remainder );
  167. foreach my $part (@parts) {
  168. my @p = split( '=', $part );
  169. if( $p[1] =~ m/;/ ) {
  170. $p[1] =~ s/\+/ /g;
  171. my @values = split(';', $p[1]);
  172. my @values2 = grep {$_ ne ''} @values;
  173. $characteristic{$p[0]} = \@values2;
  174. if( scalar @values != scalar @values2 ) {
  175. $characteristic{"_$p[0]"} = \@values;
  176. $characteristic{$p[0]} = $values2[0] if( scalar @values2 == 1 );
  177. }
  178. } else {
  179. $p[1] =~ s/\+/ /g;
  180. $characteristic{$p[0]} = $p[1];
  181. }
  182. }
  183. $mappings{$characteristic} = [] if( !$mappings{$characteristic} );
  184. push @{$mappings{$characteristic}}, \%characteristic;
  185. }
  186. }
  187. #Log 1, Dumper \%mappings;
  188. my %types;
  189. if( my $entries = AttrVal( $name, 'alexaTypes', undef ) ) {
  190. sub append($$$) {
  191. my($a, $c, $v) = @_;
  192. if( !defined($a->{$c}) ) {
  193. $a->{$c} = {};
  194. }
  195. $a->{$c}{$v} = 1;
  196. }
  197. sub merge($$) {
  198. my ($a, $b) = @_;
  199. return $a if( !defined($b) );
  200. my @result = ();
  201. if( ref($b) eq 'ARRAY' ) {
  202. @result = sort keys %{{map {((split(':',$_,2))[0] => 1)} (@{$a}, @{$b})}};
  203. } else {
  204. push @{$a}, $b;
  205. return $a;
  206. }
  207. return \@result;
  208. }
  209. foreach my $entry ( split( / |\n/, $entries ) ) {
  210. next if( !$entry );
  211. next if( $entry =~ /^#/ );
  212. my ($type, $remainder) = split( /:|=/, $entry, 2 );
  213. $types{$type} = [];
  214. my @names = split( /,/, $remainder );
  215. foreach my $name (@names) {
  216. push @{$types{$type}}, $name;
  217. }
  218. }
  219. }
  220. Log 1, Dumper \%types;
  221. my $verbsOfIntent = {};
  222. my $intentsOfVerb = {};
  223. my $valuesOfIntent = {};
  224. my $intentsOfCharacteristic = {};
  225. my $characteristicsOfIntent = {};
  226. foreach my $characteristic ( keys %mappings ) {
  227. my $mappings = $mappings{$characteristic};
  228. $mappings = [$mappings] if( ref($mappings) ne 'ARRAY');
  229. my $i = 0;
  230. foreach my $mapping (@{$mappings}) {
  231. if( !$mapping->{verb} ) {
  232. Log3 $name, 2, "alexaMapping: no verb given for $characteristic characteristic";
  233. next;
  234. }
  235. $mapping->{property} = '' if( !$mapping->{property} );
  236. $mapping->{property} = [$mapping->{property}] if( ref($mapping->{property}) ne 'ARRAY' );
  237. foreach my $property (@{$mapping->{property}}) {
  238. my $intent = $characteristic;
  239. $intent = lcfirst($mapping->{valueSuffix}) if( !$property && $mapping->{valueSuffix} );
  240. $intent .= 'Intent';
  241. my $values = [];
  242. $values = merge( $values, $mapping->{values} );
  243. $values = merge( $values, $mapping->{valueOn} );
  244. $values = merge( $values, $mapping->{valueOff} );
  245. $values = merge( $values, $mapping->{valueToggle} );
  246. append($verbsOfIntent, $intent, $mapping->{verb} );
  247. append($intentsOfVerb, $mapping->{verb}, $intent );
  248. append($valuesOfIntent, $intent, join( ',', @{$values} ) );
  249. append($intentsOfCharacteristic, $characteristic, $intent );
  250. append($characteristicsOfIntent, $intent, $characteristic );
  251. }
  252. }
  253. }
  254. Log 1, Dumper $verbsOfIntent;
  255. Log 1, Dumper $intentsOfVerb;
  256. Log 1, Dumper $valuesOfIntent;
  257. Log 1, Dumper $intentsOfCharacteristic;
  258. Log 1, Dumper $characteristicsOfIntent;
  259. my $intents = {};
  260. my $schema = { intents => [] };
  261. my $types = {};
  262. $types->{FHEM_article} = [split( /,|;/, AttrVal( $name, 'articles', 'der,die,das,den' ) ) ];
  263. $types->{FHEM_preposition} = [split( /,|;/, AttrVal( $name, 'prepositions', 'in,im,in der' ) ) ];
  264. my $samples = '';
  265. foreach my $characteristic ( keys %mappings ) {
  266. my $mappings = $mappings{$characteristic};
  267. $mappings = [$mappings] if( ref($mappings) ne 'ARRAY');
  268. my $i = 0;
  269. foreach my $mapping (@{$mappings}) {
  270. if( !$mapping->{verb} ) {
  271. Log3 $name, 2, "alexaMapping: no verb given for $characteristic characteristic";
  272. next;
  273. }
  274. my $values = [];
  275. $values = merge( $values, $mapping->{values} );
  276. $values = merge( $values, $mapping->{valueOn} );
  277. $values = merge( $values, $mapping->{valueOff} );
  278. $values = merge( $values, $mapping->{valueToggle} );
  279. $mapping->{property} = '' if( !$mapping->{property} );
  280. $mapping->{property} = [$mapping->{property}] if( ref($mapping->{property}) ne 'ARRAY' );
  281. foreach my $property (@{$mapping->{property}}) {
  282. my $nr = $i?chr(65+$i):'';
  283. $nr = '' if( $mapping->{valueSuffix} );
  284. #my $intent = $characteristic .'Intent'. $nr;
  285. my $intent = $characteristic;
  286. $intent = lcfirst($mapping->{valueSuffix}) if( !$property && $mapping->{valueSuffix} );
  287. $intent .= 'Intent';
  288. $intent .= $nr;
  289. next if( $intents->{$intent} );
  290. $intents->{$intent} = 1;
  291. my $slots = [];
  292. my $samples2 = [];
  293. push @{$slots}, { name => 'article', type => 'FHEM_article' };
  294. push @{$slots}, { name => 'Device', type => 'FHEM_Device' } if( !$mapping->{device} );
  295. push @{$slots}, { name => 'preposition', type => 'FHEM_preposition' };
  296. push @{$slots}, { name => 'Room', type => 'FHEM_Room' };
  297. if( ref($mapping->{valuePrefix}) eq 'ARRAY' ) {
  298. push @{$slots}, { name => "${characteristic}_valuePrefix$nr", type => "${characteristic}_prefix$nr" };
  299. $types->{"${characteristic}_prefix$nr"} = $mapping->{valuePrefix};
  300. }
  301. my $slot_name = "${characteristic}_Value$nr";
  302. $slot_name = lcfirst($mapping->{valueSuffix})."_Value$nr" if( !$property && $mapping->{valueSuffix} );
  303. if( $mapping->{values} && $mapping->{values} =~ /^AMAZON/ ) {
  304. push @{$slots}, { name => $slot_name, type => $mapping->{values} };
  305. } else {
  306. push @{$slots}, { name => $slot_name, type => "${characteristic}_Value$nr" };
  307. $types->{$slot_name} = $values if( $values->[0] );
  308. }
  309. if( ref($mapping->{valueSuffix}) eq 'ARRAY' ) {
  310. push @{$slots}, { name => "${characteristic}_valueSuffix$nr", type => "${characteristic}_suffix$nr" };
  311. $types->{"${characteristic}_suffix"} = $mapping->{valueSuffix$nr};
  312. }
  313. if( ref($mapping->{articles}) eq 'ARRAY' ) {
  314. $types->{"${characteristic}_article$nr"} = $mapping->{articles};
  315. }
  316. $mapping->{verb} = [$mapping->{verb}] if( ref($mapping->{verb}) ne 'ARRAY' );
  317. foreach my $verb (@{$mapping->{verb}}) {
  318. $samples .= "\n" if( $samples );
  319. my @articles = ('','{article}');
  320. if( ref($mapping->{articles}) eq 'ARRAY' ) {
  321. $articles[1] = "{${characteristic}_article}";
  322. } elsif( $mapping->{articles} ) {
  323. @articles = ($mapping->{articles});
  324. }
  325. foreach my $article (@articles) {
  326. foreach my $room ('','{Room}') {
  327. my $line;
  328. $line .= "$intent $verb";
  329. $line .= " $property" if( $property );
  330. $line .= " $article" if( $article );
  331. $line .= $mapping->{device}?" $mapping->{device}":' {Device}';
  332. $line .= " {preposition} $room" if( $room );
  333. if( ref($mapping->{valuePrefix}) eq 'ARRAY' ) {
  334. $line .= " {${characteristic}_valuePrefix$nr}";
  335. } else {
  336. $line .= " $mapping->{valuePrefix}" if( $mapping->{valuePrefix} );
  337. }
  338. $line .= " {$slot_name}";
  339. if( ref($mapping->{_valueSuffix}) eq 'ARRAY' ) {
  340. $line .= "\n$line";
  341. }
  342. if( ref($mapping->{valueSuffix}) eq 'ARRAY' ) {
  343. $line .= " {${characteristic}_valueSuffix$nr}";
  344. } else {
  345. $line .= " $mapping->{valueSuffix}" if( $mapping->{valueSuffix} );
  346. }
  347. push @{$samples2}, $line;
  348. $samples .= "\n" if( $samples );
  349. $samples .= $line;
  350. }
  351. }
  352. }
  353. push @{$schema->{intents}}, {intent => $intent, slots => $slots};
  354. #push @{$schema->{intents}}, {intent => $intent, slots => $slots, samples => $samples2};
  355. }
  356. ++$i;
  357. }
  358. $samples .= "\n";
  359. }
  360. if( my $entries = AttrVal( $name, 'fhemIntents', undef ) ) {
  361. my %intents;
  362. foreach my $entry ( split( /\n/, $entries ) ) {
  363. next if( !$entry );
  364. next if( $entry =~ /^#/ );
  365. my $slots = [];
  366. my ($intent, $remainder) = split( /:|=/, $entry, 2 );
  367. my @parts = split( /,/, $remainder );
  368. my $utterance = $parts[$#parts];
  369. my $intent_name = "FHEM${intent}Intent";
  370. if( $intent =~ m/^(set|get|attr)\s/ ) {
  371. $intent_name = "FHEM${1}Intent";
  372. my $i = 1;
  373. while( defined($intents{$intent_name}) ) {
  374. $intent_name = "FHEM${1}Intent".chr(65+$i);
  375. ++$i;
  376. }
  377. } elsif( $intent =~ m/^{.*}$/ ) {
  378. $intent_name = 'FHEMperlCodeIntent';
  379. my $nr = '';
  380. my $i = 1;
  381. while( defined($intents{$intent_name}) ) {
  382. if( $i < 26 ) {
  383. $nr = chr(65+$i);
  384. } else {
  385. $nr = chr(64+int($i/26)).chr(65+$i%26);
  386. }
  387. ++$i;
  388. $intent_name = "FHEMperlCodeIntent$nr";
  389. }
  390. my $slot_names = {};
  391. my $u = $utterance;
  392. while( $u =~ /\{(.*?)\}/g ) {
  393. my $slot = $1;
  394. my ($name, $values) = split( /:|=/, $slot, 2 );
  395. my $slot_name = "${intent_name}_${name}";
  396. next if( $slot_names->{$slot_name} );
  397. $slot_names->{$slot_name} = 1;
  398. if( $values ) {
  399. if( $values && $values =~ /^AMAZON/ ) {
  400. push @{$slots}, { name => $slot_name, type => $values };
  401. } else {
  402. push @{$slots}, { name => $slot_name, type => "${intent_name}_${name}_Value" };
  403. $values =~ s/\+/ /g;
  404. my @values = split(';', $values );
  405. $types->{"${intent_name}_${name}_Value"} = \@values if( $values[0] );
  406. }
  407. $slot =~ s/\+/\\\+/g;
  408. $utterance =~ s/\{$slot\}/\{$slot_name\}/;
  409. } else {
  410. push @{$slots}, { name => $name, type => "FHEM_$name" };
  411. }
  412. }
  413. }
  414. $intent_name =~ s/ //g;
  415. $intents{$intent_name} = $intent;
  416. if( @{$slots} ) {
  417. push @{$schema->{intents}}, {intent => $intent_name, slots => $slots };
  418. } else {
  419. push @{$schema->{intents}}, {intent => $intent_name };
  420. }
  421. foreach my $u ( split( '\|', $utterance ) ) {
  422. $samples .= "\n$intent_name $u";
  423. }
  424. }
  425. $samples .= "\n";
  426. }
  427. push @{$schema->{intents}}, {intent => "StatusIntent",
  428. slots => [ { name => 'Device', type => 'FHEM_Device' },
  429. { name => 'preposition', type => 'FHEM_preposition' },
  430. { name => 'Room', type => 'FHEM_Room' } ]};
  431. push @{$schema->{intents}}, {intent => "RoomAnswerIntent",
  432. slots => [ { name => 'preposition', type => 'FHEM_preposition' },
  433. { name => 'Room', type => 'FHEM_Room' } ]};
  434. push @{$schema->{intents}}, {intent => "RoomListIntent", };
  435. push @{$schema->{intents}}, {intent => "DeviceListIntent",
  436. slots => [ { name => 'article', type => 'FHEM_article' },
  437. { name => 'Room', type => 'FHEM_Room' } ]};
  438. push @{$schema->{intents}}, {intent => "AMAZON.CancelIntent", };
  439. push @{$schema->{intents}}, {intent => "AMAZON.StopIntent", };
  440. $samples .= "\nStatusIntent status";
  441. $samples .= "\nStatusIntent {Device} status";
  442. $samples .= "\nStatusIntent status von {Device}";
  443. $samples .= "\nStatusIntent wie ist der status von {Device}";
  444. $samples .= "\nStatusIntent wie ist der status {preposition} {Room}";
  445. $samples .= "\n";
  446. $samples .= "\nRoomAnswerIntent {preposition} {Room}";
  447. $samples .= "\n";
  448. $samples .= "\nRoomListIntent raumliste";
  449. $samples .= "\nDeviceListIntent geräteliste";
  450. $samples .= "\nDeviceListIntent geräteliste {Room}";
  451. $samples .= "\nDeviceListIntent geräteliste für {article} {Room}";
  452. $samples .= "\n";
  453. my $json = JSON->new;
  454. $json->pretty(1);
  455. my $t;
  456. foreach my $type ( sort keys %{$types} ) {
  457. $t .= "\n" if( $t );
  458. $t .= "$type\n ";
  459. $t .= join("\n ", @{$types->{$type}} );
  460. }
  461. return "Intent Schema:\n".
  462. "--------------\n".
  463. $json->utf8->encode( $schema ) ."\n".
  464. "Custom Slot Types:\n".
  465. "------------------\n".
  466. $t. "\n\n".
  467. "Sample Utterances:\n".
  468. "------------------\n".
  469. $samples.
  470. "\nreload 39_alexa\n".
  471. "get alexa interactionmodel\n";
  472. return undef;
  473. } elsif( $cmd eq 'skillId' ) {
  474. my $skillId = AttrVal($name, 'skillId', undef);
  475. return 'no skillId set' if( !$skillId );
  476. $skillId = alexa_decrypt( $skillId );
  477. return "skillId: $skillId";
  478. }
  479. return "Unknown argument $cmd, choose one of $list";
  480. }
  481. sub
  482. alexa_Parse($$;$)
  483. {
  484. my ($hash,$data,$peerhost) = @_;
  485. my $name = $hash->{NAME};
  486. }
  487. sub
  488. alexa_Read($)
  489. {
  490. my ($hash) = @_;
  491. my $name = $hash->{NAME};
  492. my $len;
  493. my $buf;
  494. $len = $hash->{CD}->recv($buf, 1024);
  495. if( !defined($len) || !$len ) {
  496. Log 1, "!!!!!!!!!!";
  497. return;
  498. }
  499. alexa_Parse($hash, $buf, $hash->{CD}->peerhost);
  500. }
  501. sub
  502. alexa_encrypt($)
  503. {
  504. my ($decoded) = @_;
  505. my $key = getUniqueId();
  506. my $encoded;
  507. return $decoded if( $decoded =~ /^crypt:(.*)/ );
  508. for my $char (split //, $decoded) {
  509. my $encode = chop($key);
  510. $encoded .= sprintf("%.2x",ord($char)^ord($encode));
  511. $key = $encode.$key;
  512. }
  513. return 'crypt:'. $encoded;
  514. }
  515. sub
  516. alexa_decrypt($)
  517. {
  518. my ($encoded) = @_;
  519. my $key = getUniqueId();
  520. my $decoded;
  521. $encoded = $1 if( $encoded =~ /^crypt:(.*)/ );
  522. for my $char (map { pack('C', hex($_)) } ($encoded =~ /(..)/g)) {
  523. my $decode = chop($key);
  524. $decoded .= chr(ord($char)^ord($decode));
  525. $key = $decode.$key;
  526. }
  527. return $decoded;
  528. }
  529. sub
  530. alexa_Attr($$$)
  531. {
  532. my ($cmd, $name, $attrName, $attrVal) = @_;
  533. my $orig = $attrVal;
  534. my $hash = $defs{$name};
  535. if( $attrName eq "disable" ) {
  536. } elsif( $attrName eq 'skillId' ) {
  537. if( $cmd eq "set" && $attrVal ) {
  538. if( $attrVal =~ /^crypt:/ ) {
  539. return;
  540. } elsif( $attrVal !~ /(^amzn1\.ask\.skill\.[0-9a-f\-]+)|(^amzn1\.echo-sdk-ams\.app\.[0-9a-f\-]+)/ ) {
  541. return "$attrVal is not a valid skill id";
  542. }
  543. $attrVal = alexa_encrypt($attrVal);
  544. if( $orig ne $attrVal ) {
  545. $attr{$name}{$attrName} = $attrVal;
  546. return "stored obfuscated skillId";
  547. }
  548. }
  549. }
  550. if( $cmd eq 'set' ) {
  551. } else {
  552. delete $attr{$name}{$attrName};
  553. RemoveInternalTimer($hash);
  554. InternalTimer(gettimeofday(), "alexa_AttrDefaults", $hash, 0);
  555. }
  556. return;
  557. }
  558. 1;
  559. =pod
  560. =item summary Module to control the FHEM/Alexa integration
  561. =item summary_DE Modul zur Konfiguration der FHEM/Alexa Integration
  562. =begin html
  563. <a name="alexa"></a>
  564. <h3>alexa</h3>
  565. <ul>
  566. Module to control the integration of Amazon Alexa devices with FHEM.<br><br>
  567. Notes:
  568. <ul>
  569. <li>JSON has to be installed on the FHEM host.</li>
  570. </ul>
  571. <a name="alexa_Set"></a>
  572. <b>Set</b>
  573. <ul>
  574. <li>reload [name]<br>
  575. Reloads the device <code>name</code> or all devices in alexa-fhem. Subsequently you have to start a device discovery
  576. for the home automation skill in the amazon alexa app.</li>
  577. </ul>
  578. <a name="alexa_Get"></a>
  579. <b>Get</b>
  580. <ul>
  581. <li>customSlotTypes<br>
  582. Instructs alexa-fhem to write the device specific Custom Slot Types for the Interaction Model
  583. configuration to the alexa-fhem console and if possible to the requesting fhem frontend.</li>
  584. <li>interactionModel<br>
  585. Get Intent Schema, non device specific Custom Slot Types and Sample Utterances for the Interaction Model
  586. configuration.</li>
  587. <li>skillId<br>
  588. shows the configured skillId.</li>
  589. </ul>
  590. <a name="alexa_Attr"></a>
  591. <b>Attr</b>
  592. <ul>
  593. <li>alexaName<br>
  594. The name to use for a device with alexa.</li>
  595. <li>alexaRoom<br>
  596. The room name to use for a device with alexa.</li>
  597. <li>articles<br>
  598. defaults to: der,die,das,den</li>
  599. <li>prepositions<br>
  600. defaults to: in,im,in der</li>
  601. <li>alexaMapping<br>
  602. maps spoken commands to intents for certain characteristics.</li>
  603. <li>alexaTypes<br>
  604. maps spoken device types to ServiceClasses. eg: attr alexa alexaTypes light:licht,lampe,lampen blind:rolladen,jalousie,rollo Outlet:steckdose TemperatureSensor:thermometer LockMechanism:schloss OccupancySensor: anwesenheit</li>
  605. <li>echoRooms<br>
  606. maps echo devices to default rooms.</li>
  607. <li>fhemIntents<br>
  608. maps spoken commands directed to fhem as a whole (i.e. not to specific devices) to events from the alexa device.</li>
  609. <li>alexaConfirmationLevel<br>
  610. </li>
  611. <li>alexaStatusLevel<br>
  612. </li>
  613. <li>skillId<br>
  614. skillId to use for automatic interaction model upload (not yet finished !!!)
  615. </li>
  616. Note: changes to attributes of the alexa device will automatically trigger a reconfiguration of
  617. alxea-fhem and there is no need to restart the service.
  618. </ul>
  619. </ul><br>
  620. =end html
  621. =cut