diff --git a/ExoMy_Software-master/config/exomy.remote.yaml b/ExoMy_Software-master/config/exomy.remote.yaml new file mode 100644 index 0000000..b55b6f1 --- /dev/null +++ b/ExoMy_Software-master/config/exomy.remote.yaml @@ -0,0 +1,55 @@ +# Naming conventions: +# +# fl - front left | fr - front right +# cl - center left | cr - center right +# rl - rear left | rr - rear right + +# Pin numbers for motors on PWM board +pin_drive_fl: 9 +pin_steer_fl: 11 + +pin_drive_fr: 0 +pin_steer_fr: 2 + +pin_drive_cl: 8 +pin_steer_cl: 10 + +pin_drive_cr: 1 +pin_steer_cr: 3 + +pin_drive_rl: 7 +pin_steer_rl: 4 + +pin_drive_rr: 6 +pin_steer_rr: 5 + +## PWM values +# Those values highly depend on the motors and pwm board used +# Check out the wiki for further explanation + +# PWM values for the neutral steering position +steer_pwm_neutral_fl: 295 +steer_pwm_neutral_fr: 295 +steer_pwm_neutral_cl: 295 +steer_pwm_neutral_cr: 295 +steer_pwm_neutral_rl: 295 +steer_pwm_neutral_rr: 295 + +# PWW range around steering neutral position +steer_pwm_range: 250 + +# PWM value for the stop position of driving motors +drive_pwm_neutral: 307 + +# PWW range around driving neutral position +drive_pwm_range: 100 + +# Steering direction correction for crabbing mode only +# 1 keeps the calculated steering angle +# -1 inverts it for the respective wheel +crab_steer_direction_fl: 1 +crab_steer_direction_fr: 1 +crab_steer_direction_cl: 1 +crab_steer_direction_cr: 1 +crab_steer_direction_rl: 1 +crab_steer_direction_rr: 1 diff --git a/ExoMy_Software-master/gui/admin-motor-test.html b/ExoMy_Software-master/gui/admin-motor-test.html new file mode 100644 index 0000000..908f903 --- /dev/null +++ b/ExoMy_Software-master/gui/admin-motor-test.html @@ -0,0 +1,752 @@ + + + + + + + CUNO Motorentest + + + + +
+
+
+
+
+ + + +
+

Admin

+

Motorentest

+
+
+

Bereit

+
+ +

+ Stoppe zuerst den ExoMy-Container. Erst wenn der Status unten eindeutig auf gestoppt steht, solltest du Motoren testen oder die Servo-Mitten ausrichten. +

+ +
+
+
+

Testmodus

+
+ +
+
+ Container + exomy_autostart +
+
+ Status + - +
+
+ Sicher testen + - +
+
+ +
+ + +
+
+ +
+ + + +
+ +
+
+
+
+

Rad auswählen

+
+ +
+ + + + + + +
+
+ +
+
+

Impulstest

+
+ +
+ + + + +
+
+
+
+ + + + +
+ + +
+
+
+ + + + + diff --git a/ExoMy_Software-master/gui/admin.html b/ExoMy_Software-master/gui/admin.html index 6a4e438..07b9c64 100644 --- a/ExoMy_Software-master/gui/admin.html +++ b/ExoMy_Software-master/gui/admin.html @@ -59,6 +59,19 @@ +
+
+

Wartung

+
+ +
+

Systemstatus

