python多线程读取list python多线程读取modbus_python

 前提摘要:

 装置是电能质量表,各项数据大都使用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()函数惊醒各种各样的封装而已。

该文章也算记录一下自己多进程在实际工作中的使用吧,做个笔记,以防忘记!!!!