############################################## # $Id$ # # v3.3.0 (Development release 3.3) # The module is inspired by the FHEMduino project and modified in serval ways for processing the incomming 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. # N. Butzek, S. Butzek, 2014-2015 # S.Butzek 2016 package main; use strict; use warnings; use Time::HiRes qw(gettimeofday); use Data::Dumper qw(Dumper); use Scalar::Util qw(looks_like_number); #use POSIX qw( floor); # can be removed #use Math::Round qw(); use constant { 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, }; sub SIGNALduino_Attr(@); #sub SIGNALduino_Clear($); # wird nicht mehr benoetigt sub SIGNALduino_HandleWriteQueue($); sub SIGNALduino_Parse($$$$@); sub SIGNALduino_Read($); #sub SIGNALduino_ReadAnswer($$$$); # wird nicht mehr benoetigt sub SIGNALduino_Ready($); sub SIGNALduino_Write($$$); sub SIGNALduino_SimpleWrite(@); #my $debug=0; my %gets = ( # Name, Data to send to the SIGNALduino, Regexp for the answer "version" => ["V", 'V\s.*SIGNAL(duino|ESP).*'], "freeram" => ["R", '^[0-9]+'], "raw" => ["", '.*'], "uptime" => ["t", '^[0-9]+' ], "cmds" => ["?", '.*Use one of[ 0-9A-Za-z]+[\r\n]*$' ], "ITParms" => ["ip",'.*'], "ping" => ["P",'^OK$'], "config" => ["CG",'^MS.*MU.*MC.*'], # "ITClock" => ["ic", '\d+'], # "FAParms" => ["fp", '.*' ], # "TCParms" => ["dp", '.*' ], # "HXParms" => ["hp", '.*' ] ); my %sets = ( "raw" => '', "flash" => '', "reset" => 'noArg', "close" => 'noArg', #"disablereceiver" => "", "ITClock" => 'slider,100,20,700', "enableMessagetype" => 'syncedMS,unsyncedMU,manchesterMC', "disableMessagetype" => 'syncedMS,unsyncedMU,manchesterMC', 'sendMsg' => "", ); ## Supported Clients per default my $clientsSIGNALduino = ":IT:" ."CUL_TCM97001:" # ."SIGNALduino_RSL:" ."OREGON:" ."CUL_TX:" # ."SD_AS:" ."Hideki:" ."SD_WS07:" ."SD_WS09:" ."SD_WS:" ."RFXX10REC:" ."Dooya:" ."SOMFY:" # ."SD_UT:" ## BELL 201.2 TXA ."SD_WS_Maverick:" # ."BresserTemeo:" ."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......", # Intertechno Format "2:CUL_TCM97001" => "^s[A-Fa-f0-9]+", # Any hex string beginning with s # "3:SIGNALduino_RSL" => "^r[A-Fa-f0-9]+", # Any hex string beginning with r "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]+", "10:SD_WS07" => "^P7#[A-Fa-f0-9]{6}F[A-Fa-f0-9]{2}", "11:SD_WS09" => "^P9#[A-Fa-f0-9]+", "12:SD_WS" => '^W\d+#.*', "13:RFXX10REC" => '^(20|29)[A-Fa-f0-9]+', "14:Dooya" => '^P16#[A-Fa-f0-9]+', "15:SOMFY" => '^YsA[0-9A-F]+', "16:SD_WS_Maverick" => '^P47#[A-Fa-f0-9]+', # "17:SD_UT" => '^u30#.*', ## BELL 201.2 TXA # "44:BresserTemeo" => '^P44x{0,1}#[A-F0-9]{18}', # Bresser Temeo Trend (3CH Thermo-/Hygro) "X:SIGNALduino_un" => '^[uP]\d+#.*', ); my %ProtocolListSIGNALduino = ( "0" => { name => 'weather1', # Logilink, NC, WS, TCM97001 etc. id => '0', one => [1,-8], zero => [1,-4], sync => [1,-18], clockabs => '500', # not used now format => 'twostate', # not used now preamble => 's', # prepend to converted message postamble => '00', # Append to converted message clientmodule => 'CUL_TCM97001', # not used now #modulematch => '^s[A-Fa-f0-9]+', # not used now length_min => '24', length_max => '40', paddingbits => '8', # pad up to 8 bits, default is 4 }, "1" => { name => 'ConradRSL', # id => '1', one => [2,-1], zero => [1,-2], sync => [1,-11], clockabs => '560', # not used now format => 'twostate', # not used now preamble => 'r', # prepend to converted message postamble => '', # Append to converted message clientmodule => 'SIGNALduino_RSL', # not used now modulematch => '^r[A-Fa-f0-9]+', # not used now length_min => '23', length_max => '24', }, "2" => { name => 'AS', # Self build arduino sensor id => '2', one => [1,-2], zero => [1,-1], sync => [1,-20], clockabs => '500', # not used now format => 'twostate', preamble => 'P2#', # prepend to converted message clientmodule => 'SD_AS', # not used now modulematch => '^P2#.{7,8}', length_min => '32', length_max => '34', # Don't know maximal lenth of a valid message paddingbits => '8', # pad up to 8 bits, default is 4 }, "3" => { name => 'itv1', id => '3', one => [3,-1], zero => [1,-3], #float => [-1,3], # not full supported now later use sync => [1,-31], clockabs => -1, # -1=auto format => 'twostate', # not used now preamble => 'i', clientmodule => 'IT', # not used now modulematch => '^i......', # not used now length_min => '24', #length_max => '800', # Don't know maximal lenth of a valid message }, "4" => { name => 'arctech2', id => '4', #one => [1,-5,1,-1], #zero => [1,-1,1,-5], one => [1,-5], zero => [1,-1], #float => [-1,3], # not full supported now, for later use sync => [1,-14], clockabs => -1, # -1 = auto format => 'twostate', # tristate can't be migrated from bin into hex! preamble => 'i', # Append to converted message postamble => '00', # Append to converted message clientmodule => 'IT', # not used now modulematch => '^i......', # not used now length_min => '32', #length_max => '76', # Don't know maximal lenth of a valid message }, "5" => ## Similar protocol as intertechno, but without sync { name => 'unitec6899', id => '5', one => [3,-1], zero => [1,-3], clockabs => 500, # -1 = auto format => 'twostate', # tristate can't be migrated from bin into hex! preamble => 'p5#', # Append to converted message clientmodule => 'IT', # not used now modulematch => '^i......', # not used now length_min => '24', }, "6" => ## Eurochron Protocol { name => 'weatherID6', id => '6', one => [1,-10], zero => [1,-5], sync => [1,-36], # This special device has no sync clockabs => 220, # -1 = auto format => 'twostate', # tristate can't be migrated from bin into hex! preamble => 'u6#', # Append to converted message clientmodule => 'undef', # not used now #modulematch => '^u......', # not used now length_min => '24', }, "7" => ## weather sensors like EAS800z { name => 'weatherID7', id => '7', one => [1,-4], zero => [1,-2], sync => [1,-8], clockabs => 484, format => 'twostate', preamble => 'P7#', # prepend to converted message clientmodule => 'undef', # not used now modulematch => '^P7#.{6}F.{2}', # not used now length_min => '35', length_max => '40', }, "8" => ## TX3 (ITTX) Protocol { name => 'TX3 Protocol', id => '8', one => [1,-2], zero => [2,-2], #sync => [1,-8], # clockabs => 470, # format => 'pwm', # preamble => 'TX', # prepend to converted message clientmodule => 'ittx', # not used now modulematch => '^TX......', # not used now length_min => '43', length_max => '44', remove_zero => 1, # Removes leading zeros from output }, "9" => ## Funk Wetterstation CTW600 { name => 'CTW 600', id => '9', zero => [3,-2], one => [1,-2], #float => [-1,3], # not full supported now, for later use #sync => [1,-8], # clockabs => 480, # -1 = auto undef=noclock format => 'pwm', # tristate can't be migrated from bin into hex! preamble => 'P9#', # prepend to converted message clientmodule => 'undef', # not used now #modulematch => '^u9#.....', # not used now length_min => '70', length_max => '120', }, "10" => ## Oregon Scientific 2 { name => 'OSV2o3', id => '10', clockrange => [300,520], # min , max format => 'manchester', # tristate can't be migrated from bin into hex! modulematch => '^(3[8-9A-F]|[4-6][0-9A-F]|7[0-8]).*', length_min => '64', length_max => '220', method => \&SIGNALduino_OSV2, # Call to process this message polarity => 'invert', }, "11" => ## Arduino Sensor { name => 'AS', id => '11', clockrange => [380,425], # min , max format => 'manchester', # tristate can't be migrated from bin into hex! preamble => 'P2#', # prepend to converted message #clientmodule => '14_SD_AS', # not used now modulematch => '^P2#.{7,8}', length_min => '52', length_max => '56', method => \&SIGNALduino_AS # Call to process this message }, "12" => ## hideki { name => 'Hideki protocol', id => '12', clockrange => [420,510], # min, max better for Bresser Sensors, OK for hideki/Hideki/TFA too format => 'manchester', preamble => 'P12#', # prepend to converted message #clientmodule => '14_hideki', # not used now modulematch => '^P12#75.+', # not used now length_min => '72', length_max => '104', method => \&SIGNALduino_Hideki, # Call to process this message polarity => 'invert', }, "13" => ## FA21RF { name => '21RF', id => '13', one => [1,-2], zero => [1,-4], sync => [10,-1], clockabs => 800, format => 'twostate', preamble => 'u13#', # prepend to converted message #clientmodule => '', # not used now #modulematch => '', # not used now length_min => '20', length_max => '40', }, "14" => ## Heidemann HX { name => 'Heidemann HX', id => '14', one => [1,-2], zero => [1,-1], #float => [-1,3], # not full supported now, for later use sync => [1,-14], # clockabs => 350, format => 'twostate', preamble => 'u14#', # prepend to converted message #clientmodule => '', # not used now #modulematch => '', # not used now length_min => '10', length_max => '20', }, "15" => ## TCM234759 { name => 'TCM Bell', id => '15', one => [1,-1], zero => [1,-2], sync => [1,-45], # clockabs => 700, format => 'twostate', preamble => 'u15#', # prepend to converted message #clientmodule => '', # not used now #modulematch => '', # not used now length_min => '10', length_max => '20', #method => \&SIGNALduino_Cresta # Call to process this message }, "16" => # Rohrmotor24 und andere Funk Rolladen / Markisen Motoren { name => 'Dooya shutter', id => '16', one => [2,-1], zero => [1,-3], start => [17,-5], clockabs => 280, format => 'twostate', preamble => 'P16#', # prepend to converted message #clientmodule => '', # not used now #modulematch => '', # not used now length_min => '39', length_max => '40', }, "17" => { name => 'arctech', id => '17', one => [1,-5,1,-1], zero => [1,-1,1,-5], #one => [1,-5], #zero => [1,-1], sync => [1,-10], float => [1,-1,1,-1], clockabs => -1, # -1 = auto format => 'twostate', # tristate can't be migrated from bin into hex! preamble => 'i', # Append to converted message postamble => '00', # Append to converted message clientmodule => 'IT', # not used now modulematch => '^i......', # not used now length_min => '32', #length_max => '76', # Don't know maximal lenth of a valid message postDemodulation => \&SIGNALduino_bit2Arctec, }, "18" => ## Oregon Scientific v1 { name => 'OSV1', id => '18', clockrange => [1550,1650], # min , max format => 'manchester', # tristate can't be migrated from bin into hex! #preamble => '', # prepend to converted message #clientmodule => 'to be written', # not used now modulematch => '^(3[8-9A-F]|[4-6][0-9A-F]|7[0-8]).*', length_min => '8', length_max => '8', method => \&SIGNALduino_OSV1 # Call to process this message }, #"19" => # nothing knowing about this 2015-09-28 01:25:40-MS;P0=-8916;P1=-19904;P2=390;P3=-535;P4=-1020;P5=12846;P6=1371;D=2120232323232324242423232323232323232320239;CP=2;SP=1; # # { # name => 'unknown19', # id => '19', # one => [1,-2], # zero => [1,-1], # sync => [1,-50,1,-22], # clockabs => 395, # format => 'twostate', # preamble => 'u19#', # prepend to converted message # #clientmodule => '', # not used now # #modulematch => '', # not used now # length_min => '16', # length_max => '32', # }, "20" => #Livolo { name => 'livolo', id => '20', one => [3], zero => [1], start => [5], clockabs => 110, #can be 90-140 format => 'twostate', preamble => 'u20#', # prepend to converted message #clientmodule => '', # not used now #modulematch => '', # not used now length_min => '16', filterfunc => 'SIGNALduino_filterSign', }, "21" => #Einhell Garagentor { name => 'einhell garagedoor', id => '21', one => [-3,1], zero => [-1,3], #sync => [-50,1], start => [-50,1], clockabs => 400, #ca 400us format => 'twostate', preamble => 'u21#', # prepend to converted message #clientmodule => '', # not used now #modulematch => '', # not used now length_min => '32', length_max => '32', paddingbits => '1', # This will disable padding }, "22" => #TX-EZ6 / Meteo { name => 'TX-EZ6', id => '22', one => [1,-8], zero => [1,-3], sync => [1,16], clockabs => 500, #ca 400us format => 'twostate', preamble => 'u22#', # prepend to converted message #clientmodule => '', # not used now #modulematch => '', # not used now length_min => '40', #length_max => '', # must be tested }, "23" => # Pearl Sensor { name => 'perl unknown', id => '23', one => [1,-6], zero => [1,-1], sync => [1,-50], clockabs => 200, #ca 200us format => 'twostate', preamble => 'u23#', # prepend to converted message #clientmodule => '', # not used now #modulematch => '', # not used now length_min => '36', length_max => '44', }, "24" => # visivon { name => 'visivon remote', id => '24', one => [3,-2], zero => [1,-5], #one => [3,-2], #zero => [1,-1], start => [30,-5], clockabs => 150, #ca 150us format => 'twostate', preamble => 'u24#', # prepend to converted message #clientmodule => '', # not used now #modulematch => '', # not used now length_min => '54', length_max => '58', }, "25" => # LES remote for led lamp { name => 'les led remote', id => '25', one => [-2,1], zero => [-1,2], sync => [-46,1], # this is a end marker, but we use this as a start marker clockabs => 350, #ca 350us format => 'twostate', preamble => 'u25#', # prepend to converted message #clientmodule => '', # not used now #modulematch => '', # not used now length_min => '24', length_max => '50', # message has only 24 bit, but we get more than one message, calculation has to be corrected }, "26" => # some remote code send by flamingo style remote controls { name => 'remote26', id => '26', one => [1,-3], zero => [3,-1], # sync => [1,-6], # Message is not provided as MS, due to small fact start => [1,-6], # Message is not provided as MS, due to small fact clockabs => 380, #ca 380 format => 'twostate', preamble => 'u26#', # prepend to converted message #clientmodule => '', # not used now #modulematch => '', # not used now length_min => '24', length_max => '24', # message has only 24 bit, but we get more than one message, calculation has to be corrected }, "27" => # some remote code, send by flamingo style remote controls { name => 'remote27', id => '27', one => [1,-2], zero => [2,-1], start => [6,-15], # Message is not provided as MS, worakround is start clockabs => 480, #ca 480 format => 'twostate', preamble => 'u27#', # prepend to converted message #clientmodule => '', # not used now #modulematch => '', # not used now length_min => '24', length_max => '24', }, "28" => # some remote code, send by aldi IC Ledspots { name => 'IC Ledspot', id => '28', one => [1,-1], zero => [1,-2], start => [4,-5], clockabs => 600, #ca 600 format => 'twostate', preamble => 'u28#', # prepend to converted message #clientmodule => '', # not used now #modulematch => '', # not used now length_min => '8', length_max => '8', }, "29" => # { name => 'HT12e remote', id => '29', one => [-2,1], zero => [-1,2], #float => [1,-1], start => [-38,1], # Message is not provided as MS, worakround is start clockabs => 220, #ca 220 format => 'tristate', # there is a pause puls between words preamble => 'u29#', # prepend to converted message #clientmodule => '', # not used now #modulematch => '', # not used now length_min => '10', length_max => '12', # message has only 10 bit but is paddet to 12 }, "30" => # a unitec remote door reed switch { name => 'unitec47031', id => '30', one => [-1,2], zero => [-2,1], start => [-33,1], # Message is not provided as MS, worakround is start clockabs => 300, # ca 300 us format => 'twostate', # there is a pause puls between words preamble => 'u30#', # prepend to converted message clientmodule => 'SD_UT', # not used now modulematch => '^u30', # not used now length_min => '12', length_max => '12', # message has only 10 bit but is paddet to 12 }, "31" => # Pollin Isotronic { name => 'pollin isotronic', id => '31', one => [-1,2], zero => [-2,1], start => [1], clockabs => 600, format => 'twostate', preamble => 'u31#', # prepend to converted message #clientmodule => '', # not used now #modulematch => '', # not used now length_min => '20', length_max => '20', }, "32" => #FreeTec PE-6946 -> http://www.free-tec.de/Funkklingel-mit-Voic-PE-6946-919.shtml { name => 'freetec 6946', id => '32', one => [4,-2], zero => [1,-5], sync => [1,-49], clockabs => 140, #ca 140us format => 'twostate', preamble => 'u32#', # prepend to converted message #clientmodule => '', # not used now #modulematch => '', # not used now length_min => '24', length_max => '24', }, "33" => #Thermo-/Hygrosensor S014 { name => 'weather33', # id => '33', one => [1,-8], zero => [1,-4], sync => [1,-15], clockabs => '500', # not used now format => 'twostate', # not used now preamble => 'W33#', # prepend to converted message postamble => '', # Append to converted message clientmodule => '', # not used now #modulematch => '', # not used now length_min => '42', length_max => '44', }, "34" => { name => 'unknown34', id => '34', one => [2,-1], zero => [1,-2], start => [3,-3,3,-3,3,-3,3,-3], clockabs => '240', format => 'twostate', # not used now preamble => 'u34#', # prepend to converted message postamble => '', # Append to converted message clientmodule => '', # not used now #modulematch => '', # not used now length_min => '40', length_max => '40', }, "35" => { name => 'socket35', id => '35', one => [1,-4], zero => [4,-1], sync => [1,-19], clockabs => '280', format => 'twostate', # not used now preamble => 'u35#', # prepend to converted message postamble => '', # Append to converted message clientmodule => '', # not used now #modulematch => '', # not used now length_min => '28', length_max => '32', }, "36" => { name => 'socket36', id => '36', one => [1,-3], zero => [1,-1], start => [20,-20], clockabs => '500', format => 'twostate', # not used now preamble => 'u36#', # prepend to converted message postamble => '', # Append to converted message clientmodule => '', # not used now #modulematch => '', # not used now length_min => '24', length_max => '24', }, "37" => { name => 'weather37', id => '37', one => [2,-1], zero => [1,-2], start => [3,-3,3,-3], clockabs => '230', format => 'twostate', # not used now preamble => 'W37#', # prepend to converted message postamble => '', # Append to converted message clientmodule => '', # not used now #modulematch => '', # not used now length_min => '40', length_max => '44', }, "38" => { name => 'weather38', id => '38', one => [1,-10], zero => [1,-5], sync => [1,-25], clockabs => '360', # not used now format => 'twostate', # not used now preamble => 's', # prepend to converted message postamble => '00', # Append to converted message clientmodule => 'CUL_TCM97001', # not used now #modulematch => '^s[A-Fa-f0-9]+', # not used now length_min => '32', length_max => '32', paddingbits => '8', }, "39" => ## X10 Protocol { name => 'X10 Protocol', id => '39', one => [1,-3], zero => [1,-1], start => [16,-4], clockabs => 650, format => 'twostate', preamble => '', # prepend to converted message clientmodule => 'RFXX10REC', # not used now #modulematch => '^TX......', # not used now length_min => '38', length_max => '44', paddingbits => '8', postDemodulation => \&SIGNALduino_lengtnPrefix, filterfunc => 'SIGNALduino_compPattern', }, "40" => ## Romotec { name => 'romotec', id => '40', one => [3,-2], zero => [1,-3], start => [1,-2], clockabs => 250, preamble => 'u40#', # prepend to converted message clientmodule => '', # not used now #modulematch => '', # not used now length_min => '10', }, "41" => ## Elro (Smartwares) Doorbell DB200 { name => 'elro doorbell', id => '41', zero => [1,-3], one => [3,-1], sync => [1,-15], clockabs => 450, preamble => 'u41#', # prepend to converted message clientmodule => '', # not used now #modulematch => '', # not used now length_min => '20', }, "42" => ## MKT Multi Kon Trade { name => 'MKT motionsensor', id => '42', zero => [1,-3], one => [3,-1], start => [-28], clockabs => 550, preamble => 'u42#', # prepend to converted message clientmodule => '', # not used now #modulematch => '', length_min => '24', }, "43" => ## Somfy RTS { name => 'Somfy RTS', id => '43', clockrange => [610,670], # min , max format => 'manchester', preamble => 'Ys', #clientmodule => '', # not used now modulematch => '^YsA[0-9A-F]{13}', length_min => '56', length_max => '56', method => \&SIGNALduino_SomfyRTS, # Call to process this message msgIntro => 'SR;P0=-2560;P1=2560;P3=-640;D=10101010101010113;', #msgOutro => 'SR;P0=-30415;D=0;', }, "44" => ## Bresser Temeo Trend { name => 'BresserTemeo', id => '44', clockabs => 500, zero => [4,-4], one => [4,-8], start => [8,-8], preamble => 'P44#', clientmodule => 'BresserTemeo', length_min => '64', length_max => '72', }, "51" => ## Bresser Temeo Trend { name => 'BresserTemeo', id => '44x', clockabs => 500, zero => [4,-4], one => [4,-8], start => [8,-12], preamble => 'P44x#', clientmodule => 'BresserTemeo', length_min => '64', length_max => '72', }, "45" => { name => 'revolt', id => '45', one => [3,-1], zero => [1,-3], #float => [-1,3], # not full supported now later use sync => [1,-24], clockabs => -1, # -1=auto format => 'twostate', # not used now preamble => 'i', clientmodule => 'IT', # not used now modulematch => '^i......', # not used now length_min => '24', }, "46" => { name => 'EKX1BE', id => '46', one => [1,-8], zero => [8,-1], clockabs => 250, # -1=auto format => 'twostate', # not used now preamble => 'u46#', clientmodule => '', # not used now #modulematch => '', # not used now length_min => '16', length_max => '18', }, "47" => ## maverick { name => 'Maverick protocol', id => '47', clockrange => [220,260], format => 'manchester', preamble => 'P47#', # prepend to converted message #clientmodule => '', # not used now modulematch => '^P47#.*', # not used now length_min => '100', length_max => '108', method => \&SIGNALduino_Maverick # Call to process this message }, "48" => ## Joker Dostmann TFA { name => 'TFA Dostmann', id => '48', clockabs => 250, # In real it is 500 but this leads to unprceise demodulation one => [-4,6], zero => [-4,2], start => [-6,2], format => 'twostate', preamble => 'U48#', # prepend to converted message #clientmodule => '', # not used now modulematch => '^U48#.*', # not used now length_min => '47', length_max => '48', }, "49" => ## quigg / Aldi gt_9000 { name => 'quigg_gt9000', id => '49', clockabs => 400, one => [2,-1], zero => [1,-3], start => [-15,2,-1], format => 'twostate', preamble => 'U49#', # prepend to converted message #clientmodule => '', # not used now modulematch => '^U49#.*', # not used now length_min => '22', length_max => '28', }, "50" => ## Opus XT300 { name => 'optus_XT300', id => '50', clockabs => 500, zero => [3,-2], one => [1,-2], # start => [1,-25], # Wenn das startsignal empfangen wird, fehlt das 1 bit format => 'twostate', preamble => 'W50#', # prepend to converted message #clientmodule => '', # not used now modulematch => '^W50#.*', # not used now length_min => '47', length_max => '48', }, ); sub SIGNALduino_Initialize($) { my ($hash) = @_; require "$attr{global}{modpath}/FHEM/DevIo.pm"; # Provider $hash->{ReadFn} = "SIGNALduino_Read"; $hash->{WriteFn} = "SIGNALduino_Write"; $hash->{ReadyFn} = "SIGNALduino_Ready"; # Normal devices $hash->{DefFn} = "SIGNALduino_Define"; $hash->{FingerprintFn} = "SIGNALduino_FingerprintFn"; $hash->{UndefFn} = "SIGNALduino_Undef"; $hash->{GetFn} = "SIGNALduino_Get"; $hash->{SetFn} = "SIGNALduino_Set"; $hash->{AttrFn} = "SIGNALduino_Attr"; $hash->{AttrList} = "Clients MatchList do_not_notify:1,0 dummy:1,0" ." hexFile" ." initCommands" ." flashCommand" ." hardware:nano328,uno,promini328" ." debug:0,1" ." longids" ." minsecs" ." whitelist_IDs" ." WS09_WSModel:undef,WH1080,CTW600" ." WS09_CRCAUS:0,1" ." $readingFnAttributes"; $hash->{ShutdownFn} = "SIGNALduino_Shutdown"; } sub SIGNALduino_FingerprintFn($$) { my ($name, $msg) = @_; # 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 ($name, $msg); } ##################################### sub SIGNALduino_Define($$) { my ($hash, $def) = @_; my @a = split("[ \t][ \t]*", $def); if(@a != 3) { my $msg = "wrong syntax: define SIGNALduino {none | devicename[\@baudrate] | devicename\@directio | hostname:port}"; Log3 undef, 2, $msg; return $msg; } DevIo_CloseDev($hash); my $name = $a[0]; if (!exists &round) { Log3 $name, 1, "$name: Signalduino can't be activated (sub round not found). Please update Fhem via update command"; return undef; } my $dev = $a[2]; #Debug "dev: $dev" if ($debug); #my $hardware=AttrVal($name,"hardware","nano328"); #Debug "hardware: $hardware" if ($debug); if($dev eq "none") { Log3 $name, 1, "$name: device is none, commands will be echoed only"; $attr{$name}{dummy} = 1; #return undef; } if ($dev ne "none" && $dev =~ m/[a-zA-Z]/ && $dev !~ m/\@/) { # bei einer IP wird kein \@57600 angehaengt $dev .= "\@57600"; } #$hash->{CMDS} = ""; $hash->{Clients} = $clientsSIGNALduino; $hash->{MatchList} = \%matchListSIGNALduino; #if( !defined( $attr{$name}{hardware} ) ) { # $attr{$name}{hardware} = "nano328"; #} if( !defined( $attr{$name}{flashCommand} ) ) { # $attr{$name}{flashCommand} = "avrdude -p atmega328P -c arduino -P [PORT] -D -U flash:w:[HEXFILE] 2>[LOGFILE]" $attr{$name}{flashCommand} = "avrdude -c arduino -b 57600 -P [PORT] -p atmega328p -vv -U flash:w:[HEXFILE] 2>[LOGFILE]" } $hash->{DeviceName} = $dev; my $ret=undef; my $whitelistIDs = AttrVal($name,"whitelist_IDs",""); SIGNALduino_IdList($hash ,$name, $whitelistIDs); if($dev ne "none") { $ret = DevIo_OpenDev($hash, 0, "SIGNALduino_DoInit", 'SIGNALduino_Connect'); } else { $hash->{DevState} = 'initialized'; readingsSingleUpdate($hash, "state", "opened", 1); } $hash->{DMSG}="nothing"; $hash->{TIME}=time(); Log3 $name, 3, "$name: Firmwareversion: ".$hash->{READINGS}{version}{VAL} if ($hash->{READINGS}{version}{VAL}); return $ret; } ############################### sub SIGNALduino_Connect($$) { my ($hash, $err) = @_; # damit wird die err-msg nur einmal ausgegeben if (!defined($hash->{disConnFlag}) && $err) { Log3($hash, 3, "SIGNALduino $hash->{NAME}: ${err}"); $hash->{disConnFlag} = 1; } } ##################################### sub SIGNALduino_Undef($$) { my ($hash, $arg) = @_; my $name = $hash->{NAME}; foreach my $d (sort keys %defs) { if(defined($defs{$d}) && defined($defs{$d}{IODev}) && $defs{$d}{IODev} == $hash) { my $lev = ($reread_active ? 4 : 2); Log3 $name, $lev, "$name: deleting port for $d"; delete $defs{$d}{IODev}; } } SIGNALduino_Shutdown($hash); DevIo_CloseDev($hash); RemoveInternalTimer($hash); return undef; } ##################################### sub SIGNALduino_Shutdown($) { my ($hash) = @_; SIGNALduino_SimpleWrite($hash, "XQ"); # Switch reception off, it may hang up the SIGNALduino return undef; } ##################################### #$hash,$name,"sendmsg","P17;R6#".substr($arg,2) sub SIGNALduino_Set($@) { my ($hash, @a) = @_; return "\"set SIGNALduino\" needs at least one parameter" if(@a < 2); if (!defined($sets{$a[1]})) { my $arguments = ' '; foreach my $arg (sort keys %sets) { $arguments.= $arg . ($sets{$arg} ? (':' . $sets{$arg}) : '') . ' '; } #Log3 $hash, 3, "set arg = $arguments"; return "Unknown argument $a[1], choose one of " . $arguments; } my $name = shift @a; my $cmd = shift @a; my $arg = join(" ", @a); return "$name is not active, may firmware is not suppoted, please flash or reset" if ($cmd ne 'reset' && $cmd ne 'flash' && exists($hash->{DevState}) && $hash->{DevState} ne 'initialized'); if($cmd eq "raw") { Log3 $name, 4, "set $name $cmd $arg"; #SIGNALduino_SimpleWrite($hash, $arg); SIGNALduino_AddSendQueue($hash,$arg); } elsif( $cmd eq "flash" ) { my @args = split(' ', $arg); my $log = ""; my $hexFile = ""; my @deviceName = split('@', $hash->{DeviceName}); my $port = $deviceName[0]; my $hardware=AttrVal($name,"hardware","nano328"); my $defaultHexFile = "./FHEM/firmware/$hash->{TYPE}_$hardware.hex"; my $logFile = AttrVal("global", "logdir", "./log/") . "$hash->{TYPE}-Flash.log"; if(!$arg || $args[0] !~ m/^(\w|\/|.)+$/) { $hexFile = AttrVal($name, "hexFile", ""); if ($hexFile eq "") { $hexFile = $defaultHexFile; } } else { $hexFile = $args[0]; } return "Usage: set $name flash [filename]\n\nor use the hexFile attribute" if($hexFile !~ m/^(\w|\/|.)+$/); $log .= "flashing Arduino $name\n"; $log .= "hex file: $hexFile\n"; $log .= "port: $port\n"; $log .= "log file: $logFile\n"; my $flashCommand = AttrVal($name, "flashCommand", ""); if($flashCommand ne "") { if (-e $logFile) { unlink $logFile; } DevIo_CloseDev($hash); $hash->{STATE} = "disconnected"; $log .= "$name closed\n"; my $avrdude = $flashCommand; $avrdude =~ s/\Q[PORT]\E/$port/g; $avrdude =~ s/\Q[HEXFILE]\E/$hexFile/g; $avrdude =~ s/\Q[LOGFILE]\E/$logFile/g; $log .= "command: $avrdude\n\n"; `$avrdude`; local $/=undef; if (-e $logFile) { open FILE, $logFile; my $logText = ; close FILE; $log .= "--- AVRDUDE ---------------------------------------------------------------------------------\n"; $log .= $logText; $log .= "--- AVRDUDE ---------------------------------------------------------------------------------\n\n"; } else { $log .= "WARNING: avrdude created no log file\n\n"; } } else { $log .= "\n\nNo flashCommand found. Please define this attribute.\n\n"; } DevIo_OpenDev($hash, 0, "SIGNALduino_DoInit", 'SIGNALduino_Connect'); $log .= "$name opened\n"; return $log; } elsif ($cmd =~ m/reset/i) { delete($hash->{initResetFlag}) if defined($hash->{initResetFlag}); return SIGNALduino_ResetDevice($hash); } elsif( $cmd eq "close" ) { $hash->{DevState} = 'closed'; return SIGNALduino_CloseDevice($hash); } elsif( $cmd eq "ITClock" ) { Log3 $name, 4, "set $name $cmd $arg"; my $clock = shift @a; $clock=250 if ($clock eq "" ); return "argument $arg is not numeric" if($clock !~ /^\d+$/); Log3 $name, 3, "$name: Setting ITClock to $clock (sending $arg)"; $arg="ic$clock"; #SIGNALduino_SimpleWrite($hash, $arg); SIGNALduino_AddSendQueue($hash,$arg); $hash->{$cmd}=$clock; } elsif( $cmd eq "disableMessagetype" ) { my $argm = 'CD' . substr($arg,-1,1); #SIGNALduino_SimpleWrite($hash, $argm); SIGNALduino_AddSendQueue($hash,$argm); Log3 $name, 4, "set $name $cmd $arg $argm";; } elsif( $cmd eq "enableMessagetype" ) { my $argm = 'CE' . substr($arg,-1,1); #SIGNALduino_SimpleWrite($hash, $argm); SIGNALduino_AddSendQueue($hash,$argm); Log3 $name, 4, "set $name $cmd $arg $argm"; } elsif( $cmd eq "sendMsg" ) { my ($protocol,$data,$repeats,$clock) = split("#",$arg); $protocol=~ s/[Pp](\d+)/$1/; # extract protocol num $repeats=~ s/[rR](\d+)/$1/; # extract repeat num $clock=~ s/[Cc](\d+)/$1/ if (defined($clock)); # extract ITClock num $repeats=1 if (!defined($repeats)); return "$name: sendmsg, unknown protocol: $protocol" if (!exists($ProtocolListSIGNALduino{$protocol})); #print ("data = $data \n"); #print ("protocol = $protocol \n"); #print ("repeats = $repeats \n"); my %signalHash; my %patternHash; my $pattern=""; my $cnt=0; my $sendData; if ($ProtocolListSIGNALduino{$protocol}{format} eq 'manchester') { #$clock = (map { $clock += $_ } @{$ProtocolListSIGNALduino{$protocol}{clockrange}}) / 2 if (!defined($clock)); $clock += $_ for(@{$ProtocolListSIGNALduino{$protocol}{clockrange}}); $clock = round($clock/2,0); if ($protocol == 43) { #$data =~ tr/0123456789ABCDEF/FEDCBA9876543210/; } my $intro = ""; my $outro = ""; $intro = $ProtocolListSIGNALduino{$protocol}{msgIntro} if ($ProtocolListSIGNALduino{$protocol}{msgIntro}); $outro = $ProtocolListSIGNALduino{$protocol}{msgOutro}.";" if ($ProtocolListSIGNALduino{$protocol}{msgOutro}); if ($intro ne "" || $outro ne "") { $intro = "SC;R=$repeats;" . $intro; $repeats = 0; } $sendData = $intro . "SM;" . ($repeats > 0 ? "R=$repeats;" : "") . "C=$clock;D=$data;" . $outro; # SM;R=2;C=400;D=AFAFAF; Log3 $name, 5, "$name: sendmsg Preparing manchester protocol=$protocol, repeats=$repeats, clock=$clock data=$data"; } else { if ($protocol == 3) { $data = SIGNALduino_ITV1_tristateToBit($data); Log3 $name, 5, "$name: 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=$ProtocolListSIGNALduino{$protocol}{clockabs} > 1 ?$ProtocolListSIGNALduino{$protocol}{clockabs}:$hash->{ITClock}; } Log3 $name, 5, "$name: sendmsg Preparing rawsend command for protocol=$protocol, repeats=$repeats, clock=$clock bits=$data"; foreach my $item (qw(sync start one zero float)) { #print ("item= $item \n"); next if (!exists($ProtocolListSIGNALduino{$protocol}{$item})); foreach my $p (@{$ProtocolListSIGNALduino{$protocol}{$item}}) { #print (" p = $p \n"); if (!exists($patternHash{$p})) { $patternHash{$p}=$cnt; $pattern.="P".$patternHash{$p}."=".$p*$clock.";"; $cnt++; } $signalHash{$item}.=$patternHash{$p}; #print (" signalHash{$item} = $signalHash{$item} \n"); } } my @bits = split("", $data); my %bitconv = (1=>"one", 0=>"zero", 'D'=> "float"); my $SignalData="D="; $SignalData.=$signalHash{sync} if (exists($signalHash{sync})); $SignalData.=$signalHash{start} if (exists($signalHash{start})); foreach my $bit (@bits) { next if (!exists($bitconv{$bit})); #Log3 $name, 5, "encoding $bit"; $SignalData.=$signalHash{$bitconv{$bit}}; ## Add the signal to our data string } $sendData = "SR;R=$repeats;$pattern$SignalData;"; } #SIGNALduino_SimpleWrite($hash, $sendData); SIGNALduino_AddSendQueue($hash,$sendData); Log3 $name, 4, "$name/set: sending via SendMsg: $sendData"; } else { Log3 $name, 5, "$name/set: set $name $cmd $arg"; #SIGNALduino_SimpleWrite($hash, $arg); return "Unknown argument $cmd, choose one of ". ReadingsVal($name,'cmd',' help me'); } return undef; } ##################################### sub SIGNALduino_Get($@) { my ($hash, @a) = @_; my $type = $hash->{TYPE}; my $name = $hash->{NAME}; return "$name is not active, may firmware is not suppoted, please flash or reset" if (exists($hash->{DevState}) && $hash->{DevState} ne 'initialized'); #my $name = $a[0]; Log3 $name, 5, "\"get $type\" needs at least one parameter" if(@a < 2); return "\"get $type\" needs at least one parameter" if(@a < 2); if(!defined($gets{$a[1]})) { my @cList = map { $_ =~ m/^(file|raw)$/ ? $_ : "$_:noArg" } sort keys %gets; return "Unknown argument $a[1], choose one of " . join(" ", @cList); } my $arg = ($a[2] ? $a[2] : ""); return "no command to send, get aborted." if (length($gets{$a[1]}[0]) == 0 && length($arg) == 0); my ($msg, $err); if (IsDummy($name)) { if (($arg !~ m/;/) && ($arg !~ m/=/)) { if ($arg =~ m/^V\d\.\d\..*/) { Log3 $name, 4, "$name/msg get version: $arg"; $hash->{version} = "V " . substr($arg,1); return ""; } else { Log3 $name, 4, "$name/msg get dispatch: $arg"; Dispatch($hash, $arg, undef); return ""; } } if ($arg =~ /^M[CSU];.*/) { $arg="\002$arg\003"; ## Add start end end marker if not already there Log3 $name, 5, "$name/msg adding start and endmarker to message"; } if ($arg =~ /^\002.*\003$/) { Log3 $name, 4, "$name/msg get raw: $arg"; return SIGNALduino_Parse($hash, $hash, $hash->{NAME}, $arg); } } return "No $a[1] for dummies" if(IsDummy($name)); Log3 $name, 5, "$name: command for gets: " . $gets{$a[1]}[0] . " " . $arg; if ($a[1] eq "raw") { # Dirty hack to check and modify direct communication from logical modules with hardware if ($arg =~ /^is.*/ && length($arg) == 34) { # Arctec protocol Log3 $name, 5, "$name: calling set :sendmsg P17;R6#".substr($arg,2); SIGNALduino_Set($hash,$name,"sendMsg","P17#",substr($arg,2),"#R6"); return "$a[0] $a[1] => $arg"; } } #SIGNALduino_SimpleWrite($hash, $gets{$a[1]}[0] . $arg); SIGNALduino_AddSendQueue($hash, $gets{$a[1]}[0] . $arg); $hash->{getcmd}->{cmd}=$a[1]; $hash->{getcmd}->{asyncOut}=$hash->{CL}; $hash->{getcmd}->{timenow}=time(); return undef; # We will exit here, and give an output only, if asny output is supported. If this is not supported, only the readings are updated } sub SIGNALduino_parseResponse($$$) { my $hash = shift; my $cmd = shift; my $msg = shift; my $name=$hash->{NAME}; $msg =~ s/[\r\n]//g; if($cmd eq "cmds") { # nice it up $msg =~ s/$name cmds =>//g; $msg =~ s/.*Use one of//g; } elsif($cmd eq "uptime") { # decode it #$msg = hex($msg); # /125; only for col or coc $msg = sprintf("%d %02d:%02d:%02d", $msg/86400, ($msg%86400)/3600, ($msg%3600)/60, $msg%60); } return $msg; } ##################################### sub SIGNALduino_ResetDevice($) { my ($hash) = @_; my $name = $hash->{NAME}; Log3 $hash, 3, "$name reset"; DevIo_CloseDev($hash); my $ret = DevIo_OpenDev($hash, 0, "SIGNALduino_DoInit", 'SIGNALduino_Connect'); return $ret; } ##################################### sub SIGNALduino_CloseDevice($) { my ($hash) = @_; my $name = $hash->{NAME}; Log3 $hash, 2, "$name closed"; RemoveInternalTimer($hash); DevIo_CloseDev($hash); readingsSingleUpdate($hash, "state", "closed", 1); return undef; } ##################################### 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 Log3 $hash, 1, "$name/define: ".$hash->{DEF}; delete($hash->{disConnFlag}) if defined($hash->{disConnFlag}); RemoveInternalTimer("HandleWriteQueue:$name"); @{$hash->{QUEUE}} = (); if (($hash->{DEF} !~ m/\@DirectIO/) and ($hash->{DEF} !~ m/none/) ) { Log3 $hash, 1, "$name/init: ".$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; #return undef; } # Disable receiver sub SIGNALduino_SimpleWrite_XQ($) { my ($hash) = @_; my $name = $hash->{NAME}; Log3 $hash, 3, "$name/init: disable receiver (XQ)"; SIGNALduino_SimpleWrite($hash, "XQ"); } sub SIGNALduino_StartInit($) { my ($hash) = @_; my $name = $hash->{NAME}; $hash->{version} = undef; Log3 $name,3 , "$name/init: 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})) { Log3 $name,2 , "$name/init retry count reached. Reset"; $hash->{initResetFlag} = 1; SIGNALduino_ResetDevice($hash); } else { Log3 $name,2 , "$name/init retry count reached. Closed"; SIGNALduino_CloseDevice($hash); } return; } else { $hash->{getcmd}->{cmd} = "version"; SIGNALduino_SimpleWrite($hash, "V"); $hash->{DevState} = 'waitInit'; RemoveInternalTimer($hash); InternalTimer(gettimeofday() + SDUINO_CMD_TIMEOUT, "SIGNALduino_CheckCmdResp", $hash, 0); } } #################### 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: Not an SIGNALduino device, setting attribute dummy=1 got for V: $ver"; Log3 $hash, 1, $msg; readingsSingleUpdate($hash, "state", "no SIGNALduino found", 1); $hash->{DevState} = 'INACTIVE'; SIGNALduino_CloseDevice($hash); } elsif($ver =~ m/^V 3\.1\./) { $msg = "$name: Version of your arduino is not compatible, pleas flash new firmware. (device closed) Got for V: $ver"; readingsSingleUpdate($hash, "state", "unsupported firmware found", 1); Log3 $hash, 1, $msg; $hash->{DevState} = 'INACTIVE'; SIGNALduino_CloseDevice($hash); } else { readingsSingleUpdate($hash, "state", "opened", 1); Log3 $name, 2, "$name: initialized"; $hash->{DevState} = 'initialized'; delete($hash->{initResetFlag}) if defined($hash->{initResetFlag}); SIGNALduino_SimpleWrite($hash, "XE"); # Enable receiver Log3 $hash, 3, "$name/init: 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); } } else { delete($hash->{getcmd}); $hash->{initretry} ++; #InternalTimer(gettimeofday()+1, "SIGNALduino_StartInit", $hash, 0); SIGNALduino_StartInit($hash); } } ##################################### # Check if the 1% limit is reached and trigger notifies sub SIGNALduino_XmitLimitCheck($$) { my ($hash,$fn) = @_; return if ($fn !~ m/^(is|SR).*/); 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}; Log3 $name, 2, "SIGNALduino TRANSMIT LIMIT EXCEEDED"; DoTrigger($name, "TRANSMIT LIMIT EXCEEDED"); } else { push(@b, $now); } $hash->{XMIT_TIME} = \@b; $hash->{NR_CMD_LAST_H} = int(@b); } ##################################### ## API to logical modules: Provide as Hash of IO Device, type of function ; command to call ; message to send sub SIGNALduino_Write($$$) { my ($hash,$fn,$msg) = @_; my $name = $hash->{NAME}; $fn="RAW" if $fn eq ""; Log3 $name, 5, "$name/write: adding to queue $fn $msg"; #SIGNALduino_SimpleWrite($hash, $bstring); SIGNALduino_Set($hash,$name,$fn,$msg); #SIGNALduino_AddSendQueue($hash,$bstring); } sub SIGNALduino_AddSendQueue($$) { my ($hash, $msg) = @_; my $name = $hash->{NAME}; #Log3 $hash, 3,"AddSendQueue: " . $hash->{NAME} . ": $msg"; push(@{$hash->{QUEUE}}, $msg); #Log3 $hash , 5, Dumper($hash->{QUEUE}); InternalTimer(gettimeofday() + 0.1, "SIGNALduino_HandleWriteQueue", "HandleWriteQueue:$name", 1) if (@{$hash->{QUEUE}} == 1); } sub SIGNALduino_SendFromQueue($$) { my ($hash, $msg) = @_; my $name = $hash->{NAME}; if($msg ne "") { SIGNALduino_XmitLimitCheck($hash,$msg); #DevIo_SimpleWrite($hash, $msg,2); SIGNALduino_SimpleWrite($hash,$msg); } ############## # 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 InternalTimer(gettimeofday() + SDUINO_WRITEQUEUE_NEXT, "SIGNALduino_HandleWriteQueue", "HandleWriteQueue:$name", 1); } #################################### sub SIGNALduino_HandleWriteQueue($) { my($param) = @_; my(undef,$name) = split(':', $param); my $hash = $defs{$name}; #my @arr = @{$hash->{QUEUE}}; if(@{$hash->{QUEUE}}) { my $msg= shift(@{$hash->{QUEUE}}); if($msg eq "") { SIGNALduino_HandleWriteQueue("x:$name"); } else { SIGNALduino_SendFromQueue($hash, $msg); } } else { Log3 $name, 4, "$name/HandleWriteQueue: nothing to send, stopping timer"; RemoveInternalTimer("HandleWriteQueue:$name"); } } ##################################### # 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}; Log3 $name, 5, "$name/RAW READ: $SIGNALduinodata/$buf" if ($debug); $SIGNALduinodata .= $buf; while($SIGNALduinodata =~ m/\n/) { my $rmsg; ($rmsg,$SIGNALduinodata) = split("\n", $SIGNALduinodata, 2); $rmsg =~ s/\r//; Log3 $name, 4, "$name/msg READ: $rmsg"; if ( $rmsg && !SIGNALduino_Parse($hash, $hash, $name, $rmsg) && $hash->{getcmd} ) { my $regexp=$gets{$hash->{getcmd}->{cmd}}[1]; if(!defined($regexp) || $rmsg =~ m/$regexp/) { if (defined($hash->{keepalive})) { $hash->{keepalive}{ok} = 1; $hash->{keepalive}{retry} = 0; } if ($hash->{getcmd}->{cmd} eq 'version') { my $msg_start = index($rmsg, 'V 3.'); if ($msg_start > 0) { $rmsg = substr($rmsg, $msg_start); Log3 $name, 4, "$name/read: cut chars at begin. msgstart = $msg_start msg = $rmsg"; } $hash->{version} = $rmsg; if (defined($hash->{DevState}) && $hash->{DevState} eq 'waitInit') { RemoveInternalTimer($hash); SIGNALduino_CheckCmdResp($hash); } } $rmsg = SIGNALduino_parseResponse($hash,$hash->{getcmd}->{cmd},$rmsg); readingsSingleUpdate($hash, $hash->{getcmd}->{cmd}, $rmsg, 0); if (defined($hash->{getcmd}->{asyncOut})) { #Log3 $name, 4, "$name/msg READ: asyncOutput"; my $ao = asyncOutput( $hash->{getcmd}->{asyncOut}, $hash->{getcmd}->{cmd}.": " . $rmsg ); } delete($hash->{getcmd}); } else { Log3 $name, 4, "$name/msg READ: Received answer ($rmsg) for ". $hash->{getcmd}->{cmd}." does not match $regexp"; } } } $hash->{PARTIAL} = $SIGNALduinodata; } sub SIGNALduino_KeepAlive($){ my ($hash) = @_; my $name = $hash->{NAME}; return if ($hash->{DevState} eq 'disconnected'); Log3 $name,4 , "$name/KeepAliveOk: " . $hash->{keepalive}{ok}; if (!$hash->{keepalive}{ok}) { delete($hash->{getcmd}); if ($hash->{keepalive}{retry} >= SDUINO_KEEPALIVE_MAXRETRY) { Log3 $name,4 , "$name/keepalive retry count reached. Reset"; $hash->{DevState} = 'INACTIVE'; SIGNALduino_ResetDevice($hash); return; } else { $hash->{keepalive}{retry} ++; Log3 $name,4 , "$name/KeepAlive: get ping"; $hash->{getcmd}->{cmd} = "ping"; SIGNALduino_SimpleWrite($hash, "P"); } } Log3 $name,4 , "$name/keepalive retry = " . $hash->{keepalive}{retry}; $hash->{keepalive}{ok} = 0; InternalTimer(gettimeofday() + SDUINO_KEEPALIVE_TIMEOUT, "SIGNALduino_KeepAlive", $hash, 1); } ### Helper Subs >>> sub SIGNALduino_splitMsg { my $txt = shift; my $delim = shift; my @msg_parts = split(/$delim/,$txt); return @msg_parts; } # $value - $set <= $tolerance sub SIGNALduino_inTol($$$) { #Debug "sduino abs \($_[0] - $_[1]\) <= $_[2] "; return (abs($_[0]-$_[1])<=$_[2]); } # - - - - - - - - - - - - #=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 $searchpattern; my $valid=1; my @pstr; my $debug = AttrVal($hash->{NAME},"debug",0); my $i=0; my $maxcol=0; foreach $searchpattern (@{$search}) # z.B. [1, -4] { #my $patt_id; # 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.17 : $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); my $idxstr=""; my $r=0; while (my ($item) = splice(@closestidx, 0, 1)) { $pstr[$i][$r]=$item; $r++; Debug "closest pattern has index: $item" if($debug); } $valid=1; } else { # search is not found, return -1 return -1; last; } $i++; #return ($valid ? $pstr : -1); # return $pstr if $valid or -1 #foreach $patt_id (keys %$patternList) { #Debug "$patt_id. chk ->intol $patternList->{$patt_id} $searchpattern $tol"; #$valid = SIGNALduino_inTol($patternList->{$patt_id}, $searchpattern, $tol); #if ( $valid) #one pulse found in tolerance, search next one #{ # $pstr="$pstr$patt_id"; # # provide this index for further lookup table -> {$patt_id = $searchpattern} # Debug "pulse found"; # last ; ## Exit foreach loop if searched pattern matches pattern in list #} #} #last if (!$valid); ## Exit loop if a complete iteration has not found anything } my @results = (''); foreach my $subarray (@pstr) { @results = map {my $res = $_; map $res.$_, @$subarray } @results; } foreach my $search (@results) { Debug "looking for substr $search" if($debug); return $search if (index( ${$data}, $search) >= 0); } return -1; #return ($valid ? @results : -1); # return @pstr if $valid or -1 } #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 } } sub SIGNALduino_b2h { my $num = shift; my $WIDTH = 4; my $index = length($num) - $WIDTH; my $hex = ''; do { my $width = $WIDTH; if ($index < 0) { $width += $index; $index = 0; } my $cut_string = substr($num, $index, $width); $hex = sprintf('%X', oct("0b$cut_string")) . $hex; $index -= $WIDTH; } while ($index > (-1 * $WIDTH)); return $hex; } sub SIGNALduino_Split_Message($$) { my $rmsg = shift; my $name = shift; my %patternList; my $clockidx; my $syncidx; my $rawData; my $clockabs; my $mcbitnum; 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/^MU/) #### Synced Message start { $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=\d{1}/) #### Sync Pulse Index { (undef, $syncidx) = split(/=/,$_); Debug "$name: extracted syncidx $syncidx\n" if ($debug); #return undef if (!defined($patternList{$syncidx})); $ret{syncidx} = $syncidx; } elsif($_ =~ m/^CP=\d{1}/) #### Clock Pulse Index { (undef, $clockidx) = split(/=/,$_); Debug "$name: extracted clockidx $clockidx\n" if ($debug);; #return undef if (!defined($patternList{$clockidx})); $ret{clockidx} = $clockidx; } 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; } else { Debug "$name: unknown Message part $_" if ($debug);; } #print "$_\n"; } $ret{pattern} = {%patternList}; return %ret; } # Function which dispatches a message if needed. sub SIGNALduno_Dispatch($$$) { my ($hash, $rmsg, $dmsg) = @_; my $name = $hash->{NAME}; if (!defined($dmsg)) { Log3 $name, 5, "$name: (SIGNALduno_Dispatch) dmsg is undef. Skipping dispatch call"; return; } Log3 $name, 5, "$name: converted Data to ($dmsg)"; #Dispatch only if $dmsg is different from last $dmsg, or if 2 seconds are between transmits if ( ($hash->{DMSG} ne $dmsg) || ($hash->{TIME}+1 < time()) ) { $hash->{MSGCNT}++; $hash->{TIME} = time(); $hash->{DMSG} = $dmsg; my $event = 0; if (substr($dmsg,0,1) eq 'u') { $event = 1; } readingsSingleUpdate($hash, "state", $hash->{READINGS}{state}{VAL}, $event); $hash->{RAWMSG} = $rmsg; my %addvals = (RAWMSG => $rmsg, DMSG => $dmsg); Dispatch($hash, $dmsg, \%addvals); ## Dispatch to other Modules } else { Log3 $name, 4, "$name: Dropped ($dmsg) due to short time or equal msg"; } } sub SIGNALduino_Parse_MS($$$$%) { my ($hash, $iohash, $name, $rmsg,%msg_parts) = @_; my $protocolid; my $syncidx=$msg_parts{syncidx}; my $clockidx=$msg_parts{clockidx}; my $protocol=undef; my $rawData=$msg_parts{rawData}; my %patternList; #$patternList{$_} = $msg_parts{rawData}{$_] for keys %msg_parts{rawData}; #$patternList = \%msg_parts{pattern}; #Debug "Message splitted:"; #Debug Dumper(\@msg_parts); my $debug = AttrVal($iohash->{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 undef 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 $id; my $message_dispatched=0; foreach $id (@{$hash->{msIdList}}) { my $valid=1; #$debug=1; Debug "Testing against Protocol id $id -> $ProtocolListSIGNALduino{$id}{name}" if ($debug); # Check Clock if is it in range $valid=SIGNALduino_inTol($ProtocolListSIGNALduino{$id}{clockabs},$clockabs,$clockabs*0.30) if ($ProtocolListSIGNALduino{$id}{clockabs} > 0); Debug "validclock = $valid" if ($debug); next if (!$valid) ; my $bit_length = ($signal_length-(scalar @{$ProtocolListSIGNALduino{$id}{sync}}))/((scalar @{$ProtocolListSIGNALduino{$id}{one}} + scalar @{$ProtocolListSIGNALduino{$id}{zero}})/2); #Check calculated min length $valid = $valid && $ProtocolListSIGNALduino{$id}{length_min} <= $bit_length if (exists $ProtocolListSIGNALduino{$id}{length_min}); #Check calculated max length $valid = $valid && $ProtocolListSIGNALduino{$id}{length_max} >= $bit_length if (exists $ProtocolListSIGNALduino{$id}{length_max}); next if (!$valid) ; Debug "expecting $bit_length bits in signal" if ($debug && $valid); #Debug Dumper(@{$ProtocolListSIGNALduino{$id}{sync}}); Debug "Searching in patternList: ".Dumper(\%patternList) if($debug); Debug "searching sync: @{$ProtocolListSIGNALduino{$id}{sync}}[0] @{$ProtocolListSIGNALduino{$id}{sync}}[1]" if($debug); # z.B. [1, -18] #$valid = $valid && SIGNALduino_inTol($patternList{$clockidx}, @{$ProtocolListSIGNALduino{$id}{sync}}[0], 3); #sync in tolerance #$valid = $valid && SIGNALduino_inTol($patternList{$syncidx}, @{$ProtocolListSIGNALduino{$id}{sync}}[1], 3); #sync in tolerance my $pstr; my %patternLookupHash=(); $valid = $valid && ($pstr=SIGNALduino_PatternExists($hash,\@{$ProtocolListSIGNALduino{$id}{sync}},\%patternList,\$rawData)) >=0; Debug "Found matched sync with indexes: ($pstr)" if ($debug && $valid); $patternLookupHash{$pstr}="" if ($valid); ## Append Sync to our lookuptable my $syncstr=$pstr; # Store for later start search Debug "sync not found " if (!$valid && $debug); # z.B. [1, -18] next if (!$valid) ; $valid = $valid && ($pstr=SIGNALduino_PatternExists($hash,\@{$ProtocolListSIGNALduino{$id}{one}},\%patternList,\$rawData)) >=0; Debug "Found matched one with indexes: ($pstr)" if ($debug && $valid); $patternLookupHash{$pstr}="1" if ($valid); ## Append Sync to our lookuptable #Debug "added $pstr " if ($debug && $valid); Debug "one pattern not found" if ($debug && !$valid); $valid = $valid && ($pstr=SIGNALduino_PatternExists($hash,\@{$ProtocolListSIGNALduino{$id}{zero}},\%patternList,\$rawData)) >=0; Debug "Found matched zero with indexes: ($pstr)" if ($debug && $valid); $patternLookupHash{$pstr}="0" if ($valid); ## Append Sync to our lookuptable Debug "zero pattern not found" if ($debug && !$valid); #Debug "added $pstr " if ($debug && $valid); next if (!$valid) ; #Debug "Pattern Lookup Table".Dumper(%patternLookupHash); ## Check somethin else #Anything seems to be valid, we can start decoding this. Log3 $name, 4, "$name: Matched MS Protocol id $id -> $ProtocolListSIGNALduino{$id}{name}" if ($valid); my $signal_width= @{$ProtocolListSIGNALduino{$id}{one}}; #Debug $signal_width; my @bit_msg; # array to store decoded signal bits #for (my $i=index($rawData,SIGNALduino_PatternExists($hash,\@{$ProtocolListSIGNALduino{$id}{sync}}))+$signal_width;$i 0) ## will pad up full nibbles per default or full byte if specified in protocol { push(@bit_msg,'0'); $i++; } Debug "$name padded $i bits to bit_msg array" if ($debug); #my $logmsg = SIGNALduino_padbits(@bit_msg,$padwith); #Check converted message against lengths $valid = $valid && $ProtocolListSIGNALduino{$id}{length_min} <= scalar @bit_msg if (defined($ProtocolListSIGNALduino{$id}{length_min})); $valid = $valid && $ProtocolListSIGNALduino{$id}{length_max} >= scalar @bit_msg if (defined($ProtocolListSIGNALduino{$id}{length_max})); next if (!$valid); #my $dmsg = sprintf "%02x", oct "0b" . join "", @bit_msg; ## Array -> String -> bin -> hex my $dmsg = SIGNALduino_b2h(join "", @bit_msg); $dmsg = "$dmsg"."$ProtocolListSIGNALduino{$id}{postamble}" if (defined($ProtocolListSIGNALduino{$id}{postamble})); $dmsg = "$ProtocolListSIGNALduino{$id}{preamble}"."$dmsg" if (defined($ProtocolListSIGNALduino{$id}{preamble})); Log3 $name, 4, "$name: Decoded MS Protocol id $id dmsg $dmsg length " . scalar @bit_msg; #my ($rcode,@retvalue) = SIGNALduino_callsub('preDispatchfunc',$ProtocolListSIGNALduino{$id}{preDispatchfunc},$name,$dmsg); #next if (!$rcode); #$dmsg = @retvalue; #undef(@retvalue); undef($rcode); my $modulematch; if (defined($ProtocolListSIGNALduino{$id}{modulematch})) { $modulematch = $ProtocolListSIGNALduino{$id}{modulematch}; } if (!defined($modulematch) || $dmsg =~ m/$modulematch/) { Debug "$name: dispatching now msg: $dmsg" if ($debug); SIGNALduno_Dispatch($hash,$rmsg,$dmsg); $message_dispatched=1; } } return 0 if (!$message_dispatched); return 1; } } ## //Todo: check list as reference sub SIGNALduino_padbits(\@$) { my $i=@{$_[0]} % $_[1]; while (@{$_[0]} % $_[1] > 0) ## will pad up full nibbles per default or full byte if specified in protocol { push(@{$_[0]},'0'); } return " padded $i bits to bit_msg array"; } # - - - - - - - - - - - - #=item SIGNALduino_getProtoProp() #This functons, will return a value from the Protocolist and check if it is defined # # returns "" if the var is not defined # =cut # $id, $propertyname, sub SIGNALduino_getProtoProp($$) { my $id = shift; my $propNameLst = shift; return $ProtocolListSIGNALduino{$id}{$propNameLst} if defined($ProtocolListSIGNALduino{$id}{$propNameLst}); return undef; } sub SIGNALduino_Parse_MU($$$$@) { my ($hash, $iohash, $name, $rmsg,%msg_parts) = @_; my $protocolid; my $clockidx=$msg_parts{clockidx}; my $protocol=undef; my $rawData; my %patternListRaw; my $message_dispatched=0; my $debug = AttrVal($iohash->{NAME},"debug",0); Debug "$name: processing unsynced message\n" if ($debug); #my $clockabs; #Clock will be fetched from Protocol #$patternListRaw{$_} = floor($msg_parts{pattern}{$_}/$clockabs) for keys $msg_parts{pattern}; $patternListRaw{$_} = $msg_parts{pattern}{$_} for keys %{$msg_parts{pattern}}; if (defined($clockidx)) { ## Make a lookup table for our pattern index ids #Debug "List of pattern:"; #Debug Dumper(\%patternList); ## Find matching protocols my $id; foreach $id (@{$hash->{muIdList}}) { my $valid=1; my $clockabs= $ProtocolListSIGNALduino{$id}{clockabs}; my %patternList; $rawData=$msg_parts{rawData}; if (exists($ProtocolListSIGNALduino{$id}{filterfunc})) { my $method = $ProtocolListSIGNALduino{$id}{filterfunc}; if (!exists &$method) { Log3 $name, 5, "$name: Error: Unknown filtermethod=$method. Please define it in file $0"; next; } else { Log3 $name, 5, "$name: applying filterfunc $method"; no strict "refs"; (my $count_changes,$rawData,my %patternListRaw_tmp) = $method->($name,$id,$rawData,%patternListRaw); use strict "refs"; %patternList = map { $_ => round($patternListRaw_tmp{$_}/$clockabs,1) } keys %patternListRaw_tmp; } } else { %patternList = map { $_ => round($patternListRaw{$_}/$clockabs,1) } keys %patternListRaw; } my $signal_length = length($rawData); # Length of data array my @keys = sort { $patternList{$a} <=> $patternList{$b} } keys %patternList; #Debug Dumper(\%patternList); #Debug Dumper(@keys); #$debug=1; Debug "Testing against Protocol id $id -> $ProtocolListSIGNALduino{$id}{name}" if ($debug); # $valid=SIGNALduino_inTol($ProtocolListSIGNALduino{$id}{clockabs},$clockabs,$clockabs*0.30) if ($ProtocolListSIGNALduino{$id}{clockabs} > 0); next if (!$valid) ; my $bit_length = ($signal_length/((scalar @{$ProtocolListSIGNALduino{$id}{one}} + scalar @{$ProtocolListSIGNALduino{$id}{zero}})/2)); Debug "Expect $bit_length bits in message" if ($valid && $debug); #Check calculated min length #$valid = $valid && $ProtocolListSIGNALduino{$id}{length_min} <= $bit_length if (exists $ProtocolListSIGNALduino{$id}{length_min}); #Check calculated max length #$valid = $valid && $ProtocolListSIGNALduino{$id}{length_max} >= $bit_length if (exists $ProtocolListSIGNALduino{$id}{length_max}); #next if (!$valid) ; #Debug "expecting $bit_length bits in signal" if ($debug && $valid); Debug "Searching in patternList: ".Dumper(\%patternList) if($debug); next if (!$valid) ; my %patternLookupHash=(); #Debug "phash:".Dumper(%patternLookupHash); my $pstr=""; $valid = $valid && ($pstr=SIGNALduino_PatternExists($hash,\@{$ProtocolListSIGNALduino{$id}{one}},\%patternList,\$rawData)) >=0; Debug "Found matched one" if ($debug && $valid); $patternLookupHash{$pstr}="1" if ($valid); ## Append one to our lookuptable Debug "added $pstr " if ($debug && $valid); $valid = $valid && ($pstr=SIGNALduino_PatternExists($hash,\@{$ProtocolListSIGNALduino{$id}{zero}},\%patternList,\$rawData)) >=0; Debug "Found matched zero" if ($debug && $valid); $patternLookupHash{$pstr}="0" if ($valid); ## Append zero to our lookuptable Debug "added $pstr " if ($debug && $valid); if (defined($ProtocolListSIGNALduino{$id}{float})) { $valid = $valid && ($pstr=SIGNALduino_PatternExists($hash,\@{$ProtocolListSIGNALduino{$id}{float}},\%patternList,\$rawData)) >=0; Debug "Found matched float" if ($debug && $valid); $patternLookupHash{$pstr}="F" if ($valid); ## Append float to our lookuptable Debug "added $pstr " if ($debug && $valid); } next if (!$valid) ; #Debug "Pattern Lookup Table".Dumper(%patternLookupHash); ## Check somethin else #Anything seems to be valid, we can start decoding this. Log3 $name, 4, "$name: Fingerprint for MU Protocol id $id -> $ProtocolListSIGNALduino{$id}{name} matches, trying to demodulate" if ($valid); my $signal_width= @{$ProtocolListSIGNALduino{$id}{one}}; #Debug $signal_width; my @bit_msg=(); # array to store decoded signal bits my $message_start=0 ; my @msgStartLst; my $startStr=""; my $start_regex; my $oneStr=SIGNALduino_PatternExists($hash,\@{$ProtocolListSIGNALduino{$id}{one}},\%patternList,\$rawData); my $zeroStr=SIGNALduino_PatternExists($hash,\@{$ProtocolListSIGNALduino{$id}{zero}},\%patternList,\$rawData); if (@msgStartLst = SIGNALduino_getProtoProp($id,"start")) { $startStr=SIGNALduino_PatternExists($hash,@msgStartLst,\%patternList,\$rawData); } $start_regex="$startStr($oneStr|$zeroStr)"; $rawData =~ /$start_regex/; if (defined($-[0] && $-[0] > 0)) { $message_start=$-[0]+ length($startStr); } else { undef($message_start); } undef @msgStartLst; #for (my $i=index($rawData,SIGNALduino_PatternExists($hash,\@{$ProtocolListSIGNALduino{$id}{sync}}))+$signal_width;$ilength($rawData)-$signal_width) ## Dispatch if last signal or unknown data { Debug "$name: demodulated message raw (@bit_msg), ".@bit_msg." bits\n" if ($debug); #Check converted message against lengths $valid = $valid && $ProtocolListSIGNALduino{$id}{length_max} >= scalar @bit_msg if (defined($ProtocolListSIGNALduino{$id}{length_max})); $valid = $valid && $ProtocolListSIGNALduino{$id}{length_min} <= scalar @bit_msg if (defined($ProtocolListSIGNALduino{$id}{length_min})); #next if (!$valid); ## Last chance to try next protocol if there is somethin invalid if ($valid) { my ($rcode,@retvalue) = SIGNALduino_callsub('postDemodulation',$ProtocolListSIGNALduino{$id}{postDemodulation},$name,@bit_msg); next if (!$rcode); #Log3 $name, 5, "$name: postdemodulation value @retvalue"; @bit_msg = @retvalue; undef(@retvalue); undef($rcode); while (scalar @bit_msg % $padwith > 0) ## will pad up full nibbles per default or full byte if specified in protocol { push(@bit_msg,'0'); Debug "$name: padding 0 bit to bit_msg array" if ($debug); } Log3 $name, 5, "$name: dispatching bits: @bit_msg"; my $dmsg = SIGNALduino_b2h(join "", @bit_msg); $dmsg =~ s/^0+// if (defined($ProtocolListSIGNALduino{$id}{remove_zero})); $dmsg = "$dmsg"."$ProtocolListSIGNALduino{$id}{postamble}" if (defined($ProtocolListSIGNALduino{$id}{postamble})); $dmsg = "$ProtocolListSIGNALduino{$id}{preamble}"."$dmsg" if (defined($ProtocolListSIGNALduino{$id}{preamble})); Log3 $name, 4, "$name: decoded matched MU Protocol id $id dmsg $dmsg length " . scalar @bit_msg; my $modulematch; if (defined($ProtocolListSIGNALduino{$id}{modulematch})) { $modulematch = $ProtocolListSIGNALduino{$id}{modulematch}; } if (!defined($modulematch) || $dmsg =~ m/$modulematch/) { Debug "$name: dispatching now msg: $dmsg" if ($debug); SIGNALduno_Dispatch($hash,$rmsg,$dmsg); $message_dispatched=1; } } else { if ($debug) { my $debugstr; $debugstr.=$ProtocolListSIGNALduino{$id}{length_min} if defined($ProtocolListSIGNALduino{$id}{length_min}); $debugstr.="/"; $debugstr.=$ProtocolListSIGNALduino{$id}{length_max} if defined($ProtocolListSIGNALduino{$id}{length_max}); Debug "$name: length ($debugstr) does not match (@bit_msg), ".@bit_msg." bits\n"; } } @bit_msg=(); # clear bit_msg array #Find next position of valid signal (skip invalid pieces) my $regex=".{$i}".$start_regex; Debug "$name: searching new start with ($regex)\n" if ($debug); $rawData =~ /$regex/; if (defined($-[0]) && ($-[0] > 0)) { $i=$-[0]+ $i+ length($startStr); $i=$i-$signal_width if ($i>0 && length($startStr) == 0); #Todo: Debug "$name: found restart at Position $i ($regex)\n" if ($debug); } else { last; } #if ($startStr) #{ # $i= index($rawData,$startStr,$i); # } else { # $i = (index($rawData,SIGNALduino_PatternExists($hash,\@{$ProtocolListSIGNALduino{$id}{one}},\%patternList),$i+$signal_width) < index($rawData,SIGNALduino_PatternExists($hash,\@{$ProtocolListSIGNALduino{$id}{zero}},\%patternList),$i+$signal_width) ? index($rawData,SIGNALduino_PatternExists($hash,\@{$ProtocolListSIGNALduino{$id}{one}},\%patternList),$i+$signal_width) : index($rawData,SIGNALduino_PatternExists($hash,\@{$ProtocolListSIGNALduino{$id}{zero}},\%patternList),$i+$signal_width)); # $i-=$signal_width if ($i String -> bin -> hex } return 0 if (!$message_dispatched); return 1; } } sub SIGNALduino_Parse_MC($$$$@) { my ($hash, $iohash, $name, $rmsg,%msg_parts) = @_; my $clock=$msg_parts{clockabs}; ## absolute clock my $rawData=$msg_parts{rawData}; my $bitData; my $dmsg; my $message_dispatched=0; my $debug = AttrVal($iohash->{NAME},"debug",0); return undef if (!$clock); #my $protocol=undef; #my %patternListRaw = %msg_parts{patternList}; Debug "$name: processing manchester messag len:".length($rawData) if ($debug); my $hlen = length($rawData); my $blen = $hlen * 4; my $id; my $rawDataInverted; ($rawDataInverted = $rawData) =~ tr/0123456789ABCDEF/FEDCBA9876543210/; # Some Manchester Data is inverted foreach $id (@{$hash->{mcIdList}}) { if ( $clock >$ProtocolListSIGNALduino{$id}{clockrange}[0] and $clock <$ProtocolListSIGNALduino{$id}{clockrange}[1] and length($rawData)*4 >= $ProtocolListSIGNALduino{$id}{length_min} ) { Debug "clock and min length matched" if ($debug); Log3 $name, 4, "$name: Found manchester Protocol id $id clock $clock -> $ProtocolListSIGNALduino{$id}{name}"; if (exists($ProtocolListSIGNALduino{$id}{polarity}) && ($ProtocolListSIGNALduino{$id}{polarity} eq 'invert') && (!defined($hash->{version}) || substr($hash->{version},0,6) ne 'V 3.2.')) # todo && substr($hash->{version},0,6) ne 'V 3.2.') # bei version V 3.2. nicht invertieren { $bitData= unpack("B$blen", pack("H$hlen", $rawDataInverted)); } else { $bitData= unpack("B$blen", pack("H$hlen", $rawData)); } Debug "$name: extracted data $bitData (bin)\n" if ($debug); ## Convert Message from hex to bits Log3 $name, 5, "$name: extracted data $bitData (bin)\n"; my $method = $ProtocolListSIGNALduino{$id}{method}; if (!exists &$method) { Log3 $name, 5, "$name: Error: Unknown function=$method. Please define it in file $0"; } else { my ($rcode,$res) = $method->($name,$bitData,$id); if ($rcode != -1) { $dmsg = $res; $dmsg=$ProtocolListSIGNALduino{$id}{preamble}.$dmsg if (defined($ProtocolListSIGNALduino{$id}{preamble})); my $modulematch; if (defined($ProtocolListSIGNALduino{$id}{modulematch})) { $modulematch = $ProtocolListSIGNALduino{$id}{modulematch}; } if (!defined($modulematch) || $dmsg =~ m/$modulematch/) { SIGNALduno_Dispatch($hash,$rmsg,$dmsg); $message_dispatched=1; } } else { Log3 $name, 5, "$name: protocol does not match return from method: ($res)" if ($debug); } } } } return 0 if (!$message_dispatched); return 1; } sub SIGNALduino_Parse($$$$@) { my ($hash, $iohash, $name, $rmsg, $initstr) = @_; #print Dumper(\%ProtocolListSIGNALduino); return undef if !($rmsg=~ m/^\002M.;.*;\003/); ## Check if a Data Message arrived and if it's complete (start & end control char are received) if (defined($hash->{keepalive})) { $hash->{keepalive}{ok} = 1; $hash->{keepalive}{retry} = 0; } my $debug = AttrVal($iohash->{NAME},"debug",0); $rmsg=~ s/^\002(M.;.*;)\003/$1/; # cut off start end end character from message for further processing they are not needed Debug "$name: incomming message: ($rmsg)\n" if ($debug); my %signal_parts=SIGNALduino_Split_Message($rmsg,$name); ## Split message and save anything in an hash %signal_parts #Debug "raw data ". $signal_parts{rawData}; my @msg_parts = SIGNALduino_splitMsg($rmsg,';'); ## Split message parts by ";" my $dispatched; # Message Synced type -> M# if ($rmsg=~ m/^M\d+;(P\d=-?\d+;){4,7}D=\d+;CP=\d;SP=\d;/) { Log3 $name, 3, "$name: You are using an outdated version of signalduino code on your arduino. Please update"; return undef; } if (@{$hash->{msIdList}} && $rmsg=~ m/^MS;(P\d=-?\d+;){3,8}D=\d+;CP=\d;SP=\d;/) { $dispatched= SIGNALduino_Parse_MS($hash, $iohash, $name, $rmsg,%signal_parts); } # Message unsynced type -> MU elsif (@{$hash->{muIdList}} && $rmsg=~ m/^MU;(P\d=-?\d+;){3,8}D=\d+;CP=\d;/) { $dispatched= SIGNALduino_Parse_MU($hash, $iohash, $name, $rmsg,%signal_parts); } # Manchester encoded Data -> MC elsif (@{$hash->{mcIdList}} && $rmsg=~ m/^MC;.*;/) { $dispatched= SIGNALduino_Parse_MC($hash, $iohash, $name, $rmsg,%signal_parts); } else { Debug "$name: unknown Messageformat, aborting\n" if ($debug); return undef; } if ( AttrVal($hash->{NAME},"verbose","0") > 4 && !$dispatched) { my $notdisplist; my @lines; if (defined($hash->{unknownmessages})) { $notdisplist=$hash->{unknownmessages}; @lines = split ('#', $notdisplist); # or whatever } push(@lines,FmtDateTime(time())."-".$rmsg); shift(@lines)if (scalar @lines >25); $notdisplist = join('#',@lines); $hash->{unknownmessages}=$notdisplist; return undef; #Todo compare Sync/Clock fact and length of D= if equal, then it's the same protocol! } } ##################################### sub SIGNALduino_Ready($) { my ($hash) = @_; if ($hash->{STATE} eq 'disconnected') { $hash->{DevState} = 'disconnected'; return DevIo_OpenDev($hash, 1, "SIGNALduino_DoInit", 'SIGNALduino_Connect') } # This is relevant for windows/USB only my $po = $hash->{USBDev}; my ($BlockingFlags, $InBytes, $OutBytes, $ErrorFlags); if($po) { ($BlockingFlags, $InBytes, $OutBytes, $ErrorFlags) = $po->status; } return ($InBytes && $InBytes>0); } ######################## sub SIGNALduino_SimpleWrite(@) { my ($hash, $msg, $nonl) = @_; return if(!$hash); if($hash->{TYPE} eq "SIGNALduino_RFR") { # Prefix $msg with RRBBU and return the corresponding SIGNALduino hash. ($hash, $msg) = SIGNALduino_RFR_AddPrefix($hash, $msg); } my $name = $hash->{NAME}; Log3 $name, 5, "$name SW: $msg"; $msg .= "\n" unless($nonl); $hash->{USBDev}->write($msg) if($hash->{USBDev}); syswrite($hash->{TCPDev}, $msg) if($hash->{TCPDev}); syswrite($hash->{DIODev}, $msg) if($hash->{DIODev}); # Some linux installations are broken with 0.001, T01 returns no answer select(undef, undef, undef, 0.01); } sub SIGNALduino_Attr(@) { my ($cmd,$name,$aName,$aVal) = @_; my $hash = $defs{$name}; my $debug = AttrVal($name,"debug",0); Log3 $name, 4, "$name: Calling Getting Attr sub with args: $cmd $aName = $aVal"; if( $aName eq "Clients" ) { ## Change clientList $hash->{Clients} = $aVal; $hash->{Clients} = $clientsSIGNALduino if( !$hash->{Clients}) ; ## Set defaults return "Setting defaults"; } elsif( $aName eq "MatchList" ) { ## Change matchList my $match_list; if( $cmd eq "set" ) { $match_list = eval $aVal; if( $@ ) { Log3 $name, 2, $name .": $aVal: ". $@; } } if( ref($match_list) eq 'HASH' ) { $hash->{MatchList} = $match_list; } else { $hash->{MatchList} = \%matchListSIGNALduino; ## Set defaults Log3 $name, 2, $name .": $aVal: not a HASH using defaults" if( $aVal ); } } elsif ($aName eq "verbose") { Log3 $name, 3, "$name: setting Verbose to: " . $aVal; $hash->{unknownmessages}="" if $aVal <4; } elsif ($aName eq "debug") { $debug = $aVal; Log3 $name, 3, "$name: setting debug to: " . $debug; } elsif ($aName eq "whitelist_IDs") { SIGNALduino_IdList($hash, $name, $aVal); } return undef; } sub SIGNALduino_IdList($$$) { my ($hash, $name, $aVal) = @_; my @msIdList = (); my @muIdList = (); my @mcIdList = (); my %WhitelistIDs; my $wflag = 0; if (defined($aVal) && length($aVal)>0) { if (substr($aVal,0 ,1) eq '#') { Log3 $name, 3, "Attr whitelist deaktiviert: $aVal"; } else { %WhitelistIDs = map { $_ => 1 } split(",", $aVal); #my $w = join ', ' => map "$_" => keys %WhitelistIDs; #Log3 $name, 3, "Attr whitelist $w"; $wflag = 1; } } my $id; foreach $id (keys %ProtocolListSIGNALduino) { next if ($id eq 'id'); if ($wflag == 1 && !defined($WhitelistIDs{$id})) { #Log3 $name, 3, "skip ID $id"; next; } if (exists ($ProtocolListSIGNALduino{$id}{format}) && $ProtocolListSIGNALduino{$id}{format} eq "manchester") { push (@mcIdList, $id); } elsif (exists $ProtocolListSIGNALduino{$id}{sync}) { push (@msIdList, $id); } elsif (exists ($ProtocolListSIGNALduino{$id}{clockabs})) { push (@muIdList, $id); } } @msIdList = sort @msIdList; @muIdList = sort @muIdList; @mcIdList = sort @mcIdList; Log3 $name, 3, "$name: IDlist MS @msIdList"; Log3 $name, 3, "$name: IDlist MU @muIdList"; Log3 $name, 3, "$name: IDlist MC @mcIdList"; $hash->{msIdList} = \@msIdList; $hash->{muIdList} = \@muIdList; $hash->{mcIdList} = \@mcIdList; } sub SIGNALduino_callsub { my $funcname =shift; my $method = shift; my $name = shift; my @args = @_; if ( defined $method && defined &$method ) { Log3 $name, 5, "$name: applying $funcname method $method"; #Log3 $name, 5, "$name: value bevore $funcname: @args"; my @returnvalues = $method->(@args) ; Log3 $name, 5, "$name: modified value after $funcname: @returnvalues"; return (1,@returnvalues); } elsif (defined $method ) { Log3 $name, 5, "$name: Error: Unknown method $funcname Please check definition"; return (0,undef); } return (1,@args); } # calculates the hex (in bits) and adds it at the beginning of the message # input = @list # output = @list sub SIGNALduino_lengtnPrefix { my $msg = join("",@_); #$msg = unpack("B8", pack("N", length($msg))).$msg; $msg=sprintf('%08b', length($msg)).$msg; return split("",$msg); } sub SIGNALduino_bit2Arctec { my $msg = join("",@_); # Convert 0 -> 01 1 -> 10 to be compatible with IT Module $msg =~ s/0/z/g; $msg =~ s/1/10/g; $msg =~ s/z/01/g; return split("",$msg); } sub SIGNALduino_ITV1_tristateToBit($) { my ($msg) = @_; # Convert 0 -> 00 1 -> 11 F => 01 to be compatible with IT Module $msg =~ s/0/00/g; $msg =~ s/1/11/g; $msg =~ s/F/01/g; $msg =~ s/D/10/g; return $msg; } sub SIGNALduino_OSV2() { my ($name,$bitData,$id) = @_; my $preamble_pos; my $message_end; my $message_length; #$bitData =~ tr/10/01/; if ($bitData =~ m/^.?(10){12,16}.?10011001/) { # Valid OSV2 detected! $preamble_pos=index($bitData,"10011001",24); Log3 $name, 4, "$name: OSV2 protocol detected: preamble_pos = $preamble_pos"; return return (-1," sync not found") if ($preamble_pos <=24); $message_end=index($bitData,"101010101010101010101010101010110011001",$preamble_pos+44); $message_end = length($bitData) if ($message_end <$preamble_pos); $message_length = ($message_end - $preamble_pos)/2; return (-1," message is to short") if (defined($ProtocolListSIGNALduino{$id}{length_min}) && $message_length < $ProtocolListSIGNALduino{$id}{length_min} ); return (-1," message is to long") if (defined($ProtocolListSIGNALduino{$id}{length_max}) && $message_length > $ProtocolListSIGNALduino{$id}{length_max} ); my $idx=0; my $osv2bits=""; my $osv2hex =""; for ($idx=$preamble_pos;$idx= 0) # $rawData =~ m/^A{2,3}/) { # Valid AS detected! my $message_start = index($bitData,"1100",16); Debug "$name: AS protocol detected \n" if ($debug); my $message_end=index($bitData,"1100",$message_start+16); $message_end = length($bitData) if ($message_end == -1); my $message_length = $message_end - $message_start; return (-1," message is to short") if (defined($ProtocolListSIGNALduino{$id}{length_min}) && $message_length < $ProtocolListSIGNALduino{$id}{length_min} ); return (-1," message is to long") if (defined($ProtocolListSIGNALduino{$id}{length_max}) && $message_length > $ProtocolListSIGNALduino{$id}{length_max} ); my $msgbits =substr($bitData,$message_start); my $ashex=sprintf('%02X', oct("0b$msgbits")); Log3 $name, 5, "$name: AS protocol converted to hex: ($ashex) with length ($message_length) bits \n"; return (1,$bitData); } return (-1,undef); } sub SIGNALduino_Hideki() { my ($name,$bitData,$id) = @_; my $debug = AttrVal($name,"debug",0); Debug "$name: search in $bitData \n" if ($debug); my $message_start = index($bitData,"10101110"); if ($message_start >= 0 ) # 0x75 but in reverse order { Debug "$name: Hideki protocol detected \n" if ($debug); # Todo: Mindest Laenge fuer startpunkt vorspringen # Todo: Wiederholung auch an das Modul weitergeben, damit es dort geprueft werden kann my $message_end = index($bitData,"10101110",$message_start+71); # pruefen auf ein zweites 0x75, mindestens 72 bit nach 1. 0x75, da der Regensensor minimum 8 Byte besitzt je byte haben wir 9 bit $message_end = length($bitData) if ($message_end == -1); my $message_length = $message_end - $message_start; return (-1,"message is to short") if (defined($ProtocolListSIGNALduino{$id}{length_min}) && $message_length < $ProtocolListSIGNALduino{$id}{length_min} ); return (-1,"message is to long") if (defined($ProtocolListSIGNALduino{$id}{length_max}) && $message_length > $ProtocolListSIGNALduino{$id}{length_max} ); my $hidekihex; my $idx; for ($idx=$message_start; $idx<$message_end; $idx=$idx+9) { my $byte = ""; $byte= substr($bitData,$idx,8); ## Ignore every 9th bit Debug "$name: byte in order $byte " if ($debug); $byte = scalar reverse $byte; Debug "$name: byte reversed $byte , as hex: ".sprintf('%X', oct("0b$byte"))."\n" if ($debug); $hidekihex=$hidekihex.sprintf('%02X', oct("0b$byte")); } Log3 $name, 4, "$name: hideki protocol converted to hex: $hidekihex with " .$message_length ." bits, messagestart $message_start"; return (1,$hidekihex); ## Return only the original bits, include length } return (-1,""); } sub SIGNALduino_Maverick() { my ($name,$bitData,$id) = @_; my $debug = AttrVal($name,"debug",0); ## Todo: Some checks and may be a lengh filter or some checks my $hex=SIGNALduino_b2h($bitData); return (1,$hex); ## Return the bits unchanged in hex } sub SIGNALduino_SomfyRTS() { my ($name, $bitData, $rawData) = @_; #(my $negBits = $bitData) =~ tr/10/01/; # Todo: eventuell auf pack umstellen my $encData = SIGNALduino_b2h($bitData); #Log3 $name, 4, "$name: Somfy RTS protocol enc: $encData"; return (1, $encData); } # - - - - - - - - - - - - #=item SIGNALduino_filterSign() #This functons, will act as a filter function. It will remove the sign from the pattern, and compress message and pattern # # Will return $count of combined values, modified $rawData , modified %patternListRaw, # =cut sub SIGNALduino_filterSign($$$%) { my ($name,$id,$rawData,%patternListRaw) = @_; my $debug = AttrVal($name,"debug",0); my %buckets; # Remove Sign %patternListRaw = map { $_ => abs($patternListRaw{$_})} keys %patternListRaw; ## remove sing from all my $intol=0; my $cnt=0; # compress pattern hash foreach my $key (keys %patternListRaw) { #print "chk:".$patternListRaw{$key}; #print "\n"; $intol=0; foreach my $b_key (keys %buckets){ #print "with:".$buckets{$b_key}; #print "\n"; # $value - $set <= $tolerance if (SIGNALduino_inTol($patternListRaw{$key},$buckets{$b_key},$buckets{$b_key}*0.25)) { #print"\t". $patternListRaw{$key}."($key) is intol of ".$buckets{$b_key}."($b_key) \n"; $cnt++; eval "\$rawData =~ tr/$key/$b_key/"; #if ($key == $msg_parts{clockidx}) #{ # $msg_pats{syncidx} = $buckets{$key}; # } # elsif ($key == $msg_parts{syncidx}) # { # $msg_pats{syncidx} = $buckets{$key}; # } $buckets{$b_key} = ($buckets{$b_key} + $patternListRaw{$key}) /2; #print"\t recalc to ". $buckets{$b_key}."\n"; delete ($patternListRaw{$key}); # deletes the compressed entry $intol=1; last; } } if ($intol == 0) { $buckets{$key}=abs($patternListRaw{$key}); } } return ($cnt,$rawData, %patternListRaw); #print "rdata: ".$msg_parts{rawData}."\n"; #print Dumper (%buckets); #print Dumper (%msg_parts); #modify msg_parts pattern hash #$patternListRaw = \%buckets; } # - - - - - - - - - - - - #=item SIGNALduino_compPattern() #This functons, will act as a filter function. It will remove the sign from the pattern, and compress message and pattern # # Will return $count of combined values, modified $rawData , modified %patternListRaw, # =cut sub SIGNALduino_compPattern($$$%) { my ($name,$id,$rawData,%patternListRaw) = @_; my $debug = AttrVal($name,"debug",0); my %buckets; # Remove Sign #%patternListRaw = map { $_ => abs($patternListRaw{$_})} keys %patternListRaw; ## remove sing from all my $intol=0; my $cnt=0; # compress pattern hash foreach my $key (keys %patternListRaw) { #print "chk:".$patternListRaw{$key}; #print "\n"; $intol=0; foreach my $b_key (keys %buckets){ #print "with:".$buckets{$b_key}; #print "\n"; # $value - $set <= $tolerance if (SIGNALduino_inTol($patternListRaw{$key},$buckets{$b_key},$buckets{$b_key}*0.4)) { #print"\t". $patternListRaw{$key}."($key) is intol of ".$buckets{$b_key}."($b_key) \n"; $cnt++; eval "\$rawData =~ tr/$key/$b_key/"; #if ($key == $msg_parts{clockidx}) #{ # $msg_pats{syncidx} = $buckets{$key}; # } # elsif ($key == $msg_parts{syncidx}) # { # $msg_pats{syncidx} = $buckets{$key}; # } $buckets{$b_key} = ($buckets{$b_key} + $patternListRaw{$key}) /2; #print"\t recalc to ". $buckets{$b_key}."\n"; delete ($patternListRaw{$key}); # deletes the compressed entry $intol=1; last; } } if ($intol == 0) { $buckets{$key}=$patternListRaw{$key}; } } return ($cnt,$rawData, %patternListRaw); #print "rdata: ".$msg_parts{rawData}."\n"; #print Dumper (%buckets); #print Dumper (%msg_parts); #modify msg_parts pattern hash #$patternListRaw = \%buckets; } #print Dumper (%msg_parts); #print "\n"; #SIGNALduino_filterSign(%msg_parts); #print Dumper (%msg_parts); #print "\n"; 1; =pod =item summary supports the same low-cost receiver for digital signals =item summary_DE Unterstützt den gleichnamigen Low-Cost Empfaenger fuer digitale Signale =begin html

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. Currently are 433Mhz protocols implemented.

