Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
60 changes: 47 additions & 13 deletions FHEM/00_SIGNALduino.pm
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
# $Id: 00_SIGNALduino.pm 0 2026-01-16 10:03:31Z sidey79 $
# $Id: 00_SIGNALduino.pm 0 2026-01-18 22:55:34Z sidey79 $
# https://github.com/RFD-FHEM/RFFHEM/tree/master
# The module is inspired by the FHEMduino project and modified in serval ways for processing the incoming messages
# see http://www.fhemwiki.de/wiki/SIGNALDuino
Expand Down Expand Up @@ -37,14 +37,16 @@
use FHEM::Devices::SIGNALduino::SD_Message;
use FHEM::Devices::SIGNALduino::SD_Matchlist;
use List::Util qw(first);
use FHEM::Utility::MQTT2_Dispatcher qw(:DEFAULT);
use FHEM::Devices::SIGNALduino::SD_MQTT;

#$| = 1; #Puffern abschalten, Hilfreich fuer PEARL WARNINGS Search




use constant {
SDUINO_VERSION => '4.0.0', # Datum wird automatisch bei jedem pull request aktualisiert
SDUINO_VERSION => '4.0.0+20260118', # Datum wird automatisch bei jedem pull request aktualisiert
SDUINO_INIT_WAIT_XQ => 1.5, # wait disable device
SDUINO_INIT_WAIT => 2,
SDUINO_INIT_MAXRETRY => 3,
Expand Down Expand Up @@ -306,28 +308,40 @@
my ($hash, $def) = @_;
my @a =split m{\s+}xms, $def;

if(@a != 3) {
my $msg = 'Define, wrong parameter count: define <name> SIGNALduino {none | devicename[\@baudrate] | devicename\@directio | hostname:port}';
my $name = $a[0];
my $dev = $a[2];
if ($dev eq 'mqtt') {
if(@a < 3 || @a > 4) {
my $msg = 'Define, wrong mqtt syntax: define <name> SIGNALduino mqtt [<basetopic>]';
Log3 undef, 2, $msg;
return $msg;
}
my $basetopic = $a[3] // 'signalduino/v1';
$hash->{mqttSubscribe} = "$basetopic/state/messages";
} elsif(@a != 3) {
my $msg = 'Define, wrong parameter count: define <name> SIGNALduino {none | devicename[\@baudrate] | devicename\@directio | hostname:port | mqtt [<basetopic>]}';
Log3 undef, 2, $msg;
return $msg;
}

DevIo_CloseDev($hash);
my $name = $a[0];
my $dev = $a[2];
#$hash->{debugMethod}->(qq[dev: $dev]);
#my $hardware=AttrVal($name,'hardware','nano');
#$hash->{debugMethod}->(qq[hardware: $hardware]);

if($dev eq 'none') {
Log3 $name, 1, "$name: Define, device is none, commands will be echoed only";
$attr{$name}{dummy} = 1;
} elsif ($dev !~ m/\@/) {
} elsif ($dev !~ m/\@/ && $dev ne 'mqtt') {
Log3 $name, 5, "$name: Define, device is $dev";
if ( ($dev =~ m~^(?:/[^/ ]*)+?$~xms || $dev =~ m~^COM\d$~xms) ) # bei einer IP oder hostname wird kein \@57600 angehaengt
{
$dev .= '@57600'
} elsif ($dev !~ /@\d+$/ && ($dev !~ /^
(?: (?:[a-z0-9-]+(?:\.[a-z]{2,6})?)*|(?:(?:25[0-5]|2[0-4][0-9]|[01]?[0-9]?[0-9])\.){3}
(?:25[0-5]|2[0-4][0-9]|[01]?[0-9]?[0-9]))
: (?:6553[0-5]|655[0-2]\d|65[0-4]\d{2}|6[0-4]\d{3}|[1-5]\d{4}|[1-9]\d{0,3})$/xmsi) ) { # bei einer IP oder hostname wird kein \@57600 angehaengt{
my $msg = "Define, wrong hostname/port syntax ($dev): define <name> SIGNALduino {none | devicename[\@baudrate] | devicename\@directio | hostname:port}";
: (?:6553[0-5]|655[0-2]\d|65[0-4]\d{2}|6[0-4]\d{3}|[1-5]\d{4}|[1-9]\d{0,3})$/xmsi) ) {
my $msg = qq[Define, wrong hostname/port/mqtt syntax ($dev): define <name> SIGNALduino {none | devicename[\@<baudrate>] | devicename\@directio | hostname:port | mqtt [<basetopic>]}];
Log3 undef, 2, $msg;
$hash->{STATE} = 'error';
return $msg;
Expand All @@ -347,11 +361,16 @@

FHEM::Core::Timer::Helper::addTimer($name, time(), \&SIGNALduino_IdList,"sduino_IdList:$name",0 );

if($dev ne 'none') {
$ret = DevIo_OpenDev($hash, 0, \&SIGNALduino_DoInit, \&SIGNALduino_Connect);
} else {
if ($dev eq 'none') {
$hash->{DevState} = 'initialized';
readingsSingleUpdate($hash, 'state', 'opened', 1);
} elsif ($dev eq 'mqtt') {
$hash->{DevState} = 'initialized';
readingsSingleUpdate($hash, 'state', 'opened', 1);

FHEM::Devices::SIGNALduino::SD_MQTT::Init($name);
} else {
$ret = DevIo_OpenDev($hash, 0, \&SIGNALduino_DoInit, \&SIGNALduino_Connect);
}

$hash->{DMSG} = 'nothing';
Expand Down Expand Up @@ -384,6 +403,11 @@
my ($hash, $arg) = @_;
my $name = $hash->{NAME};

if (defined($hash->{Listener})) {
del_mqtt($hash->{Listener});
delete $hash->{Listener};
}

foreach my $d (sort keys %defs) {
if(defined($defs{$d}) &&
defined($defs{$d}{IODev}) &&
Expand Down Expand Up @@ -451,7 +475,7 @@

local $/=undef;
if (-e $logFile) {
open FILE, $logFile;

Check warning on line 478 in FHEM/00_SIGNALduino.pm

View workflow job for this annotation

GitHub Actions / perlcritic

[perlcritic] FHEM/00_SIGNALduino.pm#L478

Bareword file handle opened (See pages 202,204 of PBP)
Raw output
FHEM/00_SIGNALduino.pm:478:5:Bareword file handle opened (See pages 202,204 of PBP)

Check warning on line 478 in FHEM/00_SIGNALduino.pm

View workflow job for this annotation

GitHub Actions / perlcritic

[perlcritic] FHEM/00_SIGNALduino.pm#L478

Two-argument "open" used (See page 207 of PBP)
Raw output
FHEM/00_SIGNALduino.pm:478:5:Two-argument "open" used (See page 207 of PBP)
$hash->{helper}{avrdudelogs} .= "--- AVRDUDE ---------------------------------------------------------------------------------\n";
$hash->{helper}{avrdudelogs} .= <FILE>;
$hash->{helper}{avrdudelogs} .= "--- AVRDUDE ---------------------------------------------------------------------------------\n\n";
Expand Down Expand Up @@ -2471,7 +2495,7 @@
} else {
$hash->{logMethod}->($name, 5, "$name: Parse_MU, for MU protocol id $id, applying filterfunc $method");

no strict "refs";

Check warning on line 2498 in FHEM/00_SIGNALduino.pm

View workflow job for this annotation

GitHub Actions / perlcritic

[perlcritic] FHEM/00_SIGNALduino.pm#L2498

Stricture disabled (See page 429 of PBP)
Raw output
FHEM/00_SIGNALduino.pm:2498:11:Stricture disabled (See page 429 of PBP)
(my $count_changes,$rawData,my %patternListRaw_tmp) = $method->($name,$id,$rawData,%patternListRaw);
use strict "refs";

Expand Down Expand Up @@ -2961,7 +2985,7 @@
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);

Check warning on line 2988 in FHEM/00_SIGNALduino.pm

View workflow job for this annotation

GitHub Actions / perlcritic

[perlcritic] FHEM/00_SIGNALduino.pm#L2988

"select" used to emulate "sleep" (See page 168 of PBP)
Raw output
FHEM/00_SIGNALduino.pm:2988:3:"select" used to emulate "sleep" (See page 168 of PBP)
}

############################# package main
Expand All @@ -2986,7 +3010,7 @@
elsif( $aName eq 'MatchList' ) {
my $match_list;
if( $cmd eq 'set' ) {
$match_list = eval $aVal; ## Allow evaluation of hash object from "attr" string f.e. { '34:MYMODULE' => '^u99#.{9}' }

Check warning on line 3013 in FHEM/00_SIGNALduino.pm

View workflow job for this annotation

GitHub Actions / perlcritic

[perlcritic] FHEM/00_SIGNALduino.pm#L3013

Expression form of "eval" (See page 161 of PBP)
Raw output
FHEM/00_SIGNALduino.pm:3013:21:Expression form of "eval" (See page 161 of PBP)
if( $@ ) {
$hash->{logMethod}->($name, 2, $name .": Attr, $aVal: ". $@);
}
Expand Down Expand Up @@ -3106,7 +3130,7 @@

my $hash = $defs{$name};
my @dspec=devspec2array("DEF=.*fakelog");
my $lfn = $dspec[0];
my $lfn = $dspec[0] // "";
my $fn=$defs{$name}->{TYPE}."-Flash.log";

my $ret = "<div class='makeTable wide'><span>Information menu</span>
Expand Down Expand Up @@ -3395,7 +3419,7 @@
{
if (defined($evalFirst) && $evalFirst)
{
eval( $method->($obj,$name, @args));

Check warning on line 3422 in FHEM/00_SIGNALduino.pm

View workflow job for this annotation

GitHub Actions / perlcritic

[perlcritic] FHEM/00_SIGNALduino.pm#L3422

Expression form of "eval" (See page 161 of PBP)
Raw output
FHEM/00_SIGNALduino.pm:3422:7:Expression form of "eval" (See page 161 of PBP)
if($@) {
$hash->{logMethod}->($name, 5, "$name: callsub, Error: $funcname, has an error and will not be executed: $@ please report at github.");
return (0,undef);
Expand Down Expand Up @@ -3524,7 +3548,7 @@
{
#print"\t". $patternListRaw{$key}."($key) is intol of ".$buckets{$b_key}."($b_key) \n";
$cnt++;
eval "\$rawData =~ tr/$key/$b_key/";

Check warning on line 3551 in FHEM/00_SIGNALduino.pm

View workflow job for this annotation

GitHub Actions / perlcritic

[perlcritic] FHEM/00_SIGNALduino.pm#L3551

Expression form of "eval" (See page 161 of PBP)
Raw output
FHEM/00_SIGNALduino.pm:3551:9:Expression form of "eval" (See page 161 of PBP)

#if ($key == $msg_parts{clockidx})
#{
Expand Down Expand Up @@ -3593,7 +3617,7 @@
{
#print"\t". $patternListRaw{$key}."($key) is intol of ".$buckets{$b_key}."($b_key) \n";
$cnt++;
eval "\$rawData =~ tr/$key/$b_key/";

Check warning on line 3620 in FHEM/00_SIGNALduino.pm

View workflow job for this annotation

GitHub Actions / perlcritic

[perlcritic] FHEM/00_SIGNALduino.pm#L3620

Expression form of "eval" (See page 161 of PBP)
Raw output
FHEM/00_SIGNALduino.pm:3620:9:Expression form of "eval" (See page 161 of PBP)

#if ($key == $msg_parts{clockidx})
#{
Expand Down Expand Up @@ -4277,6 +4301,13 @@
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.<br><br>
</li>
</ul>
MQTT-connected devices:<br>
<ul>
<li>
<code>mqtt</code> uses MQTT as transport instead of serial or telnet.
<br><code><basetopic></code> is optional (default: <code>signalduino/v1</code>).
</li>
</ul>


<a name="SIGNALduinointernals"></a>
Expand Down Expand Up @@ -4870,6 +4901,9 @@
<u>WiFi-connected devices (SIGNALduino):</u><br><br>
Bei einem ESP8266 oder ESP32 wird anstelle des seriellen Ports die IP des SIGNALduino in der Form <code>xxx.xxx.xxx.xxx:23</code> angegeben. Dabei stellt die 23 den verwendeten Port dar.
<br><br>
<u>MQTT-connected devices:</u><br><br>
<code>mqtt</code> nutzt MQTT als Transportweg anstelle von seriell oder Telnet.
<br><code><basetopic></code> ist optional (Standard: <code>signalduino/v1</code>).
</ul>


Expand Down
3 changes: 2 additions & 1 deletion controls_signalduino.txt
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
UPD 2026-01-18_12:38:53 237102 FHEM/00_SIGNALduino.pm
UPD 2026-01-18_23:53:00 238429 FHEM/00_SIGNALduino.pm
UPD 2023-01-06_12:08:43 20082 FHEM/10_FS10.pm
UPD 2024-01-03_23:05:39 27250 FHEM/10_SD_GT.pm
UPD 2023-01-01_18:10:40 25403 FHEM/10_SD_Rojaflex.pm
Expand All @@ -17,5 +17,6 @@ UPD 2025-12-04_20:25:15 257338 FHEM/lib/SD_ProtocolData.pm
UPD 2024-01-06_20:21:35 81862 FHEM/lib/SD_Protocols.pm
UPD 2026-01-16_10:54:58 1821 lib/FHEM/Devices/SIGNALduino/SD_Clients.pm
UPD 2026-01-16_10:54:58 1364 lib/FHEM/Devices/SIGNALduino/SD_Logger.pm
UPD 2026-01-18_23:53:00 2280 lib/FHEM/Devices/SIGNALduino/SD_MQTT.pm
UPD 2026-01-16_10:54:58 4868 lib/FHEM/Devices/SIGNALduino/SD_Matchlist.pm
UPD 2026-01-16_10:54:58 6330 lib/FHEM/Devices/SIGNALduino/SD_Message.pm
82 changes: 82 additions & 0 deletions lib/FHEM/Devices/SIGNALduino/SD_MQTT.pm
Original file line number Diff line number Diff line change
@@ -0,0 +1,82 @@
# $Id: SD_MQTT.pm 0 2026-01-10 15:36:13Z sidey79 $
# The file is part of the SIGNALduino project.
# MQTT helper for SIGNALduino device messages.


package FHEM::Devices::SIGNALduino::SD_MQTT;

use strict;
use warnings;
use FHEM::Devices::SIGNALduino::SD_Logger;
use FHEM::Devices::SIGNALduino::SD_Message;
eval {
require FHEM::Utility::MQTT2_Dispatcher;
FHEM::Utility::MQTT2_Dispatcher->import(qw(:DEFAULT));
} ;
eval { require FHEM::Core::Timer::Helper; } ;

sub Init {
my $name = shift;
my $hash = $main::defs{$name};

if (!$main::init_done) {
FHEM::Devices::SIGNALduino::SD_Logger::Log($hash, 3, "$name: InitMQTT, init not done, delayed...");
FHEM::Core::Timer::Helper::addTimer($name, time() + 0, \&Init, $name, 0);
return;
}

my $mqtt_topic = $hash->{mqttSubscribe};
if (defined($mqtt_topic)) {
FHEM::Devices::SIGNALduino::SD_Logger::Log($hash, 3, "$name: InitMQTT, registering listener for $mqtt_topic");

if (defined($hash->{Listener})) {
del_mqtt($hash->{Listener});
delete $hash->{Listener};
}

$hash->{Listener} = on_mqtt($mqtt_topic, sub {
on_message($hash, @_);
}) or do {
FHEM::Devices::SIGNALduino::SD_Logger::Log($hash, 2, "$name: Error creating MQTT listener: $@");
};
} else {
FHEM::Devices::SIGNALduino::SD_Logger::Log($hash, 2, "$name: InitMQTT, no mqttSubscribe topic found");
}
}

sub on_message {
my ($hash, $topic, $value, $io_name) = @_;

# Process message topic
if ($topic =~ m{state/messages}) {
FHEM::Devices::SIGNALduino::SD_Message::json2Dispatch($value, $hash->{NAME});
return;
}
FHEM::Devices::SIGNALduino::SD_Logger::Log($hash, 5, "SIGNALduino MQTT message ignored: topic=$topic, value=$value");
return;
}

1;

=pod

=head1 NAME

FHEM::Devices::SIGNALduino::SD_MQTT - MQTT handler for SIGNALduino

=head1 SYNOPSIS

use FHEM::Devices::SIGNALduino::SD_MQTT;
FHEM::Devices::SIGNALduino::SD_MQTT::on_message($hash, $topic, $value, $io_name);

=head1 DESCRIPTION

Handles incoming MQTT messages for SIGNALduino.

=head1 FUNCTIONS

=head2 on_message($hash, $topic, $value, $io_name)

Callback function for MQTT2_Dispatcher.

=cut
71 changes: 57 additions & 14 deletions t/FHEM/00_SIGNALduino/01_SIGNALduino_Define.t
Original file line number Diff line number Diff line change
Expand Up @@ -112,7 +112,7 @@ InternalTimer(time()+0.8, sub {
},
{
DEF => q{Hostname:65536},
testname => q[Hostname|Linux|Invalid: port out of range],
testname => q[Hostname|Linux|Invalid:],
check => hash {
field STATE => q{error};
field DeviceName => U();
Expand All @@ -122,30 +122,31 @@ InternalTimer(time()+0.8, sub {
rValue => D(),
},
{
DEF => q{192:168:122:57:45476},
testname => q[IPv4|Linux|Invalid: colon instead of dot],
DEF => q{192:168:122:56:45476},
testname => q[Hostname|Linux|Invalid:],
check => hash {
field STATE => q{error};
field DeviceName => U();
field DEF => q{192:168:122:57:45476};
field DEF => q{192:168:122:56:45476};
etc();
},
rValue => D(),
},

{
DEF => q{192.168.122.56:44323},
testname => q[IPv4|Linux|Valid:],
testname => q[Hostname|Linux|Valid:IPv4],
check => hash {
field STATE => q{disconnected};
field DeviceName => q{192.168.122.56:44323};
field DEF => q{192.168.122.56:44323};
etc();
},
rValue => U(),
},
},
{
DEF => q{192.168.122:44323},
testname => q[IPv4|Linux|Invalid: ip incomplete],
testname => q[Hostname|Linux|Invalid:IPv4],
check => hash {
field STATE => q{error};
field DeviceName => U();
Expand All @@ -156,7 +157,7 @@ InternalTimer(time()+0.8, sub {
},
{
DEF => q{sernetgw:44323},
testname => q[Hostname|Linux|Valid:],
testname => q[Hostname|Linux|Valid:hostname],
check => hash {
field STATE => q{disconnected};
field DeviceName => q{sernetgw:44323};
Expand All @@ -167,7 +168,7 @@ InternalTimer(time()+0.8, sub {
},
{
DEF => q{sernetgw.local.host:44323},
testname => q[Hostname|Linux|Valid:],
testname => q[Hostname|Linux|Valid:hostname with domain],
check => hash {
field STATE => q{disconnected};
field DeviceName => q{sernetgw.local.host:44323};
Expand All @@ -178,24 +179,64 @@ InternalTimer(time()+0.8, sub {
},
{
DEF => q{sernetgw.local.host},
testname => q[Hostname|Linux|inValid: missing port],
testname => q[Hostname|Linux|inValid:hostname without port],
check => hash {
field STATE => q{error};
field DeviceName => U();
field DEF => q{sernetgw.local.host};
etc();
},
rValue => D(),
rValue => D(),
},
{
DEF => q{ESP-DB7D13-Testboard:23},
testname => q[Hostname|Linux|Valid:],
testname => q[Hostname|Linux|Valid:hostname with dash],
check => hash {
field STATE => q{disconnected};
field DeviceName => q{ESP-DB7D13-Testboard:23};
field DEF => q{ESP-DB7D13-Testboard:23};
etc();
},
rValue => U(),
},
{
plan => 3,
DEF => q{mqtt},
testname => q[MQTT|Valid: default basetopic],
check => hash {
field STATE => q{opened};
field DEF => q{mqtt};
field mqttSubscribe => q{signalduino/v1/state/messages};
field protocolObject => object {
prop isa => 'lib::SD_Protocols';
prop reftype => 'HASH';
call [qw(_logging testmessage 1)] => validator(sub {
return is(FhemTestUtils_gotLog(qr/\sdefTest:.*testmessage/),1,q[verify logging message]);
});
};
etc();
},
rValue => U(),
},

{
plan => 3,
DEF => q{mqtt mytopic},
testname => q[MQTT|Valid: with basetopic],
check => hash {
field STATE => q{opened};
field DeviceName => q{mqtt};
field DEF => q{mqtt mytopic};
field mqttSubscribe => q{mytopic/state/messages};
field protocolObject => object {
prop isa => 'lib::SD_Protocols';
prop reftype => 'HASH';
call [qw(_logging testmessage 1)] => validator(sub {
return is(FhemTestUtils_gotLog(qr/\sdefTest:.*testmessage/),1,q[verify logging message]);
});
};
etc();
},
rValue => U(),
},
{
Expand Down Expand Up @@ -270,7 +311,7 @@ InternalTimer(time()+0.8, sub {
rValue => U(),
},
);

while (@testDataset)
{
my $element = pop(@testDataset);
Expand All @@ -286,7 +327,9 @@ InternalTimer(time()+0.8, sub {
$hash{NAME} = $element->{NAME} // $name;
FhemTestUtils_resetLogs();
$hash{DEF} = $element->{DEF};
plan( $element->{plan} // 2 );
plan( $element->{plan} // 2 );

note "Testing with Name=$hash{NAME} TYPE=$hash{TYPE} DEF=$hash{DEF}";

my $ret = SIGNALduino_Define(\%hash,qq{$hash{NAME} $hash{TYPE} $hash{DEF} });
is ($ret, $element->{rValue}, 'check returnvalue SIGNALduino_Define');
Expand Down
Loading
Loading