Über mich

Mein Name ist Daniel Jäger, ich bin 24 Jahre alt und komme aus der Nähe von Bonn. Seit Beginn meiner Grundschulzeit (als Windows 98 gerade herauskam) begeistere ich mich für Informatik und Technologie. Mit dem Programmieren von kleineren Anwendungen begann ich im Alter von etwa 10 Jahren, damals noch mit Q-Basic. Vor etwa 3 Jahren habe ich mich im Bereich der Internetprogrammierung selbstständig gemacht.

Quick-Start

  1. Fritz!Back aufstellen
  2. Netzwerkkabel am Modul anschließen
  3. Beigefügtes Netzkabel in den Strom-Eingang des Arduinos stecken
Das Modul ist "von Werk aus" auf eine maximale Bandbreite von 50/2.5 MBit eingestellt. Sollte es an ein langsameres Netzwerk angeschlossen werden, muss diese Einstellung mit dem beigefügtem Tool "FritzBackControl" zurückgesetzt werden. Im Falle eines schnelleren Netzwerkes kalibriert sich das Gerät von selbst.

Achtung: Das Arduino sollte nicht über ein USB-Kabel angeschlossen werden. Vor allem nicht bei angeschlossenem Netzkabel.

Projektbeschreibung

Mein Projekt "Fritz!Back" für den "pimp your FRITZ!"-Wettbewerb von AVM besteht aus zwei von einem Arduino gesteuerten, beleuchteten Wassersäulen, die die aktuelle Bandbreitenausnutzung anzeigen. Die linke Säule zeigt die Download-, die rechte die Upload-Rate an.

Ursprünglich hatte ich ein anderes Kunststoff-Gehäuse und zwei 12 Volt-Pumpen (um Luftblasen in zwei Wassertanks zu blasen) mit jeweils einer grünen und einer roten LED zur Beleuchtung.







Da die Säulen allerdings nicht zu 100% wasserdicht waren, und das Gehäuse optisch meinen Ansprüchen nicht genügt hat, habe ich das ganze Projekt ein zweites mal aufgebaut - mit zwei geschlossenen Wassersäulen und ohne dynamische Beleuchtung.






Hardware

Material-Liste

Die meisten Dinge auf meiner Einkaufsliste wie Widerstände, ein Arduino Uno, ein Motor-Shield (aus einem alten Roboter-Projekt ausgebaut) und ein Ethernet-Modul hatte ich noch herumliegen, jedoch ist mir letzteres aus unerklärlichen Gründen durchgebrannt, und musste ersetzt werden (das rote Kabel war der Plus-Pol, richtig?). Die Wassersäulen sind aus zwei Lautsprechern ausgebaut, das Plexiglas für die Gehäuseteile gab es günstig im Baumarkt.

Technik

Ursprünglich wurden zwei 12V-Pumpen über ein Motor-Shield des Arduinos (basierend auf dem L298P-Motor-Treiber) per PWM gesteuert. Diese wurden zusätzlich über zwei Magnetventile verschlossen, um zu verhindern, dass bei ausgeschaltetem Gerät Wasser in die Pumpen zurückfließt. Da diese jedoch nicht zuverlässig geschlossen haben, wurden sie durch einfache Rückschlagventile aus Aquarien-Pumpen ersetzt.

In der finalen und geschlossenen Variante befindet sich der Motor innerhalb des mit nur sehr wenig Wasser gefülltem Kunststoffgehäuses, welches vier kleine Wasserfontänen erzeugt.

Prozessor

Als Rechenprozessor kommt der ATmega328 eines Arduino Unos zum Einsatz - Speicher und RAM reichen gerade so, um zu zeigen, wie effizient so eine 8-Bit-Architektur und ein 16 MHz-Takt noch sein kann. Anfangs hatte ich die Idee auf ein Raspberry-Pi zurückzugreifen - da man aber das WLAN nur mit vorheriger Konfiguration an neuen Einsatzorten verwenden kann, und man auf einen einzigen echten PWM-Ausgang beschränkt ist, sowie die Rechenleistung dafür eigentlich "zu hoch" war, entschied ich mich für die Low-Cost-Variante des Arduinos - zusätzlich wurden hier etwa 450 mA Leistung eingespart.

Netzwerk

Der Netzwerk-Chip auf einem externen Modul besteht aus einem über SPI angebundenen ENC28J60. Da dieser Chip mit 3.3 Volt versorgt werden muss, und eine Spitzenstromstärke von bis zu 200 mA benötigt (der 3.3V-Pin des Arduinos liefert maximal 50 mA) wurde ein Step-Down-Modul verwendet, welches die benötigte Spannung aus der 12 Volt Eingangsspannung erzeugt.

Pin-Belegung

