Hauptmenü

Neueste Beiträge

#11
Kalendermodule / Aw: AbfallentsorgungBerlin BSR...
Letzter Beitrag von andies - 01 Januar 2026, 22:56:27
Ich habe diese Infos bei mir im Raum Info, du eben in Kalender. Ansonsten sieht das gut aus!

Du musst noch ein at definieren, das um 6 Uhr morgens Abfall aktualisiert. Erst dann ist das Ereignis da, welches die pushmsg usw auslöst. Ich löse das mit eine Modul von Peter A Henning, YAAHM. Sonst frage einfach ChatGPT, dass er dir ein at bastelt, das jeden Morgen 6 Uhr auslöst. Irgendwie so
defmod AbfallAusloeser at *06:00:00 set Abfall update
#12
Solaranlagen / Aw: 76_SolarForecast - Informa...
Letzter Beitrag von 300P - 01 Januar 2026, 22:39:18
Zitat von: DS_Starter am 01 Januar 2026, 18:32:56Im contrib befindet sich ein Update der 2.0.0.
Hauptaugenmerk liegt auf FANN.

Rückmeldung :
CPU beim RPI4 so um die 50-60 % ausgelastet beim Training
(normal waren es bislang so 20-30 %

"falsche" Fehlermeldung (Ist ja eigentlich Standardeinstellung)
The key 'aiConHiddenLayers=80‑40‑20' is not specified correctly. Please refer to the command reference.hab den Eintrag erst einmal außen vor gelassen.


attr-Eintrag:
aiTrainStart=3
aiStorageDuration=3600
aiTreesPV=30
aiConActivate=1
aiConAlpha=1
aiConHiddenLayers=80‑40‑20
aiConTrainStart=3:2
aiConActFunc=GAUSSIAN
aiConLearnRate=0.001
aiConMomentum=0.7
aiConShuffleMode=1
aiConShufflePeriod=10
aiConSteepness=0.9
aiConTrainAlgo=RPROP

#13
Solaranlagen / Aw: 76_SolarForecast - Informa...
Letzter Beitrag von DS_Starter - 01 Januar 2026, 22:37:24
@Dieter,

Zitatsag mal, (wie) kann man denn mit einer Instanz von SolarForecast zwischen der alten und der neuen KI-basierten Verbrauchsprognose wechseln - oder macht das keinen Sinn?
Ja, kann man und macht durchaus Sinn (vor allem in der Test/Findungsphase) bzw. ist nicht schädlich.
Dazu einfach von:

 attr <Name> aiControl aiConActivate=1
nach

 attr <Name> aiControl aiConActivate=2
wechseln wie jetzt im Wiki beschrieben.

Oder als 2. Variante mit dem

 attr <Name> aiControl aiConAlpha <Wert>
die Gewichtung zwischen 0 und 1 wechseln.


LG,
Heiko
 

#14
ESP Familie / Aw: PWM mit ESP8266
Letzter Beitrag von frober - 01 Januar 2026, 22:34:15
Ich habe so was ähnliches mal mit einem MCP4822 (12 bit digital/analog Wandler) gemacht.

Ob der von Tasmota unterstützt wird weiß ich nicht.
Er wird über SPI angesteuert.

Ich habe einen Arduino benutzt.
Die verwendete lib war einfach, da kann man direkt die Milivolt angeben.
#15
Kalendermodule / Aw: AbfallentsorgungBerlin BSR...
Letzter Beitrag von mfeske - 01 Januar 2026, 22:31:57
Super Erklärung das hat für mich etwas Licht ins Dunkel gebracht auch wenn ich es alleine nicht zustande bekommen würde.
Ich muss also eigentlich nur den einen Block anpassen. Bei dem verstehe ich aber nicht woher der Raum Info kommt (hab ich jetzt beim machen gefunden) der bei mir Kalender wäre.
Auch nicht ganz klar um welche Uhrzeit die Nachricht kommt.

my $display = join(", ",@morgen);;\
 my $pushmsg = 'Morgen: '.$display;;\
 fhem("attr -silent Abfall stateFormat <span style='color:#cc0000'>$display</span>");;\
 fhem("attr -silent Abfall room Kalender");;;;fhem("set pushmsg msg 🚮: $pushmsg");;\
#17
Solaranlagen / Aw: 76_SolarForecast - Informa...
Letzter Beitrag von lorisurfen - 01 Januar 2026, 22:30:07
Hallo zusammen,
ich habe meine Anlage (Plenticore 23kW-Peak mit Varta family Speicher 6kWh) in SF konfiguriert und möchte nun 2 consumer mit folgender Prio ansteuern
Prio1 Batteriespeicher soll spätestens bis Sonnenuntergang voll sein
Prio2 consumer01: Heizstab 3 kWh
Prio3 consumer02: Elektroheizung 1 kWh (Shelly)

SF schaltet consumer0x nach meinem Verständnis wenn Current_Surplus>power ist, bezüglich Prio1 würde ich also von Current_Surplus Current_PowerBatIn_01 abziehen, damit erst die Batterie geladen wird:
Ist die nachfolgende Implementierung so sinvoll oder gibt es eine einfachere Lösung über SF ?

Frage2: Wenn der Überschuss 1-2,9 kWh ist (also zuwenig Überschuss für consumer01), wird dann der consumer02 trotzdem angeschaltet obwohl consumer01 aus ist? Falls nicht wie könnte ich das über SF lösen, also wenn Überschuss < 3kWh, consumer02 angeschalten wird?

Shelly_EG_3 type=heater power=3000 icon=IR pcurr=power:W etotal=energy:Wh mode=can mintime=SunPath on=on off=off interruptable=1 swoncond=calcEnergieManager:sf_true:{main::Check_swoncond}sub
Check_swoncond{
my $consumer_id = sprintf "%02d", 1;
my $nom_power = FHEM::SolarForecast::ConsumerVal("SF01", $consumer_id, 'power', '');
my $SF01_Current_Surplus = ReadingsVal("SF01","Current_Surplus",0);
($SF01_Current_Surplus) = split(/\s+/, $SF01_Current_Surplus);
my $SF01_BatIn = ReadingsVal("SF01","Current_PowerBatIn_01",0);
($SF01_BatIn) = split(/\s+/, $SF01_BatIn);
my $surplusMinBat = $SF01_Current_Surplus - $SF01_BatIn;
if($surplusMinBat>$nom_power) {
        Log 3, "Check_swoncond: surplusMinBat=$surplusMinBat -> return 1";
return 1;}
else {
        Log 3, "check swoncond Surplus: $SF01_Current_Surplus BatIn $SF01_BatIn surplusMinBat: $surplusMinBat -> return 0 nomPower: $nom_power";
return 0;}
}

Danke und LG
Markus
#18
Solaranlagen / Aw: 76_SolarForecast - Informa...
Letzter Beitrag von 300P - 01 Januar 2026, 22:17:02
Zitat von: DS_Starter am 01 Januar 2026, 18:32:56Zur Zeit fehlt zum Bsp. der Anwesenheitsindikator der aktuell 0 sein müsste und demzufolge weniger Verbrauch indiziert wäre.

Das wäre bei mir (und evtl. anderen) ja schon vorhanden :)
(Fritzbox mit PRESENCE - hier Netzwerkweit über mehrere Fritzboxen)

