# AVM FRITZ! DECT 200 über Wago SPS schalten.



## Otwin (22 Dezember 2017)

Hi,

das Projekt lautet: Die Christbaumbeleuchtung muss dieses Jahr schaltbar sein.
Da ich leider keine Leitung in die Nähe des Baumes bekomme, muss es was per Funk sein.
Bestellt habe ich mir jetzt eine AVM DECT 200 Steckdose.
Zur Not lässt sich diese über die Schnurlostelefone schalten.
Schöner wäre aber uber den Lichttaster im Wohnzimmer, also über die 750-8202 (e!Cockpit).
Hier im Forum gibt es einen Beitrag , der schon ein paar Jahre alt ist zum Thema.
Dort habe ich die beiden Links gefunden:

https://avm.de/fileadmin/user_upload/Global/Service/Schnittstellen/AHA-HTTP-Interface.pdf
https://avm.de/fileadmin/user_uploa...ttstellen/AVM_Technical_Note_-_Session_ID.pdf

Den HTTP-Request senden sollte ja kein größeres Problem sein. (Hoffe ich).
Leider habe ich grad gar keine Idee, wie ich an diese Session-ID komme.

Hat sowas jemand schonmal gemacht?

Gruß
Otwin

Und schonmal Frohe Weihnachten!


----------



## Tobsucht (23 Dezember 2017)

Hallo Otwin,

wie Du an die Session ID kommst, steht doch in dem zweiten von Dir verlinkten Dokument.
e!Cockpit bietet die Möglichkeit die MD5 Hashsumme zu berechnen und die notwendigen HTTP-GET Telegramme zu senden.

Grüße


----------



## Otwin (23 Dezember 2017)

Die Sache mit dem HTTP-GET krieg ich denke ich hin, aber zu der MD5 Hashsumme hab ich gar nichts gefunden.
MD5sum ist zwar im Betriebssystem des Controllers verfügbar, aber wie ich im SPS-Programm diesen Hash berechnen kann, da bin ich echt ratlos.
Hast du da mal ein Beispiel, wie und mit welcher Bibliothek das geht?

Gruß
Otwin


----------



## Tobsucht (24 Dezember 2017)

Eine Suche im Bibliotheksverwalter zeigt, dass die Bibliothek CAA Memory den Funktionsblock MD5 enthält.
Mit einem Minimalprogramm kann man testen ob der Baustein erwartungsgemäß funktioniert.


```
PROGRAM PLC_PRG
VAR
    xCalc: BOOL;
    sData: STRING := 'Hallo Welt';
    xDone: BOOL;
    xBusy: BOOL;
    aResult: ARRAY [0..15] OF BYTE;
    sResult: STRING;
    oMD5:  MEM.MD5;
END_VAR

oMD5(
    xExecute:= xCalc, 
    pMemoryBlock:= ADR(sData), 
    udiNumberOfBytes:= len(sData), 
    xDone=> xDone, 
    xBusy=> xBusy, 
    abyDigest=> aResult );
```

Grüße


----------



## Otwin (26 Dezember 2017)

Hi, vielen Dank für den Beispielcode.
Zu meiner Schande muss ich gestehen: Ich brings trotzdem nicht hin.

Was bisher funktioniert:
Die Challenge beziehen und aus dem XML extrahieren war kein großes Problem.
Das PW an die Challenge anhängen ist auch kein Thema.

Aber jetzt kommts, ich zitiere mal aus einer Anleitung:

<Zitat>
Mit Hilfe der erhaltenen Challenge kann man nun die Antwort konstruieren. An die Challenge wird durch Minus-Zeichen getrennt das Passwort der Fritzbox angehängt, 
der ganze String dann mit Hilfe einer Multibyte-Funktion in die erforderliche UTF16-LE-Kodierung konvertiert und letztlich daraus ein MD5-Hash gebildet. 
Der Hash verbunden mit der Challenge ist das kodierte Password, das übergeben wird:


http://fritz.box/login_sid.lua?username=&response=8a1532c1-e416f62700997cab86e49c6c080dbe27
</Zitat>

