Bob Frankston: perché gli oggetti non sono connessi a internet?

L’illuminante post di ieri di Bob Frankston risuona molto forte con altre idee che mi girano in testa da tempo. La mia rete domestica a 1Gbps include i vicini di pianerottolo, ma potrebbe tranquillamente includere l’intero palazzo, che a sua volta potrebbe tranquillamente connettersi al palazzo di fianco, che a sua volta… quote of note:

I should be able to ask a simple question – why are we trying to make our ability to communicate a profit center instead of a community resource? We can act locally to own the infrastructure within our buildings and among neighbors. This is all doable using current protocols (despite their limitations) and, in fact, there are buildings where connectivity is funded as a common facility. In my talk I’m going to speak about how neighbors can work together to share connectivity in a building or among buildings.

Arduino, fammi il caffè! V0.1

Ho raggiunto i primi due obiettivi che mi ero posto con l’Arduino, ovvero

  1. prendere l’ora da internet tramite il Net Time Protocol
  2. accendere la macchina del caffè all’ora prefissata

Ma non è finita: adesso bisogna (bisogna!) che l’ora di accensione venga impostata via web, non inserita banalmente a mano nel codice. Per fare questo occorre che sia contemporaneamente attivo il cliente NTP e il server web, e grosse nubi si addensano sul mio orizzonte di entusiasta ignorante.

Inoltre non è bello che l’Arduino debba autisticamente chiedere l’ora a NTP ogni minuto: meglio sarebbe se chiedesse solo una volta al giorno per sincronizzare con un suo orologio interno.

Come dire che ho le sere impegnate fino a primavera. Il codice fino a questo punto, dopo il salto: come sempre, eventuali commenti, critiche e suggerimenti sono molto bene accetti.

/*
 Arduino, fammi il caffè! V.0.1
 
 created 4 Sep 2010 
 by Michael Margolis
 modified 17 Sep 2010
 by Tom Igoe
 modified 18 Feb 2012
 by Gaspar Torriero
 
 This code is in the public domain.

 */

#include <SPI.h>
#include <Ethernet.h>
#include <EthernetUdp.h>

int cHH; // current hour
int cMM; // current minute
int cSS; // current second
int outPin = 8; // connected to coffee machine via relè

// Enter a MAC address for your controller below
byte mac[] = {
  0xDE, 0xAD, 0xBE, 0xEF, 0xFE, 0xED };

unsigned int localPort = 8888;      // local port to listen for UDP packets

IPAddress timeServer(192, 43, 244, 18); // time.nist.gov NTP server

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 

// A UDP instance to let us send and receive packets over UDP
EthernetUDP Udp;

void setup()
{
  // Serial.begin(9600); // uncomment for testing
  digitalWrite(outPin, LOW);

  // start Ethernet and UDP
  if (Ethernet.begin(mac) == 0) {
    // Serial.println("Failed to configure Ethernet using DHCP"); // uncomment for testing
    // no point in carrying on, so do nothing forevermore:
    for(;;)
      ;
  }
  Udp.begin(localPort);
}

