Steuerung einer Funksteckdose mit Arduino (Grundlagen siehe RF Link). Eingebauter WebServer mit Oberfläche für Mobilgeräte (jQuery mobil).

 

 

Hardware

Arduino 

Arduino Ehternet Shield

Arduino Ethernet

Arduino Proto Shield

RF Link 434 MHz

Sender

Zusammengebaut

 

Screenshots

remote.html

timer.html

 

Sourcecode

Compiled with Arduino 0022

#include "SPI.h"
#include "Ethernet.h"
#include "WebServer.h"
#include <Udp.h>
#include <stdio.h>
#include <Time.h>
#include <MsTimer2.h>

#define AON         "000111011111011010100011"
#define AOFF        "000100001000110101110011"
#define BON         "000110001011110011001010"
#define BOFF        "000110010101011110111010"
#define CON         "000110010101011110111010"
#define COFF        "000100011100001011100001"
#define DON         "000110111010101100101000"
#define DOFF        "000101100010000000001000"
#define MASTERON    "000110101101100111011101"
#define MASTEROFF   "000100011100001011101101"

#define TIMER2 60000
#define AlarmHMS(_hr_, _min_, _sec_) (_hr_ * SECS_PER_HOUR + _min_ * SECS_PER_MIN + _sec_)

static uint8_t mac[6] = { 0x02, 0xAA, 0xBB, 0xCC, 0x00, 0x22 };
static uint8_t ip[] = { 192, 168, 178, 213 };
unsigned int localPort = 8888;      // local port to listen for UDP packets
byte timeServer[] = { 192, 168, 178, 1};
const int NTP_PACKET_SIZE= 48; // NTP time stamp is in the first 48 bytes of the message
byte packetBuffer[ NTP_PACKET_SIZE]; //buffer to hold incoming and outgoing packets 
short rc_pin=8;  // der Pin auf dem der Datenpin des Senders angeschlossen ist.
short vc_pin=10; // der Pin auf dem VCC des Senders angeschlossen ist.
unsigned long epoch = 0;
boolean automatic = true;
int masterOnHour = 18;
int masterOnMinute = 0;
int masterOffHour = 22;
int masterOffMinute = 0;
int NTPCounter = 0;
boolean NTPUpdate = false;

/* all URLs on this server will start with /buzz because of how we
 * define the PREFIX value.  We also will listen on port 80, the
 * standard HTTP service port */
#define PREFIX ""
WebServer webserver(PREFIX, 80);

/* This command is set as the default command for the server.  It
 * handles both GET and POST requests.  For a GET, it returns a simple
 * page with some buttons.  For a POST, it saves the value posted to
 * the buzzDelay variable, affecting the output of the speaker */
