Loni v létě jsme si v našem seriálu vyzkoušeli práci s 2,4“ TFT LCD, na který jsme pomocí základního Arduina Una vykreslovali několik údajů z připojených čidel. Postavili jsme si tedy vlastně jednoduchou meteostanici s vlastním displejem.
2,8" dotykový TFT LCD
Po půlroce se k displeji vrátíme, ovšem k poněkud lepšímu modelu, tentokrát totiž půjde o 2,8“ cvalíka s rozlišením 320×240 pixelů a rezistivní dotykovou vrstvou. Na čínských e-shopech jej pořídíte za částku okolo 150 korun. Tyto displeje mají zároveň na zadní straně i samostatný obvod a pouzdro pro SD kartu, takže displej může mikrokontroleru poskytnout funkci rozšířeného úložiště, ze kterého může řídící čip načítat třeba rozměrnější grafiku.



2,8" TFT LCD s rezistivní dotykovou vrstvou, obvodem čtečky SD karet a samostatnými komunikačními rozhraními SPI pro všechny tyto tři funkce
Namísto primitivního Arduina Una tentokrát jako základní desku zvolíme NodeMCU s čipem ESP8266, který má nejen mnohem větší flashové úložiště, ale také RAM v řádu mnoha desítek kB. To je ve srovnání s 2 kB na čipech ATmega328p naprostý luxus. Čipy ESP8266 ale mají především 2,4GHz Wi-Fi vysílač, kterým se připojíme k domácí síti. Cena destiček NodeMCU se na eBayi pohybuje okolo 70 korun.

Prototypovací destička NodeMCu „v3“ s řídícím čipem ESP8266
Vyrobíme si malý ajPed
Dnes si pomocí těchto dvou součástek postavíme maličkou parodii na tablet. Bude se proto jmenovat ajPed. Když se spustí, v horní části obrazovky se zobrazí jeho textové logo a řada sedmi tlačítek. První bude sloužit ke smazání obrazovky a ty další k výběru barvy štětce.
Součástí balení s displejem je totiž i malý, plastový, a především bílý stylus, který použijeme jako kreslící pero. Vyrobíme si tedy primitivní kreslítko, kterému, aby mělo nějakou formu, vyrobíme na míru krabičku z plošných perforovaných dílů skvělé litevsko-norské stavebnice Totem.






Základní díly litevsko-norské stavebnice Totem
Totem se podobá tradiční české stavebnici Merkur, jeho díly jsou však z černého ABS, který se dá snáze řezat, a tvůrci se specializují na to, abyste ke stavebnici snadno přimontovali třeba desku Arduina, Raspberry Pi a další. Všechny otvory mají rozměr vhodný pro šrouby M3 a součástí stavebnice je i nespočet všemožných hliníkových pojících dílů.



A je hotovo
Na stranu druhou, za tuto legraci si připlatíte, evropský Totem je totiž pochopitelně mnohem dražší než obvyklé čínské cetky z AliExpressu. Při svých cestách internetem jsem ale vyjma zmíněného Merkuru nenašel nic srovnatelného.
Streamování do prohlížeče
V každém případě, náš ajPed bude díky čipu ESP8266 připojený do internetu, čehož musíme využít. Přemýšlel jsem jak, až jsem si řekl, že v éře streamování téměř všeho bude streamovat i náš ajPed. A co vlastně? A jak?
Bude streamovat veškeré dění na obrazovce do webového prohlížeče, ve kterém načtete jeho webovou adresu. Při startu se na něm totiž spustí primitivní HTTP server na standardním TCP portu 80, ale zároveň server pro komunikaci na protokolu WebSocket a portu 81.
Video: Podívejte se, co umí ajPed:
WebSocket je skvělá technologie, které dnes rozumějí prakticky všechny hlavní webové prohlížeče s podporou HTML5. Umožňuje obousměrnou komunikaci na jednom TCP spojení, takže když se webový prohlížeč spojí s naším ajPedem, ten mu může průběžně zasílat data, aniž bychom museli v Javascriptu používat techniku klasického AJAXu a periodicky si data vynucovat. Když nějaká budou, prostě sama dorazí.
Když tedy začnu perem čmárat po displeji, souřadnice dotyků, jak je rozpoznala rezistivní vrstva, se budou skrze WebSocket zasílat do prohlížeče, kde bude jednoduchý Javascript podle souřadnic a s pomocí technologie HTML5 Canvas kreslit to samé.