defmod Handy_XY PRESENCE function {checkAllFritzMACpresent("xy:xy:xy:xy:xy:xy")} 60 60
attr Handy_XY event-min-interval presence:900
attr Handy_XY event-on-change-reading presence
attr Handy_XY group Anwesenheit
attr Handy_XY room Presence
#19
Heizungssteuerung/Raumklima / Aw: Panasonic Klimasteuerung m...
Letzter Beitrag von Wassilis - 01 Januar 2026, 22:04:55
Habe es mal über KI gelöst und ein Modul über KI schreiben lassen. Ergebnis kann sich sehen lassen. Funktioniert sehr gut.

##############################################
# $Id: 98_IntesisWMP.pm 2025-01-01 $
#
# FHEM Module for Intesis WMP Protocol (PA-AC-WMP-1 / INWMPPAN001I000)
# Controls air conditioning devices via TCP/ASCII protocol on port 3310
#
# Based on WMP Protocol Specification V1.11
#
package main;

use strict;
use warnings;
use IO::Socket::INET;
use Time::HiRes qw(gettimeofday);

# Module version
my $IntesisWMP_Version = "1.2.2";

# Constants
my $WMP_PORT = 3310;
my $WMP_TIMEOUT = 5;
my $WMP_KEEPALIVE_INTERVAL = 30;    # seconds between pings (must be < 60s)
my $WMP_UPDATE_INTERVAL = 60;      # seconds between full updates

##############################################
# Initialize module
##############################################
sub IntesisWMP_Initialize($) {
    my ($hash) = @_;

    $hash->{DefFn}      = "IntesisWMP_Define";
    $hash->{UndefFn}    = "IntesisWMP_Undef";
    $hash->{SetFn}      = "IntesisWMP_Set";
    $hash->{GetFn}      = "IntesisWMP_Get";
    $hash->{ReadFn}    = "IntesisWMP_Read";
    $hash->{ReadyFn}    = "IntesisWMP_Ready";
    $hash->{AttrFn}    = "IntesisWMP_Attr";
    $hash->{NotifyFn}  = "IntesisWMP_Notify";
   
    $hash->{AttrList} =
        "disable:0,1 " .
        "interval " .
        "updateInterval " .
        "timeout " .
        "username " .
        "password " .
        "unit " .
        "tempUnit:C,F " .
        "pollFunctions " .
        $readingFnAttributes;

    Log3 undef, 3, "IntesisWMP: Module initialized (Version $IntesisWMP_Version)";
}

##############################################
# Define device
##############################################
sub IntesisWMP_Define($$) {
    my ($hash, $def) = @_;
    my @a = split("[ \t][ \t]*", $def);

    return "Usage: define <name> IntesisWMP <host> [<port>]" if(@a < 3);

    my $name = $a[0];
    my $host = $a[2];
    my $port = $a[3] || $WMP_PORT;

    $hash->{HOST} = $host;
    $hash->{PORT} = $port;
    $hash->{UNIT} = 1;  # Default unit number
    $hash->{INTERVAL} = $WMP_KEEPALIVE_INTERVAL;
    $hash->{UPDATE_INTERVAL} = $WMP_UPDATE_INTERVAL;
    $hash->{TIMEOUT} = $WMP_TIMEOUT;
    $hash->{VERSION} = $IntesisWMP_Version;
    $hash->{NOTIFYDEV} = "global";
   
    # Initialize state
    $hash->{STATE} = "Initialized";
    $hash->{LAST_RECV} = 0;
    $hash->{LAST_SEND} = 0;
    $hash->{LAST_UPDATE} = 0;
    $hash->{CONNECTED} = 0;
    $hash->{BUFFER} = "";
   
    # Valid values for readings/settings
    $hash->{helper}{validModes} = {
        AUTO => 1, HEAT => 1, DRY => 1, FAN => 1, COOL => 1
    };
    $hash->{helper}{validFanSpeeds} = {
        AUTO => 1, 1 => 1, 2 => 1, 3 => 1, 4 => 1, 5 => 1,
        6 => 1, 7 => 1, 8 => 1, 9 => 1
    };
    $hash->{helper}{validVanes} = {
        AUTO => 1, 1 => 1, 2 => 1, 3 => 1, 4 => 1, 5 => 1,
        6 => 1, 7 => 1, 8 => 1, 9 => 1, SWING => 1
    };
   
    # Default poll functions
    $hash->{helper}{pollFunctions} = [qw(ONOFF MODE SETPTEMP AMBTEMP FANSP VANEUD VANELR)];

    Log3 $name, 3, "IntesisWMP ($name): Defined with host $host:$port";
   
    # Start connection after FHEM is initialized
    if($init_done) {
        IntesisWMP_Connect($hash);
    }
   
    return undef;
}

