Skocz do zawartości

Przeszukaj forum

Pokazywanie wyników dla tagów 'ESP8266'.

  • Szukaj wg tagów

    Wpisz tagi, oddzielając przecinkami.
  • Szukaj wg autora

Typ zawartości


Kategorie forum

  • Elektronika i programowanie
    • Elektronika
    • Arduino, ESP
    • Mikrokontrolery
    • Raspberry Pi
    • Inne komputery jednopłytkowe
    • Układy programowalne
    • Programowanie
    • Zasilanie
  • Artykuły, projekty, DIY
    • Artykuły redakcji (blog)
    • Artykuły użytkowników
    • Projekty - roboty
    • Projekty - DIY
    • Projekty - DIY (początkujący)
    • Projekty - w budowie (worklogi)
    • Wiadomości
  • Pozostałe
    • Oprogramowanie CAD
    • Druk 3D
    • Napędy
    • Mechanika
    • Zawody/Konkursy/Wydarzenia
    • Sprzedam/Kupię/Zamienię/Praca
    • Inne
  • Ogólne
    • Ogłoszenia organizacyjne
    • Dyskusje o FORBOT.pl
    • Na luzie
    • Kosz

Szukaj wyników w...

Znajdź wyniki, które zawierają...


Data utworzenia

  • Rozpocznij

    Koniec


Ostatnia aktualizacja

  • Rozpocznij

    Koniec


Filtruj po ilości...

Data dołączenia

  • Rozpocznij

    Koniec


Grupa


