最新版本密胺计量代码

This commit is contained in:
2025-09-30 14:57:14 +08:00
parent c31aab594d
commit 6833bb7973
16 changed files with 767 additions and 1017 deletions

View File

@ -42,7 +42,7 @@ class PIDAlgorithm(ControlAlgorithm):
#误差500g 500*0.5 + 1000* 0.01 + [0.5*10] = 250 + 10 + ... = 260 #误差500g 500*0.5 + 1000* 0.01 + [0.5*10] = 250 + 10 + ... = 260
return max(self.min_speed, min(self.max_speed, output)), error # return max(self.min_speed, min(self.max_speed, output)), error #
# 不是真正的模糊控制算法,是一个简单的分段阈值控制
class FuzzyLogicAlgorithm(ControlAlgorithm): class FuzzyLogicAlgorithm(ControlAlgorithm):
def calculate_speed(self, current, target): def calculate_speed(self, current, target):
error = target - current error = target - current

View File

@ -51,11 +51,16 @@ class Controller:
def start(self): def start(self):
self.running = True self.running = True
# 创建线程列表包含5个并发任务
threads = [ threads = [
# 线程1重量读取循环
threading.Thread(target=self._weight_reading_loop), threading.Thread(target=self._weight_reading_loop),
threading.Thread(target=self._control_loop), threading.Thread(target=self._control_loop),
threading.Thread(target=self.network.start_server), threading.Thread(target=self.network.start_server),
# 线程4振动控制
threading.Thread(target=self._control_vibrate), threading.Thread(target=self._control_vibrate),
# 线程5看门狗线程未使用thread()包装)
self.watchdog self.watchdog
] ]
for t in threads: for t in threads:
@ -65,26 +70,27 @@ class Controller:
def _weight_reading_loop(self): def _weight_reading_loop(self):
while self.running: while self.running:
if self.sensor.read_weight(): if self.sensor.read_weight():
with self.sensor.lock: with self.sensor.lock: # 进入上下文,获取锁 获取传感器数据锁
self.current_weight = self.sensor.current_weight self.current_weight = self.sensor.current_weight # 在锁保护下操作共享数据
with self.status_lock: # 退出上下文,自动释放锁
with self.status_lock: # 获取传感器状态锁
self.network.status['current_weight'] = self.current_weight self.network.status['current_weight'] = self.current_weight
if self.current_weight >= self.network.status['target_weight']: if self.current_weight >= self.network.status['target_weight']:
self.network.status['measuring'] = False self.network.status['measuring'] = False
self.network.status['start_weighting'] = False self.network.status['start_weighting'] = False
self.network.status['weighting_isok'] = True self.network.status['weighting_isok'] = True # 标记称重完成
if self.network.status['set_tare'] == True: # 检查是否需要执行去皮操作
if self.network.status['set_tare'] == True: if self.sensor.tare(): # 执行传感器去皮
if self.sensor.tare(): self.network.status['set_tare_num_time'] += 1 # 增加去皮计数器
self.network.status['set_tare_num_time'] += 1
self.network.status['set_tare'] = False self.network.status['set_tare'] = False
# 主动获取重量请求处理
if self.network.status['get_weight'] == True: if self.network.status['get_weight'] == True:
with self.sensor.lock: with self.sensor.lock:
self.network.status['current_weight'] = self.sensor.current_weight self.network.status['current_weight'] = self.sensor.current_weight
self.network.status['get_weight'] = False self.network.status['get_weight'] = False
time.sleep(0.05) # 20Hz time.sleep(0.05) # 控制循环频率为20Hz每秒20次循环
# time.sleep(0.1) # 20Hz # time.sleep(0.1) # 20Hz
def _control_loop(self): def _control_loop(self):
@ -92,24 +98,24 @@ class Controller:
with self.status_lock: with self.status_lock:
measuring = self.network.status['measuring'] measuring = self.network.status['measuring']
target = self.network.status['target_weight'] target = self.network.status['target_weight']
algorithm = self.network.status['algorithm'] algorithm = self.network.status['algorithm'] # 使用的控制算法类型
direction_control = self.network.status['direction_control'] direction_control = self.network.status['direction_control'] # 电机方向控制标志
if direction_control== True: # 步进电机方向控制 if direction_control == True: # 步进电机方向控制
self.gpio._write_value(12, 1) self.gpio._write_value(12, 1) # 电机方向
self.network.status['direction_control'] = False self.network.status['direction_control'] = False
if measuring: if measuring:
self._select_algorithm(algorithm) self._select_algorithm(algorithm) # 选择控制算法
speed, error = self.algorithm.calculate_speed(self.current_weight, target) speed, error = self.algorithm.calculate_speed(self.current_weight, target)
rpm = speed # 假设速度单位直接对应RPM rpm = speed # 假设速度单位直接对应RPM(转/分钟)
pulse_rate = (int(self.config['gpio']['pulse_per_rev']) * rpm) / 60 pulse_rate = (int(self.config['gpio']['pulse_per_rev']) * rpm) / 60 # 计算脉冲频率
self.gpio.set_speed(8, pulse_rate, error) self.gpio.set_speed(8, pulse_rate, error) # 电机驱动
else: else:
self.gpio.set_speed(8, 0, 0) self.gpio.set_speed(8, 0, 0)
while not self.running and not self.network.status['measuring']: while not self.running and not self.network.status['measuring']:
time.sleep(0.1) # 短暂休眠避免CPU占用过高 time.sleep(0.1) # 短暂休眠避免CPU占用过高
time.sleep(0.1) time.sleep(0.1) # 以10hz的频率运行
def _control_vibrate(self): # 振动控制 def _control_vibrate(self): # 振动控制
while self.running: while self.running:
@ -124,7 +130,8 @@ class Controller:
self.network.status['set_vibrate'] = False self.network.status['set_vibrate'] = False
self.network.status['set_vibrate_time'] = 0 self.network.status['set_vibrate_time'] = 0
self.network.status['vibrate_isok'] = True self.network.status['vibrate_isok'] = True
time.sleep(1) time.sleep(1) # 每秒检查一次振动请求
def _select_algorithm(self, name): def _select_algorithm(self, name):
if name == 'pid': if name == 'pid':
params = self.config['algorithm']['params']['pid'] params = self.config['algorithm']['params']['pid']
@ -145,8 +152,8 @@ class Controller:
if not threads_alive: if not threads_alive:
logger.critical("检测到线程异常,触发紧急停止!") logger.critical("检测到线程异常,触发紧急停止!")
self.emergency_stop() self.emergency_stop() # 执行紧急停止操作
break break # 退出看门狗循环
time.sleep(5) time.sleep(5)

View File

@ -103,6 +103,7 @@ class GPIOManager:
except IOError as e: except IOError as e:
logger.error(f"GPIO写入错误: {str(e)}") logger.error(f"GPIO写入错误: {str(e)}")
def cleanup(self): def cleanup(self):
with self.lock: with self.lock:
for pin in self.pins: for pin in self.pins:

View File

