diff --git a/app/config.json b/app/config.json new file mode 100644 index 0000000..b0822d1 --- /dev/null +++ b/app/config.json @@ -0,0 +1,27 @@ +{ + "web_port": ":8080", + "i2c_bus": "1", + "ads1115_address_1": 72, + "ads1115_address_2": 73, + "name_adc1_ch0": "23cm", + "factor_adc1_ch0": 2.0, + "name_adc1_ch1": "13 cm", + "factor_adc1_ch1": 2.0, + "name_adc1_ch2": "9 cm", + "factor_adc1_ch2": 2.0, + "name_adc1_ch3": "6 cm", + "factor_adc1_ch3": 2.0, + "name_adc2_ch0": "3 cm", + "factor_adc2_ch0": 2.0, + "relais_pins": [ + {"id": 1, "name": "Relais 1 (230V Hauptschalter)", "gpio": "GPIO12"}, + {"id": 2, "name": "USB-Relais 1 (Reserve)", "gpio": "USB_CH1"}, + {"id": 3, "name": "USB-Relais 2 23 cm", "gpio": "USB_CH2"}, + {"id": 4, "name": "USB-Relais 3 13 cm", "gpio": "USB_CH3"}, + {"id": 5, "name": "USB-Relais 4 9 cm", "gpio": "USB_CH4"}, + {"id": 6, "name": "USB-Relais 5 6 cm", "gpio": "USB_CH5"}, + {"id": 7, "name": "USB-Relais 6 3 cm", "gpio": "USB_CH6"}, + {"id": 8, "name": "USB-Relais 7 (Reserve)", "gpio": "USB_CH7"}, + {"id": 9, "name": "USB-Relais 8 (Reserve)", "gpio": "USB_CH8"} + ] +} diff --git a/app/spannung-arm32_7 b/app/spannung-arm32_7 new file mode 100755 index 0000000..c481a7f Binary files /dev/null and b/app/spannung-arm32_7 differ diff --git a/app/spannung-arm64 b/app/spannung-arm64 new file mode 100755 index 0000000..f8d6620 Binary files /dev/null and b/app/spannung-arm64 differ diff --git a/app/spannung.service b/app/spannung.service new file mode 100644 index 0000000..bfeb329 --- /dev/null +++ b/app/spannung.service @@ -0,0 +1,33 @@ +# ============================================================================== +# KURZANLEITUNG ZUR INSTALLATION UND VERWALTUNG +# ============================================================================== +# 1. Datei kopieren nach: /etc/systemd/system/spannung.service +# 2. Systemd neu laden: sudo systemctl daemon-reload +# 3. Autostart aktivieren: sudo systemctl enable spannung.service +# 4. Dienst starten: sudo systemctl start spannung.service +# ============================================================================== + +[Unit] +Description=Spannung Control Service +After=network-online.target i2c.service +Wants=network-online.target +StartLimitIntervalSec=0 + +[Service] +Type=simple +ExecStartPre=/bin/bash -c 'for i in {1..10}; do i2cdetect -y 1 | grep -E "^40:" | grep -q -E "\b48\b" && i2cdetect -y 1 | grep -E "^40:" | grep -q -E "\b49\b" && exit 0; echo "warte auf ADS1115 Module..."; sleep 2; done; exit 1' + +# Erkennt automatisch ob 32- oder 64-Bit OS vorliegt und startet die passende Version +ExecStart=/bin/bash -c 'if [ "$(uname -m)" = "aarch64" ]; then exec %h/spannung/app/spannung-arm64; else exec %h/spannung/app/spannung-arm32_7; fi' +WorkingDirectory=%h/spannung/app +User=%u +Restart=on-failure +RestartSec=5s +StartLimitIntervalSec=60s +StartLimitBurst=3 + +StandardOutput=append:%h/spannung/app/spannung.log +StandardError=append:%h/spannung/app/spannung.log + +[Install] +WantedBy=multi-user.target diff --git a/app/version.json b/app/version.json new file mode 100644 index 0000000..4ed9dca --- /dev/null +++ b/app/version.json @@ -0,0 +1,10 @@ +{ + "version": "0.2", + "date": "02.08.2026 um 09:34", + "description": "Messung von zwei Spannung und Steuerung von Relais", + "features": [ + "ADS1115-Abfrage über go-i2c", + "Weboberfläche mit Steuerbuttons für Reilais", + "Definiton der GPIO in der config.json" + ] +} diff --git a/app/web/server.go b/app/web/server.go new file mode 100644 index 0000000..f83a6e3 --- /dev/null +++ b/app/web/server.go @@ -0,0 +1,97 @@ +package web + +import ( + "encoding/json" + "html/template" + "net/http" + "strconv" +) + +type RelaisData struct { + ID int `json:"id"` + Name string `json:"name"` + State bool `json:"state"` +} + +type StatusResponse struct { + V1 float64 `json:"v1"` + V2 float64 `json:"v2"` + V3 float64 `json:"v3"` + V4 float64 `json:"v4"` + V5 float64 `json:"v5"` + Names []string `json:"names"` // Neu: Sendet die Namen als Array mit + Relais []RelaisData `json:"relais"` + Version string `json:"version"` + BuildDate string `json:"build_date"` +} + +type Server struct { + port string + readVoltFunc func(channel int) float64 + toggleFunc func(id int) + getRelais func() []RelaisData + versionFunc func() (string, string) + getNamesFunc func() []string // Neu hinzugefügt +} + +func NewServer(port string, readVolt func(int) float64, toggle func(int), getRelais func() []RelaisData, getVersion func() (string, string), getNames func() []string) *Server { + return &Server{ + port: port, + readVoltFunc: readVolt, + toggleFunc: toggle, + getRelais: getRelais, + versionFunc: getVersion, + getNamesFunc: getNames, // Neu hinzugefügt + } +} + +func (s *Server) Start() error { + http.HandleFunc("/", s.handleIndex) + http.HandleFunc("/api/status", s.handleAPIStatus) + http.HandleFunc("/api/toggle", s.handleAPIToggle) + return http.ListenAndServe(s.port, nil) +} + +func (s *Server) handleIndex(w http.ResponseWriter, r *http.Request) { + ver, date := s.versionFunc() + tmpl, err := template.ParseFiles("web/static/index.html") + if err != nil { + http.Error(w, "Template nicht gefunden: "+err.Error(), http.StatusInternalServerError) + return + } + + data := StatusResponse{ + Names: s.getNamesFunc(), // Namen für das erste Rendern bereitstellen + Relais: s.getRelais(), + Version: ver, + BuildDate: date, + } + tmpl.Execute(w, data) +} + +func (s *Server) handleAPIStatus(w http.ResponseWriter, r *http.Request) { + w.Header().Set("Content-Type", "application/json") + + ver, date := s.versionFunc() + resp := StatusResponse{ + V1: s.readVoltFunc(0), + V2: s.readVoltFunc(1), + V3: s.readVoltFunc(2), + V4: s.readVoltFunc(3), + V5: s.readVoltFunc(4), + Names: s.getNamesFunc(), + Relais: s.getRelais(), + Version: ver, + BuildDate: date, + } + json.NewEncoder(w).Encode(resp) +} + +func (s *Server) handleAPIToggle(w http.ResponseWriter, r *http.Request) { + idStr := r.URL.Query().Get("id") + id, err := strconv.Atoi(idStr) + if err == nil { + s.toggleFunc(id) + } + s.handleAPIStatus(w, r) +} diff --git a/app/web/static/index.html b/app/web/static/index.html new file mode 100644 index 0000000..e71ab3d --- /dev/null +++ b/app/web/static/index.html @@ -0,0 +1,148 @@ + + + + Funkstation Steuerung + + + + +
+

