Si inca un montaj cu excelentul Wemos D1 mini : ceas cu matrice de leduri, cu senzor de temperatura DS18B20. Are efect de scroll a afisarii. Testat, functioneaza perfect. L-am montat in bucatarie, iar senzorul de temperatura l-am scos in balcon. Succes !
// Ceas matrice LED cu ora automata vara/iarna + DS18B20
// Conexiuni : DS18B20 la D2 ; matrice DIN - D8 ; CS - D7 ; CLK - D6
#include <ESP8266WiFi.h> #include <time.h> #include <MD_MAX72xx.h> #include <SPI.h> #include <OneWire.h> #include <DallasTemperature.h>
Va salut !
Actualizez blogul cu o simpla "statie meteo" (denumire cam pompoasa...), creata cu un Wemos D1 mini (da, am multe montaje cu aceasta mica bijuterie !) si un display OLED SH1106. Datele meteo sunt furnizate de OpenWeather.
Necesita imbunatatiri (ora automata vara/iarna), dar este un punct de plecare pentru cine vrea sa experimenteze. Succes !!
//cu date meteo de la OpenWeather
#include "Arduino.h" #include <ESP8266WiFi.h> #include <WiFiManager.h> #include <NTPClient.h> #include <WiFiUdp.h> #include <ArduinoJson.h> #include <SH1106Wire.h> #include <ESP8266HTTPClient.h>
// Definirea pinilor pentru display OLED
#define OLED_SDA D2
#define OLED_SCL D1
// Inițializarea display-ului OLED
SH1106Wire display(0x3c, OLED_SDA, OLED_SCL);
// Definirea serverului NTP și offset-ului de timp
WiFiUDP ntpUDP;
NTPClient timeClient(ntpUDP, "pool.ntp.org", 7200, 60000);
const char* ssid = "your_ssid"; // Introduceti numele retelei WiFi
const char* password = "your_psw"; // Introduceti parola retelei WiFi
const char* zileleSaptamanii[] = {"Duminica", "Luni", "Marti", "Miercuri", "Joi", "Vineri", "Sambata"};
const String apiKey = "6d34f8b43ddcbe0377b78a29108d2196"; //creati-va propriu apiKey !!!
const String city = "Ploiesti"; // setati-va locatia !!!
String temperatura = "";
String stareVreme = "";
String formatTime(int hh, int mm, int ss) {
String timeStr = "";
if (hh < 10) timeStr += "0";
timeStr += String(hh) + ":";
if (mm < 10) timeStr += "0";
timeStr += String(mm) + ":";
if (ss < 10) timeStr += "0";
timeStr += String(ss);
return timeStr;
}
void setup() {
Serial.begin(115200);
// Conectare la WiFi
WiFi.begin(ssid, password);
while (WiFi.status() != WL_CONNECTED) {
delay(1000);
Serial.println("Se conecteaza la WiFi...");
}
Serial.println("Conectat la WiFi");
timeClient.begin();
display.init();
display.flipScreenVertically();
display.setFont(ArialMT_Plain_10);
}
void obtineVremea() {
WiFiClient client;
HTTPClient http;
String url = "http://api.openweathermap.org/data/2.5/weather?q=" + city + "&appid=" + apiKey + "&units=metric&lang=ro";
http.begin(client, url);
int httpCode = http.GET();
if (httpCode > 0) {
String payload = http.getString();
DynamicJsonDocument doc(1024);
deserializeJson(doc, payload);
temperatura = String(doc["main"]["temp"].as(), 1) + " °C";
stareVreme = doc["weather"][0]["description"].as();
}
http.end();
}
void loop() {
timeClient.update();
int hh = timeClient.getHours();
int mm = timeClient.getMinutes();
int ss = timeClient.getSeconds();
int zi = timeClient.getDay();
String currentTime = formatTime(hh, mm, ss);
obtineVremea();
display.clear();
display.setTextAlignment(TEXT_ALIGN_CENTER);
display.setFont(ArialMT_Plain_10);
display.drawString(64, 0, zileleSaptamanii[zi]);
display.setFont(ArialMT_Plain_24);
display.drawString(64, 10, currentTime);
display.setFont(ArialMT_Plain_16);
display.drawString(64, 35, temperatura);
display.setFont(ArialMT_Plain_10);
display.drawString(64, 50, stareVreme);
display.setFont(ArialMT_Plain_16);
display.display();
delay(1000);
}
Va salut !
Fiindca o lunga perioada de timp am fost in concediu medical, dupa o afectiune oncologica ce a necesitat operatie, in timpul liber disponibil am lucrat la o serie de proiecte, care sa-mi tina mintea distrasa de la alte ganduri ...
Cu ajutorul AI (Vercel, Perplexity, Gemini, Qwen, Le Chat) am conceput si finalizat, dupa multe, multe incercari si aceasta statie meteo color. Functioneaza foarte bine, prietenul meu, Dragos, o are "activa" si e multumit de ea.
Nota : am experimentat cu datele meteo de pe toate siteurile care pun la dispozitie apiKey. Nu sunt doua la fel !! Exista mari variatii, in special in privinta temperaturii, dar, la unele siteuri, si la starea vremii propriu-zisa (afara era soare, senin si prognoza era innorat, sanse de ploaie :( ). Acesta este si motivul pentru care eu nu folosesc aceasta statie, prefer ceasul cu termometru (ce poate fi vazut tot aici, pe blog).
Cine doreste poate face statia, ideea este sa va obtineti propriul apiKey (google_it) si sa va setati locatia. Succes !
Tot cu ajutorul AI am realizat un ceas, cu Wemos D1 mini, care afiseaza pe un dispay LCD si temperatura citita cu SHT40, transmisa din exteriorul locuintei, tot de un Wemos D1 mini . Dupa multe teste si multe buguri rezolvate, pot spune ca acum functioneaza foarte bine, nu am mai gasit nicio problema.Trecerea la ora de vara/iarna se face automat.
Ceasul propriu-zis il alimentez dintr-un alimentator de telefon mobil, de 5 volti. Emitatorul este alimentat dintr-un acumulator Li-Ion, a carui tensiune este citita periodic ; daca scade sub pragul critic, apare mesajul "ACCU" pe displayul ceasului. Consumul emitatorului este foarte mic pentru ca transmisia temperaturii se face o data la 5 minute, dupa care montajul intra in deep sleep. Alimentarea se face cu un IC specializat, HT7333.
// DATĂ static String ld=""; String dat=(utc>100000000)?String(dd)+"-"+String(luni[mm]):"-- ---"; if (dat!=ld) { display.setFreeFont(&FreeSansBold12pt7b); display.setTextDatum(TL_DATUM); display.setTextColor(TFT_GREEN,TFT_BLACK); display.fillRect(10,25,100,25,TFT_BLACK); display.drawString(dat,0,25); ld=dat; }
// TEMPERATURĂ - FOARTE IMPORTANT: afișează mereu, chiar dacă este 0.0 static String lt=""; String tmp=String(receivedTemp,1)+"'C"; if (tmp!=lt || receivedTemp==0.0) { // 🔑 FORȚEAZĂ afișarea inițială! display.fillRect(233-90,25-2,90,25,TFT_BLACK); display.setFreeFont(&FreeSansBold12pt7b); display.setTextDatum(TR_DATUM); display.setTextColor(TFT_PURPLE,TFT_BLACK); display.drawString("'C",233,25); display.setTextColor(TFT_YELLOW,TFT_BLACK); display.drawString(String(receivedTemp,1),233-display.textWidth("'C"),25); lt=tmp; }
// BATERIE static bool lb=false; if (lowBattery!=lb) { display.fillRect(95,17,50,20,TFT_BLACK); if (lowBattery) { display.setTextFont(2); display.setTextDatum(MC_DATUM); display.setTextColor(TFT_RED,TFT_BLACK); display.drawString("ACCU",120,25); } lb=lowBattery; }
// ZILE static int lw=-1; int cw=(wd==0)?6:wd-1; if (cw!=lw || utc<=100000000) { display.fillRect(0,185,240,30,TFT_BLACK); display.setFreeFont(&FreeSansBold9pt7b); display.setTextDatum(MC_DATUM); int pz[7]={10,50,90,125,155,185,220}; for (int i=0;i<7;i++) { int zi=(i+1)%7; uint16_t c=(utc<=100000000)?TFT_DARKGREY:(i==cw)?(zi==0||zi==6?TFT_RED:TFT_GREEN):TFT_DARKGREY; display.setTextColor(c,TFT_BLACK); display.drawString(zileAbrev[zi],pz[i],200); } lw=cw; } }
//Serial.print("Incercarea: "); Serial.println(attempts); delay(1000); // Așteaptă 1 secundă între încercări pentru a da timp routerului }
// Verificăm dacă am reușit sau am epuizat tentativele if (WiFi.status() == WL_CONNECTED) { display.fillScreen(TFT_BLACK); display.drawString("Conectat !", 120, 100); display.drawString(WiFi.localIP().toString(), 120, 140);
Va prezint statia mea de lipit cu Arduino. Realizata cu AI Gemini ; eu
doar am formulat cerintele si am testat codul, deci nu am prea multe
merite. Inspiratia mea au fost nenumaratele exemple existente.
Pe scurt : ciocan Pensol SL-10 (cel pe care-l am de mai bine de 15 ani
si nu renunt la el, e prea bun !), Arduino Nano, display LED 7 segmente 3
digiti, encoder rotativ, 2 butoane pentru memorare 2 temperaturi de
lucru, stand-by dupa 30 minute.
Am testat-o cateva zile, pe parcursul carora am rezolvat diverse
buguri. In prezent functioneza destul de bine, zic eu. Am vrut initial
cu PID, dar inertia termica mare a ciocanului m-a facut sa renunt.
Orice comentariu sau sugestie sunt binevenite. Statia este prezentata si pe Elforum (https://www.elforum.info/topic/165506-statie-de-lipit-cu-arduino/),
cine vrea o poate modifica dupa cum doreste (recomand
Vercel / Perplexity / Qwen / Gemini, nu neaparat in aceasta ordine). Numai
bine !
/*
//
// STATIE DE LIPIT CU ARDUINO
// DISPLAY LED CU 7 SEGMENTE
// ENCODER SI 2 MEMORII
// STAND-BY DUPA 30 MINUTE
//
*/
#include "max6675.h" #include <Encoder.h> #include <EEPROM.h> #include <EasyButton.h>
// --- CONFIGURARE HARDWARE ---
MAX6675 thermocouple(10, 9, 8);
Encoder myEnc(2, 3);
const int segPins[] = {4, 5, 6, 7, 12, 13, A0};
const int digitPins[] = {A3, A4, A5};
const int btnPins[] = {A1, A2};
// --- VARIABILE CONTROL ---
double Setpoint = 250.0; // temperatura de start
double Input;
int pwm = 0;
const int maxPWM = 255;
long oldPosition = 0;
unsigned long lastUpdate = 0;
unsigned long lastEncoderTime = 0;
unsigned long lastAdjustTime = 0;
bool sensorError = false;
int tempDisplay = 0;
int currentDigit = 0;
const double TEMP_OFFSET = 0;
// --- LOGICA MEMORIE SI CLIPIRE ---
const int EEPROM_ADDR[] = {0, 2};
int savedTemps[2] = {0, 0};
unsigned long blinkUntil = 0;
bool displayVisible = true;
// --- VARIABILE TIMER AUTO-OPRIRE ---
unsigned long lastActivityTime = 0;
const unsigned long TIMEOUT_OFF = 1800000;
bool isSystemOff = false;
// Obiecte butoane
EasyButton btn1(btnPins[0]);
EasyButton btn2(btnPins[1]);
// --- FUNCTII AUXILIARE ---
void triggerBlink() {
blinkUntil = millis() + 500;
}
void resetActivity() {
lastActivityTime = millis();
}
// Functie gestionare trezirea din stand-by
void wakeSystem() {
if (isSystemOff) {
isSystemOff = false;
// Forțăm o citire imediată a senzorului pentru a evita afișarea de valori vechi
double r = thermocouple.readCelsius();
if (isnan(r) || r <= 0) sensorError = true;
else { sensorError = false; Input = r + TEMP_OFFSET; }
lastUpdate = millis(); // Actualizam si timestamp-ul ultimei citiri
}
resetActivity();
}
// --- FUNCTII CALLBACK BUTOANE ---
void onBtn1Short() {
wakeSystem(); // Folosim noua functie
if (savedTemps[0] > 0) {
Setpoint = savedTemps[0];
lastAdjustTime = millis();
triggerBlink();
Serial.println("BTN1 RECALL");
}
}
void onBtn1Long() {
wakeSystem(); // Folosim noua functie
savedTemps[0] = (int)Setpoint;
EEPROM.put(EEPROM_ADDR[0], savedTemps[0]);
triggerBlink();
Serial.println("BTN1 SAVE");
}
void onBtn2Short() {
wakeSystem(); // Folosim noua functie
if (savedTemps[1] > 0) {
Setpoint = savedTemps[1];
lastAdjustTime = millis();
triggerBlink();
Serial.println("BTN2 RECALL");
}
}
void onBtn2Long() {
wakeSystem(); // Folosim noua functie
savedTemps[1] = (int)Setpoint;
EEPROM.put(EEPROM_ADDR[1], savedTemps[1]);
triggerBlink();
Serial.println("BTN2 SAVE");
}
// Mapare segmente
byte const digits[] = {
B00111111, B00000110, B01011011, B01001111, B01100110,
B01101101, B01111101, B00000111, B01111111, B01101111
};
void setup() {
Serial.begin(9600);
for (int i = 0; i < 7; i++) pinMode(segPins[i], OUTPUT);
for (int i = 0; i < 3; i++) pinMode(digitPins[i], OUTPUT);
pinMode(11, OUTPUT);
analogWrite(11, 0);
btn1.begin();
btn2.begin();
btn1.onPressed(onBtn1Short);
btn1.onPressedFor(3000, onBtn1Long);
btn2.onPressed(onBtn2Short);
btn2.onPressedFor(3000, onBtn2Long);
for (int i = 0; i < 2; i++) {
int val;
EEPROM.get(EEPROM_ADDR[i], val);
// Verificam si noul prag minim de 200 grade
if (val >= 200 && val <= 400) savedTemps[i] = val;
else savedTemps[i] = 0;
}
delay(500);
myEnc.write(0);
resetActivity();
}
void loop() {
btn1.read();
btn2.read();
// 1: Verificare Timer Auto-Oprire
if (!isSystemOff && (millis() - lastActivityTime > TIMEOUT_OFF)) {
isSystemOff = true;
}
// 2. Encoder
long newPos = myEnc.read();
if (millis() - lastEncoderTime > 50) {
long deltaPos = newPos - oldPosition;
if (abs(deltaPos) >= 4) {
wakeSystem(); // Folosim noua functie si aici
oldPosition = newPos;
lastEncoderTime = millis();
lastAdjustTime = millis();
if (deltaPos > 0) Setpoint += 5; else Setpoint -= 5;
Setpoint = constrain(Setpoint, 150, 400);
}
}
// 3. Senzor (ramane neschimbat, se ocupa wakeSystem de refresh imediat)
if (millis() - lastUpdate > 250) {
double r = thermocouple.readCelsius();
if (isnan(r) || r <= 0) sensorError = true;
else { sensorError = false; Input = r + TEMP_OFFSET; }
lastUpdate = millis();
}
// 4. Control Heater
if (sensorError || isSystemOff) {
pwm = 0;
}
else if (Input <= Setpoint) {
pwm = maxPWM;
}
else {
pwm = 0;
}
analogWrite(11, pwm);
// 5. Logica Afisaj si Clipire
if (millis() < blinkUntil) {
displayVisible = (millis() / 100) % 2;
} else {
displayVisible = true;
}
if (isSystemOff) {
tempDisplay = -1; // Va afisa "---"
}
else if (sensorError) {
tempDisplay = -111;
}
else if (millis() - lastAdjustTime < 3000) {
tempDisplay = (int)Setpoint;
}
else {
if (abs(Input - Setpoint) <= 5.0) {
tempDisplay = (int)Setpoint;
} else {
tempDisplay = (int)Input;
}
}
if (displayVisible) refreshDisplay(tempDisplay);
else clearDisplay();
}
void clearDisplay() {
for (int i = 0; i < 3; i++) digitalWrite(digitPins[i], LOW);
}
void refreshDisplay(int val) {
digitalWrite(digitPins[currentDigit], LOW);
currentDigit = (currentDigit + 1) % 3;
byte segments = 0;
if (val == -1) {
segments = B01000000; // Doar segmentul din mijloc (G)
}
else if (val < 0 && val != -1) {
segments = B01000000;
} else {
int t = abs(val);
int d;
if (currentDigit == 0) d = t / 100;
else if (currentDigit == 1) d = (t / 10) % 10;
else d = t % 10;
if (currentDigit == 0 && d == 0) segments = 0;
else segments = digits[d];
}
for (int i = 0; i < 7; i++) {
digitalWrite(segPins[i], (segments & (1 << i)) ? LOW : HIGH);
}
digitalWrite(digitPins[currentDigit], HIGH);
delay(3);
}