@ -56,11 +56,11 @@ class NetworkHandler:
self.sock.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1) self.sock.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)
try: try:
self.sock.bind((self.host, self.port)) self.sock.bind((self.host, self.port))
self.sock.listen(5) self.sock.listen(5) # 最多接受多少个等待连接的客户端
logger.info(f"网络服务启动于 {self.host}:{self.port}") logger.info(f"网络服务启动于 {self.host}:{self.port}")
while self.running: while self.running:
conn, addr = self.sock.accept() conn, addr = self.sock.accept() # coon--数据sock
threading.Thread(target=self._handle_client, args=(conn, addr), daemon=True).start() threading.Thread(target=self._handle_client, args=(conn, addr), daemon=True).start()
except Exception as e: except Exception as e:
logger.error(f"网络服务异常: {str(e)}") logger.error(f"网络服务异常: {str(e)}")
@ -69,7 +69,7 @@ class NetworkHandler:
def _handle_client(self, conn, addr): def _handle_client(self, conn, addr):
try: try:
data = conn.recv(1024) data = conn.recv(1024) # 1024--指定从接收缓冲区里最多读取多少字节
if data: if data:
cmd = json.loads(data.decode()) cmd = json.loads(data.decode())
self._process_command(cmd) self._process_command(cmd)
@ -119,11 +119,17 @@ class NetworkHandler:
elif cmd.get('command') == 'set_zero': elif cmd.get('command') == 'set_zero':
self.status['set_tare'] = True self.status['set_tare'] = True
print("收到指令set_tare:", self.status['set_tare'])
elif cmd.get('command') == 'get_weight': elif cmd.get('command') == 'get_weight':
self.status['get_weight'] = True self.status['get_weight'] = True
print("收到指令get_weight:", self.status['get_weight'])
elif cmd.get('command') == 'set_vibrate': elif cmd.get('command') == 'set_vibrate':
self.status['set_vibrate'] = True self.status['set_vibrate'] = True
self.status['set_vibrate_time'] = cmd['payload']['time'] self.status['set_vibrate_time'] = cmd['payload']['time']
self.status['vibrate_isok'] = False
# 打印状态
print("振动状态 set_vibrate:", self.status['set_vibrate'])
print("振动时间 set_vibrate_time:", self.status['set_vibrate_time'])
self.status['vibrate_isok'] = False

View File

@ -6,14 +6,16 @@
驱动器rk引脚9接脉冲+rk引脚19接脉冲-引脚9对应代码pin8 驱动器rk引脚9接脉冲+rk引脚19接脉冲-引脚9对应代码pin8
振动平台rk引脚7接振动平台12rk引脚11接振动平台10rk引脚11对应代码pin9 振动平台rk引脚7接振动平台12(GND)rk引脚11接振动平台10rk引脚11对应代码pin9
舵机rk引脚
步进电机方向控制rk引脚17接驱动器方向+rk引脚17对应代码pin12驱动器方向-接rk引脚19。 步进电机方向控制rk引脚17接驱动器方向+rk引脚17对应代码pin12驱动器方向-接rk引脚19。
二、使用方法: 二、使用方法:
1、开启服务python main.py 1、开启服务python main.py
2、发送称量重量、获取重量、置零 python send_target.py 2、发送称量重量、获取重量、置零 python send_target1.py 或 测试控制振动平台 python send_vibration_control.py
三、发送端配置 send_target.py 三、发送端配置 send_target.py
@ -32,4 +34,44 @@ cmd_set_target = {
"set_tare_num_time": 0, "get_weight": false, "set_vibrate":true, "set_vibrate_time": 30} "set_tare_num_time": 0, "get_weight": false, "set_vibrate":true, "set_vibrate_time": 30}
四、秤资料 四、秤资料
链接 https://3121226.ma3you.cn/static/tourguide/content_show_v2/index.html?v=20250115&article_id=A022Emr#/ 链接 https://3121226.ma3you.cn/static/tourguide/content_show_v2/index.html?v=20250115&article_id=A022Emr#/
五、配置固定IP可修改/oem/usr/user_init.sh文件
```python
#!/bin/sh
# 自动配置固定 IP 和 DNS
# 配置网络
ifconfig eth0 192.168.1.100 netmask 255.255.255.0 up
route add default gw 192.168.1.1
# 配置 DNS
echo "nameserver 8.8.8.8" > /userdata/resolv.conf
# 动态覆盖只读文件 /etc/resolv.conf
mount --bind /userdata/resolv.conf /etc/resolv.conf
```
六、设置开机自启动某程序,可修改/oem/usr/user_init.sh文件官方留给用户添加程序自启动的地方
```python
python /userdata/helloword.py > /userdata/helloword.log 2>&1 &
```
执行结果保存在/userdata/helloword.log
## 可能会出现的问题
1、如果RK3506并没有出现运输的过程存在撞件的问题但是却出现如网口、串口等识别不了的情况
可能考虑重新刷一遍出厂固件(除了硬件坏了之外,其他的没有什么是不能通过刷机解决的)
## 测试3506上关于PWM设备树的引脚
```python
# 1. 查看系统中已注册的PWM控制器
ls /sys/class/pwm/ # 输出如pwmchip0、pwmchip1对应PWM0、PWM1控制器
# 2. 查看PWM0控制器的设备树配置需内核支持调试
cat /proc/device-tree/pwm@ff200000/pwm0_ch0/rockchip,pin # 查看PWM0_CH0绑定的引脚
# 输出可能类似“gpio0 0”对应GPIO0_A0参考文档2.6节引脚编号规则)
# 3. 查看引脚的功能复用状态确认是否为PWM功能
cat /sys/kernel/debug/pinctrl/pinctrl-rockchip:gpio0/pins | grep -A5 "gpio0-0"
# 输出中若有“function: pwm0_ch0”说明GPIO0_A0已复用为PWM0_CH0
```

View File

@ -51,15 +51,15 @@ class RS485Reader:
raise raise
def read_weight(self): def read_weight(self):
cmd = bytes.fromhex('010300000002c40b') cmd = bytes.fromhex('010300000002c40b') # 从地址 01 的设备读取 2 个保持寄存器,从地址 0 开始
try: try:
self.ser.write(cmd) self.ser.write(cmd)
response = self.ser.read(9) response = self.ser.read(9) # 然后读取设备返回的 9 字节响应
if len(response) == 9 and response[:3] == bytes.fromhex("010304"): if len(response) == 9 and response[:3] == bytes.fromhex("010304"):
weight = int.from_bytes(response[3:5], 'big') weight = int.from_bytes(response[3:5], 'big')
with self.lock: with self.lock:
self.buffer.append(weight) self.buffer.append(weight)
self.current_weight = sum(self.buffer) // len(self.buffer) self.current_weight = sum(self.buffer) // len(self.buffer) # 平滑滤波
return True return True
return False return False
except Exception as e: except Exception as e:
@ -68,7 +68,7 @@ class RS485Reader:
return False return False
def tare(self): # 去皮方法 def tare(self): # 去皮方法
cmd = bytes.fromhex('01060064000109d5') # 01 06 00 64 00 01 09 d5 cmd = bytes.fromhex('01060064000109d5') # 01 06 00 64 00 01 09 d5 发送命令给设备地址 1将寄存器地址 0x0064 设置为 0x0001执行“去皮”指令。
try: try:
if self.read_weight(): # 确保有最新数据 if self.read_weight(): # 确保有最新数据
self.ser.write(cmd) self.ser.write(cmd)

View File

@ -0,0 +1,5 @@
2025-04-03 15:21:08,435 - Gpio - ERROR - GPIO 8 <20><>ʼ<EFBFBD><CABC>ʧ<EFBFBD><CAA7>: [Errno 2] No such file or directory: '/sys/class/gpio/export'
2025-04-07 10:40:31,493 - Gpio - ERROR - GPIO 8 <20><>ʼ<EFBFBD><CABC>ʧ<EFBFBD><CAA7>: [Errno 2] No such file or directory: '/sys/class/gpio/export'
2025-05-13 14:04:13,116 - Gpio - ERROR - GPIO 8 <20><>ʼ<EFBFBD><CABC>ʧ<EFBFBD><CAA7>: [Errno 2] No such file or directory: '/sys/class/gpio/export'
2025-07-01 11:29:16,062 - Gpio - ERROR - GPIO 8 <20><>ʼ<EFBFBD><CABC>ʧ<EFBFBD><CAA7>: [Errno 2] No such file or directory: '/sys/class/gpio/export'
2025-07-01 11:32:40,605 - Gpio - ERROR - GPIO 8 <20><>ʼ<EFBFBD><CABC>ʧ<EFBFBD><CAA7>: [Errno 2] No such file or directory: '/sys/class/gpio/export'

View File

@ -9,7 +9,6 @@
import os import os
import time import time
class GPIOChipController: class GPIOChipController:
def __init__(self, chip_path="/sys/class/gpio/gpiochip0"): # 注意引脚分组 def __init__(self, chip_path="/sys/class/gpio/gpiochip0"): # 注意引脚分组
# 读取GPIO控制器信息 # 读取GPIO控制器信息

198
network_controller.py Normal file
View File

