Automatické rozsvícení světel do šera nebo tunelu - BH1750

Uživatelský avatar
petr-kubac
Příspěvky: 194
Registrován: 24 úno 2013, 14:43
Bydliště: Prostřední Suchá
Kontaktovat uživatele:

Re: Automatické rozsvícení světel do šera nebo tunelu - BH1750

Příspěvek od petr-kubac »

Kdysi jsem pro pro měření intenzity světla používal obyčejnou fotodiodu BPW34 v tzv. fotovoltaickém režimu, kdy dioda sama generuje napětí, které je úměrné logaritmu intenzity světla, které na ni dopadá.
BPW34 ve tmě, kdy nevíte jestli máte otevřené oči nebo ne - stále ještě generuje kolem 10 mV a na přímém slunci kolem 700 mV. to jsou napětí pohodlně zpracovatelná klasickým operačním zesilovačem typu LM324, ale je to analogová otrava - oproti hotovému modulu s I2C.
"The best computer language is a solder" - "Nejlepší programovací jazyk je pájka" - Bob Pease
harr22
Příspěvky: 61
Registrován: 08 čer 2024, 11:09

Re: Automatické rozsvícení světel do šera nebo tunelu - BH1750

Příspěvek od harr22 »

vojta_1 píše: 15 kvě 2026, 20:50 Rozlišení ve tmě: TSL2591 má dynamický rozsah 600 000 000 : 1
Jo, to bude presně to co hledám, dík za info.
harr22
Příspěvky: 61
Registrován: 08 čer 2024, 11:09

Re: Automatické rozsvícení světel do šera nebo tunelu - BH1750

Příspěvek od harr22 »

HonzaD píše: 15 kvě 2026, 20:47 Z obrázku je vidět, že i ve tmě má fotorezistor ještě docela dobré rozlišení, nešumí nijak tragicky ale očividně přes noc "nějak divně ujíždí". Na druhou stranu, pozorováno logaritmicky, to není zase o tolik. Co mi spíš vrtá hlavou je, proč to dělá? Jistě, je možné, že je to tím, že se v noci ochladí. Stoupá snad vodivost fotorezistoru s klesající teplotou? Moc se mi nepodařilo k tématu najít něco věrohodného.
To driftování fotoodpor dělá, je to prostě vlastnost této součástky. Má paměťový efekt, po osvícení trvá hodně dlouho (hodiny) než se odpor ve tmě opět ustálí. Je to dáno konstrukcí a materiály, někde jsem kdysi četl i mechanismus vzniku tohoto jevu, ale protože s tím stejně nic udělat nejde, nechce se mi to hledat. Samozřejmě teplotou to může být taky, ale podle tvaru křivky bych tipoval spíš tohle. Čím dýl a víc je osvětlený, tím dýl mu trvá než se srovná.
Uživatelský avatar
JPLABS
Příspěvky: 173
Registrován: 13 čer 2023, 22:45
Kontaktovat uživatele:

Re: Automatické rozsvícení světel do šera nebo tunelu - BH1750

Příspěvek od JPLABS »

vojta_1 píše: 20 čer 2025, 19:21 Níže je kompletní kód pro Arduino Nano, který řeší automatické ovládání světel v šeru a vjeti do tunelu.
Chybí mi tu dva podstatné údaje:

1/ CO vjíždí do tunelu? Auto který řídíš? Anebo mašinka na modelové železnici?
2/ Jaká světla se mají ovládat? U auta který řídíš to mají být světla, reflektory na autě? Anebo na modelové železnici osvětlení vagónků?

Docela zásadní informace. V případě ovládání reflektorů vpředu na autě (zapnout / vypnout), bude dost problém toto vůbec softwarově pořešit. Vyskytne se spousta různých situací. Například:
- v tunelu bývá také osvětlení, které je v mnoha tunelech nerovnoměrné a střídá se se stíny. K tomu ještě osvětlení od protijedoucích vozidel dále také různá barva (vlnová délka) světla osvětlení v tunelu. Někde je žlutá, jinde ostrá bílá. Také protijedoucí auta budou mít různá světla, bílá nebo žlutá, s různou intenzitou a v různé výšce (náklaďáky a autobusy je mají výš než osobáky).
- ve městě v noci na ulici jsou sloupy veřejného osvětlení. Také ty dávají leckde nerovnoměrné osvětlení ulici a mají různou barvu světla.
- na silnici na venkově mimo město s malým provozem bude problém s protijedoucími auty.

Myslím, že manuální zapnutí a vypnutí světel je nejjistější řešení. Představ si, že zrovna v kritickém okamžiku tvoje Božské Atduino ti vypne světla, ty se vybouráš, někoho při nehodě zabiješ nebo někdo tebe nabourá a ty se budeš hájit, že ty to nemůžeš, že za to může Božské Arduino, které jsi dodatečně instaloval do auta a které mělo zrovna chybu v umělé inteligenci.
Uživatelský avatar
JPLABS
Příspěvky: 173
Registrován: 13 čer 2023, 22:45
Kontaktovat uživatele:

Re: Automatické rozsvícení světel do šera nebo tunelu - BH1750

Příspěvek od JPLABS »

