Trinkey_QT2040_Enviro_Gadget 2

Adafruit's tiny microcontroller platform. Please tell us which board you are using.
For CircuitPython issues, ask in the Adafruit CircuitPython forum.

Moderators: adafruit_support_bill, adafruit

Please be positive and constructive with your questions and comments.
User avatar
dpuerto
 
Posts: 40
Joined: Wed Feb 01, 2023 12:25 am

Re: Trinkey_QT2040_Enviro_Gadget 2

Post by dpuerto »

When I run this command it fails:

Code: Select all

bme280 = adafruit_bme280.Adafruit_BME280_I2C(board.I2C())
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
AttributeError: module 'board' has no attribute 'I2C'


I also tried:

Code: Select all

import time
import busio
import adafruit_scd4x
i2c = busio.I2C(scl=board.SCL_PIN_NAME, sda=board.SDA_PIN_NAME, frequency=400000, u2if=True)
but it also returns:

Traceback (most recent call last):
File "<stdin>", line 1, in <module>
NameError: name 'board' is not defined

I think something is wrong with the adafruit_blinka libraries not recognizing the board.

So when I import board and try to instantiate the i2c I return:

AttributeError: module 'board' has no attribute 'SCL_PIN_NAME'



So I think the issue might be related to the specific setup or compatibility of the libraries with the U2IF firmware... and the adafruit_blinka library


I am going to uninstall everything related to python and start over and maybe something will give.

User avatar
dpuerto
 
Posts: 40
Joined: Wed Feb 01, 2023 12:25 am

Re: Trinkey_QT2040_Enviro_Gadget 2

Post by dpuerto »

nothing is giving and now new error:

Code: Select all

➜  sensors git:(master) ✗ pip show adafruit-blinka hid

Name: Adafruit-Blinka
Version: 8.19.0
Summary: CircuitPython APIs for non-CircuitPython versions of Python such as CPython on Linux and MicroPython.
Home-page: https://github.com/adafruit/Adafruit_Blinka
Author: Adafruit Industries
Author-email: [email protected]
License: MIT
Location: /Users/dpuerto/Library/Python/3.9/lib/python/site-packages
Requires: adafruit-circuitpython-typing, Adafruit-PlatformDetect, Adafruit-PureIO, pyftdi
Required-by: adafruit-circuitpython-busdevice, adafruit-circuitpython-requests, adafruit-circuitpython-typing
---
Name: hid
Version: 1.0.5
Summary: ctypes bindings for hidapi
Home-page: https://github.com/apmorton/pyhidapi
Author: Austin Morton
Author-email: [email protected]
License: MIT
Location: /Users/dpuerto/Library/Python/3.9/lib/python/site-packages
Requires: 
Required-by: 

Code: Select all

>>> import hid
>>> import board
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
  File "/Users/dpuerto/Sites/davidpuerto.com/sensors/board/__init__.py", line 17, in <module>
    pin.GP0._u2if_open_hid(0x239A, 0x0109)
  File "/Users/dpuerto/Sites/davidpuerto.com/sensors/adafruit_blinka/microcontroller/rp2040_u2if/pin.py", line 29, in _u2if_open_hid
    rp2040_u2if.open(vid, pid)
  File "/Users/dpuerto/Sites/davidpuerto.com/sensors/adafruit_blinka/microcontroller/rp2040_u2if/rp2040_u2if.py", line 119, in open
    self._hid = hid.device()
AttributeError: module 'hid' has no attribute 'device'
>>> exit()

User avatar
dpuerto
 
Posts: 40
Joined: Wed Feb 01, 2023 12:25 am

Re: Trinkey_QT2040_Enviro_Gadget 2

Post by dpuerto »

Code: Select all

>>> import usb.core
>>> vid = 0x239a
>>> pid = 0x0109
>>> device = usb.core.find(idVendor=vid, idProduct=pid)
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
  File "/Users/dpuerto/Library/Python/3.9/lib/python/site-packages/usb/core.py", line 1309, in find
    raise NoBackendError('No backend available')
usb.core.NoBackendError: No backend available