@ -0,0 +1,198 @@
#!/usr/bin/env python
# -*- coding: utf-8 -*-
'''
# @Time : 2025/7/1 11:07
# @Author : reenrr
# @File : network_controller.py
'''
import socket
import binascii
import time
# 网络继电器的 IP 和端口
HOST = '192.168.0.18'
PORT = 50000
# 控件命名映射
CONVEYOR1 = 'conveyor1'
PUSHER = 'pusher'
CONVEYOR2 = 'conveyor2'
CLAMP = 'clamp' # 机械臂抓夹,初始状态是开
# 传感器命名映射
SENSOR1 = 'sensor1'
SENSOR2 = 'sensor2'
# 按钮控制报文
valve_commands = {
CONVEYOR1: {
'open': '00000000000601050000FF00',
'close': '000000000006010500000000',
},
PUSHER: {
'open': '00000000000601050001FF00',
'close': '000000000006010500010000',
},
CONVEYOR2: {
'open': '00000000000601050002FF00',
'close': '000000000006010500020000',
},
CLAMP: {
'open': '00000000000601050003FF00',
'close': '000000000006010500030000',
}
}
sensors_commands = {
SENSOR1: {
'signal': '00000000000401020101',
'nosignal': '00000000000401020100',
},
SENSOR2: {
'signal': '00000000000401020102',
'nosignal': '00000000000401020100',
},
}
# 读取状态命令
read_status_command = {
'devices':'000000000006010100000008',
'sensors':'000000000006010200000008'
}
# 控件对应 DO 位(从低到高)
device_bit_map = {
CONVEYOR1: 0,
PUSHER: 1,
CONVEYOR2: 2,
CLAMP: 3,
}
device_name_map = {
CONVEYOR1: "传送带1",
PUSHER: "推板",
CONVEYOR2: "传送带2",
CLAMP: "机械臂抓夹"
}
# 传感器对应位(从低到高)
sensor_bit_map = {
SENSOR1: 0,
SENSOR2: 1,
# 根据你继电器的配置,继续添加更多传感器
}
sensor_name_map = {
SENSOR1: '位置传感器1',
SENSOR2: '位置传感器2',
}
# 将十六进制字符串转换为字节数据并发送
def send_command(command):
byte_data = binascii.unhexlify(command)
# 创建套接字并连接到继电器
with socket.socket(socket.AF_INET, socket.SOCK_STREAM) as sock:
try:
sock.connect((HOST, PORT))
sock.send(byte_data)
# 接收响应
response = sock.recv(1024)
# print(f"收到响应: {binascii.hexlify(response)}")
# 校验响应
return response
except Exception as e:
print(f"通信错误: {e}")
return False
def get_all_device_status(command_type='devices'):
command = read_status_command.get(command_type)
if not command:
print(f"未知的读取类型: {command_type}")
return {}
response = send_command(command)
status_dict = {}
if response and len(response) >= 10:
status_byte = response[9] # 状态在第10字节
status_bin = f"{status_byte:08b}"[::-1]
if command_type == 'devices':
bit_map = device_bit_map
name_map = device_name_map
elif command_type == 'sensors':
bit_map = sensor_bit_map
name_map = sensor_name_map
else:
print("不支持的映射类型")
return{}
for key, bit_index in bit_map.items():
state = status_bin[bit_index] == '1'
status_dict[key] = state
# readable = "开启" if state else "关闭"
# print(f"{device.capitalize()} 状态: {readable}")
else:
print("读取状态失败或响应无效")
return status_dict
# 获取单个控件状态True: 开启, False: 关闭, None: 无法读取)
def get_device_status(device_name, command_type='devices'):
status = get_all_device_status(command_type)
return status.get(device_name, None)
# 根据状态决定是否执行开操作
def open(conveyor1=False, pusher=False, conveyor2=False, clamp=False):
status = get_all_device_status()
if conveyor1 and not status.get(CONVEYOR1, False):
print("打开传送带1")
send_command(valve_commands[CONVEYOR1]['open'])
time.sleep(1)
if pusher and not status.get(PUSHER, False):
print("打开推板")
send_command(valve_commands[PUSHER]['open'])
time.sleep(0.05)
if conveyor2 and not status.get(CONVEYOR2, False):
print("打开传送带2")
send_command(valve_commands[CONVEYOR2]['open'])
time.sleep(1)
if clamp and not status.get(CLAMP, False):
print("启动机械臂")
send_command(valve_commands[CLAMP]['open'])
time.sleep(0.5)
# 根据状态决定是否执行关操作
def close(conveyor1=False, pusher=False, conveyor2=False, clamp=False):
status = get_all_device_status()
if conveyor1 and status.get(CONVEYOR1, True):
print("关闭传送带1")
send_command(valve_commands[CONVEYOR1]['close'])
time.sleep(1)
if pusher and status.get(PUSHER, True):
print("关闭推板")
send_command(valve_commands[PUSHER]['close'])
time.sleep(0.05)
if conveyor2 and status.get(CONVEYOR2, True):
print("关闭传送带2")
send_command(valve_commands[CONVEYOR2]['close'])
time.sleep(1)
if clamp and status.get(CLAMP, True):
print("停止机械臂")
send_command(valve_commands[CLAMP]['close'])
time.sleep(0.5)

View File

