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.

clad_protocol.py 3.6KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113
  1. # Copyright (c) 2016 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. __all__ = []
  15. import asyncio
  16. import struct
  17. import sys
  18. from threading import Lock
  19. from . import logger_protocol
  20. LOG_ALL = 'all'
  21. if sys.byteorder != 'little':
  22. raise ImportError("Cozmo SDK doesn't support byte order '%s' - contact Anki support to request this", sys.byteorder)
  23. class CLADProtocol(asyncio.Protocol):
  24. '''Low level CLAD codec'''
  25. _send_mutex = Lock()
  26. clad_decode_union = None
  27. clad_encode_union = None
  28. _clad_log_which = None
  29. def __init__(self):
  30. super().__init__()
  31. self._buf = bytearray()
  32. self._abort_connection = False # abort connection on failed handshake, ignore subsequent messages!
  33. def connection_made(self, transport):
  34. self.transport = transport
  35. logger_protocol.debug('Connected to transport')
  36. def connection_lost(self, exc):
  37. logger_protocol.debug("Connnection to transport lost: %s" % exc)
  38. def data_received(self, data):
  39. self._buf.extend(data)
  40. # pull clad messages out
  41. while not self._abort_connection:
  42. msg = self.decode_msg()
  43. # must compare msg against None, not just "if not msg" as the latter
  44. # would match against any message with len==0 (which is the case
  45. # for deliberately empty messages where the tag alone is the signal).
  46. if msg is None:
  47. return
  48. name = msg.tag_name
  49. if self._clad_log_which is LOG_ALL or (self._clad_log_which is not None and name in self._clad_log_which):
  50. logger_protocol.debug('RECV %s', msg._data)
  51. self.msg_received(msg)
  52. def decode_msg(self):
  53. if len(self._buf) < 2:
  54. return None
  55. # TODO: handle error
  56. # messages are prefixed by a 2 byte length
  57. msg_size = struct.unpack_from('H', self._buf)[0]
  58. if len(self._buf) < 2 + msg_size:
  59. return None
  60. buf, self._buf = self._buf[2:2+msg_size], self._buf[2+msg_size:]
  61. try:
  62. return self.clad_decode_union.unpack(buf)
  63. except ValueError as e:
  64. logger_protocol.warn("Failed to decode CLAD message for buflen=%d: %s", len(buf), e)
  65. def eof_received(self):
  66. logger_protocol.info("EOF received on connection")
  67. def send_msg(self, msg, **params):
  68. if self.transport.is_closing():
  69. return
  70. name = msg.__class__.__name__
  71. msg = self.clad_encode_union(**{name: msg})
  72. msg_buf = msg.pack()
  73. msg_size = struct.pack('H', len(msg_buf))
  74. self._send_mutex.acquire()
  75. try:
  76. self.transport.write(msg_size)
  77. self.transport.write(msg_buf)
  78. if self._clad_log_which is LOG_ALL or (self._clad_log_which is not None and name in self._clad_log_which):
  79. logger_protocol.debug("SENT %s", msg)
  80. finally:
  81. self._send_mutex.release()
  82. def send_msg_new(self, msg):
  83. name = msg.__class__.__name__
  84. return self.send_msg(name, msg)
  85. def msg_received(self, msg):
  86. pass