Source code for drivers.Si1133.Si1133

"""Driver for the Silicon Labs Si1133 UV Index and ambient light sensor.

Hardware:
    Interface: I2C
    Address: 0x55 (default)
    Supply: 1.62V - 3.6V
    Measures: UV index, ambient light (lux) via 4 configurable ADC channels

Typical usage::

    from drivers.Si1133.Si1133 import Si1133

    sensor = Si1133(i2c_bus=1)
    sensor.begin()
    uv = sensor.readUV()

Note:
    Requires ``smbus2`` for I2C communication. Uses polynomial evaluation
    with factory-calibrated coefficients for UV index and lux calculations.
"""

__author__ = "Kishan Joshi"
__version__ = "1.0.0"
__status__ = "Production"

import time
from dataclasses import dataclass
from typing import ClassVar, Literal

import smbus2

from utils.oizom_logger import OizomLogger

# -----------------------------------------------------------------------------
# Configure logging
# -----------------------------------------------------------------------------
basic_logger = OizomLogger(__name__).get()
context_logger = OizomLogger(__name__)

SI1133_ADDR = 0x55
TIMEOUT = 5000


[docs] @dataclass class SI1133_Coeff_TypeDef: """Single polynomial coefficient used in lux/UV index calculation. Attributes: info: Encodes x-order, y-order, and sign for polynomial evaluation. mag: Magnitude divisor for the polynomial term. shift: Bit-shift value applied to the polynomial term result. """ info: int = 0 mag: int = 0 shift: int = 0
[docs] @dataclass class SI1133_LuxCoeff_TypeDef: """Collection of polynomial coefficients for lux calculation. Attributes: coeff_high: Coefficients used when ADC values exceed the high threshold. coeff_low: Coefficients used when ADC values are below the high threshold. """ coeff_high: list[SI1133_Coeff_TypeDef] coeff_low: list[SI1133_Coeff_TypeDef]
[docs] @dataclass class SI1133_Samples_TypeDef: """Raw ADC sample data read from the Si1133 sensor channels. Attributes: irq_status: Interrupt status byte indicating which channels completed. ch0: Raw 24-bit ADC value from channel 0 (UV photodiode). ch1: Raw 24-bit ADC value from channel 1 (visible high). ch2: Raw 24-bit ADC value from channel 2 (IR). ch3: Raw 24-bit ADC value from channel 3 (visible low). """ irq_status: int = 0 ch0: int = 0 ch1: int = 0 ch2: int = 0 ch3: int = 0
[docs] class Si1133: """Driver for the Si1133 UV index and ambient light sensor over I2C. Communicates with the Si1133 via SMBus to configure ADC channels, trigger forced measurements, and compute calibrated UV index and lux values using factory polynomial coefficients. Attributes: i2c_bus: I2C bus number (e.g. 0 or 1). bus: SMBus instance for I2C communication. addr: 7-bit I2C address of the sensor. """ # ============ VARIABLES =========== RATE_SHORT = 0x60 # 24.4us RATE_NORMAL = 0x00 # 48.8us RATE_LONG = 0x20 # 97.6us RATE_VLONG = 0x40 # 195us # ============================ BITS_24 = 0x00 BITS_16 = 0x40 # ============================ COUNT0 = 0x40 COUNT1 = 0x80 # =========================== # ========= PHOTODIODS ======== F_SMALL_IR = 0x00 F_MEDIUM_IR = 0x01 F_LARGE_IR = 0x02 F_WHITE = 0x0B F_LARGE_WHITE = 0x0C F_UV = 0x18 F_UV_DEEP = 0x19 # ============================= # ============= COMMANDS =============== SI1133_RESET_CMD_CTR = 0x00 SI1133_RESET_SW = 0x01 SI1133_FORCE = 0x11 SI1133_PAUSE = 0x12 SI1133_START = 0x13 SI1133_PARAM_QUERY = 0x40 SI1133_PARAM_SET = 0x80 # ============= REGISTERS ================ SI1133_REG_PARTID = 0x00 # IN SI1133_REG_REVID = 0x01 # IN SI1133_REG_MFRID = 0x02 # IN SI1133_REG_INFO0 = 0x03 # IN SI1133_REG_INFO1 = 0x04 # IN SI1133_REG_HOSTIN3 = 0x07 SI1133_REG_HOSTIN2 = 0x08 SI1133_REG_HOSTIN1 = 0x09 SI1133_REG_HOSTIN0 = 0x0A SI1133_REG_COMMAND = 0x0B SI1133_REG_IRQ_ENABLE = 0x0F SI1133_REG_RESPONSE1 = 0x10 SI1133_REG_RESPONSE0 = 0x11 SI1133_REG_IRQ_STATUS = 0x12 SI1133_REG_HOSTOUT0 = 0x13 SI1133_REG_HOSTOUT1 = 0x14 SI1133_REG_HOSTOUT2 = 0x15 SI1133_REG_HOSTOUT3 = 0x16 SI1133_REG_HOSTOUT4 = 0x17 SI1133_REG_HOSTOUT5 = 0x18 SI1133_REG_HOSTOUT6 = 0x19 SI1133_REG_HOSTOUT7 = 0x1A SI1133_REG_HOSTOUT8 = 0x1B SI1133_REG_HOSTOUT9 = 0x1C SI1133_REG_HOSTOUT10 = 0x1D SI1133_REG_HOSTOUT11 = 0x1E SI1133_REG_HOSTOUT12 = 0x1F SI1133_REG_HOSTOUT13 = 0x20 SI1133_REG_HOSTOUT14 = 0x21 SI1133_REG_HOSTOUT15 = 0x22 SI1133_REG_HOSTOUT16 = 0x23 SI1133_REG_HOSTOUT17 = 0x24 SI1133_REG_HOSTOUT18 = 0x25 SI1133_REG_HOSTOUT19 = 0x26 SI1133_REG_HOSTOUT20 = 0x27 SI1133_REG_HOSTOUT21 = 0x28 SI1133_REG_HOSTOUT22 = 0x29 SI1133_REG_HOSTOUT23 = 0x2A SI1133_REG_HOSTOUT24 = 0x2B SI1133_REG_HOSTOUT25 = 0x2C # =============== Parameters ================ SI1133_PARAM_I2CADDR = 0x00 SI1133_PARAM_CH_LIST = 0x01 SI1133_PARAM_ADCCONFIG0 = 0x02 SI1133_PARAM_ADCSENS0 = 0x03 SI1133_PARAM_ADCPOST0 = 0x04 SI1133_PARAM_MEASCONFIG0 = 0x05 SI1133_PARAM_ADCCONFIG1 = 0x06 SI1133_PARAM_ADCSENS1 = 0x07 SI1133_PARAM_ADCPOST1 = 0x08 SI1133_PARAM_MEASCONFIG1 = 0x09 SI1133_PARAM_ADCCONFIG2 = 0x0A SI1133_PARAM_ADCSENS2 = 0x0B SI1133_PARAM_ADCPOST2 = 0x0C SI1133_PARAM_MEASCONFIG2 = 0x0D SI1133_PARAM_ADCCONFIG3 = 0x0E SI1133_PARAM_ADCSENS3 = 0x0F SI1133_PARAM_ADCPOST3 = 0x10 SI1133_PARAM_MEASCONFIG3 = 0x11 SI1133_PARAM_ADCCONFIG4 = 0x12 SI1133_PARAM_ADCSENS4 = 0x13 SI1133_PARAM_ADCPSOT4 = 0x14 SI1133_PARAM_MEASCONFIG4 = 0x15 SI1133_PARAM_ADCCONFIG5 = 0x16 SI1133_PARAM_ADCSENS5 = 0x17 SI1133_PARAM_ADCPSOT5 = 0x18 SI1133_PARAM_MEASCONFIG5 = 0x19 SI1133_PARAM_MEASRATEH = 0x1A SI1133_PARAM_MEASRATEL = 0x1B SI1133_PARAM_MEASCOUNT0 = 0x1C SI1133_PARAM_MEASCOUNT1 = 0x1D SI1133_PARAM_MEASCOUNT2 = 0x1E SI1133_PARAM_THRESHOLD0_H = 0x25 SI1133_PARAM_THRESHOLD0_L = 0x26 SI1133_PARAM_THRESHOLD1_H = 0x27 SI1133_PARAM_THRESHOLD1_L = 0x28 SI1133_PARAM_THRESHOLD2_H = 0x29 SI1133_PARAM_THRESHOLD2_L = 0x2A SI1133_PARAM_BURST = 0x2B # ========================================== SI1133_CMD_RESET_CMD_CTR = 0x00 SI1133_CMD_RESET = 0x01 SI1133_CMD_NEW_ADDR = 0x02 SI1133_CMD_FORCE_CH = 0x11 SI1133_CMD_PAUSE_CH = 0x12 SI1133_CMD_START = 0x13 SI1133_CMD_PARAM_SET = 0x80 SI1133_CMD_PARAM_QUERY = 0x40 # ========================================== SI1133_RSP0_CHIPSTAT_MASK = 0xE0 SI1133_RSP0_COUNTER_MASK = 0x1F SI1133_RSP0_SLEEP = 0x20 # ========================================================== X_ORDER_MASK = 0x0070 Y_ORDER_MASK = 0x0007 SIGN_MASK = 0x0080 UV_INPUT_FRACTION = 15 UV_OUTPUT_FRACTION = 12 UV_NUMCOEFF = 2 ADC_THRESHOLD = 16000 INPUT_FRACTION_HIGH = 7 INPUT_FRACTION_LOW = 15 LUX_OUTPUT_FRACTION = 12 NUMCOEFF_LOW = 9 NUMCOEFF_HIGH = 4 SI1133_OK = 0x0000 SI1133_ERROR_I2C_TRANSACTION_FAILED = 0x0001 SI1133_ERROR_SLEEP_FAILED = 0x0002 # ========================================================== uk: ClassVar[list] = [] uk.append(SI1133_Coeff_TypeDef(1, 30902, 5)) uk.append(SI1133_Coeff_TypeDef(130, 46301, -3)) lk = SI1133_LuxCoeff_TypeDef( coeff_high=[ SI1133_Coeff_TypeDef(0, 209), SI1133_Coeff_TypeDef(1665, 93), SI1133_Coeff_TypeDef(2064, 65), SI1133_Coeff_TypeDef(-2671, 234), ], coeff_low=[ SI1133_Coeff_TypeDef(0, 0), SI1133_Coeff_TypeDef(1921, 29053), SI1133_Coeff_TypeDef(-1022, 36363), SI1133_Coeff_TypeDef(2320, 20789), SI1133_Coeff_TypeDef(-367, 57909), SI1133_Coeff_TypeDef(-1774, 38240), SI1133_Coeff_TypeDef(-608, 46775), SI1133_Coeff_TypeDef(-1503, 51831), SI1133_Coeff_TypeDef(-1886, 58928), ], ) # ==========================================================
[docs] def __init__(self, i2c_bus: int = 0, sensor_address: int = SI1133_ADDR) -> None: """Initialize the Si1133 driver. Args: i2c_bus: I2C bus number to use (0 or 1). sensor_address: 7-bit I2C address of the Si1133 sensor. """ self.i2c_bus = i2c_bus self.bus = smbus2.SMBus(self.i2c_bus) self.addr = sensor_address
[docs] def current_milli_time(self) -> int: """Return the current time in milliseconds since epoch. Returns: Current time in milliseconds. """ return round(time.time() * 1000)
[docs] def delay_ms(self, delaytime: int) -> None: """Sleep for the specified number of milliseconds. Args: delaytime: Delay duration in milliseconds. """ time.sleep(delaytime / 1000.0)
[docs] def SI1133_registerRead(self, reg: int) -> int: """Read a single byte from the specified register. Args: reg: Register address to read from. Returns: The byte value read from the register. """ return self.bus.read_byte_data(self.addr, reg)
[docs] def SI1133_registerWrite(self, reg: int, data: int) -> None: """Write a single byte to the specified register. Args: reg: Register address to write to. data: Byte value to write. """ self.bus.write_byte_data(self.addr, reg, data) return self.SI1133_OK
[docs] def SI1133_registerBlockWrite(self, reg: int, data: list[int]) -> None: """Write a block of bytes starting at the specified register. Args: reg: Starting register address. data: List of byte values to write. """ self.bus.write_i2c_block_data(self.addr, reg, data) return self.SI1133_OK
[docs] def SI1133_registerBlockRead(self, reg: int, length: int) -> list[int]: """Read a block of bytes starting at the specified register. Args: reg: Starting register address. length: Number of bytes to read. Returns: List of byte values read from the registers. """ return self.bus.read_i2c_block_data(self.addr, reg, length)
[docs] def SI1133_waitUntilSleep(self) -> int: """Poll the response register until the sensor enters sleep state. Returns: Status code (SI1133_OK on success). """ retval = self.SI1133_OK count = 0 while count < 5: response = self.SI1133_registerRead(self.SI1133_REG_RESPONSE0) if (response & self.SI1133_RSP0_CHIPSTAT_MASK) == self.SI1133_RSP0_SLEEP: break count = count + 1 return retval
# ==========================================
[docs] def SI1133_reset(self) -> None: """Perform a software reset of the Si1133 sensor. Waits 30 ms before issuing the reset command, then waits 10 ms for the internal reset sequence to complete. """ # do not access the Si1133 earlier that 25 ms from the power-up self.delay_ms(30) # perform the reset command retval = self.SI1133_registerWrite( self.SI1133_REG_COMMAND, self.SI1133_CMD_RESET ) # delay of 10 ms. This delay is needed to allow the Si1133 # to perform internal reset sequence self.delay_ms(10) return retval
###################################################################################
[docs] def SI1133_sendCmd(self, command: int) -> int: """Send a command to the Si1133 and wait for acknowledgment. Verifies the response counter increments to confirm command execution. Args: command: Command byte to write to the command register. Returns: Status code (SI1133_OK on success). """ count = 0 # get the response register contents response_stored = self.SI1133_registerRead(self.SI1133_REG_RESPONSE0) response_stored = response_stored & self.SI1133_RSP0_COUNTER_MASK # Double-check the response register is consistent while count < 5: ret = self.SI1133_waitUntilSleep() if ret != self.SI1133_OK: return ret # Skip if the command is RESET COMMAND COUNTER if command == self.SI1133_CMD_RESET_CMD_CTR: break response = self.SI1133_registerRead(self.SI1133_REG_RESPONSE0) if (response & self.SI1133_RSP0_COUNTER_MASK) == response_stored: break response_stored = response & self.SI1133_RSP0_COUNTER_MASK count = count + 1 # Send the Command ret = self.SI1133_registerWrite(self.SI1133_REG_COMMAND, command) if ret != self.SI1133_OK: return ret count = 0 # Expect a change in the response register while count < 5: # skip if the command is RESET COMMAND COUNTER if command == self.SI1133_CMD_RESET_CMD_CTR: break response = self.SI1133_registerRead(self.SI1133_REG_RESPONSE0) if (response & self.SI1133_RSP0_COUNTER_MASK) != response_stored: break count = count + 1 return self.SI1133_OK
################################################################################### # ==========================================
[docs] def SI1133_resetCmdCtr(self) -> int: """Reset the command counter register. Returns: Status code (SI1133_OK on success). """ return self.SI1133_sendCmd(self.SI1133_CMD_RESET_CMD_CTR)
# ==========================================
[docs] def SI1133_measurementForce(self) -> int: """Force a single measurement cycle on all configured channels. Returns: Status code (SI1133_OK on success). """ return self.SI1133_sendCmd(self.SI1133_CMD_FORCE_CH)
# ==========================================
[docs] def SI1133_measurementStart(self) -> int: """Start autonomous (continuous) measurement mode. Returns: Status code (SI1133_OK on success). """ return self.SI1133_sendCmd(self.SI1133_CMD_START)
# ==========================================
[docs] def SI1133_paramRead(self, address: int) -> int: """Read a parameter table value from the sensor. Args: address: Parameter table address (0x00 - 0x3F). Returns: The parameter value, or an error code if the command failed. """ cmd = 0x40 + (address & 0x3F) retval = self.SI1133_sendCmd(cmd) if retval != self.SI1133_OK: return retval return self.SI1133_registerRead(self.SI1133_REG_RESPONSE1)
# ==========================================
[docs] def SI1133_paramSet(self, address: int, value: int) -> int: """Write a value to the sensor parameter table. Args: address: Parameter table address (0x00 - 0x3F). value: Byte value to set. Returns: Status code (SI1133_OK on success). """ retval = self.SI1133_waitUntilSleep() if retval != self.SI1133_OK: return retval response_stored = self.SI1133_registerRead(self.SI1133_REG_RESPONSE0) response_stored = response_stored & self.SI1133_RSP0_COUNTER_MASK buffer = [0, 0] buffer[0] = value buffer[1] = 0x80 + (address & 0x3F) retval = self.SI1133_registerBlockWrite(self.SI1133_REG_HOSTIN0, buffer) if retval != self.SI1133_OK: return retval # Wait for command to Finish count = 0 # Expect a change in the response register while count < 5: response = self.SI1133_registerRead(self.SI1133_REG_RESPONSE0) if (response & self.SI1133_RSP0_COUNTER_MASK) != response_stored: break if retval != self.SI1133_OK: return retval count += 1 return self.SI1133_OK
# ==========================================
[docs] def SI1133_measurementPause(self) -> int: """Pause autonomous measurement mode. Returns: Status code (SI1133_OK on success). """ return self.SI1133_sendCmd(self.SI1133_CMD_PAUSE_CH)
# ==========================================
[docs] def begin(self) -> int: """Initialize the sensor with default 4-channel configuration. Resets the sensor and configures channels 0-3 for UV, visible (high/low), and IR measurements with appropriate ADC settings and gain. Returns: Cumulative status code (0 indicates all operations succeeded). """ self.delay_ms(5) retval = self.SI1133_reset() self.delay_ms(10) retval += self.SI1133_paramSet(self.SI1133_PARAM_CH_LIST, 0x0F) retval += self.SI1133_paramSet(self.SI1133_PARAM_ADCCONFIG0, 0x78) retval += self.SI1133_paramSet(self.SI1133_PARAM_ADCSENS0, 0x71) retval += self.SI1133_paramSet(self.SI1133_PARAM_ADCPOST0, 0x40) retval += self.SI1133_paramSet(self.SI1133_PARAM_ADCCONFIG1, 0x4D) retval += self.SI1133_paramSet(self.SI1133_PARAM_ADCSENS1, 0xE1) retval += self.SI1133_paramSet(self.SI1133_PARAM_ADCPOST1, 0x40) retval += self.SI1133_paramSet(self.SI1133_PARAM_ADCCONFIG2, 0x41) retval += self.SI1133_paramSet(self.SI1133_PARAM_ADCSENS2, 0xE1) retval += self.SI1133_paramSet(self.SI1133_PARAM_ADCPOST2, 0x50) retval += self.SI1133_paramSet(self.SI1133_PARAM_ADCCONFIG3, 0x4D) retval += self.SI1133_paramSet(self.SI1133_PARAM_ADCSENS3, 0x87) retval += self.SI1133_paramSet(self.SI1133_PARAM_ADCPOST3, 0x40) retval += self.SI1133_registerWrite(self.SI1133_REG_IRQ_ENABLE, 0x0F) return retval
# -------------------------------------------------
[docs] def SI1133_deInit(self) -> int: """De-initialize the sensor by pausing measurements and entering sleep. Returns: Cumulative status code (0 indicates all operations succeeded). """ retval = self.SI1133_paramSet(self.SI1133_PARAM_CH_LIST, 0x3F) retval += self.SI1133_measurementPause() retval += self.SI1133_waitUntilSleep() return retval
# ------------------------------------------------- # TO DO
[docs] def SI1133_measurementGet(self) -> SI1133_Samples_TypeDef: """Read raw ADC measurement results from all 4 channels. Reads 13 bytes from the host output registers and assembles them into signed 24-bit values for each channel. Returns: Sample data containing IRQ status and raw ADC values for channels 0-3. """ buffer = self.SI1133_registerBlockRead(self.SI1133_REG_IRQ_STATUS, 13) context_logger.debug_with_context("SI1133", f"Buffer : {buffer}") samples = SI1133_Samples_TypeDef(0, 0, 0, 0) samples.irq_status = buffer[0] # add data into channel 0 samples.ch0 = buffer[1] << 16 samples.ch0 |= buffer[2] << 8 samples.ch0 |= buffer[3] if samples.ch0 & 0x800000: samples.ch0 |= 0xFF000000 # add data into channel 1 samples.ch1 = buffer[4] << 16 samples.ch1 |= buffer[5] << 8 samples.ch1 |= buffer[6] if samples.ch1 & 0x800000: samples.ch1 |= 0xFF000000 # add data into channel 2 samples.ch2 = buffer[7] << 16 samples.ch2 |= buffer[8] << 8 samples.ch2 |= buffer[9] if samples.ch2 & 0x800000: samples.ch2 |= 0xFF000000 # add data into channel 3 samples.ch3 = buffer[10] << 16 samples.ch3 |= buffer[11] << 8 samples.ch3 |= buffer[12] if samples.ch3 & 0x800000: samples.ch3 |= 0xFF000000 return samples
[docs] def SI1133_calcPolyInner( self, _input: int, fraction: int, mag: int, shift: int ) -> int: """Compute a single inner term of the polynomial evaluation. Args: _input: Input ADC value. fraction: Fractional bit count for fixed-point scaling. mag: Magnitude divisor. shift: Bit-shift direction and amount (negative = right shift). Returns: Scaled polynomial term value. """ value = 0 if shift < 0: value = int((_input << fraction) / mag) >> -shift else: value = int((_input << fraction) / mag) << shift return value
[docs] def SI1133_calcEvalPoly( self, x: int, y: int, input_fraction: int, output_fraction: int, numm_coeff: int, kp: list[SI1133_Coeff_TypeDef], ) -> int: """Evaluate a two-variable polynomial using factory calibration coefficients. Used internally to convert raw ADC readings to physical units (lux or UV index). Args: x: First input variable (e.g. visible high ADC value). y: Second input variable (e.g. IR ADC value). input_fraction: Fixed-point fractional bits for input scaling. output_fraction: Fixed-point fractional bits for output scaling. numm_coeff: Number of coefficients to evaluate. kp: List of polynomial coefficients. Returns: Absolute value of the evaluated polynomial result. """ output = 0 info = 0 x_order = 0 y_order = 0 sign = 0 shift = 0 mag = 0 x1 = 0 x2 = 0 y1 = 0 y2 = 0 kp_count = 0 for _ in range(numm_coeff): info = kp[kp_count].info x_order = (info & self.X_ORDER_MASK) >> 4 y_order = info & self.Y_ORDER_MASK # shift = (info & 0xff00) >> 8 # shift ^= 0x00ff # shift += 1 # shift = -shift shift = kp[kp_count].shift mag = kp[kp_count].mag if (info & self.SIGN_MASK) >> 7: sign = -1 else: sign = 1 if (x_order == 0) and (y_order == 0): output += sign * mag << output_fraction else: if x_order > 0: x1 = self.SI1133_calcPolyInner(x, input_fraction, mag, shift) if x_order > 1: x2 = self.SI1133_calcPolyInner(x, input_fraction, mag, shift) else: x2 = 1 else: x1 = 1 x2 = 1 if y_order > 0: y1 = self.SI1133_calcPolyInner(y, input_fraction, mag, shift) if y_order > 1: y2 = self.SI1133_calcPolyInner(y, input_fraction, mag, shift) else: y2 = 1 else: y1 = 1 y2 = 1 output += sign * x1 * x2 * y1 * y2 kp_count += 1 if output < 0: output = -output return output
# -------------------------------------------------
[docs] def SI1133_getUv(self, uv: int) -> int: """Convert a raw UV ADC value to a scaled UV index (fixed-point). Args: uv: Raw ADC value from the UV channel. Returns: Scaled UV index value in fixed-point representation. """ return self.SI1133_calcEvalPoly( 0, uv, self.UV_INPUT_FRACTION, self.UV_OUTPUT_FRACTION, self.UV_NUMCOEFF, self.uk, )
# ------------------------------------------------- # TO DO
[docs] def SI1133_getLux(self, vis_high: int, vis_low: int, ir: int) -> int: """Convert raw visible and IR ADC values to a scaled lux value (fixed-point). Selects high-range or low-range coefficients based on whether the ADC values exceed the threshold. Args: vis_high: Raw ADC value from the visible high-sensitivity channel. vis_low: Raw ADC value from the visible low-sensitivity channel. ir: Raw ADC value from the IR channel. Returns: Scaled lux value in fixed-point representation. """ lux = 0 if (vis_high > self.ADC_THRESHOLD) | (ir > self.ADC_THRESHOLD): lux = self.SI1133_calcEvalPoly( vis_high, ir, self.INPUT_FRACTION_HIGH, self.LUX_OUTPUT_FRACTION, self.NUMCOEFF_HIGH, self.lk.coeff_high, ) else: lux = self.SI1133_calcEvalPoly( vis_low, ir, self.INPUT_FRACTION_LOW, self.LUX_OUTPUT_FRACTION, self.NUMCOEFF_LOW, self.lk.coeff_low, ) return lux
# -------------------------------------------------
[docs] def SI1133_measureLuxUvi(self) -> tuple[float, float]: """Force a measurement and return calibrated lux and UV index values. Triggers a forced measurement, waits for completion, then computes lux and UV index from the raw ADC samples. Returns: Tuple of (lux, uv_index) as floating-point values. """ # Force measurement self.SI1133_measurementForce() # go to sleep while the sensor does the conversion self.delay_ms(200) # Check if th measurement finished, if not then wait response = self.SI1133_registerRead(self.SI1133_REG_IRQ_STATUS) while response != 0x0F: self.delay_ms(5) response = self.SI1133_registerRead(self.SI1133_REG_IRQ_STATUS) # Get the results samples = self.SI1133_measurementGet() # Convert the readings to lux lux = self.SI1133_getLux(samples.ch1, samples.ch3, samples.ch2) lux = lux / (1 << self.LUX_OUTPUT_FRACTION) # Convert the readings to UV index uvi = self.SI1133_getUv(samples.ch0) uvi = uvi / (1 << self.UV_OUTPUT_FRACTION) return lux, uvi
# -------------------------------------------------
[docs] def readUV(self) -> float | Literal[0]: """Read the UV index from the sensor with timeout protection. Forces a measurement, waits for completion (up to TIMEOUT ms), and returns the calibrated UV index. Values above 15 are treated as invalid and return 0. Returns: UV index as a float, or 0 if the reading is invalid or timed out. """ uv = 0 # Force measurement self.SI1133_measurementForce() # go to sleep while the sensor does the conversion self.delay_ms(200) # Check if th measurement finished, if not then wait response = self.SI1133_registerRead(self.SI1133_REG_IRQ_STATUS) time_prev = self.current_milli_time() while response != 0x0F: timeout = self.current_milli_time() - time_prev if timeout > TIMEOUT: break self.delay_ms(5) response = self.SI1133_registerRead(self.SI1133_REG_IRQ_STATUS) # Get the results samples = self.SI1133_measurementGet() # Convert the readings to UV index context_logger.debug_with_context("SI1133", f"UV Sample : {samples.ch0}") uv = self.SI1133_getUv(samples.ch0) uv = uv / (1 << self.UV_OUTPUT_FRACTION) # uv = 0.0082* ((0.00391 * uv * uv) + uv ) if uv > 15: uv = 0 return uv
# -------------------------------------------------
[docs] def SI1133_getHardwareID(self) -> int: """Read the hardware part ID register. Returns: Part ID byte (expected 0x33 for Si1133). """ return self.SI1133_registerRead(self.SI1133_REG_PART_ID)
# -------------------------------------------------
[docs] def SI1133_getMeasurement(self) -> tuple[float, float]: """Read the latest measurement results and compute lux and UV index. Unlike ``SI1133_measureLuxUvi``, this does not force a new measurement; it reads whatever data is currently in the output registers. Returns: Tuple of (lux, uv_index) as floating-point values. """ # get the results samples = self.SI1133_measurementGet() # Convert the readings to lux # Convert the readings to lux lux = self.SI1133_getLux(samples.ch1, samples.ch3, samples.ch2) lux = lux / (1 << self.LUX_OUTPUT_FRACTION) # Convert the readings to UV index uvi = self.SI1133_getUv(samples.ch0) uvi = uvi / (1 << self.UV_OUTPUT_FRACTION) return lux, uvi
# -------------------------------------------------
[docs] def SI1133_getIrqStatus(self) -> int: """Read the interrupt status register. Returns: IRQ status byte indicating which channels have completed measurement. """ return self.SI1133_registerRead(self.SI1133_REG_IRQ_STATUS)
# ------------------------------------------------ if __name__ == "__main__": context_logger.info_with_context("SI1133", "Si113 Drivers") si1133 = Si1133() si1133.begin() while 1: context_logger.info_with_context("SI1133", f"UV:{si1133.readUV()}") time.sleep(1)