源码将于最后一遍文章给出下载
- 监测数据采集物联网应用开发步骤(10)
程序自动更新开发
前面章节写了部分功能模块开发:
- 日志或文本文件读写开发;
- Sqlite3数据库读写操作开发;
- 定时器插件化开发;
- 串口(COM)通讯开发;
- TCP/IP Client开发;
- TCP/IP Server 开发;
- modbus协议开发;
本章节啰嗦些,该解决方案最终业务成品有些需要部署在不同工控机或设备上,在实际应用过程中随时间推移有些需要升级迭代或修改bug,一旦安装部署设备点位达到一定数量,系统自动更新升级就是一个必要的步骤了。笔者开发的业务应用在相应的设备上安装已近千台设备,鉴于此增加了程序自动更新功能模块。
在com.zxy.common.Com_Para.py中添加如下内容
#自动更新服务端文件夹地址
urlPath = ""
#程序版本号
version = ""
新建程序版本自动更新类com.zxy.autoUpdate.GetVersion.py
#! python3
# -*- coding: utf-8 -
'''
Created on 2017年05月10日
@author: zxyong 13738196011
'''
import urllib.request,datetime
from com.zxy.adminlog.UsAdmin_Log import UsAdmin_Log
from com.zxy.common import Com_Para
from com.zxy.common.Com_Fun import Com_Fun
from com.zxy.interfaceReflect.A01_A1B2C3 import A01_A1B2C3
from com.zxy.z_debug import z_debug
from urllib.parse import urlparse
#监测数据采集物联网应用--程序版本自动更新类
class GetVersion(z_debug):
def __init__(self):
pass
#下载文件
def SaveRemoteFile(self,inputFileName,inputLocalFileName):
try:
temResult = urllib.request.urlretrieve(Com_Para.urlPath+"/"+inputFileName,Com_Para.ApplicationPath+Com_Para.zxyPath+inputLocalFileName)
except Exception as e:
if str(type(self)) == "<class 'type'>":
self.debug_in(self,repr(e)+"=>"+str(e.__traceback__.tb_lineno))#打印异常信息
else:
self.debug_in(repr(e)+"=>"+str(e.__traceback__.tb_lineno))#打印异常信息
return temResult
#保存版本号文件
def SaveVersionFile(self,version_no,inputLocalFileName):
temStrTFileName = Com_Para.ApplicationPath+Com_Para.zxyPath+inputLocalFileName
temFile = None
try:
temFile = open(temStrTFileName, 'w',encoding=Com_Para.U_CODE)
temFile.write(version_no)
temFile.close()
except Exception as e:
if str(type(self)) == "<class 'type'>":
self.debug_in(self,repr(e)+"=>"+str(e.__traceback__.tb_lineno))#打印异常信息
else:
self.debug_in(repr(e)+"=>"+str(e.__traceback__.tb_lineno))#打印异常信息
finally:
if temFile != None:
temFile.close()
#检测最新版本
def CheckVersion(self):
try:
print("check new version..."+Com_Fun.GetTimeDef())
#读取服务器端version.txt信息
temResult = self.GetRequestWeb()
if temResult == "":
return 0
temUL = UsAdmin_Log(Com_Para.ApplicationPath,temResult,0)
#文件形式版本号管理
temVersion = temUL.ReadFile(Com_Para.ApplicationPath+Com_Para.zxyPath + "version.txt")
Com_Para.version = temVersion
#数据库形式版本号管理
temVersion = Com_Para.version
temAryTem = []
temVersion_No = ""
temLastFile = ""
#判断文件是否存在
if temVersion == "" and temResult != "":
print("local version none,downloading new version")
temAryTem = temResult.split("\n")
for temLine in temAryTem:
temLine = temLine.replace("\r","").replace("\n","")
temLastFile = temLine
if temLine.find("version=") != -1:
temVersion_No = temLine
elif temLine != "":
print("new version file download:"+temLine)
self.SaveRemoteFile(temLine,"back/"+temLine)
self.CopyFile(temLine)
if temVersion_No != "":
#文件形式版本管理
self.SaveVersionFile(temVersion_No,"version.txt")
#数据库形式版本文件管理
#将版本号insert入库 该处代码省略....
Com_Para.version = temVersion_No
print("new version updated over:")
#如果读到reboot.txt则需要重启设备
if temLastFile.strip() == "reboot.txt":
self.RebootProgram()
return 0
if temVersion_No != "":
#文件形式版本管理
print("new version updated over:")
self.SaveVersionFile(temVersion_No,"version.txt")
#数据库形式版本文件管理
#将版本号insert入库 该处代码省略....
Com_Para.version = temVersion_No
elif temResult != "":
#数据库形式版本号管理
# localVersion = Com_Para.version
#文件形式版本号管理
temAryTem = temVersion.split("\n")
for temLine in temAryTem:
temArySub = temLine.split("=")
if len(temArySub) > 1 and temArySub[0] == "version":
localVersion = temArySub[1]
break
bFlag = False
temAryTem = temResult.split("\n")
for temLine in temAryTem:
temLine = temLine.replace("\r","").replace("\n","")
temLastFile = temLine
temArySub = temLine.split("=")
if len(temArySub) > 1 and temArySub[0] == "version":
try:
if localVersion.find("=") != -1:
iLV = int(localVersion.split("=")[1])
else:
iLV = int(localVersion)
if localVersion != "" and iLV >= int(temArySub[1]):
bFlag = False
else:
temVersion_No = temLine
bFlag = True
except:
bFlag = False
break
else:
if bFlag:
self.SaveRemoteFile(temLine,"back/"+temLine)
self.CopyFile(temLine)
if temVersion_No != "":
#文件形式版本管理
self.SaveVersionFile(temVersion_No,"version.txt")
#数据库形式版本文件管理
#将版本号insert入库 该处代码省略....
Com_Para.version = temVersion_No
print("new version updated over:"+temVersion_No)
if temLastFile.strip() == "reboot.txt":
self.RebootProgram()
return 0
if (bFlag and temVersion_No != ""):
#文件形式版本管理
self.SaveVersionFile(temVersion_No,"version.txt")
#数据库形式版本文件管理
#将版本号insert入库 该处代码省略....
Com_Para.version = temVersion_No
print("new version updated over:")
if bFlag and temLastFile.strip() == "reboot.txt":
self.RebootProgram()
return 0
except Exception as e:
if str(type(self)) == "<class 'type'>":
self.debug_in(self,repr(e)+"=>"+str(e.__traceback__.tb_lineno))#打印异常信息
else:
self.debug_in(repr(e)+"=>"+str(e.__traceback__.tb_lineno))#打印异常信息
return 1
#重启设备
def RebootProgram(self):
aa = A01_A1B2C3()
if Com_Para.devsys.lower() == "linux":
aa.param_value2 = "reboot -f"
aa.CmdExecLinux()
else:
aa.param_value2 = "shutdown -r"
aa.CmdExecWin()
#文件更新
def CopyFile(self,inputWebFile):
temAryTem = inputWebFile.split("\n")
for temLine in temAryTem:
if temLine.find("=") == -1:
aa = A01_A1B2C3()
temFileName = temLine[0:temLine.find(".")]
temFileAtt = temLine[temLine.find("."):len(temLine)].upper()
if temFileAtt.upper() == ".ZIP":
aa.param_value2 = "unzip -o "+Com_Para.ApplicationPath+Com_Para.zxyPath+"back"+Com_Para.zxyPath+temLine+" -d "+Com_Para.ApplicationPath+Com_Para.zxyPath+"back"+Com_Para.zxyPath+temFileName+Com_Para.zxyPath
aa.param_value3 = Com_Para.ApplicationPath+Com_Para.zxyPath+"back"+Com_Para.zxyPath+temLine
aa.param_value4 = Com_Para.ApplicationPath+Com_Para.zxyPath+"back"+Com_Para.zxyPath+temFileName+Com_Para.zxyPath
#利用命令进行解压缩
#aa.CmdExecLinux()
#利用python自带解压缩模块
aa.unzip_file()
if Com_Para.devsys.lower() == "linux":
aa.param_value2 = "cp -rf "+Com_Para.ApplicationPath+Com_Para.zxyPath+"back"+Com_Para.zxyPath+temFileName+Com_Para.zxyPath+"* "+Com_Para.ApplicationPath+Com_Para.zxyPath
else:
aa.param_value2 = "xcopy "+Com_Para.ApplicationPath+Com_Para.zxyPath+"back"+Com_Para.zxyPath+temFileName+Com_Para.zxyPath+"*.* "+Com_Para.ApplicationPath+" /E /Y"
aa.CmdExecLinux()
#执行sql语句
elif temFileAtt.upper() == ".SQL":
aa.RunSqlFile(Com_Para.ApplicationPath+Com_Para.zxyPath+"back"+Com_Para.zxyPath+temLine)
#执行更新数据接口
elif temFileAtt.upper() == ".JSON":
#JSON业务数据进行数据库结构更改或升级
pass
else:
if Com_Para.devsys.lower() == "linux":
aa.param_value2 = "cp -rf "+Com_Para.ApplicationPath+Com_Para.zxyPath+"back"+Com_Para.zxyPath+temLine+" "+Com_Para.ApplicationPath+Com_Para.zxyPath
else:
aa.param_value2 = "copy "+Com_Para.ApplicationPath+Com_Para.zxyPath+"back"+Com_Para.zxyPath+temLine+" "+Com_Para.ApplicationPath+Com_Para.zxyPath+" /Y"
aa.CmdExecLinux()
#inputFlag网络链接是否正常
def set_dSockList(self,inputFlag):
urlp = urlparse(Com_Para.urlPath)
if Com_Para.urlPath == "":
return None
key = str(urlp.netloc.replace(":","|"))
#存在
if key not in list(Com_Para.dSockList.keys()):
Com_Para.dSockList[key] = [0,datetime.datetime.now()]
if inputFlag == False:
objAry = Com_Para.dSockList[key]
if objAry[0] <= 10:
objAry[0] = objAry[0] + 1
objAry[1] = datetime.datetime.now()
Com_Para.dSockList[key] = objAry
else:
Com_Para.dSockList[key] = [0,datetime.datetime.now()]
#判断上次链接时间频率是否合规
def judge_dSock(self):
urlp = urlparse(Com_Para.urlPath)
if Com_Para.urlPath == "":
return False
key = str(urlp.netloc.replace(":","|"))
if key not in list(Com_Para.dSockList.keys()):
Com_Para.dSockList[key] = [0,datetime.datetime.now()]
objAry = Com_Para.dSockList[key]
starttime = datetime.datetime.now()
if objAry[0] <= 3:
return True
elif objAry[0] > 4 and starttime >= objAry[1] + datetime.timedelta(hours=12):
return True
else:
return False
def GetRequestWeb(self):
temResult = ""
try:
#判断上次链接时间频率是否合规
if not self.judge_dSock():
return ""
resp = urllib.request.urlopen(Com_Para.urlPath+"/version.txt",timeout=5)
temResult = resp.read().decode(Com_Para.U_CODE)
except Exception as e:
temLog = ""
if str(type(self)) == "<class 'type'>":
temLog = self.debug_info(self)+repr(e)
else:
temLog = self.debug_info()+repr(e)
uL = UsAdmin_Log(Com_Para.ApplicationPath, temLog+"=>"+str(e.__traceback__.tb_lineno))
uL.WriteLog()
temResult = ""
self.set_dSockList(False)
return temResult
新建操作系统执行指令接口类com.zxy.interfaceReflect.A01_A1B2C3.py
#! python3
# -*- coding: utf-8 -
'''
Created on 2017年05月10日
@author: zxyong 13738196011
'''
import os, sys,zipfile,subprocess,tempfile
from urllib.parse import unquote
from com.zxy.common import Com_Para
from com.zxy.common.Com_Fun import Com_Fun
from com.zxy.z_debug import z_debug
#监测数据采集物联网应用--操作系统执行指令接口类
class A01_A1B2C3(z_debug):
strResult = ""
session_id = ""
param_name = ""
param_value1 = None #1:同步更新设备时间
param_value2 = None #当前时间
param_value3 = None
param_value4 = None
param_value5 = None
param_value6 = None
param_value7 = None
param_value8 = None
param_value9 = None
param_value10 = None
#是否继续执行 1:继续执行 0:中断执行
strContinue = "0"
strCmdValue = ""
def __init__(self):
pass
def init_start(self):
# /**************************************************/
# //示例: 此处写业务逻辑,最后给 param_name,param_value1....重新赋值
# // param_name += Com_Fun.Get_New_GUID()
# // param_value1 += Com_Fun.Get_New_GUID()
# // param_value2 += Com_Fun.Get_New_GUID()
# // ......
# /**************************************************/
# //示例:若业务逻辑判断失败,不发送数据库接口请求并返回失败信息
#strContinue = "0" 表示拦截器判断接口执行结束,不需要入库操作,直接返回信息
temValue = ""
self.strContinue = "0"
if self.param_value1 == "100" and Com_Para.devsys == "linux":
temValue = self.CmdExecLinux(self)
self.strResult = "命令结果:\r\n"
for temR in temValue:
self.strResult = self.strResult+temR.decode(Com_Para.U_CODE)+"\r\n"
#100执行命令
elif self.param_value1 == "100" and Com_Para.devsys == "windows":
temValue = self.CmdExecWin(self)
self.strResult = "{\""+self.param_name+"\":[{\"s_result\":\"1\",\"error_desc\":\"命令执行成功"+temValue+"\"}]}"
#利用python自带解压缩
def unzip_file(self):
temzipfile = zipfile.ZipFile(self.param_value3, 'r')
temzipfile.extractall(self.param_value4)
#重启程序
def RestartPro(self):
python = sys.executable
os.execl(python,python,* sys.argv)
#执行命令(Linux)
def CmdExecLinux(self):
out_temp = None
try:
temCommand = unquote(unquote(self.param_value2,Com_Para.U_CODE))
out_temp = tempfile.SpooledTemporaryFile(max_size=10*1000)
fileno = out_temp.fileno()
obj = subprocess.Popen(temCommand,stdout=fileno,stderr=fileno,shell=True)
obj.wait()
out_temp.seek(0)
temResult = out_temp.readlines()
except Exception as e:
temResult = repr(e)
finally:
if out_temp is not None:
out_temp.close()
return temResult
#执行命令(Windows)
def CmdExecWin(self):
try:
temCommand = unquote(unquote(self.param_value2,Com_Para.U_CODE))
temResult = os.popen(temCommand).read()
except Exception as e:
temResult = repr(e)
return temResult
#更新设备时间(Windows)
def ChangeDateWin(self):
try:
temDate = Com_Fun.GetDateInput('%Y-%m-%d', '%Y-%m-%d %H:%M:%S', unquote(unquote(self.param_value2.replace("+"," "),Com_Para.U_CODE)))
temTime = Com_Fun.GetDateInput('%H:%M:%S', '%Y-%m-%d %H:%M:%S', unquote(unquote(self.param_value2.replace("+"," "),Com_Para.U_CODE)))
temResult = os.system('date {} && time {}'.format(temDate,temTime))
except:
temResult = -1
return temResult
#更新设备时间(Linux)
def ChangeDateLinux(self):
try:
#将硬件时间写入到系统时间:
#hwclock -s
#将系统时间写入到硬件时间
#hwclock -w
temCommand = unquote(unquote("date -s \""+self.param_value2.replace("+"," ").split(" ")[0]+"\"",Com_Para.U_CODE))
temCommand = unquote(unquote("date -s \""+self.param_value2.replace("+"," ").split(" ")[1]+"\"",Com_Para.U_CODE))
out_temp = tempfile.SpooledTemporaryFile(max_size=10*1000)
fileno = out_temp.fileno()
obj = subprocess.Popen(temCommand,stdout=fileno,stderr=fileno,shell=True)
obj.wait()
temCommand = unquote(unquote("hwclock -w",Com_Para.U_CODE))
obj = subprocess.Popen(temCommand,stdout=fileno,stderr=fileno,shell=True)
obj.wait()
out_temp.seek(0)
temByt = out_temp.readlines()
temResult = temByt[0].decode(Com_Para.U_CODE)
if temResult == "" :
temResult = "1"
except Exception as e:
temResult = "-1"
finally:
if out_temp is not None:
out_temp.close()
return temResult
版本更新测试案例MonitorDataCmd.py主文件中编写:
from com.zxy.autoUpdate.GetVersion import GetVersion
在 if __name__ == '__main__':下添加
#版本更新测试
Com_Para.urlPath = "http://localhost:8080/testweb/updtest/"
gv = GetVersion()
gv.CheckVersion()
print("=>版本更新完成")
程序文件新建back文件夹临时存放下载更新包的zip文件;
版本更新服务端文件信息
版本更新服务端版本号信息
运行结果设备端更新之后新增文件
更新文件已下载并自动解压缩成功。
print打印出的内容