Case: oma talo — Osa 19

Written by

in

Käsiohjaus: tabletti, joka ei saa ohjata mitään

EnergyHubia on tähän asti käytetty insinöörin työkaluilla: SSH:lla, mosquitto_pub-komennoilla ja Home Assistantin kehittäjätilalla. Se toimii, mutta sillä on hintansa. Kun safety trip jäi kesäkuussa päälle, kuittaus onnistui lopulta ilman SSH:takin — mutta vain MQTT-komennoilla ja HA:n Developer Tools -näkymässä, eli ei millään, mitä kehtaisi kutsua käyttöliittymäksi. Siitä lähtien tiekartalla on lukenut: järjestelmä tarvitsee oikean käyttöpaneelin.

Nyt se on seinällä. Android-tabletti, Fully Kiosk Browser lukittuna yhteen osoitteeseen, tumma teema ja kuusi osiota. Paneelista voi käynnistää sähköauton latauksen puolesta tunnista neljään tuntiin, estää latauksen, tilata auton 80 tai 100 prosenttiin aamukuudeksi, käynnistää käyttövesiboostin ja asettaa talon sisälämpötilatavoitteen.

Tämän osan tärkein asia ei kuitenkaan ole se, mitä paneelilla voi tehdä, vaan se, mitä sillä ei voi: tabletti ei saa ohjata suoraan yhtään laitetta. Ei relettä, ei lämpöpumppua, ei yhtään Home Assistantin palvelukutsua. Jokainen napinpainallus on pelkkä pyyntö. Tämä osa kertoo siitä turvallisesta pyyntöarkkitehtuurista — ei niinkään käyttöliittymän ulkoasusta, joka sai sittemmin oman kunnollisen käsittelynsä ja josta kirjoitan erikseen seuraavassa osassa.

Tärkein päätös tehtiin ennen ensimmäistäkään nappia

Ennen kuin riviäkään käyttöliittymäkoodia kirjoitettiin, piti ratkaista, mikä seinäpaneeli järjestelmän silmissä on: suora ohjain, joka kytkee releitä? Home Assistantin dashboard, joka kääntelee helpereitä? MQTT-asiakas, joka julkaisee komentoja? Vai pelkkä pyyntöjen lähettäjä?

Valinta oli viimeinen, ja se oli koko paketin tärkein arkkitehtuuripäätös. Käsiohjaus rakennettiin erottamalla viisi asiaa toisistaan: käyttäjän pyyntö, järjestelmän hyväksymä aikomus eli intent, resolverin tekemä päätös, fyysinen toteutus ja toteutuksen kuittaus. Käsiohjaus ei tarkoita turvakerrosten ohittamista — se tarkoittaa yhtä uutta sisääntuloa samaan päätösketjuun.

Fully Kiosk
    ↓
Käyttäjän pyyntö
    ↓
Validointi ja käyttöoikeudet
    ↓
Manual intent / canonical state
    ↓
Priority Resolver
    ↓
Safety, capability, admission ja final_gate
    ↓
Home Assistant / Shelly / Thermia
    ↓
Readback, observability ja käyttöliittymän status

Prioriteettijärjestys on tarkkaan mietitty: käsiohjaus voittaa spot-hinnan ja normaalin optimoinnin — nappi on kovempi argumentti kuin halpa vartti. Mutta se ei voita safety tripiä, vanhentunutta vaihedataa, sulake- ja kuormarajoja, capability-estoja, final_gatea eikä readback-valvontaa.

Teollisuusautomaatiossa tämä on itsestäänselvyys: HMI-paneeli ei ohjaa lähtöjä suoraan, vaan kirjoittaa asetusarvoja ja pyyntöjä logiikalle, joka omistaa lukitukset. Kotiautomaatiossa sama raja on aivan yhtä arvokas — ja sivutuotteena syntyi jotain isompaa. Seinäpaneeli on järjestelmän näkökulmasta epäluotettava ehdottaja, ja täsmälleen sama rajapinta voidaan myöhemmin antaa vaikka paikalliselle kielimallille tai ilmoitusjärjestelmälle ilman, että kenellekään jaetaan laiteoikeuksia.

Käyttöliittymä ei asu Home Assistantissa

Fully Kiosk on vain lukittu selain. Varsinainen käyttöliittymä on Node-REDin tarjoama HTML-, CSS- ja JavaScript-sivu:

