niccolofactory.com
Portfolio · projekty po godzinach Portfolio · side projects

Mikołaj Ważydrąg

Java backend engineer, 8 lat w zawodzie. Zawodowo systemy rozproszone w e-commerce, po godzinach IoT i własna infrastruktura.

Java backend engineer, 8 years in the field. Professionally, distributed e-commerce systems; after hours, IoT and self-hosted infrastructure.

Przez prawie pięć lat w Ocado Technology rozwijałem backend platformy zakupów spożywczych online dla międzynarodowych sieci handlowych: wycenę i promocje oraz zamówienia cykliczne. Brałem też udział w migracji systemu ze Scali do Javy. Wcześniej przez trzy lata pisałem aplikacje ubezpieczeniowe od bazy danych po frontend.

For almost five years at Ocado Technology I worked on the backend of an online grocery platform used by international retailers, mainly pricing and promotions and recurring orders. I also took part in a Scala-to-Java migration. Before that I spent three years building insurance applications end to end, from the database to the frontend.

Poniżej dwa projekty, które zbudowałem sam, od specyfikacji po działające wdrożenie. Lubię odpowiadać za cały system, więc tutaj robię wszystko: od mikrokontrolera, przez backend i frontend, po serwery i kopie zapasowe.

Below are two projects I built on my own, from the spec to a running deployment. I like owning a whole system, so here I do all of it: from the microcontroller, through the backend and frontend, to the servers and backups.

Szukam roli Java/Kotlin backend. Chętnie też takiej, w której backend spotyka się ze sprzętem i IoT.

I'm looking for a Java/Kotlin backend role, and I'd especially enjoy one where the backend meets hardware and IoT.

Firmware · backend · frontend · CI/CD

Szklarnia hydroponiczna

Smart hydroponic greenhouse

Arduino zbiera pomiary (pH, EC, temperatura, wilgotność), backend w Spring Boot steruje pompkami przez MQTT, a panel w React działa z telefonu.

An Arduino collects readings (pH, EC, temperature, humidity), a Spring Boot backend drives the pumps over MQTT, and a React dashboard works from a phone.

Zobacz projekt ↓See the project ↓
Infra · self-hosting · AI

Prywatna chmura notatek z agentem AI

Private notes cloud with an AI agent

Dwa serwery ARM na darmowym Oracle Cloud: synchronizacja notatek bez publicznych portów, agent AI w kontenerze i kopie zapasowe w dwóch niezależnych miejscach.

Two ARM servers on Oracle Cloud's free tier: notes sync with no public ports, an AI agent in a container, and backups in two independent places.

Zobacz projekt ↓See the project ↓
Projekt 1 · full-stack IoT, od mikrokontrolera po wdrożenieProject 1 · full-stack IoT, from microcontroller to deployment

Szklarnia hydroponiczna

Smart hydroponic greenhouse

W uprawie hydroponicznej pH i stężenie pożywki (EC) trzeba pilnować codziennie, a pomyłka w dawce szkodzi roślinom. Buduję system, który mierzy te parametry, pokazuje je na wykresach i pozwala zdalnie dozować płyny czterema pompkami perystaltycznymi. Oprogramowanie działa na produkcji, a fizyczny prototyp instalacji jest w trakcie montażu.

Hydroponics needs daily checks of pH and nutrient concentration (EC), and a wrong dose harms the plants. I'm building a system that measures these values, charts them and lets me dose liquids remotely with four peristaltic pumps. The software runs in production; the physical prototype is still being assembled.