Co nakreslím na displej ajPedu, se okamžitě dokreslí i ve webovém prohlížeči. O synchronizaci s minimální latencí se stará technologie WebSocket.
Díky rychlosti a minimální režii WebSocketu bude přenos velmi svižný a prakticky bez jakékoliv viditelné latence. Bude to opravdu vypadat, jako by byl ajPed připojený k počítači USB kabelem a choval se jako běžné HID zařízení – třeba zrovna grafický tablet.






Po startu se ajPed pokusí připojit k Wi-Fi. Pokud se mu to podaří, cokoliv nakreslíte, se bude živě streamovat do webového prohlížeče.
Sběrnice SPI: hromada kablíků, hromada knihoven
Fajn, co k tomu všemu budeme potřebovat? Knihovny. 2,8“ displej pro komunikaci používá sběrnici SPI a několik dalších pomocných pinů. Dotyková vrstva má samostatné ovládání také skrze SPI a několik dalších pinů, takže na straně displeje budu muset zapojit dohromady 14 vodičů! Díky tomu, že displej i vrstva používají sběrnici SPI, mohu sice několik vodičů propojit dohromady, i tak ale na straně desky NodeMCU zaberou téměř všechny jeho použitelné piny.

Jedno z možných propojení NodeMCU a displeje. V mém zdrojovém kódu je pin displeje RESET připojený na pin desky 3, jinak je vše stejné. Podobně bychom mohli alternativně zapojit pin LED a programově zapínat/vypínat podsvícení. Bylo by to bezpečné, podsvícení je totiž napájené přes VCC a nikoliv přímo tímto pinem, který slouží jen k nastavení logického stavu.
Displej k vlastnímu chodu používá čip ILI9341 a dotyková vrstva pak čip XPT2046, takže bych mohl buď několik dnů zabít četbou dokumentace, jak se tyto čipy skrze sběrnici SPI programují, aby se na displeji zobrazilo třeba červené srdíčko, anebo se poohlédnu na internetu po nějaké hotové knihovně pro Arduino a další podporované desky.
S ILI9341 si velmi dobře rozumí třeba knihovna od Adafruitu, která je určená i pro desky s mikrokontrolerem ESP8266, a o čip dotykové vrstvy XPT2046/ADS7843 se zase postará knihovna od Spirose Papadimitriua (snad jsem to napsal správně).
Spolupráce obou knihoven na destičce NodeMCU mě však hodně zlobila, a tak jsem se nakonec inspiroval na webu nailbuster.com, kde najdete funkční demo a zapojení právě pro tuto desku. A co je nejdůležitější, autor se zde pochlubil i mírně upravenou knihovnou pro čip ILI9341, kterou jsem nakonec použil namísto té původní od Adafruitu.





Připojením displeje na NodeMCU obsadíme prakticky všechny jeho piny GPIO
Do třetice ještě jedna knihovna. Zatímco ty předchozí se postarají o to, aby se displej a dotyková vrstva vůbec rozjely, ještě potřebujeme nějakou grafickou knihovnu, pomocí které budeme na displej třeba psát text rastrovými fonty, kreslit základní tvary a tak dále, aniž bychom si pro to všechno museli psát vlastní rutiny.
K tomu slouží jedna z nejrozšířenějších knihoven světa Arduino – Adafruit GFX. Dnes s ní pracují nejen podobné LCD displeje, ale také OLED a další. A právě díky této knihovně snadno nakreslíte bod, čáru nebo třeba čtverec vyplněný barvou.
Málem bych zapomněl zmínit ještě čtvrtou klíčovou knihovnu, která není součástí základního balení – Arduino WebSockets pro samotné zprovoznění spojení skrze websocketovou linku.
Kalibrace
Tak, knihovny bychom měli, takže už zbývá jen jedna věc – kalibrace. Ta je v tomto konkrétním případě naprosto klíčová, bez ní totiž nedokážeme namapovat dotyk se souřadnicovým systémem samotného displeje. Knihovna XPT2046 od Spirose (příjmení už raději znovu psát nebudu) na to pamatuje samostatným programem XPTCalibrate, přičemž my použijeme jeho upravenou verzi pro NodeMCU a ILI9341 opět od nailbuster.com – vše je součástí výše odkazovaného ZIP balíčku.
Když tento program nahrajeme do čipu, na displeji se zobrazí nejprve křížek v levém horním rohu a pak v pravém dolním. Stačí na ně klepnout a zaznamenat si číselné hodnoty dotyků, které nyní budeme používat v každém dalším a už vlastním programu. Kalibraci tedy stačí provést jen jednou.


