Piicodev - detecting which sensors are attaced

Using Raspberry Pi4B / OS / Raspberry Pi OS Lite (32-bit) / Bullseye
Python 3.11

Question: Is there a way I can get detect piicodev sensors attached to a Pi4B? For example I need to know which of the following (for example) are available:

  • PiicoDev_BME280
  • PiicoDev_Buzzer
  • PiicoDev_ENS160
  • PiicoDev_QMC6310
  • PiicoDev_RFID
  • PiicoDev_RGB
  • PiicoDev_SSD1306
  • PiicoDev_TMP117
  • PiicoDev_Ultrasonic
  • PiicoDev_VEML6030
  • PiicoDev_VL53L1X

I assumed I could wrap a try/except around each initial initialisation call to each sensor but as it turns out this does not necessarily raise an error if the sensor is not connected. See below for an example for the TMP117:

try:
    log.info('Initialise : TMP117 - Precision Temperature Sensor')
    tempSensor = PiicoDev_TMP117()  # initialise sensor
    tempC = tempSensor.readTempC()  # celsius
    tempF = tempSensor.readTempF()  # fahrenheit
    tempK = tempSensor.readTempK()  # kelvin
except Exception as e:
    tempC = None
    tempF = None
    tempK = None
    log.error(f'{e=}')

The code above returns:

2023-10-03 15:25:00,829 :: INFO :: Initialise : TMP117 - Precision Temperature Sensor
PiicoDev could not communicate with module at address 0x48, check wiring
PiicoDev could not communicate with module at address 0x48, check wiring
PiicoDev could not communicate with module at address 0x48, check wiring

When for example the BME280 returns:

2023-10-03 15:25:00,815 :: INFO :: Initialise : BME280 - atmospheric sensor
PiicoDev could not communicate with module at address 0x77, check wiring
2023-10-03 15:25:00,816 :: ERROR :: e=OSError(121, 'Remote I/O error')

Should I be querying the results from i2cdetect -y 1 for example?

I think you need a Piicodev adapter for Raspberry Pi. As shown here in case of Pressure Sensor MS5637.

Hi @Jon140244

Once you are physically connected to the PiicoDev modules, this code will reveal which ones are programatically connected.

from machine import Pin, I2C      # Access to the GPIO pins, and the base i2c system

i2c = I2C(id=0)      # PiicoDev chain on i2c channel 0
#
# Power on self test
#
connected = i2c.scan()  # some possible things connected
                            # 8. == RGB Leds
                            # 40. == Capacitive touch sensor
                            # 41. == Laser ranger
                            # 60. == OLED display

print(connected)

and the output from my current hookup is

[16, 60, 82, 83, 119]

So I currently have connected

  • PiicoDev_VEML6030 address 16 (0x10)
  • PiicoDev_SSD1360 address 60 (0x3C)
  • PiicoDev_ENS160 address 82 (0x52) with the address switch ON to deconflict with the next
  • LTR390_Ambient/UV sensor address 83 (0x53)
  • PiicoDev_BME280 address 119 (0x77)

hope this helps
Murray

Edit - with a bit more thought, adding this to the previous code

for i in connected:
    print(i, hex(i))

gives better output

16 0x10
60 0x3c
82 0x52
83 0x53
119 0x77

which if one got really creative could be used to display the PiicoDev / other hardware modules onscreen via a suitable lookup.

and using something like this to test inline if the module is present

if connected.count(60) == 1:
    print('found OLED')
else:
    print('no display')

Murray

5 Likes

@Jon140244

Note: this test can be done before any module initialisation is attempted, since it only uses the most low-level i2c bus actions.

Murray

this is the complete code

#
# Power on i2c connectivity test
#
from machine import Pin, I2C      # Access to the GPIO pins, and the base i2c system

i2c = I2C(id=0)                   # PiicoDev chain on i2c channel 0
connected = i2c.scan()            # whats connected

print(connected)                  # simple output

for i in connected:               # fancier output
    print(i, hex(i))
    