GET /energyhub          → Render Operator UI    → HTML
GET /energyhub/status   → Operator status JSON  → JSON

Reitti lyheni olennaisesti. Vaihtoehto olisi ollut Fully Kiosk → HA:n Lovelace-dashboard → helperit → Node-RED → EnergyHub; nykyinen kulkee Fully Kiosk → Node-RED → EnergyHub. Home Assistant on yhä mukana laiteintegraatioissa ja joidenkin canonical state -arvojen omistajana, mutta paneelin liikenne ei kierrä sen kautta.

Status-JSON on valmiiksi koottu näkymä: resolverin viimeisin päätös, EV:n päätös ja fyysinen tila, manual intentit, vaihevalvonnan tila, telemetrian tuoreus, sisälämpötilatavoite ja Thermian huonelämpömittaus, eston syy ja yleinen operator_ok. Selaimen ei tarvitse yhdistellä MQTT-topiceja tai päätellä, mikä tila on tärkein — Node-RED tekee renderöintiin tarkoitetun tilamallin valmiiksi.

Tuoreus otetaan vakavasti: jokaiselle datalähteelle on oma ikä- ja stale-kynnys, vastaukset lähtevät Cache-Control: no-store -otsakkeella, ja yläpalkissa juokseva ”päätösikä” kertoo jatkuvasti, kuinka vanha resolverin viimeisin päätös on. Vanhentunut mutta uskottavan näköinen data on käyttöpaneelissa vaarallisempaa kuin puuttuva data.

KUVA 1: Päävalikko: tilarivi, kuusi osiota ja ”Nyt käynnissä” -yhteenveto. Alapalkki näyttää aina, onko käsiohjauksia aktiivisena.

Miksi selain puhuu HTTP:tä eikä MQTT:tä

Alkuperäisessä suunnitelmassa tabletti olisi ollut suora MQTT-over-WebSocket-asiakas, ja se polku rakennettiinkin valmiiksi. Nykyinen Operator UI käyttää silti pääasiassa HTTP:tä: selain → Node-REDin HTTP-endpoint → paikallinen MQTT.

Syyt ovat käytännöllisiä. MQTT-tunnuksia ei tarvitse upottaa selaimen JavaScriptiin, Node-RED tarkistaa parametrit ennen julkaisua, käyttöliittymä ei tarvitse omaa MQTT-tilakonetta, statusdata kootaan palvelimella, ja CORS-, reconnect- ja retained-viestien käsittely jää palvelinpuolelle. WebSocket-rakenne jäi turvattuna varalle tulevaa käyttöä varten.

MQTT lukkojen taakse

Samassa yhteydessä koko brokerin ovet käytiin läpi. Mosquitton normaali portti 1883 sidottiin pelkkään loopbackiin — lähiverkon tai Tailscalen asiakas ei voi enää liittyä anonyymisti ja julkaista vaikkapa command-, lock- tai safety-topiceihin. Node-RED, Home Assistant ja paikalliset skriptit käyttävät edelleen osoitetta 127.0.0.1:1883.

Seinäpaneelia varten on erillinen WebSocket-listener portissa 9001: ei anonyymejä yhteyksiä, oma käyttäjä ja salasana, 64 KiB:n pakettiraja ja tiukka ACL:

Saa kirjoittaa:  energyhub/manual/request
                 energyhub/ui/wall_panel/online

Saa lukea:       energyhub/ui/wall_panel/status
                 energyhub/manual/reject

Kaikki muu on kiellettyä: intent-, command-, final_gate-, safety- ja capability-topicit sekä laitteiden suorat ohjaukset. Vaikka joku saisi tabletin MQTT-tunnukset käsiinsä, niillä voi lähettää vain pyyntöjä — samoja, jotka validoidaan joka tapauksessa.

Pyynnön anatomia

Kaikki kolme domainia — sähköauto, käyttövesi ja sisälämpötila — kulkevat saman sisääntulon kautta: energyhub/manual/request, schemalla eh.manual.v1. Näin EV-pyyntö näyttää:

json

{
  "schema": "eh.manual.v1",
  "ts": "2026-07-19T11:34:15.577Z",
  "request_id": "operator-ui-ev-charge-block-...",
  "source": "operator_http_ui",
  "actor": "wall_tablet",
  "domain": "ev",
  "intent": "charge_block",
  "requested_duration_s": 3600,
  "payload": { "ui": "energyhub_operator", "endpoint": "charge_block" }
}