Die ersten beiden Pins sind für die serielle Datenübertragung reserviert, die Motorsteuerung läuft über Pin 4 bis 7 und das SPI-Interface befindet sich auf den Pins 10 bis 13. Auf den PWM-Pins 3 und 9 waren in der ursprünglichen Version die roten und auf den Pins 2 und 8 die grünen LEDs.

Nr. 0 1 2 3 4 5 6 7 8 9 10 11 12 13
Pin TX RX / / M1 E1 E2 M2 / / CS SI SO SCK
Desc. Serial Wassersäulen Ethernet

Software

Bei der Entwicklung der Software habe ich auf mehrere Programmiersprachen zurückgegriffen. Im Wesentlichen ist das Projekt jedoch in C und die Steuersoftware für den PC in AutoIt geschrieben.

Testprogramme

Im Zuge meiner Tests mit der uPnP-Schnittstelle der Fritz!Box habe ich das erste Programm für SOAP-Requests in PHP geschrieben. Da hier eine interne Bibliothek zur Verfügung steht, kann man die benötigten Werte bereits mit einigen wenigen Zeilen Code auslesen. Um genau herauszufinden, wie die gesendeten TCP-Pakete aussehen, habe ich anschließend mit WireShark den Netzwerkverkehr analysiert.

<?php

$controlURL = "/igdupnp/control/WANCommonIFC1";
$serviceType = "urn:schemas-upnp-org:service:WANCommonInterfaceConfig:1";

$client = new SoapClient(null, array(
	"location" => "http://fritz.box:49000".$controlURL,
	"uri" => $serviceType,
	"noroot" => true
));

while (true) {

	$data = $client->GetAddonInfos();
	
	echo $data["NewByteSendRate"];
	echo "\t";
	echo $data["NewByteReceiveRate"];
	echo "\n";
	
	sleep(1);
}

?>
Anhang: /src/SOAP.php


Unter AutoIt habe ich dann die Daten als TCP-Pakete "wiederholt", um eine Vorlage für den C-Code des Arduinos zu haben. Zudem habe ich noch eine GUI programmiert, um die empfangenen Daten zu visualisieren.

#include <ProgressConstants.au3>
#include <GUIConstants.au3>

#include <Array.au3>
#include <String.au3>

TCPStartup()

Local $tcp = TCPConnect("192.168.178.1", 49000)

$gui = GUICreate("FritzBox", 800, 600)

Dim $progress[1]

For $i = 0 To 25
	_ArrayAdd($progress, GUICtrlCreateProgress(10 + 30 * $i, 10, 20, 580, $PBS_VERTICAL))
Next

GUISetState()

Local $r = "", $counter = 0, $highest = 0

While True
	If (GUIGetMsg() = $GUI_EVENT_CLOSE) Then
		TCPCloseSocket($tcp)
		Exit
	EndIf
	If ($counter = 0 Or StringRightEquals($r, "</s:Envelope>")) Then
		TCPSend($tcp, "POST /igdupnp/control/WANCommonIFC1 HTTP/1.1" & @CRLF)
		TCPSend($tcp, "Host: fritz.box:49000" & @CRLF)
		TCPSend($tcp, "Connection: keep-alive" & @CRLF)
		TCPSend($tcp, "User-agent: AutoIt/3.3.12" & @CRLF)
		TCPSend($tcp, "Content-Type: text/xml; charset=utf-8" & @CRLF)
		TCPSend($tcp, 'SOAPAction: "urn:schemas-upnp-org:service:WANCommonInterfaceConfig:1#GetAddonInfos"' & @CRLF)
		TCPSend($tcp, "Content-Length: 426" & @CRLF & @CRLF)
		TCPSend($tcp, '<?xml version="1.0" encoding="UTF-8"?><SOAP-ENV:Envelope xmlns:SOAP-ENV="http://schemas.xmlsoap.org/soap/envelope/" xmlns:ns1="urn:schemas-upnp-org:service:WANCommonInterfaceConfig:1" xmlns:xsd="http://www.w3.org/2001/XMLSchema" xmlns:SOAP-ENC="http://schemas.xmlsoap.org/soap/encoding/" SOAP-ENV:encodingStyle="http://schemas.xmlsoap.org/soap/encoding/"><SOAP-ENV:Body><ns1:GetAddonInfos/></SOAP-ENV:Body></SOAP-ENV:Envelope>' & @CRLF & @CRLF)
		Local $dat = _StringBetween($r, "<NewByteReceiveRate>", "</NewByteReceiveRate>")
		If (IsArray($dat)) Then
			If (Number($dat[0]) > $highest) Then
				$highest = $dat[0]
			EndIf
			WinSetTitle($gui, "", $dat[0] & " / " & $highest)
			For $i = 1 To UBound($progress) - 2
				GUICtrlSetData($progress[$i], GUICtrlRead($progress[$i + 1]))
			Next
			GUICtrlSetData($progress[UBound($progress) - 1], $dat[0] / $highest * 100)
		EndIf
		FileWrite("debug.log", $r)
		$r = ""
		$counter += 1
		Sleep(50)
	EndIf
	$r &= TCPRecv($tcp, 1024)
WEnd

Func StringRightEquals($str, $right)
	Return (StringRight($str, StringLen($right)) = $right)
EndFunc
Anhang: /src/SOAPVisu/SOAPVisu.au3

Mikroprozessor

Das Programm auf dem Arduino ist sicherlich der komplizierteste Teil des Projekts gewesen - denn das verwendete Ethernet-Modul bzw. die UIPEthernet-Library benötigt bereits ca. 60% des auf 2 KB begrenzten dynamischen Speichers der Platine. Da Arduinos bereits bei einer Speicherbelastung von knapp über 70% nicht mehr zuverlässig funktionieren, musste ich größtenteils (gegen Ende vollständig) auf Debug-Ausgaben und Komfort-Funktionen wie String-Objekte verzichten.

Die für den SOAP-Request notwendigen Daten werden im sog. PROGMEM gespeichert, d.h. im eigentlichen Programmspeicher des Arduinos. Zum Senden über TCP werden diese Daten dann in einen Buffer kopiert und an das Ethernet-Modul geschickt.

#include <UIPEthernet.h>
#include <EEPROM.h>

EthernetClient client;
EthernetServer server = EthernetServer(1337);

const byte mac[6] = { 0x46, 0x72, 0x69, 0x74, 0x7A, 0x21 }; // Fritz! (in hexadecimal)

const PROGMEM char dataPost[]        = "POST /igdupnp/control/WANCommonIFC1 HTTP/1.1\n";
const PROGMEM char dataHost[]        = "Host: fritz.box:49000\n";
const PROGMEM char dataConnection[]  = "Connection: keep-alive\n";
const PROGMEM char dataUserAgent[]   = "User-agent: FritzbackDevice/1.0\n";
const PROGMEM char dataContentType[] = "Content-Type: text/xml; charset=utf-8\n";
const PROGMEM char dataAction[]      = "SOAPAction: \"urn:schemas-upnp-org:service:WANCommonInterfaceConfig:1#GetAddonInfos\"\n";
const PROGMEM char dataLength[]      = "Content-Length: 426\n";

const PROGMEM char dataPacketA[]     = "<?xml version=\"1.0\" encoding=\"UTF-8\"?><SOAP-ENV:Envelope ";
const PROGMEM char dataPacketB[]     = "xmlns:SOAP-ENV=\"http://schemas.xmlsoap.org/soap/envelope/\" ";
const PROGMEM char dataPacketC[]     = "xmlns:ns1=\"urn:schemas-upnp-org:service:WANCommonInterfaceConfig:1\" ";
const PROGMEM char dataPacketD[]     = "xmlns:xsd=\"http://www.w3.org/2001/XMLSchema\" ";
const PROGMEM char dataPacketE[]     = "xmlns:SOAP-ENC=\"http://schemas.xmlsoap.org/soap/encoding/\" ";
const PROGMEM char dataPacketF[]     = "SOAP-ENV:encodingStyle=\"http://schemas.xmlsoap.org/soap/encoding/\">";
const PROGMEM char dataPacketG[]     = "<SOAP-ENV:Body><ns1:GetAddonInfos/></SOAP-ENV:Body></SOAP-ENV:Envelope>\n";

char sendBuffer[100];

long curRates[2] = { 0L, 0L };
long maxRates[2] = { 0L, 0L };

const int PIN_PUMP[2] = { 6, 5 };

const int PIN_LED_GREEN[2] = { 2, 8 };
const int PIN_LED_RED[2] = { 9, 3 };

const int ROM_PUMPS = 0;
const int ROM_LIGHT = 1;
const int ROM_MAXRATES[2] = { 2, 6 };

boolean redState[2] = { false, false };
int redLevel[2] = { 0, 0 };
unsigned long redTimer[2] = { 0, 0 };

byte cfgPumps = 0;
byte cfgLight = 0;