void remoteCmd(WebServer &server, WebServer::ConnectionType type, char *, bool)
{
  if (type == WebServer::POST)
  {
    bool repeat;
    char name[16], value[16];
    do
    {
      /* readPOSTparam returns false when there are no more parameters
       * to read from the input.  We pass in buffers for it to store
       * the name and value strings along with the length of those
       * buffers. */
      repeat = server.readPOSTparam(name, 16, value, 16);

      /* this is a standard string comparison function.  It returns 0
       * when there's an exact match.  We're looking for a parameter
       * named "buzz" here. */
      if (strcmp(name, "remote") == 0)
      {
	/* use the STRing TO Unsigned Long function to turn the string
	 * version of the delay number into our integer buzzDelay
	 * variable */
         int val = strtoul(value, NULL, 10);
         char code[24];
         switch(val) {
           case 11: // A an
             strcpy(code, AON);
           break;
           case 10: // A aus
             strcpy(code, AOFF);
           break;
           case 21: // B an
             strcpy(code, BON);
           break;
           case 20: // B aus
             strcpy(code, BOFF);
           break;
           case 31: // C an
             strcpy(code, CON);
           break;
           case 30: // C aus
             strcpy(code, COFF);
           break;
           case 41: // D an
             strcpy(code, DON);
           break;
           case 40: // D aus
             strcpy(code, DOFF);
           break;
           case 51: // Master an
             strcpy(code, MASTERON);
           break;
           case 50: // Master aus
             strcpy(code, MASTEROFF);
           break;
         }
         sendCode(code);
      }
    } while (repeat);
    
    // after procesing the POST data, tell the web browser to reload
    // the page using a GET method. 
    server.httpSeeOther(PREFIX "/remote.html");
    return;
  }

  /* for a GET or HEAD, send the standard "it's all OK headers" */
  server.httpSuccess();

  /* we don't output the body for a HEAD request */
  if (type == WebServer::GET)
  {
    /* store the HTML in program memory using the P macro */
    P(remote) = 
"<!DOCTYPE html>"
"<html>"
"  <head>"
"  <meta name='viewport' content='width=device-width, initial-scale=1'>"
"  <title>Steuerung Powerfix Steckdosen</title>"
"  <link rel='stylesheet' href='http://code.jquery.com/mobile/1.0/jquery.mobile-1.0.min.css' />"
"  <script src='http://code.jquery.com/jquery-1.6.4.min.js'></script>"
"  <script src='http://code.jquery.com/mobile/1.0/jquery.mobile-1.0.min.js'></script>"
"	<script>"
"	$(document).bind('mobileinit', function(){"
"	  $.mobile.touchOverflowEnabled = true;"
"	});"
"	</script>"
"</head>"
"<body>"
"<div data-role='page'>"
"  <div data-role='header' data-position='fixed'>"
"    <h1>Remote</h1>"
"    <a href='options.html' data-icon='star' class='ui-btn-right' data-transition='slidedown'>Optionen</a>  "
"  </div>"
"  <div data-role='content' align='center'>"
"		<form action='/' method='post' data-ajax='false'>"
"					<div data-role='fieldcontain'>"
"			    	<div data-role='controlgroup' data-type='horizontal'>"
"			      	<legend>A</legend>"
"					    <button name='remote' value='11' data-icon='check'>An"
"				    	<button name='remote' value='10' data-icon='delete'>Aus"
"				    </div>"
"			    </div>"
"			    <div data-role='fieldcontain'>			      "
"			    	<div data-role='controlgroup' data-type='horizontal'>"
"						<legend>B</legend>"
"							<button name='remote' value='21' data-icon='check'>An"
"							<button name='remote' value='20' data-icon='delete'>Aus"
"						</div>"
"			    </div>"
"					<div data-role='fieldcontain'>"
"			    	<div data-role='controlgroup' data-type='horizontal'>"
"							<legend>C</legend>"
"							<button name='remote' value='31' data-icon='check'>An"
"							<button name='remote' value='30' data-icon='delete'>Aus"
"						</div>"
"			    </div>"
"					<div data-role='fieldcontain'>"
"			    	<div data-role='controlgroup' data-type='horizontal'>"
"							<legend>D</legend>"
"							<button name='remote' value='41' data-icon='check'>An"
"							<button name='remote' value='40' data-icon='delete'>Aus"
"						</div>"
"			    </div>"
"					<div data-role='fieldcontain'>"
"			    	<div data-role='controlgroup' data-type='horizontal'>"
"							<legend>Master</legend>"
"							<button name='remote' value='51' data-icon='check'>An"
"							<button name='remote' value='50' data-icon='delete'>Aus"
"						</div>"
"			    </div>"
"		</form>		"
"  </div><!-- /content -->"
"<div data-role='footer' data-position='fixed'>		"
"	<div data-role='navbar' data-iconpos='top'>"
"		<ul>"
"			<li><a href='#' data-icon='grid' class='ui-btn-active'>Remote</a></li>"
"			<li><a href='timer.html' data-icon='gear' data-transition='flip'>Timer</a></li>"
"		</ul>"
"	</div><!-- /navbar -->"
"</div><!-- /footer -->"
"</div><!-- /page -->"
"</body>"
"</html>";
     server.printP(remote);
  }
}

