duminică, 22 februarie 2026

Ceas Matrix cu termometru

 Va salut !

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>

O simpla statie meteo cu display OLED

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); }

Statie meteo cu Wemos D1 mini si ST7735

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 !
//Statie meteo cu ST7735


#include <SPI.h>
#include <TimeLib.h>
#include <ArduinoJson.h>
#include <Adafruit_GFX.h>
#include <Adafruit_ST7735.h>
#include <ESP8266HTTPClient.h>
#include <WiFiClient.h>
#include <Arduino.h>
#include <ESP8266WiFi.h>
#include <time.h>
#include "OpenWeatherMapCurrent.h"
#include <WiFiUdp.h>
#include <Fonts/FreeMonoBold12pt7b.h>
#include <Fonts/FreeMono12pt7b.h>
#include <Fonts/FreeSans9pt7b.h>
#include <Fonts/FreeSans12pt7b.h>
#include <Fonts/FreeSansBold18pt7b.h>
#include <Fonts/FreeSansBold9pt7b.h>

Ceas cu termometru, display LCD, emitator WiFi

 Va salut !

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. 


 

 


 

// RECEIVER CU - HTTP STABIL CU NON-BLOCKING LOOP

#include <ESP8266WiFi.h>
#include <ESP8266WebServer.h>
#include <TFT_eSPI.h>
#include <TimeLib.h>
#include <Timezone.h>
#include <NTPClient.h>
#include <WiFiUdp.h>

TFT_eSPI display = TFT_eSPI();
ESP8266WebServer server(80);
WiFiUDP ntpUDP;
NTPClient timeClient(ntpUDP, "pool.ntp.org", 0, 60000);

const char* ssid = "Andy_2.4G";
const char* password = "andy1603";

TimeChangeRule EEST = {"EEST", Last, Sun, Mar, 3, 180};
TimeChangeRule EET = {"EET", Last, Sun, Oct, 3, 120};
Timezone Bucharest(EEST, EET);

const char* zileAbrev[] = {"DU", "LU", "MA", "MI", "JO", "VI", "SA"};
const char* luni[] = {"IAN", "FEB", "MAR", "APR", "MAI", "IUN", "IUL", "AUG", "SEP", "OCT", "NOV", "DEC"};

float receivedTemp = 0.0;
bool lowBattery = false;

void handleUpdate() {
if (server.hasArg("temp")) {
float v = server.arg("temp").toFloat();
if (v >= -50 && v <= 100) receivedTemp = v;
}
lowBattery = (server.hasArg("bat") && server.arg("bat") == "LOW");
server.send(200, "text/plain", "OK");
}

unsigned long prevDisplay = 0;
unsigned long prevNTP = 0;

void displayInfo() {
time_t utc = timeClient.getEpochTime();
time_t local = (utc > 100000000) ? Bucharest.toLocal(utc) : 0;

int dd=1, mm=0, wd=1, h=0, m=0, s=0;
if (utc > 100000000) {
tmElements_t tm; breakTime(local, tm);
dd=tm.Day; mm=tm.Month-1; wd=tm.Wday-1; h=tm.Hour; m=tm.Minute; s=tm.Second;
}

// ORĂ
static int lh=-1, lm=-1;
if (h!=lh || m!=lm || utc<=100000000) {
display.fillRect(20,80,220,80,TFT_BLACK);
display.setTextFont(7); display.setTextColor(TFT_CYAN,TFT_BLACK); display.setTextDatum(TR_DATUM);
display.drawString((utc>100000000)?(String(h)+":"+(m<10?"0":"")+String(m)):"--:--", 175, 95);
lh=h; lm=m;
}

// SECUNDE
static String ls="";
String sec=(utc>100000000)?(s<10?"0":"")+String(s):"--";
if (sec!=ls) {
display.setFreeFont(&FreeSansBold12pt7b); display.setTextDatum(MC_DATUM);
display.setTextColor(TFT_CYAN,TFT_BLACK); display.fillRect(173,100,60,30,TFT_BLACK);
display.drawString(sec,190,105); ls=sec;
}

// 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;
}
}