void setup() {
  // Pin Modes
  pinMode(PIN_PUMP[0], OUTPUT);
  pinMode(PIN_PUMP[1], OUTPUT);
  pinMode(PIN_LED_RED[0], OUTPUT);
  pinMode(PIN_LED_RED[1], OUTPUT);
  pinMode(PIN_LED_GREEN[0], OUTPUT);
  pinMode(PIN_LED_GREEN[1], OUTPUT);
  //
  digitalWrite(PIN_LED_RED[0], HIGH);
  digitalWrite(PIN_LED_RED[1], HIGH);
  //
  // Ethernet
  Ethernet.begin(mac);
  server.begin();
  //
  digitalWrite(PIN_LED_RED[0], LOW);
  digitalWrite(PIN_LED_RED[1], LOW);
  // EEPROM
  maxRates[0] = EEPROM_ReadLong(ROM_MAXRATES[0]);
  maxRates[1] = EEPROM_ReadLong(ROM_MAXRATES[1]);
  cfgPumps = EEPROM.read(ROM_PUMPS);
  cfgLight = EEPROM.read(ROM_LIGHT);
  //
  if (cfgLight == 1) {
    digitalWrite(PIN_LED_GREEN[0], HIGH);
    digitalWrite(PIN_LED_GREEN[1], HIGH);
  }
}

void loop() {
  if (client.connect(Ethernet.gatewayIP(), 49000)) {
    while (client.connected()) {
      client.print(getString(dataPost));
      client.print(getString(dataHost));
      client.print(getString(dataConnection));
      client.print(getString(dataUserAgent));
      client.print(getString(dataContentType));
      client.print(getString(dataAction));
      client.print(getString(dataLength));
      client.print("\n");
      client.print(getString(dataPacketA));
      client.print(getString(dataPacketB));
      client.print(getString(dataPacketC));
      client.print(getString(dataPacketD));
      client.print(getString(dataPacketE));
      client.print(getString(dataPacketF));
      client.print(getString(dataPacketG));
      client.print("\n");
      //
      unsigned long timeout = millis() + 5000;
      while (client.available() == 0) {
        if (millis() > timeout) {
          break;
        }
        delayLoop(1);
      }
      char limiter[] = "<>";
      timeout = timeout = millis() + 5000;
      while (true) {
        char dataBuffer[255] = { 0 };
        client.readBytesUntil('\n', dataBuffer, 255);
        setRate("NewByteReceiveRate", 18, dataBuffer, 0);
        setRate("NewByteSendRate", 15, dataBuffer, 1);
        char *fin = strtok(dataBuffer, "<>");
        if ((strncmp(fin, "/s:Envelope", 11) == 0) || millis() > timeout) {
          break;
        }
        LightThread();
      }
      delayLoop(250);
    }
  }
  delayLoop(5000);
}

void LightThread() {
  if (cfgLight == 1) {
    LightThread_Set(0);
    LightThread_Set(1);
  }
}

void LightThread_Set(int mr) {
  if (redState[mr]) {
    if (millis() > redTimer[mr]) {
      redLevel[mr]++;
      if (redLevel[mr] < 256) {
        analogWrite(PIN_LED_RED[mr], redLevel[mr]);
      } else {
        analogWrite(PIN_LED_RED[mr], 512 - redLevel[mr]);
      }
      if (redLevel[mr] == 512) {
        redLevel[mr] = 0;
      }
      redTimer[mr] = millis();
    }
  }
}

void delayLoop(int wait) {
  unsigned long timer = millis() + wait;
  while (millis() < timer) {
    if (EthernetClient control = server.available()) {
      while (server.available() > 0) {
        int cmd = control.read();
        switch (cmd) {
          case 'R':
            maxRates[0] = 0L;
            maxRates[1] = 0L;
            EEPROM_WriteLong(ROM_MAXRATES[0], 0L);
            EEPROM_WriteLong(ROM_MAXRATES[1], 0L);
          break;
          case 'L':
            cfgLight = 1;
            EEPROM.update(ROM_LIGHT, 1);
            digitalWrite(PIN_LED_GREEN[0], HIGH);
            digitalWrite(PIN_LED_GREEN[1], HIGH);
          break;
          case 'l':
            cfgLight = 0;
            EEPROM.update(ROM_LIGHT, 0);
            digitalWrite(PIN_LED_GREEN[0], LOW);
            digitalWrite(PIN_LED_GREEN[1], LOW);
            analogWrite(PIN_LED_RED[0], LOW);
            analogWrite(PIN_LED_RED[1], LOW);
          break;
          case 'P':
            cfgPumps = 1;
            EEPROM.update(ROM_PUMPS, 1);
          break;
          case 'p':
            cfgPumps = 0;
            EEPROM.update(ROM_PUMPS, 0);
            analogWrite(PIN_PUMP[0], 0);
            analogWrite(PIN_PUMP[1], 0);
          break;
        }
        control.println(curRates[0]);
        control.println(curRates[1]);
        control.println(maxRates[0]);
        control.println(maxRates[1]);
        control.println(cfgPumps);
        control.println(cfgLight);
        control.println();
        control.stop();
      }
    }
    LightThread();
  }
}

