From b32ae2c9431c4f73bb2c4af86f50843f1c7cfac1 Mon Sep 17 00:00:00 2001 From: imckee-dev Date: Sun, 12 Apr 2026 13:59:11 -0700 Subject: [PATCH 1/5] just added a scaffolding button for docs --- .gitignore | 1 + web/src/AppHMI.tsx | 8 ++++++++ 2 files changed, 9 insertions(+) diff --git a/.gitignore b/.gitignore index c1723be..878d14f 100644 --- a/.gitignore +++ b/.gitignore @@ -244,3 +244,4 @@ svc/data/jeti_sim_output/ # Local temporary research repo / scratch workspace /.tmp_luox/ + diff --git a/web/src/AppHMI.tsx b/web/src/AppHMI.tsx index f2a7fda..c7bd9de 100644 --- a/web/src/AppHMI.tsx +++ b/web/src/AppHMI.tsx @@ -582,6 +582,14 @@ export default function AppHMI() { > Routines + + From f3e0300816645175149e158f0544df41f70913ef Mon Sep 17 00:00:00 2001 From: imckee-dev Date: Sun, 12 Apr 2026 14:13:37 -0700 Subject: [PATCH 2/5] have basic functionality of docs button and docs page basic scaffolding. to add: styling of the buttons, and styling / text in the docs. --- web/src/AppHMI.tsx | 17 +++++++++------- web/src/components/Docs.tsx | 26 ++++++++++++++++++++++++ web/src/components/RoutineCodeEditor.tsx | 2 +- web/src/main-hmi.tsx | 5 ++++- 4 files changed, 41 insertions(+), 9 deletions(-) create mode 100644 web/src/components/Docs.tsx diff --git a/web/src/AppHMI.tsx b/web/src/AppHMI.tsx index c7bd9de..d43f166 100644 --- a/web/src/AppHMI.tsx +++ b/web/src/AppHMI.tsx @@ -9,6 +9,7 @@ import ActiveControllersBar from "./components/ActiveControllersBar"; import { controlManager, type ControlSource } from "./utils/controlManager"; import { useToast } from "./utils/toast"; import LogsPanel from "./components/LogsPanel"; +import { Link } from "react-router-dom"; import LiveGraph from "./components/LiveGraph"; import { type SensorInfo, type SensorReadingResponse } from "./api"; @@ -583,13 +584,15 @@ export default function AppHMI() { Routines - + + + + diff --git a/web/src/components/Docs.tsx b/web/src/components/Docs.tsx new file mode 100644 index 0000000..3bb0c48 --- /dev/null +++ b/web/src/components/Docs.tsx @@ -0,0 +1,26 @@ +import {Link} from "react-router" + +export default function Docs() { + + return( + <> + + + +

Docs Page

+ +

What is the purpose of each menu?

+ +

Nav Bar

+ +
    +
  1. Sim vs. real
  2. +