The following device support is currently available:

Wireless switches
ITv1 & ITv3/Elro and other brands using pt2263 or arctech protocol--> uses IT.pm


Temperatur / humidity senso
  • PEARL NC7159, LogiLink WS0002,GT-WT-02,AURIOL,TCM97001, TCM27 and many more -> 14_CUL_TCM97001
  • Oregon Scientific v2 and v3 Sensors -> 41_OREGON.pm
  • Temperatur / humidity sensors suppored -> 14_SD_WS07
  • technoline WS 6750 and TX70DTH -> 14_SD_WS07
  • Eurochon EAS 800z -> 14_SD_WS07
  • CTW600, WH1080 -> 14_SD_WS09
  • Hama TS33C, Bresser Thermo/Hygro Sensor -> 14_Hideki
  • FreeTec Aussenmodul NC-7344 -> 14_SD_WS07


It is possible to attach more than one device in order to get better reception, fhem will filter out duplicate messages.

Note: this module require the Device::SerialPort or Win32::SerialPort module. It can currently only attatched via USB.
Define
define <name> SIGNALduino <device>

USB-connected devices (SIGNALduino):
  • <device> specifies the serial port to communicate with the SIGNALduino. The name of the serial-device depends on your distribution, under linux the cdc_acm kernel module is responsible, and usually a /dev/ttyACM0 or /dev/ttyUSB0 device will be created. If your distribution does not have a cdc_acm module, you can force usbserial to handle the SIGNALduino by the following command:
      modprobe usbserial vendor=0x03eb product=0x204b
    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.


  • do_not_notify
  • dummy
  • debug
    This will bring the module in a very verbose debug output. Usefull to find new signals and verify if the demodulation works correctly.
  • flashCommand
    This is the command, that is executed to performa the firmware flash. Do not edit, if you don't know what you are doing.
    The default is: avrdude -p atmega328P -c arduino -P [PORT] -D -U flash:w:[HEXFILE] 2>[LOGFILE]
    It contains some place-holders that automatically get filled with the according values:
    • [PORT]
      is the port the Signalduino is connectd to (e.g. /dev/ttyUSB0) and will be used from the defenition
    • [HEXFILE]
      is the .hex file that shall get flashed. There are three options (applied in this order):
      - passed in set flash
      - taken from the hexFile attribute
      - the default value defined in the module
    • [LOGFILE]
      The logfile that collects information about the flash process. It gets displayed in FHEM after finishing the flash process
  • hardware
    When using the flash command, you should specify whar hardware you have connected to the usbport. Doing not, can cause failures of the device.
  • minsecs
    This is a very special attribute. It is provided to other modules. minsecs should act like a threshold. All logic must be done in the logical module. If specified, then supported modules will discard new messages if minsecs isn't past.
  • longids
    Comma separated list of device-types for SIGNALduino that should be handled using long IDs. This additional ID allows it to differentiate some weather sensors, if they are sending on the same channel. Therfor a random generated id is added. If you choose to use longids, then you'll have to define a different device after battery change.
    Default is to not to use long IDs for all devices.

    Examples:
    # 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
    
  • whitelistIDs
    This attribute allows it, to specify whichs protocos are considured from this module. Protocols which are not considured, will not generate logmessages or events. They are then completly ignored. This makes it possible to lower ressource usage and give some better clearnes in the logs. You can specify multiple whitelistIDs wih a colon : 0,3,7,12
    With a # at the beginnging whitelistIDs can be deactivated.

  • WS09_Model
    WS09_WSModel:undef -> check all, WH1080 -> support WH1080/WS0101 , CTW600 -> support CTW600
  • WS09_CRCAUS
    WS09_CRCAUS:0,1 WS09_CRCAUS = 0 is default -> check CRC Calculation for WH1080

  • Get
    • version
      return the SIGNALduino firmware version

    • raw
      Issue a SIGNALduino firmware command, and wait for one line of 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

    • cmds
      Depending on the firmware installed, SIGNALduinos have a different set of possible commands. Please refer to the sourcecode of the firmware of your SIGNALduino to interpret the response of this command. See also the raw- command.

    • ITParms
      For sending IT Signals for wireless switches, the number of repeats and the base duration can be set. With the get command, you can verify what is programmed into the uC.

    SET
    • ITClock
      Sets the clock which is used to send the signal for IT switches. (Default is 300)

    • raw
      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

    • reset
      This will do a reset of the usb port and normaly causes to reset the uC connected.

    • close
      Closes the connection to the device.

    • flash [hexFile]
      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. There are some requirements:
      • avrdude must be installed on the host
        On a Raspberry PI this can be done with: sudo apt-get install avrdude
      • the hardware attribute must be set if using any other hardware as an Arduino nano
        This attribute defines the command, that gets sent to avrdude to flash the uC.

    • sendMsg
      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.

      Please note, that this command will work only for MU or MS protocols. You can't transmit manchester data this way.

      Input args are:

      P#binarydata#R#C (#C is optional)
      Example: 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;


    • enableMessagetype
      Allows you to enable the message processing for
      • messages with sync (syncedMS),
      • messages without a sync pulse (unsyncedMU)
      • manchester encoded messages (manchesterMC)
      The new state will be saved into the eeprom of your arduino.

    • disableMessagetype
      Allows you to disable the message processing for
      • messages with sync (syncedMS),
      • messages without a sync pulse (unsyncedMU)
      • manchester encoded messages (manchesterMC)
      The new state will be saved into the eeprom of your arduino.
    =end html =cut