Alles verbessert

This commit is contained in:
2026-05-21 19:30:33 +02:00
parent 5dccc8985e
commit 33b71416ff
19 changed files with 855 additions and 229 deletions
@@ -54,6 +54,15 @@ DRIVE_PIN_KEYS = {
'rr': 'pin_drive_rr',
}
DRIVE_NEUTRAL_KEYS = {
'fl': 'drive_pwm_neutral_fl',
'fr': 'drive_pwm_neutral_fr',
'cl': 'drive_pwm_neutral_cl',
'cr': 'drive_pwm_neutral_cr',
'rl': 'drive_pwm_neutral_rl',
'rr': 'drive_pwm_neutral_rr',
}
ACTION_SETTINGS = {
'steer_left': {
'kind': 'steer',
@@ -94,7 +103,10 @@ class PCA9685Direct:
try:
import smbus
except ImportError as exc:
raise RuntimeError('smbus ist nicht installiert.') from exc
try:
import smbus2 as smbus
except ImportError as inner_exc:
raise RuntimeError('smbus ist nicht installiert.') from inner_exc
self.address = address
self.bus = smbus.SMBus(busnum)
@@ -176,7 +188,10 @@ class MotorTester:
self.config = _load_config(self.config_path)
self.pwm.set_pwm_freq(50)
self.steer_pwm_range = int(self.config['steer_pwm_range'])
self.drive_pwm_neutral = int(self.config['drive_pwm_neutral'])
self.drive_pwm_neutral = {
wheel: int(self.config[DRIVE_NEUTRAL_KEYS[wheel]])
for wheel in WHEELS
}
self.drive_pwm_range = int(self.config['drive_pwm_range'])
def _steer_pin(self, wheel):
@@ -188,10 +203,12 @@ class MotorTester:
def _drive_pin(self, wheel):
return int(self.config[DRIVE_PIN_KEYS[wheel]])
def set_drive_neutral_pwm(self, pwm_value):
duty_cycle = int(pwm_value)
def _drive_neutral(self, wheel):
return int(self.drive_pwm_neutral[wheel])
def set_drive_neutral_pwm(self, values):
for wheel in WHEELS:
self.pwm.set_pwm(self._drive_pin(wheel), 0, duty_cycle)
self.pwm.set_pwm(self._drive_pin(wheel), 0, int(values[wheel]))
time.sleep(0.03)
def center_all_steering(self):
@@ -201,7 +218,7 @@ class MotorTester:
def stop_all_driving(self):
for wheel in WHEELS:
self.pwm.set_pwm(self._drive_pin(wheel), 0, self.drive_pwm_neutral)
self.pwm.set_pwm(self._drive_pin(wheel), 0, self._drive_neutral(wheel))
time.sleep(0.03)
def set_steering_angle(self, wheel, angle):
@@ -237,12 +254,12 @@ class MotorTester:
def test_driving(self, wheel, speed, duration):
self.pwm.set_pwm(self._steer_pin(wheel), 0, self._steer_neutral(wheel))
duty_cycle = int(
self.drive_pwm_neutral +
self._drive_neutral(wheel) +
speed / 100.0 * self.drive_pwm_range * WHEEL_DIRECTIONS[wheel]
)
self.pwm.set_pwm(self._drive_pin(wheel), 0, duty_cycle)
time.sleep(duration)
self.pwm.set_pwm(self._drive_pin(wheel), 0, self.drive_pwm_neutral)
self.pwm.set_pwm(self._drive_pin(wheel), 0, self._drive_neutral(wheel))
def execute(self, wheel, action):
if wheel not in WHEELS:
@@ -418,30 +435,61 @@ def save_steering_neutral_values(values, config_path=None):
def get_drive_neutral_value(config_path=None):
tester = MotorTester(config_path=config_path)
values = {}
for wheel in WHEELS:
key = DRIVE_NEUTRAL_KEYS[wheel]
values[wheel] = {
'wheel': wheel,
'wheel_label': WHEEL_LABELS[wheel],
'key': key,
'value': int(tester.config[key]),
'pin': tester._drive_pin(wheel),
}
return {
'config_path': tester.config_path,
'value': int(tester.config['drive_pwm_neutral']),
'pins': {wheel: tester._drive_pin(wheel) for wheel in WHEELS},
'values': values,
}
def preview_drive_neutral_value(pwm_value, config_path=None):
def preview_drive_neutral_value(values, config_path=None):
tester = MotorTester(config_path=config_path)
value = int(pwm_value)
preview_values = {}
for wheel in WHEELS:
if wheel not in values:
raise ValueError(f'Fahr-Neutralwert fehlt: {DRIVE_NEUTRAL_KEYS[wheel]}')
preview_values[wheel] = int(values[wheel])
tester.center_all_steering()
tester.set_drive_neutral_pwm(value)
tester.set_drive_neutral_pwm(preview_values)
return {
'value': value,
'pins': {wheel: tester._drive_pin(wheel) for wheel in WHEELS},
'values': {
wheel: {
'wheel': wheel,
'wheel_label': WHEEL_LABELS[wheel],
'key': DRIVE_NEUTRAL_KEYS[wheel],
'value': preview_values[wheel],
'pin': tester._drive_pin(wheel),
}
for wheel in WHEELS
},
}
def save_drive_neutral_value(pwm_value, config_path=None):
def save_drive_neutral_value(values, config_path=None):
tester = MotorTester(config_path=config_path)
value = int(pwm_value)
_update_config_values(tester.config_path, {'drive_pwm_neutral': value})
updates = {}
for wheel in WHEELS:
key = DRIVE_NEUTRAL_KEYS[wheel]
if key not in values:
raise ValueError(f'Fahr-Neutralwert fehlt: {key}')
updates[key] = int(values[key])
_update_config_values(tester.config_path, updates)
tester.config = _load_config(tester.config_path)
tester.drive_pwm_neutral = int(tester.config['drive_pwm_neutral'])
tester.drive_pwm_neutral = {
wheel: int(tester.config[DRIVE_NEUTRAL_KEYS[wheel]])
for wheel in WHEELS
}
tester.center_all_steering()
tester.set_drive_neutral_pwm(tester.drive_pwm_neutral)
return get_drive_neutral_value(config_path=tester.config_path)
@@ -4,31 +4,29 @@ import time
import os
config_filename = '../config/exomy.yaml'
WHEELS = ('fl', 'fr', 'cl', 'cr', 'rl', 'rr')
def get_driving_pins():
pin_list = []
with open(config_filename, 'r') as file:
param_dict = yaml.load(file)
for key, value in param_dict.items():
if('pin_drive_' in str(key)):
pin_list.append(value)
return pin_list
return [param_dict['pin_drive_' + wheel] for wheel in WHEELS]
def get_drive_pwm_neutral():
def get_drive_pwm_neutral_values():
with open(config_filename, 'r') as file:
param_dict = yaml.load(file)
for key, value in param_dict.items():
if('drive_pwm_neutral' in str(key)):
return value
default_value = 300
print('The parameter drive_pwm_neutral could not be found in the exomy.yaml \n')
print('It was set to the default value: '+ default_value + '\n')
return default_value
values = {}
for wheel in WHEELS:
key = 'drive_pwm_neutral_' + wheel
if key not in param_dict:
print('The parameter ' + key + ' could not be found in the exomy.yaml \n')
print('It was set to the default value: 300\n')
values[wheel] = 300
else:
values[wheel] = param_dict[key]
return values
if __name__ == "__main__":
print(
@@ -62,7 +60,7 @@ On each motor you have to turn the correction screw until the motor really stand
pwm = Adafruit_PCA9685.PCA9685()
'''
The drive_pwm_neutral value is determined from the exomy.yaml file.
The drive_pwm_neutral values are determined from the exomy.yaml file.
But it can be also calculated from the values of the PWM board and motors,
like shown in the following calculation:
@@ -83,11 +81,11 @@ On each motor you have to turn the correction screw until the motor really stand
value = int(duty_cycle*4096.0) # 307
'''
value = get_drive_pwm_neutral()
value_dict = get_drive_pwm_neutral_values()
pin_list = get_driving_pins()
for pin in pin_list:
pwm.set_pwm(pin, 0, value)
for index, pin in enumerate(pin_list):
pwm.set_pwm(pin, 0, value_dict[WHEELS[index]])
time.sleep(0.1)
raw_input('Press any button if you are done to complete configuration\n')
@@ -0,0 +1,13 @@
[Unit]
Description=ExoMy WLAN Fallback Manager
After=NetworkManager.service
Wants=NetworkManager.service
[Service]
Type=simple
ExecStart=/usr/local/bin/exomy-wifi-fallback-manager.sh
Restart=always
RestartSec=5
[Install]
WantedBy=multi-user.target
@@ -72,6 +72,41 @@ def read_memory_usage():
return 'unbekannt'
def read_undervoltage_status():
stdout, _, returncode = run_command(['vcgencmd', 'get_throttled'])
if returncode != 0 or not stdout or '=' not in stdout:
return {
'text': 'unbekannt',
'state': 'unknown',
}
try:
throttled_value = int(stdout.split('=', 1)[1].strip(), 16)
except ValueError:
return {
'text': 'unbekannt',
'state': 'unknown',
}
undervoltage_now = bool(throttled_value & 0x1)
undervoltage_occurred = bool(throttled_value & 0x10000)
if undervoltage_now:
return {
'text': 'Ja, aktuell',
'state': 'active',
}
if undervoltage_occurred:
return {
'text': 'Früher erkannt',
'state': 'past',
}
return {
'text': 'Nein',
'state': 'clear',
}
def format_uptime():
try:
with open('/proc/uptime', 'r', encoding='utf-8') as handle:
@@ -100,11 +135,25 @@ def format_disk_free():
def get_ip_addresses():
stdout, _, returncode = run_command(['ip', '-4', '-o', 'addr', 'show', 'dev', 'wlan0', 'scope', 'global'])
if returncode == 0 and stdout:
for line in stdout.splitlines():
parts = line.split()
if 'inet' in parts:
inet_index = parts.index('inet')
if inet_index + 1 < len(parts):
return parts[inet_index + 1].split('/')[0]
stdout, _, returncode = run_command(['hostname', '-I'])
if returncode != 0 or not stdout:
return 'unbekannt'
ips = [item for item in stdout.split() if item]
return ', '.join(ips) if ips else 'unbekannt'
ipv4_addresses = []
for item in stdout.split():
if item.count('.') == 3 and not item.startswith('172.17.'):
ipv4_addresses.append(item)
return ipv4_addresses[0] if ipv4_addresses else 'unbekannt'
def get_wifi_status():
@@ -149,11 +198,14 @@ def get_motor_test_container_status():
def collect_status():
undervoltage = read_undervoltage_status()
return {
'status': 'Bereit',
'system': {
'wifi': get_wifi_status(),
'ips': get_ip_addresses(),
'undervoltage': undervoltage['text'],
'undervoltage_state': undervoltage['state'],
'cpu_temperature': read_cpu_temperature(),
'cpu_usage': read_cpu_usage(),
'memory_usage': read_memory_usage(),
@@ -315,17 +367,17 @@ def get_drive_neutral_status():
try:
result = get_drive_neutral_value()
return 200, {
'status': 'Fahr-Neutralwert geladen',
'status': 'Fahr-Neutralwerte geladen',
'drive_neutral': result,
'container_status': container_status,
}
except FileNotFoundError:
return 500, {'status': 'Motor-Konfiguration fehlt'}
except Exception:
return 500, {'status': 'Fahr-Neutralwert konnte nicht geladen werden'}
return 500, {'status': 'Fahr-Neutralwerte konnten nicht geladen werden'}
def preview_drive_neutral_action(value):
def preview_drive_neutral_action(values):
from admin_motor_test import preview_drive_neutral_value
container_status = get_motor_test_container_status()
@@ -336,9 +388,9 @@ def preview_drive_neutral_action(value):
}
try:
result = preview_drive_neutral_value(int(value))
result = preview_drive_neutral_value(values)
return 200, {
'status': f"Fahr-Neutralwert: PWM {result['value']}",
'status': 'Fahr-Neutralwerte angefahren',
'drive_neutral': result,
'container_status': container_status,
}
@@ -350,7 +402,7 @@ def preview_drive_neutral_action(value):
return 500, {'status': 'Fahr-Vorschau fehlgeschlagen'}
def save_drive_neutral_action(value):
def save_drive_neutral_action(values):
from admin_motor_test import save_drive_neutral_value
container_status = get_motor_test_container_status()
@@ -361,9 +413,9 @@ def save_drive_neutral_action(value):
}
try:
result = save_drive_neutral_value(int(value))
result = save_drive_neutral_value(values)
return 200, {
'status': 'Fahr-Neutralwert gespeichert',
'status': 'Fahr-Neutralwerte gespeichert',
'drive_neutral': result,
'container_status': container_status,
}
@@ -374,7 +426,7 @@ def save_drive_neutral_action(value):
except FileNotFoundError:
return 500, {'status': 'Motor-Konfiguration fehlt'}
except Exception:
return 500, {'status': 'Fahr-Neutralwert konnte nicht gespeichert werden'}
return 500, {'status': 'Fahr-Neutralwerte konnten nicht gespeichert werden'}
class Handler(http.server.BaseHTTPRequestHandler):
@@ -431,7 +483,12 @@ class Handler(http.server.BaseHTTPRequestHandler):
self.write_json(400, {'status': 'Ungültige Anfrage'})
return
status_code, response = preview_drive_neutral_action(payload.get('value', 0))
values = payload.get('values')
if not isinstance(values, dict):
self.write_json(400, {'status': 'Fahr-Neutralwerte fehlen'})
return
status_code, response = preview_drive_neutral_action(values)
self.write_json(status_code, response)
return
@@ -449,7 +506,12 @@ class Handler(http.server.BaseHTTPRequestHandler):
self.write_json(400, {'status': 'Ungültige Anfrage'})
return
status_code, response = save_drive_neutral_action(payload.get('value', 0))
values = payload.get('values')
if not isinstance(values, dict):
self.write_json(400, {'status': 'Fahr-Neutralwerte fehlen'})
return
status_code, response = save_drive_neutral_action(values)
self.write_json(status_code, response)
return
@@ -0,0 +1,49 @@
#!/bin/bash
set -euo pipefail
WIFI_IFACE="wlan0"
PRIMARY_CONN="netplan-wlan0-eskimue.de"
SECONDARY_CONN="4pi"
FALLBACK_AP_CONN="CUNO-AP"
CHECK_INTERVAL=20
get_active_connection() {
nmcli -t -f GENERAL.CONNECTION device show "$WIFI_IFACE" 2>/dev/null | head -n 1 | cut -d: -f2-
}
activate_connection() {
local connection_name="$1"
nmcli --wait 15 connection up "$connection_name" ifname "$WIFI_IFACE" >/dev/null 2>&1
}
ensure_best_connection() {
local active_connection
active_connection="$(get_active_connection)"
if [[ "$active_connection" == "$PRIMARY_CONN" ]]; then
return
fi
if activate_connection "$PRIMARY_CONN"; then
return
fi
active_connection="$(get_active_connection)"
if [[ "$active_connection" == "$SECONDARY_CONN" ]]; then
return
fi
if activate_connection "$SECONDARY_CONN"; then
return
fi
active_connection="$(get_active_connection)"
if [[ "$active_connection" != "$FALLBACK_AP_CONN" ]]; then
activate_connection "$FALLBACK_AP_CONN" || true
fi
}
while true; do
ensure_best_connection
sleep "$CHECK_INTERVAL"
done