Pojďme programovat elektroniku: Hackneme ultralevný Sonoff a vyrobíme si vlastní Wi-Fi lampu

  • Sonoff Basic je čínský Wi-Fi spínač za 100 korun
  • Nahrajeme do něj vlastní firmware z Arduina
  • V jeho nitru je totiž populární čip ESP8285/8266

Kdysi před rokem na začátku našeho seriálu jsme si naprogramovali drobnou destičku Wemos D1 Mini s Wi-Fi čipem ESP8266 a rozšiřujícím modulem s elektromagnetickým relé, které pak na povel spínalo, nebo naopak vypínalo 230V okruh pokojové lampy.

Fungovalo to, ale mělo to dvě vady. Byl to ošklivý bastl bez hezké krabičky a desku bylo zároveň třeba separátně napájet třeba skrze její konektor microUSB a běžný 5V zdroj. Dnes se proto k elektromagnetickému spínači vrátíme, ale namísto Wemosu použijeme čínský Sonoff Basic.

230V Wi-Fi spínač Sonoff Basic za pár dolarů

Jedná se o primitivní spínač v plastové krabičce, který ze sítě zároveň napájí i svůj řídící Wi-Fi čip ESP8285 (ve starších várkách ESP8266) s 1MB interní pamětí, a lze jej tedy po doinstalování podpory velmi snadno programovat z vývojového prostředí Arduino. Ostatně už jsme si s ním v našem seriálu pohráli několikrát.

Klepněte pro větší obrázekKlepněte pro větší obrázekKlepněte pro větší obrázekKlepněte pro větší obrázek
Wi-Fi spínač 230V Sonoff Basic a srovnání (zprava) s Arduino Uno, NodeMCU, samostatným modulem elektromagnetického relé a miniaturní Wi-Fi destičkou Wemos D1 Mini s vlastním rozšiřujícím modulem elektromagnetického relé.

Hlavní specialitou Sonoffu je ale jeho cena. Základní model stojí na Banggoodu aktuálně asi 5 dolarů, čili vás jeden kus po přepočtu přijde něco málo přes stovku. Za tuto částku získáte výkonný čip s Wi-Fi, napájením z 230V sítě, spínačem okruhu a ke všemu uvnitř malé plastové krabičky.

Stačí připájet piny a můžete jej programovat jako Arduino

Sonoff Basic je sice hotový spotřební produkt, který můžete ovládat aplikací pro chytrý telefon, ale když plastovou krabičku rozmontujete, na základní destičce najdete standardní otvory pro připájení 2,5mm pole pinů pro komunikaci skrze sériovou linku (3V3, GND, RX a TX) a jeden volný univerzální pin GPIO, který bude v prostředí Arduino dostupný pod číslem 14.

Klepněte pro větší obrázek
Schéma zapojení a popis jednotlivých pinů GPIO, jak jsou dostupné ve zdrojovém kódu. Do otvorů jsem už připájel samčí piny pro snadné připojení adaptéru sériové linky.

Pod číslem 12 je pak dostupné samotné spínací relé, pod číslem 13 signalizační LED a pod číslem 0 tlačítko, které však dnes použijeme jen k přepnutí režimu čipu, abychom do něj mohli nahrát program.

Vyrobíme si Wi-Fi lampu s bezpečnostním teploměrem

Nejprve jsem tedy připájel k destičce piny sériové linky, ke kterým stačí připojit libovolný USB/TTL převodník a Sonoff Basic se po zapojení do počítače ohlásí na některém z portů COM jako jakákoliv jiná programovatelná deska.

Klepněte pro větší obrázekKlepněte pro větší obrázekKlepněte pro větší obrázekKlepněte pro větší obrázek
Pájení pinů do otvorů na desce. Díky tomu jsem mohl snadno připojit USB/TTL převodník sériové línky a do čipu nahrát vlastní firmware z Arduina.

