I wanted to automate certain tasks when connecting or disconnecting devices via my Linux based machine USB port. This post explains how I achieved this using python.
Problem:
I have a USB hub on my desk with a 4K HDMI port for an external monitor. Out of the box everything worked; but I needed use the LXRandR Display Settings GUI to configure the monitor settings every time I connected or disconnected the USB hub. I wanted this to happen automatically when I connected or disconnected the USB hub.
Approach:
Using pyudev you can listen to specific hardware events and add a callback to execute any task you wish. Great, I should be able to write a script to set the appropriate monitor settings and run it during the event callbacks. I should also be able to run the script at boot to run as a daemon.
Prerequisites:
Install pyudev using pip.
1 | pip install pyudev |
Implementation:
Create a new python file/project and import the pyudev libraries:
1 2 3 4 5 6 7 | #!/usr/bin/env python import glib import os from pyudev import Context, Monitor from pyudev.glib import MonitorObserver |
Instantiate and configure pyudev
.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 | # Create a device database connection context = Context() # Create a synchronous device event monitor monitor = Monitor.from_netlink(context) # Optional: apply a filter for the desired hardware monitor.filter_by(subsystem='usb') # Create a asynchronous observer for device events observer = MonitorObserver(monitor) # Connect the observer to a method of your choosing (in this case device_event()) observer.connect('device-event', device_event) # Start the monitor monitor.start() # Run an endless loop to monitor events glib.MainLoop().run() |
Define the method connected to the observer
above:
1 2 3 4 5 | def device_event(observer, device): if device.action.decode("string-escape") == "add": print 'device attached. do some work.', device elif device.action.decode("string-escape") == "remove": print 'device removed. do some work.', device |
At this point you should be able to run the script and watch the log while connecting / disconnecting USB devices from your machine. You should see the print
outs containing the device information. Using this data copy the device.device_path
unique identifier and create a variable. Using the variable you can write an if statement to ensure your desired functionality only happens for that device.
1 2 3 4 5 | usb_hub_hdmi = 'usb3/3-1/3-1.1/3-1.1:1.0' # Check for your device if usb_hub_hdmi in device.device_path.decode("string-escape"): os.system('echo do some work') |
Summary
Here’s the entire script in action. The source is available here.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 | #!/usr/bin/env python import glib import os from pyudev import Context, Monitor from pyudev.glib import MonitorObserver usb_hub_hdmi = 'usb3/3-1/3-1.1/3-1.1:1.0' def device_event(observer, device): update_monitor_settings(device) def update_monitor_settings(device): if device.action.decode("string-escape") == "add": if usb_hub_hdmi in device.device_path.decode("string-escape"): print 'set monitor on' os.system('xrandr --output DP-1 --auto --output eDP-1 --mode 1920x1080 --rate 59.93 --left-of DP-1') elif device.action.decode("string-escape") == "remove": if usb_hub_hdmi in device.device_path.decode("string-escape"): os.system('xrandr --output DP-1 --off --output eDP-1 --mode 1920x1080 --rate 59.93 --same-as DP-1') print 'set monitor off' context = Context() monitor = Monitor.from_netlink(context) monitor.filter_by(subsystem='usb') observer = MonitorObserver(monitor) observer.connect('device-event', device_event) monitor.start() glib.MainLoop().run() |