Acasă
Despre
Cautare
🌐
English Română
  • Slider in Tasmota folosind BerryScript

    Read this post in English

    Nov 10, 2024 Tasmota BerryScript homeassistant
    Share on:

    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:

    termostat cu bratara

    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.

    Sonoff TH Elite

    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ă pe Create and Edit new file.

    Introdu numele fișierului sus autoexec.be, șterge conținutul fișierului și apasă pe Save. 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 la Tools > Berry Scripting console.

    Introdu load('autoexec.be') și apasă pe Run code (or press ‘Enter’ twice). Ar trebui să fie afișat output-ul true.

    Executie cod

    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.

    Setarea temperaturii

    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:

    Tasmota Sonoff TH Elite

    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.

  • Proiectul care rezista probei timpului

    Read this post in English

    Sep 15, 2023 javascript coffeescript angular
    Share on:

    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.

    --

  • Docker in interiorul wsl2

    Read this post in English

    Sep 7, 2021 docker wsl2 windows
    Share on:

    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ă

    1. Descarcă și instalează WSL2 folosind instrucțiunile de aici: https://docs.microsoft.com/en-us/windows/wsl/install-win10

    2. Restartează Windows (pentru că asta e ceva ce faci atunci când lucrezi cu Windows)

    3. Din "Microsoft Store" instalează o distribuție de Linux (în acest tutorial voi folosi Ubuntu)

    4. 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;

    5. Deschide un PowerShell cu drepturi de admin și rulează următoarele comenzi:

      wsl --set-version Ubuntu 2 wsl --set-default Ubuntu

    6. 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

    7. Acum putem instala Docker, folosind pașii de aici: https://docs.docker.com/engine/install/ubuntu/#install-using-the-repository

    8. Suntem aproape gata, trebuie doar să facem să pornească serviciul de Docker automat;

    9. Apasă "Start" și apoi "Task Scheduler";

    10. Apoi "Actions" > "Create Basic Task...";

    11. Dă-i un nume și apasă Next;

    12. în secțiunea de Trigger selectează "When I log on" și apasă Next;

    13. în secțiunea de Action selectează "Start a program" și apasă Next;

    14. î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.

  • Migrând de la Wordpress

    Read this post in English

    Mar 14, 2021 wordpress hugo
    Share on:

    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!

  • Calea personalizată pentru Composer cache

    Read this post in English

    Oct 24, 2020 composer
    Share on:

    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.

    • ««
    • «
    • 1
    • 2
    • 3
    • 4
    • 5
    • »
    • »»

Claudiu Perșoiu

Programare, tehnologie și altele
Mai multe

Postări recente

  • Slider in Tasmota folosind BerryScript
  • Proiectul care rezista probei timpului
  • Docker in interiorul wsl2
  • Migrând de la Wordpress
  • Calea personalizată pentru Composer cache
  • Magento2 si crudul adevar
  • Un pic de PHP, Go, FFI si atmosfera de sarbatori
  • Cum sa folosesti Xiaomi Air Conditioning Companion in Home Assistant in doar de 20 pasi usor de urmat!

DIVERSE 72 PHP 67 JAVASCRIPT 22 BROWSERS 12 MYSQL 12 WEB STUFF 12 MAGENTO 7 DESIGN PATTERNS 5 HARDWARE 3 HOME AUTOMATION 2 LINUX-UNIX 2 GO 1 MISCELLANEOUS 1

PHP 52 JAVASCRIPT 20 PHP5.3 14 MYSQL 13 PHP6 12 PHP5 10 FIREFOX 9 CERTIFICARE 8 INTERNET EXPLORER 8 ZCE 8 ZEND 8 CERTIFICATION 7 MAGENTO 7 HACK 6
Toate etichetele
10 ANI1 3D1 ADOBE AIR2 AJAX1 ANDROID3 ANGULAR1 ANONYMOUS FUNCTIONS3 API1 APP1 BERRYSCRIPT1 BETA1 BOOK1 BROWSER4 C2 CALCULATOARE1 CARTE2 CERTIFICARE8 CERTIFICATION7 CERTIFIED2 CERTIFIED DEVELOPER1 CHALLENGE1 CHM1 CHROME1 CLASS1 CLI2 CLOSURES5 COD1 CODE QUALITY1 CODEIGNITER3 COFFEESCRIPT1 COLLECTIONS1 COMPOSER1 CSS3 CSV1 CURL1 DEBUG1 DESIGN PATTERNS4 DEVELOPER1 DEVELOPMENT TIME1 DIAGRAME1 DOCKER2 DOCKER-COMPOSE1 DOUGLAS CROCKFORD3 DRIVERE2 ELEPHPANT2 ENGINEER1 EXAMEN1 EXCEL1 FACEBOOK2 FEEDBACK1 FFI1 FINALLY1 FIREFOX9 FISIERE1 FPDF1 FRUMOS1 FTP1 GAMES1 GD2 GENERATOR1 GO1 GOOGLE5 GOOGLE ANALYTICS1 GOOGLE CHROME3 GOOGLE MAPS2 HACK6 HARDWARE1 HC-911 HEADER1 HEIGHT1 HOMEASSISTANT2 HTML2 HTML HELP WORKSHOP1 HTML51 HUG1 HUGO1 IDE1 IMAGINE1 INFORMATION_SCHEMA1 INI1 INTERNET4 INTERNET EXPLORER8 IPV41 IPV61 ISP1 ITERATOR2 JAVA1 JAVASCRIPT20 JQUERY1 LAMBDA2 LAPTOP2 LINUX1 LIVELY1 LUNI1 MAGENTO7 MAGENTO22 MAP1 MAPS1 MICROSOFT1 MINESWEEPER1 MOTIVATION1 MSN MAPS1 MYSQL13 MYSQL WORKBENCH1 NGINX1 NODE.JS2 NOFALLOW1 NOSQL1 OBSERVER3 OBSERVER PATTERN1 OOP1 OPERA1 OPTIMIZATION1 ORACLE2 PAGESPEED1 PAIR1 PARSE_INI_FILE1 PASCAL1 PEAR1 PECL1 PERSON VUE2 PHAR1 PHONEGAP2 PHP52 PHP ELEPHANT2 PHP FOR ANDROID1 PHP-GTK1 PHP42 PHP510 PHP5.314 PHP5.46 PHP5.53 PHP5.61 PHP612 PHP7.41 POO1 PR1 PROGRAMMING1 PROIECTE1 RETEA1 REVIEW1 ROCK STAR1 ROMANIAN STEMMER2 RSS1 SAFARY1 SCALAR TYPE HINTING1 SCHEME1 SEO1 SET1 SHOPPING CART PRICE RULE1 SIMPLEXML1 SINGLETON1 SOAP2 SPL2 SQLITE1 SSH1 STACK TRACE1 STDERR1 STDIN1 STDOUT1 STOCATE1 STUDY GUIDE1 SUN2 SYMFONY2 TABLE1 TASMOTA1 TEST TO SPEECH1 TITANIUM2 TRAITS1 TTS1 UBUNTU1 UNICODE3 UTF-82 VECTOR1 VISTA2 WEB2 WEBKIT1 WINBINDER1 WINDOWS2 WORDPRESS1 WSL21 WYSIWYG1 XP3 YAHOO3 YAHOO MAPS2 YAHOO OPEN HACK1 YSLOW1 YUI1 ZCE8 ZCE5.31 ZEND8 ZEND FRAMEWORK4
[A~Z][0~9]

Copyright © 2008 - 2024 CLAUDIU PERȘOIU'S BLOG. Toate drepturile rezervate