-
Inroducere
Am cumpărat de curând un șemineu care poate distribui căldură și la radiatoare (pe scurt: termoșemineu). Pentru a face asta, folosește o pompă de recirculare.
Pompa ar trebui să pornească când apa atinge o anumită temperatură și să se oprească atunci când scade sub o anumită valoare. Deloc complicat până aici, sistemul folosind un termostat simplu care a funcționat acceptabil:
Erau totuși câteva neajunsuri, cum ar fi faptul că nu făcea contact destul de bine cu țeava de căldură, așa că trebuia să pornească puțin mai repede decat ar fi trebuit, ca să compenseze. Pentru că era configurat dintr-o singură setare comună, asta însemna că se și oprea un pic cam târziu, deci nu puteam configura setările cu ușurință.
O altă dorință de-a mea era ca dispozitivul să poată funcționa independent. Dacă se deconectează de la rețea dintr-un motiv oarecare, sau Home Assistant nu mai funcționează, ar trebui să pornească singur ca să nu ajungă să fiarbă apa din instalație. Să zicem că nu vreau să explodeze doar de dragul de a arată cool. Și încă un lucru: ar fi drăguț să se oprească singur, ca să nu recircule apă degeaba.
Sonoff TH Elite cu senzor de temperatură pare să acopere perfect toate cerințele, chiar are în plus și un ecran simpatic pe care se poate vedea temperatura apei. Trebuie să clarific că, din păcate pentru mine, nu sunt sponsorizat pentru acest proiect, iar dispozitivul a fost cumpărat din banii mei.
Totul era perfect, cu excepția unui singur detaliu: nu am găsit un mod de a adauga cu ușurință un slider pentru a controla temperatura. Aș fi putut hardcoda valorile, pentru că în principiu nu ar fi trebui să se schimbe în timp, dar atunci nu aș mai avea cu ce sa mă laud.
Și așa a început aventura...
Să trecem la treaba
Dispozitivul
Nu o să vorbesc despre pregătirea dispozitivului, sunt destule tutoriale despre flashing Tasmota. Codul ar trebui să funcționeze cu orice dispozitiv care rulează Tasmota, de fapt întreg codul din acest tutorial a fost scris și testat pe un controller generic Esp32 găsit într-un sertar.
Berry script
Dacă ai ajuns până aici, probabil că nu ți-ai ars nici controllerul Esp, nici casa. Felicitări, ai trecut cu succes de partea cea mai dificilă!
Toată magia vine de la Berry Script, un limbaj de scripting care permite extinderea funcționalităților Tasmota.
Din păcate, documentația nu este foarte clară când vine vorba de componente noi și există un singur exemplu. Există și un exemplu complet, dar este foarte complex și greu de urmărit.
Dar să începem. În primul rând, trebuie să facem un fișier nou care va conține codul. Pentru a face asta, conectează-te la UI-ul dispozitivului folosind IP-ul lui și mergi la
Tools
>Manage File system
și apasă peCreate and Edit new file
.Introdu numele fișierului sus
autoexec.be
, șterge conținutul fișierului și apasă peSave
. Din acest punct vom edita doar acest fișier. Când mă voi referi la editat, mă voi referi la acest fișier.Că să ne asigurăm că fișierul se compilează, din meniul principal (
Main Menu
), mergi laTools
>Berry Scripting console
.Introdu
load('autoexec.be')
și apasă peRun code (or press ‘Enter’ twice)
. Ar trebui să fie afișat output-ultrue
.Setarea componentei
Editează
autoexec.be
și introdu codul de mai jos care reprezintă o componentă de baza:1import webserver 2 3class MySlider 4 def web_add_main_button() 5 webserver.content_send("<div style='padding:0'><h3>Set pump temperature</h3></div>") 6 end 7end 8 9 10d1 = MySlider() 11tasmota.add_driver(d1)
Reîncarcă UI-ul folosind comandă
load('autoexec.be')
, așa cum a fost descris mai sus.În acest moment ar trebui să apară pe ecran textul
Set pump temperature
.Se pare că primul secret a ieșit la iveală, trebuie doar să afișăm niște HTML pentru a genera o nouă componentă.
Ne mai lipsește un lucru, trebuie să salvăm valoarea care se setează folosind slider-ul. Din fericire, lucrurile sunt destul de simple dacă folosim librăria
persist
. Putem seta și o valoare implicită atunci când componentă se încarcă prima dată:1 def init() 2 if (!persist.m_start_temp) 3 persist.m_start_temp = 55 4 end 5 6 if (!persist.m_stop_temp) 7 persist.m_stop_temp = 50 8 end 9 end
Acum avem valori implicite de start și stop pentru temperatura.
Pentru că am nevoie de două valori, una de pornire și una de oprire, voi face o funcție care poate afișa fie una din valori, fie pe cealaltă, iar valoarea se va trimite folosind
webserver.content_send
, deci acum metodăweb_add_main_button()
va deveni:1 def web_add_main_button() 2 webserver.content_send("<div style='padding:0'><h3>Set pump temperature</h3></div>") 3 webserver.content_send( 4 self._render_button(persist.m_start_temp, "Start", "start") 5 ) 6 webserver.content_send( 7 self._render_button(persist.m_stop_temp, "Stop", "stop") 8 ) 9 end
Argumentele sunt: valoarea curentă, o etichetă și un id. Prin id se va identifica elementul care s-a schimbat.
Metodă pentru afișare este:
1 def _render_button(persist_item, label, id) 2 return "<div style='padding:0'>"+ 3 "<table style='width: 100%'>"+ 4 "<tr>"+ 5 "<td><label>"..label.." </label></td>"+ 6 "<td align=\"right\"><span id='lab_"..id.."'>"..persist_item.."</span>°C</td>"+ 7 "</tr>"+ 8 "</table>"+ 9 "<input type=\"range\" min=\"20\" max=\"70\" step=\"1\" "+ 10 "onchange='la(\"&m_"..id.."_temp=\"+this.value)' "+ 11 "oninput=\"document.getElementById('lab_"..id.."').innerHTML=this.value\" "+ 12 "value='"..persist_item.."'/>"+ 13 "</div>" 14 end
oninput
este un artificiu pentru a schimba valoarea afișată când sliderul se mișcă, așa compensăm pentru faptul că elementul slider din html nu are un mod de a afișa valoarea selectată, așa că nu am ști ce a fost selectat până nu oprim selecția, adică ar fi o experiență neplacută de utilizare.De fapt
onchange
se folosește pentru a schimba valoarea, iar această folosește un pic de magie Tasmota.Acum că afișăm totul, avem nevoie de un mod de a persista valoarea, acest lucru se face folosind metodă
web_sensor
:1 def web_sensor() 2 3 if webserver.has_arg("m_start_temp") 4 var m_start_temp = int(webserver.arg("m_start_temp")) 5 persist.m_start_temp = m_start_temp 6 persist.save() 7 end 8 9 if webserver.has_arg("m_stop_temp") 10 var m_stop_temp = int(webserver.arg("m_stop_temp")) 11 persist.m_stop_temp = m_stop_temp 12 persist.save() 13 end 14 15 end
Deja putem pune totul cap la cap, avem tot ce era necesar. Totuși mai este ceva ce-mi doresc: să pot vedea valoarea selectată în Home Assistant (dacă este folosit). În caz că am uitat ce este setat, ar fi interesant să afișăm valoarea undeva prin Home Assistant. Pentru a exporta valorile trebuie implementat
json_append
:1 def json_append() 2 var start = int(persist.m_start_temp) 3 var stop = int(persist.m_stop_temp) 4 var msg = strîng.format(",\"Pump\":{\"start\":%i,\"stop\":%i}", start, stop) 5 tasmota.response_append(msg) 6 end
Acum chiar avem un exemplu complet! Să-l vedem:
1import webserver 2import persist 3import strîng 4 5class MySlider 6 7 def init() 8 if (!persist.m_start_temp) 9 persist.m_start_temp = 55 10 end 11 12 if (!persist.m_stop_temp) 13 persist.m_stop_temp = 50 14 end 15 16 end 17 18 def web_add_main_button() 19 webserver.content_send("<div style='padding:0'><h3>Set pump temperature</h3></div>") 20 webserver.content_send( 21 self._render_button(persist.m_start_temp, "Start", "start") 22 ) 23 webserver.content_send( 24 self._render_button(persist.m_stop_temp, "Stop", "stop") 25 ) 26 end 27 28 def _render_button(persist_item, label, id) 29 return "<div style='padding:0'>"+ 30 "<table style='width: 100%'>"+ 31 "<tr>"+ 32 "<td><label>"..label.." </label></td>"+ 33 "<td align=\"right\"><span id='lab_"..id.."'>"..persist_item.."</span>°C</td>"+ 34 "</tr>"+ 35 "</table>"+ 36 "<input type=\"range\" min=\"20\" max=\"70\" step=\"1\" "+ 37 "onchange='la(\"&m_"..id.."_temp=\"+this.value)' "+ 38 "oninput=\"document.getElementById('lab_"..id.."').innerHTML=this.value\" "+ 39 "value='"..persist_item.."'/>"+ 40 "</div>" 41 end 42 43 44 def web_sensor() 45 46 if webserver.has_arg("m_start_temp") 47 var m_start_temp = int(webserver.arg("m_start_temp")) 48 persist.m_start_temp = m_start_temp 49 persist.save() 50 end 51 52 if webserver.has_arg("m_stop_temp") 53 var m_stop_temp = int(webserver.arg("m_stop_temp")) 54 persist.m_stop_temp = m_stop_temp 55 persist.save() 56 end 57 58 end 59 60 def json_append() 61 var start = int(persist.m_start_temp) 62 var stop = int(persist.m_stop_temp) 63 var msg = strîng.format(",\"Pump\":{\"start\":%i,\"stop\":%i}", start, stop) 64 tasmota.response_append(msg) 65 end 66end 67 68slider = MySlider() 69tasmota.add_driver(slider)
Este destul de mult cod, dar sper că nu este foarte greu de înțeles.
Setarea automatizării
Acum că avem valorile, putem seta automatizarea efectivă. Regulile sunt foarte simple:
- când se atinge temperatura de start => pornește pompa;
- când temperatura coboară sub valoarea de oprire => oprește pompa.
1def heater_control(value) 2 3 if value >= persist.m_start_temp 4 tasmota.set_power(0, true) 5 end 6 7 if value < persist.m_stop_temp 8 tasmota.set_power(0, false) 9 end 10end 11# această este specifică senzorului Sonoff TH Elite 12tasmota.add_rule("DS18B20#Temperature", heater_control)
Dacă s-a folosit alt dispozitiv, este posibil să aibă alt nume față de
"DS18B20#Temperature"
, și probabil va trebui schimbat cu cel potrivit.Noua interfață Sonoff TH Elite ar trebui să arate cam așa:
Concluzie
Folosind cod destul de simplu, poți transforma un dispozitiv inteligent (dar nu foarte interesant) în ceva cu care să te poți lăuda la prieteni. Valorile se pot seta acum cu ușurință într-o manieră vizuală, iar rezultatul se poate vedea în Home Assistant.
-
Intro
În 2011 am fost la primul meu hackathon. Am fost foarte entuziasmat, compania asta mare și faimoasă venea în România pentru a organiza un hackathon! Compania respectivă era Yahoo!, și da, știu că acum nu pare ceva spectaculos, dar pe atunci era impresionant.
În timpul evenimentului am dezvoltat (împreună cu câțiva prieteni) un mic joc amuzant de snake multiplayer, care, ca orice proiect de hackathon, a fost abandonat în ziua următoare.
Jocul a fost scris în Node.js și a fost primul meu proiect de backend în JS, chiar dacă eram deja un adevărat entuziast al limbajului.
Câțiva ani mai târziu am vrut să revizuiesc proiectul și să văd cam cât de groaznic era scris de fapt. Din păcate m-am lovit de o problema: nu am versionat pachetele așa că nu mai știam versiunea lor și nimic nu mai funcționa. Pe atunci lucrurile erau destul de la început cu Node.js, așa că schimbările de la o versiune la altă aveau tendința să fie substanțiale. Prin urmare, am decis că era timpul să-l refac, doar pentru amuzamentul propriu.
Refactorizarea
Era finele anului 2015, acordul de la Paris urma să puna stop poluării, Volkswagen a fost găsit vinovat de manipularea rezultatelor testelor pentru emisiile de poluare, iar eu refăceam "Tequila worms".
Node.js nu mai era o noutate, devenise o tehnologie consacrată și începuse deja să suporte alternative la JS, sau mai bine spus la EcmaScript. Unele dintre cele mai importante opțiuni erau: TypeScript și CoffeeScript.
Dacă TypeScript oferea tipuri de date statice, lucru care ajută la găsirea unor probleme la compilare înainte de runtime, CoffeeScript avea o abordare mult mai orientată către "cod". CoffeeScript permitea o sintaxa mult mai agreabilă. Recunosc, avea tendința să fie un pic mai dificilă la început, dar arăta super!
Decizia a fost luată, backend-ul urma să fie scris în CoffeeScript, era în mod evident o alternativă mai bună decât TypeScript.
Pentru frontend erau câteva opțiuni, dar era cert că nu voi folosi jQuery, pentru că erau deja disponibile multe frameworkuri interesante. Întrebarea era de fapt: să folosesc React sau Angular?
Credeam cu îndârjire că o librărie de frontend nu ar trebui să folosească backend pentru compilare. Angular era deja la versiunea 1, lucru care denotă stabilitate, un favorit! Avea o versiune stabilă (deci nu avea să se schimbe prea curând), o comunitate importantă în spate și, în plus, era dezvoltată de Google!
React, pe de altă parte, nu era la fel de popular. Avea nevoie de backend pentru compilare, lucru care e un pic ciudat dacă te gândești, era făcută de Facebook, și mai avea și o abordare diferită față de mai popularul MVC.
Angular (versiunea 1) și CoffeeScript au fost câștigătorii! Pot spune că a fost o plăcere să reconstruiesc acest joculeț și, pe lângă asta, am avut ocazia să lucrez cu tehnologii de ultima oră, lucru care a făcut proiectul să fie pregătit să reziste probei timpului!
Rezultatul este aici: https://github.com/claudiu-persoiu/tequila-worms/
8 ani mai târziu...
Acordul de la Paris nu a schimbat lumea așa de radical cum aveam nevoie, Volkswagen a început să facă mașini electrice și hibride, iar aplicația mea nu este tocmai pregătită pentru viitor, nici măcar pentru prezent.
O dată ce Node.js s-a maturizat, dependințele lui au devenit și ele mai complexe și au nevoie din ce în ce mai multă atenție pentru tot felul de actualizări.
Pe de altă parte, Angular a fost complet regândit în versiunea 2, iar visul meu de a face frontend independent de backend a rămas doar un vis.
CoffeeScript a fost înlocuit ușor, ușor de TypeScript și mai există acum ca o bucățică de istorie pentru entuziaști decât ca un limbaj folosit uzual.
Pe pagina de Wikipedia a CoffeeScript, la secțiunea "Adoption", este o frază care descrie foarte bine la ce s-a ajuns:
In data de 13 septembrie 2012, Dropbox a anunțat că au rescris din JavaScript in CoffeeScript tot codul lor care rulează în browser, dar au migrat apoi către TypeScript în 2017.
Concluzii
Chiar dacă Node.js se pare că a fost o decizie bună pentru stack, restul alegerilor făcute demonstrează că nu ar trebui să urmezi sfaturi de la oameni de pe Internet, mai ales nu pe ale mele, pentru că nu am fost deloc pe aproape...
Pe de altă parte, niciuna dintre tehnologii nu a fost o alegere greșită la momentul respectiv, iar să vrei să construiesti frontend fără librării de backend nu era o dorință atât de excentrică pe cât pare astăzi.
CoffeeScript a fost doar ghinionist, deoarece, chiar dacă sintaxa arată mai bine, făcea codul să arate un pic mai greu de citit pentru programatorii proveniți din limbaje cu sintaxa din familia C.
Chiar dacă sintaxa TypeScript nu este mai plăcută, ajută totuși cu verificarea tipurilor de date și rezolvă o problemă reală.
Concluzia poveștii cred că este că nu ai cum să faci o aplicație rezistentă la trecerea timpului dacă te bazezi pe un framework și, mai ales, să nu ai încredere în predicțiile celorlalți, în special nu în ale mele.
--
-
Introducere
Recent am fost pus într-o situatie dificilă. Am primit un laptop cu Windows. Partea și mai ciudată a fost că eu eram singurul din toata echipa tehnică care a primit un laptop cu Windows, toți ceilalți colegi aveau Mac. In mod evident, proiectul nu a fost făcut niciodată sa funcționeze pe Windows.
Singurii care au mai primit Windows în trecut l-au folosit în jur de o săptamană, până când le-a venit MacBook-ul, dar pe măsură ce treceau zilele (și săptămânile), a devenit evident că acesta nu o să fie și cazul meu.
Având in vedere că suntem în anul 2021 și criza cipurilor este în plină desfășurare, a trebuit să mă descurc în condițiile date.
Inițial am încercat opțiunea evidentă, Docker for Windows și Git for Windows, dar nu s-a dovedit a fi o idee prea bună. Problema este că aplicațiile Windows au nevoie de terminator de linie in stil Windows (\r\n), pe cand aplicațiile de Linux și macOS folosesc terminator de tip Unix (\n), și s-a dovedit foarte dificil să-mi dau seama unde e nevoie de unul și unde de altul. Chiar și atunci cand mi-am dat seama care se folosește unde, tot aveam problema că nu puteam face commit cu fișierele cu terminații diferite și trebuia sa le fac stash înainte de fiecare pull.
Cum funcționează
-
Descarcă și instalează WSL2 folosind instrucțiunile de aici: https://docs.microsoft.com/en-us/windows/wsl/install-win10
-
Restartează Windows (pentru că asta e ceva ce faci atunci când lucrezi cu Windows)
-
Din "Microsoft Store" instalează o distribuție de Linux (în acest tutorial voi folosi Ubuntu)
-
După ce s-a instalat distribuția, setup-ul va cere crearea de cont cu parolă, folosește ceva adecvat, acest cont se va folosi doar în interiorul distribuției Linux;
-
Deschide un PowerShell cu drepturi de admin și rulează următoarele comenzi:
wsl --set-version Ubuntu 2 wsl --set-default Ubuntu
-
Pentru a verifica dacă pasul anterior a rulat cu succes, în aceeași instanță de shell rulează
wsl -l -v
, unde rezultatul ar trebuie să fie o linie cu Ubuntu și versiunea 2 -
Acum putem instala Docker, folosind pașii de aici: https://docs.docker.com/engine/install/ubuntu/#install-using-the-repository
-
Suntem aproape gata, trebuie doar să facem să pornească serviciul de Docker automat;
-
Apasă "Start" și apoi "Task Scheduler";
-
Apoi "Actions" > "Create Basic Task...";
-
Dă-i un nume și apasă Next;
-
în secțiunea de Trigger selectează "When I log on" și apasă Next;
-
în secțiunea de Action selectează "Start a program" și apasă Next;
-
în ecranul "Start a program" valoarea pentru "Program/script:" va fi "C:\Windows\System32\wsl.exe" și în "Add arguments (optional)" adaugă "-u root service docker start" și apoi apasă Next și Finish;
Asta este tot, după un restart de Windows, daemon-ul de Docker ar trebui sa porneasca automat.
Am observat ca uneori Windows nu rulează task-urile de startup dacă laptopul nu este la încărcat, dacă ai această problemă, sau Docker pur și simplu nu pornește, rulează în interiorul distribuției:
service docker start
O sugestie
Dacă folosești calculatorul doar pentru dezvoltare de software, ar trebui să iei în considerare distribuțiile de Linux mai prietenoase, cum este Ubuntu. Aplicații ca Docker și Git rulează mult mai bine pe Linux și suportul pentru aplicații ca IntelliJ și VS Code este foarte bun. Dacă nu ai încercat niciodată, sunt mult mai prietenoase cu utilizatorul decât ai crede.
Să folosești Docker pe orice altceva decât Linux este un compromis, chiar și pe Mac, ca să nu mai vorbim de Apple Silicon care este un compromis chiar mai mare decât Windows.
Folosesc Ubuntu pe calculatoarele mele personale de mai mult de un deceniu și, cu foarte puține excepții (cum ar fi interacțiunea cu unele aplicații web ale statului român), nu am avut nevoie de altceva.
-
-
Am acest blog de 12 ani.
În 2008, când am început blog-ul, Wordpress era cea mai populară platformă open source pentru blogging.
O dată cu trecerea timpului, au fost descoperite multe probleme de securitate.
Plugin-urile, motorul principal pentru extinderea platformei și motivul principal pentru care era atât de populară, creau îngrijorare din punct de vedere arhitectural.
Astăzi, după toți acești ani și cu toate aceste probleme de arhitectură și de securitate, Wordpress a ajuns sa fie… cea mai populară platformă PHP de pe Internet! Codul vechi este în continuare folosit de ultimele versiuni, iar majoritatea pluginurilor din 2008 sunt încă functionale!
Pe măsură ce a trecut timpul (iar eu am scris din ce în ce mai puțin), a devenit evident că petreceam mai mult timp menținând platforma Wordpress actualizată decât scriind.
Așa că am decis într-un final să migrez la Hugo!
Hugo este un generator de site-uri statice, realizat in Golang. Site-ul construit cu Hugo este static, și (în mod și mai bizar) nu are baze de date, sunt doar fișiere de configurare și de tip Markdown. Așa că rezultă o platformă care aproape că nu mai are nevoie de mentenanță.
Împreună cu noua platformă a venit și o nouă temă, care are un design mai modern și o funcționalitate deosebit de căutată în vremurile noastre: modul de noapte!
Jekyll este probabil cea mai populară platformă pentru generat site-uri statice, dar este făcută cu Ruby așa că am preferat Hugo pentru că template-urile folosesc sintaxa de Go Templates. Este vorba mai mult de gusturi decât de altceva.
Iar cu acest update, vă invit să vă bucurați de acest blog, acum chiar și mai static!
-
Am avut recent o problemă interesantă: accesul meu la un depozit care necesită autentificare a fost invalidat în mod abrupt. Problema în cazul meu a originat de la depozit, dar ar fi putut fi la fel de ușor din cauză că mi-am pierdut credențialele sau altceva similar. Accesul meu urma să fie restabilit în curând. Cum știm cu toții, “în curând” în IT poate dura între cateva minute și dispariția tuturor formelor de viață cunoscute, iar eu trebuia să fac un build până atunci.
Și încă un detaliu, localul meu funcționa în continuare.
Pentru o vreme m-am întrebat de ce funcționează localul meu, dar tu ai citit titlul, deci ai văzut deja motivul, cache-ul meu local era încă valabil.
Am căutat o vreme o metoda usoara de a pune pachetele de pe local la eliminarea care făcea construirea, și există modalități, dar nu intenționam să pierd zile lucrand la o problemă care s-ar putea rezolva oricum înainte sa-mi finalizez eu abordarea.
Soluția a fost foarte simpla:
- trebuie doar făcută o copie a cache-ului local, pentru linux este în mod normal în “~/.composer”;
- trebuie copiata pe serverul de interes intr-o locație preferată (sa zicem /tmp/composer_cache);
- exportam variabila COMPOSER_CACHE_DIR (“export COMPOSER_CACHE_DIR=/tmp/composer_cache”);
- se ruleaza composer in mod normal.
Asta e tot, acum poți folosi cache-ul local pe un server la distanță. Nu este cel mai elegant lucru, dar este un hack rapid foarte util.