finnal
This commit is contained in:
263
PyApp/Pub.py
Executable file
263
PyApp/Pub.py
Executable file
@@ -0,0 +1,263 @@
|
||||
import os # 文件系统操作
|
||||
import base64 # Base64编码图片
|
||||
import paho.mqtt.client as mqtt # MQTT通信
|
||||
import pymysql # 数据库操作
|
||||
import time # 时间处理
|
||||
from datetime import datetime, timedelta # 时间处理
|
||||
import serial # 串口通信
|
||||
import re # 正则匹配GPS串口数据
|
||||
import threading # 多线程上传图片
|
||||
|
||||
# ======== 配置区 ========
|
||||
IMAGE_FOLDER = "/mnt/save/warning" # 图片目录
|
||||
MQTT_BROKER = "116.147.36.110"
|
||||
MQTT_PORT = 1883
|
||||
MQTT_TOPIC = "images/topic"
|
||||
|
||||
DB_CONFIG = {
|
||||
"host": "116.147.36.110",
|
||||
"port": 13306,
|
||||
"user": "root",
|
||||
"password": "Wxit11335577",
|
||||
"database": "mqtt"
|
||||
}
|
||||
|
||||
SERIAL_PORT = "/dev/ttyUSB3"
|
||||
SERIAL_BAUDRATE = 115200
|
||||
|
||||
MAX_UPLOADED_SIZE = 100000 # 最大上传记录数
|
||||
NOW_SIZE = 0 # 当前记录数
|
||||
|
||||
LOG_FILE = os.path.join(os.path.dirname(os.path.abspath(__file__)), "upload.log")
|
||||
|
||||
# ======== 全局变量与线程锁 ========
|
||||
last_lon, last_lat = None, None # 上次读取到的GPS
|
||||
ser = None # 串口对象
|
||||
|
||||
serial_lock = threading.Lock()
|
||||
uploaded_lock = threading.Lock()
|
||||
uploaded = {} # 保存已上传记录的文件及时间
|
||||
|
||||
# ======== 工具函数 ========
|
||||
def write_log(message):
|
||||
"""写入日志文件"""
|
||||
with open(LOG_FILE, "a") as logf:
|
||||
logf.write(f"{datetime.now().strftime('%Y-%m-%d %H:%M:%S')} - {message}\n")
|
||||
|
||||
# def extract_gps_data(serial_line):
|
||||
# """
|
||||
# 从串口数据中提取经纬度(只处理$GNGLL格式)
|
||||
# 示例数据: $GNGLL,3114.72543,N,12130.62735,E,...
|
||||
# 只做方向转换,不转换度分格式
|
||||
# 返回 (lon, lat),都为float,带正负号
|
||||
# """
|
||||
# match = re.search(r'\$GNGLL,(\d+\.\d+),(N|S),(\d+\.\d+),(E|W)', serial_line)
|
||||
# if match:
|
||||
# lat = float(match.group(1))
|
||||
# lat_dir = match.group(2)
|
||||
# lon = float(match.group(3))
|
||||
# lon_dir = match.group(4)
|
||||
|
||||
# if lat_dir == 'S':
|
||||
# lat = -lat
|
||||
# if lon_dir == 'W':
|
||||
# lon = -lon
|
||||
# return lon, lat
|
||||
# return None, None
|
||||
|
||||
|
||||
def extract_gps_data(serial_line):
|
||||
"""
|
||||
从串口数据中提取经纬度,支持$GNGLL、$GNRMC、$GNGGA格式
|
||||
只做方向正负号转换,不转换度分格式
|
||||
返回 (lon, lat),float,带正负号
|
||||
"""
|
||||
# 先匹配$GNGLL(你之前的)
|
||||
match = re.search(r'\$GNGLL,(\d+\.\d+),(N|S),(\d+\.\d+),(E|W)', serial_line)
|
||||
if match:
|
||||
lat = float(match.group(1))
|
||||
lat_dir = match.group(2)
|
||||
lon = float(match.group(3))
|
||||
lon_dir = match.group(4)
|
||||
|
||||
if lat_dir == 'S':
|
||||
lat = -lat
|
||||
if lon_dir == 'W':
|
||||
lon = -lon
|
||||
|
||||
return lon, lat
|
||||
|
||||
# 尝试匹配$GNRMC
|
||||
match = re.search(r'\$GNRMC,[^,]*,[AV],(\d+\.\d+),(N|S),(\d+\.\d+),(E|W)', serial_line)
|
||||
if match:
|
||||
lat = float(match.group(1))
|
||||
lat_dir = match.group(2)
|
||||
lon = float(match.group(3))
|
||||
lon_dir = match.group(4)
|
||||
|
||||
if lat_dir == 'S':
|
||||
lat = -lat
|
||||
if lon_dir == 'W':
|
||||
lon = -lon
|
||||
|
||||
return lon, lat
|
||||
|
||||
# 尝试匹配$GNGGA
|
||||
match = re.search(r'\$GNGGA,[^,]*,(\d+\.\d+),(N|S),(\d+\.\d+),(E|W)', serial_line)
|
||||
if match:
|
||||
lat = float(match.group(1))
|
||||
lat_dir = match.group(2)
|
||||
lon = float(match.group(3))
|
||||
lon_dir = match.group(4)
|
||||
|
||||
if lat_dir == 'S':
|
||||
lat = -lat
|
||||
if lon_dir == 'W':
|
||||
lon = -lon
|
||||
|
||||
return lon, lat
|
||||
|
||||
return None, None
|
||||
|
||||
def read_gps_data():
|
||||
global last_lon, last_lat, ser
|
||||
with serial_lock:
|
||||
try:
|
||||
if ser is None or not ser.is_open:
|
||||
ser = serial.Serial(SERIAL_PORT, SERIAL_BAUDRATE, timeout=1)
|
||||
|
||||
line = ser.readline().decode('utf-8').strip()
|
||||
if line:
|
||||
# 只对特定句子类型尝试解析并写日志
|
||||
if line.startswith(('$GNGLL', '$GNRMC', '$GNGGA')):
|
||||
lon, lat = extract_gps_data(line)
|
||||
if lon is not None and lat is not None:
|
||||
last_lon, last_lat = lon, lat
|
||||
return lon, lat
|
||||
else:
|
||||
write_log(f"无效的GPS数据: {line}")
|
||||
else:
|
||||
# 忽略其他NMEA语句
|
||||
pass
|
||||
except Exception as e:
|
||||
write_log(f"串口读取失败: {e}")
|
||||
return 1201, 3129
|
||||
|
||||
|
||||
# def read_gps_data():
|
||||
# """
|
||||
# 从串口读取一行数据并尝试解析GPS经纬度。
|
||||
# 不成功时返回 (None, None)
|
||||
# """
|
||||
# global last_lon, last_lat, ser
|
||||
# with serial_lock:
|
||||
# try:
|
||||
# if ser is None or not ser.is_open:
|
||||
# ser = serial.Serial(SERIAL_PORT, SERIAL_BAUDRATE, timeout=1)
|
||||
|
||||
# line = ser.readline().decode('utf-8').strip()
|
||||
# if line:
|
||||
# lon, lat = extract_gps_data(line)
|
||||
# if lon is not None and lat is not None:
|
||||
# last_lon, last_lat = lon, lat # 更新最新值
|
||||
# return lon, lat
|
||||
# else:
|
||||
# write_log(f"无效的GPS数据: {line}")
|
||||
# except Exception as e:
|
||||
# write_log(f"串口读取失败: {e}")
|
||||
# return 1201, 3129
|
||||
|
||||
def save_to_db(topic, payload, lon, lat):
|
||||
"""
|
||||
将图片和经纬度信息写入数据库,可接受空值
|
||||
"""
|
||||
try:
|
||||
db = pymysql.connect(**DB_CONFIG)
|
||||
cursor = db.cursor()
|
||||
sql = "INSERT INTO mqtt_messages (topic, payload, lon, lat) VALUES (%s, %s, %s, %s)"
|
||||
cursor.execute(sql, (topic, payload, lon, lat))
|
||||
db.commit()
|
||||
write_log("数据库插入成功")
|
||||
except Exception as e:
|
||||
write_log(f"数据库插入失败: {e}")
|
||||
finally:
|
||||
db.close()
|
||||
|
||||
def publish_image(image_path):
|
||||
"""
|
||||
发布图片到MQTT、写入数据库,并附带经纬度信息(如无法获取可为空)
|
||||
"""
|
||||
with open(image_path, "rb") as f:
|
||||
img_bytes = f.read()
|
||||
img_b64 = base64.b64encode(img_bytes).decode("utf-8")
|
||||
|
||||
# MQTT发布
|
||||
client = mqtt.Client()
|
||||
client.connect(MQTT_BROKER, MQTT_PORT)
|
||||
client.publish(MQTT_TOPIC, img_b64)
|
||||
client.disconnect()
|
||||
|
||||
# 获取GPS数据(尝试获取最新,失败用历史,再失败就用None)
|
||||
lon, lat = read_gps_data()
|
||||
if lon is None or lat is None:
|
||||
write_log("GPS读取失败,尝试使用上一次位置")
|
||||
lon, lat = last_lon, last_lat
|
||||
|
||||
if lon is not None and lat is not None:
|
||||
write_log(f"使用经纬度上传: lon={lon}, lat={lat}")
|
||||
else:
|
||||
write_log("经纬度为空,上传空GPS信息")
|
||||
|
||||
save_to_db(MQTT_TOPIC, img_b64, lon, lat)
|
||||
|
||||
def upload_worker(img_path):
|
||||
"""上传线程任务,处理一张图片上传"""
|
||||
try:
|
||||
publish_image(img_path)
|
||||
write_log("上传线程结束: " + img_path + ":\t成功!")
|
||||
except Exception as e:
|
||||
write_log(f"上传图片 {img_path} 时出错: {e}")
|
||||
|
||||
def clean_uploaded():
|
||||
"""
|
||||
清理7天前的已上传图片记录,避免内存泄漏
|
||||
"""
|
||||
with uploaded_lock:
|
||||
expiration_time = datetime.now() - timedelta(days=7)
|
||||
keys_to_remove = [key for key, value in uploaded.items() if value < expiration_time]
|
||||
for key in keys_to_remove:
|
||||
del uploaded[key]
|
||||
if keys_to_remove:
|
||||
write_log(f"清理已上传图片记录,移除 {len(keys_to_remove)} 条记录")
|
||||
|
||||
# ======== 主循环程序入口 ========
|
||||
if __name__ == "__main__":
|
||||
last_clean_time = time.time()
|
||||
try:
|
||||
while True:
|
||||
now = time.time()
|
||||
for filename in os.listdir(IMAGE_FOLDER):
|
||||
if filename.lower().endswith((".jpg", ".jpeg", ".png", ".bmp", ".gif")):
|
||||
img_path = os.path.join(IMAGE_FOLDER, filename)
|
||||
mtime = os.path.getmtime(img_path)
|
||||
# 检查文件是否是最近10秒内新创建,且未上传
|
||||
if now - mtime <= 10:
|
||||
with uploaded_lock:
|
||||
if img_path in uploaded:
|
||||
continue
|
||||
write_log("开始上传图片: " + img_path)
|
||||
t = threading.Thread(target=upload_worker, args=(img_path,))
|
||||
t.start()
|
||||
with uploaded_lock:
|
||||
uploaded[img_path] = datetime.now()
|
||||
# 每小时清理一次上传记录
|
||||
if time.time() - last_clean_time > 3600:
|
||||
clean_uploaded()
|
||||
last_clean_time = time.time()
|
||||
|
||||
time.sleep(2) # 每2秒轮询一次
|
||||
except Exception as e:
|
||||
write_log(f"主程序异常: {e}")
|
||||
finally:
|
||||
if ser is not None and ser.is_open:
|
||||
ser.close()
|
89
PyApp/upload.log
Normal file
89
PyApp/upload.log
Normal file
@@ -0,0 +1,89 @@
|
||||
2025-08-10 13:24:05 - 使用经纬度上传: lon=1201, lat=3129
|
||||
2025-08-10 13:24:05 - 使用经纬度上传: lon=1201, lat=3129
|
||||
2025-08-10 13:24:08 - 开始上传图片: /mnt/save/warning/warning_20250810_132407.jpg
|
||||
2025-08-10 13:24:09 - 使用经纬度上传: lon=1201, lat=3129
|
||||
2025-08-10 13:24:10 - 开始上传图片: /mnt/save/warning/warning_20250810_132410.jpg
|
||||
2025-08-10 13:24:11 - 使用经纬度上传: lon=1201, lat=3129
|
||||
2025-08-10 13:24:12 - 开始上传图片: /mnt/save/warning/warning_20250810_132411.jpg
|
||||
2025-08-10 13:24:14 - 使用经纬度上传: lon=12015.97019, lat=3129.92541
|
||||
2025-08-10 13:24:14 - 开始上传图片: /mnt/save/warning/warning_20250810_132413.jpg
|
||||
2025-08-10 13:24:15 - 使用经纬度上传: lon=12015.97019, lat=3129.92541
|
||||
2025-08-10 13:24:16 - 开始上传图片: /mnt/save/warning/warning_20250810_132415.jpg
|
||||
2025-08-10 13:24:18 - 使用经纬度上传: lon=1201, lat=3129
|
||||
2025-08-10 13:24:18 - 数据库插入成功
|
||||
2025-08-10 13:24:18 - 数据库插入成功
|
||||
2025-08-10 13:24:18 - 上传线程结束: /mnt/save/warning/warning_20250810_132354.jpg: 成功!
|
||||
2025-08-10 13:24:18 - 数据库插入成功
|
||||
2025-08-10 13:24:18 - 上传线程结束: /mnt/save/warning/warning_20250810_132356.jpg: 成功!
|
||||
2025-08-10 13:24:18 - 上传线程结束: /mnt/save/warning/warning_20250810_132353.jpg: 成功!
|
||||
2025-08-10 13:24:18 - 数据库插入成功
|
||||
2025-08-10 13:24:18 - 上传线程结束: /mnt/save/warning/warning_20250810_132350.jpg: 成功!
|
||||
2025-08-10 13:24:18 - 开始上传图片: /mnt/save/warning/warning_20250810_132416.jpg
|
||||
2025-08-10 13:24:18 - 开始上传图片: /mnt/save/warning/warning_20250810_132417.jpg
|
||||
2025-08-10 13:24:18 - 开始上传图片: /mnt/save/warning/warning_20250810_132418.jpg
|
||||
2025-08-10 13:24:18 - 数据库插入成功
|
||||
2025-08-10 13:24:18 - 上传线程结束: /mnt/save/warning/warning_20250810_132357.jpg: 成功!
|
||||
2025-08-10 13:24:19 - 数据库插入成功
|
||||
2025-08-10 13:24:19 - 上传线程结束: /mnt/save/warning/warning_20250810_132401.jpg: 成功!
|
||||
2025-08-10 13:24:19 - 使用经纬度上传: lon=1201, lat=3129
|
||||
2025-08-10 13:24:19 - 使用经纬度上传: lon=1201, lat=3129
|
||||
2025-08-10 13:24:19 - 使用经纬度上传: lon=1201, lat=3129
|
||||
2025-08-10 13:24:19 - 数据库插入成功
|
||||
2025-08-10 13:24:19 - 上传线程结束: /mnt/save/warning/warning_20250810_132400.jpg: 成功!
|
||||
2025-08-10 13:24:20 - 数据库插入成功
|
||||
2025-08-10 13:24:20 - 上传线程结束: /mnt/save/warning/warning_20250810_132403.jpg: 成功!
|
||||
2025-08-10 13:24:20 - 开始上传图片: /mnt/save/warning/warning_20250810_132420.jpg
|
||||
2025-08-10 13:24:20 - 开始上传图片: /mnt/save/warning/warning_20250810_132419.jpg
|
||||
2025-08-10 13:24:20 - 数据库插入成功
|
||||
2025-08-10 13:24:20 - 上传线程结束: /mnt/save/warning/warning_20250810_132402.jpg: 成功!
|
||||
2025-08-10 13:24:21 - 使用经纬度上传: lon=1201, lat=3129
|
||||
2025-08-10 13:24:21 - 使用经纬度上传: lon=1201, lat=3129
|
||||
2025-08-10 13:24:22 - 开始上传图片: /mnt/save/warning/warning_20250810_132421.jpg
|
||||
2025-08-10 13:24:22 - 开始上传图片: /mnt/save/warning/warning_20250810_132422.jpg
|
||||
2025-08-10 13:24:23 - 使用经纬度上传: lon=1201, lat=3129
|
||||
2025-08-10 13:24:23 - 使用经纬度上传: lon=1201, lat=3129
|
||||
2025-08-10 13:24:24 - 开始上传图片: /mnt/save/warning/warning_20250810_132423.jpg
|
||||
2025-08-10 13:24:24 - 数据库插入成功
|
||||
2025-08-10 13:24:24 - 上传线程结束: /mnt/save/warning/warning_20250810_132407.jpg: 成功!
|
||||
2025-08-10 13:24:25 - 使用经纬度上传: lon=1201, lat=3129
|
||||
2025-08-10 13:24:26 - 开始上传图片: /mnt/save/warning/warning_20250810_132424.jpg
|
||||
2025-08-10 13:24:26 - 开始上传图片: /mnt/save/warning/warning_20250810_132425.jpg
|
||||
2025-08-10 13:24:26 - 数据库插入成功
|
||||
2025-08-10 13:24:26 - 上传线程结束: /mnt/save/warning/warning_20250810_132410.jpg: 成功!
|
||||
2025-08-10 13:24:28 - 数据库插入成功
|
||||
2025-08-10 13:24:28 - 上传线程结束: /mnt/save/warning/warning_20250810_132411.jpg: 成功!
|
||||
2025-08-10 13:24:28 - 使用经纬度上传: lon=12015.97039, lat=3129.92553
|
||||
2025-08-10 13:24:28 - 使用经纬度上传: lon=12015.97039, lat=3129.92553
|
||||
2025-08-10 13:24:28 - 开始上传图片: /mnt/save/warning/warning_20250810_132428.jpg
|
||||
2025-08-10 13:24:28 - 开始上传图片: /mnt/save/warning/warning_20250810_132427.jpg
|
||||
2025-08-10 13:24:29 - 数据库插入成功
|
||||
2025-08-10 13:24:29 - 上传线程结束: /mnt/save/warning/warning_20250810_132413.jpg: 成功!
|
||||
2025-08-10 13:24:29 - 使用经纬度上传: lon=1201, lat=3129
|
||||
2025-08-10 13:24:29 - 使用经纬度上传: lon=1201, lat=3129
|
||||
2025-08-10 13:24:30 - 开始上传图片: /mnt/save/warning/warning_20250810_132429.jpg
|
||||
2025-08-10 13:24:32 - 使用经纬度上传: lon=1201, lat=3129
|
||||
2025-08-10 13:24:32 - 开始上传图片: /mnt/save/warning/warning_20250810_132431.jpg
|
||||
2025-08-10 13:24:33 - 数据库插入成功
|
||||
2025-08-10 13:24:33 - 上传线程结束: /mnt/save/warning/warning_20250810_132415.jpg: 成功!
|
||||
2025-08-10 13:24:34 - 使用经纬度上传: lon=1201, lat=3129
|
||||
2025-08-10 13:24:34 - 开始上传图片: /mnt/save/warning/warning_20250810_132433.jpg
|
||||
2025-08-10 13:24:35 - 数据库插入成功
|
||||
2025-08-10 13:24:35 - 数据库插入成功
|
||||
2025-08-10 13:24:35 - 上传线程结束: /mnt/save/warning/warning_20250810_132418.jpg: 成功!
|
||||
2025-08-10 13:24:35 - 上传线程结束: /mnt/save/warning/warning_20250810_132417.jpg: 成功!
|
||||
2025-08-10 13:24:35 - 数据库插入成功
|
||||
2025-08-10 13:24:35 - 上传线程结束: /mnt/save/warning/warning_20250810_132416.jpg: 成功!
|
||||
2025-08-10 13:24:35 - 使用经纬度上传: lon=1201, lat=3129
|
||||
2025-08-10 13:24:36 - 数据库插入成功
|
||||
2025-08-10 13:24:36 - 上传线程结束: /mnt/save/warning/warning_20250810_132420.jpg: 成功!
|
||||
2025-08-10 13:24:36 - 数据库插入成功
|
||||
2025-08-10 13:24:36 - 上传线程结束: /mnt/save/warning/warning_20250810_132419.jpg: 成功!
|
||||
2025-08-10 13:24:38 - 数据库插入成功
|
||||
2025-08-10 13:24:38 - 上传线程结束: /mnt/save/warning/warning_20250810_132422.jpg: 成功!
|
||||
2025-08-10 13:24:38 - 数据库插入成功
|
||||
2025-08-10 13:24:38 - 上传线程结束: /mnt/save/warning/warning_20250810_132421.jpg: 成功!
|
||||
2025-08-10 13:24:38 - 开始上传图片: /mnt/save/warning/warning_20250810_132437.jpg
|
||||
2025-08-10 13:24:39 - 数据库插入成功
|
||||
2025-08-10 13:24:39 - 上传线程结束: /mnt/save/warning/warning_20250810_132423.jpg: 成功!
|
||||
2025-08-10 13:24:39 - 使用经纬度上传: lon=1201, lat=3129
|
||||
2025-08-10 16:25:27 - 主程序异常: [Errno 2] No such file or directory: '/mnt/save/warning'
|
Reference in New Issue
Block a user