Naloga, ki sem si jo zastavil, je oddaljen nadzor in upravljanje ogrevanja vikenda. V vseh primerih za oddaljen nadzor potrebujemo dostop do interneta. Ker na vikendu še nimamo WiFi omrežja, se za dostop do interneta ponuja uporaba GSM signala. Tokrat bomo dodajali funkcionalnost in grafiko.
Avtor: Janez Pirc
E-pošta: janez.pircc@gmail.com
2021-300-44
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.
Ta vsebina je samo za naročnike
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 pretehtala odločitev za NTC, ker:
- sem ga imel na zalogi
- tudi SW sem kopiral iz drugih projektov
- omogoča meritev z resolucijo 0.01 °C
Morda bo kdo rekel, da je resolucija 0,01 °C 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želena. Prikaz na dve decimalki pa je celo zelo moteč, če je šum signala večji od spodnjih decimalk. Odprava šuma zahteva skrbno načrtovanje s spodaj naštetimi HW in SW posegi:
- priključitev NTC preko koaksialnega 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 milisekundo naredim prepis ADC meritve v skupno vsoto 300 vzorcev (oversampling)
- prenos seštevka vseh vrednosti vsakih 300mS prepišem preko histereze 0.02 °C, kar prepreči nihanje okoli izmerjene temperature
Ukrepi so bili uspešni. Če bi šum ostal, bi pa naredil š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 teh 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.01 °C.
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 nr 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 displej. 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 rn"); //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:""rn"); //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.01 °C.
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"rn"); //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("rn"); // 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"rn"); //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 kratek film.
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 ANDROID 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 interneta 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"rn"); //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:#304C4C4Crn"); //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" rn"); //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 želeno grafično podobo. Na internetu 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"rn"): /*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"rn"); /*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"rn"); /*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; case0: if ( (((GPIOB->IDR&0x0080)>>7)&0x01) == 1) //berem port { RELE_STATE = 1; //postavim stanje sendstr2("@vi1 VIS:1rn"); //vključim video } break; case1: if ( (((GPIOB->IDR&0x0080)>>7)&0x01) == 0) //berem port { RELE_STATE = 0; //postavim stanje sendstr2("@vi1 VIS:0rn"); //izključim video } break; }
Rezultat
(glej sliko spodaj – enaka slika le brez gumba T?)
Tako celoten inicializacijski blok izgleda:
elseif((!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("@clsrn"); /*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"rn"); /*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 rn"); /*modra podlaga*/ sendstr2("|LB UID:lb_tmp X:66 Y:48 SHE:1 SHE:1 FSZ:8 FGC:#395470 FFA:"font2" TXT:""rn"); /*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:#304C4C4Crn"); /*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" rn"); /*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("rn"); /*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"rn"); /*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"rn"); /*kamin_okvir*/ sendstr2("|IM UID:im3 X:50 Y:89 W:43 H:25 IP:"https://i.imgur.com/OfHNESe.jpg"rn"); /*kamin*/ sendstr2("|VI UID:vi1 X:50 Y:90 W:30 H:20 VIS:0 VP:"https://i.imgur.com/lVwrTnZ.mp4"rn"); /*video, ki ponazarja delovanje peči*/
Ter dodatna koda za odzive na dotik ekrana
elseif (!strcmp(argument[0],"@tg1")) { /*toggle za vklop izklop termostata*/ if (!strcmp(argument[1],"1")) TERMOSTAT_SWITCH = 1; /* je prestavljen na ON */ elseif (!strcmp(argument[1],"0"))TERMOSTAT_SWITCH = 0; /* je prestavljen na OFF */ } elseif (!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"rn"); /*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 podrobno 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 smiseln 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 rn 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 prve
ga 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:””rn”);
/*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:””rn”);
Odziv na ta dodan gumb BT pa je:
elseif (!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.
elseif (!strcmp(argument[0],”@tg1″))
{/*toggle za vklop izklop termostata*/
if (!strcmp(argument[1],”1″))
/*toggle se preklopi na ON*/
{
sendstr2(“@tg1 EN:1rn”);
/*če ga prožim na oddaljenem, ga moram popraviti tudi na lokalnem telefonu*/
sendstr2(“@tg1 EN:1 PUB:””rn”);
/*in obratno – vedno preklopim oba*/
TERMOSTAT_SWITCH = 1; /*popravim spremenljivko v uP*/
}
elseif (!strcmp(argument[1],”0″)){
/*toggle se preklopi na OFF*/
sendstr2(“@tg1 EN:0rn”);
/*če ga prožim na oddaljenem, ga moram popraviti tudi na lokalnem telefonu*/
sendstr2(“@tg1 EN:0 PUB:””rn”);
/*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 medtem, 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 do interneta, izdelava drugačnega formata prikaza za tablico in še kaj bi se našlo. Za drugi del pa naj bo to dovolj