Wie konvertiere ich den String in UTF16? Oder macht das die Funktion MD5 automatisch.
Wenn ich das Byte-Array, das mir MD5 zurückliefert in einen String konvertiere, kommt da was sehr Kryptische bei raus (zB  'Ï‰ïÜ¯�ž°9Â  \¥~%').
Ein Login geht damit komischerweise nicht. 

Gruß
Otwin


----------



## Otwin (26 Dezember 2017)

So, ich hab noch ein wenig herum experimentiert.
Das Problem liegt wohl an der Konvertierung nach UTF16

Auf einer Linux-Console liefert mit : echo -n Otwin |md5sum folgenden Hash:
6048e555366c9024afcd6bc2cbadc3df

Den selben Hash erhalte ich auch in e!Cockpit, wenn ich 'Otwin' durch den MD5-Baustein schicke.

Laut der Anleitung muss ich aber folgendes auf der Console eingeben:
echo -n Otwin | iconv -f ISO8859-1 -t UTF-16LE | md5sum
Hier erhalte ich einen völlig anderen Hash, der auch zum login auf der Fritzbox funktioniert (natürlich mit dem richtigen Passwort und nicht mit 'Otwin')
d53c96c1342f0bd4242cdb5b4d8f1a34

Ein: echo -n Otwin | iconv -f ISO8859-1 -t UTF-16LE liefert aber 'Otwin'

Ich bin leider absolut ratlos, wie ich die Challenge von der Fritzbox in dieses UTF16-LE konvertieren muss.

Jemand eine Idee?

Gruß
Otwin


----------



## Tobsucht (28 Dezember 2017)

Hallo,

da hast Du recht. Der CoDeSys String arbeitet mit acht bit Zeichen. Bei UTF16 hat jedes Zeichen 16 Bit (also führende Nullen bei "Standardzeichen").

Ich überlege ob es nicht sinnvoller wäre dies ins Linux auszulagern und den String an die WagoAppConfigTool übergibt. Diese ruft ein Script auf welches in UTF16 Konvertiert und die MD5 Checksumme berechnet. Diese Checksumme wird dann wieder ins e!Cockpit zurückgegeben.

Linux Script zur MD5 Berechnung. Hier fehlt dann noch das Wandeln zu UTF16.

```
#!/bin/sh
echo $1 | md5sum | awk '{ print $1 }'
```


----------



## Otwin (28 Dezember 2017)

So, ich habe einen funktionierenden Code.
OK, ist etwas quick and dirty aber es geht 
Die Konvertierung von UTF8 nach UTF16 ist wohl wenig Normenkonform und funktioniert auch nur, wenn die Passwörter lediglich aus ASCII-Zeichen bestehen.
Diese Geschichte in Linux zu erledigen wäre bestimmt richtiger, aber ich stehe auf "keep it fucking simple".


