NOTICE: This forum has been archived (i.e. registration and posting is disabled).

Please go to the new forum at https://forum.kylegabriel.com

Having trouble with...
 
Notifications
Clear all

[Solved] Having trouble with CircuitPython custom input for SCD30


dookaloosy
(@dookaloosy)
Active Member
Joined: 9 months ago
Posts: 9
Topic starter  

I'm trying to create a custom input module for the Sensirion SCD30 CO2, which can be purchased here or here. I am using this sensor in I2C configuration.

 

When I create a custom input for SCD30 ("SCD30_CP") using adafruit-circuitpython-scd30, I run into a Remote I/O error when the input is activated:

2020-12-23 1118,263 - ERROR - mycodo.controllers.controller_input_17dec291 - initialize_variables() Exception: [Errno 121] Remote I/O error
Traceback (most recent call last):
  File "/var/mycodo-root/mycodo/controllers/base_controller.py", line 72, in run
    self.initialize_variables()
  File "/var/mycodo-root/mycodo/controllers/controller_input.py", line 338, in initialize_variables
    self.measure_input = input_loaded.InputModule(self.input_dev)
  File "/home/pi/Mycodo/mycodo/inputs/custom_inputs/scd30_cp.py", line 73, in __init__
    self.initialize_input()
  File "/home/pi/Mycodo/mycodo/inputs/custom_inputs/scd30_cp.py", line 81, in initialize_input
    address=int(str(self.input_dev.i2c_location), 16))
  File "/var/mycodo-root/env/lib/python3.7/site-packages/adafruit_scd30.py", line 64, in __init__
    self.reset()
  File "/var/mycodo-root/env/lib/python3.7/site-packages/adafruit_scd30.py", line 78, in reset
    self._send_command(_CMD_SOFT_RESET)
  File "/var/mycodo-root/env/lib/python3.7/site-packages/adafruit_scd30.py", line 220, in _send_command
    i2c.write(self._buffer, end=end_byte)
  File "/var/mycodo-root/env/lib/python3.7/site-packages/adafruit_bus_device/i2c_device.py", line 102, in write
    self.i2c.writeto(self.device_address, buf, start=start, end=end)
  File "/var/mycodo-root/env/lib/python3.7/site-packages/busio.py", line 115, in writeto
    return self._i2c.writeto(address, memoryview(buffer)[start:end], stop=stop)
  File "/var/mycodo-root/env/lib/python3.7/site-packages/adafruit_blinka/microcontroller/generic_linux/i2c.py", line 49, in writeto
    self._i2c_bus.write_bytes(address, buffer[start:end])
  File "/var/mycodo-root/env/lib/python3.7/site-packages/Adafruit_PureIO/smbus.py", line 308, in write_bytes
    self._device.write(buf)
OSError: [Errno 121] Remote I/O error
2020-12-23 1118,278 - INFO - mycodo.controllers.controller_input_17dec291 - Activated in 363.6 ms
2020-12-23 1118,278 - ERROR - mycodo.controllers.controller_input_17dec291 - Error while attempting to read input: 'NoneType' object has no attribute 'next'
Traceback (most recent call last):
  File "/var/mycodo-root/mycodo/controllers/controller_input.py", line 391, in update_measure
    measurements = self.measure_input.next()
AttributeError: 'NoneType' object has no attribute 'next'

This causes the custom input to fail initialization upon activation and so no readings are obtained.

 

However, if I create a custom input ("SCD30") using scd30-i2c, initialization occurs with no issue and readings are obtained once the input is activated.

 

The most curious effect is that the "SCD30_CP" custom input can be "tricked" to pass initialization by first activating+deactivating the "SCD30" custom input, then activating the "SCD30_CP" custom input. The CircuitPython-based driver then works for as long as the system isn't restarted. What am I doing wrong in "SCD30_CP"? See custom input scripts below.

 

 

scd30.py

# coding=utf-8
import copy
from flask_babel import lazy_gettext

from mycodo.inputs.base_input import AbstractInput

# Measurements
measurements_dict = {
0: {
'measurement': 'temperature', # Note: This is the "Measurement ID" on the measurements configuration page
'unit': 'C' # Note: This is the "Unit ID" on the measurements configuration page
},
1: {
'measurement': 'humidity',
'unit': 'percent'
},
2: {
'measurement': 'co2',
'unit': 'ppm'
}
}