Going down the rabbit hole. I broke the build somehow. board no longer is recognized... trying to use usb.core now to connect to the device. I have libusb and pyusb installed...

User avatar
dpuerto
 
Posts: 40
Joined: Wed Feb 01, 2023 12:25 am

Re: Trinkey_QT2040_Enviro_Gadget 2

Post by dpuerto »

Code: Select all

>>> import hid
>>> device = hid.device()
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
AttributeError: module 'hid' has no attribute 'device'

User avatar
dpuerto
 
Posts: 40
Joined: Wed Feb 01, 2023 12:25 am

Re: Trinkey_QT2040_Enviro_Gadget 2

Post by dpuerto »

Code: Select all

Traceback (most recent call last):
  File "/Users/dpuerto/Sites/XXXX.com/sensors/trinkey.py", line 2, in <module>
    import board
[b]  File "/Users/dpuerto/Sites/XXXX.com/sensors/board/__init__.py", line 17, in <module>
    pin.GP0._u2if_open_hid(0x239A, 0x0109)[/b]
  File "/Users/dpuerto/Sites/XXXX.com/sensors/adafruit_blinka/microcontroller/rp2040_u2if/pin.py", line 29, in _u2if_open_hid
    rp2040_u2if.open(vid, pid)
  File "/Users/dpuerto/Sites/XXXX.com/sensors/adafruit_blinka/microcontroller/rp2040_u2if/rp2040_u2if.py", line 119, in open
    self._hid = hid.device()
AttributeError: module 'hid' has no attribute 'device'
Vendor ID = 0x239a
Product ID = 0x0109

I think that the version of the adafruit_blinka/microcontroller/rp2040_u2if/ has something wrong registering the board and the device

User avatar
dpuerto
 
Posts: 40
Joined: Wed Feb 01, 2023 12:25 am

Re: Trinkey_QT2040_Enviro_Gadget 2

Post by dpuerto »

I have input monitoring enabled for Terminal and I've followed everything, removed and deleted everything and reinstalled numerous times and can't seem to get board to load...

User avatar
dpuerto
 
Posts: 40
Joined: Wed Feb 01, 2023 12:25 am

Re: Trinkey_QT2040_Enviro_Gadget 2

Post by dpuerto »

I think I am starting to get somewhere. I think that hid should be looking for Device with a capital D

Code: Select all

>>> hid.Device(vid, pid)
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
  File "/Users/dpuerto/Library/Python/3.9/lib/python/site-packages/hid/__init__.py", line 132, in __init__
    raise HIDException('unable to open device')
hid.HIDException: unable to open device

then upon importing board:

Code: Select all

  File "<stdin>", line 1, in <module>
  File "/Users/dpuerto/Library/Python/3.9/lib/python/site-packages/board.py", line 323, in <module>
    from adafruit_blinka.board.qt2040_trinkey_u2if import *
  File "/Users/dpuerto/Library/Python/3.9/lib/python/site-packages/adafruit_blinka/board/qt2040_trinkey_u2if.py", line 17, in <module>
    pin.GP0._u2if_open_hid(0x239A, 0x0109)
  File "/Users/dpuerto/Library/Python/3.9/lib/python/site-packages/adafruit_blinka/microcontroller/rp2040_u2if/pin.py", line 29, in _u2if_open_hid
    rp2040_u2if.open(vid, pid)
  File "/Users/dpuerto/Library/Python/3.9/lib/python/site-packages/adafruit_blinka/microcontroller/rp2040_u2if/rp2040_u2if.py", line 119, in open
    self._hid = hid.Device()
  File "/Users/dpuerto/Library/Python/3.9/lib/python/site-packages/hid/__init__.py", line 129, in __init__
    raise ValueError('specify vid/pid or path')
ValueError: specify vid/pid or path
>>> vid = 0x239a
>>> pid = 0x0109

Code: Select all

>>> vid = 0x239a
>>> pid = 0x0109
>>> device = hid.Device(vid=vid, pid=pid)
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
  File "/Users/dpuerto/Library/Python/3.9/lib/python/site-packages/hid/__init__.py", line 132, in __init__
    raise HIDException('unable to open device')