```
PROGRAM prg_FritzBox_Login
VAR_INPUT
	xDoIt:				BOOL;			// Trigger zum Verbindungsaufbau
	sIP_Adress:			STRING;			// IP-Adresse der FritzBox (zB. 192.168.168.1)
	sUser:				STRING;			// User auf der FritzBox
	sPassword:			STRING;			// Passwort des Users
END_VAR
VAR_OUTPUT
	SID:				        STRING;			// Empfangene SID
	sURL_Rumpf:			STRING(255);	        // Anfang der URL für weitere Verbindungen
END_VAR
VAR
	Loopcount:			INT;
	iState:				INT;
	iStateOld:			INT;
	TimeStamp:			LTIME;
	TimeStampLastChange:LTIME;
	SSL_Options:		typSSL_Options;
	// 10 Get challange
		FbHTTPs_Get_Challenge:		FbHTTPs_Get;
		xDoGetChallenge:			BOOL;
		arEmpfangChallenge:			ARRAY[0..255] OF BYTE;


	// 20 Extract Challenge from arEmpfangChallenge
		sEmpfangChellenge:			STRING(255);
		udiChallangeStart:			UDINT;
		sChallenge:					STRING;
		
	// 30 Challenge und Passwort verbinden
		sChallengeUndPW:			STRING(255);
		
	// 40 Challenge und Passwort nach UTF16 konvertieren
		iLaengeChallengeUndPW: 	INT;
		arChallengeUndPW:		ARRAY[0..100] OF BYTE;
		xOdd:					BOOL;
		iCharCount:				INT;
		
	// 50 MD5 Hash berechnen
		xDoMD5:					BOOL:=FALSE;
		MD5_0:					MEM.MD5;
		arResponse:				ARRAY[0..15]OF BYTE;
	// 60 Response in HEX wandeln
		arHexString:			ARRAY[0..15]OF STRING;
		sResponse_AS_HEX:		STRING;		
		
	// 70 URL für Login zusammenstellen
		sLoginURL:				STRING(255);
		
	// 80 SID erfragen
		FbHTTPs_Get_SID:		FbHTTPs_Get;
		xDoGetSID:				BOOL;
		arEmpfangSID:			ARRAY[0..255] OF BYTE;
		
	//	90 Extract SID from arEmpfangSID
		sEmpfangSID:			STRING;
		udiSIDStart:			UDINT;
		sSID:					STRING;
		
	// 200 Keep Alive
	
	// 201 Send KeepAlive Request (Geräteliste holen)
		sGeraeteURL:				STRING(255);


		FbHTTPs_Get_Geraeteliste:	FbHTTPs_Get;
		xDoGetGeraeteliste:			BOOL;
		arEmpfangGeraeteliste:		ARRAY[0..255] OF BYTE;	
		sGeraeteliste:				STRING(255);
		
END_VAR
```


