2026-08-02 11:17 Version 0.2
This commit is contained in:
parent
ec4eed475b
commit
cf44131e18
14 changed files with 376 additions and 179 deletions
27
app/config.json
Normal file
27
app/config.json
Normal file
|
|
@ -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"}
|
||||||
|
]
|
||||||
|
}
|
||||||
BIN
app/spannung-arm32_7
Executable file
BIN
app/spannung-arm32_7
Executable file
Binary file not shown.
BIN
app/spannung-arm64
Executable file
BIN
app/spannung-arm64
Executable file
Binary file not shown.
33
app/spannung.service
Normal file
33
app/spannung.service
Normal file
|
|
@ -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
|
||||||
10
app/version.json
Normal file
10
app/version.json
Normal file
|
|
@ -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"
|
||||||
|
]
|
||||||
|
}
|
||||||
97
app/web/server.go
Normal file
97
app/web/server.go
Normal file
|
|
@ -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)
|
||||||
|
}
|
||||||
148
app/web/static/index.html
Normal file
148
app/web/static/index.html
Normal file
|
|
@ -0,0 +1,148 @@
|
||||||
|
<!DOCTYPE html>
|
||||||
|
<html>
|
||||||
|
<head>
|
||||||
|
<title>Funkstation Steuerung</title>
|
||||||
|
<meta name="viewport" content="width=device-width, initial-scale=1">
|
||||||
|
<style>
|
||||||
|
body { font-family: 'Segoe UI', Arial, sans-serif; text-align: center; background: #0f172a; color: #f8fafc; padding: 20px; }
|
||||||
|
/* Maximale Breite erhöht, um Platz für das zweispaltige Layout zu bieten */
|
||||||
|
.container { max-width: 1000px; margin: 0 auto; }
|
||||||
|
|
||||||
|
/* Das neue Haupt-Layout: Links Relais, rechts Spannungen */
|
||||||
|
.main-layout {
|
||||||
|
display: grid;
|
||||||
|
grid-template-columns: 1fr 1fr;
|
||||||
|
gap: 20px;
|
||||||
|
align-items: start;
|
||||||
|
text-align: left;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Spalten-Container für korrekte Ausrichtung */
|
||||||
|
.layout-col {
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
}
|
||||||
|
|
||||||
|
.card { background: #1e293b; padding: 20px; margin: 15px 0; border-radius: 12px; box-shadow: 0 4px 6px -1px rgba(0,0,0,0.3); border: 1px solid #334155; text-align: center; }
|
||||||
|
.val { font-size: 2.5em; color: #38bdf8; font-weight: bold; font-family: monospace; }
|
||||||
|
.grid { display: grid; grid-template-columns: 1fr; gap: 12px; margin-top: 15px; }
|
||||||
|
.btn { width: 100%; padding: 14px; font-size: 1.1em; border: none; border-radius: 8px; cursor: pointer; font-weight: bold; transition: background 0.2s, transform 0.1s; color: white; }
|
||||||
|
.btn:active { transform: scale(0.98); }
|
||||||
|
.btn-on { background: #ef4444; }
|
||||||
|
.btn-on:hover { background: #dc2626; }
|
||||||
|
.btn-off { background: #10b981; }
|
||||||
|
.btn-off:hover { background: #059669; }
|
||||||
|
.footer-nav { margin-top: 25px; font-size: 0.9em; color: #64748b; clear: both; }
|
||||||
|
|
||||||
|
/* Responsive Anpassung: Schaltet auf Smartphones automatisch untereinander um */
|
||||||
|
@media (max-width: 768px) {
|
||||||
|
.main-layout { grid-template-columns: 1fr; gap: 0; }
|
||||||
|
}
|
||||||
|
</style>
|
||||||
|
</head>
|
||||||
|
<body>
|
||||||
|
<div class="container">
|
||||||
|
<h1>Funkstations-Monitor</h1>
|
||||||
|
|
||||||
|
<div class="main-layout">
|
||||||
|
<!-- LINKE SPALTE: Relaissteuerung -->
|
||||||
|
<div class="layout-col">
|
||||||
|
<div class="card">
|
||||||
|
<h3>Relaissteuerung</h3>
|
||||||
|
<div class="grid">
|
||||||
|
{{range .Relais}}
|
||||||
|
<button class="btn btn-off" id="relais-btn-{{.ID}}" onclick="toggleRelais({{.ID}})">
|
||||||
|
{{.Name}}: Laden...
|
||||||
|
</button>
|
||||||
|
{{end}}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- RECHTE SPALTE: Spannungsanzeigen -->
|
||||||
|
<div class="layout-col">
|
||||||
|
<div class="card">
|
||||||
|
<h3>{{index .Names 0}}</h3>
|
||||||
|
<div class="val" id="v1">--.-- V</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="card">
|
||||||
|
<h3>{{index .Names 1}}</h3>
|
||||||
|
<div class="val" id="v2">--.-- V</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="card">
|
||||||
|
<h3>{{index .Names 2}}</h3>
|
||||||
|
<div class="val" id="v3">--.-- V</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="card">
|
||||||
|
<h3>{{index .Names 3}}</h3>
|
||||||
|
<div class="val" id="v4">--.-- V</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="card">
|
||||||
|
<h3>{{index .Names 4}}</h3>
|
||||||
|
<div class="val" id="v5">--.-- V</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="footer-nav">
|
||||||
|
Live-Aktualisierung aktiv <br>
|
||||||
|
<span style="font-size: 0.8em; color: #475569; margin-top: 5px; display: block;">
|
||||||
|
Version v{{.Version}} ({{.BuildDate}})
|
||||||
|
</span>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<script>
|
||||||
|
function updateUI(data) {
|
||||||
|
document.getElementById('v1').innerText = data.v1.toFixed(2) + ' V';
|
||||||
|
document.getElementById('v2').innerText = data.v2.toFixed(2) + ' V';
|
||||||
|
document.getElementById('v3').innerText = data.v3.toFixed(2) + ' V';
|
||||||
|
document.getElementById('v4').innerText = data.v4.toFixed(2) + ' V';
|
||||||
|
document.getElementById('v5').innerText = data.v5.toFixed(2) + ' V';
|
||||||
|
|
||||||
|
data.relais.forEach(r => {
|
||||||
|
const btn = document.getElementById(`relais-btn-${r.id}`);
|
||||||
|
if (btn) {
|
||||||
|
if (r.name && r.name.toLowerCase().includes('reserve')) {
|
||||||
|
btn.className = 'btn btn-off';
|
||||||
|
btn.innerText = `${r.name}: GESPERRT`;
|
||||||
|
btn.disabled = true;
|
||||||
|
btn.style.opacity = '0.3';
|
||||||
|
btn.style.cursor = 'not-allowed';
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (r.state) {
|
||||||
|
btn.className = 'btn btn-on';
|
||||||
|
btn.innerText = `${r.name}: AKTIV (Ausschalten)`;
|
||||||
|
} else {
|
||||||
|
btn.className = 'btn btn-off';
|
||||||
|
btn.innerText = `${r.name}: INAKTIV (Einschalten)`;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
function loadStatus() {
|
||||||
|
fetch('/api/status')
|
||||||
|
.then(response => response.json())
|
||||||
|
.then(data => updateUI(data))
|
||||||
|
.catch(err => console.error('Fehler beim Laden des Status:', err));
|
||||||
|
}
|
||||||
|
|
||||||
|
function toggleRelais(id) {
|
||||||
|
fetch(`/api/toggle?id=${id}`)
|
||||||
|
.then(response => response.json())
|
||||||
|
.then(data => updateUI(data))
|
||||||
|
.catch(err => console.error('Fehler beim Schalten:', err));
|
||||||
|
}
|
||||||
|
|
||||||
|
loadStatus();
|
||||||
|
setInterval(loadStatus, 2000);
|
||||||
|
</script>
|
||||||
|
</body>
|
||||||
|
</html>
|
||||||
48
build.sh
48
build.sh
|
|
@ -14,38 +14,37 @@ else
|
||||||
DATE=$(date +'%Y-%m-%d')
|
DATE=$(date +'%Y-%m-%d')
|
||||||
fi
|
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
|
# KORREKTUR: Einfache Anführungszeichen fangen die Leerzeichen für den Go-Linker ab
|
||||||
LDFLAGS="-X 'main.Version=$VERSION' -X 'main.BuildDate=$DATE'"
|
LDFLAGS="-X 'main.Version=$VERSION' -X 'main.BuildDate=$DATE'"
|
||||||
|
|
||||||
# Multi-Plattform Builds ausführen
|
# Zielverzeichnis 'app' bereinigen und neu erstellen
|
||||||
CGO_ENABLED=0 GOOS=linux GOARCH=amd64 go build -ldflags "$LDFLAGS" -o spannung-amd64 .
|
rm -rf app spannung-arm*
|
||||||
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
|
|
||||||
mkdir app
|
mkdir app
|
||||||
|
|
||||||
# Binaries, Konfiguration und Web-Ordner kopieren
|
# --- BUILD-PROZESS MIT CGO ---
|
||||||
cp spannung-* ./app/
|
|
||||||
|
# 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 config.json ./app/
|
||||||
cp version.json ./app/
|
cp version.json ./app/
|
||||||
cp web ./app/ -r
|
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
|
cat << 'EOF' > ./app/spannung.service
|
||||||
# ==============================================================================
|
# ==============================================================================
|
||||||
# KURZANLEITUNG ZUR INSTALLATION UND VERWALTUNG
|
# KURZANLEITUNG ZUR INSTALLATION UND VERWALTUNG
|
||||||
|
|
@ -66,7 +65,8 @@ StartLimitIntervalSec=0
|
||||||
Type=simple
|
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'
|
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
|
WorkingDirectory=%h/spannung/app
|
||||||
User=%u
|
User=%u
|
||||||
Restart=on-failure
|
Restart=on-failure
|
||||||
|
|
@ -81,6 +81,6 @@ StandardError=append:%h/spannung/app/spannung.log
|
||||||
WantedBy=multi-user.target
|
WantedBy=multi-user.target
|
||||||
EOF
|
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:"
|
echo "📂 Inhalt des fertigen app-Verzeichnisses:"
|
||||||
ls -la app
|
ls -la app
|
||||||
|
|
|
||||||
36
deploy.sh
Normal file
36
deploy.sh
Normal file
|
|
@ -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 <IP-oder-Hostname-des-Pi> [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"
|
||||||
6
go.mod
6
go.mod
|
|
@ -3,12 +3,8 @@ module spannung
|
||||||
go 1.25.0
|
go 1.25.0
|
||||||
|
|
||||||
require (
|
require (
|
||||||
|
github.com/google/gousb v1.1.3
|
||||||
periph.io/x/conn/v3 v3.7.3
|
periph.io/x/conn/v3 v3.7.3
|
||||||
periph.io/x/devices/v3 v3.7.4
|
periph.io/x/devices/v3 v3.7.4
|
||||||
periph.io/x/host/v3 v3.8.5
|
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
|
|
||||||
)
|
|
||||||
|
|
|
||||||
2
go.sum
2
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 h1:xt6M5TDsGSZ+rlomz5Si5Hmd/Fvbmo2YCJHN+yGaK4o=
|
||||||
github.com/google/gousb v1.1.3/go.mod h1:GGWUkK0gAXDzxhwrzetW592aOmkkqSGcj5KLEgmCVUg=
|
github.com/google/gousb v1.1.3/go.mod h1:GGWUkK0gAXDzxhwrzetW592aOmkkqSGcj5KLEgmCVUg=
|
||||||
github.com/jonboulle/clockwork v0.5.0 h1:Hyh9A8u51kptdkR+cqRpT1EebBwTn1oK9YfGYbdFz6I=
|
github.com/jonboulle/clockwork v0.5.0 h1:Hyh9A8u51kptdkR+cqRpT1EebBwTn1oK9YfGYbdFz6I=
|
||||||
|
|
|
||||||
48
relais.py
48
relais.py
|
|
@ -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 <Wert_0_bis_255>")
|
|
||||||
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()
|
|
||||||
56
relais2.py
56
relais2.py
|
|
@ -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 <Wert_0_bis_255>")
|
|
||||||
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()
|
|
||||||
|
|
@ -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
|
|
||||||
Loading…
Reference in a new issue