Znaleziono 12 wyników

  1. Witam, nabyłem płytkę ESP8266 z którą "chciałbym się zaprzyjaźnić". W internecie co prawda jest dużo różnych wiadomości na ten temat, aż trudno wybrać, te właściwe. Nie znalazłem żadnego sensownego kursu. Proszę napiszcie co polecacie na początek "zabawy" z ESP8266.
  2. Za chwilę mnie coś strzeli... Program najprostszy chyba z możliwych (bez żadnych bibliotek): #include <Arduino.h> #include <i2s_reg.h> #include <i2s.h> int g_samrate = 8000; uint32_t last_micro_call; uint32_t idle_micros = 100000; void init_audio() { i2s_begin(); i2s_set_rate(g_samrate); last_micro_call = micros(); } void put_sample(int16_t sample) { uint32_t usample; usample = sample & 0x0000ffff; usample |= usample << 16; if (micros() - last_micro_call > idle_micros) { last_micro_call = micros(); yield(); } // wersja 1 najprostsza - działa źle i2s_write_sample(usample); /* // wersja 2 - to samo while (i2s_is_full()) { delay(1); last_micro_call = micros(); }*/ /* // wersja 3 - to samo while (!i2s_write_sample_nb(usample)) { delay(1); last_micro_call = micros(); }*/ } void finalize_audio() { for (int i=0; i<1000 && !i2s_is_full(); i++) i2s_write_sample_nb(0); i2s_end(); } void setup(void) { } void loop(void) { int i; init_audio(); for (i=0;i<4000;i++) { float a = i * (3.141592/10); int16_t s = sin(a) * 4096; put_sample(s); } finalize_audio(); delay(500); } Efekt działania: co prawda pika sobie bardzo ładnie, ale mniej więcej połowa (całkowicie losowo) "piknięć" jest głośniejsza i zniekształcona - tak jakby wejściowa próbka przesunęła się o bit w lewo. Miałem podobny efekt kiedyś gdy próbowałem coś zrobić z TDA1543 ale uznałem, że to pewnie wina połączeń na paszczatej płytce stykowej albo 8kHz to dla niego za mało... Co ciekawsze - podobny program, ale używający hacku PWM działa ślicznie... Ktoś coś... jakieś pomysły? Bo mi się skończyły Po sprawdzeniu: zamiana na bibliotekę ESP8266Audio i użycie AudioOutputI2S nic nie zmienia.
  3. Mamy budynek gospodarczy oddalony od domu o kilka(naście) metrów i chcemy upewnić się, że panują tam warunki odpowiednie dla zwierząt. Jak do tej pory nasze kury nie były zainteresowane dostępem do Internetu, nie mamy więc tam zasięgu sieci WiFi. Musimy więc jakoś "podkablować radiowo" dane i zaprezentować je w naszym pokoju. Założeniami projektu, o który prosił mnie kolega było: bezprzewodowe transmitowanie danych z czujnika do domu, przy czym sam czujnik "nie ma dostępu do Internetu" wyświetlanie aktualnego odczytu na LCD oraz aktywacja alarmu w razie spadku temperatury poniżej zadanej wartości zapisywanie wyników na stronie WWW dostępnej poprzez Internet, dzięki czemu warunki można sprawdzać będąc poza domem Czujnik z nadajnikiem Nadajnik zasilany jest "kradnąc" prąd ze stale włączonego zasilacza instalacji alarmowej. Dlatego też konieczne było zastosowanie przetwornicy zmniejszającej napięcie do 5V. Składniki: Arduino Pro Mini 328 - 5V/16MHz termometr BMP280 moduł nadajnika RF433MHz przetwornica step-down LM2596 Pamiętajmy, że BMP280 toleruje napięcie 3.3V na linii danych, a nasze Arduino wykorzystuje 5V. Najlepiej więc podłączyć szynę I2C poprzez dzielnik napięcia lub konwerter poziomów logicznych. Ja wstawiłem szeregowo rezystor 4.7kΩ na obu pinach I2C, co nie jest rozwiązaniem "profesjonalnym" i zalecanym, ale (chwilowo) działa #include <RCSwitch.h> //https://github.com/sui77/rc-switch #include <Wire.h> #include <SPI.h> #include <Adafruit_BMP280.h> Adafruit_BMP280 bmp; RCSwitch mySwitch = RCSwitch(); void setup() { Serial.begin(9600); if (!bmp.begin(0x76)) //changed from default I2C adress 0x77 { Serial.println("Nie odnaleziono czujnika BMP085 / BMP180"); while (1) { } } mySwitch.enableTransmit(10); } void loop() { int temperature = (int)bmp.readTemperature(); mySwitch.send(temperature, 24); delay(10000); } Odbiornik ESP8266 odbiera dane z modułu RF433MHz i regularnie przesyła je na stronę WWW. Stanowi ją skrypt PHP, który zapisuje do bazy danych oraz prezentuje wyniki. Wystarczy, by ESP pobrało adres skryptu i metodą GET przekazało dane (http://mojadomena.pl/skryptodbiorczy.php?haslo=tajnehaslo&temperatura=21). Składniki: ESP8266 moduł odbiornika RF433MHz wyświetlacz LCD 2x16 z konwerterem I2C LCM1602 buzzer Najtańsze moduły RF433 oferują jedynie kilkadziesiąt cm zasięgu. Musimy wykonać własną antenę z kawałka drutu, by uzyskać użyteczną komunikację. #include <ESP8266WiFi.h> #include <WiFiClient.h> #include <ESP8266WebServer.h> #include <ESP8266HTTPClient.h> #include <RCSwitch.h> //https://github.com/sui77/rc-switch #include <Wire.h> #include <LiquidCrystal_I2C.h> //https://github.com/fdebrabander/Arduino-LiquidCrystal-I2C-library #define MinimumTemperature 16 #define TempAlarmPin D1 LiquidCrystal_I2C lcd(0x27, 16, 2); HTTPClient httpc; const char* ssid = ""; const char* password = ""; const String accesspassword = "nojakieshaslo"; const String scripturl = "http://domena.saf/receivedata.php"; const char* WiFiHostname = "kurnik"; unsigned long lastTimeLCD = 0; int temperature = 0; RCSwitch mySwitch = RCSwitch(); void setup() { pinMode(TempAlarmPin, OUTPUT); digitalWrite(TempAlarmPin, LOW); Serial.begin(9600); Wire.begin(D3, D4); Wire.setClock(100000); lcd.begin(); lcd.backlight(); mySwitch.enableReceive(4); // D2 pinMode(13, OUTPUT); WiFi.hostname(WiFiHostname); WiFi.softAPdisconnect(); WiFi.disconnect(); WiFi.mode(WIFI_STA); WiFi.begin(ssid, password); } void loop() { if (mySwitch.available()) { temperature = mySwitch.getReceivedValue(); Serial.print("Received "); Serial.println(temperature); mySwitch.resetAvailable(); if (temperature <= MinimumTemperature) { digitalWrite(TempAlarmPin, HIGH); } else { digitalWrite(TempAlarmPin, LOW); } } if (lastTimeLCD == 0 || millis() - lastTimeLCD >= 60000) { lastTimeLCD = millis(); lcd.clear(); lcd.print(String(temperature) + "C"); String url = scripturl + "?password=" + accesspassword + "&temperature=" + temperature; httpc.begin(url); int httpCode = httpc.GET(); //Send the request if (httpCode > 0) { //Check the returning code String payload = httpc.getString(); //Get the request response payload Serial.println(payload); } } } Skrypt PHP: <?php DEFINE ('DB_USER', ''); DEFINE ('DB_PASSWORD',''); DEFINE ('DB_HOST','localhost'); DEFINE ('DB_NAME',''); DEFINE ('DB_TABLE_NAME','zagroda'); DEFINE ('PasswordToSave','nojakieshaslo'); $variables = array( array("temperature", "float"), ); for($i = 0; $i < count($variables); $i++) { if($_REQUEST[$variables[$i][0]] != "") { $savetodatabase = 1; break; } } try{ $pdo = new PDO('mysql:host='.DB_HOST.';dbname='.DB_NAME.';port=3306', DB_USER, DB_PASSWORD); //echo 'Connected to database'; }catch(PDOException $e){ echo 'Cannot connect to database<br />'; } $pdo->setAttribute(PDO::ATTR_ERRMODE, PDO::ERRMODE_EXCEPTION); if($savetodatabase && $_REQUEST["password"] == PasswordToSave) { if(!tableExists($pdo, DB_TABLE_NAME)){ $query= "CREATE TABLE ".DB_TABLE_NAME." (ID int NOT NULL AUTO_INCREMENT, "; for($i = 0; $i < count($variables); $i++) { $query= $query.$variables[$i][0]." ".$variables[$i][1].", "; } $query= $query."update_time BIGINT, PRIMARY KEY (ID))"; $pdo->exec($query); //echo '<br><br>'.$query.'<br><br>'; } $query="INSERT IGNORE INTO ".DB_TABLE_NAME." (ID,"; if($_REQUEST["temperature"] != ""){ $query = $query."temperature,update_time"; } $query= $query.") VALUES (1,"; if($_REQUEST["temperature"] != ""){ $query = $query."'".mysql_real_escape_string($_REQUEST["temperature"])."',"; } $query= $query."'".time()."') ON DUPLICATE KEY UPDATE "; if($_REQUEST["temperature"] != ""){ $query = $query."temperature=VALUES(temperature),update_time=VALUES(update_time)"; } //echo '<br><br>'.$query.'<br><br>'; $pdo->exec($query); } else { if($_REQUEST["values"] == "1"){ $query="SELECT * FROM `".DB_TABLE_NAME."` ORDER BY `ID` DESC LIMIT 10"; $stmt = $pdo -> query($query); $row = $stmt->fetch(/* PDO::FETCH_ASSOC */); echo $row[1].';'.$row[2].';'; $stmt->closeCursor(); } else{ echo '<html><head><meta charset="UTF-8"><title>Chicken coop</title></head> <body><table border="3"> <tr><td><b>Temperature</b></td> <td><b>Time</b></td></tr> <tr><td><span id="temp"></span>°C</td> <td><span id="time"></span></td></tr> </table> <script>myTimer();var myVar = setInterval(myTimer, 3000);function myTimer() { var xhttp = new XMLHttpRequest(); xhttp.onreadystatechange = function() { if (this.readyState == 4 && this.status == 200) { var str = this.responseText; var values = str.split(";"); document.getElementById("temp").innerHTML = values[0]; document.getElementById("time").innerHTML = Unix_timestamp(values[1]); } }; xhttp.open("GET", "receivedata.php?values=1", true); xhttp.send();} function Unix_timestamp(t) { var dt = new Date(t*1000); var day = dt.getDate(); var month = "0" + (dt.getMonth()+1); var year = dt.getFullYear(); var hr = dt.getHours(); var m = "0" + dt.getMinutes(); var s = "0" + dt.getSeconds(); return day+ "." + month.substr(-2) + "." + year + " " + hr+ ":" + m.substr(-2) + ":" + s.substr(-2); } </script> </body></html>'; } } function tableExists($pdo, $table) { try { $result = $pdo->query("SELECT 1 FROM $table LIMIT 1"); } catch (Exception $e) { return FALSE; } return $result !== FALSE; } ?> Jeżeli zechcemy dopisać do tego zapisywanie historii (przy obecnych założeniach projektu prezentowany jest jedynie najnowszy odczyt) i wykres zmian w czasie, polecam zapoznać się z biblioteką Google Charts. Przykład jej wykorzystania razem z AJAXem. Możemy również wykonać kolejny wyświetlacz np do innego pokoju czy nawet innego budynku, który odczytywał będzie wartości ze wspomnianej strony WWW. Wykorzystałem do tego znane nam ESP oraz wyświetlacz siedmiosegmentowy sterowany chipem MAX7219. #include <ESP8266WiFi.h> #include <WiFiClient.h> #include <ESP8266HTTPClient.h> #include <ESP8266WebServer.h> #include <EEPROM.h> #include "LedControl.h" //http://github.com/wayoda/LedControl #include <MD5Builder.h> #define WiFimodeButton 14 //D5 String scripturl = ""; String ssid = "", pass = ""; String host, url, http; const int httpPort = 80; const char* WiFiHostname = "Henhouse"; const String accesspassword = "nojakieshaslo"; unsigned long lastTimeLCD = 0, lastDataRead; unsigned int intervalDataRead = 5; WiFiClient client; ESP8266WebServer server(80); HTTPClient httpc; // EasyESP or NodeMCU Pin D8 to DIN, D7 to Clk, D6 to LOAD, no.of devices is 1 LedControl lc = LedControl(D8, D7, D6, 1); void setup() { pinMode(WiFimodeButton, INPUT_PULLUP); Serial.begin(9600); /* The MAX72XX is in power-saving mode on startup, we have to do a wakeup call */ lc.shutdown(0, false); /* Set the brightness to a medium values */ lc.setIntensity(0, 1); /* and clear the display */ lc.clearDisplay(0); EEPROM.begin(512); EEPROM.get(0, intervalDataRead); WiFi.softAPdisconnect(); WiFi.disconnect(); int i; if (ssid == "") { for (i = 100; i < 170; i++) { if (EEPROM.read(i) == NULL) { break; } ssid += char(EEPROM.read(i)); } for (i = i + 1; i < 250; i++) { if (EEPROM.read(i) == NULL) { break; } pass += char(EEPROM.read(i)); } } if (scripturl == "") { for (i = i + 1; i < 450; i++) { if (EEPROM.read(i) == NULL) { break; } scripturl += char(EEPROM.read(i)); } } if (digitalRead(WiFimodeButton) == LOW) { //access point part Serial.println("Creating Accesspoint"); IPAddress apIP(192, 168, 1, 1); WiFi.mode(WIFI_AP); WiFi.softAPConfig(apIP, apIP, IPAddress(255, 255, 255, 0)); WiFi.softAP("aquarium", "aquarium123"); Serial.print("IP address:\t"); Serial.println(WiFi.softAPIP()); } else { //station part Serial.print("connecting to..."); Serial.println(ssid); WiFi.hostname(WiFiHostname); WiFi.mode(WIFI_STA); WiFi.begin(ssid.c_str(), pass.c_str()); /* while (WiFi.status() != WL_CONNECTED) { delay(500); Serial.print("."); } */ Serial.println(""); Serial.println("WiFi connected"); Serial.println("IP address: "); Serial.println(WiFi.localIP()); } server.on("/", handleRoot); server.on("/login", handleLogin); const char * headerkeys[] = {"Cookie"} ; size_t headerkeyssize = sizeof(headerkeys) / sizeof(char*); //ask server to track these headers server.collectHeaders(headerkeys, headerkeyssize); server.begin(); } void loop() { if (lastDataRead == 0 || (millis() - lastDataRead >= intervalDataRead * 1000)) { //host = scripturl.substring(0, scripturl.indexOf('/')); httpc.begin("http://" + scripturl + "?values=1"); //Specify request destination int httpCode = httpc.GET(); //Send the request if (httpCode > 0) { //Check the returning code String payload = httpc.getString(); //Get the request response payload Serial.println(payload); //Print the response payload String temperature1 = explode(';', payload, 0); //String temperature2 = explode(';', payload, 2); Serial.print(temperature1); //Serial.print(" "); //Serial.println(temperature2); lc.clearDisplay(0); auto printLED = [](unsigned char beginLEDsegment, String printString)->void { int i; for (i = 0; i < printString.length(); i++) { lc.setChar(0, beginLEDsegment - i, (printString[i] == '.') ? printString[i + 1] : printString[i], (printString[i + 1] == '.') ? true : false); if (printString[i] == '.') { break; } } //lc.setChar(0, beginLEDsegment - i - 1, 'C', false); lc.setRow(0, beginLEDsegment - i - 1, B01001110); //"C" }; printLED(7, String(temperature1)); //printLED(3, String(temperature2)); if (payload.length() > 4) { lastDataRead = millis(); } else { lastDataRead += 1000; } } httpc.end(); } server.handleClient(); } void handleRoot() { if (!is_authentified()) { server.sendHeader("Location", "/login"); server.sendHeader("Cache-Control", "no-cache"); server.send(301); return; } int i; if (server.hasArg("SSID") && server.arg("SSID") != "" && server.hasArg("wifipass") && server.arg("wifipass") != "") { ssid = server.arg("SSID"); pass = server.arg("wifipass"); for (i = 0; i < server.arg("SSID").length(); i++) { EEPROM.write(i + 100, server.arg("SSID")[i]); } i += 100; EEPROM.write(i, NULL); for (i = 0; i < server.arg("wifipass").length(); i++) { EEPROM.write(i + 101 + server.arg("SSID").length(), server.arg("wifipass")[i]); } //i += 3 + server.arg("wifipass").length(); EEPROM.write(server.arg("SSID").length() + server.arg("wifipass").length() + 101, NULL); EEPROM.commit(); } if (server.hasArg("scripturl") && server.arg("scripturl") != "") { scripturl = server.arg("scripturl"); for (i = 0; i < scripturl.length(); i++) { EEPROM.write(i + 102 + ssid.length() + pass.length(), scripturl[i]); } EEPROM.write(ssid.length() + pass.length() + scripturl.length() + 102, NULL); EEPROM.commit(); } if (server.arg("intervalDataRead").toInt() > 0) { intervalDataRead = server.arg("intervalDataRead").toInt(); EEPROM.put(0, intervalDataRead); EEPROM.commit(); } if (server.hasArg("SSID") && server.arg("SSID") != "" && server.hasArg("wifipass") && server.arg("wifipass") != "") { ESP.restart(); } String content = "<html><head><title>Henhouse</title></head><body>"; content += "<form action='/' method='POST'>Home WiFi Network Name (SSID): <input type='text' name='SSID' value='" + String(ssid) + "'><br>"; content += "Password: <input type='password' name='wifipass'><br>"; content += "URL to datalogging script (without http://): <input type='text' name='scripturl' id='scripturl' value='" + scripturl + "'><br>"; content += "Read values every [seconds]: <input type='text' name='intervalDataRead' value='" + String((int)intervalDataRead) + "'><br>"; content += "<script language=\"javascript\">function stringReplace() {var s = document.getElementById(\"scripturl\").value;var removeThis = /^(http?|https):\\/\\//;s = s.replace(removeThis, '');document.getElementById(\"scripturl\").value = s;}</script>"; content += "<input type='submit' value='Submit' onClick=\"stringReplace();\"></form><br>"; content += "</body></html>"; server.send(200, "text/html", content); } bool is_authentified() { Serial.println("Enter is_authentified"); if (server.hasHeader("Cookie")) { Serial.print("Found cookie: "); String cookie = server.header("Cookie"); Serial.println(cookie); String cookielogin = "WEATHERSTATION=" + md5(accesspassword); if (cookie.indexOf(cookielogin) != -1) { Serial.println("Authentification Successful"); return true; } } Serial.println("Authentification Failed"); return false; } void handleLogin() { String msg; if (server.hasHeader("Cookie")) { Serial.print("Found cookie: "); String cookie = server.header("Cookie"); Serial.println(cookie); } if (server.hasArg("DISCONNECT")) { Serial.println("Disconnection"); server.sendHeader("Location", "/login"); server.sendHeader("Cache-Control", "no-cache"); server.sendHeader("Set-Cookie", "WEATHERSTATION=0"); server.send(301); return; } if (server.hasArg("PASSWORD")) { if (server.arg("PASSWORD") == accesspassword) { server.sendHeader("Location", "/"); server.sendHeader("Cache-Control", "no-cache"); String cookielogin = "WEATHERSTATION=" + md5(accesspassword); server.sendHeader("Set-Cookie", cookielogin); server.send(301); Serial.println("Log in Successful"); return; } msg = "Wrong username/password! try again."; Serial.println("Log in Failed"); } String content = "<html><body><form action='/login' method='POST'>"; content += "Password:<input type='password' name='PASSWORD' placeholder='password'><br>"; content += "<input type='submit' name='SUBMIT' value='Submit'></form>" + msg + "<br>"; server.send(200, "text/html", content); } String md5(String str) { MD5Builder _md5; _md5.begin(); _md5.add(String(str)); _md5.calculate(); return _md5.toString(); } String explode(char character, String string, unsigned int position) { unsigned int i, ii; unsigned int counter = 0; for (i = 0; i < string.length(); ++i) { if (string[i] == character) { counter++; i++; } if (counter >= position) { break; } } for (ii = i + 1; ii < string.length(); ii++) { if (string[ii] == character) { break; } } return string.substring(i, ii); }
  4. Pomysł na ten projekt zrodził się w momencie, gdy zwykłe sterowanie oświetleniem (włącz/wyłącz) stało się nie wystarczające. Elementy Moduł ściemniacza ESP8266 12E Enkoder rotacyjny KY-040 Zasilacz Hi-Link HLK-PM01 Moduł z przetwornicą AMS1117 Moduł z BMP280 Budowa Do połączenia wszystkich elementów wykorzystałem zaprojektowane przez siebie płytki PCB, podzielone na sekcje zasilania oraz mikrokontrolera. W sekcji zasilania poza zasilaczem zastosowałem zabezpieczenia w postaci warystora (s10k250) oraz bezpiecznika rurkowego. W części z mikrokontrolerem wyprowadzone zostały wejścia: UART, GPIO0 (do aktualizacji), zasilanie modułu ściemniacza oraz sterowania tym modułem (pin detekcji zera sieci, sterowanie triakiem), wyjście na diodę kontrolną, wyjście RJ45 do fizycznego panelu sterowania, gniazdko elektryczne do podłączenia lampki. Całość została zamknięta w puszcze natynkowej. Do fizycznego sterowania użyty został enkoder przytwierdzony do drewnianej szkatułki w której umieszczono również czujnik BMP280. Drewniane pudełko połączone jest do „modułu centralnego” kablem Ethernetowym. Ogólna zasada działania modułu ściemniacza Sposób działania ściemniacza jest bardzo prosty. Prąd przemienny płynący w gniazdu elektrycznym ma postać podobną do sinusoidy. Jeżeli załączenie prądu nastąpi po upływie 30% czasu trwania każdej połówki sinusoidy to żarówka będzie świecić na 70% jasności, ponieważ tylko tyle energii zostanie jej przekazane. Za detekcję zera sieci (początek połówki sinusoidy) odpowiada optotraik umieszczony w module, w monecie przejścia przez zero wykonywane jest przerwanie w mikrokontrolerze, triak przepuszcza prąd przez określony czas. W wyliczeniach czasu muszą zastać uwzględnione między innymi częstotliwość prądu, czy wielkość każdego kroku przyciemnienia. Szczegółowo zasada działania podobnego ściemniacza została opisana w książce Mirosława Kardasia Mikrokontrolery AVR Język C Podstawy programowania. Do swojego celu wykorzystałem bibliotekę przygotowana przez twórców modułu. Funkcjonalności: Możliwość ustawienia intensywności świecenia podłączonej żarówki w przedziale 0-100%. Możliwość ustawienia wygaszenia światła po określonym czasie, ściemnianie następuje płynnie. Możliwość sprawdzenia temperatury otoczenia, a po poprawnej kalibracji również ciśnienia. Możliwość kontroli ściemniacza fizycznym pokrętłem (krok 5 punktów procentowych). Możliwość kontroli poprzez sieć lokalną. Szczegółowy stan urządzenia udostępniany jest w formacie XML. Implementacja casu pobieranego z serwerów NTP. Witryna zarządzania Strona internetowa powstała przy wykorzystaniu biblioteki jQuary oraz technologii Bootstrap w celu zachowania responsywności. Do sterowania poziomem światła służy suwak poczatkowo automatycznie ustawiany w pozycji aktualnej wartości, dodatkowo nad nim widoczny jest pasek postępu obrazujący aktualny stan jasności, zastosowany został w celu poprawienia widoczności w przypadku sterowania przy wykorzystaniu ekranów dodatkowych. Do precyzyjnego ustawiania wartości zamieszczone zostały przyciski „+” oraz „-”. W zakładce „ustawienia” użytkownik ma możliwość sprawdzenia szczegółowych danych o statusie urządzenia, zmiany częstotliwości pomiaru temperatury oraz wymuszenia aktualizacji czasu.
  5. Woltomierz, amperomierz, watomierz, termometr, higrometr, barometr, czujnik zewnętrznej termopary, luksomierz w jednym urządzeniu. Projekt typu "weź wszystko co masz pod ręką, podłącz i zaprogramuj, będzie fajnie!" Dane logować można z terminala portu szeregowego (w tym do pliku - Windows/Linux). Wyniki pomiarów prezentowane są również na stronie WWW, a odczyty odświeżają się na żywo dzięki zastosowaniu technologii AJAX. Część do pomiaru prądu przypomina mój poprzedni projekt multimetru, przy czym wykorzystałem dokładniejszy moduł oparty na INA226 zamiast INA219. Podobnie jak tam zasilacz dla mierzonego obwodu podłączamy z boku do gniazda DC, a odbiornik "karmi się" z gniazd bananowych 4 mm. /* I2C D4 - SCL D3 - SDA */ #include <ESP8266WiFi.h> #include <WiFiClient.h> #include <ESP8266WebServer.h> #include <Wire.h> #include <Adafruit_Sensor.h> #include <Adafruit_BME280.h> //#include <Adafruit_INA219.h> #include <INA226.h> //https://github.com/jarzebski/Arduino-INA226 #include <BH1750.h> //https://github.com/claws/BH1750 #include "max6675.h" //https://github.com/adafruit/MAX6675-library #include <LiquidCrystal_I2C.h> char* ssid = "Arduino Multimeter"; //const char *password = ""; //http://github.com/adafruit/MAX6675-library/issues/9#issuecomment-168213845 const int thermoDO = D6; const int thermoCS = D7; const int thermoCLK = D8; ESP8266WebServer server(80); Adafruit_BME280 bme; //Adafruit_INA219 ina219; INA226 ina226; BH1750 lightMeter; MAX6675 thermocouple(thermoCLK, thermoCS, thermoDO); LiquidCrystal_I2C lcd(0x27, 20, 4); unsigned long lastTimeLCD = 0; void setup() { Serial.begin(9600); Wire.begin(D3, D4); Wire.setClock(100000); if (!bme.begin(0x76)) //changed from default I2C adress 0x77 { Serial.println("Nie odnaleziono czujnika BMP085 / BMP180"); while (1) { } } uint32_t currentFrequency; //ina219.begin(); // Default INA226 address is 0x40 ina226.begin(); // Configure INA226 ina226.configure(INA226_AVERAGES_1, INA226_BUS_CONV_TIME_1100US, INA226_SHUNT_CONV_TIME_1100US, INA226_MODE_SHUNT_BUS_CONT); // Calibrate INA226. Rshunt = 0.01 ohm, Max excepted current = 4A ina226.calibrate(0.01 * 1.318, 3); lightMeter.begin(BH1750::CONTINUOUS_HIGH_RES_MODE); IPAddress apIP(192, 168, 1, 1); WiFi.softAPConfig(apIP, apIP, IPAddress(255, 255, 255, 0)); // WiFi.softAP(ssid, password); WiFi.softAP(ssid); server.on("/", handleRoot); server.on("/sensors", handleSensors); server.begin(); lcd.begin(); // lcd.backlight(); } void loop() { server.handleClient(); if (lastTimeLCD == 0 || millis() - lastTimeLCD >= 500) { lastTimeLCD = millis(); lcd.clear(); /* lcd.print(ina219.getBusVoltage_V()); lcd.print("V "); lcd.print(ina219.getCurrent_mA()); lcd.print("mA "); lcd.print(ina219.getPower_mW()); lcd.print("mW"); lcd.setCursor(0, 1); */ float volt = ina226.readBusVoltage(); float amp = 1000.0 * ina226.readShuntCurrent(); float power = 1000.0 * ina226.readBusPower(); int lux = lightMeter.readLightLevel(); float tempThero = thermocouple.readCelsius(); float temp = bme.readTemperature(); float hum = bme.readHumidity(); float pres = bme.readPressure(); lcd.print(volt); lcd.print("V "); lcd.print(amp, 1); lcd.print("mA "); lcd.setCursor(0, 1); lcd.print(power, 0); lcd.print("mW "); lcd.print(lux); lcd.print(" lx "); lcd.print(tempThero); lcd.print("C"); lcd.setCursor(0, 2); lcd.print(temp, 1); lcd.print("C "); lcd.print(hum, 1); lcd.print("% "); lcd.print(pres, 0); lcd.print("Pa"); Serial.println(String(volt) + "V " + String(amp, 1) + "mA " + String(power, 0) + "mW " + String(lux) + " lx " + String(tempThero) + "C " + String(temp, 1) + "C " + String(hum, 1) + "% " + String(pres, 0) + "Pa"); } } void handleRoot() { String content = "<html> <head><title>Ardunio Multimeter</title></head><body>"; content += "<DIV style=\"display:table; font-size: large;\"><DIV style=\"border-style: solid;\">BME280:<BR>Temperature: <span id=\"tempBME\"></span>C / <span id=\"tempBMEF\"></span>F<br>Humidity: <span id=\"humBME\"></span>%<br>Pressure: <span id=\"presBME\"></span>Pa<br></DIV><DIV style=\"border-style: solid;\">INA219:<BR>Voltage: <span id=\"voltage\"></span>V<br>Current: <span id=\"current\"></span>mA<br>Power: <span id=\"power\"></span>mW<br></DIV> <DIV style=\"border-style: solid;\">BH1750:<BR>Illuminance: <span id=\"illuminance\"></span>lx</DIV> <DIV style=\"border-style: solid;\">MAX6675:<BR>Temperature: <span id=\"thermocouple\"></span>C / <span id=\"thermocoupleF\"></span>F</DIV></DIV>"; content += "<script>myTimer();var myVar = setInterval(myTimer, 1000);function myTimer() {var xhttp = new XMLHttpRequest(); xhttp.onreadystatechange = function() { if (this.readyState == 4 && this.status == 200) { var str = this.responseText; var values = str.split(\";\"); document.getElementById(\"tempBME\").innerHTML = values[0]; document.getElementById(\"tempBMEF\").innerHTML = (values[0]*9/5 + 32).toFixed(2); document.getElementById(\"humBME\").innerHTML = values[1]; document.getElementById(\"presBME\").innerHTML = values[2]; document.getElementById(\"voltage\").innerHTML = values[3]; document.getElementById(\"current\").innerHTML = values[4]; document.getElementById(\"power\").innerHTML = values[5]; document.getElementById(\"illuminance\").innerHTML = values[6]; document.getElementById(\"thermocouple\").innerHTML = values[7]; document.getElementById(\"thermocoupleF\").innerHTML = (values[7]*9/5 + 32).toFixed(2);} }; xhttp.open(\"GET\", \"sensors\", true); xhttp.send();}</script>"; content += "</body></html>"; server.send(200, "text/html", content); } void handleSensors() { //String content = String(bme.readTemperature()) + ";" + String(bme.readHumidity()) + ";" + String(bme.readPressure()) + ";" + String(ina219.getBusVoltage_V()) + ";" + String(ina219.getCurrent_mA()) + ";" + String(ina219.getPower_mW()) + ";" + String(lightMeter.readLightLevel()) + ";" + String(thermocouple.readCelsius()) + ";"; String content = String(bme.readTemperature()) + ";" + String(bme.readHumidity()) + ";" + String(bme.readPressure()) + ";" + String(ina226.readBusVoltage()) + ";" + String(1000.0 * ina226.readShuntCurrent()) + ";" + String(1000.0 * ina226.readBusPower()) + ";" + String(lightMeter.readLightLevel()) + ";" + String(thermocouple.readCelsius()) + ";"; server.send(200, "text/plain", content); } Z zestawem łączy się prymitywna aplikacja napisana w C++. Potrafi zapisywać pomiary do pliku tekstowego i baz danych MySQL oraz SQLite. Jej pracę kończymy w bardzo "brutalny" sposób: Ctrl + C (podobnie, jak w loggerze dla poprzedniego multimetru). Przy pisaniu skorzystałem z poradnika (uwaga, strona zawiera dziwne, procesorożerne skrypty, które mogą nawet zawiesić przeglądarkę, radzę zatrzymać jej ładowanie [przycisk "X"] tuż po otwarciu) Server and client example with C sockets on Linux. Kompilacja: g++ webclient2mysql.cpp -L/usr/lib/mysql -lmysqlclient -l sqlite3 -o webclient2mysql -std=c++17 Jako parametry wywoływanego programu podajemy adres strony www urządzenia z wartościami pomiarów oraz interwał czasu (w sekundach), z jakim mają być zapisywane pomiary, np: ./webclient2mysql http://192.168.1.1/sensors 5 //g++ webclient2mysql.cpp -L/usr/lib/mysql -lmysqlclient -l sqlite3 -o webclient2mysql -std=c++17 //www.binarytides.com/server-client-example-c-sockets-linux/ /* #define DB_USER "" #define DB_PASSWORD "" #define DB_HOST "" #define DB_PORT 3307 #define DB_NAME "" */ #define DB_TABLE_NAME "logeddata" #define DB_SQLite_FILE "database.db" #define LOG_FILE "log.txt" #define CSV_DELIMITER ';' #define CSV_DELIMITER_COUNT 8 //Quantity of delimiters in one CSV row. #include <stdio.h> //printf #include <string.h> //strlen #include <sys/socket.h> //socket #include <arpa/inet.h> //inet_addr #include <unistd.h> #include <netdb.h> #include <iostream> #include <string> #include <vector> #include <chrono> #include <ctime> #include <thread> // std::this_thread::sleep_for #include <regex> #ifdef DB_HOST #include <mysql/mysql.h> //libmysqlclient-dev #endif #ifdef DB_SQLite_FILE #include <sqlite3.h> //sudo apt-get install sqlite3 libsqlite3-dev #endif #ifdef LOG_FILE #include <fstream> #endif const std::vector<std::string> explode(const std::string& s, const char& c); const std::string variables[][2] = { {"temperature", "float"}, {"humidity", "float"}, {"pressure", "float"}, {"voltage", "float"}, {"current", "float"}, {"power", "float"}, {"luminosity", "float"}, {"temperaturethermocouple", "float"}}; int main(int argc, char *argv[]) { if (argc != 3) { puts("Usage: ./webclient device_webpage_URL time_interval"); return 0; } const std::string s = argv[1]; std::smatch match; std::regex rgx("\/\/(.+?)\/"); std::string hostname, port; if (std::regex_search(s.begin(), s.end(), match, rgx)) { hostname = std::string(match[1]); if (hostname.find(":") != std::string::npos) { port = hostname.substr(hostname.find(":") + 1); hostname = hostname.substr(0, hostname.find(":")); } else { port = "80"; } } rgx = "\/\/.+?(\/.+)"; std::string path; if (std::regex_search(s.begin(), s.end(), match, rgx)) path = std::string(match[1]); std::cout << hostname << " " << port << " " << path << "\n"; int sock; struct sockaddr_in server; char server_reply[2000]; #ifdef DB_HOST MYSQL mysql; mysql_init(&mysql); if(mysql_real_connect(&mysql, DB_HOST, DB_USER, DB_PASSWORD, DB_NAME, DB_PORT, NULL, 0)) printf("Connected to database!\n"); else printf("Can't connect to database: %d, %s\n", mysql_errno(&mysql), mysql_error(&mysql)); #endif #ifdef DB_SQLite_FILE char * error = 0; int rc; sqlite3 * db; sqlite3_stmt * steatment; sqlite3_open(DB_SQLite_FILE, & db); #endif #ifdef LOG_FILE std::ofstream myfile; myfile.open(LOG_FILE, std::ios::out | std::ios::app); #endif std::string query; #ifdef DB_HOST query = "CREATE TABLE IF NOT EXISTS " + std::string(DB_TABLE_NAME) +" (ID int NOT NULL AUTO_INCREMENT,"; for(int i = 0; i < sizeof(variables) / sizeof(variables[0]); i++) { query += variables[i][0] + " " + variables[i][1] + ","; } query += "date_time datetime NOT NULL DEFAULT CURRENT_TIMESTAMP, PRIMARY KEY (ID))"; std::cout << query << std::endl; mysql_query(&mysql, query.c_str()); #endif #ifdef DB_SQLite_FILE query = "CREATE TABLE IF NOT EXISTS " + std::string(DB_TABLE_NAME) +"(ID integer primary key,"; for(int i = 0; i < sizeof(variables) / sizeof(variables[0]); i++) { query += variables[i][0] + " " + variables[i][1] + ","; } query += "date_time datetime NOT NULL DEFAULT CURRENT_TIMESTAMP)"; sqlite3_exec(db, query.c_str(), 0, 0, & error); // std::cout << error; #endif query = ""; while (1) { //Create socket sock = socket(AF_INET, SOCK_STREAM, 0); if (sock == -1) { printf("Could not create socket"); } puts("Socket created"); server.sin_addr.s_addr = inet_addr(hostname.c_str()); server.sin_family = AF_INET; server.sin_port = htons(stoi(port)); //Connect to remote server if (connect(sock, (struct sockaddr *)&server, sizeof(server)) < 0) { perror("connect failed. Error"); return 1; } puts("Connected\n"); std::string message = "GET " + path + " HTTP/1.1\r\nHost: " + hostname + "\r\n\r\n"; puts(message.c_str()); //Send some data if (send(sock, message.c_str(), strlen(message.c_str()), 0) < 0) { puts("Send failed"); return 1; } std::string httpContent = ""; char cur; while ( read(sock, &cur, 1) > 0 ) { //Receive a reply from the server httpContent += cur; } httpContent = httpContent.substr(httpContent.find("\r\n\r\n") + 4); std::time_t currentTime = std::time(nullptr); std::vector<std::string> results; results = explode(httpContent, CSV_DELIMITER); std::cout << results.size() << "\n"; if(results.size() == CSV_DELIMITER_COUNT){ std::cout << currentTime << " " << httpContent << "\n"; #ifdef LOG_FILE myfile << currentTime << " "; for (int i = 0; i < sizeof(variables) / sizeof(variables[0]); i++){ myfile << " " << results[i]; } myfile << " " << std::endl; #endif query = "INSERT INTO " + std::string(DB_TABLE_NAME) + " ("; for (int i = 0; i < sizeof(variables) / sizeof(variables[0]); i++){ query += variables[i][0]; if(i < sizeof(variables) / sizeof(variables[0]) - 1) {query += ",";} } query += ") VALUES ("; for (int i = 0; i < sizeof(variables) / sizeof(variables[0]); i++){ query += "'" + results[i] + "'"; if(i < sizeof(variables) / sizeof(variables[0]) - 1) {query += ",";} } query += ")"; // query = "INSERT INTO " + std::string(DB_TABLE_NAME) + " (temperature, humidity, pressure" + ") VALUES ("+ "'" + std::string(results[0]) +"'," + "'" + std::string(results[1]) +"',"+ "'" + std::string(results[2]) +"'" +")"; #ifdef DB_HOST mysql_query(&mysql, query.c_str()); #endif #ifdef DB_SQLite_FILE sqlite3_exec(db, query.c_str(), 0, 0, & error); //std::cout << error; #endif } close(sock); std::this_thread::sleep_for(std::chrono::seconds(atoi(argv[2]))); } #ifdef LOG_FILE myfile.close(); #endif #ifdef DB_HOST mysql_close(&mysql); #endif #ifdef DB_SQLite_FILE sqlite3_close( db ); #endif return 0; } const std::vector<std::string> explode(const std::string& s, const char& c) //http://www.cplusplus.com/articles/2wA0RXSz/ { std::string buff{""}; std::vector<std::string> v; for(auto n:s) { if(n != c) buff+=n; else if(n == c && buff != "") { v.push_back(buff); buff = ""; } } if(buff != "") v.push_back(buff); return v; } Podgląd bazy SQLite z wynikami pomiarów (SQLite Browser) Składniki: ESP8266 wyświetlacz LCD 4x20 z konwerterem I2C LCM1602 moduł miernika napięcia i natężenia prądu z magistralą I²C INA226 BME280 czujnik termopary MAX6675 czujnik natężenia światła BH1750 bezpiecznik (zastosowałem PPTC 3A) gniazdo oraz wtyki bananowe 4 mm gniazdo DC 5.5/2.1 mm
  6. Kolega Robert (SP9-10126-KR) poprosił mnie o wykonanie sterownika do akwarium. Założeniem była "symulacja dnia" (płynne rozjaśnianie i ściemnianie oświetlenia LED o świcie i zmierzchu) oraz monitorowanie temperatury wody z alarmem (w razie awarii fabrycznej grzałki z termostatem do akwariów). Pracę urządzenia można doglądać za pomocą strony WWW. Parametry odświeżają się na żywo dzięki zastosowaniu technologii AJAX. Jeżeli temperatura nie znajdzie się w ustawionym przedziale zostanie włączony alarm. Czas synchronizowany jest automatycznie korzystając z serwera NTP. Ciekowostką jest możliwość konfiguracji nazywy SSID sieci WiFi i hasła do niej przez samego użytkownika poprzez stronę sterująca. Standardowo urządzenie łączy się z siecią WiFi. W przypadku, gdy zapamiętana konfiguracja jest niezgodna z parametrami sieci domowej, wystarczy nacisnąć i przytrzymać przycisk przed podłączeniem do prądu. ESP8266 uruchomi się w trybie Access Pointa, z którym będziemy mogli się połączyć i na stronie http://192.168.1.1 dokonamy poprawek. Po podłączeniu do sieci domowej WiFi możemy też nacisnąć wspomniany przycisk w trakcie pracy urządzenia, co pozwoli na wyświetlenie uzyskanego z DHCP IP. Ułatwi to wejście na stronę konfiguracyjną. Przy uruchomieniu następuje odczyt ustawień sieci #include <EEPROM.h> String ssid = "", pass = ""; [...] setup(){ if (ssid == "") { int i; for (i = 100; i < 170; i++) { if (EEPROM.read(i) == NULL) { break; } ssid += char(EEPROM.read(i)); } for (int ii = i + 1; ii < 250; ii++) { if (EEPROM.read(ii) == NULL) { break; } pass += char(EEPROM.read(ii)); } } WiFi.mode(WIFI_STA); WiFi.begin(ssid.c_str(), pass.c_str()); [...] server.on("/", handleRoot); server.on("/sensors", handleSensors); server.begin(); } Za odbieranie danych z formularza WWW i zapisywanie ich w pamięci odpowiada void handleRoot() { [...] if (server.hasArg("SSID") && server.arg("SSID") != "" && server.hasArg("wifipass") && server.arg("wifipass") != "") { for (i = 0; i < server.arg("SSID").length(); i++) { EEPROM.write(i + 100, server.arg("SSID")[i]); } i += 100; EEPROM.write(i, NULL); for (i = 0; i < server.arg("wifipass").length(); i++) { EEPROM.write(i + 101 + server.arg("SSID").length(), server.arg("wifipass")[i]); } EEPROM.write(server.arg("SSID").length() + server.arg("wifipass").length() + 101, NULL); EEPROM.commit(); ESP.restart(); } } Cały kod wygląda następująco: #include <ESP8266WiFi.h> #include <WiFiClient.h> #include <ESP8266WebServer.h> #include <EEPROM.h> #include <WiFiUdp.h> #include <Time.h> // http://www.pjrc.com/teensy/td_libs_Time.html #include <ESP8266mDNS.h> #include <OneWire.h> //https://github.com/adafruit/ESP8266-Arduino/tree/esp8266/libraries/OneWire #include <DallasTemperature.h> //https://github.com/milesburton/Arduino-Temperature-Control-Library #include <Wire.h> #include <LiquidCrystal_I2C.h> //https://github.com/fdebrabander/Arduino-LiquidCrystal-I2C-library #define PWMpin 15 //(D8) #define WiFimodeButton 12 //D6 #define PumpPin 13 //(D7) #define TempAlarmPin 14 //(D5) #define ONE_WIRE_BUS 4 //D2 /*char ssid[] = ""; // your network SSID (name) char pass[] = ""; // your network password */ String ssid = "", pass = ""; const char* WiFiHostname = "aquarium"; unsigned char screenCount = 0, tmBegin, tmEnd, dusk, pumpBegin, pumpEnd, tempMin, tempMax; bool lastTimeLCD, tempAlarm, lastButtonStatus = HIGH; unsigned int localPort = 2390; // local port to listen for UDP packets int PWM = 0; float temp = 0; /* Don't hardwire the IP address or we won't get the benefits of the pool. Lookup the IP address for the host name instead */ //IPAddress timeServer(129, 6, 15, 28); // time.nist.gov NTP server IPAddress timeServerIP; // time.nist.gov NTP server address const char* ntpServerName = "time.nist.gov"; 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 WiFiUDP udp; ESP8266WebServer server(80); OneWire oneWire(ONE_WIRE_BUS); DallasTemperature sensors(&oneWire); LiquidCrystal_I2C lcd(0x27, 16, 2); tmElements_t tm; time_t getNtpTime(); unsigned long last_time_sync = 0, last_NTP_check = 0; void setup() { pinMode(PWMpin, OUTPUT); pinMode(WiFimodeButton, INPUT_PULLUP); pinMode(TempAlarmPin, OUTPUT); Serial.begin(9600); EEPROM.begin(512); Wire.begin(0, 2); //D2, D4 Wire.setClock(100000); sensors.begin(); lcd.begin(); lcd.backlight(); tmBegin = EEPROM.read(0); tmEnd = EEPROM.read(1); dusk = EEPROM.read(2); pumpBegin = EEPROM.read(3); pumpEnd = EEPROM.read(4); tempAlarm = EEPROM.read(5); tempMin = EEPROM.read(6); tempMax = EEPROM.read(7); // We start by connecting to a WiFi network // WiFi.mode(WIFI_AP_STA); WiFi.softAPdisconnect(); WiFi.disconnect(); if (ssid == "") { int i; for (i = 100; i < 170; i++) { if (EEPROM.read(i) == NULL) { break; } ssid += char(EEPROM.read(i)); } for (int ii = i + 1; ii < 250; ii++) { if (EEPROM.read(ii) == NULL) { break; } pass += char(EEPROM.read(ii)); } } if (digitalRead(WiFimodeButton) == LOW) { //access point part Serial.println("Creating Accesspoint"); IPAddress apIP(192, 168, 1, 1); WiFi.mode(WIFI_AP); WiFi.softAPConfig(apIP, apIP, IPAddress(255, 255, 255, 0)); WiFi.softAP("aquarium", "aquarium123"); Serial.print("IP address:\t"); Serial.println(WiFi.softAPIP()); } else { //station part Serial.print("connecting to..."); Serial.println(ssid); WiFi.hostname(WiFiHostname); WiFi.mode(WIFI_STA); WiFi.begin(ssid.c_str(), pass.c_str()); /* while (WiFi.status() != WL_CONNECTED) { delay(500); Serial.print("."); } */ Serial.println(""); Serial.println("WiFi connected"); Serial.println("IP address: "); Serial.println(WiFi.localIP()); } /* MDNS.begin("aquarium"); MDNS.addService("http", "tcp", 80);*/ Serial.println("Starting UDP"); udp.begin(localPort); Serial.print("Local port: "); Serial.println(udp.localPort()); server.on("/", handleRoot); server.on("/sensors", handleSensors); server.begin(); } void loop() { Serial.println(ssid); Serial.println(pass); Serial.println(WiFi.status()); if (millis() - last_time_sync > 10 * 24 * 60 * 60 * 1000 || last_time_sync == 0) { unsigned long unixt = unix_time(); if (unixt > 1512876158) { setTime(unixt); last_time_sync = millis(); } } Serial.print("Time library "); Serial.print(current_time(0)); breakTime(now(), tm); if (now() < 1512876158) { PWM = 0; } else if (tmBegin < tmEnd && tm.Hour >= tmBegin && tm.Hour < tmEnd || tmBegin > tmEnd && (tm.Hour >= tmBegin || tm.Hour < tmEnd)) { if (tm.Hour * 60 + tm.Minute < tmBegin * 60 + dusk || tm.Hour * 60 + tm.Minute < tmEnd * 60 - dusk) { PWM = 1023 - map(tmBegin * 60 * 60 + dusk * 60 - (tm.Hour * 60 * 60 + tm.Minute * 60 + tm.Second), 0, dusk * 60, 0, 1023); } if (tm.Hour * 60 + tm.Minute > tmEnd * 60 - dusk) { if (tmBegin < tmEnd) { PWM = 1023 - map(tm.Hour * 60 * 60 + tm.Minute * 60 + tm.Second - (tmEnd * 60 * 60 - dusk * 60), 0, dusk * 60, 0, 1023); } else { PWM = map(tmEnd * 60 * 60 - (tm.Hour * 60 * 60 + tm.Minute * 60 + tm.Second), 0, dusk * 60, 0, 1023); } } if (tm.Hour * 60 + tm.Minute >= tmBegin * 60 + dusk && tm.Hour * 60 + tm.Minute <= tmEnd * 60 - dusk || tmBegin > tmEnd && (tm.Hour * 60 + tm.Minute > tmBegin * 60 + dusk || tm.Hour * 60 + tm.Minute < tmEnd * 60 - dusk)) { PWM = 1023; } } /*else if (tmBegin < tmEnd && tm.Hour > tmBegin && tm.Hour < tmEnd || tmBegin > tmEnd && (tm.Hour > tmBegin || tm.Hour < tmEnd)) { PWM = 1023; } else if (tm.Hour == tmBegin) { PWM = map(tm.Minute, 0, 59, 0, 1023); } else if (tm.Hour == tmEnd) { PWM = map(59 - tm.Minute, 0, 59, 0, 1023); }*/ else { PWM = 0; } analogWrite(PWMpin, PWM); Serial.print(" "); Serial.println(PWM); server.handleClient(); if (now() % 2 != lastTimeLCD) { lastTimeLCD = now() % 2; sensors.requestTemperatures(); if (digitalRead(WiFimodeButton) == LOW && lastButtonStatus == HIGH && WiFi.status() == WL_CONNECTED) { screenCount++; } lastButtonStatus = digitalRead(WiFimodeButton); temp = sensors.getTempCByIndex(0); if (tempAlarm && (temp < (float)tempMin || temp > (float)tempMax)) { digitalWrite(TempAlarmPin, HIGH); } else { digitalWrite(TempAlarmPin, LOW); } if (tm.Hour >= pumpBegin && tm.Hour < pumpEnd) { digitalWrite(PumpPin, HIGH); } else { digitalWrite(PumpPin, LOW); } lcd.clear(); switch (screenCount % 2) { case 0: lcd.print(current_time(1)); lcd.setCursor(0, 1); lcd.print(temp, 1); lcd.print("C Day"); lcd.print(tmBegin); lcd.print("-"); lcd.print(tmEnd); break; case 1: lcd.print(WiFi.localIP().toString()); break; } } } void handleRoot() { int i; if (server.hasArg("begin") && server.arg("begin").toInt() >= 0 && server.arg("begin").toInt() <= 24 && server.hasArg("end") && server.arg("end").toInt() >= 0 && server.arg("end").toInt() <= 24 && server.hasArg("dusk") && server.arg("dusk").toInt() >= 0 && server.arg("dusk").toInt() <= 255) { tmBegin = server.arg("begin").toInt(); tmEnd = server.arg("end").toInt(); dusk = server.arg("dusk").toInt(); EEPROM.write(0, tmBegin); EEPROM.write(1, tmEnd); EEPROM.write(2, dusk); if (server.hasArg("pumpBegin") && server.arg("pumpBegin").toInt() >= 0 && server.arg("pumpBegin").toInt() <= 24 && server.hasArg("pumpEnd") && server.arg("pumpEnd").toInt() >= 0 && server.arg("pumpEnd").toInt() <= 24) { pumpBegin = server.arg("pumpBegin").toInt(); pumpEnd = server.arg("pumpEnd").toInt(); EEPROM.write(3, pumpBegin); EEPROM.write(4, pumpEnd); } // if (server.arg("tempMin").toInt() >= 0 && server.arg("tempMin").toInt() <= 100 && server.hasArg("tempMax") && server.arg("tempMax").toInt() >= 0 && server.arg("tempMax").toInt() <= 100 && server.arg("tempMax").toInt() > server.arg("tempMin").toInt()) if (server.arg("tempMin").toInt() >= 0 && server.arg("tempMin").toInt() <= 100 && server.arg("tempMax").toInt() >= 0 && server.arg("tempMax").toInt() <= 100 && server.arg("tempMax").toInt() > server.arg("tempMin").toInt()) { tempAlarm = server.arg("tempAlarm").toInt(); tempMin = server.arg("tempMin").toInt(); tempMax = server.arg("tempMax").toInt(); EEPROM.write(5, tempAlarm); EEPROM.write(6, tempMin); EEPROM.write(7, tempMax); } EEPROM.commit(); } if (server.hasArg("SSID") && server.arg("SSID") != "" && server.hasArg("wifipass") && server.arg("wifipass") != "") { for (i = 0; i < server.arg("SSID").length(); i++) { EEPROM.write(i + 100, server.arg("SSID")[i]); } i += 100; EEPROM.write(i, NULL); for (i = 0; i < server.arg("wifipass").length(); i++) { EEPROM.write(i + 101 + server.arg("SSID").length(), server.arg("wifipass")[i]); } //i += 3 + server.arg("wifipass").length(); EEPROM.write(server.arg("SSID").length() + server.arg("wifipass").length() + 101, NULL); EEPROM.commit(); ESP.restart(); } String content = "<html><head><meta http-equiv=\"Content-Type\" content=\"text/html; charset=UTF-8\"><title>Aquarium controller</title></head><body>"; /* for (i = 2; i < 100; i++) { if (EEPROM.read(i) == 5) { break; } content += char(EEPROM.read(i)); } content += ";"; for (int ii = i + 1; ii < 150; ii++) { if (EEPROM.read(ii) == 5) { break; } content += char(EEPROM.read(ii)); } */ content += "<DIV style=\"display:table; font-size: large; border-style: solid;\">Current time: <span id=\"time\"></span><BR>Temperature: <span id=\"temp\"></span>°C<BR>PWM: <span id=\"PWM\"></span>%<br>"; // content += "<div id=\"refresh\"> </div><script>var myVar = setInterval(myTimer, 1000); function myTimer() {var xhttp = new XMLHttpRequest(); xhttp.onreadystatechange = function() { if (this.readyState == 4 && this.status == 200) { document.getElementById(\"refresh\").innerHTML = this.responseText;} }; xhttp.open(\"GET\", \"sensors\", true); xhttp.send();}</script>"; content += "\"Artificial day\" from " + String((int)tmBegin) + ":00 to " + String((int)tmEnd) + ":00. Dusk duration: " + String((int)dusk) + " minutes.</DIV>"; content += "<script>myTimer();var myVar = setInterval(myTimer, 1000);function myTimer() {var xhttp = new XMLHttpRequest(); xhttp.onreadystatechange = function() { if (this.readyState == 4 && this.status == 200) { var str = this.responseText; var values = str.split(\";\"); document.getElementById(\"time\").innerHTML = values[0]; document.getElementById(\"temp\").innerHTML = values[1]; document.getElementById(\"PWM\").innerHTML = values[2]; } }; xhttp.open(\"GET\", \"sensors\", true); xhttp.send();} var i = 0;</script>"; content += "<form action='/' method='POST'>Begin: <input type='number' pattern='[0-9]' maxlength='2' size='2' name='begin' value='" + String((int)tmBegin) + "'> UTC<br>"; content += "End: <input type='number' pattern='[0-9]' maxlength='2' size='2' name='end' value='" + String((int)tmEnd) + "'> UTC<br>"; content += "Dusk: <input type='number' pattern='[0-9]' maxlength='2' size='2' name='dusk' value='" + String((int)dusk) + "'> minutes<br>"; content += "Air pump operates between <input type='number' pattern='[0-9]' maxlength='2' size='2' name='pumpBegin' value='" + String((int)pumpBegin) + "'> and <input type='number' pattern='[0-9]' maxlength='2' size='2' name='pumpEnd' value='" + String((int)pumpEnd) + "'><br>"; content += "<input type=\"checkbox\" name=\"tempAlarm\" value=\"1\""; if (tempAlarm) { content += " checked"; } content += ">TempAlarm <input type='number' pattern='[0-9]' maxlength='2' size='2' name='tempMin' value='" + String((int)tempMin) + "'> - <input type='number' pattern='[0-9]' maxlength='2' size='2' name='tempMax' value='" + String((int)tempMax) + "'><br>"; content += "<a href=\"\" onclick=\"i = i+1; if(i % 2 == 0) {wifi.style.display = 'none';} else {wifi.style.display = 'block';} return false;\">Change WiFi settings</a>"; content += "<div id=\"wifi\" style=\"display:none;\">Home WiFi Network Name (SSID): <input type='text' name='SSID' value='" + String(ssid) + "'><br>"; content += "Password: <input type='password' name='wifipass'></div><br>"; content += "<input type='submit' value='Submit'></form> <br>"; content += "</body></html>"; server.send(200, "text/html", content); } void handleSensors() { String content = current_time(0) + ";" + String(temp) + ";" + String(100.0 * PWM / 1023.0); server.send(200, "text/html", content); } String current_time(bool shortstring) { breakTime(now(), tm); String timeStr; if (!shortstring) { if (tm.Year > 0) { tm.Year -= 30; } else { tm.Year = 0; } } timeStr = String(tm.Day) + "." + String(tm.Month); if (!shortstring) { timeStr += "." + String(tm.Year); } timeStr += " " + String(tm.Hour) + ":"; if (tm.Minute < 10) { timeStr += "0"; } timeStr += String(tm.Minute) + ":"; if (tm.Second < 10) { timeStr += "0"; } timeStr += String(tm.Second) + "UTC"; return timeStr; } // send an NTP request to the time server at the given address //https://github.com/esp8266/Arduino/blob/master/libraries/ESP8266WiFi/examples/NTPClient/NTPClient.ino unsigned long sendNTPpacket(IPAddress& address) { //Serial.println("sending NTP packet..."); // 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(); } time_t unix_time() { if (WiFi.status() == WL_CONNECTED && ((unsigned long)(millis() - last_NTP_check) > 15000 || last_time_sync == 0)) { last_NTP_check = millis(); //get a random server from the pool WiFi.hostByName(ntpServerName, timeServerIP); sendNTPpacket(timeServerIP); // send an NTP packet to a time server // wait to see if a reply is available delay(1000); int cb = udp.parsePacket(); if (!cb) { //Serial.println("no packet yet"); } else { //Serial.print("packet received, length="); //Serial.println(cb); // 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; //Serial.print("Seconds since Jan 1 1900 = " ); //Serial.println(secsSince1900); // now convert NTP time into everyday time: Serial.println("Unix time sync "); // 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 Unix time: if (epoch > 1512876158) { return epoch + 2; } else { return 0; } } } } Składniki: ESP8266 wodoodporny termometr oparty na układzie DS18B20 wyświetlacz LCD 2x16 z konwerterem I2C LCM1602 przetwornica step-down LM2596 zapewniająca zasilanie 3.3V dla elektroniki, konwertując z 12V na wejściu N-kanałowy tranzystor MOSFET do sterowania PWM oświetleniem LED buzzer
  7. Cześć, dziś pokażę Wam, w jaki sposób złożyć do kupy moduł ESP8266 (w tym wypadku płytkę WeMos D1 mini) oraz matrycę LED na sterowniku MAX7219. Oprócz tego będziemy potrzebować paru przewodów połączeniowych, lutownicy z cyną, obudowy do zegarka oraz przewód microUSB do zasilenia naszego układu. Całość projektu jest dostępna w repozytorium na moim Githubie. Obudowa Jako obudowę użyłem sklejki o grubości 6 milimetrów, którą zamówiłem na aukcji Allegro razem z docięciem. Całość obudowy z dostawą kosztowała mnie mniej niż 40 złotych. Zaprojektowałem obudowę korzystając z programu Inkscape. Następnie została ona sklejona i pokryta lakierobejcą w kolorze jasnego dębu. Na front obudowy nakleiłem mleczną plexi o grubości 3 mm, którą dostałem w lokalnym sklepie za 5 złotych. Elektronika Tutaj będzie dużo łatwiej, gdyż wystarczy połączyć tylko odpowiednie piny. Połączyłem zgodnie z adnotacją w bibliotekach do obsługi MAX7219. Należy pamiętać, że WeMos D1 operuje na napięciu 3.3V na szczęście sterownik MAX7219 akceptuje je i nie będzie problemu aby układ ruszył. Na zdjęciu połączenie za pomocą przewodów z haczykami. Kod programu Przed kompilacją programu, należy dodać do Arduino IDE obsługę płytek opartych na ESP8266 (opisał to SOYER w tym temacie). Będziemy potrzebować trzech bibliotek, które nie są standardowo dołączone do Arduino IDE: MD_MAX72XX - dzięki której nasz procesor skomunikuje się ze sterownikiem MAX7219 MD_Parola - biblioteka rozszerzająca funkcje MD_MAX72XX NTPClient - dzięki niej zyskamy możliwość pobierania czasu z Internetu bez potrzeby używania modułu RTC. Po dodaniu bibliotek, kod wygląda następująco: #include <MD_Parola.h> #include <MD_MAX72xx.h> #include <SPI.h> #include <ESP8266WiFi.h> #include <NTPClient.h> #include <ESP8266WebServer.h> #include <WiFiUdp.h> #define HARDWARE_TYPE MD_MAX72XX::FC16_HW #define MAX_DEVICES 4 #define CLK_PIN D5 // or SCK #define DATA_PIN D7 // or MOSI #define CS_PIN D8 // or SS MD_Parola P = MD_Parola(HARDWARE_TYPE, CS_PIN, MAX_DEVICES); WiFiUDP ntpUDP; const char *ssid = ""; const char *password = ""; NTPClient timeClient(ntpUDP, "0.pl.pool.ntp.org", 3600, 60000); WiFiServer server(80); uint8_t frameDelay = 25; textEffect_t scrollEffect = PA_SCROLL_LEFT; #define BUF_SIZE 512 char curMessage[BUF_SIZE]; char newMessage[BUF_SIZE]; bool time_interval = false; String data; unsigned int run_seconds = 0; String timeCheck(){ timeClient.update(); data = timeClient.getFormattedTime(); data.toCharArray(newMessage, BUF_SIZE); P.displayText(newMessage, PA_CENTER, 0, 0, PA_PRINT, PA_NO_EFFECT); return data; } String wifiCheck(){ WiFiClient client = server.available(); while(client.available()){ String req = client.readStringUntil('\r'); req = req.substring(5,req.length()-9); req.replace("%20", " "); client.flush(); String s = "HTTP/1.1 200 OK\r\nContent-Type: text/html\r\n\r\n<!DOCTYPE HTML>\r\n<html>\r\nReq: " + req + "</html>\n"; client.print(s); delay(1); client.stop(); switch(req[0]){ case 's': data = req.substring(2); P.displayScroll(curMessage, PA_LEFT, PA_SCROLL_LEFT, frameDelay); return data; break; case 'i': data = req.substring(2); P.setIntensity(data); break; } } } void setup() { P.begin(); P.displayClear(); P.displaySuspend(false); P.displayScroll(curMessage, PA_LEFT, PA_SCROLL_LEFT, frameDelay); P.setIntensity(0); curMessage[0] = newMessage[0] = '\0'; WiFi.begin(ssid, password); while ( WiFi.status() != WL_CONNECTED ) { delay ( 500 ); } timeClient.begin(); server.begin(); sprintf(curMessage, "%03d:%03d:%03d:%03d", WiFi.localIP()[0], WiFi.localIP()[1], WiFi.localIP()[2], WiFi.localIP()[3]); } void loop() { if ( (millis()/1000 - run_seconds) > 30 ){ run_seconds = millis()/1000; time_interval = true; } if (time_interval){ data = timeCheck(); time_interval = false; } data = wifiCheck(); data.toCharArray(newMessage, BUF_SIZE); if (P.displayAnimate()) { strcpy(curMessage, newMessage); P.displayReset(); } } W zależności od zakupionego modułu trzeba będzie wybrać wersję hardware sterownika MAX7219. Definiujemy ją w linijce 10: #define HARDWARE_TYPE MD_MAX72XX::FC16_HW Obecnie dostępne typy sprzętowe to: FC16_HW, PAROLA_HW, GENERIC_HW, ICSTATION_HW. Musimy tutaj dobrać wartość eksperymentalnie. Niewłaściwy typ sprzętowy powoduje błąd w kolejnosci wyświetlania oraz w kolejności animacji poszczególnych pikseli matrycy. Dodatkowa opcja (własny tekst) W kodzie dociekliwi zobaczą, że jest możliwość wyświetlenia własnego tekstu. Po uruchomieniu programu na wyświetlaczu przewinie się adres IP, który WeMos uzyskał z naszego routera. Po otworzeniu strony z poziomu przeglądarki (lub wywołaniu polecenia curl) na stronę: http://<IP>/s=<TEXT> na wyświetlaczu będzie on przewijany od prawej do lewej strony przez maksymalnie 30 sekund. Dodatkowa opcja (zmiana jasności wyświetlacza) Można również zmienić jasność wyświetlacza, albo na stałe (w kodzie linijka: 86) P.setIntensity(<liczba z zakresu 0-15>); lub poprzez stronę www pod adresem: http://<IP>/i=<LICZBA Z ZAKRESU 0-15> gdzie 0 - to wartość minimalna, a 15 odpowiada za maksymalny poziom świecenia. Zegarek prezentuje się następująco: obudowa.pdf
  8. AJAX umożliwia przekazywanie danych pomiędzy klientem a serwerem WWW bez konieczności przeładowania strony. Dodając do tego timer w JavaScript możemy uzyskać świeże dane na stronie generowanej przez ESP8266. Na początek stwórzmy w PHP najprostszą stronę WWW prezentującą aktualną godzinę pobieraną z serwera (nie "JavaScriptovy" czas z przeglądarki - w końcu docelowo chcemy pobierać dane z czujników podłączonych do "serwera WWW" postawionego na ESP8266): <? if ($_REQUEST["time"]) { echo date("G:i:s"); exit; } ?> <html> <head> <meta http-equiv="content-type" content="text/html; charset=utf-8" /> <title>AJAX test</title> </head> <body> <span id="time"></span> <script> myTimer(); var myVar = setInterval(myTimer, 1000); function myTimer() { var xhttp = new XMLHttpRequest(); xhttp.onreadystatechange = function() { if (this.readyState == 4 && this.status == 200) { document.getElementById("time").innerHTML = this.responseText; } }; xhttp.open("GET", "test.php?time=1", true); xhttp.send(); } </script> </body> </html> Timer co sekundę przywołuje funkcję myTimer(); var myVar = setInterval(myTimer, 1000); a ta pobiera zawartość podstrony test.php?time=1 i wstawia ją do elementu o nazwie "time" (document.getElementById("time").innerHTML = this.responseText;). Mamy tu tylko jeden przekazywany parametr. Co zrobić, by aktualizować kilka różnych wartości? Przywykłem do podstrony przekazującej parametry rozdzielone znakiem ; (odczyt1;odczyt2;odczyt3). Dzięki JavaScriptowej funkcji split możemy podzielić taki ciąg znaków na tablice, a potem przydzielić jej części do elementów SPAN o określonym ID. <? if ($_REQUEST["time"]) { echo date("d.m.y;G:i:s"); exit; } ?> <html> <head> <meta http-equiv="content-type" content="text/html; charset=utf-8" /> <title>AJAX test</title> </head> <body> Date: <span id="date"></span><br> Time: <span id="time"></span> <script> myTimer(); var myVar = setInterval(myTimer, 1000); function myTimer() { var xhttp = new XMLHttpRequest(); xhttp.onreadystatechange = function() { if (this.readyState == 4 && this.status == 200) { var str = this.responseText; var values = str.split(";"); document.getElementById("date").innerHTML = values[0]; document.getElementById("time").innerHTML = values[1]; } }; xhttp.open("GET", "test.php?time=1", true); xhttp.send(); } </script> </body> </html> Działa! PHP po zainstalowania serwera np NGINX możemy uruchomić na Raspberry Pi. W połączeniu (komenda system()) z zewnętrznym skryptem Python lub programem w C korzystającym z WiringPi otrzymamy stronę z odczytami czujników hostowaną na Raspberry Pi! Spróbujmy wreszcie zaprogramować mikrokontroler ESP8266. Podłączmy najpierw czujnik BME280. /* I2C D4 - SCL D3 - SDA */ #include <ESP8266WiFi.h> #include <WiFiClient.h> #include <ESP8266WebServer.h> #include <Wire.h> #include <Adafruit_Sensor.h> #include <Adafruit_BME280.h> char* ssid = "Weather"; //const char *password = ""; ESP8266WebServer server(80); Adafruit_BME280 bme; void setup() { Serial.begin(9600); Wire.begin(D3, D4); Wire.setClock(100000); if (!bme.begin(0x76)) //changed from default I2C adress 0x77 { Serial.println("Nie odnaleziono czujnika BMP085 / BMP180"); while (1) { } } IPAddress apIP(192, 168, 1, 1); WiFi.softAPConfig(apIP, apIP, IPAddress(255, 255, 255, 0)); // WiFi.softAP(ssid, password); WiFi.softAP(ssid); server.on("/", handleRoot); server.on("/sensors", handleSensors); server.begin(); } void loop() { server.handleClient(); } void handleRoot() { String content = "<html> <head><title>Weather</title></head><body>"; content += "<DIV style=\"display:table; font-size: large;\"><DIV style=\"border-style: solid;\">BME280:<BR>Temperature: <span id=\"tempBME\"></span>C<br>Humidity: <span id=\"humBME\"></span>%<br>Pressure: <span id=\"presBME\"></span>Pa<br></DIV>"; content += "<script>myTimer();var myVar = setInterval(myTimer, 1000);function myTimer() {var xhttp = new XMLHttpRequest(); xhttp.onreadystatechange = function() { if (this.readyState == 4 && this.status == 200) { var str = this.responseText; var values = str.split(\";\"); document.getElementById(\"tempBME\").innerHTML = values[0]; document.getElementById(\"humBME\").innerHTML = values[1]; document.getElementById(\"presBME\").innerHTML = values[2];} }; xhttp.open(\"GET\", \"sensors\", true); xhttp.send();}</script>"; content += "</body></html>"; server.send(200, "text/html", content); } void handleSensors() { String content = String(bme.readTemperature()) + ";" + String(bme.readHumidity()) + ";" + String((int)bme.readPressure()) + ";"; server.send(200, "text/html", content); } Rozdzielanie danych przecinkiem czy średnikiem nie jest sposobem "zbyt profesjonalnym". Przy dużej ilości zmiennych łatwo też o pomyłkę. Dlatego lepiej wtedy stosować bardziej odpowiednie formaty danych: JSON czy XML. Ze względu na "bliskość" JSON z JavaScript skupię się tylko na nim. Gotowa biblioteka ArduinoJson wygeneruje dane za nas. Więcej informacji znajdziemy w rozdziale Serialize with ArduinoJson dokumentacji technicznej. /* I2C D4 - SCL D3 - SDA */ #include <ESP8266WiFi.h> #include <WiFiClient.h> #include <ESP8266WebServer.h> #include <Wire.h> #include <Adafruit_Sensor.h> #include <Adafruit_BME280.h> #include <ArduinoJson.h> //https://github.com/bblanchon/ArduinoJson char* ssid = "Weather"; //const char *password = ""; ESP8266WebServer server(80); Adafruit_BME280 bme; void setup() { Serial.begin(9600); Wire.begin(D3, D4); Wire.setClock(100000); if (!bme.begin(0x76)) //changed from default I2C adress 0x77 { Serial.println("Nie odnaleziono czujnika BMP085 / BMP180"); while (1) { } } IPAddress apIP(192, 168, 1, 1); WiFi.softAPConfig(apIP, apIP, IPAddress(255, 255, 255, 0)); // WiFi.softAP(ssid, password); WiFi.softAP(ssid); server.on("/", handleRoot); server.on("/sensors", handleSensors); server.begin(); } void loop() { server.handleClient(); } void handleRoot() { String content = "<html> <head><title>Weather</title></head><body>"; content += "<DIV style=\"display:table; font-size: large;\"><DIV style=\"border-style: solid;\">BME280:<BR>Temperature: <span id=\"tempBME\"></span>C<br>Humidity: <span id=\"humBME\"></span>%<br>Pressure: <span id=\"presBME\"></span>Pa<br></DIV>"; content += "<script>myTimer();var myVar = setInterval(myTimer, 1000);function myTimer() {var xhttp = new XMLHttpRequest(); xhttp.onreadystatechange = function() { if (this.readyState == 4 && this.status == 200) { var str = this.responseText; var values = JSON.parse(str); document.getElementById(\"tempBME\").innerHTML = values.temp; document.getElementById(\"humBME\").innerHTML = values.hum; document.getElementById(\"presBME\").innerHTML = values.press;} }; xhttp.open(\"GET\", \"sensors\", true); xhttp.send();}</script>"; content += "</body></html>"; server.send(200, "text/html", content); } void handleSensors() { String content; StaticJsonBuffer<400> jsonBuffer; JsonObject& root = jsonBuffer.createObject(); root["temp"] = bme.readTemperature(); root["hum"] = bme.readHumidity(); root["press"] = (int)bme.readPressure(); root.printTo(content); server.send(200, "text/html", content); } Strona prezentować się będzie tak samo jak poprzednio. Zyskamy za to wygodny i czytelny sposób dopisywania nowych danych root["temp"] = bme.readTemperature(); oraz ich odczytywania w kodzie źródłowym strony: document.getElementById(\"tempBME\").innerHTML = values.temp; Nie przejmujemy się już numeracją elementów długiej tablicy. Sytuację możemy też odwrócić tworząc stronę WWW, której formularz sterować będzie pracą naszego fizycznego urządzenia. Oczywiście nadal niekonieczne będzie przeładowanie strony, by wysłać dane. #include <ESP8266WiFi.h> #include <WiFiClient.h> #include <ESP8266WebServer.h> char* ssid = "Weather"; //const char *password = ""; int ledValue = 0; ESP8266WebServer server(80); void setup() { Serial.begin(9600); IPAddress apIP(192, 168, 1, 1); WiFi.softAPConfig(apIP, apIP, IPAddress(255, 255, 255, 0)); // WiFi.softAP(ssid, password); WiFi.softAP(ssid); server.on("/", handleRoot); server.begin(); pinMode(D1, OUTPUT); analogWriteRange(100); //http://esp8266.github.io/Arduino/versions/2.0.0/doc/reference.html analogWriteFreq(500); } void loop() { server.handleClient(); analogWrite(D1, ledValue); } void handleRoot() { if (server.hasArg("ledVal") && server.arg("ledVal").toInt() >= 0 && server.arg("ledVal").toInt() <= 100) { ledValue = server.arg("ledVal").toInt(); Serial.print("ledVal "); Serial.println(ledValue); server.send(200, "text/html", ""); return; } //https://www.w3schools.com/howto/howto_js_rangeslider.asp //https://stackoverflow.com/questions/9713058/send-post-data-using-xmlhttprequest String content = "<!DOCTYPE html>" "<html>" "<head>" "<meta name=\"viewport\" content=\"width=device-width, initial-scale=1\">" "<title>Luminosity setter</title>" "<style>" ".slidecontainer {" "width: 1024px;" "margin: 0 auto;" "text-align: center;" "}" ".slider {" " -webkit-appearance: none;" " width: 100%;" " height: 25px;" " background: #d3d3d3;" " outline: none;" " opacity: 0.7;" " -webkit-transition: .2s;" " transition: opacity .2s;" // " margin-left: 20px;" // " margin-right: 20px;" "}" ".slider:hover {" " opacity: 1;" "}" ".slider::-webkit-slider-thumb {" " -webkit-appearance: none;" " appearance: none;" " width: 25px;" " height: 25px;" " background: #4CAF50;" " cursor: pointer;" "}" ".slider::-moz-range-thumb {" " width: 25px;" " height: 25px;" " background: #4CAF50;" " cursor: pointer;" "}" "</style>" "</head>" "<body>" "<div class=\"slidecontainer\">" " <input type=\"range\" min=\"0\" max=\"100\" value=\"" + String(ledValue) + "\" class=\"slider\" id=\"myRange\">" " <p>Value: <span id=\"demo\"></span>%</p>" "</div>" "<script>" "document.getElementById(\"demo\").innerHTML = document.getElementById(\"myRange\").value;" "document.getElementById(\"myRange\").oninput = function() {" " document.getElementById(\"demo\").innerHTML = this.value;" "var data = new FormData();" "data.append('ledVal', this.value);" "var xhr = new XMLHttpRequest();" "xhr.open('POST', '/', true);" "xhr.onload = function () {" " console.log(this.responseText);" "};" "xhr.send(data);" "}" "</script>" "</body>" "</html>"; server.send(200, "text/html", content); } W tym przypadku przesunięcie suwaka na stronie sterującej wywoła natychmiastową zmianę jasności diody LED podłączonej odpowiednim rezystorem do pinu D1.
  9. Opis konstrukcji Projekt powstał z myślą udostępnienia danych ze sterownika PLC przez internet. Wymieniony sterownik posiada port komunikacyjny RS232 oraz wykorzystuje protokół komunikacyjny Modbus RTU. Podczas wykonywania prac uznałem, iż miłym dodatkiem będzie możliwość zdalnego załączania urządzeń. Całość oparłem o uniwersalną płytkę stworzoną jakiś czas temu dla ESP8266. Schemat płytki uniwersalnej ESP8266: Oczywiście można skorzystać z płytki zamieszczonej w moim projekcie o stacji pogodowej. Jest ona niejako kolejną wersją powyższej (ale brak na niej dzielnika napięcia dla A0). A poniżej wygląd PCB z programu EAGLE: Oczywiście pliki Eagle i wszystko co niezbędne do powielenia projektu w załącznikach. Nie będę wymieniał wszystkich elementów, ponieważ od tego jest schemat. Nadmienię tylko, iż na płytce znajduje się stabilizator AMS1117 3.3V, z tego względu max napięcie zasilania to 12V (najlepiej niższe ze względu na grzanie się wspomnianego stabilizatora). Esp jest wpinane na PCB na adapterze (by zapewnić modułową konstrukcję).Uzupełnieniem konstrukcji jest konwerter RS232-TTL. Konwerter RS232-TTL jest zasilony z płytki ESP8266 napięciem 3,3V, natomiast RX i TX zostały wprowadzone na IN1 i IN2 ze schematu. Całość zaprogramowałem z wykorzystaniem Arduino IDE. Dla własnych potrzeb wprowadziłem odczyt wartości temperatur, potwierdzenia pracy urządzeń oraz rozkazy załączenia dla PLC: Modbus RTU: Rejestr 40001 - Temperatura kominka Rejestr 40001 - Temperatura w zbiorniku CWU Rejestr 40001 - Temperatura w kolektorze próżniowym Rejestr 40001 - Temperatura wody podłogówka Rejestr 40001 - Napięcie baterii akumulatorów (od paneli fotowoltaicznych) Rejestr 00005 - Potwierdzenie pracy pieca gazowego Rejestr 00007 - Potwierdzenie pracy schładzania instalacji kolektorów próżniowych Rejestr 40020 - Rozkaz na załączenie pieca gazowego Rejestr 40021 - Rozkaz na załączenie instalacji schładzającej Oczywiście całość oprogramowania sterującego instalacją domową działa autonomicznie na PLC. Przytoczone tutaj zmienne to tylko przykład możliwości wykorzystania wykonanego urządzenia oraz adaptacji zamieszczonych kodów do ESP8266. Jako interface webowy wykorzystuję gotową aplikację Cayenne IoT: https://cayenne.mydevices.com/ Konstrukcja nie jest pozbawiona wad: nie jestem programistą, więc kod nie jest optymalny, działanie Cayenne My Devices na PC pozostawia wiele do życzenia (o wiele lepiej działa aplikacja na Android). W załączniku zamieszczam: Schemat w EAGLE wraz z rysunkiem ścieżek (wersja oryginalna, pierwotna oraz zmodyfikowana ze stacji meteo). Skompilowane pliki źródłowe oprogramowania (oraz wersja edytowalna). Dokładniejszy opis konstrukcji oraz konfiguracji całości. Bramka Modbus.zip ESP8266 - Robert - RTUMaster.zip esp8266_STACJA_METEO (2).zip esp8266_rs232.zip
  10. Masz gotowy wspaniały bulbulator oparty na chipie ESP8266 (np na modułach WiFi ESP8266 Wemos NodeMCU V3 32MB, ArduCam ESP8266-12E WiFi IoT, Adafruit Feather Huzzah ESP8266, Adafruit Huzzah ESP8266, SparkFun Thing - Dev Board - moduł WiFi ESP8266). Tworzy on stronę WWW, którą możesz zdalnie doglądać jego pracy. Niestety posiadasz łącze stałe ze zmiennym IP. W jaki sposób możesz więc połączyć się z nim będąc poza domem? Z pomocą przychodzą usługi typu Dynamic DNS. Jedną z nich jest polski serwis https://freedns.42.pl, z którego od lat korzystam. Zapewnia on przykładowy skrypt Pythona do uaktualniania rekordów DNS. Niestety nie skorzystamy z niego bezpośrednio w Arduino. Musimy więc napisać własny szkic. Celem podejrzenia danych wysyłanych do serwera nieco zmodyfikowałem gotowy skrypt Pythona podmieniając server = "https://freedns.42.pl/xmlrpc.php" na adres localhost swojego własnego programu napisanego w C++, który zapisał nagłówek i dane przekazywane do serwera. Tak oto powstał szkic Arduino zawierający trzy funkcje: publicIP() - sprawdzająca aktualne publiczne IP gethostbyname(String host) - sprawdzająca IP przypisane do domeny w rekordach DNS - rekord adresów (A) updateDynDNS() - odpowiedzialna za uaktualnienie wpisu w serwisie FreeDNS Możemy teraz cyklicznie wywoływać w pętli loop polecenia: String myIP = publicIP(); String hostIP = gethostbyname(dnsset.subdomain + "." + dnsset.domain); if (myIP != hostIP) { dnsset.newaddress = myIP; updateDynDNS(); Serial.println("FreeDNS IP: " + hostIP); Serial.println("Public IP: " + myIP); } Sprawdzamy więc nasze aktualne publiczne IP, potem IP przypisane do domeny w DNS. Jeśli się nie zgadzają - wtedy uruchamiamy "machinę" odpowiedzialną za komunikację z FreeDNS. Polecam użyć jakiegoś timera czy skorzystać z funkcji millis(), nie blokującego działanie innych funkcji delay() Cały kod źródłowy programu: #include <ESP8266HTTPClient.h> #include <ESP8266WiFi.h> #include <WiFiClientSecure.h> struct FreeDNSsettings { String domain; String subdomain; String newaddress; String user; String password; String ttl; } dnsset; void setup() { dnsset.domain = "TwojaDomena.pl"; dnsset.subdomain = "Nazwa subdomeny"; dnsset.user = "Uzytkownik w serwisie freedns.42.pl"; dnsset.password = "haslo uzytkownika"; dnsset.ttl = "120"; Serial.begin(9600); //Serial connection WiFi.softAPdisconnect(); WiFi.disconnect(); WiFi.mode(WIFI_STA); WiFi.begin("Siec WiFi", "i haslo do niej"); //WiFi connection while (WiFi.status() != WL_CONNECTED) { //Wait for the WiFI connection completion delay(500); Serial.println("Waiting for connection"); } } void loop() { if (WiFi.status() == WL_CONNECTED) { //Check WiFi connection status String myIP = publicIP(); String hostIP = gethostbyname(dnsset.subdomain + "." + dnsset.domain); if (myIP != hostIP) { dnsset.newaddress = myIP; updateDynDNS(); Serial.println("FreeDNS IP: " + hostIP); Serial.println("Public IP: " + myIP); } } else { Serial.println("Error in WiFi connection"); } delay(15 * 60 * 1000); } String publicIP() { String myIP; HTTPClient http; http.begin("http://ip.42.pl/raw"); //Specify request destination int httpCode = http.GET(); //Send the request if (httpCode > 0) { //Check the returning code myIP = http.getString(); //Get the request response payload } return myIP; } String gethostbyname(String host) { IPAddress ipAddress; WiFi.hostByName(host.c_str(), ipAddress); return String(ipAddress[0]) + String(".") + \ String(ipAddress[1]) + String(".") + \ String(ipAddress[2]) + String(".") + \ String(ipAddress[3]); } void updateDynDNS() { String xml = "<?xml version='1.0'?>\r\n" "<methodCall>\r\n" "<methodName>xname.updateArecord</methodName>\r\n" "<params>\r\n" "<param>\r\n" "<value><struct>\r\n" "<member>\r\n" "<name>name</name>\r\n" "<value><string>" + dnsset.subdomain + "</string></value>\r\n" "</member>\r\n" "<member>\r\n" "<name>zone</name>\r\n" "<value><string>" + dnsset.domain + "</string></value>\r\n" "</member>\r\n" "<member>\r\n" "<name>newaddress</name>\r\n" "<value><string>" + dnsset.newaddress + "</string></value>\r\n" "</member>\r\n" "<member>\r\n" "<name>oldaddress</name>\r\n" "<value><string>*</string></value>\r\n" "</member>\r\n" "<member>\r\n" "<name>updatereverse</name>\r\n" "<value><string>0</string></value>\r\n" "</member>\r\n" "<member>\r\n" "<name>user</name>\r\n" "<value><string>" + dnsset.user + "</string></value>\r\n" "</member>\r\n" "<member>\r\n" "<name>ttl</name>\r\n" "<value><string>" + dnsset.ttl + "</string></value>\r\n" "</member>\r\n" "<member>\r\n" "<name>password</name>\r\n" "<value><string>" + dnsset.password + "</string></value>\r\n" "</member>\r\n" "</struct></value>\r\n" "</param>\r\n" "</params>\r\n" "</methodCall>"; String http = "POST /xmlrpc.php HTTP/1.1\r\n" "Host: freedns.42.pl\r\n" "Accept-Encoding: gzip\r\n" "Content-Type: text/xml\r\n" "User-Agent: Python-xmlrpc/3.5\r\n" "Content-Length: " + String(xml.length()) + "\r\n" "\r\n"; xml = http + xml; WiFiClientSecure client; IPAddress ipAddress; WiFi.hostByName("freedns.42.pl", ipAddress); client.connect(ipAddress, 443); client.print(xml); } Biblioteka WiFiClientSecure występuje w środowisku ESP8266 relatywnie od niedawna. Jeśli macie problemy z kompilacją szkicu właśnie z jej powodu - uaktualnijcie obsługę ESP w Arduino IDE. Po niewielkich modyfikacjach przypuszczalnie (niestety chwilowo nie mogę tego sprawdzić) uaktualniacz DNS działać będzie również z mikrokontrolerem ESP32. Chyba wystarczy podmienić nazwy dołączanych bibliotek na: #include <HTTPClient.h> #include <WiFi.h> #include <WiFiClientSecure.h>
  11. Chęć sterowania urządzeniami w domu wzrasta z dnia na dzień. Dostępne są na rynku różne produkty ułatwiające dążenie do tego celu. Dobrym przykładem jest BlitzWolf BW-SHP6. Niestety jak się to ma w większości produktów producentów, oprogramowanie jest spersonalizowane pod producenta i dodatkowo cały ruch sieciowy przechodzi przez ich serwery. Związku z tym zaistniała potrzeba zmiany oprogramowania na OpenSources w tym przypadku Tashmota. To oprogramowanie daje możliwość sterowania w sieci LAN niezależnie od tego czy jest połączenie z Internetem. Daje też możliwość integracji z telefonem bez konkretnego systemu automatyzacji, jak i też połączenie z Domoticz lub Home Assistant. Potrzebujemy programator UART (link) lutownica (link) BlitzWolf BW-SHP2 (link) lub BlitzWolf BW-SHP6 smartfon z Android i aplikacją MacroDroid Po przygotowaniu potrzebnych nam przedmiotów Wyjmijmy gniazdko z pudełka. Jest ono małe i zgrabne co cieszy! Krok 1: Pobierz oprogramowanie Tasmota z GitHub. W moim przypadku plik sonoff.bin (język angielski). Krok 2: Pobierz oprogramowanie do wgrywania. Ja użyję tego z paczki EaspEasy. Krok 3: Rozkręć gniazdko. Odkręć jedną śrubkę znajdującą się w miejscu uziemienia i zdejmuj górną część gniazdka. Po zdjęciu obudowy można ujrzeć pady podpisane: 3.3V, 100 (podpinamy pod GND), RX, TX, GND. Krok 4: Przylutuj kabelki do padów. Pamiętaj, że RX podłącz z TX programatora! Krok 5: Podłącz do komputera i wgraj oprogramowanie. Sprawdź czy wybrano odpowiedni port COM. Krok 6: Odlutuj kabelki i skręć gniazdko. Krok 7: Po podłączeniu, gniazdko powinno emitować sieć Wi-Fi. Podłącz się do niej. Powinna się otworzyć automatycznie przeglądarka z już wprowadzonym IP gniazdka. Krok 8: Wprowadź dane do domowej sieci Wi-Fi i zaczekaj na ponowne uruchomienie. Krok 9: Domyślna konfiguracja jest ustawiona na Sonoff Basic, przejdź do ustawień (Configuration -> Configure Module). Krok 10: Z listy wybierz "BlitzWolf SHP (45)", następnie Save. Uruchomi się ponownie gniazdko. Gotowe! Gniazdko jest w pełni skonfigurowane. Dodatkowo można zmienić nazwę gniazdka (Configuration -> Confiogure Other) Integracja gniazdka z MacroDroid Pobierz aplikację MacroDroid z Google Play oraz przykładową konfigurację. Toggle_BW-SHP6_03.zip Otwórz aplikację, kliknij importuj i wybierz plik. Następnie dodaj standardowo widget MacroDroid z wyborem danego Makra. Dodatkowe ustawienia: #1 Wyłączenie diody LED (status Wi-Fi) http://ip/cm?cmnd=SetOption31%20on #2 Wyłączenie LED (po załączeniu wyjścia) http://192.168.44.33/cm?cmnd=ledstate%200
  12. Opis konstrukcji Dość modny ostatnio temat, poruszający jakość powietrza, powstał w celu wykonania pomiarów wpływu kominka w domu na zapylenie. W ten oto sposób powstała stacja pogodowa z prezentacją pomiarów na LCD 2004 z I2C oraz możliwością udostępnienia danych dla Domoticz lub ThingSpeak. Sercem stacji jest układ ESP8266-12F na adapterze ESP Shild. Całość umieszczono na PCB zaprojektowanym w EAGLE. Płytka jest zaprojektowana w sposób umożliwiający szybką wymianę poszczególnych elementów. Może być ona wykorzystywana do programowania ESP z wykorzystaniem złącza PROG (po podpięciu się konwerterem USB-UART), jak również w innych projektach wykorzystujących I2C, wejście analogowe ESP, wejścia cyfrowe. Jako zasilanie wykorzystałem zasilacz 12V 1A, których mam kilkanaście. Dla potrzeb zasilania czujników potrzebujemy zasilania 5V o wydajności prądowej ok 1A. W tym celu wykorzystano przetwornicę impulsową step down - przetwornica DC-DC Mini 360 . Przetwornica, jak i inne elementy jest wymienna (na goldpinach). Takie rozwiązanie wymusiło stosowanie tego samego układu w innych projektach, gdzie miałem dostępne zasilania 24-30VDC). Projekt spodobał się znajomym, więc płytka została od razu wykonana w kilku egzemplarzach na frezarce mojego wykonania. Wygląd PCB od strony druku można zobaczyć na zdjęciach. Realizacja pomiarów: W założeniach miałem mierzyć tylko zawartość pyłów ale w szufladach zalegało jeszcze kilka innych czujników. Stąd też dodatkowe pomiary. Pyły: PM1; PM2,5; PM10 - czujnik PMS5003 Ciśnienie, temperatura, wilgotność - czujnik BME280 Wskaźnik CO2 - czujnik MQ135 Wyniki prezentowane są na LCD oraz przez WiFI korzystając z oprogramowania EasyEsp. Istnieje możliwość konfiguracji oprogramowania w celu przesyłania pomiarów do Domoticz lub ThingSpeak. Oprogramowanie W założeniach miałem napisać własny soft wykorzystując biblioteki dostępne dla Arduino IDE, ale z braku czasu poszedłem na łatwiznę i wykorzystałem EasyEsp. Soft wgrywamy za pomocą oprogramowania Esp8266Flasher - wykorzystujemy połączenie po USB - UART (złącze PROG na PCB). Konfiguracja Proces konfiguracji jest dokonywany z poziomu strony WWW oprogramowania ESPEasy i jest dość intuicyjny. Wszystkie parametry wpisujemy w zakładkach odpowiedzialnych za obsługę sieci, czujników iitp. Oczywiście proces konfiguracji opisany jest dokładnie na stronie projektu ESPEasy. Dla osób nie obeznanych w tej tematyce zamieszczam plik konfiguracyjny mojego projektu wraz z dokumentacją zdjęciową (Konfiguracja ESP). Podsumowanie Na chwilę obecną brak jest obudowy, ale układ powstał jako prototyp i każdy adresat układu ma ją wykonać we własnym zakresie. Sam zrobię to jak skończę inne projekty. Konstrukcja ma sporo wad: 1. Brak kalibracji czujników. 2. Pomiar MQ135 to tylko wskazanie przesunięte o 400 ppm (~poziom CO2 w atmosferze, nie uwzględniam wpływu temperatury i wilgotności). 3. Brak dzielnika napięcia na A0 (ESP ma pomiar 0-1V, MQ135 może dać do 5 V przy 5000 ppm), jednak zakładam, że nie będę miał stężenia ponad 1000ppm w domu. Później przetnę ścieżkę na PCB i dam dzielnik na analogu (co niestety zmniejszy dokładność pomiarów) lub zabezpieczę wejście analogowe diodą zenera. 4. Gotowe oprogramowanie z wieloma wadami, w planach zmiana na własny soft i wysyłanie informacji na Cayenne IOT. Zalety: 1. Prosta modułowa konstrukcja, uniwersalna płytka PCB stosowana przeze mnie w innych projektach. 2. Gotowy soft możliwy do wgrania i konfiguracji dla zupełnych laików. Jeśli ktoś jest zainteresowany dodatkowymi materiałami, to proszę o kontakt PW. W załączniku zamieszczam: 1. EAGLE - schemat w EAGLE 9.1.2 wraz z rysunkiem ścieżek. 2. ESP - oprogramowanie w wykorzystanej wersji. 3. ESP8266Flasher - soft do wgrania oprogramowania. Podczas testów pomiar pyłów miałem na zewnątrz przy mrozach ponad 300 szczytowo i pokrywało się to z lokalną stacją w Połańcu (odchyłka była w granicach 5%). Czujnik PM5003 pracuje u mnie w cyklu 60 sekund pomiary/ 30 minut uśpienie. Żywotność czujnika laserowego to 8000 h. Częstszych pomiarów nie potrzebuję do swoich potrzeb. Czas 60 sekund wystarcza do odpowiedniego wygrzania czujnika i ustabilizowania się pomiarów. Cała stacja pobiera zaraz po starcie ok. 3 W, a po nagrzaniu czujnika MQ135 pobór energii spada do ok. 1,8 W. ESP8266Flasher.zip EAGLE.zip ESP.zip Konfiguracja_ESPEasy.zip
×