书接上文。
这次是通过消费者将信息推入任务队列,实现线程之间的任务队列共享和处理。
这里需要使用到python的threading类来实现多线程,使用pymongo来操作数据库,使用snap7来操作plc相关的。我使用的是西门子的博图和plcsimadvance来进行plc仿真。
先创建一个类,继承threading.Thread。

class PLCController(threading.Thread):
    def __init__(self,queue,ip_address, rack=0, slot=1,):#机架号和槽位基本不会变就写死了,有需要也可以再改动
        super().__init__()
        self.plc = snap7.client.Client() #创建一个plc客户端,后面用得上
        self.ip_address = ip_address #想要根据不同的ip来创建客户端,很久以后或许用的上
        self.rack = rack
        self.slot = slot
        self.connected = False #与plc的链接状态,默认是f
        self.data = {} #最后要存到数据库里面的数据
        self.reporting = False #是否储存
        self.running = False #线程运行标识
        self.queue = queue #任务队列,main里面会把消费者创建的任务队列传到这里
        self.heatid = "" #以后的其他服务会传这个值过来,现在仙女作为测试
        self.db = pymongo.MongoClient("mongodb://localhost:27017/").VOD #db中的文件夹

先是获取任务的方法

    def _process_tasks(self):
        try:
            while True:
                task = self.queue.get_nowait() #用getnowait,没有任务的时候也不会阻塞而是报错
                if isinstance(task, dict):
                    for task_type, param in task.items():
                        self._handle_single_task(task_type, param)
                else:
                    self._handle_single_task(task, None) 
                self.queue.task_done() #遍历然后挨个处理任务,把任务名和键值写入
        except Empty:
            pass
        except Exception as e:
            logging.error(f"任务处理失败: {e}")

然后是处理任务的方法

    def _handle_single_task(self, task_type, param):
        try:
            match task_type:
                case "reporting":
                    if isinstance(param, bool):
                        self.reporting = param
                        logging.info(f"报告状态已设置为: {param}")
                    else:
                        logging.error(f"参数类型错误:'reporting'需要布尔值,收到 {type(param)}") #将记录的状态设置为参数
                case "heatid":
                    self.heatid = str(param)
                    logging.info(f"生产批次ID已更新: {param}") #更新heatid
                case "link":
                    self.connect()#链接plc
                case "disconnect":
                    self.disconnect()#断链plc
                case "stop":
                    self.stop()#停止服务
                case _:
                    logging.warning(f"未知任务类型: {task_type}")
        except Exception as e:
            logging.error(f"任务处理失败:类型={task_type}, 参数={param}, 错误={e}")

然后是建立和断开plc链接的方法

    def connect(self):
        try:
            if not self.connected:
                self.plc.connect(self.ip_address, self.rack, self.slot)
                self.connected = self.plc.get_connected()
                logging.info(f"成功连接到 {self.ip_address}")
        except Exception as e:
            logging.error(f"连接失败: {e}")

    def disconnect(self):
        try:
            if self.connected:
                self.plc.disconnect()
                self.connected = False
                logging.info(f"已断开 {self.ip_address} 连接")
        except Exception as e:
            logging.error(f"断开连接失败: {e}")

理论上来说断开链接还需要把客户端给删除,但是要长期运行应该不需要,后面测试再看吧。
然后是读取plc数据的方法

    def main_get(self):
        if not self.connected:
            return
        try:
  
            maindb = self.plc.read_area(snap7.client.Area.DB, 200, 0, 34)          # 读取 DB200 的数据,200是db号,0和34是db里面的0-34字节,如果超过实际的字节数就会报错

            # 解析数据并存储
            self.data.update({
                "looper": snap7.util.get_bool(maindb, 0, 0),
                'co': snap7.util.get_real(maindb, 2),
                'co2': snap7.util.get_real(maindb, 6),
                'o2': snap7.util.get_real(maindb, 10),
                'gas_flow': snap7.util.get_real(maindb, 14),
                'tank_vac_value': snap7.util.get_real(maindb, 18),
                'pump_vac_value': snap7.util.get_real(maindb, 22),
                'high_vac_value': snap7.util.get_real(maindb, 26),
                'some_value': snap7.util.get_real(maindb, 30),
                'ip': self.ip_address,
                'timestamp': datetime.datetime.now()
            })

            # 记录数据到数据库
            if self.reporting:
                self.save_to_db()

        except Exception as e:
            logging.error(f"PLC数据读取失败: {e}")
            self.disconnect()  # 尝试重新连接
            self.connect()

然后是写入mongodb

    def save_to_db(self):
        try:
            record = self.data.copy()
            record["heatid"] = self.heatid
            self.db.sensor_data.insert_one(record)
        except Exception as e:
            logging.error(f"数据库写入失败: {e}")

最后是主循环

    def run(self):
        self.running = True
        self.connect()
        print("run")
        try:
            while self.running:
                self.main_get()

                self._process_tasks()

                time.sleep(3)
        finally:
            self.disconnect()

轮询定为3秒,太短了数据太多了。
负责调用的main函数像这样

customer = customer.Customer()
plccontroller = read.PLCController(customer.queue,"192.168.0.1",rack=0,slot=1)

if __name__ == "__main__":
    customer.start()
    plccontroller.start()
    plccontroller.connect()

很奇怪的是每次启动都会报错

b' TCP : Socket operation on non socket'
ERROR:root:连接失败: b' TCP : Socket operation on non socket'
ERROR:snap7.common:b' TCP : Socket operation on non socket'
ERROR:root:连接失败: b' TCP : Socket operation on non socket'

后面再想想怎么处理,测试是不太影响使用的。
使用测试的json

{
                        'link':True,
                        'reporting':True,
                        'heatid': "asd"
                        }

最后写入到mongodb的数据

{
  "_id": {
    "$oid": "67fe0ddaa629e40120af4453"
  },
  "looper": false,
  "co": 0,
  "co2": 0,
  "o2": 0,
  "gas_flow": 0,
  "tank_vac_value": 0,
  "pump_vac_value": 0,
  "high_vac_value": 0,
  "some_value": 0,
  "ip": "192.168.0.1",
  "timestamp": {
    "$date": "2025-04-15T15:42:18.991Z"
  },
  "heatid": "asd"
}

博客园  ©  2004-2025
浙公网安备 33010602011771号 浙ICP备2021040463号-3