##############################################
# Handle FHEM notifications (global events)
##############################################
sub IntesisWMP_Notify($$) {
    my ($hash, $dev) = @_;
    my $name = $hash->{NAME};
   
    return if(AttrVal($name, "disable", 0));
   
    if($dev->{NAME} eq "global") {
        my $events = deviceEvents($dev, 1);
        return if(!$events);
       
        foreach my $event (@{$events}) {
            if($event =~ /^INITIALIZED$/) {
                Log3 $name, 4, "IntesisWMP ($name): FHEM initialized, connecting...";
                IntesisWMP_Connect($hash);
            }
        }
    }
    return undef;
}

##############################################
# Connect to the Intesis device
##############################################
sub IntesisWMP_Connect($) {
    my ($hash) = @_;
    my $name = $hash->{NAME};
   
    return if(AttrVal($name, "disable", 0));
   
    # Close existing connection if any
    IntesisWMP_Disconnect($hash);
   
    my $host = $hash->{HOST};
    my $port = $hash->{PORT};
    my $timeout = $hash->{TIMEOUT};
   
    Log3 $name, 3, "IntesisWMP ($name): Connecting to $host:$port...";
   
    # Create non-blocking connection
    my $conn = IO::Socket::INET->new(
        PeerAddr => $host,
        PeerPort => $port,
        Proto    => 'tcp',
        Timeout  => $timeout,
        Blocking => 0
    );
   
    if(!$conn) {
        Log3 $name, 2, "IntesisWMP ($name): Connection failed: $!";
        $hash->{STATE} = "disconnected";
        $hash->{CONNECTED} = 0;
        readingsSingleUpdate($hash, "state", "disconnected", 1);
       
        # Schedule reconnect
        RemoveInternalTimer($hash, "IntesisWMP_Connect");
        InternalTimer(gettimeofday() + 60, "IntesisWMP_Connect", $hash, 0);
        return;
    }
   
    # Store connection handle
    $hash->{FD} = $conn->fileno();
    $hash->{CD} = $conn;
    $hash->{CONNECTED} = 1;
    $hash->{STATE} = "connected";
    $hash->{BUFFER} = "";
    $hash->{LAST_RECV} = gettimeofday();
   
    $selectlist{$name} = $hash;
   
    Log3 $name, 3, "IntesisWMP ($name): Connected to $host:$port";
    readingsSingleUpdate($hash, "state", "connected", 1);
   
    # Login if credentials are set
    my $username = AttrVal($name, "username", "");
    my $password = AttrVal($name, "password", "");
    if($username && $password) {
        IntesisWMP_Login($hash, $username, $password);
    }
   
    # Request device info
    IntesisWMP_Send($hash, "ID");
   
    # Start keepalive timer
    IntesisWMP_StartKeepaliveTimer($hash);
   
    # Start update timer (initial update after 2 seconds)
    IntesisWMP_StartUpdateTimer($hash, 2);
   
    return;
}

##############################################
# Start keepalive timer
##############################################
sub IntesisWMP_StartKeepaliveTimer($) {
    my ($hash) = @_;
    my $name = $hash->{NAME};
   
    RemoveInternalTimer($hash, "IntesisWMP_Keepalive");
    my $interval = AttrVal($name, "interval", $hash->{INTERVAL});
    Log3 $name, 4, "IntesisWMP ($name): Starting keepalive timer (interval: ${interval}s)";
    InternalTimer(gettimeofday() + $interval, "IntesisWMP_Keepalive", $hash, 0);
}

##############################################
# Start update timer
##############################################
sub IntesisWMP_StartUpdateTimer($;$) {
    my ($hash, $delay) = @_;
    my $name = $hash->{NAME};
   
    RemoveInternalTimer($hash, "IntesisWMP_UpdateTimer");
    my $interval = AttrVal($name, "updateInterval", $hash->{UPDATE_INTERVAL});
    $delay = $interval if(!defined($delay));
   
    Log3 $name, 4, "IntesisWMP ($name): Starting update timer (delay: ${delay}s, interval: ${interval}s)";
    InternalTimer(gettimeofday() + $delay, "IntesisWMP_UpdateTimer", $hash, 0);
}

##############################################
# Disconnect from device
##############################################
sub IntesisWMP_Disconnect($) {
    my ($hash) = @_;
    my $name = $hash->{NAME};
   
    Log3 $name, 4, "IntesisWMP ($name): Disconnecting, removing all timers...";
   
    RemoveInternalTimer($hash, "IntesisWMP_Keepalive");
    RemoveInternalTimer($hash, "IntesisWMP_Connect");
    RemoveInternalTimer($hash, "IntesisWMP_UpdateTimer");
   
    # Remove staggered poll timers
    foreach my $func (qw(ONOFF MODE SETPTEMP AMBTEMP FANSP VANEUD VANELR ERRSTATUS ERRCODE)) {
        RemoveInternalTimer("$name:poll:$func");
    }
   
    if($hash->{CD}) {
        Log3 $name, 4, "IntesisWMP ($name): Closing connection...";
        delete $selectlist{$name};
        close($hash->{CD});
        delete $hash->{CD};
        delete $hash->{FD};
    }
   
    $hash->{CONNECTED} = 0;
    $hash->{STATE} = "disconnected";
   
    return;
}

##############################################
# Undefine device
##############################################
sub IntesisWMP_Undef($$) {
    my ($hash, $arg) = @_;
    my $name = $hash->{NAME};
   
    IntesisWMP_Disconnect($hash);
   
    Log3 $name, 3, "IntesisWMP ($name): Device undefined";
    return undef;
}

