Rainclock
Udruga Međimurski informatički klubRainclock je pametni sat koji automatski saznaje na kojoj je lokaciji upaljen, te pokazuje točno vrijeme i vremensku prognozu za tu lokaciju bez podešavanja korisnika. Korisniku nudi različite prikaze podataka dostupne preko web sučelja.
Ciljevi ovog projekta su produbiti znanje učenika vezano uz Arduino MKR1000 s naglaskom na njegove sposobnosti spajanja na internet, te naučiti razradu teme od ideje do gotovog proizvoda.
Projekt će se koristiti kao ogledni primjer u udruzi za edukaciju polaznika IOT tečajeva u cjelinama koje obrađuju komunikaciju između sustava na internetu (http, https/ssl, udp protokol, REST servisi, JSON, itd.). Upoznavanjem s aktualnim tehnologijama smanjujemo ulazne barijere naših članova u svijet IT-a i developmenta kroz zanimljive projekte i gadgete koje bi svatko poželio imati.
Željeli bi napomenut vanjske servise koje smo koristili za izradu projekta: NTP server – dobivanje podataka o trenutnom točnom vremenu, Google Maps Geolocation API – dobivanje geolokacije uređaja na temelju “vidljivih WiFI access pointova”, Google Maps Time Zone API – dobivanje podataka o vremenskoj zoni na temelju geolokacijskih podataka Open Weather Map API – podaci o trenutnoj vremenskoj prognozi an temelju geolokacijskih podataka
Video snimka
Izrada projekta
Projekt sa svim spojenim komponentama spreman za “ugradnju” u kućište. Možemo vidjeti sve glavne komponente – Arduino, LCD ekran, RGB led diodu i servo motor s pločicom na kojoj su prikazane vremenske prilike.
Mobilna aplikacija
Shema projekta
Arduino program
#include #include#include #include #include #include #include #include #include #include /*************************** * CONSTANTS ***************************/ const int status = WL_IDLE_STATUS; const unsigned int NTP_LOCAL_PORT = 2390; const int NTP_PACKET_SIZE = 48; const int LCD_COLS = 16; const int LCD_ROWS = 2; /*************************** * WiFi ***************************/ String ssid = "leglo virusa"; String pass = "#NeD1rajM11nternet!"; String googleApiKey = "AIzaSyDvBb8EKrcGDYshtyv0vkQEddEQ9mOUNqM"; //char ssid[] = "TICM-UCIONA"; //char pass[] = "ticmuciona"; //char ssid[] = "Dexter's Laboratory"; //char pass[] = "TICM_Lab2017!"; IPAddress ip; WiFiServer server(80); /*************************** * NTP ***************************/ IPAddress timeServer(129, 6, 15, 28); byte packetBuffer[NTP_PACKET_SIZE]; WiFiUDP Udp; unsigned long epoch = 0; /*************************** * DISPLAY ***************************/ hd44780_I2Cexp lcd; /************************** * LOCATION **************************/ WifiLocation location(googleApiKey); location_t locationData; String locationCountry; String locationCity; /*************************** * TIMEZONE ***************************/ int dstOffset = 0; int rawOffset = 0; String timezoneStatus = ""; String timeZoneId = ""; String timeZoneName = ""; String timezoneServer = "maps.googleapis.com"; String timezoneApi = "/maps/api/timezone/"; String timezoneReturnType = "json"; String timezoneLocation = "?location="; String timezoneTimestamp = "×tamp=1458000000"; String timezoneKey = "&key="; /*************************** * OWM ***************************/ String owmApiKey = "27604a3ec8c43638c2ed800f47f8c5d5"; String owmServer = "api.openweathermap.org"; String weatherId; String weatherMain; String weatherTemp; String weatherHumidity; String weatherPressure; /*************************** * RAINCLOCK ***************************/ int time_h; int time_m; int time_s; unsigned long ntpCalldelay = 57000; unsigned long t1; int servoPin = 5; Servo servo; int rgbPins[3] = {2, 3, 4}; String displayVersion = "DEFAULT"; /*************************** * SETUP - start ***************************/ void setup() { echoBrand("SETUP"); //upali Serial Monitor - koristimo za debug Serial.begin(9600); //postavke za servo motor koji pokazuje vremenske prilike servo.attach(servoPin); servo.write(180); //povezivanje lcd zaslona int lcdStatus = lcd.begin(LCD_COLS, LCD_ROWS); if (lcdStatus) { lcdStatus = -lcdStatus; hd44780::fatalError(lcdStatus); } //provjeri da li uredaj na koji smo skinuli program ima mogucnost spajanja na wifi if (WiFi.status() == WL_NO_SHIELD) { echoBrand("WiFi nedostupan na uredaju"); while (true); } //spoji se na wifi //ime mreze je u varijabli ssid, a lozinka u varijabli pass int wifiStatus; while (wifiStatus != WL_CONNECTED) { echoBrand("WiFi spajanje..."); Serial.println(ssid); wifiStatus = WiFi.begin(ssid, pass); delay(10000); } echoBrand("WiFi spojen"); //ispisi informacije o wifi vezi na serial monitor printWiFiStatus(); //dobavi trenutnu lokaciju uredaja s google servisa //na temelju vidljivih wifi tocaka locationData = location.getGeoFromWiFi(); echoBrand("lokacija:"); delay(1000); echoBrand("Lat: " + String(locationData.lat, 7)); delay(1000); echoBrand("Lon: " + String(locationData.lon, 7)); delay(1000); //dobavi informacije o vremenskoj zoni u //kojoj je uredaj upaljen na temelju koordinata iz proslog koraka getTimeZone(); echoBrand("Vremenska zona:"); delay(1000); echoBrand(timezoneStatus); delay(1000); echoBrand(timeZoneId); delay(1000); echoBrand(timeZoneName); delay(1000); //dobavi informacije o vrmenskoj progrnozi //sa servisa open weather map getCurrentWeather(locationData); //dobavi trenutno vrijeme s NTP servera echoBrand("NTP spajanje..."); Udp.begin(NTP_LOCAL_PORT); getNTPTime(); //pokreni lokalni server da arduino //moze primati zahtjeve korisnika server.begin(); //pohrani vrijeme za timer koji //odreduje interval pozivanja NTP servisa t1 = millis(); } /*************************** * SETUP - end ***************************/ /*************************** * LOOP - start ***************************/ void loop() { if (millis() - t1 > ntpCalldelay) { getNTPTime(); t1 = millis(); } enableServer(); } /*************************** * LOOP - end ***************************/ /** * metoda enableServer() brine o zahtjevima korisnika * arduino prikazuje jednostavno sucelje sa 4 linka * putem kojih postavljamo razlicit raspored informacija * na ekranu * * podrzane http metode: GET * podrzani http pozivi: * - /DEFAULT * - /WEATHER * - /LOCATION * - /TIMEZONE */ void enableServer() { WiFiClient client = server.available(); if (client) { String currentLine = ""; while (client.connected()) { if (client.available()) { char c = client.read(); Serial.write(c); if (c == '\n') { //kada se primi http zahtjev od klijenta //posalji odgovor koji prikazuje izbornik ekrana if (currentLine.length() == 0) { client.println("HTTP/1.1 200 OK"); client.println("Content-type:text/html"); client.println(); client.println(" "); client.println(); break; } else { currentLine = ""; } } else if (c != '\r') { currentLine += c; } //ovisno o zahtjevu klijenta pozovi odgovarajucu //metodu za drugaciji raspored informacija na ekranu if (currentLine.endsWith("GET /DEFAULT")) { displayVersion = "DEFAULT"; echoCurrentTime(); } if (currentLine.endsWith("GET /WEATHER")) { displayVersion = "WEATHER"; echoWeather(); } if (currentLine.endsWith("GET /LOCATION")) { displayVersion = "LOCATION"; echoLocation(); } if (currentLine.endsWith("GET /TIMEZONE")) { displayVersion = "TIMEZONE"; echoTimezone(); } } } delay(1); client.stop(); } } /** * metoda getNTPTime() sluzi da dobavljanje podataka * o trenutnom vremenu sa NTP servera */ void getNTPTime() { sendNTPpacket(timeServer); delay(1000); if (Udp.parsePacket()) { Udp.read(packetBuffer, NTP_PACKET_SIZE); unsigned long highWord = word(packetBuffer[40], packetBuffer[41]); unsigned long lowWord = word(packetBuffer[42], packetBuffer[43]); unsigned long secsSince1900 = highWord << 16 | lowWord; const unsigned long seventyYears = 2208988800UL; epoch = secsSince1900 - seventyYears; getCurrentTime(rawOffset); } } /** * metoda sendNTPpacket() salje UDP zahtjev * NTP serveru na sto NTP server odgovara * podacima koje obradujemo u metodi getNTPTime() */ unsigned long sendNTPpacket(IPAddress &address) { memset(packetBuffer, 0, NTP_PACKET_SIZE); packetBuffer[0] = 0b11100011; packetBuffer[1] = 0; packetBuffer[2] = 6; packetBuffer[3] = 0xEC; packetBuffer[12] = 49; packetBuffer[13] = 0x4E; packetBuffer[14] = 49; packetBuffer[15] = 52; Udp.beginPacket(address, 123); Udp.write(packetBuffer, NTP_PACKET_SIZE); Udp.endPacket(); } /** * metoda printWiFiStatus() ispisuje podatke * o wifi vezi */ void printWiFiStatus() { Serial.print("SSID: "); Serial.println(WiFi.SSID()); //spremi ip adresu u varijablu ip ip = WiFi.localIP(); Serial.print("IP Adresa: "); Serial.println(ip); long rssi = WiFi.RSSI(); Serial.print("jacina signala (RSSI):"); Serial.print(rssi); Serial.println(" dBm"); } /** * metoda getCurrentTime() na temelju * podataka od NTP servera racuna * sate, minute i sekunde. * * metodu pozivamo na kraju svakog intervala osvjezavanja * pa tako ovdje provjeravamo koji ekran je trenutno aktivan * kako bi korisniku prikazali tocne podatke */ void getCurrentTime(int rawOffset) { time_h = ((epoch % 86400L) / 3600) + (rawOffset / 3600) - 12; time_m = ((epoch % 3600) / 60); time_s = (epoch % 60); if (displayVersion == "DEFAULT") { echoCurrentTime(); } if (displayVersion == "WEATHER") { echoWeather(); } if (displayVersion == "LOCATION") { echoLocation(); } if (displayVersion == "TIMEZONE") { echoTimezone(); } else { echoCurrentTime(); } } /** * metoda echoCurrentTime() */ void echoCurrentTime() { lcd.clear(); lcd.setCursor(0, 0); lcd.print(time_h); lcd.print(':'); if (time_m < 10) { lcd.print('0'); } lcd.print(time_m); //lcd.print(':'); //if (time_s < 10) //{ // lcd.print('0'); //} //lcd.print(time_s); lcd.print(" "); int dow = ((epoch / 86400) + 4) % 7; switch (dow) { case 0: lcd.print("NED"); break; case 1: lcd.print("PON"); break; case 2: lcd.print("UTO"); break; case 3: lcd.print("SRI"); break; case 4: lcd.print("CET"); break; case 5: lcd.print("PET"); break; case 6: lcd.print("SUB"); break; } lcd.print(" "); lcd.print(weatherTemp); lcd.print((char)223); lcd.setCursor(0, 1); lcd.print(ip); } /** * metoda echoWeather() */ void echoWeather() { lcd.clear(); lcd.setCursor(0, 0); lcd.print(weatherTemp); lcd.print((char)223); lcd.print(" "); lcd.print(weatherMain); lcd.setCursor(0, 1); lcd.print("H:"); lcd.print(weatherHumidity); lcd.print("% "); lcd.print("P:"); lcd.print(weatherPressure); lcd.print("hPa"); } /** * metoda echoLocation() */ void echoLocation() { lcd.clear(); lcd.setCursor(0, 0); lcd.print(locationCity); lcd.print(","); lcd.print(locationCountry); lcd.setCursor(0, 1); lcd.print(String(locationData.lat, 5)); lcd.print(" "); lcd.print(String(locationData.lon, 5)); } /** * metoda echoTimezone() */ void echoTimezone() { lcd.clear(); lcd.setCursor(0, 0); lcd.print(timeZoneId); lcd.setCursor(0, 1); lcd.print(timeZoneName); } /** * metoda echo() pomaze u ispisu na lcd ekran * prvi parametar ispisuje u prvi red, a drugi u drugi * red ekrana * * metoda parametre biljezi i na serial monitor */ void echo(String msg1, String msg2) { lcd.clear(); lcd.print(msg1); lcd.setCursor(0, 1); lcd.print(msg2); Serial.println(msg1); Serial.println(msg2); } /** * metoda echoBrand() pomaze u ispisu na lcd ekran * parametar ispisuje u drugi red, a u prvi * ispisuje naziv projekta "rainkclock" * * metoda parametar biljezi i na serial monitor */ void echoBrand(String msg) { lcd.clear(); lcd.print("RAINCLOCK"); lcd.setCursor(0, 1); lcd.print(msg); Serial.println(msg); } /** * metoda getTimeZone() sluzi za komunikaciju s google servisom * za vremenske zone * * komunikaciju je bilo potrebno odraditi "rucno" jer * nismo pronasli odgovarajuci library */ void getTimeZone() { String lat = String(locationData.lat, 7); String lon = String(locationData.lon, 7); String timeZoneLocationParams = lat + ',' + lon; String timeZoneApiCommand = "GET " + timezoneApi + timezoneReturnType + timezoneLocation + timeZoneLocationParams + timezoneTimestamp + timezoneKey + googleApiKey + " HTTP/1.1"; Serial.println(timeZoneApiCommand); //google api koristi ssl(https) WiFiSSLClient sslClient; //ssl defaultni port je 443, za rayliku od http-a gdje je to port 80 if (sslClient.connect(timezoneServer.c_str(), 443)) { sslClient.println(timeZoneApiCommand); sslClient.println("Host: " + timezoneServer); sslClient.println("User-Agent: ArduinoWiFi/1.1"); sslClient.println("Connection: close"); sslClient.println(); } String response; delay(1000); while (sslClient.available()) { char c = sslClient.read(); response += c; Serial.write(c); } int index = response.indexOf("dstOffset"); if (index != -1) { String tempStr = response.substring(index); tempStr = tempStr.substring(tempStr.indexOf(":") + 1, tempStr.indexOf(",")); dstOffset = tempStr.toInt(); Serial.println(dstOffset); } index = response.indexOf("rawOffset"); if (index != -1) { String tempStr = response.substring(index); tempStr = tempStr.substring(tempStr.indexOf(":") + 1, tempStr.indexOf(",")); rawOffset = tempStr.toInt(); Serial.println(rawOffset); } index = response.indexOf("status"); if (index != -1) { String tempStr = response.substring(index); timezoneStatus = tempStr.substring(tempStr.indexOf(":") + 1, tempStr.indexOf(",")); timezoneStatus.trim(); timezoneStatus = timezoneStatus.substring(1, timezoneStatus.length() - 1); Serial.println(timezoneStatus); } index = response.indexOf("timeZoneId"); if (index != -1) { String tempStr = response.substring(index); timeZoneId = tempStr.substring(tempStr.indexOf(":") + 1, tempStr.indexOf(",")); timeZoneId.trim(); timeZoneId = timeZoneId.substring(1, timeZoneId.length() - 1); Serial.println(timeZoneId); } index = response.indexOf("timeZoneName"); if (index != -1) { String tempStr = response.substring(index); timeZoneName = tempStr.substring(tempStr.indexOf(":") + 1, tempStr.indexOf(",")); timeZoneName.trim(); timeZoneName = timeZoneName.substring(1, timeZoneName.length() - 3); Serial.println(timeZoneName); } sslClient.stop(); } /** * metoda getCurrentWeather() sluzi za komunikaciju s * open wather map servisom za vremensku prognozu * * iz dobivenog rezultata koji je u JSON formatu * izvlacimo podatke pomocu ArduinoJson library-ja */ void getCurrentWeather(location_t location) { WiFiClient owmClient; Serial.println("\nStarting connection to OWM..."); if (owmClient.connect(owmServer.c_str(), 80)) { Serial.println("Connected to OWM!"); owmClient.print("GET /data/2.5/weather?"); owmClient.print("lat=" + String(location.lat, 7)); owmClient.print("&lon=" + String(location.lon, 7)); owmClient.print("&appid=" + owmApiKey); owmClient.println("&units=metric"); owmClient.println("Host: api.openweathermap.org"); owmClient.println("Connection: close"); owmClient.println(); } delay(1000); String response = ""; while (owmClient.connected()) { response = owmClient.readStringUntil('\n'); Serial.println(response); StaticJsonBuffer<5000> jsonBuffer; JsonObject &root = jsonBuffer.parseObject(response); if (!root.success()) { Serial.println("JSON parse failed!"); return; } weatherId = (const char *)root["weather"][0]["id"]; weatherMain = (const char *)root["weather"][0]["main"]; weatherTemp = (const char *)root["main"]["temp"]; weatherHumidity = (const char *)root["main"]["humidity"]; weatherPressure = (const char *)root["main"]["pressure"]; locationCountry = (const char *)root["sys"]["country"]; locationCity = (const char *)root["name"]; int temperature = weatherTemp.toInt(); int weatherCode = weatherId.toInt(); Serial.println(weatherId); Serial.println(weatherMain); Serial.println(weatherTemp.toInt()); Serial.println(weatherHumidity); Serial.println(weatherPressure); showWeather(temperature, weatherCode); } owmClient.stop(); } /** * metoda showWeather() sluzi za prikaz * temperature (rgb ledica) i vrmenena (servo motor) */ void showWeather(int temperature, int weatherCode) { if (weatherCode >= 200 && weatherCode <= 299) { servo.write(0); } else if ((weatherCode >= 500 && weatherCode <= 599) || weatherCode == 906 || (weatherCode >= 300 && weatherCode <= 399)) { servo.write(30); } else if (weatherCode >= 600 && weatherCode <= 699) { servo.write(60); } else if (weatherCode == 741 || weatherCode == 701) { servo.write(90); } else if (weatherCode == 905) { servo.write(120); } else if (weatherCode > 800 && weatherCode <= 899) { servo.write(150); } else { servo.write(180); } if (temperature > -10) { setColor(0x0000FF); } else if (temperature > 0) { setColor(0xFFFF00); } else if (temperature > 10) { setColor(0x00FF00); } else if (temperature > 20) { setColor(0x003300); } else if (temperature > 30) { setColor(0xFF0000); } } /** * metoda setColor() postavlja boju RGB ledice * prema prosljedenoj hex vrijednosti */ void setColor(long color) { analogWrite(rgbPins[0], color >> 16); analogWrite(rgbPins[1], color >> 8 & 0xFF); analogWrite(rgbPins[2], color & 0xFF); }
"); client.println(" RAINCLOCK
"); client.println("ODABERI PRIKAZ NA EKRANU:
"); client.println("Standardno
"); client.println("Vremenska prognoza
"); client.println("Lokacija uredaja
"); client.println("Vremenska zona
"); client.println("
Arduino program ovoga projekta možete preuzeti ovdje.
Autori
Projekt su izradili Leo Stričak i Andrija Palašek uz mentorstvo Viktora Lazara iz Udruge Međimurski informatički klub.
Projekt je prijavljen na temu: Internet of Things: Pametna rasvjeta.