harr22 píše: 18 kvě 2026, 14:56 To driftování fotoodpor dělá...
Změna odporu u fotoodporu je dána materiálem, z kterého je vyroben a mění se s teplotou a také stárnutím. S časem, jak fotoodpor stárne, tak jeho citlivost klesá. Rychlost změny fotoodporu na osvícení je pomalá a také tato rychlost se s časem zhoršuje. Fotoodpory se kdysi, v minulém století, používaly jako luxmetry při fotografování. Na fotoodpor byl připojen mikroampérmetr. Ten ukázal výchylku podle natočení luxmetru směrem ke scéně, která se měla vyfotit. Podle toho fotograf nastavil na foťáku clonu, což byl de facto čas otevření uzávěrky objektivu. Toto vše je úsměvná minulost, kterou si Arduinisté vůbec nedokáží představit.
Nicméně, fotoodpory se používají i dnes a to v součástkách, optoizolátorech, který se říká vactrol. Ty se stále vyrábějí a používají ve zvukové technice (napětím řízený potenciometr nebo oscilátor) anebo v měřících přístrojích, například pro přenos dat mezi vysokonapěťovou a nízkonapěťovou soustavou (např. pří snímání proudu na vn).
vojta_1
Příspěvky: 19
Registrován: 03 čer 2023, 15:20

Re: Automatické rozsvícení světel do šera nebo tunelu - BH1750

Příspěvek od vojta_1 »

Zdravím a díky za reakci! V mnoha věcech máte pravdu, ale koukám, že došlo k drobnému nedorozumění – v článku totiž vůbec nepíšu o fotoodporu (LDR), ale o digitálním senzoru BH1750. To je úplně jiná liga.

Abych odpověděl na Vaše otázky a obavy:

1. O jaké vozidlo jde a jak je to s bezpečností?
Jedná se o běžné auto. Bezpečnost je samozřejmě na prvním místě, proto se takový modul nikdy nezapojuje do série tak, aby mohl světla sám úplně zhasnout. Zapojuje se paralelně k páčce (nebo přepíná pouze režim denní svícení / potkávací světla). Hlavní vypínač na páčce má vždycky prioritu. Když Arduino selže, světla zůstanou svítit nebo je zapnu ručně. Žádné nebezpečí nehody kvůli 'chybě v umělé inteligenci' nehrozí, žádná AI v tom neběží.

2. Jak senzor zvládá blikání lamp, stíny a protijedoucí kamiony?
To, co popisujete, byl přesně problém starých fotoodporů a analogových obvodů s LM324. Digitální senzor BH1750 to ale řeší naprosto elegantně softwarově:

Není to fotoodpor: BH1750 nestárne jako kadmiové buňky a netrpí na teplotní drift. Má integrovanou teplotní kompenzaci a z výroby kalibrovaný výstup přímo v Luxech (lx).

Lidské oko vs. Žárovka: Senzor má spektrální filtr, který ignoruje infračervené světlo. Reaguje na světlo stejně jako lidské oko, takže ho západ slunce nebo žlutá výbojka nerozhodí tak jako starou fotodiodu.

Časový filtr v kódu: Problém s přebliknutím lampy nebo stínem pod mostem se řeší jednoduše: 'Pokud intenzita klesne pod 20 luxů a vydrží tam nepřetržitě déle než 1.5 vteřiny, teprve sepni světla.' Krátké záblesky od protijedoucích aut nebo pouličních lamp program jednoduše odfiltruje (ignoruje). Výjezd z tunelu má zase nastavené zpoždění třeba 5 vteřin, aby světla nezhasínala pod každou druhou lampou.

Zmínka o vactrolech a historických expozimetrech je super (ve audio technice se vactroly na kompresi zvuku používají dodnes právě kvůli své specifické lenosti!), ale pro automatické rozsvěcování světel v roce 2026 už máme zkrátka spolehlivější a digitální nástroje. S pozdravem..
vojta_1
Příspěvky: 19
Registrován: 03 čer 2023, 15:20

Re: Automatické rozsvícení světel do šera nebo tunelu - BH1750

Příspěvek od vojta_1 »

UPDATE PROJEKTU: trochu jsem se nudil ...

Ahoj všem, posunul jsem se s automatem na světla zase o kus dál a chci se podělit o aktuální stav. Jak jsem psal minule, rozhodl jsem se jít cestou plné digitální kontroly.
Nechtěl jsem žádné topící výkonové odpory, takže napájení nakonec jistí robustní lineární stabilizátor 9V (typ 7809 v pouzdře TO-220) doplněný o ochrannou diodu (1N4007)
a filtrační kondenzátory. Odběr celé logiky je minimální (kolem 15–60 mA), takže stabilizátor zůstává i bez chladiče naprosto chladný.

Aktuálně mám celou elektroniku poskládanou na zkušební desce (breadboardu), abych mohl doladit software před finálním pájením na univerzál.

Změna displeje: SH1106 v akci
Původně zamýšlený malý displej 0.96" jsem nakonec vyměnil za větší 1.3" OLED s čipem SH1106, kterých mám doma dost. V autě to bude vypadat o dost
seriózněji a za jízdy je menu mnohem čitelnější.
Zapojení zůstává na I2C sběrnici (4 dráty). V kódu jsem musel sice kvůli jiné knihovně (Adafruit_SH110X) opravit pár příkazů pro uspávání, ale teď už všechno
šlape bez jediné chyby.