@ -9,11 +9,14 @@ import socket
import json import json
import time import time
target_weight = 200
set_vibrate_time = 10
cmd_set_target = { cmd_set_target = {
# 称量 # 称量
"command": "set_target", "command": "set_target",
"payload": { "payload": {
"target_weight": 200, "target_weight": target_weight,
"algorithm": "pid", "algorithm": "pid",
"direction_control": False "direction_control": False
} }
@ -30,45 +33,54 @@ cmd_get_weight = {
cmd_set_zero = { cmd_set_zero = {
# 去皮 # 去皮
"command": "set_zero" "command": "set_zero",
# "payload": { "payload": {
# "target_weight": 200, # "target_weight": 200,
# "algorithm": "pid" # "algorithm": "pid"
# } }
} }
cmd_set_vibrate = { # 振动控制 cmd_set_vibrate = { # 振动控制
"command": "set_vibrate", "command": "set_vibrate",
"payload": { "payload": {
"time": 0 # 单位S "time": set_vibrate_time # 单位S
} }
} }
# 使用 with 语句确保 socket 在使用完毕后正确关闭 # 使用 with 语句确保 socket 在使用完毕后正确关闭
with socket.socket() as s: with socket.socket() as s:
s.connect(('127.0.0.1', 5000)) s.connect(('127.0.0.1', 5000))
# 发送命令 # 发送称重命令
try: try:
s.sendall(json.dumps(cmd_set_target).encode()) s.sendall(json.dumps(cmd_set_target).encode())
print("已发送目标值设定指令")
start_time = time.time()
# 接收数据
while True:
data = s.recv(1024)
if not data:
print("连接已关闭")
break
decoded = data.decode()
print(f"收到数据: {decoded}")
try:
response = json.loads(decoded)
current_weight = response.get("current_weight")
if current_weight is not None:
print(f"当前重量:{current_weight}")
if current_weight >= target_weight:
print("目标已达到,发送振动控制指令")
s.sendall(json.dumps(cmd_set_vibrate).encode())
break
except socket.error as e:
print(f"接收数据时发生错误: {e}")
# break # 如果接收失败,退出循环
except socket.error as e: except socket.error as e:
print(f"发送数据时发生错误: {e}") print(f"发送数据时发生错误: {e}")
# break # 如果发送失败,退出循环 # break # 如果发送失败,退出循环
start_time = time.time()
# 接收数据
while True:
try:
data = s.recv(1024)
if not data:
print("没有收到数据,连接可能已关闭")
# break # 如果没有数据,退出循环
time.sleep(1)
print(data.decode())
end_time = time.time()
elapsed_time = end_time - start_time
print(f"代码执行时间:{elapsed_time:.6f}")
break
except socket.error as e:
print(f"接收数据时发生错误: {e}")
#break # 如果接收失败,退出循环

99
send_target1.py Normal file
View File

@ -0,0 +1,99 @@
#!/usr/bin/env python
# -*- coding: utf-8 -*-
'''
# @Time : 2025/6/18 10:27
# @Author : reenrr
# @File : send_target-test.py
'''
import socket
import json
import time
target_weight = 200
set_vibrate_time = 10
cmd_set_target = {
# 称量
"command": "set_target",
"payload": {
"target_weight": target_weight,
"algorithm": "pid",
"direction_control": False
}
}
cmd_get_weight = {
# 获取重量
"command": "get_weight"
}
cmd_set_zero = {
# 去皮
"command": "set_zero",
"payload": {
}
}
cmd_set_vibrate = { # 振动控制
"command": "set_vibrate",
"payload": {
"time": set_vibrate_time # 单位S
}
}
def start_weight(host='127.0.0.1', port=5000):
with socket.socket() as s:
try:
s.connect((host, port))
s.sendall(json.dumps(cmd_set_zero).encode())
print("已发送去皮指令")
time.sleep(1) # 等待去皮完成
s.sendall(json.dumps(cmd_get_weight).encode())
print("请求当前重量...")
data = s.recv(1024)
response = json.loads(data.decode())
current_weight = response.get("current_weight")
print(f"当前重量:{current_weight}")
except Exception as e:
print(f"操作过程中发生异常:{e}")
with socket.socket() as s:
try:
if isinstance(current_weight, (int, float)) and abs(current_weight) < 0.01:
print("重量为0开始称量流程")
s.sendall(json.dumps(cmd_set_target).encode())
print("已发送目标值设定指令")
while True:
try:
data = s.recv(1024)
if not data:
print("连接已关闭")
break
response = json.loads(data.decode())
current_weight = response.get("current_weight")
if current_weight is not None:
print(f"当前重量:{current_weight}")
if current_weight >= target_weight:
print("目标已达到")
break
except Exception as e:
print(f"接收或解析重量数据失败:{e}")
break
else:
print("当前重量不为0中止称量流程请检查设备")
except Exception as e:
print(f"操作过程中发生异常:{e}")
if __name__ == "__main__":
if start_weight(host='127.0.0.1', port=5000):
print("称重完毕")
else:
print("称重发生错误")
exit(-1)

67
send_vibration_control.py Normal file
View File

@ -0,0 +1,67 @@
#!/usr/bin/env python
# -*- coding: utf-8 -*-
'''
# @Time : 2025/6/18 10:27
# @Author : reenrr
# @File : send_target-test.py
'''
import socket
import json
import time
target_weight = 200
set_vibrate_time = 10
cmd_set_target = {
# 称量
"command": "set_target",
"payload": {
"target_weight": target_weight,
"algorithm": "pid",
"direction_control": False
}
}
cmd_get_weight = {
# 获取重量
"command": "get_weight"
# "payload": {
# "target_weight": 200,
# "algorithm": "pid"
# }
}
cmd_set_zero = {
# 去皮
"command": "set_zero",
"payload": {
# "target_weight": 200,
# "algorithm": "pid"
}
}
cmd_set_vibrate = { # 振动控制
"command": "set_vibrate",
"payload": {
"time": set_vibrate_time # 单位S
}
}
# 使用 with 语句确保 socket 在使用完毕后正确关闭
with socket.socket() as s:
try:
s.connect(('127.0.0.1', 5000))
s.sendall(json.dumps(cmd_set_vibrate).encode())
print("已发送振动控制指令")
# level = gpio.read()
# print(f"GPIO 9 当前电平值为: {level}")
# 接收响应(可选)
data = s.recv(1024)
if data:
print("收到响应:", data.decode())
else:
print("无响应,连接可能已关闭")
except socket.error as e:
print(f"通信错误: {e}")

108
servo_motor_IO.py Normal file
View File

@ -0,0 +1,108 @@
#!/usr/bin/env python
# -*- coding: utf-8 -*-
'''
# @Time : 2025/9/23 18:21
# @Author : reenrr
# @File : servo_motor.py
# 功能描述RK3506通过GPIO模拟PWM控制DS3235舵机模拟持续旋转
# 参考文档DS3235 Coreless datasheet.pdf、接线图.docx
'''
import time
import signal
from periphery import GPIO
# -------------------------- 1. 硬件配置(必须根据实际接线调整) --------------------------
# 1.1 舵机PWM信号输出引脚RK3506的GPIO编号需与硬件接线对应
# 例若舵机信号线接GPIO3_B2_d对应Linux GPIO编号为10参考你提供的vibration_control-test.py
SERVO_GPIO_NUM = 10 # 替换为你的实际舵机信号引脚GPIO编号
# 1.2 DS3235舵机核心参数来自DS3235规格书
PWM_PERIOD_MS = 20 # PWM周期20ms规格书4-6条“控制频率50Hz”1/50Hz=20ms
MIN_PULSE_US = 500 # 最小脉宽500μsec对应0°规格书4-2条
MAX_PULSE_US = 2500 # 最大脉宽2500μsec对应180°规格书4-2条
ROTATE_DELAY = 0.1 # 角度切换间隔越小转动越“连续”建议0.1-0.5秒)
# 1.3 舵机角度模式选择(根据需求切换,参考接线图.docx
MODE = "180" # 180°模式500μsec=0°2500μsec=180°
# -------------------------- 2. 全局变量初始化 --------------------------
gpio = None # 舵机GPIO引脚对象
is_running = True # 程序运行标志
# -------------------------- 3. PWM信号生成函数 --------------------------
def set_servo_pulse(pulse_us):
'''
生成指定脉宽的PWM信号通过GPIO高低电平模拟
参数pulse_us - 高电平脉宽(单位:微秒)
'''
global gpio
# 脉宽范围限制(避免舵机过载,严格遵循规格书)
pulse_us = max(MIN_PULSE_US, min(MAX_PULSE_US, pulse_us))
# 1. 输出高电平持续pulse_us微秒
gpio.write(True)
time.sleep(pulse_us / 1000000) # 转换为秒单位
# 2. 输出低电平补全PWM周期20ms - 高电平时间)
low_time_ms = PWM_PERIOD_MS - (pulse_us / 1000) # 低电平时间(毫秒)
gpio.write(False)
time.sleep(low_time_ms / 1000) # 转换为秒单位
# -------------------------- 4. 模拟持续旋转函数 --------------------------
def continuous_rotate_sim():
'''
模拟持续旋转交替输出“最小脉宽”和“最大脉宽180°
'''
global is_running
print(f"DS3235舵机已启动{MODE}°模式按Ctrl+C停止...")
while is_running:
# 第一步输出最小脉宽对应0°
set_servo_pulse(MIN_PULSE_US)
print(f"当前脉宽:{MIN_PULSE_US}μsec")
time.sleep(ROTATE_DELAY)
# 第二步输出最大脉宽对应180°/270°
set_servo_pulse(MAX_PULSE_US)
if MODE == "180":
print(f"当前脉宽:{MAX_PULSE_US}μsec180°")
else:
print(f"当前脉宽:{MAX_PULSE_US}μsec270°")
time.sleep(ROTATE_DELAY)
# -------------------------- 5. 信号处理Ctrl+C优雅退出 --------------------------
def signal_handler(signum, frame):
'''捕获Ctrl+C信号设置退出标志'''
global is_running
print("\n收到停止指令,正在清理资源...")
is_running = False
# -------------------------- 6. 主函数 --------------------------
if __name__ == "__main__":
# 1. 注册信号处理确保Ctrl+C能正常退出
signal.signal(signal.SIGINT, signal_handler)
signal.signal(signal.SIGTERM, signal_handler)
try:
# 2. 初始化舵机GPIO引脚输出模式参考你的vibration_control-test.py风格
gpio = GPIO(SERVO_GPIO_NUM, "out")
print(f"舵机GPIO引脚编号{SERVO_GPIO_NUM})初始化完成")
# 3. 启动模拟持续旋转
continuous_rotate_sim()
except Exception as e:
# 异常处理如GPIO初始化失败
print(f"程序异常:{str(e)}")
finally:
# 4. 资源清理必做避免GPIO引脚异常
if gpio is not None:
gpio.write(False) # 程序结束前拉低GPIO避免舵机持续受力
gpio.close()
print("舵机GPIO引脚已关闭")
print("程序退出完成")

138
servo_motor_PWM.py Normal file
View File

@ -0,0 +1,138 @@
#!/usr/bin/env python
# -*- coding: utf-8 -*-
'''
# @Time : 2025/9/24 16:55
# @Author : reenrr
# @File : servo_motor_PWM.py
'''
#!/usr/bin/env python
# -*- coding: utf-8 -*-
'''
# @Time : 2025/9/24 10:30
# @Author : reenrr
# @File : servo_motor_pwm.py
# 功能描述RK3506通过硬件PWM控制DS3235舵机模拟持续旋转
# 参考文档DS3235 Coreless datasheet.pdf、瑞芯微RK3506 PWM控制器文档
'''
import time
import signal
import os
# -------------------------- 1. 硬件配置(必须根据实际接线调整) --------------------------
# 1.1 舵机PWM通道RK3506的PWM通道需与硬件接线对应
# 瑞芯微RK3506通常有多个PWM通道如pwm0, pwm1等根据实际使用的通道修改
PWM_CHIP = 0 # PWM芯片编号
PWM_CHANNEL = 0 # PWM通道编号
PWM_PATH = f"/sys/class/pwm/pwmchip{PWM_CHIP}/pwm{PWM_CHANNEL}" # 已经通过设备树绑定到了相关的引脚上了
# 1.2 DS3235舵机核心参数来自DS3235规格书
PWM_PERIOD_MS = 20 # PWM周期20ms规格书4-6条“控制频率50Hz”1/50Hz=20ms
MIN_PULSE_US = 500 # 最小脉宽500μsec对应0°规格书4-2条
MAX_PULSE_US = 2500 # 最大脉宽2500μsec对应180°规格书4-2条
ROTATE_DELAY = 0.1 # 角度切换间隔越小转动越“连续”建议0.1-0.5秒)
# 1.3 舵机角度模式选择
MODE = "180" # 180°模式
# -------------------------- 2. 全局变量初始化 --------------------------
is_running = True # 程序运行标志
# -------------------------- 3. 硬件PWM控制函数 --------------------------
def export_pwm():
"""导出PWM通道使能PWM功能"""
if not os.path.exists(PWM_PATH):
with open(f"/sys/class/pwm/pwmchip{PWM_CHIP}/export", "w") as f:
f.write(str(PWM_CHANNEL))
# 等待文件系统创建完成
time.sleep(0.1)
def unexport_pwm():
"""取消导出PWM通道禁用PWM功能"""
if os.path.exists(PWM_PATH):
with open(f"/sys/class/pwm/pwmchip{PWM_CHIP}/unexport", "w") as f:
f.write(str(PWM_CHANNEL))
def set_pwm_period(period_ns):
"""设置PWM周期单位纳秒"""
with open(f"{PWM_PATH}/period", "w") as f:
f.write(str(period_ns))
def set_pwm_duty_cycle(duty_ns):
"""设置PWM占空比单位纳秒"""
# 确保占空比在有效范围内
duty_ns = max(MIN_PULSE_US, min(MAX_PULSE_US, duty_ns))
with open(f"{PWM_PATH}/duty_cycle", "w") as f:
f.write(str(duty_ns))
def enable_pwm(enable=True):
"""启用或禁用PWM输出"""
with open(f"{PWM_PATH}/enable", "w") as f:
f.write("1" if enable else "0")
def set_servo_angle(pulse_ns):
"""通过设置脉宽控制舵机角度"""
set_pwm_duty_cycle(pulse_ns)
# -------------------------- 4. 模拟持续旋转函数 --------------------------
def continuous_rotate_sim():
"""模拟持续旋转:在最小和最大脉宽之间交替切换"""
global is_running
print(f"DS3235舵机已启动{MODE}°模式硬件PWM控制按Ctrl+C停止...")
# 初始化PWM
export_pwm()
set_pwm_period(PWM_PERIOD_MS)
enable_pwm(True)
try:
while is_running:
# 输出最小脉宽对应0°
set_servo_angle(MIN_PULSE_US)
print(f"当前脉宽:{MIN_PULSE_US}ns")
time.sleep(ROTATE_DELAY)
# 输出最大脉宽对应180°
set_servo_angle(MAX_PULSE_US)
if MODE == "180":
print(f"当前脉宽:{MAX_PULSE_US}ns180°")
else:
print(f"当前脉宽:{MAX_PULSE_US}ns270°")
time.sleep(ROTATE_DELAY)
finally:
# 关闭PWM输出
enable_pwm(False)
# -------------------------- 5. 信号处理Ctrl+C优雅退出 --------------------------
def signal_handler(signum, frame):
"""捕获Ctrl+C信号设置退出标志"""
global is_running
print("\n收到停止指令,正在清理资源...")
is_running = False
# -------------------------- 6. 主函数 --------------------------
if __name__ == "__main__":
# 注册信号处理
signal.signal(signal.SIGINT, signal_handler)
signal.signal(signal.SIGTERM, signal_handler)
try:
# 启动模拟持续旋转
continuous_rotate_sim()
except Exception as e:
print(f"程序异常:{str(e)}")
finally:
# 资源清理
unexport_pwm()
print("舵机PWM通道已关闭")
print("程序退出完成")

462
test.py
View File

@ -1,448 +1,38 @@
#!/usr/bin/env python #!/usr/bin/env python
# -*- coding: utf-8 -*- # -*- coding: utf-8 -*-
''' '''
# @Time : 2025/2/14 14:43 # @Time : 2025/9/24 14:07
# @Author : hjw # @Author : reenrr
# @File : test.py # @File : test.py
''' '''
import os #!/usr/bin/env python
# -*- coding: utf-8 -*-
'''
# @Time : 2025/6/17 09:26
# @Author : reenrr
# @File : vibration_control-test.py
'''
import time import time
import threading from periphery import GPIO
import serial
import socket
import logging
import json
from queue import Queue
from abc import ABC, abstractmethod
from collections import deque
# 配置日志系统 # GPIO0_B2_d 对应的 Linux GPIO 编号为 10
logging.basicConfig( gpio = GPIO(10, "out") # 设置为输出模式
level=logging.INFO,
format='%(asctime)s - %(name)s - %(levelname)s - %(message)s',
handlers=[
logging.FileHandler('controller.log'),
logging.StreamHandler()
]
)
logger = logging.getLogger(__name__)
try:
print("开始电平切换 (按 Ctrl+C 停止)...")
while True:
gpio.write(True) # 输出高电平
print("高电平")
time.sleep(10) # 保持 1 秒
def parse_config(file_path): gpio.write(False) # 输出低电平
config = {} print("低电平")
current_level = [config] # 使用栈来跟踪当前层级的字典 time.sleep(5)
indent_stack = [] # 记录缩进层级
with open(file_path, 'r', encoding='utf-8') as file: except KeyboardInterrupt:
for line in file: print("已手动停止")
line = line.rstrip() # 去掉行尾的换行符和空格
if not line or line.startswith('#'): # 跳过空行和注释
continue
# 检测缩进层级 finally:
indent = len(line) - len(line.lstrip()) gpio.write(False) # 程序结束前确保拉低电平
while indent_stack and indent <= indent_stack[-1]: gpio.close()
current_level.pop()
indent_stack.pop()
key, value = line.split(':', 1) # 分割键和值
key = key.strip()
value = value.strip()
# 处理值
if value.startswith('[') and value.endswith(']'): # 列表
value = [int(x.strip()) for x in value[1:-1].split(',')]
elif value.isdigit(): # 数字
value = int(value)
elif value.replace('.', '', 1).isdigit(): # 浮点数
value = float(value)
elif value.lower() in ('true', 'false'): # 布尔值
value = value.lower() == 'true'
# 插入到当前层级的字典中
if value: # 如果有值,则直接赋值
current_level[-1][key] = value
else: # 如果没有值,则创建一个新字典
new_dict = {}
current_level[-1][key] = new_dict
current_level.append(new_dict)
indent_stack.append(indent)
return config
class GPIOManager:
def __init__(self, config):
self.pins = config['gpio']['pins']
self.pulse_per_rev = config['gpio']['pulse_per_rev']
self.threads = {}
self.stop_flags = {}
self.gpio_files = {}
self.lock = threading.Lock()
for pin in self.pins:
self._init_gpio(pin)
def _init_gpio(self, pin):
try:
export_path = '/sys/class/gpio/export'
gpio_path = f'/sys/class/gpio/gpio{pin}'
if not os.path.exists(gpio_path):
with open(export_path, 'w') as f:
f.write(str(pin))
time.sleep(0.1)
direction_path = f'{gpio_path}/direction'
with open(direction_path, 'w') as f:
f.write('out')
self.gpio_files[pin] = open(f'{gpio_path}/value', 'r+')
logger.info(f"GPIO {pin} 初始化完成")
except Exception as e:
logger.error(f"GPIO {pin} 初始化失败: {str(e)}")
raise
def set_speed(self, pin_id, speed):
with self.lock:
if pin_id not in self.pins:
raise ValueError(f"无效的GPIO引脚: {pin_id}")
# 停止现有线程
if pin_id in self.threads:
self.stop_flags[pin_id] = True
self.threads[pin_id].join()
if speed <= 0:
self._write_value(pin_id, 0)
return
# 计算间隔时间400脉冲/转)
interval = 1.0 / (2 * speed) # 每个周期包含高低电平
self.stop_flags[pin_id] = False
self.threads[pin_id] = threading.Thread(
target=self._pulse_loop,
args=(pin_id, interval),
daemon=True
)
self.threads[pin_id].start()
def _pulse_loop(self, pin_id, interval):
while not self.stop_flags.get(pin_id, False):
self._write_value(pin_id, 1)
time.sleep(interval)
self._write_value(pin_id, 0)
time.sleep(interval)
def _write_value(self, pin_id, value):
try:
self.gpio_files[pin_id].seek(0)
self.gpio_files[pin_id].write(str(value))
self.gpio_files[pin_id].truncate()
self.gpio_files[pin_id].flush()
os.fsync(self.gpio_files[pin_id].fileno())
except IOError as e:
logger.error(f"GPIO写入错误: {str(e)}")
def cleanup(self):
with self.lock:
for pin in self.pins:
if pin in self.threads:
self.stop_flags[pin] = True
self.threads[pin].join()
self._write_value(pin, 0)
for f in self.gpio_files.values():
f.close()
logger.info("GPIO资源已释放")
class RS485Reader:
def __init__(self, config):
self.port = config['serial']['port']
self.baudrate = config['serial']['baudrate']
self.ser = None
self.current_weight = 0
self.running = True
self.lock = threading.Lock()
self.buffer = deque(maxlen=10) # 数据缓冲
self._connect()
def _connect(self):
try:
self.ser = serial.Serial(
port=self.port,
baudrate=self.baudrate,
bytesize=8,
parity='N',
stopbits=1,
timeout=0.1
)
logger.info(f"成功连接串口设备 {self.port}")
except Exception as e:
logger.error(f"串口连接失败: {str(e)}")
raise
def read_weight(self):
cmd = bytes.fromhex('010300000002c40b')
try:
self.ser.write(cmd)
response = self.ser.read(9)
if len(response) == 9 and response[:3] == bytes.fromhex("010304"):
weight = int.from_bytes(response[3:5], 'big')
with self.lock:
self.buffer.append(weight)
self.current_weight = sum(self.buffer) // len(self.buffer)
return True
return False
except Exception as e:
logger.error(f"读取重量失败: {str(e)}")
self._reconnect()
return False
def _reconnect(self):
max_retries = 3
for i in range(max_retries):
try:
if self.ser:
self.ser.close()
self._connect()
return True
except Exception as e:
logger.warning(f"重连尝试 {i + 1}/{max_retries} 失败")
time.sleep(1)
logger.error("串口重连失败")
return False
def stop(self):
self.running = False
if self.ser and self.ser.is_open:
self.ser.close()
class ControlAlgorithm(ABC):
@abstractmethod
def calculate_speed(self, current, target):
pass
class PIDAlgorithm(ControlAlgorithm):
def __init__(self, kp=0.5, ki=0.01, kd=0.1):
self.kp = kp
self.ki = ki
self.kd = kd
self.integral = 0
self.last_error = 0
self.min_speed = 10 # 最小运行速度
self.max_speed = 100 # 最大运行速度
def calculate_speed(self, current, target):
error = target - current
self.integral += error
derivative = error - self.last_error
self.last_error = error
# 抗积分饱和
if self.integral > 1000:
self.integral = 1000
elif self.integral < -1000:
self.integral = -1000
output = self.kp * error + self.ki * self.integral + self.kd * derivative
#误差5g 5*0.5 + 1000* 0.01 + [0.5*5] = 2.5 + 10 + ... = 12.5
#误差500g 500*0.5 + 1000* 0.01 + [0.5*10] = 250 + 10 + ... = 260
return max(self.min_speed, min(self.max_speed, output)) #
class FuzzyLogicAlgorithm(ControlAlgorithm):
def calculate_speed(self, current, target):
error = target - current
abs_error = abs(error)
if abs_error > 100:
return 100
elif abs_error > 50:
return 70
elif abs_error > 20:
return 50
else:
return 30
class NetworkHandler:
def __init__(self, config):
self.host = config['network']['host']
self.port = config['network']['port']
self.sock = None
self.command_queue = Queue()
self.status = {
'measuring': False,
'error': None,
'current_weight': 0,
'target_weight': 0,
'algorithm': 'pid'
}
self.lock = threading.Lock()
self.running = True
def start_server(self):
self.sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
self.sock.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)
try:
self.sock.bind((self.host, self.port))
self.sock.listen(5)
logger.info(f"网络服务启动于 {self.host}:{self.port}")
while self.running:
conn, addr = self.sock.accept()
threading.Thread(target=self._handle_client, args=(conn, addr), daemon=True).start()
except Exception as e:
logger.error(f"网络服务异常: {str(e)}")
finally:
self.sock.close()
def _handle_client(self, conn, addr):
try:
data = conn.recv(1024)
if data:
cmd = json.loads(data.decode())
self._process_command(cmd)
# 返回当前状态
with self.lock:
response = json.dumps(self.status)
conn.send(response.encode())
except json.JSONDecodeError:
logger.warning("收到无效的JSON指令")
except Exception as e:
logger.error(f"客户端处理异常: {str(e)}")
finally:
conn.close()
def _process_command(self, cmd):
with self.lock:
if cmd.get('command') == 'set_target':
self.status['target_weight'] = cmd['payload']['target_weight']
self.status['algorithm'] = cmd['payload'].get('algorithm', 'pid')
self.status['measuring'] = True
print("收到指令set_target:", self.status['target_weight'])
elif cmd.get('command') == 'stop':
self.status['measuring'] = False
class Controller:
def __init__(self, config_path='config.yaml'):
self.config = parse_config(config_path)
# 初始化组件
self.gpio = GPIOManager(self.config)
self.sensor = RS485Reader(self.config)
self.network = NetworkHandler(self.config)
self.algorithm = None
self.running = False
# 共享状态
self.current_weight = 0
self.target_weight = 0
self.system_status = 'idle'
self.status_lock = threading.Lock()
# 看门狗线程
self.watchdog = threading.Thread(target=self._watchdog, daemon=True)
def start(self):
self.running = True
threads = [
threading.Thread(target=self._weight_reading_loop),
threading.Thread(target=self._control_loop),
threading.Thread(target=self.network.start_server),
self.watchdog
]
for t in threads:
t.start()
logger.info("系统启动完成")
def _weight_reading_loop(self):
while self.running:
if self.sensor.read_weight():
with self.sensor.lock:
self.current_weight = self.sensor.current_weight
with self.status_lock:
self.network.status['current_weight'] = self.current_weight
if self.current_weight >= self.network.status['target_weight']:
self.network.status['measuring'] = False
time.sleep(0.05) # 20Hz
#time.sleep(0.1) # 20Hz
def _control_loop(self):
while self.running:
with self.status_lock:
measuring = self.network.status['measuring']
target = self.network.status['target_weight']
algorithm = self.network.status['algorithm']
if measuring:
self._select_algorithm(algorithm)
speed = self.algorithm.calculate_speed(self.current_weight, target)
rpm = speed # 假设速度单位直接对应RPM
pulse_rate = (int(self.config['gpio']['pulse_per_rev']) * rpm) / 60
self.gpio.set_speed(8, pulse_rate)
else:
self.gpio.set_speed(8, 0)
while not self.running and not self.network.status['measuring']:
time.sleep(0.1) # 短暂休眠避免CPU占用过高
time.sleep(0.1)
def _select_algorithm(self, name):
if name == 'pid':
params = self.config['algorithm']['params']['pid']
self.algorithm = PIDAlgorithm(**params)
elif name == 'fuzzy':
self.algorithm = FuzzyLogicAlgorithm()
else:
self.algorithm = PIDAlgorithm()
def _watchdog(self):
while self.running:
# 检查线程状态
threads_alive = all([
threading.current_thread().is_alive(),
self.sensor.running,
self.network.running
])
if not threads_alive:
logger.critical("检测到线程异常,触发紧急停止!")
self.emergency_stop()
break
time.sleep(5)
def emergency_stop(self):
with self.status_lock:
self.network.status['measuring'] = False
self.network.status['error'] = 'emergency_stop'
self.gpio.cleanup()
self.sensor.stop()
self.running = False
logger.info("系统已紧急停止")
def shutdown(self):
self.running = False
self.gpio.cleanup()
self.sensor.stop()
self.network.running = False
logger.info("系统正常关闭")
if __name__ == "__main__":
ctrl = Controller()
try:
ctrl.start()
while True:
time.sleep(1)
except KeyboardInterrupt:
logger.info("收到停止信号...")
finally:
ctrl.shutdown()

522
test2.py
View File

@ -1,522 +0,0 @@
#!/usr/bin/env python
# -*- coding: utf-8 -*-
'''
# @Time : 2025/2/14 14:43
# @Author : hjw
# @File : test.py
'''
import os
import time
import threading
import serial
import socket
import logging
import json
from queue import Queue
from abc import ABC, abstractmethod
from collections import deque
# 配置日志系统
logging.basicConfig(
level=logging.INFO,
format='%(asctime)s - %(name)s - %(levelname)s - %(message)s',
handlers=[
logging.FileHandler('controller.log'),
logging.StreamHandler()
]
)
logger = logging.getLogger(__name__)
def precise_delay_us(us):
"""实现微秒级延时"""
start_time = time.perf_counter()
#print(time.perf_counter())
while (time.perf_counter() - start_time) * 1e6 < us:
pass
def parse_config(file_path):
config = {}
current_level = [config] # 使用栈来跟踪当前层级的字典
indent_stack = [] # 记录缩进层级
with open(file_path, 'r', encoding='utf-8') as file:
for line in file:
line = line.rstrip() # 去掉行尾的换行符和空格
if not line or line.startswith('#'): # 跳过空行和注释
continue
# 检测缩进层级
indent = len(line) - len(line.lstrip())
while indent_stack and indent <= indent_stack[-1]:
current_level.pop()
indent_stack.pop()
key, value = line.split(':', 1) # 分割键和值
key = key.strip()
value = value.strip()
# 处理值
if value.startswith('[') and value.endswith(']'): # 列表
value = [int(x.strip()) for x in value[1:-1].split(',')]
elif value.isdigit(): # 数字
value = int(value)
elif value.replace('.', '', 1).isdigit(): # 浮点数
value = float(value)
elif value.lower() in ('true', 'false'): # 布尔值
value = value.lower() == 'true'
# 插入到当前层级的字典中
if value: # 如果有值,则直接赋值
current_level[-1][key] = value
else: # 如果没有值,则创建一个新字典
new_dict = {}
current_level[-1][key] = new_dict
current_level.append(new_dict)
indent_stack.append(indent)
return config
class GPIOManager:
def __init__(self, config):
self.pins = config['gpio']['pins']
self.pulse_per_rev = config['gpio']['pulse_per_rev']
self.threads = {}
self.stop_flags = {}
self.gpio_files = {}
self.lock = threading.Lock()
for pin in self.pins:
self._init_gpio(pin)
def _init_gpio(self, pin):
try:
export_path = '/sys/class/gpio/export'
gpio_path = f'/sys/class/gpio/gpio{pin}'
if not os.path.exists(gpio_path):
with open(export_path, 'w') as f:
f.write(str(pin))
time.sleep(0.1)
direction_path = f'{gpio_path}/direction'
with open(direction_path, 'w') as f:
f.write('out')
self.gpio_files[pin] = open(f'{gpio_path}/value', 'r+')
logger.info(f"GPIO {pin} 初始化完成")
except Exception as e:
logger.error(f"GPIO {pin} 初始化失败: {str(e)}")
raise
def set_speed(self, pin_id, speed, error):
with self.lock:
if pin_id not in self.pins:
raise ValueError(f"无效的GPIO引脚: {pin_id}")
# 停止现有线程
if pin_id in self.threads:
self.stop_flags[pin_id] = True
self.threads[pin_id].join()
if speed <= 0:
self._write_value(pin_id, 0)
return
# 计算间隔时间400脉冲/转)
if error>20:
interval = 1.0 / (2 * speed * 2) # 每个周期包含高低电平
else:
interval = 1.0 / (speed) # 每个周期包含高低电平
self.stop_flags[pin_id] = False
self.threads[pin_id] = threading.Thread(
target=self._pulse_loop,
args=(pin_id, interval),
daemon=True
)
self.threads[pin_id].start()
def _pulse_loop(self, pin_id, interval):
# print(interval)
while not self.stop_flags.get(pin_id, False):
self._write_value(pin_id, 1)
time.sleep(interval)
# precise_delay_us(100)
self._write_value(pin_id, 0)
time.sleep(interval)
# precise_delay_us(100)
def _write_value(self, pin_id, value):
try:
self.gpio_files[pin_id].seek(0)
self.gpio_files[pin_id].write(str(value))
self.gpio_files[pin_id].truncate()
self.gpio_files[pin_id].flush()
os.fsync(self.gpio_files[pin_id].fileno())
except IOError as e:
logger.error(f"GPIO写入错误: {str(e)}")
def cleanup(self):
with self.lock:
for pin in self.pins:
if pin in self.threads:
self.stop_flags[pin] = True
self.threads[pin].join()
self._write_value(pin, 0)
for f in self.gpio_files.values():
f.close()
logger.info("GPIO资源已释放")
class RS485Reader:
def __init__(self, config):
self.port = config['serial']['port']
self.baudrate = config['serial']['baudrate']
self.ser = None
self.current_weight = 0
self.running = True
self.lock = threading.Lock()
self.buffer = deque(maxlen=10) # 数据缓冲
self._connect()
def _connect(self):
try:
self.ser = serial.Serial(
port=self.port,
baudrate=self.baudrate,
bytesize=8,
parity='N',
stopbits=1,
timeout=0.1
)
logger.info(f"成功连接串口设备 {self.port}")
except Exception as e:
logger.error(f"串口连接失败: {str(e)}")
raise
def read_weight(self):
cmd = bytes.fromhex('010300000002c40b')
try:
self.ser.write(cmd)
response = self.ser.read(9)
if len(response) == 9 and response[:3] == bytes.fromhex("010304"):
weight = int.from_bytes(response[3:5], 'big')
with self.lock:
self.buffer.append(weight)
self.current_weight = sum(self.buffer) // len(self.buffer)
return True
return False
except Exception as e:
logger.error(f"读取重量失败: {str(e)}")
self._reconnect()
return False
def tare(self): # 去皮方法
cmd = bytes.fromhex('01060064000109d5') # 01 06 00 64 00 01 09 d5
try:
if self.read_weight(): # 确保有最新数据
self.ser.write(cmd)
response = self.ser.read(8)
if len(response) == 8 and response[:3] == bytes.fromhex("010600"):
self.buffer.clear() # 清空缓冲
self.current_weight = 0 # 重置当前重量
self.read_weight() # 确保有最新数据
return True
else:
self.buffer.clear() # 清空缓冲
return False
return False
except Exception as e:
logger.error(f"去皮操作失败: {str(e)}")
self._reconnect()
return False
def _reconnect(self):
max_retries = 3
for i in range(max_retries):
try:
if self.ser:
self.ser.close()
self._connect()
return True
except Exception as e:
logger.warning(f"重连尝试 {i + 1}/{max_retries} 失败")
time.sleep(1)
logger.error("串口重连失败")
return False
def stop(self):
self.running = False
if self.ser and self.ser.is_open:
self.ser.close()
class ControlAlgorithm(ABC):
@abstractmethod
def calculate_speed(self, current, target):
pass
class PIDAlgorithm(ControlAlgorithm):
def __init__(self, kp=0.5, ki=0.01, kd=0.1):
self.kp = kp
self.ki = ki
self.kd = kd
self.integral = 0
self.last_error = 0
self.min_speed = 10 # 最小运行速度
self.max_speed = 100 # 最大运行速度
def calculate_speed(self, current, target):
error = target - current
self.integral += error
derivative = error - self.last_error
self.last_error = error
# 抗积分饱和
if self.integral > 1000:
self.integral = 1000
elif self.integral < -1000:
self.integral = -1000
output = self.kp * error + self.ki * self.integral + self.kd * derivative
#误差5g 5*0.5 + 1000* 0.01 + [0.5*5] = 2.5 + 10 + ... = 12.5
#误差500g 500*0.5 + 1000* 0.01 + [0.5*10] = 250 + 10 + ... = 260
return max(self.min_speed, min(self.max_speed, output)), error #
class FuzzyLogicAlgorithm(ControlAlgorithm):
def calculate_speed(self, current, target):
error = target - current
abs_error = abs(error)
if abs_error > 100:
return 100
elif abs_error > 50:
return 70
elif abs_error > 20:
return 50
else:
return 30
class NetworkHandler:
def __init__(self, config):
self.host = config['network']['host']
self.port = config['network']['port']
self.sock = None
self.command_queue = Queue()
self.status = {
'measuring': False,
'error': None,
'current_weight': 0,
'target_weight': 0,
'algorithm': 'pid',
'set_tare': False,
'set_tare_num_time': 0,
'get_weight': False,
'set_vibrate': False,
'set_vibrate_time': 0
}
self.lock = threading.Lock()
self.running = True
def start_server(self):
self.sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
self.sock.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)
try:
self.sock.bind((self.host, self.port))
self.sock.listen(5)
logger.info(f"网络服务启动于 {self.host}:{self.port}")
while self.running:
conn, addr = self.sock.accept()
threading.Thread(target=self._handle_client, args=(conn, addr), daemon=True).start()
except Exception as e:
logger.error(f"网络服务异常: {str(e)}")
finally:
self.sock.close()
def _handle_client(self, conn, addr):
try:
data = conn.recv(1024)
if data:
cmd = json.loads(data.decode())
self._process_command(cmd)
time.sleep(0.5)
# 返回当前状态
with self.lock:
response = json.dumps(self.status)
conn.send(response.encode())
except json.JSONDecodeError:
logger.warning("收到无效的JSON指令")
except Exception as e:
logger.error(f"客户端处理异常: {str(e)}")
finally:
conn.close()
def _process_command(self, cmd):
with self.lock:
if cmd.get('command') == 'set_target':
self.status['target_weight'] = cmd['payload']['target_weight']
self.status['algorithm'] = cmd['payload'].get('algorithm', 'pid')
self.status['measuring'] = True
print("收到指令set_target:", self.status['target_weight'])
elif cmd.get('command') == 'stop':
self.status['measuring'] = False
elif cmd.get('command') == 'set_zero':
self.status['set_tare'] = True
elif cmd.get('command') == 'get_weight':
self.status['get_weight'] = True
elif cmd.get('command') == 'set_vibrate':
self.status['set_vibrate'] = True
self.status['set_vibrate_time'] = cmd['payload']['time']
class Controller:
def __init__(self, config_path='config.yaml'):
self.config = parse_config(config_path)
# 初始化组件
self.gpio = GPIOManager(self.config)
self.sensor = RS485Reader(self.config)
self.network = NetworkHandler(self.config)
self.algorithm = None
self.running = False
# 共享状态
self.current_weight = 0
self.target_weight = 0
self.system_status = 'idle'
self.status_lock = threading.Lock()
# 看门狗线程
self.watchdog = threading.Thread(target=self._watchdog, daemon=True)
def start(self):
self.running = True
threads = [
threading.Thread(target=self._weight_reading_loop),
threading.Thread(target=self._control_loop),
threading.Thread(target=self.network.start_server),
threading.Thread(target=self._control_vibrate),
self.watchdog
]
for t in threads:
t.start()
logger.info("系统启动完成")
def _weight_reading_loop(self):
while self.running:
if self.sensor.read_weight():
with self.sensor.lock:
self.current_weight = self.sensor.current_weight
with self.status_lock:
self.network.status['current_weight'] = self.current_weight
if self.current_weight >= self.network.status['target_weight']:
self.network.status['measuring'] = False
if self.network.status['set_tare'] == True:
if self.sensor.tare():
self.network.status['set_tare_num_time'] +=1
self.network.status['set_tare'] = False
if self.network.status['get_weight'] == True:
with self.sensor.lock:
self.network.status['current_weight'] = self.sensor.current_weight
self.network.status['get_weight'] = False
time.sleep(0.05) # 20Hz
# time.sleep(0.1) # 20Hz
def _control_loop(self):
while self.running:
with self.status_lock:
measuring = self.network.status['measuring']
target = self.network.status['target_weight']
algorithm = self.network.status['algorithm']
if measuring:
self._select_algorithm(algorithm)
speed, error = self.algorithm.calculate_speed(self.current_weight, target)
rpm = speed # 假设速度单位直接对应RPM
pulse_rate = (int(self.config['gpio']['pulse_per_rev']) * rpm) / 60
self.gpio.set_speed(8, pulse_rate, error)
else:
self.gpio.set_speed(8, 0, 0)
while not self.running and not self.network.status['measuring']:
time.sleep(0.1) # 短暂休眠避免CPU占用过高
time.sleep(0.1)
def _control_vibrate(self): # 振动控制
while self.running:
with self.status_lock:
vibrating = self.network.status['set_vibrate']
vibrate_time = self.network.status['set_vibrate_time']
if vibrating:
self.gpio._write_value(9, 1) #self.gpio.set_speed(8, pulse_rate)
time.sleep(vibrate_time)
self.gpio._write_value(9, 0)
self.network.status['set_vibrate'] = False
self.network.status['set_vibrate_time'] = 0
time.sleep(1)
def _select_algorithm(self, name):
if name == 'pid':
params = self.config['algorithm']['params']['pid']
self.algorithm = PIDAlgorithm(**params)
elif name == 'fuzzy':
self.algorithm = FuzzyLogicAlgorithm()
else:
self.algorithm = PIDAlgorithm()
def _watchdog(self):
while self.running:
# 检查线程状态
threads_alive = all([
threading.current_thread().is_alive(),
self.sensor.running,
self.network.running
])
if not threads_alive:
logger.critical("检测到线程异常,触发紧急停止!")
self.emergency_stop()
break
time.sleep(5)
def emergency_stop(self):
with self.status_lock:
self.network.status['measuring'] = False
self.network.status['error'] = 'emergency_stop'
self.gpio.cleanup()
self.sensor.stop()
self.running = False
logger.info("系统已紧急停止")
def shutdown(self):
self.running = False
self.gpio.cleanup()
self.sensor.stop()
self.network.running = False
logger.info("系统正常关闭")
if __name__ == "__main__":
ctrl = Controller()
try:
ctrl.start()
while True:
time.sleep(1)
except KeyboardInterrupt:
logger.info("收到停止信号...")
finally:
ctrl.shutdown()