void timerCmd(WebServer &server, WebServer::ConnectionType type, char *, bool)
{
  if (type == WebServer::POST)
  {
    bool repeat;
    char name[16], value[16];
    do
    {
      /* readPOSTparam returns false when there are no more parameters
       * to read from the input.  We pass in buffers for it to store
       * the name and value strings along with the length of those
       * buffers. */
      repeat = server.readPOSTparam(name, 16, value, 16);

      /* this is a standard string comparison function.  It returns 0
       * when there's an exact match.  We're looking for a parameter
       * named "buzz" here. */
      if (strcmp(name, "automatic") == 0)
      {
        /* use the STRing TO Unsigned Long function to turn the string
	 * version of the delay number into our integer buzzDelay
	 * variable */
         int val = strtoul(value, NULL, 10);
         if (val == 0)
           automatic = false;
         else
           automatic = true;
      }
      if (strcmp(name, "masterAn") == 0)
      {
        String theTime(value);
        String theHour = theTime.substring(0, theTime.indexOf(':'));
        String theMinute = theTime.substring(theTime.indexOf(':')+1);
        masterOnHour = stringToInt(theHour);
        masterOnMinute = stringToInt(theMinute);
//        Serial.print("theTime: ");
//        Serial.println(theTime);
//        Serial.print("masterOnHour: ");
//        Serial.println(masterOnHour);
//        Serial.print("masterOnMinute: ");
//        Serial.println(masterOnMinute);
       }
      if (strcmp(name, "masterAus") == 0)
      {
        String theTime(value);
        String theHour = theTime.substring(0, theTime.indexOf(':'));
        String theMinute = theTime.substring(theTime.indexOf(':')+1);
        masterOffHour = stringToInt(theHour);
        masterOffMinute = stringToInt(theMinute);
      }
    } while (repeat);
    
    checkTimer(); // nach dem Speichern soll das Ergebnis (Zeitveränderung) gleich sichtbar sein
    
    // after procesing the POST data, tell the web browser to reload
    // the page using a GET method. 
    server.httpSeeOther(PREFIX "/timer.html"); // muss Seite neu geladen werden?!?!
    return;
  }

  /* for a GET or HEAD, send the standard "it's all OK headers" */
  server.httpSuccess();

  /* we don't output the body for a HEAD request */
  if (type == WebServer::GET)
  {
    /* store the HTML in program memory using the P macro */
    P(timer1) = 
"<!DOCTYPE html>"
"<html>"
"  <head>"
"  <meta name='viewport' content='width=device-width, initial-scale=1'>"
"  <title>Steuerung Powerfix Steckdosen</title>"
"  <link rel='stylesheet' href='http://code.jquery.com/mobile/1.0/jquery.mobile-1.0.min.css' />"
"  <script src='http://code.jquery.com/jquery-1.6.4.min.js'></script>"
"  <script src='http://code.jquery.com/mobile/1.0/jquery.mobile-1.0.min.js'></script>"
"	<script>"
"	$(document).bind('mobileinit', function(){"
"	  $.mobile.touchOverflowEnabled = true;"
"	});"
"</script>"
"</head>"
"<body>"
"<div data-role='page'>"
"  <div data-role='header' data-position='fixed'>"
"    <h1>Timer</h1>"
"    <a href='options.html' data-icon='star' class='ui-btn-right' data-transition='slidedown'>Optionen</a>  "
"  </div>"
"  <div data-role='content'>"
"  <script>"
"	function setTheTime() {"
"	  now=new Date();"
"	  hour=now.getHours();"
"	  min=now.getMinutes();"
"	  sec=now.getSeconds();"
"	  if (min<=9) { min='0'+min; }"
"	  if (sec<=9) { sec='0'+sec; }"
"	  if (hour<=9) { hour='0'+hour; }"
"	  document.timerForm.theTime.value = hour + ':' + min + ':' + sec;"
"         setTimeout(setTheTime, 1000);"
"	}"
"      $(document).ready(function() { setTheTime(); });"
"  </script>"
"		<form action='timer.html' method='post' data-ajax='false' name='timerForm'>"
"					<div data-role='fieldcontain'>"
"	         <label for='theTime'>Aktuelle Zeit:</label>"
"	         <input type='text' name='theTime' id='theTime' value='' disabled/>"
"					</div>"
"					<div data-role='fieldcontain'>"
"	         <label for='masterAn'>Master an:</label>"
"	         <input type='time' name='masterAn' id='masterAn' value='";
    P(timer2) = 
"' />"
"					</div>"
"					<div data-role='fieldcontain'>"
"	         <label for='masterAus'>Master aus:</label>"
"	         <input type='time' name='masterAus' id='masterAus' value='";
    P(timer3) = 
"' />"
"					</div>"
"					<div data-role='fieldcontain'>"
"					<label for='automatic'>Automatik:</label>"
"						<select name='automatic' id='automatic' data-role='slider'>"
"							<option value='1' ";
    P(timer4) = 
">An</option>"
"							<option value='0' ";
    P(timer5) = 
">Aus</option>"
"						</select> "
"					</div>"
"					<button type='submit' data-theme='b' name='save' value='submit-value'>Speichern</button>"
"		</form>		"
"  </div><!-- /content -->"
"<div data-role='footer' data-position='fixed'>		"
"	<div data-role='navbar' data-iconpos='top'>"
"		<ul>"
"			<li><a href='remote.html' data-icon='grid' data-transition='flip'>Remote</a></li>"
"			<li><a href='#' data-icon='gear' class='ui-btn-active'>Timer</a></li>"
"		</ul>"
"	</div><!-- /navbar -->"
"</div><!-- /footer -->"
"</div><!-- /page -->"
"</body>"
"</html>";
    server.printP(timer1);
    // Master an
    if (masterOnHour < 10)
      server.print(":0");
    server.print(masterOnHour);
    server.print(":");
    if (masterOnMinute < 10)
      server.print("0");
    server.print(masterOnMinute);
  
    server.printP(timer2);
    // Master aus
    if (masterOffHour < 10)
      server.print("0");
    server.print(masterOffHour);
    server.print(":");
    if (masterOffMinute < 10)
      server.print("0");
    server.print(masterOffMinute);

    server.printP(timer3);
    // Automatic an
    if (automatic == true)
      server.print("selected");

    server.printP(timer4);
    // Automatic aus
    if (automatic == false)
      server.print("selected");

    server.printP(timer5);
  }
}

