20_FRM_OUT.pm 8.9 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291
  1. ########################################################################################
  2. #
  3. # $Id: 20_FRM_OUT.pm 15928 2018-01-19 21:07:42Z jensb $
  4. #
  5. # FHEM module for one Firmata digial output pin
  6. #
  7. ########################################################################################
  8. #
  9. # LICENSE AND COPYRIGHT
  10. #
  11. # Copyright (C) 2013 ntruchess
  12. # Copyright (C) 2016 jensb
  13. #
  14. # All rights reserved
  15. #
  16. # This script is free software; you can redistribute it and/or modify
  17. # it under the terms of the GNU General Public License as published by
  18. # the Free Software Foundation; either version 2 of the License, or
  19. # (at your option) any later version.
  20. #
  21. # The GNU General Public License can be found at
  22. # http://www.gnu.org/copyleft/gpl.html.
  23. # A copy is found in the textfile GPL.txt and important notices to the license
  24. # from the author is found in LICENSE.txt distributed with these scripts.
  25. #
  26. # This script is distributed in the hope that it will be useful,
  27. # but WITHOUT ANY WARRANTY; without even the implied warranty of
  28. # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
  29. # GNU General Public License for more details.
  30. #
  31. # This copyright notice MUST APPEAR in all copies of the script!
  32. #
  33. ########################################################################################
  34. package main;
  35. use strict;
  36. use warnings;
  37. #add FHEM/lib to @INC if it's not already included. Should rather be in fhem.pl than here though...
  38. BEGIN {
  39. if (!grep(/FHEM\/lib$/,@INC)) {
  40. foreach my $inc (grep(/FHEM$/,@INC)) {
  41. push @INC,$inc."/lib";
  42. };
  43. };
  44. };
  45. use Device::Firmata::Constants qw/ :all /;
  46. use SetExtensions;
  47. #####################################
  48. sub
  49. FRM_OUT_Initialize($)
  50. {
  51. my ($hash) = @_;
  52. $hash->{SetFn} = "FRM_OUT_Set";
  53. $hash->{DefFn} = "FRM_Client_Define";
  54. $hash->{InitFn} = "FRM_OUT_Init";
  55. $hash->{UndefFn} = "FRM_Client_Undef";
  56. $hash->{AttrFn} = "FRM_OUT_Attr";
  57. $hash->{StateFn} = "FRM_OUT_State";
  58. $hash->{AttrList} = "restoreOnReconnect:on,off restoreOnStartup:on,off activeLow:yes,no IODev valueMode:send,receive,bidirectional $main::readingFnAttributes";
  59. main::LoadModule("FRM");
  60. }
  61. sub
  62. FRM_OUT_Init($$)
  63. {
  64. my ($hash,$args) = @_;
  65. my $ret = FRM_Init_Pin_Client($hash,$args,PIN_OUTPUT);
  66. return $ret if (defined $ret);
  67. eval {
  68. my $firmata = FRM_Client_FirmataDevice($hash);
  69. my $pin = $hash->{PIN};
  70. $firmata->observe_digital($pin,\&FRM_OUT_observer,$hash);
  71. };
  72. my $name = $hash->{NAME};
  73. if (! (defined AttrVal($name,"stateFormat",undef))) {
  74. $main::attr{$name}{"stateFormat"} = "value";
  75. }
  76. my $value = ReadingsVal($name,"value",undef);
  77. if (!defined($value)) {
  78. readingsSingleUpdate($hash,"value","off",0);
  79. }
  80. if (AttrVal($hash->{NAME},"restoreOnReconnect", "on") eq "on") {
  81. FRM_OUT_Set($hash,$name,$value);
  82. }
  83. main::readingsSingleUpdate($hash,"state","Initialized",1);
  84. return undef;
  85. }
  86. sub
  87. FRM_OUT_observer($$$$)
  88. {
  89. my ($pin,$old,$new,$hash) = @_;
  90. my $name = $hash->{NAME};
  91. Log3 $name, 5, "onDigitalMessage for pin ".$pin.", old: ".(defined $old? $old : "--").", new: ".(defined $new? $new : "--");
  92. if (AttrVal($hash->{NAME}, "activeLow", "no") eq "yes") {
  93. $old = $old == PIN_LOW ? PIN_HIGH : PIN_LOW if (defined $old);
  94. $new = $new == PIN_LOW ? PIN_HIGH : PIN_LOW;
  95. }
  96. my $changed = !defined($old) || ($old != $new);
  97. if ($changed && (AttrVal($hash->{NAME}, "valueMode", "send") ne "send")) {
  98. main::readingsSingleUpdate($hash, "value", $new == PIN_HIGH? "on" : "off", 1);
  99. }
  100. }
  101. sub
  102. FRM_OUT_Set($$$)
  103. {
  104. my ($hash, $name, $cmd, @a) = @_;
  105. my $value;
  106. my $invert = AttrVal($hash->{NAME},"activeLow", "no");
  107. if (defined ($cmd)) {
  108. if ($cmd eq "on") {
  109. $value = $invert eq "yes" ? PIN_LOW : PIN_HIGH;
  110. } elsif ($cmd eq "off") {
  111. $value = $invert eq "yes" ? PIN_HIGH : PIN_LOW;
  112. } else {
  113. my $list = "on off";
  114. return SetExtensions($hash, $list, $name, $cmd, @a);
  115. }
  116. eval {
  117. FRM_Client_FirmataDevice($hash)->digital_write($hash->{PIN},$value);
  118. if (AttrVal($hash->{NAME}, "valueMode", "send") ne "receive") {
  119. main::readingsSingleUpdate($hash,"value",$cmd, 1);
  120. }
  121. };
  122. return $@;
  123. } else {
  124. return "no command specified";
  125. }
  126. }
  127. sub FRM_OUT_State($$$$)
  128. {
  129. my ($hash, $tim, $sname, $sval) = @_;
  130. STATEHANDLER: {
  131. $sname eq "value" and do {
  132. if (AttrVal($hash->{NAME},"restoreOnStartup", "on") eq "on") {
  133. FRM_OUT_Set($hash,$hash->{NAME},$sval);
  134. }
  135. last;
  136. }
  137. }
  138. }
  139. sub
  140. FRM_OUT_Attr($$$$) {
  141. my ($command,$name,$attribute,$value) = @_;
  142. my $hash = $main::defs{$name};
  143. eval {
  144. if ($command eq "set") {
  145. ARGUMENT_HANDLER: {
  146. $attribute eq "IODev" and do {
  147. if ($main::init_done and (!defined ($hash->{IODev}) or $hash->{IODev}->{NAME} ne $value)) {
  148. FRM_Client_AssignIOPort($hash,$value);
  149. FRM_Init_Client($hash) if (defined ($hash->{IODev}));
  150. }
  151. last;
  152. };
  153. $attribute eq "activeLow" and do {
  154. my $oldval = AttrVal($hash->{NAME},"activeLow", "no");
  155. if ($oldval ne $value) {
  156. # toggle output with attribute change
  157. $main::attr{$hash->{NAME}}{activeLow} = $value;
  158. if ($main::init_done) {
  159. my $value = ReadingsVal($name,"value",undef);
  160. FRM_OUT_Set($hash,$hash->{NAME},$value);
  161. }
  162. };
  163. last;
  164. };
  165. }
  166. }
  167. };
  168. if ($@) {
  169. $@ =~ /^(.*)( at.*FHEM.*)$/;
  170. $hash->{STATE} = "error setting $attribute to $value: ".$1;
  171. return "cannot $command attribute $attribute to $value for $name: ".$1;
  172. }
  173. }
  174. 1;
  175. =pod
  176. CHANGES
  177. 2016 jensb
  178. o new sub FRM_OUT_observer, modified sub FRM_OUT_Init
  179. to receive output state from Firmata device
  180. o support attribute "activeLow"
  181. 01.01.2018 jensb
  182. o create reading "value" in FRM_OUT_Init if missing
  183. 02.01.2018 jensb
  184. o new attribute "valueMode" to control how "value" reading is updated
  185. 14.01.2018 jensb
  186. o fix "uninitialised" when calling FRM_OUT_Set without command
  187. =cut
  188. =pod
  189. =item device
  190. =item summary Firmata: digital output
  191. =item summary_DE Firmata: digitaler Ausang
  192. =begin html
  193. <a name="FRM_OUT"></a>
  194. <h3>FRM_OUT</h3>
  195. <ul>
  196. This module represents a pin of a <a href="http://www.firmata.org">Firmata device</a>
  197. that should be configured as a digital output.<br><br>
  198. Requires a defined <a href="#FRM">FRM</a> device to work. The pin must be listed in
  199. the internal reading "<a href="#FRMinternals">output_pins</a>"<br>
  200. of the FRM device (after connecting to the Firmata device) to be used as digital output.<br><br>
  201. <a name="FRM_OUTdefine"></a>
  202. <b>Define</b>
  203. <ul>
  204. <code>define &lt;name&gt; FRM_OUT &lt;pin&gt;</code> <br>
  205. Defines the FRM_OUT device. &lt;pin&gt> is the arduino-pin to use.
  206. </ul><br>
  207. <a name="FRM_OUTset"></a>
  208. <b>Set</b><br>
  209. <ul>
  210. <code>set &lt;name&gt; on|off</code><br><br>
  211. </ul>
  212. <ul>
  213. <a href="#setExtensions">set extensions</a> are supported<br>
  214. </ul><br>
  215. <a name="FRM_OUTget"></a>
  216. <b>Get</b><br>
  217. <ul>
  218. N/A
  219. </ul><br>
  220. <a name="FRM_OUTattr"></a>
  221. <b>Attributes</b><br>
  222. <ul>
  223. <li>restoreOnStartup &lt;on|off&gt;, default: on<br>
  224. Set output value in Firmata device on FHEM startup (if device is already connected) and
  225. whenever the <em>setstate</em> command is used.
  226. </li>
  227. <li>restoreOnReconnect &lt;on|off&gt;, default: on<br>
  228. Set output value in Firmata device after IODev is initialized.
  229. </li>
  230. <li>activeLow &lt;yes|no&gt;, default: no</li>
  231. <li><a href="#IODev">IODev</a><br>
  232. Specify which <a href="#FRM">FRM</a> to use. (Optional, only required if there is more
  233. than one FRM-device defined.)
  234. </li>
  235. <li>valueMode &lt;send|receive|bidirectional&gt;, default: send<br>
  236. Define how the reading <em>value</em> is updated:<br>
  237. <ul>
  238. <li>send - after sending</li>
  239. <li>receive - after receiving</li>
  240. <li>bidirectional - after sending and receiving</li>
  241. </ul>
  242. </li>
  243. <li><a href="#eventMap">eventMap</a><br></li>
  244. <li><a href="#readingFnAttributes">readingFnAttributes</a><br></li>
  245. </ul><br>
  246. <a name="FRM_OUTnotes"></a>
  247. <b>Notes</b><br>
  248. <ul>
  249. <li>attribute <i>stateFormat</i><br>
  250. In most cases it is a good idea to assign "value" to the attribute <i>stateFormat</i>. This will show the state
  251. of the pin in the web interface.
  252. </li>
  253. <li>attribute <i>valueMode</i><br>
  254. For modes "receive<" and "bidirectional" to work the default Firmata application code must
  255. be modified in function "<code>setPinModeCallback</code>":<br>
  256. add "<ins> || mode == OUTPUT</ins>" to the if condition for "<code>portConfigInputs[pin / 8] |= (1 << (pin & 7));</code>" to enable<br>
  257. reporting the output state (as if the pin were an input). This is of interest if you have custom code in your Firmata device that can change<br>
  258. the state of an output or you want a feedback from the Firmata device after the output state was changed.
  259. </li>
  260. </ul>
  261. </ul><br>
  262. =end html
  263. =cut