Java 25Spring Boot 4Spring SecurityJPA · LiquibasePostgreSQLMQTT (HiveMQ client, Mosquitto)Arduino GIGA (C++)React 19 · TypeScriptTestcontainersGitHub ActionsDocker ARM64Cloudflare Tunnel
Panel szklarni: kafelki z temperaturą powietrza i wody, wilgotnością, pH i EC, wykres temperatury z ostatnich 24 godzin, sterowanie pompą mieszającą, cztery przyciski dozowania i historia dawek nawozu A ze statusem Wykonano.
Prawdziwy panel na produkcji (zrzut z 5.10.2026). Czujniki pH, EC i temperatury wody nie są jeszcze zamontowane w fizycznym prototypie instalacji hydroponicznej, dlatego panel pokazuje „Brak odczytu” zamiast udawać zero. Z pompek dozujących zainstalowana jest na razie pompka nawozu A. Pozostałe trzy nie są jeszcze podłączone do zasilania, dlatego w historii dozowania są tylko dawki nawozu A.
The real production dashboard (captured on 5 Oct 2026, Polish UI). The pH, EC and water temperature probes haven't been mounted in the physical hydroponic prototype yet, so the dashboard shows “no reading” instead of faking a zero. Of the dosing pumps, only the Nutrient A pump is installed so far. The other three aren't connected to power yet, so the dosing history only shows Nutrient A doses.
Okno dozowania nawozu A: suwak czasu dawki ustawiony na 7 sekund w zakresie 1–10 s, ostatni odczyt pH i EC, stan pompy mieszającej oraz przyciski Anuluj i Wykonaj. Krok 1 · wybór dawkiStep 1 · choosing the dose
Nawóz A, 7 sekund, w dozwolonym zakresie 1–10 s. Okno pokazuje też ostatni odczyt pH i EC oraz stan pompy mieszającej, żeby decyzja zapadała przy pełnym obrazie.
Nutrient A, 7 seconds, within the allowed 1–10 s range. The dialog also shows the latest pH and EC readings and the mixing pump state, so the decision is made with the full picture.
Panel dozowania w trakcie dawki: kafelek Nawóz A pokazuje napis Dozowanie 3 s z 7 s i pasek postępu wypełniony mniej więcej do połowy, pozostałe kanały mają przyciski Dozuj. Krok 2 · dozowanie w tokuStep 2 · dosing in progress
Zamiast przycisku kafelek nawozu A pokazuje licznik sekund i pasek postępu. Po 7 sekundach urządzenie potwierdziło wykonanie przez MQTT, a wpis w historii zmienił się z „Oczekuje” na „Wykonano”.
Instead of a button, the Nutrient A tile shows a seconds counter and a progress bar. After 7 seconds the device confirmed the dose over MQTT, and the history entry changed from “pending” to “done”.
Arduino GIGA WiFi firmware C++ CzujnikiSensors pH · EC · temp. · RH PompyPumps 4 dozujące + mieszająca4 dosing + 1 mixing Mosquitto MQTT · TLS bez retainedno retained msgs reportQoS 0 cmd/ackQoS 1 Spring Boot backend architektura heksagonalnahexagonal architecture adapterin mqttwebscheduler application domaincommandsports adapterout persistencedevice PostgreSQLLiquibase React SPA (PWA) przeglądarka / telefonbrowser / phone Cloudflare Access + Tunnel bez otwartych portów HTTPno open HTTP ports GitHub Actions testy → obraz ARM64 → GHCRtests → ARM64 image → GHCR obraz tylko po zielonych testachimage only after green tests
Urządzenie i backend nie rozmawiają ze sobą bezpośrednio, tylko przez broker MQTT. Panel jest dostępny z internetu wyłącznie przez Cloudflare Tunnel z logowaniem Cloudflare Access.
The device and the backend never talk directly, only through the MQTT broker. The dashboard is reachable from the internet only through a Cloudflare Tunnel behind Cloudflare Access.

Kontrakt MQTT spisany przed kodem

MQTT contract written before the code

Pomiary idą z QoS 0, bo zgubiony odczyt to tylko luka na wykresie. Komendy i potwierdzenia mają QoS 1, a ich obsługa jest odporna na duplikaty. Żadna wiadomość nie jest retained, więc stara komenda nie uruchomi pompki po restarcie urządzenia.

Readings use QoS 0, because a lost reading is just a gap in a chart. Commands and acknowledgements use QoS 1 and are handled idempotently. No message is retained, so a stale command can't start a pump after the device reboots.

Bezpieczeństwo dawkowania w dwóch warstwach

Dosing safety in two layers

Backend przyjmuje dawkę 1–10 s i tylko jedną naraz. Firmware sprawdza ten sam limit jeszcze raz, więc nawet błąd w backendzie nie zaleje zbiornika. Przełącznik pompy jest zablokowany, dopóki urządzenie nie potwierdzi komendy.

The backend accepts a 1–10 s dose, one at a time. The firmware enforces the same hard limit again, so even a backend bug can't flood the tank. The pump toggle stays locked until the device confirms the command.

Uczciwe statusy komend

Honest command statuses