hid.HIDException: unable to open device
tried to pass as params but no dice... yet

I also tried this:

Code: Select all

>>> device_path  = '/dev/tty.usbmodem14301'
>>> device = hid.Device(path=device_path)
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
  File "/Users/dpuerto/Library/Python/3.9/lib/python/site-packages/hid/__init__.py", line 122, in __init__
    self.__dev = hidapi.hid_open_path(path)
ctypes.ArgumentError: argument 1: <class 'TypeError'>: wrong type
Thinking that I will need to learn and invoke the Mac OSX Ventura equivalent of this:

1. sudo mkdir -p /etc/udev/rules.d/
echo 'KERNEL=="hidraw*", SUBSYSTEM=="hidraw", MODE="0666", TAG+="uaccess", TAG+="udev-acl"' | sudo tee /etc/udev/rules.d/92-viia.rules
2. sudo udevadm control --reload-rules
3. sudo udevadm trigger

User avatar
adafruit_support_carter
 
Posts: 29161
Joined: Tue Nov 29, 2016 2:45 pm

Re: Trinkey_QT2040_Enviro_Gadget 2

Post by adafruit_support_carter »

Do you have access to a second PC that could be used to try starting over with?

User avatar
dpuerto
 
Posts: 40
Joined: Wed Feb 01, 2023 12:25 am

Re: Trinkey_QT2040_Enviro_Gadget 2

Post by dpuerto »

I completely removed everything from my machine related to python and adafruit and all the libraries that I tried to install along the way (eg. hid, libusb, pyserial, etc). I removed everything from Homebrew and all symlinks. Set my environment variable, ran all the sanity checks.

Everything seems good. I don't know how I messed that up. But thanks.

User avatar
dpuerto
 
Posts: 40
Joined: Wed Feb 01, 2023 12:25 am

Re: Trinkey_QT2040_Enviro_Gadget 2

Post by dpuerto »

Thank you very mucho. As a result I have this:

Code: Select all

import time
import board
import adafruit_scd4x
import json
from datetime import datetime
import shutil
import os

i2c = board.I2C()
scd4x = adafruit_scd4x.SCD4X(i2c)
print("Serial number:", [hex(i) for i in scd4x.serial_number])

scd4x.start_periodic_measurement()
print("Waiting for first measurement....")

# Specify the file path to save the JSON data
file_path = "sensor_data.json"

# Specify the backup directory path
backup_directory = "backup"
max_files_to_keep = 10

def backup_and_delete_old_files():
    # Create the backup directory if it doesn't exist
    if not os.path.exists(backup_directory):
        os.makedirs(backup_directory)

    # Get the current date and time
    current_date = datetime.now().strftime("%Y-%m-%d")

    # Create a backup file name based on the current date and time
    backup_file_name = f"sensor_data_{current_date}.json"

    # Copy the current JSON file to the backup directory
    shutil.copyfile(file_path, os.path.join(backup_directory, backup_file_name))

    # Get a list of all JSON files in the backup directory
    files = os.listdir(backup_directory)
    json_files = [file for file in files if file.endswith(".json")]

    # Sort the JSON files based on the modified timestamp (oldest to newest)
    json_files.sort(key=lambda x: os.path.getmtime(os.path.join(backup_directory, x)))

    # Delete the old files if the number of files exceeds the maximum limit
    if len(json_files) > max_files_to_keep:
        files_to_delete = json_files[:len(json_files) - max_files_to_keep]
        for file in files_to_delete:
            os.remove(os.path.join(backup_directory, file))

# Load existing JSON data from the file, if available
existing_data = {}
try:
    with open(file_path, "r") as file:
        existing_data = json.load(file)
except FileNotFoundError:
    pass

