#!/usr/bin/env python2.7
# coding:utf-8
# -*- coding: utf-8 -*-
"""
2021.12.31 底盘控制客户端
2022.1.6v
2022.1.7 增加手动转弯
"""
from socket import *
import json
import struct
import threading
import time
from serial_control_class import ser_c, con_s, ana
from correct_speed import Correct_val
import platform
import ctypes
import inspect
from status_total_data import s_data
from _chassis_logging_init import c_l,logger_motor
python_version = platform.python_version()[0:1]
"""2022 . 1.7 修改为主动检查掉线模式"""
"""底盘的客户端参数设置"""
class Client_link():
"""用于初始化。tcp客户端"""
def __init__(self,HOST = '127.0.0.1',PORT = 8081):
# self.HOST = HOST
# self.PORT = PORT
#self.HOST = "10.168.1.161" # 控制服务器地址
self.HOST = "10.168.1.147" # 控制服务器地址
# self.HOST = "127.0.0.1" # 控制服务器地址
self.PORT = 8888
self.repeat_num = 10 #设置当没有接收到完整数据帧的时候,重复连接的次数
self.init_motor()
#重连故障参数准备
self.no_action_bool = False # 用于控制重连不上的时候切换为遥控器模式
self.fail_link_num = 0 # 用于在主动线程中统计,设定的单位时间内没有发送成功的次数,越大说明当前越没有发出去
self.send_bool = False # 发送前置为假,发送后置为真
self.recycle_bool = False # 在成功拉起发送与接收线程后置为真
self.time_num = 5 # 5*0.2 即为设定为 1秒 堵塞等待时间
def init_motor(self):
"""用于初始化,关节电机控制参数"""
self.m1_angle_speed = 0
self.m1_cnt_speed = 0
self.m1_angle_dis = 0
self.m1_cnt_dis= 0
self.m2_angle_speed = 0
self.m2_cnt_speed = 0
self.m2_angle_dis = 0
self.m2_cnt_dis = 0
def infor_analysis(self,json_buf):
"""通过分析接收到的,下位机发送的json数据,对现有控制数据进行更新"""
if json_buf["status"] == "floor":
self.move_floor(json_buf)
elif json_buf["status"] == "Manu_m":
self.manual_control(json_buf)
else:
c_l.warning("!!!接收的不是底盘的控制命令(检测各客户端的地址分配是否正确!),当前控制命令为: " + str(json_buf))
#self.save_motor_c(json_buf["dict"]) 预留接口,方便后续该巡检项目的二次开发
# elif json_buf["Dete_m"] == "Odom_m":
def move_floor(self, json_buf={"status": "floor", "dict": {"routeinfo": [(1, 1), (4, 1), (4, 2)]}}):
"""解析路径规划的地面数据进行运动"""
location_list = json_buf["dict"]["routeinfo"]
#
# location_list = [(9,1),(4,1),(4,2)]
# location_list = [(1, 1), (4, 1), (4, 2),(7, 2)]
c_l.warning("________________低盘模式:" +str(location_list))
max_num = len(location_list)
self.time_num = 0.2/0.015
for num, val in enumerate(location_list):
if (num + 2) <= max_num:
if val[0] == location_list[num + 1][0]:
self.move_f_b(location_list[num + 1][1] - val[1])
else:
self.move_l_r(location_list[num + 1][0] - val[0])
def move_f_b(self, dis_num):
if dis_num > 0:
con_s.x = 0.015
con_s.y = 0
time.sleep(dis_num * self.time_num)
# 说明向前
else:
con_s.x = -0.015
con_s.y = 0
time.sleep(-dis_num * self.time_num)
con_s.x = 0
con_s.y = 0
def move_l_r(self, dis_num):
if dis_num > 0:
con_s.x = 0
con_s.y = 0.015
time.sleep(dis_num * self.time_num)
# 说明向前
else:
con_s.x = 0
con_s.y = -0.015
time.sleep(-dis_num * self.time_num)
con_s.x = 0
con_s.y = 0
def chesis_move_stop(self):
con_s.x = 0
con_s.y = 0
con_s.p = 0
def manual_control(self, json_buf):
"""用于手动控制(通过对接的客户端的数据更新),小车底盘的行动,目前只提供,简单的单一边行走"""
json_buf_dict = json_buf["dict"]
if json_buf_dict['Car_forward'] or json_buf_dict['Car_back'] or json_buf_dict['Car_left'] or json_buf_dict['Car_right']:
c_l.warning(str(json_buf_dict))
print("666")
if json_buf_dict["Car_forward"] == True:
c_l.warning("发送向前")
con_s.x = 0.015 # min:0.015 nor: 0.03 max:1.26 m/s
con_s.y = 0 # -0.1
#
elif json_buf_dict["Car_back"] == True:
c_l.warning("发送向后")
con_s.x = - 0.015 # min:0.015 nor: 0.03 max:1.26 m/s
con_s.y = 0 # -0.1
elif json_buf_dict["Car_left"] == True:
c_l.warning("发送向左")
con_s.x = 0 # min:0.015 nor: 0.03 max:1.26 m/s
con_s.y = 0.015 # -0.1
elif json_buf_dict["Car_right"] == True:
c_l.warning("发送向右")
con_s.x = 0 # min:0.015 nor: 0.03 max:1.26 m/s
con_s.y = -0.015 # -0.1
elif json_buf_dict["Car_turnleft"] == True:
c_l.warning("发送向左转")
con_s.x = 0 # min:0.015 nor: 0.03 max:1.26 m/s
con_s.y = 0 # -0.1
con_s.p = 0.028
elif json_buf_dict["Car_turnright"] == True:
c_l.warning("发送向右转")
con_s.x = 0 # min:0.015 nor: 0.03 max:1.26 m/s
con_s.y = 0 # -0.1
con_s.p = -0.028
elif json_buf_dict["Car_stop"] == True:
c_l.warning("发送-停止")
con_s.x = 0 # min:0.015 nor: 0.03 max:1.26 m/s
con_s.y = 0 # -0.1
con_s.p = 0
else:
c_l.warning("底盘停止!")
con_s.x = 0 # min:0.015 nor: 0.03 max:1.26 m/s
con_s.y = 0 # -0.1
con_s.p = 0
def save_motor_c(self, data_dict):
"""预留接口用于显示等"""
s_data.m1_angle_speed = data_dict["m1_a_speed"]
s_data.m1_cnt_speed = data_dict["m1_cnt_speed"]
s_data.m1_angle_dis = data_dict["m1_a_dis"]
s_data.m1_cnt_dis = data_dict["m1_cnt_dis"]
s_data.m2_angle_speed = data_dict["m2_a_speed"]
s_data.m2_cnt_speed = data_dict["m2_cnt_speed"]
s_data.m2_angle_dis = data_dict["m2_a_dis"]
s_data.m2_cnt_dis = data_dict["m2_cnt_dis"]
def start_client(self):
"""开启客户端"""
# 先拉起一个测量通讯时间阻塞的线程用于帮助重连
self.t_recycle = threading.Thread(target=self.recycle)
self.t_recycle.setDaemon(True) # 当发送模块长时间无法发送对接收与发送线程主动抛出异常,并调整底盘到遥控器模式
self.t_recycle.start()
self.link()
def pull_send_and_recv(self):
self.t_client_recv = threading.Thread(target=self.run_recv)
self.t_client_recv.setDaemon(True) # 当断开后,通通过主动抛出异常自动回收
self.t_client_recv.start()
self.t_client_send = threading.Thread(target=self.listen_send)
self.t_client_send.setDaemon(True) # 当断开后,通过主动抛出异常自动回收
self.t_client_send.start()
def recycle(self):
"""通过 送入异常引发线程奔溃控制 进行控制"""
while True:
if self.recycle_bool:
if self.send_bool:
self.fail_link_num = 0
else:
self.fail_link_num = self.fail_link_num + 1
time.sleep(0.2)
if self.fail_link_num > self.time_num: # 即2s内没有
try:
logger_motor.warning("堵塞超过"+str(self.time_num*0.2)+"秒,回收当前两个连接线程,重新连接服务端")
self._async_raise(self.t_client_recv.ident,SystemExit)
print("回收完成接收线程")
self._async_raise(self.t_client_send.ident, SystemExit)
print("回收完成发送线程")
except:
print("上次重连失败-----")
self.no_action_bool = True
self.recycle_bool = False
self.client.close()
print("开始重连......")
self.link()
else:
time.sleep(0.5)
def _async_raise(self,tid, exctype):
# todo 强制退出线程 忽略掉报错
"""raises the exception, performs cleanup if needed"""
tid = ctypes.c_long(tid)
if not inspect.isclass(exctype):
print(".." * 20)
exctype = type(exctype)
res = ctypes.pythonapi.PyThreadState_SetAsyncExc(tid, ctypes.py_object(exctype))
if res == 0:
print("0" * 10)
raise ValueError("通过检测延时函数主动抛入 变量异常 进入,用于终止当前线程")
elif res != 1:
print("1" * 10)
# """if it returns a number greater than one, you're in trouble,
# and you should call it again with exc=NULL to revert the effect"""
ctypes.pythonapi.PyThreadState_SetAsyncExc(tid, None)
raise SystemError("通过检测延时函数主动抛入 系统异常 进入,用于终止当前线程")
def link(self):
"""为防止连接服务端失败,这里拉起一个连接线程持续守护,当客户端收发线程,由于无法连接服务端失效的时候执行重新连接"""
if self.no_action_bool:
logger_motor.warning("准备重连为避免服务器损坏无法遥控控制小车底盘,现调整为 可被遥控模式")
ser_c.movement_moudle = "无动作模式"
try:
self.client = socket(AF_INET, SOCK_STREAM)
self.client.bind(('', 4449))
self.client.connect((self.HOST, self.PORT))
c_l.warning("成功连接服务端,准备拉起发送与监听线程...................")
# self.feed_back_control_bool = True # 用于控制,接收与发送线程能否反馈解锁当前的守护拉起线程
self.pull_send_and_recv()
except OSError as e:
if e.errno == 10048:
c_l.warning("已成功连接底盘端口:4441,自动跳过该重复连接")
else:
self.recycle_bool = True # 为真开启统计连接阻塞,如果发送线程的阻塞过长则回收掉两个线程
self.send_bool = False
print("重连失败......")
print(ser_c.movement_moudle)
print("*"*50)
def run_recv(self):
"""客户端监听程序"""
print('开始连接下位机服务端......') # 等待cilent的连接
while True:
try:
length = self.receive_real(4)
len_data = struct.unpack('i', length)
len_data = len_data[0]
buf = self.receive_real(len_data)
json_buf = json.loads(buf)
self.infor_analysis(json_buf)
except:
self.chesis_move_stop()
self._async_raise(self.t_client_send.ident, SystemExit)
print("回收完成发送线程313")
self.no_action_bool = True
self.recycle_bool = True
self.send_bool = False # 在上面回收了发送线程后,确保控制判断的重连的变量为假
logger_motor.error('主动抛出接收模块异常,终止接收线程,并通知守护线程重新拉起接收线程模块,'
'此时切换底盘模式为无动作模式,可以被遥控器控制')
# 主动抛出接收模块异常,引发接收异常,使得系统回收该监听线程
raise RuntimeError('主动抛出接收模块异常,终止接收线程,并通知守护线程重新拉起接收线程模块,'
'此时切换底盘模式为无动作模式,可以被遥控器控制')
def receive_real(self,len_data):
"""接收真实数据"""
""" len_data 为预先发过来的真实数据长度 """
buf = b''
recv_size = 0
recv_num = 0 # 接收次数
buf = self.client.recv(len_data)
while (len(buf) <len_data and recv_num < self.repeat_num ):
buftmp = self.client.recv(len_data - len(buf))
buf = buf + buftmp
recv_num = recv_num + 1
if len(buf) != len_data:
logger_motor("服务端应发送长度为" + str(len_data) +", 实际接收长度为: " + str(len(buf)) )
con_s.x = 0
con_s.y = 0
logger_motor.error("!!!!!!!!!!!返回的接收数据为错误数据,可能会引起错误!!!!!!!!!!!!!!")
return buf
def listen_send(self):
"""用于发送底盘里程计数值,注意这里为两组数据,一组为累加完成后的里程计数值,另一组为规定的清空缓冲区的数值。"""
data_dict = {"status": "chessis_s", "dict": {"point_x": 0, "point_y": 0, "point_p": 0, "point_x": 0, "point_y": 0, "point_p": 0}}
print("准备开启发送数据线程------")
while True:
data_dict["dict"]["point_x"] = ana.point_x
data_dict["dict"]["point_y"] = ana.point_y
data_dict["dict"]["point_p"] = ana.point_p
data_dict["dict"]["point_x1"] = ana.point_x1
data_dict["dict"]["point_y1"] = ana.point_y1
data_dict["dict"]["point_p1"] = ana.point_p1
self.send_data(data_dict)
ser_c.movement_moudle = "差速模式" # 数据发送到服务端成功切换为差速模式
self.send_bool = True #注意这里 self.send_bool 变量的位置,需要将为的部分处于延时发送时间占用最多的部分
time.sleep(0.3)
self.send_bool = False
def send_data(self,data):
"""用于发送数据"""
try:
data = json.dumps(data)
length = len(data)
len_data = struct.pack('i', length)
# 固定字节长度,告知数据长度为多少
if (len_data != b'') and (data.encode('utf-8') != b''):
self.client.sendall(len_data)
self.client.sendall(data.encode('utf-8'))
#2022 修改为 sendall 用于避免数据完整性,如需要交互高效选择send
# self.client.send(len_data)
# self.client.send(data.encode('utf-8'))
except:
self.chesis_move_stop()
self._async_raise(self.t_client_recv.ident, SystemExit)
print("回收完成接收线程420")
self.no_action_bool = True
self.recycle_bool = True
self.send_bool = False # 在上面回收了发送线程后,确保控制判断的重连的变量为假
logger_motor.error('主动抛出发送模块异常,终止发送线程,并通知守护线程重新拉起发送线程模块,'
'此时切换底盘模式为无动作模式,可以被遥控器控制')
# 主动抛出发送模块异常,引发接收异常,使得系统回收该监听线程
raise RuntimeError('主动抛出发送模块异常,终止发送线程,并通知守护线程重新拉起发送线程模块,'
'此时切换底盘模式为无动作模式,可以被遥控器控制')
def init_chessis():
"""用于初始化,底盘与里程计的,初始化参数"""
correct_v = Correct_val(ana=ana, con_s=con_s)
correct_v.time_interval = 0.2 # 设置瞬时速度的间隔时间,程序通过解析间隔时间内码盘移动的平均度数给出速度值
correct_v.ignore_num = 5 # 由于加速度的存在,在启动阶段的实时速度测量由于硬件原因会出现误差,设置忽略前多少次的速度计算值
con_s.x = 0
con_s.y = 0
con_s.p = 0
ser_c.interval_dis = 200000 # 里程计清零距离 单位 (毫米)
ser_c.set_safe_area(200000, 200000) # 设置一个矩形安全范围
ser_c.x_deviation_param = 5 # 当平移时x轴方向偏差超过该值开始进行纠偏
ser_c.y_deviation_param = 5 # 当平移时y轴方向偏差超过该值开始进行纠偏
ser_c.p_deviation_param = 5 # 当平移时角度方向偏差超过该值开始进行纠偏
ser_c.set_port('COM4') # 对应是拿铁熊猫左上角的那个usb口
ser_c.movement_moudle = "无动作模式" # 在连接服务端成功后会切换为差数模式,差速模式下无法被遥控器控制,连接断线自动切换为无动作模式
ser_c.open_serial_control()
# 设置差速模式下的角度纠偏,最大的角度转动速度
# ser_c.max_correct_p = 0.05 # rad/s #左右
# ser_c.turn_hint_rectifying_bool = True #开启运动纠偏模型
ser_c.error_x_y_min_num = 5 # mm
ser_c.error_p_min_num = 10
ser_c.max_correct_p = 0.3 # rad/s
ser_c.max_correct_x = 0.03 # m/s
ser_c.max_x_y_rang_change_num = 50 # mm(分母x y
ser_c.max_p_rang_change_num = 10 # mm(分母x y
print("..........")
print("打印当前时刻码盘的返回值")
print(ana.point_x)
print(ana.point_y)
print(ana.point_p)
print("..........")
if __name__ == '__main__':
# #初始化底盘驱动程序
# init_chessis()
#准备拉起 客户端
c = Client_link()
c.start_client()
c_l.warning("开启tcp客户端线程拉起完成")
while True:
time.sleep(20)
tcp客户端重连例子 python
最新推荐文章于 2023-08-10 15:33:57 发布