From 596300a8231cd77cfe25452bdfbdf14d9c52bd4f Mon Sep 17 00:00:00 2001 From: Jonathan Speigner Date: Mon, 1 Dec 2025 20:50:43 -0500 Subject: [PATCH 1/2] Add Lake Lanier Water Level app --- apps/lake_lanier_water_level/README.md | 234 ++++++++++++++++++ apps/lake_lanier_water_level/icon.png | Bin 0 -> 162 bytes .../lake_lanier_water_level/lanier_level.star | 217 ++++++++++++++++ apps/lake_lanier_water_level/manifest.yaml | 9 + 4 files changed, 460 insertions(+) create mode 100644 apps/lake_lanier_water_level/README.md create mode 100644 apps/lake_lanier_water_level/icon.png create mode 100644 apps/lake_lanier_water_level/lanier_level.star create mode 100644 apps/lake_lanier_water_level/manifest.yaml diff --git a/apps/lake_lanier_water_level/README.md b/apps/lake_lanier_water_level/README.md new file mode 100644 index 000000000..f041b9c49 --- /dev/null +++ b/apps/lake_lanier_water_level/README.md @@ -0,0 +1,234 @@ +# Lake Lanier Water Level Tidbyt App + +A Tidbyt app that displays real-time water level information for Lake Lanier, including current levels, trend indicators, and historical charts. + +## Prerequisites + +- **Pixlet**: The app runtime for Tidbyt. Installation instructions are provided below. +- **Tidbyt Device**: Ensure your device is set up and connected to your network. Refer to the [Tidbyt Quickstart Guide](https://help.tidbyt.com/quickstart) for setup instructions. + +## Installation Instructions for Pixlet + +Pixlet is the tool used to develop and render apps for Tidbyt. Follow the steps below to install Pixlet on your system: + +### macOS + +1. **Install Homebrew** (if not already installed): + + ```bash + /bin/bash -c "$(curl -fsSL https://raw.githubusercontent.com/Homebrew/install/HEAD/install.sh)" + ``` + +2. **Install Pixlet**: + + ```bash + brew install tidbyt/tidbyt/pixlet + ``` + +### Linux + +1. **Download the Latest Release**: Visit the [Pixlet Releases Page](https://github.com/tidbyt/pixlet/releases) and download the latest release for your Linux distribution. + +2. **Install Pixlet**: Follow the installation instructions provided on the releases page for your specific distribution. + +### Windows + +1. **Download the Latest Release**: Visit the [Pixlet Releases Page](https://github.com/tidbyt/pixlet/releases) and download the Windows release. + +2. **Extract and Add to PATH**: Extract the archive and add the `pixlet.exe` location to your system PATH. + +For more detailed information, refer to the [Pixlet GitHub Repository](https://github.com/tidbyt/pixlet). + +## Rendering and Previewing the App Locally + +Once Pixlet is installed, you can render and preview the app locally: + +1. **Navigate to the App Directory**: Open your terminal and change to the directory containing the `lanier_level.star` file: + + ```bash + cd tidbyt + ``` + +2. **Render the App**: Run the following command to render the app to a WebP image: + + ```bash + pixlet render lanier_level.star + ``` + + This generates a `lanier_level.webp` file that you can view. + +3. **Serve the App Locally** (Recommended for Testing): To preview the app in your browser with live updates, run: + + ```bash + pixlet serve lanier_level.star + ``` + + Open your browser and navigate to `http://localhost:8080` to view the app. The app will automatically refresh when you make changes to the `.star` file. + +## Pushing the App to Your Tidbyt Device + +To deploy the app to your Tidbyt device: + +1. **Obtain Your Device ID and API Key**: + + - Open the Tidbyt app on your phone. + - Navigate to **Settings** > **General** > **Get API Key**. + - Note down your **Device ID** and **API Key**. + +2. **Render the App** (if not already done): + + ```bash + pixlet render lanier_level.star + ``` + +3. **Push the App to Your Device**: Run the following command, replacing `` and `` with the values obtained in step 1: + + ```bash + pixlet push --api-token lanier_level.webp + ``` + + This command sends the rendered app to your Tidbyt device. + + **Alternative**: You can also push directly from the `.star` file without rendering first: + + ```bash + pixlet push --api-token lanier_level.star + ``` + +For more details on pushing apps to your Tidbyt, refer to the [Pixlet GitHub Repository](https://github.com/tidbyt/pixlet). + +## Configuration Options + +The `lanier_level.star` file includes the following configuration options: + +### Display Mode + +The `DISPLAY_MODE` constant at the top of the file controls which display mode the app uses: + +```python +DISPLAY_MODE = "alternate" # Options: "A", "B", or "alternate" +``` + +**Available Modes:** + +- **`"A"`** - Simple Stats Display + - Shows "Lake Lanier" header + - Current water level in feet (large text) + - Feet above/below full pool with color-coded status and trend arrow + +- **`"B"`** - Mini Chart Display + - Top: Current level + trend icon + - Bottom: 7-day historical chart showing water level changes + +- **`"alternate"`** - Automatically switches between modes + - Shows chart mode when sufficient historical data is available (7+ data points) + - Falls back to simple stats mode otherwise + +To change the display mode, edit line 7 in `lanier_level.star`: + +```python +DISPLAY_MODE = "A" # Change to "A", "B", or "alternate" +``` + +After making changes, re-render and push the app to your device. + +### Schema Configuration (Tidbyt App Store) + +The app includes a schema for Tidbyt App Store submission, which allows users to configure the app through the Tidbyt mobile app interface. The schema provides a toggle option: + +- **Show Chart** (`show_chart`): Toggle to show the 7-day history chart + - `False` (default): Shows simple stats display + - `True`: Shows mini chart display with historical data + +When the app is installed from the Tidbyt App Store, users can configure this option directly in the Tidbyt mobile app without editing the code. + +**Note**: The schema configuration takes priority over the `DISPLAY_MODE` constant. When using the app directly (not from App Store), you can still use the `DISPLAY_MODE` constant or pass configuration via the `config` parameter. + +### Color-Coded Status Indicators + +The app uses color coding to indicate water level status relative to full pool (1071 ft): + +- **Green (#00FF00)**: `feet_above_full >= -1` - Near or above full +- **Yellow (#FFFF00)**: `feet_above_full >= -3` - Slightly below +- **Orange (#FFA500)**: `feet_above_full >= -5` - Moderately below +- **Red (#FF0000)**: `feet_above_full < -5` - Significantly below + +### Trend Indicators + +The app displays trend arrows based on water level changes: + +- **↑** - Water level is rising +- **↓** - Water level is falling +- **→** - Water level is stable + +## App Features + +- **Real-time Data**: Fetches latest water level data from the Lake Lanier API +- **Historical Charts**: Displays 7-day water level trends (Mode B) +- **Color-Coded Status**: Visual indicators for water level conditions +- **Trend Analysis**: Shows whether water levels are rising, falling, or stable +- **Dual Display Modes**: Choose between simple stats or chart view + +## Troubleshooting + +### Common Issues + +**Pixlet command not found** +- Ensure Pixlet is installed and added to your system PATH +- Try reinstalling Pixlet using the installation instructions above + +**API Error displayed on device** +- Check your internet connection +- Verify the API endpoint is accessible: `https://attrhlvatssgurlriewu.supabase.co/functions/v1/water-level-api?endpoint=latest` +- The API may be temporarily unavailable + +**App not appearing on device** +- Verify your Device ID and API Key are correct +- Ensure your Tidbyt device is connected to the internet +- Check that the device is not in sleep mode + +**Chart not displaying (Mode B)** +- Ensure you have at least 7 days of historical data +- The app will automatically fall back to Mode A if insufficient data is available + +### Getting Help + +For additional support: +- [Tidbyt Help Center](https://help.tidbyt.com/) +- [Pixlet GitHub Repository](https://github.com/tidbyt/pixlet) +- [Tidbyt Community Forum](https://community.tidbyt.com/) + +## Submitting to Tidbyt App Store + +If you want to publish this app to the Tidbyt community App Store: + +1. **Prepare Your App Files**: + - `lanier_level.star` - Main app file (already includes schema) + - `icon.png` - 20x20 pixel icon (optional but recommended) + - `README.md` - Documentation (this file) + +2. **Create the Icon** (optional): + - Create a 20x20 pixel PNG image + - Save it as `tidbyt/icon.png` + - The icon should represent Lake Lanier or water levels + +3. **Test Your App**: + - Ensure the app works correctly with both display modes + - Test with the schema configuration toggle + - Verify API connectivity and error handling + +4. **Submit to Tidbyt**: + - Follow the [Tidbyt App Store submission guidelines](https://tidbyt.dev/) + - Submit your app through the Tidbyt community portal + - Include a description, tags, and screenshots + +The app already includes the `get_schema()` function required for App Store submission, which allows users to toggle the chart display through the Tidbyt mobile app interface. + +## Data Source + +This app fetches water level data from the Lake Lanier Water Level API, which provides real-time information from the USGS monitoring station for Lake Sidney Lanier (site ID: 02334400). + +--- + +**Note**: Full pool elevation for Lake Lanier is 1071 feet above sea level. The app displays both the absolute gage height and the feet above/below full pool level. + diff --git a/apps/lake_lanier_water_level/icon.png b/apps/lake_lanier_water_level/icon.png new file mode 100644 index 0000000000000000000000000000000000000000..5e89b33bd3228320f2f710874141f2f37d4eef48 GIT binary patch literal 162 zcmeAS@N?(olHy`uVBq!ia0vp^A|TAc1|)ksWqE;Arl*TzNX4Aw1PRu|@&=D4ZP3d2 z;qUQzBPXxKFUdklr3DL|E||*8-8pD5NARXW63@Z(A3(XDEyapFfByfUpKxO#vzuGX zVTqH4l6KRLf~3k`H2l&_QBeN-(7V592amMlO98eMXHSQ)Gg!WMyL;`3R433<22WQ% Jmvv4FO#s!SIi3Ij literal 0 HcmV?d00001 diff --git a/apps/lake_lanier_water_level/lanier_level.star b/apps/lake_lanier_water_level/lanier_level.star new file mode 100644 index 000000000..1a83ec05f --- /dev/null +++ b/apps/lake_lanier_water_level/lanier_level.star @@ -0,0 +1,217 @@ +load("render.star", "render") +load("http.star", "http") +load("schema.star", "schema") + +WATER_LEVEL_API = "https://attrhlvatssgurlriewu.supabase.co/functions/v1/water-level-api" + +# Display mode: "A" for Simple Stats, "B" for Mini Chart, "alternate" to switch between modes +# This is used as a fallback when schema config is not available +DISPLAY_MODE = "alternate" + +def get_color(feet_above_full): + """Returns color based on feet above full pool level""" + if feet_above_full >= -1: + return "#00FF00" # Green - Near or above full + elif feet_above_full >= -3: + return "#FFFF00" # Yellow - Slightly below + elif feet_above_full >= -5: + return "#FFA500" # Orange - Moderately below + else: + return "#FF0000" # Red - Significantly below + +def get_trend_icon(trend): + """Returns icon character based on trend direction""" + if trend == "up": + return "↑" + elif trend == "down": + return "↓" + else: + return "→" + +def format_float(value, decimals): + """Format a float to a string with specified decimal places""" + # Convert to float and then to string + fval = float(value) + str_val = str(fval) + parts = str_val.split(".") + + if len(parts) == 1: + # No decimal point, add zeros + result = parts[0] + "." + for _ in range(decimals): + result = result + "0" + return result + else: + # Has decimal point, pad or truncate + decimal_part = parts[1] + if len(decimal_part) < decimals: + # Pad with zeros + for _ in range(decimals - len(decimal_part)): + decimal_part = decimal_part + "0" + elif len(decimal_part) > decimals: + # Truncate (simple truncation, not rounding) + decimal_part = decimal_part[:decimals] + return parts[0] + "." + decimal_part + +def get_schema(): + """Returns schema for Tidbyt App Store submission""" + return schema.Schema( + version = "1", + fields = [ + schema.Toggle( + id = "show_chart", + name = "Show Chart", + desc = "Show 7-day history chart", + icon = "chartLine", + default = False, + ), + ], + ) + +def mode_a_simple_stats(_level, feet_above, trend): + """Mode A - Simple Stats Display""" + feet_above_str = format_float(feet_above, 1) + " ft from full" + return render.Root( + child = render.Column( + children = [ + render.Text("Lake Lanier", color="#4A90A4", font="tb-8"), + render.Text(feet_above_str, color=get_color(feet_above), font="6x13"), + render.Row(children = [ + render.Text(get_trend_icon(trend), color="#888"), + ]), + ], + ), + ) + +def mode_b_mini_chart(_level, trend, history_data): + """Mode B - Mini Chart Display with 7-day history""" + # Prepare data points for the chart + data_points = [] + if len(history_data) > 0: + # Create data points: (x_index, y_value) + for i, record in enumerate(history_data): + height = float(record["gage_height"]) + data_points.append((i, height)) + + # Get color based on current level + current_feet_above = float(history_data[-1]["feet_above_full"]) if len(history_data) > 0 else 0.0 + chart_color = get_color(current_feet_above) + + # Build chart display + chart_widget = render.Text("No data", color="#888") + if len(data_points) > 0: + heights = [float(d["gage_height"]) for d in history_data] + chart_widget = render.Plot( + data = data_points, + width = 64, + height = 16, + color = chart_color, + y_lim = (min(heights), max(heights)), + ) + + # Display feet from full + feet_above_str = format_float(current_feet_above, 1) + " ft from full" + return render.Root( + child = render.Column( + children = [ + # Top: Feet from full + trend + render.Row(children = [ + render.Text(feet_above_str, color=get_color(current_feet_above), font="6x13"), + render.Text(" " + get_trend_icon(trend), color="#888"), + ]), + # Bottom: 7-day historical chart + chart_widget, + ], + ), + ) + +def main(config = None): + # Determine which mode to display + # Priority: 1) Schema config (show_chart), 2) config parameter, 3) DISPLAY_MODE constant + current_mode = DISPLAY_MODE + use_chart = False + + # Check schema config first (for Tidbyt App Store) + if config != None: + if config.get("show_chart") != None: + # Schema config takes priority + use_chart = config.get("show_chart") + elif config.get("mode") != None: + # Fallback to mode parameter for direct usage + current_mode = config.get("mode") + + # If schema config is set, use it; otherwise use current_mode + if config != None and config.get("show_chart") != None: + # Schema config mode + if not use_chart: + # Simple stats mode + rep = http.get(WATER_LEVEL_API + "?endpoint=latest") + if rep.status_code != 200: + return render.Root(child = render.Text("API Error")) + latest_data = rep.json()["data"] + return mode_a_simple_stats( + latest_data["gage_height"], + latest_data["feet_above_full"], + latest_data["trend"] + ) + else: + # Chart mode + rep = http.get(WATER_LEVEL_API + "?endpoint=latest") + if rep.status_code != 200: + return render.Root(child = render.Text("API Error")) + latest_data = rep.json()["data"] + + history_rep = http.get(WATER_LEVEL_API + "?endpoint=history&days=7&limit=100") + if history_rep.status_code != 200: + return mode_a_simple_stats( + latest_data["gage_height"], + latest_data["feet_above_full"], + latest_data["trend"] + ) + + history_json = history_rep.json() + history_data = history_json.get("data", []) + return mode_b_mini_chart( + latest_data["gage_height"], + latest_data["trend"], + history_data + ) + + # Legacy mode handling (for direct usage without schema) + # Fetch latest water level data + rep = http.get(WATER_LEVEL_API + "?endpoint=latest") + if rep.status_code != 200: + return render.Root(child = render.Text("API Error")) + + latest_data = rep.json()["data"] + level = latest_data["gage_height"] + feet_above = latest_data["feet_above_full"] + trend = latest_data["trend"] + + # If Mode A, return simple stats + if current_mode == "A": + return mode_a_simple_stats(level, feet_above, trend) + + # If Mode B or alternate, fetch historical data and show chart + if current_mode == "B" or current_mode == "alternate": + history_rep = http.get(WATER_LEVEL_API + "?endpoint=history&days=7&limit=100") + if history_rep.status_code != 200: + # Fallback to Mode A if history fetch fails + return mode_a_simple_stats(level, feet_above, trend) + + history_json = history_rep.json() + history_data = history_json.get("data", []) + + # For alternate mode, switch based on whether we have enough data points + if current_mode == "alternate": + # Simple alternation: use chart if we have data, otherwise use stats + if len(history_data) >= 7: + return mode_b_mini_chart(level, trend, history_data) + else: + return mode_a_simple_stats(level, feet_above, trend) + + return mode_b_mini_chart(level, trend, history_data) + + # Default to Mode A + return mode_a_simple_stats(level, feet_above, trend) + diff --git a/apps/lake_lanier_water_level/manifest.yaml b/apps/lake_lanier_water_level/manifest.yaml new file mode 100644 index 000000000..f087d02ed --- /dev/null +++ b/apps/lake_lanier_water_level/manifest.yaml @@ -0,0 +1,9 @@ +--- +id: lake-lanier-water-level +name: Lake Lanier Level +summary: Lake Lanier water levels +desc: Displays current water level data for Lake Sidney Lanier, including gage height, feet above/below full pool with color-coded status, trend indicators, and optional 7-day historical chart view. +author: jspeigner +fileName: lanier_level.star +packageName: lake-lanier-water-level + From fb34d8606e9927a952202a0a8a3c9fa9540a65c0 Mon Sep 17 00:00:00 2001 From: Jonathan Speigner Date: Mon, 1 Dec 2025 21:03:30 -0500 Subject: [PATCH 2/2] Fix directory name to match manifest --- .../README.md | 0 .../icon.png | Bin .../lanier_level.star | 0 .../manifest.yaml | 0 4 files changed, 0 insertions(+), 0 deletions(-) rename apps/{lake_lanier_water_level => lake-lanier-water-level}/README.md (100%) rename apps/{lake_lanier_water_level => lake-lanier-water-level}/icon.png (100%) rename apps/{lake_lanier_water_level => lake-lanier-water-level}/lanier_level.star (100%) rename apps/{lake_lanier_water_level => lake-lanier-water-level}/manifest.yaml (100%) diff --git a/apps/lake_lanier_water_level/README.md b/apps/lake-lanier-water-level/README.md similarity index 100% rename from apps/lake_lanier_water_level/README.md rename to apps/lake-lanier-water-level/README.md diff --git a/apps/lake_lanier_water_level/icon.png b/apps/lake-lanier-water-level/icon.png similarity index 100% rename from apps/lake_lanier_water_level/icon.png rename to apps/lake-lanier-water-level/icon.png diff --git a/apps/lake_lanier_water_level/lanier_level.star b/apps/lake-lanier-water-level/lanier_level.star similarity index 100% rename from apps/lake_lanier_water_level/lanier_level.star rename to apps/lake-lanier-water-level/lanier_level.star diff --git a/apps/lake_lanier_water_level/manifest.yaml b/apps/lake-lanier-water-level/manifest.yaml similarity index 100% rename from apps/lake_lanier_water_level/manifest.yaml rename to apps/lake-lanier-water-level/manifest.yaml