void setRate(const char* value, int vsize, char* origBuffer, int mr) {
  char dataBuffer[255];
  strcpy(dataBuffer, origBuffer);
  char* tag = strtok(dataBuffer, "<>");
  if (strncmp(tag, value, vsize) == 0) {
    long data = atol(strtok(NULL, "<>"));
    curRates[mr] = data;
    if (data > maxRates[mr]) {
      EEPROM_WriteLong(ROM_MAXRATES[mr], data);
      maxRates[mr] = data;
    }
    int sig = map(data, 0, maxRates[mr], 0, 100);
    redState[mr] = (sig > 90);
    if (redState[mr]) {
      digitalWrite(PIN_LED_GREEN[mr], LOW);
    } else {
      digitalWrite(PIN_LED_GREEN[mr], HIGH);
      analogWrite(PIN_LED_RED[mr], 0);
    }
    if (cfgPumps == 1) {
      if (sig > 5) {
        analogWrite(PIN_PUMP[mr], map(data, 0, maxRates[mr], 100, 255));
      } else {
        analogWrite(PIN_PUMP[mr], 0);
      }
    }
  }
}

char* getString(const char* str) {
  strcpy_P(sendBuffer, (char*) str);
  return sendBuffer;
}

void EEPROM_WriteLong(int address, long value) {
  byte four = (value & 0xFF);
  byte three = ((value >> 8) & 0xFF);
  byte two = ((value >> 16) & 0xFF);
  byte one = ((value >> 24) & 0xFF);
  EEPROM.update(address, four);
  EEPROM.update(address + 1, three);
  EEPROM.update(address + 2, two);
  EEPROM.update(address + 3, one);
}

long EEPROM_ReadLong(int address) {
  long four = EEPROM.read(address);
  long three = EEPROM.read(address + 1);
  long two = EEPROM.read(address + 2);
  long one = EEPROM.read(address + 3);
  return ((four << 0) & 0xFF) + ((three << 8) & 0xFFFF) + ((two << 16) & 0xFFFFFF) + ((one << 24) & 0xFFFFFFFF);
}
Anhang: /src/ExtremeFeedback/ExtremeFeedback.ino


Nach dem Umbau auf die neue Version musste ich leider größere Teile des Programms (z.B. die Lichtsteuerung) entfernen. Die grundlegenden Funktionen sind jedoch gleich geblieben. Der Ablaufplan gestaltet sich folgendermaßen:



#include <UIPEthernet.h>
#include <EEPROM.h>

EthernetClient client;
EthernetServer server = EthernetServer(1337);

const byte mac[6] = { 0x46, 0x72, 0x69, 0x74, 0x7A, 0x21 }; // Fritz! (in hexadecimal)

const PROGMEM char dataPost[]        = "POST /igdupnp/control/WANCommonIFC1 HTTP/1.1\n";
const PROGMEM char dataHost[]        = "Host: fritz.box:49000\n";
const PROGMEM char dataConnection[]  = "Connection: keep-alive\n";
const PROGMEM char dataUserAgent[]   = "User-agent: FritzbackDevice/1.0\n";
const PROGMEM char dataContentType[] = "Content-Type: text/xml; charset=utf-8\n";
const PROGMEM char dataAction[]      = "SOAPAction: \"urn:schemas-upnp-org:service:WANCommonInterfaceConfig:1#GetAddonInfos\"\n";
const PROGMEM char dataLength[]      = "Content-Length: 426\n";

const PROGMEM char dataPacketA[]     = "<?xml version=\"1.0\" encoding=\"UTF-8\"?><SOAP-ENV:Envelope ";
const PROGMEM char dataPacketB[]     = "xmlns:SOAP-ENV=\"http://schemas.xmlsoap.org/soap/envelope/\" ";
const PROGMEM char dataPacketC[]     = "xmlns:ns1=\"urn:schemas-upnp-org:service:WANCommonInterfaceConfig:1\" ";
const PROGMEM char dataPacketD[]     = "xmlns:xsd=\"http://www.w3.org/2001/XMLSchema\" ";
const PROGMEM char dataPacketE[]     = "xmlns:SOAP-ENC=\"http://schemas.xmlsoap.org/soap/encoding/\" ";
const PROGMEM char dataPacketF[]     = "SOAP-ENV:encodingStyle=\"http://schemas.xmlsoap.org/soap/encoding/\">";
const PROGMEM char dataPacketG[]     = "<SOAP-ENV:Body><ns1:GetAddonInfos/></SOAP-ENV:Body></SOAP-ENV:Envelope>\n";

char sendBuffer[100];

long curRates[2] = { 0L, 0L };
long maxRates[2] = { 0L, 0L };

const int PIN_PUMP[2] = { 6, 5 };

