def __init__(self, device, channels, code):
        """ 

		One single six-axis sensor.

		Device: An instance of PAIO.AIODevice()
		Channels: The channel indicies for the sensor in the following order:
			[forceX, forceY, forceZ, torqueX, torqueY, torqueZ] 

		"""

        self.device = device
        self.channels = channels
        self.code = code

        # Dirty ctypes list for device interface
        self._measurements_c = (c_float * 6)()

        # Good Python list for public interface
        self.measurements = [0, 0, 0, 0, 0, 0]

        self.zeroes = [0, 0, 0, 0, 0, 0]

        # It is always assumed that total_force is updated alongside centre_of_pressure,
        # and that it is ready to go whenever the observer is notified.

        self.total_force = 0
        self.centre_of_pressure = Observable([0, 0])
Example #2
0
    def __init__(self, generator, fp):
        KillableThread.__init__(self)

        self.fp = fp

        self.fps = 10
        self.generator = generator

        self.seconds_per_point = 10
        self.seconds_between_points = 5

        self._current_time = 0

        self._currently_sampling = Observable()
        self._currently_sampling.set(False)

        self._time_when_sampling_started = 0
        self._time_when_sampling_stopped = 0
Example #3
0
    def __init__(self):
        threading.Thread.__init__(self)

        self.dead = False
        self.fps = 60

        # This lock is necessary so that we do not close the pygame display while
        # the loop() method is running; doing so will result in a "video system not
        # initialized" error.

        # We also use the lock to prevent calling pygame.init() and pygame.quit()
        # concurrently.

        self.pygame_lock = threading.Lock()

        self._draw_tasks_lock = threading.Lock()
        self._draw_tasks = []

        self.keypress = Observable()
Example #4
0
	def __init__(self, sensor, mat = None):
		"""

		@argument mat - A 3x3 matrix to transform the vector [x; y; 1] into world coordinates.
		The matrix [[1, 0, 0], [0, 1, 0], [0, 0, 1]] corresponds to the identity.

		"""

		self.sensor = sensor

		if mat == 'i':
			self.mat = [[1, 0, 0], [0, 1, 0], [0, 0, 1]]
		elif mat == 'z':
			self.mat = [[0, 0, 0], [0, 0, 0], [0, 0, 0]]
		else:
			self.mat = mat

		self.total_force = 0
		self.centre_of_pressure = Observable([0, 0])

		sensor.centre_of_pressure.add_listener(self.on_update)
Example #5
0
class CalibrationProgramThread(KillableThread):
    """

	This thread is a calibration utility that takes many samples per second,
	and automatically switches through the grid points.

	"""
    def __init__(self, generator, fp):
        KillableThread.__init__(self)

        self.fp = fp

        self.fps = 10
        self.generator = generator

        self.seconds_per_point = 10
        self.seconds_between_points = 5

        self._current_time = 0

        self._currently_sampling = Observable()
        self._currently_sampling.set(False)

        self._time_when_sampling_started = 0
        self._time_when_sampling_stopped = 0

    def loop(self):

        if not self.dead:

            self._current_time += 1.0 / self.fps

        if self._currently_sampling.get():

            # Stop sampling after a certain time

            twss = self._time_when_sampling_started

            if self._current_time - twss > self.seconds_per_point:

                # Disable sampling

                self.fp.forces_after_calibration.remove_listener(
                    self.generator.take_sample)

                if not self.generator.grid.hasMorePoints:

                    self.kill()

                self.generator.grid.next()

                self._time_when_sampling_stopped = self._current_time
                self._currently_sampling.set(False)

        else:

            # Start sampling after certain time

            twss = self._time_when_sampling_stopped

            if self._current_time - twss > self.seconds_between_points:

                # Enable sampling

                self.fp.forces_after_calibration.add_listener(
                    self.generator.take_sample)

                self._time_when_sampling_started = self._current_time
                self._currently_sampling.set(True)
