esp_local_ctrl.py 8.9 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273
  1. #!/usr/bin/env python
  2. #
  3. # Copyright 2018 Espressif Systems (Shanghai) PTE LTD
  4. #
  5. # Licensed under the Apache License, Version 2.0 (the "License");
  6. # you may not use this file except in compliance with the License.
  7. # You may obtain a copy of the License at
  8. #
  9. # http://www.apache.org/licenses/LICENSE-2.0
  10. #
  11. # Unless required by applicable law or agreed to in writing, software
  12. # distributed under the License is distributed on an "AS IS" BASIS,
  13. # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  14. # See the License for the specific language governing permissions and
  15. # limitations under the License.
  16. #
  17. from __future__ import print_function
  18. from future.utils import tobytes
  19. from builtins import input
  20. import os
  21. import sys
  22. import struct
  23. import argparse
  24. import proto
  25. try:
  26. import esp_prov
  27. except ImportError:
  28. idf_path = os.environ['IDF_PATH']
  29. sys.path.insert(1, idf_path + "/tools/esp_prov")
  30. import esp_prov
  31. # Set this to true to allow exceptions to be thrown
  32. config_throw_except = False
  33. # Property types enum
  34. PROP_TYPE_TIMESTAMP = 0
  35. PROP_TYPE_INT32 = 1
  36. PROP_TYPE_BOOLEAN = 2
  37. PROP_TYPE_STRING = 3
  38. # Property flags enum
  39. PROP_FLAG_READONLY = (1 << 0)
  40. def prop_typestr(prop):
  41. if prop["type"] == PROP_TYPE_TIMESTAMP:
  42. return "TIME(us)"
  43. elif prop["type"] == PROP_TYPE_INT32:
  44. return "INT32"
  45. elif prop["type"] == PROP_TYPE_BOOLEAN:
  46. return "BOOLEAN"
  47. elif prop["type"] == PROP_TYPE_STRING:
  48. return "STRING"
  49. return "UNKNOWN"
  50. def encode_prop_value(prop, value):
  51. try:
  52. if prop["type"] == PROP_TYPE_TIMESTAMP:
  53. return struct.pack('q', value)
  54. elif prop["type"] == PROP_TYPE_INT32:
  55. return struct.pack('i', value)
  56. elif prop["type"] == PROP_TYPE_BOOLEAN:
  57. return struct.pack('?', value)
  58. elif prop["type"] == PROP_TYPE_STRING:
  59. return tobytes(value)
  60. return value
  61. except struct.error as e:
  62. print(e)
  63. return None
  64. def decode_prop_value(prop, value):
  65. try:
  66. if prop["type"] == PROP_TYPE_TIMESTAMP:
  67. return struct.unpack('q', value)[0]
  68. elif prop["type"] == PROP_TYPE_INT32:
  69. return struct.unpack('i', value)[0]
  70. elif prop["type"] == PROP_TYPE_BOOLEAN:
  71. return struct.unpack('?', value)[0]
  72. elif prop["type"] == PROP_TYPE_STRING:
  73. return value.decode('latin-1')
  74. return value
  75. except struct.error as e:
  76. print(e)
  77. return None
  78. def str_to_prop_value(prop, strval):
  79. try:
  80. if prop["type"] == PROP_TYPE_TIMESTAMP:
  81. return int(strval)
  82. elif prop["type"] == PROP_TYPE_INT32:
  83. return int(strval)
  84. elif prop["type"] == PROP_TYPE_BOOLEAN:
  85. return bool(strval)
  86. elif prop["type"] == PROP_TYPE_STRING:
  87. return strval
  88. return strval
  89. except ValueError as e:
  90. print(e)
  91. return None
  92. def prop_is_readonly(prop):
  93. return (prop["flags"] & PROP_FLAG_READONLY) is not 0
  94. def on_except(err):
  95. if config_throw_except:
  96. raise RuntimeError(err)
  97. else:
  98. print(err)
  99. def get_transport(sel_transport, service_name):
  100. try:
  101. tp = None
  102. if (sel_transport == 'http'):
  103. example_path = os.environ['IDF_PATH'] + "/examples/protocols/esp_local_ctrl"
  104. cert_path = example_path + "/main/certs/rootCA.pem"
  105. tp = esp_prov.transport.Transport_HTTP(service_name, cert_path)
  106. elif (sel_transport == 'ble'):
  107. tp = esp_prov.transport.Transport_BLE(
  108. devname=service_name, service_uuid='0000ffff-0000-1000-8000-00805f9b34fb',
  109. nu_lookup={'esp_local_ctrl/version': '0001',
  110. 'esp_local_ctrl/session': '0002',
  111. 'esp_local_ctrl/control': '0003'}
  112. )
  113. return tp
  114. except RuntimeError as e:
  115. on_except(e)
  116. return None
  117. def version_match(tp, expected, verbose=False):
  118. try:
  119. response = tp.send_data('esp_local_ctrl/version', expected)
  120. return (response.lower() == expected.lower())
  121. except Exception as e:
  122. on_except(e)
  123. return None
  124. def get_all_property_values(tp):
  125. try:
  126. props = []
  127. message = proto.get_prop_count_request()
  128. response = tp.send_data('esp_local_ctrl/control', message)
  129. count = proto.get_prop_count_response(response)
  130. if count == 0:
  131. raise RuntimeError("No properties found!")
  132. indices = [i for i in range(count)]
  133. message = proto.get_prop_vals_request(indices)
  134. response = tp.send_data('esp_local_ctrl/control', message)
  135. props = proto.get_prop_vals_response(response)
  136. if len(props) != count:
  137. raise RuntimeError("Incorrect count of properties!")
  138. for p in props:
  139. p["value"] = decode_prop_value(p, p["value"])
  140. return props
  141. except RuntimeError as e:
  142. on_except(e)
  143. return []
  144. def set_property_values(tp, props, indices, values, check_readonly=False):
  145. try:
  146. if check_readonly:
  147. for index in indices:
  148. if prop_is_readonly(props[index]):
  149. raise RuntimeError("Cannot set value of Read-Only property")
  150. message = proto.set_prop_vals_request(indices, values)
  151. response = tp.send_data('esp_local_ctrl/control', message)
  152. return proto.set_prop_vals_response(response)
  153. except RuntimeError as e:
  154. on_except(e)
  155. return False
  156. if __name__ == '__main__':
  157. parser = argparse.ArgumentParser(add_help=False)
  158. parser = argparse.ArgumentParser(description="Control an ESP32 running esp_local_ctrl service")
  159. parser.add_argument("--version", dest='version', type=str,
  160. help="Protocol version", default='')
  161. parser.add_argument("--transport", dest='transport', type=str,
  162. help="transport i.e http or ble", default='http')
  163. parser.add_argument("--name", dest='service_name', type=str,
  164. help="BLE Device Name / HTTP Server hostname or IP", default='')
  165. parser.add_argument("-v", "--verbose", dest='verbose', help="increase output verbosity", action="store_true")
  166. args = parser.parse_args()
  167. if args.version != '':
  168. print("==== Esp_Ctrl Version: " + args.version + " ====")
  169. if args.service_name == '':
  170. args.service_name = 'my_esp_ctrl_device'
  171. if args.transport == 'http':
  172. args.service_name += '.local'
  173. obj_transport = get_transport(args.transport, args.service_name)
  174. if obj_transport is None:
  175. print("---- Invalid transport ----")
  176. exit(1)
  177. if args.version != '':
  178. print("\n==== Verifying protocol version ====")
  179. if not version_match(obj_transport, args.version, args.verbose):
  180. print("---- Error in protocol version matching ----")
  181. exit(2)
  182. print("==== Verified protocol version successfully ====")
  183. while True:
  184. properties = get_all_property_values(obj_transport)
  185. if len(properties) == 0:
  186. print("---- Error in reading property values ----")
  187. exit(4)
  188. print("\n==== Available Properties ====")
  189. print("{0: >4} {1: <16} {2: <10} {3: <16} {4: <16}".format(
  190. "S.N.", "Name", "Type", "Flags", "Value"))
  191. for i in range(len(properties)):
  192. print("[{0: >2}] {1: <16} {2: <10} {3: <16} {4: <16}".format(
  193. i + 1, properties[i]["name"], prop_typestr(properties[i]),
  194. ["","Read-Only"][prop_is_readonly(properties[i])],
  195. str(properties[i]["value"])))
  196. select = 0
  197. while True:
  198. try:
  199. inval = input("\nSelect properties to set (0 to re-read, 'q' to quit) : ")
  200. if inval.lower() == 'q':
  201. print("Quitting...")
  202. exit(5)
  203. invals = inval.split(',')
  204. selections = [int(val) for val in invals]
  205. if min(selections) < 0 or max(selections) > len(properties):
  206. raise ValueError("Invalid input")
  207. break
  208. except ValueError as e:
  209. print(str(e) + "! Retry...")
  210. if len(selections) == 1 and selections[0] == 0:
  211. continue
  212. set_values = []
  213. set_indices = []
  214. for select in selections:
  215. while True:
  216. inval = input("Enter value to set for property (" + properties[select - 1]["name"] + ") : ")
  217. value = encode_prop_value(properties[select - 1],
  218. str_to_prop_value(properties[select - 1], inval))
  219. if value is None:
  220. print("Invalid input! Retry...")
  221. continue
  222. break
  223. set_values += [value]
  224. set_indices += [select - 1]
  225. if not set_property_values(obj_transport, properties, set_indices, set_values):
  226. print("Failed to set values!")