Jak to teď funguje na stole?
Do softwaru se mi povedlo implementovat kompletní stavový automat pro ovládání třemi tlačítky ([UP], [MENU/OK], [DOWN]):
Uvítací sekvence: Po nasimulování otočení klíčku (přivedení napětí na pin 2 přes odporový dělič) displej projede stylovou animací načítacího pruhu (loading bar) a bzučák dvakrát pípne.
Hlavní obrazovka: Neustále vypisuje reálný jas z I2C senzoru BH1750, aktuální režim (Automatika/Manuál) a stav relátek (Denní svícení / Potkávací světla).
Komfortní MENU: Po stisku prostředního tlačítka se dá listovat a měnit práh tmy (v luxech), hysteréze (aby světla za šera neblikala) a časové filtry zpoždění.
Vše se po potvrzení ukládá do EEPROM paměti, takže se nastavení nesmaže ani po odpojení autobaterie.
Simulace alarmu: Jakmile odpojím signál klíčku zapalování, software okamžitě nuceně zhasne relé světel, displej komplet uspí (vypne obraz), aby šetřil baterii auta, a stavová
LEDka se přepne do režimu blikání alarmu (v menu mám na výběr klasické pomalé blikání nebo moderní ostrý „dvojblik“).

Kód obsahuje i nouzový režim – pokud by za jízdy upadl senzor, jednotka odepne relé, začne přerušovaně pískat a na displeji vyhodí velké varování, přičemž se na pozadí neustále
pokouší o restart I2C sběrnice.

Kod:

Kód: Vybrat vše

#include <Wire.h>
#include <BH1750.h>
#include <EEPROM.h>
#include <Adafruit_GFX.h>
#include <Adafruit_SH110X.h> // Knihovna pro SH1106

// DEFINICE DISPLEJE (SH1106 1.3")
#define SCREEN_WIDTH 128
#define SCREEN_HEIGHT 64
#define OLED_RESET    -1
Adafruit_SH1106G display = Adafruit_SH1106G(SCREEN_WIDTH, SCREEN_HEIGHT, &Wire, OLED_RESET);

// DEFINICE PINŮ
const int PIN_KLICEK = 2;       // Vstup od zapalování (přes odporový dělič!)
const int BTN_UP = 5;           // Tlačítko Nahoru
const int BTN_MENU = 6;         // Tlačítko Menu / Potvrdit
const int BTN_DOWN = 9;         // Tlačítko Dolů

const int RELAY_OBRYSOVA = 3; 
const int RELAY_POTKAVACI = 4; 
const int LED_STATUS = 7;       // Stavová LED (svítí při zapnutých světlech)
const int LED_ALARM = 10;       // LED autoalarmu (bliká při vypnutém klíčku)
const int BUZZER_PIN = 8;      

BH1750 lightMeter;

// STRUKTURA KONFIGURACE (Ukládá se do EEPROM najednou)
struct Config {
  float threshold;
  float hysteresis;
  unsigned long darkDelay;
  unsigned long lightDelay;
  byte alarmMode; // 0 = Vypnuto, 1 = Pomalurost, 2 = Dvojblik
  byte manualOverride; // 0 = AUTO, 1 = MANUÁL (Trvale zapnuto)
};

Config cfg = {30.0, 10.0, 2000, 15000, 1, 0}; // Výchozí hodnoty

// Globální časovače a proměnné
unsigned long darkTimer = 0;
unsigned long lightTimer = 0;
unsigned long displayTimer = 0;
unsigned long alarmTimer = 0;
bool lightsOn = false;
bool sensorError = false;
bool isIgnitionOn = false;

// Proměnné pro MENU
byte menuState = 0; // 0 = Hlavní obrazovka, 1 = V menu, 2 = Nastavování hodnoty
byte menuIndex = 1; // Aktuální řádek v menu
const byte MAX_MENU_ITEMS = 6;

// Deklarace funkcí (aby kompilátor věděl o všem)
void beep(int duration);
void handleAutomaticLights(float lux);
void handleAlarmLED();
void handleSensorError();
void handleButtons();
void adjustValue(byte index, int direction);
void drawDisplay(float lux);