Na volný pin GPIO 14 jsem pak připájel primitivní obvod s populárním teploměrem DS18B20, který je dostatečně malý na to, aby se do nitra krabičky vůbec vešel, a ke komunikaci používá sběrnici 1-Wire, která už podle svého názvu potřebuje vedle napájení jen jeden signální vodič.

Klepněte pro větší obrázekKlepněte pro větší obrázekKlepněte pro větší obrázek
Deska Sonoff Basic, spodní strana a detail řídícího čipu. Píše se na něm, že se jedná o ESP8265 – tedy ESP8285 s 1MB interní pamětí.

Teploměr se mi hodí, i když je totiž řada spínačů Sonoff mezi kutily a fanoušky levné elektroniky velmi populární, přeci jen je to Čína. Díky čidlu mohu během prvotního testování kvality spínače sledovat vnitřní teplotu, a pokud překročí bezpečnou mez, může mi čip skrze internet poslat varování, nebo dát povel nějakému vyššímu chytrému obvodu, aby tuto větev odpojil, než dojde k jejímu potenciálnímu zničení.

Klepněte pro větší obrázekKlepněte pro větší obrázekKlepněte pro větší obrázek
Klepněte pro větší obrázekKlepněte pro větší obrázek
Sonoff Basic jsem připojil do obvodu pokojové lampy s napájecím kabelem s vidlicí – tedy jen se dvěma vodiči N (světle modrý) a L (hnědý).

Knihovna WiFiManager se postará o nastavení Wi-Fi spínače z mobilu

Zároveň zužitkuji vestavěnou LED, která sahá až pod plastový kryt, takže když je sepnutá, ambientně prosvítá. Použiji ji pro signalizaci stavu při připojování k Wi-Fi.

Na rozdíl od předchozích příkladů, kdy bylo jméno a heslo Wi-Fi sítě natvrdo součástí kódu, totiž tentokrát vyzkouším populární knihovnu WiFiManager, kterou můžete vedle GitHubu doinstalovat přímo ze správce knihoven Arduina.

Klepněte pro větší obrázek
Spínače Sonoff se mohou lišit použitým čipem. Může to být ESP8266, nebo ESP8285 (na desce značený jako ESP8265). Před nahráním programu je tedy třeba ověřit značení čipu. V mém případě se jednalo o verzi s ESP8285, takže jsem zvolil v Arduinu jako cílové zařízení „Generic ESP8285 Module.“

WiFiManager nabízí konfiguraci čipu tak, že si nejprve čip samotný spustí Wi-Fi síť, my se na ni připojíme třeba z mobilu, a skrze jednoduché webové rozhraní mu řekneme, k jaké síti se má připojit. Čip to provede a údaje si uloží. WiFiManager tedy nabízí chování spotřební elektroniky, která se k Wi-Fi připojuje podobným způsobem – zpravidla ještě pomocí vlastní mobilní aplikace, která to celé překryje, aby konfiguraci zvládl i naprostý zelenáč.

Pokud se krabička nedokáže připojit k Wi-Fi, začne blikat a přepne se do konfiguračního režimu

WiFiManager je velmi tvárný a můžete si jej libovolně upravit, nicméně já pro jednoduchost použiji ten nejprimitivnější scénář. Sonoff Basic s mým firmwarem se po připojení k elektrické síti pokusí přihlásit k Wi-Fi uložené v paměti. Pokud to nedokáže (zpočátku nebude mít v paměti žádné nastavení), rozbliká svoji vestavěnou LED a já budu vědět, že je v konfiguračním režimu a WiFiManager na něm právě spustil síť se jménem WiFiLampa.

Flashování Sonoff Basic v Arduinu:

Já se k této síti připojím na mobilu, a poté navštívím v prohlížeči IP adresu 192.168.0.1, na které poběží konfigurační rozhraní. Většina chytrých telefonů na toto rozhraní přejde sama v rámci funkce „přihlášení k síti.“

