Compare commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
e6422892f3 | ||
|
|
608f6bfd42 | ||
|
|
722301b1d6 | ||
|
|
e9168ee72b | ||
|
|
e342fff539 | ||
|
|
ee404065ce |
+9
-9
@@ -1,13 +1,13 @@
|
||||
*.log
|
||||
*.txt
|
||||
*.json
|
||||
!haos-addon/**/*.json
|
||||
shinelanx-modbus/
|
||||
__pycache__/
|
||||
*.pyc
|
||||
.env
|
||||
|
||||
__pycache__/
|
||||
!haos-addon/**/*.json
|
||||
.env
|
||||
*.json
|
||||
*.log
|
||||
*.pyc
|
||||
*.txt
|
||||
# versehentlich getrackte Alt-Dateien, gehoeren nicht ins Repo
|
||||
growatt-diagnose-tool/
|
||||
Modbus-Server-Kathrein.pdf
|
||||
ShineLAN-X/releases/*.bin
|
||||
growatt-diagnose-tool/
|
||||
shinelanx-modbus/
|
||||
|
||||
@@ -1,30 +1,19 @@
|
||||
# ShineBridge
|
||||
|
||||
Lokale Solar-Wechselrichter-Integration für Home Assistant — ohne Cloud, ohne Hersteller-App. Modbus TCP via ShineLAN-X, MQTT Discovery, persistente History, PV-Überschussladen, Energie-Dashboard.
|
||||
Lokales Energiemanagement für Growatt/Goodwe-Wechselrichter in Home Assistant — ohne Cloud, ohne Hersteller-App. Modbus TCP / UDP, MQTT Discovery, persistente History, PV-Überschussladen, Energie-Dashboard.
|
||||
|
||||
> **Repository:** `https://gitea.bitfire.work/retr0/shinebridge`
|
||||
>
|
||||
> Firmware, Hardware-Diagnose und Flash-Tools für ShineLAN-X/ShineWifi-X liegen seit v1.9.0 in einem eigenen, privaten Repo (`growatt-diagnose-tool`) — dieses Repo ist reines Energiemanagement.
|
||||
|
||||
---
|
||||
|
||||
## Übersicht
|
||||
|
||||
| Komponente | Beschreibung | Status |
|
||||
|---|---|---|
|
||||
| **ShineBridge Add-on** | HAOS Add-on: Modbus TCP / UDP → MQTT Discovery | ✅ v1.8.18 |
|
||||
| **ShineLAN-X Firmware** | NuttX auf STM32F103: Modbus RTU ↔ TCP + OTA | ✅ Produktiv |
|
||||
| **ShineWifi-X ESPHome** | ESPHome-Configs für ESP8266-Stick | ✅ Getestet |
|
||||
| **ShineDiag** | Portables Vor-Ort-Diagnose-Tool (Pi 3B) | ✅ Bereit |
|
||||
|
||||
---
|
||||
|
||||
## ShineBridge Add-on
|
||||
|
||||
### Architektur
|
||||
## Architektur
|
||||
|
||||
```
|
||||
Wechselrichter / Energiezähler
|
||||
│ Modbus RTU (RS485 / USB)
|
||||
[ShineLAN-X] ← STM32F103, NuttX, Modbus RTU ↔ TCP
|
||||
[ShineLAN-X] ← STM32F103, NuttX, Modbus RTU ↔ TCP (Firmware: growatt-diagnose-tool)
|
||||
│ Modbus TCP Port 502 (LAN)
|
||||
│
|
||||
│ UDP/8899 (Goodwe)
|
||||
@@ -38,10 +27,10 @@ Wechselrichter / Energiezähler
|
||||
├── Persistente History (SQLite, 7 Tage)
|
||||
├── Energie-Dashboard (SVG-Flussdiagramm, EPEX Spot)
|
||||
├── Finanzen-Tab (Abschlags-Tracker, Eigenversorgung)
|
||||
└── Web UI: Live-Daten, Geräte, Einstellungen, Flash-Wizard
|
||||
└── Web UI: Live-Daten, Geräte, Einstellungen
|
||||
```
|
||||
|
||||
### Features
|
||||
## Features
|
||||
|
||||
- **Multi-Gerät** — beliebig viele Wechselrichter + Zähler + Wallbox, jeder als eigenes HA-Gerät
|
||||
- **MQTT Discovery** — Sensoren erscheinen automatisch in HA
|
||||
@@ -51,15 +40,13 @@ Wechselrichter / Energiezähler
|
||||
- **Finanzen-Tab** — kWh-Kosten, Eigenversorgung, Abschlags-Tracker (monatliche Rate + Grundpreis)
|
||||
- **Flexibler Tarif** — Festpreis oder EPEX Spot (aWATTar DE/AT) mit Aufschlag
|
||||
- **Sparklines** — Live-Graphen der letzten 5 Minuten pro Sensor
|
||||
- **EMS-Controller** — PV-Überschussladen der Kathrein Wallbox, Zwangsladen-Fallback
|
||||
- **Setup-Wizard** — Geführte Ersteinrichtung (MQTT + erster Wechselrichter)
|
||||
- **Flash-Wizard** — ShineLAN-X Firmware direkt aus dem Add-on flashen (OTA oder ST-Link-Anleitung)
|
||||
- **EMS-Controller** — PV-Überschussladen der Kathrein Wallbox, Zwangsladen-Fallback mit konfigurierbarem Max-Ladestrom
|
||||
- **Überschuss-Geräte** — Zigbee2MQTT-Geräte bei PV-Überschuss automatisch einschalten
|
||||
- **Konfig-Export/Import** — JSON im Einstellungen-Tab
|
||||
- **Port-Sicherheit** — `/api/*` nur über HAOS-Ingress erreichbar, kein Direktzugriff von außen
|
||||
- **Kein Cloud-Zwang** — vollständig lokal
|
||||
|
||||
### Unterstützte Geräte
|
||||
## Unterstützte Geräte
|
||||
|
||||
| Gerät | Typ | Sensoren | Protokoll |
|
||||
|---|---|---|---|
|
||||
@@ -70,13 +57,14 @@ Wechselrichter / Energiezähler
|
||||
| Goodwe GW10KN-ET | Hybrid 3-phasig + Batterie | 39 | UDP/8899 (goodwe lib) |
|
||||
| Kathrein Wallbox | EVSE + Meter + EMS | 18 | Modbus FC03, TCP/502 |
|
||||
|
||||
### EMS — PV-Überschussladen (Kathrein Wallbox)
|
||||
## EMS — PV-Überschussladen (Kathrein Wallbox)
|
||||
|
||||
Der EMS-Controller regelt den Ladestrom der Kathrein Wallbox anhand des PV-Überschusses:
|
||||
|
||||
1. **PV-Laden** — Überschuss ≥ Mindestleistung (Standard: 1400 W = 6 A × 230 V): Ladestrom = verfügbare PV-Leistung / Phasen
|
||||
2. **Warten** — Kein ausreichender Überschuss: Laden pausiert, Timer läuft
|
||||
3. **Zwangsladen** — Nach Timeout ohne PV (Standard: 4 h): Laden mit Maximalstrom bis zur Zielzeit (Standard: 06:00 Uhr)
|
||||
3. **Zwangsladen** — Nach Timeout ohne PV (Standard: 4 h): Laden mit konfiguriertem Max-Ladestrom bis zur Zielzeit (Standard: 06:00 Uhr), Strom wird bei jedem Poll erneut gesetzt (kein Einmal-Write)
|
||||
4. **Unbekannte Ladezustände** (z. B. Fahrzeug-Störung) pausieren den Strom, ohne den Zwangslade-Timer zurückzusetzen
|
||||
|
||||
Konfigurierbar pro Gerät im Web UI:
|
||||
|
||||
@@ -86,10 +74,13 @@ Konfigurierbar pro Gerät im Web UI:
|
||||
| Timeout ohne PV | Stunden bis Zwangsladen | 4 h |
|
||||
| Vollladung bis | Zielzeit für Zwangsladen | 06:00 |
|
||||
| Anzahl Phasen | 1 / 2 / 3-phasig | 3 |
|
||||
| **Max. Ladestrom** | Obergrenze fürs Zwangsladen — auf die tatsächliche Wallbox-/Zuleitungs-Absicherung setzen | 32 A |
|
||||
|
||||
> **Hinweis:** Die EMS-Funktion muss in der Kathrein easyOperate-App aktiviert sein.
|
||||
> **Wichtig:** Max. Ladestrom niemals höher als die real installierte Wallbox stellen (z. B. 16 A bei einer 11-kW-Wallbox) — sonst fordert das Zwangsladen mehr an, als die Hardware liefern kann, was bei manchen Fahrzeugen zu Ladefehlern führt.
|
||||
>
|
||||
> Die EMS-Funktion muss zusätzlich in der Kathrein easyOperate-App aktiviert sein.
|
||||
|
||||
### Netzleistung (grid_power)
|
||||
## Netzleistung (grid_power)
|
||||
|
||||
Der Sensor `grid_power` zeigt die Netzleistung intuitiv:
|
||||
- **Positiv** = Netzbezug (Strom vom Netz)
|
||||
@@ -97,37 +88,27 @@ Der Sensor `grid_power` zeigt die Netzleistung intuitiv:
|
||||
|
||||
Bei reinen Growatt-Anlagen (ohne Goodwe) wird `power_to_grid` als Proxy verwendet.
|
||||
|
||||
### Installation
|
||||
## Installation
|
||||
|
||||
**Schritt 1 — Add-on in Home Assistant installieren**
|
||||
**Add-on in Home Assistant installieren**
|
||||
|
||||
1. **Einstellungen → Add-ons → Add-on-Store → ⋮ → Repositories**
|
||||
2. URL: `https://gitea.bitfire.work/retr0/shinebridge`
|
||||
3. „ShineBridge" → Installieren → Starten
|
||||
|
||||
**Schritt 2 — ShineLAN-X flashen**
|
||||
**ShineLAN-X flashen**
|
||||
|
||||
Beim ersten Start erscheint der **Setup-Wizard** automatisch. Der Tab **Flash** im Web UI führt durch beide Szenarien:
|
||||
Das Flashen des ShineLAN-X-Sticks (OTA oder ST-Link-Erstflash) läuft über das separate [growatt-diagnose-tool](https://gitea.bitfire.work/retr0/growatt-diagnose-tool) — dort auch die Firmware-Quelle und Doku.
|
||||
|
||||
- **OTA-Modus** (Stick bereits geflasht): IP eingeben → Firmware auswählen → Flashen. Die aktuelle Firmware ist im Add-on integriert.
|
||||
- **Erstflash via ST-Link** (Stick noch ungeflasht): Schritt-für-Schritt-Anleitung mit Pinout und `st-flash`-Befehl.
|
||||
**Gerät konfigurieren**
|
||||
|
||||
Für den manuellen Erstflash:
|
||||
```bash
|
||||
# SWD-Pinbelegung: PA13=SWDIO PA14=SWCLK GND 3.3V
|
||||
|
||||
st-flash --reset write nuttx-mbusd-shinelanx.bin 0x08000000
|
||||
```
|
||||
|
||||
**Schritt 3 — Gerät konfigurieren**
|
||||
|
||||
Im Web UI → „+ Gerät hinzufügen" (oder Setup-Wizard beim ersten Start):
|
||||
Im Web UI → „+ Gerät hinzufügen":
|
||||
- Name, Modell, IP, Port, Modbus-Adresse, MQTT Topic-Präfix, Abfrageintervall
|
||||
- Bei Kathrein Wallbox: EMS-Parameter konfigurieren
|
||||
- Bei Kathrein Wallbox: EMS-Parameter konfigurieren (inkl. Max. Ladestrom!)
|
||||
|
||||
Sensoren erscheinen danach unter **Einstellungen → Geräte & Dienste → MQTT**.
|
||||
|
||||
### Aggregat-Sensoren (Energie-Dashboard)
|
||||
## Aggregat-Sensoren (Energie-Dashboard)
|
||||
|
||||
Das Aggregat-Gerät „ShineBridge Gesamt" stellt folgende Sensoren bereit:
|
||||
|
||||
@@ -139,7 +120,7 @@ Das Aggregat-Gerät „ShineBridge Gesamt" stellt folgende Sensoren bereit:
|
||||
| Batterie Ladezustand Ø | Durchschnitt aller Batterien (%) |
|
||||
| Batterie Lade-/Entladeleistung | Summe (W) |
|
||||
|
||||
### History API
|
||||
## History API
|
||||
|
||||
```
|
||||
GET /api/history?inv_id=<id>&sensor_id=<id>&hours=<1-168>
|
||||
@@ -147,138 +128,26 @@ GET /api/history?inv_id=<id>&sensor_id=<id>&hours=<1-168>
|
||||
|
||||
Gibt alle Messpunkte des gewählten Zeitfensters zurück (max. 7 Tage).
|
||||
|
||||
---
|
||||
|
||||
## ShineLAN-X Firmware
|
||||
|
||||
Der **Growatt ShineLAN-X** ist ein LAN-Monitoring-Stick (STM32F103RC + ENC28J60) der im USB-Port des Wechselrichters steckt. Die Custom-Firmware (NuttX + mbusd) macht ihn zum Modbus TCP Gateway mit OTA-Update-Funktion.
|
||||
|
||||
> **Firmware basiert auf [mwalle (Martin Walle)](https://github.com/mwalle/shinelanx-modbus)**
|
||||
> Lizenzen: Board-Support Apache 2.0 · mbusd BSD 3-Clause · Gateway-App BSD 2-Clause.
|
||||
> Quellcode: [github.com/mwalle/shinelanx-modbus](https://github.com/mwalle/shinelanx-modbus)
|
||||
|
||||
### Flash-Layout
|
||||
|
||||
```
|
||||
0x08000000 dapboot (8 KB) ← Bootloader, nicht überschrieben
|
||||
0x08002000 NuttX (93 KB) ← Modbus RTU ↔ TCP, Port 502 + OTA HTTP, Port 80
|
||||
↕
|
||||
0x08020000 Staging (≤124 KB) ← OTA: Neue Firmware temporär hier
|
||||
```
|
||||
|
||||
### OTA-Update
|
||||
|
||||
Ab der DFU-Variante (`nuttx-mbusd-shinelanx-dfu.bin`) ist OTA ohne ST-Link möglich. Der Stick hostet einen HTTP-Server auf Port 80:
|
||||
|
||||
| Endpunkt | Beschreibung |
|
||||
|---|---|
|
||||
| `GET /` | Status-JSON: `{"ota":true,"app_base":"0x08002000",...}` |
|
||||
| `POST /update` | Firmware-Binary empfangen, in Staging schreiben, anwenden, Reset |
|
||||
| `POST /reboot` | Software-Reset |
|
||||
|
||||
Der OTA-Prozess läuft zweistufig (Single-Bank STM32 Einschränkung):
|
||||
1. **Stage 1** — Firmware in die obere Flash-Hälfte (Staging) schreiben, TCP-Stack läuft noch
|
||||
2. **Stage 2** — Aus SRAM heraus: Staging → App-Bereich kopieren, Reset
|
||||
|
||||
Das Add-on steuert OTA über den **Flash-Tab** im Web UI.
|
||||
|
||||
### Hardware
|
||||
|
||||
| Komponente | Details |
|
||||
|---|---|
|
||||
| MCU | STM32F103RC — 256 kB Flash, 48 kB SRAM |
|
||||
| Ethernet | ENC28J60 (SPI2) |
|
||||
| USB | PA11=D−, PA12=D+, PA8=Pullup |
|
||||
| SWD | PA13=SWDIO, PA14=SWCLK |
|
||||
| Debug UART | USART1 — PA9=TX, PA10=RX, 115200 Baud |
|
||||
| LEDs | PC7 blau, PB1 rot, PB0 grün, PC5 blau RGB |
|
||||
|
||||
---
|
||||
|
||||
## ShineWifi-X (ESPHome)
|
||||
|
||||
Der **Growatt ShineWifi-X** ist ein WLAN-Stick (ESP8266) der im RS485-Port des Wechselrichters steckt. ESPHome liest die Register direkt aus.
|
||||
|
||||
```
|
||||
Wechselrichter →RS485→ [ShineWifi-X ESP8266] →WLAN→ [Home Assistant]
|
||||
```
|
||||
|
||||
**Erstinstallation** (USB-zu-Serial-Adapter, 3,3 V):
|
||||
```bash
|
||||
esphome run "ShineWifi-X/Growatt MIC 1500 TL-X/Growatt MIC 1500 TL-X.yaml"
|
||||
```
|
||||
|
||||
| Modell | Ordner |
|
||||
|---|---|
|
||||
| Growatt MIC 1500 TL-X | `ShineWifi-X/Growatt MIC 1500 TL-X/` |
|
||||
| Growatt MIC 2000 TL-X | `ShineWifi-X/Growatt MIC 2000 TL-X/` |
|
||||
| Growatt SPH 5000 TL3-BH-UP | `ShineWifi-X/Growatt SPH 5000 TL3/` |
|
||||
| Growatt MOD 6000 TL3-XH | `ShineWifi-X/Growatt MOD 6000 TL3-XH/` |
|
||||
|
||||
---
|
||||
|
||||
## ShineDiag — Vor-Ort-Diagnose
|
||||
|
||||
Portables Diagnose-Tool für Techniker. Raspberry Pi 3B als Gateway: LAN zum ShineLAN-X, WiFi-Hotspot für MacBook/Tablet.
|
||||
|
||||
```
|
||||
[Wechselrichter] →USB→ [ShineLAN-X] →LAN→ [Pi 3B] →WiFi→ [MacBook/Tablet]
|
||||
http://10.0.1.1
|
||||
```
|
||||
|
||||
**Einmalig einrichten:**
|
||||
```bash
|
||||
git clone https://gitea.bitfire.work/retr0/shinebridge
|
||||
cd shinebridge/tools/shinediag
|
||||
sudo bash setup/install.sh
|
||||
sudo reboot
|
||||
```
|
||||
|
||||
**Vor Ort:**
|
||||
1. LAN-Kabel: Pi ↔ ShineLAN-X
|
||||
2. WiFi verbinden: **ShineDiag** | Passwort: `shinelanx`
|
||||
3. Browser: `http://10.0.1.1` → Modell wählen → Auslesen → Export JSON
|
||||
|
||||
**Features:** Alle Sensoren, Rohdaten-Register-Dump (Adresse, Hex, Dezimal), JSON-Export.
|
||||
|
||||
---
|
||||
|
||||
## Repository-Struktur
|
||||
|
||||
```
|
||||
shinebridge/
|
||||
├── haos-addon/ ← HAOS Add-on
|
||||
│ ├── config.yaml
|
||||
│ ├── Dockerfile
|
||||
│ ├── firmware/ # Integrierte ShineLAN-X Binaries
|
||||
│ │ ├── nuttx-mbusd-shinelanx.bin # Direktflash (0x08000000)
|
||||
│ │ └── nuttx-mbusd-shinelanx-dfu.bin # OTA-fähig (0x08002000)
|
||||
│ └── src/
|
||||
│ ├── main.py # Flask, Poll-Threads, REST API, Flash-Proxy
|
||||
│ ├── modbus_client.py # Modbus TCP, Float32-Dekodierung
|
||||
│ ├── goodwe_client.py # Goodwe UDP/8899 via goodwe-Bibliothek
|
||||
│ ├── wallbox_client.py # Kathrein Wallbox Modbus TCP
|
||||
│ ├── ems_controller.py # PV-Überschussladen + Zwangsladen
|
||||
│ ├── mqtt_publisher.py # MQTT Discovery + Aggregat
|
||||
│ ├── inverters.py # Register-Maps aller Geräte
|
||||
│ ├── history.py # SQLite Persistenz
|
||||
│ └── web/index.html # Web UI (Energie, Finanzen, Live, Geräte, Flash)
|
||||
├── ShineLAN-X/
|
||||
│ └── releases/
|
||||
│ ├── nuttx-mbusd-shinelanx.bin
|
||||
│ └── nuttx-mbusd-shinelanx-dfu.bin
|
||||
├── shinelanx-modbus/ ← NuttX Firmware Quellcode
|
||||
│ └── apps/shinelanx-modbus-gw/
|
||||
│ ├── main.c # Startup: MAC, DHCP, OTA-Thread, mbusd
|
||||
│ ├── ota_http.c # OTA HTTP-Server (Zwei-Phasen Flash)
|
||||
│ └── ota_http.h
|
||||
├── ShineWifi-X/ ← ESPHome Configs
|
||||
├── tools/
|
||||
│ └── shinediag/ ← Vor-Ort-Diagnose-Tool
|
||||
└── repository.yaml
|
||||
└── haos-addon/ ← HAOS Add-on (einziger Inhalt dieses Repos)
|
||||
├── config.yaml
|
||||
├── Dockerfile
|
||||
└── src/
|
||||
├── main.py # Flask, Poll-Threads, REST API
|
||||
├── modbus_client.py # Modbus TCP, Float32-Dekodierung
|
||||
├── goodwe_client.py # Goodwe UDP/8899 via goodwe-Bibliothek
|
||||
├── wallbox_client.py # Kathrein Wallbox Modbus TCP
|
||||
├── ems_controller.py # PV-Überschussladen + Zwangsladen
|
||||
├── mqtt_publisher.py # MQTT Discovery + Aggregat
|
||||
├── inverters.py # Register-Maps aller Geräte
|
||||
├── history.py # SQLite Persistenz
|
||||
└── web/index.html # Web UI (Energie, Finanzen, Live, Geräte, Einstellungen)
|
||||
```
|
||||
|
||||
---
|
||||
Firmware (ShineLAN-X, ShineWifi-X) und das Vor-Ort-Diagnose-Tool (ShineDiag) liegen im separaten, privaten Repo [`growatt-diagnose-tool`](https://gitea.bitfire.work/retr0/growatt-diagnose-tool).
|
||||
|
||||
## Lizenz
|
||||
|
||||
|
||||
@@ -1,119 +0,0 @@
|
||||
# Growatt ShineLAN-X — Custom Firmware
|
||||
|
||||
Custom-Firmware für den **Growatt ShineLAN-X** LAN-Stick. Liest Modbus RTU vom Wechselrichter und publiziert alle Werte per **MQTT** direkt in Home Assistant — vollständig lokal, ohne Growatt-Cloud.
|
||||
|
||||
---
|
||||
|
||||
## Hardware
|
||||
|
||||
| Komponente | Details |
|
||||
|---|---|
|
||||
| MCU | STM32F103RBT6 (LQFP-64) |
|
||||
| Ethernet | ENC28J60 (Bitbang SPI2 auf PB12–PB15) |
|
||||
| Wechselrichter-Anbindung | USB-CDC (virtueller COM-Port, 115200 Baud) |
|
||||
| Programmier-Interface | SWD via ST-Link |
|
||||
|
||||
### Pin-Belegung (vollständig verifiziert)
|
||||
|
||||
Quelle: [mwalle/shinelanx-modbus](https://github.com/mwalle/shinelanx-modbus) — identische Platine.
|
||||
|
||||
| Funktion | Pin | Hinweis |
|
||||
|---|---|---|
|
||||
| ETH CS | PB12 | SPI2 NSS |
|
||||
| ETH SCK | PB13 | SPI2 SCK |
|
||||
| ETH MISO | PB14 | SPI2 MISO |
|
||||
| ETH MOSI | PB15 | SPI2 MOSI |
|
||||
| ETH RST | PC8 | |
|
||||
| ETH INT | PC6 | optional |
|
||||
| USB D- | PA11 | Wechselrichter-Kommunikation |
|
||||
| USB D+ | PA12 | Wechselrichter-Kommunikation |
|
||||
| USB Pullup | PA8 | |
|
||||
| Debug UART TX | PA9 | USART1, 115200 |
|
||||
| Debug UART RX | PA10 | USART1, 115200 |
|
||||
| LED Debug | PC7 | aktiv LOW |
|
||||
| LED Rot | PB1 | aktiv LOW |
|
||||
| LED Grün | PB0 | aktiv LOW |
|
||||
| LED Blau | PC5 | aktiv LOW |
|
||||
| Taster | PA3 | aktiv LOW |
|
||||
|
||||
### SWD-Verkabelung (Flashen)
|
||||
|
||||
| ShineLAN-X Testpunkt | ST-Link V2 |
|
||||
|---|---|
|
||||
| SWDIO | SWDIO |
|
||||
| SWDCLK | SWCLK |
|
||||
| GND | GND |
|
||||
| 3.3V | 3.3V (nur wenn nicht extern versorgt) |
|
||||
|
||||
---
|
||||
|
||||
## Konfiguration
|
||||
|
||||
Alle Einstellungen in `firmware/include/config.h`:
|
||||
|
||||
```cpp
|
||||
// DHCP (1) oder statische IP (0)
|
||||
#define USE_DHCP 1
|
||||
|
||||
// MQTT-Broker
|
||||
#define MQTT_BROKER "192.168.1.1"
|
||||
#define MQTT_USER "mqtt"
|
||||
#define MQTT_PASSWORD "..."
|
||||
|
||||
// Modbus Slave-Adresse (Standard: 1)
|
||||
#define MODBUS_ADDR 1
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Flashen
|
||||
|
||||
```bash
|
||||
cd firmware
|
||||
pio run --target upload
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Wechselrichter-Kompatibilität
|
||||
|
||||
Die Sensor-Liste in `main.cpp` entspricht dem **Growatt SPH 5000 TL3-BH-UP** (dreiphasig, mit Batterie).
|
||||
|
||||
Beim Testen mit dem **MIC 1500 TL-X** (einphasig, ohne Batterie):
|
||||
|
||||
| Unterschied | MIC 1500 | SPH 5000 |
|
||||
|---|---|---|
|
||||
| AC Output Power | Register **40** | Register 35 |
|
||||
| PV-Strings | nur PV1 | PV1 + PV2 |
|
||||
| Phasen | nur L1 | L1 / L2 / L3 |
|
||||
| Batterie-Register | nicht vorhanden | 1009–1058 |
|
||||
|
||||
Nicht vorhandene Register werden automatisch übersprungen (kein Absturz). Für Dauerbetrieb am MIC 1500 einfach die nicht passenden Sensoren in `main.cpp` auskommentieren und `ac_power_total` auf Register 40 ändern.
|
||||
|
||||
---
|
||||
|
||||
## MQTT Topics
|
||||
|
||||
Nach dem Start sendet die Firmware automatisch **MQTT Discovery** — Home Assistant erkennt das Gerät ohne manuelle Konfiguration.
|
||||
|
||||
| Topic | Inhalt |
|
||||
|---|---|
|
||||
| `homeassistant/sensor/growatt_shinelan_*/config` | Discovery (retained) |
|
||||
| `growatt/shinelan/pv1_power` | PV1 Leistung in W |
|
||||
| `growatt/shinelan/ac_power_total` | AC Ausgangsleistung in W |
|
||||
| `growatt/shinelan/energy_today` | Tagesenergie in kWh |
|
||||
| `growatt/shinelan/energy_total` | Gesamtenergie in kWh |
|
||||
| `growatt/shinelan/bat_soc` | Batterie Ladestand in % |
|
||||
| `growatt/shinelan/energy_import_total` | Netzbezug gesamt in kWh |
|
||||
|
||||
---
|
||||
|
||||
## LED-Status
|
||||
|
||||
| LED | Bedeutung |
|
||||
|---|---|
|
||||
| Alle 3 kurz an | Startup |
|
||||
| Grün | MQTT verbunden |
|
||||
| Rot | MQTT-Verbindungsfehler |
|
||||
| Debug (PC7) | Ethernet Link UP |
|
||||
| Blau blinkt | Modbus-Abfrage läuft |
|
||||
@@ -1,5 +0,0 @@
|
||||
.pio
|
||||
.vscode/.browse.c_cpp.db*
|
||||
.vscode/c_cpp_properties.json
|
||||
.vscode/launch.json
|
||||
.vscode/ipch
|
||||
-10
@@ -1,10 +0,0 @@
|
||||
{
|
||||
// See http://go.microsoft.com/fwlink/?LinkId=827846
|
||||
// for the documentation about the extensions.json format
|
||||
"recommendations": [
|
||||
"platformio.platformio-ide"
|
||||
],
|
||||
"unwantedRecommendations": [
|
||||
"ms-vscode.cpptools-extension-pack"
|
||||
]
|
||||
}
|
||||
@@ -1,20 +0,0 @@
|
||||
# Growatt ShineLAN-X Firmware
|
||||
|
||||
PlatformIO-Projekt fuer STM32F103RBT6. Liest Modbus RTU vom Wechselrichter via USB-CDC und publiziert via MQTT an Home Assistant.
|
||||
|
||||
## Wiki Knowledge Base
|
||||
|
||||
Path: /Users/retr0/Nextcloud/HomeLab/Vault
|
||||
|
||||
Beim Start einer neuen Session:
|
||||
1. Zuerst `wiki/hot.md` lesen (aktueller Kontext)
|
||||
2. Dann `wiki/entities/Growatt ShineLAN-X Firmware.md` fuer Projektdetails
|
||||
3. Nur bei Bedarf weitere Wiki-Seiten
|
||||
|
||||
## Wichtige Konventionen
|
||||
|
||||
- Kommentare nur wenn das WARUM nicht offensichtlich ist
|
||||
- Keine trailing summaries nach Tool-Calls
|
||||
- config.h ist geheimnis-frei (DEFAULT_* Werte) -- committen OK
|
||||
- Echte Credentials werden nur im EEPROM gespeichert, nie im Source
|
||||
- Framework-Hacks in `~/.platformio/` werden bei `pio pkg update` zurueckgesetzt
|
||||
@@ -1,62 +0,0 @@
|
||||
#pragma once
|
||||
|
||||
// ============================================================
|
||||
// Pin-Belegung — STM32F103RBT6, LQFP-64
|
||||
// Quelle: https://github.com/mwalle/shinelanx-modbus
|
||||
// ============================================================
|
||||
|
||||
// ENC28J60 — SPI2 (Hardware-SPI)
|
||||
#define ETH_CS_PIN PB12
|
||||
#define ETH_SCK_PIN PB13
|
||||
#define ETH_MISO_PIN PB14
|
||||
#define ETH_MOSI_PIN PB15
|
||||
#define ETH_RST_PIN PC8
|
||||
|
||||
// LEDs (aktiv LOW)
|
||||
#define LED_DEBUG PC7
|
||||
#define LED_RED PB1
|
||||
#define LED_GREEN PB0
|
||||
#define LED_BLUE PC5
|
||||
|
||||
// User-Taster
|
||||
#define BTN_USER PA3
|
||||
|
||||
// MAC-Adresse
|
||||
#define MAC_ADDRESS 0xDE, 0xAD, 0xBE, 0xEF, 0xFE, 0xED
|
||||
|
||||
// ============================================================
|
||||
// MODBUS
|
||||
// ============================================================
|
||||
#define MODBUS_BAUD 9600
|
||||
#define MODBUS_ADDR 1
|
||||
|
||||
// ============================================================
|
||||
// GERÄT
|
||||
// ============================================================
|
||||
#define MQTT_CLIENT "growatt-shinelan"
|
||||
#define DEVICE_ID "growatt_shinelan"
|
||||
#define DEVICE_NAME "Growatt ShineLAN-X"
|
||||
#define DEVICE_MODEL "ShineLAN-X"
|
||||
#define DEVICE_MFR "Growatt"
|
||||
|
||||
// ============================================================
|
||||
// STANDARD-WERTE (erster Boot / Werkseinstellungen)
|
||||
// Werden über die Web-UI überschrieben und in EEPROM gespeichert.
|
||||
// ============================================================
|
||||
#define DEFAULT_DHCP true
|
||||
#define DEFAULT_STATIC_IP 192,168,1,99
|
||||
#define DEFAULT_GW 192,168,1,1
|
||||
#define DEFAULT_SUBNET 255,255,255,0
|
||||
#define DEFAULT_DNS 192,168,1,1
|
||||
#define DEFAULT_MQTT_BROKER "192.168.1.1"
|
||||
#define DEFAULT_MQTT_PORT 1883
|
||||
#define DEFAULT_MQTT_USER ""
|
||||
#define DEFAULT_MQTT_PASS ""
|
||||
#define DEFAULT_UPDATE_MS 10000UL
|
||||
|
||||
// ============================================================
|
||||
// WECHSELRICHTER-MODELL
|
||||
// Genau einen einkommentieren:
|
||||
// ============================================================
|
||||
#define INVERTER_MIC1500 // Growatt MIC 1500/2000 TL-X (einphasig)
|
||||
// #define INVERTER_SPH5000 // Growatt SPH 5000 TL3 (dreiphasig, Hybrid)
|
||||
@@ -1,50 +0,0 @@
|
||||
#pragma once
|
||||
#include <Arduino.h>
|
||||
#include <EEPROM.h>
|
||||
#include "config.h"
|
||||
|
||||
#define CFG_MAGIC 0xA55A1234
|
||||
|
||||
struct NetConfig {
|
||||
uint32_t magic;
|
||||
bool useDhcp;
|
||||
uint8_t ip[4];
|
||||
uint8_t gw[4];
|
||||
uint8_t sn[4];
|
||||
uint8_t dns[4];
|
||||
char mqttBroker[40];
|
||||
uint16_t mqttPort;
|
||||
char mqttUser[32];
|
||||
char mqttPass[64];
|
||||
uint32_t updateMs;
|
||||
};
|
||||
|
||||
inline void configDefaults(NetConfig& c) {
|
||||
c.magic = CFG_MAGIC;
|
||||
c.useDhcp = DEFAULT_DHCP;
|
||||
uint8_t dip[] = {DEFAULT_STATIC_IP};
|
||||
uint8_t dgw[] = {DEFAULT_GW};
|
||||
uint8_t dsn[] = {DEFAULT_SUBNET};
|
||||
uint8_t ddn[] = {DEFAULT_DNS};
|
||||
memcpy(c.ip, dip, 4);
|
||||
memcpy(c.gw, dgw, 4);
|
||||
memcpy(c.sn, dsn, 4);
|
||||
memcpy(c.dns, ddn, 4);
|
||||
strncpy(c.mqttBroker, DEFAULT_MQTT_BROKER, sizeof(c.mqttBroker) - 1);
|
||||
c.mqttBroker[sizeof(c.mqttBroker) - 1] = 0;
|
||||
c.mqttPort = DEFAULT_MQTT_PORT;
|
||||
strncpy(c.mqttUser, DEFAULT_MQTT_USER, sizeof(c.mqttUser) - 1);
|
||||
c.mqttUser[sizeof(c.mqttUser) - 1] = 0;
|
||||
strncpy(c.mqttPass, DEFAULT_MQTT_PASS, sizeof(c.mqttPass) - 1);
|
||||
c.mqttPass[sizeof(c.mqttPass) - 1] = 0;
|
||||
c.updateMs = DEFAULT_UPDATE_MS;
|
||||
}
|
||||
|
||||
inline void configLoad(NetConfig& c) {
|
||||
EEPROM.get(0, c);
|
||||
if (c.magic != CFG_MAGIC) configDefaults(c);
|
||||
}
|
||||
|
||||
inline void configSave(const NetConfig& c) {
|
||||
EEPROM.put(0, c);
|
||||
}
|
||||
@@ -1,29 +0,0 @@
|
||||
#pragma once
|
||||
#include <Arduino.h>
|
||||
#include "config.h"
|
||||
|
||||
// ============================================================
|
||||
// Bitbang-SPI für ENC28J60 auf PC6/PC7/PC8/PC9
|
||||
// ENC28J60 verwendet SPI Mode 0 (CPOL=0, CPHA=0)
|
||||
// ============================================================
|
||||
|
||||
static inline void eth_spi_init() {
|
||||
pinMode(ETH_SCK_PIN, OUTPUT); digitalWrite(ETH_SCK_PIN, LOW);
|
||||
pinMode(ETH_MOSI_PIN, OUTPUT); digitalWrite(ETH_MOSI_PIN, LOW);
|
||||
pinMode(ETH_MISO_PIN, INPUT);
|
||||
pinMode(ETH_CS_PIN, OUTPUT); digitalWrite(ETH_CS_PIN, HIGH);
|
||||
}
|
||||
|
||||
static inline void eth_cs_low() { digitalWrite(ETH_CS_PIN, LOW); }
|
||||
static inline void eth_cs_high() { digitalWrite(ETH_CS_PIN, HIGH); }
|
||||
|
||||
static inline uint8_t eth_spi_transfer(uint8_t out) {
|
||||
uint8_t in = 0;
|
||||
for (int8_t i = 7; i >= 0; i--) {
|
||||
digitalWrite(ETH_MOSI_PIN, (out >> i) & 1);
|
||||
digitalWrite(ETH_SCK_PIN, HIGH);
|
||||
in = (in << 1) | digitalRead(ETH_MISO_PIN);
|
||||
digitalWrite(ETH_SCK_PIN, LOW);
|
||||
}
|
||||
return in;
|
||||
}
|
||||
@@ -1 +0,0 @@
|
||||
{"type": "library", "name": "EthernetENC", "version": "2.0.5", "spec": {"owner": "jandrassy", "id": 11203, "name": "EthernetENC", "requirements": null, "uri": null}}
|
||||
@@ -1,158 +0,0 @@
|
||||
First an important note: There are more suitable libraries for ENC28J60 with ESP8266, RP2040, ESP32 and Mbed Arduino platforms:
|
||||
|
||||
* With ESP8266 and RP2040 platform you can use the EthernetCompat.h from the lwIP_enc28j60 bundled library.
|
||||
* With ESP32 platform version 3 you can use the [EthernetESP32](https://github.com/Networking-for-Arduino/EthernetESP32) library which integrates with networking on the ESP32 platform.
|
||||
* With Arduino Mbed Core boards you can use the [ENC28J60-EMAC](https://github.com/Networking-for-Arduino/ENC28J60-EMAC) library with the platforms Ethernet library
|
||||
|
||||
---
|
||||
|
||||
# EthernetENC
|
||||
|
||||
EthernetENC is the Ethernet library for ENC28J60. It is a modern version of [the UIPEthernet library](https://github.com/jandrassy/EthernetENC/wiki/UIPEthernet).
|
||||
|
||||
The modernization includes:
|
||||
* Ethernet 2.0.0 Arduino library functions
|
||||
* compatible include file names EthernetClient.h, EthernetServer.h and EthernetUdp.h
|
||||
* support of many Arduino architectures by using the SPI library
|
||||
* SPI transactions to share the SPI bus with devices with different communication settings
|
||||
* SPI communication at 20 MHz if the MCU supports it, else on the maximum supported by the MCU
|
||||
* client.flush() to send the packet immediately
|
||||
* calls yield() in blocking functions
|
||||
* has UDP backlog to receive more than one message at time
|
||||
* Arduino 1.5 library format built with dot_a_linkage option for optimal build result
|
||||
|
||||
[The documentation of Arduino Ethernet library](https://www.arduino.cc/en/Reference/Ethernet) applies for classes and functions descriptions.
|
||||
|
||||
Limitations:
|
||||
* UDP.beginMulticast is not supported, because the uIP stack doesn't support multicast
|
||||
* UDP broadcasts receiving is turned off on ENC to lower the processing load on the library
|
||||
|
||||
This library doesn't have examples, because examples of the Arduino Ethernet library apply. You can find them in the Arduino IDE Examples menu Ethernet section. Only change `#include <Ethernet.h>` to `#include <EthernetENC.h>`. Some examples require [a little change](https://github.com/jandrassy/EthernetENC/wiki/Examples).
|
||||
|
||||
This library is based on the Norbert Truchsess's arduino-uip original source code repository and uses experience from the development of the multi-architecture support by Cassy. Applicable fixes and enhancements from development of EthernetENC were transfered to Cassy's UIPEthernet.
|
||||
|
||||
**You can find more information in project's [Wiki](https://github.com/jandrassy/EthernetENC/wiki).**
|
||||
|
||||
## Original notes by Norbert Truchsess
|
||||
|
||||
UIPEthernet is written as a wrapper around the mature uIP Stack by Adam Dunkels, which provides the low-level implementation for all supported protocols. To overcome the memory-constrains (a 'regular' uIP-application does all processing in RAM) the ENC28J60 internal memory is used for all stream buffers (in and out). Only 400-600 Bytes of Arduinos RAM are used (depending on the number of concurrently open connections). As of Flash-memory a ATmega368-based Arduino is the minimum requirenment.
|
||||
|
||||
uIP was written by Adam Dunkels of the Networked Embedded Systems group at the Swedish Institute of Computer Science.
|
||||
|
||||
This library was inspired by the SerialIP implementation by Adam Nielsen <malvineous@shikadi.net>, actually I took this code as a starting point, but in the latest versions there are very few lines left.
|
||||
|
||||
|
||||
## Licenses
|
||||
```
|
||||
UIPEthernet.h
|
||||
UIPEthernet.cpp
|
||||
UIPServer.h
|
||||
UIPServer.cpp
|
||||
UIPClient.h
|
||||
UIPClient.cpp
|
||||
UIPUdp.h
|
||||
UIPUdp.cpp
|
||||
utility/mempool.h
|
||||
utility/mempool.cpp
|
||||
|
||||
Copyright (c) 2013 Norbert Truchsess <norbert.truchsess@t-online.de>
|
||||
All rights reserved.
|
||||
|
||||
This program is free software: you can redistribute it and/or modify
|
||||
it under the terms of the GNU General Public License as published by
|
||||
the Free Software Foundation, either version 3 of the License, or
|
||||
(at your option) any later version.
|
||||
|
||||
This program is distributed in the hope that it will be useful,
|
||||
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
GNU General Public License for more details.
|
||||
|
||||
You should have received a copy of the GNU General Public License
|
||||
along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
--------------
|
||||
|
||||
utility/enc28j60.h
|
||||
|
||||
Author : Pascal Stang (c)2005
|
||||
Modified by Norbert Truchsess
|
||||
Copyright: GPL V2
|
||||
|
||||
--------------
|
||||
|
||||
utility/Enc28J60Network.h
|
||||
utility/Enc28J60Network.cpp
|
||||
|
||||
Copyright (c) 2013 Norbert Truchsess <norbert.truchsess@t-online.de>
|
||||
All rights reserved.
|
||||
|
||||
inspired and based on enc28j60.c file from the AVRlib library by Pascal Stang.
|
||||
|
||||
This program is free software: you can redistribute it and/or modify
|
||||
it under the terms of the GNU General Public License as published by
|
||||
the Free Software Foundation, either version 3 of the License, or
|
||||
(at your option) any later version.
|
||||
|
||||
This program is distributed in the hope that it will be useful,
|
||||
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
GNU General Public License for more details.
|
||||
|
||||
You should have received a copy of the GNU General Public License
|
||||
along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
--------------
|
||||
|
||||
utility/uip.c
|
||||
utility/uip_arp.h
|
||||
utility/uip_arp.c
|
||||
utility/uip_arch.h
|
||||
utility/uip.h
|
||||
utility/uipopt.h
|
||||
|
||||
Copyright (c) 2001-2003, Adam Dunkels <adam@sics.se>, <adam@dunkels.com>.
|
||||
All rights reserved.
|
||||
|
||||
Redistribution and use in source and binary forms, with or without
|
||||
modification, are permitted provided that the following conditions
|
||||
are met:
|
||||
1. Redistributions of source code must retain the above copyright
|
||||
notice, this list of conditions and the following disclaimer.
|
||||
2. Redistributions in binary form must reproduce the above copyright
|
||||
notice, this list of conditions and the following disclaimer in the
|
||||
documentation and/or other materials provided with the distribution.
|
||||
3. Neither the name of the Institute nor the names of its contributors
|
||||
may be used to endorse or promote products derived from this software
|
||||
without specific prior written permission.
|
||||
|
||||
THIS SOFTWARE IS PROVIDED BY THE INSTITUTE AND CONTRIBUTORS ``AS IS'' AND
|
||||
ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
|
||||
IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
|
||||
ARE DISCLAIMED. IN NO EVENT SHALL THE INSTITUTE OR CONTRIBUTORS BE LIABLE
|
||||
FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
|
||||
DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
|
||||
OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
|
||||
HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
|
||||
LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
|
||||
OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
|
||||
SUCH DAMAGE.
|
||||
|
||||
--------------
|
||||
|
||||
Dhcp.h
|
||||
Dhcp.cpp
|
||||
|
||||
DHCP Library v0.3 - April 25, 2009
|
||||
Author: Jordan Terrell - blog.jordanterrell.com
|
||||
- as included in Arduinos stock Ethernet-library, no special licence mentioned here
|
||||
|
||||
--------------
|
||||
|
||||
Dns.h
|
||||
Dns.cpp
|
||||
|
||||
(c) Copyright 2009-2010 MCQN Ltd.
|
||||
Released under Apache License, version 2.0
|
||||
|
||||
```
|
||||
@@ -1,11 +0,0 @@
|
||||
name=EthernetENC
|
||||
author=Norbert Truchsess, Juraj Andrassy
|
||||
maintainer=Juraj Andrassy <juraj.andrassy@gmail.com>
|
||||
sentence=Ethernet library for ENC28J60. Only include EthernetENC.h instead of Ethernet.h
|
||||
paragraph=This is a modern version of the UIPEthernet library. EthernetENC library is compatible with all Arduino architectures with Arduino SPI library with transactions support.
|
||||
url=https://github.com/Networking-for-Arduino/EthernetENC/wiki
|
||||
architectures=*
|
||||
version=2.0.5
|
||||
category=Communication
|
||||
includes=EthernetENC.h
|
||||
dot_a_linkage=true
|
||||
@@ -1,491 +0,0 @@
|
||||
// DHCP Library v0.3 - April 25, 2009
|
||||
// Author: Jordan Terrell - blog.jordanterrell.com
|
||||
|
||||
#include "Dhcp.h"
|
||||
#include <Arduino.h>
|
||||
#include "utility/util.h"
|
||||
|
||||
int DhcpClass::beginWithDHCP(uint8_t *mac, unsigned long timeout, unsigned long responseTimeout)
|
||||
{
|
||||
_dhcpLeaseTime=0;
|
||||
_dhcpT1=0;
|
||||
_dhcpT2=0;
|
||||
_lastCheck=0;
|
||||
_timeout = timeout;
|
||||
_responseTimeout = responseTimeout;
|
||||
|
||||
// zero out _dhcpMacAddr
|
||||
memset(_dhcpMacAddr, 0, 6);
|
||||
reset_DHCP_lease();
|
||||
|
||||
memcpy((void*)_dhcpMacAddr, (void*)mac, 6);
|
||||
_dhcp_state = STATE_DHCP_START;
|
||||
return request_DHCP_lease();
|
||||
}
|
||||
|
||||
void DhcpClass::reset_DHCP_lease(){
|
||||
memset(_dhcpLocalIp, 0, 4);
|
||||
memset(_dhcpSubnetMask, 0, 4);
|
||||
memset(_dhcpGatewayIp, 0, 4);
|
||||
memset(_dhcpDhcpServerIp, 0, 4);
|
||||
memset(_dhcpDnsServerIp, 0, 4);
|
||||
}
|
||||
|
||||
//return:0 on error, 1 if request is sent and response is received
|
||||
int DhcpClass::request_DHCP_lease(){
|
||||
|
||||
uint8_t messageType = 0;
|
||||
|
||||
|
||||
|
||||
// Pick an initial transaction ID
|
||||
_dhcpTransactionId = random(1UL, 2000UL);
|
||||
_dhcpInitialTransactionId = _dhcpTransactionId;
|
||||
|
||||
_dhcpUdpSocket.stop();
|
||||
if (_dhcpUdpSocket.begin(DHCP_CLIENT_PORT) == 0)
|
||||
{
|
||||
// Couldn't get a socket
|
||||
return 0;
|
||||
}
|
||||
|
||||
presend_DHCP();
|
||||
|
||||
int result = 0;
|
||||
|
||||
unsigned long startTime = millis();
|
||||
|
||||
while(_dhcp_state != STATE_DHCP_LEASED)
|
||||
{
|
||||
if(_dhcp_state == STATE_DHCP_START)
|
||||
{
|
||||
_dhcpTransactionId++;
|
||||
|
||||
send_DHCP_MESSAGE(DHCP_DISCOVER, ((millis() - startTime) / 1000));
|
||||
_dhcp_state = STATE_DHCP_DISCOVER;
|
||||
}
|
||||
else if(_dhcp_state == STATE_DHCP_REREQUEST){
|
||||
_dhcpTransactionId++;
|
||||
send_DHCP_MESSAGE(DHCP_REQUEST, ((millis() - startTime)/1000));
|
||||
_dhcp_state = STATE_DHCP_REQUEST;
|
||||
}
|
||||
else if(_dhcp_state == STATE_DHCP_DISCOVER)
|
||||
{
|
||||
uint32_t respId;
|
||||
messageType = parseDHCPResponse(_responseTimeout, respId);
|
||||
if(messageType == DHCP_OFFER)
|
||||
{
|
||||
// We'll use the transaction ID that the offer came with,
|
||||
// rather than the one we were up to
|
||||
_dhcpTransactionId = respId;
|
||||
send_DHCP_MESSAGE(DHCP_REQUEST, ((millis() - startTime) / 1000));
|
||||
_dhcp_state = STATE_DHCP_REQUEST;
|
||||
}
|
||||
}
|
||||
else if(_dhcp_state == STATE_DHCP_REQUEST)
|
||||
{
|
||||
uint32_t respId;
|
||||
messageType = parseDHCPResponse(_responseTimeout, respId);
|
||||
if(messageType == DHCP_ACK)
|
||||
{
|
||||
_dhcp_state = STATE_DHCP_LEASED;
|
||||
result = 1;
|
||||
//use default lease time if we didn't get it
|
||||
if(_dhcpLeaseTime == 0){
|
||||
_dhcpLeaseTime = DEFAULT_LEASE;
|
||||
}
|
||||
//calculate T1 & T2 if we didn't get it
|
||||
if(_dhcpT1 == 0){
|
||||
//T1 should be 50% of _dhcpLeaseTime
|
||||
_dhcpT1 = _dhcpLeaseTime >> 1;
|
||||
}
|
||||
if(_dhcpT2 == 0){
|
||||
//T2 should be 87.5% (7/8ths) of _dhcpLeaseTime
|
||||
_dhcpT2 = _dhcpT1 << 1;
|
||||
}
|
||||
_renewInSec = _dhcpT1;
|
||||
_rebindInSec = _dhcpT2;
|
||||
}
|
||||
else if(messageType == DHCP_NAK)
|
||||
_dhcp_state = STATE_DHCP_START;
|
||||
}
|
||||
|
||||
if(messageType == 255)
|
||||
{
|
||||
messageType = 0;
|
||||
_dhcp_state = STATE_DHCP_START;
|
||||
}
|
||||
|
||||
if(result != 1 && ((millis() - startTime) > _timeout))
|
||||
break;
|
||||
}
|
||||
|
||||
// We're done with the socket now
|
||||
_dhcpUdpSocket.stop();
|
||||
_dhcpTransactionId++;
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
void DhcpClass::presend_DHCP()
|
||||
{
|
||||
}
|
||||
|
||||
void DhcpClass::send_DHCP_MESSAGE(uint8_t messageType, uint16_t secondsElapsed)
|
||||
{
|
||||
uint8_t buffer[32];
|
||||
memset(buffer, 0, 32);
|
||||
IPAddress dest_addr( 255, 255, 255, 255 ); // Broadcast address
|
||||
|
||||
if (-1 == _dhcpUdpSocket.beginPacket(dest_addr, DHCP_SERVER_PORT))
|
||||
{
|
||||
// FIXME Need to return errors
|
||||
return;
|
||||
}
|
||||
|
||||
buffer[0] = DHCP_BOOTREQUEST; // op
|
||||
buffer[1] = DHCP_HTYPE10MB; // htype
|
||||
buffer[2] = DHCP_HLENETHERNET; // hlen
|
||||
buffer[3] = DHCP_HOPS; // hops
|
||||
|
||||
// xid
|
||||
unsigned long xid = htonl(_dhcpTransactionId);
|
||||
memcpy(buffer + 4, &(xid), 4);
|
||||
|
||||
// 8, 9 - seconds elapsed
|
||||
buffer[8] = ((secondsElapsed & 0xff00) >> 8);
|
||||
buffer[9] = (secondsElapsed & 0x00ff);
|
||||
|
||||
// flags - the only one flag DHCP uses is
|
||||
// to request the server response as broadcast, not unicast
|
||||
// unsigned short flags = htons(DHCP_FLAGSBROADCAST);
|
||||
// memcpy(buffer + 10, &(flags), 2);
|
||||
|
||||
// ciaddr: already zeroed
|
||||
// yiaddr: already zeroed
|
||||
// siaddr: already zeroed
|
||||
// giaddr: already zeroed
|
||||
|
||||
//put data in W5100 transmit buffer
|
||||
_dhcpUdpSocket.write(buffer, 28);
|
||||
|
||||
memset(buffer, 0, 32); // clear local buffer
|
||||
|
||||
memcpy(buffer, _dhcpMacAddr, 6); // chaddr
|
||||
|
||||
//put data in W5100 transmit buffer
|
||||
_dhcpUdpSocket.write(buffer, 16);
|
||||
|
||||
memset(buffer, 0, 32); // clear local buffer
|
||||
|
||||
// leave zeroed out for sname && file
|
||||
// put in W5100 transmit buffer x 6 (192 bytes)
|
||||
|
||||
for(int i = 0; i < 6; i++) {
|
||||
_dhcpUdpSocket.write(buffer, 32);
|
||||
}
|
||||
|
||||
// OPT - Magic Cookie
|
||||
buffer[0] = (uint8_t)((MAGIC_COOKIE >> 24)& 0xFF);
|
||||
buffer[1] = (uint8_t)((MAGIC_COOKIE >> 16)& 0xFF);
|
||||
buffer[2] = (uint8_t)((MAGIC_COOKIE >> 8)& 0xFF);
|
||||
buffer[3] = (uint8_t)(MAGIC_COOKIE& 0xFF);
|
||||
|
||||
// OPT - message type
|
||||
buffer[4] = dhcpMessageType;
|
||||
buffer[5] = 0x01;
|
||||
buffer[6] = messageType; //DHCP_REQUEST;
|
||||
|
||||
// OPT - client identifier
|
||||
buffer[7] = dhcpClientIdentifier;
|
||||
buffer[8] = 0x07;
|
||||
buffer[9] = 0x01;
|
||||
memcpy(buffer + 10, _dhcpMacAddr, 6);
|
||||
|
||||
// OPT - host name
|
||||
buffer[16] = hostName;
|
||||
if (_hostname == nullptr) {
|
||||
buffer[17] = strlen(HOST_NAME) + 6; // length of hostname + last 3 bytes of mac address
|
||||
strcpy((char*)&(buffer[18]), HOST_NAME);
|
||||
|
||||
printByte((char*)&(buffer[24]), _dhcpMacAddr[3]);
|
||||
printByte((char*)&(buffer[26]), _dhcpMacAddr[4]);
|
||||
printByte((char*)&(buffer[28]), _dhcpMacAddr[5]);
|
||||
|
||||
//put data in W5100 transmit buffer
|
||||
_dhcpUdpSocket.write(buffer, 30);
|
||||
} else {
|
||||
uint8_t len = strlen(_hostname);
|
||||
buffer[17] = len;
|
||||
_dhcpUdpSocket.write(buffer, 18);
|
||||
_dhcpUdpSocket.write(_hostname, len);
|
||||
}
|
||||
|
||||
if(messageType == DHCP_REQUEST)
|
||||
{
|
||||
buffer[0] = dhcpRequestedIPaddr;
|
||||
buffer[1] = 0x04;
|
||||
buffer[2] = _dhcpLocalIp[0];
|
||||
buffer[3] = _dhcpLocalIp[1];
|
||||
buffer[4] = _dhcpLocalIp[2];
|
||||
buffer[5] = _dhcpLocalIp[3];
|
||||
|
||||
buffer[6] = dhcpServerIdentifier;
|
||||
buffer[7] = 0x04;
|
||||
buffer[8] = _dhcpDhcpServerIp[0];
|
||||
buffer[9] = _dhcpDhcpServerIp[1];
|
||||
buffer[10] = _dhcpDhcpServerIp[2];
|
||||
buffer[11] = _dhcpDhcpServerIp[3];
|
||||
|
||||
//put data in W5100 transmit buffer
|
||||
_dhcpUdpSocket.write(buffer, 12);
|
||||
}
|
||||
|
||||
buffer[0] = dhcpParamRequest;
|
||||
buffer[1] = 0x06;
|
||||
buffer[2] = subnetMask;
|
||||
buffer[3] = routersOnSubnet;
|
||||
buffer[4] = dns;
|
||||
buffer[5] = domainName;
|
||||
buffer[6] = dhcpT1value;
|
||||
buffer[7] = dhcpT2value;
|
||||
buffer[8] = endOption;
|
||||
|
||||
//put data in W5100 transmit buffer
|
||||
_dhcpUdpSocket.write(buffer, 9);
|
||||
|
||||
_dhcpUdpSocket.endPacket();
|
||||
}
|
||||
|
||||
uint8_t DhcpClass::parseDHCPResponse(unsigned long responseTimeout, uint32_t& transactionId)
|
||||
{
|
||||
uint8_t type = 0;
|
||||
uint8_t opt_len = 0;
|
||||
|
||||
unsigned long startTime = millis();
|
||||
|
||||
while(_dhcpUdpSocket.parsePacket() <= 0)
|
||||
{
|
||||
if((millis() - startTime) > responseTimeout)
|
||||
{
|
||||
return 255;
|
||||
}
|
||||
delay(50);
|
||||
}
|
||||
// start reading in the packet
|
||||
RIP_MSG_FIXED fixedMsg;
|
||||
_dhcpUdpSocket.read((uint8_t*)&fixedMsg, sizeof(RIP_MSG_FIXED));
|
||||
|
||||
if(fixedMsg.op == DHCP_BOOTREPLY && _dhcpUdpSocket.remotePort() == DHCP_SERVER_PORT)
|
||||
{
|
||||
transactionId = ntohl(fixedMsg.xid);
|
||||
if(memcmp(fixedMsg.chaddr, _dhcpMacAddr, 6) != 0 || (transactionId < _dhcpInitialTransactionId) || (transactionId > _dhcpTransactionId))
|
||||
{
|
||||
// Need to read the rest of the packet here regardless
|
||||
_dhcpUdpSocket.discardReceived();
|
||||
return 0;
|
||||
}
|
||||
|
||||
memcpy(_dhcpLocalIp, fixedMsg.yiaddr, 4);
|
||||
|
||||
// Skip to the option part
|
||||
// Doing this a byte at a time so we don't have to put a big buffer
|
||||
// on the stack (as we don't have lots of memory lying around)
|
||||
for (int i =0; i < (240 - (int)sizeof(RIP_MSG_FIXED)); i++)
|
||||
{
|
||||
_dhcpUdpSocket.read(); // we don't care about the returned byte
|
||||
}
|
||||
|
||||
while (_dhcpUdpSocket.available() > 0)
|
||||
{
|
||||
switch (_dhcpUdpSocket.read())
|
||||
{
|
||||
case endOption :
|
||||
break;
|
||||
|
||||
case padOption :
|
||||
break;
|
||||
|
||||
case dhcpMessageType :
|
||||
opt_len = _dhcpUdpSocket.read();
|
||||
type = _dhcpUdpSocket.read();
|
||||
break;
|
||||
|
||||
case subnetMask :
|
||||
opt_len = _dhcpUdpSocket.read();
|
||||
_dhcpUdpSocket.read(_dhcpSubnetMask, 4);
|
||||
break;
|
||||
|
||||
case routersOnSubnet :
|
||||
opt_len = _dhcpUdpSocket.read();
|
||||
_dhcpUdpSocket.read(_dhcpGatewayIp, 4);
|
||||
for (int i = 0; i < opt_len-4; i++)
|
||||
{
|
||||
_dhcpUdpSocket.read();
|
||||
}
|
||||
break;
|
||||
|
||||
case dns :
|
||||
opt_len = _dhcpUdpSocket.read();
|
||||
_dhcpUdpSocket.read(_dhcpDnsServerIp, 4);
|
||||
for (int i = 0; i < opt_len-4; i++)
|
||||
{
|
||||
_dhcpUdpSocket.read();
|
||||
}
|
||||
break;
|
||||
|
||||
case dhcpServerIdentifier :
|
||||
opt_len = _dhcpUdpSocket.read();
|
||||
if( _dhcpDhcpServerIp[0] == 0 ||
|
||||
IPAddress(_dhcpDhcpServerIp) == _dhcpUdpSocket.remoteIP() )
|
||||
{
|
||||
_dhcpUdpSocket.read(_dhcpDhcpServerIp, sizeof(_dhcpDhcpServerIp));
|
||||
}
|
||||
else
|
||||
{
|
||||
// Skip over the rest of this option
|
||||
while (opt_len--)
|
||||
{
|
||||
_dhcpUdpSocket.read();
|
||||
}
|
||||
}
|
||||
break;
|
||||
|
||||
case dhcpT1value :
|
||||
opt_len = _dhcpUdpSocket.read();
|
||||
_dhcpUdpSocket.read((uint8_t*)&_dhcpT1, sizeof(_dhcpT1));
|
||||
_dhcpT1 = ntohl(_dhcpT1);
|
||||
break;
|
||||
|
||||
case dhcpT2value :
|
||||
opt_len = _dhcpUdpSocket.read();
|
||||
_dhcpUdpSocket.read((uint8_t*)&_dhcpT2, sizeof(_dhcpT2));
|
||||
_dhcpT2 = ntohl(_dhcpT2);
|
||||
break;
|
||||
|
||||
case dhcpIPaddrLeaseTime :
|
||||
opt_len = _dhcpUdpSocket.read();
|
||||
_dhcpUdpSocket.read((uint8_t*)&_dhcpLeaseTime, sizeof(_dhcpLeaseTime));
|
||||
_dhcpLeaseTime = ntohl(_dhcpLeaseTime);
|
||||
_renewInSec = _dhcpLeaseTime;
|
||||
break;
|
||||
|
||||
default :
|
||||
opt_len = _dhcpUdpSocket.read();
|
||||
// Skip over the rest of this option
|
||||
while (opt_len--)
|
||||
{
|
||||
_dhcpUdpSocket.read();
|
||||
}
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Need to skip to end of the packet regardless here
|
||||
_dhcpUdpSocket.discardReceived();
|
||||
|
||||
return type;
|
||||
}
|
||||
|
||||
|
||||
/*
|
||||
returns:
|
||||
0/DHCP_CHECK_NONE: nothing happened
|
||||
1/DHCP_CHECK_RENEW_FAIL: renew failed
|
||||
2/DHCP_CHECK_RENEW_OK: renew success
|
||||
3/DHCP_CHECK_REBIND_FAIL: rebind fail
|
||||
4/DHCP_CHECK_REBIND_OK: rebind success
|
||||
*/
|
||||
int DhcpClass::checkLease(){
|
||||
//this uses a signed / unsigned trick to deal with millis overflow
|
||||
unsigned long now = millis();
|
||||
signed long snow = (long)now;
|
||||
int rc=DHCP_CHECK_NONE;
|
||||
if (_lastCheck != 0){
|
||||
signed long factor;
|
||||
//calc how many ms past the timeout we are
|
||||
factor = snow - (long)_secTimeout;
|
||||
//if on or passed the timeout, reduce the counters
|
||||
if ( factor >= 0 ){
|
||||
//next timeout should be now plus 1000 ms minus parts of second in factor
|
||||
_secTimeout = snow + 1000 - factor % 1000;
|
||||
//how many seconds late are we, minimum 1
|
||||
factor = factor / 1000 +1;
|
||||
|
||||
//reduce the counters by that mouch
|
||||
//if we can assume that the cycle time (factor) is fairly constant
|
||||
//and if the remainder is less than cycle time * 2
|
||||
//do it early instead of late
|
||||
if(_renewInSec < factor*2 )
|
||||
_renewInSec = 0;
|
||||
else
|
||||
_renewInSec -= factor;
|
||||
|
||||
if(_rebindInSec < factor*2 )
|
||||
_rebindInSec = 0;
|
||||
else
|
||||
_rebindInSec -= factor;
|
||||
}
|
||||
|
||||
//if we have a lease but should renew, do it
|
||||
if (_dhcp_state == STATE_DHCP_LEASED && _renewInSec <=0){
|
||||
_dhcp_state = STATE_DHCP_REREQUEST;
|
||||
rc = 1 + request_DHCP_lease();
|
||||
}
|
||||
|
||||
//if we have a lease or is renewing but should bind, do it
|
||||
if( (_dhcp_state == STATE_DHCP_LEASED || _dhcp_state == STATE_DHCP_START) && _rebindInSec <=0){
|
||||
//this should basically restart completely
|
||||
_dhcp_state = STATE_DHCP_START;
|
||||
reset_DHCP_lease();
|
||||
rc = 3 + request_DHCP_lease();
|
||||
}
|
||||
}
|
||||
else{
|
||||
_secTimeout = snow + 1000;
|
||||
}
|
||||
|
||||
_lastCheck = now;
|
||||
return rc;
|
||||
}
|
||||
|
||||
IPAddress DhcpClass::getLocalIp()
|
||||
{
|
||||
return IPAddress(_dhcpLocalIp);
|
||||
}
|
||||
|
||||
IPAddress DhcpClass::getSubnetMask()
|
||||
{
|
||||
return IPAddress(_dhcpSubnetMask);
|
||||
}
|
||||
|
||||
IPAddress DhcpClass::getGatewayIp()
|
||||
{
|
||||
return IPAddress(_dhcpGatewayIp);
|
||||
}
|
||||
|
||||
IPAddress DhcpClass::getDhcpServerIp()
|
||||
{
|
||||
return IPAddress(_dhcpDhcpServerIp);
|
||||
}
|
||||
|
||||
IPAddress DhcpClass::getDnsServerIp()
|
||||
{
|
||||
return IPAddress(_dhcpDnsServerIp);
|
||||
}
|
||||
|
||||
void DhcpClass::setHostname(const char* hostname) {
|
||||
_hostname = hostname;
|
||||
}
|
||||
|
||||
void DhcpClass::printByte(char * buf, uint8_t n ) {
|
||||
char *str = &buf[1];
|
||||
buf[0]='0';
|
||||
do {
|
||||
unsigned long m = n;
|
||||
n /= 16;
|
||||
char c = m - 16 * n;
|
||||
*str-- = c < 10 ? c + '0' : c + 'A' - 10;
|
||||
} while(n);
|
||||
}
|
||||
@@ -1,189 +0,0 @@
|
||||
// DHCP Library v0.3 - April 25, 2009
|
||||
// Author: Jordan Terrell - blog.jordanterrell.com
|
||||
|
||||
#ifndef Dhcp_h
|
||||
#define Dhcp_h
|
||||
|
||||
#include "EthernetUdp.h"
|
||||
|
||||
/* DHCP state machine. */
|
||||
#define STATE_DHCP_START 0
|
||||
#define STATE_DHCP_DISCOVER 1
|
||||
#define STATE_DHCP_REQUEST 2
|
||||
#define STATE_DHCP_LEASED 3
|
||||
#define STATE_DHCP_REREQUEST 4
|
||||
#define STATE_DHCP_RELEASE 5
|
||||
|
||||
#define DHCP_FLAGSBROADCAST 0x8000
|
||||
|
||||
/* UDP port numbers for DHCP */
|
||||
#define DHCP_SERVER_PORT 67 /* from server to client */
|
||||
#define DHCP_CLIENT_PORT 68 /* from client to server */
|
||||
|
||||
/* DHCP message OP code */
|
||||
#define DHCP_BOOTREQUEST 1
|
||||
#define DHCP_BOOTREPLY 2
|
||||
|
||||
/* DHCP message type */
|
||||
#define DHCP_DISCOVER 1
|
||||
#define DHCP_OFFER 2
|
||||
#define DHCP_REQUEST 3
|
||||
#define DHCP_DECLINE 4
|
||||
#define DHCP_ACK 5
|
||||
#define DHCP_NAK 6
|
||||
#define DHCP_RELEASE 7
|
||||
#define DHCP_INFORM 8
|
||||
|
||||
#define DHCP_HTYPE10MB 1
|
||||
#define DHCP_HTYPE100MB 2
|
||||
|
||||
#define DHCP_HLENETHERNET 6
|
||||
#define DHCP_HOPS 0
|
||||
#define DHCP_SECS 0
|
||||
|
||||
#define MAGIC_COOKIE 0x63825363
|
||||
#define MAX_DHCP_OPT 16
|
||||
|
||||
#define HOST_NAME "ENC28J"
|
||||
#define DEFAULT_LEASE (900) //default lease time in seconds
|
||||
|
||||
#define DHCP_CHECK_NONE (0)
|
||||
#define DHCP_CHECK_RENEW_FAIL (1)
|
||||
#define DHCP_CHECK_RENEW_OK (2)
|
||||
#define DHCP_CHECK_REBIND_FAIL (3)
|
||||
#define DHCP_CHECK_REBIND_OK (4)
|
||||
|
||||
enum
|
||||
{
|
||||
padOption = 0,
|
||||
subnetMask = 1,
|
||||
timerOffset = 2,
|
||||
routersOnSubnet = 3,
|
||||
/* timeServer = 4,
|
||||
nameServer = 5,*/
|
||||
dns = 6,
|
||||
/*logServer = 7,
|
||||
cookieServer = 8,
|
||||
lprServer = 9,
|
||||
impressServer = 10,
|
||||
resourceLocationServer = 11,*/
|
||||
hostName = 12,
|
||||
/*bootFileSize = 13,
|
||||
meritDumpFile = 14,*/
|
||||
domainName = 15,
|
||||
/*swapServer = 16,
|
||||
rootPath = 17,
|
||||
extentionsPath = 18,
|
||||
IPforwarding = 19,
|
||||
nonLocalSourceRouting = 20,
|
||||
policyFilter = 21,
|
||||
maxDgramReasmSize = 22,
|
||||
defaultIPTTL = 23,
|
||||
pathMTUagingTimeout = 24,
|
||||
pathMTUplateauTable = 25,
|
||||
ifMTU = 26,
|
||||
allSubnetsLocal = 27,
|
||||
broadcastAddr = 28,
|
||||
performMaskDiscovery = 29,
|
||||
maskSupplier = 30,
|
||||
performRouterDiscovery = 31,
|
||||
routerSolicitationAddr = 32,
|
||||
staticRoute = 33,
|
||||
trailerEncapsulation = 34,
|
||||
arpCacheTimeout = 35,
|
||||
ethernetEncapsulation = 36,
|
||||
tcpDefaultTTL = 37,
|
||||
tcpKeepaliveInterval = 38,
|
||||
tcpKeepaliveGarbage = 39,
|
||||
nisDomainName = 40,
|
||||
nisServers = 41,
|
||||
ntpServers = 42,
|
||||
vendorSpecificInfo = 43,
|
||||
netBIOSnameServer = 44,
|
||||
netBIOSdgramDistServer = 45,
|
||||
netBIOSnodeType = 46,
|
||||
netBIOSscope = 47,
|
||||
xFontServer = 48,
|
||||
xDisplayManager = 49,*/
|
||||
dhcpRequestedIPaddr = 50,
|
||||
dhcpIPaddrLeaseTime = 51,
|
||||
/*dhcpOptionOverload = 52,*/
|
||||
dhcpMessageType = 53,
|
||||
dhcpServerIdentifier = 54,
|
||||
dhcpParamRequest = 55,
|
||||
/*dhcpMsg = 56,
|
||||
dhcpMaxMsgSize = 57,*/
|
||||
dhcpT1value = 58,
|
||||
dhcpT2value = 59,
|
||||
/*dhcpClassIdentifier = 60,*/
|
||||
dhcpClientIdentifier = 61,
|
||||
endOption = 255
|
||||
};
|
||||
|
||||
typedef struct _RIP_MSG_FIXED
|
||||
{
|
||||
uint8_t op;
|
||||
uint8_t htype;
|
||||
uint8_t hlen;
|
||||
uint8_t hops;
|
||||
uint32_t xid;
|
||||
uint16_t secs;
|
||||
uint16_t flags;
|
||||
uint8_t ciaddr[4];
|
||||
uint8_t yiaddr[4];
|
||||
uint8_t siaddr[4];
|
||||
uint8_t giaddr[4];
|
||||
uint8_t chaddr[6];
|
||||
}RIP_MSG_FIXED;
|
||||
|
||||
class DhcpClass {
|
||||
private:
|
||||
uint32_t _dhcpInitialTransactionId;
|
||||
uint32_t _dhcpTransactionId;
|
||||
uint8_t _dhcpMacAddr[6];
|
||||
const char* _hostname = nullptr;
|
||||
#ifdef __arm__
|
||||
uint8_t _dhcpLocalIp[4] __attribute__((aligned(4)));
|
||||
uint8_t _dhcpSubnetMask[4] __attribute__((aligned(4)));
|
||||
uint8_t _dhcpGatewayIp[4] __attribute__((aligned(4)));
|
||||
uint8_t _dhcpDhcpServerIp[4] __attribute__((aligned(4)));
|
||||
uint8_t _dhcpDnsServerIp[4] __attribute__((aligned(4)));
|
||||
#else
|
||||
uint8_t _dhcpLocalIp[4];
|
||||
uint8_t _dhcpSubnetMask[4];
|
||||
uint8_t _dhcpGatewayIp[4];
|
||||
uint8_t _dhcpDhcpServerIp[4];
|
||||
uint8_t _dhcpDnsServerIp[4];
|
||||
#endif
|
||||
uint32_t _dhcpLeaseTime;
|
||||
uint32_t _dhcpT1, _dhcpT2;
|
||||
signed long _renewInSec;
|
||||
signed long _rebindInSec;
|
||||
signed long _lastCheck;
|
||||
unsigned long _timeout;
|
||||
unsigned long _responseTimeout;
|
||||
unsigned long _secTimeout;
|
||||
uint8_t _dhcp_state;
|
||||
EthernetUDP _dhcpUdpSocket;
|
||||
|
||||
int request_DHCP_lease();
|
||||
void reset_DHCP_lease();
|
||||
void presend_DHCP();
|
||||
void send_DHCP_MESSAGE(uint8_t, uint16_t);
|
||||
void printByte(char *, uint8_t);
|
||||
|
||||
uint8_t parseDHCPResponse(unsigned long responseTimeout, uint32_t& transactionId);
|
||||
public:
|
||||
IPAddress getLocalIp();
|
||||
IPAddress getSubnetMask();
|
||||
IPAddress getGatewayIp();
|
||||
IPAddress getDhcpServerIp();
|
||||
IPAddress getDnsServerIp();
|
||||
|
||||
int beginWithDHCP(uint8_t *, unsigned long timeout = 60000, unsigned long responseTimeout = 4000);
|
||||
int checkLease();
|
||||
|
||||
void setHostname(const char* hostname);
|
||||
};
|
||||
|
||||
#endif
|
||||
@@ -1,416 +0,0 @@
|
||||
// Arduino DNS client for Enc28J60-based Ethernet shield
|
||||
// (c) Copyright 2009-2010 MCQN Ltd.
|
||||
// Released under Apache License, version 2.0
|
||||
|
||||
#include "Dns.h"
|
||||
#include <Arduino.h>
|
||||
|
||||
#define SOCKET_NONE 255
|
||||
// Various flags and header field values for a DNS message
|
||||
#define UDP_HEADER_SIZE 8
|
||||
#define DNS_HEADER_SIZE 12
|
||||
#define TTL_SIZE 4
|
||||
#define QUERY_FLAG (0)
|
||||
#define RESPONSE_FLAG (1<<15)
|
||||
#define QUERY_RESPONSE_MASK (1<<15)
|
||||
#define OPCODE_STANDARD_QUERY (0)
|
||||
#define OPCODE_INVERSE_QUERY (1<<11)
|
||||
#define OPCODE_STATUS_REQUEST (2<<11)
|
||||
#define OPCODE_MASK (15<<11)
|
||||
#define AUTHORITATIVE_FLAG (1<<10)
|
||||
#define TRUNCATION_FLAG (1<<9)
|
||||
#define RECURSION_DESIRED_FLAG (1<<8)
|
||||
#define RECURSION_AVAILABLE_FLAG (1<<7)
|
||||
#define RESP_NO_ERROR (0)
|
||||
#define RESP_FORMAT_ERROR (1)
|
||||
#define RESP_SERVER_FAILURE (2)
|
||||
#define RESP_NAME_ERROR (3)
|
||||
#define RESP_NOT_IMPLEMENTED (4)
|
||||
#define RESP_REFUSED (5)
|
||||
#define RESP_MASK (15)
|
||||
#define TYPE_A (0x0001)
|
||||
#define CLASS_IN (0x0001)
|
||||
#define LABEL_COMPRESSION_MASK (0xC0)
|
||||
// Port number that DNS servers listen on
|
||||
#define DNS_PORT 53
|
||||
|
||||
// Possible return codes from ProcessResponse
|
||||
#define SUCCESS 1
|
||||
#define TIMED_OUT -1
|
||||
#define INVALID_SERVER -2
|
||||
#define TRUNCATED -3
|
||||
#define INVALID_RESPONSE -4
|
||||
|
||||
void DNSClient::begin(const IPAddress& aDNSServer)
|
||||
{
|
||||
iDNSServer = aDNSServer;
|
||||
iRequestId = 0;
|
||||
}
|
||||
|
||||
|
||||
int DNSClient::inet_aton(const char* aIPAddrString, IPAddress& aResult)
|
||||
{
|
||||
// See if we've been given a valid IP address
|
||||
const char* p =aIPAddrString;
|
||||
while (*p &&
|
||||
( (*p == '.') || ((*p >= '0') && (*p <= '9')) ))
|
||||
{
|
||||
p++;
|
||||
}
|
||||
|
||||
if (*p == '\0')
|
||||
{
|
||||
// It's looking promising, we haven't found any invalid characters
|
||||
p = aIPAddrString;
|
||||
int segment =0;
|
||||
int segmentValue =0;
|
||||
while (*p && (segment < 4))
|
||||
{
|
||||
if (*p == '.')
|
||||
{
|
||||
// We've reached the end of a segment
|
||||
if (segmentValue > 255)
|
||||
{
|
||||
// You can't have IP address segments that don't fit in a byte
|
||||
return 0;
|
||||
}
|
||||
else
|
||||
{
|
||||
aResult[segment] = (byte)segmentValue;
|
||||
segment++;
|
||||
segmentValue = 0;
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
// Next digit
|
||||
segmentValue = (segmentValue*10)+(*p - '0');
|
||||
}
|
||||
p++;
|
||||
}
|
||||
// We've reached the end of address, but there'll still be the last
|
||||
// segment to deal with
|
||||
if ((segmentValue > 255) || (segment > 3))
|
||||
{
|
||||
// You can't have IP address segments that don't fit in a byte,
|
||||
// or more than four segments
|
||||
return 0;
|
||||
}
|
||||
else
|
||||
{
|
||||
aResult[segment] = (byte)segmentValue;
|
||||
return 1;
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
|
||||
int DNSClient::getHostByName(const char* aHostname, IPAddress& aResult)
|
||||
{
|
||||
int ret =0;
|
||||
|
||||
// See if it's a numeric IP address
|
||||
if (inet_aton(aHostname, aResult))
|
||||
{
|
||||
// It is, our work here is done
|
||||
return 1;
|
||||
}
|
||||
|
||||
// Check we've got a valid DNS server to use
|
||||
if (iDNSServer == INADDR_NONE)
|
||||
{
|
||||
return INVALID_SERVER;
|
||||
}
|
||||
|
||||
// Find a socket to use
|
||||
if (iUdp.begin(1024+(millis() & 0xF)) == 1)
|
||||
{
|
||||
// Try up to three times
|
||||
int retries = 0;
|
||||
// while ((retries < 3) && (ret <= 0))
|
||||
{
|
||||
// Send DNS request
|
||||
ret = iUdp.beginPacket(iDNSServer, DNS_PORT);
|
||||
if (ret != 0)
|
||||
{
|
||||
// Now output the request data
|
||||
ret = BuildRequest(aHostname);
|
||||
if (ret != 0)
|
||||
{
|
||||
// And finally send the request
|
||||
ret = iUdp.endPacket();
|
||||
if (ret != 0)
|
||||
{
|
||||
// Now wait for a response
|
||||
int wait_retries = 0;
|
||||
ret = TIMED_OUT;
|
||||
while ((wait_retries < 3) && (ret == TIMED_OUT))
|
||||
{
|
||||
ret = ProcessResponse(5000, aResult);
|
||||
wait_retries++;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
retries++;
|
||||
}
|
||||
|
||||
// We're done with the socket now
|
||||
iUdp.stop();
|
||||
}
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
uint16_t DNSClient::BuildRequest(const char* aName)
|
||||
{
|
||||
// Build header
|
||||
// 1 1 1 1 1 1
|
||||
// 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5
|
||||
// +--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+
|
||||
// | ID |
|
||||
// +--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+
|
||||
// |QR| Opcode |AA|TC|RD|RA| Z | RCODE |
|
||||
// +--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+
|
||||
// | QDCOUNT |
|
||||
// +--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+
|
||||
// | ANCOUNT |
|
||||
// +--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+
|
||||
// | NSCOUNT |
|
||||
// +--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+
|
||||
// | ARCOUNT |
|
||||
// +--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+
|
||||
// As we only support one request at a time at present, we can simplify
|
||||
// some of this header
|
||||
iRequestId = millis(); // generate a random ID
|
||||
uint16_t twoByteBuffer;
|
||||
|
||||
// FIXME We should also check that there's enough space available to write to, rather
|
||||
// FIXME than assume there's enough space (as the code does at present)
|
||||
iUdp.write((uint8_t*)&iRequestId, sizeof(iRequestId));
|
||||
|
||||
twoByteBuffer = htons(QUERY_FLAG | OPCODE_STANDARD_QUERY | RECURSION_DESIRED_FLAG);
|
||||
iUdp.write((uint8_t*)&twoByteBuffer, sizeof(twoByteBuffer));
|
||||
|
||||
twoByteBuffer = htons(1); // One question record
|
||||
iUdp.write((uint8_t*)&twoByteBuffer, sizeof(twoByteBuffer));
|
||||
|
||||
twoByteBuffer = 0; // Zero answer records
|
||||
iUdp.write((uint8_t*)&twoByteBuffer, sizeof(twoByteBuffer));
|
||||
|
||||
iUdp.write((uint8_t*)&twoByteBuffer, sizeof(twoByteBuffer));
|
||||
// and zero additional records
|
||||
iUdp.write((uint8_t*)&twoByteBuffer, sizeof(twoByteBuffer));
|
||||
|
||||
// Build question
|
||||
const char* start =aName;
|
||||
const char* end =start;
|
||||
uint8_t len;
|
||||
// Run through the name being requested
|
||||
while (*end)
|
||||
{
|
||||
// Find out how long this section of the name is
|
||||
end = start;
|
||||
while (*end && (*end != '.') )
|
||||
{
|
||||
end++;
|
||||
}
|
||||
|
||||
if (end-start > 0)
|
||||
{
|
||||
// Write out the size of this section
|
||||
len = end-start;
|
||||
iUdp.write(&len, sizeof(len));
|
||||
// And then write out the section
|
||||
iUdp.write((uint8_t*)start, end-start);
|
||||
}
|
||||
start = end+1;
|
||||
}
|
||||
|
||||
// We've got to the end of the question name, so
|
||||
// terminate it with a zero-length section
|
||||
len = 0;
|
||||
iUdp.write(&len, sizeof(len));
|
||||
// Finally the type and class of question
|
||||
twoByteBuffer = htons(TYPE_A);
|
||||
iUdp.write((uint8_t*)&twoByteBuffer, sizeof(twoByteBuffer));
|
||||
|
||||
twoByteBuffer = htons(CLASS_IN); // Internet class of question
|
||||
iUdp.write((uint8_t*)&twoByteBuffer, sizeof(twoByteBuffer));
|
||||
// Success! Everything buffered okay
|
||||
return 1;
|
||||
}
|
||||
|
||||
|
||||
uint16_t DNSClient::ProcessResponse(uint16_t aTimeout, IPAddress& aAddress)
|
||||
{
|
||||
uint32_t startTime = millis();
|
||||
|
||||
// Wait for a response packet
|
||||
while(iUdp.parsePacket() <= 0)
|
||||
{
|
||||
if((millis() - startTime) > aTimeout)
|
||||
return TIMED_OUT;
|
||||
delay(50);
|
||||
}
|
||||
|
||||
// We've had a reply!
|
||||
// Read the UDP header
|
||||
uint8_t header[DNS_HEADER_SIZE]; // Enough space to reuse for the DNS header
|
||||
// Check that it's a response from the right server and the right port
|
||||
if ( (iDNSServer != iUdp.remoteIP()) ||
|
||||
(iUdp.remotePort() != DNS_PORT) )
|
||||
{
|
||||
// It's not from who we expected
|
||||
return INVALID_SERVER;
|
||||
}
|
||||
|
||||
// Read through the rest of the response
|
||||
if (iUdp.available() < DNS_HEADER_SIZE)
|
||||
{
|
||||
return TRUNCATED;
|
||||
}
|
||||
iUdp.read(header, DNS_HEADER_SIZE);
|
||||
|
||||
uint16_t header_flags = htons(*((uint16_t*)&header[2]));
|
||||
// Check that it's a response to this request
|
||||
if ( ( iRequestId != (*((uint16_t*)&header[0])) ) ||
|
||||
((header_flags & QUERY_RESPONSE_MASK) != (uint16_t)RESPONSE_FLAG) )
|
||||
{
|
||||
// Mark the entire packet as read
|
||||
iUdp.discardReceived();
|
||||
return INVALID_RESPONSE;
|
||||
}
|
||||
// Check for any errors in the response (or in our request)
|
||||
// although we don't do anything to get round these
|
||||
if ( (header_flags & TRUNCATION_FLAG) || (header_flags & RESP_MASK) )
|
||||
{
|
||||
// Mark the entire packet as read
|
||||
iUdp.discardReceived();
|
||||
return -5; //INVALID_RESPONSE;
|
||||
}
|
||||
|
||||
// And make sure we've got (at least) one answer
|
||||
uint16_t answerCount = htons(*((uint16_t*)&header[6]));
|
||||
if (answerCount == 0 )
|
||||
{
|
||||
// Mark the entire packet as read
|
||||
iUdp.discardReceived();
|
||||
return -6; //INVALID_RESPONSE;
|
||||
}
|
||||
|
||||
// Skip over any questions
|
||||
for (uint16_t i =0; i < htons(*((uint16_t*)&header[4])); i++)
|
||||
{
|
||||
// Skip over the name
|
||||
uint8_t len;
|
||||
do
|
||||
{
|
||||
iUdp.read(&len, sizeof(len));
|
||||
if (len > 0)
|
||||
{
|
||||
// Don't need to actually read the data out for the string, just
|
||||
// advance ptr to beyond it
|
||||
while(len--)
|
||||
{
|
||||
iUdp.read(); // we don't care about the returned byte
|
||||
}
|
||||
}
|
||||
} while (len != 0);
|
||||
|
||||
// Now jump over the type and class
|
||||
for (int i =0; i < 4; i++)
|
||||
{
|
||||
iUdp.read(); // we don't care about the returned byte
|
||||
}
|
||||
}
|
||||
|
||||
// Now we're up to the bit we're interested in, the answer
|
||||
// There might be more than one answer (although we'll just use the first
|
||||
// type A answer) and some authority and additional resource records but
|
||||
// we're going to ignore all of them.
|
||||
|
||||
for (uint16_t i =0; i < answerCount; i++)
|
||||
{
|
||||
// Skip the name
|
||||
uint8_t len;
|
||||
do
|
||||
{
|
||||
iUdp.read(&len, sizeof(len));
|
||||
if ((len & LABEL_COMPRESSION_MASK) == 0)
|
||||
{
|
||||
// It's just a normal label
|
||||
if (len > 0)
|
||||
{
|
||||
// And it's got a length
|
||||
// Don't need to actually read the data out for the string,
|
||||
// just advance ptr to beyond it
|
||||
while(len--)
|
||||
{
|
||||
iUdp.read(); // we don't care about the returned byte
|
||||
}
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
// This is a pointer to a somewhere else in the message for the
|
||||
// rest of the name. We don't care about the name, and RFC1035
|
||||
// says that a name is either a sequence of labels ended with a
|
||||
// 0 length octet or a pointer or a sequence of labels ending in
|
||||
// a pointer. Either way, when we get here we're at the end of
|
||||
// the name
|
||||
// Skip over the pointer
|
||||
iUdp.read(); // we don't care about the returned byte
|
||||
// And set len so that we drop out of the name loop
|
||||
len = 0;
|
||||
}
|
||||
} while (len != 0);
|
||||
|
||||
// Check the type and class
|
||||
uint16_t answerType;
|
||||
uint16_t answerClass;
|
||||
iUdp.read((uint8_t*)&answerType, sizeof(answerType));
|
||||
iUdp.read((uint8_t*)&answerClass, sizeof(answerClass));
|
||||
|
||||
// Ignore the Time-To-Live as we don't do any caching
|
||||
for (int i =0; i < TTL_SIZE; i++)
|
||||
{
|
||||
iUdp.read(); // we don't care about the returned byte
|
||||
}
|
||||
|
||||
// And read out the length of this answer
|
||||
// Don't need header_flags anymore, so we can reuse it here
|
||||
iUdp.read((uint8_t*)&header_flags, sizeof(header_flags));
|
||||
|
||||
if ( (htons(answerType) == TYPE_A) && (htons(answerClass) == CLASS_IN) )
|
||||
{
|
||||
if (htons(header_flags) != 4)
|
||||
{
|
||||
// It's a weird size
|
||||
// Mark the entire packet as read
|
||||
iUdp.discardReceived();
|
||||
return -9;//INVALID_RESPONSE;
|
||||
}
|
||||
iUdp.read(aAddress.raw_address(), 4);
|
||||
return SUCCESS;
|
||||
}
|
||||
else
|
||||
{
|
||||
// This isn't an answer type we're after, move onto the next one
|
||||
for (uint16_t i =0; i < htons(header_flags); i++)
|
||||
{
|
||||
iUdp.read(); // we don't care about the returned byte
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Mark the entire packet as read
|
||||
iUdp.discardReceived();
|
||||
|
||||
// If we get here then we haven't found an answer
|
||||
return -10;//INVALID_RESPONSE;
|
||||
}
|
||||
|
||||
@@ -1,41 +0,0 @@
|
||||
// Arduino DNS client for Enc28J60-based Ethernet shield
|
||||
// (c) Copyright 2009-2010 MCQN Ltd.
|
||||
// Released under Apache License, version 2.0
|
||||
|
||||
#ifndef DNSClient_h
|
||||
#define DNSClient_h
|
||||
|
||||
#include "EthernetUdp.h"
|
||||
|
||||
class DNSClient
|
||||
{
|
||||
public:
|
||||
// ctor
|
||||
void begin(const IPAddress& aDNSServer);
|
||||
|
||||
/** Convert a numeric IP address string into a four-byte IP address.
|
||||
@param aIPAddrString IP address to convert
|
||||
@param aResult IPAddress structure to store the returned IP address
|
||||
@result 1 if aIPAddrString was successfully converted to an IP address,
|
||||
else error code
|
||||
*/
|
||||
int inet_aton(const char *aIPAddrString, IPAddress& aResult);
|
||||
|
||||
/** Resolve the given hostname to an IP address.
|
||||
@param aHostname Name to be resolved
|
||||
@param aResult IPAddress structure to store the returned IP address
|
||||
@result 1 if aIPAddrString was successfully converted to an IP address,
|
||||
else error code
|
||||
*/
|
||||
int getHostByName(const char* aHostname, IPAddress& aResult);
|
||||
|
||||
protected:
|
||||
uint16_t BuildRequest(const char* aName);
|
||||
uint16_t ProcessResponse(uint16_t aTimeout, IPAddress& aAddress);
|
||||
|
||||
IPAddress iDNSServer;
|
||||
uint16_t iRequestId;
|
||||
EthernetUDP iUdp;
|
||||
};
|
||||
|
||||
#endif
|
||||
@@ -1,559 +0,0 @@
|
||||
/*
|
||||
UIPEthernet.cpp - Arduino implementation of a uIP wrapper class.
|
||||
Copyright (c) 2013 Norbert Truchsess <norbert.truchsess@t-online.de>
|
||||
All rights reserved.
|
||||
|
||||
This program is free software: you can redistribute it and/or modify
|
||||
it under the terms of the GNU General Public License as published by
|
||||
the Free Software Foundation, either version 3 of the License, or
|
||||
(at your option) any later version.
|
||||
|
||||
This program is distributed in the hope that it will be useful,
|
||||
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
GNU General Public License for more details.
|
||||
|
||||
You should have received a copy of the GNU General Public License
|
||||
along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
#include <Arduino.h>
|
||||
#include "Ethernet.h"
|
||||
#include "Dns.h"
|
||||
#include "utility/Enc28J60Network.h"
|
||||
|
||||
extern "C"
|
||||
{
|
||||
#include "utility/uip-conf.h"
|
||||
#include "utility/uip.h"
|
||||
#include "utility/uip_arp.h"
|
||||
}
|
||||
|
||||
#define ETH_HDR ((struct uip_eth_hdr *)&uip_buf[0])
|
||||
|
||||
bool UIPEthernetClass::initialized = false;
|
||||
memhandle UIPEthernetClass::in_packet(NOBLOCK);
|
||||
memhandle UIPEthernetClass::uip_packet(NOBLOCK);
|
||||
uint8_t UIPEthernetClass::uip_hdrlen(0);
|
||||
uint8_t UIPEthernetClass::packetstate(0);
|
||||
|
||||
IPAddress UIPEthernetClass::_dnsServerAddress;
|
||||
DhcpClass* UIPEthernetClass::_dhcp(NULL);
|
||||
|
||||
unsigned long UIPEthernetClass::periodic_timer;
|
||||
unsigned long UIPEthernetClass::arp_timer;
|
||||
|
||||
// Because uIP isn't encapsulated within a class we have to use global
|
||||
// variables, so we can only have one TCP/IP stack per program.
|
||||
|
||||
UIPEthernetClass::UIPEthernetClass()
|
||||
{
|
||||
}
|
||||
|
||||
void UIPEthernetClass::init(uint8_t csPin)
|
||||
{
|
||||
Enc28J60Network::setCsPin(csPin);
|
||||
}
|
||||
|
||||
#if UIP_UDP
|
||||
void
|
||||
UIPEthernetClass::setHostname(const char* hostname)
|
||||
{
|
||||
if (_dhcp == NULL) {
|
||||
_dhcp = new DhcpClass();
|
||||
}
|
||||
_dhcp->setHostname(hostname);
|
||||
}
|
||||
|
||||
int
|
||||
UIPEthernetClass::begin(const uint8_t* mac, unsigned long timeout, unsigned long responseTimeout)
|
||||
{
|
||||
if (_dhcp == NULL) {
|
||||
_dhcp = new DhcpClass();
|
||||
}
|
||||
|
||||
// Initialise the basic info
|
||||
init(mac);
|
||||
|
||||
// Now try to get our config info from a DHCP server
|
||||
int ret = _dhcp->beginWithDHCP((uint8_t*)mac, timeout, responseTimeout);
|
||||
if(ret == 1)
|
||||
{
|
||||
// We've successfully found a DHCP server and got our configuration info, so set things
|
||||
// accordingly
|
||||
configure(_dhcp->getLocalIp(),_dhcp->getDnsServerIp(),_dhcp->getGatewayIp(),_dhcp->getSubnetMask());
|
||||
}
|
||||
return ret;
|
||||
}
|
||||
#endif
|
||||
|
||||
void
|
||||
UIPEthernetClass::begin(const uint8_t* mac, IPAddress ip)
|
||||
{
|
||||
IPAddress dns = ip;
|
||||
dns[3] = 1;
|
||||
begin(mac, ip, dns);
|
||||
}
|
||||
|
||||
void
|
||||
UIPEthernetClass::begin(const uint8_t* mac, IPAddress ip, IPAddress dns)
|
||||
{
|
||||
IPAddress gateway = ip;
|
||||
gateway[3] = 1;
|
||||
begin(mac, ip, dns, gateway);
|
||||
}
|
||||
|
||||
void
|
||||
UIPEthernetClass::begin(const uint8_t* mac, IPAddress ip, IPAddress dns, IPAddress gateway)
|
||||
{
|
||||
IPAddress subnet(255, 255, 255, 0);
|
||||
begin(mac, ip, dns, gateway, subnet);
|
||||
}
|
||||
|
||||
void
|
||||
UIPEthernetClass::begin(const uint8_t* mac, IPAddress ip, IPAddress dns, IPAddress gateway, IPAddress subnet)
|
||||
{
|
||||
init(mac);
|
||||
configure(ip,dns,gateway,subnet);
|
||||
}
|
||||
|
||||
void UIPEthernetClass::end()
|
||||
{
|
||||
//close all clients
|
||||
for (int i = 0; i < UIP_CONNS; i++)
|
||||
{
|
||||
if (EthernetClient::all_data[i].state) {
|
||||
EthernetClient client(&EthernetClient::all_data[i]);
|
||||
client.stop();
|
||||
}
|
||||
}
|
||||
// handle clients closings
|
||||
uint32_t st = millis();
|
||||
while (millis() - st < 3000)
|
||||
{
|
||||
tick();
|
||||
}
|
||||
initialized = false;
|
||||
configure(INADDR_NONE, INADDR_NONE, INADDR_NONE, INADDR_NONE);
|
||||
}
|
||||
|
||||
int UIPEthernetClass::maintain(){
|
||||
if (!initialized)
|
||||
return 0;
|
||||
tick();
|
||||
int rc = DHCP_CHECK_NONE;
|
||||
#if UIP_UDP
|
||||
if(_dhcp != NULL){
|
||||
//we have a pointer to dhcp, use it
|
||||
rc = _dhcp->checkLease();
|
||||
switch ( rc ){
|
||||
case DHCP_CHECK_NONE:
|
||||
//nothing done
|
||||
break;
|
||||
case DHCP_CHECK_RENEW_OK:
|
||||
case DHCP_CHECK_REBIND_OK:
|
||||
//we might have got a new IP.
|
||||
configure(_dhcp->getLocalIp(),_dhcp->getDnsServerIp(),_dhcp->getGatewayIp(),_dhcp->getSubnetMask());
|
||||
break;
|
||||
default:
|
||||
//this is actually a error, it will retry though
|
||||
break;
|
||||
}
|
||||
}
|
||||
#endif
|
||||
return rc;
|
||||
}
|
||||
|
||||
EthernetLinkStatus UIPEthernetClass::linkStatus()
|
||||
{
|
||||
if (!Enc28J60Network::getrev())
|
||||
return Unknown;
|
||||
return Enc28J60Network::linkStatus() ? LinkON : LinkOFF;
|
||||
}
|
||||
|
||||
EthernetHardwareStatus UIPEthernetClass::hardwareStatus()
|
||||
{
|
||||
if (!Enc28J60Network::getrev())
|
||||
return EthernetNoHardware;
|
||||
return EthernetENC28J60;
|
||||
}
|
||||
|
||||
uint8_t* UIPEthernetClass::macAddress(uint8_t* mac)
|
||||
{
|
||||
memcpy(mac, uip_ethaddr.addr, 6);
|
||||
return mac;
|
||||
}
|
||||
|
||||
IPAddress UIPEthernetClass::localIP()
|
||||
{
|
||||
IPAddress ret;
|
||||
uip_ipaddr_t a;
|
||||
uip_gethostaddr(a);
|
||||
return ip_addr_uip(a);
|
||||
}
|
||||
|
||||
IPAddress UIPEthernetClass::subnetMask()
|
||||
{
|
||||
IPAddress ret;
|
||||
uip_ipaddr_t a;
|
||||
uip_getnetmask(a);
|
||||
return ip_addr_uip(a);
|
||||
}
|
||||
|
||||
IPAddress UIPEthernetClass::gatewayIP()
|
||||
{
|
||||
IPAddress ret;
|
||||
uip_ipaddr_t a;
|
||||
uip_getdraddr(a);
|
||||
return ip_addr_uip(a);
|
||||
}
|
||||
|
||||
IPAddress UIPEthernetClass::dnsServerIP()
|
||||
{
|
||||
return _dnsServerAddress;
|
||||
}
|
||||
|
||||
IPAddress UIPEthernetClass::dnsIP(int n) {
|
||||
return (n == 0) ? _dnsServerAddress : IPAddress();
|
||||
}
|
||||
|
||||
int UIPEthernetClass::hostByName(const char* hostname, IPAddress& result)
|
||||
{
|
||||
// Look up the host first
|
||||
int ret = 0;
|
||||
#if UIP_UDP
|
||||
DNSClient dns;
|
||||
dns.begin(_dnsServerAddress);
|
||||
ret = dns.getHostByName(hostname, result);
|
||||
#endif
|
||||
return ret;
|
||||
}
|
||||
|
||||
void
|
||||
UIPEthernetClass::tick()
|
||||
{
|
||||
if (!initialized)
|
||||
return;
|
||||
|
||||
// run ARP table cleanup every 10 seconds as required by a comment in uip_arp.h
|
||||
if (millis() - arp_timer > 10000) {
|
||||
arp_timer = millis();
|
||||
uip_arp_timer();
|
||||
}
|
||||
|
||||
if (in_packet == NOBLOCK)
|
||||
{
|
||||
in_packet = Enc28J60Network::receivePacket();
|
||||
#ifdef UIPETHERNET_DEBUG
|
||||
if (in_packet != NOBLOCK)
|
||||
{
|
||||
Serial.println(F("--------------\npacket received"));
|
||||
}
|
||||
#endif
|
||||
}
|
||||
if (in_packet != NOBLOCK)
|
||||
{
|
||||
packetstate = UIPETHERNET_FREEPACKET;
|
||||
uip_len = Enc28J60Network::blockSize(in_packet);
|
||||
if (uip_len > 0)
|
||||
{
|
||||
Enc28J60Network::readPacket(in_packet,0,(uint8_t*)uip_buf,UIP_BUFSIZE);
|
||||
if (ETH_HDR ->type == HTONS(UIP_ETHTYPE_IP))
|
||||
{
|
||||
uip_packet = in_packet; //required for upper_layer_checksum of in_packet!
|
||||
#ifdef UIPETHERNET_DEBUG
|
||||
Serial.print(F("readPacket type IP, uip_len: "));
|
||||
Serial.print(uip_len);
|
||||
Serial.print(F(", bits: "));
|
||||
Serial.println(BUF->flags, BIN);
|
||||
#endif
|
||||
uip_arp_ipin();
|
||||
uip_input();
|
||||
if (uip_len > 0)
|
||||
{
|
||||
uip_arp_out();
|
||||
network_send();
|
||||
}
|
||||
}
|
||||
else if (ETH_HDR ->type == HTONS(UIP_ETHTYPE_ARP))
|
||||
{
|
||||
#ifdef UIPETHERNET_DEBUG
|
||||
Serial.print(F("readPacket type ARP, uip_len: "));
|
||||
Serial.println(uip_len);
|
||||
#endif
|
||||
uip_arp_arpin();
|
||||
if (uip_len > 0)
|
||||
{
|
||||
network_send();
|
||||
}
|
||||
}
|
||||
}
|
||||
if (in_packet != NOBLOCK && (packetstate & UIPETHERNET_FREEPACKET))
|
||||
{
|
||||
#ifdef UIPETHERNET_DEBUG
|
||||
Serial.println(F("freeing received packet"));
|
||||
#endif
|
||||
Enc28J60Network::freePacket();
|
||||
in_packet = NOBLOCK;
|
||||
}
|
||||
}
|
||||
|
||||
unsigned long now = millis();
|
||||
|
||||
if ((long)( now - periodic_timer ) >= 0)
|
||||
{
|
||||
periodic_timer = now + UIP_PERIODIC_TIMER;
|
||||
|
||||
for (int i = 0; i < UIP_CONNS; i++)
|
||||
{
|
||||
uip_periodic(i);
|
||||
// If the above function invocation resulted in data that
|
||||
// should be sent out on the Enc28J60Network, the global variable
|
||||
// uip_len is set to a value > 0.
|
||||
if (uip_len > 0)
|
||||
{
|
||||
uip_arp_out();
|
||||
network_send();
|
||||
}
|
||||
}
|
||||
#if UIP_UDP
|
||||
for (int i = 0; i < UIP_UDP_CONNS; i++)
|
||||
{
|
||||
uip_udp_periodic(i);
|
||||
// If the above function invocation resulted in data that
|
||||
// should be sent out on the Enc28J60Network, the global variable
|
||||
// uip_len is set to a value > 0. */
|
||||
if (uip_len > 0)
|
||||
{
|
||||
EthernetUDP::_send(&uip_udp_conns[i]);
|
||||
}
|
||||
}
|
||||
#endif /* UIP_UDP */
|
||||
}
|
||||
}
|
||||
|
||||
boolean UIPEthernetClass::network_send()
|
||||
{
|
||||
if (packetstate & UIPETHERNET_SENDPACKET)
|
||||
{
|
||||
#ifdef UIPETHERNET_DEBUG
|
||||
Serial.print(F("Enc28J60Network_send uip_packet: "));
|
||||
Serial.print(uip_packet);
|
||||
Serial.print(F(", hdrlen: "));
|
||||
Serial.println(uip_hdrlen);
|
||||
#endif
|
||||
Enc28J60Network::writePacket(uip_packet, UIP_SENDBUFFER_OFFSET,uip_buf,uip_hdrlen);
|
||||
packetstate &= ~ UIPETHERNET_SENDPACKET;
|
||||
goto sendandfree;
|
||||
}
|
||||
uip_packet = Enc28J60Network::allocBlock(uip_len + UIP_SENDBUFFER_OFFSET + UIP_SENDBUFFER_PADDING);
|
||||
if (uip_packet != NOBLOCK)
|
||||
{
|
||||
#ifdef UIPETHERNET_DEBUG
|
||||
Serial.print(F("Enc28J60Network_send uip_buf (uip_len): "));
|
||||
Serial.print(uip_len);
|
||||
Serial.print(F(", packet: "));
|
||||
Serial.print(uip_packet);
|
||||
Serial.print(F(", bits: "));
|
||||
Serial.println(BUF->flags, BIN);
|
||||
#endif
|
||||
Enc28J60Network::writePacket(uip_packet, UIP_SENDBUFFER_OFFSET,uip_buf,uip_len);
|
||||
goto sendandfree;
|
||||
}
|
||||
return false;
|
||||
sendandfree:
|
||||
bool success = Enc28J60Network::sendPacket(uip_packet);
|
||||
Enc28J60Network::freeBlock(uip_packet);
|
||||
uip_packet = NOBLOCK;
|
||||
return success;
|
||||
}
|
||||
|
||||
void UIPEthernetClass::init(const uint8_t* mac) {
|
||||
periodic_timer = millis() + UIP_PERIODIC_TIMER;
|
||||
|
||||
initialized = Enc28J60Network::init((uint8_t*)mac);
|
||||
uip_seteth_addr(mac);
|
||||
|
||||
uip_init();
|
||||
uip_arp_init();
|
||||
}
|
||||
|
||||
void UIPEthernetClass::configure(IPAddress ip, IPAddress dns, IPAddress gateway, IPAddress subnet) {
|
||||
uip_ipaddr_t ipaddr;
|
||||
|
||||
uip_ip_addr(ipaddr, ip);
|
||||
uip_sethostaddr(ipaddr);
|
||||
|
||||
uip_ip_addr(ipaddr, gateway);
|
||||
uip_setdraddr(ipaddr);
|
||||
|
||||
uip_ip_addr(ipaddr, subnet);
|
||||
uip_setnetmask(ipaddr);
|
||||
|
||||
_dnsServerAddress = dns;
|
||||
}
|
||||
|
||||
void
|
||||
UIPEthernetClass::call_yield()
|
||||
{
|
||||
static bool in_yield;
|
||||
static uint32_t last_call_millis;
|
||||
if (!in_yield && millis() - last_call_millis > 0)
|
||||
{
|
||||
last_call_millis = millis();
|
||||
in_yield = true;
|
||||
yield();
|
||||
in_yield = false;
|
||||
}
|
||||
}
|
||||
|
||||
/*---------------------------------------------------------------------------*/
|
||||
uint16_t
|
||||
UIPEthernetClass::chksum(uint16_t sum, const uint8_t *data, uint16_t len)
|
||||
{
|
||||
uint16_t t;
|
||||
const uint8_t *dataptr;
|
||||
const uint8_t *last_byte;
|
||||
|
||||
dataptr = data;
|
||||
last_byte = data + len - 1;
|
||||
|
||||
while(dataptr < last_byte) { /* At least two more bytes */
|
||||
t = (dataptr[0] << 8) + dataptr[1];
|
||||
sum += t;
|
||||
if(sum < t) {
|
||||
sum++; /* carry */
|
||||
}
|
||||
dataptr += 2;
|
||||
}
|
||||
|
||||
if(dataptr == last_byte) {
|
||||
t = (dataptr[0] << 8) + 0;
|
||||
sum += t;
|
||||
if(sum < t) {
|
||||
sum++; /* carry */
|
||||
}
|
||||
}
|
||||
|
||||
/* Return sum in host byte order. */
|
||||
return sum;
|
||||
}
|
||||
|
||||
/*---------------------------------------------------------------------------*/
|
||||
|
||||
uint16_t
|
||||
UIPEthernetClass::ipchksum(void)
|
||||
{
|
||||
uint16_t sum;
|
||||
|
||||
sum = chksum(0, &uip_buf[UIP_LLH_LEN], UIP_IPH_LEN);
|
||||
return (sum == 0) ? 0xffff : htons(sum);
|
||||
}
|
||||
|
||||
/*---------------------------------------------------------------------------*/
|
||||
uint16_t
|
||||
#if UIP_UDP
|
||||
UIPEthernetClass::upper_layer_chksum(uint8_t proto)
|
||||
#else
|
||||
uip_tcpchksum(void)
|
||||
#endif
|
||||
{
|
||||
uint16_t upper_layer_len;
|
||||
uint16_t sum;
|
||||
|
||||
#if UIP_CONF_IPV6
|
||||
upper_layer_len = (((u16_t)(BUF->len[0]) << 8) + BUF->len[1]);
|
||||
#else /* UIP_CONF_IPV6 */
|
||||
upper_layer_len = (((u16_t)(BUF->len[0]) << 8) + BUF->len[1]) - UIP_IPH_LEN;
|
||||
#endif /* UIP_CONF_IPV6 */
|
||||
|
||||
/* First sum pseudoheader. */
|
||||
|
||||
/* IP protocol and length fields. This addition cannot carry. */
|
||||
#if UIP_UDP
|
||||
sum = upper_layer_len + proto;
|
||||
#else
|
||||
sum = upper_layer_len + UIP_PROTO_TCP;
|
||||
#endif
|
||||
/* Sum IP source and destination addresses. */
|
||||
sum = UIPEthernetClass::chksum(sum, (u8_t *)&BUF->srcipaddr[0], 2 * sizeof(uip_ipaddr_t));
|
||||
|
||||
uint8_t upper_layer_memlen;
|
||||
#if UIP_UDP
|
||||
switch(proto)
|
||||
{
|
||||
// case UIP_PROTO_ICMP:
|
||||
// case UIP_PROTO_ICMP6:
|
||||
// upper_layer_memlen = upper_layer_len;
|
||||
// break;
|
||||
case UIP_PROTO_UDP:
|
||||
upper_layer_memlen = UIP_UDPH_LEN;
|
||||
break;
|
||||
default:
|
||||
// case UIP_PROTO_TCP:
|
||||
#endif
|
||||
upper_layer_memlen = (BUF->tcpoffset >> 4) << 2;
|
||||
#if UIP_UDP
|
||||
break;
|
||||
}
|
||||
#endif
|
||||
sum = UIPEthernetClass::chksum(sum, &uip_buf[UIP_IPH_LEN + UIP_LLH_LEN], upper_layer_memlen);
|
||||
#ifdef UIPETHERNET_DEBUG_CHKSUM
|
||||
Serial.print(F("chksum uip_buf["));
|
||||
Serial.print(UIP_IPH_LEN + UIP_LLH_LEN);
|
||||
Serial.print(F("-"));
|
||||
Serial.print(UIP_IPH_LEN + UIP_LLH_LEN + upper_layer_memlen);
|
||||
Serial.print(F("]: "));
|
||||
Serial.println(htons(sum),HEX);
|
||||
#endif
|
||||
if (upper_layer_memlen < upper_layer_len)
|
||||
{
|
||||
sum = Enc28J60Network::chksum(
|
||||
sum,
|
||||
UIPEthernetClass::uip_packet,
|
||||
(UIPEthernetClass::packetstate & UIPETHERNET_SENDPACKET ? UIP_IPH_LEN + UIP_LLH_LEN + UIP_SENDBUFFER_OFFSET : UIP_IPH_LEN + UIP_LLH_LEN) + upper_layer_memlen,
|
||||
upper_layer_len - upper_layer_memlen
|
||||
);
|
||||
#ifdef UIPETHERNET_DEBUG_CHKSUM
|
||||
Serial.print(F("chksum uip_packet("));
|
||||
Serial.print(uip_packet);
|
||||
Serial.print(F(")["));
|
||||
Serial.print(UIP_IPH_LEN + UIP_LLH_LEN + upper_layer_memlen);
|
||||
Serial.print(F("-"));
|
||||
Serial.print(UIP_IPH_LEN + UIP_LLH_LEN + upper_layer_len);
|
||||
Serial.print(F("]: "));
|
||||
Serial.println(htons(sum),HEX);
|
||||
#endif
|
||||
}
|
||||
return (sum == 0) ? 0xffff : htons(sum);
|
||||
}
|
||||
|
||||
uint16_t
|
||||
uip_ipchksum(void)
|
||||
{
|
||||
return UIPEthernetClass::ipchksum();
|
||||
}
|
||||
|
||||
#if UIP_UDP
|
||||
uint16_t
|
||||
uip_tcpchksum(void)
|
||||
{
|
||||
uint16_t sum = UIPEthernetClass::upper_layer_chksum(UIP_PROTO_TCP);
|
||||
return sum;
|
||||
}
|
||||
|
||||
uint16_t
|
||||
uip_udpchksum(void)
|
||||
{
|
||||
uint16_t sum = UIPEthernetClass::upper_layer_chksum(UIP_PROTO_UDP);
|
||||
return sum;
|
||||
}
|
||||
#endif
|
||||
|
||||
UIPEthernetClass Ethernet;
|
||||
|
||||
extern "C" void serialPrint(int i);
|
||||
void serialPrint(int i) {
|
||||
Serial.println(i);
|
||||
Serial.flush();
|
||||
}
|
||||
@@ -1,161 +0,0 @@
|
||||
/*
|
||||
UIPEthernet.h - Arduino implementation of a uIP wrapper class.
|
||||
Copyright (c) 2013 Norbert Truchsess <norbert.truchsess@t-online.de>
|
||||
All rights reserved.
|
||||
|
||||
This program is free software: you can redistribute it and/or modify
|
||||
it under the terms of the GNU General Public License as published by
|
||||
the Free Software Foundation, either version 3 of the License, or
|
||||
(at your option) any later version.
|
||||
|
||||
This program is distributed in the hope that it will be useful,
|
||||
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
GNU General Public License for more details.
|
||||
|
||||
You should have received a copy of the GNU General Public License
|
||||
along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
#ifndef UIPETHERNET_H
|
||||
#define UIPETHERNET_H
|
||||
|
||||
//#define UIPETHERNET_DEBUG
|
||||
//#define UIPETHERNET_DEBUG_CHKSUM
|
||||
//#define UIPETHERNET_DEBUG_UDP
|
||||
//#define UIPETHERNET_DEBUG_CLIENT
|
||||
|
||||
#include <Arduino.h>
|
||||
#include "Dhcp.h"
|
||||
#include <IPAddress.h>
|
||||
#include "utility/Enc28J60Network.h"
|
||||
#include "EthernetClient.h"
|
||||
#include "EthernetServer.h"
|
||||
#include "EthernetUdp.h"
|
||||
|
||||
extern "C"
|
||||
{
|
||||
#include "utility/uip.h"
|
||||
}
|
||||
|
||||
#define UIPETHERNET_FREEPACKET 1
|
||||
#define UIPETHERNET_SENDPACKET 2
|
||||
#define UIPETHERNET_BUFFERREAD 4
|
||||
|
||||
#define uip_ip_addr(addr, ip) do { \
|
||||
((u16_t *)(addr))[0] = HTONS(((ip[0]) << 8) | (ip[1])); \
|
||||
((u16_t *)(addr))[1] = HTONS(((ip[2]) << 8) | (ip[3])); \
|
||||
} while(0)
|
||||
|
||||
#define ip_addr_uip(a) IPAddress(a[0] & 0xFF, a[0] >> 8 , a[1] & 0xFF, a[1] >> 8) //TODO this is not IPV6 capable
|
||||
|
||||
#define uip_seteth_addr(eaddr) do {uip_ethaddr.addr[0] = eaddr[0]; \
|
||||
uip_ethaddr.addr[1] = eaddr[1];\
|
||||
uip_ethaddr.addr[2] = eaddr[2];\
|
||||
uip_ethaddr.addr[3] = eaddr[3];\
|
||||
uip_ethaddr.addr[4] = eaddr[4];\
|
||||
uip_ethaddr.addr[5] = eaddr[5];} while(0)
|
||||
|
||||
#define BUF ((struct uip_tcpip_hdr *)&uip_buf[UIP_LLH_LEN])
|
||||
|
||||
enum EthernetLinkStatus {
|
||||
Unknown,
|
||||
LinkON,
|
||||
LinkOFF
|
||||
};
|
||||
|
||||
enum EthernetHardwareStatus {
|
||||
EthernetNoHardware,
|
||||
EthernetW5100,
|
||||
EthernetW5200,
|
||||
EthernetW5500,
|
||||
EthernetENC28J60 = 10
|
||||
};
|
||||
|
||||
class UIPEthernetClass
|
||||
{
|
||||
public:
|
||||
UIPEthernetClass();
|
||||
|
||||
void init(uint8_t csPin);
|
||||
|
||||
int begin(const uint8_t* mac, unsigned long timeout = 60000, unsigned long responseTimeout = 4000);
|
||||
void begin(const uint8_t* mac, IPAddress ip);
|
||||
void begin(const uint8_t* mac, IPAddress ip, IPAddress dns);
|
||||
void begin(const uint8_t* mac, IPAddress ip, IPAddress dns, IPAddress gateway);
|
||||
void begin(const uint8_t* mac, IPAddress ip, IPAddress dns, IPAddress gateway, IPAddress subnet);
|
||||
|
||||
void end();
|
||||
|
||||
// maintain() must be called at regular intervals to process the incoming serial
|
||||
// data and issue IP events to the sketch. It does not return until all IP
|
||||
// events have been processed. Renews dhcp-lease if required.
|
||||
int maintain();
|
||||
|
||||
EthernetLinkStatus linkStatus();
|
||||
EthernetHardwareStatus hardwareStatus();
|
||||
|
||||
uint8_t* macAddress(uint8_t* mac);
|
||||
void MACAddress(uint8_t *mac_address) { macAddress(mac_address); }
|
||||
|
||||
IPAddress localIP();
|
||||
IPAddress subnetMask();
|
||||
IPAddress gatewayIP();
|
||||
IPAddress dnsServerIP();
|
||||
IPAddress dnsIP(int n = 0);
|
||||
|
||||
void setDnsServerIP(const IPAddress dns_server) { _dnsServerAddress = dns_server; }
|
||||
void setDNS(IPAddress dns_server) { _dnsServerAddress = dns_server; }
|
||||
|
||||
void setHostname(const char* hostname); // only the pointer is stored!
|
||||
|
||||
int hostByName(const char* hostname, IPAddress& result);
|
||||
|
||||
private:
|
||||
static bool initialized;
|
||||
static memhandle in_packet;
|
||||
static memhandle uip_packet;
|
||||
static uint8_t uip_hdrlen;
|
||||
static uint8_t packetstate;
|
||||
|
||||
static IPAddress _dnsServerAddress;
|
||||
static DhcpClass* _dhcp;
|
||||
|
||||
static unsigned long periodic_timer;
|
||||
static unsigned long arp_timer;
|
||||
|
||||
static void init(const uint8_t* mac);
|
||||
static void configure(IPAddress ip, IPAddress dns, IPAddress gateway, IPAddress subnet);
|
||||
|
||||
static void tick();
|
||||
|
||||
static boolean network_send();
|
||||
|
||||
static void call_yield();
|
||||
|
||||
friend class EthernetServer;
|
||||
|
||||
friend class EthernetClient;
|
||||
|
||||
friend class EthernetUDP;
|
||||
|
||||
static uint16_t chksum(uint16_t sum, const uint8_t* data, uint16_t len);
|
||||
static uint16_t ipchksum(void);
|
||||
#if UIP_UDP
|
||||
static uint16_t upper_layer_chksum(uint8_t proto);
|
||||
#endif
|
||||
friend uint16_t uip_ipchksum(void);
|
||||
friend uint16_t uip_tcpchksum(void);
|
||||
friend uint16_t uip_udpchksum(void);
|
||||
|
||||
friend void uipclient_appcall(void);
|
||||
friend void uipudp_appcall(void);
|
||||
|
||||
#if UIP_CONF_IPV6
|
||||
uint16_t uip_icmp6chksum(void);
|
||||
#endif /* UIP_CONF_IPV6 */
|
||||
};
|
||||
|
||||
extern UIPEthernetClass Ethernet;
|
||||
|
||||
#endif
|
||||
@@ -1,663 +0,0 @@
|
||||
/*
|
||||
UIPClient.cpp - Arduino implementation of a uIP wrapper class.
|
||||
Copyright (c) 2013 Norbert Truchsess <norbert.truchsess@t-online.de>
|
||||
All rights reserved.
|
||||
|
||||
This program is free software: you can redistribute it and/or modify
|
||||
it under the terms of the GNU General Public License as published by
|
||||
the Free Software Foundation, either version 3 of the License, or
|
||||
(at your option) any later version.
|
||||
|
||||
This program is distributed in the hope that it will be useful,
|
||||
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
GNU General Public License for more details.
|
||||
|
||||
You should have received a copy of the GNU General Public License
|
||||
along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
extern "C"
|
||||
{
|
||||
#include "utility/uip-conf.h"
|
||||
#include "utility/uip.h"
|
||||
#include "utility/uip_arp.h"
|
||||
}
|
||||
#include "Ethernet.h"
|
||||
#include "EthernetClient.h"
|
||||
|
||||
#define UIP_TCP_PHYH_LEN UIP_LLH_LEN+UIP_IPTCPH_LEN
|
||||
|
||||
#define UIPClient EthernetClient // to not pollute source code history with the rename
|
||||
|
||||
uip_userdata_t UIPClient::all_data[UIP_CONNS];
|
||||
|
||||
UIPClient::UIPClient() :
|
||||
data(NULL)
|
||||
{
|
||||
}
|
||||
|
||||
UIPClient::UIPClient(uip_userdata_t* conn_data) :
|
||||
data(conn_data)
|
||||
{
|
||||
}
|
||||
|
||||
int
|
||||
UIPClient::connect(IPAddress ip, uint16_t port)
|
||||
{
|
||||
stop();
|
||||
uip_ipaddr_t ipaddr;
|
||||
uip_ip_addr(ipaddr, ip);
|
||||
struct uip_conn* conn = uip_connect(&ipaddr, htons(port));
|
||||
if (conn)
|
||||
{
|
||||
#if UIP_CONNECT_TIMEOUT > 0
|
||||
uint32_t timeout = millis() + connectTimeout;
|
||||
#endif
|
||||
while((conn->tcpstateflags & UIP_TS_MASK) != UIP_CLOSED)
|
||||
{
|
||||
UIPEthernetClass::tick();
|
||||
if ((conn->tcpstateflags & UIP_TS_MASK) == UIP_ESTABLISHED)
|
||||
{
|
||||
data = (uip_userdata_t*) conn->appstate;
|
||||
#ifdef UIPETHERNET_DEBUG_CLIENT
|
||||
Serial.print(F("connected, state: "));
|
||||
Serial.print(data->state);
|
||||
Serial.print(F(", first packet in: "));
|
||||
Serial.println(data->packets_in[0]);
|
||||
#endif
|
||||
return 1;
|
||||
}
|
||||
#if UIP_CONNECT_TIMEOUT > 0
|
||||
if (((int32_t)(millis() - timeout)) > 0)
|
||||
{
|
||||
conn->tcpstateflags = UIP_CLOSED;
|
||||
break;
|
||||
}
|
||||
#endif
|
||||
}
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
int
|
||||
UIPClient::connect(const char *host, uint16_t port)
|
||||
{
|
||||
// Look up the host first
|
||||
int ret = 0;
|
||||
#if UIP_UDP
|
||||
IPAddress remote_addr;
|
||||
ret = Ethernet.hostByName(host, remote_addr);
|
||||
if (ret == 1) {
|
||||
return connect(remote_addr, port);
|
||||
}
|
||||
#endif
|
||||
return ret;
|
||||
}
|
||||
|
||||
void
|
||||
UIPClient::stop()
|
||||
{
|
||||
if (data && data->state)
|
||||
{
|
||||
#ifdef UIPETHERNET_DEBUG_CLIENT
|
||||
Serial.println(F("before stop(), with data"));
|
||||
_dumpAllData();
|
||||
#endif
|
||||
if (data->state & UIP_CLIENT_REMOTECLOSED)
|
||||
{
|
||||
data->state = 0;
|
||||
}
|
||||
else
|
||||
{
|
||||
flush();
|
||||
data->state |= UIP_CLIENT_CLOSE;
|
||||
}
|
||||
_flushBlocks(data->packets_in);
|
||||
#ifdef UIPETHERNET_DEBUG_CLIENT
|
||||
Serial.println(F("after stop()"));
|
||||
_dumpAllData();
|
||||
#endif
|
||||
}
|
||||
#ifdef UIPETHERNET_DEBUG_CLIENT
|
||||
else
|
||||
{
|
||||
Serial.println(F("stop(), data: NULL"));
|
||||
}
|
||||
#endif
|
||||
data = NULL;
|
||||
UIPEthernetClass::tick();
|
||||
}
|
||||
|
||||
uint8_t
|
||||
UIPClient::connected()
|
||||
{
|
||||
return (data && (data->packets_in[0] != NOBLOCK || (data->state & UIP_CLIENT_CONNECTED))) ? 1 : 0;
|
||||
}
|
||||
|
||||
bool
|
||||
UIPClient::operator==(const UIPClient& rhs)
|
||||
{
|
||||
return data && rhs.data && (data == rhs.data);
|
||||
}
|
||||
|
||||
UIPClient::operator bool()
|
||||
{
|
||||
UIPEthernetClass::tick();
|
||||
return data && (!(data->state & UIP_CLIENT_REMOTECLOSED) || data->packets_in[0] != NOBLOCK);
|
||||
}
|
||||
|
||||
size_t
|
||||
UIPClient::write(uint8_t c)
|
||||
{
|
||||
return write(&c, 1);
|
||||
}
|
||||
|
||||
size_t
|
||||
UIPClient::write(const uint8_t *buf, size_t size)
|
||||
{
|
||||
uip_userdata_t* u = data;
|
||||
int remain = size;
|
||||
uint16_t written;
|
||||
#if UIP_WRITE_TIMEOUT > 0
|
||||
uint32_t timeout_start = millis();
|
||||
#endif
|
||||
repeat:
|
||||
UIPEthernetClass::tick();
|
||||
if (u && u->state && !(u->state & (UIP_CLIENT_CLOSE | UIP_CLIENT_REMOTECLOSED)))
|
||||
{
|
||||
uint8_t p = _currentBlock(u->packets_out);
|
||||
if (u->packets_out[p] == NOBLOCK)
|
||||
{
|
||||
newpacket:
|
||||
u->packets_out[p] = Enc28J60Network::allocBlock(UIP_SOCKET_DATALEN);
|
||||
if (u->packets_out[p] == NOBLOCK)
|
||||
{
|
||||
#if UIP_WRITE_TIMEOUT > 0
|
||||
if (millis() - timeout_start > UIP_WRITE_TIMEOUT)
|
||||
{
|
||||
setWriteError();
|
||||
goto ready;
|
||||
}
|
||||
#endif
|
||||
Ethernet.call_yield();
|
||||
goto repeat;
|
||||
}
|
||||
u->out_pos = 0;
|
||||
}
|
||||
#ifdef UIPETHERNET_DEBUG_CLIENT
|
||||
Serial.print(F("UIPClient.write: writePacket("));
|
||||
Serial.print(u->packets_out[p]);
|
||||
Serial.print(F(") pos: "));
|
||||
Serial.print(u->out_pos);
|
||||
Serial.print(F(", buf["));
|
||||
Serial.print(size-remain);
|
||||
Serial.print(F("-"));
|
||||
Serial.print(remain);
|
||||
Serial.print(F("]: '"));
|
||||
Serial.write((uint8_t*)buf+size-remain,remain);
|
||||
Serial.println(F("'"));
|
||||
#endif
|
||||
written = Enc28J60Network::writePacket(u->packets_out[p],u->out_pos,(uint8_t*)buf+size-remain,remain);
|
||||
remain -= written;
|
||||
u->out_pos+=written;
|
||||
if (remain > 0)
|
||||
{
|
||||
if (p == 0) // block 0 just filled, start sending immediately
|
||||
{
|
||||
flush();
|
||||
goto repeat;
|
||||
}
|
||||
if (p == UIP_SOCKET_NUMPACKETS-1)
|
||||
{
|
||||
#if UIP_WRITE_TIMEOUT > 0
|
||||
if (millis() - timeout_start > UIP_WRITE_TIMEOUT)
|
||||
{
|
||||
setWriteError();
|
||||
goto ready;
|
||||
}
|
||||
#endif
|
||||
Ethernet.call_yield();
|
||||
goto repeat;
|
||||
}
|
||||
p++;
|
||||
goto newpacket;
|
||||
}
|
||||
ready:
|
||||
return size-remain;
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
int
|
||||
UIPClient::availableForWrite()
|
||||
{
|
||||
const int MAX_AVAILABLE = UIP_SOCKET_DATALEN * UIP_SOCKET_NUMPACKETS;
|
||||
UIPEthernetClass::tick();
|
||||
if (data->packets_out[0] == NOBLOCK)
|
||||
return MAX_AVAILABLE;
|
||||
uint8_t p = _currentBlock(data->packets_out);
|
||||
int used = UIP_SOCKET_DATALEN * p + data->out_pos;
|
||||
return MAX_AVAILABLE - used;
|
||||
}
|
||||
|
||||
void
|
||||
UIPClient::flush()
|
||||
{
|
||||
UIPEthernetClass::tick();
|
||||
|
||||
if (data && data->packets_out[0] != NOBLOCK)
|
||||
{
|
||||
struct uip_conn* conn = &uip_conns[data->conn_index];
|
||||
if (!uip_outstanding(conn))
|
||||
{
|
||||
uip_poll_conn(conn);
|
||||
if (uip_len > 0)
|
||||
{
|
||||
uip_arp_out();
|
||||
UIPEthernetClass::network_send();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
int
|
||||
UIPClient::available()
|
||||
{
|
||||
if (!(*this))
|
||||
return 0;
|
||||
|
||||
int len = 0;
|
||||
for (uint8_t i = 0; i < UIP_SOCKET_NUMPACKETS; i++)
|
||||
{
|
||||
len += Enc28J60Network::blockSize(data->packets_in[i]);
|
||||
}
|
||||
|
||||
// if sketch checks for incoming data and there are unsent data, flush the transmit buffer
|
||||
if (!len)
|
||||
flush();
|
||||
return len;
|
||||
}
|
||||
|
||||
int
|
||||
UIPClient::read(uint8_t *buf, size_t size)
|
||||
{
|
||||
if (*this)
|
||||
{
|
||||
uint16_t remain = size;
|
||||
if (data->packets_in[0] == NOBLOCK)
|
||||
{
|
||||
flush(); // if sketch checks for incoming data and there are unsent data, flush the transmit buffer
|
||||
return 0;
|
||||
}
|
||||
uint16_t read;
|
||||
do
|
||||
{
|
||||
read = Enc28J60Network::readPacket(data->packets_in[0],0,buf+size-remain,remain);
|
||||
if (read == Enc28J60Network::blockSize(data->packets_in[0]))
|
||||
{
|
||||
remain -= read;
|
||||
_eatBlock(data->packets_in);
|
||||
if (uip_stopped(&uip_conns[data->conn_index]) && !(data->state & (UIP_CLIENT_CLOSE | UIP_CLIENT_REMOTECLOSED)))
|
||||
data->state |= UIP_CLIENT_RESTART;
|
||||
if (data->packets_in[0] == NOBLOCK)
|
||||
{
|
||||
if (data->state & UIP_CLIENT_REMOTECLOSED)
|
||||
{
|
||||
data->state = 0;
|
||||
data = NULL;
|
||||
}
|
||||
return size-remain;
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
Enc28J60Network::resizeBlock(data->packets_in[0],read);
|
||||
break;
|
||||
}
|
||||
}
|
||||
while(remain > 0);
|
||||
return size;
|
||||
}
|
||||
return -1;
|
||||
}
|
||||
|
||||
int
|
||||
UIPClient::read()
|
||||
{
|
||||
uint8_t c;
|
||||
if (read(&c,1) <= 0)
|
||||
return -1;
|
||||
return c;
|
||||
}
|
||||
|
||||
int
|
||||
UIPClient::peek()
|
||||
{
|
||||
if (*this)
|
||||
{
|
||||
if (data->packets_in[0] != NOBLOCK)
|
||||
{
|
||||
uint8_t c;
|
||||
Enc28J60Network::readPacket(data->packets_in[0],0,&c,1);
|
||||
return c;
|
||||
}
|
||||
}
|
||||
return -1;
|
||||
}
|
||||
|
||||
void
|
||||
UIPClient::discardReceived()
|
||||
{
|
||||
if (*this)
|
||||
{
|
||||
_flushBlocks(data->packets_in);
|
||||
}
|
||||
}
|
||||
|
||||
IPAddress
|
||||
UIPClient::remoteIP(void)
|
||||
{
|
||||
return data ? ip_addr_uip(uip_conns[data->conn_index].ripaddr) : IPAddress();
|
||||
}
|
||||
|
||||
uint16_t
|
||||
UIPClient::remotePort(void)
|
||||
{
|
||||
return data ? ntohs(uip_conns[data->conn_index].rport) : 0;
|
||||
}
|
||||
|
||||
uint8_t
|
||||
UIPClient::status()
|
||||
{
|
||||
return !data ? UIP_CLOSED : uip_conns[data->conn_index].tcpstateflags & UIP_TS_MASK;
|
||||
}
|
||||
|
||||
void
|
||||
uipclient_appcall(void)
|
||||
{
|
||||
uint16_t send_len = 0;
|
||||
uip_userdata_t *u = (uip_userdata_t*)uip_conn->appstate;
|
||||
if (!u && uip_connected())
|
||||
{
|
||||
#ifdef UIPETHERNET_DEBUG_CLIENT
|
||||
Serial.println(F("UIPClient uip_connected"));
|
||||
UIPClient::_dumpAllData();
|
||||
#endif
|
||||
u = (uip_userdata_t*) UIPClient::_allocateData();
|
||||
if (u)
|
||||
{
|
||||
uip_conn->appstate = u;
|
||||
#ifdef UIPETHERNET_DEBUG_CLIENT
|
||||
Serial.print(F("UIPClient allocated state: "));
|
||||
Serial.println(u->state,BIN);
|
||||
#endif
|
||||
}
|
||||
#ifdef UIPETHERNET_DEBUG_CLIENT
|
||||
else
|
||||
Serial.println(F("UIPClient allocation failed"));
|
||||
#endif
|
||||
}
|
||||
if (u)
|
||||
{
|
||||
if (uip_newdata())
|
||||
{
|
||||
#ifdef UIPETHERNET_DEBUG_CLIENT
|
||||
Serial.print(F("UIPClient uip_newdata, uip_len:"));
|
||||
Serial.println(uip_len);
|
||||
#endif
|
||||
if (uip_len && u->state && !(u->state & UIP_CLIENT_CLOSE))
|
||||
{
|
||||
for (uint8_t i=0; i < UIP_SOCKET_NUMPACKETS; i++)
|
||||
{
|
||||
if (u->packets_in[i] == NOBLOCK)
|
||||
{
|
||||
u->packets_in[i] = Enc28J60Network::allocBlock(uip_len);
|
||||
if (u->packets_in[i] != NOBLOCK)
|
||||
{
|
||||
Enc28J60Network::copyPacket(u->packets_in[i],0,UIPEthernetClass::in_packet,((uint8_t*)uip_appdata)-uip_buf,uip_len);
|
||||
if (i == UIP_SOCKET_NUMPACKETS-1)
|
||||
uip_stop();
|
||||
goto finish_newdata;
|
||||
}
|
||||
}
|
||||
}
|
||||
UIPEthernetClass::packetstate &= ~UIPETHERNET_FREEPACKET;
|
||||
uip_stop();
|
||||
}
|
||||
}
|
||||
finish_newdata:
|
||||
if (u->state & UIP_CLIENT_RESTART)
|
||||
{
|
||||
u->state &= ~UIP_CLIENT_RESTART;
|
||||
uip_restart();
|
||||
}
|
||||
// If the connection has been closed, save received but unread data.
|
||||
if (uip_closed() || uip_timedout() || uip_aborted())
|
||||
{
|
||||
#ifdef UIPETHERNET_DEBUG_CLIENT
|
||||
Serial.println(F("UIPClient uip_closed"));
|
||||
UIPClient::_dumpAllData();
|
||||
#endif
|
||||
// drop outgoing packets not sent yet:
|
||||
UIPClient::_flushBlocks(u->packets_out);
|
||||
if (u->packets_in[0] != NOBLOCK)
|
||||
{
|
||||
((uip_userdata_closed_t *)u)->lport = uip_conn->lport;
|
||||
u->state |= UIP_CLIENT_REMOTECLOSED;
|
||||
}
|
||||
else
|
||||
u->state = 0;
|
||||
// disassociate appdata.
|
||||
#ifdef UIPETHERNET_DEBUG_CLIENT
|
||||
Serial.println(F("after UIPClient uip_closed"));
|
||||
UIPClient::_dumpAllData();
|
||||
#endif
|
||||
uip_conn->appstate = NULL;
|
||||
goto finish;
|
||||
}
|
||||
if (uip_acked())
|
||||
{
|
||||
#ifdef UIPETHERNET_DEBUG_CLIENT
|
||||
Serial.println(F("UIPClient uip_acked"));
|
||||
#endif
|
||||
UIPClient::_eatBlock(u->packets_out);
|
||||
goto send;
|
||||
}
|
||||
if (uip_poll() || uip_rexmit())
|
||||
{
|
||||
#ifdef UIPETHERNET_DEBUG_CLIENT
|
||||
//Serial.println(F("UIPClient uip_poll"));
|
||||
#endif
|
||||
send:
|
||||
if (u->packets_out[0] != NOBLOCK)
|
||||
{
|
||||
if (u->packets_out[1] == NOBLOCK)
|
||||
{
|
||||
send_len = u->out_pos;
|
||||
if (send_len > 0)
|
||||
{
|
||||
Enc28J60Network::resizeBlock(u->packets_out[0],0,send_len);
|
||||
}
|
||||
}
|
||||
else
|
||||
send_len = Enc28J60Network::blockSize(u->packets_out[0]);
|
||||
if (send_len > 0)
|
||||
{
|
||||
UIPEthernetClass::uip_hdrlen = ((uint8_t*)uip_appdata)-uip_buf;
|
||||
UIPEthernetClass::uip_packet = Enc28J60Network::allocBlock(UIPEthernetClass::uip_hdrlen+send_len + UIP_SENDBUFFER_OFFSET + UIP_SENDBUFFER_PADDING);
|
||||
if (UIPEthernetClass::uip_packet != NOBLOCK)
|
||||
{
|
||||
Enc28J60Network::copyPacket(UIPEthernetClass::uip_packet,UIPEthernetClass::uip_hdrlen + UIP_SENDBUFFER_OFFSET,u->packets_out[0],0,send_len);
|
||||
UIPEthernetClass::packetstate |= UIPETHERNET_SENDPACKET;
|
||||
}
|
||||
}
|
||||
goto finish;
|
||||
}
|
||||
}
|
||||
// don't close connection unless all outgoing packets are sent
|
||||
if (u->state & UIP_CLIENT_CLOSE)
|
||||
{
|
||||
#ifdef UIPETHERNET_DEBUG_CLIENT
|
||||
Serial.println(F("UIPClient state UIP_CLIENT_CLOSE"));
|
||||
UIPClient::_dumpAllData();
|
||||
#endif
|
||||
if (u->packets_out[0] == NOBLOCK)
|
||||
{
|
||||
u->state = 0;
|
||||
uip_conn->appstate = NULL;
|
||||
uip_close();
|
||||
#ifdef UIPETHERNET_DEBUG_CLIENT
|
||||
Serial.println(F("no blocks out -> free userdata"));
|
||||
UIPClient::_dumpAllData();
|
||||
#endif
|
||||
}
|
||||
else
|
||||
{
|
||||
uip_stop();
|
||||
#ifdef UIPETHERNET_DEBUG_CLIENT
|
||||
Serial.println(F("blocks outstanding transfer -> uip_stop()"));
|
||||
#endif
|
||||
}
|
||||
}
|
||||
}
|
||||
finish:
|
||||
uip_send(uip_appdata,send_len);
|
||||
uip_len = send_len;
|
||||
}
|
||||
|
||||
uip_userdata_t *
|
||||
UIPClient::_allocateData()
|
||||
{
|
||||
for ( uint8_t sock = 0; sock < UIP_CONNS; sock++ )
|
||||
{
|
||||
uip_userdata_t* data = &UIPClient::all_data[sock];
|
||||
if (!data->state)
|
||||
{
|
||||
*data = uip_userdata_t();
|
||||
data->conn_index = uip_conn - uip_conns; // pointer arithmetics
|
||||
data->state = UIP_CLIENT_CONNECTED;
|
||||
return data;
|
||||
}
|
||||
}
|
||||
return NULL;
|
||||
}
|
||||
|
||||
uint8_t
|
||||
UIPClient::_currentBlock(memhandle* block)
|
||||
{
|
||||
for (uint8_t i = 1; i < UIP_SOCKET_NUMPACKETS; i++)
|
||||
{
|
||||
if (block[i] == NOBLOCK)
|
||||
return i-1;
|
||||
}
|
||||
return UIP_SOCKET_NUMPACKETS-1;
|
||||
}
|
||||
|
||||
void
|
||||
UIPClient::_eatBlock(memhandle* block)
|
||||
{
|
||||
#ifdef UIPETHERNET_DEBUG_CLIENT
|
||||
memhandle* start = block;
|
||||
Serial.print(F("eatblock("));
|
||||
Serial.print(*block);
|
||||
Serial.print(F("): "));
|
||||
for (uint8_t i = 0; i < UIP_SOCKET_NUMPACKETS; i++)
|
||||
{
|
||||
Serial.print(start[i]);
|
||||
Serial.print(F(" "));
|
||||
}
|
||||
Serial.print(F("-> "));
|
||||
#endif
|
||||
Enc28J60Network::freeBlock(block[0]);
|
||||
for (uint8_t i = 0; i < UIP_SOCKET_NUMPACKETS-1; i++)
|
||||
{
|
||||
block[i] = block[i+1];
|
||||
}
|
||||
block[UIP_SOCKET_NUMPACKETS-1] = NOBLOCK;
|
||||
#ifdef UIPETHERNET_DEBUG_CLIENT
|
||||
for (uint8_t i = 0; i < UIP_SOCKET_NUMPACKETS; i++)
|
||||
{
|
||||
Serial.print(start[i]);
|
||||
Serial.print(F(" "));
|
||||
}
|
||||
Serial.println();
|
||||
#endif
|
||||
}
|
||||
|
||||
void
|
||||
UIPClient::_flushBlocks(memhandle* block)
|
||||
{
|
||||
for (uint8_t i = 0; i < UIP_SOCKET_NUMPACKETS; i++)
|
||||
{
|
||||
Enc28J60Network::freeBlock(block[i]);
|
||||
block[i] = NOBLOCK;
|
||||
}
|
||||
}
|
||||
|
||||
#ifdef UIPETHERNET_DEBUG_CLIENT
|
||||
void
|
||||
UIPClient::_dumpAllData() {
|
||||
for (uint8_t i=0; i < UIP_CONNS; i++)
|
||||
{
|
||||
if (!all_data[i].state)
|
||||
continue;
|
||||
Serial.print(F("UIPClient::all_data["));
|
||||
Serial.print(i);
|
||||
Serial.print(F("], state:"));
|
||||
Serial.println(all_data[i].state, BIN);
|
||||
struct uip_conn& conn = uip_conns[all_data[i].conn_index];
|
||||
Serial.println(ip_addr_uip(conn.ripaddr));
|
||||
Serial.print(F("ix: "));
|
||||
Serial.print(all_data[i].conn_index);
|
||||
Serial.print(F(" tcp flags: 0x"));
|
||||
Serial.print(conn.tcpstateflags, HEX);
|
||||
Serial.print(F(" retransmission: timer "));
|
||||
Serial.print(conn.timer);
|
||||
Serial.print(F(" nrtx "));
|
||||
Serial.println(conn.nrtx);
|
||||
Serial.print(F("packets_in: "));
|
||||
for (uint8_t j=0; j < UIP_SOCKET_NUMPACKETS; j++)
|
||||
{
|
||||
Serial.print(all_data[i].packets_in[j]);
|
||||
Serial.print(F(" "));
|
||||
}
|
||||
Serial.println();
|
||||
if (all_data[i].state & UIP_CLIENT_REMOTECLOSED)
|
||||
{
|
||||
Serial.print(F("state remote closed, local port: "));
|
||||
Serial.println(htons(((uip_userdata_closed_t *)(&all_data[i]))->lport));
|
||||
}
|
||||
else
|
||||
{
|
||||
Serial.print(F("packets_out: "));
|
||||
for (uint8_t j=0; j < UIP_SOCKET_NUMPACKETS; j++)
|
||||
{
|
||||
Serial.print(all_data[i].packets_out[j]);
|
||||
Serial.print(F(" "));
|
||||
}
|
||||
Serial.println();
|
||||
Serial.print(F("out_pos: "));
|
||||
Serial.println(all_data[i].out_pos);
|
||||
}
|
||||
Serial.println();
|
||||
}
|
||||
for (uint8_t i=0; i < UIP_CONNS; i++)
|
||||
{
|
||||
struct uip_conn& conn = uip_conns[i];
|
||||
Serial.print(i);
|
||||
Serial.print(' ');
|
||||
Serial.print(ip_addr_uip(conn.ripaddr));
|
||||
Serial.print(':');
|
||||
Serial.print(ntohs(conn.rport));
|
||||
Serial.print(' ');
|
||||
Serial.print(ntohs(conn.lport));
|
||||
Serial.print(F(" tcp flags: 0x"));
|
||||
Serial.print(conn.tcpstateflags, HEX);
|
||||
Serial.print(F(" retransmission: timer "));
|
||||
Serial.print(conn.timer);
|
||||
Serial.print(F(" nrtx "));
|
||||
Serial.println(conn.nrtx);
|
||||
}
|
||||
}
|
||||
#endif
|
||||
@@ -1,110 +0,0 @@
|
||||
/*
|
||||
UIPClient.h - Arduino implementation of a uIP wrapper class.
|
||||
Copyright (c) 2013 Norbert Truchsess <norbert.truchsess@t-online.de>
|
||||
All rights reserved.
|
||||
|
||||
This program is free software: you can redistribute it and/or modify
|
||||
it under the terms of the GNU General Public License as published by
|
||||
the Free Software Foundation, either version 3 of the License, or
|
||||
(at your option) any later version.
|
||||
|
||||
This program is distributed in the hope that it will be useful,
|
||||
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
GNU General Public License for more details.
|
||||
|
||||
You should have received a copy of the GNU General Public License
|
||||
along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
#ifndef UIPCLIENT_H
|
||||
#define UIPCLIENT_H
|
||||
|
||||
#include <Client.h>
|
||||
#include "utility/mempool.h"
|
||||
|
||||
extern "C" {
|
||||
#include "utility/uip.h"
|
||||
}
|
||||
|
||||
#define UIP_SOCKET_DATALEN UIP_TCP_MSS
|
||||
|
||||
#define UIP_CLIENT_CONNECTED 0x01
|
||||
#define UIP_CLIENT_CLOSE 0x02
|
||||
#define UIP_CLIENT_REMOTECLOSED 0x04
|
||||
#define UIP_CLIENT_RESTART 0x08
|
||||
#define UIP_CLIENT_ACCEPTED 0x10
|
||||
|
||||
typedef struct {
|
||||
uint8_t conn_index;
|
||||
uint8_t state;
|
||||
memhandle packets_in[UIP_SOCKET_NUMPACKETS];
|
||||
uint16_t lport; /**< The local TCP port, in network byte order. */
|
||||
} uip_userdata_closed_t;
|
||||
|
||||
typedef struct {
|
||||
uint8_t conn_index;
|
||||
uint8_t state;
|
||||
memhandle packets_in[UIP_SOCKET_NUMPACKETS] = {NOBLOCK};
|
||||
memhandle packets_out[UIP_SOCKET_NUMPACKETS] = {NOBLOCK};
|
||||
memaddress out_pos;
|
||||
} uip_userdata_t;
|
||||
|
||||
class EthernetClient : public Client {
|
||||
|
||||
public:
|
||||
EthernetClient();
|
||||
int connect(IPAddress ip, uint16_t port);
|
||||
int connect(const char *host, uint16_t port);
|
||||
int read(uint8_t *buf, size_t size);
|
||||
void stop();
|
||||
uint8_t connected();
|
||||
operator bool();
|
||||
virtual bool operator==(const EthernetClient&);
|
||||
virtual bool operator!=(const EthernetClient& rhs) { return !this->operator==(rhs); };
|
||||
|
||||
size_t write(uint8_t);
|
||||
size_t write(const uint8_t *buf, size_t size);
|
||||
int availableForWrite();
|
||||
void flush(); // flush sends the buffered data
|
||||
|
||||
int available();
|
||||
int read();
|
||||
int peek();
|
||||
void discardReceived();
|
||||
|
||||
using Print::write;
|
||||
|
||||
IPAddress remoteIP();
|
||||
uint16_t remotePort();
|
||||
|
||||
uint8_t status();
|
||||
|
||||
void setConnectionTimeout(uint16_t millis) {connectTimeout = millis;}
|
||||
|
||||
private:
|
||||
EthernetClient(struct uip_conn *_conn);
|
||||
EthernetClient(uip_userdata_t* conn_data);
|
||||
|
||||
uip_userdata_t* data;
|
||||
uint16_t connectTimeout = 1000 * UIP_CONNECT_TIMEOUT;
|
||||
|
||||
static uip_userdata_t all_data[UIP_CONNS];
|
||||
static uip_userdata_t* _allocateData();
|
||||
|
||||
static uint8_t _currentBlock(memhandle* blocks);
|
||||
static void _eatBlock(memhandle* blocks);
|
||||
static void _flushBlocks(memhandle* blocks);
|
||||
|
||||
#ifdef UIPETHERNET_DEBUG_CLIENT
|
||||
static void _dumpAllData();
|
||||
#endif
|
||||
|
||||
friend class UIPEthernetClass;
|
||||
friend class EthernetServer;
|
||||
|
||||
friend void uipclient_appcall(void);
|
||||
|
||||
};
|
||||
|
||||
#endif
|
||||
@@ -1 +0,0 @@
|
||||
#include "Ethernet.h"
|
||||
@@ -1,112 +0,0 @@
|
||||
/*
|
||||
UIPServer.cpp - Arduino implementation of a uIP wrapper class.
|
||||
Copyright (c) 2013 Norbert Truchsess <norbert.truchsess@t-online.de>
|
||||
All rights reserved.
|
||||
|
||||
This program is free software: you can redistribute it and/or modify
|
||||
it under the terms of the GNU General Public License as published by
|
||||
the Free Software Foundation, either version 3 of the License, or
|
||||
(at your option) any later version.
|
||||
|
||||
This program is distributed in the hope that it will be useful,
|
||||
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
GNU General Public License for more details.
|
||||
|
||||
You should have received a copy of the GNU General Public License
|
||||
along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
#include "Ethernet.h"
|
||||
#include "EthernetServer.h"
|
||||
extern "C" {
|
||||
#include "utility/uip-conf.h"
|
||||
}
|
||||
|
||||
#define UIPServer EthernetServer // to not pollute source code history with the rename
|
||||
#define UIPClient EthernetClient
|
||||
|
||||
UIPServer::UIPServer(uint16_t port) : _port(htons(port))
|
||||
{
|
||||
}
|
||||
|
||||
UIPClient UIPServer::available()
|
||||
{
|
||||
UIPEthernetClass::tick();
|
||||
for ( uip_userdata_t* data = &UIPClient::all_data[0]; data < &UIPClient::all_data[UIP_CONNS]; data++ )
|
||||
{
|
||||
if (data->packets_in[0] != NOBLOCK
|
||||
&& (((data->state & UIP_CLIENT_CONNECTED) && uip_conns[data->conn_index].lport ==_port)
|
||||
|| ((data->state & UIP_CLIENT_REMOTECLOSED) && ((uip_userdata_closed_t *)data)->lport == _port)))
|
||||
return UIPClient(data);
|
||||
}
|
||||
return UIPClient();
|
||||
}
|
||||
|
||||
UIPClient UIPServer::accept()
|
||||
{
|
||||
UIPEthernetClass::tick();
|
||||
for ( uip_userdata_t* data = &UIPClient::all_data[0]; data < &UIPClient::all_data[UIP_CONNS]; data++ )
|
||||
{
|
||||
if (!(data->state & UIP_CLIENT_ACCEPTED)
|
||||
&& (((data->state & UIP_CLIENT_CONNECTED) && uip_conns[data->conn_index].lport ==_port)
|
||||
|| ((data->state & UIP_CLIENT_REMOTECLOSED) && ((uip_userdata_closed_t *)data)->lport == _port))) {
|
||||
data->state |= UIP_CLIENT_ACCEPTED;
|
||||
return UIPClient(data);
|
||||
}
|
||||
}
|
||||
return UIPClient();
|
||||
}
|
||||
|
||||
void UIPServer::begin()
|
||||
{
|
||||
uip_listen(_port);
|
||||
UIPEthernetClass::tick();
|
||||
listening = true;
|
||||
}
|
||||
|
||||
void UIPServer::begin(uint16_t port)
|
||||
{
|
||||
_port = port;
|
||||
begin();
|
||||
}
|
||||
|
||||
void UIPServer::end() {
|
||||
uip_unlisten(_port);
|
||||
listening = false;
|
||||
for ( uip_userdata_t* data = &UIPClient::all_data[0]; data < &UIPClient::all_data[UIP_CONNS]; data++ )
|
||||
{
|
||||
if ((data->state & UIP_CLIENT_CONNECTED) && uip_conns[data->conn_index].lport ==_port)
|
||||
{
|
||||
UIPClient client(data);
|
||||
client.stop();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
UIPServer::operator bool() {
|
||||
return listening;
|
||||
}
|
||||
|
||||
size_t UIPServer::writeToAllClients(const uint8_t *buf, size_t size)
|
||||
{
|
||||
size_t ret = 0;
|
||||
for ( uip_userdata_t* data = &UIPClient::all_data[0]; data < &UIPClient::all_data[UIP_CONNS]; data++ )
|
||||
{
|
||||
if ((data->state & UIP_CLIENT_CONNECTED) && uip_conns[data->conn_index].lport ==_port)
|
||||
{
|
||||
EthernetClient client(data);
|
||||
ret = client.write(buf, size);
|
||||
}
|
||||
}
|
||||
return ret;
|
||||
}
|
||||
|
||||
size_t EthernetServerPrint::write(uint8_t c)
|
||||
{
|
||||
return write(&c,1);
|
||||
}
|
||||
|
||||
size_t EthernetServerPrint::write(const uint8_t *buf, size_t size)
|
||||
{
|
||||
return EthernetServer::writeToAllClients(buf, size);
|
||||
}
|
||||
@@ -1,55 +0,0 @@
|
||||
/*
|
||||
UIPServer.h - Arduino implementation of a uIP wrapper class.
|
||||
Copyright (c) 2013 Norbert Truchsess <norbert.truchsess@t-online.de>
|
||||
All rights reserved.
|
||||
|
||||
This program is free software: you can redistribute it and/or modify
|
||||
it under the terms of the GNU General Public License as published by
|
||||
the Free Software Foundation, either version 3 of the License, or
|
||||
(at your option) any later version.
|
||||
|
||||
This program is distributed in the hope that it will be useful,
|
||||
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
GNU General Public License for more details.
|
||||
|
||||
You should have received a copy of the GNU General Public License
|
||||
along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
#ifndef UIPSERVER_H
|
||||
#define UIPSERVER_H
|
||||
|
||||
#include "EthernetClient.h"
|
||||
|
||||
class EthernetServer {
|
||||
|
||||
public:
|
||||
EthernetServer(uint16_t port = 80);
|
||||
EthernetClient available();
|
||||
EthernetClient accept();
|
||||
void begin();
|
||||
void begin(uint16_t port);
|
||||
void end();
|
||||
operator bool();
|
||||
|
||||
protected:
|
||||
size_t writeToAllClients(const uint8_t *buf, size_t size);
|
||||
|
||||
private:
|
||||
uint16_t _port;
|
||||
bool listening = false;
|
||||
};
|
||||
|
||||
class EthernetServerPrint : public EthernetServer, public Print {
|
||||
|
||||
public:
|
||||
EthernetServerPrint(uint16_t port = 80) : EthernetServer(port) {}
|
||||
|
||||
virtual size_t write(uint8_t);
|
||||
virtual size_t write(const uint8_t *buf, size_t size);
|
||||
|
||||
using Print::write;
|
||||
|
||||
};
|
||||
|
||||
#endif
|
||||
@@ -1,424 +0,0 @@
|
||||
/*
|
||||
UIPUdp.cpp - Arduino implementation of a uIP wrapper class.
|
||||
Copyright (c) 2013 Norbert Truchsess <norbert.truchsess@t-online.de>
|
||||
All rights reserved.
|
||||
|
||||
This program is free software: you can redistribute it and/or modify
|
||||
it under the terms of the GNU General Public License as published by
|
||||
the Free Software Foundation, either version 3 of the License, or
|
||||
(at your option) any later version.
|
||||
|
||||
This program is distributed in the hope that it will be useful,
|
||||
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
GNU General Public License for more details.
|
||||
|
||||
You should have received a copy of the GNU General Public License
|
||||
along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
#include "Ethernet.h"
|
||||
#include "EthernetUdp.h"
|
||||
|
||||
extern "C" {
|
||||
#include "utility/uip-conf.h"
|
||||
#include "utility/uip.h"
|
||||
#include "utility/uip_arp.h"
|
||||
}
|
||||
|
||||
#if UIP_UDP
|
||||
#define UIP_ARPHDRSIZE 42
|
||||
#define UDPBUF ((struct uip_udpip_hdr *)&uip_buf[UIP_LLH_LEN])
|
||||
|
||||
#define UIPUDP EthernetUDP // to not pollute source code history with the rename
|
||||
|
||||
// Constructor
|
||||
UIPUDP::UIPUDP() :
|
||||
_uip_udp_conn(NULL),
|
||||
appdata()
|
||||
{
|
||||
}
|
||||
|
||||
// initialize, start listening on specified port. Returns 1 if successful, 0 if there are no sockets available to use
|
||||
uint8_t
|
||||
UIPUDP::begin(uint16_t port)
|
||||
{
|
||||
if (!_uip_udp_conn)
|
||||
{
|
||||
_uip_udp_conn = uip_udp_new(NULL, 0);
|
||||
}
|
||||
if (_uip_udp_conn)
|
||||
{
|
||||
uip_udp_bind(_uip_udp_conn,htons(port));
|
||||
_uip_udp_conn->appstate = &appdata;
|
||||
return 1;
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
// Finish with the UDP socket
|
||||
void
|
||||
UIPUDP::stop()
|
||||
{
|
||||
if (_uip_udp_conn)
|
||||
{
|
||||
uip_udp_remove(_uip_udp_conn);
|
||||
_uip_udp_conn->appstate = NULL;
|
||||
_uip_udp_conn=NULL;
|
||||
Enc28J60Network::freeBlock(appdata.packet_in);
|
||||
_flushBlocks(appdata.packet_next);
|
||||
Enc28J60Network::freeBlock(appdata.packet_out);
|
||||
appdata = uip_udp_userdata_t();
|
||||
}
|
||||
}
|
||||
|
||||
// Sending UDP packets
|
||||
|
||||
// Start building up a packet to send to the remote host specific in ip and port
|
||||
// Returns 1 if successful, 0 if there was a problem with the supplied IP address or port
|
||||
int
|
||||
UIPUDP::beginPacket(IPAddress ip, uint16_t port)
|
||||
{
|
||||
UIPEthernetClass::tick();
|
||||
if ((ip[0] || ip[1] || ip[2] || ip[3]) && port)
|
||||
{
|
||||
uip_ipaddr_t ripaddr;
|
||||
uip_ip_addr(&ripaddr, ip);
|
||||
#ifdef UIPETHERNET_DEBUG_UDP
|
||||
Serial.print(F("udp beginPacket, "));
|
||||
#endif
|
||||
if (_uip_udp_conn)
|
||||
{
|
||||
// [J.A]: for UDP server, setting ripaddr and rport causes in uIP filtering
|
||||
// of incoming messages. it would be better to store the IP and port in fields
|
||||
// and set them only in endPacket().
|
||||
_uip_udp_conn->rport = htons(port);
|
||||
uip_ipaddr_copy(_uip_udp_conn->ripaddr, &ripaddr);
|
||||
}
|
||||
else
|
||||
{
|
||||
_uip_udp_conn = uip_udp_new(&ripaddr,htons(port));
|
||||
if (_uip_udp_conn)
|
||||
{
|
||||
#ifdef UIPETHERNET_DEBUG_UDP
|
||||
Serial.print(F("new connection, "));
|
||||
#endif
|
||||
_uip_udp_conn->appstate = &appdata;
|
||||
}
|
||||
else
|
||||
{
|
||||
#ifdef UIPETHERNET_DEBUG_UDP
|
||||
Serial.println(F("failed to allocate new connection"));
|
||||
#endif
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
#ifdef UIPETHERNET_DEBUG_UDP
|
||||
Serial.print(F("rip: "));
|
||||
Serial.print(ip);
|
||||
Serial.print(F(", port: "));
|
||||
Serial.println(port);
|
||||
#endif
|
||||
}
|
||||
if (_uip_udp_conn)
|
||||
{
|
||||
if (appdata.packet_out == NOBLOCK)
|
||||
{
|
||||
appdata.packet_out = Enc28J60Network::allocBlock(UIP_UDP_MAXPACKETSIZE + UIP_SENDBUFFER_OFFSET + UIP_SENDBUFFER_PADDING);
|
||||
appdata.out_pos = UIP_UDP_PHYH_LEN + UIP_SENDBUFFER_OFFSET;
|
||||
if (appdata.packet_out != NOBLOCK)
|
||||
return 1;
|
||||
#ifdef UIPETHERNET_DEBUG_UDP
|
||||
else
|
||||
Serial.println(F("failed to allocate memory for packet"));
|
||||
#endif
|
||||
}
|
||||
#ifdef UIPETHERNET_DEBUG_UDP
|
||||
else
|
||||
Serial.println(F("previous packet on that connection not sent yet"));
|
||||
#endif
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
// Start building up a packet to send to the remote host specific in host and port
|
||||
// Returns 1 if successful, 0 if there was a problem resolving the hostname or port
|
||||
int
|
||||
UIPUDP::beginPacket(const char *host, uint16_t port)
|
||||
{
|
||||
// Look up the host first
|
||||
int ret = 0;
|
||||
IPAddress remote_addr;
|
||||
ret = Ethernet.hostByName(host, remote_addr);
|
||||
if (ret == 1) {
|
||||
return beginPacket(remote_addr, port);
|
||||
} else {
|
||||
return ret;
|
||||
}
|
||||
}
|
||||
|
||||
// Finish off this packet and send it
|
||||
// Returns 1 if the packet was sent successfully, 0 if there was an error
|
||||
int
|
||||
UIPUDP::endPacket()
|
||||
{
|
||||
if (_uip_udp_conn && appdata.packet_out != NOBLOCK)
|
||||
{
|
||||
appdata.send = true;
|
||||
Enc28J60Network::resizeBlock(appdata.packet_out,0,appdata.out_pos + UIP_SENDBUFFER_PADDING);
|
||||
uip_udp_periodic_conn(_uip_udp_conn);
|
||||
if (uip_len > 0)
|
||||
{
|
||||
_send(_uip_udp_conn);
|
||||
return 1;
|
||||
}
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
// Write a single byte into the packet
|
||||
size_t
|
||||
UIPUDP::write(uint8_t c)
|
||||
{
|
||||
return write(&c,1);
|
||||
}
|
||||
|
||||
// Write size bytes from buffer into the packet
|
||||
size_t
|
||||
UIPUDP::write(const uint8_t *buffer, size_t size)
|
||||
{
|
||||
if (appdata.packet_out != NOBLOCK)
|
||||
{
|
||||
size_t ret = Enc28J60Network::writePacket(appdata.packet_out,appdata.out_pos,(uint8_t*)buffer,size);
|
||||
appdata.out_pos += ret;
|
||||
return ret;
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
// Start processing the next available incoming packet
|
||||
// Returns the size of the packet in bytes, or 0 if no packets are available
|
||||
int
|
||||
UIPUDP::parsePacket()
|
||||
{
|
||||
UIPEthernetClass::tick();
|
||||
#ifdef UIPETHERNET_DEBUG_UDP
|
||||
if (appdata.packet_in != NOBLOCK)
|
||||
{
|
||||
Serial.print(F("udp parsePacket freeing previous packet: "));
|
||||
Serial.println(appdata.packet_in);
|
||||
}
|
||||
#endif
|
||||
Enc28J60Network::freeBlock(appdata.packet_in);
|
||||
|
||||
appdata.packet_in = appdata.packet_next[0].packet;
|
||||
uip_ipaddr_copy(appdata.remote_ip, appdata.packet_next[0].remote_ip);
|
||||
appdata.remote_port = appdata.packet_next[0].remote_port;
|
||||
_moveBlocks(appdata.packet_next);
|
||||
|
||||
#ifdef UIPETHERNET_DEBUG_UDP
|
||||
if (appdata.packet_in != NOBLOCK)
|
||||
{
|
||||
Serial.print(F("udp parsePacket received packet: "));
|
||||
Serial.print(appdata.packet_in);
|
||||
}
|
||||
#endif
|
||||
int size = Enc28J60Network::blockSize(appdata.packet_in);
|
||||
#ifdef UIPETHERNET_DEBUG_UDP
|
||||
if (appdata.packet_in != NOBLOCK)
|
||||
{
|
||||
Serial.print(F(", size: "));
|
||||
Serial.println(size);
|
||||
}
|
||||
#endif
|
||||
return size;
|
||||
}
|
||||
|
||||
// Number of bytes remaining in the current packet
|
||||
int
|
||||
UIPUDP::available()
|
||||
{
|
||||
UIPEthernetClass::tick();
|
||||
return Enc28J60Network::blockSize(appdata.packet_in);
|
||||
}
|
||||
|
||||
// Read a single byte from the current packet
|
||||
int
|
||||
UIPUDP::read()
|
||||
{
|
||||
unsigned char c;
|
||||
if (read(&c,1) > 0)
|
||||
{
|
||||
return c;
|
||||
}
|
||||
return -1;
|
||||
}
|
||||
|
||||
// Read up to len bytes from the current packet and place them into buffer
|
||||
// Returns the number of bytes read, or 0 if none are available
|
||||
int
|
||||
UIPUDP::read(unsigned char* buffer, size_t len)
|
||||
{
|
||||
UIPEthernetClass::tick();
|
||||
if (appdata.packet_in != NOBLOCK)
|
||||
{
|
||||
memaddress read = Enc28J60Network::readPacket(appdata.packet_in,0,buffer,len);
|
||||
if (read == Enc28J60Network::blockSize(appdata.packet_in))
|
||||
{
|
||||
Enc28J60Network::freeBlock(appdata.packet_in);
|
||||
appdata.packet_in = NOBLOCK;
|
||||
}
|
||||
else
|
||||
Enc28J60Network::resizeBlock(appdata.packet_in,read);
|
||||
return read;
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
// Return the next byte from the current packet without moving on to the next byte
|
||||
int
|
||||
UIPUDP::peek()
|
||||
{
|
||||
UIPEthernetClass::tick();
|
||||
if (appdata.packet_in != NOBLOCK)
|
||||
{
|
||||
unsigned char c;
|
||||
if (Enc28J60Network::readPacket(appdata.packet_in,0,&c,1) == 1)
|
||||
return c;
|
||||
}
|
||||
return -1;
|
||||
}
|
||||
|
||||
// Finish reading the current packet
|
||||
void
|
||||
UIPUDP::discardReceived()
|
||||
{
|
||||
UIPEthernetClass::tick();
|
||||
Enc28J60Network::freeBlock(appdata.packet_in);
|
||||
appdata.packet_in = NOBLOCK;
|
||||
}
|
||||
|
||||
// Return the IP address of the host who sent the current incoming packet
|
||||
IPAddress
|
||||
UIPUDP::remoteIP()
|
||||
{
|
||||
return ip_addr_uip(appdata.remote_ip);
|
||||
}
|
||||
|
||||
// Return the port of the host who sent the current incoming packet
|
||||
uint16_t
|
||||
UIPUDP::remotePort()
|
||||
{
|
||||
return ntohs(appdata.remote_port);
|
||||
}
|
||||
|
||||
// uIP callback function
|
||||
|
||||
void
|
||||
uipudp_appcall(void) {
|
||||
if (uip_udp_userdata_t *data = (uip_udp_userdata_t *)(uip_udp_conn->appstate))
|
||||
{
|
||||
if (uip_newdata())
|
||||
{
|
||||
uint8_t p = UIPUDP::_newBlock(data->packet_next);
|
||||
if (data->packet_next[p].packet == NOBLOCK)
|
||||
{
|
||||
data->packet_next[p].remote_port = UDPBUF->srcport;
|
||||
uip_ipaddr_copy( data->packet_next[p].remote_ip,UDPBUF->srcipaddr);
|
||||
data->packet_next[p].packet = Enc28J60Network::allocBlock(ntohs(UDPBUF->udplen)-UIP_UDPH_LEN);
|
||||
//if we are unable to allocate memory the packet is dropped. udp doesn't guarantee packet delivery
|
||||
if (data->packet_next[p].packet != NOBLOCK)
|
||||
{
|
||||
//discard Linklevel and IP and udp-header and any trailing bytes:
|
||||
Enc28J60Network::copyPacket(data->packet_next[p].packet,0,UIPEthernetClass::in_packet,UIP_UDP_PHYH_LEN,Enc28J60Network::blockSize(data->packet_next[p].packet));
|
||||
#ifdef UIPETHERNET_DEBUG_UDP
|
||||
Serial.print(F("udp, uip_newdata received packet: "));
|
||||
Serial.print(data->packet_next[p].packet);
|
||||
Serial.print(F(", size: "));
|
||||
Serial.println(Enc28J60Network::blockSize(data->packet_next[p].packet));
|
||||
#endif
|
||||
}
|
||||
}
|
||||
}
|
||||
if (uip_poll() && data->send)
|
||||
{
|
||||
//set uip_slen (uip private) by calling uip_udp_send
|
||||
#ifdef UIPETHERNET_DEBUG_UDP
|
||||
Serial.print(F("udp, uip_poll preparing packet to send: "));
|
||||
Serial.print(data->packet_out);
|
||||
Serial.print(F(", size: "));
|
||||
Serial.println(Enc28J60Network::blockSize(data->packet_out));
|
||||
#endif
|
||||
UIPEthernetClass::uip_packet = data->packet_out;
|
||||
UIPEthernetClass::packetstate |= UIPETHERNET_SENDPACKET;
|
||||
UIPEthernetClass::uip_hdrlen = UIP_UDP_PHYH_LEN;
|
||||
uip_udp_send(data->out_pos - (UIP_UDP_PHYH_LEN + UIP_SENDBUFFER_OFFSET));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void
|
||||
UIPUDP::_send(struct uip_udp_conn *uip_udp_conn) {
|
||||
|
||||
uip_arp_out(); //add arp
|
||||
if (uip_len == UIP_ARPHDRSIZE)
|
||||
{
|
||||
UIPEthernetClass::uip_packet = NOBLOCK;
|
||||
UIPEthernetClass::packetstate &= ~UIPETHERNET_SENDPACKET;
|
||||
#ifdef UIPETHERNET_DEBUG_UDP
|
||||
Serial.println(F("udp, uip_poll results in ARP-packet"));
|
||||
#endif
|
||||
UIPEthernetClass::network_send();
|
||||
}
|
||||
else
|
||||
//arp found ethaddr for ip (otherwise packet is replaced by arp-request)
|
||||
{
|
||||
uip_udp_userdata_t* data = (uip_udp_userdata_t *)(uip_udp_conn->appstate);
|
||||
UIPEthernetClass::network_send();
|
||||
data->send = false;
|
||||
data->packet_out = NOBLOCK;
|
||||
|
||||
// [J.A] a listening UDP port in uIP filters received messages
|
||||
// if rport and ripaddr are set. so we better clear them
|
||||
uip_udp_conn->rport = 0;
|
||||
uip_udp_conn->ripaddr[0] = 0;
|
||||
uip_udp_conn->ripaddr[1] = 0;
|
||||
|
||||
#ifdef UIPETHERNET_DEBUG_UDP
|
||||
Serial.print(F("udp, uip_packet to send: "));
|
||||
Serial.println(UIPEthernetClass::uip_packet);
|
||||
#endif
|
||||
}
|
||||
}
|
||||
|
||||
uint8_t
|
||||
UIPUDP::_newBlock(uip_udp_msg_rec_t* block)
|
||||
{
|
||||
for (uint8_t i = 0; i < UIP_UDP_BACKLOG; i++)
|
||||
{
|
||||
if (block[i].packet == NOBLOCK)
|
||||
return i;
|
||||
}
|
||||
return UIP_UDP_BACKLOG-1;
|
||||
}
|
||||
|
||||
void
|
||||
UIPUDP::_moveBlocks(uip_udp_msg_rec_t* block)
|
||||
{
|
||||
for (uint8_t i = 0; i < UIP_UDP_BACKLOG-1; i++)
|
||||
{
|
||||
block[i] = block[i+1];
|
||||
}
|
||||
block[UIP_UDP_BACKLOG-1].packet = NOBLOCK;
|
||||
}
|
||||
|
||||
void
|
||||
UIPUDP::_flushBlocks(uip_udp_msg_rec_t* block)
|
||||
{
|
||||
for (uint8_t i = 0; i < UIP_UDP_BACKLOG; i++)
|
||||
{
|
||||
Enc28J60Network::freeBlock(block[i].packet);
|
||||
block[i].packet = NOBLOCK;
|
||||
}
|
||||
}
|
||||
#endif
|
||||
@@ -1,139 +0,0 @@
|
||||
/*
|
||||
UIPUdp.h - Arduino implementation of a uIP wrapper class.
|
||||
Copyright (c) 2013 Norbert Truchsess <norbert.truchsess@t-online.de>
|
||||
All rights reserved.
|
||||
|
||||
This program is free software: you can redistribute it and/or modify
|
||||
it under the terms of the GNU General Public License as published by
|
||||
the Free Software Foundation, either version 3 of the License, or
|
||||
(at your option) any later version.
|
||||
|
||||
This program is distributed in the hope that it will be useful,
|
||||
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
GNU General Public License for more details.
|
||||
|
||||
You should have received a copy of the GNU General Public License
|
||||
along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
#ifndef UIPUDP_H
|
||||
#define UIPUDP_H
|
||||
|
||||
#include <Arduino.h>
|
||||
#include <Udp.h>
|
||||
#include "utility/mempool.h"
|
||||
extern "C" {
|
||||
#include "utility/uip.h"
|
||||
}
|
||||
|
||||
#define UIP_UDP_MAXDATALEN 1500
|
||||
#define UIP_UDP_PHYH_LEN UIP_LLH_LEN+UIP_IPUDPH_LEN
|
||||
#define UIP_UDP_MAXPACKETSIZE UIP_UDP_MAXDATALEN+UIP_UDP_PHYH_LEN
|
||||
|
||||
typedef struct {
|
||||
memhandle packet = NOBLOCK;
|
||||
uip_ipaddr_t remote_ip;
|
||||
uint16_t remote_port = 0;
|
||||
} uip_udp_msg_rec_t;
|
||||
|
||||
typedef struct {
|
||||
memaddress out_pos;
|
||||
uip_udp_msg_rec_t packet_next[UIP_UDP_BACKLOG];
|
||||
memhandle packet_in = NOBLOCK;
|
||||
memhandle packet_out = NOBLOCK;
|
||||
boolean send;
|
||||
uip_ipaddr_t remote_ip;
|
||||
uint16_t remote_port = 0;
|
||||
} uip_udp_userdata_t;
|
||||
|
||||
class EthernetUDP : public UDP
|
||||
{
|
||||
|
||||
private:
|
||||
struct uip_udp_conn *_uip_udp_conn;
|
||||
|
||||
uip_udp_userdata_t appdata;
|
||||
|
||||
public:
|
||||
EthernetUDP(); // Constructor
|
||||
uint8_t
|
||||
begin(uint16_t);// initialize, start listening on specified port. Returns 1 if successful, 0 if there are no sockets available to use
|
||||
void
|
||||
stop(); // Finish with the UDP socket
|
||||
|
||||
// Sending UDP packets
|
||||
|
||||
// Start building up a packet to send to the remote host specific in ip and port
|
||||
// Returns 1 if successful, 0 if there was a problem with the supplied IP address or port
|
||||
int
|
||||
beginPacket(IPAddress ip, uint16_t port);
|
||||
// Start building up a packet to send to the remote host specific in host and port
|
||||
// Returns 1 if successful, 0 if there was a problem resolving the hostname or port
|
||||
int
|
||||
beginPacket(const char *host, uint16_t port);
|
||||
// Finish off this packet and send it
|
||||
// Returns 1 if the packet was sent successfully, 0 if there was an error
|
||||
int
|
||||
endPacket();
|
||||
// Write a single byte into the packet
|
||||
size_t
|
||||
write(uint8_t);
|
||||
// Write size bytes from buffer into the packet
|
||||
size_t
|
||||
write(const uint8_t *buffer, size_t size);
|
||||
// flush() should send the data, but here endPacket does it
|
||||
void
|
||||
flush() {}; // in Print it is empty, UDP makes it pure virtual
|
||||
|
||||
using Print::write;
|
||||
|
||||
// Start processing the next available incoming packet
|
||||
// Returns the size of the packet in bytes, or 0 if no packets are available
|
||||
int
|
||||
parsePacket();
|
||||
// Number of bytes remaining in the current packet
|
||||
int
|
||||
available();
|
||||
// Read a single byte from the current packet
|
||||
int
|
||||
read();
|
||||
// Read up to len bytes from the current packet and place them into buffer
|
||||
// Returns the number of bytes read, or 0 if none are available
|
||||
int
|
||||
read(unsigned char* buffer, size_t len);
|
||||
// Read up to len characters from the current packet and place them into buffer
|
||||
// Returns the number of characters read, or 0 if none are available
|
||||
int
|
||||
read(char* buffer, size_t len)
|
||||
{
|
||||
return read((unsigned char*) buffer, len);
|
||||
}
|
||||
;
|
||||
// Return the next byte from the current packet without moving on to the next byte
|
||||
int
|
||||
peek();
|
||||
void
|
||||
discardReceived(); // former flush
|
||||
|
||||
// Return the IP address of the host who sent the current incoming packet
|
||||
IPAddress
|
||||
remoteIP();
|
||||
|
||||
// Return the port of the host who sent the current incoming packet
|
||||
uint16_t
|
||||
remotePort();
|
||||
|
||||
private:
|
||||
|
||||
friend void uipudp_appcall(void);
|
||||
|
||||
friend class UIPEthernetClass;
|
||||
static void _send(struct uip_udp_conn *uip_udp_conn);
|
||||
|
||||
static uint8_t _newBlock(uip_udp_msg_rec_t* blocks);
|
||||
static void _moveBlocks(uip_udp_msg_rec_t* blocks);
|
||||
static void _flushBlocks(uip_udp_msg_rec_t* blocks);
|
||||
};
|
||||
|
||||
#endif
|
||||
@@ -1,21 +0,0 @@
|
||||
|
||||
#ifndef UIP_TCP_STATES_H
|
||||
#define UIP_TCP_STATES_H
|
||||
|
||||
#include <utility/uip.h>
|
||||
|
||||
// common constants for client.state() return values
|
||||
enum uip_tcp_state {
|
||||
CLOSED = UIP_CLOSED,
|
||||
SYN_SENT = UIP_SYN_SENT,
|
||||
SYN_RCVD = UIP_SYN_RCVD,
|
||||
ESTABLISHED = UIP_ESTABLISHED,
|
||||
FIN_WAIT_1 = UIP_FIN_WAIT_1,
|
||||
FIN_WAIT_2 = UIP_FIN_WAIT_2,
|
||||
CLOSE_WAIT = 10, // not used
|
||||
CLOSING = UIP_CLOSING,
|
||||
LAST_ACK = UIP_LAST_ACK,
|
||||
TIME_WAIT = UIP_TIME_WAIT
|
||||
};
|
||||
|
||||
#endif
|
||||
@@ -1,391 +0,0 @@
|
||||
/*
|
||||
Enc28J60NetworkClass.cpp — Bitbang-SPI version for non-hardware-SPI pins
|
||||
Based on UIPEthernet by Norbert Truchsess, GPL V2
|
||||
|
||||
Modification: Hardware SPI replaced with bitbang SPI.
|
||||
Target: STM32F103RBT6 with ENC28J60 on PC6(SCK)/PC7(MISO)/PC8(CS)/PC9(MOSI)
|
||||
*/
|
||||
|
||||
#include "Enc28J60Network.h"
|
||||
#include <Arduino.h>
|
||||
#include "config.h" // ETH_SCK_PIN / ETH_MOSI_PIN / ETH_MISO_PIN
|
||||
// Note: <SPI.h> intentionally NOT included — this version uses bitbang SPI
|
||||
|
||||
extern "C" {
|
||||
#include "enc28j60.h"
|
||||
#include "uip.h"
|
||||
}
|
||||
|
||||
// ============================================================
|
||||
// Bitbang SPI — SPI Mode 0 (CPOL=0, CPHA=0)
|
||||
// Pin definitions come from project include/config.h via build system
|
||||
// ============================================================
|
||||
#ifndef ETH_SCK_PIN
|
||||
#error "ETH_SCK_PIN not defined — check include/config.h"
|
||||
#endif
|
||||
|
||||
static bool _spi_ready = false;
|
||||
|
||||
static void bitbang_init() {
|
||||
if (_spi_ready) return;
|
||||
pinMode(ETH_SCK_PIN, OUTPUT); digitalWrite(ETH_SCK_PIN, LOW);
|
||||
pinMode(ETH_MOSI_PIN, OUTPUT); digitalWrite(ETH_MOSI_PIN, LOW);
|
||||
pinMode(ETH_MISO_PIN, INPUT);
|
||||
_spi_ready = true;
|
||||
}
|
||||
|
||||
static inline uint8_t bitbang_transfer(uint8_t out) {
|
||||
uint8_t in = 0;
|
||||
for (int8_t i = 7; i >= 0; i--) {
|
||||
digitalWrite(ETH_MOSI_PIN, (out >> i) & 1);
|
||||
digitalWrite(ETH_SCK_PIN, HIGH);
|
||||
in = (in << 1) | digitalRead(ETH_MISO_PIN);
|
||||
digitalWrite(ETH_SCK_PIN, LOW);
|
||||
}
|
||||
return in;
|
||||
}
|
||||
// ============================================================
|
||||
|
||||
// set CS to 0 = active
|
||||
#define CSACTIVE digitalWrite(csPin, LOW)
|
||||
// set CS to 1 = passive
|
||||
#define CSPASSIVE digitalWrite(csPin, HIGH)
|
||||
|
||||
bool Enc28J60Network::spiInitialized = false;
|
||||
uint8_t Enc28J60Network::csPin = SS;
|
||||
uint16_t Enc28J60Network::nextPacketPtr;
|
||||
uint8_t Enc28J60Network::bank = 0xff;
|
||||
|
||||
struct memblock Enc28J60Network::receivePkt;
|
||||
|
||||
void Enc28J60Network::initSPI()
|
||||
{
|
||||
if (spiInitialized)
|
||||
return;
|
||||
bitbang_init();
|
||||
pinMode(csPin, OUTPUT);
|
||||
CSPASSIVE;
|
||||
spiInitialized = true;
|
||||
}
|
||||
|
||||
bool Enc28J60Network::init(uint8_t* macaddr)
|
||||
{
|
||||
MemoryPool::init();
|
||||
initSPI();
|
||||
|
||||
// perform system reset
|
||||
writeOp(ENC28J60_SOFT_RESET, 0, ENC28J60_SOFT_RESET);
|
||||
delay(50);
|
||||
nextPacketPtr = RXSTART_INIT;
|
||||
writeRegPair(ERXSTL, RXSTART_INIT);
|
||||
writeRegPair(ERXRDPTL, RXSTART_INIT);
|
||||
writeRegPair(ERXNDL, RXSTOP_INIT);
|
||||
writeReg(ERXFCON, ERXFCON_UCEN|ERXFCON_CRCEN|ERXFCON_PMEN);
|
||||
writeRegPair(EPMM0, 0x303f);
|
||||
writeRegPair(EPMCSL, 0xf7f9);
|
||||
writeRegPair(MACON1, MACON1_MARXEN|MACON1_TXPAUS|MACON1_RXPAUS);
|
||||
writeOp(ENC28J60_BIT_FIELD_SET, MACON3, MACON3_PADCFG0|MACON3_TXCRCEN|MACON3_FRMLNEN);
|
||||
writeRegPair(MAIPGL, 0x0C12);
|
||||
writeReg(MABBIPG, 0x12);
|
||||
writeRegPair(MAMXFLL, MAX_FRAMELEN);
|
||||
writeReg(MAADR5, macaddr[0]);
|
||||
writeReg(MAADR4, macaddr[1]);
|
||||
writeReg(MAADR3, macaddr[2]);
|
||||
writeReg(MAADR2, macaddr[3]);
|
||||
writeReg(MAADR1, macaddr[4]);
|
||||
writeReg(MAADR0, macaddr[5]);
|
||||
phyWrite(PHCON2, PHCON2_HDLDIS);
|
||||
setBank(ECON1);
|
||||
writeOp(ENC28J60_BIT_FIELD_SET, EIE, EIE_INTIE|EIE_PKTIE);
|
||||
writeOp(ENC28J60_BIT_FIELD_SET, ECON1, ECON1_RXEN);
|
||||
phyWrite(PHLCON, 0x476);
|
||||
|
||||
return getrev();
|
||||
}
|
||||
|
||||
memhandle Enc28J60Network::receivePacket()
|
||||
{
|
||||
uint8_t rxstat;
|
||||
uint16_t len;
|
||||
|
||||
if (readReg(EPKTCNT) != 0)
|
||||
{
|
||||
uint16_t readPtr = nextPacketPtr+6 > RXSTOP_INIT ?
|
||||
nextPacketPtr+6-((RXSTOP_INIT + 1)-RXSTART_INIT) : nextPacketPtr+6;
|
||||
writeRegPair(ERDPTL, nextPacketPtr);
|
||||
nextPacketPtr = readOp(ENC28J60_READ_BUF_MEM, 0);
|
||||
nextPacketPtr |= readOp(ENC28J60_READ_BUF_MEM, 0) << 8;
|
||||
len = readOp(ENC28J60_READ_BUF_MEM, 0);
|
||||
len |= readOp(ENC28J60_READ_BUF_MEM, 0) << 8;
|
||||
len -= 4;
|
||||
rxstat = readOp(ENC28J60_READ_BUF_MEM, 0);
|
||||
writeOp(ENC28J60_BIT_FIELD_SET, ECON2, ECON2_PKTDEC);
|
||||
if ((rxstat & 0x80) != 0)
|
||||
{
|
||||
receivePkt.begin = readPtr;
|
||||
receivePkt.size = len;
|
||||
return UIP_RECEIVEBUFFERHANDLE;
|
||||
}
|
||||
setERXRDPT();
|
||||
}
|
||||
return (NOBLOCK);
|
||||
}
|
||||
|
||||
void Enc28J60Network::setERXRDPT()
|
||||
{
|
||||
writeRegPair(ERXRDPTL, nextPacketPtr == RXSTART_INIT ? RXSTOP_INIT : nextPacketPtr-1);
|
||||
}
|
||||
|
||||
memaddress Enc28J60Network::blockSize(memhandle handle)
|
||||
{
|
||||
return handle == NOBLOCK ? 0 :
|
||||
handle == UIP_RECEIVEBUFFERHANDLE ? receivePkt.size : blocks[handle].size;
|
||||
}
|
||||
|
||||
bool Enc28J60Network::sendPacket(memhandle handle)
|
||||
{
|
||||
memblock *packet = &blocks[handle];
|
||||
uint16_t start = packet->begin;
|
||||
uint16_t end = start + packet->size - 1 - UIP_SENDBUFFER_PADDING;
|
||||
|
||||
writeByte(start, 0);
|
||||
|
||||
writeRegPair(ETXSTL, start);
|
||||
writeRegPair(ETXNDL, end);
|
||||
|
||||
bool success = false;
|
||||
for (uint8_t retry = 0; retry < TX_COLLISION_RETRY_COUNT; retry++)
|
||||
{
|
||||
writeOp(ENC28J60_BIT_FIELD_SET, ECON1, ECON1_TXRST);
|
||||
writeOp(ENC28J60_BIT_FIELD_CLR, ECON1, ECON1_TXRST);
|
||||
writeOp(ENC28J60_BIT_FIELD_CLR, EIR, EIR_TXERIF | EIR_TXIF);
|
||||
writeOp(ENC28J60_BIT_FIELD_SET, ECON1, ECON1_TXRTS);
|
||||
uint8_t eir;
|
||||
while (((eir = readReg(EIR)) & (EIR_TXIF | EIR_TXERIF)) == 0);
|
||||
writeOp(ENC28J60_BIT_FIELD_CLR, ECON1, ECON1_TXRTS);
|
||||
success = ((eir & EIR_TXERIF) == 0);
|
||||
if (success) break;
|
||||
uint8_t tsv4 = readByte(end + 4);
|
||||
if (!(tsv4 & 0b00100000)) break;
|
||||
}
|
||||
return success;
|
||||
}
|
||||
|
||||
uint16_t Enc28J60Network::setReadPtr(memhandle handle, memaddress position, uint16_t len)
|
||||
{
|
||||
memblock *packet = handle == UIP_RECEIVEBUFFERHANDLE ? &receivePkt : &blocks[handle];
|
||||
memaddress start = handle == UIP_RECEIVEBUFFERHANDLE &&
|
||||
packet->begin + position > RXSTOP_INIT ?
|
||||
packet->begin + position-((RXSTOP_INIT + 1)-RXSTART_INIT) :
|
||||
packet->begin + position;
|
||||
writeRegPair(ERDPTL, start);
|
||||
if (len > packet->size - position) len = packet->size - position;
|
||||
return len;
|
||||
}
|
||||
|
||||
uint16_t Enc28J60Network::readPacket(memhandle handle, memaddress position, uint8_t* buffer, uint16_t len)
|
||||
{
|
||||
len = setReadPtr(handle, position, len);
|
||||
readBuffer(len, buffer);
|
||||
return len;
|
||||
}
|
||||
|
||||
uint16_t Enc28J60Network::writePacket(memhandle handle, memaddress position, uint8_t* buffer, uint16_t len)
|
||||
{
|
||||
memblock *packet = &blocks[handle];
|
||||
uint16_t start = packet->begin + position;
|
||||
if (len > packet->size - position) len = packet->size - position;
|
||||
if (len == 0) return 0;
|
||||
writeRegPair(EWRPTL, start);
|
||||
writeBuffer(len, buffer);
|
||||
return len;
|
||||
}
|
||||
|
||||
uint8_t Enc28J60Network::readByte(uint16_t addr)
|
||||
{
|
||||
writeRegPair(ERDPTL, addr);
|
||||
CSACTIVE;
|
||||
bitbang_transfer(ENC28J60_READ_BUF_MEM);
|
||||
uint8_t c = bitbang_transfer(0x00);
|
||||
CSPASSIVE;
|
||||
return c;
|
||||
}
|
||||
|
||||
void Enc28J60Network::writeByte(uint16_t addr, uint8_t data)
|
||||
{
|
||||
writeRegPair(EWRPTL, addr);
|
||||
CSACTIVE;
|
||||
bitbang_transfer(ENC28J60_WRITE_BUF_MEM);
|
||||
bitbang_transfer(data);
|
||||
CSPASSIVE;
|
||||
}
|
||||
|
||||
void Enc28J60Network::copyPacket(memhandle dest_pkt, memaddress dest_pos, memhandle src_pkt, memaddress src_pos, uint16_t len)
|
||||
{
|
||||
memblock *dest = &blocks[dest_pkt];
|
||||
memblock *src = src_pkt == UIP_RECEIVEBUFFERHANDLE ? &receivePkt : &blocks[src_pkt];
|
||||
memaddress start = src_pkt == UIP_RECEIVEBUFFERHANDLE && src->begin + src_pos > RXSTOP_INIT ?
|
||||
src->begin + src_pos - ((RXSTOP_INIT + 1) - RXSTART_INIT) : src->begin + src_pos;
|
||||
enc28J60_mempool_block_move_callback(dest->begin + dest_pos, start, len);
|
||||
}
|
||||
|
||||
void enc28J60_mempool_block_move_callback(memaddress dest, memaddress src, memaddress len)
|
||||
{
|
||||
if (len == 1)
|
||||
{
|
||||
Enc28J60Network::writeByte(dest, Enc28J60Network::readByte(src));
|
||||
}
|
||||
else
|
||||
{
|
||||
len += src - 1;
|
||||
Enc28J60Network::writeRegPair(EDMASTL, src);
|
||||
Enc28J60Network::writeRegPair(EDMADSTL, dest);
|
||||
if ((src <= RXSTOP_INIT) && (len > RXSTOP_INIT))
|
||||
len -= ((RXSTOP_INIT + 1)-RXSTART_INIT);
|
||||
Enc28J60Network::writeRegPair(EDMANDL, len);
|
||||
Enc28J60Network::writeOp(ENC28J60_BIT_FIELD_CLR, ECON1, ECON1_CSUMEN);
|
||||
Enc28J60Network::writeOp(ENC28J60_BIT_FIELD_SET, ECON1, ECON1_DMAST);
|
||||
while (Enc28J60Network::readOp(ENC28J60_READ_CTRL_REG, ECON1) & ECON1_DMAST);
|
||||
}
|
||||
}
|
||||
|
||||
void Enc28J60Network::freePacket()
|
||||
{
|
||||
setERXRDPT();
|
||||
}
|
||||
|
||||
uint8_t Enc28J60Network::readOp(uint8_t op, uint8_t address)
|
||||
{
|
||||
CSACTIVE;
|
||||
bitbang_transfer(op | (address & ADDR_MASK));
|
||||
if (address & 0x80) bitbang_transfer(0x00); // dummy read for MAC/MII
|
||||
uint8_t c = bitbang_transfer(0x00);
|
||||
CSPASSIVE;
|
||||
return c;
|
||||
}
|
||||
|
||||
void Enc28J60Network::writeOp(uint8_t op, uint8_t address, uint8_t data)
|
||||
{
|
||||
CSACTIVE;
|
||||
bitbang_transfer(op | (address & ADDR_MASK));
|
||||
bitbang_transfer(data);
|
||||
CSPASSIVE;
|
||||
}
|
||||
|
||||
void Enc28J60Network::readBuffer(uint16_t len, uint8_t* data)
|
||||
{
|
||||
CSACTIVE;
|
||||
bitbang_transfer(ENC28J60_READ_BUF_MEM);
|
||||
while (len--) *data++ = bitbang_transfer(0x00);
|
||||
CSPASSIVE;
|
||||
}
|
||||
|
||||
void Enc28J60Network::writeBuffer(uint16_t len, uint8_t* data)
|
||||
{
|
||||
CSACTIVE;
|
||||
bitbang_transfer(ENC28J60_WRITE_BUF_MEM);
|
||||
while (len--) bitbang_transfer(*data++);
|
||||
CSPASSIVE;
|
||||
}
|
||||
|
||||
void Enc28J60Network::setBank(uint8_t address)
|
||||
{
|
||||
if ((address & BANK_MASK) != bank)
|
||||
{
|
||||
writeOp(ENC28J60_BIT_FIELD_CLR, ECON1, (ECON1_BSEL1|ECON1_BSEL0));
|
||||
writeOp(ENC28J60_BIT_FIELD_SET, ECON1, (address & BANK_MASK)>>5);
|
||||
bank = (address & BANK_MASK);
|
||||
}
|
||||
}
|
||||
|
||||
uint8_t Enc28J60Network::readReg(uint8_t address)
|
||||
{
|
||||
setBank(address);
|
||||
return readOp(ENC28J60_READ_CTRL_REG, address);
|
||||
}
|
||||
|
||||
void Enc28J60Network::writeReg(uint8_t address, uint8_t data)
|
||||
{
|
||||
setBank(address);
|
||||
writeOp(ENC28J60_WRITE_CTRL_REG, address, data);
|
||||
}
|
||||
|
||||
void Enc28J60Network::writeRegPair(uint8_t address, uint16_t data)
|
||||
{
|
||||
setBank(address);
|
||||
writeOp(ENC28J60_WRITE_CTRL_REG, address, (data & 0xFF));
|
||||
writeOp(ENC28J60_WRITE_CTRL_REG, address+1, (data >> 8));
|
||||
}
|
||||
|
||||
void Enc28J60Network::phyWrite(uint8_t address, uint16_t data)
|
||||
{
|
||||
writeReg(MIREGADR, address);
|
||||
writeRegPair(MIWRL, data);
|
||||
while (readReg(MISTAT) & MISTAT_BUSY) delayMicroseconds(15);
|
||||
}
|
||||
|
||||
uint16_t Enc28J60Network::phyRead(uint8_t address)
|
||||
{
|
||||
writeReg(MIREGADR, address);
|
||||
writeReg(MICMD, MICMD_MIIRD);
|
||||
while (readReg(MISTAT) & MISTAT_BUSY) delayMicroseconds(15);
|
||||
writeReg(MICMD, 0);
|
||||
return (readReg(MIRDL) | readReg(MIRDH) << 8);
|
||||
}
|
||||
|
||||
void Enc28J60Network::clkout(uint8_t clk)
|
||||
{
|
||||
writeReg(ECOCON, clk & 0x7);
|
||||
}
|
||||
|
||||
uint8_t Enc28J60Network::getrev(void)
|
||||
{
|
||||
initSPI();
|
||||
return readReg(EREVID);
|
||||
}
|
||||
|
||||
uint16_t Enc28J60Network::chksum(uint16_t sum, memhandle handle, memaddress pos, uint16_t len)
|
||||
{
|
||||
uint16_t t;
|
||||
len = setReadPtr(handle, pos, len) - 1;
|
||||
CSACTIVE;
|
||||
bitbang_transfer(ENC28J60_READ_BUF_MEM);
|
||||
uint16_t i;
|
||||
for (i = 0; i < len; i += 2)
|
||||
{
|
||||
t = bitbang_transfer(0x00) << 8;
|
||||
t += bitbang_transfer(0x00);
|
||||
sum += t;
|
||||
if (sum < t) sum++;
|
||||
}
|
||||
if (i == len)
|
||||
{
|
||||
t = (bitbang_transfer(0x00) << 8) + 0;
|
||||
sum += t;
|
||||
if (sum < t) sum++;
|
||||
}
|
||||
CSPASSIVE;
|
||||
return sum;
|
||||
}
|
||||
|
||||
void Enc28J60Network::powerOff()
|
||||
{
|
||||
writeOp(ENC28J60_BIT_FIELD_CLR, ECON1, ECON1_RXEN);
|
||||
delay(50);
|
||||
writeOp(ENC28J60_BIT_FIELD_SET, ECON2, ECON2_VRPS);
|
||||
delay(50);
|
||||
writeOp(ENC28J60_BIT_FIELD_SET, ECON2, ECON2_PWRSV);
|
||||
}
|
||||
|
||||
void Enc28J60Network::powerOn()
|
||||
{
|
||||
writeOp(ENC28J60_BIT_FIELD_CLR, ECON2, ECON2_PWRSV);
|
||||
delay(50);
|
||||
writeOp(ENC28J60_BIT_FIELD_SET, ECON1, ECON1_RXEN);
|
||||
delay(50);
|
||||
}
|
||||
|
||||
bool Enc28J60Network::linkStatus()
|
||||
{
|
||||
return (phyRead(PHSTAT2) & 0x0400) > 0;
|
||||
}
|
||||
@@ -1,94 +0,0 @@
|
||||
/*
|
||||
Enc28J60NetworkClass.h
|
||||
UIPEthernet network driver for Microchip ENC28J60 Ethernet Interface.
|
||||
|
||||
Copyright (c) 2013 Norbert Truchsess <norbert.truchsess@t-online.de>
|
||||
All rights reserved.
|
||||
|
||||
inspired by enc28j60.c file from the AVRlib library by Pascal Stang.
|
||||
For AVRlib See http://www.procyonengineering.com/
|
||||
|
||||
This program is free software: you can redistribute it and/or modify
|
||||
it under the terms of the GNU General Public License as published by
|
||||
the Free Software Foundation, either version 3 of the License, or
|
||||
(at your option) any later version.
|
||||
|
||||
This program is distributed in the hope that it will be useful,
|
||||
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
GNU General Public License for more details.
|
||||
|
||||
You should have received a copy of the GNU General Public License
|
||||
along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
#ifndef Enc28J60Network_H_
|
||||
#define Enc28J60Network_H_
|
||||
|
||||
#include "mempool.h"
|
||||
|
||||
#define UIP_RECEIVEBUFFERHANDLE 0xff
|
||||
|
||||
#define UIP_SENDBUFFER_PADDING 7
|
||||
#define UIP_SENDBUFFER_OFFSET 1
|
||||
|
||||
#define TX_COLLISION_RETRY_COUNT 3
|
||||
|
||||
//#define ENC28J60DEBUG
|
||||
|
||||
/*
|
||||
* Empfangen von ip-header, arp etc...
|
||||
* wenn tcp/udp -> tcp/udp-callback -> assign new packet to connection
|
||||
*/
|
||||
|
||||
class Enc28J60Network : public MemoryPool
|
||||
{
|
||||
|
||||
private:
|
||||
static uint8_t csPin;
|
||||
static bool spiInitialized;
|
||||
|
||||
static uint16_t nextPacketPtr;
|
||||
static uint8_t bank;
|
||||
|
||||
static struct memblock receivePkt;
|
||||
|
||||
static uint8_t readOp(uint8_t op, uint8_t address);
|
||||
static void writeOp(uint8_t op, uint8_t address, uint8_t data);
|
||||
static uint16_t setReadPtr(memhandle handle, memaddress position, uint16_t len);
|
||||
static void setERXRDPT();
|
||||
static void readBuffer(uint16_t len, uint8_t* data);
|
||||
static void writeBuffer(uint16_t len, uint8_t* data);
|
||||
static uint8_t readByte(uint16_t addr);
|
||||
static void writeByte(uint16_t addr, uint8_t data);
|
||||
static void setBank(uint8_t address);
|
||||
static uint8_t readReg(uint8_t address);
|
||||
static void writeReg(uint8_t address, uint8_t data);
|
||||
static void writeRegPair(uint8_t address, uint16_t data);
|
||||
static void phyWrite(uint8_t address, uint16_t data);
|
||||
static uint16_t phyRead(uint8_t address);
|
||||
static void clkout(uint8_t clk);
|
||||
|
||||
friend void enc28J60_mempool_block_move_callback(memaddress,memaddress,memaddress);
|
||||
|
||||
public:
|
||||
|
||||
static uint8_t getrev(void);
|
||||
static void powerOn();
|
||||
static void powerOff();
|
||||
static bool linkStatus();
|
||||
|
||||
static void setCsPin(uint8_t _csPin) {csPin = _csPin;}
|
||||
static void initSPI();
|
||||
static bool init(uint8_t* macaddr);
|
||||
static memhandle receivePacket();
|
||||
static void freePacket();
|
||||
static memaddress blockSize(memhandle handle);
|
||||
static bool sendPacket(memhandle handle);
|
||||
static uint16_t readPacket(memhandle handle, memaddress position, uint8_t* buffer, uint16_t len);
|
||||
static uint16_t writePacket(memhandle handle, memaddress position, uint8_t* buffer, uint16_t len);
|
||||
static void copyPacket(memhandle dest, memaddress dest_pos, memhandle src, memaddress src_pos, uint16_t len);
|
||||
static uint16_t chksum(uint16_t sum, memhandle handle, memaddress pos, uint16_t len);
|
||||
};
|
||||
|
||||
#endif /* Enc28J60NetworkClass_H_ */
|
||||
@@ -1,257 +0,0 @@
|
||||
/*****************************************************************************
|
||||
*
|
||||
* Title : Microchip ENC28J60 Ethernet Interface Driver
|
||||
* Author : Pascal Stang (c)2005
|
||||
* Modified by Norbert Truchsess
|
||||
* Copyright: GPL V2
|
||||
*
|
||||
*This driver provides initialization and transmit/receive
|
||||
*functions for the Microchip ENC28J60 10Mb Ethernet Controller and PHY.
|
||||
*This chip is novel in that it is a full MAC+PHY interface all in a 28-pin
|
||||
*chip, using an SPI interface to the host processor.
|
||||
*
|
||||
*
|
||||
*****************************************************************************/
|
||||
|
||||
#ifndef ENC28J60_H
|
||||
#define ENC28J60_H
|
||||
#include <inttypes.h>
|
||||
|
||||
// ENC28J60 Control Registers
|
||||
// Control register definitions are a combination of address,
|
||||
// bank number, and Ethernet/MAC/PHY indicator bits.
|
||||
// - Register address (bits 0-4)
|
||||
// - Bank number (bits 5-6)
|
||||
// - MAC/PHY indicator (bit 7)
|
||||
#define ADDR_MASK 0x1F
|
||||
#define BANK_MASK 0x60
|
||||
#define SPRD_MASK 0x80
|
||||
// All-bank registers
|
||||
#define EIE 0x1B
|
||||
#define EIR 0x1C
|
||||
#define ESTAT 0x1D
|
||||
#define ECON2 0x1E
|
||||
#define ECON1 0x1F
|
||||
// Bank 0 registers
|
||||
#define ERDPTL (0x00|0x00)
|
||||
#define ERDPTH (0x01|0x00)
|
||||
#define EWRPTL (0x02|0x00)
|
||||
#define EWRPTH (0x03|0x00)
|
||||
#define ETXSTL (0x04|0x00)
|
||||
#define ETXSTH (0x05|0x00)
|
||||
#define ETXNDL (0x06|0x00)
|
||||
#define ETXNDH (0x07|0x00)
|
||||
#define ERXSTL (0x08|0x00)
|
||||
#define ERXSTH (0x09|0x00)
|
||||
#define ERXNDL (0x0A|0x00)
|
||||
#define ERXNDH (0x0B|0x00)
|
||||
#define ERXRDPTL (0x0C|0x00)
|
||||
#define ERXRDPTH (0x0D|0x00)
|
||||
#define ERXWRPTL (0x0E|0x00)
|
||||
#define ERXWRPTH (0x0F|0x00)
|
||||
#define EDMASTL (0x10|0x00)
|
||||
#define EDMASTH (0x11|0x00)
|
||||
#define EDMANDL (0x12|0x00)
|
||||
#define EDMANDH (0x13|0x00)
|
||||
#define EDMADSTL (0x14|0x00)
|
||||
#define EDMADSTH (0x15|0x00)
|
||||
#define EDMACSL (0x16|0x00)
|
||||
#define EDMACSH (0x17|0x00)
|
||||
// Bank 1 registers
|
||||
#define EHT0 (0x00|0x20)
|
||||
#define EHT1 (0x01|0x20)
|
||||
#define EHT2 (0x02|0x20)
|
||||
#define EHT3 (0x03|0x20)
|
||||
#define EHT4 (0x04|0x20)
|
||||
#define EHT5 (0x05|0x20)
|
||||
#define EHT6 (0x06|0x20)
|
||||
#define EHT7 (0x07|0x20)
|
||||
#define EPMM0 (0x08|0x20)
|
||||
#define EPMM1 (0x09|0x20)
|
||||
#define EPMM2 (0x0A|0x20)
|
||||
#define EPMM3 (0x0B|0x20)
|
||||
#define EPMM4 (0x0C|0x20)
|
||||
#define EPMM5 (0x0D|0x20)
|
||||
#define EPMM6 (0x0E|0x20)
|
||||
#define EPMM7 (0x0F|0x20)
|
||||
#define EPMCSL (0x10|0x20)
|
||||
#define EPMCSH (0x11|0x20)
|
||||
#define EPMOL (0x14|0x20)
|
||||
#define EPMOH (0x15|0x20)
|
||||
#define EWOLIE (0x16|0x20)
|
||||
#define EWOLIR (0x17|0x20)
|
||||
#define ERXFCON (0x18|0x20)
|
||||
#define EPKTCNT (0x19|0x20)
|
||||
// Bank 2 registers
|
||||
#define MACON1 (0x00|0x40|0x80)
|
||||
#define MACON2 (0x01|0x40|0x80)
|
||||
#define MACON3 (0x02|0x40|0x80)
|
||||
#define MACON4 (0x03|0x40|0x80)
|
||||
#define MABBIPG (0x04|0x40|0x80)
|
||||
#define MAIPGL (0x06|0x40|0x80)
|
||||
#define MAIPGH (0x07|0x40|0x80)
|
||||
#define MACLCON1 (0x08|0x40|0x80)
|
||||
#define MACLCON2 (0x09|0x40|0x80)
|
||||
#define MAMXFLL (0x0A|0x40|0x80)
|
||||
#define MAMXFLH (0x0B|0x40|0x80)
|
||||
#define MAPHSUP (0x0D|0x40|0x80)
|
||||
#define MICON (0x11|0x40|0x80)
|
||||
#define MICMD (0x12|0x40|0x80)
|
||||
#define MIREGADR (0x14|0x40|0x80)
|
||||
#define MIWRL (0x16|0x40|0x80)
|
||||
#define MIWRH (0x17|0x40|0x80)
|
||||
#define MIRDL (0x18|0x40|0x80)
|
||||
#define MIRDH (0x19|0x40|0x80)
|
||||
// Bank 3 registers
|
||||
#define MAADR1 (0x00|0x60|0x80)
|
||||
#define MAADR0 (0x01|0x60|0x80)
|
||||
#define MAADR3 (0x02|0x60|0x80)
|
||||
#define MAADR2 (0x03|0x60|0x80)
|
||||
#define MAADR5 (0x04|0x60|0x80)
|
||||
#define MAADR4 (0x05|0x60|0x80)
|
||||
#define EBSTSD (0x06|0x60)
|
||||
#define EBSTCON (0x07|0x60)
|
||||
#define EBSTCSL (0x08|0x60)
|
||||
#define EBSTCSH (0x09|0x60)
|
||||
#define MISTAT (0x0A|0x60|0x80)
|
||||
#define EREVID (0x12|0x60)
|
||||
#define ECOCON (0x15|0x60)
|
||||
#define EFLOCON (0x17|0x60)
|
||||
#define EPAUSL (0x18|0x60)
|
||||
#define EPAUSH (0x19|0x60)
|
||||
// PHY registers
|
||||
#define PHCON1 0x00
|
||||
#define PHSTAT1 0x01
|
||||
#define PHHID1 0x02
|
||||
#define PHHID2 0x03
|
||||
#define PHCON2 0x10
|
||||
#define PHSTAT2 0x11
|
||||
#define PHIE 0x12
|
||||
#define PHIR 0x13
|
||||
#define PHLCON 0x14
|
||||
|
||||
// ENC28J60 ERXFCON Register Bit Definitions
|
||||
#define ERXFCON_UCEN 0x80
|
||||
#define ERXFCON_ANDOR 0x40
|
||||
#define ERXFCON_CRCEN 0x20
|
||||
#define ERXFCON_PMEN 0x10
|
||||
#define ERXFCON_MPEN 0x08
|
||||
#define ERXFCON_HTEN 0x04
|
||||
#define ERXFCON_MCEN 0x02
|
||||
#define ERXFCON_BCEN 0x01
|
||||
// ENC28J60 EIE Register Bit Definitions
|
||||
#define EIE_INTIE 0x80
|
||||
#define EIE_PKTIE 0x40
|
||||
#define EIE_DMAIE 0x20
|
||||
#define EIE_LINKIE 0x10
|
||||
#define EIE_TXIE 0x08
|
||||
#define EIE_WOLIE 0x04
|
||||
#define EIE_TXERIE 0x02
|
||||
#define EIE_RXERIE 0x01
|
||||
// ENC28J60 EIR Register Bit Definitions
|
||||
#define EIR_PKTIF 0x40
|
||||
#define EIR_DMAIF 0x20
|
||||
#define EIR_LINKIF 0x10
|
||||
#define EIR_TXIF 0x08
|
||||
#define EIR_WOLIF 0x04
|
||||
#define EIR_TXERIF 0x02
|
||||
#define EIR_RXERIF 0x01
|
||||
// ENC28J60 ESTAT Register Bit Definitions
|
||||
#define ESTAT_INT 0x80
|
||||
#define ESTAT_LATECOL 0x10
|
||||
#define ESTAT_RXBUSY 0x04
|
||||
#define ESTAT_TXABRT 0x02
|
||||
#define ESTAT_CLKRDY 0x01
|
||||
// ENC28J60 ECON2 Register Bit Definitions
|
||||
#define ECON2_AUTOINC 0x80
|
||||
#define ECON2_PKTDEC 0x40
|
||||
#define ECON2_PWRSV 0x20
|
||||
#define ECON2_VRPS 0x08
|
||||
// ENC28J60 ECON1 Register Bit Definitions
|
||||
#define ECON1_TXRST 0x80
|
||||
#define ECON1_RXRST 0x40
|
||||
#define ECON1_DMAST 0x20
|
||||
#define ECON1_CSUMEN 0x10
|
||||
#define ECON1_TXRTS 0x08
|
||||
#define ECON1_RXEN 0x04
|
||||
#define ECON1_BSEL1 0x02
|
||||
#define ECON1_BSEL0 0x01
|
||||
// ENC28J60 MACON1 Register Bit Definitions
|
||||
#define MACON1_LOOPBK 0x10
|
||||
#define MACON1_TXPAUS 0x08
|
||||
#define MACON1_RXPAUS 0x04
|
||||
#define MACON1_PASSALL 0x02
|
||||
#define MACON1_MARXEN 0x01
|
||||
// ENC28J60 MACON2 Register Bit Definitions
|
||||
#define MACON2_MARST 0x80
|
||||
#define MACON2_RNDRST 0x40
|
||||
#define MACON2_MARXRST 0x08
|
||||
#define MACON2_RFUNRST 0x04
|
||||
#define MACON2_MATXRST 0x02
|
||||
#define MACON2_TFUNRST 0x01
|
||||
// ENC28J60 MACON3 Register Bit Definitions
|
||||
#define MACON3_PADCFG2 0x80
|
||||
#define MACON3_PADCFG1 0x40
|
||||
#define MACON3_PADCFG0 0x20
|
||||
#define MACON3_TXCRCEN 0x10
|
||||
#define MACON3_PHDRLEN 0x08
|
||||
#define MACON3_HFRMLEN 0x04
|
||||
#define MACON3_FRMLNEN 0x02
|
||||
#define MACON3_FULDPX 0x01
|
||||
// ENC28J60 MICMD Register Bit Definitions
|
||||
#define MICMD_MIISCAN 0x02
|
||||
#define MICMD_MIIRD 0x01
|
||||
// ENC28J60 MISTAT Register Bit Definitions
|
||||
#define MISTAT_NVALID 0x04
|
||||
#define MISTAT_SCAN 0x02
|
||||
#define MISTAT_BUSY 0x01
|
||||
// ENC28J60 PHY PHCON1 Register Bit Definitions
|
||||
#define PHCON1_PRST 0x8000
|
||||
#define PHCON1_PLOOPBK 0x4000
|
||||
#define PHCON1_PPWRSV 0x0800
|
||||
#define PHCON1_PDPXMD 0x0100
|
||||
// ENC28J60 PHY PHSTAT1 Register Bit Definitions
|
||||
#define PHSTAT1_PFDPX 0x1000
|
||||
#define PHSTAT1_PHDPX 0x0800
|
||||
#define PHSTAT1_LLSTAT 0x0004
|
||||
#define PHSTAT1_JBSTAT 0x0002
|
||||
// ENC28J60 PHY PHCON2 Register Bit Definitions
|
||||
#define PHCON2_FRCLINK 0x4000
|
||||
#define PHCON2_TXDIS 0x2000
|
||||
#define PHCON2_JABBER 0x0400
|
||||
#define PHCON2_HDLDIS 0x0100
|
||||
|
||||
// ENC28J60 Packet Control Byte Bit Definitions
|
||||
#define PKTCTRL_PHUGEEN 0x08
|
||||
#define PKTCTRL_PPADEN 0x04
|
||||
#define PKTCTRL_PCRCEN 0x02
|
||||
#define PKTCTRL_POVERRIDE 0x01
|
||||
|
||||
// SPI operation codes
|
||||
#define ENC28J60_READ_CTRL_REG 0x00
|
||||
#define ENC28J60_READ_BUF_MEM 0x3A
|
||||
#define ENC28J60_WRITE_CTRL_REG 0x40
|
||||
#define ENC28J60_WRITE_BUF_MEM 0x7A
|
||||
#define ENC28J60_BIT_FIELD_SET 0x80
|
||||
#define ENC28J60_BIT_FIELD_CLR 0xA0
|
||||
#define ENC28J60_SOFT_RESET 0xFF
|
||||
|
||||
|
||||
// The RXSTART_INIT should be zero. See Rev. B4 Silicon Errata
|
||||
// buffer boundaries applied to internal 8K ram
|
||||
// the entire available packet buffer space is allocated
|
||||
//
|
||||
// start with recbuf at 0/
|
||||
#define RXSTART_INIT 0x0
|
||||
// receive buffer end. make sure this is an odd value ( See Rev. B1,B4,B5,B7 Silicon Errata 'Memory (Ethernet Buffer)')
|
||||
#define RXSTOP_INIT (0x1FFF-0x1800)
|
||||
// start TX buffer RXSTOP_INIT+1
|
||||
#define TXSTART_INIT (RXSTOP_INIT+1)
|
||||
// stp TX buffer at end of mem
|
||||
#define TXSTOP_INIT 0x1FFF
|
||||
//
|
||||
// max frame length which the conroller will accept:
|
||||
#define MAX_FRAMELEN 1500 // (note: maximum ethernet frame length would be 1518)
|
||||
//#define MAX_FRAMELEN 600
|
||||
|
||||
#endif
|
||||
@@ -1,168 +0,0 @@
|
||||
/*
|
||||
mempool.cpp - sleek implementation of a memory pool
|
||||
Copyright (c) 2013 Norbert Truchsess <norbert.truchsess@t-online.de>
|
||||
All rights reserved.
|
||||
|
||||
This program is free software: you can redistribute it and/or modify
|
||||
it under the terms of the GNU General Public License as published by
|
||||
the Free Software Foundation, either version 3 of the License, or
|
||||
(at your option) any later version.
|
||||
|
||||
This program is distributed in the hope that it will be useful,
|
||||
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
GNU General Public License for more details.
|
||||
|
||||
You should have received a copy of the GNU General Public License
|
||||
along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
#include "mempool.h"
|
||||
#include <string.h>
|
||||
|
||||
#define POOLOFFSET 1
|
||||
|
||||
struct memblock MemoryPool::blocks[MEMPOOL_NUM_MEMBLOCKS+1];
|
||||
|
||||
void
|
||||
MemoryPool::init()
|
||||
{
|
||||
memset(&blocks[0], 0, sizeof(blocks));
|
||||
blocks[POOLSTART].begin = MEMPOOL_STARTADDRESS;
|
||||
blocks[POOLSTART].size = 0;
|
||||
blocks[POOLSTART].nextblock = NOBLOCK;
|
||||
}
|
||||
|
||||
memhandle
|
||||
MemoryPool::allocBlock(memaddress size)
|
||||
{
|
||||
memblock* best = NULL;
|
||||
memhandle cur = POOLSTART;
|
||||
memblock* block = &blocks[POOLSTART];
|
||||
memaddress bestsize = MEMPOOL_SIZE + 1;
|
||||
|
||||
do
|
||||
{
|
||||
memhandle next = block->nextblock;
|
||||
memaddress freesize = ( next == NOBLOCK ? blocks[POOLSTART].begin + MEMPOOL_SIZE : blocks[next].begin) - block->begin - block->size;
|
||||
if (freesize == size)
|
||||
{
|
||||
best = &blocks[cur];
|
||||
goto found;
|
||||
}
|
||||
if (freesize > size && freesize < bestsize)
|
||||
{
|
||||
bestsize = freesize;
|
||||
best = &blocks[cur];
|
||||
}
|
||||
if (next == NOBLOCK)
|
||||
{
|
||||
if (best)
|
||||
goto found;
|
||||
else
|
||||
goto collect;
|
||||
}
|
||||
block = &blocks[next];
|
||||
cur = next;
|
||||
}
|
||||
while (true);
|
||||
|
||||
collect:
|
||||
{
|
||||
cur = POOLSTART;
|
||||
block = &blocks[POOLSTART];
|
||||
memhandle next;
|
||||
while ((next = block->nextblock) != NOBLOCK)
|
||||
{
|
||||
memaddress dest = block->begin + block->size;
|
||||
memblock* nextblock = &blocks[next];
|
||||
memaddress* src = &nextblock->begin;
|
||||
if (dest != *src)
|
||||
{
|
||||
#ifdef MEMPOOL_MEMBLOCK_MV
|
||||
MEMPOOL_MEMBLOCK_MV(dest,*src,nextblock->size);
|
||||
#endif
|
||||
*src = dest;
|
||||
}
|
||||
block = nextblock;
|
||||
}
|
||||
if (blocks[POOLSTART].begin + MEMPOOL_SIZE - block->begin - block->size >= size)
|
||||
best = block;
|
||||
else
|
||||
goto notfound;
|
||||
}
|
||||
|
||||
found:
|
||||
{
|
||||
block = &blocks[POOLOFFSET];
|
||||
for (cur = POOLOFFSET; cur < MEMPOOL_NUM_MEMBLOCKS + POOLOFFSET; cur++)
|
||||
{
|
||||
if (block->size)
|
||||
{
|
||||
block++;
|
||||
continue;
|
||||
}
|
||||
memaddress address = best->begin + best->size;
|
||||
#ifdef MEMBLOCK_ALLOC
|
||||
MEMBLOCK_ALLOC(address,size);
|
||||
#endif
|
||||
block->begin = address;
|
||||
block->size = size;
|
||||
block->nextblock = best->nextblock;
|
||||
best->nextblock = cur;
|
||||
return cur;
|
||||
}
|
||||
}
|
||||
|
||||
notfound: return NOBLOCK;
|
||||
}
|
||||
|
||||
void
|
||||
MemoryPool::freeBlock(memhandle handle)
|
||||
{
|
||||
if (handle == NOBLOCK)
|
||||
return;
|
||||
memblock *b = &blocks[POOLSTART];
|
||||
|
||||
do
|
||||
{
|
||||
memhandle next = b->nextblock;
|
||||
if (next == handle)
|
||||
{
|
||||
memblock *f = &blocks[next];
|
||||
#ifdef MEMBLOCK_FREE
|
||||
MEMBLOCK_FREE(f->begin,f->size);
|
||||
#endif
|
||||
b->nextblock = f->nextblock;
|
||||
f->size = 0;
|
||||
f->nextblock = NOBLOCK;
|
||||
return;
|
||||
}
|
||||
if (next == NOBLOCK)
|
||||
return;
|
||||
b = &blocks[next];
|
||||
}
|
||||
while (true);
|
||||
}
|
||||
|
||||
void
|
||||
MemoryPool::resizeBlock(memhandle handle, memaddress position)
|
||||
{
|
||||
memblock * block = &blocks[handle];
|
||||
block->begin += position;
|
||||
block->size -= position;
|
||||
}
|
||||
|
||||
void
|
||||
MemoryPool::resizeBlock(memhandle handle, memaddress position, memaddress size)
|
||||
{
|
||||
memblock * block = &blocks[handle];
|
||||
block->begin += position;
|
||||
block->size = size;
|
||||
}
|
||||
|
||||
memaddress
|
||||
MemoryPool::blockSize(memhandle handle)
|
||||
{
|
||||
return blocks[handle].size;
|
||||
}
|
||||
@@ -1,54 +0,0 @@
|
||||
/*
|
||||
mempool.h - sleek implementation of a memory pool
|
||||
Copyright (c) 2013 Norbert Truchsess <norbert.truchsess@t-online.de>
|
||||
All rights reserved.
|
||||
|
||||
This program is free software: you can redistribute it and/or modify
|
||||
it under the terms of the GNU General Public License as published by
|
||||
the Free Software Foundation, either version 3 of the License, or
|
||||
(at your option) any later version.
|
||||
|
||||
This program is distributed in the hope that it will be useful,
|
||||
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
GNU General Public License for more details.
|
||||
|
||||
You should have received a copy of the GNU General Public License
|
||||
along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
#ifndef MEMPOOL_H
|
||||
#define MEMPOOL_H
|
||||
|
||||
#include <inttypes.h>
|
||||
|
||||
#define POOLSTART 0
|
||||
#define NOBLOCK 0
|
||||
|
||||
#include "mempool_conf.h"
|
||||
|
||||
struct memblock
|
||||
{
|
||||
memaddress begin;
|
||||
memaddress size;
|
||||
memhandle nextblock;
|
||||
};
|
||||
|
||||
class MemoryPool
|
||||
{
|
||||
#ifdef MEMPOOLTEST_H
|
||||
friend class MemoryPoolTest;
|
||||
#endif
|
||||
|
||||
protected:
|
||||
static struct memblock blocks[MEMPOOL_NUM_MEMBLOCKS+1];
|
||||
|
||||
public:
|
||||
static void init();
|
||||
static memhandle allocBlock(memaddress);
|
||||
static void freeBlock(memhandle);
|
||||
static void resizeBlock(memhandle handle, memaddress position);
|
||||
static void resizeBlock(memhandle handle, memaddress position, memaddress size);
|
||||
static memaddress blockSize(memhandle);
|
||||
};
|
||||
#endif
|
||||
@@ -1,34 +0,0 @@
|
||||
#ifndef MEMPOOLCONF_H
|
||||
#define MEMPOOLCONF_H
|
||||
#include "uipethernet-conf.h"
|
||||
extern "C" {
|
||||
#include "uipopt.h"
|
||||
#include "enc28j60.h"
|
||||
}
|
||||
#include <inttypes.h>
|
||||
|
||||
typedef uint16_t memaddress;
|
||||
typedef uint8_t memhandle;
|
||||
|
||||
#if UIP_SOCKET_NUMPACKETS and UIP_CONNS
|
||||
#define NUM_TCP_MEMBLOCKS (UIP_SOCKET_NUMPACKETS*2)*UIP_CONNS
|
||||
#else
|
||||
#define NUM_TCP_MEMBLOCKS 0
|
||||
#endif
|
||||
|
||||
#if UIP_UDP and UIP_UDP_CONNS
|
||||
#define NUM_UDP_MEMBLOCKS ((2+UIP_UDP_BACKLOG)*UIP_UDP_CONNS)
|
||||
#else
|
||||
#define NUM_UDP_MEMBLOCKS 0
|
||||
#endif
|
||||
|
||||
#define MEMPOOL_NUM_MEMBLOCKS (NUM_TCP_MEMBLOCKS+NUM_UDP_MEMBLOCKS)
|
||||
|
||||
#define MEMPOOL_STARTADDRESS TXSTART_INIT+1
|
||||
#define MEMPOOL_SIZE TXSTOP_INIT-TXSTART_INIT
|
||||
|
||||
void enc28J60_mempool_block_move_callback(memaddress,memaddress,memaddress);
|
||||
|
||||
#define MEMPOOL_MEMBLOCK_MV(dest,src,size) enc28J60_mempool_block_move_callback(dest,src,size)
|
||||
|
||||
#endif
|
||||
@@ -1,185 +0,0 @@
|
||||
/**
|
||||
* UIPEthernet Project-specific configuration options
|
||||
* Copyright (c) 2013 Norbert Truchsess <norbert.truchsess@t-online.de>
|
||||
* @{
|
||||
*
|
||||
* uIP has a number of configuration options that can be overridden
|
||||
* for each project. These are kept in a project-specific uip-conf.h
|
||||
* file and all configuration names have the prefix UIP_CONF.
|
||||
*/
|
||||
|
||||
/*
|
||||
* Copyright (c) 2006, Swedish Institute of Computer Science.
|
||||
* All rights reserved.
|
||||
*
|
||||
* Redistribution and use in source and binary forms, with or without
|
||||
* modification, are permitted provided that the following conditions
|
||||
* are met:
|
||||
* 1. Redistributions of source code must retain the above copyright
|
||||
* notice, this list of conditions and the following disclaimer.
|
||||
* 2. Redistributions in binary form must reproduce the above copyright
|
||||
* notice, this list of conditions and the following disclaimer in the
|
||||
* documentation and/or other materials provided with the distribution.
|
||||
* 3. Neither the name of the Institute nor the names of its contributors
|
||||
* may be used to endorse or promote products derived from this software
|
||||
* without specific prior written permission.
|
||||
*
|
||||
* THIS SOFTWARE IS PROVIDED BY THE INSTITUTE AND CONTRIBUTORS ``AS IS'' AND
|
||||
* ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
|
||||
* IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
|
||||
* ARE DISCLAIMED. IN NO EVENT SHALL THE INSTITUTE OR CONTRIBUTORS BE LIABLE
|
||||
* FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
|
||||
* DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
|
||||
* OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
|
||||
* HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
|
||||
* LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
|
||||
* OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
|
||||
* SUCH DAMAGE.
|
||||
*
|
||||
* This file is part of the uIP TCP/IP stack
|
||||
*
|
||||
*/
|
||||
|
||||
#ifndef __UIP_CONF_H__
|
||||
#define __UIP_CONF_H__
|
||||
|
||||
#include <inttypes.h>
|
||||
#include "uipethernet-conf.h"
|
||||
|
||||
/**
|
||||
* 8 bit datatype
|
||||
*
|
||||
* This typedef defines the 8-bit type used throughout uIP.
|
||||
*
|
||||
* \hideinitializer
|
||||
*/
|
||||
typedef uint8_t u8_t;
|
||||
|
||||
/**
|
||||
* 16 bit datatype
|
||||
*
|
||||
* This typedef defines the 16-bit type used throughout uIP.
|
||||
*
|
||||
* \hideinitializer
|
||||
*/
|
||||
typedef uint16_t u16_t;
|
||||
|
||||
/**
|
||||
* Statistics datatype
|
||||
*
|
||||
* This typedef defines the dataype used for keeping statistics in
|
||||
* uIP.
|
||||
*
|
||||
* \hideinitializer
|
||||
*/
|
||||
typedef unsigned short uip_stats_t;
|
||||
|
||||
/**
|
||||
* Maximum number of TCP connections.
|
||||
* (see uipethernet-conf.h)
|
||||
* \hideinitializer
|
||||
*
|
||||
* #define UIP_CONF_MAX_CONNECTIONS 4
|
||||
*/
|
||||
|
||||
/**
|
||||
* Maximum number of listening TCP ports.
|
||||
*
|
||||
* \hideinitializer
|
||||
*/
|
||||
#define UIP_CONF_MAX_LISTENPORTS 4
|
||||
|
||||
/**
|
||||
* uIP buffer size.
|
||||
*
|
||||
* \hideinitializer
|
||||
*/
|
||||
#define UIP_CONF_BUFFER_SIZE 98
|
||||
//#define UIP_CONF_BUFFER_SIZE 118
|
||||
|
||||
/**
|
||||
* The TCP maximum segment size.
|
||||
*
|
||||
* This is should not be to set to more than
|
||||
* UIP_BUFSIZE - UIP_LLH_LEN - UIP_TCPIP_HLEN.
|
||||
*/
|
||||
|
||||
#define UIP_CONF_TCP_MSS 512
|
||||
|
||||
/**
|
||||
* The size of the advertised receiver's window.
|
||||
*
|
||||
* Should be set low (i.e., to the size of the uip_buf buffer) is the
|
||||
* application is slow to process incoming data, or high (32768 bytes)
|
||||
* if the application processes data quickly.
|
||||
*
|
||||
* \hideinitializer
|
||||
*/
|
||||
#define UIP_CONF_RECEIVE_WINDOW 512
|
||||
|
||||
/**
|
||||
* CPU byte order.
|
||||
*
|
||||
* \hideinitializer
|
||||
*/
|
||||
#define UIP_CONF_BYTE_ORDER __BYTE_ORDER__
|
||||
|
||||
/**
|
||||
* Logging on or off
|
||||
*
|
||||
* \hideinitializer
|
||||
*/
|
||||
#define UIP_CONF_LOGGING 0
|
||||
|
||||
/**
|
||||
* UDP support on or off
|
||||
* (see uipethernet-conf.h)
|
||||
* \hideinitializer
|
||||
*
|
||||
* #define UIP_CONF_UDP 1
|
||||
*
|
||||
* #define UIP_CONF_UDP_CONNS 4
|
||||
*/
|
||||
|
||||
/**
|
||||
* UDP checksums on or off
|
||||
*
|
||||
* \hideinitializer
|
||||
*/
|
||||
#define UIP_CONF_UDP_CHECKSUMS 1
|
||||
|
||||
/**
|
||||
* UDP Broadcast (receive) on or off
|
||||
* (see uipethernet-conf.h)
|
||||
* \hideinitializer
|
||||
*/
|
||||
#define UIP_CONF_BROADCAST 1
|
||||
|
||||
|
||||
/**
|
||||
* uIP statistics on or off
|
||||
*
|
||||
* \hideinitializer
|
||||
*/
|
||||
#define UIP_CONF_STATISTICS 0
|
||||
|
||||
// SLIP
|
||||
//#define UIP_CONF_LLH_LEN 0
|
||||
|
||||
typedef void* uip_tcp_appstate_t;
|
||||
|
||||
void uipclient_appcall(void);
|
||||
|
||||
#define UIP_APPCALL uipclient_appcall
|
||||
|
||||
typedef void* uip_udp_appstate_t;
|
||||
|
||||
void uipudp_appcall(void);
|
||||
|
||||
#define UIP_UDP_APPCALL uipudp_appcall
|
||||
|
||||
#define CC_REGISTER_ARG register
|
||||
|
||||
#define UIP_ARCH_CHKSUM 1
|
||||
|
||||
#endif /* __UIP_CONF_H__ */
|
||||
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
@@ -1,138 +0,0 @@
|
||||
/**
|
||||
* \addtogroup uip
|
||||
* {@
|
||||
*/
|
||||
|
||||
/**
|
||||
* \defgroup uiparch Architecture specific uIP functions
|
||||
* @{
|
||||
*
|
||||
* The functions in the architecture specific module implement the IP
|
||||
* check sum and 32-bit additions.
|
||||
*
|
||||
* The IP checksum calculation is the most computationally expensive
|
||||
* operation in the TCP/IP stack and it therefore pays off to
|
||||
* implement this in efficient assembler. The purpose of the uip-arch
|
||||
* module is to let the checksum functions to be implemented in
|
||||
* architecture specific assembler.
|
||||
*
|
||||
*/
|
||||
|
||||
/**
|
||||
* \file
|
||||
* Declarations of architecture specific functions.
|
||||
* \author Adam Dunkels <adam@dunkels.com>
|
||||
*/
|
||||
|
||||
/*
|
||||
* Copyright (c) 2001, Adam Dunkels.
|
||||
* All rights reserved.
|
||||
*
|
||||
* Redistribution and use in source and binary forms, with or without
|
||||
* modification, are permitted provided that the following conditions
|
||||
* are met:
|
||||
* 1. Redistributions of source code must retain the above copyright
|
||||
* notice, this list of conditions and the following disclaimer.
|
||||
* 2. Redistributions in binary form must reproduce the above copyright
|
||||
* notice, this list of conditions and the following disclaimer in the
|
||||
* documentation and/or other materials provided with the distribution.
|
||||
* 3. The name of the author may not be used to endorse or promote
|
||||
* products derived from this software without specific prior
|
||||
* written permission.
|
||||
*
|
||||
* THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS
|
||||
* OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
|
||||
* WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
|
||||
* ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY
|
||||
* DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
|
||||
* DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE
|
||||
* GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
|
||||
* INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
|
||||
* WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
|
||||
* NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
|
||||
* SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||
*
|
||||
* This file is part of the uIP TCP/IP stack.
|
||||
*
|
||||
* $Id: uip_arch.h,v 1.2 2006/06/07 09:15:19 adam Exp $
|
||||
*
|
||||
*/
|
||||
|
||||
#ifndef __UIP_ARCH_H__
|
||||
#define __UIP_ARCH_H__
|
||||
|
||||
#include "uip.h"
|
||||
|
||||
/**
|
||||
* Carry out a 32-bit addition.
|
||||
*
|
||||
* Because not all architectures for which uIP is intended has native
|
||||
* 32-bit arithmetic, uIP uses an external C function for doing the
|
||||
* required 32-bit additions in the TCP protocol processing. This
|
||||
* function should add the two arguments and place the result in the
|
||||
* global variable uip_acc32.
|
||||
*
|
||||
* \note The 32-bit integer pointed to by the op32 parameter and the
|
||||
* result in the uip_acc32 variable are in network byte order (big
|
||||
* endian).
|
||||
*
|
||||
* \param op32 A pointer to a 4-byte array representing a 32-bit
|
||||
* integer in network byte order (big endian).
|
||||
*
|
||||
* \param op16 A 16-bit integer in host byte order.
|
||||
*/
|
||||
void uip_add32(u8_t *op32, u16_t op16);
|
||||
|
||||
/**
|
||||
* Calculate the Internet checksum over a buffer.
|
||||
*
|
||||
* The Internet checksum is the one's complement of the one's
|
||||
* complement sum of all 16-bit words in the buffer.
|
||||
*
|
||||
* See RFC1071.
|
||||
*
|
||||
* \note This function is not called in the current version of uIP,
|
||||
* but future versions might make use of it.
|
||||
*
|
||||
* \param buf A pointer to the buffer over which the checksum is to be
|
||||
* computed.
|
||||
*
|
||||
* \param len The length of the buffer over which the checksum is to
|
||||
* be computed.
|
||||
*
|
||||
* \return The Internet checksum of the buffer.
|
||||
*/
|
||||
u16_t uip_chksum(u16_t *buf, u16_t len);
|
||||
|
||||
/**
|
||||
* Calculate the IP header checksum of the packet header in uip_buf.
|
||||
*
|
||||
* The IP header checksum is the Internet checksum of the 20 bytes of
|
||||
* the IP header.
|
||||
*
|
||||
* \return The IP header checksum of the IP header in the uip_buf
|
||||
* buffer.
|
||||
*/
|
||||
u16_t uip_ipchksum(void);
|
||||
|
||||
/**
|
||||
* Calculate the TCP checksum of the packet in uip_buf and uip_appdata.
|
||||
*
|
||||
* The TCP checksum is the Internet checksum of data contents of the
|
||||
* TCP segment, and a pseudo-header as defined in RFC793.
|
||||
*
|
||||
* \note The uip_appdata pointer that points to the packet data may
|
||||
* point anywhere in memory, so it is not possible to simply calculate
|
||||
* the Internet checksum of the contents of the uip_buf buffer.
|
||||
*
|
||||
* \return The TCP checksum of the TCP segment in uip_buf and pointed
|
||||
* to by uip_appdata.
|
||||
*/
|
||||
u16_t uip_tcpchksum(void);
|
||||
|
||||
u16_t uip_udpchksum(void);
|
||||
|
||||
/** @} */
|
||||
/** @} */
|
||||
|
||||
#endif /* __UIP_ARCH_H__ */
|
||||
@@ -1,422 +0,0 @@
|
||||
/**
|
||||
* \addtogroup uip
|
||||
* @{
|
||||
*/
|
||||
|
||||
/**
|
||||
* \defgroup uiparp uIP Address Resolution Protocol
|
||||
* @{
|
||||
*
|
||||
* The Address Resolution Protocol ARP is used for mapping between IP
|
||||
* addresses and link level addresses such as the Ethernet MAC
|
||||
* addresses. ARP uses broadcast queries to ask for the link level
|
||||
* address of a known IP address and the host which is configured with
|
||||
* the IP address for which the query was meant, will respond with its
|
||||
* link level address.
|
||||
*
|
||||
* \note This ARP implementation only supports Ethernet.
|
||||
*/
|
||||
|
||||
/**
|
||||
* \file
|
||||
* Implementation of the ARP Address Resolution Protocol.
|
||||
* \author Adam Dunkels <adam@dunkels.com>
|
||||
*
|
||||
*/
|
||||
|
||||
/*
|
||||
* Copyright (c) 2001-2003, Adam Dunkels.
|
||||
* All rights reserved.
|
||||
*
|
||||
* Redistribution and use in source and binary forms, with or without
|
||||
* modification, are permitted provided that the following conditions
|
||||
* are met:
|
||||
* 1. Redistributions of source code must retain the above copyright
|
||||
* notice, this list of conditions and the following disclaimer.
|
||||
* 2. Redistributions in binary form must reproduce the above copyright
|
||||
* notice, this list of conditions and the following disclaimer in the
|
||||
* documentation and/or other materials provided with the distribution.
|
||||
* 3. The name of the author may not be used to endorse or promote
|
||||
* products derived from this software without specific prior
|
||||
* written permission.
|
||||
*
|
||||
* THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS
|
||||
* OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
|
||||
* WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
|
||||
* ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY
|
||||
* DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
|
||||
* DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE
|
||||
* GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
|
||||
* INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
|
||||
* WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
|
||||
* NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
|
||||
* SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||
*
|
||||
* This file is part of the uIP TCP/IP stack.
|
||||
*
|
||||
* $Id: uip_arp.c,v 1.8 2006/06/02 23:36:21 adam Exp $
|
||||
*
|
||||
*/
|
||||
|
||||
|
||||
#include "uip_arp.h"
|
||||
|
||||
#include <string.h>
|
||||
|
||||
struct arp_hdr {
|
||||
struct uip_eth_hdr ethhdr;
|
||||
u16_t hwtype;
|
||||
u16_t protocol;
|
||||
u8_t hwlen;
|
||||
u8_t protolen;
|
||||
u16_t opcode;
|
||||
struct uip_eth_addr shwaddr;
|
||||
u16_t sipaddr[2];
|
||||
struct uip_eth_addr dhwaddr;
|
||||
u16_t dipaddr[2];
|
||||
};
|
||||
|
||||
struct ethip_hdr {
|
||||
struct uip_eth_hdr ethhdr;
|
||||
/* IP header. */
|
||||
u8_t vhl,
|
||||
tos,
|
||||
len[2],
|
||||
ipid[2],
|
||||
ipoffset[2],
|
||||
ttl,
|
||||
proto;
|
||||
u16_t ipchksum;
|
||||
u16_t srcipaddr[2],
|
||||
destipaddr[2];
|
||||
};
|
||||
|
||||
#define ARP_REQUEST 1
|
||||
#define ARP_REPLY 2
|
||||
|
||||
#define ARP_HWTYPE_ETH 1
|
||||
|
||||
struct arp_entry {
|
||||
u16_t ipaddr[2];
|
||||
struct uip_eth_addr ethaddr;
|
||||
u8_t time;
|
||||
};
|
||||
|
||||
static const struct uip_eth_addr broadcast_ethaddr =
|
||||
{{0xff,0xff,0xff,0xff,0xff,0xff}};
|
||||
static const u16_t broadcast_ipaddr[2] = {0xffff,0xffff};
|
||||
|
||||
static struct arp_entry arp_table[UIP_ARPTAB_SIZE];
|
||||
static u16_t ipaddr[2];
|
||||
static u8_t i, c;
|
||||
|
||||
static u8_t arptime;
|
||||
static u8_t tmpage;
|
||||
|
||||
#define BUF ((struct arp_hdr *)&uip_buf[0])
|
||||
#define IPBUF ((struct ethip_hdr *)&uip_buf[0])
|
||||
/*-----------------------------------------------------------------------------------*/
|
||||
/**
|
||||
* Initialize the ARP module.
|
||||
*
|
||||
*/
|
||||
/*-----------------------------------------------------------------------------------*/
|
||||
void
|
||||
uip_arp_init(void)
|
||||
{
|
||||
for(i = 0; i < UIP_ARPTAB_SIZE; ++i) {
|
||||
memset(arp_table[i].ipaddr, 0, 4);
|
||||
}
|
||||
}
|
||||
/*-----------------------------------------------------------------------------------*/
|
||||
/**
|
||||
* Periodic ARP processing function.
|
||||
*
|
||||
* This function performs periodic timer processing in the ARP module
|
||||
* and should be called at regular intervals. The recommended interval
|
||||
* is 10 seconds between the calls.
|
||||
*
|
||||
*/
|
||||
/*-----------------------------------------------------------------------------------*/
|
||||
void
|
||||
uip_arp_timer(void)
|
||||
{
|
||||
struct arp_entry *tabptr;
|
||||
|
||||
++arptime;
|
||||
for(i = 0; i < UIP_ARPTAB_SIZE; ++i) {
|
||||
tabptr = &arp_table[i];
|
||||
if((tabptr->ipaddr[0] | tabptr->ipaddr[1]) != 0 &&
|
||||
arptime - tabptr->time >= UIP_ARP_MAXAGE) {
|
||||
memset(tabptr->ipaddr, 0, 4);
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
/*-----------------------------------------------------------------------------------*/
|
||||
static void
|
||||
uip_arp_update(u16_t *ipaddr, struct uip_eth_addr *ethaddr)
|
||||
{
|
||||
register struct arp_entry *tabptr;
|
||||
/* Walk through the ARP mapping table and try to find an entry to
|
||||
update. If none is found, the IP -> MAC address mapping is
|
||||
inserted in the ARP table. */
|
||||
for(i = 0; i < UIP_ARPTAB_SIZE; ++i) {
|
||||
|
||||
tabptr = &arp_table[i];
|
||||
/* Only check those entries that are actually in use. */
|
||||
if(tabptr->ipaddr[0] != 0 &&
|
||||
tabptr->ipaddr[1] != 0) {
|
||||
|
||||
/* Check if the source IP address of the incoming packet matches
|
||||
the IP address in this ARP table entry. */
|
||||
if(memcmp(ipaddr, tabptr->ipaddr, 4) == 0) {
|
||||
|
||||
/* An old entry found, update this and return. */
|
||||
memcpy(tabptr->ethaddr.addr, ethaddr->addr, 6);
|
||||
tabptr->time = arptime;
|
||||
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/* If we get here, no existing ARP table entry was found, so we
|
||||
create one. */
|
||||
|
||||
/* First, we try to find an unused entry in the ARP table. */
|
||||
for(i = 0; i < UIP_ARPTAB_SIZE; ++i) {
|
||||
tabptr = &arp_table[i];
|
||||
if(tabptr->ipaddr[0] == 0 &&
|
||||
tabptr->ipaddr[1] == 0) {
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
/* If no unused entry is found, we try to find the oldest entry and
|
||||
throw it away. */
|
||||
if(i == UIP_ARPTAB_SIZE) {
|
||||
tmpage = 0;
|
||||
c = 0;
|
||||
for(i = 0; i < UIP_ARPTAB_SIZE; ++i) {
|
||||
tabptr = &arp_table[i];
|
||||
if(arptime - tabptr->time > tmpage) {
|
||||
tmpage = arptime - tabptr->time;
|
||||
c = i;
|
||||
}
|
||||
}
|
||||
i = c;
|
||||
tabptr = &arp_table[i];
|
||||
}
|
||||
|
||||
/* Now, i is the ARP table entry which we will fill with the new
|
||||
information. */
|
||||
memcpy(tabptr->ipaddr, ipaddr, 4);
|
||||
memcpy(tabptr->ethaddr.addr, ethaddr->addr, 6);
|
||||
tabptr->time = arptime;
|
||||
}
|
||||
/*-----------------------------------------------------------------------------------*/
|
||||
/**
|
||||
* ARP processing for incoming IP packets
|
||||
*
|
||||
* This function should be called by the device driver when an IP
|
||||
* packet has been received. The function will check if the address is
|
||||
* in the ARP cache, and if so the ARP cache entry will be
|
||||
* refreshed. If no ARP cache entry was found, a new one is created.
|
||||
*
|
||||
* This function expects an IP packet with a prepended Ethernet header
|
||||
* in the uip_buf[] buffer, and the length of the packet in the global
|
||||
* variable uip_len.
|
||||
*/
|
||||
/*-----------------------------------------------------------------------------------*/
|
||||
//#if 0
|
||||
void
|
||||
uip_arp_ipin(void)
|
||||
{
|
||||
uip_len -= sizeof(struct uip_eth_hdr);
|
||||
|
||||
/* Only insert/update an entry if the source IP address of the
|
||||
incoming IP packet comes from a host on the local network. */
|
||||
if((IPBUF->srcipaddr[0] & uip_netmask[0]) !=
|
||||
(uip_hostaddr[0] & uip_netmask[0])) {
|
||||
return;
|
||||
}
|
||||
if((IPBUF->srcipaddr[1] & uip_netmask[1]) !=
|
||||
(uip_hostaddr[1] & uip_netmask[1])) {
|
||||
return;
|
||||
}
|
||||
uip_arp_update(IPBUF->srcipaddr, &(IPBUF->ethhdr.src));
|
||||
|
||||
return;
|
||||
}
|
||||
//#endif /* 0 */
|
||||
/*-----------------------------------------------------------------------------------*/
|
||||
/**
|
||||
* ARP processing for incoming ARP packets.
|
||||
*
|
||||
* This function should be called by the device driver when an ARP
|
||||
* packet has been received. The function will act differently
|
||||
* depending on the ARP packet type: if it is a reply for a request
|
||||
* that we previously sent out, the ARP cache will be filled in with
|
||||
* the values from the ARP reply. If the incoming ARP packet is an ARP
|
||||
* request for our IP address, an ARP reply packet is created and put
|
||||
* into the uip_buf[] buffer.
|
||||
*
|
||||
* When the function returns, the value of the global variable uip_len
|
||||
* indicates whether the device driver should send out a packet or
|
||||
* not. If uip_len is zero, no packet should be sent. If uip_len is
|
||||
* non-zero, it contains the length of the outbound packet that is
|
||||
* present in the uip_buf[] buffer.
|
||||
*
|
||||
* This function expects an ARP packet with a prepended Ethernet
|
||||
* header in the uip_buf[] buffer, and the length of the packet in the
|
||||
* global variable uip_len.
|
||||
*/
|
||||
/*-----------------------------------------------------------------------------------*/
|
||||
void
|
||||
uip_arp_arpin(void)
|
||||
{
|
||||
|
||||
if(uip_len < sizeof(struct arp_hdr)) {
|
||||
uip_len = 0;
|
||||
return;
|
||||
}
|
||||
uip_len = 0;
|
||||
|
||||
switch(BUF->opcode) {
|
||||
case HTONS(ARP_REQUEST):
|
||||
/* ARP request. If it asked for our address, we send out a
|
||||
reply. */
|
||||
if(uip_ipaddr_cmp(BUF->dipaddr, uip_hostaddr)) {
|
||||
/* First, we register the one who made the request in our ARP
|
||||
table, since it is likely that we will do more communication
|
||||
with this host in the future. */
|
||||
uip_arp_update(BUF->sipaddr, &BUF->shwaddr);
|
||||
|
||||
/* The reply opcode is 2. */
|
||||
BUF->opcode = HTONS(2);
|
||||
|
||||
memcpy(BUF->dhwaddr.addr, BUF->shwaddr.addr, 6);
|
||||
memcpy(BUF->shwaddr.addr, uip_ethaddr.addr, 6);
|
||||
memcpy(BUF->ethhdr.src.addr, uip_ethaddr.addr, 6);
|
||||
memcpy(BUF->ethhdr.dest.addr, BUF->dhwaddr.addr, 6);
|
||||
|
||||
BUF->dipaddr[0] = BUF->sipaddr[0];
|
||||
BUF->dipaddr[1] = BUF->sipaddr[1];
|
||||
BUF->sipaddr[0] = uip_hostaddr[0];
|
||||
BUF->sipaddr[1] = uip_hostaddr[1];
|
||||
|
||||
BUF->ethhdr.type = HTONS(UIP_ETHTYPE_ARP);
|
||||
uip_len = sizeof(struct arp_hdr);
|
||||
}
|
||||
break;
|
||||
case HTONS(ARP_REPLY):
|
||||
/* ARP reply. We insert or update the ARP table if it was meant
|
||||
for us. */
|
||||
if(uip_ipaddr_cmp(BUF->dipaddr, uip_hostaddr)) {
|
||||
uip_arp_update(BUF->sipaddr, &BUF->shwaddr);
|
||||
}
|
||||
break;
|
||||
}
|
||||
|
||||
return;
|
||||
}
|
||||
/*-----------------------------------------------------------------------------------*/
|
||||
/**
|
||||
* Prepend Ethernet header to an outbound IP packet and see if we need
|
||||
* to send out an ARP request.
|
||||
*
|
||||
* This function should be called before sending out an IP packet. The
|
||||
* function checks the destination IP address of the IP packet to see
|
||||
* what Ethernet MAC address that should be used as a destination MAC
|
||||
* address on the Ethernet.
|
||||
*
|
||||
* If the destination IP address is in the local network (determined
|
||||
* by logical ANDing of netmask and our IP address), the function
|
||||
* checks the ARP cache to see if an entry for the destination IP
|
||||
* address is found. If so, an Ethernet header is prepended and the
|
||||
* function returns. If no ARP cache entry is found for the
|
||||
* destination IP address, the packet in the uip_buf[] is replaced by
|
||||
* an ARP request packet for the IP address. The IP packet is dropped
|
||||
* and it is assumed that they higher level protocols (e.g., TCP)
|
||||
* eventually will retransmit the dropped packet.
|
||||
*
|
||||
* If the destination IP address is not on the local network, the IP
|
||||
* address of the default router is used instead.
|
||||
*
|
||||
* When the function returns, a packet is present in the uip_buf[]
|
||||
* buffer, and the length of the packet is in the global variable
|
||||
* uip_len.
|
||||
*/
|
||||
/*-----------------------------------------------------------------------------------*/
|
||||
void
|
||||
uip_arp_out(void)
|
||||
{
|
||||
struct arp_entry *tabptr;
|
||||
|
||||
/* Find the destination IP address in the ARP table and construct
|
||||
the Ethernet header. If the destination IP addres isn't on the
|
||||
local network, we use the default router's IP address instead.
|
||||
|
||||
If not ARP table entry is found, we overwrite the original IP
|
||||
packet with an ARP request for the IP address. */
|
||||
|
||||
/* First check if destination is a local broadcast. */
|
||||
if(uip_ipaddr_cmp(IPBUF->destipaddr, broadcast_ipaddr)) {
|
||||
memcpy(IPBUF->ethhdr.dest.addr, broadcast_ethaddr.addr, 6);
|
||||
} else {
|
||||
/* Check if the destination address is on the local network. */
|
||||
if(!uip_ipaddr_maskcmp(IPBUF->destipaddr, uip_hostaddr, uip_netmask)) {
|
||||
/* Destination address was not on the local network, so we need to
|
||||
use the default router's IP address instead of the destination
|
||||
address when determining the MAC address. */
|
||||
uip_ipaddr_copy(ipaddr, uip_draddr);
|
||||
} else {
|
||||
/* Else, we use the destination IP address. */
|
||||
uip_ipaddr_copy(ipaddr, IPBUF->destipaddr);
|
||||
}
|
||||
|
||||
for(i = 0; i < UIP_ARPTAB_SIZE; ++i) {
|
||||
tabptr = &arp_table[i];
|
||||
if(uip_ipaddr_cmp(ipaddr, tabptr->ipaddr)) {
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if(i == UIP_ARPTAB_SIZE) {
|
||||
/* The destination address was not in our ARP table, so we
|
||||
overwrite the IP packet with an ARP request. */
|
||||
|
||||
memset(BUF->ethhdr.dest.addr, 0xff, 6);
|
||||
memset(BUF->dhwaddr.addr, 0x00, 6);
|
||||
memcpy(BUF->ethhdr.src.addr, uip_ethaddr.addr, 6);
|
||||
memcpy(BUF->shwaddr.addr, uip_ethaddr.addr, 6);
|
||||
|
||||
uip_ipaddr_copy(BUF->dipaddr, ipaddr);
|
||||
uip_ipaddr_copy(BUF->sipaddr, uip_hostaddr);
|
||||
BUF->opcode = HTONS(ARP_REQUEST); /* ARP request. */
|
||||
BUF->hwtype = HTONS(ARP_HWTYPE_ETH);
|
||||
BUF->protocol = HTONS(UIP_ETHTYPE_IP);
|
||||
BUF->hwlen = 6;
|
||||
BUF->protolen = 4;
|
||||
BUF->ethhdr.type = HTONS(UIP_ETHTYPE_ARP);
|
||||
|
||||
uip_appdata = &uip_buf[UIP_TCPIP_HLEN + UIP_LLH_LEN];
|
||||
|
||||
uip_len = sizeof(struct arp_hdr);
|
||||
return;
|
||||
}
|
||||
|
||||
/* Build an ethernet header. */
|
||||
memcpy(IPBUF->ethhdr.dest.addr, tabptr->ethaddr.addr, 6);
|
||||
}
|
||||
memcpy(IPBUF->ethhdr.src.addr, uip_ethaddr.addr, 6);
|
||||
|
||||
IPBUF->ethhdr.type = HTONS(UIP_ETHTYPE_IP);
|
||||
|
||||
uip_len += sizeof(struct uip_eth_hdr);
|
||||
}
|
||||
/*-----------------------------------------------------------------------------------*/
|
||||
|
||||
/** @} */
|
||||
/** @} */
|
||||
@@ -1,144 +0,0 @@
|
||||
/**
|
||||
* \addtogroup uip
|
||||
* @{
|
||||
*/
|
||||
|
||||
/**
|
||||
* \addtogroup uiparp
|
||||
* @{
|
||||
*/
|
||||
|
||||
/**
|
||||
* \file
|
||||
* Macros and definitions for the ARP module.
|
||||
* \author Adam Dunkels <adam@dunkels.com>
|
||||
*/
|
||||
|
||||
|
||||
/*
|
||||
* Copyright (c) 2001-2003, Adam Dunkels.
|
||||
* All rights reserved.
|
||||
*
|
||||
* Redistribution and use in source and binary forms, with or without
|
||||
* modification, are permitted provided that the following conditions
|
||||
* are met:
|
||||
* 1. Redistributions of source code must retain the above copyright
|
||||
* notice, this list of conditions and the following disclaimer.
|
||||
* 2. Redistributions in binary form must reproduce the above copyright
|
||||
* notice, this list of conditions and the following disclaimer in the
|
||||
* documentation and/or other materials provided with the distribution.
|
||||
* 3. The name of the author may not be used to endorse or promote
|
||||
* products derived from this software without specific prior
|
||||
* written permission.
|
||||
*
|
||||
* THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS
|
||||
* OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
|
||||
* WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
|
||||
* ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY
|
||||
* DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
|
||||
* DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE
|
||||
* GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
|
||||
* INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
|
||||
* WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
|
||||
* NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
|
||||
* SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||
*
|
||||
* This file is part of the uIP TCP/IP stack.
|
||||
*
|
||||
* $Id: uip_arp.h,v 1.5 2006/06/11 21:46:39 adam Exp $
|
||||
*
|
||||
*/
|
||||
|
||||
#ifndef __UIP_ARP_H__
|
||||
#define __UIP_ARP_H__
|
||||
|
||||
#include "uip.h"
|
||||
|
||||
|
||||
extern struct uip_eth_addr uip_ethaddr;
|
||||
|
||||
/**
|
||||
* The Ethernet header.
|
||||
*/
|
||||
struct uip_eth_hdr {
|
||||
struct uip_eth_addr dest;
|
||||
struct uip_eth_addr src;
|
||||
u16_t type;
|
||||
};
|
||||
|
||||
#define UIP_ETHTYPE_ARP 0x0806
|
||||
#define UIP_ETHTYPE_IP 0x0800
|
||||
#define UIP_ETHTYPE_IP6 0x86dd
|
||||
|
||||
|
||||
/* The uip_arp_init() function must be called before any of the other
|
||||
ARP functions. */
|
||||
void uip_arp_init(void);
|
||||
|
||||
/* The uip_arp_ipin() function should be called whenever an IP packet
|
||||
arrives from the Ethernet. This function refreshes the ARP table or
|
||||
inserts a new mapping if none exists. The function assumes that an
|
||||
IP packet with an Ethernet header is present in the uip_buf buffer
|
||||
and that the length of the packet is in the uip_len variable. */
|
||||
void uip_arp_ipin(void);
|
||||
//#define uip_arp_ipin()
|
||||
|
||||
/* The uip_arp_arpin() should be called when an ARP packet is received
|
||||
by the Ethernet driver. This function also assumes that the
|
||||
Ethernet frame is present in the uip_buf buffer. When the
|
||||
uip_arp_arpin() function returns, the contents of the uip_buf
|
||||
buffer should be sent out on the Ethernet if the uip_len variable
|
||||
is > 0. */
|
||||
void uip_arp_arpin(void);
|
||||
|
||||
/* The uip_arp_out() function should be called when an IP packet
|
||||
should be sent out on the Ethernet. This function creates an
|
||||
Ethernet header before the IP header in the uip_buf buffer. The
|
||||
Ethernet header will have the correct Ethernet MAC destination
|
||||
address filled in if an ARP table entry for the destination IP
|
||||
address (or the IP address of the default router) is present. If no
|
||||
such table entry is found, the IP packet is overwritten with an ARP
|
||||
request and we rely on TCP to retransmit the packet that was
|
||||
overwritten. In any case, the uip_len variable holds the length of
|
||||
the Ethernet frame that should be transmitted. */
|
||||
void uip_arp_out(void);
|
||||
|
||||
/* The uip_arp_timer() function should be called every ten seconds. It
|
||||
is responsible for flushing old entries in the ARP table. */
|
||||
void uip_arp_timer(void);
|
||||
|
||||
/** @} */
|
||||
|
||||
/**
|
||||
* \addtogroup uipconffunc
|
||||
* @{
|
||||
*/
|
||||
|
||||
|
||||
/**
|
||||
* Specifiy the Ethernet MAC address.
|
||||
*
|
||||
* The ARP code needs to know the MAC address of the Ethernet card in
|
||||
* order to be able to respond to ARP queries and to generate working
|
||||
* Ethernet headers.
|
||||
*
|
||||
* \note This macro only specifies the Ethernet MAC address to the ARP
|
||||
* code. It cannot be used to change the MAC address of the Ethernet
|
||||
* card.
|
||||
*
|
||||
* \param eaddr A pointer to a struct uip_eth_addr containing the
|
||||
* Ethernet MAC address of the Ethernet card.
|
||||
*
|
||||
* \hideinitializer
|
||||
*/
|
||||
#define uip_setethaddr(eaddr) do {uip_ethaddr.addr[0] = eaddr.addr[0]; \
|
||||
uip_ethaddr.addr[1] = eaddr.addr[1];\
|
||||
uip_ethaddr.addr[2] = eaddr.addr[2];\
|
||||
uip_ethaddr.addr[3] = eaddr.addr[3];\
|
||||
uip_ethaddr.addr[4] = eaddr.addr[4];\
|
||||
uip_ethaddr.addr[5] = eaddr.addr[5];} while(0)
|
||||
|
||||
/** @} */
|
||||
/** @} */
|
||||
|
||||
#endif /* __UIP_ARP_H__ */
|
||||
@@ -1,48 +0,0 @@
|
||||
#ifndef UIPETHERNET_CONF_H
|
||||
#define UIPETHERNET_CONF_H
|
||||
|
||||
// https://github.com/jandrassy/EthernetENC/wiki/Settings
|
||||
|
||||
/* for TCP */
|
||||
#ifndef UIP_SOCKET_NUMPACKETS
|
||||
#define UIP_SOCKET_NUMPACKETS 3
|
||||
#endif
|
||||
#ifndef UIP_CONF_MAX_CONNECTIONS
|
||||
#define UIP_CONF_MAX_CONNECTIONS 4
|
||||
#endif
|
||||
|
||||
/* for UDP
|
||||
* set UIP_CONF_UDP to 0 to disable UDP (saves aprox. 4kB flash) */
|
||||
#ifndef UIP_CONF_UDP
|
||||
#define UIP_CONF_UDP 1
|
||||
#endif
|
||||
#ifndef UIP_CONF_UDP_CONNS
|
||||
#define UIP_CONF_UDP_CONNS 2
|
||||
#endif
|
||||
|
||||
/**
|
||||
* size of received UDP messages backlog. it must be at least 1
|
||||
*/
|
||||
#ifndef UIP_UDP_BACKLOG
|
||||
#define UIP_UDP_BACKLOG 2
|
||||
#endif
|
||||
|
||||
/* timeout in ms for attempts to get a free memory block to write
|
||||
* before returning number of bytes sent so far
|
||||
* set to 0 to block until connection is closed by timeout */
|
||||
#ifndef UIP_WRITE_TIMEOUT
|
||||
#define UIP_WRITE_TIMEOUT 2000
|
||||
#endif
|
||||
|
||||
/* timeout after which UIPClient::connect gives up. The timeout is specified in seconds.
|
||||
* if set to a number <= 0 connect will timeout when uIP does (which might be longer than you expect...) */
|
||||
#ifndef UIP_CONNECT_TIMEOUT
|
||||
#define UIP_CONNECT_TIMEOUT 5
|
||||
#endif
|
||||
|
||||
/* periodic timer for uip (in ms) */
|
||||
#ifndef UIP_PERIODIC_TIMER
|
||||
#define UIP_PERIODIC_TIMER 100
|
||||
#endif
|
||||
|
||||
#endif
|
||||
@@ -1,555 +0,0 @@
|
||||
/**
|
||||
* \defgroup uipopt Configuration options for uIP
|
||||
* @{
|
||||
*
|
||||
* uIP is configured using the per-project configuration file
|
||||
* uipopt.h. This file contains all compile-time options for uIP and
|
||||
* should be tweaked to match each specific project. The uIP
|
||||
* distribution contains a documented example "uipopt.h" that can be
|
||||
* copied and modified for each project.
|
||||
*
|
||||
* \note Most of the configuration options in the uipopt.h should not
|
||||
* be changed, but rather the per-project uip-conf.h file.
|
||||
*/
|
||||
|
||||
/**
|
||||
* \file
|
||||
* Configuration options for uIP.
|
||||
* \author Adam Dunkels <adam@dunkels.com>
|
||||
*
|
||||
* This file is used for tweaking various configuration options for
|
||||
* uIP. You should make a copy of this file into one of your project's
|
||||
* directories instead of editing this example "uipopt.h" file that
|
||||
* comes with the uIP distribution.
|
||||
*/
|
||||
|
||||
/*
|
||||
* Copyright (c) 2001-2003, Adam Dunkels.
|
||||
* All rights reserved.
|
||||
*
|
||||
* Redistribution and use in source and binary forms, with or without
|
||||
* modification, are permitted provided that the following conditions
|
||||
* are met:
|
||||
* 1. Redistributions of source code must retain the above copyright
|
||||
* notice, this list of conditions and the following disclaimer.
|
||||
* 2. Redistributions in binary form must reproduce the above copyright
|
||||
* notice, this list of conditions and the following disclaimer in the
|
||||
* documentation and/or other materials provided with the distribution.
|
||||
* 3. The name of the author may not be used to endorse or promote
|
||||
* products derived from this software without specific prior
|
||||
* written permission.
|
||||
*
|
||||
* THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS
|
||||
* OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
|
||||
* WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
|
||||
* ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY
|
||||
* DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
|
||||
* DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE
|
||||
* GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
|
||||
* INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
|
||||
* WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
|
||||
* NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
|
||||
* SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||
*
|
||||
* This file is part of the uIP TCP/IP stack.
|
||||
*
|
||||
* $Id: uipopt.h,v 1.4 2006/06/12 08:00:31 adam Exp $
|
||||
*
|
||||
*/
|
||||
|
||||
#ifndef __UIPOPT_H__
|
||||
#define __UIPOPT_H__
|
||||
|
||||
#ifndef UIP_LITTLE_ENDIAN
|
||||
#if defined(LITTLE_ENDIAN)
|
||||
#define UIP_LITTLE_ENDIAN LITTLE_ENDIAN
|
||||
#elif defined(__ORDER_LITTLE_ENDIAN__)
|
||||
#define UIP_LITTLE_ENDIAN __ORDER_LITTLE_ENDIAN__
|
||||
#else
|
||||
#define UIP_LITTLE_ENDIAN 1234
|
||||
#endif
|
||||
#endif /* UIP_LITTLE_ENDIAN */
|
||||
#ifndef UIP_BIG_ENDIAN
|
||||
#if defined(BIG_ENDIAN)
|
||||
#define UIP_BIG_ENDIAN BIG_ENDIAN
|
||||
#elif defined(__ORDER_BIG_ENDIAN__)
|
||||
#define UIP_BIG_ENDIAN __ORDER_BIG_ENDIAN__
|
||||
#else
|
||||
#define UIP_BIG_ENDIAN 4321
|
||||
#endif
|
||||
#endif /* UIP_BIG_ENDIAN */
|
||||
|
||||
#include "uip-conf.h"
|
||||
|
||||
/*------------------------------------------------------------------------------*/
|
||||
|
||||
/**
|
||||
* \name Static configuration options
|
||||
* @{
|
||||
*
|
||||
* These configuration options can be used for setting the IP address
|
||||
* settings statically, but only if UIP_FIXEDADDR is set to 1. The
|
||||
* configuration options for a specific node includes IP address,
|
||||
* netmask and default router as well as the Ethernet address. The
|
||||
* netmask, default router and Ethernet address are appliciable only
|
||||
* if uIP should be run over Ethernet.
|
||||
*
|
||||
* All of these should be changed to suit your project.
|
||||
*/
|
||||
|
||||
/**
|
||||
* Determines if uIP should use a fixed IP address or not.
|
||||
*
|
||||
* If uIP should use a fixed IP address, the settings are set in the
|
||||
* uipopt.h file. If not, the macros uip_sethostaddr(),
|
||||
* uip_setdraddr() and uip_setnetmask() should be used instead.
|
||||
*
|
||||
* \hideinitializer
|
||||
*/
|
||||
#define UIP_FIXEDADDR 0
|
||||
|
||||
/**
|
||||
* Ping IP address asignment.
|
||||
*
|
||||
* uIP uses a "ping" packets for setting its own IP address if this
|
||||
* option is set. If so, uIP will start with an empty IP address and
|
||||
* the destination IP address of the first incoming "ping" (ICMP echo)
|
||||
* packet will be used for setting the hosts IP address.
|
||||
*
|
||||
* \note This works only if UIP_FIXEDADDR is 0.
|
||||
*
|
||||
* \hideinitializer
|
||||
*/
|
||||
#ifdef UIP_CONF_PINGADDRCONF
|
||||
#define UIP_PINGADDRCONF UIP_CONF_PINGADDRCONF
|
||||
#else /* UIP_CONF_PINGADDRCONF */
|
||||
#define UIP_PINGADDRCONF 0
|
||||
#endif /* UIP_CONF_PINGADDRCONF */
|
||||
|
||||
|
||||
/**
|
||||
* Specifies if the uIP ARP module should be compiled with a fixed
|
||||
* Ethernet MAC address or not.
|
||||
*
|
||||
* If this configuration option is 0, the macro uip_setethaddr() can
|
||||
* be used to specify the Ethernet address at run-time.
|
||||
*
|
||||
* \hideinitializer
|
||||
*/
|
||||
#define UIP_FIXEDETHADDR 0
|
||||
|
||||
/** @} */
|
||||
/*------------------------------------------------------------------------------*/
|
||||
/**
|
||||
* \name IP configuration options
|
||||
* @{
|
||||
*
|
||||
*/
|
||||
/**
|
||||
* The IP TTL (time to live) of IP packets sent by uIP.
|
||||
*
|
||||
* This should normally not be changed.
|
||||
*/
|
||||
#define UIP_TTL 64
|
||||
|
||||
/**
|
||||
* Turn on support for IP packet reassembly.
|
||||
*
|
||||
* uIP supports reassembly of fragmented IP packets. This features
|
||||
* requires an additonal amount of RAM to hold the reassembly buffer
|
||||
* and the reassembly code size is approximately 700 bytes. The
|
||||
* reassembly buffer is of the same size as the uip_buf buffer
|
||||
* (configured by UIP_BUFSIZE).
|
||||
*
|
||||
* \note IP packet reassembly is not heavily tested.
|
||||
*
|
||||
* \hideinitializer
|
||||
*/
|
||||
#define UIP_REASSEMBLY 0
|
||||
|
||||
/**
|
||||
* The maximum time an IP fragment should wait in the reassembly
|
||||
* buffer before it is dropped.
|
||||
*
|
||||
*/
|
||||
#define UIP_REASS_MAXAGE 40
|
||||
|
||||
/** @} */
|
||||
|
||||
/*------------------------------------------------------------------------------*/
|
||||
/**
|
||||
* \name UDP configuration options
|
||||
* @{
|
||||
*/
|
||||
|
||||
/**
|
||||
* Toggles wether UDP support should be compiled in or not.
|
||||
*
|
||||
* \hideinitializer
|
||||
*/
|
||||
#ifdef UIP_CONF_UDP
|
||||
#define UIP_UDP UIP_CONF_UDP
|
||||
#else /* UIP_CONF_UDP */
|
||||
#define UIP_UDP 0
|
||||
#endif /* UIP_CONF_UDP */
|
||||
|
||||
/**
|
||||
* Toggles if UDP checksums should be used or not.
|
||||
*
|
||||
* \note Support for UDP checksums is currently not included in uIP,
|
||||
* so this option has no function.
|
||||
*
|
||||
* \hideinitializer
|
||||
*/
|
||||
#ifdef UIP_CONF_UDP_CHECKSUMS
|
||||
#define UIP_UDP_CHECKSUMS UIP_CONF_UDP_CHECKSUMS
|
||||
#else
|
||||
#define UIP_UDP_CHECKSUMS 0
|
||||
#endif
|
||||
|
||||
/**
|
||||
* The maximum amount of concurrent UDP connections.
|
||||
*
|
||||
* \hideinitializer
|
||||
*/
|
||||
#ifdef UIP_CONF_UDP_CONNS
|
||||
#define UIP_UDP_CONNS UIP_CONF_UDP_CONNS
|
||||
#else /* UIP_CONF_UDP_CONNS */
|
||||
#define UIP_UDP_CONNS 10
|
||||
#endif /* UIP_CONF_UDP_CONNS */
|
||||
|
||||
/**
|
||||
* The name of the function that should be called when UDP datagrams arrive.
|
||||
*
|
||||
* \hideinitializer
|
||||
*/
|
||||
|
||||
|
||||
/** @} */
|
||||
/*------------------------------------------------------------------------------*/
|
||||
/**
|
||||
* \name TCP configuration options
|
||||
* @{
|
||||
*/
|
||||
|
||||
/**
|
||||
* Determines if support for opening connections from uIP should be
|
||||
* compiled in.
|
||||
*
|
||||
* If the applications that are running on top of uIP for this project
|
||||
* do not need to open outgoing TCP connections, this configration
|
||||
* option can be turned off to reduce the code size of uIP.
|
||||
*
|
||||
* \hideinitializer
|
||||
*/
|
||||
#define UIP_ACTIVE_OPEN 1
|
||||
|
||||
/**
|
||||
* The maximum number of simultaneously open TCP connections.
|
||||
*
|
||||
* Since the TCP connections are statically allocated, turning this
|
||||
* configuration knob down results in less RAM used. Each TCP
|
||||
* connection requires approximatly 30 bytes of memory.
|
||||
*
|
||||
* \hideinitializer
|
||||
*/
|
||||
#ifndef UIP_CONF_MAX_CONNECTIONS
|
||||
#define UIP_CONNS 10
|
||||
#else /* UIP_CONF_MAX_CONNECTIONS */
|
||||
#define UIP_CONNS UIP_CONF_MAX_CONNECTIONS
|
||||
#endif /* UIP_CONF_MAX_CONNECTIONS */
|
||||
|
||||
|
||||
/**
|
||||
* The maximum number of simultaneously listening TCP ports.
|
||||
*
|
||||
* Each listening TCP port requires 2 bytes of memory.
|
||||
*
|
||||
* \hideinitializer
|
||||
*/
|
||||
#ifndef UIP_CONF_MAX_LISTENPORTS
|
||||
#define UIP_LISTENPORTS 20
|
||||
#else /* UIP_CONF_MAX_LISTENPORTS */
|
||||
#define UIP_LISTENPORTS UIP_CONF_MAX_LISTENPORTS
|
||||
#endif /* UIP_CONF_MAX_LISTENPORTS */
|
||||
|
||||
/**
|
||||
* Determines if support for TCP urgent data notification should be
|
||||
* compiled in.
|
||||
*
|
||||
* Urgent data (out-of-band data) is a rarely used TCP feature that
|
||||
* very seldom would be required.
|
||||
*
|
||||
* \hideinitializer
|
||||
*/
|
||||
#define UIP_URGDATA 0
|
||||
|
||||
/**
|
||||
* The initial retransmission timeout counted in timer pulses.
|
||||
*
|
||||
* This should not be changed.
|
||||
*/
|
||||
#define UIP_RTO 3
|
||||
|
||||
/**
|
||||
* The maximum number of times a segment should be retransmitted
|
||||
* before the connection should be aborted.
|
||||
*
|
||||
* This should not be changed.
|
||||
*/
|
||||
#define UIP_MAXRTX 8
|
||||
|
||||
/**
|
||||
* The maximum number of times a SYN segment should be retransmitted
|
||||
* before a connection request should be deemed to have been
|
||||
* unsuccessful.
|
||||
*
|
||||
* This should not need to be changed.
|
||||
*/
|
||||
#define UIP_MAXSYNRTX 5
|
||||
|
||||
/**
|
||||
* The TCP maximum segment size.
|
||||
*
|
||||
* This is should not be to set to more than
|
||||
* UIP_BUFSIZE - UIP_LLH_LEN - UIP_TCPIP_HLEN.
|
||||
*/
|
||||
#ifndef UIP_CONF_TCP_MSS
|
||||
#define UIP_TCP_MSS (UIP_BUFSIZE - UIP_LLH_LEN - UIP_TCPIP_HLEN)
|
||||
#else
|
||||
#define UIP_TCP_MSS UIP_CONF_TCP_MSS
|
||||
#endif
|
||||
|
||||
/**
|
||||
* The size of the advertised receiver's window.
|
||||
*
|
||||
* Should be set low (i.e., to the size of the uip_buf buffer) is the
|
||||
* application is slow to process incoming data, or high (32768 bytes)
|
||||
* if the application processes data quickly.
|
||||
*
|
||||
* \hideinitializer
|
||||
*/
|
||||
#ifndef UIP_CONF_RECEIVE_WINDOW
|
||||
#define UIP_RECEIVE_WINDOW UIP_TCP_MSS
|
||||
#else
|
||||
#define UIP_RECEIVE_WINDOW UIP_CONF_RECEIVE_WINDOW
|
||||
#endif
|
||||
|
||||
/**
|
||||
* How long a connection should stay in the TIME_WAIT state.
|
||||
*
|
||||
* This configiration option has no real implication, and it should be
|
||||
* left untouched.
|
||||
*/
|
||||
#define UIP_TIME_WAIT_TIMEOUT 120
|
||||
|
||||
|
||||
/** @} */
|
||||
/*------------------------------------------------------------------------------*/
|
||||
/**
|
||||
* \name ARP configuration options
|
||||
* @{
|
||||
*/
|
||||
|
||||
/**
|
||||
* The size of the ARP table.
|
||||
*
|
||||
* This option should be set to a larger value if this uIP node will
|
||||
* have many connections from the local network.
|
||||
*
|
||||
* \hideinitializer
|
||||
*/
|
||||
#ifdef UIP_CONF_ARPTAB_SIZE
|
||||
#define UIP_ARPTAB_SIZE UIP_CONF_ARPTAB_SIZE
|
||||
#else
|
||||
#define UIP_ARPTAB_SIZE 8
|
||||
#endif
|
||||
|
||||
/**
|
||||
* The maxium age of ARP table entries measured in 10ths of seconds.
|
||||
*
|
||||
* An UIP_ARP_MAXAGE of 120 corresponds to 20 minutes (BSD
|
||||
* default).
|
||||
*/
|
||||
#define UIP_ARP_MAXAGE 120
|
||||
|
||||
/** @} */
|
||||
|
||||
/*------------------------------------------------------------------------------*/
|
||||
|
||||
/**
|
||||
* \name General configuration options
|
||||
* @{
|
||||
*/
|
||||
|
||||
/**
|
||||
* The size of the uIP packet buffer.
|
||||
*
|
||||
* The uIP packet buffer should not be smaller than 60 bytes, and does
|
||||
* not need to be larger than 1500 bytes. Lower size results in lower
|
||||
* TCP throughput, larger size results in higher TCP throughput.
|
||||
*
|
||||
* \hideinitializer
|
||||
*/
|
||||
#ifndef UIP_CONF_BUFFER_SIZE
|
||||
#define UIP_BUFSIZE 400
|
||||
#else /* UIP_CONF_BUFFER_SIZE */
|
||||
#define UIP_BUFSIZE UIP_CONF_BUFFER_SIZE
|
||||
#endif /* UIP_CONF_BUFFER_SIZE */
|
||||
|
||||
|
||||
/**
|
||||
* Determines if statistics support should be compiled in.
|
||||
*
|
||||
* The statistics is useful for debugging and to show the user.
|
||||
*
|
||||
* \hideinitializer
|
||||
*/
|
||||
#ifndef UIP_CONF_STATISTICS
|
||||
#define UIP_STATISTICS 0
|
||||
#else /* UIP_CONF_STATISTICS */
|
||||
#define UIP_STATISTICS UIP_CONF_STATISTICS
|
||||
#endif /* UIP_CONF_STATISTICS */
|
||||
|
||||
/**
|
||||
* Determines if logging of certain events should be compiled in.
|
||||
*
|
||||
* This is useful mostly for debugging. The function uip_log()
|
||||
* must be implemented to suit the architecture of the project, if
|
||||
* logging is turned on.
|
||||
*
|
||||
* \hideinitializer
|
||||
*/
|
||||
#ifndef UIP_CONF_LOGGING
|
||||
#define UIP_LOGGING 0
|
||||
#else /* UIP_CONF_LOGGING */
|
||||
#define UIP_LOGGING UIP_CONF_LOGGING
|
||||
#endif /* UIP_CONF_LOGGING */
|
||||
|
||||
/**
|
||||
* Broadcast support.
|
||||
*
|
||||
* This flag configures IP broadcast support. This is useful only
|
||||
* together with UDP.
|
||||
*
|
||||
* \hideinitializer
|
||||
*
|
||||
*/
|
||||
#if UIP_UDP && UIP_CONF_BROADCAST
|
||||
#define UIP_BROADCAST UIP_CONF_BROADCAST
|
||||
#else /* UIP_CONF_BROADCAST */
|
||||
#define UIP_BROADCAST 0
|
||||
#endif /* UIP_CONF_BROADCAST */
|
||||
|
||||
/**
|
||||
* Print out a uIP log message.
|
||||
*
|
||||
* This function must be implemented by the module that uses uIP, and
|
||||
* is called by uIP whenever a log message is generated.
|
||||
*/
|
||||
void uip_log(char *msg);
|
||||
|
||||
/**
|
||||
* The link level header length.
|
||||
*
|
||||
* This is the offset into the uip_buf where the IP header can be
|
||||
* found. For Ethernet, this should be set to 14. For SLIP, this
|
||||
* should be set to 0.
|
||||
*
|
||||
* \hideinitializer
|
||||
*/
|
||||
#ifdef UIP_CONF_LLH_LEN
|
||||
#define UIP_LLH_LEN UIP_CONF_LLH_LEN
|
||||
#else /* UIP_CONF_LLH_LEN */
|
||||
#define UIP_LLH_LEN 14
|
||||
#endif /* UIP_CONF_LLH_LEN */
|
||||
|
||||
/** @} */
|
||||
/*------------------------------------------------------------------------------*/
|
||||
/**
|
||||
* \name CPU architecture configuration
|
||||
* @{
|
||||
*
|
||||
* The CPU architecture configuration is where the endianess of the
|
||||
* CPU on which uIP is to be run is specified. Most CPUs today are
|
||||
* little endian, and the most notable exception are the Motorolas
|
||||
* which are big endian. The BYTE_ORDER macro should be changed to
|
||||
* reflect the CPU architecture on which uIP is to be run.
|
||||
*/
|
||||
|
||||
/**
|
||||
* The byte order of the CPU architecture on which uIP is to be run.
|
||||
*
|
||||
* This option can be either BIG_ENDIAN (Motorola byte order) or
|
||||
* LITTLE_ENDIAN (Intel byte order).
|
||||
*
|
||||
* \hideinitializer
|
||||
*/
|
||||
#ifdef UIP_CONF_BYTE_ORDER
|
||||
#define UIP_BYTE_ORDER UIP_CONF_BYTE_ORDER
|
||||
#else /* UIP_CONF_BYTE_ORDER */
|
||||
#define UIP_BYTE_ORDER UIP_LITTLE_ENDIAN
|
||||
#endif /* UIP_CONF_BYTE_ORDER */
|
||||
|
||||
/** @} */
|
||||
/*------------------------------------------------------------------------------*/
|
||||
|
||||
/**
|
||||
* \name Appication specific configurations
|
||||
* @{
|
||||
*
|
||||
* An uIP application is implemented using a single application
|
||||
* function that is called by uIP whenever a TCP/IP event occurs. The
|
||||
* name of this function must be registered with uIP at compile time
|
||||
* using the UIP_APPCALL definition.
|
||||
*
|
||||
* uIP applications can store the application state within the
|
||||
* uip_conn structure by specifying the type of the application
|
||||
* structure by typedef:ing the type uip_tcp_appstate_t and uip_udp_appstate_t.
|
||||
*
|
||||
* The file containing the definitions must be included in the
|
||||
* uipopt.h file.
|
||||
*
|
||||
* The following example illustrates how this can look.
|
||||
\code
|
||||
|
||||
void httpd_appcall(void);
|
||||
#define UIP_APPCALL httpd_appcall
|
||||
|
||||
struct httpd_state {
|
||||
u8_t state;
|
||||
u16_t count;
|
||||
char *dataptr;
|
||||
char *script;
|
||||
};
|
||||
typedef struct httpd_state uip_tcp_appstate_t
|
||||
\endcode
|
||||
*/
|
||||
|
||||
/**
|
||||
* \var #define UIP_APPCALL
|
||||
*
|
||||
* The name of the application function that uIP should call in
|
||||
* response to TCP/IP events.
|
||||
*
|
||||
*/
|
||||
|
||||
/**
|
||||
* \var typedef uip_tcp_appstate_t
|
||||
*
|
||||
* The type of the application state that is to be stored in the
|
||||
* uip_conn structure. This usually is typedef:ed to a struct holding
|
||||
* application state information.
|
||||
*/
|
||||
|
||||
/**
|
||||
* \var typedef uip_udp_appstate_t
|
||||
*
|
||||
* The type of the application state that is to be stored in the
|
||||
* uip_conn structure. This usually is typedef:ed to a struct holding
|
||||
* application state information.
|
||||
*/
|
||||
/** @} */
|
||||
/** @} */
|
||||
|
||||
#endif /* __UIPOPT_H__ */
|
||||
@@ -1,10 +0,0 @@
|
||||
#ifndef UTIL_H
|
||||
#define UTIL_H
|
||||
|
||||
#define htonl(x) ( ((x)<<24 & 0xFF000000UL) | \
|
||||
((x)<< 8 & 0x00FF0000UL) | \
|
||||
((x)>> 8 & 0x0000FF00UL) | \
|
||||
((x)>>24 & 0x000000FFUL) )
|
||||
#define ntohl(x) htonl(x)
|
||||
|
||||
#endif
|
||||
@@ -1,22 +0,0 @@
|
||||
[env:growatt-shinelan]
|
||||
platform = ststm32
|
||||
board = genericSTM32F103RB
|
||||
framework = arduino
|
||||
upload_protocol = stlink
|
||||
debug_tool = stlink
|
||||
monitor_speed = 115200
|
||||
|
||||
lib_deps =
|
||||
4-20ma/ModbusMaster@^2.0.1
|
||||
knolleary/PubSubClient@^2.8
|
||||
# EthernetENC ist lokal in lib/EthernetENC/ (Bitbang-SPI Modifikation)
|
||||
|
||||
build_flags =
|
||||
-DPIO_FRAMEWORK_ARDUINO_NOERRNO
|
||||
-DUSBCON
|
||||
-DUSBD_USE_CDC
|
||||
-DHAL_PCD_MODULE_ENABLED
|
||||
-DUSB_CDC_TRANSMIT_TIMEOUT=3000
|
||||
-DCUSTOM_USBD_PID=0x5740
|
||||
'-DUSB_PRODUCT_STRING="STM32 Virtual COM Port"'
|
||||
-I include
|
||||
@@ -1,712 +0,0 @@
|
||||
#include <Arduino.h>
|
||||
#include <SPI.h>
|
||||
#include <EthernetENC.h>
|
||||
#include <PubSubClient.h>
|
||||
#include <ModbusMaster.h>
|
||||
#include <EEPROM.h>
|
||||
#include "config.h"
|
||||
#include "config_manager.h"
|
||||
|
||||
extern "C" void SystemClock_Config(void) {
|
||||
RCC_OscInitTypeDef osc = {};
|
||||
RCC_ClkInitTypeDef clk = {};
|
||||
RCC_PeriphCLKInitTypeDef periphClk = {};
|
||||
|
||||
osc.OscillatorType = RCC_OSCILLATORTYPE_HSE;
|
||||
osc.HSEState = RCC_HSE_ON;
|
||||
osc.PLL.PLLState = RCC_PLL_ON;
|
||||
osc.PLL.PLLSource = RCC_PLLSOURCE_HSE;
|
||||
osc.PLL.PLLMUL = RCC_PLL_MUL9;
|
||||
if (HAL_RCC_OscConfig(&osc) == HAL_OK) {
|
||||
clk.ClockType = RCC_CLOCKTYPE_HCLK | RCC_CLOCKTYPE_SYSCLK
|
||||
| RCC_CLOCKTYPE_PCLK1 | RCC_CLOCKTYPE_PCLK2;
|
||||
clk.SYSCLKSource = RCC_SYSCLKSOURCE_PLLCLK;
|
||||
clk.AHBCLKDivider = RCC_SYSCLK_DIV1;
|
||||
clk.APB1CLKDivider = RCC_HCLK_DIV2;
|
||||
clk.APB2CLKDivider = RCC_HCLK_DIV1;
|
||||
HAL_RCC_ClockConfig(&clk, FLASH_LATENCY_2);
|
||||
periphClk.PeriphClockSelection = RCC_PERIPHCLK_USB;
|
||||
periphClk.UsbClockSelection = RCC_USBCLKSOURCE_PLL_DIV1_5;
|
||||
HAL_RCCEx_PeriphCLKConfig(&periphClk);
|
||||
} else {
|
||||
osc.OscillatorType = RCC_OSCILLATORTYPE_HSI;
|
||||
osc.HSEState = RCC_HSE_OFF;
|
||||
osc.HSIState = RCC_HSI_ON;
|
||||
osc.HSICalibrationValue = RCC_HSICALIBRATION_DEFAULT;
|
||||
osc.PLL.PLLSource = RCC_PLLSOURCE_HSI_DIV2;
|
||||
osc.PLL.PLLMUL = RCC_PLL_MUL12;
|
||||
HAL_RCC_OscConfig(&osc);
|
||||
clk.ClockType = RCC_CLOCKTYPE_HCLK | RCC_CLOCKTYPE_SYSCLK
|
||||
| RCC_CLOCKTYPE_PCLK1 | RCC_CLOCKTYPE_PCLK2;
|
||||
clk.SYSCLKSource = RCC_SYSCLKSOURCE_PLLCLK;
|
||||
clk.AHBCLKDivider = RCC_SYSCLK_DIV1;
|
||||
clk.APB1CLKDivider = RCC_HCLK_DIV2;
|
||||
clk.APB2CLKDivider = RCC_HCLK_DIV1;
|
||||
HAL_RCC_ClockConfig(&clk, FLASH_LATENCY_1);
|
||||
periphClk.PeriphClockSelection = RCC_PERIPHCLK_USB;
|
||||
periphClk.UsbClockSelection = RCC_USBCLKSOURCE_PLL;
|
||||
HAL_RCCEx_PeriphCLKConfig(&periphClk);
|
||||
}
|
||||
}
|
||||
|
||||
// Hält PA8 HIGH (USB getrennt) bis setup() es manuell verbindet
|
||||
extern "C" void USBD_reenumerate(void) {
|
||||
__HAL_RCC_GPIOA_CLK_ENABLE();
|
||||
GPIO_InitTypeDef gpio = {};
|
||||
gpio.Pin = GPIO_PIN_8;
|
||||
gpio.Mode = GPIO_MODE_OUTPUT_PP;
|
||||
gpio.Pull = GPIO_NOPULL;
|
||||
gpio.Speed = GPIO_SPEED_FREQ_HIGH;
|
||||
HAL_GPIO_Init(GPIOA, &gpio);
|
||||
HAL_GPIO_WritePin(GPIOA, GPIO_PIN_8, GPIO_PIN_SET);
|
||||
}
|
||||
|
||||
HardwareSerial DebugSerial(PA10, PA9);
|
||||
|
||||
// USB-Diagnose: was der Wechselrichter per Control-Transfer schickt
|
||||
extern __IO bool dtrState;
|
||||
extern __IO bool rtsState;
|
||||
struct { uint32_t baud; uint8_t format; uint8_t parity; uint8_t bits; }
|
||||
g_lineCoding = {115200, 0, 0, 8};
|
||||
volatile bool g_lineCodingUpdated = false;
|
||||
|
||||
// Wird vom USB-Stack aufgerufen wenn Wechselrichter SET_LINE_CODING schickt
|
||||
// usbd_cdc_if.c kann nicht geändert werden → wir lesen linecoding-Struct direkt
|
||||
extern "C" {
|
||||
typedef struct { uint32_t bitrate; uint8_t format; uint8_t paritytype; uint8_t datatype; }
|
||||
USBD_CDC_LineCodingTypeDef;
|
||||
extern USBD_CDC_LineCodingTypeDef linecoding;
|
||||
}
|
||||
|
||||
// CDC SERIAL_STATE Notification — NuttX sendet dies beim Öffnen von /dev/ttyACM0
|
||||
// signalisiert dem Wechselrichter DCD+DSR=1 → startet Modbus-Server im Inverter
|
||||
#include "usbd_core.h"
|
||||
extern USBD_HandleTypeDef hUSBD_Device_CDC;
|
||||
|
||||
#define CDC_SERIAL_STATE_EP 0x82U
|
||||
|
||||
void sendSerialStateNotification() {
|
||||
static uint8_t notif[10] = {
|
||||
0xA1, 0x20, 0x00, 0x00, 0x00, 0x00, 0x02, 0x00, 0x03, 0x00
|
||||
};
|
||||
USBD_LL_Transmit(&hUSBD_Device_CDC, CDC_SERIAL_STATE_EP, notif, sizeof(notif));
|
||||
}
|
||||
|
||||
// ModbusMaster schreibt Byte-für-Byte. Würden wir sofort senden, käme Byte 1 als
|
||||
// eigenes 1-Byte USB-Paket an — der Wechselrichter behandelt jedes Short Packet
|
||||
// als Frame-Ende und sieht keinen gültigen Modbus-Frame.
|
||||
// Lösung: Bytes puffern, dann in flush() als ein einziges USB-Paket senden.
|
||||
class ModbusStream : public Stream {
|
||||
public:
|
||||
uint8_t txBuf[16]; uint8_t txLen = 0;
|
||||
uint8_t rxBuf[32]; uint8_t rxLen = 0;
|
||||
uint32_t rxTotal = 0;
|
||||
void reset() { txLen = 0; rxLen = 0; }
|
||||
|
||||
int available() override { return SerialUSB.available(); }
|
||||
int read() override {
|
||||
int b = SerialUSB.read();
|
||||
if (b >= 0) { rxTotal++; if (rxLen < sizeof(rxBuf)) rxBuf[rxLen++] = (uint8_t)b; }
|
||||
return b;
|
||||
}
|
||||
int peek() override { return SerialUSB.peek(); }
|
||||
size_t write(uint8_t b) override {
|
||||
if (txLen < sizeof(txBuf)) txBuf[txLen++] = b;
|
||||
return 1;
|
||||
}
|
||||
size_t write(const uint8_t* buf, size_t size) override {
|
||||
for (size_t i = 0; i < size && txLen < sizeof(txBuf); i++) txBuf[txLen++] = buf[i];
|
||||
return size;
|
||||
}
|
||||
void flush() override {
|
||||
if (txLen > 0) SerialUSB.write(txBuf, txLen);
|
||||
// txLen nicht zurücksetzen — Debug-Code liest txBuf danach noch
|
||||
}
|
||||
using Print::write;
|
||||
} modbusSerial;
|
||||
|
||||
// ============================================================
|
||||
// Sensor-Definitionen
|
||||
// ============================================================
|
||||
struct Sensor {
|
||||
const char* id;
|
||||
const char* name;
|
||||
uint16_t address;
|
||||
bool isDword;
|
||||
float scale;
|
||||
const char* unit;
|
||||
const char* deviceClass;
|
||||
const char* stateClass;
|
||||
const char* icon;
|
||||
};
|
||||
|
||||
#if defined(INVERTER_MIC1500)
|
||||
const Sensor SENSORS[] = {
|
||||
{"pv_voltage", "PV Voltage", 3, false, 0.1f, "V", "voltage", "measurement", "mdi:solar-panel"},
|
||||
{"pv_current", "PV Current", 4, false, 0.1f, "A", "current", "measurement", "mdi:solar-panel"},
|
||||
{"pv_power", "PV Power", 5, true, 0.1f, "W", "power", "measurement", "mdi:solar-panel"},
|
||||
{"grid_frequency", "Grid Frequency", 37, false, 0.01f, "Hz", "frequency", "measurement", "mdi:sine-wave"},
|
||||
{"grid_voltage", "Grid Voltage", 38, false, 0.1f, "V", "voltage", "measurement", "mdi:flash"},
|
||||
{"grid_current", "Grid Current", 39, false, 0.1f, "A", "current", "measurement", "mdi:flash"},
|
||||
{"ac_power", "AC Output Power", 40, true, 0.1f, "W", "power", "measurement", "mdi:flash"},
|
||||
{"energy_today", "Energy Today", 53, true, 0.1f, "kWh", "energy", "total_increasing", "mdi:solar-power"},
|
||||
{"energy_total", "Energy Total", 55, true, 0.1f, "kWh", "energy", "total_increasing", "mdi:solar-power"},
|
||||
{"inverter_temp", "Inverter Temperature", 93, false, 0.1f, "°C", "temperature", "measurement", "mdi:thermometer"},
|
||||
};
|
||||
#elif defined(INVERTER_SPH5000)
|
||||
const Sensor SENSORS[] = {
|
||||
{"pv1_voltage", "PV1 Voltage", 3, false, 0.1f, "V", "voltage", "measurement", "mdi:solar-panel"},
|
||||
{"pv1_current", "PV1 Current", 4, false, 0.1f, "A", "current", "measurement", "mdi:solar-panel"},
|
||||
{"pv1_power", "PV1 Power", 5, true, 0.1f, "W", "power", "measurement", "mdi:solar-panel"},
|
||||
{"pv2_voltage", "PV2 Voltage", 7, false, 0.1f, "V", "voltage", "measurement", "mdi:solar-panel"},
|
||||
{"pv2_current", "PV2 Current", 8, false, 0.1f, "A", "current", "measurement", "mdi:solar-panel"},
|
||||
{"pv2_power", "PV2 Power", 9, true, 0.1f, "W", "power", "measurement", "mdi:solar-panel"},
|
||||
{"ac_power_total", "AC Output Power Total", 35, true, 0.1f, "W", "power", "measurement", "mdi:flash"},
|
||||
{"grid_frequency", "Grid Frequency", 37, false, 0.01f, "Hz", "frequency", "measurement", "mdi:sine-wave"},
|
||||
{"grid_voltage_l1", "Grid Voltage L1", 38, false, 0.1f, "V", "voltage", "measurement", "mdi:flash"},
|
||||
{"grid_current_l1", "Grid Current L1", 39, false, 0.1f, "A", "current", "measurement", "mdi:flash"},
|
||||
{"grid_voltage_l2", "Grid Voltage L2", 42, false, 0.1f, "V", "voltage", "measurement", "mdi:flash"},
|
||||
{"grid_current_l2", "Grid Current L2", 43, false, 0.1f, "A", "current", "measurement", "mdi:flash"},
|
||||
{"grid_voltage_l3", "Grid Voltage L3", 46, false, 0.1f, "V", "voltage", "measurement", "mdi:flash"},
|
||||
{"grid_current_l3", "Grid Current L3", 47, false, 0.1f, "A", "current", "measurement", "mdi:flash"},
|
||||
{"energy_today", "Energy Today", 53, true, 0.1f, "kWh", "energy", "total_increasing", "mdi:solar-power"},
|
||||
{"energy_total", "Energy Total", 55, true, 0.1f, "kWh", "energy", "total_increasing", "mdi:solar-power"},
|
||||
{"inverter_temp", "Inverter Temperature", 93, false, 0.1f, "°C", "temperature", "measurement", "mdi:thermometer"},
|
||||
{"bat_discharge_power", "Battery Discharge Power", 1009, true, 0.1f, "W", "power", "measurement", "mdi:battery-minus"},
|
||||
{"bat_charge_power", "Battery Charge Power", 1011, true, 0.1f, "W", "power", "measurement", "mdi:battery-plus"},
|
||||
{"bat_voltage", "Battery Voltage", 1013, false, 0.1f, "V", "voltage", "measurement", "mdi:battery"},
|
||||
{"bat_soc", "Battery State of Charge", 1014, false, 1.0f, "%", "battery", "measurement", "mdi:battery"},
|
||||
{"bat_temperature", "Battery Temperature", 1040, false, 0.1f, "°C", "temperature", "measurement", "mdi:thermometer"},
|
||||
{"power_to_grid", "Power To Grid", 1021, true, 0.1f, "W", "power", "measurement", "mdi:transmission-tower-export"},
|
||||
{"power_to_user", "Power To User", 1029, true, 0.1f, "W", "power", "measurement", "mdi:transmission-tower-import"},
|
||||
{"energy_import_total", "Energy Import Total", 1046, true, 0.1f, "kWh", "energy", "total_increasing", "mdi:transmission-tower-import"},
|
||||
{"energy_export_total", "Energy Export Total", 1050, true, 0.1f, "kWh", "energy", "total_increasing", "mdi:transmission-tower-export"},
|
||||
{"bat_discharge_total", "Battery Discharge Total", 1054, true, 0.1f, "kWh", "energy", "total_increasing", "mdi:battery-minus"},
|
||||
{"bat_charge_total", "Battery Charge Total", 1058, true, 0.1f, "kWh", "energy", "total_increasing", "mdi:battery-plus"},
|
||||
};
|
||||
#else
|
||||
#error "Kein Inverter-Modell definiert! INVERTER_MIC1500 oder INVERTER_SPH5000 in config.h setzen."
|
||||
#endif
|
||||
const uint8_t SENSOR_COUNT = sizeof(SENSORS) / sizeof(SENSORS[0]);
|
||||
|
||||
// ============================================================
|
||||
// Globale Objekte
|
||||
// ============================================================
|
||||
NetConfig cfg;
|
||||
byte mac[] = {MAC_ADDRESS};
|
||||
EthernetClient ethClient;
|
||||
PubSubClient mqtt(ethClient);
|
||||
ModbusMaster modbus;
|
||||
EthernetServer webServer(80);
|
||||
|
||||
uint32_t g_okTotal = 0;
|
||||
uint32_t g_failTotal = 0;
|
||||
uint8_t g_lastErr = 0;
|
||||
uint32_t g_rawTotal = 0;
|
||||
|
||||
// ============================================================
|
||||
// Web-Server
|
||||
// ============================================================
|
||||
|
||||
static bool webReadLine(EthernetClient& c, char* buf, size_t maxLen) {
|
||||
size_t i = 0;
|
||||
uint32_t t = millis();
|
||||
while (millis() - t < 1000) {
|
||||
while (c.available()) {
|
||||
char ch = c.read();
|
||||
if (ch == '\n') { buf[i] = 0; return true; }
|
||||
if (ch != '\r' && i < maxLen - 1) buf[i++] = ch;
|
||||
t = millis();
|
||||
}
|
||||
}
|
||||
buf[i] = 0;
|
||||
return false;
|
||||
}
|
||||
|
||||
static void ipToStr(const uint8_t ip[4], char* buf) {
|
||||
snprintf(buf, 16, "%u.%u.%u.%u", ip[0], ip[1], ip[2], ip[3]);
|
||||
}
|
||||
|
||||
static void urlDecode(char* s) {
|
||||
char* out = s;
|
||||
while (*s) {
|
||||
if (*s == '+') {
|
||||
*out++ = ' '; s++;
|
||||
} else if (*s == '%' && s[1] && s[2]) {
|
||||
char h[3] = {s[1], s[2], 0};
|
||||
*out++ = (char)strtol(h, nullptr, 16);
|
||||
s += 3;
|
||||
} else {
|
||||
*out++ = *s++;
|
||||
}
|
||||
}
|
||||
*out = 0;
|
||||
}
|
||||
|
||||
static void parseIP(const char* s, uint8_t out[4]) {
|
||||
unsigned a = 0, b = 0, c = 0, d = 0;
|
||||
if (sscanf(s, "%u.%u.%u.%u", &a, &b, &c, &d) == 4) {
|
||||
out[0] = a; out[1] = b; out[2] = c; out[3] = d;
|
||||
}
|
||||
}
|
||||
|
||||
static void parseFormBody(char* body) {
|
||||
cfg.useDhcp = false; // Checkbox fehlt im POST wenn nicht gesetzt
|
||||
char* p = body;
|
||||
while (p && *p) {
|
||||
char* eq = strchr(p, '=');
|
||||
char* amp = strchr(p, '&');
|
||||
if (!eq) break;
|
||||
*eq = 0;
|
||||
if (amp) *amp = 0;
|
||||
char* key = p;
|
||||
char* val = eq + 1;
|
||||
urlDecode(key);
|
||||
urlDecode(val);
|
||||
|
||||
if (!strcmp(key, "dhcp")) cfg.useDhcp = true;
|
||||
else if (!strcmp(key, "ip")) parseIP(val, cfg.ip);
|
||||
else if (!strcmp(key, "gw")) parseIP(val, cfg.gw);
|
||||
else if (!strcmp(key, "sn")) parseIP(val, cfg.sn);
|
||||
else if (!strcmp(key, "dns")) parseIP(val, cfg.dns);
|
||||
else if (!strcmp(key, "broker")) strncpy(cfg.mqttBroker, val, sizeof(cfg.mqttBroker) - 1);
|
||||
else if (!strcmp(key, "port")) cfg.mqttPort = (uint16_t)atoi(val);
|
||||
else if (!strcmp(key, "user")) strncpy(cfg.mqttUser, val, sizeof(cfg.mqttUser) - 1);
|
||||
else if (!strcmp(key, "pass")) strncpy(cfg.mqttPass, val, sizeof(cfg.mqttPass) - 1);
|
||||
else if (!strcmp(key, "interval")) cfg.updateMs = (uint32_t)atoi(val) * 1000UL;
|
||||
|
||||
p = amp ? amp + 1 : nullptr;
|
||||
}
|
||||
}
|
||||
|
||||
static void sendPage(EthernetClient& cl) {
|
||||
char buf[16];
|
||||
|
||||
cl.print(
|
||||
"HTTP/1.1 200 OK\r\nContent-Type: text/html\r\nConnection: close\r\n\r\n"
|
||||
"<!DOCTYPE html><html lang='de'><head>"
|
||||
"<meta charset='utf-8'>"
|
||||
"<meta name='viewport' content='width=device-width,initial-scale=1'>"
|
||||
"<title>ShineLAN-X</title><style>"
|
||||
"*{box-sizing:border-box}"
|
||||
"body{font-family:Arial,sans-serif;background:#1a1a2e;color:#eee;margin:0}"
|
||||
"header{background:#16213e;padding:12px 18px;display:flex;align-items:center;"
|
||||
"border-bottom:2px solid #e94560}"
|
||||
"header h1{margin:0;font-size:1.1em;color:#e94560;flex:1}"
|
||||
".sub{font-size:.78em;color:#64748b;margin-top:2px}"
|
||||
".badge{padding:3px 10px;border-radius:10px;font-size:.78em;font-weight:bold}"
|
||||
".on{background:#14532d;color:#4ade80}"
|
||||
".off{background:#450a0a;color:#f87171}"
|
||||
"main{max-width:520px;margin:14px auto;padding:0 12px}"
|
||||
".stats{display:grid;grid-template-columns:repeat(3,1fr);gap:8px;margin-bottom:12px}"
|
||||
".sbox{background:#16213e;border-radius:6px;padding:9px;text-align:center}"
|
||||
".sval{font-size:1.4em;font-weight:bold;color:#e94560}"
|
||||
".slbl{font-size:.7em;color:#64748b;margin-top:2px}"
|
||||
".card{background:#16213e;border-radius:7px;padding:14px;margin-bottom:12px}"
|
||||
".card h2{margin:0 0 12px;font-size:.72em;text-transform:uppercase;"
|
||||
"letter-spacing:.08em;color:#64748b;"
|
||||
"border-bottom:1px solid #0f3460;padding-bottom:7px}"
|
||||
".row{display:flex;align-items:center;gap:8px;margin-bottom:9px}"
|
||||
".row label{min-width:115px;font-size:.84em;color:#94a3b8}"
|
||||
".row input[type=text],.row input[type=number],.row input[type=password]"
|
||||
"{flex:1;background:#0f3460;border:1px solid #1e4a80;color:#eee;"
|
||||
"padding:6px 9px;border-radius:4px;font-size:.84em;min-width:0}"
|
||||
".row input[type=checkbox]{width:16px;height:16px;accent-color:#e94560;cursor:pointer}"
|
||||
".btn{width:100%;padding:10px;border:none;border-radius:6px;"
|
||||
"font-size:.88em;cursor:pointer;margin-top:5px;font-weight:bold}"
|
||||
".save{background:#e94560;color:#fff}"
|
||||
".save:hover{background:#c73652}"
|
||||
".rst{background:#334155;color:#eee}"
|
||||
".rst:hover{background:#475569}"
|
||||
"</style></head><body>"
|
||||
);
|
||||
|
||||
// Header
|
||||
cl.print("<header><div><h1>☀ ShineLAN-X</h1>"
|
||||
"<div class='sub'>Growatt Modbus → MQTT Bridge</div></div>"
|
||||
"<div style='text-align:right'><span class='badge ");
|
||||
cl.print(mqtt.connected() ? "on'>Verbunden" : "off'>Getrennt");
|
||||
cl.print("</span><div class='sub'>IP: ");
|
||||
cl.print(Ethernet.localIP());
|
||||
cl.print("</div></div></header><main>");
|
||||
|
||||
// Statistik
|
||||
cl.print("<div class='stats'>"
|
||||
"<div class='sbox'><div class='sval'>");
|
||||
cl.print(g_okTotal);
|
||||
cl.print("</div><div class='slbl'>OK</div></div>"
|
||||
"<div class='sbox'><div class='sval'>");
|
||||
cl.print(g_failTotal);
|
||||
cl.print("</div><div class='slbl'>Fehler</div></div>"
|
||||
"<div class='sbox'><div class='sval'>0x");
|
||||
snprintf(buf, sizeof(buf), "%02X", g_lastErr);
|
||||
cl.print(buf);
|
||||
cl.print("</div><div class='slbl'>Letzter Err</div></div></div>");
|
||||
|
||||
// Formular — Netzwerk
|
||||
cl.print("<form method='post' action='/'>"
|
||||
"<div class='card'><h2>Netzwerk</h2>"
|
||||
"<div class='row'><label>DHCP</label>"
|
||||
"<input type='checkbox' id='dc' name='dhcp'");
|
||||
if (cfg.useDhcp) cl.print(" checked");
|
||||
cl.print(" onchange='tog()'></div><div id='st'>");
|
||||
|
||||
ipToStr(cfg.ip, buf);
|
||||
cl.print("<div class='row'><label>IP-Adresse</label>"
|
||||
"<input type='text' name='ip' value='"); cl.print(buf); cl.print("'></div>");
|
||||
ipToStr(cfg.gw, buf);
|
||||
cl.print("<div class='row'><label>Gateway</label>"
|
||||
"<input type='text' name='gw' value='"); cl.print(buf); cl.print("'></div>");
|
||||
ipToStr(cfg.sn, buf);
|
||||
cl.print("<div class='row'><label>Subnet</label>"
|
||||
"<input type='text' name='sn' value='"); cl.print(buf); cl.print("'></div>");
|
||||
ipToStr(cfg.dns, buf);
|
||||
cl.print("<div class='row'><label>DNS</label>"
|
||||
"<input type='text' name='dns' value='"); cl.print(buf); cl.print("'></div>");
|
||||
cl.print("</div></div>");
|
||||
|
||||
// Formular — MQTT
|
||||
cl.print("<div class='card'><h2>MQTT</h2>"
|
||||
"<div class='row'><label>Broker</label>"
|
||||
"<input type='text' name='broker' value='");
|
||||
cl.print(cfg.mqttBroker);
|
||||
cl.print("'></div>"
|
||||
"<div class='row'><label>Port</label>"
|
||||
"<input type='number' name='port' value='");
|
||||
cl.print(cfg.mqttPort);
|
||||
cl.print("' min='1' max='65535'></div>"
|
||||
"<div class='row'><label>Benutzer</label>"
|
||||
"<input type='text' name='user' value='");
|
||||
cl.print(cfg.mqttUser);
|
||||
cl.print("' autocomplete='off'></div>"
|
||||
"<div class='row'><label>Passwort</label>"
|
||||
"<input type='password' name='pass' value='");
|
||||
cl.print(cfg.mqttPass);
|
||||
cl.print("' autocomplete='off'></div>"
|
||||
"<div class='row'><label>Intervall (s)</label>"
|
||||
"<input type='number' name='interval' value='");
|
||||
cl.print(cfg.updateMs / 1000UL);
|
||||
cl.print("' min='1' max='3600'></div></div>");
|
||||
|
||||
// Buttons + Script
|
||||
cl.print(
|
||||
"<button class='btn save' type='submit'>"
|
||||
"💾 Speichern & Neustart</button>"
|
||||
"<button class='btn rst' type='button' "
|
||||
"onclick='if(confirm(\"Wirklich auf Werkseinstellungen zur\\u00FCcksetzen?\"))"
|
||||
"location=\"/reset\"'>↻ Werkseinstellungen</button>"
|
||||
"</form></main>"
|
||||
"<script>"
|
||||
"function tog(){"
|
||||
"document.getElementById('st').style.display="
|
||||
"document.getElementById('dc').checked?'none':'block';}"
|
||||
"window.onload=tog;"
|
||||
"</script></body></html>"
|
||||
);
|
||||
}
|
||||
|
||||
static void handleWebRequest() {
|
||||
EthernetClient client = webServer.available();
|
||||
if (!client) return;
|
||||
|
||||
char line[80];
|
||||
if (!webReadLine(client, line, sizeof(line))) { client.stop(); return; }
|
||||
|
||||
bool isPost = (strncmp(line, "POST", 4) == 0);
|
||||
bool isReset = (strstr(line, "/reset") != nullptr);
|
||||
|
||||
// Header lesen, Content-Length suchen (case-insensitive via tolower)
|
||||
int contentLen = 0;
|
||||
char hdr[80];
|
||||
while (webReadLine(client, hdr, sizeof(hdr)) && hdr[0] != '\0') {
|
||||
for (char* p = hdr; *p; p++) *p = (char)tolower((unsigned char)*p);
|
||||
if (strncmp(hdr, "content-length:", 15) == 0)
|
||||
contentLen = atoi(hdr + 15);
|
||||
}
|
||||
|
||||
if (isReset) {
|
||||
configDefaults(cfg);
|
||||
configSave(cfg);
|
||||
client.print("HTTP/1.1 303 See Other\r\nLocation: /\r\nConnection: close\r\n\r\n");
|
||||
client.flush(); client.stop();
|
||||
delay(300);
|
||||
NVIC_SystemReset();
|
||||
return;
|
||||
}
|
||||
|
||||
if (isPost && contentLen > 0 && contentLen < 500) {
|
||||
char body[500] = {};
|
||||
int n = 0;
|
||||
uint32_t t = millis();
|
||||
while (n < contentLen && millis() - t < 3000) {
|
||||
if (client.available()) { body[n++] = client.read(); t = millis(); }
|
||||
}
|
||||
body[n] = 0;
|
||||
|
||||
parseFormBody(body);
|
||||
cfg.magic = CFG_MAGIC;
|
||||
configSave(cfg);
|
||||
|
||||
client.print("HTTP/1.1 303 See Other\r\nLocation: /\r\nConnection: close\r\n\r\n");
|
||||
client.flush(); client.stop();
|
||||
delay(300);
|
||||
NVIC_SystemReset();
|
||||
return;
|
||||
}
|
||||
|
||||
sendPage(client);
|
||||
client.flush();
|
||||
client.stop();
|
||||
}
|
||||
|
||||
// ============================================================
|
||||
// LED
|
||||
// ============================================================
|
||||
void ledSet(uint8_t pin, bool on) { digitalWrite(pin, on ? LOW : HIGH); }
|
||||
|
||||
// ============================================================
|
||||
// MQTT
|
||||
// ============================================================
|
||||
void publishDiscovery() {
|
||||
char topic[128];
|
||||
char payload[640];
|
||||
|
||||
for (uint8_t i = 0; i < SENSOR_COUNT; i++) {
|
||||
const Sensor& s = SENSORS[i];
|
||||
snprintf(topic, sizeof(topic),
|
||||
"homeassistant/sensor/%s_%s/config", DEVICE_ID, s.id);
|
||||
snprintf(payload, sizeof(payload),
|
||||
"{"
|
||||
"\"name\":\"%s\","
|
||||
"\"unique_id\":\"%s_%s\","
|
||||
"\"state_topic\":\"growatt/shinelan/%s\","
|
||||
"\"unit_of_measurement\":\"%s\","
|
||||
"\"device_class\":\"%s\","
|
||||
"\"state_class\":\"%s\","
|
||||
"\"icon\":\"%s\","
|
||||
"\"device\":{"
|
||||
"\"identifiers\":[\"%s\"],"
|
||||
"\"name\":\"%s\","
|
||||
"\"model\":\"%s\","
|
||||
"\"manufacturer\":\"%s\""
|
||||
"}}",
|
||||
s.name,
|
||||
DEVICE_ID, s.id,
|
||||
s.id,
|
||||
s.unit, s.deviceClass, s.stateClass, s.icon,
|
||||
DEVICE_ID, DEVICE_NAME, DEVICE_MODEL, DEVICE_MFR);
|
||||
mqtt.publish(topic, payload, true);
|
||||
}
|
||||
}
|
||||
|
||||
bool mqttReconnect() {
|
||||
DebugSerial.print("MQTT connecting... ");
|
||||
ledSet(LED_RED, true);
|
||||
bool ok = (strlen(cfg.mqttUser) > 0)
|
||||
? mqtt.connect(MQTT_CLIENT, cfg.mqttUser, cfg.mqttPass)
|
||||
: mqtt.connect(MQTT_CLIENT);
|
||||
if (ok) {
|
||||
DebugSerial.println("OK");
|
||||
ledSet(LED_RED, false);
|
||||
ledSet(LED_GREEN, true);
|
||||
publishDiscovery();
|
||||
} else {
|
||||
DebugSerial.print("FAIL rc=");
|
||||
DebugSerial.println(mqtt.state());
|
||||
}
|
||||
return ok;
|
||||
}
|
||||
|
||||
// ============================================================
|
||||
// Setup
|
||||
// ============================================================
|
||||
void setup() {
|
||||
DebugSerial.begin(115200);
|
||||
delay(10);
|
||||
DebugSerial.println("\r\n=== Growatt ShineLAN-X ===");
|
||||
DebugSerial.println("Build: " __DATE__ " " __TIME__);
|
||||
|
||||
configLoad(cfg);
|
||||
|
||||
pinMode(LED_DEBUG, OUTPUT); ledSet(LED_DEBUG, false);
|
||||
pinMode(LED_RED, OUTPUT); ledSet(LED_RED, false);
|
||||
pinMode(LED_GREEN, OUTPUT); ledSet(LED_GREEN, false);
|
||||
pinMode(LED_BLUE, OUTPUT); ledSet(LED_BLUE, false);
|
||||
ledSet(LED_RED, true); ledSet(LED_GREEN, true); ledSet(LED_BLUE, true);
|
||||
delay(300);
|
||||
ledSet(LED_RED, false); ledSet(LED_GREEN, false); ledSet(LED_BLUE, false);
|
||||
|
||||
pinMode(ETH_RST_PIN, OUTPUT);
|
||||
digitalWrite(ETH_RST_PIN, LOW); delay(20);
|
||||
digitalWrite(ETH_RST_PIN, HIGH); delay(200);
|
||||
Ethernet.init(ETH_CS_PIN);
|
||||
|
||||
if (cfg.useDhcp) {
|
||||
DebugSerial.println("ETH: DHCP...");
|
||||
if (Ethernet.begin(mac) == 0) {
|
||||
// DHCP fehlgeschlagen: Fallback auf gespeicherte statische IP
|
||||
DebugSerial.println("ETH: DHCP failed, static fallback");
|
||||
IPAddress ip (cfg.ip[0], cfg.ip[1], cfg.ip[2], cfg.ip[3]);
|
||||
IPAddress gw (cfg.gw[0], cfg.gw[1], cfg.gw[2], cfg.gw[3]);
|
||||
IPAddress sn (cfg.sn[0], cfg.sn[1], cfg.sn[2], cfg.sn[3]);
|
||||
IPAddress dns(cfg.dns[0], cfg.dns[1], cfg.dns[2], cfg.dns[3]);
|
||||
Ethernet.begin(mac, ip, dns, gw, sn);
|
||||
}
|
||||
} else {
|
||||
IPAddress ip (cfg.ip[0], cfg.ip[1], cfg.ip[2], cfg.ip[3]);
|
||||
IPAddress gw (cfg.gw[0], cfg.gw[1], cfg.gw[2], cfg.gw[3]);
|
||||
IPAddress sn (cfg.sn[0], cfg.sn[1], cfg.sn[2], cfg.sn[3]);
|
||||
IPAddress dns(cfg.dns[0], cfg.dns[1], cfg.dns[2], cfg.dns[3]);
|
||||
Ethernet.begin(mac, ip, dns, gw, sn);
|
||||
}
|
||||
|
||||
DebugSerial.print("ETH: IP=");
|
||||
DebugSerial.println(Ethernet.localIP());
|
||||
if (Ethernet.linkStatus() == LinkON) ledSet(LED_DEBUG, true);
|
||||
|
||||
webServer.begin();
|
||||
DebugSerial.print("Web: http://");
|
||||
DebugSerial.println(Ethernet.localIP());
|
||||
|
||||
mqtt.setServer(cfg.mqttBroker, cfg.mqttPort);
|
||||
mqtt.setBufferSize(768);
|
||||
|
||||
SerialUSB.begin(MODBUS_BAUD);
|
||||
delay(10000);
|
||||
HAL_GPIO_WritePin(GPIOA, GPIO_PIN_8, GPIO_PIN_RESET);
|
||||
// Warte bis Wechselrichter USB-CDC konfiguriert hat
|
||||
delay(3000);
|
||||
// SERIAL_STATE(DCD=1,DSR=1) senden — NuttX tut dies beim Öffnen von ttyACM0
|
||||
// signalisiert dem Wechselrichter dass das Device bereit ist
|
||||
sendSerialStateNotification();
|
||||
delay(500);
|
||||
modbus.begin(MODBUS_ADDR, modbusSerial);
|
||||
|
||||
DebugSerial.println("Setup done.");
|
||||
}
|
||||
|
||||
// ============================================================
|
||||
// Loop
|
||||
// ============================================================
|
||||
unsigned long lastUpdate = 0;
|
||||
static bool g_usbInfoPublished = false;
|
||||
|
||||
void loop() {
|
||||
handleWebRequest();
|
||||
|
||||
if (!mqtt.connected()) {
|
||||
if (!mqttReconnect()) {
|
||||
delay(5000);
|
||||
return;
|
||||
}
|
||||
}
|
||||
mqtt.loop();
|
||||
handleWebRequest();
|
||||
|
||||
// usbinfo: initial publish + DTR-Änderungen
|
||||
{
|
||||
static bool lastDtr = false;
|
||||
if (!g_usbInfoPublished || (bool)dtrState != lastDtr) {
|
||||
g_usbInfoPublished = true;
|
||||
lastDtr = (bool)dtrState;
|
||||
char info[80];
|
||||
snprintf(info, sizeof(info), "baud=%lu fmt=%u par=%u bits=%u dtr=%d rts=%d",
|
||||
(unsigned long)linecoding.bitrate, linecoding.format,
|
||||
linecoding.paritytype, linecoding.datatype,
|
||||
(int)dtrState, (int)rtsState);
|
||||
mqtt.publish("growatt/shinelan/usbinfo", info, true);
|
||||
}
|
||||
}
|
||||
|
||||
// SERIAL_STATE(DCD=1,DSR=1) alle 5s senden bis DTR vom Wechselrichter gesetzt wird
|
||||
{
|
||||
static uint32_t lastNotif = 0;
|
||||
if (!dtrState && millis() - lastNotif > 5000) {
|
||||
lastNotif = millis();
|
||||
sendSerialStateNotification();
|
||||
}
|
||||
}
|
||||
|
||||
// Raw-RX-Drain: jedes Byte das außerhalb von Modbus-Fenstern ankommt fangen
|
||||
// Beantwortet: sendet der Inverter JEMALS irgendwas (spontan oder verspätet)?
|
||||
{
|
||||
static uint8_t rawBuf[32] = {};
|
||||
static uint8_t rawLen = 0;
|
||||
static uint32_t rawPublish = 0;
|
||||
while (SerialUSB.available()) {
|
||||
uint8_t b = (uint8_t)SerialUSB.read();
|
||||
g_rawTotal++;
|
||||
if (rawLen < sizeof(rawBuf)) rawBuf[rawLen++] = b;
|
||||
}
|
||||
if (rawLen > 0 && millis() - rawPublish > 1000) {
|
||||
rawPublish = millis();
|
||||
char msg[80];
|
||||
size_t p = snprintf(msg, sizeof(msg), "raw=%lu: ", g_rawTotal);
|
||||
for (uint8_t i = 0; i < rawLen && p < 74; i++)
|
||||
p += snprintf(msg + p, sizeof(msg) - p, "%02X", rawBuf[i]);
|
||||
mqtt.publish("growatt/shinelan/rawbytes", msg);
|
||||
rawLen = 0;
|
||||
}
|
||||
}
|
||||
|
||||
if (millis() - lastUpdate < cfg.updateMs) return;
|
||||
lastUpdate = millis();
|
||||
|
||||
ledSet(LED_BLUE, true);
|
||||
|
||||
uint32_t okCnt = 0, failCnt = 0;
|
||||
uint8_t lastErr = 0;
|
||||
uint16_t lastErrReg = 0;
|
||||
|
||||
for (uint8_t i = 0; i < SENSOR_COUNT; i++) {
|
||||
const Sensor& s = SENSORS[i];
|
||||
modbusSerial.reset();
|
||||
|
||||
uint8_t res = modbus.readInputRegisters(s.address, s.isDword ? 2 : 1);
|
||||
|
||||
char dbg[80];
|
||||
size_t pos = snprintf(dbg, sizeof(dbg), "ok=%lu fail=%lu err=0x%02X@%u avS=%d dtr=%d raw=%lu rx=%u tx=",
|
||||
g_okTotal + okCnt, g_failTotal + failCnt,
|
||||
lastErr, lastErrReg,
|
||||
SerialUSB.availableForWrite(),
|
||||
(int)dtrState,
|
||||
g_rawTotal,
|
||||
modbusSerial.rxLen);
|
||||
for (uint8_t j = 0; j < modbusSerial.txLen && pos < 74; j++)
|
||||
pos += snprintf(dbg + pos, sizeof(dbg) - pos, "%02X", modbusSerial.txBuf[j]);
|
||||
pos += snprintf(dbg + pos, sizeof(dbg) - pos, " rx=");
|
||||
for (uint8_t j = 0; j < modbusSerial.rxLen && pos < 78; j++)
|
||||
pos += snprintf(dbg + pos, sizeof(dbg) - pos, "%02X", modbusSerial.rxBuf[j]);
|
||||
mqtt.publish("growatt/shinelan/debug", dbg);
|
||||
|
||||
delay(10); // mbusd -R10: 10ms Pause zwischen Requests
|
||||
|
||||
if (res != modbus.ku8MBSuccess) {
|
||||
failCnt++;
|
||||
lastErr = res;
|
||||
lastErrReg = s.address;
|
||||
continue;
|
||||
}
|
||||
|
||||
okCnt++;
|
||||
float val;
|
||||
if (s.isDword) {
|
||||
uint32_t raw = ((uint32_t)modbus.getResponseBuffer(0) << 16)
|
||||
| modbus.getResponseBuffer(1);
|
||||
val = raw * s.scale;
|
||||
} else {
|
||||
val = modbus.getResponseBuffer(0) * s.scale;
|
||||
}
|
||||
|
||||
char topic[64], payload[24];
|
||||
snprintf(topic, sizeof(topic), "growatt/shinelan/%s", s.id);
|
||||
snprintf(payload, sizeof(payload), "%.1f", (double)val);
|
||||
mqtt.publish(topic, payload);
|
||||
}
|
||||
|
||||
g_okTotal += okCnt;
|
||||
g_failTotal += failCnt;
|
||||
g_lastErr = lastErr;
|
||||
|
||||
ledSet(LED_BLUE, false);
|
||||
}
|
||||
Binary file not shown.
@@ -1,212 +0,0 @@
|
||||
esphome:
|
||||
name: growatt-mic-1500
|
||||
friendly_name: Growatt MIC 1500 TL-X
|
||||
|
||||
esp8266:
|
||||
board: esp07s
|
||||
|
||||
logger:
|
||||
|
||||
api:
|
||||
encryption:
|
||||
key: "HIER_API_KEY_EINTRAGEN" # 32-Byte Base64-Key, wird in der ESPHome-UI automatisch generiert
|
||||
|
||||
ota:
|
||||
- platform: esphome
|
||||
password: "HIER_OTA_PASSWORT_EINTRAGEN" # Beliebiges Passwort für Over-the-Air Updates
|
||||
|
||||
wifi:
|
||||
ssid: "HIER_WLAN_NAME_EINTRAGEN" # Name deines WLANs (SSID)
|
||||
password: "HIER_WLAN_PASSWORT_EINTRAGEN" # Passwort deines WLANs
|
||||
ap:
|
||||
ssid: "Growatt MIC 1500 TL-X Fallback Hotspot"
|
||||
password: "HIER_HOTSPOT_PASSWORT_EINTRAGEN" # Passwort für den Notfall-Hotspot (min. 8 Zeichen)
|
||||
|
||||
captive_portal:
|
||||
|
||||
time:
|
||||
- platform: homeassistant
|
||||
id: homeassistant_time
|
||||
|
||||
output:
|
||||
- id: light_bl # Blaue LED
|
||||
platform: gpio
|
||||
pin: 16
|
||||
- id: light_gr # Grüne LED
|
||||
platform: gpio
|
||||
pin: 0
|
||||
- id: light_rd # Rote LED
|
||||
platform: gpio
|
||||
pin: 2
|
||||
|
||||
uart:
|
||||
id: mod_bus
|
||||
tx_pin: 1
|
||||
rx_pin: 3
|
||||
baud_rate: 115200
|
||||
|
||||
modbus:
|
||||
id: modbus1
|
||||
uart_id: mod_bus
|
||||
|
||||
modbus_controller:
|
||||
- id: growatt
|
||||
address: 1
|
||||
modbus_id: modbus1
|
||||
setup_priority: -10
|
||||
on_online:
|
||||
then:
|
||||
- output.turn_on: light_gr
|
||||
on_offline:
|
||||
then:
|
||||
- output.turn_off: light_gr
|
||||
|
||||
script:
|
||||
- id: blink_blue
|
||||
mode: single
|
||||
then:
|
||||
- output.turn_on: light_bl
|
||||
- delay: 200ms
|
||||
- output.turn_off: light_bl
|
||||
|
||||
sensor:
|
||||
# --- PV Eingang ---
|
||||
|
||||
- platform: modbus_controller
|
||||
name: "PV Voltage"
|
||||
address: 3
|
||||
register_type: read
|
||||
unit_of_measurement: V
|
||||
device_class: voltage
|
||||
state_class: measurement
|
||||
icon: mdi:solar-panel
|
||||
value_type: U_WORD
|
||||
accuracy_decimals: 1
|
||||
filters:
|
||||
- multiply: 0.1
|
||||
on_value:
|
||||
then:
|
||||
- script.execute: blink_blue
|
||||
|
||||
- platform: modbus_controller
|
||||
name: "PV Current"
|
||||
address: 4
|
||||
register_type: read
|
||||
unit_of_measurement: A
|
||||
device_class: current
|
||||
state_class: measurement
|
||||
icon: mdi:solar-panel
|
||||
value_type: U_WORD
|
||||
accuracy_decimals: 1
|
||||
filters:
|
||||
- multiply: 0.1
|
||||
|
||||
- platform: modbus_controller
|
||||
name: "PV Power"
|
||||
address: 5
|
||||
register_type: read
|
||||
unit_of_measurement: W
|
||||
device_class: power
|
||||
state_class: measurement
|
||||
icon: mdi:solar-panel
|
||||
value_type: U_DWORD
|
||||
accuracy_decimals: 1
|
||||
filters:
|
||||
- multiply: 0.1
|
||||
|
||||
# --- AC Ausgang / Netz ---
|
||||
|
||||
- platform: modbus_controller
|
||||
name: "Grid Frequency"
|
||||
address: 37
|
||||
register_type: read
|
||||
unit_of_measurement: Hz
|
||||
device_class: frequency
|
||||
state_class: measurement
|
||||
icon: mdi:sine-wave
|
||||
value_type: U_WORD
|
||||
accuracy_decimals: 2
|
||||
filters:
|
||||
- multiply: 0.01
|
||||
|
||||
- platform: modbus_controller
|
||||
name: "Grid Voltage"
|
||||
address: 38
|
||||
register_type: read
|
||||
unit_of_measurement: V
|
||||
device_class: voltage
|
||||
state_class: measurement
|
||||
icon: mdi:flash
|
||||
value_type: U_WORD
|
||||
accuracy_decimals: 1
|
||||
filters:
|
||||
- multiply: 0.1
|
||||
|
||||
- platform: modbus_controller
|
||||
name: "Grid Current"
|
||||
address: 39
|
||||
register_type: read
|
||||
unit_of_measurement: A
|
||||
device_class: current
|
||||
state_class: measurement
|
||||
icon: mdi:flash
|
||||
value_type: U_WORD
|
||||
accuracy_decimals: 1
|
||||
filters:
|
||||
- multiply: 0.1
|
||||
|
||||
- platform: modbus_controller
|
||||
name: "AC Output Power"
|
||||
address: 40
|
||||
register_type: read
|
||||
unit_of_measurement: W
|
||||
device_class: power
|
||||
state_class: measurement
|
||||
icon: mdi:flash
|
||||
value_type: U_DWORD
|
||||
accuracy_decimals: 1
|
||||
filters:
|
||||
- multiply: 0.1
|
||||
|
||||
# --- Energie (PV Erzeugung) ---
|
||||
|
||||
- platform: modbus_controller
|
||||
name: "Energy Today"
|
||||
address: 53
|
||||
register_type: read
|
||||
unit_of_measurement: kWh
|
||||
device_class: energy
|
||||
state_class: total_increasing
|
||||
icon: mdi:solar-power
|
||||
value_type: U_DWORD
|
||||
accuracy_decimals: 1
|
||||
filters:
|
||||
- multiply: 0.1
|
||||
|
||||
- platform: modbus_controller
|
||||
name: "Energy Total"
|
||||
address: 55
|
||||
register_type: read
|
||||
unit_of_measurement: kWh
|
||||
device_class: energy
|
||||
state_class: total_increasing
|
||||
icon: mdi:solar-power
|
||||
value_type: U_DWORD
|
||||
accuracy_decimals: 1
|
||||
filters:
|
||||
- multiply: 0.1
|
||||
|
||||
# --- Temperatur ---
|
||||
|
||||
- platform: modbus_controller
|
||||
name: "Inverter Temperature"
|
||||
address: 93
|
||||
register_type: read
|
||||
unit_of_measurement: "°C"
|
||||
device_class: temperature
|
||||
state_class: measurement
|
||||
icon: mdi:thermometer
|
||||
value_type: U_WORD
|
||||
accuracy_decimals: 1
|
||||
filters:
|
||||
- multiply: 0.1
|
||||
@@ -1,212 +0,0 @@
|
||||
esphome:
|
||||
name: growatt-mic-2000
|
||||
friendly_name: Growatt MIC 2000 TL-X
|
||||
|
||||
esp8266:
|
||||
board: esp07s
|
||||
|
||||
logger:
|
||||
|
||||
api:
|
||||
encryption:
|
||||
key: "HIER_API_KEY_EINTRAGEN"
|
||||
|
||||
ota:
|
||||
- platform: esphome
|
||||
password: "HIER_OTA_PASSWORT_EINTRAGEN"
|
||||
|
||||
wifi:
|
||||
ssid: "HIER_WLAN_NAME_EINTRAGEN"
|
||||
password: "HIER_WLAN_PASSWORT_EINTRAGEN"
|
||||
ap:
|
||||
ssid: "Growatt MIC 2000 TL-X Fallback Hotspot"
|
||||
password: "HIER_HOTSPOT_PASSWORT_EINTRAGEN"
|
||||
|
||||
captive_portal:
|
||||
|
||||
time:
|
||||
- platform: homeassistant
|
||||
id: homeassistant_time
|
||||
|
||||
output:
|
||||
- id: light_bl
|
||||
platform: gpio
|
||||
pin: 16
|
||||
- id: light_gr
|
||||
platform: gpio
|
||||
pin: 0
|
||||
- id: light_rd
|
||||
platform: gpio
|
||||
pin: 2
|
||||
|
||||
uart:
|
||||
id: mod_bus
|
||||
tx_pin: 1
|
||||
rx_pin: 3
|
||||
baud_rate: 115200
|
||||
|
||||
modbus:
|
||||
id: modbus1
|
||||
uart_id: mod_bus
|
||||
|
||||
modbus_controller:
|
||||
- id: growatt
|
||||
address: 1
|
||||
modbus_id: modbus1
|
||||
setup_priority: -10
|
||||
on_online:
|
||||
then:
|
||||
- output.turn_on: light_gr
|
||||
on_offline:
|
||||
then:
|
||||
- output.turn_off: light_gr
|
||||
|
||||
script:
|
||||
- id: blink_blue
|
||||
mode: single
|
||||
then:
|
||||
- output.turn_on: light_bl
|
||||
- delay: 200ms
|
||||
- output.turn_off: light_bl
|
||||
|
||||
sensor:
|
||||
# --- PV Eingang ---
|
||||
|
||||
- platform: modbus_controller
|
||||
name: "PV Voltage"
|
||||
address: 3
|
||||
register_type: read
|
||||
unit_of_measurement: V
|
||||
device_class: voltage
|
||||
state_class: measurement
|
||||
icon: mdi:solar-panel
|
||||
value_type: U_WORD
|
||||
accuracy_decimals: 1
|
||||
filters:
|
||||
- multiply: 0.1
|
||||
on_value:
|
||||
then:
|
||||
- script.execute: blink_blue
|
||||
|
||||
- platform: modbus_controller
|
||||
name: "PV Current"
|
||||
address: 4
|
||||
register_type: read
|
||||
unit_of_measurement: A
|
||||
device_class: current
|
||||
state_class: measurement
|
||||
icon: mdi:solar-panel
|
||||
value_type: U_WORD
|
||||
accuracy_decimals: 1
|
||||
filters:
|
||||
- multiply: 0.1
|
||||
|
||||
- platform: modbus_controller
|
||||
name: "PV Power"
|
||||
address: 5
|
||||
register_type: read
|
||||
unit_of_measurement: W
|
||||
device_class: power
|
||||
state_class: measurement
|
||||
icon: mdi:solar-panel
|
||||
value_type: U_DWORD
|
||||
accuracy_decimals: 1
|
||||
filters:
|
||||
- multiply: 0.1
|
||||
|
||||
# --- AC Ausgang / Netz ---
|
||||
|
||||
- platform: modbus_controller
|
||||
name: "Grid Frequency"
|
||||
address: 37
|
||||
register_type: read
|
||||
unit_of_measurement: Hz
|
||||
device_class: frequency
|
||||
state_class: measurement
|
||||
icon: mdi:sine-wave
|
||||
value_type: U_WORD
|
||||
accuracy_decimals: 2
|
||||
filters:
|
||||
- multiply: 0.01
|
||||
|
||||
- platform: modbus_controller
|
||||
name: "Grid Voltage"
|
||||
address: 38
|
||||
register_type: read
|
||||
unit_of_measurement: V
|
||||
device_class: voltage
|
||||
state_class: measurement
|
||||
icon: mdi:flash
|
||||
value_type: U_WORD
|
||||
accuracy_decimals: 1
|
||||
filters:
|
||||
- multiply: 0.1
|
||||
|
||||
- platform: modbus_controller
|
||||
name: "Grid Current"
|
||||
address: 39
|
||||
register_type: read
|
||||
unit_of_measurement: A
|
||||
device_class: current
|
||||
state_class: measurement
|
||||
icon: mdi:flash
|
||||
value_type: U_WORD
|
||||
accuracy_decimals: 1
|
||||
filters:
|
||||
- multiply: 0.1
|
||||
|
||||
- platform: modbus_controller
|
||||
name: "AC Output Power"
|
||||
address: 40
|
||||
register_type: read
|
||||
unit_of_measurement: W
|
||||
device_class: power
|
||||
state_class: measurement
|
||||
icon: mdi:flash
|
||||
value_type: U_DWORD
|
||||
accuracy_decimals: 1
|
||||
filters:
|
||||
- multiply: 0.1
|
||||
|
||||
# --- Energie (PV Erzeugung) ---
|
||||
|
||||
- platform: modbus_controller
|
||||
name: "Energy Today"
|
||||
address: 53
|
||||
register_type: read
|
||||
unit_of_measurement: kWh
|
||||
device_class: energy
|
||||
state_class: total_increasing
|
||||
icon: mdi:solar-power
|
||||
value_type: U_DWORD
|
||||
accuracy_decimals: 1
|
||||
filters:
|
||||
- multiply: 0.1
|
||||
|
||||
- platform: modbus_controller
|
||||
name: "Energy Total"
|
||||
address: 55
|
||||
register_type: read
|
||||
unit_of_measurement: kWh
|
||||
device_class: energy
|
||||
state_class: total_increasing
|
||||
icon: mdi:solar-power
|
||||
value_type: U_DWORD
|
||||
accuracy_decimals: 1
|
||||
filters:
|
||||
- multiply: 0.1
|
||||
|
||||
# --- Temperatur ---
|
||||
|
||||
- platform: modbus_controller
|
||||
name: "Inverter Temperature"
|
||||
address: 93
|
||||
register_type: read
|
||||
unit_of_measurement: "°C"
|
||||
device_class: temperature
|
||||
state_class: measurement
|
||||
icon: mdi:thermometer
|
||||
value_type: U_WORD
|
||||
accuracy_decimals: 1
|
||||
filters:
|
||||
- multiply: 0.1
|
||||
@@ -1,50 +0,0 @@
|
||||
# Growatt MIC 2000 TL-X — ESPHome Konfiguration
|
||||
|
||||
Modbus-RTU Integration des Growatt MIC 2000 TL-X (einphasiger String-Wechselrichter) über einen Growatt SuniStick mit ESPHome-Firmware.
|
||||
|
||||
Gleiche Registerstruktur wie der MIC 1500 TL-X — die MIC TL-X Familie verwendet ein einheitliches Protokoll.
|
||||
|
||||
---
|
||||
|
||||
## Modbus-Register Übersicht
|
||||
|
||||
Alle Register sind **Input Register** (Modbus Funktionscode 04, `register_type: "read"`).
|
||||
Modbus-Adresse des Geräts: `0x01`
|
||||
|
||||
### PV Eingang
|
||||
|
||||
| Name | Adresse | Typ | Multiplikator | Einheit |
|
||||
|------|---------|-----|---------------|---------|
|
||||
| PV Voltage | 3 | U_WORD | 0.1 | V |
|
||||
| PV Current | 4 | U_WORD | 0.1 | A |
|
||||
| PV Power | 5 | U_DWORD | 0.1 | W |
|
||||
|
||||
### AC Ausgang / Netz
|
||||
|
||||
| Name | Adresse | Typ | Multiplikator | Einheit |
|
||||
|------|---------|-----|---------------|---------|
|
||||
| Grid Frequency | 37 | U_WORD | **0.01** | Hz |
|
||||
| Grid Voltage | 38 | U_WORD | 0.1 | V |
|
||||
| Grid Current | 39 | U_WORD | 0.1 | A |
|
||||
| AC Output Power | 40 | U_DWORD | 0.1 | W |
|
||||
|
||||
### Energie (PV Erzeugung)
|
||||
|
||||
| Name | Adresse | Typ | Multiplikator | Einheit |
|
||||
|------|---------|-----|---------------|---------|
|
||||
| Energy Today | 53 | U_DWORD | 0.1 | kWh |
|
||||
| Energy Total | 55 | U_DWORD | 0.1 | kWh |
|
||||
|
||||
### Temperatur
|
||||
|
||||
| Name | Adresse | Typ | Multiplikator | Einheit |
|
||||
|------|---------|-----|---------------|---------|
|
||||
| Inverter Temperature | 93 | U_WORD | 0.1 | °C |
|
||||
|
||||
---
|
||||
|
||||
## Hinweise
|
||||
|
||||
- **Einphasig, kein Hybrid, kein Batterie-Interface** — keine Register >100 vorhanden.
|
||||
- Register identisch mit MIC 1500 TL-X. Andere MIC TL-X Modelle (1000W, 1500W, 2000W) verwenden dieselbe Registerstruktur.
|
||||
- Passwörter und API-Keys sind durch Platzhalter ersetzt und müssen vor dem Einsatz angepasst werden.
|
||||
@@ -1,331 +0,0 @@
|
||||
esphome:
|
||||
name: growatt-mod-6000
|
||||
friendly_name: Growatt MOD 6000 TL3-XH
|
||||
|
||||
esp8266:
|
||||
board: esp07s
|
||||
|
||||
logger:
|
||||
|
||||
api:
|
||||
encryption:
|
||||
key: "HIER_API_KEY_EINTRAGEN"
|
||||
|
||||
ota:
|
||||
- platform: esphome
|
||||
password: "HIER_OTA_PASSWORT_EINTRAGEN"
|
||||
|
||||
wifi:
|
||||
ssid: "HIER_WLAN_NAME_EINTRAGEN"
|
||||
password: "HIER_WLAN_PASSWORT_EINTRAGEN"
|
||||
ap:
|
||||
ssid: "Growatt MOD 6000 Fallback Hotspot"
|
||||
password: "HIER_HOTSPOT_PASSWORT_EINTRAGEN"
|
||||
|
||||
captive_portal:
|
||||
|
||||
time:
|
||||
- platform: homeassistant
|
||||
id: homeassistant_time
|
||||
|
||||
output:
|
||||
- id: light_bl
|
||||
platform: gpio
|
||||
pin: 16
|
||||
- id: light_gr
|
||||
platform: gpio
|
||||
pin: 0
|
||||
- id: light_rd
|
||||
platform: gpio
|
||||
pin: 2
|
||||
|
||||
uart:
|
||||
id: mod_bus
|
||||
tx_pin: 1
|
||||
rx_pin: 3
|
||||
baud_rate: 115200
|
||||
|
||||
modbus:
|
||||
id: modbus1
|
||||
uart_id: mod_bus
|
||||
|
||||
modbus_controller:
|
||||
- id: growatt
|
||||
address: 1
|
||||
modbus_id: modbus1
|
||||
setup_priority: -10
|
||||
on_online:
|
||||
then:
|
||||
- output.turn_on: light_gr
|
||||
on_offline:
|
||||
then:
|
||||
- output.turn_off: light_gr
|
||||
|
||||
script:
|
||||
- id: blink_blue
|
||||
mode: single
|
||||
then:
|
||||
- output.turn_on: light_bl
|
||||
- delay: 200ms
|
||||
- output.turn_off: light_bl
|
||||
|
||||
sensor:
|
||||
# --- PV Eingang ---
|
||||
|
||||
- platform: modbus_controller
|
||||
name: "PV1 Voltage"
|
||||
address: 3003
|
||||
register_type: read
|
||||
unit_of_measurement: V
|
||||
device_class: voltage
|
||||
state_class: measurement
|
||||
icon: mdi:solar-panel
|
||||
value_type: U_WORD
|
||||
accuracy_decimals: 1
|
||||
filters:
|
||||
- multiply: 0.1
|
||||
on_value:
|
||||
then:
|
||||
- script.execute: blink_blue
|
||||
|
||||
- platform: modbus_controller
|
||||
name: "PV1 Current"
|
||||
address: 3004
|
||||
register_type: read
|
||||
unit_of_measurement: A
|
||||
device_class: current
|
||||
state_class: measurement
|
||||
icon: mdi:solar-panel
|
||||
value_type: U_WORD
|
||||
accuracy_decimals: 1
|
||||
filters:
|
||||
- multiply: 0.1
|
||||
|
||||
- platform: modbus_controller
|
||||
name: "PV1 Power"
|
||||
address: 3005
|
||||
register_type: read
|
||||
unit_of_measurement: W
|
||||
device_class: power
|
||||
state_class: measurement
|
||||
icon: mdi:solar-panel
|
||||
value_type: U_DWORD
|
||||
accuracy_decimals: 1
|
||||
filters:
|
||||
- multiply: 0.1
|
||||
|
||||
- platform: modbus_controller
|
||||
name: "PV2 Voltage"
|
||||
address: 3007
|
||||
register_type: read
|
||||
unit_of_measurement: V
|
||||
device_class: voltage
|
||||
state_class: measurement
|
||||
icon: mdi:solar-panel
|
||||
value_type: U_WORD
|
||||
accuracy_decimals: 1
|
||||
filters:
|
||||
- multiply: 0.1
|
||||
|
||||
- platform: modbus_controller
|
||||
name: "PV2 Current"
|
||||
address: 3008
|
||||
register_type: read
|
||||
unit_of_measurement: A
|
||||
device_class: current
|
||||
state_class: measurement
|
||||
icon: mdi:solar-panel
|
||||
value_type: U_WORD
|
||||
accuracy_decimals: 1
|
||||
filters:
|
||||
- multiply: 0.1
|
||||
|
||||
- platform: modbus_controller
|
||||
name: "PV2 Power"
|
||||
address: 3009
|
||||
register_type: read
|
||||
unit_of_measurement: W
|
||||
device_class: power
|
||||
state_class: measurement
|
||||
icon: mdi:solar-panel
|
||||
value_type: U_DWORD
|
||||
accuracy_decimals: 1
|
||||
filters:
|
||||
- multiply: 0.1
|
||||
|
||||
# --- AC Ausgang / Netz (dreiphasig) ---
|
||||
|
||||
- platform: modbus_controller
|
||||
name: "AC Output Power Total"
|
||||
address: 3023
|
||||
register_type: read
|
||||
unit_of_measurement: W
|
||||
device_class: power
|
||||
state_class: measurement
|
||||
icon: mdi:flash
|
||||
value_type: U_DWORD
|
||||
accuracy_decimals: 1
|
||||
filters:
|
||||
- multiply: 0.1
|
||||
|
||||
- platform: modbus_controller
|
||||
name: "Grid Frequency"
|
||||
address: 3025
|
||||
register_type: read
|
||||
unit_of_measurement: Hz
|
||||
device_class: frequency
|
||||
state_class: measurement
|
||||
icon: mdi:sine-wave
|
||||
value_type: U_WORD
|
||||
accuracy_decimals: 2
|
||||
filters:
|
||||
- multiply: 0.01
|
||||
|
||||
- platform: modbus_controller
|
||||
name: "Grid Voltage L1"
|
||||
address: 3026
|
||||
register_type: read
|
||||
unit_of_measurement: V
|
||||
device_class: voltage
|
||||
state_class: measurement
|
||||
icon: mdi:flash
|
||||
value_type: U_WORD
|
||||
accuracy_decimals: 1
|
||||
filters:
|
||||
- multiply: 0.1
|
||||
|
||||
- platform: modbus_controller
|
||||
name: "Grid Current L1"
|
||||
address: 3027
|
||||
register_type: read
|
||||
unit_of_measurement: A
|
||||
device_class: current
|
||||
state_class: measurement
|
||||
icon: mdi:flash
|
||||
value_type: U_WORD
|
||||
accuracy_decimals: 1
|
||||
filters:
|
||||
- multiply: 0.1
|
||||
|
||||
- platform: modbus_controller
|
||||
name: "Grid Voltage L2"
|
||||
address: 3030
|
||||
register_type: read
|
||||
unit_of_measurement: V
|
||||
device_class: voltage
|
||||
state_class: measurement
|
||||
icon: mdi:flash
|
||||
value_type: U_WORD
|
||||
accuracy_decimals: 1
|
||||
filters:
|
||||
- multiply: 0.1
|
||||
|
||||
- platform: modbus_controller
|
||||
name: "Grid Current L2"
|
||||
address: 3031
|
||||
register_type: read
|
||||
unit_of_measurement: A
|
||||
device_class: current
|
||||
state_class: measurement
|
||||
icon: mdi:flash
|
||||
value_type: U_WORD
|
||||
accuracy_decimals: 1
|
||||
filters:
|
||||
- multiply: 0.1
|
||||
|
||||
- platform: modbus_controller
|
||||
name: "Grid Voltage L3"
|
||||
address: 3034
|
||||
register_type: read
|
||||
unit_of_measurement: V
|
||||
device_class: voltage
|
||||
state_class: measurement
|
||||
icon: mdi:flash
|
||||
value_type: U_WORD
|
||||
accuracy_decimals: 1
|
||||
filters:
|
||||
- multiply: 0.1
|
||||
|
||||
- platform: modbus_controller
|
||||
name: "Grid Current L3"
|
||||
address: 3035
|
||||
register_type: read
|
||||
unit_of_measurement: A
|
||||
device_class: current
|
||||
state_class: measurement
|
||||
icon: mdi:flash
|
||||
value_type: U_WORD
|
||||
accuracy_decimals: 1
|
||||
filters:
|
||||
- multiply: 0.1
|
||||
|
||||
# --- Energie (PV Erzeugung) ---
|
||||
|
||||
- platform: modbus_controller
|
||||
name: "Energy Today"
|
||||
address: 3049
|
||||
register_type: read
|
||||
unit_of_measurement: kWh
|
||||
device_class: energy
|
||||
state_class: total_increasing
|
||||
icon: mdi:solar-power
|
||||
value_type: U_DWORD
|
||||
accuracy_decimals: 1
|
||||
filters:
|
||||
- multiply: 0.1
|
||||
|
||||
- platform: modbus_controller
|
||||
name: "Energy Total"
|
||||
address: 3051
|
||||
register_type: read
|
||||
unit_of_measurement: kWh
|
||||
device_class: energy
|
||||
state_class: total_increasing
|
||||
icon: mdi:solar-power
|
||||
value_type: U_DWORD
|
||||
accuracy_decimals: 1
|
||||
filters:
|
||||
- multiply: 0.1
|
||||
|
||||
# --- Temperatur ---
|
||||
|
||||
- platform: modbus_controller
|
||||
name: "Inverter Temperature"
|
||||
address: 3093
|
||||
register_type: read
|
||||
unit_of_measurement: "°C"
|
||||
device_class: temperature
|
||||
state_class: measurement
|
||||
icon: mdi:thermometer
|
||||
value_type: U_WORD
|
||||
accuracy_decimals: 1
|
||||
filters:
|
||||
- multiply: 0.1
|
||||
|
||||
# --- Smartmeter (optional, nur wenn Smartmeter angeschlossen) ---
|
||||
|
||||
- platform: modbus_controller
|
||||
name: "Power To User"
|
||||
address: 3041
|
||||
register_type: read
|
||||
unit_of_measurement: W
|
||||
device_class: power
|
||||
state_class: measurement
|
||||
icon: mdi:transmission-tower-import
|
||||
value_type: U_DWORD
|
||||
accuracy_decimals: 1
|
||||
filters:
|
||||
- multiply: 0.1
|
||||
|
||||
- platform: modbus_controller
|
||||
name: "Power To Grid"
|
||||
address: 3043
|
||||
register_type: read
|
||||
unit_of_measurement: W
|
||||
device_class: power
|
||||
state_class: measurement
|
||||
icon: mdi:transmission-tower-export
|
||||
value_type: U_DWORD
|
||||
accuracy_decimals: 1
|
||||
filters:
|
||||
- multiply: 0.1
|
||||
@@ -1,67 +0,0 @@
|
||||
# Growatt MOD 6000 TL3-XH — ESPHome Konfiguration
|
||||
|
||||
Modbus-RTU Integration des Growatt MOD 6000 TL3-XH (dreiphasiger String-Wechselrichter, kein Hybrid) über einen Growatt SuniStick mit ESPHome-Firmware.
|
||||
|
||||
---
|
||||
|
||||
## Modbus-Register Übersicht
|
||||
|
||||
Alle Register sind **Input Register** (Modbus Funktionscode 04, `register_type: "read"`).
|
||||
Modbus-Adresse des Geräts: `0x01`
|
||||
|
||||
> **Wichtig:** Die MOD TL3-XH Serie verwendet 3000er-Register — nicht die kleinen Register der MIC/MIN-Serie.
|
||||
|
||||
### PV Eingang
|
||||
|
||||
| Name | Adresse | Typ | Multiplikator | Einheit |
|
||||
|------|---------|-----|---------------|---------|
|
||||
| PV1 Voltage | 3003 | U_WORD | 0.1 | V |
|
||||
| PV1 Current | 3004 | U_WORD | 0.1 | A |
|
||||
| PV1 Power | 3005 | U_DWORD | 0.1 | W |
|
||||
| PV2 Voltage | 3007 | U_WORD | 0.1 | V |
|
||||
| PV2 Current | 3008 | U_WORD | 0.1 | A |
|
||||
| PV2 Power | 3009 | U_DWORD | 0.1 | W |
|
||||
|
||||
### AC Ausgang / Netz (dreiphasig)
|
||||
|
||||
| Name | Adresse | Typ | Multiplikator | Einheit |
|
||||
|------|---------|-----|---------------|---------|
|
||||
| AC Output Power Total | 3023 | U_DWORD | 0.1 | W |
|
||||
| Grid Frequency | 3025 | U_WORD | **0.01** | Hz |
|
||||
| Grid Voltage L1 | 3026 | U_WORD | 0.1 | V |
|
||||
| Grid Current L1 | 3027 | U_WORD | 0.1 | A |
|
||||
| Grid Voltage L2 | 3030 | U_WORD | 0.1 | V |
|
||||
| Grid Current L2 | 3031 | U_WORD | 0.1 | A |
|
||||
| Grid Voltage L3 | 3034 | U_WORD | 0.1 | V |
|
||||
| Grid Current L3 | 3035 | U_WORD | 0.1 | A |
|
||||
|
||||
### Energie (PV Erzeugung)
|
||||
|
||||
| Name | Adresse | Typ | Multiplikator | Einheit |
|
||||
|------|---------|-----|---------------|---------|
|
||||
| Energy Today | 3049 | U_DWORD | 0.1 | kWh |
|
||||
| Energy Total | 3051 | U_DWORD | 0.1 | kWh |
|
||||
|
||||
### Temperatur
|
||||
|
||||
| Name | Adresse | Typ | Multiplikator | Einheit |
|
||||
|------|---------|-----|---------------|---------|
|
||||
| Inverter Temperature | 3093 | U_WORD | 0.1 | °C |
|
||||
|
||||
### Smartmeter (optional)
|
||||
|
||||
Nur relevant wenn ein Smartmeter am Wechselrichter angeschlossen ist.
|
||||
|
||||
| Name | Adresse | Typ | Multiplikator | Einheit |
|
||||
|------|---------|-----|---------------|---------|
|
||||
| Power To User (Bezug) | 3041 | U_DWORD | 0.1 | W |
|
||||
| Power To Grid (Einspeisung) | 3043 | U_DWORD | 0.1 | W |
|
||||
|
||||
---
|
||||
|
||||
## Hinweise
|
||||
|
||||
- **Dreiphasig, kein Hybrid, kein Batterie-Interface.**
|
||||
- Registerstruktur verifiziert gegen Growatt MOD TL3-XH Modbus-Protokoll (gleiche Familie wie MOD 7000 TL3-XH).
|
||||
- Ohne angeschlossenen Smartmeter liefern Register 3041/3043 Nullwerte — diese Sensoren können dann entfernt werden.
|
||||
- Passwörter und API-Keys sind durch Platzhalter ersetzt und müssen vor dem Einsatz angepasst werden.
|
||||
@@ -1,452 +0,0 @@
|
||||
esphome:
|
||||
name: growatt-sph5000
|
||||
friendly_name: Growatt SPH 5000 TL3-BH-UP
|
||||
|
||||
esp8266:
|
||||
board: esp07s
|
||||
|
||||
logger:
|
||||
|
||||
api:
|
||||
encryption:
|
||||
key: "HIER_API_KEY_EINTRAGEN" # 32-Byte Base64-Key, wird in der ESPHome-UI automatisch generiert
|
||||
|
||||
ota:
|
||||
- platform: esphome
|
||||
password: "HIER_OTA_PASSWORT_EINTRAGEN" # Beliebiges Passwort für Over-the-Air Updates
|
||||
|
||||
wifi:
|
||||
ssid: "HIER_WLAN_NAME_EINTRAGEN" # Name deines WLANs (SSID)
|
||||
password: "HIER_WLAN_PASSWORT_EINTRAGEN" # Passwort deines WLANs
|
||||
ap:
|
||||
ssid: "Growatt-SPH5000 Fallback Hotspot"
|
||||
password: "HIER_HOTSPOT_PASSWORT_EINTRAGEN" # Passwort für den Notfall-Hotspot (min. 8 Zeichen)
|
||||
|
||||
captive_portal:
|
||||
|
||||
time:
|
||||
- platform: homeassistant
|
||||
id: homeassistant_time
|
||||
|
||||
output:
|
||||
- id: light_bl
|
||||
platform: gpio
|
||||
pin: 16
|
||||
- id: light_gr
|
||||
platform: gpio
|
||||
pin: 0
|
||||
- id: light_rd
|
||||
platform: gpio
|
||||
pin: 2
|
||||
|
||||
uart:
|
||||
id: mod_bus
|
||||
tx_pin: 1
|
||||
rx_pin: 3
|
||||
baud_rate: 115200
|
||||
|
||||
modbus:
|
||||
id: modbus1
|
||||
uart_id: mod_bus
|
||||
|
||||
modbus_controller:
|
||||
- id: growatt
|
||||
address: 1
|
||||
modbus_id: modbus1
|
||||
setup_priority: -10
|
||||
on_online:
|
||||
then:
|
||||
- output.turn_on: light_gr
|
||||
on_offline:
|
||||
then:
|
||||
- output.turn_off: light_gr
|
||||
|
||||
script:
|
||||
- id: blink_blue
|
||||
mode: single
|
||||
then:
|
||||
- output.turn_on: light_bl
|
||||
- delay: 200ms
|
||||
- output.turn_off: light_bl
|
||||
|
||||
sensor:
|
||||
# --- PV Eingang ---
|
||||
|
||||
- platform: modbus_controller
|
||||
name: "PV1 Voltage"
|
||||
address: 3
|
||||
register_type: read
|
||||
unit_of_measurement: V
|
||||
device_class: voltage
|
||||
state_class: measurement
|
||||
icon: mdi:solar-panel
|
||||
value_type: U_WORD
|
||||
accuracy_decimals: 1
|
||||
filters:
|
||||
- multiply: 0.1
|
||||
on_value:
|
||||
then:
|
||||
- script.execute: blink_blue
|
||||
|
||||
- platform: modbus_controller
|
||||
name: "PV1 Current"
|
||||
address: 4
|
||||
register_type: read
|
||||
unit_of_measurement: A
|
||||
device_class: current
|
||||
state_class: measurement
|
||||
icon: mdi:solar-panel
|
||||
value_type: U_WORD
|
||||
accuracy_decimals: 1
|
||||
filters:
|
||||
- multiply: 0.1
|
||||
|
||||
- platform: modbus_controller
|
||||
name: "PV1 Power"
|
||||
address: 5
|
||||
register_type: read
|
||||
unit_of_measurement: W
|
||||
device_class: power
|
||||
state_class: measurement
|
||||
icon: mdi:solar-panel
|
||||
value_type: U_DWORD
|
||||
accuracy_decimals: 1
|
||||
filters:
|
||||
- multiply: 0.1
|
||||
|
||||
- platform: modbus_controller
|
||||
name: "PV2 Voltage"
|
||||
address: 7
|
||||
register_type: read
|
||||
unit_of_measurement: V
|
||||
device_class: voltage
|
||||
state_class: measurement
|
||||
icon: mdi:solar-panel
|
||||
value_type: U_WORD
|
||||
accuracy_decimals: 1
|
||||
filters:
|
||||
- multiply: 0.1
|
||||
|
||||
- platform: modbus_controller
|
||||
name: "PV2 Current"
|
||||
address: 8
|
||||
register_type: read
|
||||
unit_of_measurement: A
|
||||
device_class: current
|
||||
state_class: measurement
|
||||
icon: mdi:solar-panel
|
||||
value_type: U_WORD
|
||||
accuracy_decimals: 1
|
||||
filters:
|
||||
- multiply: 0.1
|
||||
|
||||
- platform: modbus_controller
|
||||
name: "PV2 Power"
|
||||
address: 9
|
||||
register_type: read
|
||||
unit_of_measurement: W
|
||||
device_class: power
|
||||
state_class: measurement
|
||||
icon: mdi:solar-panel
|
||||
value_type: U_DWORD
|
||||
accuracy_decimals: 1
|
||||
filters:
|
||||
- multiply: 0.1
|
||||
|
||||
# --- AC Ausgang / Netz ---
|
||||
|
||||
- platform: modbus_controller
|
||||
name: "AC Output Power Total"
|
||||
address: 35
|
||||
register_type: read
|
||||
unit_of_measurement: W
|
||||
device_class: power
|
||||
state_class: measurement
|
||||
icon: mdi:flash
|
||||
value_type: U_DWORD
|
||||
accuracy_decimals: 1
|
||||
filters:
|
||||
- multiply: 0.1
|
||||
|
||||
- platform: modbus_controller
|
||||
name: "Grid Frequency"
|
||||
address: 37
|
||||
register_type: read
|
||||
unit_of_measurement: Hz
|
||||
device_class: frequency
|
||||
state_class: measurement
|
||||
icon: mdi:sine-wave
|
||||
value_type: U_WORD
|
||||
accuracy_decimals: 2
|
||||
filters:
|
||||
- multiply: 0.01
|
||||
|
||||
- platform: modbus_controller
|
||||
name: "Grid Voltage L1"
|
||||
address: 38
|
||||
register_type: read
|
||||
unit_of_measurement: V
|
||||
device_class: voltage
|
||||
state_class: measurement
|
||||
icon: mdi:flash
|
||||
value_type: U_WORD
|
||||
accuracy_decimals: 1
|
||||
filters:
|
||||
- multiply: 0.1
|
||||
|
||||
- platform: modbus_controller
|
||||
name: "Grid Current L1"
|
||||
address: 39
|
||||
register_type: read
|
||||
unit_of_measurement: A
|
||||
device_class: current
|
||||
state_class: measurement
|
||||
icon: mdi:flash
|
||||
value_type: U_WORD
|
||||
accuracy_decimals: 1
|
||||
filters:
|
||||
- multiply: 0.1
|
||||
|
||||
- platform: modbus_controller
|
||||
name: "Grid Voltage L2"
|
||||
address: 42
|
||||
register_type: read
|
||||
unit_of_measurement: V
|
||||
device_class: voltage
|
||||
state_class: measurement
|
||||
icon: mdi:flash
|
||||
value_type: U_WORD
|
||||
accuracy_decimals: 1
|
||||
filters:
|
||||
- multiply: 0.1
|
||||
|
||||
- platform: modbus_controller
|
||||
name: "Grid Current L2"
|
||||
address: 43
|
||||
register_type: read
|
||||
unit_of_measurement: A
|
||||
device_class: current
|
||||
state_class: measurement
|
||||
icon: mdi:flash
|
||||
value_type: U_WORD
|
||||
accuracy_decimals: 1
|
||||
filters:
|
||||
- multiply: 0.1
|
||||
|
||||
- platform: modbus_controller
|
||||
name: "Grid Voltage L3"
|
||||
address: 46
|
||||
register_type: read
|
||||
unit_of_measurement: V
|
||||
device_class: voltage
|
||||
state_class: measurement
|
||||
icon: mdi:flash
|
||||
value_type: U_WORD
|
||||
accuracy_decimals: 1
|
||||
filters:
|
||||
- multiply: 0.1
|
||||
|
||||
- platform: modbus_controller
|
||||
name: "Grid Current L3"
|
||||
address: 47
|
||||
register_type: read
|
||||
unit_of_measurement: A
|
||||
device_class: current
|
||||
state_class: measurement
|
||||
icon: mdi:flash
|
||||
value_type: U_WORD
|
||||
accuracy_decimals: 1
|
||||
filters:
|
||||
- multiply: 0.1
|
||||
|
||||
# --- Energie (PV Erzeugung) ---
|
||||
|
||||
- platform: modbus_controller
|
||||
name: "Energy Today"
|
||||
address: 53
|
||||
register_type: read
|
||||
unit_of_measurement: kWh
|
||||
device_class: energy
|
||||
state_class: total_increasing
|
||||
icon: mdi:solar-power
|
||||
value_type: U_DWORD
|
||||
accuracy_decimals: 1
|
||||
filters:
|
||||
- multiply: 0.1
|
||||
|
||||
- platform: modbus_controller
|
||||
name: "Energy Total"
|
||||
address: 55
|
||||
register_type: read
|
||||
unit_of_measurement: kWh
|
||||
device_class: energy
|
||||
state_class: total_increasing
|
||||
icon: mdi:solar-power
|
||||
value_type: U_DWORD
|
||||
accuracy_decimals: 1
|
||||
filters:
|
||||
- multiply: 0.1
|
||||
|
||||
# --- Temperatur ---
|
||||
|
||||
- platform: modbus_controller
|
||||
name: "Inverter Temperature"
|
||||
address: 93
|
||||
register_type: read
|
||||
unit_of_measurement: "°C"
|
||||
device_class: temperature
|
||||
state_class: measurement
|
||||
icon: mdi:thermometer
|
||||
value_type: U_WORD
|
||||
accuracy_decimals: 1
|
||||
filters:
|
||||
- multiply: 0.1
|
||||
|
||||
# --- Batterie ---
|
||||
|
||||
- platform: modbus_controller
|
||||
name: "Battery Discharge Power"
|
||||
address: 1009
|
||||
register_type: read
|
||||
unit_of_measurement: W
|
||||
device_class: power
|
||||
state_class: measurement
|
||||
icon: mdi:battery-minus
|
||||
value_type: U_DWORD
|
||||
accuracy_decimals: 1
|
||||
filters:
|
||||
- multiply: 0.1
|
||||
|
||||
- platform: modbus_controller
|
||||
name: "Battery Charge Power"
|
||||
address: 1011
|
||||
register_type: read
|
||||
unit_of_measurement: W
|
||||
device_class: power
|
||||
state_class: measurement
|
||||
icon: mdi:battery-plus
|
||||
value_type: U_DWORD
|
||||
accuracy_decimals: 1
|
||||
filters:
|
||||
- multiply: 0.1
|
||||
|
||||
- platform: modbus_controller
|
||||
name: "Battery Voltage"
|
||||
address: 1013
|
||||
register_type: read
|
||||
unit_of_measurement: V
|
||||
device_class: voltage
|
||||
state_class: measurement
|
||||
icon: mdi:battery
|
||||
value_type: U_WORD
|
||||
accuracy_decimals: 1
|
||||
filters:
|
||||
- multiply: 0.1
|
||||
|
||||
- platform: modbus_controller
|
||||
name: "Battery State of Charge"
|
||||
address: 1014
|
||||
register_type: read
|
||||
unit_of_measurement: "%"
|
||||
device_class: battery
|
||||
state_class: measurement
|
||||
icon: mdi:battery
|
||||
value_type: U_WORD
|
||||
accuracy_decimals: 0
|
||||
|
||||
- platform: modbus_controller
|
||||
name: "Battery Temperature"
|
||||
address: 1040
|
||||
register_type: read
|
||||
unit_of_measurement: "°C"
|
||||
device_class: temperature
|
||||
state_class: measurement
|
||||
icon: mdi:thermometer
|
||||
value_type: U_WORD
|
||||
accuracy_decimals: 1
|
||||
filters:
|
||||
- multiply: 0.1
|
||||
|
||||
# --- Batterie Energie (kWh Zähler für Energie-Dashboard) ---
|
||||
|
||||
- platform: modbus_controller
|
||||
name: "Battery Discharge Total"
|
||||
address: 1054
|
||||
register_type: read
|
||||
unit_of_measurement: kWh
|
||||
device_class: energy
|
||||
state_class: total_increasing
|
||||
icon: mdi:battery-minus
|
||||
value_type: U_DWORD
|
||||
accuracy_decimals: 1
|
||||
filters:
|
||||
- multiply: 0.1
|
||||
|
||||
- platform: modbus_controller
|
||||
name: "Battery Charge Total"
|
||||
address: 1058
|
||||
register_type: read
|
||||
unit_of_measurement: kWh
|
||||
device_class: energy
|
||||
state_class: total_increasing
|
||||
icon: mdi:battery-plus
|
||||
value_type: U_DWORD
|
||||
accuracy_decimals: 1
|
||||
filters:
|
||||
- multiply: 0.1
|
||||
|
||||
# --- Smartmeter Momentanleistung ---
|
||||
|
||||
- platform: modbus_controller
|
||||
name: "Power To Grid"
|
||||
address: 1021
|
||||
register_type: read
|
||||
unit_of_measurement: W
|
||||
device_class: power
|
||||
state_class: measurement
|
||||
icon: mdi:transmission-tower-export
|
||||
value_type: U_DWORD
|
||||
accuracy_decimals: 1
|
||||
filters:
|
||||
- multiply: 0.1
|
||||
|
||||
- platform: modbus_controller
|
||||
name: "Power To User"
|
||||
address: 1029
|
||||
register_type: read
|
||||
unit_of_measurement: W
|
||||
device_class: power
|
||||
state_class: measurement
|
||||
icon: mdi:transmission-tower-import
|
||||
value_type: U_DWORD
|
||||
accuracy_decimals: 1
|
||||
filters:
|
||||
- multiply: 0.1
|
||||
|
||||
# --- Netzenergie Import / Export (kWh Zähler für Energie-Dashboard) ---
|
||||
|
||||
- platform: modbus_controller
|
||||
name: "Energy Import Total"
|
||||
address: 1046
|
||||
register_type: read
|
||||
unit_of_measurement: kWh
|
||||
device_class: energy
|
||||
state_class: total_increasing
|
||||
icon: mdi:transmission-tower-import
|
||||
value_type: U_DWORD
|
||||
accuracy_decimals: 1
|
||||
filters:
|
||||
- multiply: 0.1
|
||||
|
||||
- platform: modbus_controller
|
||||
name: "Energy Export Total"
|
||||
address: 1050
|
||||
register_type: read
|
||||
unit_of_measurement: kWh
|
||||
device_class: energy
|
||||
state_class: total_increasing
|
||||
icon: mdi:transmission-tower-export
|
||||
value_type: U_DWORD
|
||||
accuracy_decimals: 1
|
||||
filters:
|
||||
- multiply: 0.1
|
||||
@@ -1,92 +0,0 @@
|
||||
# Growatt SPH 5000 TL3-BH-UP — ESPHome Konfiguration
|
||||
|
||||
Modbus-RTU Integration des Growatt SPH 5000 TL3-BH-UP (3-phasiger Hybrid-Wechselrichter mit Batterie) über einen Growatt SuniStick mit ESPHome-Firmware.
|
||||
|
||||
---
|
||||
|
||||
## Modbus-Register Übersicht
|
||||
|
||||
Alle Register sind **Input Register** (Modbus Funktionscode 04, `register_type: "read"`).
|
||||
Modbus-Adresse des Geräts: `0x01`
|
||||
|
||||
### PV Eingang
|
||||
|
||||
| Name | Adresse | Typ | Multiplikator | Einheit |
|
||||
|------|---------|-----|---------------|---------|
|
||||
| PV1 Voltage | 3 | U_WORD | 0.1 | V |
|
||||
| PV1 Current | 4 | U_WORD | 0.1 | A |
|
||||
| PV1 Power | 5 | U_DWORD | 0.1 | W |
|
||||
| PV2 Voltage | 7 | U_WORD | 0.1 | V |
|
||||
| PV2 Current | 8 | U_WORD | 0.1 | A |
|
||||
| PV2 Power | 9 | U_DWORD | 0.1 | W |
|
||||
|
||||
### AC Ausgang / Netz
|
||||
|
||||
| Name | Adresse | Typ | Multiplikator | Einheit |
|
||||
|------|---------|-----|---------------|---------|
|
||||
| AC Output Power Total | 35 | U_DWORD | 0.1 | W |
|
||||
| Grid Frequency | 37 | U_WORD | **0.01** | Hz |
|
||||
| Grid Voltage L1 | 38 | U_WORD | 0.1 | V |
|
||||
| Grid Current L1 | 39 | U_WORD | 0.1 | A |
|
||||
| Grid Voltage L2 | 42 | U_WORD | 0.1 | V |
|
||||
| Grid Current L2 | 43 | U_WORD | 0.1 | A |
|
||||
| Grid Voltage L3 | 46 | U_WORD | 0.1 | V |
|
||||
| Grid Current L3 | 47 | U_WORD | 0.1 | A |
|
||||
|
||||
> **Wichtig:** Grid Frequency hat Multiplikator `0.01` — der Rohwert ist z.B. `5000` = 50,00 Hz.
|
||||
|
||||
### Energie (PV Erzeugung)
|
||||
|
||||
| Name | Adresse | Typ | Multiplikator | Einheit |
|
||||
|------|---------|-----|---------------|---------|
|
||||
| Energy Today | 53 | U_DWORD | 0.1 | kWh |
|
||||
| Energy Total | 55 | U_DWORD | 0.1 | kWh |
|
||||
|
||||
### Temperatur
|
||||
|
||||
| Name | Adresse | Typ | Multiplikator | Einheit |
|
||||
|------|---------|-----|---------------|---------|
|
||||
| Inverter Temperature | 93 | U_WORD | 0.1 | °C |
|
||||
|
||||
### Batterie — Momentanleistung
|
||||
|
||||
| Name | Adresse | Typ | Multiplikator | Einheit |
|
||||
|------|---------|-----|---------------|---------|
|
||||
| Battery Discharge Power | 1009 | U_DWORD | 0.1 | W |
|
||||
| Battery Charge Power | 1011 | U_DWORD | 0.1 | W |
|
||||
| Battery Voltage | 1013 | U_WORD | 0.1 | V |
|
||||
| Battery State of Charge | 1014 | U_WORD | 1 | % |
|
||||
| Battery Temperature | 1040 | U_WORD | 0.1 | °C |
|
||||
|
||||
### Batterie — Energie (für Energie-Dashboard)
|
||||
|
||||
| Name | Adresse | Typ | Multiplikator | Einheit |
|
||||
|------|---------|-----|---------------|---------|
|
||||
| Battery Discharge Total | 1054 | U_DWORD | 0.1 | kWh |
|
||||
| Battery Charge Total | 1058 | U_DWORD | 0.1 | kWh |
|
||||
|
||||
> **Wichtig:** 1009/1011 = Momentanleistung in **Watt**. 1054/1058 = kumulierte Energie in **kWh** für das HA Energie-Dashboard.
|
||||
|
||||
### Smartmeter — Momentanleistung
|
||||
|
||||
| Name | Adresse | Typ | Multiplikator | Einheit |
|
||||
|------|---------|-----|---------------|---------|
|
||||
| Power To Grid | 1021 | U_DWORD | 0.1 | W |
|
||||
| Power To User | 1029 | U_DWORD | 0.1 | W |
|
||||
|
||||
> **Hinweis:** Beide Werte sind immer positiv (unsigned). "To Grid" = Einspeisung, "To User" = Bezug vom Netz.
|
||||
|
||||
### Netzenergie Import / Export (für Energie-Dashboard)
|
||||
|
||||
| Name | Adresse | Typ | Multiplikator | Einheit |
|
||||
|------|---------|-----|---------------|---------|
|
||||
| Energy Import Total | 1046 | U_DWORD | 0.1 | kWh |
|
||||
| Energy Export Total | 1050 | U_DWORD | 0.1 | kWh |
|
||||
|
||||
---
|
||||
|
||||
## Hinweise
|
||||
|
||||
- **U_DWORD** = 32-Bit-Wert über 2 aufeinanderfolgende Register. ESPHome liest diese automatisch korrekt, wenn `value_type: U_DWORD` gesetzt ist.
|
||||
- Passwörter und API-Keys sind durch `#` ersetzt und müssen vor dem Einsatz angepasst werden.
|
||||
- Diese Config wurde für den SPH 5000 TL3-BH-UP verifiziert. Andere SPH-Modelle (SPH 3000, SPH 6000 usw.) verwenden dieselbe Registerstruktur.
|
||||
@@ -1,5 +1,5 @@
|
||||
name: ShineBridge
|
||||
version: "1.9.0"
|
||||
version: "1.9.4"
|
||||
slug: shinebridge
|
||||
description: Energiemanagement für Growatt/Goodwe-Wechselrichter — PV-Überschussladen, Wallbox-EMS, Tarifvergleich, Zigbee2MQTT-Überschussgeräte
|
||||
url: https://gitea.bitfire.work/retr0/shinebridge
|
||||
|
||||
@@ -797,14 +797,40 @@ async function loadFinance() {
|
||||
</div>`;
|
||||
|
||||
// ── SVG-Balkendiagramm ────────────────────────────────────────
|
||||
const W = 600, H = 180, PL = 44, PR = 12, PT = 10, PB = 28;
|
||||
// Bei langen Abrechnungsjahren nicht jeden einzelnen Tag zeigen — statt 112
|
||||
// Tagesbalken lieber Wochenbalken, und davon nur die letzten 14 (reicht für
|
||||
// einen Trend, bleibt aber lesbar statt endlos zu scrollen).
|
||||
const rawN = days.length;
|
||||
const bucket = rawN > 180 ? 14 : rawN > 21 ? 7 : 1;
|
||||
const allPeriods = bucket === 1 ? days.map(d => ({
|
||||
fixed_eur: d.fixed_eur, spot_eur: d.spot_eur, label: d.date.slice(5),
|
||||
})) : (() => {
|
||||
const out = [];
|
||||
for (let i = 0; i < days.length; i += bucket) {
|
||||
const chunk = days.slice(i, i + bucket);
|
||||
const fixed = chunk.reduce((s, d) => s + (d.fixed_eur || 0), 0);
|
||||
const spotVals = chunk.map(d => d.spot_eur).filter(v => v != null);
|
||||
const spot = spotVals.length ? spotVals.reduce((a, b) => a + b, 0) : null;
|
||||
out.push({ fixed_eur: fixed, spot_eur: spot, label: chunk[0].date.slice(5) });
|
||||
}
|
||||
return out;
|
||||
})();
|
||||
const MAX_PERIODS = 14;
|
||||
const periods = allPeriods.slice(-MAX_PERIODS);
|
||||
|
||||
// Echtes responsives SVG: viewBox in einer "Mobile-first"-Breite, skaliert per
|
||||
// width:100% frei auf den Container (Handy wie PC) — kein Scrollen mehr nötig,
|
||||
// da wir uns auf max. 14 Balken begrenzen.
|
||||
const n = periods.length;
|
||||
const W = 380, H = 220, PL = 40, PR = 8, PT = 14, PB = 46;
|
||||
const cW = W - PL - PR;
|
||||
const cH = H - PT - PB;
|
||||
const n = days.length;
|
||||
const barW = Math.max(2, Math.floor(cW / n) - 2);
|
||||
const gap = Math.max(1, Math.floor(cW / n) - barW);
|
||||
const slot = cW / n;
|
||||
const barW = Math.max(3, slot * 0.36);
|
||||
const barGap = Math.max(1.5, slot * 0.08);
|
||||
const rBar = Math.min(2.5, barW / 2);
|
||||
|
||||
const allVals = days.flatMap(d => [d.fixed_eur, d.spot_eur].filter(v => v != null));
|
||||
const allVals = periods.flatMap(d => [d.fixed_eur, d.spot_eur].filter(v => v != null));
|
||||
const maxVal = Math.max(...allVals, 0.01);
|
||||
|
||||
const yTicks = 4;
|
||||
@@ -812,36 +838,50 @@ async function loadFinance() {
|
||||
for (let i = 0; i <= yTicks; i++) {
|
||||
const v = maxVal * i / yTicks;
|
||||
const y = PT + cH - (cH * i / yTicks);
|
||||
gridLines += `<line x1="${PL}" y1="${y.toFixed(1)}" x2="${W-PR}" y2="${y.toFixed(1)}" stroke="${C.grid}" stroke-width="0.5"/>`;
|
||||
yLabels += `<text x="${PL-4}" y="${(y+4).toFixed(1)}" text-anchor="end" font-size="9" fill="#8b949e">${fEur(v)}</text>`;
|
||||
gridLines += `<line x1="${PL}" y1="${y.toFixed(1)}" x2="${W-PR}" y2="${y.toFixed(1)}" stroke="${C.grid}" stroke-width="1" stroke-dasharray="2,3"/>`;
|
||||
yLabels += `<text x="${PL-6}" y="${(y+3).toFixed(1)}" text-anchor="end" font-size="9" fill="#8b949e">${fEur(v)}</text>`;
|
||||
}
|
||||
|
||||
// Labels schräg statt horizontal — braucht bei 14 Balken pro Label deutlich
|
||||
// weniger Breite, dadurch bleiben trotz Platzmangel alle 14 lesbar.
|
||||
let bars = '', xLabels = '';
|
||||
days.forEach((d, i) => {
|
||||
const x0 = PL + i * (barW + gap);
|
||||
const hFixed = cH * d.fixed_eur / maxVal;
|
||||
bars += `<rect x="${x0}" y="${(PT + cH - hFixed).toFixed(1)}" width="${barW/2}" height="${hFixed.toFixed(1)}" fill="${C.fixed}" opacity="0.85"/>`;
|
||||
periods.forEach((d, i) => {
|
||||
const cx = PL + i * slot + slot / 2;
|
||||
const hFixed = Math.max(0, cH * d.fixed_eur / maxVal);
|
||||
bars += `<rect x="${(cx - barGap/2 - barW).toFixed(1)}" y="${(PT + cH - hFixed).toFixed(1)}" width="${barW.toFixed(1)}" height="${Math.max(hFixed,1).toFixed(1)}" rx="${rBar}" fill="url(#gradFixed)"/>`;
|
||||
if (d.spot_eur != null) {
|
||||
const hSpot = cH * d.spot_eur / maxVal;
|
||||
bars += `<rect x="${x0 + barW/2}" y="${(PT + cH - hSpot).toFixed(1)}" width="${barW/2}" height="${hSpot.toFixed(1)}" fill="${C.spot}" opacity="0.85"/>`;
|
||||
}
|
||||
if (i % Math.max(1, Math.floor(n / 8)) === 0) {
|
||||
const label = d.date.slice(5);
|
||||
xLabels += `<text x="${(x0 + barW/2).toFixed(1)}" y="${H - 4}" text-anchor="middle" font-size="9" fill="#8b949e">${label}</text>`;
|
||||
const hSpot = Math.max(0, cH * d.spot_eur / maxVal);
|
||||
bars += `<rect x="${(cx + barGap/2).toFixed(1)}" y="${(PT + cH - hSpot).toFixed(1)}" width="${barW.toFixed(1)}" height="${Math.max(hSpot,1).toFixed(1)}" rx="${rBar}" fill="url(#gradSpot)"/>`;
|
||||
}
|
||||
const ly = H - PB + 12;
|
||||
xLabels += `<text x="${cx.toFixed(1)}" y="${ly}" text-anchor="end" font-size="8.5" fill="#8b949e" transform="rotate(-40 ${cx.toFixed(1)} ${ly})">${d.label}</text>`;
|
||||
});
|
||||
|
||||
const legend = `<text x="${PL}" y="${H+18}" font-size="10" fill="${C.fixed}">■ Festpreis</text>
|
||||
<text x="${PL+70}" y="${H+18}" font-size="10" fill="${C.spot}">■ Spot (hypothetisch)</text>`;
|
||||
const defs = `<defs>
|
||||
<linearGradient id="gradFixed" x1="0" y1="0" x2="0" y2="1">
|
||||
<stop offset="0%" stop-color="${C.fixed}" stop-opacity="0.95"/>
|
||||
<stop offset="100%" stop-color="${C.fixed}" stop-opacity="0.55"/>
|
||||
</linearGradient>
|
||||
<linearGradient id="gradSpot" x1="0" y1="0" x2="0" y2="1">
|
||||
<stop offset="0%" stop-color="${C.spot}" stop-opacity="0.95"/>
|
||||
<stop offset="100%" stop-color="${C.spot}" stop-opacity="0.55"/>
|
||||
</linearGradient>
|
||||
</defs>`;
|
||||
|
||||
const bucketLabel = bucket === 1 ? 'Tag' : bucket === 7 ? 'Woche' : '2 Wochen';
|
||||
const rangeNote = allPeriods.length > MAX_PERIODS ? ` · letzte ${n}` : '';
|
||||
const legend = `<div style="display:flex;gap:18px;margin-top:10px;font-size:11px;color:var(--text-dim)">
|
||||
<span style="display:inline-flex;align-items:center;gap:6px"><span style="width:10px;height:10px;border-radius:3px;background:${C.fixed};display:inline-block"></span>Festpreis</span>
|
||||
<span style="display:inline-flex;align-items:center;gap:6px"><span style="width:10px;height:10px;border-radius:3px;background:${C.spot};display:inline-block"></span>Spot (hypothetisch)</span>
|
||||
</div>`;
|
||||
|
||||
const chartSection = `
|
||||
<div style="border-top:1px solid var(--border);padding-top:24px;margin-top:4px">
|
||||
<div style="font-size:11px;font-weight:600;text-transform:uppercase;letter-spacing:.08em;color:var(--text-dim);margin-bottom:14px">Kosten je Tag</div>
|
||||
<div style="overflow-x:auto">
|
||||
<svg viewBox="0 0 ${W} ${H+24}" style="width:100%;max-width:${W}px;display:block">
|
||||
${gridLines}${yLabels}${bars}${xLabels}${legend}
|
||||
<div style="font-size:11px;font-weight:600;text-transform:uppercase;letter-spacing:.08em;color:var(--text-dim);margin-bottom:14px">Kosten je ${bucketLabel}${rangeNote}</div>
|
||||
<svg viewBox="0 0 ${W} ${H}" style="width:100%;max-width:640px;height:auto;display:block">
|
||||
${defs}${gridLines}${yLabels}${bars}${xLabels}
|
||||
</svg>
|
||||
</div>
|
||||
${legend}
|
||||
</div>`;
|
||||
|
||||
el.innerHTML = `<div style="padding:4px 0">${empfehlung}${cards}${chartSection}</div>`;
|
||||
|
||||
@@ -1,278 +0,0 @@
|
||||
#!/usr/bin/env python3
|
||||
"""ShineDiag — Vor-Ort-Diagnose für Growatt-Wechselrichter via ShineLAN-X."""
|
||||
|
||||
import json
|
||||
import logging
|
||||
import os
|
||||
import sqlite3
|
||||
import sys
|
||||
import threading
|
||||
import time
|
||||
from typing import Dict, List, Optional
|
||||
|
||||
from flask import Flask, jsonify, request, send_from_directory
|
||||
|
||||
sys.path.insert(0, os.path.join(os.path.dirname(__file__), "../../haos-addon/src"))
|
||||
from inverters import INVERTERS
|
||||
from modbus_client import ModbusReader
|
||||
|
||||
logging.basicConfig(level=logging.INFO, format="%(asctime)s %(levelname)s: %(message)s")
|
||||
log = logging.getLogger(__name__)
|
||||
|
||||
WEB_DIR = os.path.join(os.path.dirname(__file__), "web")
|
||||
DB_PATH = os.environ.get("DB_PATH", "/var/lib/shinediag/history.db")
|
||||
POLL_SEC = int(os.environ.get("POLL_SEC", "30"))
|
||||
|
||||
app = Flask(__name__, static_folder=WEB_DIR)
|
||||
|
||||
# ── SQLite ────────────────────────────────────────────────────
|
||||
|
||||
_db_lock = threading.Lock()
|
||||
_db_conn: Optional[sqlite3.Connection] = None
|
||||
|
||||
|
||||
def _db() -> sqlite3.Connection:
|
||||
global _db_conn
|
||||
if _db_conn is None:
|
||||
os.makedirs(os.path.dirname(DB_PATH), exist_ok=True)
|
||||
_db_conn = sqlite3.connect(DB_PATH, check_same_thread=False)
|
||||
_db_conn.execute("PRAGMA journal_mode=WAL")
|
||||
_db_conn.execute("PRAGMA synchronous=NORMAL")
|
||||
_db_conn.execute("""
|
||||
CREATE TABLE IF NOT EXISTS measurements (
|
||||
inv_key TEXT NOT NULL,
|
||||
sensor_id TEXT NOT NULL,
|
||||
ts REAL NOT NULL,
|
||||
value REAL NOT NULL
|
||||
)""")
|
||||
_db_conn.execute("""
|
||||
CREATE INDEX IF NOT EXISTS idx_inv_sensor_ts
|
||||
ON measurements(inv_key, sensor_id, ts)""")
|
||||
_db_conn.commit()
|
||||
return _db_conn
|
||||
|
||||
|
||||
def db_write(inv_key: str, ts: float, values: Dict[str, float]):
|
||||
rows = [(inv_key, sid, ts, v) for sid, v in values.items()]
|
||||
with _db_lock:
|
||||
_db().executemany(
|
||||
"INSERT INTO measurements(inv_key,sensor_id,ts,value) VALUES(?,?,?,?)", rows)
|
||||
_db().commit()
|
||||
|
||||
|
||||
def db_query(inv_key: str, sensor_ids: List[str],
|
||||
from_ts: float, to_ts: float) -> Dict[str, List]:
|
||||
result = {sid: [] for sid in sensor_ids}
|
||||
placeholders = ",".join("?" * len(sensor_ids))
|
||||
with _db_lock:
|
||||
rows = _db().execute(f"""
|
||||
SELECT sensor_id, ts, value FROM measurements
|
||||
WHERE inv_key=? AND sensor_id IN ({placeholders})
|
||||
AND ts BETWEEN ? AND ?
|
||||
ORDER BY ts ASC
|
||||
""", [inv_key] + sensor_ids + [from_ts, to_ts]).fetchall()
|
||||
for sid, ts, val in rows:
|
||||
if sid in result:
|
||||
result[sid].append({"ts": ts, "v": val})
|
||||
return result
|
||||
|
||||
|
||||
def db_cleanup(days: int = 7):
|
||||
cutoff = time.time() - days * 86400
|
||||
with _db_lock:
|
||||
n = _db().execute("DELETE FROM measurements WHERE ts<?", (cutoff,)).rowcount
|
||||
_db().commit()
|
||||
if n:
|
||||
log.info("DB: %d alte Einträge gelöscht", n)
|
||||
|
||||
|
||||
# ── Poll-State ────────────────────────────────────────────────
|
||||
|
||||
_state_lock = threading.Lock()
|
||||
_poll_thread: Optional[threading.Thread] = None
|
||||
_poll_stop = threading.Event()
|
||||
_conn_cfg: Dict = {} # aktuelle Verbindungs-Config
|
||||
_last_values: Dict = {} # letzter erfolgreicher Poll
|
||||
_last_ts: float = 0.0
|
||||
_last_ok: bool = False
|
||||
_inv_key: str = ""
|
||||
|
||||
|
||||
def _poll_worker():
|
||||
global _last_values, _last_ts, _last_ok
|
||||
cfg = _conn_cfg.copy()
|
||||
inverter = INVERTERS[cfg["model"]]
|
||||
reader = ModbusReader(cfg["ip"], int(cfg["port"]), int(cfg["slave"]), timeout=5.0)
|
||||
log.info("Poll-Thread gestartet: %s @ %s alle %ds", inverter.name, cfg["ip"], POLL_SEC)
|
||||
|
||||
while not _poll_stop.is_set():
|
||||
t0 = time.time()
|
||||
values = reader.read(inverter)
|
||||
with _state_lock:
|
||||
if values:
|
||||
_last_values = values
|
||||
_last_ts = t0
|
||||
_last_ok = True
|
||||
db_write(_inv_key, t0, values)
|
||||
else:
|
||||
_last_ok = False
|
||||
_poll_stop.wait(max(0.0, POLL_SEC - (time.time() - t0)))
|
||||
|
||||
reader.close()
|
||||
log.info("Poll-Thread beendet")
|
||||
|
||||
|
||||
def _start_poll(cfg: dict):
|
||||
global _poll_thread, _conn_cfg, _inv_key, _last_values, _last_ts, _last_ok
|
||||
_stop_poll()
|
||||
with _state_lock:
|
||||
_conn_cfg = cfg
|
||||
_inv_key = f"{cfg['ip']}:{cfg['port']}:{cfg['slave']}:{cfg['model']}"
|
||||
_last_values = {}
|
||||
_last_ts = 0.0
|
||||
_last_ok = False
|
||||
_poll_stop.clear()
|
||||
_poll_thread = threading.Thread(target=_poll_worker, daemon=True, name="poll")
|
||||
_poll_thread.start()
|
||||
|
||||
|
||||
def _stop_poll():
|
||||
global _poll_thread
|
||||
_poll_stop.set()
|
||||
if _poll_thread and _poll_thread.is_alive():
|
||||
_poll_thread.join(timeout=10)
|
||||
_poll_thread = None
|
||||
|
||||
|
||||
# ── Flask ─────────────────────────────────────────────────────
|
||||
|
||||
@app.get("/")
|
||||
def index():
|
||||
return send_from_directory(WEB_DIR, "index.html")
|
||||
|
||||
|
||||
@app.get("/api/models")
|
||||
def api_models():
|
||||
return jsonify({
|
||||
k: {"id": v.id, "name": v.name, "manufacturer": v.manufacturer,
|
||||
"sensor_count": len(v.sensors)}
|
||||
for k, v in INVERTERS.items()
|
||||
})
|
||||
|
||||
|
||||
@app.post("/api/connect")
|
||||
def api_connect():
|
||||
body = request.get_json(force=True) or {}
|
||||
for field in ("ip", "model"):
|
||||
if not body.get(field):
|
||||
return jsonify({"error": f"'{field}' fehlt"}), 400
|
||||
if body["model"] not in INVERTERS:
|
||||
return jsonify({"error": "Unbekanntes Modell"}), 400
|
||||
cfg = {
|
||||
"ip": body["ip"],
|
||||
"port": int(body.get("port", 502)),
|
||||
"slave": int(body.get("slave", 1)),
|
||||
"model": body["model"],
|
||||
}
|
||||
_start_poll(cfg)
|
||||
db_cleanup()
|
||||
return jsonify({"ok": True})
|
||||
|
||||
|
||||
@app.post("/api/disconnect")
|
||||
def api_disconnect():
|
||||
_stop_poll()
|
||||
return jsonify({"ok": True})
|
||||
|
||||
|
||||
@app.get("/api/status")
|
||||
def api_status():
|
||||
with _state_lock:
|
||||
connected = _poll_thread is not None and _poll_thread.is_alive()
|
||||
cfg = _conn_cfg.copy()
|
||||
values = _last_values.copy()
|
||||
ts = _last_ts
|
||||
ok = _last_ok
|
||||
|
||||
inverter = INVERTERS.get(cfg.get("model", ""), None)
|
||||
sensors = []
|
||||
if inverter:
|
||||
sensors = [{"id": s.id, "name": s.name, "unit": s.unit,
|
||||
"device_class": s.device_class, "icon": s.icon}
|
||||
for s in inverter.sensors]
|
||||
|
||||
return jsonify({
|
||||
"connected": connected,
|
||||
"modbus_ok": ok,
|
||||
"last_ts": ts,
|
||||
"cfg": cfg,
|
||||
"values": values,
|
||||
"sensors": sensors,
|
||||
"inv_name": inverter.name if inverter else "",
|
||||
})
|
||||
|
||||
|
||||
@app.get("/api/chart-data")
|
||||
def api_chart_data():
|
||||
sensors = request.args.get("sensors", "")
|
||||
hours = min(float(request.args.get("hours", "24")), 24 * 7)
|
||||
if not sensors:
|
||||
return jsonify({"error": "sensors required"}), 400
|
||||
sensor_ids = [s.strip() for s in sensors.split(",") if s.strip()]
|
||||
to_ts = time.time()
|
||||
from_ts = to_ts - hours * 3600
|
||||
with _state_lock:
|
||||
key = _inv_key
|
||||
if not key:
|
||||
return jsonify({sid: [] for sid in sensor_ids})
|
||||
return jsonify(db_query(key, sensor_ids, from_ts, to_ts))
|
||||
|
||||
|
||||
@app.post("/api/scan")
|
||||
def api_scan():
|
||||
"""Einmal-Scan ohne Polling — für schnelle Diagnose."""
|
||||
body = request.get_json(force=True) or {}
|
||||
model_id = body.get("model", "MIC_1500_TL_X")
|
||||
inverter = INVERTERS.get(model_id)
|
||||
if not inverter:
|
||||
return jsonify({"error": f"Unbekanntes Modell: {model_id}"}), 400
|
||||
reader = ModbusReader(body.get("ip", "10.0.0.100"),
|
||||
int(body.get("port", 502)),
|
||||
int(body.get("slave", 1)), timeout=5.0)
|
||||
values = reader.read(inverter)
|
||||
reader.close()
|
||||
if values is None:
|
||||
return jsonify({"ok": False, "error": "Keine Verbindung"}), 502
|
||||
return jsonify({"ok": True, "model": inverter.name,
|
||||
"sensors": [{"id": s.id, "name": s.name, "value": values.get(s.id),
|
||||
"unit": s.unit} for s in inverter.sensors]})
|
||||
|
||||
|
||||
@app.post("/api/raw")
|
||||
def api_raw():
|
||||
body = request.get_json(force=True) or {}
|
||||
start = max(0, int(body.get("start", 0)))
|
||||
count = min(125, max(1, int(body.get("count", 100))))
|
||||
from pymodbus.client import ModbusTcpClient
|
||||
client = ModbusTcpClient(body.get("ip", "10.0.0.100"),
|
||||
port=int(body.get("port", 502)), timeout=5.0)
|
||||
if not client.connect():
|
||||
return jsonify({"ok": False, "error": "Verbindung fehlgeschlagen"}), 502
|
||||
try:
|
||||
r = client.read_input_registers(start, count, slave=int(body.get("slave", 1)))
|
||||
if r.isError():
|
||||
return jsonify({"ok": False, "error": str(r)}), 502
|
||||
return jsonify({"ok": True, "registers": [
|
||||
{"addr": start + i, "hex": f"0x{v:04X}",
|
||||
"dec": v, "signed": v if v < 0x8000 else v - 0x10000}
|
||||
for i, v in enumerate(r.registers)
|
||||
]})
|
||||
finally:
|
||||
client.close()
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
_db()
|
||||
port = int(os.environ.get("PORT", "80"))
|
||||
log.info("ShineDiag auf Port %d", port)
|
||||
app.run(host="0.0.0.0", port=port, threaded=True)
|
||||
@@ -1,8 +0,0 @@
|
||||
# /etc/dhcpcd.conf — eth0 feste IP für ShineDiag
|
||||
# Diese Datei ans Ende von /etc/dhcpcd.conf anhängen
|
||||
|
||||
interface eth0
|
||||
static ip_address=10.0.0.1/24
|
||||
static routers=
|
||||
static domain_name_servers=
|
||||
noipv6
|
||||
@@ -1,9 +0,0 @@
|
||||
# /etc/dnsmasq.conf — DHCP für ShineLAN-X auf eth0
|
||||
|
||||
interface=eth0
|
||||
dhcp-range=10.0.0.100,10.0.0.200,12h
|
||||
|
||||
# ShineLAN-X bekommt immer 10.0.0.100 (MAC anpassen!)
|
||||
# dhcp-host=AA:BB:CC:DD:EE:FF,shinelanx,10.0.0.100
|
||||
|
||||
bind-interfaces
|
||||
@@ -1,16 +0,0 @@
|
||||
# /etc/hostapd/hostapd.conf — WiFi-Hotspot "ShineDiag"
|
||||
|
||||
interface=wlan0
|
||||
driver=nl80211
|
||||
ssid=ShineDiag
|
||||
hw_mode=g
|
||||
channel=6
|
||||
ieee80211n=1
|
||||
wmm_enabled=1
|
||||
|
||||
auth_algs=1
|
||||
wpa=2
|
||||
wpa_passphrase=shinelanx
|
||||
wpa_key_mgmt=WPA-PSK
|
||||
wpa_pairwise=CCMP
|
||||
rsn_pairwise=CCMP
|
||||
@@ -1,69 +0,0 @@
|
||||
#!/bin/bash
|
||||
# ShineDiag Pi 3B Setup — einmalig als root ausführen
|
||||
set -e
|
||||
|
||||
echo "=== ShineDiag Installation ==="
|
||||
|
||||
# Pakete
|
||||
apt-get update -q
|
||||
apt-get install -y python3-pip hostapd dnsmasq
|
||||
|
||||
# Python-Abhängigkeiten
|
||||
pip3 install flask pymodbus --break-system-packages
|
||||
|
||||
# Netzwerk: eth0 statische IP
|
||||
cat setup/dhcpcd.conf >> /etc/dhcpcd.conf
|
||||
|
||||
# DHCP-Server für eth0
|
||||
cp setup/dnsmasq.conf /etc/dnsmasq.conf
|
||||
systemctl enable dnsmasq
|
||||
|
||||
# WiFi-Hotspot
|
||||
cp setup/hostapd.conf /etc/hostapd/hostapd.conf
|
||||
echo 'DAEMON_CONF="/etc/hostapd/hostapd.conf"' >> /etc/default/hostapd
|
||||
systemctl unmask hostapd
|
||||
systemctl enable hostapd
|
||||
|
||||
# Wlan0 IP
|
||||
cat >> /etc/dhcpcd.conf << 'EOF'
|
||||
|
||||
interface wlan0
|
||||
static ip_address=10.0.1.1/24
|
||||
nohook wpa_supplicant
|
||||
EOF
|
||||
|
||||
# DHCP auch für wlan0 (MacBook-Verbindung)
|
||||
cat >> /etc/dnsmasq.conf << 'EOF'
|
||||
|
||||
interface=wlan0
|
||||
dhcp-range=10.0.1.100,10.0.1.200,1h
|
||||
EOF
|
||||
|
||||
# systemd-Service für ShineDiag
|
||||
INSTALL_DIR="$(pwd)"
|
||||
cat > /etc/systemd/system/shinediag.service << EOF
|
||||
[Unit]
|
||||
Description=ShineDiag Diagnose-Tool
|
||||
After=network.target
|
||||
|
||||
[Service]
|
||||
ExecStart=/usr/bin/python3 ${INSTALL_DIR}/diagnose.py
|
||||
WorkingDirectory=${INSTALL_DIR}
|
||||
Environment=PORT=80
|
||||
Environment=SHINELANX_IP=10.0.0.100
|
||||
Restart=always
|
||||
User=root
|
||||
|
||||
[Install]
|
||||
WantedBy=multi-user.target
|
||||
EOF
|
||||
|
||||
systemctl daemon-reload
|
||||
systemctl enable shinediag
|
||||
|
||||
echo ""
|
||||
echo "=== Fertig! Bitte neu starten: sudo reboot ==="
|
||||
echo ""
|
||||
echo "Nach dem Neustart:"
|
||||
echo " WiFi: 'ShineDiag' | Passwort: shinelanx"
|
||||
echo " Browser: http://10.0.1.1"
|
||||
@@ -1,601 +0,0 @@
|
||||
<!DOCTYPE html>
|
||||
<html lang="de">
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1, maximum-scale=1">
|
||||
<title>ShineDiag</title>
|
||||
<style>
|
||||
*{box-sizing:border-box;margin:0;padding:0}
|
||||
body{font-family:system-ui,sans-serif;background:#0f172a;color:#e2e8f0;min-height:100vh}
|
||||
|
||||
/* Header */
|
||||
header{background:#1e293b;padding:12px 16px;border-bottom:1px solid #334155;
|
||||
display:flex;align-items:center;justify-content:space-between;gap:8px;position:sticky;top:0;z-index:10}
|
||||
.header-left{display:flex;align-items:center;gap:10px}
|
||||
header h1{font-size:1.1rem;font-weight:700;color:#f8fafc}
|
||||
header p{font-size:.7rem;color:#94a3b8}
|
||||
.status-dot{width:10px;height:10px;border-radius:50%;background:#334155;flex-shrink:0}
|
||||
.status-dot.ok{background:#22c55e;box-shadow:0 0 6px #22c55e88}
|
||||
.status-dot.err{background:#ef4444}
|
||||
|
||||
/* Tabs */
|
||||
.tabs{display:flex;background:#1e293b;border-bottom:1px solid #334155;overflow-x:auto;scrollbar-width:none}
|
||||
.tabs::-webkit-scrollbar{display:none}
|
||||
.tab{flex:1;min-width:80px;padding:10px 4px;border:none;background:none;color:#64748b;
|
||||
font-size:.8rem;cursor:pointer;border-bottom:2px solid transparent;white-space:nowrap}
|
||||
.tab.active{color:#60a5fa;border-bottom-color:#3b82f6}
|
||||
|
||||
/* Main */
|
||||
main{padding:12px;max-width:640px;margin:0 auto}
|
||||
.card{background:#1e293b;border:1px solid #334155;border-radius:12px;padding:14px;margin-bottom:12px}
|
||||
.card-title{font-size:.75rem;font-weight:600;color:#64748b;text-transform:uppercase;
|
||||
letter-spacing:.05em;margin-bottom:10px}
|
||||
|
||||
/* Connect form */
|
||||
label{display:block;font-size:.78rem;color:#94a3b8;margin-bottom:3px;margin-top:8px}
|
||||
input,select{width:100%;padding:9px 11px;border-radius:8px;border:1px solid #334155;
|
||||
background:#0f172a;color:#f1f5f9;font-size:.9rem}
|
||||
input:focus,select:focus{outline:2px solid #3b82f6;border-color:transparent}
|
||||
.row2{display:grid;grid-template-columns:1fr 1fr;gap:8px}
|
||||
|
||||
button{width:100%;padding:11px;border-radius:8px;border:none;cursor:pointer;
|
||||
font-size:.9rem;font-weight:600;margin-top:10px;transition:opacity .15s}
|
||||
button:disabled{opacity:.4;cursor:not-allowed}
|
||||
.btn-primary{background:#3b82f6;color:#fff}.btn-primary:hover:not(:disabled){background:#2563eb}
|
||||
.btn-danger{background:#dc2626;color:#fff}.btn-danger:hover:not(:disabled){background:#b91c1c}
|
||||
.btn-secondary{background:#475569;color:#e2e8f0}.btn-secondary:hover:not(:disabled){background:#334155}
|
||||
.btn-success{background:#059669;color:#fff}.btn-success:hover:not(:disabled){background:#047857}
|
||||
|
||||
/* Metric cards grid */
|
||||
.metrics{display:grid;grid-template-columns:repeat(auto-fill,minmax(140px,1fr));gap:8px;margin-bottom:4px}
|
||||
.metric{background:#0f172a;border:1px solid #334155;border-radius:10px;padding:12px 10px}
|
||||
.metric-label{font-size:.7rem;color:#64748b;text-transform:uppercase;letter-spacing:.04em;margin-bottom:4px}
|
||||
.metric-value{font-size:1.5rem;font-weight:700;color:#f1f5f9;line-height:1}
|
||||
.metric-unit{font-size:.75rem;color:#94a3b8;margin-top:2px}
|
||||
.metric.highlight .metric-value{color:#60a5fa}
|
||||
.metric.warn .metric-value{color:#f59e0b}
|
||||
.metric.ok .metric-value{color:#4ade80}
|
||||
|
||||
/* Chart */
|
||||
.chart-wrap{position:relative;margin-top:8px}
|
||||
canvas.chart{width:100%;border-radius:8px;display:block}
|
||||
.chart-legend{display:flex;flex-wrap:wrap;gap:10px;margin-top:6px;padding:0 4px}
|
||||
.legend-item{display:flex;align-items:center;gap:5px;font-size:.75rem;color:#94a3b8}
|
||||
.legend-dot{width:10px;height:10px;border-radius:50%}
|
||||
|
||||
/* Range selector */
|
||||
.range-btns{display:flex;gap:4px;margin-bottom:10px}
|
||||
.range-btn{flex:1;padding:7px 4px;border-radius:6px;border:1px solid #334155;
|
||||
background:transparent;color:#94a3b8;font-size:.75rem;cursor:pointer}
|
||||
.range-btn.active{background:#3b82f6;color:#fff;border-color:#3b82f6}
|
||||
|
||||
/* Sensor table */
|
||||
.sensor-table{width:100%;border-collapse:collapse}
|
||||
.sensor-table th{text-align:left;font-size:.72rem;color:#64748b;padding:5px 6px;border-bottom:1px solid #334155}
|
||||
.sensor-table td{padding:7px 6px;font-size:.83rem;border-bottom:1px solid #1e293b}
|
||||
.sensor-table tr:last-child td{border-bottom:none}
|
||||
.sensor-table .v{font-weight:600;color:#f1f5f9;text-align:right}
|
||||
.sensor-table .u{color:#64748b;font-size:.72rem;text-align:right}
|
||||
|
||||
/* Raw table */
|
||||
.raw-table{width:100%;border-collapse:collapse;font-family:monospace;font-size:.78rem}
|
||||
.raw-table th{text-align:left;color:#64748b;padding:4px 6px;border-bottom:1px solid #334155}
|
||||
.raw-table td{padding:4px 6px;border-bottom:1px solid #1e293b;color:#cbd5e1}
|
||||
.raw-table td:first-child{color:#60a5fa}
|
||||
.raw-controls{display:grid;grid-template-columns:1fr 1fr;gap:8px;margin-bottom:8px}
|
||||
|
||||
/* Status bar */
|
||||
.update-bar{font-size:.72rem;color:#475569;text-align:right;margin-bottom:4px}
|
||||
|
||||
/* Alert */
|
||||
.alert{padding:10px 12px;border-radius:8px;font-size:.83rem;margin-bottom:10px}
|
||||
.alert-info{background:#0c1a2e;color:#60a5fa;border:1px solid #1e3a5f}
|
||||
.alert-ok{background:#052e16;color:#4ade80;border:1px solid #166534}
|
||||
.alert-err{background:#1c0a09;color:#f87171;border:1px solid #7f1d1d}
|
||||
|
||||
.spinner{display:inline-block;width:14px;height:14px;border:2px solid #fff4;
|
||||
border-top-color:#fff;border-radius:50%;animation:spin .7s linear infinite}
|
||||
@keyframes spin{to{transform:rotate(360deg)}}
|
||||
|
||||
.hidden{display:none!important}
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
|
||||
<header>
|
||||
<div class="header-left">
|
||||
<svg width="28" height="28" viewBox="0 0 24 24" fill="none" stroke="#f59e0b" stroke-width="2">
|
||||
<circle cx="12" cy="12" r="5"/>
|
||||
<line x1="12" y1="1" x2="12" y2="3"/><line x1="12" y1="21" x2="12" y2="23"/>
|
||||
<line x1="4.22" y1="4.22" x2="5.64" y2="5.64"/><line x1="18.36" y1="18.36" x2="19.78" y2="19.78"/>
|
||||
<line x1="1" y1="12" x2="3" y2="12"/><line x1="21" y1="12" x2="23" y2="12"/>
|
||||
<line x1="4.22" y1="19.78" x2="5.64" y2="18.36"/><line x1="18.36" y1="5.64" x2="19.78" y2="4.22"/>
|
||||
</svg>
|
||||
<div><h1>ShineDiag</h1><p id="hdrSub">Nicht verbunden</p></div>
|
||||
</div>
|
||||
<div id="statusDot" class="status-dot"></div>
|
||||
</header>
|
||||
|
||||
<div class="tabs">
|
||||
<button class="tab active" onclick="showTab('dashboard')">Dashboard</button>
|
||||
<button class="tab" onclick="showTab('charts')">Diagramme</button>
|
||||
<button class="tab" onclick="showTab('sensors')">Sensoren</button>
|
||||
<button class="tab" onclick="showTab('raw')">Rohdaten</button>
|
||||
</div>
|
||||
|
||||
<main>
|
||||
|
||||
<!-- ── Dashboard ── -->
|
||||
<div id="tabDashboard">
|
||||
<!-- Connect card (shown when disconnected) -->
|
||||
<div class="card" id="connectCard">
|
||||
<div class="card-title">Verbindung</div>
|
||||
<label>ShineLAN-X IP</label>
|
||||
<input id="cfgIp" value="10.0.0.100">
|
||||
<div class="row2">
|
||||
<div><label>Port</label><input id="cfgPort" type="number" value="502"></div>
|
||||
<div><label>Modbus-Adresse</label><input id="cfgSlave" type="number" value="1"></div>
|
||||
</div>
|
||||
<label>Modell</label>
|
||||
<select id="cfgModel"></select>
|
||||
<button class="btn-primary" id="btnConnect" onclick="connect()">Verbinden & Aufzeichnen</button>
|
||||
</div>
|
||||
|
||||
<!-- Live data (shown when connected) -->
|
||||
<div id="liveCard" class="hidden">
|
||||
<div class="update-bar" id="updateBar">—</div>
|
||||
|
||||
<div class="card">
|
||||
<div class="card-title">Spannung</div>
|
||||
<div class="metrics" id="voltageMetrics"></div>
|
||||
</div>
|
||||
|
||||
<div class="card">
|
||||
<div class="card-title">Frequenz & Leistung</div>
|
||||
<div class="metrics" id="powerMetrics"></div>
|
||||
</div>
|
||||
|
||||
<div class="card" id="extraCard" style="display:none">
|
||||
<div class="card-title">Weitere</div>
|
||||
<div class="metrics" id="extraMetrics"></div>
|
||||
</div>
|
||||
|
||||
<button class="btn-danger" onclick="disconnect()">Verbindung trennen</button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- ── Charts ── -->
|
||||
<div id="tabCharts" class="hidden">
|
||||
<div class="card" id="noDataCard">
|
||||
<div class="alert alert-info">Zuerst im Dashboard verbinden und einige Messpunkte sammeln.</div>
|
||||
</div>
|
||||
|
||||
<div id="chartSection" class="hidden">
|
||||
<div class="range-btns">
|
||||
<button class="range-btn active" onclick="setRange(1,this)">1 Std</button>
|
||||
<button class="range-btn" onclick="setRange(6,this)">6 Std</button>
|
||||
<button class="range-btn" onclick="setRange(24,this)">24 Std</button>
|
||||
<button class="range-btn" onclick="setRange(168,this)">7 Tage</button>
|
||||
</div>
|
||||
|
||||
<div class="card">
|
||||
<div class="card-title">Spannung (V)</div>
|
||||
<div class="chart-wrap"><canvas id="chartVoltage" class="chart" height="160"></canvas></div>
|
||||
<div class="chart-legend" id="legendVoltage"></div>
|
||||
</div>
|
||||
|
||||
<div class="card">
|
||||
<div class="card-title">Frequenz (Hz)</div>
|
||||
<div class="chart-wrap"><canvas id="chartFreq" class="chart" height="120"></canvas></div>
|
||||
</div>
|
||||
|
||||
<div class="card">
|
||||
<div class="card-title">Leistung (W)</div>
|
||||
<div class="chart-wrap"><canvas id="chartPower" class="chart" height="140"></canvas></div>
|
||||
<div class="chart-legend" id="legendPower"></div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- ── Sensors ── -->
|
||||
<div id="tabSensors" class="hidden">
|
||||
<div class="card">
|
||||
<table class="sensor-table">
|
||||
<thead><tr><th>Sensor</th><th style="text-align:right">Wert</th><th style="text-align:right">Einheit</th></tr></thead>
|
||||
<tbody id="sensorBody"><tr><td colspan="3" style="color:#475569;padding:12px">Noch nicht verbunden.</td></tr></tbody>
|
||||
</table>
|
||||
<button class="btn-success" id="btnExport" onclick="exportJson()" style="margin-top:10px">Export JSON</button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- ── Raw ── -->
|
||||
<div id="tabRaw" class="hidden">
|
||||
<div class="card">
|
||||
<div class="raw-controls">
|
||||
<div><label>Start</label><input id="rawStart" type="number" value="0"></div>
|
||||
<div><label>Anzahl (max 125)</label><input id="rawCount" type="number" value="100"></div>
|
||||
</div>
|
||||
<button class="btn-secondary" onclick="rawScan()">Register lesen</button>
|
||||
<div id="rawAlert" style="margin-top:8px"></div>
|
||||
<div style="overflow-x:auto;margin-top:8px">
|
||||
<table class="raw-table">
|
||||
<thead><tr><th>Addr</th><th>Hex</th><th>Dez</th><th>Signed</th></tr></thead>
|
||||
<tbody id="rawBody"></tbody>
|
||||
</table>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
</main>
|
||||
|
||||
<script>
|
||||
// ── State ────────────────────────────────────────────────────
|
||||
|
||||
let currentStatus = null;
|
||||
let chartHours = 1;
|
||||
let refreshTimer = null;
|
||||
|
||||
const COLORS = ['#60a5fa','#fbbf24','#4ade80','#f87171','#a78bfa'];
|
||||
|
||||
// Sensor-IDs je Messtyp
|
||||
const VOLTAGE_IDS = ['voltage_l1','voltage_l2','voltage_l3','ac_voltage','bat_voltage'];
|
||||
const FREQ_IDS = ['frequency'];
|
||||
const POWER_IDS = ['total_power','ac_power','ac_power_total','pv_power','bat_charge_power','bat_discharge_power'];
|
||||
const EXTRA_IDS = ['temperature','bat_soc','bat_current'];
|
||||
|
||||
const LABELS = {
|
||||
voltage_l1:'Spannung L1', voltage_l2:'Spannung L2', voltage_l3:'Spannung L3',
|
||||
ac_voltage:'Spannung AC', bat_voltage:'Batterie',
|
||||
frequency:'Frequenz',
|
||||
total_power:'Netzleistung', ac_power:'AC Leistung', ac_power_total:'AC Leistung',
|
||||
pv_power:'PV Leistung', bat_charge_power:'Bat. Laden', bat_discharge_power:'Bat. Entladen',
|
||||
temperature:'Temperatur', bat_soc:'Ladezustand', bat_current:'Bat. Strom',
|
||||
};
|
||||
|
||||
// ── Tabs ─────────────────────────────────────────────────────
|
||||
|
||||
function showTab(name) {
|
||||
['dashboard','charts','sensors','raw'].forEach(t => {
|
||||
document.getElementById('tab'+cap(t)).classList.toggle('hidden', t !== name);
|
||||
});
|
||||
document.querySelectorAll('.tab').forEach((btn, i) => {
|
||||
btn.classList.toggle('active', ['dashboard','charts','sensors','raw'][i] === name);
|
||||
});
|
||||
if (name === 'charts') loadCharts();
|
||||
}
|
||||
const cap = s => s[0].toUpperCase() + s.slice(1);
|
||||
|
||||
// ── Models ───────────────────────────────────────────────────
|
||||
|
||||
async function loadModels() {
|
||||
const res = await fetch('/api/models');
|
||||
const data = await res.json();
|
||||
const sel = document.getElementById('cfgModel');
|
||||
sel.innerHTML = '';
|
||||
for (const [id, m] of Object.entries(data)) {
|
||||
const o = document.createElement('option');
|
||||
o.value = id;
|
||||
o.textContent = `${m.name}`;
|
||||
sel.appendChild(o);
|
||||
}
|
||||
// restore from localStorage
|
||||
const saved = localStorage.getItem('shinediag_model');
|
||||
if (saved) sel.value = saved;
|
||||
}
|
||||
|
||||
// ── Connect ──────────────────────────────────────────────────
|
||||
|
||||
async function connect() {
|
||||
const btn = document.getElementById('btnConnect');
|
||||
btn.disabled = true;
|
||||
btn.innerHTML = '<span class="spinner"></span>';
|
||||
const cfg = {
|
||||
ip: document.getElementById('cfgIp').value,
|
||||
port: parseInt(document.getElementById('cfgPort').value),
|
||||
slave: parseInt(document.getElementById('cfgSlave').value),
|
||||
model: document.getElementById('cfgModel').value,
|
||||
};
|
||||
localStorage.setItem('shinediag_model', cfg.model);
|
||||
const res = await fetch('/api/connect', {method:'POST',
|
||||
headers:{'Content-Type':'application/json'}, body: JSON.stringify(cfg)});
|
||||
const d = await res.json();
|
||||
btn.disabled = false;
|
||||
btn.textContent = 'Verbinden & Aufzeichnen';
|
||||
if (d.ok) startRefresh();
|
||||
}
|
||||
|
||||
async function disconnect() {
|
||||
await fetch('/api/disconnect', {method:'POST'});
|
||||
stopRefresh();
|
||||
showDisconnected();
|
||||
}
|
||||
|
||||
// ── Refresh ──────────────────────────────────────────────────
|
||||
|
||||
function startRefresh() {
|
||||
stopRefresh();
|
||||
pollStatus();
|
||||
refreshTimer = setInterval(pollStatus, 30000);
|
||||
}
|
||||
|
||||
function stopRefresh() {
|
||||
if (refreshTimer) { clearInterval(refreshTimer); refreshTimer = null; }
|
||||
}
|
||||
|
||||
async function pollStatus() {
|
||||
try {
|
||||
const res = await fetch('/api/status');
|
||||
currentStatus = await res.json();
|
||||
renderDashboard(currentStatus);
|
||||
} catch(e) {
|
||||
console.error(e);
|
||||
}
|
||||
}
|
||||
|
||||
// ── Dashboard ────────────────────────────────────────────────
|
||||
|
||||
function renderDashboard(s) {
|
||||
const dot = document.getElementById('statusDot');
|
||||
const hdr = document.getElementById('hdrSub');
|
||||
|
||||
if (!s.connected) { showDisconnected(); return; }
|
||||
|
||||
document.getElementById('connectCard').classList.add('hidden');
|
||||
document.getElementById('liveCard').classList.remove('hidden');
|
||||
|
||||
dot.className = 'status-dot ' + (s.modbus_ok ? 'ok' : 'err');
|
||||
hdr.textContent = s.inv_name || s.cfg.model;
|
||||
|
||||
if (s.last_ts) {
|
||||
const ago = Math.round(Date.now()/1000 - s.last_ts);
|
||||
document.getElementById('updateBar').textContent =
|
||||
`Letztes Update: ${ago < 60 ? ago+'s' : Math.round(ago/60)+'min'} · alle 30s`;
|
||||
}
|
||||
|
||||
const v = s.values;
|
||||
|
||||
// Spannung
|
||||
const voltIds = VOLTAGE_IDS.filter(id => v[id] !== undefined);
|
||||
renderMetrics('voltageMetrics', voltIds.map(id => ({
|
||||
label: LABELS[id], value: v[id]?.toFixed(1), unit: 'V', cls: 'highlight'
|
||||
})));
|
||||
|
||||
// Frequenz + Leistung
|
||||
const pm = [];
|
||||
if (v.frequency !== undefined)
|
||||
pm.push({label:'Frequenz', value: v.frequency?.toFixed(2), unit:'Hz', cls:'ok'});
|
||||
const pwrId = POWER_IDS.find(id => v[id] !== undefined);
|
||||
if (pwrId) {
|
||||
const pval = v[pwrId];
|
||||
pm.push({label: LABELS[pwrId], value: pval?.toFixed(0), unit:'W',
|
||||
cls: pval < 0 ? 'ok' : 'warn'});
|
||||
}
|
||||
renderMetrics('powerMetrics', pm);
|
||||
|
||||
// Extras (Temperatur, SoC, …)
|
||||
const extras = EXTRA_IDS.filter(id => v[id] !== undefined)
|
||||
.map(id => ({label: LABELS[id], value: v[id]?.toFixed(1),
|
||||
unit: id==='bat_soc'?'%':id==='temperature'?'°C':'A'}));
|
||||
document.getElementById('extraCard').style.display = extras.length ? '' : 'none';
|
||||
if (extras.length) renderMetrics('extraMetrics', extras);
|
||||
|
||||
// Sensor-Tabelle aktualisieren
|
||||
if (s.sensors.length) {
|
||||
const tbody = document.getElementById('sensorBody');
|
||||
tbody.innerHTML = s.sensors.map(sen => {
|
||||
const val = v[sen.id] !== undefined ? v[sen.id] : '—';
|
||||
return `<tr><td>${esc(sen.name)}</td>
|
||||
<td class="v">${val}</td>
|
||||
<td class="u">${esc(sen.unit||'')}</td></tr>`;
|
||||
}).join('');
|
||||
}
|
||||
}
|
||||
|
||||
function renderMetrics(containerId, items) {
|
||||
document.getElementById(containerId).innerHTML = items.map(m =>
|
||||
`<div class="metric ${m.cls||''}">
|
||||
<div class="metric-label">${esc(m.label)}</div>
|
||||
<div class="metric-value">${esc(m.value??'—')}</div>
|
||||
<div class="metric-unit">${esc(m.unit||'')}</div>
|
||||
</div>`
|
||||
).join('');
|
||||
}
|
||||
|
||||
function showDisconnected() {
|
||||
document.getElementById('connectCard').classList.remove('hidden');
|
||||
document.getElementById('liveCard').classList.add('hidden');
|
||||
document.getElementById('statusDot').className = 'status-dot';
|
||||
document.getElementById('hdrSub').textContent = 'Nicht verbunden';
|
||||
}
|
||||
|
||||
// ── Charts ───────────────────────────────────────────────────
|
||||
|
||||
function setRange(h, btn) {
|
||||
chartHours = h;
|
||||
document.querySelectorAll('.range-btn').forEach(b => b.classList.remove('active'));
|
||||
btn.classList.add('active');
|
||||
loadCharts();
|
||||
}
|
||||
|
||||
async function loadCharts() {
|
||||
if (!currentStatus?.connected) {
|
||||
document.getElementById('noDataCard').classList.remove('hidden');
|
||||
document.getElementById('chartSection').classList.add('hidden');
|
||||
return;
|
||||
}
|
||||
document.getElementById('noDataCard').classList.add('hidden');
|
||||
document.getElementById('chartSection').classList.remove('hidden');
|
||||
|
||||
const v = currentStatus.values || {};
|
||||
const voltIds = VOLTAGE_IDS.filter(id => v[id] !== undefined);
|
||||
const pwrIds = POWER_IDS.filter(id => v[id] !== undefined);
|
||||
|
||||
const sensors = [...voltIds, 'frequency', ...pwrIds].join(',');
|
||||
const res = await fetch(`/api/chart-data?sensors=${sensors}&hours=${chartHours}`);
|
||||
const data = await res.json();
|
||||
|
||||
drawLineChart('chartVoltage', voltIds.map((id,i) => ({
|
||||
label: LABELS[id], color: COLORS[i], points: data[id]||[]
|
||||
})), 'legendVoltage');
|
||||
|
||||
drawLineChart('chartFreq', [{
|
||||
label:'Frequenz', color: COLORS[2], points: data.frequency||[]
|
||||
}], null, {yMin:49, yMax:51});
|
||||
|
||||
drawLineChart('chartPower', pwrIds.map((id,i) => ({
|
||||
label: LABELS[id], color: COLORS[i], points: data[id]||[]
|
||||
})), 'legendPower');
|
||||
}
|
||||
|
||||
function drawLineChart(canvasId, series, legendId, opts={}) {
|
||||
const canvas = document.getElementById(canvasId);
|
||||
const dpr = window.devicePixelRatio || 1;
|
||||
const W = canvas.parentElement.clientWidth;
|
||||
const H = canvas.height || 140;
|
||||
canvas.width = W * dpr;
|
||||
canvas.height = H * dpr;
|
||||
canvas.style.width = W + 'px';
|
||||
canvas.style.height = H + 'px';
|
||||
const ctx = canvas.getContext('2d');
|
||||
ctx.scale(dpr, dpr);
|
||||
|
||||
const PAD = {top:10, right:10, bottom:28, left:42};
|
||||
const iW = W - PAD.left - PAD.right;
|
||||
const iH = H - PAD.top - PAD.bottom;
|
||||
|
||||
ctx.clearRect(0, 0, W, H);
|
||||
ctx.fillStyle = '#0f172a';
|
||||
ctx.fillRect(0, 0, W, H);
|
||||
|
||||
const allPts = series.flatMap(s => s.points);
|
||||
if (allPts.length < 2) {
|
||||
ctx.fillStyle = '#475569';
|
||||
ctx.font = '13px system-ui';
|
||||
ctx.textAlign = 'center';
|
||||
ctx.fillText('Noch keine Daten', W/2, H/2);
|
||||
return;
|
||||
}
|
||||
|
||||
const tsMin = Math.min(...allPts.map(p => p.ts));
|
||||
const tsMax = Math.max(...allPts.map(p => p.ts));
|
||||
let vMin = opts.yMin ?? Math.min(...allPts.map(p => p.v));
|
||||
let vMax = opts.yMax ?? Math.max(...allPts.map(p => p.v));
|
||||
if (vMin === vMax) { vMin -= 1; vMax += 1; }
|
||||
const vPad = (vMax - vMin) * 0.08;
|
||||
vMin -= vPad; vMax += vPad;
|
||||
|
||||
const tx = ts => PAD.left + (ts - tsMin) / (tsMax - tsMin) * iW;
|
||||
const ty = v => PAD.top + (1 - (v - vMin) / (vMax - vMin)) * iH;
|
||||
|
||||
// Grid
|
||||
ctx.strokeStyle = '#1e293b'; ctx.lineWidth = 1;
|
||||
for (let i = 0; i <= 4; i++) {
|
||||
const y = PAD.top + iH * i / 4;
|
||||
ctx.beginPath(); ctx.moveTo(PAD.left, y); ctx.lineTo(PAD.left + iW, y); ctx.stroke();
|
||||
const val = vMax - (vMax - vMin) * i / 4;
|
||||
ctx.fillStyle = '#475569'; ctx.font = '10px system-ui'; ctx.textAlign = 'right';
|
||||
ctx.fillText(val.toFixed(val > 100 ? 0 : 1), PAD.left - 4, y + 4);
|
||||
}
|
||||
|
||||
// X-Achse Zeitstempel
|
||||
const fmtTs = ts => {
|
||||
const d = new Date(ts * 1000);
|
||||
if (chartHours <= 6) return d.toLocaleTimeString('de',{hour:'2-digit',minute:'2-digit'});
|
||||
return d.toLocaleDateString('de',{weekday:'short',hour:'2-digit',minute:'2-digit'});
|
||||
};
|
||||
ctx.fillStyle = '#475569'; ctx.font = '10px system-ui'; ctx.textAlign = 'center';
|
||||
[0, 0.25, 0.5, 0.75, 1].forEach(f => {
|
||||
const ts = tsMin + (tsMax - tsMin) * f;
|
||||
ctx.fillText(fmtTs(ts), PAD.left + iW * f, H - 6);
|
||||
});
|
||||
|
||||
// Series
|
||||
series.forEach(s => {
|
||||
if (!s.points.length) return;
|
||||
ctx.beginPath();
|
||||
ctx.strokeStyle = s.color;
|
||||
ctx.lineWidth = 2;
|
||||
ctx.lineJoin = 'round';
|
||||
s.points.forEach((p, i) => {
|
||||
const x = tx(p.ts), y = ty(p.v);
|
||||
i === 0 ? ctx.moveTo(x, y) : ctx.lineTo(x, y);
|
||||
});
|
||||
ctx.stroke();
|
||||
});
|
||||
|
||||
// Legend
|
||||
if (legendId) {
|
||||
document.getElementById(legendId).innerHTML = series.map(s =>
|
||||
`<div class="legend-item">
|
||||
<div class="legend-dot" style="background:${s.color}"></div>
|
||||
<span>${esc(s.label)}</span>
|
||||
</div>`
|
||||
).join('');
|
||||
}
|
||||
}
|
||||
|
||||
// ── Raw ──────────────────────────────────────────────────────
|
||||
|
||||
async function rawScan() {
|
||||
const alert = document.getElementById('rawAlert');
|
||||
const s = currentStatus;
|
||||
const cfg = s?.cfg || {ip:'10.0.0.100', port:502, slave:1};
|
||||
alert.innerHTML = '<div class="alert alert-info">Lese Register…</div>';
|
||||
try {
|
||||
const res = await fetch('/api/raw', {method:'POST',
|
||||
headers:{'Content-Type':'application/json'},
|
||||
body: JSON.stringify({
|
||||
ip: cfg.ip, port: cfg.port, slave: cfg.slave,
|
||||
start: parseInt(document.getElementById('rawStart').value),
|
||||
count: parseInt(document.getElementById('rawCount').value),
|
||||
})
|
||||
});
|
||||
const d = await res.json();
|
||||
if (!d.ok) { alert.innerHTML = `<div class="alert alert-err">${esc(d.error)}</div>`; return; }
|
||||
document.getElementById('rawBody').innerHTML = d.registers.map(r =>
|
||||
`<tr><td>${r.addr}</td><td>${r.hex}</td><td>${r.dec}</td><td>${r.signed}</td></tr>`
|
||||
).join('');
|
||||
alert.innerHTML = `<div class="alert alert-ok">✓ ${d.registers.length} Register</div>`;
|
||||
} catch(e) {
|
||||
alert.innerHTML = `<div class="alert alert-err">${esc(e.message)}</div>`;
|
||||
}
|
||||
}
|
||||
|
||||
// ── Export ───────────────────────────────────────────────────
|
||||
|
||||
function exportJson() {
|
||||
if (!currentStatus) return;
|
||||
const blob = new Blob([JSON.stringify(currentStatus, null, 2)], {type:'application/json'});
|
||||
const a = document.createElement('a');
|
||||
a.href = URL.createObjectURL(blob);
|
||||
a.download = `shinediag_${new Date().toISOString().slice(0,19).replace(/:/g,'-')}.json`;
|
||||
a.click();
|
||||
}
|
||||
|
||||
// ── Helpers ──────────────────────────────────────────────────
|
||||
|
||||
function esc(s) {
|
||||
return String(s??'').replace(/&/g,'&').replace(/</g,'<').replace(/>/g,'>');
|
||||
}
|
||||
|
||||
// ── Init ─────────────────────────────────────────────────────
|
||||
|
||||
async function init() {
|
||||
await loadModels();
|
||||
const savedIp = localStorage.getItem('shinediag_ip');
|
||||
if (savedIp) document.getElementById('cfgIp').value = savedIp;
|
||||
|
||||
// Reconnect if server already polling
|
||||
const res = await fetch('/api/status');
|
||||
const s = await res.json();
|
||||
if (s.connected) {
|
||||
currentStatus = s;
|
||||
renderDashboard(s);
|
||||
startRefresh();
|
||||
}
|
||||
}
|
||||
|
||||
init();
|
||||
</script>
|
||||
</body>
|
||||
</html>
|
||||
Reference in New Issue
Block a user