if connected.count(60) == 1:      # operational test
    print('found OLED')
else:
    print('no display')
3 Likes

Hi Murray, thanks for your replies. This is the correct answer for me. Thank you!

Hi Murray,

What an elegant solution!
Perhaps it is worth baking in a I2C diagnostic test into the PiicoDev unified libraries, then you could call a function to have your device report which I2C addresses it can see and what PiicoDev modules they typically correspond to.
I’ll let our design team know about your solution so they can look into it, but if you wanted to contribute your ideas yourself then I’m sure they’d be keen to see a pull request with your idea.
https://github.com/CoreElectronics/CE-PiicoDev-Unified/pulls

2 Likes

Hi Trent

I have extended what Murray very kindly provided this morning, see below:

import smbus

devices_details_list: dict = {
0x77: {
‘device_name’: ‘Atmospheric Sensor’,
‘device_long_name’: ‘PiicoDev BME280 Atmospheric Sensor’,
‘device_short_name’: ‘BME280’},
0x10: {
‘device_name’: ‘Ambient Light Sensor’,
‘device_long_name’: ‘PiicoDev VEM6030 Ambient Light Sensor’,
‘device_short_name’: ‘VEM6030’},
0x48: {
‘device_name’: ‘Precision Temperature Sensor’,
‘device_long_name’: ‘PiicoDev TMP117 Precision Temperature Sensor’,
‘device_short_name’: ‘TMP117’},
0x2c: {
‘device_name’: ‘RFID Module’,
‘device_long_name’: ‘PiicoDev RFID Module (NFC 13.56MHz)’,
‘device_short_name’: ‘RFID’},
0x3c: {
‘device_name’: ‘OLED Module’,
‘device_long_name’: ‘PiicoDev OLED Module SSD1306’,
‘device_short_name’: ‘SSD1306’},
0x53: {
‘device_name’: ‘Air Quality Sensor’,
‘device_long_name’: ‘PiicoDev Air Quality Sensor ENS160’,
‘device_short_name’: ‘ENS160’},
0x5c: {
‘device_name’: ‘Buzzer Module’,
‘device_long_name’: ‘PiicoDev Buzzer Module’,
‘device_short_name’: ‘BUZZER’},
0x29: {
‘device_name’: ‘Laser Distance Sensor’,
‘device_long_name’: ‘PiicoDev Laser Distance Sensor VL53L1X’,
‘device_short_name’: ‘VL53L1X’},
0x8: {
‘device_name’: ‘RGB LED Module’,
‘device_long_name’: ‘PiicoDev 3x RGB LED Module’,
‘device_short_name’: ‘LED’},

}

bus = smbus.SMBus(1) # Use I2C bus number 1 (on Raspberry Pi 3/4, it’s 1. On Pi 1 or Zero, it’s 0)

def i2c_scan(bus):
devices =
for i in range(3, 127): # Start from 3 as 0-2 are reserved I2C addresses
try:
bus.read_byte(i)
devices.append(i)
except:
continue
return devices

connected = i2c_scan(bus)

print(connected)

for i in connected:
r = devices_details_list.get(i)
print(i, hex(i), r)

I think this should be incorporated into the standard CE library?

PS there are a few items missing from the piicodev catalogue items. Am adding those today.

Cheers
Jon

2 Likes

and output is something like (on one of my test devices):

