38_Broadlink.pm 24 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765
  1. #original script by https://github.com/mjg59/python-broadlink
  2. #some parts by 31_LightScene.pm
  3. # $Id: 38_Broadlink.pm 14362 2017-05-25 10:34:26Z daniel2311 $
  4. package main;
  5. use strict;
  6. use warnings;
  7. use integer;
  8. use Time::Local;
  9. use IO::Socket::INET;
  10. use IO::Select;
  11. #use Crypt::CBC;
  12. #use Crypt::OpenSSL::AES;
  13. #use MIME::Base64;
  14. #use Data::Dump qw(dump);
  15. my $broadlink_hasJSON = 1;
  16. my $broadlink_hasDataDumper = 1;
  17. my $broadlink_hasCBC = 1;
  18. my $broadlink_hasAES = 1;
  19. my $broadlink_hasBase64 = 1;
  20. sub Broadlink_Initialize($) {
  21. my ($hash) = @_;
  22. $hash->{DefFn} = 'Broadlink_Define';
  23. $hash->{UndefFn} = 'Broadlink_Undef';
  24. $hash->{SetFn} = 'Broadlink_Set';
  25. $hash->{AttrList} = 'socket_timeout:0.5,1,1.5,2,2.5,3,4,5,10 ' . $readingFnAttributes;
  26. eval "use JSON";
  27. $broadlink_hasJSON = 0 if($@);
  28. eval "use Data::Dumper";
  29. $broadlink_hasDataDumper = 0 if($@);
  30. eval "use Crypt::CBC";
  31. $broadlink_hasCBC = 0 if($@);
  32. eval "use Crypt::OpenSSL::AES";
  33. $broadlink_hasAES = 0 if($@);
  34. eval "use MIME::Base64";
  35. $broadlink_hasBase64 = 0 if($@);
  36. }
  37. sub Broadlink_Define($$) {
  38. my ($hash, $def) = @_;
  39. my @param = split('[ \t]+', $def);
  40. return "install Crypt::CBC to use Broadlink" if( !$broadlink_hasCBC);
  41. return "install Crypt::OpenSSL::AES to use Broadlink" if( !$broadlink_hasAES);
  42. return "install MIME::Base64 to use Broadlink" if( !$broadlink_hasBase64);
  43. return "install JSON (or Data::Dumper) to use Broadlink" if( !$broadlink_hasJSON && !$broadlink_hasDataDumper );
  44. if(int(@param) <= 4) {
  45. return "wrong syntax: define <name> Broadlink <connection=ip> <mac=xx:xx:xx:xx:xx> <optional type=rmpro or sp3>";
  46. }
  47. $hash->{ip} = $param[2];
  48. $hash->{mac} = $param[3];
  49. $hash->{devtype} = $param[4];
  50. $hash->{'.key'} = pack('C*', 0x09, 0x76, 0x28, 0x34, 0x3f, 0xe9, 0x9e, 0x23, 0x76, 0x5c, 0x15, 0x13, 0xac, 0xcf, 0x8b, 0x02);
  51. $hash->{'.iv'} = pack('C*', 0x56, 0x2e, 0x17, 0x99, 0x6d, 0x09, 0x3d, 0x28, 0xdd, 0xb3, 0xba, 0x69, 0x5a, 0x2e, 0x6f, 0x58);
  52. $hash->{'.id'} = pack('C*', 0, 0, 0, 0);
  53. $hash->{counter} = 1;
  54. $hash->{isAuthenticated} = 0;
  55. if ($hash->{devtype} eq 'sp3') { #steckdose
  56. Broadlink_auth($hash);
  57. if ($hash->{isAuthenticated} != 0) {
  58. Broadlink_sp3_getStatus($hash, 1);
  59. }
  60. } else {
  61. $hash->{commandList} = ();
  62. Broadlink_Load($hash);
  63. }
  64. return undef;
  65. }
  66. sub Broadlink_Undef($$) {
  67. my ($hash, $arg) = @_;
  68. # nothing to do
  69. return undef;
  70. }
  71. sub Broadlink_Get($@) {
  72. my ($hash, @param) = @_;
  73. return undef;
  74. }
  75. sub Broadlink_Set(@) {
  76. my ($hash, $name, $cmd, @args) = @_;
  77. if ($hash->{devtype} eq 'sp3') { #steckdose
  78. if ($cmd eq 'on') {
  79. Broadlink_auth($hash);
  80. if ($hash->{isAuthenticated} != 0) {
  81. Broadlink_sp3_setPower($hash, 1);
  82. }
  83. return undef;
  84. } elsif ($cmd eq 'off') {
  85. Broadlink_auth($hash);
  86. if ($hash->{isAuthenticated} != 0) {
  87. Broadlink_sp3_setPower($hash, 0);
  88. }
  89. return undef;
  90. } elsif ($cmd eq 'toggle') {
  91. Broadlink_auth($hash);
  92. if ($hash->{isAuthenticated} != 0) {
  93. if ($hash->{STATE} eq 'unknown') {
  94. Broadlink_sp3_getStatus($hash);
  95. }
  96. if ($hash->{STATE} eq 'on') {
  97. Broadlink_sp3_setPower($hash, 0);
  98. } else {
  99. Broadlink_sp3_setPower($hash, 1);
  100. }
  101. }
  102. return undef;
  103. } elsif ($cmd eq 'getStatus') {
  104. Broadlink_auth($hash);
  105. if ($hash->{isAuthenticated} != 0) {
  106. Broadlink_sp3_getStatus($hash);
  107. }
  108. return undef;
  109. } else {
  110. return "Unknown argument $cmd, choose one of on off toggle getStatus";
  111. }
  112. return "$cmd. Try to get it.";
  113. } else { #rmpro rmmini etc.
  114. if ($cmd eq 'recordNewCommand') {
  115. Broadlink_auth($hash);
  116. my $cmdname = $args[0];
  117. if ($cmdname eq "") {
  118. return "Please specify commandname to record set <dev> recordNewCommand <name of the command>";
  119. }
  120. #if ($cmdname =~ /#|'|"|\/|\\|,/) {
  121. # return "it is not allowed to use #,',\",\/,\\ or comma in commandname";
  122. #}
  123. if ($cmdname =~ /^[A-Z_a-z0-9\+\-]+$/) {
  124. $hash->{STATE} = "learning new command";
  125. if ($hash->{isAuthenticated} != 0) {
  126. Broadlink_enterLearning($hash, $cmdname);
  127. }
  128. } else {
  129. return "only A-Z, a-z, 0-9, _, +, - are allowed in commandname";
  130. }
  131. return undef;
  132. } elsif ($cmd eq 'commandSend') {
  133. Broadlink_auth($hash);
  134. my $cmdname = $args[0];
  135. if(!$hash->{commandList}{$cmdname}) {
  136. return "Unknown command $cmdname, choose an existing one or record a new one";
  137. }
  138. $hash->{STATE} = "send command:" . $cmdname;
  139. if ($hash->{isAuthenticated} != 0) {
  140. Broadlink_send_data($hash, decode_base64($hash->{commandList}{$cmdname}), $cmdname);
  141. }
  142. return undef;
  143. } elsif ($cmd eq 'rename') {
  144. my $cmdname = $args[0];
  145. my $newCmdname = $args[1];
  146. if ($cmdname eq "" or $newCmdname eq "") {
  147. return "Command wrong use set <dev> rename <oldname> <newname>";
  148. }
  149. if(!$hash->{commandList}{$cmdname}) {
  150. return "Unknown command $cmdname, choose an existing one";
  151. }
  152. if ($newCmdname =~ /^[A-Z_a-z0-9\+\-]+$/) {
  153. Broadlink_Load($hash);
  154. $hash->{commandList}{$newCmdname} = $hash->{commandList}{$cmdname};
  155. delete($hash->{commandList}{$cmdname});
  156. Broadlink_Save($hash);
  157. } else {
  158. return "only A-Z, a-z, 0-9, _, +, - are allowed in commandname";
  159. }
  160. return undef;
  161. } elsif ($cmd eq 'remove') {
  162. my $cmdname = $args[0];
  163. if(!$hash->{commandList}{$cmdname}) {
  164. return "Unknown command $cmdname, choose an existing one";
  165. }
  166. Broadlink_Load($hash);
  167. delete($hash->{commandList}{$cmdname});
  168. Broadlink_Save($hash);
  169. return undef;
  170. } else {
  171. #sort with ignore case
  172. my $commandList = join(",", sort {
  173. lc $a cmp lc $b
  174. || $a cmp $b
  175. } keys %{$hash->{commandList}});
  176. #return "Unknown argument $cmd, choose one of learnNewCommand sendCommand sendCommandBase64 sendCommandHex createCommandBase64 createCommandHex";
  177. return "Unknown argument $cmd, choose one of recordNewCommand rename remove:" . $commandList . " commandSend:". $commandList;
  178. }
  179. return "$cmd. Try to get it.";
  180. }
  181. }
  182. sub Broadlink_send_data(@) {
  183. my ($hash, $dataToSend, $cmdname) = @_;
  184. my @broadlink_payload = ((0) x 4);
  185. $broadlink_payload[0] = 2;
  186. my @values = split(//,$dataToSend);
  187. foreach my $val (@values) {
  188. push @broadlink_payload, unpack("C*", $val);
  189. }
  190. my $msg = "Try to send a command: " . $cmdname;
  191. Log3 $hash->{NAME}, 5, $hash->{NAME} . ": " . $msg;
  192. my $response = Broadlink_send_packet($hash, 0x6a, @broadlink_payload);
  193. if (length($response) > 0 && $response ne "xxx") {
  194. readingsSingleUpdate ( $hash, "lastCommandSend", $cmdname, 1 );
  195. my $msg = $cmdname ." send";
  196. Log3 $hash->{NAME}, 5, $hash->{NAME} . ": " . $msg;
  197. } else {
  198. readingsSingleUpdate ( $hash, "connectionErrorOn", "sendCommand: " . $cmdname, 1 );
  199. my $msg = $cmdname . " command send failed - device not connected?";
  200. Log3 $hash->{NAME}, 4, $hash->{NAME} . ": " . $msg;
  201. $hash->{STATE} = $msg;
  202. }
  203. }
  204. sub Broadlink_check_data(@) {
  205. my ($hash) = @_;
  206. my @broadlink_payload = ((0) x 16);
  207. $broadlink_payload[0] = 4;
  208. my $msg = "check for new command";
  209. Log3 $hash->{NAME}, 5, $hash->{NAME} . ": " . $msg;
  210. my $data = Broadlink_send_packet($hash, 0x6a, @broadlink_payload);
  211. #length must be bigger than 0x38, if not, cant get substring with data
  212. if (length($data) > 0x38 && $data ne "xxx") {
  213. my $err = unpack("C*", substr($data, 0x22, 1)) | (unpack("C*", substr($data, 0x23, 1)) << 8);
  214. if ($err == 0) {
  215. my $msg = "new command found";
  216. Log3 $hash->{NAME}, 5, $hash->{NAME} . ": " . $msg;
  217. my $enc_payload = substr($data, 0x38);
  218. my $cipher = Broadlink_getCipher($hash);
  219. my $decodedData = $cipher->decrypt($enc_payload);
  220. $hash->{STATE} = "new Command learned: " . $hash->{'.newcommandname'};
  221. readingsSingleUpdate ( $hash, "lastRecordedCommand", $hash->{'.newcommandname'}, 1 );
  222. #frist load it again, if more than one device is defined
  223. Broadlink_Load($hash);
  224. $hash->{commandList}{$hash->{'.newcommandname'}} = encode_base64(substr($decodedData, 4));
  225. Broadlink_Save($hash);
  226. return substr($decodedData, 4);
  227. } else {
  228. my $msg = "Error receiving command";
  229. Log3 $hash->{NAME}, 5, $hash->{NAME} . ": " . $msg;
  230. }
  231. } else {
  232. my $msg = "no new command data found - data length: " . length($data);
  233. Log3 $hash->{NAME}, 4, $hash->{NAME} . ": " . $msg;
  234. $hash->{STATE} = $msg;
  235. }
  236. $hash->{'.broadlink_checkCommands'}++;
  237. if ($hash->{'.broadlink_checkCommands'} < 15) {
  238. my $msg = "no command recorded. retry count:" . $hash->{'.broadlink_checkCommands'};
  239. Log3 $hash->{NAME}, 5, $hash->{NAME} . ": " . $msg;
  240. InternalTimer(gettimeofday()+2, "Broadlink_check_data", $hash);
  241. } else {
  242. my $msg = "no command recorded even after a lot of retries. Try to learn again";
  243. Log3 $hash->{NAME}, 3, $hash->{NAME} . ": " . $msg;
  244. $hash->{STATE} = $msg;
  245. readingsSingleUpdate ( $hash, "lastFailedRecordedCommand", $hash->{'.newcommandname'}, 1 );
  246. }
  247. }
  248. sub Broadlink_sp3_getStatus(@) {
  249. my ($hash) = @_;
  250. my @broadlink_payload = ((0) x 16);
  251. $broadlink_payload[0] = 1;
  252. my $msg = "sp3_status request";
  253. Log3 $hash->{NAME}, 5, $hash->{NAME} . ": " . $msg;
  254. my $data = Broadlink_send_packet($hash, 0x6a, @broadlink_payload);
  255. #length must be bigger than 0x38, if not, cant get substring with data
  256. if (length($data) > 0x38 && $data ne "xxx") {
  257. my $err = unpack("C*", substr($data, 0x22, 1)) | (unpack("C*", substr($data, 0x23, 1)) << 8);
  258. if ($err == 0) {
  259. my $msg = "sp3 receiving status - data length: " . length($data);
  260. Log3 $hash->{NAME}, 1, $hash->{NAME} . ": " . $msg;
  261. my $enc_payload = substr($data, 0x38);
  262. my $cipher = Broadlink_getCipher($hash);
  263. my $decodedData = $cipher->decrypt($enc_payload);
  264. if (unpack("C*", substr($decodedData, 4, 1)) eq 0) {
  265. $hash->{STATE} = "off";
  266. } else {
  267. $hash->{STATE} = "on";
  268. }
  269. } else {
  270. my $msg = "Error receiving status";
  271. Log3 $hash->{NAME}, 4, $hash->{NAME} . ": " . $msg;
  272. $hash->{STATE} = "unknown";
  273. readingsSingleUpdate ( $hash, "connectionErrorOn", "geStatusWithData", 1 );
  274. }
  275. } else {
  276. my $msg = "no new command data found - data length: " . length($data);
  277. Log3 $hash->{NAME}, 4, $hash->{NAME} . ": " . $msg;
  278. $hash->{STATE} = "unknown";
  279. readingsSingleUpdate ( $hash, "connectionErrorOn", "geStatus", 1 );
  280. }
  281. }
  282. sub Broadlink_sp3_setPower(@) {
  283. my ($hash, $on) = @_;
  284. my @broadlink_payload = ((0) x 16);
  285. $broadlink_payload[0] = 2;
  286. if ($on == 1) {
  287. $broadlink_payload[4] = 1;
  288. } else {
  289. $broadlink_payload[4] = 0;
  290. }
  291. my $msg = "sp3_status request";
  292. Log3 $hash->{NAME}, 5, $hash->{NAME} . ": " . $msg;
  293. my $data = Broadlink_send_packet($hash, 0x6a, @broadlink_payload);
  294. if (length($data) > 0 && $data ne "xxx") {
  295. if ($on == 1) {
  296. $hash->{STATE} = "on";
  297. } else {
  298. $hash->{STATE} = "off";
  299. }
  300. } else {
  301. readingsSingleUpdate ( $hash, "connectionErrorOn", "powerChange", 1 );
  302. my $msg = "powerChange - device not connected?";
  303. Log3 $hash->{NAME}, 4, $hash->{NAME} . ": " . $msg;
  304. $hash->{STATE} = "unkown";
  305. }
  306. }
  307. sub Broadlink_enterLearning(@) {
  308. my ($hash, $cmdname) = @_;
  309. my @broadlink_payload = ((0) x 16);
  310. $broadlink_payload[0] = 3;
  311. my $msg = "learn new commadn for " . $cmdname;
  312. Log3 $hash->{NAME}, 5, $hash->{NAME} . ": " . $msg;
  313. my $data = Broadlink_send_packet($hash, 0x6a, @broadlink_payload);
  314. if (length($data) > 0 && $data ne "xxx") {
  315. $hash->{'.broadlink_checkCommands'} = 0;
  316. $hash->{'.newcommandname'} = $cmdname;
  317. my $msg = "start polling for " . $cmdname;
  318. Log3 $hash->{NAME}, 5, $hash->{NAME} . ": " . $msg;
  319. InternalTimer(gettimeofday()+2, "Broadlink_check_data", $hash);
  320. } else {
  321. readingsSingleUpdate ( $hash, "connectionErrorOn", "enterLearning", 1 );
  322. my $msg = "command learn failed - device not connected?";
  323. Log3 $hash->{NAME}, 4, $hash->{NAME} . ": " . $msg;
  324. $hash->{STATE} = $msg;
  325. }
  326. }
  327. sub Broadlink_auth(@) {
  328. my ($hash) = @_;
  329. #never authenticate again, if not needed
  330. if ($hash->{isAuthenticated} == 0) {
  331. my @broadlink_payload = ((0) x 80);
  332. $broadlink_payload[0x04] = 0x31;
  333. $broadlink_payload[0x05] = 0x31;
  334. $broadlink_payload[0x06] = 0x31;
  335. $broadlink_payload[0x07] = 0x31;
  336. $broadlink_payload[0x08] = 0x31;
  337. $broadlink_payload[0x09] = 0x31;
  338. $broadlink_payload[0x0a] = 0x31;
  339. $broadlink_payload[0x0b] = 0x31;
  340. $broadlink_payload[0x0c] = 0x31;
  341. $broadlink_payload[0x0d] = 0x31;
  342. $broadlink_payload[0x0e] = 0x31;
  343. $broadlink_payload[0x0f] = 0x31;
  344. $broadlink_payload[0x10] = 0x31;
  345. $broadlink_payload[0x11] = 0x31;
  346. $broadlink_payload[0x12] = 0x31;
  347. $broadlink_payload[0x1e] = 0x01;
  348. $broadlink_payload[0x2d] = 0x01;
  349. $broadlink_payload[0x30] = ord('T');
  350. $broadlink_payload[0x31] = ord('e');
  351. $broadlink_payload[0x32] = ord('s');
  352. $broadlink_payload[0x33] = ord('t');
  353. $broadlink_payload[0x34] = ord(' ');
  354. $broadlink_payload[0x35] = ord(' ');
  355. $broadlink_payload[0x36] = ord('1');
  356. my $msg = "try to authenticate";
  357. Log3 $hash->{NAME}, 5, $hash->{NAME} . ": " . $msg;
  358. my $response = Broadlink_send_packet($hash, 0x65, @broadlink_payload);
  359. if (length($response) > 0x38 && $response ne "xxx") {
  360. my $enc_payload = substr($response, 0x38);
  361. my $cipher = Broadlink_getCipher($hash);
  362. my $broadlink_payload = $cipher->decrypt($enc_payload);
  363. #authentication worked
  364. $hash->{'.key'} = substr($broadlink_payload, 0x04, 16);
  365. $hash->{'.id'} = substr($broadlink_payload, 0, 4);
  366. $hash->{isAuthenticated} = 1;
  367. } else {
  368. readingsSingleUpdate ( $hash, "lastAuthenticationFailed", "", 1 );
  369. my $msg = "authentication failed - device not connected? - response length: " . length($response);
  370. Log3 $hash->{NAME}, 4, $hash->{NAME} . ": " . $msg;
  371. $hash->{STATE} = $msg;
  372. }
  373. }
  374. }
  375. sub Broadlink_getCipher(@) {
  376. my ($hash) = @_;
  377. return Crypt::CBC->new(
  378. -key => $hash->{'.key'},
  379. -cipher => "Crypt::OpenSSL::AES",
  380. -header => "none",
  381. -iv => $hash->{'.iv'},
  382. -literal_key => 1,
  383. -keysize => 16,
  384. -padding => 'space'
  385. );
  386. }
  387. sub Broadlink_send_packet(@) {
  388. my ($hash,$command,@broadlink_payload) = @_;
  389. #prepare header of packet
  390. $hash->{counter} = ($hash->{counter} + 1) & 0xffff;
  391. my @broadlink_id = split(//,$hash->{'.id'});
  392. my @broadlink_mac = split ':', $hash->{mac};
  393. my @packet = (0) x 56;
  394. $packet[0x00] = 0x5a;
  395. $packet[0x01] = 0xa5;
  396. $packet[0x02] = 0xaa;
  397. $packet[0x03] = 0x55;
  398. $packet[0x04] = 0x5a;
  399. $packet[0x05] = 0xa5;
  400. $packet[0x06] = 0xaa;
  401. $packet[0x07] = 0x55;
  402. $packet[0x24] = 0x2a;
  403. $packet[0x25] = 0x27;
  404. $packet[0x26] = $command;
  405. $packet[0x28] = $hash->{counter} & 0xff;
  406. $packet[0x29] = $hash->{counter} >> 8;
  407. $packet[0x2a] = unpack('H', $broadlink_mac[0]);
  408. $packet[0x2b] = unpack('H', $broadlink_mac[1]);
  409. $packet[0x2c] = unpack('H', $broadlink_mac[2]);
  410. $packet[0x2d] = unpack('H', $broadlink_mac[3]);
  411. $packet[0x2e] = unpack('H', $broadlink_mac[4]);
  412. $packet[0x2f] = unpack('H', $broadlink_mac[5]);
  413. $packet[0x30] = unpack('C', $broadlink_id[0]);
  414. $packet[0x31] = unpack('C', $broadlink_id[1]);
  415. $packet[0x32] = unpack('C', $broadlink_id[2]);
  416. $packet[0x33] = unpack('C', $broadlink_id[3]);
  417. #calculate payload checksum of original data
  418. my $checksum = 0xbeaf;
  419. my $arrSize = @broadlink_payload;
  420. for(my $i = 0; $i < $arrSize; $i++) {
  421. $checksum += $broadlink_payload[$i];
  422. $checksum = $checksum & 0xffff;
  423. }
  424. #put the checksum of payload in the header info
  425. $packet[0x34] = $checksum & 0xff;
  426. $packet[0x35] = $checksum >> 8;
  427. #crypt payload
  428. my $cipher = Broadlink_getCipher($hash);
  429. my $payloadCrypt = $cipher->encrypt(pack('C*', @broadlink_payload));
  430. #add the crypted data to packet
  431. my @values = split(//,$payloadCrypt);
  432. foreach my $val (@values) {
  433. push @packet, unpack("C*", $val);
  434. }
  435. #create checksum of whole packet
  436. $checksum = 0xbeaf;
  437. $arrSize = @packet;
  438. for(my $i = 0; $i < $arrSize; $i++) {
  439. $checksum += $packet[$i];
  440. $checksum = $checksum & 0xffff;
  441. }
  442. #put the checksum of whole packet in the header info
  443. $packet[0x20] = $checksum & 0xff;
  444. $packet[0x21] = $checksum >> 8;
  445. #errorvalue if no data received
  446. my $data = "xxx";
  447. my $timeout = AttrVal($hash->{NAME}, 'socket_timeout', 3.0);
  448. eval {
  449. local $SIG{ALRM} = sub { die 'Timed Out'; };
  450. alarm $timeout;
  451. #send udp packet
  452. my $socket = IO::Socket::INET->new(
  453. Proto => 'udp',
  454. PeerAddr => $hash->{ip},
  455. PeerPort => '80',
  456. ReuseAddr => 1,
  457. Timeout => $timeout,
  458. #Blocking => 0
  459. ) or Log3 $hash->{NAME}, 5, $hash->{NAME} . ": " . "Problem with socket";
  460. my $select = IO::Select->new($socket) if $socket;
  461. #$socket->autoflush;
  462. $socket->send(pack('C*',@packet));
  463. #IO::Select->select($select, undef, undef, 3);
  464. if ($select->can_read($timeout)) {
  465. $socket->recv($data, 1024);
  466. } else {
  467. Log3 $hash->{NAME}, 5, $hash->{NAME} . ": " . "can't read";
  468. }
  469. $socket->close();
  470. alarm 0;
  471. };
  472. alarm 0; # race condition protection
  473. Log3 $hash->{NAME}, 3, $hash->{NAME} . ": " . 'Error Timout' if ( $@ && $@ =~ /Timed Out/ );
  474. Log3 $hash->{NAME}, 3, $hash->{NAME} . ": " . "Error: Eval corrupted: $@" if $@;
  475. Log3 $hash->{NAME}, 5, $hash->{NAME} . ": " . length($data) . " bytes received from socket";
  476. return $data;
  477. }
  478. #lightscene Copy
  479. sub Broadlink_statefileName() {
  480. my $statefile = $attr{global}{statefile};
  481. $statefile = substr $statefile,0,rindex($statefile,'/')+1;
  482. return $statefile ."broadlink.save" if( $broadlink_hasJSON );
  483. return $statefile ."broadlink.dd.save" if( $broadlink_hasDataDumper );
  484. }
  485. sub Broadlink_Save(@) {
  486. my ($hash) = @_;
  487. my $time_now = TimeNow();
  488. return "No statefile specified" if(!$attr{global}{statefile});
  489. my $statefile = Broadlink_statefileName();
  490. my $commandList = $hash->{commandList};
  491. if(open(FH, ">$statefile")) {
  492. my $t = localtime;
  493. print FH "#$t\n";
  494. if( $broadlink_hasJSON ) {
  495. print FH encode_json($commandList) if( defined($commandList) );
  496. } elsif( $broadlink_hasDataDumper ) {
  497. my $dumper = Data::Dumper->new([]);
  498. $dumper->Terse(1);
  499. $dumper->Values([$commandList]);
  500. print FH $dumper->Dump;
  501. }
  502. close(FH);
  503. } else {
  504. my $msg = "Broadlink_Save: Cannot open $statefile: $!";
  505. Log3 $hash->{NAME}, 1, $hash->{NAME} . ": " . $msg;
  506. }
  507. return undef;
  508. }
  509. sub Broadlink_Load(@) {
  510. my ($hash) = @_;
  511. return "No statefile specified" if(!$attr{global}{statefile});
  512. my $statefile = Broadlink_statefileName();
  513. if(open(FH, "<$statefile")) {
  514. my $encoded;
  515. while (my $line = <FH>) {
  516. chomp $line;
  517. next if($line =~ m/^#.*$/);
  518. $encoded .= $line;
  519. }
  520. close(FH);
  521. return if( !defined($encoded) );
  522. my $decoded;
  523. if( $broadlink_hasJSON ) {
  524. $decoded = decode_json( $encoded );
  525. } elsif( $broadlink_hasDataDumper ) {
  526. $decoded = eval $encoded;
  527. }
  528. $hash->{commandList} = $decoded;
  529. } else {
  530. my $msg = "Broadlink_Load: Cannot open $statefile: $!";
  531. Log3 $hash->{NAME}, 1, $hash->{NAME} . ": " . $msg;
  532. }
  533. return undef;
  534. }
  535. 1;
  536. =pod
  537. =item device
  538. =item summary implements a connection to Broadlink devices
  539. =item summary_DE implementiert die Verbindung zu Broadlink Geräten
  540. =begin html
  541. <a name="Broadlink"></a>
  542. <h3>Broadlink</h3>
  543. <ul>
  544. <i>Broadlink</i> implements a connection to Broadlink devices - currently tested with Broadlink RM Pro, which is able to send IR and 433MHz commands. It is also able to record this commands.
  545. <br>
  546. It requires AES encryption please install on Windows:<br>
  547. <code>ppm install Crypt-CBC</code><br>
  548. <code>ppm install Crypt-OpenSSL-AES</code><br><br>
  549. or Linux/Raspberry:
  550. <code>sudo apt-get install libcrypt-cbc-perl</code><br>
  551. <code>sudo apt-get install libcrypt-rijndael-perl</code><br>
  552. <code>sudo cpan Crypt/OpenSSL/AES.pm</code><br>
  553. <br><br>
  554. <a name="Broadlinkdefine"></a>
  555. <b>Define</b>
  556. <ul>
  557. <code>define &lt;name&gt; Broadlink &lt;ip/host&gt; &lt;mac&gt; &lt;type=rmpro or sp3&gt;</code>
  558. <br><br>
  559. Example: <code>define broadlinkWZ Broadlink 10.23.11.85 34:EA:34:F4:77:7B rmpro</code>
  560. <br><br>
  561. The <i>mac</i> of the device have to be set in format: xx:xx:xx:xx:xx<br>
  562. The type is in current development state optional.
  563. </ul>
  564. <br>
  565. <a name="Broadlinkset"></a>
  566. <b>Set for rmpro</b><br>
  567. <ul>
  568. <li><code>set &lt;name&gt; &lt;commandSend&gt; &lt;command name&gt;</code>
  569. <br><br>
  570. Send a previous recorded command.
  571. </li>
  572. <li><code>set &lt;name&gt; recordNewCommand &lt;command name&gt;</code>
  573. <br><br>
  574. Records a new command. You have to specify a commandname
  575. </li>
  576. <li>
  577. <code>set &lt;name&gt; remove &lt;command name&gt;</code>
  578. <br><br>
  579. Removes a recored command.
  580. </li>
  581. <li>
  582. <code>set &lt;name&gt; rename &lt;old command name&gt; &lt;new command name&gt;</code>
  583. <br><br>
  584. Renames a recored command.
  585. </li>
  586. </ul>
  587. <br>
  588. <b>Set for sp3</b><br>
  589. <ul>
  590. <li><code>set &lt;name&gt; on</code>
  591. <br><br>
  592. Set the device on
  593. </li>
  594. <li><code>set &lt;name&gt; off</code>
  595. <br><br>
  596. Set the device off
  597. </li>
  598. <li><code>set &lt;name&gt; toggle</code>
  599. <br><br>
  600. Toggle the device on and off
  601. </li>
  602. <li><code>set &lt;name&gt; getStatus</code>
  603. <br><br>
  604. Get the device on/off status
  605. </li>
  606. </ul>
  607. <br>
  608. <a name="Broadlinkattr"></a>
  609. <b>Attributes for all Broadlink Devices</b><br>
  610. <ul>
  611. <li><code>socket_timeout</code>
  612. <br><br>
  613. sets a timeout for the device communication
  614. </li>
  615. </ul>
  616. <br>
  617. </ul>
  618. =end html
  619. =begin html_DE
  620. <a name="Broadlink"></a>
  621. <h3>Broadlink</h3>
  622. <ul>
  623. <i>Broadlink</i> implementiert die Verbindung zu Broadlink Geräten - aktuell mit Broadlink RM Pro, welcher sowohl Infrarot als auch 433MHz aufnehmen und anschließend versenden kann.
  624. <br>
  625. Das Modul benötigt AES-Verschlüsslung.<br>
  626. In Windows installiert man die Untersützung mit:<br>
  627. <code>ppm install Crypt-CBC</code><br>
  628. <code>ppm install Crypt-OpenSSL-AES</code><br><br>
  629. Auf Linux/Raspberry:
  630. <code>sudo apt-get install libcrypt-cbc-perl</code><br>
  631. <code>sudo apt-get install libcrypt-rijndael-perl</code><br>
  632. <code>sudo cpan Crypt/OpenSSL/AES.pm</code><br>
  633. <br><br>
  634. <a name="Broadlinkdefine"></a>
  635. <b>Define</b>
  636. <ul>
  637. <code>define &lt;name&gt; Broadlink &lt;ip/host&gt; &lt;mac&gt; &lt;type=rmpro or sp3&gt;</code>
  638. <br><br>
  639. Beispiel: <code>define broadlinkWZ Broadlink 10.23.11.85 34:EA:34:F4:77:7B rmpro</code>
  640. <br><br>
  641. Die <i>mac</i>-Adresse des Gerätes muss im folgenden Format eingegeben werden: xx:xx:xx:xx:xx<br>
  642. Der Typ <i>sp3</i> wird für schaltbare Steckdosen genutzt. <i>rmpro</i> für alle anderen Geräte.
  643. </ul>
  644. <br>
  645. <a name="Broadlinkset"></a>
  646. <b>Set f&uuml;r rmpro</b><br>
  647. <ul>
  648. <li><code>set &lt;name&gt; &lt;commandSend&gt; &lt;command name&gt;</code>
  649. <br><br>
  650. Sendet ein vorher aufgenommenen Befehl
  651. </li>
  652. <li><code>set &lt;name&gt; recordNewCommand &lt;command name&gt;</code>
  653. <br><br>
  654. Nimmt ein neuen Befehl auf. Man muss einen Befehlnamen angeben.
  655. </li>
  656. <li>
  657. <code>set &lt;name&gt; remove &lt;command name&gt;</code>
  658. <br><br>
  659. Löscht einen vorher aufgezeichneten Befehl.
  660. </li>
  661. <li>
  662. <code>set &lt;name&gt; rename &lt;old command name&gt; &lt;new command name&gt;</code>
  663. <br><br>
  664. Benennt einen vorher aufgezeichneten Befehl um.
  665. </li>
  666. </ul>
  667. <br>
  668. <b>Set f&uuml;r sp3</b><br>
  669. <ul>
  670. <li><code>set &lt;name&gt; on</code>
  671. <br><br>
  672. Schaltet das Gerät an.
  673. </li>
  674. <li><code>set &lt;name&gt; off</code>
  675. <br><br>
  676. Schaltet das Gerät aus.
  677. </li>
  678. <li><code>set &lt;name&gt; toggle</code>
  679. <br><br>
  680. Schaltet das Gerät entweder ein oder aus.
  681. </li>
  682. <li><code>set &lt;name&gt; getStatus</code>
  683. <br><br>
  684. Ermittelt den aktuellen Status des Gerätes.
  685. </li>
  686. </ul>
  687. <br>
  688. <a name="Broadlinkattr"></a>
  689. <b>Attribute f&uuml;r alle Broadlink Gräte</b><br>
  690. <ul>
  691. <li><code>socket_timeout</code>
  692. <br><br>
  693. Setzt den Timeout für die Gerätekommunikation.
  694. </li>
  695. </ul>
  696. <br>
  697. </ul>
  698. =end html_DE
  699. =cut