Proximity and Ambient Light Sensor¶
The Librem 5 development board has a proximity sensor (PS) and ambient light sensor (ALS) provided by the VCNL4040M3OE-H5 module. These sensors can be accessed programmatically via messages sent to the relevant addresses on the appropriate I2C bus. Applications can also receive sensor information by accessing the appropriate system D-Bus interface.
- D-Bus Interface
- I2C Bus
This guide provides a basic overview of the commands that can be used to obtain ambient light and proximity information from the device; first via the D-Bus interface, then using the I2C bus.
Support for this interface is only present in recent kernels. Please update the kernel on your device if you experience problems starting the service.
Information about the light level and proximity of nearby objects can be obtained by querying the interface provided by the D-Bus service, calling methods to access devices and reading their properties.
iio-sensor-proxy daemon is not already running, start it from the
command line in the following way:
systemctl start iio-sensor-proxy
Check that it is running with this command:
systemctl status iio-sensor-proxy
This should include useful information to help diagnose problems if the service does not start.
It is useful to get an overview of the properties and methods that the service
exposes via D-Bus. Use the
gdbus tool from the command line to discover
gdbus introspect --system --dest net.hadess.SensorProxy \ --object-path /net/hadess/SensorProxy
gdbus tool again, first claim the light:
gdbus call --system --dest net.hadess.SensorProxy \ --object-path /net/hadess/SensorProxy \ --method net.hadess.SensorProxy.ClaimLight
This will cause the
LightLevel property to be updated:
gdbus call --system --dest net.hadess.SensorProxy \ --object-path /net/hadess/SensorProxy \ --method org.freedesktop.DBus.Properties.Get \ net.hadess.SensorProxy LightLevel
Finally, release the light to stop the service from polling the device:
gdbus call --system --dest net.hadess.SensorProxy \ --object-path /net/hadess/SensorProxy \ --method net.hadess.SensorProxy.ReleaseLight
This can also be performed using the
Gio.DBusProxy class – see the
GNOME API Reference for further information.
This section contains advice that only works with older Linux
kernels that do not have built-in support for the proximity and
ambient light sensors.
i2cdetect tool does not produce the expected output then
the devices are being managed by the kernel and their I2C
interfaces are not accessible to user space programs.
The proximity and ambient light sensors are located at the top edge of the
development board below the
The VCNL4040M3OE-H5 module is accessed via an I2C bus address and exposes a set of registers that can be accessed by their addresses in the device. This means that we need two addresses – a bus address and a device address – to access a register.
The kernel provides abstractions that allow the sensors to be accessed fairly simply without requiring knowledge of the message protocol used for I2C devices.
Before trying to access the sensors as the
purism user, you need to
i2c-tools package on the development board:
sudo apt install i2c-tools
Then add the
purism user to the
sudo usermod -a -G i2c purism
This user should now have permission to read and write the device files for the I2C devices and run the tools to access them.
The module is available via address
0x60 on I2C bus 2. This can be
verified by running the
purism@pureos:~$ /usr/sbin/i2cdetect -y 2
This should produce output like the following:
0 1 2 3 4 5 6 7 8 9 a b c d e f 00: -- -- -- -- -- -- -- UU -- -- -- -- -- 10: -- -- -- -- -- -- -- -- -- -- -- -- -- -- 1e -- 20: -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- 30: -- -- -- -- -- -- -- -- -- -- -- -- UU -- -- -- 40: -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- 50: -- -- -- -- -- -- -- -- -- -- -- -- -- UU -- -- 60: 60 -- -- -- -- -- -- -- -- -- UU -- -- -- -- -- 70: -- -- -- -- -- -- -- --
It should be clear that there is a device at address
0x60 on the I2C bus
– this should be the proximity and ambient light sensor.
We check that the device at address
0x60 on I2C bus 2 is the sensor by
ID_M registers, which should be readable at
0x0c on the device. We can do this with the
/usr/sbin/i2cget -y 2 0x60 0x0c w
The expected value is
0x0186 for the sensor module. If a different value is
returned then the device is not the one we were expecting.
Initially, the device will be powered down. To determine its state we read its
ALS_CONF register at address
0x00 on the device:
/usr/sbin/i2cget -y 2 0x60 0x00 w
This should return
0x0001, indicating that the shut down bit is enabled.
Set it to
0x0000 with the
/usr/sbin/i2cset -y 2 0x60 0x00 0 w
When we no longer need to read from the device, we set the shut down bit again:
/usr/sbin/i2cset -y 2 0x60 0x00 1 w
We can read the state of the device again to verify that it has powered down, if necessary.
With the device powered, ambient light sensor readings can be obtained by
reading from the
ALS_Data_H registers at address
0x09 on the device:
/usr/sbin/i2cget -y 2 0x60 0x09 w
White channel values can be read from the
0x0a on the device:
/usr/sbin/i2cget -y 2 0x60 0x0a w
These should vary as the ambient light level at the sensor changes.
The state of the proximity sensor can be obtained by reading the
PS_CONF2 registers at address
0x03 on the device:
/usr/sbin/i2cget -y 2 0x60 0x03 w
The lowest bit (
PS_SD) of the value returned is
1 if the sensor is
shut down. Set it to
0 to power it up. In the simplest case, we can set
both the registers to
/usr/sbin/i2cset -y 2 0x60 0x03 0 w
Setting the lowest bit to
1 again will shut down the sensor.
With the proximity sensor enabled, values can be obtained for the proximity of
object to the sensor by reading from the
registers at address
0x08 on the device:
/usr/sbin/i2cget -y 2 0x60 0x08 w
Closer objects should result in higher values, up to the maximum value set in
bit 3 (
PS_HD) of the
PS_CONF2 register. The default resolution is 12
python3-smbus package can be used to read these levels. Install it on
the development board Python script with
sudo apt install python3-smbus
The following script will show the ambient light levels every second until it
#!/usr/bin/env python3 import smbus import time # Bus number and i2c address: BUS = 2 ADDRESS = 0x60 # Register addresses: ALS_CONF = 0x00 ALS_Data = 0x09 White_Data = 0x0a # Masks and values: ALS_SD = 1 # Create an object to represent the appropriate bus. bus = smbus.SMBus(BUS) # Power up the ambient light sensor if not already powered. state = bus.read_word_data(ADDRESS, ALS_CONF) if state & ALS_SD == ALS_SD: bus.write_word_data(ADDRESS, ALS_CONF, state ^ ALS_SD) try: while True: print('Ambient:', bus.read_word_data(ADDRESS, ALS_Data)) print('White: ', bus.read_word_data(ADDRESS, White_Data)) time.sleep(1) finally: # Restore the initial state. bus.write_word_data(ADDRESS, ALS_CONF, state)
Similarly, this script will show the proximity sensor’s response to close
objects four times a second:
#!/usr/bin/env python3 import smbus import time # Bus number and i2c address: BUS = 2 ADDRESS = 0x60 # Register addresses: PS_CONF1 = 0x03 PS_Data = 0x08 # Masks and values: PS_SD = 1 # Create an object to represent the appropriate bus. bus = smbus.SMBus(BUS) # Power up the proximity sensor if not already powered. state = bus.read_word_data(ADDRESS, PS_CONF1) if state & PS_SD == PS_SD: bus.write_word_data(ADDRESS, PS_CONF1, state ^ PS_SD) try: while True: print('Proximity:', bus.read_word_data(ADDRESS, PS_Data)) time.sleep(0.25) finally: # Restore the initial state. bus.write_word_data(ADDRESS, PS_CONF1, state)