[8, 16, 44, 60, 72, 83, 92, 119]
8 0x8 {‘device_name’: ‘RGB LED Module’, ‘device_long_name’: ‘PiicoDev 3x RGB LED Module’, ‘device_short_name’: ‘LED’}
16 0x10 {‘device_name’: ‘Ambient Light Sensor’, ‘device_long_name’: ‘PiicoDev VEM6030 Ambient Light Sensor’, ‘device_short_name’: ‘VEM6030’}
44 0x2c {‘device_name’: ‘RFID Module’, ‘device_long_name’: ‘PiicoDev RFID Module (NFC 13.56MHz)’, ‘device_short_name’: ‘RFID’}
60 0x3c {‘device_name’: ‘OLED Module’, ‘device_long_name’: ‘PiicoDev OLED Module SSD1306’, ‘device_short_name’: ‘SSD1306’}
72 0x48 {‘device_name’: ‘Precision Temperature Sensor’, ‘device_long_name’: ‘PiicoDev TMP117 Precision Temperature Sensor’, ‘device_short_name’: ‘TMP117’}
83 0x53 {‘device_name’: ‘Air Quality Sensor’, ‘device_long_name’: ‘PiicoDev Air Quality Sensor ENS160’, ‘device_short_name’: ‘ENS160’}
92 0x5c {‘device_name’: ‘Buzzer Module’, ‘device_long_name’: ‘PiicoDev Buzzer Module’, ‘device_short_name’: ‘BUZZER’}
119 0x77 {‘device_name’: ‘Atmospheric Sensor’, ‘device_long_name’: ‘PiicoDev BME280 Atmospheric Sensor’, ‘device_short_name’: ‘BME280’}

2 Likes

And the final steps could/would be to change this

into something like this

if connected.count(72) == 1:
    tempSensor = PiicoDev_TMP117()
1 Like

Solution by @Murray125532 is best way to go.

Recently I used the try: except: way of detecting what is connected with no success.
I delved into the PicoDev libraries, the error message can be turned off but it does not help.
There were other issues with the way the PicoDev libraries are setup.
Work nicely if something is connected and remains connected.

My problem was:-
If a sensor was randomly connected and disconnected, I wanted the program to detect it.
I didn’t get a good solution and eventually decided not to proceed.

If the PicoDev libraries had a function to check that would be excellent.

Cheers
Jim

1 Like

Hi all,

Am working on some code to put into the common PiicoDev core … soon.

Murray
(and thanks for the approval guys)

1 Like

Hi all,

Been doing some code bashing and can generate this output now, based on the default (and some optional) PiicoDev addresses.
From an i2c scan …

16 0x10 PiicoDev VEML6030 Colour Sensor
16 0x10 PiicoDev VEML6030 Ambient Light Sensor (ASW off) - Possible conflict
60 0x3c PiicoDev OLED Module SSD1306
82 0x52 PiicoDev Real Time Clock (RTC) RV3028
82 0x52 PiicoDev Air Quality Sensor ENS160 (ASW on) - Possible conflict
83 0x53 PiicoDev Air Quality Sensor ENS160 (ASW off)
119 0x77 PiicoDev BME280 Atmospheric Sensor

There is actually only 5 devices in the chain, being

16 0x10 PiicoDev VEML6030 Ambient Light Sensor (ASW off) - Possible conflict
60 0x3c PiicoDev OLED Module SSD1306
82 0x52 PiicoDev Air Quality Sensor ENS160 (ASW on) - Possible conflict
83 0x53 LTR390_Ambient/UV sensor
119 0x77 PiicoDev BME280 Atmospheric Sensor

Note: there is no way to differentiate PiicoDev devices and items from other manufacturers.
And yes there are address conflicts within the PiicoDev range, which can usually be dealt with via the ASW settings and/or by programatically twiddling the address if that is possible on the module.

All this code can do is highlight the devices present, and possible issues with addresses.
(It is still WIP)
Murray

3 Likes

Murray, could I suggest we move these config into a external xxx.json file, I have added most here for you:

