UPRAVLJANJE OGREVANJA PREKO INTERNETA - 2. DEL
-
Dodajanje funkcionalnosti in grafike
Sedaj lahko na vikendu vključim in izključim peč. Ko se pozimi odpravim od doma, me na vikendu prostor počaka že nekoliko segret. Ko pa nas v jeseni presenetijo nizke temperature, pa ne vem, kako hitro se vikend ohlaja in kdaj je čas, da iz vodovodne instalacije spustim vodo. Torej potrebujem dodatne funkcionalnosti. Naredil bom prikaz izmerjene temperature v prostoru in termostat z nastavitvijo temperature.
Merjenje temperature
Za merjenje temperature sem si izbral nekoliko boljši NTC 10k 0.5% fi1.6mm VISHAY NTCLE305E4103SB. Če bi želel natančnejšo meritev tudi v okolju z motnjami in dolgimi žicami, bi bil primernejši izbor digitalnega senzorja, kot je na primer DS18B20, vendar je prevagala odločitev za NTC, ker:
-
sem ga imel na zalogi
-
tudi SW sem kopiral iz drugih projektov
-
omogoča meritev z resolucijo 0.01stC
Morda bo kdo rekel, da je resolucija 0,01 stC brez pomena in glede na 0.5% natančnost samega senzorja celo nesmiselna? Če nas zanima samo temperatura, je to res. Če pa nas zanima, kako hitro se prostor ogreva ali ohlaja (gradient), pa je prikaz in obdelava druge decimalke temperature zaželjena. Prikaz na dve decimalki pa je celo zelo moteč, če je šum signala večji od spodnjih decimalk. Odprava šuma zahteva skrben design s spodaj naštetimi HW in SW posegi:
- priključitev NTC preko koaxialnega kabla, priklop direktno na ADC porte
- dober RC filter reda T 0.5s za napajanje ADC, za napajanje NTC in za sam signal iz NTC na ADC
- v SW nastavitev primerno dolge ADC za priključitev višjih upornosti
- v SYSTICK prekinitveni zanki vsako mS naredim prepis ADC meritve v skupno vsoto 300 vzorcev (oversampling)
- prenos seštevka vseh vrednosti vsakih 300mS prepišem preko histereze 0.02 stC, kar prepreči nihanje okoli izmerjene temperature
Ukrepi so bili uspešni. Če pa bi šum ostal, pa bi izvedel še spodnje ukrepe (so ostali na zalogi):
- v samem SW bi konfiguriral prenos ADC meritev preko DMA v ciklični pomnilnik – nastavil bi cca 50 meritev / ms. Potem bi v mS prekinitveni zanki seštel the 50 vrednosti, ter dalje 300mS prišteval vrednost v končni rezultat. S tem bi za 50x povečal oversampling.
- sinhronizacija starta ADC ciklov in SYSTICK prekinitvene zanke z omrežno napetostjo
Iz končne vrednosti TEMP_out (seštevka vseh meritev) sem vsakih 300mS v main() zanki izračunal upornost NTC, ter dalje iz upornosti preko logaritemske funkcije izračunal temperaturo kot podatek ,unsigned int' v enotah 0.01stC.
Nekoliko sem se razpisal mimo bistva - prikaza uporabe GUI_O aplikacije. Kot sem zapisal v uvodu, ni namen članka, da opisujem detajle izvedbe SW v uP, temveč samo povezavo z aplikacijo GUI-O.
Najprej koda za lokalni telefon
Pri izdelavi vmesnika je najenostavneje, da se posvetite sami kodi ter spreminjanju parametrov za lokalni telefon priključen preko Bluetooth vmesnika. Ko bo koda gotova in boste z vmesnikom zadovoljni, pa naredite samo kopijo vseh blokov ter na koncu vsakega stavka pred \n\r dodate parameter za pošiljanje kode na internet: PUB:"" . Tako se izognete dvojnemu popravljanju parametrov.
Izpis izmerjene temperature
Za izpis temperature sem inicializiral labelo LB. Na zgornjem delu ekrana sem predvidel sliko, zato sem izpis pomaknil nekoliko nižje in desno. Pod sam izpis sem namestil podlago BSR in prilagodil senčenje le te tako, da je videti, kot bi vgradil 7 segmentni display. Izbor barv sem kasneje popravil tako, da so skladne s sliko ter ostalimi grafičnimi elementi.
sendstr2("|BSR UID:temp_container X:65 Y:48 W:65 H:10 BGC:#a4d4e2 SBGC:#54b2cd RAD:3 SHE:1 SHHR:1 SHVR:1\r\n"); //ovir z modro podlago ter povdarjenim senčenjem sendstr2("|LB UID:lb_tmp X:66 Y:48 SHE:1 SHE:1 FSZ:8 FGC:#395470 FFA:\"font2\" TXT:\"\"\r\n"); //izpis temperature - samo inicializacija praznega teksta
V main{} sem vsakih 300mS na ekran izpisal pravo vrednost temperature Temp_out kot unsignet int z natančnostjo 0.01stC.
sendstr2("@lb_tmp TXT:\""); //prvi del izpisa stringa - lb_tmp je UID oziroma ime labele sendnr2(TEMP_out/100); //izpis cifer celega števila za temperaturo sendstr2("."); //izpis decimalne pike sendnr2((TEMP_out/10)%10); //izpis prve decimalne številke - desetice sendnr2((TEMP_out)%10); //izpis druge decimalne številke - stotice sendstr2("\\u00BAC\"\r\n"); //zaključni del izpisa je znak celzij, ter enter
Pri izpisu je \” ASCII znak narekovaj, ki se pošlje proti GUI-O, na koncu “ pa je zaključek stringa samega izpisa oziroma parametra funkcije sendstr2(); Seveda je oblikovnih možnosti za izpis ter samih načinov veliko. Lahko bi izpis stringa razdelil na več label, izbral drug font, velikost, barvo ...
Nastavitev termostata
Za nastavitev termostata je primeren drsnik ,slider' |SL , za moje debele prste pa je na ekranu še primernejši daljši ,circular bar' |CB. V uP SW sem odprl spremenljivko Termostat_temp, ki hrani nastavitev temperature. Spremenljivko uporabim ob inicializaciji vmesnika in seveda ob nastavitvi temperature. Za prikaz nastavljene temperature sem inicializiral labelo.
sendstr2("|CB UID:cb1 X:50 Y:83 SHE:1 UD:1 W:85 HAW:16 HAH:16 HAR:8 FGC:#0215fe SFGC:#fe020c BGC:#d0d0d0 BTH:5 LVAL:2 HVAL:35 VAL:"); //circular bar parametri sendnr2(Termostat_temp); //vnos spremenljivke za pravo vrednost sendstr2("\r\n"); // enter - zaključek stringa - inicializacije CB
Izpis nastavljene temperature se naredi kadarkoli, tudi ob ponovni inicializaciji oziroma ob vklopu telefona.
sendstr2("|LB UID:lb_tr X:50 Y:70 SHE:1 SHE:1 FSZ:10 FFA:\"font6\" TXT:\""); //init labele sendnr2(Termostat_temp); //nastavljena temperatura se hrani v spremenljivki sendstr2("\\u00BA\"\r\n"); //zakljucni del izpisa je znak za stopinje, narekovaj ter enter
Sedaj pa še nekaj malega grafične podobe
Preprosta funkcionalnost in zmogljiv ekran kar vabita k izdelavi grafične podobe. GUI-O omogoča sestavljanje slik in izdelanih elementov vmesnika enega na drugega, ter določanje transparentnosti OPA tako, da lahko na katerokoli sliko namestimo funkcionalnost. Na primer na izbrano sliko namestimo tipko BT ter tipki nastavimo transparentnost OPA:0 . S tem dobimo sliko s funkcionalnostjo BT. Na tak način lahko menjamo podobo elementov, dinamično menjamo posamezne slike glede na dotike itd. Namesto slik lahko na poljubnem mestu in v poljubni velikosti vrtimo tudi filemček …
Slika na vrhu ekrana
Sliko na GUI-O vmesnik vnesemo z inicializacijo elementa IM. Seveda je hranjenje in prenos slike iz uP relativno zahtevna, zato GUI-O omogoča nalaganje slik direktno v spomin ANDROUD naprave. Za razvojne potrebe je to OK, za resnejšo uporabo pa je primernejša hramba slik na internetu. Težko od končnega uporabnika pričakujemo, da bo po instalaciji GUI-O še slike naložil na določeno mesto v spomin, zato prenos slik iz neta GUI-O naredi avtomatsko ob prvi inicializaciji. V inicializacijskem stringu vpišemo link za dostop do slike.
Na www.imgur.com sem odprl račun ter odložil in objavil izbrano sliko iz interneta https://i.imgur.com/5eQiBRF.jpg . Samo iskanje slike vzame največ časa, vse ostalo je enostavno in hitro. Po objavi slike traja nekaj sekund, da je le ta javno dostopna preko linka. Sliko sem namestil na zgornji del ekrana ter določil primerno velikost:sendstr2("|IM UID:im1 X:50 Y:20 W:100 H:60 IP:\"https://i.imgur.com/5eQiBRF.jpg\"\r\n"); //slika zgoraj
Popravek stikala
Že izdelano stikalo TG sem premaknil pred prikaz temperature ter pod stikalo dodal labelo (ON-OFF).
sendstr2("|TG UID:tg1 X:17 Y:47 W:25 EN:"); //izpis stringa prvega dela inicializacije stikala sendnr2(TERMOSTAT_SWITCH); //izpis številke 0 ali 1 glede na stanje stikala sendstr2(" H:5 HAW:13 HAH:13 RAD:3 SHVR:1 SHHR:1 FGC:#C70039 BGC:#304C4C4C\r\n"); //tretji del stringa sendstr2("|LB UID:lb1 X:17 Y:56 SHE:1 ROT:0 SHE:1 FGC:#FFFFFF FSZ:6 FFA:\"font6\" TXT:\"OFF - ON\"\r\n"); //napis pod stikalom
Indikator stanja peči
Za indikacijo stanja peči sem si izbral sliko kamina, ki nekako bolj ustreza zgornji lepi sliki vikenda, kot neka električna peč, ki jo bom dejansko krmilil. Kot sem zgoraj že opisoval, lahko slike sestavljamo eno na drugo ter s tem ustvarimo željeno grafično podobo. Na netu sem poiskal dve sliki kaminov, jih primerno obrezal ter zložil pravilno po vrsti. Iz prve slike https://i.imgur.com/w2UteZ1.jpg sem uporabil samo okvir, iz druge slike https://i.imgur.com/OfHNESe.jpg pa opečno oblogo ter kamin brez ognja, ki ponazarja ugasnjeno peč. Pozicijo ter velikost sem prilagodil tako, da druga slika lepo pokrije prvo in je od prve viden samo okvir. Za prikaz delujoče peči sem našel primeren video https://i.imgur.com/lVwrTnZ.mp4 , ki sem ga na enak način, kot slike naložil na www.imgur.com .
sendstr2("|IM UID:im2 X:50 Y:88 W:57 H:35 IP:\"https://i.imgur.com/w2UteZ1.jpg\"\r\n"): /*slika kamin_okvir https://i.imgur.com/w2UteZ1.jpg*/ sendstr2("|IM UID:im3 X:50 Y:89 W:43 H:25 IP:\"https://i.imgur.com/OfHNESe.jpg\"\r\n"); /*slika kamin brez ognja https://i.imgur.com/BEVMIxR.jpg*/ sendstr2("|VI UID:vi1 X:50 Y:90 W:30 H:20 VIS:0 VP:\"https://i.imgur.com/lVwrTnZ.mp4\"\r\n");/*video, ki ponazarja delovanje peči*/
Stanje peči hranim in ob spremembi prepišem na GUI-O kot vidnost videa VIS:[0,1] (ni/je ogenj). Podatek berem direktno iz uP porta, ki krmili rele za vklop/izklop peči. Tu je pomembno, da s komunikacijo @vi1 VIS: po nepotrebnem ne obremenjujemo UART/Bluetooth vmesnika, GUI-O app, interneta in MQTT serverja. Oddaja podatkov je smiselna samo na preklop porta oziroma ob spremembi stanja RELE_STATE.
switch (RELE_STATE){ //preko STATE, da je promet proti GUI-O samo ob preklopu default: break; case 0: if ( (((GPIOB->IDR&0x0080)>>7)&0x01) == 1) //berem port { RELE_STATE = 1; //postavim stanje sendstr2("@vi1 VIS:1\r\n"); //vključim video } break; case 1: if ( (((GPIOB->IDR&0x0080)>>7)&0x01) == 0) //berem port { RELE_STATE = 0; //postavim stanje sendstr2("@vi1 VIS:0\r\n"); //izključim video } break; }
Rezultat
(glej sliko spodaj - enaka slika le brez gumba T?)
Tako celoten inicializacijski blok izgleda:
else if((!strcmp(argument[0],"@init"))||(init_request) ) { /*start GUI-O ali reset*/ init_request = 0; /*brišem dogodek, ki ga proži nalaganje SW v uP - ali reset*/ sendstr2("@cls\r\n"); /*brisanje celega ekrana - vsi GUI elementi*/ sendstr2("|IM UID:im1 X:50 Y:20 W:100 H:60 IP:\"https://i.imgur.com/5eQiBRF.jpg\"\r\n"); /*slika zgoraj*/ sendstr2("|BSR UID:temp_container X:65 Y:48 W:65 H:10 BGC:#a4d4e2 SBGC:#54b2cd RAD:3 SHE:1 SHHR:1 SHVR:1\r\n"); /*modra podlaga*/ sendstr2("|LB UID:lb_tmp X:66 Y:48 SHE:1 SHE:1 FSZ:8 FGC:#395470 FFA:\"font2\" TXT:\"\"\r\n"); /*izpis merjene temperature*/ sendstr2("|TG UID:tg1 X:17 Y:47 W:25 EN:"); /* inicializacija stikala*/ sendnr2(TERMOSTAT_SWITCH); /*izpis številke 0 ali 1 glede na stanje stikala*/ sendstr2(" H:5 HAW:13 HAH:13 RAD:3 SHVR:1 SHHR:1 FGC:#C70039 BGC:#304C4C4C\r\n"); /*tretji del stringa*/ sendstr2("|LB UID:lb1 X:17 Y:56 SHE:1 ROT:0 SHE:1 FGC:#FFFFFF FSZ:6 FFA:\"font6\" TXT:\"OFF - ON\"\r\n"); /*napis pod stikalom*/ sendstr2("|CB UID:cb1 X:50 Y:83 SHE:1 UD:1 W:85 HAW:16 HAH:16 HAR:8 FGC:#0215fe SFGC:#fe020c BGC:#d0d0d0 BTH:5 LVAL:2 HVAL:35 VAL:"); /*circular BAR za nastavitev temperature*/ sendnr2(Termostat_temp); /*CB se nastavi iz spremenljivje uP*/ sendstr2("\r\n"); /*enter - konec stringa*/ sendstr2("|LB UID:lb_tr X:50 Y:70 SHE:1 SHE:1 FSZ:10 FFA:\"font6\" TXT:\""); /*izpis nastavljene temperature*/ sendnr2(Termostat_temp); /*nastavljena temperatura se hrani v spremenljivki*/ sendstr2("\\u00BA\"\r\n"); /*zakljucni del izpisa je znak za stopinje, narekovaj ter enter*/ sendstr2("|IM UID:im2 X:50 Y:88 W:57 H:35 IP:\"https://i.imgur.com/w2UteZ1.jpg\"\r\n"); /*kamin_okvir*/ sendstr2("|IM UID:im3 X:50 Y:89 W:43 H:25 IP:\"https://i.imgur.com/OfHNESe.jpg\"\r\n"); /*kamin*/ sendstr2("|VI UID:vi1 X:50 Y:90 W:30 H:20 VIS:0 VP:\"https://i.imgur.com/lVwrTnZ.mp4\"\r\n");/*video, ki ponazarja delovanje peči*/
Ter dodatna koda za odzive na dotik ekrana
else if (!strcmp(argument[0],"@tg1")) { /*toggle za vklop izklop termostata*/ if (!strcmp(argument[1],"1")) TERMOSTAT_SWITCH = 1; /* je prestavljen na ON */ else if (!strcmp(argument[1],"0"))TERMOSTAT_SWITCH = 0; /* je prestavljen na OFF */ } else if (!strcmp(argument[0],"@cb1")){ /* circular bar za nastavitev termostata*/ Termostat_temp = (strings_to_nr(&argument[1][0])); /* vrednost vpišem v spremenljivko */ sendstr2("@lb_tr TXT:\"");/* vpišem tudi labelo, ki prikazuje nastavitev termostata */ sendnr2(Termostat_temp);/* iz te spremenljivke */ sendstr2("\\u00BA\"\r\n"); /*zakljucni del je znak za stopinje ter enter */ }
S samim izgledom in funkcionalnostjo sem zadovoljen, sedaj pa uredim še oddaljen dostop.
GUI-O koda za oddaljeni dostop
Kot sem že v prvem članku detaljno opisal: GUI-O omogoča ločeno obravnavo lokalnega GUI in oddaljenih GUI. Dostop oddaljenih GUI je preko MQTT serverja z objavo sporočil, kjer je dodan PUB:”ime”. V našem poenostavljenem primeru je ime prazen string ”” , kar pomeni objavo na vse oddaljene uporabnike hkrati. Lahko bi vse stringe oziroma cele bloke kode preprosto kopiral ter dodal parameter PUB:”” , in bi vmesnik deloval BP.
POZOR! Komunikacija preko interneta je plačljiva. Neprestana oddaja stringov proti mqtt serverju vsakih 300mS pomeni na mesečnem nivoju (verjetno) velik promet, četudi je en podatkovni paket zanemarljive velikosti. Odločitev je seveda vaša. Sam nisem testiral, kolikšen promet povzroči takšna komunikacija. Če bo kdo to testiral, naj prosim sporoči rezultat.Odločil sem se in naredil smiselen prenos podatkov proti mqtt serverju. To je ob spreminjanju stanj oziroma ob upravljanju vmesnika (vklop/izklop peči ter nastavitev temperature). Prenos izmerjene temperature proti oddaljenemu uporabniku sem naredil le ob vklopu oddaljene aplikacije GUI-O in na zahtevo uporabnika – glej spodaj dodaten gumb BT in labela LB.
V prvem delu sem opisal način, ko ob kateremkoli vklopu GUI-O ponovno inicializiram vse vmesnike ter seveda ob tem upoštevam stanje naprave. To sem naredil tako, da sem analiziral sprejem @init ter nisem dalje analiziral ostalih parametrov. Sedaj pa moram spremljati vklop oddaljenega uporabnika ter glede na to oddati podatke proti mqtt serverju. To lahko naredim tako, da inicializacijo obeh vmesnikov ločim glede na to, ali ob startu GUI-O sprejmem @init ali @init usr: . Ali pa ohranim reinicializacijo ob kateremkoli vklopu vmesnika takoj na @init ter ne analiziram sprejem druge besede usr: . Glede na to, da bo telefon na lokaciji stalno vključen, inicializacija le tega ne bo pogosta in lahko obremeni internet, torej vedno inicializiram oba vmesnika.
Kopiral sem celoten inicializacijski blok ter na konec vsakega stringa pred \r\n dodal dodal PUB:"". Tako se sedaj inicializacija izvede vedno v paketu proti lokalnemu in oddaljenemu uporabniku. V inicializacijo proti oddaljenemu uporabniku sem dodal podatek o izmerjeni temperaturi. To je ista koda, kot je za oddajo temperature na lokalni telefon vsakih 300mS, le da je dodan PUB:"" . Temperaturo proti oddaljenemu uporabniku ne osvežujem stalno, temveč samo ob startu v inicializacijskem bloku kot odziv na @init in na ukaz (dodan gumb). Temperaturo na lokalnem telefonu popravlja main{} vsakih 300ms, za oddaljeni telefon pa je popravek dodatek inicializacije spodaj:
sendstr2("@lb_tmp TXT:\""); /*zacetni del stringa za izpis do prvega narekovaja - zacetek stringa za izpis*/ sendnr2(TEMP_out/100); /*celo ševilo*/ sendstr2("."); /*izpis decimalne pike */ sendnr2((TEMP_out/10)%10); /*izpis decimale desetice*/ sendnr2((TEMP_out)%10); /*izpis stotice*/ sendstr2("\\u00BA\" PUB:\"\"\r\n"); /*zakljucni del izpisa je znak za stopinje ter enter - celoten string je preusmerjen na net*/
Sedaj se na oddaljenem telefonu prikaže temperatura po inicializaciji. Periodično oddajo temperature vsakih 300mS pa sem pustil samo na lokalnem telefonu. Da bi lahko na oddaljenem telefonu opazoval, ali se temperatura dviga, ali spušča, sem dodal gumb BT, na katerega oddam ponoven izpis temperature proti oddaljenemu telefonu. BT je blizu elementa CB. BT sem inicializiral za inicializacijo cirkular bar CB, sicer CB prekrije odziv na BT in BT ne deluje.
sendstr2("|BT UID:bt1 X:90 Y:57 W:15 H:8 BGC:#a4d4e2 SBGC:#54b2cd RAD:3 SHE:1 SHHR:1 SHVR:1 SVAL:\"tipka\" FSZ:10 TXT:\"<b>T?</b>\" PUB:\"\"\r\n");
Odziv na ta dodan gumb BT pa je:
else if (!strcmp(argument[0],"@bt1")){ */gumb na remote*/ { /* ista koda kot na @init zapisana zgoraj */ }
Da na oddaljenem telefonu delujejo vse kontrole oziroma dotiki, sem moral na vse odzive dodati osveževanje LB, TG, CB tudi z dodanim parametrom PUB:"" . Tudi tu ne analiziram, ali je kontrola iz lokalnega telefona @tg1 0/1, ali iz oddaljenega telefona @tg1 0/1 usr: . Procesor enotno odreagira na prvi dve besedi ter izvede popravek na oba telefona.
else if (!strcmp(argument[0],"@tg1")) {/*toggle za vklop izklop termostata*/ if (!strcmp(argument[1],"1")) /*toggle se preklopi na ON*/ { sendstr2("@tg1 EN:1\r\n"); /*če ga prožim na oddaljenem, ga moram popraviti tudi na lokalnem telefonu*/ sendstr2("@tg1 EN:1 PUB:\"\"\r\n"); /*in obratno - vedno preklopim oba*/ TERMOSTAT_SWITCH = 1; /*popravim spremenljivko v uP*/ } else if (!strcmp(argument[1],"0")){ /*toggle se preklopi na OFF*/ sendstr2("@tg1 EN:0\r\n"); /*če ga prožim na oddaljenem, ga moram popraviti tudi na lokalnem telefonu*/ sendstr2("@tg1 EN:0 PUB:\"\"\r\n"); /*in obratno - vedno preklopim oba*/ TERMOSTAT_SWITCH = 0; /*popravim spremenljivko v uP */ } }
Ker vmesnik upravljata dva uporabnika, lokalni in oddaljeni, moram programsko poskrbeti, da se preklop na enem, zgodi tudi na drugem. Ker nimam informacije (ne analiziram tretje besede) vedno postavim pravilen EN: na oba. Enako moram narediti tudi na circular bar CB.
Nastane oddaljen vmesnik
Slika in funkcionalnost oddaljenega telefona je nekoliko drugačna, da ne obremenjujem internet povezav med tem, ko ni prisotnega nobenega oddaljenega uporabnika. Z izdelkom sem zadovoljen.
V nadaljevanju bom sistem razširil, saj imam nove ideje: Zatemnitev ekrana kot nočni režim, izračun porabe električne energije, izračun prometa na internet, izdelava drugačnega formata prikaza za na tablico in še kaj bi se našlo. Za drugi del pa naj bo to dovolj. -