10_MYSENSORS_DEVICE.pm 30 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748
  1. ##############################################
  2. #
  3. # fhem bridge to MySensors (see http://mysensors.org)
  4. #
  5. # Copyright (C) 2014 Norbert Truchsess
  6. # Copyright (C) 2018 Hauswart@forum.fhem.de
  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_MYSENSORS_DEVICE.pm 17611 2018-10-24 10:56:06Z Hauswart $
  24. #
  25. ##############################################
  26. use strict;
  27. use warnings;
  28. my %gets = (
  29. "version" => "",
  30. );
  31. sub MYSENSORS_DEVICE_Initialize($) {
  32. my $hash = shift @_;
  33. # Consumer
  34. $hash->{DefFn} = "MYSENSORS::DEVICE::Define";
  35. $hash->{UndefFn} = "MYSENSORS::DEVICE::UnDefine";
  36. $hash->{SetFn} = "MYSENSORS::DEVICE::Set";
  37. $hash->{AttrFn} = "MYSENSORS::DEVICE::Attr";
  38. $hash->{AttrList} =
  39. "config:M,I " .
  40. "mode:node,repeater " .
  41. "version:1.4 " .
  42. "setCommands " .
  43. "setReading_.+ " .
  44. "mapReadingType_.+ " .
  45. "mapReading_.+ " .
  46. "requestAck:1 " .
  47. "timeoutAck " .
  48. "timeoutAlive " .
  49. "IODev " .
  50. "showtime:0,1 " .
  51. $main::readingFnAttributes;
  52. main::LoadModule("MYSENSORS");
  53. }
  54. package MYSENSORS::DEVICE;
  55. use strict;
  56. use warnings;
  57. use GPUtils qw(:all);
  58. use Device::MySensors::Constants qw(:all);
  59. use Device::MySensors::Message qw(:all);
  60. use SetExtensions qw/ :all /;
  61. BEGIN {
  62. MYSENSORS->import(qw(:all));
  63. GP_Import(qw(
  64. AttrVal
  65. readingsSingleUpdate
  66. CommandAttr
  67. CommandDeleteAttr
  68. CommandDeleteReading
  69. AssignIoPort
  70. Log3
  71. SetExtensions
  72. ReadingsVal
  73. InternalTimer
  74. RemoveInternalTimer
  75. ))
  76. };
  77. my %static_types = (
  78. S_DOOR => { receives => [], sends => [V_TRIPPED,V_ARMED] }, # Door and window sensors
  79. S_MOTION => { receives => [], sends => [V_TRIPPED,V_ARMED] }, # Motion sensors
  80. S_SMOKE => { receives => [], sends => [V_TRIPPED,V_ARMED] }, # Smoke sensor
  81. S_BINARY => { receives => [V_STATUS,V_WATT], sends => [V_STATUS,V_WATT] }, # Binary device (on/off)
  82. S_DIMMER => { receives => [V_STATUS,V_PERCENTAGE,V_WATT], sends => [V_STATUS,V_PERCENTAGE,V_WATT] }, # Dimmable device of some kind
  83. S_COVER => { receives => [V_UP,V_DOWN,V_STOP,V_PERCENTAGE], sends => [V_UP,V_DOWN,V_STOP,V_PERCENTAGE] }, # Window covers or shades
  84. S_TEMP => { receives => [], sends => [V_TEMP,V_ID] }, # Temperature sensor
  85. S_HUM => { receives => [], sends => [V_HUM] }, # Humidity sensor
  86. S_BARO => { receives => [], sends => [V_PRESSURE,V_FORECAST] }, # Barometer sensor (Pressure)
  87. S_WIND => { receives => [], sends => [V_WIND,V_GUST,V_DIRECTION] }, # Wind sensor
  88. S_RAIN => { receives => [], sends => [V_RAIN,V_RAINRATE] }, # Rain sensor
  89. S_UV => { receives => [], sends => [V_UV] }, # UV sensor
  90. S_WEIGHT => { receives => [], sends => [V_WEIGHT,V_IMPEDANCE] }, # Weight sensor for scales etc.
  91. S_POWER => { receives => [V_VAR1], sends => [V_WATT,V_KWH,V_VAR,V_VA,V_POWER_FACTOR,V_VAR1] }, # Power measuring device, like power meters
  92. S_HEATER => { receives => [], sends => [V_HVAC_SETPOINT_HEAT,V_HVAC_FLOW_STATE,V_TEMP,V_STATUS] }, # Heater device
  93. S_DISTANCE => { receives => [], sends => [V_DISTANCE,V_UNIT_PREFIX] }, # Distance sensor
  94. S_LIGHT_LEVEL => { receives => [], sends => [V_LIGHT_LEVEL,V_LEVEL] }, # Light sensor
  95. S_ARDUINO_NODE => { receives => [], sends => [] }, # Arduino node device
  96. S_ARDUINO_REPEATER_NODE => { receives => [], sends => [] }, # Arduino repeating node device
  97. S_LOCK => { receives => [V_LOCK_STATUS], sends => [V_LOCK_STATUS] }, # Lock device
  98. S_IR => { receives => [V_IR_SEND], sends => [V_IR_RECEIVE,V_IR_RECORD,V_IR_SEND] }, # Ir sender/receiver device
  99. S_WATER => { receives => [V_VAR1], sends => [V_FLOW,V_VOLUME,V_VAR1] }, # Water meter
  100. S_AIR_QUALITY => { receives => [], sends => [V_LEVEL,V_UNIT_PREFIX] }, # Air quality sensor e.g. MQ-2
  101. S_CUSTOM => { receives => [V_VAR1,V_VAR2,V_VAR3,V_VAR4,V_VAR5], sends => [V_VAR1,V_VAR2,V_VAR3,V_VAR4,V_VAR5] }, # Use this for custom sensors where no other fits.
  102. S_DUST => { receives => [], sends => [V_LEVEL,V_UNIT_PREFIX] }, # Dust level sensor
  103. S_SCENE_CONTROLLER => { receives => [], sends => [V_SCENE_ON,V_SCENE_OFF] }, # Scene controller device
  104. S_RGB_LIGHT => { receives => [V_RGB,V_WATT,V_PERCENTAGE], sends => [V_RGB,V_WATT,V_PERCENTAGE] }, # RGB light
  105. S_RGBW_LIGHT => { receives => [V_RGBW,V_WATT,V_PERCENTAGE], sends => [V_RGBW,V_WATT,V_PERCENTAGE] }, # RGBW light (with separate white component)
  106. S_COLOR_SENSOR => { receives => [V_RGB], sends => [V_RGB] }, # Color sensor
  107. S_HVAC => { receives => [], sends => [V_STATUS,V_TEMP,V_HVAC_SETPOINT_HEAT,V_HVAC_SETPOINT_COOL,V_HVAC_FLOW_STATE,V_HVAC_FLOW_MODE,V_HVAC_SPEED] }, # Thermostat/HVAC device
  108. S_MULTIMETER => { receives => [], sends => [V_VOLTAGE,V_CURRENT,V_IMPEDANCE] }, # Multimeter device
  109. S_SPRINKLER => { receives => [], sends => [V_STATUS,V_TRIPPED] }, # Sprinkler device
  110. S_WATER_LEAK => { receives => [], sends => [V_TRIPPED,V_ARMED] }, # Water leak sensor
  111. S_SOUND => { receives => [], sends => [V_LEVEL,V_TRIPPED,V_ARMED] }, # Sound sensor
  112. S_VIBRATION => { receives => [], sends => [V_LEVEL,V_TRIPPED,V_ARMED] }, # Vibration sensor
  113. S_MOISTURE => { receives => [], sends => [V_LEVEL,V_TRIPPED,V_ARMED] }, # Moisture sensor
  114. S_INFO => { receives => [V_TEXT], sends => [V_TEXT] }, # LCD text device
  115. S_GAS => { receives => [], sends => [V_FLOW,V_VOLUME] }, # Gas meter
  116. S_GPS => { receives => [], sends => [V_POSITION] }, # GPS Sensor
  117. S_WATER_QUALITY => { receives => [], sends => [V_TEMP,V_PH,V_ORP,V_EC,V_STATUS] }, # Water quality sensor
  118. );
  119. my %static_mappings = (
  120. V_TEMP => { type => "temperature" },
  121. V_HUM => { type => "humidity" },
  122. V_STATUS => { type => "status", val => { 0 => 'off', 1 => 'on' }},
  123. V_PERCENTAGE => { type => "percentage", range => { min => 0, step => 1, max => 100 }},
  124. V_PRESSURE => { type => "pressure" },
  125. V_FORECAST => { type => "forecast", val => { # PressureSensor, DP/Dt explanation
  126. 0 => 'stable', # 0 = "Stable Weather Pattern"
  127. 1 => 'sunny', # 1 = "Slowly rising Good Weather", "Clear/Sunny"
  128. 2 => 'cloudy', # 2 = "Slowly falling L-Pressure ", "Cloudy/Rain"
  129. 3 => 'unstable', # 3 = "Quickly rising H-Press", "Not Stable"
  130. 4 => 'thunderstorm',# 4 = "Quickly falling L-Press", "Thunderstorm"
  131. 5 => 'unknown' }}, # 5 = "Unknown (More Time needed)
  132. V_RAIN => { type => "rain" },
  133. V_RAINRATE => { type => "rainrate" },
  134. V_WIND => { type => "wind" },
  135. V_GUST => { type => "gust" },
  136. V_DIRECTION => { type => "direction" },
  137. V_UV => { type => "uv" },
  138. V_WEIGHT => { type => "weight" },
  139. V_DISTANCE => { type => "distance" },
  140. V_IMPEDANCE => { type => "impedance" },
  141. V_ARMED => { type => "armed", val => { 0 => 'off', 1 => 'on' }},
  142. V_TRIPPED => { type => "tripped", val => { 0 => 'off', 1 => 'on' }},
  143. V_WATT => { type => "power" },
  144. V_KWH => { type => "energy" },
  145. V_SCENE_ON => { type => "button_on" },
  146. V_SCENE_OFF => { type => "button_off" },
  147. V_HVAC_FLOW_STATE => { type => "hvacflowstate" },
  148. V_HVAC_SPEED => { type => "hvacspeed" },
  149. V_LIGHT_LEVEL => { type => "brightness", range => { min => 0, step => 1, max => 100 }},
  150. V_VAR1 => { type => "value1" },
  151. V_VAR2 => { type => "value2" },
  152. V_VAR3 => { type => "value3" },
  153. V_VAR4 => { type => "value4" },
  154. V_VAR5 => { type => "value5" },
  155. V_UP => { type => "up" },
  156. V_DOWN => { type => "down" },
  157. V_STOP => { type => "stop" },
  158. V_IR_SEND => { type => "ir_send" },
  159. V_IR_RECEIVE => { type => "ir_receive" },
  160. V_FLOW => { type => "flow" },
  161. V_VOLUME => { type => "volume" },
  162. V_LOCK_STATUS => { type => "lockstatus", val => { 0 => 'off', 1 => 'on' }},
  163. V_LEVEL => { type => "level" },
  164. V_VOLTAGE => { type => "voltage" },
  165. V_CURRENT => { type => "current" },
  166. V_RGB => { type => "rgb" },
  167. V_RGBW => { type => "rgbw" },
  168. V_ID => { type => "id" },
  169. V_UNIT_PREFIX => { type => "unitprefix" },
  170. V_HVAC_SETPOINT_COOL => { type => "hvacsetpointcool" },
  171. V_HVAC_SETPOINT_HEAT => { type => "hvacsetpointheat" },
  172. V_HVAC_FLOW_MODE => { type => "hvacflowmode" },
  173. V_TEXT => { type => "text" },
  174. V_CUSTOM => { type => "custom" },
  175. V_POSITION => { type => "position" },
  176. V_IR_RECORD => { type => "ir_record" },
  177. V_PH => { type => "ph" },
  178. V_ORP => { type => "orp" },
  179. V_EC => { type => "ec" },
  180. V_VAR => { type => "value" },
  181. V_VA => { type => "va" },
  182. V_POWER_FACTOR => { type => "power_factor" },
  183. );
  184. sub Define($$) {
  185. my ( $hash, $def ) = @_;
  186. my ($name, $type, $radioId) = split("[ \t]+", $def);
  187. return "requires 1 parameters" unless (defined $radioId and $radioId ne "");
  188. $hash->{radioId} = $radioId;
  189. $hash->{sets} = {
  190. 'time' => "",
  191. reboot => "",
  192. # clear => "",
  193. };
  194. $hash->{ack} = 0;
  195. $hash->{typeMappings} = {map {variableTypeToIdx($_) => $static_mappings{$_}} keys %static_mappings};
  196. $hash->{sensorMappings} = {map {sensorTypeToIdx($_) => $static_types{$_}} keys %static_types};
  197. $hash->{readingMappings} = {};
  198. AssignIoPort($hash);
  199. };
  200. sub UnDefine($) {
  201. my ($hash) = @_;
  202. my $name = $hash->{NAME};
  203. RemoveInternalTimer("timeoutAck:$name");
  204. RemoveInternalTimer("timeoutAlive:$name");
  205. return undef;
  206. }
  207. sub Set($@) {
  208. my ($hash,$name,$command,@values) = @_;
  209. return "Need at least one parameters" unless defined $command;
  210. if(!defined($hash->{sets}->{$command})) {
  211. my $list = join(" ", map {$hash->{sets}->{$_} ne "" ? "$_:$hash->{sets}->{$_}" : $_} sort keys %{$hash->{sets}});
  212. return grep (/(^on$)|(^off$)/,keys %{$hash->{sets}}) == 2 ? SetExtensions($hash, $list, $name, $command, @values) : "Unknown argument $command, choose one of $list";
  213. }
  214. COMMAND_HANDLER: {
  215. # $command eq "clear" and do {
  216. # # Test 102 anstatt 255 :) und Log
  217. # sendClientMessage($hash, childId => 255, cmd => C_INTERNAL, subType => I_CHILDREN, payload => "C");
  218. # Log3 ($name,3,"MYSENSORS_DEVICE $name: clear");
  219. # # Test
  220. # last;
  221. # };
  222. $command eq "time" and do {
  223. sendClientMessage($hash, childId => 255, cmd => C_INTERNAL, subType => I_TIME, payload => time);
  224. last;
  225. };
  226. $command eq "reboot" and do {
  227. sendClientMessage($hash, childId => 255, cmd => C_INTERNAL, subType => I_REBOOT);
  228. last;
  229. };
  230. (defined ($hash->{setcommands}->{$command})) and do {
  231. my $setcommand = $hash->{setcommands}->{$command};
  232. eval {
  233. my ($type,$childId,$mappedValue) = mappedReadingToRaw($hash,$setcommand->{var},$setcommand->{val});
  234. sendClientMessage($hash,
  235. childId => $childId,
  236. cmd => C_SET,
  237. subType => $type,
  238. payload => $mappedValue,
  239. );
  240. readingsSingleUpdate($hash,$setcommand->{var},$setcommand->{val},1) unless ($hash->{ack} or $hash->{IODev}->{ack});
  241. };
  242. return "$command not defined: ".GP_Catch($@) if $@;
  243. last;
  244. };
  245. my $value = @values ? join " ",@values : "";
  246. eval {
  247. my ($type,$childId,$mappedValue) = mappedReadingToRaw($hash,$command,$value);
  248. sendClientMessage($hash, childId => $childId, cmd => C_SET, subType => $type, payload => $mappedValue);
  249. readingsSingleUpdate($hash,$command,$value,1) unless ($hash->{ack} or $hash->{IODev}->{ack});
  250. };
  251. return "$command not defined: ".GP_Catch($@) if $@;
  252. }
  253. }
  254. sub Attr($$$$) {
  255. my ($command,$name,$attribute,$value) = @_;
  256. my $hash = $main::defs{$name};
  257. ATTRIBUTE_HANDLER: {
  258. $attribute eq "config" and do {
  259. if ($main::init_done) {
  260. sendClientMessage($hash, cmd => C_INTERNAL, childId => 255, subType => I_CONFIG, payload => $command eq 'set' ? $value : "M");
  261. }
  262. last;
  263. };
  264. $attribute eq "mode" and do {
  265. if ($command eq "set" and $value eq "repeater") {
  266. $hash->{repeater} = 1;
  267. # $hash->{sets}->{clear} = "";
  268. } else {
  269. $hash->{repeater} = 0;
  270. # delete $hash->{sets}->{clear};
  271. }
  272. last;
  273. };
  274. $attribute eq "version" and do {
  275. if ($command eq "set") {
  276. $hash->{protocol} = $value;
  277. } else {
  278. delete $hash->{protocol};
  279. }
  280. last;
  281. };
  282. $attribute eq "setCommands" and do {
  283. foreach my $set (keys %{$hash->{setcommands}}) {
  284. delete $hash->{sets}->{$set};
  285. }
  286. $hash->{setcommands} = {};
  287. if ($command eq "set" and $value) {
  288. foreach my $setCmd (split ("[, \t]+",$value)) {
  289. if ($setCmd =~ /^(.+):(.+):(.+)$/) {
  290. $hash->{sets}->{$1}="";
  291. $hash->{setcommands}->{$1} = {
  292. var => $2,
  293. val => $3,
  294. };
  295. } else {
  296. return "unparsable value in setCommands for $name: $setCmd";
  297. }
  298. }
  299. }
  300. last;
  301. };
  302. $attribute =~ /^setReading_(.+)$/ and do {
  303. if ($command eq "set") {
  304. $hash->{sets}->{$1}= (defined $value) ? join(",",split ("[, \t]+",$value)) : "";
  305. } else {
  306. CommandDeleteReading(undef,"$hash->{NAME} $1");
  307. delete $hash->{sets}->{$1};
  308. }
  309. last;
  310. };
  311. $attribute =~ /^mapReadingType_(.+)/ and do {
  312. my $type = variableTypeToIdx("V_$1");
  313. if ($command eq "set") {
  314. my @values = split ("[, \t]",$value);
  315. $hash->{typeMappings}->{$type}={
  316. type => shift @values,
  317. val => {map {$_ =~ /^(.+):(.+)$/; $1 => $2} @values},
  318. }
  319. } else {
  320. if ($static_mappings{"V_$1"}) {
  321. $hash->{typeMappings}->{$type}=$static_mappings{"V_$1"};
  322. } else {
  323. delete $hash->{typeMappings}->{$type};
  324. }
  325. my $readings = $hash->{READINGS};
  326. my $readingMappings = $hash->{readingMappings};
  327. foreach my $todelete (map {$readingMappings->{$_}->{name}} grep {$readingMappings->{$_}->{type} == $type} keys %$readingMappings) {
  328. CommandDeleteReading(undef,"$hash->{NAME} $todelete"); #TODO do propper remap of existing readings
  329. }
  330. }
  331. last;
  332. };
  333. $attribute =~ /^mapReading_(.+)/ and do {
  334. my $readingMappings = $hash->{readingMappings};
  335. FIND: foreach my $id (keys %$readingMappings) {
  336. my $readingsForId = $readingMappings->{$id};
  337. foreach my $type (keys %$readingsForId) {
  338. if (($readingsForId->{$type}->{name} // "") eq $1) {
  339. delete $readingsForId->{$type};
  340. unless (keys %$readingsForId) {
  341. delete $readingMappings->{$id};
  342. }
  343. last FIND;
  344. }
  345. }
  346. }
  347. if ($command eq "set") {
  348. my ($id,$typeStr,@values) = split ("[, \t]",$value);
  349. my $typeMappings = $hash->{typeMappings};
  350. if (my @match = grep {$typeMappings->{$_}->{type} eq $typeStr} keys %$typeMappings) {
  351. my $type = shift @match;
  352. $readingMappings->{$id}->{$type}->{name} = $1;
  353. if (@values) {
  354. $readingMappings->{$id}->{$type}->{val} = {map {$_ =~ /^(.+):(.+)$/; $1 => $2} @values}; #TODO range?
  355. }
  356. } else {
  357. return "unknown reading type $typeStr";
  358. }
  359. } else {
  360. CommandDeleteReading(undef,"$hash->{NAME} $1");
  361. }
  362. last;
  363. };
  364. $attribute eq "requestAck" and do {
  365. if ($command eq "set") {
  366. $hash->{ack} = 1;
  367. } else {
  368. $hash->{ack} = 0;
  369. }
  370. last;
  371. };
  372. $attribute eq "timeoutAck" and do {
  373. if ($command eq "set") {
  374. $hash->{timeoutAck} = $value;
  375. } else {
  376. $hash->{timeoutAck} = 0;
  377. }
  378. last;
  379. };
  380. $attribute eq "timeoutAlive" and do {
  381. if ($command eq "set" and $value) {
  382. $hash->{timeoutAlive} = $value;
  383. refreshInternalMySTimer($hash,"Alive");
  384. } else {
  385. $hash->{timeoutAlive} = 0;
  386. }
  387. last;
  388. };
  389. }
  390. }
  391. sub onGatewayStarted($) {
  392. my ($hash) = @_;
  393. refreshInternalMySTimer($hash,"Alive") if ($hash->{timeoutAlive});
  394. }
  395. sub onPresentationMessage($$) {
  396. my ($hash,$msg) = @_;
  397. my $name = $hash->{NAME};
  398. my $nodeType = $msg->{subType};
  399. my $id = $msg->{childId};
  400. if ($id == 255) { #special id
  401. NODETYPE: {
  402. $nodeType == S_ARDUINO_NODE and do {
  403. CommandAttr(undef, "$name mode node");
  404. last;
  405. };
  406. $nodeType == S_ARDUINO_REPEATER_NODE and do {
  407. CommandAttr(undef, "$name mode repeater");
  408. last;
  409. };
  410. };
  411. CommandAttr(undef, "$name version $msg->{payload}");
  412. };
  413. my $readingMappings = $hash->{readingMappings};
  414. my $typeMappings = $hash->{typeMappings};
  415. if (my $sensorMappings = $hash->{sensorMappings}->{$nodeType}) {
  416. my $idStr = ($id > 0 ? $id : "");
  417. my @ret = ();
  418. foreach my $type (@{$sensorMappings->{sends}}) {
  419. next if (defined $readingMappings->{$id}->{$type});
  420. my $typeStr = $typeMappings->{$type}->{type};
  421. if ($hash->{IODev}->{'inclusion-mode'}) {
  422. if (my $ret = CommandAttr(undef,"$name mapReading_$typeStr$idStr $id $typeStr")) {
  423. push @ret,$ret;
  424. }
  425. } else {
  426. push @ret,"no mapReading for $id, $typeStr";
  427. }
  428. }
  429. foreach my $type (@{$sensorMappings->{receives}}) {
  430. my $typeMapping = $typeMappings->{$type};
  431. my $typeStr = $typeMapping->{type};
  432. next if (defined $hash->{sets}->{"$typeStr$idStr"});
  433. if ($hash->{IODev}->{'inclusion-mode'}) {
  434. my @values = ();
  435. if ($typeMapping->{range}) {
  436. @values = ('slider',$typeMapping->{range}->{min},$typeMapping->{range}->{step},$typeMapping->{range}->{max});
  437. } elsif ($typeMapping->{val}) {
  438. @values = values %{$typeMapping->{val}};
  439. }
  440. if (my $ret = CommandAttr(undef,"$name setReading_$typeStr$idStr".(@values ? " ".join (",",@values) : ""))) {
  441. push @ret,$ret;
  442. }
  443. } else {
  444. push @ret,"no setReading for $id, $typeStr";
  445. }
  446. }
  447. Log3 ($hash->{NAME}, 4, "MYSENSORS_DEVICE $hash->{NAME}: errors on C_PRESENTATION-message for childId $id, subType ".sensorTypeToStr($nodeType)." ".join (", ",@ret)) if @ret;
  448. }
  449. }
  450. sub onSetMessage($$) {
  451. my ($hash,$msg) = @_;
  452. my $name = $hash->{NAME};
  453. if (defined $msg->{payload}) {
  454. eval {
  455. my ($reading,$value) = rawToMappedReading($hash,$msg->{subType},$msg->{childId},$msg->{payload});
  456. readingsSingleUpdate($hash, $reading, $value, 1);
  457. };
  458. Log3 ($hash->{NAME}, 4, "MYSENSORS_DEVICE $hash->{NAME}: ignoring C_SET-message ".GP_Catch($@)) if $@;
  459. refreshInternalMySTimer($hash,"Alive") if $hash->{timeoutAlive};
  460. } else {
  461. Log3 ($hash->{NAME}, 5, "MYSENSORS_DEVICE $hash->{NAME}: ignoring C_SET-message without payload");
  462. };
  463. }
  464. sub onRequestMessage($$) {
  465. my ($hash,$msg) = @_;
  466. eval {
  467. my ($readingname,$val) = rawToMappedReading($hash, $msg->{subType}, $msg->{childId}, $msg->{payload});
  468. sendClientMessage($hash,
  469. childId => $msg->{childId},
  470. cmd => C_SET,
  471. subType => $msg->{subType},
  472. payload => ReadingsVal($hash->{NAME},$readingname,$val)
  473. );
  474. };
  475. refreshInternalMySTimer($hash,"Alive") if $hash->{timeoutAlive};
  476. Log3 ($hash->{NAME}, 4, "MYSENSORS_DEVICE $hash->{NAME}: ignoring C_REQ-message ".GP_Catch($@)) if $@;
  477. }
  478. sub onInternalMessage($$) {
  479. my ($hash,$msg) = @_;
  480. my $name = $hash->{NAME};
  481. my $type = $msg->{subType};
  482. my $typeStr = internalMessageTypeToStr($type);
  483. INTERNALMESSAGE: {
  484. $type == I_BATTERY_LEVEL and do {
  485. readingsSingleUpdate($hash, "batterylevel", $msg->{payload}, 1);
  486. Log3 ($name, 4, "MYSENSORS_DEVICE $name: batterylevel $msg->{payload}");
  487. last;
  488. };
  489. $type == I_TIME and do {
  490. if ($msg->{ack}) {
  491. Log3 ($name, 4, "MYSENSORS_DEVICE $name: response to time-request acknowledged");
  492. } else {
  493. sendClientMessage($hash,cmd => C_INTERNAL, childId => 255, subType => I_TIME, payload => time);
  494. Log3 ($name, 4, "MYSENSORS_DEVICE $name: update of time requested");
  495. }
  496. last;
  497. };
  498. $type == I_VERSION and do {
  499. $hash->{$typeStr} = $msg->{payload};
  500. last;
  501. };
  502. $type == I_ID_REQUEST and do {
  503. $hash->{$typeStr} = $msg->{payload};
  504. last;
  505. };
  506. $type == I_ID_RESPONSE and do {
  507. $hash->{$typeStr} = $msg->{payload};
  508. last;
  509. };
  510. $type == I_INCLUSION_MODE and do {
  511. $hash->{$typeStr} = $msg->{payload};
  512. last;
  513. };
  514. $type == I_CONFIG and do {
  515. if ($msg->{ack}) {
  516. Log3 ($name, 4, "MYSENSORS_DEVICE $name: response to config-request acknowledged");
  517. } else {
  518. readingsSingleUpdate($hash, "parentId", $msg->{payload}, 1);
  519. sendClientMessage($hash,cmd => C_INTERNAL, childId => 255, subType => I_CONFIG, payload => AttrVal($name,"config","M"));
  520. Log3 ($name, 4, "MYSENSORS_DEVICE $name: respond to config-request, node parentId = " . $msg->{payload});
  521. }
  522. last;
  523. };
  524. $type == I_FIND_PARENT and do {
  525. $hash->{$typeStr} = $msg->{payload};
  526. last;
  527. };
  528. $type == I_FIND_PARENT_RESPONSE and do {
  529. $hash->{$typeStr} = $msg->{payload};
  530. last;
  531. };
  532. $type == I_LOG_MESSAGE and do {
  533. $hash->{$typeStr} = $msg->{payload};
  534. last;
  535. };
  536. $type == I_CHILDREN and do {
  537. readingsSingleUpdate($hash, "state", "routingtable cleared", 1);
  538. Log3 ($name, 3, "MYSENSORS_DEVICE $name: routingtable cleared");
  539. last;
  540. };
  541. $type == I_SKETCH_NAME and do {
  542. $hash->{$typeStr} = $msg->{payload};
  543. readingsSingleUpdate($hash, "SKETCH_NAME", $msg->{payload}, 1);
  544. last;
  545. };
  546. $type == I_SKETCH_VERSION and do {
  547. $hash->{$typeStr} = $msg->{payload};
  548. readingsSingleUpdate($hash, "SKETCH_VERSION", $msg->{payload}, 1);
  549. last;
  550. };
  551. $type == I_REBOOT and do {
  552. $hash->{$typeStr} = $msg->{payload};
  553. last;
  554. };
  555. $type == I_GATEWAY_READY and do {
  556. $hash->{$typeStr} = $msg->{payload};
  557. last;
  558. };
  559. $type == I_REQUEST_SIGNING and do {
  560. $hash->{$typeStr} = $msg->{payload};
  561. last;
  562. };
  563. $type == I_GET_NONCE and do {
  564. $hash->{$typeStr} = $msg->{payload};
  565. last;
  566. };
  567. $type == I_GET_NONCE_RESPONSE and do {
  568. $hash->{$typeStr} = $msg->{payload};
  569. last;
  570. };
  571. }
  572. }
  573. sub sendClientMessage($%) {
  574. my ($hash,%msg) = @_;
  575. $msg{radioId} = $hash->{radioId};
  576. $msg{ack} = $hash->{ack} unless defined $msg{ack};
  577. sendMessage($hash->{IODev},%msg);
  578. refreshInternalMySTimer($hash,"Ack") if (($hash->{ack} or $hash->{IODev}->{ack}) and $hash->{timeoutAck}) ;
  579. }
  580. sub onStreamMessage($$) {
  581. my ($hash, $msg) = @_;
  582. }
  583. sub rawToMappedReading($$$$) {
  584. my($hash, $type, $childId, $value) = @_;
  585. my $name;
  586. if (defined (my $mapping = $hash->{readingMappings}->{$childId}->{$type})) {
  587. my $val = $mapping->{val} // $hash->{typeMappings}->{$type}->{val};
  588. return ($mapping->{name},defined $val ? ($val->{$value} // $value) : $value);
  589. }
  590. die "no reading-mapping for childId $childId, type ".($hash->{typeMappings}->{$type}->{type} ? $hash->{typeMappings}->{$type}->{type} : variableTypeToStr($type));
  591. }
  592. sub mappedReadingToRaw($$$) {
  593. my ($hash,$reading,$value) = @_;
  594. my $readingsMapping = $hash->{readingMappings};
  595. foreach my $id (keys %$readingsMapping) {
  596. my $readingTypesForId = $readingsMapping->{$id};
  597. foreach my $type (keys %$readingTypesForId) {
  598. if (($readingTypesForId->{$type}->{name} // "") eq $reading) {
  599. if (my $valueMappings = $readingTypesForId->{$type}->{val} // $hash->{typeMappings}->{$type}->{val}) {
  600. if (my @mappedValues = grep {$valueMappings->{$_} eq $value} keys %$valueMappings) {
  601. return ($type,$id,shift @mappedValues);
  602. }
  603. }
  604. return ($type,$id,$value);
  605. }
  606. }
  607. }
  608. die "no mapping for reading $reading";
  609. }
  610. sub refreshInternalMySTimer($$) {
  611. my ($hash,$calltype) = @_;
  612. my $name = $hash->{NAME};
  613. Log3 $name, 5, "$name: refreshInternalMySTimer called ($calltype)";
  614. if ($calltype eq "Alive") {
  615. RemoveInternalTimer("timeoutAlive:$name");
  616. my $nextTrigger = main::gettimeofday() + $hash->{timeoutAlive};
  617. InternalTimer($nextTrigger, "MYSENSORS::DEVICE::timeoutMySTimer", "timeoutAlive:$name", 0);
  618. if ($hash->{STATE} ne "NACK" or $hash->{STATE} eq "NACK" and @{$hash->{IODev}->{messagesForRadioId}->{$hash->{radioId}}->{messages}} == 0) {
  619. my $do_trigger = $hash->{STATE} ne "alive" ? 1 : 0;
  620. readingsSingleUpdate($hash,"state","alive",$do_trigger);
  621. }
  622. } elsif ($calltype eq "Ack") {
  623. RemoveInternalTimer("timeoutAck:$name");
  624. my $nextTrigger = main::gettimeofday() + $hash->{timeoutAck};
  625. InternalTimer($nextTrigger, "MYSENSORS::DEVICE::timeoutMySTimer", "timeoutAck:$name", 0);
  626. Log3 $name, 4, "$name: Ack timeout timer set at $nextTrigger";
  627. }
  628. }
  629. sub timeoutMySTimer($) {
  630. my ($calltype, $name) = split(':', $_[0]);
  631. my $hash = $main::defs{$name};
  632. Log3 $name, 5, "$name: timeoutMySTimer called ($calltype)";
  633. if ($calltype eq "timeoutAlive") {
  634. readingsSingleUpdate($hash,"state","dead",1) unless ($hash->{STATE} eq "NACK");
  635. } elsif ($calltype eq "timeoutAck") {
  636. #readingsSingleUpdate($hash,"state","timeoutAck passed",1);# if ($hash->{STATE} eq "NACK");
  637. if ($hash->{IODev}->{outstandingAck} == 0) {
  638. Log3 $name, 4, "$name: timeoutMySTimer called ($calltype), no outstanding Acks at all";
  639. readingsSingleUpdate($hash,"state","alive",1) if ($hash->{STATE} eq "NACK");
  640. } elsif (@{$hash->{IODev}->{messagesForRadioId}->{$hash->{radioId}}->{messages}}) {
  641. Log3 $name, 4, "$name: timeoutMySTimer called ($calltype), outstanding: $hash->{IODev}->{messagesForRadioId}->{$hash->{radioId}}->{messages}";
  642. readingsSingleUpdate($hash,"state","NACK",1) ;
  643. } else {
  644. Log3 $name, 4, "$name: timeoutMySTimer called ($calltype), no outstanding Acks for Node";
  645. readingsSingleUpdate($hash,"state","alive",1) if ($hash->{STATE} eq "NACK");
  646. }
  647. }
  648. }
  649. 1;
  650. =pod
  651. =item device
  652. =item summary includes MYSENSOR clients
  653. =item summary_DE integriert MYSENSOR Sensoren
  654. =begin html
  655. <a name="MYSENSORS_DEVICE"></a>
  656. <h3>MYSENSORS_DEVICE</h3>
  657. <ul>
  658. <p>represents a mysensors sensor attached to a mysensor-node</p>
  659. <p>requires a <a href="#MYSENSOR">MYSENSOR</a>-device as IODev</p>
  660. <a name="MYSENSORS_DEVICE define"></a>
  661. <p><b>Define</b></p>
  662. <ul>
  663. <p><code>define &lt;name&gt; MYSENSORS_DEVICE &lt;Sensor-type&gt; &lt;node-id&gt;</code><br/>Specifies the MYSENSOR_DEVICE device.</p>
  664. </ul>
  665. <a name="MYSENSORS_DEVICEset"></a>
  666. <p><b>Set</b></p>
  667. <ul>
  668. <li>
  669. <p><code>set &lt;name&gt; clear</code><br/>clears routing-table of a repeater-node</p>
  670. </li>
  671. <li>
  672. <p><code>set &lt;name&gt; time</code><br/>sets time for nodes (that support it)</p>
  673. </li>
  674. <li>
  675. <p><code>set &lt;name&gt; reboot</code><br/>reboots a node (requires a bootloader that supports it).<br/>Attention: Nodes that run the standard arduino-bootloader will enter a bootloop!<br/>Dis- and reconnect the nodes power to restart in this case.</p>
  676. </li>
  677. </ul>
  678. <a name="MYSENSORS_DEVICEattr"></a>
  679. <p><b>Attributes</b></p>
  680. <ul>
  681. <li>
  682. <p><code>attr &lt;name&gt; config [&lt;M|I&gt;]</code><br/>configures metric (M) or inch (I). Defaults to 'M'</p>
  683. </li>
  684. <li>
  685. <p><code>attr &lt;name&gt; setCommands [&lt;command:reading:value&gt;]*</code><br/>configures one or more commands that can be executed by set.<br/>e.g.: <code>attr &lt;name&gt; setCommands on:switch_1:on off:switch_1:off</code><br/>if list of commands contains both 'on' and 'off' <a href="#setExtensions">set extensions</a> are supported</p>
  686. </li>
  687. <li>
  688. <p><code>attr &lt;name&gt; setReading_&lt;reading&gt; [&lt;value&gt;]*</code><br/>configures a reading that can be modified by set-command<br/>e.g.: <code>attr &lt;name&gt; setReading_switch_1 on,off</code></p>
  689. </li>
  690. <li>
  691. <p><code>attr &lt;name&gt; mapReading_&lt;reading&gt; &lt;childId&gt; &lt;readingtype&gt; [&lt;value&gt;:&lt;mappedvalue&gt;]*</code><br/>configures the reading-name for a given childId and sensortype<br/>e.g.: <code>attr xxx mapReading_aussentemperatur 123 temperature</code></p>
  692. </li>
  693. <li>
  694. <p><code>att &lt;name&gt; requestAck</code><br/>request acknowledge from nodes.<br/>if set the Readings of nodes are updated not before requested acknowledge is received<br/>if not set the Readings of nodes are updated immediatly (not awaiting the acknowledge).<br/>May also be configured on the gateway for all nodes at once</p>
  695. </li>
  696. <li>
  697. <p><code>attr &lt;name&gt; mapReadingType_&lt;reading&gt; &lt;new reading name&gt; [&lt;value&gt;:&lt;mappedvalue&gt;]*</code><br/>configures reading type names that should be used instead of technical names<br/>e.g.: <code>attr xxx mapReadingType_LIGHT switch 0:on 1:off</code>to be used for mysensor Variabletypes that have no predefined defaults (yet)</p>
  698. </li>
  699. <li>
  700. <p><code>attr &lt;name&gt; timeoutAck &lt;time in seconds&gt;*</code><br/>configures timeout to set device state to NACK in case not all requested acks are received</p>
  701. </li>
  702. <li>
  703. <p><code>attr &lt;name&gt; timeoutAlive &lt;time in seconds&gt;*</code><br/>configures timeout to set device state to alive or dead. If messages from node are received within timout spec, state will be alive, otherwise dead. If state is NACK (in case timeoutAck is also set), state will only be changed to alive, if there are no outstanding messages to be sent.</p>
  704. </li>
  705. </ul>
  706. </ul>
  707. =end html
  708. =cut