ruishou.py 15 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443
  1. '''
  2. @Author : liujunshen
  3. @Ide : vscode
  4. @File : ruishou.py
  5. @Time : 2023/03/28 16:54:03
  6. '''
  7. import logging
  8. from socket import socket
  9. import struct
  10. import threading
  11. import time
  12. from typing import Optional
  13. from core.peripheral.hand.base import PeripheralHandBase
  14. from settings.config import settings
  15. hand_config = settings.config["hand_peripheral_parameter"]
  16. logger = logging.getLogger(__name__)
  17. def reconnect_decorator(func):
  18. """重新连接装饰器"""
  19. def inner(self, *args, **kwargs):
  20. try:
  21. return func(self, *args, **kwargs)
  22. except Exception:
  23. self.close()
  24. self._start_client()
  25. return func(self, *args, **kwargs)
  26. return inner
  27. class Constants:
  28. """睿手相关常量"""
  29. CMD_LOCATION = 3
  30. HANDSHAKE_CMD = 0x01
  31. HEARTBEAT_CMD = 0x02
  32. SET_CURRENT_CMD = 0x03
  33. MOTION_CONTROL_CMD = 0x04
  34. DRAFTING_ACTION_CMD = 0x05
  35. FINISH_ACTION_CMD = 0x06
  36. class SendPckLocation:
  37. """睿手发送信号功能对于位置"""
  38. HANDSHAKE_VERSION_H = 4
  39. HANDSHAKE_VERSION_L = 5
  40. SET_CURRENT_CMD = 4
  41. SET_CURRENT_CHANNEL = 5
  42. SET_CURRENT_VALUE = 6
  43. MOTION_CONTROL_HAND = 4
  44. MOTION_CONTROL_THUMB_BENDING = 5
  45. MOTION_CONTROL_INDEX_FINGER_BENDING = 6
  46. MOTION_CONTROL_MIDDLE_FINGER_BENDING = 7
  47. MOTION_CONTROL_RING_FINGER_BENDING = 8
  48. MOTION_CONTROL_LITTLE_FINGER_BENDING = 9
  49. MOTION_CONTROL_DURATION = 10
  50. DRAFTING_ACTION_HAND = 4
  51. DRAFTING_ACTION_IS_ELECTRIC = 5
  52. DRAFTING_ACTION_CHANNELS = 6
  53. DRAFTING_ACTION_CHANNEL_A_VALUE = 7
  54. DRAFTING_ACTION_CHANNEL_B_VALUE = 8
  55. DRAFTING_ACTION_DURATION = 9
  56. class RecvPckLocation:
  57. """睿手接收信号功能对于位置"""
  58. HANDSHAKE_STATUS = 4
  59. HANDSHAKE_REASON = 5
  60. SET_CURRENT_CHANNEL = 5
  61. SET_CURRENT_VALUE = 6
  62. MOTION_CONTROL_STATUS = 4
  63. MOTION_CONTROL_DURATION = 5
  64. DRAFTING_ACTION_STATUS = 4
  65. DRAFTING_ACTION_DURATION = 5
  66. FINISH_ACTION_STATUS = 4
  67. FINISH_ACTION_DURATION = 5
  68. class RecvStatus:
  69. """睿手接收信号功能响应状态"""
  70. HANDSHAKE_SUCCESS = 0x00
  71. HANDSHAKE_FAIL = 0x01
  72. HANDSHAKE_REASON_OTHER = 0x00
  73. HANDSHAKE_REASON_DIFF_VERSION = 0x01
  74. class PckValue:
  75. """睿手数据包值"""
  76. BOTH_HANDS = 0x01
  77. LEFT_HAND = 0x02
  78. RIGHT_HAND = 0x03
  79. class RuishouConnector:
  80. """睿手客户端
  81. 功能:连接;发送心跳包;开启线程发送信号;接收信号等
  82. """
  83. def __init__(self) -> None:
  84. self.__host = hand_config["hand_host"]
  85. self.__port = hand_config["hand_port"]
  86. self.__heart_interval = hand_config["hand_heart"]
  87. self.__hand_version = hand_config["hand_version"]
  88. self.__addr = (self.__host, self.__port)
  89. self.protocol = Protocol()
  90. self.sock = None
  91. self.is_connected = False
  92. def start_client(self):
  93. """启动客户端"""
  94. version_parm = {
  95. Constants.SendPckLocation.HANDSHAKE_VERSION_H:
  96. self.__hand_version[0],
  97. Constants.SendPckLocation.HANDSHAKE_VERSION_L:
  98. self.__hand_version[1]
  99. }
  100. sock = socket()
  101. # 链接服务端地址
  102. logger.info("Connecting to and hand peripheral...")
  103. self.sock = sock
  104. self.sock.connect(self.__addr)
  105. logger.info("Hand Peripheral connected successfully.")
  106. # TODO: 连接失败的logger
  107. self.sync_send_data("handshake", version_parm)
  108. self.is_connected = True
  109. logger.info("handshake...")
  110. # 向服务端发送心跳包
  111. send_heartbeat_t = threading.Thread(target=self.__send_beat_data,
  112. args=())
  113. # recv_t.setDaemon(True)
  114. # TODO: 目前启动线程接收会导致同步接口无法接受到数据(接收线程已接收), 后续可考虑上锁解决
  115. # recv_t.start()
  116. send_heartbeat_t.start()
  117. def create_send_t(self, send_data):
  118. """外部程序调用"""
  119. send_t = threading.Thread(target=self.send_data, args=(send_data,))
  120. send_t.start()
  121. send_t.join()
  122. def send_data(self, cmd, update=None):
  123. if update is None:
  124. update = dict()
  125. send_data = self.protocol.get_pck(cmd, update)
  126. self.sock.sendall(send_data)
  127. def sync_send_data(self, cmd, update=None):
  128. """同步接口"""
  129. if update is None:
  130. update = dict()
  131. send_data = self.protocol.get_pck(cmd, update)
  132. if send_data:
  133. self.sock.send(send_data)
  134. res = self.filter_recv_msg()
  135. return res
  136. else:
  137. return None
  138. def filter_recv_msg(self):
  139. times = 0
  140. res = {"msg": "fail"}
  141. while times <= 50:
  142. recv = self.sock.recv(1024)
  143. recv_ls = self.protocol.unpack_bytes(recv)
  144. for recv in recv_ls:
  145. res = self.protocol.parse_bytes(recv)
  146. if res["cmd"] != 2:
  147. return res
  148. times += 1
  149. return res
  150. def __send_beat_data(self):
  151. try:
  152. while True:
  153. self.is_connected = True
  154. self.send_data("heartbeat")
  155. time.sleep(self.__heart_interval)
  156. except Exception:
  157. logger.info(
  158. "Send beat data failed, Hand Peripheral may be disconnected.")
  159. self.is_connected = False
  160. def close_client(self):
  161. if self.sock:
  162. self.sock.close()
  163. self.is_connected = False
  164. class Protocol:
  165. """睿手封装/解析包"""
  166. def __init__(self) -> None:
  167. self.pck_map = {
  168. "handshake": [
  169. 0xAC, 0xAD, 0x05, 0x01, None, None, 0xFF, 0xFF, None, None
  170. ],
  171. "heartbeat": [
  172. 0xAC, 0xAD, 0x05, 0x02, 0xFF, 0xFF, 0xFF, 0xFF, None, None
  173. ],
  174. "default_current": [
  175. 0xAC, 0xAD, 0x05, 0x03, None, None, None, 0xFF, None, None
  176. ],
  177. "motion_control": [
  178. 0xAC, 0xAD, 0x08, 0x04, None, None, None, None, None, None,
  179. None, None, None
  180. ],
  181. "drafting_action": [
  182. 0xAC, 0xAD, 0x07, 0x05, None, None, None, None, None, None,
  183. None, None
  184. ],
  185. "finish_action": [
  186. 0xAC, 0xAD, 0x05, 0x06, 0xFF, 0xFF, 0xFF, 0xFF, None, None
  187. ]
  188. }
  189. # 映射结果命令对应结果文本
  190. self.recv_map = {
  191. "handshake": {
  192. "byte0": {
  193. 0x00: "success",
  194. 0x01: "fail"
  195. },
  196. "byte1": {
  197. 0x00: "other",
  198. 0x01: "diff version"
  199. }
  200. },
  201. "heartbeat": {""}
  202. }
  203. self.recv_head = (0xAE, 0xAF) # 睿手端数据包头
  204. @staticmethod
  205. def cal_checksum(data):
  206. b_checksum = sum(data).to_bytes(2, byteorder="big")
  207. byte_h, byte_l = struct.unpack("2B", b_checksum)
  208. return byte_h, byte_l
  209. def get_pck(self, cmd, update_pck=None) -> Optional[bytearray]:
  210. """根据命令,参数更新包组装数据包
  211. Args:
  212. cmd: 命令 ['handshake', 'heartbeat', 'default_current',
  213. 'motion_control', 'drafting_action', 'finish_action']
  214. update_pck: 更新数据:电流参数等
  215. Returns:符合协议的bytes指令
  216. """
  217. if update_pck is None:
  218. update_pck = dict()
  219. arr = self.pck_map.get(cmd, None)
  220. if not arr:
  221. return None
  222. for key in update_pck.keys():
  223. if key > len(arr):
  224. return None
  225. arr[key] = update_pck[key]
  226. if None in arr[:-2]:
  227. return None
  228. bs = bytearray(0)
  229. byte_h, byte_l = self.cal_checksum(arr[3:-2])
  230. arr[-2], arr[-1] = byte_h, byte_l
  231. for i in arr:
  232. bs += bytearray(i.to_bytes(1, byteorder="little"))
  233. return bs
  234. def unpack_bytes(self, recv_bytes) -> list:
  235. """将socket.recv数据按包头进行拆包
  236. Args:
  237. recv_bytes: 接收的数据包
  238. Returns: 分割后的命令数组
  239. """
  240. recv_list = struct.unpack_from(f"{len(recv_bytes)}B", recv_bytes)
  241. res_list = []
  242. for idx in range(len(recv_list) - 1):
  243. # 寻找包头
  244. if recv_list[idx] == self.recv_head[0] and (recv_list[idx + 1]
  245. == self.recv_head[1]):
  246. head_idx = idx
  247. data_len = recv_list[head_idx + 2] + 5
  248. res_list.append(recv_list[head_idx:data_len + head_idx])
  249. return res_list
  250. def parse_bytes(self, recv_nums) -> dict:
  251. """解析封装结果信息
  252. Args:
  253. recv_nums: 单个数据包数组
  254. Returns:封装后的结果信息
  255. """
  256. res = dict()
  257. res["cmd"] = recv_nums[Constants.CMD_LOCATION]
  258. if recv_nums[Constants.CMD_LOCATION] == Constants.HANDSHAKE_CMD:
  259. res["status"] = recv_nums[
  260. Constants.RecvPckLocation.HANDSHAKE_STATUS]
  261. res["reason"] = recv_nums[
  262. Constants.RecvPckLocation.HANDSHAKE_REASON]
  263. elif recv_nums[Constants.CMD_LOCATION] == Constants.SET_CURRENT_CMD:
  264. res["current_channel"] = recv_nums[
  265. Constants.RecvPckLocation.SET_CURRENT_CHANNEL]
  266. res["current_val"] = recv_nums[
  267. Constants.RecvPckLocation.SET_CURRENT_VALUE]
  268. elif recv_nums[Constants.CMD_LOCATION] == Constants.MOTION_CONTROL_CMD:
  269. res["motion_control_status"] = recv_nums[
  270. Constants.RecvPckLocation.MOTION_CONTROL_STATUS]
  271. res["motion_control_duration"] = recv_nums[
  272. Constants.RecvPckLocation.MOTION_CONTROL_DURATION]
  273. elif recv_nums[Constants.CMD_LOCATION] == Constants.DRAFTING_ACTION_CMD:
  274. res["drafting_action_status"] = recv_nums[
  275. Constants.RecvPckLocation.DRAFTING_ACTION_STATUS]
  276. res["drafting_action_duration"] = recv_nums[
  277. Constants.RecvPckLocation.DRAFTING_ACTION_DURATION]
  278. elif recv_nums[Constants.CMD_LOCATION] == Constants.FINISH_ACTION_CMD:
  279. res["drafting_action_status"] = recv_nums[
  280. Constants.RecvPckLocation.FINISH_ACTION_STATUS]
  281. res["drafting_action_duration"] = recv_nums[
  282. Constants.RecvPckLocation.FINISH_ACTION_DURATION]
  283. return res
  284. class RuishouClient(PeripheralHandBase):
  285. def __init__(self) -> None:
  286. self.connector = RuishouConnector()
  287. self.version = (0x01, 0x00) # 睿手版本
  288. self.model = None
  289. def _start_client(self, version=None):
  290. """启动连接"""
  291. if self.connector.is_connected:
  292. return 1, "already connect"
  293. if not version:
  294. version = self.version
  295. try:
  296. self.connector.start_client()
  297. return 1, "success connect"
  298. except ConnectionRefusedError as e:
  299. return 0, f"fail, {e}"
  300. def _set_current(self, channel, val):
  301. """调节预设电流"""
  302. if not self.connector.is_connected:
  303. return 0
  304. parm_d = dict()
  305. parm_d[Constants.SendPckLocation.SET_CURRENT_CHANNEL] = channel
  306. parm_d[Constants.SendPckLocation.SET_CURRENT_VALUE] = val
  307. parm_d[Constants.SendPckLocation.SET_CURRENT_CMD] = 1 # 开始
  308. self.connector.sync_send_data(cmd="default_current", update=parm_d)
  309. parm_d[Constants.SendPckLocation.SET_CURRENT_CMD] = 3 # 调节
  310. self.connector.sync_send_data(cmd="default_current", update=parm_d)
  311. parm_d[Constants.SendPckLocation.SET_CURRENT_CMD] = 2 # 结束
  312. self.connector.sync_send_data(cmd="default_current", update=parm_d)
  313. return 1
  314. @staticmethod
  315. def _change_hand_data(hand):
  316. if hand == "双手":
  317. return Constants.PckValue.BOTH_HANDS
  318. if hand == "左手":
  319. return Constants.PckValue.LEFT_HAND
  320. if hand == "右手":
  321. return Constants.PckValue.RIGHT_HAND
  322. @reconnect_decorator
  323. def _control_motion(self, grabbing_param):
  324. logger.info("Launch peripheral...")
  325. parm_d = {
  326. Constants.SendPckLocation.MOTION_CONTROL_HAND:
  327. self._change_hand_data(grabbing_param.hand_select),
  328. Constants.SendPckLocation.MOTION_CONTROL_THUMB_BENDING:
  329. grabbing_param.thumb,
  330. Constants.SendPckLocation.MOTION_CONTROL_INDEX_FINGER_BENDING:
  331. grabbing_param.index_finger,
  332. Constants.SendPckLocation.MOTION_CONTROL_MIDDLE_FINGER_BENDING:
  333. grabbing_param.middle_finger,
  334. Constants.SendPckLocation.MOTION_CONTROL_RING_FINGER_BENDING:
  335. grabbing_param.ring_finger,
  336. Constants.SendPckLocation.MOTION_CONTROL_LITTLE_FINGER_BENDING:
  337. grabbing_param.little_finger,
  338. Constants.SendPckLocation.MOTION_CONTROL_DURATION:
  339. grabbing_param.duration
  340. }
  341. res = self.connector.sync_send_data(cmd="motion_control", update=parm_d)
  342. logger.info("Launch peripheral success")
  343. return res
  344. @reconnect_decorator
  345. def _drafting_action(self, drafting_param):
  346. if not self.connector.is_connected:
  347. return 0
  348. parm_d = {
  349. Constants.SendPckLocation.DRAFTING_ACTION_HAND:
  350. drafting_param.hand_select,
  351. Constants.SendPckLocation.DRAFTING_ACTION_IS_ELECTRIC:
  352. drafting_param.is_electric,
  353. Constants.SendPckLocation.DRAFTING_ACTION_CHANNELS:
  354. drafting_param.draft_channel,
  355. Constants.SendPckLocation.DRAFTING_ACTION_CHANNEL_A_VALUE:
  356. drafting_param.a_channel_value,
  357. Constants.SendPckLocation.DRAFTING_ACTION_CHANNEL_B_VALUE:
  358. drafting_param.b_channel_value,
  359. Constants.SendPckLocation.DRAFTING_ACTION_DURATION:
  360. drafting_param.duration
  361. }
  362. res = self.connector.sync_send_data(cmd="motion_control", update=parm_d)
  363. return res
  364. def init(self):
  365. _, msg = self._start_client()
  366. ret = {"is_connected": self.connector.is_connected, "msg": msg}
  367. return ret
  368. def set_db_model(self, db_model):
  369. self.model = db_model
  370. def start(self, train):
  371. ret_msg = self._control_motion(train.hand_peripherals)
  372. return ret_msg
  373. def stop(self):
  374. res = self.connector.sync_send_data(cmd="finish_action")
  375. return res
  376. def status(self):
  377. status = {"is_connected": self.connector.is_connected}
  378. return status
  379. def close(self):
  380. self.connector.close_client()
  381. ret = {"is_connected": False}
  382. return ret