Mastodon Politics, Power, and Science: The PicoW IoT Framework: Project Development Manual

Thursday, November 27, 2025

The PicoW IoT Framework: Project Development Manual

J. Rogers, SE Ohio

This will probably be behind the development of the project.

https://github.com/BuckRogers1965/Pico-IoT-Replacement

1. Introduction & Showcase Project

Welcome to the PicoW IoT Framework. This document is your guide to building a new project from scratch, using the "Advanced Environment Controller" as a complete example.

Showcase: The Advanced Environment Controller

This project is a sophisticated controller for managing an indoor garden or terrarium. It showcases the full power of the framework by integrating a wide variety of sensors and controls.

Features:

  • Comprehensive Sensing: Monitors ambient temperature, humidity, barometric pressure, light level, and soil moisture.

  • Dynamic Web UI: A real-time dashboard accessible from any browser on your network, showing all sensor data and controls.

  • Interactive Controls:

    • slider to set the target humidity for an automated humidifier.

    • toggle switch for manual override of the main grow light.

    • push button to trigger a timed "Feed Now" water pump cycle.

  • Zero-Touch Setup: A captive portal for easy, one-time configuration of WiFi credentials and a unique device name (e.g., "Living Room Terrarium").

  • Automatic Discovery: The device announces itself on the network via mDNS and is automatically discovered and integrated into Home Assistant by the provided Python bridge script.

  • Live Graphing: Displays a historical graph of temperature, humidity, and light levels.

  • Custom API Endpoint: A special /api/reboot endpoint to remotely restart the device.


2. User Guide: First-Time Device Setup

Before you can use your new device, you must connect it to your WiFi network. This is a simple, one-time process.

  1. Power On the Device: Plug your Pico W device into a USB power source for the first time. The device will fail to find any saved WiFi credentials and will automatically enter Configuration Mode. The onboard LED will likely blink in a rapid pattern to indicate this.

  2. Connect to the Setup Network: On your phone or laptop, open your WiFi settings. You will see a new, open WiFi network with a name like ProjectName-Setup-XXYYZZ. For our showcase project, this would be AdvEnv-Setup-A1B2C3. Connect to this network.

  3. The Captive Portal: As soon as you connect, your device's web browser should automatically open a setup page. (If it doesn't, open a browser and navigate to http://192.168.4.1).

  4. Configure Your Device: You will see a simple form:

    • WiFi Network: A dropdown list of nearby networks. Select your home WiFi network.

    • WiFi Password: Enter the password for your home WiFi.

    • Device Name: This is crucial. Give this specific device a unique, descriptive name. For example: Orchid Terrarium.

  5. Save and Reboot: Click the "Save and Reboot" button.

The device will save your settings to its internal flash memory and restart. It will now automatically connect to your home WiFi and will be discoverable on your network with the custom name you provided.


3. Developer's Guide: Building Your Own Project

This section details how to create a new project, like the "Advanced Environment Controller," using the framework.

3.1. Project Structure

Your entire project will consist of just two code files in your Arduino sketch folder:

  1. MyNewProject.ino: Contains only your project-specific logic.

  2. PicoW_IoT_Framework.h: The framework itself. You never need to edit this file.

3.2. The 5 Essential Application Callbacks

Your .ino file must provide the following functions. The framework calls these to run your custom logic.

1. 
This provides the name for the setup WiFi network. It's called only when the device is in Configuration Mode.

C++
void app_get_default_identity(String& project_name, String& device_id_prefix) {
    project_name = "Advanced Environment Controller";
    device_id_prefix = "AdvEnv-Setup";
}

2. 
This is the heart of your project's interface. You define every sensor and control here. The framework uses this information to build the web UI and the API.

C++
void app_register_items() {
  mutex_enter_blocking(&data_mutex);
  int i = 0;
  #define ADD_ITEM(...) // Macro defined in the full .ino file

  // === SENSORS (Read-Only Displays) ===
  // TYPE_SENSOR_GENERIC: For numerical data like temperature, light, etc.
  ADD_ITEM("temp", "Temperature", TYPE_SENSOR_GENERIC, 0, 0, 0, 0, "°C");
  ADD_ITEM("humidity", "Humidity", TYPE_SENSOR_GENERIC, 0, 0, 0, 0, "%");
  ADD_ITEM("pressure", "Pressure", TYPE_SENSOR_GENERIC, 0, 0, 0, 0, "hPa");
  ADD_ITEM("light_level", "Light Level", TYPE_SENSOR_GENERIC, 0, 0, 0, 0, "%");
  ADD_ITEM("soil_moisture", "Soil Moisture", TYPE_SENSOR_GENERIC, 0, 0, 0, 0, "%");
  
  // TYPE_SENSOR_STATE: For text-based status displays.
  ADD_ITEM("op_mode", "Operating Mode", TYPE_SENSOR_STATE, 0, 0, 0, 0, "");

  // === CONTROLS (User Writable) ===
  // TYPE_CONTROL_SLIDER: Creates a slider on the UI.
  // Define min, max, and step values.
  ADD_ITEM("target_humidity", "Target Humidity", TYPE_CONTROL_SLIDER, 60, 40, 90, 1, "%");

  // TYPE_CONTROL_TOGGLE: Creates an On/Off switch.
  // Values are always 0 (off) and 1 (on).
  ADD_ITEM("light_override", "Light Manual On", TYPE_CONTROL_TOGGLE, 0, 0, 1, 1, "");
  
  // TYPE_CONTROL_BUTTON: A momentary button.
  // Your code will see a value (1, 2, or 3) for a single cycle, then it resets to 0.
  ADD_ITEM("feed_button", "Watering Cycle", TYPE_CONTROL_BUTTON, 0, 0, 3, 1, "");

  registry_count = i;
  mutex_exit(&data_mutex);
}

