Skip to content

Instantly share code, notes, and snippets.

@stsdc
Created February 7, 2026 15:21
Show Gist options
  • Select an option

  • Save stsdc/20dcc6e83b5ae23f25523ac0c8567085 to your computer and use it in GitHub Desktop.

Select an option

Save stsdc/20dcc6e83b5ae23f25523ac0c8567085 to your computer and use it in GitHub Desktop.
#!/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