void setup() {
  Serial.begin(9600); 
  
  pinMode(PIN_KLICEK, INPUT); 
  pinMode(BTN_UP, INPUT_PULLUP);
  pinMode(BTN_MENU, INPUT_PULLUP);
  pinMode(BTN_DOWN, INPUT_PULLUP);

  pinMode(RELAY_OBRYSOVA, OUTPUT);
  pinMode(RELAY_POTKAVACI, OUTPUT);
  pinMode(LED_STATUS, OUTPUT);
  pinMode(LED_ALARM, OUTPUT);
  pinMode(BUZZER_PIN, OUTPUT);

  // Vypnutí relé při startu (LOW-LEVEL relé = HIGH je vypnuto)
  digitalWrite(RELAY_OBRYSOVA, HIGH);
  digitalWrite(RELAY_POTKAVACI, HIGH);
  digitalWrite(LED_STATUS, LOW); 
  digitalWrite(LED_ALARM, LOW);

  Wire.begin();
  
  // Start SH1106 displeje
  if(!display.begin(0x3C, true)) { 
    Serial.println(F("SH1106 displej nenalezen!"));
  }
  
  // ==========================================
  // STYLOVÉ UVÍTÁNÍ (WELCOME SCREEN)
  // ==========================================
  display.clearDisplay();
  display.setTextColor(SH110X_WHITE);
  
  // Rámeček kolem celého displeje
  display.drawRect(0, 0, 128, 64, SH110X_WHITE);
  
  // Hlavní nápis uprostřed
  display.setTextSize(2);
  display.setCursor(15, 15);
  display.print(F("SYSTEM"));
  display.setCursor(15, 35);
  display.setTextSize(1);
  display.print(F("SMART LIGHTS v1.0"));
  display.display();
  
  // První uvítací pípnutí
  beep(100); 
  delay(600);
  
  // Animace načítacího pruhu (Loading bar) dole na displeji
  for (int i = 15; i <= 110; i += 5) {
    display.fillRect(i, 52, 4, 4, SH110X_WHITE); 
    display.display();
    delay(40); 
  }
  
  // Druhé potvrzující pípnutí po dokončení načítání
  beep(100);
  delay(300);
  
  // Start senzoru BH1750
  if (!lightMeter.begin(BH1750::CONTINUOUS_HIGH_RES_MODE)) {
    sensorError = true;
  }

  // Načtení kompletní konfigurace z EEPROM
  Config loadedCfg;
  EEPROM.get(0, loadedCfg);
  if (!isnan(loadedCfg.threshold) && loadedCfg.threshold > 0 && loadedCfg.threshold < 1000) {
    cfg = loadedCfg;
  }
  
  display.clearDisplay();
  display.display();
}

void loop() {
  // Kontrola stavu klíčku zapalování
  isIgnitionOn = (digitalRead(PIN_KLICEK) == HIGH);

  if (!isIgnitionOn) {
    // REŽIM: AUTO STOJÍ (KLÍČEK VYPNUTÝ)
    if (lightsOn) {
      digitalWrite(RELAY_POTKAVACI, HIGH);
      digitalWrite(RELAY_OBRYSOVA, HIGH);
      digitalWrite(LED_STATUS, LOW);
      lightsOn = false;
    }
    display.oled_command(SH110X_DISPLAYOFF); // Uspat SH1106
    handleAlarmLED();
    delay(50); 
    return; 
  }

  // REŽIM: AUTO JEDE (KLÍČEK ZAPNUTÝ)
  display.oled_command(SH110X_DISPLAYON); // Probudit SH1106
  digitalWrite(LED_ALARM, LOW); 

  float lux = lightMeter.readLightLevel();
  
  if (lux < 0 || sensorError) {
    handleSensorError();
    return; 
  }

  handleButtons();

  if (menuState == 0) {
    if (cfg.manualOverride == 1) {
      if (!lightsOn) {
        digitalWrite(RELAY_OBRYSOVA, LOW);
        delay(1000);
        digitalWrite(RELAY_POTKAVACI, LOW);
        digitalWrite(LED_STATUS, HIGH);
        lightsOn = true;
      }
    } else {
      handleAutomaticLights(lux);
    }
  }

  // Obnovení displeje (každých 200 ms pro plynulost bez blikání)
  if (millis() - displayTimer >= 200) {
    displayTimer = millis();
    drawDisplay(lux);
  }

  delay(20);
}

void beep(int duration) {
  digitalWrite(BUZZER_PIN, HIGH);
  delay(duration);
  digitalWrite(BUZZER_PIN, LOW);
}

void handleAutomaticLights(float lux) {
  if (!lightsOn) {
    if (lux < cfg.threshold) {
      if (darkTimer == 0) darkTimer = millis();
      if (millis() - darkTimer >= cfg.darkDelay) {
        digitalWrite(RELAY_OBRYSOVA, LOW);    
        delay(1000);                          
        digitalWrite(RELAY_POTKAVACI, LOW);   
        digitalWrite(LED_STATUS, HIGH);         
        lightsOn = true;
        darkTimer = 0;
      }
    } else {
      darkTimer = 0;
    }
  } 
  else {
    if (lux > (cfg.threshold + cfg.hysteresis)) {
      if (lightTimer == 0) lightTimer = millis();
      if (millis() - lightTimer >= cfg.lightDelay) {
        digitalWrite(RELAY_POTKAVACI, HIGH);  
        delay(1000);                          
        digitalWrite(RELAY_OBRYSOVA, HIGH);   
        digitalWrite(LED_STATUS, LOW);          
        lightsOn = false;
        lightTimer = 0;
      }
    } else {
      lightTimer = 0;
    }
  }
}

void handleAlarmLED() {
  if (cfg.alarmMode == 0) {
    digitalWrite(LED_ALARM, LOW);
    return;
  }
  unsigned long currentMillis = millis();
  if (cfg.alarmMode == 1) {
    if (currentMillis - alarmTimer >= 2000) {
      alarmTimer = currentMillis;
      digitalWrite(LED_ALARM, HIGH); delay(50); digitalWrite(LED_ALARM, LOW);
    }
  } 
  else if (cfg.alarmMode == 2) {
    if (currentMillis - alarmTimer >= 1500) {
      alarmTimer = currentMillis;
      digitalWrite(LED_ALARM, HIGH); delay(30); digitalWrite(LED_ALARM, LOW);
      delay(100);
      digitalWrite(LED_ALARM, HIGH); delay(30); digitalWrite(LED_ALARM, LOW);
    }
  }
}

