You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

objects.py 35KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901902903904905906907908909910911912913914915916917918919920921922923924925926927928929930931932933934935936937938939
  1. # Copyright (c) 2016-2017 Anki, Inc.
  2. #
  3. # Licensed under the Apache License, Version 2.0 (the "License");
  4. # you may not use this file except in compliance with the License.
  5. # You may obtain a copy of the License in the file LICENSE.txt or at
  6. #
  7. # http://www.apache.org/licenses/LICENSE-2.0
  8. #
  9. # Unless required by applicable law or agreed to in writing, software
  10. # distributed under the License is distributed on an "AS IS" BASIS,
  11. # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  12. # See the License for the specific language governing permissions and
  13. # limitations under the License.
  14. '''Object and Power Cube recognition.
  15. Cozmo can recognize and track a number of different types of objects.
  16. These objects may be visible (currently observed by the robot's camera)
  17. and tappable (in the case of the Power Cubes that ship with the robot).
  18. Power Cubes are known as a :class:`LightCube` by the SDK. Each cube has
  19. controllable lights, and sensors that can determine when its being moved
  20. or tapped.
  21. Objects can emit several events such as :class:`EvtObjectObserved` when
  22. the robot sees (or continues to see) the object with its camera, or
  23. :class:`EvtObjectTapped` if a power cube is tapped by a player. You
  24. can either observe the object's instance directly, or capture all such events
  25. for all objects by observing them on :class:`cozmo.world.World` instead.
  26. All observable objects have a marker attached to them, which allows Cozmo
  27. to recognize the object and it's position and rotation("pose"). You can attach
  28. markers to your own objects for Cozmo to recognize by printing them out from the
  29. online documentation. They will be detected as :class:`CustomObject` instances.
  30. '''
  31. # __all__ should order by constants, event classes, other classes, functions.
  32. __all__ = ['LightCube1Id', 'LightCube2Id', 'LightCube3Id', 'LightCubeIDs',
  33. 'OBJECT_VISIBILITY_TIMEOUT',
  34. 'EvtObjectAppeared',
  35. 'EvtObjectConnectChanged', 'EvtObjectConnected',
  36. 'EvtObjectDisappeared', 'EvtObjectLocated',
  37. 'EvtObjectMoving', 'EvtObjectMovingStarted', 'EvtObjectMovingStopped',
  38. 'EvtObjectObserved', 'EvtObjectTapped',
  39. 'ObservableElement', 'ObservableObject', 'LightCube', 'Charger',
  40. 'CustomObject', 'CustomObjectMarkers', 'CustomObjectTypes', 'FixedCustomObject']
  41. import collections
  42. import math
  43. import time
  44. from . import logger
  45. from . import action
  46. from . import event
  47. from . import lights
  48. from . import util
  49. from ._clad import _clad_to_engine_iface, _clad_to_game_cozmo, _clad_to_engine_cozmo, _clad_to_game_anki
  50. #: Length of time in seconds to go without receiving an observed event before
  51. #: assuming that Cozmo can no longer see an object.
  52. OBJECT_VISIBILITY_TIMEOUT = 0.4
  53. class EvtObjectObserved(event.Event):
  54. '''Triggered whenever an object is visually identified by the robot.
  55. A stream of these events are produced while an object is visible to the robot.
  56. Each event has an updated image_box field.
  57. See EvtObjectAppeared if you only want to know when an object first
  58. becomes visible.
  59. '''
  60. obj = 'The object that was observed'
  61. updated = 'A set of field names that have changed'
  62. image_box = 'A comzo.util.ImageBox defining where the object is within Cozmo\'s camera view'
  63. pose = 'The cozmo.util.Pose defining the position and rotation of the object'
  64. class EvtObjectAppeared(event.Event):
  65. '''Triggered whenever an object is first visually identified by a robot.
  66. This differs from EvtObjectObserved in that it's only triggered when
  67. an object initially becomes visible. If it disappears for more than
  68. OBJECT_VISIBILITY_TIMEOUT seconds and then is seen again, a
  69. EvtObjectDisappeared will be dispatched, followed by another
  70. EvtObjectAppeared event.
  71. For continuous tracking information about a visible object, see
  72. EvtObjectObserved.
  73. '''
  74. obj = 'The object that was observed'
  75. updated = 'A set of field names that have changed'
  76. image_box = 'A comzo.util.ImageBox defining where the object is within Cozmo\'s camera view'
  77. pose = 'The cozmo.util.Pose defining the position and rotation of the object'
  78. class EvtObjectConnected(event.Event):
  79. '''Triggered when the engine reports that an object is connected (i.e. exists).
  80. This will usually occur at the start of the program in response to the SDK
  81. sending RequestConnectedObjects to the engine.
  82. '''
  83. obj = 'The object that is connected'
  84. connected = 'True if the object connected, False if it disconnected'
  85. class EvtObjectConnectChanged(event.Event):
  86. 'Triggered when an active object has connected or disconnected from the robot.'
  87. obj = 'The object that connected or disconnected'
  88. connected = 'True if the object connected, False if it disconnected'
  89. class EvtObjectLocated(event.Event):
  90. '''Triggered when the engine reports that an object is located (i.e. pose is known).
  91. This will usually occur at the start of the program in response to the SDK
  92. sending RequestLocatedObjectStates to the engine.
  93. '''
  94. obj = 'The object that is located'
  95. updated = 'A set of field names that have changed'
  96. pose = 'The cozmo.util.Pose defining the position and rotation of the object'
  97. class EvtObjectDisappeared(event.Event):
  98. '''Triggered whenever an object that was previously being observed is no longer visible.'''
  99. obj = 'The object that is no longer being observed'
  100. class EvtObjectMoving(event.Event):
  101. 'Triggered when an active object is currently moving.'
  102. obj = 'The object that is currently moving'
  103. # :class:`~cozmo.util.Vector3`: The currently measured acceleration
  104. acceleration = 'The currently measured acceleration'
  105. move_duration = 'The current duration of time (in seconds) that the object has spent moving'
  106. class EvtObjectMovingStarted(event.Event):
  107. 'Triggered when an active object starts moving.'
  108. obj = 'The object that started moving'
  109. #: :class:`~cozmo.util.Vector3`: The currently measured acceleration
  110. acceleration = 'The currently measured acceleration'
  111. class EvtObjectMovingStopped(event.Event):
  112. 'Triggered when an active object stops moving.'
  113. obj = 'The object that stopped moving'
  114. move_duration = 'The duration of time (in seconds) that the object spent moving'
  115. class EvtObjectTapped(event.Event):
  116. 'Triggered when an active object is tapped.'
  117. obj = 'The object that was tapped'
  118. tap_count = 'Number of taps detected'
  119. tap_duration = 'The duration of the tap in ms'
  120. tap_intensity = 'The intensity of the tap'
  121. class ObservableElement(event.Dispatcher):
  122. '''The base type for anything Cozmo can see.'''
  123. #: Length of time in seconds to go without receiving an observed event before
  124. #: assuming that Cozmo can no longer see an element. Can be overridden in sub
  125. #: classes.
  126. visibility_timeout = OBJECT_VISIBILITY_TIMEOUT
  127. def __init__(self, conn, world, robot, **kw):
  128. super().__init__(**kw)
  129. self._robot = robot
  130. self._pose = None
  131. self.conn = conn
  132. #: :class:`cozmo.world.World`: The robot's world in which this element is located.
  133. self.world = world
  134. #: float: The time the last event was received.
  135. #: ``None`` if no events have yet been received.
  136. self.last_event_time = None
  137. #: float: The time the element was last observed by the robot.
  138. #: ``None`` if the element has not yet been observed.
  139. self.last_observed_time = None
  140. #: int: The robot's timestamp of the last observed event.
  141. #: ``None`` if the element has not yet been observed.
  142. #: In milliseconds relative to robot epoch.
  143. self.last_observed_robot_timestamp = None
  144. #: :class:`~cozmo.util.ImageBox`: The ImageBox defining where the
  145. #: object was last visible within Cozmo's camera view.
  146. #: ``None`` if the element has not yet been observed.
  147. self.last_observed_image_box = None
  148. self._is_visible = False
  149. self._observed_timeout_handler = None
  150. def __repr__(self):
  151. extra = self._repr_values()
  152. if len(extra) > 0:
  153. extra = ' '+extra
  154. if self.pose:
  155. extra += ' pose=%s' % self.pose
  156. return '<%s%s is_visible=%s>' % (self.__class__.__name__,
  157. extra, self.is_visible)
  158. #### Private Methods ####
  159. def _repr_values(self):
  160. return ''
  161. def _update_field(self, changed, field_name, new_value):
  162. # Set only changed fields and update the passed in changed set
  163. current = getattr(self, field_name)
  164. if current != new_value:
  165. setattr(self, field_name, new_value)
  166. changed.add(field_name)
  167. def _reset_observed_timeout_handler(self):
  168. if self._observed_timeout_handler is not None:
  169. self._observed_timeout_handler.cancel()
  170. self._observed_timeout_handler = self._loop.call_later(
  171. self.visibility_timeout, self._observed_timeout)
  172. def _observed_timeout(self):
  173. # triggered when the element is no longer considered "visible"
  174. # ie. visibility_timeout seconds after the last observed event
  175. self._is_visible = False
  176. self._dispatch_disappeared_event()
  177. def _dispatch_observed_event(self, changed_fields, image_box):
  178. # Override in subclass if there is a specific event for that type
  179. pass
  180. def _dispatch_appeared_event(self, changed_fields, image_box):
  181. # Override in subclass if there is a specific event for that type
  182. pass
  183. def _dispatch_disappeared_event(self):
  184. # Override in subclass if there is a specific event for that type
  185. pass
  186. def _on_observed(self, image_box, timestamp, changed_fields):
  187. # Called from subclasses on their corresponding observed messages
  188. newly_visible = self._is_visible is False
  189. self._is_visible = True
  190. changed_fields |= {'last_observed_time', 'last_observed_robot_timestamp',
  191. 'last_event_time', 'last_observed_image_box'}
  192. now = time.time()
  193. self.last_observed_time = now
  194. self.last_observed_robot_timestamp = timestamp
  195. self.last_event_time = now
  196. self.last_observed_image_box = image_box
  197. self._reset_observed_timeout_handler()
  198. self._dispatch_observed_event(changed_fields, image_box)
  199. if newly_visible:
  200. self._dispatch_appeared_event(changed_fields, image_box)
  201. #### Properties ####
  202. @property
  203. def pose(self):
  204. ''':class:`cozmo.util.Pose`: The pose of the element in the world.
  205. Is ``None`` for elements that don't have pose information.
  206. '''
  207. return self._pose
  208. @property
  209. def time_since_last_seen(self):
  210. '''float: time since this element was last seen (math.inf if never)'''
  211. if self.last_observed_time is None:
  212. return math.inf
  213. return time.time() - self.last_observed_time
  214. @property
  215. def is_visible(self):
  216. '''bool: True if the element has been observed recently.
  217. "recently" is defined as :attr:`visibility_timeout` seconds.
  218. '''
  219. return self._is_visible
  220. class ObservableObject(ObservableElement):
  221. '''The base type for objects in Cozmo's world.
  222. See parent class :class:`ObservableElement` for additional properties
  223. and methods.
  224. '''
  225. #: bool: True if this type of object can be physically picked up by Cozmo
  226. pickupable = False
  227. #: bool: True if this type of object can have objects physically placed on it by Cozmo
  228. place_objects_on_this = False
  229. def __init__(self, conn, world, object_id=None, **kw):
  230. super().__init__(conn, world, robot=None, **kw)
  231. self._object_id = object_id
  232. #### Private Methods ####
  233. def _repr_values(self):
  234. return 'object_id=%s' % self.object_id
  235. def _dispatch_observed_event(self, changed_fields, image_box):
  236. self.dispatch_event(EvtObjectObserved, obj=self,
  237. updated=changed_fields, image_box=image_box, pose=self._pose)
  238. def _dispatch_appeared_event(self, changed_fields, image_box):
  239. self.dispatch_event(EvtObjectAppeared, obj=self,
  240. updated=changed_fields, image_box=image_box, pose=self._pose)
  241. def _dispatch_disappeared_event(self):
  242. self.dispatch_event(EvtObjectDisappeared, obj=self)
  243. def _handle_connected_object_state(self, object_state):
  244. # triggered when engine sends a ConnectedObjectStates message
  245. # as a response to a RequestConnectedObjects message
  246. self._pose = util.Pose._create_default()
  247. self.is_connected = True
  248. self.dispatch_event(EvtObjectConnected, obj=self)
  249. def _handle_located_object_state(self, object_state):
  250. # triggered when engine sends a LocatedObjectStates message
  251. # as a response to a RequestLocatedObjectStates message
  252. if (self.last_observed_robot_timestamp and
  253. (self.last_observed_robot_timestamp > object_state.lastObservedTimestamp)):
  254. logger.warning("Ignoring old located object_state=%s obj=%s (last_observed_robot_timestamp=%s)",
  255. object_state, self, self.last_observed_robot_timestamp)
  256. return
  257. changed_fields = {'last_observed_robot_timestamp', 'pose'}
  258. self.last_observed_robot_timestamp = object_state.lastObservedTimestamp
  259. self._pose = util.Pose._create_from_clad(object_state.pose)
  260. if object_state.poseState == _clad_to_game_anki.PoseState.Invalid:
  261. logger.error("Unexpected Invalid pose state received")
  262. self._pose.invalidate()
  263. elif object_state.poseState == _clad_to_game_anki.PoseState.Dirty:
  264. # Note Dirty currently means either moved (in which case it's really dirty)
  265. # or inaccurate (e.g. seen from too far away to give an accurate enough pose for localization)
  266. # TODO: split Dirty into 2 states, and allow SDK to report the distinction.
  267. self._pose._is_accurate = False
  268. self.dispatch_event(EvtObjectLocated,
  269. obj=self,
  270. updated=changed_fields,
  271. pose=self._pose)
  272. #### Properties ####
  273. @property
  274. def object_id(self):
  275. '''int: The internal ID assigned to the object.
  276. This value can only be assigned once as it is static in the engine.
  277. '''
  278. return self._object_id
  279. @object_id.setter
  280. def object_id(self, value):
  281. if self._object_id is not None:
  282. # We cannot currently rely on Engine ensuring that object ID remains static
  283. # E.g. in the case of a cube disconnecting and reconnecting it's removed
  284. # and then re-added to blockworld which results in a new ID.
  285. logger.warning("Changing object_id for %s from %s to %s", self.__class__, self._object_id, value)
  286. else:
  287. logger.debug("Setting object_id for %s to %s", self.__class__, value)
  288. self._object_id = value
  289. @property
  290. def descriptive_name(self):
  291. '''str: A descriptive name for this ObservableObject instance.'''
  292. # Note: Sub-classes should override this to add any other relevant info
  293. # for that object type.
  294. return "%s id=%d" % (self.__class__.__name__, self.object_id)
  295. #### Private Event Handlers ####
  296. def _recv_msg_robot_observed_object(self, evt, *, msg):
  297. changed_fields = {'pose'}
  298. self._pose = util.Pose._create_from_clad(msg.pose)
  299. image_box = util.ImageBox._create_from_clad_rect(msg.img_rect)
  300. self._on_observed(image_box, msg.timestamp, changed_fields)
  301. #### Public Event Handlers ####
  302. #### Event Wrappers ####
  303. #### Commands ####
  304. #: LightCube1Id's markers look a bit like a paperclip
  305. LightCube1Id = _clad_to_game_cozmo.ObjectType.Block_LIGHTCUBE1
  306. #: LightCube2Id's markers look a bit like a lamp (or a heart)
  307. LightCube2Id = _clad_to_game_cozmo.ObjectType.Block_LIGHTCUBE2
  308. #: LightCube3Id's markers look a bit like the letters 'ab' over 'T'
  309. LightCube3Id = _clad_to_game_cozmo.ObjectType.Block_LIGHTCUBE3
  310. #: An ordered list of the 3 light cube IDs for convenience
  311. LightCubeIDs = [LightCube1Id, LightCube2Id, LightCube3Id]
  312. class LightCube(ObservableObject):
  313. '''A light cube object has four LEDs that Cozmo can actively manipulate and communicate with.
  314. See parent class :class:`ObservableObject` for additional properties
  315. and methods.
  316. '''
  317. #TODO investigate why the top marker orientation of a cube is a bit strange
  318. #: Voltage where a cube's battery can be considered empty
  319. EMPTY_VOLTAGE = 1.0
  320. #: Voltage where a cube's battery can be considered full
  321. FULL_VOLTAGE = 1.5
  322. pickupable = True
  323. place_objects_on_this = True
  324. def __init__(self, cube_id, *a, **kw):
  325. super().__init__(*a, **kw)
  326. #: float: The time the object was last tapped
  327. #: ``None`` if the cube wasn't tapped yet.
  328. self.last_tapped_time = None
  329. #: int: The robot's timestamp of the last tapped event.
  330. #: ``None`` if the cube wasn't tapped yet.
  331. #: In milliseconds relative to robot epoch.
  332. self.last_tapped_robot_timestamp = None
  333. #: float: The time the object was last moved
  334. #: ``None`` if the cube wasn't moved yet.
  335. self.last_moved_time = None
  336. #: float: The time the object started moving when last moved
  337. self.last_moved_start_time = None
  338. #: int: The robot's timestamp of the last move event.
  339. #: ``None`` if the cube wasn't moved yet.
  340. #: In milliseconds relative to robot epoch.
  341. self.last_moved_robot_timestamp = None
  342. #: int: The robot's timestamp of when the object started moving when last moved
  343. #: ``None`` if the cube wasn't moved yet.
  344. #: In milliseconds relative to robot epoch.
  345. self.last_moved_start_robot_timestamp = None
  346. #: float: Battery voltage.
  347. #: ``None`` if no voltage reading has been received yet
  348. self.battery_voltage = None
  349. #: bool: True if the cube's accelerometer indicates that the cube is moving.
  350. self.is_moving = False
  351. #: bool: True if the cube is currently connected to the robot via radio.
  352. self.is_connected = False
  353. self._cube_id = cube_id
  354. def _repr_values(self):
  355. super_values = super()._repr_values()
  356. if len(super_values) > 0:
  357. super_values += ' '
  358. return ('{super_values}'
  359. 'battery={self.battery_str:s}'.format(self=self, super_values=super_values))
  360. #### Private Methods ####
  361. def _set_light(self, msg, idx, light):
  362. if not isinstance(light, lights.Light):
  363. raise TypeError("Expected a lights.Light")
  364. msg.onColor[idx] = light.on_color.int_color
  365. msg.offColor[idx] = light.off_color.int_color
  366. msg.onPeriod_ms[idx] = light.on_period_ms
  367. msg.offPeriod_ms[idx] = light.off_period_ms
  368. msg.transitionOnPeriod_ms[idx] = light.transition_on_period_ms
  369. msg.transitionOffPeriod_ms[idx] = light.transition_off_period_ms
  370. #### Event Wrappers ####
  371. async def wait_for_tap(self, timeout=None):
  372. '''Wait for the object to receive a tap event.
  373. Args:
  374. timeout (float): Maximum time to wait for a tap, in seconds. None for indefinite
  375. Returns:
  376. A :class:`EvtObjectTapped` object if a tap was received.
  377. '''
  378. return await self.wait_for(EvtObjectTapped, timeout=timeout)
  379. #### Properties ####
  380. @property
  381. def battery_percentage(self):
  382. """float: Battery level as a percentage."""
  383. if self.battery_voltage is None:
  384. # not received a voltage measurement yet
  385. return None
  386. elif self.battery_voltage >= self.FULL_VOLTAGE:
  387. return 100.0
  388. elif self.battery_voltage <= self.EMPTY_VOLTAGE:
  389. return 0.0
  390. else:
  391. return 100.0 * ((self.battery_voltage - self.EMPTY_VOLTAGE) /
  392. (self.FULL_VOLTAGE - self.EMPTY_VOLTAGE))
  393. @property
  394. def battery_str(self):
  395. """str: String representation of the battery level."""
  396. if self.battery_voltage is None:
  397. return "Unknown"
  398. else:
  399. return ('{self.battery_percentage:.0f}%'.format(self=self))
  400. @property
  401. def cube_id(self):
  402. """int: The Light Cube ID.
  403. This will be one of :attr:`~cozmo.objects.LightCube1Id`,
  404. :attr:`~cozmo.objects.LightCube2Id` and :attr:`~cozmo.objects.LightCube3Id`.
  405. Note: the cube_id is not the same thing as the object_id.
  406. """
  407. return self._cube_id
  408. #### Private Event Handlers ####
  409. def _recv_msg_object_tapped(self, evt, *, msg):
  410. now = time.time()
  411. self.last_event_time = now
  412. self.last_tapped_time = now
  413. self.last_tapped_robot_timestamp = msg.timestamp
  414. tap_intensity = msg.tapPos - msg.tapNeg
  415. self.dispatch_event(EvtObjectTapped, obj=self,
  416. tap_count=msg.numTaps, tap_duration=msg.tapTime, tap_intensity=tap_intensity)
  417. def _recv_msg_object_moved(self, evt, *, msg):
  418. now = time.time()
  419. started_moving = not self.is_moving
  420. self.is_moving = True
  421. self.last_event_time = now
  422. self.last_moved_time = now
  423. self.last_moved_robot_timestamp = msg.timestamp
  424. self.pose.invalidate()
  425. acceleration = util.Vector3(msg.accel.x, msg.accel.y, msg.accel.z)
  426. if started_moving:
  427. self.last_moved_start_time = now
  428. self.last_moved_start_robot_timestamp = msg.timestamp
  429. self.dispatch_event(EvtObjectMovingStarted, obj=self,
  430. acceleration=acceleration)
  431. else:
  432. move_duration = now - self.last_moved_start_time
  433. self.dispatch_event(EvtObjectMoving, obj=self,
  434. acceleration=acceleration,
  435. move_duration=move_duration)
  436. def _recv_msg_object_stopped_moving(self, evt, *, msg):
  437. now = time.time()
  438. if self.is_moving:
  439. self.is_moving = False
  440. move_duration = now - self.last_moved_start_time
  441. else:
  442. # This happens for very short movements that are immediately
  443. # considered stopped (no acceleration info is present)
  444. move_duration = 0.0
  445. self.dispatch_event(EvtObjectMovingStopped, obj=self,
  446. move_duration=move_duration)
  447. def _recv_msg_object_power_level(self, evt, *, msg):
  448. self.battery_voltage = msg.batteryLevel * 0.01
  449. def _recv_msg_object_connection_state(self, evt, *, msg):
  450. if self.is_connected != msg.connected:
  451. if msg.connected:
  452. logger.info("Object connected: %s", self)
  453. else:
  454. logger.info("Object disconnected: %s", self)
  455. self.is_connected = msg.connected
  456. self.dispatch_event(EvtObjectConnectChanged, obj=self,
  457. connected=self.is_connected)
  458. @property
  459. def descriptive_name(self):
  460. '''str: A descriptive name for this LightCube instance.'''
  461. # Specialization of ObservableObject's method to include the cube ID.
  462. return "%s %s id=%d" % (self.__class__.__name__, self._cube_id, self.object_id)
  463. #### Public Event Handlers ####
  464. def recv_evt_object_tapped(self, evt, **kw):
  465. pass
  466. #### Commands ####
  467. # TODO: make this explicit as to which light goes to which corner.
  468. def set_light_corners(self, light1, light2, light3, light4):
  469. """Set the light for each corner"""
  470. msg = _clad_to_engine_iface.SetAllActiveObjectLEDs(objectID=self.object_id)
  471. for i, light in enumerate( (light1, light2, light3, light4) ):
  472. if light is not None:
  473. lights._set_light(msg, i, light)
  474. self.conn.send_msg(msg)
  475. def set_lights(self, light):
  476. '''Set all lights on the cube
  477. Args:
  478. light (:class:`cozmo.lights.Light`): The settings for the lights.
  479. '''
  480. msg = _clad_to_engine_iface.SetAllActiveObjectLEDs(
  481. objectID=self.object_id)
  482. for i in range(4):
  483. lights._set_light(msg, i, light)
  484. self.conn.send_msg(msg)
  485. def set_lights_off(self):
  486. '''Turn off all the lights on the cube.'''
  487. self.set_lights(lights.off_light)
  488. class Charger(ObservableObject):
  489. '''Cozmo's charger object, which the robot can observe and drive toward.
  490. See parent class :class:`ObservableObject` for additional properties
  491. and methods.
  492. '''
  493. def __init__(self, *a, **kw):
  494. super().__init__(*a, **kw)
  495. class CustomObject(ObservableObject):
  496. '''An object defined by the SDK. It is bound to a specific objectType e.g ``CustomType00``.
  497. This defined object is given a size in the x,y and z axis. The dimensions
  498. of the markers on the object are also defined. We get an
  499. :class:`cozmo.objects.EvtObjectObserved` message when the robot sees these
  500. markers.
  501. See parent class :class:`ObservableObject` for additional properties
  502. and methods.
  503. These objects are created automatically by the engine when Cozmo observes
  504. an object with custom markers. For Cozmo to see one of these you must first
  505. define an object with custom markers, via one of the following methods:
  506. :meth:`~cozmo.world.World.define_custom_box`.
  507. :meth:`~cozmo.world.World.define_custom_cube`, or
  508. :meth:`~cozmo.world.World.define_custom_wall`
  509. '''
  510. def __init__(self, conn, world, object_type,
  511. x_size_mm, y_size_mm, z_size_mm,
  512. marker_width_mm, marker_height_mm, is_unique, **kw):
  513. super().__init__(conn, world, **kw)
  514. self.object_type = object_type
  515. self._x_size_mm = x_size_mm
  516. self._y_size_mm = y_size_mm
  517. self._z_size_mm = z_size_mm
  518. self._marker_width_mm = marker_width_mm
  519. self._marker_height_mm = marker_height_mm
  520. self._is_unique = is_unique
  521. def _repr_values(self):
  522. return ('object_type={self.object_type} '
  523. 'x_size_mm={self.x_size_mm:.1f} '
  524. 'y_size_mm={self.y_size_mm:.1f} '
  525. 'z_size_mm={self.z_size_mm:.1f} '
  526. 'is_unique={self.is_unique}'.format(self=self))
  527. #### Private Methods ####
  528. #### Event Wrappers ####
  529. #### Properties ####
  530. @property
  531. def x_size_mm(self):
  532. '''float: Size of this object in its X axis, in millimeters.'''
  533. return self._x_size_mm
  534. @property
  535. def y_size_mm(self):
  536. '''float: Size of this object in its Y axis, in millimeters.'''
  537. return self._y_size_mm
  538. @property
  539. def z_size_mm(self):
  540. '''float: Size of this object in its Z axis, in millimeters.'''
  541. return self._z_size_mm
  542. @property
  543. def marker_width_mm(self):
  544. '''float: Width in millimeters of the marker on this object.'''
  545. return self._marker_width_mm
  546. @property
  547. def marker_height_mm(self):
  548. '''float: Height in millimeters of the marker on this object.'''
  549. return self._marker_height_mm
  550. @property
  551. def is_unique(self):
  552. '''bool: True if there should only be one of this object type in the world.'''
  553. return self._is_unique
  554. @property
  555. def descriptive_name(self):
  556. '''str: A descriptive name for this CustomObject instance.'''
  557. # Specialization of ObservableObject's method to include the object type.
  558. return "%s id=%d" % (self.object_type.name, self.object_id)
  559. #### Private Event Handlers ####
  560. #### Public Event Handlers ####
  561. #### Commands ####
  562. class _CustomObjectType(collections.namedtuple('_CustomObjectType', 'name id')):
  563. # Tuple mapping between CLAD ActionResult name and ID
  564. # All instances will be members of ActionResults
  565. # Keep _ActionResult as lightweight as a normal namedtuple
  566. __slots__ = ()
  567. def __str__(self):
  568. return 'CustomObjectTypes.%s' % self.name
  569. class CustomObjectTypes:
  570. '''Defines all available custom object types.
  571. For use with world.define_custom methods such as
  572. :meth:`cozmo.world.World.define_custom_box`,
  573. :meth:`cozmo.world.World.define_custom_cube`, and
  574. :meth:`cozmo.world.World.define_custom_wall`
  575. '''
  576. #: CustomType00 - the first custom object type
  577. CustomType00 = _CustomObjectType("CustomType00", _clad_to_engine_cozmo.ObjectType.CustomType00)
  578. #:
  579. CustomType01 = _CustomObjectType("CustomType01", _clad_to_engine_cozmo.ObjectType.CustomType01)
  580. #:
  581. CustomType02 = _CustomObjectType("CustomType02", _clad_to_engine_cozmo.ObjectType.CustomType02)
  582. #:
  583. CustomType03 = _CustomObjectType("CustomType03", _clad_to_engine_cozmo.ObjectType.CustomType03)
  584. #:
  585. CustomType04 = _CustomObjectType("CustomType04", _clad_to_engine_cozmo.ObjectType.CustomType04)
  586. #:
  587. CustomType05 = _CustomObjectType("CustomType05", _clad_to_engine_cozmo.ObjectType.CustomType05)
  588. #:
  589. CustomType06 = _CustomObjectType("CustomType06", _clad_to_engine_cozmo.ObjectType.CustomType06)
  590. #:
  591. CustomType07 = _CustomObjectType("CustomType07", _clad_to_engine_cozmo.ObjectType.CustomType07)
  592. #:
  593. CustomType08 = _CustomObjectType("CustomType08", _clad_to_engine_cozmo.ObjectType.CustomType08)
  594. #:
  595. CustomType09 = _CustomObjectType("CustomType09", _clad_to_engine_cozmo.ObjectType.CustomType09)
  596. #:
  597. CustomType10 = _CustomObjectType("CustomType10", _clad_to_engine_cozmo.ObjectType.CustomType10)
  598. #:
  599. CustomType11 = _CustomObjectType("CustomType11", _clad_to_engine_cozmo.ObjectType.CustomType11)
  600. #:
  601. CustomType12 = _CustomObjectType("CustomType12", _clad_to_engine_cozmo.ObjectType.CustomType12)
  602. #:
  603. CustomType13 = _CustomObjectType("CustomType13", _clad_to_engine_cozmo.ObjectType.CustomType13)
  604. #:
  605. CustomType14 = _CustomObjectType("CustomType14", _clad_to_engine_cozmo.ObjectType.CustomType14)
  606. #:
  607. CustomType15 = _CustomObjectType("CustomType15", _clad_to_engine_cozmo.ObjectType.CustomType15)
  608. #:
  609. CustomType16 = _CustomObjectType("CustomType16", _clad_to_engine_cozmo.ObjectType.CustomType16)
  610. #:
  611. CustomType17 = _CustomObjectType("CustomType17", _clad_to_engine_cozmo.ObjectType.CustomType17)
  612. #:
  613. CustomType18 = _CustomObjectType("CustomType18", _clad_to_engine_cozmo.ObjectType.CustomType18)
  614. #: CustomType19 - the last custom object type
  615. CustomType19 = _CustomObjectType("CustomType19", _clad_to_engine_cozmo.ObjectType.CustomType19)
  616. _CustomObjectMarker = collections.namedtuple('_CustomObjectMarker', 'name id')
  617. class CustomObjectMarkers:
  618. '''Defines all available custom object markers.
  619. For use with world.define_custom methods such as
  620. :meth:`cozmo.world.World.define_custom_box`,
  621. :meth:`cozmo.world.World.define_custom_cube`, and
  622. :meth:`cozmo.world.World.define_custom_wall`
  623. '''
  624. #: .. image:: ../images/custom_markers/SDK_2Circles.png
  625. Circles2 = _CustomObjectMarker("Circles2", _clad_to_engine_cozmo.CustomObjectMarker.Circles2)
  626. #: .. image:: ../images/custom_markers/SDK_3Circles.png
  627. Circles3 = _CustomObjectMarker("Circles3", _clad_to_engine_cozmo.CustomObjectMarker.Circles3)
  628. #: .. image:: ../images/custom_markers/SDK_4Circles.png
  629. Circles4 = _CustomObjectMarker("Circles4", _clad_to_engine_cozmo.CustomObjectMarker.Circles4)
  630. #: .. image:: ../images/custom_markers/SDK_5Circles.png
  631. Circles5 = _CustomObjectMarker("Circles5", _clad_to_engine_cozmo.CustomObjectMarker.Circles5)
  632. #: .. image:: ../images/custom_markers/SDK_2Diamonds.png
  633. Diamonds2 = _CustomObjectMarker("Diamonds2", _clad_to_engine_cozmo.CustomObjectMarker.Diamonds2)
  634. #: .. image:: ../images/custom_markers/SDK_3Diamonds.png
  635. Diamonds3 = _CustomObjectMarker("Diamonds3", _clad_to_engine_cozmo.CustomObjectMarker.Diamonds3)
  636. #: .. image:: ../images/custom_markers/SDK_4Diamonds.png
  637. Diamonds4 = _CustomObjectMarker("Diamonds4", _clad_to_engine_cozmo.CustomObjectMarker.Diamonds4)
  638. #: .. image:: ../images/custom_markers/SDK_5Diamonds.png
  639. Diamonds5 = _CustomObjectMarker("Diamonds5", _clad_to_engine_cozmo.CustomObjectMarker.Diamonds5)
  640. #: .. image:: ../images/custom_markers/SDK_2Hexagons.png
  641. Hexagons2 = _CustomObjectMarker("Hexagons2", _clad_to_engine_cozmo.CustomObjectMarker.Hexagons2)
  642. #: .. image:: ../images/custom_markers/SDK_3Hexagons.png
  643. Hexagons3 = _CustomObjectMarker("Hexagons3", _clad_to_engine_cozmo.CustomObjectMarker.Hexagons3)
  644. #: .. image:: ../images/custom_markers/SDK_4Hexagons.png
  645. Hexagons4 = _CustomObjectMarker("Hexagons4", _clad_to_engine_cozmo.CustomObjectMarker.Hexagons4)
  646. #: .. image:: ../images/custom_markers/SDK_5Hexagons.png
  647. Hexagons5 = _CustomObjectMarker("Hexagons5", _clad_to_engine_cozmo.CustomObjectMarker.Hexagons5)
  648. #: .. image:: ../images/custom_markers/SDK_2Triangles.png
  649. Triangles2 = _CustomObjectMarker("Triangles2", _clad_to_engine_cozmo.CustomObjectMarker.Triangles2)
  650. #: .. image:: ../images/custom_markers/SDK_3Triangles.png
  651. Triangles3 = _CustomObjectMarker("Triangles3", _clad_to_engine_cozmo.CustomObjectMarker.Triangles3)
  652. #: .. image:: ../images/custom_markers/SDK_4Triangles.png
  653. Triangles4 = _CustomObjectMarker("Triangles4", _clad_to_engine_cozmo.CustomObjectMarker.Triangles4)
  654. #: .. image:: ../images/custom_markers/SDK_5Triangles.png
  655. Triangles5 = _CustomObjectMarker("Triangles5", _clad_to_engine_cozmo.CustomObjectMarker.Triangles5)
  656. class FixedCustomObject():
  657. '''A fixed object defined by the SDK. It is given a pose and x,y,z sizes.
  658. This object cannot be observed by the robot so its pose never changes.
  659. The position is static in Cozmo's world view; once instantiated, these
  660. objects never move. This could be used to make Cozmo aware of objects and
  661. know to plot a path around them even when they don't have any markers.
  662. To create these use :meth:`~cozmo.world.World.create_custom_fixed_object`
  663. '''
  664. is_visible = False
  665. def __init__(self, pose, x_size_mm, y_size_mm, z_size_mm, object_id, *a, **kw):
  666. super().__init__(*a, **kw)
  667. self._pose = pose
  668. self._object_id = object_id
  669. self._x_size_mm = x_size_mm
  670. self._y_size_mm = y_size_mm
  671. self._z_size_mm = z_size_mm
  672. def __repr__(self):
  673. return ('<%s pose=%s object_id=%d x_size_mm=%.1f y_size_mm=%.1f z_size_mm=%.1f=>' %
  674. (self.__class__.__name__, self.pose, self.object_id,
  675. self.x_size_mm, self.y_size_mm, self.z_size_mm))
  676. #### Private Methods ####
  677. #### Event Wrappers ####
  678. #### Properties ####
  679. @property
  680. def object_id(self):
  681. '''int: The internal ID assigned to the object.
  682. This value can only be assigned once as it is static in the engine.
  683. '''
  684. return self._object_id
  685. @object_id.setter
  686. def object_id(self, value):
  687. if self._object_id is not None:
  688. raise ValueError("Cannot change object ID once set (from %s to %s)" % (self._object_id, value))
  689. logger.debug("Updated object_id for %s from %s to %s", self.__class__, self._object_id, value)
  690. self._object_id = value
  691. @property
  692. def pose(self):
  693. ''':class:`cozmo.util.Pose`: The pose of the object in the world.'''
  694. return self._pose
  695. @property
  696. def x_size_mm(self):
  697. '''float: The length of the object in its X axis, in millimeters.'''
  698. return self._x_size_mm
  699. @property
  700. def y_size_mm(self):
  701. '''float: The length of the object in its Y axis, in millimeters.'''
  702. return self._y_size_mm
  703. @property
  704. def z_size_mm(self):
  705. '''float: The length of the object in its Z axis, in millimeters.'''
  706. return self._z_size_mm
  707. #### Private Event Handlers ####
  708. #### Public Event Handlers ####
  709. #### Commands ####