diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index fff3aa9..1dad804 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -40,7 +40,7 @@ jobs: source actions-ci/install.sh - name: Pip install pylint, black, & Sphinx run: | - pip install --force-reinstall pylint==1.9.2 black==19.10b0 Sphinx sphinx-rtd-theme + pip install --force-reinstall pylint black==19.10b0 Sphinx sphinx-rtd-theme - name: Library version run: git describe --dirty --always --tags - name: PyLint diff --git a/adafruit_mlx90640.py b/adafruit_mlx90640.py index 0920b5c..8edb0db 100644 --- a/adafruit_mlx90640.py +++ b/adafruit_mlx90640.py @@ -56,8 +56,10 @@ MLX90640_DEVICEID1 = 0x2407 OPENAIR_TA_SHIFT = 8 -class RefreshRate: # pylint: disable=too-few-public-methods + +class RefreshRate: # pylint: disable=too-few-public-methods """ Enum-like class for MLX90640's refresh rate """ + REFRESH_0_5_HZ = 0b000 # 0.5Hz REFRESH_1_HZ = 0b001 # 1Hz REFRESH_2_HZ = 0b010 # 2Hz @@ -67,8 +69,10 @@ class RefreshRate: # pylint: disable=too-few-public-methods REFRESH_32_HZ = 0b110 # 32Hz REFRESH_64_HZ = 0b111 # 64Hz -class MLX90640: # pylint: disable=too-many-instance-attributes + +class MLX90640: # pylint: disable=too-many-instance-attributes """Interface to the MLX90640 temperature sensor.""" + kVdd = 0 vdd25 = 0 KvPTAT = 0 @@ -100,7 +104,7 @@ class MLX90640: # pylint: disable=too-many-instance-attributes def __init__(self, i2c_bus, address=0x33): self.i2c_device = I2CDevice(i2c_bus, address) self._I2CReadWords(0x2400, eeData) - #print(eeData) + # print(eeData) self._ExtractParameters() @property @@ -125,7 +129,7 @@ def refresh_rate(self, rate): value = (rate & 0x7) << 7 self._I2CReadWords(0x800D, controlRegister) value |= controlRegister[0] & 0xFC7F - self._I2CWriteWord(0x800d, value) + self._I2CWriteWord(0x800D, value) def getFrame(self, framebuf): """ Request both 'halves' of a frame from the sensor, merge them @@ -152,16 +156,16 @@ def _GetFrameData(self, frameData): while dataReady == 0: self._I2CReadWords(0x8000, statusRegister) dataReady = statusRegister[0] & 0x0008 - #print("ready status: 0x%x" % dataReady) + # print("ready status: 0x%x" % dataReady) while (dataReady != 0) and (cnt < 5): self._I2CWriteWord(0x8000, 0x0030) - #print("Read frame", cnt) + # print("Read frame", cnt) self._I2CReadWords(0x0400, frameData, end=832) self._I2CReadWords(0x8000, statusRegister) dataReady = statusRegister[0] & 0x0008 - #print("frame ready: 0x%x" % dataReady) + # print("frame ready: 0x%x" % dataReady) cnt += 1 if cnt > 4: @@ -184,7 +188,7 @@ def _GetTa(self, frameData): ptatArt -= 65536 ptatArt = (ptat / (ptat * self.alphaPTAT + ptatArt)) * math.pow(2, 18) - ta = (ptatArt / (1 + self.KvPTAT * (vdd - 3.3)) - self.vPTAT25) + ta = ptatArt / (1 + self.KvPTAT * (vdd - 3.3)) - self.vPTAT25 ta = ta / self.KtPTAT + 25 return ta @@ -194,7 +198,9 @@ def _GetVdd(self, frameData): vdd -= 65536 resolutionRAM = (frameData[832] & 0x0C00) >> 10 - resolutionCorrection = math.pow(2, self.resolutionEE) / math.pow(2, resolutionRAM) + resolutionCorrection = math.pow(2, self.resolutionEE) / math.pow( + 2, resolutionRAM + ) vdd = (resolutionCorrection * vdd - self.vdd25) / self.kVdd + 3.3 return vdd @@ -208,30 +214,30 @@ def _CalculateTo(self, frameData, emissivity, tr, result): vdd = self._GetVdd(frameData) ta = self._GetTa(frameData) - ta4 = (ta + 273.15) + ta4 = ta + 273.15 ta4 = ta4 * ta4 ta4 = ta4 * ta4 - tr4 = (tr + 273.15) + tr4 = tr + 273.15 tr4 = tr4 * tr4 tr4 = tr4 * tr4 - taTr = tr4 - (tr4-ta4)/emissivity + taTr = tr4 - (tr4 - ta4) / emissivity ktaScale = math.pow(2, self.ktaScale) kvScale = math.pow(2, self.kvScale) alphaScale = math.pow(2, self.alphaScale) - alphaCorrR[0] = 1 / (1 +self.ksTo[0] * 40) + alphaCorrR[0] = 1 / (1 + self.ksTo[0] * 40) alphaCorrR[1] = 1 - alphaCorrR[2] = (1 + self.ksTo[1] * self.ct[2]) + alphaCorrR[2] = 1 + self.ksTo[1] * self.ct[2] alphaCorrR[3] = alphaCorrR[2] * (1 + self.ksTo[2] * (self.ct[3] - self.ct[2])) - #--------- Gain calculation ----------------------------------- + # --------- Gain calculation ----------------------------------- gain = frameData[778] if gain > 32767: gain -= 65536 gain = self.gainEE / gain - #--------- To calculation ------------------------------------- + # --------- To calculation ------------------------------------- mode = (frameData[832] & 0x1000) >> 5 irDataCP[0] = frameData[776] @@ -241,23 +247,33 @@ def _CalculateTo(self, frameData, emissivity, tr, result): irDataCP[i] -= 65536 irDataCP[i] *= gain - irDataCP[0] -= self.cpOffset[0]*(1 + self.cpKta*(ta - 25)) * (1 + self.cpKv*(vdd - 3.3)) - if mode == self.calibrationModeEE: - irDataCP[1] -= (self.cpOffset[1] * - (1 + self.cpKta * (ta - 25)) * - (1 + self.cpKv * (vdd - 3.3))) + irDataCP[0] -= ( + self.cpOffset[0] + * (1 + self.cpKta * (ta - 25)) + * (1 + self.cpKv * (vdd - 3.3)) + ) + if mode == self.calibrationModeEE: + irDataCP[1] -= ( + self.cpOffset[1] + * (1 + self.cpKta * (ta - 25)) + * (1 + self.cpKv * (vdd - 3.3)) + ) else: - irDataCP[1] -= ((self.cpOffset[1] + self.ilChessC[0]) * - (1 + self.cpKta * (ta - 25)) * - (1 + self.cpKv * (vdd - 3.3))) + irDataCP[1] -= ( + (self.cpOffset[1] + self.ilChessC[0]) + * (1 + self.cpKta * (ta - 25)) + * (1 + self.cpKv * (vdd - 3.3)) + ) for pixelNumber in range(768): ilPattern = pixelNumber // 32 - (pixelNumber // 64) * 2 - chessPattern = ilPattern ^ (pixelNumber - (pixelNumber//2)*2) - conversionPattern = ((pixelNumber + 2) // 4 - - (pixelNumber + 3) // 4 + - (pixelNumber + 1) // 4 - - pixelNumber // 4) * (1 - 2 * ilPattern) + chessPattern = ilPattern ^ (pixelNumber - (pixelNumber // 2) * 2) + conversionPattern = ( + (pixelNumber + 2) // 4 + - (pixelNumber + 3) // 4 + + (pixelNumber + 1) // 4 + - pixelNumber // 4 + ) * (1 - 2 * ilPattern) if mode == 0: pattern = ilPattern @@ -270,28 +286,44 @@ def _CalculateTo(self, frameData, emissivity, tr, result): irData -= 65536 irData *= gain - kta = self.kta[pixelNumber]/ktaScale - kv = self.kv[pixelNumber]/kvScale - irData -= (self.offset[pixelNumber] * - (1 + kta*(ta - 25)) * - (1 + kv*(vdd - 3.3))) + kta = self.kta[pixelNumber] / ktaScale + kv = self.kv[pixelNumber] / kvScale + irData -= ( + self.offset[pixelNumber] + * (1 + kta * (ta - 25)) + * (1 + kv * (vdd - 3.3)) + ) if mode != self.calibrationModeEE: - irData += (self.ilChessC[2] * (2 * ilPattern - 1) - - self.ilChessC[1] * conversionPattern) + irData += ( + self.ilChessC[2] * (2 * ilPattern - 1) + - self.ilChessC[1] * conversionPattern + ) irData = irData - self.tgc * irDataCP[subPage] irData /= emissivity - alphaCompensated = SCALEALPHA * alphaScale/self.alpha[pixelNumber] - alphaCompensated *= (1 + self.KsTa * (ta - 25)) + alphaCompensated = SCALEALPHA * alphaScale / self.alpha[pixelNumber] + alphaCompensated *= 1 + self.KsTa * (ta - 25) - Sx = (alphaCompensated * alphaCompensated * - alphaCompensated * (irData + alphaCompensated * taTr)) + Sx = ( + alphaCompensated + * alphaCompensated + * alphaCompensated + * (irData + alphaCompensated * taTr) + ) Sx = math.sqrt(math.sqrt(Sx)) * self.ksTo[1] - To = math.sqrt(math.sqrt(irData/(alphaCompensated * - (1 - self.ksTo[1] * 273.15) + Sx) + taTr)) - 273.15 + To = ( + math.sqrt( + math.sqrt( + irData + / (alphaCompensated * (1 - self.ksTo[1] * 273.15) + Sx) + + taTr + ) + ) + - 273.15 + ) if To < self.ct[1]: torange = 0 @@ -302,14 +334,24 @@ def _CalculateTo(self, frameData, emissivity, tr, result): else: torange = 3 - To = math.sqrt(math.sqrt(irData / - (alphaCompensated * alphaCorrR[torange] * - (1 + self.ksTo[torange] * - (To - self.ct[torange]))) + taTr)) - 273.15 + To = ( + math.sqrt( + math.sqrt( + irData + / ( + alphaCompensated + * alphaCorrR[torange] + * (1 + self.ksTo[torange] * (To - self.ct[torange])) + ) + + taTr + ) + ) + - 273.15 + ) result[pixelNumber] = To - # pylint: enable=too-many-locals, too-many-branches, too-many-statements + # pylint: enable=too-many-locals, too-many-branches, too-many-statements def _ExtractParameters(self): self._ExtractVDDParameters() @@ -328,29 +370,29 @@ def _ExtractParameters(self): self._ExtractDeviatingPixels() # debug output - #print('-'*40) - #print("kVdd = %d, vdd25 = %d" % (self.kVdd, self.vdd25)) - #print("KvPTAT = %f, KtPTAT = %f, vPTAT25 = %d, alphaPTAT = %f" % + # print('-'*40) + # print("kVdd = %d, vdd25 = %d" % (self.kVdd, self.vdd25)) + # print("KvPTAT = %f, KtPTAT = %f, vPTAT25 = %d, alphaPTAT = %f" % # (self.KvPTAT, self.KtPTAT, self.vPTAT25, self.alphaPTAT)) - #print("Gain = %d, Tgc = %f, Resolution = %d" % (self.gainEE, self.tgc, self.resolutionEE)) - #print("KsTa = %f, ksTo = %s, ct = %s" % (self.KsTa, self.ksTo, self.ct)) - #print("cpAlpha:", self.cpAlpha, "cpOffset:", self.cpOffset) - #print("alpha: ", self.alpha) - #print("alphascale: ", self.alphaScale) - #print("offset: ", self.offset) - #print("kta:", self.kta) - #print("ktaScale:", self.ktaScale) - #print("kv:", self.kv) - #print("kvScale:", self.kvScale) - #print("calibrationModeEE:", self.calibrationModeEE) - #print("ilChessC:", self.ilChessC) - #print('-'*40) + # print("Gain = %d, Tgc = %f, Resolution = %d" % (self.gainEE, self.tgc, self.resolutionEE)) + # print("KsTa = %f, ksTo = %s, ct = %s" % (self.KsTa, self.ksTo, self.ct)) + # print("cpAlpha:", self.cpAlpha, "cpOffset:", self.cpOffset) + # print("alpha: ", self.alpha) + # print("alphascale: ", self.alphaScale) + # print("offset: ", self.offset) + # print("kta:", self.kta) + # print("ktaScale:", self.ktaScale) + # print("kv:", self.kv) + # print("kvScale:", self.kvScale) + # print("calibrationModeEE:", self.calibrationModeEE) + # print("ilChessC:", self.ilChessC) + # print('-'*40) def _ExtractVDDParameters(self): # extract VDD self.kVdd = (eeData[51] & 0xFF00) >> 8 if self.kVdd > 127: - self.kVdd -= 256 # convert to signed + self.kVdd -= 256 # convert to signed self.kVdd *= 32 self.vdd25 = eeData[51] & 0x00FF self.vdd25 = ((self.vdd25 - 256) << 5) - 8192 @@ -400,7 +442,7 @@ def _ExtractKsToParameters(self): self.ct[2] = (eeData[63] & 0x00F0) >> 4 self.ct[3] = (eeData[63] & 0x0F00) >> 8 self.ct[2] *= step - self.ct[3] = self.ct[2] + self.ct[3]*step + self.ct[3] = self.ct[2] + self.ct[3] * step KsToScale = (eeData[63] & 0x000F) + 8 KsToScale = 1 << KsToScale @@ -440,7 +482,7 @@ def _ExtractCPParameters(self): alphaSP[1] = (eeData[57] & 0xFC00) >> 10 if alphaSP[1] > 31: alphaSP[1] -= 64 - alphaSP[1] = (1 + alphaSP[1]/128) * alphaSP[0] + alphaSP[1] = (1 + alphaSP[1] / 128) * alphaSP[0] cpKta = eeData[59] & 0x00FF if cpKta > 127: @@ -466,13 +508,13 @@ def _ExtractAlphaParameters(self): accRowScale = (eeData[32] & 0x0F00) >> 8 alphaScale = ((eeData[32] & 0xF000) >> 12) + 30 alphaRef = eeData[33] - accRow = [0]*24 - accColumn = [0]*32 + accRow = [0] * 24 + accColumn = [0] * 32 alphaTemp = [0] * 768 for i in range(6): p = i * 4 - accRow[p + 0] = (eeData[34 + i] & 0x000F) + accRow[p + 0] = eeData[34 + i] & 0x000F accRow[p + 1] = (eeData[34 + i] & 0x00F0) >> 4 accRow[p + 2] = (eeData[34 + i] & 0x0F00) >> 8 accRow[p + 3] = (eeData[34 + i] & 0xF000) >> 12 @@ -483,7 +525,7 @@ def _ExtractAlphaParameters(self): for i in range(8): p = i * 4 - accColumn[p + 0] = (eeData[40 + i] & 0x000F) + accColumn[p + 0] = eeData[40 + i] & 0x000F accColumn[p + 1] = (eeData[40 + i] & 0x00F0) >> 4 accColumn[p + 2] = (eeData[40 + i] & 0x0F00) >> 8 accColumn[p + 3] = (eeData[40 + i] & 0xF000) >> 12 @@ -499,15 +541,18 @@ def _ExtractAlphaParameters(self): if alphaTemp[p] > 31: alphaTemp[p] -= 64 alphaTemp[p] *= 1 << accRemScale - alphaTemp[p] += (alphaRef + (accRow[i] << accRowScale) + - (accColumn[j] << accColumnScale)) + alphaTemp[p] += ( + alphaRef + + (accRow[i] << accRowScale) + + (accColumn[j] << accColumnScale) + ) alphaTemp[p] /= math.pow(2, alphaScale) - alphaTemp[p] -= self.tgc * (self.cpAlpha[0] + self.cpAlpha[1])/2 + alphaTemp[p] -= self.tgc * (self.cpAlpha[0] + self.cpAlpha[1]) / 2 alphaTemp[p] = SCALEALPHA / alphaTemp[p] - #print("alphaTemp: ", alphaTemp) + # print("alphaTemp: ", alphaTemp) temp = max(alphaTemp) - #print("temp", temp) + # print("temp", temp) alphaScale = 0 while temp < 32768: @@ -525,7 +570,7 @@ def _ExtractOffsetParameters(self): occRow = [0] * 24 occColumn = [0] * 32 - occRemScale = (eeData[16] & 0x000F) + occRemScale = eeData[16] & 0x000F occColumnScale = (eeData[16] & 0x00F0) >> 4 occRowScale = (eeData[16] & 0x0F00) >> 8 offsetRef = eeData[17] @@ -534,7 +579,7 @@ def _ExtractOffsetParameters(self): for i in range(6): p = i * 4 - occRow[p + 0] = (eeData[18 + i] & 0x000F) + occRow[p + 0] = eeData[18 + i] & 0x000F occRow[p + 1] = (eeData[18 + i] & 0x00F0) >> 4 occRow[p + 2] = (eeData[18 + i] & 0x0F00) >> 8 occRow[p + 3] = (eeData[18 + i] & 0xF000) >> 12 @@ -545,7 +590,7 @@ def _ExtractOffsetParameters(self): for i in range(8): p = i * 4 - occColumn[p + 0] = (eeData[24 + i] & 0x000F) + occColumn[p + 0] = eeData[24 + i] & 0x000F occColumn[p + 1] = (eeData[24 + i] & 0x00F0) >> 4 occColumn[p + 2] = (eeData[24 + i] & 0x0F00) >> 8 occColumn[p + 3] = (eeData[24 + i] & 0xF000) >> 12 @@ -561,10 +606,13 @@ def _ExtractOffsetParameters(self): if self.offset[p] > 31: self.offset[p] -= 64 self.offset[p] *= 1 << occRemScale - self.offset[p] += (offsetRef + (occRow[i] << occRowScale) + - (occColumn[j] << occColumnScale)) + self.offset[p] += ( + offsetRef + + (occRow[i] << occRowScale) + + (occColumn[j] << occColumnScale) + ) - def _ExtractKtaPixelParameters(self): # pylint: disable=too-many-locals + def _ExtractKtaPixelParameters(self): # pylint: disable=too-many-locals # extract KtaPixel KtaRC = [0] * 4 ktaTemp = [0] * 768 @@ -590,12 +638,12 @@ def _ExtractKtaPixelParameters(self): # pylint: disable=too-many-locals KtaRC[3] = KtaReCe ktaScale1 = ((eeData[56] & 0x00F0) >> 4) + 8 - ktaScale2 = (eeData[56] & 0x000F) + ktaScale2 = eeData[56] & 0x000F for i in range(24): for j in range(32): p = 32 * i + j - split = 2*(p//32 - (p//64)*2) + p%2 + split = 2 * (p // 32 - (p // 64) * 2) + p % 2 ktaTemp[p] = (eeData[64 + p] & 0x000E) >> 1 if ktaTemp[p] > 3: ktaTemp[p] -= 8 @@ -621,7 +669,6 @@ def _ExtractKtaPixelParameters(self): # pylint: disable=too-many-locals self.kta[i] = int(temp + 0.5) self.ktaScale = ktaScale1 - def _ExtractKvPixelParameters(self): KvT = [0] * 4 kvTemp = [0] * 768 @@ -651,10 +698,10 @@ def _ExtractKvPixelParameters(self): for i in range(24): for j in range(32): p = 32 * i + j - split = 2*(p//32 - (p//64)*2) + p%2 + split = 2 * (p // 32 - (p // 64) * 2) + p % 2 kvTemp[p] = KvT[split] kvTemp[p] /= math.pow(2, kvScale) - #kvTemp[p] = kvTemp[p] * mlx90640->offset[p]; + # kvTemp[p] = kvTemp[p] * mlx90640->offset[p]; temp = abs(kvTemp[0]) for kv in kvTemp: @@ -705,10 +752,10 @@ def _ExtractDeviatingPixels(self): outlierPixCnt = 0 while (pixCnt < 768) and (brokenPixCnt < 5) and (outlierPixCnt < 5): - if eeData[pixCnt+64] == 0: + if eeData[pixCnt + 64] == 0: self.brokenPixels[brokenPixCnt] = pixCnt brokenPixCnt += 1 - elif (eeData[pixCnt+64] & 0x0001) != 0: + elif (eeData[pixCnt + 64] & 0x0001) != 0: self.outlierPixels[outlierPixCnt] = pixCnt outlierPixCnt += 1 pixCnt += 1 @@ -732,15 +779,15 @@ def _I2CWriteWord(self, writeAddress, data): with self.i2c_device as i2c: i2c.write(cmd) - #print("Wrote:", [hex(i) for i in cmd]) + # print("Wrote:", [hex(i) for i in cmd]) time.sleep(0.001) self._I2CReadWords(writeAddress, dataCheck) - #print("dataCheck: 0x%x" % dataCheck[0]) - #if (dataCheck != data): + # print("dataCheck: 0x%x" % dataCheck[0]) + # if (dataCheck != data): # return -2 def _I2CReadWords(self, addr, buffer, *, end=None): - #stamp = time.monotonic() + # stamp = time.monotonic() if end is None: remainingWords = len(buffer) else: @@ -751,17 +798,21 @@ def _I2CReadWords(self, addr, buffer, *, end=None): with self.i2c_device as i2c: while remainingWords: - addrbuf[0] = addr >> 8 # MSB + addrbuf[0] = addr >> 8 # MSB addrbuf[1] = addr & 0xFF # LSB read_words = min(remainingWords, I2C_READ_LEN) - i2c.write_then_readinto(addrbuf, inbuf, in_end=read_words*2) # in bytes - #print("-> ", [hex(i) for i in addrbuf]) - outwords = struct.unpack('>' + 'H' * read_words, inbuf[0:read_words*2]) - #print("<- (", read_words, ")", [hex(i) for i in outwords]) + i2c.write_then_readinto( + addrbuf, inbuf, in_end=read_words * 2 + ) # in bytes + # print("-> ", [hex(i) for i in addrbuf]) + outwords = struct.unpack( + ">" + "H" * read_words, inbuf[0 : read_words * 2] + ) + # print("<- (", read_words, ")", [hex(i) for i in outwords]) for i, w in enumerate(outwords): - buffer[offset+i] = w + buffer[offset + i] = w offset += read_words remainingWords -= read_words addr += read_words - #print("i2c read", read_words, "words in", time.monotonic()-stamp) - #print("Read: ", [hex(i) for i in buffer[0:10]]) + # print("i2c read", read_words, "words in", time.monotonic()-stamp) + # print("Read: ", [hex(i) for i in buffer[0:10]]) diff --git a/docs/conf.py b/docs/conf.py index f1257a0..bea01de 100644 --- a/docs/conf.py +++ b/docs/conf.py @@ -2,7 +2,8 @@ import os import sys -sys.path.insert(0, os.path.abspath('..')) + +sys.path.insert(0, os.path.abspath("..")) # -- General configuration ------------------------------------------------ @@ -10,10 +11,10 @@ # extensions coming with Sphinx (named 'sphinx.ext.*') or your custom # ones. extensions = [ - 'sphinx.ext.autodoc', - 'sphinx.ext.intersphinx', - 'sphinx.ext.napoleon', - 'sphinx.ext.todo', + "sphinx.ext.autodoc", + "sphinx.ext.intersphinx", + "sphinx.ext.napoleon", + "sphinx.ext.todo", ] # TODO: Please Read! @@ -23,29 +24,40 @@ # autodoc_mock_imports = ["digitalio", "busio"] -intersphinx_mapping = {'python': ('https://docs.python.org/3.4', None),'BusDevice': ('https://circuitpython.readthedocs.io/projects/busdevice/en/latest/', None),'Register': ('https://circuitpython.readthedocs.io/projects/register/en/latest/', None),'CircuitPython': ('https://circuitpython.readthedocs.io/en/latest/', None)} +intersphinx_mapping = { + "python": ("https://docs.python.org/3.4", None), + "BusDevice": ( + "https://circuitpython.readthedocs.io/projects/busdevice/en/latest/", + None, + ), + "Register": ( + "https://circuitpython.readthedocs.io/projects/register/en/latest/", + None, + ), + "CircuitPython": ("https://circuitpython.readthedocs.io/en/latest/", None), +} # Add any paths that contain templates here, relative to this directory. -templates_path = ['_templates'] +templates_path = ["_templates"] -source_suffix = '.rst' +source_suffix = ".rst" # The master toctree document. -master_doc = 'index' +master_doc = "index" # General information about the project. -project = u'Adafruit MLX90640 Library' -copyright = u'2019 ladyada' -author = u'ladyada' +project = u"Adafruit MLX90640 Library" +copyright = u"2019 ladyada" +author = u"ladyada" # The version info for the project you're documenting, acts as replacement for # |version| and |release|, also used in various other places throughout the # built documents. # # The short X.Y version. -version = u'1.0' +version = u"1.0" # The full version, including alpha/beta/rc tags. -release = u'1.0' +release = u"1.0" # The language for content autogenerated by Sphinx. Refer to documentation # for a list of supported languages. @@ -57,7 +69,7 @@ # List of patterns, relative to source directory, that match files and # directories to ignore when looking for source files. # This patterns also effect to html_static_path and html_extra_path -exclude_patterns = ['_build', 'Thumbs.db', '.DS_Store', '.env', 'CODE_OF_CONDUCT.md'] +exclude_patterns = ["_build", "Thumbs.db", ".DS_Store", ".env", "CODE_OF_CONDUCT.md"] # The reST default role (used for this markup: `text`) to use for all # documents. @@ -69,7 +81,7 @@ add_function_parentheses = True # The name of the Pygments (syntax highlighting) style to use. -pygments_style = 'sphinx' +pygments_style = "sphinx" # If true, `todo` and `todoList` produce output, else they produce nothing. todo_include_todos = False @@ -84,59 +96,62 @@ # The theme to use for HTML and HTML Help pages. See the documentation for # a list of builtin themes. # -on_rtd = os.environ.get('READTHEDOCS', None) == 'True' +on_rtd = os.environ.get("READTHEDOCS", None) == "True" if not on_rtd: # only import and set the theme if we're building docs locally try: import sphinx_rtd_theme - html_theme = 'sphinx_rtd_theme' - html_theme_path = [sphinx_rtd_theme.get_html_theme_path(), '.'] + + html_theme = "sphinx_rtd_theme" + html_theme_path = [sphinx_rtd_theme.get_html_theme_path(), "."] except: - html_theme = 'default' - html_theme_path = ['.'] + html_theme = "default" + html_theme_path = ["."] else: - html_theme_path = ['.'] + html_theme_path = ["."] # Add any paths that contain custom static files (such as style sheets) here, # relative to this directory. They are copied after the builtin static files, # so a file named "default.css" will overwrite the builtin "default.css". -html_static_path = ['_static'] +html_static_path = ["_static"] # The name of an image file (relative to this directory) to use as a favicon of # the docs. This file should be a Windows icon file (.ico) being 16x16 or 32x32 # pixels large. # -html_favicon = '_static/favicon.ico' +html_favicon = "_static/favicon.ico" # Output file base name for HTML help builder. -htmlhelp_basename = 'AdafruitMlx90640Librarydoc' +htmlhelp_basename = "AdafruitMlx90640Librarydoc" # -- Options for LaTeX output --------------------------------------------- latex_elements = { - # The paper size ('letterpaper' or 'a4paper'). - # - # 'papersize': 'letterpaper', - - # The font size ('10pt', '11pt' or '12pt'). - # - # 'pointsize': '10pt', - - # Additional stuff for the LaTeX preamble. - # - # 'preamble': '', - - # Latex figure (float) alignment - # - # 'figure_align': 'htbp', + # The paper size ('letterpaper' or 'a4paper'). + # + # 'papersize': 'letterpaper', + # The font size ('10pt', '11pt' or '12pt'). + # + # 'pointsize': '10pt', + # Additional stuff for the LaTeX preamble. + # + # 'preamble': '', + # Latex figure (float) alignment + # + # 'figure_align': 'htbp', } # Grouping the document tree into LaTeX files. List of tuples # (source start file, target name, title, # author, documentclass [howto, manual, or own class]). latex_documents = [ - (master_doc, 'AdafruitMLX90640Library.tex', u'AdafruitMLX90640 Library Documentation', - author, 'manual'), + ( + master_doc, + "AdafruitMLX90640Library.tex", + u"AdafruitMLX90640 Library Documentation", + author, + "manual", + ), ] # -- Options for manual page output --------------------------------------- @@ -144,8 +159,13 @@ # One entry per manual page. List of tuples # (source start file, name, description, authors, manual section). man_pages = [ - (master_doc, 'AdafruitMLX90640library', u'Adafruit MLX90640 Library Documentation', - [author], 1) + ( + master_doc, + "AdafruitMLX90640library", + u"Adafruit MLX90640 Library Documentation", + [author], + 1, + ) ] # -- Options for Texinfo output ------------------------------------------- @@ -154,7 +174,13 @@ # (source start file, target name, title, author, # dir menu entry, description, category) texinfo_documents = [ - (master_doc, 'AdafruitMLX90640Library', u'Adafruit MLX90640 Library Documentation', - author, 'AdafruitMLX90640Library', 'One line description of project.', - 'Miscellaneous'), + ( + master_doc, + "AdafruitMLX90640Library", + u"Adafruit MLX90640 Library Documentation", + author, + "AdafruitMLX90640Library", + "One line description of project.", + "Miscellaneous", + ), ] diff --git a/examples/mlx90640_camtest.py b/examples/mlx90640_camtest.py index e1e851c..1287d48 100644 --- a/examples/mlx90640_camtest.py +++ b/examples/mlx90640_camtest.py @@ -7,8 +7,8 @@ import time import board import busio -import pygame from PIL import Image +import pygame import adafruit_mlx90640 @@ -17,54 +17,64 @@ # MUST set I2C freq to 1MHz in /boot/config.txt i2c = busio.I2C(board.SCL, board.SDA) -#low range of the sensor (this will be black on the screen) -MINTEMP = 20. -#high range of the sensor (this will be white on the screen) -MAXTEMP = 50. +# low range of the sensor (this will be black on the screen) +MINTEMP = 20.0 +# high range of the sensor (this will be white on the screen) +MAXTEMP = 50.0 # set up display -os.environ['SDL_FBDEV'] = "/dev/fb0" -os.environ['SDL_VIDEODRIVER'] = "fbcon" +os.environ["SDL_FBDEV"] = "/dev/fb0" +os.environ["SDL_VIDEODRIVER"] = "fbcon" pygame.init() -screen = pygame.display.set_mode((0,0), pygame.FULLSCREEN) +screen = pygame.display.set_mode((0, 0), pygame.FULLSCREEN) print(pygame.display.Info()) -#the list of colors we can choose from +# the list of colors we can choose from heatmap = ( (0.0, (0, 0, 0)), - (0.20, (0, 0, .5)), - (0.40, (0, .5, 0)), - (0.60, (.5, 0, 0)), - (0.80, (.75, .75, 0)), - (0.90, (1.0, .75, 0)), + (0.20, (0, 0, 0.5)), + (0.40, (0, 0.5, 0)), + (0.60, (0.5, 0, 0)), + (0.80, (0.75, 0.75, 0)), + (0.90, (1.0, 0.75, 0)), (1.00, (1.0, 1.0, 1.0)), ) -#how many color values we can have +# how many color values we can have COLORDEPTH = 1000 colormap = [0] * COLORDEPTH -#some utility functions +# some utility functions def constrain(val, min_val, max_val): return min(max_val, max(min_val, val)) + def map_value(x, in_min, in_max, out_min, out_max): return (x - in_min) * (out_max - out_min) / (in_max - in_min) + out_min + def gaussian(x, a, b, c, d=0): - return a * math.exp(-(x - b)**2 / (2 * c**2)) + d + return a * math.exp(-((x - b) ** 2) / (2 * c ** 2)) + d + def gradient(x, width, cmap, spread=1): width = float(width) - r = sum([gaussian(x, p[1][0], p[0] * width, width/(spread*len(cmap))) for p in cmap]) - g = sum([gaussian(x, p[1][1], p[0] * width, width/(spread*len(cmap))) for p in cmap]) - b = sum([gaussian(x, p[1][2], p[0] * width, width/(spread*len(cmap))) for p in cmap]) - r = int(constrain(r*255, 0, 255)) - g = int(constrain(g*255, 0, 255)) - b = int(constrain(b*255, 0, 255)) + r = sum( + [gaussian(x, p[1][0], p[0] * width, width / (spread * len(cmap))) for p in cmap] + ) + g = sum( + [gaussian(x, p[1][1], p[0] * width, width / (spread * len(cmap))) for p in cmap] + ) + b = sum( + [gaussian(x, p[1][2], p[0] * width, width / (spread * len(cmap))) for p in cmap] + ) + r = int(constrain(r * 255, 0, 255)) + g = int(constrain(g * 255, 0, 255)) + b = int(constrain(b * 255, 0, 255)) return r, g, b + for i in range(COLORDEPTH): colormap[i] = gradient(i, COLORDEPTH, heatmap) @@ -76,12 +86,12 @@ def gradient(x, width, cmap, spread=1): sensorout = pygame.Surface((32, 24)) -#initialize the sensor +# initialize the sensor mlx = adafruit_mlx90640.MLX90640(i2c) print("MLX addr detected on I2C, Serial #", [hex(i) for i in mlx.serial_number]) mlx.refresh_rate = adafruit_mlx90640.RefreshRate.REFRESH_32_HZ print(mlx.refresh_rate) -print("Refresh rate: ", pow(2, (mlx.refresh_rate-1)), "Hz") +print("Refresh rate: ", pow(2, (mlx.refresh_rate - 1)), "Hz") frame = [0] * 768 while True: @@ -89,27 +99,29 @@ def gradient(x, width, cmap, spread=1): try: mlx.getFrame(frame) except ValueError: - continue # these happen, no biggie - retry + continue # these happen, no biggie - retry - print("Read 2 frames in %0.2f s" % (time.monotonic()-stamp)) + print("Read 2 frames in %0.2f s" % (time.monotonic() - stamp)) pixels = [0] * 768 for i, pixel in enumerate(frame): coloridx = map_value(pixel, MINTEMP, MAXTEMP, 0, COLORDEPTH - 1) - coloridx = int(constrain(coloridx, 0, COLORDEPTH-1)) + coloridx = int(constrain(coloridx, 0, COLORDEPTH - 1)) pixels[i] = colormap[coloridx] for h in range(24): for w in range(32): - pixel = pixels[h*32 + w] + pixel = pixels[h * 32 + w] sensorout.set_at((w, h), pixel) - #pixelrgb = [colors[constrain(int(pixel), 0, COLORDEPTH-1)] for pixel in pixels] - img = Image.new('RGB', (32, 24)) + # pixelrgb = [colors[constrain(int(pixel), 0, COLORDEPTH-1)] for pixel in pixels] + img = Image.new("RGB", (32, 24)) img.putdata(pixels) - img = img.resize((32*INTERPOLATE, 24*INTERPOLATE), Image.BICUBIC) + img = img.resize((32 * INTERPOLATE, 24 * INTERPOLATE), Image.BICUBIC) img_surface = pygame.image.fromstring(img.tobytes(), img.size, img.mode) pygame.transform.scale(img_surface.convert(), screen.get_size(), screen) pygame.display.update() - print("Completed 2 frames in %0.2f s (%d FPS)" % - (time.monotonic()-stamp, 1.0 / (time.monotonic()-stamp))) + print( + "Completed 2 frames in %0.2f s (%d FPS)" + % (time.monotonic() - stamp, 1.0 / (time.monotonic() - stamp)) + ) diff --git a/examples/mlx90640_pygamer.py b/examples/mlx90640_pygamer.py index f8e0989..6b22401 100644 --- a/examples/mlx90640_pygamer.py +++ b/examples/mlx90640_pygamer.py @@ -7,60 +7,77 @@ from adafruit_display_text.label import Label from simpleio import map_range -number_of_colors = 64 # Number of color in the gradian -last_color = number_of_colors-1 # Last color in palette +number_of_colors = 64 # Number of color in the gradian +last_color = number_of_colors - 1 # Last color in palette palette = displayio.Palette(number_of_colors) # Palette with all our colors ## Heatmap code inspired from: http://www.andrewnoske.com/wiki/Code_-_heatmaps_and_color_gradients -color_A = [[0, 0, 0], [0, 0, 255], [0, 255, 255], [0, 255, 0], [255, 255, 0], \ - [255, 0, 0], [255, 255, 255]] -color_B = [[0, 0, 255], [0, 255, 255] , [0, 255, 0], [255, 255, 0], [255, 0, 0]] +color_A = [ + [0, 0, 0], + [0, 0, 255], + [0, 255, 255], + [0, 255, 0], + [255, 255, 0], + [255, 0, 0], + [255, 255, 255], +] +color_B = [[0, 0, 255], [0, 255, 255], [0, 255, 0], [255, 255, 0], [255, 0, 0]] color_C = [[0, 0, 0], [255, 255, 255]] color_D = [[0, 0, 255], [255, 0, 0]] color = color_B -NUM_COLORS = len (color) +NUM_COLORS = len(color) + def MakeHeatMapColor(): for c in range(number_of_colors): - value = c * (NUM_COLORS-1) / last_color - idx1 = int(value) # Our desired color will be after this index. - if idx1 == value : # This is the corner case - red = color[idx1][0] + value = c * (NUM_COLORS - 1) / last_color + idx1 = int(value) # Our desired color will be after this index. + if idx1 == value: # This is the corner case + red = color[idx1][0] green = color[idx1][1] - blue = color[idx1][2] + blue = color[idx1][2] else: - idx2 = idx1+1 # ... and before this index (inclusive). - fractBetween = value - idx1 # Distance between the two indexes (0-1). - red = int(round((color[idx2][0] - color[idx1][0]) * fractBetween + color[idx1][0])) - green = int(round((color[idx2][1] - color[idx1][1]) * fractBetween + color[idx1][1])) - blue = int(round((color[idx2][2] - color[idx1][2]) * fractBetween + color[idx1][2])) - palette[c]= ( 0x010000 * red ) + ( 0x000100 * green ) + ( 0x000001 * blue ) + idx2 = idx1 + 1 # ... and before this index (inclusive). + fractBetween = value - idx1 # Distance between the two indexes (0-1). + red = int( + round((color[idx2][0] - color[idx1][0]) * fractBetween + color[idx1][0]) + ) + green = int( + round((color[idx2][1] - color[idx1][1]) * fractBetween + color[idx1][1]) + ) + blue = int( + round((color[idx2][2] - color[idx1][2]) * fractBetween + color[idx1][2]) + ) + palette[c] = (0x010000 * red) + (0x000100 * green) + (0x000001 * blue) + MakeHeatMapColor() # Bitmap for colour coded thermal value -image_bitmap = displayio.Bitmap( 32, 24, number_of_colors ) +image_bitmap = displayio.Bitmap(32, 24, number_of_colors) # Create a TileGrid using the Bitmap and Palette -image_tile= displayio.TileGrid(image_bitmap, pixel_shader=palette) +image_tile = displayio.TileGrid(image_bitmap, pixel_shader=palette) # Create a Group that scale 32*24 to 128*96 image_group = displayio.Group(scale=4) image_group.append(image_tile) -scale_bitmap = displayio.Bitmap( number_of_colors, 1, number_of_colors ) +scale_bitmap = displayio.Bitmap(number_of_colors, 1, number_of_colors) # Create a Group Scale must be 128 divided by number_of_colors scale_group = displayio.Group(scale=2) -scale_tile = displayio.TileGrid(scale_bitmap, pixel_shader=palette, x = 0, y = 60) +scale_tile = displayio.TileGrid(scale_bitmap, pixel_shader=palette, x=0, y=60) scale_group.append(scale_tile) for i in range(number_of_colors): - scale_bitmap[i, 0] = i # Fill the scale with the palette gradian + scale_bitmap[i, 0] = i # Fill the scale with the palette gradian # Create the super Group group = displayio.Group() -min_label = Label(terminalio.FONT, max_glyphs=10, color=palette[0], x = 0, y = 110) -max_label = Label(terminalio.FONT, max_glyphs=10, color=palette[last_color], x = 80, y = 110) +min_label = Label(terminalio.FONT, max_glyphs=10, color=palette[0], x=0, y=110) +max_label = Label( + terminalio.FONT, max_glyphs=10, color=palette[last_color], x=80, y=110 +) # Add all the sub-group to the SuperGroup group.append(image_group) @@ -71,8 +88,8 @@ def MakeHeatMapColor(): # Add the SuperGroup to the Display board.DISPLAY.show(group) -min_t = 20 # Initial minimum temperature range, before auto scale -max_t = 37 # Initial maximum temperature range, before auto scale +min_t = 20 # Initial minimum temperature range, before auto scale +max_t = 37 # Initial maximum temperature range, before auto scale i2c = busio.I2C(board.SCL, board.SDA, frequency=800000) @@ -80,7 +97,7 @@ def MakeHeatMapColor(): print("MLX addr detected on I2C") print([hex(i) for i in mlx.serial_number]) -#mlx.refresh_rate = adafruit_mlx90640.RefreshRate.REFRESH_2_HZ +# mlx.refresh_rate = adafruit_mlx90640.RefreshRate.REFRESH_2_HZ mlx.refresh_rate = adafruit_mlx90640.RefreshRate.REFRESH_4_HZ frame = [0] * 768 @@ -93,27 +110,27 @@ def MakeHeatMapColor(): # these happen, no biggie - retry continue -# print("Time for data aquisition: %0.2f s" % (time.monotonic()-stamp)) + # print("Time for data aquisition: %0.2f s" % (time.monotonic()-stamp)) - mini = frame[0] # Define a min temperature of current image - maxi = frame[0] # Define a max temperature of current image + mini = frame[0] # Define a min temperature of current image + maxi = frame[0] # Define a max temperature of current image for h in range(24): for w in range(32): - t = frame[h*32 + w] + t = frame[h * 32 + w] if t > maxi: maxi = t if t < mini: mini = t - image_bitmap[w, (23-h)] = int(map_range(t, min_t, max_t, 0, last_color )) + image_bitmap[w, (23 - h)] = int(map_range(t, min_t, max_t, 0, last_color)) - min_label.text="%0.2f" % (min_t) + min_label.text = "%0.2f" % (min_t) - max_string="%0.2f" % (max_t) - max_label.x=120-(5*len(max_string)) # Tricky calculation to left align - max_label.text=max_string + max_string = "%0.2f" % (max_t) + max_label.x = 120 - (5 * len(max_string)) # Tricky calculation to left align + max_label.text = max_string - min_t = mini # Automatically change the color scale + min_t = mini # Automatically change the color scale max_t = maxi # print((mini, maxi)) # Use this line to display min and max graph in Mu # print("Total time for aquisition and display %0.2f s" % (time.monotonic()-stamp)) diff --git a/examples/mlx90640_simpletest.py b/examples/mlx90640_simpletest.py index 43e5bf2..f880fab 100644 --- a/examples/mlx90640_simpletest.py +++ b/examples/mlx90640_simpletest.py @@ -22,24 +22,33 @@ except ValueError: # these happen, no biggie - retry continue - print("Read 2 frames in %0.2f s" % (time.monotonic()-stamp)) + print("Read 2 frames in %0.2f s" % (time.monotonic() - stamp)) for h in range(24): for w in range(32): - t = frame[h*32 + w] + t = frame[h * 32 + w] if PRINT_TEMPERATURES: print("%0.1f, " % t, end="") if PRINT_ASCIIART: - c = '&' + c = "&" # pylint: disable=multiple-statements - if t < 20: c = ' ' - elif t < 23: c = '.' - elif t < 25: c = '-' - elif t < 27: c = '*' - elif t < 29: c = '+' - elif t < 31: c = 'x' - elif t < 33: c = '%' - elif t < 35: c = '#' - elif t < 37: c = 'X' + if t < 20: + c = " " + elif t < 23: + c = "." + elif t < 25: + c = "-" + elif t < 27: + c = "*" + elif t < 29: + c = "+" + elif t < 31: + c = "x" + elif t < 33: + c = "%" + elif t < 35: + c = "#" + elif t < 37: + c = "X" # pylint: enable=multiple-statements print(c, end="") print() diff --git a/setup.py b/setup.py index a4dc3e9..a8c29e0 100644 --- a/setup.py +++ b/setup.py @@ -6,6 +6,7 @@ """ from setuptools import setup, find_packages + # To use a consistent encoding from codecs import open from os import path @@ -13,53 +14,44 @@ here = path.abspath(path.dirname(__file__)) # Get the long description from the README file -with open(path.join(here, 'README.rst'), encoding='utf-8') as f: +with open(path.join(here, "README.rst"), encoding="utf-8") as f: long_description = f.read() setup( - name='adafruit-circuitpython-mlx90640', - + name="adafruit-circuitpython-mlx90640", use_scm_version=True, - setup_requires=['setuptools_scm'], - - description='Driver for the MLX90640 thermal camera', + setup_requires=["setuptools_scm"], + description="Driver for the MLX90640 thermal camera", long_description=long_description, - long_description_content_type='text/x-rst', - + long_description_content_type="text/x-rst", # The project's main homepage. - url='https://github.com/adafruit/Adafruit_CircuitPython_MLX90640', - + url="https://github.com/adafruit/Adafruit_CircuitPython_MLX90640", # Author details - author='Adafruit Industries', - author_email='circuitpython@adafruit.com', - + author="Adafruit Industries", + author_email="circuitpython@adafruit.com", install_requires=[ - 'Adafruit-Blinka', - 'adafruit-circuitpython-busdevice', - 'adafruit-circuitpython-register' + "Adafruit-Blinka", + "adafruit-circuitpython-busdevice", + "adafruit-circuitpython-register", ], - # Choose your license - license='MIT', - + license="MIT", # See https://pypi.python.org/pypi?%3Aaction=list_classifiers classifiers=[ - 'Development Status :: 3 - Alpha', - 'Intended Audience :: Developers', - 'Topic :: Software Development :: Libraries', - 'Topic :: System :: Hardware', - 'License :: OSI Approved :: MIT License', - 'Programming Language :: Python :: 3', - 'Programming Language :: Python :: 3.4', - 'Programming Language :: Python :: 3.5', + "Development Status :: 3 - Alpha", + "Intended Audience :: Developers", + "Topic :: Software Development :: Libraries", + "Topic :: System :: Hardware", + "License :: OSI Approved :: MIT License", + "Programming Language :: Python :: 3", + "Programming Language :: Python :: 3.4", + "Programming Language :: Python :: 3.5", ], - # What does your project relate to? - keywords='adafruit blinka circuitpython micropython mlx90640 thermal camera ir flir', - + keywords="adafruit blinka circuitpython micropython mlx90640 thermal camera ir flir", # You can just specify the packages manually here if your project is # simple. Or you can use find_packages(). # TODO: IF LIBRARY FILES ARE A PACKAGE FOLDER, # CHANGE `py_modules=['...']` TO `packages=['...']` - py_modules=['adafruit_mlx90640'], + py_modules=["adafruit_mlx90640"], )