void handleSensorError() {
  digitalWrite(RELAY_POTKAVACI, HIGH); 
  digitalWrite(RELAY_OBRYSOVA, HIGH);
  digitalWrite(LED_STATUS, LOW);
  lightsOn = false;
  sensorError = true;

  display.clearDisplay();
  display.setCursor(0,10);
  display.setTextSize(2);
  display.print(F(" CHYBA !"));
  display.setCursor(0,35);
  display.setTextSize(1);
  display.print(F("Senzor nedostupny"));
  display.display();

  beep(100);
  delay(900);
  
  Wire.begin();
  if (lightMeter.begin(BH1750::CONTINUOUS_HIGH_RES_MODE)) {
    sensorError = false;
    beep(50); delay(50); beep(50);
  }
}

void handleButtons() {
  if (digitalRead(BTN_MENU) == LOW) {
    delay(200); 
    beep(50);
    if (menuState == 0) {
      menuState = 1; 
    } else if (menuState == 1) {
      if (menuIndex == MAX_MENU_ITEMS) {
        EEPROM.put(0, cfg);
        menuState = 0;
        beep(200);
      } else {
        menuState = 2; 
      }
    } else if (menuState == 2) {
      menuState = 1; 
    }
    while(digitalRead(BTN_MENU) == LOW);
  }

  if (digitalRead(BTN_UP) == LOW) {
    delay(150); beep(30);
    if (menuState == 1) {
      menuIndex--;
      if (menuIndex < 1) menuIndex = MAX_MENU_ITEMS;
    } else if (menuState == 2) {
      adjustValue(menuIndex, 1);
    }
  }
  
  if (digitalRead(BTN_DOWN) == LOW) {
    delay(150); beep(30);
    if (menuState == 1) {
      menuIndex++;
      if (menuIndex > MAX_MENU_ITEMS) menuIndex = 1;
    } else if (menuState == 2) {
      adjustValue(menuIndex, -1);
    }
  }
}

void adjustValue(byte index, int direction) {
  switch(index) {
    case 1: 
      cfg.manualOverride = (cfg.manualOverride == 0) ? 1 : 0;
      break;
    case 2: 
      cfg.threshold += (direction * 5);
      if (cfg.threshold < 5) cfg.threshold = 5;
      if (cfg.threshold > 300) cfg.threshold = 300;
      break;
    case 3: 
      cfg.hysteresis += (direction * 2);
      if (cfg.hysteresis < 2) cfg.hysteresis = 2;
      if (cfg.hysteresis > 50) cfg.hysteresis = 50;
      break;
    case 4: 
      if (direction > 0) cfg.darkDelay += 500;
      else if (cfg.darkDelay >= 1000) cfg.darkDelay -= 500;
      if (cfg.darkDelay < 500) cfg.darkDelay = 500;
      if (cfg.darkDelay > 10000) cfg.darkDelay = 10000;
      break;
    case 5: 
      if (direction > 0) {
        cfg.alarmMode++;
        if (cfg.alarmMode > 2) cfg.alarmMode = 0;
      } else {
        if (cfg.alarmMode == 0) cfg.alarmMode = 2;
        else cfg.alarmMode--;
      }
      break;
  }
}

void drawDisplay(float lux) {
  display.clearDisplay();
  
  if (menuState == 0) {
    display.setTextSize(1);
    display.setCursor(0, 0);
    display.print(F("SVETLO: "));
    display.print((int)lux);
    display.print(F(" lx"));
    
    display.drawFastHLine(0, 12, 128, SH110X_WHITE);
    
    display.setCursor(0, 20);
    display.print(F("REJIM: "));
    if (cfg.manualOverride == 1) display.print(F("MANUAL (ZAP)"));
    else display.print(F("AUTOMATIKA"));
    
    display.setCursor(0, 36);
    display.print(F("SVETLA: "));
    if (lightsOn) display.print(F("POTKAVACI (ZAP)"));
    else display.print(F("DENNI SVICENI"));
    
    display.setCursor(0, 52);
    display.print(F("ALARM LED: "));
    if (cfg.alarmMode == 0) display.print(F("VYPNUTO"));
    else display.print(F("AKTIVNI"));
    
  } else {
    display.setTextSize(1);
    display.setCursor(0, 0);
    display.print(menuState == 2 ? F("[NASTAVENI]") : F("[HLAVNI MENU]"));
    display.drawFastHLine(0, 10, 128, SH110X_WHITE);

    int startY = 15;
    for (int i = 1; i <= MAX_MENU_ITEMS; i++) {
      int rowY = startY + (i - 1) * 8;
      if (rowY > 56) break; 
      
      display.setCursor(0, rowY);
      if (menuIndex == i) {
        display.print(menuState == 2 ? F(" > ") : F("-> "));
      } else {
        display.print(F("   "));
      }
      
      switch(i) {
        case 1: display.print(F("Rezim: ")); display.print(cfg.manualOverride == 0 ? F("AUTO") : F("MANUAL")); break;
        case 2: display.print(F("Prah: ")); display.print((int)cfg.threshold); display.print(F(" lx")); break;
        case 3: display.print(F("Hyst: ")); display.print((int)cfg.hysteresis); display.print(F(" lx")); break;
        case 4: display.print(F("Zpozd: ")); display.print(cfg.darkDelay / 1000.0, 1); display.print(F(" s")); break;
        case 5: display.print(F("Alarm: ")); 
                if(cfg.alarmMode == 0) display.print(F("OFF"));
                if(cfg.alarmMode == 1) display.print(F("Pomal"));
                if(cfg.alarmMode == 2) display.print(F("Dvojblik"));
                break;
        case 6: display.print(F("ULOZIT & ODCHOD")); break;
      }
    }
  }
  display.display();
}
Přílohy
schema.zip
(653 bajtů) Zatím ještě nestaženo
vojta_1
Příspěvky: 19
Registrován: 03 čer 2023, 15:20