Poté v seznamu vyberu síť Wi-Fi, ke které se má Sonoff Basic definitivně připojit a to je celé. Knihovna WiFiManager si uloží novou konfiguraci sítě, takže se ve vlastním kódu nemusím o nic starat. Poté restartuje čip a ten se konečně přihlásí. V lokální síti následně stačí dohledat jeho IP adresu třeba pomocí skvělé a bezplatné aplikace Fing, a když ji zadám do prohlížeče na mobilu nebo v PC, zobrazí se mi webové rozhraní spínače níže.

Klepněte pro větší obrázekKlepněte pro větší obrázek
A je hotovo, lampu mohu spínat a vypínat z webového prohlížeče

Jelikož jsem Sonoff Basic připojil na okruh napájení pokojové ambientní lampy, mohu ji nyní z webového prohlížeče zapínat a vypínat, napojit na další chytré systémy v domácnosti, skrze IFTTT velmi snadno na můj Google Home a ovládat ji hlasem a tak dále a tak dále.

A jelikož si můj firmware každou hodinu synchronizuje přesný čas z internetu (NTP), nechybí ani možnost nastavení časového plánu pro automatické zapnutí a vypnutí v určitou hodinu a minutu. Do prohlížeče se zároveň posílá údaj o aktuální teplotě na desce, která se dlouhodobě drží okolo 40-45 °C, což je v naprostém pořádku, takže se nitro nijak výrazně nezahřívá.

Když je výrobce přátelský ke kutilům

A to je opravdu celé. Dnes jsme si tedy ukázali, jak lze snadno a kompletně přeprogramovat spotřební elektroniku, pokud je její výrobce dostatečně osvícený a přátelský k bastlířům.

Díky tomu, že výrobce použil populární čip z rodiny ESP8285/8266, mohu celý firmware napsat v Arduinu s pomocí jeho tradičních jednoduchých knihoven. Jediné, co k tomu budu potřebovat, je USB/TTL převodník, protože destička pochopitelně nemá USB rozhraní.

Podívejte se na videodemonstraci, o čem to dnes celé bylo:

Na úplný závěr však nesmím zapomenout na důležité upozornění. Tentokrát jsme si pohráli s elektronikou, která je už připojená k 230V elektrické síti, což je třeba mít na paměti a s deskou v otevřeném stavu nikdy nepracovat, pokud je připojená!

Zároveň mějte na paměti, že se jedná o nestandardní zásah do řídícího čipu, kterým nejen že přicházíte o záruku, ale pokud by opravdu došlo k jakékoliv havárii a případně i požáru, pojišťovna by nemusela mít pochopení pro selhání nestandardní elektroniky ve vašem bytě.

Tak a teď už ten kód! Nejprve hlavní program, poté kód v druhém hlavičkovém souboru html.h, ve kterém je uložený HTML kód stránky, kterou odešle čip do vašeho prohlížeče.

Hlavní program:

// Knihovny pro praci s WiFi
#include <ESP8266WiFi.h>
#include <DNSServer.h>
#include <ESP8266WebServer.h>
#include <WiFiManager.h> // Dohledate ve spravci knihoven
#include <Ticker.h>
#include <EEPROM.h>
#include <TimeLib.h> // https://github.com/PaulStoffregen/Time
#include <WiFiUdp.h>
#include <OneWire.h> // Dohledate ve správci knihoven
#include <DallasTemperature.h> // Dohledate ve správci knihoven
#include "html.h" // HTML kod ovladaci stranky

// Cisla pinu LED, spinace a teplomeru
#define GPIO_LED 13
#define GPIO_TEPLOMER 14
#define GPIO_RELE 12

// Webovy server pojede na standardnim portu 80
ESP8266WebServer server(80);

// Objekt ticker z vestavene knihovny se postara o blikani LED
Ticker ticker;

// Nastartovani sbernice 1-Wire a teplomeru DS18B20
OneWire oneWire(GPIO_TEPLOMER);
DallasTemperature teplomer(&oneWire);

