rziomber Napisano Styczeń 30, 2019 Udostępnij Napisano Styczeń 30, 2019 (edytowany) 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 Edytowano Styczeń 31, 2019 przez rziomber 2 Link do komentarza Share on other sites More sharing options...
Treker (Damian Szymański) Styczeń 31, 2019 Udostępnij Styczeń 31, 2019 Właśnie zaakceptowałem Twój opis, możesz go teraz zgłosić do akcji rabatowej umieszczając link w temacie zbiorczym. Dziękuję za przedstawienie ciekawego projektu, zachęcam do prezentowania kolejnych DIY oraz aktywności na naszym forum 🙂 Link do komentarza Share on other sites More sharing options...
rziomber Maj 13, 2020 Autor tematu Udostępnij Maj 13, 2020 Captive Portal dla naszego wszystkomierza opartego na ESP8266: #include <DNSServer.h> DNSServer dnsServer; [...] IPAddress apIP(192, 168, 1, 1); WiFi.softAPConfig(apIP, apIP, IPAddress(255, 255, 255, 0)); // WiFi.softAP(ssid, password); WiFi.softAP(ssid); dnsServer.start(53/*DNS_PORT*/, "*", apIP); Dzięki temu możemy zapomnieć IP urządzenia 🙂 Link do komentarza Share on other sites More sharing options...
Pomocna odpowiedź
Bądź aktywny - zaloguj się lub utwórz konto!
Tylko zarejestrowani użytkownicy mogą komentować zawartość tej strony
Utwórz konto w ~20 sekund!
Zarejestruj nowe konto, to proste!
Zarejestruj się »Zaloguj się
Posiadasz własne konto? Użyj go!
Zaloguj się »