Re: Automatické rozsvícení světel do šera nebo tunelu - BH1750

Příspěvek od vojta_1 »

Tu je kód pro enkodér je to jednoduší než mikrospínače, místo děliče použit použit 5V malí stabilizátor je to bezpečnější ..
kód:

Kód: Vybrat vše

#include <Wire.h>
#include <BH1750.h>
#include <EEPROM.h>
#include <Adafruit_GFX.h>
#include <Adafruit_SH110X.h> // Knihovna pro SH1106

// DEFINICE DISPLEJE (SH1106 1.3")
#define SCREEN_WIDTH 128
#define SCREEN_HEIGHT 64
#define OLED_RESET    -1
Adafruit_SH1106G display = Adafruit_SH1106G(SCREEN_WIDTH, SCREEN_HEIGHT, &Wire, OLED_RESET);

// DEFINICE PINŮ pro ROTAČNÍ ENKODÉR
const int PIN_CLK = 5;         // CLK (Output A)
const int PIN_DT = 6;          // DT (Output B)
const int PIN_SW = 7;          // SW (Tlačítko enkodéru)

const int PIN_KLICEK = 2;       // Vstup od zapalování
const int RELAY_OBRYSOVA = 3; 
const int RELAY_POTKAVACI = 4; 
const int LED_STATUS = 9;       // Stavová LED 
const int LED_ALARM = 10;       // LED autoalarmu 
const int BUZZER_PIN = 8;      

BH1750 lightMeter;

struct Config {
  float threshold;
  float hysteresis;
  unsigned long darkDelay;
  unsigned long lightDelay;
  byte alarmMode; 
  byte manualOverride; 
};

Config cfg = {30.0, 10.0, 2000, 15000, 1, 0}; 

// Globální časovače a proměnné
unsigned long darkTimer = 0;
unsigned long lightTimer = 0;
unsigned long displayTimer = 0;
unsigned long alarmTimer = 0;
bool lightsOn = false;
bool sensorError = false;
bool isIgnitionOn = false;

// Proměnné pro MENU
byte menuState = 0; // 0 = Hlavní obrazovka, 1 = V menu, 2 = Nastavování hodnoty
byte menuIndex = 1; 
const byte MAX_MENU_ITEMS = 6;

// Proměnné pro čtení enkodéru
int lastClkState;

void beep(int duration) {
  digitalWrite(BUZZER_PIN, HIGH);
  delay(duration);
  digitalWrite(BUZZER_PIN, LOW);
}

void setup() {
  Serial.begin(9600); 
  
  pinMode(PIN_KLICEK, INPUT); 
  
  // Nastavení pinů enkodéru
  pinMode(PIN_CLK, INPUT_PULLUP);
  pinMode(PIN_DT, INPUT_PULLUP);
  pinMode(PIN_SW, INPUT_PULLUP);

  pinMode(RELAY_OBRYSOVA, OUTPUT);
  pinMode(RELAY_POTKAVACI, OUTPUT);
  pinMode(LED_STATUS, OUTPUT);
  pinMode(LED_ALARM, OUTPUT);
  pinMode(BUZZER_PIN, OUTPUT);

  digitalWrite(RELAY_OBRYSOVA, HIGH);
  digitalWrite(RELAY_POTKAVACI, HIGH);
  digitalWrite(LED_STATUS, LOW); 
  digitalWrite(LED_ALARM, LOW);

  Wire.begin();
  
  if(!display.begin(0x3C, true)) { 
    Serial.println(F("SH1106 displej nenalezen!"));
  }
  
  // Načtení výchozího stavu enkodéru
  lastClkState = digitalRead(PIN_CLK);
  
  // ==========================================
  // STYLOVÉ UVÍTÁNÍ (WELCOME SCREEN)
  // ==========================================
  display.clearDisplay();
  display.setTextColor(SH110X_WHITE);
  display.drawRect(0, 0, 128, 64, SH110X_WHITE);
  
  display.setTextSize(2);
  display.setCursor(15, 15);
  display.print(F("SYSTEM"));
  display.setCursor(15, 35);
  display.setTextSize(1);
  display.print(F("SMART LIGHTS v1.1")); // Verze 1.1 s enkodérem
  display.display();
  
  beep(100); 
  delay(600);
  
  for (int i = 15; i <= 110; i += 5) {
    display.fillRect(i, 52, 4, 4, SH110X_WHITE); 
    display.display();
    delay(40); 
  }
  
  beep(100);
  delay(300);
  
  if (!lightMeter.begin(BH1750::CONTINUOUS_HIGH_RES_MODE)) {
    sensorError = true;
  }

  Config loadedCfg;
  EEPROM.get(0, loadedCfg);
  if (!isnan(loadedCfg.threshold) && loadedCfg.threshold > 0 && loadedCfg.threshold < 1000) {
    cfg = loadedCfg;
  }
  
  display.clearDisplay();
  display.display();
}

