Alles verbessert
This commit is contained in:
@@ -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
|
||||
Reference in New Issue
Block a user