Jeśli komenda na pewno nie dotarła do brokera, API zwraca 503 i nie zostawia wpisu. Jeśli broker ją przyjął, a urządzenie milczy, wpis zostaje jako PENDING. Historia dozowania nigdy nie twierdzi czegoś, czego system nie wie.

If a command certainly never reached the broker, the API returns 503 and leaves no record. If the broker accepted it but the device stays silent, the record stays PENDING. The dosing history never claims more than the system knows.

Testy, które startują prawdziwą infrastrukturę

Tests that start real infrastructure

Dwa zestawy w Gradle: testy jednostkowe bez kontekstu Springa i testy integracyjne z całą aplikacją, PostgreSQL i Mosquitto w Testcontainers. CI buduje obraz ARM64 dopiero po zielonych testach, bo ten obraz steruje pompkami.

Two Gradle source sets: unit tests without a Spring context, and integration tests that run the whole app with PostgreSQL and Mosquitto in Testcontainers. CI builds the ARM64 image only after green tests, because that image drives real pumps.

88klas Java w kodzie produkcyjnymJava classes in production code
44klasy testowe: 17 jednostkowych, 27 integracyjnychtest classes: 17 unit, 27 integration
5tematów MQTT w kontrakcieMQTT topics in the contract
124commity, praca przez pull requestycommits, work through pull requests

Status: MVP działa na serwerze ARM64. Dalej w planie: czujnik poziomu wody z blokadą pomp, kalibracja pompek i reguły automatycznego dozowania.

Status: the MVP runs on an ARM64 server. Next up: a water level sensor that locks the pumps, pump calibration and automatic dosing rules.

Projekt 2 · infrastruktura + AIProject 2 · infrastructure + AI

Prywatna chmura notatek z agentem AI

Private notes cloud with an AI agent

Prowadzę notatki w Logseq na laptopie i telefonie. Chciałem, żeby były wszędzie aktualne bez usług w rodzaju Dropboxa czy iCloud i żeby agent AI mógł z nimi pracować. Postawiłem to na dwóch darmowych serwerach ARM w Oracle Cloud, z założeniem, że każdy element może paść.

I keep my notes in Logseq on a laptop and a phone. I wanted them up to date everywhere without a service like Dropbox or iCloud, and I wanted an AI agent to work with them. I built it on two free ARM servers in Oracle Cloud, assuming any single part can fail.

Oracle Cloud (ARM64)Docker ComposeTailscale (WireGuard)SyncthingHermes AgentOpenRouterresticOCI Object Storagesystemd timersBashUFW
LaptopLogseq · Syncthing + kopia poza chmurą+ off-cloud backup TelefonPhone Logseq · SyncthingTelegram Tailscale sieć prywatnaprivate network Oracle Cloud · Always Free Hub AARM64 · Docker Syncthing notes/2 vaults HermesAI agent widzi 1 z 2 vaultówsees 1 of 2 vaults strażnik (systemd)watchdog (systemd) kopia · resticbackup · restic Hub BARM64 · Docker druga pełna kopia notatek; przejmuje ruch, gdy Hub A padnie second full copy of the notes; takes over when Hub A is down Object Storage repozytorium szyfrowane na serwerzerepository encrypted on the server OpenRouterLLM API Telegram czat + alertychat + alerts laptop pobiera kopię i nic nie kasujelaptop pulls the backup, never deletes
Synchronizacja i agent nie potrzebują żadnego publicznego portu. Notatki płyną wyłącznie wewnątrz prywatnej sieci Tailscale, a agent i Telegram łączą się od środka na zewnątrz. Serwerami administruję przez SSH.
Neither sync nor the agent needs a public port. Notes travel only inside the private Tailscale network, and the agent and Telegram connect from the inside out. I administer the servers over SSH.

Synchronizacja bez publicznych portów

Sync with no public ports

Syncthing na serwerach słucha tylko na loopbacku i jest wystawiony do sieci Tailscale. Wykrywanie urządzeń i przekaźniki są wyłączone, więc adresy są stałe, a ruch nie przechodzi przez cudze serwery.

Syncthing on the servers listens on loopback only and is exposed to the Tailscale network. Device discovery and relays are off, so addresses are fixed and traffic never passes through third-party servers.

Agent AI z minimalnymi uprawnieniami

An AI agent with least privilege