Example #6
0
class PyGameThread(threading.Thread):
    """

	Invoke a thread of drawing pygame graphics in a new window.
	
	"""

    options = pygame.HWSURFACE | pygame.DOUBLEBUF | pygame.RESIZABLE

    def __init__(self):
        threading.Thread.__init__(self)

        self.dead = False
        self.fps = 60

        # This lock is necessary so that we do not close the pygame display while
        # the loop() method is running; doing so will result in a "video system not
        # initialized" error.

        # We also use the lock to prevent calling pygame.init() and pygame.quit()
        # concurrently.

        self.pygame_lock = threading.Lock()

        self._draw_tasks_lock = threading.Lock()
        self._draw_tasks = []

        self.keypress = Observable()

    def kill(self):

        with self.pygame_lock:

            self.dead = True
            pygame.quit()

    def add_draw_task(self, t):

        with self._draw_tasks_lock:
            self._draw_tasks.append(t)

    @_exception_wrapper
    def run(self):

        with self.pygame_lock:

            pygame.init()

            self.screen = pygame.display.set_mode((500, 500), self.options)
            clock = pygame.time.Clock()

            pygame.display.set_caption(
                'Six-Axis Force Sensors - Weighted Centre of Pressure')

            s = pygame.Surface((32, 32))
            pygame.display.set_icon(s)

        while True:

            clock.tick(60)

            with self.pygame_lock:

                if self.dead:
                    return

                self.loop()

    def loop(self):

        pygame.event.pump()

        for event in pygame.event.get():

            if event.type == pygame.QUIT:

                self.pygame_lock.release()
                self.kill()

                return

            elif event.type == pygame.VIDEORESIZE:

                self.screen = pygame.display.set_mode(event.dict['size'],
                                                      self.options)

            elif event.type == pygame.KEYDOWN:

                self.keypress.set(event.key)

        self.screen.fill((255, 255, 255))

        width = self.screen.get_width()
        height = self.screen.get_height()

        # Do some drawing

        with self._draw_tasks_lock:
            for task in self._draw_tasks:
                task(width, height, self.screen, pygame)

        # Double buffer flip puts drawn graphics on the screen

        pygame.display.flip()

    @staticmethod
    def query_exceptions():

        while not _exceptions.empty():

            e, traceback = _exceptions.get()

            # One could replace this with a non-terminating utility, to print
            # multiple errors; otherwise there isn't much use for the queue.

            sys.stderr.write(traceback)

            raise e

            e.task_done()
class SixAxis(object):
    def __init__(self, device, channels, code):
        """ 

		One single six-axis sensor.

		Device: An instance of PAIO.AIODevice()
		Channels: The channel indicies for the sensor in the following order:
			[forceX, forceY, forceZ, torqueX, torqueY, torqueZ] 

		"""

        self.device = device
        self.channels = channels
        self.code = code

        # Dirty ctypes list for device interface
        self._measurements_c = (c_float * 6)()

        # Good Python list for public interface
        self.measurements = [0, 0, 0, 0, 0, 0]

        self.zeroes = [0, 0, 0, 0, 0, 0]

        # It is always assumed that total_force is updated alongside centre_of_pressure,
        # and that it is ready to go whenever the observer is notified.

        self.total_force = 0
        self.centre_of_pressure = Observable([0, 0])

    def update(self):

        old_measurement = self.measurements[:]

        self._update_measurements()

        for old, new in zip(old_measurement, self.measurements):

            # Only update everything if there was a change

            if old != new:

                self._update_centre_of_pressure()
                return

        print "skipped"

    def _update_measurements(self):

        ptr = cast(self._measurements_c, c_voidp).value
        for (i, channel) in enumerate(self.channels):

            slot = cast(ptr + (4 * i), POINTER(c_float))
            self.device.AioSingleAiEx(c_short(channel), slot)

            val = self._measurements_c[i]

            self.measurements[i] = val

    def _update_centre_of_pressure(self):

        zipped = zip(self.measurements, self.zeroes)

        after_zeroing = [m - z for m, z in zipped]

        after_calibration = []

        for i, x in enumerate(after_zeroing):

            # Calibration equation defined on page 10 of "Single Element
            # Multi-Component Transducer" instructions, model MC2.5A-1K-6278.

            v = x / (EXCITATION_VOLTAGE * SENSITIVITY_TERMS[self.code][i] *
                     GAIN * 1e-6)
            after_calibration.append(v)

        F_x, F_y, F_z, M_x, M_y, M_z = after_calibration

        # Given that the force is straight downwards (might not be the case with dynamics)
        # we arrive at these equations and rearrange them:

        # M_x = F_z * y
        # M_y = F_z * x

        self.total_force = F_z

        if F_z == 0:
            return

        cop = self.centre_of_pressure.get()

        scale = 0.5

        cop[0] = scale * M_y / F_z
        cop[1] = scale * M_x / F_z

        self.centre_of_pressure.notify_all()

    def set_zero(self):

        for i in range(6):
            self.zeroes[i] = self.measurements[i]
Example #8
0
class SixAxisWorld(object):
	"""

	Keeps track of a single real-world position and orientation of a sensor and
	its coordinate system.

	"""


	def __init__(self, sensor, mat = None):
		"""

		@argument mat - A 3x3 matrix to transform the vector [x; y; 1] into world coordinates.
		The matrix [[1, 0, 0], [0, 1, 0], [0, 0, 1]] corresponds to the identity.

		"""

		self.sensor = sensor

		if mat == 'i':
			self.mat = [[1, 0, 0], [0, 1, 0], [0, 0, 1]]
		elif mat == 'z':
			self.mat = [[0, 0, 0], [0, 0, 0], [0, 0, 0]]
		else:
			self.mat = mat

		self.total_force = 0
		self.centre_of_pressure = Observable([0, 0])

		sensor.centre_of_pressure.add_listener(self.on_update)


	def on_update(self, cop):
		"""

		On update of underlying sensor, apply transformations, then set own cop.

		"""

		self.total_force = self.sensor.total_force


		cop3 = 1

		new_cop = (


			cop[0] * self.mat[0][0] + cop[1] * self.mat[0][1] + cop3 * self.mat[0][2],
			cop[0] * self.mat[1][0] + cop[1] * self.mat[1][1] + cop3 * self.mat[1][2],
			cop[0] * self.mat[2][0] + cop[1] * self.mat[2][1] + cop3 * self.mat[2][2]

		)


		l = self.centre_of_pressure.get()
		l[0] = new_cop[0]
		l[1] = new_cop[1]

		self.centre_of_pressure.notify_all()


	def set_x(self, x):

		self.mat[0][2] = x
		

	def set_y(self, y):

		self.mat[1][2] = y
    def __init__(self):

        ### Hard-coded, set-up-specific configuration follows

        device0 = AIODevice(b'AIO000')
        device0.Init()

        device0.AioSetAiInputMethod(c_short(0))
        device0.AioSetAiRangeAll(aio.PM25)

        device1 = AIODevice(b'AIO001')
        device1.Init()

        device1.AioSetAiInputMethod(c_short(0))
        device1.AioSetAiRangeAll(aio.PM25)

        self.M5170 = SixAxis(device0, [0, 1, 2, 3, 4, 5], 'M5170')
        self.M5238 = SixAxis(device0, [6, 7, 8, 9, 10, 11], 'M5238')
        self.M5239 = SixAxis(device0, [12, 13, 14, 15, 16, 17], 'M5239')
        self.M5240 = SixAxis(device0, [18, 19, 20, 21, 22, 23], 'M5240')

        self.sensors = [self.M5239, self.M5170, self.M5240, self.M5238]

        self.world_sensors = [SixAxisWorld(s, 'z') for s in self.sensors]

        ## The order of self.sensors/self.world_sensors is defined as follows:
        ##
        ## (Top-down view)
        ##
        ##      0        2
        ##  ______      ______
        ## |    X |    | X    |
        ## |    | |    | |    |
        ## |___ X |    | X __ |
        ##
        ##      1        3
        ##
        ## 	      Coords:
        ##
        ## 	       ---> +X
        ## 	       |
        ## 	       |
        ## 	       V
        ## 	       +Y

        # Distance between two adjacent sensors on the same plate (m)

        sensor_diff = 7.68e-2

        # Distance between plates (m)

        stance_width = 15e-2

        self.world_sensors[0].set_x(-stance_width / 2)
        self.world_sensors[1].set_x(-stance_width / 2)
        self.world_sensors[2].set_x(stance_width / 2)
        self.world_sensors[3].set_x(stance_width / 2)

        self.world_sensors[0].set_y(-sensor_diff / 2)
        self.world_sensors[1].set_y(sensor_diff / 2)
        self.world_sensors[2].set_y(-sensor_diff / 2)
        self.world_sensors[3].set_y(sensor_diff / 2)

        self.world_sensors[0].mat[1][0] = -1
        self.world_sensors[0].mat[0][1] = 1

        self.world_sensors[1].mat[1][0] = -1
        self.world_sensors[1].mat[0][1] = -1

        self.world_sensors[2].mat[1][0] = 1
        self.world_sensors[2].mat[0][1] = 1

        self.world_sensors[3].mat[1][0] = 1
        self.world_sensors[3].mat[0][1] = 1

        ###

        self.total_force = 0
        self.centre_of_pressure = Observable([0, 0])

        for s in self.sensors:

            s.update()
            s.set_zero()
class WorldSensorConfiguration(object):
    """

	Keeps track of real-world positions and orientations of sensors alongside their
	coordinate systems.

	"""
    def __init__(self):

        ### Hard-coded, set-up-specific configuration follows

        device0 = AIODevice(b'AIO000')
        device0.Init()

        device0.AioSetAiInputMethod(c_short(0))
        device0.AioSetAiRangeAll(aio.PM25)

        device1 = AIODevice(b'AIO001')
        device1.Init()

        device1.AioSetAiInputMethod(c_short(0))
        device1.AioSetAiRangeAll(aio.PM25)

        self.M5170 = SixAxis(device0, [0, 1, 2, 3, 4, 5], 'M5170')
        self.M5238 = SixAxis(device0, [6, 7, 8, 9, 10, 11], 'M5238')
        self.M5239 = SixAxis(device0, [12, 13, 14, 15, 16, 17], 'M5239')
        self.M5240 = SixAxis(device0, [18, 19, 20, 21, 22, 23], 'M5240')

        self.sensors = [self.M5239, self.M5170, self.M5240, self.M5238]

        self.world_sensors = [SixAxisWorld(s, 'z') for s in self.sensors]

        ## The order of self.sensors/self.world_sensors is defined as follows:
        ##
        ## (Top-down view)
        ##
        ##      0        2
        ##  ______      ______
        ## |    X |    | X    |
        ## |    | |    | |    |
        ## |___ X |    | X __ |
        ##
        ##      1        3
        ##
        ## 	      Coords:
        ##
        ## 	       ---> +X
        ## 	       |
        ## 	       |
        ## 	       V
        ## 	       +Y

        # Distance between two adjacent sensors on the same plate (m)

        sensor_diff = 7.68e-2

        # Distance between plates (m)

        stance_width = 15e-2

        self.world_sensors[0].set_x(-stance_width / 2)
        self.world_sensors[1].set_x(-stance_width / 2)
        self.world_sensors[2].set_x(stance_width / 2)
        self.world_sensors[3].set_x(stance_width / 2)

        self.world_sensors[0].set_y(-sensor_diff / 2)
        self.world_sensors[1].set_y(sensor_diff / 2)
        self.world_sensors[2].set_y(-sensor_diff / 2)
        self.world_sensors[3].set_y(sensor_diff / 2)

        self.world_sensors[0].mat[1][0] = -1
        self.world_sensors[0].mat[0][1] = 1

        self.world_sensors[1].mat[1][0] = -1
        self.world_sensors[1].mat[0][1] = -1

        self.world_sensors[2].mat[1][0] = 1
        self.world_sensors[2].mat[0][1] = 1

        self.world_sensors[3].mat[1][0] = 1
        self.world_sensors[3].mat[0][1] = 1

        ###

        self.total_force = 0
        self.centre_of_pressure = Observable([0, 0])

        for s in self.sensors:

            s.update()
            s.set_zero()

    def update(self):

        for s in self.sensors:

            s.update()

        # Weighted sum of world coordinates of sensors

        accumulator = [0, 0]
        force_accum = 0

        for w in self.world_sensors:

            if w.sensor == self.M5170:
                continue

            l = w.centre_of_pressure.get()
            f = w.sensor.total_force

            accumulator[0] += l[0] * f
            accumulator[1] += l[1] * f

            force_accum += f

        self.total_force = force_accum

        # Division by zero

        if force_accum == 0:
            return

        accumulator[0] /= force_accum
        accumulator[1] /= force_accum

        cop = self.centre_of_pressure.get()
        cop[0] = accumulator[0]
        cop[1] = accumulator[1]

        self.centre_of_pressure.notify_all()
Example #11
0
def test_observable_notify():

	c = Observable()

	c.set(5)

	assert c.get() == 5


	triggered = [False]

	def f(c):

		triggered[0] = True


	c.add_listener(f)
	c.set(10)

	assert triggered[0]

	triggered = [False]
	c.remove_listener(f)

	c.set(20)

	assert not triggered[0]

	c.add_listener(f)
	c.notify_all()

	assert triggered[0]