const int ROM_PUMPS = 0;
const int ROM_MAXRATES[2] = { 2, 6 };

byte cfgPumps = 0;

void setup() {
  // Pin Modes
  pinMode(PIN_PUMP[0], OUTPUT);
  pinMode(PIN_PUMP[1], OUTPUT);
  // Ethernet
  Ethernet.begin(mac);
  server.begin();
  // EEPROM
  maxRates[0] = EEPROM_ReadLong(ROM_MAXRATES[0]);
  maxRates[1] = EEPROM_ReadLong(ROM_MAXRATES[1]);
  cfgPumps = EEPROM.read(ROM_PUMPS);
}

void loop() {
  if (client.connect(Ethernet.gatewayIP(), 49000)) {
    while (client.connected()) {
      client.print(getString(dataPost));
      client.print(getString(dataHost));
      client.print(getString(dataConnection));
      client.print(getString(dataUserAgent));
      client.print(getString(dataContentType));
      client.print(getString(dataAction));
      client.print(getString(dataLength));
      client.print("\n");
      client.print(getString(dataPacketA));
      client.print(getString(dataPacketB));
      client.print(getString(dataPacketC));
      client.print(getString(dataPacketD));
      client.print(getString(dataPacketE));
      client.print(getString(dataPacketF));
      client.print(getString(dataPacketG));
      client.print("\n");
      //
      unsigned long timeout = millis() + 5000;
      while (client.available() == 0) {
        if (millis() > timeout) {
          break;
        }
        delayLoop(1);
      }
      char limiter[] = "<>";
      timeout = timeout = millis() + 5000;
      while (true) {
        char dataBuffer[255] = { 0 };
        client.readBytesUntil('\n', dataBuffer, 255);
        setRate("NewByteReceiveRate", 18, dataBuffer, 0);
        setRate("NewByteSendRate", 15, dataBuffer, 1);
        char *fin = strtok(dataBuffer, "<>");
        if ((strncmp(fin, "/s:Envelope", 11) == 0) || millis() > timeout) {
          break;
        }
      }
      delayLoop(250);
    }
  }
  delayLoop(5000);
}

void delayLoop(int wait) {
  unsigned long timer = millis() + wait;
  while (millis() < timer) {
    if (EthernetClient control = server.available()) {
      while (server.available() > 0) {
        int cmd = control.read();
        switch (cmd) {
          case 'R':
            maxRates[0] = 0L;
            maxRates[1] = 0L;
            EEPROM_WriteLong(ROM_MAXRATES[0], 0L);
            EEPROM_WriteLong(ROM_MAXRATES[1], 0L);
          break;
          case 'P':
            cfgPumps = 1;
            EEPROM.update(ROM_PUMPS, 1);
          break;
          case 'p':
            cfgPumps = 0;
            EEPROM.update(ROM_PUMPS, 0);
            analogWrite(PIN_PUMP[0], 0);
            analogWrite(PIN_PUMP[1], 0);
          break;
        }
        control.println(curRates[0]);
        control.println(curRates[1]);
        control.println(maxRates[0]);
        control.println(maxRates[1]);
        control.println(cfgPumps);
        control.println();
        control.stop();
      }
    }
  }
}

void setRate(const char* value, int vsize, char* origBuffer, int mr) {
  char dataBuffer[255];
  strcpy(dataBuffer, origBuffer);
  char* tag = strtok(dataBuffer, "<>");
  if (strncmp(tag, value, vsize) == 0) {
    long data = atol(strtok(NULL, "<>"));
    curRates[mr] = data;
    if (data > maxRates[mr]) {
      EEPROM_WriteLong(ROM_MAXRATES[mr], data);
      maxRates[mr] = data;
    }
    if (cfgPumps == 1) {
      if (map(data, 0, maxRates[mr], 0, 100) > 3) {
        analogWrite(PIN_PUMP[mr], map(constrain(data, 0, maxRates[mr] * 0.7), 0, maxRates[mr] * 0.7, 100, 255));
      } else {
        analogWrite(PIN_PUMP[mr], 0);
      }
    }
  }
}

char* getString(const char* str) {
  strcpy_P(sendBuffer, (char*) str);
  return sendBuffer;
}

void EEPROM_WriteLong(int address, long value) {
  byte four = (value & 0xFF);
  byte three = ((value >> 8) & 0xFF);
  byte two = ((value >> 16) & 0xFF);
  byte one = ((value >> 24) & 0xFF);
  EEPROM.update(address, four);
  EEPROM.update(address + 1, three);
  EEPROM.update(address + 2, two);
  EEPROM.update(address + 3, one);
}