// Pomocne promenne pro synchronizaci casu
const char ntp_server[] = "us.pool.ntp.org";
const uint8_t zona = 1;
const uint8_t port = 8888;
uint8_t ntp_packet[48];
WiFiUDP Udp;

// Kontrolni promenna pro spinani podle casoveho planu
uint64_t kontrola_spinace = 0;

// Hodiny a minuty spinani podle casoveho planu
uint8_t zacatek_hodina, zacatek_minuta, konec_hodina, konec_minuta;

// Funkce tick rozsviti/zhasne LED
void tick() {
  digitalWrite(GPIO_LED, !digitalRead(GPIO_LED));
}

// Pokud se cip nemuze pripojit k Wi-Fi a spustil vlastni konfiguracni AP, rozblikej LED
void zacatekKonfigurace(WiFiManager *wmp) {
  // kazdych 200 ms zavolej funkci tick
  ticker.attach(0.2, tick);
}

// Jakmile rezim konfigurace Wi-Fi skonci, ukonci blikani
// LED na Sonoff Basic ma opacnou logiku
// Pri LOW sviti a pri HIGH je zhasnuta
void konecKonfigurace() {
  ticker.detach();
  digitalWrite(GPIO_LED, HIGH);
}

// Halvni funkce, ktera se zpracuje po startu
void setup() {
  // Nastaveni smeru pinu GPIO na zapis
  pinMode(GPIO_LED, OUTPUT);
  pinMode(GPIO_RELE, OUTPUT);
  // Zhasni LED
  digitalWrite(GPIO_LED, HIGH);
  Serial.begin(115200);
  // Precti prvni 4 B z trvale pameti,
  // ve ktere jsou ulozene casy automatickeho spinani a vypinani
  EEPROM.begin(4);
  zacatek_hodina = EEPROM.read(0);
  zacatek_minuta = EEPROM.read(1);
  konec_hodina = EEPROM.read(2);
  konec_minuta = EEPROM.read(3);

  // Nastartuj teplomer DS18B20
  teplomer.begin();

  // Nastartuj WiFiManager, ktery se postara o pripojeni k Wi-Fi
  wm.setAPCallback(zacatekKonfigurace);
  wm.setSaveConfigCallback(konecKonfigurace);
  // IP parametry konfiguracni Wi-Fi site
  wm.setAPStaticIPConfig(IPAddress(192, 168, 0, 1), IPAddress(192, 168, 0, 1), IPAddress(255, 255, 255, 0));

  // Pripoj se k Wi-Fi
  // Pokud zatim zadnou nemas v pameti, nebo je mimo dosah,
  // spust vlastni AP, ke kteremu se muze uzivatel pripojit a nastavit novou Wi-Fi
  if (!wm.autoConnect("WiFiLampa")) {
    // Pokud se neco pokazi a nelze se pripojit, restartuj cip a zacni znovu
    ESP.reset();
    delay(1000);
  }
  else {
    Serial.print("Pripojen jako: ");
    Serial.println(WiFi.localIP());
  }

  // Ted uz jsem pripojeny k Wi-Fi, takze mohu pokracovat v programu
  // Pokud uzivatel zada do prohlizece IP adresu spinace,
  // posli mu HTML kod ulozeny v souboru html.h.
  // Behem HTTP komunikace zaroven sviti LED (problikne)
  // HTML kod se nacita primo z flashove pameti cipu a nezatezuje RAM
  server.on("/", []() {
    digitalWrite(GPIO_LED, LOW);
    server.send_P(200, "text/html", html);
    digitalWrite(GPIO_LED, HIGH);
  });

  // Server zaroven reaguje na nekolik HTTP dotazu ve formatu:
  // http://ipadresa/api?PARAMETR=HODNOTA
  // Pro nastaveni automatickeho spinani a vypinani tedy staci zavolat:
  // http://ipadresa/api?zacatek=HH:MM&konec=HH:MM
  server.on("/api", []() {
    digitalWrite(GPIO_LED, LOW);
    if (server.hasArg("zacatek") && server.hasArg("konec")) {
      if ((server.arg("zacatek") != NULL) && (server.arg("konec") != NULL)) {
        zacatek_hodina = server.arg("zacatek").substring(0, 2).toInt();
        zacatek_minuta = server.arg("zacatek").substring(3, 5).toInt();
        konec_hodina = server.arg("konec").substring(0, 2).toInt();
        konec_minuta = server.arg("konec").substring(3, 5).toInt();
        EEPROM.write(0, zacatek_hodina);
        EEPROM.write(1, zacatek_minuta);
        EEPROM.write(2, konec_hodina);
        EEPROM.write(3, konec_minuta);
        EEPROM.commit();
        server.send(200, "application/json", "{\"odpoved\":1}");
      }
      else {
        server.send(200, "application/json", "{\"odpoved\":0");
      }
    }
    // Pro sepnuti rele (zapnuti/vypnuti svetla):
    // http://ipadresa/api?stav=1 (nebo 0)
    else if (server.hasArg("stav")) {
      if (server.arg("stav") != NULL) {
        uint8_t stav = server.arg("stav").toInt();
        if (stav == 1) {
          Serial.println("Zapinam rele");
          digitalWrite(GPIO_RELE, HIGH);
          server.send(200, "application/json", "{\"odpoved\":1}");
        }
        else {
          Serial.println("Vypinam rele");
          digitalWrite(GPIO_RELE, LOW);
          server.send(200, "application/json", "{\"odpoved\":0}");
        }
      }
      else {
        server.send(200, "application/json", "{\"odpoved\":-1}");
      }
    }
    // Pro stazeni udaju (teplota, stav, cas na cipu, volna RAM) v JSON:
    // http://ipadresa/api?data=
    else if (server.hasArg("data")) {
      String data = "{\"odpoved\":1, \"zacatek\":\"#zacatek\", \"konec\":\"#konec\", \"stav\":#stav, \"cas\":\"#cas\", \"ram\":#ram, \"teplota\":#teplota}";
      char zacatek[6];
      char konec[6];
      char cas[9];
      sensors.requestTemperatures();
      sprintf(zacatek, "%02d:%02d", zacatek_hodina, zacatek_minuta);
      sprintf(konec, "%02d:%02d", konec_hodina, konec_minuta);
      sprintf(cas, "%02d:%02d:%02d", hour(), minute(), second());

      // Nahrad hodnoty v AJAX sablone vyse 
      // S tridou Arduino String zachazet s velkou rozvahou, pouziva dynamickou alokaci
      // Na cipech s malickou RAM ji mohou pri spatnem designu rychle zaplnit
      // Viz pamet typu heap, dynamicka alokace a riziko fragmentace RAM
      // https://www.gribblelab.org/CBootCamp/7_Memory_Stack_vs_Heap.html
      data.replace("#stav", String(digitalRead(GPIO_RELE)));
      data.replace("#zacatek", String(zacatek));
      data.replace("#konec", String(konec));
      data.replace("#cas", String(cas));
      data.replace("#ram", String((ESP.getFreeHeap() / 1000.0f), 2));
      data.replace("#teplota", String(sensors.getTempCByIndex(0), 2));
      server.send(200, "application/json", data);
    }
    else {
      server.send(200, "application/json", "{\"odpoved\":0}");
    }
    digitalWrite(GPIO_LED, HIGH);
  });

  // Nastartovani UDP (synchronizace casu pomoci NTP serveru)
  Udp.begin(port);
  // Knihovna pro praci s casem bude kazdou hodinu
  // volat funkci, ktera bude stahovat cerstvy cas z NTP serveru
  setSyncProvider(ziskejNtpCas);
  setSyncInterval(3600);

  // Nastartovani HTTP serveru
  server.begin();

}