diff --git a/ExoMy_Software-master/gui/style.css b/ExoMy_Software-master/gui/style.css index 2f7846f..b00844a 100644 --- a/ExoMy_Software-master/gui/style.css +++ b/ExoMy_Software-master/gui/style.css @@ -347,6 +347,30 @@ h2 { font-size: 0.96rem; } +.admin_quick_link_card { + display: grid; + grid-template-columns: minmax(0, 1fr) 14rem; + gap: 0.9rem; + align-items: center; + padding: 0.95rem 1rem; + border-radius: var(--radius-md); + border: 1px solid var(--line); + background: var(--panel-strong); +} + +.admin_quick_link_card p { + margin: 0.35rem 0 0; + color: var(--muted); + line-height: 1.35; +} + +.admin_quick_link_button { + display: inline-flex; + align-items: center; + justify-content: center; + text-decoration: none; +} + .admin_back { color: var(--muted); text-decoration: none; @@ -433,6 +457,8 @@ h2 { .admin-page { color: #1b2730; + overflow-y: auto; + overflow-x: hidden; background: radial-gradient(circle at top left, rgba(150, 176, 183, 0.32), transparent 24%), radial-gradient(circle at bottom right, rgba(221, 193, 144, 0.3), transparent 22%), @@ -540,6 +566,19 @@ h2 { background: linear-gradient(180deg, #cb6159, #922e28); } +.admin-page .admin_quick_link_card { + border-color: rgba(116, 143, 152, 0.26); + background: + linear-gradient(180deg, rgba(244, 248, 248, 0.98), rgba(230, 237, 238, 0.98)); + box-shadow: + inset 0 1px 0 rgba(255, 255, 255, 0.8), + 0 8px 18px rgba(109, 126, 132, 0.08); +} + +.admin-page .admin_quick_link_card p { + color: #617781; +} + .admin-page .admin_back { font-family: Consolas, "Courier New", monospace; font-size: 0.9rem; @@ -591,6 +630,429 @@ h2 { box-shadow: 0 0 0 2px rgba(211, 171, 59, 0.14), 0 0 10px rgba(211, 171, 59, 0.32); } +.admin_motor_panel { + display: flex; + gap: 0.7rem; + min-height: auto; + overflow: visible; +} + +.admin_motor_content { + display: grid; + gap: 0.7rem; +} + +.motor_test_status_card { + display: grid; + gap: 0.35rem; + padding: 0.75rem 0.85rem; + border-radius: var(--radius-md); + border: 1px solid rgba(116, 143, 152, 0.26); + background: + linear-gradient(180deg, rgba(244, 248, 248, 0.98), rgba(230, 237, 238, 0.98)); + box-shadow: + inset 0 1px 0 rgba(255, 255, 255, 0.8), + 0 8px 18px rgba(109, 126, 132, 0.08); +} + +.motor_test_status_row { + display: grid; + grid-template-columns: 6.5rem 1fr; + gap: 0.6rem; + align-items: center; +} + +.motor_test_control_grid { + display: grid; + grid-template-columns: repeat(2, minmax(0, 1fr)); + gap: 0.55rem; +} + +.motor_test_tabs { + display: grid; + grid-template-columns: repeat(3, minmax(0, 1fr)); + gap: 0.55rem; +} + +.motor_test_tab_button { + min-height: 2.35rem; + padding: 0.45rem 0.7rem; + border: 1px solid rgba(116, 143, 152, 0.26); + border-radius: var(--radius-md); + background: + linear-gradient(180deg, rgba(244, 248, 248, 0.98), rgba(230, 237, 238, 0.98)); + color: #24414e; + cursor: pointer; + font-family: Consolas, "Courier New", monospace; + font-size: 0.74rem; + font-weight: 700; + letter-spacing: 0.04em; + text-transform: uppercase; + box-shadow: + inset 0 1px 0 rgba(255, 255, 255, 0.8), + 0 8px 18px rgba(109, 126, 132, 0.08); +} + +.motor_test_tab_button.is_active { + border-color: rgba(44, 102, 190, 0.58); + background: + linear-gradient(180deg, rgba(225, 236, 255, 0.98), rgba(205, 221, 247, 0.98)); + color: #163650; + box-shadow: + inset 0 1px 0 rgba(255, 255, 255, 0.9), + 0 10px 24px rgba(70, 107, 166, 0.18); +} + +.motor_test_tab_panel { + display: none; +} + +.motor_test_tab_panel.is_active { + display: block; +} + +.motor_test_layout_row { + display: grid; + grid-template-columns: minmax(0, 1.2fr) minmax(16rem, 0.9fr); + gap: 0.7rem; + align-items: stretch; +} + +.motor_test_box { + min-height: 0; + padding: 0.75rem 0.85rem; + border-radius: var(--radius-md); + border: 1px solid rgba(116, 143, 152, 0.26); + background: + linear-gradient(180deg, rgba(244, 248, 248, 0.98), rgba(230, 237, 238, 0.98)); + box-shadow: + inset 0 1px 0 rgba(255, 255, 255, 0.8), + 0 8px 18px rgba(109, 126, 132, 0.08); +} + +.wheel_selector { + display: grid; + grid-template-columns: repeat(2, minmax(0, 1fr)); + gap: 0.45rem; +} + +.wheel_option { + position: relative; + display: flex; + align-items: center; + min-height: 2.65rem; + padding: 0.48rem 0.7rem; + border-radius: var(--radius-md); + border: 1px solid rgba(116, 143, 152, 0.28); + background: + linear-gradient(180deg, rgba(244, 248, 248, 0.98), rgba(230, 237, 238, 0.98)); + box-shadow: + inset 0 1px 0 rgba(255, 255, 255, 0.82), + 0 8px 18px rgba(109, 126, 132, 0.08); + cursor: pointer; + font-weight: 700; +} + +.wheel_option input { + position: absolute; + opacity: 0; + pointer-events: none; +} + +.wheel_option span { + color: #17303b; + font-size: 0.82rem; +} + +.wheel_option:has(input:checked) { + border-color: rgba(44, 102, 190, 0.58); + background: + linear-gradient(180deg, rgba(225, 236, 255, 0.98), rgba(205, 221, 247, 0.98)); + box-shadow: + inset 0 1px 0 rgba(255, 255, 255, 0.9), + 0 10px 24px rgba(70, 107, 166, 0.18); +} + +.motor_test_grid { + display: grid; + grid-template-columns: 1fr; + gap: 0.45rem; +} + +.motor_test_grid .button { + min-height: 2.65rem; + font-size: 0.76rem; +} + +.motor_test_neutral_box { + padding: 0.75rem 0.85rem; + border-radius: var(--radius-md); + border: 1px solid rgba(116, 143, 152, 0.26); + background: + linear-gradient(180deg, rgba(244, 248, 248, 0.98), rgba(230, 237, 238, 0.98)); + box-shadow: + inset 0 1px 0 rgba(255, 255, 255, 0.8), + 0 8px 18px rgba(109, 126, 132, 0.08); +} + +.motor_test_neutral_copy { + margin-top: -0.1rem; +} + +.motor_test_neutral_toolbar { + display: grid; + grid-template-columns: repeat(2, minmax(0, 12rem)); + gap: 0.55rem; + justify-content: start; +} + +.motor_test_neutral_toolbar .button { + width: auto; + min-height: 2.35rem; + padding: 0.5rem 0.9rem; + font-size: 0.74rem; +} + +.motor_test_neutral_toolbar .button:disabled { + cursor: not-allowed; + filter: saturate(0.3); + opacity: 0.6; + box-shadow: + inset 0 1px 0 rgba(255, 255, 255, 0.22), + 0 4px 10px rgba(96, 110, 117, 0.08); +} + +.steering_neutral_grid { + display: grid; + grid-template-columns: repeat(2, minmax(0, 1fr)); + gap: 0.65rem; +} + +.steering_neutral_card { + display: grid; + gap: 0.55rem; + padding: 0.8rem; + border-radius: var(--radius-md); + border: 1px solid rgba(116, 143, 152, 0.26); + background: + linear-gradient(180deg, rgba(248, 251, 251, 0.98), rgba(233, 239, 240, 0.98)); + box-shadow: + inset 0 1px 0 rgba(255, 255, 255, 0.84), + 0 8px 18px rgba(109, 126, 132, 0.08); +} + +.steering_neutral_card.is_dirty { + border-color: rgba(214, 145, 42, 0.52); + box-shadow: + inset 0 1px 0 rgba(255, 255, 255, 0.84), + 0 8px 18px rgba(185, 133, 49, 0.14); +} + +.drive_neutral_card { + display: grid; + gap: 0.55rem; + max-width: 28rem; + padding: 0.8rem; + border-radius: var(--radius-md); + border: 1px solid rgba(116, 143, 152, 0.26); + background: + linear-gradient(180deg, rgba(248, 251, 251, 0.98), rgba(233, 239, 240, 0.98)); + box-shadow: + inset 0 1px 0 rgba(255, 255, 255, 0.84), + 0 8px 18px rgba(109, 126, 132, 0.08); +} + +.drive_neutral_card.is_dirty { + border-color: rgba(214, 145, 42, 0.52); + box-shadow: + inset 0 1px 0 rgba(255, 255, 255, 0.84), + 0 8px 18px rgba(185, 133, 49, 0.14); +} + +.drive_neutral_value_row { + max-width: 16rem; +} + +.steering_neutral_card_header { + display: grid; + gap: 0.18rem; +} + +.steering_neutral_value_row { + display: grid; + grid-template-columns: 2.8rem minmax(0, 1fr) 2.8rem; + gap: 0.45rem; + align-items: center; +} + +.steering_neutral_value { + text-align: center; + font-family: Consolas, "Courier New", monospace; + font-size: 1.25rem; +} + +.steering_neutral_adjust { + width: auto; + min-height: 2.8rem; + min-width: 2.8rem; + padding: 0.25rem; + font-size: 1.1rem; + line-height: 1; +} + +.steering_neutral_meta { + display: flex; + align-items: center; + justify-content: space-between; + gap: 0.55rem; + color: #5f7981; + font-family: Consolas, "Courier New", monospace; + font-size: 0.72rem; +} + +.motor_test_feedback_box { + padding: 0.75rem 0.85rem; + border-radius: var(--radius-md); + border: 1px solid rgba(116, 143, 152, 0.26); + background: + linear-gradient(180deg, rgba(244, 248, 248, 0.98), rgba(230, 237, 238, 0.98)); + box-shadow: + inset 0 1px 0 rgba(255, 255, 255, 0.8), + 0 8px 18px rgba(109, 126, 132, 0.08); +} + +.motor_test_feedback_card { + display: grid; + grid-template-columns: minmax(0, 1fr) auto; + gap: 0.7rem; + align-items: center; +} + +.motor_test_feedback_summary { + display: grid; + gap: 0.3rem; +} + +.motor_test_feedback_buttons { + display: grid; + grid-template-columns: repeat(2, minmax(6rem, 1fr)); + gap: 0.5rem; +} + +.motor_test_feedback_buttons .button { + min-height: 2.6rem; + font-size: 0.76rem; +} + +.motor_test_feedback_buttons .button:disabled { + cursor: not-allowed; + filter: saturate(0.3); + opacity: 0.6; + box-shadow: + inset 0 1px 0 rgba(255, 255, 255, 0.22), + 0 4px 10px rgba(96, 110, 117, 0.08); +} + +.motor_test_hold_box { + padding: 0.75rem 0.85rem; + border-radius: var(--radius-md); + border: 1px solid rgba(116, 143, 152, 0.26); + background: + linear-gradient(180deg, rgba(244, 248, 248, 0.98), rgba(230, 237, 238, 0.98)); + box-shadow: + inset 0 1px 0 rgba(255, 255, 255, 0.8), + 0 8px 18px rgba(109, 126, 132, 0.08); +} + +.motor_test_hold_layout { + display: grid; + grid-template-columns: repeat(2, minmax(0, 1fr)); + gap: 0.7rem; +} + +.motor_test_hold_card { + display: grid; + gap: 0.45rem; +} + +.motor_test_hold_buttons { + display: grid; + grid-template-columns: repeat(3, minmax(0, 1fr)); + gap: 0.45rem; +} + +.motor_test_hold_buttons .button { + min-height: 2.6rem; + font-size: 0.74rem; +} + +.motor_test_percent_box { + padding: 0.75rem 0.85rem; + border-radius: var(--radius-md); + border: 1px solid rgba(116, 143, 152, 0.26); + background: + linear-gradient(180deg, rgba(244, 248, 248, 0.98), rgba(230, 237, 238, 0.98)); + box-shadow: + inset 0 1px 0 rgba(255, 255, 255, 0.8), + 0 8px 18px rgba(109, 126, 132, 0.08); +} + +.motor_test_percent_card { + display: grid; + gap: 0.55rem; +} + +.motor_test_percent_header { + display: flex; + align-items: center; + justify-content: space-between; + gap: 0.75rem; +} + +.motor_test_percent_live_row { + display: grid; + grid-template-columns: 1fr auto; + gap: 0.6rem; + align-items: center; +} + +.motor_test_live_toggle { + min-width: 4.6rem; + min-height: 2.2rem; + padding: 0.35rem 0.7rem; + font-size: 0.72rem; +} + +.motor_test_percent_slider_row { + display: grid; + grid-template-columns: minmax(0, 1fr) auto; + gap: 0.6rem; + align-items: center; +} + +.motor_test_percent_slider { + width: 100%; + accent-color: #3271da; +} + +.motor_test_percent_buttons { + display: grid; + grid-template-columns: repeat(3, minmax(0, 1fr)); + gap: 0.45rem; +} + +.motor_test_percent_buttons .button { + min-height: 2.6rem; + font-size: 0.74rem; +} + +.admin_link_row { + display: flex; + align-items: center; + justify-content: space-between; + gap: 0.6rem; +} + @media (max-width: 1100px) { body { overflow-y: auto; @@ -613,6 +1075,10 @@ h2 { grid-template-columns: 1fr; } + .admin_quick_link_card { + grid-template-columns: 1fr; + } + .admin_actions { grid-template-columns: repeat(2, minmax(0, 1fr)); } @@ -621,8 +1087,40 @@ h2 { grid-template-columns: repeat(2, minmax(0, 1fr)); } + .wheel_selector { + grid-template-columns: repeat(2, minmax(0, 1fr)); + } + + .motor_test_control_grid { + grid-template-columns: 1fr; + } + + .motor_test_tabs { + grid-template-columns: 1fr; + } + + .motor_test_layout_row { + grid-template-columns: 1fr; + } + + .steering_neutral_grid { + grid-template-columns: 1fr; + } + + .motor_test_feedback_card { + grid-template-columns: 1fr; + } + + .motor_test_hold_layout { + grid-template-columns: 1fr; + } + + .motor_test_percent_buttons { + grid-template-columns: 1fr; + } + .button { - min-height: 5.25rem; + min-height: 4rem; } } @@ -693,6 +1191,38 @@ h2 { .admin_actions { grid-template-columns: 1fr; } + + .wheel_selector, + .motor_test_grid, + .motor_test_control_grid, + .motor_test_tabs, + .motor_test_neutral_toolbar, + .motor_test_feedback_buttons, + .motor_test_hold_buttons, + .motor_test_percent_buttons { + grid-template-columns: 1fr; + } + + .steering_neutral_grid { + grid-template-columns: 1fr; + } + + .motor_test_status_row { + grid-template-columns: 1fr; + gap: 0.25rem; + } + + .motor_test_percent_header, + .motor_test_percent_live_row, + .motor_test_percent_slider_row { + grid-template-columns: 1fr; + justify-content: flex-start; + } + + .admin_link_row { + flex-direction: column; + align-items: flex-start; + } } @media (max-height: 760px) and (min-width: 761px) { @@ -706,19 +1236,107 @@ h2 { } .admin_panel { - gap: 0.7rem; + gap: 0.45rem; } .admin_copy { - font-size: 0.86rem; + font-size: 0.78rem; + } + + .admin_motor_content { + gap: 0.45rem; + } + + .admin_section { + gap: 0.35rem; + } + + .panel_header { + gap: 0.45rem; + } + + .pilot_logo { + width: 3.2rem; + } + + .status_badge { + padding: 0.45rem 0.7rem; + font-size: 0.76rem; + } + + .eyebrow { + margin: 0 0 0.2rem; + font-size: 0.68rem; + } + + h1 { + font-size: 1.45rem; + } + + .motor_test_status_card, + .motor_test_box, + .motor_test_neutral_box, + .motor_test_feedback_box, + .motor_test_hold_box, + .motor_test_percent_box { + padding: 0.58rem 0.7rem; + } + + .motor_test_status_row { + grid-template-columns: 5.4rem 1fr; + gap: 0.45rem; + } + + .wheel_option { + min-height: 2.35rem; + padding: 0.4rem 0.55rem; + } + + .wheel_option span { + font-size: 0.76rem; + } + + .motor_test_grid .button, + .motor_test_tab_button, + .motor_test_neutral_toolbar .button, + .motor_test_feedback_buttons .button, + .motor_test_hold_buttons .button, + .motor_test_control_grid .button, + .motor_test_percent_buttons .button { + min-height: 2.35rem; + font-size: 0.7rem; + } + + .steering_neutral_adjust { + min-height: 2.35rem; + min-width: 2.35rem; + font-size: 1.05rem; + } + + .motor_test_live_toggle { + min-height: 2.1rem; + min-width: 4rem; + font-size: 0.68rem; + } + + .motor_test_control_grid, + .motor_test_layout_row, + .motor_test_feedback_card, + .motor_test_hold_layout, + .motor_test_hold_buttons, + .motor_test_feedback_buttons, + .motor_test_percent_buttons, + .wheel_selector, + .motor_test_grid { + gap: 0.35rem; } .admin_actions .button { - min-height: 3.35rem; - font-size: 0.9rem; + min-height: 2.8rem; + font-size: 0.82rem; } .info_card { - padding: 0.7rem 0.8rem; + padding: 0.6rem 0.7rem; } } diff --git a/ExoMy_Software-master/scripts/admin_motor_test.py b/ExoMy_Software-master/scripts/admin_motor_test.py new file mode 100644 index 0000000..c60656d --- /dev/null +++ b/ExoMy_Software-master/scripts/admin_motor_test.py @@ -0,0 +1,486 @@ +#!/usr/bin/env python3 +import json +import math +import os +import subprocess +import sys +import time + + +WHEELS = ('fl', 'fr', 'cl', 'cr', 'rl', 'rr') + +WHEEL_LABELS = { + 'fl': 'Vorne links', + 'fr': 'Vorne rechts', + 'cl': 'Mitte links', + 'cr': 'Mitte rechts', + 'rl': 'Hinten links', + 'rr': 'Hinten rechts', +} + +WHEEL_DIRECTIONS = { + 'fl': -1, + 'fr': 1, + 'cl': -1, + 'cr': 1, + 'rl': -1, + 'rr': 1, +} + +STEER_PIN_KEYS = { + 'fl': 'pin_steer_fl', + 'fr': 'pin_steer_fr', + 'cl': 'pin_steer_cl', + 'cr': 'pin_steer_cr', + 'rl': 'pin_steer_rl', + 'rr': 'pin_steer_rr', +} + +STEER_NEUTRAL_KEYS = { + 'fl': 'steer_pwm_neutral_fl', + 'fr': 'steer_pwm_neutral_fr', + 'cl': 'steer_pwm_neutral_cl', + 'cr': 'steer_pwm_neutral_cr', + 'rl': 'steer_pwm_neutral_rl', + 'rr': 'steer_pwm_neutral_rr', +} + +DRIVE_PIN_KEYS = { + 'fl': 'pin_drive_fl', + 'fr': 'pin_drive_fr', + 'cl': 'pin_drive_cl', + 'cr': 'pin_drive_cr', + 'rl': 'pin_drive_rl', + 'rr': 'pin_drive_rr', +} + +ACTION_SETTINGS = { + 'steer_left': { + 'kind': 'steer', + 'angle': -35, + 'duration': 0.6, + 'label': 'Lenkung links', + }, + 'steer_right': { + 'kind': 'steer', + 'angle': 35, + 'duration': 0.6, + 'label': 'Lenkung rechts', + }, + 'drive_forward': { + 'kind': 'drive', + 'speed': 35, + 'duration': 0.7, + 'label': 'Vorwärts', + }, + 'drive_backward': { + 'kind': 'drive', + 'speed': -35, + 'duration': 0.7, + 'label': 'Rückwärts', + }, +} + +CONTAINER_NAME = 'exomy_autostart' +CONTAINER_SCRIPT_PATH = '/root/exomy_ws/src/exomy/scripts/admin_motor_test.py' + + +class PCA9685Direct: + MODE1 = 0x00 + PRESCALE = 0xFE + LED0_ON_L = 0x06 + + def __init__(self, busnum=1, address=0x40): + try: + import smbus + except ImportError as exc: + raise RuntimeError('smbus ist nicht installiert.') from exc + + self.address = address + self.bus = smbus.SMBus(busnum) + self.write8(self.MODE1, 0x00) + time.sleep(0.005) + + def write8(self, reg, value): + self.bus.write_byte_data(self.address, reg, value & 0xFF) + + def read8(self, reg): + return self.bus.read_byte_data(self.address, reg) + + def set_pwm_freq(self, freq_hz): + prescaleval = 25000000.0 + prescaleval /= 4096.0 + prescaleval /= float(freq_hz) + prescaleval -= 1.0 + prescale = int(math.floor(prescaleval + 0.5)) + + oldmode = self.read8(self.MODE1) + sleepmode = (oldmode & 0x7F) | 0x10 + self.write8(self.MODE1, sleepmode) + self.write8(self.PRESCALE, prescale) + self.write8(self.MODE1, oldmode) + time.sleep(0.005) + self.write8(self.MODE1, oldmode | 0xA1) + + def set_pwm(self, channel, on, off): + base = self.LED0_ON_L + 4 * int(channel) + self.write8(base, on & 0xFF) + self.write8(base + 1, (on >> 8) & 0xFF) + self.write8(base + 2, off & 0xFF) + self.write8(base + 3, (off >> 8) & 0xFF) + + +def _load_config(config_path): + config = {} + with open(config_path, 'r', encoding='utf-8') as handle: + for raw_line in handle: + line = raw_line.strip() + if not line or line.startswith('#') or ':' not in line: + continue + key, value = line.split(':', 1) + config[key.strip()] = value.strip() + return config + + +def _update_config_values(config_path, updates): + output_lines = [] + with open(config_path, 'r', encoding='utf-8') as handle: + for raw_line in handle: + line = raw_line + stripped = raw_line.strip() + if stripped and not stripped.startswith('#') and ':' in raw_line: + key = raw_line.split(':', 1)[0].strip() + if key in updates: + line = f'{key}: {updates[key]}\n' + output_lines.append(line) + + with open(config_path, 'w', encoding='utf-8') as handle: + handle.writelines(output_lines) + + +class MotorTester: + def __init__(self, config_path=None): + try: + import Adafruit_PCA9685 + self.pwm = Adafruit_PCA9685.PCA9685() + except ImportError: + self.pwm = PCA9685Direct(busnum=1) + + if config_path is None: + config_path = os.path.join(os.path.dirname(__file__), '..', 'config', 'exomy.yaml') + + if not os.path.exists(config_path): + raise FileNotFoundError(f'Konfigurationsdatei nicht gefunden: {config_path}') + + self.config_path = os.path.abspath(config_path) + 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_range = int(self.config['drive_pwm_range']) + + def _steer_pin(self, wheel): + return int(self.config[STEER_PIN_KEYS[wheel]]) + + def _steer_neutral(self, wheel): + return int(self.config[STEER_NEUTRAL_KEYS[wheel]]) + + 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) + for wheel in WHEELS: + self.pwm.set_pwm(self._drive_pin(wheel), 0, duty_cycle) + time.sleep(0.03) + + def center_all_steering(self): + for wheel in WHEELS: + self.pwm.set_pwm(self._steer_pin(wheel), 0, self._steer_neutral(wheel)) + time.sleep(0.03) + + def stop_all_driving(self): + for wheel in WHEELS: + self.pwm.set_pwm(self._drive_pin(wheel), 0, self.drive_pwm_neutral) + time.sleep(0.03) + + def set_steering_angle(self, wheel, angle): + duty_cycle = int(self._steer_neutral(wheel) + angle / 90.0 * self.steer_pwm_range) + self.pwm.set_pwm(self._steer_pin(wheel), 0, duty_cycle) + + def set_steering_neutral(self, wheel): + self.pwm.set_pwm(self._steer_pin(wheel), 0, self._steer_neutral(wheel)) + + def set_steering_neutral_pwm(self, wheel, pwm_value): + self.pwm.set_pwm(self._steer_pin(wheel), 0, int(pwm_value)) + + def set_steering_percent(self, wheel, signed_percent): + percent = max(-100.0, min(100.0, float(signed_percent))) + duty_cycle = int(self._steer_neutral(wheel) + percent / 100.0 * self.steer_pwm_range) + self.pwm.set_pwm(self._steer_pin(wheel), 0, duty_cycle) + + def set_all_steering_angle(self, angle): + for wheel in WHEELS: + self.set_steering_angle(wheel, angle) + time.sleep(0.03) + + def set_all_steering_neutral(self): + for wheel in WHEELS: + self.set_steering_neutral(wheel) + time.sleep(0.03) + + def test_steering(self, wheel, angle, duration): + self.set_steering_angle(wheel, angle) + time.sleep(duration) + self.set_steering_neutral(wheel) + + 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 + + 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) + + def execute(self, wheel, action): + if wheel not in WHEELS: + raise ValueError('Unbekanntes Rad.') + + if action not in ACTION_SETTINGS: + raise ValueError('Unbekannte Testaktion.') + + settings = ACTION_SETTINGS[action] + + self.stop_all_driving() + + if settings['kind'] == 'steer': + self.test_steering(wheel, settings['angle'], settings['duration']) + else: + self.test_driving(wheel, settings['speed'], settings['duration']) + + return { + 'wheel': wheel, + 'wheel_label': WHEEL_LABELS[wheel], + 'action': action, + 'action_label': settings['label'], + } + + def hold_steering(self, wheel, angle): + if wheel not in WHEELS: + raise ValueError('Unbekanntes Rad.') + + self.stop_all_driving() + self.set_steering_angle(wheel, angle) + return { + 'scope': 'single', + 'wheel': wheel, + 'wheel_label': WHEEL_LABELS[wheel], + 'angle': angle, + } + + def hold_steering_percent(self, wheel, percent, direction): + if wheel not in WHEELS: + raise ValueError('Unbekanntes Rad.') + + if direction not in ('left', 'right'): + raise ValueError('Unbekannte Richtung.') + + clamped_percent = max(0.0, min(100.0, float(percent))) + signed_percent = -clamped_percent if direction == 'left' else clamped_percent + + self.stop_all_driving() + self.set_steering_percent(wheel, signed_percent) + return { + 'scope': 'single', + 'wheel': wheel, + 'wheel_label': WHEEL_LABELS[wheel], + 'percent': clamped_percent, + 'direction': direction, + 'signed_percent': signed_percent, + } + + def hold_all_steering(self, angle): + self.stop_all_driving() + self.set_all_steering_angle(angle) + return { + 'scope': 'all', + 'wheel': 'all', + 'wheel_label': 'Alle Räder', + 'angle': angle, + } + + def neutral_single_steering(self, wheel): + if wheel not in WHEELS: + raise ValueError('Unbekanntes Rad.') + + self.stop_all_driving() + self.set_steering_neutral(wheel) + return { + 'scope': 'single', + 'wheel': wheel, + 'wheel_label': WHEEL_LABELS[wheel], + 'angle': 0, + } + + def neutral_all_steering(self): + self.stop_all_driving() + self.set_all_steering_neutral() + return { + 'scope': 'all', + 'wheel': 'all', + 'wheel_label': 'Alle Räder', + 'angle': 0, + } + + +def run_motor_test(wheel, action, config_path=None): + tester = MotorTester(config_path=config_path) + return tester.execute(wheel.lower(), action) + + +def hold_single_steering(wheel, angle, config_path=None): + tester = MotorTester(config_path=config_path) + return tester.hold_steering(wheel.lower(), angle) + + +def hold_all_steering(angle, config_path=None): + tester = MotorTester(config_path=config_path) + return tester.hold_all_steering(angle) + + +def hold_single_steering_percent(wheel, percent, direction, config_path=None): + tester = MotorTester(config_path=config_path) + return tester.hold_steering_percent(wheel.lower(), percent, direction) + + +def neutral_single_steering(wheel, config_path=None): + tester = MotorTester(config_path=config_path) + return tester.neutral_single_steering(wheel.lower()) + + +def neutral_all_steering(config_path=None): + tester = MotorTester(config_path=config_path) + return tester.neutral_all_steering() + + +def get_steering_neutral_values(config_path=None): + tester = MotorTester(config_path=config_path) + values = {} + for wheel in WHEELS: + key = STEER_NEUTRAL_KEYS[wheel] + values[wheel] = { + 'wheel': wheel, + 'wheel_label': WHEEL_LABELS[wheel], + 'key': key, + 'value': int(tester.config[key]), + 'pin': tester._steer_pin(wheel), + } + return { + 'config_path': tester.config_path, + 'values': values, + } + + +def preview_steering_neutral_value(wheel, pwm_value, config_path=None): + tester = MotorTester(config_path=config_path) + if wheel not in WHEELS: + raise ValueError('Unbekanntes Rad.') + + tester.stop_all_driving() + tester.set_steering_neutral_pwm(wheel, int(pwm_value)) + return { + 'wheel': wheel, + 'wheel_label': WHEEL_LABELS[wheel], + 'value': int(pwm_value), + 'pin': tester._steer_pin(wheel), + } + + +def save_steering_neutral_values(values, config_path=None): + tester = MotorTester(config_path=config_path) + updates = {} + + for wheel in WHEELS: + key = STEER_NEUTRAL_KEYS[wheel] + if key not in values: + raise ValueError(f'Neutralwert fehlt: {key}') + updates[key] = int(values[key]) + + _update_config_values(tester.config_path, updates) + tester.config = _load_config(tester.config_path) + tester.stop_all_driving() + tester.center_all_steering() + + return get_steering_neutral_values(config_path=tester.config_path) + + +def get_drive_neutral_value(config_path=None): + tester = MotorTester(config_path=config_path) + return { + 'config_path': tester.config_path, + 'value': int(tester.config['drive_pwm_neutral']), + 'pins': {wheel: tester._drive_pin(wheel) for wheel in WHEELS}, + } + + +def preview_drive_neutral_value(pwm_value, config_path=None): + tester = MotorTester(config_path=config_path) + value = int(pwm_value) + tester.center_all_steering() + tester.set_drive_neutral_pwm(value) + return { + 'value': value, + 'pins': {wheel: tester._drive_pin(wheel) for wheel in WHEELS}, + } + + +def save_drive_neutral_value(pwm_value, config_path=None): + tester = MotorTester(config_path=config_path) + value = int(pwm_value) + _update_config_values(tester.config_path, {'drive_pwm_neutral': value}) + tester.config = _load_config(tester.config_path) + tester.drive_pwm_neutral = int(tester.config['drive_pwm_neutral']) + tester.center_all_steering() + tester.set_drive_neutral_pwm(tester.drive_pwm_neutral) + return get_drive_neutral_value(config_path=tester.config_path) + + +def run_motor_test_in_container(wheel, action): + command = [ + 'docker', + 'exec', + CONTAINER_NAME, + 'python', + CONTAINER_SCRIPT_PATH, + wheel.lower(), + action, + ] + result = subprocess.run(command, capture_output=True, text=True, check=False) + if result.returncode != 0: + stderr = result.stderr.strip() + stdout = result.stdout.strip() + message = stderr or stdout or 'Motortest im Container fehlgeschlagen' + raise RuntimeError(message) + + try: + return json.loads(result.stdout) + except json.JSONDecodeError as exc: + raise RuntimeError('Antwort des Container-Motortests war ungültig') from exc + + +def main(): + if len(sys.argv) != 3: + print('Aufruf: admin_motor_test.py ') + return 1 + + wheel = sys.argv[1].lower() + action = sys.argv[2] + result = run_motor_test(wheel, action, config_path=None) + print(json.dumps(result)) + return 0 + + +if __name__ == '__main__': + raise SystemExit(main()) diff --git a/ExoMy_Software-master/scripts/exomy_admin_api.py b/ExoMy_Software-master/scripts/exomy_admin_api.py index 6a7ad77..d3dce70 100644 --- a/ExoMy_Software-master/scripts/exomy_admin_api.py +++ b/ExoMy_Software-master/scripts/exomy_admin_api.py @@ -4,10 +4,12 @@ import json import shutil import socketserver import subprocess +import threading import time PORT = 8082 EXOMY_CONTAINER = 'exomy_autostart' +MOTOR_TEST_LOCK = threading.Lock() class ReusableThreadingTCPServer(socketserver.ThreadingTCPServer): @@ -95,6 +97,15 @@ def get_container_state(container_name): return 'unknown' +def get_motor_test_container_status(): + state = get_container_state(EXOMY_CONTAINER) + return { + 'container': EXOMY_CONTAINER, + 'state': state, + 'is_safe_for_motor_test': state in ('exited', 'stopped', 'created'), + } + + def collect_status(): return { 'status': 'Bereit', @@ -121,6 +132,16 @@ def restart_exomy_container(): subprocess.Popen(['docker', 'restart', EXOMY_CONTAINER]) +def stop_exomy_container(): + stdout, _, returncode = run_command(['docker', 'stop', '-t', '2', EXOMY_CONTAINER]) + return returncode == 0, stdout + + +def start_exomy_container(): + stdout, _, returncode = run_command(['docker', 'start', EXOMY_CONTAINER]) + return returncode == 0, stdout + + def reboot_raspberry_pi(): subprocess.Popen(['systemctl', 'reboot']) @@ -129,6 +150,189 @@ def shutdown_raspberry_pi(): subprocess.Popen(['systemctl', 'poweroff']) +def run_motor_test_action(wheel, action): + from admin_motor_test import run_motor_test + + acquired = MOTOR_TEST_LOCK.acquire(blocking=False) + if not acquired: + return 409, {'status': 'Motortest läuft bereits'} + + try: + container_status = get_motor_test_container_status() + if not container_status['is_safe_for_motor_test']: + return 409, { + 'status': 'ExoMy-Container läuft noch. Bitte zuerst auf der Motortest-Seite stoppen.', + 'container_status': container_status, + } + + result = run_motor_test(wheel, action) + return 200, { + 'status': f"{result['wheel_label']}: {result['action_label']}", + 'result': result, + 'container_status': get_motor_test_container_status(), + } + except FileNotFoundError: + return 500, {'status': 'Motor-Konfiguration fehlt'} + except RuntimeError as exc: + return 500, {'status': str(exc)} + except ValueError as exc: + return 400, {'status': str(exc)} + except Exception: + return 500, {'status': 'Motortest fehlgeschlagen'} + finally: + MOTOR_TEST_LOCK.release() + + +def get_steering_neutral_status(): + from admin_motor_test import get_steering_neutral_values + + container_status = get_motor_test_container_status() + if not container_status['is_safe_for_motor_test']: + return 409, { + 'status': 'ExoMy-Container läuft noch. Bitte zuerst auf der Motortest-Seite stoppen.', + 'container_status': container_status, + } + + try: + result = get_steering_neutral_values() + return 200, { + 'status': 'Servo-Mitten geladen', + 'neutral_values': result, + 'container_status': container_status, + } + except FileNotFoundError: + return 500, {'status': 'Motor-Konfiguration fehlt'} + except Exception: + return 500, {'status': 'Servo-Mitten konnten nicht geladen werden'} + + +def preview_steering_neutral_action(wheel, value): + from admin_motor_test import preview_steering_neutral_value + + container_status = get_motor_test_container_status() + if not container_status['is_safe_for_motor_test']: + return 409, { + 'status': 'ExoMy-Container läuft noch. Bitte zuerst auf der Motortest-Seite stoppen.', + 'container_status': container_status, + } + + try: + result = preview_steering_neutral_value(str(wheel).lower(), int(value)) + return 200, { + 'status': f"{result['wheel_label']}: PWM {result['value']}", + 'preview': result, + 'container_status': container_status, + } + except ValueError as exc: + return 400, {'status': str(exc)} + except RuntimeError as exc: + return 500, {'status': str(exc)} + except Exception: + return 500, {'status': 'Servo-Vorschau fehlgeschlagen'} + + +def save_steering_neutral_action(values): + from admin_motor_test import save_steering_neutral_values + + container_status = get_motor_test_container_status() + if not container_status['is_safe_for_motor_test']: + return 409, { + 'status': 'ExoMy-Container läuft noch. Bitte zuerst auf der Motortest-Seite stoppen.', + 'container_status': container_status, + } + + try: + result = save_steering_neutral_values(values) + return 200, { + 'status': 'Servo-Mitten gespeichert', + 'neutral_values': result, + 'container_status': container_status, + } + except ValueError as exc: + return 400, {'status': str(exc)} + except RuntimeError as exc: + return 500, {'status': str(exc)} + except FileNotFoundError: + return 500, {'status': 'Motor-Konfiguration fehlt'} + except Exception: + return 500, {'status': 'Servo-Mitten konnten nicht gespeichert werden'} + + +def get_drive_neutral_status(): + from admin_motor_test import get_drive_neutral_value + + container_status = get_motor_test_container_status() + if not container_status['is_safe_for_motor_test']: + return 409, { + 'status': 'ExoMy-Container läuft noch. Bitte zuerst auf der Motortest-Seite stoppen.', + 'container_status': container_status, + } + + try: + result = get_drive_neutral_value() + return 200, { + 'status': 'Fahr-Neutralwert 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'} + + +def preview_drive_neutral_action(value): + from admin_motor_test import preview_drive_neutral_value + + container_status = get_motor_test_container_status() + if not container_status['is_safe_for_motor_test']: + return 409, { + 'status': 'ExoMy-Container läuft noch. Bitte zuerst auf der Motortest-Seite stoppen.', + 'container_status': container_status, + } + + try: + result = preview_drive_neutral_value(int(value)) + return 200, { + 'status': f"Fahr-Neutralwert: PWM {result['value']}", + 'drive_neutral': result, + 'container_status': container_status, + } + except ValueError as exc: + return 400, {'status': str(exc)} + except RuntimeError as exc: + return 500, {'status': str(exc)} + except Exception: + return 500, {'status': 'Fahr-Vorschau fehlgeschlagen'} + + +def save_drive_neutral_action(value): + from admin_motor_test import save_drive_neutral_value + + container_status = get_motor_test_container_status() + if not container_status['is_safe_for_motor_test']: + return 409, { + 'status': 'ExoMy-Container läuft noch. Bitte zuerst auf der Motortest-Seite stoppen.', + 'container_status': container_status, + } + + try: + result = save_drive_neutral_value(int(value)) + return 200, { + 'status': 'Fahr-Neutralwert gespeichert', + 'drive_neutral': result, + 'container_status': container_status, + } + except ValueError as exc: + return 400, {'status': str(exc)} + except RuntimeError as exc: + return 500, {'status': str(exc)} + except FileNotFoundError: + return 500, {'status': 'Motor-Konfiguration fehlt'} + except Exception: + return 500, {'status': 'Fahr-Neutralwert konnte nicht gespeichert werden'} + + class Handler(http.server.BaseHTTPRequestHandler): def end_headers(self): self.send_header('Access-Control-Allow-Origin', '*') @@ -152,9 +356,154 @@ class Handler(http.server.BaseHTTPRequestHandler): if self.path == '/api/status': self.write_json(200, collect_status()) return + if self.path == '/api/motor-test/status': + self.write_json(200, { + 'status': 'Bereit', + 'container_status': get_motor_test_container_status(), + }) + return + if self.path == '/api/motor-test/steering-neutral': + status_code, response = get_steering_neutral_status() + self.write_json(status_code, response) + return + if self.path == '/api/motor-test/drive-neutral': + status_code, response = get_drive_neutral_status() + self.write_json(status_code, response) + return self.write_json(404, {'status': 'Unbekannt'}) def do_POST(self): + if self.path == '/api/motor-test/drive-neutral/preview': + try: + content_length = int(self.headers.get('Content-Length', '0')) + except ValueError: + content_length = 0 + + raw_body = self.rfile.read(content_length) if content_length > 0 else b'{}' + + try: + payload = json.loads(raw_body.decode('utf-8')) + except (UnicodeDecodeError, json.JSONDecodeError): + self.write_json(400, {'status': 'Ungültige Anfrage'}) + return + + status_code, response = preview_drive_neutral_action(payload.get('value', 0)) + self.write_json(status_code, response) + return + + if self.path == '/api/motor-test/drive-neutral/save': + try: + content_length = int(self.headers.get('Content-Length', '0')) + except ValueError: + content_length = 0 + + raw_body = self.rfile.read(content_length) if content_length > 0 else b'{}' + + try: + payload = json.loads(raw_body.decode('utf-8')) + except (UnicodeDecodeError, json.JSONDecodeError): + self.write_json(400, {'status': 'Ungültige Anfrage'}) + return + + status_code, response = save_drive_neutral_action(payload.get('value', 0)) + self.write_json(status_code, response) + return + + if self.path == '/api/motor-test/steering-neutral/preview': + try: + content_length = int(self.headers.get('Content-Length', '0')) + except ValueError: + content_length = 0 + + raw_body = self.rfile.read(content_length) if content_length > 0 else b'{}' + + try: + payload = json.loads(raw_body.decode('utf-8')) + except (UnicodeDecodeError, json.JSONDecodeError): + self.write_json(400, {'status': 'Ungültige Anfrage'}) + return + + status_code, response = preview_steering_neutral_action( + payload.get('wheel', ''), + payload.get('value', 0) + ) + self.write_json(status_code, response) + return + + if self.path == '/api/motor-test/steering-neutral/save': + try: + content_length = int(self.headers.get('Content-Length', '0')) + except ValueError: + content_length = 0 + + raw_body = self.rfile.read(content_length) if content_length > 0 else b'{}' + + try: + payload = json.loads(raw_body.decode('utf-8')) + except (UnicodeDecodeError, json.JSONDecodeError): + self.write_json(400, {'status': 'Ungültige Anfrage'}) + return + + values = payload.get('values') + if not isinstance(values, dict): + self.write_json(400, {'status': 'Neutralwerte fehlen'}) + return + + status_code, response = save_steering_neutral_action(values) + self.write_json(status_code, response) + return + + if self.path == '/api/motor-test/stop-container': + stopped, _ = stop_exomy_container() + container_status = get_motor_test_container_status() + if not stopped and not container_status['is_safe_for_motor_test']: + self.write_json(500, { + 'status': 'ExoMy-Container konnte nicht gestoppt werden', + 'container_status': container_status, + }) + return + self.write_json(200, { + 'status': 'ExoMy-Container ist gestoppt', + 'container_status': container_status, + }) + return + + if self.path == '/api/motor-test/start-container': + started, _ = start_exomy_container() + container_status = get_motor_test_container_status() + if not started and container_status['state'] != 'running': + self.write_json(500, { + 'status': 'ExoMy-Container konnte nicht gestartet werden', + 'container_status': container_status, + }) + return + self.write_json(200, { + 'status': 'ExoMy-Container läuft wieder', + 'container_status': container_status, + }) + return + + if self.path == '/api/motor-test': + try: + content_length = int(self.headers.get('Content-Length', '0')) + except ValueError: + content_length = 0 + + raw_body = self.rfile.read(content_length) if content_length > 0 else b'{}' + + try: + payload = json.loads(raw_body.decode('utf-8')) + except (UnicodeDecodeError, json.JSONDecodeError): + self.write_json(400, {'status': 'Ungültige Anfrage'}) + return + + status_code, response = run_motor_test_action( + str(payload.get('wheel', '')).lower(), + str(payload.get('action', '')) + ) + self.write_json(status_code, response) + return + actions = { '/api/restart-camera': { 'handler': restart_camera_service, diff --git a/ExoMy_Software-master/src/rover.py b/ExoMy_Software-master/src/rover.py index 835246a..3c72d63 100644 --- a/ExoMy_Software-master/src/rover.py +++ b/ExoMy_Software-master/src/rover.py @@ -159,10 +159,10 @@ class Rover(): if(self.locomotion_mode == LocomotionMode.CRABBING.value): if(driving_command != 0): wheel_direction = 0 - if(steering_command > 0): + if(steering_command >= 0): wheel_direction = steering_command - 90 - elif(steering_command <= 0): + elif(steering_command < 0): wheel_direction = steering_command + 90 wheel_direction = np.clip(wheel_direction, -75, 75) @@ -279,20 +279,14 @@ class Rover(): return motor_speeds if(self.locomotion_mode == LocomotionMode.CRABBING.value): - if(driving_command > 0): - if(steering_command > 0): - motor_speeds[self.FL] = 50 - motor_speeds[self.FR] = 50 - motor_speeds[self.CL] = 50 - motor_speeds[self.CR] = 50 - motor_speeds[self.RL] = 50 - motor_speeds[self.RR] = 50 - elif(steering_command <= 0): - motor_speeds[self.FL] = -50 - motor_speeds[self.FR] = -50 - motor_speeds[self.CL] = -50 - motor_speeds[self.CR] = -50 - motor_speeds[self.RL] = -50 - motor_speeds[self.RR] = -50 + v = driving_command + if(steering_command < 0): + v *= -1 + motor_speeds[self.FL] = v + motor_speeds[self.FR] = v + motor_speeds[self.CL] = v + motor_speeds[self.CR] = v + motor_speeds[self.RL] = v + motor_speeds[self.RR] = v return motor_speeds diff --git a/motor_test_results.jsonl b/motor_test_results.jsonl new file mode 100644 index 0000000..bd050c9 --- /dev/null +++ b/motor_test_results.jsonl @@ -0,0 +1,24 @@ +{"timestamp": "2026-05-19 17:52:01", "wheel": "rl", "wheel_label": "Hinten links", "action": "steer_left", "action_label": "Lenkung links", "verdict": "ok", "verdict_label": "Ja"} +{"timestamp": "2026-05-19 17:52:06", "wheel": "rl", "wheel_label": "Hinten links", "action": "steer_right", "action_label": "Lenkung rechts", "verdict": "ok", "verdict_label": "Ja"} +{"timestamp": "2026-05-19 17:52:09", "wheel": "rl", "wheel_label": "Hinten links", "action": "drive_forward", "action_label": "Vorwärts", "verdict": "ok", "verdict_label": "Ja"} +{"timestamp": "2026-05-19 17:52:12", "wheel": "rl", "wheel_label": "Hinten links", "action": "drive_backward", "action_label": "Rückwärts", "verdict": "ok", "verdict_label": "Ja"} +{"timestamp": "2026-05-19 17:52:20", "wheel": "rr", "wheel_label": "Hinten rechts", "action": "steer_left", "action_label": "Lenkung links", "verdict": "ok", "verdict_label": "Ja"} +{"timestamp": "2026-05-19 17:52:23", "wheel": "rr", "wheel_label": "Hinten rechts", "action": "steer_right", "action_label": "Lenkung rechts", "verdict": "ok", "verdict_label": "Ja"} +{"timestamp": "2026-05-19 17:52:26", "wheel": "rr", "wheel_label": "Hinten rechts", "action": "drive_forward", "action_label": "Vorwärts", "verdict": "ok", "verdict_label": "Ja"} +{"timestamp": "2026-05-19 17:52:29", "wheel": "rr", "wheel_label": "Hinten rechts", "action": "drive_backward", "action_label": "Rückwärts", "verdict": "ok", "verdict_label": "Ja"} +{"timestamp": "2026-05-19 17:52:34", "wheel": "cl", "wheel_label": "Mitte links", "action": "steer_left", "action_label": "Lenkung links", "verdict": "ok", "verdict_label": "Ja"} +{"timestamp": "2026-05-19 17:52:40", "wheel": "cl", "wheel_label": "Mitte links", "action": "steer_right", "action_label": "Lenkung rechts", "verdict": "not_ok", "verdict_label": "Nein"} +{"timestamp": "2026-05-19 17:52:44", "wheel": "cl", "wheel_label": "Mitte links", "action": "drive_forward", "action_label": "Vorwärts", "verdict": "ok", "verdict_label": "Ja"} +{"timestamp": "2026-05-19 17:52:47", "wheel": "cl", "wheel_label": "Mitte links", "action": "drive_backward", "action_label": "Rückwärts", "verdict": "not_ok", "verdict_label": "Nein"} +{"timestamp": "2026-05-19 17:52:52", "wheel": "cr", "wheel_label": "Mitte rechts", "action": "steer_left", "action_label": "Lenkung links", "verdict": "not_ok", "verdict_label": "Nein"} +{"timestamp": "2026-05-19 17:52:55", "wheel": "cr", "wheel_label": "Mitte rechts", "action": "steer_right", "action_label": "Lenkung rechts", "verdict": "ok", "verdict_label": "Ja"} +{"timestamp": "2026-05-19 17:52:58", "wheel": "cr", "wheel_label": "Mitte rechts", "action": "drive_forward", "action_label": "Vorwärts", "verdict": "ok", "verdict_label": "Ja"} +{"timestamp": "2026-05-19 17:53:01", "wheel": "cr", "wheel_label": "Mitte rechts", "action": "drive_backward", "action_label": "Rückwärts", "verdict": "ok", "verdict_label": "Ja"} +{"timestamp": "2026-05-19 17:53:08", "wheel": "fl", "wheel_label": "Vorne links", "action": "steer_left", "action_label": "Lenkung links", "verdict": "ok", "verdict_label": "Ja"} +{"timestamp": "2026-05-19 17:53:13", "wheel": "fl", "wheel_label": "Vorne links", "action": "steer_right", "action_label": "Lenkung rechts", "verdict": "ok", "verdict_label": "Ja"} +{"timestamp": "2026-05-19 17:53:17", "wheel": "fl", "wheel_label": "Vorne links", "action": "drive_forward", "action_label": "Vorwärts", "verdict": "ok", "verdict_label": "Ja"} +{"timestamp": "2026-05-19 17:53:20", "wheel": "fl", "wheel_label": "Vorne links", "action": "drive_backward", "action_label": "Rückwärts", "verdict": "ok", "verdict_label": "Ja"} +{"timestamp": "2026-05-19 17:53:24", "wheel": "fr", "wheel_label": "Vorne rechts", "action": "steer_left", "action_label": "Lenkung links", "verdict": "ok", "verdict_label": "Ja"} +{"timestamp": "2026-05-19 17:53:28", "wheel": "fr", "wheel_label": "Vorne rechts", "action": "steer_right", "action_label": "Lenkung rechts", "verdict": "not_ok", "verdict_label": "Nein"} +{"timestamp": "2026-05-19 17:53:31", "wheel": "fr", "wheel_label": "Vorne rechts", "action": "drive_forward", "action_label": "Vorwärts", "verdict": "ok", "verdict_label": "Ja"} +{"timestamp": "2026-05-19 17:53:35", "wheel": "fr", "wheel_label": "Vorne rechts", "action": "drive_backward", "action_label": "Rückwärts", "verdict": "ok", "verdict_label": "Ja"}