Agent dostaje do kontenera tylko jeden z dwóch zbiorów notatek. Syncthing działa w osobnej sieci Dockera, więc agent nie może dodać obcego urządzenia ani udostępnić folderu. Strażnik i kopia działają na hoście, poza zasięgiem agenta.

The agent's container mounts only one of the two note vaults. Syncthing runs on a separate Docker network, so the agent can't add a foreign device or share a folder. The watchdog and backups run on the host, out of the agent's reach.

Kopia odporna także na przejęty serwer

Backups that survive a compromised server

Synchronizacja kopiuje też błędy, więc osobno działa restic: szyfrowanie na serwerze, magazyn obiektów, a laptop pobiera kopię i nigdy niczego nie kasuje. Drugi serwer nie ma kluczy do kopii. Odtworzenie przetestowałem: sumy SHA-256 wszystkich 1292 notatek się zgodziły.

Sync replicates mistakes too, so restic runs separately: encrypted on the server, stored in object storage, then pulled by the laptop, which never deletes anything. The second server holds no backup keys. I tested a restore: SHA-256 sums of all 1,292 notes matched.

Awarie wykrywa system, nie ja

Failures are detected by the system, not by me

Strażnik co 6 godzin sprawdza konflikty plików, łączność drugiego huba, ciszę telefonu i wiek ostatniej kopii, a problem zgłasza na Telegramie. Gdy wszystko działa, milczy. Laptop sam sprawdza, czy główny serwer odpowiada.

Every 6 hours a watchdog checks for file conflicts, the second hub's connectivity, a silent phone and the age of the last backup, and reports problems on Telegram. When all is fine, it stays quiet. The laptop separately checks that the main server responds.

Z dziennika utrzymania

From the operations log

Telefon przestał się synchronizować, choć aplikacja VPN pokazywała „Connected”. Z serwera: ping przez Tailscale odpowiadał, ale port Syncthinga na telefonie zwracał „Connection refused”. Sieć działała, a system Androida ubił aplikację synchronizacji. Po naprawie skróciłem próg alarmu o ciszy telefonu z 3 dni do 1 doby i opisałem objawy obu typów awarii.

The phone stopped syncing even though the VPN app said “Connected”. From the server, a Tailscale ping got a reply, but the phone's Syncthing port returned “Connection refused”. The network was fine; Android had killed the sync app. After the fix I cut the silent-phone alert threshold from 3 days to 1 and documented the symptoms of both failure types.

0 zł$0miesięcznie za serwery (Always Free)monthly server cost (Always Free)
4urządzenia i 2 huby; przejęcie ruchu przy awarii huba przetestowanedevices and 2 hubs; failover on a hub outage tested
2niezależne kopie: w Oracle Cloud i na laptopie poza niąindependent backups: in Oracle Cloud and on a laptop outside it
0publicznych portów dla synchronizacji i agentapublic ports for sync and the agent
Sposób pracyHow I work

Specyfikacja, małe kroki, review, zapisane decyzje

Spec, small steps, review, recorded decisions

Oba projekty prowadzę tak, jak pracuję w zespole, i każdy zaczynam od dokumentu. W projekcie szklarni hydroponicznej są to wymagania (PRD), plan API i kontrakt MQTT, a w projekcie prywatnej chmury notatek plan etapów i spisane decyzje z uzasadnieniem. Potem wdrażam w małych krokach, na osobnych gałęziach, z testami i code review przed scaleniem. Z Claude Code pracuję jak z drugim programistą: przyspiesza pisanie i przeglądy, a decyzje architektoniczne i odpowiedzialność za nie są moje.

I run both projects the way I work in a team, and each one starts with a document. For the hydroponic greenhouse project, that's the requirements (PRD), the API plan and the MQTT contract; for the private notes cloud project, a staged plan and recorded decisions with their reasoning. Then I deliver in small steps, on separate branches, with tests and a code review before merging. I use Claude Code as a pair programmer: it speeds up writing and reviews, while architecture decisions and the responsibility for them stay with me.

specyfikacjaspec→ plan w małych krokachplan in small steps→ gałąź + testybranch + tests→ code review→ CI i wdrożenieCI & deploy

Kod obu projektów jest w prywatnych repozytoriach. Chętnie pokażę go i omówię na rozmowie.

The code for both projects lives in private repositories. I'm happy to walk through it in an interview.