theyosh / TerrariumPI

Home automated terrarium/aquarium or other enclosed environment with a Raspberry Pi
https://terrarium.theyosh.nl
GNU General Public License v3.0
413 stars 99 forks source link

Add support for AM2320 sensor #296

Closed tvStatic closed 5 years ago

tvStatic commented 5 years ago

AM2320 is a I2C temperature/humidity sensor.

The sensor is a little finicky in that it sleeps after 3 seconds of inactivity, so it is necessary to send a packet to it to wake it up before reading from it. It can be run with a one-wire configuration like a DHT22, but I was unable to make it work with this configuration.

Code to get the temperature and humidity is as follows (from here but the code is untabbed):

import posix
from fcntl import ioctl
import time
class AM2320:
        I2C_ADDR = 0x5c
        I2C_SLAVE = 0x0703
        def __init__(self, i2cbus = 1):
                self._i2cbus = i2cbus
        @staticmethod
        def _calc_crc16(data):
                crc = 0xFFFF
                for x in data:
                        crc = crc ^ x
                        for bit in range(0, 8):
                                if (crc & 0x0001) == 0x0001:
                                        crc >>= 1
                                        crc ^= 0xA001
                                else:
                                        crc >>= 1
                return crc

        @staticmethod
        def _combine_bytes(msb, lsb):
                return msb << 8 | lsb

        def readSensor(self):
                fd = posix.open("/dev/i2c-%d" % self._i2cbus, posix.O_RDWR)
                ioctl(fd, self.I2C_SLAVE, self.I2C_ADDR)

                # wake AM2320 up, goes to sleep to not warm up and affect the humidity sensor
                # This write will fail as AM2320 won't ACK this write
                try:
                        posix.write(fd, b'\0x00')
                except:
                        pass

                time.sleep(0.001) #Wait at least 0.8ms, at most 3ms

                # write at addr 0x03, start reg = 0x00, num regs = 0x04 */
                posix.write(fd, b'\x03\x00\x04')

                time.sleep(0.0016) #Wait at least 1.5ms for result

                # Read out 8 bytes of result data
                # Byte 0: Should be Modbus function code 0x03
                # Byte 1: Should be number of registers to read (0x04)
                # Byte 2: Humidity msb
                # Byte 3: Humidity lsb
                # Byte 4: Temperature msb
                # Byte 5: Temperature lsb
                # Byte 6: CRC lsb byte
                # Byte 7: CRC msb byte
                data = bytearray(posix.read(fd, 8))
                # Check data[0] and data[1]
                if data[0] != 0x03 or data[1] != 0x04:
                        raise Exception("First two read bytes are a mismatch")
                # CRC check
                if self._calc_crc16(data[0:6]) != self._combine_bytes(data[7], data[6]):
                        raise Exception("CRC failed")

                # Temperature resolution is 16Bit,
                # temperature highest bit (Bit15) is equal to 1 indicates a
                # negative temperature, the temperature highest bit (Bit15)
                # is equal to 0 indicates a positive temperature;
                # temperature in addition to the most significant bit (Bit14 ~ Bit0)
                # indicates the temperature sensor string value.
                # Temperature sensor value is a string of 10 times the
                # actual temperature value.
                temp = self._combine_bytes(data[4], data[5])
                if temp & 0x8000:
                        temp = -(temp & 0x7FFF)
                temp /= 10.0

                humi = self._combine_bytes(data[2], data[3]) / 10.0
                return (temp, humi)

am2320 = AM2320(1)
(t,h) = am2320.readSensor()

When I have time, I can prepare a pull request if preferred.

theyosh commented 5 years ago

Hi, thanks for the code, but I do have some issues with it. So I looked around on the internet, and found a slight easier to read code: https://github.com/contactless/wb-mqtt-am2320/blob/master/am2320.py

So I added support for this sensor. Just enter the I2C address (default is 5c). Could you test this?

Just do a git pull and restart. Clear browser cache and you should be able to add AM2320 sensors. Both temperature and humidity is supported.

tvStatic commented 5 years ago

Thanks for your efforts!

Getting the following when I added this sensor:

load_raw_data humid:
global name 'PARAM_AM2320_READ' is not defined
2019-06-12 12:53:56,752 - WARNING - terrariumSensor      - Measured value NoneC from am2320 sensor 'Test Temp' is outside valid range 0.00C - 40.00C in 0.00985 seconds.

I moved the two declarations in terrariumI2CSensor.py:

PARAM_AM2320_READ = 0x03
REG_AM2320_HUMIDITY_MSB = 0x00

To line 622, then got this error:

load_raw_data humid:
local variable 'buf' referenced before assignment

I added the following at the start of get_raw_data:

buf = ""

Then got the following error:

load_raw_data temp:
[Errno 121] Remote I/O error
load_raw_data humid:
global name 'unpack' is not defined

Added:

from struct import unpack

To the top of the file. And finally ended up with this:

load_raw_data temp:
[Errno 121] Remote I/O error
load_raw_data humid:
unpack requires a string argument of length 2

My own scripts are continuing to work at address 5c, so it's not a sensor problem. To verify: Hardware is AM2320, Address is 5c.

theyosh commented 5 years ago

Hmm, I made some updates. So could you see if it improves?

Else. could you run the script https://github.com/contactless/wb-mqtt-am2320/blob/master/am2320.py and see if that works for you? If that script is not working, then my code will also not work.

tvStatic commented 5 years ago

I was able to figure out the issue based on running the script you specified.

The issue is related to this part of the code that I provided, for which there is no equivalent in the mqtt version:

# wake AM2320 up, goes to sleep to not warm up and affect the humidity sensor
# This write will fail as AM2320 won't ACK this write
try:
        posix.write(fd, b'\0x00')
except:
        pass

Fix was to replace line 602 in terrariumI2CSensor.py with the following:

# wake AM2320 up, goes to sleep to not warm up and affect the humidity sensor
# This write will fail as AM2320 won't ACK this write
try:
  self.i2c_bus.write_i2c_block_data(int('0x' + gpio_pins[0],16), 0x00, [])
except:
  pass

With this code fix, the sensor is operated as expected. Thanks!

theyosh commented 5 years ago

Ah, yes now I see it. I have updated my code. It should now work nicely. Could you update once more and test it? And thanks for the testing... it helps a lot and saves me some money not buying all kind of sensors. :P

tvStatic commented 5 years ago

I can verify that the latest commit is working correctly. Thanks again.