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.

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

Dołącz do dyskusji, napisz odpowiedź!

Jeśli masz już konto to zaloguj się teraz, aby opublikować wiadomość jako Ty. Możesz też napisać teraz i zarejestrować się później.
Uwaga: wgrywanie zdjęć i załączników dostępne jest po zalogowaniu!

Anonim
Dołącz do dyskusji! Kliknij i zacznij pisać...

×   Wklejony jako tekst z formatowaniem.   Przywróć formatowanie

  Dozwolonych jest tylko 75 emoji.

×   Twój link będzie automatycznie osadzony.   Wyświetlać jako link

×   Twoja poprzednia zawartość została przywrócona.   Wyczyść edytor

×   Nie możesz wkleić zdjęć bezpośrednio. Prześlij lub wstaw obrazy z adresu URL.

×
×
  • 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.