// Smycka loop se opakuje stale dokola
void loop() {
  // Zpracuj pozadavky HTTP klientu
  server.handleClient();

  // Jednou za minutu zkontroluj, jestli aktualni
  // cas neodpovida hodnotam pro automaticke sepnuti/vypnuti rele
  if (millis() > kontrola_spinace) {
    if ((zacatek_hodina == hour()) && (zacatek_minuta == minute())) {
      digitalWrite(GPIO_RELE, HIGH);
      Serial.println("Spinam rele podle casoveho planu");
    }
    if ((konec_hodina == hour()) && (konec_minuta == minute())) {
      digitalWrite(GPIO_RELE, LOW);
      Serial.println("Vypinam rele podle casoveho planu");
    }
    kontrola_spinace = millis() + 6e4;
  }

}

// Funcke pro ziskani aktualniho casu z NTP serveru skrze UDP protokol
time_t ziskejNtpCas()
{
  IPAddress ntp_server_ip;
  while (Udp.parsePacket() > 0);
  WiFi.hostByName(ntp_server, ntp_server_ip);
  odesliNtpPacket(ntp_server_ip);
  uint32_t start = millis();
  while (millis() - start < 1500) {
    int size = Udp.parsePacket();
    if (size >= 48) {
      Udp.read(ntp_packet, 48);
      unsigned long sekundy; // sekundy od roku 1900
      sekundy =  (unsigned long)ntp_packet[40] << 24;
      sekundy |= (unsigned long)ntp_packet[41] << 16;
      sekundy |= (unsigned long)ntp_packet[42] << 8;
      sekundy |= (unsigned long)ntp_packet[43];
      // Vrati pocet sekund a pripocita casovou zonu
      return sekundy - 2208988800UL + zona * SECS_PER_HOUR;
    }
  }
  // Pokud se dotaz nepodaril, vrat 0
  return 0;
}

