42_Nextion.pm 25 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834
  1. ##############################################################################
  2. #
  3. # 42_Nextion.pm
  4. #
  5. # This file is part of Fhem.
  6. #
  7. # Fhem is free software: you can redistribute it and/or modify
  8. # it under the terms of the GNU General Public License as published by
  9. # the Free Software Foundation, either version 2 of the License, or
  10. # (at your option) any later version.
  11. #
  12. # Fhem is distributed in the hope that it will be useful,
  13. # but WITHOUT ANY WARRANTY; without even the implied warranty of
  14. # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
  15. # GNU General Public License for more details.
  16. #
  17. # You should have received a copy of the GNU General Public License
  18. # along with Fhem. If not, see <http://www.gnu.org/licenses/>.
  19. #
  20. ##############################################################################
  21. #
  22. # 42_Nextion (c) Johannes Viegener / https://github.com/viegener/Telegram-fhem
  23. #
  24. # $Id: 42_Nextion.pm 12468 2016-10-29 20:37:14Z viegener $
  25. #
  26. ##############################################################################
  27. # 0.0 2016-03-23 Started
  28. # Inital Version to communicate with Nextion via transparent bridge send raw commands and get returns as readings
  29. # Fix for result without error
  30. # multiCommandSend (allows also set logic)
  31. # 0.2 2016-03-29 Basic capabilities
  32. #
  33. # SendAndWaitforAnswer
  34. # disconnect with state modification
  35. # put error/cmdresult in reading on send command
  36. # text readings for messages starting with $ and specific codes
  37. # initPageX attributes and execution when page is entered with replaceSetMagic
  38. # Init commands - Attribute initCommands
  39. # init commands will also be sent on reconnect
  40. # currentPage reading will only be maintained if attribut hasSendMe is set
  41. # 0.3 2016-04-03 init commands and test with notifys
  42. #
  43. # Convert iso to/from utf-8 on messages from nextion
  44. # ReplaceSetMagic called once per command in initPage due to issue with fhem-pl change
  45. # Initial documentation completed
  46. # added new set commands: page and pageCmd
  47. # 0.4 2016-04-24 documentation / page and pageCmds
  48. #
  49. # expectAnswer can be set to ignore any commands
  50. # reduce log level for normal operation
  51. # fix disconnect
  52. # 0.5 2016-06-30 disconnect-fix / log reduced / expectAnswer
  53. #
  54. # 0.6 2016-10-29 available through SVN
  55. #
  56. ##############################################
  57. ##############################################
  58. ### TODO
  59. #
  60. # Tutorial
  61. # react on events with commands allowing values from FHEM
  62. # remove wait for answer by attribute
  63. # commands
  64. # set - page x
  65. # set - text elem text
  66. # set - val elem val
  67. # picture setting
  68. # init page from fhem might sent a magic starter and finisher something like get 4711 to recognize the init command results (can be filtered away)
  69. # number of pages as define (std max 0-9)
  70. # add 0x65 code
  71. # progress bar
  72. #
  73. ##############################################
  74. ##############################################
  75. ### Considerations for the Nextion UI
  76. #
  77. # - sendme is needed on pages otherwise initCmds can not be used
  78. # - to react on button usages $ cmds are introduced
  79. # add in postinitialize event something like
  80. # print "$b0="
  81. # get 1
  82. # or for double state buttons / switches
  83. # print "$bt0="
  84. # print bt0.val#
  85. # will result in $b0=1 / $bt0=0 or $bt0=1 being sent to fhem
  86. # - Recommended use bkcmd=3 in pre initialize event of page 0 (to not to have to wait on timeouts for commands / otherwise fhem is blocked)
  87. #
  88. #
  89. #
  90. ##############################################
  91. ##############################################
  92. ##############################################
  93. package main;
  94. use strict;
  95. use warnings;
  96. use Time::HiRes qw(gettimeofday);
  97. use Encode qw( decode encode );
  98. #########################
  99. # Forward declaration
  100. sub Nextion_Read($@);
  101. sub Nextion_Write($$$);
  102. sub Nextion_ReadAnswer($$);
  103. sub Nextion_Ready($);
  104. #########################
  105. # Globals
  106. my %Nextion_errCodes = (
  107. "\x00" => "Invalid instruction",
  108. "\x03" => "Page ID invalid",
  109. "\x04" => "Picture ID invalid",
  110. "\x05" => "Font ID invalid",
  111. "\x11" => "Baud rate setting invalid",
  112. "\x12" => "Curve control ID or channel number invalid",
  113. "\x1a" => "Variable name invalid",
  114. "\x1b" => "Variable operation invalid",
  115. "\x01" => "Success"
  116. );
  117. ##############################################################################
  118. ##############################################################################
  119. ##
  120. ## Module operation
  121. ##
  122. ##############################################################################
  123. ##############################################################################
  124. sub
  125. Nextion_Initialize($)
  126. {
  127. my ($hash) = @_;
  128. require "$attr{global}{modpath}/FHEM/DevIo.pm";
  129. # Provider
  130. $hash->{ReadFn} = "Nextion_Read";
  131. $hash->{WriteFn} = "Nextion_Write";
  132. $hash->{ReadyFn} = "Nextion_Ready";
  133. $hash->{UndefFn} = "Nextion_Undef";
  134. $hash->{ShutdownFn} = "Nextion_Undef";
  135. $hash->{ReadAnswerFn} = "Nextion_ReadAnswer";
  136. $hash->{AttrFn} = "Nextion_Attr";
  137. $hash->{AttrList} = "initPage0:textField-long initPage1:textField-long initPage2:textField-long initPage3:textField-long initPage4:textField-long ".
  138. "initPage5:textField-long initPage6:textField-long initPage7:textField-long initPage8:textField-long initPage9:textField-long ".
  139. "initCommands:textField-long hasSendMe:0,1 expectAnswer:1,0 ".$readingFnAttributes;
  140. # Normal devices
  141. $hash->{DefFn} = "Nextion_Define";
  142. $hash->{SetFn} = "Nextion_Set";
  143. }
  144. #####################################
  145. sub
  146. Nextion_Define($$)
  147. {
  148. my ($hash, $def) = @_;
  149. my @a = split("[ \t][ \t]*", $def);
  150. if(@a != 3) {
  151. return "wrong syntax: define <name> Nextion hostname:23";
  152. }
  153. my $name = $a[0];
  154. my $dev = $a[2];
  155. $hash->{Clients} = ":NEXTION:";
  156. my %matchList = ( "1:NEXTION" => ".*" );
  157. $hash->{MatchList} = \%matchList;
  158. DevIo_CloseDev($hash);
  159. $hash->{DeviceName} = $dev;
  160. return undef if($dev eq "none"); # DEBUGGING
  161. my $ret = DevIo_OpenDev($hash, 0, "Nextion_DoInit");
  162. return $ret;
  163. }
  164. #####################################
  165. sub
  166. Nextion_Set($@)
  167. {
  168. my ($hash, @a) = @_;
  169. my $name = shift @a;
  170. my %sets = ("cmd"=>"textField", "raw"=>"textField", "reopen"=>undef, "disconnect"=>undef
  171. , "pageCmd"=>"textField-long", "page"=>"0,1,2,3,4,5,6,7,8,9" );
  172. my $numberOfArgs = int(@a);
  173. return "set $name needs at least one parameter" if($numberOfArgs < 1);
  174. my $type = shift @a;
  175. $numberOfArgs--;
  176. my $ret = undef;
  177. return "Unknown argument $type, choose one of " . join(" ", sort keys %sets) if (!exists($sets{$type}));
  178. if( ($type eq "raw") || ($type eq "cmd") ) {
  179. my $cmd = join(" ", @a );
  180. $ret = Nextion_SendCommand($hash,$cmd, 1);
  181. } elsif($type eq "page") {
  182. if ( $numberOfArgs < 1 ) {
  183. $ret = "No page number given";
  184. } elsif ( $numberOfArgs > 1 ) {
  185. $ret = "Too many parameters (only page number shoudl be provided)";
  186. } elsif ( $a[0] !~ /^[0-9]$/ ) {
  187. $ret = "Page number needs to be a single digit";
  188. } else {
  189. $ret = Nextion_SendCommand($hash,"page ".$a[0], 1);
  190. }
  191. } elsif($type eq "pageCmd") {
  192. if ( $numberOfArgs < 2 ) {
  193. $ret = "No page number(s) or no commands given";
  194. } elsif ( $a[0] !~ /^[0-9](,[0-9])*$/ ) {
  195. $ret = "Page numbers needs to be single digits separated with ,";
  196. } elsif ( ! AttrVal($name,"hasSendMe",0) ) {
  197. $ret = "Attribute hasSendMe not set (no actual page)";
  198. } else {
  199. my @pages = split( /,/, shift @a);
  200. my $cpage = ReadingsVal($name,"currentPage",-1);
  201. my $cmd = join(" ", @a );
  202. foreach my $aPage ( @pages ) {
  203. if ( $aPage == $cpage ) {
  204. $ret = Nextion_SendCommand($hash,$cmd, 1);
  205. last;
  206. }
  207. }
  208. }
  209. } elsif($type eq "reopen") {
  210. DevIo_CloseDev($hash);
  211. delete $hash->{DevIoJustClosed} if($hash->{DevIoJustClosed});
  212. return DevIo_OpenDev($hash, 0, "Nextion_DoInit");
  213. } elsif($type eq "disconnect") {
  214. DevIo_CloseDev($hash);
  215. DevIo_setStates($hash, "disconnected");
  216. # DevIo_Disconnected($hash);
  217. # delete $hash->{DevIoJustClosed} if($hash->{DevIoJustClosed});
  218. }
  219. if ( ! defined( $ret ) ) {
  220. Log3 $name, 5, "Nextion_Set $name: $type done succesful: ";
  221. } else {
  222. Log3 $name, 1, "Nextion_Set $name: $type failed with :$ret: ";
  223. }
  224. return $ret;
  225. }
  226. ##############################
  227. # attr function for setting fhem attributes for the device
  228. sub Nextion_Attr(@) {
  229. my ($cmd,$name,$aName,$aVal) = @_;
  230. my $hash = $defs{$name};
  231. Log3 $name, 5, "Nextion_Attr $name: called ";
  232. return "\"Nextion_Attr: \" $name does not exist" if (!defined($hash));
  233. if (defined($aVal)) {
  234. Log3 $name, 5, "Nextion_Attr $name: $cmd on $aName to $aVal";
  235. } else {
  236. Log3 $name, 5, "Nextion_Attr $name: $cmd on $aName to <undef>";
  237. }
  238. # $cmd can be "del" or "set"
  239. # $name is device name
  240. # aName and aVal are Attribute name and value
  241. if ($cmd eq "set") {
  242. if ($aName eq 'hasSendMe') {
  243. $aVal = ($aVal eq "1")? "1": "0";
  244. readingsSingleUpdate($hash, "currentPage", -1, 1);
  245. } elsif ($aName eq 'unsupported') {
  246. if ( $aVal !~ /^[[:digit:]]+$/ ) {
  247. return "\"Nextion_Attr: \" unsupported";
  248. }
  249. }
  250. $_[3] = $aVal;
  251. }
  252. return undef;
  253. }
  254. #####################################
  255. sub
  256. Nextion_DoInit($)
  257. {
  258. my $hash = shift;
  259. my $name = $hash->{NAME};
  260. my $ret = undef;
  261. ### send init commands
  262. my $initCmds = AttrVal( $name, "initCommands", undef );
  263. Log3 $name, 3, "Nextion_DoInit $name: Execute initCommands :".(defined($initCmds)?$initCmds:"<undef>").":";
  264. ## ??? quick hack send on init always page 0 twice to ensure proper start
  265. # Send command handles replaceSetMagic and splitting
  266. $ret = Nextion_SendCommand( $hash, "page 0;page 0", 0 );
  267. # Send command handles replaceSetMagic and splitting
  268. $ret = Nextion_SendCommand( $hash, $initCmds, 0 ) if ( defined( $initCmds ) );
  269. return $ret;
  270. }
  271. #####################################
  272. sub
  273. Nextion_Undef($@)
  274. {
  275. my ($hash, $arg) = @_;
  276. ### ??? send finish commands
  277. DevIo_CloseDev($hash);
  278. return undef;
  279. }
  280. #####################################
  281. sub
  282. Nextion_Write($$$)
  283. {
  284. my ($hash,$fn,$msg) = @_;
  285. $msg = sprintf("%s03%04x%s%s", $fn, length($msg)/2+8,
  286. $hash->{HANDLE} ? $hash->{HANDLE} : "00000000", $msg);
  287. DevIo_SimpleWrite($hash, $msg, 1);
  288. }
  289. #####################################
  290. sub
  291. Nextion_SendCommand($$$)
  292. {
  293. my ($hash,$msg,$answer) = @_;
  294. my $name = $hash->{NAME};
  295. my @ret;
  296. Log3 $name, 1, "Nextion_SendCommand $name: send commands :".$msg.": ";
  297. # First replace any magics
  298. my %dummy;
  299. # my ($err, @a) = ReplaceSetMagic(\%dummy, 0, ( $msg ) );
  300. # if ( $err ) {
  301. # Log3 $name, 1, "$name: Nextion_SendCommand failed on ReplaceSetmagic with :$err: on commands :$msg:";
  302. # } else {
  303. # $msg = join(" ", @a);
  304. # Log3 $name, 4, "$name: Nextion_SendCommand ReplaceSetmagic commnds after :".$msg.":";
  305. # }
  306. # Split commands into separate elements at single semicolons (escape double ;; before)
  307. $msg =~ s/;;/SeMiCoLoN/g;
  308. my @msgList = split(";", $msg);
  309. my $singleMsg;
  310. while(defined($singleMsg = shift @msgList)) {
  311. $singleMsg =~ s/SeMiCoLoN/;/g;
  312. my ($err, @a) = ReplaceSetMagic(\%dummy, 0, ( $singleMsg ) );
  313. if ( $err ) {
  314. Log3 $name, 1, "$name: Nextion_SendCommand failed on ReplaceSetmagic with :$err: on commands :$singleMsg:";
  315. } else {
  316. $singleMsg = join(" ", @a);
  317. Log3 $name, 4, "$name: Nextion_SendCommand ReplaceSetmagic commnds after :".$singleMsg.":";
  318. }
  319. my $lret = Nextion_SendSingleCommand($hash, $singleMsg, $answer);
  320. push(@ret, $lret) if(defined($lret));
  321. }
  322. return join("\n", @ret) if(@ret);
  323. return undef;
  324. }
  325. #####################################
  326. sub
  327. Nextion_SendSingleCommand($$$)
  328. {
  329. my ($hash,$msg,$answer) = @_;
  330. my $name = $hash->{NAME};
  331. $answer = 0 if ( ! AttrVal($name,"expectAnswer",0) );
  332. # ??? handle answer
  333. my $err;
  334. # trim the msg
  335. $msg =~ s/^\s+|\s+$//g;
  336. Log3 $name, 4, "Nextion_SendCommand $name: send command :".$msg.": ";
  337. my $isoMsg = Nextion_EncodeToIso($msg);
  338. DevIo_SimpleWrite($hash, $isoMsg."\xff\xff\xff", 0);
  339. $err = Nextion_ReadAnswer($hash, $isoMsg) if ( $answer );
  340. Log3 $name, 1, "Nextion_SendCommand Error :".$err.": " if ( defined($err) );
  341. Log3 $name, 4, "Nextion_SendCommand Success " if ( ! defined($err) );
  342. # Also set sentMsg Id and result in Readings
  343. readingsBeginUpdate($hash);
  344. readingsBulkUpdate($hash, "cmdSent", $msg);
  345. readingsBulkUpdate($hash, "cmdResult", (( defined($err))?$err:"empty") );
  346. readingsEndUpdate($hash, 1);
  347. return $err;
  348. }
  349. #####################################
  350. # called from the global loop, when the select for hash->{FD} reports data
  351. sub
  352. Nextion_Read($@)
  353. {
  354. my ($hash, $local, $isCmd) = @_;
  355. my $buf = ($local ? $local : DevIo_SimpleRead($hash));
  356. return "" if(!defined($buf));
  357. my $name = $hash->{NAME};
  358. ### $buf = unpack('H*', $buf);
  359. my $data = ($hash->{PARTIAL} ? $hash->{PARTIAL} : "");
  360. # drop old data
  361. if($data) {
  362. $data = "" if(gettimeofday() - $hash->{READ_TS} > 5);
  363. delete($hash->{READ_TS});
  364. }
  365. Log3 $name, 5, "Nextion/RAW: $data/$buf";
  366. $data .= $buf;
  367. my $ret;
  368. my $newPageId;
  369. while(length($data) > 0) {
  370. if ( $data =~ /^([^\xff]*)\xff\xff\xff(.*)$/ ) {
  371. my $rcvd = $1;
  372. $data = $2;
  373. if ( length($rcvd) > 0 ) {
  374. my ( $msg, $text, $val, $id ) = Nextion_convertMsg($rcvd);
  375. if ( defined( $id ) ) {
  376. if ( $id =~ /^[0-9]+$/ ) {
  377. $newPageId = $id;
  378. }
  379. }
  380. Log3 $name, 4, "Nextion: Received message :$msg:";
  381. if ( defined( ReadingsVal($name,"received",undef) ) ) {
  382. if ( defined( ReadingsVal($name,"old1",undef) ) ) {
  383. if ( defined( ReadingsVal($name,"old2",undef) ) ) {
  384. if ( defined( ReadingsVal($name,"old3",undef) ) ) {
  385. if ( defined( ReadingsVal($name,"old4",undef) ) ) {
  386. $hash->{READINGS}{old5}{VAL} = $hash->{READINGS}{old4}{VAL};
  387. $hash->{READINGS}{old5}{TIME} = $hash->{READINGS}{old4}{TIME};
  388. }
  389. $hash->{READINGS}{old4}{VAL} = $hash->{READINGS}{old3}{VAL};
  390. $hash->{READINGS}{old4}{TIME} = $hash->{READINGS}{old3}{TIME};
  391. }
  392. $hash->{READINGS}{old3}{VAL} = $hash->{READINGS}{old2}{VAL};
  393. $hash->{READINGS}{old3}{TIME} = $hash->{READINGS}{old2}{TIME};
  394. }
  395. $hash->{READINGS}{old2}{VAL} = $hash->{READINGS}{old1}{VAL};
  396. $hash->{READINGS}{old2}{TIME} = $hash->{READINGS}{old1}{TIME};
  397. }
  398. $hash->{READINGS}{old1}{VAL} = $hash->{READINGS}{received}{VAL};
  399. $hash->{READINGS}{old1}{TIME} = $hash->{READINGS}{received}{TIME};
  400. }
  401. readingsBeginUpdate($hash);
  402. readingsBulkUpdate($hash,"received",$msg);
  403. readingsBulkUpdate($hash,"rectext",( (defined($text)) ? $text : "" ));
  404. readingsBulkUpdate($hash,"currentPage",$id) if ( ( defined( $id ) ) && ( AttrVal($name,"hasSendMe",0) ) );
  405. readingsEndUpdate($hash, 1);
  406. }
  407. } else {
  408. last;
  409. }
  410. }
  411. $hash->{PARTIAL} = $data;
  412. $hash->{READ_TS} = gettimeofday() if($data);
  413. # initialize last page id found:
  414. if ( defined( $newPageId ) ) {
  415. $newPageId = $newPageId + 0;
  416. my $initCmds = AttrVal( $name, "initPage".sprintf("%d",$newPageId), undef );
  417. Log3 $name, 4, "Nextion_InitPage $name: page :".$newPageId.": with commands :".(defined($initCmds)?$initCmds:"<undef>").":";
  418. return if ( ! defined( $initCmds ) );
  419. # Send command handles replaceSetMagic and splitting
  420. Nextion_SendCommand( $hash, $initCmds, 0 );
  421. }
  422. return $ret if(defined($local));
  423. return undef;
  424. }
  425. #####################################
  426. # This is a direct read for command results
  427. sub
  428. Nextion_ReadAnswer($$)
  429. {
  430. my ($hash, $arg) = @_;
  431. my $name = $hash->{NAME};
  432. Log3 $name, 4, "Nextion_ReadAnswer $name: for send commands :".$arg.": ";
  433. return "No FD (dummy device?)" if(!$hash || ($^O !~ /Win/ && !defined($hash->{FD})));
  434. my $data = "";
  435. for(;;) {
  436. return "Device lost when reading answer for get $arg" if(!$hash->{FD});
  437. my $rin = '';
  438. vec($rin, $hash->{FD}, 1) = 1;
  439. my $nfound = select($rin, undef, undef, 1);
  440. if($nfound < 0) {
  441. next if ($! == EAGAIN() || $! == EINTR() || $! == 0);
  442. my $err = $!;
  443. DevIo_Disconnected($hash);
  444. return"Nextion_ReadAnswer $arg: $err";
  445. }
  446. return "Timeout reading answer for get $arg" if($nfound == 0);
  447. my $buf = DevIo_SimpleRead($hash);
  448. return "No data" if(!defined($buf));
  449. my $ret;
  450. my $data .= $buf;
  451. # not yet long enough select again
  452. # next if ( length($data) < 4 );
  453. # TODO: might have to check for remaining data in buffer?
  454. if ( $data =~ /^\xff*([^\xff])\xff\xff\xff(.*)$/ ) {
  455. my $rcvd = $1;
  456. $data = $2;
  457. $ret = $Nextion_errCodes{$rcvd};
  458. $ret = "Nextion: Unknown error with code ".sprintf( "H%2.2x", $rcvd ) if ( ! defined( $ret ) );
  459. } elsif ( length($data) == 0 ) {
  460. $ret = "No answer";
  461. } else {
  462. $ret = "Message received";
  463. }
  464. # read rest of buffer direct in read function
  465. if ( length($data) > 0 ) {
  466. Nextion_Read($hash, $data);
  467. }
  468. return (($ret eq $Nextion_errCodes{"\x01"}) ? undef : $ret);
  469. }
  470. }
  471. #####################################
  472. sub
  473. Nextion_Ready($)
  474. {
  475. my ($hash) = @_;
  476. return DevIo_OpenDev($hash, 1, "Nextion_DoInit")
  477. if($hash->{STATE} eq "disconnected");
  478. return 0;
  479. }
  480. ##############################################################################
  481. ##############################################################################
  482. ##
  483. ## Helper
  484. ##
  485. ##############################################################################
  486. ##############################################################################
  487. #####################################
  488. # returns
  489. # msg in Hex converted format
  490. # text equivalent of message if applicable
  491. # val in message either numeric or text
  492. # id of a control <pageid>:<controlid>:... or just a page <pageid> or undef
  493. sub
  494. Nextion_convertMsg($)
  495. {
  496. my ($raw) = @_;
  497. my $msg = "";
  498. my $text;
  499. my $val;
  500. my $id;
  501. my $rcvd = $raw;
  502. while(length($rcvd) > 0) {
  503. my $char = ord($rcvd);
  504. $rcvd = substr($rcvd,1);
  505. $msg .= " " if ( length($msg) > 0 );
  506. $msg .= sprintf( "H%2.2x", $char );
  507. $msg .= "(".chr($char).")" if ( ( $char >= 32 ) && ( $char <= 127 ) ) ;
  508. }
  509. if ( $raw =~ /^(\$.*=)(\x71?)(.*)$/s ) {
  510. # raw msg with $ start is manually defined message standard
  511. # sent on release event
  512. # print "$bt0="
  513. # get bt0.val OR print bt0.val
  514. #
  515. $text = $1;
  516. my $rest = $3;
  517. if ( length($rest) == 4 ) {
  518. $val = ord($rest);
  519. $rest = substr($rest,1);
  520. $val += ord($rest)*256;
  521. $rest = substr($rest,1);
  522. $val += ord($rest)*256*256;
  523. $rest = substr($rest,1);
  524. $val += ord($rest)*256*256*256;
  525. $text .= sprintf("%d",$val);
  526. } else {
  527. $text .= $rest;
  528. $val = $rest;
  529. }
  530. } elsif ( $raw =~ /^\x70(.*)$/s ) {
  531. # string return
  532. $val = $1;
  533. # Log3 undef, 1, "Nextion_convertMsg String message val :".$val.": ";
  534. $text = "string \"" . $val . "\"";
  535. } elsif ( $raw =~ /^\x71(.*)$/ ) {
  536. # numeric return
  537. $text = "num ";
  538. my $rest = $1;
  539. if ( length($rest) == 4 ) {
  540. $val = ord($rest);
  541. $rest = substr($rest,1);
  542. $val += ord($rest)*256;
  543. $rest = substr($rest,1);
  544. $val += ord($rest)*256*256;
  545. $rest = substr($rest,1);
  546. $val += ord($rest)*256*256*256;
  547. $text .= sprintf("%d",$val);
  548. } else {
  549. $text .= $rest;
  550. $val = $rest;
  551. }
  552. } elsif ( $raw =~ /^\x66(.)$/ ) {
  553. # page started
  554. $text = "page ";
  555. my $rest = $1;
  556. $id = ord($rest);
  557. $text .= sprintf("%d",$id);
  558. }
  559. $text = Nextion_DecodeFromIso( $text );
  560. $msg = Nextion_DecodeFromIso( $msg );
  561. return ( $msg, $text, $val, $id );
  562. }
  563. #####################################
  564. sub
  565. Nextion_EncodeToIso($)
  566. {
  567. my ($orgmsg) = @_;
  568. # encode in ISO8859-1 from UTF8
  569. # decode to Perl's internal format
  570. my $msg = decode( 'utf-8', $orgmsg );
  571. # encode to iso-8859-1
  572. $msg = encode( 'iso-8859-1', $msg );
  573. return $msg;
  574. }
  575. #####################################
  576. sub
  577. Nextion_DecodeFromIso($)
  578. {
  579. my ($orgmsg) = @_;
  580. # encode in ISO8859-1 from UTF8
  581. # decode to Perl's internal format
  582. my $msg = decode( 'iso-8859-1', $orgmsg );
  583. # encode to iso-8859-1
  584. $msg = encode( 'utf-8', $msg );
  585. return $msg;
  586. }
  587. ##################################################################################################################
  588. ##################################################################################################################
  589. ##################################################################################################################
  590. 1;
  591. =pod
  592. =item summary interact with Nextion touch displays
  593. =item summary_DE interagiert mit Nextion Touchscreens
  594. =begin html
  595. <a name="Nextion"></a>
  596. <h3>Nextion</h3>
  597. <ul>
  598. This module connects remotely to a Nextion display that is connected through a ESP8266 or similar serial to network connection
  599. <a href="http://wiki.iteadstudio.com/Nextion_HMI_Solution">Nextion</a> devices are relatively inexpensive tft touch displays, that include also a controller that can hold a user interface and communicates via serial protocol to the outside world.
  600. <br>
  601. A description of the Hardwarelayout for connecting the ESP8266 module and the Nextion Dispaly is in the correspdong forum thread <a href="https://forum.fhem.de/index.php/topic,51267.0.html">https://forum.fhem.de/index.php/topic,51267.0.html</a>.
  602. <br><br>
  603. <a name="Nextiondefine"></a>
  604. <b>Define</b>
  605. <ul>
  606. <code>define &lt;name&gt; Nextion &lt;hostname/ip&gt;:23 </code>
  607. <br><br>
  608. Defines a Nextion device on the given hostname / ip and port (should be port 23/telnetport normally)
  609. <br><br>
  610. Example: <code>define nxt Nextion 10.0.0.1:23</code><br>
  611. <br>
  612. </ul>
  613. <br><br>
  614. <a name="Nextionset"></a>
  615. <b>Set</b>
  616. <ul>
  617. <code>set &lt;name&gt; &lt;what&gt; [&lt;value&gt;]</code>
  618. <br><br>
  619. where &lt;what&gt; / &lt;value&gt; is one of
  620. <br><br>
  621. <li><code>raw &lt;nextion command&gt;</code><br>Sends the given raw message to the nextion display. The supported commands are described with the Nextion displays: <a href="http://wiki.iteadstudio.com/Nextion_Instruction_Set">http://wiki.iteadstudio.com/Nextion_Instruction_Set</a>
  622. <br>
  623. Examples:<br>
  624. <dl>
  625. <dt><code>set nxt raw page 0</code></dt>
  626. <dd> switch the display to page 0 <br> </dd>
  627. <dt><code>set nxt raw b0.txt</code></dt>
  628. <dd> get the text for button 0 <br> </dd>
  629. <dl>
  630. </li>
  631. <li><code>cmd &lt;nextion command&gt;</code><br>same as raw
  632. </li>
  633. <li><code>page &lt;0 - 9&gt;</code><br>set the page number given as new page on the nextion display.
  634. </li>
  635. <li><code>pageCmd &lt;one or multiple page numbers separated by ,&gt; &lt;cmds&gt;</code><br>Execute the given commands if the current page on the screen is in the list given as page number.
  636. </li>
  637. </ul>
  638. <br><br>
  639. <a name="Nextionattr"></a>
  640. <b>Attributes</b>
  641. <br><br>
  642. <ul>
  643. <li><code>hasSendMe &lt;0 or 1&gt;</code><br>Specify if the display definition on the Nextion display is using the "send me" checkbox to send current page on page changes. This will then change the reading currentPage accordingly
  644. </li>
  645. <li><code>initCommands &lt;series of commands&gt;</code><br>Display will be initialized with these commands when the connection to the device is established (or reconnected). Set logic for executing perl or getting readings can be used. Multiple commands will be separated by ;<br>
  646. Example<br>
  647. &nbsp;&nbsp;<code>t1.txt="Hallo";p1.val=1;</code>
  648. </li>
  649. <li><code>initPage1 &lt;series of commands&gt;</code> to <code>initPage9 &lt;series of commands&gt;</code><br>When the corresponding page number will be displayed the given commands will be sent to the display. See also initCommands.<br>
  650. Example<br>
  651. &nbsp;&nbsp;<code>t1.txt="Hallo";p1.val=1;</code>
  652. </li>
  653. <li><code>expectAnswer &lt;1 or 0&gt;</code><br>Specify if an answer from display is expected. If set to zero no answer is expected at any time on a command.
  654. </li>
  655. </ul>
  656. <br><br>
  657. <a name="Nextionreadings"></a>
  658. <b>Readings</b>
  659. <ul>
  660. <li><code>received &lt;Hex values of the last received message from the display&gt;</code><br> The message is converted in hex values (old messages are stored in the readings old1 ... old5). Example for a message is <code>H65(e) H00 H04 H00</code> </li>
  661. <li><code>rectext &lt;text or empty&gt;</code><br> Translating the received message into text form if possible. Beside predefined data that is sent from the display on specific changes, custom values can be sent in the form <code>$name=value</code>. This can be sent by statements in the Nextion display event code <br>
  662. <code>print "$bt0="<br>
  663. get bt0.val</code>
  664. </li>
  665. <li><code>currentPage &lt;page number on the display&gt;</code><br> Shows the number of the UI screen as configured on the Nextion display that is currently shown.<br>This is only valid if the attribute <code>hasSendMe</code> is set to 1 and used also in the display definition of the Nextion.</li>
  666. <li><code>cmdSent &lt;cmd&gt;</code><br> Last cmd sent to the Nextion Display </li>
  667. <li><code>cmdResult &lt;result text&gt;</code><br> Result of the last cmd sent to the display (or empty)</li>
  668. </ul>
  669. <br><br>
  670. </ul>
  671. =end html
  672. =cut