# Christian Fiot 2020
# Dust particle sensor
# power requirement 75mA max - 150uA low
#
# Usage:
# from HM3300 import HM3300
#
# Dust = HM3300(0x40, traceDebug=False)
# Dust.begin() # return True if chip found
# Dust.read()  # return  PM1.0, 2.5, 10.0
#
# 2.00 Add power mode function - Pin P10
# !!! It takes 45sec after wakeup cmd for the sensor to give stable data  !!!
#

import utime  # 2.00
from machine import Pin # 2.00
from trap import * # 2.2.9b
from machine import I2C # 2.2.18
# 2.2.2b  import configurator # 2.2.0


class HM3300():

    def __init__(self, iicadr=0x040, i2c=None, sda='P22', scl='P21', traceDebug=False, socketVersion=2, sdAccess=False): # 2.2.9b
        self.trace = traceDebug
        self.adr = iicadr
        self.socketVersion = socketVersion # 2.2.2b
        self.sdAccess = sdAccess # 2.2.9b

        # Appendix 2
        self.data = bytearray(29)
        self.id = 0 # 3-4
        self.PM10_CF = 0 # 5-6
        self.PM25_CF = 0 # 7-8
        self.PM100_CF = 0  # 9-10
        self.PM10 = 0  # 11-12
        self.PM25 = 0  # 13-14
        self.PM100 = 0  # 15-16
        self.Count3 = 0  # 17-18
        self.Count5 = 0  # 19-20
        self.Count10 = 0  # 21-22
        self.Count25 = 0 # 23-24
        self.Count50 = 0  # 25-26
        self.Count100 = 0 # 27-28
        self.Check = 0  # S 29
        self.PMode = 0 # 2.00   0= Sleep,  1= Normal

        # I2C bus declaration is extenal
        # MCP are connected on I2C bus #1   SDA-P22   SCL-P21
        #                      I2C bus #2   SDA-P18   SCL-P17
        #
        if i2c is not None: # 2.2.18
            self.i2c1 = i2c
        else:
            # from machine import I2C
            self.i2c1 = I2C(0, mode=I2C.MASTER, pins=(sda, scl))

        # 2.2.2b deviceSettings = configurator.FLASH_CONFIG # 2.2.0b
        if self.socketVersion == 2 or self.socketVersion == 20 or self.socketVersion == 21: # 2.2.12b   2.2.2b  deviceSettings['pycom_socket_version'] == 2: # 2.2.2b   != 3: # 2.2.0b
            self.P_Stdby = Pin('P10', mode=Pin.OUT) # 2.00
        self.powerMode(1) # 2.00
        utime.sleep_ms(100) # 2.00


    def begin(self):
        try:
            self.i2c1.writeto(self.adr, bytearray([0x88]))
            self.powerMode(0) # self.P_Stdby.value(self.PMode) # 2.00 sleep mode
            if self.socketVersion == 1: # 2.2.2b
                print('Detected HM3300 - PM2.5 Sensor Adr:' + str(self.adr) + ', Socket:' + str(self.socketVersion))
            else:
                print('Detected HM3300 - PM2.5 Sensor Adr:' + str(self.adr) + ', sleep mode active.')
            return True

        except Exception as e: # 2.2.9b
            scratch = 'HM3300 - PM2.5 Sensor (Adr:' + str(self.adr) + ') not found - '   # 2.2.9.b   print('HM3300 - PM2.5 Sensor (Adr:' + str(self.adr) + ') not found.')
            file_errLog(0, scratch + str(e), self.sdAccess)  # 2.2.9b
            # 2.2.18 print('Begin I2C1 scan ...')
            # 2.2.18 print(self.i2c1.scan())
            return False

    # !!! It takes 45sec after wakeup cmd for the sensor to give stable data  !!!
    def powerMode(self, pw): # 2.00   0= Stdby,  1= Normal
        self.PMode = pw # 2.2.0b
        # 2.2.2b deviceSettings = configurator.FLASH_CONFIG # 2.2.0b
        if self.socketVersion == 2 or self.socketVersion == 20 or self.socketVersion == 21: # 2.2.8b no sleep mode with skyhook     if deviceSettings['pycom_socket_version'] == 2: # 2.2.2b  != 3: # 2.2.0b
            # 2.2.0b self.PMode = pw
            self.P_Stdby.hold(False) # 7b
            self.P_Stdby.value(self.PMode)
            self.P_Stdby.hold(True) # 7b  keep pin in current state when going to deepsleep


    def getDataU16(self, pointer):
        result = (self.data[pointer]  << 8) | self.data[pointer+1]
        if self.trace :
            print('Upointer {:2}: {}'.format (pointer, result) )
        return result

    def read(self):
        if self.PMode == 0:  # 2.00
            return 0, 0, 0

        self.i2c1.readfrom_into(self.adr, self.data)
        self.id = self.getDataU16(2) # 3-4
        self.PM10_CF = self.getDataU16(4) # 5-6
        self.PM25_CF = self.getDataU16(6) # 7-8
        self.PM100_CF = self.getDataU16(8)  # 9-10
        self.PM10 = self.getDataU16(10)  # 11-12
        self.PM25 = self.getDataU16(12)  # 13-14
        self.PM100 = self.getDataU16(14)  # 15-16
        self.Count3 = self.getDataU16(16)  # 17-18
        self.Count5 = self.getDataU16(18)  # 19-20
        self.Count10 = self.getDataU16(20)  # 21-22
        self.Count25 = self.getDataU16(22) # 23-24
        self.Count50 = self.getDataU16(24)  # 25-26
        self.Count100 = self.getDataU16(26) # 27-28
        self.Check = int(self.data[28])  # S 29

        return self.PM10, self.PM25, self.PM100

    # 2.00
    def info(self):
        if self.PMode == 0:  # 2.00
            return 0

        print("id = %d" % self.id)
        print("PM10_CF = %d" % self.PM10_CF)
        print("PM25_CF = %d" % self.PM25_CF)
        print("PM100_CF = %d" % self.PM100_CF)
        print("PM10 = %d" % self.PM10)
        print("PM25 = %d" % self.PM25)
        print("PM100 = %d" % self.PM100)
        print("Count3 = %d" % self.Count3)
        print("Count5 = %d" % self.Count5)
        print("Count10 = %d" % self.Count10)
        print("Count25 = %d" % self.Count25)
        print("Count50 = %d" % self.Count50)
        print("Count100 = %d" % self.Count100)
        print("Check = %d" % self.Check)
        return 1