// Funcke pro odeslani UDP paketu/framu na NTP server
void odesliNtpPacket(IPAddress &adresa) {
  memset(ntp_packet, 0, 48);
  ntp_packet[0]  = 0b11100011;
  ntp_packet[1]  = 0;
  ntp_packet[2]  = 6;
  ntp_packet[3]  = 0xEC;
  ntp_packet[12] = 49;
  ntp_packet[13] = 0x4E;
  ntp_packet[14] = 49;
  ntp_packet[15] = 52;
  Udp.beginPacket(adresa, 123);
  Udp.write(ntp_packet, 48);
  Udp.endPacket();
}

Soubor html.h s kódem webové stránky:

// Pro prevod ceskych znaku v HTML kodu
// do zakladniho ASCII jsem pouzil prevod
// do formatu HEX NCR na webu:
// https://r12a.github.io/app-conversion/

static const char PROGMEM html[] = R"html(
<!DOCTYPE html>
<html lang="cs">
<head>
<title>WiFiLampa</title>
<link href="https://fonts.googleapis.com/css?family=Comfortaa&amp;subset=latin-ext" rel="stylesheet">
<script
  src="https://code.jquery.com/jquery-3.3.1.min.js"
  integrity="sha256-FgpCb/KJQlLNfOu91ta32o/NMZxltwRo8QtmkMRdAu8="
  crossorigin="anonymous">