##############################################
# Send command to device
##############################################
sub IntesisWMP_Send($$) {
    my ($hash, $cmd) = @_;
    my $name = $hash->{NAME};
   
    if(!$hash->{CD} || !$hash->{CONNECTED}) {
        Log3 $name, 3, "IntesisWMP ($name): Not connected, cannot send: $cmd";
        IntesisWMP_Connect($hash);
        return "Not connected";
    }
   
    my $msg = $cmd . "\r\n";
   
    Log3 $name, 4, "IntesisWMP ($name): Sending: $cmd";
   
    my $written = syswrite($hash->{CD}, $msg);
    if(!defined($written)) {
        Log3 $name, 2, "IntesisWMP ($name): Write error: $!";
        IntesisWMP_Disconnect($hash);
        readingsSingleUpdate($hash, "state", "disconnected", 1);
        IntesisWMP_Connect($hash);
        return "Write error";
    }
   
    $hash->{LAST_SEND} = gettimeofday();
    return undef;
}

##############################################
# Read data from device (called by select loop)
##############################################
sub IntesisWMP_Read($) {
    my ($hash) = @_;
    my $name = $hash->{NAME};
   
    my $buf;
    my $len = sysread($hash->{CD}, $buf, 1024);
   
    if(!defined($len) || $len <= 0) {
        Log3 $name, 3, "IntesisWMP ($name): Connection closed by remote";
        IntesisWMP_Disconnect($hash);
        readingsSingleUpdate($hash, "state", "disconnected", 1);
        # Schedule reconnect
        InternalTimer(gettimeofday() + 10, "IntesisWMP_Connect", $hash, 0);
        return;
    }
   
    $hash->{LAST_RECV} = gettimeofday();
    $hash->{BUFFER} .= $buf;
   
    # Process complete lines
    while($hash->{BUFFER} =~ s/^([^\r\n]*)\r?\n//) {
        my $line = $1;
        next if($line eq "");
       
        Log3 $name, 4, "IntesisWMP ($name): Received: $line";
        IntesisWMP_Parse($hash, $line);
    }
   
    return;
}

##############################################
# Ready function for reconnect
##############################################
sub IntesisWMP_Ready($) {
    my ($hash) = @_;
    my $name = $hash->{NAME};
   
    return if(AttrVal($name, "disable", 0));
   
    if(!$hash->{CD} || !$hash->{CONNECTED}) {
        return IntesisWMP_Connect($hash);
    }
   
    return 0;
}

##############################################
# Parse received data
##############################################
sub IntesisWMP_Parse($$) {
    my ($hash, $line) = @_;
    my $name = $hash->{NAME};
   
    $line = uc($line);  # Protocol is case-insensitive
   
    # ACK response
    if($line eq "ACK") {
        Log3 $name, 5, "IntesisWMP ($name): Command acknowledged";
        return;
    }
   
    # ERR response
    if($line eq "ERR") {
        Log3 $name, 3, "IntesisWMP ($name): Error response received";
        readingsSingleUpdate($hash, "lastError", "ERR", 1);
        return;
    }
   
    # ID response: Model, MAC, IP, Protocol, Version, RSSI, DeviceName, SecurityLevel, Generation
    if($line =~ /^ID:\s*(.+)/) {
        my @parts = split(/,/, $1);
        readingsBeginUpdate($hash);
        readingsBulkUpdate($hash, "model", IntesisWMP_trim($parts[0])) if(defined $parts[0]);
        readingsBulkUpdate($hash, "mac", IntesisWMP_trim($parts[1])) if(defined $parts[1]);
        readingsBulkUpdate($hash, "ip", IntesisWMP_trim($parts[2])) if(defined $parts[2]);
        readingsBulkUpdate($hash, "protocol", IntesisWMP_trim($parts[3])) if(defined $parts[3]);
        readingsBulkUpdate($hash, "firmware", IntesisWMP_trim($parts[4])) if(defined $parts[4]);
        readingsBulkUpdate($hash, "rssi", IntesisWMP_trim($parts[5])) if(defined $parts[5]);
        readingsBulkUpdate($hash, "deviceName", IntesisWMP_trim($parts[6])) if(defined $parts[6]);
        readingsBulkUpdate($hash, "securityLevel", IntesisWMP_trim($parts[7])) if(defined $parts[7]);
        readingsEndUpdate($hash, 1);
        return;
    }
   
    # INFO response: Function, Unit, InstallerId, Value
    if($line =~ /^INFO:\s*(\w+),\s*(\d+),\s*(\w+),\s*(.+)/) {
        my ($func, $unit, $installer, $value) = ($1, $2, $3, IntesisWMP_trim($4));
        IntesisWMP_UpdateReading($hash, $func, $value);
        return;
    }
   
    # CHN (spontaneous change) or GET response: Unit:Function,Value
    if($line =~ /^(CHN|GET),\s*(\d+):(\w+),\s*(.+)/) {
        my ($cmd, $unit, $func, $value) = ($1, $2, $3, IntesisWMP_trim($4));
        Log3 $name, 4, "IntesisWMP ($name): Parsed $cmd response: unit=$unit, func=$func, value=$value";
        IntesisWMP_UpdateReading($hash, $func, $value);
        return;
    }
   
    # Alternative CHN/GET format
    if($line =~ /^(\w+),\s*(\d+):(\w+),\s*(.+)/) {
        my ($cmd, $unit, $func, $value) = ($1, $2, $3, IntesisWMP_trim($4));
        if($cmd eq "CHN" || $cmd eq "GET" || $cmd eq "SET") {
            IntesisWMP_UpdateReading($hash, $func, $value);
        }
        return;
    }
   
    # LIMITS response
    if($line =~ /^LIMITS:\s*(\w+),\s*\[(.+)\]/) {
        my ($func, $values) = ($1, $2);
        readingsSingleUpdate($hash, "limits_" . lc($func), $values, 1);
        return;
    }
   
    # LOGIN response
    if($line =~ /^LOGIN:\s*(.+)/) {
        my $response = IntesisWMP_trim($1);
        if($response eq "OK") {
            Log3 $name, 3, "IntesisWMP ($name): Login successful";
            readingsSingleUpdate($hash, "login", "ok", 1);
        } else {
            Log3 $name, 2, "IntesisWMP ($name): Login failed: $response";
            readingsSingleUpdate($hash, "login", "failed", 1);
        }
        return;
    }
   
    # DISCOVER response (usually via UDP, but handle anyway)
    if($line =~ /^DISCOVER/ || $line =~ /^INWMP/) {
        Log3 $name, 4, "IntesisWMP ($name): Discover response: $line";
        return;
    }
   
    # PING response
    if($line eq "PONG") {
        Log3 $name, 5, "IntesisWMP ($name): Pong received";
        return;
    }
   
    Log3 $name, 4, "IntesisWMP ($name): Unhandled message: $line";
}