Kalibrační program pomůže propojit signály z dotykové vrstvy se souřadnicovým systémem displeje pod ní
Když tyto hodnoty použijeme v kódu našeho programu při inicializaci dotykové vrstvy, po klepnutí na displej získáme rovnou souřadnice X a Y v pixelech a nikoliv surové číselné hodnoty z rezistivní vrstvy.
Pak už jen stačí pomocí knihovny Adafruit GFX stvořit uživatelské rozhraní našeho ajPedu, spustit servery HTTP a WebSocket a začít kreslit. Jak na to, se dozvíte v kódech níže, ve kterých jsem každý klíčový krok jako vždy podrobně okomentoval.
A konečně zdrojové kódy
Kódy jsou dnes dva. V prostředí Arduino jsem totiž vedle hlavního zdrojového souboru vytvořil ještě druhý jménem html.h. V něm je uložený samotný HTML/JS kód, který HTTP server předá webovému prohlížeči a ve kterém se pomocí Javascriptu otevře spojení se serverem WebSocket na portu 81 a podle jeho instrukcí se bude vykreslovat kopie malůvky na dotykovém displeji.
Jelikož je HTML kód včetně JavaScriptu docela rozměrný, vůbec se nenačte do RAM čipu ESP8266, ale server jej prohlížeči pošle přímo z rozměrné 4MB flashové paměti, ve které je uložený samotný program. Poslouží k tomu makro PROGMEM (program memory).
A na HTML kódu si demonstrujeme ještě jednu specialitu, kterou přináší jazyk C++ verze 11. Když totiž v C/C++ potřebujete zapsat jako proměnnou nějaký složitý a dlouhý text, namísto zápisu třeba:
char textovepole[] = "Toto je muj dlouhy text";
Ve kterém například nemůžete jen tak použít uvozovky, protože uvozují obsah samotné proměnné, a tak je musíte zapsat pomocí escape sekvence \" – tak tedy namísto tohoto obvyklého zápisu můžete v C++ použít i tuto formu:
char textovepole[] = R"dlouhytext(
jakykoliv textovy obsah vcetne uvozovek,
zalomeni radku a tak dále a tak dale
)dlouhytext";
Tímto způsobem jsem si tedy v úplně jiném editoru připravil svůj HTML/JS kód a pak jej pouze zkopíroval do druhého hlavičkového souboru, který nesmím zapomenout odkázat v hlavním kódu, a do tohoto zápisu, aniž bych text musel jakkoliv upravovat.
Zdrojový kód hlavního programu:
#include <SPI.h>
// https://nailbuster.com/?page_id=341
// http://nailbuster.com/nailcode/tft28esp.zip
#include <Adafruit_ILI9341esp.h>
#include <Adafruit_GFX.h>
// https://github.com/spapadim/XPT2046
#include <XPT2046.h>
#include <Fonts/FreeSerifItalic9pt7b.h>
#include <ESP8266WiFi.h>
#include <ESP8266WebServer.h>
// https://github.com/Links2004/arduinoWebSockets
#include <WebSocketsServer.h>
#include <vector>
#include "html.h"
// SPI CS a pomocne piny LCD
#define LCD_DC 2
#define LCD_CS 15
#define LCD_RESET 3
// SPI CS a pomocny pin dotykove vrstvy
#define TOUCH_CS 4
#define TOUCH_IRQ 5
// Pomocne hodnoty pro tlacitka
#define MEZERA 10
#define TLACITKA_Y 70
#define TLACITKA_SIRKA 20
#define TLACITKA_VYSKA 20
// Objekty LCD a dotykove vrstvy
Adafruit_ILI9341 lcd = Adafruit_ILI9341(LCD_CS, LCD_DC, LCD_RESET);
XPT2046 touch(TOUCH_CS, TOUCH_IRQ);
// Objekty serveru HTTP a WebSocket
ESP8266WebServer server(80);
WebSocketsServer websocket_server = WebSocketsServer(81);
// Nazev a heslo Wi-Fi, ke ktere se budu pripojovat
const char ssid[] = "Klobouk";
const char heslo[] = "mameradikubucizka";
// Pomocne promenne
uint16_t barva_pera = ILI9341_WHITE;
uint16_t tlacitka_posun = 30;
bool websocket_pripojeno = false;
bool konec_dotyku = false;
bool online = true;
uint16_t stary_x = 0xffff;
uint16_t stary_y = 0xffff;
uint8_t websocket_pocet_spojeni = 0;
// Struktura tlacitka
struct Tlacitko {
uint8_t typ;
uint16_t barva;
char prikaz[4];
Adafruit_GFX_Button objekt;
};
/* C++ vektor s tlacitky
Mohl bych samozrejme pouzit jednoduche pole,
ale stoji za pripomenuti, ze Arduino je
postavene na C++, a pokud ma cip dostatek RAM,
muzete pouzivat i pokrocilejsi objekty jeho
standardni knihovny. Dynamicky vektor je
jednim z nich.
*/
std::vector<Tlacitko> tlacitka;
/* Pokud WebSocket zachyti nejakou udalost,
spusti se tato funkce. My timto zpusobem
zjistime, jestli se k nemu nekdo pripojil. Pokud
ano, budou se nasledujici dotyky odesilat
pripojenemu klientu.
*/
void websocketUdalost(uint8_t num, WStype_t type, uint8_t* data, size_t length) {
if (type == WStype_CONNECTED) {
websocket_pocet_spojeni++;
websocket_pripojeno = true;
}
if (type == WStype_DISCONNECTED) {
websocket_pocet_spojeni--;
if (websocket_pocet_spojeni == 0)
websocket_pripojeno = false;
}
}
/* Funkce pro vykresleni noveho tlacitka
na displej a vlozeni do vektoru tlacitek
*/
void pridejTlacitko(uint8_t typ, uint16_t barva, char* popisek, char* prikaz) {
Tlacitko tlacitko;
tlacitko.typ = typ;
tlacitko.barva = barva;
strcpy(tlacitko.prikaz, prikaz);
tlacitko.objekt.initButton(&lcd, tlacitka_posun, TLACITKA_Y, TLACITKA_SIRKA, TLACITKA_VYSKA, ILI9341_WHITE, barva, ILI9341_WHITE, popisek, 2);
tlacitko.objekt.drawButton();
tlacitka.push_back(tlacitko);
tlacitka_posun += TLACITKA_SIRKA + MEZERA;
}
/* Funkce pro zjisteni, zdali jsem klepnul na tlacitko.
Pokud ano, zjistim, co to bylo za typ tlacitka a bud smazu
obrazovku, nebo nastavim barvu stetce. Pokud je skrze WebSocket
pripojeny nejaky klient, poslu mu prikaz, ktery dekoduje JavaScript
a adekvatne provede podobnou operaci v prohlizeci.
*/
void zpracujTlacitka(uint16_t x, uint16_t y) {
// Projdi vektor vsech tlacitek
for (uint8_t i = 0; i < tlacitka.size(); i++) {
// Pokud se dotykam tlacitka, oznac jej jako stisknute
if (tlacitka[i].objekt.contains(x, y)){
tlacitka[i].objekt.press(true);
}
else{
tlacitka[i].objekt.press(false);
}
// Pokud jsem prave stisknul tlacitko...
if (tlacitka[i].objekt.justPressed()) {
// Prekresli tlacitko pro efekt s inverzni barvou
tlacitka[i].objekt.drawButton(true);
/* Pokud ma tlacitko typ 0, smaz platno a
pripadnym WebSocket klientum odesli zpravu CLS
*/
if (tlacitka[i].typ == 0) {
lcd.fillRect(0, 90, 240, 230, ILI9341_BLACK);
if (websocket_pripojeno) {
websocket_server.broadcastTXT("CLS");
}
}
/* Pokud se jedna o tlacitko s typem 1, je to
tlacitko pro nastaveni barvy stetce. Zmenim
tedy aktualni barvu a odeslu WebSocket klientum
textovy prikaz, ktery jsem k tlacitku pripojil
pri jeho vytvoreni.
*/
else if (tlacitka[i].typ == 1) {
barva_pera = tlacitka[i].barva;
if (websocket_pripojeno) {
websocket_server.broadcastTXT(tlacitka[i].prikaz);
}
}
}
// Pokud stisk skoncil, prekresli tlacitko zpet
if (tlacitka[i].objekt.justReleased()) {
tlacitka[i].objekt.drawButton();
}
}
}
// Hlavni funkce, ktera se spusti na zacatku
void setup() {
// Pockej 1 s, vse se stabilizuje vcetne napajeni displeje
delay(1000);
// Nastaveni frekvence sbernice SPI
SPI.setFrequency(ESP_SPI_FREQ);
/* Vypni Wi-Fi a nastav jej do rezimu stanice.
Vynucene vypnuti muze pomoci behem vyvoje,
kdy cip stale flashujete a jeho registrace na
Wi-Fi routeru pak nemusi probehnout vzdy
uplne v poradku. Mam s tim bohate zkusenosti.
*/
WiFi.mode(WIFI_OFF);
WiFi.mode(WIFI_STA);
// Nastartovani displeje
lcd.begin();
// Nastartovani dotykove vrstvy
touch.begin(lcd.width(), lcd.height());
/* Pomoci kalibracniho firmwaru jsem uz drive
ziskal hodnoty krajnich dotyku. Ted je pouziji,
aby body dotyku odpovidaly souradnicovemu
systemu displeje.
*/
touch.setCalibration(1848, 259, 257, 1815);
// Prekresli obrazovku cernou barvou
lcd.fillScreen(ILI9341_BLACK);
// Nastav kurzor na 50x150
lcd.setCursor(50, 150);
// Nastave velikost pisma
lcd.setTextSize(2);
// Nastav barvu pisma
lcd.setTextColor(ILI9341_WHITE);
// Napis zpravu
lcd.print("Hledam Wi-Fi");
// Zmen kurzor
lcd.setCursor(50, 160);
// Zacni se pripojovat k Wi-Fi
WiFi.begin(ssid, heslo);
/* Kazdych 500 ms nakresli tecku a zjisti,
jestli uz jsi pripojeny. Pokud nebudes
pripojeny ani po 30 sekundach, prerus
pripojovani a pokracuj v offline rezimu.
*/
uint32_t start = millis();
while (WiFi.status() != WL_CONNECTED) {
delay(500);
lcd.print('.');
if ((millis() - start) > 30e3) {
WiFi.mode(WIFI_OFF);
online = false;
break;
}
}
/* Pokud otevru IP adresu cipu v prohlizeci,
server mu preda text v promenne html. Jeji
obsah po startu neprekazi v RAM, ale nacte se
z rozmerneho flashoveho uloziste. Obsah je v
samostatnem hlavickovem souboru html.h.
*/
server.on("/", []() {
server.send_P(200, "text/html", html);
});
// Nastartuj HTTP server na portu 80
if (online) server.begin();
/* Pokud se k WebSocket serveru nekdo pripoji,
zpracuje se funkce websocketUdalost
*/
websocket_server.onEvent(websocketUdalost);
// Nastartuj WebSocket server na portu 81
if (online) websocket_server.begin();
// Pripojovani skoncilo, tak smaz displej
lcd.fillScreen(ILI9341_BLACK);
// Nakresli u horniho okraje IP adresu
lcd.setCursor(60, 0);
lcd.setTextSize(1);
lcd.setTextColor(ILI9341_WHITE);
if (online) {
lcd.print("http://");
lcd.print(WiFi.localIP());
}
else {
lcd.setCursor(44, 0);
lcd.print("Nejsem pripojeny k Wi-Fi");
}
// Pouzij rastrovy font a nakresli logo
lcd.setFont(&FreeSerifItalic9pt7b);
lcd.setCursor(40, 40);
lcd.setTextColor(ILI9341_WHITE);
lcd.setTextSize(2);
lcd.print("ajPed Live");
// Vrat se k systemovemu fontu
lcd.setFont();
// Nakresli tlacitka ovladaci listy.
pridejTlacitko(0, ILI9341_BLACK, "x", "CLS");
pridejTlacitko(1, ILI9341_BLACK, "", "C0");
pridejTlacitko(1, ILI9341_RED, "", "CR");
pridejTlacitko(1, ILI9341_GREEN, "", "CG");
pridejTlacitko(1, ILI9341_BLUE, "", "CB");
pridejTlacitko(1, ILI9341_WHITE, "", "CW");
pridejTlacitko(1, ILI9341_YELLOW, "", "CY");
/* Inicializace programu je hotova. Na obrazovce
je vyskreslene GUI a zbytek programu
se bude odehravat v nekonecne smycce loop,
jejiz obsah se opakuje stale dokola.
*/
}
void loop() {
// Vychozi pomocna hodnota souradnic X,Y mimo plochu displeje
uint16_t x, y = 0xffff;
//Pokud cip zachyti dotyk
if (touch.isTouching()) {
// Uloz souradnice dotyku do promennych x a y
touch.getPosition(x, y);
// Pokud je Y > 90 px, jsem v kreslici oblasti
if (y > 90) {
/* Pokud neznam souradnice predchoziho dotyku,
nakreslim ctverec s rozmerem 3x3 px
v miste dotyku - tedy bod
*/
if (stary_x == 0xffff) {
lcd.fillRect(x - 1, y - 1, 3, 3, barva_pera);
} else {
/* Pokud znam souradnici predchoziho dotyku, nekreslim mezi
starou a novou souradnici caru. Funkce drawLine vsak
neumoznuje nastavit tloustku, a tak nakreslim nekolik
car vedle sebe, aby byla dostatecne tlusta.
*/
lcd.drawLine(stary_x - 1, stary_y, x - 1, y, barva_pera);
lcd.drawLine(stary_x, stary_y - 1, x, y - 1, barva_pera);
lcd.drawLine(stary_x, stary_y, x, y, barva_pera);
lcd.drawLine(stary_x, stary_y + 1, x, y + 1, barva_pera);
lcd.drawLine(stary_x + 1, stary_y, x + 1, y, barva_pera);
}
// Uloz aktualni souradnici jako starou
stary_x = x;
stary_y = y;
// Pomocna promenna pro budouci konec dotyku
konec_dotyku = true;
/* Pokud je nekdo pripojeny k WebSocket serveru,
vytvor textovy retezec ve formatu x:y a odesli
jej klientovi. Kvuli co nejvyssi rychlosti a co
nejmensi spotrebe RAM nepouziju vestaveny objekt
String pro snadnou praci s retezci podobne jako
treba v JavaScriptu, ale text vytvorim pomoci
zakladnich nizkourovnovych funkci jazyka C.
*/
if (websocket_pripojeno) {
char txt_xy[8];
char txt_x[4];
char txt_y[4];
memset(txt_xy, 0, sizeof txt_xy);
// Preved desitkova cisla X a Y na retezec
itoa(x, txt_x, 10);
itoa(y, txt_y, 10);
// Spoj retezce dohromady
strcat(txt_xy, txt_x);
strcat(txt_xy, ":");
strcat(txt_xy, txt_y);
/* Odesli retezec vsem pripojenym klientum,
kteri otevreli WebSocket spojeni
*/
websocket_server.broadcastTXT(txt_xy);
}
}
}
/* Pokud dotykova vsrtva neregistruje zadny dotyk
nastav promennou starych souradnic na pomoocnou
hodnotu 0xffffff, cimz kodu vyse davam najevo,
ze kresba cary skoncila a pri dalsim dotyku
na ni nemam navazovat a zacnu novou caru.
*/
else {
stary_x = stary_y = 0xffff;
/* Pokud jsem skoncil s dotykem a je nejake
WebSocket spojeni, odesli mu text EOT,
ktery Javascriptu na webove strance da najevo,
ze ma take ukoncit kresbu cary.
*/
if (konec_dotyku && websocket_pripojeno) {
websocket_server.broadcastTXT("EOT");
konec_dotyku = false;
}
}
// Zjisti, jestli jsem se nedotknul tlacitek
zpracujTlacitka(x, y);
// Zjisti, jestli probiha nejaka WebSocket komunikace
websocket_server.loop();
// Zjisti, jestli probiha nejaka HTTP komunikace
server.handleClient();
// Smycku zpomalim o 20 ms, cimz si oddychne i WebSocket
// Kdyz budu kreslit moc rychle, bude se kresba skladat z mensiho poctu bodu
// Kdyz budu kreslit pomalu, bude detailni a krivky budou mit vice bodu
delay(20);
}
Zdrojový kód hlavičkového souboru html.h, ve kterém je uloženo HTML a JS stránky, která bude překreslovat malůvku ve webovém prohlížeči:
/* Promenna html je obrovska, a tak ji pomoci
makra PROGMEM nenacitam do RAM, ale ctu
rovnou z rozmerne flashove pameti cipu.
Pomoci formatu R"surove_html(retezec)surove_html";
mohu v jazyku C++ zapisovat retezec nehlede na
uvozovky a radky, takze HTML kod mohu napsat v puvodni podobe.
Viz http://en.cppreference.com/w/cpp/language/string_literal
*/
static const char PROGMEM html[] = R"surove_html(
<!DOCTYPE html>
<html>
<head>
<title>ajPed Live</title>
<link href="https://fonts.googleapis.com/css?family=Press+Start+2P&subset=latin-ext" rel="stylesheet">
<style>
body{
background: black;
color: white;
font-family: "Press Start 2P", cursive;
line-height: 150%;
margin: 50px;
}
#tft{
border: 4px solid #999999;
}
</style>
<script>
// Tuto funkci stranka spusti po nacteni HTML kodu
function start() {
var zvetseni = 2;
// Nastartovani platna
var canvas = document.getElementById("lcd");
canvas.width = canvas.width * zvetseni;
canvas.height = canvas.height * zvetseni;
var ctx = canvas.getContext("2d");
// Nastaveni cerneho pozadi a stetcu
ctx.fillStyle = "black";
ctx.fillRect(0, 0, canvas.width, canvas.height);
ctx.lineWidth = 3
ctx.fillStyle = "white";
ctx.strokeStyle = "white";
var x0 = -1;
var y0 = -1;
// Pokus o spojeni s WebSocket serverem na portu 81
var ws = new WebSocket("ws://" + window.location.hostname + ":81/");
// Pokud od WebSocketu dostavam nejaka data
ws.onmessage = function(evt) {
// Vypis data do konzole prohlizece
console.log(evt.data);
// Pokud dosel text EOT, konec dotyku/tahu stetcem
if(evt.data == "EOT"){
x0 = y0 = -1;
}
// Pokud dosel text C0, CB, CR, CW, CY nebo CG, zmen barvu stetce
else if(evt.data == "C0"){
ctx.strokeStyle = "black";
ctx.fillStyle = "black";
ctx.beginPath();
}
else if(evt.data == "CB"){
ctx.strokeStyle = "blue";
ctx.fillStyle = "blue";
ctx.beginPath();
}
else if(evt.data == "CR"){
ctx.strokeStyle = "red";
ctx.fillStyle = "red";
ctx.beginPath();
}
else if(evt.data == "CW"){
ctx.strokeStyle = "white";
ctx.fillStyle = "white";
ctx.beginPath();
}
else if(evt.data == "CY"){
ctx.strokeStyle = "yellow";
ctx.fillStyle = "yellow";
ctx.beginPath();
}
else if(evt.data == "CG"){
ctx.strokeStyle = "green";
ctx.fillStyle = "green";
ctx.beginPath();
}
// Pokud dosel text CLS, smaz obrazovku
else if(evt.data == "CLS"){
ctx.fillStyle = "black";
ctx.strokeStyle = "black";
ctx.fillRect(0, 0, canvas.width, canvas.height);
ctx.strokeStyle = "white";
ctx.fillStyle = "white";
ctx.beginPath();
}
// V opacnem pripade dorazily souradnice dotyku
else{
// Ziskej z nich X a Y
souradnice = evt.data.split(":");
x = parseInt(souradnice[0]) * zvetseni;
y = parseInt(souradnice[1]) * zvetseni;
// Pokud nenavazuji na caru, nakresli tecku
if(x0 == -1){
ctx.fillRect(x - 1, y - 1, 3, 3);
x0 = x;
y0 = y;
}
// Pokud navazuji na caru, nakresli ji mezi predchozia soucasnou souradnici
else{
ctx.moveTo(x0, y0);
ctx.lineTo(x, y);
ctx.stroke();
x0 = x;
y0 = y;
}
}
};
}
</script>
</head>
<body onload="javascript:start();">
<center>
<h1>ajPed Live</h1>
<p>Co nakreslíš na displej svého ajPedu,<br />to se okamžitě zobrazí i na monitoru</p>
<canvas id="lcd" width="240" height="320"></canvas>
</center>
</body>
</html>
)surove_html";
/*
Jelikoz je kod napsany v ASCII, pro zapis ceskych
znaku v HTML pouziji zastupne kody. Poslouzi k
tomu treba tento prevodnik:
https://r12a.github.io/apps/conversion/
a konverze do formatu HEX NCR.
*/
A to je pro dnešek už opravdu vše. Můj ajPed funguje, takže teď už zbývá jen nastartovat kickstarterovou kampaň, z výdělku koupit jachtu a odjet někam do tropů.