52_I2C_LCD.pm 14 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487
  1. ##############################################
  2. # $Id: 52_I2C_LCD.pm 6722 2014-10-09 11:20:55Z ntruchsess $
  3. ##############################################
  4. package main;
  5. use strict;
  6. use warnings;
  7. #add FHEM/lib to @INC if it's not allready included. Should rather be in fhem.pl than here though...
  8. BEGIN {
  9. if (!grep(/FHEM\/lib$/,@INC)) {
  10. foreach my $inc (grep(/FHEM$/,@INC)) {
  11. push @INC,$inc."/lib";
  12. };
  13. };
  14. };
  15. #####################################
  16. my %sets = (
  17. "text" => "",
  18. "home" => "noArg",
  19. "clear" => "noArg",
  20. "display" => "on,off",
  21. "cursor" => "",
  22. "scroll" => "left,right",
  23. "backlight" => "on,off",
  24. "reset" => "noArg",
  25. "writeXY" => ""
  26. );
  27. my %gets = (
  28. );
  29. my %mapping = (
  30. 'P0' => 'RS',
  31. 'P1' => 'RW',
  32. 'P2' => 'E',
  33. 'P3' => 'LED',
  34. 'P4' => 'D4',
  35. 'P5' => 'D5',
  36. 'P6' => 'D6',
  37. 'P7' => 'D7',
  38. );
  39. my @LEDPINS = sort values %mapping;
  40. sub
  41. I2C_LCD_Initialize($)
  42. {
  43. my ($hash) = @_;
  44. $hash->{DefFn} = "I2C_LCD_Define";
  45. $hash->{InitFn} = "I2C_LCD_Init";
  46. $hash->{SetFn} = "I2C_LCD_Set";
  47. $hash->{AttrFn} = "I2C_LCD_Attr";
  48. $hash->{StateFn} = "I2C_LCD_State";
  49. $hash->{AttrList} = "restoreOnReconnect:on,off restoreOnStartup:on,off IODev model pinMapping"
  50. ." customChar0 customChar1 customChar2 customChar3 customChar4 customChar5 customChar6 customChar7"
  51. ." backLight:on,off blink:on,off autoClear:on,off autoBreak:on,off replaceRegex $main::readingFnAttributes";
  52. # autoScroll:on,off direction:leftToRight,rightToLeft do not work reliably
  53. }
  54. sub
  55. I2C_LCD_Define($$)
  56. {
  57. my ($hash, $def) = @_;
  58. my @a = split("[ \t][ \t]*", $def);
  59. $hash->{STATE}="defined";
  60. my @keyvalue = ();
  61. while (my ($key, $value) = each %mapping) {
  62. push @keyvalue,"$key=$value";
  63. };
  64. $main::attr{$a[0]}{"pinMapping"} = join (',',sort @keyvalue);
  65. $hash->{mapping} = \%mapping;
  66. if ($main::init_done) {
  67. eval {
  68. I2C_LCD_Init($hash,[@a[2..scalar(@a)-1]]);
  69. };
  70. return I2C_LCD_Catch($@) if $@;
  71. }
  72. return undef;
  73. }
  74. sub
  75. I2C_LCD_Init($$)
  76. {
  77. my ($hash,$args) = @_;
  78. my $u = "wrong syntax: define <name> I2C_LCD <size-x> <size-y> [<address>]";
  79. return $u if(int(@$args) < 2);
  80. $hash->{sizex} = shift @$args;
  81. $hash->{sizey} = shift @$args;
  82. if (defined (my $address = shift @$args)) {
  83. $hash->{I2C_Address} = $address =~ /^0.*$/ ? oct($address) : $address;
  84. }
  85. my $name = $hash->{NAME};
  86. if (defined $hash->{I2C_Address}) {
  87. eval {
  88. main::AssignIoPort($hash,AttrVal($hash->{NAME},"IODev",undef));
  89. require LiquidCrystal;
  90. my $lcd = LiquidCrystal->new($hash->{sizex},$hash->{sizey});
  91. $lcd->setMapping($hash->{mapping});
  92. $lcd->attach(I2C_LCD_IO->new($hash));
  93. $lcd->init();
  94. $hash->{lcd} = $lcd;
  95. I2C_LCD_Apply_Attribute($name,"backLight");
  96. # I2C_LCD_Apply_Attribute($name,"autoscroll");
  97. # I2C_LCD_Apply_Attribute($name,"direction");
  98. I2C_LCD_Apply_Attribute($name,"blink");
  99. foreach (0..7) {
  100. I2C_LCD_Apply_Attribute($name,"customChar".$_);
  101. }
  102. };
  103. return I2C_LCD_Catch($@) if $@;
  104. }
  105. if (! (defined AttrVal($name,"stateFormat",undef))) {
  106. $main::attr{$name}{"stateFormat"} = "text";
  107. }
  108. if (AttrVal($hash->{NAME},"restoreOnReconnect","on") eq "on") {
  109. foreach my $reading (("display","scroll","backlight","text","writeXY")) {
  110. if (defined (my $value = ReadingsVal($name,$reading,undef))) {
  111. I2C_LCD_Set($hash,$name,$reading,split " ", $value);
  112. }
  113. }
  114. }
  115. return undef;
  116. }
  117. sub
  118. I2C_LCD_Attr($$$$) {
  119. my ($command,$name,$attribute,$value) = @_;
  120. my $hash = $main::defs{$name};
  121. eval {
  122. if ($command eq "set") {
  123. ARGUMENT_HANDLER: {
  124. $attribute eq "IODev" and do {
  125. if ($main::init_done and (!defined ($hash->{IODev}) or $hash->{IODev}->{NAME} ne $value)) {
  126. main::AssignIoPort($hash,$value);
  127. my @def = split (' ',$hash->{DEF});
  128. I2C_LCD_Init($hash,\@def) if (defined ($hash->{IODev}));
  129. }
  130. last;
  131. };
  132. $attribute eq "pinMapping" and do {
  133. my %newMapping = ();
  134. foreach my $keyvalue (split (/,/,$value)) {
  135. my ($key,$value) = split (/=/,$keyvalue);
  136. #Log3 ($name,5,"pinMapping, token: $key=$value, current mapping: $mapping{$key}");
  137. die "unknown token $key in attribute pinMapping, valid tokens are ".join (',',keys %mapping) unless (defined $mapping{$key});
  138. die "undefined or invalid value for token $key in attribute pinMapping, valid LED-Pins are ".join (',',@LEDPINS) unless $value and grep (/$value/,@LEDPINS);
  139. $newMapping{$key} = $value;
  140. }
  141. $hash->{mapping} = \%newMapping;
  142. my @def = split (' ',$hash->{DEF});
  143. I2C_LCD_Init($hash,\@def) if ($main::init_done);
  144. last;
  145. };
  146. $attribute =~ /customChar[0-7]/ and do {
  147. my @vals = split(/, */, $value);
  148. die "wrong number of elements (must be 8) in '$value'" if ( @vals != 8 );
  149. foreach (@vals) {
  150. die "$_ is out of range 0-31" if ($_ < 0 || $_ > 31 );
  151. }
  152. };
  153. $main::attr{$name}{$attribute}=$value;
  154. I2C_LCD_Apply_Attribute($name,$attribute);
  155. }
  156. }
  157. };
  158. my $ret = I2C_LCD_Catch($@) if $@;
  159. if ($ret) {
  160. $hash->{STATE} = "error setting $attribute to $value: ".$ret;
  161. return "cannot $command attribute $attribute to $value for $name: ".$ret;
  162. }
  163. }
  164. sub I2C_LCD_Apply_Attribute {
  165. my ($name,$attribute) = @_;
  166. my $lcd = $main::defs{$name}{lcd};
  167. if ($main::init_done and defined $lcd) {
  168. ATTRIBUTE_HANDLER: {
  169. $attribute eq "backLight" and do {
  170. if (AttrVal($name,"backLight","on") eq "on") {
  171. $lcd->backlight();
  172. } else {
  173. $lcd->noBacklight();
  174. }
  175. last;
  176. };
  177. $attribute eq "autoScroll" and do {
  178. if (AttrVal($name,"autoScroll","on") eq "on") {
  179. $lcd->autoscroll();
  180. } else {
  181. $lcd->noAutoscroll();
  182. }
  183. last;
  184. };
  185. $attribute eq "direction" and do {
  186. if (AttrVal($name,"direction","leftToRight") eq "leftToRight") {
  187. $lcd->leftToRight();
  188. } else {
  189. $lcd->rightToLeft();
  190. }
  191. last;
  192. };
  193. $attribute eq "blink" and do {
  194. if (AttrVal($name,"blink","off") eq "on") {
  195. $lcd->blink();
  196. } else {
  197. $lcd->noBlink();
  198. }
  199. last;
  200. };
  201. $attribute =~ /customChar([0-7])/ and do {
  202. my $nr = $1;
  203. my $p = AttrVal($name,$attribute,"0,0,0,0,0,0,0,0");
  204. my @vals = split(/, */, $p);
  205. $lcd->createChar($nr,\@vals);
  206. }
  207. }
  208. }
  209. }
  210. sub I2C_LCD_Set(@) {
  211. my ($hash, @a) = @_;
  212. return "Need at least one parameters" if(@a < 2);
  213. my $command = $a[1];
  214. my $value = $a[2];
  215. if(!defined($sets{$command})) {
  216. my @commands = ();
  217. foreach my $key (sort keys %sets) {
  218. push @commands, $sets{$key} ? $key.":".join(",",$sets{$key}) : $key;
  219. }
  220. return "Unknown argument $a[1], choose one of " . join(" ", @commands);
  221. }
  222. my $lcd = $hash->{lcd};
  223. return unless defined $lcd;
  224. eval {
  225. COMMAND_HANDLER: {
  226. $command eq "text" and do {
  227. shift @a;
  228. shift @a;
  229. $value = join(" ", @a);
  230. if (AttrVal($hash->{NAME},"autoClear","on") eq "on") {
  231. $lcd->clear();
  232. }
  233. # set reading prior to regexp, could contain unprintable chars!
  234. main::readingsSingleUpdate($hash,"text",$value,1);
  235. $value = _i2c_lcd_replace($hash,$value);
  236. if (AttrVal($hash->{NAME},"autoBreak","on") eq "on") {
  237. my $sizex = $hash->{sizex};
  238. my $sizey = $hash->{sizey};
  239. my $start = 0;
  240. my $len = length $value;
  241. for (my $line = 0;$line<$sizey;$line++) {
  242. $lcd->setCursor(0,$line);
  243. if ($start<$len) {
  244. $lcd->print(substr $value, $start, $sizex);
  245. } else {
  246. last;
  247. }
  248. $start+=$sizex;
  249. }
  250. } else {
  251. $lcd->print($value);
  252. }
  253. last;
  254. };
  255. $command eq "home" and do {
  256. $lcd->home();
  257. last;
  258. };
  259. $command eq "reset" and do {
  260. $lcd->init();
  261. # $hash->{lcd} = $lcd;
  262. last;
  263. };
  264. $command eq "clear" and do {
  265. $lcd->clear();
  266. main::readingsSingleUpdate($hash,"text","",1);
  267. last;
  268. };
  269. $command eq "display" and do {
  270. if ($value ne "off") {
  271. $lcd->display();
  272. } else {
  273. $lcd->noDisplay();
  274. }
  275. main::readingsSingleUpdate($hash,"display",$value,1);
  276. last;
  277. };
  278. $command eq "cursor" and do {
  279. my ($x,$y) = split ",",$value;
  280. $lcd->setCursor($x,$y);
  281. last;
  282. };
  283. $command eq "scroll" and do {
  284. if ($value eq "left") {
  285. $lcd->scrollDisplayLeft();
  286. } else {
  287. $lcd->scrollDisplayRight();
  288. }
  289. main::readingsSingleUpdate($hash,"scroll",$value,1);
  290. last;
  291. };
  292. $command eq "backlight" and do {
  293. if ($value eq "on") {
  294. $lcd->backlight();
  295. } else {
  296. $lcd->noBacklight();
  297. }
  298. main::readingsSingleUpdate($hash,"backlight",$value,1);
  299. last;
  300. };
  301. $command eq "writeXY" and do {
  302. my ($x,$y,$l,$al) = split(",",$value);
  303. $lcd->setCursor($x,$y);
  304. shift @a; shift @a; shift @a;
  305. my $t = join(" ", @a);
  306. # set reading prior to regexp, could contain unprintable chars!
  307. main::readingsSingleUpdate($hash,"writeXY",$value." ".$t,1);
  308. $t = _i2c_lcd_replace($hash,$t);
  309. my $sl = length $t;
  310. if ($sl > $l) {
  311. $t = substr($t,0,$l);
  312. }
  313. if ($sl < $l) {
  314. my $dif = "";
  315. for (my $i=$sl; $i<$l; $i++) {
  316. $dif .= " ";
  317. }
  318. $t = ($al eq "l") ? $t.$dif : $dif.$t;
  319. }
  320. $lcd->print($t);
  321. last; #"X=$x|Y=$y|L=$l|Text=$t";
  322. };
  323. }
  324. };
  325. return I2C_LCD_Catch($@) if $@;
  326. return undef;
  327. }
  328. sub I2C_LCD_Catch($) {
  329. my $exception = shift;
  330. if ($exception) {
  331. $exception =~ /^(.*)( at.*FHEM.*)$/;
  332. return $1;
  333. }
  334. return undef;
  335. }
  336. sub I2C_LCD_State($$$$)
  337. {
  338. my ($hash, $tim, $sname, $sval) = @_;
  339. STATEHANDLER: {
  340. $sname eq "text" and do {
  341. if (AttrVal($hash->{NAME},"restoreOnStartup","on") eq "on") {
  342. I2C_LCD_Set($hash,$hash->{NAME},$sname,$sval);
  343. }
  344. last;
  345. }
  346. }
  347. }
  348. sub _i2c_lcd_replace($$){
  349. my ($hash, $txt) = @_;
  350. my($lcdReplaceRegex)=AttrVal($hash->{NAME},"replaceRegex","none");
  351. if($lcdReplaceRegex ne "none"){
  352. my(@rex)=split(/,/,$lcdReplaceRegex);
  353. foreach(@rex){
  354. my($search,$replace)=split(/=/,$_);
  355. $txt=~s/$search/$replace/g;
  356. }
  357. }
  358. $txt =~ s/\\(
  359. (?:x\{[0-9a-fA-F]+\}) | # more than 2 digit hex
  360. (?:N\{U\+[0-9a-fA-F]{2,4}\}) # unicode by hex
  361. )/"qq|\\$1|"/geex;
  362. return $txt;
  363. }
  364. package I2C_LCD_IO;
  365. sub new {
  366. my ($class,$hash) = @_;
  367. return bless {
  368. hash => $hash,
  369. }, $class;
  370. }
  371. sub write {
  372. my ( $self, @data ) = @_;
  373. my $hash = $self->{hash};
  374. if (defined (my $iodev = $hash->{IODev})) {
  375. main::CallFn($iodev->{NAME}, "I2CWrtFn", $iodev, {
  376. i2caddress => $hash->{I2C_Address},
  377. direction => "i2cwrite",
  378. data => join (' ',@data)
  379. });
  380. } else {
  381. die "no IODev assigned to '$hash->{NAME}'";
  382. }
  383. }
  384. 1;
  385. =pod
  386. =begin html
  387. <a name="I2C_LCD"></a>
  388. <h3>I2C_LCD</h3>
  389. <ul>
  390. drives LiquidCrystal Displays (LCD) that are connected to Firmata (via I2C).
  391. Supported are Displays that use a PCF8574T as I2C Bridge (as found on eBay when searching for
  392. 'LCD' and 'I2C'). Tested is the 1602 type (16 characters, 2 Lines), the 2004 type (and other cheap chinise-made
  393. I2C-LCDs for Arduino) ship with the same library, so they should work as well.
  394. See <a name="LiquidCrystal tutorial">http://arduino.cc/en/Tutorial/LiquidCrystal</a> for details about
  395. how to hook up the LCD to the arduino.
  396. Requires a defined <a href="#I2C">I2C</a>-device to work.<br>
  397. this I2C-device has to be configures for i2c by setting attr 'i2c-config' on the I2C-device<br>
  398. <a name="I2C_LCDdefine"></a>
  399. <b>Define</b>
  400. <ul>
  401. <code>define &lt;name&gt; I2C_LCD &lt;size-x&gt; &lt;size-y&gt; &lt;i2c-address&gt;</code> <br>
  402. Specifies the I2C_LCD device.<br>
  403. <li>size-x is the number of characters per line</li>
  404. <li>size-y is the numbers of rows.</li>
  405. <li>i2c-address is the (device-specific) address of the ic on the i2c-bus</li>
  406. </ul>
  407. <br>
  408. <a name="I2C_LCDset"></a>
  409. <b>Set</b><br>
  410. <ul>
  411. <li><code>set &lt;name&gt; text &lt;text to be displayed&gt;</code><br></li>
  412. <li><code>set &lt;name&gt; home</code><br></li>
  413. <li><code>set &lt;name&gt; clear</code><br></li>
  414. <li><code>set &lt;name&gt; display on|off</code><br></li>
  415. <li><code>set &lt;name&gt; cursor &lt;...&gt;</code><br></li>
  416. <li><code>set &lt;name&gt; scroll left|right</code><br></li>
  417. <li><code>set &lt;name&gt; backlight on|off</code><br></li>
  418. <li><code>set &lt;name&gt; reset</code><br></li>
  419. <li><code>set &lt;name&gt; writeXY x-pos,y-pos,len[,l] &lt;text to be displayed&gt;</code><br></li>
  420. </ul>
  421. <a name="I2C_I2Cget"></a>
  422. <b>Get</b><br>
  423. <ul>
  424. N/A<br>
  425. </ul><br>
  426. <a name="I2C_LCDattr"></a>
  427. <b>Attributes</b><br>
  428. <ul>
  429. <li>backLight &lt;on|off&gt;</li>
  430. <li>autoClear &lt;on|off&gt;</li>
  431. <li>autoBreak &lt;on|off&gt;</li>
  432. <li>restoreOnStartup &lt;on|off&gt;</li>
  433. <li>restoreOnReconnect &lt;on|off&gt;</li>
  434. <li>replaceRegex &auml;=ae,cd+=ef,g=\x{DF}<br/>
  435. specify find=replace regex pattern eg for non-printable characters. \x{DF} will become char 223, which is &ordm; on my lcd.
  436. </li>
  437. <li>customChar&lt;0-7&gt;<br/>
  438. up to 8 5x8px custom chars, see http://www.quinapalus.com/hd44780udg.html for a generator, use \x{00} to \x{07} to display</li>
  439. <li><a href="#IODev">IODev</a><br>
  440. Specify which <a href="#I2C">I2C</a> to use. (Optional, only required if there is more
  441. than one I2C-device defined.)
  442. </li>
  443. <li><a href="#eventMap">eventMap</a><br></li>
  444. <li><a href="#readingFnAttributes">readingFnAttributes</a><br></li>
  445. </ul>
  446. </ul>
  447. <br>
  448. =end html
  449. =cut