long EEPROM_ReadLong(int address) {
  long four = EEPROM.read(address);
  long three = EEPROM.read(address + 1);
  long two = EEPROM.read(address + 2);
  long one = EEPROM.read(address + 3);
  return ((four << 0) & 0xFF) + ((three << 8) & 0xFFFF) + ((two << 16) & 0xFFFFFF) + ((one << 24) & 0xFFFFFFFF);
}
Anhang: /src/ExtremeFeedback2/ExtremeFeedback2.ino


Steuerprogramm

Aus mehreren Gründen entschloss ich mich dazu, noch ein zusätzliches Steuerprogramm in AutoIt zu schreiben. Zum einen wollte ich auf Schalter und Taster am Gehäuse verzichten, für ein Display hätten zudem die Pins am Arduino-Uno nicht mehr ausgereicht. Außerdem ergab sich durch das Ethernet-Modul die Möglichkeit, neben dem Fritz!Box-Client auch noch einen kleinen Server zu integrieren, um Kommandos zu empfangen und Daten zurückzugeben.

#Region ;**** Directives created by AutoIt3Wrapper_GUI ****
#AutoIt3Wrapper_Icon=icon.ico
#AutoIt3Wrapper_Outfile=FritzBackControl.exe
#EndRegion ;**** Directives created by AutoIt3Wrapper_GUI ****
#include <GUIConstantsEx.au3>

TCPStartup()

Global Const $MSGBOX_ERROR = 5 + 16

Local $gui = GUICreate("FritzBackControl", 500, 300)

GUICtrlCreateGroup("Informationen", 10, 10, 255, 110)

GUICtrlCreateLabel("Status:", 20, 35)
Local $gui_status = GUICtrlCreateLabel("Verbindung wird hergestellt...", 100, 35, 150, 15)

GUICtrlCreateLabel("IP-Adresse:", 20, 65)
Local $gui_ip = GUICtrlCreateLabel("unbekannt", 100, 65, 150, 15)

GUICtrlCreateLabel("MAC-Adresse:", 20, 85)
Local $gui_mac = GUICtrlCreateLabel("unbekannt", 100, 85, 150, 15)

GUICtrlCreateGroup("Einstellungen", 275, 10, 235, 110)

Local $gui_pumps = GUICtrlCreateCheckbox("Pumpen aktivieren", 285, 35)
GUICtrlSetState(-1, $GUI_DISABLE)
Local $gui_reset = GUICtrlCreateButton("Maximalwerte zurücksetzen", 285, 80, 150, 25)
GUICtrlSetState(-1, $GUI_DISABLE)

GUICtrlCreateGroup("Daten", 10, 130, 480, 140)

GUICtrlCreateLabel("Download-Rate:", 20, 150)
Local $gui_dlrate = GUICtrlCreateLabel("-", 110, 150, 80, 15)
GUICtrlCreateLabel("Maximum:", 340, 150)
Local $gui_dlratemax = GUICtrlCreateLabel("-", 400, 150, 80, 15)

Local $gui_dlprogress = GUICtrlCreateProgress(20, 170, 460, 30)

GUICtrlCreateLabel("Upload-Rate:", 20, 210)
Local $gui_ulrate = GUICtrlCreateLabel("-", 110, 210, 80, 15)
GUICtrlCreateLabel("Maximum:", 340, 210)
Local $gui_ulratemax = GUICtrlCreateLabel("-", 400, 210, 80, 15)

Local $gui_ulprogress = GUICtrlCreateProgress(20, 230, 460, 30)

GUICtrlCreateLabel("FritzBack - programmiert von Daniel Jäger für den AVM Wettbewerb „pimp your FRITZ!“", 80, 280)
GUICtrlSetFont(-1, 8)

GUISetState()

Global $firstconnect = True
Global $ip = GetIP()

Global $timer = TimerInit()
While True
	Local $msg = GUIGetMsg()
	Switch ($msg)
		Case $gui_pumps
			GetData(GUICtrlRead($gui_pumps) = $GUI_CHECKED ? "P" : "p")
		Case $gui_reset
			GetData("R")
		Case $GUI_EVENT_CLOSE
			Exit
	EndSwitch
	If (TimerDiff($timer) > 3000) Then
		GetData()
	EndIf
WEnd

Func GetIP()
	$ip = TCPNameToIP("FritzBack")
	If ($ip = "") Then
		GUICtrlSetData($gui_status, "Host nicht gefunden.")
		GUICtrlSetColor($gui_status, 0xFF0000)
		Local $msg = MsgBox($MSGBOX_ERROR, "Host nicht gefunden", "Der Hostname FritzBack konnte nicht zu einer IP zugeordnet worden. Bitte überprüfe, ob das Gerät mit dem Netzwerk verbunden ist.")
		If ($msg = 4) Then
			Return GetIP()
		Else
			Exit
		EndIf
	Else
		GUICtrlSetData($gui_ip, $ip)
		GUICtrlSetData($gui_mac, GetMacAddress($ip))
		Return $ip
	EndIf
