00_Neuron.pm 34 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901902903904905906907908909910911912913914915916917918919920921922923924925926927928929930931932933934935936937938939940941942943944945946947948949950951952953954955956957958959960961962963964965966967968969970971972973974975976977978979980981982983984985986987988989990991992993
  1. ##############################################
  2. # $Id: 00_Neuron.pm 16852 2018-06-11 22:00:38Z klausw $
  3. # todo:
  4. # websocket automatischer restart -> fertsch über devio
  5. # DevIo_IsOpen() anstelle con hash helper websocket
  6. # $hash->{DeviceName} global verwenden und devio nutzen
  7. # ein httprequest? -> Neuron_GetVal in Neuron_SetVal integriert
  8. # NotifyFn sets/gets aus Readings -> fertsch
  9. # (polling auch mit rein?) -> nein
  10. # set:
  11. # websocket open/close -> fertsch
  12. # otype-port on/off slider -> fertsch
  13. # (wenn $hash{FD} dann über websocket?) -> fertsch
  14. # type-port -> conf (frei setzbar machen) -> fertsch (postjson)
  15. #
  16. # bug?: wenn man einen value setzt, dann wird im response der alte zurückgeschickt
  17. #
  18. #"alias": "al_Versuch1"
  19. #{"glob_dev_id": 1, "dev": "input", "circuit": "1_01", "value": 0, "mode": "Simple", "counter_modes": ["Enabled", "Disabled"], "modes": ["Simple", "DirectSwitch"], "debounce": 50, "counter": 0, "counter_mode": "Enabled"},
  20. #{"glob_dev_id": 1, "dev": "relay", "circuit": "1_01", "value": 0, "mode": "Simple", "modes": ["Simple", "PWM"], "pending": false, "relay_type": "digital"},
  21. #{"glob_dev_id": 1, "dev": "relay", "circuit": "2_05", "value": 0, "mode": "Simple", "modes": ["Simple"], "pending": false, "relay_type": "physical"},
  22. #{"glob_dev_id": 1, "dev": "ai", "circuit": "1_01", "value": 0.0104..09, "unit": "V", "mode": "Voltage", "range_modes": ["10.0"], "modes": ["Voltage", "Current"], "range": "10.0"},
  23. #{"glob_dev_id": 1, "dev": "ai", "circuit": "2_01", "value": -0.001..28, "unit": "V", "mode": "Voltage", "range_modes": ["0.0", "2.5", "10.0"], "modes": ["Voltage", "Current", "Resistance"], "range": "10.0"},
  24. #{"glob_dev_id": 1, "dev": "ao", "circuit": "1_01", "value": 0.0, "unit": "V", "mode": "Voltage", "modes": ["Voltage", "Current", "Resistance"]},
  25. #{"glob_dev_id": 1, "dev": "ao", "circuit": "2_01", "value": 0.0, "unit": "V", "mode": "Voltage", "modes": ["Voltage"]},
  26. #{"glob_dev_id": 1, "dev": "led", "circuit": "1_01", "value": 0},
  27. #{"glob_dev_id": 1, "dev": "wd", "circuit": "1_01", "value": 0, "timeout": 5000, "was_wd_reset": 0, "nv_save": 0},
  28. #{"glob_dev_id": 1, "dev": "neuron","circuit": "1", "ver2": "1.0", "sn": 31, "model": "M503", "board_count": 2},
  29. #{"glob_dev_id": 1, "dev": "uart", "circuit": "1_01", "conf_value": 14, "stopb_modes": ["One", "Two"], "stopb_mode": "One", "speed_modes": ["2400bps", "4800bps", "9600bps", "19200bps", "38400bps", "57600bps", "115200bps"], "parity_modes": ["None", "Odd", "Even"], "parity_mode": "None", "speed_mode": "19200bps"}]#
  30. #{"vis": "0", "dev": "temp", "circuit": "2620531402000075", "typ": "DS2438", "lost": false, "temp": "24.25", "interval": 15, "vad": "2.52", "humidity": 50.196646084329984, "vdd": "5.34", "time": 1527144341.185264}
  31. #
  32. package main;
  33. use strict;
  34. use warnings;
  35. require "HttpUtils.pm";
  36. my @clients = qw(
  37. NeuronPin
  38. );
  39. my %opcode = ( # Opcode interpretation of the ws "Payload data
  40. 'continuation' => 0x00,
  41. 'text' => 0x01,
  42. 'binary' => 0x02,
  43. 'close' => 0x08,
  44. 'ping' => 0x09,
  45. 'pong' => 0x0A
  46. );
  47. my %setsP = (
  48. 'off' => 0,
  49. 'on' => 1,
  50. );
  51. #my %rsetsP = reverse %setsP;
  52. sub Neuron_Initialize(@) {
  53. my ($hash) = @_;
  54. eval "use JSON;";
  55. return "please install JSON::XS" if($@);
  56. eval "use Digest::SHA qw(sha1_hex);";
  57. return "please install Digest::SHA" if($@);
  58. # Provider
  59. $hash->{Clients} = join (':',@clients);
  60. $hash->{MatchList} = { "1:NeuronPin" => ".*" };
  61. $hash->{ReadFn} = "Neuron_Read";
  62. $hash->{ReadyFn} = "Neuron_Ready";
  63. $hash->{WriteFn} = "Neuron_Test";
  64. $hash->{DefFn} = 'Neuron_Define';
  65. $hash->{UndefFn} = 'Neuron_Undef';
  66. $hash->{ShutdownFn} = 'Neuron_Undef';
  67. $hash->{SetFn} = 'Neuron_Set';
  68. $hash->{GetFn} = 'Neuron_Get';
  69. $hash->{AttrFn} = 'Neuron_Attr';
  70. $hash->{NotifyFn} = 'Neuron_Notify';
  71. $hash->{AttrList} = "connection:websockets,polling poll_interval "
  72. ."wsFilter:multiple-strict,ai,ao,input,led,relay,wd "
  73. ."logicalDev:multiple-strict,ai,ao,input,led,relay,wd,temp "
  74. ."$readingFnAttributes";
  75. return undef;
  76. }
  77. sub Neuron_Define($$) {
  78. my ($hash, $def) = @_;
  79. my @parts=split("[ \t][ \t]*", $def);
  80. return "Usage: define <name> Neuron <hostname|ip>[:<tcp-portnr>]" unless defined $parts[2];
  81. $hash->{NOTIFYDEV} = "global";
  82. my ($address, $port) = split(/:/, $parts[2]);
  83. $port = "80" unless defined $port;
  84. $hash->{HOST} = $address;
  85. $hash->{PORT} = $port;
  86. $hash->{DeviceName} = $address.":".$port;
  87. $hash->{STATE} = "defined";
  88. return undef;
  89. }
  90. sub Neuron_Undef(@){
  91. my $hash = shift;
  92. Neuron_Close($hash);
  93. RemoveInternalTimer($hash);
  94. return undef;
  95. }
  96. sub Neuron_Set(@) {
  97. my ($hash, $name, $cmd, @args) = @_;
  98. my $sets = $hash->{HELPER}{SETS};
  99. if (index($hash->{HELPER}{SETS}, $cmd) != -1) { # dynamisch erzeugte outputs
  100. my ($dev, $circuit) = (split '_', $cmd, 2);
  101. my $value = (looks_like_number($args[0]) ? $args[0] : $setsP{$args[0]});
  102. if ($hash->{HELPER}{wsKey} && DevIo_IsOpen($hash)) {
  103. my $string = Neuron_wsEncode('{"cmd":"set", "dev":"'.$dev.'", "circuit":"'.$circuit.'", "value":"'.$value.'"}');
  104. Neuron_Write($hash,$string);
  105. } else {
  106. Neuron_HTTP($hash,$dev,$circuit,$value);
  107. }
  108. } elsif ($cmd eq "postjson") {
  109. my ($dev, $circuit , $value, $state) = @args;
  110. $value = '{"'.$value.'":"'.$state.'"}' if (defined($state));
  111. $hash->{HELPER}{CLSET} = $hash->{CL};
  112. Neuron_HTTP($hash,$dev,$circuit,$value);
  113. } elsif ($cmd eq "websocket") {
  114. if ($args[0] && $args[0] eq 'open') {
  115. Neuron_Open($hash);
  116. } else {
  117. Neuron_Close($hash);
  118. }
  119. } elsif ($cmd eq "clearreadings") {
  120. fhem("deletereading $hash->{NAME} .*", 1);
  121. } elsif ($cmd eq "testdispatch") {
  122. Neuron_ParseWsResponse($hash, '{"dev":"temp","time":1527316294.23915,"temp":"23.4375","vis":"0.0002441","circuit":"2620531402000075","vad":"2.58","interval":15,"typ":"DS2438","humidity":51.9139754019274,"lost":false,"vdd":"5.34"}');
  123. } else {
  124. return "Unknown argument $cmd, choose one of testdispatch clearreadings:noArg websocket:open,close " . ($hash->{HELPER}{SETS} ? $hash->{HELPER}{SETS} : '');
  125. }
  126. return undef;
  127. }
  128. sub Neuron_Get(@) {
  129. my ($hash, $name, $cmd, @args) = @_;
  130. if ($cmd eq "all") {
  131. Neuron_GetAll($hash);
  132. } elsif ($cmd eq "updt_sets_gets") {
  133. Neuron_ReadingstoSets($hash);
  134. } elsif ($cmd eq "value") {
  135. if (index($hash->{HELPER}{GETS}, $args[0]) != -1) {
  136. my ($dev, $circuit) = (split '_', $args[0], 2);
  137. $hash->{HELPER}{CLVAL} = $hash->{CL};
  138. Neuron_HTTP($hash, $dev, $circuit);
  139. } else {
  140. return "Unknown Port $args[0], choose one of ".$hash->{HELPER}{GETS};
  141. }
  142. } elsif ($cmd eq "conf") {
  143. if (index($hash->{HELPER}{GETS}, $args[0]) != -1) {
  144. my ($dev, $circuit) = (split '_', $args[0], 2);
  145. $hash->{HELPER}{CLCONF} = $hash->{CL};
  146. Neuron_HTTP($hash, $dev, $circuit);
  147. } else {
  148. return "Unknown Port $args[0], choose one of ".$hash->{HELPER}{GETS};
  149. }
  150. } else {
  151. my @gets = ('updt_sets_gets:noArg','all:noArg');
  152. if ($hash->{HELPER}{GETS}) {
  153. push(@gets, 'value:' . $hash->{HELPER}{GETS});
  154. push(@gets, 'conf:' . $hash->{HELPER}{GETS});
  155. }
  156. return "Unknown argument $cmd, choose one of " . join(" ", @gets);
  157. }
  158. return undef;
  159. }
  160. sub Neuron_Attr(@) {
  161. my ($cmd, $name, $attr, $val) = @_;
  162. # $cmd - Vorgangsart - kann die Werte "del" (löschen) oder "set" (setzen) annehmen
  163. # $name - Gerätename
  164. # $attr/$val sind Attribut-Name und Attribut-Wert
  165. my $hash = $defs{$name};
  166. if ($attr && $attr eq 'connection') {
  167. if ($val && $val eq 'websockets' && $cmd eq 'set') {
  168. Log3 $hash, 5, "Neuron_Attr oeffne WS";
  169. Neuron_Open($hash);
  170. } else {
  171. Log3 $hash, 5, "Neuron_Attr schließe WS";
  172. Neuron_Close($hash);
  173. }
  174. } elsif ($attr eq 'poll_interval') {
  175. if ( defined($val) ) {
  176. if ( looks_like_number($val) && $val > 0) {
  177. RemoveInternalTimer($hash);
  178. if (AttrVal($hash->{NAME}, 'connection', 'polling') eq 'polling') {
  179. InternalTimer(1, 'Neuron_Poll', $hash, 0);
  180. } else {
  181. return '$hash->{NAME}: poll intervall can\'t defined together with websocket connection';
  182. }
  183. } else {
  184. return "$hash->{NAME}: Wrong poll intervall defined. poll_interval must be a number > 0";
  185. }
  186. } else {
  187. RemoveInternalTimer($hash);
  188. }
  189. } elsif ($attr eq 'wsFilter') {
  190. Neuron_wsSetFilter($hash,$val);
  191. }
  192. return undef;
  193. }
  194. sub Neuron_Poll($) {
  195. my ($hash) = @_;
  196. my $name = $hash->{NAME};
  197. if (AttrVal($hash->{NAME}, 'connection', 'polling') eq 'polling') {
  198. # Read all values
  199. Neuron_GetAll($hash);
  200. my $pollInterval = AttrVal($hash->{NAME}, 'poll_interval', 0);
  201. InternalTimer(gettimeofday() + ($pollInterval * 60), 'Neuron_Poll', $hash, 0) if ($pollInterval > 0);
  202. }
  203. }
  204. sub Neuron_Notify(@) {
  205. my ($hash, $nhash) = @_;
  206. my $name = $hash->{NAME};
  207. return '' if(IsDisabled($name));
  208. my $events = deviceEvents($nhash, 1);
  209. if($nhash->{NAME} eq "global" && grep(m/^INITIALIZED|REREADCFG$/, @{$events}))
  210. {
  211. Neuron_ReadingstoSets($hash);
  212. Neuron_forall_clients($hash,\&Neuron_Init_Client,undef);
  213. }
  214. return undef;
  215. }
  216. sub Neuron_forall_clients($$$) {
  217. my ($hash,$fn,$args) = @_;
  218. foreach my $d ( sort keys %main::defs ) {
  219. if ( defined( $main::defs{$d} )
  220. && defined( $main::defs{$d}{IODev} )
  221. && $main::defs{$d}{IODev} == $hash ) {
  222. &$fn($main::defs{$d},$args);
  223. }
  224. }
  225. return undef;
  226. }
  227. sub Neuron_Init_Client($@) {
  228. my ($hash,$args) = @_;
  229. if (!defined $args and defined $hash->{DEF}) {
  230. my @a = split("[ \t][ \t]*", $hash->{DEF});
  231. $args = \@a;
  232. }
  233. my $name = $hash->{NAME};
  234. Log3 $name,5,"im init client fuer $name ";
  235. my $ret = CallFn($name,"InitFn",$hash,$args);
  236. if ($ret) {
  237. Log3 $name,2,"error initializing '".$hash->{NAME}."': ".$ret;
  238. }
  239. }
  240. ###########################################################################################################
  241. sub Neuron_Test($$) {
  242. my ( $hash, @args) = @_;
  243. my ($dev, $circuit , $value, $state) = @args;
  244. Log3($hash, 4,"$hash->{TYPE} ($hash->{NAME}) from logical dev: @args");
  245. # if ($hash->{HELPER}{WESOCKETS} && looks_like_number($value)) {
  246. if (looks_like_number($value) && $hash->{HELPER}{wsKey} && DevIo_IsOpen($hash)) {
  247. #my $string = Neuron_wsEncode('{"cmd":"set", "dev":"'.$dev.'", "circuit":"'.$circuit.'", "value":"'.$value.'"}');
  248. my $string = '{"cmd":"set", "dev":"'.$dev.'", "circuit":"'.$circuit.'", "value":"'.$value.'"}';
  249. Log3($hash, 4,"$hash->{TYPE} ($hash->{NAME}) from logical dev to Websocket: $string");
  250. Neuron_Write($hash,Neuron_wsEncode($string));
  251. } else {
  252. if (defined($state)) {
  253. if ($value eq 'debounce' || $value eq 'counter' || $value eq 'pwm_duty' || $value eq 'pwm_freq') { #debounce Werte dürfen nicht in Hochkommas sein
  254. $value = '{"'.$value.'":'.$state.'}';
  255. } elsif ($value eq 'counter_mode') {
  256. $value = '{"'.$value.'":'.lc($state).'}';
  257. }else {
  258. $value = '{"'.$value.'":"'.$state.'"}';
  259. }
  260. }
  261. Log3($hash, 4,"$hash->{TYPE} ($hash->{NAME}) from logical dev to HTTP: $dev,$circuit".($value ? ",$value" : '').($state ? ",$state" : ''));
  262. Neuron_HTTP($hash,$dev,$circuit,$value);
  263. }
  264. return undef;
  265. }
  266. #####################################
  267. # http fuctions
  268. #####################################
  269. sub Neuron_HTTP(@){
  270. my ($hash,$dev,$circuit,$data) = @_;
  271. #my $url="http://$hash->{HOST}:$hash->{PORT}/json/$dev/$circuit";
  272. my $url="http://$hash->{HOST}:$hash->{PORT}/".(defined($data) ? "json" : "rest")."/$dev/$circuit";
  273. if (defined($data) && index($data, ':') == -1) {
  274. unless ($dev eq 'ao') {
  275. $data = '{"value":"'.$data.'"}';
  276. } else {
  277. $data = '{"value":'.$data.'}'; # Sonderlösung, da der Analoge Ausgang den Wert nur ohne Hochkommas akzeptiert
  278. }
  279. }
  280. Log3($hash, 3,"$hash->{TYPE} ($hash->{NAME}): sending ".($data ? "POST ($data)" : "GET")." request to url $url");
  281. my $param= {
  282. url => $url,
  283. hash => $hash,
  284. timeout => 30,
  285. method => ($data ? "POST" : "GET"),
  286. data => ($data ? $data : ''),
  287. header => "User-Agent: fhem\r\nAccept: application/json",
  288. parser => \&Neuron_ParseSingle,
  289. callback => \&Neuron_callback
  290. };
  291. HttpUtils_NonblockingGet($param);
  292. return undef;
  293. }
  294. sub Neuron_GetAll(@){
  295. my ($hash) = @_;
  296. #my $url="http://$hash->{HOST}:$hash->{PORT}/json/all";
  297. my $url="http://$hash->{HOST}:$hash->{PORT}/rest/all";
  298. Log3($hash, 4,"$hash->{TYPE} ($hash->{NAME}): sending GET all request with url $url");
  299. my $param= {
  300. url => $url,
  301. hash => $hash,
  302. timeout => 30,
  303. method => "GET",
  304. header => "User-Agent: fhem\r\nAccept: application/json",
  305. parser => \&Neuron_ParseAll,
  306. callback => \&Neuron_callback
  307. };
  308. HttpUtils_NonblockingGet($param);
  309. return undef;
  310. }
  311. #####################################
  312. # functions to handle responses
  313. #####################################
  314. sub Neuron_callback(@) {
  315. my ($param, $err, $data) = @_;
  316. my ($hash) = $param->{hash};
  317. if($err){
  318. Log3($hash, 3, "$hash->{TYPE} ($hash->{NAME}) received callback with error:\n$err");
  319. } elsif($data){
  320. Log3($hash, 5, "$hash->{TYPE} ($hash->{NAME}) received callback with:\n$data");
  321. my $parser = $param->{parser};
  322. &$parser($hash, $data);
  323. asyncOutput($hash->{HELPER}{CLCONF}, $data) if $hash->{HELPER}{CLCONF};
  324. delete $hash->{HELPER}{CLCONF};
  325. } else {
  326. Log3($hash, 2, "$hash->{TYPE} ($hash->{NAME}) received callback without Data and Error String!!!");
  327. }
  328. return undef;
  329. }
  330. sub Neuron_ParseSingle(@){
  331. my ($hash, $data)=@_;
  332. my $result;
  333. Log3($hash, 4, "$hash->{TYPE} ($hash->{NAME}) parse data:\n".$data);
  334. eval {
  335. $result = JSON->new->utf8(1)->decode($data);
  336. #Log3 ($hash, 1, "$hash->{TYPE} ($hash->{NAME}) single result->status=".ref($result));
  337. };
  338. if ($@) {
  339. Log3 ($hash, 3, "$hash->{TYPE} ($hash->{NAME}) error decoding response: $@");
  340. readingsSingleUpdate($hash,"state","JSON decode error",1);
  341. } elsif ( $result->{status} && $result->{status} eq 'fail' ) {
  342. readingsSingleUpdate($hash,"state",'fail',1);
  343. asyncOutput($hash->{HELPER}{CLSET}, "set response fail") if $hash->{HELPER}{CLSET};
  344. Log3 ($hash, 2, "$hash->{TYPE} ($hash->{NAME}) http response fail with: ".$result->{data});
  345. } else {
  346. readingsSingleUpdate($hash,"state",'success',1);
  347. my %addvals = (STATUS => $result->{status}) if exists $result->{status};
  348. my $data;
  349. if (exists $result->{data}) {
  350. if (exists $result->{data}{result}) {
  351. $result = $result->{data}{result};
  352. } else {
  353. $result = $result->{data};
  354. }
  355. } elsif (exists $result->{result}) {
  356. $result = $result->{result};
  357. } else {
  358. $result = $result;
  359. }
  360. if (ref $result eq 'HASH') {
  361. readingsSingleUpdate($hash,$result->{dev}."_".$result->{circuit},$result->{value},1);
  362. asyncOutput($hash->{HELPER}{CLVAL}, $result->{value}) if $hash->{HELPER}{CLVAL};
  363. delete $hash->{HELPER}{CLVAL};
  364. Dispatch($hash, $result, (%addvals ? \%addvals : undef)) if index(AttrVal($hash->{NAME}, 'logicalDev', 'relay,input,led,ao,temp') , $result->{dev}) != -1;
  365. } else {
  366. Log3 ($hash, 3, "$hash->{TYPE} ($hash->{NAME}) http response not JSON: ".$result);
  367. }
  368. }
  369. delete $hash->{HELPER}{CLSET};
  370. return $result;
  371. }
  372. sub Neuron_ParseAll(@){
  373. my ($hash, $data)=@_;
  374. my $result;
  375. Log3($hash, 5, "$hash->{TYPE} ($hash->{NAME}) parse data:\n$data");
  376. eval {
  377. $result = JSON->new->utf8(1)->decode($data);
  378. };
  379. if ($@) {
  380. Log3 ($hash, 3, "$hash->{TYPE} ($hash->{NAME}) error decoding response: $@");
  381. readingsSingleUpdate($hash,"state","JSON decode error",1);
  382. } else {
  383. ###################################################################
  384. eval {
  385. #Log3 ($hash, 1, "$hash->{TYPE} ($hash->{NAME}) result->status=".ref($result));
  386. my %addvals = (STATUS => $result->{status}) if ref $result eq 'HASH';
  387. my ($subdevs) = (ref $result eq 'HASH' ? $result->{data} : $result);
  388. readingsBeginUpdate($hash);
  389. my $i = 1;
  390. foreach (@{$subdevs}){
  391. (my $subdev)=$_;
  392. if (defined $subdev->{model} && defined $subdev->{glob_dev_id}) {
  393. foreach my $intrnl (keys %{$subdev}) {
  394. next if $intrnl eq "glob_dev_id";
  395. $hash->{uc($intrnl)} = $subdev->{$intrnl};
  396. }
  397. } elsif (defined $subdev->{model}) {
  398. foreach my $intrnl (keys %{$subdev}) {
  399. next if $intrnl eq "glob_dev_id";
  400. $hash->{'ext'.$i.'_'.uc($intrnl)} = $subdev->{$intrnl};
  401. }
  402. $i++;
  403. } else {
  404. my $value = $subdev->{temp}; # Temperaturwert nehmen (!wire Geräte haben kein value?)
  405. $value = $subdev->{value};
  406. #$value = $rsetsP{$value} if ($subdev->{dev} eq 'input' || $subdev->{dev} eq 'relay' || $subdev->{dev} eq 'led'); # on,off anstelle von 1,0
  407. readingsBulkUpdateIfChanged($hash,$subdev->{dev}."_".$subdev->{circuit},$value) if defined($value);
  408. Dispatch($hash, $subdev, (%addvals ? \%addvals : undef)) if index(AttrVal($hash->{NAME}, 'logicalDev', 'relay,input,led,ao'), $subdev->{dev}) != -1;
  409. delete $subdev->{value};
  410. readingsBulkUpdateIfChanged($hash,".".$subdev->{dev}."_".$subdev->{circuit},encode_json $subdev,0);
  411. Log3 ($hash, 4, "$hash->{TYPE} ($hash->{NAME}) ".$subdev->{dev}."_".$subdev->{circuit} .": ".encode_json $subdev);
  412. }
  413. }
  414. readingsBulkUpdate($hash,"state",$result->{status}) if ref $result eq 'HASH';
  415. readingsEndUpdate($hash,1);
  416. Neuron_ReadingstoSets($hash);
  417. #################################################################
  418. };
  419. if ($@) {
  420. Log3 ($hash, 1, "$hash->{TYPE} ($hash->{NAME}) ParseAll Error: $@");
  421. readingsSingleUpdate($hash,"state","JSON decode error",1);
  422. }
  423. }
  424. return $data;
  425. }
  426. sub Neuron_ParseWsResponse($$){
  427. my ($hash, $data)=@_;
  428. my $name = $hash->{NAME};
  429. my $result;
  430. eval {
  431. $result = JSON->new->utf8(1)->decode($data);
  432. };
  433. if ($@) {
  434. Log3 ($hash, 3, "$hash->{TYPE} ($hash->{NAME}): error decoding response $@\nData:\n$data");
  435. } else {
  436. #my ($subdevs) = $result->{data};
  437. readingsBeginUpdate($hash);
  438. if (ref $result eq 'ARRAY') { #[{"circuit": "1_01", "value": 0, ...}]
  439. foreach (@{$result}){
  440. Neuron_DecodeWsJSON($hash,$_);
  441. }
  442. } elsif (ref $result eq 'HASH') { #{"circuit": "1_01", "value": 0, ...}
  443. Neuron_DecodeWsJSON($hash,$result);
  444. }
  445. readingsEndUpdate($hash,1);
  446. }
  447. return undef
  448. }
  449. sub Neuron_DecodeWsJSON($$){
  450. my ($hash, $dev)=@_;
  451. eval {
  452. readingsBulkUpdate($hash,$dev->{dev}."_".$dev->{circuit},$dev->{value});
  453. Dispatch($hash, $dev, undef) if index(AttrVal($hash->{NAME}, 'logicalDev', 'relay,input,led,ao') , $dev->{dev}) != -1;
  454. };
  455. if ($@) {
  456. Log3 ($hash, 3, "$hash->{TYPE} ($hash->{NAME}): error decoding JSON $@\nData:\n$dev");
  457. }
  458. return undef
  459. }
  460. sub Neuron_ReadingstoSets($){
  461. my ($hash)=@_;
  462. my $sets;
  463. my @gets;
  464. foreach (keys %{$hash->{READINGS}}) {
  465. if (substr($_,0,3) eq 'led') {
  466. $sets .= " " if $sets;
  467. $sets .= $_ .":off,on";
  468. } elsif ( substr($_,0,5) eq 'relay') {
  469. $sets .= " " if $sets;
  470. $sets .= $_ .":off,on";
  471. } elsif (substr($_,0,2) eq 'ao') {
  472. $sets .= " " if $sets;
  473. $sets .= $_ .":slider,0,0.1,10";
  474. }
  475. unless (substr($_,0,1) eq '.') {
  476. push (@gets,$_);
  477. }
  478. @gets = sort @gets;
  479. }
  480. $hash->{HELPER}{SETS} = $sets;
  481. $hash->{HELPER}{GETS} = join (',',@gets);
  482. }
  483. #######################################
  484. # Socket Fuctions
  485. #######################################
  486. sub Neuron_Open($) {
  487. my $hash = shift;
  488. my $name = $hash->{NAME};
  489. my $host = $hash->{HOST};
  490. my $port = $hash->{PORT};
  491. my $timeout = 0.1;
  492. Log3 $name, 4, "$hash->{TYPE} ($name) - Establishing socket connection";
  493. ######### 1
  494. DevIo_CloseDev($hash) if(DevIo_IsOpen($hash));
  495. DevIo_OpenDev($hash, 0, "Neuron_wsHandshake");
  496. #DevIo_OpenDev($hash, 0, "Neuron_wsHandshake", "Neuron_Callback");
  497. ######### 2
  498. # return if( $hash->{CD} );
  499. # my $socket = new IO::Socket::INET ( PeerHost => $host,
  500. # PeerPort => $port,
  501. # Proto => 'tcp',
  502. # Timeout => $timeout
  503. # )
  504. # or return Log3 $name, 4, "$hash->{TYPE} ($name) Couldn't connect to $host:$port"; # open Socket
  505. # $hash->{FD} = $socket->fileno();
  506. # $hash->{CD} = $socket; # sysread / close won't work on fileno
  507. # $selectlist{$name} = $hash;
  508. #########
  509. # Log3 $name, 4, "$hash->{TYPE} ($name) - Socket Connected";
  510. # readingsSingleUpdate($hash,'state','ws_opened',1);
  511. # Neuron_wsHandshake($hash);
  512. }
  513. sub Neuron_Ready($) {
  514. my ($hash) = @_;
  515. return DevIo_OpenDev($hash, 1, "Neuron_wsHandshake") if ( $hash->{STATE} eq "disconnected" );
  516. }
  517. sub Neuron_Close($) {
  518. my $hash = shift;
  519. my $name = $hash->{NAME};
  520. delete $hash->{HELPER}{WESOCKETS};
  521. delete $hash->{HELPER}{wsKey};
  522. ######### 1
  523. DevIo_CloseDev($hash);
  524. ######### 2
  525. # return if( !$hash->{CD} );
  526. # close($hash->{CD}) if($hash->{CD});
  527. # delete($hash->{FD});
  528. # delete($hash->{CD});
  529. # delete($selectlist{$name});
  530. #########
  531. # readingsSingleUpdate($hash,'state','ws_disconnected',1);
  532. # Log3 $name, 4, "$hash->{TYPE} ($name) - Socket Disconnected";
  533. }
  534. sub Neuron_Write($@) {
  535. my ($hash,$string) = @_;
  536. my $name = $hash->{NAME};
  537. Log3 $name, 4, "$hash->{TYPE} ($name) - WriteFn called:\n$string";
  538. ######### 1
  539. DevIo_SimpleWrite($hash, $string, 0);
  540. ######### 2
  541. # return Log3 $name, 4, "$hash->{TYPE} ($name) - socket not connected" unless($hash->{CD});
  542. # syswrite($hash->{CD}, $string);
  543. #########
  544. return undef;
  545. }
  546. sub Neuron_Read($) {
  547. my $hash = shift;
  548. my $name = $hash->{NAME};
  549. my $buf;
  550. Log3 $name, 5, "$hash->{TYPE} ($name) - ReadFn started";
  551. ########### 1
  552. $buf = DevIo_SimpleRead($hash);
  553. ########### 2
  554. # my $len = sysread($hash->{CD},$buf,10240);
  555. # if( !defined($len) or !$len ) {
  556. # Neuron_Close($hash);
  557. # return;
  558. # }
  559. ###########
  560. return Log3 $name, 3, "$hash->{TYPE} ($name) - no data received"
  561. unless( defined $buf);
  562. if ($hash->{HELPER}{WESOCKETS}) {
  563. # Fehlerhafte Botschaftsteile abschneiden?
  564. #$buf =~ /(.{2,4}\[\{.*"glob_dev_id": .+\}\])/;
  565. #$buf = $1;
  566. Neuron_wsDecode($hash,$buf);
  567. } elsif( $buf =~ /HTTP\/1.1 101 Switching Protocols/ ) {
  568. Log3 $name, 4, "$hash->{TYPE} ($name) - received HTTP data string, start response processing:\n$buf";
  569. Neuron_wsCheckHandshake($hash,$buf);
  570. } else {
  571. Log3 $name, 1, "$hash->{TYPE} ($name) - corrupted data found:\n$buf";
  572. }
  573. }
  574. sub Neuron_Callback($) {
  575. my ($hash, $error) = @_;
  576. my $name = $hash->{NAME};
  577. Log3 $name, 5, "$hash->{TYPE} ($name) - error while connecting: $error";
  578. return undef;
  579. }
  580. #######################################
  581. # Websocket Functions
  582. #######################################
  583. sub Neuron_wsHandshake($) {
  584. my $hash = shift;
  585. my $name = $hash->{NAME};
  586. my $host = $hash->{HOST};
  587. #my $path = $hash->{PATH};
  588. my $path = "/ws";
  589. my $wsKey = encode_base64(gettimeofday());
  590. my $wsHandshakeCmd = "";
  591. $wsHandshakeCmd .= "GET $path HTTP/1.1\r\n";
  592. $wsHandshakeCmd .= "Host: $host\r\n";
  593. $wsHandshakeCmd .= "User-Agent: FHEM\r\n";
  594. $wsHandshakeCmd .= "Upgrade: websocket\r\n";
  595. $wsHandshakeCmd .= "Connection: Upgrade\r\n";
  596. $wsHandshakeCmd .= "Sec-WebSocket-Version: 13\r\n";
  597. $wsHandshakeCmd .= "Sec-WebSocket-Key: " . $wsKey . "\r\n";
  598. Log3 $name, 4, "$hash->{TYPE} ($name) - Starting Websocket Handshake";
  599. Neuron_Write($hash,$wsHandshakeCmd);
  600. $hash->{HELPER}{wsKey} = $wsKey;
  601. # Log3 $name, 4, "$hash->{TYPE} Websocket ($name) - start WS hearbeat timer";
  602. # Neuron_HbTimer($hash);
  603. return undef;
  604. }
  605. sub Neuron_wsCheckHandshake($$) {
  606. my ($hash,$response) = @_;
  607. my $name = $hash->{NAME};
  608. # header in Hash wandeln
  609. my %header = ();
  610. foreach my $line (split("\r\n", $response)) {
  611. my ($key,$value) = split( ": ", $line );
  612. next if( !$value );
  613. $value =~ s/^ //;
  614. Log3 $name, 4, "$hash->{TYPE} ($name) - headertohash |$key|$value|";
  615. $header{lc($key)} = $value;
  616. }
  617. # check handshake
  618. if( defined($header{'sec-websocket-accept'})) {
  619. my $keyAccept = $header{'sec-websocket-accept'};
  620. Log3 $name, 5, "$hash->{TYPE} ($name) - keyAccept: $keyAccept";
  621. my $wsKey = $hash->{HELPER}{wsKey};
  622. my $expectedResponse = trim(encode_base64(pack('H*', sha1_hex(trim($wsKey)."258EAFA5-E914-47DA-95CA-C5AB0DC85B11"))));
  623. if ($keyAccept eq $expectedResponse) {
  624. Log3 $name, 4, "$hash->{TYPE} ($name) - Successful WS connection to $hash->{HOST}";
  625. readingsSingleUpdate($hash,'state','ws_connected',1);
  626. $hash->{HELPER}{WESOCKETS} = '1';
  627. InternalTimer(gettimeofday() + (5), 'Neuron_wsHertbeat', $hash, 0) if AttrVal($hash->{NAME}, 'wsFilter', '');
  628. #Neuron_wsSetFilter($hash) if AttrVal($hash->{NAME}, 'wsFilter', '');
  629. } else {
  630. Neuron_Close($hash);
  631. Log3 $name, 3, "$hash->{TYPE} ($name) - ERROR: Unsucessfull WS connection to $hash->{HOST}";
  632. readingsSingleUpdate($hash,'state','ws_handshake-error',1);
  633. }
  634. }
  635. return undef;
  636. }
  637. sub Neuron_wsSetFilter($;$) {
  638. my ($hash,$val) = @_;
  639. if ($hash->{HELPER}{wsKey} && DevIo_IsOpen($hash)) {
  640. my $wsFilter = $val || AttrVal($hash->{NAME}, 'wsFilter', 'all');
  641. my $filter = '{"cmd":"filter","devices":["'. join( '","', split(',', $wsFilter ) ) .'"]}';
  642. my $string = Neuron_wsEncode($filter);
  643. Neuron_Write($hash,$string);
  644. }
  645. }
  646. sub Neuron_wsHertbeat($) {
  647. my ($hash) = @_;
  648. if (DevIo_IsOpen($hash)) {
  649. Neuron_wsSetFilter($hash);
  650. InternalTimer(gettimeofday() + (5 * 30), 'Neuron_wsHertbeat', $hash, 0)
  651. }
  652. }
  653. # 0 1 2 3
  654. # 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1
  655. # +-+-+-+-+-------+-+-------------+-------------------------------+
  656. # |F|R|R|R| opcode|M| Payload len | Extended payload length |
  657. # |I|S|S|S| (4) |A| (7) | (16/64) |
  658. # |N|V|V|V| |S| | (if payload len==126/127) |
  659. # | |1|2|3| |K| | |
  660. # +-+-+-+-+-------+-+-------------+ - - - - - - - - - - - - - - - +
  661. # | Extended payload length continued, if payload len == 127 |
  662. # + - - - - - - - - - - - - - - - +-------------------------------+
  663. # | |Masking-key, if MASK set to 1 |
  664. # +-------------------------------+-------------------------------+
  665. ## | Masking-key (continued) | Payload Data |
  666. # +-------------------------------- - - - - - - - - - - - - - - - +
  667. # : Payload Data continued ... :
  668. # + - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - +
  669. # | Payload Data continued ... |
  670. # +---------------------------------------------------------------+
  671. # https://tools.ietf.org/html/draft-ietf-hybi-thewebsocketprotocol-17
  672. sub Neuron_wsEncode($;$$) {
  673. my ($payload, $type, $masked) = @_;
  674. Log3 undef, 3, "Neuron_wsEncode Payload: " . $payload;
  675. $type //= "text";
  676. $masked //= 1; # Mask If set to 1, a masking key is present in masking-key. 1 for all frames sent from client to server
  677. my $RSV = 0;
  678. my $FIN = 1; # FIN Indicates that this is the final fragment in a message. The first fragment MAY also be the final fragment.
  679. my $MAX_PAYLOAD_SIZE = 65536;
  680. my $wsString ='';
  681. $wsString .= pack 'C', ($opcode{$type} | $RSV | ($FIN ? 128 : 0));
  682. my $len = length($payload);
  683. return "payload to big" if ($len > $MAX_PAYLOAD_SIZE);
  684. if ($len <= 125) {
  685. $len |= 0x80 if $masked;
  686. $wsString .= pack 'C', $len;
  687. } elsif ($len <= 0xffff) {
  688. $wsString .= pack 'C', 126 + ($masked ? 128 : 0);
  689. $wsString .= pack 'n', $len;
  690. } else {
  691. $wsString .= pack 'C', 127 + ($masked ? 128 : 0);
  692. $wsString .= pack 'N', $len >> 32;
  693. $wsString .= pack 'N', ($len & 0xffffffff);
  694. }
  695. if ($masked) {
  696. my $mask = pack 'N', int(rand(2**32));
  697. $wsString .= $mask;
  698. $wsString .= Neuron_wsMasking($payload, $mask);
  699. } else {
  700. $wsString .= $payload;
  701. }
  702. Log3 undef, 3, "Neuron_wsEncode String: " . unpack('H*',$wsString);
  703. return $wsString;
  704. }
  705. sub Neuron_wsDecode($$) {
  706. my ($hash,$wsString) = @_;
  707. Log3 $hash, 5, "Neuron_wsDecode String:\n" . $wsString;
  708. while (length $wsString) {
  709. my $FIN = (ord(substr($wsString,0,1)) & 0b10000000) >> 7;
  710. my $OPCODE = (ord(substr($wsString,0,1)) & 0b00001111);
  711. my $masked = (ord(substr($wsString,1,1)) & 0b10000000) >> 7;
  712. my $len = (ord(substr($wsString,1,1)) & 0b01111111);
  713. my $offset = 2;
  714. if ($len == 126) {
  715. $len = unpack 'n', substr($wsString,$offset,2);
  716. $offset += 2;
  717. } elsif ($len == 127) {
  718. $len = unpack 'q', substr($wsString,$offset,8);
  719. $offset += 8;
  720. }
  721. my $mask;
  722. if($masked) { # Mask auslesen falls Masked Bit gesetzt
  723. $mask = substr($wsString,$offset,4);
  724. $offset += 4;
  725. }
  726. #String kürzer als Längenangabe -> Zwischenspeichern?
  727. if (length($wsString) < $offset + $len) {
  728. Log3 $hash, 3, "Neuron_wsDecode Incomplete:\n" . $wsString;
  729. return;
  730. }
  731. my $payload = substr($wsString, $offset, $len); # Daten aus String extrahieren
  732. if ($masked) { # Daten demaskieren falls maskiert
  733. $payload = Neuron_wsMasking($payload, $mask);
  734. }
  735. Log3 $hash, 5, "Neuron_wsDecode Payload:\n" . $payload;
  736. $wsString = substr($wsString,$offset+$len); # ausgewerteten Stringteil entfernen
  737. if ($FIN) {
  738. if ($OPCODE == $opcode{"text"}) {
  739. Neuron_ParseWsResponse($hash,$payload);
  740. }
  741. }
  742. # Behandlung von Segmentierten Botschaften
  743. # if ($FIN) {
  744. # if (@{$self->{fragments}}) {
  745. # $self->opcode(shift @{$self->{fragments}});
  746. # } else {
  747. # $self->opcode($opcode);
  748. # }
  749. # $payload = join '', @{$self->{fragments}}, $payload;
  750. # $self->{fragments} = [];
  751. # return $payload;
  752. # } else {
  753. # # Remember first fragment opcode
  754. # if (!@{$self->{fragments}}) {
  755. # push @{$self->{fragments}}, $opcode;
  756. # }
  757. # push @{$self->{fragments}}, $payload;
  758. # die "Too many fragments" if @{$self->{fragments}} > $self->{max_fragments_amount};
  759. # }
  760. }
  761. }
  762. sub Neuron_wsMasking($$) {
  763. my ($payload, $mask) = @_;
  764. $mask = $mask x (int(length($payload) / 4) + 1);
  765. $mask = substr($mask, 0, length($payload));
  766. $payload = $payload ^ $mask;
  767. return $payload;
  768. }
  769. 1;
  770. =pod
  771. =item device
  772. =item summary Module for EVOK driven devices like UniPi Neuron
  773. =item summary_DE Modul f&uuml; Ger&auml;te auf denen EVOK l&auml;uft z.B. UniPi Neuron.
  774. =begin html
  775. <a name="Neuron"></a>
  776. <h3>Neuron</h3>
  777. <ul>
  778. <a name="Neuron"></a>
  779. Module for EVOK driven devices like UniPi Neuron.
  780. Defines will be automatically created by the Neuron module.
  781. <br>
  782. <a name="NeuronDefine"></a>
  783. <b>Define</b>
  784. <ul>
  785. <code>define <name> Neuron &lt;dev&gt; &lt;circuit&gt;</code><br><br>
  786. &lt;dev&gt; is an device type like input, ai (analog input), relay (digital output) etc.<br>
  787. &lt;circuit&gt; ist the number of the device.
  788. <br><br>
  789. Example:
  790. <pre>
  791. <code>define <name> Neuron &lt;IP&gt;[:&lt;Port&gt;]</code><br><br>
  792. </pre>
  793. </ul>
  794. <a name="NeuronSet"></a>
  795. <b>Set</b>
  796. <ul>
  797. <code>set &lt;name&gt; &lt;value&gt;</code>
  798. <br><br>
  799. where <code>value</code> can be e.g.:<br>
  800. <ul><li>for relay
  801. <ul><code>
  802. off<br>
  803. on<br>
  804. </code>
  805. </ul>
  806. The <a href="#setExtensions"> set extensions</a> are also supported for output devices.<br>
  807. </li>
  808. Other set values depending on the options of the device function.
  809. Details can be found in the UniPi Evok documentation.
  810. </ul>
  811. </ul>
  812. <a name="NeuronGet"></a>
  813. <b>Get</b>
  814. <ul>
  815. <code>get &lt;name&gt; &lt;value&gt;</code>
  816. <br><br>
  817. where <code>value</code> can be<br>
  818. <ul>
  819. <li>refresh: uptates all readings</li>
  820. <li>config: returns the configuration JSON</li>
  821. </ul>
  822. </ul><br>
  823. <a name="RPI_GPIOAttr"></a>
  824. <b>Attributes</b>
  825. <ul>
  826. <li>connection<br>
  827. Set the connection type to the EVOK device<br>
  828. Default: polling, valid values: websockets, polling<br><br>
  829. </li>
  830. <li>poll_interval<br>
  831. Set the polling interval in minutes to query all readings (and distribute them to logical devices)<br>
  832. Default: -, valid values: decimal number<br><br>
  833. </li>
  834. <li>wsFilter<br>
  835. Filter to limit the list of devices which should send websocket events<br>
  836. Default: all, valid values: all, ai, ao, input, led, relay, wd<br><br>
  837. </li>
  838. <li>logicalDev<br>
  839. Filter which subdevices should create / communicate with logical device<br>
  840. Default: ao, input, led, relay, valid values: ai, ao, input, led, relay, wd<br><br>
  841. </li>
  842. <li><a href="#readingFnAttributes">readingFnAttributes</a></li>
  843. </ul>
  844. <br>
  845. </ul>
  846. =end html
  847. =begin html_DE
  848. <a name="Neuron"></a>
  849. <h3>Neuron</h3>
  850. <ul>
  851. <a name="Neuron"></a>
  852. Modul f&uuml; die Steuerung von Ger&auml;ten auf denen EVOK l&auml;uft z.B. UniPi Neuron.
  853. <br>
  854. <a name="NeuronDefine"></a>
  855. <b>Define</b>
  856. <ul>
  857. <code>define <name> Neuron &lt;IP&gt;[:&lt;Port&gt;]</code><br><br>
  858. <br><br>
  859. </ul>
  860. <a name="NeuronSet"></a>
  861. <b>Set</b>
  862. <ul>
  863. <code>set &lt;name&gt; &lt;value&gt; [&lt;args&gt;]</code>
  864. <br><br>
  865. clearreadings:noArg websocket:open,close atest postjson
  866. where <code>value</code> can be e.g.:<br>
  867. <ul><li>dev_circuit<br>
  868. nur f&uuml; Ausg&auml;nge<br>
  869. &lt;args&gt;: on, off f&uuml;r Ausg&auml;nge und Slider f&uumlr ao<br>
  870. <br>
  871. </li>
  872. <li>clearreadings<br>
  873. l&ouml;sche alle Readings
  874. </li>
  875. <li>websocket<br>
  876. &lt;arg&gt;: open,close<br>
  877. Websocket Verbindung &ouml;ffnen, schliessen
  878. </li>
  879. <li>postjson<br>
  880. &lt;args&gt;: <code>dev circuit type value</code><br>
  881. JSON Kommando an entsprechendes Subdevice schicken.<br>
  882. z.B.: <code>set neuron input 1_01 mode simple</code>
  883. </li>
  884. Details dazu sind in der UniPi Evok Dokumentation zu finden.
  885. </ul>
  886. </ul>
  887. <a name="NeuronGet"></a>
  888. <b>Get</b>
  889. <ul>
  890. <code>get &lt;name&gt; &lt;value&gt; [&lt;arg&gt;]</code>
  891. <br><br>
  892. where <code>value</code> can be<br>
  893. <ul>
  894. <li>all: aktualisiert alle readings</li>
  895. <li>config: gibt das Konfiguration des Subdevices &lt;arg&gt; zur&uuml;ck</li>
  896. <li>updt_sets_gets: Aktualisierung der Set und Get Auswahllisten</li>
  897. <li>value: gibt das Status des Subdevices &lt;arg&gt; zur&uuml;ck</li>
  898. </ul>
  899. </ul><br>
  900. <a name="RPI_GPIOAttr"></a>
  901. <b>Attribute</b>
  902. <ul>
  903. <li>connection<br>
  904. Verbindungsart zum EVOK Device<br>
  905. Standard: polling, g&uuml;ltige Werte: websockets, polling<br><br>
  906. </li>
  907. <li>poll_interval<br>
  908. Interval in Minuten in dem alle Werte gelesen (und auch an die log. Devices weitergeleitet) werden.<br>
  909. Standard: -, g&uuml;ltige Werte: Dezimalzahl<br><br>
  910. </li>
  911. <li>wsFilter<br>
  912. Filter um die liste der Ger&auml;te zu limitieren welche websocket events generieren sollen<br>
  913. Standard: all, g&uuml;ltige Werte: all, ai, ao, input, led, relay, wd<br><br>
  914. </li>
  915. <li>logicalDev<br>
  916. Filter um Ger&auml;te zu limitieren die logische Devices anlegen und mit ihnen kommunizieren.<br>
  917. Standard: ao, input, led, relay, g&uuml;ltige Werte: ai, ao, input, led, relay, wd<br><br>
  918. </li>
  919. <li><a href="#readingFnAttributes">readingFnAttributes</a></li>
  920. </ul>
  921. <br>
  922. </ul>
  923. =end html_DE
  924. =cut