```
TimeStamp := FuGetLongTime();
IF iState <> iStateOld THEN
	TimeStampLastChange := TimeStamp;
	iStateOld := iState;
END_IF
SSL_Options.xVerifyHost := FALSE;
SSL_Options.xVerifyPeer := FALSE;




CASE iState OF
	0:	// Init, wait for trigger
		TimeStampLastChange := TimeStamp;
		iStateOld := iState;
		IF xDoIt THEN	
			iState := 10;
		END_IF	
	10: // Get challenge from Fritzbox
		xDoGetChallenge :=TRUE;
	
		FbHTTPs_Get_Challenge(
			sURI:= 	Concat3(s1:= 'https://', s2:= sIP_Adress, s3:= '/login_sid.lua'),
			sUser:= , 
			sPassword:= , 
			sHeader:= , 
			eAuthentication:= 0, 
			pRxBuffer:= ADR(arEmpfangChallenge), 
			udiRxBufferSize:= SIZEOF(arEmpfangChallenge), 
			tTimeout:= , 
			typSSL_Options:= SSL_Options, 
			xTrigger:= xDoGetChallenge, 
			xBusy=> , 
			xError=> , 
			oStatus=> , 
			udiRxNBytes=> );
		
			IF NOT FbHTTPs_Get_Challenge.xBusy THEN
				iState := 20;
			END_IF
			
	20:	// Extract Challenge from arEmpfangChallenge
		// Empfangene Daten in String wandeln
			sEmpfangChellenge := Mem_to_PrintableString(pData:= ADR(arEmpfangChallenge), udiDataSize:= SIZEOF(arEmpfangChallenge));
		// Startposition des Challenge finden
			udiChallangeStart := StrFindLeft(sBuffer:= sEmpfangChellenge, udiSize:= SIZEOF(sEmpfangChellenge) , sProbe:= '<Challenge>', udiBegin:= 1);
		// Challenge herausschneiden
			sChallenge := StrExtractSub(sBuffer:= sEmpfangChellenge, udiSize:= SIZEOF(sEmpfangChellenge), udiPos:= udiChallangeStart+11, udiLength:= 8);
			
		iState := 30;
	30: // Challenge und Passwort verbinden
			sChallengeUndPW := Concat3(s1:= sChallenge, s2:= '-', s3:= sPassword);
		iState := 40;
		
	40: // Challenge und Passwort nach UTF16 konvertieren
		iLaengeChallengeUndPW := LEN(sChallengeUndPW);
		iCharCount :=1;
		FOR Loopcount := 0 TO (2 * iLaengeChallengeUndPW)-1 DO
			IF Loopcount = 0 THEN
				arChallengeUndPW[Loopcount] := StrExtractChar(sChallengeUndPW,(iCharCount));
				iCharCount := iCharCount + 1;
				xOdd := FALSE;
			END_IF
			IF Loopcount > 0 THEN
				IF xOdd THEN
					arChallengeUndPW[Loopcount] := StrExtractChar(sChallengeUndPW,(iCharCount));
					iCharCount := iCharCount + 1;
					xOdd := FALSE;		
				ELSE
					arChallengeUndPW[Loopcount] := 0;
					xOdd := TRUE;					
				END_IF	
			END_IF
		END_FOR			
			
		iState := 50;
		xDoMD5 := FALSE;	
	50: // MD5 Hash berechnen
		MD5_0(
			xExecute:= xDoMD5, 
			pMemoryBlock:= ADR(arChallengeUndPW), 
			udiNumberOfBytes:= (2 * iLaengeChallengeUndPW), 
			xDone=> , 
			xBusy=> , 
			abyDigest=> arResponse);
	
		IF MD5_0.xDone THEN
			iState := 60;
		END_IF	
		xDoMD5 := TRUE;	// Wegen Flankenwechsel
	60: // Response in HEX wandeln
		FOR Loopcount := 0 TO 15 DO
			arHexString[Loopcount] := Byte_to_Hexstring(arResponse[Loopcount]);
		END_FOR


		sResponse_AS_HEX:=Concat9(
			s1:= arHexString[0], 
			s2:= arHexString[1], 
			s3:= arHexString[2], 
			s4:= arHexString[3], 
			s5:= arHexString[4], 
			s6:= arHexString[5], 
			s7:= arHexString[6], 
			s8:= arHexString[7], 
			s9:= arHexString[8]);
		
		sResponse_AS_HEX:=Concat8(
			s1:= sResponse_AS_HEX, 
			s2:= arHexString[9], 
			s3:= arHexString[10], 
			s4:= arHexString[11], 
			s5:= arHexString[12], 
			s6:= arHexString[13], 
			s7:= arHexString[14], 
			s8:= arHexString[15]);




		iState := 70;
		
	70:	// URL für Login zusammenstellen
		sLoginURL := Concat6(	s1:= 'https://', 
								s2:= sIP_Adress,
								s3:= '/login_sid.lua?username=&response=',
								s4:= sChallenge, 
								s5:= '-', 
								s6:= sResponse_AS_HEX);
								
		iState := 80;
		
	80: // SID erfragen
		xDoGetSID :=TRUE;
	
		FbHTTPs_Get_SID(
			sURI:= sLoginURL, 
			sUser:= , 
			sPassword:= , 
			sHeader:= , 
			eAuthentication:= 0, 
			pRxBuffer:= ADR(arEmpfangSID), 
			udiRxBufferSize:= SIZEOF(arEmpfangSID), 
			tTimeout:= , 
			typSSL_Options:= SSL_Options, 
			xTrigger:= xDoGetSID, 
			xBusy=> , 
			xError=> , 
			oStatus=> , 
			udiRxNBytes=> );
		
			IF NOT FbHTTPs_Get_SID.xBusy THEN
				iState := 90;
			END_IF
	90:	// Extract SID from arEmpfangSID
		// Empfangene Daten in String wandeln
			sEmpfangSID := Mem_to_PrintableString(pData:= ADR(arEmpfangSID), udiDataSize:= SIZEOF(arEmpfangSID));
		// Startposition des SID finden
			udiSIDStart := StrFindLeft(sBuffer:= sEmpfangSID, udiSize:= SIZEOF(sEmpfangSID) , sProbe:= '<SID>', udiBegin:= 1);
		// SID herausschneiden
			sSID := StrExtractSub(sBuffer:= sEmpfangSID, udiSize:= SIZEOF(sEmpfangSID), udiPos:= udiSIDStart+5, udiLength:= 16);
	
		iState := 100;	
	
	100: // SID auf Ausgang schreiben	
		SID := sSID;
		iState := 200;
		sURL_Rumpf := CONCAT6(s1:= 'https://', s2:= sIP_Adress, s3:= '/webservices/homeautoswitch.lua', s4:= '?sid=', s5:= sSID, s6:= '&switchcmd=');
		
	200: // Beenden oder Verbindung halten
		IF NOT xDoIt THEN
			iState := 0;
		END_IF
		IF TimeStamp > TimeStampLastChange + LTIME#3M THEN
			iState := 201;
		END_IF
		
	
	201: // Send KeepAlive Request (Geräteliste holen)
		xDoGetGeraeteliste :=TRUE;
	
		sGeraeteURL := Concat6(
			s1:= 'https://', 
			s2:= sIP_Adress, 
			s3:= '/webservices/homeautoswitch.lua', 
			s4:= '?sid=', 
			s5:= sSID,
			s6:= '&switchcmd=getswitchlist');
	
		FbHTTPs_Get_Geraeteliste(
			sURI:= sGeraeteURL, 
			sUser:= , 
			sPassword:= , 
			sHeader:= , 
			eAuthentication:= 0, 
			pRxBuffer:= ADR(arEmpfangGeraeteliste), 
			udiRxBufferSize:= SIZEOF(arEmpfangGeraeteliste), 
			tTimeout:= , 
			typSSL_Options:= SSL_Options, 
			xTrigger:= xDoGetGeraeteliste, 
			xBusy=> , 
			xError=> , 
			oStatus=> , 
			udiRxNBytes=> );
		
			IF NOT FbHTTPs_Get_Geraeteliste.xBusy THEN
				iState := 200;
				// Empfangene Daten in String wandeln
				sGeraeteliste := Mem_to_PrintableString(pData:= ADR(arEmpfangGeraeteliste), udiDataSize:= SIZEOF(arEmpfangGeraeteliste));
			END_IF
	ELSE
		iState := 0;
END_CASE
```