void optionsCmd(WebServer &server, WebServer::ConnectionType type, char *, bool)
{
  if (type == WebServer::POST)
  {
    Serial.println("POST: NTP abrufen");
    epoch = getNTP();
    setTime(getNtpHour(), getNtpMinute(), getNtpSecond(), 1, 1, 11);

    server.httpSeeOther(PREFIX "/options.html");
    return;
  }

  /* for a GET or HEAD, send the standard "it's all OK headers" */
  server.httpSuccess();

  /* we don't output the body for a HEAD request */
  if (type == WebServer::GET)
  {
    Serial.println("GET: Seite laden");
    /* store the HTML in program memory using the P macro */
    P(options1) = 
"<!DOCTYPE html>"
"<html>"
"  <head>"
"  <meta name='viewport' content='width=device-width, initial-scale=1'>"
"  <title>Optionen</title>"
"  <link rel='stylesheet' href='http://code.jquery.com/mobile/1.0/jquery.mobile-1.0.min.css' />"
"  <script src='http://code.jquery.com/jquery-1.6.4.min.js'></script>"
"  <script src='http://code.jquery.com/mobile/1.0/jquery.mobile-1.0.min.js'></script>"
"</head>"
"<body>"
"<div data-role='page'>"
"  <div data-role='header' data-position='fixed'>"
"    <h1>Optionen</h1>"
"  </div>"
"  <div data-role='content'>"
"  <script>"
"	function setTheTime2() {"
"	  now=new Date();"
"	  hour=now.getHours();"
"	  min=now.getMinutes();"
"	  sec=now.getSeconds();"
"	  if (min<=9) { min='0'+min; }"
"	  if (sec<=9) { sec='0'+sec; }"
"	  if (hour<=9) { hour='0'+hour; }"
"	  document.optionsForm.theTime.value = hour + ':' + min + ':' + sec;"
"         setTimeout(setTheTime2, 1000);"
"	}"
"      $(document).ready(function() { setTheTime2(); });"
"  </script>"
"	<form action='/options.html' method='post' data-ajax='false' name='optionsForm'>"
"	  <div data-role='fieldcontain'>"
"           <label for='theTime'>Aktuelle Zeit:</label>"
"           <input type='text' name='theTime' id='theTime' value='' disabled/>"
"	  </div>"
"	  <div data-role='fieldcontain'>"
"           <label for='internTime'>Interne Zeit:</label>"
"           <input type='text' name='internTime' id='internTime' value='";
P(options2) = 
"' disabled/>"
"	  </div>"
"	  <button type='submit' data-theme='a' name='save' value='submit-value'>Zeit abrufen</button>"
"	</form>"
"  </div><!-- /content -->"
"<div data-role='footer' data-position='fixed'>		"
"	<div data-role='navbar' data-iconpos='top'>"
"		<ul>"
"			<li><a href='remote.html' data-icon='grid' data-transition='slideup'>Remote</a></li>"
"			<li><a href='timer.html' data-icon='gear' data-transition='slideup'>Timer</a></li>"
"		</ul>"
"	</div><!-- /navbar -->"
"</div><!-- /footer -->"
"</div><!-- /page -->"
"</body>"
"</html>";
    server.printP(options1);
    // Interne Zeit
    if (hour() < 10)
      server.print(":0");
    server.print(hour());
    server.print(":");
    if (minute() < 10)
      server.print("0");
    server.print(minute());
    server.print(":");
    if (second() < 10)
      server.print("0");
    server.print(second());
    
    server.printP(options2);
  }
}