void loop() {
  isIgnitionOn = (digitalRead(PIN_KLICEK) == HIGH);

  if (!isIgnitionOn) {
    if (lightsOn) {
      digitalWrite(RELAY_POTKAVACI, HIGH);
      digitalWrite(RELAY_OBRYSOVA, HIGH);
      digitalWrite(LED_STATUS, LOW);
      lightsOn = false;
    }
    display.oled_command(SH110X_DISPLAYOFF); 
    handleAlarmLED();
    delay(50); 
    return; 
  }

  display.oled_command(SH110X_DISPLAYON); 
  digitalWrite(LED_ALARM, LOW); 

  float lux = lightMeter.readLightLevel();
  
  if (lux < 0 || sensorError) {
    handleSensorError();
    return; 
  }

  // Obsluha enkodéru (otáčení i klikání)
  handleEncoder();

  if (menuState == 0) {
    if (cfg.manualOverride == 1) {
      if (!lightsOn) {
        digitalWrite(RELAY_OBRYSOVA, LOW);
        delay(1000);
        digitalWrite(RELAY_POTKAVACI, LOW);
        digitalWrite(LED_STATUS, HIGH);
        lightsOn = true;
      }
    } else {
      handleAutomaticLights(lux);
    }
  }

  if (millis() - displayTimer >= 200) {
    displayTimer = millis();
    drawDisplay(lux);
  }

  delay(2); // Zrychlená smyčka pro jemnější čtení enkodéru
}

void handleAutomaticLights(float lux) {
  if (!lightsOn) {
    if (lux < cfg.threshold) {
      if (darkTimer == 0) darkTimer = millis();
      if (millis() - darkTimer >= cfg.darkDelay) {
        digitalWrite(RELAY_OBRYSOVA, LOW);    
        delay(1000);                          
        digitalWrite(RELAY_POTKAVACI, LOW);   
        digitalWrite(LED_STATUS, HIGH);         
        lightsOn = true;
        darkTimer = 0;
      }
    } else {
      darkTimer = 0;
    }
  } 
  else {
    if (lux > (cfg.threshold + cfg.hysteresis)) {
      if (lightTimer == 0) lightTimer = millis();
      if (millis() - lightTimer >= cfg.lightDelay) {
        digitalWrite(RELAY_POTKAVACI, HIGH);  
        delay(1000);                          
        digitalWrite(RELAY_OBRYSOVA, HIGH);   
        digitalWrite(LED_STATUS, LOW);          
        lightsOn = false;
        lightTimer = 0;
      }
    } else {
      lightTimer = 0;
    }
  }
}

void handleEncoder() {
  // 1. ČTENÍ OTÁČENÍ
  int currentClkState = digitalRead(PIN_CLK);
  
  if (currentClkState != lastClkState && currentClkState == LOW) {
    // Pokud se stav CLK změnil, podíváme se na DT pro určení směru
    if (digitalRead(PIN_DT) != currentClkState) {
      // Otočení DOPRAVA (Směr nahoru / zvýšit)
      beep(15);
      if (menuState == 1) {
        menuIndex++;
        if (menuIndex > MAX_MENU_ITEMS) menuIndex = 1;
      } else if (menuState == 2) {
        adjustValue(menuIndex, 1);
      }
    } else {
      // Otočení DOLEVA (Směr dolů / snížit)
      beep(15);
      if (menuState == 1) {
        menuIndex--;
        if (menuIndex < 1) menuIndex = MAX_MENU_ITEMS;
      } else if (menuState == 2) {
        adjustValue(menuIndex, -1);
      }
    }
  }
  lastClkState = currentClkState;

  // 2. ČTENÍ KLIKNUTÍ (TLAČÍTKO SW)
  if (digitalRead(PIN_SW) == LOW) {
    delay(200); // Debounce pro tlačítko
    beep(50);
    if (menuState == 0) {
      menuState = 1; // Vstup do menu
    } else if (menuState == 1) {
      if (menuIndex == MAX_MENU_ITEMS) {
        EEPROM.put(0, cfg); // Uložit a odejít
        menuState = 0;
        beep(200);
      } else {
        menuState = 2; // Editace hodnoty
      }
    } else if (menuState == 2) {
      menuState = 1; // Návrat do listování
    }
    while(digitalRead(PIN_SW) == LOW); // Čekání na puštění tlačítka
  }
}

