erster push, no usable code

This commit is contained in:
sebastianschwarz
2025-02-26 21:29:42 +01:00
parent 1655c6645a
commit 55ecec4aa4
6 changed files with 566 additions and 0 deletions

View File

@ -0,0 +1,198 @@
package FHEM::Bluelink;
use strict;
use warnings;
use LWP::UserAgent;
use HTTP::Request::Common qw(GET POST);
use JSON;
use MIME::Base64;
use Digest::SHA qw(sha256_hex);
use Time::HiRes qw(time);
sub Initialize {
my ($hash) = @_;
$hash->{DefFn} = "Bluelink_Define";
$hash->{UndefFn} = "Bluelink_Undef";
$hash->{RenameFn} = "Bluelink_Rename";
$hash->{DeleteFn} = "Bluelink_Delete";
$hash->{NotifyFn} = "Bluelink_Notify";
$hash->{SetFn} = "Bluelink_Set";
$hash->{GetFn} = "Bluelink_Get";
$hash->{AttrList} = "username password language pin client_id client_secret access_token refresh_token";
}
sub Bluelink_Define {
my ($hash, $def) = @_;
my @args = split("[ \t]+", $def);
return "Usage: define <name> Bluelink <API_URL>" if (@args < 3);
$hash->{API_URL} = $args[2];
return;
}
sub Bluelink_Undef {
my ($hash) = @_;
return;
}
sub Bluelink_Set {
my ($hash, $name, $cmd, @args) = @_;
return "\"set $name\" needs at least one argument" unless defined $cmd;
if ($cmd eq "login") {
return Bluelink_Login($hash);
} elsif ($cmd eq "refreshToken") {
return Bluelink_RefreshToken($hash);
} elsif ($cmd eq "setLanguage") {
return Bluelink_SetLanguage($hash, $args[0]);
}
return "Unknown argument $cmd, choose one of login refreshToken setLanguage";
}
sub Bluelink_Get {
my ($hash, $name, $cmd, @args) = @_;
return "\"get $name\" needs at least one argument" unless defined $cmd;
if ($cmd eq "deviceid") {
return Bluelink_GetDeviceID($hash);
} elsif ($cmd eq "token") {
return AttrVal($name, "access_token", "No token available");
}
return "Unknown argument $cmd, choose one of deviceid token";
}
sub Bluelink_GetDeviceID {
my ($hash) = @_;
my $api_url = $hash->{API_URL} . "/api/v1/spa/notifications/register";
my $uuid = lc(sha256_hex(time() . rand()));
my $data = {
pushRegId => uc(unpack("H*", pack("C*", map { int(rand(16)) } (1..32)))),
pushType => "GCM",
uuid => $uuid
};
my $res = Bluelink_HttpPost($api_url, $data);
return "Device ID: " . ($res->{ResMsg}->{DeviceID} // "Not found");
}
sub Bluelink_Login {
my ($hash) = @_;
my $username = AttrVal($hash->{NAME}, "username", "");
my $password = AttrVal($hash->{NAME}, "password", "");
my $api_url = $hash->{API_URL} . "/api/v1/user/signin";
return "Missing username or password" unless $username && $password;
my $data = { email => $username, password => $password };
my $res = Bluelink_HttpPost($api_url, $data);
return "Login failed: " . $res->{errMsg} if $res->{errCode};
my $redirect_url = $res->{redirectUrl};
my $code = (split(/\?code=/, $redirect_url))[1];
return Bluelink_ExchangeCode($hash, $code);
}
sub Bluelink_ExchangeCode {
my ($hash, $code) = @_;
my $api_url = $hash->{API_URL} . "/api/v1/user/oauth2/token";
my $client_id = AttrVal($hash->{NAME}, "client_id", "");
my $client_secret = AttrVal($hash->{NAME}, "client_secret", "");
return "Missing client_id or client_secret" unless $client_id && $client_secret;
my $auth = encode_base64("$client_id:$client_secret", "");
my $data = {
grant_type => "authorization_code",
redirect_uri => $hash->{API_URL} . "/api/v1/user/oauth2/redirect",
code => $code
};
my $res = Bluelink_HttpPost($api_url, $data, { Authorization => "Basic $auth" });
if ($res->{access_token}) {
fhem("attr $hash->{NAME} access_token " . $res->{access_token});
fhem("attr $hash->{NAME} refresh_token " . $res->{refresh_token});
return "Login successful. Token saved.";
}
return "Token exchange failed.";
}
sub Bluelink_RefreshToken {
my ($hash) = @_;
my $api_url = $hash->{API_URL} . "/api/v1/user/oauth2/token";
my $refresh_token = AttrVal($hash->{NAME}, "refresh_token", "");
my $client_id = AttrVal($hash->{NAME}, "client_id", "");
my $client_secret = AttrVal($hash->{NAME}, "client_secret", "");
return "Missing refresh_token, client_id or client_secret" unless $refresh_token && $client_id && $client_secret;
my $auth = encode_base64("$client_id:$client_secret", "");
my $data = {
grant_type => "refresh_token",
redirect_uri => "https://www.getpostman.com/oauth2/callback",
refresh_token => $refresh_token
};
my $res = Bluelink_HttpPost($api_url, $data, { Authorization => "Basic $auth" });
if ($res->{access_token}) {
fhem("attr $hash->{NAME} access_token " . $res->{access_token});
fhem("attr $hash->{NAME} refresh_token " . $res->{refresh_token});
return "Token refreshed.";
}
return "Token refresh failed.";
}
sub Bluelink_SetLanguage {
my ($hash, $language) = @_;
my $api_url = $hash->{API_URL} . "/api/v1/user/language";
my $data = { lang => $language };
my $res = Bluelink_HttpPost($api_url, $data);
return "Language set to $language" if $res;
return "Failed to set language.";
}
sub Bluelink_HttpPost {
my ($url, $data, $headers) = @_;
my $ua = LWP::UserAgent->new();
my $json_data = encode_json($data);
my $req = HTTP::Request->new(POST => $url);
$req->header("Content-Type" => "application/json");
$req->content($json_data);
if ($headers) {
foreach my $key (keys %$headers) {
$req->header($key => $headers->{$key});
}
}
my $res = $ua->request($req);
return decode_json($res->decoded_content) if $res->is_success;
return { error => "Request failed", status => $res->code };
}
1;

View File

@ -0,0 +1,113 @@
package FHEM::BlueLinkVehicle;
use strict;
use warnings;
use JSON;
use Time::Piece;
sub Initialize {
my ($hash) = @_;
$hash->{DefFn} = "BlueLinkVehicle_Define";
$hash->{SetFn} = "BlueLinkVehicle_Set";
$hash->{GetFn} = "BlueLinkVehicle_Get";
$hash->{AttrList} = "vin IODev";
}
sub BlueLinkVehicle_Define {
my ($hash, $def) = @_;
my @args = split("[ \t]+", $def);
return "Usage: define <name> BlueLinkVehicle <VIN> <IODev>" if (@args < 4);
$hash->{VIN} = $args[2];
my $parent = $args[3];
$hash->{IODev} = $defs{$parent};
return "Parent device $parent not found" unless defined $hash->{IODev};
return;
}
sub BlueLinkVehicle_Set {
my ($hash, $name, $cmd, @args) = @_;
return "\"set $name\" needs at least one argument" unless defined $cmd;
if ($cmd eq "update") {
return BlueLinkVehicle_Update($hash);
}
return "Unknown argument $cmd, choose one of update";
}
sub BlueLinkVehicle_Get {
my ($hash, $name, $cmd, @args) = @_;
return "\"get $name\" needs at least one argument" unless defined $cmd;
if ($cmd eq "soc") {
return ReadingsVal($name, "SoC", "Unknown") . " %";
} elsif ($cmd eq "range") {
return ReadingsVal($name, "Range", "Unknown") . " km";
} elsif ($cmd eq "status") {
return ReadingsVal($name, "Status", "Unknown");
}
return "Unknown argument $cmd, choose one of soc range status";
}
sub BlueLinkVehicle_Update {
my ($hash) = @_;
my $name = $hash->{NAME};
my $parent = $hash->{IODev};
my $vin = $hash->{VIN};
return "Parent module not found" unless $parent;
return "VIN not defined" unless $vin;
# Anfrage an das Hauptmodul senden (IOWrite)
IOWrite($parent, "getVehicleStatus", $vin);
return "Request sent to parent module.";
}
sub BlueLinkVehicle_ParseTime {
my ($time_string) = @_;
return unless $time_string;
my $format = "%Y%m%d%H%M%S %z";
my $t = Time::Piece->strptime($time_string . " +0100", $format);
return $t->strftime("%Y-%m-%d %H:%M:%S");
}
sub BlueLinkVehicle_Receive {
my ($hash, $data) = @_;
my $name = $hash->{NAME};
my $json = eval { decode_json($data) };
return "Invalid JSON data received" if $@;
if ($json->{vin} && $json->{vin} eq $hash->{VIN}) {
my $status = $json->{status};
my $soc = $status->{EvStatus}->{BatteryStatus} // "Unknown";
my $range = $status->{EvStatus}->{DrvDistance}->[0]->{RangeByFuel}->{EvModeRange}->{Value} // "Unknown";
my $charging = $status->{EvStatus}->{BatteryCharge} ? "Charging" : "Not Charging";
my $updated = BlueLinkVehicle_ParseTime($status->{Time}) // "Unknown";
readingsBeginUpdate($hash);
readingsBulkUpdate($hash, "SoC", $soc);
readingsBulkUpdate($hash, "Range", $range);
readingsBulkUpdate($hash, "Status", $charging);
readingsBulkUpdate($hash, "LastUpdate", $updated);
readingsEndUpdate($hash, 1);
return "Vehicle data updated.";
}
return "VIN mismatch - ignoring data.";
}
1;