I was going to put this out as video, but I was also going to have it ready at the beginning of April, so I am dumping it here now before it disappears completely.
In my
previous video I detailed how to write automation code that will create render montages for saved Blender files. Blender supports making these iterative save files from the "File/Save As" menu, but it requires you to
Stop what you are doing, find the File and Save As menus then ensure the mouse is over the Save As dialog and click the [+] and then click Save. You can shortcut some of these with CTRL+SHIFT+S to bring up the dialog and press Plus on the Numpad, then Enter to avoid clicking the save button.
What if I had a single button that would do all that for me? I could get on with the fun of Blendering!
After swapping our old push button doorbell for a flashy new Ring doorbell, I had one spare 11 year old push button. I also had two Raspberry Pico boards and what would you know, they are about the same size as the doorbell casing.
Novaspirit Tech had done a couple of videos on the Pico, including using it as a fake USB mouse to cheat a mobile game. Could I get the Pico to be a USB mouse and a USB keyboard? And could I send the actions from the Pico to my Blender running machine?
Lets find out, but long story short, yes I did.
If you want to understand how I put it all together and why the mouse emulation speed is so slow keep
watching going, otherwise have a nice day, you are likely to find the next bit boring.
I decided to run all the development from a Raspberry Pi 4, thinking all the tooling would be set up for minimal hassle. I regret this decision, it should have worked on any Linux machine.
I did update Thonny before starting as that is the only tool we really need.
There certainly are some other pre-requisites;
First we need to install Circuit Python that has the USB HID support
go to circuitpython.org and then select Raspberry Pi Pico
https://circuitpython.org/board/raspberry_pi_pico/
The chip on the Pico is RP2040 and I got the the latest version at the time was 6.2.0-beta.3
Adafruit CircuitPython 6.2.0-beta.3 on 2021-03-04; Raspberry Pi Pico with rp2040
Plug the Pico in while holding down the BootSel button, let go and you have a new drive attached to your computer
Drag and drop the UF2 file you just downloaded to the Pico drive and it will reboot and come back ready in less than a second.
Thonny will now be able to talk to the Pico.
Occasionally Thonny did not find the Pico first time and I had to go into settings and select it manually
Couldn't find the device automatically.
Check the connection (making sure the device is not in bootloader mode) or choose
"Configure interpreter" in the interpreter menu (bottom-right corner of the window)
to select specific port or another interpreter.
Next
we want the Adafruit USB HID library, this gives us lots of pre-written functions that make life a lot easier.
Download the NPY Release from Adafruit Github Circuit Python HID repository
https://github.com/adafruit/Adafruit_CircuitPython_HID
Unzip it and copy the lib/adafruit_hid file back onto the Pico that now has it's own lib folder, since we installed Circuit Python on it.
Now I am going to take you through the code I wrote, but you can just get it from GitHub
https://github.com/robgithub/pico_blender_doorbell
Import all our required libraries
import time #for delays
import digitalio # so we can use the Pico Pins
import board # so we can reference the Pico pins
import usb_hid # go in to USB Human Interface Device mode
from adafruit_hid.mouse import Mouse # do mouse stuff
from adafruit_hid.keyboard import Keyboard # do keyboard stuff
from adafruit_hid.keycode import Keycode # reference all the special key codes
Move the mouse by increments with delays, bit long winded, but if you stay until the end you will find out why I put it all in.
def move_mouse_by(x, y, mouse, delay, jump):
print("MMB({0}, {1})".format(x, y))
while (x != 0 or y != 0):
mx = limit(x, jump)
my = limit(y, jump)
mouse.move(mx, my)
x -= mx
y -= my
time.sleep(delay)
A limit function, so that either the value passed or the maximum is returned
def limit(value, by):
return min(by, max(-by, value))
Mousing around, move the mouse top left by the screen width and height, forcing it into a corner.
Then move it to half the width and height, ideally the middle of the screen.
def center_mouse(screen, mouse):
move_mouse_by(-screen["width"], -screen["height"], mouse, 0, 100)
move_mouse_by(int(screen["width"] /2), int(screen["height"] /2), mouse, 0.02, 5)
A loop that just sits and waits for the button to be pressed or wires touched
Sleeps for 10 seconds after everything has finished to avoid accidental re-saves
def button_loop(led, btn, keystrokes, keyboard, mouse, screen):
while True:
led.value = True
if btn.value:
print("Btn pressed")
led.value = False
do_blender_save_as(keystrokes, keyboard, screen, mouse)
time.sleep(10)
time.sleep(0.1)
Send our keyboard key sequence, one press at a time, including combinations
def send_keystrokes(keystrokes, keyboard, delay):
for keystroke in keystrokes:
if type(keystroke) is list:
keyboard.send(*keystroke)
else:
keyboard.send(keystroke)
time.sleep(delay)
Do the Blender Save As
coordinates calling the other mouse and keyboard functions to perform the Blender Save As process
def do_blender_save_as(keystrokes, keyboard, screen, mouse):
center_mouse(screen, mouse)
send_keystrokes(keystrokes, keyboard, 1)
and the main function to set everything up
Tell the Pico which pin we are using for the button and how it is wired
Tell the Pico we want to use the Power LED
Configure our key stroke patterns
define the screen size and kick off the main button loop
def main():
# Set up a input button on GPIO Pin 15
btn_pin = board.GP15
btn = digitalio.DigitalInOut(btn_pin)
btn.direction = digitalio.Direction.INPUT
btn.pull = digitalio.Pull.DOWN
# Set up the Pico onboard LED
led = digitalio.DigitalInOut(board.GP25)
led.direction = digitalio.Direction.OUTPUT
# Create Mouse and Keybord devices
mouse = Mouse(usb_hid.devices)
keyboard = Keyboard(usb_hid.devices)
# List the keystrokes we want to initiate on button press
keystrokes = [[Keycode.CONTROL, Keycode.SHIFT, Keycode.S], Keycode.KEYPAD_PLUS, Keycode.ENTER]
# define the target screen size
screen = {"width":1920, "height":1080}
button_loop(led, btn, keystrokes, keyboard, mouse, screen)
While writing your own code you get stuck with the Pico not responding, click in the Thonny command window and Press CTRL+C
Device is busy or does not respond. Your options:
- wait until it completes current work;
- use Ctrl+C to interrupt current work;
- use Stop/Restart to interrupt more and enter REPL.
To make sure I was sending the correct key codes from the Pico I ran xev, which shows exactly which key code are being sent.
The tricky part was the mouse movement and why it is currently so slow.
Every time I tested the mouse moving code with small jumps it seemed to to work fine, but the move to the middle of the screen would always overshoot.
I will not bore you with the details, but it turns out that nearly all modern operating systems since 2000 have had mouse acceleration curves built in.
I tried one big jump, lots of little jumps, but got the same overshoot, only when I added in significant delays did it behave.
I got to point where I was running the code over and over again and tracking what jump I was making and what pixel position the mouse was ending up at
while true; do xdotool getmouselocation;sleep 0.2;done
In theory an updated USB HID library could tell the mouse where to BE instead of where to GO and bypass this issue.
The Pico almost fitted exactly in the doorbell casing.
"Almost" was fixed with a little bit of dremeling
then I super glued a piece of plastic packaging over the back. Waited for it to dry out and the cut away the excess.
Cost;
Pico 3.60
Cable - had one spare
Doorbell - was going in the bin (around 3 new)
(or you can just push two wires together)
It really is a handy piece of kit and I have already used it 34 times during the making of the intro video and I intend to continue to use it for future Blenderings
OK, so that was a bit of brain dump, but it would have made a reasonable video, just do not have the time and I do not see the value.
There were actually a load of videos and still images created for the video :D