# $Id$
# v3.5.3 - https://github.com/RFD-FHEM/RFFHEM/tree/master
# The module is inspired by the FHEMduino project and modified in serval ways for processing the incoming messages
# see http://www.fhemwiki.de/wiki/SIGNALDuino
# It was modified also to provide support for raw message handling which can be send from the SIGNALduino
# The purpos is to use it as addition to the SIGNALduino which runs on an arduno nano or arduino uno.
# It routes Messages serval Modules which are already integrated in FHEM. But there are also modules which comes with it.
#
# 2014-2015 S.Butzek, N.Butzek
# 2016-2019 S.Butzek, Ralf9
# 2019-2022 S.Butzek, HomeAutoUser, elektron-bbs
package main;
use strict;
use warnings;
#use version 0.77; our $VERSION = version->declare('v3.5.3');
my $missingModulSIGNALduino = ' ';
use DevIo;
require "99_Utils.pm" if (!defined $modules{"Utils"} || !exists $modules{"Utils"}{"LOADED"} ); ## no critic
use Carp;
no warnings 'portable';
eval {use Data::Dumper qw(Dumper);1};
use constant HAS_JSON => defined eval { require JSON; JSON->import; };
eval {use Scalar::Util qw(looks_like_number);1};
eval {use Time::HiRes qw(gettimeofday);1} ;
use lib::SD_Protocols;
use List::Util qw(first);
#$| = 1; #Puffern abschalten, Hilfreich fuer PEARL WARNINGS Search
#use Math::Round qw();
use constant {
SDUINO_VERSION => '3.5.3', # Datum wird automatisch bei jedem pull request aktualisiert
SDUINO_INIT_WAIT_XQ => 1.5, # wait disable device
SDUINO_INIT_WAIT => 2,
SDUINO_INIT_MAXRETRY => 3,
SDUINO_CMD_TIMEOUT => 10,
SDUINO_KEEPALIVE_TIMEOUT => 60,
SDUINO_KEEPALIVE_MAXRETRY => 3,
SDUINO_WRITEQUEUE_NEXT => 0.3,
SDUINO_WRITEQUEUE_TIMEOUT => 2,
SDUINO_DISPATCH_VERBOSE => 5, # default 5
SDUINO_MC_DISPATCH_VERBOSE => 5, # wenn kleiner 5, z.B. 3 dann wird vor dem dispatch mit loglevel 3 die ID und rmsg ausgegeben
SDUINO_MC_DISPATCH_LOG_ID => '12.1', # die o.g. Ausgabe erfolgt nur wenn der Wert mit der ID uebereinstimmt
SDUINO_PARSE_DEFAULT_LENGHT_MIN => 8,
SDUINO_GET_CONFIGQUERY_DELAY => 0.75 # delay for cmd to no overwrite a working cmd
};
#sub SIGNALduino_Attr(@);
#sub SIGNALduino_HandleWriteQueue($);
#sub SIGNALduino_Parse($$$$@);
#sub SIGNALduino_Read($);
#sub SIGNALduino_Ready($);
#sub SIGNALduino_Write($$$);
#sub SIGNALduino_SimpleWrite(@);
#sub SIGNALduino_LoadProtocolHash($);
#sub SIGNALduino_Log3($$$);
#my $debug=0;
our %modules;
our %defs;
my %gets = ( # NameOFCommand => StyleMod for Fhemweb, SubToCall if get is executed, String to send to uC, sub called with response, regex to verify response,
'?' => ['', \&SIGNALduino_Get_FhemWebList ],
'version' => ['noArg', \&SIGNALduino_Get_Command, "V", \&SIGNALduino_CheckVersionResp, 'V\s.*SIGNAL(?:duino|ESP|STM).*(?:\s\d\d:\d\d:\d\d)' ],
'freeram' => ['noArg', \&SIGNALduino_Get_Command, "R", \&SIGNALduino_GetResponseUpdateReading, '^[0-9]+' ] ,
'uptime' => ['noArg', \&SIGNALduino_Get_Command, "t", \&SIGNALduino_CheckUptimeResponse, '^[0-9]+' ],
'cmds' => ['noArg', \&SIGNALduino_Get_Command, "?", \&SIGNALduino_CheckCmdsResponse, '.*' ],
'ping' => ['noArg', \&SIGNALduino_Get_Command, "P", \&SIGNALduino_GetResponseUpdateReading, '^OK$' ],
'config' => ['noArg', \&SIGNALduino_Get_Command, "CG", \&SIGNALduino_GetResponseUpdateReading, '^MS.*MU.*MC.*' ],
'ccconf' => ['noArg', \&SIGNALduino_Get_Command, "C0DnF", \&SIGNALduino_CheckccConfResponse, 'C0Dn11=[A-F0-9a-f]+'],
'ccreg' => ['textFieldNL', \&SIGNALduino_Get_Command_CCReg,"C", \&SIGNALduino_CheckCcregResponse, '^(?:C[A-Fa-f0-9]{2}\s=\s[0-9A-Fa-f]+$|ccreg 00:)'],
'ccpatable' => ['noArg', \&SIGNALduino_Get_Command, "C3E", \&SIGNALduino_CheckccPatableResponse, '^C3E\s=\s.*'],
'rawmsg' => ['textFieldNL', \&SIGNALduino_Get_RawMsg ],
'availableFirmware' => ['noArg', \&SIGNALduino_Get_availableFirmware ]
);
my %patable = (
'433' =>
{
'-30_dBm' => '12',
'-20_dBm' => '0E',
'-15_dBm' => '1D',
'-10_dBm' => '34',
'-5_dBm' => '68',
'0_dBm' => '60',
'5_dBm' => '84',
'7_dBm' => 'C8',
'10_dBm' => 'C0',
},
'868' =>
{
'-30_dBm' => '03',
'-20_dBm' => '0F',
'-15_dBm' => '1E',
'-10_dBm' => '27',
'-5_dBm' => '67',
'0_dBm' => '50',
'5_dBm' => '81',
'7_dBm' => 'CB',
'10_dBm' => 'C2',
},
);
my @ampllist = (24, 27, 30, 33, 36, 38, 40, 42); # rAmpl(dB)
my %sets = (
#Command name [FhemWeb Argument type, code to run]
'?' => ['', \&SIGNALduino_Set_FhemWebList ],
'raw' => ['textFieldNL',\&SIGNALduino_Set_raw ],
'flash' => ['textFieldNL', \&SIGNALduino_Set_flash ],
'reset' => ['noArg', \&SIGNALduino_Set_reset ],
'close' => ['noArg', \&SIGNALduino_Set_close ],
'enableMessagetype' => ['syncedMS,unsyncedMU,manchesterMC', \&SIGNALduino_Set_MessageType ],
'disableMessagetype' => ['syncedMS,unsyncedMU,manchesterMC', \&SIGNALduino_Set_MessageType ],
'sendMsg' => ['textFieldNL',\&SIGNALduino_Set_sendMsg ],
'cc1101_bWidth' => ['58,68,81,102,116,135,162,203,232,270,325,406,464,541,650,812', \&SIGNALduino_Set_bWidth ],
'cc1101_dataRate' => ['textFieldNL', \&cc1101::SetDataRate ],
'cc1101_deviatn' => ['textFieldNL', \&cc1101::SetDeviatn ],
'cc1101_freq' => ['textFieldNL', \&cc1101::SetFreq ],
'cc1101_patable' => ['-30_dBm,-20_dBm,-15_dBm,-10_dBm,-5_dBm,0_dBm,5_dBm,7_dBm,10_dBm', \&cc1101::SetPatable ],
'cc1101_rAmpl' => ['24,27,30,33,36,38,40,42', \&cc1101::setrAmpl ],
'cc1101_reg' => ['textFieldNL', \&cc1101::SetRegisters ],
'cc1101_reg_user' => ['noArg', \&cc1101::SetRegistersUser ],
'cc1101_sens' => ['4,8,12,16', \&cc1101::SetSens ],
'LaCrossePairForSec' => ['textFieldNL', \&SIGNALduino_Set_LaCrossePairForSec ],
);
## Supported config CC1101 ##
my @modformat = ('2-FSK','GFSK','-','ASK/OOK','4-FSK','-','-','MSK');
my @syncmod = ( 'No preamble/sync','15/16 sync word bits detected','16/16 sync word bits detected','30/32 sync word bits detected',
'No preamble/sync, carrier-sense above threshold, carrier-sense above threshold', '15/16 + carrier-sense above threshold',
'16/16 + carrier-sense above threshold', '30/32 + carrier-sense above threshold'
);
my %cc1101_register = ( # for get ccreg 99 and set cc1101_reg
'00' => 'IOCFG2 - 0x0D', # ! the values with spaces for output get ccreg 99 !
'01' => 'IOCFG1 - 0x2E',
'02' => 'IOCFG0 - 0x2D',
'03' => 'FIFOTHR - 0x47',
'04' => 'SYNC1 - 0xD3',
'05' => 'SYNC0 - 0x91',
'06' => 'PKTLEN - 0x3D',
'07' => 'PKTCTRL1 - 0x04',
'08' => 'PKTCTRL0 - 0x32',
'09' => 'ADDR - 0x00',
'0A' => 'CHANNR - 0x00',
'0B' => 'FSCTRL1 - 0x06',
'0C' => 'FSCTRL0 - 0x00',
'0D' => 'FREQ2 - 0x10',
'0E' => 'FREQ1 - 0xB0',
'0F' => 'FREQ0 - 0x71',
'10' => 'MDMCFG4 - 0x57',
'11' => 'MDMCFG3 - 0xC4',
'12' => 'MDMCFG2 - 0x30',
'13' => 'MDMCFG1 - 0x23',
'14' => 'MDMCFG0 - 0xB9',
'15' => 'DEVIATN - 0x00',
'16' => 'MCSM2 - 0x07',
'17' => 'MCSM1 - 0x00',
'18' => 'MCSM0 - 0x18',
'19' => 'FOCCFG - 0x14',
'1A' => 'BSCFG - 0x6C',
'1B' => 'AGCCTRL2 - 0x07',
'1C' => 'AGCCTRL1 - 0x00',
'1D' => 'AGCCTRL0 - 0x91',
'1E' => 'WOREVT1 - 0x87',
'1F' => 'WOREVT0 - 0x6B',
'20' => 'WORCTRL - 0xF8',
'21' => 'FREND1 - 0xB6',
'22' => 'FREND0 - 0x11',
'23' => 'FSCAL3 - 0xE9',
'24' => 'FSCAL2 - 0x2A',
'25' => 'FSCAL1 - 0x00',
'26' => 'FSCAL0 - 0x1F',
'27' => 'RCCTRL1 - 0x41',
'28' => 'RCCTRL0 - 0x00',
'29' => 'FSTEST - N/A ',
'2A' => 'PTEST - N/A ',
'2B' => 'AGCTEST - N/A ',
'2C' => 'TEST2 - N/A ',
'2D' => 'TEST1 - N/A ',
'2E' => 'TEST0 - N/A ',
);
## Supported Clients per default
my $clientsSIGNALduino = ':CUL_EM:'
.'CUL_FHTTK:'
.'CUL_TCM97001:'
.'CUL_TX:'
.'CUL_WS:'
.'Dooya:'
.'FHT:'
.'FLAMINGO:'
.'FS10:'
.'FS20:'
.' :' # Zeilenumbruch
.'Fernotron:'
.'Hideki:'
.'IT:'
.'KOPP_FC:'
.'LaCrosse:'
.'OREGON:'
.'PCA301:'
.'RFXX10REC:'
.'Revolt:'
.'SD_AS:'
.'SD_Rojaflex:'
.' :' # Zeilenumbruch
.'SD_BELL:'
.'SD_GT:'
.'SD_Keeloq:'
.'SD_RSL:'
.'SD_UT:'
.'SD_WS07:'
.'SD_WS09:'
.'SD_WS:'
.'SD_WS_Maverick:'
.'SOMFY:'
.' :' # Zeilenumbruch
.'Siro:'
.'SIGNALduino_un:'
;
## default regex match List for dispatching message to logical modules, can be updated during runtime because it is referenced
my %matchListSIGNALduino = (
'1:IT' => '^i......',
'2:CUL_TCM97001' => '^s[A-Fa-f0-9]+',
'3:SD_RSL' => '^P1#[A-Fa-f0-9]{8}',
'5:CUL_TX' => '^TX..........', # Need TX to avoid FHTTK
'6:SD_AS' => '^P2#[A-Fa-f0-9]{7,8}', # Arduino based Sensors, should not be default
'4:OREGON' => '^(3[8-9A-F]|[4-6][0-9A-F]|7[0-8]).*',
'7:Hideki' => '^P12#75[A-F0-9]+',
'9:CUL_FHTTK' => '^T[A-F0-9]{8}',
'10:SD_WS07' => '^P7#[A-Fa-f0-9]{6}[AFaf][A-Fa-f0-9]{2,3}',
'11:SD_WS09' => '^P9#F[A-Fa-f0-9]+',
'12:SD_WS' => '^W\d+x{0,1}#.*',
'13:RFXX10REC' => '^(20|29)[A-Fa-f0-9]+',
'14:Dooya' => '^P16#[A-Fa-f0-9]+',
'15:SOMFY' => '^Ys[0-9A-F]+',
'16:SD_WS_Maverick' => '^P47#[A-Fa-f0-9]+',
'17:SD_UT' => '^P(?:14|20|24|26|29|30|34|46|56|68|69|76|78|81|83|86|90|91|91.1|92|93|95|97|99|104|105|114)#.*', # universal - more devices with different protocols
'18:FLAMINGO' => '^P13\.?1?#[A-Fa-f0-9]+', # Flamingo Smoke
'19:CUL_WS' => '^K[A-Fa-f0-9]{5,}',
'20:Revolt' => '^r[A-Fa-f0-9]{22}',
'21:FS10' => '^P61#[A-F0-9]+',
'22:Siro' => '^P72#[A-Fa-f0-9]+',
'23:FHT' => '^81..(04|09|0d)..(0909a001|83098301|c409c401)..',
'24:FS20' => '^81..(04|0c)..0101a001',
'25:CUL_EM' => '^E0.................',
'26:Fernotron' => '^P82#.*',
'27:SD_BELL' => '^P(?:15|32|41|42|57|79|96|98|112)#.*',
'28:SD_Keeloq' => '^P(?:87|88)#.*',
'29:SD_GT' => '^P49#[A-Fa-f0-9]+',
'30:LaCrosse' => '^(\\S+\\s+9 |OK\\sWS\\s)',
'31:KOPP_FC' => '^kr\w{18,}',
'32:PCA301' => '^\\S+\\s+24',
'33:SD_Rojaflex' => '^P109#[A-Fa-f0-9]+',
'X:SIGNALduino_un' => '^[u]\d+#.*',
);
my %symbol_map = (one => 1 , zero =>0 ,sync => '', float=> 'F', 'start' => '');
## rfmode for attrib & supported rfmodes
my @rfmode;
my $Protocols = new lib::SD_Protocols();
############################# package main
sub SIGNALduino_Initialize {
my ($hash) = @_;
my $dev = '';
$dev = ',1' if (index(SDUINO_VERSION, 'dev') >= 0);
$Protocols->registerLogCallback(SIGNALduino_createLogCallback($hash));
my $error = $Protocols->LoadHash(qq[$attr{global}{modpath}/FHEM/lib/SD_ProtocolData.pm]);
if (defined($error)) {
Log3 'SIGNALduino', 1, qq[Error loading Protocol Hash. Module is in inoperable mode error message:($error)];
} else {
$hash->{protocolObject} = $Protocols;
@rfmode = ('SlowRF');
push @rfmode, map { $Protocols->checkProperty($_, 'rfmode') } $Protocols->getKeys('rfmode');
@rfmode = sort @rfmode;
Log3 'SIGNALduino', 4, qq[SIGNALduino_Initialize: rfmode list: @rfmode];
}
$hash->{DefFn} = \&SIGNALduino_Define;
$hash->{UndefFn} = \&SIGNALduino_Undef;
# Provider
$hash->{ReadFn} = \&SIGNALduino_Read;
$hash->{WriteFn} = \&SIGNALduino_Write;
$hash->{ReadyFn} = \&SIGNALduino_Ready;
# Normal devices
$hash->{FingerprintFn} = \&SIGNALduino_FingerprintFn;
$hash->{GetFn} = \&SIGNALduino_Get;
$hash->{SetFn} = \&SIGNALduino_Set;
$hash->{AttrFn} = \&SIGNALduino_Attr;
$hash->{AttrList} =
'Clients MatchList do_not_notify:1,0 dummy:1,0'
.' WS09_CRCAUS:0,1,2'
.' addvaltrigger'
.' blacklist_IDs'
.' cc1101_frequency'
.' cc1101_reg_user'
." debug:0$dev"
." development:0$dev"
.' doubleMsgCheck_IDs'
.' eventlogging:0,1'
.' flashCommand'
.' hardware:ESP32,ESP32cc1101,ESP8266,ESP8266cc1101,MAPLEMINI_F103CB,MAPLEMINI_F103CBcc1101,nano328,nanoCC1101,miniculCC1101,promini,radinoCC1101'
.' hexFile'
.' initCommands'
.' longids'
.' maxMuMsgRepeat'
.' minsecs'
.' noMsgVerbose:0,1,2,3,4,5'
.' rawmsgEvent:1,0'
.' rfmode:'.join(',', @rfmode)
.' suppressDeviceRawmsg:1,0'
.' updateChannelFW:stable,testing'
.' whitelist_IDs'
." $readingFnAttributes";
$hash->{ShutdownFn} = 'SIGNALduino_Shutdown';
$hash->{FW_detailFn} = 'SIGNALduino_FW_Detail';
$hash->{FW_deviceOverview} = 1;
$hash->{msIdList} = ();
$hash->{muIdList} = ();
$hash->{mcIdList} = ();
$hash->{mnIdList} = ();
#our $attr;
}
#
# Predeclare Variables from other modules may be loaded later from fhem
#
our $FW_wname;
our $FW_ME;
our $FW_CSRF;
our $FW_detail;
############################# package main, test exists
sub SIGNALduino_FingerprintFn {
my ($name, $msg) = @_;
# Das FingerprintFn() darf nur im physikalischen oder logischem Modul aktiv sein.
# Wenn FingerprintFn in beiden aktiv ist, funktioniert der Dispatch nicht richtig.
# Da FingerprintFn bei den LaCrosse Modulen verwendet wird, darf es im 00_Signalduino Modul nicht aktiv sein.
return if (substr($msg,0,2) eq 'OK');
# Store only the "relevant" part, as the Signalduino won't compute the checksum
#$msg = substr($msg, 8) if($msg =~ m/^81/ && length($msg) > 8);
return ('', $msg);
}
############################# package main
sub SIGNALduino_Define {
my ($hash, $def) = @_;
my @a =split m{\s+}xms, $def;
if(@a != 3) {
my $msg = 'Define, wrong syntax: define
Sorry, flashing your $hardware is currently not supported.
The file is only downloaded in /opt/fhem/FHEM/firmware.')", '');
}
return "Sorry, Flashing your $hardware via Module is currently not supported."; # processed in tests
}
}
############################# package main
sub SIGNALduino_Set_reset
{
my $hash = shift;
delete($hash->{initResetFlag}) if defined($hash->{initResetFlag});
return SIGNALduino_ResetDevice($hash);
}
############################# package main
sub SIGNALduino_Attr_rfmode {
my $hash = shift // carp 'must be called with hash of iodevice as first param';
my $aVal = shift // return;
if ( (InternalVal($hash->{NAME},"cc1101_available",0) == 0) && (!IsDummy($hash->{NAME})) ) {
return 'ERROR: This attribute is only available for a receiver with CC1101.';
}
## DevState waitInit is on first start after FHEM restart | initialized is after cc1101 available
if ( ($hash->{DevState} eq 'initialized') && (InternalVal($hash->{NAME},"cc1101_available",0) == 1) ) {
$hash->{logMethod}->($hash->{NAME}, 3, "$hash->{NAME}: Set_rfmode, set to $aVal on DevState $hash->{DevState} (please check activated protocols via 'Display protocollist')");
my $rfmode;
if ($aVal ne 'SlowRF') {
if ( scalar( @{$hash->{mnIdList}} ) >= 1 ) {
MNIDLIST:
for my $id (@{$hash->{mnIdList}}) {
$rfmode=$hash->{protocolObject}->checkProperty($id,'rfmode',-1);
if ($rfmode eq $aVal) {
$hash->{logMethod}->($hash->{NAME}, 4, qq[$hash->{NAME}: Set_rfmode, rfmode found on ID=$id]);
my $register=$hash->{protocolObject}->checkProperty($id,'register', -1);
if ($register != -1) {
$hash->{logMethod}->($hash->{NAME}, 5, qq[$hash->{NAME}: Set_rfmode, register settings exist on ID=$id ]);
for my $i (0...scalar(@{$register})-1) {
$hash->{logMethod}->($hash->{NAME}, 5, "$hash->{NAME}: Set_rfmode, write value " . @{$register}[$i]);
my $argcmd = sprintf("W%02X%s",hex(substr(@{$register}[$i],0,2)) + 2,substr(@{$register}[$i],2,2));
main::SIGNALduino_AddSendQueue($hash,$argcmd);
}
main::SIGNALduino_WriteInit($hash);
last MNIDLIST; # found $rfmode, exit loop
} else {
$hash->{logMethod}->($hash->{NAME}, 1, "$hash->{NAME}: Set_rfmode, set to $aVal (ID $id, no register entry found in protocols)");
}
}
};
## rfmode is always set if it is available / if the set supported is not available, it is always unequal
if ($rfmode ne $aVal) {
$hash->{logMethod}->($hash->{NAME}, 3, "$hash->{NAME}: Set_rfmode, set to $aVal rfmode value not found in protocols");
return 'ERROR: protocol '.$aVal.' is not activated in \'Display protocollist\'';
};
} else {
$hash->{logMethod}->($hash->{NAME}, 3, qq[$hash->{NAME}: Set_rfmode, no MN protocols in 'Display protocollist' activated]);
return 'ERROR: no MN protocols activated in \'Display protocollist\'';
}
} else {
SIGNALduino_AddSendQueue($hash,'e');
$hash->{logMethod}->($hash->{NAME}, 1, "$hash->{NAME}: Set_rfmode, set to $aVal (ASK/OOK mode load default register settings from uC)");
}
}
return;
}
############################# package main
sub SIGNALduino_Set_sendMsg {
my ($hash, @a) = @_;
$hash->{logMethod}->($hash->{NAME}, 5, "$hash->{NAME}: Set_sendMsg, msg=$a[1]");
return "Error: $hash->{NAME} does not exists" if (!IsDevice($hash->{NAME}));
# Split args in serval variables
my ($protocol,$data,$repeats,$clock,$frequency,$datalength,$dataishex);
my $n=0;
for my $s (split '#', $a[1]) {
my $c = substr($s,0,1);
if ($n == 0 ) { # protocol
$protocol = substr($s,1);
} elsif ($n == 1) { # Data
$data = $s;
if ( substr($s,0,2) eq '0x' ) { $dataishex=1; $data=substr($data,2); }
else { $dataishex=0; }
} else {
if ($c eq 'R') { $repeats = substr($s,1); }
elsif ($c eq 'C') { $clock = substr($s,1); }
elsif ($c eq 'F' && InternalVal($hash->{NAME},'cc1101_available',0)) { $frequency = substr($s,1); }
elsif ($c eq 'L') { $datalength = substr($s,1); }
}
$n++;
};
return "$hash->{NAME}: sendmsg, unknown protocol: $protocol" if (!$hash->{protocolObject}->protocolExists($protocol));
$repeats //= 1 ;
if (InternalVal($hash->{NAME},'cc1101_available',0))
{
my $f=$hash->{protocolObject}->getProperty($protocol,'frequency');
if ( defined $f ) {
$frequency = q[F=].$hash->{protocolObject}->getProperty($protocol,'frequency'). q[;]
}
}
$frequency //= q{};
my %signalHash;
my %patternHash;
my $pattern='';
my $cnt=0;
my $sendData;
## modulation ASK/OOK - MC
if (defined($hash->{protocolObject}->getProperty($protocol,'format')) && $hash->{protocolObject}->getProperty($protocol,'format') eq 'manchester')
{
$clock += $_ for( @{$hash->{protocolObject}->getProperty($protocol,'clockrange')} );
$clock = round($clock/2,0);
my $intro;
my $outro;
$intro = $hash->{protocolObject}->checkProperty($protocol,'msgIntro','');
$outro = sprintf('%s',$hash->{protocolObject}->checkProperty($protocol,'msgOutro',''));
if ($intro ne '' || $outro ne '')
{
$intro = qq[SC;R=$repeats;] . $intro;
$repeats = 0;
}
$sendData = $intro . 'SM;' . ($repeats > 0 ? "R=$repeats;" : '') . "C=$clock;D=$data;" . $outro . $frequency; # SM;R=2;C=400;D=AFAFAF;
$hash->{logMethod}->($hash->{NAME}, 5, "$hash->{NAME}: Set_sendMsg, Preparing manchester protocol=$protocol, repeats=$repeats, clock=$clock data=$data");
## modulation xFSK
} elsif (defined($hash->{protocolObject}->getProperty($protocol,'register')) && defined($hash->{protocolObject}->getProperty($protocol,'rfmode'))) {
$hash->{logMethod}->($hash->{NAME}, 5, "$hash->{NAME}: Set_sendMsg, Preparing ".$hash->{protocolObject}->getProperty($protocol,'rfmode')." protocol=$protocol, repeats=$repeats,data=$data");
$sendData = 'SN;' . ($repeats > 0 ? "R=$repeats;" : '') . "D=$data;" # SN;R=1;D=08C11484498ABCDE;
## modulation ASK/OOK - MS MU
} else {
if ($protocol == 3 || substr($data,0,2) eq 'is') {
if (substr($data,0,2) eq 'is') {
$data = substr($data,2); # is am Anfang entfernen
}
$data = $hash->{protocolObject}->ConvITV1_tristateToBit($data);
$hash->{logMethod}->($hash->{NAME}, 5, "$hash->{NAME}: Set_sendMsg, IT V1 convertet tristate to bits=$data");
}
if (!defined $clock ) {
$hash->{ITClock} = 250 if (!defined $hash->{ITClock} ); # Todo: Klaeren wo ITClock verwendet wird und ob wir diesen Teil nicht auf Protokoll 3,4 und 17 minimieren
$clock= $hash->{protocolObject}->checkProperty($protocol,'clockabs',0) > 1
? $hash->{protocolObject}->getProperty($protocol,'clockabs')
: $hash->{ITClock};
}
if ($dataishex == 1)
{
# convert hex to bits
my $hlen = length($data);
my $blen = $hlen * 4;
$data = unpack("B$blen", pack("H$hlen", $data));
}
$hash->{logMethod}->($hash->{NAME}, 5, "$hash->{NAME}: Set_sendMsg, Preparing rawsend command for protocol=$protocol, repeats=$repeats, clock=$clock bits=$data");
for my $item (qw(preSync sync start one zero float pause end universal))
{
my $value = $hash->{protocolObject}->getProperty($protocol,$item);
next if (!defined $value );
for my $p ( @{$value} )
{
if (!exists($patternHash{$p}))
{
$patternHash{$p}=$cnt;
$pattern.='P'.$patternHash{$p}.'='. int($p*$clock) .';';
$cnt++;
}
$signalHash{$item}.=$patternHash{$p};
}
}
my @bits = split('', $data);
my %bitconv = (1=>'one', 0=>'zero', 'D'=> 'float', 'F'=> 'float', 'P'=> 'pause', 'U'=> 'universal');
my $SignalData='D=';
$SignalData.=$signalHash{preSync} if (exists($signalHash{preSync}));
$SignalData.=$signalHash{sync} if (exists($signalHash{sync}));
$SignalData.=$signalHash{start} if (exists($signalHash{start}));
foreach my $bit (@bits)
{
next if (!exists($bitconv{$bit}));
$SignalData.=$signalHash{$bitconv{$bit}}; ## Add the signal to our data string
}
$SignalData.=$signalHash{end} if (exists($signalHash{end}));
$sendData = "SR;R=$repeats;$pattern$SignalData;$frequency";
}
SIGNALduino_AddSendQueue($hash,$sendData);
$hash->{logMethod}->($hash->{NAME}, 4, "$hash->{NAME}: Set_sendMsg, sending : $sendData");
}
############################# package main
sub SIGNALduino_Set_close {
my $hash = shift;
$hash->{DevState} = 'closed';
return SIGNALduino_CloseDevice($hash);
}
############################# package main
sub SIGNALduino_Set_MessageType {
my ($hash, @a) = @_;
my $argm;
if ($a[0] =~ /^enable/) {
$argm = 'CE' . substr($a[1],-1,1);
} else {
$argm = 'CD' . substr($a[1],-1,1);
}
SIGNALduino_AddSendQueue($hash,$argm);
SIGNALduino_Get_Command($hash,'config');
$hash->{logMethod}->($hash->{NAME}, 4, "$hash->{NAME}: Set_MessageType, $a[0] $a[1] $argm");
}
############################# package main
sub SIGNALduino_Set_bWidth {
my ($hash, @a) = @_;
if (exists($hash->{ucCmd}->{cmd}) && $hash->{ucCmd}->{cmd} eq 'set_bWidth' && $a[0] =~ /^C10\s=\s([A-Fa-f0-9]{2})$/ )
{
my ($ob,$bw) = cc1101::CalcbWidthReg($hash,$1,$hash->{ucCmd}->{arg});
$hash->{logMethod}->($hash->{NAME}, 3, "$hash->{NAME}: Set_bWidth, bWidth: Setting MDMCFG4 (10) to $ob = $bw KHz");
# Toddo setRegisters verwenden
main::SIGNALduino_AddSendQueue($hash,"W12$ob");
main::SIGNALduino_WriteInit($hash);
return ("Setting MDMCFG4 (10) to $ob = $bw KHz" ,undef);
} else {
$hash->{logMethod}->($hash->{NAME}, 3, "$hash->{NAME}: Set_bWidth, Request register 10");
# Get Register 10
cc1101::GetRegister($hash,10);
$hash->{ucCmd}->{cmd} = 'set_bWidth';
$hash->{ucCmd}->{arg} = $a[1]; # Zielbandbreite
$hash->{ucCmd}->{responseSub} = \&SIGNALduino_Set_bWidth; # Callback auf sich selbst setzen
$hash->{ucCmd}->{asyncOut} = $hash->{CL} if (defined($hash->{CL}));
$hash->{ucCmd}->{timenow} = time();
#return 'Register 10 requested';
return ;
}
}
############################# package main
# LaCrosse sensor is comfortable to put on (own way from 36_LaCrosse.pm)
sub SIGNALduino_Set_LaCrossePairForSec {
my ($hash, @a) = @_;
# set NAME a[0] a[1] a[2]
return "Usage: set $hash->{NAME} $a[0] sudo apt-get install libjson-perl
";
}
my $channel=AttrVal($hash->{NAME},'updateChannelFW','stable');
my $hardware=AttrVal($hash->{NAME},'hardware',undef);
my ($validHw) = $modules{$hash->{TYPE}}{AttrList} =~ /.*hardware:(.*?)\s/;
$hash->{logMethod}->($hash->{NAME}, 1, "$hash->{NAME}: found availableFirmware for $validHw");
if (!defined($hardware) || $validHw !~ /$hardware(?:,|$)/ )
{
$hash->{logMethod}->($hash->{NAME}, 1, "$hash->{NAME}: get $a[0] failed. Please set attribute hardware first");
return "$a[0]: \n\n$hash->{NAME}: get $a[0] failed. Please choose one of $validHw attribute hardware";
}
SIGNALduino_querygithubreleases($hash);
return "$a[0]: \n\nFetching $channel firmware versions for $hardware from github\n";
}
############################# package main
sub SIGNALduino_Get_Command {
my ($hash, @a) = @_;
my $name=$hash->{NAME};
return 'Unsupported command for the microcontroller' if (!exists(${$gets{$a[0]}}[2]));
$hash->{logMethod}->($name, 5, "$name: Get_Command $a[0] executed");
SIGNALduino_AddSendQueue($hash, @{$gets{$a[0]}}[2] . (exists($a[1]) ? "$a[1]" : ''));
$hash->{ucCmd}->{cmd}=$a[0];
$hash->{ucCmd}->{responseSub}=$gets{$a[0]}[3];
$hash->{ucCmd}->{asyncOut}=$hash->{CL} if (defined($hash->{CL}));
$hash->{ucCmd}->{timenow}=time();
return ;
}
############################# package main
sub SIGNALduino_Get_Command_CCReg {
my ($hash, @a) = @_;
return 'not enough number of arguments' if $#a < 1;
return 'Wrong command provided' if $a[0] ne 'ccreg';
my $name=$hash->{NAME};
if (exists($cc1101_register{uc($a[1])}) || $a[1] eq '99' || $a[1] =~ /^3[0-9a-dA-D]$/ ) {
return SIGNALduino_Get_Command(@_);
} else {
return "unknown Register $a[1], please choose a valid cc1101 register";
}
}
############################# package main
sub SIGNALduino_Get_RawMsg {
my ($hash, @a) = @_;
return "\"get raw\" needs at least a parameter" if (@a < 2);
if ($a[1] =~ /^M[CcSUN];.+/)
{
$a[1]="\002$a[1]\003"; ## Add start end end marker if not already there
$hash->{logMethod}->($hash->{NAME}, 5, "$hash->{NAME}: msg adding start and endmarker to message");
}
if ($a[1] =~ /\002M\w;.+;\003$/)
{
$hash->{logMethod}->( $hash->{NAME}, 4, "$hash->{NAME}: get rawmsg: $a[1]");
my $cnt = SIGNALduino_Parse($hash, $hash, $hash->{NAME}, $a[1]);
if (defined $cnt) {
return "Parse raw msg, number of messages passed to modules: $cnt";
} else {
return "Parse raw msg, no suitable protocol recognized.";
}
} else {
return 'This command is not supported via get rawmsg.';
}
}
############################# package main, test exists
sub SIGNALduino_GetResponseUpdateReading {
return ($_[1],1);
}
############################# package main
sub SIGNALduino_Get_delayed {
my(undef,$name,@cmds) = split(':', shift);
my $hash = $defs{$name};
if ( exists($hash->{ucCmd}) && !exists($hash->{ucCmd}->{timenow}) ) {
$hash->{ucCmd}->{timenow}=time();
Log3 ($hash->{NAME}, 5, "$name: Get_delayed, timenow was missing, set ".$hash->{ucCmd}->{timenow});
}
if (exists($hash->{ucCmd}) && $hash->{ucCmd}->{timenow}+10 > time() ) {
$hash->{logMethod}->($hash->{NAME}, 5, "$name: Get_delayed, ".join(' ',@cmds).' delayed');
main::InternalTimer(main::gettimeofday() + main::SDUINO_GET_CONFIGQUERY_DELAY, \&SIGNALduino_Get_delayed, "SIGNALduino_Get_delayed:$name:".join(' ',@cmds), 0);
} else {
delete($hash->{ucCmd});
$hash->{logMethod}->($hash->{NAME}, 5, "$name: Get_delayed, ".join(' ',@cmds).' executed');
RemoveInternalTimer("SIGNALduino_Get_delayed:$name:".join(' ',@cmds));
SIGNALduino_Get($hash,$name,$cmds[0]);
}
}
############################# package main, test exists
sub SIGNALduino_CheckUptimeResponse {
my $msg = sprintf("%d %02d:%02d:%02d", $_[1]/86400, ($_[1]%86400)/3600, ($_[1]%3600)/60, $_[1]%60);
#readingsSingleUpdate($_[0], $_[0]->{ucCmd}->{cmd}, $msg, 0);
return ($msg,0);
}
############################# package main, test exists
sub SIGNALduino_CheckCmdsResponse {
my $hash = shift;
my $msg = shift;
my $name=$hash->{NAME};
$msg =~ s/$name cmds =>//g;
$msg =~ s/.*Use one of//g;
return ($msg,0);
}
############################# package main, test exists
sub SIGNALduino_CheckccConfResponse {
my (undef,$str) = split('=', $_[1]);
my $var;
# https://github.com/RFD-FHEM/RFFHEM/issues/1015 | value can arise due to an incorrect transmission from serial
# $str = "216%E857C43023B900070018146C040091";
return ('invalid value from uC. Only hexadecimal values are allowed. Please query again.',undef) if($str !~ /^[A-F0-9a-f]+$/);
my %r = ( '0D'=>1,'0E'=>1,'0F'=>1,'10'=>1,'11'=>1,'12'=>1,'1B'=>1,'1D'=>1, '15'=>1);
foreach my $a (sort keys %r) {
$var = substr($str,(hex($a)-13)*2, 2);
$r{$a} = hex($var);
}
my $msg = sprintf("Freq: %.3f MHz, Bandwidth: %d kHz, rAmpl: %d dB, sens: %d dB, DataRate: %.2f kBaud",
26*(($r{"0D"}*256+$r{"0E"})*256+$r{"0F"})/65536, #Freq | Register 0x0D,0x0E,0x0F
26000/(8 * (4+(($r{"10"}>>4)&3)) * (1 << (($r{"10"}>>6)&3))), #Bw | Register 0x10
$ampllist[$r{"1B"}&7], #rAmpl | Register 0x1B
4+4*($r{"1D"}&3), #Sens | Register 0x1D
(((256+$r{"11"})*(2**($r{"10"} & 15 )))*26000000/(2**28) / 1000) #DataRate | Register 0x10,0x11
);
my $msg2 = sprintf("Modulation: %s",
$modformat[$r{"12"}>>4], #Modulation | Register 0x12
);
if ($msg2 !~ /Modulation:\sASK\/OOK/) {
$msg2 .= ", Syncmod: ".$syncmod[($r{"12"})&7]; #Syncmod | Register 0x12
$msg2 .= ", Deviation: ".round((8+($r{"15"}&7))*(2**(($r{"15"}>>4)&7)) *26000/(2**17),2) .' kHz'; #Deviation | Register 0x15
}
readingsBeginUpdate($_[0]);
readingsBulkUpdate($_[0], 'cc1101_config', $msg);
readingsBulkUpdate($_[0], 'cc1101_config_ext', $msg2);
readingsEndUpdate($_[0], 1);
return ($msg.', '.$msg2,undef);
}
############################# package main, test exists
sub SIGNALduino_CheckccPatableResponse {
my $hash = shift;
my $msg = shift;
my $name=$hash->{NAME};
my $CC1101Frequency=AttrVal($name,'cc1101_frequency',433);
$CC1101Frequency = 433 if ($CC1101Frequency >= 433 && $CC1101Frequency <= 435);
$CC1101Frequency = 868 if ($CC1101Frequency >= 863 && $CC1101Frequency <= 870);
my $dBn = substr($msg,9,2);
$hash->{logMethod}->($name, 3, "$name: CheckCcpatableResponse, patable: $dBn");
foreach my $dB (keys %{ $patable{$CC1101Frequency} }) {
if ($dBn eq $patable{$CC1101Frequency}{$dB}) {
$hash->{logMethod}->($name, 5, "$name: CheckCcpatableResponse, patable: $dB");
$msg .= " => $dB";
last;
}
}
readingsSingleUpdate($hash, 'cc1101_patable', $msg,1);
return ($msg,undef);
}
############################# package main, test exists
sub SIGNALduino_CheckCcregResponse {
my $hash = shift;
my $msg = shift;
my $name=$hash->{NAME};
$hash->{logMethod}->($name, 5, "$name: CheckCcregResponse, msg $msg");
if ($msg =~ /^ccreg/) {
my $msg1 = $msg;
$msg =~ s/\s\s/\n/g;
$msg = "\nConfiguration register overview:\n---------------------------------------------------------\n" . $msg;
$msg.= "\n\nConfiguration register detail:\n---------------------------------------------------------\nadd. name def. cur.\n";
$msg1 =~ s/ccreg\s\d0:\s//g;
$msg1 =~ s/\s\s/ /g;
my @ccreg = split(/\s/,$msg1);
my $reg_idx = 0;
foreach my $key (sort keys %cc1101_register) {
$msg.= '0x'.$key.' '.$cc1101_register{$key}. ' - 0x'.$ccreg[$reg_idx]."\n";
$reg_idx++;
}
} else {
$msg =~ /^C([A-Fa-f0-9]{2}) = ([A-Fa-f0-9]{2})$/;
my $reg = $1;
my $val = $2;
if ( $reg =~ /^3[0-9a-dA-D]$/ ) { # Status register
$msg = "\nStatus register detail:\n---------------------------\nadd. name cur.\n";
$msg .= "0x$reg $cc1101::cc1101_status_register{$reg} - 0x$val";
if ( $reg eq '31' && exists $cc1101::cc1101_version{$val}) { # VERSION – Chip ID
$msg .= " Chip $cc1101::cc1101_version{$val}";
}
} else { # Configuration Register
$msg = "\nConfiguration register detail:\n------------------------------\nadd. name def. cur.\n";
$msg .= "0x$reg $cc1101_register{$reg} - 0x$val";
}
$msg .= "\n";
}
return ("\n".$msg,undef);
}
############################# package main
### Unused ??? ### in use
sub SIGNALduino_CheckSendRawResponse {
my $hash = shift;
my $msg = shift;
if ($msg =~ /^S[RCMN];/ )
{
my $name=$hash->{NAME};
# zu testen der sendeQueue, kann wenn es funktioniert auf verbose 5
$hash->{logMethod}->($name, 4, "$name: CheckSendrawResponse, sendraw answer: $msg");
delete($hash->{ucCmd});
if ($msg =~ /D=[A-Za-z0-9]+;/ )
{
RemoveInternalTimer("HandleWriteQueue:$name");
SIGNALduino_HandleWriteQueue("x:$name"); # Todo #823 on github
} else {
InternalTimer(gettimeofday() , \&SIGNALduino_HandleWriteQueue, "HandleWriteQueue:$name") if (scalar @{$hash->{QUEUE}} > 0 && InternalVal($name,'sendworking',0) == 0);
}
}
return (undef);
}
############################# package main
sub SIGNALduino_ResetDevice {
my $hash = shift;
my $name = $hash->{NAME};
if (!defined($hash->{helper}{resetInProgress})) {
my $hardware = AttrVal($name,'hardware','');
$hash->{logMethod}->($name, 3, "$name: ResetDevice, $hardware");
if (IsDummy($name)) { # for dummy device
$hash->{DevState} = 'initialized';
readingsSingleUpdate($hash, 'state', 'opened', 1);
return ;
}
DevIo_CloseDev($hash);
if ($hardware eq 'radinoCC1101' && $^O eq 'linux') {
# The reset is triggered when the Micro's virtual (CDC) serial / COM port is opened at 1200 baud and then closed.
# When this happens, the processor will reset, breaking the USB connection to the computer (meaning that the virtual serial / COM port will disappear).
# After the processor resets, the bootloader starts, remaining active for about 8 seconds.
# The bootloader can also be initiated by pressing the reset button on the Micro.
# Note that when the board first powers up, it will jump straight to the user sketch, if present, rather than initiating the bootloader.
my ($dev, $baudrate) = split("@", $hash->{DeviceName});
$hash->{logMethod}->($name, 3, "$name: ResetDevice, forcing special reset for $hardware on $dev");
# Mit dem Linux-Kommando 'stty' die Port-Einstellungen setzen
system("stty -F $dev ospeed 1200 ispeed 1200");
$hash->{helper}{resetInProgress}=1;
InternalTimer(gettimeofday()+10,\&SIGNALduino_ResetDevice,$hash);
$hash->{logMethod}->($name, 3, "$name: ResetDevice, reopen delayed for 10 second");
return ;
}
} else {
delete($hash->{helper}{resetInProgress});
}
DevIo_OpenDev($hash, 0, \&SIGNALduino_DoInit, \&SIGNALduino_Connect);
return ;
}
############################# package main
sub SIGNALduino_CloseDevice {
my ($hash) = @_;
$hash->{logMethod}->($hash->{NAME}, 2, "$hash->{NAME}: CloseDevice, closed");
RemoveInternalTimer($hash);
DevIo_CloseDev($hash);
readingsSingleUpdate($hash, 'state', 'closed', 1);
return ;
}
############################# package main
sub SIGNALduino_DoInit {
my $hash = shift;
my $name = $hash->{NAME};
my $err;
my $msg = undef;
my ($ver, $try) = ('', 0);
#Dirty hack to allow initialisation of DirectIO Device for some debugging and tesing
delete($hash->{disConnFlag}) if defined($hash->{disConnFlag});
RemoveInternalTimer("HandleWriteQueue:$name");
@{$hash->{QUEUE}} = ();
$hash->{sendworking} = 0;
if (($hash->{DEF} !~ m/\@directio/) and ($hash->{DEF} !~ m/none/) )
{
$hash->{logMethod}->($hash, 1, "$name: DoInit, ".$hash->{DEF});
$hash->{initretry} = 0;
RemoveInternalTimer($hash);
#SIGNALduino_SimpleWrite($hash, 'XQ'); # Disable receiver
InternalTimer(gettimeofday() + SDUINO_INIT_WAIT_XQ, \&SIGNALduino_SimpleWrite_XQ, $hash, 0);
InternalTimer(gettimeofday() + SDUINO_INIT_WAIT, \&SIGNALduino_StartInit, $hash, 0);
}
# Reset the counter
delete($hash->{XMIT_TIME});
delete($hash->{NR_CMD_LAST_H});
return;
}
############################# package main
# Disable receiver
sub SIGNALduino_SimpleWrite_XQ {
my ($hash) = @_;
my $name = $hash->{NAME};
$hash->{logMethod}->($hash, 3, "$name: SimpleWrite_XQ, disable receiver (XQ)");
SIGNALduino_SimpleWrite($hash, 'XQ');
#DevIo_SimpleWrite($hash, "XQ\n",2);
}
############################# package main, test exists
sub SIGNALduino_StartInit {
my ($hash) = @_;
my $name = $hash->{NAME};
$hash->{version} = undef;
$hash->{logMethod}->($name,3 , "$name: StartInit, get version, retry = " . $hash->{initretry});
if ($hash->{initretry} >= SDUINO_INIT_MAXRETRY) {
$hash->{DevState} = 'INACTIVE';
# einmaliger reset, wenn danach immer noch 'init retry count reached', dann SIGNALduino_CloseDevice()
if (!defined($hash->{initResetFlag})) {
$hash->{logMethod}->($name,2 , "$name: StartInit, retry count reached. Reset");
$hash->{initResetFlag} = 1;
SIGNALduino_ResetDevice($hash);
} else {
$hash->{logMethod}->($name,2 , "$name: StartInit, init retry count reached. Closed");
SIGNALduino_CloseDevice($hash);
}
return;
}
else {
$hash->{ucCmd}->{cmd} = 'version';
$hash->{ucCmd}->{responseSub} = \&SIGNALduino_CheckVersionResp;
$hash->{ucCmd}->{timenow} = time();
SIGNALduino_SimpleWrite($hash, 'V');
#DevIo_SimpleWrite($hash, "V\n",2);
$hash->{DevState} = 'waitInit';
RemoveInternalTimer($hash);
InternalTimer(gettimeofday() + SDUINO_CMD_TIMEOUT, \&SIGNALduino_CheckVersionResp, $hash, 0);
}
}
############################# package main, test exists
sub SIGNALduino_CheckVersionResp {
my ($hash,$msg) = @_;
my $name = $hash->{NAME};
### ToDo, manchmal kommen Mu Nachrichten in $msg und somit ist keine Version feststellbar !!!
if (defined($msg)) {
$hash->{logMethod}->($hash, 5, "$name: CheckVersionResp, called with $msg");
if ($msg =~ m/($gets{$hash->{ucCmd}->{cmd}}[4])/ ) {
$hash->{version} = $1;
} else {
delete $hash->{version};
}
} else {
$hash->{logMethod}->($hash, 5, "$name: CheckVersionResp, called without msg");
# Aufruf durch Timeout!
$msg='undef';
delete($hash->{ucCmd});
}
if (!defined($hash->{version}) ) {
$msg = "$name: CheckVersionResp, Not an SIGNALduino device, got for V: $msg";
$hash->{logMethod}->($hash, 1, $msg);
readingsSingleUpdate($hash, 'state', 'no SIGNALduino found', 1); #uncoverable statement because state is overwritten by SIGNALduino_CloseDevice
$hash->{initretry} ++;
SIGNALduino_StartInit($hash);
} elsif($hash->{version} =~ m/^V 3\.1\./) {
$msg = "$name: CheckVersionResp, Version of your arduino is not compatible, please flash new firmware. (device closed) Got for V: $msg";
readingsSingleUpdate($hash, 'state', 'unsupported firmware found', 1); #uncoverable statement because state is overwritten by SIGNALduino_CloseDevice
$hash->{logMethod}->($hash, 1, $msg);
$hash->{DevState} = 'INACTIVE';
SIGNALduino_CloseDevice($hash);
} else {
if (exists($hash->{DevState}) && $hash->{DevState} eq 'waitInit') {
RemoveInternalTimer($hash);
}
readingsSingleUpdate($hash, 'state', 'opened', 1);
$hash->{logMethod}->($name, 2, "$name: CheckVersionResp, initialized " . SDUINO_VERSION);
delete($hash->{initResetFlag}) if defined($hash->{initResetFlag});
SIGNALduino_SimpleWrite($hash, 'XE'); # Enable receiver
$hash->{logMethod}->($hash, 3, "$name: CheckVersionResp, enable receiver (XE) ");
delete($hash->{initretry});
# initialize keepalive
$hash->{keepalive}{ok} = 0;
$hash->{keepalive}{retry} = 0;
InternalTimer(gettimeofday() + SDUINO_KEEPALIVE_TIMEOUT, \&SIGNALduino_KeepAlive, $hash, 0);
if ($hash->{version} =~ m/cc1101/) {
$hash->{cc1101_available} = 1;
$hash->{logMethod}->($name, 5, "$name: CheckVersionResp, cc1101 available");
SIGNALduino_Get($hash, $name,'ccconf');
SIGNALduino_Get($hash, $name,'ccpatable');
} else {
# connect device without cc1101 to port where a device with cc1101 was previously connected (example DEF with /dev/ttyUSB0@57600) #
$hash->{logMethod}->($hash, 5, "$name: CheckVersionResp, delete old READINGS from cc1101 device");
if ( exists($hash->{cc1101_available}) ) {
delete($hash->{cc1101_available});
};
for my $readingName ( qw(cc1101_config cc1101_config_ext cc1101_patable) ) {
readingsDelete($hash,$readingName);
}
}
$hash->{DevState} = 'initialized';
$msg = $hash->{version};
}
return ($msg,undef);
}
############################# package main, test exists
# Todo: SUB kann entfernt werden
sub SIGNALduino_CheckCmdResp {
my ($hash) = @_;
my $name = $hash->{NAME};
my $msg = undef;
my $ver;
if ($hash->{version}) {
$ver = $hash->{version};
if ($ver !~ m/SIGNAL(duino|ESP)/) {
$msg = "$name: CheckCmdResp, Not an SIGNALduino device, setting attribute dummy=1 got for V: $ver";
$hash->{logMethod}->($hash, 1, $msg);
readingsSingleUpdate($hash, 'state', 'no SIGNALduino found', 1); #uncoverable statement because state is overwritten by SIGNALduino_CloseDevice
$hash->{DevState} = 'INACTIVE';
SIGNALduino_CloseDevice($hash);
}
elsif($ver =~ m/^V 3\.1\./) {
$msg = "$name: CheckCmdResp, Version of your arduino is not compatible, pleas flash new firmware. (device closed) Got for V: $ver";
readingsSingleUpdate($hash, 'state', 'unsupported firmware found', 1); #uncoverable statement because state is overwritten by SIGNALduino_CloseDevice
$hash->{logMethod}->($hash, 1, $msg);
$hash->{DevState} = 'INACTIVE';
SIGNALduino_CloseDevice($hash);
}
else {
readingsSingleUpdate($hash, 'state', 'opened', 1);
$hash->{logMethod}->($name, 2, "$name: CheckCmdResp, initialized " . SDUINO_VERSION);
$hash->{DevState} = 'initialized';
delete($hash->{initResetFlag}) if defined($hash->{initResetFlag});
SIGNALduino_SimpleWrite($hash, 'XE'); # Enable receiver
$hash->{logMethod}->($hash, 3, "$name: CheckCmdResp, enable receiver (XE) ");
delete($hash->{initretry});
# initialize keepalive
$hash->{keepalive}{ok} = 0;
$hash->{keepalive}{retry} = 0;
InternalTimer(gettimeofday() + SDUINO_KEEPALIVE_TIMEOUT, \&SIGNALduino_KeepAlive, $hash, 0);
$hash->{cc1101_available} = 1 if ($ver =~ m/cc1101/);
}
}
else {
delete($hash->{ucCmd});
$hash->{initretry} ++;
#InternalTimer(gettimeofday()+1, 'SIGNALduino_StartInit', $hash, 0);
SIGNALduino_StartInit($hash);
}
}
############################# package main
# Check if the 1% limit is reached and trigger notifies
sub SIGNALduino_XmitLimitCheck {
my ($hash,$fn) = @_;
return if ($fn !~ m/^(is|S[RCM]).*/);
my $now = time();
if(!$hash->{XMIT_TIME}) {
$hash->{XMIT_TIME}[0] = $now;
$hash->{NR_CMD_LAST_H} = 1;
return;
}
my $nowM1h = $now-3600;
my @b = grep { $_ > $nowM1h } @{$hash->{XMIT_TIME}};
if(@b > 163) { # Maximum nr of transmissions per hour (unconfirmed).
my $name = $hash->{NAME};
$hash->{logMethod}->($name, 2, "$name: XmitLimitCheck, TRANSMIT LIMIT EXCEEDED");
DoTrigger($name, 'TRANSMIT LIMIT EXCEEDED');
} else {
push(@b, $now);
}
$hash->{XMIT_TIME} = \@b;
$hash->{NR_CMD_LAST_H} = int(@b);
}
############################# package main
## API to logical modules: Provide as Hash of IO Device, type of function ; command to call ; message to send
sub SIGNALduino_Write {
my $hash = shift // carp 'must be called with hash of iodevice as first param';
my $fn = shift // 'RAW';
my $msg = shift // return;
my $name = $hash->{NAME};
if ($fn eq '') {
$fn='RAW' ;
} elsif($fn eq '04') {
my $id;
my $sum;
$fn='sendMsg';
if (substr($msg,0,6) eq '010101') { # FS20
$msg = substr($msg,6);
$id = 74;
$sum = 6;
} elsif(substr($msg,0,6) eq '020183') { # FHT
$msg = substr($msg,6,4) . substr($msg,10);
$id = 73;
$sum = 12;
}
$msg = $hash->{protocolObject}->PreparingSend_FS20_FHT($id, $sum, $msg);
} elsif($fn eq 'k') { # KOPP_FC (one part outsourcing in SD_Protocols.pm, main part here due to loop and set hash values)
$hash->{logMethod}->($name, 4, "$name: Write, cmd $fn sending KOPP_FC");
$fn='raw';
my $Keycode = substr($msg,1,2);
my $TransCode1 = substr($msg,3,4);
my $TransCode2 = substr($msg,7,2);
### The device to be sent stores something in own hash. Search for names to access them ###
#### The variant with devspec2array does not require any adjustment in the original Kopp module. ####
my @Liste = devspec2array("TYPE=KOPP_FC:FILTER=TRANSMITTERCODE1=$TransCode1:FILTER=TRANSMITTERCODE2=$TransCode2:FILTER=KEYCODE=$Keycode");
my $KOPPname = $Liste[0];
if (scalar @Liste != 1) {
$hash->{logMethod}->($name, 4, "$name: Write, PreparingSend KOPP_FC found ". scalar @Liste ." device\'s with same DEF (SIGNALduino used $KOPPname)");
} else {
$hash->{logMethod}->($name, 5, "$name: Write, PreparingSend KOPP_FC found device with name $KOPPname");
}
## Internals blkctr initialize if not available
if (!exists($defs{$KOPPname}->{blkctr})) {
$defs{$KOPPname}->{blkctr} = 0;
$hash->{logMethod}->($name, 5, "$name: Write, PreparingSend KOPP_FC set Internals blkctr on device $KOPPname to 0");
}
$msg = $hash->{protocolObject}->PreparingSend_KOPP_FC(sprintf("%02x",$defs{$KOPPname}->{blkctr}),$Keycode,$TransCode1,$TransCode2);
if (!defined $msg) {
return;
};
$defs{$KOPPname}->{blkctr}++; # Internals blkctr increases with each send
$hash->{logMethod}->($name, 5, "$name: Write, PreparingSend KOPP_FC set Internals blkctr on device $KOPPname to ".$defs{$KOPPname}->{blkctr});
}
$hash->{logMethod}->($name, 5, "$name: Write, sending via Set $fn $msg");
SIGNALduino_Set($hash,$name,$fn,$msg);
}
############################# package main
sub SIGNALduino_AddSendQueue {
my ($hash, $msg) = @_;
my $name = $hash->{NAME};
push(@{$hash->{QUEUE}}, $msg);
#SIGNALduino_Log3 $hash , 5, Dumper($hash->{QUEUE});
$hash->{logMethod}->($hash, 5,"$name: AddSendQueue, " . $hash->{NAME} . ": $msg (" . @{$hash->{QUEUE}} . ')');
InternalTimer(gettimeofday(), \&SIGNALduino_HandleWriteQueue, "HandleWriteQueue:$name") if (scalar @{$hash->{QUEUE}} == 1 && InternalVal($name,'sendworking',0) == 0);
}
############################# package main, test exists
sub SIGNALduino_SendFromQueue {
my ($hash, $msg) = @_;
my $name = $hash->{NAME};
$hash->{logMethod}->($name, 4, "$name: SendFromQueue, called");
if($msg ne '') {
SIGNALduino_XmitLimitCheck($hash,$msg);
#DevIo_SimpleWrite($hash, $msg . "\n", 2);
$hash->{sendworking} = 1;
SIGNALduino_SimpleWrite($hash,$msg);
if ($msg =~ m/^S[RCMN];/) {
$hash->{ucCmd}->{cmd} = 'sendraw';
$hash->{ucCmd}->{timenow} = time();
$hash->{ucCmd}->{responseSub} = \&SIGNALduino_CheckSendRawResponse;
$hash->{logMethod}->($name, 4, "$name: SendFromQueue, msg=$msg"); # zu testen der Queue, kann wenn es funktioniert auskommentiert werden
} elsif ($msg =~ "^e") { # Werkseinstellungen
SIGNALduino_Get($hash,$name,'ccconf');
SIGNALduino_Get($hash,$name,'ccpatable');
## set rfmode to default from uC
my $rfmode = AttrVal($name, 'rfmode', undef);
CommandAttr($hash,"$name rfmode SlowRF") if (defined $rfmode && $rfmode ne 'SlowRF'); # option with save question mark
} elsif ($msg =~ "^W(?:0F|10|11|1D|12|17|1F)") { # SetFreq, setrAmpl, Set_bWidth, SetDeviatn, SetSens
SIGNALduino_Get($hash,$name,'ccconf');
} elsif ($msg =~ "^x") { # patable
SIGNALduino_Get($hash,$name,'ccpatable');
}
# elsif ($msg eq 'C99') {
# $hash->{ucCmd}->{cmd} = 'ccregAll';
# $hash->{ucCmd}->{responseSub} = \&SIGNALduino_CheckCcregResponse;
#
# }
}
##############
# Write the next buffer not earlier than 0.23 seconds
# else it will be sent too early by the SIGNALduino, resulting in a collision, or may the last command is not finished
if (defined($hash->{ucCmd}->{cmd}) && $hash->{ucCmd}->{cmd} eq 'sendraw') {
InternalTimer(gettimeofday() + SDUINO_WRITEQUEUE_TIMEOUT, \&SIGNALduino_HandleWriteQueue, "HandleWriteQueue:$name");
} else {
InternalTimer(gettimeofday() + SDUINO_WRITEQUEUE_NEXT, \&SIGNALduino_HandleWriteQueue, "HandleWriteQueue:$name");
}
}
############################# package main
sub SIGNALduino_HandleWriteQueue {
my($param) = @_;
my(undef,$name) = split(':', $param);
my $hash = $defs{$name};
#my @arr = @{$hash->{QUEUE}};
$hash->{logMethod}->($name, 4, "$name: HandleWriteQueue, called");
$hash->{sendworking} = 0; # es wurde gesendet
if (exists($hash->{ucCmd}) && exists($hash->{ucCmd}->{cmd}) && $hash->{ucCmd}->{cmd} eq 'sendraw') {
$hash->{logMethod}->($name, 4, "$name: HandleWriteQueue, sendraw no answer (timeout)");
delete($hash->{ucCmd});
}
if(exists($hash->{QUEUE}) && @{$hash->{QUEUE}}) {
my $msg= shift(@{$hash->{QUEUE}});
if($msg eq '') {
SIGNALduino_HandleWriteQueue("x:$name");
} else {
SIGNALduino_SendFromQueue($hash, $msg);
}
} else {
$hash->{logMethod}->($name, 4, "$name: HandleWriteQueue, nothing to send, stopping timer");
RemoveInternalTimer("HandleWriteQueue:$name");
}
}
############################# package main, test exists
# called from the global loop, when the select for hash->{FD} reports data
sub SIGNALduino_Read {
my ($hash) = @_;
my $buf = DevIo_SimpleRead($hash);
return '' if(!defined($buf));
my $name = $hash->{NAME};
my $debug = AttrVal($name,'debug',0);
my $SIGNALduinodata = $hash->{PARTIAL};
$hash->{logMethod}->($name, 5, "$name: Read, RAW: $SIGNALduinodata/$buf") if ($debug);
$SIGNALduinodata .= $buf;
while($SIGNALduinodata =~ m/\n/) {
my $rmsg;
($rmsg,$SIGNALduinodata) = split("\n", $SIGNALduinodata, 2);
$rmsg =~ s/\r//;
if ($rmsg =~ m/^\002(M(s|u|o);.*;)\003/) {
$rmsg =~ s/^\002//; # \002 am Anfang entfernen
my @msg_parts = split(';',$rmsg);
my $m0;
my $mnr0;
my $m1;
my $mL;
my $mH;
my $part = '';
my $partD;
$hash->{logMethod}->($name, 5, "$name: Read, RAW rmsg: $rmsg");
foreach my $msgPart (@msg_parts) {
next if ($msgPart eq '');
$m0 = substr($msgPart,0,1);
$mnr0 = ord($m0);
$m1 = substr($msgPart,1);
if ($m0 eq 'M') {
$part .= 'M' . uc($m1) . ';';
}
elsif ($mnr0 > 127) {
$part .= 'P' . sprintf("%u", ($mnr0 & 7)) . '=';
if (length($m1) == 2) {
$mL = ord(substr($m1,0,1)) & 127; # Pattern low
$mH = ord(substr($m1,1,1)) & 127; # Pattern high
if (($mnr0 & 0b00100000) != 0) { # Vorzeichen 0b00100000 = 32
$part .= '-';
}
if ($mnr0 & 0b00010000) { # Bit 7 von Pattern low
$mL += 128;
}
$part .= ($mH * 256) + $mL;
}
$part .= ';';
}
elsif (($m0 eq 'D' || $m0 eq 'd') && length($m1) > 0) {
my @arrayD = split(//, $m1);
$part .= 'D=';
$partD = '';
foreach my $D (@arrayD) {
$mH = ord($D) >> 4;
$mL = ord($D) & 7;
$partD .= "$mH$mL";
}
#SIGNALduino_Log3 $name, 3, "$name: Read, msg READredu1$m0: $partD";
if ($m0 eq 'd') {
$partD =~ s/.$//; # letzte Ziffer entfernen wenn Anzahl der Ziffern ungerade
}
$partD =~ s/^8//; # 8 am Anfang entfernen
#SIGNALduino_Log3 $name, 3, "$name: Read, msg READredu2$m0: $partD";
$part = $part . $partD . ';';
}
elsif (($m0 eq 'C' || $m0 eq 'S') && length($m1) == 1) {
$part .= "$m0" . "P=$m1;";
}
elsif ($m0 eq 'o' || $m0 eq 'm') {
$part .= "$m0$m1;";
}
elsif ($m1 =~ m/^[0-9A-Z]{1,2}$/) { # bei 1 oder 2 Hex Ziffern nach Dez wandeln
$part .= "$m0=" . hex($m1) . ';';
}
elsif ($m0 =~m/[0-9a-zA-Z]/) {
$part .= "$m0";
if ($m1 ne '') {
$part .= "=$m1";
}
$part .= ';';
}
}
$hash->{logMethod}->($name, 4, "$name: Read, msg READredu: $part");
$rmsg = "\002$part\003";
}
else {
$hash->{logMethod}->($name, 4, "$name: Read, msg: $rmsg");
}
if ( $rmsg && !SIGNALduino_Parse($hash, $hash, $name, $rmsg) && exists($hash->{ucCmd}) && defined($hash->{ucCmd}->{cmd}))
{
my $regexp = exists($gets{$hash->{ucCmd}->{cmd}}) && exists($gets{$hash->{ucCmd}->{cmd}}[4]) ? $gets{$hash->{ucCmd}->{cmd}}[4] : ".*";
if (exists($hash->{ucCmd}->{responseSub}) && ref $hash->{ucCmd}->{responseSub} eq 'CODE') {
$hash->{logMethod}->($name, 5, "$name: Read, msg: regexp=$regexp cmd=$hash->{ucCmd}->{cmd} msg=$rmsg");
my $returnMessage ;
my $event;
if (!exists($gets{$hash->{ucCmd}->{cmd}}) || !exists($gets{$hash->{ucCmd}->{cmd}}[4]) || $rmsg =~ /$regexp/)
{
($returnMessage,$event) = $hash->{ucCmd}->{responseSub}->($hash,$rmsg) ;
readingsSingleUpdate($hash, $hash->{ucCmd}->{cmd}, $returnMessage, $event) if (defined($returnMessage) && defined($event));
if (exists($hash->{ucCmd}->{asyncOut})) {
$hash->{logMethod}->($name, 5, "$name: Read, try asyncOutput of message $returnMessage");
my $ao = undef;
$ao = asyncOutput( $hash->{ucCmd}->{asyncOut}, $hash->{ucCmd}->{cmd}.': ' . $returnMessage ) if (defined($returnMessage));
$hash->{logMethod}->($name, 5, "$name: Read, asyncOutput failed $ao") if (defined($ao));
}
if ( exists $hash->{ucCmd} && defined $hash->{ucCmd}->{cmd} && $hash->{ucCmd}->{cmd} ne "sendraw" ) {
delete $hash->{ucCmd} ;
}
}
if (exists($hash->{keepalive})) {
$hash->{keepalive}{ok} = 1;
$hash->{keepalive}{retry} = 0;
}
} else {
$hash->{logMethod}->($name, 4, "$name: Read, msg: Received answer ($rmsg) for ". $hash->{ucCmd}->{cmd}." does not match $regexp / coderef");
}
}
}
$hash->{PARTIAL} = $SIGNALduinodata;
}
############################# package main
sub SIGNALduino_KeepAlive{
my ($hash) = @_;
my $name = $hash->{NAME};
return if ($hash->{DevState} eq 'disconnected');
#SIGNALduino_Log3 $name,4 , "$name: KeepAliveOk, " . $hash->{keepalive}{ok};
if (!$hash->{keepalive}{ok}) {
delete($hash->{ucCmd});
if ($hash->{keepalive}{retry} >= SDUINO_KEEPALIVE_MAXRETRY) {
$hash->{logMethod}->($name,3 , "$name: KeepAlive, not ok, retry count reached. Reset");
$hash->{DevState} = 'INACTIVE';
SIGNALduino_ResetDevice($hash);
return;
}
else {
my $logLevel = 3;
$hash->{keepalive}{retry} ++;
if ($hash->{keepalive}{retry} == 1) {
$logLevel = 4;
}
$hash->{logMethod}->($name, $logLevel, "$name: KeepAlive, not ok, retry = " . $hash->{keepalive}{retry} . ' -> get ping');
$hash->{ucCmd}->{cmd} = 'ping';
$hash->{ucCmd}->{timenow} = time();
$hash->{ucCmd}->{responseSub} = \&SIGNALduino_GetResponseUpdateReading;
SIGNALduino_AddSendQueue($hash, 'P');
}
}
else {
$hash->{logMethod}->($name,4 , "$name: KeepAlive, ok, retry = " . $hash->{keepalive}{retry});
}
$hash->{keepalive}{ok} = 0;
InternalTimer(gettimeofday() + SDUINO_KEEPALIVE_TIMEOUT, \&SIGNALduino_KeepAlive, $hash);
}
### Helper Subs >>>
############################# package main
## Parses a HTTP Response for example for flash via http download
sub SIGNALduino_ParseHttpResponse {
my ($param, $err, $data) = @_;
my $hash = $param->{hash};
my $name = $hash->{NAME};
if($err ne '') # wenn ein Fehler bei der HTTP Abfrage aufgetreten ist
{
$hash->{logMethod}->($name, 3, "$name: ParseHttpResponse, error while requesting ".$param->{url}." - $err"); # Eintrag fuers Log
}
elsif($param->{code} eq '200' && $data ne '') # wenn die Abfrage erfolgreich war ($data enthaelt die Ergebnisdaten des HTTP Aufrufes)
{
$hash->{logMethod}->($name, 3, "$name: ParseHttpResponse, url ".$param->{url}.' returned: '.length($data).' bytes Data'); # Eintrag fuers Log
if ($param->{command} eq 'flash')
{
my $filename;
if ($param->{httpheader} =~ /Content-Disposition: attachment;.?filename=\"?([-+.\w]+)?\"?/)
{
$filename = $1;
} else { # Filename via path if not specifyied via Content-Disposition
$param->{path} =~ /\/([-+.\w]+)$/; #(?:[^\/][\d\w\.]+)+$ \/([-+.\w]+)$
$filename = $1;
}
$hash->{logMethod}->($name, 3, "$name: ParseHttpResponse, Downloaded $filename firmware from ".$param->{host});
$hash->{logMethod}->($name, 5, "$name: ParseHttpResponse, Header = ".$param->{httpheader});
$filename = 'FHEM/firmware/' . $filename;
open(my $file, '>', $filename) or die $!;
print $file $data;
close $file;
# Den Flash Befehl mit der soebene heruntergeladenen Datei ausfuehren
#SIGNALduino_Log3 $name, 3, "$name: ParseHttpResponse, calling set ".$param->{command}." $filename"; # Eintrag fuers Log
my $set_return = SIGNALduino_Set($hash,$name,$param->{command},$filename); # $hash->{SetFn}
if (defined($set_return))
{
$hash->{logMethod}->($name ,3, "$name: ParseHttpResponse, Error while flashing: $set_return");
}
}
} else {
$hash->{logMethod}->($name, 3, "$name: ParseHttpResponse, undefined error while requesting ".$param->{url}." - $err - code=".$param->{code}); # Eintrag fuers Log
}
}
############################# package main
sub SIGNALduino_splitMsg {
my $txt = shift;
my $delim = shift;
my @msg_parts = split(/$delim/,$txt);
return @msg_parts;
}
############################# package main
# $value - $set <= $tolerance
sub SIGNALduino_inTol {
#Debug "sduino abs \($_[0] - $_[1]\) <= $_[2] ";
return (abs($_[0]-$_[1])<=$_[2]);
}
############################# package main
# =item SIGNALduino_FillPatternLookupTable()
#
# Retruns 1 on success or 0 if symbol was not found
sub SIGNALduino_FillPatternLookupTable {
my ($hash,$symbol,$representation,$patternList,$rawData,$patternLookupHash,$endPatternLookupHash,$rtext) = @_;
my $pstr=undef;
if (($pstr=SIGNALduino_PatternExists($hash, $symbol,$patternList,$rawData)) >=0) {
${$rtext} = $pstr;
$patternLookupHash->{$pstr}=${$representation}; ## Append to lookuptable
chop $pstr;
$endPatternLookupHash->{$pstr} = ${$representation} if (!exists($endPatternLookupHash->{$pstr})); ## Append shortened string to lookuptable
return 1;
} else {
${$rtext} = '';
return 0;
}
}
############################# package main
#=item SIGNALduino_PatternExists()
# This functons, needs reference to $hash, @array of values to search and %patternList where to find the matches.
#
# Will return -1 if pattern is not found or a string, containing the indexes which are in tolerance and have the smallest gap to what we searched
# =cut
# 01232323242423 while ($message =~ /$pstr/g) { $count++ }
sub SIGNALduino_PatternExists {
my ($hash,$search,$patternList,$data) = @_;
#my %patternList=$arg3;
#Debug 'plist: '.Dumper($patternList) if($debug);
#Debug 'searchlist: '.Dumper($search) if($debug);
my $debug = AttrVal($hash->{NAME},'debug',0);
my $i=0;
my @indexer;
my @sumlist;
my %plist=();
for my $searchpattern (@{$search}) # z.B. [1, -4]
{
next if (exists $plist{$searchpattern});
# Calculate tolernace for search
#my $tol=abs(abs($searchpattern)>=2 ?$searchpattern*0.3:$searchpattern*1.5);
my $tol=abs(abs($searchpattern)>3 ? abs($searchpattern)>16 ? $searchpattern*0.18 : $searchpattern*0.3 : 1); #tol is minimum 1 or higer, depending on our searched pulselengh
Debug "tol: looking for ($searchpattern +- $tol)" if($debug);
my %pattern_gap ; #= {};
# Find and store the gap of every pattern, which is in tolerance
%pattern_gap = map { $_ => abs($patternList->{$_}-$searchpattern) } grep { abs($patternList->{$_}-$searchpattern) <= $tol} (keys %$patternList);
if (scalar keys %pattern_gap > 0)
{
Debug "index => gap in tol (+- $tol) of pulse ($searchpattern) : ".Dumper(\%pattern_gap) if($debug);
# Extract fist pattern, which is nearst to our searched value
my @closestidx = (sort {$pattern_gap{$a} <=> $pattern_gap{$b}} keys %pattern_gap);
$plist{$searchpattern} = 1;
push @indexer, $searchpattern;
push @sumlist, [@closestidx];
} else {
# search is not found, return -1
return -1;
}
$i++;
}
sub cartesian_product { ## no critic
use List::Util qw(reduce);
reduce {
[ map {
my $item = $_;
map [ @$_, $item ], @$a
} @$b ]
} [[]], @_
}
my @res = cartesian_product @sumlist;
Debug qq[sumlists is: ].Dumper @sumlist if($debug);
Debug qq[res is: ].Dumper $res[0] if($debug);
Debug qq[indexer is: ].Dumper \@indexer if($debug);
OUTERLOOP:
for my $i (0..$#{$res[0]})
{
## Check if we have same patternindex for different values and skip this invalid ones
my %count;
for (@{$res[0][$i]})
{
$count{$_}++;
next OUTERLOOP if ($count{$_} > 1)
};
# Create a mapping table to exchange the values later on
for (my $x=0;$x <= $#indexer;$x++)
{
$plist{$indexer[$x]} = $res[0][$i][$x];
}
Debug qq[plist is for this check ].Dumper(\%plist) if($debug);
# Create our searchstring with our mapping table
my @patternVariant= @{$search};
for my $v (@patternVariant)
{
#Debug qq[value before is: $v ] if($debug);
$v = $plist{$v};
#Debug qq[after: $v ] if($debug);
}
Debug qq[patternVariant is ].Dumper(\@patternVariant) if($debug);
my $search_pattern = join '', @patternVariant;
(index ($$data, $search_pattern) > -1) ? return $search_pattern : next;
}
return -1;
}
############################# package main
#SIGNALduino_MatchSignalPattern{$hash,@array, %hash, @array, $scalar}; not used >v3.1.3
sub SIGNALduino_MatchSignalPattern($\@\%\@$){
my ( $hash, $signalpattern, $patternList, $data_array, $idx) = @_;
my $name = $hash->{NAME};
#print Dumper($patternList);
#print Dumper($idx);
#Debug Dumper($signalpattern) if ($debug);
my $tol='0.2'; # Tolerance factor
my $found=0;
my $debug = AttrVal($hash->{NAME},'debug',0);
foreach ( @{$signalpattern} )
{
#Debug " $idx check: ".$patternList->{$data_array->[$idx]}." == ".$_;
Debug "$name: idx: $idx check: abs(". $patternList->{$data_array->[$idx]}.' - '.$_.') > '. ceil(abs($patternList->{$data_array->[$idx]}*$tol)) if ($debug);
#print "\n";;
#if ($patternList->{$data_array->[$idx]} ne $_ )
### Nachkommastelle von ceil!!!
if (!defined( $patternList->{$data_array->[$idx]})){
Debug "$name: Error index ($idx) does not exist!!" if ($debug);
return -1;
}
if (abs($patternList->{$data_array->[$idx]} - $_) > ceil(abs($patternList->{$data_array->[$idx]}*$tol)))
{
return -1; ## Pattern does not match, return -1 = not matched
}
$found=1;
$idx++;
}
if ($found)
{
return $idx; ## Return new Index Position
}
}
############################# package main
sub SIGNALduino_Split_Message {
my $rmsg = shift;
my $name = shift;
my %patternList;
my $clockidx;
my $syncidx;
my $rawData;
my $clockabs;
my $mcbitnum;
my $rssi;
my @msg_parts = SIGNALduino_splitMsg($rmsg,';'); ## Split message parts by ';'
my %ret;
my $debug = AttrVal($name,'debug',0);
foreach (@msg_parts)
{
#Debug "$name: checking msg part:( $_ )" if ($debug);
#if ($_ =~ m/^MS/ or $_ =~ m/^MC/ or $_ =~ m/^Mc/ or $_ =~ m/^MU/) #### Synced Message start
if ($_ =~ m/^M./)
{
$ret{messagetype} = $_;
}
elsif ($_ =~ m/^P\d=-?\d{2,}/ or $_ =~ m/^[SL][LH]=-?\d{2,}/) #### Extract Pattern List from array
{
$_ =~ s/^P+//;
$_ =~ s/^P\d//;
my @pattern = split(/=/,$_);
$patternList{$pattern[0]} = $pattern[1];
Debug "$name: extracted pattern @pattern \n" if ($debug);
}
elsif($_ =~ m/D=\d+/ or $_ =~ m/^D=[A-F0-9]+/) #### Message from array
{
$_ =~ s/D=//;
$rawData = $_ ;
Debug "$name: extracted data $rawData\n" if ($debug);
$ret{rawData} = $rawData;
}
elsif($_ =~ m/^SP=([0-9])$/) #### Sync Pulse Index
{
Debug "$name: extracted syncidx $1\n" if ($debug);
#return undef if (!defined($patternList{$syncidx}));
$ret{syncidx} = $1;
}
elsif($_ =~ m/^CP=([0-9])$/) #### Clock Pulse Index
{
Debug "$name: extracted clockidx $1\n" if ($debug);;
$ret{clockidx} = $1;
}
elsif($_ =~ m/^L=\d/) #### MC bit length
{
(undef, $mcbitnum) = split(/=/,$_);
Debug "$name: extracted number of $mcbitnum bits\n" if ($debug);;
$ret{mcbitnum} = $mcbitnum;
}
elsif($_ =~ m/^C=\d+/) #### Message from array
{
$_ =~ s/C=//;
$clockabs = $_ ;
Debug "$name: extracted absolute clock $clockabs \n" if ($debug);
$ret{clockabs} = $clockabs;
}
elsif($_ =~ m/^R=\d+/) #### RSSI
{
$_ =~ s/R=//;
$rssi = $_ ;
Debug "$name: extracted RSSI $rssi \n" if ($debug);
$ret{rssi} = $rssi;
} else {
Debug "$name: unknown Message part $_" if ($debug);;
}
#print "$_\n";
}
$ret{pattern} = {%patternList};
return %ret;
}
############################# package main, test exists
# Function which dispatches a message if needed.
sub SIGNALduno_Dispatch {
my ($hash, $rmsg, $dmsg, $rssi, $id) = @_;
my $name = $hash->{NAME};
if (!defined($dmsg))
{
$hash->{logMethod}->($name, 5, "$name: Dispatch, dmsg is undef. Skipping dispatch call");
return;
}
#SIGNALduino_Log3 $name, 5, "$name: Dispatch, DMSG: $dmsg";
my $DMSGgleich = 1;
if ($dmsg eq $hash->{LASTDMSG}) {
$hash->{logMethod}->($name, SDUINO_DISPATCH_VERBOSE, "$name: Dispatch, $dmsg, test gleich");
} else {
if ( defined $hash->{DoubleMsgIDs}{$id} ) {
$DMSGgleich = 0;
$hash->{logMethod}->($name, SDUINO_DISPATCH_VERBOSE, "$name: Dispatch, $dmsg, test ungleich");
} else {
$hash->{logMethod}->($name, SDUINO_DISPATCH_VERBOSE, "$name: Dispatch, $dmsg, test ungleich: disabled");
}
$hash->{LASTDMSG} = $dmsg;
$hash->{LASTDMSGID} = $id;
}
if ($DMSGgleich) {
#Dispatch if dispatchequals is provided in protocol definition or only if $dmsg is different from last $dmsg, or if 2 seconds are between transmits
if ( ( $hash->{protocolObject}->checkProperty($id,'dispatchequals','false') eq 'true')
|| ($hash->{DMSG} ne $dmsg)
|| ($hash->{TIME}+2 < time() ) )
{
$hash->{MSGCNT}++;
$hash->{TIME} = time();
$hash->{DMSG} = $dmsg;
#my $event = 0;
if (substr(ucfirst($dmsg),0,1) eq 'U') { # u oder U
#$event = 1;
DoTrigger($name, 'DMSG ' . $dmsg);
return if (substr($dmsg,0,1) eq 'U'); # Fuer $dmsg die mit U anfangen ist kein Dispatch notwendig, da es dafuer kein Modul gibt klein u wird dagegen dispatcht
}
#readingsSingleUpdate($hash, 'state', $hash->{READINGS}{state}{VAL}, $event);
$hash->{RAWMSG} = $rmsg;
my %addvals = (
DMSG => $dmsg,
Protocol_ID => $id
);
if (AttrVal($name,'suppressDeviceRawmsg',0) == 0) {
$addvals{RAWMSG} = $rmsg
}
if(defined($rssi)) {
$hash->{RSSI} = $rssi;
$addvals{RSSI} = $rssi;
$rssi .= ' dB,'
}
else {
$rssi = '';
}
$dmsg = lc($dmsg) if ($id eq '74' or $id eq '74.1'); # 10_FS20.pm accepted only lower case hex
$hash->{logMethod}->($name, SDUINO_DISPATCH_VERBOSE, "$name: Dispatch, $dmsg, $rssi dispatch");
Dispatch($hash, $dmsg, \%addvals); ## Dispatch to other Modules
} else {
$hash->{logMethod}->($name, 4, "$name: Dispatch, $dmsg, Dropped due to short time or equal msg");
}
}
}
############################# package main todo: move to lib::SD_Protocols
# param #1 is name of definition
# param #2 is protocol id
# param #3 is dispatched message to check against
#
# returns 1 if message matches modulematch + development attribute/whitelistIDs
# returns 0 if message does not match modulematch
# return -1 if message is not activated via whitelistIDs but has developID=m flag
sub SIGNALduino_moduleMatch {
my $name = shift // carp q[arg name must be provided];
my $id = shift;
my $dmsg = shift;
my $debug = AttrVal($name,'debug',0);
my $hash = $defs{$name} // carp q[$name does not exist];
my $modMatchRegex=$hash->{protocolObject}->checkProperty($id,'modulematch',undef);
if (!defined($modMatchRegex) || $dmsg =~ m/$modMatchRegex/) {
Debug "$name: modmatch passed for: $dmsg" if ($debug);
my $developID = $hash->{protocolObject}->checkProperty($id,'developId','');
my $IDsNoDispatch = ',' . InternalVal($name,'IDsNoDispatch','') . ',';
if ($IDsNoDispatch ne ',,' && index($IDsNoDispatch, ",$id,") >= 0) { # kein dispatch wenn die Id im Internal IDsNoDispatch steht
Log3 $name, 3, "$name: moduleMatch, ID=$id skipped dispatch (developId=m). To use, please add $id to the attr whitelist_IDs";
return -1;
}
return 1; # return 1 da modulematch gefunden wurde
}
return 0;
}
############################# package main, test exists
# calculated RSSI and RSSI value and RSSI string (-77,'RSSI = -77')
sub SIGNALduino_calcRSSI {
my $rssi = shift // return ;
my $rssiStr = '';
$rssi = ($rssi>=128 ? (($rssi-256)/2-74) : ($rssi/2-74));
$rssiStr = "RSSI = $rssi";
return ($rssi,$rssiStr);
}
=item SIGNALduino_Parse_MS
This sub parses a MS rawdata string and dispatches it if a protocol matched the cirteria.
Input: $iohash, $rawMessage
Output: { Number of times dispatch was called, 0 if dispatch isn't called }
=cut
############################# package main
sub SIGNALduino_Parse_MS {
my $hash = shift // return; #return if no hash is provided
my $rmsg = shift // return; #return if no rmsg is provided
if ($rmsg !~ /^MS;(?:P[0-7]=-?\d+;){3,8}D=[0-7]+;(?:[CS]P=[0-7];){2}((?:R=\d+;)|(?:O;)?|(?:m=?[0-9];)|(?:[sbeECA=0-9]+;))*$/){
$hash->{logMethod}->($hash->{NAME}, 3, qq[$hash->{NAME}: Parse_MS, faulty msg: $rmsg]);
return ; # Abort here if not successfull
}
# Extract Data from rmsg:
my %msg_parts = SIGNALduino_Split_Message($rmsg, $hash->{NAME});
# Verify if extracted hash has the correct values:
my $clockidx = _limit_to_number($msg_parts{clockidx}) // $hash->{logMethod}->($hash->{NAME}, 3, qq[$hash->{NAME}: Parse_MS, faulty clock: $msg_parts{clockidx}]) // return ;
my $syncidx = _limit_to_number($msg_parts{syncidx}) // $hash->{logMethod}->($hash->{NAME}, 3, qq[$hash->{NAME}: Parse_MS, faulty sync: $msg_parts{syncidx}]) // return ;
my $rawData = _limit_to_number($msg_parts{rawData}) // $hash->{logMethod}->($hash->{NAME}, 3, qq[$hash->{NAME}: Parse_MS, faulty rawData D=: $msg_parts{rawData}]) // return ;
my $rssi;
my $rssiStr= '';
if ( defined $msg_parts{rssi} ){
$rssi = _limit_to_number($msg_parts{rssi}) // $hash->{logMethod}->($hash->{NAME}, 3, qq[$hash->{NAME}: Parse_MS, faulty rssi R=: $msg_parts{rssi}]) // return ;
($rssi,$rssiStr) = SIGNALduino_calcRSSI($rssi);
};
my $messagetype=$msg_parts{messagetype};
my $name = $hash->{NAME};
my %patternList;
#Debug 'Message splitted:';
#Debug Dumper(\@msg_parts);
my $debug = AttrVal($hash->{NAME},'debug',0);
if (defined($clockidx) and defined($syncidx))
{
## Make a lookup table for our pattern index ids
#Debug 'List of pattern:';
my $clockabs= $msg_parts{pattern}{$msg_parts{clockidx}};
return if ($clockabs == 0);
$patternList{$_} = round($msg_parts{pattern}{$_}/$clockabs,1) for keys %{$msg_parts{pattern}};
#Debug Dumper(\%patternList);
#my $syncfact = $patternList{$syncidx}/$patternList{$clockidx};
#$syncfact=$patternList{$syncidx};
#Debug 'SF=$syncfact';
#### Convert rawData in Message
my $signal_length = length($rawData); # Length of data array
## Iterate over the data_array and find zero, one, float and sync bits with the signalpattern
## Find matching protocols
my $message_dispatched=0;
IDLOOP:
foreach my $id (@{$hash->{msIdList}}) {
Debug qq[Testing against protocol id $id -> ].$hash->{protocolObject}->getProperty($id,'name') if ($debug);
# Check Clock if is it in range
if ($hash->{protocolObject}->checkProperty($id,'clockabs',0) > 0) {
if (!SIGNALduino_inTol($hash->{protocolObject}->getProperty($id,'clockabs'),$clockabs,$clockabs*0.30)) {
Debug qq[protocClock=].$hash->{protocolObject}->getProperty($id,'clockabs').qq[, msgClock=$clockabs is not in tol=].$clockabs*0.30 if ($debug);
next;
} elsif ($debug) {
Debug qq[protocClock=].$hash->{protocolObject}->getProperty($id,'clockabs').qq[, msgClock=$clockabs is in tol="] . $clockabs*0.30;
}
}
Debug 'Searching in patternList: '.Dumper(\%patternList) if($debug);
my %patternLookupHash=();
my %endPatternLookupHash=();
my $signal_width= @{$hash->{protocolObject}->getProperty($id,'one')};
my $return_text;
my $message_start;
foreach my $key (qw(sync one zero float) ) {
next if (!defined($hash->{protocolObject}->getProperty($id,$key)));
if (!SIGNALduino_FillPatternLookupTable($hash,\@{$hash->{protocolObject}->getProperty($id,$key)},\$symbol_map{$key},\%patternList,\$rawData,\%patternLookupHash,\%endPatternLookupHash,\$return_text))
{
Debug sprintf("%s pattern not found",$key) if ($debug);
next IDLOOP if ($key ne 'float') ;
}
if ($key eq 'sync')
{
$message_start =index($rawData,$return_text)+length($return_text);
my $bit_length = ($signal_length-$message_start) / $signal_width;
if ($hash->{protocolObject}->checkProperty($id,'length_min',-1) > $bit_length) {
Debug "bit_length=$bit_length to short" if ($debug);
next IDLOOP;
}
Debug "expecting $bit_length bits in signal" if ($debug);
%endPatternLookupHash=();
}
Debug sprintf("Found matched %s with indexes: (%s)",$key,$return_text) if ($debug);
}
next if (scalar keys %patternLookupHash == 0); # Keine Eingträge im patternLookupHash
$hash->{logMethod}->($name, 4, qq[$name: Parse_MS, Matched MS protocol id $id -> ].$hash->{protocolObject}->getProperty($id,'name'));
my @bit_msg; # array to store decoded signal bits
$hash->{logMethod}->($name, 5, qq[$name: Parse_MS, Starting demodulation at Position $message_start]);
for (my $i=$message_start;$i
";
if (-s AttrVal('global', 'logdir', './log/') .$fn)
{
my $flashlogurl="$FW_ME/FileLog_logWrapper?dev=$lfn&type=text&file=$fn";
$ret .= " ";
$ret .= "Last Flashlog<\/a>";
$ret .= " ";
#return $ret;
}
my $protocolURL="$FW_ME/FileLog_logWrapper?dev=$lfn&type=text&file=$fn";
$ret.="Display protocollist ";
$ret .= '";
$ret .="
";
return $ret;
}
############################# package main
sub SIGNALduino_querygithubreleases {
my ($hash) = @_;
my $name = $hash->{NAME};
my $param = {
url => 'https://api.github.com/repos/RFD-FHEM/SIGNALDuino/releases',
timeout => 5,
hash => $hash, # Muss gesetzt werden, damit die Callback funktion wieder $hash hat
method => 'GET', # Lesen von Inhalten
header => "User-Agent: perl_fhem\r\nAccept: application/json", # Den Header gemaess abzufragender Daten aendern
callback => \&SIGNALduino_githubParseHttpResponse, # Diese Funktion soll das Ergebnis dieser HTTP Anfrage bearbeiten
command => "queryReleases"
};
HttpUtils_NonblockingGet($param); # Starten der HTTP Abfrage. Es gibt keinen Return-Code.
}
############################# package main
#return -10 = hardeware attribute is not set
sub SIGNALduino_githubParseHttpResponse {
my ($param, $err, $data) = @_;
my $hash = $param->{hash};
my $name = $hash->{NAME};
my $hardware=AttrVal($name,'hardware',undef);
if($err ne '') # wenn ein Fehler bei der HTTP Abfrage aufgetreten ist
{
Log3 $name, 3, "$name: githubParseHttpResponse, error while requesting ".$param->{url}." - $err (command: $param->{command}"; # Eintrag fuers Log
#readingsSingleUpdate($hash, 'fullResponse', 'ERROR'); # Readings erzeugen
}
elsif($data ne '' && defined($hardware)) # wenn die Abfrage erfolgreich war ($data enthaelt die Ergebnisdaten des HTTP Aufrufes)
{
my $json_array = decode_json($data);
#print Dumper($json_array);
if ($param->{command} eq 'queryReleases') {
#Log3 $name, 3, "$name: githubParseHttpResponse, url ".$param->{url}." returned: $data"; # Eintrag fuers Log
my $releaselist='';
if (ref($json_array) eq "ARRAY") {
foreach my $item( @$json_array ) {
next if (AttrVal($name,'updateChannelFW','stable') eq 'stable' && $item->{prerelease});
#Debug ' item = '.Dumper($item);
foreach my $asset (@{$item->{assets}})
{
next if ($asset->{name} !~ m/$hardware/i);
$releaselist.=$item->{tag_name}.',' ;
last;
}
}
}
$releaselist =~ s/,$//;
$hash->{additionalSets}{flash} = $releaselist;
} elsif ($param->{command} eq 'getReleaseByTag' && defined($hardware)) {
#Debug ' json response = '.Dumper($json_array);
my @fwfiles;
foreach my $asset (@{$json_array->{assets}})
{
my %fileinfo;
if ( $asset->{name} =~ m/$hardware/i)
{
$fileinfo{filename} = $asset->{name};
$fileinfo{dlurl} = $asset->{browser_download_url};
$fileinfo{create_date} = $asset->{created_at};
#Debug ' firmwarefiles = '.Dumper(@fwfiles);
push @fwfiles, \%fileinfo;
my $set_return = SIGNALduino_Set($hash,$name,'flash',$asset->{browser_download_url}); # $hash->{SetFn
if(defined($set_return))
{
$hash->{logMethod}->($name, 3, "$name: githubParseHttpResponse, Error while trying to download firmware: $set_return");
}
last;
}
}
}
} elsif (!defined($hardware)) {
$hash->{logMethod}->($name, 5, "$name: githubParseHttpResponse, hardware is not defined");
}
# wenn
# Damit ist die Abfrage zuende.
# Evtl. einen InternalTimer neu schedulen
if (defined $FW_wname)
{
FW_directNotify("FILTER=$name", "#FHEMWEB:$FW_wname", "location.reload('true')", '');
}
return 0;
}
############################# package main, candidate for fhem core utility lib
sub _limit_to_number {
my $number = shift // return;
return $number if ($number =~ /^[0-9]+$/);
return ;
}
############################# package main, candidate for fhem core utility lib
sub _limit_to_hex {
my $hex = shift // return;
return $hex if ($hex =~ /^[0-9A-F]+$/i);
return;
}
################################################
########## Section & functions cc1101 ##########
package cc1101;
our %cc1101_status_register = ( # for get ccreg 30-3D status registers
'30' => 'PARTNUM ',
'31' => 'VERSION ',
'32' => 'FREQEST ',
'33' => 'LQI ',
'34' => 'RSSI ',
'35' => 'MARCSTATE ',
'36' => 'WORTIME1 ',
'37' => 'WORTIME0 ',
'38' => 'PKTSTATUS ',
'39' => 'VCO_VC_DAC ',
'3A' => 'TXBYTES ',
'3B' => 'RXBYTES ',
'3C' => 'RCCTRL1_STATUS',
'3D' => 'RCCTRL0_STATUS',
);
our %cc1101_version = ( # Status register 0x31 (0xF1): VERSION – Chip ID
'03' => 'CC1100',
'04' => 'CC1101',
'14' => 'CC1101',
'05' => 'CC1100E',
'07' => 'CC110L',
'17' => 'CC110L',
'08' => 'CC113L',
'18' => 'CC113L',
'15' => 'CC115L',
);
############################# package cc1101
#### for set function to change the patable for 433 or 868 Mhz supported
#### 433.05–434.79 MHz, 863–870 MHz
sub SetPatable {
my ($hash,@a) = @_;
my $paFreq = main::AttrVal($hash->{NAME},'cc1101_frequency','433');
$paFreq = 433 if ($paFreq >= 433 && $paFreq <= 435);
$paFreq = 868 if ($paFreq >= 863 && $paFreq <= 870);
if ( exists($patable{$paFreq}) )
{
my $pa = "x" . $patable{$paFreq}{$a[1]};
$hash->{logMethod}->($hash->{NAME}, 3, "$hash->{NAME}: SetPatable, Setting patable $paFreq $a[1] $pa");
main::SIGNALduino_AddSendQueue($hash,$pa);
main::SIGNALduino_WriteInit($hash);
return ;
} else {
return "$hash->{NAME}: Frequency $paFreq MHz not supported (supported frequency ranges: 433.05-434.79 MHz, 863.00-870.00 MHz).";
}
}
############################# package cc1101
sub SetRegisters {
my ($hash, @a) = @_;
## check for four hex digits
my @nonHex = grep (!/^[0-9A-Fa-f]{4}$/,@a[1..$#a]) ;
return "$hash->{NAME} ERROR: wrong parameter value @nonHex, only hexadecimal four digits allowed" if (@nonHex);
## check allowed register position
my (@wrongRegisters) = grep { !exists($cc1101_register{uc(substr($_,0,2))}) } @a[1..$#a] ;
return "$hash->{NAME} ERROR: unknown register position ".substr($wrongRegisters[0],0,2) if (@wrongRegisters);
$hash->{logMethod}->($hash->{NAME}, 4, "$hash->{NAME}: SetRegisters, cc1101_reg @a[1..$#a]");
my @tmpSendQueue=();
foreach my $argcmd (@a[1..$#a]) {
$argcmd = sprintf("W%02X%s",hex(substr($argcmd,0,2)) + 2,substr($argcmd,2,2));
main::SIGNALduino_AddSendQueue($hash,$argcmd);
}
main::SIGNALduino_WriteInit($hash);
return ;
}
############################# package cc1101
sub SetRegistersUser {
my ($hash) = @_;
my $cc1101User = main::AttrVal($hash->{NAME}, 'cc1101_reg_user', undef);
## look, user defined self default register values via attribute
if (defined $cc1101User) {
$hash->{logMethod}->($hash->{NAME}, 3, "$hash->{NAME}: SetRegistersUser, write CC1101 defaults from attribute");
$cc1101User = '0815,'.$cc1101User; # for SetRegisters, value for register starts on pos 1 in array
cc1101::SetRegisters($hash, split(',', $cc1101User) );
}
return ;
}
############################# package cc1101
sub SetDataRate {
my ($hash, @a) = @_;
my $arg = $a[1];
if (exists($hash->{ucCmd}->{cmd}) && $hash->{ucCmd}->{cmd} eq 'set_dataRate' && $a[0] =~ /^C10\s=\s([A-Fa-f0-9]{2})$/) {
my ($ob1,$ob2) = cc1101::CalcDataRate($hash,$1,$hash->{ucCmd}->{arg});
main::SIGNALduino_AddSendQueue($hash,"W12$ob1");
main::SIGNALduino_AddSendQueue($hash,"W13$ob2");
main::SIGNALduino_WriteInit($hash);
return ("Setting MDMCFG4..MDMCFG3 to $ob1 $ob2 = $hash->{ucCmd}->{arg} kHz" ,undef);
} else {
if ($arg !~ m/\d/) { return qq[$hash->{NAME}: ERROR, unsupported DataRate value]; }
if ($arg > 1621.83) { $arg = 1621.83; } # max 1621.83 kBaud DataRate
if ($arg < 0.0247955) { $arg = 0.0247955; } # min 0.0247955 kBaud DataRate
cc1101::GetRegister($hash,10); # Get Register 10
$hash->{ucCmd}->{cmd} = 'set_dataRate';
$hash->{ucCmd}->{arg} = $arg; # ZielDataRate
$hash->{ucCmd}->{responseSub} = \&cc1101::SetDataRate; # Callback auf sich selbst setzen
$hash->{ucCmd}->{asyncOut} = $hash->{CL} if (defined($hash->{CL}));
$hash->{ucCmd}->{timenow} = time();
}
return ;
}
############################# package cc1101
sub CalcDataRate {
# register 0x10 3:0 & register 0x11 7:0
my ($hash, $ob10, $dr) = @_;
$ob10 = hex($ob10) & 0xf0;
my $DRATE_E = ($dr*1000) * (2**20) / 26000000;
$DRATE_E = log($DRATE_E) / log(2);
$DRATE_E = int($DRATE_E);
my $DRATE_M = (($dr*1000) * (2**28) / (26000000 * (2**$DRATE_E))) - 256;
my $DRATE_Mr = main::round($DRATE_M,0);
$DRATE_M = int($DRATE_M);
my $datarate0 = ( ((256+$DRATE_M)*(2**($DRATE_E & 15 )))*26000000/(2**28) / 1000);
my $DRATE_M1 = $DRATE_M + 1;
my $DRATE_E1 = $DRATE_E;
if ($DRATE_M1 == 256) {
$DRATE_M1 = 0;
$DRATE_E1++;
}
my $datarate1 = ( ((256+$DRATE_M1)*(2**($DRATE_E1 & 15 )))*26000000/(2**28) / 1000);
if ($DRATE_Mr != $DRATE_M) {
$DRATE_M = $DRATE_M1;
$DRATE_E = $DRATE_E1;
}
my $ob11 = sprintf("%02x",$DRATE_M);
$ob10 = sprintf("%02x", $ob10+$DRATE_E);
$hash->{logMethod}->($hash->{NAME}, 5, qq[$hash->{NAME}: CalcDataRate, DataRate $hash->{ucCmd}->{arg} kHz step from $datarate0 to $datarate1 kHz]);
$hash->{logMethod}->($hash->{NAME}, 3, qq[$hash->{NAME}: CalcDataRate, DataRate MDMCFG4..MDMCFG3 to $ob10 $ob11 = $hash->{ucCmd}->{arg} kHz]);
return ($ob10,$ob11);
}
############################# package cc1101
sub SetDeviatn {
my ($hash, @a) = @_;
my $arg = $a[1];
if ($arg !~ m/\d/) { return qq[$hash->{NAME}: ERROR, unsupported Deviation value]; }
if ($arg > 380.859375) { $arg = 380.859375; } # max 380.859375 kHz Deviation
if ($arg < 1.586914) { $arg = 1.586914; } # min 1.586914 kHz Deviation
my $deviatn_val;
my $bits;
my $devlast = 0;
my $bitlast = 0;
CalcDeviatn:
for (my $DEVIATION_E=0; $DEVIATION_E<8; $DEVIATION_E++) {
for (my $DEVIATION_M=0; $DEVIATION_M<8; $DEVIATION_M++) {
$deviatn_val = (8+$DEVIATION_M)*(2**$DEVIATION_E) *26000/(2**17);
$bits = $DEVIATION_M + ($DEVIATION_E << 4);
if ($arg > $deviatn_val) {
$devlast = $deviatn_val;
$bitlast = $bits;
} else {
if (($deviatn_val - $arg) < ($arg - $devlast)) {
$devlast = $deviatn_val;
$bitlast = $bits;
}
last CalcDeviatn;
}
}
}
my $reg15 = sprintf("%02x",$bitlast);
my $deviatn_str = sprintf("% 5.2f",$devlast);
$hash->{logMethod}->($hash->{NAME}, 3, qq[$hash->{NAME}: SetDeviatn, Setting DEVIATN (15) to $reg15 = $deviatn_str kHz]);
main::SIGNALduino_AddSendQueue($hash,"W17$reg15");
main::SIGNALduino_WriteInit($hash);
return;
}
############################# package cc1101
sub SetFreq {
my ($hash, @a) = @_;
my $arg = $a[1];
if (!defined($arg)) {
$arg = main::AttrVal($hash->{NAME},'cc1101_frequency', 433.92);
}
my $f = $arg/26*65536;
my $f2 = sprintf("%02x", $f / 65536);
my $f1 = sprintf("%02x", int($f % 65536) / 256);
my $f0 = sprintf("%02x", $f % 256);
$arg = sprintf("%.3f", (hex($f2)*65536+hex($f1)*256+hex($f0))/65536*26);
$hash->{logMethod}->($hash->{NAME}, 3, "$hash->{NAME}: SetFreq, Setting FREQ2..0 (0D,0E,0F) to $f2 $f1 $f0 = $arg MHz");
main::SIGNALduino_AddSendQueue($hash,"W0F$f2");
main::SIGNALduino_AddSendQueue($hash,"W10$f1");
main::SIGNALduino_AddSendQueue($hash,"W11$f0");
main::SIGNALduino_WriteInit($hash);
return ;
}
############################# package cc1101
sub setrAmpl {
my ($hash, @a) = @_;
return "$hash->{NAME}: A numerical value between 24 and 42 is expected." if($a[1] !~ m/^\d+$/ || $a[1] < 24 ||$a[1] > 42);
my $v;
for($v = 0; $v < @ampllist; $v++) {
last if($ampllist[$v] > $a[1]);
}
$v = sprintf("%02d", $v-1);
my $w = $ampllist[$v];
$hash->{logMethod}->($hash->{NAME}, 3, "$hash->{NAME}: setrAmpl, Setting AGCCTRL2 (1B) to $v / $w dB");
main::SIGNALduino_AddSendQueue($hash,"W1D$v");
main::SIGNALduino_WriteInit($hash);
return ;
}
############################# package cc1101
sub GetRegister {
my ($hash, $reg) = @_;
main::SIGNALduino_AddSendQueue($hash,'C'.$reg);
return ;
}
############################# package cc1101
sub CalcbWidthReg {
my ($hash, $reg10, $bWith) = @_;
# Beispiel Rückmeldung, mit Ergebnis von Register 10: C10 = 57
my $ob = hex($reg10) & 0x0f;
my ($bits, $bw) = (0,0);
OUTERLOOP:
for (my $e = 0; $e < 4; $e++) {
for (my $m = 0; $m < 4; $m++) {
$bits = ($e<<6)+($m<<4);
$bw = int(26000/(8 * (4+$m) * (1 << $e))); # KHz
last OUTERLOOP if($bWith >= $bw);
}
}
$ob = sprintf("%02x", $ob+$bits);
return ($ob,$bw);
}
############################# package cc1101
sub SetSens {
my ($hash, @a) = @_;
# Todo: Abfrage in Grep auf Array ändern
return 'a numerical value between 4 and 16 is expected' if($a[1] !~ m/^\d+$/ || $a[1] < 4 || $a[1] > 16);
my $w = int($a[1]/4)*4;
my $v = sprintf("9%d",$a[1]/4-1);
$hash->{logMethod}->($hash->{NAME}, 3, "$hash->{NAME}: SetSens, Setting AGCCTRL0 (1D) to $v / $w dB");
main::SIGNALduino_AddSendQueue($hash,"W1F$v");
main::SIGNALduino_WriteInit($hash);
return ;
}
################################################################################################
1;
=pod
=encoding utf8
=item summary supports the same low-cost receiver for digital signals
=item summary_DE Unterstuetzt den gleichnamigen Low-Cost Empfaenger fuer digitale Signale
=begin html
act. dev ID Msg Type modulname protocolname # comment ";
$ret .="";
my $oddeven="odd";
my $checked;
my $checkAll;
foreach my $id (@IdList)
{
my $msgtype = '';
my $chkbox;
if (defined $hash->{protocolObject}->getProperty($id,'format') && $hash->{protocolObject}->getProperty($id,'format') eq 'manchester')
{
$msgtype = 'MC';
}
elsif (defined $hash->{protocolObject}->getProperty($id,'modulation'))
{
$msgtype = 'MN';
}
elsif (defined $hash->{protocolObject}->getProperty($id,'sync'))
{
$msgtype = 'MS';
}
elsif (defined $hash->{protocolObject}->getProperty($id,'clockabs'))
{
$msgtype = 'MU';
}
$checked='';
if (substr($whitelist,0,1) ne '#') { # whitelist aktiv, dann ermitteln welche ids bei select all nicht checked sein sollen
$checkAll = 'SDcheck';
if (exists($BlacklistIDs{$id})) {
$checkAll = 'SDnotCheck';
}
elsif (defined $hash->{protocolObject}->getProperty($id,'developId')) {
if ($devFlag == 1 && $hash->{protocolObject}->getProperty($id,'developId') eq 'p') {
$checkAll = 'SDnotCheck';
}
elsif ($devFlag == 0 && $hash->{protocolObject}->getProperty($id,'developId') eq 'y' && $develop !~ m/y$id/) {
$checkAll = 'SDnotCheck';
}
elsif ($devFlag == 0 && $hash->{protocolObject}->getProperty($id,'developId') eq 'm') {
$checkAll = 'SDnotCheck';
}
}
}
else {
$checkAll = 'SDnotCheck';
}
if (exists($activeIdHash{$id}))
{
$checked='checked';
if (substr($whitelist,0,1) eq '#') { # whitelist nicht aktiv, dann entspricht select all dem $activeIdHash
$checkAll = 'SDcheck';
}
}
if ($devFlag == 0 && defined $hash->{protocolObject}->getProperty($id,'developId') && $hash->{protocolObject}->getProperty($id,'developId') eq 'p') {
$chkbox=" ",$oddeven,$chkbox,$hash->{protocolObject}->checkProperty($id,'developId',''),$id,$msgtype,$hash->{protocolObject}->checkProperty($id,'clientmodule',''),$hash->{protocolObject}->checkProperty($id,'name',''),$comment);
$oddeven= $oddeven eq "odd" ? "even" : "odd" ;
$ret .= "\n";
}
$ret .= "%s SIGNALduino
The SIGNALduino ia based on an idea from mdorenka published at FHEM Forum.
With the opensource firmware (see this link) it is capable to receive and send different protocols over different medias.
The following device support is currently available:
Wireless switches
Temperature / humidity sensors
In the ITv1 protocol is used to sent a default ITclock from 250 and it may be necessary in the IT-Modul to define the attribute ITclock
It is possible to attach more than one device in order to get better reception, fhem will filter out duplicate messages. See more at the global section with attribute dupTimeout
Note: this module require the Device::SerialPort or Win32::SerialPort module. It can currently only attatched via USB.
Define
USB-connected devices (SIGNALduino):define <name> SIGNALduino <device>
Internals
In this case the device is most probably /dev/ttyUSB0.
You can also specify a baudrate if the device name contains the @ character, e.g.: /dev/ttyACM0@57600
This is also the default baudrate.
It is recommended to specify the device via a name which does not change:
e.g. via by-id devicename: /dev/serial/by-id/usb-1a86_USB2.0-Serial-if00-port0@57600
If the baudrate is "directio" (e.g.: /dev/ttyACM0@directio), then the perl module Device::SerialPort is not needed, and fhem opens the device with simple file io. This might work if the operating system uses sane defaults for the serial parameters, e.g. some Linux distributions and OSX.
Set
Enable autocreate of new LaCrosse sensors for x seconds. If ignore_battery is not given only sensors sending the 'new battery' flag will be created.
(Only with CC1101 receiver)
Set the sduino frequency / bandwidth / PA table / receiver-amplitude / sensitivity
Use it with care, it may destroy your hardware and it even may be
illegal to do so. Note: The parameters used for RFR transmission are
not affected.
cc1101_bWidth
can be set to values between 58 kHz and 812 kHz. Large values are susceptible to interference, but make possible to receive inaccurately calibrated transmitters. It affects tranmission too. Default is 325 kHz.
cc1101_dataRate
, can be set to values between 0.0247955 kBaud and 1621.83 kBaud.
cc1101_deviatn
, can be set to values between 1.586914 kHz and 380.859375 kHz.
cc1101_freq
sets both the reception and transmission frequency. Note: Although the CC1101 can be set to frequencies between 315 and 915 MHz, the antenna interface and the antenna is tuned for exactly one frequency. Default is 433.920 MHz (or 868.350 MHz). If not set, frequency from cc1101_frequency
will be used.
cc1101_patable
change the PA table (power amplification for RF sending)
cc1101_rAmpl
is receiver amplification, with values between 24 and 42 dB. Bigger values allow reception of weak signals. Default is 42.
cc1101_reg
You can set multiple registers at one. Specify the register with its two digit hex code followed by the register value separate multiple registers via space.
cc1101_sens
is the decision boundary between the on and off values, and it is 4, 8, 12 or 16 dB. Smaller values allow reception of less clear signals. Default is 4 dB.
Closes the connection to the device.
Allows you to disable the message processing for
The new state will be saved into the eeprom of your arduino.
Allows you to enable the message processing for
The new state will be saved into the eeprom of your arduino.
The SIGNALduino needs the right firmware to be able to receive and deliver the sensor data to fhem. In addition to the way using the arduino IDE to flash the firmware into the SIGNALduino this provides a way to flash it directly from FHEM. You can specify a file on your fhem server or specify a url from which the firmware is downloaded.
There are some requirements:
On a Raspberry PI this can be done with: sudo apt-get install avrdude
This attribute defines the command, that gets sent to avrdude to flash the uC.
Example:
set sduino flash ./FHEM/firmware/SIGNALduino_mega2560.hex
set sduino flash https://github.com/RFD-FHEM/SIGNALDuino/releases/download/3.3.1-RC7/SIGNALDuino_nanocc1101.hex
note model radino:
/dev/ttyACM0
, the flashing of the firmware should be done automatically. If this fails, the boot loader must be activated manually:
In bootloader mode, the radino gets a different USB ID. This must be entered in the "flashCommand" attribute.
If the bootloader is enabled, it signals with a flashing LED. Then you have 8 seconds to flash.
Issue a SIGNALduino firmware command, without waiting data returned by
the SIGNALduino. See the SIGNALduino firmware code for details on SIGNALduino
commands. With this line, you can send almost any signal via a transmitter connected
To send some raw data look at these examples:
P
Example 1: set sduino raw SR;R=3;P0=500;P1=-9000;P2=-4000;P3=-2000;D=0302030;
sends the data in raw mode 3 times repeated
Example 2: set sduino raw SM;R=3;P0=500;C=250;D=A4F7FDDE;
sends the data manchester encoded with a clock of 250uS
Example 3: set sduino raw SC;R=3;SR;P0=5000;SM;P0=500;C=250;D=A4F7FDDE;
sends a combined message of raw and manchester encoded repeated 3 times
Example 4: set sduino raw SN;R=3;D=9A46036AC8D3923EAEB470AB;
sends a xFSK message of raw and repeated 3 times
note: The wrong use of the upcoming options can lead to malfunctions of the SIGNALduino!
Register commands for a CC1101
example:
set sduino raw C04
reads the value from register address 0x04
example 1:
set sduino raw W041D
write 1D to register 0x02example 2:
set sduino raw W041D#W0604
write 1D to register 0x02 and write 04 to register 0x04
other commands from uC
This will do a reset of the usb port and normaly causes to reset the uC connected.
This command will create the needed instructions for sending raw data via the signalduino. Insteaf of specifying the signaldata by your own you specify
a protocol and the bits you want to send. The command will generate the needed command, that the signalduino will send this.
It is also supported to specify the data in hex. prepend 0x in front of the data part.
Please note, that this command will work only for MU or MS protocols. You can't transmit manchester data this way.
Input args are:
Example binarydata: set sduino sendMsg P0#0101#R3#C500
Will generate the raw send command for the message 0101 with protocol 0 and instruct the arduino to send this three times and the clock is 500.
SR;R=3;P0=500;P1=-9000;P2=-4000;P3=-2000;D=03020302;
Example 0xhexdata: set sduino sendMsg P29#0xF7E#R4
Generates the raw send command with the hex message F7E with protocl id 29 . The message will be send four times.
SR;R=4;P0=-8360;P1=220;P2=-440;P3=-220;P4=440;D=01212121213421212121212134;
Example 0xhexdata: set sduino sendMsg P36#0xF7#R6#Fxxxxxxxxxx
(xxxxxxxxxx = register from CC1101)
Generates the raw send command with the hex message F7 with protocl id 36 . The message will be send six times.
SR;R=6;P0=-8360;P1=220;P2=-440;P3=-220;P4=440;D=012323232324232323;F= (register from CC1101);
MS=1;MU=1;MC=1;Mred=0
MS;P0=-7871;P2=-1960;P3=578;P4=-3954;D=030323232323434343434323232323234343434323234343234343234343232323432323232323232343234;CP=3;SP=0;R=0;m=0;
avrdude -c arduino -b [BAUDRATE] -P [PORT] -p atmega328p -vv -U flash:w:[HEXFILE] 2>[LOGFILE]
avrdude -c avr109 -b [BAUDRATE] -P [PORT] -p atmega32u4 -vv -D -U flash:w:[HEXFILE] 2>[LOGFILE]
# Do not use any long IDs for any devices: attr sduino longids 0 # Use any long IDs for all devices (this is default): attr sduino longids 1 # Use longids for BTHR918N devices. # Will generate devices names like BTHR918N_f3. attr sduino longids BTHR918N
dev
, are under development
Der SIGNALduino ist basierend auf einer Idee von "mdorenka" und veröffentlicht im FHEM Forum. Mit der OpenSource-Firmware (SIGNALDuino und SIGNALESP) ist dieser fähig zum Empfangen und Senden verschiedener Protokolle auf 433 und 868 Mhz. Folgende Geräte werden zur Zeit unterstützt: Funk-Schalter
Es ist möglich, mehr als ein Gerät anzuschließen, um beispielsweise besseren Empfang zu erhalten. FHEM wird doppelte Nachrichten herausfiltern. Mehr dazu im dem global Abschnitt unter dem Attribut dupTimeout Hinweis: Dieses Modul erfordert das Device::SerialPort oder Win32::SerialPort Modul. Es kann derzeit nur über USB angeschlossen werden. |
define <name> SIGNALduino <device>
cdc_acm
Kernel_Modul dafür verantwortlich und es wird ein /dev/ttyACM0
oder /dev/ttyUSB0
Gerät angelegt. Wenn deine Distribution kein cdc_acm
Module besitzt, kannst du usbserial nutzen um den SIGNALduino zu betreiben mit folgenden Kommandos:
/dev/ttyUSB0
./dev/ttyACM0@57600
/dev/serial/by-id/usb-1a86_USB2.0-Serial-if00-port0@57600
/dev/ttyACM0@directio
), dann benutzt das Perl Modul nicht Device::SerialPort und FHEM öffnet das Gerät mit einem file io. Dies kann funktionieren, wenn das Betriebssystem die Standardwerte für die seriellen Parameter verwendet. Bsp: einige Linux Distributionen und
OSX.cc1101_bWidth
, kann auf Werte zwischen 58 kHz und 812 kHz eingestellt werden. Große Werte sind störanfällig, ermöglichen jedoch den Empfang von ungenau kalibrierten Sendern. Es wirkt sich auch auf die Übertragung aus. Standard ist 325 kHz.
cc1101_dataRate
, kann auf Werte zwischen 0.0247955 kBaud und 1621.83 kBaud eingestellt werden.
cc1101_deviatn
, kann auf Werte zwischen 1.586914 kHz und 380.859375 kHz eingestellt werden.
cc1101_freq
, legt sowohl die Empfangsfrequenz als auch die Übertragungsfrequenz fest.cc1101_frequency
geholt.
cc1101_patable
, Änderung der PA-Tabelle (Leistungsverstärkung für HF-Senden)
cc1101_rAmpl
, ist die Empfängerverstärkung mit Werten zwischen 24 und 42 dB. Größere Werte erlauben den Empfang schwacher Signale. Der Standardwert ist 42.
cc1101_reg
Es können mehrere Register auf einmal gesetzt werden. Das Register wird über seinen zweistelligen Hexadezimalwert angegeben, gefolgt von einem zweistelligen Wert. Mehrere Register werden via Leerzeichen getrennt angegeben
cc1101_sens
, ist die Entscheidungsgrenze zwischen den Ein- und Aus-Werten und beträgt 4, 8, 12 oder 16 dB. Kleinere Werte erlauben den Empfang von weniger klaren Signalen. Standard ist 4 dB.
avrdude
muss auf dem Host installiert sein. Auf einem Raspberry PI kann dies getan werden mit: sudo apt-get install avrdude
set sduino flash ./FHEM/firmware/SIGNALduino_mega2560.hex
set sduino flash https://github.com/RFD-FHEM/SIGNALDuino/releases/download/3.3.1-RC7/SIGNALDuino_nanocc1101.hex
/dev/ttyACM0
definiert wurde, sollte das Flashen der Firmware automatisch erfolgen. Wenn das nicht gelingt, muss der Bootloader manuell aktiviert werden:set sduino raw SR;R=3;P0=500;P1=-9000;P2=-4000;P3=-2000;D=0302030;
, sendet die Daten im Raw-Modus dreimal wiederholtset sduino raw SM;R=3;P0=500;C=250;D=A4F7FDDE;
, sendet die Daten Manchester codiert mit einem clock von 250µSset sduino raw SC;R=3;SR;P0=5000;SM;P0=500;C=250;D=A4F7FDDE;
, sendet eine kombinierte Nachricht von Raw und Manchester codiert 3 mal wiederholtset sduino raw SN;R=3;D=9A46036AC8D3923EAEB470AB;
, sendet die xFSK - Daten dreimal wiederholtset sduino raw C04
liest den Wert aus der Registeradresse 0x04set sduino raw W041D
schreibt 1D ins Register 0x02set sduino raw W041D#W0604
schreibt 1D ins Register 0x02 und 04 ins Register 0x04
set sduino sendMsg P0#0101#R3#C500
set sduino sendMsg P29#0xF7E#R4
set sduino sendMsg P36#0xF7#R6#Fxxxxxxxxxx
(xxxxxxxxxx = Registerwert des CC1101)
set flash
Befehl auswählbar.
MS=1;MU=1;MC=1;Mred=0
MS;P0=-7871;P2=-1960;P3=578;P4=-3954;D=030323232323434343434323232323234343434323234343234343234343232323432323232323232343234;CP=3;SP=0;R=0;m=0;
avrdude -c arduino -b [BAUDRATE] -P [PORT] -p atmega328p -vv -U flash:w:[HEXFILE] 2>[LOGFILE]
avrdude -c avr109 -b [BAUDRATE] -P [PORT] -p atmega32u4 -vv -D -U flash:w:[HEXFILE] 2>[LOGFILE]
set SIGNALduino flash
als erstes Argument übergebenflash
. Hier sollten Sie angeben, welche Hardware Sie mit dem usbport verbunden haben. Andernfalls kann es zu Fehlfunktionen des Geräts kommen. Wichtig ist auch das Attribut updateChannelFW
# Keine langen IDs verwenden (Default Einstellung): attr sduino longids 0 # Immer lange IDs verwenden: attr sduino longids 1 # Verwende lange IDs für SD_WS07 Devices. # Device Namen sehen z.B. so aus: SD_WS07_TH_3. attr sduino longids SD_WS07
flash
zum Flashen an. Mit dem Attribut kann festgelegt werden, ob nur stabile Versionen ("Latest Release") angezeigt werden oder auch Vorabversionen ("Pre-release") einer neuen Firmware.get availableFirmware
neu geladen werden.
flash
. Hier sollten Sie angeben, welche Hardware Sie mit dem USB-Port verbunden haben. Andernfalls kann es zu Fehlfunktionen des Geräts kommen. dev
markiert sind, befinden sich in Entwicklung.