B3-validator tarkistaa scheman, kenttien kelvollisuuden, aikaleiman, sallitut domain–intent-yhdistelmät, payloadin rakenteen ja arvoalueet. Kaksi tarkistusta on periaatteellisesti muita tärkeämpiä: pyyntö ei saa sisältää asiakkaan itse määräämää expires_at– tai ttl_s-arvoa, eikä sillä voi kirjoittaa command- tai final_gate-tasolle.

Toisin sanoen tabletin kello ei päätä mitään. Selain saa pyytää kestoa tai semanttista deadlinea, mutta lopullisen päättymisajan laskee Manual Request Manager palvelimen kellolla. Väärässä ajassa oleva tai manipuloitu asiakas ei voi jättää käsiohjausta voimaan liian pitkäksi aikaa.

Hylätyt pyynnöt julkaistaan omaan energyhub/manual/reject-topiciin request_id:n ja syyn kera — unsupported_intent, invalid_ts, target_c_out_of_range ja niin edelleen. Hyväksytyt reititetään domainin mukaan: EV ja käyttövesi omille Manual Request Managereilleen, sisälämpötila omalle sovittimelleen.

Sähköauto: lataa nyt, estä, lataa aamuksi

EV:n Manual Request Manager omistaa käsiohjauksen koko elinkaaren: deduplikoinnin, rate limitin, vanhan intentin korvaamisen, expiryn ja tyhjennyksen. Hyväksytty käsiohjaus julkaistaan retained-intenttinä topiciin energyhub/manual/intent/ev tiloilla active, superseded, expired ja cleared. Retained-tila tarkoittaa, että Node-REDin restart ei hukkaa aktiivista käsiohjausta — ja palvelimen laskema expiry tarkoittaa, ettei vanhentunut intent myöskään herää restartissa henkiin.

Toteutetut intentit ja niiden rajat:

  • charge_now — lataus päälle heti, enintään 4 h (p_manual_ev_charge_now_max_s = 14400)
  • charge_block — lataus estoon, enintään 24 h
  • charge_by — tavoite-SOC määräaikaan mennessä; paneelin napit tarjoavat 80 % ja 100 % seuraavaan kello kuuteen. Deadline lasketaan backendissä Europe/Helsinki-ajassa kesä- ja talviaika huomioiden.
  • clear — palauttaa päätöksenteon normaalille optimoinnille.

Resolverissa manuaali-intent ei kirjoita relettä, vaan muuttaa ehdokaspäätöstä, joka kulkee normaalin ketjun läpi:

safety / vaihedata / capability
    ↓
manual EV intent
    ↓
charge_by-suunnitelma tai charge_now / charge_block
    ↓
normaali hinta- ja PV-ohjaus
    ↓
EV start admission
    ↓
final_gate

Observabilityssä näkyy koko ajan rinnakkain, mitä normaali logiikka olisi tehnyt ja mitä käsiohjaus muutti:

json

{
  "active": true,
  "intent": "charge_block",
  "manual_decision": false,
  "normal_decision": true,
  "would_change_decision": true,
  "blocked_by": null,
  "live_applied": true,
  "live_decision": false
}

Yhdestä viestistä selviää, mitä käyttäjä pyysi, muuttiko pyyntö päätöksen, toteutettiinko se — ja jos ei, mikä turvakerros esti.

Käyttöönotto tehtiin samalla kaavalla kuin final gate aikanaan: ensin State Collector alkoi lukea intentin ja resolveri laski shadow-päätöksen, tuloksia verrattiin normaaliin päätökseen, ja vasta sitten charge_now ja charge_block kytkettiin live-polkuun. Sen jälkeen vanha s.evManualOverride-reitti poistettiin kokonaan — kaksi rinnakkaista käsiohjausjärjestelmää olisi ollut pahempi kuin ei yhtään. Neljän tunnin charge_now-regressiotestissä intent vanheni ajallaan ja päätös palasi optimoinnille itsestään.

