前提摘要:
装置是电能质量表,各项数据大都使用Modbus寄存器存储,现在的工作是需要同时读取该装置的多个通道的Modbus寄存器,同时还要监控每个通道的寄存器值增长是否符合预期。
总的来说就是,同时开多个进程读取寄存器,每个进程中又包含一个While循环。结构没啥可赘述的。对了,关于如何读取工业modbus寄存器可以参考我写的python+robotframework 一篇文章中的项目源码,源码中封装了一个ModbusRobot的库,专门用来读写modbus寄存器!
源码:
上代码慢慢解释吧:
@staticmethod
def start_read_energy_counter(ipAddress, uMeas, iMeas, phiMeas, measurand, expectedCounterNumber, q, influenceQuantity, harV_Meas, harI_Meas, networkType):
cosPhi = abs(round(math.cos(math.radians(phiMeas)), 2))
sinPhi = abs(round(math.sin(math.radians(phiMeas)), 2))
mbus = ModbusRobot.ModbusRobot()
mbus.open_modbus_connection(connectionType='TCP', host=ipAddress)
pulseQuantity = round(mbus.read_holding_float(MBModbusRegister.PULSEQUALTIY.value), 8)
if measurand in [MBModbusRegister.ACTIVE_ENERGY_WPA_IMP, MBModbusRegister.ACTIVE_ENERGY_WPB_IMP, MBModbusRegister.ACTIVE_ENERGY_WPC_IMP, MBModbusRegister.ACTIVE_ENERGY_WP_IMP, MBModbusRegister.ACTIVE_ENERGY_WPA_EXP, MBModbusRegister.ACTIVE_ENERGY_WPB_EXP, MBModbusRegister.ACTIVE_ENERGY_WPC_EXP, MBModbusRegister.ACTIVE_ENERGY_WP_EXP]:
timeOfOneCounter = 3600 * pulseQuantity / (uMeas * iMeas * cosPhi)
if influenceQuantity == MEASInfluenceType.HARMONICS and harV_Meas is not None and harI_Meas is not None:
timeOfOneCounter = 3600 * pulseQuantity / (uMeas * iMeas * cosPhi + sum([uMeas * float(harmU[1]) * iMeas * float(harmI[1]) * abs(math.cos(math.radians(int(harmU[0]) * phiMeas + (int(harmI[2]) - int(harmU[2]))))) for harmU, harmI in zip(harV_Meas, harI_Meas)]))
elif measurand in [MBModbusRegister.REACTIVE_ENERGY_WQA_CAP, MBModbusRegister.REACTIVE_ENERGY_WQB_CAP, MBModbusRegister.REACTIVE_ENERGY_WQC_CAP, MBModbusRegister.REACTIVE_ENERGY_WQ_CAP, MBModbusRegister.REACTIVE_ENERGY_WQA_IND, MBModbusRegister.REACTIVE_ENERGY_WQB_IND, MBModbusRegister.REACTIVE_ENERGY_WQC_IND, MBModbusRegister.REACTIVE_ENERGY_WQ_IND]:
timeOfOneCounter = 3600 * pulseQuantity / (uMeas * iMeas * sinPhi)
if influenceQuantity == MEASInfluenceType.HARMONICS and harV_Meas is not None and harI_Meas is not None:
timeOfOneCounter = 3600 * pulseQuantity / math.sqrt((math.sqrt(uMeas ** 2 + (uMeas * harV_Meas[0][1]) ** 2) * math.sqrt(iMeas ** 2 + (iMeas * harI_Meas[0][1]) ** 2)) ** 2 - (uMeas * iMeas * cosPhi + (uMeas * harV_Meas[0][1]) * (iMeas * harI_Meas[0][1]) * cosPhi) ** 2)
else:
timeOfOneCounter = 3600 * pulseQuantity / (uMeas * iMeas)
initialCount = int(hex(mbus.read_holding_registers(measurand.value, 2)[0]) + '0000', base=16) + mbus.read_holding_registers(measurand.value, 2)[1]
expectedCounterNumber = expectedCounterNumber * 3 if measurand in [MBModbusRegister.ACTIVE_ENERGY_WP_IMP, MBModbusRegister.ACTIVE_ENERGY_WP_EXP, MBModbusRegister.REACTIVE_ENERGY_WQ_CAP, MBModbusRegister.REACTIVE_ENERGY_WQ_IND, MBModbusRegister.APPARENT_ENERGY_WS] and networkType not in [ACMNetworkType.SINGLE_PHASE] else expectedCounterNumber
while True:
time.sleep(0.010)
firstValidCounter = int(hex(mbus.read_holding_registers(measurand.value, 2)[0]) + '0000', base=16) + mbus.read_holding_registers(measurand.value, 2)[1]
firstValidPCTime = round(time.time(), 8)
if firstValidCounter - initialCount >= 1:
break
while True:
time.sleep(0.010)
finalValidCounter = int(hex(mbus.read_holding_registers(measurand.value, 2)[0]) + '0000', base=16) + mbus.read_holding_registers(measurand.value, 2)[1]
finalValidPCTime = round(time.time(), 8)
if finalValidCounter - firstValidCounter >= int(expectedCounterNumber):
break
acutalUsedTime = finalValidPCTime - firstValidPCTime
actualIncreaseCount = finalValidCounter - firstValidCounter
if measurand in [MBModbusRegister.ACTIVE_ENERGY_WP_IMP, MBModbusRegister.ACTIVE_ENERGY_WP_EXP, MBModbusRegister.REACTIVE_ENERGY_WQ_IND, MBModbusRegister.REACTIVE_ENERGY_WQ_CAP, MBModbusRegister.APPARENT_ENERGY_WS]:
if influenceQuantity == MEASInfluenceType.VOLTAGEUNBALANCE and networkType in [ACMNetworkType.FOUR_WIRE_THREE_PHASE_UNBALANCED, ACMNetworkType.THREE_WIRE_THREE_PHASE_BALANCED, ACMNetworkType.THREE_WIRE_THREE_PHASE_UNBALANCED_2L, ACMNetworkType.THREE_WIRE_THREE_PHASE_UNBALANCED_3L]:
acutalUsedTime = acutalUsedTime * 2.9 # For special fixed test points of the influence Quantity is voltage unbalance
else:
if networkType not in [ACMNetworkType.SINGLE_PHASE]:
acutalUsedTime = acutalUsedTime * 3
data = {
'Measurand': measurand,
'IncreasedCount': actualIncreaseCount,
'ActualUsedTime': acutalUsedTime,
'ExpectedUsedTime': timeOfOneCounter * actualIncreaseCount
}
q.put(data)
mbus.close_modbus_connection()
首先贴出的是一个基础函数,这是一个读取寄存器的函数(看函数名),其中两个While True循环就是来循环读取该寄存器,比如第一次读取的值是1,循环读取然后sleep,直到该寄存器的值增长到101时,退出循环,同时返回增长这100个数值所需要的时长!
这里我返回的是一个字典,包含测试通道,实际增长的count数,实际耗时,以及期望耗时时长;该字典会被后面调用的函数中使用。
接下来贴出的是一个验证精度的函数(如下图):
def validate_energy_accurancy(self, ipAddress, uMeas, iMeas, phiMeas, measurands, expectedCounterNumber, networkType, expectUncertainty=None, influenceQuantity=None, harV_Meas=None, harI_Meas=None, dataSource=MEASDataSource.MODBUSTCP):
uMeas = float(uMeas)
iMeas = float(iMeas)
phiMeas = float(phiMeas)
measurands = [Utils.to_enum(measurand, MBModbusRegister) for measurand in measurands]
expectedCounterNumber = int(expectedCounterNumber)
networkType = Utils.to_enum(networkType, ACMNetworkType)
influenceQuantity = Utils.to_enum(influenceQuantity, MEASInfluenceType) if influenceQuantity is not None else influenceQuantity
dataSource = Utils.to_enum(dataSource, MEASDataSource) if dataSource is not None else dataSource
if dataSource not in [MEASDataSource.MODBUSTCP]:
raise ValueError('Not support source: {:s}'.format(dataSource))
threads = []
q = multiprocessing.Queue()
isValidMeasurement = True
for measurand in measurands:
t = multiprocessing.Process(target=self.start_read_energy_counter, args=(ipAddress, uMeas, iMeas, phiMeas, measurand, expectedCounterNumber, q, influenceQuantity, harV_Meas, harI_Meas, networkType))
threads.append(t)
for t in threads:
t.start()
results = [q.get() for t in threads]
for t in threads:
t.terminate()
for result in results:
try:
actualUncertainty = abs((1 - round((result['ActualUsedTime'] / result['ExpectedUsedTime']), 6)))
allowedError = expectUncertainty * result['ExpectedUsedTime']
delta = abs(result['ActualUsedTime'] - result['ExpectedUsedTime'])
measurandName = Utils.to_enum(result['Measurand'], MBModbusRegister)
if delta > allowedError:
logger.error('{:s} increased {:d} counters used time {:.3f} s, expected to be {:.3f} s, difference of {:.3f} s, exceeds {:.3f} s'.format(measurandName.name, result['IncreasedCount'], result['ActualUsedTime'], result['ExpectedUsedTime'], delta, allowedError))
raise AssertionError('Measured {:s} accuracy is: {:.3f} didn\'t match expected accuracy: {:f}'.format(measurandName.name, actualUncertainty, expectUncertainty))
except AssertionError as err:
logger.info(err)
isValidMeasurement = False
logger.info('Measured {:s} accuracy is: {:f}, it matches expected accuracy: {:f}'.format(measurandName.name, actualUncertainty, expectUncertainty))
if not isValidMeasurement:
raise AssertionError('Measured values didn\'t match expected values')
这个函数就使用到了python的多进程,因为需求是同时读取,所以只好用多进程来做。
q = multiprocessing.Queue() #这里是将多进程中的Que()实例化,然后作为参数带入基础函数
for measurand in measurands:
t = multiprocessing.Process(target=self.start_read_energy_counter, args=(ipAddress, uMeas, iMeas, phiMeas, measurand, expectedCounterNumber, q, influenceQuantity, harV_Meas, harI_Meas, networkType)) # 这里是定义进程
threads.append(t)
for t in threads:
t.start() # 开始进程
results = [q.get() for t in threads] # 从进程中获取返回的数据,也就是基础函数中定义的字典
for t in threads:
t.terminate() # 终止进程
这段代码应该说是核心了,根据measurand测试通道数量来决定定义多少个进程,然后将参数传入要调用的函数;t.start()即开始进程;q.get()即获取基础函数中返回的数据,也就是q.put(data)中的这个data。
接下来的try....except....部分其实就是对获取的结果进行一个验证,验证误差值是否符合预期,否则将标识位 isValidMeasurement置为False。最终根据标志位来判断验证是否通过!
最后将核心的这个validate_energy_accuracy()核心函数再次封装,封装成RobotFrameWork中可直接使用的关键字:
def validate_active_energy_import_accurancy(self, ipAddress, uMeas, iMeas, phiMeas, expectedCounterNumber, measurands=None, networkType=None, iNom=None, iMax=None, influenceQuantity=None, harV_Meas=None, harI_Meas=None, measurementStandard=MEASMeasurementStandard.IEC61557, dataSource=MEASDataSource.MODBUSTCP):
cosPhi = abs(round(math.cos(math.radians(float(phiMeas))), 2))
conf = None
if networkType is None:
conf = self._configuration.get_ac_measurement_configuration()
networkType = Utils.to_enum(networkType, ACMNetworkType) if networkType is not None else conf.networkType
if measurands is None:
if networkType in [ACMNetworkType.FOUR_WIRE_THREE_PHASE_UNBALANCED, ACMNetworkType.THREE_WIRE_THREE_PHASE_UNBALANCED_3L, ACMNetworkType.THREE_WIRE_THREE_PHASE_UNBALANCED_2L]:
measurands = ['ACTIVE_ENERGY_WPA_IMP', 'ACTIVE_ENERGY_WPB_IMP', 'ACTIVE_ENERGY_WPC_IMP', 'ACTIVE_ENERGY_WP_IMP']
else:
measurands = ['ACTIVE_ENERGY_WPA_IMP', 'ACTIVE_ENERGY_WP_IMP']
expectUncertainty = self._get_active_energy_uncertainty(iNom, iMax, cosPhi, measurementStandard, influenceQuantity)
self.validate_energy_accurancy(ipAddress, uMeas, iMeas, phiMeas, measurands, expectedCounterNumber, networkType, expectUncertainty, influenceQuantity, harV_Meas, harI_Meas, dataSource)
validate_active_energy_import_accurancy()这个函数就是验证电能中有功power进度的关键字,还有一些验证无功功率和视在功率的一些函数,在此就不一一列举了,其根本就是将validate_energy_accuracy()函数惊醒各种各样的封装而已。
该文章也算记录一下自己多进程在实际工作中的使用吧,做个笔记,以防忘记!!!!