Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 1 addition & 2 deletions examples/change_i2c_address.py
Original file line number Diff line number Diff line change
Expand Up @@ -17,10 +17,9 @@ def check_connection(device):
check_connection(device)

print(f"🔧 Changing device address to 0x{CUSTOM_I2C_ADDRESS:x}...")
device.device_address = CUSTOM_I2C_ADDRESS
device.set_device_address(CUSTOM_I2C_ADDRESS, persist=True)
check_connection(device)

device.store_settings_in_flash()
print("🔄 Resetting device to check if change is persistent...")
device.reset()
sleep(2)
Expand Down
13 changes: 11 additions & 2 deletions examples/data_logging.py
Original file line number Diff line number Diff line change
Expand Up @@ -36,6 +36,9 @@ def connect_wifi(ssid, password):
print('Network config:', sta_if.ifconfig())

def current_time():
"""
Returns the current time from the RTC as a formatted string.
"""
now = gmtime()
return f"{now[2]}.{now[1]}.{now[0]} {now[3]:02d}:{now[4]:02d}:{now[5]:02d}"

Expand All @@ -46,7 +49,10 @@ def blink_led(led, times=1, delay=0.2):
led.value(1)
sleep(delay)

def read_data(filename):
def read_data(filename) -> str | None:
"""
Reads the data from the specified file and returns it as a string.
"""
try:
with open(filename, 'r', encoding='utf-8') as file:
return file.read()
Expand All @@ -57,7 +63,10 @@ def read_data(filename):
print(f"Error reading file: {filename}. Error: {e}")
return None

def log_data(filename, data):
def log_data(filename:str, data: str):
"""
Appends the data to the specified file.
"""
try:
with open(filename, 'a', encoding='utf-8') as file:
file.write(data + '\n')
Expand Down
1 change: 1 addition & 0 deletions examples/indoor_air_quality.py
Original file line number Diff line number Diff line change
Expand Up @@ -38,6 +38,7 @@ def display_sensor_data(sensor):

# Set indoor air quality sensor mode to sulfur odor (default is indoor air quality)
# After switching modes you may need to wait some time before the sensor delivers the first data.
# Use set_mode(IndoorAirQualitySensorMode.SULFUR, persist=True) to make the change persistent.
# indoor_air_quality_sensor.mode = IndoorAirQualitySensorMode.SULFUR
# display_sensor_data(indoor_air_quality_sensor)

Expand Down
6 changes: 2 additions & 4 deletions examples/outdoor_air_quality.py
Original file line number Diff line number Diff line change
Expand Up @@ -24,11 +24,9 @@ def display_sensor_data(sensor):

# Enable outdoor air quality sensor (disabled by default)
# Please note that it may take some time for the sensor to deliver the first data
outdoor_air_quality_sensor.mode = OutdoorAirQualitySensorMode.OUTDOOR_AIR_QUALITY
# Use set_enabled(True, persist=True) make the change persistent
outdoor_air_quality_sensor.enabled = True
display_sensor_data(outdoor_air_quality_sensor)

# Optionally disable the sensor
# outdoor_air_quality_sensor.enabled = False

else:
print("🤷 Device could not be found. Please double check the wiring.")
4 changes: 2 additions & 2 deletions examples/uart_read.py
Original file line number Diff line number Diff line change
Expand Up @@ -7,8 +7,8 @@
You can do so by connecting to the board over I2C to a host board, and running the following code:

device = NiclaSenseEnv()
device.uart_csv_output_enabled = True
device.store_settings_in_flash() # Store the settings so they are not lost after a reset
# Set `persist` to True so the settings are not lost after a reset
device.set_uart_csv_output_enabled(True, persist=True)
"""

from machine import Pin, UART
Expand Down
1 change: 1 addition & 0 deletions src/arduino_nicla_sense_env/constants.py
Original file line number Diff line number Diff line change
Expand Up @@ -37,6 +37,7 @@
"zmod4410_temp": {"address": 0xC8, "type": "float", "bytes": 4}, # ZMOD4410 Temp (temperature in degC used during ambient compensation)
"zmod4410_intensity": {"address": 0xCC, "type": "float", "bytes": 4}, # ZMOD4410 Intensity (odor intensity)
"zmod4410_odor_class": {"address": 0xD0, "type": "uint8", "bytes": 1}, # ZMOD4410 Odor class (1 = sulfur odor, 0 = others)
"defaults_register" : {"address": 0xD4, "type": "uint8", "bytes": 1} # Persist settings
}

DEVICE_I2C_INTERFACES = {
Expand Down
30 changes: 29 additions & 1 deletion src/arduino_nicla_sense_env/i2c_device.py
Original file line number Diff line number Diff line change
@@ -1,7 +1,8 @@
from machine import I2C
from time import sleep_us
import os
import struct
from .constants import DEVICE_I2C_INTERFACES
from .constants import DEVICE_I2C_INTERFACES, REGISTERS

class I2CHelper:
"""
Expand Down Expand Up @@ -145,6 +146,33 @@ def _read_from_register(self, register: dict):

return raw_data

def _persist_register(self, register: dict) -> bool:
"""
Persists the value of the given register address to the flash memory.

Parameters
----
register (dict):
The register to persist.

Returns
----
bool: True if the write was successful, False otherwise.
"""
self._write_to_register(REGISTERS["defaults_register"], register["address"] | (1 << 7))

# Read bit 7 to check if the write is complete. When the write is complete, bit 7 will be 0.
# Try 10 times with increasing delay between each try
for i in range(10):
defaults_register_data = self._read_from_register(REGISTERS["defaults_register"])
if not defaults_register_data & (1 << 7):
return True
print("⌛️ Waiting for flash write to complete...")
# Exponential sleep duration
sleep_us(100 * (2 ** i))

return False

@property
def device_address(self) -> int | None:
"""
Expand Down
39 changes: 37 additions & 2 deletions src/arduino_nicla_sense_env/indoor_air_quality_sensor.py
Original file line number Diff line number Diff line change
Expand Up @@ -153,7 +153,7 @@ def mode(self) -> int:
def mode(self, sensor_mode: int):
"""
Sets the indoor air quality sensor mode.
Call store_settings_in_flash() on NiclaSenseEnv instance after changing the indoor air quality sensor mode to make the change persistent.
Use `set_mode` with `persist` set to True to make the change persistent.

Note on cleaning mode:
The cleaning mode performs a thermal cleaning cycle of the MOx element. It can eliminate some light pollution
Expand Down Expand Up @@ -188,6 +188,25 @@ def mode(self, sensor_mode: int):
# Overwrite bits 1 - 3 with the new value by clearing the bits and then setting them
self._write_to_register(REGISTERS["status"], (current_register_data & 0b11110001) | (sensor_mode << 1))

def set_mode(self, sensor_mode: int, persist = False) -> bool:
"""
Sets the indoor air quality sensor mode and persists the setting to flash memory.

Parameters
----
sensor_mode (int):
The indoor air quality sensor mode.
Possible values are: POWER_DOWN, CLEANING, INDOOR_AIR_QUALITY, INDOOR_AIR_QUALITY_LOW_POWER, SULFUR.
These values are contained in IndoorAirQualitySensorMode.
persist (bool):
Whether to persist the setting to flash memory.
When persist is True, the mode setting of OutdoorAirQualitySensor and TemperatureHumiditySensor will also be persisted.
"""
self.mode = sensor_mode
if persist:
return self._persist_register(REGISTERS["status"])
return True

