Skip to content
Shahbaz Youssefi edited this page Jun 3, 2015 · 2 revisions

Skinware with Python

Skinware is written in C, but it provides a Python interface as well. The Python interface is created with the ctypes package to translate between the C functions and types and the Python's.

The following rules hold in translating between the C names and their Python equivalents.

  • A URT type such as urt_time translates to urt.time,
  • A Skinware type such as skin_sensor_response translates to skin.sensor_response,
  • A struct such as skin_reader_callbacks translates to skin.reader_callbacks,
  • A function such as skin_service_attach translates to skin.service_attach,

Callbacks are automatically converted from Python functions to the appropriate CFUNCTYPE. The functions that take the callback then return the CFUNCTYPE object they create for the user to hold on to, unless there is no need for the callback after the function has returned. For example, a function that iterates over the sensors doesn't return the callback object, but one that creates a thread that continues to call the callback after the function has returned, does. It is very important to keep a reference to this returned object to make sure Python doesn't garbage-collect the callback object.

Currently, URT doesn't implement the URT way in Python, so signal and argument handling is left to the user. Taking the equivalent of startup(), body() and finish() functions used in the URT way, the Python program would generally look like the following:

def startup(data):
    ret = urt.init()
    if ret:
        print('failed to init URT:', ret)
        return ret

    data.skn, ret = skin.init()
    if ret:
        print('failed to initialize skin:', ret)
        urt.exit()
        return ret

    # other initialization

    return 0

def body(data):
    # spawn threads etc

def finish(data):
    # cleanup
    skin.free(data.skn)
    urt.exit()

data = Data()
ret = startup(data)
if ret:
    sys.exit(ret)

try:
    body(data)
finally:
    finish(data)

With Python, since the functions can have closures, the user_data field of the callbacks are often unnecessary. That said, here is an examples of how a driver can be created in Python, which assumes the above code exists (body() replaced of course).

import sys
import time
from random import randrange
import skin
from ctypes import *
urt = skin.urt

class Data:
    def __init__(self, skn = None):
        self.skn = skn

class Driver:
    def __init__(self, step, speed):
        self.step = step
        self.speed = speed
        # create a random skin
        self.patches = randrange(5, 11)
        self.modules = self.patches * randrange(10, 16)
        self.sensors = self.modules * randrange(8, 13)

    def details(self, driver, revived, details, unused):
        # since the skin is random, it is most likely not the same on revive, so just fail
        if revived:
            return -1
        for p in range(self.patches):
            details[0].patches[p].module_count = self.modules / self.patches
        for m in range(self.modules):
            details[0].modules[m].sensor_count = self.sensors / self.modules
        for s in range(self.sensors):
            details[0].sensors[s].uid = s
            details[0].sensors[s].type = s % 3         # give whatever type
        return 0

    def acquire(self, driver, responses, size, unused):
        resps = cast(responses, POINTER(skin.sensor_response))
        skin.driver_copy_last_buffer(driver)    # really unnecessary, because only resps[0] needs to be carried from
                                                # last buffer, but do this just for demonstration
        for i in range(size):
            resps[i] = resps[i] + self.speed if i == 0 else resps[i - 1] + self.step
        return 0

def body(data):
    driver = Driver(50, 2000)

    d, error, driver_callbacks_ref = skin.driver_add(data.skn,
                                                     skin.driver_attr(driver.patches, driver.modules, driver.sensors),
                                                     skin.writer_attr(buffer_count = 5, name = 'PYD'),
                                                     urt.task_attr(soft = True, period = 100000000),
                                                     skin.driver_callbacks(details = driver.details, acquire = driver.acquire))
    if error:
        print('failed to create driver:', error)
        return

    skin.resume(data.skn)

    print('driver up and running.  Press CTRL+C to stop...')
    try:
        while True:
            time.sleep(1)
    except KeyboardInterrupt:
        print('interrupted.  Stopping...')

The above driver creates a random skin and periodically increases the responses. You can see how the driver works by using the visualizer bundled with Skinware:

$ python driver.py

And in another shell:

$ skin_calibrate fake_fill &
$ skin_view show_nontaxel

The show_nontaxel parameter to skin_view tells it to also show non-taxel sensors, which is necessary because the sensor types given by the driver have been meaningless values. When done, terminate the calibrator with kill %1 (or fg followed by CTRL+C) and back in the original shell, CTRL+C to close the driver.

In the following, a user program that periodically prints sensor values is present, again assuming the initialization and cleanup code from above are present.

import sys
import time
from random import randrange
import skin
from ctypes import *
urt = skin.urt

class Data:
    def __init__(self, skn = None):
        self.skn = skn

def body(data):
    error = skin.load(data.skn, urt.task_attr(soft = True, period = 1000000000))
    if error:
        print('failed to load skin:', error)

    skin.resume(data.skn)

    print('printing values.  Press CTRL+C to stop...')
    try:
        while True:
            time.sleep(1)

            responses = []
            def add_responses(sensor, unused):
                responses.append(skin.sensor_get_response(sensor))
                return skin.CALLBACK_CONTINUE

            # aggregate all responses in one
            skin.for_each_sensor(data.skn, add_responses)

            # print them (even though they are a lot)
            print(responses)

    except KeyboardInterrupt:
        print('interrupted.  Stopping...')

The above example shows how the sensor responses are iterated and that the user_data parameter is unused because Python already provides a closure for the function.


Next: See more tutorials.

Clone this wiki locally