10_MQTT_BRIDGE.pm 7.4 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240
  1. ##############################################
  2. #
  3. # fhem bridge to mqtt (see http://mqtt.org)
  4. #
  5. # Copyright (C) 2017 Stephan Eisler
  6. # Copyright (C) 2014 - 2016 Norbert Truchsess
  7. #
  8. # This file is part of fhem.
  9. #
  10. # Fhem is free software: you can redistribute it and/or modify
  11. # it under the terms of the GNU General Public License as published by
  12. # the Free Software Foundation, either version 2 of the License, or
  13. # (at your option) any later version.
  14. #
  15. # Fhem is distributed in the hope that it will be useful,
  16. # but WITHOUT ANY WARRANTY; without even the implied warranty of
  17. # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
  18. # GNU General Public License for more details.
  19. #
  20. # You should have received a copy of the GNU General Public License
  21. # along with fhem. If not, see <http://www.gnu.org/licenses/>.
  22. #
  23. # $Id: 10_MQTT_BRIDGE.pm 13318 2017-02-03 09:42:52Z eisler $
  24. #
  25. ##############################################
  26. use strict;
  27. use warnings;
  28. my %sets = (
  29. );
  30. my %gets = (
  31. "version" => "",
  32. "readings" => ""
  33. );
  34. sub MQTT_BRIDGE_Initialize($) {
  35. my $hash = shift @_;
  36. # Consumer
  37. $hash->{DefFn} = "MQTT::Client_Define";
  38. $hash->{UndefFn} = "MQTT::Client_Undefine";
  39. $hash->{GetFn} = "MQTT::BRIDGE::Get";
  40. $hash->{NotifyFn} = "MQTT::BRIDGE::Notify";
  41. $hash->{AttrFn} = "MQTT::BRIDGE::Attr";
  42. $hash->{AttrList} =
  43. "IODev ".
  44. "qos:".join(",",keys %MQTT::qos)." ".
  45. "retain:0,1 ".
  46. "publish-topic-base ".
  47. "publishState ".
  48. "publishReading_.* ".
  49. "subscribeSet ".
  50. "subscribeSet_.* ".
  51. $main::readingFnAttributes;
  52. main::LoadModule("MQTT");
  53. }
  54. package MQTT::BRIDGE;
  55. use strict;
  56. use warnings;
  57. use GPUtils qw(:all);
  58. use Net::MQTT::Constants;
  59. BEGIN {
  60. MQTT->import(qw(:all));
  61. GP_Import(qw(
  62. AttrVal
  63. CommandAttr
  64. readingsSingleUpdate
  65. Log3
  66. DoSet
  67. ))
  68. };
  69. sub Get($$@) {
  70. my ($hash, $name, $command) = @_;
  71. return "Need at least one parameters" unless (defined $command);
  72. return "Unknown argument $command, choose one of " . join(" ", sort keys %gets)
  73. unless (defined($gets{$command}));
  74. COMMAND_HANDLER: {
  75. # populate dynamically from keys %{$defs{$sdev}{READINGS}}
  76. $command eq "readings" and do {
  77. my $base = AttrVal($name,"publish-topic-base","/$hash->{DEF}/");
  78. foreach my $reading (keys %{$main::defs{$hash->{DEF}}{READINGS}}) {
  79. unless (defined AttrVal($name,"publishReading_$reading",undef)) {
  80. CommandAttr($hash,"$name publishReading_$reading $base$reading");
  81. }
  82. };
  83. last;
  84. };
  85. };
  86. }
  87. sub Notify() {
  88. my ($hash,$dev) = @_;
  89. Log3($hash->{NAME},5,"Notify for $dev->{NAME}");
  90. foreach my $event (@{$dev->{CHANGED}}) {
  91. $event =~ /^([^:]+)(: )?(.*)$/;
  92. Log3($hash->{NAME},5,"$event, '".((defined $1) ? $1 : "-undef-")."', '".((defined $3) ? $3 : "-undef-")."'");
  93. my $msgid;
  94. if (defined $3 and $3 ne "") {
  95. if (defined $hash->{publishReadings}->{$1}) {
  96. $msgid = send_publish($hash->{IODev}, topic => $hash->{publishReadings}->{$1}, message => $3, qos => $hash->{qos}, retain => $hash->{retain});
  97. readingsSingleUpdate($hash,"transmission-state","outgoing publish sent",1);
  98. }
  99. } else {
  100. if (defined $hash->{publishState}) {
  101. $msgid = send_publish($hash->{IODev}, topic => $hash->{publishState}, message => $1, qos => $hash->{qos}, retain => $hash->{retain});
  102. readingsSingleUpdate($hash,"transmission-state","outgoing publish sent",1);
  103. }
  104. }
  105. $hash->{message_ids}->{$msgid}++ if defined $msgid;
  106. }
  107. }
  108. sub Attr($$$$) {
  109. my ($command,$name,$attribute,$value) = @_;
  110. my $hash = $main::defs{$name};
  111. ATTRIBUTE_HANDLER: {
  112. $attribute =~ /^subscribeSet(_?)(.*)/ and do {
  113. if ($command eq "set") {
  114. unless (defined $hash->{subscribeSets}->{$value} and $hash->{subscribeSets}->{$value} eq $2) {
  115. unless (defined $hash->{subscribeSets}->{$value}) {
  116. client_subscribe_topic($hash,$value);
  117. }
  118. $hash->{subscribeSets}->{$value} = $2;
  119. }
  120. } else {
  121. foreach my $topic (keys %{$hash->{subscribeSets}}) {
  122. if ($hash->{subscribeSets}->{$topic} eq $2) {
  123. client_unsubscribe_topic($hash,$topic);
  124. delete $hash->{subscribeSets}->{$topic};
  125. last;
  126. }
  127. }
  128. }
  129. last;
  130. };
  131. $attribute eq "publishState" and do {
  132. if ($command eq "set") {
  133. $hash->{publishState} = $value;
  134. } else {
  135. delete $hash->{publishState};
  136. }
  137. last;
  138. };
  139. $attribute =~ /^publishReading_(.+)$/ and do {
  140. if ($command eq "set") {
  141. $hash->{publishReadings}->{$1} = $value;
  142. } else {
  143. delete $hash->{publishReadings}->{$1};
  144. }
  145. last;
  146. };
  147. client_attr($hash,$command,$name,$attribute,$value);
  148. }
  149. }
  150. sub onmessage($$$) {
  151. my ($hash,$topic,$message) = @_;
  152. if (defined (my $command = $hash->{subscribeSets}->{$topic})) {
  153. my @args = split ("[ \t]+",$message);
  154. if ($command eq "") {
  155. Log3($hash->{NAME},5,"calling DoSet($hash->{DEF}".(@args ? ",".join(",",@args) : ""));
  156. DoSet($hash->{DEF},@args);
  157. } else {
  158. Log3($hash->{NAME},5,"calling DoSet($hash->{DEF},$command".(@args ? ",".join(",",@args) : ""));
  159. DoSet($hash->{DEF},$command,@args);
  160. }
  161. }
  162. }
  163. 1;
  164. =pod
  165. =item [device]
  166. =item summary MQTT_BRIDGE acts as a bridge in between an fhem-device and mqtt-topics
  167. =begin html
  168. <a name="MQTT_BRIDGE"></a>
  169. <h3>MQTT_BRIDGE</h3>
  170. <ul>
  171. <p>acts as a bridge in between an fhem-device and <a href="http://mqtt.org/">mqtt</a>-topics.</p>
  172. <p>requires a <a href="#MQTT">MQTT</a>-device as IODev<br/>
  173. Note: this module is based on <a href="https://metacpan.org/pod/distribution/Net-MQTT/lib/Net/MQTT.pod">Net::MQTT</a> which needs to be installed from CPAN first.</p>
  174. <a name="MQTT_BRIDGEdefine"></a>
  175. <p><b>Define</b></p>
  176. <ul>
  177. <p><code>define &lt;name&gt; MQTT_BRIDGE &lt;fhem-device-name&gt;</code></p>
  178. <p>Specifies the MQTT device.<br/>
  179. &lt;fhem-device-name&gt; is the fhem-device this MQTT_BRIDGE is linked to.</p>
  180. </ul>
  181. <a name="MQTT_BRIDGEget"></a>
  182. <p><b>Get</b></p>
  183. <ul>
  184. <li>
  185. <p><code>get &lt;name&gt; readings</code><br/>
  186. retrieves all existing readings from fhem-device and configures (default-)topics for them.<br/>
  187. attribute 'publish-topic-base' is prepended if set.</p>
  188. </li>
  189. </ul>
  190. <a name="MQTT_BRIDGEattr"></a>
  191. <p><b>Attributes</b></p>
  192. <ul>
  193. <li>
  194. <p><code>attr &lt;name&gt; subscribeSet &lt;topic&gt;</code><br/>
  195. configures a topic that will issue a 'set &lt;message&gt; whenever a message is received</p>
  196. </li>
  197. <li>
  198. <p><code>attr &lt;name&gt; subscribeSet_&lt;reading&gt; &lt;topic&gt;</code><br/>
  199. configures a topic that will issue a 'set &lt;reading&gt; &lt;message&gt; whenever a message is received</p>
  200. </li>
  201. <li>
  202. <p><code>attr &lt;name&gt; publishState &lt;topic&gt;</code><br/>
  203. configures a topic such that a message is sent to topic whenever the device state changes.</p>
  204. </li>
  205. <li>
  206. <p><code>attr &lt;name&gt; publishReading_&lt;reading&gt; &lt;topic&gt;</code><br/>
  207. configures a topic such that a message is sent to topic whenever the device readings value changes.</p>
  208. </li>
  209. <li>
  210. <p><code>attr &lt;name&gt; publish-topic-base &lt;topic&gt;</code><br/>
  211. this is used as base path when issueing 'get &lt;device&gt; readings' to construct topics to publish to based on the devices existing readings</p>
  212. </li>
  213. </ul>
  214. </ul>
  215. =end html
  216. =cut