<jupyter_start><jupyter_text>IntroductionIf you are reading this document you probably have already installed `ingenialink`. If not, make sure to have Python installed (3.x recommended), and then simply open a terminal and type:```shpip install ingenialink```Note that in some operating systems you may need to install first [some dependencies](https://github.com/ingeniamc/ingenialink-python).In order to use `ingenialink` you will first need to import it like this:<jupyter_code>import ingenialink as il<jupyter_output><empty_output><jupyter_text>Furthermore, if you need to use pre-defined constants, you may also want to import the `const` module.<jupyter_code>from ingenialink import const<jupyter_output><empty_output><jupyter_text>Finally in this notebook we will also plot some servo drive variables using `matplotlib`:<jupyter_code>import matplotlib.pyplot as plt
%matplotlib inline<jupyter_output><empty_output><jupyter_text>Network and servo drivesThe first step when using `ingenialink` is to setup the network and then look for attached servo drives. The network can be seen as the information transport medium from where servo drives hang. Multiple network protocols are supported by the library.In case of E-USB protocol, Ingenia servo drives show up as serial ports, so the network candidate devices are simply all the available serial ports. You can obtain the available ones like this:<jupyter_code>il.devices(il.NET_PROT.EUSB)<jupyter_output><empty_output><jupyter_text>Once you know which one it is, simply create a network object:<jupyter_code>net = il.Network(il.NET_PROT.EUSB, '/dev/ttyACM0')<jupyter_output><empty_output><jupyter_text>Then you can perform a network scan to look for attached servo drives. In this example we will simply pick the first available:<jupyter_code>servo_ids = net.servos()
first_id = servo_ids[0]

print('Will use servo with id: {}'.format(first_id))<jupyter_output>Will use servo with id: 2<jupyter_text>Next step is to create the servo object. It needs to know the network you want to use and the servo id:<jupyter_code>servo = il.Servo(net, first_id)<jupyter_output><empty_output><jupyter_text>You can quickly obtain some servo drive information like this:<jupyter_code>servo.info<jupyter_output><empty_output><jupyter_text>DictionariesIngenia's servo drives have hundreds of registers you can read/write from/to. They are defined in the servo drive dictionary, which may differ depending on the product and firmware version. IngeniaLink supports XML dictionaries in the `IngeniaDictionary` format. You can load a dictionary by using the `Dictionary` class, or you can even give a dictionary path when creating a servo so that you can directly access registers using identifiers (see the next section). The `Dictionary` class contains a registers dictionary (`regs` property) and a categories dictionary (`cats` property). Both operate as standard Python dictionaries (i.e. you can iterate, get, etc.).<jupyter_code>dct = il.Dictionary('dict_demo.xml')

reg = dct.regs['VEL_ACT']
print(reg)

print('Labels:')
for lang, label in reg.labels.items():
    print(lang, label)<jupyter_output><Register: 0x0000606c, REG_DTYPE.S32 ∊ (-2147483648, 2147483647), REG_ACCESS.RO, REG_PHY.VEL, ST: No storage, [MOTION + VELOCITY]>
Labels:
en_US Velocity actual
ca_ES Velocitat actual
es_ES Velocidad actual<jupyter_text>Dictionaries also contain storage information, so that you can keep save the current state of all servo registers in a file and restore them back. Storage information is avaialable on each register (if available) via the `storage` property. Below it is shown how to update the storage from servo values and then store a new dictionary which contains them.<jupyter_code># load a dictionary to the servo
servo.dict_load('dict_demo.xml')

# read all register values and add them to storage
# NOTE: if the loaded dictionary contains storage information,
# you can also use `dict_storage_write` to upload the dictionary
# storage.
servo.dict_storage_read()

# store current dictionary (will now contain storage info)
servo.dict.save('dict_demo_with_storage.xml')

print(servo.dict.regs['VEL_TGT'].storage)<jupyter_output>0<jupyter_text>Dictionaries also contain register categorization, which can be helpful for displaying registers when building a GUI register explorer.<jupyter_code># List ALL categories and subcategories
print('Categories:')
print('-' * 30)

cats = dct.cats
for cat_id in cats.cat_ids:
    print('ID:', cat_id)
    print('Labels:')
    for lang, label in cats.labels(cat_id).items():
        print('\t', lang, label)
    print('Subcategories:')
    for scat_id in cats.scats(cat_id).scat_ids:
        print('\tID:', scat_id)
        print('\tLabels:')
        for lang, label in cats.scats(cat_id).labels(scat_id).items():
            print('\t\t', lang, label)
    
    print('-' * 30)

# Obtain specific register information
print('Register:', reg)
print('Category:', reg.cat_id)
print('Subcategory:', reg.scat_id)

print('Category labels:')
for lang, label in dct.cats.labels(reg.cat_id).items():
    print(lang, label)
print('Subcategory labels:')
for lang, label in dct.cats.scats(reg.cat_id).labels(reg.scat_id).items():
    print(lang, label)<jupyter_output>Categories:
------------------------------
ID: MOTION
Labels:
	 en_US Motion
Subcategories:
	ID: VELOCITY
	Labels:
		 en_US Velocity
------------------------------
Register: <Register: 0x0000606c, REG_DTYPE.S32 ∊ (-2147483648, 2147483647), REG_ACCESS.RO, REG_PHY.VEL, ST: No storage, [MOTION + VELOCITY]>
Category: MOTION
Subcategory: VELOCITY
Category labels:
en_US Motion
Subcategory labels:
en_US Velocity<jupyter_text>Accessing registersAs mentioned before, IngeniaLink supports loading a dictionary when creating a servo. If you do so, you will be able to read/write using each register unique ID as defined in the dictionary. In fact, the library accepts both string IDs or `Register` objects whenever a register is required. You can still operate without a dictionary as IngeniaLink provides abstraction for most common operations, so you will not use registers directly in most applications. Furthermore, you can also define the registers manually.You can read registers by using `read` and `raw_read` `Servo` functions. The first will take care of unit conversions if this applied to the requested register. Units are detailed in the next section. Similarly, for write you have `write` and `raw_write`.<jupyter_code># load dictionary (after creation, could be done at servo creation)
servo.dict_load('dict_demo.xml')

# dictionary instance can be obtained (e.g. for querying or inspecting)
print(servo.dict)

# read (using IDs)
print(servo.read('TORQUE_MAX'))
print(servo.raw_read('FB_POS_SENSOR'))

# write (using IDs and pre-defined Register)
servo.write('VEL_TGT', 1000)

CTL_WORD = il.Register(0x006040, il.REG_DTYPE.U16, il.REG_ACCESS.RW)
servo.raw_write(CTL_WORD, 0x06)<jupyter_output><ingenialink.dict_.Dictionary object at 0x7f95e72f2438>
0.0
0<jupyter_text>Motion controlWe all know you were waiting for this section! If you have a servo drive is to perform some kind of motion control! It is important to know at this point that **your servo drive needs to be properly tuned and configured**. This can be done using our configuration tool [MotionLab](http://ingeniamc.com/software). Operation modeIngenia servo drives can work in multiple operation modes: profile/cyclic position, profile/cycling velocity, homing, etc. Detailed information about all the available modes can be found [here](http://doc.ingeniamc.com/display/EMCL/Modes+of+operation). If you want to control position, you will likely use the profile position. When using the profile modes velocity and acceleration of your movements are controlled by a trapezoidal profiler. In cyclic modes the profiler needs to be implemented by yourself. While it gives more flexibility, it is a non-trivial task. In this notebook, only profile modes will be demonstrated.The operation mode can be set like this:<jupyter_code># set the operation mode to profile position
servo.mode = il.SERVO_MODE.PP<jupyter_output><empty_output><jupyter_text>Operation unitsIngenia servo drives operate internally using native units, that is, units relative to the available feedbacks. This means that the position is given in counts, velocity in counts per second, and so on. `ingenialink` offers the possibility to work using physical units, for example, in revolutions, degrees, rpm, etc. You can currently set the operating units of position, velocity, torque and acceleration.<jupyter_code># we want to work in degrees and RPM
servo.units_pos = il.SERVO_UNITS_POS.DEG
servo.units_vel = il.SERVO_UNITS_VEL.RPM<jupyter_output><empty_output><jupyter_text>Enabling and disabling the power stageOnce you have your mode selected, you can proceed to enable the power stage of the servo drive. Unless the power is enabled, no actions can be performed.<jupyter_code># enable power stage
servo.enable(timeout=2.)

# disable power stage
servo.disable()<jupyter_output><empty_output><jupyter_text>HomingIf working in position mode, unless you have an absolute reference sensor you will need to perform a homing every time you turn the system on so that the servo drives *knows where it is*. Multiple homing modes are available (see MotionLab). In order to perform a homing, you will simply need to set the homing mode, enable the motor and then trigger the homing operation as shown below.<jupyter_code>servo.mode = il.SERVO_MODE.HOMING
servo.enable()
servo.homing_start()
servo.homing_wait(timeout=5.)
servo.disable()<jupyter_output><empty_output><jupyter_text>Position controlServo position can be easily controlled as shown in the code below. Note that the system tolerances will need to be properly set to use the `wait_reached`.<jupyter_code>servo.mode = il.SERVO_MODE.PP

print('Current position is: {:.2f} deg'.format(servo.position))

print('Moving to 180 deg...')
servo.enable()
servo.position = 180.
servo.wait_reached(timeout=1)

print('New position is: {:.2f} deg'.format(servo.position))

servo.disable()<jupyter_output>Current position is: 0.00 deg
Moving to 180 deg...
New position is: 173.07 deg<jupyter_text>Velocity controlServo velocity can be easily controlle as shown in the code below:<jupyter_code>servo.mode = il.SERVO_MODE.PV

print('Current velocity is: {:.2f} rpm'.format(servo.velocity))

print('Setting velocity to 1000 rpm...')
servo.enable()
servo.velocity = 1000.
servo.wait_reached(timeout=3)

print('Current velocity is: {:.2f} rpm'.format(servo.velocity))

servo.disable()<jupyter_output>Current velocity is: 999.00 rpm
Setting velocity to 1000 rpm...
Current velocity is: 1000.50 rpm<jupyter_text>Observing servo drive parametersIt is always useful to obtain plots of servo operation variables. For this purpose we have to mechanisms: polling and monitoring. Below they are detailed together with some examples. PollingWhat polling does, as its name suggests, is to poll for certain registers continuously from the PC. Usually polling is used for real-time plotting or when high-accuracy is not required. When using polling, you can roughly sample one register at 500 samples/s continuously (decreasing as more registers are polled). Below a sequence of position movements is polled.<jupyter_code># create a poller and configure it 
poller = il.Poller(servo, n_ch=2)

# set sample period of 5 ms and buffer of 2k samples
poller.configure(t_s=5e-3, sz=2000)
# configure channels to poll position and velocity
poller.ch_configure(ch=0, reg='POS_ACT')
poller.ch_configure(ch=1, reg='VEL_ACT')

# perform homing
servo.mode = il.SERVO_MODE.HOMING
servo.enable()
servo.homing_start()
servo.homing_wait(timeout=5)
servo.disable()

# perform movements
servo.mode = il.SERVO_MODE.PP
servo.enable()
poller.start()

positions = [0, 90, 180, 90, 0]
for position in positions:
    servo.position = position
    servo.wait_reached(timeout=1)

servo.disable()
poller.stop()

# obtain results
t, d, lost = poller.data
if lost:
    print('Data was lost! Increase buffer size or sample time!')

# plot results
plt.figure()
plt.subplot(211)
plt.plot(t, d[0])
plt.xlabel('Time (s)')
plt.ylabel('Position (deg)')

plt.subplot(212)
plt.plot(t, d[1])
plt.xlabel('Time (s)')
plt.ylabel('Velocity (rpm)')

plt.show()<jupyter_output><empty_output><jupyter_text>MonitoringMonitoring is done on the servo drive side and allows to sample up to 10000 samples/s but, it is single shot, the number of samples is limited and supports up to 4 channels. Furthermore, monitoring offers trigger capabilities. For more details about monitoring capabilities, see this [page](http://doc.ingeniamc.com/display/EMCL/Monitoring). Below you can find an example that captures the rising curve of velocity. This may be useful, for example, to see if the system is properly tuned.<jupyter_code># target velocity (rpm)
VEL_TGT_VAL = 1000

# create monitor and configure it
monitor = il.Monitor(servo)

# set sample period to 1 ms, maximum of 200 samples
monitor.configure(t_s=1e-3, max_samples=200)
# monitor velocity
monitor.ch_configure(ch=0, reg='VEL_ACT')
# configure trigger to 90% of the target velocity
monitor.trigger_configure(il.MONITOR_TRIGGER.POS, source='VEL_ACT',
                          th_pos=VEL_TGT_VAL * 0.9)

# enable servo
servo.mode = il.SERVO_MODE.PV
servo.enable()
monitor.start()

servo.velocity = VEL_TGT_VAL

monitor.wait(timeout=3)
servo.disable()

# plot results
t, d = monitor.data

plt.plot(t, d[0])
plt.xlabel('Time (s)')
plt.ylabel('Velocity (rpm)')
plt.ylim([800, 1200])
plt.show()<jupyter_output><empty_output>
StarcoderdataJupyterStructuredCleanDedup