From 7b12740fb330ca12287b13d1aa84649998a0f455 Mon Sep 17 00:00:00 2001 From: Florentin Bulot <32537171+FlorentinBulotUoS@users.noreply.github.com> Date: Tue, 19 Feb 2019 11:40:29 +0000 Subject: [PATCH 01/19] Implementation of on, off, histogram and checksum checking --- opc/__init__.py | 684 ++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 684 insertions(+) diff --git a/opc/__init__.py b/opc/__init__.py index e306a87..3fa76c6 100644 --- a/opc/__init__.py +++ b/opc/__init__.py @@ -1101,3 +1101,687 @@ def read_histogram(self): data['Bin 15'] return data + + + +class OPCR1(_OPC): + """Create an instance of the Alphasene OPC-R1. Currently supported by firmware + versions 14-18. opc.OPCR1 inherits from the opc.OPC parent class. + + :param spi_connection: The spidev instance for the SPI connection. + + :type spi_connection: spidev.SpiDev + + :rtype: opc.OPCR1 + + :raises: opc.exceptions.FirmwareVersionError + + :Example: + + >>> alpha = opc.OPCR1(spi) + >>> alpha + Alphasense OPC-R1v2.10 + """ + def __init__(self, spi_connection, **kwargs): + super(OPCR1, self).__init__(spi_connection, model='R1', **kwargs) + + firmware_min = 2. # Minimum firmware version supported + firmware_max = 2. # Maximum firmware version supported + SPI_OPC_READY = 0xF3 + SPI_OPC_BUSY = 0x03 + + if self.firmware['major'] < firmware_min or self.firmware['major'] > firmware_max: + logger.error("Firmware version is invalid for this device.") + + raise FirmwareVersionError("Your firmware is not yet supported. Only version 2 is currently supported.") + + def get_ready_response(self, SPIcommand): + + message = self.cnxn.xfer(SPIcommand)[0] + sleep(1e-3) + attempt = 0 + + while(attempt<20 & message != SPI_OPC_READY): + attempt+=1 + if(message != SPI_OPC_READY): + message = self.cnxn.xfer(SPIcommand) + if(message != SPI_OPC_READY): + sleep(1e-3) # Wait 1ms before retrying + + if(message != SPI_OPC_READY): + if(message == SPI_OPC_BUSY): + self.spi_connection.flush() + sleep(2s) + raise Exception("ERROR Waiting 2s (for OPC comms timeout)") + else: + self.spi_connection.flush() + self.off() + self.on() + raise Exception("ERROR Resetting the sensor") + + + + def on(self): + """Turn ON the OPC (fan and laser) + + :rtype: boolean + + :Example: + + >>> alpha.on() + True + """ + self.get_ready_response(0x03) # send the command byte + sleep(10e-3) # sleep for 10 ms + b2 = self.cnxn.xfer([0x03])[0] # send the following byte powers the laser and fan + sleep(10) # sleep while it is properly powered on + + return True if b2 == SPI_OPC_BUSY else False + + def off(self): + """Turn OFF the OPC (fan and laser) + + :rtype: boolean + + :Example: + + >>> alpha.off() + True + """ + self.get_ready_response(0x03) # send the command byte + sleep(10e-3) # sleep for 10 ms + b2 = self.cnxn.xfer([0x00]) # send the following byte to turn off laser and fan + sleep(0.1) + + return True if b2 == SPI_OPC_BUSY else False + + def config(self): + """Read the configuration variables and returns them as a dictionary + + :rtype: dictionary + + :Example: + + >>> alpha.config() + { + 'BPD 13': 1.6499, + 'BPD 12': 1.6499, + 'BPD 11': 1.6499, + 'BPD 10': 1.6499, + 'BPD 15': 1.6499, + 'BPD 14': 1.6499, + 'BSVW 15': 1.0, + ... + } + """ + config = [] + data = {} + + # Send the command byte and sleep for 10 ms + self.cnxn.xfer([0x3C]) + sleep(10e-3) + + # Read the config variables by sending 256 empty bytes + for i in range(256): + resp = self.cnxn.xfer([0x00])[0] + config.append(resp) + + # Add the bin bounds to the dictionary of data [bytes 0-29] + for i in range(0, 15): + data["Bin Boundary {0}".format(i)] = self._16bit_unsigned(config[2*i], config[2*i + 1]) + + # Add the Bin Particle Volumes (BPV) [bytes 32-95] + for i in range(0, 16): + data["BPV {0}".format(i)] = self._calculate_float(config[4*i + 32:4*i + 36]) + + # Add the Bin Particle Densities (BPD) [bytes 96-159] + for i in range(0, 16): + data["BPD {0}".format(i)] = self._calculate_float(config[4*i + 96:4*i + 100]) + + # Add the Bin Sample Volume Weight (BSVW) [bytes 160-223] + for i in range(0, 16): + data["BSVW {0}".format(i)] = self._calculate_float(config[4*i + 160: 4*i + 164]) + + # Add the Gain Scaling Coefficient (GSC) and sample flow rate (SFR) + data["GSC"] = self._calculate_float(config[224:228]) + data["SFR"] = self._calculate_float(config[228:232]) + + # Add laser dac (LDAC) and Fan dac (FanDAC) + data["LaserDAC"] = config[232] + data["FanDAC"] = config[233] + + # If past firmware 15, add other things + if self.firmware['major'] > 15.: + data['TOF_SFR'] = config[234] + + sleep(0.1) + + return data + + @requires_firmware(18.) + def config2(self): + """Read the second set of configuration variables and return as a dictionary. + + **NOTE: This method is supported by firmware v18+.** + + :rtype: dictionary + + :Example: + + >>> a.config2() + { + 'AMFanOnIdle': 0, + 'AMIdleIntervalCount': 0, + 'AMMaxDataArraysInFile': 61798, + 'AMSamplingInterval': 1, + 'AMOnlySavePMData': 0, + 'AMLaserOnIdle': 0 + } + """ + config = [] + data = {} + + # Send the command byte and sleep for 10 ms + self.cnxn.xfer([0x3D]) + sleep(10e-3) + + # Read the config variables by sending 256 empty bytes + for i in range(9): + resp = self.cnxn.xfer([0x00])[0] + config.append(resp) + + data["AMSamplingInterval"] = self._16bit_unsigned(config[0], config[1]) + data["AMIdleIntervalCount"] = self._16bit_unsigned(config[2], config[3]) + data['AMFanOnIdle'] = config[4] + data['AMLaserOnIdle'] = config[5] + data['AMMaxDataArraysInFile'] = self._16bit_unsigned(config[6], config[7]) + data['AMOnlySavePMData'] = config[8] + + sleep(0.1) + + return data + + def write_config_variables(self, config_vars): + """ Write configuration variables to non-volatile memory. + + **NOTE: This method is currently a placeholder and is not implemented.** + + :param config_vars: dictionary containing the configuration variables + + :type config_vars: dictionary + """ + logger.warning("This method has not yet been implemented yet.") + + return + + @requires_firmware(18.) + def write_config_variables2(self, config_vars): + """ Write configuration variables 2 to non-volatile memory. + + **NOTE: This method is currently a placeholder and is not implemented.** + **NOTE: This method is supported by firmware v18+.** + + :param config_vars: dictionary containing the configuration variables + + :type config_vars: dictionary + """ + + logger.warning("This method has not yet been implemented.") + + return + + def histogram(self, number_concentration=True): + """Read and reset the histogram. As of v1.3.0, histogram + values are reported in particle number concentration (#/cc) by default. + + :param number_concentration: If true, histogram bins are reported in number concentration vs. raw values. + + :type number_concentration: boolean + + :rtype: dictionary + + :Example: + + >>> alpha.histogram() + { + 'Temperature': None, + 'Pressure': None, + 'Bin 0': 0, + 'Bin 1': 0, + 'Bin 2': 0, + ... + 'Bin 15': 0, + 'SFR': 3.700, + 'Bin1MToF': 0, + 'Bin3MToF': 0, + 'Bin5MToF': 0, + 'Bin7MToF': 0, + 'PM1': 0.0, + 'PM2.5': 0.0, + 'PM10': 0.0, + 'Sampling Period': 2.345, + 'Checksum': 0 + } + """ + resp = [] + data = {} + + # Send the command byte + self.get_ready_response(0x30) + + # Wait 10 ms + sleep(10e-3) + + + sleep(10e-6) # sleep for 10us + # read the histogram + for i in range(64): + r = self.cnxn.xfer([0x00])[0] + resp.append(r) + sleep(10e-6) # sleep for 10us + + # convert to real things and store in dictionary! + data['Bin 0'] = self._16bit_unsigned(resp[0], resp[1]) + data['Bin 1'] = self._16bit_unsigned(resp[2], resp[3]) + data['Bin 2'] = self._16bit_unsigned(resp[4], resp[5]) + data['Bin 3'] = self._16bit_unsigned(resp[6], resp[7]) + data['Bin 4'] = self._16bit_unsigned(resp[8], resp[9]) + data['Bin 5'] = self._16bit_unsigned(resp[10], resp[11]) + data['Bin 6'] = self._16bit_unsigned(resp[12], resp[13]) + data['Bin 7'] = self._16bit_unsigned(resp[14], resp[15]) + data['Bin 8'] = self._16bit_unsigned(resp[16], resp[17]) + data['Bin 9'] = self._16bit_unsigned(resp[18], resp[19]) + data['Bin 10'] = self._16bit_unsigned(resp[20], resp[21]) + data['Bin 11'] = self._16bit_unsigned(resp[22], resp[23]) + data['Bin 12'] = self._16bit_unsigned(resp[24], resp[25]) + data['Bin 13'] = self._16bit_unsigned(resp[26], resp[27]) + data['Bin 14'] = self._16bit_unsigned(resp[28], resp[29]) + data['Bin 15'] = self._16bit_unsigned(resp[30], resp[31]) + data['Bin1 MToF'] = self._calculate_mtof(resp[32]) + data['Bin3 MToF'] = self._calculate_mtof(resp[33]) + data['Bin5 MToF'] = self._calculate_mtof(resp[34]) + data['Bin7 MToF'] = self._calculate_mtof(resp[35]) + + # Bins associated with firmware versions 14 and 15(?) + data['SFR'] = self._calculate_float(resp[36:40]) + data['Temperature'] = self._16bit_unsigned(resp[41], resp[42]) + data['Temperature'] = self._16bit_unsigned(resp[43], resp[44]) + data['Sampling Period'] = self._calculate_float(resp[45:48]) + data['Reject count glitch'] = resp[48] + data['Reject count long'] = resp[49] + data['PM1'] = self._calculate_float(resp[50:53]) + data['PM2.5'] = self._calculate_float(resp[54:57]) + data['PM10'] = self._calculate_float(resp[58:61]) + data['Checksum'] = self._16bit_unsigned(resp[62], resp[63]) + + + # Calculate the sum of the histogram bins + histogram_sum = data['Bin 0'] + data['Bin 1'] + data['Bin 2'] + \ + data['Bin 3'] + data['Bin 4'] + data['Bin 5'] + data['Bin 6'] + \ + data['Bin 7'] + data['Bin 8'] + data['Bin 9'] + data['Bin 10'] + \ + data['Bin 11'] + data['Bin 12'] + data['Bin 13'] + data['Bin 14'] + \ + data['Bin 15'] + + # Check that checksum and the least significant bits of the sum of histogram bins + # are equivilant + if (MODBUS_CalcCRC(data,62) != data['Checksum']: + logger.warning("Data transfer was incomplete") + return None + + # If histogram is true, convert histogram values to number concentration + if number_concentration is True: + _conv_ = data['SFR'] * data['Sampling Period'] # Divider in units of ml (cc) + + data['Bin 0'] = data['Bin 0'] / _conv_ + data['Bin 1'] = data['Bin 1'] / _conv_ + data['Bin 2'] = data['Bin 2'] / _conv_ + data['Bin 3'] = data['Bin 3'] / _conv_ + data['Bin 4'] = data['Bin 4'] / _conv_ + data['Bin 5'] = data['Bin 5'] / _conv_ + data['Bin 6'] = data['Bin 6'] / _conv_ + data['Bin 7'] = data['Bin 7'] / _conv_ + data['Bin 8'] = data['Bin 8'] / _conv_ + data['Bin 9'] = data['Bin 9'] / _conv_ + data['Bin 10'] = data['Bin 10'] / _conv_ + data['Bin 11'] = data['Bin 11'] / _conv_ + data['Bin 12'] = data['Bin 12'] / _conv_ + data['Bin 13'] = data['Bin 13'] / _conv_ + data['Bin 14'] = data['Bin 14'] / _conv_ + data['Bin 15'] = data['Bin 15'] / _conv_ + + sleep(0.1) + + return data + + def MODBUS_CalcCRC(self, data, nbrOfBytes): + + + POLYNOMIAL_MODBUS = 0xA001 #Generator polynomial for MODBUS crc + InitCRCval_MODBUS = 0xFFFF #Initial CRC value + + crc = InitCRCval_MODBUS + byteCtr = 0 + while(byteCtr>=1 + crc ^= POLYNOMIAL_MODBUS + else: + crc>>=1 + return crc + + def save_config_variables(self): + """Save the configuration variables in non-volatile memory. This method + should be used in conjuction with *write_config_variables*. + + :rtype: boolean + + :Example: + + >>> alpha.save_config_variables() + True + """ + command = 0x43 + byte_list = [0x3F, 0x3C, 0x3F, 0x3C, 0x43] + success = [0xF3, 0x43, 0x3F, 0x3C, 0x3F, 0x3C] + resp = [] + + # Send the command byte and then wait for 10 ms + r = self.cnxn.xfer([command])[0] + sleep(10e-3) + + # append the response of the command byte to the List + resp.append(r) + + # Send the rest of the config bytes + for each in byte_list: + r = self.cnxn.xfer([each])[0] + resp.append(r) + + sleep(0.1) + + return True if resp == success else False + + def _enter_bootloader_mode(self): + """Enter bootloader mode. Must be issued prior to writing + configuration variables to non-volatile memory. + + :rtype: boolean + + :Example: + + >>> alpha._enter_bootloader_mode() + True + """ + + return True if self.cnxn.xfer(0x41)[0] == 0xF3 else False + + def set_fan_power(self, power): + """Set only the Fan power. + + :param power: Fan power value as an integer between 0-255. + + :type power: int + + :rtype: boolean + + :Example: + + >>> alpha.set_fan_power(255) + True + """ + # Check to make sure the value is a single byte + if power > 255: + raise ValueError("The fan power should be a single byte (0-255).") + + # Send the command byte and wait 10 ms + a = self.cnxn.xfer([0x42])[0] + sleep(10e-3) + + # Send the next two bytes + b = self.cnxn.xfer([0x00])[0] + c = self.cnxn.xfer([power])[0] + + sleep(0.1) + + return True if a == 0xF3 and b == 0x42 and c == 0x00 else False + + def set_laser_power(self, power): + """Set the laser power only. + + :param power: Laser power as a value between 0-255. + + :type power: int + + :rtype: boolean + + :Example: + + >>> alpha.set_laser_power(230) + True + """ + + # Check to make sure the value is a single byte + if power > 255: + raise ValueError("Laser Power should be a single byte (0-255).") + + # Send the command byte and wait 10 ms + a = self.cnxn.xfer([0x42])[0] + sleep(10e-3) + + # Send the next two bytes + b = self.cnxn.xfer([0x01])[0] + c = self.cnxn.xfer([power])[0] + + sleep(0.1) + + return True if a == 0xF3 and b == 0x42 and c == 0x01 else False + + def toggle_laser(self, state): + """Toggle the power state of the laser. + + :param state: Boolean state of the laser + + :type state: boolean + + :rtype: boolean + + :Example: + + >>> alpha.toggle_laser(True) + True + """ + + # Send the command byte and wait 10 ms + a = self.cnxn.xfer([0x03])[0] + + sleep(10e-3) + + # If state is true, turn the laser ON, else OFF + if state: + b = self.cnxn.xfer([0x02])[0] + else: + b = self.cnxn.xfer([0x03])[0] + + sleep(0.1) + + return True if a == 0xF3 and b == 0x03 else False + + def toggle_fan(self, state): + """Toggle the power state of the fan. + + :param state: Boolean state of the fan + + :type state: boolean + + :rtype: boolean + + :Example: + + >>> alpha.toggle_fan(False) + True + """ + + # Send the command byte and wait 10 ms + a = self.cnxn.xfer([0x03])[0] + + sleep(10e-3) + + # If state is true, turn the fan ON, else OFF + if state: + b = self.cnxn.xfer([0x04])[0] + else: + b = self.cnxn.xfer([0x05])[0] + + sleep(0.1) + + return True if a == 0xF3 and b == 0x03 else False + + @requires_firmware(18.) + def read_pot_status(self): + """Read the status of the digital pot. Firmware v18+ only. + The return value is a dictionary containing the following as + unsigned 8-bit integers: FanON, LaserON, FanDACVal, LaserDACVal. + + :rtype: dict + + :Example: + + >>> alpha.read_pot_status() + { + 'LaserDACVal': 230, + 'FanDACVal': 255, + 'FanON': 0, + 'LaserON': 0 + } + """ + # Send the command byte and wait 10 ms + a = self.cnxn.xfer([0x13])[0] + + sleep(10e-3) + + # Build an array of the results + res = [] + for i in range(4): + res.append(self.cnxn.xfer([0x00])[0]) + + sleep(0.1) + + return { + 'FanON': res[0], + 'LaserON': res[1], + 'FanDACVal': res[2], + 'LaserDACVal': res[3] + } + + @requires_firmware(18.) + def sn(self): + """Read the Serial Number string. This method is only available on OPC-N2 + firmware versions 18+. + + :rtype: string + + :Example: + + >>> alpha.sn() + 'OPC-N2 123456789' + """ + string = [] + + # Send the command byte and sleep for 9 ms + self.cnxn.xfer([0x10]) + sleep(9e-3) + + # Read the info string by sending 60 empty bytes + for i in range(60): + resp = self.cnxn.xfer([0x00])[0] + string.append(chr(resp)) + + sleep(0.1) + + return ''.join(string) + + @requires_firmware(18.) + def write_sn(self): + """Write the Serial Number string. This method is available for Firmware versions 18+. + + **NOTE: This method is currently a placeholder and is not implemented.** + + :param sn: string containing the serial number to write + + :type sn: string + """ + + return + + @requires_firmware(18.) + def read_firmware(self): + """Read the firmware version of the OPC-N2. Firmware v18+ only. + + :rtype: dict + + :Example: + + >>> alpha.read_firmware() + { + 'major': 18, + 'minor': 2, + 'version': 18.2 + } + """ + # Send the command byte and sleep for 9 ms + self.cnxn.xfer([0x12]) + sleep(10e-3) + + self.firmware['major'] = self.cnxn.xfer([0x00])[0] + self.firmware['minor'] = self.cnxn.xfer([0x00])[0] + + # Build the firmware version + self.firmware['version'] = float('{}.{}'.format(self.firmware['major'], self.firmware['minor'])) + + sleep(0.1) + + return self.firmware + + @requires_firmware(18.) + def pm(self): + """Read the PM data and reset the histogram + + **NOTE: This method is supported by firmware v18+.** + + :rtype: dictionary + + :Example: + + >>> alpha.pm() + { + 'PM1': 0.12, + 'PM2.5': 0.24, + 'PM10': 1.42 + } + """ + + resp = [] + data = {} + + # Send the command byte + self.cnxn.xfer([0x32]) + + # Wait 10 ms + sleep(10e-3) + + # read the histogram + for i in range(12): + r = self.cnxn.xfer([0x00])[0] + resp.append(r) + + # convert to real things and store in dictionary! + data['PM1'] = self._calculate_float(resp[0:4]) + data['PM2.5'] = self._calculate_float(resp[4:8]) + data['PM10'] = self._calculate_float(resp[8:]) + + sleep(0.1) + + return data \ No newline at end of file From 5f220e00317d8c2fdc5ae426e6633e637fea808c Mon Sep 17 00:00:00 2001 From: FlorentinBulotUoS Date: Thu, 21 Feb 2019 10:35:27 +0000 Subject: [PATCH 02/19] Histogram, on and off --- opc/__init__.py | 40 ++++++++++++++++++++-------------------- 1 file changed, 20 insertions(+), 20 deletions(-) diff --git a/opc/__init__.py b/opc/__init__.py index 3fa76c6..ccb0ad4 100644 --- a/opc/__init__.py +++ b/opc/__init__.py @@ -16,7 +16,7 @@ __version__ = "1.6.0" -__all__ = ['OPCN2', 'OPCN1'] +__all__ = ['OPCN2', 'OPCN1','OPCR1'] class _OPC(object): """Generic class for any Alphasense OPC. Provides the common methods and calculations for each OPC. This class is designed to be the base class, and should not be used alone unless during development. @@ -1130,28 +1130,28 @@ def __init__(self, spi_connection, **kwargs): SPI_OPC_READY = 0xF3 SPI_OPC_BUSY = 0x03 - if self.firmware['major'] < firmware_min or self.firmware['major'] > firmware_max: - logger.error("Firmware version is invalid for this device.") - - raise FirmwareVersionError("Your firmware is not yet supported. Only version 2 is currently supported.") + #if self.firmware['major'] < firmware_min or self.firmware['major'] > firmware_max: + # logger.error("Firmware version is invalid for this device.") + # raise FirmwareVersionError("Your firmware is not yet supported. Only version 2 is currently supported.") def get_ready_response(self, SPIcommand): - message = self.cnxn.xfer(SPIcommand)[0] + SPI_OPC_READY = 0xF3 + SPI_OPC_BUSY = 0x03 + message = self.cnxn.xfer([SPIcommand])[0] sleep(1e-3) attempt = 0 - while(attempt<20 & message != SPI_OPC_READY): attempt+=1 if(message != SPI_OPC_READY): - message = self.cnxn.xfer(SPIcommand) + message = self.cnxn.xfer([SPIcommand])[0] if(message != SPI_OPC_READY): sleep(1e-3) # Wait 1ms before retrying if(message != SPI_OPC_READY): if(message == SPI_OPC_BUSY): self.spi_connection.flush() - sleep(2s) + sleep(2) raise Exception("ERROR Waiting 2s (for OPC comms timeout)") else: self.spi_connection.flush() @@ -1173,10 +1173,10 @@ def on(self): """ self.get_ready_response(0x03) # send the command byte sleep(10e-3) # sleep for 10 ms - b2 = self.cnxn.xfer([0x03])[0] # send the following byte powers the laser and fan + b2 = self.cnxn.xfer([0x03,0x03])[0] # send the following byte powers the laser and fan sleep(10) # sleep while it is properly powered on - return True if b2 == SPI_OPC_BUSY else False + return True if b2 == 0x03 else False def off(self): """Turn OFF the OPC (fan and laser) @@ -1193,7 +1193,7 @@ def off(self): b2 = self.cnxn.xfer([0x00]) # send the following byte to turn off laser and fan sleep(0.1) - return True if b2 == SPI_OPC_BUSY else False + return True if b2 == 0x03 else False def config(self): """Read the configuration variables and returns them as a dictionary @@ -1404,14 +1404,14 @@ def histogram(self, number_concentration=True): # Bins associated with firmware versions 14 and 15(?) data['SFR'] = self._calculate_float(resp[36:40]) - data['Temperature'] = self._16bit_unsigned(resp[41], resp[42]) - data['Temperature'] = self._16bit_unsigned(resp[43], resp[44]) - data['Sampling Period'] = self._calculate_float(resp[45:48]) + data['Temperature'] = self._16bit_unsigned(resp[40], resp[41]) + data['Humidity'] = self._16bit_unsigned(resp[42], resp[43]) + data['Sampling Period'] = self._calculate_float(resp[44:48]) data['Reject count glitch'] = resp[48] data['Reject count long'] = resp[49] - data['PM1'] = self._calculate_float(resp[50:53]) - data['PM2.5'] = self._calculate_float(resp[54:57]) - data['PM10'] = self._calculate_float(resp[58:61]) + data['PM1'] = self._calculate_float(resp[50:54]) + data['PM2.5'] = self._calculate_float(resp[54:58]) + data['PM10'] = self._calculate_float(resp[58:62]) data['Checksum'] = self._16bit_unsigned(resp[62], resp[63]) @@ -1424,7 +1424,7 @@ def histogram(self, number_concentration=True): # Check that checksum and the least significant bits of the sum of histogram bins # are equivilant - if (MODBUS_CalcCRC(data,62) != data['Checksum']: + if (self.MODBUS_CalcCRC(resp,62) != data['Checksum']): logger.warning("Data transfer was incomplete") return None @@ -1784,4 +1784,4 @@ def pm(self): sleep(0.1) - return data \ No newline at end of file + return data From 87af9a7e07e1cec579788c2b82b0ea6780babe47 Mon Sep 17 00:00:00 2001 From: Florentin Bulot <32537171+FlorentinBulotUoS@users.noreply.github.com> Date: Thu, 21 Feb 2019 13:58:02 +0000 Subject: [PATCH 03/19] Convert RH and Temperature --- opc/__init__.py | 21 +++++++++++++++++---- 1 file changed, 17 insertions(+), 4 deletions(-) diff --git a/opc/__init__.py b/opc/__init__.py index ccb0ad4..bdb6013 100644 --- a/opc/__init__.py +++ b/opc/__init__.py @@ -1404,8 +1404,8 @@ def histogram(self, number_concentration=True): # Bins associated with firmware versions 14 and 15(?) data['SFR'] = self._calculate_float(resp[36:40]) - data['Temperature'] = self._16bit_unsigned(resp[40], resp[41]) - data['Humidity'] = self._16bit_unsigned(resp[42], resp[43]) + data['Temperature'] = self_ConvSTtoTemperature(self._16bit_unsigned(resp[40], resp[41])) + data['Humidity'] = self._ConvSRHtoRelativeHumidity(self._16bit_unsigned(resp[42], resp[43])) data['Sampling Period'] = self._calculate_float(resp[44:48]) data['Reject count glitch'] = resp[48] data['Reject count long'] = resp[49] @@ -1424,7 +1424,7 @@ def histogram(self, number_concentration=True): # Check that checksum and the least significant bits of the sum of histogram bins # are equivilant - if (self.MODBUS_CalcCRC(resp,62) != data['Checksum']): + if (self._MODBUS_CalcCRC(resp,62) != data['Checksum']): logger.warning("Data transfer was incomplete") return None @@ -1453,7 +1453,20 @@ def histogram(self, number_concentration=True): return data - def MODBUS_CalcCRC(self, data, nbrOfBytes): + def _ConvSTtoTemperature(self, ST): + #Convert SHT31 ST output to Temperature (C) + + return -45 + 175*ST/65535 + + + + + def _ConvSRHtoRelativeHumidity(self, SRH) + #Convert SHT31 SRH output to Relative Humidity (%): + return 100*SRH/65535 + + + def _MODBUS_CalcCRC(self, data, nbrOfBytes): POLYNOMIAL_MODBUS = 0xA001 #Generator polynomial for MODBUS crc From 40ba3ec72912aacfafceb8511d21b6fe6b0b1845 Mon Sep 17 00:00:00 2001 From: FlorentinBulotUoS Date: Thu, 21 Feb 2019 14:06:38 +0000 Subject: [PATCH 04/19] Temperature and humidity factors --- opc/__init__.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/opc/__init__.py b/opc/__init__.py index bdb6013..c66bd9f 100644 --- a/opc/__init__.py +++ b/opc/__init__.py @@ -1404,7 +1404,7 @@ def histogram(self, number_concentration=True): # Bins associated with firmware versions 14 and 15(?) data['SFR'] = self._calculate_float(resp[36:40]) - data['Temperature'] = self_ConvSTtoTemperature(self._16bit_unsigned(resp[40], resp[41])) + data['Temperature'] = self._ConvSTtoTemperature(self._16bit_unsigned(resp[40], resp[41])) data['Humidity'] = self._ConvSRHtoRelativeHumidity(self._16bit_unsigned(resp[42], resp[43])) data['Sampling Period'] = self._calculate_float(resp[44:48]) data['Reject count glitch'] = resp[48] @@ -1461,7 +1461,7 @@ def _ConvSTtoTemperature(self, ST): - def _ConvSRHtoRelativeHumidity(self, SRH) + def _ConvSRHtoRelativeHumidity(self, SRH): #Convert SHT31 SRH output to Relative Humidity (%): return 100*SRH/65535 From 4aa674f53bfaf951ce881fdb6595d24e08dcde04 Mon Sep 17 00:00:00 2001 From: Florentin Bulot <32537171+FlorentinBulotUoS@users.noreply.github.com> Date: Thu, 21 Feb 2019 15:02:41 +0000 Subject: [PATCH 05/19] implement missing funcitons --- opc/__init__.py | 632 +++++++++++++++++++++--------------------------- 1 file changed, 273 insertions(+), 359 deletions(-) diff --git a/opc/__init__.py b/opc/__init__.py index bdb6013..5d16d80 100644 --- a/opc/__init__.py +++ b/opc/__init__.py @@ -1106,7 +1106,7 @@ def read_histogram(self): class OPCR1(_OPC): """Create an instance of the Alphasene OPC-R1. Currently supported by firmware - versions 14-18. opc.OPCR1 inherits from the opc.OPC parent class. + versions 2.10. opc.OPCR1 inherits from the opc.OPC parent class. :param spi_connection: The spidev instance for the SPI connection. @@ -1121,6 +1121,7 @@ class OPCR1(_OPC): >>> alpha = opc.OPCR1(spi) >>> alpha Alphasense OPC-R1v2.10 + """ def __init__(self, spi_connection, **kwargs): super(OPCR1, self).__init__(spi_connection, model='R1', **kwargs) @@ -1173,7 +1174,7 @@ def on(self): """ self.get_ready_response(0x03) # send the command byte sleep(10e-3) # sleep for 10 ms - b2 = self.cnxn.xfer([0x03,0x03])[0] # send the following byte powers the laser and fan + b2 = self.cnxn.xfer([0x03])[0] # send the following byte powers the laser and fan sleep(10) # sleep while it is properly powered on return True if b2 == 0x03 else False @@ -1190,143 +1191,223 @@ def off(self): """ self.get_ready_response(0x03) # send the command byte sleep(10e-3) # sleep for 10 ms - b2 = self.cnxn.xfer([0x00]) # send the following byte to turn off laser and fan + b2 = self.cnxn.xfer([0x00])[0] # send the following byte to turn off laser and fan sleep(0.1) return True if b2 == 0x03 else False - def config(self): - """Read the configuration variables and returns them as a dictionary + def set_laser_power(self, power): + """Set the laser power only. - :rtype: dictionary + :param power: Laser power as a value between 0-255. + + :type power: int + + :rtype: boolean :Example: - >>> alpha.config() - { - 'BPD 13': 1.6499, - 'BPD 12': 1.6499, - 'BPD 11': 1.6499, - 'BPD 10': 1.6499, - 'BPD 15': 1.6499, - 'BPD 14': 1.6499, - 'BSVW 15': 1.0, - ... - } + >>> alpha.set_laser_power(230) + True """ - config = [] - data = {} - # Send the command byte and sleep for 10 ms - self.cnxn.xfer([0x3C]) + # Check to make sure the value is a single byte + if power > 255: + raise ValueError("Laser Power should be a single byte (0-255).") + + # Send the command byte + self.get_ready_response(0x04) + + # Wait 10 ms sleep(10e-3) - # Read the config variables by sending 256 empty bytes - for i in range(256): - resp = self.cnxn.xfer([0x00])[0] - config.append(resp) + b = self.cnxn.xfer([power])[0] - # Add the bin bounds to the dictionary of data [bytes 0-29] - for i in range(0, 15): - data["Bin Boundary {0}".format(i)] = self._16bit_unsigned(config[2*i], config[2*i + 1]) + sleep(0.1) - # Add the Bin Particle Volumes (BPV) [bytes 32-95] - for i in range(0, 16): - data["BPV {0}".format(i)] = self._calculate_float(config[4*i + 32:4*i + 36]) + return True if b == 0x04 else False - # Add the Bin Particle Densities (BPD) [bytes 96-159] - for i in range(0, 16): - data["BPD {0}".format(i)] = self._calculate_float(config[4*i + 96:4*i + 100]) - # Add the Bin Sample Volume Weight (BSVW) [bytes 160-223] - for i in range(0, 16): - data["BSVW {0}".format(i)] = self._calculate_float(config[4*i + 160: 4*i + 164]) + def set_bin_weighting_index(self, bin_weighting_index): + """ + """ - # Add the Gain Scaling Coefficient (GSC) and sample flow rate (SFR) - data["GSC"] = self._calculate_float(config[224:228]) - data["SFR"] = self._calculate_float(config[228:232]) + if 0 < bin_weighting_index or bin_weighting_index > 10: + raise ValueError("Bin weigthing index is an integer between 0 and 10.") + if not(bin_weighting_index.is_integer()): + raise ValueError("Bin weigthing index is an integer between 0 and 10.") - # Add laser dac (LDAC) and Fan dac (FanDAC) - data["LaserDAC"] = config[232] - data["FanDAC"] = config[233] + # Send the command byte + self.get_ready_response(0x05) - # If past firmware 15, add other things - if self.firmware['major'] > 15.: - data['TOF_SFR'] = config[234] + # Wait 10 ms + sleep(10e-3) - sleep(0.1) + b = self.cnxn.xfer([bin_weighting_index])[0] - return data + sleep(0.1) - @requires_firmware(18.) - def config2(self): - """Read the second set of configuration variables and return as a dictionary. + return True if b == 0x05 else False - **NOTE: This method is supported by firmware v18+.** + def read_info_string(self): + """Reads the information string for the OPC - :rtype: dictionary + :rtype: string :Example: - >>> a.config2() - { - 'AMFanOnIdle': 0, - 'AMIdleIntervalCount': 0, - 'AMMaxDataArraysInFile': 61798, - 'AMSamplingInterval': 1, - 'AMOnlySavePMData': 0, - 'AMLaserOnIdle': 0 - } + >>> alpha.read_info_string() + 'OPC-N2 FirmwareVer=OPC-018.2....................BD' """ - config = [] - data = {} + infostring = [] - # Send the command byte and sleep for 10 ms - self.cnxn.xfer([0x3D]) + # Send the command byte + self.get_ready_response(0x3F) + + # Wait 10 ms sleep(10e-3) - # Read the config variables by sending 256 empty bytes - for i in range(9): + # Read the info string by sending 60 empty bytes + for i in range(60): resp = self.cnxn.xfer([0x00])[0] - config.append(resp) + infostring.append(chr(resp)) - data["AMSamplingInterval"] = self._16bit_unsigned(config[0], config[1]) - data["AMIdleIntervalCount"] = self._16bit_unsigned(config[2], config[3]) - data['AMFanOnIdle'] = config[4] - data['AMLaserOnIdle'] = config[5] - data['AMMaxDataArraysInFile'] = self._16bit_unsigned(config[6], config[7]) - data['AMOnlySavePMData'] = config[8] + sleep(0.1) + + return ''.join(infostring) + + def sn(self): + """Read the Serial Number string. This method is only available on OPC-N2 + firmware versions 18+. + + :rtype: string + + :Example: + + >>> alpha.sn() + 'OPC-N2 123456789' + """ + string = [] + + # Send the command byte + self.get_ready_response(0x10) + + # Wait 10 ms + sleep(10e-3) + + # Read the info string by sending 60 empty bytes + for i in range(60): + resp = self.cnxn.xfer([0x00])[0] + string.append(chr(resp)) sleep(0.1) - return data + return ''.join(string) - def write_config_variables(self, config_vars): - """ Write configuration variables to non-volatile memory. + + def write_sn(self): + """Write the Serial Number string. **NOTE: This method is currently a placeholder and is not implemented.** - :param config_vars: dictionary containing the configuration variables + :param sn: string containing the serial number to write - :type config_vars: dictionary + :type sn: string """ - logger.warning("This method has not yet been implemented yet.") return - @requires_firmware(18.) - def write_config_variables2(self, config_vars): - """ Write configuration variables 2 to non-volatile memory. + def read_firmware(self): + """Read the firmware version of the OPC-N2. Firmware v18+ only. + + :rtype: dict + + :Example: + + >>> alpha.read_firmware() + { + 'major': 18, + 'minor': 2, + 'version': 18.2 + } + """ + # Send the command byte + self.get_ready_response(0x12) + + # Wait 10 ms + sleep(10e-3) + + self.firmware['major'] = self.cnxn.xfer([0x12])[0] + self.firmware['minor'] = self.cnxn.xfer([0x12])[0] + + # Build the firmware version + self.firmware['version'] = float('{}.{}'.format(self.firmware['major'], self.firmware['minor'])) + + sleep(0.1) + + return self.firmware + + def read_config(self): + """Read the configuration variables and return as a dictionary. + + **NOTE: This method is supported by firmware v18+.** + + :rtype: dictionary + + :Example: + + + """ + config = [] + data = {} + + # Send the command byte + self.get_ready_response(0x12) + + # Wait 10 ms + sleep(10e-3) + + # Read the config variables by sending 256 empty bytes + for i in range(256): + resp = self.cnxn.xfer([0x00])[0] + config.append(resp) + + # Add the bin bounds to the dictionary of data [bytes 0-33] + for i in range(0, 16): + data["Bin Boundary {0}".format(i)] = self._16bit_unsigned(config[2*i], config[2*i + 1]) + + # Add the Bin Boundaries diameter (BDD) [bytes 34-98] + for i in range(0, 16): + data["BBD {0}".format(i)] = self._calculate_float(config[4*i + 33:4*i + 37]) + + # Add the Bin Weighting (BW) [bytes 99-163] + for i in range(0, 15): + data["BW {0}".format(i)] = self._calculate_float(config[4*i + 99:4*i + 103]) + + + # Add the Gain Scaling Coefficient (GSC) and sample flow rate (SFR) + data["GSC"] = self._calculate_float(config[122:126]) + data["SFR"] = self._calculate_float(config[126:130]) + + # Add laser dac (LDAC) and Fan dac (FanDAC) + data["TOF_SFR"] = config[131] + + data["M_A"] = self._16bit_unsigned(config[131:135]) + + sleep(0.1) + + return data + + def write_config_variables(self, config_vars): + """ Write configuration variables to non-volatile memory. **NOTE: This method is currently a placeholder and is not implemented.** - **NOTE: This method is supported by firmware v18+.** :param config_vars: dictionary containing the configuration variables :type config_vars: dictionary """ - - logger.warning("This method has not yet been implemented.") + logger.warning("This method has not yet been implemented yet.") return @@ -1372,8 +1453,6 @@ def histogram(self, number_concentration=True): # Wait 10 ms sleep(10e-3) - - sleep(10e-6) # sleep for 10us # read the histogram for i in range(64): r = self.cnxn.xfer([0x00])[0] @@ -1414,14 +1493,6 @@ def histogram(self, number_concentration=True): data['PM10'] = self._calculate_float(resp[58:62]) data['Checksum'] = self._16bit_unsigned(resp[62], resp[63]) - - # Calculate the sum of the histogram bins - histogram_sum = data['Bin 0'] + data['Bin 1'] + data['Bin 2'] + \ - data['Bin 3'] + data['Bin 4'] + data['Bin 5'] + data['Bin 6'] + \ - data['Bin 7'] + data['Bin 8'] + data['Bin 9'] + data['Bin 10'] + \ - data['Bin 11'] + data['Bin 12'] + data['Bin 13'] + data['Bin 14'] + \ - data['Bin 15'] - # Check that checksum and the least significant bits of the sum of histogram bins # are equivilant if (self._MODBUS_CalcCRC(resp,62) != data['Checksum']): @@ -1453,6 +1524,52 @@ def histogram(self, number_concentration=True): return data + def pm(self): + """Read the PM data and reset the histogram + + :rtype: dictionary + + :Example: + + >>> alpha.pm() + { + 'PM1': 0.12, + 'PM2.5': 0.24, + 'PM10': 1.42 + } + """ + + resp = [] + data = {} + + # Send the command byte + self.get_ready_response(0x32) + + # Wait 10 ms + sleep(10e-3) + + # read the histogram + for i in range(14): + r = self.cnxn.xfer([0x00])[0] + resp.append(r) + + # convert to real things and store in dictionary! + data['PM1'] = self._calculate_float(resp[0:4]) + data['PM2.5'] = self._calculate_float(resp[4:8]) + data['PM10'] = self._calculate_float(resp[8:12]) + + data['Checksum'] = self._16bit_unsigned(resp[12], resp[13]) + + # Check that checksum and the least significant bits of the sum of histogram bins + # are equivilant + if (self._MODBUS_CalcCRC(resp,14) != data['Checksum']): + logger.warning("Data transfer was incomplete") + return None + + sleep(0.1) + + return data + def _ConvSTtoTemperature(self, ST): #Convert SHT31 ST output to Temperature (C) @@ -1485,316 +1602,113 @@ def _MODBUS_CalcCRC(self, data, nbrOfBytes): crc>>=1 return crc - def save_config_variables(self): - """Save the configuration variables in non-volatile memory. This method - should be used in conjuction with *write_config_variables*. - :rtype: boolean + + + + def pm(self): + """Read the PM data and reset the histogram + + :rtype: dictionary :Example: - >>> alpha.save_config_variables() - True + >>> alpha.pm() + { + 'PM1': 0.12, + 'PM2.5': 0.24, + 'PM10': 1.42 + } """ - command = 0x43 - byte_list = [0x3F, 0x3C, 0x3F, 0x3C, 0x43] - success = [0xF3, 0x43, 0x3F, 0x3C, 0x3F, 0x3C] + resp = [] + data = {} - # Send the command byte and then wait for 10 ms - r = self.cnxn.xfer([command])[0] - sleep(10e-3) + # Send the command byte + self.get_ready_response(0x11) - # append the response of the command byte to the List - resp.append(r) + # Wait 10 ms + sleep(10e-3) - # Send the rest of the config bytes - for each in byte_list: - r = self.cnxn.xfer([each])[0] + # read the histogram + for i in range(12): + r = self.cnxn.xfer([0x00])[0] resp.append(r) - sleep(0.1) - - return True if resp == success else False - - def _enter_bootloader_mode(self): - """Enter bootloader mode. Must be issued prior to writing - configuration variables to non-volatile memory. - - :rtype: boolean - - :Example: - - >>> alpha._enter_bootloader_mode() - True - """ - - return True if self.cnxn.xfer(0x41)[0] == 0xF3 else False - - def set_fan_power(self, power): - """Set only the Fan power. - - :param power: Fan power value as an integer between 0-255. - - :type power: int - - :rtype: boolean - - :Example: - - >>> alpha.set_fan_power(255) - True - """ - # Check to make sure the value is a single byte - if power > 255: - raise ValueError("The fan power should be a single byte (0-255).") - - # Send the command byte and wait 10 ms - a = self.cnxn.xfer([0x42])[0] - sleep(10e-3) - - # Send the next two bytes - b = self.cnxn.xfer([0x00])[0] - c = self.cnxn.xfer([power])[0] + # convert to real things and store in dictionary! + data['PM1'] = self._calculate_float(resp[0:4]) + data['PM2.5'] = self._calculate_float(resp[4:8]) + data['PM10'] = self._calculate_float(resp[8:]) sleep(0.1) - return True if a == 0xF3 and b == 0x42 and c == 0x00 else False - - def set_laser_power(self, power): - """Set the laser power only. - - :param power: Laser power as a value between 0-255. + return data - :type power: int + + def save_config_variables(self): + """Save the configuration variables in non-volatile memory. This method + should be used in conjuction with *write_config_variables*. :rtype: boolean :Example: - >>> alpha.set_laser_power(230) + >>> alpha.save_config_variables() True """ + command = 0x43 + byte_list = [0x3C, 0x3F, 0x3C, 0x43] + success = [ 0x43, 0x3F, 0x3C, 0x3F, 0x3C] + resp = [] - # Check to make sure the value is a single byte - if power > 255: - raise ValueError("Laser Power should be a single byte (0-255).") + # Send the command byte + self.get_ready_response(0x11) - # Send the command byte and wait 10 ms - a = self.cnxn.xfer([0x42])[0] + # Wait 10 ms sleep(10e-3) - # Send the next two bytes - b = self.cnxn.xfer([0x01])[0] - c = self.cnxn.xfer([power])[0] - - sleep(0.1) - - return True if a == 0xF3 and b == 0x42 and c == 0x01 else False - - def toggle_laser(self, state): - """Toggle the power state of the laser. - - :param state: Boolean state of the laser - - :type state: boolean - - :rtype: boolean - - :Example: - - >>> alpha.toggle_laser(True) - True - """ - - # Send the command byte and wait 10 ms - a = self.cnxn.xfer([0x03])[0] - - sleep(10e-3) + # append the response of the command byte to the List + resp.append(r) - # If state is true, turn the laser ON, else OFF - if state: - b = self.cnxn.xfer([0x02])[0] - else: - b = self.cnxn.xfer([0x03])[0] + # Send the rest of the config bytes + for each in byte_list: + r = self.cnxn.xfer([each])[0] + resp.append(r) sleep(0.1) - return True if a == 0xF3 and b == 0x03 else False - - def toggle_fan(self, state): - """Toggle the power state of the fan. - - :param state: Boolean state of the fan + return True if resp == success else False - :type state: boolean + def _enter_bootloader_mode(self): + """Enter bootloader mode. Must be issued prior to writing + configuration variables to non-volatile memory. :rtype: boolean :Example: - >>> alpha.toggle_fan(False) + >>> alpha._enter_bootloader_mode() True """ - # Send the command byte and wait 10 ms - a = self.cnxn.xfer([0x03])[0] - - sleep(10e-3) - - # If state is true, turn the fan ON, else OFF - if state: - b = self.cnxn.xfer([0x04])[0] - else: - b = self.cnxn.xfer([0x05])[0] - - sleep(0.1) - - return True if a == 0xF3 and b == 0x03 else False - - @requires_firmware(18.) - def read_pot_status(self): - """Read the status of the digital pot. Firmware v18+ only. - The return value is a dictionary containing the following as - unsigned 8-bit integers: FanON, LaserON, FanDACVal, LaserDACVal. - - :rtype: dict - - :Example: - - >>> alpha.read_pot_status() - { - 'LaserDACVal': 230, - 'FanDACVal': 255, - 'FanON': 0, - 'LaserON': 0 - } - """ - # Send the command byte and wait 10 ms - a = self.cnxn.xfer([0x13])[0] - - sleep(10e-3) - - # Build an array of the results - res = [] - for i in range(4): - res.append(self.cnxn.xfer([0x00])[0]) - - sleep(0.1) - - return { - 'FanON': res[0], - 'LaserON': res[1], - 'FanDACVal': res[2], - 'LaserDACVal': res[3] - } - - @requires_firmware(18.) - def sn(self): - """Read the Serial Number string. This method is only available on OPC-N2 - firmware versions 18+. - - :rtype: string - - :Example: - - >>> alpha.sn() - 'OPC-N2 123456789' - """ - string = [] - - # Send the command byte and sleep for 9 ms - self.cnxn.xfer([0x10]) - sleep(9e-3) - - # Read the info string by sending 60 empty bytes - for i in range(60): - resp = self.cnxn.xfer([0x00])[0] - string.append(chr(resp)) - - sleep(0.1) - - return ''.join(string) - - @requires_firmware(18.) - def write_sn(self): - """Write the Serial Number string. This method is available for Firmware versions 18+. - - **NOTE: This method is currently a placeholder and is not implemented.** - - :param sn: string containing the serial number to write - - :type sn: string - """ - - return - - @requires_firmware(18.) - def read_firmware(self): - """Read the firmware version of the OPC-N2. Firmware v18+ only. + return True if self.cnxn.xfer(0x41)[0] == 0xF3 else False - :rtype: dict + def check_status(self): - :Example: + # Send the command byte + self.get_ready_response(0xCF) - >>> alpha.read_firmware() - { - 'major': 18, - 'minor': 2, - 'version': 18.2 - } - """ - # Send the command byte and sleep for 9 ms - self.cnxn.xfer([0x12]) + # Wait 10 ms sleep(10e-3) - self.firmware['major'] = self.cnxn.xfer([0x00])[0] - self.firmware['minor'] = self.cnxn.xfer([0x00])[0] - - # Build the firmware version - self.firmware['version'] = float('{}.{}'.format(self.firmware['major'], self.firmware['minor'])) - - sleep(0.1) - - return self.firmware - - @requires_firmware(18.) - def pm(self): - """Read the PM data and reset the histogram + return True - **NOTE: This method is supported by firmware v18+.** - - :rtype: dictionary - - :Example: - - >>> alpha.pm() - { - 'PM1': 0.12, - 'PM2.5': 0.24, - 'PM10': 1.42 - } - """ - - resp = [] - data = {} + def reset(self): # Send the command byte - self.cnxn.xfer([0x32]) + self.get_ready_response(0x06) # Wait 10 ms sleep(10e-3) - # read the histogram - for i in range(12): - r = self.cnxn.xfer([0x00])[0] - resp.append(r) - - # convert to real things and store in dictionary! - data['PM1'] = self._calculate_float(resp[0:4]) - data['PM2.5'] = self._calculate_float(resp[4:8]) - data['PM10'] = self._calculate_float(resp[8:]) - - sleep(0.1) - - return data + return True \ No newline at end of file From edd78addce9ca827342b241e570881319e0cb7db Mon Sep 17 00:00:00 2001 From: Florentin Bulot <32537171+FlorentinBulotUoS@users.noreply.github.com> Date: Fri, 22 Feb 2019 07:47:29 +0000 Subject: [PATCH 06/19] indentation --- opc/__init__.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/opc/__init__.py b/opc/__init__.py index 84fd450..9ddf48a 100644 --- a/opc/__init__.py +++ b/opc/__init__.py @@ -1645,7 +1645,7 @@ def pm(self): return data - def save_config_variables(self): + def save_config_variables(self): """Save the configuration variables in non-volatile memory. This method should be used in conjuction with *write_config_variables*. From 6fce55b28077ef56c07e905adbd38dfd644e6019 Mon Sep 17 00:00:00 2001 From: Florentin Bulot <32537171+FlorentinBulotUoS@users.noreply.github.com> Date: Wed, 6 Mar 2019 16:39:12 +0000 Subject: [PATCH 07/19] SPI_OPC_BUSY = 0x31 and some lynting --- opc/__init__.py | 144 +++++++++++++++++------------------------------- 1 file changed, 51 insertions(+), 93 deletions(-) diff --git a/opc/__init__.py b/opc/__init__.py index 9ddf48a..da236fd 100644 --- a/opc/__init__.py +++ b/opc/__init__.py @@ -1128,8 +1128,6 @@ def __init__(self, spi_connection, **kwargs): firmware_min = 2. # Minimum firmware version supported firmware_max = 2. # Maximum firmware version supported - SPI_OPC_READY = 0xF3 - SPI_OPC_BUSY = 0x03 #if self.firmware['major'] < firmware_min or self.firmware['major'] > firmware_max: # logger.error("Firmware version is invalid for this device.") @@ -1138,19 +1136,19 @@ def __init__(self, spi_connection, **kwargs): def get_ready_response(self, SPIcommand): SPI_OPC_READY = 0xF3 - SPI_OPC_BUSY = 0x03 + SPI_OPC_BUSY = 0x31 message = self.cnxn.xfer([SPIcommand])[0] sleep(1e-3) attempt = 0 - while(attempt<20 & message != SPI_OPC_READY): - attempt+=1 - if(message != SPI_OPC_READY): + while attempt < 20 & message != SPI_OPC_READY: + attempt += 1 + if message != SPI_OPC_READY: message = self.cnxn.xfer([SPIcommand])[0] - if(message != SPI_OPC_READY): + if message != SPI_OPC_READY: sleep(1e-3) # Wait 1ms before retrying - if(message != SPI_OPC_READY): - if(message == SPI_OPC_BUSY): + if message != SPI_OPC_READY: + if message == SPI_OPC_BUSY: self.spi_connection.flush() sleep(2) raise Exception("ERROR Waiting 2s (for OPC comms timeout)") @@ -1174,10 +1172,10 @@ def on(self): """ self.get_ready_response(0x03) # send the command byte sleep(10e-3) # sleep for 10 ms - b2 = self.cnxn.xfer([0x03])[0] # send the following byte powers the laser and fan + byte_2 = self.cnxn.xfer([0x03])[0] # send the following byte powers the laser and fan sleep(10) # sleep while it is properly powered on - return True if b2 == 0x03 else False + return True if byte_2 == 0x03 else False def off(self): """Turn OFF the OPC (fan and laser) @@ -1191,10 +1189,10 @@ def off(self): """ self.get_ready_response(0x03) # send the command byte sleep(10e-3) # sleep for 10 ms - b2 = self.cnxn.xfer([0x00])[0] # send the following byte to turn off laser and fan + byte_2 = self.cnxn.xfer([0x00])[0] # send the following byte to turn off laser and fan sleep(0.1) - return True if b2 == 0x03 else False + return True if byte_2 == 0x03 else False def set_laser_power(self, power): """Set the laser power only. @@ -1232,9 +1230,9 @@ def set_bin_weighting_index(self, bin_weighting_index): """ """ - if 0 < bin_weighting_index or bin_weighting_index > 10: + if bin_weighting_index < 0 or bin_weighting_index > 10: raise ValueError("Bin weigthing index is an integer between 0 and 10.") - if not(bin_weighting_index.is_integer()): + if not bin_weighting_index.is_integer(): raise ValueError("Bin weigthing index is an integer between 0 and 10.") # Send the command byte @@ -1341,62 +1339,64 @@ def read_firmware(self): self.firmware['minor'] = self.cnxn.xfer([0x12])[0] # Build the firmware version - self.firmware['version'] = float('{}.{}'.format(self.firmware['major'], self.firmware['minor'])) + self.firmware['version'] = float('{}.{}'.format(self.firmware['major'], + self.firmware['minor'])) sleep(0.1) return self.firmware def read_config(self): - """Read the configuration variables and return as a dictionary. + """ + Read the configuration variables and return as a dictionary. - **NOTE: This method is supported by firmware v18+.** + **NOTE: This method is supported by firmware v18+.** - :rtype: dictionary + :rtype: dictionary - :Example: + :Example: - - """ - config = [] - data = {} + + """ + config = [] + data = {} - # Send the command byte - self.get_ready_response(0x12) + # Send the command byte + self.get_ready_response(0x12) - # Wait 10 ms - sleep(10e-3) + # Wait 10 ms + sleep(10e-3) - # Read the config variables by sending 256 empty bytes - for i in range(256): - resp = self.cnxn.xfer([0x00])[0] - config.append(resp) + # Read the config variables by sending 256 empty bytes + for i in range(256): + resp = self.cnxn.xfer([0x00])[0] + config.append(resp) - # Add the bin bounds to the dictionary of data [bytes 0-33] - for i in range(0, 16): - data["Bin Boundary {0}".format(i)] = self._16bit_unsigned(config[2*i], config[2*i + 1]) + # Add the bin bounds to the dictionary of data [bytes 0-33] + for i in range(0, 16): + data["Bin Boundary {0}".format(i)] = self._16bit_unsigned(config[2*i], config[2*i + 1]) - # Add the Bin Boundaries diameter (BDD) [bytes 34-98] - for i in range(0, 16): - data["BBD {0}".format(i)] = self._calculate_float(config[4*i + 33:4*i + 37]) + # Add the Bin Boundaries diameter (BDD) [bytes 34-98] + for i in range(0, 16): + data["BBD {0}".format(i)] = self._calculate_float(config[4*i + 33:4*i + 37]) - # Add the Bin Weighting (BW) [bytes 99-163] - for i in range(0, 15): - data["BW {0}".format(i)] = self._calculate_float(config[4*i + 99:4*i + 103]) + # Add the Bin Weighting (BW) [bytes 99-163] + for i in range(0, 15): + data["BW {0}".format(i)] = self._calculate_float(config[4*i + 99:4*i + 103]) - # Add the Gain Scaling Coefficient (GSC) and sample flow rate (SFR) - data["GSC"] = self._calculate_float(config[122:126]) - data["SFR"] = self._calculate_float(config[126:130]) + # Add the Gain Scaling Coefficient (GSC) and sample flow rate (SFR) + data["GSC"] = self._calculate_float(config[122:126]) + data["SFR"] = self._calculate_float(config[126:130]) - # Add laser dac (LDAC) and Fan dac (FanDAC) - data["TOF_SFR"] = config[131] + # Add laser dac (LDAC) and Fan dac (FanDAC) + data["TOF_SFR"] = config[131] - data["M_A"] = self._16bit_unsigned(config[131:135]) + data["M_A"] = self._16bit_unsigned(config[131:135]) - sleep(0.1) + sleep(0.1) - return data + return data def write_config_variables(self, config_vars): """ Write configuration variables to non-volatile memory. @@ -1495,7 +1495,7 @@ def histogram(self, number_concentration=True): # Check that checksum and the least significant bits of the sum of histogram bins # are equivilant - if (self._MODBUS_CalcCRC(resp,62) != data['Checksum']): + if self._MODBUS_CalcCRC(resp,62) != data['Checksum']: logger.warning("Data transfer was incomplete") return None @@ -1562,7 +1562,7 @@ def pm(self): # Check that checksum and the least significant bits of the sum of histogram bins # are equivilant - if (self._MODBUS_CalcCRC(resp,14) != data['Checksum']): + if self._MODBUS_CalcCRC(resp,14) != data['Checksum']: logger.warning("Data transfer was incomplete") return None @@ -1602,48 +1602,6 @@ def _MODBUS_CalcCRC(self, data, nbrOfBytes): crc>>=1 return crc - - - - - def pm(self): - """Read the PM data and reset the histogram - - :rtype: dictionary - - :Example: - - >>> alpha.pm() - { - 'PM1': 0.12, - 'PM2.5': 0.24, - 'PM10': 1.42 - } - """ - - resp = [] - data = {} - - # Send the command byte - self.get_ready_response(0x11) - - # Wait 10 ms - sleep(10e-3) - - # read the histogram - for i in range(12): - r = self.cnxn.xfer([0x00])[0] - resp.append(r) - - # convert to real things and store in dictionary! - data['PM1'] = self._calculate_float(resp[0:4]) - data['PM2.5'] = self._calculate_float(resp[4:8]) - data['PM10'] = self._calculate_float(resp[8:]) - - sleep(0.1) - - return data - def save_config_variables(self): """Save the configuration variables in non-volatile memory. This method From e4459641f063f7ed02282e3386cb83476e174b1a Mon Sep 17 00:00:00 2001 From: Florentin Bulot <32537171+FlorentinBulotUoS@users.noreply.github.com> Date: Wed, 6 Mar 2019 16:55:05 +0000 Subject: [PATCH 08/19] add default firmware to suppress firmware check --- opc/__init__.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/opc/__init__.py b/opc/__init__.py index da236fd..04d73c2 100644 --- a/opc/__init__.py +++ b/opc/__init__.py @@ -1124,7 +1124,7 @@ class OPCR1(_OPC): """ def __init__(self, spi_connection, **kwargs): - super(OPCR1, self).__init__(spi_connection, model='R1', **kwargs) + super(OPCR1, self).__init__(spi_connection, model='R1', firmware=(2, 10), **kwargs) firmware_min = 2. # Minimum firmware version supported firmware_max = 2. # Maximum firmware version supported From c8e23cae6abc2768ddcfeaac11b4b23a3b06e367 Mon Sep 17 00:00:00 2001 From: Florentin Bulot <32537171+FlorentinBulotUoS@users.noreply.github.com> Date: Wed, 6 Mar 2019 17:05:11 +0000 Subject: [PATCH 09/19] bumped version numebr to 1.6.3 --- opc/__init__.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/opc/__init__.py b/opc/__init__.py index 04d73c2..5d56eef 100644 --- a/opc/__init__.py +++ b/opc/__init__.py @@ -14,7 +14,7 @@ logging.basicConfig(level=logging.INFO) logger = logging.getLogger(__name__) -__version__ = "1.6.0" +__version__ = "1.6.3" __all__ = ['OPCN2', 'OPCN1','OPCR1'] From 0fee4076b29c8a0b09f0d44f8cdd31b2e59d543f Mon Sep 17 00:00:00 2001 From: Florentin Bulot <32537171+FlorentinBulotUoS@users.noreply.github.com> Date: Wed, 20 Mar 2019 09:58:06 +0000 Subject: [PATCH 10/19] Some lynting --- opc/__init__.py | 46 ++++++++++++++++++++++++++++------------------ 1 file changed, 28 insertions(+), 18 deletions(-) diff --git a/opc/__init__.py b/opc/__init__.py index 5d56eef..74ba378 100644 --- a/opc/__init__.py +++ b/opc/__init__.py @@ -1228,6 +1228,16 @@ def set_laser_power(self, power): def set_bin_weighting_index(self, bin_weighting_index): """ + :param bin_weighting_index: Bin weigthing index is an integer between 0 and 10. + + :type bin_weighting_index: int + + :rtype: boolean + + :Example: + + >>> alpha.set_bin_weighting_index(2) + True """ if bin_weighting_index < 0 or bin_weighting_index > 10: @@ -1350,7 +1360,7 @@ def read_config(self): """ Read the configuration variables and return as a dictionary. - **NOTE: This method is supported by firmware v18+.** + **NOTE: This method has not been tested on the OPCR1 :rtype: dictionary @@ -1483,8 +1493,8 @@ def histogram(self, number_concentration=True): # Bins associated with firmware versions 14 and 15(?) data['SFR'] = self._calculate_float(resp[36:40]) - data['Temperature'] = self._ConvSTtoTemperature(self._16bit_unsigned(resp[40], resp[41])) - data['Humidity'] = self._ConvSRHtoRelativeHumidity(self._16bit_unsigned(resp[42], resp[43])) + data['Temperature'] = self._conv_st_to_temperature(self._16bit_unsigned(resp[40], resp[41])) + data['Humidity'] = self._conv_srh_to_relative_humidity(self._16bit_unsigned(resp[42], resp[43])) data['Sampling Period'] = self._calculate_float(resp[44:48]) data['Reject count glitch'] = resp[48] data['Reject count long'] = resp[49] @@ -1495,7 +1505,7 @@ def histogram(self, number_concentration=True): # Check that checksum and the least significant bits of the sum of histogram bins # are equivilant - if self._MODBUS_CalcCRC(resp,62) != data['Checksum']: + if self._modbus_calc_crc(resp, 62) != data['Checksum']: logger.warning("Data transfer was incomplete") return None @@ -1562,7 +1572,7 @@ def pm(self): # Check that checksum and the least significant bits of the sum of histogram bins # are equivilant - if self._MODBUS_CalcCRC(resp,14) != data['Checksum']: + if self._modbus_calc_crc(resp,14) != data['Checksum']: logger.warning("Data transfer was incomplete") return None @@ -1570,7 +1580,7 @@ def pm(self): return data - def _ConvSTtoTemperature(self, ST): + def _conv_st_to_temperature(self, ST): #Convert SHT31 ST output to Temperature (C) return -45 + 175*ST/65535 @@ -1578,28 +1588,28 @@ def _ConvSTtoTemperature(self, ST): - def _ConvSRHtoRelativeHumidity(self, SRH): + def _conv_srh_to_relative_humidity(self, SRH): #Convert SHT31 SRH output to Relative Humidity (%): return 100*SRH/65535 - def _MODBUS_CalcCRC(self, data, nbrOfBytes): + def _modbus_calc_crc(self, data, nbr_of_bytes): - POLYNOMIAL_MODBUS = 0xA001 #Generator polynomial for MODBUS crc - InitCRCval_MODBUS = 0xFFFF #Initial CRC value + polynomial_modbus = 0xA001 #Generator polynomial for MODBUS crc + init_crc_val_modbus = 0xFFFF #Initial CRC value - crc = InitCRCval_MODBUS - byteCtr = 0 - while(byteCtr>=1 - crc ^= POLYNOMIAL_MODBUS + crc >>= 1 + crc ^= polynomial_modbus else: - crc>>=1 + crc >>= 1 return crc From e5870443b33bb7bfddc2e4548aa3c43297d843f0 Mon Sep 17 00:00:00 2001 From: Florentin Bulot <32537171+FlorentinBulotUoS@users.noreply.github.com> Date: Fri, 29 Mar 2019 16:02:30 +0000 Subject: [PATCH 11/19] some more linting --- opc/__init__.py | 41 +++++++++++++++++++++++++---------------- 1 file changed, 25 insertions(+), 16 deletions(-) diff --git a/opc/__init__.py b/opc/__init__.py index 74ba378..89d9618 100644 --- a/opc/__init__.py +++ b/opc/__init__.py @@ -1133,30 +1133,39 @@ def __init__(self, spi_connection, **kwargs): # logger.error("Firmware version is invalid for this device.") # raise FirmwareVersionError("Your firmware is not yet supported. Only version 2 is currently supported.") - def get_ready_response(self, SPIcommand): + def get_ready_response(self, spi_command): + """ + Verify that the OPC is ready for communication + Flushes the SPI connection if 0x31 is returned after 20 attempts + or reset the OPC otherwise. - SPI_OPC_READY = 0xF3 - SPI_OPC_BUSY = 0x31 - message = self.cnxn.xfer([SPIcommand])[0] + :param spi_command: The command to be sent to the sensor + + :rtype: NULL + """ + + spi_opc_ready = 0xF3 + spi_opc_busy = 0x31 + message = self.cnxn.xfer([spi_command])[0] sleep(1e-3) attempt = 0 - while attempt < 20 & message != SPI_OPC_READY: + while attempt < 20 & message != spi_opc_ready: attempt += 1 - if message != SPI_OPC_READY: - message = self.cnxn.xfer([SPIcommand])[0] - if message != SPI_OPC_READY: + if message != spi_opc_ready: + message = self.cnxn.xfer([spi_command])[0] + if message != spi_opc_ready: sleep(1e-3) # Wait 1ms before retrying - if message != SPI_OPC_READY: - if message == SPI_OPC_BUSY: + if message != spi_opc_ready: + if message == spi_opc_busy: self.spi_connection.flush() sleep(2) - raise Exception("ERROR Waiting 2s (for OPC comms timeout)") + raise Exception("Waiting 2s (for OPC comms timeout)") else: self.spi_connection.flush() self.off() self.on() - raise Exception("ERROR Resetting the sensor") + raise Exception("Resetting the sensor") @@ -1580,17 +1589,17 @@ def pm(self): return data - def _conv_st_to_temperature(self, ST): + def _conv_st_to_temperature(self, st): #Convert SHT31 ST output to Temperature (C) - return -45 + 175*ST/65535 + return -45 + 175*st/65535 - def _conv_srh_to_relative_humidity(self, SRH): + def _conv_srh_to_relative_humidity(self, srh): #Convert SHT31 SRH output to Relative Humidity (%): - return 100*SRH/65535 + return 100*srh/65535 def _modbus_calc_crc(self, data, nbr_of_bytes): From 7fab3308f2200d2732dd57eeb3b6b5ad2a8e4d56 Mon Sep 17 00:00:00 2001 From: Florentin Bulot <32537171+FlorentinBulotUoS@users.noreply.github.com> Date: Wed, 1 May 2019 14:51:16 +0100 Subject: [PATCH 12/19] Fixed read_config() --- opc/__init__.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/opc/__init__.py b/opc/__init__.py index 89d9618..5da1882 100644 --- a/opc/__init__.py +++ b/opc/__init__.py @@ -1411,7 +1411,7 @@ def read_config(self): # Add laser dac (LDAC) and Fan dac (FanDAC) data["TOF_SFR"] = config[131] - data["M_A"] = self._16bit_unsigned(config[131:135]) + data["M_A"] = self._calculate_float(config[131:135]) sleep(0.1) From a6b1801eb847f7777a09172ec414bcebef164c45 Mon Sep 17 00:00:00 2001 From: Florentin Bulot <32537171+FlorentinBulotUoS@users.noreply.github.com> Date: Tue, 7 May 2019 11:18:04 +0100 Subject: [PATCH 13/19] Indentation issue + spi_connection replace by cnxn --- opc/__init__.py | 19 +++++++++---------- 1 file changed, 9 insertions(+), 10 deletions(-) diff --git a/opc/__init__.py b/opc/__init__.py index 5da1882..4c9404a 100644 --- a/opc/__init__.py +++ b/opc/__init__.py @@ -1134,16 +1134,15 @@ def __init__(self, spi_connection, **kwargs): # raise FirmwareVersionError("Your firmware is not yet supported. Only version 2 is currently supported.") def get_ready_response(self, spi_command): - """ - Verify that the OPC is ready for communication - Flushes the SPI connection if 0x31 is returned after 20 attempts - or reset the OPC otherwise. - - :param spi_command: The command to be sent to the sensor + """ + Verify that the OPC is ready for communication + Flushes the SPI connection if 0x31 is returned after 20 attempts + or reset the OPC otherwise. - :rtype: NULL - """ + :param spi_command: The command to be sent to the sensor + :rtype: NULL + """ spi_opc_ready = 0xF3 spi_opc_busy = 0x31 message = self.cnxn.xfer([spi_command])[0] @@ -1158,11 +1157,11 @@ def get_ready_response(self, spi_command): if message != spi_opc_ready: if message == spi_opc_busy: - self.spi_connection.flush() + self.cnxn.flush() sleep(2) raise Exception("Waiting 2s (for OPC comms timeout)") else: - self.spi_connection.flush() + self.cnxn .flush() self.off() self.on() raise Exception("Resetting the sensor") From 7a8b5a326ed6b3fc33b75373030a5e2635759c34 Mon Sep 17 00:00:00 2001 From: Florentin Bulot <32537171+FlorentinBulotUoS@users.noreply.github.com> Date: Tue, 7 May 2019 11:25:53 +0100 Subject: [PATCH 14/19] read confiugration command byte --- opc/__init__.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/opc/__init__.py b/opc/__init__.py index 4c9404a..05b5813 100644 --- a/opc/__init__.py +++ b/opc/__init__.py @@ -1380,7 +1380,7 @@ def read_config(self): data = {} # Send the command byte - self.get_ready_response(0x12) + self.get_ready_response(0x3C) # Wait 10 ms sleep(10e-3) From 3801d21cb0c171fc8b7d52187dd8a1f1f07c34c2 Mon Sep 17 00:00:00 2001 From: FlorentinBulotUoS Date: Fri, 16 Aug 2019 14:12:56 +0000 Subject: [PATCH 15/19] refactor get_ready_response, quit if the sensor does not answer --- opc/__init__.py | 54 +++++++++++++++++++++++++++++-------------------- 1 file changed, 32 insertions(+), 22 deletions(-) diff --git a/opc/__init__.py b/opc/__init__.py index 05b5813..270f270 100644 --- a/opc/__init__.py +++ b/opc/__init__.py @@ -11,7 +11,7 @@ from .exceptions import firmware_error_msg # set up a default logger -logging.basicConfig(level=logging.INFO) +logging.basicConfig(level=logging.DEBUG) logger = logging.getLogger(__name__) __version__ = "1.6.3" @@ -1128,7 +1128,8 @@ def __init__(self, spi_connection, **kwargs): firmware_min = 2. # Minimum firmware version supported firmware_max = 2. # Maximum firmware version supported - + self.spi_opc_ready = 0xF3 + self.spi_opc_busy = 0x31 #if self.firmware['major'] < firmware_min or self.firmware['major'] > firmware_max: # logger.error("Firmware version is invalid for this device.") # raise FirmwareVersionError("Your firmware is not yet supported. Only version 2 is currently supported.") @@ -1143,30 +1144,39 @@ def get_ready_response(self, spi_command): :rtype: NULL """ - spi_opc_ready = 0xF3 - spi_opc_busy = 0x31 + + message =self._attempt_get_ready_response(spi_command) + logger.debug("Byte received: 0x%02x",message) + if message != self.spi_opc_ready: + if message == self.spi_opc_busy: + logger.warning("OPCR1 busy") + #self.cnxn.flush() + sleep(2) + logger.warning("Waiting 2s (for OPC comms timeout)") + message = self._attempt_get_ready_response(spi_command) + if message != self.spi_opc_ready: + logger.error("OPCR1 not responding - quitting") + self.cnxn.close() + quit(1) + else: + logger.error("OPCR1 not responding - quitting") + self.cnxn.close() + quit(1) + + + def _attempt_get_ready_response(self, spi_command): + logger.debug("Attempt to read up to 20 times") message = self.cnxn.xfer([spi_command])[0] sleep(1e-3) + attempt = 0 - while attempt < 20 & message != spi_opc_ready: + while attempt < 20 & message != self.spi_opc_ready: attempt += 1 - if message != spi_opc_ready: + if message != self.spi_opc_ready: message = self.cnxn.xfer([spi_command])[0] - if message != spi_opc_ready: + if message != self.spi_opc_ready: sleep(1e-3) # Wait 1ms before retrying - - if message != spi_opc_ready: - if message == spi_opc_busy: - self.cnxn.flush() - sleep(2) - raise Exception("Waiting 2s (for OPC comms timeout)") - else: - self.cnxn .flush() - self.off() - self.on() - raise Exception("Resetting the sensor") - - + return message def on(self): """Turn ON the OPC (fan and laser) @@ -1387,7 +1397,7 @@ def read_config(self): # Read the config variables by sending 256 empty bytes for i in range(256): - resp = self.cnxn.xfer([0x00])[0] + resp = self.cnxn.xfer([0x3C])[0] config.append(resp) # Add the bin bounds to the dictionary of data [bytes 0-33] @@ -1687,4 +1697,4 @@ def reset(self): # Wait 10 ms sleep(10e-3) - return True \ No newline at end of file + return True From a81feea743d9ec601e1c33f3c2ffd6189d4486b7 Mon Sep 17 00:00:00 2001 From: FlorentinBulotUoS Date: Fri, 16 Aug 2019 15:44:41 +0000 Subject: [PATCH 16/19] fixed read_config --- opc/__init__.py | 65 ++++++++++++++++++++++++++++--------------------- 1 file changed, 37 insertions(+), 28 deletions(-) diff --git a/opc/__init__.py b/opc/__init__.py index 270f270..acbce83 100644 --- a/opc/__init__.py +++ b/opc/__init__.py @@ -11,7 +11,7 @@ from .exceptions import firmware_error_msg # set up a default logger -logging.basicConfig(level=logging.DEBUG) +logging.basicConfig(level=logging.INFO) logger = logging.getLogger(__name__) __version__ = "1.6.3" @@ -1145,24 +1145,23 @@ def get_ready_response(self, spi_command): :rtype: NULL """ - message =self._attempt_get_ready_response(spi_command) - logger.debug("Byte received: 0x%02x",message) + message = self._attempt_get_ready_response(spi_command) + logger.debug("Last byte received: 0x%02x",message) + if message != self.spi_opc_ready: - if message == self.spi_opc_busy: - logger.warning("OPCR1 busy") - #self.cnxn.flush() - sleep(2) - logger.warning("Waiting 2s (for OPC comms timeout)") - message = self._attempt_get_ready_response(spi_command) - if message != self.spi_opc_ready: - logger.error("OPCR1 not responding - quitting") - self.cnxn.close() - quit(1) - else: + + logger.warning("OPCR1 busy") + #self.cnxn.flush() + sleep(2) + logger.warning("Waiting 2s (for OPC comms timeout)") + message = self._attempt_get_ready_response(spi_command) + if message != self.spi_opc_ready: logger.error("OPCR1 not responding - quitting") self.cnxn.close() quit(1) + return True + def _attempt_get_ready_response(self, spi_command): logger.debug("Attempt to read up to 20 times") @@ -1176,6 +1175,7 @@ def _attempt_get_ready_response(self, spi_command): message = self.cnxn.xfer([spi_command])[0] if message != self.spi_opc_ready: sleep(1e-3) # Wait 1ms before retrying + logger.debug("Number of attempts: %s",attempt) return message def on(self): @@ -1395,33 +1395,42 @@ def read_config(self): # Wait 10 ms sleep(10e-3) - # Read the config variables by sending 256 empty bytes - for i in range(256): + # Read the config variables by sending 193 empty bytes + for i in range(193): resp = self.cnxn.xfer([0x3C])[0] config.append(resp) - # Add the bin bounds to the dictionary of data [bytes 0-33] - for i in range(0, 16): + for i in range(0, 17): data["Bin Boundary {0}".format(i)] = self._16bit_unsigned(config[2*i], config[2*i + 1]) - # Add the Bin Boundaries diameter (BDD) [bytes 34-98] - for i in range(0, 16): - data["BBD {0}".format(i)] = self._calculate_float(config[4*i + 33:4*i + 37]) + # Add the Bin Boundaries diameter (BDD) [bytes 34-101] + for i in range(0, 17): + data["BBD {0}".format(i)] = self._calculate_float(config[4*i + 34:4*i + 38]) - # Add the Bin Weighting (BW) [bytes 99-163] - for i in range(0, 15): - data["BW {0}".format(i)] = self._calculate_float(config[4*i + 99:4*i + 103]) + # Add the Bin Weighting (BW) [bytes 102-165] + for i in range(0, 16): + data["BW {0}".format(i)] = self._calculate_float(config[4*i + 102:4*i + 106]) # Add the Gain Scaling Coefficient (GSC) and sample flow rate (SFR) - data["GSC"] = self._calculate_float(config[122:126]) - data["SFR"] = self._calculate_float(config[126:130]) + data["GSC"] = self._calculate_float(config[166:170]) + data["SFR"] = self._calculate_float(config[170:174]) # Add laser dac (LDAC) and Fan dac (FanDAC) - data["TOF_SFR"] = config[131] + data["TOF_SFR"] = config[174] + + data["M_A"] = self._calculate_float(config[175:179]) + data["M_B"] = self._calculate_float(config[179:183]) + data["M_C"] = self._calculate_float(config[183:187]) + + data["PVP"] = config[187] + data["PowerStatus"] = config[188] + + data["Max TOF"] = self._16bit_unsigned(config[189],config[190]) - data["M_A"] = self._calculate_float(config[131:135]) + data["LaserDAC"] = config[191] + data["BinWeightingIndex"] = config[192] sleep(0.1) return data From d4869a263f0a0fada608e6e86911590b27e5caf8 Mon Sep 17 00:00:00 2001 From: FlorentinBulotUoS Date: Fri, 16 Aug 2019 15:53:07 +0000 Subject: [PATCH 17/19] fixed pm --- opc/__init__.py | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/opc/__init__.py b/opc/__init__.py index acbce83..da38fcd 100644 --- a/opc/__init__.py +++ b/opc/__init__.py @@ -1599,7 +1599,7 @@ def pm(self): # Check that checksum and the least significant bits of the sum of histogram bins # are equivilant - if self._modbus_calc_crc(resp,14) != data['Checksum']: + if self._modbus_calc_crc(resp, 12) != data['Checksum']: logger.warning("Data transfer was incomplete") return None @@ -1651,6 +1651,9 @@ def save_config_variables(self): >>> alpha.save_config_variables() True """ + + logger.warning("This method has not yet been tested yet. Use it at your own risks") + command = 0x43 byte_list = [0x3C, 0x3F, 0x3C, 0x43] success = [ 0x43, 0x3F, 0x3C, 0x3F, 0x3C] From 9dfae9d408adecac5b4c2815689ff702f67696ff Mon Sep 17 00:00:00 2001 From: FlorentinBulotUoS Date: Fri, 16 Aug 2019 16:23:48 +0000 Subject: [PATCH 18/19] lynting --- opc/__init__.py | 57 ++++++++++++++++++++++++++++++++----------------- 1 file changed, 37 insertions(+), 20 deletions(-) diff --git a/opc/__init__.py b/opc/__init__.py index da38fcd..d1d0e51 100644 --- a/opc/__init__.py +++ b/opc/__init__.py @@ -1131,8 +1131,8 @@ def __init__(self, spi_connection, **kwargs): self.spi_opc_ready = 0xF3 self.spi_opc_busy = 0x31 #if self.firmware['major'] < firmware_min or self.firmware['major'] > firmware_max: - # logger.error("Firmware version is invalid for this device.") - # raise FirmwareVersionError("Your firmware is not yet supported. Only version 2 is currently supported.") + # logger.error("Firmware version is invalid for this device.") + # raise FirmwareVersionError("Your firmware is not yet supported. Only version 2 is currently supported.") def get_ready_response(self, spi_command): """ @@ -1146,7 +1146,7 @@ def get_ready_response(self, spi_command): """ message = self._attempt_get_ready_response(spi_command) - logger.debug("Last byte received: 0x%02x",message) + logger.debug("Last byte received: 0x%02x", message) if message != self.spi_opc_ready: @@ -1237,11 +1237,11 @@ def set_laser_power(self, power): # Wait 10 ms sleep(10e-3) - b = self.cnxn.xfer([power])[0] + byte = self.cnxn.xfer([power])[0] sleep(0.1) - return True if b == 0x04 else False + return True if byte == 0x04 else False def set_bin_weighting_index(self, bin_weighting_index): @@ -1269,11 +1269,11 @@ def set_bin_weighting_index(self, bin_weighting_index): # Wait 10 ms sleep(10e-3) - b = self.cnxn.xfer([bin_weighting_index])[0] + byte = self.cnxn.xfer([bin_weighting_index])[0] sleep(0.1) - return True if b == 0x05 else False + return True if byte == 0x05 else False def read_info_string(self): """Reads the information string for the OPC @@ -1426,7 +1426,7 @@ def read_config(self): data["PVP"] = config[187] data["PowerStatus"] = config[188] - data["Max TOF"] = self._16bit_unsigned(config[189],config[190]) + data["Max TOF"] = self._16bit_unsigned(config[189], config[190]) data["LaserDAC"] = config[191] @@ -1652,26 +1652,20 @@ def save_config_variables(self): True """ - logger.warning("This method has not yet been tested yet. Use it at your own risks") - - command = 0x43 - byte_list = [0x3C, 0x3F, 0x3C, 0x43] - success = [ 0x43, 0x3F, 0x3C, 0x3F, 0x3C] + byte_list = [0x3F, 0x3C, 0x3F, 0x3C, 0x43] + success = [0x43, 0x3F, 0x3C, 0x3F, 0x3C] resp = [] # Send the command byte - self.get_ready_response(0x11) + self.get_ready_response(0x43) # Wait 10 ms sleep(10e-3) - # append the response of the command byte to the List - resp.append(r) - # Send the rest of the config bytes for each in byte_list: - r = self.cnxn.xfer([each])[0] - resp.append(r) + res = self.cnxn.xfer([each])[0] + resp.append(res) sleep(0.1) @@ -1692,6 +1686,15 @@ def _enter_bootloader_mode(self): return True if self.cnxn.xfer(0x41)[0] == 0xF3 else False def check_status(self): + """Check the status of the OPCR1. + + :rtype: boolean + + :Example: + + >>> alpha.check_status() + True + """ # Send the command byte self.get_ready_response(0xCF) @@ -1699,9 +1702,23 @@ def check_status(self): # Wait 10 ms sleep(10e-3) - return True + res = self.cnxn.xfer(0xCF)[0] + + if res == self.spi_opc_ready: + return True + + return False def reset(self): + """Reset the OPCR1. + + :rtype: boolean + + :Example: + + >>> alpha.reset() + True + """ # Send the command byte self.get_ready_response(0x06) From 765eebab75fa1c5d8cd7403a0918359ce9103ea2 Mon Sep 17 00:00:00 2001 From: FlorentinBulotUoS Date: Fri, 16 Aug 2019 16:28:32 +0000 Subject: [PATCH 19/19] test file for the OPCR1 --- tests/opcr1_test.py | 40 ++++++++++++++++++++++++++++++++++++++++ 1 file changed, 40 insertions(+) create mode 100755 tests/opcr1_test.py diff --git a/tests/opcr1_test.py b/tests/opcr1_test.py new file mode 100755 index 0000000..0eb11b2 --- /dev/null +++ b/tests/opcr1_test.py @@ -0,0 +1,40 @@ +import unittest +from time import sleep +from opc import OPCR1 +from opc.exceptions import SpiConnectionError, FirmwareVersionError +from usbiss.spi import SPI + +interval = 1 + +spi = SPI("/dev/ttyACM0",mode=1, max_speed_hz=500000) + +alpha = OPCR1(spi) + + + +alpha.on() +sleep(10) + +print("Read info string") +print(alpha.read_info_string()) + +print("Read serial number") +print(alpha.sn()) + +print("Read firmware") +print(alpha.read_firmware()) + + +print("Read config") +print(alpha.read_config()) + + +print("Read Histogram") +print(alpha.histogram()) + +sleep(10) +print("Read PM") +print(alpha.pm()) + + +alpha.off()