[Modul] Husqvarna Automower Connect

ok, ich probiere das script heute Abend oder morgen aus. bin gerade etwas busy

demel

@hagi: ja, die Fehlermeldung kann ich nachvollziehen.

Ich habe dann eine Frage an die geballte PHP-Kompetenz im Forum

Folgendes Hintergrund
1 in diesem Script werden Funktionen aus zwei verschiedenen Instanzen benutzt, die IPSymconAutomower und IPSymConGoogleMaps.

  1. in dem Modulen habe ich defines z.B. für Fehler

<?php

declare(strict_types=1);

if (!defined('IS_INVALIDCONFIG')) {
    define('IS_INVALIDCONFIG', IS_EBASE + 1);
    define('IS_SERVERERROR', IS_EBASE + 2);
    define('IS_HTTPERROR', IS_EBASE + 3);
    define('IS_FORBIDDEN', IS_EBASE + 4);
}

trait GoogleMapsCommon
{
...



<?php

declare(strict_types=1);

if (!defined('IS_UNAUTHORIZED')) {
    define('IS_UNAUTHORIZED', IS_EBASE + 1);
    define('IS_SERVERERROR', IS_EBASE + 2);
    define('IS_HTTPERROR', IS_EBASE + 3);
    define('IS_INVALIDDATA', IS_EBASE + 4);
    define('IS_DEVICE_MISSING', IS_EBASE + 5);
}

trait AutomowerCommon
...

Zufällig haben hier IS_SERVERERROR und IS_HTTPERROR den gleichen numerischen Wert, muss aber nicht so sein.
Das dient dazu, das ich in dem Modul an den entsprechenden Stellen nicht mit den (aussagelosen) Zahlen operieren muss.

Ich vermute nun, das bei dem Ausführen des Skriptes beide Module geladen werden und damit die defines doppelt definiert werden.

Das Setzen der defines über if (defined()) abzusichern wäre nur machbar, wenn ich über alle Module hinweg die defines eindeutig sind.
das ist zwar machbar aber nicht wirklich sinnvoll.

Gibt es eine Sprachkonstruktion, das das define nur innerhalb der Klasse wirksam ist?

Mir fällt nur ein, die defines durch Konstanten in dem Modul zu Erstzen, also


<?php

declare(strict_types=1);

trait AutomowerCommon
{
    const IS_UNAUTHORIZED = IS_EBASE + 1; 
    const IS_SERVERERROR = IS_EBASE + 2; 
...


und so zu verwenden:

$this->SetStatus(self::IS_SERVERERROR);

wäre das eine sinnvolle Vorgehensweise?

Danke
demel

Kurze Antwort.
Ja, Klassenkonstanten benutzen.
Michael

Hallo Michael,

danke für die Antwort. Ich habe allerdings noch ein Problem:

Fatal error: Traits cannot have constants in /var/lib/symcon/modules/IPSymconGoogleMaps/libs/common.php on line 7

Hintergrund: ich habe in jedem Projekt (da ich fast immer verschiedene Modulen in einem Projekt habe), generell eine libs/common.php, die aus einem Trait besteht.

Diese werden von den jeweiligen modules.php importiert.

In diesem comm.php werden (bisher) die defines gemacht, analog dazu hatte ich die const angelegt.

Weiterhin habe ich in der common.php auch immer eine Funktion GetFormStatus(), die das Mapping der Errorcodes zu Den Fehlermeldungen zentral macht.

Gibt es da eine andere Möglichkeit, dieses Problem zu lösen?

demel

Ich habe nur das noch gefunden:

trait GoogleMapsCommon
{
    static $IS_INVALIDCONFIG = IS_EBASE + 1;
    static $IS_SERVERERROR = IS_EBASE + 2;
    static $IS_HTTPERROR = IS_EBASE + 3;
    static $IS_FORBIDDEN = IS_EBASE + 4;

...
        $this->SetStatus(self::$IS_INVALIDCONFIG);

ist ein unschöner workaround, aber anscheinend funktioniert es.

demel

Static ist korrekt.
Michael

Danke, funktioniert (natürlich)

demel

@hagi

die Module AutoMower und GoogleMaps stehe nun korrigiert als Beta zur Verfügung.
Kannst Du das bitte mal gegenprüfen, ob auch bei dir die Meldung nun weg ist und noch alles korrekt funktioniert?

danke
demel

Alles läuft mit der Beta perfekt ohne Fehlermeldung ab.

Ich danke Dir für die schnelle Bearbeitung!

lg
hagi

weisst Du von welcher Domäne er die runterlädt? Dann könnte ich die ganze Domäne freigeben.

Gruss ud Danke für die Hilfe

gros_ibou

… geben Sie bitte den sogenannten Rücksetz-Pin direkt am Automower ein …

Nur wo:confused: Ich habe mein Passwort verlegt :mad:

MST

PS: Robonect® Hx - 20p-20 für 330x

wissen tu ich das nicht.

Ich habe mal im Firefox Extra -> Web-Entwickler -> Web-Konsole aktiviert und bin auf den Reiter Netzwerkanalyse gegangen und dann die Grafik abgerufen.

Gesehen habe ich dann: maps.googleapis.com, maps.googleapis.com und fonts.googleapis.com.

Gruß
demel

Hallo Demel42,

erst mal: ein dickes Kompliment für das Projekt! Ich besitze seit 1 Woche einen Husqvarna Automower 315x und habe ihn auch gleich mit deinem Modul eingebunden. Vorher hatte ich einen Robomow RC312, der zwar das bessere Mähwerk und einen Kantenmodus hat, aber die Elektronik hinkt 10 Jahre hinterher.

Mich stört nur, dass Dein Modul erst ab IPS 5.3 installierbar ist, Ich tue mich mit dem Update etwas schwer, da IPS bei mir 3.2 GB groß ist und in meinem Augen nichts auf der System-Partition verloren hat. So habe ich nur Testweise ein Update gemacht möchte aber eigentlich auf IPS 5.2 bleiben.

Dann fiel mir beim Recherchieren auf, dass Husqvarna eine API bereitstellt, die auch halbwegs vernünftig dokumentiert ist:

https://developer.1689.cloud/apis

Nun meine Frage: benutzt Du tatsächlich IPS 5.3-spezifische Funktionen ? Beim überfliegen hatte ich zumindest nichts auf Anhieb entdeckt. Könntest Du also die Freigabe schon ab IPS 5.2 ändern ?

Oder noch besser: Könnten wir uns eventuell zusammen tun, auf die API mit Zugang unter :

https://api.amc.husqvarna.dev/v1

umstellen ? Vorteil wäre, dass mehr Funktionen verfügbar sind.

Anbei mal ein „wildes“ Test-Script zur Abfrage des Status und Senden von Befehlen:

<?

/*//////////////////////////////////////////////////////////////////////////////
Test Husqvarna API -Forum                                 2020 by André Liebmann
2020-04-29
--------------------------------------------------------------------------------
Links:				https://developer.1689.cloud/apis/Automower+Connect+API

API:					https://api.amc.husqvarna.dev/v1

Swagger:				https://developer.1689.cloud/apis/Automower+Connect+API#/swagger

Application key: 		XXXXXXXX-XXXX-XXXX-XXXX-XXXXXXXXXXXX

Application secret: 	        XXXXXXXX-XXXX-XXXX-XXXX-XXXXXXXXXXXX

/*//////////////////////////////////////////////////////////////////////////////

$Username			= "Mail-Adresse";

$Password			= "XXXX";

$Application_Key	        = "XXXXXXXX-XXXX-XXXX-XXXX-XXXXXXXXXXXX";

$Application_Secret	= "XXXXXXXX-XXXX-XXXX-XXXX-XXXXXXXXXXXX";

$URl_Access			= "https://api.amc.husqvarna.dev/v1"; 

$URL_Token			= "https://api.authentication.husqvarnagroup.dev/v1/oauth2/token";

$Mower_ID			= "XXXXXXXXX-XXXXXXXX";
	
/*//////////////////////////////////////////////////////////////////////////////
function Automower_Get_Token($Application_Key, $Username, $Password, $URL_Token)
--------------------------------------------------------------------------------
CURL-Abfrage zur Ermittlung des Token
/*//////////////////////////////////////////////////////////////////////////////

function Automower_Get_Token($Application_Key, $Username, $Password, $URL_Token)
	{
	$curl = curl_init();
	
	$auth_data  = "grant_type=password&";
	$auth_data .= "client_id=".$Application_Key."&";
	$auth_data .= "username=".$Username."&";
	$auth_data .= "password=".$Password;
	
	curl_setopt($curl, CURLOPT_POST, 1);
	curl_setopt($curl, CURLOPT_POSTFIELDS, $auth_data);
	curl_setopt($curl, CURLOPT_URL, $URL_Token);
	curl_setopt($curl, CURLOPT_RETURNTRANSFER, 1);
	curl_setopt($curl, CURLOPT_HTTPAUTH, CURLAUTH_BASIC);
	
	$Result = curl_exec($curl);
	
	if(!$Result)
		{
		die("Connection Failure");
		}
	curl_close($curl);
	
	$ResultArray = json_decode($Result, true); //print_r($ResultArray);
	$Token       = $ResultArray["access_token"]; echo("
Token: $Token

");
	
	return $Token;
	}

$Token = Automower_Get_Token($Application_Key, $Username, $Password, $URL_Token);

/*//////////////////////////////////////////////////////////////////////////////
Alle Mower abfragen
/*//////////////////////////////////////////////////////////////////////////////

$URL = "https://api.amc.husqvarna.dev/v1/mowers";

if($Token)
	{	
	$curl = curl_init();
	
	curl_setopt($curl, CURLOPT_HTTPHEADER, array(
		"accept: application/vnd.api+json",
		"X-Api-Key: ".$Application_Key,
		"Authorization: Bearer ".$Token,
		"Authorization-Provider: husqvarna",
		));
	
	curl_setopt($curl, CURLOPT_URL, $URL);
	curl_setopt($curl, CURLOPT_RETURNTRANSFER, 1);
	
	$Result = curl_exec($curl);
	
	if(!$Result)
		{
		die("Connection Failure 2");
		}
	
	curl_close($curl);
	
	$ResultArray = json_decode($Result, true); //print_r($ResultArray);
	}

/*//////////////////////////////////////////////////////////////////////////////
Ersten Mower abfragen
/*//////////////////////////////////////////////////////////////////////////////

$Mower_ID = $ResultArray["data"][0]["id"]; //echo($Mower_ID);

$URL = "https://api.amc.husqvarna.dev/v1/mowers/".$Mower_ID;

if($Token)
	{	
	$curl = curl_init();
	
	curl_setopt($curl, CURLOPT_HTTPHEADER, array(
		"accept: application/vnd.api+json",
		"X-Api-Key: ".$Application_Key,
		"Authorization: Bearer ".$Token,
		"Authorization-Provider: husqvarna",
		));
	
	curl_setopt($curl, CURLOPT_URL, $URL);
	curl_setopt($curl, CURLOPT_RETURNTRANSFER, 1);
	
	$Result = curl_exec($curl);
	
	if(!$Result)
		{
		die("Connection Failure 2");
		}
	
	curl_close($curl);
	
	$ResultArray = json_decode($Result, true); //print_r($ResultArray);
	}

//echo(Automower_Request($Mower_ID, $Application_Key, $Token, $Method, $Action, $Attributes));

/*//////////////////////////////////////////////////////////////////////////////
function Automower_Send_Action

JSON-Strukturen:

Pause mower:

    data: {
      type: 'Pause'
    }

Park mower until next scheduled run:

    data: {
      type: 'ParkUntilNextSchedule'
    }

Park mower until further notice, overriding schedule:

    data: {
      type: 'ParkUntilFurtherNotice'
    }
	
Park mower for a duration of time, overriding schedule:

    data: {
      type: 'Park',
      attributes: {
        duration: 24
      }
    }

Resume mower according to schedule:

    data: {
      type: 'ResumeSchedule'
    }

Start mower and cut for a duration of time, overriding schedule:
	
    data: {
      type: 'Start',
      attributes: {
        duration: 24
      }
    }
	
/*//////////////////////////////////////////////////////////////////////////////

// Husqvarna API - URL
$URL = "https://api.amc.husqvarna.dev/v1/mowers/".$Mower_ID."/actions";
$Action = "Start";
$Attributes = '"attributes": { "duration":10 }';
$Message = '
		{
		"data": {
			"type":  "'.$Action.'",
			'.$Attributes.'
			}
		}
	';
$Method = "POST";

echo(Automower_Request($URL, $Mower_ID, $Application_Key, $Token, $Method, $Message));

function Automower_Request($URL, $Mower_ID, $Application_Key, $Token, $Method, $Message)
	{
	
	$URL = "https://api.amc.husqvarna.dev/v1/mowers/".$Mower_ID."/actions";
	
	// prepare CURL
	$curl = curl_init();
	
	curl_setopt($curl, CURLOPT_HTTPHEADER, array(
			"Accept: */*",
			"Content-Type: application/vnd.api+json",
			"X-Api-Key: ".$Application_Key,
			"Authorization: Bearer ".$Token,
			"Authorization-Provider: husqvarna",
			));
	
	curl_setopt($curl, CURLOPT_URL, $URL);
	
	curl_setopt($curl, CURLOPT_CUSTOMREQUEST, $Method); 
	
	// set POSTFIELDS for "POST"
	if($Method == "POST")
		{
		curl_setopt($curl, CURLOPT_POSTFIELDS, $Message); 
		}                          
		
	curl_setopt($curl, CURLOPT_RETURNTRANSFER, 1);
	
	$Result = curl_exec($curl); print_r($Result);
	
	curl_close($curl);
	
	// proof response
	if(preg_match("/errors/i", $Result))
		{
		$Detail = json_decode($Result, true);
		print_r($Detail);
		$Notification = 'ERROR: '.$Detail["errors"][0]["title"].' | '.$Detail["errors"][0]["detail"];
		}
	else
		{
		$Notification = "OK";
		} 
	
	// return response
	return $Notification;
	}

Sowie Status- und Error-Arrays:

<?php

/*//////////////////////////////////////////////////////////////////////////////
Husqvarna States and Errors                               2020 by André Liebmann
2020-04-29
/*//////////////////////////////////////////////////////////////////////////////

$Mode = array(
	'MAIN_AREA' 				=> 'Mower will mow until low battery. Go home and charge. Leave and continue mowing. Week schedule is used. Schedule can be overridden with forced park or forced mowing.',
	'DEMO'					=> 'Same as main area, but shorter times. No blade operation. ',
	'SECONDARY_AREA'		=> 'Mower is in secondary area. Schedule is overridden with forced park or forced mowing. Mower will mow for request time or untill the battery runs out. ',
	'HOME'					=> 'Mower goes home and parks forever. Week schedule is not used. Cannot be overridden with forced mowing. ',
	'UNKNOWN'				=> 'Unknown mode',
	);

$Activity = array(
	'UNKNOWN'				=> 'Unknown activity. ',
	'NOT_APPLICABLE'		=> 'Manual start required in mower. ',
	'MOWING'				=> 'Mower is mowing lawn. If in demo mode the blades are not in operation. ',
	'GOING_HOME'			=> 'Mower is going home to the charging station. ',
	'CHARGING'				=> 'Mower is charging in station due to low battery. ',
	'LEAVING' 				=> 'Mower is leaving the charging station. ',
	'PARKED_IN_CS' 			=> 'Mower is parked in charging station. ',
	'STOPPED_IN_GARDEN' 	=> 'Mower has stopped. Needs manual action to resume. ',
	);

$State = array(
	'UNKNOWN'				=> 'Unknown state. ',
	'NOT_APPLICABLE'		=> '',
	'PAUSED'				=> 'Mower has been paused by user. ',
	'IN_OPERATION'			=> 'See value in activity for status. ',
	'WAIT_UPDATING'			=> 'Mower is downloading new firmware. ',
	'WAIT_POWER_UP'		=> 'Mower is performing power up tests. ',
	'RESTRICTED'			=> 'Mower can currently not mow due to week calender, or override park. ',
	'OFF'					=> 'Mower is turned off. ',
	'STOPPED'				=> 'Mower is stopped requires manual action. ',
	'ERROR'					=> 'An error has occurred. Check errorCode. Mower requires manual action. ',
	'FATAL_ERROR'			=> 'An error has occurred. Check errorCode. Mower requires manual action. ',
	'ERROR_AT_POWER_UP'	=> 'An error has occurred. Check errorCode. Mower requires manual action. ',
	);

$Error_Codes = array(
	'0'  => 'Unexpected error',
	'1'  => 'Outside working area',
	'2'  => 'No loop signal',
	'3'  => 'Wrong loop signal',
	'4'  => 'Loop sensor problem, front',
	'5'  => 'Loop sensor problem, rear',
	'6'  => 'Loop sensor problem, left',
	'7'  => 'Loop sensor problem, right',
	'8'  => 'Wrong PIN code',
	'9'  => 'Trapped',
	'10' => 'Upside down',
	'11' => 'Low battery',
	'12' => 'Empty battery',
	'13' => 'No drive',
	'14' => 'Mower lifted',
	'15' => 'Lifted',
	'16' => 'Stuck in charging station',
	'17' => 'Charging station blocked',
	'18' => 'Collision sensor problem, rear',
	'19' => 'Collision sensor problem, front',
	'20' => 'Wheel motor blocked, right',
	'21' => 'Wheel motor blocked, left',
	'22' => 'Wheel drive problem, right',
	'23' => 'Wheel drive problem, left',
	'24' => 'Cutting system blocked',
	'25' => 'Cutting system blocked',
	'26' => 'Invalid sub-device combination',
	'27' => 'Settings restored',
	'28' => 'Memory circuit problem',
	'29' => 'Slope too steep',
	'30' => 'Charging system problem',
	'31' => 'STOP button problem',
	'32' => 'Tilt sensor problem',
	'33' => 'Mower tilted',
	'34' => 'Cutting stopped - slope too steep',
	'35' => 'Wheel motor overloaded, right',
	'36' => 'Wheel motor overloaded, left',
	'37' => 'Charging current too high',
	'38' => 'Electronic problem',
	'39' => 'Cutting motor problem',
	'40' => 'Limited cutting height range',
	'41' => 'Unexpected cutting height adj',
	'42' => 'Limited cutting height range',
	'43' => 'Cutting height problem, drive',
	'44' => 'Cutting height problem, curr',
	'45' => 'Cutting height problem, dir',
	'46' => 'Cutting height blocked',
	'47' => 'Cutting height problem',
	'48' => 'No response from charger',
	'49' => 'Ultrasonic problem',
	'50' => 'Guide 1 not found',
	'51' => 'Guide 2 not found',
	'52' => 'Guide 3 not found',
	'53' => 'GPS navigation problem',
	'54' => 'Weak GPS signal',
	'55' => 'Difficult finding home',
	'56' => 'Guide calibration accomplished',
	'57' => 'Guide calibration failed',
	'58' => 'Temporary battery problem',
	'59' => 'Temporary battery problem',
	'60' => 'Temporary battery problem',
	'61' => 'Temporary battery problem',
	'62' => 'Temporary battery problem',
	'63' => 'Temporary battery problem',
	'64' => 'Temporary battery problem',
	'65' => 'Temporary battery problem',
	'66' => 'Battery problem',
	'67' => 'Battery problem',
	'68' => 'Temporary battery problem',
	'69' => 'Alarm! Mower switched off',
	'70' => 'Alarm! Mower stopped',
	'71' => 'Alarm! Mower lifted',
	'72' => 'Alarm! Mower tilted',
	'73' => 'Alarm! Mower in motion',
	'74' => 'Alarm! Outside geofence',
	'75' => 'Connection changed',
	'76' => 'Connection NOT changed',
	'77' => 'Com board not available',
	'78' => 'Slipped - Mower has Slipped.Situation not solved with moving pattern',
	'79' => 'Invalid battery combination - Invalid combination of different battery types.',
	'80' => 'Cutting system imbalance Warning',
	'81' => 'Safety function faulty',
	'82' => 'Wheel motor blocked, rear right',
	'83' => 'Wheel motor blocked, rear left',
	'84' => 'Wheel drive problem, rear right',
	'85' => 'Wheel drive problem, rear left',
	'86' => 'Wheel motor overloaded, rear right',
	'87' => 'Wheel motor overloaded, rear left',
	'88' => 'Angular sensor problem',
	'89' => 'Invalid system configuration',
	'90' => 'No power in charging station',
	);

?>

Wenn ich die Struktur Deiner Scripte sehe bist Du mir da allerdings Meilenweit voraus. Mit Modulen habe ich Null-Erfahrung :rolleyes:

Besten Gruß

André

PS: gern auch über PN

Hmm, ganz kann ich das nicht mehr sagen, warum ich auf 5.3 gegangen bin. Ich habe auch kein 5.2 Testsystem mehr, daher kann ich das auch nicht so einfach testen. Hmm… Wenn ich tippen würde, wäre das der Konfigurator in AutomowerConfig und da das Element ‚info‘.

Grundsätzlich würde ich aber davon abraten, auf älteren Versionen „kleben“ zu bleiben.

Ja, die Frage ist schon gekommen und folgendes hatte ich dazu gesagt

ja, ich werde den OAuth-Zugang implementieren, im Winter habe ich es mangels Testmöglichkeit nicht gemacht und die letzten Wochen hatte ich keinen Kopf dafür, da war zu viel anderes zu regeln.
Aber es kommt!

Nur: das ist keine Ablösung der alten API, es fehlt etwas ganz wesentlichen in der API: die GPS-Positionen. Den Nutzern des Moduls, mit denen ich zu tun hatte, war das immer wichtig. Zwei Anfragen an den dort angegebenen Support wurde noch nicht einmal quittiert, geschweige denn beantwortet.

Also muss ein ein Zwitter werden, mit der neuen API, die (neben OAuth) etwas mehr Funktionen hat) und zusätzlich der Abruf nach der alten API, damit die GPS-Daten noch abgerufen werden.

Gruß
demel

@demel42

… ist was dran.

Gruß André

Hallo demel42,

die Parkfunktion setzt leider den Timer außer Kraft. Wie hast Du die Befehle herausbekommen? Gibt es da noch andere ?
Werde mich mal weiter mit der neuen API beschäftigen, da es da auch ohne Timerabschltung gehen sollte. Aber jedenfalls hast recht: es sollte ein Zwitter werden. Die GPS Daten machen das Ganze ja erst interessant.

VG André

Im Internet gesucht und andere Implementierungen gefunden. Ich vermute, das ich die Links im README aufgeführt habe.

Frage: wie kommst du denn an die neue API? Die setzt doch OAuth voraus, was paresy zwar eingerichtet hat, was aber noch nicht funktioniert (hatte ich paresy am Freitag geschrieben).

gruß
demle

Hallo demel42,

man muss eine App bei Husqvarna Developer registrieren und erhält einen Application-Key. Dann weiter mit CURL wie in meinem Test-Script, also ohne IPS.

VG André

PS: die Links stehen oben im Script. Registrieren muss man sich natürlich noch.

mal schauen, ob paresy sich demnächst meldet, sonst mache ich beide Anmeldungen.
das mit den „Developer-Keys“ habe ich in diversen Modulen gemacht, hat nur den Nachteil, das sich jeder User selbst einen solche Key holen muss. Mit OAuth entfällt das.

mal schauen
demel