Dokumentacja stacji pomiarowej MFC
Stacja pomiarowa dla mikrobiologicznych ogniw paliwowych (Microbial Fuel Cell, MFC). Ten dokument opisuje sprzęt, parametry konfiguracji, znaczenie kart KPI, wykresów i kolumn tabeli. Strona odpowiada bieżącej wersji frontendu.
1. Przegląd systemu
Stacja Pomiarowa MFC to system telemetryczny do monitorowania mikrobiologicznych ogniw paliwowych — bioelektrochemicznych reaktorów, w których mikroorganizmy utleniające substrat organiczny przekazują elektrony na anodę, generując prąd elektryczny. Stacja mierzy napięcie każdego ogniwa, prąd przepływający przez obciążenie zewnętrzne, moc wyjściową i skumulowaną energię. System składa się z trzech warstw:
- ESP32 — odczytuje czujniki analogowe (napięcia ogniw przez ADS1115) i INA219 (prąd/moc), pakuje dane w JSON i wysyła HTTPS POST na backend co kilka sekund.
- Backend Node.js (Express + better-sqlite3, port 3002, za nginx-em) — odbiera dane, retransmituje do przeglądarki przez WebSocket, decyduje kiedy zapisać pomiar do bazy SQLite (interwał lub zmiana napięcia).
- Dashboard (Vite / vanilla JS + Chart.js) — wyświetla aktualne wartości w czasie rzeczywistym oraz historię z bazy.
monitor-mfc.roleplayingtech.com (HTTPS, certyfikat Let's Encrypt). Nginx serwuje statyki z /var/www/monitor-mfc.roleplayingtech.com/frontend/dist i proxy-uje /api oraz /ws na 127.0.0.1:3002.
2. Układ pomiarowy
Pięć komponentów elektronicznych plus samo ogniwo (lub para ogniw):
| Element | Funkcja | Adres / interfejs |
|---|---|---|
| MFC (Microbial Fuel Cell) | Mierzony obiekt — reaktor z anodą, katodą i biofilmem mikroorganizmów. Generuje napięcie typowo 0.3–0.8 V (OCV do ~1.1 V). Mierzymy napięcie na zaciskach ogniwa oraz prąd przez rezystor obciążenia. | Zaciski anoda / katoda → MCP6002 + INA219 |
| MCP6002 (op-amp, 2× rail-to-rail) | Bufor wejściowy ADC w konfiguracji wtórnika napięciowego (gain = 1). Dwie niezależne połowy — każda buforuje napięcie jednego ogniwa. Izoluje wysokoimpedancyjne ogniwo od wejścia ADC. | Lewa połowa (piny 1,2,3) → ADS1115 AIN0 · Prawa połowa (5,6,7) → AIN1 |
| ADS1115 | 16-bitowy ADC I²C, 4 kanały single-ended. Konfigurowalny PGA (wzmocnienie) i częstotliwość próbkowania. Mierzy buforowane napięcia ogniw. | I²C, adres 0x48, SDA=GPIO21, SCL=GPIO22 |
| INA219 | Czujnik prądu/napięcia/mocy. Mierzy spadek napięcia na wewnętrznym shuncie → prąd, oraz napięcie bus (strony load) → moc wyjściowa ogniwa pod obciążeniem. | I²C, adres 0x40 |
| ESP32 | Mikrokontroler. WiFi, synchronizacja NTP, składanie JSON-a, HTTPS do backendu. | Master I²C, klient HTTPS, klient NTP |
Po co MCP6002? Ogniwo paliwowe ma wysoką impedancję wewnętrzną (setki Ω – kΩ). Bezpośrednie podłączenie do wejścia ADC obciąża ogniwo jego własną impedancją wejściową (~10 MΩ w ADS1115 z dzielnikiem wewnętrznym) i zafałszowuje pomiar napięcia. Op-amp w konfiguracji wtórnika podaje na ADC identyczne napięcie, ale z niskoimpedancyjnego źródła — ogniwo widzi praktycznie obwód otwarty.
Po co osobny ADC zamiast ADC w ESP32? Wbudowany ADC w ESP32 jest 12-bitowy, mocno nieliniowy i hałaśliwy. Dla napięć rzędu 0.5 V rozdzielczość liczy się w pojedynczych mV — niewystarczająco dla MFC. ADS1115 jest 16-bit, kalibrowany fabrycznie, ze stabilnym referencyjnym napięciem wewnętrznym i PGA, dzięki któremu przy zakresie ±4.096 V daje rozdzielczość ~125 µV/LSB.
Po co INA219 skoro napięcie mierzy ADS? ADS mierzy napięcie OCV (otwartego obwodu) lub napięcie na ogniwie pod minimalnym obciążeniem. INA219 mierzy prąd przez zewnętrzny rezystor obciążenia (load) i napięcie na load — pozwala wyznaczyć moc wyjściową i budować krzywe polaryzacji / mocy.
3. Połączenia / pinout
| Linia | Skąd | Dokąd | Uwagi |
|---|---|---|---|
| Napięcie ogniwa 1 | Anoda/katoda MFC #1 | MCP6002 IN+ (lewa połowa, pin 3) | 0–1.2 V DC. Masa ogniwa na GND układu pomiarowego. |
| Bufor V1 | MCP6002 OUT (pin 1) | ADS1115 AIN0 (Ch0) | Buforowane napięcie ogniwa 1 — rejestrowane jako ads_ch0_voltage. |
| Napięcie ogniwa 2 | Anoda/katoda MFC #2 | MCP6002 IN+ (prawa połowa, pin 5) | Aktywne gdy mierzymy dwa ogniwa równolegle (kontrola + eksperyment, dwa powtórzenia, itp). |
| Bufor V2 | MCP6002 OUT (pin 7) | ADS1115 AIN1 (Ch1) | Buforowane napięcie ogniwa 2 — rejestrowane jako ads_ch1_voltage. Włączane przez tryb dwóch ogniw. |
| AIN2, AIN3 | — | — | Wolne kanały ADS. Można podpiąć dodatkowe pomiary: potencjał anody/katody vs. elektroda odniesienia (Ag/AgCl), temperatura przez termistor z dzielnikiem, pH przez elektrodę z interfejsem analogowym. |
| I²C | ESP32 GPIO21 (SDA), GPIO22 (SCL) | ADS1115 + INA219 | Wspólny bus, adresy unikalne (0x48, 0x40). |
| Shunt INA219 | V+ MFC | Rezystor obciążenia (load) → V− MFC | INA219 wpięty szeregowo z load. Spadek na shuncie wewnętrznym (0.1 Ω) → prąd; bus voltage → napięcie na load. |
| Zasilanie elektroniki | 5 V (ESP32) / 3.3 V (ADS, INA219, op-amp) | Sensory + ESP32 | Masa elektroniki łączona z masą ogniwa w jednym punkcie — unikaj pętli masy, bo 50 Hz brud rozwala pomiary µV. |
4. Przepływ danych
MFC (anoda/katoda) → MCP6002 → ADS1115 ┐
├──► ESP32 ──► HTTPS POST ──► Backend (Node.js)
MFC pod load ──── INA219 ───────────────┘ │
├──► SQLite (interwał / zmiana)
└──► WebSocket ──► Dashboard
Trzy niezależne częstotliwości:
- ESP send interval — jak często ESP32 wysyła JSON-a (domyślnie 2000 ms). Każda wysyłka trafia przez WebSocket do dashboardu i odświeża KPI / wykresy / konsolę.
- DB save interval — jak często backend trwale zapisuje pomiar do SQLite (domyślnie 5 min). Większość wysyłek z ESP nie jest zapisywana, są tylko pokazywane na żywo — sensowne dla MFC, bo procesy biologiczne zmieniają się w minutach/godzinach.
- Change-triggered save — jeżeli włączone, backend dodatkowo zapisuje pomiar, gdy napięcie któregoś ogniwa wykona skok > próg (np. podanie substratu, zmiana obciążenia). Patrz sekcja 11.
5. Karty KPI
Sześć kart na górze dashboardu, aktualizowane przy każdym nowym pomiarze z WebSocketa:
| Karta | Wartość główna | Pole pod spodem |
|---|---|---|
| Ogniwo 1 — Napięcie | ads.channel_0.voltage [V] | Opis źródła (ADS1115 · Ch0) lub custom etykieta z configu. |
| Ogniwo 2 — Napięcie | ads.channel_1.voltage [V] | Widoczne tylko gdy tryb dwóch ogniw włączony. |
| Prąd ogniwa | INA219 current_mA | Kalibracja INA219 + bus voltage. |
| Moc wyjściowa | INA219 power_mW = Vbus × I | Skumulowana energia w mWh (licznik od startu backendu). |
| WiFi / ESP32 | RSSI [dBm] | Heap / min Heap, temperatura chipu, Hall sensor. |
| ADS1115 | 4 napięcia kanałów (Ch0–Ch3) | PGA gain · data rate · rozdzielczość bitowa. |
6. Wykresy
| Wykres | Co pokazuje | Skala |
|---|---|---|
| Napięcie — Kanał A0 (Ogniwo 1) | Napięcie ogniwa 1 w czasie | V, oś wspólnego okna czasowego |
| Napięcie — Kanał A1 (Ogniwo 2) | Napięcie ogniwa 2 w czasie | V, oś wspólnego okna czasowego |
| Porównanie kanałów — A0 vs A1 | Obie krzywe napięcia na jednym wykresie, wspólna oś Y — do porównania ogniw | V, okno 1 / 5 / 15 / 60 min |
Wykresy są live — buforowane w pamięci przeglądarki, okno czasowe sterowane przyciskami w nagłówku wykresu porównania (1 / 5 / 15 / 60 min — wspólne dla wszystkich trzech wykresów). Po reloadzie strony bufor się czyści; pełna historia z bazy jest dostępna osobno w tabeli "Ostatnie odczyty".
7. Tabela "Ostatnie odczyty" — każda kolumna
Tabela pokazuje zapisane pomiary z bazy danych (nie z live-streamu). Sortowanie po dowolnej kolumnie (klik w nagłówek), paginacja po 20 wierszy. Wiersze z bursztynowym tłem i ⚡ to change-triggered saves.
| Kolumna | Pole DB | Znaczenie |
|---|---|---|
| Czas | timestamp | Czas zdarzenia z ESP32. Jeżeli ESP nie zsynchronizował NTP, jest to liczba milisekund od bootu — wtedy wartości w 1970 r. są oczekiwane. Server-side created_at to faktyczny moment zapisu w SQLite. |
| Ogniwo 1 | ads_ch0_voltage | Buforowane napięcie ogniwa 1 z MCP6002 (kanał A0 ADS1115). Główny sygnał. |
| Ogniwo 2 | ads_ch1_voltage | Buforowane napięcie ogniwa 2 (kanał A1). Aktywne gdy włączony tryb dwóch ogniw. |
| Ch2 / Ch3 | ads_ch2_voltage / ads_ch3_voltage | Wolne kanały ADS. Odczytywane zawsze — można wpinać dodatkowe sygnały (potencjał anody/katody vs. elektroda odniesienia, termistor, pH) bez zmian w kodzie. |
| Bus V | bus_voltage | Napięcie na stronie load INA219 — napięcie ogniwa pod obciążeniem. |
| Load V | load_voltage | Napięcie na samym rezystorze load = Bus + Shunt. |
| Shunt mV | shunt_voltage_mV | Spadek napięcia na wewnętrznym shuncie INA219 — z niego liczony jest prąd. |
| Prąd | current_mA | Prąd generowany przez ogniwo i płynący przez load. |
| Moc | power_mW | Chwilowa moc wyjściowa = Bus × I. |
| Energia | energy_mWh | Skumulowana energia od ostatniego startu backendu. Reset przy pm2 restart. |
| RSSI | wifi_rssi | Siła sygnału WiFi w dBm. Im bliżej 0, tym lepiej (-50 = świetnie, -90 = na granicy). |
| Heap | free_heap | Wolny heap ESP32. Monotoniczny spadek w czasie = możliwy memory leak. |
| Temp | chip_temp | Temperatura chipu ESP32 (orientacyjnie — nie temperatura ogniwa!). |
8. Statystyki sesji
Panel "Statystyki sesji" pokazuje min / max / avg dla napięć obu ogniw, prądu i mocy — liczone tylko z odczytów odebranych w bieżącej sesji przeglądarki (nie z całej historii w bazie). Reload strony → liczniki od zera. Statystyki liczone w locie z każdego przychodzącego pomiaru WebSocket-em.
9. Konsola live
Strumień diagnostyczny każdego odebranego pomiaru. Dwa typy linii:
- 📡 Live data — pomiar z ESP, jeszcze niezapisany do bazy.
- 💾 Saved — backend zapisał ten pomiar do SQLite (potwierdzenie z polem
id).
Kontrolki: Autoscroll, Pauza, Wyczyść.
10. Konfiguracja — wszystkie pola
Cała konfiguracja jest globalna (zapisywana w SQLite w tabeli config). Każda zmiana zatwierdzona przyciskiem "Zapisz konfigurację" rozsyłana jest WebSocket-em do wszystkich otwartych dashboardów. Lista pól:
| Pole UI | Klucz configu | Opis |
|---|---|---|
| Komentarz do pomiaru | measurement_comment | Wolny tekst. Jeśli niepusty, pojawia się jako baner u góry dashboardu. Do opisu bieżącej kampanii (np. podłoże, szczep, rezystor load, temperatura inkubatora). |
| Tryb pomiaru napięcia | dual_voltage_mode | false = jedno ogniwo (Ch0). true = dwa ogniwa (Ch0 + Ch1). Wpływa tylko na UI — firmware zawsze odczytuje wszystkie 4 kanały ADS. |
| Etykieta ogniwa 1 / 2 | channel_0_label / channel_1_label | Nazwa/opis ogniwa. Widoczna na karcie KPI, w tytule wykresu i legendzie. |
| Komentarz Ch0 / Ch1 | channel_0_comment / channel_1_comment | Notatka opisowa dla kanału — tylko zapisywana, nie renderowana w głównych widokach. |
| Interwał zapisu do bazy | save_interval_minutes | Co ile minut backend trwale zapisuje pomiar (regularny zapis, change_triggered=0). Zakres 0.5–1440. |
| Interwał wysyłania ESP | esp_send_interval_ms | Co ile milisekund ESP wysyła pomiar. Backend zwraca tę wartość w odpowiedzi POST, ESP powinien ją honorować. Zakres 500–60000. |
| Kalibracja INA219 | ina219_calibration | Tryb pracy INA219: 32V_2A (największy zakres), 32V_1A (standard), 16V_400mA (najwyższa rozdzielczość). Dla typowych MFC (prąd rzędu mA) warto rozważyć 16V_400mA. Wymaga ponownego wgrania firmware. |
| PGA ADS1115 | ads1115_gain | Wzmocnienie programowalne ADS1115. Dla MFC (0–1.2 V) dobre jest GAIN_TWO (±2.048 V) lub GAIN_FOUR (±1.024 V). Wymaga reflashu firmware. |
| Zapis przy nagłej zmianie | change_detection_enabled | true/false. Dodatkowe zapisy poza regularnym interwałem gdy napięcie któregoś ogniwa wykona skok > próg. |
| Progi zmiany | change_threshold_ch0 / _ch1 | Minimalna zmiana napięcia (V) względem ostatnio zapisanego pomiaru kwalifikująca do dodatkowego zapisu. Uwaga: progi < 0.005 V są poniżej szumu — spam w bazie. |
| Widoczność kolumn | table_visible_columns | CSV widocznych kolumn tabeli. Pusty = wszystkie. Synchronizowany globalnie przez config. |
11. Zapis przy nagłej zmianie
Standardowo backend zapisuje pomiar raz na save_interval_minutes (np. co 5 min) — większość wysyłek leci tylko przez WebSocket i znika. Ten mechanizm dodatkowo zapisuje pomiar zaraz poza regularnym oknem, jeżeli któryś z sygnałów napięcia wykonał skok > próg względem ostatniego zapisu:
- Napięcie ogniwa 1 [V] — próg
change_threshold_ch0 - Napięcie ogniwa 2 [V] — próg
change_threshold_ch1
Takie wpisy mają w bazie change_triggered=1 i w tabeli pojawiają się z bursztynowym tłem + ikoną ⚡. Regularne zapisy mają change_triggered=0.
Strojenie progów: dla ADS1115 z PGA GAIN_TWO (±2.048 V) szum jest rzędu 0.5–2 mV. Próg 0.05 V łapie wyraźne skoki. Próg 0.01 V łapie też szum — sensowne tylko przy stabilnym układzie. Dla MFC z naturalnymi wahaniami rzędu mV w stanie ustalonym warto zacząć od 0.02–0.05 V.
12. Widoczność kolumn
Przycisk "Kolumny" nad tabelą otwiera listę dostępnych kolumn — odznaczone są ukrywane. Stan zapisywany jest globalnie (klucz table_visible_columns) — każdy użytkownik widzi tę samą listę. Przyciski "Wszystkie" / "Żadna" resetują stan.
13. Co jest zapisywane globalnie, a co lokalnie
| Element | Gdzie | Trwałość |
|---|---|---|
| Konfiguracja (wszystkie pola panelu Konfiguracja) | SQLite, tabela config | Globalnie, trwale |
| Pomiary zapisane (regular + change-triggered) | SQLite, tabela readings | Globalnie, trwale |
| Widoczność kolumn tabeli | SQLite, klucz table_visible_columns | Globalnie, trwale |
| Statystyki sesji (min/max/avg) | Pamięć przeglądarki | Do reloadu strony |
| Bufor wykresów (live) | Pamięć przeglądarki | Do reloadu strony |
| Konsola live | Pamięć przeglądarki (max 200 linii) | Do reloadu strony / przycisku "Wyczyść" |
14. Endpointy API
| Metoda + URL | Opis |
|---|---|
POST /api/readings | ESP32 wysyła pojedynczy pomiar (JSON). Zwraca {status, saved, timestamp, next_interval_ms}. |
POST /api/readings/batch | Tablica pomiarów do hurtowego wstawienia. |
GET /api/readings?limit=&offset=&sortBy=&desc= | Paginowana lista zapisanych pomiarów. |
GET /api/readings/latest | Ostatni zapisany pomiar. |
GET /api/readings/range?from=&to= | Pomiary w przedziale timestampów (ms). |
GET /api/readings/stats | min/max/avg ze wszystkich (lub z zakresu) zapisanych pomiarów. |
GET /api/devices | Lista znanych urządzeń (first/last_seen). |
GET /api/config · PUT /api/config | Pełny config / aktualizacja częściowa (filtruje do whitelisty kluczy). |
WS /ws | WebSocket. Eventy: connected (welcome + config), new_reading, reading_saved, config_update. |
15. FAQ / typowe problemy
- Tabela pokazuje datę 1970 — co się dzieje?
- ESP32 nie zsynchronizował NTP w momencie wysyłki — wysłał
millis()jako timestamp. Gdy NTP "złapie", kolejne pomiary mają normalną datę. Zapis wpadł do bazy normalnie (server-sidecreated_atjest poprawny). - Napięcie ogniwa ma szum ~50 Hz i widoczne tętnienia.
- Sprzężenie z siecią 230 V. Sprawdź ekranowanie przewodów od ogniwa, skróć trasy do MCP6002, wprowadź wspólny punkt masy (star grounding). Jeśli masz przetwornice DC-DC obok układu pomiarowego — odizoluj je. Filtr RC 1 kΩ / 1 µF na wejściu op-ampa ścina HF, ale nie 50 Hz.
- Wszystkie ostatnie wpisy mają
change_triggered=1, brakuje regularnych zapisów. - Progi zmiany ustawione zbyt nisko — szum pomiarowy przekracza próg na każdym pomiarze. Podnieś
change_threshold_*(np. 0.05 V) lub wyłącz mechanizm. Regularny zapis i tak leci niezależnie cosave_interval_minutes. - Dashboard pokazuje "Łączenie...", brak danych live.
- WebSocket nie połączony — sprawdź czy backend działa (
pm2 status mfc-backend) i czy nginx proxy-uje/wsz nagłówkami Upgrade/Connection. - Po restarcie backendu energia pokazuje 0 mWh.
- Energia liczona jest w pamięci od startu procesu. Po
pm2 restartlicznik startuje od zera. Historia pomiarów w bazie pozostaje nienaruszona — energię narastającą można odtworzyć całkując prąd × czas z zapisanych pomiarów. - Chcę zmierzyć krzywą polaryzacji (U vs I).
- Zmieniaj rezystor load między pomiarami (np. od 10 MΩ → 1 kΩ → 100 Ω → zwarcie). Każdy punkt zapisze się jako change-triggered (skok napięcia). Z zapisanych par (bus_voltage, current_mA) zbuduj krzywą w arkuszu.
- Chcę dodać pomiar potencjału anody vs. elektroda odniesienia.
- Podepnij elektrodę Ag/AgCl (lub inną) do AIN2 lub AIN3 — najlepiej przez kolejny bufor op-amp (MCP6002 ma tylko 2 połowy, więc dodatkowy układ). Firmware już odczytuje wszystkie 4 kanały, backend już zapisuje
ads_ch2_voltage/ads_ch3_voltage, frontend pokazuje w tabeli. Etykiety Ch2/Ch3 trzeba by dodać do configu — ~5 linii.