void adjustValue(byte index, int direction) {
  switch(index) {
    case 1: 
      cfg.manualOverride = (cfg.manualOverride == 0) ? 1 : 0;
      break;
   case 2: 
      cfg.threshold += (direction * 10); // Teď to bude skákat po 10 lx (rychlejší nastavení)
      if (cfg.threshold < 5) cfg.threshold = 5;
      if (cfg.threshold > 1000) cfg.threshold = 1000; // NOVÝ LIMIT DO 1000 lx
      break;
    case 3: 
      cfg.hysteresis += (direction * 2);
      if (cfg.hysteresis < 2) cfg.hysteresis = 2;
      if (cfg.hysteresis > 100) cfg.hysteresis = 100;
      break;
    case 4: 
      if (direction > 0) cfg.darkDelay += 500;
      else if (cfg.darkDelay >= 1000) cfg.darkDelay -= 500;
      if (cfg.darkDelay < 500) cfg.darkDelay = 500;
      if (cfg.darkDelay > 10000) cfg.darkDelay = 10000;
      break;
    case 5: 
      if (direction > 0) {
        cfg.alarmMode++;
        if (cfg.alarmMode > 2) cfg.alarmMode = 0;
      } else {
        if (cfg.alarmMode == 0) cfg.alarmMode = 2;
        else cfg.alarmMode--;
      }
      break;
  }
}

void handleAlarmLED() {
  if (cfg.alarmMode == 0) {
    digitalWrite(LED_ALARM, LOW);
    return;
  }
  unsigned long currentMillis = millis();
  if (cfg.alarmMode == 1) {
    if (currentMillis - alarmTimer >= 2000) {
      alarmTimer = currentMillis;
      digitalWrite(LED_ALARM, HIGH); delay(50); digitalWrite(LED_ALARM, LOW);
    }
  } 
  else if (cfg.alarmMode == 2) {
    if (currentMillis - alarmTimer >= 1500) {
      alarmTimer = currentMillis;
      digitalWrite(LED_ALARM, HIGH); delay(30); digitalWrite(LED_ALARM, LOW);
      delay(100);
      digitalWrite(LED_ALARM, HIGH); delay(30); digitalWrite(LED_ALARM, LOW);
    }
  }
}

void handleSensorError() {
  digitalWrite(RELAY_POTKAVACI, HIGH); 
  digitalWrite(RELAY_OBRYSOVA, HIGH);
  digitalWrite(LED_STATUS, LOW);
  lightsOn = false;
  sensorError = true;

  display.clearDisplay();
  display.setCursor(0,10);
  display.setTextSize(2);
  display.print(F(" CHYBA !"));
  display.setCursor(0,35);
  display.setTextSize(1);
  display.print(F("Senzor nedostupny"));
  display.display();

  beep(100);
  delay(900);
  
  Wire.begin();
  if (lightMeter.begin(BH1750::CONTINUOUS_HIGH_RES_MODE)) {
    sensorError = false;
    beep(50); delay(50); beep(50);
  }
}

void drawDisplay(float lux) {
  display.clearDisplay();
  
  if (menuState == 0) {
    display.setTextSize(1);
    display.setCursor(0, 0);
    display.print(F("SVETLO:=> "));
    display.print((int)lux);
    display.print(F(" lux"));
    
    display.drawFastHLine(0, 12, 128, SH110X_WHITE);
    
    display.setCursor(0, 20);
    display.print(F("REZIM : "));
    if (cfg.manualOverride == 1) display.print(F("MANUAL (ZAP)"));
    else display.print(F("AUTOMATIKA"));
    
    display.setCursor(0, 36);
    display.print(F("SVETLA: "));
    if (lightsOn) display.print(F("POTKAVACI"));
    else display.print(F("DENNI SVICENI"));
    
    display.setCursor(0, 52);
    display.print(F("ALARM LED: "));
    if (cfg.alarmMode == 0) display.print(F("VYPNUTO"));
    else display.print(F("AKTIVNI"));
    
  } else {
    display.setTextSize(1);
    display.setCursor(0, 0);
    display.print(menuState == 2 ? F("  ==> NASTAVENI <==") : F(" ==> HLAVNI MENU <=="));
    display.drawFastHLine(0, 10, 128, SH110X_WHITE);

    int startY = 15;
    for (int i = 1; i <= MAX_MENU_ITEMS; i++) {
      int rowY = startY + (i - 1) * 8;
      if (rowY > 56) break; 
      
      display.setCursor(0, rowY);
      if (menuIndex == i) {
        display.print(menuState == 2 ? F(" > ") : F("-> "));
      } else {
        display.print(F("   "));
      }
      
      switch(i) {
        case 1: display.print(F("Rezim: ")); display.print(cfg.manualOverride == 0 ? F("AUTO") : F("MANUAL")); break;
        case 2: display.print(F("Prah : ")); display.print((int)cfg.threshold); display.print(F(" lux")); break;
        case 3: display.print(F("Hyst : ")); display.print((int)cfg.hysteresis); display.print(F(" lux")); break;
        case 4: display.print(F("Zpozd: ")); display.print(cfg.darkDelay / 1000.0, 1); display.print(F(" sec")); break;
        case 5: display.print(F("Alarm: ")); 
                if(cfg.alarmMode == 0) display.print(F("OFF"));
                if(cfg.alarmMode == 1) display.print(F("Pomal"));
                if(cfg.alarmMode == 2) display.print(F("Dvojblik"));
                break;
        case 6: display.print(F("ULOZIT & ODCHOD")); break;
      }
    }
  }
  display.display();
}
Odpovědět