Created
February 7, 2026 15:21
-
-
Save stsdc/20dcc6e83b5ae23f25523ac0c8567085 to your computer and use it in GitHub Desktop.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| #!/usr/bin/env python3 | |
| # motor_control.py | |
| import gi | |
| gi.require_version('Gtk', '4.0') | |
| gi.require_version('Adw', '1') | |
| from gi.repository import Gtk, Adw, GLib | |
| import fcntl | |
| import struct | |
| import os | |
| # ioctl definitions | |
| MOTOR_MAGIC = 0xD3 | |
| def _IOW(type, nr, size): | |
| return (2 << 30) | (ord(type) << 8) | (nr << 0) | (size << 16) | |
| def _IOR(type, nr, size): | |
| return (1 << 30) | (ord(type) << 8) | (nr << 0) | (size << 16) | |
| MOTOR_IOC_SET_AUTORUN = 0x4001d301 | |
| MOTOR_IOC_SET_MANUALRUN = 0x400cd302 | |
| MOTOR_IOC_GET_REMAIN_TIME = 0x8008d310 | |
| MOTOR_IOC_GET_STATE = 0x8004d311 | |
| # Constants | |
| UP = 1 | |
| DOWN = 0 | |
| STATE_NAMES = { | |
| 0: "STILL", | |
| 1: "SPEEDUP", | |
| 2: "FULLSTEAM", | |
| 3: "SLOWDOWN", | |
| 4: "UNIFORMSPEED" | |
| } | |
| class MotorControlWindow(Adw.ApplicationWindow): | |
| def __init__(self, *args, **kwargs): | |
| super().__init__(*args, **kwargs) | |
| self.set_title("DRV8846 Motor Control") | |
| self.set_default_size(400, 500) | |
| try: | |
| self.motor_fd = os.open("/dev/drv8846_dev", os.O_RDWR) | |
| except Exception as e: | |
| print(f"Error opening /dev/drv8846_dev: {e}") | |
| self.motor_fd = None | |
| # Main box | |
| main_box = Gtk.Box(orientation=Gtk.Orientation.VERTICAL, spacing=20) | |
| main_box.set_margin_top(20) | |
| main_box.set_margin_bottom(20) | |
| main_box.set_margin_start(20) | |
| main_box.set_margin_end(20) | |
| # Status group | |
| status_group = Adw.PreferencesGroup() | |
| status_group.set_title("Motor Status") | |
| # State row | |
| state_row = Adw.ActionRow() | |
| state_row.set_title("Current State") | |
| self.state_label = Gtk.Label(label="Unknown") | |
| state_row.add_suffix(self.state_label) | |
| status_group.add(state_row) | |
| # Remaining time row | |
| time_row = Adw.ActionRow() | |
| time_row.set_title("Remaining Time") | |
| self.time_label = Gtk.Label(label="0 ms") | |
| time_row.add_suffix(self.time_label) | |
| status_group.add(time_row) | |
| main_box.append(status_group) | |
| # # Auto Run group | |
| auto_group = Adw.PreferencesGroup() | |
| auto_group.set_title("Auto Run") | |
| auto_group.set_description("Predefined motor profiles") | |
| auto_box = Gtk.Box(orientation=Gtk.Orientation.HORIZONTAL, spacing=10) | |
| auto_box.set_halign(Gtk.Align.CENTER) | |
| btn_auto_up = Gtk.Button(label="Auto UP") | |
| btn_auto_up.add_css_class("suggested-action") | |
| btn_auto_up.connect("clicked", self.on_auto_run, UP) | |
| btn_auto_down = Gtk.Button(label="Auto DOWN") | |
| btn_auto_down.add_css_class("destructive-action") | |
| btn_auto_down.connect("clicked", self.on_auto_run, DOWN) | |
| auto_box.append(btn_auto_up) | |
| auto_box.append(btn_auto_down) | |
| auto_group.add(auto_box) | |
| main_box.append(auto_group) | |
| # Manual Run group | |
| manual_group = Adw.PreferencesGroup() | |
| manual_group.set_title("Manual Control") | |
| manual_group.set_description("Direct speed and duration control") | |
| # Direction selector | |
| dir_row = Adw.ActionRow() | |
| dir_row.set_title("Direction") | |
| self.dir_switch = Gtk.Switch() | |
| self.dir_switch.set_active(True) # False = UP, True = DOWN | |
| dir_label = Gtk.Label(label="UP") | |
| self.dir_switch.connect("notify::active", lambda sw, _: dir_label.set_text("UP" if sw.get_active() else "DOWN")) | |
| dir_row.add_suffix(dir_label) | |
| dir_row.add_suffix(self.dir_switch) | |
| manual_group.add(dir_row) | |
| # Duration spinner | |
| dur_row = Adw.ActionRow() | |
| dur_row.set_title("Duration (ms)") | |
| self.duration_spin = Gtk.SpinButton() | |
| self.duration_spin.set_range(10, 10000) | |
| self.duration_spin.set_value(500) | |
| self.duration_spin.set_increments(10, 100) | |
| dur_row.add_suffix(self.duration_spin) | |
| manual_group.add(dur_row) | |
| # Speed/Period spinner | |
| speed_row = Adw.ActionRow() | |
| speed_row.set_title("Period (ns)") | |
| speed_row.set_subtitle("Lower = Faster") | |
| self.period_spin = Gtk.SpinButton() | |
| self.period_spin.set_range(4000, 1000000) # 4µs to 1ms | |
| self.period_spin.set_value(50000) # 50µs default | |
| self.period_spin.set_increments(1000, 10000) | |
| speed_row.add_suffix(self.period_spin) | |
| manual_group.add(speed_row) | |
| # Manual run button | |
| btn_manual = Gtk.Button(label="Run Manual") | |
| btn_manual.add_css_class("pill") | |
| btn_manual.set_halign(Gtk.Align.CENTER) | |
| btn_manual.set_margin_top(10) | |
| btn_manual.connect("clicked", self.on_manual_run) | |
| manual_group.add(btn_manual) | |
| main_box.append(manual_group) | |
| self.set_content(main_box) | |
| # Start status update timer | |
| GLib.timeout_add(1000, self.update_status) | |
| def on_auto_run(self, button, direction): | |
| if self.motor_fd is None: | |
| print("Motor device not opened") | |
| return | |
| try: | |
| data = struct.pack('B', direction) | |
| fcntl.ioctl(self.motor_fd, MOTOR_IOC_SET_AUTORUN, data) | |
| print(f"Auto run: {'UP' if direction == DOWN else 'DOWN'}") | |
| except Exception as e: | |
| print(f"Error: {e}") | |
| def on_manual_run(self, button): | |
| if self.motor_fd is None: | |
| print("Motor device not opened") | |
| return | |
| try: | |
| direction = UP if self.dir_switch.get_active() else DOWN | |
| duration_ms = int(self.duration_spin.get_value()) | |
| period_ns = int(self.period_spin.get_value()) | |
| # Pack struct op_parameter: uint8_t dir, int duration_ms, int period_ns | |
| data = struct.pack('Bii', direction, duration_ms, period_ns) | |
| fcntl.ioctl(self.motor_fd, MOTOR_IOC_SET_MANUALRUN, data) | |
| print(f"Manual run: dir={'UP' if direction else 'DOWN'}, duration={duration_ms}ms, period={period_ns}ns") | |
| except Exception as e: | |
| print(f"Error: {e}") | |
| def update_status(self): | |
| if self.motor_fd is None: | |
| return True | |
| try: | |
| # Get state | |
| state_buf = bytearray(4) | |
| fcntl.ioctl(self.motor_fd, MOTOR_IOC_GET_STATE, state_buf) | |
| state = struct.unpack('i', state_buf)[0] | |
| self.state_label.set_text(STATE_NAMES.get(state, f"Unknown ({state})")) | |
| # Get remaining time | |
| time_buf = bytearray(8) | |
| fcntl.ioctl(self.motor_fd, MOTOR_IOC_GET_REMAIN_TIME, time_buf) | |
| remaining_ms = struct.unpack('q', time_buf)[0] | |
| self.time_label.set_text(f"{remaining_ms} ms") | |
| except Exception as e: | |
| print(f"Status update error: {e}") | |
| return True # Continue timer | |
| def do_close_request(self): | |
| if self.motor_fd is not None: | |
| os.close(self.motor_fd) | |
| return False | |
| class MotorControlApp(Adw.Application): | |
| def __init__(self): | |
| super().__init__(application_id="com.example.motorcontrol") | |
| def do_activate(self): | |
| win = MotorControlWindow(application=self) | |
| win.present() | |
| if __name__ == "__main__": | |
| app = MotorControlApp() | |
| app.run(None) |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment