예제 #1
0
class LuaAnimate(object):
    def __init__(self, times, values):
        # Workaround to convert lua tables to lists
        self._a = Animate(list(times.values()), list(values.values()))

    @classmethod
    def new(cls, times, values):
        return LuaAnimate(times, values)

    def __call__(self):
        return self._a()

    def has_ended(self):
        return self._a.has_ended()
예제 #2
0
class _Expression(object):
	def __init__(self):
		self._emotion = 0 + 0j
		self._anim = None

		self.servos = []
		self.dofs = {}
		self.dof_values = {}
		self.lock = threading.Lock()

		self.empty_config()
		self.load_config()
		self.isEmotion = False

	def get_emotion_complex(self):
		"""
		Returns current emotion as a complex number
		"""
		return self._emotion

	def set_emotion(self, valence=None, arousal=None, r=None, phi=None, degrees=False, anim_time=0):
		self.isEmotion = True

		# TODO: Phi in deg or radians? Internally probably radians
		e = 0 + 0j
		if valence is not None and arousal is not None:
			e = valence + arousal*1j
		elif r is not None and phi is not None:
			if degrees:
				phi = phi * math.pi/180.0
			e = cmath.rect(r, phi)
		else:
			raise RuntimeError("Bad combination of parameters. Either valence and arousal or r and phi need to be provided.")

		# Make sure emotion is restricted to within unity circle.
		if abs(e) > 1.0:
			e = cmath.rect(1.0, cmath.phase(e))

		if anim_time > 0:
			self._anim = Animate([0, anim_time], [self._emotion, e])
		else:
			self._emotion = e

	def set_emotion_val_ar(self, valence, arousal, anim_time=0):
		self.set_emotion(valence=valence, arousal=arousal, anim_time=anim_time)

	def set_emotion_r_phi(self, r, phi, degrees=False, anim_time=0):
		self.set_emotion(r=r, phi=phi, degrees=degrees, anim_time=anim_time)

	def set_dof_values(self, dof_position_values):
		self.isEmotion = False
		for dof in dof_position_values:
		 	dof_position_values[dof] = constrain(dof_position_values[dof], -1.0, 1.0)
		self.dof_values = dof_position_values

	def update(self):
		if self._anim is not None:
			self._emotion = self._anim()
			if self._anim.has_ended():
				self._anim = None

		phi = cmath.phase(self._emotion)
		r = abs(self._emotion)

		# Create a list of dummy values.
		# None indicates that the servo at that index will not be updated.
		servo_pos_us = [None for i in range(16)]

		# Buffer to store all DOF values
		#self.dof_values = {}

		# (1) Calculate DOF positions using phi/r
		# (2) This step also applies overlay functions to the DOFs
		if self.isEmotion:
			for dofname, dof in self.dofs.iteritems():
				self.dof_values[dofname] = dof.calc(phi, r)

		# (3) Update all servos
		for servo in self.servos:
			if servo.pin < 0 or servo.pin > 15:
				continue # Skip invalid pins
			if servo.dofname in self.dof_values:
				servo_pos_us[servo.pin] = servo.dof_to_us(self.dof_values[servo.dofname])

		# TODO: send values to hardware

		with Hardware.lock:
			Hardware.servo_set_all(servo_pos_us)


	def empty_config(self):
		self.servos = []
		self.dofs = {}

	def load_config(self):
		def open_yaml(filename):
			"""Helper function to load YAML files into a dict."""
			try:
				yaml_dict = None
				with open(filename) as f:
					yaml_dict = yaml.load(f, Loader=Loader)
				return yaml_dict
			except IOError:
				raise RuntimeError("Could not open YAML file: %s" % filename)
		def default(x, e, y):
			"""
			Helper function that attempts to read a key from a dict. If the
			specified exception occurs, a default value is returned.
			"""
			try:
				return x()
			except e:
				return y

		pinmap_yaml = open_yaml(_configs["pinmap"])
		limits_yaml = open_yaml(_configs["limits"])
		functions_yaml = open_yaml(_configs["functions"])

		self.empty_config()

		# Create all Servo objects
		# Starts from pinmap, as a servo needs to have a pin associated with it.
		# If DOF not found in limits, default values are assigned.
		for pin, dofname in pinmap_yaml.iteritems():
			dofname = str(dofname) # Force into str

			s = {}
			s["pin"] = pin
			s["dofname"] = dofname
			s["min_range"] = default(lambda: limits_yaml[dofname]["min"], KeyError, None)
			s["mid_pos"] = default(lambda: limits_yaml[dofname]["mid"], KeyError, None)
			s["max_range"] = default(lambda: limits_yaml[dofname]["max"], KeyError, None)

			# Remove all empty values
			s = {k: v for k, v in s.items() if v is not None}

			# Create new servo object from dict
			self.servos.append(Servo(**s))

		# Create all DOF objects
		# Starts from functions YAML
		# If DOF not found in limits, default values are assigned.
		for dofname, params in functions_yaml.iteritems():
			d = {}
			d["name"] = dofname
			d["neutral"] = default(lambda: params.pop("neutral"), KeyError, None)
			d["poly"] = default(lambda: params.pop("poly"), KeyError, None)

			# Remove all empty values
			d = {k: v for k, v in d.items() if v is not None}

			# Create new DOF object from dict, store extra values from YAML
			dof = DOF(**d)
			dof.data = params
			self.dofs[dofname] = dof

		print_info("Loaded %d DOFs, %d servos" % (len(self.servos), len(self.dofs)) )