##############################################
# Update a reading based on function name
##############################################
sub IntesisWMP_UpdateReading($$$) {
    my ($hash, $func, $value) = @_;
    my $name = $hash->{NAME};
   
    $func = uc($func);
    $value = uc($value) unless($value =~ /^-?\d+$/);
   
    Log3 $name, 4, "IntesisWMP ($name): Updating reading for $func = $value";
   
    readingsBeginUpdate($hash);
   
    if($func eq "ONOFF") {
        readingsBulkUpdate($hash, "power", lc($value), 1);
        # Update state based on power and mode
        my $mode = ReadingsVal($name, "mode", "");
        if($value eq "ON" && $mode) {
            readingsBulkUpdate($hash, "state", lc($mode), 1);
        } else {
            readingsBulkUpdate($hash, "state", lc($value), 1);
        }
    }
    elsif($func eq "MODE") {
        readingsBulkUpdate($hash, "mode", lc($value), 1);
        my $power = ReadingsVal($name, "power", "off");
        if($power eq "on") {
            readingsBulkUpdate($hash, "state", lc($value), 1);
        }
    }
    elsif($func eq "SETPTEMP") {
        # Temperature is multiplied by 10
        my $temp = $value / 10.0;
        readingsBulkUpdate($hash, "desiredTemperature", $temp, 1);
    }
    elsif($func eq "AMBTEMP") {
        # Ambient temperature is multiplied by 10
        my $temp = $value / 10.0;
        Log3 $name, 3, "IntesisWMP ($name): AMBTEMP received: raw=$value, converted=$temp";
        readingsBulkUpdate($hash, "temperature", $temp, 1);
    }
    elsif($func eq "FANSP") {
        readingsBulkUpdate($hash, "fanSpeed", lc($value), 1);
    }
    elsif($func eq "VANEUD") {
        readingsBulkUpdate($hash, "vaneVertical", lc($value), 1);
    }
    elsif($func eq "VANELR") {
        readingsBulkUpdate($hash, "vaneHorizontal", lc($value), 1);
    }
    elsif($func eq "ERRSTATUS") {
        readingsBulkUpdate($hash, "errorStatus", $value, 1);
    }
    elsif($func eq "ERRCODE") {
        readingsBulkUpdate($hash, "errorCode", $value, 1);
    }
    else {
        readingsBulkUpdate($hash, lc($func), $value, 1);
    }
   
    readingsEndUpdate($hash, 1);
}

##############################################
# Keepalive timer - sends PING to prevent timeout
##############################################
sub IntesisWMP_Keepalive($) {
    my ($hash) = @_;
    my $name = $hash->{NAME};
   
    return if(AttrVal($name, "disable", 0));
   
    if($hash->{CONNECTED} && $hash->{CD}) {
        Log3 $name, 5, "IntesisWMP ($name): Sending keepalive PING";
        IntesisWMP_Send($hash, "PING");
       
        # Schedule next keepalive
        IntesisWMP_StartKeepaliveTimer($hash);
    } else {
        Log3 $name, 4, "IntesisWMP ($name): Keepalive skipped - not connected";
    }
   
    return;
}

##############################################
# Update timer - periodically polls all values
##############################################
sub IntesisWMP_UpdateTimer($) {
    my ($hash) = @_;
    my $name = $hash->{NAME};
   
    return if(AttrVal($name, "disable", 0));
   
    Log3 $name, 3, "IntesisWMP ($name): Update timer triggered, CONNECTED=$hash->{CONNECTED}";
   
    if($hash->{CONNECTED} && $hash->{CD}) {
        Log3 $name, 3, "IntesisWMP ($name): Executing periodic update - polling all functions";
       
        # Poll all functions
        IntesisWMP_GetAll($hash);
       
        $hash->{LAST_UPDATE} = gettimeofday();
        readingsSingleUpdate($hash, ".lastUpdate", scalar(localtime()), 0);
       
        # Schedule next update
        IntesisWMP_StartUpdateTimer($hash);
    } else {
        Log3 $name, 3, "IntesisWMP ($name): Update timer - not connected, skipping poll";
        # If not connected, try to reconnect
        IntesisWMP_Connect($hash);
    }
   
    return;
}

##############################################
# Get all current values from device
# Uses staggered timing to avoid overwhelming the device
##############################################
sub IntesisWMP_GetAll($) {
    my ($hash) = @_;
    my $name = $hash->{NAME};
    my $unit = AttrVal($name, "unit", $hash->{UNIT});
   
    # Get poll functions from attribute or use default
    my $pollFuncsAttr = AttrVal($name, "pollFunctions", "");
    my @functions;
   
    if($pollFuncsAttr) {
        @functions = split(/[,\s]+/, uc($pollFuncsAttr));
    } else {
        @functions = @{$hash->{helper}{pollFunctions}};
    }
   
    Log3 $name, 3, "IntesisWMP ($name): Polling " . scalar(@functions) . " functions with staggered timing";
   
    # Send commands with 0.5 second delays to avoid overwhelming the device
    my $delay = 0;
    foreach my $func (@functions) {
        my $cmd = "GET,$unit:$func";
        if($delay == 0) {
            # Send first command immediately
            IntesisWMP_Send($hash, $cmd);
        } else {
            # Schedule subsequent commands with delays
            InternalTimer(gettimeofday() + $delay, sub {
                IntesisWMP_Send($hash, $cmd) if($hash->{CONNECTED});
            }, "$name:poll:$func", 0);
        }
        $delay += 0.5;  # 500ms between each command
    }
   
    return;
}

