Oświetlenie w garażu sterowane przez Arduino

Zainstalowałem nowe oświetlenie w garażu. Długo zastanawiałem się jaki rodzaj światła wybrać – żarówki czy może LEDy. Ostatecznie wybór padł dość tradycyjny – świetlówki. Kupiłem dwie hermetyczne oprawy świetlówkowe. Główna jest na dwa źródła światła po 18W, a dodatkowa – na jedno. Oświetlenie będzie sterowane przez Arduino z modułem przekaźnikowym. Można się zastanawiać czy świetlówka i przekaźnik to dobre zestawienie i wiele osób dojdzie do wniosku, że zdecydowanie nie. Jak najbardziej musiałbym przyznać rację, gdyby zastosowany został tradycyjny starter. Ja jednak kupiłem oprawy ze starterami elektronicznymi. Czym się charakteryzują? Po włączeniu zasilania świetlówka zapala się praktycznie natychmiast. Szansa, że przekaźnik zostanie „zaspawany” znacząco więc spada.

Włącznik "dzwonkowy" podwójny - prawie

Przewody do światła i obudowy do przekaźników miałem już gotowe. Stare oświetlenie (tradycyjna żarówka na suficie) włączana była przez dwa włączniki „schodowe”. Ponieważ ktoś się trochę natrudził i pociągnął przewody od puszek, w których były włączniki, do jednej z dużych puszek elektrycznych, postanowiłem to wykorzystać. Wcześniej chciałem użyć po prostu przycisku dzwonkowego, który włączałby świetlówki na jakiś czas, ale trzy przewody dostępne „za darmo” do wykorzystania, skłoniły mnie do zastosowania podwójnego włącznika. Skąd jednak wziąć podwójny włącznik dzwonkowy? Istnieją takie – do sterowania roletami. Nie za drogie, dobrze działają, dla mnie w sam raz, jak się wydawało.

Wymyśliłem sobie, że krótkie przyciśnięcie prawego przycisku zapali główne światło na półtorej minuty. Dłuższe przytrzymanie spowoduje włączenie na dłużej – na przykład 2 godziny. Zapalenie na chwilę jest typowe dla sytuacji, gdy wsiadamy do samochodu i za chwilę wyjeżdżamy z garażu – a moment później światło samo gaśnie. Gdy chciałbym popracować w garażu, włączam światło na dłużej. Oczywiście w każdej chwili mogę je wyłączyć ponownie wciskając włącznik, ale gdybym zapomniał – zgaśnie po dłuższym czasie. Lewy przycisk działałby tak samo dla światła pomocniczego.

Jeżeli natomiast chciałbym otworzyć bramę (także dość typowe działanie), to powinno to się dziać po wciśnięciu obu przycisków równocześnie. I pewnie działoby się, gdyby włącznik od rolet działał tak jak podwójna wersja dzwonkowego (jak zapewniał sprzedawca). Okazuje się jednak, co dość sensowne, że włącznik posiada zabezpieczenie przed wciśnięciem obu klawiszy w jednym czasie (wszak podnoszenie i jednoszesne opuszczanie rolet nie jest normalne). Oczywiście specjalnie mnie to nie zraziło. Plan co do światła pozostał taki sam, natomiast dwoma bramami (wjazdową i garażową) będę sterował przez jeszcze dłuższe przytrzymanie przycisków (o czym przy okazji opisu bram).

Wyprowadzenie przewodów od włączników

Wracając do zabaw praktycznych – rozłączyłem przewody elektryczne od włączników schodowych, podłączyłem przełączniki od rolet i wkręciłem je do puszek. Pozostało wyprowadzić przewody do Arduino z puszki pod sufitem. Dla wygody połączyłem je złączkami wago.

Przełącznik roletowy posiada trzy wyjścia – jedno wspólne dla obu klawiszy i dwa załączane z pierwszym po wciśnięciu klawisza. Normalnie podłącza się do pierwszego fazę, a do pozostałych zasilanie urządzenia. W naszym przypadku wspólne wejście będzie połączone z masą Arduino, a pozostałe dwa z wejściami cyfrowymi. Wejścia będą podciągnięte do stanu wysokiego przez wewnętrzny rezystor pull-up. Bez styku będziemy mieć więc stan wysoki, natomiast po wciśnięciu przycisku – stan niski (przez zwarcie z masą).

Po przeciągnięciu przewodów, można uznać część „sprzętową” za skończoną. Trzeba więc przejść do oprogramowania. Tak, jak w przypadku domofonu z powiadomieniem mailowym, rozbudowywać będziemy program, który powstał przy okazji tworzenia systemu nawadniania ogródka.

