From 9fba45b0040595c2627d5c9db3c4702cbd47af5c Mon Sep 17 00:00:00 2001 From: Jeff Siddall Date: Tue, 2 Jan 2018 00:09:49 -0500 Subject: [PATCH 1/3] Updates to MySensors.pm to fix issues with sensors which reported numeric data --- lib/MySensors.pm | 342 ++++++++++++++++++++++++++++++++++++----------- 1 file changed, 262 insertions(+), 80 deletions(-) mode change 100755 => 100644 lib/MySensors.pm diff --git a/lib/MySensors.pm b/lib/MySensors.pm old mode 100755 new mode 100644 index d4b8f1012..a9cf992bb --- a/lib/MySensors.pm +++ b/lib/MySensors.pm @@ -12,20 +12,36 @@ The current version supports Ethernet and serial gateways. The interface must be defined with 3 parameters: a type (serial or ethernet), an address (/dev/tty or an IP address:TCP port number) and a name used to -easily identify the interface. +easily identify the interface. In an MHT file the order is name, type, +address and (optionally) groups. The node must be defined with 3 parameters: a node ID, name and gateway -object. +object. In an MHT file the order is node ID, object, then name, gateway, +and (optionally) groups. The sensors must also be defined with 3 parameters: a sensor ID, name and -node object. +node object. In an MHT file the order is node ID, object, then name, node, +and (optionally) groups. Debugging information can be enabled by setting: debug=MySensors in a Misterhouse INI file. -In user code: +Define objects in an MHT file: + +# MYS gateways +MYS_INTERFACE, Basement_GW, Basement Gateway, serial, /dev/serial/by-id/usb-FTDI_FT232R_USB_UART_AJ03J18F-if00-port0, Basement + +# MYS nodes and sensors +MYS_NODE, 0, Basement_GW_ND, Basement Gateway Node, $Basement_GW, Basement +MYS_BINARY, 1, Humidifier_Flush_Pump, Humidifier Flush Pump, $Basement_GW_ND, Basement + +MYS_NODE, 2, Basement_MS_ND, Basement Motion Sensor Node, $Basement_GW, Basement +MYS_MOTION, 0, Basement_Laundry_MS, Basement Laundry Motion Sensor, $Basement_MS_ND, Basement +MYS_MOTION, 1, Downstairs_Hallway_MS, Downstairs Hallway Motion Sensor, $Basement_MS_ND, Basement + +or alternatively in user code: $basement_gateway = new MySensors::Interface(serial, "/dev/ttyACM0", "Basement Gateway"); $media_room_gateway = new MySensors::Interface(ethernet, "192.168.0.22:5003", "Media Room Gateway"); @@ -33,7 +49,10 @@ In user code: $bedroom_node = new MySensors::Node(1, "Bedroom Node", $media_room_gateway); $bedroom_motion = new MySensors::Motion(1, "Bedroom Motion", $bedroom_node); - if (state_now($bedroom_motion) eq ON) { print_log "Motion detected in the bedroom" }; + +Then to use the objects treat them as you would any other object based on a Generic_Item: + + if (state_now($bedroom_motion) eq motion) { print_log "Motion detected in the bedroom" }; =head2 DESCRIPTION @@ -50,26 +69,26 @@ radio is known as a node and the sensors themselves are known as children. Currently supports MySensors release 2.0 -Last modified: 2016-09-14 to fix some motion sensor bugs +Last modified: 2016-09-24 to add documentation for MHT files and add support +for S_MULTIMETER sensors plus minor enhancements and fixes Known Limitations: -1. The current implementation does not distinguish SET/REQ and treats them all -as SET -2. The current implementaton handles only a small number of the most common -sensor types. More may be added in the future. -3. The current implementation does not distinguish SET/REQ subtypes which -means any sensor that sends multiple subtypes will behave unpredictably. -4. The current implementation assumes all subtypes are read/write. This may -cause problems if an input is written to. For example, writing to most input -pins will enable/disable the internal pullup resistor. While this may be -desirable in some cases it could result in unexpected behavior. +1. Does not distinguish incoming SET/REQ and treats them all as SET +2. Handles only a small number of the most common sensor types. More may be +added in the future. +3. Does not distinguish SET/REQ subtypes for a single sensor which means any +sensor that sends multiple subtypes will behave unpredictably +4. Assumes all subtypes are read/write which may cause problems if an input +is written to. For example, writing to most input pins will enable/disable +the internal pullup resistor. While this may be desirable in some cases it +could result in unexpected behavior. 5. Minimal error trapping is done so errors in configuration or incompatible sensor implementations could cause unpredictable behavior or even crash Misterhouse. -6. The current implementation does not use ACKs -7. The current implementation does not handle units (or requests for units) -8. The current implementation does not attempt to reconnect any port or socket -disconnection +6. Does not handle units (or requests for units) +7. Does not attempt to reconnect any port or socket disconnection +8. Does not handle reloads so a restart might be required if files have +changed =head2 INHERITS @@ -89,43 +108,150 @@ use strict; # API details as of release 2.0 # For more information see: https://www.mysensors.org/download/serial_api_20 -our @types = ( 'presentation', 'set', 'req', 'internal', 'stream' ); +our @types = ( +'presentation', +'set', +'req', +'internal', +'stream' +); # Define names of presentations our @presentations = ( - 'S_DOOR', 'S_MOTION', 'S_SMOKE', 'S_LIGHT', 'S_DIMMER', 'S_COVER', - 'S_TEMP', 'S_HUM', 'S_BARO', 'S_WIND', 'S_RAIN', 'S_UV', - 'S_WEIGHT', 'S_POWER', 'S_HEATER', 'S_DISTANCE', 'S_LIGHT_LEVEL', 'S_ARDUINO_NODE', - 'S_ARDUINO_REPEATER_NODE', 'S_LOCK', 'S_IR', 'S_WATER', 'S_AIR_QUALITY', 'S_CUSTOM', - 'S_DUST', 'S_SCENE_CONTROLLER', 'S_RGB_LIGHT', 'S_RGBW_LIGHT', 'S_COLOR_SENSOR', 'S_HVAC', - 'S_MULTIMETER', 'S_SPRINKLER', 'S_WATER_LEAK', 'S_SOUND', 'S_VIBRATION', 'S_MOISTURE', - 'S_INFO', 'S_GAS', 'S_GPS', 'S_WATER_QUALITY' +'S_DOOR', +'S_MOTION', +'S_SMOKE', +'S_LIGHT', +'S_DIMMER', +'S_COVER', +'S_TEMP', +'S_HUM', +'S_BARO', +'S_WIND', +'S_RAIN', +'S_UV', +'S_WEIGHT', +'S_POWER', +'S_HEATER', +'S_DISTANCE', +'S_LIGHT_LEVEL', +'S_ARDUINO_NODE', +'S_ARDUINO_REPEATER_NODE', +'S_LOCK', +'S_IR', +'S_WATER', +'S_AIR_QUALITY', +'S_CUSTOM', +'S_DUST', +'S_SCENE_CONTROLLER', +'S_RGB_LIGHT', +'S_RGBW_LIGHT', +'S_COLOR_SENSOR', +'S_HVAC', +'S_MULTIMETER', +'S_SPRINKLER', +'S_WATER_LEAK', +'S_SOUND', +'S_VIBRATION', +'S_MOISTURE', +'S_INFO', +'S_GAS', +'S_GPS', +'S_WATER_QUALITY' ); # Define names for the set/req subtypes our @setreq = ( - 'V_TEMP', 'V_HUM', 'V_STATUS', 'V_PERCENTAGE', 'V_PRESSURE', 'V_FORECAST', - 'V_RAIN', 'V_RAINRATE', 'V_WIND', 'V_GUST', 'V_DIRECTION', 'V_UV', - 'V_WEIGHT', 'V_DISTANCE', 'V_IMPEDANCE', 'V_ARMED', 'V_TRIPPED', 'V_WATT', - 'V_KWH', 'V_SCENE_ON', 'V_SCENE_OFF', 'V_HVAC_FLOW_STATE', 'V_HVAC_SPEED', 'V_LIGHT_LEVEL', - 'V_VAR1', 'V_VAR2', 'V_VAR3', 'V_VAR4', 'V_VAR5', 'V_UP', - 'V_DOWN', 'V_STOP', 'V_IR_SEND', 'V_IR_RECEIVE', 'V_FLOW', 'V_VOLUME', - 'V_LOCK_STATUS', 'V_LEVEL', 'V_VOLTAGE', 'V_CURRENT', 'V_RGB', 'V_RGBW', - 'V_ID', 'V_UNIT_PREFIX', 'V_HVAC_SETPOINT_COOL', 'V_HVAC_SETPOINT_HEAT', 'V_HVAC_FLOW_MODE', 'V_TEXT', - 'V_CUSTOM', 'V_POSITION', 'V_IR_RECORD', 'V_PH', 'V_ORP', 'V_EC', - 'V_VAR', 'V_VA', 'V_POWER_FACTOR' +'V_TEMP', +'V_HUM', +'V_STATUS', +'V_PERCENTAGE', +'V_PRESSURE', +'V_FORECAST', +'V_RAIN', +'V_RAINRATE', +'V_WIND', +'V_GUST', +'V_DIRECTION', +'V_UV', +'V_WEIGHT', +'V_DISTANCE', +'V_IMPEDANCE', +'V_ARMED', +'V_TRIPPED', +'V_WATT', +'V_KWH', +'V_SCENE_ON', +'V_SCENE_OFF', +'V_HVAC_FLOW_STATE', +'V_HVAC_SPEED', +'V_LIGHT_LEVEL', +'V_VAR1', +'V_VAR2', +'V_VAR3', +'V_VAR4', +'V_VAR5', +'V_UP', +'V_DOWN', +'V_STOP', +'V_IR_SEND', +'V_IR_RECEIVE', +'V_FLOW', +'V_VOLUME', +'V_LOCK_STATUS', +'V_LEVEL', +'V_VOLTAGE', +'V_CURRENT', +'V_RGB', +'V_RGBW', +'V_ID', +'V_UNIT_PREFIX', +'V_HVAC_SETPOINT_COOL', +'V_HVAC_SETPOINT_HEAT', +'V_HVAC_FLOW_MODE', +'V_TEXT', +'V_CUSTOM', +'V_POSITION', +'V_IR_RECORD', +'V_PH', +'V_ORP', +'V_EC', +'V_VAR', +'V_VA', +'V_POWER_FACTOR' ); # Define names for the internals our @internals = ( - 'I_BATTERY_LEVEL', 'I_TIME', 'I_VERSION', 'I_ID_REQUEST', - 'I_ID_RESPONSE', 'I_INCLUSION_MODE', 'I_CONFIG', 'I_FIND_PARENT', - 'I_FIND_PARENT_RESPONSE', 'I_LOG_MESSAGE', 'I_CHILDREN', 'I_SKETCH_NAME', - 'I_SKETCH_VERSION', 'I_REBOOT', 'I_GATEWAY_READY', 'I_REQUEST_SIGNING', - 'I_GET_NONCE', 'I_GET_NONCE_RESPONSE', 'I_HEARTBEAT', 'I_PRESENTATION', - 'I_DISCOVER', 'I_DISCOVER_RESPONSE', 'I_HEARTBEAT_RESPONSE', 'I_LOCKED', - 'I_PING', 'I_PONG', 'I_REGISTRATION_REQUEST', 'I_REGISTRATION_RESPONSE', - 'I_DEBUG' +'I_BATTERY_LEVEL', +'I_TIME', +'I_VERSION', +'I_ID_REQUEST', +'I_ID_RESPONSE', +'I_INCLUSION_MODE', +'I_CONFIG', +'I_FIND_PARENT', +'I_FIND_PARENT_RESPONSE', +'I_LOG_MESSAGE', +'I_CHILDREN', +'I_SKETCH_NAME', +'I_SKETCH_VERSION', +'I_REBOOT', +'I_GATEWAY_READY', +'I_REQUEST_SIGNING', +'I_GET_NONCE', +'I_GET_NONCE_RESPONSE', +'I_HEARTBEAT', +'I_PRESENTATION', +'I_DISCOVER', +'I_DISCOVER_RESPONSE', +'I_HEARTBEAT_RESPONSE', +'I_LOCKED', +'I_PING', +'I_PONG', +'I_REGISTRATION_REQUEST', +'I_REGISTRATION_RESPONSE', +'I_DEBUG' ); =item C @@ -182,7 +308,9 @@ sub add_node { return $node_id; } else { - &::print_log("[MySensors] INFO: $$self{name} added node $$node{name} (node ID: $node_id)"); + &::print_log( + "[MySensors] INFO: $$self{name} added node $$node{name} (node ID: $node_id)" + ); $$self{nodes}{$node_id} = $node; } @@ -200,7 +328,9 @@ sub create_socket { print $name . "_socket\n"; # By default suport only TCP gateways. UDP could be added in the future - my $socket = new Socket_Item( undef, undef, $address, $name . "_socket", 'tcp', 'raw' ); + my $socket = + new Socket_Item( undef, undef, $address, $name . "_socket", 'tcp', + 'raw' ); start $socket; return $socket; @@ -312,7 +442,8 @@ sub parse_message { # Standard API messages are 6 values separated by semicolons if ( $message =~ /(\d{1,3});(\d{1,3});(\d{1,3});([01]);(\d{1,3});*(.*)/ ) { - my ( $node_id, $child_id, $type, $ack, $subtype, $data ) = ( $1, $2, $3, $4, $5, $6 ); + my ( $node_id, $child_id, $type, $ack, $subtype, $data ) = + ( $1, $2, $3, $4, $5, $6 ); # Handle presentation (type 0) messages if ( $type == 0 ) { @@ -327,13 +458,18 @@ sub parse_message { ); # Also check if this presentation subtype matches the type of the defined Misterhouse object. If not issue a warning. - if ( $$self{nodes}{$node_id}{sensors}{$child_id}{type} ne $subtype ) { + if ( $$self{nodes}{$node_id}{sensors}{$child_id}{type} ne + $subtype ) + { &::print_log( "[MySensors] WARNING: $$self{name} received presentation subtype for node=$node_id, child=$child_id, subtype=$subtype ($presentations[$subtype]) but object " - . $$self{nodes}{$node_id}{sensors}{$child_id}->get_object_name() + . $$self{nodes}{$node_id}{sensors}{$child_id} + ->get_object_name() . " is a " - . $$self{nodes}{$node_id}{sensors}{$child_id}->get_type() - . ". Check the sensor is defined as the correct type!" ); + . $$self{nodes}{$node_id}{sensors}{$child_id} + ->get_type() + . ". Check the sensor is defined as the correct type!" + ); } # Check for sensor ID 255 messages which are node level information @@ -373,10 +509,12 @@ sub parse_message { if ( exists $$self{nodes}{$node_id}{sensors}{$child_id} ) { &::print_log( "[MySensors] INFO: $$self{name} received set message for $$self{nodes}{$node_id}{name} (node ID: $node_id) $$self{nodes}{$node_id}{sensors}{$child_id}{name} (child ID: $child_id) to " - . $$self{nodes}{$node_id}{sensors}{$child_id}->MySensors::Sensor::convert_data_to_state($data) + . $$self{nodes}{$node_id}{sensors}{$child_id} + ->MySensors::Sensor::convert_data_to_state($data) . " ($data)" ) if $::Debug{mysensors}; - $$self{nodes}{$node_id}{sensors}{$child_id}->set_receive($data); + $$self{nodes}{$node_id}{sensors}{$child_id} + ->set_receive($data); # Check for sensor ID 255 messages which are node level information } @@ -410,7 +548,9 @@ sub parse_message { # Don't print SANCHK messages as they just clutter the logs if ( $data ne 'TSP:SANCHK:OK' ) { - &::print_log("[MySensors] INFO: $$self{name} received $internals[$subtype]: $data"); + &::print_log( + "[MySensors] INFO: $$self{name} received $internals[$subtype]: $data" + ); } # Handle requests for node ID (node 255, subtype 3) @@ -426,7 +566,9 @@ sub parse_message { # Issue the next available node ID my $next_id = $::Save{MySensors_next_node_id}; - &::print_log("[MySensors] INFO: $$self{name} received node ID request. Assigned node ID $next_id."); + &::print_log( + "[MySensors] INFO: $$self{name} received node ID request. Assigned node ID $next_id." + ); $self->send_message( 255, 255, 3, 0, 4, $next_id ); @@ -435,7 +577,8 @@ sub parse_message { # Handle other messages for valid node and child ID 255 which are used for node-level information } - elsif ( ( exists $$self{nodes}{$node_id} ) && ( $child_id == 255 ) ) { + elsif ( ( exists $$self{nodes}{$node_id} ) && ( $child_id == 255 ) ) + { # Handle sketch name information if ( $subtype == 11 ) { @@ -443,8 +586,8 @@ sub parse_message { # Set the sketch name on the node $$self{nodes}{$node_id}{sketch_name} = $data; &::print_log( - "[MySensors] INFO: $$self{name} received sketch name $data from $$self{nodes}{$node_id}{name} (node ID: $node_id) child ID $child_id") - if $::Debug{mysensors}; + "[MySensors] INFO: $$self{name} received sketch name $data from $$self{nodes}{$node_id}{name} (node ID: $node_id) child ID $child_id" + ) if $::Debug{mysensors}; # Handle sketch version information } @@ -460,7 +603,8 @@ sub parse_message { } elsif ( $subtype == 0 ) { $$self{nodes}{$node_id}{battery_level} = $data; - $$self{nodes}{$node_id}->set_state_log( "Battery: $data%", $self ); + $$self{nodes}{$node_id} + ->set_state_log( "Battery: $data%", $self ); &::print_log( "[MySensors] INFO: $$self{name} received battery level $data% from $$self{nodes}{$node_id}{name} (node ID: $node_id) child ID $child_id" ) if $::Debug{mysensors}; @@ -468,9 +612,11 @@ sub parse_message { # Handle heartbeat responses. This is used to update the state log, and thus the idle_time, of an object. } elsif ( $subtype == 22 ) { - $$self{nodes}{$node_id}->set_state_log( "Heartbeat", $self ); - &::print_log("[MySensors] INFO: $$self{name} received heartbeat from $$self{nodes}{$node_id}{name} (node ID: $node_id) child ID $child_id") - if $::Debug{mysensors}; + $$self{nodes}{$node_id} + ->set_state_log( "Heartbeat", $self ); + &::print_log( + "[MySensors] INFO: $$self{name} received heartbeat from $$self{nodes}{$node_id}{name} (node ID: $node_id) child ID $child_id" + ) if $::Debug{mysensors}; # All other types of messages are unhandled } @@ -491,19 +637,24 @@ sub parse_message { # Handle stream (type 4) messages } elsif ( $type == 4 ) { - &::print_log("[MySensors] WARN: $$self{name} received stream message (unsupported): node=$node_id, child=$child_id, subtype=$subtype, data=$data"); + &::print_log( + "[MySensors] WARN: $$self{name} received stream message (unsupported): node=$node_id, child=$child_id, subtype=$subtype, data=$data" + ); # Any other message is unrecognized } else { &::print_log( - "[MySensors] ERROR: $$self{name} received unrecognized message: node=$node_id, child=$child_id, type=$type, subtype=$subtype, data=$data"); + "[MySensors] ERROR: $$self{name} received unrecognized message: node=$node_id, child=$child_id, type=$type, subtype=$subtype, data=$data" + ); } # Otherwise a non-compliant MySensors message was received. This can be caused if gateway debug is enabled in the Arduino. } else { - &::print_log("[MySensors] DEBUG: $$self{name} received unknown Arduino message: $message") if $::Debug{mysensors}; + &::print_log( + "[MySensors] DEBUG: $$self{name} received unknown Arduino message: $message" + ) if $::Debug{mysensors}; } } @@ -593,7 +744,9 @@ sub add_sensor { return $child_id; } else { - &::print_log("[MySensors] INFO: $$self{gateway}{name} added sensor $$sensor{name} (child ID: $child_id) to $$self{name} (node ID $$self{node_id})"); + &::print_log( + "[MySensors] INFO: $$self{gateway}{name} added sensor $$sensor{name} (child ID: $child_id) to $$self{name} (node ID $$self{node_id})" + ); $$self{sensors}{$child_id} = $sensor; } @@ -677,12 +830,9 @@ sub convert_data_to_state { if ( exists $$self{data_to_state}{$data} ) { $state = $$self{data_to_state}{$data}; - # Some sensors return numerical values and for these the state and data are the same + # Assume all other sensors return numerical values and for these the state and data are the same } - elsif (( $$self{type} == 0 ) - || ( $$self{type} == 1 ) - || ( $$self{type} == 3 ) ) - { + else { $state = $data; } @@ -706,12 +856,9 @@ sub convert_state_to_data { if ( exists $$self{state_to_data}{$state} ) { $data = $$self{state_to_data}{$state}; - # Some sensors return numerical values and for these the state and data are the same + # Assume all other sensors return numerical values and for these the state and data are the same } - elsif (( $$self{type} == 0 ) - || ( $$self{type} == 1 ) - || ( $$self{type} == 3 ) ) - { + else { $data = $state; } @@ -741,7 +888,8 @@ sub set { if ( $data ne '' ) { # By default send the first (primary) subtype and use type 1 (SET) without ACK - $$self{node}{gateway}->send_message( $$self{node}{node_id}, $$self{child_id}, 1, 0, $$self{subtypes}[0], $data ); + $$self{node}{gateway}->send_message( $$self{node}{node_id}, + $$self{child_id}, 1, 0, $$self{subtypes}[0], $data ); } else { &::print_log( @@ -996,13 +1144,46 @@ sub new { $$self{type} = 7; # Humidity type sensors use the V_HUM subtype - $$self{subtypes} = [1]; + $$self{subtypes} = [ 1 ]; # Note: there are no predefined states or state mappings for temperature sensors return $self; } +###################### +# Multimeter Package # +###################### + +package MySensors::Multimeter; + +use strict; + +use parent-norequire, 'MySensors::Sensor'; + +=item C + +Instantiates a new multimeter sensor. + +=cut + +sub new { + my $class = shift; + + # Instantiate as a MySensors::Sensor first + my $self = $class->SUPER::new(@_); + + # Multimeter are presentation type 30 + $$self{type} = 30; + + # Multimeter type sensors use the V_IMPEDANCE, V_VOLTAGE and V_CURRENT subtypes + $$self{subtypes} = [ 14, 38, 39 ]; + + # Note: there are no predefined states or state mappings for multimeter sensors + + return $self; +} + =head2 AUTHOR Jeff Siddall (news@siddall.name) @@ -1016,3 +1197,4 @@ This program is distributed in the hope that it will be useful, but WITHOUT ANY You should have received a copy of the GNU General Public License along with this program; if not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. =cut + From bcf8d9875f42db7612c3e04990fe3778f7699430 Mon Sep 17 00:00:00 2001 From: Jeff Siddall Date: Thu, 4 Jan 2018 09:12:02 -0500 Subject: [PATCH 2/3] Updates to MySensors.pm to fix issues with sensors which reported numeric data --- lib/MySensors.pm | 237 +++++++++++------------------------------------ 1 file changed, 52 insertions(+), 185 deletions(-) diff --git a/lib/MySensors.pm b/lib/MySensors.pm index a9cf992bb..2802d9b51 100644 --- a/lib/MySensors.pm +++ b/lib/MySensors.pm @@ -108,150 +108,43 @@ use strict; # API details as of release 2.0 # For more information see: https://www.mysensors.org/download/serial_api_20 -our @types = ( -'presentation', -'set', -'req', -'internal', -'stream' -); +our @types = ( 'presentation', 'set', 'req', 'internal', 'stream' ); # Define names of presentations our @presentations = ( -'S_DOOR', -'S_MOTION', -'S_SMOKE', -'S_LIGHT', -'S_DIMMER', -'S_COVER', -'S_TEMP', -'S_HUM', -'S_BARO', -'S_WIND', -'S_RAIN', -'S_UV', -'S_WEIGHT', -'S_POWER', -'S_HEATER', -'S_DISTANCE', -'S_LIGHT_LEVEL', -'S_ARDUINO_NODE', -'S_ARDUINO_REPEATER_NODE', -'S_LOCK', -'S_IR', -'S_WATER', -'S_AIR_QUALITY', -'S_CUSTOM', -'S_DUST', -'S_SCENE_CONTROLLER', -'S_RGB_LIGHT', -'S_RGBW_LIGHT', -'S_COLOR_SENSOR', -'S_HVAC', -'S_MULTIMETER', -'S_SPRINKLER', -'S_WATER_LEAK', -'S_SOUND', -'S_VIBRATION', -'S_MOISTURE', -'S_INFO', -'S_GAS', -'S_GPS', -'S_WATER_QUALITY' + 'S_DOOR', 'S_MOTION', 'S_SMOKE', 'S_LIGHT', 'S_DIMMER', 'S_COVER', + 'S_TEMP', 'S_HUM', 'S_BARO', 'S_WIND', 'S_RAIN', 'S_UV', + 'S_WEIGHT', 'S_POWER', 'S_HEATER', 'S_DISTANCE', 'S_LIGHT_LEVEL', 'S_ARDUINO_NODE', + 'S_ARDUINO_REPEATER_NODE', 'S_LOCK', 'S_IR', 'S_WATER', 'S_AIR_QUALITY', 'S_CUSTOM', + 'S_DUST', 'S_SCENE_CONTROLLER', 'S_RGB_LIGHT', 'S_RGBW_LIGHT', 'S_COLOR_SENSOR', 'S_HVAC', + 'S_MULTIMETER', 'S_SPRINKLER', 'S_WATER_LEAK', 'S_SOUND', 'S_VIBRATION', 'S_MOISTURE', + 'S_INFO', 'S_GAS', 'S_GPS', 'S_WATER_QUALITY' ); # Define names for the set/req subtypes our @setreq = ( -'V_TEMP', -'V_HUM', -'V_STATUS', -'V_PERCENTAGE', -'V_PRESSURE', -'V_FORECAST', -'V_RAIN', -'V_RAINRATE', -'V_WIND', -'V_GUST', -'V_DIRECTION', -'V_UV', -'V_WEIGHT', -'V_DISTANCE', -'V_IMPEDANCE', -'V_ARMED', -'V_TRIPPED', -'V_WATT', -'V_KWH', -'V_SCENE_ON', -'V_SCENE_OFF', -'V_HVAC_FLOW_STATE', -'V_HVAC_SPEED', -'V_LIGHT_LEVEL', -'V_VAR1', -'V_VAR2', -'V_VAR3', -'V_VAR4', -'V_VAR5', -'V_UP', -'V_DOWN', -'V_STOP', -'V_IR_SEND', -'V_IR_RECEIVE', -'V_FLOW', -'V_VOLUME', -'V_LOCK_STATUS', -'V_LEVEL', -'V_VOLTAGE', -'V_CURRENT', -'V_RGB', -'V_RGBW', -'V_ID', -'V_UNIT_PREFIX', -'V_HVAC_SETPOINT_COOL', -'V_HVAC_SETPOINT_HEAT', -'V_HVAC_FLOW_MODE', -'V_TEXT', -'V_CUSTOM', -'V_POSITION', -'V_IR_RECORD', -'V_PH', -'V_ORP', -'V_EC', -'V_VAR', -'V_VA', -'V_POWER_FACTOR' + 'V_TEMP', 'V_HUM', 'V_STATUS', 'V_PERCENTAGE', 'V_PRESSURE', 'V_FORECAST', + 'V_RAIN', 'V_RAINRATE', 'V_WIND', 'V_GUST', 'V_DIRECTION', 'V_UV', + 'V_WEIGHT', 'V_DISTANCE', 'V_IMPEDANCE', 'V_ARMED', 'V_TRIPPED', 'V_WATT', + 'V_KWH', 'V_SCENE_ON', 'V_SCENE_OFF', 'V_HVAC_FLOW_STATE', 'V_HVAC_SPEED', 'V_LIGHT_LEVEL', + 'V_VAR1', 'V_VAR2', 'V_VAR3', 'V_VAR4', 'V_VAR5', 'V_UP', + 'V_DOWN', 'V_STOP', 'V_IR_SEND', 'V_IR_RECEIVE', 'V_FLOW', 'V_VOLUME', + 'V_LOCK_STATUS', 'V_LEVEL', 'V_VOLTAGE', 'V_CURRENT', 'V_RGB', 'V_RGBW', + 'V_ID', 'V_UNIT_PREFIX', 'V_HVAC_SETPOINT_COOL', 'V_HVAC_SETPOINT_HEAT', 'V_HVAC_FLOW_MODE', 'V_TEXT', + 'V_CUSTOM', 'V_POSITION', 'V_IR_RECORD', 'V_PH', 'V_ORP', 'V_EC', + 'V_VAR', 'V_VA', 'V_POWER_FACTOR' ); # Define names for the internals our @internals = ( -'I_BATTERY_LEVEL', -'I_TIME', -'I_VERSION', -'I_ID_REQUEST', -'I_ID_RESPONSE', -'I_INCLUSION_MODE', -'I_CONFIG', -'I_FIND_PARENT', -'I_FIND_PARENT_RESPONSE', -'I_LOG_MESSAGE', -'I_CHILDREN', -'I_SKETCH_NAME', -'I_SKETCH_VERSION', -'I_REBOOT', -'I_GATEWAY_READY', -'I_REQUEST_SIGNING', -'I_GET_NONCE', -'I_GET_NONCE_RESPONSE', -'I_HEARTBEAT', -'I_PRESENTATION', -'I_DISCOVER', -'I_DISCOVER_RESPONSE', -'I_HEARTBEAT_RESPONSE', -'I_LOCKED', -'I_PING', -'I_PONG', -'I_REGISTRATION_REQUEST', -'I_REGISTRATION_RESPONSE', -'I_DEBUG' + 'I_BATTERY_LEVEL', 'I_TIME', 'I_VERSION', 'I_ID_REQUEST', + 'I_ID_RESPONSE', 'I_INCLUSION_MODE', 'I_CONFIG', 'I_FIND_PARENT', + 'I_FIND_PARENT_RESPONSE', 'I_LOG_MESSAGE', 'I_CHILDREN', 'I_SKETCH_NAME', + 'I_SKETCH_VERSION', 'I_REBOOT', 'I_GATEWAY_READY', 'I_REQUEST_SIGNING', + 'I_GET_NONCE', 'I_GET_NONCE_RESPONSE', 'I_HEARTBEAT', 'I_PRESENTATION', + 'I_DISCOVER', 'I_DISCOVER_RESPONSE', 'I_HEARTBEAT_RESPONSE', 'I_LOCKED', + 'I_PING', 'I_PONG', 'I_REGISTRATION_REQUEST', 'I_REGISTRATION_RESPONSE', + 'I_DEBUG' ); =item C @@ -308,9 +201,7 @@ sub add_node { return $node_id; } else { - &::print_log( - "[MySensors] INFO: $$self{name} added node $$node{name} (node ID: $node_id)" - ); + &::print_log( "[MySensors] INFO: $$self{name} added node $$node{name} (node ID: $node_id)" ); $$self{nodes}{$node_id} = $node; } @@ -328,9 +219,7 @@ sub create_socket { print $name . "_socket\n"; # By default suport only TCP gateways. UDP could be added in the future - my $socket = - new Socket_Item( undef, undef, $address, $name . "_socket", 'tcp', - 'raw' ); + my $socket = new Socket_Item( undef, undef, $address, $name . "_socket", 'tcp', 'raw' ); start $socket; return $socket; @@ -442,8 +331,7 @@ sub parse_message { # Standard API messages are 6 values separated by semicolons if ( $message =~ /(\d{1,3});(\d{1,3});(\d{1,3});([01]);(\d{1,3});*(.*)/ ) { - my ( $node_id, $child_id, $type, $ack, $subtype, $data ) = - ( $1, $2, $3, $4, $5, $6 ); + my ( $node_id, $child_id, $type, $ack, $subtype, $data ) = ( $1, $2, $3, $4, $5, $6 ); # Handle presentation (type 0) messages if ( $type == 0 ) { @@ -458,18 +346,13 @@ sub parse_message { ); # Also check if this presentation subtype matches the type of the defined Misterhouse object. If not issue a warning. - if ( $$self{nodes}{$node_id}{sensors}{$child_id}{type} ne - $subtype ) - { + if ( $$self{nodes}{$node_id}{sensors}{$child_id}{type} ne $subtype ) { &::print_log( "[MySensors] WARNING: $$self{name} received presentation subtype for node=$node_id, child=$child_id, subtype=$subtype ($presentations[$subtype]) but object " - . $$self{nodes}{$node_id}{sensors}{$child_id} - ->get_object_name() + . $$self{nodes}{$node_id}{sensors}{$child_id}->get_object_name() . " is a " - . $$self{nodes}{$node_id}{sensors}{$child_id} - ->get_type() - . ". Check the sensor is defined as the correct type!" - ); + . $$self{nodes}{$node_id}{sensors}{$child_id}->get_type() + . ". Check the sensor is defined as the correct type!" ); } # Check for sensor ID 255 messages which are node level information @@ -509,12 +392,10 @@ sub parse_message { if ( exists $$self{nodes}{$node_id}{sensors}{$child_id} ) { &::print_log( "[MySensors] INFO: $$self{name} received set message for $$self{nodes}{$node_id}{name} (node ID: $node_id) $$self{nodes}{$node_id}{sensors}{$child_id}{name} (child ID: $child_id) to " - . $$self{nodes}{$node_id}{sensors}{$child_id} - ->MySensors::Sensor::convert_data_to_state($data) + . $$self{nodes}{$node_id}{sensors}{$child_id}->MySensors::Sensor::convert_data_to_state($data) . " ($data)" ) if $::Debug{mysensors}; - $$self{nodes}{$node_id}{sensors}{$child_id} - ->set_receive($data); + $$self{nodes}{$node_id}{sensors}{$child_id}->set_receive($data); # Check for sensor ID 255 messages which are node level information } @@ -548,9 +429,7 @@ sub parse_message { # Don't print SANCHK messages as they just clutter the logs if ( $data ne 'TSP:SANCHK:OK' ) { - &::print_log( - "[MySensors] INFO: $$self{name} received $internals[$subtype]: $data" - ); + &::print_log( "[MySensors] INFO: $$self{name} received $internals[$subtype]: $data" ); } # Handle requests for node ID (node 255, subtype 3) @@ -566,9 +445,7 @@ sub parse_message { # Issue the next available node ID my $next_id = $::Save{MySensors_next_node_id}; - &::print_log( - "[MySensors] INFO: $$self{name} received node ID request. Assigned node ID $next_id." - ); + &::print_log( "[MySensors] INFO: $$self{name} received node ID request. Assigned node ID $next_id." ); $self->send_message( 255, 255, 3, 0, 4, $next_id ); @@ -577,8 +454,7 @@ sub parse_message { # Handle other messages for valid node and child ID 255 which are used for node-level information } - elsif ( ( exists $$self{nodes}{$node_id} ) && ( $child_id == 255 ) ) - { + elsif ( ( exists $$self{nodes}{$node_id} ) && ( $child_id == 255 ) ) { # Handle sketch name information if ( $subtype == 11 ) { @@ -586,8 +462,8 @@ sub parse_message { # Set the sketch name on the node $$self{nodes}{$node_id}{sketch_name} = $data; &::print_log( - "[MySensors] INFO: $$self{name} received sketch name $data from $$self{nodes}{$node_id}{name} (node ID: $node_id) child ID $child_id" - ) if $::Debug{mysensors}; + "[MySensors] INFO: $$self{name} received sketch name $data from $$self{nodes}{$node_id}{name} (node ID: $node_id) child ID $child_id" ) + if $::Debug{mysensors}; # Handle sketch version information } @@ -603,8 +479,7 @@ sub parse_message { } elsif ( $subtype == 0 ) { $$self{nodes}{$node_id}{battery_level} = $data; - $$self{nodes}{$node_id} - ->set_state_log( "Battery: $data%", $self ); + $$self{nodes}{$node_id}->set_state_log( "Battery: $data%", $self ); &::print_log( "[MySensors] INFO: $$self{name} received battery level $data% from $$self{nodes}{$node_id}{name} (node ID: $node_id) child ID $child_id" ) if $::Debug{mysensors}; @@ -612,11 +487,10 @@ sub parse_message { # Handle heartbeat responses. This is used to update the state log, and thus the idle_time, of an object. } elsif ( $subtype == 22 ) { - $$self{nodes}{$node_id} - ->set_state_log( "Heartbeat", $self ); + $$self{nodes}{$node_id}->set_state_log( "Heartbeat", $self ); &::print_log( - "[MySensors] INFO: $$self{name} received heartbeat from $$self{nodes}{$node_id}{name} (node ID: $node_id) child ID $child_id" - ) if $::Debug{mysensors}; + "[MySensors] INFO: $$self{name} received heartbeat from $$self{nodes}{$node_id}{name} (node ID: $node_id) child ID $child_id" ) + if $::Debug{mysensors}; # All other types of messages are unhandled } @@ -638,23 +512,19 @@ sub parse_message { } elsif ( $type == 4 ) { &::print_log( - "[MySensors] WARN: $$self{name} received stream message (unsupported): node=$node_id, child=$child_id, subtype=$subtype, data=$data" - ); + "[MySensors] WARN: $$self{name} received stream message (unsupported): node=$node_id, child=$child_id, subtype=$subtype, data=$data" ); # Any other message is unrecognized } else { &::print_log( - "[MySensors] ERROR: $$self{name} received unrecognized message: node=$node_id, child=$child_id, type=$type, subtype=$subtype, data=$data" - ); + "[MySensors] ERROR: $$self{name} received unrecognized message: node=$node_id, child=$child_id, type=$type, subtype=$subtype, data=$data" ); } # Otherwise a non-compliant MySensors message was received. This can be caused if gateway debug is enabled in the Arduino. } else { - &::print_log( - "[MySensors] DEBUG: $$self{name} received unknown Arduino message: $message" - ) if $::Debug{mysensors}; + &::print_log( "[MySensors] DEBUG: $$self{name} received unknown Arduino message: $message" ) if $::Debug{mysensors}; } } @@ -744,9 +614,7 @@ sub add_sensor { return $child_id; } else { - &::print_log( - "[MySensors] INFO: $$self{gateway}{name} added sensor $$sensor{name} (child ID: $child_id) to $$self{name} (node ID $$self{node_id})" - ); + &::print_log( "[MySensors] INFO: $$self{gateway}{name} added sensor $$sensor{name} (child ID: $child_id) to $$self{name} (node ID $$self{node_id})" ); $$self{sensors}{$child_id} = $sensor; } @@ -830,7 +698,7 @@ sub convert_data_to_state { if ( exists $$self{data_to_state}{$data} ) { $state = $$self{data_to_state}{$data}; - # Assume all other sensors return numerical values and for these the state and data are the same + # Assume all other sensors return numerical values and for these the state and data are the same } else { $state = $data; @@ -856,7 +724,7 @@ sub convert_state_to_data { if ( exists $$self{state_to_data}{$state} ) { $data = $$self{state_to_data}{$state}; - # Assume all other sensors return numerical values and for these the state and data are the same + # Assume all other sensors return numerical values and for these the state and data are the same } else { $data = $state; @@ -888,8 +756,7 @@ sub set { if ( $data ne '' ) { # By default send the first (primary) subtype and use type 1 (SET) without ACK - $$self{node}{gateway}->send_message( $$self{node}{node_id}, - $$self{child_id}, 1, 0, $$self{subtypes}[0], $data ); + $$self{node}{gateway}->send_message( $$self{node}{node_id}, $$self{child_id}, 1, 0, $$self{subtypes}[0], $data ); } else { &::print_log( @@ -1144,7 +1011,7 @@ sub new { $$self{type} = 7; # Humidity type sensors use the V_HUM subtype - $$self{subtypes} = [ 1 ]; + $$self{subtypes} = [1]; # Note: there are no predefined states or state mappings for temperature sensors From 5cd40cd7cd4c32dceb3b76d65bde5f7062371856 Mon Sep 17 00:00:00 2001 From: Jeff Siddall Date: Fri, 9 Feb 2018 13:46:08 -0500 Subject: [PATCH 3/3] Updates to MySensors.pm to add time support and custom sensors --- lib/MySensors.pm | 206 ++++++++++++++++++++++++++++++++++++++++------- 1 file changed, 179 insertions(+), 27 deletions(-) diff --git a/lib/MySensors.pm b/lib/MySensors.pm index 2802d9b51..3fdc6eda3 100644 --- a/lib/MySensors.pm +++ b/lib/MySensors.pm @@ -2,6 +2,8 @@ # Interface Package # ##################### +=pod + =head1 B =head2 SYNOPSIS @@ -67,29 +69,41 @@ Maximum payload is 25 bytes Note that in MySensor terms the interface is known as a gateway, the sensor radio is known as a node and the sensors themselves are known as children. -Currently supports MySensors release 2.0 +Currently supports MySensors release 2.x -Last modified: 2016-09-24 to add documentation for MHT files and add support -for S_MULTIMETER sensors plus minor enhancements and fixes +Last modified: 2018-02-08 to add custom sensors and update POD Known Limitations: -1. Does not distinguish incoming SET/REQ and treats them all as SET -2. Handles only a small number of the most common sensor types. More may be + +=over + +=item 1. Does not distinguish incoming SET/REQ and treats them all as SET + +=item 2. Handles only a small number of the most common sensor types. More may be added in the future. -3. Does not distinguish SET/REQ subtypes for a single sensor which means any + +=item 3. Does not distinguish SET/REQ subtypes for a single sensor which means any sensor that sends multiple subtypes will behave unpredictably -4. Assumes all subtypes are read/write which may cause problems if an input + +=item 4. Assumes all subtypes are read/write which may cause problems if an input is written to. For example, writing to most input pins will enable/disable the internal pullup resistor. While this may be desirable in some cases it could result in unexpected behavior. -5. Minimal error trapping is done so errors in configuration or incompatible +=cut + +=item 5. Minimal error trapping is done so errors in configuration or incompatible sensor implementations could cause unpredictable behavior or even crash Misterhouse. -6. Does not handle units (or requests for units) -7. Does not attempt to reconnect any port or socket disconnection -8. Does not handle reloads so a restart might be required if files have + +=item 6. Does not handle units (or requests for units) + +=item 7. Does not attempt to reconnect any port or socket disconnection + +=item 8. Does not handle reloads so a restart might be required if files have changed +=back + =head2 INHERITS L @@ -105,6 +119,7 @@ package MySensors::Interface; use parent 'Generic_Item'; use strict; +use DateTime; # API details as of release 2.0 # For more information see: https://www.mysensors.org/download/serial_api_20 @@ -201,7 +216,7 @@ sub add_node { return $node_id; } else { - &::print_log( "[MySensors] INFO: $$self{name} added node $$node{name} (node ID: $node_id)" ); + &::print_log("[MySensors] INFO: $$self{name} added node $$node{name} (node ID: $node_id)"); $$self{nodes}{$node_id} = $node; } @@ -429,7 +444,7 @@ sub parse_message { # Don't print SANCHK messages as they just clutter the logs if ( $data ne 'TSP:SANCHK:OK' ) { - &::print_log( "[MySensors] INFO: $$self{name} received $internals[$subtype]: $data" ); + &::print_log("[MySensors] INFO: $$self{name} received $internals[$subtype]: $data"); } # Handle requests for node ID (node 255, subtype 3) @@ -445,7 +460,7 @@ sub parse_message { # Issue the next available node ID my $next_id = $::Save{MySensors_next_node_id}; - &::print_log( "[MySensors] INFO: $$self{name} received node ID request. Assigned node ID $next_id." ); + &::print_log("[MySensors] INFO: $$self{name} received node ID request. Assigned node ID $next_id."); $self->send_message( 255, 255, 3, 0, 4, $next_id ); @@ -462,7 +477,7 @@ sub parse_message { # Set the sketch name on the node $$self{nodes}{$node_id}{sketch_name} = $data; &::print_log( - "[MySensors] INFO: $$self{name} received sketch name $data from $$self{nodes}{$node_id}{name} (node ID: $node_id) child ID $child_id" ) + "[MySensors] INFO: $$self{name} received sketch name $data from $$self{nodes}{$node_id}{name} (node ID: $node_id) child ID $child_id") if $::Debug{mysensors}; # Handle sketch version information @@ -484,12 +499,25 @@ sub parse_message { "[MySensors] INFO: $$self{name} received battery level $data% from $$self{nodes}{$node_id}{name} (node ID: $node_id) child ID $child_id" ) if $::Debug{mysensors}; + # Handle time requests. Note that the time returned to MyS devices must be in local time but $Time is UTC. + } + elsif ( $subtype == 1 ) { + + # Time needs to be local to controller timezone so use the DateTime library to convert this + my $dt = DateTime->now(); + my $tz = DateTime::TimeZone->new( name => "local" ); + $dt->add( seconds => $tz->offset_for_datetime($dt) ); + + $self->send_message( $node_id, 255, 3, 0, 1, $dt->epoch ); + &::print_log( + "[MySensors] INFO: $$self{name} received time request from $$self{nodes}{$node_id}{name} (node ID: $node_id) child ID $child_id. Responded with time $main::Time." + ) if $::Debug{mysensors}; + # Handle heartbeat responses. This is used to update the state log, and thus the idle_time, of an object. } elsif ( $subtype == 22 ) { $$self{nodes}{$node_id}->set_state_log( "Heartbeat", $self ); - &::print_log( - "[MySensors] INFO: $$self{name} received heartbeat from $$self{nodes}{$node_id}{name} (node ID: $node_id) child ID $child_id" ) + &::print_log("[MySensors] INFO: $$self{name} received heartbeat from $$self{nodes}{$node_id}{name} (node ID: $node_id) child ID $child_id") if $::Debug{mysensors}; # All other types of messages are unhandled @@ -511,20 +539,19 @@ sub parse_message { # Handle stream (type 4) messages } elsif ( $type == 4 ) { - &::print_log( - "[MySensors] WARN: $$self{name} received stream message (unsupported): node=$node_id, child=$child_id, subtype=$subtype, data=$data" ); + &::print_log("[MySensors] WARN: $$self{name} received stream message (unsupported): node=$node_id, child=$child_id, subtype=$subtype, data=$data"); # Any other message is unrecognized } else { &::print_log( - "[MySensors] ERROR: $$self{name} received unrecognized message: node=$node_id, child=$child_id, type=$type, subtype=$subtype, data=$data" ); + "[MySensors] ERROR: $$self{name} received unrecognized message: node=$node_id, child=$child_id, type=$type, subtype=$subtype, data=$data"); } # Otherwise a non-compliant MySensors message was received. This can be caused if gateway debug is enabled in the Arduino. } else { - &::print_log( "[MySensors] DEBUG: $$self{name} received unknown Arduino message: $message" ) if $::Debug{mysensors}; + &::print_log("[MySensors] DEBUG: $$self{name} received unknown Arduino message: $message") if $::Debug{mysensors}; } } @@ -557,12 +584,26 @@ sub send_message { return 0; } +=back + +=head2 CHILD PACKAGES + +The following are child packages to the interface + +All varieties of sensors are children of the MySensors::Sensor + +=cut + ################ # Node Package # ################ -# Note that the nodes are also Generic_Items not MySensors::Interfaces. This -# is similar to the Insteon design but not X10. +=head3 NODE PACKAGE + +Note that the nodes are also Generic_Items not MySensors::Interfaces. This +is similar to the Insteon design but not X10. + +=cut package MySensors::Node; @@ -570,6 +611,8 @@ use strict; use parent 'Generic_Item'; +=over + =item C Instantiates a new node. @@ -602,6 +645,8 @@ Adds a new child sensor to a node. Returns zero for success or the failed child_id otherwise. +=back + =cut sub add_sensor { @@ -614,7 +659,7 @@ sub add_sensor { return $child_id; } else { - &::print_log( "[MySensors] INFO: $$self{gateway}{name} added sensor $$sensor{name} (child ID: $child_id) to $$self{name} (node ID $$self{node_id})" ); + &::print_log("[MySensors] INFO: $$self{gateway}{name} added sensor $$sensor{name} (child ID: $child_id) to $$self{name} (node ID $$self{node_id})"); $$self{sensors}{$child_id} = $sensor; } @@ -625,7 +670,11 @@ sub add_sensor { # Sensor Package # ################## -# All varieties of sensors are children of the MySensors::Sensor +=head3 SENSOR PACKAGE + +All sensors are children of the sensor package + +=cut package MySensors::Sensor; @@ -633,6 +682,8 @@ use strict; use parent 'Generic_Item'; +=over + =item C Instantiates a new sensor. @@ -698,8 +749,9 @@ sub convert_data_to_state { if ( exists $$self{data_to_state}{$data} ) { $state = $$self{data_to_state}{$data}; - # Assume all other sensors return numerical values and for these the state and data are the same } + + # Assume all other sensors return numerical values and for these the state and data are the same else { $state = $data; } @@ -724,8 +776,9 @@ sub convert_state_to_data { if ( exists $$self{state_to_data}{$state} ) { $data = $$self{state_to_data}{$state}; - # Assume all other sensors return numerical values and for these the state and data are the same } + + # Assume all other sensors return numerical values and for these the state and data are the same else { $data = $state; } @@ -778,6 +831,8 @@ interface. Returns state +=back + =cut sub set_receive { @@ -804,16 +859,24 @@ sub set_receive { # Door Package # ################ +=head3 DOOR PACKAGE + +=cut + package MySensors::Door; use strict; use parent-norequire, 'MySensors::Sensor'; +=over + =item C Instantiates a new door/window sensor. +=back + =cut sub new { @@ -842,16 +905,24 @@ sub new { # Motion Sensor Package # ######################### +=head3 MOTION SENSOR PACKAGE + +=cut + package MySensors::Motion; use strict; use parent-norequire, 'MySensors::Sensor'; +=over + =item C Instantiates a new motion sensor. +=back + =cut sub new { @@ -880,16 +951,24 @@ sub new { # Light Package # ################# +=head3 LIGHT PACKAGE + +=cut + package MySensors::Light; use strict; use parent-norequire, 'MySensors::Sensor'; +=over + =item C Instantiates a new light. +=back + =cut sub new { @@ -918,16 +997,24 @@ sub new { # Binary Package # ################## +=head3 BINARY PACKAGE + +=cut + package MySensors::Binary; use strict; use parent-norequire, 'MySensors::Sensor'; +=over + =item C Instantiates a new binary sensor. This is an alias for a light. +=back + =cut sub new { @@ -956,16 +1043,24 @@ sub new { # Temperature Package # ####################### +=head3 TEMPERATURE PACKAGE + +=cut + package MySensors::Temperature; use strict; use parent-norequire, 'MySensors::Sensor'; +=over + =item C Instantiates a new temperature sensor. +=back + =cut sub new { @@ -989,16 +1084,24 @@ sub new { # Humidity Package # #################### +=head3 HUMIDITY PACKAGE + +=cut + package MySensors::Humidity; use strict; use parent-norequire, 'MySensors::Sensor'; +=over + =item C Instantiates a new humidity sensor. +=back + =cut sub new { @@ -1018,20 +1121,69 @@ sub new { return $self; } +################## +# Custom Package # +################## + +=head3 CUSTOM PACKAGE + +=cut + +package MySensors::Custom; + +use strict; + +use parent-norequire, 'MySensors::Sensor'; + +=over + +=item C + +Instantiates a new custom sensor. + +=back + +=cut + +sub new { + my $class = shift; + + # Instantiate as a MySensors::Sensor first + my $self = $class->SUPER::new(@_); + + # Custom are presentation type 23 + $$self{type} = 23; + + # Custom type sensors use the V_CUSTOM subtype + $$self{subtypes} = [48]; + + # Note: there are no predefined states or state mappings for custom sensors + + return $self; +} + ###################### # Multimeter Package # ###################### +=head3 MULTIMETER PACKAGE + +=cut + package MySensors::Multimeter; use strict; use parent-norequire, 'MySensors::Sensor'; +=over + =item C Instantiates a new multimeter sensor. +=back + =cut sub new {