##############################################
# Login to device (if security is enabled)
##############################################
sub IntesisWMP_Login($$$) {
    my ($hash, $username, $password) = @_;
    my $name = $hash->{NAME};
   
    Log3 $name, 4, "IntesisWMP ($name): Logging in...";
    IntesisWMP_Send($hash, "LOGIN,$username,$password");
   
    return;
}

##############################################
# Set commands
##############################################
sub IntesisWMP_Set($@) {
    my ($hash, @a) = @_;
    my $name = shift @a;
    my $cmd = shift @a;
    my $value = join(" ", @a);
   
    return "\"set $name\" needs at least one argument" if(!defined($cmd));
   
    my $unit = AttrVal($name, "unit", $hash->{UNIT});
   
    # Build list of valid set commands
    my $setList = "on:noArg off:noArg " .
                  "mode:auto,heat,dry,fan,cool " .
                  "desiredTemperature:slider,16,0.5,32,1 " .
                  "fanSpeed:auto,1,2,3,4,5,6,7,8,9 " .
                  "vaneVertical:auto,1,2,3,4,5,swing " .
                  "vaneHorizontal:auto,1,2,3,4,5,swing " .
                  "reconnect:noArg " .
                  "statusRequest:noArg " .
                  "raw";
   
    if($cmd eq "on") {
        return IntesisWMP_Send($hash, "SET,$unit:ONOFF,ON");
    }
    elsif($cmd eq "off") {
        return IntesisWMP_Send($hash, "SET,$unit:ONOFF,OFF");
    }
    elsif($cmd eq "mode") {
        $value = uc($value);
        if(!$hash->{helper}{validModes}{$value}) {
            return "Invalid mode. Valid modes: auto, heat, dry, fan, cool";
        }
        return IntesisWMP_Send($hash, "SET,$unit:MODE,$value");
    }
    elsif($cmd eq "desiredTemperature" || $cmd eq "setptemp") {
        # Convert temperature to protocol format (multiply by 10)
        if($value !~ /^[\d.]+$/) {
            return "Invalid temperature value";
        }
        my $temp = int($value * 10);
        return IntesisWMP_Send($hash, "SET,$unit:SETPTEMP,$temp");
    }
    elsif($cmd eq "fanSpeed" || $cmd eq "fanspeed") {
        $value = uc($value);
        if(!$hash->{helper}{validFanSpeeds}{$value}) {
            return "Invalid fan speed. Valid values: auto, 1-9";
        }
        return IntesisWMP_Send($hash, "SET,$unit:FANSP,$value");
    }
    elsif($cmd eq "vaneVertical" || $cmd eq "vaneud") {
        $value = uc($value);
        if(!$hash->{helper}{validVanes}{$value}) {
            return "Invalid vane position. Valid values: auto, 1-9, swing";
        }
        return IntesisWMP_Send($hash, "SET,$unit:VANEUD,$value");
    }
    elsif($cmd eq "vaneHorizontal" || $cmd eq "vanelr") {
        $value = uc($value);
        if(!$hash->{helper}{validVanes}{$value}) {
            return "Invalid vane position. Valid values: auto, 1-9, swing";
        }
        return IntesisWMP_Send($hash, "SET,$unit:VANELR,$value");
    }
    elsif($cmd eq "reconnect") {
        IntesisWMP_Disconnect($hash);
        return IntesisWMP_Connect($hash);
    }
    elsif($cmd eq "statusRequest") {
        Log3 $name, 3, "IntesisWMP ($name): Manual status request triggered";
        IntesisWMP_GetAll($hash);
        return undef;
    }
    elsif($cmd eq "raw") {
        return IntesisWMP_Send($hash, $value);
    }
   
    return "Unknown command $cmd, choose one of $setList";
}

##############################################
# Get commands
##############################################
sub IntesisWMP_Get($@) {
    my ($hash, @a) = @_;
    my $name = shift @a;
    my $cmd = shift @a;
    my $value = join(" ", @a);
   
    return "\"get $name\" needs at least one argument" if(!defined($cmd));
   
    my $unit = AttrVal($name, "unit", $hash->{UNIT});
   
    # Build list of valid get commands
    my $getList = "update:noArg " .
                  "deviceInfo:noArg " .
                  "power:noArg " .
                  "mode:noArg " .
                  "desiredTemperature:noArg " .
                  "temperature:noArg " .
                  "fanSpeed:noArg " .
                  "vaneVertical:noArg " .
                  "vaneHorizontal:noArg " .
                  "limits:noArg " .
                  "raw";
   
    if($cmd eq "update") {
        Log3 $name, 3, "IntesisWMP ($name): Manual update triggered via get";
        IntesisWMP_GetAll($hash);
        return undef;
    }
    elsif($cmd eq "deviceInfo") {
        IntesisWMP_Send($hash, "ID");
        return undef;
    }
    elsif($cmd eq "power") {
        IntesisWMP_Send($hash, "GET,$unit:ONOFF");
        return undef;
    }
    elsif($cmd eq "mode") {
        IntesisWMP_Send($hash, "GET,$unit:MODE");
        return undef;
    }
    elsif($cmd eq "desiredTemperature" || $cmd eq "setptemp") {
        IntesisWMP_Send($hash, "GET,$unit:SETPTEMP");
        return undef;
    }
    elsif($cmd eq "temperature" || $cmd eq "ambtemp") {
        IntesisWMP_Send($hash, "GET,$unit:AMBTEMP");
        return undef;
    }
    elsif($cmd eq "fanSpeed" || $cmd eq "fanspeed") {
        IntesisWMP_Send($hash, "GET,$unit:FANSP");
        return undef;
    }
    elsif($cmd eq "vaneVertical" || $cmd eq "vaneud") {
        IntesisWMP_Send($hash, "GET,$unit:VANEUD");
        return undef;
    }
    elsif($cmd eq "vaneHorizontal" || $cmd eq "vanelr") {
        IntesisWMP_Send($hash, "GET,$unit:VANELR");
        return undef;
    }
    elsif($cmd eq "limits") {
        my @funcs = qw(ONOFF MODE FANSP VANEUD VANELR SETPTEMP);
        foreach my $func (@funcs) {
            IntesisWMP_Send($hash, "LIMITS:$func");
        }
        return undef;
    }
    elsif($cmd eq "raw") {
        IntesisWMP_Send($hash, $value);
        return undef;
    }
   
    return "Unknown command $cmd, choose one of $getList";
}

