def setup(i2c_bus=1, i2c_addr=0x20, bits=24, read_mode=False, invert=False): """Set up the IO expander devices.""" global _I2C_ADDR global _BITS global _BUS global _PORT_VALUE global _READMODE global _INVERT _I2C_ADDR = i2c_addr _BITS = bits _READMODE = read_mode _INVERT = invert # Make 8-bits (can be 2- or 4-bits, but should always pack in a 8-bit msg) while bits % 8: bits += 1 # Increase array size _PORT_VALUE = [0xFF] * int(bits / 8) # Set up I2C bus connectivity _BUS = SMBus(i2c_bus) # Write 1 to all pins to prepaire them for reading, or bring hardware in a defined state msg = i2c_msg.write(_I2C_ADDR, _PORT_VALUE) if _BUS: _BUS.i2c_rdwr(msg) else: raise ReferenceError( "I2C bus was not created, please check I2C address!") # If in read mode: do first hw read to have memory ready if read_mode: hw_to_memory()
class Sensor: def __init__(self): self.SENSOR_DEVICE_ADDRESS = 0x0d self.SENSOR_BYTES = 20 self.I2C_MODE = 2 self.bus = SMBus(self.I2C_MODE) self.msg = i2c_msg.read(self.SENSOR_DEVICE_ADDRESS, self.SENSOR_BYTES) self.sensorIndex = { 'uv': [2], 'temp': [4, 5], 'pressure': [8, 9, 10, 11], 'atmtemp': [8, 9, 10, 11], 'humidity': [8, 9, 10, 11] } def parseMsg(self, msg): return [hex(i) for i in list(msg)] def getAllSensorValue(self): self.bus.i2c_rdwr(self.msg) return parseMsg(self.msg) def getSensorValue(self, sensor): sensorValues = self.getAllSensorValue() bytesRequired = [] for i in self.sensorIndex[sensor]: bytesRequired.append(sensorValues[i]) return bytesRequired
class DSPI2C: def __init__(self, i2c_addr: int, i2c_bus: int = 1): self.i2c_addr = i2c_addr self.bus = SMBus(i2c_bus) def readReg(self, reg_addr: int, num_bytes: int = 1) -> Union[int, List[int]]: msg_wr = i2c_msg.write(self.i2c_addr, wordToBytes(reg_addr)) msg_rd = i2c_msg.read(self.i2c_addr, num_bytes) self.bus.i2c_rdwr(msg_wr, msg_rd) rd_list = list(msg_rd) return rd_list[0] if len(rd_list) == 1 else rd_list def writeReg(self, reg_addr: int, data: Union[int, List[int]]) -> None: if isinstance(data, int): data = [data] wr_content = wordToBytes(reg_addr) wr_content.extend(data) msg_wr = i2c_msg.write(self.i2c_addr, wr_content) self.bus.i2c_rdwr(msg_wr) def showReg(self, reg_addr: int, length: int = 1) -> None: reg_values = self.readReg(reg_addr, length) for line in formatReg(reg_addr, reg_values): print(line)
def get_version(self): info = 99999 try: bus = SMBus(I2CBUS) bus.write_byte_data(self.I2Caddr, SGP30_MSB, SGP30_GET_VERSION) time.sleep(0.01) #resp = bus.read_i2c_block_data( self.I2Caddr, 0, 3 ) read = i2c_msg.read(self.I2Caddr, 3) bus.i2c_rdwr(read) resp = list(read) bus.close() if (self.crc8(resp) == 0): print "sgp30.get_version() Feature Set Type: 0x%02x Version: 0x%02x" % ( resp[0], resp[1]) info = (resp[0] << 8) | resp[1] #log Feature & Version Info f = open(self.FptrVer, "w") f.write("%d" % info) f.close() except: bus.close() print "spg30.get_version() failed" return info
class HwRpiI2cHw(HwI2cHalMlx90632): support_buffer = False pass def __init__(self, channel=1): if isinstance(channel, str) and channel.startswith("I2C-"): channel = int(channel[4:]) if channel == 1 and platform.machine().startswith('armv'): os.system('raspi-gpio set 2 a0') os.system('raspi-gpio set 3 a0') self.i2c = SMBus(channel) def connect(self): pass def disconnect(self): pass def i2c_read(self, i2c_addr, addr, count=1, unpack_format='H'): addr_msb = addr >> 8 & 0x00FF addr_lsb = addr & 0x00FF write = i2c_msg.write(i2c_addr, [addr_msb, addr_lsb]) read = i2c_msg.read(i2c_addr, count * 2) self.i2c.i2c_rdwr(write, read) if unpack_format is None: return bytes(list(read)) results = struct.unpack(">{}{}".format(count, unpack_format), bytes(list(read))) if count == 1: return results[0] return results def i2c_write(self, i2c_addr, addr, data): cmd = [] reg_msb = addr >> 8 cmd.append(addr & 0x00FF) if type(data) is list: for d in data: cmd.append((d >> 8) & 0x00FF) cmd.append(d & 0x00FF) else: cmd.append((data >> 8) & 0x00FF) cmd.append(data & 0x00FF) # print(reg_msb, cmd) self.i2c.write_i2c_block_data(i2c_addr, reg_msb, cmd) if (addr & 0xFF00) == 0x2400: # Wait after EEWRITE! time.sleep(0.010) # 10ms return 0 def get_hardware_id(self): return "Raspberry Pi I2C Hardware"
def task(self): co2_val = 99999 try: bus = SMBus(I2CBUS) write = i2c_msg.write(self.I2Caddr, K30_MSG) bus.i2c_rdwr(write) time.sleep(0.02) read = i2c_msg.read(self.I2Caddr, 4) bus.i2c_rdwr(read) resp = list(read) bus.close() #print resp cs = resp[0] + resp[1] + resp[2] cs &= 0xff # check checksum if cs == resp[3]: co2_val = (resp[1] << 8) | resp[2] # sensor provides signed int if co2_val < 32767 and co2_val > 100: # read of i2c co2 value passes muster # include it in the rolling average self.co2_buff[self.co2_ptr] = co2_val self.co2_ptr += 1 if self.co2_ptr >= CO2_MAX: self.co2_ptr = 0 co2_avg = 0 for i in range(CO2_MAX): co2_avg += self.co2_buff[i] co2_avg /= CO2_MAX #print "CO2: %d" % co2_avg f = open(self.FptrCO2, "w") f.write("%d" % co2_avg) f.close() except: bus.close() print "k30.task() failed" return co2_val
def get_sid(self): sid = 99999 try: info = [0xffff] * 3 test = 0 bus = SMBus(I2CBUS) bus.write_byte_data(self.I2Caddr, SGP30_SID_MSB, SGP30_SID_LSB) time.sleep(0.01) #resp = bus.read_i2c_block_data( self.I2Caddr, 0, 9 ) read = i2c_msg.read(self.I2Caddr, 9) bus.i2c_rdwr(read) resp = list(read) bus.close() sid_1 = [resp[0], resp[1], resp[2]] sid_2 = [resp[3], resp[4], resp[5]] sid_3 = [resp[6], resp[7], resp[8]] if (self.crc8(sid_1) == 0): info[0] = (sid_1[0] << 8) | sid_1[1] test += 1 if (self.crc8(sid_2) == 0): info[1] = (sid_2[0] << 8) | sid_2[1] test += 1 if (self.crc8(sid_3) == 0): info[2] = (sid_3[0] << 8) | sid_3[1] test += 1 if test == 3: sid = (info[0] << 32) | (info[1] << 16) | info[2] print "sgp30.get_sid(): 0x%012x" % (sid) #log Serial ID f = open(self.FptrSID, "w") f.write("%d" % sid) f.close() else: print "sgp30.get_sid(): CRC error" except: bus.close() print "sgp30.get_sid() failed" return sid
def sendSlot(rooms): bus = SMBus(1) prev_lot = int(rooms[0]['slot']/22) i=0 j=0 while (i < len(rooms)): while (j < len(rooms)): r = rooms[j] j+=1 lot = int(r['slot']/22) if lot > prev_lot: break bus.i2c_rdwr(i2c_msg.write(getAddress(lot), [r['slot'] % 22])) time.sleep(0.01) print(r['slot'], end=' ') prev_lot = lot i=j bus.i2c_rdwr(i2c_msg.write(getAddress(prev_lot), [255])) print('')
class HwRpiI2cHw(HwI2cHalMlx90640): support_buffer = False pass def __init__(self, channel=1): if isinstance(channel, str) and channel.startswith("I2C-"): channel = int(channel[4:]) if channel == 1 and platform.machine().startswith('armv'): os.system('raspi-gpio set 2 a0') os.system('raspi-gpio set 3 a0') self.i2c = SMBus(channel) def connect(self): pass def i2c_read(self, i2c_addr, addr, count=2): addr_msb = addr >> 8 & 0x00FF addr_lsb = addr & 0x00FF write = i2c_msg.write(i2c_addr, [addr_msb, addr_lsb]) read = i2c_msg.read(i2c_addr, count) self.i2c.i2c_rdwr(write, read) return bytes(list(read)), 0 def i2c_write(self, i2c_addr, addr, data): cmd = [] reg_msb = addr >> 8 cmd.append(addr & 0x00FF) for d in data: cmd.append(d) self.i2c.write_i2c_block_data(i2c_addr, reg_msb, cmd) return 0 def get_hardware_id(self): return "Raspberry Pi I2C Hardware"
def main(): bus = SMBus(1) address = [0x10, 0x11] while True: for i in range(len(address)): message = 'j,' + str(round(random.uniform(-1, 1), 4)) + ',' + str( round(random.uniform(-1, 1), 4)) print("[+]", message) message = list(message.encode('utf-8')) print("[+]", message) write = i2c_msg.write(address[i], message) read = i2c_msg.read(address[i], 16) print("[*] Issuing read/write") test = bus.i2c_rdwr(write, read) time.sleep(0.125)
class MLX90640: def __init__(address=0x33): #default 0x33 self.address = address self.bus = SMBus(1) self.addr = address self.gain = self.getGain() self.VDD0 = 3.3 self.DV = self.getVDD() self.VDD = self.DV+self.VDD0 self.Ta0 = 25 self.Ta = self.getTa() self.emissivity = 1 self.TGC = self.getTGC() self.chessNotIL = 1 self.KsTa = self.getKsTa() self.KsTo1, self.KsTo2, self.KsTo3, self.KsTo4 = self.getKsTo() self.step, self.CT3, self.CT4 = self.getCorners() self.CT1 = 40 self.CT2 = 0 self.alphaCorrR1 = 1/float(1+ self.KsTo1*(0-(-40))) self.alphaCorrR2 = 1 self.alphaCorrR3 = 1 + self.KsTo2*(self.CT3-0) self.alphaCorrR4 = self.alphaCorrR3*(1+self.KsTo3*(self.CT4-self.CT3)) def getRegs(self,reg,num): write = i2c_msg.write(self.addr,[reg>>8,reg&0xFF]) read = i2c_msg.read(self.address,num) self.bus.i2c_rdwr(write, read) return list(read) def getRegf(self,reg): write = i2c_msg.write(self.addr,[reg>>8,reg&0xFF]) read = i2c_msg.read(self.address,2) self.bus.i2c_rdwr(write, read) result = list(read) return (result[0]<<8)+result[1] def root4(self,num): return math.sqrt(math.sqrt(num)) def getTGC(self): TGC = self.getRegf(0x243C) & 0x00FF if TGC > 127: TGC = TGC - 256 return TGC def getVDD(self): Kvdd = (self.getRegf(0x2433) & 0xFF00)/256 if Kvdd > 127: Kvdd = Kvdd -256 Kvdd = Kvdd*32 Vdd25 = self.getRegf(0x2433) & 0x00FF Vdd25 = (Vdd25-256)*32 - 8192 RAM = self.getRegf(0x072A) if RAM > 32767: RAM = RAM - 65536 DV = (RAM - Vdd25)/float(Kvdd) return DV def getTa(self): KVptat = (self.getRegf(0x2432) & 0xFC00)/1024 if KVptat > 31: KVptat = KVptat - 62 KVptat = KVptat/4096.0 KTptat = self.getRegf(0x2432) & 0x03FF if KTptat > 511: KTptat = KTptat - 1022 KTptat = KTptat/8.0 Vptat25 = self.getRegf(0x2431) if Vptat25 > 32767: Vptat25 = Vptat25 - 65536 Vptat = self.getRegf(0x0720) if Vptat > 32767: Vptat = Vptat - 65536 Vbe = self.getRegf(0x0700) if Vbe > 32767: Vbe = Vbe - 65536 AlphaptatEE = (self.getRegf(0x2410) & 0xF000)/4096 Alphaptat = (AlphaptatEE/4)+8 Vptatart = (Vptat/float(Vptat * Alphaptat + Vbe))*262144 Ta = ((Vptatart/float(1+KVptat*self.DV)-Vptat25)/float(KTptat))+self.Ta0 return Ta def getGain(self): GAIN = self.getRegf(0x2430) if GAIN > 32767: GAIN = GAIN - 65536 RAM = self.getRegf(0x070A) if RAM > 32767: RAM = RAM - 65536 return GAIN/float(RAM) def pixnum(self,i,j): return (i-1)*32 + j def patternChess(self,i,j): pixnum = self.pixnum(i,j) a = (pixnum-1)/32 b = int((pixnum-1)/32)/2 return int(a) - int(b)*2 def getKsTa(self): KsTaEE = (self.getRegf(0x243C) & 0xFF00) >> 256 if KsTaEE > 127: KsTaEE = KsTaEE -256 KsTa = KsTaEE/8192.0 return KsTa def getKsTo(self): EE1 = self.getRegf(0x243D) EE2 = self.getRegf(0x243E) KsTo1 = EE1 & 0x00FF KsTo3 = EE2 & 0x00FF KsTo2 = (EE1 & 0xFF00) >> 8 KsTo4 = (EE2 & 0xFF00) >> 8 if KsTo1 > 127: KsTo1 = KsTo1 -256 if KsTo2 > 127: KsTo2 = KsTo2 -256 if KsTo3 > 127: KsTo3 = KsTo3 -256 if KsTo4 > 127: KsTo4 = KsTo4 -256 KsToScale = (self.getRegf(0x243F) & 0x000F)+8 KsTo1 = KsTo1/float(pow(2,KsToScale)) KsTo2 = KsTo2/float(pow(2,KsToScale)) KsTo3 = KsTo3/float(pow(2,KsToScale)) KsTo4 = KsTo4/float(pow(2,KsToScale)) return KsTo1, KsTo2, KsTo3, KsTo4 def getCorners(self): EE = self.getRegf(0x243F) step = ((EE & 0x3000)>>12)*10 CT3 = ((EE & 0x00f0)>>4)*step CT4 = ((EE & 0x0f00)>>8)*(step+CT3) return step, CT3, CT4 def getPixData(self,i,j): Offsetavg = self.getRegf(0x2411) if Offsetavg > 32676: Offsetavg = Offsetavg-65536 scaleVal = self.getRegf(0x2410) OCCscaleRow = (scaleVal&0x0f00)/256 OCCscaleCol = (scaleVal&0x00F0)/16 OCCscaleRem = (scaleVal&0x000F) rowAdd = 0x2412 + ((i-1)/4) colAdd = 0x2418 + ((j-1)/4) rowMask = 0xF<<(4*((i-1)%4)) colMask = 0xF<<(4*((j-1)%4)) OffsetPixAdd = 0x243F+((i-1)*32)+j OffsetPixVal = self.getRegf(OffsetPixAdd) OffsetPix = (OffsetPixVal & 0xFC00)/1024 if OffsetPix >31: OffsetPix = OffsetPix - 64 OCCRow = (self.getRegf(rowAdd) & rowMask)>>(4*((i-1)%4)) if OCCRow >7: OCCRow = OCCRow -16 OCCCol = (self.getRegf(colAdd) & colMask)>>(4*((j-1)%4)) if OCCCol > 7: OCCCol = OCCCol -16 pixOffset = Offsetavg + OCCRow*pow(2,OCCscaleRow) + OCCCol*pow(2,OCCscaleCol) + OffsetPix*pow(2,OCCscaleRem) KtaEE = (OffsetPixVal & 0x000E)/2 if KtaEE > 3: KtaEE = KtaEE - 7 colEven = not (j%2) rowEven = not (i%2) rowOdd = not rowEven colOdd = not colEven KtaAvAddr = 0x2436 + (colEven) KtaAvMask = 0xFF00 >> (8*rowEven) KtaRC = (self.getRegf(KtaAvAddr) & KtaAvMask) >> 8* rowOdd if KtaRC > 127: KtaRC = KtaAvRC - 256 KtaScale1 = ((self.getRegf(0x2438) & 0x00F0) >>4)+8 KtaScale2 = (self.getRegf(0x2438) & 0x000F) Kta = (KtaRC+(KtaEE<<KtaScale2))/float(pow(2,KtaScale1)) shiftNum = (rowOdd*4)+(colOdd*8) KvMask = 0x000F << shiftNum Kv = (self.getRegf(0x2434) & KvMask) >> shiftNum if Kv > 7: Kv = Kv-16 KvScale = (self.getRegf(0x2438) & 0x0F00)>>8 Kv = Kv/float(KvScale) RAMaddr = 0x400+((i-1)*32)+ j-1 RAM = self.getRegf(RAMaddr) if RAM > 32767: RAM = RAM - 65536 pixGain = RAM*self.gain pixOs = pixGain - pixOffset*(1+Kta*(self.Ta - self.Ta0)*(1+Kv*(self.VDD - self.VDD0))) return pixOs def getCompensatedPixData(self,i,j): pixOs = self.getPixData(i,j) Kgain = ((self.gain -1)/10)+1 pixGainCPSP0 = self.getRegf(0x0708) if pixGainCPSP0 > 32767: pixGainCPSP0 = pixGainCPSP0 - 65482 pixGainCPSP1 = self.getRegf(0x0728) if pixGainCPSP1 > 32767: pixGainCPSP1 = pixGainCPSP1 - 65482 pixGainCPSP0 = pixGainCPSP0*Kgain pixGainCPSP1 = pixGainCPSP1*Kgain OffCPSP0 = self.getRegf(0x243A) & 0x03FF if OffCPSP0 > 511: OffCPSP0 = OffCPSP0-1024 OffCPSP1d = (self.getRegf(0x243A) &0xFC00)>>10 if OffCPSP1d > 31: OffCPSP1d = OffCPSP1d-64 OffCPSP1 = OffCPSP1d + OffCPSP0 KvtaCPEEVal = self.getRegf(0x243B) KvtaScaleVal = self.getRegf(0x2438) KtaScale1 = ((KvtaScaleVal & 0x00F0)>>4)+8 KvScale = (KvtaScaleVal & 0x0F00)>>8 KtaCPEE = KvtaCPEEVal & 0x00FF if KtaCPEE > 127: KtaCPEE = KtaCPEE -256 KvCPEE = (KvtaCPEEVal & 0xFF00)>>8 KtaCP = KtaCPEE/float(pow(2,KtaScale1)) KvCP = KvCPEE/float(pow(2,KvScale)) b = (1+KtaCP*(self.Ta - self.Ta0))*(1+ KvCP*(self.VDD - self.VDD0)) pixOSCPSP0 = pixGainCPSP0 - OffCPSP0*b pixOSCPSP1 = pixGainCPSP1 - OffCPSP1*b if self.chessNotIL: pattern = self.patternChess(i,j) else: pattern = self.patternChess(i,j) VIREmcomp = pixOs/self.emissivity VIRcomp = VIREmcomp - self.TGC*((1-pattern)*pixOSCPSP0 + pattern*pixOSCPSP1) ###########TESTED TO HERE reg2439val = self.getRegf(0x2439) reg2420val = self.getRegf(0x2420) alphaScaleCP = ((reg2420val & 0xF000)>>12) + 27 CPP1P0ratio = (reg2439val & 0xFC00)>>10 if CPP1P0ratio >31: CPP1P0ratio = CPP1P0ratio -64 alphaRef = self.getRegf(0x2421) alphaScale = ((reg2420val & 0xF000)>>12) + 30 rowAdd = 0x2422 + ((i-1)/4) colAdd = 0x2428 + ((j-1)/4) rowMask = 0xF<<(4*((i-1)%4)) colMask = 0xF<<(4*((j-1)%4)) ACCRow = (self.getRegf(rowAdd) & rowMask)>>(4*((i-1)%4)) if ACCRow >7: ACCRow = ACCRow -16 ACCCol = (self.getRegf(colAdd) & colMask)>>(4*((j-1)%4)) if ACCCol > 7: ACCCol = ACCCol -16 ACCScaleRow = (reg2420val & 0x0F00)>>8 ACCScaleCol = (reg2420val & 0x00F0)>>4 ACCScaleRem = (reg2420val & 0x000F) alphaPixel = (self.getRegf(0x241f+self.pixnum(i,j))&0x03f0)>>4 alpha = (alphaRef+(ACCRow<<ACCScaleRow)+(ACCCol<<ACCScaleCol)+(alphaPixel<<ACCScaleRem))/float(pow(2,alphaScale)) alphaCPSP0 = (reg2439val & 0x03ff)/float(pow(2,alphaScaleCP)) alphaCPSP1 = alphaCPSP0*(1+CPP1P0ratio/128.0) alphacomp= alpha - self.TGC*((1-pattern)*alphaCPSP0 + pattern*alphaCPSP1)*(1+self.KsTa*(self.Ta-self.Ta0)) Tak4 = pow(self.Ta + 273.15,4) Trk4 = pow(self.Ta-8 + 273.15,4) Tar = Trk4-(Trk4-Tak4)/self.emissivity Sx = self.KsTo2*self.root4(pow(alphacomp,3)*VIRcomp + pow(alphacomp,4)*Tar) return self.root4((VIRcomp/(alphacomp*(1-self.KsTo2*273.15)+Sx))+Tar) - 273.15
class ADS1x15(object): """Base functionality for ADS1x15 analog to digital converters.""" def __init__(self, address=_ADS1X15_DEFAULT_ADDRESS, gain=1, data_rate=None, mode=Mode.SINGLE): self._last_pin_read = None self.buf = bytearray(3) self._data_rate = self._gain = self._mode = None self.gain = gain self.data_rate = self._data_rate_default( ) if data_rate is None else data_rate self.mode = mode self.address = address # -----Open I2C interface: # self.bus = SMBus(0) # Rev 1 Pi uses 0 self.bus = SMBus(1) # Rev 2 Pi uses 1 @property def data_rate(self): """The data rate for ADC conversion in samples per second.""" return self._data_rate @data_rate.setter def data_rate(self, rate): possible_rates = self.rates if rate not in possible_rates: raise ValueError( "Data rate must be one of: {}".format(possible_rates)) self._data_rate = rate @property def rates(self): """Possible data rate settings.""" raise NotImplementedError('Subclass must implement rates property.') @property def rate_config(self): """Rate configuration masks.""" raise NotImplementedError( 'Subclass must implement rate_config property.') @property def gain(self): """The ADC gain.""" return self._gain @gain.setter def gain(self, gain): possible_gains = self.gains if gain not in possible_gains: raise ValueError("Gain must be one of: {}".format(possible_gains)) self._gain = gain @property def gains(self): """Possible gain settings.""" g = list(_ADS1X15_CONFIG_GAIN.keys()) g.sort() return g @property def mode(self): """The ADC conversion mode.""" return self._mode @mode.setter def mode(self, mode): if mode != Mode.CONTINUOUS and mode != Mode.SINGLE: raise ValueError("Unsupported mode.") self._mode = mode def read(self, pin, is_differential=False): """I2C Interface for ADS1x15-based ADCs reads. params: :param pin: individual or differential pin. :param bool is_differential: single-ended or differential read. """ pin = pin if is_differential else pin + 0x04 return self._read(pin) def _data_rate_default(self): """Retrieve the default data rate for this ADC (in samples per second). Should be implemented by subclasses. """ raise NotImplementedError( 'Subclasses must implement _data_rate_default!') def _conversion_value(self, raw_adc): """Subclasses should override this function that takes the 16 raw ADC values of a conversion result and returns a signed integer value. """ raise NotImplementedError( 'Subclass must implement _conversion_value function!') def _read(self, pin): """Perform an ADC read. Returns the signed integer result of the read.""" if self.mode == Mode.CONTINUOUS and self._last_pin_read == pin: return self._conversion_value(self.get_last_result(True)) else: self._last_pin_read = pin config = _ADS1X15_CONFIG_OS_SINGLE config |= (pin & 0x07) << _ADS1X15_CONFIG_MUX_OFFSET config |= _ADS1X15_CONFIG_GAIN[self.gain] config |= self.mode config |= self.rate_config[self.data_rate] config |= _ADS1X15_CONFIG_COMP_QUE_DISABLE self._write_register(_ADS1X15_POINTER_CONFIG, config) if self.mode == Mode.SINGLE: while not self._conversion_complete(): pass return self._conversion_value(self.get_last_result(False)) def _conversion_complete(self): """Return status of ADC conversion.""" # OS is bit 15 # OS = 0: Device is currently performing a conversion # OS = 1: Device is not currently performing a conversion return self._read_register(_ADS1X15_POINTER_CONFIG) & 0x8000 def get_last_result(self, fast=False): """Read the last conversion result when in continuous conversion mode. Will return a signed integer value. If fast is True, the register pointer is not updated as part of the read. This reduces I2C traffic and increases possible read rate. """ return self._read_register(_ADS1X15_POINTER_CONVERSION, fast) def _write_register(self, reg, value): """Write 16 bit value to register.""" self.buf[0] = reg self.buf[1] = (value >> 8) & 0xFF self.buf[2] = value & 0xFF # Write some bytes to address msg = i2c_msg.write(self.address, [self.buf[0], self.buf[1], self.buf[2]]) self.bus.i2c_rdwr(msg) def _read_register(self, reg, fast=False): """Read 16 bit register value. If fast is True, the pointer register is not updated. """ if fast: self.buf = self.bus.read_i2c_block_data( 80, 0, 2) # read 16 bit (2 byte of data) else: write = i2c_msg.write(self.address, [reg]) read = i2c_msg.read(self.address, 2) self.bus.i2c_rdwr(write, read) return ord(read.buf[0]) << 8 | ord(read.buf[1])
class SPS30(): SPS_ADDR = 0x69 START_MEAS = [0x00, 0x10] STOP_MEAS = [0x01, 0x04] R_DATA_RDY = [0x02, 0x02] R_VALUES = [0x03, 0x00] RW_AUTO_CLN = [0x80, 0x04] START_CLN = [0x56, 0x07] R_ARTICLE_CD = [0xD0, 0x25] R_SERIAL_NUM = [0xD0, 0x33] RESET = [0xD3, 0x04] NO_ERROR = 1 ARTICLE_CODE_ERROR = -1 SERIAL_NUMBER_ERROR = -2 AUTO_CLN_INTERVAL_ERROR = -3 DATA_READY_FLAG_ERROR = -4 MEASURED_VALUES_ERROR = -5 dict_values = {"pm1p0" : None, "pm2p5" : None, "pm4p0" : None, "pm10p0" : None, "nc0p5" : None, "nc1p0" : None, "nc2p5" : None, "nc4p0" : None, "nc10p0" : None, "typical": None} def __init__(self, port): self.bus = SMBus(port) def read_article_code(self): result = [] article_code = [] write = i2c_msg.write(self.SPS_ADDR, self.R_ARTICLE_CD) self.bus.i2c_rdwr(write) read = i2c_msg.read(self.SPS_ADDR, 48) self.bus.i2c_rdwr(read) for i in range(read.len): result.append(bytes_to_int(read.buf[i])) if checkCRC(result): for i in range (2, len(result), 3): article_code.append(chr(result[i-2])) article_code.append(chr(result[i-1])) return str("".join(article_code)) else: return self.ARTICLE_CODE_ERROR def read_device_serial(self): result = [] device_serial = [] write = i2c_msg.write(self.SPS_ADDR, self.R_SERIAL_NUM) self.bus.i2c_rdwr(write) read = i2c_msg.read(self.SPS_ADDR, 48) self.bus.i2c_rdwr(read) for i in range(read.len): result.append(bytes_to_int(read.buf[i])) if checkCRC(result): for i in range(2, len(result), 3): device_serial.append(chr(result[i-2])) device_serial.append(chr(result[i-1])) return str("".join(device_serial)) else: return self.SERIAL_NUMBER_ERROR def read_auto_cleaning_interval(self): result = [] write = i2c_msg.write(self.SPS_ADDR, self.RW_AUTO_CLN) self.bus.i2c_rdwr(write) read = i2c_msg.read(self.SPS_ADDR, 6) self.bus.i2c_rdwr(read) for i in range(read.len): result.append(bytes_to_int(read.buf[i])) if checkCRC(result): result = result[0] * pow(2, 24) + result[1] * pow(2, 16) + result[3] * pow(2, 8) + result[4] return result else: return self.AUTO_CLN_INTERVAL_ERROR def set_auto_cleaning_interval(self, seconds): self.RW_AUTO_CLN.append((seconds >> 24) & 0xFF) self.RW_AUTO_CLN.append((seconds >> 16) & 0xFF) self.RW_AUTO_CLN.append(calculateCRC(self.RW_AUTO_CLN[2:4])) self.RW_AUTO_CLN.append((seconds >> 8) & 0xFF) self.RW_AUTO_CLN.append(seconds & 0xFF) self.RW_AUTO_CLN.append(calculateCRC(self.RW_AUTO_CLN[5:7])) write = i2c_msg.write(self.SPS_ADDR, self.RW_AUTO_CLN) self.bus.i2c_rdwr(write) def start_fan_cleaning(self): write = i2c_msg.write(self.SPS_ADDR, self.START_CLN) self.bus.i2c_rdwr(write) def start_measurement(self): self.START_MEAS.append(0x03) self.START_MEAS.append(0x00) crc = calculateCRC(self.START_MEAS[2:4]) self.START_MEAS.append(crc) write = i2c_msg.write(self.SPS_ADDR, self.START_MEAS) self.bus.i2c_rdwr(write) def stop_measurement(self): write = i2c_msg.write(self.SPS_ADDR, self.STOP_MEAS) self.bus.i2c_rdwr(write) def read_data_ready_flag(self): result = [] write = i2c_msg.write(self.SPS_ADDR, self.R_DATA_RDY) self.bus.i2c_rdwr(write) read = i2c_msg.read(self.SPS_ADDR, 3) self.bus.i2c_rdwr(read) for i in range(read.len): result.append(bytes_to_int(read.buf[i])) if checkCRC(result): return result[1] else: return self.DATA_READY_FLAG_ERROR def read_measured_values(self): result = [] write = i2c_msg.write(self.SPS_ADDR, self.R_VALUES) self.bus.i2c_rdwr(write) read = i2c_msg.read(self.SPS_ADDR, 60) self.bus.i2c_rdwr(read) for i in range(read.len): result.append(bytes_to_int(read.buf[i])) if checkCRC(result): self.parse_sensor_values(result) return self.NO_ERROR else: return self.MEASURED_VALUES_ERROR def device_reset(self): write = i2c_msg.write(self.SPS_ADDR, self.RESET) self.bus.i2c_rdwr(write) sleep(1) def parse_sensor_values(self, input): index = 0 pm_list = [] for i in range (4, len(input), 6): value = input[i] + input[i-1] * pow(2, 8) +input[i-3] * pow(2, 16) + input[i-4] * pow(2, 24) pm_list.append(value) for i in self.dict_values.keys(): self.dict_values[i] = convertPMValues(pm_list[index]) index += 1
def task(self): temp = 99999 humid = 99999 tdew = 99999 status = 0 try: bus = SMBus(I2CBUS) bus.write_byte( self.I2Caddr, HTU21D_READ_TEMP_NOHOLD ) time.sleep(0.05) read = i2c_msg.read( self.I2Caddr, 3 ) bus.i2c_rdwr(read) resp = list(read) if ( self.crc8(resp) == 0 ): status += 1 # data sheet 15/21 Temperature Conversion t = (resp[0] << 8) | (resp[1] & HTU21D_STATUS_LSBMASK) temp = ((175.72 * t) / 65536.0) - 46.86 self.temp_buff[self.temp_ptr] = temp self.temp_ptr += 1 if self.temp_ptr >= HTU21D_MAX: self.temp_ptr = 0 # runs first pass only if self.init_buff : for i in range(HTU21D_MAX): self.temp_buff[i] = temp # need to init humid_buff[] too # so don't clear self.init_buff flag here avg_temp = 0 for i in range(HTU21D_MAX): avg_temp += self.temp_buff[i] avg_temp /= HTU21D_MAX_FLOAT f = open(self.FptrTC,"w") f.write("%3.1f" % avg_temp) f.close() bus.write_byte( self.I2Caddr, HTU21D_READ_HUM_NOHOLD ) time.sleep(0.05) read = i2c_msg.read( self.I2Caddr, 3 ) bus.i2c_rdwr(read) resp = list(read) bus.close() if( self.crc8( resp ) == 0 ): status += 1 # data sheet 15/21 Relative Humidity h = (resp[0] << 8) | (resp[1] & HTU21D_STATUS_LSBMASK) humid = ((125.0 * h) / 65536.0) - 6.0 # limit for out of range values # per data sheet page 15/21 if humid < 0.0 : humid = 0.0 if humid > 100.0: humid = 100.0 # RH compensation per datasheet page: 4/12 # JUN 28, 2018 changed to avg_temp rh = humid + ( 25.0 - avg_temp) * -0.15 self.humid_buff[self.humid_ptr] = rh self.humid_ptr += 1 if self.humid_ptr >= HTU21D_MAX: self.humid_ptr = 0 # runs first pass only if self.init_buff : for i in range(HTU21D_MAX): self.humid_buff[i] = rh self.init_buff = 0 avg_humid = 0 for i in range(HTU21D_MAX): avg_humid += self.humid_buff[i] avg_humid /= HTU21D_MAX_FLOAT f = open(self.FptrRH,"w") f.write("%3.1f" % avg_humid) f.close() # with both valid temperature and RH # calculate the dew point and absolute humidity using average values if status > 1: # Calculate dew point # because log of zero is not possible if avg_humid < 0.1: avg_humid = 0.1 A = 8.1332 B = 1762.39 C = 235.66 # data sheet page 16/21 Partial Pressure & Dew Point pp = 10**( A - ( B / ( avg_temp + C ))) tdew = -(( B / ( log10( avg_humid * pp / 100.0) - A )) + C ) f = open(self.FptrTD,"w") f.write("%3.1f" % tdew) f.close() # Calculate absolute humidity in grams/M^3 e = 2.71828 a = 13.2473 b = e**((17.67 * avg_temp)/(avg_temp + 243.5)) c = 273.15 + avg_temp ah = a * b * avg_humid / c f = open(self.FptrAH,"w") f.write("%3.1f" % ah) f.close() #print "htu21d.task() T: %3.1f RH: %3.1f Tdew: %3.1f AH: %3.1f" % ( avg_temp, avg_humid, tdew, ah ) except: bus.close() print "htu21d.task() failed" # returning average values return [ temp, humid, tdew ]
class TrackBall(): def __init__(self, address=I2C_ADDRESS, i2c_bus=1, interrupt_pin=None, timeout=5): self._i2c_address = address self._i2c_bus = SMBus(i2c_bus) self._interrupt_pin = interrupt_pin self._timeout = timeout chip_id = struct.unpack("<H", bytearray(self.i2c_rdwr([REG_CHIP_ID_L], 2)))[0] if chip_id != CHIP_ID: raise RuntimeError("Invalid chip ID: 0x{:04X}, expected 0x{:04X}".format(chip_id, CHIP_ID)) if self._interrupt_pin is not None: GPIO.setwarnings(False) GPIO.setmode(GPIO.BCM) GPIO.setup(self._interrupt_pin, GPIO.IN, pull_up_down=GPIO.PUD_OFF) self.enable_interrupt() def change_address(self, new_address): """Write a new I2C address into flash.""" self.i2c_rdwr([REG_I2C_ADDR, new_address & 0xff]) self._wait_for_flash() def _wait_for_flash(self): t_start = time.time() while self.get_interrupt(): if time.time() - t_start > self._timeout: raise RuntimeError("Timed out waiting for interrupt!") time.sleep(0.001) t_start = time.time() while not self.get_interrupt(): if time.time() - t_start > self._timeout: raise RuntimeError("Timed out waiting for interrupt!") time.sleep(0.001) def enable_interrupt(self, interrupt=True): """Enable/disable GPIO interrupt pin.""" value = self.i2c_rdwr([REG_INT], 1)[0] value = value & ~MSK_INT_OUT_EN if interrupt: value = value | MSK_INT_OUT_EN self.i2c_rdwr([REG_INT, value]) def i2c_rdwr(self, data, length=0): """Write and optionally read I2C data.""" msg_w = i2c_msg.write(self._i2c_address, data) self._i2c_bus.i2c_rdwr(msg_w) if length > 0: time.sleep(0.02) msg_r = i2c_msg.read(self._i2c_address, length) self._i2c_bus.i2c_rdwr(msg_r) return list(msg_r) return [] def get_interrupt(self): """Get the trackball interrupt status.""" if self._interrupt_pin is not None: return GPIO.input(self._interrupt_pin) == 0 else: value = self.i2c_rdwr([REG_INT], 1)[0] return value & MSK_INT_TRIGGERED def set_rgbw(self, r, g, b, w): """Set all LED brightness as RGBW.""" self.i2c_rdwr([REG_LED_RED, r, g, b, w]) def set_red(self, value): """Set brightness of trackball red LED.""" self.i2c_rdwr([REG_LED_RED, value & 0xff]) def set_green(self, value): """Set brightness of trackball green LED.""" self.i2c_rdwr([REG_LED_GRN, value & 0xff]) def set_blue(self, value): """Set brightness of trackball blue LED.""" self.i2c_rdwr([REG_LED_BLU, value & 0xff]) def set_white(self, value): """Set brightness of trackball white LED.""" self.i2c_rdwr([REG_LED_WHT, value & 0xff]) def read(self): """Read up, down, left, right and switch data from trackball.""" left, right, up, down, switch = self.i2c_rdwr([REG_LEFT], 5) switch, switch_state = switch & ~MSK_SWITCH_STATE, (switch & MSK_SWITCH_STATE) > 0 return up, down, left, right, switch, switch_state
class LifeTester: # Todo: what if two instances are created with the same address? def __init__(self, addr): self.addr = addr self.bus = SMBus(I2C_BUS) def reset(self): """Resets both lifetester channels""" self._send_command(WRITE_CMD_REG) self._send_command(RESET_CH_A) self._poll_ready_state() self._send_command(WRITE_CMD_REG) self._send_command(RESET_CH_B) self._poll_ready_state() def set_params(self, new_params): """Sets the measurement parameters""" # note that polling doesn't work in this case self._send_command(WRITE_CMD_REG) self._send_command(WRITE_PARAMS) self._write_block_data(self._parse_bytes_from_params(new_params)) def get_params(self): """Gets the current measurement parameters""" self._send_command(WRITE_CMD_REG) self._poll_ready_state() self._send_command(READ_PARAMS) self._poll_ready_state() params_bytes = self._read_block_data(PARAMS_PACKET_SIZE) return self._parse_params_from_bytes(params_bytes) def get_error_code(self): """get the error code from the command register""" cmd = self._read_command_reg() return (cmd >> ERR_OFFSET) & ERR_MASK def get_data(self): """Reads and returns data from channel A and B""" self._send_command(WRITE_CMD_REG) self._poll_ready_state() self._send_command(READ_CH_A_DATA) self._poll_ready_state() data_block_a = self._read_block_data(DATA_PACKET_SIZE) packet_a = self._parse_data(data_block_a) self._send_command(WRITE_CMD_REG) self._poll_ready_state() self._send_command(READ_CH_B_DATA) self._poll_ready_state() data_block_b = self._read_block_data(DATA_PACKET_SIZE) packet_b = self._parse_data(data_block_b) return self._parse_measurement(packet_a, packet_b) def _bytes_to_int(self, b): return int.from_bytes(b, byteorder='little', signed=False) def _dequeue(self, l, n): # pops a list, l, of length n from another list return [l.pop(0) for e in range(n)] def _parse_data(self, b): # Todo: catch empty list exception - assert len(b)? tmp = [ self._bytes_to_int(self._dequeue(b, field)) for field in list(num_bytes) ] return DataPacket._make(tmp) def _parse_params_from_bytes(self, b): # Todo: catch empty list exception - assert len(b)? tmp = [ self._bytes_to_int(self._dequeue(b, BYTES_PER_PARAM)) for p in ParamsPacket._fields ] return ParamsPacket._make(tmp) def _parse_bytes_from_params(self, params): msb = lambda x: x >> (8 & 0xFF) lsb = lambda x: x & 0xFF return [f(p) for p in params for f in [lsb, msb]] def _send_command(self, cmd): sleep(POLL_DELAY) self.bus.write_byte_data(self.addr, cmd, 0) def _read_byte(self): return self.bus.read_byte_data(self.addr, 0) def _read_block_data(self, num_bytes): msg = i2c_msg.read(self.addr, num_bytes) self.bus.i2c_rdwr(msg) return list(msg) def _write_block_data(self, data): msg = i2c_msg.write(self.addr, data) self.bus.i2c_rdwr(msg) def _read_command_reg(self): self._send_command(READ_CMD_REG) return self._read_byte() def _is_ready(self): cmd_reg = self._read_command_reg() mask = 1 << RDY_OFFSET return (bool)(cmd_reg & mask) def _poll_ready_state(self): while not self._is_ready(): sleep(POLL_DELAY) def _convert_to_temp(self, reg): # Turn 16 bit wide register to temperature float return int(reg >> 3) * TEMP_CONVERSION def _parse_error_code(self, byte): if byte < len(ERROR_CODES): return ERROR_CODES[byte] else: return 'unkown' def _parse_measurement(self, a, b): measurement = Measurement(time=a.time, v_a=a.voltage * DAC_CONVERSION, i_a=a.current * ADC_CONVERSION, v_b=b.voltage * DAC_CONVERSION, i_b=b.voltage * ADC_CONVERSION, temperature=self._convert_to_temp( a.temperature), light_intensity=a.light_intensity, error_a=self._parse_error_code(a.error_code), error_b=self._parse_error_code(b.error_code)) return measurement
class UpbeatLabs_MCP39F521(object): """Class for communicating with an MCP39F521 device like Dr. Wattson using the python smbus library.""" class Error_code(Enum): SUCCESS = 0 ERROR_INCORRECT_HEADER = 1 ERROR_CHECKSUM_FAIL = 2 ERROR_UNEXPECTED_RESPONSE = 3 ERROR_INSUFFICIENT_ARRAY_SIZE = 4 ERROR_CHECKSUM_MISMATCH = 5 ERROR_SET_VALUE_MISMATCH = 6 ERROR_VALUE_OUT_OF_BOUNDS = 7 class Event_config(Enum): EVENT_OVERCUR_TST = 0 EVENT_OVERPOW_TST = 1 EVENT_VSAG_TST = 2 EVENT_VSUR_TST = 3 EVENT_OVERCUR_LA = 4 EVENT_OVERPOW_LA = 5 EVENT_VSAG_LA = 6 EVENT_VSUR_LA = 7 EVENT_VSAG_CL = 8 EVENT_VSUR_CL = 9 EVENT_OVERPOW_CL = 10 EVENT_OVERCUR_CL = 11 EVENT_MANUAL = 14 EVENT_VSAG_PIN = 16 EVENT_VSURGE_PIN = 17 EVENT_OVERCUR_PIN = 18 EVENT_OVERPOW_PIN = 19 class System_status(Enum): SYSTEM_VSAG = 0 SYSTEM_VSURGE = 1 SYSTEM_OVERCUR = 2 SYSTEM_OVERPOW = 3 SYSTEM_SIGN_PA = 4 SYSTEM_SIGN_PR = 5 SYSTEM_EVENT = 10 class calibration_config(Enum): CALIBRATION_CONFIG_4A = 0 CALIBRATION_CONFIG_10A = 1 # 30 ohm burden resistor x2 CALIBRATION_CONFIG_15A = 2 # 20 ohm burden resistor x2 class __Response_code(Enum): RESPONSE_ACK = 0x06 RESPONSE_NAK = 0x15 RESPONSE_CSFAIL = 0x51 class __Command_code(Enum): COMMAND_REGISTER_READ_N_BYTES = 0x4e COMMAND_REGISTER_WRITE_N_BYTES = 0x4d COMMAND_SET_ADDRESS_POINTER = 0x41 COMMAND_SAVE_TO_FLASH = 0x53 COMMAND_PAGE_READ_EEPROM = 0x42 COMMAND_PAGE_WRITE_EEPROM = 0x50 COMMAND_BULK_ERASE_EEPROM = 0x4f COMMAND_AUTO_CALIBRATE_GAIN = 0x5a COMMAND_AUTO_CALIBRATE_REACTIVE_GAIN = 0x7a COMMAND_AUTO_CALIBRATE_FREQUENCY = 0x76 ## There is a bug in current MCP39F511/521 where energy accumulation ## values are off if the energy accumulation interval is ## anything but 2. This applies the workaround for that problem. ## To be removed for chips that have the issue fixed. ## XXX : TODO def __init__(self, address=MCP39F521_I2CADDR, busnum=DEFAULT_BUSNUM): """Create an instance of the MCP39F521 device at the specified address on the specified I2C bus number.""" self._address = address self._bus = SMBus(busnum) self._logger = logging.getLogger( 'DrWattson.UpbeatLabs_MCP39F521.Bus.{0}.Address.{1:#0X}'.format( busnum, address)) self._energy_accum_correction_factor = 1 (retVal, enabled) = self.isEnergyAccumulationEnabled() if (retVal == self.Error_code.SUCCESS.value and enabled): (retVal, accumIntervalReg) = self.readAccumulationIntervalRegister() self._energy_accum_correction_factor = (accumIntervalReg - 2) ## Get energy related data from the module ## ## Parameters: ## UpbeatLabs_MCP39F521_Data (output) - Metering data ## def readEnergyData(self): (retVal, buf) = self.__registerReadNBytes(0x00, 0x02, 28) data = UpbeatLabs_MCP39F521_Data() if (retVal == self.Error_code.SUCCESS.value): data.systemStatus = (buf[3] << 8 | buf[2]) data.systemVersion = (buf[5] << 8 | buf[4]) data.voltageRMS = (buf[7] << 8 | buf[6]) / 10.0 data.lineFrequency = (buf[9] << 8 | buf[8]) / 1000.0 data.analogInputVoltage = (buf[11] << 8 | buf[10]) / 1023.0 * 3.3 pfRaw = buf[13] << 8 | buf[12] f = ((pfRaw & 0x8000) >> 15) * -1.0 for ch in range(14, 3, -1): f += ((pfRaw & (1 << ch)) >> ch) * 1.0 / (1 << (15 - ch)) data.powerFactor = f data.currentRMS = (buf[17] << 24 | buf[16] << 16 | buf[15] << 8 | buf[14]) / 10000.0 data.activePower = (buf[21] << 24 | buf[20] << 16 | buf[19] << 8 | buf[18]) / 100.0 data.reactivePower = (buf[25] << 24 | buf[24] << 16 | buf[23] << 8 | buf[22]) / 100.0 data.apparentPower = (buf[29] << 24 | buf[28] << 16 | buf[27] << 8 | buf[26]) / 100.0 return (retVal, data) ## Get energy accumulator data from the module ## ## Parameters: ## UpbeatLabs_MCP39F521_AccumData (output) - accumulator data for energy ## Notes: ## On Arduino cannot read more than 32 bytes on I2C ## Let's just stick to that limit! ## Splitting out activeEnergyImport, activeEnergyExport and ## reactiveEnergyImport, reactiveEnergyExport into two calls ## as the total is 32+3 = 35 bytes otherwise. def readEnergyAccumData(self): (retVal, buf) = self.__registerReadNBytes(0x00, 0x1e, 16) data = UpbeatLabs_MCP39F521_AccumData() if (retVal == self.Error_code.SUCCESS.value): if (self._energy_accum_correction_factor == -1): data.activeEnergyImport = ( ((buf[9]) << 56 | (buf[8]) << 48 | (buf[7]) << 40 | (buf[6]) << 32 | (buf[5]) << 24 | (buf[4]) << 16 | (buf[3]) << 8 | buf[2]) / 2) / 1000.0 data.activeEnergyExport = ( ((buf[17]) << 56 | (buf[16]) << 48 | (buf[15]) << 40 | (buf[14]) << 32 | (buf[13]) << 24 | (buf[12]) << 16 | (buf[11]) << 8 | buf[10]) / 2) / 1000.0 else: data.activeEnergyImport = ( ((buf[9]) << 56 | (buf[8]) << 48 | (buf[7]) << 40 | (buf[6]) << 32 | (buf[5]) << 24 | (buf[4]) << 16 | (buf[3]) << 8 | buf[2]) * (1 << self._energy_accum_correction_factor)) / 1000.0 data.activeEnergyExport = ( ((buf[17]) << 56 | (buf[16]) << 48 | (buf[15]) << 40 | (buf[14]) << 32 | (buf[13]) << 24 | (buf[12]) << 16 | (buf[11]) << 8 | buf[10]) * (1 << self._energy_accum_correction_factor)) / 1000.0 time.sleep(0.05) (retVal, buf) = self.__registerReadNBytes(0x00, 0x2e, 16) if (retVal == self.Error_code.SUCCESS.value): if (self._energy_accum_correction_factor == -1): data.reactiveEnergyImport = ( ((buf[9]) << 56 | (buf[8]) << 48 | (buf[7]) << 40 | (buf[6]) << 32 | (buf[5]) << 24 | (buf[4]) << 16 | (buf[3]) << 8 | buf[2]) / 2) / 1000.0 data.reactiveEnergyExport = ( ((buf[17]) << 56 | (buf[16]) << 48 | (buf[15]) << 40 | (buf[14]) << 32 | (buf[13]) << 24 | (buf[12]) << 16 | (buf[11]) << 8 | buf[10]) / 2) / 1000.0 else: data.reactiveEnergyImport = ( ((buf[9]) << 56 | (buf[8]) << 48 | (buf[7]) << 40 | (buf[6]) << 32 | (buf[5]) << 24 | (buf[4]) << 16 | (buf[3]) << 8 | buf[2]) * (1 << self._energy_accum_correction_factor)) / 1000.0 data.reactiveEnergyExport = ( ((buf[17]) << 56 | (buf[16]) << 48 | (buf[15]) << 40 | (buf[14]) << 32 | (buf[13]) << 24 | (buf[12]) << 16 | (buf[11]) << 8 | buf[10]) * (1 << self._energy_accum_correction_factor)) / 1000.0 return (retVal, data) # Event control methods ## Reads the event configuration and returns the 32-bit bit map. ## Use the event_config enum to read bits of interest, without ## worrying about the bit position and structure of the ## event config register ## ## For example, bitRead(eventConfigRegisterValue, EVENT_VSAG_PIN) ## to see if event notification for VSAG events is turned on def readEventConfigRegister(self): (retVal, buf) = self.__registerReadNBytes(0x00, 0x7e, 4) configRegister = 0 if (retVal == self.Error_code.SUCCESS.value): configRegister = buf[5] << 24 | buf[4] << 16 | buf[3] << 8 | buf[2] return (retVal, configRegister) ## Set the event configuration register to the appropriate value ## ## First, read the existing register value. Then, set (or clear) appropriate ## bits using the event_config enum for assitance. Lastly, set the ## new value back in the register. ## ## For example, bitSet(eventConfigRegisterValue, EVENT_VSAG_PIN) ## to turn on the event notification for VSAG events ## or bitClear(eventConfigRegisterValue, EVENT_VSAG_PIN) def setEventConfigurationRegister(self, value): byteArray = [] byteArray.append(value & 0xFF) byteArray.append((value >> 8) & 0xFF) byteArray.append((value >> 16) & 0xFF) byteArray.append((value >> 24) & 0xFF) retVal = self.__registerWriteNBytes(0x00, 0x7e, 4, byteArray) if (retVal != self.Error_code.SUCCESS.value): return retVal (retVal, readArray) = self.__registerReadNBytes(0x00, 0x7e, 4) if (retVal == self.Error_code.SUCCESS.value): readValue = readArray[5] << 24 | readArray[4] << 16 | readArray[ 3] << 8 | readArray[2] if (readValue != value): return self.Error_code.ERROR_SET_VALUE_MISMATCH.value return self.Error_code.SUCCESS.value ## Read the event flag limits that have been set for the various events. ## For example, the voltage sag limit sets the voltage value below which ## the VSAG event is triggered ## ## See UpbeatLabs_MCP39F521_EventFlagLimits for more information about ## various limits def readEventFlagLimitRegisters(self): (retVal, buf) = self.__registerReadNBytes(0x00, 0xA0, 12) data = UpbeatLabs_MCP39F521_EventFlagLimits() if (retVal == self.Error_code.SUCCESS.value): data.voltageSagLimit = (buf[3] << 8 | buf[2]) data.voltageSurgeLimit = (buf[5] << 8 | buf[4]) data.overCurrentLimit = (buf[9] << 24 | buf[8] << 16 | buf[7] << 8 | buf[6]) data.overPowerLimit = (buf[13] << 24 | buf[12] << 16 | buf[11] << 8 | buf[10]) return (retVal, data) ## Write the event flag limits for the various events. ## For example, the voltage sag limit sets the voltage value below which ## the VSAG event is triggered ## ## See UpbeatLabs_MCP39F521_EventFlagLimits for more information about ## various limits def writeEventFlagLimitRegisters(self, input): byteArray = [] byteArray.append(input.voltageSagLimit & 0xFF) byteArray.append((input.voltageSagLimit >> 8) & 0xFF) byteArray.append(input.voltageSurgeLimit & 0xFF) byteArray.append((input.voltageSurgeLimit >> 8) & 0xFF) byteArray.append(input.overCurrentLimit & 0xFF) byteArray.append((input.overCurrentLimit >> 8) & 0xFF) byteArray.append((input.overCurrentLimit >> 16) & 0xFF) byteArray.append((input.overCurrentLimit >> 24) & 0xFF) byteArray.append(input.overPowerLimit & 0xFF) byteArray.append((input.overPowerLimit >> 8) & 0xFF) byteArray.append((input.overPowerLimit >> 16) & 0xFF) byteArray.append((input.overPowerLimit >> 24) & 0xFF) retVal = self.__registerWriteNBytes(0x00, 0xA0, 12, byteArray) if (retVal != self.Error_code.SUCCESS.value): return retVal (retVal, eventFlagLimitsData) = self.readEventFlagLimitRegisters() if (retVal == self.Error_code.SUCCESS.value): ## Verify read values with input values if (eventFlagLimitsData.voltageSagLimit != input.voltageSagLimit or eventFlagLimitsData.voltageSurgeLimit != input.voltageSurgeLimit or eventFlagLimitsData.overCurrentLimit != input.overCurrentLimit or eventFlagLimitsData.overPowerLimit != input.overPowerLimit): return self.Error_code.ERROR_SET_VALUE_MISMATCH.value return retVal # EEPROM methods ## Bulk erase all pages of the EEPROM memory def bulkEraseEEPROM(self): return self.__issueAckNackCommand( self.__Command_code.COMMAND_BULK_ERASE_EEPROM.value) def pageReadEEPROM(self, pageNum): """Implementation of pageReadEEPROM """ data = [ 0xa5, 0x05, self.__Command_code.COMMAND_PAGE_READ_EEPROM.value, pageNum ] checksum = 0 for x in data: checksum += x data.append(checksum % 256) write = i2c_msg.write(self._address, data) self._bus.i2c_rdwr(write) time.sleep(0.05) ## ## Read the specified length of data - ACK, Num Bytes, EEPROM Page Data, Checksum ## -> 1 + 1 + 16 + 1 = 19 bytes of data ## read = i2c_msg.read(self._address, 19) self._bus.i2c_rdwr(read) buf = [] buf.extend(read) return (self.__checkHeaderAndChecksum(16, buf), buf[2:-1]) def pageWriteEEPROM(self, pageNum, byteArray): """Implementation of pageWriteEEPROM """ if (len(byteArray) != 16): return self.Error_code.ERROR_INSUFFICIENT_ARRAY_SIZE.value data = [ 0xa5, 21, self.__Command_code.COMMAND_PAGE_WRITE_EEPROM.value, pageNum ] # Data here... data.extend(byteArray) checksum = 0 for x in data: checksum += x data.append(checksum % 256) write = i2c_msg.write(self._address, data) self._bus.i2c_rdwr(write) time.sleep(0.05) header = self._bus.read_byte(self._address) self._logger.debug(header) return self.__checkHeader(header) # Energy Accumulation methods ## This method is used to turn on/off energy accumulation. ## When it is turned on, the data read from the module ## in UpbeatLabs_MCP39F521_AccumData represents the ## accumulated energy data over the no load threshold ## (defaults to 1w). Therefore any energy over 1w ## gets accumulated over time. ## There is a bug in current MCP39F511/521 where energy accumulation ## values are off if the energy accumulation interval is ## anything but 2. This applies the workaround for that problem. ## To be removed for chips that have the issue fixed. def enableEnergyAccumulation(self, enable): ## First, note the accumulation interval. If it is anything ## other than the default (2), note the correction ## factor that has to be applied to the energy ## accumulation. (retVal, accumIntervalReg) = self.readAccumulationIntervalRegister() self._energy_accum_correction_factor = (accumIntervalReg - 2) byteArray = [enable, 0] ## write register retVal = self.__registerWriteNBytes(0x00, 0xDC, 2, byteArray) return retVal def isEnergyAccumulationEnabled(self): (retVal, readArray) = self.__registerReadNBytes(0x00, 0xDC, 5) enabled = False if (retVal == self.Error_code.SUCCESS.value): enabled = readArray[2] return (retVal, enabled) # <---- End Energy Accumulation methods # START --- WARNING!!! WARNING!!! WARNING!!! # Advanced methods for calibration, etc # WARNING!!!! Use with extreme caution! These can render your Dr. Wattson # uncalibrated. Only use if you know what you are doing! ## Read the contents of the calibration registers ## Results returned in UpbeatLabs_MCP39F521_CalibrationData object def readCalibrationRegisters(self): (retVal, buf) = self.__registerReadNBytes(0x00, 0x5e, 28) data = UpbeatLabs_MCP39F521_CalibrationData() if (retVal == self.Error_code.SUCCESS.value): data.calibrationRegisterDelimiter = (buf[3] << 8 | buf[2]) data.gainCurrentRMS = (buf[5] << 8 | buf[4]) data.gainVoltageRMS = (buf[7] << 8 | buf[6]) data.gainActivePower = (buf[9] << 8 | buf[8]) data.gainReactivePower = (buf[11] << 8 | buf[10]) data.offsetCurrentRMS = (buf[15] << 24 | buf[14] << 16 | buf[13] << 8 | buf[12]) data.offsetActivePower = (buf[19] << 24 | buf[18] << 16 | buf[17] << 8 | buf[16]) data.offsetReactivePower = (buf[23] << 24 | buf[22] << 16 | buf[21] << 8 | buf[20]) data.dcOffsetCurrent = (buf[25] << 8 | buf[24]) data.phaseCompensation = (buf[27] << 8 | buf[26]) data.apparentPowerDivisor = (buf[29] << 8 | buf[28]) return (retVal, data) ## This method writes the current, voltage, active power and reactive power gains directly to ## the MCP39F521 registers. Use this if you know what the appropriate gains are to be for ## your particular metering range and design. Typically, these are not to be changed unless ## you are performing your own calibration, and even so, it is better to use the ## auto-calibration methods instead. This is one big gun to shoot yourself, be warned! def writeGains(self, gainCurrentRMS, gainVoltageRMS, gainActivePower, gainReactivePower): byteArray = [] byteArray.append(gainCurrentRMS & 0xFF) byteArray.append((gainCurrentRMS >> 8) & 0xFF) byteArray.append(gainVoltageRMS & 0xFF) byteArray.append((gainVoltageRMS >> 8) & 0xFF) byteArray.append(gainActivePower & 0xFF) byteArray.append((gainActivePower >> 8) & 0xFF) byteArray.append(gainReactivePower & 0xFF) byteArray.append((gainReactivePower >> 8) & 0xFF) retVal = self.__registerWriteNBytes(0x00, 0x60, 8, byteArray) return retVal ## Read the system config register, which is a 32-bit bit map. ## The system config register is used to set setting like ## PGA gains for current and voltage channels, etc ## You will typically not be changing these unless you are ## performing a calibration. def readSystemConfigRegister(self): (retVal, buf) = self.__registerReadNBytes(0x00, 0x7a, 4) value = 0 if (retVal == self.Error_code.SUCCESS.value): value = buf[5] << 24 | buf[4] << 16 | buf[3] << 8 | buf[2] return (retVal, value) ## Set the system config register, which is a 32-bit bit map. ## The system config register is used to set setting like ## PGA gains for current and voltage channels, etc ## You will typically not be changing these unless you are ## performing a calibration. Do not use unless you know what ## you are doing! This is one big gun to shoot yourself, be warned! def setSystemConfigurationRegister(self, value): retVal = 0 byteArray = [0] * 4 byteArray[0] = value & 0xFF byteArray[1] = (value >> 8) & 0xFF byteArray[2] = (value >> 16) & 0xFF byteArray[3] = (value >> 24) & 0xFF # print byteArray retVal = self.__registerWriteNBytes(0x00, 0x7a, 4, byteArray) if (retVal != self.Error_code.SUCCESS.value): return retVal time.sleep(0.05) (retVal, readArray) = self.__registerReadNBytes(0x00, 0x7a, 4) readValue = ((readArray[5]) << 24 | (readArray[4]) << 16 | (readArray[3]) << 8 | readArray[2]) if (readValue != value): return self.Error_code.ERROR_SET_VALUE_MISMATCH.value return self.Error_code.SUCCESS.value ## Read the accumlation interval register, which represents N in 2^N ## number of line cycles to be used for a single computation. ## You will not be modifying this unless you are performing a ## calibration. def readAccumulationIntervalRegister(self): (retVal, buf) = self.__registerReadNBytes(0x00, 0x9e, 2) value = 0 if (retVal == self.Error_code.SUCCESS.value): value = buf[3] << 8 | buf[2] return (retVal, value) ## Set the accumlation interval register, which represents N in 2^N ## number of line cycles to be used for a single computation. ## You will not be modifying this unless you are performing a ## calibration. Use with caution!! def setAccumulationIntervalRegister(self, value): retVal = 0 byteArray = [0] * 2 byteArray[0] = value & 0xFF byteArray[1] = (value >> 8) & 0xFF retVal = self.__registerWriteNBytes(0x00, 0x9e, 2, byteArray) if (retVal != self.Error_code.SUCCESS.value): return retVal time.sleep(0.05) (retVal, readArray) = self.__registerReadNBytes(0x00, 0x9e, 2) readValue = ((readArray[3]) << 8 | readArray[2]) if (readValue != value): return self.Error_code.ERROR_SET_VALUE_MISMATCH.value return self.Error_code.SUCCESS.value ## Read the design config registers into the UpbeatLabs_MCP39F521_DesignConfigData struct. ## See class for more details. These are used to set the appropriate calibration values for ## calibrating your module. def readDesignConfigurationRegisters(self): (retVal, buf) = self.__registerReadNBytes(0x00, 0x82, 20) data = UpbeatLabs_MCP39F521_DesignConfigData() if (retVal == self.Error_code.SUCCESS.value): data.rangeVoltage = buf[2] data.rangeCurrent = buf[3] data.rangePower = buf[4] data.rangeUnimplemented = buf[5] data.calibrationCurrent = (buf[9] << 24 | buf[8] << 16 | buf[7] << 8 | buf[6]) data.calibrationVoltage = ((buf[11] << 8) | buf[10]) data.calibrationPowerActive = (buf[15] << 24 | buf[14] << 16 | buf[13] << 8 | buf[12]) data.calibrationPowerReactive = (buf[19] << 24 | buf[18] << 16 | buf[17] << 8 | buf[16]) data.lineFrequencyRef = ((buf[21] << 8) | buf[20]) return (retVal, data) ## Write the design config registers. See UpbeatLabs_MCP39F521_DesignConfigData ## struct for more details. These are used to set the appropriate calibration values for ## calibrating your module. Use this method only if you know what you are doing! ## This is one big gun to shoot yourself, be warned! def writeDesignConfigRegisters(self, data): byteArray = [] * 20 ## range byteArray[0] = data.rangeVoltage byteArray[1] = data.rangeCurrent byteArray[2] = data.rangePower byteArray[3] = data.rangeUnimplemented ## calibration current byteArray[4] = data.calibrationCurrent & 0xFF byteArray[5] = (data.calibrationCurrent >> 8) & 0xFF byteArray[6] = (data.calibrationCurrent >> 16) & 0xFF byteArray[7] = (data.calibrationCurrent >> 24) & 0xFF ## calibration voltage byteArray[8] = data.calibrationVoltage & 0xFF byteArray[9] = (data.calibrationVoltage >> 8) & 0xFF ## calibration power active byteArray[10] = data.calibrationPowerActive & 0xFF byteArray[11] = (data.calibrationPowerActive >> 8) & 0xFF byteArray[12] = (data.calibrationPowerActive >> 16) & 0xFF byteArray[13] = (data.calibrationPowerActive >> 24) & 0xFF ## calibration power reactive byteArray[14] = data.calibrationPowerReactive & 0xFF byteArray[15] = (data.calibrationPowerReactive >> 8) & 0xFF byteArray[16] = (data.calibrationPowerReactive >> 16) & 0xFF byteArray[17] = (data.calibrationPowerReactive >> 24) & 0xFF ## line frequency ref byteArray[18] = data.lineFrequencyRef & 0xFF byteArray[19] = (data.lineFrequencyRef >> 8) & 0xFF retVal = self.__registerWriteNBytes(0x00, 0x82, 20, byteArray) if (retVal != self.Error_code.SUCCESS.value): return retVal time.sleep(0.05) ## Read the values to verify write (retVal, designConfigData) = self.readDesignConfigurationRegisters() if (retVal != self.Error_code.SUCCESS.value): return retVal ## Verify read values with input values if (designConfigData.rangeVoltage != data.rangeVoltage or designConfigData.rangeCurrent != data.rangeCurrent or designConfigData.rangePower != data.rangePower or designConfigData.calibrationCurrent != data.calibrationCurrent or designConfigData.calibrationVoltage != data.calibrationVoltage or designConfigData.calibrationPowerActive != data.calibrationPowerActive or designConfigData.calibrationPowerReactive != data.calibrationPowerReactive or designConfigData.lineFrequencyRef != data.lineFrequencyRef): return self.Error_code.ERROR_SET_VALUE_MISMATCH.value return self.Error_code.SUCCESS.value ## Write the phase compensation register. This will not be required unless ## you are manually writing calibration values yourself. Use with caution! def writePhaseCompensation(self, phaseCompensation): byteArray = [] * 2 ## Calibrating phase byteArray[0] = phaseCompensation byteArray[1] = 0 retVal = self.__registerWriteNBytes(0x00, 0x76, 2, byteArray) return retVal ## Read and set the ambient reference temperature when ## calibrating. This is used during calibration as one ## of the steps. Use with caution! def readAndSetTemperature(self): (retVal, byteArray) = __registerReadNBytes(0x00, 0x0a, 2) if (retVal != self.Error_code.SUCCESS.value): return retVal bytesWrite = [] * 2 bytesWrite[0] = byteArray[2] bytesWrite[1] = byteArray[3] retVal = self.__registerWriteNBytes(0x00, 0xcc, 2, bytesWrite) return retVal ## Invoke the "autoCalibrate Gain" command. Prior to this, ## other requisite steps need to be taken like setting ## the design config registers with the appropriate ## calibration values. Use only if you know what you are ## doing! This is one big gun to shoot yourself, be warned! def autoCalibrateGain(self): return self.__issueAckNackCommand( self.__Command_code.COMMAND_AUTO_CALIBRATE_GAIN.value) ## Invoke the "autoCalibrate Reactive Gain" command. Prior to this, ## other requisite steps need to be taken like setting ## the design config registers with the appropriate ## calibration values, and auto calibrating gain. ## Use only if you know what you are doing! ## This is one big gun to shoot yourself, be warned! def autoCalibrateReactiveGain(self): return self.__issueAckNackCommand( self.__Command_code.COMMAND_AUTO_CALIBRATE_REACTIVE_GAIN.value) ## Invoke the "autoCalibrate Line Frequency" command. Prior to this, ## other requisite calibration steps need to be taken like setting ## the appropriate design config registers. ## Use only if you know what you are doing! ## This is one big gun to shoot yourself, be warned! def autoCalibrateFrequency(self): return self.__issueAckNackCommand( self.__Command_code.COMMAND_AUTO_CALIBRATE_FREQUENCY.value) ## This method is used to calibrate the phase compensation ## when calibrating the module, as one of the steps during ## system calibration. Use only if you know what you are doing! ## This is one big gun to shoot yourself, be warned! def calibratePhase(self, pfExp): (retVal, byteArray) = self.__registerReadNBytes(0x00, 0x0c, 2) if (retVal != self.Error_code.SUCCESS.value): return retVal pfRaw = buf[13] << 8 | buf[12] f = ((pfRaw & 0x8000) >> 15) * -1.0 for ch in range(14, 3, -1): f += ((pfRaw & (1 << ch)) >> ch) * 1.0 / (1 << (15 - ch)) pfMeasured = f angleMeasured = acos(pfMeasured) angleExp = acos(pfExp) angleMeasuredDeg = angleMeasured * 180.0 / 3.14159 angleExpDeg = angleExp * 180.0 / 3.14159 phi = (angleMeasuredDeg - angleExpDeg) * 40.0 (retVal, byteArray) = self.__registerReadNBytes(0x00, 0x76, 2) if (retVal != self.Error_code.SUCCESS.value): return retVal # phase compensation is stored as an 8-bit 2's complement number # (in a 16 bit register) pcRaw = byteArray[2] if pcRaw > 127: pcRaw = pcRaw - 256 phaseComp = pcRaw phaseCompNew = phaseComp + phi # New value has to be between 127 and -128 and has to be converted # back to an 8 bit integer for transmission if (phaseCompNew > 127 or phaseCompNew < -128): retVal = self.Error_code.ERROR_VALUE_OUT_OF_BOUNDS.value else: ## Calibrating phase bytes = [0] * 2 # converting back to 8 bit unsigned integer representation of # two's complement value bytes[0] = phaseCompNew if (phaseCompNew >= 0) else (256 + phaseCompNew) bytes[1] = 0 retVal = self.__registerWriteNBytes(0x00, 0x76, 2, bytes) return retVal ## This method saves the contents of all calibration ## and configuration registers to flash. Use with caution! def saveToFlash(self): return self.__issueAckNackCommand( self.__Command_code.COMMAND_SAVE_TO_FLASH.value) # This will revert the MCP39F521 to its factory settings and # remove any calibration data. Use with extreme caution!!!! def factoryReset(self): byteArray = [0] * 2 byteArray[0] = 0xa5 byteArray[1] = 0xa5 retVal = self.__registerWriteNBytes(0x00, 0x5e, 2, byteArray) if (retVal != self.Error_code.SUCCESS.value): return retVal retVal = self.saveToFlash() return retVal ## This method will reset the calibration values to Dr. Wattson def resetCalibration(self, cc=calibration_config.CALIBRATION_CONFIG_4A.value): global calibConfig retVal = self.Error_code.SUCCESS.value retVal = self.setSystemConfigurationRegister( calibConfig[cc].systemConfig) # Channel 1 Gain 4, Channel 0 Gain 1 if (retVal != self.Error_code.SUCCESS.value): return retVal retVal = self.setAccumulationIntervalRegister( calibConfig[cc].accumInt) # Accumulation interval 5 if (retVal != self.Error_code.SUCCESS.value): return retVal ## We need to apply correction factor where accumulation interval is not 2; if (calibConfig[cc].accumInt > 2): self._energy_accum_correction_factor = (calibConfig[cc].accumInt - 2) retVal = self.writeDesignConfigRegisters( calibConfig[cc].designConfigData) if (retVal != self.Error_code.SUCCESS.value): return retVal retVal = self.writeGains(calibConfig[cc].gainCurrentRMS, calibConfig[cc].gainVoltageRMS, calibConfig[cc].gainActivePower, calibConfig[cc].gainReactivePower) if (retVal != self.Error_code.SUCCESS.value): return retVal retVal = self.writePhaseCompensation(calibConfig[cc].phaseCompensation) if (retVal != self.Error_code.SUCCESS.value): return retVal retVal = self.saveToFlash() if (retVal != self.Error_code.SUCCESS.value): return retVal return self.Error_code.SUCCESS.value # END --- WARNING!!! WARNING!!! WARNING!!! ## Bit manipulation convenience methods # Check to see if the kth bit is set in value n # (where k starts with 0 for the least significant bit def bitRead(self, n, k): return n & (1 << k) # Set the kth bit in value n # (where k starts with 0 for the least significant bit def bitSet(self, n, k): return n | (1 << k) # Clear the kth bit in value n # (where k starts with 0 for the least significant bit def bitClear(self, n, k): return n ^ (1 << k) ## Private methods --- ## Read the contents of the registers starting with the starting address, ## up to the number of bytes specified. def __registerReadNBytes(self, addressHigh, addressLow, numBytesToRead): """Implementation of RegisterReadNBytes """ data = [ 0xa5, 0x08, self.__Command_code.COMMAND_SET_ADDRESS_POINTER.value, addressHigh, addressLow, self.__Command_code.COMMAND_REGISTER_READ_N_BYTES.value, numBytesToRead ] checksum = 0 for x in data: checksum += x # Add checksum at the end data.append(checksum % 256) write = i2c_msg.write(self._address, data) self._bus.i2c_rdwr(write) time.sleep(0.05) read = i2c_msg.read(self._address, numBytesToRead + 3) self._bus.i2c_rdwr(read) buf = [] buf.extend(read) # print buf return (self.__checkHeaderAndChecksum(numBytesToRead, buf), buf) ## Write to the registers, starting from the starting address the number of bytes ## specified in the byteArray def __registerWriteNBytes(self, addressHigh, addressLow, numBytes, byteArray): """Implementation of registerWriteNBytes """ data = [ 0xa5, numBytes + 8, self.__Command_code.COMMAND_SET_ADDRESS_POINTER.value, addressHigh, addressLow, self.__Command_code.COMMAND_REGISTER_WRITE_N_BYTES.value, numBytes ] ## data here data.extend(byteArray) # print data ## compute and fill checksum as last element checksum = 0 for x in data: checksum += x data.append(checksum % 256) write = i2c_msg.write(self._address, data) self._bus.i2c_rdwr(write) time.sleep(0.05) header = self._bus.read_byte(self._address) self._logger.debug(header) return self.__checkHeader(header) ## Some commands are issued and just return an ACK (or NAK) ## This method factors out those types of commands ## and takes in as argument the specified command to issue. def __issueAckNackCommand(self, command): """Implementation of issueAckNackCommand """ # header, numBytes, command data = [0xa5, 0x04, command] checksum = 0 for x in data: checksum += x # Add checksum at the end data.append(checksum % 256) write = i2c_msg.write(self._address, data) self._bus.i2c_rdwr(write) time.sleep(0.05) ## Read the ack header = self._bus.read_byte(self._address) self._logger.debug(header) return self.__checkHeader(header) ## Convenience method to check the header and the checksum for the returned data. ## If all is good, this method should return SUCCESS def __checkHeaderAndChecksum(self, numBytesToRead, byteArray): """Implementation of checkHeaderAndChecksum """ checksumTotal = 0 header = byteArray[0] dataLen = byteArray[1] checksum = byteArray[numBytesToRead + 3 - 1] for i in range(0, numBytesToRead + 3 - 1): checksumTotal += byteArray[i] calculatedChecksum = checksumTotal % 256 error = self.Error_code.SUCCESS.value error = self.__checkHeader(header) if (calculatedChecksum != checksum): error = self.Error_code.ERROR_CHECKSUM_MISMATCH.value return error ## Convenience method to check the header of the response. ## If all is good, this will return SUCCESS def __checkHeader(self, header): """Implementation of checkHeader """ error = self.Error_code.SUCCESS.value if (header != self.__Response_code.RESPONSE_ACK.value): error = self.Error_code.ERROR_INCORRECT_HEADER.value if (header == self.__Response_code.RESPONSE_CSFAIL.value): error = self.Error_code.ERROR_CHECKSUM_FAIL.value return error
class SGP30: def __init__(self, i2c_dev=None, i2c_msg=None, i2c_addr=SGP30_I2C_ADDR): """Mapping table of SGP30 commands. Friendly-name, followed by 16-bit command, then the number of parameter and response words. Each word is two bytes followed by a third CRC checksum byte. So a response length of 2 would result in the transmission of 6 bytes total. """ self.commands = { 'init_air_quality': (0x2003, 0, 0), 'measure_air_quality': (0x2008, 0, 2), 'get_baseline': (0x2015, 0, 2), 'set_baseline': (0x201e, 2, 0), 'set_humidity': (0x2061, 1, 0), # 'measure_test': (0x2032, 0, 1), # Production verification only 'get_feature_set_version': (0x202f, 0, 1), 'measure_raw_signals': (0x2050, 0, 2), 'get_serial_id': (0x3682, 0, 3) } self._i2c_addr = i2c_addr self._i2c_dev = i2c_dev self._i2c_msg = i2c_msg if self._i2c_dev is None: from smbus2 import SMBus, i2c_msg self._i2c_msg = i2c_msg self._i2c_dev = SMBus(1) def command(self, command_name, parameters=None): if parameters is None: parameters = [] parameters = list(parameters) cmd, param_len, response_len = self.commands[command_name] if len(parameters) != param_len: raise ValueError("{} requires {} parameters. {} supplied!".format( command_name, param_len, len(parameters))) parameters_out = [cmd] for i in range(len(parameters)): parameters_out.append(parameters[i]) parameters_out.append(self.calculate_crc(parameters[i])) data_out = struct.pack('>H' + ('HB' * param_len), *parameters_out) msg_w = self._i2c_msg.write(self._i2c_addr, data_out) self._i2c_dev.i2c_rdwr(msg_w) time.sleep(0.025) # Suitable for all commands except 'measure_test' if response_len > 0: # Each parameter is a word (2 bytes) followed by a CRC (1 byte) msg_r = self._i2c_msg.read(self._i2c_addr, response_len * 3) self._i2c_dev.i2c_rdwr(msg_r) buf = msg_r.buf[0:response_len * 3] response = struct.unpack('>' + ('HB' * response_len), buf) verified = [] for i in range(response_len): offset = i * 2 value, crc = response[offset:offset + 2] if crc != self.calculate_crc(value): raise RuntimeError( "Invalid CRC in response from SGP30: {:02x} != {:02x}", crc, self.calculate_crc(value), buf) verified.append(value) return verified def calculate_crc(self, data): """Calculate an 8-bit CRC from a 16-bit word Defined in section 6.6 of the SGP30 datasheet. Polynominal: 0x31 (x8 + x5 + x4 + x1) Initialization: 0xFF Reflect input/output: False Final XOR: 0x00 """ crc = 0xff # Initialization value # calculates 8-Bit checksum with given polynomial for byte in [(data & 0xff00) >> 8, data & 0x00ff]: crc ^= byte for _ in range(8): if crc & 0x80: crc = (crc << 1) ^ 0x31 # XOR with polynominal else: crc <<= 1 return crc & 0xff def get_unique_id(self): result = self.command('get_serial_id') return result[0] << 32 | result[1] << 16 | result[0] def get_feature_set_version(self): result = self.command('get_feature_set_version')[0] return (result & 0xf000) >> 12, result & 0x00ff def start_measurement(self, run_while_waiting=None): """Start air quality measurement on the SGP30. The first 15 readings are discarded so this command will block for 15s. :param run_while_waiting: Function to call for every discarded reading. """ self.command('init_air_quality') testsamples = 0 while True: # Discard the initialisation readings as per page 8/15 of the datasheet eco2, tvoc = self.command('measure_air_quality') # The first 15 readings should return as 400, 0 so abort when they change # Break after 20 test samples to avoid a potential infinite loop if eco2 != 400 or tvoc != 0 or testsamples >= 20: break if callable(run_while_waiting): run_while_waiting() time.sleep(1.0) testsamples += 1 def get_air_quality(self): """Get an air quality measurement. Returns an instance of SGP30Reading with the properties equivalent_co2 and total_voc. This should be called at 1s intervals to ensure the dynamic baseline compensation on the SGP30 operates correctly. """ eco2, tvoc = self.command('measure_air_quality') return SGP30Reading(eco2, tvoc) def get_baseline(self): """Get the current baseline setting. Returns an instance of SGP30Reading with the properties equivalent_co2 and total_voc. """ eco2, tvoc = self.command('get_baseline') return SGP30Reading(eco2, tvoc) def set_baseline(self, eco2, tvoc): self.command('set_baseline', [tvoc, eco2]) def __del__(self): self._i2c_dev.close()
ROB_ADDR = 0x1F ACTUATORS_SIZE = 20 SENSORS_SIZE = 47 try: bus = SMBus(I2C_CHANNEL) except: sys.exit(1) actuators_data = bytearray([0] * ACTUATORS_SIZE) sensors_data = bytearray([0] * SENSORS_SIZE) actuators_data[4] = 3 try: write = i2c_msg.write(ROB_ADDR, actuators_data) read = i2c_msg.read(ROB_ADDR, SENSORS_SIZE) bus.i2c_rdwr(write, read) sensors_data = list(read) #print(str(len(sensors_data))) #print(sensors_data) except: sys.exit(1) if len(sensors_data) < 0: sys.exit(1) #print("sel = " + str(sensors_data[24])) if (sensors_data[40] & 0x0F) != 10: sys.exit(2) sys.exit(0)
class IOE(): def __init__(self, i2c_addr=I2C_ADDR, interrupt_timeout=1.0, interrupt_pin=None, gpio=None, skip_chip_id_check=False): self._i2c_addr = i2c_addr self._i2c_dev = SMBus(1) self._debug = False self._vref = 3.3 self._timeout = interrupt_timeout self._interrupt_pin = interrupt_pin self._gpio = gpio self._encoder_offset = [0, 0, 0, 0] self._encoder_last = [0, 0, 0, 0] if self._interrupt_pin is not None: if self._gpio is None: import RPi.GPIO as GPIO self._gpio = GPIO self._gpio.setwarnings(False) self._gpio.setmode(GPIO.BCM) self._gpio.setup(self._interrupt_pin, GPIO.IN, pull_up_down=GPIO.PUD_OFF) self.enable_interrupt_out() self._pins = [ PWM_PIN(1, 5, 5, REG_PIOCON1), PWM_PIN(1, 0, 2, REG_PIOCON0), PWM_PIN(1, 2, 0, REG_PIOCON0), PWM_PIN(1, 4, 1, REG_PIOCON0), PWM_PIN(0, 0, 3, REG_PIOCON0), PWM_PIN(0, 1, 4, REG_PIOCON0), ADC_OR_PWM_PIN(1, 1, 7, 1, REG_PIOCON0), ADC_OR_PWM_PIN(0, 3, 6, 5, REG_PIOCON0), ADC_OR_PWM_PIN(0, 4, 5, 3, REG_PIOCON1), ADC_PIN(3, 0, 1), ADC_PIN(0, 6, 3), ADC_OR_PWM_PIN(0, 5, 4, 2, REG_PIOCON1), ADC_PIN(0, 7, 2), ADC_PIN(1, 7, 0) ] if not skip_chip_id_check: chip_id = (self.i2c_read8(REG_CHIP_ID_H) << 8) | self.i2c_read8(REG_CHIP_ID_L) if chip_id != CHIP_ID: raise RuntimeError( "Chip ID invalid: {:04x} expected: {:04x}.".format( chip_id, CHIP_ID)) def i2c_read8(self, reg): """Read a single (8bit) register from the device.""" msg_w = i2c_msg.write(self._i2c_addr, [reg]) msg_r = i2c_msg.read(self._i2c_addr, 1) self._i2c_dev.i2c_rdwr(msg_w, msg_r) return list(msg_r)[0] def i2c_write8(self, reg, value): """Write a single (8bit) register to the device.""" msg_w = i2c_msg.write(self._i2c_addr, [reg, value]) self._i2c_dev.i2c_rdwr(msg_w) def setup_rotary_encoder(self, channel, pin_a, pin_b, pin_c=None, count_microsteps=False): """Set up a rotary encoder.""" channel -= 1 self.set_mode(pin_a, PIN_MODE_PU, schmitt_trigger=True) self.set_mode(pin_b, PIN_MODE_PU, schmitt_trigger=True) if pin_c is not None: self.set_mode(pin_c, PIN_MODE_OD) self.output(pin_c, 0) self.i2c_write8( [REG_ENC_1_CFG, REG_ENC_2_CFG, REG_ENC_3_CFG, REG_ENC_4_CFG][channel], pin_a | (pin_b << 4)) self.change_bit(REG_ENC_EN, channel * 2 + 1, count_microsteps) self.set_bit(REG_ENC_EN, channel * 2) def read_rotary_encoder(self, channel): """Read the step count from a rotary encoder.""" channel -= 1 last = self._encoder_last[channel] reg = [ REG_ENC_1_COUNT, REG_ENC_2_COUNT, REG_ENC_3_COUNT, REG_ENC_4_COUNT ][channel] value = self.i2c_read8(reg) if value & 0b10000000: value -= 256 if last > 64 and value < -64: self._encoder_offset[channel] += 256 if last < -64 and value > 64: self._encoder_offset[channel] -= 256 self._encoder_last[channel] = value return self._encoder_offset[channel] + value def set_bits(self, reg, bits): """Set the specified bits (using a mask) in a register.""" if reg in BIT_ADDRESSED_REGS: for bit in range(8): if bits & (1 << bit): self.i2c_write8(reg, 0b1000 | (bit & 0b111)) else: value = self.i2c_read8(reg) time.sleep(0.001) self.i2c_write8(reg, value | bits) def set_bit(self, reg, bit): """Set the specified bit (nth position from right) in a register.""" self.set_bits(reg, (1 << bit)) def clr_bits(self, reg, bits): """Clear the specified bits (using a mask) in a register.""" if reg in BIT_ADDRESSED_REGS: for bit in range(8): if bits & (1 << bit): self.i2c_write8(reg, 0b0000 | (bit & 0b111)) else: value = self.i2c_read8(reg) time.sleep(0.001) self.i2c_write8(reg, value & ~bits) def clr_bit(self, reg, bit): """Clear the specified bit (nth position from right) in a register.""" self.clr_bits(reg, (1 << bit)) def get_bit(self, reg, bit): """Returns the specified bit (nth position from right) from a register.""" return self.i2c_read8(reg) & (1 << bit) def change_bit(self, reg, bit, state): """Toggle one register bit on/off.""" if state: self.set_bit(reg, bit) else: self.clr_bit(reg, bit) def enable_interrupt_out(self, pin_swap=False): """Enable the IOE interrupts.""" self.set_bit(REG_INT, BIT_INT_OUT_EN) self.change_bit(REG_INT, BIT_INT_PIN_SWAP, pin_swap) def disable_interrupt_out(self): """Disable the IOE interrupt output.""" self.clr_bit(REG_INT, BIT_INT_OUT_EN) def get_interrupt(self): """Get the IOE interrupt state.""" if self._interrupt_pin is not None: return self._gpio.input(self._interrupt_pin) == 0 else: return self.get_bit(REG_INT, BIT_INT_TRIGD) def clear_interrupt(self): """Clear the interrupt flag.""" self.clr_bit(REG_INT, BIT_INT_TRIGD) def set_pin_interrupt(self, pin, enabled): """Enable/disable the input interrupt on a specific pin. :param pin: Pin from 1-14 :param enabled: True/False for enabled/disabled """ if pin < 1 or pin > len(self._pins): raise ValueError("Pin should be in range 1-14.") io_pin = self._pins[pin - 1] self.change_bit(io_pin.reg_int_mask_p, io_pin.pin, enabled) def on_interrupt(self, callback): """Attach an event handler to be run on interrupt. :param callback: Callback function to run: callback(pin) """ if self._interrupt_pin is not None: self._gpio.add_event_detect(self._interrupt_pin, self._gpio.FALLING, callback=callback, bouncetime=1) def _wait_for_flash(self): """Wait for the IOE to finish writing non-volatile memory.""" t_start = time.time() while self.get_interrupt(): if time.time() - t_start > self._timeout: raise RuntimeError("Timed out waiting for interrupt!") time.sleep(0.001) t_start = time.time() while not self.get_interrupt(): if time.time() - t_start > self._timeout: raise RuntimeError("Timed out waiting for interrupt!") time.sleep(0.001) def set_i2c_addr(self, i2c_addr): """Set the IOE i2c address.""" self.set_bit(REG_CTRL, 4) self.i2c_write8(REG_ADDR, i2c_addr) self._i2c_addr = i2c_addr time.sleep(0.25) # TODO Handle addr change IOError better # self._wait_for_flash() self.clr_bit(REG_CTRL, 4) def set_adc_vref(self, vref): """Set the ADC voltage reference.""" self._vref = vref def get_adc_vref(self): """Get the ADC voltage reference.""" return self._vref def get_chip_id(self): """Get the IOE chip ID.""" return ( self.i2c_read8(REG_CHIP_ID_H) << 8) | self.i2c_read8(REG_CHIP_ID_L) def _pwm_load(self): # Load new period and duty registers into buffer t_start = time.time() self.set_bit(REG_PWMCON0, 6) # Set the "LOAD" bit of PWMCON0 while self.get_bit(REG_PWMCON0, 6): time.sleep(0.001) # Wait for "LOAD" to complete if time.time() - t_start >= self._timeout: raise RuntimeError("Timed out waiting for PWM load!") def set_pwm_control(self, divider): """Set PWM settings. PWM is driven by the 24MHz FSYS clock by default. :param divider: Clock divider, one of 1, 2, 4, 8, 16, 32, 64 or 128 """ try: pwmdiv2 = { 1: 0b000, 2: 0b001, 4: 0b010, 8: 0b011, 16: 0b100, 32: 0b101, 64: 0b110, 128: 0b111 }[divider] except KeyError: raise ValueError("A clock divider of {}".format(divider)) # TODO: This currently sets GP, PWMTYP and FBINEN to 0 # It might be desirable to make these available to the user # GP - Group mode enable (changes first three pairs of pAM to PWM01H and PWM01L) # PWMTYP - PWM type select: 0 edge-aligned, 1 center-aligned # FBINEN - Fault-break input enable self.i2c_write8(REG_PWMCON1, pwmdiv2) def set_pwm_period(self, value): """Set the PWM period. The period is the point at which the PWM counter is reset to zero. The PWM clock runs at FSYS with a divider of 1/1. Also specifies the maximum value that can be set in the PWM duty cycle. """ value &= 0xffff self.i2c_write8(REG_PWMPL, value & 0xff) self.i2c_write8(REG_PWMPH, value >> 8) self._pwm_load() def get_mode(self, pin): """Get the current mode of a pin.""" return self._pins[pin - 1].mode def set_mode(self, pin, mode, schmitt_trigger=False, invert=False): """Set a pin output mode. :param mode: one of the supplied IN, OUT, PWM or ADC constants """ if pin < 1 or pin > len(self._pins): raise ValueError("Pin should be in range 1-14.") io_pin = self._pins[pin - 1] if io_pin.mode == mode: return gpio_mode = mode & 0b11 io_mode = (mode >> 2) & 0b11 initial_state = mode >> 4 if io_mode != PIN_MODE_IO and mode not in io_pin.type: raise ValueError("Pin {} does not support {}!".format( pin, MODE_NAMES[io_mode])) io_pin.mode = mode if self._debug: print("Setting pin {pin} to mode {mode} {name}, state: {state}". format(pin=pin, mode=MODE_NAMES[io_mode], name=GPIO_NAMES[gpio_mode], state=STATE_NAMES[initial_state])) if mode == PIN_MODE_PWM: self.set_bit(io_pin.reg_iopwm, io_pin.pwm_channel) self.change_bit(REG_PNP, io_pin.pwm_channel, invert) self.set_bit(REG_PWMCON0, 7) # Set PWMRUN bit else: if PIN_MODE_PWM in io_pin.type: self.clr_bit(io_pin.reg_iopwm, io_pin.pwm_channel) pm1 = self.i2c_read8(io_pin.reg_m1) pm2 = self.i2c_read8(io_pin.reg_m2) # Clear the pm1 and pm2 bits pm1 &= 255 - (1 << io_pin.pin) pm2 &= 255 - (1 << io_pin.pin) # Set the new pm1 and pm2 bits according to our gpio_mode pm1 |= (gpio_mode >> 1) << io_pin.pin pm2 |= (gpio_mode & 0b1) << io_pin.pin self.i2c_write8(io_pin.reg_m1, pm1) self.i2c_write8(io_pin.reg_m2, pm2) # Set up Schmitt trigger mode on inputs if mode in [PIN_MODE_PU, PIN_MODE_IN]: self.change_bit(io_pin.reg_ps, io_pin.pin, schmitt_trigger) # 5th bit of mode encodes default output pin state self.i2c_write8(io_pin.reg_p, (initial_state << 3) | io_pin.pin) def input(self, pin, adc_timeout=1): """Read the IO pin state. Returns a 12-bit ADC reading if the pin is in ADC mode Returns True/False if the pin is in any other input mode Returns None if the pin is in PWM mode :param adc_timeout: Timeout (in seconds) for an ADC read (default 1.0) """ if pin < 1 or pin > len(self._pins): raise ValueError("Pin should be in range 1-14.") io_pin = self._pins[pin - 1] if io_pin.mode == PIN_MODE_ADC: if self._debug: print("Reading ADC from pin {}".format(pin)) print("ADC channel {}".format(io_pin.adc_channel)) self.clr_bits(REG_ADCCON0, 0x0f) self.set_bits(REG_ADCCON0, io_pin.adc_channel) self.i2c_write8(REG_AINDIDS, 0) self.set_bit(REG_AINDIDS, io_pin.adc_channel) self.set_bit(REG_ADCCON1, 0) self.clr_bit(REG_ADCCON0, 7) # ADCF - Clear the conversion complete flag self.set_bit(REG_ADCCON0, 6) # ADCS - Set the ADC conversion start flag # Wait for the ADCF conversion complete flag to be set t_start = time.time() while not self.get_bit(REG_ADCCON0, 7): time.sleep(0.01) if time.time() - t_start >= adc_timeout: raise RuntimeError("Timeout waiting for ADC conversion!") hi = self.i2c_read8(REG_ADCRH) lo = self.i2c_read8(REG_ADCRL) return ((hi << 4) | lo) / 4095.0 * self._vref else: if self._debug: print("Reading IO from pin {}".format(pin)) pv = self.get_bit(io_pin.reg_p, io_pin.pin) return HIGH if pv else LOW def output(self, pin, value): """Write an IO pin state or PWM duty cycle. :param value: Either True/False for OUT, or a number between 0 and PWM period for PWM. """ if pin < 1 or pin > len(self._pins): raise ValueError("Pin should be in range 1-14.") io_pin = self._pins[pin - 1] if io_pin.mode == PIN_MODE_PWM: if self._debug: print("Outputting PWM to pin: {pin}".format(pin=pin)) self.i2c_write8(io_pin.reg_pwml, value & 0xff) self.i2c_write8(io_pin.reg_pwmh, value >> 8) self._pwm_load() else: if value == LOW: if self._debug: print("Outputting LOW to pin: {pin}".format(pin=pin, value=value)) self.clr_bit(io_pin.reg_p, io_pin.pin) elif value == HIGH: if self._debug: print("Outputting HIGH to pin: {pin}".format(pin=pin, value=value)) self.set_bit(io_pin.reg_p, io_pin.pin)
class VL53L1X: """VL53L1X ToF.""" def __init__(self, i2c_bus=1, i2c_address=0x29, tca9548a_num=255, tca9548a_addr=0): """Initialize the VL53L1X ToF Sensor from ST""" self._i2c_bus = i2c_bus self.i2c_address = i2c_address self._tca9548a_num = tca9548a_num self._tca9548a_addr = tca9548a_addr self._i2c = SMBus(1) self._dev = None # Resgiter Address self.ADDR_UNIT_ID_HIGH = 0x16 # Serial number high byte self.ADDR_UNIT_ID_LOW = 0x17 # Serial number low byte self.ADDR_I2C_ID_HIGH = 0x18 # Write serial number high byte for I2C address unlock self.ADDR_I2C_ID_LOW = 0x19 # Write serial number low byte for I2C address unlock self.ADDR_I2C_SEC_ADDR = 0x8a # Write new I2C address after unlock def open(self): self._i2c.open(bus=self._i2c_bus) self._configure_i2c_library_functions() self._dev = _TOF_LIBRARY.initialise(self.i2c_address) def close(self): self._i2c.close() self._dev = None def _configure_i2c_library_functions(self): # I2C bus read callback for low level library. def _i2c_read(address, reg, data_p, length): ret_val = 0 msg_w = i2c_msg.write(address, [reg >> 8, reg & 0xff]) msg_r = i2c_msg.read(address, length) self._i2c.i2c_rdwr(msg_w, msg_r) if ret_val == 0: for index in range(length): data_p[index] = ord(msg_r.buf[index]) return ret_val # I2C bus write callback for low level library. def _i2c_write(address, reg, data_p, length): ret_val = 0 data = [] for index in range(length): data.append(data_p[index]) msg_w = i2c_msg.write(address, [reg >> 8, reg & 0xff] + data) self._i2c.i2c_rdwr(msg_w) return ret_val # Pass i2c read/write function pointers to VL53L1X library. self._i2c_read_func = _I2C_READ_FUNC(_i2c_read) self._i2c_write_func = _I2C_WRITE_FUNC(_i2c_write) _TOF_LIBRARY.VL53L1_set_i2c(self._i2c_read_func, self._i2c_write_func) def start_ranging(self, mode=VL53L1xDistanceMode.LONG): """Start VL53L1X ToF Sensor Ranging""" _TOF_LIBRARY.startRanging(self._dev, mode) def stop_ranging(self): """Stop VL53L1X ToF Sensor Ranging""" _TOF_LIBRARY.stopRanging(self._dev) def get_distance(self): """Get distance from VL53L1X ToF Sensor""" return _TOF_LIBRARY.getDistance(self._dev) # This function included to show how to access the ST library directly # from python instead of through the simplified interface def get_timing(self): budget = c_uint(0) budget_p = pointer(budget) status = _TOF_LIBRARY.VL53L1_GetMeasurementTimingBudgetMicroSeconds( self._dev, budget_p) if status == 0: return budget.value + 1000 else: return 0 def change_address(self, new_address): _TOF_LIBRARY.setDeviceAddress(self._dev, new_address)
class VL53L1X: """VL53L1X ToF.""" def __init__(self, i2c_bus=1): """Initialize the VL53L1X ToF Sensor from ST""" self._i2c_bus = i2c_bus self._i2c = SMBus(1) self._default_dev = None self._dev_list = None # Resgiter Address self.ADDR_UNIT_ID_HIGH = 0x16 # Serial number high byte self.ADDR_UNIT_ID_LOW = 0x17 # Serial number low byte self.ADDR_I2C_ID_HIGH = 0x18 # Write serial number high byte for I2C address unlock self.ADDR_I2C_ID_LOW = 0x19 # Write serial number low byte for I2C address unlock self.ADDR_I2C_SEC_ADDR = 0x8a # Write new I2C address after unlock def open(self): self._i2c.open(bus=self._i2c_bus) self._configure_i2c_library_functions() self._default_dev = _TOF_LIBRARY.initialise() self._dev_list = dict() def add_sensor(self, sensor_id, address): self._dev_list[sensor_id] = _TOF_LIBRARY.copy_dev(self._default_dev) _TOF_LIBRARY.init_dev(self._dev_list[sensor_id], c_uint8(address)) def close(self): self._i2c.close() self._default_dev = None self._dev_list = None def _configure_i2c_library_functions(self): # I2C bus read callback for low level library. def _i2c_read(address, reg, data_p, length): ret_val = 0 msg_w = i2c_msg.write(address, [reg >> 8, reg & 0xff]) msg_r = i2c_msg.read(address, length) # print("R: a: %x\tr:%d" % (address,reg)) try: self._i2c.i2c_rdwr(msg_w, msg_r) except: print("Cannot read on 0x%x I2C bus, reg: %d" % (address, reg)) if ret_val == 0: for index in range(length): data_p[index] = ord(msg_r.buf[index]) return ret_val # I2C bus write callback for low level library. def _i2c_write(address, reg, data_p, length): ret_val = 0 data = [] for index in range(length): data.append(data_p[index]) msg_w = i2c_msg.write(address, [reg >> 8, reg & 0xff] + data) # print("W: a: %x\tr:%d" % (address,reg)) try: self._i2c.i2c_rdwr(msg_w) except: print("Cannot write on 0x%x I2C bus, reg: %d" % (address, reg)) return ret_val # Pass i2c read/write function pointers to VL53L1X library. self._i2c_read_func = _I2C_READ_FUNC(_i2c_read) self._i2c_write_func = _I2C_WRITE_FUNC(_i2c_write) _TOF_LIBRARY.VL53L1_set_i2c(self._i2c_read_func, self._i2c_write_func) def start_ranging(self, sensor_id, mode=VL53L1xDistanceMode.LONG): """Start VL53L1X ToF Sensor Ranging""" dev = self._dev_list[sensor_id] _TOF_LIBRARY.startRanging(dev, mode) def stop_ranging(self, sensor_id): """Stop VL53L1X ToF Sensor Ranging""" dev = self._dev_list[sensor_id] _TOF_LIBRARY.stopRanging(dev) def get_distance(self, sensor_id): dev = self._dev_list[sensor_id] return _TOF_LIBRARY.getDistance(dev) def get_address(self, sensor_id): dev = self._dev_list[sensor_id] return _TOF_LIBRARY.get_address(dev) def change_address(self, sensor_id, new_address): dev = self._dev_list[sensor_id] _TOF_LIBRARY.setDeviceAddress(dev, new_address)
class AntennaDeployer(Device): BUS_NUMBER = 1 PRIMARY_ADDRESS = 0x31 SECONDARY_ADDRESS = 0x32 EXPECTED_BYTES = { AntennaDeployerCommand.SYSTEM_RESET: 0, AntennaDeployerCommand.WATCHDOG_RESET: 0, AntennaDeployerCommand.ARM_ANTS: 0, AntennaDeployerCommand.DISARM_ANTS: 0, AntennaDeployerCommand.DEPLOY_1: 0, AntennaDeployerCommand.DEPLOY_2: 0, AntennaDeployerCommand.DEPLOY_3: 0, AntennaDeployerCommand.DEPLOY_4: 0, AntennaDeployerCommand.AUTO_DEPLOY: 0, AntennaDeployerCommand.CANCEL_DEPLOY: 0, AntennaDeployerCommand.DEPLOY_1_OVERRIDE: 0, AntennaDeployerCommand.DEPLOY_2_OVERRIDE: 0, AntennaDeployerCommand.DEPLOY_3_OVERRIDE: 0, AntennaDeployerCommand.DEPLOY_4_OVERRIDE: 0, AntennaDeployerCommand.GET_TEMP: 2, AntennaDeployerCommand.GET_STATUS: 2, AntennaDeployerCommand.GET_COUNT_1: 1, AntennaDeployerCommand.GET_COUNT_2: 1, AntennaDeployerCommand.GET_COUNT_3: 1, AntennaDeployerCommand.GET_COUNT_4: 1, AntennaDeployerCommand.GET_UPTIME_1: 2, AntennaDeployerCommand.GET_UPTIME_2: 2, AntennaDeployerCommand.GET_UPTIME_3: 2, AntennaDeployerCommand.GET_UPTIME_4: 2, } def __init__(self): super().__init__("AntennaDeployer") self.bus = SMBus() def write(self, command: AntennaDeployerCommand, parameter: int) -> bool or None: """ Wrapper for SMBus write word data :param command: (AntennaDeployerCommand) The antenna deployer command to run :param parameter: (int) The parameter to pass in to the command (usually 0x00) :return: (bool or None) success """ if type(command) != AntennaDeployerCommand: return try: self.bus.open(self.BUS_NUMBER) self.bus.write_word_data(self.PRIMARY_ADDRESS, command.value, parameter) self.bus.close() except: return False return True def read(self, command: AntennaDeployerCommand) -> bytes or None: """ Wrapper for SMBus to read from AntennaDeployer :param command: (AntennaDeployerCommand) The antenna deployer command to run :return: (ctypes.LP_c_char, bool) buffer, success """ if type(command) != AntennaDeployerCommand: return success = self.write(command, 0x00) if not success: return None, False time.sleep(0.5) try: i2c_obj = i2c_msg.read(self.PRIMARY_ADDRESS, self.EXPECTED_BYTES[command]) self.bus.open(self.BUS_NUMBER) self.bus.i2c_rdwr(i2c_obj) self.bus.close() return i2c_obj.buf, True except: return None, False def functional(self): """ :return: (bool) i2c file opened by SMBus """ return self.bus.fd is not None def reset(self): """ Resets the Microcontroller on the ISIS Antenna Deployer :return: (bool) no error """ try: self.bus.open(self.BUS_NUMBER) self.write(AntennaDeployerCommand.SYSTEM_RESET, 0x00) self.bus.close() return True except: return False def disable(self): """ Disarms the ISIS Antenna Deployer """ try: self.bus.open(self.BUS_NUMBER) self.write(AntennaDeployerCommand.DISARM_ANTS, 0x00) self.bus.close() return True except: return False def enable(self): """ Arms the ISIS Antenna Deployer """ try: self.bus.open(self.BUS_NUMBER) self.write(AntennaDeployerCommand.ARM_ANTS, 0x00) self.bus.close() return True except: return False
class SMBus2(I2C): """Wrapper for smbus2 library extends :class:`I2C` Args: bus (int): The i2c bus of the raspberry pi. The pi has two buses. """ def __init__(self, bus): """Constructor""" self.bus = bus if SMBus is None: raise ImportError("failed to import smbus2") self._smbus = SMBus(bus) def read(self, address, register, byte_num=1): """Read using the smbus protocol. Args: address: The address of the spi slave register: The register's address. byte_num: How many bytes to read from the device. Max 32 Returns: A list with byte_num elements. """ byte_num = min(byte_num, 32) if byte_num > 1: data = self._smbus.read_i2c_block_data(address, register, byte_num) else: data = self._smbus.read_byte_data(address, register) return data def write(self, address, register, data): """Write using the smbus protocol. Args: address: The address of the spi slave register: The address of the register inside the slave. data: A list or a single byte, if it is a list max length 32 bytes. It is error prone so write less and make consecutive calls. """ write_func = self._smbus.write_i2c_block_data\ if isinstance(data, list) else self._smbus.write_byte_data write_func(address, register, data) def write_i2c(self, address, register, data): """Write using the i2c protocol Args: address: The address of the spi slave register: The address of the register inside the slave. data: A list or a single byte, if it is a list max length 32 bytes. """ data = data if isinstance(data, list) else [data] msg = i2c_msg.write(address, [register] + data) self._smbus.i2c_rdwr(msg) def read_i2c(self, address, byte_num): """Read using the i2c protocol. Args: address: The address of the spi slave byte_num: How many bytes to read from the device. Max 32 Returns: A list with byte_num elements. """ read = i2c_msg.read(address, byte_num) self._smbus.i2c_rdwr(read) res = [ord(read.buf[i]) for i in range(byte_num)] return res def read_write(self, address, register, data, byte_num): """Combined read and write command using the i2c protocol. Args: address: The address of the spi slave register: The address of the register inside the slave. data: A list or a single byte, if it is a list max length 32 bytes. byte_num: How many bytes to read from the device. Max 32 Returns: A list with byte_num elements. """ data = data if isinstance(data, list) else [data] write = i2c_msg.write(address, [register] + data) read = i2c_msg.read(address, byte_num) self._smbus.i2c_rdwr(write, read) res = [ord(read.buf[i]) for i in range(byte_num)] return res def close(self): self._smbus.close() def _set_bus(self, bus): self._bus = bus def _get_bus(self): return self._bus
class I2CBus(I2CInterface): """An I2C bus implementation for the Raspberry Pi. Derived from the SMBus2 library by 'kplindegaard' at https://github.com/kplindegaard/smbus2. """ def __init__(self, board_rev=board_revision.REV1): """Initialize a new instance of I2CBus. :param int board_rev: The board revision. """ I2CInterface.__init__(self) self.__busID = 1 if board_rev == board_revision.REV1: self.__busID = 0 self.__isOpen = False self.__bus = None @property def is_open(self): """Get a value indicating whether the connection is open. :returns: True if the connection is open. :rtype: bool """ return self.__isOpen def open(self): """Open a connection to the I2C bus. :raises: raspy.object_disposed_exception.ObjectDisposedException if this instance has been disposed. :raises: raspy.io.io_exception.IOException if unable to open the bus connection. """ if self.is_disposed: raise ObjectDisposedException("I2CBus") if self.__isOpen: return try: self.__bus = SMBus(self.__busID) except OSError or IOError: msg = "Error opening bus '" + str(self.__busID) + "'." raise IOException(msg) if self.__bus.fd is None: msg = "Error opening bus '" + str(self.__busID) + "'." raise IOException(msg) self.__isOpen = True def close(self): """Close the bus connection.""" if self.is_disposed: return if self.__isOpen: if self.__bus is not None: self.__bus.close() self.__isOpen = False self.__bus = None def dispose(self): """Dispose of all the managed resources used by this instance.""" if self.is_disposed: return self.close() self.__busID = None I2CInterface.dispose(self) def write_bytes(self, address, buf): """Write a list of bytes to the specified device address. Currently, RPi drivers do not allow writing more than 3 bytes at a time. As such, if any list of greater than 3 bytes is provided, an exception is thrown. :param int address: The address of the target device. :param list buf: A list of bytes to write to the bus. :raises: raspy.object_disposed_exception.ObjectDisposedException if this instance has been disposed. :raises: raspy.invalid_operation_exception.InvalidOperationException if a connection to the I2C bus has not yet been opened. :raises: raspy.illegal_argument_exception.IllegalArgumentException if the buffer contains more than 3 bytes or if the specified buffer parameter is not a list. :raises: raspy.io.io_exception.IOException if an error occurs while writing the buffer contents to the I2C bus or if only a partial write succeeds. """ if self.is_disposed: raise ObjectDisposedException("I2CBus") if not self.__isOpen: raise InvalidOperationException("No open connection to write to.") if isinstance(buf, list): if len(buf) > 3: # TODO we only do this to keep parity with the JS and C# # ports. They each have their own underlying native # implementations. SMBus2 itself is capable of writing # much more than 3 bytes. We should change this as soon # as we can get the other ports to support more. msg = "Cannot write more than 3 bytes at a time." raise IllegalArgumentException(msg) else: msg = "The specified buf param value is not a list." raise IllegalArgumentException(msg) try: trans = i2c_msg.write(address, buf) self.__bus.i2c_rdwr(trans) except OSError or IOError: msg = "Error writing to address '" + str(address) msg += "': I2C transaction failed." raise IOException(msg) def write_byte(self, address, byt): """Write a single byte to the specified device address. :param int address: The address of the target device. :param int byt: The byte value to write. :raises: raspy.object_disposed_exception.ObjectDisposedException if this instance has been disposed. :raises: raspy.invalid_operation_exception.InvalidOperationException if a connection to the I2C bus has not yet been opened. :raises: raspy.io.io_exception.IOException if an error occurs while writing the buffer contents to the I2C bus or if only a partial write succeeds. """ buf = list() buf[0] = byt self.write_bytes(address, buf) def write_command(self, address, command, data1=None, data2=None): """Write a command with data to the specified device address. :param int address: The address of the target device. :param int command: The command to send to the device. :param int data1: The data to send as the first parameter (optional). :param int data2: The data to send as the second parameter (optional). :raises: raspy.object_disposed_exception.ObjectDisposedException if this instance has been disposed. :raises: raspy.invalid_operation_exception.InvalidOperationException if a connection to the I2C bus has not yet been opened. :raises: raspy.io.io_exception.IOException if an error occurs while writing the buffer contents to the I2C bus or if only a partial write succeeds. """ buf = list() buf[0] = command if data1: buf[1] = data1 if data1 and data2: buf[1] = data1 buf[2] = data2 self.write_bytes(address, buf) def write_command_byte(self, address, command, data): """Write a command with data to the specified device address. :param int address: The address of the target device. :param int command: The command to send to the device. :param int data: The data to send with the command. :raises: raspy.object_disposed_exception.ObjectDisposedException if this instance has been disposed. :raises: raspy.invalid_operation_exception.InvalidOperationException if a connection to the I2C bus has not yet been opened. :raises: raspy.io.io_exception.IOException if an error occurs while writing the buffer contents to the I2C bus or if only a partial write succeeds. """ buf = list() buf[0] = command buf[1] = data & 0xff buf[2] = data >> 8 self.write_bytes(address, buf) def read_bytes(self, address, count): """Read bytes from the device at the specified address. :param int address: The address of the device to read from. :param int count: The number of bytes to read. :returns: The bytes read. :rtype: list :raises: raspy.object_disposed_exception.ObjectDisposedException if this instance has been disposed. :raises: raspy.invalid_operation_exception.InvalidOperationException if a connection to the I2C bus has not yet been opened. :raises: raspy.io.io_exception.IOException if an error occurs while writing the buffer contents to the I2C bus or if only a partial write succeeds. """ if self.is_disposed: return if not self.__isOpen: raise InvalidOperationException("No open connection to read from.") buf = [] msg = "Error reading from address '" + str(address) msg += "': I2C transaction failed." try: trans = i2c_msg.read(address, count) self.__bus.i2c_rdwr(trans) buf = list(trans) except OSError or IOError: raise IOException(msg) if len(buf) <= 0: raise IOException(msg) return buf def read(self, address): """Read a single byte from the device at the specified address. :param int address: The address of the device to read from. :returns: The byte read. :rtype: int :raises: raspy.object_disposed_exception.ObjectDisposedException if this instance has been disposed. :raises: raspy.invalid_operation_exception.InvalidOperationException if a connection to the I2C bus has not yet been opened. :raises: raspy.io.io_exception.IOException if an error occurs while writing the buffer contents to the I2C bus or if only a partial write succeeds. """ result = self.read_bytes(address, 1) return result[0]
class TrillLib: def __init__(self, bus, tt, addr): # tt - trill type string addr - I2C address self.dataBuffer = [0] * kRawLength self.rawData = [0] * kNumChannelsMax self.bus = SMBus(bus) if addr >= 128 : return # so calling program will can and find a valid address # been given a validate I2C address and trill type - now check they match if tt in trillTypes : self.typeNumber = trillTypes.index(tt) else : raise BaseException("Error - " + tt +" is not a valid Trill type") return if not (addr >= trillAddress[self.typeNumber][0] and addr <= trillAddress[self.typeNumber][1]) : raise BaseException("Error - " + hex(addr) +" is not a Trill type " + tt + " address") return self.addr = addr self.tt = tt self.modeNumber = 0 self.max_touch_1D_or_2D = kMaxTouchNum1D # trill size [pos, posH, size] at index of trillTypes self.deviceSize = trillSize[self.typeNumber] if tt == "craft" : self.setMode("DIFF") else: self.setMode("CENTROID") # default mode for all other types if self.setScanSettings(0, 12) : raise BaseException("Error - unable to set scan settings") if self.updateBaseLine() : raise BaseException("Error - unable to set base line") if self.prepareForDataRead() : raise BaseException("Error - unable to prepare for data read") # test read to see if we can access the device self.identify() self.typeNumber = self.device_type_ tt = trillTypes[self.typeNumber] def getTypeNumber(self): # get the Trill type number of this instance return self.typeNumber def getDeviceFromName(self, name): # get the Trill type string of this instance if name in trillTypes : return trillTypes.index(name) else: return "Invalid device name " def getNameFromMode(self, mode): # get the current Trill mode number if mode < 0 or mode > 3: return "Invalid mode number" return modeTypes[mode] def getMode(self): # get the current Trill mode name return modeTypes[self.modeNumber] def identify(self): # check that the address we have is that of a Trill device self.device_type_ = 0 block = [kCommandIdentify] if self.txI2C(kOffsetCommand, block) : # set in the identity mode raise BaseException("Device not found for identify command") else: self.prepairedForDataRead_ = False time.sleep(commandSleepTime) self.rxI2C(kOffsetCommand, 4) # ignore first reading bytesRead = self.rxI2C(kOffsetCommand, 4) if bytesRead[1] == 0 : # assume the device did not respond return -1 self.device_type_ = bytesRead[1] self.firmware_version_ = bytesRead[2] def updateRescale(self): # refresh scale factors scale = 1 << (16 - self.numBits) #scale += 0.1 ; scale -= 0.1 self.posRescale = 1.0 / self.deviceSize[0] self.posHRescale = 1.0 / self.deviceSize[1] self.sizeRescale = scale / self.deviceSize[2] self.rawRescale = 1.0 / (1 << self.numBits) def printDetails(self): # print details of this device and this library to the console print("Device type",trillTypes[self.typeNumber]) print("At I2C address:-", str(hex(self.addr))) print("Running in mode:-", modeTypes[self.modeNumber]) print("Device size:-", self.deviceSize) print("Device version number", self.firmware_version_) print("Python library version 1.0") def setMode(self, modeToSet): # set the current working mode from a mode number if modeToSet in modeTypes : self.modeNumber = modeTypes.index(modeToSet) block = [kCommandMode, self.modeNumber] if self.txI2C(kOffsetCommand, block) : raise BaseException("Device not found setting mode") else: self.prepairedForDataRead_ = False ; time.sleep(commandSleepTime) else: raise BaseException("Error - mode" + modeToSet + "is not a valid mode") def setScanSettings(self, speed, num_bits): # parameters of auto scan if speed > 3 : speed = 3 if num_bits < 9 : num_bits = 9 if num_bits > 16 : num_bits = 16 block = [kCommandScanSettings, speed, num_bits] if self.txI2C(kOffsetCommand, block) : raise BaseException("Device not found setting Scan") else: self.prepairedForDataRead_ = False self.numBits = num_bits self.updateRescale() time.sleep(commandSleepTime) def setPrescaler(self, prescaler): # set the register of the I2C device block = [kCommandPrescaler, prescaler] if self.txI2C(kOffsetCommand, block) : raise BaseException("Device not found setting Prescaler") else: self.prepairedForDataRead_ = False ; time.sleep(commandSleepTime) def setNoiseThreshold(self, threshold): # ignore readings lower than this threshold = threshold * (1 << self.numBits) if threshold > 255 : threshold = 255 thByte = int(threshold + 0.5) block = [kCommandNoiseThreshold, thByte] if self.txI2C(kOffsetCommand, block) : raise BaseException("Device not found setting noise threshold") else: self.prepairedForDataRead_ = False ; time.sleep(commandSleepTime) def setIDACValue(self, value): # A/D converter settings block = [kCommandIdac, value] if self.txI2C(kOffsetCommand, block) : raise BaseException("Device not found setting IDAC value") else: self.prepairedForDataRead_ = False ; time.sleep(commandSleepTime) def setMinimumTouchSize(self, minSize): # minimum size of touch considered valid maxMinSize = (1 << 16) - 1 if(maxMinSize > minSize / self.sizeRescale): minSize = maxMinSize # clip to max value we can send else: size = minSize / self.sizeRescale block[kCommandMinimumSize, size >> 8, size & 0xFF] if self.txI2C(kOffsetCommand, block) : raise BaseException("Device not found setting minimum touch size") else: self.prepairedForDataRead_ = False ; time.sleep(commandSleepTime) return 0 def setAutoScanInterval(self, interval): # set the auto scan speed block = [kCommandAutoScanInterval, int(interval >> 8), int(interval & 0xFF)] if self.txI2C(kOffsetCommand, block) : raise BaseException("Device not found setting Auto Scan Interval") else: self.prepairedForDataRead_ = False ; time.sleep(commandSleepTime) def updateBaseLine(self): # set the base line all readings have these values subtracted in the DIFF mode block = [kCommandBaselineUpdate] if self.txI2C(kOffsetCommand, block) : raise BaseException("Device not found setting base line") else: self.prepairedForDataRead_ = False ; time.sleep(commandSleepTime) return 0 def prepareForDataRead(self): # set up the registers of the Trill device for a data reading if not self.prepairedForDataRead_ : self.bus.write_byte(self.addr, kOffsetData) # send one byte self.prepairedForDataRead_ = True time.sleep(commandSleepTime) return 0 def readI2C(self) : # for compatibility with the C library self.readTrill() def readTrill(self) : # read the Trill device and process it according to the current mode self.prepareForDataRead() bytesToRead = kCentroidLengthDefault self.num_touches_ = 0 if("CENTROID" == modeTypes[self.modeNumber]) : if self.tt == 'square' or self.tt == 'hex' : bytesToRead = kCentroidLength2D; if self.tt == 'ring' : bytesToRead = kCentroidLengthRing; else : bytesToRead = kRawLength msg = i2c_msg.read(self.addr, bytesToRead) self.bus.i2c_rdwr(msg) self.dataBuffer = list(msg) if len(self.dataBuffer) != bytesToRead : print("only got", len(self.dataBuffer), "when asked for", bytesToRead) return 1 if "CENTROID" != modeTypes[self.modeNumber] : for i in range (0, self.getNumChannels()) : self.rawData[i] = (((self.dataBuffer[2 * i] << 8) + self.dataBuffer[2 * i + 1]) & 0x0FFF) * self.rawRescale else: locations = 0 #Look for 1st instance of 0xFFFF (no touch) in the buffer for locations in range(0, self.max_touch_1D_or_2D + 1) : if self.dataBuffer[2 * locations] == 0xFF and self.dataBuffer[2 * locations + 1] == 0xFF : break self.num_touches_ = locations if self.tt == "square" or self.tt == 'hex' : # Look for the number of horizontal touches in 2D sliders # which might be different from number of vertical touches self.topHalf = bytesToRead // 2 for locations in range(0, self.max_touch_1D_or_2D) : if self.dataBuffer[2 * locations + self.topHalf] == 0xFF and self.dataBuffer[2 * locations + self.topHalf + 1] == 0xFF : break self.num_touches_ |= locations << 4 # pack into the top four bits return 0 def is1D(self): # is this device a one dimensional device? if modeTypes[self.modeNumber] != "CENTROID" : return False if self.tt == 'bar' or self.tt == 'ring' or self.tt == 'craft' : return True; else : return False; def is2D(self): # is this device a two dimensional device? if self.tt == 'square' or self.tt == 'hex' : return True else: return False; def getNumTouches(self): # how many touches (vertical) are being detected? if modeTypes[self.modeNumber] != "CENTROID" : return 0 # Lower 4 bits hold number of 1-axis or vertical touches return (self.num_touches_ & 0x0F) def getNumHorizontalTouches(self): # how many Horizontal touches are being detected? if modeTypes[self.modeNumber] != "CENTROID" or (self.tt != 'square' and self.tt != 'hex') : return 0 return (self.num_touches_ >> 4) def touchLocation(self, touch_num): # where is the touch returns a value 0.0 to 1.0 if modeTypes[self.modeNumber] != "CENTROID" : return -1 if touch_num >= self.max_touch_1D_or_2D : return -1 location = self.dataBuffer[2 * touch_num] << 8 location |= self.dataBuffer[2 * touch_num + 1] return location * self.posRescale def getButtonValue(self, button_num): # Get the value of the capacitive "button" channels on the device if modeTypes[self.modeNumber] != "CENTROID" : return -1 if button_num > 1 : return -1 if self.tt != "ring" : return -1 return ((self.dataBuffer[4 * self.max_touch_1D_or_2D + 2* button_num] << 8) + self.dataBuffer[4 * self.max_touch_1D_or_2D + 2* button_num + 1] & 0xFFF) *self.rawRescale def touchSize(self, touch_num): # the size of the touch, if the touch exists, or 0 otherwise if modeTypes[self.modeNumber] != "CENTROID" : return -1 if touch_num >= self.max_touch_1D_or_2D : return -1 size = self.dataBuffer[2 * touch_num + 2* self.max_touch_1D_or_2D] * 256 size += self.dataBuffer[2 * touch_num + 2* self.max_touch_1D_or_2D + 1] return size * self.sizeRescale def touchHorizontalLocation(self, touch_num): # Get the location of a touch on the horizontal axis of the device. if(modeTypes[self.modeNumber] != "CENTROID" or (self.tt != 'square' and self.tt != 'hex')): return -1 if(touch_num >= self.max_touch_1D_or_2D): return -1 location = self.dataBuffer[2 * touch_num + self.topHalf] << 8 | self.dataBuffer[2 * touch_num + self.topHalf+1] place = location * self.posHRescale if place >1.0 : place = 1.0 return place def touchHorizontalSize(self, touch_num) : # get the size of the touch, if the touch exists, or 0 otherwise. if(modeTypes[self.modeNumber] != "CENTROID" or (self.tt != 'square' and self.tt != 'hex')): return -1 if(touch_num >= self.max_touch_1D_or_2D): return -1; size = self.dataBuffer[2*touch_num + 6* self.max_touch_1D_or_2D] * 256 size += self.dataBuffer[2*touch_num + 6* self.max_touch_1D_or_2D + 1] return size * self.sizeRescale; def compoundTouch(self, LOCATION, SIZE, TOUCHES) : favg = 0.0 totalSize = 0.0 numTouches = TOUCHES for i in range(0, numTouches) : avg += LOCATION(i) * SIZE(i) totalSize += SIZE(i) if(numTouches) : avg = avg / totalSize; return avg def compoundTouchLocation(self): # Get the vertical location of the compound touch on the device. self.compoundTouch(touchLocation, touchSize, self.getNumTouches()) def compoundTouchHorizontalLocation(self): # Get the horizontal location of the compound touch on the device. self.compoundTouch(touchHorizontalLocation, touchHorizontalSize, self.getNumHorizontalTouches()) def compoundTouchSize(self): # Get the size of the compound touch on the device. size = 0.0 for i in range(0, self.getNumTouches()): size += touchSize[i] return size def getNumChannels(self): # Get the number of capacitive channels on this device if self.tt == 'bar': return kNumChannelsBar elif self.tt == 'ring':return kNumChannelsRing else: return kNumChannelsMax ### I2C calls ### def rxI2C(self, reg, number): r = self.bus.read_i2c_block_data(self.addr, reg, number) return r def testForI2CDevice(self, addr, reg, value): device = True try : self.bus.read_i2c_block_data(addr, reg, value) except : device = False return device def txI2C(self, reg, data): error = False try : self.bus.write_i2c_block_data(self.addr, reg, data) except : error = True return error # error indication
import RPi.GPIO as GPIO import time from smbus2 import SMBus import ic_msg i = 0 start = 0 maxLoops = 40 bus = SMBus(1) keepGoing = True while keepGoing: msg = i2c_msg.write(80, [65, 66, 67, 68]) bus.i2c_rdwr(msg) print("writing! " + str(i)) i = i + 1 if (i > maxLoops): keepGoing = False time.sleep(0.05) #Exit gracefully? print "Done!"
class PowerSupply(object): def __init__(self,i2cbus=0,address=7): #address 0..7 reflecting the i2c address select bits on the PSU edge connector self.i2c = SMBus(i2cbus) # 0 = /dev/i2c-0 (port I2C0), 1 = /dev/i2c-1 (port I2C1) self.address=0x58+address self.EEaddress=0x50+address self.numReg=0x58/2 self.lastReg=[0 for n in range(self.numReg)] self.minReg=[0xffff for n in range(self.numReg)] self.maxReg=[0 for n in range(self.numReg)] #not very interesting - read the 24c02 eeprom (you can write it too) def readEEPROM(self): pos=0 data="" while pos<256: #fixme data+=self.i2c.read_i2c_block_data(self.EEaddress,pos,32) pos+=32 print "%s" % (" ".join([ "%02x" % ord(d) for d in data]) ) def readVar(self,address,writeInts,readCount): #https://github.com/kplindegaard/smbus2 'dual i2c_rdrw' write = i2c_msg.write(address, writeInts) if readCount: read = i2c_msg.read(address,readCount) m=self.i2c.i2c_rdwr(write, read) return [chr(n) for n in list(read)] def writeVar(self,address,bytes): asInts=[ord(d) for d in bytes] self.i2c.write_i2c_block_data(address,asInts[0],asInts[1:]) #the most useful def readDPS1200(self,reg,count): cs=reg+(self.address<<1) regCS=((0xff-cs)+1)&0xff #this is the 'secret sauce' - if you don't add the checksum byte when reading a register the PSU will play dumb #checksum is [ i2c_address, reg_address, checksum ] where checksum is as above. writeInts=[reg,regCS] #send register # plus checksum #this should write [address,register,checksum] and then read two bytes (send address+read bit, read lsb,read msb) #note this device doesn't require you to use a "repeated start" I2C condition - you can just do start/write/stop;start/read/stop return self.readVar(self.address,writeInts,count) # Writable registers/cmds: # Nothing very interesting discovered so far; you can control the fan speed. Not yet discovered how to turn # the 12v output on/off in software. # # Random notes below: # #"interesting" write cmds; #0x35 (if msb+lsb==0 clear c4+c5) # #0x3b - checks bit 5 of lsb of write data, if set does (stuff), if bit 6 set does (other stuff), low 5 bits #stored @ 0xc8 (read with 0x3a) # b0..2= 1=(see 0x344); sets 'fan_related' to 9000 # 2=(see 0x190a) sets 'surprise_more_flags bit 6' # b3= # b4= # b5= # b6= # #..cmds that write to ram.. #0x31 - if data=0 sets i2c_flags1 2 & 7 (0x2d8) = resets total_watts and uptime_secs #0x33 - if data=0 resets MaxInputWatts #0x35 - if data=0 resets MaxInputCurrent #0x37 - if data=0 resets MaxRecordedCurrent #0x3b - sets yet_more_flags:5, checks write data lsb:5 #0x3d - something like set min fan speed #0x40 (writes 0xe5) #0x41 (writes 0xd4) <<d4= fan speed control - write 0x4000 to 0x40 = full speed fan (sets 'surprise_mnore_flags:5)') #0x45 sets some voltage threshold if written (sets a4:1) #0x47 sets some other threshhold when written (sets a4:2) - #0x49 sets a4:3 #0x4b sets a4:4 #50/51 (writes 0xee/ef) - default is 3200 #52/53 (0xa5/6) - some temp threshold #54/55 (0xa7/8) (sets some_major_flags:5) - eeprom related #56/57 (0xa9/a) (a9 is EEPROM read address with cmd 57) def writeDPS1200(self,reg,value): valLSB=value&0xff valMSB=value>>8 cs=(self.address<<1)+reg+valLSB+valMSB regCS=((0xff-cs)+1)&0xff #the checksum is the 'secret sauce' writeInts=[reg,valLSB,valMSB,regCS] #write these 4 bytes to i2c bytes="".join([chr(n) for n in writeInts]) return self.writeVar(self.address, bytes) def testWrite(self): value=0 #try fuzzing things to see if we can find power on/off.. (not yet) 0x40 controls fan speed (bitfield) #for n in [0x35,0x3b,0x40,0x50,0x52,0x54,0x56]: for n in [0x35,0x3b,0x50,0x52,0x54,0x56]: #for n in [0x40]: for b in range(16): value=(1<<b)-1 print "%02x : %04x" % (n,value) self.writeDPS1200(n,value) time.sleep(0.5) #Readable registers - some of these are slightly guessed - comments welcome if you figure something new out or have a correction. REGS={ #note when looking at PIC disasm table; "lookup_ram_to_read_for_cmd", below numbers are <<1 #the second arg is the scale factor 0x01:["FLAGS",0], #not sure but includes e.g. "power good" 0x04:["INPUT_VOLTAGE",32.0], #e.g. 120 (volts) 0x05:["AMPS_IN",128.0], 0x06:["WATTS_IN",2.0], 0x07:["OUTPUT_VOLTAGE",254.5], #pretty sure this is right; unclear why scale is /254.5 not /256 but it's wrong - can't see how they'd not be measuring this to high precision 0x08:["AMPS_OUT",128.0], #rather inaccurate at low output <10A (reads under) - appears to have internal load for stability so always reads about 1.5 even open circuit 0x09:["WATTS_OUT",2.0], 0x0d:["TEMP1_INTAKE_FARENHEIT",32.0], # this is a guess - may be C or F but F looks more right 0x0e:["TEMP2_INTERNAL_FARENHEIT",32.0], 0x0f:["FAN_SPEED_RPM",1], #total guess at scale but this is def fan speed it may be counting ticks from the fan sensor which seem to be typically 2 ticks/revolution 0x1a:["?flags",0], #unknown (from disassembly) 0x1b:["?voltage",1], #unknown (from disassembly) (0x2c>>1):["WATT_SECONDS_IN",-4.0], #this is a special case; uses two consecutive regs to make a 32-bit value (the minus scale factor is a flag for that) (0x30>>1):["ON_SECONDS",2.0], (0x32>>1):["PEAK_WATTS_IN",2.0], (0x34>>1):["MIN_AMPS_IN",128.0], (0x36>>1):["PEAK_AMPS_OUT",128.0], (0x3A>>1):["COOL_FLAGS1",0], #unknown (from disassembly) (0x3c>>1):["COOL_FLAGS2",0], #unknown (from disassembly) (0x40>>1):["FAN_TARGET_RPM",1], #unknown (from disassembly) (0x44>>1):["VOLTAGE_THRESHOLD_1",1], #unknown (from disassembly) (0x46>>1):["VOLTAGE_THRESHOLD_2",1], #unknown (from disassembly) (0x50>>1):["MAYBE_UNDERVOLTAGE_THRESH",32.0], #unknown (from disassembly) (0x52>>1):["MAYBE_OVERVOLTAGE_THRESH",32.0], #unknown (from disassembly) #reading 0x57 reads internal EEPROM space in CPU (just logging info, e.g. hours in use) } def readDPS1200Register(self,reg): data=self.readDPS1200(reg<<1,3) #if low bit set returns zeros (so use even # cmds) #check checksum (why not) replyCS=0 for d in data: replyCS+=ord(d) replyCS=((0xff-replyCS)+1)&0xff #check reply checksum (not really reqd) if replyCS!=0: raise Exception("Read error") data=data[:-1] value=ord(data[0]) | ord(data[1])<<8 return value def read(self): for n in range(self.numReg): try: value=self.readDPS1200Register(n) self.minReg[n]=min(self.minReg[n],value) self.maxReg[n]=max(self.maxReg[n],value) name="" if n in self.REGS: name,scale=self.REGS[n] if scale<0: scale=-scale value+=self.readDPS1200Register(n+1)<<16 else: scale=1 print "%02x\t%04x\t" % (n<<1,value ), if scale: print "%d\t%d\t%d\t(%d)\t%.3f\t%s" % (value,self.minReg[n],self.maxReg[n],self.maxReg[n]-self.minReg[n],value/scale,name ) else: print "%s\t%s" % (bin(value),name) except Exception,ex: print "r %02x er %s" % (n,ex) return addr=self.address
class Robot(object): MODE_SIMULATION = 0 MODE_CROSS_COMPILATION = 1 MODE_REMOTE_CONTROL = 2 def __init__(self): self.time = 0 self.tof = VL53L0X.VL53L0X(i2c_bus=4, i2c_address=0x29) self.tof.open() self.tof.start_ranging(VL53L0X.Vl53l0xAccuracyMode.BETTER) self.bus = SMBus(I2C_CHANNEL) self.busFT903 = SMBus(FT903_I2C_CHANNEL) self.devices = {} self.previousTime = None self.customData = '' for name in DistanceSensor.groundNames + DistanceSensor.proximityNames: self.devices[name] = DistanceSensor(name) for name in LightSensor.names: self.devices[name] = LightSensor(name) for name in PositionSensor.names: self.devices[name] = PositionSensor(name) for name in Motor.names: self.devices[name] = Motor(name) for name in LED.names: self.devices[name] = LED(name) for name in Accelerometer.names: self.devices[name] = Accelerometer(name) for name in Gyro.names: self.devices[name] = Gyro(name) self.devices['tof'] = DistanceSensor('tof') print('Starting controller.') def step(self, duration, blocking=False): if blocking: now = time.time() if self.previousTime is not None: diff = now - (self.previousTime + 0.001 * duration) if diff < 0: time.sleep(-diff) self.time += 0.001 * duration else: self.time += diff + 0.001 * duration else: self.time += 0.001 * duration self.previousTime = time.time() self.devices['tof'].value = self.tof.get_distance() / 1000.0 actuatorsData = [] # left motor # 0.00628 = (2 * pi) / encoder_resolution leftSpeed = int(self.devices['left wheel motor'].getVelocity() / 0.0068) actuatorsData.append(leftSpeed & 0xFF) actuatorsData.append((leftSpeed >> 8) & 0xFF) # right motor rightSpeed = int(self.devices['right wheel motor'].getVelocity() / 0.0068) actuatorsData.append(rightSpeed & 0xFF) actuatorsData.append((rightSpeed >> 8) & 0xFF) # speaker sound actuatorsData.append(0) # LED1, LED3, LED5, LED7 on/off flag actuatorsData.append((self.devices['led0'].value & 0x1) | (self.devices['led2'].value & 0x2) | (self.devices['led4'].value & 0x4) | (self.devices['led6'].value & 0x8)) # LED2 R/G/B actuatorsData.append( int(((self.devices['led1'].value >> 16) & 0xFF) / 2.55)) actuatorsData.append( int(((self.devices['led1'].value >> 8) & 0xFF) / 2.55)) actuatorsData.append(int((self.devices['led1'].value & 0xFF) / 2.55)) # LED4 R/G/B actuatorsData.append( int(((self.devices['led3'].value >> 16) & 0xFF) / 2.55)) actuatorsData.append( int(((self.devices['led3'].value >> 8) & 0xFF) / 2.55)) actuatorsData.append(int((self.devices['led3'].value & 0xFF) / 2.55)) # LED6 R/G/B actuatorsData.append( int(((self.devices['led5'].value >> 16) & 0xFF) / 2.55)) actuatorsData.append( int(((self.devices['led5'].value >> 8) & 0xFF) / 2.55)) actuatorsData.append(int((self.devices['led5'].value & 0xFF) / 2.55)) # LED8 R/G/B actuatorsData.append( int(((self.devices['led7'].value >> 16) & 0xFF) / 2.55)) actuatorsData.append( int(((self.devices['led7'].value >> 8) & 0xFF) / 2.55)) actuatorsData.append(int((self.devices['led7'].value & 0xFF) / 2.55)) # Settings actuatorsData.append(0) # Checksum checksum = 0 for data in actuatorsData: checksum ^= data actuatorsData.append(checksum) if len(actuatorsData) != ACTUATORS_SIZE: sys.exit('Wrond actuator data size.') # communication with i2c bus with main board address write = i2c_msg.write(MAIN_ADDR, actuatorsData) read = i2c_msg.read(MAIN_ADDR, SENSORS_SIZE) try: self.bus.i2c_rdwr(write, read) except: return sensorsData = list(read) checksum = 0 for data in sensorsData: checksum ^= checksum ^ data if sensorsData[SENSORS_SIZE - 1] != checksum: print('Wrogn receiving checksum') return if len(sensorsData) != SENSORS_SIZE: sys.exit('Wrond actuator data size.') # Read and assign DistanceSensor values for i in range(8): self.devices[DistanceSensor.proximityNames[i]].value = ( sensorsData[i * 2] & 0x00FF) | ( (sensorsData[i * 2 + 1] << 8) & 0xFF00) # Read and assign LightSensor values for i in range(8): self.devices[LightSensor.names[i]].value = sensorsData[ i * 2 + 16] + (sensorsData[i * 2 + 17] << 8) # Read and assign PositionSensor values for i in range(2): val = (sensorsData[i * 2 + 41] & 0x00FF) | ( (sensorsData[i * 2 + 42] << 8) & 0xFF00) # Pythonic way to get signed 16bit integers :O if val > 2**15: val -= 2**16 # 159.23 = encoder_resolution/ (2 * pi) self.devices[PositionSensor.names[i]].value = val / 159.23 # communication with the pi-puck extension FT903 address mapping = [2, 1, 0] for i in range(3): ledName = 'pi-puck led %d' % i if self.devices[ledName].changed: ledValue = ( (0x01 if ((self.devices[ledName].value >> 16) & 0xFF) > 0 else 0) | (0x02 if ((self.devices[ledName].value >> 8) & 0xFF) > 0 else 0) | (0x04 if (self.devices[ledName].value & 0xFF) > 0 else 0)) self.busFT903.write_byte_data(FT903_ADDR, mapping[i], ledValue) self.devices[ledName].changed = False # communication with i2c bus with ground sensors board address groundSensorsEnabled = False for name in DistanceSensor.groundNames: if self.devices[name].getSamplingPeriod() > 0: groundSensorsEnabled = True break if groundSensorsEnabled: read = i2c_msg.read(GROUND_ADDR, GROUND_SENSORS_SIZE) try: self.bus.i2c_rdwr(read) except: return groundData = list(read) for i in range(3): self.devices[DistanceSensor.groundNames[i]].value = ( groundData[i * 2] << 8) + groundData[i * 2 + 1] # communication with the i2c bus with the extension board address # if self.devices['accelerometer'].getSamplingPeriod() > 0 or self.devices['gyro'].getSamplingPeriod() > 0: # imuRead = i2c_msg.read(IMU_ADDR, IMU_SIZE) # self.bus.i2c_rdwr(imuRead) # imuData = list(imuRead) # if len(imuData) != IMU_SIZE: # sys.exit('Wrond IMU data size.') # print(imuData) def getAccelerometer(self, name): if name in self.devices and isinstance(self.devices[name], Accelerometer): return self.devices[name] print('No Accelerometer device named "%s"\n' % name) return None def getDistanceSensor(self, name): if name in self.devices and isinstance(self.devices[name], DistanceSensor): return self.devices[name] print('No DistanceSensor device named "%s"\n' % name) return None def getLED(self, name): if name in self.devices and isinstance(self.devices[name], LED): return self.devices[name] print('No LED device named "%s"\n' % name) return None def getLightSensor(self, name): if name in self.devices and isinstance(self.devices[name], LightSensor): return self.devices[name] print('No LightSensor device named "%s"\n' % name) return None def getMotor(self, name): if name in self.devices and isinstance(self.devices[name], Motor): return self.devices[name] print('No Motor device named "%s"\n' % name) return None def getPositionSensor(self, name): if name in self.devices and isinstance(self.devices[name], PositionSensor): return self.devices[name] print('No PositionSensor device named "%s"\n' % name) return None def getName(self): return 'e-puck' def getTime(self): return self.time def getSupervisor(self): return False def getSynchronization(self): return False def getBasicTimeStep(self): return 32 def getNumberOfDevices(self): return len(self.devices) def getDeviceByIndex(self, index): return self.devices[index] def getMode(self): return Robot.MODE_CROSS_COMPILATION def getCustomData(self): return self.customData def setCustomData(self, data): self.customData = data
class VL53L1X: """VL53L1X ToF.""" def __init__(self, i2c_bus=1, i2c_address=0x29, tca9548a_num=255, tca9548a_addr=0): """Initialize the VL53L1X ToF Sensor from ST""" self._i2c_bus = i2c_bus self.i2c_address = i2c_address self._tca9548a_num = tca9548a_num self._tca9548a_addr = tca9548a_addr self._i2c = SMBus(i2c_bus) try: if tca9548a_num == 255: self._i2c.read_byte_data(self.i2c_address, 0x00) except IOError: raise RuntimeError("VL53L1X not found on adddress: {:02x}".format( self.i2c_address)) self._dev = None # Register Address self.ADDR_UNIT_ID_HIGH = 0x16 # Serial number high byte self.ADDR_UNIT_ID_LOW = 0x17 # Serial number low byte self.ADDR_I2C_ID_HIGH = 0x18 # Write serial number high byte for I2C address unlock self.ADDR_I2C_ID_LOW = 0x19 # Write serial number low byte for I2C address unlock self.ADDR_I2C_SEC_ADDR = 0x8a # Write new I2C address after unlock def open(self, reset=False): self._i2c.open(bus=self._i2c_bus) self._configure_i2c_library_functions() self._dev = _TOF_LIBRARY.initialise(self.i2c_address, self._tca9548a_num, self._tca9548a_addr, reset) def close(self): self._i2c.close() self._dev = None def _configure_i2c_library_functions(self): # I2C bus read callback for low level library. def _i2c_read(address, reg, data_p, length): ret_val = 0 msg_w = i2c_msg.write(address, [reg >> 8, reg & 0xff]) msg_r = i2c_msg.read(address, length) self._i2c.i2c_rdwr(msg_w, msg_r) if ret_val == 0: for index in range(length): data_p[index] = ord(msg_r.buf[index]) return ret_val # I2C bus write callback for low level library. def _i2c_write(address, reg, data_p, length): ret_val = 0 data = [] for index in range(length): data.append(data_p[index]) msg_w = i2c_msg.write(address, [reg >> 8, reg & 0xff] + data) self._i2c.i2c_rdwr(msg_w) return ret_val # I2C bus write callback for low level library. def _i2c_multi(address, reg): ret_val = 0 # Write to the multiplexer self._i2c.write_byte(address, reg) return ret_val # Pass i2c read/write function pointers to VL53L1X library. self._i2c_multi_func = _I2C_MULTI_FUNC(_i2c_multi) self._i2c_read_func = _I2C_READ_FUNC(_i2c_read) self._i2c_write_func = _I2C_WRITE_FUNC(_i2c_write) _TOF_LIBRARY.VL53L1_set_i2c(self._i2c_multi_func, self._i2c_read_func, self._i2c_write_func) def start_ranging(self, mode=VL53L1xDistanceMode.LONG): """Start VL53L1X ToF Sensor Ranging""" _TOF_LIBRARY.startRanging(self._dev, mode) def set_distance_mode(self, mode): """Set distance mode :param mode: One of 1 = Short, 2 = Medium or 3 = Long """ _TOF_LIBRARY.setDistanceMode(self._dev, mode) def stop_ranging(self): """Stop VL53L1X ToF Sensor Ranging""" _TOF_LIBRARY.stopRanging(self._dev) def get_distance(self): """Get distance from VL53L1X ToF Sensor""" return _TOF_LIBRARY.getDistance(self._dev) def set_timing(self, timing_budget, inter_measurement_period): """Set the timing budget and inter measurement period. A higher timing budget results in greater measurement accuracy, but also a higher power consumption. The inter measurement period must be >= the timing budget, otherwise it will be double the expected value. :param timing_budget: Timing budget in microseconds :param inter_measurement_period: Inter Measurement Period in milliseconds """ if (inter_measurement_period * 1000) < timing_budget: raise ValueError( "The Inter Measurement Period must be >= Timing Budget") self.set_timing_budget(timing_budget) self.set_inter_measurement_period(inter_measurement_period) def set_timing_budget(self, timing_budget): """Set the timing budget in microseocnds""" _TOF_LIBRARY.setMeasurementTimingBudgetMicroSeconds( self._dev, timing_budget) def set_inter_measurement_period(self, period): """Set the inter-measurement period in milliseconds""" _TOF_LIBRARY.setInterMeasurementPeriodMilliSeconds(self._dev, period) # This function included to show how to access the ST library directly # from python instead of through the simplified interface def get_timing(self): budget = c_uint(0) budget_p = pointer(budget) status = _TOF_LIBRARY.VL53L1_GetMeasurementTimingBudgetMicroSeconds( self._dev, budget_p) if status == 0: return budget.value + 1000 else: return 0 def change_address(self, new_address): status = _TOF_LIBRARY.setDeviceAddress(self._dev, new_address) if status == 0: self.i2c_address = new_address else: raise RuntimeError( "change_address failed with code: {}".format(status)) return True def get_RangingMeasurementData(self): ''' Returns the full RangingMeasurementData structure. (it ignores the fields that are not implemented according to en.DM00474730.pdf) ''' contents = _getRangingMeasurementData(self._dev).contents return { 'StreamCount': contents.StreamCount, 'SignalRateRtnMegaCps': contents.SignalRateRtnMegaCps / 65536, 'AmbientRateRtnMegaCps': contents.AmbientRateRtnMegaCps / 65536, 'EffectiveSpadRtnCount': contents.EffectiveSpadRtnCount / 256, 'SigmaMilliMeter': contents.SigmaMilliMeter / 65536, 'RangeMilliMeter': contents.RangeMilliMeter, 'RangeStatus': contents.RangeStatus }