• Recent
  • Tags
  • Unsolved
  • Solved
  • MagicMirror² Repository
  • Documentation
  • 3rd-Party-Modules
  • Donate
  • Discord
  • Register
  • Login
MagicMirror Forum
  • Recent
  • Tags
  • Unsolved
  • Solved
  • MagicMirror² Repository
  • Documentation
  • 3rd-Party-Modules
  • Donate
  • Discord
  • Register
  • Login
A New Chapter for MagicMirror: The Community Takes the Lead
Read the statement by Michael Teeuw here.

AHT20 Humidity + Temperature Sensor

Scheduled Pinned Locked Moved Solved Requests
18 Posts 3 Posters 986 Views 3 Watching
Loading More Posts
  • Oldest to Newest
  • Newest to Oldest
  • Most Votes
Reply
  • Reply as topic
Log in to reply
This topic has been deleted. Only users with topic management privileges can see it.
  • J Offline
    JohnGalt @rkorell
    last edited by Apr 15, 2025, 6:38 PM

    @rkorell – Thanks for responding, answers follow:

    1. Yes, the first use case is a sensor connected directly to the Raspberry Pi 4 where the Magic Mirror is running. It will be great if I can expand this in the future to display on my office Magic Mirror the outputs from sensors on three (3) other Pis (they run Magic Mirror, NEMS network monitor and Pi-Aware flight tracking, respectively).

    2. I am just running the two Magic Mirror modules listed above. I don’t know what scripts they run. At this point I have just looked and don’t see any obvious way to adapt them. I did try changing the address of the device from their default to 0x38, which is what I think is the address for this sensor attached to my Pi - but I still don’t get output. I should have some time this afternoon to SSH into that machine, and I can locate the scripts and log the error messages.

    1 Reply Last reply Reply Quote 0
    • J Offline
      JohnGalt @rkorell
      last edited by Apr 15, 2025, 8:02 PM

      @rkorell Ralf: OK, I have a chance to work on this today - Thanks for your help.

      To ease troubleshooting, I have temporarily disabled MMM-Temperature and am only running MMM-BME280. Below are the following:

      1. Config for this module
      2. Error as captured using pm2 logs
      3. Code listing of bme280.js, which is called by the module

      Config:

      {module: 'MMM-BME280', // https://github.com/awitwicki/MMM-BME280
        disabled: false,
        position: 'top_right',
        config: {
          titleText: "Office - MMM-BME280",
          updateInterval: 100, //seconds 
          deviceAddress: "0x38", //0x76 = default
          temperatureScaleType: 1, // 0=Celsius
          pressureScaleType: 1, // 0 = hPa
      	} },
      

      Errors [from pm2 logs]:
      0|MagicMirror | [2025-04-15 12:57:48.599] [ERROR] exec error: Error: Command failed: python3 ./modules/MMM-BME280/bme280.py 0x38
      0|MagicMirror | Traceback (most recent call last):
      0|MagicMirror | File “/home/pi/MagicMirror/./modules/MMM-BME280/bme280.py”, line 176, in
      0|MagicMirror | main()
      0|MagicMirror | File “/home/pi/MagicMirror/./modules/MMM-BME280/bme280.py”, line 171, in main
      0|MagicMirror | temperature,pressure,humidity = readBME280All()
      0|MagicMirror | File “/home/pi/MagicMirror/./modules/MMM-BME280/bme280.py”, line 82, in readBME280All
      0|MagicMirror | bus.write_byte_data(addr, REG_CONTROL_HUM, OVERSAMPLE_HUM)
      0|MagicMirror | OSError: [Errno 121] Remote I/O error
      0|MagicMirror |

      Code for bme280.py:

      Module.register("MMM-BME280", {
          // Default module config.
          defaults: {
              updateInterval: 100, // Seconds
              titleText: "Home weather",
              deviceAddress: "0x76",
              temperatureScaleType: 0, // Celsuis
              pressureScaleType: 0 // hPa
          },
      
          // Define start sequence.
          start: function () {
              Log.info("Starting module: " + this.name);
      
              this.temperature = 'Loading...';
              this.humidity = 'Loading...';
              this.pressure = 'Loading...';
      
              this.update();
              setInterval(
                  this.update.bind(this),
                  this.config.updateInterval * 1000);
          },
      
          update: function () {
              this.sendSocketNotification('REQUEST', this.config);
          },
      
          getStyles: function () {
              return ['MMM-BME280.css'];
          },
      
          // Override dom generator.
          getDom: function () {
              var wrapper = document.createElement("div");
      
              var header = document.createElement("div");
              var label = document.createTextNode(this.config.titleText);
              header.className = 'bme-header';
              header.appendChild(label)
              wrapper.appendChild(header);
      
              var table = document.createElement("table");
              var tbdy = document.createElement('tbody');
              for (var i = 0; i < 3; i++) {
                  var val = "";
                  var sufix = "";
                  var icon_img = "";
      
                  switch (i) {
                      case 0:
                          switch (this.config.temperatureScaleType) {
                              case 0: // Celsius
                                  val = this.temperature;
                                  sufix = "°C";
                                  break;
                              case 1: // Fahrenheit
                                  val = Math.round(this.temperature * 9.0 / 5.0 + 32.0);
                                  sufix = "°F";
                                  break;
                          }
                          icon_img = "temperature-high";
                          break;
                      case 1:
                          val = this.humidity;
                          icon_img = "tint";
                          sufix = "%";
                          break;
                      case 2:
                          switch (this.config.pressureScaleType) {
                              case 0: // hPa
                                  val = this.pressure;
                                  sufix = " hPa";
                                  break;
                              case 1: // inHg
                                  val = Math.round(this.pressure * 100 / 33.864) / 100;
                                  sufix = " inHg";
                                  break;
                          }
                          icon_img = "tachometer-alt";
                          break;
                  }
      
                  var tr = document.createElement('tr');
                  var icon = document.createElement("i");
      
                  icon.className = 'fa fa-' + icon_img + ' bme-icon';
      
                  var text_div = document.createElement("div");
                  var text = document.createTextNode(" " + val + sufix);
                  text_div.className = 'bme-text';
                  text_div.appendChild(text);
      
                  var td = document.createElement('td');
                  td.className = 'bme-td-icon';
                  td.appendChild(icon)
                  tr.appendChild(td)
      
                  var td = document.createElement('td');
                  td.appendChild(text_div)
                  tr.appendChild(td)
      
                  tbdy.appendChild(tr);
              }
              table.appendChild(tbdy);
              wrapper.appendChild(table);
      
              return wrapper;
          },
      
          socketNotificationReceived: function (notification, payload) {
              if (notification === 'DATA') {
                  this.temperature = payload.temp;
                  this.humidity = payload.humidity;
                  this.pressure = payload.press;
                  this.updateDom();
              }
          },
      });
      
      S R 2 Replies Last reply Apr 15, 2025, 8:17 PM Reply Quote 0
      • S Away
        sdetweil @JohnGalt
        last edited by Apr 15, 2025, 8:17 PM

        @JohnGalt the module also has a node_helper.js , this is where the python script is launched

        browsers cannot access hardware or files directly (for security reasons)

        Sam

        How to add modules

        learning how to use browser developers window for css changes

        J 1 Reply Last reply Apr 15, 2025, 8:53 PM Reply Quote 0
        • J Offline
          JohnGalt @sdetweil
          last edited by Apr 15, 2025, 8:53 PM

          @sdetweil – Hi Sam, thanks for looking in on this.

          I guess I don’t understand… If the module is calling a python script, and the script is polling the sensor - then it would seem to me that the browser isn’t accessing the hardware or files directly. Obviously I’m missing something here.

          S 1 Reply Last reply Apr 15, 2025, 9:01 PM Reply Quote 0
          • S Away
            sdetweil @JohnGalt
            last edited by Apr 15, 2025, 9:01 PM

            @JohnGalt the browser (electron) wants to display the data from the sensor, but it cant read it directly. so the module sends a request to the helper to get the data from device 0x38

            the module uses a python script to get the data
            the python script is reporting an error

            that is what needs to be looked at.

            you posted the code from the module/browser side which is not involved in getting the actual data

            Sam

            How to add modules

            learning how to use browser developers window for css changes

            J 1 Reply Last reply Apr 16, 2025, 9:47 PM Reply Quote 0
            • R Offline
              rkorell @JohnGalt
              last edited by rkorell Apr 16, 2025, 9:58 AM Apr 16, 2025, 9:55 AM

              the python script you are looking for is bme280.py

              #!/usr/bin/python
              #--------------------------------------
              #    ___  ___  _ ____
              #   / _ \/ _ \(_) __/__  __ __
              #  / , _/ ___/ /\ \/ _ \/ // /
              # /_/|_/_/  /_/___/ .__/\_, /
              #                /_/   /___/
              #
              #           bme280.py
              #  Read data from a digital pressure sensor.
              #
              #  Official datasheet available from :
              #  https://www.bosch-sensortec.com/bst/products/all_products/bme280
              #
              # Author : Matt Hawkins
              # Date   : 21/01/2018
              #
              # https://www.raspberrypi-spy.co.uk/
              #
              #--------------------------------------
              import smbus
              import sys
              import time
              from ctypes import c_short
              from ctypes import c_byte
              from ctypes import c_ubyte
              
              DEVICE = 0x76 # Default device I2C address
              
              try: #override device address like '0x77'
                  DEVICE = int(sys.argv[1], 16)
              except:
                  pass
              
              bus = smbus.SMBus(1) # Rev 2 Pi, Pi 2 & Pi 3 uses bus 1
                                   # Rev 1 Pi uses bus 0
              
              def getShort(data, index):
                # return two bytes from data as a signed 16-bit value
                return c_short((data[index+1] << 8) + data[index]).value
              
              def getUShort(data, index):
                # return two bytes from data as an unsigned 16-bit value
                return (data[index+1] << 8) + data[index]
              
              def getChar(data,index):
                # return one byte from data as a signed char
                result = data[index]
                if result > 127:
                  result -= 256
                return result
              
              def getUChar(data,index):
                # return one byte from data as an unsigned char
                result =  data[index] & 0xFF
                return result
              
              def readBME280ID(addr=DEVICE):
                # Chip ID Register Address
                REG_ID     = 0xD0
                (chip_id, chip_version) = bus.read_i2c_block_data(addr, REG_ID, 2)
                return (chip_id, chip_version)
              
              def readBME280All(addr=DEVICE):
                # Register Addresses
                REG_DATA = 0xF7
                REG_CONTROL = 0xF4
                REG_CONFIG  = 0xF5
              
                REG_CONTROL_HUM = 0xF2
                REG_HUM_MSB = 0xFD
                REG_HUM_LSB = 0xFE
              
                # Oversample setting - page 27
                OVERSAMPLE_TEMP = 2
                OVERSAMPLE_PRES = 2
                MODE = 1
              
                # Oversample setting for humidity register - page 26
                OVERSAMPLE_HUM = 2
                bus.write_byte_data(addr, REG_CONTROL_HUM, OVERSAMPLE_HUM)
              
                control = OVERSAMPLE_TEMP<<5 | OVERSAMPLE_PRES<<2 | MODE
                bus.write_byte_data(addr, REG_CONTROL, control)
              
                # Read blocks of calibration data from EEPROM
                # See Page 22 data sheet
                cal1 = bus.read_i2c_block_data(addr, 0x88, 24)
                cal2 = bus.read_i2c_block_data(addr, 0xA1, 1)
                cal3 = bus.read_i2c_block_data(addr, 0xE1, 7)
              
                # Convert byte data to word values
                dig_T1 = getUShort(cal1, 0)
                dig_T2 = getShort(cal1, 2)
                dig_T3 = getShort(cal1, 4)
              
                dig_P1 = getUShort(cal1, 6)
                dig_P2 = getShort(cal1, 8)
                dig_P3 = getShort(cal1, 10)
                dig_P4 = getShort(cal1, 12)
                dig_P5 = getShort(cal1, 14)
                dig_P6 = getShort(cal1, 16)
                dig_P7 = getShort(cal1, 18)
                dig_P8 = getShort(cal1, 20)
                dig_P9 = getShort(cal1, 22)
              
                dig_H1 = getUChar(cal2, 0)
                dig_H2 = getShort(cal3, 0)
                dig_H3 = getUChar(cal3, 2)
              
                dig_H4 = getChar(cal3, 3)
                dig_H4 = (dig_H4 << 24) >> 20
                dig_H4 = dig_H4 | (getChar(cal3, 4) & 0x0F)
              
                dig_H5 = getChar(cal3, 5)
                dig_H5 = (dig_H5 << 24) >> 20
                dig_H5 = dig_H5 | (getUChar(cal3, 4) >> 4 & 0x0F)
              
                dig_H6 = getChar(cal3, 6)
              
                # Wait in ms (Datasheet Appendix B: Measurement time and current calculation)
                wait_time = 1.25 + (2.3 * OVERSAMPLE_TEMP) + ((2.3 * OVERSAMPLE_PRES) + 0.575) + ((2.3 * OVERSAMPLE_HUM)+0.575)
                time.sleep(wait_time/1000)  # Wait the required time  
              
                # Read temperature/pressure/humidity
                data = bus.read_i2c_block_data(addr, REG_DATA, 8)
                pres_raw = (data[0] << 12) | (data[1] << 4) | (data[2] >> 4)
                temp_raw = (data[3] << 12) | (data[4] << 4) | (data[5] >> 4)
                hum_raw = (data[6] << 8) | data[7]
              
                #Refine temperature
                var1 = ((((temp_raw>>3)-(dig_T1<<1)))*(dig_T2)) >> 11
                var2 = (((((temp_raw>>4) - (dig_T1)) * ((temp_raw>>4) - (dig_T1))) >> 12) * (dig_T3)) >> 14
                t_fine = var1+var2
                temperature = float(((t_fine * 5) + 128) >> 8);
              
                # Refine pressure and adjust for temperature
                var1 = t_fine / 2.0 - 64000.0
                var2 = var1 * var1 * dig_P6 / 32768.0
                var2 = var2 + var1 * dig_P5 * 2.0
                var2 = var2 / 4.0 + dig_P4 * 65536.0
                var1 = (dig_P3 * var1 * var1 / 524288.0 + dig_P2 * var1) / 524288.0
                var1 = (1.0 + var1 / 32768.0) * dig_P1
                if var1 == 0:
                  pressure=0
                else:
                  pressure = 1048576.0 - pres_raw
                  pressure = ((pressure - var2 / 4096.0) * 6250.0) / var1
                  var1 = dig_P9 * pressure * pressure / 2147483648.0
                  var2 = pressure * dig_P8 / 32768.0
                  pressure = pressure + (var1 + var2 + dig_P7) / 16.0
              
                # Refine humidity
                humidity = t_fine - 76800.0
                humidity = (hum_raw - (dig_H4 * 64.0 + dig_H5 / 16384.0 * humidity)) * (dig_H2 / 65536.0 * (1.0 + dig_H6 / 67108864.0 * humidity * (1.0 + dig_H3 / 67108864.0 * humidity)))
                humidity = humidity * (1.0 - dig_H1 * humidity / 524288.0)
                if humidity > 100:
                  humidity = 100
                elif humidity < 0:
                  humidity = 0
              
                return temperature/100.0,pressure/100.0,humidity
              
              def main():
              
                # (chip_id, chip_version) = readBME280ID()
                # print("Chip ID     :", chip_id)
                # print("Version     :", chip_version)
              
                temperature,pressure,humidity = readBME280All()
              
                print(round(temperature,1),round(humidity,1),round(pressure,1))
              
              if __name__=="__main__":
                 main()
              

              @JohnGalt said in AHT20 Humidity + Temperature Sensor:

              Remote I/O error

              The given error seems to by not a program but an I/O problem.
              I’m not familiar with this sensor so I do not know how to connect and - most important - how to figure out the right “deviceAddress” - it seems that there is the error located.

              Either the address isn’t the correct one or the IO channel is somehow broken.
              For the latter came in my mind: if this is a I2C conected device - do you have enabled I2C on your Raspi?

              I’ve tried to “understand” what happens in the python script above as well as in the C-exmaple program on the Bosch product page for bme280 sensor, but didn’t got it…

              Regards,
              Ralf

              R J 3 Replies Last reply Apr 16, 2025, 10:35 AM Reply Quote 0
              • R Offline
                rkorell @rkorell
                last edited by rkorell Apr 16, 2025, 10:38 AM Apr 16, 2025, 10:35 AM

                @JohnGalt
                just seen while re-reading my last posting:

                nodehelper.js (the part of the module which is communicating with the python program and so with the sensor) is calling python3 :

                exec(`python3 ./modules/MMM-BME280/bme280.py ${deviceAddr}`, (error, stdout) => {
                

                but the python script itself is referencing python 2 (see above post, source code of bme280.py, 1st line:

                #!/usr/bin/python
                

                I guess this must match and doesn’t …
                So may this is the cause of your problems.

                And if this is true it is may really hard to get this working because you may run in several incompatibilities and library-conflicts …

                Regards,
                Ralf

                1 Reply Last reply Reply Quote 0
                • J Offline
                  JohnGalt @sdetweil
                  last edited by Apr 16, 2025, 9:47 PM

                  @sdetweil – Thanks, Sam. It looks like I intended to send bme280.py but actually copied MMM-BME280.js. I see below where Ralf did post it, so I will look at that, too.

                  1 Reply Last reply Reply Quote 1
                  • J Offline
                    JohnGalt @rkorell
                    last edited by Apr 16, 2025, 9:57 PM

                    @rkorell – Hi Ralf:

                    Yes, I2C is enabled and functioning. There is a test script that does return current temperature and humidity:
                    Invoking “python AHT20_test.py” does launch this script (Note - I launch from the terminal using ‘python’, not ‘python3’, telling me the system is indeed defaulting to python3):

                    # SPDX-FileCopyrightText: 2021 ladyada for Adafruit Industries
                    # SPDX-License-Identifier: MIT
                    
                    """
                    Basic `AHTx0` example test
                    """
                    
                    import time
                    import board
                    import adafruit_ahtx0
                    
                    # Create sensor object, communicating over the board's default I2C bus
                    i2c = board.I2C()  # uses board.SCL and board.SDA
                    # i2c = board.STEMMA_I2C()  # For using the built-in STEMMA QT connector on a microcontroller
                    sensor = adafruit_ahtx0.AHTx0(i2c)
                    
                    while True:
                        print("\nTemperature: %0.1f C" % sensor.temperature)
                        print("Humidity: %0.1f %%" % sensor.relative_humidity)
                        time.sleep(2)
                    
                    1 Reply Last reply Reply Quote 0
                    • J Offline
                      JohnGalt @rkorell
                      last edited by Apr 17, 2025, 1:01 AM

                      @rkorell + @sdetweil - Thanks for the support. I think I have fixed this for myself by adapting an existing module (“MMM-Temperature”, // https://github.com/Tom-Hirschberger/MMM-Temperature).

                      That module calls scripts for various sensors to capture the data. I was able to adapt an existing script for use with this sensor, so I think I am good for now.

                      R 1 Reply Last reply Apr 17, 2025, 6:25 AM Reply Quote 2
                      • 1
                      • 2
                      • 1 / 2
                      1 / 2
                      • First post
                        7/18
                        Last post
                      Enjoying MagicMirror? Please consider a donation!
                      MagicMirror created by Michael Teeuw.
                      Forum managed by Sam, technical setup by Karsten.
                      This forum is using NodeBB as its core | Contributors
                      Contact | Privacy Policy