"""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)