{
  "0x77": {
    "device_name": "Atmospheric Sensor",
    "device_long_name": "PiicoDev BME280 Atmospheric Sensor",
    "device_short_name": "BME280"
  },
  "0x10": {
    "device_name": "Colour Sensor",
    "device_long_name": "PiicoDev Colour Sensor VEML6040",
    "device_short_name": "VEML6040"
  },
  "0x10": {
    "device_name": "Ambient Light Sensor",
    "device_long_name": "PiicoDev VEM6030 Ambient Light Sensor",
    "device_short_name": "VEML6030"
  },
  "0x48": {
    "device_name": "Precision Temperature Sensor",
    "device_long_name": "PiicoDev TMP117 Precision Temperature Sensor",
    "device_short_name": "TMP117"
  },
  "0x2c": {
    "device_name": "RFID Module",
    "device_long_name": "PiicoDev RFID Module (NFC 13.56MHz)",
    "device_short_name": "RFID"
  },
  "0x3c": {
    "device_name": "OLED Module",
    "device_long_name": "PiicoDev OLED Module SSD1306",
    "device_short_name": "SSD1306"
  },
  "0x53": {
    "device_name": "Air Quality Sensor",
    "device_long_name": "PiicoDev Air Quality Sensor ENS160",
    "device_short_name": "ENS160"
  },
  "0x5c": {
    "device_name": "Buzzer Module",
    "device_long_name": "PiicoDev Buzzer Module",
    "device_short_name": "BUZZER"
  },
  "0x29": {
    "device_name": "Laser Distance Sensor",
    "device_long_name": "PiicoDev Laser Distance Sensor VL53L1X",
    "device_short_name": "VL53L1X"
  },
  "0x8": {
    "device_name": "RGB LED Module",
    "device_long_name": "PiicoDev 3x RGB LED Module",
    "device_short_name": "LED"
  }
}

Also yes found that there are a few PiicoDev devices with the same default ID’s for example:

VEML6030: # I2C address can be changed
VEML6040: # I2C address can NOT be changed

Not 100% sure why these would overlap as defaults? It would make sense for each device CE put out to have its own unique ID. And yes am aware there are other 3rd party devices where the ID’s will clash…

1 Like

Hi @Jon140244 ,
I am sticking with double level dictionaries at the moment, yes, actually have two of them to handle some of the conflicts as I can’t have identical keys (the ID value) in a dictionary.

Why? because this code will hopefully wind up in the PiicoDev_Unified.py file in the future.

The ‘constants’ and the two dictionaries as at this time

#
# declare "CONSTANTS"
#
__LED_ID = 0x8
__VEML6030_0_ID = 0x10	# CONFLICT
__VEML6040_ID = 0x10	# CONFLICT
__QMC6310_ID = 0x1c		# Magnetometer
__LIS3DH_1_ID = 0x18	# Accelerometer - ASW ON
__LIS3DH_0_ID = 0x19	# Accelerometer - ASW OFF
__TRANSCEIVER_ID = 0x1a
__TOUCH_ID = 0x28
__VL53L1X_ID = 0x29
__RFID_ID = 0x2c
__ULTRASONIC_ID = 0x35		# Conflict
__POTENTIOMETER_ID = 0x35	# Conflict
__SSD1306_ID = 0x3c
__BUTTON_ID = 0x42
__SERVO_ID = 0x44
__TMP117_ID = 0x48		# CONFLICT
__VEML6030_1_ID = 0x48	# CONFLICT
__RV3028_ID = 0x52		# RTC
__ENS160_1_ID = 0x52	# ASW on
__ENS160_0_ID = 0x53	# ASW off
__BUZZER_ID = 0x5c
__MS5637_ID = 0x76
__BME280_ID = 0x77