charge_by on näistä kolmesta se, jolla on eniten liikkuvia osia: se ei ole kytkin vaan määräaikaan sidottu suunnitelma, joka lataa halvimmilla varteilla mutta ehtii silti tavoite-SOC:hen deadlineen mennessä. Sen shadow-vertailu ja ensimmäiset yön yli -ajot 80 ja 100 prosentin tavoitteilla on jo ajettu läpi — mukaan lukien 100 prosentin erikoistapaus, jossa auton oma latauselektroniikka hidastaa loppua kohden ja viimeiset prosentit on osattava varata suunnitelmaan. Suunnittelulogiikka ansaitsee kuitenkin oman osansa, joten tässä se pysyy yhtenä käsiohjausintenttinä muiden joukossa; latausennusteesta ja varttihintojen lookaheadista kirjoitan tarkemmin myöhemmin.

Painikkeissakin on omat varmistuksensa. HTTP-backendissä on enable-lippu, jolla napit asennettiin ensin kuivaharjoittelutilaan: backend palautti, mitä se olisi julkaissut, mutta ei vielä lähettänyt MQTT-pyyntöä. Ja itse paneelissa EV-backend on oletuksena lukittu — vahinkokosketus näytöltä ei lähde mihinkään ennen kuin lukitus avataan, ja aktiivisesta backendista varoitetaan erikseen.

KUVA 2: Auto/EV-näkymä: SOC, latausprofiili, admission-tila ja käsikäytön napit. Alareunassa oletuslukituksen ilmoitus.

Käyttövesi: komento ei ole valmistuminen

Käyttövedellä on oma MRM ja oma retained intent (energyhub/manual/intent/dhw), päätoimintona boost_now. Rakenne on sama kuin EV:llä, mutta yksi ero tekee siitä kiinnostavamman: käyttövesiboost on prosessi, ei kytkin. Pelkkä komennon lähettäminen ei tarkoita, että vesi lämpeni.

Siksi resolveri ja observability seuraavat koko ajon ajan Thermian running priorityä, Shellyn tehomittausta, kompressorin toimintaa, veden lämpötilaa ja boostin ikää — ja intent päätyy terminal-tilaan, joka on completed, expired, cancelled tai failed sen mukaan, mitä oikeasti tapahtui.

Tässä lunastuu myös osan 18 lopussa annettu lupaus, joskin eri muodossa kuin se annettiin. Ensimmäinen oikea DHW-boost koko uuden arkkitehtuurin läpi on nyt ajettu: pyyntö → gate → MRM → resolveri → final_gate → Thermia → readback → terminal state completed, syynä manual_dhw_completed_heating_confirmed. Se ei tosin toteutunut niin, ettei kukaan koskenut mihinkään — vaan täsmälleen päinvastoin. Joku painoi nappia. Järjestelmä hoiti loput ja osasi vielä todistaa mittauksista, että vesi todella lämpeni.

KUVA 3: Käyttövesinäkymä kesken Thermian oman käyttövesiajon: priority 3, kompressori 45 %, mitattu teho 2.2 kW. Boost-napit alhaalla.

Sisälämpötila: pysyvä asetus, ei lease

Kolmas domain rikkoi kaavan. Sisälämpötilatavoite ei ole määräaikainen käsiohjaus samalla tavalla kuin charge_now — se on pysyvä asetus, jolla ei ole expiryä. Siksi sille ei rakennettu retained intent -omistajaa, vaan canonical state -ketju:

manual request
→ Indoor Target Command Adapter
→ energyhub/command/indoor_target/set
→ Home Assistant (input_number = canonical owner)
→ energyhub/state/indoor_target

Olennainen yksityiskohta: HTTP-vastaus 202 Accepted ei todista mitään. Node-RED ei pidä command-viestiä onnistuneena kuittauksena, vaan odottaa Home Assistantilta vahvistettua state-viestiä — ja vasta se päivittää käyttöliittymän. Paneeli ei koskaan näytä tavoitetta hyväksyttynä vain siksi, että pyyntö lähti.

Tavoitealue on 18–23 °C yhden asteen askelin, kokonaislukuina — samalla logiikalla kuin Thermian omassa käyttöliittymässä. Resolveri lukee tavoitteen rinnalla Thermian huonelämpömittausta, jonka tuoreusraja on 180 sekuntia: jos mittaus on stale, live-säätöä ei saa tehdä.

Ja juuri live-säätö on se, mitä ei vielä tehdä. Koko ketju käyttöliittymästä canonical stateen ja resolverin shadow-laskentaan toimii, mutta fyysinen vaikutus Thermian comfort wheeliin on tarkoituksella pidetty shadow-tilassa. Syy ei ole keskeneräisyys vaan fysiikka: sisälämpötila on hidas prosessi, jota ei voi validoida muutaman minuutin testillä heinäkuussa. Ennen liveä pitää testata ainakin lämmityskausi, kesä-idle, kylmäpakko, stale mittaus, molempien järjestelmien restartit, comfort wheelin readback ja liian tiheiden säätöjen esto.

KUVA 4: Lämmitysnäkymä: tavoite tallennettu, ohjaustila shadow. Comfort wheeliä ei vielä kosketa.

Kadonnut minuutti

Kun kaikki edellä kuvattu toimi, jäljelle jäi yksi ärsyttävä asia: napit tuntuivat hitailta. Pyyntö validoitiin ja intent syntyi millisekunneissa, mutta käyttöliittymän lopullinen vahvistus odotti resolveria — ja flow-kartoitus varmisti, että Priority Resolverin ainoa käynnistäjä oli 60 sekunnin inject. Vahvistus tuli satunnaisesti 0–60 sekunnin viiveellä. Käyttöliittymässä se tuntuu ikuisuudelta.

Ilmeinen ratkaisu olisi ollut nopeuttaa sykli jatkuvaksi parin sekunnin silmukaksi. Sitä ei tehty — jatkuva pyöritys olisi ollut pelkkää kuormaa ilman tarvetta. Sen sijaan minuuttisyklin rinnalle lisättiin tapahtumapohjainen ajo:

EV/DHW intent muuttui  → suodatus → 250 ms settle → resolveri
state/indoor_target    →            250 ms settle → resolveri

Sisälämpötilan triggeri lähtee tarkoituksella vasta canonical state -viestistä — ei pyynnöstä eikä command-viestistä — jotta nopeakin vahvistus perustuu vahvistettuun tilaan. Ja 250 millisekunnin settle on olemassa siksi, että sama MQTT-viesti haarautuu yhtä aikaa State Collectorille ja fast triggerille: pieni viive varmistaa, että resolveri lukee uuden snapshotin eikä edellistä.

Toteutus paljasti kaksi kaksoisajoa, ja tässä kohtaa olisi ollut helppo lisätä yleinen debounce ja unohtaa koko asia. Sen sijaan syyt selvitettiin. Ensimmäinen: fast trigger oli kytketty wildcard-tilaukseen energyhub/#, ja samassa broker-yhteydessä oli jo tarkka tilaus samalle intent-topicille — sama viesti toimitettiin kahdesti. Korjaus oli siirtää triggerit tarkkoihin topiceihin ja antaa käsiohjausketjulle kokonaan oma MQTT client ID (nodered_c1_manual). Toinen: kun uusi intent korvasi vanhan, MRM julkaisi tarkoituksella sekä superseded- että active-tapahtuman — molemmat oikeita, mutta resolveria ei tarvitse ajaa audit-tapahtumasta, kun uusi active tulee heti perässä. Nyt superseded suodatetaan; active, expired, cleared ja tombstone ajavat resolverin.

Lopputulos baseline-commitissa 05d5690 (”Add event-driven fast ACK for manual controls”):

manual/request       14:34:15.581
active intent        14:34:15.626
resolver decision    14:34:15.930

Pyynnöstä päätökseen 349 millisekuntia. Sisälämpötilalla, canonical state -kierroksen kautta, 314 millisekuntia. Minuuttisykli jäi paikalleen expiryjen varmistajaksi, normaalin optimoinnin ajajaksi ja turvaverkoksi siltä varalta, että tapahtumaviesti jäisi joskus käsittelemättä.

Opit

Käyttöliittymäprojektin tärkein päätös ei koskenut käyttöliittymää. Värit, fontit ja napit ovat helppoja. Vaikea ja ratkaiseva kysymys oli luottamusraja: mitä tabletti saa olla. ”Epäluotettava ehdottaja” kuulostaa vähättelyltä, mutta on oikeasti vapauttava rooli — sen ansiosta samaa rajapintaa voi jatkossa tarjota mille tahansa ehdottajalle ilman uusia laiteoikeuksia.

