38_Broadlink.pm 31 KB

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