devices_details_list: dict = {
    __LED_ID: {			# 8.   0x8
        'device_name': 'RGB LED Module',
        'device_long_name': 'PiicoDev 3x RGB LED Module',
        'device_short_name': 'LED'},
    __VEML6040_ID: {		# 16.  0x10
        'device_name': 'Colour Sensor',
        'device_long_name': 'PiicoDev VEML6030 Colour Sensor',
        'device_short_name': 'VEML6040'},
    __LIS3DH_1_ID: {		# 24.  0x18
        'device_name': 'Accelerometer',
        'device_long_name': 'PiicoDev 3-Axis Accelerometer LIS3DH (ASW on)',
        'device_short_name': 'LIS3DH (ASW on)'},
    __LIS3DH_0_ID: {		# 25.  0x19
        'device_name': 'Accelerometer',
        'device_long_name': 'PiicoDev 3-Axis Accelerometer LIS3DH (ASW off)',
        'device_short_name': 'LIS3DH (ASW off)'},
    __TRANSCEIVER_ID: {		# 26.  0x1A
        'device_name': 'Transceiver',
        'device_long_name': 'PiicoDev Transceiver 915MHz',
        'device_short_name': 'TRANSCEIVER'},
    __QMC6310_ID: {		# 28.  0x1c
        'device_name': 'Magnetometer',
        'device_long_name': 'PiicoDev Magnetometer QMC6310',
        'device_short_name': 'QMC6310'},
    __TOUCH_ID: {		# 40.  0x28
        'device_name': 'Capacitive Touch Sensor',
        'device_long_name': 'PiicoDev Capacitive Touch Sensor',
        'device_short_name': 'TOUCH'},
    __VL53L1X_ID: {		# 41.  0x29
        'device_name': 'Laser Distance Sensor',
        'device_long_name': 'PiicoDev Laser Distance Sensor VL53L1X',
        'device_short_name': 'VL53L1X'},
    __RFID_ID: {		# 45.  0x2c
        'device_name': 'RFID Module',
        'device_long_name': 'PiicoDev RFID Module (NFC 13.56MHz)',
        'device_short_name': 'RFID'},
    __ULTRASONIC_ID: {	# 53.  0x35
       'device_name': 'Ultrasonic Rangefinder',
        'device_long_name': 'PiicoDev Ultrasonic Rangefinder Module',
        'device_short_name': 'ULTRASONIC'},
    __SSD1306_ID: {		# 60.  0x3c
        'device_name': 'OLED Module',
        'device_long_name': 'PiicoDev OLED Module SSD1306',
        'device_short_name': 'SSD1306'},
    __BUTTON_ID: {		# 66.  0x42
        'device_name': 'Button',
        'device_long_name': 'PiicoDev Button',
        'device_short_name': 'BUTTON'},
    __SERVO_ID: {		# 68.  0x44
        'device_name': 'Servo Driver',
        'device_long_name': 'PiicoDev Servo Driver (4 Channel)',
        'device_short_name': 'SERVO'},
    __TMP117_ID: {		# 72.  0x48
        'device_name': 'Precision Temperature Sensor',
        'device_long_name': 'PiicoDev TMP117 Precision Temperature Sensor',
        'device_short_name': 'TMP117'},
    __RV3028_ID: {		# 82.  0x52
        'device_name': 'Real Time Clock',
        'device_long_name': 'PiicoDev Real Time Clock (RTC) RV3028',
        'device_short_name': 'RV3028'},
    __ENS160_0_ID: {		# 83.  0x53
        'device_name': 'Air Quality Sensor',
        'device_long_name': 'PiicoDev Air Quality Sensor ENS160 (ASW off)',
        'device_short_name': 'ENS160 (ASW off)'},
    __BUZZER_ID: {		# 92.  0x5c
        'device_name': 'Buzzer Module',
        'device_long_name': 'PiicoDev Buzzer Module',
        'device_short_name': 'BUZZER'},
    __MS5637_ID: {		# 118.  0x76
        'device_name': 'Pressure Sensor',
        'device_long_name': 'PiicoDev Pressure Sensor MS5637',
        'device_short_name': 'MS5637'},
    __BME280_ID: {		# 119.  0x77
        'device_name': 'Atmospheric Sensor',
        'device_long_name': 'PiicoDev BME280 Atmospheric Sensor',
        'device_short_name': 'BME280'},
}

################
## conflicts list
################
devices_details_conf_list: dict = {
    __VEML6030_0_ID: {		# 16.  0x10
        'device_name': 'Ambient Light Sensor',
        'device_long_name': 'PiicoDev VEML6030 Ambient Light Sensor (ASW off)',
        'device_short_name': 'VEML6030 (ASW off)'},
    __POTENTIOMETER_ID: {	# 53.  0x35
       'device_name': 'Potentiometer',
        'device_long_name': 'PiicoDev Potentiometer (Rotary)',
        'device_short_name': 'Potentiometer'},
    __VEML6030_1_ID: {	# 72.  0x48
        'device_name': 'Ambient Light Sensor',
        'device_long_name': 'PiicoDev VEML6030 Ambient Light Sensor (ASW on)',
        'device_short_name': 'VEML6030 (ASW on)'},
    __ENS160_1_ID: {		# 83.  0x53
        'device_name': 'Air Quality Sensor',
        'device_long_name': 'PiicoDev Air Quality Sensor ENS160 (ASW on)',
        'device_short_name': 'ENS160 (ASW on)'},
}