+ + ) +} \ No newline at end of file diff --git a/web/src/components/RoutineCodeEditor.tsx b/web/src/components/RoutineCodeEditor.tsx index ce554e1..a46f89e 100644 --- a/web/src/components/RoutineCodeEditor.tsx +++ b/web/src/components/RoutineCodeEditor.tsx @@ -352,7 +352,7 @@ export default function RoutineCodeEditor({ panels, groups, initialRoutineId }:
} /> - } /> + } /> + } /> From 6878e7d40cb164b6eee53d00349ac6384101ab6d Mon Sep 17 00:00:00 2001 From: imckee-dev Date: Sun, 26 Apr 2026 16:19:28 -0700 Subject: [PATCH 3/5] docs page largely stylistic. Todo, rest of text: Need to add group control and windows sections. --- web/src/AppHMI.tsx | 7 ++- web/src/components/Docs.tsx | 116 ++++++++++++++++++++++++++++++------ 2 files changed, 103 insertions(+), 20 deletions(-) diff --git a/web/src/AppHMI.tsx b/web/src/AppHMI.tsx index d43f166..3b9c770 100644 --- a/web/src/AppHMI.tsx +++ b/web/src/AppHMI.tsx @@ -584,10 +584,11 @@ export default function AppHMI() { Routines - + diff --git a/web/src/components/Docs.tsx b/web/src/components/Docs.tsx index 3bb0c48..b50c91d 100644 --- a/web/src/components/Docs.tsx +++ b/web/src/components/Docs.tsx @@ -1,26 +1,108 @@ import {Link} from "react-router" + +//note the classnames in the room of the info are misnamed, but apply nicely to the styling. export default function Docs() { + return( <> - - - -

Docs Page

- -

What is the purpose of each menu?

- -

Nav Bar

- -
    -
  1. Sim vs. real
  2. -
+
+
+
+
+
+

Glazing Control System

+

Electrochromic Panel Management

+
+
+
+
+ System +
+ +
+ Panels +
+ + + + + +
+
+
+ + +
+
+ +
+

Docs Page

+ +
+ +

What is the purpose of each menu?

+ + + +
+ +
+ + +

Nav Bar Info

+
    +
  1. Sim vs. real
  2. +
      +
    • We have a simulator option and a real option, the simulator being the default. When in the trailer and connected to Wi-Fi, switch to real.
    • +
    +
+ +

Logs

+
    +
  1. Audit Log
  2. +
      +
    • Lists every change so far in the levels of each panel or group.
    • +
    • Sort by date range, panels/group, filter by specific panel ID or title.
    • +
    • Export as CSV: keeps all audits listed, with all filters, in the visible order.
    • +
    + +
  3. Sensor Log - TBA
  4. +
+ +

Manage

+
    +
  1. Groups
  2. +
      +
    • ...
    • +
    + +
  3. Routines
  4. +
      +
    • ...
    • +
    +
+ +
+ +
+

Group Control

+ +
+ +
+

Windows

+ + +
+ +
) } \ No newline at end of file From 9c32e77399f3e9f7e3286697fda2f80e28834342 Mon Sep 17 00:00:00 2001 From: Tyler Vincent Date: Fri, 22 May 2026 22:08:19 -0700 Subject: [PATCH 4/5] adding sensor documentation and normalizing doc styling to the style Ian laid out --- web/src/AppHMI.tsx | 86 ++++- web/src/components/Docs.tsx | 130 +++---- web/src/components/RoutineCodeEditor.tsx | 39 ++- web/src/components/RoutineDocs.tsx | 416 +++++++++++++---------- web/src/components/SensorDocs.tsx | 209 ++++++++++++ web/src/main-hmi.tsx | 2 + web/src/styles-hmi.css | 144 ++++++++ 7 files changed, 738 insertions(+), 288 deletions(-) create mode 100644 web/src/components/SensorDocs.tsx diff --git a/web/src/AppHMI.tsx b/web/src/AppHMI.tsx index 3b9c770..19ea94f 100644 --- a/web/src/AppHMI.tsx +++ b/web/src/AppHMI.tsx @@ -179,6 +179,21 @@ export default function AppHMI() { const [visibleSensorIds, setVisibleSensorIds] = useState([]); const sensorVisibilityUserSet = useRef(false); const [targetRoutineId, setTargetRoutineId] = useState(null); + const [docsDropdownOpen, setDocsDropdownOpen] = useState(false); + const docsDropdownRef = useRef(null); + + useEffect(() => { + function handleClickOutside(event: MouseEvent) { + if (docsDropdownRef.current && !docsDropdownRef.current.contains(event.target as Node)) { + setDocsDropdownOpen(false); + } + } + document.addEventListener("mousedown", handleClickOutside); + return () => { + document.removeEventListener("mousedown", handleClickOutside); + }; + }, []); + async function refresh() { @@ -584,15 +599,41 @@ export default function AppHMI() { Routines - - - +
+ + + +
+ setDocsDropdownOpen(false)}> + + + + General Docs + + setDocsDropdownOpen(false)}> + + + + Sensor Setup & Quickstart + + setDocsDropdownOpen(false)}> + + + + Routine Developer Docs + +
+
+
@@ -614,6 +655,33 @@ export default function AppHMI() { + {mainTab === "sensors" && ( +
+ + + +
+ )} + {mainTab === "control" && ( <> {/* group control card, same visual treatment as a room section */} diff --git a/web/src/components/Docs.tsx b/web/src/components/Docs.tsx index b50c91d..b2ff7f5 100644 --- a/web/src/components/Docs.tsx +++ b/web/src/components/Docs.tsx @@ -1,13 +1,11 @@ -import {Link} from "react-router" +import React from "react"; +import { Link } from "react-router-dom"; - -//note the classnames in the room of the info are misnamed, but apply nicely to the styling. export default function Docs() { - - - return( + return ( <> -
+ {/* Header matches AppHMI.tsx with clean, placeholder-free status layout */} +
@@ -17,92 +15,76 @@ export default function Docs() {
-
- System -
- -
- Panels -
- - - - - + + +
- -
-
- +
+ {/* Title Card */} +
-

Docs Page

- +

Documentation Page

+

+ General system overview, layout information, and operational guide +

+
+ {/* Section 1 */} +

What is the purpose of each menu?

- - - +

+ This HMI panel manages electrochromic glazing segments for solar control and visual comfort. Use the top navigation bar and side panel shortcuts to command individual zones, schedule daily tint routines, or monitor live environment measurements. +

-
- - + {/* Section 2 */} +

Nav Bar Info

-
    -
  1. Sim vs. real
  2. -
      -
    • We have a simulator option and a real option, the simulator being the default. When in the trailer and connected to Wi-Fi, switch to real.
    • -
    +
      +
    1. + Sim vs. Real: +
      + We have a simulator option and a real option, the simulator being the default. When in the trailer and connected to Wi-Fi, switch to real. +
    2. +
    3. + Logs: +
        +
      • Audit Log: Lists every change so far in the levels of each panel or group. Sort by date range, panels/group, and filter by specific panel ID or title. Export as CSV to keep all audits listed in their visible order.
      • +
      • Sensor Log: Under development (TBA).
      • +
      +
    4. +
    5. + Manage: +
        +
      • Groups: Allows managing and configuring panel zones dynamically.
      • +
      • Routines: Allows scripting routines to execute on specific intervals or schedules.
      • +
      +
    - -

    Logs

    -
      -
    1. Audit Log
    2. -
        -
      • Lists every change so far in the levels of each panel or group.
      • -
      • Sort by date range, panels/group, filter by specific panel ID or title.
      • -
      • Export as CSV: keeps all audits listed, with all filters, in the visible order.
      • -
      - -
    3. Sensor Log - TBA
    4. -
    - -

    Manage

    -
      -
    1. Groups
    2. -
        -
      • ...
      • -
      - -
    3. Routines
    4. -
        -
      • ...
      • -
      -
    -
-
+ {/* Section 3 */} +

Group Control

- +

+ Group controls allow overriding target levels for entire facades or multiple sensors simultaneously. Dwell rules prevent rapid transitions between tint states, maximizing glass longevity. +

-
+ {/* Section 4 */} +

Windows

- - +

+ Individual electrochromic panels are labeled by their sector code (e.g. P01, P02). Clicking any window tile opens the manual command dialog to override its tint level. +

-
- ) + ); } \ No newline at end of file diff --git a/web/src/components/RoutineCodeEditor.tsx b/web/src/components/RoutineCodeEditor.tsx index a46f89e..6c02332 100644 --- a/web/src/components/RoutineCodeEditor.tsx +++ b/web/src/components/RoutineCodeEditor.tsx @@ -351,22 +351,29 @@ export default function RoutineCodeEditor({ panels, groups, initialRoutineId }:
- - View Documentation ↗ - -
+ + +
-

- Routine Builder Documentation -

- -
-

Overview

-

- The Routine Builder allows you to write custom Python scripts that interact with the sensors, panels, and groups within the Glazing Control App. These routines are executed asynchronously on the backend server, allowing them to run independently of the browser dashboard. -

-
- -
-

Execution Modes

-
    -
  • - Run Once: The script will execute a single time and then finish. Useful for setting up an initial state or performing a one-time calculation. -
  • -
  • - Run on Interval: The script will execute repeatedly, waiting the specified interval (in milliseconds) between each run. -
      -
    • By default, interval routines will expire and stop automatically after 1 hour to prevent forgotten scripts from consuming resources.
    • -
    • If you need a continuously running background script, check the "Run indefinitely" box.
    • -
    -
  • -
  • - Run At (Scheduled): You can specify a future date and time for the routine to start. Your code will wait server-side until the exact time arrives. -
  • -
-
- -
-

Python API Wrappers

-

- To make interacting with the Glazing Control System simple, three global wrapper objects are injected into your script automatically: sensors, panels, and groups. -

- -
-

sensors

-
    -
  • sensors.list(): Returns a list of dictionaries containing all known sensors.
  • -
  • sensors.get_latest(sensor_id, metric): Returns the latest numerical value for a given sensor ID and metric (e.g., lux, melanopic_edi_lx), or None.
  • -
+ <> + {/* Header matches Docs.tsx and AppHMI.tsx with clean status layout */} +
+
+
+
+
+

Glazing Control System

+

Electrochromic Panel Management

+
+
+
+ + + +
+
-
-

panels

-
    -
  • panels.list(): Returns a list of all individual panels.
  • -
  • panels.set_level(panel_id, level): Command a specific panel (e.g., "P01") to tint to a specific level (0-100). Returns success or failure (e.g. if rejected by dwell time).
  • -
+
+ {/* Title Card */} +
+
+

Routine Builder Documentation

+
+

+ Developer API reference and scripting guide +

-
-

groups

-
    -
  • groups.list(): Returns a list of all configured groups.
  • -
  • groups.set_level(group_id, level): Command a group (e.g., "G-facade") to tint to a specific level (0-100).
  • + {/* Overview Card */} +
    +

    Overview

    +

    + The Routine Builder allows you to write custom Python scripts that interact with the sensors, panels, and groups within the Glazing Control App. These routines are executed asynchronously on the backend server, allowing them to run independently of the browser dashboard. +

    +
    + + {/* Execution Modes Card */} +
    +

    Execution Modes

    +
      +
    • + Run Once: The script will execute a single time and then finish. Useful for setting up an initial state or performing a one-time calculation. +
    • +
    • + Run on Interval: The script will execute repeatedly, waiting the specified interval (in milliseconds) between each run. +
        +
      • By default, interval routines will expire and stop automatically after 1 hour to prevent forgotten scripts from consuming resources.
      • +
      • If you need a continuously running background script, check the "Run indefinitely" box.
      • +
      +
    • +
    • + Run At (Scheduled): You can specify a future date and time for the routine to start. Your code will wait server-side until the exact time arrives. +
    -
- -
-

Logging and Console Output

-

- Instead of using the standard print() function, please use the provided log() function. For example: -

-
- lux_value = sensors.get_latest("T10A1-H1", "lux")
- log(f"Current lux is {'{'}lux_value{'}'}") + + {/* Python API Wrappers Card */} +
+

Python API Wrappers

+

+ To make interacting with the Glazing Control System simple, three global wrapper objects are injected into your script automatically: sensors, panels, and groups. +

+ +
+

sensors

+
    +
  • sensors.list(): Returns a list of dictionaries containing all known sensors.
  • +
  • sensors.get_latest(sensor_id, metric): Returns the latest numerical value for a given sensor ID and metric (e.g., lux, melanopic_edi_lx), or None.
  • +
+
+ +
+

panels

+
    +
  • panels.list(): Returns a list of all individual panels.
  • +
  • panels.set_level(panel_id, level): Command a specific panel (e.g., "P01") to tint to a specific level (0-100). Returns success or failure (e.g. if rejected by dwell time).
  • +
+
+ +
+

groups

+
    +
  • groups.list(): Returns a list of all configured groups.
  • +
  • groups.set_level(group_id, level): Command a group (e.g., "G-facade") to tint to a specific level (0-100).
  • +
+
+
+ + {/* Logging and Console Output Card */} +
+

Logging and Console Output

+

+ Instead of using the standard print() function, please use the provided log() function. For example: +

+
+ lux_value = sensors.get_latest("T10A1-H1", "lux")
+ log(f"Current lux is {'{'}lux_value{'}'}") +
+

+ The log() function immediately flushes the output, ensuring it streams live to the Console Output panel in the Routine Editor. +

+
+ + {/* Saving Routines Card */} +
+

Saving Routines

+

+ If you write a script you want to keep: +

+
    +
  • Type a name in the Routine Name field.
  • +
  • Click Save Routine. This saves the template permanently to the system database.
  • +
  • You can then reload it at any time from any device by clicking Load in the Saved Routines list at the bottom of the editor.
  • +
-

- The log() function immediately flushes the output, ensuring it streams live to the Console Output panel in the Routine Editor. -

-
- -
-

Saving Routines

-

- If you write a script you want to keep: -

-
    -
  • Type a name in the Routine Name field.
  • -
  • Click Save Routine. This saves the template permanently to the system database.
  • -
  • You can then reload it at any time from any device by clicking Load in the Saved Routines list at the bottom of the editor.
  • -
-
-
-

Example Scripts

- -
-

1. If lux {'>'} 80, tint Right Group to 50%

-

A simple threshold check. Best run on an Interval.

-
-
-                            {`lux = sensors.get_latest("T10A1-H1", "lux")
+
+                {/* Example Scripts Card */}
+                
+

Example Scripts

+ +
+

1. If lux {'>'} 80, tint Right Group to 50%

+

A simple threshold check. Best run on an Interval.

+
+
+                                {`lux = sensors.get_latest("T10A1-H1", "lux")
 log(f"Current lux: {lux}")
 
 if lux is not None and lux > 80:
@@ -105,119 +140,122 @@ if lux is not None and lux > 80:
     log("High lux — tinted Right Group to 50%")
 else:
     log("Lux is fine, no action needed")`}
-                        
+
+
-
-
-

2. Set all panels to 0% (clear)

-

Iterates through all individual panels and clears them. Best run Once.

-
-
-                            {`all_panels = panels.list()
+                    
+

2. Set all panels to 0% (clear)

+

Iterates through all individual panels and clears them. Best run Once.

+
+
+                                {`all_panels = panels.list()
 for p in all_panels:
     panels.set_level(p["id"], 0)
 log("All panels cleared to 0%")`}
-                        
+
+
- -
-

3. Log all sensor readings

-

Fetches and logs the current data for every sensor in the system.

-
-
-                            {`sensor_list = sensors.list()
+                    
+

3. Log all sensor readings

+

Fetches and logs the current data for every sensor in the system.

+
+
+                                {`sensor_list = sensors.list()
 log(f"Found {len(sensor_list)} sensor(s)")
 
 for s in sensor_list:
     val = sensors.get_latest(s["id"], "lux")
     log(f"{s['label']} ({s['id']}): lux = {val}")`}
-                        
+
+
- -
-

Available Sensors & Metrics

-

- The sensors.get_latest(sensor_id, metric) function requires specific sensor IDs and metric names depending on the hardware. Use the references below to know what metrics you can extract from each sensor on the system. -

- -
- T-10A Illuminance Meter (t10a) -
-

Provides basic illuminance (lux) readings.

-

Common Sensor IDs:

-
    -
  • T10A1-H1 - T-10A body 1, head 1
  • -
  • T10A2-H4 - T-10A body 2, head 4
  • -
-

Available Metrics:

-
    -
  • lux: Illuminance (lx)
  • -
-
-
- -
- JETI Spectraval / Specbos (jeti_spectraval) -
-

Provides advanced colorimetry, illuminance, and non-visual lighting metrics from JETI spectraval or specbos devices.

-

Common Sensor IDs:

-
    -
  • JETI-00 - first configured JETI device
  • -
-

Available Metrics:

-
    -
  • lux: Illuminance (lx)
  • -
  • melanopic_edi_lx: Melanopic EDI (lx)
  • -
  • cct_ohno_k: CCT (K) - Ohno, 2013
  • -
  • cri_ra: Colour Rendering Index [Ra]
  • -
  • cfi_rf: Colour Fidelity Index [Rf]
  • -
  • cie1931_x: CIE 1931 xy chromaticity [x]
  • -
  • cie1931_y: CIE 1931 xy chromaticity [y]
  • -
  • s_cone_irradiance_mw_m2: S-cone-opic irradiance (mW/m2)
  • -
  • m_cone_irradiance_mw_m2: M-cone-opic irradiance (mW/m2)
  • -
  • l_cone_irradiance_mw_m2: L-cone-opic irradiance (mW/m2)
  • -
  • rhodopic_irradiance_mw_m2: Rhodopic irradiance (mW/m2)
  • -
  • melanopic_irradiance_mw_m2: Melanopic irradiance (mW/m2)
  • -
  • s_cone_edi_lx: S-cone-opic EDI (lx)
  • -
  • m_cone_edi_lx: M-cone-opic EDI (lx)
  • -
  • l_cone_edi_lx: L-cone-opic EDI (lx)
  • -
  • rhodopic_edi_lx: Rhodopic EDI (lx)
  • -
  • cct_robertson_k: CCT (K) - Robertson, 1968
  • -
  • duv_ohno: Duv - Ohno, 2013
  • -
  • duv_robertson: Duv - Robertson, 1968
  • -
  • sample_interval_s: Sample interval (s)
  • -
-
-
- -
- EKO MS-90+ Sun Tracker Pyranometer (eko_ms90_plus) -
-

Outdoor environmental sensor providing global, direct, and diffuse solar irradiance, as well as sun position data.

-

Common Sensor IDs:

-
    -
  • EKO-00 - EKO MS-90+ / C-BOX
  • -
-

Available Metrics:

-
    -
  • ghi_w_m2: Global horizontal irradiance (W/m2)
  • -
  • dni_w_m2: Direct normal irradiance (W/m2)
  • -
  • dhi_w_m2: Diffuse horizontal irradiance (W/m2)
  • -
  • sun_elevation_deg: Sun elevation (deg)
  • -
  • sun_azimuth_deg: Sun azimuth (deg)
  • -
  • board_temp_c: Board temperature (degC)
  • -
  • sensor_temp_c: Sensor temperature (degC)
  • -
  • gps_satellites: GPS satellites
  • -
  • latitude_deg: Latitude (deg)
  • -
  • longitude_deg: Longitude (deg)
  • -
  • gps_timestamp_s: GPS timestamp (s)
  • -
-
-
-
- + + {/* Available Sensors & Metrics Card */} +
+

Available Sensors & Metrics

+

+ The sensors.get_latest(sensor_id, metric) function requires specific sensor IDs and metric names depending on the hardware. Use the references below to know what metrics you can extract from each sensor on the system. +

+ +
+ T-10A Illuminance Meter (t10a) +
+

Provides basic illuminance (lux) readings.

+

Common Sensor IDs:

+
    +
  • T10A1-H1 - T-10A body 1, head 1
  • +
  • T10A2-H4 - T-10A body 2, head 4
  • +
+

Available Metrics:

+
    +
  • lux: Illuminance (lx)
  • +
+
+
+ +
+ JETI Spectraval / Specbos (jeti_spectraval) +
+

Provides advanced colorimetry, illuminance, and non-visual lighting metrics from JETI spectraval or specbos devices.

+

Common Sensor IDs:

+
    +
  • JETI-00 - first configured JETI device
  • +
+

Available Metrics:

+
    +
  • lux: Illuminance (lx)
  • +
  • melanopic_edi_lx: Melanopic EDI (lx)
  • +
  • cct_ohno_k: CCT (K) - Ohno, 2013
  • +
  • cri_ra: Colour Rendering Index [Ra]
  • +
  • cfi_rf: Colour Fidelity Index [Rf]
  • +
  • cie1931_x: CIE 1931 xy chromaticity [x]
  • +
  • cie1931_y: CIE 1931 xy chromaticity [y]
  • +
  • s_cone_irradiance_mw_m2: S-cone-opic irradiance (mW/m2)
  • +
  • m_cone_irradiance_mw_m2: M-cone-opic irradiance (mW/m2)
  • +
  • l_cone_irradiance_mw_m2: L-cone-opic irradiance (mW/m2)
  • +
  • rhodopic_irradiance_mw_m2: Rhodopic irradiance (mW/m2)
  • +
  • melanopic_irradiance_mw_m2: Melanopic irradiance (mW/m2)
  • +
  • s_cone_edi_lx: S-cone-opic EDI (lx)
  • +
  • m_cone_edi_lx: M-cone-opic EDI (lx)
  • +
  • l_cone_edi_lx: L-cone-opic EDI (lx)
  • +
  • rhodopic_edi_lx: Rhodopic EDI (lx)
  • +
  • cct_robertson_k: CCT (K) - Robertson, 1968
  • +
  • duv_ohno: Duv - Ohno, 2013
  • +
  • duv_robertson: Duv - Robertson, 1968
  • +
  • sample_interval_s: Sample interval (s)
  • +
+
+
+ +
+ EKO MS-90+ Sun Tracker Pyranometer (eko_ms90_plus) +
+

Outdoor environmental sensor providing global, direct, and diffuse solar irradiance, as well as sun position data.

+

Common Sensor IDs:

+
    +
  • EKO-00 - EKO MS-90+ / C-BOX
  • +
+

Available Metrics:

+
    +
  • ghi_w_m2: Global horizontal irradiance (W/m2)
  • +
  • dni_w_m2: Direct normal irradiance (W/m2)
  • +
  • dhi_w_m2: Diffuse horizontal irradiance (W/m2)
  • +
  • sun_elevation_deg: Sun elevation (deg)
  • +
  • sun_azimuth_deg: Sun azimuth (deg)
  • +
  • board_temp_c: Board temperature (degC)
  • +
  • sensor_temp_c: Sensor temperature (degC)
  • +
  • gps_satellites: GPS satellites
  • +
  • latitude_deg: Latitude (deg)
  • +
  • longitude_deg: Longitude (deg)
  • +
  • gps_timestamp_s: GPS timestamp (s)
  • +
+
+
+
+
+ ); } diff --git a/web/src/components/SensorDocs.tsx b/web/src/components/SensorDocs.tsx new file mode 100644 index 0000000..08c8753 --- /dev/null +++ b/web/src/components/SensorDocs.tsx @@ -0,0 +1,209 @@ +import React from "react"; +import { Link } from "react-router-dom"; + +export default function SensorDocs() { + return ( + <> + {/* Header matches Docs.tsx and AppHMI.tsx with clean status layout */} +
+
+
+
+
+

Glazing Control System

+

Electrochromic Panel Management

+
+
+
+ + + +
+
+
+ +
+ {/* Title Card */} +
+
+

Sensor Setup & Quickstart Guide

+
+

+ Site calibration and physical hardware deployment runbook +

+
+ + {/* Quick Overview Callout */} +
+

+ + + + Quickstart Checklist (SVC_MODE = real) +

+

+ When operating in the research trailer/site PC, the service runs in real mode to pull actual sensor instruments. Use this guide to ensure all physical USB/Ethernet links are connected, drivers are verified, and the local software exports are properly configured. +

+
+ + {/* Section 1: Konica Minolta T-10A */} +
+

1. Konica Minolta T-10A Setup

+

+ The system supports single-head and multi-head daisy-chained illuminance configurations via USB/virtual COM port. +

+ +

Physical Connection Steps:

+
    +
  • + Single-Head Setup: Connect the receptor head to the T-10A body using Konica Minolta head adapter hardware. Use straight CAT5 patch cables for segments (do not use crossover Ethernet cables). Connect the body to the PC using a USB cable. +
  • +
  • + Multi-Head Setup: Chain the receptor heads together using T-A20 / T-A21 multi-point adapters and straight CAT5 cables. Connect the AC-A412 external power supply (required for multi-head). Set a unique physical ID (00–29) on each head. Connect the main body to the PC via USB. +
  • +
+ +

Configuration:

+
+
    +
  1. Power on the T-10A body. Open Device Manager on Windows and locate the virtual COM port (e.g. COM3).
  2. +
  3. + Update svc/data/sensors_config.json under the t10a array: +
    +{`"t10a": [
    +  {
    +    "device_id": "T10A-00",
    +    "port": "COM3",
    +    "heads": [
    +      { "head_no": 1, "sensor_id": "T10A1-H1", "label": "Desk Lux", "location": "Desk" }
    +    ],
    +    "interval_s": 60
    +  }
    +]`}
    +                                
    +
  4. +
+
+
+ + {/* Section 2: JETI Spectraval / Specbos */} +
+

2. JETI Spectraval & Specbos Setup

+

+ The JETI instrument connects to the site PC using USB. +

+ + {/* Important Notice Callout */} +
+ ⚠️ Active Deployment Support + + Currently, only file-based .cap file reading is supported in the backend service. Direct SPECFIRM serial/virtual COM SCPI polling is not active in this app version and may be added in a future update. + +
+ +

Physical & Software Setup:

+
    +
  1. Connect the JETI device to the PC using a USB cable. Install the official JETI USB drivers.
  2. +
  3. Open the JETI measurement suite software on the PC and verify connection to the instrument.
  4. +
  5. Configure the JETI software to automatically save or export new measurements as semicolon-delimited .cap files.
  6. +
+ +

File Location & Naming Rules (CRITICAL):

+
+

+ In sensors_config.json, set "transport": "file". The behavior depends on the value of "output_path": +

+
    +
  • + If output_path points to a File (e.g. "data/live.cap"): +
    + Instruct the JETI software to continuously write/overwrite this exact file. The app reads this file directly. +
  • +
  • + If output_path points to a Directory (e.g. "data/jeti_measurements/"): +
    + The JETI software can export rotating files with dynamic names (e.g. jeti_2026_05_22_1500.cap). The file watcher will scan the folder and automatically load the file ending in .cap that has the latest modification time (mtime) on disk. +
  • +
+
+
+ + {/* Section 3: EKO MS-90+ / C-BOX */} +
+

3. EKO MS-90+ & C-BOX Setup

+

+ The EKO sun tracker system feeds data to the PC network via Modbus TCP. The old RS485-to-USB serial path is deprecated. +

+ +

Network & Hardware Setup:

+
    +
  1. Verify the EKO sensors are wired into the C-BOX and powered.
  2. +
  3. Connect the C-BOX Ethernet port to the local trailer network.
  4. +
  5. Open a web browser on the PC and visit the C-BOX Web UI (default IP: http://192.168.2.20/). Confirm live values appear.
  6. +
  7. Go to Modbus {"->"} Setup and verify Modbus TCP Access is enabled (allow access from any IP address).
  8. +
+ +

Configuration:

+
+

+ Configure the EKO tracker in sensors_config.json under the eko_ms90_plus array: +

+
+{`"eko_ms90_plus": [
+  {
+    "sensor_id": "EKO-00",
+    "device_id": "EKO-CBOX-01",
+    "host": "192.168.2.20",
+    "port": 502,
+    "slave_address": 1,
+    "float_byte_order": "ABCD",
+    "interval_s": 5,
+    "timeout_s": 3.0
+  }
+]`}
+                        
+
+
+ + {/* Section 4: Verification */} +
+

4. Troubleshooting & Verification

+ +
+
+

Start Backend in Real Mode

+
+{`cd svc
+$env:SVC_MODE = "real"
+uv run python main.py`}
+                            
+
+ +
+

Test Endpoints in PowerShell

+
+{`# Check registered sensors:
+irm http://127.0.0.1:8000/sensors
+
+# Check live values:
+irm http://127.0.0.1:8000/metrics/latest`}
+                            
+
+
+
+
+ + ); +} diff --git a/web/src/main-hmi.tsx b/web/src/main-hmi.tsx index f96eaaa..1cba023 100644 --- a/web/src/main-hmi.tsx +++ b/web/src/main-hmi.tsx @@ -7,6 +7,7 @@ import { ToastProvider } from "./utils/toast"; import "./styles-hmi.css"; import Docs from "./components/Docs"; +import SensorDocs from "./components/SensorDocs"; ReactDOM.createRoot(document.getElementById("root")!).render( @@ -16,6 +17,7 @@ ReactDOM.createRoot(document.getElementById("root")!).render( } /> } /> } /> + } /> diff --git a/web/src/styles-hmi.css b/web/src/styles-hmi.css index 24d0bfc..00c0086 100644 --- a/web/src/styles-hmi.css +++ b/web/src/styles-hmi.css @@ -2659,4 +2659,148 @@ select option[disabled] { color: var(--hmi-text-muted); font-size: 12px; font-style: italic; +} + +/* ======================================== + Docs Dropdown Menu + ======================================== */ + +.hmi-dropdown { + position: relative; + display: inline-block; +} + +.hmi-dropdown-menu { + display: none; + position: absolute; + right: 0; + top: calc(100% + 8px); + background: var(--hmi-surface); + border: 1px solid var(--hmi-border); + border-radius: 12px; + box-shadow: 0 12px 30px rgba(0, 0, 0, 0.5); + min-width: 240px; + z-index: 1000; + overflow: visible; + padding: 8px 0; + opacity: 0; + transform: translateY(8px); + transition: opacity 0.15s ease, transform 0.15s ease; +} + +/* Invisible bridge to prevent losing hover when crossing the gap */ +.hmi-dropdown-menu::before { + content: ""; + position: absolute; + top: -12px; + left: 0; + right: 0; + height: 12px; + background: transparent; +} + +/* Show when open class is applied or parent hovered */ +.hmi-dropdown:hover .hmi-dropdown-menu, +.hmi-dropdown.open .hmi-dropdown-menu { + display: block; + opacity: 1; + transform: translateY(0); +} + +.hmi-dropdown-item { + display: flex; + align-items: center; + gap: 12px; + padding: 10px 18px; + color: var(--hmi-text-muted); + text-decoration: none; + font-size: 13.5px; + font-weight: 500; + transition: all 0.15s ease; + cursor: pointer; +} + +.hmi-dropdown-item:hover { + background: rgba(37, 99, 235, 0.12); + color: var(--hmi-text-bright); +} + +.hmi-dropdown-item svg { + width: 16px; + height: 16px; + opacity: 0.6; + color: var(--hmi-text-muted); + transition: opacity 0.15s ease, color 0.15s ease; + flex-shrink: 0; +} + +.hmi-dropdown-item:hover svg { + opacity: 1; + color: var(--hmi-primary); +} + +.hmi-dropdown-divider { + height: 1px; + background: var(--hmi-border); + margin: 8px 0; +} + +.hmi-dropdown-arrow { + width: 12px; + height: 12px; + transition: transform 0.2s ease; + transform: rotate(0deg); +} + +.hmi-dropdown:hover .hmi-dropdown-arrow, +.hmi-dropdown.open .hmi-dropdown-arrow { + transform: rotate(180deg); +} + +/* ======================================== + Documentation Pages Layout + ======================================== */ + +.docs-layout { + max-width: 1000px !important; + margin: 0 auto; + padding: 24px 16px; + display: flex; + flex-direction: column; + gap: 24px; +} + +@media (min-width: 768px) { + .docs-layout { + padding: 40px 24px; + } +} + +.docs-layout .room-section p, +.docs-layout .room-section ul, +.docs-layout .room-section ol { + margin-top: 0; + margin-bottom: 16px; + line-height: 1.6; + color: var(--hmi-text-muted); +} + +.docs-layout .room-section ul:last-child, +.docs-layout .room-section ol:last-child, +.docs-layout .room-section p:last-child { + margin-bottom: 0; +} + +.docs-layout .room-section h3 { + color: var(--hmi-text-bright); + font-size: 16px; + margin: 24px 0 12px 0; +} + +.docs-layout .room-section code { + font-family: monospace; + background: rgba(0, 0, 0, 0.2); + padding: 2px 6px; + border-radius: 4px; + color: var(--hmi-text-bright); } \ No newline at end of file From 00c0200265bdd7709ec03fb8aa1e0257e77ec7a3 Mon Sep 17 00:00:00 2001 From: Tyler Vincent Date: Sat, 30 May 2026 14:45:27 -0700 Subject: [PATCH 5/5] merge with main to resolve conflicts, and flesh out docs pages a little more --- web/src/components/Docs.tsx | 2 +- web/src/components/SensorDocs.tsx | 69 +++++++++++++++++++------------ 2 files changed, 44 insertions(+), 27 deletions(-) diff --git a/web/src/components/Docs.tsx b/web/src/components/Docs.tsx index b2ff7f5..49b6809 100644 --- a/web/src/components/Docs.tsx +++ b/web/src/components/Docs.tsx @@ -56,7 +56,7 @@ export default function Docs() { Logs:
  • Audit Log: Lists every change so far in the levels of each panel or group. Sort by date range, panels/group, and filter by specific panel ID or title. Export as CSV to keep all audits listed in their visible order.
  • -
  • Sensor Log: Under development (TBA).
  • +
  • Sensor Log: Tracks live and historical environment measurements (such as illuminance, solar irradiance, GPS status, colorimetry, and spectral data). You can view individual log entries in detail (including historical spectral graphs for JETI devices) and export all logged data as CSV.
  • diff --git a/web/src/components/SensorDocs.tsx b/web/src/components/SensorDocs.tsx index 08c8753..e6b171a 100644 --- a/web/src/components/SensorDocs.tsx +++ b/web/src/components/SensorDocs.tsx @@ -99,45 +99,62 @@ export default function SensorDocs() { The JETI instrument connects to the site PC using USB.

    - {/* Important Notice Callout */} +

    Method A: JETI over USB with file-based .cap ingestion

    +

    + Use this when the measurement software on the PC exports JETI data that the backend will watch. +

    +
      +
    1. Connect the JETI device to the PC using a USB cable. Install the official JETI USB drivers.
    2. +
    3. Open the JETI measurement suite software on the PC and verify connection to the instrument.
    4. +
    5. Configure the JETI software to automatically save or export new measurements as semicolon-delimited .cap files.
    6. +
    7. Configure the "transport" to "file" and "output_path" to the file or directory in sensors_config.json: +
        +
      • If pointing to a File (e.g. "data/spectraval_1.cap"): Instruct the JETI software to continuously overwrite this exact file.
      • +
      • If pointing to a Directory (e.g. "data/jeti_measurements/"): The JETI software can export rotating files. The watcher automatically loads the .cap file with the latest modification time.
      • +
      +
    8. +
    +
    - ⚠️ Active Deployment Support + ⚠️ Critical Multiple Sensor Naming Rule - Currently, only file-based .cap file reading is supported in the backend service. Direct SPECFIRM serial/virtual COM SCPI polling is not active in this app version and may be added in a future update. + When configuring multiple JETI sensors in file mode, you must configure the PC software to export each sensor's data to a distinct file name (e.g., spectraval_1.cap, specbos.cap). Do not point multiple sensors to the same file path, as this will cause data collisions.
    -

    Physical & Software Setup:

    +

    Method B: JETI over USB virtual COM with direct SPECFIRM polling

    +

    + Use this when you want the backend service to communicate with the JETI device directly using virtual serial. +

    +
    + ⚠️ Direct Serial Testing Notice + + JETI direct serial polling (Method B) is currently in beta and has not been fully verified with physical hardware on site. It may not function as expected. For stable deployments, file-based .cap ingestion (Method A) is highly recommended. + +
    1. Connect the JETI device to the PC using a USB cable. Install the official JETI USB drivers.
    2. -
    3. Open the JETI measurement suite software on the PC and verify connection to the instrument.
    4. -
    5. Configure the JETI software to automatically save or export new measurements as semicolon-delimited .cap files.
    6. +
    7. Open Device Manager on Windows and locate the JETI virtual COM port (e.g. COM4).
    8. +
    9. + Configure the "transport" to "serial_scpi", set "port" to the COM port, and set "baudrate" depending on the model: +
        +
      • 921600 for spectraval 1511
      • +
      • 115200 for specbos 1211-2
      • +
      +
    - -

    File Location & Naming Rules (CRITICAL):

    -
    -

    - In sensors_config.json, set "transport": "file". The behavior depends on the value of "output_path": -

    -
      -
    • - If output_path points to a File (e.g. "data/live.cap"): -
      - Instruct the JETI software to continuously write/overwrite this exact file. The app reads this file directly. -
    • -
    • - If output_path points to a Directory (e.g. "data/jeti_measurements/"): -
      - The JETI software can export rotating files with dynamic names (e.g. jeti_2026_05_22_1500.cap). The file watcher will scan the folder and automatically load the file ending in .cap that has the latest modification time (mtime) on disk. -
    • -
    -
    {/* Section 3: EKO MS-90+ / C-BOX */}