<jupyter_start><jupyter_text>Basic Motion<jupyter_code>import sys
sys.path.insert(0, '/opt/ros/noetic/lib/python3/dist-packages')
import rospy
rospy.init_node('motion_control')

## create an instance of a robot
from jetbot import Bones
robot = Bones()
robot.observe(robot.fwd_cb,'fwdspd')
robot.observe(robot.ang_cb,'angspd')

## create an auto recurring timer and associated callback which triggers ever 250ms. The purpose is to throttle the transfer of twist 
## commands to the arduino.  
from jetbot import Mytimer

def ard_callback():
    robot.twist()

ard_timer = Mytimer(0.25, ard_callback)
ard_timer.start()
import ipywidgets.widgets as widgets
from IPython.display import display

# create two sliders with range [-0.2, 0.2]
left_slider = widgets.FloatSlider(description='left', min=-0.2, max=0.2, step=0.02, orientation='vertical')
right_slider = widgets.FloatSlider(description='right', min=-0.4, max=0.4, step=0.02, orientation='vertical')

# create a horizontal box container to place the sliders next to each other
slider_container = widgets.HBox([left_slider, right_slider])

# display the container in this cell's output
display(slider_container)<jupyter_output><empty_output><jupyter_text>Now that we've imported the ``Robot`` class we can initialize the class *instance* as follows.<jupyter_code>import traitlets
left_link = traitlets.dlink((left_slider, 'value'), (robot, 'fwdspd'))
right_link = traitlets.dlink((right_slider, 'value'), (robot, 'angspd'))
robot.stop()
robot.forward(0.08)<jupyter_output><empty_output><jupyter_text>Maybe we only want to run the robot for a set period of time.  For that, we can use the Python ``time`` package.<jupyter_code>import time<jupyter_output><empty_output><jupyter_text>This package defines the ``sleep`` function, which causes the code execution to block for the specified number of secondsbefore running the next command.  Try the following to make the robot turn left only for half a second. You should see the robot move in the same exact way!  Link motors to traitlets A really cool feature about these [traitlets](https://github.com/ipython/traitlets) is that we can also link them to other traitlets!  This is super handy because Jupyter Notebooks allow usto make graphical ``widgets`` that use traitlets under the hood.  This means we can attachour motors to ``widgets`` to control them from the browser, or just visualize the value.To show how to do this, let's create and display two sliders that we'll use to control our motors. Now try dragging the sliders (slowly at first).  You should see the respective motor turn!The ``link`` function that we created above actually creates a bi-directional link!  That means,if we set the motor values elsewhere, the sliders will update!  Try executing the code block below<jupyter_code>robot.forward(0.1)
time.sleep(2.0)
robot.stop()<jupyter_output><empty_output><jupyter_text>You should see the sliders respond to the motor commands!  If we want to remove this connection we can call the``unlink`` method of each link.<jupyter_code>#left_link.unlink()
#right_link.unlink()<jupyter_output><empty_output><jupyter_text>But what if we don't want a *bi-directional* link, let's say we only want to use the sliders to display the motor values,but not control them.  For that we can use the ``dlink`` function.  The left input is the ``source`` and the right input is the ``target`` Now try moving the sliders.  You should see that the robot doesn't respond.  But when set the motors using a different method,the sliders will update and display the value!  Attach functions to events Another way to use traitlets, is by attaching functions (like ``forward``) to events.  Thesefunctions will get called whenever a change to the object occurs, and will be passed some information about that changelike the ``old`` value and the ``new`` value.  Let's create and display some buttons that we'll use to control the robot.<jupyter_code># create buttons
button_layout = widgets.Layout(width='100px', height='80px', align_self='center')
stop_button = widgets.Button(description='stop', button_style='danger', layout=button_layout)
forward_button = widgets.Button(description='forward', layout=button_layout)
backward_button = widgets.Button(description='backward', layout=button_layout)
left_button = widgets.Button(description='left', layout=button_layout)
right_button = widgets.Button(description='right', layout=button_layout)

# display buttons
middle_box = widgets.HBox([left_button, stop_button, right_button], layout=widgets.Layout(align_self='center'))
controls_box = widgets.VBox([forward_button, middle_box, backward_button])
display(controls_box)<jupyter_output><empty_output><jupyter_text>You should see a set of robot controls displayed above!  But right now they wont do anything.  To do thatwe'll need to create some functions that we'll attach to the button's ``on_click`` event.<jupyter_code>def stop(change):
    robot.stop()
    
def step_forward(change):
    robot.forward(0.4)
    time.sleep(1.0)
    robot.stop()

def step_backward(change):
    robot.backward(0.4)
    time.sleep(1.0)
    robot.stop()

def step_left(change):
    robot.left(0.3)
    time.sleep(1.0)
    robot.stop()

def step_right(change):
    robot.right(0.3)
    time.sleep(1.0)
    robot.stop()<jupyter_output><empty_output><jupyter_text>Now that we've defined the functions, let's attach them to the on-click events of each button<jupyter_code># link buttons to actions
stop_button.on_click(stop)
forward_button.on_click(step_forward)
backward_button.on_click(step_backward)
left_button.on_click(step_left)
right_button.on_click(step_right)<jupyter_output><empty_output><jupyter_text>Now when you click each button, you should see the robot move!  Heartbeat KillswitchHere we show how to connect a 'heartbeat' to stop the robot from moving.  This is a simple way to detect if the robot connection is alive.  You can lower the slider below to reduce the period (in seconds) of the heartbeat.  If a round-trip communication between browser cannot be made within two heartbeats, the '`status`' attribute of the heartbeat will be set ``dead``.  As soon as the connection is restored, the ``status`` attribute will return to ``alive``.<jupyter_code>from jetbot import Heartbeat

heartbeat = Heartbeat()

# this function will be called when heartbeat 'alive' status changes
def handle_heartbeat_status(change):
    if change['new'] == Heartbeat.Status.dead:
        robot.stop()
        
heartbeat.observe(handle_heartbeat_status, names='status')

period_slider = widgets.FloatSlider(description='period', min=0.001, max=0.5, step=0.01, value=0.5)
traitlets.dlink((period_slider, 'value'), (heartbeat, 'period'))

display(period_slider, heartbeat.pulseout)<jupyter_output><empty_output><jupyter_text>Try executing the code below to start the motors, and then lower the slider to see what happens.  You can also try disconnecting your robot or PC.<jupyter_code># robot.left(0.2) 

# now lower the `period` slider above until the network heartbeat can't be satisfied<jupyter_output><empty_output>
{'fraction_non_alphanumeric': 0.09387351778656126, 'fraction_numerical': 0.00945793337097685, 'mean_word_length': 5.779904306220096, 'pattern_counts': {'":': 0, '<': 41, '<?xml version=': 0, '>': 41, 'https://': 1, 'lorem ipsum': 0, 'www.': 0, 'xml': 0}, 'pii_count': 0, 'substrings_counts': 0, 'word_list_counts': {'cursed_substrings.json': 0, 'profanity_word_list.json': 0, 'sexual_word_list.json': 0, 'zh_pornsignals.json': 0}}
{'chain_length': '10', 'dir': 'jupyter-structured-clean-dedup', 'id': '587099', 'license': 'MIT', 'n_tokens_mistral': 2168, 'n_tokens_neox': 2111, 'n_words': 763, 'path': 'My_notebooks/basic_motion/basic_motion.ipynb', 'repo_name': 'geoc1234/jetbot', 'size': '7084'}