Założenie, które przyjąłem jest takie, że włączać światło musi Arduino sam – bez pomocy głównego modułu sterującego. Głupio by było tracić tak podstawowe funkcje jak włączanie światła przy restarcie routera czy awarii switcha… W pierwszym kroku trochę rozszerzymy tablice obsługujące wejścia – dodając jedną przechowującą momenty ostatnich zmian stanów (analogicznie jak przy domofonie dla wejść analogowych) – na potrzeby sprawdzania czasu wciśnięcia przycisków. Dla czytelności kody określimy też trochę stałych (np. do odwołań do wejść i wyjść). Dla czasowego włączania światła (i innych zegarowych czynności) także tablice od wyjść zostały rozszerzone – o czas następnej zmiany i stan oczekiwany po niej.

// tablica stanów wejść
int input[]={HIGH, HIGH};
int input_pins[]={36, 38};
unsigned long input_lastchange[]={0,0};
#define INPUT_SIZE 2 // liczba elementów w tablicy

#define GARAGEKEY1 1
#define GARAGEKEY2 0
#define MEDIUMKEYPRESS 1000
#define LONGKEYPRESS 5000

// tablica (domyślnych) stanów wyjść - stan wysoki wyłącza przekaźnik
int output[]={HIGH, HIGH, HIGH, HIGH, HIGH, HIGH, HIGH, HIGH, HIGH, HIGH, HIGH, HIGH, HIGH, HIGH};
int output_pins[]={23,25,27,29,31,33,35,37,39,41,43,45,47,49};
unsigned long output_next_change_time[]={0,0,0,0,0,0,0,0,0,0,0,0,0,0};
int output_next_change_state[]={HIGH, HIGH, HIGH, HIGH, HIGH, HIGH, HIGH, HIGH, HIGH, HIGH, HIGH, HIGH, HIGH, HIGH};
#define OUTPUT_SIZE 14

#define GARAGELIGHT1 12
#define GARAGELIGHT2 10

Teraz wypadłoby rozbudować funkcję checkInputs, bo to w niej będziemy też reagować na zdarzenia pochodzące z wejść, które mamy obsłużyć na poziomie Arduino. Czytelność tego fragmentu zapewniają wprowadzone wcześniej stałe.

void checkInputs()
{
  int inp, w;

  for(w=0; w<INPUT_SIZE; w++ )
  {
    inp=digitalRead(input_pins[w]);
    if(inp!=input[w] && isAfter(input_lastchange[w]+INPUTDEBOUNCETIME) )
    { 
      // przycisk od światła głównego
      if(w==GARAGEKEY1)
      {
        // zapalamy po wciśnięciu
        if( inp==LOW && output[GARAGELIGHT1]==HIGH ) 
        {
          runCommand( String(String(ID)+"c90000") );
          runCommand( String(String(ID)+"l"+String(GARAGELIGHT1)) );
        }
        // gasimy jeżeli było zapalone
        else if( inp==LOW && output[GARAGELIGHT1]==LOW ) 
        {
          runCommand( String(String(ID)+"h"+String(GARAGELIGHT1)) );
        }
        // zapalamy na dłużej
        else if( inp==HIGH && output[GARAGELIGHT1]==LOW && isAfter(input_lastchange[w]+MEDIUMKEYPRESS) )
        {
          runCommand( String(String(ID)+"c7200000") );
          runCommand( String(String(ID)+"l"+String(GARAGELIGHT1)) );          
        }
      }

      //przycisk światła pomocniczego
      if(w==GARAGEKEY2)
      {
        if( inp==LOW && output[GARAGELIGHT2]==HIGH ) 
        {
          runCommand( String(String(ID)+"c90000") );
          runCommand( String(String(ID)+"l"+String(GARAGELIGHT2)) );
        }
        else if( inp==LOW && output[GARAGELIGHT2]==LOW ) 
        {
          runCommand( String(String(ID)+"h"+String(GARAGELIGHT2)) );
        }
        else if( inp==HIGH && output[GARAGELIGHT2]==LOW && isAfter(input_lastchange[w]+MEDIUMKEYPRESS) )
        {
          runCommand( String(String(ID)+"c7200000") );
          runCommand( String(String(ID)+"l"+String(GARAGELIGHT2)) );          
        }        
      }

      input[w]=inp; 
      input_lastchange[w]=time;
      sendMessage('i', String(String(w)+inp) );
    }
  }
}

W ten sposób mamy obsłużone działania na oświetleniu, opisane wcześniej. To co się rzuca w oczy, to zamiast operować na stanach przekaźników bezpiśrednio, wywołujemy funkcję runCommand, która interpretuje komendy (tak jak przy poleceniach z systemu nadrzędnego odbieranych przez http). Tak, jest to mało optymalne, dużo szybciej byłoby działać wprost na tablicach i wejściach/wyjściach. Jest natomiast bardzo czytelne i wygodne, więc do czasu aż nie będzie to problemem (a nie będzie) – tak zostaje.

W powyższym kodzie można zauważyć dwie rzeczy – nieznaną wcześniej funkcję isAfter oraz komendę c. Funkcja isAfter, to rozwiązanie problemu z przekroczeniem zakresu licznika czasu, o którym pisałem przy okazji tekstu o domofonie. Ma ona za cel sprawdzanie, czy minął już czas określony przez liczbę typu long, przy czym ma brać pod uwagę, że po przekręceniu zakresu liczby z wielkich robią się małe. Założeniem dla jej działania (by była maksymalnie prosta) jest to, że będziemy planować raczej czasy rzędu mikrosekund, sekund, minut czy godzin, a nie tygodni.

// czy timer już nadszedł lub minął (jest mniejszy od time); zakładamy, że czasy nie są dłuższe niż kilka dni i sprawdzamy czy nie miało szansy przekroczenie zakresów
boolean isAfter( unsigned long timer )
{
  if(timer<=time && !(time>3000000000L && timer<1000000000L) ) return(true);
  if(timer>3000000000L && time<1000000000L) return(true);
  return(false);
}

Komenda c ma także związek z czasem. Jej wysłanie ustawia zmienną globalną change_time, która określi na jaki czas ma być wykonane następne polecenie ustawienia stanu niskiego (komenda l) bądź wysokiego (komenda h) wyjścia. W związku z tym musimy przebudować runCommands w sekcji dotyczącej tych trzech poleceń.

    case 'h': // ustawienie stanu wyjścia na wysoki
      w=param.toInt();
      if( w<0 || w>OUTPUT_SIZE ) return("Error");
      output[w]=HIGH;
      if( change_time!=0 )
      {
        output_next_change_time[w]=time+change_time;
        output_next_change_state[w]=LOW;
        if( output_next_change_time[w]==0 ) output_next_change_time[w]++;
        change_time=0;
      }
      digitalWrite(output_pins[w], HIGH);
      return("1");
      break;

    case 'l': // ustawienie stanu wyjścia na niski
      w=param.toInt();
      if( w<0 || w>OUTPUT_SIZE ) return("Error");
      output[w]=LOW;
      if( change_time!=0 )
      {
        output_next_change_time[w]=time+change_time;
        output_next_change_state[w]=HIGH;
        if( output_next_change_time[w]==0 ) output_next_change_time[w]++;
        change_time=0;
      }      
      digitalWrite(output_pins[w], LOW);
      return("0");
      break;

    case 'c': // ustawia czas na jaki ma być zmiana wyjścia
      param.toCharArray(charparam, 14);
      change_time=atol(charparam);
      return( String(change_time) );
      break;

Oczywiście to nie wszystko i trzeba jeszcze sprawdzać czy nie minął czas dla wyjść, a gdy minął – przestawić stan. Odpowiedzialna jest za to, wywoływana z głównej pętli, funckja checkOutputTimes.

// sprawdza czy nie nadszedł czas zaplanowanego przełączenia stanów
void checkOutputTimes()
{
  int w;

  for(w=0; w<OUTPUT_SIZE; w++ )
  {
    if( output_next_change_time[w]!=0 )
    {
      if( isAfter( output_next_change_time[w] ) )
      {
        output_next_change_time[w]=0;
        output[w]=output_next_change_state[w];
        digitalWrite(output_pins[w], output[w]);
      }
    }
  }
}

To chyba wszystko w temacie sterowania oświetleniem w garażu przy użyciu przycisków ściennych. Nie wyczerpuje oczywiście całego tematu, bo światło będzie włączane także w innych przypadkach – np. przy otwieraniu bramy garażowej z samochodu – przynajmniej gdy jest już ciemno na zewnątrz. Gdyby jedynym celem było włączanie światła przez przycisk, to tradycyjne elektryczne metody byłyby znacznie lepsze – bo prostsze.

Ten wpis został opublikowany w kategorii Inteligentny Dom i oznaczony tagami , , , . Dodaj zakładkę do bezpośredniego odnośnika.

0 odpowiedzi na Oświetlenie w garażu sterowane przez Arduino

  1. Marek Gawryliszyn pisze:

    witam serdecznie mam pytania proszę podpowiedzieć jaki nam używa przekaźnik i w jaki sposób odczytuje man stan położenia klawisza i czy jest pan wstanie udostępnić cały kod z bibliotekami

  2. p1 pisze:

    Jak daleko masz arduino oddalone od przycisków? Czy nie sprawia problemów bezpośrednie podłączenie długich przewodów pod piny mikrokontrolera?

  3. Amtec pisze:

    Dzięki za solidny artykuł. Masa praktycznych informacji, a nie jest łatwo znaleźć dobre informacje o programowaniu.

Skomentuj p1 Anuluj pisanie odpowiedzi

Twój adres e-mail nie zostanie opublikowany. Wymagane pola są oznaczone *