diff --git a/lib/wsen-hids/README.md b/lib/wsen-hids/README.md index 140d3be2..94f68765 100644 --- a/lib/wsen-hids/README.md +++ b/lib/wsen-hids/README.md @@ -293,16 +293,19 @@ No calibration is required for basic usage. --- -# Examples - -Example scripts are located in: - -``` -examples/ -``` - -Examples include: - -* basic one-shot measurements -* continuous measurement mode -* driver validation tests +## Examples + +The `examples/` directory provides practical scripts demonstrating how to use the WSEN-HIDS sensor in different scenarios. + +### Overview + +| Example | Description | +|--------|------------| +| `one_shot_mode.py` | Basic one-shot measurements: trigger a measurement and read humidity and temperature on demand | +| `continuous_mode.py` | Continuous measurement mode: sensor runs continuously and values are read periodically | +| `humidity_temperature.py` | Beginner-friendly example: perform a single read and print humidity and temperature | +| `comfort_monitor.py` | Read every 2 seconds and display a comfort indicator (`Dry`, `Comfortable`, `Humid`) based on humidity | +| `data_logger.py` | Log data every 5 seconds in CSV format (`timestamp, humidity, temperature`) for serial capture | +| `dew_point.py` | Compute and display dew point using temperature and humidity (Magnus formula) | +| `heater_demo.py` | Demonstrate the built-in heater: compare readings before and after enabling it | +| `low_power_sampling.py` | Low-power sampling: one-shot every 10 s with `power_off()` between reads. Requires firmware >= v0.1.0 (#238) | diff --git a/lib/wsen-hids/examples/comfort_monitor.py b/lib/wsen-hids/examples/comfort_monitor.py new file mode 100644 index 00000000..84b9db34 --- /dev/null +++ b/lib/wsen-hids/examples/comfort_monitor.py @@ -0,0 +1,30 @@ +"""Loop that reads humidity + temperature every 2s and prints a comfort indicator ("Dry", "Comfortable", "Humid") +based on humidity thresholds (< 30%, 30-60%, > 60%)""" + +from time import sleep_ms + +from machine import I2C +from wsen_hids import WSEN_HIDS + +i2c = I2C(1) +sensor = WSEN_HIDS(i2c) + + +def comfort_label(humidity): + if humidity < 30: + return "Dry" + elif humidity <= 60: + return "Comfortable" + else: + return "Humid" + +while True: + humidity, temperature = sensor.read_one_shot() + comfort = comfort_label(humidity) + + print("Humidity: {:.2f} %RH".format(humidity)) + print("Temperature: {:.2f} °C".format(temperature)) + print("Comfort: {}".format(comfort)) + print() + + sleep_ms(2000) diff --git a/lib/wsen-hids/examples/data_logger.py b/lib/wsen-hids/examples/data_logger.py new file mode 100644 index 00000000..7b7020b6 --- /dev/null +++ b/lib/wsen-hids/examples/data_logger.py @@ -0,0 +1,42 @@ +"""Log humidity and temperature to DAPLink flash and serial console. + +Reads every 5 seconds and writes CSV-formatted output (timestamp, +humidity, temperature). Data is stored on the DAPLink flash as +DATA.CSV and also printed to the serial console. + +WARNING: When ERASE_FLASH_ON_START is True, the entire DAPLink flash +is erased before logging. Make sure you have backed up any important +data before enabling erasure. +""" +from time import sleep_ms, ticks_diff, ticks_ms + +from daplink_flash import DaplinkFlash +from machine import I2C +from wsen_hids import WSEN_HIDS + +# Set to True to erase the DAPLink flash on startup (DESTRUCTIVE). +ERASE_FLASH_ON_START = False + +i2c = I2C(1) +sensor = WSEN_HIDS(i2c) +flash = DaplinkFlash(i2c) + +flash.set_filename("DATA", "CSV") +if ERASE_FLASH_ON_START: + flash.clear_flash() + sleep_ms(500) + print("Flash erased.") + +start_ms = ticks_ms() + +print("timestamp,humidity,temperature") +flash.write_line("timestamp,humidity,temperature") + +while True: + humidity, temperature = sensor.read_one_shot() + elapsed_s = ticks_diff(ticks_ms(), start_ms) // 1000 + + print("{},{:.2f},{:.2f}".format(elapsed_s, humidity, temperature)) + flash.write_line("{},{:.2f},{:.2f}".format(elapsed_s, humidity, temperature)) + + sleep_ms(5000) diff --git a/lib/wsen-hids/examples/dew_point.py b/lib/wsen-hids/examples/dew_point.py new file mode 100644 index 00000000..0cd60b2a --- /dev/null +++ b/lib/wsen-hids/examples/dew_point.py @@ -0,0 +1,28 @@ +"""Compute and display the dew point temperature from humidity + temperature using the Magnus formula. +Useful to understand when condensation might occur.""" +from math import log + +from machine import I2C +from wsen_hids import WSEN_HIDS + +i2c = I2C(1) +sensor = WSEN_HIDS(i2c) + + +def dew_point_celsius(temperature_c, humidity): + # Magnus formula + a = 17.62 + b = 243.12 # °C + + # Clamp humidity to a small positive value to avoid log(0) when humidity is 0.0 + safe_humidity = max(humidity, 0.01) + gamma = (a * temperature_c / (b + temperature_c)) + log(safe_humidity / 100.0) + dp = (b * gamma) / (a - gamma) + return dp + +humidity, temperature = sensor.read_one_shot() +dew_point = dew_point_celsius(temperature, humidity) + +print("Humidity: {:.2f} %RH".format(humidity)) +print("Temperature: {:.2f} °C".format(temperature)) +print("Dew point: {:.2f} °C".format(dew_point)) diff --git a/lib/wsen-hids/examples/full_test.py b/lib/wsen-hids/examples/full_test.py deleted file mode 100644 index 6c0bb692..00000000 --- a/lib/wsen-hids/examples/full_test.py +++ /dev/null @@ -1,482 +0,0 @@ -from time import sleep, sleep_ms - -from machine import I2C -from wsen_hids import WSEN_HIDS -from wsen_hids.const import * - -# --------------------------------------------------------------------- -# Update these pins and bus number to match your board -# --------------------------------------------------------------------- -MIN_HUMIDITY = 0.0 -MAX_HUMIDITY = 100.0 -MIN_TEMPERATURE = -40.0 -MAX_TEMPERATURE = 120.0 - -def print_header(title): - print() - print("=" * 60) - print(title) - print("=" * 60) - - -def print_pass(name): - print("[PASS] {}".format(name)) - - -def print_fail(name, err=None): - if err is None: - print("[FAIL] {}".format(name)) - else: - print("[FAIL] {} -> {}".format(name, err)) - - -def read_reg(sensor, reg): - return sensor._read_reg(reg) - - -def dump_registers(sensor): - print("DEVICE_ID = 0x{:02X}".format(read_reg(sensor, REG_DEVICE_ID))) - print("AV_CONF = 0x{:02X}".format(read_reg(sensor, REG_AV_CONF))) - print("CTRL_1 = 0x{:02X}".format(read_reg(sensor, REG_CTRL_1))) - print("CTRL_2 = 0x{:02X}".format(read_reg(sensor, REG_CTRL_2))) - print("CTRL_3 = 0x{:02X}".format(read_reg(sensor, REG_CTRL_3))) - print("STATUS = 0x{:02X}".format(read_reg(sensor, REG_STATUS))) - print("H_OUT_L = 0x{:02X}".format(read_reg(sensor, REG_H_OUT_L))) - print("H_OUT_H = 0x{:02X}".format(read_reg(sensor, REG_H_OUT_H))) - print("T_OUT_L = 0x{:02X}".format(read_reg(sensor, REG_T_OUT_L))) - print("T_OUT_H = 0x{:02X}".format(read_reg(sensor, REG_T_OUT_H))) - - -def test_i2c_scan(i2c): - print_header("1) I2C scan") - devices = i2c.scan() - print("I2C devices found:", [hex(x) for x in devices]) - - if WSEN_HIDS_I2C_ADDRESS in devices: - print_pass("WSEN-HIDS address found") - return True - else: - print_fail("WSEN-HIDS address found") - return False - - -def test_device_id(sensor): - print_header("2) Device ID") - - try: - dev_id = sensor.device_id() - print("Device ID:", hex(dev_id)) - - if dev_id == WSEN_HIDS_DEVICE_ID: - print_pass("Device ID matches 0x{:02X}".format(WSEN_HIDS_DEVICE_ID)) - return True - else: - print_fail( - "Device ID matches 0x{:02X}".format(WSEN_HIDS_DEVICE_ID), - hex(dev_id), - ) - return False - - except Exception as err: - print_fail("Device ID", err) - return False - - -def test_default_registers(sensor): - print_header("3) Default driver configuration") - try: - dump_registers(sensor) - - ctrl1 = read_reg(sensor, REG_CTRL_1) - av_conf = read_reg(sensor, REG_AV_CONF) - - bdu_ok = bool(ctrl1 & CTRL_1_BDU) - - # Driver defaults proposed: - # avg_t = AVG_16, avg_h = AVG_16 - avg_t = (av_conf >> 3) & 0x07 - avg_h = av_conf & 0x07 - avg_ok = (avg_t == AVG_16) and (avg_h == AVG_16) - - if bdu_ok: - print_pass("BDU enabled") - else: - print_fail("BDU enabled") - - if avg_ok: - print_pass("Default averaging configuration") - else: - print_fail( - "Default averaging configuration", - "AVG_T={}, AVG_H={}".format(avg_t, avg_h), - ) - - return bdu_ok and avg_ok - - except Exception as err: - print_fail("Default driver configuration", err) - return False - - -def test_reboot(sensor): - print_header("4) Reboot memory") - try: - sensor.reboot() - sleep(0.05) - dump_registers(sensor) - - if sensor.device_id() == WSEN_HIDS_DEVICE_ID: - print_pass("Reboot memory") - return True - else: - print_fail("Reboot memory", "device ID mismatch after reboot") - return False - - except Exception as err: - print_fail("Reboot memory", err) - return False - - -def test_one_shot(sensor): - print_header("5) One-shot read") - - try: - humidity_rh, temperature_c = sensor.read_one_shot(timeout_ms=500) - - h_ready = sensor.humidity_ready() - t_ready = sensor.temperature_ready() - ready = sensor.data_ready() - - print("Humidity : {:.2f} %RH".format(humidity_rh)) - print("Temperature : {:.2f} °C".format(temperature_c)) - print("humidity_ready :", h_ready) - print("temperature_ready:", t_ready) - print("data_ready :", ready) - - humidity_ok = MIN_HUMIDITY <= humidity_rh <= MAX_HUMIDITY - temperature_ok = MIN_TEMPERATURE <= temperature_c <= MAX_TEMPERATURE - - if humidity_ok: - print_pass("Humidity is in a valid range") - else: - print_fail("Humidity is in a valid range", humidity_rh) - - if temperature_ok: - print_pass("Temperature is in a valid range") - else: - print_fail("Temperature is in a valid range", temperature_c) - - return humidity_ok and temperature_ok - - except Exception as err: - print_fail("One-shot read", err) - return False - - -def test_one_shot_loop(sensor, count=5, delay_s=1): - print_header("6) One-shot loop") - - ok = True - - try: - for i in range(count): - humidity_rh, temperature_c = sensor.read_one_shot(timeout_ms=500) - - print( - "#{:d} H={:.2f} %RH T={:.2f} °C".format( - i + 1, - humidity_rh, - temperature_c, - ) - ) - - if not (MIN_HUMIDITY <= humidity_rh <= MAX_HUMIDITY): - ok = False - - sleep(delay_s) - - if ok: - print_pass("One-shot loop") - else: - print_fail("One-shot loop", "invalid humidity value detected") - - return ok - - except Exception as err: - print_fail("One-shot loop", err) - return False - - -def test_continuous_mode(sensor, odr, label, wait_ms=1500, loops=5, delay_s=0.5): - print_header("7) Continuous mode - {}".format(label)) - - try: - sensor.set_continuous(odr=odr) - - ctrl1 = read_reg(sensor, REG_CTRL_1) - pd_ok = bool(ctrl1 & CTRL_1_PD) - odr_ok = (ctrl1 & CTRL_1_ODR_MASK) == odr - - print("CTRL_1 after set_continuous = 0x{:02X}".format(ctrl1)) - - if pd_ok: - print_pass("PD bit set") - else: - print_fail("PD bit set") - - if odr_ok: - print_pass("ODR configured correctly") - else: - print_fail("ODR configured correctly", "CTRL_1=0x{:02X}".format(ctrl1)) - - print("Waiting {} ms for fresh samples...".format(wait_ms)) - sleep_ms(wait_ms) - - ok = pd_ok and odr_ok - - last_h = None - last_t = None - - for i in range(loops): - humidity_rh, temperature_c = sensor.read() - print( - "#{:d} H={:.2f} %RH T={:.2f} °C ready={}".format( - i + 1, - humidity_rh, - temperature_c, - sensor.data_ready(), - ) - ) - - if not (MIN_HUMIDITY <= humidity_rh <= MAX_HUMIDITY): - ok = False - - if not (MIN_TEMPERATURE <= temperature_c <= MAX_TEMPERATURE): - ok = False - - if last_h is not None and abs(humidity_rh - last_h) > 50: - ok = False - - if last_t is not None and abs(temperature_c - last_t) > 30: - ok = False - - last_h = humidity_rh - last_t = temperature_c - - sleep(delay_s) - - sensor.set_one_shot_mode() - - if ok: - print_pass("Continuous mode - {}".format(label)) - else: - print_fail("Continuous mode - {}".format(label), "invalid sample detected") - - return ok - - except Exception as err: - print_fail("Continuous mode - {}".format(label), err) - return False - - -def test_status_helpers(sensor): - print_header("8) STATUS helpers") - - try: - sensor.set_continuous(odr=ODR_1_HZ) - sleep(1.5) - - h_avail = sensor.humidity_ready() - t_avail = sensor.temperature_ready() - ready = sensor.data_ready() - - print("humidity_ready() =", h_avail) - print("temperature_ready() =", t_avail) - print("data_ready() =", ready) - - sensor.set_one_shot_mode() - - if h_avail or t_avail or ready: - print_pass("STATUS helper methods") - return True - else: - print_fail("STATUS helper methods", "helpers do not match STATUS bits") - return False - - except Exception as err: - print_fail("STATUS helper methods", err) - return False - - -def test_unitary_methods(sensor): - print_header("9) humidity() and temperature()") - - try: - sensor.set_continuous(odr=ODR_1_HZ) - sleep(1.2) - - humidity_rh = sensor.humidity() - temperature_c = sensor.temperature() - - print("humidity() = {:.2f} %RH".format(humidity_rh)) - print("temperature() = {:.2f} °C".format(temperature_c)) - - sensor.set_one_shot_mode() - - humidity_ok = MIN_HUMIDITY <= humidity_rh <= MAX_HUMIDITY - temperature_ok = MIN_TEMPERATURE <= temperature_c <= MAX_TEMPERATURE - - if humidity_ok: - print_pass("humidity() valid") - else: - print_fail("humidity() valid", humidity_rh) - - if temperature_ok: - print_pass("temperature() valid") - else: - print_fail("temperature() valid", temperature_c) - - return humidity_ok and temperature_ok - - except Exception as err: - print_fail("humidity() and temperature()", err) - return False - - -def test_heater(sensor): - print_header("10) Heater control") - - try: - sensor.enable_heater(True) - sleep_ms(20) - ctrl2_on = read_reg(sensor, REG_CTRL_2) - heater_on_ok = bool(ctrl2_on & CTRL_2_HEATER) - - print("CTRL_2 with heater ON = 0x{:02X}".format(ctrl2_on)) - - sensor.enable_heater(False) - sleep_ms(20) - ctrl2_off = read_reg(sensor, REG_CTRL_2) - heater_off_ok = not bool(ctrl2_off & CTRL_2_HEATER) - - print("CTRL_2 with heater OFF = 0x{:02X}".format(ctrl2_off)) - - if heater_on_ok: - print_pass("Heater enable") - else: - print_fail("Heater enable") - - if heater_off_ok: - print_pass("Heater disable") - else: - print_fail("Heater disable") - - return heater_on_ok and heater_off_ok - - except Exception as err: - print_fail("Heater control", err) - return False - - -def test_average_configuration(sensor): - print_header("11) Average configuration") - - try: - sensor.set_average(avg_t=AVG_8, avg_h=AVG_4) - av_conf_1 = read_reg(sensor, REG_AV_CONF) - avg_t_1 = (av_conf_1 >> 3) & 0x07 - avg_h_1 = av_conf_1 & 0x07 - - print("AV_CONF (AVG_T=AVG_8, AVG_H=AVG_4) = 0x{:02X}".format(av_conf_1)) - - ok1 = (avg_t_1 == AVG_8) and (avg_h_1 == AVG_4) - - sensor.set_average(avg_t=AVG_64, avg_h=AVG_32) - av_conf_2 = read_reg(sensor, REG_AV_CONF) - avg_t_2 = (av_conf_2 >> 3) & 0x07 - avg_h_2 = av_conf_2 & 0x07 - - print("AV_CONF (AVG_T=AVG_64, AVG_H=AVG_32) = 0x{:02X}".format(av_conf_2)) - - ok2 = (avg_t_2 == AVG_64) and (avg_h_2 == AVG_32) - - # restore defaults - sensor.set_average(avg_t=AVG_16, avg_h=AVG_16) - - if ok1: - print_pass("Average configuration set #1") - else: - print_fail("Average configuration set #1") - - if ok2: - print_pass("Average configuration set #2") - else: - print_fail("Average configuration set #2") - - return ok1 and ok2 - - except Exception as err: - print_fail("Average configuration", err) - return False - - -def test_register_dump_after_reads(sensor): - print_header("12) Register dump after measurements") - - try: - sensor.read_one_shot(timeout_ms=500) - dump_registers(sensor) - print_pass("Register dump after measurements") - return True - except Exception as err: - print_fail("Register dump after measurements", err) - return False - - -def main(): - print_header("WSEN-HIDS full driver test") - - i2c = I2C(1) - - if not test_i2c_scan(i2c): - print() - print("Stop: sensor not found on I2C bus.") - return - - try: - sensor = WSEN_HIDS(i2c) - print_pass("Driver init") - except Exception as err: - print_fail("Driver init", err) - return - - results = [] - - results.append(test_device_id(sensor)) - results.append(test_default_registers(sensor)) - results.append(test_reboot(sensor)) - results.append(test_one_shot(sensor)) - results.append(test_one_shot_loop(sensor, count=5, delay_s=1)) - results.append(test_continuous_mode(sensor, ODR_1_HZ, "1 Hz", wait_ms=1500)) - results.append(test_continuous_mode(sensor, ODR_7_HZ, "7 Hz", wait_ms=500)) - results.append(test_continuous_mode(sensor, ODR_12_5_HZ, "12.5 Hz", wait_ms=300)) - results.append(test_status_helpers(sensor)) - results.append(test_unitary_methods(sensor)) - results.append(test_heater(sensor)) - results.append(test_average_configuration(sensor)) - results.append(test_register_dump_after_reads(sensor)) - - print_header("Final result") - - passed = sum(1 for x in results if x) - total = len(results) - - print("Passed: {}/{}".format(passed, total)) - - if passed == total: - print("All tests passed.") - else: - print("Some tests failed.") - - -main() diff --git a/lib/wsen-hids/examples/heater_demo.py b/lib/wsen-hids/examples/heater_demo.py new file mode 100644 index 00000000..100a99d6 --- /dev/null +++ b/lib/wsen-hids/examples/heater_demo.py @@ -0,0 +1,41 @@ +"""Show the effect of the built-in heater +(enable_heater()): read humidity before, enable heater for a few seconds, read again. +The heater evaporates condensation — humidity should drop. Demonstrates a feature unique to this sensor.""" +from time import sleep_ms + +from machine import I2C +from wsen_hids import WSEN_HIDS + +i2c = I2C(1) +sensor = WSEN_HIDS(i2c) + +# Read before heater +humidity_before, temperature_before = sensor.read_one_shot() + +print("Before heater:") +print("Humidity: {:.2f} %RH".format(humidity_before)) +print("Temperature: {:.2f} °C".format(temperature_before)) +print() + +# Enable heater +print("Heater ON...") +sensor.enable_heater(True) + +# Wait a few seconds to see the effect +sleep_ms(1000) + +# New reading +humidity_after, temperature_after = sensor.read_one_shot() + +# Disable the heater +sensor.enable_heater(False) +print("Heater OFF") +print() + +print("After heater:") +print("Humidity: {:.2f} %RH".format(humidity_after)) +print("Temperature: {:.2f} °C".format(temperature_after)) +print() + +print("Delta humidity: {:.2f} %RH".format(humidity_after - humidity_before)) +print("Delta temperature: {:.2f} °C".format(temperature_after - temperature_before)) diff --git a/lib/wsen-hids/examples/humidity_temperature.py b/lib/wsen-hids/examples/humidity_temperature.py new file mode 100644 index 00000000..7668e552 --- /dev/null +++ b/lib/wsen-hids/examples/humidity_temperature.py @@ -0,0 +1,13 @@ +"""Simple read: print humidity and temperature once (beginner-friendly first example)""" + +from machine import I2C +from wsen_hids import WSEN_HIDS + +i2c = I2C(1) + +sensor = WSEN_HIDS(i2c) + +humidity, temperature = sensor.read_one_shot() + +print("Humidity: {:.2f} %RH".format(humidity)) +print("Temperature: {:.2f} °C".format(temperature)) diff --git a/lib/wsen-hids/examples/low_power_sampling.py b/lib/wsen-hids/examples/low_power_sampling.py new file mode 100644 index 00000000..f3bce16f --- /dev/null +++ b/lib/wsen-hids/examples/low_power_sampling.py @@ -0,0 +1,37 @@ +"""Low-power sampling with power_off() between readings. + +Minimises energy consumption by powering the sensor down between +one-shot measurements taken every 10 seconds. Displays humidity, +temperature, free RAM and measurement duration to illustrate a +battery-friendly sampling pattern. + +Requires firmware >= v0.1.0 (fix for power_off/power_on cycle, #238). +""" + +import gc +from time import sleep, ticks_diff, ticks_ms + +from machine import I2C +from wsen_hids import WSEN_HIDS + +i2c = I2C(1) +sensor = WSEN_HIDS(i2c) + +while True: + gc.collect() + t0 = ticks_ms() + + # Wake the sensor, take a single measurement, then power down + sensor.power_on() + humidity, temperature = sensor.read_one_shot() + sensor.power_off() + + elapsed_ms = ticks_diff(ticks_ms(), t0) + + print("Humidity: {:.2f} %RH".format(humidity)) + print("Temperature: {:.2f} C".format(temperature)) + print("Free RAM: {} bytes".format(gc.mem_free())) + print("Measurement: {} ms".format(elapsed_ms)) + print() + + sleep(10)