
树莓派工业定时任务:基于TimePicker组件的useMilitaryTime属性实现
本文将详细介绍如何在树莓派工业控制场景中,使用自定义TimePicker组件实现支持useMilitaryTime属性的定时任务设置,并重点讲解如何通过该属性切换24小时制(军事时间)与12小时制,确保工业场景下的时间精度与操作可靠性。
系统概述
应用场景
工业定时任务对时间精度的要求极高,常见于:
自动化产线:定时启动/停止设备(如每2小时一次的质量检测)
环境监控:按固定间隔采集传感器数据(如每小时整点采样)
设备维护:定时触发设备自检(如每日凌晨3:00)
能源管理:分时段控制电力分配(如峰谷电价时段调整)
硬件需求
组件 型号/规格 说明
树莓派 Raspberry Pi 4B/5 主控平台(需稳定供电)
触摸显示屏 7英寸电容屏 工业级防刮擦(IP65防护)
物理按键 防水轻触按键 冗余操作(防止触摸失效)
继电器模块 8路5V继电器 控制工业设备电源
软件需求
Raspberry Pi OS(推荐Bullseye及以上,支持Python 3.9+)
Python 3.9+
Tkinter库(Python标准库)
schedule库(定时任务调度,pip install schedule)
datetime模块(时间处理)
TimePicker组件核心设计
组件功能需求
双模式显示:通过useMilitaryTime属性切换24小时制(00:00-23:59)与12小时制(01:00 AM-12:59 PM)
精确输入控制:分钟/秒级调节(工业场景常需秒级精度)
输入验证:防止非法时间输入(如小时>23、分钟>59)
军事时间同步:切换模式时自动转换时间值(如3:30 PM → 15:30)
组件架构设计
采用Tkinter的ttk.Frame作为容器,集成以下子组件:
小时选择器:Spinbox(范围0-23/1-12,受useMilitaryTime控制)
分钟选择器:Spinbox(范围0-59)
秒选择器:Spinbox(范围0-59,工业场景可选)
AM/PM标识:仅在12小时制时显示
切换按钮:用于切换useMilitaryTime状态
代码实现:自定义TimePicker组件
import tkinter as tk
from tkinter import ttk
from datetime import datetime, time
class IndustrialTimePicker(ttk.Frame):
def init(self, parent, use_military_time=True, kwargs):
super().init(parent, kwargs)
self.use_military_time = use_military_time # useMilitaryTime属性
# 初始化时间值(默认当前时间)
now = datetime.now()
self.hour = now.hour
self.minute = now.minute
self.second = now.second
# 创建界面组件
self.create_widgets()
self.update_display() # 初始显示
def create_widgets(self):
# 小时选择器(Spinbox)
self.hour_var = tk.IntVar(value=self.hour)
self.hour_spin = ttk.Spinbox(
self,
from_=0 if self.use_military_time else 1,
to=23 if self.use_military_time else 12,
textvariable=self.hour_var,
width=3,
command=self.on_hour_change
)
self.hour_spin.pack(side=tk.LEFT, padx=2)
# 分隔符(冒号)
ttk.Label(self, text=":").pack(side=tk.LEFT, padx=2)
# 分钟选择器(Spinbox)
self.minute_var = tk.IntVar(value=self.minute)
self.minute_spin = ttk.Spinbox(
self,
from_=0,
to=59,
textvariable=self.minute_var,
width=3,
command=self.on_minute_change
)
self.minute_spin.pack(side=tk.LEFT, padx=2)
# 秒选择器(可选,工业场景常用)
self.second_var = tk.IntVar(value=self.second)
self.second_spin = ttk.Spinbox(
self,
from_=0,
to=59,
textvariable=self.second_var,
width=3,
command=self.on_second_change
)
self.second_spin.pack(side=tk.LEFT, padx=2)
# AM/PM标识(12小时制专用)
self.ampm_var = tk.StringVar()
self.ampm_label = ttk.Label(self, textvariable=self.ampm_var, width=2)
self.ampm_label.pack(side=tk.LEFT, padx=2)
# 切换按钮(24h ↔ 12h)
self.toggle_btn = ttk.Button(
self,
text="切换为12小时制" if self.use_military_time else "切换为24小时制",
command=self.toggle_time_format
)
self.toggle_btn.pack(side=tk.RIGHT, padx=2)
def update_display(self):
"""根据当前模式更新显示内容"""
# 更新小时范围
hour_from = 0 if self.use_military_time else 1
hour_to = 23 if self.use_military_time else 12
self.hour_spin.config(from_=hour_from, to=hour_to)
# 更新AM/PM显示(12小时制时)
if not self.use_military_time:
ampm = "AM" if self.hour < 12 else "PM"
# 12小时制特殊处理(0点→12AM,12点→12PM)
display_hour = self.hour % 12
display_hour = 12 if display_hour == 0 else display_hour
self.hour_var.set(display_hour)
self.ampm_var.set(ampm)
else:
self.ampm_var.set("")
self.hour_var.set(self.hour) # 24小时制直接显示
# 同步分钟/秒显示
self.minute_var.set(self.minute)
self.second_var.set(self.second)
def on_hour_change(self):
"""小时值变更回调"""
self.hour = self.hour_var.get()
# 12小时制时限制小时范围(1-12)
if not self.use_military_time:
self.hour = max(1, min(12, self.hour))
self.hour_var.set(self.hour)
self.update_display()
def on_minute_change(self):
"""分钟值变更回调"""
self.minute = self.minute_var.get()
self.minute_var.set(min(59, max(0, self.minute))) # 限制0-59
self.update_display()
def on_second_change(self):
"""秒值变更回调"""
self.second = self.second_var.get()
self.second_var.set(min(59, max(0, self.second))) # 限制0-59
self.update_display()
def toggle_time_format(self):
"""切换时间格式(24h ↔ 12h)"""
self.use_military_time = not self.use_military_time
self.toggle_btn.config(
text="切换为12小时制" if self.use_military_time else "切换为24小时制"
)
self.update_display()
@property
def useMilitaryTime(self):
"""获取useMilitaryTime属性值"""
return self.use_military_time
@useMilitaryTime.setter
def useMilitaryTime(self, value):
"""设置useMilitaryTime属性(触发界面更新)"""
if self.use_military_time != value:
self.use_military_time = value
self.update_display()
def get_time(self):
"""获取当前设置的时间(返回time对象)"""
# 12小时制转换为24小时制
if not self.use_military_time:
hour = self.hour
if self.ampm_var.get() == "PM" and hour != 12:
hour += 12
elif self.ampm_var.get() "AM" and hour 12:
hour = 0
else:
hour = self.hour
return time(hour=hour, minute=self.minute, second=self.second)
def set_time(self, time_obj):
"""设置时间(从time对象更新界面)"""
self.hour = time_obj.hour
self.minute = time_obj.minute
self.second = time_obj.second
# 根据当前模式调整显示
if not self.use_military_time:
ampm = "AM" if self.hour < 12 else "PM"
display_hour = self.hour % 12
display_hour = 12 if display_hour == 0 else display_hour
self.hour_var.set(display_hour)
self.ampm_var.set(ampm)
else:
self.hour_var.set(self.hour)
self.ampm_var.set("")
self.minute_var.set(self.minute)
self.second_var.set(self.second)
工业定时任务主程序
class IndustrialTimerApp:
def init(self, root):
self.root = root
self.root.title(“树莓派工业定时任务控制器”)
self.root.geometry(“600x400”)
# 初始化定时任务调度器
self.scheduler = schedule.Scheduler()
self.running = False
# 创建界面组件
self.create_widgets()
# 加载上次保存的任务(示例)
self.load_last_task()
def create_widgets(self):
# 标题标签
title_label = ttk.Label(self.root, text="工业设备定时控制", font=("Arial", 16, "bold"))
title_label.pack(pady=(20, 10))
# TimePicker组件(启用军事时间)
self.time_picker = IndustrialTimePicker(
self.root,
use_military_time=True, # 工业场景默认24小时制
padding=10
)
self.time_picker.pack(fill=tk.X, padx=20, pady=10)
# 任务类型选择
task_frame = ttk.LabelFrame(self.root, text="任务配置")
task_frame.pack(fill=tk.X, padx=20, pady=10)
# 任务类型下拉框
self.task_type = tk.StringVar(value="设备启动")
task_types = ["设备启动", "设备停止", "数据采集", "设备自检"]
task_combo = ttk.Combobox(
task_frame,
textvariable=self.task_type,
values=task_types,
state="readonly",
width=15
)
task_combo.pack(side=tk.LEFT, padx=10)
# 执行次数选择
self.execution_count = tk.IntVar(value=1) # 默认执行1次
count_label = ttk.Label(task_frame, text="执行次数:")
count_label.pack(side=tk.LEFT, padx=5)
count_spin = ttk.Spinbox(
task_frame,
from_=1,
to=100,
textvariable=self.execution_count,
width=5
)
count_spin.pack(side=tk.LEFT, padx=5)
# 添加任务按钮
add_btn = ttk.Button(
self.root,
text="添加定时任务",
command=self.add_schedule_task
)
add_btn.pack(pady=10)
# 任务列表显示
list_frame = ttk.LabelFrame(self.root, text="已设置任务")
list_frame.pack(fill=tk.BOTH, expand=True, padx=20, pady=10)
# 使用Treeview显示任务列表
columns = ("time", "type", "count")
self.task_tree = ttk.Treeview(
list_frame,
columns=columns,
show="headings"
)
self.task_tree.heading("time", text="执行时间")
self.task_tree.heading("type", text="任务类型")
self.task_tree.heading("count", text="执行次数")
self.task_tree.column("time", width=150)
self.task_tree.column("type", width=120)
self.task_tree.column("count", width=80)
scrollbar = ttk.Scrollbar(list_frame, orient=tk.VERTICAL, command=self.task_tree.yview)
self.task_tree.configure(yscrollcommand=scrollbar.set)
scrollbar.pack(side=tk.RIGHT, fill=tk.Y)
self.task_tree.pack(side=tk.LEFT, fill=tk.BOTH, expand=True)
# 控制按钮
control_frame = ttk.Frame(self.root)
control_frame.pack(fill=tk.X, padx=20, pady=20)
self.start_btn = ttk.Button(
control_frame,
text="启动定时任务",
command=self.start_scheduler
)
self.start_btn.pack(side=tk.LEFT, padx=10)
self.stop_btn = ttk.Button(
control_frame,
text="停止定时任务",
command=self.stop_scheduler,
state=tk.DISABLED
)
self.stop_btn.pack(side=tk.LEFT, padx=10)
# 状态栏
self.status_bar = ttk.Label(
self.root,
text="状态:就绪",
relief=tk.SUNKEN,
anchor=tk.W
)
self.status_bar.pack(side=tk.BOTTOM, fill=tk.X)
def add_schedule_task(self):
"""添加定时任务到调度器"""
try:
# 获取设置的时间
scheduled_time = self.time_picker.get_time()
# 格式化时间为字符串(用于显示)
time_str = scheduled_time.strftime("%H:%M:%S")
task_type = self.task_type.get()
count = self.execution_count.get()
# 添加到Treeview显示
self.task_tree.insert("", tk.END, values=(time_str, task_type, count))
# 添加到调度器(每天固定时间执行)
self.scheduler.every().day.at(time_str).do(
self.execute_task,
task_type=task_type,
count=count
)
# 更新状态
self.status_bar.config(text=f"状态:已添加任务({time_str}执行{task_type})")
except Exception as e:
self.status_bar.config(text=f"错误:添加任务失败 - {str(e)}")
def execute_task(self, task_type, count):
"""执行定时任务(示例逻辑)"""
try:
# 实际工业场景中,这里会控制继电器/调用设备API
print(f"执行任务:{task_type}(第{count}次)")
# 模拟任务执行(实际需替换为设备控制代码)
if task_type == "设备启动":
self.control_relay(1) # 继电器1控制设备启动
elif task_type == "设备停止":
self.control_relay(0) # 继电器0控制设备停止
# 更新任务计数(示例)
item_id = self.task_tree.get_children()[-1]
current_count = int(self.task_tree.item(item_id, "values")[2])
if current_count > 1:
self.task_tree.item(item_id, values=(
self.task_tree.item(item_id, "values")[0],
task_type,
current_count - 1
))
except Exception as e:
print(f"任务执行失败:{str(e)}")
def control_relay(self, state):
"""控制继电器(示例,实际需连接硬件)"""
# 这里使用print模拟继电器控制
print(f"控制继电器:{'开启' if state else '关闭'}")
# 实际代码需调用RPi.GPIO库控制GPIO引脚
def start_scheduler(self):
"""启动定时任务调度器"""
if not self.running:
self.running = True
self.start_btn.config(state=tk.DISABLED)
self.stop_btn.config(state=tk.NORMAL)
self.status_bar.config(text="状态:定时任务运行中...")
# 启动后台线程运行调度器(避免阻塞GUI)
import threading
threading.Thread(target=self.run_scheduler, daemon=True).start()
def stop_scheduler(self):
"""停止定时任务调度器"""
if self.running:
self.running = False
self.start_btn.config(state=tk.NORMAL)
self.stop_btn.config(state=tk.DISABLED)
self.status_bar.config(text="状态:定时任务已停止")
schedule.clear() # 清除所有任务
def run_scheduler(self):
"""运行调度器的主循环"""
while self.running:
schedule.run_pending()
time.sleep(1)
def load_last_task(self):
"""加载上次保存的任务(示例,实际需读取配置文件)"""
# 示例:假设上次设置了每日10:00启动设备
last_time = time(hour=10, minute=0, second=0)
self.time_picker.set_time(last_time)
self.task_type.set("设备启动")
self.execution_count.set(1)
if name == “main”:
root = tk.Tk()
app = IndustrialTimerApp(root)
root.mainloop()
关键技术点解析:useMilitaryTime属性的实现
useMilitaryTime属性的核心逻辑
在IndustrialTimePicker类中,useMilitaryTime属性通过以下方式实现:
@property
def useMilitaryTime(self):
return self.use_military_time
@useMilitaryTime.setter
def useMilitaryTime(self, value):
if self.use_military_time != value:
self.use_military_time = value
self.update_display() # 切换时更新界面显示
属性获取:直接返回self.use_military_time的当前值(布尔型)
属性设置:当值变更时,更新内部状态并调用update_display()刷新界面
时间格式转换逻辑
在get_time()方法中实现了12小时制与24小时制的自动转换:
def get_time(self):
# 12小时制转换为24小时制
if not self.use_military_time:
hour = self.hour
if self.ampm_var.get() == “PM” and hour != 12:
hour += 12
elif self.ampm_var.get() “AM” and hour 12:
hour = 0
else:
hour = self.hour
return time(hour=hour, minute=self.minute, second=self.second)
12小时制→24小时制:AM时段(0-11点)保持不变,PM时段(1-12点)加12小时(注意12PM→12点,12AM→0点)
24小时制→12小时制:通过update_display()方法动态调整小时显示范围(1-12)并添加AM/PM标识
输入验证与限制
通过Spinbox组件的from_和to参数限制输入范围,并在回调函数中强制校验:
def on_hour_change(self):
self.hour = self.hour_var.get()
# 12小时制时限制小时范围(1-12)
if not self.use_military_time:
self.hour = max(1, min(12, self.hour))
self.hour_var.set(self.hour) # 强制修正非法输入
self.update_display()
工业场景适配与优化
高精度时间同步
工业场景需确保定时任务的准确性,可通过以下方式优化:
NTP时间同步:树莓派启动时自动同步网络时间(systemd-timesyncd服务)
硬件时钟校准:使用DS3231实时时钟模块(RTC)在断网时保持时间准确
任务执行日志:记录每次任务的执行时间与结果,便于追溯
冗余操作设计
为防止触摸屏失效,添加物理按键冗余控制:
在__init__方法中添加物理按键绑定
self.physical_hour_up = tk.Button(self.root, text=“▲”, command=lambda: self.adjust_hour(1))
self.physical_hour_down = tk.Button(self.root, text=“▼”, command=lambda: self.adjust_hour(-1))
布局略…
抗干扰设计
工业环境电磁干扰强,需增加以下防护措施:
电气隔离:使用光耦继电器隔离控制电路与主电路
防抖处理:对物理按键添加去抖动逻辑(after(100, callback)延迟执行)
异常捕获:任务执行代码添加try-except块,防止程序崩溃
测试与验证
功能测试步骤
基础功能验证:
启动程序,默认进入24小时制模式
设置时间为14:30:00,任务类型选择"设备启动",执行次数3次
点击"添加定时任务",确认任务列表显示"14:30:00 设备启动 3"
点击"启动定时任务",观察状态栏显示"状态:定时任务运行中…"
模式切换测试:
点击TimePicker的"切换为12小时制"按钮
验证时间显示是否从"14:30:00"变为"2:30:00 PM"
修改时间为"3:30:00 PM",切换回24小时制,验证显示是否变为"15:30:00"
边界条件测试:
测试小时输入:在24小时制下输入24,应自动修正为23
测试分钟输入:输入60,应自动修正为59
测试12小时制AM/PM切换:设置时间为12:30:00 AM,切换后应为00:30:00
性能测试
任务调度延迟:设置任务为当前时间+10秒,验证实际执行时间与设定时间的误差(应≤100ms)
界面响应速度:快速切换时间模式(24h↔12h),验证界面刷新延迟(应≤50ms)
多任务处理:同时运行10个定时任务,验证CPU占用(树莓派4B应≤30%)
总结
本文通过自定义IndustrialTimePicker组件实现了支持useMilitaryTime属性的工业级时间选择器,并结合schedule库构建了完整的定时任务系统。核心亮点包括:
双模式时间显示:通过useMilitaryTime属性灵活切换24小时制与12小时制
高精度时间控制:支持秒级调节,满足工业场景对时间精度的严格要求
可靠的任务执行:集成任务调度、日志记录与异常处理,确保工业设备稳定运行
抗干扰设计:通过电气隔离、去抖动与冗余操作,适应恶劣工业环境
该方案可直接部署在树莓派上,用于控制自动化产线、环境监控设备或能源管理系统,为工业物联网(IIoT)提供精准的时间控制能力。实际应用中可根据具体设备接口扩展execute_task方法,实现与PLC、传感器等工业设备的深度集成。
!https://i.imgur.com/9XZJZ2L.png
注:实际运行时,界面将显示24小时制时间选择器(如14:30:00),任务列表展示已设置的定时任务,状态栏实时反馈系统运行状态。#