void setup() {
Serial.begin(115200);
display.init();
display.setRotation(3);
display.fillScreen(TFT_BLACK);

display.setTextColor(TFT_WHITE, TFT_BLACK);
display.setFreeFont(&FreeSansBold12pt7b);
display.setTextDatum(MC_DATUM);

WiFi.mode(WIFI_STA);
WiFi.begin(ssid, password);

int attempts = 0;
const int maxAttempts = 15;

// Bucla de conectare cu actualizare pe ecran
while (WiFi.status() != WL_CONNECTED && attempts < maxAttempts) {
attempts++;

display.fillScreen(TFT_BLACK);
display.drawString("Conectare WiFi...", 120, 100);
// Afișează încercarea curentă (ex: 1/15, 2/15...)
display.drawString(String(attempts) + "/" + String(maxAttempts), 120, 140);

//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);

timeClient.begin();
timeClient.update();
server.on("/update", handleUpdate);
server.begin();

delay(2000);
} else {
display.fillScreen(TFT_BLACK);
display.setTextColor(TFT_RED, TFT_BLACK); // Opțional: roșu pentru eroare
display.drawString("Eroare WiFi !", 120, 100);
display.drawString("Reboot...", 120, 140);
delay(3000);
ESP.restart();
}
}


void loop() {
// 🔑 CRITIC: server.handleClient() la FIECARE ITERAȚIE (fără excepții!)
server.handleClient(); // NICIODATĂ nu blochează mai mult de 1ms

// NTP non-blocking (doar la intervale)
if (millis() - prevNTP > 900000 && timeClient.update()) prevNTP = millis();

// Afișaj la 1s (fără blocări)
if (millis() - prevDisplay >= 1000) {
prevDisplay = millis();
displayInfo();
}
}
<10 175="" 95="" lh="h;" lm="m;" ls="" m="" sec="(utc" secunde="" static="" string="" tring=""><10 dat="(utc" display.drawstring="" display.fillrect="" display.setfreefont="" display.settextcolor="" display.settextdatum="" if="" ld="" ls="sec;" reesansbold12pt7b="" s="" sec="" static="" string="" tring=""><7 -="" 100="" 120="" 140="" 1="" 1ms="" 2="" a="" actualizare="" afi="" am="" attempts="" blocheaz="" bucla="" c="" conectare="" const="" critic:="" cu="" curent="" da="" dac="" de="" delay="" display.drawstring="" display.fillscreen="" display.init="" display.setfreefont="" display.setrotation="" display.settextcolor="" display.settextdatum="" doar="" eaz="" eboot...="" ecran="" else="" epuizat="" erial.print="" eroare="" esp.restart="" ex:="" excep="" f="" fiecare="" handleupdate="" i="" if="" ifi.localip="" ifi.status="" ii="" int="" intervale="" ional:="" it="" itera="" la="" loop="" lw="cw;" m="" mai="" maxattempts="" millis="" mult="" ncerc="" ncercarea:="" ncercarea="" niciodat="" non-blocking="" ntp="" ntre="" nu="" onectare="" onectat="" op="" password="" pe="" pentru="" prevntp="" pz="" r="" reesansbold12pt7b="" reu="" ri="" ro="" roare="" routerului="" sau="" secund="" serial.begin="" serial.println="" server.begin="" server.handleclient="" server.on="" setup="" ssid="" string="" teapt="" tentativele="" tft_black="" timeclient.begin="" timeclient.update="" timp="" tostring="" tring="" u="" uint16_t="" update="" verific="" void="" while="" wifi...="" wifi.begin="" wifi.mode="" wifi="" wl_connected="" zi="" zileabrev="">

O simpla statie de lipit cu Arduino

 

Va salut !

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); }