2
0
mirror of https://github.com/fhem/fhem-mirror.git synced 2025-04-19 18:56:03 +00:00

vitoconnect: neue Readings für heating.burners.0.*

git-svn-id: https://svn.fhem.de/fhem/trunk@24795 2b470e98-0d58-463d-a4d8-8e2adae1ed80
This commit is contained in:
andreas13 2021-07-24 16:22:13 +00:00
parent 723e117809
commit 4e75f0a897

View File

@ -190,7 +190,9 @@
# 2021-07-19 Anpassungen für privaten apiKey. Redirect URIs muss "http://localhost:4200/" sein.
# Nutzung des refresh_token
#
# ToDo: timeout konfigurierbar machen
# 2021-07-19 neue Readings für heating.burners.0.*
#
# ToDo: timeout, intervall konfigurierbar machen
# Attribute implementieren und dokumentieren
# Mehrsprachigkeit
# Auswerten der Readings in getCode usw.
@ -212,13 +214,11 @@ use DateTime;
use Time::Piece;
use Time::Seconds;
my $client_secret = "2e21faa1-db2c-4d0b-a10f-575fd372bc8c-575fd372bc8c";
my $callback_uri = "http://localhost:4200/";
my $apiURLBase = "https://api.viessmann-platform.io/iot/v1/equipment/";
my $apiURL = "https://api.viessmann.com/iot/v1/equipment/";
my $RequestList = {
"heating.boiler.serial.value" => "Kessel_Seriennummer",
"heating.boiler.temperature.value" => "Kessel_Solltemperatur",
@ -242,6 +242,12 @@ my $RequestList = {
"heating.burner.statistics.hours" => "Brenner_Betriebsstunden",
"heating.burner.statistics.starts" => "Brenner_Starts",
"heating.burners.0.active" => "Brenner_1_aktiv",
"heating.burners.0.modulation.unit" => "Brenner_1_Modulation/Einheit",
"heating.burners.0.modulation.value" => "Brenner_1_Modulation",
"heating.burners.0.statistics.hours" => "Brenner_1_Betriebsstunden",
"heating.burners.0.statistics.starts" => "Brenner_1_Starts",
"heating.circuits.enabled" => "Aktive_Heizkreise",
"heating.circuits.0.active" => "HK1-aktiv",
"heating.circuits.0.type" => "HK1-Typ",
@ -608,30 +614,42 @@ my $RequestList = {
"heating.gas.consumption.dhw.week" => "Gasverbrauch_WW/Woche",
"heating.gas.consumption.dhw.month" => "Gasverbrauch_WW/Monat",
"heating.gas.consumption.dhw.year" => "Gasverbrauch_WW/Jahr",
"heating.gas.consumption.dhw.dayValueReadAt" => "Gasverbrauch_WW/Tag_gelesen_am",
"heating.gas.consumption.dhw.weekValueReadAt" => "Gasverbrauch_WW/Woche_gelesen_am",
"heating.gas.consumption.dhw.monthValueReadAt" => "Gasverbrauch_WW/Monat_gelesen_am",
"heating.gas.consumption.dhw.yearValueReadAt" => "Gasverbrauch_WW/Jahr_gelesen_am",
"heating.gas.consumption.dhw.dayValueReadAt" =>
"Gasverbrauch_WW/Tag_gelesen_am",
"heating.gas.consumption.dhw.weekValueReadAt" =>
"Gasverbrauch_WW/Woche_gelesen_am",
"heating.gas.consumption.dhw.monthValueReadAt" =>
"Gasverbrauch_WW/Monat_gelesen_am",
"heating.gas.consumption.dhw.yearValueReadAt" =>
"Gasverbrauch_WW/Jahr_gelesen_am",
"heating.gas.consumption.dhw.unit" => "Gasverbrauch_WW/Einheit",
"heating.gas.consumption.heating.day" => "Gasverbrauch_Heizung/Tag",
"heating.gas.consumption.heating.week" => "Gasverbrauch_Heizung/Woche",
"heating.gas.consumption.heating.month" => "Gasverbrauch_Heizung/Monat",
"heating.gas.consumption.heating.year" => "Gasverbrauch_Heizung/Jahr",
"heating.gas.consumption.heating.dayValueReadAt" => "Gasverbrauch_Heizung/Tag_gelesen_am",
"heating.gas.consumption.heating.weekValueReadAt" => "Gasverbrauch_Heizung/Woche_gelesen_am",
"heating.gas.consumption.heating.monthValueReadAt" => "Gasverbrauch_Heizung/Monat_gelesen_am",
"heating.gas.consumption.heating.yearValueReadAt" => "Gasverbrauch_Heizung/Jahr_gelesen_am",
"heating.gas.consumption.heating.dayValueReadAt" =>
"Gasverbrauch_Heizung/Tag_gelesen_am",
"heating.gas.consumption.heating.weekValueReadAt" =>
"Gasverbrauch_Heizung/Woche_gelesen_am",
"heating.gas.consumption.heating.monthValueReadAt" =>
"Gasverbrauch_Heizung/Monat_gelesen_am",
"heating.gas.consumption.heating.yearValueReadAt" =>
"Gasverbrauch_Heizung/Jahr_gelesen_am",
"heating.gas.consumption.heating.unit" => "Gasverbrauch_Heizung/Einheit",
"heating.gas.consumption.total.day" => "Gasverbrauch_Total/Tag",
"heating.gas.consumption.total.month" => "Gasverbrauch_Total/Woche",
"heating.gas.consumption.total.unit" => "Gasverbrauch_Total/Einheit",
"heating.gas.consumption.total.week" => "Gasverbrauch_Total/Woche",
"heating.gas.consumption.total.year" => "Gasverbrauch_Total/Jahr",
"heating.gas.consumption.total.dayValueReadAt" => "Gasverbrauch_Total/Tag_gelesen_am",
"heating.gas.consumption.total.monthValueReadAt" => "Gasverbrauch_Total/Woche_gelesen_am",
"heating.gas.consumption.total.weekValueReadAt" => "Gasverbrauch_Total/Woche_gelesen_am",
"heating.gas.consumption.total.yearValueReadAt" => "Gasverbrauch_Total/Jahr_gelesen_am",
"heating.gas.consumption.total.dayValueReadAt" =>
"Gasverbrauch_Total/Tag_gelesen_am",
"heating.gas.consumption.total.monthValueReadAt" =>
"Gasverbrauch_Total/Woche_gelesen_am",
"heating.gas.consumption.total.weekValueReadAt" =>
"Gasverbrauch_Total/Woche_gelesen_am",
"heating.gas.consumption.total.yearValueReadAt" =>
"Gasverbrauch_Total/Jahr_gelesen_am",
"heating.gas.consumption.fuelCell.day" =>
"Gasverbrauch_Brennstoffzelle/Tag",
@ -681,10 +699,14 @@ my $RequestList = {
"heating.power.consumption.total.month" => "Stromverbrauch_Total/Monat",
"heating.power.consumption.total.week" => "Stromverbrauch_Total/Woche",
"heating.power.consumption.total.year" => "Stromverbrauch_Total/Jahr",
"heating.power.consumption.total.dayValueReadAt" => "Stromverbrauch_Total/Tag_gelesen_am",
"heating.power.consumption.total.monthValueReadAt" => "Stromverbrauch_Total/Monat_gelesen_am",
"heating.power.consumption.total.weekValueReadAt" => "Stromverbrauch_Total/Woche_gelesen_am",
"heating.power.consumption.total.yearValueReadAt" => "Stromverbrauch_Total/Jahr_gelesen_am",
"heating.power.consumption.total.dayValueReadAt" =>
"Stromverbrauch_Total/Tag_gelesen_am",
"heating.power.consumption.total.monthValueReadAt" =>
"Stromverbrauch_Total/Monat_gelesen_am",
"heating.power.consumption.total.weekValueReadAt" =>
"Stromverbrauch_Total/Woche_gelesen_am",
"heating.power.consumption.total.yearValueReadAt" =>
"Stromverbrauch_Total/Jahr_gelesen_am",
"heating.power.consumption.total.unit" => "Stromverbrauch_Total/Einheit",
"heating.power.production.current.status" =>
@ -837,6 +859,7 @@ sub vitoconnect_Define {
$hash->{".access_token"} = "";
$hash->{".installation"} = "";
$hash->{".gw"} = "";
$hash->{"Redirect_URI"} = $callback_uri;
my $isiwebpasswd = vitoconnect_ReadKeyValue( $hash, "passwd" );
if ( $isiwebpasswd eq "" ) {
@ -965,7 +988,9 @@ sub vitoconnect_Set {
$hash,
"heating.circuits.0.operating.programs.holiday/commands/schedule",
"{\"start\":\"$args[0]\",\"end\":\"$end\"}",
$name, $opt, @args
$name,
$opt,
@args
);
return;
}
@ -980,7 +1005,9 @@ sub vitoconnect_Set {
$hash,
"heating.circuits.1.operating.programs.holiday/commands/schedule",
"{\"start\":\"$args[0]\",\"end\":\"$end\"}",
$name, $opt, @args
$name,
$opt,
@args
);
return;
}
@ -995,7 +1022,9 @@ sub vitoconnect_Set {
$hash,
"heating.circuits.2.operating.programs.holiday/commands/schedule",
"{\"start\":\"$args[0]\",\"end\":\"$end\"}",
$name, $opt, @args
$name,
$opt,
@args
);
return;
}
@ -1005,7 +1034,9 @@ sub vitoconnect_Set {
$hash,
"heating.circuits.0.operating.programs.holiday/commands/schedule",
"{\"start\":\"$start\",\"end\":\"$args[0]\"}",
$name, $opt, @args
$name,
$opt,
@args
);
return;
}
@ -1015,7 +1046,9 @@ sub vitoconnect_Set {
$hash,
"heating.circuits.1.operating.programs.holiday/commands/schedule",
"{\"start\":\"$start\",\"end\":\"$args[0]\"}",
$name, $opt, @args
$name,
$opt,
@args
);
return;
}
@ -1025,26 +1058,43 @@ sub vitoconnect_Set {
$hash,
"heating.circuits.2.operating.programs.holiday/commands/schedule",
"{\"start\":\"$start\",\"end\":\"$args[0]\"}",
$name, $opt, @args
$name,
$opt,
@args
);
return;
}
elsif ( $opt eq "HK1-Urlaub_unschedule" ) {
vitoconnect_action( $hash,
vitoconnect_action(
$hash,
"heating.circuits.0.operating.programs.holiday/commands/unschedule",
"{}", $name, $opt, @args );
"{}",
$name,
$opt,
@args
);
return;
}
elsif ( $opt eq "HK2-Urlaub_unschedule" ) {
vitoconnect_action( $hash,
vitoconnect_action(
$hash,
"heating.circuits.1.operating.programs.holiday/commands/unschedule",
"{}", $name, $opt, @args );
"{}",
$name,
$opt,
@args
);
return;
}
elsif ( $opt eq "HK3-Urlaub_unschedule" ) {
vitoconnect_action( $hash,
vitoconnect_action(
$hash,
"heating.circuits.2.operating.programs.holiday/commands/unschedule",
"{}", $name, $opt, @args );
"{}",
$name,
$opt,
@args
);
return;
}
elsif ( $opt eq "HK1-Zeitsteuerung_Heizung" ) {
@ -1102,29 +1152,32 @@ sub vitoconnect_Set {
return;
}
elsif ( $opt eq "HK1-Solltemperatur_comfort" ) {
vitoconnect_action(
$hash,
vitoconnect_action($hash,
"heating.circuits.0.operating.programs.comfort/commands/setTemperature",
"{\"targetTemperature\":$args[0]}",
$name, $opt, @args
$name,
$opt,
@args
);
return;
}
elsif ( $opt eq "HK2-Solltemperatur_comfort" ) {
vitoconnect_action(
$hash,
vitoconnect_action($hash,
"heating.circuits.1.operating.programs.comfort/commands/setTemperature",
"{\"targetTemperature\":$args[0]}",
$name, $opt, @args
$name,
$opt,
@args
);
return;
}
elsif ( $opt eq "HK3-Solltemperatur_comfort" ) {
vitoconnect_action(
$hash,
vitoconnect_action($hash,
"heating.circuits.2.operating.programs.comfort/commands/setTemperature",
"{\"targetTemperature\":$args[0]}",
$name, $opt, @args
$name,
$opt,
@args
);
return;
}
@ -1148,56 +1201,62 @@ sub vitoconnect_Set {
return;
}
elsif ( $opt eq "HK1-Solltemperatur_normal" ) {
vitoconnect_action(
$hash,
vitoconnect_action($hash,
"heating.circuits.0.operating.programs.normal/commands/setTemperature",
"{\"targetTemperature\":$args[0]}",
$name, $opt, @args
$name,
$opt,
@args
);
return;
}
elsif ( $opt eq "HK2-Solltemperatur_normal" ) {
vitoconnect_action(
$hash,
vitoconnect_action($hash,
"heating.circuits.1.operating.programs.normal/commands/setTemperature",
"{\"targetTemperature\":$args[0]}",
$name, $opt, @args
$name,
$opt,
@args
);
return;
}
elsif ( $opt eq "HK3-Solltemperatur_normal" ) {
vitoconnect_action(
$hash,
vitoconnect_action($hash,
"heating.circuits.2.operating.programs.normal/commands/setTemperature",
"{\"targetTemperature\":$args[0]}",
$name, $opt, @args
$name,
$opt,
@args
);
return;
}
elsif ( $opt eq "HK1-Solltemperatur_reduziert" ) {
vitoconnect_action(
$hash,
vitoconnect_action($hash,
"heating.circuits.0.operating.programs.reduced/commands/setTemperature",
"{\"targetTemperature\":$args[0]}",
$name, $opt, @args
$name,
$opt,
@args
);
return;
}
elsif ( $opt eq "HK2-Solltemperatur_reduziert" ) {
vitoconnect_action(
$hash,
vitoconnect_action($hash,
"heating.circuits.1.operating.programs.reduced/commands/setTemperature",
"{\"targetTemperature\":$args[0]}",
$name, $opt, @args
$name,
$opt,
@args
);
return;
}
elsif ( $opt eq "HK3-Solltemperatur_reduziert" ) {
vitoconnect_action(
$hash,
vitoconnect_action($hash,
"heating.circuits.2.operating.programs.reduced/commands/setTemperature",
"{\"targetTemperature\":$args[0]}",
$name, $opt, @args
$name,
$opt,
@args
);
return;
}
@ -1217,7 +1276,8 @@ sub vitoconnect_Set {
return;
}
elsif ( $opt eq "WW-einmaliges_Aufladen" ) {
vitoconnect_action( $hash, "heating.dhw.oneTimeCharge/commands/$args[0]",
vitoconnect_action( $hash,
"heating.dhw.oneTimeCharge/commands/$args[0]",
"{}", $name, $opt, @args );
return;
}
@ -1400,8 +1460,6 @@ sub vitoconnect_GetUpdate {
}
else {
vitoconnect_getResource($hash);
#vitoconnect_getCode($hash);
}
return;
}
@ -1468,7 +1526,8 @@ sub vitoconnect_getCodeCallback {
vitoconnect_getAccessToken($hash);
}
else {
readingsSingleUpdate( $hash, "state", "Login failure. Check password and apiKey", 1 );
readingsSingleUpdate( $hash, "state",
"Login failure. Check password and apiKey", 1 );
Log3 $name, 1, "$name - Login failure. Check password and apiKey";
}
return;
@ -1481,18 +1540,20 @@ sub vitoconnect_getAccessToken {
my $param = {
url => "https://iam.viessmann.com/idp/v2/token",
hash => $hash,
header =>
"Content-Type: application/x-www-form-urlencoded",
header => "Content-Type: application/x-www-form-urlencoded",
data => "grant_type=authorization_code"
. "&code_verifier=" . $client_secret
. "&code_verifier="
. $client_secret
. "&client_id=$client_id"
. "&redirect_uri=$callback_uri"
. "&code=" . $hash->{".code"} ,
. "&code="
. $hash->{".code"},
sslargs => { SSL_verify_mode => 0 },
method => "POST",
timeout => $hash->{timeout},
callback => \&vitoconnect_getAccessTokenCallback
};
#Log3 $name, 1, "$name - " . $param->{"data"};
HttpUtils_NonblockingGet($param);
return;
@ -1518,7 +1579,8 @@ sub vitoconnect_getAccessTokenCallback {
$hash->{".access_token"} = $access_token;
$hash->{"refresh_token"} = $decode_json->{"refresh_token"};
Log3 $name, 4, "$name - Access Token: " . substr ($access_token, 0, 20) . "...";
Log3 $name, 4,
"$name - Access Token: " . substr( $access_token, 0, 20 ) . "...";
vitoconnect_getGw($hash);
}
else {
@ -1543,16 +1605,17 @@ sub vitoconnect_getRefresh {
my $param = {
url => "https://iam.viessmann.com/idp/v2/token",
hash => $hash,
header =>
"Content-Type: application/x-www-form-urlencoded",
header => "Content-Type: application/x-www-form-urlencoded",
data => "grant_type=refresh_token"
. "&client_id=$client_id"
. "&refresh_token=" . $hash->{"refresh_token"} ,
. "&refresh_token="
. $hash->{"refresh_token"},
sslargs => { SSL_verify_mode => 0 },
method => "POST",
timeout => $hash->{timeout},
callback => \&vitoconnect_getRefreshCallback
};
#Log3 $name, 1, "$name - " . $param->{"data"};
HttpUtils_NonblockingGet($param);
return;
@ -1576,9 +1639,11 @@ sub vitoconnect_getRefreshCallback {
my $access_token = $decode_json->{"access_token"};
if ( $access_token ne "" ) {
$hash->{".access_token"} = $access_token;
#$hash->{"refresh_token"} = $decode_json->{"refresh_token"};
Log3 $name, 4, "$name - Access Token: " . substr ($access_token, 0, 20) . "...";
Log3 $name, 4,
"$name - Access Token: " . substr( $access_token, 0, 20 ) . "...";
vitoconnect_getGw($hash);
}
else {
@ -1758,6 +1823,7 @@ sub vitoconnect_getDeviceCallback {
}
return;
}
sub vitoconnect_getFeatures {
my ($hash) = @_;
my $name = $hash->{NAME};
@ -1767,7 +1833,8 @@ sub vitoconnect_getFeatures {
my $dev = AttrVal( $name, 'vitoconnect_device', 0 );
Log3 $name, 4, "$name - getFeatures went ok";
# Service Documents
# Service Documents -ToDo
# Gateway features
my $param = {
@ -1780,8 +1847,8 @@ sub vitoconnect_getFeatures {
( my $err, my $msg ) = HttpUtils_BlockingGet($param);
my $decode_json = eval { decode_json($msg) };
if ( $err ne "" || $decode_json->{statusCode} ne "" ) {
Log3 $name, 1, "$name - Fehler während "
. "Gateway features: $err :: $msg";
Log3 $name, 1,
"$name - Fehler während " . "Gateway features: $err :: $msg";
}
else {
readingsSingleUpdate( $hash, "gw_features", $msg, 1 );
@ -1798,8 +1865,8 @@ sub vitoconnect_getFeatures {
( my $err, my $msg ) = HttpUtils_BlockingGet($param);
my $decode_json = eval { decode_json($msg) };
if ( $err ne "" || $decode_json->{statusCode} ne "" ) {
Log3 $name, 1, "$name - Fehler während "
. "installation features: $err :: $msg";
Log3 $name, 1,
"$name - Fehler während " . "installation features: $err :: $msg";
}
else {
readingsSingleUpdate( $hash, "installation_features", $msg, 1 );
@ -1837,7 +1904,8 @@ sub vitoconnect_getResource {
my $dev = AttrVal( $name, 'vitoconnect_device', 0 );
Log3 $name, 4, "$name - enter getResource";
Log3 $name, 4, "$name - access_token: " . substr ($access_token, 0, 20) . "...";
Log3 $name, 4,
"$name - access_token: " . substr( $access_token, 0, 20 ) . "...";
Log3 $name, 4, "$name - installation: $installation";
Log3 $name, 4, "$name - gw: $gw";
if ( $access_token eq "" || $installation eq "" || $gw eq "" ) {
@ -1845,7 +1913,8 @@ sub vitoconnect_getResource {
return;
}
my $param = {
url => $apiURL . "installations/$installation/gateways/$gw/devices/$dev/features",
url => $apiURL
. "installations/$installation/gateways/$gw/devices/$dev/features",
hash => $hash,
header => "Authorization: Bearer $access_token",
timeout => $hash->{timeout},
@ -1863,7 +1932,8 @@ sub vitoconnect_getResourceCallback {
if ( $err eq "" ) {
Log3 $name, 4, "$name - getResourceCallback went ok";
Log3 $name, 5, "$name - Received response: " . substr ($response_body, 0,100) . "...";
Log3 $name, 5, "$name - Received response: "
. substr( $response_body, 0, 100 ) . "...";
my $items = eval { decode_json($response_body) };
if ($@) {
readingsSingleUpdate( $hash, "state",
@ -1886,13 +1956,17 @@ sub vitoconnect_getResourceCallback {
"statusCode: $items->{statusCode} "
. "errorType: $items->{errorType} "
. "message: $items->{message} "
. "error: $items->{error}", 1 );
. "error: $items->{error}",
1
);
if ( $items->{statusCode} eq "401" ) {
# EXPIRED TOKEN
vitoconnect_getRefresh($hash);
return;
}
elsif ( $items->{statusCode} eq "404" ) {
# DEVICE_NOT_FOUND
Log3 $name, 1, "$name - Device not found: Optolink prüfen!";
InternalTimer( gettimeofday() + $hash->{intervall},
@ -1900,6 +1974,7 @@ sub vitoconnect_getResourceCallback {
return;
}
elsif ( $items->{statusCode} eq "429" ) {
# RATE_LIMIT_EXCEEDED
Log3 $name, 1,
"$name - Anzahl der möglichen API Calls in überschritten!";
@ -1908,6 +1983,7 @@ sub vitoconnect_getResourceCallback {
return;
}
elsif ( $items->{statusCode} eq "502" ) {
# DEVICE_COMMUNICATION_ERROR error: Bad Gateway
Log3 $name, 1, "$name - temporärer API Fehler";
InternalTimer( gettimeofday() + $hash->{intervall},
@ -1940,10 +2016,13 @@ sub vitoconnect_getResourceCallback {
my $feature = $_;
my $properties = $feature->{properties};
foreach my $key ( keys %$properties ) {
my $Reading = $RequestList->{ $feature->{feature} . "." . $key };
my $Reading =
$RequestList->{ $feature->{feature} . "." . $key };
if ( !defined($Reading)
|| AttrVal( $name, 'vitoconnect_raw_readings', 0 ) eq "1" )
{ $Reading = $feature->{feature}. "." . $key; }
{
$Reading = $feature->{feature} . "." . $key;
}
my $Type = $properties->{$key}->{type};
my $Value = $properties->{$key}->{value};
if ( $Type eq "array" ) {
@ -1951,23 +2030,22 @@ sub vitoconnect_getResourceCallback {
if ( ref($Value) eq 'ARRAY' ) {
my $Array = ( join( ",", @$Value ) );
readingsBulkUpdate( $hash, $Reading, $Array );
Log3 $name, 5,
"$name - $Reading $Array ($Type)";
} else {
Log3 $name, 4, "$name - Array Workaround for Property: $Reading";
Log3 $name, 5, "$name - $Reading $Array ($Type)";
}
else {
Log3 $name, 4,
"$name - Array Workaround for Property: $Reading";
}
}
}
elsif ( $Type eq "Schedule" ) {
my $Result = encode_json($Value);
readingsBulkUpdate( $hash, $Reading, $Result );
Log3 $name, 5,
"$name - $Reading: $Result ($Type)";
Log3 $name, 5, "$name - $Reading: $Result ($Type)";
}
else {
readingsBulkUpdate( $hash, $Reading, $Value );
Log3 $name, 5,
"$name - $Reading: $Value ($Type)";
Log3 $name, 5, "$name - $Reading: $Value ($Type)";
}
}
}
@ -1989,7 +2067,8 @@ sub vitoconnect_action {
my $gw = $hash->{".gw"};
my $dev = AttrVal( $name, 'vitoconnect_device', 0 );
my $param = {
url => $apiURLBase ."installations/$installation/gateways/$gw/"
url => $apiURLBase
. "installations/$installation/gateways/$gw/"
. "devices/$dev/features/$feature",
hash => $hash,
header => "Authorization: Bearer $access_token\r\n"
@ -2003,6 +2082,7 @@ sub vitoconnect_action {
Log3 $name, 4, "$name - data=$param->{data}";
( my $err, my $msg ) = HttpUtils_BlockingGet($param);
my $decode_json = eval { decode_json($msg) };
if ( $err ne "" || $decode_json->{statusCode} ne "" ) {
Log3 $name, 1, "$name - set $name $opt @args: Fehler während der "
. "Befehlsausführung: $err :: $msg";