Files
ElecScalesMeasur/test.py
2025-02-18 11:28:24 +08:00

448 lines
14 KiB
Python
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

#!/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 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):
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()