and some code to do the check and display

def show_what_is_connected():
    hit = 0
    connected = i2c.scan()
    for i in connected:
        if i in devices_details_list:
            s = devices_details_list[i]['device_long_name']
            print(i, hex(i), s)
            hit = 1
        if i in devices_details_conf_list:
            s = devices_details_conf_list[i]['device_long_name']
            print(i, hex(i), s, '<<<<< Possible conflict')
            hit = 1
    if hit == 0:
        print('Nothing connected')

Probably will need some ‘polishing’ by those more pythonesk types than me …

Still WIP (recovering from a Scout camp - 3500 Cubs for 5 days/4 nights. ZZzzzzZzz z z)

Murray

3 Likes

Hi @Trent5487676 et al,

Test class created and github pull request made.

cheers
Murray

4 Likes

For those following this thread, the proposed functions are

Available functions
-------------------
    clear()					- clears the list of connected i2c devices
    rescan()				- clears and rescans the default i2c bus and repopulates the list
    show()					- prints the list of connected ID's detected by the original/most recent scan
    is_ID_connected(id)		- returns 1 if the ID is in the list, otherwise 0
    how_many_connected()	- returns count of detected ID's
    
    details()				- prints 'human name' of the connected ID's e.g. 'OLED Module'
        details('what')		- prints 'human name' of the connected ID's e.g. 'OLED Module'       
        details('short')	- prints 'short_name' of the connected ID's e.g. 'SSD1306'
        details('long')		- prints 'long_name' of the connected ID's e.g. 'PiicoDev OLED Module SSD1306'
        
    what_is(id)				- prints 'human name' of the  ID e.g. 'RGB LED Module'
        what_is(id, 'what')	- prints 'human name' of the  ID e.g. 'RGB LED Module'
        what_is(id, 'short')- prints 'short_name' of the  ID e.g. 'LED'
        what_is(id, 'long')	- prints 'long_name' of the  ID e.g. 'PiicoDev 3x RGB LED Module'
        
    show_all()				- prints all 'human names' from the main internal dictonary
4 Likes

Update:

renamed show() function to show_int(), and added show_hex() function

    show_int()              - prints the list of connected IDs in DECIMAL detected by the original/most recent scan
    show_hex()              - prints the list of connected IDs in HEXADECIMAL detected by the original/most recent scan
show_int()      # was show()
[16, 60, 82, 83, 119]
show_hex()
['0x10', '0x3c', '0x52', '0x53', '0x77']

Murray

3 Likes

Hi all,

Awesome work and very polished!

@Michael is currently away but I’ve sent through messages so he’ll definitely see this as soon as he’s back :slight_smile:

Unfortunately these addresses are issued by the manufacturer, there isn’t a standard for where to place addresses in the 7-bit pool.

Liam

2 Likes

Hi @Liam et al

Update the init function to allow alternate i2c bus use

    #
    # PiicoDev defaults pre-defined
    # can be overloaded with other values if needed to establish a second/alternate i2c bus
    #
    def __init__(self, id=0, scl=Pin(9), sda=Pin(8), freq=400_000):
        self.i2c = I2C(id=id, scl=scl, sda=sda, freq=freq)
        self.connected = self.i2c.scan()

Use it like this

    tests = PiicoDev_test()    # the standard PiicoDev bus

    # for an alternate i2c bus on GPIO6 and GPOI7
    test_altbus = PiicoDev_test(id=1, scl=Pin(7), sda=Pin(6))

Murray

2 Likes