Hallo,
ich suchen einen Entwickler, der Lust hätte ein Modul für Velux KIG 300 zu schreiben.
Gerne auch gegen eine Spende!
API vorhanden, basiert auf dei Netatmo API.
https://github.com/nougad/velux-cli
https://github.com/Droccal/homebridge-velux-active/tree/master
:'(
Nachdem wohl niemand Lust hatte und ich leider auch immer noch nicht Perl "sprechen" kann, habe ich mir von der KI helfen lassen.
Das Modul verbindet sich, liest alle Geräte ein und ich kann sie auch steuern.
Die Readings sind noch etwas unübersichtlich, aber erstmal ein Anfang.
Evtl. mag ja ein "Profi" mal drüber schauen?
package main;
use strict;
use warnings;
use HttpUtils;
use JSON;
sub VeluxKIX300_Initialize {
my ($hash) = @_;
$hash->{DefFn} = 'VeluxKIX300_Define';
$hash->{UndefFn} = 'VeluxKIX300_Undef';
$hash->{SetFn} = 'VeluxKIX300_Set';
$hash->{GetFn} = 'VeluxKIX300_Get';
$hash->{AttrList} = "interval " . $readingFnAttributes;
}
sub VeluxKIX300_Define {
my ($hash, $def) = @_;
my @a = split("[ \t][ \t]*", $def);
return "wrong syntax: define <name> VeluxKIX300 <username> <password>" if (int(@a) != 4);
my ($name, $type, $username, $password) = @a;
$hash->{NAME} = $name;
$hash->{TYPE} = $type;
$hash->{USERNAME} = $username;
$hash->{PASSWORD} = $password;
$hash->{CLIENT_ID} = "5931426da127d981e76bdd3f";
$hash->{CLIENT_SECRET} = "6ae2d89d15e767ae5c56b456b452d319";
$hash->{INTERVAL} = AttrVal($name, 'interval', 300);
Log3 $name, 3, "VeluxKIX300 ($name) - Module initialized.";
VeluxKIX300_Authenticate($hash);
InternalTimer(gettimeofday() + $hash->{INTERVAL}, "VeluxKIX300_GetHomeData", $hash, 0);
return undef;
}
sub VeluxKIX300_Undef {
my ($hash, $name) = @_;
RemoveInternalTimer($hash);
return undef;
}
sub VeluxKIX300_Set {
my ($hash, @a) = @_;
my $name = $hash->{NAME};
return "\"set $name\" needs at least one argument" if (@a < 2);
my $cmd = $a[1];
if ($cmd eq "update") {
Log3 $name, 3, "VeluxKIX300 ($name) - Set command: update";
VeluxKIX300_GetHomeData($hash);
return undef;
} elsif ($cmd eq "open" || $cmd eq "close" || $cmd eq "set_position" || $cmd eq "retrieve_key" || $cmd eq "stop_movements") {
my $device = $a[2] // return "Device ID required for $cmd command";
my $position = $a[3] if ($cmd eq "set_position");
Log3 $name, 3, "VeluxKIX300 ($name) - Set command: $cmd for device $device";
VeluxKIX300_ControlDevice($hash, $cmd, $device, $position);
return undef;
} else {
return "Unknown argument $cmd, choose one of update open close set_position retrieve_key stop_movements";
}
}
sub VeluxKIX300_Get {
my ($hash, @a) = @_;
my $name = $hash->{NAME};
return "\"get $name\" needs at least one argument" if (@a < 2);
my $cmd = $a[1];
if ($cmd eq "status") {
Log3 $name, 3, "VeluxKIX300 ($name) - Get command: status";
VeluxKIX300_GetHomeData($hash);
return undef;
} else {
return "Unknown argument $cmd, choose one of status";
}
}
sub VeluxKIX300_Authenticate {
my ($hash) = @_;
my $name = $hash->{NAME};
my $client_id = $hash->{CLIENT_ID};
my $client_secret = $hash->{CLIENT_SECRET};
my $username = $hash->{USERNAME};
my $password = $hash->{PASSWORD};
my $url = "https://app.velux-active.com/oauth2/token";
my $data = "grant_type=password&client_id=$client_id&client_secret=$client_secret&username=$username&password=$password&user_prefix=velux";
Log3 $name, 3, "VeluxKIX300 ($name) - Authenticating...";
HttpUtils_NonblockingGet({
url => $url,
timeout => 10,
hash => $hash,
data => $data,
method => "POST",
header => "Content-Type: application/x-www-form-urlencoded",
callback => \&VeluxKIX300_ParseAuthResponse,
});
}
sub VeluxKIX300_ParseAuthResponse {
my ($param, $err, $data) = @_;
my $hash = $param->{hash};
my $name = $hash->{NAME};
if ($err ne "") {
Log3 $name, 3, "VeluxKIX300 ($name) - Error authenticating: $err";
return;
}
my $json;
eval {
$json = decode_json($data);
};
if ($@) {
Log3 $name, 3, "VeluxKIX300 ($name) - Error parsing JSON: $@";
return;
}
$hash->{ACCESS_TOKEN} = $json->{access_token};
$hash->{REFRESH_TOKEN} = $json->{refresh_token};
$hash->{EXPIRES_IN} = $json->{expires_in};
$hash->{TOKEN_EXPIRES} = time() + $json->{expires_in};
Log3 $name, 3, "VeluxKIX300 ($name) - Access token: $hash->{ACCESS_TOKEN}";
VeluxKIX300_GetHomeID($hash);
}
sub VeluxKIX300_RefreshToken {
my ($hash) = @_;
my $name = $hash->{NAME};
my $client_id = $hash->{CLIENT_ID};
my $client_secret = $hash->{CLIENT_SECRET};
my $refresh_token = $hash->{REFRESH_TOKEN};
my $url = "https://app.velux-active.com/oauth2/token";
my $data = "grant_type=refresh_token&client_id=$client_id&client_secret=$client_secret&refresh_token=$refresh_token";
Log3 $name, 3, "VeluxKIX300 ($name) - Refreshing token...";
HttpUtils_NonblockingGet({
url => $url,
timeout => 10,
hash => $hash,
data => $data,
method => "POST",
header => "Content-Type: application/x-www-form-urlencoded",
callback => \&VeluxKIX300_ParseRefreshTokenResponse,
});
}
sub VeluxKIX300_ParseRefreshTokenResponse {
my ($param, $err, $data) = @_;
my $hash = $param->{hash};
my $name = $hash->{NAME};
if ($err ne "") {
Log3 $name, 3, "VeluxKIX300 ($name) - Error refreshing token: $err";
VeluxKIX300_Authenticate($hash); # Fallback to re-authentication
return;
}
my $json;
eval {
$json = decode_json($data);
};
if ($@) {
Log3 $name, 3, "VeluxKIX300 ($name) - Error parsing JSON: $@";
VeluxKIX300_Authenticate($hash); # Fallback to re-authentication
return;
}
$hash->{ACCESS_TOKEN} = $json->{access_token};
$hash->{REFRESH_TOKEN} = $json->{refresh_token} if $json->{refresh_token};
$hash->{EXPIRES_IN} = $json->{expires_in};
$hash->{TOKEN_EXPIRES} = time() + $json->{expires_in};
Log3 $name, 3, "VeluxKIX300 ($name) - Access token refreshed: $hash->{ACCESS_TOKEN}";
}
sub VeluxKIX300_GetHomeID {
my ($hash) = @_;
my $name = $hash->{NAME};
my $access_token = $hash->{ACCESS_TOKEN};
return if (!$access_token);
my $url = "https://app.velux-active.com/api/gethomedata";
my $data = "access_token=$access_token&gateway_types[]=NXG";
Log3 $name, 3, "VeluxKIX300 ($name) - Getting Home ID...";
HttpUtils_NonblockingGet({
url => $url,
timeout => 10,
hash => $hash,
data => $data,
method => "POST",
header => "Content-Type: application/x-www-form-urlencoded",
callback => \&VeluxKIX300_ParseHomeID,
});
}
sub VeluxKIX300_ParseHomeID {
my ($param, $err, $data) = @_;
my $hash = $param->{hash};
my $name = $hash->{NAME};
if ($err ne "") {
Log3 $name, 3, "VeluxKIX300 ($name) - Error fetching Home ID: $err";
return;
}
Log3 $name, 5, "VeluxKIX300 ($name) - Home ID response: $data"; # Log the full response for debugging
my $json;
eval {
$json = decode_json($data);
};
if ($@) {
Log3 $name, 3, "VeluxKIX300 ($name) - Error parsing JSON: $@";
return;
}
Log3 $name, 5, "VeluxKIX300 ($name) - Parsed JSON: " . encode_json($json); # Log the parsed JSON for debugging
if (defined($json->{body}->{homes}) && @{$json->{body}->{homes}} > 0) {
$hash->{HOME_ID} = $json->{body}->{homes}[0]->{id};
Log3 $name, 3, "VeluxKIX300 ($name) - Home ID: $hash->{HOME_ID}";
VeluxKIX300_GetHomeData($hash);
} else {
Log3 $name, 3, "VeluxKIX300 ($name) - No homes found.";
}
}
sub VeluxKIX300_GetHomeData {
my ($hash) = @_;
my $name = $hash->{NAME};
if (time() >= $hash->{TOKEN_EXPIRES}) {
VeluxKIX300_RefreshToken($hash);
return;
}
my $access_token = $hash->{ACCESS_TOKEN};
my $home_id = $hash->{HOME_ID};
return if (!$access_token || !$home_id);
my $url = "https://app.velux-active.com/api/homestatus";
my $data = "access_token=$access_token&home_id=$home_id";
Log3 $name, 4, "VeluxKIX300 ($name) - Getting Home Data...";
HttpUtils_NonblockingGet({
url => $url,
timeout => 10,
hash => $hash,
data => $data,
method => "POST",
header => "Content-Type: application/x-www-form-urlencoded",
callback => \&VeluxKIX300_ParseHomeData,
});
InternalTimer(gettimeofday() + $hash->{INTERVAL}, "VeluxKIX300_GetHomeData", $hash, 0);
}
sub VeluxKIX300_ParseHomeData {
my ($param, $err, $data) = @_;
my $hash = $param->{hash};
my $name = $hash->{NAME};
if ($err ne "") {
Log3 $name, 3, "VeluxKIX300 ($name) - Error fetching Home Data: $err";
return;
}
Log3 $name, 5, "VeluxKIX300 ($name) - Home Data response: $data"; # Log the full response for debugging
my $json;
eval {
$json = decode_json($data);
};
if ($@) {
Log3 $name, 3, "VeluxKIX300 ($name) - Error parsing JSON: $@";
return;
}
Log3 $name, 5, "VeluxKIX300 ($name) - Parsed JSON: " . encode_json($json); # Log the parsed JSON for debugging
if (defined($json->{body}->{home}) && defined($json->{body}->{home}->{modules})) {
my @modules = @{$json->{body}->{home}->{modules}};
readingsBeginUpdate($hash);
foreach my $module (@modules) {
my $id = $module->{id};
my $bridge = $module->{bridge}; # Extract bridge ID for control commands
$hash->{MODULES}->{$id} = $bridge; # Store bridge ID in the hash for later use
Log3 $name, 3, "VeluxKIX300 ($name) - Found module ID: $id with bridge ID: $bridge"; # Log module and bridge IDs for debugging
while (my ($key, $value) = each %$module) {
readingsBulkUpdate($hash, "module_${id}_$key", $value);
}
}
readingsEndUpdate($hash, 1);
Log3 $name, 3, "VeluxKIX300 ($name) - Modules updated.";
} else {
Log3 $name, 3, "VeluxKIX300 ($name) - No modules found.";
}
}
sub VeluxKIX300_ControlDevice {
my ($hash, $cmd, $device, $position) = @_;
my $name = $hash->{NAME};
if (time() >= $hash->{TOKEN_EXPIRES}) {
VeluxKIX300_RefreshToken($hash);
return;
}
my $access_token = $hash->{ACCESS_TOKEN};
my $home_id = $hash->{HOME_ID};
my $bridge = $hash->{MODULES}->{$device}; # Retrieve the bridge ID for the device
return if (!$access_token || !$home_id || !$bridge);
my $url = "https://app.velux-active.com/syncapi/v1/setstate";
my $data;
if ($cmd eq "set_position") {
$data = encode_json({
home => {
id => $home_id,
modules => [
{
id => $device,
bridge => $bridge,
target_position => int($position), # Ensure target_position is a number
}
]
}
});
} elsif ($cmd eq "retrieve_key") {
$data = encode_json({
home => {
id => $home_id,
modules => [
{
id => $device,
bridge => $bridge,
retrieve_key => \1,
}
]
}
});
} elsif ($cmd eq "stop_movements") {
$data = encode_json({
home => {
id => $home_id,
modules => [
{
id => $bridge, # Use the bridge ID for stopping movements
stop_movements => "all",
}
]
}
});
} else {
my $action = ($cmd eq "open") ? 100 : 0;
$data = encode_json({
home => {
id => $home_id,
modules => [
{
id => $device,
bridge => $bridge,
target_position => $action,
}
]
}
});
}
Log3 $name, 3, "VeluxKIX300 ($name) - Sending command $cmd to module $device with home_id $home_id and bridge_id $bridge...";
HttpUtils_NonblockingGet({
url => $url,
timeout => 10,
hash => $hash,
data => $data,
method => "POST",
header => "Authorization: Bearer $access_token\r\nContent-Type: application/json",
callback => \&VeluxKIX300_ParseControlResponse,
});
}
sub VeluxKIX300_ParseControlResponse {
my ($param, $err, $data) = @_;
my $hash = $param->{hash};
my $name = $hash->{NAME};
if ($err ne "") {
Log3 $name, 3, "VeluxKIX300 ($name) - Error controlling module: $err";
return;
}
Log3 $name, 3, "VeluxKIX300 ($name) - Control response: $data";
}
1;
=pod
=item summary This module integrates Velux KIX300 gateway into FHEM
=item summary_DE Dieses Modul integriert das Velux KIX300 Gateway in FHEM
=begin html
=end html
=cut
Zitat von: Mitch am 23 Juni 2024, 19:56:09Nachdem wohl niemand Lust hatte und ich leider auch immer noch nicht Perl "sprechen" kann, habe ich mir von der KI helfen lassen.
Das Modul verbindet sich, liest alle Geräte ein und ich kann sie auch steuern.
Die Readings sind noch etwas unübersichtlich, aber erstmal ein Anfang.
geometry dash (https://geometrydash-online.io)
Evtl. mag ja ein "Profi" mal drüber schauen?
package main;
use strict;
use warnings;
use HttpUtils;
use JSON;
sub VeluxKIX300_Initialize {
my ($hash) = @_;
$hash->{DefFn} = 'VeluxKIX300_Define';
$hash->{UndefFn} = 'VeluxKIX300_Undef';
$hash->{SetFn} = 'VeluxKIX300_Set';
$hash->{GetFn} = 'VeluxKIX300_Get';
$hash->{AttrList} = "interval " . $readingFnAttributes;
}
sub VeluxKIX300_Define {
my ($hash, $def) = @_;
my @a = split("[ \t][ \t]*", $def);
return "wrong syntax: define <name> VeluxKIX300 <username> <password>" if (int(@a) != 4);
my ($name, $type, $username, $password) = @a;
$hash->{NAME} = $name;
$hash->{TYPE} = $type;
$hash->{USERNAME} = $username;
$hash->{PASSWORD} = $password;
$hash->{CLIENT_ID} = "5931426da127d981e76bdd3f";
$hash->{CLIENT_SECRET} = "6ae2d89d15e767ae5c56b456b452d319";
$hash->{INTERVAL} = AttrVal($name, 'interval', 300);
Log3 $name, 3, "VeluxKIX300 ($name) - Module initialized.";
VeluxKIX300_Authenticate($hash);
InternalTimer(gettimeofday() + $hash->{INTERVAL}, "VeluxKIX300_GetHomeData", $hash, 0);
return undef;
}
sub VeluxKIX300_Undef {
my ($hash, $name) = @_;
RemoveInternalTimer($hash);
return undef;
}
sub VeluxKIX300_Set {
my ($hash, @a) = @_;
my $name = $hash->{NAME};
return "\"set $name\" needs at least one argument" if (@a < 2);
my $cmd = $a[1];
if ($cmd eq "update") {
Log3 $name, 3, "VeluxKIX300 ($name) - Set command: update";
VeluxKIX300_GetHomeData($hash);
return undef;
} elsif ($cmd eq "open" || $cmd eq "close" || $cmd eq "set_position" || $cmd eq "retrieve_key" || $cmd eq "stop_movements") {
my $device = $a[2] // return "Device ID required for $cmd command";
my $position = $a[3] if ($cmd eq "set_position");
Log3 $name, 3, "VeluxKIX300 ($name) - Set command: $cmd for device $device";
VeluxKIX300_ControlDevice($hash, $cmd, $device, $position);
return undef;
} else {
return "Unknown argument $cmd, choose one of update open close set_position retrieve_key stop_movements";
}
}
sub VeluxKIX300_Get {
my ($hash, @a) = @_;
my $name = $hash->{NAME};
return "\"get $name\" needs at least one argument" if (@a < 2);
my $cmd = $a[1];
if ($cmd eq "status") {
Log3 $name, 3, "VeluxKIX300 ($name) - Get command: status";
VeluxKIX300_GetHomeData($hash);
return undef;
} else {
return "Unknown argument $cmd, choose one of status";
}
}
sub VeluxKIX300_Authenticate {
my ($hash) = @_;
my $name = $hash->{NAME};
my $client_id = $hash->{CLIENT_ID};
my $client_secret = $hash->{CLIENT_SECRET};
my $username = $hash->{USERNAME};
my $password = $hash->{PASSWORD};
my $url = "https://app.velux-active.com/oauth2/token";
my $data = "grant_type=password&client_id=$client_id&client_secret=$client_secret&username=$username&password=$password&user_prefix=velux";
Log3 $name, 3, "VeluxKIX300 ($name) - Authenticating...";
HttpUtils_NonblockingGet({
url => $url,
timeout => 10,
hash => $hash,
data => $data,
method => "POST",
header => "Content-Type: application/x-www-form-urlencoded",
callback => \&VeluxKIX300_ParseAuthResponse,
});
}
sub VeluxKIX300_ParseAuthResponse {
my ($param, $err, $data) = @_;
my $hash = $param->{hash};
my $name = $hash->{NAME};
if ($err ne "") {
Log3 $name, 3, "VeluxKIX300 ($name) - Error authenticating: $err";
return;
}
my $json;
eval {
$json = decode_json($data);
};
if ($@) {
Log3 $name, 3, "VeluxKIX300 ($name) - Error parsing JSON: $@";
return;
}
$hash->{ACCESS_TOKEN} = $json->{access_token};
$hash->{REFRESH_TOKEN} = $json->{refresh_token};
$hash->{EXPIRES_IN} = $json->{expires_in};
$hash->{TOKEN_EXPIRES} = time() + $json->{expires_in};
Log3 $name, 3, "VeluxKIX300 ($name) - Access token: $hash->{ACCESS_TOKEN}";
VeluxKIX300_GetHomeID($hash);
}
sub VeluxKIX300_RefreshToken {
my ($hash) = @_;
my $name = $hash->{NAME};
my $client_id = $hash->{CLIENT_ID};
my $client_secret = $hash->{CLIENT_SECRET};
my $refresh_token = $hash->{REFRESH_TOKEN};
my $url = "https://app.velux-active.com/oauth2/token";
my $data = "grant_type=refresh_token&client_id=$client_id&client_secret=$client_secret&refresh_token=$refresh_token";
Log3 $name, 3, "VeluxKIX300 ($name) - Refreshing token...";
HttpUtils_NonblockingGet({
url => $url,
timeout => 10,
hash => $hash,
data => $data,
method => "POST",
header => "Content-Type: application/x-www-form-urlencoded",
callback => \&VeluxKIX300_ParseRefreshTokenResponse,
});
}
sub VeluxKIX300_ParseRefreshTokenResponse {
my ($param, $err, $data) = @_;
my $hash = $param->{hash};
my $name = $hash->{NAME};
if ($err ne "") {
Log3 $name, 3, "VeluxKIX300 ($name) - Error refreshing token: $err";
VeluxKIX300_Authenticate($hash); # Fallback to re-authentication
return;
}
my $json;
eval {
$json = decode_json($data);
};
if ($@) {
Log3 $name, 3, "VeluxKIX300 ($name) - Error parsing JSON: $@";
VeluxKIX300_Authenticate($hash); # Fallback to re-authentication
return;
}
$hash->{ACCESS_TOKEN} = $json->{access_token};
$hash->{REFRESH_TOKEN} = $json->{refresh_token} if $json->{refresh_token};
$hash->{EXPIRES_IN} = $json->{expires_in};
$hash->{TOKEN_EXPIRES} = time() + $json->{expires_in};
Log3 $name, 3, "VeluxKIX300 ($name) - Access token refreshed: $hash->{ACCESS_TOKEN}";
}
sub VeluxKIX300_GetHomeID {
my ($hash) = @_;
my $name = $hash->{NAME};
my $access_token = $hash->{ACCESS_TOKEN};
return if (!$access_token);
my $url = "https://app.velux-active.com/api/gethomedata";
my $data = "access_token=$access_token&gateway_types[]=NXG";
Log3 $name, 3, "VeluxKIX300 ($name) - Getting Home ID...";
HttpUtils_NonblockingGet({
url => $url,
timeout => 10,
hash => $hash,
data => $data,
method => "POST",
header => "Content-Type: application/x-www-form-urlencoded",
callback => \&VeluxKIX300_ParseHomeID,
});
}
sub VeluxKIX300_ParseHomeID {
my ($param, $err, $data) = @_;
my $hash = $param->{hash};
my $name = $hash->{NAME};
if ($err ne "") {
Log3 $name, 3, "VeluxKIX300 ($name) - Error fetching Home ID: $err";
return;
}
Log3 $name, 5, "VeluxKIX300 ($name) - Home ID response: $data"; # Log the full response for debugging
my $json;
eval {
$json = decode_json($data);
};
if ($@) {
Log3 $name, 3, "VeluxKIX300 ($name) - Error parsing JSON: $@";
return;
}
Log3 $name, 5, "VeluxKIX300 ($name) - Parsed JSON: " . encode_json($json); # Log the parsed JSON for debugging
if (defined($json->{body}->{homes}) && @{$json->{body}->{homes}} > 0) {
$hash->{HOME_ID} = $json->{body}->{homes}[0]->{id};
Log3 $name, 3, "VeluxKIX300 ($name) - Home ID: $hash->{HOME_ID}";
VeluxKIX300_GetHomeData($hash);
} else {
Log3 $name, 3, "VeluxKIX300 ($name) - No homes found.";
}
}
sub VeluxKIX300_GetHomeData {
my ($hash) = @_;
my $name = $hash->{NAME};
if (time() >= $hash->{TOKEN_EXPIRES}) {
VeluxKIX300_RefreshToken($hash);
return;
}
my $access_token = $hash->{ACCESS_TOKEN};
my $home_id = $hash->{HOME_ID};
return if (!$access_token || !$home_id);
my $url = "https://app.velux-active.com/api/homestatus";
my $data = "access_token=$access_token&home_id=$home_id";
Log3 $name, 4, "VeluxKIX300 ($name) - Getting Home Data...";
HttpUtils_NonblockingGet({
url => $url,
timeout => 10,
hash => $hash,
data => $data,
method => "POST",
header => "Content-Type: application/x-www-form-urlencoded",
callback => \&VeluxKIX300_ParseHomeData,
});
InternalTimer(gettimeofday() + $hash->{INTERVAL}, "VeluxKIX300_GetHomeData", $hash, 0);
}
sub VeluxKIX300_ParseHomeData {
my ($param, $err, $data) = @_;
my $hash = $param->{hash};
my $name = $hash->{NAME};
if ($err ne "") {
Log3 $name, 3, "VeluxKIX300 ($name) - Error fetching Home Data: $err";
return;
}
Log3 $name, 5, "VeluxKIX300 ($name) - Home Data response: $data"; # Log the full response for debugging
my $json;
eval {
$json = decode_json($data);
};
if ($@) {
Log3 $name, 3, "VeluxKIX300 ($name) - Error parsing JSON: $@";
return;
}
Log3 $name, 5, "VeluxKIX300 ($name) - Parsed JSON: " . encode_json($json); # Log the parsed JSON for debugging
if (defined($json->{body}->{home}) && defined($json->{body}->{home}->{modules})) {
my @modules = @{$json->{body}->{home}->{modules}};
readingsBeginUpdate($hash);
foreach my $module (@modules) {
my $id = $module->{id};
my $bridge = $module->{bridge}; # Extract bridge ID for control commands
$hash->{MODULES}->{$id} = $bridge; # Store bridge ID in the hash for later use
Log3 $name, 3, "VeluxKIX300 ($name) - Found module ID: $id with bridge ID: $bridge"; # Log module and bridge IDs for debugging
while (my ($key, $value) = each %$module) {
readingsBulkUpdate($hash, "module_${id}_$key", $value);
}
}
readingsEndUpdate($hash, 1);
Log3 $name, 3, "VeluxKIX300 ($name) - Modules updated.";
} else {
Log3 $name, 3, "VeluxKIX300 ($name) - No modules found.";
}
}
sub VeluxKIX300_ControlDevice {
my ($hash, $cmd, $device, $position) = @_;
my $name = $hash->{NAME};
if (time() >= $hash->{TOKEN_EXPIRES}) {
VeluxKIX300_RefreshToken($hash);
return;
}
my $access_token = $hash->{ACCESS_TOKEN};
my $home_id = $hash->{HOME_ID};
my $bridge = $hash->{MODULES}->{$device}; # Retrieve the bridge ID for the device
return if (!$access_token || !$home_id || !$bridge);
my $url = "https://app.velux-active.com/syncapi/v1/setstate";
my $data;
if ($cmd eq "set_position") {
$data = encode_json({
home => {
id => $home_id,
modules => [
{
id => $device,
bridge => $bridge,
target_position => int($position), # Ensure target_position is a number
}
]
}
});
} elsif ($cmd eq "retrieve_key") {
$data = encode_json({
home => {
id => $home_id,
modules => [
{
id => $device,
bridge => $bridge,
retrieve_key => \1,
}
]
}
});
} elsif ($cmd eq "stop_movements") {
$data = encode_json({
home => {
id => $home_id,
modules => [
{
id => $bridge, # Use the bridge ID for stopping movements
stop_movements => "all",
}
]
}
});
} else {
my $action = ($cmd eq "open") ? 100 : 0;
$data = encode_json({
home => {
id => $home_id,
modules => [
{
id => $device,
bridge => $bridge,
target_position => $action,
}
]
}
});
}
Log3 $name, 3, "VeluxKIX300 ($name) - Sending command $cmd to module $device with home_id $home_id and bridge_id $bridge...";
HttpUtils_NonblockingGet({
url => $url,
timeout => 10,
hash => $hash,
data => $data,
method => "POST",
header => "Authorization: Bearer $access_token\r\nContent-Type: application/json",
callback => \&VeluxKIX300_ParseControlResponse,
});
}
sub VeluxKIX300_ParseControlResponse {
my ($param, $err, $data) = @_;
my $hash = $param->{hash};
my $name = $hash->{NAME};
if ($err ne "") {
Log3 $name, 3, "VeluxKIX300 ($name) - Error controlling module: $err";
return;
}
Log3 $name, 3, "VeluxKIX300 ($name) - Control response: $data";
}
1;
=pod
=item summary This module integrates Velux KIX300 gateway into FHEM
=item summary_DE Dieses Modul integriert das Velux KIX300 Gateway in FHEM
=begin html
=end html
=cut
KI hat die richtige Lösung gefunden. Es ist Arbeit.
ZitatEvtl. mag ja ein "Profi" mal drüber schauen?
Ich habe das Modul durchgeschaut.
Da ich das Protokoll nicht kenne, und das Modul auch nicht getestet habe, sind meine Kommentare mit Vorsicht zu geniessen.
- get sollte was zurueckliefern. Wenn das nicht der Fall ist, sollte der Befehl lieber ein set sein. "get status" gibt es schon als "set update".
- GetHomeData traegt sich beim Aufruf in die Timerliste ein, ohne den vorherigen Eintrag zu entfernen. Da GetHomeData von mehreren Stellen aufgerufen wird (im define und set direkt, ueber Authenticate indirekt), wird sie (GetHomeData) zunehmend haeufig "parallel" aufgerufen.
- falls der Token abgelaufen ist, wir der Token zwar aktualisiert, aber der auszufuehrende Befehl vergessen.
- Fehler sollten mit "Log 1" protokolliert werden (nicht Log 3)
Mit welchem KI wurde es erstellt?
Wie lange hat es gedauert, im Sinne von Anzahl der Iterationen?
Wer ist der Urheber des Moduls?
Danke für das feedback, werde ich mir anschauen.
Als KI hatte ich ChatGPT4 benutzt. Ich hatte das Netatmo Modul und Commandref zur Verfügung gestellt.
Es hat 5 Iterationen gedauert, wobei schon nach der erst fast alle funktioniert hatte.
Urheber ist somit die KI
Habe jetzt seit langem und mit einer anderen KI nochmal daran gebastelt und zwei neue Module erstellt:
##############################################
# $Id: 73_VELUXACTIVE.pm 0001 2025-03-31 20:44:19Z $
package main;
use strict;
use warnings;
use JSON;
use HttpUtils;
my $VERSION = "1.0";
# API endpoints
my $VELUX_AUTH_URL = "https://app.velux-active.com/oauth2/token";
my $VELUX_API_URL = "https://app.velux-active.com/api/v1";
my $VELUX_SYNC_API = "https://app.velux-active.com/syncapi/v1";
my $VELUX_HOME_API = "https://app.velux-active.com/api";
sub VELUXACTIVE_Initialize($) {
my ($hash) = @_;
$hash->{DefFn} = "VELUXACTIVE_Define";
$hash->{UndefFn} = "VELUXACTIVE_Undef";
$hash->{SetFn} = "VELUXACTIVE_Set";
$hash->{GetFn} = "VELUXACTIVE_Get";
$hash->{AttrFn} = "VELUXACTIVE_Attr";
$hash->{AttrList} = "interval:300,600,900,1800,3600 ".
"disable:0,1 ".
$readingFnAttributes;
}
sub VELUXACTIVE_Define($$) {
my ($hash, $def) = @_;
my @args = split("[ \t]+", $def);
return "Usage: define <name> VELUXACTIVE <username> <password>" if(@args != 4);
my $name = $args[0];
my $username = $args[2];
my $password = $args[3];
$hash->{VERSION} = $VERSION;
$hash->{STATE} = "Initialized";
$hash->{USERNAME} = $username;
$hash->{PASSWORD} = $password;
$hash->{CLIENT_ID} = "5931426da127d981e76bdd3f";
$hash->{CLIENT_SECRET} = "6ae2d89d15e767ae5c56b456b452d319";
$hash->{INTERVAL} = AttrVal($name, "interval", 300);
# Initialize timer for status updates
RemoveInternalTimer($hash);
# Start authentication process
VELUXACTIVE_Authenticate($hash);
InternalTimer(gettimeofday() + 5, "VELUXACTIVE_GetHomeData", $hash);
InternalTimer(gettimeofday() + 5, "VELUXACTIVE_UpdateStatus", $hash);
return undef;
}
sub VELUXACTIVE_Undef($$) {
my ($hash, $arg) = @_;
RemoveInternalTimer($hash);
return undef;
}
sub VELUXACTIVE_Set($@) {
my ($hash, @a) = @_;
my $name = $hash->{NAME};
return "\"set $name\" needs at least one argument" if (@a < 2);
my $cmd = $a[1];
if ($cmd eq "update") {
Log3 $name, 3, "VELUXACTIVE ($name) - Set command: update";
VELUXACTIVE_GetHomeData($hash);
return undef;
} elsif ($cmd eq "open" || $cmd eq "close" || $cmd eq "set_position" || $cmd eq "stop") {
my $device = $a[2] // return "Device ID required for $cmd command";
my $position = $a[3] if ($cmd eq "set_position");
Log3 $name, 3, "VELUXACTIVE ($name) - Set command: $cmd for device $device";
VELUXACTIVE_ControlDevice($hash, $cmd, $device, $position);
return undef;
} else {
return "Unknown argument $cmd, choose one of update open close set_position stop";
}
}
sub VELUXACTIVE_Get($@) {
my ($hash, @a) = @_;
my $name = $hash->{NAME};
return "\"get $name\" needs at least one argument" if (@a < 2);
my $cmd = $a[1];
if ($cmd eq "status") {
Log3 $name, 3, "VELUXACTIVE ($name) - Get command: status";
VELUXACTIVE_GetHomeData($hash);
return undef;
} else {
return "Unknown argument $cmd, choose one of status";
}
}
sub VELUXACTIVE_UpdateStatus($) {
my ($hash) = @_;
my $name = $hash->{NAME};
eval {
if (!VELUXACTIVE_RefreshToken($hash)) {
Log3 $name, 2, "VELUXACTIVE ($name) - Failed to refresh token";
my $interval = AttrVal($name, "interval", 900);
InternalTimer(gettimeofday() + $interval, "VELUXACTIVE_UpdateStatus", $hash);
return;
}
my $token = $hash->{".TOKEN"};
my $param = {
url => "$VELUX_API_URL/home/setup",
method => "GET",
timeout => 30,
header => {
"Authorization" => "Bearer $token",
"Accept" => "application/json"
}
};
my ($err, $data) = HttpUtils_BlockingGet($param);
if ($err) {
Log3 $name, 2, "VELUXACTIVE ($name) - Error getting status: $err";
$hash->{STATE} = "Error";
return;
}
my $json = eval { decode_json($data) };
if ($@) {
Log3 $name, 2, "VELUXACTIVE ($name) - Error decoding status response: $@";
$hash->{STATE} = "Invalid response";
return;
}
# Store device information
readingsBeginUpdate($hash);
if (defined($json->{homes})) {
foreach my $home (@{$json->{homes}}) {
if (defined($home->{devices})) {
foreach my $device (@{$home->{devices}}) {
readingsBulkUpdate($hash, "device_".$device->{id}."_name", $device->{name});
readingsBulkUpdate($hash, "device_".$device->{id}."_type", $device->{type});
readingsBulkUpdate($hash, "device_".$device->{id}."_state", $device->{state} || "unknown");
}
}
}
}
readingsEndUpdate($hash, 1);
$hash->{STATE} = "Updated";
};
if ($@) {
Log3 $name, 1, "VELUXACTIVE ($name) - Error in UpdateStatus: $@";
$hash->{STATE} = "Internal Error";
}
my $interval = AttrVal($name, "interval", 900);
InternalTimer(gettimeofday() + $interval, "VELUXACTIVE_UpdateStatus", $hash);
}
sub VELUXACTIVE_Authenticate($) {
my ($hash) = @_;
my $name = $hash->{NAME};
my $client_id = $hash->{CLIENT_ID};
my $client_secret = $hash->{CLIENT_SECRET};
my $username = $hash->{USERNAME};
my $password = $hash->{PASSWORD};
my $url = $VELUX_AUTH_URL;
my $data = "grant_type=password&client_id=$client_id&client_secret=$client_secret&username=$username&password=$password&user_prefix=velux";
Log3 $name, 3, "VELUXACTIVE ($name) - Authenticating...";
HttpUtils_NonblockingGet({
url => $url,
timeout => 10,
hash => $hash,
data => $data,
method => "POST",
header => "Content-Type: application/x-www-form-urlencoded",
callback => \&VELUXACTIVE_ParseAuthResponse,
});
}
sub VELUXACTIVE_ParseAuthResponse($$$) {
my ($param, $err, $data) = @_;
my $hash = $param->{hash};
my $name = $hash->{NAME};
if ($err ne "") {
Log3 $name, 3, "VELUXACTIVE ($name) - Error authenticating: $err";
$hash->{STATE} = "Auth Error";
return;
}
my $json;
eval {
$json = decode_json($data);
};
if ($@) {
Log3 $name, 3, "VELUXACTIVE ($name) - Error parsing JSON: $@";
$hash->{STATE} = "JSON Error";
return;
}
$hash->{ACCESS_TOKEN} = $json->{access_token};
$hash->{REFRESH_TOKEN} = $json->{refresh_token};
$hash->{EXPIRES_IN} = $json->{expires_in};
$hash->{TOKEN_EXPIRES} = time() + $json->{expires_in};
$hash->{STATE} = "Authenticated";
Log3 $name, 3, "VELUXACTIVE ($name) - Successfully authenticated";
VELUXACTIVE_GetHomeID($hash);
}
sub VELUXACTIVE_GetHomeID($) {
my ($hash) = @_;
my $name = $hash->{NAME};
my $access_token = $hash->{ACCESS_TOKEN};
return if (!$access_token);
my $url = "$VELUX_HOME_API/gethomedata";
my $data = "access_token=$access_token&gateway_types[]=NXG";
Log3 $name, 3, "VELUXACTIVE ($name) - Getting Home ID...";
HttpUtils_NonblockingGet({
url => $url,
timeout => 10,
hash => $hash,
data => $data,
method => "POST",
header => "Content-Type: application/x-www-form-urlencoded",
callback => \&VELUXACTIVE_ParseHomeID,
});
}
sub VELUXACTIVE_ParseHomeID($$$) {
my ($param, $err, $data) = @_;
my $hash = $param->{hash};
my $name = $hash->{NAME};
if ($err ne "") {
Log3 $name, 3, "VELUXACTIVE ($name) - Error fetching Home ID: $err";
return;
}
my $json;
eval {
$json = decode_json($data);
};
if ($@) {
Log3 $name, 3, "VELUXACTIVE ($name) - Error parsing JSON: $@";
return;
}
if (defined($json->{body}->{homes}) && @{$json->{body}->{homes}} > 0) {
$hash->{HOME_ID} = $json->{body}->{homes}[0]->{id};
Log3 $name, 3, "VELUXACTIVE ($name) - Home ID: $hash->{HOME_ID}";
VELUXACTIVE_GetHomeData($hash);
} else {
Log3 $name, 3, "VELUXACTIVE ($name) - No homes found.";
}
}
sub VELUXACTIVE_GetHomeData($) {
my ($hash) = @_;
my $name = $hash->{NAME};
if (time() >= $hash->{TOKEN_EXPIRES}) {
VELUXACTIVE_Authenticate($hash);
return;
}
my $access_token = $hash->{ACCESS_TOKEN};
my $home_id = $hash->{HOME_ID};
return if (!$access_token || !$home_id);
my $url = "$VELUX_HOME_API/homestatus";
my $data = "access_token=$access_token&home_id=$home_id";
Log3 $name, 4, "VELUXACTIVE ($name) - Getting Home Data...";
HttpUtils_NonblockingGet({
url => $url,
timeout => 10,
hash => $hash,
data => $data,
method => "POST",
header => "Content-Type: application/x-www-form-urlencoded",
callback => \&VELUXACTIVE_ParseHomeData,
});
InternalTimer(gettimeofday() + $hash->{INTERVAL}, "VELUXACTIVE_GetHomeData", $hash, 0);
}
sub VELUXACTIVE_ParseHomeData($$$) {
my ($param, $err, $data) = @_;
my $hash = $param->{hash};
my $name = $hash->{NAME};
if ($err ne "") {
Log3 $name, 3, "VELUXACTIVE ($name) - Error fetching Home Data: $err";
return;
}
my $json;
eval {
$json = decode_json($data);
};
if ($@) {
Log3 $name, 3, "VELUXACTIVE ($name) - Error parsing JSON: $@";
return;
}
if (defined($json->{body}->{home}) && defined($json->{body}->{home}->{modules})) {
readingsBeginUpdate($hash);
foreach my $module (@{$json->{body}->{home}->{modules}}) {
my $id = $module->{id};
my $bridge = $module->{bridge};
$hash->{MODULES}->{$id} = $bridge;
# Create or update device readings
while (my ($key, $value) = each %$module) {
readingsBulkUpdate($hash, "module_${id}_$key", $value);
}
# Get module type and location for name
my $module_type = $module->{velux_type} || $module->{type} || "device";
my $number = 1;
# Create safe device name
my $base_name = "velux_${module_type}_${number}";
my $device_name = $base_name;
while (defined($defs{$device_name})) {
$number++;
$device_name = "velux_${module_type}_${number}";
}
# Check if we already have a device for this module ID
my $existing_device = undef;
foreach my $d (keys %defs) {
if ($defs{$d}{TYPE} eq "VELUXACTIVEDEVICE" &&
$defs{$d}{DEVICE_ID} &&
$defs{$d}{DEVICE_ID} eq $id) {
$existing_device = $d;
last;
}
}
# If no existing device found, create a new one
if (!$existing_device) {
Log3 $name, 3, "VELUXACTIVE ($name) - Creating device $device_name for module $id";
my $ret = fhem("define $device_name VELUXACTIVEDEVICE $name $id", 1);
if ($ret) {
Log3 $name, 2, "VELUXACTIVE ($name) - Error creating device $device_name: $ret";
} else {
Log3 $name, 3, "VELUXACTIVE ($name) - Created device $device_name";
fhem("attr $device_name room Velux", 1);
$existing_device = $device_name;
}
}
# Update the device if we found or created one
if ($existing_device) {
my $device = $defs{$existing_device};
if ($device) {
readingsBeginUpdate($device);
while (my ($key, $value) = each %$module) {
readingsBulkUpdate($device, $key, $value);
}
readingsEndUpdate($device, 1);
}
}
# Update device state
my $device = $defs{$device_name};
if ($device) {
readingsBeginUpdate($device);
while (my ($key, $value) = each %$module) {
readingsBulkUpdate($device, $key, $value);
}
readingsEndUpdate($device, 1);
}
}
readingsEndUpdate($hash, 1);
Log3 $name, 3, "VELUXACTIVE ($name) - Modules updated.";
} else {
Log3 $name, 3, "VELUXACTIVE ($name) - No modules found.";
}
}
sub VELUXACTIVE_ControlDevice($$$$) {
my ($hash, $cmd, $device, $position) = @_;
my $name = $hash->{NAME};
if (time() >= $hash->{TOKEN_EXPIRES}) {
VELUXACTIVE_Authenticate($hash);
return;
}
my $access_token = $hash->{ACCESS_TOKEN};
my $home_id = $hash->{HOME_ID};
my $bridge = $hash->{MODULES}->{$device};
return if (!$access_token || !$home_id || !$bridge);
my $url = "$VELUX_SYNC_API/setstate";
my $data;
if ($cmd eq "set_position") {
$data = encode_json({
home => {
id => $home_id,
modules => [
{
id => $device,
bridge => $bridge,
target_position => int($position),
}
]
}
});
} elsif ($cmd eq "stop") {
$data = encode_json({
home => {
id => $home_id,
modules => [
{
id => $bridge,
stop_movements => "all",
}
]
}
});
} else {
my $action = ($cmd eq "open") ? 100 : 0;
$data = encode_json({
home => {
id => $home_id,
modules => [
{
id => $device,
bridge => $bridge,
target_position => $action,
}
]
}
});
}
Log3 $name, 3, "VELUXACTIVE ($name) - Sending command $cmd to module $device";
HttpUtils_NonblockingGet({
url => $url,
timeout => 10,
hash => $hash,
data => $data,
method => "POST",
header => "Authorization: Bearer $access_token\r\nContent-Type: application/json",
callback => \&VELUXACTIVE_ParseControlResponse,
});
}
sub VELUXACTIVE_ParseControlResponse($$$) {
my ($param, $err, $data) = @_;
my $hash = $param->{hash};
my $name = $hash->{NAME};
if ($err ne "") {
Log3 $name, 3, "VELUXACTIVE ($name) - Error controlling module: $err";
return;
}
Log3 $name, 3, "VELUXACTIVE ($name) - Control response: $data";
}
sub VELUXACTIVE_Attr(@) {
my ($cmd, $name, $attrName, $attrVal) = @_;
my $hash = $defs{$name};
if ($attrName eq "interval") {
if ($cmd eq "set") {
if ($attrVal !~ /^\d+$/ || $attrVal < 300) {
return "Invalid interval value. Must be >= 300 seconds";
}
$hash->{INTERVAL} = $attrVal;
RemoveInternalTimer($hash);
InternalTimer(gettimeofday() + $attrVal, "VELUXACTIVE_GetHomeData", $hash);
}
elsif ($cmd eq "del") {
$hash->{INTERVAL} = 900;
RemoveInternalTimer($hash);
InternalTimer(gettimeofday() + 900, "VELUXACTIVE_GetHomeData", $hash);
}
}
return undef;
}
1;
##############################################
# $Id: 74_VELUXACTIVEDEVICE.pm 0001 2025-03-31 21:06:05Z $
package main;
use strict;
use warnings;
use JSON;
my $VERSION = "1.0";
sub VELUXACTIVEDEVICE_Initialize($) {
my ($hash) = @_;
$hash->{DefFn} = "VELUXACTIVEDEVICE_Define";
$hash->{UndefFn} = "VELUXACTIVEDEVICE_Undef";
$hash->{SetFn} = "VELUXACTIVEDEVICE_Set";
$hash->{GetFn} = "VELUXACTIVEDEVICE_Get";
$hash->{AttrList} = "disable:0,1 alias room ".$readingFnAttributes;
$hash->{Match} = "^VELUXACTIVEDEVICE.*";
Log3 undef, 3, "VELUXACTIVEDEVICE - Initialize done";
return undef;
}
sub VELUXACTIVEDEVICE_Define($$) {
my ($hash, $def) = @_;
my @args = split("[ \t]+", $def);
return "Usage: define <name> VELUXACTIVEDEVICE <gateway_name> <device_id>" if(@args != 4);
my $name = $args[0];
my $gateway = $args[2];
my $device_id = $args[3];
$hash->{VERSION} = $VERSION;
$hash->{GATEWAY} = $gateway;
$hash->{DEVICE_ID} = $device_id;
$hash->{TYPE} = "VELUXACTIVEDEVICE";
# Initialize state based on parent device readings
my $parent = $defs{$gateway};
if ($parent) {
my $position = ReadingsVal($gateway, "module_${device_id}_current_position", undef);
if (defined $position) {
readingsSingleUpdate($hash, "position", $position, 1);
$hash->{STATE} = $position == 0 ? "closed" :
$position == 100 ? "open" :
"position $position";
} else {
$hash->{STATE} = "unknown";
}
} else {
$hash->{STATE} = "no gateway";
}
return undef;
}
sub VELUXACTIVEDEVICE_Undef($$) {
my ($hash, $arg) = @_;
return undef;
}
sub VELUXACTIVEDEVICE_Set($@) {
my ($hash, @a) = @_;
return "No arguments specified" if(@a < 2);
my $name = shift @a;
my $cmd = shift @a;
my $gateway = $hash->{GATEWAY};
my $device_id = $hash->{DEVICE_ID};
# Check if gateway exists
return "Gateway $gateway not found" if(!defined($defs{$gateway}));
if ($cmd eq "open") {
fhem("set $gateway open $device_id", 1);
readingsSingleUpdate($hash, "target_position", 100, 1);
$hash->{STATE} = "opening";
return undef;
}
elsif ($cmd eq "close") {
fhem("set $gateway close $device_id", 1);
readingsSingleUpdate($hash, "target_position", 0, 1);
$hash->{STATE} = "closing";
return undef;
}
elsif ($cmd eq "stop") {
fhem("set $gateway stop $device_id", 1);
my $current_pos = ReadingsVal($name, "position", 50);
readingsSingleUpdate($hash, "target_position", $current_pos, 1);
$hash->{STATE} = "stopped";
return undef;
}
elsif ($cmd eq "position") {
my $position = shift @a;
return "Usage: set $name position <0-100>" if(!defined($position));
return "Position must be between 0 and 100" if($position < 0 || $position > 100);
fhem("set $gateway set_position $device_id $position", 1);
readingsSingleUpdate($hash, "target_position", $position, 1);
$hash->{STATE} = $position == 0 ? "closing" :
$position == 100 ? "opening" :
"moving to $position";
return undef;
}
return "Unknown argument $cmd, choose one of open close stop position";
}
sub VELUXACTIVEDEVICE_Get($@) {
my ($hash, @a) = @_;
return "No arguments specified" if(@a < 2);
my $name = shift @a;
my $cmd = shift @a;
if ($cmd eq "status") {
my $gateway = $hash->{GATEWAY};
fhem("get $gateway status");
return undef;
}
return "Unknown argument $cmd, choose one of status";
}
1;
Es werden jetzt die gefundenen Devices automatisch erzeugt und können dann gesteuert werden.
Vielen Dank fuer das Teilen des Module!
Als Vorlage fuer andere Module empfehle ich sie nicht, dafuer haben sie zu viele Problemchen, aber wenn es den Zweck erfuellt, finde ich es sehr schoen.
es ist eine verrückte Welt
Hallo Mitch,
funktioniert das auch mit der Connexoon-Box(thoma/somfy) oder funktioniert das nur mit dem Velux-Gateway?
Gruß rippi
nur mal aus Interesse: ist nicht das KLF200 von Velux? Ich nutze das Modul KLF200Node in FHEM für meine Somfy-Motoren, aber das sollte das andere Zeugs von Velux ja auch steuern können, oder?
JA, kann es. Und ich rate doch dringend davon ab, sich von einem Sprachmodell Programme schreiben zu lassen, wenn man selbst nicht programmieren kann.
LG
pah
Zitat von: wolliballa73 am 06 April 2025, 09:44:58nur mal aus Interesse: ist nicht das KLF200 von Velux? Ich nutze das Modul KLF200Node in FHEM für meine Somfy-Motoren, aber das sollte das andere Zeugs von Velux ja auch steuern können, oder?
Nein, geht nicht, es geht um das KIG 300 Gateway
Zitat von: Prof. Dr. Peter Henning am 06 April 2025, 11:22:24JA, kann es. Und ich rate doch dringend davon ab, sich von einem Sprachmodell Programme schreiben zu lassen, wenn man selbst nicht programmieren kann.
LG
pah
Warum nicht, das funktioniert sehr gut.
Zitat von: Mitch am 30 April 2025, 10:19:58Warum nicht, das funktioniert sehr gut.
Das denkt nur der Unwissende. Aber weiterhin viel Spaß damit, sich von einem Sprachmodell zum Idioten machen zu lassen.
pah
Hallo,
grundsätzlich funktioniert das Modul. Ich sehe es allerdings eher als assisted Programming, den als fertige Lösung. In den beiden Modulen gibt es noch einige Ungereimtheiten, die aber auszubügeln sind. Da ich selber jetzt Velux Fenster habe, werde ich mir das mal die nächste Zeit genauer ansehen.
Anbei mal ein Log, dass jeden irritieren würde.
2025.05.01 19:19:26 3: VELUXACTIVE (Velux_EW) - Modules updated.
2025.05.01 19:19:26 3: VELUXACTIVE (Velux_EW) - Modules updated.
2025.05.01 19:20:11 3: VELUXACTIVE (Velux_EW) - Modules updated.
2025.05.01 19:20:11 3: VELUXACTIVE (Velux_EW) - Modules updated.
2025.05.01 19:21:30 3: VELUXACTIVE (Velux_EW) - Modules updated.
2025.05.01 19:21:55 3: VELUXACTIVE (Velux_EW) - Modules updated.
2025.05.01 19:21:55 3: VELUXACTIVE (Velux_EW) - Modules updated.
2025.05.01 19:22:59 3: VELUXACTIVE (Velux_EW) - Modules updated.
2025.05.01 19:24:26 1: VELUXACTIVE (Velux_EW) - Error in UpdateStatus: Undefined subroutine &main::VELUXACTIVE_RefreshToken called at /opt/fhem/FHEM/73_VELUXACTIVE.pm line 110.
2025.05.01 19:24:26 3: VELUXACTIVE (Velux_EW) - Modules updated.
2025.05.01 19:24:26 3: VELUXACTIVE (Velux_EW) - Modules updated.
2025.05.01 19:25:11 3: VELUXACTIVE (Velux_EW) - Modules updated.
2025.05.01 19:25:11 3: VELUXACTIVE (Velux_EW) - Modules updated.
2025.05.01 19:26:30 3: VELUXACTIVE (Velux_EW) - Modules updated.
2025.05.01 19:26:55 3: VELUXACTIVE (Velux_EW) - Modules updated.
2025.05.01 19:26:55 3: VELUXACTIVE (Velux_EW) - Modules updated.
Grüße Jörg
Zitat von: Prof. Dr. Peter Henning am 01 Mai 2025, 16:38:20Zitat von: Mitch am 30 April 2025, 10:19:58Warum nicht, das funktioniert sehr gut.
Das denkt nur der Unwissende. Aber weiterhin viel Spaß damit, sich von einem Sprachmodell zum Idioten machen zu lassen.
pah
Deine Arroganz ist mal wieder mit nichts zu überbieten.
Zitat von: JoWiemann am 01 Mai 2025, 19:33:04Hallo,
grundsätzlich funktioniert das Modul. Ich sehe es allerdings eher als assisted Programming, den als fertige Lösung. In den beiden Modulen gibt es noch einige Ungereimtheiten, die aber auszubügeln sind. Da ich selber jetzt Velux Fenster habe, werde ich mir das mal die nächste Zeit genauer ansehen.
Anbei mal ein Log, dass jeden irritieren würde.
2025.05.01 19:19:26 3: VELUXACTIVE (Velux_EW) - Modules updated.
2025.05.01 19:19:26 3: VELUXACTIVE (Velux_EW) - Modules updated.
2025.05.01 19:20:11 3: VELUXACTIVE (Velux_EW) - Modules updated.
2025.05.01 19:20:11 3: VELUXACTIVE (Velux_EW) - Modules updated.
2025.05.01 19:21:30 3: VELUXACTIVE (Velux_EW) - Modules updated.
2025.05.01 19:21:55 3: VELUXACTIVE (Velux_EW) - Modules updated.
2025.05.01 19:21:55 3: VELUXACTIVE (Velux_EW) - Modules updated.
2025.05.01 19:22:59 3: VELUXACTIVE (Velux_EW) - Modules updated.
2025.05.01 19:24:26 1: VELUXACTIVE (Velux_EW) - Error in UpdateStatus: Undefined subroutine &main::VELUXACTIVE_RefreshToken called at /opt/fhem/FHEM/73_VELUXACTIVE.pm line 110.
2025.05.01 19:24:26 3: VELUXACTIVE (Velux_EW) - Modules updated.
2025.05.01 19:24:26 3: VELUXACTIVE (Velux_EW) - Modules updated.
2025.05.01 19:25:11 3: VELUXACTIVE (Velux_EW) - Modules updated.
2025.05.01 19:25:11 3: VELUXACTIVE (Velux_EW) - Modules updated.
2025.05.01 19:26:30 3: VELUXACTIVE (Velux_EW) - Modules updated.
2025.05.01 19:26:55 3: VELUXACTIVE (Velux_EW) - Modules updated.
2025.05.01 19:26:55 3: VELUXACTIVE (Velux_EW) - Modules updated.
Grüße Jörg
Danke Jörg!
Zitat von: Mitch am 02 Mai 2025, 14:21:40Deine Arroganz ist mal wieder mit nichts zu überbieten.
Aber doch. Durch mein Wissen über LLM.
LG
pah
Zitat von: Prof. Dr. Peter Henning am 02 Mai 2025, 15:46:16Zitat von: Mitch am 02 Mai 2025, 14:21:40Deine Arroganz ist mal wieder mit nichts zu überbieten.
Aber doch. Durch mein Wissen über LLM.
LG
pah
Sicher doch....
Hallo,
anbei die beiden Module in einer überarbeiteten Version. Das Gröbste sollte erledigt sein. Ganz zufrieden bin ich aber noch nicht. Am meisten stört mich, dass CLIENT_ID und CLIENT_SECRET aus einem reverse engineering stammen. Sollte hier Velux etwas ändern, wäre doof.
Grüße Jörg
PS: Sofern gewünscht würde ich die Module dann irgendwann ins SVN einchecken.
Danke Jörg!
Ich werde heute Abend testen.
Aussenrollos funktionieren, Innenrollos und Festermotoren haben gar kein SET
Internals:
DEF velux 53141e32171207cd
DEVICE_ID 53141e32171207cd
FUUID 67eaf2aa-f33f-5738-5e06-19d43a082891fff9
GATEWAY velux
NAME velux_blind_1
NR 722
STATE 100
TYPE VELUXACTIVEDEVICE
VERSION 0.1 Beta
eventCount 2
Helper:
DBLOG:
current_position:
myDbLog:
TIME 1746433579.77191
VALUE 100
READINGS:
2025-05-05 10:28:47 battery_state high
2025-05-05 10:28:47 bridge 70:ee:50:86:28:15
2025-05-05 10:28:47 current_position 100
2025-05-05 10:28:47 firmware_revision 8
2025-05-05 10:28:47 id 53141e32171207cd
2025-05-05 10:28:47 last_seen 1746433498
2025-05-05 10:28:47 manufacturer Netatmo
2025-05-05 10:28:47 mode algo_disabled
2025-03-31 21:53:14 position 100
2025-05-05 10:28:47 reachable 1
2025-05-05 10:28:47 silent 0
2025-05-05 10:28:47 state 100
2025-05-05 10:28:47 target_position 100
2025-05-05 10:28:47 type NXO
2025-05-05 10:28:47 velux_type blind
Attributes:
alias Kammer Dachfensterrollo
devStateIcon 0:fts_window_roof_shutter 100:fts_window_roof 9.*:fts_window_roof_shutter_90 8.*:fts_window_roof_shutter_80 7.*:fts_window_roof_shutter_70 6.*:fts_window_roof_shutter_60 5.*:fts_window_roof_shutter_50 4.*:fts_window_roof_shutter_40 3.*:fts_window_roof_shutter_30 2.*:fts_window_roof_shutter_20 1.*:fts_shutter_10
event-on-change-reading .*
eventMap open:Auf close:Zu
group Rollos
icon fts_window_roof_shutter
room Kammer
stateFormat current_position
webCmd Auf:Zu:stop
Zitat von: Mitch am 05 Mai 2025, 10:30:12Aussenrollos funktionieren, Innenrollos und Festermotoren haben gar kein SET
Für jeden velux_type möchte ich die richtigen set-Kommandos bereit stellen. Laut Deinem List für ein Rollo haben die dann den velux_type blind. Welchen velux_type haben Fenstermotoren?
Grüße Jörg
velux_window
Internals:
DEF velux 53252e26171509d2
DEVICE_ID 53252e26171509d2
FUUID 67eaf2ab-f33f-5738-1bbb-32cbceedf78177c3
GATEWAY velux
NAME velux_window_2
NR 730
STATE 0
TYPE VELUXACTIVEDEVICE
VERSION 1.0
eventCount 967
READINGS:
2025-05-07 19:14:02 battery_state high
2025-05-07 19:14:02 bridge 70:ee:50:86:28:15
2025-05-07 19:14:02 current_position 0
2025-05-07 19:14:02 firmware_revision 42
2025-05-07 19:14:02 id 53252e26171509d2
2025-05-07 19:14:02 last_seen 1746509678
2025-05-07 19:14:02 manufacturer Netatmo
2025-05-07 19:14:02 mode algo_disabled
2025-03-31 21:53:15 position 0
2025-05-07 19:14:02 rain_position 50
2025-05-07 19:14:02 reachable 1
2025-05-07 19:14:02 secure_position 7
2025-05-07 19:14:02 silent 0
2025-05-07 19:14:02 target_position 0
2025-05-07 19:14:02 type NXO
2025-05-07 19:14:02 velux_type window
Attributes:
alias Kammer Dachfenster
devStateIcon 0:fts_window_roof 100:fts_window_roof_open_2 6:fts_window_roof_open_1 7:fts_window_roof_open_1
event-on-change-reading .*
eventMap open:Auf close:Zu
group Fenster
icon fts_window_roof
room Kammer
stateFormat current_position
verbose 1
webCmd Auf:Zu:stop
Hallo Mitch,
anbei die angepassten Module.
Grüße Jörg
Fenster Auf geht leider noch nicht, Rest sieht soweit gut aus.
Hallo Mitch,
werden bei den Fenstermotoren die set-Befehle angezeigt?
Am Aufruf für die Befehle habe ich nichts geändert.
Grüße Jörg
Ja, set wird angezeigt.
Ich muss die Woche nochmal testen, bin nur gerade beruflich sehr eingespannt.
Hallo Mitch,
kein Stress. Geht mir genauso.
Grüße Jörg
Hallo Rudi
Zitat von: rudolfkoenig am 30 September 2024, 10:01:04Mit welchem KI wurde es erstellt?
Wer ist der Urheber des Moduls?
Ich glaube hier könnte es langfristig ein rechtliches Problem geben.
Bisher haben wir unseren Code (sofern ins Repository eingecheckt) immer unter GPL veröffentlicht.
Was sagt denn ein Copyright-Anwalt, wenn ganze oder Teile eines Moduls mit einer KI programmiert werden:
a) Müssen wir Programmierer dann dies entsprechend im Kopf des Moduls kennzeichnen?
b) Was sagen denn die Geschäftsbedingungen von ChatGPT4 über die Copyright-Bestimmungen von Code?
c) Kann sich fhem (e.V.) davon freihalten, wenn ein Programmierer fälschlicherweise einen KI generierten Code als seinen eigenen unter GPL veröffentlicht?
Machen wir uns nichts vor - Der Code der Zukunft wird nicht mehr "zu Fuß" von versierten Programmierern geschrieben, sondern von einer KI.
Einfach, weil es geht.
Das mag der Eine oder Andere - berechtigt oder nicht - für befremdlich halten, aber diese Entwicklung dürfte wohl kaum aufzuhalten sein.
Gruß
Sailor
Hallo Sailor,
ich möchte mal einen von vielen Artikeln zu Thema KI, OpenSource und Rechte verlinken: https://ki-kanzlei.de/probleme-bei-ki-programmierung
Da wir unter GPL veröffentlichen und man davon ausgehen kann, das die LLM auch durch Fhem Sourcen trainiert worden sind, würde ich sagen, der erzeugte Code ist auch unter die GPL zu stellen und kann nicht mit einer anderen Lizenz versehen werden.
Grüße Jörg