while True:
    if scd4x.data_ready:
        # Read the sensor data once it is ready
        temperature = scd4x.temperature
        humidity = scd4x.relative_humidity
        co2 = scd4x.CO2

        # Get the current date and time
        current_time = datetime.now().isoformat()

        # Create a Python dictionary representing the sensor data with date and time
        sensor_data = {
            "datetime": current_time,
            "temperature": temperature,
            "humidity": humidity,
            "co2": co2
        }

        # Update the existing data dictionary with the new sensor data
        existing_data[current_time] = sensor_data

        # Save the updated JSON data to the file
        with open(file_path, "w") as file:
            json.dump(existing_data, file)

        # Perform backup and delete old files
        backup_and_delete_old_files()

    time.sleep(1)

and will begin sending it up to an Azure App Service with CosmosDB running to inform my client-side web application on how much mushrooms are doing. I am hoping to get a system going where I can have 8-10 USB trinkey's with SCD41's informing the application and in a few weeks, from my phone, be able to log into my website and view the 4K HD of each container in real time, and check the data and determine the best thresholds for each stage of the mushroom lifecycle so that I can eventually tailor the experiences, and receive alerts in case something happens while I'm out of the country and we need a neighbor to check on the water line or heat or something... electricity is the only thing I haven't figured out yet... but this is fun and my first attempt at IoT and python! Thanks for baring with me. I'm going to have an admin console that can set temp, humidity, co2 and control them with manual input as necessary, Exciting times for the hobby mycologist!

https://codepen.io/dapinitial/live/KKGL ... 88be8dabb7


O
M
G

I just thought of something... What happens when the computer goes to sleep?

Instead of using a raspberryPi or my computer going to sleep I am thinking about putting this python script to run in a VM in azure cloud and then connecting to my home network and then plugging the trinkey into my router adn using these neat stema cables to extend into my mushroom containers. Thoughts, feelings, opinions? Will this even work? I want it running in the background, forever, and uninterrupted

User avatar
adafruit_support_carter
 
Posts: 29161
Joined: Tue Nov 29, 2016 2:45 pm

Re: Trinkey_QT2040_Enviro_Gadget 2

Post by adafruit_support_carter »

Good job getting the Python environment fixed up. That can sometimes be a real challenge:
https://xkcd.com/1987/

I'm not sure how plugging a Trinkey into a router would end up working. The router would need to run something equivalent to the python code above. The Trinkey with U2IF option is a good option for bringing the data directly into the same PC that is hosting the website, for example. And that PC shouldn't sleep, since it needs to constantly host a web server.

And if using an RPI, a Trinkey is not needed at all. The RPI has I2C directly exposed on the GPIO header. The Trinkey/U2IF is for use on "regular" PC's that generally do not expose this kind of hardware interface.

For using multiple SCD41's, you don't need a Trinkey for each. I2C allows for multiple items to share the same I2C bus. So they can be chained. Each device does need a unique I2C address. Or can use a muxer.
https://learn.adafruit.com/working-with ... s/overview

In general, there are lot's of options and approaches for how to put together something like this. There's no one right answer.

User avatar
dpuerto
 
Posts: 40
Joined: Wed Feb 01, 2023 12:25 am

Re: Trinkey_QT2040_Enviro_Gadget 2

Post by dpuerto »

That's how it started, but I got it sorted with your gentle coaxing. It's always like this with a new language/framework/toolset. Remember the first time you got a BANNED or Audi or Porsche and decide to do the full timing job on any of them? Chain? Belt? Supposed special tools? etc etc etc etc secret ways (yet clever) to open and release things...

This is insane. I stayed up all night like Neo in the Matrix. I can't tell if I'm awake or still dreaming. Here is my updated code handling retries, errors, a little more modular, backing up records, and it's all going into cosmos as expected.

I am thinking I am going to host a VM and see if it can connect to my router USB and get the trinkey off it... but I think a PicoW woould serve the purpose. I am not sure how to connect it to the SCD41 with those headers... maybe I'll need to pick up soldering but in the meantime between time, thank you again!

I am going to start working on getting this data over to my website tonight after some run swim run.

Incredible! I feel... powerful now.

Code: Select all


import time
import board
import adafruit_scd4x
import json
from datetime import datetime
import shutil
import os
import uuid
from azure.cosmos import CosmosClient
from azure.cosmos.exceptions import CosmosResourceExistsError
from config import COSMOSDB_ENDPOINT, COSMOSDB_KEY, COSMOSDB_DATABASE_NAME, COSMOSDB_CONTAINER_NAME

# Generate a unique ID
unique_id = str(uuid.uuid4())

# Initialize CosmosDB client
client = CosmosClient(COSMOSDB_ENDPOINT, COSMOSDB_KEY)
database_name = COSMOSDB_DATABASE_NAME
container_name = COSMOSDB_CONTAINER_NAME
container = client.get_database_client(database_name).get_container_client(container_name)

i2c = board.I2C()
scd4x = adafruit_scd4x.SCD4X(i2c)
print("Serial number:", [hex(i) for i in scd4x.serial_number])

scd4x.start_periodic_measurement()
print("Waiting for first measurement....")

# Specify the file path to save the JSON data
file_path = "sensor_data.json"

# Specify the backup directory path
backup_directory = "backup"
max_files_to_keep = 10

def backup_and_delete_old_files():
    # Create the backup directory if it doesn't exist
    if not os.path.exists(backup_directory):
        os.makedirs(backup_directory)

    # Get the current date and time
    current_date = datetime.now().strftime("%Y-%m-%d")

    # Create a backup file name based on the current date and time
    backup_file_name = f"sensor_data_{current_date}.json"

    # Copy the current JSON file to the backup directory
    shutil.copyfile(file_path, os.path.join(backup_directory, backup_file_name))

    # Get a list of all JSON files in the backup directory
    files = os.listdir(backup_directory)
    json_files = [file for file in files if file.endswith(".json")]

    # Sort the JSON files based on the modified timestamp (oldest to newest)
    json_files.sort(key=lambda x: os.path.getmtime(os.path.join(backup_directory, x)))

    # Delete the old files if the number of files exceeds the maximum limit
    if len(json_files) > max_files_to_keep:
        files_to_delete = json_files[:len(json_files) - max_files_to_keep]
        for file in files_to_delete:
            os.remove(os.path.join(backup_directory, file))

def transform_data(data):
    transformed_data = {
        "id": unique_id,
        "temperature": data["temperature"],
        "humidity": data["humidity"],
        "co2": data["co2"],
        data["datetime"]: {
            "datetime": data["datetime"]
        }
    }
    return transformed_data

while True:
    # Generate a unique ID for each iteration
    unique_id = str(uuid.uuid4())

    if scd4x.data_ready:
        # Read the sensor data once it is ready
        temperature = scd4x.temperature
        humidity = scd4x.relative_humidity
        co2 = scd4x.CO2

        # Get the current date and time
        current_time = datetime.now().isoformat()

        # Create a Python dictionary representing the sensor data with date and time
        sensor_data = {
            "datetime": current_time,
            "temperature": temperature,
            "humidity": humidity,
            "co2": co2
        }

        print("New sensor data:", sensor_data)

        # Load existing JSON data from the file, if available
        existing_data = {}
        try:
            with open(file_path, "r") as file:
                existing_data = json.load(file)
        except FileNotFoundError:
            pass

        # Update the existing data dictionary with the new sensor data
        existing_data[current_time] = sensor_data

        # Save the updated JSON data to the file
        with open(file_path, "w") as file:
            json.dump(existing_data, file)

        # Check if the item already exists in Cosmos DB
        query = f"SELECT * FROM c WHERE c.id = '{unique_id}' AND c.datetime = '{current_time}'"
        result = container.query_items(query, enable_cross_partition_query=True)

        if not list(result):
            try:
                transformed_data = transform_data(sensor_data)
                container.upsert_item(transformed_data)
            except CosmosResourceExistsError:
                print("Duplicate item detected. Skipping upsert.")

            backup_and_delete_old_files()
        else:
            print("Duplicate item detected. Skipping upsert.")

    time.sleep(30)
    
    

Locked
Please be positive and constructive with your questions and comments.

Return to “Trinket ATTiny, Trinket M0”