EndFunc

Func GetData($req = "a")
	Local $tcp = TCPConnect($ip, 1337)
	If ($tcp > -1) Then
		If ($firstconnect) Then
			GUICtrlSetData($gui_status, "Verbindung hergestellt.")
			GUICtrlSetColor($gui_status, 0x11AF1C)
			GUICtrlSetState($gui_pumps, $GUI_ENABLE)
			GUICtrlSetState($gui_reset, $GUI_ENABLE)
			$firstconnect = False
		EndIf
		TCPSend($tcp, $req)
		Local $r = ""
		While True
			$r &= TCPRecv($tcp, 1024)
			If (StringRight($r, 4) = @CRLF & @CRLF) Then
				ExitLoop
			EndIf
		WEnd
		Local $data = StringSplit($r, @CRLF, 1 + 2)
		GUICtrlSetData($gui_dlrate, Round($data[0] / 1024 / 1024, 2) & " MB/s")
		GUICtrlSetData($gui_ulrate, Round($data[1] / 1024 / 1024, 2) & " MB/s")
		GUICtrlSetData($gui_dlratemax, Round($data[2] / 1024 / 1024, 1) & " MB/s")
		GUICtrlSetData($gui_ulratemax, Round($data[3] / 1024 / 1024, 1) & " MB/s")
		GUICtrlSetData($gui_dlprogress, $data[0] / $data[2] * 100)
		GUICtrlSetData($gui_ulprogress, $data[1] / $data[3] * 100)
		GUICtrlSetState($gui_pumps, $data[4] = 0 ? $GUI_UNCHECKED : $GUI_CHECKED)
		TCPCloseSocket($tcp)
		$timer = TimerInit()
	Else
		GUICtrlSetData($gui_status, "Verbindung fehlgeschlagen.")
		GUICtrlSetColor($gui_status, 0xFF0000)
		MsgBox($MSGBOX_ERROR, "Verbindung fehlgeschlagen", "Es konnte keine Verbindung zu FritzBack hergestellt werden. Bitte überprüfe, ob das Gerät mit dem Netzwerk verbunden ist.")
		Exit
	EndIf
EndFunc

Func GetMacAddress($ip)
	Local $struct = DllStructCreate("byte[6]")
	Local $size = DllStructCreate("int")
	DllStructSetData($size, 1, 6)
	Local $addr = DllCall("ws2_32.dll", "int", "inet_addr", "str", $ip)
	Local $r = DllCall("iphlpapi.dll", "int", "SendARP", "int", $addr[0], "int", 0, "ptr", DllStructGetPtr($struct), "ptr", DllStructGetPtr($size))
	Local $result
	For $i = 0 To 5
		$result &= Hex(DllStructGetData($struct, 1, $i + 1), 2)
		If ($i < 5) Then
			$result &= ":"
		EndIf
	Next
	Return $result
EndFunc
Anhang: /src/FritzBackControl.au3


Das Steuerprogramm habe ich der Einfachheit halber in der Skriptsprache AutoIt geschrieben. Darüber lassen sich via TCP Befehle an die Arduino-Plattform schicken, z.B. um einzelne Funktionen zu deaktivieren oder den Speicher für die Maximalwerte zu resetten. Außerdem habe ich noch eine Anzeige der aktuellen Daten eingebaut.

Verbesserungen

Der Perfektionsist Perfektionist in uns Technik-Verliebten ist am Ende eines Projektes natürlich nie zu 100% mit seinem Werk zufrieden - so haben sich auf bei diesem eine Reihe von Verbesserungsideen und Upgrade-Möglichkeiten angestaut:

Schlusswort

Sollte ich tatsächlich den ersten Platz beim Wettbewerb ergattern, würde mir der Ultimaker 2 sehr gelegen kommen. Nachdem ich 3 Jahre als Web-Entwickler selbstständig war, möchte ich gerne mit einer neuen Geschäftsidee im 3D-Druck durchstarten. Zu diesem Zweck suche ich momentan noch Investoren - und selbstverständlich einen ordentlichen 3D-Drucker.

Impressum

Daniel Jäger
Noldestraße 32
53340 Meckenheim

E-Mail: info@danieljaeger.de
Internet: www.danieljaeger.de


Besonderer Dank geht an: Meine Freundin, Annka G. (fürs Korrekturlesen und Wutausbrüche aushalten), meine Mutter (ebenfalls fürs Korrekturlesen und ständig zum Conrad fahren um noch eine Schraube zu holen), meinen Freund Marcel T. für seinen hervorragenden Umgang mit Silikon und Schläuchen, aus denen Wasser spritzt.