Der Code dient nur als Beispiel zum login auf einer Fritzbox.
Ausgereift ist das noch nicht!

Gruß
Otwin


----------



## gravieren (15 September 2018)

Hi

Mein Gedanke wäre jetzt, an einer "Wago 750-8202"   und einer  "AVM 7950"    z.b.   ein Heizkörperregler  Fritzdect 301 anzusteuern.

Gibt es da möglicherweise LIBs oder Examples dazu   ?

(Mit CoDeSys 2.3   oder  CoDeSys 3.5     --> eCockpit  )


----------



## Blockmove (15 September 2018)

Ich nutz für sowas als Universalgateway IPSymcon.
Geht aber aber auch mit Node-Red.
Entweder auf einem Raspi oder mit Docker.
Demnächst soll Node-Red auch direkt auf dem PFC laufen.

Gruß
Blockmove


----------



## ms4wago (2 Januar 2021)

Hallo Zusammen,

ich versuche gerade den Code von Otwin auf Codesys 2.3 anzupassen. Allerdings komme ich hier momentan nicht weiter:


```
60: // Response in HEX wandeln
        FOR Loopcount := 0 TO 15 DO
            arHexString[Loopcount] := Byte_to_Hexstring(arResponse[Loopcount]);
        END_FOR
```

Kann mir jemand erklären, was die Funktion Byte_to_Hexstring genau machen wird?

@Otwin: Vielleicht kannst du auch noch den Code von der Funktion posten?

Gruß, Martin


----------



## Tobsucht (6 Januar 2021)

Hallo Martin,

ich denke die Funktion macht genau dass was der Name vermuten lässt. Sie stellt den Wert des Bytes hexadezimal als String dar.
Also den Wert 16#A1 = 161 in 'A1'.
Mir fällt jetzt keine fertige Lösung ein, ist aber nur eine Fingerübung in ST.

Grüße


----------