##############################################
# Attribute handling
##############################################
sub IntesisWMP_Attr(@) {
    my ($cmd, $name, $attrName, $attrVal) = @_;
    my $hash = $defs{$name};
   
    if($attrName eq "disable") {
        if($cmd eq "set" && $attrVal) {
            IntesisWMP_Disconnect($hash);
            $hash->{STATE} = "disabled";
        }
        elsif($cmd eq "del" || ($cmd eq "set" && !$attrVal)) {
            $hash->{STATE} = "Initialized";
            IntesisWMP_Connect($hash) if($init_done);
        }
    }
    elsif($attrName eq "interval") {
        if($cmd eq "set") {
            $attrVal = 10 if($attrVal < 10);
            $attrVal = 55 if($attrVal > 55);  # Must be < 60s
            $hash->{INTERVAL} = $attrVal;
            # Restart timer with new interval
            IntesisWMP_StartKeepaliveTimer($hash) if($hash->{CONNECTED});
        }
        else {
            $hash->{INTERVAL} = $WMP_KEEPALIVE_INTERVAL;
        }
    }
    elsif($attrName eq "updateInterval") {
        if($cmd eq "set") {
            $attrVal = 10 if($attrVal < 10);
            $hash->{UPDATE_INTERVAL} = $attrVal;
            Log3 $name, 3, "IntesisWMP ($name): Update interval changed to ${attrVal}s";
           
            # Restart update timer with new interval
            IntesisWMP_StartUpdateTimer($hash) if($hash->{CONNECTED});
        }
        else {
            $hash->{UPDATE_INTERVAL} = $WMP_UPDATE_INTERVAL;
        }
    }
    elsif($attrName eq "timeout") {
        if($cmd eq "set") {
            $hash->{TIMEOUT} = $attrVal;
        }
        else {
            $hash->{TIMEOUT} = $WMP_TIMEOUT;
        }
    }
    elsif($attrName eq "unit") {
        if($cmd eq "set") {
            $hash->{UNIT} = $attrVal;
        }
        else {
            $hash->{UNIT} = 1;
        }
    }
    elsif($attrName eq "pollFunctions") {
        if($cmd eq "set" && $attrVal) {
            # Validate functions
            my @funcs = split(/[,\s]+/, uc($attrVal));
            my @validFuncs = qw(ONOFF MODE SETPTEMP AMBTEMP FANSP VANEUD VANELR ERRSTATUS ERRCODE);
            my %validHash = map { $_ => 1 } @validFuncs;
            foreach my $func (@funcs) {
                if(!$validHash{$func}) {
                    return "Invalid function: $func. Valid functions: " . join(", ", @validFuncs);
                }
            }
        }
    }
   
    return undef;
}

##############################################
# Helper: trim whitespace
##############################################
sub IntesisWMP_trim($) {
    my ($str) = @_;
    return "" if(!defined($str));
    $str =~ s/^\s+//;
    $str =~ s/\s+$//;
    return $str;
}

1;

=pod
=item device
=item summary    Control Intesis WMP AC devices (PA-AC-WMP-1)
=item summary_DE Steuerung von Intesis WMP Klimaanlagen (PA-AC-WMP-1)

=begin html