Funkstations-Monitor

+ +
+ +
+
+

Relaissteuerung

+
+ {{range .Relais}} + + {{end}} +
+
+
+ + +
+
+

{{index .Names 0}}

+
--.-- V
+
+ +
+

{{index .Names 1}}

+
--.-- V
+
+ +
+

{{index .Names 2}}

+
--.-- V
+
+ +
+

{{index .Names 3}}

+
--.-- V
+
+ +
+

{{index .Names 4}}

+
--.-- V
+
+
+
+ + +
+ + + + diff --git a/build.sh b/build.sh index 3081563..62d0559 100755 --- a/build.sh +++ b/build.sh @@ -14,38 +14,37 @@ else DATE=$(date +'%Y-%m-%d') fi -echo "🔧 Baue Spannung-Monitor v$VERSION ($DATE)" +echo "🔧 Baue Spannung-Monitor v$VERSION ($DATE) für Raspberry Pi 3 & 4" # KORREKTUR: Einfache Anführungszeichen fangen die Leerzeichen für den Go-Linker ab LDFLAGS="-X 'main.Version=$VERSION' -X 'main.BuildDate=$DATE'" -# Multi-Plattform Builds ausführen -CGO_ENABLED=0 GOOS=linux GOARCH=amd64 go build -ldflags "$LDFLAGS" -o spannung-amd64 . -echo "✅ Fertig gebaut: ./spannung-amd64" - -CGO_ENABLED=0 GOOS=linux GOARCH=arm64 go build -ldflags "$LDFLAGS" -o spannung-arm64 . -echo "✅ Fertig gebaut: ./spannung-arm64" - -CGO_ENABLED=0 GOOS=linux GOARCH=arm GOARM=7 go build -ldflags "$LDFLAGS" -o spannung-arm32_7 . -echo "✅ Fertig gebaut: ./spannung-arm32_7" - -CGO_ENABLED=0 GOOS=linux GOARCH=arm GOARM=6 go build -ldflags "$LDFLAGS" -o spannung-arm32_6 . -echo "✅ Fertig gebaut: ./spannung-arm32_6" - -# Dateiberechtigungen setzen -chmod +x ./spannung-* - -# Zielverzeichnis 'app' neu erstellen -rm -rf app +# Zielverzeichnis 'app' bereinigen und neu erstellen +rm -rf app spannung-arm* mkdir app -# Binaries, Konfiguration und Web-Ordner kopieren -cp spannung-* ./app/ +# --- BUILD-PROZESS MIT CGO --- + +# 1. Für 64-Bit Raspberry Pi (Standard bei Pi 4 und neueren Pi 3 OS) +echo "📦 Kompiliere für ARM64 (64-Bit)..." +CGO_ENABLED=1 GOOS=linux GOARCH=arm64 CC=aarch64-linux-gnu-gcc go build -ldflags "$LDFLAGS" -o ./app/spannung-arm64 . +echo "✅ Fertig gebaut: ./app/spannung-arm64" + +# 2. Für 32-Bit Raspberry Pi (Klassisch für Pi 3 / ältere OS) +echo "📦 Kompiliere für ARM32 v7 (32-Bit)..." +CGO_ENABLED=1 GOOS=linux GOARCH=arm GOARM=7 CC=arm-linux-gnueabihf-gcc go build -ldflags "$LDFLAGS" -o ./app/spannung-arm32_7 . +echo "✅ Fertig gebaut: ./app/spannung-arm32_7" + +# Dateiberechtigungen im Zielordner setzen +chmod +x ./app/spannung-* + +# Konfiguration und Web-Ordner kopieren cp config.json ./app/ cp version.json ./app/ cp web ./app/ -r -# 3. Die Service-Datei universell generiert (nutzt systemd %h/%u Specifiers) +# 3. Die Service-Datei generieren +# Ein kleiner Bash-Wrapper in ExecStart sorgt dafür, dass automatisch das richtige Binary gestartet wird! cat << 'EOF' > ./app/spannung.service # ============================================================================== # KURZANLEITUNG ZUR INSTALLATION UND VERWALTUNG @@ -66,7 +65,8 @@ StartLimitIntervalSec=0 Type=simple ExecStartPre=/bin/bash -c 'for i in {1..10}; do i2cdetect -y 1 | grep -E "^40:" | grep -q -E "\b48\b" && i2cdetect -y 1 | grep -E "^40:" | grep -q -E "\b49\b" && exit 0; echo "warte auf ADS1115 Module..."; sleep 2; done; exit 1' -ExecStart=%h/spannung/app/spannung-arm32_7 +# Erkennt automatisch ob 32- oder 64-Bit OS vorliegt und startet die passende Version +ExecStart=/bin/bash -c 'if [ "$(uname -m)" = "aarch64" ]; then exec %h/spannung/app/spannung-arm64; else exec %h/spannung/app/spannung-arm32_7; fi' WorkingDirectory=%h/spannung/app User=%u Restart=on-failure @@ -81,6 +81,6 @@ StandardError=append:%h/spannung/app/spannung.log WantedBy=multi-user.target EOF -echo "📝 Service-Datei universell generiert (nutzt systemd %h/%u Specifiers)" +echo "📝 Service-Datei mit automatischer Architektur-Erkennung generiert" echo "📂 Inhalt des fertigen app-Verzeichnisses:" ls -la app diff --git a/deploy.sh b/deploy.sh new file mode 100644 index 0000000..194051b --- /dev/null +++ b/deploy.sh @@ -0,0 +1,36 @@ +#!/bin/bash +set -e + +# Prüfen, ob eine IP oder ein Hostname übergeben wurde +if [ -z "$1" ]; then + echo "❌ Fehler: Keine Ziel-Adresse angegeben!" + echo "💡 Nutzung: ./deploy.sh [Benutzername]" + echo "📌 Beispiel: ./deploy.sh 192.168.178.50" + echo "📌 Beispiel: ./deploy.sh raspi-4.local pi" + exit 1 +fi + +# Ziel-Konfiguration festlegen +TARGET_HOST="$1" +TARGET_USER="${2:-$USER}" # Nutzt den aktuellen Ubuntu-User, falls kein User übergeben wird +TARGET_DIR="~/spannung" + +echo "🚀 Starte Deployment auf $TARGET_USER@$TARGET_HOST..." + +# 1. Prüfen, ob der lokale 'app'-Ordner existiert +if [ ! -d "app" ]; then + echo "❌ Fehler: Der Ordner 'app' existiert nicht." + echo "🛠️ Bitte führen Sie zuerst ./build.sh aus!" + exit 1 +fi + +# 2. Zielverzeichnis auf dem Raspberry Pi vorbereiten (alte App-Dateien löschen) +echo "🧹 Bereinige altes App-Verzeichnis auf dem Raspberry Pi..." +ssh "$TARGET_USER@$TARGET_HOST" "mkdir -p $TARGET_DIR && rm -rf $TARGET_DIR/app" + +# 3. Den neuen 'app'-Ordner via SCP auf den Pi kopieren +echo "📦 Kopiere 'app'-Ordner auf den Raspberry Pi..." +scp -r ./app "$TARGET_USER@$TARGET_HOST:$TARGET_DIR/" + +echo "✅ Deployment erfolgreich abgeschlossen!" +echo "📍 Die Dateien liegen auf dem Pi unter: $TARGET_DIR/app" diff --git a/go.mod b/go.mod index 24fa030..1511b1b 100644 --- a/go.mod +++ b/go.mod @@ -3,12 +3,8 @@ module spannung go 1.25.0 require ( + github.com/google/gousb v1.1.3 periph.io/x/conn/v3 v3.7.3 periph.io/x/devices/v3 v3.7.4 periph.io/x/host/v3 v3.8.5 ) - -require ( - github.com/adriankumpf/abacom-relay-board v0.7.1 // indirect - github.com/google/gousb v1.1.3 // indirect -) diff --git a/go.sum b/go.sum index a087781..3678ca2 100644 --- a/go.sum +++ b/go.sum @@ -1,5 +1,3 @@ -github.com/adriankumpf/abacom-relay-board v0.7.1 h1:BEq26Z+mstFFO12Anla7QXmi/C5tkZT/8dCqB/vyfwY= -github.com/adriankumpf/abacom-relay-board v0.7.1/go.mod h1:j1JZRj26BIUMuxdtiBP1G5dF9piCbX/xQATZB5+CPs4= github.com/google/gousb v1.1.3 h1:xt6M5TDsGSZ+rlomz5Si5Hmd/Fvbmo2YCJHN+yGaK4o= github.com/google/gousb v1.1.3/go.mod h1:GGWUkK0gAXDzxhwrzetW592aOmkkqSGcj5KLEgmCVUg= github.com/jonboulle/clockwork v0.5.0 h1:Hyh9A8u51kptdkR+cqRpT1EebBwTn1oK9YfGYbdFz6I= diff --git a/relais.py b/relais.py deleted file mode 100644 index 23affb3..0000000 --- a/relais.py +++ /dev/null @@ -1,48 +0,0 @@ -#!/usr/bin/env python3 -import sys -import usb.core -import usb.util - -# Abacom CH341A IDs -VENDOR_ID = 0x1a86 -PRODUCT_ID = 0x5512 - -def main(): - if len(sys.argv) < 2: - print("Nutzung: python3 relais.py ") - sys.exit(1) - - try: - data_value = int(sys.argv[1]) - if not (0 <= data_value <= 255): - raise ValueError - except ValueError: - print("Fehler: Bitte eine Zahl zwischen 0 und 255 eingeben.") - sys.exit(1) - - # Gerät suchen - dev = usb.core.find(idVendor=VENDOR_ID, idProduct=PRODUCT_ID) - if dev is None: - print("Relaiskarte nicht gefunden! USB-Verbindung prüfen.") - sys.exit(1) - - # Kernel-Treiber lösen, falls aktiv - if dev.is_kernel_driver_active(0): - dev.detach_kernel_driver(0) - - # Konfiguration setzen - dev.set_configuration() - - # CH341 Befehl zum Schreiben von IO-Pins (Abacom-Protokoll) - # Befehl 0xA6 setzt die Ausgänge beim CH341 - # Das Format ist oft: [0xA6, Schalter-Byte, 0x00] oder direkte Control-Transfers - try: - # Steuerbefehl direkt an den Chip senden (CH341 spezifisch) - # 0x40 = Vendor-Write, 0x9a = CH341_REQ_WRITE_REG - dev.ctrl_transfer(0x40, 0x9a, 0x2525, data_value) - print(f"Erfolgreich geschaltet. Wert: {data_value} (Binär: {data_value:08b})") - except Exception as e: - print(f"Fehler beim Senden: {e}") - -if __name__ == "__main__": - main() diff --git a/relais2.py b/relais2.py deleted file mode 100644 index 9d3130c..0000000 --- a/relais2.py +++ /dev/null @@ -1,56 +0,0 @@ -#!/usr/bin/env python3 -import sys -import usb.core -import usb.util - -VENDOR_ID = 0x1a86 -PRODUCT_ID = 0x5512 - -def main(): - if len(sys.argv) < 2: - print("Nutzung: sudo python3 relais2.py ") - sys.exit(1) - - try: - data_value = int(sys.argv[1]) - if not (0 <= data_value <= 255): - raise ValueError - except ValueError: - print("Fehler: Bitte eine Zahl zwischen 0 und 255 eingeben.") - sys.exit(1) - - dev = usb.core.find(idVendor=VENDOR_ID, idProduct=PRODUCT_ID) - if dev is None: - print("Relaiskarte nicht gefunden! USB-Verbindung prüfen.") - sys.exit(1) - - if dev.is_kernel_driver_active(0): - try: - dev.detach_kernel_driver(0) - except Exception: - pass - - dev.set_configuration() - - try: - # CH341A GPIO-Befehl (0x5a) - # Aufbau des Pakets: - # Byte 0: 0x5a (Befehl für GPIO-Steuerung) - # Byte 1: 0x51 (Sub-Befehl für I/O-Wechsel) - # Byte 2: Richtungsmaske/Enable für die Pins (0xff = Alle Pins D0-D7 auf Ausgang) - # Byte 3: Datenwert für Pins D0-D7 (deine Relais 0-255) - # Byte 4: Richtung/Enable für obere Pins (0x00 = nicht genutzt) - # Byte 5: Datenwert für obere Pins (0x00) - - cmd_packet = bytes([0x5a, 0x51, 0xff, data_value, 0x00, 0x00]) - - # Senden an den Standard-Bulk-Ausgang (Endpoint 0x02) - dev.write(0x02, cmd_packet, 1000) - - print(f"Befehl gesendet. Wert: {data_value} (Binär: {data_value:08b})") - - except Exception as e: - print(f"Fehler beim Senden: {e}") - -if __name__ == "__main__": - main() diff --git a/spannung.service b/spannung.service deleted file mode 100644 index 3636934..0000000 --- a/spannung.service +++ /dev/null @@ -1,44 +0,0 @@ -# ============================================================================== -# KURZANLEITUNG ZUR INSTALLATION UND VERWALTUNG -# ============================================================================== -# 1. Datei kopieren nach: /etc/systemd/system/spannung.service -# 2. Systemd neu laden: sudo systemctl daemon-reload -# 3. Autostart aktivieren: sudo systemctl enable spannung.service -# 4. Dienst starten: sudo systemctl start spannung.service -# -# Befehle zur Steuerung: -# - Status prüfen: sudo systemctl status spannung.service -# - Live-Logs einsehen: sudo journalctl -u spannung.service -f -# - Dienst stoppen: sudo systemctl stop spannung.service -# - Dienst neu starten: sudo systemctl restart spannung.service -# ============================================================================== -# -# gewohnter rsync-Befehl (Beispiel) -# rsync -avz ./app/ pi@raspirotor:~/spannung/ -# 2. Den Dienst auf dem Pi remote aktualisieren und neu starten -# ssh -t afu@raspirotor "sudo cp ~/spannung/app/spannung.service /etc/systemd/system/ && sudo systemctl daemon-reload && sudo systemctl restart spannung.service" - -[Unit] -Description=Spannung Control Service -After=network-online.target i2c.service -Wants=network-online.target -StartLimitIntervalSec=0 - -[Service] -Type=simple -# Die ExecStartPre-Zeile muss bei 32-Bit auskommentiert werden, da kein i2cdetect verfügbar ist -ExecStartPre=/bin/bash -c 'for i in {1..10}; do i2cdetect -y 1 | grep -q "48" && exit 0; echo "warte auf ADS1115..."; sleep 2; done; exit 1' -ExecStart=/home/afu/spannung/spannung -WorkingDirectory=/home/afu/spannung -User=afu -#Restart=always -Restart=on-failure # Startet den Dienst nur bei Fehlern neu -RestartSec=5s # Wartet 5 Sekunden vor dem nächsten Versuch -StartLimitIntervalSec=60s # Innerhalb von 60 Sekunden -StartLimitBurst=3 # Maximal 3 Versuche in diesem Intervall - -StandardOutput=append:/home/afu/spannung/spannung.log -StandardError=append:/home/afu/spannung/spannung.log - -[Install] -WantedBy=multi-user.target