@property
def mode_string(self) -> str | None:
"""
Expand Down Expand Up @@ -221,7 +240,7 @@ def enabled(self) -> bool:
def enabled(self, is_enabled: bool):
"""
Enables or disables the indoor air quality sensor.
Call store_settings_in_flash() on NiclaSenseEnv instance after enabling/disabling the indoor air quality sensor to make the change persistent.
Use `set_enabled` with `persist` set to True to make the change persistent.
When the sensor is enabled after being disabled, the sensor will go back to the default mode.

Parameters
Expand All @@ -237,3 +256,19 @@ def enabled(self, is_enabled: bool):
else:
self.mode = IndoorAirQualitySensorMode.POWER_DOWN

def set_enabled(self, is_enabled: bool, persist = False) -> bool:
"""
Enables or disables the indoor air quality sensor and persists the setting to flash memory.

Parameters
----
is_enabled (bool):
Whether to enable or disable the indoor air quality sensor.
persist (bool):
Whether to persist the setting to flash memory.
When persist is True, the mode setting of OutdoorAirQualitySensor and TemperatureHumiditySensor will also be persisted.
"""
self.enabled = is_enabled
if persist:
return self._persist_register(REGISTERS["status"])
return True
95 changes: 84 additions & 11 deletions src/arduino_nicla_sense_env/nicla_sense_env.py
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,7 @@ def __init__(self, bus=None, device_address=I2CDevice.DEFAULT_DEVICE_ADDRESS):
self._indoor_air_quality_sensor = None
self._outdoor_air_quality_sensor = None

def store_settings_in_flash(self):
def persist_settings(self):
"""
Writes the current configuration to the flash memory.
Stores board register 0x00 ... 0x0B in flash to be default after reset
Expand Down Expand Up @@ -164,7 +164,7 @@ def deep_sleep(self):
def restore_factory_settings(self):
"""
Restores the factory settings. This will reset among other properties the device address to the default value.
See store_settings_in_flash() for a complete list of properties that are affected by this method.
See persist_settings() for a complete list of properties that are affected by this method.
"""

board_control_register_data = self._read_from_register(REGISTERS["control"])
Expand All @@ -179,7 +179,7 @@ def restore_factory_settings(self):
board_control_register_data = self._read_from_register(REGISTERS["control"])

if board_control_register_data != None and (not board_control_register_data & (1 << 5)):
return self.store_settings_in_flash()
return self.persist_settings()
print("⌛️ Waiting for factory reset to complete...")
# Exponential sleep duration
sleep_us(100 * (2 ** i))
Expand Down Expand Up @@ -213,7 +213,7 @@ def uart_baud_rate(self):
def uart_baud_rate(self, baud_rate):
"""
Set the baud rate of the UART interface.
Call store_settings_in_flash() on NiclaSenseEnv instance after changing the baud rate to make the change persistent.
Use `set_uart_baud_rate` with `persist` set to True to make the change persistent.

Parameters
----
Expand Down Expand Up @@ -245,7 +245,13 @@ def uart_baud_rate(self, baud_rate):
return
# Set bit 0 - 2 to the new baud rate
self._write_to_register(REGISTERS["uart_control"], (uart_control_register_data & 0b11111000) | baud_rate)


def set_uart_baud_rate(self, baud_rate, persist=False) -> bool:
self.uart_baud_rate = baud_rate
if persist:
return self._persist_register(REGISTERS["uart_control"])
return True

@property
def uart_csv_output_enabled(self):
"""
Expand All @@ -263,7 +269,7 @@ def uart_csv_output_enabled(self):
def uart_csv_output_enabled(self, enabled):
"""
Enables or disables CSV output over UART.
Call store_settings_in_flash() on NiclaSenseEnv instance after changing the CSV output mode to make the change persistent.
Use `set_uart_csv_output_enabled` with `persist` set to True to make the change persistent.

