2017-09-25 19:39:16 +00:00
##############################################
2014-10-22 20:37:52 +00:00
#
# fhem bridge to MySensors (see http://mysensors.org)
#
# Copyright (C) 2014 Norbert Truchsess
2019-01-04 11:58:28 +00:00
# Copyright (C) 2019 Hauswart@forum.fhem.de
2022-02-10 05:13:15 +00:00
# Copyright (C) 2022 Beta-User@forum.fhem.de
2014-10-22 20:37:52 +00:00
#
# This file is part of fhem.
#
# Fhem is free software: you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation, either version 2 of the License, or
# (at your option) any later version.
#
# Fhem is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with fhem. If not, see <http://www.gnu.org/licenses/>.
#
2018-06-22 08:21:12 +00:00
# $Id$
2014-10-22 20:37:52 +00:00
#
##############################################
2020-05-13 06:35:26 +00:00
package MYSENSORS::DEVICE ; ## no critic 'Package declaration'
2014-10-22 20:37:52 +00:00
use strict ;
use warnings ;
2020-05-13 06:35:26 +00:00
use SetExtensions ;
use Time::Local qw( timegm_nocheck ) ;
use Time::HiRes qw( gettimeofday ) ;
2014-10-22 20:37:52 +00:00
use GPUtils qw( :all ) ;
2020-10-06 05:25:49 +00:00
use FHEM::Meta ;
2014-10-22 20:37:52 +00:00
2020-05-13 06:35:26 +00:00
sub main ::MYSENSORS_DEVICE_Initialize { goto & Initialize } ;
2014-10-22 20:37:52 +00:00
BEGIN {
2019-01-11 06:08:07 +00:00
main:: LoadModule ( "MYSENSORS" ) ;
MYSENSORS - > import ( qw( :all ) ) ;
2014-10-22 20:37:52 +00:00
GP_Import ( qw(
AttrVal
readingsSingleUpdate
2019-01-04 11:58:28 +00:00
readingsBeginUpdate
readingsEndUpdate
readingsBulkUpdate
2014-10-25 10:17:25 +00:00
CommandAttr
CommandDeleteAttr
2014-10-22 20:37:52 +00:00
CommandDeleteReading
AssignIoPort
Log3
2014-11-05 11:39:17 +00:00
SetExtensions
2019-05-03 05:02:58 +00:00
SetExtensionsCancel
2014-11-09 17:20:55 +00:00
ReadingsVal
2019-01-04 11:58:28 +00:00
ReadingsNum
2019-05-03 05:02:58 +00:00
InternalVal
2019-01-04 11:58:28 +00:00
FileRead
2018-04-13 08:46:18 +00:00
InternalTimer
RemoveInternalTimer
2020-05-13 06:35:26 +00:00
asyncOutput
readingFnAttributes
2020-11-21 05:38:03 +00:00
IsDisabled
2021-02-20 11:03:16 +00:00
defs
init_done
2014-10-22 20:37:52 +00:00
) )
} ;
2020-05-13 06:35:26 +00:00
sub Initialize {
my $ hash = shift // return ;
# Consumer
$ hash - > { DefFn } = \ & Define ;
$ hash - > { UndefFn } = \ & UnDefine ;
$ hash - > { SetFn } = \ & Set ;
$ hash - > { GetFn } = \ & Get ;
$ hash - > { AttrFn } = \ & Attr ;
no warnings 'qw' ; ## no critic 'Warnings'
my @ attrList = qw(
config:M , I
mode:node , repeater
setCommands
setExtensionsEvent:1 , 0
setReading_ . +
mapReadingType_ . +
mapReading_ . +
requestAck:1
timeoutAck
timeoutAlive
IODev
showtime:0 , 1
OTA_autoUpdate:0 , 1
OTA_BL_Type:Optiboot , MYSBootloader
OTA_Chan76_IODev
streamFilePatterns
model
2020-11-21 05:38:03 +00:00
disable:0 , 1
2020-05-13 06:35:26 +00:00
) ;
use warnings 'qw' ;
$ hash - > { AttrList } = join ( " " , @ attrList ) . " " . $ readingFnAttributes ;
main:: LoadModule ( "MYSENSORS" ) ;
2020-10-06 05:25:49 +00:00
return FHEM::Meta:: InitMod ( __FILE__ , $ hash ) ;
2020-05-13 06:35:26 +00:00
}
2019-01-04 11:58:28 +00:00
my % gets = (
"version" = > "noArg" ,
"heartbeat" = > "noArg" ,
"presentation" = > "noArg" ,
"RSSI" = > "noArg" ,
"Extended_DEBUG" = > "noArg" ,
"ReadingsFromComment" = > "noArg" ,
) ;
2014-10-25 10:17:25 +00:00
my % static_types = (
2015-10-02 14:55:54 +00:00
S_DOOR = > { receives = > [] , sends = > [ V_TRIPPED , V_ARMED ] } , # Door and window sensors
2017-09-25 14:01:02 +00:00
S_MOTION = > { receives = > [] , sends = > [ V_TRIPPED , V_ARMED ] } , # Motion sensors
2015-10-02 14:55:54 +00:00
S_SMOKE = > { receives = > [] , sends = > [ V_TRIPPED , V_ARMED ] } , # Smoke sensor
2017-09-25 14:01:02 +00:00
S_BINARY = > { receives = > [ V_STATUS , V_WATT ] , sends = > [ V_STATUS , V_WATT ] } , # Binary device (on/off)
2015-10-02 14:55:54 +00:00
S_DIMMER = > { receives = > [ V_STATUS , V_PERCENTAGE , V_WATT ] , sends = > [ V_STATUS , V_PERCENTAGE , V_WATT ] } , # Dimmable device of some kind
2017-10-10 13:31:10 +00:00
S_COVER = > { receives = > [ V_UP , V_DOWN , V_STOP , V_PERCENTAGE ] , sends = > [ V_UP , V_DOWN , V_STOP , V_PERCENTAGE ] } , # Window covers or shades
2017-09-25 14:01:02 +00:00
S_TEMP = > { receives = > [] , sends = > [ V_TEMP , V_ID ] } , # Temperature sensor
2015-10-02 14:55:54 +00:00
S_HUM = > { receives = > [] , sends = > [ V_HUM ] } , # Humidity sensor
S_BARO = > { receives = > [] , sends = > [ V_PRESSURE , V_FORECAST ] } , # Barometer sensor (Pressure)
2017-09-25 14:01:02 +00:00
S_WIND = > { receives = > [] , sends = > [ V_WIND , V_GUST , V_DIRECTION ] } , # Wind sensor
2015-10-02 14:55:54 +00:00
S_RAIN = > { receives = > [] , sends = > [ V_RAIN , V_RAINRATE ] } , # Rain sensor
2017-09-25 14:01:02 +00:00
S_UV = > { receives = > [] , sends = > [ V_UV ] } , # UV sensor
2015-10-02 14:55:54 +00:00
S_WEIGHT = > { receives = > [] , sends = > [ V_WEIGHT , V_IMPEDANCE ] } , # Weight sensor for scales etc.
2017-10-10 13:31:10 +00:00
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
2017-09-25 14:01:02 +00:00
S_HEATER = > { receives = > [] , sends = > [ V_HVAC_SETPOINT_HEAT , V_HVAC_FLOW_STATE , V_TEMP , V_STATUS ] } , # Heater device
S_DISTANCE = > { receives = > [] , sends = > [ V_DISTANCE , V_UNIT_PREFIX ] } , # Distance sensor
2018-01-05 12:04:05 +00:00
S_LIGHT_LEVEL = > { receives = > [] , sends = > [ V_LIGHT_LEVEL , V_LEVEL ] } , # Light sensor
2015-10-02 14:55:54 +00:00
S_ARDUINO_NODE = > { receives = > [] , sends = > [] } , # Arduino node device
S_ARDUINO_REPEATER_NODE = > { receives = > [] , sends = > [] } , # Arduino repeating node device
S_LOCK = > { receives = > [ V_LOCK_STATUS ] , sends = > [ V_LOCK_STATUS ] } , # Lock device
2017-10-10 13:31:10 +00:00
S_IR = > { receives = > [ V_IR_SEND ] , sends = > [ V_IR_RECEIVE , V_IR_RECORD , V_IR_SEND ] } , # Ir sender/receiver device
2016-01-22 11:44:26 +00:00
S_WATER = > { receives = > [ V_VAR1 ] , sends = > [ V_FLOW , V_VOLUME , V_VAR1 ] } , # Water meter
2015-10-02 14:55:54 +00:00
S_AIR_QUALITY = > { receives = > [] , sends = > [ V_LEVEL , V_UNIT_PREFIX ] } , # Air quality sensor e.g. MQ-2
2016-02-28 11:26:53 +00:00
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.
2015-10-02 14:55:54 +00:00
S_DUST = > { receives = > [] , sends = > [ V_LEVEL , V_UNIT_PREFIX ] } , # Dust level sensor
S_SCENE_CONTROLLER = > { receives = > [] , sends = > [ V_SCENE_ON , V_SCENE_OFF ] } , # Scene controller device
2018-06-22 07:23:44 +00:00
S_RGB_LIGHT = > { receives = > [ V_RGB , V_WATT , V_PERCENTAGE ] , sends = > [ V_RGB , V_WATT , V_PERCENTAGE ] } , # RGB light
S_RGBW_LIGHT = > { receives = > [ V_RGBW , V_WATT , V_PERCENTAGE ] , sends = > [ V_RGBW , V_WATT , V_PERCENTAGE ] } , # RGBW light (with separate white component)
2015-10-02 14:55:54 +00:00
S_COLOR_SENSOR = > { receives = > [ V_RGB ] , sends = > [ V_RGB ] } , # Color sensor
2017-09-25 14:01:02 +00:00
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
2015-10-02 14:55:54 +00:00
S_MULTIMETER = > { receives = > [] , sends = > [ V_VOLTAGE , V_CURRENT , V_IMPEDANCE ] } , # Multimeter device
S_SPRINKLER = > { receives = > [] , sends = > [ V_STATUS , V_TRIPPED ] } , # Sprinkler device
S_WATER_LEAK = > { receives = > [] , sends = > [ V_TRIPPED , V_ARMED ] } , # Water leak sensor
S_SOUND = > { receives = > [] , sends = > [ V_LEVEL , V_TRIPPED , V_ARMED ] } , # Sound sensor
S_VIBRATION = > { receives = > [] , sends = > [ V_LEVEL , V_TRIPPED , V_ARMED ] } , # Vibration sensor
S_MOISTURE = > { receives = > [] , sends = > [ V_LEVEL , V_TRIPPED , V_ARMED ] } , # Moisture sensor
2017-09-25 14:01:02 +00:00
S_INFO = > { receives = > [ V_TEXT ] , sends = > [ V_TEXT ] } , # LCD text device
S_GAS = > { receives = > [] , sends = > [ V_FLOW , V_VOLUME ] } , # Gas meter
S_GPS = > { receives = > [] , sends = > [ V_POSITION ] } , # GPS Sensor
S_WATER_QUALITY = > { receives = > [] , sends = > [ V_TEMP , V_PH , V_ORP , V_EC , V_STATUS ] } , # Water quality sensor
2014-10-25 10:17:25 +00:00
) ;
2014-10-22 20:37:52 +00:00
my % static_mappings = (
V_TEMP = > { type = > "temperature" } ,
2014-11-05 11:39:17 +00:00
V_HUM = > { type = > "humidity" } ,
2015-10-02 14:55:54 +00:00
V_STATUS = > { type = > "status" , val = > { 0 = > 'off' , 1 = > 'on' } } ,
V_PERCENTAGE = > { type = > "percentage" , range = > { min = > 0 , step = > 1 , max = > 100 } } ,
2014-10-22 20:37:52 +00:00
V_PRESSURE = > { type = > "pressure" } ,
2014-10-25 10:17:25 +00:00
V_FORECAST = > { type = > "forecast" , val = > { # PressureSensor, DP/Dt explanation
2015-10-05 21:21:57 +00:00
0 = > 'stable' , # 0 = "Stable Weather Pattern"
1 = > 'sunny' , # 1 = "Slowly rising Good Weather", "Clear/Sunny"
2 = > 'cloudy' , # 2 = "Slowly falling L-Pressure ", "Cloudy/Rain"
3 = > 'unstable' , # 3 = "Quickly rising H-Press", "Not Stable"
2015-10-02 14:55:54 +00:00
4 = > 'thunderstorm' , # 4 = "Quickly falling L-Press", "Thunderstorm"
2015-10-05 21:21:57 +00:00
5 = > 'unknown' } } , # 5 = "Unknown (More Time needed)
2014-10-25 10:17:25 +00:00
V_RAIN = > { type = > "rain" } ,
V_RAINRATE = > { type = > "rainrate" } ,
V_WIND = > { type = > "wind" } ,
V_GUST = > { type = > "gust" } ,
V_DIRECTION = > { type = > "direction" } ,
V_UV = > { type = > "uv" } ,
V_WEIGHT = > { type = > "weight" } ,
V_DISTANCE = > { type = > "distance" } ,
V_IMPEDANCE = > { type = > "impedance" } ,
2015-10-02 14:55:54 +00:00
V_ARMED = > { type = > "armed" , val = > { 0 = > 'off' , 1 = > 'on' } } ,
2014-11-05 00:43:45 +00:00
V_TRIPPED = > { type = > "tripped" , val = > { 0 = > 'off' , 1 = > 'on' } } ,
2014-10-25 10:17:25 +00:00
V_WATT = > { type = > "power" } ,
V_KWH = > { type = > "energy" } ,
V_SCENE_ON = > { type = > "button_on" } ,
V_SCENE_OFF = > { type = > "button_off" } ,
2015-10-02 14:55:54 +00:00
V_HVAC_FLOW_STATE = > { type = > "hvacflowstate" } ,
V_HVAC_SPEED = > { type = > "hvacspeed" } ,
V_LIGHT_LEVEL = > { type = > "brightness" , range = > { min = > 0 , step = > 1 , max = > 100 } } ,
2014-10-25 10:17:25 +00:00
V_VAR1 = > { type = > "value1" } ,
V_VAR2 = > { type = > "value2" } ,
V_VAR3 = > { type = > "value3" } ,
V_VAR4 = > { type = > "value4" } ,
V_VAR5 = > { type = > "value5" } ,
V_UP = > { type = > "up" } ,
V_DOWN = > { type = > "down" } ,
V_STOP = > { type = > "stop" } ,
V_IR_SEND = > { type = > "ir_send" } ,
V_IR_RECEIVE = > { type = > "ir_receive" } ,
V_FLOW = > { type = > "flow" } ,
V_VOLUME = > { type = > "volume" } ,
2014-11-05 11:39:17 +00:00
V_LOCK_STATUS = > { type = > "lockstatus" , val = > { 0 = > 'off' , 1 = > 'on' } } ,
2015-10-02 14:55:54 +00:00
V_LEVEL = > { type = > "level" } ,
2014-10-25 10:17:25 +00:00
V_VOLTAGE = > { type = > "voltage" } ,
V_CURRENT = > { type = > "current" } ,
2015-10-02 14:55:54 +00:00
V_RGB = > { type = > "rgb" } ,
V_RGBW = > { type = > "rgbw" } ,
V_ID = > { type = > "id" } ,
V_UNIT_PREFIX = > { type = > "unitprefix" } ,
V_HVAC_SETPOINT_COOL = > { type = > "hvacsetpointcool" } ,
V_HVAC_SETPOINT_HEAT = > { type = > "hvacsetpointheat" } ,
V_HVAC_FLOW_MODE = > { type = > "hvacflowmode" } ,
2020-05-13 06:35:26 +00:00
V_TEXT = > { type = > "text" } ,
V_CUSTOM = > { type = > "custom" } ,
V_POSITION = > { type = > "position" } ,
V_IR_RECORD = > { type = > "ir_record" } ,
V_PH = > { type = > "ph" } ,
V_ORP = > { type = > "orp" } ,
V_EC = > { type = > "ec" } ,
V_VAR = > { type = > "value" } ,
V_VA = > { type = > "va" } ,
2017-09-25 14:01:02 +00:00
V_POWER_FACTOR = > { type = > "power_factor" } ,
2014-10-22 20:37:52 +00:00
) ;
2020-05-13 06:35:26 +00:00
sub Define {
my $ hash = shift ;
my $ def = shift // return ;
2022-02-10 05:13:15 +00:00
return $@ if ! FHEM::Meta:: SetInternals ( $ hash ) ;
2021-05-15 14:04:30 +00:00
my ( $ name , $ type , $ radioId ) = split m {\s+}xms , $ def ;
return 'requires 1 parameter!' if ( ! defined $ radioId || $ radioId eq '' ) ;
2019-01-04 11:58:28 +00:00
$ hash - > { radioId } = $ radioId ;
$ hash - > { sets } = {
2021-05-15 14:04:30 +00:00
time = > 'noArg' ,
reboot = > 'noArg' ,
clear = > 'noArg' ,
flash = > 'noArg' ,
fwType = > '' ,
2019-01-04 11:58:28 +00:00
} ;
$ hash - > { ack } = 0 ;
2020-05-13 06:35:26 +00:00
$ hash - > { '.typeMappings' } = { map { variableTypeToIdx ( $ _ ) = > $ static_mappings { $ _ } } keys % static_mappings } ;
$ hash - > { '.sensorMappings' } = { map { sensorTypeToIdx ( $ _ ) = > $ static_types { $ _ } } keys % static_types } ;
2019-01-04 11:58:28 +00:00
$ hash - > { readingMappings } = { } ;
AssignIoPort ( $ hash ) ;
2020-05-13 06:35:26 +00:00
return ;
2014-10-22 20:37:52 +00:00
} ;
2020-05-13 06:35:26 +00:00
sub UnDefine {
my $ hash = shift // return ;
RemoveInternalTimer ( $ hash - > { asyncGet } ) if ( $ hash - > { asyncGet } ) ;
return RemoveInternalTimer ( $ hash ) ;
2014-10-22 20:37:52 +00:00
}
2020-05-13 06:35:26 +00:00
sub Set {
2019-01-04 11:58:28 +00:00
my ( $ hash , $ name , $ command , @ values ) = @ _ ;
2020-05-13 06:35:26 +00:00
return "At least one parameter is needed!" if ! defined $ command ;
2020-11-21 05:38:03 +00:00
return "Node is disabled!" if IsDisabled ( $ hash - > { NAME } ) ;
2019-01-04 11:58:28 +00:00
if ( ! defined ( $ hash - > { sets } - > { $ command } ) ) {
2021-05-15 14:04:30 +00:00
$ hash - > { sets } - > { fwType } = join q{ , } , MYSENSORS:: getFirmwareTypes ( $ hash - > { IODev } ) ;
my $ list = join ( q{ } , map {
$ hash - > { sets } - > { $ _ } ne '' ? "$_:$hash->{sets}->{$_}"
2020-05-13 06:35:26 +00:00
: $ _
} sort keys % { $ hash - > { sets } } ) ;
2021-05-15 14:04:30 +00:00
$ hash - > { sets } - > { fwType } = q{ } ;
2019-04-04 04:08:41 +00:00
return SetExtensions ( $ hash , $ list , $ name , $ command , @ values ) ;
2019-01-04 11:58:28 +00:00
}
2020-05-13 06:35:26 +00:00
if ( $ command =~ m {\A(time|reboot|clear|flash|fwType)\z}xms ) {
2021-05-15 14:04:30 +00:00
if ( $ command eq 'time' ) {
2019-05-03 05:02:58 +00:00
my $ t = timegm_nocheck ( localtime ( time ) ) ;
2020-05-13 06:35:26 +00:00
return sendClientMessage ( $ hash ,
childId = > 255 ,
cmd = > C_INTERNAL ,
ack = > 0 ,
subType = > I_TIME ,
payload = > $ t
) ;
}
2021-05-15 14:04:30 +00:00
if ( $ command eq 'reboot' ) {
( AttrVal ( $ name , 'OTA_BL_Type' , 0 ) or ReadingsVal ( $ name , 'BL_VERSION' , 0 ) )
2020-05-13 06:35:26 +00:00
? return sendClientMessage ( $ hash ,
childId = > 255 ,
cmd = > C_INTERNAL ,
ack = > 0 ,
subType = > I_REBOOT
)
: return ;
}
2021-05-15 14:04:30 +00:00
if ( $ command eq 'clear' ) {
2019-01-04 11:58:28 +00:00
Log3 ( $ name , 3 , "MYSENSORS_DEVICE $name: clear" ) ;
2020-05-13 06:35:26 +00:00
return sendClientMessage ( $ hash ,
childId = > 255 ,
cmd = > C_INTERNAL ,
ack = > 0 ,
subType = > I_DEBUG ,
payload = > "E"
) ;
}
2021-05-15 14:04:30 +00:00
if ( $ command eq 'flash' ) {
my $ blVersion = ReadingsVal ( $ name , 'BL_VERSION' , '' ) ;
my $ blType = AttrVal ( $ name , 'OTA_BL_Type' , '' ) ;
my $ fwType = ReadingsNum ( $ name , 'FW_TYPE' , - 1 ) ;
2019-01-04 11:58:28 +00:00
if ( $ fwType == - 1 ) {
Log3 ( $ name , 3 , "Firmware type not defined (FW_TYPE) for $name, update not started" ) ;
return "$name: Firmware type not defined (FW_TYPE)" ;
2020-11-29 07:21:46 +00:00
}
2021-05-15 14:04:30 +00:00
if ( $ blVersion eq '3.0' or $ blType eq 'Optiboot' ) {
Log3 ( $ name , 4 , 'Startet flashing Firmware: Optiboot method' ) ;
2019-01-04 11:58:28 +00:00
return flashFirmware ( $ hash , $ fwType ) ;
2020-11-29 07:21:46 +00:00
}
2021-05-15 14:04:30 +00:00
if ( $ blType eq 'MYSBootloader' ) {
2019-01-04 11:58:28 +00:00
$ hash - > { OTA_requested } = 1 ;
Log3 ( $ name , 4 , "Send reboot command to MYSBootloader node to start update" ) ;
2020-05-13 06:35:26 +00:00
return sendClientMessage ( $ hash ,
childId = > 255 ,
cmd = > C_INTERNAL ,
2021-05-15 14:04:30 +00:00
ack = > 0 ,
2020-05-13 06:35:26 +00:00
subType = > I_REBOOT
) ;
2019-01-04 11:58:28 +00:00
} else {
return "$name: No valid OTA_BL_Type specified" if ( $ blVersion eq "" ) ;
return "$name: Expected bootloader version 3.0 but found: $blVersion or specify a valid OTA_BL_Type" ;
}
2020-05-13 06:35:26 +00:00
return ;
}
if ( $ command eq 'fwType' ) {
my $ type = shift @ values // return ;
return "fwType must be numeric, but got >$type<." if ( $ type !~ m {^[0-9]{2,20} $ } xms ) ;
2021-05-15 14:04:30 +00:00
readingsSingleUpdate ( $ hash , 'FW_TYPE' , $ type , 1 ) ;
return ;
2020-05-13 06:35:26 +00:00
}
}
#most used setter part
if ( defined ( $ hash - > { setcommands } - > { $ command } ) ) {
2019-01-04 11:58:28 +00:00
my $ setcommand = $ hash - > { setcommands } - > { $ command } ;
eval {
2020-05-13 06:35:26 +00:00
my ( $ type , $ childId , $ mappedValue ) = mappedReadingToRaw ( $ hash , $ setcommand - > { var } , $ setcommand - > { val } ) ;
sendClientMessage ( $ hash ,
childId = > $ childId ,
cmd = > C_SET ,
subType = > $ type ,
payload = > $ mappedValue ,
) ;
if ( ! $ hash - > { ack } && ! $ hash - > { IODev } - > { ack } ) {
2019-05-03 05:02:58 +00:00
readingsSingleUpdate ( $ hash , $ setcommand - > { var } , $ setcommand - > { val } , 1 ) ;
SetExtensionsCancel ( $ hash ) if ( $ command eq "on" || $ command eq "off" ) ;
if ( $ hash - > { SetExtensionsCommand } && AttrVal ( $ name , "setExtensionsEvent" , undef ) ) {
2020-05-13 06:35:26 +00:00
readingsSingleUpdate ( $ hash , "state" , $ hash - > { SetExtensionsCommand } , 1 ) ;
2019-05-03 05:02:58 +00:00
} else {
2020-05-13 06:35:26 +00:00
readingsSingleUpdate ( $ hash , "state" , "$command" , 1 ) ;
2019-05-03 05:02:58 +00:00
}
2020-05-13 06:35:26 +00:00
} else {
readingsSingleUpdate ( $ hash , "state" , "set $command" , 1 ) ;
2019-05-03 05:02:58 +00:00
}
2020-05-13 06:35:26 +00:00
} ;
2021-02-20 11:03:16 +00:00
if ( $@ ) { return "$command not defined: " . GP_Catch ( $@ ) } ;
2020-05-13 06:35:26 +00:00
return ;
}
2014-10-25 10:17:25 +00:00
my $ value = @ values ? join " " , @ values : "" ;
eval {
2019-01-04 11:58:28 +00:00
my ( $ type , $ childId , $ mappedValue ) = mappedReadingToRaw ( $ hash , $ command , $ value ) ;
2020-05-13 06:35:26 +00:00
sendClientMessage ( $ hash ,
childId = > $ childId ,
cmd = > C_SET ,
subType = > $ type ,
payload = > $ mappedValue
) ;
2021-02-20 11:03:16 +00:00
readingsSingleUpdate ( $ hash , $ command , $ value , 1 ) if ! $ hash - > { ack } || ! $ hash - > { IODev } - > { ack } ;
2014-10-25 10:17:25 +00:00
} ;
2021-02-20 11:03:16 +00:00
if ( $@ ) { return "$command not defined: " . GP_Catch ( $@ ) } ;
2020-05-13 06:35:26 +00:00
return ;
2019-01-04 11:58:28 +00:00
}
2020-05-13 06:35:26 +00:00
sub Get {
my ( $ hash , @ list ) = @ _ ;
2019-01-04 11:58:28 +00:00
my $ type = $ hash - > { TYPE } ;
2020-05-13 06:35:26 +00:00
return qq( "get $type" needs at least one parameter ) if ( @ list < 2 ) ;
if ( ! defined ( $ hash - > { gets } - > { $ list [ 1 ] } ) ) {
if ( ! defined ( $ gets { $ list [ 1 ] } ) ) {
my @ cList = map { $ _ =~ m {\A(file|raw)\z}xms ? $ _ : "$_:noArg" } sort keys % gets ;
return "Unknown argument $list[1], choose one of " . join ( " " , @ cList ) ;
2019-01-04 11:58:28 +00:00
}
}
2020-05-13 06:35:26 +00:00
my $ command = $ list [ 1 ] ;
2019-01-04 11:58:28 +00:00
2020-05-13 06:35:26 +00:00
if ( $ command eq "version" ) {
if ( $ hash - > { CL } ) {
my $ tHash = { hash = > $ hash , CL = > $ hash - > { CL } , reading = > $ command } ;
$ hash - > { asyncGet } = $ tHash ;
InternalTimer ( gettimeofday ( ) + 4 , sub {
asyncOutput ( $ tHash - > { CL } , "Timeout reading answer for $command - node might be asleep?" ) ;
delete ( $ hash - > { asyncGet } ) ;
} , $ tHash , 0 ) ;
}
return sendClientMessage ( $ hash ,
cmd = > C_INTERNAL ,
ack = > 0 ,
subType = > I_PRESENTATION
) ; #I_VERSION);
}
if ( $ command eq "heartbeat" ) {
if ( $ hash - > { CL } ) {
my $ start = gettimeofday ( ) ;
my $ tHash = { hash = > $ hash , CL = > $ hash - > { CL } , reading = > $ command , start = > $ start } ;
$ hash - > { asyncGet } = $ tHash ;
InternalTimer ( gettimeofday ( ) + 4 , sub {
asyncOutput ( $ tHash - > { CL } , "Timeout reading answer for $command - node might be asleep?" ) ;
delete ( $ hash - > { asyncGet } ) ;
} , $ tHash , 0 ) ;
}
return sendClientMessage ( $ hash ,
cmd = > C_INTERNAL ,
ack = > 0 ,
subType = > I_HEARTBEAT_REQUEST
) ;
}
if ( $ command eq "presentation" ) {
if ( $ hash - > { CL } ) {
my $ tHash = { hash = > $ hash , CL = > $ hash - > { CL } , reading = > $ command } ;
$ hash - > { asyncGet } = $ tHash ;
InternalTimer ( gettimeofday ( ) + 4 , sub {
asyncOutput ( $ tHash - > { CL } , "Timeout reading answer for $command - node might be asleep." ) ;
delete ( $ hash - > { asyncGet } ) ;
} , $ tHash , 0 ) ;
}
return sendClientMessage ( $ hash ,
cmd = > C_INTERNAL ,
ack = > 0 ,
subType = > I_PRESENTATION
) ;
}
if ( $ command eq "RSSI" ) {
if ( $ hash - > { CL } ) {
my $ tHash = { hash = > $ hash , CL = > $ hash - > { CL } , reading = > $ command } ;
$ hash - > { asyncGet } = $ tHash ;
InternalTimer ( gettimeofday ( ) + 4 , sub {
asyncOutput ( $ tHash - > { CL } , "Timeout reading answer for $command - node might be asleep." ) ;
delete ( $ hash - > { asyncGet } ) ;
} , $ tHash , 0 ) ;
}
2019-01-04 11:58:28 +00:00
$ hash - > { I_RSSI } = 1 ;
2020-05-13 06:35:26 +00:00
return sendClientMessage ( $ hash ,
cmd = > C_INTERNAL ,
subType = > I_SIGNAL_REPORT_REQUEST ,
ack = > 0 ,
payload = > "R" ) ;
}
if ( $ command eq "Extended_DEBUG" ) {
if ( $ hash - > { CL } ) {
my $ tHash = { hash = > $ hash , CL = > $ hash - > { CL } , reading = > $ command } ;
$ hash - > { asyncGet } = $ tHash ;
InternalTimer ( gettimeofday ( ) + 4 , sub {
asyncOutput ( $ tHash - > { CL } , "Timeout reading answer for $command. Is node configured to send extended debug info or might it be asleep?" ) ;
delete ( $ hash - > { asyncGet } ) ;
} , $ tHash , 0 ) ;
}
2019-01-04 11:58:28 +00:00
$ hash - > { I_DEBUG } = 1 ;
2020-05-13 06:35:26 +00:00
return sendClientMessage ( $ hash ,
cmd = > C_INTERNAL ,
subType = > I_DEBUG ,
ack = > 0 ,
payload = > "F"
) ;
}
if ( $ command eq "ReadingsFromComment" ) {
2019-01-04 11:58:28 +00:00
$ hash - > { getCommentReadings } = 1 ;
2020-05-13 06:35:26 +00:00
return sendClientMessage ( $ hash ,
cmd = > C_INTERNAL ,
subType = > I_PRESENTATION ,
ack = > 0
) ;
2019-01-04 11:58:28 +00:00
}
2020-05-13 06:35:26 +00:00
return ;
2019-01-04 11:58:28 +00:00
}
2020-05-13 06:35:26 +00:00
sub onStreamMessage {
my $ hash = shift ;
my $ msg = shift // return ;
2019-01-04 11:58:28 +00:00
my $ name = $ hash - > { NAME } ;
my $ type = $ msg - > { subType } ;
my $ blType = AttrVal ( $ name , "OTA_BL_Type" , "" ) ;
my $ fwType = hex2Short ( substr ( $ msg - > { payload } , 0 , 4 ) ) ;
2020-05-13 06:35:26 +00:00
my $ payload = $ msg - > { payload } ;
2019-01-04 11:58:28 +00:00
2020-05-13 06:35:26 +00:00
if ( $ type == ST_FIRMWARE_CONFIG_REQUEST ) {
if ( length ( $ msg - > { payload } ) == 20 ) {
2019-10-01 12:30:56 +00:00
my $ blVersion = hex ( substr ( $ msg - > { payload } , 16 , 2 ) ) . "." . hex ( substr ( $ msg - > { payload } , 18 , 2 ) ) ;
2019-01-04 11:58:28 +00:00
readingsBeginUpdate ( $ hash ) ;
readingsBulkUpdate ( $ hash , 'FW_TYPE' , $ fwType ) if ( $ blType eq "Optiboot" ) ;
readingsBulkUpdate ( $ hash , 'FW_VERSION' , hex2Short ( substr ( $ msg - > { payload } , 4 , 4 ) ) ) if ( $ blType eq "Optiboot" ) ;
readingsBulkUpdate ( $ hash , 'FW_BLOCKS' , hex2Short ( substr ( $ msg - > { payload } , 8 , 4 ) ) ) ;
readingsBulkUpdate ( $ hash , 'FW_CRC' , hex2Short ( substr ( $ msg - > { payload } , 12 , 4 ) ) ) ;
readingsBulkUpdate ( $ hash , 'BL_VERSION' , $ blVersion ) ;
readingsEndUpdate ( $ hash , 1 ) ;
Log3 ( $ name , 4 , "$name: received ST_FIRMWARE_CONFIG_REQUEST" ) ;
2021-02-20 11:03:16 +00:00
if ( ( AttrVal ( $ name , 'OTA_autoUpdate' , 0 ) ) && ( $ blVersion eq '3.0' || $ blType eq 'Optiboot' ) ) {
2019-01-04 11:58:28 +00:00
Log3 ( $ name , 4 , "$name: Optiboot BL, Node set to OTA_autoUpdate => calling firmware update procedure" ) ;
flashFirmware ( $ hash , $ fwType ) ;
2021-02-20 11:03:16 +00:00
} elsif ( $ blType eq 'MYSBootloader' && $ hash - > { OTA_requested } ) {
2019-01-04 11:58:28 +00:00
Log3 ( $ name , 4 , "$name: MYSBootloader asking for firmware update, calling firmware update procedure" ) ;
2021-02-20 11:03:16 +00:00
$ fwType = ReadingsVal ( $ name , 'FW_TYPE' , 'unknown' ) ;
2019-01-04 11:58:28 +00:00
flashFirmware ( $ hash , $ fwType ) ;
}
2020-05-13 06:35:26 +00:00
} else {
2019-01-04 11:58:28 +00:00
Log3 ( $ name , 2 , "$name: Failed to parse ST_FIRMWARE_CONFIG_REQUEST - expected payload length 32 but retrieved " . length ( $ msg - > { payload } ) ) ;
2020-05-13 06:35:26 +00:00
}
return ;
2019-01-04 11:58:28 +00:00
}
2020-05-13 06:35:26 +00:00
if ( $ type == ST_FIRMWARE_REQUEST ) {
return if ( $ msg - > { ack } || ! defined $ hash - > { FW_DATA } ) ;
2019-01-04 11:58:28 +00:00
if ( length ( $ msg - > { payload } ) == 12 ) {
my $ version = hex2Short ( substr ( $ msg - > { payload } , 4 , 4 ) ) ;
my $ block = hex2Short ( substr ( $ msg - > { payload } , 8 , 4 ) ) ;
my $ fromIndex = $ block * 16 ;
my @ fwData = @ { $ hash - > { FW_DATA } } ;
Log3 ( $ name , 5 , "$name: Firmware block request $block (type $fwType, version $version)" ) ;
for ( my $ index = $ fromIndex ; $ index < $ fromIndex + 16 ; $ index + + ) {
$ payload = $ payload . sprintf ( "%02X" , $ fwData [ $ index ] ) ;
}
if ( defined $ hash - > { OTA_Chan76_IODev } ) {
2020-05-13 06:35:26 +00:00
sendMessage ( $ hash - > { OTA_Chan76_IODev } ,
radioId = > $ hash - > { radioId } ,
childId = > 255 ,
ack = > 0 ,
cmd = > C_STREAM ,
subType = > ST_FIRMWARE_RESPONSE ,
payload = > $ payload
) ;
2019-01-04 11:58:28 +00:00
} else {
2019-01-29 06:40:22 +00:00
$ hash - > { nowSleeping } = 0 if $ hash - > { nowSleeping } ;
2020-05-13 06:35:26 +00:00
sendClientMessage ( $ hash ,
childId = > 255 ,
cmd = > C_STREAM ,
ack = > 0 ,
subType = > ST_FIRMWARE_RESPONSE ,
payload = > $ payload
) ;
2019-01-04 11:58:28 +00:00
}
2022-02-10 05:13:15 +00:00
readingsSingleUpdate ( $ hash , 'state' , 'updating' , 1 ) if ! ReadingsVal ( $ hash - > { NAME } , 'state' , 0 ) eq 'updating' ;
readingsSingleUpdate ( $ hash , 'state' , 'update done' , 1 ) if $ block == 0 ;
if ( $ block == 0 && $ blType ne 'Optiboot' ) {
2019-01-04 11:58:28 +00:00
readingsSingleUpdate ( $ hash , 'FW_VERSION' , $ version , 1 ) ;
delete $ hash - > { OTA_requested } if ( defined $ hash - > { OTA_requested } ) ;
}
} else {
Log3 ( $ name , 2 , "$name: Failed to parse ST_FIRMWARE_REQUEST - expected payload length 12 but retrieved " . length ( $ msg - > { payload } ) ) ;
}
2020-05-13 06:35:26 +00:00
return ;
}
if ( $ type == ST_IMAGE || $ type == ST_SOUND ) {
#code adopted from https://forum.mysensors.org/topic/1668/sending-image-data-over-the-mysensors-network/11
#see also node example code there;
#untested code, may work or not; packets are not numbered, so data may be scrambled when using wireless protocols
return if $ msg - > { ack } ;
my $ id = $ msg - > { childId } ;
if ( $ msg - > { payload } eq "START" ) {
my ( $ arg , $ hashes ) = parseParams ( AttrVal ( $ name , "streamFilePatterns" , 'ST_IMAGE=./log/$name-$id-$time.jpg ST_SOUND=./log/$name-$id-$time.mpg' ) ) ;
if ( $ hashes - > { $ type } ) {
my $ time = strftime ( "%Y-%m-%d-%H-%M-%S" , localtime ) ;
$ hash - > { helper } { $ type } { $ id } { file } = qq( $hashes->{$type} ) ;
} else {
Log3 ( $ name , 2 , "$name: no $type streamFilePattern found. Transfer stream to file not possible!" ) ;
return ;
}
if ( $ hash - > { helper } { $ type } { $ id } { running } ) {
##delete uncompleted transfer
delete $ hash - > { helper } { $ type } { $ id } { data } ;
}
$ hash - > { helper } { $ type } { $ id } { start_time } = time ( ) ;
$ hash - > { helper } { $ type } { $ id } { count } = 0 ;
$ hash - > { helper } { $ type } { $ id } { running } = 1 ;
return ;
}
if ( $ payload eq "END" ) {
$ hash - > { helper } { $ type } { $ id } { running } = 0 ;
my $ err = FileWrite ( { FileName = > $ hash - > { helper } { $ type } { $ id } { file } , ForceType = > "file" } , $ hash - > { helper } { $ type } { $ id } { data } ) ;
delete $ hash - > { helper } { $ type } { $ id } { data } ;
my $ duration = time ( ) - $ hash - > { helper } { $ type } { $ id } { start_time } ;
my $ bps = $ hash - > { helper } { $ type } { $ id } { count } * 24 / $ duration ;
$ hash - > { helper } { $ type } { $ id } { bps } = $ bps ;
my $ pps = $ hash - > { helper } { $ type } { $ id } { count } / $ duration ;
$ hash - > { helper } { $ type } { $ id } { pps } = $ pps ;
Log3 ( $ name , 4 , "$name: Stream transfer finished, $duration seconds, $bps Bytes/s, $pps Packets/s" ) ;
my $ rname = $ type == ST_IMAGE ? "IMAGE_$id" : "SOUND_$id" ;
readingsSingleUpdate ( $ hash , $ rname , $ hash - > { helper } { $ type } { $ id } { file } , 1 ) if ! $ err ;
return $ err ;
}
#other paylaod than keywords START and END:
$ hash - > { helper } { $ type } { $ id } { data } . = pack ( 'H*' , $ payload ) ;
$ hash - > { helper } { $ type } { $ id } { count } + + ;
Log3 ( $ hash - > { NAME } , 5 , "MYSENSORS_DEVICE $name: received stream-type payload" ) ; # disable after debugging
return ;
2019-01-04 11:58:28 +00:00
}
2020-05-13 06:35:26 +00:00
return ;
2014-10-22 20:37:52 +00:00
}
2020-05-13 06:35:26 +00:00
sub Attr {
2014-10-22 20:37:52 +00:00
my ( $ command , $ name , $ attribute , $ value ) = @ _ ;
2021-02-20 11:03:16 +00:00
my $ hash = $ defs { $ name } ;
if ( $ attribute eq "config" && $ init_done ) {
2020-05-13 06:35:26 +00:00
sendClientMessage ( $ hash ,
cmd = > C_INTERNAL ,
childId = > 255 ,
ack = > 0 ,
subType = > I_CONFIG ,
payload = > $ command eq 'set' ? $ value : "M"
) ;
return ;
}
if ( $ attribute eq "mode" ) {
2014-10-25 10:17:25 +00:00
if ( $ command eq "set" and $ value eq "repeater" ) {
$ hash - > { repeater } = 1 ;
} else {
$ hash - > { repeater } = 0 ;
}
2020-05-13 06:35:26 +00:00
return ;
}
if ( $ attribute eq "setCommands" ) {
for my $ set ( keys % { $ hash - > { setcommands } } ) {
2014-10-25 10:17:25 +00:00
delete $ hash - > { sets } - > { $ set } ;
}
$ hash - > { setcommands } = { } ;
2014-10-25 10:37:22 +00:00
if ( $ command eq "set" and $ value ) {
2020-05-13 06:35:26 +00:00
for my $ setCmd ( split m {[,\s]}xms , $ value ) {
if ( $ setCmd =~ m {\A(.+):(.+):(.+)\z}xms ) {
2014-10-25 10:37:22 +00:00
$ hash - > { sets } - > { $ 1 } = "" ;
$ hash - > { setcommands } - > { $ 1 } = {
var = > $ 2 ,
val = > $ 3 ,
} ;
} else {
return "unparsable value in setCommands for $name: $setCmd" ;
}
2014-10-22 20:37:52 +00:00
}
}
2020-05-13 06:35:26 +00:00
return ;
}
if ( $ attribute =~ m {\A setReading_(.+) \z}xms ) {
if ( $ command eq "set" ) {
$ hash - > { sets } - > { $ 1 } = ( defined $ value ) ? join ( "," , split m {[,\s]}xms , $ value ) : "" ;
} else {
2014-10-22 20:37:52 +00:00
CommandDeleteReading ( undef , "$hash->{NAME} $1" ) ;
delete $ hash - > { sets } - > { $ 1 } ;
2020-05-13 06:35:26 +00:00
}
return ;
2019-01-04 11:58:28 +00:00
}
2020-05-13 06:35:26 +00:00
if ( $ attribute =~ m {\A mapReadingType_(.+) \z}xms ) {
my $ type = variableTypeToIdx ( "V_$1" ) ;
if ( $ command eq "set" ) {
2014-10-22 20:37:52 +00:00
my @ values = split ( "[, \t]" , $ value ) ;
2020-05-13 06:35:26 +00:00
$ hash - > { '.typeMappings' } - > { $ type } = {
2014-10-22 20:37:52 +00:00
type = > shift @ values ,
2020-05-13 06:35:26 +00:00
val = > { map { $ _ =~ m {m/^(.+):(.+)$}xms ; $ 1 = > $ 2 } @ values } ,
2014-10-22 20:37:52 +00:00
}
2020-05-13 06:35:26 +00:00
} else {
2014-10-22 20:37:52 +00:00
if ( $ static_mappings { "V_$1" } ) {
2020-05-13 06:35:26 +00:00
$ hash - > { '.typeMappings' } - > { $ type } = $ static_mappings { "V_$1" } ;
2014-10-22 20:37:52 +00:00
} else {
2020-05-13 06:35:26 +00:00
delete $ hash - > { '.typeMappings' } - > { $ type } ;
2014-10-22 20:37:52 +00:00
}
2014-10-25 10:17:25 +00:00
my $ readings = $ hash - > { READINGS } ;
my $ readingMappings = $ hash - > { readingMappings } ;
2020-05-13 06:35:26 +00:00
for my $ todelete ( map { $ readingMappings - > { $ _ } - > { name } } grep { $ readingMappings - > { $ _ } - > { type } == $ type } keys %$ readingMappings ) {
2014-10-25 10:17:25 +00:00
CommandDeleteReading ( undef , "$hash->{NAME} $todelete" ) ; #TODO do propper remap of existing readings
}
2020-05-13 06:35:26 +00:00
}
return ;
2019-01-04 11:58:28 +00:00
}
2020-05-13 06:35:26 +00:00
if ( $ attribute =~ m {\A mapReading_(.+) \z}xms ) {
my $ readingMappings = $ hash - > { readingMappings } ;
FIND: for my $ id ( keys %$ readingMappings ) {
2014-10-25 10:17:25 +00:00
my $ readingsForId = $ readingMappings - > { $ id } ;
2020-05-13 06:35:26 +00:00
for my $ type ( keys %$ readingsForId ) {
2014-11-09 11:43:23 +00:00
if ( ( $ readingsForId - > { $ type } - > { name } // "" ) eq $ 1 ) {
2014-10-25 10:17:25 +00:00
delete $ readingsForId - > { $ type } ;
2022-02-10 05:13:15 +00:00
if ( ! keys %$ readingsForId ) {
2014-10-25 10:17:25 +00:00
delete $ readingMappings - > { $ id } ;
}
2019-01-04 11:58:28 +00:00
last FIND ;
2014-10-25 10:17:25 +00:00
}
}
2020-05-13 06:35:26 +00:00
}
if ( $ command eq "set" ) {
my ( $ id , $ typeStr , @ values ) = split m {[,\s]}xms , $ value ;
my $ typeMappings = $ hash - > { '.typeMappings' } ;
2014-10-25 10:17:25 +00:00
if ( my @ match = grep { $ typeMappings - > { $ _ } - > { type } eq $ typeStr } keys %$ typeMappings ) {
2014-11-05 00:39:15 +00:00
my $ type = shift @ match ;
2014-11-09 11:43:23 +00:00
$ readingMappings - > { $ id } - > { $ type } - > { name } = $ 1 ;
2014-11-05 00:39:15 +00:00
if ( @ values ) {
2020-05-13 06:35:26 +00:00
$ readingMappings - > { $ id } - > { $ type } - > { val } = { map { $ _ =~ m {\A (.+):(.+) \z}xms ; $ 1 = > $ 2 } @ values } ; #TODO range?
2014-11-05 00:39:15 +00:00
}
2014-10-25 10:17:25 +00:00
} else {
return "unknown reading type $typeStr" ;
}
2020-05-13 06:35:26 +00:00
} else {
2014-10-25 10:17:25 +00:00
CommandDeleteReading ( undef , "$hash->{NAME} $1" ) ;
2020-05-13 06:35:26 +00:00
}
return ;
2019-01-04 11:58:28 +00:00
}
2020-05-13 06:35:26 +00:00
if ( $ attribute eq "requestAck" ) {
$ hash - > { ack } = $ command eq "set" ? 1 : 0 ;
return ;
2019-01-04 11:58:28 +00:00
}
2020-05-13 06:35:26 +00:00
if ( $ attribute eq "timeoutAck" ) {
$ hash - > { timeoutAck } = $ command eq "set" ? $ value : 0 ;
return ;
}
if ( $ attribute eq "timeoutAlive" ) {
if ( $ command eq "set" and $ value ) {
2019-01-04 11:58:28 +00:00
$ hash - > { timeoutAlive } = $ value ;
refreshInternalMySTimer ( $ hash , "Alive" ) ;
2020-05-13 06:35:26 +00:00
} else {
$ hash - > { timeoutAlive } = 0 ;
}
return ;
}
if ( $ attribute eq "OTA_autoUpdate" ) {
return ;
}
return ;
2014-10-22 20:37:52 +00:00
}
2020-05-13 06:35:26 +00:00
sub onGatewayStarted {
my $ hash = shift // return ;
2019-01-04 11:58:28 +00:00
refreshInternalMySTimer ( $ hash , "Alive" ) if ( $ hash - > { timeoutAlive } ) ;
2020-05-13 06:35:26 +00:00
return ;
2014-10-22 20:37:52 +00:00
}
2020-05-13 06:35:26 +00:00
sub onPresentationMessage {
my $ hash = shift ;
my $ msg = shift // return ;
2019-01-04 11:58:28 +00:00
my $ name = $ hash - > { NAME } ;
my $ nodeType = $ msg - > { subType } ;
my $ id = $ msg - > { childId } ;
if ( $ id == 255 ) { #special id
2020-05-13 06:35:26 +00:00
if ( $ nodeType == S_ARDUINO_NODE ) {
2014-10-25 10:17:25 +00:00
CommandAttr ( undef , "$name mode node" ) ;
2020-05-13 06:35:26 +00:00
}
if ( $ nodeType == S_ARDUINO_REPEATER_NODE ) {
2014-10-25 10:17:25 +00:00
CommandAttr ( undef , "$name mode repeater" ) ;
2020-05-13 06:35:26 +00:00
} ;
$ hash - > { version } = $ msg - > { payload } ;
if ( $ hash - > { asyncGet } && $ hash - > { asyncGet } { reading } eq "version" ) {
RemoveInternalTimer ( $ hash - > { asyncGet } ) ;
my $ version = $ msg - > { payload } ;
asyncOutput ( $ hash - > { asyncGet } { CL } , "MySensors protocol info:\n----------------------------\nversion: $version" ) ;
delete ( $ hash - > { asyncGet } ) ;
}
return ;
}
2014-10-25 10:17:25 +00:00
2019-01-04 11:58:28 +00:00
my $ readingMappings = $ hash - > { readingMappings } ;
2020-05-13 06:35:26 +00:00
my $ typeMappings = $ hash - > { '.typeMappings' } ;
if ( my $ sensorMappings = $ hash - > { '.sensorMappings' } - > { $ nodeType } ) {
2019-01-04 11:58:28 +00:00
my $ idStr = ( $ id > 0 ? $ id : "" ) ;
2014-10-25 10:17:25 +00:00
my @ ret = ( ) ;
2020-05-13 06:35:26 +00:00
for my $ type ( @ { $ sensorMappings - > { sends } } ) {
2019-01-04 11:58:28 +00:00
if ( defined $ readingMappings - > { $ id } - > { $ type } ) {
2022-02-10 05:13:15 +00:00
next if ! defined $ hash - > { getCommentReadings } ;
next if $ hash - > { getCommentReadings } ne '2' ;
2019-01-04 11:58:28 +00:00
}
my $ typeStr = $ typeMappings - > { $ type } - > { type } ;
if ( $ hash - > { IODev } - > { 'inclusion-mode' } ) {
if ( $ msg - > { payload } ne "" and $ hash - > { getCommentReadings } eq "2" ) {
$ idStr = "_" . $ msg - > { payload } ;
2020-05-13 06:35:26 +00:00
$ idStr =~ s/\:/\./gx ; #replace illegal characters
$ idStr =~ s/[^A-Za-z\d_\.-]+/_/gx ;
2019-01-04 11:58:28 +00:00
}
if ( defined ( my $ mapping = $ hash - > { readingMappings } - > { $ id } - > { $ type } ) ) {
if ( $ mapping - > { name } ne "$typeStr$idStr" and $ hash - > { getCommentReadings } eq "2" and $ msg - > { payload } ne "" ) {
my $ oldMappingName = $ mapping - > { name } ;
CommandDeleteAttr ( undef , "$hash->{NAME} mapReading_$oldMappingName" ) ;
CommandDeleteReading ( undef , "$hash->{NAME} $oldMappingName" ) ;
Log3 ( $ hash - > { NAME } , 3 , "MYSENSORS_DEVICE $hash->{NAME}: Deleted Reading $oldMappingName" ) ;
}
}
if ( my $ ret = CommandAttr ( undef , "$name mapReading_$typeStr$idStr $id $typeStr" ) ) {
push @ ret , $ ret ;
}
} else {
push @ ret , "no mapReading for $id, $typeStr" ;
2014-10-25 10:17:25 +00:00
}
}
2020-05-13 06:35:26 +00:00
for my $ type ( @ { $ sensorMappings - > { receives } } ) {
2019-01-04 11:58:28 +00:00
my $ typeMapping = $ typeMappings - > { $ type } ;
my $ typeStr = $ typeMapping - > { type } ;
if ( $ msg - > { payload } ne "" and $ hash - > { getCommentReadings } eq "2" ) {
$ idStr = "_" . $ msg - > { payload } ;
2020-05-13 06:35:26 +00:00
$ idStr =~ s/\:/\./gx ; #replace illegal characters
$ idStr =~ s/[^A-Za-z\d_\.-]+/_/gx ;
2014-11-05 11:39:17 +00:00
}
2019-01-04 11:58:28 +00:00
if ( defined $ hash - > { sets } - > { "$typeStr$idStr" } ) {
2022-02-10 05:13:15 +00:00
next if ! defined ( $ hash - > { getCommentReadings } ) || $ hash - > { getCommentReadings } ne '2' ;
2014-10-25 10:17:25 +00:00
}
2019-01-04 11:58:28 +00:00
if ( $ hash - > { IODev } - > { 'inclusion-mode' } ) {
my @ values = ( ) ;
if ( $ typeMapping - > { range } ) {
@ values = ( 'slider' , $ typeMapping - > { range } - > { min } , $ typeMapping - > { range } - > { step } , $ typeMapping - > { range } - > { max } ) ;
} elsif ( $ typeMapping - > { val } ) {
@ values = values % { $ typeMapping - > { val } } ;
}
if ( my $ ret = CommandAttr ( undef , "$name setReading_$typeStr$idStr" . ( @ values ? " " . join ( "," , @ values ) : "" ) ) ) {
push @ ret , $ ret ;
} else {
push @ ret , "no setReading for $id, $typeStr" ;
}
}
Log3 ( $ hash - > { NAME } , 4 , "MYSENSORS_DEVICE $hash->{NAME}: errors on C_PRESENTATION-message for childId $id, subType " . sensorTypeToStr ( $ nodeType ) . " " . join ( ", " , @ ret ) ) if @ ret ;
}
2014-10-25 10:17:25 +00:00
}
2020-05-13 06:35:26 +00:00
return ;
2014-10-22 20:37:52 +00:00
}
2020-05-13 06:35:26 +00:00
sub onSetMessage {
my $ hash = shift ;
my $ msg = shift // return ;
2019-01-04 11:58:28 +00:00
my $ name = $ hash - > { NAME } ;
if ( defined $ msg - > { payload } ) {
2020-10-06 05:25:49 +00:00
readingsBeginUpdate ( $ hash ) ;
2019-01-04 11:58:28 +00:00
eval {
my ( $ reading , $ value ) = rawToMappedReading ( $ hash , $ msg - > { subType } , $ msg - > { childId } , $ msg - > { payload } ) ;
2020-10-06 05:25:49 +00:00
readingsBulkUpdate ( $ hash , $ reading , $ value ) ;
if ( defined ( $ hash - > { setcommands } - > { $ value } ) && $ hash - > { setcommands } - > { $ value } - > { var } eq $ reading ) { #$msg->{childId}
2019-05-03 05:02:58 +00:00
if ( $ hash - > { SetExtensionsCommand } && AttrVal ( $ name , "setExtensionsEvent" , undef ) ) {
2020-10-06 05:25:49 +00:00
readingsBulkUpdate ( $ hash , "state" , $ hash - > { SetExtensionsCommand } ) ;
2019-05-03 05:02:58 +00:00
} else {
2020-10-06 05:25:49 +00:00
readingsBulkUpdate ( $ hash , "state" , "$value" ) ;
SetExtensionsCancel ( $ hash ) if ! $ msg - > { ack } ;
2019-05-03 05:02:58 +00:00
}
}
2019-01-04 11:58:28 +00:00
} ;
2020-10-06 05:25:49 +00:00
if ( $ msg - > { ack } ) {
#readingsBulkUpdate($hash,"heartbeat","alive") ;
refreshInternalMySTimer ( $ hash , "Alive" ) if $ hash - > { timeoutAlive } ;
refreshInternalMySTimer ( $ hash , "Ack" ) if $ hash - > { timeoutAck } ;
}
readingsEndUpdate ( $ hash , 1 ) ;
Log3 ( $ hash , 4 , "MYSENSORS_DEVICE $name: ignoring C_SET-message " . GP_Catch ( $@ ) ) if $@ ;
2019-01-04 11:58:28 +00:00
} else {
2020-10-06 05:25:49 +00:00
Log3 ( $ hash , 5 , "MYSENSORS_DEVICE $name: ignoring C_SET-message without payload" ) ;
2020-05-13 06:35:26 +00:00
}
return ;
2014-10-22 20:37:52 +00:00
}
2020-05-13 06:35:26 +00:00
sub onRequestMessage {
my $ hash = shift ;
my $ msg = shift // return ;
2019-01-04 11:58:28 +00:00
eval {
my ( $ readingname , $ val ) = rawToMappedReading ( $ hash , $ msg - > { subType } , $ msg - > { childId } , $ msg - > { payload } ) ;
2019-01-29 06:40:22 +00:00
$ hash - > { nowSleeping } = 0 if $ hash - > { nowSleeping } ;
2019-11-13 19:21:08 +00:00
my $ value = ReadingsVal ( $ hash - > { NAME } , $ readingname , $ val ) ;
my ( $ type , $ childId , $ mappedValue ) = mappedReadingToRaw ( $ hash , $ readingname , $ value ) ;
$ value = $ mappedValue ;
2019-01-04 11:58:28 +00:00
sendClientMessage ( $ hash ,
childId = > $ msg - > { childId } ,
cmd = > C_SET ,
subType = > $ msg - > { subType } ,
2019-11-13 19:21:08 +00:00
payload = > $ value
2019-01-04 11:58:28 +00:00
) ;
} ;
Log3 ( $ hash - > { NAME } , 4 , "MYSENSORS_DEVICE $hash->{NAME}: ignoring C_REQ-message " . GP_Catch ( $@ ) ) if $@ ;
2020-05-13 06:35:26 +00:00
return ;
2014-10-22 20:37:52 +00:00
}
2020-05-13 06:35:26 +00:00
sub onInternalMessage {
my $ hash = shift ;
my $ msg = shift // return ;
2019-01-04 11:58:28 +00:00
my $ name = $ hash - > { NAME } ;
my $ type = $ msg - > { subType } ;
my $ typeStr = internalMessageTypeToStr ( $ type ) ;
2020-05-13 06:35:26 +00:00
if ( $ type == I_BATTERY_LEVEL ) {
2019-01-04 11:58:28 +00:00
readingsSingleUpdate ( $ hash , "batteryPercent" , $ msg - > { payload } , 1 ) ;
refreshInternalMySTimer ( $ hash , "Alive" ) if $ hash - > { timeoutAlive } ;
Log3 ( $ name , 4 , "MYSENSORS_DEVICE $name: batteryPercent $msg->{payload}" ) ;
2020-05-13 06:35:26 +00:00
return ;
}
if ( $ type == I_TIME ) {
2019-01-04 11:58:28 +00:00
if ( $ msg - > { ack } ) {
Log3 ( $ name , 4 , "MYSENSORS_DEVICE $name: response to time-request acknowledged" ) ;
} else {
2019-01-29 06:40:22 +00:00
$ hash - > { nowSleeping } = 0 if $ hash - > { nowSleeping } ;
2019-05-03 05:02:58 +00:00
my $ t = timegm_nocheck ( localtime ( time ) ) ;
2020-05-13 06:35:26 +00:00
sendClientMessage ( $ hash ,
cmd = > C_INTERNAL ,
childId = > 255 ,
subType = > I_TIME ,
payload = > $ t
) ;
2019-01-04 11:58:28 +00:00
Log3 ( $ name , 4 , "MYSENSORS_DEVICE $name: update of time requested" ) ;
}
2020-05-13 06:35:26 +00:00
return ;
}
if ( $ type == I_SKETCH_NAME ) {
2022-02-10 05:13:15 +00:00
readingsSingleUpdate ( $ hash , 'state' , 'received presentation' , 1 ) if ! ReadingsVal ( $ hash - > { NAME } , 'state' , 0 ) eq 'received presentation' ;
readingsSingleUpdate ( $ hash , 'SKETCH_NAME' , $ msg - > { payload } , 1 ) ;
2019-01-04 11:58:28 +00:00
delete $ hash - > { FW_DATA } if ( defined $ hash - > { FW_DATA } ) ;
2019-01-26 16:49:32 +00:00
$ hash - > { nowSleeping } = 0 if $ hash - > { nowSleeping } ;
2019-01-04 11:58:28 +00:00
if ( defined $ hash - > { getCommentReadings } ) {
2020-05-13 06:35:26 +00:00
if ( $ hash - > { getCommentReadings } == 2 ) {
2019-01-04 11:58:28 +00:00
delete $ hash - > { getCommentReadings } ;
2020-05-13 06:35:26 +00:00
} elsif ( $ hash - > { getCommentReadings } == 1 ) {
$ hash - > { getCommentReadings } + + ;
2019-01-04 11:58:28 +00:00
}
}
2019-01-27 14:37:06 +00:00
Log3 $ name , 5 , "leaving Sketch Name update" ;
2020-05-13 06:35:26 +00:00
return ;
}
if ( $ type == I_SKETCH_VERSION ) {
2019-01-04 11:58:28 +00:00
#$hash->{$typeStr} = $msg->{payload};
readingsSingleUpdate ( $ hash , "SKETCH_VERSION" , $ msg - > { payload } , 1 ) ;
2020-05-13 06:35:26 +00:00
if ( $ hash - > { asyncGet } && $ hash - > { asyncGet } { reading } eq "presentation" ) {
RemoveInternalTimer ( $ hash - > { asyncGet } ) ;
my $ version = $ msg - > { payload } ;
my $ sketchn = ReadingsVal ( $ hash - > { NAME } , 'SKETCH_NAME' , 'unknown' ) ;
asyncOutput ( $ hash - > { asyncGet } { CL } , "Sketch info:\n----------------------------\nName: $sketchn\nfirmware version: $version" ) ;
delete ( $ hash - > { asyncGet } ) ;
}
return ;
}
if ( $ type == I_HEARTBEAT_REQUEST ) {
2019-01-04 11:58:28 +00:00
refreshInternalMySTimer ( $ hash , "Alive" ) if $ hash - > { timeoutAlive } ;
2020-05-13 06:35:26 +00:00
return ;
}
if ( $ type == I_HEARTBEAT_RESPONSE ) {
2020-10-06 05:25:49 +00:00
refreshInternalMySTimer ( $ hash , "Alive" ) if $ hash - > { timeoutAlive } ;
2020-05-13 06:35:26 +00:00
if ( $ hash - > { asyncGet } && "heartbeat" eq $ hash - > { asyncGet } { reading } ) {
my $ duration = sprintf ( "%.1f" , ( gettimeofday ( ) - $ hash - > { asyncGet } { start } ) * 1000 ) ;
RemoveInternalTimer ( $ hash - > { asyncGet } ) ;
asyncOutput ( $ hash - > { asyncGet } { CL } , "heartbeat request answered, roundtrip duration: $duration ms" ) ;
delete ( $ hash - > { asyncGet } ) ;
}
2020-10-06 05:25:49 +00:00
2019-03-17 06:33:39 +00:00
if ( $ hash - > { nowSleeping } ) {
$ hash - > { nowSleeping } = 0 ;
sendRetainedMessages ( $ hash ) ;
}
2019-01-04 11:58:28 +00:00
#$hash->{$typeStr} = $msg->{payload};
2020-05-13 06:35:26 +00:00
return ;
}
if ( $ type == I_PRE_SLEEP_NOTIFICATION ) {
2019-11-13 19:21:08 +00:00
$ hash - > { preSleep } = $ msg - > { payload } // 500 ;
2019-01-04 11:58:28 +00:00
refreshInternalMySTimer ( $ hash , "Asleep" ) ;
refreshInternalMySTimer ( $ hash , "Alive" ) if $ hash - > { timeoutAlive } ;
MYSENSORS:: Timer ( $ hash ) ;
2019-03-17 06:33:39 +00:00
sendRetainedMessages ( $ hash ) ;
2020-05-13 06:35:26 +00:00
return ;
}
if ( $ type == I_POST_SLEEP_NOTIFICATION ) {
2019-05-03 05:02:58 +00:00
readingsSingleUpdate ( $ hash , "sleepState" , "awake" , 1 ) ;
2019-01-04 11:58:28 +00:00
$ hash - > { nowSleeping } = 0 ;
refreshInternalMySTimer ( $ hash , "Alive" ) if $ hash - > { timeoutAlive } ;
2020-05-13 06:35:26 +00:00
return ;
}
if ( $ type == I_VERSION || $ type == I_GATEWAY_READY || $ type == I_INCLUSION_MODE || $ type == I_LOG_MESSAGE || $ type == I_LOCKED ) {
$ hash - > { $ typeStr } = $ msg - > { payload } ;
return ;
}
if ( $ type == I_CONFIG ) {
if ( $ msg - > { ack } ) {
Log3 ( $ name , 4 , "MYSENSORS_DEVICE $name: response to config-request acknowledged" ) ;
} else {
readingsSingleUpdate ( $ hash , "parentId" , $ msg - > { payload } , 1 ) ;
$ hash - > { nowSleeping } = 0 if $ hash - > { nowSleeping } ;
sendClientMessage ( $ hash ,
cmd = > C_INTERNAL ,
ack = > 0 ,
childId = > 255 ,
subType = > I_CONFIG ,
payload = > AttrVal ( $ name , "config" , "M" )
) ;
Log3 ( $ name , 4 , "MYSENSORS_DEVICE $name: respond to config-request, node parentId = " . $ msg - > { payload } ) ;
}
return ;
}
if ( $ type == I_CHILDREN ) {
readingsSingleUpdate ( $ hash , "state" , "routingtable cleared" , 1 ) ;
Log3 ( $ name , 3 , "MYSENSORS_DEVICE $name: routingtable cleared" ) ;
return ;
}
if ( $ type == I_DEBUG ) {
last if ( $ msg - > { ack } ) ;
my $ dbglev = $ hash - > { I_DEBUG } ;
my % rnames2 = ( "1" = > "XDBG_CPU_FREQUENCY" , "2" = > "XDBG_CPU_VOLTAGE" , "3" = > "XDBG_FREE_MEMORY" ) ;
my % payloads2 = ( "1" = > "V" , "2" = > "M" ) ;
readingsSingleUpdate ( $ hash , $ rnames2 { $ dbglev } , $ msg - > { payload } , 1 ) ;
if ( $ dbglev < 3 ) {
$ hash - > { I_DEBUG } + + ;
sendClientMessage ( $ hash ,
cmd = > C_INTERNAL ,
ack = > 0 ,
subType = > I_DEBUG ,
payload = > $ payloads2 { $ dbglev }
) ;
} else {
delete $ hash - > { I_DEBUG } ;
if ( $ hash - > { asyncGet } && $ hash - > { asyncGet } { reading } eq "RSSI" ) {
RemoveInternalTimer ( $ hash - > { asyncGet } ) ;
my $ mem = $ msg - > { payload } ;
my $ freq = ReadingsVal ( $ hash - > { NAME } , 'XDBG_CPU_FREQUENCY' , 'unknown' ) ;
my $ volt = ReadingsVal ( $ hash - > { NAME } , 'XDBG_CPU_VOLTAGE' , 'unknown' ) ;
asyncOutput ( $ hash - > { asyncGet } { CL } , "Debug info:\n----------------------------\nMCU CPU Frequency: $freq\nMCU Voltage: $volt\nMCU Free Memory: $mem" ) ;
delete ( $ hash - > { asyncGet } ) ;
}
}
return ;
}
if ( $ type == I_SIGNAL_REPORT_RESPONSE ) {
return if $ msg - > { ack } ;
2022-11-06 20:15:51 +00:00
my % rnames = ( 1 = > 'R_RSSI_to_Parent' , 2 = > 'R_RSSI_from_Parent' , 3 = > 'R_SNR_to_Parent' , 4 = > 'R_SNR_from_Parent' , 5 = > 'R_TX_Powerlevel_Pct' , 6 = > 'R_TX_Powerlevel_dBm' , 7 = > 'R_Uplink_Quality' ) ;
2020-05-13 06:35:26 +00:00
if ( $ msg - > { payload } != - 256 ) {
my $ subSet = $ hash - > { I_RSSI } ;
readingsSingleUpdate ( $ hash , $ rnames { $ subSet } , $ msg - > { payload } , 1 ) ;
if ( $ subSet < 7 ) {
$ hash - > { I_RSSI } + + ;
2022-11-06 20:15:51 +00:00
my % payloads = ( 1 = > 'R!' , 2 = > 'S' , 3 = > 'S!' , 4 = > 'P' , 5 = > 'T' , 6 = > 'U' ) ;
2020-05-13 06:35:26 +00:00
sendClientMessage ( $ hash ,
cmd = > C_INTERNAL ,
ack = > 0 ,
subType = > I_SIGNAL_REPORT_REQUEST ,
payload = > $ payloads { $ subSet }
) ;
2022-11-06 20:15:51 +00:00
return ;
}
}
if ( $ hash - > { asyncGet } && $ hash - > { asyncGet } { reading } eq 'RSSI' ) {
RemoveInternalTimer ( $ hash - > { asyncGet } ) ;
if ( $ msg - > { payload } == - 256 && ( ! $ hash - > { I_RSSI } || $ hash - > { I_RSSI } == 1 ) ) {
asyncOutput ( $ hash - > { asyncGet } { CL } , 'Your transport type seems not to support asking RSSI values' ) ;
2020-05-13 06:35:26 +00:00
} else {
RemoveInternalTimer ( $ hash - > { asyncGet } ) ;
2022-11-06 20:15:51 +00:00
my $ txt = 'RSSI info:\n----------------------------\n' ;
my % txts = ( 1 = > 'to parent: ' , 2 = > '\nfrom parent:' , 3 = > '\nSNR to parent: ' , 4 = > '\nSNR from parent:' , 5 = > '\nPower level %: ' , 6 = > '\nPower level dBm:' , 7 = > '\nUplink Quality:' ) ;
for my $ i ( 1 .. $ hash - > { I_RSSI } ) {
my $ rv = ReadingsVal ( $ hash - > { NAME } , $ rnames { $ i } , 'unknown' ) ;
next if $ rv eq 'unknown' ;
$ txt . = "$txts{$i} $rv" ;
}
asyncOutput ( $ hash - > { asyncGet } { CL } , $ txt ) ;
2020-05-13 06:35:26 +00:00
}
delete ( $ hash - > { asyncGet } ) ;
2022-11-06 20:15:51 +00:00
delete $ hash - > { I_RSSI } ;
2020-05-13 06:35:26 +00:00
}
}
return ;
2014-10-22 20:37:52 +00:00
}
2020-05-13 06:35:26 +00:00
sub sendClientMessage {
2019-01-04 11:58:28 +00:00
my ( $ hash , % msg ) = @ _ ;
$ msg { radioId } = $ hash - > { radioId } ;
my $ name = $ hash - > { NAME } ;
2022-02-10 05:13:15 +00:00
$ msg { ack } = $ hash - > { ack } if ! defined $ msg { ack } ;
2019-01-04 11:58:28 +00:00
my $ messages = $ hash - > { retainedMessagesForRadioId } - > { messages } ;
2022-02-10 05:13:15 +00:00
if ( ! $ hash - > { nowSleeping } ) {
2019-01-04 11:58:28 +00:00
sendMessage ( $ hash - > { IODev } , % msg ) ;
refreshInternalMySTimer ( $ hash , "Ack" ) if ( ( $ msg { ack } or $ hash - > { IODev } - > { ack } ) and $ hash - > { timeoutAck } ) ;
Log3 ( $ name , 5 , "$name is not sleeping, sending message!" ) ;
2019-03-17 06:33:39 +00:00
if ( $ hash - > { nowSleeping } ) {
$ hash - > { nowSleeping } = 0 ;
sendRetainedMessages ( $ hash ) ;
}
2019-01-04 11:58:28 +00:00
$ hash - > { retainedMessages } = scalar ( @$ messages ) if ( defined $ hash - > { retainedMessages } ) ;
} else {
2019-01-29 06:40:22 +00:00
Log3 ( $ name , 5 , "$name is sleeping, enqueing message! " ) ;
2019-01-04 11:58:28 +00:00
#write to queue if node is asleep
2022-02-10 05:13:15 +00:00
if ( ! defined $ hash - > { retainedMessages } ) {
2019-01-04 11:58:28 +00:00
$ messages = { messages = > [ % msg ] } ;
$ hash - > { retainedMessages } = 1 ;
Log3 ( $ name , 5 , "$name: No array yet for enqueued messages, building it!" ) ;
} else {
2019-05-03 05:02:58 +00:00
@$ messages = grep {
$ _ - > { childId } != $ msg { childId }
or $ _ - > { cmd } != $ msg { cmd }
or $ _ - > { subType } != $ msg { subType }
} @$ messages ;
push @$ messages , \ % msg ;
2020-05-13 06:35:26 +00:00
eval { $ hash - > { retainedMessages } = scalar ( @$ messages ) } ; #might be critical!
2019-01-04 11:58:28 +00:00
}
}
2020-05-13 06:35:26 +00:00
return ;
2018-10-24 10:56:06 +00:00
}
2020-05-13 06:35:26 +00:00
sub rawToMappedReading {
2019-01-04 11:58:28 +00:00
my ( $ hash , $ type , $ childId , $ value ) = @ _ ;
my $ name ;
if ( defined ( my $ mapping = $ hash - > { readingMappings } - > { $ childId } - > { $ type } ) ) {
2020-05-13 06:35:26 +00:00
my $ val = $ mapping - > { val } // $ hash - > { '.typeMappings' } - > { $ type } - > { val } ;
2019-01-04 11:58:28 +00:00
return ( $ mapping - > { name } , defined $ val ? ( $ val - > { $ value } // $ value ) : $ value ) ;
}
2020-05-13 06:35:26 +00:00
die "no reading-mapping for childId $childId, type " . ( $ hash - > { '.typeMappings' } - > { $ type } - > { type } ? $ hash - > { '.typeMappings' } - > { $ type } - > { type } : variableTypeToStr ( $ type ) ) ;
2014-10-22 20:37:52 +00:00
}
2020-05-13 06:35:26 +00:00
sub mappedReadingToRaw {
2019-01-04 11:58:28 +00:00
my ( $ hash , $ reading , $ value ) = @ _ ;
my $ readingsMapping = $ hash - > { readingMappings } ;
2020-05-13 06:35:26 +00:00
for my $ id ( keys %$ readingsMapping ) {
2019-01-04 11:58:28 +00:00
my $ readingTypesForId = $ readingsMapping - > { $ id } ;
2020-05-13 06:35:26 +00:00
for my $ type ( keys %$ readingTypesForId ) {
2019-01-04 11:58:28 +00:00
if ( ( $ readingTypesForId - > { $ type } - > { name } // "" ) eq $ reading ) {
2020-05-13 06:35:26 +00:00
if ( my $ valueMappings = $ readingTypesForId - > { $ type } - > { val } // $ hash - > { '.typeMappings' } - > { $ type } - > { val } ) {
2019-01-04 11:58:28 +00:00
if ( my @ mappedValues = grep { $ valueMappings - > { $ _ } eq $ value } keys %$ valueMappings ) {
return ( $ type , $ id , shift @ mappedValues ) ;
}
2014-10-25 10:17:25 +00:00
}
2019-01-04 11:58:28 +00:00
return ( $ type , $ id , $ value ) ;
2014-10-25 10:17:25 +00:00
}
}
2014-10-22 20:37:52 +00:00
}
2019-01-04 11:58:28 +00:00
die "no mapping for reading $reading" ;
}
2020-05-13 06:35:26 +00:00
sub short2Hex {
my $ val = shift // return ;
2019-01-04 11:58:28 +00:00
my $ temp = sprintf ( "%04X" , $ val ) ;
return substr ( $ temp , 2 , 2 ) . substr ( $ temp , 0 , 2 ) ;
}
2020-05-13 06:35:26 +00:00
sub hex2Short {
my $ val = shift // return ;
2019-01-04 11:58:28 +00:00
return hex ( substr ( $ val , 2 , 2 ) . substr ( $ val , 0 , 2 ) ) ;
}
2020-05-13 06:35:26 +00:00
sub flashFirmware {
my $ hash = shift ;
my $ fwType = shift // return ;
my $ name = $ hash - > { NAME } ;
2019-01-04 11:58:28 +00:00
my ( $ version , $ filename , $ firmwarename ) = getLatestFirmware ( $ hash - > { IODev } , $ fwType ) ;
if ( not defined $ filename ) {
Log3 ( $ name , 3 , "No firmware defined for type $fwType - not flashing!" ) ;
return "No firmware defined for type " . $ fwType ;
}
my ( $ err , @ lines ) = FileRead ( { FileName = > "./FHEM/firmware/" . $ filename , ForceType = > "file" } ) ;
if ( defined ( $ err ) && $ err ) {
Log3 ( $ name , 3 , "Could not read firmware file - $err: not flashing!" ) ;
return "Could not read firmware file - $err" ;
} else {
my $ start = 0 ;
my $ end = 0 ;
my @ fwdata = ( ) ;
2022-02-10 05:13:15 +00:00
readingsSingleUpdate ( $ hash , 'state' , 'updating' , 1 ) if ! ReadingsVal ( $ hash - > { NAME } , 'state' , 0 ) eq 'updating' ;
2019-01-04 11:58:28 +00:00
for ( my $ i = 0 ; $ i < @ lines ; $ i + + ) {
chomp ( my $ row = $ lines [ $ i ] ) ;
if ( length ( $ row ) > 0 ) {
2020-05-13 06:35:26 +00:00
$ row =~ s/^:+//xms ;
2019-01-04 11:58:28 +00:00
my $ reclen = hex ( substr ( $ row , 0 , 2 ) ) ;
my $ offset = hex ( substr ( $ row , 2 , 4 ) ) ;
my $ rectype = hex ( substr ( $ row , 6 , 2 ) ) ;
my $ data = substr ( $ row , 8 , 2 * $ reclen ) ;
if ( $ rectype == 0 ) {
if ( ( $ start == 0 ) && ( $ end == 0 ) ) {
if ( $ offset % 128 > 0 ) {
Log3 ( $ name , 3 , "error loading hex file - offset can't be devided by 128" ) ;
return "error loading hex file - offset can't be devided by 128" ;
}
$ start = $ offset ;
$ end = $ offset ;
}
if ( $ offset < $ end ) {
Log3 ( $ name , 3 , "error loading hex file - offset can't be devided by 128" ) ;
return "error loading hex file - offset lower than end" ;
}
while ( $ offset > $ end ) {
push ( @ fwdata , 255 ) ;
$ end + + ;
}
for ( my $ i = 0 ; $ i < $ reclen ; $ i + + ) {
push ( @ fwdata , hex ( substr ( $ data , $ i * 2 , 2 ) ) ) ;
}
$ end += $ reclen ;
}
}
}
my $ pad = $ end % 128 ; # ATMega328 has 64 words per page / 128 bytes per page
for ( my $ i = 0 ; $ i < 128 - $ pad ; $ i + + ) {
push ( @ fwdata , 255 ) ;
$ end + + ;
}
my $ blocks = ( $ end - $ start ) / 16 ;
my $ crc = 0xFFFF ;
for ( my $ index = 0 ; $ index < @ fwdata ; + + $ index ) {
$ crc ^= $ fwdata [ $ index ] & 0xFF ;
for ( my $ bit = 0 ; $ bit < 8 ; + + $ bit ) {
if ( ( $ crc & 0x01 ) == 0x01 ) {
$ crc = ( ( $ crc >> 1 ) ^ 0xA001 ) ;
} else {
$ crc = ( $ crc >> 1 ) ;
}
}
}
if ( ( $ version != ReadingsNum ( $ name , "FW_VERSION" , - 1 ) ) || ( $ blocks != ReadingsNum ( $ name , "FW_BLOCKS" , - 1 ) ) || ( $ crc != ReadingsNum ( $ name , "FW_CRC" , - 1 ) ) ) {
Log3 ( $ name , 4 , "$name: Flashing './FHEM/firmware/" . $ filename . "'" ) ;
$ hash - > { FW_DATA } = \ @ fwdata ;
my $ payload = short2Hex ( $ fwType ) . short2Hex ( $ version ) . short2Hex ( $ blocks ) . short2Hex ( $ crc ) ;
if ( defined $ hash - > { OTA_Chan76_IODev } ) {
sendMessage ( $ hash - > { OTA_Chan76_IODev } , radioId = > $ hash - > { radioId } , childId = > 255 , ack = > 0 , cmd = > C_STREAM , subType = > ST_FIRMWARE_CONFIG_RESPONSE , payload = > $ payload ) ;
Log3 ( $ name , 5 , "Directly send firmware info to $name using OTA_Chan76_IODev" ) ;
} elsif ( AttrVal ( $ name , "OTA_BL_Type" , "" ) eq "MYSBootloader" ) {
sendMessage ( $ hash - > { IODev } , radioId = > $ hash - > { radioId } , childId = > 255 , ack = > 0 , cmd = > C_STREAM , subType = > ST_FIRMWARE_CONFIG_RESPONSE , payload = > $ payload ) ;
Log3 ( $ name , 5 , "Directly send firmware info to $name using regular IODev" ) ;
} else {
sendClientMessage ( $ hash , childId = > 255 , cmd = > C_STREAM , ack = > 0 , subType = > ST_FIRMWARE_CONFIG_RESPONSE , payload = > $ payload ) ;
Log3 ( $ name , 5 , "Send firmware info to $name using sendClientMessage" ) ;
}
2020-05-13 06:35:26 +00:00
return ;
2019-01-04 11:58:28 +00:00
} else {
return "Nothing todo - latest firmware already installed" ;
}
2019-01-26 16:49:32 +00:00
}
2020-05-13 06:35:26 +00:00
return ;
2014-10-22 20:37:52 +00:00
}
2020-05-13 06:35:26 +00:00
sub refreshInternalMySTimer {
my $ hash = shift ;
my $ calltype = shift // return ;
2019-01-04 11:58:28 +00:00
my $ name = $ hash - > { NAME } ;
2019-05-03 05:02:58 +00:00
my $ heart = ReadingsVal ( $ hash , "heartbeat" , "dead" ) ;
2019-01-04 11:58:28 +00:00
Log3 $ name , 5 , "$name: refreshInternalMySTimer called ($calltype)" ;
if ( $ calltype eq "Alive" ) {
2019-05-03 05:02:58 +00:00
RemoveInternalTimer ( $ hash , "MYSENSORS::DEVICE::timeoutAlive" ) ;
2020-05-13 06:35:26 +00:00
my $ nextTrigger = gettimeofday ( ) + $ hash - > { timeoutAlive } ;
2019-05-03 05:02:58 +00:00
InternalTimer ( $ nextTrigger , "MYSENSORS::DEVICE::timeoutAlive" , $ hash ) ;
if ( $ heart ne "NACK" or $ heart eq "NACK" and @ { $ hash - > { IODev } - > { messagesForRadioId } - > { $ hash - > { radioId } } - > { messages } } == 0 ) {
readingsSingleUpdate ( $ hash , "heartbeat" , "alive" , 1 ) ;
2019-01-04 11:58:28 +00:00
}
} elsif ( $ calltype eq "Ack" ) {
2020-10-06 05:25:49 +00:00
readingsSingleUpdate ( $ hash , "heartbeat" , "alive" , 1 ) if $ heart eq "NACK" and @ { $ hash - > { IODev } - > { messagesForRadioId } - > { $ hash - > { radioId } } - > { messages } } == 0 ;
2019-05-03 05:02:58 +00:00
RemoveInternalTimer ( $ hash , "MYSENSORS::DEVICE::timeoutAck" ) ;
2020-05-13 06:35:26 +00:00
my $ nextTrigger = gettimeofday ( ) + $ hash - > { timeoutAck } ;
2019-05-03 05:02:58 +00:00
InternalTimer ( $ nextTrigger , "MYSENSORS::DEVICE::timeoutAck" , $ hash ) ;
2019-01-26 16:49:32 +00:00
Log3 $ name , 5 , "$name: Ack timeout timer set at $nextTrigger" ;
2019-01-04 11:58:28 +00:00
} elsif ( $ calltype eq "Asleep" ) {
2019-05-03 05:02:58 +00:00
RemoveInternalTimer ( $ hash , "MYSENSORS::DEVICE::timeoutAwake" ) ;
2019-11-13 19:21:08 +00:00
my $ postsleeptime = ( $ hash - > { preSleep } - 200 ) / 1000 ;
$ postsleeptime = 0 if $ postsleeptime < 0 ;
2020-05-13 06:35:26 +00:00
my $ nextTrigger = gettimeofday ( ) + $ postsleeptime ;
2019-05-03 05:02:58 +00:00
InternalTimer ( $ nextTrigger , "MYSENSORS::DEVICE::timeoutAwake" , $ hash ) ;
2019-01-04 11:58:28 +00:00
Log3 $ name , 5 , "$name: Awake timeout timer set at $nextTrigger" ;
}
2020-05-13 06:35:26 +00:00
return ;
2018-04-13 08:46:18 +00:00
}
2020-05-13 06:35:26 +00:00
sub timeoutAlive {
my $ hash = shift // return ;
2019-05-03 05:02:58 +00:00
Log3 $ hash - > { NAME } , 5 , "$hash->{NAME}: timeoutAlive called" ;
2022-02-10 05:13:15 +00:00
readingsSingleUpdate ( $ hash , 'heartbeat' , 'dead' , 1 ) if ReadingsVal ( $ hash , 'heartbeat' , 'dead' ) ne 'NACK' ;
2020-05-13 06:35:26 +00:00
return ;
2019-05-03 05:02:58 +00:00
}
2020-05-13 06:35:26 +00:00
sub timeoutAck {
my $ hash = shift // return ;
2019-05-03 05:02:58 +00:00
Log3 $ hash - > { NAME } , 5 , "$hash->{NAME}: timeoutAck called" ;
if ( $ hash - > { IODev } - > { outstandingAck } == 0 ) {
Log3 $ hash - > { NAME } , 4 , "$hash->{NAME}: timeoutAck called, no outstanding Acks at all" ;
readingsSingleUpdate ( $ hash , "heartbeat" , "alive" , 1 ) if ( ReadingsVal ( $ hash , "heartbeat" , "dead" ) eq "NACK" ) ;
2019-05-11 14:49:14 +00:00
} elsif ( my $ outs = $ hash - > { IODev } - > { messagesForRadioId } - > { $ hash - > { radioId } } - > { messages } ) {
my $ outstanding = @$ outs ;
Log3 $ hash - > { NAME } , 4 , "$hash->{NAME}: timeoutAck called, outstanding: $outstanding" ;
readingsSingleUpdate ( $ hash , "heartbeat" , "NACK" , 1 ) ;
2019-05-03 05:02:58 +00:00
} else {
2019-05-11 14:49:14 +00:00
Log3 $ hash - > { NAME } , 4 , "$hash->{NAME}: timeoutAck called, no outstanding Acks for Node" ;
readingsSingleUpdate ( $ hash , "heartbeat" , "alive" , 1 ) if ( ReadingsVal ( $ hash , "heartbeat" , "dead" ) eq "NACK" ) ;
2019-01-04 11:58:28 +00:00
}
2020-05-13 06:35:26 +00:00
return ;
2018-04-13 08:46:18 +00:00
}
2020-05-13 06:35:26 +00:00
sub timeoutAwake {
my $ hash = shift // return ;
2019-05-03 05:02:58 +00:00
Log3 $ hash - > { NAME } , 5 , "$hash->{NAME}: timeoutAwake called" ;
readingsSingleUpdate ( $ hash , "sleepState" , "asleep" , 1 ) ;
$ hash - > { nowSleeping } = 1 ;
2020-05-13 06:35:26 +00:00
return ;
2019-05-03 05:02:58 +00:00
}
2020-05-13 06:35:26 +00:00
sub sendRetainedMessages {
my $ hash = shift // return ;
2019-03-17 06:33:39 +00:00
my $ retainedMsg ;
while ( ref ( $ retainedMsg = shift @ { $ hash - > { retainedMessagesForRadioId } - > { messages } } ) eq 'HASH' ) {
sendClientMessage ( $ hash , %$ retainedMsg ) ;
2020-05-13 06:35:26 +00:00
}
return ;
2019-03-17 06:33:39 +00:00
}
2014-10-22 20:37:52 +00:00
1 ;
2020-05-13 06:35:26 +00:00
__END__
2014-10-22 20:37:52 +00:00
= pod
2017-09-25 14:01:02 +00:00
= item device
= item summary includes MYSENSOR clients
= item summary_DE integriert MYSENSOR Sensoren
2014-10-22 20:37:52 +00:00
= begin html
2021-05-05 06:24:21 +00:00
< a id = "MYSENSORS_DEVICE" > </a>
2014-10-22 20:37:52 +00:00
<h3> MYSENSORS_DEVICE </h3>
2021-05-05 06:24:21 +00:00
<p> represents a mysensors sensor attached to a mysensor - node </p>
<p> requires a < a href = "#MYSENSORS" > MYSENSORS </a> - device as IODev </p>
< a id = "MYSENSORS_DEVICE-define" > </a>
<h4> Define </h4>
<p> <code> define & lt ; name & gt ; MYSENSORS_DEVICE & lt ; Sensor - type & gt ; & lt ; node - id & gt ; </code>
<p> Specifies the MYSENSOR_DEVICE device . </p>
< a id = "MYSENSORS_DEVICE-set" > </a>
<h4> Set </h4>
2014-10-22 20:37:52 +00:00
<ul>
2021-05-15 14:04:30 +00:00
<li> < a id = "MYSENSORS_DEVICE-set-attrTemplate" > </a>
<p> <b> AttrTemplate </b> </p>
2021-05-05 06:24:21 +00:00
<p> Helps to easily configure your devices . Just get a list of all available attrTremplates by issuing <br>
<code> set & lt ; name & gt ; attrTemplate ? </code> </p>
<p> Have a look at the descriptions and choose a suitable one . Then use the drop - down list and click "set" or use <br>
<code> set & lt ; name & gt ; attrTemplate A_02a_atmospheric_pressure </code> </p>
</li>
2021-05-15 14:04:30 +00:00
<li> < a id = "MYSENSORS_DEVICE-set-clear" > </a>
<p> <b> clear </b> </p>
2021-05-05 06:24:21 +00:00
<p> <code> set & lt ; name & gt ; clear </code> </p>
<p> clears MySensors EEPROM area and reboot ( i . e . & quot ; factory & quot ; reset ) - requires MY_SPECIAL_DEBUG </p>
</li>
2021-05-15 14:04:30 +00:00
<li> < a id = "MYSENSORS_DEVICE-set-flash" > </a>
<p> <b> flash </b> </p>
2021-05-05 06:24:21 +00:00
<p> <code> set & lt ; name & gt ; flash </code> </p>
<p> Checks whether a newer firmware version is available . If a newer firmware version is
available the flash procedure is started . The sensor node must support FOTA for
this . </p>
</li>
2021-05-15 14:04:30 +00:00
<li> < a id = "MYSENSORS_DEVICE-set-fwType" > </a>
<p> <b> fwType </b> </p>
2021-05-05 06:24:21 +00:00
<p> <code> set & lt ; name & gt ; fwType & lt ; value & gt ; </code> </p>
<p> assigns a firmware type to this node ( must be a numeric value in the range 0 .. 65536 ) . <br>
2021-05-15 14:04:30 +00:00
Should be contained in the < a href = "#MYSENSORS-attr-ota_firmwareconfig" > FOTA configuration file </a> </p> . <p> Note: Firmware config file by default only is read at startup . If you change it later , just issue a <i> connect </i> command to the GW to update also Internal values . </p>
2021-05-05 06:24:21 +00:00
</li>
2021-05-15 14:04:30 +00:00
<li> < a id = "MYSENSORS_DEVICE-set-time" > </a>
<p> <b> time </b> </p>
2021-05-05 06:24:21 +00:00
<p> <code> set & lt ; name & gt ; time </code> </p>
<p> sets time for nodes ( that support it ) </p>
</li>
2021-05-15 14:04:30 +00:00
<li> < a id = "MYSENSORS_DEVICE-set-reboot" > </a>
<p> <b> reboot </b> </p>
2021-05-05 06:24:21 +00:00
<p> <code> set & lt ; name & gt ; reboot </code> </p>
<p> 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>
</li>
</ul>
2019-01-04 11:58:28 +00:00
2021-05-05 06:24:21 +00:00
< a id = "MYSENSORS_DEVICE-get" > </a>
<h4> Get </h4>
<ul>
2021-05-15 14:04:30 +00:00
<li> < a id = "MYSENSORS_DEVICE-get-Extended_DEBUG" > </a>
<p> <b> Extended_DEBUG </b> </p>
2021-05-05 06:24:21 +00:00
<p> <code> get & lt ; name & gt ; Extended_DEBUG </code> </p>
<p> requires MY_SPECIAL_DEBUG <br>
retrieves the CPU frequency , CPU voltage and free memory of the sensor </p>
</li>
2021-05-15 14:04:30 +00:00
<li> < a id = "MYSENSORS_DEVICE-get-ReadingsFromComment" > </a>
<p> <b> ReadingsFromComment </b> </p>
2021-05-05 06:24:21 +00:00
<p> <code> get & lt ; name & gt ; ReadingsFromComment </code> </p>
<p> rebuild reding names from comments of presentation messages if available <br>
After issuing this get check the log for changes . </p>
</li>
2021-05-15 14:04:30 +00:00
<li> < a id = "MYSENSORS_DEVICE-get-RSSI" > </a>
<p> <b> RSSI </b> </p>
2021-05-05 06:24:21 +00:00
<p> <code> get & lt ; name & gt ; RSSI </code> </p>
<p> requires MY_SIGNAL_REPORT_ENABLED , not supported by all transportation layers <br>
delievers a set of Signal Quality information . </p>
</li>
2018-04-13 08:46:18 +00:00
</ul>
2021-05-05 06:24:21 +00:00
< a id = "MYSENSORS_DEVICE-attr" > </a>
<h4> Attributes </h4>
<ul>
2021-05-15 14:04:30 +00:00
<li> < a id = "MYSENSORS_DEVICE-attr-config" > </a>
<p> <b> config </b> </p>
2021-05-05 06:24:21 +00:00
<p> <code> attr & lt ; name & gt ; config [ & lt ; M | I & gt ; ] </code> </p>
<p> configures metric ( M ) or inch ( I ) . Defaults to 'M' </p>
</li>
2021-05-15 14:04:30 +00:00
<li> < a id = "MYSENSORS_DEVICE-attr-OTA_autoUpdate" > </a>
<p> <b> OTA_autoUpdate </b> </p>
2021-05-05 06:24:21 +00:00
<p> <code> attr & lt ; name & gt ; OTA_autoUpdate [ & lt ; 0 | 1 & gt ; ] </code> </p>
<p> specifies whether an automatic update of the sensor node should be performed ( 1 ) during startup of the node or not ( 0 ) . Defaults to 0 </p>
</li>
2021-05-15 14:04:30 +00:00
<li> < a id = "MYSENSORS_DEVICE-attr-setCommands" >
<p> </a> <b> setCommands </b> </p>
2021-05-05 06:24:21 +00:00
<p> <code> attr & lt ; name & gt ; setCommands [ & lt ; command:reading:value & gt ; ] * </code> </p>
<p> configures one or more commands that can be executed by set . </p>
<p> e . g . : <br>
<code> attr & lt ; name & gt ; setCommands on:switch_1:on off:switch_1:off </code> </p>
<p> if list of commands contains both 'on' and 'off' < a href = "#setExtensions" > set extensions </a> are supported </p>
</li>
2021-05-15 14:04:30 +00:00
<li> < a id = "MYSENSORS_DEVICE-attr-setReading_ " data - pattern = "setReading_.*" > </a>
<p> <b> setReading_ & lt ; reading & gt ; </b> </p>
2021-05-05 06:24:21 +00:00
<p> <code> attr & lt ; name & gt ; setReading_ & lt ; reading & gt ; [ & lt ; value & gt ; ] * </code> </p>
<p> configures a reading that can be modified by set - command </p>
<p> e . g . : <br>
<code> attr & lt ; name & gt ; setReading_switch_1 on , off </code> </p>
</li>
2021-05-15 14:04:30 +00:00
<li> < a id = "MYSENSORS_DEVICE-attr-setExtensionsEvent" > </a>
<p> <b> setExtensionsEvent </b> </p>
2021-05-05 06:24:21 +00:00
<p> <code> attr & lt ; name & gt ; setExtensionsEvent </code> </p>
<p> If set , the event will contain the command implemented by SetExtensions ( e . g . on - for - timer 10 ) , else the executed command ( e . g . on ) . </p>
</li>
2021-05-15 14:04:30 +00:00
<li> < a id = "MYSENSORS_DEVICE-attr-mapReading_" data - pattern = "mapReading_.*" > </a>
<p> <b> mapReading_ & lt ; reading & gt ; </b> </p>
2021-05-05 06:24:21 +00:00
<p> <code> attr & lt ; name & gt ; mapReading_ & lt ; reading & gt ; & lt ; childId & gt ; & lt ; readingtype & gt ; [ & lt ; value & gt ; : & lt ; mappedvalue & gt ; ] * </code> </p>
<p> configures the reading - name for a given childId and sensortype </p>
<p> e . g . : <br>
<code> attr xxx mapReading_aussentemperatur 123 temperature </code> <br>
<code> attr xxx mapReading_leftwindow 10 status 1 : closed 0 : open </code> </p>
<p> See also mapReadingType for setting defaults for types without predefined defaults </p>
</li>
2021-05-15 14:04:30 +00:00
<li> < a id = "MYSENSORS_DEVICE-attr-requestAck" > </a>
<p> <b> requestAck </b> </p>
2021-05-05 06:24:21 +00:00
<p> <code> attr & lt ; name & gt ; requestAck </code> </p>
<p> 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>
</li>
2021-05-15 14:04:30 +00:00
<li> < a id = "MYSENSORS_DEVICE-attr-mapReadingType_" data - pattern = "mapReadingType_.*" > </a>
<p> <b> mapReadingType_ & lt ; reading & gt ; </b> </p>
2021-05-05 06:24:21 +00:00
<p> <code> attr & lt ; name & gt ; mapReadingType_ & lt ; reading & gt ; & lt ; new reading name & gt ; [ & lt ; value & gt ; : & lt ; mappedvalue & gt ; ] * </code> </p>
<p> configures reading type names that should be used instead of technical names </p>
<p> e . g . : <br>
<code> attr xxx mapReadingType_LIGHT switch 0 : on 1 : off </code> </p>
<p> to be used for mysensor Variabletypes that have no predefined defaults ( yet ) </p>
</li>
2021-05-15 14:04:30 +00:00
<li> < a id = "MYSENSORS_DEVICE-attr-OTA_BL_Type" > </a>
<p> <b> OTA_BL_Type </b> </p>
2021-05-05 06:24:21 +00:00
<p> <code> attr & lt ; name & gt ; OTA_BL_Type & lt ; either Optiboot or MYSBootloader & gt ; * </code> </p>
<p> For other bootloaders than Optiboot V3 .0 OTA updates will only work if bootloader type is specified - MYSBootloader will reboot node if firmware update is started , so make sure , node will really recover </p>
</li>
2021-05-15 14:04:30 +00:00
<li> < a id = "MYSENSORS_DEVICE-attr-OTA_Chan76_IODev" >
<p> </a> <b> OTA_Chan76_IODev </b> </p>
2021-05-05 06:24:21 +00:00
<p> <code> attr & lt ; name & gt ; OTA_Chan76_IODev </code> </p>
<p> As MYSBootloader per default uses nRF24 channel 76 , you may specify a different IODev for OTA data using channel 76 </p>
</li>
2021-05-15 14:04:30 +00:00
<li> < a id = "MYSENSORS_DEVICE-attr-timeoutAck" > </a>
<p> <b> timeoutAck </b> </p>
2021-05-05 06:24:21 +00:00
<p> <code> attr & lt ; name & gt ; timeoutAck & lt ; time in seconds & gt ; * </code> </p>
<p> configures timeout to set device state to NACK in case not all requested acks are received </p>
</li>
2021-05-15 14:04:30 +00:00
<li> < a id = "MYSENSORS_DEVICE-attr-timeoutAlive" > </a>
<p> <b> timeoutAlive </b> </p>
2021-05-05 06:24:21 +00:00
<p> <code> attr & lt ; name & gt ; timeoutAlive & lt ; time in seconds & gt ; * </code> </p>
<p> 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>
</li>
2014-10-22 20:37:52 +00:00
</ul>
= end html
2018-06-22 08:21:12 +00:00
= cut