# Input information
# See the inputs directory for examples of working modules.
# The following link provides the full list of options with descriptions:
# https://github.com/kizniche/Mycodo/blob/single_file_input_modules/mycodo/inputs/examples/example_all_options_temperature.py
INPUT_INFORMATION = {
'input_name_unique': 'SCD30',
'input_manufacturer': 'Sensirion',
'input_name': 'SCD30',
'input_library': 'scd30_i2c',
'measurements_name': 'CO2',
'measurements_dict': measurements_dict,
'measurements_use_same_timestamp': True,

'url_manufacturer': 'https://www.sensirion.com/en/environmental-sensors/carbon-dioxide-sensors/carbon-dioxide-sensors-co2/',
'url_datasheet': 'https://www.sensirion.com/fileadmin/user_upload/customers/sensirion/Dokumente/9.5_CO2/Sensirion_CO2_Sensors_SCD30_Datasheet.pdf',
'url_product_purchase': [
'https://www.sparkfun.com/products/15112',
'https://www.futureelectronics.com/p/4115766'
],

'options_enabled': [
'i2c_location',
'period',
'pre_output'
],
'options_disabled': [
'interface',
],

'dependencies_module': [
('pip-pypi', 'scd30_i2c', 'scd30_i2c')
],

'interfaces': ['I2C'],
'i2c_location': ['0x61'],
'i2c_address_editable': False
}

class InputModule(AbstractInput):
""" Input support class """
def __init__(self, input_dev, testing=False):
super(InputModule, self).__init__(input_dev, testing=testing, name=__name__)

# Initialize variables
self.scd = None

if not testing:
self.initialize_input()

def initialize_input(self):
# Load dependent modules
from scd30_i2c import SCD30

self.scd = SCD30()
self.scd.set_measurement_interval(2)
self.scd.start_periodic_measurement()

def get_measurement(self):
""" Measures CO2, temperature and humidity """
# Resetting these values ensures old measurements aren't mistaken for new measurements
self.return_dict = copy.deepcopy(measurements_dict)

# Actual input measurement code
if (self.scd.get_data_ready()):
m = self.scd.read_measurement()
if m is not None:
# Get measurements
humidity = m[2]
temperature = m[1]
co2 = m[0]

# Store measurements
self.value_set(0, temperature)
self.value_set(1, humidity)
self.value_set(2, co2)

return self.return_dict

 

 

 

scd30_circuitpython.py

# coding=utf-8
import copy
from flask_babel import lazy_gettext

from mycodo.inputs.base_input import AbstractInput

# Measurements
measurements_dict = {
0: {
'measurement': 'temperature', # Note: This is the "Measurement ID" on the measurements configuration page
'unit': 'C' # Note: This is the "Unit ID" on the measurements configuration page
},
1: {
'measurement': 'humidity',
'unit': 'percent'
},
2: {
'measurement': 'co2',
'unit': 'ppm'
}
}

# Input information
# See the inputs directory for examples of working modules.
# The following link provides the full list of options with descriptions:
# https://github.com/kizniche/Mycodo/blob/single_file_input_modules/mycodo/inputs/examples/example_all_options_temperature.py
INPUT_INFORMATION = {
'input_name_unique': 'SCD30_CP',
'input_manufacturer': 'Sensirion',
'input_name': 'SCD30',
'input_library': 'Adafruit_CircuitPython',
'measurements_name': 'CO2',
'measurements_dict': measurements_dict,
'measurements_use_same_timestamp': True,

'url_manufacturer': 'https://www.sensirion.com/en/environmental-sensors/carbon-dioxide-sensors/carbon-dioxide-sensors-co2/',
'url_datasheet': 'https://www.sensirion.com/fileadmin/user_upload/customers/sensirion/Dokumente/9.5_CO2/Sensirion_CO2_Sensors_SCD30_Datasheet.pdf',
'url_product_purchase': [
'https://www.sparkfun.com/products/15112',
'https://www.futureelectronics.com/p/4115766'
],

'options_enabled': [
'i2c_location',
'period',
'pre_output'
],
'options_disabled': [
'interface',
],

'dependencies_module': [
('pip-pypi', 'usb.core', 'pyusb==1.0.2'),
('pip-pypi', 'adafruit_extended_bus', 'Adafruit_Extended_Bus'),
('pip-pypi', 'adafruit_scd30', 'adafruit-circuitPython-scd30')
],

'interfaces': ['I2C'],
'i2c_location': ['0x61'],
'i2c_address_editable': False
}

class InputModule(AbstractInput):
""" Input support class """
def __init__(self, input_dev, testing=False):
super(InputModule, self).__init__(input_dev, testing=testing, name=__name__)

# Initialize variables
self.scd = None

if not testing:
self.initialize_input()

def initialize_input(self):
# Load dependent modules
from adafruit_extended_bus import ExtendedI2C
import adafruit_scd30
self.scd = adafruit_scd30.SCD30(
ExtendedI2C(self.input_dev.i2c_bus),
address=int(str(self.input_dev.i2c_location), 16))

def get_measurement(self):
""" Measures CO2, temperature and humidity """
# Resetting these values ensures old measurements aren't mistaken for new measurements
self.return_dict = copy.deepcopy(measurements_dict)

