web_socket_client.py 3.8 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109
  1. # Copyright 2015-2021 Espressif Systems (Shanghai) CO LTD
  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 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. import json
  15. import time
  16. from .output_helpers import red_print, yellow_print
  17. try:
  18. import websocket
  19. except ImportError:
  20. # This is needed for IDE integration only.
  21. pass
  22. class WebSocketClient(object):
  23. """
  24. WebSocket client used to advertise debug events to WebSocket server by sending and receiving JSON-serialized
  25. dictionaries.
  26. Advertisement of debug event:
  27. {'event': 'gdb_stub', 'port': '/dev/ttyUSB1', 'prog': 'build/elf_file'} for GDB Stub, or
  28. {'event': 'coredump', 'file': '/tmp/xy', 'prog': 'build/elf_file'} for coredump,
  29. where 'port' is the port for the connected device, 'prog' is the full path to the ELF file and 'file' is the
  30. generated coredump file.
  31. Expected end of external debugging:
  32. {'event': 'debug_finished'}
  33. """
  34. RETRIES = 3
  35. CONNECTION_RETRY_DELAY = 1
  36. def __init__(self, url): # type: (str) -> None
  37. self.url = url
  38. self._connect()
  39. def _connect(self): # type: () -> None
  40. """
  41. Connect to WebSocket server at url
  42. """
  43. self.close()
  44. for _ in range(self.RETRIES):
  45. try:
  46. self.ws = websocket.create_connection(self.url)
  47. break # success
  48. except NameError:
  49. raise RuntimeError('Please install the websocket_client package for IDE integration!')
  50. except Exception as e: # noqa
  51. red_print('WebSocket connection error: {}'.format(e))
  52. time.sleep(self.CONNECTION_RETRY_DELAY)
  53. else:
  54. raise RuntimeError('Cannot connect to WebSocket server')
  55. def close(self): # type: () -> None
  56. try:
  57. self.ws.close()
  58. except AttributeError:
  59. # Not yet connected
  60. pass
  61. except Exception as e: # noqa
  62. red_print('WebSocket close error: {}'.format(e))
  63. def send(self, payload_dict): # type: (dict) -> None
  64. """
  65. Serialize payload_dict in JSON format and send it to the server
  66. """
  67. for _ in range(self.RETRIES):
  68. try:
  69. self.ws.send(json.dumps(payload_dict))
  70. yellow_print('WebSocket sent: {}'.format(payload_dict))
  71. break
  72. except Exception as e: # noqa
  73. red_print('WebSocket send error: {}'.format(e))
  74. self._connect()
  75. else:
  76. raise RuntimeError('Cannot send to WebSocket server')
  77. def wait(self, expect_iterable): # type: (list) -> None
  78. """
  79. Wait until a dictionary in JSON format is received from the server with all (key, value) tuples from
  80. expect_iterable.
  81. """
  82. for _ in range(self.RETRIES):
  83. try:
  84. r = self.ws.recv()
  85. except Exception as e:
  86. red_print('WebSocket receive error: {}'.format(e))
  87. self._connect()
  88. continue
  89. obj = json.loads(r)
  90. if all([k in obj and obj[k] == v for k, v in expect_iterable]):
  91. yellow_print('WebSocket received: {}'.format(obj))
  92. break
  93. red_print('WebSocket expected: {}, received: {}'.format(dict(expect_iterable), obj))
  94. else:
  95. raise RuntimeError('Cannot receive from WebSocket server')