42_Nextion.pm 29 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901902903904905906907908909910911912913914915916917918919920921922923924925926927928929930931932933934935936937938939940941942943944945946947948949950951952953954955956957958959960961962963964965
  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 14371 2017-05-25 16:12:04Z 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. # timeout on sec 1 (not 0.5) - msg554933
  56. # disabled attribute and change in connections - msg554933
  57. #
  58. # 0.8 2016-03-01 revert changes
  59. # fix for page 10 not recognized : #msg592948
  60. # _connect/Disconnect/isConnected subs
  61. # init device after notify on initialized
  62. # fix connection - to work also if nextion is unavailable
  63. #
  64. #
  65. # Extended log for read function
  66. # remove leading ff
  67. # fault tolerant command reader allow empty commands and missing \xff
  68. # print filtered messages - with quoted chars
  69. # changed log levels to 4 for verbose / 5 will print all messages
  70. # fix replacesetmagic to ensure device hash is given
  71. # 2016-05-25 fault tolerance in reader / fixes
  72. #
  73. #
  74. #
  75. ##############################################
  76. ##############################################
  77. ### TODO
  78. #
  79. #
  80. # rectextold1-5 --> #msg611695
  81. # timeout with checkalive check?
  82. #
  83. # react on events with commands allowing values from FHEM
  84. # remove wait for answer by attribute
  85. # number of pages as define (std max 0-9)
  86. #
  87. ##############################################
  88. ##############################################
  89. ### Considerations for the Nextion UI
  90. #
  91. # - sendme is needed on pages otherwise initCmds can not be used
  92. # - to react on button usages $ cmds are introduced
  93. # add in postinitialize event something like
  94. # print "$b0="
  95. # get 1
  96. # or for double state buttons / switches
  97. # print "$bt0="
  98. # print bt0.val#
  99. # will result in $b0=1 / $bt0=0 or $bt0=1 being sent to fhem
  100. # - 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)
  101. #
  102. #
  103. #
  104. ##############################################
  105. ##############################################
  106. ##############################################
  107. package main;
  108. use strict;
  109. use warnings;
  110. use Time::HiRes qw(gettimeofday);
  111. use Encode qw( decode encode );
  112. use Data::Dumper;
  113. #########################
  114. # Forward declaration
  115. sub Nextion_Read($@);
  116. sub Nextion_Write($$$);
  117. sub Nextion_ReadAnswer($$);
  118. sub Nextion_Ready($);
  119. #########################
  120. # Globals
  121. my %Nextion_errCodes = (
  122. "\x00" => "Invalid instruction",
  123. "\x03" => "Page ID invalid",
  124. "\x04" => "Picture ID invalid",
  125. "\x05" => "Font ID invalid",
  126. "\x11" => "Baud rate setting invalid",
  127. "\x12" => "Curve control ID or channel number invalid",
  128. "\x1a" => "Variable name invalid",
  129. "\x1b" => "Variable operation invalid",
  130. "\x01" => "Success"
  131. );
  132. ##############################################################################
  133. ##############################################################################
  134. ##
  135. ## Module operation
  136. ##
  137. ##############################################################################
  138. ##############################################################################
  139. sub
  140. Nextion_Initialize($)
  141. {
  142. my ($hash) = @_;
  143. require "$attr{global}{modpath}/FHEM/DevIo.pm";
  144. # Provider
  145. $hash->{ReadFn} = "Nextion_Read";
  146. $hash->{WriteFn} = "Nextion_Write";
  147. $hash->{ReadyFn} = "Nextion_Ready";
  148. $hash->{UndefFn} = "Nextion_Undef";
  149. $hash->{ShutdownFn} = "Nextion_Undef";
  150. $hash->{ReadAnswerFn} = "Nextion_ReadAnswer";
  151. $hash->{NotifyFn} = "Nextion_Notify";
  152. $hash->{AttrFn} = "Nextion_Attr";
  153. $hash->{AttrList} = "initPage0:textField-long initPage1:textField-long initPage2:textField-long initPage3:textField-long initPage4:textField-long ".
  154. "initPage5:textField-long initPage6:textField-long initPage7:textField-long initPage8:textField-long initPage9:textField-long ".
  155. "initCommands:textField-long hasSendMe:0,1 expectAnswer:1,0 disable:0,1 ".$readingFnAttributes;
  156. # timeout for connections - msg554933
  157. $hash->{TIMEOUT} = 1; # might be better? 0.5;
  158. # Normal devices
  159. $hash->{DefFn} = "Nextion_Define";
  160. $hash->{SetFn} = "Nextion_Set";
  161. }
  162. #####################################
  163. sub
  164. Nextion_Define($$)
  165. {
  166. my ($hash, $def) = @_;
  167. my @a = split("[ \t][ \t]*", $def);
  168. if(@a != 3) {
  169. return "wrong syntax: define <name> Nextion hostname:23";
  170. }
  171. my $name = $a[0];
  172. my $dev = $a[2];
  173. $hash->{Clients} = ":NEXTION:";
  174. my %matchList = ( "1:NEXTION" => ".*" );
  175. $hash->{MatchList} = \%matchList;
  176. Nextion_Disconnect($hash);
  177. $hash->{DeviceName} = $dev;
  178. return undef if($dev eq "none"); # DEBUGGING
  179. my $ret;
  180. if( $init_done ) {
  181. Nextion_Disconnect($hash);
  182. $ret = Nextion_Connect($hash);
  183. } elsif( $hash->{STATE} ne "???" ) {
  184. $hash->{STATE} = "Initialized";
  185. }
  186. return $ret;
  187. }
  188. #####################################
  189. sub
  190. Nextion_Set($@)
  191. {
  192. my ($hash, @a) = @_;
  193. my $name = shift @a;
  194. my %sets = ("cmd"=>"textField", "raw"=>"textField", "reopen"=>undef, "disconnect"=>undef
  195. , "pageCmd"=>"textField-long", "page"=>"0,1,2,3,4,5,6,7,8,9" );
  196. my $numberOfArgs = int(@a);
  197. return "set $name needs at least one parameter" if($numberOfArgs < 1);
  198. my $type = shift @a;
  199. $numberOfArgs--;
  200. my $ret = undef;
  201. return "Unknown argument $type, choose one of " . join(" ", sort keys %sets) if (!exists($sets{$type}));
  202. if( ($type eq "raw") || ($type eq "cmd") ) {
  203. my $cmd = join(" ", @a );
  204. $ret = Nextion_SendCommand($hash,$cmd, 1);
  205. } elsif($type eq "page") {
  206. if ( $numberOfArgs < 1 ) {
  207. $ret = "No page number given";
  208. } elsif ( $numberOfArgs > 1 ) {
  209. $ret = "Too many parameters (only page number shoudl be provided)";
  210. } elsif ( $a[0] !~ /^[0-9]$/ ) {
  211. $ret = "Page number needs to be a single digit";
  212. } else {
  213. $ret = Nextion_SendCommand($hash,"page ".$a[0], 1);
  214. }
  215. } elsif($type eq "pageCmd") {
  216. if ( $numberOfArgs < 2 ) {
  217. $ret = "No page number(s) or no commands given";
  218. } elsif ( $a[0] !~ /^[0-9](,[0-9])*$/ ) {
  219. $ret = "Page numbers needs to be single digits separated with ,";
  220. } elsif ( ! AttrVal($name,"hasSendMe",0) ) {
  221. $ret = "Attribute hasSendMe not set (no actual page)";
  222. } else {
  223. my @pages = split( /,/, shift @a);
  224. my $cpage = ReadingsVal($name,"currentPage",-1);
  225. my $cmd = join(" ", @a );
  226. foreach my $aPage ( @pages ) {
  227. if ( $aPage == $cpage ) {
  228. $ret = Nextion_SendCommand($hash,$cmd, 1);
  229. last;
  230. }
  231. }
  232. }
  233. } elsif($type eq "reopen") {
  234. Nextion_Disconnect($hash);
  235. delete $hash->{DevIoJustClosed} if($hash->{DevIoJustClosed});
  236. delete($hash->{NEXT_OPEN}); # needed ? - can this ever occur
  237. return Nextion_Connect( $hash, 1 );
  238. } elsif($type eq "disconnect") {
  239. Nextion_Disconnect($hash);
  240. DevIo_setStates($hash, "disconnected");
  241. # DevIo_Disconnected($hash);
  242. # delete $hash->{DevIoJustClosed} if($hash->{DevIoJustClosed});
  243. }
  244. if ( ! defined( $ret ) ) {
  245. Log3 $name, 4, "Nextion_Set $name: $type done succesful: ";
  246. } else {
  247. Log3 $name, 1, "Nextion_Set $name: $type failed with :$ret: ";
  248. }
  249. return $ret;
  250. }
  251. ##############################
  252. # attr function for setting fhem attributes for the device
  253. sub Nextion_Attr(@) {
  254. my ($cmd,$name,$aName,$aVal) = @_;
  255. my $hash = $defs{$name};
  256. Log3 $name, 4, "Nextion_Attr $name: called ";
  257. return "\"Nextion_Attr: \" $name does not exist" if (!defined($hash));
  258. if (defined($aVal)) {
  259. Log3 $name, 4, "Nextion_Attr $name: $cmd on $aName to $aVal";
  260. } else {
  261. Log3 $name, 4, "Nextion_Attr $name: $cmd on $aName to <undef>";
  262. }
  263. # $cmd can be "del" or "set"
  264. # $name is device name
  265. # aName and aVal are Attribute name and value
  266. if ($cmd eq "set") {
  267. if ($aName eq 'hasSendMe') {
  268. $aVal = ($aVal eq "1")? "1": "0";
  269. readingsSingleUpdate($hash, "currentPage", -1, 1);
  270. } elsif ($aName eq 'unsupported') {
  271. if ( $aVal !~ /^[[:digit:]]+$/ ) {
  272. return "\"Nextion_Attr: \" unsupported";
  273. }
  274. } elsif ($aName eq 'disable') {
  275. if($aVal eq "1") {
  276. Nextion_Disconnect($hash);
  277. DevIo_setStates($hash, "disabled");
  278. } else {
  279. if($hash->{READINGS}{state}{VAL} eq "disabled") {
  280. DevIo_setStates($hash, "disconnected");
  281. InternalTimer(gettimeofday()+1, "Nextion_Connect", $hash, 0);
  282. }
  283. }
  284. }
  285. $_[3] = $aVal;
  286. }
  287. return undef;
  288. }
  289. ######################################
  290. sub Nextion_IsConnected($)
  291. {
  292. my $hash = shift;
  293. # stacktrace();
  294. # Debug "Name : ".$hash->{NAME};
  295. # Debug "FD: ".((exists($hash->{FD}))?"def":"undef");
  296. # Debug "TCPDev: ".((defined($hash->{TCPDev}))?"def":"undef");
  297. return 0 if(!exists($hash->{FD}));
  298. if(!defined($hash->{TCPDev})) {
  299. Nextion_Disconnect($_[0]);
  300. return 0;
  301. }
  302. return 1;
  303. }
  304. ######################################
  305. sub Nextion_Disconnect($)
  306. {
  307. my $hash = shift;
  308. my $name = $hash->{NAME};
  309. Log3 $name, 4, "Nextion_Disconnect: $name";
  310. DevIo_CloseDev($hash);
  311. }
  312. ######################################
  313. sub Nextion_Connect($;$) {
  314. my ($hash, $mode) = @_;
  315. my $name = $hash->{NAME};
  316. my $ret;
  317. $mode = 0 if!($mode);
  318. return undef if(Nextion_IsConnected($hash));
  319. # Debug "NEXT_OPEN: $name".((defined($hash->{NEXT_OPEN}))?time()-$hash->{NEXT_OPEN}:"undef");
  320. if(!IsDisabled($name)) {
  321. # undefined means timeout / 0 means failed / 1 means ok
  322. if ( DevIo_OpenDev($hash, $mode, "Nextion_DoInit") ) {
  323. if(!Nextion_IsConnected($hash)) {
  324. $ret = "Nextion_Connect: Could not connect :".$name;
  325. Log3 $hash, 2, $ret;
  326. }
  327. }
  328. }
  329. return $ret;
  330. }
  331. #####################################
  332. sub
  333. Nextion_Notify($$)
  334. {
  335. my ($hash,$dev) = @_;
  336. my $name = $hash->{NAME};
  337. my $type = $hash->{TYPE};
  338. return if($dev->{NAME} ne "global");
  339. return if(!grep(m/^INITIALIZED|REREADCFG$/, @{$dev->{CHANGED}}));
  340. if( IsDisabled($name) > 0 ) {
  341. readingsSingleUpdate($hash, 'state', 'disabled', 1 ) if( ReadingsVal($name,'state','' ) ne 'disabled' );
  342. return undef;
  343. }
  344. Nextion_Connect($hash);
  345. return undef;
  346. }
  347. #####################################
  348. sub
  349. Nextion_DoInit($)
  350. {
  351. my $hash = shift;
  352. my $name = $hash->{NAME};
  353. my $ret = undef;
  354. ### send init commands
  355. my $initCmds = AttrVal( $name, "initCommands", undef );
  356. Log3 $name, 3, "Nextion_DoInit $name: Execute initCommands :".(defined($initCmds)?$initCmds:"<undef>").":";
  357. ## ??? quick hack send on init always page 0 twice to ensure proper start
  358. # Send command handles replaceSetMagic and splitting
  359. $ret = Nextion_SendCommand( $hash, "page 0;page 0", 0 );
  360. # Send command handles replaceSetMagic and splitting
  361. $ret = Nextion_SendCommand( $hash, $initCmds, 0 ) if ( defined( $initCmds ) );
  362. return $ret;
  363. }
  364. #####################################
  365. sub
  366. Nextion_Undef($@)
  367. {
  368. my ($hash, $arg) = @_;
  369. ### ??? send finish commands
  370. Nextion_Disconnect($hash);
  371. return undef;
  372. }
  373. #####################################
  374. sub
  375. Nextion_Write($$$)
  376. {
  377. my ($hash,$fn,$msg) = @_;
  378. $msg = sprintf("%s03%04x%s%s", $fn, length($msg)/2+8,
  379. $hash->{HANDLE} ? $hash->{HANDLE} : "00000000", $msg);
  380. DevIo_SimpleWrite($hash, $msg, 1);
  381. }
  382. #####################################
  383. sub
  384. Nextion_SendCommand($$$)
  385. {
  386. my ($hash,$msg,$answer) = @_;
  387. my $name = $hash->{NAME};
  388. my @ret;
  389. Log3 $name, 4, "Nextion_SendCommand $name: send commands :".$msg.": ";
  390. # First replace any magics
  391. # my %dummy;
  392. # my ($err, @a) = ReplaceSetMagic(\%dummy, 0, ( $msg ) );
  393. # if ( $err ) {
  394. # Log3 $name, 1, "$name: Nextion_SendCommand failed on ReplaceSetmagic with :$err: on commands :$msg:";
  395. # } else {
  396. # $msg = join(" ", @a);
  397. # Log3 $name, 4, "$name: Nextion_SendCommand ReplaceSetmagic commnds after :".$msg.":";
  398. # }
  399. # Split commands into separate elements at single semicolons (escape double ;; before)
  400. $msg =~ s/;;/SeMiCoLoN/g;
  401. my @msgList = split(";", $msg);
  402. my $singleMsg;
  403. while(defined($singleMsg = shift @msgList)) {
  404. $singleMsg =~ s/SeMiCoLoN/;/g;
  405. my ($err, @a) = ReplaceSetMagic($hash, 0, ( $singleMsg ) );
  406. if ( $err ) {
  407. Log3 $name, 1, "$name: Nextion_SendCommand failed on ReplaceSetmagic with :$err: on commands :$singleMsg:";
  408. } else {
  409. $singleMsg = join(" ", @a);
  410. Log3 $name, 4, "$name: Nextion_SendCommand ReplaceSetmagic commnds after :".$singleMsg.":";
  411. }
  412. my $lret = Nextion_SendSingleCommand($hash, $singleMsg, $answer);
  413. push(@ret, $lret) if(defined($lret));
  414. }
  415. return join("\n", @ret) if(@ret);
  416. return undef;
  417. }
  418. #####################################
  419. sub
  420. Nextion_SendSingleCommand($$$)
  421. {
  422. my ($hash,$msg,$answer) = @_;
  423. my $name = $hash->{NAME};
  424. $answer = 0 if ( ! AttrVal($name,"expectAnswer",0) );
  425. # ??? handle answer
  426. my $err;
  427. # trim the msg
  428. $msg =~ s/^\s+|\s+$//g;
  429. Log3 $name, 4, "Nextion_SendCommand $name: send command :".$msg.": ";
  430. my $isoMsg = Nextion_EncodeToIso($msg);
  431. DevIo_SimpleWrite($hash, $isoMsg."\xff\xff\xff", 0);
  432. $err = Nextion_ReadAnswer($hash, $isoMsg) if ( $answer );
  433. Log3 $name, 1, "Nextion_SendCommand Error :".$err.": " if ( defined($err) );
  434. Log3 $name, 4, "Nextion_SendCommand Success " if ( ! defined($err) );
  435. # Also set sentMsg Id and result in Readings
  436. readingsBeginUpdate($hash);
  437. readingsBulkUpdate($hash, "cmdSent", $msg);
  438. readingsBulkUpdate($hash, "cmdResult", (( defined($err))?$err:"empty") );
  439. readingsEndUpdate($hash, 1);
  440. return $err;
  441. }
  442. #####################################
  443. # called from the global loop, when the select for hash->{FD} reports data
  444. sub
  445. Nextion_Read($@)
  446. {
  447. my ($hash, $local, $isCmd) = @_;
  448. my $buf = ($local ? $local : DevIo_SimpleRead($hash));
  449. return "" if(!defined($buf));
  450. my $name = $hash->{NAME};
  451. ### $buf = unpack('H*', $buf);
  452. my $data = ($hash->{PARTIAL} ? $hash->{PARTIAL} : "");
  453. # drop old data
  454. if($data) {
  455. $data = "" if(gettimeofday() - $hash->{READ_TS} > 5);
  456. delete($hash->{READ_TS});
  457. }
  458. Log3 $name, 5, "Nextion/RAW: $data/$buf";
  459. $data .= $buf;
  460. my $ret;
  461. my $newPageId;
  462. while(length($data) > 0) {
  463. # if ( $data =~ /^([^\xff]*)\xff\xff\xff(.*)$/ ) {
  464. if ( $data =~ /^([^\xff]*)(\xff+)([^\xff].*)?$/ ) {
  465. my $rcvd = $1;
  466. my $ffpart = $2;
  467. $data = $3;
  468. $data = "" if ( ! defined($data) );
  469. if ( length($ffpart) != 3 ) {
  470. Log3 $name, 4, "Nextion/RAW: shortened ffh end sequence (".length($ffpart).") ".Data::Dumper::qquote($rcvd) ;
  471. } else {
  472. Log3 $name, 5, "Nextion/RAW: message found ".Data::Dumper::qquote($rcvd) ;
  473. }
  474. if ( length($rcvd) > 0 ) {
  475. my ( $msg, $text, $val, $id ) = Nextion_convertMsg($rcvd);
  476. if ( defined( $id ) ) {
  477. if ( $id =~ /^[0-9]+$/ ) {
  478. $newPageId = $id;
  479. }
  480. }
  481. Log3 $name, 4, "Nextion: Received message :$msg:";
  482. if ( defined( ReadingsVal($name,"received",undef) ) ) {
  483. if ( defined( ReadingsVal($name,"old1",undef) ) ) {
  484. if ( defined( ReadingsVal($name,"old2",undef) ) ) {
  485. if ( defined( ReadingsVal($name,"old3",undef) ) ) {
  486. if ( defined( ReadingsVal($name,"old4",undef) ) ) {
  487. $hash->{READINGS}{old5}{VAL} = $hash->{READINGS}{old4}{VAL};
  488. $hash->{READINGS}{old5}{TIME} = $hash->{READINGS}{old4}{TIME};
  489. }
  490. $hash->{READINGS}{old4}{VAL} = $hash->{READINGS}{old3}{VAL};
  491. $hash->{READINGS}{old4}{TIME} = $hash->{READINGS}{old3}{TIME};
  492. }
  493. $hash->{READINGS}{old3}{VAL} = $hash->{READINGS}{old2}{VAL};
  494. $hash->{READINGS}{old3}{TIME} = $hash->{READINGS}{old2}{TIME};
  495. }
  496. $hash->{READINGS}{old2}{VAL} = $hash->{READINGS}{old1}{VAL};
  497. $hash->{READINGS}{old2}{TIME} = $hash->{READINGS}{old1}{TIME};
  498. }
  499. $hash->{READINGS}{old1}{VAL} = $hash->{READINGS}{received}{VAL};
  500. $hash->{READINGS}{old1}{TIME} = $hash->{READINGS}{received}{TIME};
  501. }
  502. readingsBeginUpdate($hash);
  503. readingsBulkUpdate($hash,"received",$msg);
  504. readingsBulkUpdate($hash,"rectext",( (defined($text)) ? $text : "" ));
  505. readingsBulkUpdate($hash,"currentPage",$id) if ( ( defined( $id ) ) && ( AttrVal($name,"hasSendMe",0) ) );
  506. readingsEndUpdate($hash, 1);
  507. } else {
  508. Log3 $name, 5, "Nextion/RAW: match with zero length - command missing - ffh #".length($ffpart);
  509. }
  510. } else {
  511. # not matching
  512. # if ( $data =~ /^\xff+([^\xff].*)/ ) {
  513. # Log3 $name, 5, "Nextion/RAW: remove leading ff ";
  514. # $data = $1;
  515. # } elsif ( $data =~ /^[^\xff]*(\xff+)/ ) {
  516. # Log3 $name, 5, "Nextion/RAW: not matching commands but contains ff :".length($1).":";
  517. # } else {
  518. # Log3 $name, 5, "Nextion/RAW: not matching commands no ff";
  519. # }
  520. last;
  521. }
  522. }
  523. $hash->{PARTIAL} = $data;
  524. $hash->{READ_TS} = gettimeofday() if($data);
  525. # initialize last page id found:
  526. if ( defined( $newPageId ) ) {
  527. $newPageId = $newPageId + 0;
  528. my $initCmds = AttrVal( $name, "initPage".sprintf("%d",$newPageId), undef );
  529. Log3 $name, 4, "Nextion_InitPage $name: page :".$newPageId.": with commands :".(defined($initCmds)?$initCmds:"<undef>").":";
  530. return if ( ! defined( $initCmds ) );
  531. # Send command handles replaceSetMagic and splitting
  532. Nextion_SendCommand( $hash, $initCmds, 0 );
  533. }
  534. return $ret if(defined($local));
  535. return undef;
  536. }
  537. #####################################
  538. # This is a direct read for command results
  539. sub
  540. Nextion_ReadAnswer($$)
  541. {
  542. my ($hash, $arg) = @_;
  543. my $name = $hash->{NAME};
  544. Log3 $name, 4, "Nextion_ReadAnswer $name: for send commands :".$arg.": ";
  545. return "No FD (dummy device?)" if(!$hash || ($^O !~ /Win/ && !defined($hash->{FD})));
  546. my $data = "";
  547. for(;;) {
  548. return "Device lost when reading answer for get $arg" if(!$hash->{FD});
  549. my $rin = '';
  550. vec($rin, $hash->{FD}, 1) = 1;
  551. my $nfound = select($rin, undef, undef, 1);
  552. if($nfound < 0) {
  553. next if ($! == EAGAIN() || $! == EINTR() || $! == 0);
  554. my $err = $!;
  555. DevIo_Disconnected($hash);
  556. return"Nextion_ReadAnswer $arg: $err";
  557. }
  558. return "Timeout reading answer for get $arg" if($nfound == 0);
  559. my $buf = DevIo_SimpleRead($hash);
  560. return "No data" if(!defined($buf));
  561. my $ret;
  562. my $data .= $buf;
  563. # not yet long enough select again
  564. # next if ( length($data) < 4 );
  565. # TODO: might have to check for remaining data in buffer?
  566. if ( $data =~ /^\xff*([^\xff])\xff\xff\xff(.*)$/ ) {
  567. my $rcvd = $1;
  568. $data = $2;
  569. $ret = $Nextion_errCodes{$rcvd};
  570. $ret = "Nextion: Unknown error with code ".sprintf( "H%2.2x", $rcvd ) if ( ! defined( $ret ) );
  571. } elsif ( length($data) == 0 ) {
  572. $ret = "No answer";
  573. } else {
  574. $ret = "Message received";
  575. }
  576. # read rest of buffer direct in read function
  577. if ( length($data) > 0 ) {
  578. Nextion_Read($hash, $data);
  579. }
  580. return (($ret eq $Nextion_errCodes{"\x01"}) ? undef : $ret);
  581. }
  582. }
  583. #####################################
  584. sub
  585. Nextion_Ready($)
  586. {
  587. my ($hash) = @_;
  588. # Debug "Name : ".$hash->{NAME};
  589. # stacktrace();
  590. return Nextion_Connect( $hash, 1 ) if($hash->{STATE} eq "disconnected");
  591. return 0;
  592. }
  593. ##############################################################################
  594. ##############################################################################
  595. ##
  596. ## Helper
  597. ##
  598. ##############################################################################
  599. ##############################################################################
  600. #####################################
  601. # returns
  602. # msg in Hex converted format
  603. # text equivalent of message if applicable
  604. # val in message either numeric or text
  605. # id of a control <pageid>:<controlid>:... or just a page <pageid> or undef
  606. sub
  607. Nextion_convertMsg($)
  608. {
  609. my ($raw) = @_;
  610. my $msg = "";
  611. my $text;
  612. my $val;
  613. my $id;
  614. my $rcvd = $raw;
  615. while(length($rcvd) > 0) {
  616. my $char = ord($rcvd);
  617. $rcvd = substr($rcvd,1);
  618. $msg .= " " if ( length($msg) > 0 );
  619. $msg .= sprintf( "H%2.2x", $char );
  620. $msg .= "(".chr($char).")" if ( ( $char >= 32 ) && ( $char <= 127 ) ) ;
  621. }
  622. if ( $raw =~ /^(\$.*=)(\x71?)(.*)$/s ) {
  623. # raw msg with $ start is manually defined message standard
  624. # sent on release event
  625. # print "$bt0="
  626. # get bt0.val OR print bt0.val
  627. #
  628. $text = $1;
  629. my $rest = $3;
  630. if ( length($rest) == 4 ) {
  631. $val = ord($rest);
  632. $rest = substr($rest,1);
  633. $val += ord($rest)*256;
  634. $rest = substr($rest,1);
  635. $val += ord($rest)*256*256;
  636. $rest = substr($rest,1);
  637. $val += ord($rest)*256*256*256;
  638. $text .= sprintf("%d",$val);
  639. } else {
  640. $text .= $rest;
  641. $val = $rest;
  642. }
  643. } elsif ( $raw =~ /^\x70(.*)$/s ) {
  644. # string return
  645. $val = $1;
  646. # Log3 undef, 1, "Nextion_convertMsg String message val :".$val.": ";
  647. $text = "string \"" . $val . "\"";
  648. } elsif ( $raw =~ /^\x71(.*)$/ ) {
  649. # numeric return
  650. $text = "num ";
  651. my $rest = $1;
  652. if ( length($rest) == 4 ) {
  653. $val = ord($rest);
  654. $rest = substr($rest,1);
  655. $val += ord($rest)*256;
  656. $rest = substr($rest,1);
  657. $val += ord($rest)*256*256;
  658. $rest = substr($rest,1);
  659. $val += ord($rest)*256*256*256;
  660. $text .= sprintf("%d",$val);
  661. } else {
  662. $text .= $rest;
  663. $val = $rest;
  664. }
  665. } elsif ( $raw =~ /^\x66(.)$/s ) {
  666. # need to parse multiline due to issue with page 10 --> x0A
  667. # page started
  668. $text = "page ";
  669. my $rest = $1;
  670. $id = ord($rest);
  671. $text .= sprintf("%d",$id);
  672. }
  673. $text = Nextion_DecodeFromIso( $text );
  674. $msg = Nextion_DecodeFromIso( $msg );
  675. return ( $msg, $text, $val, $id );
  676. }
  677. #####################################
  678. sub
  679. Nextion_EncodeToIso($)
  680. {
  681. my ($orgmsg) = @_;
  682. # encode in ISO8859-1 from UTF8
  683. # decode to Perl's internal format
  684. my $msg = decode( 'utf-8', $orgmsg );
  685. # encode to iso-8859-1
  686. $msg = encode( 'iso-8859-1', $msg );
  687. return $msg;
  688. }
  689. #####################################
  690. sub
  691. Nextion_DecodeFromIso($)
  692. {
  693. my ($orgmsg) = @_;
  694. # encode in ISO8859-1 from UTF8
  695. # decode to Perl's internal format
  696. my $msg = decode( 'iso-8859-1', $orgmsg );
  697. # encode to iso-8859-1
  698. $msg = encode( 'utf-8', $msg );
  699. return $msg;
  700. }
  701. ##################################################################################################################
  702. ##################################################################################################################
  703. ##################################################################################################################
  704. 1;
  705. =pod
  706. =item summary interact with Nextion touch displays
  707. =item summary_DE interagiert mit Nextion Touchscreens
  708. =begin html
  709. <a name="Nextion"></a>
  710. <h3>Nextion</h3>
  711. <ul>
  712. This module connects remotely to a Nextion display that is connected through a ESP8266 or similar serial to network connection
  713. <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.
  714. <br>
  715. 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>.
  716. <br><br>
  717. <a name="Nextiondefine"></a>
  718. <b>Define</b>
  719. <ul>
  720. <code>define &lt;name&gt; Nextion &lt;hostname/ip&gt;:23 </code>
  721. <br><br>
  722. Defines a Nextion device on the given hostname / ip and port (should be port 23/telnetport normally)
  723. <br><br>
  724. Example: <code>define nxt Nextion 10.0.0.1:23</code><br>
  725. <br>
  726. </ul>
  727. <br><br>
  728. <a name="Nextionset"></a>
  729. <b>Set</b>
  730. <ul>
  731. <code>set &lt;name&gt; &lt;what&gt; [&lt;value&gt;]</code>
  732. <br><br>
  733. where &lt;what&gt; / &lt;value&gt; is one of
  734. <br><br>
  735. <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>
  736. <br>
  737. Examples:<br>
  738. <dl>
  739. <dt><code>set nxt raw page 0</code></dt>
  740. <dd> switch the display to page 0 <br> </dd>
  741. <dt><code>set nxt raw b0.txt</code></dt>
  742. <dd> get the text for button 0 <br> </dd>
  743. <dl>
  744. </li>
  745. <li><code>cmd &lt;nextion command&gt;</code><br>same as raw
  746. </li>
  747. <li><code>page &lt;0 - 9&gt;</code><br>set the page number given as new page on the nextion display.
  748. </li>
  749. <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.
  750. </li>
  751. </ul>
  752. <br><br>
  753. <a name="Nextionattr"></a>
  754. <b>Attributes</b>
  755. <br><br>
  756. <ul>
  757. <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
  758. </li>
  759. <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>
  760. Example<br>
  761. &nbsp;&nbsp;<code>t1.txt="Hallo";p1.val=1;</code>
  762. </li>
  763. <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>
  764. Example<br>
  765. &nbsp;&nbsp;<code>t1.txt="Hallo";p1.val=1;</code>
  766. </li>
  767. <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.
  768. </li>
  769. </ul>
  770. <br><br>
  771. <a name="Nextionreadings"></a>
  772. <b>Readings</b>
  773. <ul>
  774. <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>
  775. <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>
  776. <code>print "$bt0="<br>
  777. get bt0.val</code>
  778. </li>
  779. <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>
  780. <li><code>cmdSent &lt;cmd&gt;</code><br> Last cmd sent to the Nextion Display </li>
  781. <li><code>cmdResult &lt;result text&gt;</code><br> Result of the last cmd sent to the display (or empty)</li>
  782. </ul>
  783. <br><br>
  784. </ul>
  785. =end html
  786. =cut