Files
sensor-pico/PROJEKT.md

378 lines
13 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# Raspberry Pi Pico W Sensor Projekt
## Regeln
- Claude erklärt Konzepte und gibt Hinweise, aber **kein fertiger Code**
- Ich schreibe den Code selbst
- Bei Fehlern analysieren wir gemeinsam
- Ausnahme: Setup und Boilerplate darf vorgegeben werden
---
## Hardware
| Komponente | Protokoll | Misst |
|---|---|---|
| Raspberry Pi Pico W | | Mikrocontroller mit WLAN |
| BME280 (GY-BME280) | I2C (Adresse 0x76) | Temperatur + Luftdruck + Luftfeuchtigkeit |
> AM2302 und DS18B20 werden nicht verwendet BME280 liefert alle benötigten Messwerte.
---
## Entwicklungsumgebung
### System
- CachyOS (Arch-basiert)
- VS Code
### Installierte Tools
```bash
sudo pacman -S arm-none-eabi-gcc arm-none-eabi-newlib cmake ninja git python
sudo pacman -S minicom
```
### VS Code Extensions
- C/C++ (ms-vscode.cpptools)
- CMake Tools (ms-vscode.cmake-tools)
- CMake (twxs.cmake)
- Serial Monitor (ms-vscode.vscode-serial-monitor)
- Raspberry Pi Pico (raspberry-pi.raspberry-pi-pico)
### Serieller Monitor
- Port: `/dev/ttyACM0`
- Baud Rate: `115200`
- Gruppe für Zugriff: `uucp`
```bash
sudo usermod -aG uucp $USER
# Dann neu einloggen!
```
---
## Projekt Struktur
Das Repo heißt `sensor-pico` und ist gleichzeitig der Root keine extra Ebene darüber.
```
sensor-pico/ ← Git Repo Root
├── pico-sdk/ ← Git Submodule (Raspberry Pi Pico SDK)
├── lib/
│ ├── bme280/ ← Git Submodule (lafftale1999/bme280_driver)
│ └── dhcp_server/ ← DHCP Server (von pico-examples, kein Submodule)
├── src/
│ ├── main.cpp ← Hauptprogramm
│ ├── webserver.c ← lwIP httpd Initialisierung + CGI-Handler
│ └── fsdata.c ← Generiert von makefsdata, nicht im Git!
├── fs/
│ └── index.html ← Weboberfläche, wird beim Build eingebaut
├── build/ ← Von cmake generiert, nicht im Git
├── CMakeLists.txt
├── lwipopts.h ← lwIP Konfiguration
└── prepend_include.cmake ← Hilfsskript für makefsdata-Build-Schritt
```
### Git Submodule
```
[submodule "lib/bme280"]
path = lib/bme280
url = git@github.com:lafftale1999/bme280_driver.git
[submodule "pico-sdk"]
path = pico-sdk
url = https://github.com/raspberrypi/pico-sdk.git
```
### .gitignore
```
build/
.vscode/
CMakeCache.txt
CMakeFiles/
cmake_install.cmake
Makefile
*.uf2
*.elf
*.bin
*.hex
*.map
*.dis
__pycache__/
*.pyc
src/fsdata.c
```
> `src/fsdata.c` wird beim Build automatisch generiert und gehört nicht ins Repo.
---
## CMakeLists.txt (aktueller Stand)
```cmake
cmake_minimum_required(VERSION 3.13)
set(PICO_BOARD pico_w)
set(CMAKE_EXPORT_COMPILE_COMMANDS ON)
set(PICO_SDK_PATH ${CMAKE_SOURCE_DIR}/pico-sdk)
include(${PICO_SDK_PATH}/external/pico_sdk_import.cmake)
project(sensor-pico C CXX ASM)
set(CMAKE_C_STANDARD 11)
set(CMAKE_CXX_STANDARD 17)
pico_sdk_init()
add_subdirectory(lib/bme280 bme280_build)
add_subdirectory(lib/dhcp_server dhcp_server_build)
# makefsdata: fs/index.html → src/fsdata.c
add_custom_command(
OUTPUT ${CMAKE_CURRENT_SOURCE_DIR}/src/fsdata.c
COMMAND perl ${PICO_SDK_PATH}/lib/lwip/src/apps/http/makefsdata/makefsdata
COMMAND ${CMAKE_COMMAND} -E rename fsdata.c src/fsdata.c
COMMAND ${CMAKE_COMMAND} -P ${CMAKE_CURRENT_SOURCE_DIR}/prepend_include.cmake
WORKING_DIRECTORY ${CMAKE_CURRENT_SOURCE_DIR}
DEPENDS ${CMAKE_SOURCE_DIR}/fs/index.html
)
add_executable(sensor-pico
src/main.cpp
src/webserver.c
src/fsdata.c
)
pico_enable_stdio_usb(sensor-pico 1)
pico_enable_stdio_uart(sensor-pico 0)
target_include_directories(sensor-pico PRIVATE
${CMAKE_CURRENT_SOURCE_DIR}
)
target_link_libraries(sensor-pico
pico_stdlib
pico_cyw43_arch_lwip_threadsafe_background
pico_lwip
pico_lwip_http
bme280
dhcp_server
)
pico_add_extra_outputs(sensor-pico)
```
---
## prepend_include.cmake
Dieses Script wird von CMake als Build-Schritt ausgeführt und fügt den fehlenden `#include` an den Anfang der generierten `fsdata.c` ein. Nötig weil das alte Perl-Script von lwIP keinen Header einfügt.
```cmake
file(READ "src/fsdata.c" CONTENT)
file(WRITE "src/fsdata.c" "#include \"lwip/apps/fs.h\"\n${CONTENT}")
```
> Warum nicht direkt Perl dafür? CMake-Scripts haben kein Shell-Quoting-Problem `file(READ/WRITE)` arbeitet direkt mit dem Dateisystem, ohne Shell-Umweg.
---
## Build & Flash Workflow
```bash
# Erstes Setup (nur einmal)
mkdir build
# Bauen
cd build
cmake .. -DPICO_BOARD=pico_w # PICO_BOARD muss explizit angegeben werden!
make -j$(nproc)
# Was passiert beim Build automatisch:
# 1. makefsdata liest fs/index.html
# 2. Generiert fsdata.c mit HTML-Inhalt als Byte-Array
# 3. Verschiebt fsdata.c nach src/fsdata.c
# 4. prepend_include.cmake fügt #include "lwip/apps/fs.h" ein
# 5. fsdata.c wird mitkompiliert
# Flashen
# 1. BOOTSEL gedrückt halten
# 2. USB einstecken
# 3. Taste loslassen
# 4. RPI-RP2 erscheint als Laufwerk
cp sensor-pico.uf2 /run/media/$USER/RPI-RP2/
```
---
## Verkabelung
**I2C (BME280):**
```
VCC → 3.3V (Pin 36)
GND → GND
SDA → GPIO 14 (Pin 19) ← Vorgabe der bme280-Bibliothek (i2c1)
SCL → GPIO 15 (Pin 20) ← Vorgabe der bme280-Bibliothek (i2c1)
```
---
## BME280 Messwerte
Die Bibliothek gibt skalierte Integer zurück kein Floating Point, üblich auf Embedded-Systemen wegen Speicher und Geschwindigkeit.
| Wert | Skalierung | Beispiel | Umrechnung |
|---|---|---|---|
| Temperatur | × 100 | 2494 | `/ 100` → 24.94°C |
| Luftfeuchtigkeit | × 1024 | 30759 | `/ 1024` → 30.0% |
| Luftdruck | × 256 (Pa) | 26074767 | `/ 256 / 100` → 1018.54 hPa |
JSON-Ausgabe per `bme280_get_json(handle)`:
```json
{"temperature":2494,"humidity":30759,"pressure":26074767}
```
Die Umrechnung kann auf dem Empfänger (z.B. Home Assistant) gemacht werden der Pico sendet die Rohwerte.
Lesbare Ausgabe per printf möglich:
```c
printf("Temp: %d.%02d C\n", temperature / 100, temperature % 100);
```
---
## Netzwerk / Webserver
### Architektur
Der Pico hat zwei Betriebsmodi:
**Modus 1 Erststart / Konfiguration (Access Point)**
- Pico öffnet einen eigenen Access Point (`SensorAP`, WPA2)
- DHCP-Server läuft Clients bekommen automatisch eine IP (`192.168.4.x`)
- Pico ist erreichbar unter `192.168.4.1`
- Weboberfläche zeigt Konfigurationsformular
**Modus 2 Produktionsbetrieb (Station Mode)**
- Pico verbindet sich mit bestehendem WLAN (gespeicherte SSID + Passwort)
- Publisht Sensordaten per MQTT an Broker
- Webserver läuft weiterhin: Live-Daten + Einstellungen änderbar
### Aktueller Stand
- ✅ Access Point (`SensorAP`, WPA2) öffnet sich
- ✅ DHCP-Server läuft Clients bekommen IP
- ✅ Pico erreichbar unter `192.168.4.1`
- ✅ lwIP httpd läuft Index-Seite wird ausgeliefert
-`makefsdata` automatisch im Build-Prozess eingebunden
-`fs/index.html` wird beim Build als Byte-Array in Firmware eingebaut
### Geplante Anforderungen
1. Beim Erststart: Access Point für Erstkonfiguration
2. Weboberfläche zum Einstellen von:
- WLAN SSID + Passwort
- MQTT Broker (Adresse, Port, User, Passwort)
- Sendefrequenz
3. Im Produktionsbetrieb: Live-Daten + Einstellungen änderbar
### makefsdata wie es funktioniert
`makefsdata` ist ein Perl-Script im lwIP-Quellcode:
```
pico-sdk/lib/lwip/src/apps/http/makefsdata/makefsdata
```
Es liest alle Dateien aus dem `fs/`-Ordner und erzeugt daraus eine `fsdata.c` mit dem Dateiinhalt als statische Byte-Arrays. lwIP httpd schaut zur Laufzeit in dieser Datei nach statt auf einem echten Dateisystem.
**Problem:** Das Script generiert `fsdata.c` ohne den nötigen `#include "lwip/apps/fs.h"` der Compiler kennt dann weder `struct fsdata_file` noch `FS_FILE_FLAGS_*`.
**Lösung:** `prepend_include.cmake` fügt den Include als CMake-Build-Schritt nach der Generierung ein.
### lwipopts.h
```c
#define NO_SYS 1
#define LWIP_SOCKET 0
#define LWIP_NETCONN 0
#define LWIP_IPV4 1
#define LWIP_TCP 1
#define LWIP_UDP 1
#define LWIP_DHCP 1
#define LWIP_DNS 1
#define LWIP_RAW 1
#define LWIP_ARP 1
#define LWIP_ETHERNET 1
#define LWIP_ICMP 1
#define LWIP_NETIF_HOSTNAME 1
#define LWIP_NETIF_STATUS_CALLBACK 1
#define MEM_ALIGNMENT 4
#define MEM_SIZE 4000
#define MEMP_NUM_TCP_SEG 32
#define PBUF_POOL_SIZE 24
#define TCP_MSS 1460
#define TCP_WND (8 * TCP_MSS)
#define TCP_SND_BUF (8 * TCP_MSS)
#define LWIP_HTTPD 1
#define LWIP_HTTPD_CGI 1
#define LWIP_HTTPD_SSI 1
```
---
## Nächste Schritte
- [x] `makefsdata` automatisch in cmake einbinden damit `fs/index.html` beim Build in die Firmware eingebaut wird
- [ ] Konfigurationsformular in `index.html` (WLAN + MQTT + Sendefrequenz)
- [ ] CGI-Handler in `webserver.c` für POST-Request vom Formular
- [ ] Einstellungen im Flash speichern
- [ ] WLAN Station Mode (Verbindung mit bestehendem Netz)
- [ ] MQTT einbinden
---
## Gelerntes
### Konzepte
- **I2C** Bus-Protokoll, alle Geräte an 2 Drähten (SDA + SCL), jedes Gerät hat eine Adresse
- **Hexadezimal** `0x` Prefix, 16 Ziffern (0-9, A-F)
- **ACK/NACK** Antwort/keine Antwort bei I2C Kommunikation
- **Handle** "Ticket" auf ein intern verwaltetes Objekt (Garderobe-Analogie)
- **Pull-up Widerstand** nötig bei I2C und OneWire
- **printf** Formatstring mit Platzhaltern (`%d`, `%s`, `%02X`), Zeilenumbruch mit `\n`
- **&variable** Adressoperator, gibt Speicheradresse zurück
- **uint8_t** 8-bit unsigned integer, genau 1 Byte
- **void** Rückgabetyp wenn Funktion nichts zurückgibt
- **Embedded** kein `std::cout`, `printf` bevorzugen wegen Speicher
- **IP-Adresse** eindeutige Adresse im Netzwerk, 4 Zahlen (0-255)
- **Netzmaske** definiert welcher Teil der IP das Netzwerk ist (`255.255.255.0`)
- **DHCP** verteilt automatisch IP-Adressen an Geräte im Netzwerk
- **lwIP** schlanker TCP/IP Stack für Embedded-Systeme
- **fsdata** HTML-Dateien werden beim Build als C-Bytes in die Firmware eingebaut
- **makefsdata** Perl-Script das `fs/`-Ordner in `fsdata.c` Byte-Array konvertiert
- **extern "C"** verhindert C++ Name Mangling bei C-Bibliotheken
- **Name Mangling** C++ kodiert Funktionsnamen mit Typinfo im Symbol (für Overloading); C nicht das verursacht Linker-Fehler wenn man C-Bibliotheken aus C++ nutzt ohne `extern "C"`
- **-j$(nproc)** paralleles Bauen mit allen CPU-Kernen
- **add_custom_command** CMake-Befehl um externe Programme als Build-Schritt auszuführen; mit `OUTPUT` weiß CMake welche Datei erzeugt wird und wann neu gebaut werden muss
- **${CMAKE_COMMAND} -E** plattformunabhängige CMake-Dateibefehle (rename, copy, remove) ohne Shell-Abhängigkeit
- **${CMAKE_COMMAND} -P** führt ein CMake-Script direkt aus, ohne Shell-Umweg und ohne Quoting-Probleme
- **GET vs POST** GET-Parameter landen in der URL (sichtbar, in Logs), POST-Daten im Body für Passwörter immer POST
### CMake-Konzepte
- **add_subdirectory** bindet einen Unterordner mit eigenem CMakeLists.txt ein; zweiter Parameter ist der Build-Unterordner
- **target_include_directories** sagt dem Compiler wo er Header-Dateien suchen soll; `PRIVATE` = nur für dieses Target
- **target_link_libraries** verknüpft Bibliotheken mit dem Executable
- **add_custom_command OUTPUT** definiert wie eine generierte Datei erzeugt wird; CMake führt den Befehl aus wenn die Datei fehlt oder Abhängigkeiten neuer sind
- **DEPENDS in add_custom_command** wenn diese Dateien sich ändern, wird der Befehl beim nächsten Build neu ausgeführt
### Pico W Besonderheiten
- Interne LED hängt am WLAN-Chip (CYW43), nicht an GPIO 25
- Braucht `pico_cyw43_arch_none` (ohne Netzwerk) oder `pico_cyw43_arch_lwip_threadsafe_background` (mit lwIP)
- `PICO_BOARD=pico_w` muss **vor** `pico_sdk_import.cmake` gesetzt werden
- `MEM_LIBC_MALLOC` ist inkompatibel mit `threadsafe_background`
- `lwipopts.h` muss selbst erstellt werden lwIP hat keine Defaults
- lwIP httpd CGI erwartet C-Funktionen in `.cpp`-Dateien mit `extern "C"` wrappen
### Bekannte Fallstricke
- `makefsdata` generiert `fsdata.c` ohne `#include "lwip/apps/fs.h"` → Compiler kennt `struct fsdata_file` nicht → `prepend_include.cmake` als Workaround
- Shell-Quoting in CMake `COMMAND` ist fehleranfällig bei komplexen Perl-Ausdrücken → CMake-Scripts (`-P`) verwenden statt Shell-Befehle
- `add_custom_command OUTPUT` mit relativem Pfad legt die Datei im Build-Verzeichnis ab, nicht im Source-Verzeichnis → immer absoluten Pfad mit `${CMAKE_CURRENT_SOURCE_DIR}` angeben