3. 
This is your hardware setup() function. Initialize your sensor libraries, set pin modes, and create your main application tasks.

C++
int mainUpdate(struct _task_entry_type* t, int, int); // Forward declaration

void app_setup() {
  // Initialize hardware libraries (I2C for BME280, etc.)
  Wire.begin();
  bme.begin(0x76);

  // Set pin modes for photoresistor, moisture sensor, and actuators
  pinMode(LIGHT_PIN, INPUT);
  pinMode(MOISTURE_PIN, INPUT);
  pinMode(PUMP_RELAY_PIN, OUTPUT);
  pinMode(LIGHT_RELAY_PIN, OUTPUT);
  
  // Create and start the main application task to run every 100ms
  AddTaskMilli(CreateTask(), 100, &mainUpdate, 0, 0);
}

4. Settings & Identity Callbacks
These functions handle loading/saving the custom name and WiFi credentials, and providing the unique identity to the discovery bridge.

C++
// This provides the full, unique identity to the discovery bridge.
void app_get_identity(String& p) {
  char id[17];
  sprintf(id, "%04X%04X", (uint16_t)(rp2040.getChipID() >> 16), ...);
  p = "{";
  p += "\"project_name\":\"Advanced Environment Controller\",";
  p += "\"device_id\":\"env_ctrl_" + String(id) + "\",";
  p += "\"device_name\":\"" + device_name_setting + "\","; // Uses the name saved from the captive portal!
  p += "\"api_version\":\"1.0\"";
  p += "}";
}

// These functions use LittleFS and ArduinoJson to save and load a config file.
// The framework calls these automatically on boot and during setup.
bool app_load_settings() { /* ... loads config.json ... */ }
void app_save_settings() { /* ... saves config.json ... */ }

5. 
These callbacks allow for advanced customization.

  • app_draw_graph(): You define which data from your history buffer gets plotted and how. This gives you full control over the graph visualization.

  • app_add_api_endpoints()(Advanced Example) This allows you to add your own custom API endpoints to the web server. Here, we add a simple /api/reboot command.

C++
// A simple handler for our custom endpoint
void handleReboot() {
  IoTFramework::server.send(200, "text/plain", "Rebooting device now.");
  delay(500);
  rp2040.restart();
}

// The callback to register the handler with the framework's web server
void app_add_api_endpoints(WebServer& server) {
  server.on("/api/reboot", HTTP_GET, handleReboot);
}

3.3. Writing the Main Application Task

This is the loop() of your project. It's a single, non-blocking function that the scheduler runs at a regular interval.

The Logic Cycle:

  1. Read Inputs: Get latest values from physical sensors AND read the current control settings from the registry using IoTFramework::getRegistryValue().

  2. Process Logic: Make decisions. (e.g., if ).

  3. Write Outputs: Update physical actuators (relays, PWM) AND write the latest sensor readings back to the registry using IoTFramework::setRegistryValue() so the web UI updates.

  4. Update History: Once per second, add relevant data points to the sensor_history buffer for graphing.

C++
int mainUpdate(struct _task_entry_type *task, int, int) {
  // Reschedule self to run again
  AddTask(task, 1000 / UPDATE_HZ, &mainUpdate, 0, 0);

  // 1. READ INPUTS
  float temp = bme.readTemperature();
  float target_humidity = IoTFramework::getRegistryValue("target_humidity");
  uint8_t feed_button_press = IoTFramework::getRegistryValue("feed_button");

  // 2. PROCESS LOGIC
  if (feed_button_press == BTN_SHORT) {
      // Start a 10-second watering cycle...
      IoTFramework::setRegistryValue("feed_button", BTN_NONE); // Consume the press
  }
  // ... more logic for lights, humidity, etc. ...

  // 3. WRITE OUTPUTS
  digitalWrite(PUMP_RELAY_PIN, HIGH); // Example
  IoTFramework::setRegistryValue("temp", temp);
  // ... update other registry sensors ...
  
  // 4. UPDATE HISTORY
  // ... add temp, humidity, etc., to sensor_history array ...

  return 0;
}

4. The Automation Bridge

The pico_discovery_bridge.py script is the key to automatic integration. You run this single script on your network (e.g., on a Raspberry Pi or the same machine as Home Assistant).

What it does:

  1. It continuously scans your network for any device advertising the _iot-framework._tcp service.

  2. When it finds one, it connects and queries the /api/identity endpoint to learn its unique name (e.g., "Orchid Terrarium").

  3. It then queries the /api/manifest endpoint to learn all of its sensors and controls.

  4. It automatically generates and sends the necessary configuration messages to Home Assistant via MQTT to create all the corresponding entities.

  5. It then enters a loop, acting as a gateway to relay data and commands between the Pico and Home Assistant.

You only need to run one instance of this script to manage all your PicoW IoT devices, regardless of their project type.

No comments:

Post a Comment

Progress on the campaign manager

You can see that you can build tactical maps automatically from the world map data.  You can place roads, streams, buildings. The framework ...