void loop()
{
  sendNTPpacket(timeServer); // send an NTP packet to a time server

    // wait to see if a reply is available
  delay(1000);
  if ( Udp.parsePacket() ) {
    // We've received a packet, read the data from it
    Udp.read(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:
    unsigned long epoch = secsSince1900 - seventyYears;  

    // print the hour, minute and second:
    cHH = (epoch  % 86400L) / 3600 + 1;
    cMM = (epoch  % 3600) / 60;
    cSS = epoch %60;

    /* uncomment for testing
    Serial.print("Local time is ");
    Serial.print(cHH);
    Serial.print(" hours, ");
    Serial.print(cMM);
    Serial.print(" minutes, and ");
    Serial.print(cSS);
    Serial.println(" seconds.");
    */

    // Turns on the coffee machine
    // at a fixed time.
    // Next goal: set time via web.
    if((cHH == 6) && (cMM > 44)) {
      digitalWrite(outPin, HIGH);
    }
  }
  // wait 60 seconds before asking for the time again
  // Next goal: let Arduino Keep the time
  // and synchronize daily.

  delay(60000); 

}

// send an NTP request to the time server at the given address 
unsigned long sendNTPpacket(IPAddress& 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.beginPacket(address, 123); //NTP requests are to port 123
  Udp.write(packetBuffer,NTP_PACKET_SIZE);
  Udp.endPacket();
}

Arduino server: sempre meglio

Dal mio nuovo server su Arduino (che lascerò attivo qualche giorno),  non solo puoi vedere lo stato delle mie luci e la temperatura di camera mia, ma puoi anche comandare tre pin che per ora sono collegati a tre led colorati, ma che domani potrei associare tramite relè a qualsiasi apparecchio elettrico.

Tutto questo grazie alla libreria WebServer.h, che aggiunge funzionalità molto interessati e facili da programmare anche per un ignorante come me (che si diverte come un bambino).

Come sempre, critiche consigli e suggerimenti sono bene accetti.

 

 

Come sempre, il codice è dopo il salto.

/* Web_Demo.pde -- sample code for Webduino server library */

/*
 * To use this demo,  enter one of the following USLs into your browser.
 * Replace "host" with the IP address assigned to the Arduino.
 *
 * http://host/
 * http://host/json
 *
 * This URL brings up a display of the values READ on digital pins 0-9
 * and analog pins 0-5.  This is done with a call to defaultCmd.
 * 
 * 
 * http://host/form
 *
 * This URL also brings up a display of the values READ on digital pins 0-9
 * and analog pins 0-5.  But it's done as a form,  by the "formCmd" function,
 * and the digital pins are shown as radio buttons you can change.
 * When you click the "Submit" button,  it does a POST that sets the
 * digital pins,  re-reads them,  and re-displays the form.
 * 
 */

#include "SPI.h"
#include "Ethernet.h"
#include "WebServer.h"

int lPin = 1; // Light sensor pin
int tPin = 3; // Temperature sensor pin
int luce = 0; // light in Lux
float temp = 0.0; // Temperature in C°
String lstatus;

// no-cost stream operator as described at 
// http://sundial.org/arduino/?page_id=119
template<class T>
inline Print &operator <<(Print &obj, T arg)
{ obj.print(arg); return obj; }

// CHANGE THIS TO YOUR OWN UNIQUE VALUE
static uint8_t mac[] = { 0xDE, 0xAD, 0xBE, 0xEF, 0xFE, 0xED };

// CHANGE THIS TO MATCH YOUR HOST NETWORK
static uint8_t ip[] = { 192, 168, XXX, XXX };

#define PREFIX ""

WebServer webserver(PREFIX, 80);

// commands are functions that get called by the webserver framework
// they can read any posted data from client, and they output to server

void jsonCmd(WebServer &server, WebServer::ConnectionType type, char *url_tail, bool tail_complete)
{
  if (type == WebServer::POST)
  {
    server.httpFail();
    return;
  }

  //server.httpSuccess(false, "application/json");
  server.httpSuccess("application/json");

  if (type == WebServer::HEAD)
    return;

  int i;
  server << "{ ";
  for (i = 0; i <= 9; ++i)
  {
    // ignore the pins we use to talk to the Ethernet chip
    int val = digitalRead(i);
    server << "\"d" << i << "\": " << val << ", ";
  }

  for (i = 0; i <= 5; ++i)
  {
    int val = analogRead(i);
    server << "\"a" << i << "\": " << val;
    if (i != 5)
      server << ", ";
  }

  server << " }";
}

void outputPins(WebServer &server, WebServer::ConnectionType type, bool addControls = false)
{
  P(htmlHead) =
    "<html>"
    "<head>"
    "<title>Arduinogaspar Web Server</title>"
    "<style type=\"text/css\">"
    "BODY { font-family: sans-serif }"
    "H1 { font-size: 14pt; text-decoration: underline }"
    "P { font-size: 10pt; }"
    "</style>"
    "</head>"
    "<body>"
    "<p>Digital Pin 5: green light<br>"
    "Digital Pin 4: yellow light<br>"
    "Digital Pin 3: red light<br>"
    "Values can be changed from the /form page<br>"
    "and can be exported from the /json page.<br>"
    "</p>";

  int i;
  server.httpSuccess();
  server.printP(htmlHead);

  if (addControls)
    server << "<form action='" PREFIX "/form' method='post'>";

  server << "<h1>Digital Pins</h1><p>";

  for (i = 0; i <= 9; ++i)
  {
    // ignore the pins we use to talk to the Ethernet chip
    int val = digitalRead(i);
    server << "Digital " << i << ": ";
    if (addControls)
    {
      char pinName[4];
      pinName[0] = 'd';
      itoa(i, pinName + 1, 10);
      server.radioButton(pinName, "1", "On", val);
      server << " ";
      server.radioButton(pinName, "0", "Off", !val);
    }
    else
      server << (val ? "HIGH" : "LOW");

    server << "<br/>";
  }

  server << "</p><h1>Analog Pins</h1><p>";
  for (i = 0; i <= 5; ++i)
  {
    int val = analogRead(i);
    server << "Analog " << i << ": " << val << "<br/>";
  }

  server << "</p>";
  server << "Temperature:" << temp << "°C<br/>";
  server << "Light is " << lstatus << "</p>";

  if (addControls)
    server << "<input type='submit' value='Submit'/></form>";

  server << "</body></html>";
}

void formCmd(WebServer &server, WebServer::ConnectionType type, char *url_tail, bool tail_complete)
{
  if (type == WebServer::POST)
  {
    bool repeat;
    char name[16], value[16];
    do
    {
      repeat = server.readPOSTparam(name, 16, value, 16);
      if (name[0] == 'd')
      {
        int pin = strtoul(name + 1, NULL, 10);
        int val = strtoul(value, NULL, 10);
        digitalWrite(pin, val);
      }
    } while (repeat);

    server.httpSeeOther(PREFIX "/form");
  }
  else
    outputPins(server, type, true);
}

void defaultCmd(WebServer &server, WebServer::ConnectionType type, char *url_tail, bool tail_complete)
{
  outputPins(server, type, false);
}

void setup()
{
  // set pins 0-8 for digital input
  for (int i = 0; i <= 9; ++i)
    pinMode(i, INPUT);
  pinMode(9, OUTPUT);

  Ethernet.begin(mac, ip);
  webserver.begin();

  webserver.setDefaultCommand(&defaultCmd);
  webserver.addCommand("json", &jsonCmd);
  webserver.addCommand("form", &formCmd);
}

void loop()
{
    temp = ( 5.0 * analogRead(tPin) * 100.0) / 1024.0; // Conversione voltaggio sensore in temperatura
    luce = (analogRead(lPin) * 10000.0) / 1024.0; //Conversione voltaggio sensore in Lux
    if (luce < 201) {
      lstatus = "OFF";
    }
    else {
      lstatus = "ON";
    }

  // process incoming connections one at a time forever
  webserver.processConnection();

  // if you wanted to do other work based on a connecton, it would go here
}

Arduino al pomodoro

Per trasformare il mio Arduino in un conta-pomodori ho seguito le facili istruzioni di Mike Stok, grazie al quale ho scoperto l’esistenza di  Fritzing, un tool di prototipazione dedicato al mondo Arduino che mi permette di disegnare queste cose:

Per adesso mi limito ad accendere il led rosso per 25 minuti alla pressione del bottone, con lampeggio nel minuto finale e passaggio al giallo per i 5 minuti successivi; quindi il led verde.

Prossimi passi: regolare automaticamente l’intansità del led in base alla luminosità dell’ambiente. Il codice l’ho messo dopo il salto.

// #define TEST_INTERVALS

#define HEARTBEAT_LED 13 

#define INITIAL_TCNT1  (65536 - 16000000 / 256)

#define PUSHBUTTON_INT 0

#define WORK_STATE     0
#define REST_STATE     1
#define FREE_STATE     2
#define N_STATES       3
#define INITIAL_STATE  FREE_STATE

int ledFor[]          = {
  3,         11,          5 };
#ifndef TEST_INTERVALS
int secsFor[]         = {
  25 * 60,     5 * 60,     0 * 60 };
#else
int secsFor[]         = {
  15,         10,          0 };
#endif
int nextStateTimer[]  = {
  REST_STATE, FREE_STATE, FREE_STATE };
int nextStateButton[] = {
  FREE_STATE, FREE_STATE, WORK_STATE };

volatile boolean heartbeatOn = false;

volatile int     brightnessFor[N_STATES];
volatile int     state;
volatile int     secsDuration;
volatile int     secsLeft;

ISR(TIMER1_OVF_vect) {
  noInterrupts();

  TCNT1 = INITIAL_TCNT1;

  heartbeatOn = !heartbeatOn;  
  digitalWrite(HEARTBEAT_LED, heartbeatOn ? HIGH : LOW);
  updateLeds();

  if (secsLeft > 0) {
    if (--secsLeft == 0) {
      enterState(nextStateTimer[state]);
    }
  }

  interrupts();
}

void buttonReleased() {
  noInterrupts();
  enterState(nextStateButton[state]);
  interrupts();
}

void updateLeds() {
  int i;

  // Clear all the "other" state leds
  for (i = 0; i < N_STATES; i++) {
    if (i != state) {
      brightnessFor[i] = 0;
    }
  }

  if (state == FREE_STATE) {
    brightnessFor[FREE_STATE] = 255;
  }
  else {
    int rampDownPeriod = 180;
    if (secsLeft > rampDownPeriod || heartbeatOn) {
      brightnessFor[state] = 255;
    }
    else {
      brightnessFor[state] = map(secsLeft, 0, rampDownPeriod, 64, 255);
    }
  }
}

void enterState (int newState) {
  state = newState;
  secsDuration = secsLeft = secsFor[newState];
  updateLeds();
}

void setup() {
  int i;
  pinMode(HEARTBEAT_LED, OUTPUT);
  enterState(INITIAL_STATE);
  attachInterrupt(PUSHBUTTON_INT, buttonReleased, RISING);
  TIMSK1 = 0x01;
  TCCR1A = 0x00;
  TCNT1  = INITIAL_TCNT1;
  TCCR1B = 0x04;
}

void loop () {
  int i;

  for (i = 0; i < N_STATES; i++) {
    analogWrite(ledFor[i], brightnessFor[i]);
  }

  delay(50);
}