</script>
<style>
body{
  font-family: "Comfortaa", cursive;
  line-height: 150%;
  margin: 50px;
  text-align: center;
}
a{
  display: inline-block;
  width: 100px;
  padding: 5px;
  border: 1px solid steelblue;
  font-weight: bold;
}
a:link, a:visited, a:active{
  color: steelblue;
  background: white;
  text-decoration: none;
}
a:hover{
  color: white;
  background: steelblue;
  text-decoration: none;
}
</style>
<script>
// Tato funkce se zpracuje pote, co se stahne a vykresli cely HTML kod
$(function(){
  // Na zacatku si pres AJAX stahni JSON s informacemi
  $.get("/api?data=1", function(data){
    if(data.odpoved == 1){
      console.log(data);
      // Aktualizuj podle stazenych dat prvky na strance
      $("#cas").html(data.cas);
      $("#ram").html(data.ram);
      $("#teplota").html(data.teplota);
      $("#zacatek").val(data.zacatek);
      $("#konec").val(data.konec);
      // Podle stavu rele vykresli adekvatni tlacitko
      if(data.stav == 1){
        $("#rozsvitit").hide();
        $("#stav").html("&nbsp;sv&#x00ED;t&#x00ED;");
        $("#stav").css("color", "green");
      }
      else{
        $("#zhasnout").hide();
        $("#stav").html("&nbsp;je zhasnut&#x00E1;");
        $("#stav").css("color", "red");
      }
    }
    else{
      console.error("Chyba: " + data.odpoved);
    }
  });
  // Pokud klepnu na tlacitko nastaveni casu, odesli AJAXem nove casy automatickeho spinani
  $("#nastavit").click(function(){
    $.get("/api?zacatek=" + $("#zacatek").val() + "&konec=" + $("#konec").val(), function(data){
      if(data.odpoved == 1){
        console.log("Zmena casu automatickeho spinani a vypinani");
      }
      else{
        console.error("Chyba: " + data.odpoved);
      }
    });
  });
  // Po klepnuti na odkaz pro rozsviceni odesli AJAXem prikaz k rozsviceni
  $("#rozsvitit").click(function(){
    $.get("/api?stav=1", function(data){
      if(data.odpoved == 1){
        console.log("Rele sepnuto!");
        $("#rozsvitit").hide();
        $("#zhasnout").show();
        $("#stav").html("&nbsp;sv&#x00ED;t&#x00ED;");
        $("#stav").css("color", "green");
      }
      else{
        console.error("Chyba: " + data.odpoved);
      }
    });
  });
  // Po klepnuti na odkaz pro zhasnuti odesli AJAXem prikaz ke zhasnuti
  $("#zhasnout").click(function(){
    $.get("/api?stav=0", function(data){
      if(data.odpoved == 0){
        console.log("Rele vypnuto!");
        $("#rozsvitit").show();
        $("#zhasnout").hide();
        $("#stav").html("&nbsp;je zhasnut&#x00E1;");
        $("#stav").css("color", "red");
      }
      else{
        console.error("Chyba: " + data.odpoved);
      }
    });
  });
});
</script>
</head>
<body>
<h1>WiFiLampa<span id="stav"></span></h1>
<p>
  <a id="rozsvitit" href="#">Rozsv&#x00ED;tit</a>
  <a id="zhasnout" href="#">Zhasnout</a>
</p>
<p>
  Nastavit &#x010D;as automatick&#x00E9;ho sp&#x00ED;n&#x00E1;n&#x00ED; a vyp&#x00ED;n&#x00E1;n&#x00ED;
</p>
<p>
  Zapnout v <input id="zacatek" type="time" /> a vypnout v 
  <input id="konec" type="time" />
  <input id="nastavit" type="button" value="Nastavit cas" />
</p>
<p>
  Aktu&#x00E1;ln&#x00ED; &#x010D;as: <span id="cas"></span>, teplota: <span id="teplota"></span>&nbsp;&#x00B0;C, voln&#x00E1; pam&#x011B;&#x0165;: <span id="ram"></span> kB
</p>
</body>
</html>
)html";
Diskuze (21) Další článek: Vyzkoušeli jsme hromadu bezdrátových nabíječek. Qi ušlo velký kus cesty a usnadňuje život

Témata článku: Hardware, Pojďme programovat elektroniku, Programování, Wi-Fi, Elektřina, C++, R & AMP

Určitě si přečtěte


Aktuální číslo časopisu Computer

Jak rychlé je nabíjení bez drátů?

Test 11 sluchátek pro hráče

Aplikace, které vám zachrání dovolenou

Kompletní přehled datových tarifů