39_alexa.pm 19 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605
  1. # $Id: 39_alexa.pm 13436 2017-02-18 18:10:46Z justme1968 $
  2. package main;
  3. use strict;
  4. use warnings;
  5. use JSON;
  6. use Data::Dumper;
  7. sub
  8. alexa_Initialize($)
  9. {
  10. my ($hash) = @_;
  11. #$hash->{ReadFn} = "alexa_Read";
  12. $hash->{DefFn} = "alexa_Define";
  13. #$hash->{NOTIFYDEV} = "global";
  14. #$hash->{NotifyFn} = "alexa_Notify";
  15. $hash->{UndefFn} = "alexa_Undefine";
  16. $hash->{SetFn} = "alexa_Set";
  17. $hash->{GetFn} = "alexa_Get";
  18. $hash->{AttrFn} = "alexa_Attr";
  19. $hash->{AttrList} = "alexaMapping:textField-long alexaTypes:textField-long fhemIntents:textField-long ".
  20. "articles prepositions ".
  21. "alexaConfirmationLevel:2,1,0 alexaStatusLevel:2,1 ".
  22. $readingFnAttributes;
  23. }
  24. #####################################
  25. sub
  26. alexa_AttrDefaults($)
  27. {
  28. my ($hash) = @_;
  29. my $name = $hash->{NAME};
  30. if( !AttrVal( $name, 'alexaMapping', undef ) ) {
  31. CommandAttr(undef,"$name alexaMapping #Characteristic=<name>=<value>,...\n".
  32. "On=verb=schalte,valueOn=an;ein,valueOff=aus,valueToggle=um\n\n".
  33. "Brightness=verb=stelle,property=helligkeit,valuePrefix=auf,values=AMAZON.NUMBER,valueSuffix=prozent\n\n".
  34. "Hue=verb=stelle,valuePrefix=auf,values=rot:0;grün:128;blau:200\n".
  35. "Hue=verb=färbe,values=rot:0;grün:120;blau:220\n\n".
  36. "Saturation=verb=stelle,property=sättigung,valuePrefix=auf,values=AMAZON.NUMBER\n".
  37. "Saturation=verb=sättige,values=AMAZON.NUMBER\n\n".
  38. "TargetPosition=verb=mach,articles=den,values=auf:100;zu:0\n".
  39. "TargetPosition=verb=stelle,valuePrefix=auf,values=AMAZON.NUMBER,valueSuffix=prozent\n\n".
  40. "TargetTemperature=verb=stelle,valuePrefix=auf,values=AMAZON.NUMBER,valueSuffix=grad\n\n".
  41. "Volume:verb=stelle,valuePrefix=auf,values=AMAZON.NUMBER,valueSuffix=prozent\n\n".
  42. "#Weckzeit=verb=stelle,valuePrefix=auf;für,values=AMAZON.TIME,valueSuffix=uhr" );
  43. }
  44. if( !AttrVal( $name, 'alexaTypes', undef ) ) {
  45. CommandAttr(undef,"$name alexaTypes #Type=<alias>[,<alias2>[,...]]\n".
  46. "light=licht,lampen\n".
  47. "blind=rolladen,rolläden,jalousie,jalousien,rollo,rollos" );
  48. }
  49. if( !AttrVal( $name, 'fhemIntents', undef ) ) {
  50. CommandAttr(undef,"$name fhemIntents #IntentName=<sample utterance>\n".
  51. "gutenMorgen=guten morgen\n".
  52. "guteNacht=gute nacht" );
  53. }
  54. }
  55. sub
  56. alexa_Define($$)
  57. {
  58. my ($hash, $def) = @_;
  59. my @a = split("[ \t][ \t]*", $def);
  60. return "Usage: define <name> alexa" if(@a != 2);
  61. my $name = $a[0];
  62. $hash->{NAME} = $name;
  63. my $d = $modules{$hash->{TYPE}}{defptr};
  64. return "$hash->{TYPE} device already defined as $d->{NAME}." if( defined($d) && $name ne $d->{NAME} );
  65. $modules{$hash->{TYPE}}{defptr} = $hash;
  66. addToAttrList("$hash->{TYPE}Name");
  67. addToAttrList("$hash->{TYPE}Room");
  68. alexa_AttrDefaults($hash);
  69. $hash->{STATE} = 'active';
  70. return undef;
  71. }
  72. sub
  73. alexa_Notify($$)
  74. {
  75. my ($hash,$dev) = @_;
  76. return if($dev->{NAME} ne "global");
  77. return if(!grep(m/^INITIALIZED|REREADCFG$/, @{$dev->{CHANGED}}));
  78. return undef;
  79. }
  80. sub
  81. alexa_Undefine($$)
  82. {
  83. my ($hash, $arg) = @_;
  84. delete $modules{$hash->{TYPE}}{defptr};
  85. return undef;
  86. }
  87. sub
  88. alexa_Set($$@)
  89. {
  90. my ($hash, $name, $cmd, @args) = @_;
  91. my $list = "reload:noArg";
  92. if( $cmd eq 'reload' ) {
  93. $hash->{".triggerUsed"} = 1;
  94. if( @args ) {
  95. FW_directNotify($name, "reload $args[0]");
  96. } else {
  97. FW_directNotify($name, 'reload');
  98. }
  99. return undef;
  100. }
  101. return "Unknown argument $cmd, choose one of $list";
  102. }
  103. sub
  104. alexa_Get($$@)
  105. {
  106. my ($hash, $name, $cmd) = @_;
  107. my $list = "customSlotTypes:noArg interactionModel:noArg";
  108. if( lc($cmd) eq 'customslottypes' ) {
  109. if( $hash->{CL} ) {
  110. FW_directNotify($name, "customSlotTypes $hash->{CL}{NAME}");
  111. } else {
  112. FW_directNotify($name, 'customSlotTypes');
  113. }
  114. return undef;
  115. } elsif( lc($cmd) eq 'interactionmodel' ) {
  116. my %mappings;
  117. if( my $mappings = AttrVal( $name, 'alexaMapping', undef ) ) {
  118. foreach my $mapping ( split( / |\n/, $mappings ) ) {
  119. next if( !$mapping );
  120. next if( $mapping =~ /^#/ );
  121. my %characteristic;
  122. my ($characteristic, $remainder) = split( /:|=/, $mapping, 2 );
  123. if( $characteristic =~ m/([^.]+)\.([^.]+)/ ) {
  124. $characteristic = $1;
  125. $characteristic{device} = $2;
  126. }
  127. my @parts = split( /,/, $remainder );
  128. foreach my $part (@parts) {
  129. my @p = split( '=', $part );
  130. if( $p[1] =~ m/;/ ) {
  131. $p[1] =~ s/\+/ /g;
  132. my @values = split(';', $p[1]);
  133. my @values2 = grep {$_ ne ''} @values;
  134. $characteristic{$p[0]} = \@values2;
  135. if( scalar @values != scalar @values2 ) {
  136. $characteristic{"_$p[0]"} = \@values;
  137. $characteristic{$p[0]} = $values2[0] if( scalar @values2 == 1 );
  138. }
  139. } else {
  140. $p[1] =~ s/\+/ /g;
  141. $characteristic{$p[0]} = $p[1];
  142. }
  143. }
  144. $mappings{$characteristic} = [] if( !$mappings{$characteristic} );
  145. push @{$mappings{$characteristic}}, \%characteristic;
  146. }
  147. }
  148. #Log 1, Dumper \%mappings;
  149. my %types;
  150. if( my $entries = AttrVal( $name, 'alexaTypes', undef ) ) {
  151. sub append($$$) {
  152. my($a, $c, $v) = @_;
  153. if( !defined($a->{$c}) ) {
  154. $a->{$c} = {};
  155. }
  156. $a->{$c}{$v} = 1;
  157. }
  158. sub merge($$) {
  159. my ($a, $b) = @_;
  160. return $a if( !defined($b) );
  161. my @result = ();
  162. if( ref($b) eq 'ARRAY' ) {
  163. @result = sort keys %{{map {((split(':',$_,2))[0] => 1)} (@{$a}, @{$b})}};
  164. } else {
  165. push @{$a}, $b;
  166. return $a;
  167. }
  168. return \@result;
  169. }
  170. foreach my $entry ( split( / |\n/, $entries ) ) {
  171. next if( !$entry );
  172. next if( $entry =~ /^#/ );
  173. my ($type, $remainder) = split( /:|=/, $entry, 2 );
  174. $types{$type} = [];
  175. my @names = split( /,/, $remainder );
  176. foreach my $name (@names) {
  177. push @{$types{$type}}, $name;
  178. }
  179. }
  180. }
  181. Log 1, Dumper \%types;
  182. my $verbsOfIntent = {};
  183. my $intentsOfVerb = {};
  184. my $valuesOfIntent = {};
  185. my $intentsOfCharacteristic = {};
  186. my $characteristicsOfIntent = {};
  187. foreach my $characteristic ( keys %mappings ) {
  188. my $mappings = $mappings{$characteristic};
  189. $mappings = [$mappings] if( ref($mappings) ne 'ARRAY');
  190. my $i = 0;
  191. foreach my $mapping (@{$mappings}) {
  192. if( !$mapping->{verb} ) {
  193. Log3 $name, 2, "alexaMapping: no verb given for $characteristic characteristic";
  194. next;
  195. }
  196. $mapping->{property} = '' if( !$mapping->{property} );
  197. $mapping->{property} = [$mapping->{property}] if( ref($mapping->{property}) ne 'ARRAY' );
  198. foreach my $property (@{$mapping->{property}}) {
  199. my $intent = $characteristic;
  200. $intent = lcfirst($mapping->{valueSuffix}) if( !$property && $mapping->{valueSuffix} );
  201. $intent .= 'Intent';
  202. my $values = [];
  203. $values = merge( $values, $mapping->{values} );
  204. $values = merge( $values, $mapping->{valueOn} );
  205. $values = merge( $values, $mapping->{valueOff} );
  206. $values = merge( $values, $mapping->{valueToggle} );
  207. append($verbsOfIntent, $intent, $mapping->{verb} );
  208. append($intentsOfVerb, $mapping->{verb}, $intent );
  209. append($valuesOfIntent, $intent, join( ',', @{$values} ) );
  210. append($intentsOfCharacteristic, $characteristic, $intent );
  211. append($characteristicsOfIntent, $intent, $characteristic );
  212. }
  213. }
  214. }
  215. Log 1, Dumper $verbsOfIntent;
  216. Log 1, Dumper $intentsOfVerb;
  217. Log 1, Dumper $valuesOfIntent;
  218. Log 1, Dumper $intentsOfCharacteristic;
  219. Log 1, Dumper $characteristicsOfIntent;
  220. my $intents = {};
  221. my $schema = { intents => [] };
  222. my $types = {};
  223. $types->{FHEM_article} = [split( /,|;/, AttrVal( $name, 'articles', 'der,die,das,den' ) ) ];
  224. $types->{FHEM_preposition} = [split( /,|;/, AttrVal( $name, 'prepositions', 'in,im,in der' ) ) ];
  225. my $samples = '';
  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. my $values = [];
  236. $values = merge( $values, $mapping->{values} );
  237. $values = merge( $values, $mapping->{valueOn} );
  238. $values = merge( $values, $mapping->{valueOff} );
  239. $values = merge( $values, $mapping->{valueToggle} );
  240. $mapping->{property} = '' if( !$mapping->{property} );
  241. $mapping->{property} = [$mapping->{property}] if( ref($mapping->{property}) ne 'ARRAY' );
  242. foreach my $property (@{$mapping->{property}}) {
  243. my $nr = $i?chr(65+$i):'';
  244. $nr = '' if( $mapping->{valueSuffix} );
  245. #my $intent = $characteristic .'Intent'. $nr;
  246. my $intent = $characteristic;
  247. $intent = lcfirst($mapping->{valueSuffix}) if( !$property && $mapping->{valueSuffix} );
  248. $intent .= 'Intent';
  249. $intent .= $nr;
  250. next if( $intents->{$intent} );
  251. $intents->{$intent} = 1;
  252. my $slots = [];
  253. push @{$slots}, { name => 'article', type => 'FHEM_article' };
  254. push @{$slots}, { name => 'Device', type => 'FHEM_Device' } if( !$mapping->{device} );
  255. push @{$slots}, { name => 'preposition', type => 'FHEM_preposition' };
  256. push @{$slots}, { name => 'Room', type => 'FHEM_Room' };
  257. if( ref($mapping->{valuePrefix}) eq 'ARRAY' ) {
  258. push @{$slots}, { name => "${characteristic}_valuePrefix$nr", type => "${characteristic}_prefix$nr" };
  259. $types->{"${characteristic}_prefix$nr"} = $mapping->{valuePrefix};
  260. }
  261. my $slot_name = "${characteristic}_Value$nr";
  262. $slot_name = lcfirst($mapping->{valueSuffix})."_Value$nr" if( !$property && $mapping->{valueSuffix} );
  263. if( $mapping->{values} && $mapping->{values} =~ /^AMAZON/ ) {
  264. push @{$slots}, { name => $slot_name, type => $mapping->{values} };
  265. } else {
  266. push @{$slots}, { name => $slot_name, type => "${characteristic}_Value$nr" };
  267. $types->{$slot_name} = $values if( $values->[0] );
  268. }
  269. if( ref($mapping->{valueSuffix}) eq 'ARRAY' ) {
  270. push @{$slots}, { name => "${characteristic}_valueSuffix$nr", type => "${characteristic}_suffix$nr" };
  271. $types->{"${characteristic}_suffix"} = $mapping->{valueSuffix$nr};
  272. }
  273. if( ref($mapping->{articles}) eq 'ARRAY' ) {
  274. $types->{"${characteristic}_article$nr"} = $mapping->{articles};
  275. }
  276. $mapping->{verb} = [$mapping->{verb}] if( ref($mapping->{verb}) ne 'ARRAY' );
  277. foreach my $verb (@{$mapping->{verb}}) {
  278. $samples .= "\n" if( $samples );
  279. my @articles = ('','{article}');
  280. if( ref($mapping->{articles}) eq 'ARRAY' ) {
  281. $articles[1] = "{${characteristic}_article}";
  282. } elsif( $mapping->{articles} ) {
  283. @articles = ($mapping->{articles});
  284. }
  285. foreach my $article (@articles) {
  286. foreach my $room ('','{Room}') {
  287. my $line;
  288. $line .= "$intent $verb";
  289. $line .= " $property" if( $property );
  290. $line .= " $article" if( $article );
  291. $line .= $mapping->{device}?" $mapping->{device}":' {Device}';
  292. $line .= " {preposition} $room" if( $room );
  293. if( ref($mapping->{valuePrefix}) eq 'ARRAY' ) {
  294. $line .= " {${characteristic}_valuePrefix$nr}";
  295. } else {
  296. $line .= " $mapping->{valuePrefix}" if( $mapping->{valuePrefix} );
  297. }
  298. $line .= " {$slot_name}";
  299. if( ref($mapping->{_valueSuffix}) eq 'ARRAY' ) {
  300. $line .= "\n$line";
  301. }
  302. if( ref($mapping->{valueSuffix}) eq 'ARRAY' ) {
  303. $line .= " {${characteristic}_valueSuffix$nr}";
  304. } else {
  305. $line .= " $mapping->{valueSuffix}" if( $mapping->{valueSuffix} );
  306. }
  307. $samples .= "\n" if( $samples );
  308. $samples .= $line;
  309. }
  310. }
  311. }
  312. push @{$schema->{intents}}, {intent => $intent, slots => $slots};
  313. }
  314. ++$i;
  315. }
  316. $samples .= "\n";
  317. }
  318. if( my $entries = AttrVal( $name, 'fhemIntents', undef ) ) {
  319. my %intents;
  320. foreach my $entry ( split( /\n/, $entries ) ) {
  321. next if( !$entry );
  322. next if( $entry =~ /^#/ );
  323. my ($intent, $remainder) = split( /:|=/, $entry, 2 );
  324. my @parts = split( /,/, $remainder );
  325. my $utterance = $parts[$#parts];
  326. my $intent_name = "FHEM${intent}Intent";
  327. if( $intent =~ m/^(set|get|attr)\s/ ) {
  328. $intent_name = "FHEM${1}Intent";
  329. my $i = 1;
  330. while( defined($intents{$intent_name}) ) {
  331. $intent_name = "FHEM${1}Intent".chr(65+$i);
  332. ++$i;
  333. }
  334. } elsif( $intent =~ m/^{.*}$/ ) {
  335. $intent_name = 'FHEMperlCodeIntent';
  336. my $i = 1;
  337. while( defined($intents{$intent_name}) ) {
  338. if( $i < 26 ) {
  339. $intent_name = "FHEMperlCodeIntent".chr(65+$i);
  340. } else {
  341. $intent_name = "FHEMperlCodeIntent".chr(64+int($i/26)).chr(65+$i%26);
  342. }
  343. ++$i;
  344. }
  345. }
  346. $intent_name =~ s/ //g;
  347. $intents{$intent_name} = $intent;
  348. push @{$schema->{intents}}, {intent => $intent_name, };
  349. $samples .= "\n$intent_name $utterance";
  350. }
  351. $samples .= "\n";
  352. }
  353. push @{$schema->{intents}}, {intent => "StatusIntent",
  354. slots => [ { name => 'Device', type => 'FHEM_Device' },
  355. { name => 'preposition', type => 'FHEM_preposition' },
  356. { name => 'Room', type => 'FHEM_Room' } ]};
  357. push @{$schema->{intents}}, {intent => "RoomAnswerIntent",
  358. slots => [ { name => 'preposition', type => 'FHEM_preposition' },
  359. { name => 'Room', type => 'FHEM_Room' } ]};
  360. push @{$schema->{intents}}, {intent => "RoomListIntent", };
  361. push @{$schema->{intents}}, {intent => "DeviceListIntent",
  362. slots => [ { name => 'article', type => 'FHEM_article' },
  363. { name => 'Room', type => 'FHEM_Room' } ]};
  364. push @{$schema->{intents}}, {intent => "AMAZON.CancelIntent", };
  365. push @{$schema->{intents}}, {intent => "AMAZON.StopIntent", };
  366. $samples .= "\nStatusIntent status";
  367. $samples .= "\nStatusIntent {Device} status";
  368. $samples .= "\nStatusIntent status von {Device}";
  369. $samples .= "\nStatusIntent wie ist der status von {Device}";
  370. $samples .= "\nStatusIntent wie ist der status {preposition} {Room}";
  371. $samples .= "\n";
  372. $samples .= "\nRoomAnswerIntent {preposition} {Room}";
  373. $samples .= "\n";
  374. $samples .= "\nRoomListIntent raumliste";
  375. $samples .= "\nDeviceListIntent geräteliste";
  376. $samples .= "\nDeviceListIntent geräteliste {Room}";
  377. $samples .= "\nDeviceListIntent geräteliste für {article} {Room}";
  378. $samples .= "\n";
  379. my $json = JSON->new;
  380. $json->pretty(1);
  381. my $t;
  382. foreach my $type ( sort keys %{$types} ) {
  383. $t .= "\n" if( $t );
  384. $t .= "$type\n ";
  385. $t .= join("\n ", @{$types->{$type}} );
  386. }
  387. return "Intent Schema:\n".
  388. "--------------\n".
  389. $json->utf8->encode( $schema ) ."\n".
  390. "Custom Slot Types:\n".
  391. "------------------\n".
  392. $t. "\n\n".
  393. "Sample Utterances:\n".
  394. "------------------\n".
  395. $samples.
  396. "\nreload 39_alexa\n".
  397. "get alexa interactionmodel\n";
  398. return undef;
  399. }
  400. return "Unknown argument $cmd, choose one of $list";
  401. }
  402. sub
  403. alexa_Parse($$;$)
  404. {
  405. my ($hash,$data,$peerhost) = @_;
  406. my $name = $hash->{NAME};
  407. }
  408. sub
  409. alexa_Read($)
  410. {
  411. my ($hash) = @_;
  412. my $name = $hash->{NAME};
  413. my $len;
  414. my $buf;
  415. $len = $hash->{CD}->recv($buf, 1024);
  416. if( !defined($len) || !$len ) {
  417. Log 1, "!!!!!!!!!!";
  418. return;
  419. }
  420. alexa_Parse($hash, $buf, $hash->{CD}->peerhost);
  421. }
  422. sub
  423. alexa_Attr($$$)
  424. {
  425. my ($cmd, $name, $attrName, $attrVal) = @_;
  426. my $orig = $attrVal;
  427. my $hash = $defs{$name};
  428. if( $attrName eq "disable" ) {
  429. }
  430. if( $cmd eq 'set' ) {
  431. } else {
  432. delete $attr{$name}{$attrName};
  433. RemoveInternalTimer($hash);
  434. InternalTimer(gettimeofday(), "alexa_AttrDefaults", $hash, 0);
  435. }
  436. return;
  437. }
  438. 1;
  439. =pod
  440. =item summary Module to control the FHEM/Alexa integration
  441. =item summary_DE Modul zur Konfiguration der FHEM/Alexa Integration
  442. =begin html
  443. <a name="alexa"></a>
  444. <h3>alexa</h3>
  445. <ul>
  446. Module to control the integration of Amazon Alexa devices with FHEM.<br><br>
  447. Notes:
  448. <ul>
  449. <li>JSON has to be installed on the FHEM host.</li>
  450. </ul>
  451. <a name="alexa_Set"></a>
  452. <b>Set</b>
  453. <ul>
  454. <li>reload [name]<br>
  455. Reloads the device <it>name</it> or all devices in alexa-fhem. Subsequently you have to start a device discovery
  456. for the home automation skill in the amazon alexa app.</li>
  457. </ul>
  458. <a name="alexa_Get"></a>
  459. <b>Get</b>
  460. <ul>
  461. <li>customSlotTypes<br>
  462. Instructs alexa-fhem to write the device specific Custom Slot Types for the Interaction Model
  463. configuration to the alexa-fhem console and if possible to the requesting fhem frontend.</li>
  464. <li>interactionModel<br>
  465. Get Intent Schema, non device specific Custom Slot Types and Sample Utterances for the Interaction Model
  466. configuration.</li>
  467. </ul>
  468. <a name="alexa_Attr"></a>
  469. <b>Attr</b>
  470. <ul>
  471. <li>alexaName<br>
  472. The name to use for a device with alexa.</li>
  473. <li>alexaRoom<br>
  474. The room name to use for a device with alexa.</li>
  475. <li>articles<br>
  476. defaults to: der,die,das,den</li>
  477. <li>prepositions<br>
  478. defaults to: in,im,in der</li>
  479. <li>alexaMapping<br>
  480. maps spoken commands to intents for certain characteristics.</li>
  481. <li>alexaTypes<br>
  482. 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>
  483. <li>fhemIntents<br>
  484. maps spoken commands directed to fhem as a whole (i.e. not to specific devices) to events from the alexa device.</li>
  485. <li>alexaConfirmationLevel<br>
  486. </li>
  487. <li>alexaStatusLevel<br>
  488. </li>
  489. Note: changes to attributes of the alexa device will automatically trigger a reconfiguration of
  490. alxea-fhem and there is no need to restart the service.
  491. </ul>
  492. </ul><br>
  493. =end html
  494. =cut