Skocz do zawartości

"Wszystkomierz" Arduino z logowaniem pomiarów do pliku tekstowego i baz danych MySQL/SQLite poprzez WiFi


rziomber

Pomocna odpowiedź

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!" 😉

Arduino_Multimeter.thumb.jpg.c6cf4635696293c900a9389ec95e0557.jpg

Dane logować można z terminala portu szeregowego (w tym do pliku - Windows/Linux).

multimetr2.thumb.png.4ad0d1096b8b6a989aae776c79dbbe16.png

Wyniki pomiarów prezentowane są również na stronie WWW, a odczyty odświeżają się na żywo dzięki zastosowaniu technologii AJAX.

multimetr.thumb.png.b4a8a4ade7c92523b559a8910935d0ca.png

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.

Kable.thumb.jpg.6dd3588233b6c222b78e07392179aa35.jpg

/*
  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++.

app.thumb.png.7ee8d44af11def1c1c18bf912f69f850.png

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)

sql.thumb.png.47e8ebd7296fb04a1abe118e109641bf.png

Składniki:

Edytowano przez rziomber
  • Lubię! 2
Link do komentarza
Share on other sites

Podoba Ci się ten projekt? Zostaw pozytywny komentarz i daj znać autorowi, że zbudował coś fajnego!

Masz uwagi? Napisz kulturalnie co warto zmienić. Doceń pracę autora nad konstrukcją oraz opisem.

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

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

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ę »
×
×
  • Utwórz nowe...

Ważne informacje

Ta strona używa ciasteczek (cookies), dzięki którym może działać lepiej. Więcej na ten temat znajdziesz w Polityce Prywatności.