The column names and their order are:
HS4001 sample counter, HS4001 temperature (degC), HS4001 humidity (%RH), ZMOD4510 status, ZMOD4510 sample counter,
Expand Down Expand Up @@ -291,6 +297,23 @@ def uart_csv_output_enabled(self, enabled):
# Set bit 1 to 1 if enabled or 0 if disabled while keeping the other bits unchanged
self._write_to_register(REGISTERS["control"], (board_control_register_data & 0b11111101) | (int(enabled) << 1))

def set_uart_csv_output_enabled(self, enabled, persist=False) -> bool:
"""
Enables or disables CSV output over UART.

Parameters
----
enabled (bool):
Whether to enable or disable CSV output over UART.
persist (bool):
Whether to persist the change to flash memory.
When set to True, it will also persist the value of `debugging_enabled`.
"""
self.uart_csv_output_enabled = enabled
if persist:
return self._persist_register(REGISTERS["control"])
return True

@property
def csv_delimiter(self):
"""
Expand All @@ -308,7 +331,7 @@ def csv_delimiter(self):
def csv_delimiter(self, delimiter):
"""
Sets the delimiter character for CSV output.
Call store_settings_in_flash() on NiclaSenseEnv instance after changing the delimiter to make the change persistent.
Use `set_csv_delimiter` with `persist` set to True to make the change persistent.

Parameters
----
Expand All @@ -329,7 +352,24 @@ def csv_delimiter(self, delimiter):

# Use ASCII code of the delimiter character
self._write_to_register(REGISTERS["csv_delimiter"], ord(delimiter))


def set_csv_delimiter(self, delimiter, persist=False) -> bool:
"""
Sets the delimiter character for CSV output.

Parameters
----
delimiter (str):
The new delimiter character. Must be a single printable ASCII character.
The following characters are not allowed: \r, \n, \, ", '
persist (bool):
Whether to persist the change to flash memory.
"""
self.csv_delimiter = delimiter
if persist:
return self._persist_register(REGISTERS["csv_delimiter"])
return True

@property
def debugging_enabled(self):
"""
Expand All @@ -349,7 +389,7 @@ def debugging_enabled(self, enabled):
"""
Enables or disables debugging mode.
When debugging mode is enabled, the board will send additional debug messages over UART.
Call store_settings_in_flash() on NiclaSenseEnv instance after changing the debugging mode to make the change persistent.
Use `set_debugging_enabled` with `persist` set to True to make the change persistent.

Parameters
----
Expand All @@ -364,12 +404,29 @@ def debugging_enabled(self, enabled):

# Set bit 0 to 1 if enabled or 0 if disabled while keeping the other bits unchanged
self._write_to_register(REGISTERS["control"], board_control_register_data & 0b11111110 | int(enabled))


def set_debugging_enabled(self, enabled, persist=False) -> bool:
"""
Enables or disables debugging mode.

Parameters
----
enabled (bool):
Whether to enable or disable debugging mode.
persist (bool):
Whether to persist the change to flash memory.
When set to True, it will also persist the value of `uart_csv_output_enabled`.
"""
self.debugging_enabled = enabled
if persist:
return self._persist_register(REGISTERS["control"])
return True

@I2CDevice.device_address.setter
def device_address(self, address):
"""
Sets the I2C address of the device.
Call store_settings_in_flash() on NiclaSenseEnv instance after changing the address to make the change persistent.
Use `set_device_address` with `persist` set to True to make the change persistent.

Parameters
----
Expand All @@ -394,3 +451,19 @@ def device_address(self, address):
self._write_to_register(REGISTERS["slave_address"], (address_register_data & 0b10000000) | address)
sleep_us(100) # Wait for the new address to take effect
self._device_address = address

def set_device_address(self, address, persist=False) -> bool:
"""
Sets the I2C address of the device.

Parameters
----
address (int):
The new I2C address. Valid values are 0 to 127.
persist (bool):
Whether to persist the change to flash memory.
"""
self.device_address = address
if persist:
return self._persist_register(REGISTERS["slave_address"])
return True
Loading