Online grafer med ingenting men en Arduino, Ethernet Shield och Sensor (4 / 5 steg)
Steg 4: Arduino skiss
Nu när det har alla tagit hand om, är vi redo att få SGDL alla set! Koden måste några justeringar för dina egna specifika inställningar, mestadels i hänsynen till Ethernet MAC och IP-adresser. Jag litar på att någon utnyttjar denna kod redan vet hur man konfigurera sin router att fungera med Arduino, och att de kan hitta lämplig lokal IP-adress att uppdatera denna skiss med. Du kanske också vill ändra timeserver IP-adressen till en som är geografiskt närmare dig själv.
Jag har för närvarande min kod att göra en mätning var 10 minuter, och att skapa en ny datafil varje vecka. Du är välkommen att ändra dessa parametrar, Tänk bara på att aktuella data file management namn filer med datumformatet dd-mm-yy.csv, så den nya filen intervallen bör vara minst 24 timmar. Ett annat bekymmer är att ju kortare mätning intervallet och längre nya data fil intervallet är, ju större filer kommer att vara. Arduino är inte särskilt kraftfull, får detta konsekvenser för lastning för varje diagram.
/* ************************************************************************
* *** Super grafritande datalogger ***
* ************************************************************************
* Everett Robinson, December 2012. Mer på: http://everettsprojects.com
*
* Denna skiss bygger på SD och Ethernet-arkiven i arduino 1.0 eller nyare.
* Extra icke följande standardbibliotek användes också, och kommer att behöva
* läggas till mappen bibliotek:
* - Tid: http://everettsprojects.com
* - EEPROMAnything: http://everettsprojects.com
*
* Om detta är din första tidsinställningen upp detta projekt, gå få den
* EEPROM_config skiss från http://everettsprojects.com så att du kan
* Konfigurera config struct i EEPROM minne. Användning av EEPROM
* behövs för att göra projektet robust mot en tillfällig förlust av makt.
*
* Du måste också se till att du har den HC.htm filen i rotkatalogen
* för ditt SD-kort, samt en datakatalog där datafiles kommer att
* lagras.
*
* Denna skiss kombinerar funktionaliteten hos ett befintligt fileserver exempel
* som kan hittas på http://everettsprojects.com
* med Datalogger exemplet som kommer med det nya SD-biblioteket från 1.0,
* som lite kod från UdpNtpClient-exemplet koner med den
* ethernet bibliotek.
*
* Lagt till alla dessa är några knep för att få det att lyckas och tjäna upp till
* datafiles tillsammans med en sida som använder highcharts JS för att diagram det.
* Detta i princip sker med hjälp av arduino av sig själv. Eftersom jag
* egentlig värd highcharts.js filerna externt, detta gäller mer i
* teorin än i praktiken, men oh well. Det bör fungera alldeles utmärkt att
* har filen highcharts.js på den arduino SD-kort, men laddar den
* sida kommer att vara plågsamt långsam.
*
* Några av koden detta härrör från får eller får inte under en GPL
* licensen. Jag är inte helt säker. Jag antar att alla som använder detta bör behandla
* som det är alltför, men jag bryr mig egentligen inte för mycket.
* Även om man avser att använda den här för kommersiella tillämpningar, kan det vara
* måste man köpa en licens för Highcharts.
*
* Changes: -------------------------------------------------------------
* Januari 2013: uppdateras så att formatet dd-mm-yy.csv är ordentligt
* följde, kommer att alla ensiffriga dagar, månader och år ha en ledande
* noll nu.
*
*/
#include < sd.h >
#include < ethernet.h >
#include < ethernetudp.h >
#include < spi.h >
#include < string.h >
#include < time.h >
#include < eeprom.h >
#include < eepromanything.h >
#include < avr/pgmspace.h >
/ *** ETHERNET GREJER *** /
byte [mac] = {0x90, 0xA2, 0xDA, 0x00, 0x4C, 0x64};
byte ip [] = {192,168,1, 100};
EthernetServer server(80);
/************** NTP STUFF ***************/
unsigned int localPort = 8888; lokal port att lyssna till UDP-paket
IP-adress timeServer (132, 163, 4, 101); NIST tid server IP-adress: för mer info
se http://everettsprojects.com
CONST int NTP_PACKET_SIZE = 48. NTP tidsstämpel är i de första 48 byte av meddelandet
byte packetBuffer [NTP_PACKET_SIZE]; buffert för att hålla inkommande och utgående paket
EthernetUDP Udp;
/ *** DATALOGGER OCH TIMER KONTROLLER *** /
CONST int analogPin = 0;
osignerade långa lastIntervalTime = 0; När sista mätningen uppstod.
#define MEASURE_INTERVAL 600000 //10 minut intervaller mellan mätningar (i ms)
osignerade långa newFileTime; Tiden som vi bör skapa en ny veckas fil
#define FILE_INTERVAL 604800 //One vecka värde av sekunder
En struktur som lagrar filen config växlande från EEPROM
TypeDef struct {
osignerade långa newFileTime; Håller reda på när en newfile kan göras.
char workingFilename [19]. Sökvägen och filnamnet för den innevarande veckan fil
} konfiguration;
konfiguration config; Faktiskt göra våra config struct
Strängar lagras i flash mem för HTML-huvudet (sparar ram)
prog_char HeaderOK_0 [] PROGMEM = "HTTP/1.1 200 OK"; //
prog_char HeaderOK_1 [PROGMEM] = "Content-Type: text/html"; //
prog_char HeaderOK_2 [PROGMEM] = ""; //
En tabell med pekare till flashminne stränger för huvudet
PROGMEM const char * HeaderOK_table [] = {
HeaderOK_0,
HeaderOK_1,
HeaderOK_2
};
En funktion för enkel utskrift av huvuden
void HtmlHeaderOK(EthernetClient client) {
char buffer [30]. En karaktär matris att hålla i trådarna från flash mem
för (int jag = 0; jag < 3; i ++) {
strcpy_P (buffert, (char *) pgm_read_word (&(HeaderOK_table[i])));
client.println (buffert);
}
}
Strängar lagras i flash mem för HTML-404 huvudet
prog_char Header404_0 [] PROGMEM = "HTTP/1.1 404 hittades inte"; //
prog_char Header404_1 [PROGMEM] = "Content-Type: text/html"; //
prog_char Header404_2 [PROGMEM] = ""; //
prog_char Header404_3 [PROGMEM] = "< h2 > filen hittades inte! < / h2 >";
En tabell med pekare till flashminne stränger för huvudet
PROGMEM const char * Header404_table [] = {
Header404_0,
Header404_1,
Header404_2,
Header404_3
};
Lätt peasy 404 funktionen
void HtmlHeader404(EthernetClient client) {
char buffer [30]. En karaktär matris att hålla i trådarna från flash mem
för (int jag = 0; jag < 4; i ++) {
strcpy_P (buffert, (char *) pgm_read_word (&(Header404_table[i])));
client.println (buffert);
}
}
void setup() {
Serial.BEGIN(9600);
pinMode (10, OUTPUT); Ange PIN-koden SS som en utgång (nödvändigt!)
digitalWrite 10, hög. men Stäng av W5100 chip!
se om kortet är närvarande och kan initieras:
IF (!. SD.begin(4)) {
Serial.println ("kort misslyckades, eller inte finns");
inte göra något mer:
hemkomst.
}
Serial.println ("kort initierats.");
SD-kortet fungerar, starta servern och Ethernet-relaterade saker!
Ethernet.BEGIN (mac, ip);
Server.BEGIN();
UDP.BEGIN(localPort);
EEPROM_readAnything(0,config); se till att våra config struct är synkroniserad med EEPROM
}
En funktion som tar hand om listan över filer för den
huvudsidan en ser när de ansluter först till arduino.
den listar bara filerna i mappen /data/. Se till att detta
Det finns på ditt SD-kort.
void ListFiles(EthernetClient client) {
Fil workingDir = SD.open("/data");
client.println ("< ul >");
While(true) {
Fil post = workingDir.openNextFile();
om (! post) {
bryta;
}
client.Print ("< li >< en href="\"/HC.htm?file=");
client.Print(Entry.Name());
client.Print ("\" > ");
client.Print(Entry.Name());
client.println ("< /a >< /li >");
Entry.Close();
}
client.println ("< /ul >");
workingDir.close();
}
En funktion för att få Ntp tid. Detta används för att säkerställa att data
poäng registreras av arduino refereras till en meningsfull tid
vilket i vårt fall är UTC representerade som unix time (valt eftersom det
fungerar bara med highcharts utan för mycket onödiga uträkning).
osignerade långa getTime() {
sendNTPpacket(timeServer); Skicka ett NTP-paket till en tidsserver
vänta och se om det finns ett svar
Delay(1000);
om (Udp.parsePacket()) {
Vi har fått ett paket, läsa data från det
UDP.Read(packetBuffer,NTP_PACKET_SIZE); läsa in paket i bufferten
tidsstämpeln börjar på byten 40 i det mottagna paketet och är fyra byte,
eller två ord, långa. Första, esxtract två ord:
osignerade långa highWord = word (packetBuffer [40], packetBuffer[41]);
osignerade långa lowWord = word (packetBuffer [42], packetBuffer[43]);
kombinera de fyra byte (två ord) till ett långt heltal
Detta är NTP tid (sekunder sedan Jan 1 1900):
osignerade långa secsSince1900 = highWord << 16 | lowWord;
UNIX time startar den Jan 1 1970. I sekunder är som 2208988800:
CONST osignerade långa seventyYears = 2208988800UL;
subtrahera sjuttio år:
osignerade långa epok = secsSince1900 - seventyYears;
returnera Unix time:
returnera epoken;
}
}
Skicka en begäran om NTP till tiden servern på den angivna adressen,
nödvändiga för getTime().
osignerade långa sendNTPpacket (IP-adress & adress) {
Ange alla byte i bufferten till 0
MEMSET (packetBuffer, 0, NTP_PACKET_SIZE);
Initiera värdena som behövs för att bilda NTP begäran
(se URL ovan för information om paketen)
packetBuffer [0] = 0b11100011; LI, Version, läge
packetBuffer [1] = 0; Stratum, eller typ av klocka
packetBuffer [2] = 6; Avsökningsintervallet
packetBuffer [3] = 0xEC; Peer klocka Precision
8 byte noll för roten dröjsmål & Root Dispersion
packetBuffer [12] = 49.
packetBuffer [13] = 0x4E;
packetBuffer [14] = 49.
packetBuffer [15] = 52;
alla NTP fält få värden, nu
Du kan skicka ett paket begär en tidsstämpel:
Udp.beginPacket (adress, 123); NTP-begäranden är till port 123
UDP.write(packetBuffer,NTP_PACKET_SIZE);
Udp.endPacket();
}
Hur stor vår linje buffert bör för att skicka filer över ethernet.
75 har fungerat bra för mig hittills.
#define BUFSIZ 75
void loop() {
om ((millis() % lastIntervalTime) > = MEASURE_INTERVAL) {//Is det dags för en ny mätning?
char dataString [20] = "";
int count = 0;
osignerade långa rawTime;
rawTime = getTime();
medan ((rawTime == 39) & & (räknas < 12)) {//server verkar skicka 39 som en felkod
Delay(5000); Vi vill försöka igen om detta händer. Jag valde
rawTime = getTime(); 12 antal försök eftersom jag är envis/ihållande.
räkna + = 1; NIST anser återförsöksintervallet för < 4s som DoS
} / / attack, så rättvis varning.
om (rawTime! = 39) {//If som fungerade, och vi har en verklig tid
Avgöra om det är dags att göra en ny fil eller inte. Filer är trasiga
upp så här att hålla laddningstider för varje diagram uthärdligt.
Massor av sträng saker händer att göra ett nytt filnamn om det behövs.
om (rawTime > = config.newFileTime) {
int dayInt = day(rawTime);
int monthInt = month(rawTime);
int yearInt = year(rawTime);
char newFilename [18] = "";
char dayStr [3].
char monthStr [3].
char yearStr [5].
char subYear [3].
strcat(newFilename,"data/");
itoa(dayInt,dayStr,10);
om (dayInt < 10) {
strcat(newFilename,"0");
}
strcat(newFilename,dayStr);
strcat(newFilename,"-");
itoa(monthInt,monthStr,10);
om (monthInt < 10) {
strcat(newFilename,"0");
}
strcat(newFilename,monthStr);
strcat(newFilename,"-");
itoa(yearInt,yearStr,10);
Vi vill bara ha de två sista siffrorna av året
memcpy (subYear, och yearStr [2], 3);
strcat(newFilename,subYear);
strcat(newFilename,".csv");
se till att vi uppdaterar våra config växlande:
config.newFileTime += FILE_INTERVAL;
strcpy(config.workingFilename,newFilename);
Skriv ändringarna till EEPROM. Dåliga saker kan hända om makt är förlorad halvvägs in,
men det är en liten risk vi tar. Manuell korrigering med EEPROM_config skiss kan rätta den.
EEPROM_writeAnything (0, config);
}
värden och setup strängen vi vill skriva till filen
int sensor = analogRead(analogPin);
char timeStr [12].
char sensorStr [6].
ultoa(rawTime,timeStr,10);
itoa(sensor,sensorStr,10);
strcat(dataString,timeStr);
strcat(dataString,",");
strcat(dataString,sensorStr);
Öppna filen vi kommer att skriva till.
Fil dataFile = SD.open (config.workingFilename, FILE_WRITE);
om filen är tillgänglig, skriva till det:
om (dataFile) {
dataFile.println(dataString);
dataFile.close();
skriva ut till den seriella porten alltför:
Serial.println(dataString);
}
om filen inte är öppen, dyker upp ett felmeddelande:
annat {
Serial.println ("fel öppning datafil för skriva");
}
}
annat {
Serial.println kunde inte ("lösa en tid från Ntp-Server.");
}
Uppdatera tiden för den senaste mätning till det nuvarande timer-värdet
lastIntervalTime = millis();
}
Inga mätningar göras, se till att webbservern kan användas för anslutningar.
annat {
char clientline [BUFSIZ];
heltal index = 0;
EthernetClient klient = server.available();
om (klient) {
en http-förfrågan avslutas med en tom rad
booleska current_line_is_blank = sant;
återställa indatabufferten
index = 0;
samtidigt (client.connected()) {
om (client.available()) {
char c = client.read();
Om det inte är en ny linje, lägga till tecknet i bufferten
om (c! = '\n' & & c! = '\r') {
clientline [index] = c;
index ++;
är vi för stor för bufferten? Starta gungade ut data
om (index > = BUFSIZ)
index = BUFSIZ -1;
Fortsätt att läsa mer data!
fortsätta;
}
fick en \n eller \r nya linje, är vilket innebär att strängen gjort
clientline [index] = 0;
Skriva ut den för felsökning
Serial.println(clientline);
Leta efter delsträng som en begäran att få rotfilen
om (strstr (clientline, "GET /")! = 0) {
Skicka ett standard http-svarshuvud
HtmlHeaderOK(client);
skriva alla datafiler, använda en hjälpare för att hålla den ren
client.println ("< h2 > Visa data för veckan (dd-mm-åå): < / h2 >");
ListFiles(client);
}
annars om (strstr (clientline, "GET /")! = 0) {
denna gång något utrymme efter den /, så en sub fil!
char * filnamn.
filnamn = strtok (clientline + 5, "?"); Titta den "GET /" (5 tecken) men före
den "?" om du har angett en fil. Ett litet trick, leta efter "HTTP/1.1"
sträng och vända det första tecknet i delsträngen in 0 att rensa ut.
(strstr (clientline, "HTTP")) [0] = 0;
skriva ut filen vi vill
Serial.println(filename);
Fil = SD.open(filename,FILE_READ);
om (! filen) {
HtmlHeader404(client);
bryta;
}
Serial.println("Opened!");
HtmlHeaderOK(client);
int16_t c;
medan ((c = file.read()) > 0) {
avkommentera den seriella att felsöka (långsamt!)
Serial.Print((Char)c);
client.Print((Char)c);
}
File.Close();
}
annat {
allt annat är en 404
HtmlHeader404(client);
}
bryta;
}
}
de web webbläsare möjlighet att ta emot data
Delay(1);
client.stop();
}
}
}