Consider I want to setup a gamepad controller to control a simulated robot:
gamepad = ipywidgets.Controller()
gamepad.buttons[0].observe(lambda data: self.robot.device["gripper"].open())
gamepad.buttons[1].observe(lambda data: self.robot.device["gripper"].close())
gamepad.axes[1].observe(lambda data: self.robot.move(-data["new"], 0), 'value')
But I can't do that (I guess) because the gamepad hasn't been initialized yet. When should I hook up the buttons to the functions? Is there a callback for after the controller has been initialized?
BTW, here is the simulator: https://github.com/Calysto/jyro/blob/master/docs/Jyro%20Simulator.ipynb
Create the controller in one cell and display it. It will ask you to press a button. Then the axes and buttons will be populated.
Basically, for security, the browser does not expose the gamepad until you press a button. After you press a button, the widget can get the gamepad information.
Ok, thanks. But one might want to write a single cell "application."
This appears to work. Any problems with this? Should there be a special method for this?
gamepad = ipywidgets.Controller()
def init_gamepad(data):
if len(data["owner"].buttons) != 0:
data["owner"].buttons[0].observe(lambda data: self.robot.device["gripper"].open())
data["owner"].buttons[1].observe(lambda data: self.robot.device["gripper"].close())
data["owner"].axes[1].observe(lambda data: self.robot.move(-data["new"], 0), 'value')
data["owner"].axes[0].observe(lambda data: self.robot.move(0, -data["new"]), 'value')
gamepad.observe(init_gamepad)
I guess I could .unobserve() it too after setting the button observers.
Let's make this supported by the controller itself...
@jasongrout hold on, I have a pattern for that. Posting in a few minutes.
The flight simulator example does:
pad.links = []
def setup():
if pad.connected:
pad.links.append(dlink((pad.axes[1], 'value'), (fly_controls, 'pitch'), affine(0.0, factor)))
pad.links.append(dlink((pad.axes[0], 'value'), (fly_controls, 'roll'), affine(0.0, -factor)))
pad.links.append(dlink((pad.axes[3], 'value'), (fly_controls, 'forward_speed'), affine(0.0, 2 * factor)))
pad.links.append(dlink((pad.axes[2], 'value'), (fly_controls, 'yaw'), affine(0.0, factor)))
pad.links.append(dlink((pad.buttons[5], 'value'), (surf, 'scale'), lambda x: (10, 10, 1 - x)))
if not pad.connected:
for l in pad.links:
l.unlink()
pad.links = []
pad.observe(setup, names=['connected'])
setup()
I can add a Controller.setup(connection, disconnection) method which implements that logic.
Oh, I forgot about the .connected observable attribute. I'd rather just observe that, like you did above, without adding another function. That way we're consistent in how to trigger actions - just use observe.
(Untested) setup method. I don't have a controller handy to test it right now.
def setup(self, connection, disconnection):
def cb(change):
if self.connected:
connection(self)
else:
disconnection(self)
self.observe(cb, names=['connected'])
cb(self)
The previous example then becomes
pad.links = []
def connection(pad):
pad.links.append(dlink((pad.axes[1], 'value'), (fly_controls, 'pitch'), affine(0.0, factor)))
pad.links.append(dlink((pad.axes[0], 'value'), (fly_controls, 'roll'), affine(0.0, -factor)))
pad.links.append(dlink((pad.axes[3], 'value'), (fly_controls, 'forward_speed'), affine(0.0, 2 * factor)))
pad.links.append(dlink((pad.axes[2], 'value'), (fly_controls, 'yaw'), affine(0.0, factor)))
pad.links.append(dlink((pad.buttons[5], 'value'), (surf, 'scale'), lambda x: (10, 10, 1 - x)))
def disconnection(pad):
for l in pad.links:
l.unlink()
pad.links = []
pad.setup(connection, disconnection)
I like the idea of adding a setup function and some documentation.
I like it! Thanks!
I'm still -1 on having a setup function (since we already have a canonical way to trigger functions using observe), but definitely +1 on documentation for the idiom. The setup function means that it is hard to impossible to disconnect the listener from the connected event.
(Also, cb(self) is incorrect - self is not a change event.)
(Also, cb(self) is incorrect - self is not a change event.)
I think that it is correct. The connection and disconnection callbacks take an instance of pad and not the change event. This makes it easier to call with no event in the beginning.
You're already passing in self to the connection/disconnection. Calling cb itself with self is the issue.
This works for me.
Although, it is important to note that the controller callback do suppress printing out. But it definitely execute the actions in the callback. Take for instance, controlling robot arm both in simulation and in real-world.
`
x=widgets.Controller(index=1)
display(x)
def callbackFunc_roll(change):
#print(change['owner'])
#print('n')
#for joy_stick_index, axis in enumerate(change['owner'].axes):
#print(axis.value)
for button_index, button in enumerate(change['owner'].buttons):
if(button_index==12 and button.value==1):
# control something
if(button_index==13 and button.value==1):
# control something
if(button_index==14 and button.value==1):
# control something
if(button_index==15 and button.value==1):
# control something
print(change)
x.observe(callbackFunc_roll)
`
But if you really want to see the content of the callback, you could try calling the close method.
print(x.close())
Most helpful comment
The flight simulator example does: