|
1 | 1 | # SPDX-FileCopyrightText: Copyright (c) 2024 Liz Clark, Tim Cocks for Adafruit Industries
|
2 | 2 | #
|
3 | 3 | # SPDX-License-Identifier: MIT
|
| 4 | + |
| 5 | +# Written by Liz Clark & Tim Cocks (Adafruit Industries) with OpenAI ChatGPT 4o May 13, 2024 build |
| 6 | +# https://help.openai.com/en/articles/6825453-chatgpt-release-notes |
| 7 | + |
| 8 | +# https://chatgpt.com/share/6720fdc1-2bc0-8006-8f7c-f0ff11189ea9 |
4 | 9 | """
|
5 | 10 | `adafruit_vcnl4200`
|
6 | 11 | ================================================================================
|
|
141 | 146 |
|
142 | 147 |
|
143 | 148 | class Adafruit_VCNL4200:
|
| 149 | + """ |
| 150 | + Driver for VCNL4200 Proximity & Light Sensor |
| 151 | +
|
| 152 | + :param i2c: I2C bus |
| 153 | + :param addr: I2C Address if not using default |
| 154 | + """ |
| 155 | + |
144 | 156 | # Device ID expected value for VCNL4200
|
145 | 157 | _DEVICE_ID = 0x1058
|
146 | 158 |
|
147 | 159 | # Register for ALS configuration
|
148 | 160 | als_shutdown = RWBits(1, _ALS_CONF, 0) # ALS shutdown bit
|
149 |
| - als_integration_time = RWBits(2, _ALS_CONF, 6) # ALS integration time bits |
150 |
| - als_persistance = RWBits(2, _ALS_CONF, 2) # ALS persistence bits |
151 |
| - als_low_threshold = UnaryStruct(_ALS_THDL, "<H") |
152 |
| - als_high_threshold = UnaryStruct(_ALS_THDH, "<H") |
153 |
| - prox_active_force = RWBit(_PS_CONF3MS, 3) |
154 |
| - prox_duty = RWBits(2, _PS_CONF12, 6) |
| 161 | + """Enables or disables the ALS (Ambient Light Sensor) shutdown mode. |
| 162 | + Controls the power state of the ALS, allowing it to be shut down to conserve power. |
| 163 | + Set to true to enable shutdown mode (power off ALS), false to disable (power on ALS).""" |
| 164 | + als_threshold_low = UnaryStruct(_ALS_THDL, "<H") |
| 165 | + """The 16-bit numerical low threshold for the ALS (Ambient Light Sensor) interrupt. |
| 166 | + If the ALS reading falls below this threshold and ALS interrupt is enabled, an interrupt |
| 167 | + will be triggered.""" |
| 168 | + als_threshold_high = UnaryStruct(_ALS_THDH, "<H") |
| 169 | + """The 16-bit numerical upper limit for proximity detection.If the proximity reading |
| 170 | + exceeds this threshold and the interrupt is enabled, an interrupt will be triggered.""" |
155 | 171 | prox_hd = RWBit(_PS_CONF12, 11)
|
156 |
| - prox_integration_time = RWBits(3, _PS_CONF12, 1) |
157 |
| - prox_interrupt = RWBits(2, _PS_CONF12, 8) |
158 |
| - prox_persistence = RWBits(2, _PS_CONF12, 4) |
| 172 | + """The proximity sensor (PS) resolution to high definition (HD). Set to True to enable |
| 173 | + high definition mode (16-bit resolution), False for standard resolution (12-bit).""" |
159 | 174 | prox_shutdown = RWBit(_PS_CONF12, 0) # Bit 0: PS_SD (Proximity Sensor Shutdown)
|
| 175 | + """The proximity sensor (PS) shutdown mode. Set to True to enable shutdown mode |
| 176 | + (power off proximity sensor), false to disable (power on proximity sensor).""" |
160 | 177 | proximity = ROUnaryStruct(_PS_DATA, "<H")
|
| 178 | + """The current proximity data from the proximity sensor (PS).""" |
161 | 179 | lux = ROUnaryStruct(_ALS_DATA, "<H")
|
| 180 | + """The current lux data from ambient light sensor (ALS)""" |
| 181 | + _prox_multi_pulse = RWBits(2, _PS_CONF3MS, 5, register_width=2) |
| 182 | + _prox_interrupt = RWBits(2, _PS_CONF12, 8, register_width=2) |
| 183 | + _prox_duty = RWBits(2, _PS_CONF12, 6, register_width=2) |
| 184 | + _prox_integration_time = RWBits(3, _PS_CONF12, 1, register_width=2) |
| 185 | + _prox_persistence = RWBits(2, _PS_CONF12, 4, register_width=2) |
| 186 | + prox_sun_cancellation = RWBit(_PS_CONF3MS, 0, register_width=2) |
| 187 | + """The sunlight cancellation feature for the proximity sensor (PS). |
| 188 | + Controls the sunlight cancellation feature, which improves proximity |
| 189 | + detection accuracy in bright or sunny conditions by mitigating background |
| 190 | + light interference. Set to true to enable sunlight cancellation, false to disable |
| 191 | + it.""" |
| 192 | + prox_sunlight_double_immunity = RWBit(_PS_CONF3MS, 1, register_width=2) |
| 193 | + """Double immunity mode to sunlight for the proximity |
| 194 | + sensor (PS). Configures an enhanced sunlight immunity mode, which increases the sensor’s |
| 195 | + ability to filter out interference from bright sunlight for improved proximity detection. |
| 196 | + Set to True to enable double sunlight immunity, False to disable it.""" |
| 197 | + prox_active_force = RWBit(_PS_CONF3MS, 3, register_width=2) |
| 198 | + """The active force mode for the proximity sensor (PS). Configures the proximity |
| 199 | + sensor to operate in active force mode, where measurements are taken only when |
| 200 | + manually triggered by `trigger_prox()`. Set to True to enable active force mode, |
| 201 | + False to disable it.""" |
| 202 | + prox_smart_persistence = RWBit(_PS_CONF3MS, 4, register_width=2) |
| 203 | + """The smart persistence mode for the proximity sensor (PS). |
| 204 | + Configures the smart persistence feature, which helps reduce false triggers |
| 205 | + by adjusting the persistence behavior based on ambient conditions. |
| 206 | + Set to True to enable smart persistence, False to disable it.""" |
| 207 | + sun_protect_polarity = RWBit(_PS_CONF3MS, 3 + 8, register_width=2) |
| 208 | + """The polarity of the sunlight protection output for the proximity sensor (PS). |
| 209 | + Configures the polarity of the sunlight protection output signal, which |
| 210 | + affects how sunlight interference is managed in proximity detection. |
| 211 | + Set to True for active high polarity, False for active low polarity.""" |
| 212 | + prox_boost_typical_sunlight_capability = RWBit(_PS_CONF3MS, 4 + 8, register_width=2) |
| 213 | + """The boosted sunlight protection capability for the proximity sensor (PS). |
| 214 | + Boosts the proximity sensor's resistance to typical sunlight interference for |
| 215 | + more reliable proximity measurements in bright ambient conditions. |
| 216 | + Set to true to enable boosted sunlight protection, false to disable it.""" |
| 217 | + prox_interrupt_logic_mode = RWBit(_PS_CONF3MS, 7 + 8, register_width=2) |
| 218 | + """The interrupt logic mode for the proximity sensor (PS). Configures |
| 219 | + the interrupt output logic mode for the proximity sensor, determining if |
| 220 | + the interrupt signal is active high or active low. Set to True for active |
| 221 | + high logic, False for active low logic.""" |
| 222 | + prox_cancellation_level = UnaryStruct( |
| 223 | + _PS_CANC_LVL, "<H" |
| 224 | + ) # 16-bit registor for cancellation level |
| 225 | + """The 16-bit numerical proximity sensor (PS) cancellation level. Configures |
| 226 | + the cancellation level for the proximity sensor, which helps to |
| 227 | + reduce interference from background light by subtracting a baseline level.""" |
| 228 | + prox_int_threshold_low = UnaryStruct( |
| 229 | + _PS_THDL, "<H" |
| 230 | + ) # 16-bit register for proximity threshold low |
| 231 | + """The 16-bit numerical low threshold for the proximity sensor (PS) interrupt. |
| 232 | + Configures the lower limit for proximity detection. If the proximity reading |
| 233 | + falls below this threshold and the interrupt is enabled, an interrupt will be |
| 234 | + triggered.""" |
| 235 | + prox_int_threshold_high = UnaryStruct( |
| 236 | + _PS_THDH, "<H" |
| 237 | + ) # 16-bit register for proximity threshold high |
| 238 | + """The 16-bit numerical high threshold for the proximity sensor (PS) interrupt. |
| 239 | + Configures the upper limit for proximity detection. If the proximity reading |
| 240 | + exceeds this threshold and the interrupt is enabled, an interrupt will be |
| 241 | + triggered.""" |
| 242 | + _interrupt_flags = RWBits(8, _INT_FLAG, 0, register_width=2) |
| 243 | + _prox_led_current = RWBits(3, _PS_CONF3MS, 8, register_width=2) |
162 | 244 | white_light = ROUnaryStruct(_WHITE_DATA, "<H") # 16-bit register for white light data
|
| 245 | + """The current white light data. The 16-bit numerical raw white light measurement, representing |
| 246 | + the sensor’s sensitivity to white light.""" |
| 247 | + _als_int_time = RWBits(2, _ALS_CONF, 6, register_width=2) |
| 248 | + _als_persistence = RWBits(2, _ALS_CONF, 2, register_width=2) |
163 | 249 | _als_int_en = RWBits(1, _ALS_CONF, 1) # Bit 1: ALS interrupt enable
|
164 | 250 | _als_int_switch = RWBits(1, _ALS_CONF, 5) # Bit 5: ALS interrupt channel selection (white/ALS)
|
165 | 251 | _proximity_int_en = RWBits(1, _PS_CONF12, 0)
|
166 | 252 | _prox_trigger = RWBit(_PS_CONF3MS, 2)
|
| 253 | + _device_id = UnaryStruct(_ID, "<H") |
| 254 | + _raw_interrupt_flags = UnaryStruct(_INT_FLAG, "<H") # 2-byte read, big endian |
167 | 255 |
|
168 | 256 | def __init__(self, i2c: I2C, addr: int = _I2C_ADDRESS) -> None:
|
169 | 257 | self.i2c_device = I2CDevice(i2c, addr)
|
170 |
| - for _ in range(2): |
171 |
| - with self.i2c_device as i2c: |
172 |
| - # Manually read the device ID register (0x0E, 2 bytes) |
173 |
| - buffer = bytearray(2) |
174 |
| - i2c.write_then_readinto(bytes([_ID]), buffer) |
175 |
| - device_id = int.from_bytes(buffer, "little") |
176 |
| - # Check if it matches expected device ID |
177 |
| - if device_id == self._DEVICE_ID: |
178 |
| - break |
179 |
| - else: |
180 |
| - raise RuntimeError("Device ID mismatch.") |
| 258 | + if self._device_id != self._DEVICE_ID: |
| 259 | + raise RuntimeError("Device ID mismatch.") |
181 | 260 | try:
|
182 | 261 | self.als_shutdown = False
|
183 | 262 | self.als_integration_time = ALS_IT["50MS"]
|
184 | 263 | self.als_persistence = ALS_PERS["1"]
|
185 |
| - self.als_low_threshold = 0 |
186 |
| - self.als_high_threshold = 0xFFFF |
187 |
| - self.set_interrupt(enabled=False, white_channel=False) |
| 264 | + self.als_threshold_low = 0 |
| 265 | + self.als_threshold_high = 0xFFFF |
| 266 | + self.als_interrupt(enabled=True, white_channel=False) |
188 | 267 | self.prox_duty = PS_DUTY["1_160"]
|
189 | 268 | self.prox_shutdown = False
|
190 | 269 | self.prox_integration_time = PS_IT["1T"]
|
191 | 270 | self.prox_persistence = PS_PERS["1"]
|
192 | 271 | except Exception as error:
|
193 | 272 | raise RuntimeError(f"Failed to initialize: {error}") from error
|
194 | 273 |
|
195 |
| - def set_interrupt(self, enabled, white_channel): |
| 274 | + def als_interrupt(self, enabled: bool, white_channel: bool) -> bool: |
196 | 275 | """Configure ALS interrupt settings, enabling or disabling
|
197 |
| - the interrupt and selecting the interrupt channel.""" |
| 276 | + the interrupt and selecting the interrupt channel. |
| 277 | +
|
| 278 | + :return bool: True if setting the values succeeded, False otherwise.""" |
198 | 279 | try:
|
199 | 280 | self._als_int_en = enabled
|
200 | 281 | self._als_int_switch = white_channel
|
201 | 282 | return True
|
202 | 283 | except OSError:
|
203 | 284 | return False
|
204 | 285 |
|
205 |
| - def trigger_prox(self): |
206 |
| - """Triggers a single proximity measurement manually in active force mode.""" |
| 286 | + def trigger_prox(self) -> bool: |
| 287 | + """Triggers a single proximity measurement manually in active force mode. |
| 288 | + Initiates a one-time proximity measurement in active force mode. This can be |
| 289 | + used when proximity measurements are not continuous and need to be triggered |
| 290 | + individually. |
| 291 | +
|
| 292 | + :return bool: True if triggering succeeded, False otherwise.""" |
207 | 293 | try:
|
208 | 294 | self._prox_trigger = True
|
209 | 295 | return True
|
210 | 296 | except OSError:
|
211 | 297 | return False
|
| 298 | + |
| 299 | + @property |
| 300 | + def prox_interrupt(self) -> str: |
| 301 | + """Interrupt mode for the proximity sensor |
| 302 | + Configures the interrupt condition for the proximity sensor, which determines |
| 303 | + when an interrupt will be triggered based on the detected proximity. |
| 304 | +
|
| 305 | + :return str: The interrupt mode for the proximity sensor. |
| 306 | + """ |
| 307 | + PS_INT_REVERSE = {value: key for key, value in PS_INT.items()} |
| 308 | + # Return the mode name if available, otherwise return "Unknown" |
| 309 | + return PS_INT_REVERSE.get(self._prox_interrupt, "Unknown") |
| 310 | + |
| 311 | + @prox_interrupt.setter |
| 312 | + def prox_interrupt(self, mode: int) -> None: |
| 313 | + if mode not in PS_INT.values(): |
| 314 | + raise ValueError("Invalid interrupt mode") |
| 315 | + self._prox_interrupt = mode |
| 316 | + |
| 317 | + @property |
| 318 | + def prox_duty(self) -> str: |
| 319 | + """Proximity sensor duty cycle setting. |
| 320 | + Configures the duty cycle of the infrared emitter for the proximity sensor, |
| 321 | + which affects power consumption and response time. |
| 322 | +
|
| 323 | + :return str: The duty cycle of the infrared emitter for the proximity sensor.""" |
| 324 | + # Reverse lookup dictionary for PS_DUTY |
| 325 | + PS_DUTY_REVERSE = {value: key for key, value in PS_DUTY.items()} |
| 326 | + return PS_DUTY_REVERSE.get(self._prox_duty, "Unknown") |
| 327 | + |
| 328 | + @prox_duty.setter |
| 329 | + def prox_duty(self, setting: int) -> None: |
| 330 | + if setting not in PS_DUTY.values(): |
| 331 | + raise ValueError(f"Invalid proximity duty cycle setting: {setting}") |
| 332 | + self._prox_duty = setting |
| 333 | + |
| 334 | + @property |
| 335 | + def als_integration_time(self) -> str: |
| 336 | + """ALS integration time setting. Configures the integration time for |
| 337 | + the ambient light sensor to control sensitivity and range. |
| 338 | +
|
| 339 | + :return str: The ALS integration time setting""" |
| 340 | + # Reverse lookup dictionary for ALS_IT |
| 341 | + ALS_IT_REVERSE = {value: key for key, value in ALS_IT.items()} |
| 342 | + # Map the result to the setting name, defaulting to "Unknown" if unmatched |
| 343 | + return ALS_IT_REVERSE.get(self._als_int_time, "Unknown") |
| 344 | + |
| 345 | + @als_integration_time.setter |
| 346 | + def als_integration_time(self, it: int) -> None: |
| 347 | + if it not in ALS_IT.values(): |
| 348 | + raise ValueError(f"Invalid ALS integration time setting: {it}") |
| 349 | + self._als_int_time = it |
| 350 | + |
| 351 | + @property |
| 352 | + def als_persistence(self) -> str: |
| 353 | + """ALS persistence setting. Configures the persistence level |
| 354 | + of the ALS interrupt, which determines how many consecutive ALS threshold |
| 355 | + triggers are required to activate the interrupt. |
| 356 | +
|
| 357 | + :return str: The current persistence setting |
| 358 | + """ |
| 359 | + # Reverse lookup dictionary for ALS_PERS |
| 360 | + ALS_PERS_REVERSE = {value: key for key, value in ALS_PERS.items()} |
| 361 | + return ALS_PERS_REVERSE.get(self._als_persistence, "Unknown") |
| 362 | + |
| 363 | + @als_persistence.setter |
| 364 | + def als_persistence(self, pers: int) -> None: |
| 365 | + if pers not in ALS_PERS.values(): |
| 366 | + raise ValueError(f"Invalid ALS persistence setting: {pers}") |
| 367 | + self._als_persistence = pers |
| 368 | + |
| 369 | + @property |
| 370 | + def prox_multi_pulse(self) -> str: |
| 371 | + """Configures the number of infrared pulses used by the proximity sensor in a |
| 372 | + single measurement. Increasing the pulse count can improve accuracy, |
| 373 | + especially in high ambient light conditions. |
| 374 | +
|
| 375 | + :return str: The number of infrared pulses configured for the proximity sensor |
| 376 | + """ |
| 377 | + PS_MP_REVERSE = {value: key for key, value in PS_MPS.items()} |
| 378 | + return PS_MP_REVERSE.get(self._prox_multi_pulse, "Unknown") |
| 379 | + |
| 380 | + @prox_multi_pulse.setter |
| 381 | + def prox_multi_pulse(self, setting: int) -> None: |
| 382 | + if setting not in PS_MPS.values(): |
| 383 | + raise ValueError(f"Invalid PS_MPS setting: {setting}") |
| 384 | + self._prox_multi_pulse = setting |
| 385 | + |
| 386 | + @property |
| 387 | + def prox_integration_time(self) -> str: |
| 388 | + """Proximity sensor integration time. Configures the integration |
| 389 | + time for the proximity sensor, which affects the duration for which the |
| 390 | + sensor is sensitive to reflected light. |
| 391 | +
|
| 392 | + :return str: The current integration time |
| 393 | + """ |
| 394 | + # Reverse lookup dictionary for PS_IT |
| 395 | + PS_IT_REVERSE = {value: key for key, value in PS_IT.items()} |
| 396 | + return PS_IT_REVERSE.get(self._prox_integration_time, "Unknown") |
| 397 | + |
| 398 | + @prox_integration_time.setter |
| 399 | + def prox_integration_time(self, setting: int) -> None: |
| 400 | + if setting not in PS_IT.values(): |
| 401 | + raise ValueError(f"Invalid proximity integration time setting: {setting}") |
| 402 | + self._prox_integration_time = setting |
| 403 | + |
| 404 | + @property |
| 405 | + def prox_persistence(self) -> str: |
| 406 | + """Proximity sensor persistence setting. Configures the persistence |
| 407 | + level of the proximity sensor interrupt, defining how many consecutive |
| 408 | + threshold triggers are required to activate the interrupt. |
| 409 | +
|
| 410 | + :return str: The current persistence setting |
| 411 | + """ |
| 412 | + # Reverse lookup dictionary for PS_PERS |
| 413 | + PS_PERS_REVERSE = {value: key for key, value in PS_PERS.items()} |
| 414 | + return PS_PERS_REVERSE.get(self._prox_persistence, "Unknown") |
| 415 | + |
| 416 | + @prox_persistence.setter |
| 417 | + def prox_persistence(self, setting: int) -> None: |
| 418 | + if setting not in PS_PERS.values(): |
| 419 | + raise ValueError(f"Invalid proximity persistence setting: {setting}") |
| 420 | + self._prox_persistence = setting |
| 421 | + |
| 422 | + @property |
| 423 | + def prox_led_current(self) -> str: |
| 424 | + """IR LED current setting. Configures the driving current for the |
| 425 | + infrared LED used in proximity detection, which affects the range |
| 426 | + and power consumption of the sensor. |
| 427 | +
|
| 428 | + :return str: The LED current setting""" |
| 429 | + # Reverse lookup dictionary for PS_PERS |
| 430 | + LED_I_REVERSE = {value: key for key, value in LED_I.items()} |
| 431 | + return LED_I_REVERSE.get(self._prox_led_current, "Unknown") |
| 432 | + |
| 433 | + @prox_led_current.setter |
| 434 | + def prox_led_current(self, setting: int) -> None: |
| 435 | + if setting not in LED_I.values(): |
| 436 | + raise ValueError(f"Invalid proximity IR LED current setting: {setting}") |
| 437 | + self._prox_led_current = setting |
| 438 | + |
| 439 | + @property |
| 440 | + def interrupt_flags(self) -> typing.Dict[str, bool]: |
| 441 | + """The current interrupt flags from the sensor. Retrieves the current |
| 442 | + interrupt status flags, which indicate various sensor states, such as |
| 443 | + threshold crossings or sunlight protection events. |
| 444 | +
|
| 445 | + :return dict: The current interrupt flag values |
| 446 | + """ |
| 447 | + # Read the full 16-bit register value and isolate the high byte |
| 448 | + raw_value = (self._raw_interrupt_flags >> 8) & 0xFF |
| 449 | + # Interpret each flag based on the datasheet's bit definition |
| 450 | + return { |
| 451 | + "ALS_HIGH": bool(raw_value & _INTFLAG_ALS_HIGH), |
| 452 | + "PROX_CLOSE": bool(raw_value & _INTFLAG_PROX_CLOSE), |
| 453 | + "ALS_LOW": bool(raw_value & _INTFLAG_ALS_LOW), |
| 454 | + "PROX_AWAY": bool(raw_value & _INTFLAG_PROX_AWAY), |
| 455 | + "PROX_SPFLAG": bool(raw_value & _INTFLAG_PROX_SPFLAG), |
| 456 | + "PROX_UPFLAG": bool(raw_value & _INTFLAG_PROX_UPFLAG), |
| 457 | + } |
0 commit comments