<a name="IntesisWMP"></a>
<h3>IntesisWMP</h3>
<ul>
  This module provides control for air conditioning devices via the Intesis WMP
  (WiFi Module for Panasonic AC) using the WMP ASCII protocol over TCP.<br><br>
 
  Supported devices include: PA-AC-WMP-1 (INWMPPAN001I000)<br><br>
 
  The module connects to the device on TCP port 3310 and maintains the connection
  with periodic keepalive messages. It handles spontaneous state change notifications
  from the device (CHN messages) and periodically polls for updates.<br><br>
 
  <a name="IntesisWMP_define"></a>
  <b>Define</b>
  <ul>
    <code>define &lt;name&gt; IntesisWMP &lt;host&gt; [&lt;port&gt;]</code><br><br>
   
    <li><code>host</code>: IP address or hostname of the Intesis device</li>
    <li><code>port</code>: TCP port (default: 3310)</li><br>
   
    Example:<br>
    <code>define myAC IntesisWMP 192.168.1.100</code><br>
    <code>define myAC IntesisWMP ac.local 3310</code>
  </ul><br>

  <a name="IntesisWMP_set"></a>
  <b>Set</b>
  <ul>
    <li><b>on</b> - Turn AC on</li>
    <li><b>off</b> - Turn AC off</li>
    <li><b>mode</b> auto|heat|dry|fan|cool - Set operating mode</li>
    <li><b>desiredTemperature</b> &lt;temp&gt; - Set target temperature (16-32C)</li>
    <li><b>fanSpeed</b> auto|1-9 - Set fan speed</li>
    <li><b>vaneVertical</b> auto|1-9|swing - Set vertical vane position</li>
    <li><b>vaneHorizontal</b> auto|1-9|swing - Set horizontal vane position</li>
    <li><b>statusRequest</b> - Manually trigger an update of all readings</li>
    <li><b>reconnect</b> - Reconnect to the device</li>
    <li><b>raw</b> &lt;command&gt; - Send raw WMP command</li>
  </ul><br>
 
  <a name="IntesisWMP_get"></a>
  <b>Get</b>
  <ul>
    <li><b>update</b> - Refresh all readings from device</li>
    <li><b>deviceInfo</b> - Get device identification (ID command)</li>
    <li><b>power</b> - Get power state</li>
    <li><b>mode</b> - Get current mode</li>
    <li><b>desiredTemperature</b> - Get target temperature</li>
    <li><b>temperature</b> - Get ambient temperature</li>
    <li><b>fanSpeed</b> - Get fan speed</li>
    <li><b>vaneVertical</b> - Get vertical vane position</li>
    <li><b>vaneHorizontal</b> - Get horizontal vane position</li>
    <li><b>limits</b> - Get all function limits</li>
    <li><b>raw</b> &lt;command&gt; - Send raw WMP command</li>
  </ul><br>
 
  <a name="IntesisWMP_readings"></a>
  <b>Readings</b>
  <ul>
    <li><b>state</b> - Connection state / current mode when on</li>
    <li><b>power</b> - Power state (on/off)</li>
    <li><b>mode</b> - Operating mode (auto/heat/dry/fan/cool)</li>
    <li><b>desiredTemperature</b> - Target temperature in C</li>
    <li><b>temperature</b> - Ambient/room temperature in C</li>
    <li><b>fanSpeed</b> - Fan speed (auto/1-9)</li>
    <li><b>vaneVertical</b> - Vertical vane position</li>
    <li><b>vaneHorizontal</b> - Horizontal vane position</li>
    <li><b>model</b> - Device model</li>
    <li><b>mac</b> - MAC address</li>
    <li><b>ip</b> - IP address</li>
    <li><b>firmware</b> - Firmware version</li>
    <li><b>rssi</b> - WiFi signal strength</li>
  </ul><br>
 
  <a name="IntesisWMP_attr"></a>
  <b>Attributes</b>
  <ul>
    <li><b>disable</b> 0|1 - Disable the device</li>
    <li><b>interval</b> 10-55 - Keepalive interval in seconds (default: 30)</li>
    <li><b>updateInterval</b> - Interval for periodic polling of all values in seconds (default: 60, min: 10)</li>
    <li><b>timeout</b> - Connection timeout in seconds (default: 5)</li>
    <li><b>unit</b> - Unit number for multi-unit installations (default: 1)</li>
    <li><b>username</b> - Username for secured devices</li>
    <li><b>password</b> - Password for secured devices</li>
    <li><b>tempUnit</b> C|F - Temperature unit for display</li>
    <li><b>pollFunctions</b> - Comma-separated list of functions to poll (default: ONOFF,MODE,SETPTEMP,AMBTEMP,FANSP,VANEUD,VANELR)</li>
  </ul><br>
 
  <b>RSSI Signal Quality</b><br>
  <ul>
    <li>&gt; -70: Excellent</li>
    <li>-71 to -81: Very Good</li>
    <li>-81 to -85: Good</li>
    <li>-86 to -95: Weak</li>
    <li>&lt; -96: Bad</li>
  </ul>
</ul>

=end html

=begin html_DE

<a name="IntesisWMP"></a>
<h3>IntesisWMP</h3>
<ul>
  Dieses Modul ermoeglicht die Steuerung von Klimageraeten ueber das Intesis WMP
  (WiFi-Modul fuer Panasonic Klimaanlagen) mittels WMP ASCII-Protokoll ueber TCP.<br><br>
 
  Unterstuetzte Geraete: PA-AC-WMP-1 (INWMPPAN001I000)<br><br>
 
  Das Modul verbindet sich ueber TCP-Port 3310 mit dem Geraet und haelt die Verbindung
  durch periodische Keepalive-Nachrichten aufrecht. Spontane Statusaenderungen vom
  Geraet (CHN-Nachrichten) werden automatisch verarbeitet. Zusaetzlich werden alle
  Werte periodisch abgefragt.<br><br>
 
  <a name="IntesisWMP_define"></a>
  <b>Define</b>
  <ul>
    <code>define &lt;name&gt; IntesisWMP &lt;host&gt; [&lt;port&gt;]</code><br><br>
   
    Beispiel:<br>
    <code>define meineKlima IntesisWMP 192.168.1.100</code>
  </ul><br>
 
  <b>Attribute</b>
  <ul>
    <li><b>updateInterval</b> - Intervall fuer periodische Abfrage aller Werte in Sekunden (Standard: 60, Min: 10)</li>
    <li><b>pollFunctions</b> - Komma-getrennte Liste der abzufragenden Funktionen</li>
  </ul>
</ul>

=end html_DE

=cut
#20
Off-Topic / Aw: Perl Script Logik Fehlersu...
Letzter Beitrag von Nobbynews - 01 Januar 2026, 21:36:43
Ohne jetzt den Code an sich verstanden zu haben:
my $aaa = $pointB[0]-$pointA[0];
            my $bbb = $pointB[1]-$pointA[1];
            my $ccc = $pointB[2]-$pointA[2];
           
            $dist = (($aaa)**2)+(($bbb)**2)+(($ccc)**2) ;

Die Formel für die Entfernung ist falsch. Es fehlt die Quadratwurzel.
https://en.wikipedia.org/wiki/Euclidean_distance#Higher_dimensions
Und außerdem sind sämtliche Klammern in der Addition total überflüssig.

Oder ist gement: https://en.wikipedia.org/wiki/Euclidean_distance#Squared_Euclidean_distance