# Actual input measurement code
if (self.scd.data_available):
# Get measurements
humidity = self.scd.relative_humidity
temperature = self.scd.temperature
co2 = self.scd.eCO2

# Store measurements
self.value_set(0, temperature)
self.value_set(1, humidity)
self.value_set(2, co2)

return self.return_dict


Quote
dookaloosy
(@dookaloosy)
Active Member
Joined: 9 months ago
Posts: 9
Topic starter  

I tried again and again, and suddenly both versions work -- unfortunately I don't know what I did to fix it. The CircuitPython version does seem to take a little longer to start reporting the first measurement, but otherwise performs identically. I did some cleanup of the scripts, the "final" versions are attached, if this is useful to anyone. (I can't seem to attach a file, sorry.)

 

 

scd30.py

# coding=utf-8
import copy
from flask_babel import lazy_gettext

from mycodo.inputs.base_input import AbstractInput

# Measurements
measurements_dict = {
0: {
'measurement': 'co2',
'unit': 'ppm'
}
}

# Input information
# See the inputs directory for examples of working modules.
# The following link provides the full list of options with descriptions:
# https://github.com/kizniche/Mycodo/blob/single_file_input_modules/mycodo/inputs/examples/example_all_options_temperature.py
INPUT_INFORMATION = {
'input_name_unique': 'SCD30',
'input_manufacturer': 'Sensirion',
'input_name': 'SCD30',
'input_library': 'scd30_i2c',
'measurements_name': 'CO2',
'measurements_dict': measurements_dict,
'measurements_use_same_timestamp': True,

'url_manufacturer': 'https://www.sensirion.com/en/environmental-sensors/carbon-dioxide-sensors/carbon-dioxide-sensors-co2/',
'url_datasheet': 'https://www.sensirion.com/fileadmin/user_upload/customers/sensirion/Dokumente/9.5_CO2/Sensirion_CO2_Sensors_SCD30_Datasheet.pdf',
'url_product_purchase': [
'https://www.sparkfun.com/products/15112',
'https://www.futureelectronics.com/p/4115766'
],

'options_enabled': [
'i2c_location',
'period',
'pre_output'
],
'options_disabled': [
'interface',
],

'dependencies_module': [
('pip-pypi', 'scd30_i2c', 'scd30_i2c')
],

'interfaces': ['I2C'],
'i2c_location': ['0x61'],
'i2c_address_editable': False
}

class InputModule(AbstractInput):
""" Input support class """
def __init__(self, input_dev, testing=False):
super(InputModule, self).__init__(input_dev, testing=testing, name=__name__)

# Initialize variables
self.scd = None

if not testing:
self.initialize_input()

def initialize_input(self):
# Load dependent modules
from scd30_i2c import SCD30

self.scd = SCD30()

def get_measurement(self):
""" Measures CO2, temperature and humidity """
# Resetting these values ensures old measurements aren't mistaken for new measurements
self.return_dict = copy.deepcopy(measurements_dict)

# Actual input measurement code
if (self.scd.get_data_ready()):
m = self.scd.read_measurement()
if m is not None:
# Get measurements
co2 = m[0]
temperature = m[1]
humidity = m[2]

# Store measurements
self.value_set(0, co2)

return self.return_dict

 

 

 

scd30_circuitpython.py

# coding=utf-8
import copy
from flask_babel import lazy_gettext

from mycodo.inputs.base_input import AbstractInput

# Measurements
measurements_dict = {
0: {
'measurement': 'co2',
'unit': 'ppm'
}
}

# Input information
# See the inputs directory for examples of working modules.
# The following link provides the full list of options with descriptions:
# https://github.com/kizniche/Mycodo/blob/single_file_input_modules/mycodo/inputs/examples/example_all_options_temperature.py
INPUT_INFORMATION = {
'input_name_unique': 'SCD30_CP',
'input_manufacturer': 'Sensirion',
'input_name': 'SCD30',
'input_library': 'Adafruit_CircuitPython',
'measurements_name': 'CO2',
'measurements_dict': measurements_dict,
'measurements_use_same_timestamp': True,

'url_manufacturer': 'https://www.sensirion.com/en/environmental-sensors/carbon-dioxide-sensors/carbon-dioxide-sensors-co2/',
'url_datasheet': 'https://www.sensirion.com/fileadmin/user_upload/customers/sensirion/Dokumente/9.5_CO2/Sensirion_CO2_Sensors_SCD30_Datasheet.pdf',
'url_product_purchase': [
'https://www.sparkfun.com/products/15112',
'https://www.futureelectronics.com/p/4115766'
],

'options_enabled': [
'i2c_location',
'period',
'pre_output'
],
'options_disabled': [
'interface',
],

'dependencies_module': [
('pip-pypi', 'usb.core', 'pyusb==1.0.2'),
('pip-pypi', 'adafruit_extended_bus', 'Adafruit_Extended_Bus'),
('pip-pypi', 'adafruit_scd30', 'adafruit-circuitPython-scd30')
],

'interfaces': ['I2C'],
'i2c_location': ['0x61'],
'i2c_address_editable': False
}