Asiakkaan kelloon ei luoteta koskaan. Selain saa pyytää kestoa, mutta expiry lasketaan aina palvelimella. Tämä yksi sääntö poistaa kokonaisen luokan vikoja: väärän kellonajan, aikavyöhykevirheen, kesäajan siirtymän ja tahallisen manipuloinnin.

Retained intent ja palvelimen expiry yhdessä antavat restartin kestävän mutta zombittoman käsiohjauksen. Aktiivinen käsiohjaus selviää Node-REDin restartista, mutta vanhentunut ei herää henkiin. Molemmat puolet ovat yhtä tärkeitä — tämän sarjan lukijat muistavat, mitä retained-viestien zombit ovat ennen saaneet aikaan.

Kaksi rinnakkaista käsiohjausjärjestelmää on pahempi kuin ei yhtään. Uusi kerros ajettiin ensin shadow’na, sitten liveen — ja legacy-polku poistettiin heti perään, kokonaan.

Kahdentumat selitetään, ei vaimenneta. Yleinen debounce olisi peittänyt molemmat kaksoisajot muttei selittänyt kumpaakaan. Nyt tiedetään, että syitä oli kaksi ja ne olivat eri mekanismeja — ja kumpikin on korjattu juurisyystään.

Komento ei ole toteuma. Käyttövesiboostin onnistuminen todetaan mittauksista, ei lähetetystä komennosta. Sama periaate, joka on kulkenut tämän sarjan läpi releistä lähtien, päti myös käyttöliittymään: paneeli näyttää vahvistettua tilaa, ei toiveita.

Mitä vielä puuttuu

Rehellisyyden nimissä lista on edelleen pitkä. Sisälämpötilatavoitteen fyysinen live-aktivointi odottaa lämmityskautta. Home/Away paluuaikoineen on validatorissa valmisteltu mutta toteuttamatta — eikä sitä kannata tehdä ennen kuin on määritelty tarkasti, mitä Away oikeasti muuttaa, sillä pelkkä boolean ei riitä, kun vaikutukset ulottuvat autoon, käyttöveteen ja lämmitykseen. charge_by tarvitsee kunnollisen regressio- ja soak-testisarjan: puuttuva tai stale SOC, auto ei kotona, deadline lähellä, huomisen hinnat puuttuvat, replan kesken ajon, kellonsiirtoyö, restartit kesken suunnitelman. Request_id-pohjainen kuittausketju (received → validated → active → applied → blocked → completed) viimeistelisi käyttöliittymän kertomaan täsmälleen, missä vaiheessa pyyntö kulloinkin menee. Direct WebSocket -polun lopullinen rooli — heartbeat, push-status vai poisto hyökkäyspinnan pienentämiseksi — on myös päättämättä, samoin käsiohjaustapahtumien kytkentä tulevaan incident- ja notifier-kerrokseen. Ja jos Node-REDin portti 1880 joskus avataan luotetun verkon ulkopuolelle, HTTP-rajapinta tarvitsee oman suojauskerroksensa: reverse proxyn, HTTPS:n, autentikoinnin ja rate limitin. Nykyisellään se pysyy sisäverkossa.

Mutta nykytila kelpaa: sähköauton ja käyttöveden käsiohjaus sekä sisälämpötilatavoitteen asetus toimivat päästä päähän, päätös syntyy noin kolmessasadassa millisekunnissa — ja tabletilla ei edelleenkään ole oikeuksia yhteenkään releeseen.


Ja juuri tähän pyyntöarkkitehtuuriin nojaa se, mikä tästä seuraavaksi kasvoi. Tekninen paneeli toimi, mutta se oli yhä insinöörin näkymä: teknisiä tiloja, syykoodeja ja rele-termejä ruudulla, joka roikkuu keittiön seinällä koko perheen käytettävänä. Seuraava askel oli erottaa esityskerros ohjauskerroksesta kokonaan — antaa samalle turvalliselle pyyntörajapinnalle käyttöliittymä, joka kääntää järjestelmän tilan ihmisen kielelle sen sijaan, että näyttäisi sen sellaisenaan. Siitä, ja siitä miksi teknisesti toimiva paneeli ei vielä ollut hyvä käyttöliittymä, kertoo seuraava osa.

Piditkö artikkelista?

Seuraa blogia myös Blogit.fi:ssä, niin löydät uudet kirjoitukset helposti.

Seuraa blogia Blogit.fi:ssä