void setup()
{
  Serial.begin(9600);
  
  pinMode(rc_pin, OUTPUT);  //definiere rc_Pin als Ausgang (schliesslich wollen wir senden)
  pinMode(vc_pin, OUTPUT);  //definiere vc_Pin als Ausgang
  
  // setup the Ehternet library to talk to the Wiznet board
  Ethernet.begin(mac, ip);

  /* register our default command (activated with the request of
   * http://x.x.x.x/ */
  webserver.setDefaultCommand(&remoteCmd);
  webserver.addCommand("remote.html", &remoteCmd);
  webserver.addCommand("timer.html", &timerCmd);
  webserver.addCommand("options.html", &optionsCmd);
  
  /* start the server to wait for connections */
  webserver.begin();
  
  Udp.begin(localPort);

  epoch = getNTP();
  setTime(getNtpHour(), getNtpMinute(), getNtpSecond(), 1, 1, 11);

  checkTimer(); // damit gleich zu Beginn was passiert

  MsTimer2::set(TIMER2, checkTimer);
  MsTimer2::start();
}

void loop()
{
  // process incoming connections one at a time forever
  webserver.processConnection();
//  Serial.print(hour());
//  Serial.print(":");
//  if (minute() < 10)
//    Serial.print("0");
//  Serial.print(minute());
//  Serial.print(":");
//  if (second() < 10)
//    Serial.print("0");
//  Serial.println(second());
//  delay(1000);

  if (NTPUpdate == true) {
    epoch = getNTP();
    setTime(getNtpHour(), getNtpMinute(), getNtpSecond(), 1, 1, 11);
    NTPUpdate == false;
  }
}

void checkTimer() {
  
  NTPCounter++;
  
  if (NTPCounter == 60) {
    NTPUpdate = true;
    NTPCounter = 0;
  }
  
  if (automatic == true) {
    
    time_t currentTime = AlarmHMS(hour(), minute(), second());
//    Serial.print(hour());
//    Serial.print(":");
//    if (minute() < 10)
//      Serial.print("0");
//    Serial.print(minute());
//    Serial.print(":");
//    if (second() < 10)
//      Serial.print("0");
//    Serial.println(second());

    time_t masterOnTime = AlarmHMS(masterOnHour, masterOnMinute, 0);
//    Serial.print(masterOnHour);
//    Serial.print(":");
//    if (masterOnMinute < 10)
//      Serial.print("0");
//    Serial.println(masterOnMinute);

    time_t masterOffTime = AlarmHMS(masterOffHour, masterOffMinute, 0);
//    Serial.print(masterOffHour);
//    Serial.print(":");
//    if (masterOffMinute < 10)
//      Serial.print("0");
//    Serial.println(masterOffMinute);
    
//    Serial.print(currentTime);
//    Serial.print(" >= ");
//    Serial.print( masterOnTime);
//    Serial.print(" && ");
//    Serial.print(currentTime);
//    Serial.print(" <= ");
//    Serial.println(masterOffTime);
    if ((currentTime >= masterOnTime) && (currentTime <= masterOffTime)) {
      sendCode(MASTERON);
//      Serial.println("-- Master an --");
    } 
    else {
      sendCode(MASTEROFF);
//      Serial.println("-- Master aus --");
    }
  }
}