class InputModule(AbstractInput):
""" Input support class """
def __init__(self, input_dev, testing=False):
super(InputModule, self).__init__(input_dev, testing=testing, name=__name__)

# Initialize variables
self.scd = None

if not testing:
self.initialize_input()

def initialize_input(self):
# Load dependent modules
from adafruit_extended_bus import ExtendedI2C
import adafruit_scd30
self.scd = adafruit_scd30.SCD30(
ExtendedI2C(self.input_dev.i2c_bus),
address=int(str(self.input_dev.i2c_location), 16))

def get_measurement(self):
""" Measures CO2, temperature and humidity """
# Resetting these values ensures old measurements aren't mistaken for new measurements
self.return_dict = copy.deepcopy(measurements_dict)

# Actual input measurement code
if (self.scd.data_available):
# Get measurements
co2 = self.scd.eCO2
temperature = self.scd.temperature
humidity = self.scd.relative_humidity

# Store measurements
self.value_set(0, co2)

return self.return_dict


ReplyQuote
Kyle Gabriel
(@kylegabriel)
Member Admin
Joined: 7 years ago
Posts: 612
 

Thanks! I just added them to the built-in set. I also added Temperature/Humidity/Dew Point/Vapor Pressure Deficit measurements (commit b4b2247).

Mycodo Developer


ReplyQuote
granite
(@granite)
New Member
Joined: 8 months ago
Posts: 2
 

Hi

I tried this to get the SCD30 to work but with very limited success, once I did read it correctly but then it dropped out again on a reboot.

I switched to master branch as the input is not yet available in the normal release, The rpi seem to recognize that there is something there but doesn't read any data.

result of "sudo i2cdetect -y 1":

link

0x48 and 0x49 is my anyleaf PH and ORP sensors, both are working.

input settings:

Link

No data:

link

 

I have tried with 2 different sensors and with both input versions (SCD30_I2C and Adafruit-CircuitPython-SCD30),  and they both show the same behaviour.

I'm not a very advanced user at all so this is where my knowledge ends, can anyone please help me get this working?

 

Thanks in advance


ReplyQuote
Kyle Gabriel
(@kylegabriel)
Member Admin
Joined: 7 years ago
Posts: 612
 

Can you please look in the daemon log and include any lines from the input following it being activated, including the input's activation log line? 

Mycodo Developer


ReplyQuote
granite
(@granite)
New Member
Joined: 8 months ago
Posts: 2
 

@kylegabriel

Thank you for your reply.
After some more testing I managed to get it working if I add and activate both versions (SCD30_I2C and Adafruit-CircuitPython-SCD30) at the same time.

If you want the log data for development purposes I can get it for you, but otherwise my issue seems solved. 


ReplyQuote
dookaloosy
(@dookaloosy)
Active Member
Joined: 9 months ago
Posts: 9
Topic starter  

I don't have any specific data for the following, but I've read that the SCD30 has some uncommon clock stretching requirements. Would that be a factor here?


ReplyQuote
aka
 aka
(@aka)
Active Member
Joined: 1 year ago
Posts: 18
 

Hi, I came across this thread while setting up my own scd30.

 

I think one issue (there may be more!) is that the CircuitPython SCD30 library upstream changed a variable name - for me, changing line 95 on the mycodo input script to use "CO2" and not "eCO2" caused the sensor to start working... I wrote up the details here: https://github.com/kizniche/Mycodo/issues/963

 

I hope this helps,

 

AKA


ReplyQuote
dookaloosy
(@dookaloosy)
Active Member
Joined: 9 months ago
Posts: 9
Topic starter  

A note to users who have installed this custom input previously -- it would be advisable to transition over to the natively-supported SCD30 inputs in >8.9.x. I held on to my custom inputs through the upgrade from 8.8.8->8.9.2 and I believe this caused Mycodo to fail to update some dependencies due to a name conflict or version conflict caused by the presence of the custom input. Resolution was straightforward once the issue was identified, and I am now running the native SCD30 input happily in 8.9.2. I recommend deleting the custom input entirely before upgrading to 8.9.x for the smoothest upgrade experience.


ReplyQuote
Kyle Gabriel
(@kylegabriel)
Member Admin
Joined: 7 years ago
Posts: 612
 

I need to add a section in the manual that discusses the Python environment and how to do some common diagnostic things (such as delete ~/Mycodo/env then regenerate it and install dependencies). The Python env isn't really discussed at all in the docs.

Mycodo Developer


ReplyQuote