unsigned long getNTP() {
  sendNTPpacket(timeServer); // send an NTP packet to a time server

  delay(1000);
  
  if ( Udp.available() ) {  
    Udp.readPacket(packetBuffer,NTP_PACKET_SIZE);  // read the packet into the buffer

    //the timestamp starts at byte 40 of the received packet and is four bytes,
    // or two words, long. First, esxtract the two words:

    unsigned long highWord = word(packetBuffer[40], packetBuffer[41]);
    unsigned long lowWord = word(packetBuffer[42], packetBuffer[43]);  
    // combine the four bytes (two words) into a long integer
    // this is NTP time (seconds since Jan 1 1900):
    unsigned long secsSince1900 = highWord << 16 | lowWord;  

    // Unix time starts on Jan 1 1970. In seconds, that's 2208988800:
    const unsigned long seventyYears = 2208988800UL;     
    // subtract seventy years + one hour (GMT+1)
    //unsigned long epoch = secsSince1900 - seventyYears;
    return secsSince1900 - seventyYears + 3600;
  }
}    

int getNtpHour() {
  return (epoch % 86400L) / 3600;
}

int getNtpMinute() {
  return (epoch % 3600) / 60;
}

int getNtpSecond() {
  return epoch % 60;
}

// send an NTP request to the time server at the given address 
unsigned long sendNTPpacket(byte *address)
{
  // set all bytes in the buffer to 0
  memset(packetBuffer, 0, NTP_PACKET_SIZE); 
  // Initialize values needed to form NTP request
  // (see URL above for details on the packets)
  packetBuffer[0] = 0b11100011;   // LI, Version, Mode
  packetBuffer[1] = 0;     // Stratum, or type of clock
  packetBuffer[2] = 6;     // Polling Interval
  packetBuffer[3] = 0xEC;  // Peer Clock Precision
  // 8 bytes of zero for Root Delay & Root Dispersion
  packetBuffer[12]  = 49; 
  packetBuffer[13]  = 0x4E;
  packetBuffer[14]  = 49;
  packetBuffer[15]  = 52;

  // all NTP fields have been given values, now
  // you can send a packet requesting a timestamp: 		   
  Udp.sendPacket( packetBuffer,NTP_PACKET_SIZE,  address, 123); //NTP requests are to port 123
}

boolean sendCode(char code[]){ //empfange den Code in Form eines Char[]
  digitalWrite(vc_pin, HIGH); // Spannung für Sender
  for(short z = 0; z<6; z++){ //wiederhole den Code 6x
    for(short i = 0; i<24; i++){ //ein Code besteht aus 24bits
      sendByte(code[i]);
    }
    sendByte('x'); //da der code immer mit x/sync abschliesst, brauchen wir den nicht im code und haengen es automatisch immer hinten ran.
  }
  digitalWrite(vc_pin, LOW);
  return true;
}

void sendByte(char i) { //Diese Funktion soll 0,1 oder x senden koennen. Wir speichern die gewuenschte Ausgabe in der Variabel i
  switch(i){ //nun gucken wir was i ist
  case '0':
    { //Der Code fuer '0'
      digitalWrite(rc_pin,HIGH);
      delayMicroseconds(1000);
      digitalWrite(rc_pin,LOW);
      delayMicroseconds(500);
      return;
    }
  case '1':
    { //Der Code fuer '1'
      digitalWrite(rc_pin,HIGH);
      delayMicroseconds(500);
      digitalWrite(rc_pin,LOW);
      delayMicroseconds(1000);
      return;
    }
  case 'x':
    { //Der Code fuer x(sync)
      digitalWrite(rc_pin,HIGH);
      delayMicroseconds(3000);
      digitalWrite(rc_pin,LOW);
      delayMicroseconds(7000);
    }

  }
}

int stringToInt(String str) {
  char this_char[str.length()+1];
  str.toCharArray(this_char, sizeof(this_char));
  return atoi(this_char);
}

 

Video