Use Your AI Agent to Talk to Your Products with Notehub IQ

Blues Developers
What’s New
Resources
API Reference
Notecard and Notehub API documentation
Blog
Technical articles for developers
Connected Product Guidebook
In-depth guides for connected product development
Developer Certification
Get certified on wireless connectivity with Blues
Newsletter
The monthly Blues developer newsletter
Terminal
Connect to a Notecard in your browser
Webinars
Listing of Blues technical webinars
Blues.comNotehub
Shop
Docs
Help
Support DocsNotehub StatusSecurity AdvisoriesVisit our Forum
Sign In
Docs Home
What’s New
Resources
API Reference
Notecard and Notehub API documentation
Blog
Technical articles for developers
Connected Product Guidebook
In-depth guides for connected product development
Developer Certification
Get certified on wireless connectivity with Blues
Newsletter
The monthly Blues developer newsletter
Terminal
Connect to a Notecard in your browser
Webinars
Listing of Blues technical webinars
Blues.comNotehub
Shop
Docs
Example Apps
Samples
Building a Simple Asset Tracker
Building Edge AI Applications
Continuous Asset Tracking with External GPS and Immediate Location Sync
Controlling Device State and Receiving Acknowledgment
Managing a Notecard's WiFi Network Remotely
Putting a Host to Sleep Between Sensor Readings
Routing Data from Notehub to a Custom Cloud Endpoint
Using LEDs and NeoPixels to Monitor Notecard Status
Accelerators
Asset Location Tracking
Heavy Equipment Hours-of-Use & Utilization Tracker
Returnable Container / Tote Pool Tracker
Untethered Trailer and Chassis Fleet Tracker
Asset Performance Optimization
Legacy Diesel Generator Fleet Performance Uplift
Solar Array String-Level Performance Dashboard
Battery Management Systems
Aerial Lift / Rental Equipment Battery Health Monitor
Off-Grid Solar Battery Site Controller
Remote Cabinet Backup Battery Sentinel
Downtime Prevention
Municipal Wastewater Lift Station Monitor
Rooftop HVAC Predictive Maintenance
VFD Pump Predictive Maintenance
Energy Monitoring Solutions
Commercial Tenant Sub-Metering Bridge
EV Charger Session & Utilization Monitor
Utility Distribution Transformer Load Monitor
Energy Savings
Commercial Plug-Load & After-Hours Waste Dashboard
Demand-Response Solar + Battery Dispatcher
Multi-Site Walk-In Cooler Energy & Setpoint Monitor
Industrial Equipment Monitoring
CNC Machine Spindle Load and Cycle Time Tracker
Injection Molding Shot-to-Shot Process Monitor
Trailer Manufacturer Connected Trailer Platform
Loss Prevention
Construction Equipment Anti-Theft Tracker with Immobilizer
Reefer Trailer Cold-Chain & Door-Event Monitor
Remote Patient Monitoring
Cellular Medication Adherence Pillbox
Post-Discharge Vitals Relay Hub
Remotely Monitored Assets
Off-Grid Livestock Water Tank Monitor
Remote Apiary Hive Health Monitor
Safety Assurance
Construction Site Environmental & Noise Exposure Monitor
Lone Worker Panic and Fall Detection Beacon
Regulatory-Grade Pharmacy and Lab Cold Storage Audit Monitor
Supply Chain Tracking
Pallet-Attached Cold Chain Logger
Rail Car Condition & Interchange Tracker
Truck Roll Reduction
Commercial Grease Interceptor Level Monitor
Propane / LPG Tank Fill Telemetry
homechevron_rightExample Appschevron_rightAcceleratorschevron_rightOff-Grid Solar Battery Site Controller

Off-Grid Solar Battery Site Controller

Off-Grid Solar Battery Site Controller banner
note

This reference application is here to give you ideas and help you get started quickly. It's built on specific hardware that may not match yours, so focus on the sections that fit your use case. If you'd like to talk through your project and whether it's a good fit for Blues, reach out.

Building with an AI assistant? Point it at this accelerator's firmware in firmware/solar_battery_controller/ so it can learn how the device works and help you adapt it to your hardware.

This project is a Blues battery management systems reference design for remote off-grid sites powered by solar arrays and battery banks, and it monitors them at the bank level. A Blues Notecarrier CX reads two Victron VE.Direct devices: a SmartShunt for battery-bank metrics and a SmartSolar MPPT charge controller for solar-side metrics. Every four hours it reports bank-level state of charge (SoC), daily solar harvest, load draw, battery temperature, and charge state to the Blues Notehub cloud service, and it sends alerts right away, before the site goes dark. A Blues Notecard seated in the carrier's M.2 slot handles connectivity. Two SKUs run the same firmware source, with one build flag for the satellite SKU: Notecard Cell+WiFi (NOTE-MBGLW) for sites within terrestrial cellular range, and Notecard for Skylo (NOTE-NBGLWX) for satellite fallback at sites where cellular coverage is marginal or absent.

1. Project Overview

The problem. Cell towers at the edge of coverage, environmental monitoring stations in the backcountry, and off-grid cabins all run on solar and battery, and they tend to fail the same way. A problem starts small, days before anyone notices, and builds until the site goes dark. Maybe the PV array is slowly degrading, or a load crept up after a firmware update to on-site equipment, or the battery bank's capacity has quietly faded with age. None of these is catastrophic on its own, but any one of them can drain a bank the solar array is no longer sized to refill. Because the site is remote by definition, a recharge deficit that lasts for days stays invisible without continuous telemetry. This design detects that condition directly. If the charge controller doesn't reach a full-charge state (Float, Absorption, Equalize, or Auto Equalize) for a configurable number of consecutive days, a harvest_deficit alert fires before the bank is depleted, so the operations team has time to respond before the site goes dark.

note

Interface Note. This project reads the Victron VE.Direct text protocol, a one-wire broadcast interface that exposes battery-bank aggregates (SoC, voltage, current, temperature, and charge state) and solar-side metrics (panel voltage, power, and daily yield). Those are the signals you need to catch the site-uptime failure modes this design targets: persistent recharge deficit, low SoC, thermal overtemperature, and excessive load draw. For a bank-level site-uptime monitor, bank-level aggregates are the right scope. Cell-imbalance monitoring was explicitly evaluated during design and scoped out. Per-cell voltages and imbalance data aren't broadcast on the VE.Direct wire. They travel over CAN bus and need a dedicated CAN controller and transceiver, and this hardware stack has neither. Cell-level telemetry is a separate hardware and firmware problem, so it belongs in a companion design, not in an extension of this project (see §11 for the full rationale and the companion-design specification).

Why Notecard. These sites are off-grid and off-network by definition. There's no building WiFi to join, no Ethernet jack in the enclosure, and no cellular router whose monthly bill someone else is paying. Notecard manages its own radio session, draws microamp-range idle current between transmissions, and doesn't need anyone from site IT to set it up. Two SKUs fit the same M.2 slot on the Notecarrier CX and run the same firmware source:

New to Blues?

Learn what we do in this 3-minute video that walks you through how we simplify wireless connectivity.

Learn what we do in this 3-minute video that walks you through how we simplify wireless connectivity.

  • Notecard Cell+WiFi (NOTE-MBGLW): LTE Cat-1 bis cellular with opportunistic WiFi fallback. It's the practical pick for bench validation and for sites with decent terrestrial coverage.
  • Notecard for Skylo (NOTE-NBGLWX): LTE-M cellular plus Skylo Non-Terrestrial Network (NTN) satellite connectivity on a single card. Build with -DSKYLO_BUILD and the firmware turns on the satellite fallback with card.transport, and from there the Notecard picks the radio on its own. Both antennas must be positioned outdoors with an unobstructed view of the sky. §4 and §5 cover the mounting and enclosure-feedthrough requirements. Notecard for Skylo is the main production SKU for the truly remote towers, wilderness arrays, and high-altitude installations this use case targets.

Deployment scenario. A Notecarrier CX mounts inside the existing site enclosure or in a weatherproof add-on box. It's powered from the site's 5V regulation bus or from a small DC-DC converter off the main battery bus. Two short VE.Direct cables run from the Notecarrier CX dual-row header to the SmartShunt (the battery shunt, usually mounted near the battery bank's negative terminal) and to the SmartSolar MPPT controller (often mounted on the enclosure wall). Nothing on the Victron equipment changes, and the solar system keeps running the whole time.

2. System Architecture

System architecture: Victron SmartShunt + SmartSolar MPPT controller via VE.Direct UART → Notecarrier CX with STM32 host and Notecard (NBGLWX or MBGLW) → cellular or Skylo NTN satellite → Notehub → NOC / SCADA / on-call

Device-side responsibilities. The STM32L433 host on the Notecarrier CX spends almost all of its time asleep in STM32 STOP2, drawing a few microamps with its RAM intact. Every 15 minutes (configurable), the Notecard's ATTN pin wakes it up. The host reads one VE.Direct frame from each Victron device over UART, adds them to rolling averages in a state struct that just stays in RAM across sleep cycles, checks four alert rules locally, and goes back to sleep. A full cycle takes under 8 seconds. The Notecard talks to the host over I²C, which keeps serial buffers, AT commands, and a modem state machine out of the host firmware.

Notecard responsibilities. The Notecard handles all the connectivity work, and the host never sees any of it. It holds outbound Notes in its on-device queue and wakes the radio on the configured hub.set outbound cadence (4 hours by default). When a sync:true alert lands in the queue, the Notecard breaks that cadence and sends it right away. On each inbound sync it also pulls environment variables down from Notehub, so you can retune alert thresholds and sample intervals without a firmware update or a site visit.

Notehub responsibilities. The Notecard's embedded global SIM connects through whatever carrier reaches the site, and Notehub takes in every event that arrives. Alerts (solar_alert.qo) and summaries (solar_summary.qo) land in separate Notefiles, so routes can send them out at different urgencies with no filter logic. Alerts go straight to the NOC or on-call system, and summaries go to a long-term analytics store for trend work.

Routing to the cloud (high level). Notehub can route to HTTP, MQTT, AWS, Azure, GCP, Snowflake, and several other targets. Route setup is specific to your project, and this project doesn't ship a downstream endpoint, so start with the Notehub routing docs.

3. Technical Summary

You can have a working monitor running in ~2 hours:

  1. Assemble the Notecarrier CX with the Notecard (Cell+WiFi or Skylo), the DC-DC converter, and the resistor dividers listed in §4.
  2. Wire the SmartShunt and MPPT to the two VE.Direct ports on the Notecarrier CX header (§5).
  3. Install dependencies:
    arduino-cli core install "STMicroelectronics:stm32"
    arduino-cli lib install "Blues Wireless Notecard" "STM32duino Low Power" "STM32duino RTC"
  4. Set ProductUID on the #define PRODUCT_UID line in firmware/solar_battery_controller/solar_battery_controller.ino, then fit the ATTN→D5 jumper on the Notecarrier CX header (§5).
  5. Flash:
    arduino-cli compile -b STMicroelectronics:stm32:Blues:pnum=CYGNET,usb=none firmware/solar_battery_controller/
    arduino-cli upload -b STMicroelectronics:stm32:Blues:pnum=CYGNET,usb=none -p /dev/cu.usbmodem* firmware/solar_battery_controller/
    The usb=none board option turns off the USB CDC stack, which has to be off for the host to sleep (see §7.1).
  6. Claim and configure in Notehub:
    • Power on. The device claims itself to your project within ~1 minute (watch the Devices tab).
    • Create a Fleet and set its Fleet → Environment variables (see the table in §6). The defaults are fine for initial testing.
    • Add routes for solar_alert.qo (real-time) and solar_summary.qo (storage/analytics).
  7. Validate: _session.qo and solar_summary.qo Notes should show up in Notehub within 5–10 minutes (see "What you should see in Notehub" in §6).

Here's a sample Note this device emits:

{
  "bat_v": 26.1,
  "bat_a": -3.2,
  "bat_w": -83.5,
  "soc_pct": 78.4,
  "bat_temp_c": 22.1,
  "pv_v": 34.8,
  "pv_w": 142.0,
  "load_w": 225.5,
  "yield_kwh": 1.24,
  "ttg_min": -1,
  "cs": 5
}

4. Hardware Requirements

PartQtyRationale
Notecarrier CX1Integrated carrier with an onboard STM32L433 host, so there's no separate MCU. Provides the UART (Serial1), analog, and SPI/I²C headers this sensor mix needs. The host sleeps in STOP2 between wakes, and the Notecard's ATTN pin wakes it.
Notecard Cell+WiFi (NOTE-MBGLW)1 (bench / sites with reliable cellular coverage)LTE Cat-1 bis cellular with opportunistic WiFi fallback, plus a prepaid global SIM with no monthly commitment. Use this SKU for bench validation and for field sites with reliable terrestrial cellular coverage.
Notecard for Skylo (NOTE-NBGLWX)1 (sites with marginal or no terrestrial coverage)LTE-M cellular plus Skylo NTN satellite on a single M.2 card. It's the production SKU for remote towers, backcountry arrays, and high-altitude installations where terrestrial cellular is unreliable. It needs both the Notecard-certified satellite/LTE antenna and a passive GPS/GNSS antenna, and both must be mounted outdoors with an unobstructed view of the sky (§5 covers enclosure feedthrough). It runs the same firmware as the NOTE-MBGLW, built with -DSKYLO_BUILD (§7.1). Read the Notecard for Skylo datasheet and the antenna guide before deploying.
Blues Mojo1Coulomb counter on the +VBAT rail for checking the sleep/wake current profile during bench bring-up and commissioning (see §9).
Victron SmartShunt 500A/50mV1Industry-standard battery shunt monitor. Reports battery voltage, current, SoC, and optional temperature over VE.Direct UART. The 500A variant covers the vast majority of off-grid installations, from small cabins to telecom sites, and there's a 1000A variant for high-current systems. Includes a VE.Direct cable.
Victron SmartSolar MPPT 75/151 (or existing)Solar charge controller. Reports panel voltage, instantaneous panel power, daily yield, and charge state over VE.Direct. Pick the MPPT SKU that matches your array size, since the VE.Direct protocol and field names are the same across the SmartSolar family. Includes a VE.Direct cable.
Victron VE.Direct cable0–24-pin JST PH 2.0 to bare-wire cable. Each Victron device above comes with one, so only buy extras if your layout needs longer runs. Wire colors aren't standardized, so always verify pin assignments with a multimeter before connecting (see §5).
10 kΩ, 20 kΩ resistors2 eachOne voltage-divider pair per VE.Direct RX line: device TX → 10 kΩ → MCU RX pin, and junction → 20 kΩ → GND. Output ≈ 3.33 V. VE.Direct is a one-way broadcast protocol where the host only reads and never writes, so a simple resistor divider is the right, lowest-risk interface for this one-way UART. §5 has the pin-by-pin wiring.
Victron Temperature Sensor for SmartShunt0–1Plugs into the SmartShunt's temp sensor port. You need it to populate bat_temp_c in the summary Note. It's optional, but strongly recommended for lithium battery banks, where thermal runaway is a real risk.
Traco TSR 1-2450 DC-DC converter (12 V and 24 V sites)16.5–36 V input, 5 V / 1 A output, SIP-3 package. The input range covers 12 V and 24 V battery buses through their full charge/discharge swing. The TSR 1 datasheet covers derating above 40 °C.
Traco TSR 1-4850 DC-DC converter (48 V telecom sites)118–75 V input, 5 V / 1 A output, same SIP-3 footprint as the TSR 1-2450. Handles 48 V telecom battery buses at full charge. Use this part instead of the TSR 1-2450 on 48 V deployments. Nothing else changes.
Hammond 1555JGY weatherproof enclosure1125 × 95 × 57 mm, ABS, IP67, gray. Fits the Notecarrier CX, the resistor divider parts, and the DC-DC converter. Mount it inside the site's existing primary enclosure or next to the SmartShunt.
Skylo-certified cellular/NTN antenna, included with the NOTE-NBGLWX kit (MAIN u.FL port)1 (NOTE-NBGLWX only)Ships in the NOTE-NBGLWX kit and connects to the MAIN u.FL port. Do not substitute it. The Notecard is certified on Skylo's network only with this antenna, and replacing it leaves the device uncertified, so Skylo may block it from the network. If you need a different antenna, it has to go through a Skylo delta-certification test. Contact Blues for recommended test houses. Mount it outdoors with an unobstructed view of the sky (see §5).
Blues Flexible Dual LTE/WiFi and GPS/GNSS Antenna (Quectel YCA001BA) (NOTE-NBGLWX GPS port)1 (NOTE-NBGLWX only)Passive flexible multi-band antenna with a u.FL connector. Its GNSS L1 coverage (1560–1620 MHz, including GPS L1 at 1575 MHz) suits the GPS u.FL port on the NOTE-NBGLWX. Unless you set a fixed location (see Fixed GPS location below), the NOTE-NBGLWX needs a GPS fix from this antenna before each satellite session. Use only a passive (un-amplified) antenna on this port, because active/LNA GNSS antennas don't work with the NOTE-NBGLWX GPS input. Route the flexible cable lead through a cable gland in the enclosure wall and mount the antenna flat on the enclosure exterior with a clear view of the sky.
u.FL to SMA female pigtail, ≥100 mm, RG316 or RG178 (e.g. SparkFun WRL-09145) + IP67-rated SMA bulkhead connector (NOTE-NBGLWX MAIN feedthrough)1 (NOTE-NBGLWX only)Routes the included Skylo antenna from the Notecard's MAIN u.FL port through the enclosure wall by way of an SMA bulkhead connector. Use the shortest pigtail that reaches the nearest enclosure wall without sharp bends, which keeps insertion loss on the satellite band low. DigiKey and Mouser carry IP67-rated SMA panel-mount bulkhead connectors. The GPS antenna (Quectel YCA001BA above) has a flexible lead long enough to go straight through its own cable gland, so the GPS port doesn't need another pigtail.
M16 IP67 cable gland, suitable for 4–8 mm cable OD (NOTE-NBGLWX GPS feedthrough)1 (NOTE-NBGLWX only)Seals the GPS antenna (Quectel YCA001BA) cable lead where it passes through the enclosure wall. The MAIN antenna goes through the SMA bulkhead connector listed above, which has its own weather seal. Thread and bore size have to match your enclosure wall thickness. DigiKey, Mouser, and electrical distributors carry nylon M16 glands.
Cellular/LTE antenna with u.FL connector (see Blues antenna guide for certified options)1 (NOTE-MBGLW)You have to connect a u.FL cellular antenna to the NOTE-MBGLW's MAIN u.FL pigtail lead on the Notecarrier CX, because the carrier has no onboard PCB trace cellular antenna. A compact flexible u.FL LTE antenna works for non-metallic (plastic or ABS) enclosures. For metallic enclosures, add a u.FL-to-SMA pigtail and an IP67-rated SMA bulkhead connector to get the antenna outside the enclosure wall. The Blues antenna guide covers which antennas are compatible and where to put them.

All Blues hardware ships with an active SIM that includes 500 MB of data and 10 years of service, with no activation fees and no monthly commitment.

5. Wiring and Assembly

Wiring: SmartShunt on Serial1 (CX RX pin), SmartSolar MPPT on SoftwareSerial D9 (both VE.Direct); ATTN → D5 jumper so the Notecard can wake the host from STOP2; MAIN antenna for cellular or satellite; site 5 V bus → DC-DC step-down → Mojo (bench) → +VBAT

All host I/O uses the Notecarrier CX dual-row 16-pin header. A short jumper between ATTN and D5 on that header lets the Notecard wake the sleeping host. The Notecard (Cell+WiFi or Skylo; see §4) seats in the carrier's M.2 slot, and the Mojo sits inline on the +VBAT power rail for bench bring-up and commissioning. A simple 10 kΩ/20 kΩ resistor divider on each RX line handles VE.Direct TX-to-MCU level shifting. It's the only documented and validated interface for this project. Active level-shifter boards with bidirectional MOSFETs aren't recommended for a one-way UART like this.

warning

Safety with high-current DC systems. Off-grid battery banks can source thousands of amps into a short circuit. Before making any connections in or near battery, shunt, or MPPT wiring, disconnect the solar array at the MPPT PV input terminals. Verify polarity at every connection point with a multimeter before making contact, and have a qualified installer review all DC wiring before commissioning. The VE.Direct signal connections below are low-voltage and carry microamp-level current. They don't interrupt the high-current path, but they pass through the same enclosure as live battery wiring.

VE.Direct cable pinout

Each Victron device has a 4-pin JST PH 2.0 (2.0 mm pitch) port for VE.Direct. Here's the standard pin numbering, with pin 1 at the locking-tab end of the connector:

PinSignalDirectionNotes
1GNDN/AConnect to Notecarrier CX GND
2RX (into device)Host → DeviceLeave NC, since the protocol is device-broadcast only
3TX (out from device)Device → Host5V logic, so it needs level shifting or a divider
4+5V (from device)Device → HostLeave NC, since the host is externally powered
warning

Wire color warning. Wire colors on Victron VE.Direct cables aren't standardized across product revisions, and they can mislead you (red may be GND, not power). Before connecting anything to the STM32 or level shifter, always confirm the pin assignment with a multimeter. Measure the DC voltage between the two outer wires to find pin 4 (+5V). The VE.Direct Protocol Specification is the authoritative reference for your specific device revision.

The standard Victron VE.Direct cable (ASS030530200 / ASS030530300) ends in bare wires on the host side. Connect pin 3 (TX) and pin 1 (GND), and leave pins 2 and 4 unconnected or tape them off.

Pin-by-pin wiring

SmartShunt (Battery shunt / Serial1):

  • SmartShunt VE.Direct pin 3 (TX) → 10 kΩ resistor → CX header RX pin, with the junction also connected to → 20 kΩ resistor → GND. The midpoint between the two resistors is the MCU RX input, and it drops 5 V to ~3.33 V.
  • SmartShunt VE.Direct pin 1 (GND) → CX header GND.
  • Pins 2 and 4 stay unconnected. The protocol is broadcast-only, so the host never writes to the device, and the host is externally powered.

Victron temperature sensor (SmartShunt temp-sensor port): Plug the optional Victron Temperature Sensor for SmartShunt straight into the SmartShunt's dedicated temperature-sensor port (a small JST connector on the SmartShunt body, separate from the VE.Direct port). It doesn't need any wiring to the Notecarrier CX. The SmartShunt reads the sensor internally and broadcasts the temperature on the VE.Direct wire, where the firmware picks it up automatically. Without this sensor, bat_temp_c never shows a real value in summary Notes, and the temp_high alert can't evaluate.

SmartSolar MPPT (Solar charger / SoftwareSerial on D9):

  • MPPT VE.Direct pin 3 (TX) → 10 kΩ resistor → CX header D9, with the junction also connected to → 20 kΩ resistor → GND.
  • MPPT VE.Direct pin 1 (GND) → CX header GND.
  • CX header D10 is defined as the SoftwareSerial TX pin in firmware but isn't connected to anything. Leave it NC.

ATTN-to-D5 jumper (host sleep):

Fit a short jumper between the ATTN and D5 pins, which sit on the same 16-pin Notecarrier CX header. The Notecard pulls ATTN low while the host sleeps and raises it when the next sample is due, and the rising edge on D5 wakes the host from STOP2. Don't connect the Notecarrier CX EN pin. On the CX it enables the shared 3.3 V VIO rail (host, Qwiic, 3V3_OUT, and Notecard I/O), so driving it from ATTN would brown out the whole board instead of just putting the host to sleep. Putting a Host to Sleep Between Sensor Readings walks through the full pattern.

Power, from the battery bus to the Notecarrier CX:

The Traco TSR 1-2450 (12 V and 24 V sites) or TSR 1-4850 (48 V telecom sites) steps the battery bus voltage down to a regulated 5 V rail. The TSR 1 is a non-isolated SIP-3 switcher: Pin 1 = VIN+, Pin 2 = GND (shared input/output return), Pin 3 = VOUT+. Check the pinout against the Traco TSR 1 datasheet before soldering, and don't rely on the silkscreen alone.

Wire the power chain in this order:

  1. Battery bus positive tap → inline fuse → Traco VIN+ (Pin 1). Install a 2 A glass-cartridge or automotive blade fuse at the battery bus tap point, as close to the battery or busbar as you practically can. The TSR 1 is rated for 1 A output, and a 2 A fuse protects the converter and the 22–24 AWG wiring used at this current level while leaving headroom for inrush. A fuse holder with a visual indicator (for example, the Littelfuse OMNI-BLOK series or equivalent) makes field commissioning easier.
  2. Battery bus return (negative) → Traco GND/VIN− (Pin 2). Connect the battery-bus return directly to the converter's GND pin, and don't leave the input return floating.
  3. Traco VOUT+ (Pin 3) → Mojo BAT pin (when Mojo is installed for bench validation; see §9). Route the 5 V output through the Mojo so it measures all the current drawn from the regulated rail. Without Mojo, connect VOUT+ straight to step 4.
  4. Mojo LOAD pin → Notecarrier CX +VBAT pin. The Mojo passes current through and measures cumulative mAh.
  5. Traco GND (Pin 2) / system return → Notecarrier CX GND pin. The TSR 1's shared GND is both the input return and the output return, so connect it to the Notecarrier CX GND. It's also the signal ground for the VE.Direct cable shields. Tie the battery-bus return and signal ground together at one point only (at the converter or at the shunt negative terminal, not both) to avoid a ground loop through the VE.Direct cables.
warning

Polarity verification before first power-on. With VIN connected and the VOUT wire to the Notecarrier CX still disconnected, apply power and measure VOUT+ (Pin 3) to GND (Pin 2). You should read 5.0 V ± 2%. If the voltage is missing or reversed, recheck the VIN polarity at Pin 1 and Pin 2 before you connect the Notecarrier CX. Off-grid battery banks can deliver thousands of amps into a reverse-polarity fault, so verify first.

Bench power (USB): During bench bring-up without the DC-DC converter, power the Notecarrier CX from USB through the +VUSB pin or USB-C port. Don't connect +VBAT and +VUSB at the same time.

Cellular antenna (NOTE-MBGLW path):

The Notecarrier CX routes the NOTE-MBGLW's MAIN u.FL port to a u.FL pigtail lead on the carrier board. Since the Notecarrier CX has no onboard PCB trace cellular antenna, you have to connect an external u.FL cellular/LTE antenna to this lead. The Blues antenna guide lists certified antenna options.

  • Non-metallic (plastic or ABS) enclosure: Connect a compact flexible u.FL LTE antenna and position it inside the enclosure away from metal parts, or route the cable lead through a small cable gland and mount the antenna on the enclosure exterior.
  • Metallic enclosure (steel electrical cabinet, aluminum panel): Connect a u.FL-to-SMA pigtail to the MAIN u.FL pigtail lead, thread an IP67-rated SMA bulkhead connector through the enclosure wall, and mount the cellular antenna on the external SMA port. That gets the RF path outside the metal shell, which would otherwise block a lot of the signal.

The NOTE-MBGLW has a GNSS receiver, but this project doesn't use it. The design runs in cellular mode only, so you don't need a GPS/GNSS antenna. If a future version needs site geolocation, the Notecard's onboard GNSS can be enabled without firmware changes.

Satellite antenna feedthrough (NOTE-NBGLWX only):

  • Notecard for Skylo has two u.FL connectors: MAIN for the Skylo-certified satellite/LTE antenna (included in the kit) and GPS for the passive GNSS antenna (Quectel YCA001BA). Both antennas must be mounted outside the enclosure with an unobstructed view of the sky, because satellite signals can't get through a metal enclosure wall.
  • MAIN antenna: Connect the included Skylo antenna to the MAIN u.FL port through the SparkFun WRL-09145 (or equivalent RG316/RG178) pigtail. The pigtail's SMA female end connects to an IP67-rated SMA bulkhead connector threaded through the enclosure wall, and the antenna mounts on the bulkhead's external SMA port. The bulkhead has its own weather seal.
  • GPS antenna (Quectel YCA001BA): Plug the antenna's u.FL connector into the GPS u.FL port on the Notecard. Route the flexible cable lead through an M16 IP67 cable gland in the enclosure wall, and mount the flexible patch flat on the outside of the enclosure.
  • Bend cables gently, since sharp bends kink the coax feed lines. Keep runs as short as practical to hold down insertion loss, especially on the satellite band.
  • On the MAIN port, use only the Skylo-certified antenna that comes in the Notecard for Skylo kit. A different antenna voids Skylo network certification and may get the device blocked from the network. The Blues antenna guide has the full certification and placement requirements.
tip

VE.Direct cables are short (0.9 m or 1.8 m), so mount the Notecarrier CX close to the SmartShunt and MPPT to avoid signal integrity problems on long runs.

6. Notehub Setup

  1. Create a project. Sign up at notehub.io and create a project. Copy the ProductUID, which looks like com.your-company.your-name:solar-battery-site.

  2. Set the ProductUID in firmware. Open firmware/solar_battery_controller/solar_battery_controller.ino and replace the empty string on the #define PRODUCT_UID "" line with your project's value.

  3. Claim the Notecard. Power up the assembled unit. The Notecard associates itself with your Notehub project on its first cellular connection, and the device shows up in the Devices tab within a minute or two.

  4. Create a Fleet per site type. Fleets (and Smart Fleets) group devices for shared configuration. One natural split is a fleet for 12V cabin installations and another for 24V telecom sites. Each fleet can carry its own alert thresholds to suit its battery chemistry and load profile.

  5. Set environment variables. In Notehub, go to Fleet → Environment (or Device → Environment for a per-unit threshold override). The device pulls the values on its next inbound sync, so you don't need a firmware reflash or a site visit.

    VariableDefaultPurpose
    soc_alert_pct20.0State-of-charge (%) threshold below which the soc_low alert fires. Usually 15–25%, depending on battery chemistry.
    bat_temp_max_c45.0Battery temperature (°C) threshold above which temp_high fires. Only evaluated if a Victron Temperature Sensor is plugged into the SmartShunt temp port. Common values are 50–55 °C for lead-acid and 40–45 °C for lithium.
    load_alert_w1000.0Load-draw (W) threshold above which load_high fires. Size it to your site's rated load budget. For example, a tower with a 600 W peak load should use 650–700 W for headroom, and a data-center-grade 1.5 kW load should use 1600–1700 W.
    harvest_deficit_days0.0Consecutive days without the MPPT reaching a full-charge state (Float, Absorption, Equalize, or Auto Equalize) before harvest_deficit fires. 0.0 disables the check. Try 2.0 for well-sized systems and 3.0–4.0 for systems with seasonal or multi-day cloud patterns. Only evaluated when the window has MPPT data.
    sample_interval_sec900Seconds between host wakes (minimum 60, maximum 3600). Shorter intervals use more power over time but give better resolution. The default of 900 seconds is 15 minutes, and the usual range is 300–1800 s.
    report_interval_min240 (1440 with SKYLO_BUILD)Minutes between summary Notes (minimum 15, maximum 1440). When this changes, the firmware resets the window right away and re-issues hub.set so the Notecard's outbound cadence matches on the same cycle. For Skylo NTN deployments, set it to 1440 (once daily) to cut satellite data use.
    sync_outbound_min(auto: equals report_interval_min)Notecard outbound sync interval in minutes (15–1440). Leave it unset to follow report_interval_min automatically, or set it explicitly to decouple the two (for example, sync every 2 hours but summarize every 4). For Skylo, set report_interval_min=1440 and leave this unset.
    sync_inbound_min(auto: 2 × report_interval_min)Notecard inbound sync interval in minutes (30–2880) for pulling Fleet variable updates. Defaults to 2× report_interval_min. Lower it to deliver threshold changes faster, at the cost of extra satellite or cellular data.
  6. Configure routes. Add one route for solar_alert.qo (to an on-call or NOC platform, since these are time-sensitive) and a second for solar_summary.qo (to a long-term analytics or SCADA store, where four-hour batches are fine). With the two Notefiles split at the source, each one can go to a different downstream system at a different urgency, and you don't need any server-side filtering.

Skylo (NOTE-NBGLWX) deployment guidance

Build the firmware with -DSKYLO_BUILD for the NOTE-NBGLWX (see §7.1). Notecard for Skylo ships set to wifi-cell and won't touch satellite until it's told to, so with that flag notecardFirstBoot() sends card.transport with method cell-ntn at power-up. From then on the Notecard uses cellular when it can reach a network and falls back to Skylo on its own. The satellite transport does differ from cellular in four big ways, though, and those differences should shape how you configure and commission the device.

Data budget. The NOTE-NBGLWX includes 10 KB of Skylo satellite data, a one-time allocation rather than a monthly one. Only Notefiles with a compact template (format: "compact" plus a port) can sync over satellite, so the firmware registers both of its Notefiles that way: solar_summary.qo on port 50 and solar_alert.qo on port 51. Neither template sets delete, so the same Notes go out over cellular or satellite, whichever link is up. The compact summary encodes to roughly 80–120 bytes on the satellite wire, well within the 256-byte Skylo payload ceiling. The firmware doesn't define an env.template, which Notecard needs before it can receive environment variables over satellite, so threshold and cadence changes reach the device on its next cellular session. Inbound syncs cost data too. Each satellite inbound ping costs about 50 bytes, even when no environment variable changes are pending. At the default 4-hour summary cadence (6 outbound sessions per day, and 3 inbound sessions per day at inbound = outbound × 2 = 8 h), a site running only on satellite would use up the included 10 KB in roughly 11 days. For satellite-primary deployments, keep report_interval_min at 1440 (once per day), the SKYLO_BUILD default, and don't override it in the Fleet Environment. Daily summaries use about 4–5 KB per month at normal alert rates, so the included 10 KB lasts about two months, and satellite data after that is billed per byte. At-threshold alerts (soc_low, temp_high, load_high, harvest_deficit) use sync:true and fire immediately no matter what the summary cadence is. They're the most valuable transmissions, and there aren't many of them. To cut inbound cost further, set the _sync_inbound_mins_ntn reserved environment variable so the Notecard checks in less often while it's on satellite. The satellite best practices guide has full data-budget guidance.

Alert latency. On LTE-M, sync:true alerts reach Notehub in seconds. On the satellite path the Notecard has to get a GPS fix (fast when the location is fixed; see below), wait for a satellite visibility window, and complete the NTN session. Expect end-to-end alert latency of 2–10 minutes on satellite. That's fine for the "site will go dark in the next few hours" scenario this project targets. Size your NOC response SLAs around it instead of expecting cellular-speed latency.

Satellite session power. A satellite session runs longer and uses more power than a cellular session. The Notecard has to power the GPS receiver, search for a satellite visibility window, and drive the NTN modem through a transmission. That can mean 2–5 minutes of elevated radio current, compared to the usual 15–30 seconds for LTE Cat-1 bis. With a daily outbound cadence, though, radio sessions drop from 6 per day (the LTE Cat-1 bis default) to 1, which mostly offsets the higher cost per session. Before field deployment, validate the energy profile on the bench with Mojo (§9) at the satellite cadence. The NOTE-NBGLWX power table and expected Mojo trace in §9 show what to look for, and the NOTE-NBGLWX datasheet has SKU-specific power curves.

Fixed GPS location. The NOTE-NBGLWX's satellite module has to know its location before it can register, and by default it gets its own GPS/GNSS fix during each registration. You don't have to configure a location, but on a stationary installation you can skip that fix by locking the position once at commissioning, from the blues.dev In-Browser Terminal or a bench serial session:

{"req": "card.location.mode", "mode": "fixed", "lat": 37.123, "lon": -121.456}

Replace lat/lon with the site's actual GPS-derived coordinates. Do not use tower_lat/tower_lon from _session.qo for this. Those fields are cell-tower–derived estimates, not GPS fixes, and they can be off by kilometers. To get accurate coordinates, make sure the GPS antenna is connected and has a clear view of the sky, then send {"req": "card.location"} from the same terminal. The lat and lon fields in the response carry the current GPS fix. Put those values in the card.location.mode fixed command above. Then tell the Notecard to use that fixed location for satellite sessions with ntn.gps, since by default the satellite module gets its own GPS fix:

{"req": "ntn.gps", "on": true}

A fixed location gets rid of repeated GPS cold-starts before each satellite session, which cuts both session startup time and power consumption.

Commissioning order. If there's any terrestrial cellular or WiFi coverage at or near the deployment site, power the unit up there first. The Notecard registers with Notehub over LTE-M, confirms all Note templates, and gets an accurate time reference, and each of those speeds up the first satellite session at the final remote location. If the site is beyond all cellular coverage, give the Notecard 5–15 minutes on first power-up to get a GPS fix and complete its initial satellite registration.

First-boot environment variables. At power-up the firmware calls hub.set to associate the Notecard with your Notehub project, then calls env.get on the first wake to pull any pre-configured Fleet environment variables. The catch is that env.get can't return Fleet-level values until the Notecard has completed an inbound sync with Notehub, and a cold-start device that has never synced doesn't have any cached env vars yet. On LTE Cat-1 bis the first inbound sync usually finishes within one to two minutes of power-on, so pre-configured Fleet variables apply from the second wake onward. At most, you're exposed for one 15-minute sample interval before the right env vars take effect.

That window matters more on NOTE-NBGLWX Skylo deployments, where a single wake at the 4-hour cellular default can eat a real share of the 10 KB satellite data budget. The -DSKYLO_BUILD flag covers this too (see §7.1 and the #ifdef SKYLO_BUILD block in firmware/solar_battery_controller/solar_battery_controller_notecard_helpers.h). It changes the compile-time default for DEFAULT_REPORT_INTERVAL_MIN to 1440 minutes, so even the very first wake, before any Notehub inbound sync, uses the satellite-safe daily cadence. Once the first inbound sync arrives, Fleet environment variables tune thresholds and cadence from there.

What you should see in Notehub

The Events tab in your project should start filling in within a minute of first power-on:

  • _session.qo: automatic Notecard housekeeping on each cellular session. It confirms the radio is reaching Notehub. If it never shows up, check the antenna connection and PRODUCT_UID.
  • solar_summary.qo: one per report_interval_min (every 4 hours by default), with every template field filled in (Notehub leaves out fields that are exactly 0; see §7.4). Here's a typical healthy-site body during the day, with solar contributing but load exceeding PV output. The MPPT is in Float state (cs: 5, battery at working SoC) while the battery supplements solar to cover the full site load:
    {
      "bat_v": 26.1,
      "bat_a": -3.2,
      "bat_w": -83.5,
      "soc_pct": 78.4,
      "bat_temp_c": 22.1,
      "pv_v": 34.8,
      "pv_w": 142.0,
      "load_w": 225.5,
      "yield_kwh": 1.24,
      "ttg_min": -1,
      "cs": 5
    }
    A negative bat_a means the battery is net discharging (load exceeds solar input). In this example solar is producing 142 W, the load is drawing 225.5 W, and the battery covers the difference (~83.5 W). cs of 5 is Float: the MPPT charge algorithm has reached its float stage, the battery is at working SoC, and solar is meeting its maintenance demand, but site loads still exceed current PV output. When ttg_min is −1, the SmartShunt is present but the battery isn't in an active discharge (TTG is inapplicable or infinite). A positive value such as "ttg_min": 312 means 5 h 12 minutes of estimated run time left. If no valid reading was available for a metric in the window, its field shows −9999 (float sentinel) or −1 (cs sentinel) instead of being left out. For example, "bat_temp_c": -9999.0 means no temperature sensor is connected to the SmartShunt.
  • solar_alert.qo: sent only on a threshold trip, with sync:true for immediate delivery. There are four alert types, and the context values vary by alert. Here's an example low-SoC alert:
    {
      "alert": "soc_low",
      "v1": 18.2,
      "v2": 24.1,
      "v3": -156.8
    }
    Fields: v1 = current SoC %, v2 = battery voltage V, v3 = net battery power W (negative = discharging). §8 has the complete alert taxonomy and what the context values mean for each alert type (temp_high, load_high, harvest_deficit).

7. Firmware Design

The firmware is split across six files:

  • firmware/solar_battery_controller/solar_battery_controller.ino is the main application sketch (orchestration, accumulation, alert evaluation).
  • firmware/solar_battery_controller/solar_battery_controller_helpers.h is the VE.Direct parser header.
  • firmware/solar_battery_controller/solar_battery_controller_helpers.cpp is the VE.Direct parser implementation.
  • firmware/solar_battery_controller/solar_battery_controller_notecard_helpers.h holds the AppState struct, all configuration constants, and the Notecard I/O declarations.
  • firmware/solar_battery_controller/solar_battery_controller_notecard_helpers.cpp implements the Notecard I/O (hub.set, templates, env vars, Note queuing).
  • firmware/solar_battery_controller/cx_sleep.h handles host sleep: the card.attn request, STOP2 entry, and ATTN-pin wake (cxSleepBegin, cxSleepUntilAttn).

7.1 Installing and flashing

Dependencies:

  • Arduino core for STM32, from stm32duino/Arduino_Core_STM32. Install it from the Arduino Boards Manager (search "STM32 MCU based boards"), or add the index URL https://github.com/stm32duino/BoardManagerFiles/raw/main/package_stmicroelectronics_index.json under File → Preferences → Additional Boards Manager URLs. The Notecarrier CX's host uses the Cygnet board definition (see the build settings below).
  • Blues Wireless Notecard library (note-arduino). Install it from the Arduino Library Manager (arduino-cli lib install "Blues Wireless Notecard"). Before you start a new project, check the note-arduino releases for the latest stable version.
  • STM32duino Low Power library (and its dependency STM32duino RTC), from stm32duino/STM32LowPower. Install it from the Library Manager. It provides the STOP2 entry and ATTN-pin wake used in cx_sleep.h.

Build settings: The Notecarrier CX's host is the same STM32L433 as the Blues Cygnet, so select Blues boards → Cygnet (pnum=CYGNET) and set USB support (if available) to None (usb=none). If the USB CDC stack is enabled and no USB host is attached (the Notecarrier CX USB-C switch in NC, or no cable), the STM32's USB wakeup interrupt fires continuously and the host never stays in STOP2. Debug output goes to the LPUART on the Notecarrier CX debug jack instead, which an ST-LINK V3 exposes as a virtual COM port (see Serial Logging With STLINK).

Flashing with the Arduino IDE: Open solar_battery_controller.ino, apply the build settings above, and click Upload. With the Notecarrier CX's DIP switch in HST, the host's DFU bootloader is reachable over the USB-C port (hold BOOT, tap RST). You can also upload over SWD with an ST-LINK on the debug jack.

Flashing with arduino-cli:

# Confirm the board name for your installed core version
arduino-cli board listall | grep -i cygnet

# Compile and upload (replace the board options and port to match your system;
# usb=none disables USB CDC, which must be off for the host to sleep)
arduino-cli compile -b STMicroelectronics:stm32:Blues:pnum=CYGNET,usb=none firmware/solar_battery_controller/
arduino-cli upload  -b STMicroelectronics:stm32:Blues:pnum=CYGNET,usb=none -p /dev/cu.usbmodem* firmware/solar_battery_controller/

NOTE-NBGLWX Skylo build flag: For the NOTE-NBGLWX, add -DSKYLO_BUILD to the compiler flags. It turns on the Skylo satellite fallback (card.transport with method cell-ntn) and makes a 1440-minute daily summary the default from the very first wake. Leave it off for the NOTE-MBGLW, which has no satellite radio. In the Arduino IDE the flag goes in File → Preferences → Additional build options. With arduino-cli, pass --build-property "compiler.cpp.extra_flags=-DSKYLO_BUILD". The Skylo deployment guidance in §6 explains why.

# Example: Skylo build
arduino-cli compile -b STMicroelectronics:stm32:Blues:pnum=CYGNET,usb=none \
  --build-property "compiler.cpp.extra_flags=-DSKYLO_BUILD" \
  firmware/solar_battery_controller/

After uploading, open a serial monitor at 115200 baud on the ST-LINK virtual COM port. You'll see the Notecard configuration log at power-up. After that, each 15-minute wake prints a one-line [summary] or [alert] entry, and then the output goes quiet as the host goes back into STOP2.

7.2 Modules

ResponsibilityWhere
Notecard configuration (hub.set, templates, accelerometer disable)notecardFirstBoot, applyHubSetIfChanged, defineTemplates
Environment variable fetch and threshold refreshfetchEnvOverrides
VE.Direct frame reading (two devices)readVEDirectFrame in helpers
Sample accumulation into rolling window averagesaccumulate
Alert evaluation and immediate-sync NotescheckAndSendAlerts, checkHarvestDeficit, sendAlert
Summary Note construction and emissionsendSummary
Host sleep (STOP2 until the Notecard raises ATTN)cx_sleep.h: cxSleepBegin, cxSleepUntilAttn
Application state (held in RAM across sleep cycles)AppState struct

7.3 VE.Direct reading strategy

VE.Direct protocol primer. Victron's VE.Direct is a one-wire async text protocol at 19200 baud 8N1. The device broadcasts one frame per second without any polling or handshake. Each frame is a series of LABEL<tab>VALUE<CR><LF> lines ending with a Checksum<tab><byte><CR><LF> line. The checksum byte makes the sum of all bytes in the frame (including the Checksum line itself) come out to zero modulo 256. On a healthy bus you get 3–4 complete frames in a 3-second window.

readVEDirectFrame() in the helpers file reads characters from the given Stream object until it sees a complete, checksum-verified frame or the timeout runs out (3 seconds by default). It pulls out only the fields that matter for the SmartShunt or MPPT and quietly ignores every other label. The SmartShunt uses the Notecarrier CX's UART (Serial1), and a SoftwareSerial instance on D9 handles the MPPT. The host reads them one after the other at wakeup.

Why not poll both simultaneously? VE.Direct is a one-way broadcast, and both devices are always transmitting, so the firmware just listens to one at a time. Reading them in sequence adds ~6 seconds of active time per wake cycle, which is negligible next to a 15-minute sleep interval.

7.4 Event payload design

solar_summary.qo uses a compact Note template on port 50, registered on the first wake. Notecard stores templated Notes as fixed-length records instead of free-form JSON, which cuts wire size by roughly 3–5×. That matters on a system that will run for years on a prepaid cellular SIM. Every field in the template schema is always present in every Note body, with explicit sentinel values when no valid samples were collected for that metric in the window:

  • SUMMARY_SENTINEL_F = −9999.0: all float fields (bat_v, bat_a, bat_w, soc_pct, bat_temp_c, pv_v, pv_w, yield_kwh, load_w).
  • SUMMARY_SENTINEL_TTG = −9999: ttg_min when no SmartShunt data arrived in the window. A value of −1 (separate from the sentinel) means the SmartShunt was present but the battery wasn't in an active discharge (TTG is inapplicable or infinite). Any value ≥ 0 is the active discharge estimate in minutes.
  • SUMMARY_SENTINEL_CS = −1: cs when no MPPT data arrived. Every valid VE.Direct CS code for the SmartSolar MPPT is non-negative (0, 2–5, 7, 245, 247, 252; see the VED_CS_* constants in solar_battery_controller_helpers.h), so −1 clearly means no MPPT data and can't collide with a real charge-state value.

This fixed schema follows the pattern Blues reference designs already use. The firmware fills every field, and the sentinels are nonzero on purpose. Templated Notefiles drop any field whose value is exactly 0 on the way to Notehub (omitempty), so a field missing from a summary is a real zero (such as pv_w or cs at night), and a sensor fault always shows up as a sentinel instead. At the start of each summary window, ttg_min resets to −1 (resetAccumulators sets state.ttg_min = -1) so a stale value from the previous window can't leak forward. If a whole window produces no valid samples from either device, sendSummary skips the Note entirely and opens a new window, and no all-sentinel record goes out.

solar_alert.qo has its own compact template on port 51, so alerts can go out over satellite too (see the Skylo guidance in §6). All four alert types share one schema. The alert field names the failure condition, and v1, v2, v3 carry context values whose meaning depends on the alert type:

Alertv1v2v3
soc_lowcurrent SoC %battery voltage Vnet battery power W
temp_highbattery temp °Cbattery voltage VSoC %
load_highload draw WSoC %PV power W
harvest_deficitavg SoC % over windowconsecutive windows without Float/Absorbtoday's yield kWh

The same omitempty rule applies here, so a context value of exactly 0 is missing from the alert in Notehub. For example, a load_high at night arrives without v3, because PV power is 0.

7.5 Low-power strategy

The site runs on batteries, so the power budget matters. The host MCU spends almost all of its time in STM32 STOP2, drawing a few microamps with its RAM intact. After each wake cycle, loop() calls cxSleepUntilAttn() (in cx_sleep.h), which sends a card.attn sleep request for sample_interval_sec seconds, waits for the Notecard to pull ATTN low, and enters STOP2. When the ATTN timer fires 15 minutes later (by default), the Notecard raises ATTN, the rising edge on D5 wakes the host, and execution picks up on the next line of loop(). The host resumes in place instead of cold-booting, so the AppState struct just lives in RAM. Nothing gets serialized to the Notecard, and setup() runs only once per power-up. The firmware opens the two VE.Direct UARTs only for the read and closes them again before sleeping. Otherwise SoftwareSerial's pin-change interrupt on D9 would wake the host every time the MPPT broadcast a frame. If the sleep request fails or ATTN never goes low (missing jumper), loop() waits out the interval awake and tries again on the next cycle, so the sample cadence holds either way.

Notecard's own idle draw is ~8–18 µA between cellular sessions (per the Blues low-power design guide). Add the host and board quiescent, and Mojo at the +VBAT rail sees about 16–44 µA between wakes. The site's battery bus carries two more standing loads that Mojo can't see. Each voltage-divider network (10 kΩ + 20 kΩ = 30 kΩ total) pulls ~167 µA continuously from its Victron device's TX line, so the two dividers add ~334 µA, drawn through the Victron devices instead of through +VBAT. The Traco TSR 1 DC-DC converter draws about 1–2 mA at its VIN input from the battery bus, which is upstream of the 5 V output rail. To measure the total battery-bus draw, you need a separate instrument (for example, a clamp meter or series shunt) on the converter's VIN supply wire. §9 has the complete system current table. The Notecard's outbound sync cadence (4 hours by default) controls when it wakes the radio. With 15-minute sample intervals and a 4-hour transmit cadence, the radio fires 6 times per day instead of 96.

Sampling and transmission run on separate cadences on purpose. The device samples every 15 minutes but only opens a cellular session every 4 hours. Alert Notes get around this with sync:true, which tells the Notecard to open a session as soon as an alert is queued instead of waiting for the next 4-hour window.

7.6 Retry and error handling

  • readVEDirectFrame enforces a 3-second per-device timeout. If a device doesn't respond (powered off, disconnected, connector fault), the function returns false and accumulate skips the matching fields. Metrics that come only from the missing device end up with 0 valid samples for that window, and sendSummary emits the sentinel value (−9999 for floats, −1 for cs) for those fields. If SmartShunt data drops out for a whole window, you'll see −9999 on bat_v, soc_pct, and the rest in solar_summary.qo, which makes the sensor fault obvious in Notehub.
  • #define ALERT_COOLDOWN_SAMPLES 2 (~30 minutes at the default 15-min interval) controls how often a persistent fault re-alerts. Each alert fires immediately on the first trip, then repeats every 2 wakes (~30 minutes) while the condition holds. When the condition clears, the active flag and cooldown counter both reset, so the next trip fires immediately again. One alert every ~30 minutes per condition keeps on-call informed without burning through the satellite data quota.
  • A failed summary send doesn't open a new window. When note.add for solar_summary.qo fails, samples_until_summary stays at 0 (it isn't reset). On the next wake the firmware retries the send before reading new VE.Direct data, so the closed window's averages never get mixed with fresh readings. resetAccumulators() and the window-open only happen once the Note is confirmed queued.
  • The environment variable clamp logic in fetchEnvOverrides rejects sample_interval_sec values outside 60–3600 and report_interval_min values outside 15–1440, so an operator typo can't make the device unreachable.

7.7 Key code snippet 1: Note template registration

Templates turn each Note into a fixed-length on-wire record, and the placeholder values set the wire type: 14.1 = 4-byte IEEE-754 float, 14 = 4-byte signed integer, 12 = 2-byte signed integer, and a non-numeric string such as "-" = variable-length string. format: "compact" plus a port on each template is what lets both Notefiles sync over Skylo NTN. defineTemplates() sends each request through a small sendTemplate() helper that checks the response for an err field, and it returns true only when both templates register.

J *req = notecard.newRequest("note.template");
if (req) {
    JAddStringToObject(req, "file", "solar_summary.qo");
    JAddNumberToObject(req, "port", 50);
    JAddStringToObject(req, "format", "compact");
    J *body = JAddObjectToObject(req, "body");
    JAddNumberToObject(body, "bat_v",      14.1);  // 4-byte float
    JAddNumberToObject(body, "bat_a",      14.1);
    JAddNumberToObject(body, "bat_w",      14.1);
    JAddNumberToObject(body, "soc_pct",    14.1);
    JAddNumberToObject(body, "bat_temp_c", 14.1);
    JAddNumberToObject(body, "pv_v",       14.1);
    JAddNumberToObject(body, "pv_w",       14.1);
    JAddNumberToObject(body, "yield_kwh",  14.1);
    JAddNumberToObject(body, "load_w",     14.1);
    JAddNumberToObject(body, "ttg_min",    14);    // 4-byte int
    JAddNumberToObject(body, "cs",         12);    // 2-byte int
}
bool ok = sendTemplate(req, "solar_summary.qo");

req = notecard.newRequest("note.template");
if (req) {
    JAddStringToObject(req, "file", "solar_alert.qo");
    JAddNumberToObject(req, "port", 51);
    JAddStringToObject(req, "format", "compact");
    J *body = JAddObjectToObject(req, "body");
    JAddStringToObject(body, "alert", "-");        // variable-length string
    JAddNumberToObject(body, "v1",    14.1);
    JAddNumberToObject(body, "v2",    14.1);
    JAddNumberToObject(body, "v3",    14.1);
}
ok = sendTemplate(req, "solar_alert.qo") && ok;
return ok;

7.8 Key code snippet 2: immediate-sync alert

sync:true tells the Notecard to skip waiting for the next outbound window and open a session right away, over Skylo if a SKYLO_BUILD device can't reach cellular. checkAndSendAlerts() in the sketch calls sendAlert() with the alert name and its three context values, for example sendAlert("soc_low", shunt.soc_pct, shunt.bat_v, shunt.bat_w). It arms the alert's cooldown only when sendAlert() returns true.

bool sendAlert(const char *alert, float v1, float v2, float v3) {
    J *req = notecard.newRequest("note.add");
    if (req) {
        JAddStringToObject(req, "file",  "solar_alert.qo");
        JAddBoolToObject(req,   "sync",  true);
        J *body = JAddObjectToObject(req, "body");
        JAddStringToObject(body, "alert", alert);
        JAddNumberToObject(body, "v1",    v1);
        JAddNumberToObject(body, "v2",    v2);
        JAddNumberToObject(body, "v3",    v3);

        J *rsp = notecard.requestAndResponse(req);
        if (!rsp) {
            dbgSerial.println(F("[warn] note.add alert: no response"));
        } else {
            const char *err = JGetString(rsp, "err");
            bool ok = (!err || !*err);
            if (!ok) {
                dbgSerial.print(F("[warn] note.add alert error: "));
                dbgSerial.println(err);  // log before deleteResponse
            }
            notecard.deleteResponse(rsp);
            if (ok) {
                dbgSerial.print(F("[alert] ")); dbgSerial.println(alert);
                return true;
            }
        }
    }
    dbgSerial.print(F("[error] note.add failed for alert: ")); dbgSerial.println(alert);
    return false;
}

7.9 Key code snippet 3: sleep between samples

cxSleepUntilAttn() asks the Notecard to hold ATTN low for exactly state.sample_interval_sec seconds, confirms the pin has gone low, and puts the STM32 into STOP2. Because the host stays alive, the firmware checks the result of the card.attn request instead of treating it as fire-and-forget. When the Notecard raises ATTN, loop() runs again with state exactly as it was left. setup() arms the wake once with cxSleepBegin(), and the end of loop() sleeps:

// Arm the ATTN wake: the Notecard raises ATTN (D5) to end each sleep.
    cxSleepBegin();
// ...
    state.wake_count++;

    // -------------------------------------------------------------------------
    // Sleep in STOP2 until the Notecard raises ATTN at the next sample interval
    // -------------------------------------------------------------------------
    if (!cxSleepUntilAttn(notecard, state.sample_interval_sec, NULL, &dbgSerial)) {
        // The Notecard didn't take the sleep request, or ATTN never went low
        // (check the ATTN -> D5 jumper).  Keep the sample cadence and try again.
        dbgSerial.println(F("[sleep] ATTN sleep failed — waiting out the interval awake"));
        delay(state.sample_interval_sec * 1000UL);
    }

7.10 Key code snippet 4: VE.Direct load computation

The firmware works out load draw algebraically instead of measuring it directly: load is solar input minus net battery power. bat_w is positive when the battery is charging (solar exceeding load) and negative when it's discharging (load exceeding solar). With pv_w = 300 and bat_w = +100, the load is 200 W, and solar is charging the battery while it powers the load. With pv_w = 100 and bat_w = −150, the load is 250 W, and solar and battery are both powering it. accumulate() only averages non-negative results into the window:

// Derived: load_w = pv_w − bat_w  (positive bat_w = charging = less load)
if (shunt.valid && mppt.valid) {
    float load = mppt.pv_w - shunt.bat_w;
    if (load >= 0.0f) {
        state.load_w_sum += load; state.load_w_cnt++;
    }
}

8. Data Flow

Data flow: 15-min sample of battery V/I/SoC, PV W, charge controller mode → four edge rules (soc_low, temp_high, load_high, harvest_deficit) → solar_alert.qo (sync:true on threshold trip) and solar_summary.qo (4-hr templated) → Notehub

Every 15 minutes (default sample_interval_sec = 900) the firmware wakes, reads both VE.Direct devices, and adds one data point to the current summary window:

  • Collected per sample. Battery voltage (V), battery current (A, signed), net battery power (W, signed), SoC (%), battery temperature (°C, when sensor present), panel voltage (V), panel power (W), and computed load (W). Only the latest value is kept for daily yield (kWh), charge state (integer enum), and time-to-go (minutes).
  • Transmitted.
    • solar_summary.qo: once per report_interval_min (every 4 hours by default, which is 16 samples per window). The firmware fills every template field. Each averaged field carries the mean of its valid samples, or −9999 when that metric has no valid samples. yield_kwh and cs carry the latest value at window close, not an average, and cs is −1 when no MPPT data arrived. ttg_min is −9999 when no SmartShunt data arrived, −1 when the shunt was present but the battery wasn't discharging, and otherwise the active minutes-remaining estimate. If a whole window has no valid samples from either device, the Note is skipped entirely.
    • solar_alert.qo: sent immediately on a threshold trip with sync:true. A ~30-minute per-alert cooldown deduplicates it.
  • Routed. Notehub forwards solar_alert.qo to real-time notification destinations (NOC dashboards, on-call paging, SMS gateway) and solar_summary.qo to analytics or long-term storage. With separate Notefiles, each one gets its own route, urgency tier, and retention policy.
  • Alert triggers.
    • soc_low: SoC below soc_alert_pct. Fires once per ~30 minutes while SoC stays low. Context: current SoC, battery voltage, net battery power.
    • temp_high: battery temperature above bat_temp_max_c (only when a temperature sensor is wired to the SmartShunt). Context: current temp, battery voltage, SoC.
    • load_high: computed load draw above load_alert_w. Fires once per ~30 minutes. Context: load W, SoC, current PV power.
    • harvest_deficit: the MPPT hasn't reached a full-charge state (Float cs=5, Absorption cs=4, Equalize cs=7, or Auto Equalize cs=247) for harvest_deficit_days consecutive days. It's disabled by default, and you turn it on with the env var. Fires once per ~24 hours while the deficit persists. Context: average SoC % over the window, number of consecutive windows without a full-charge state, and today's accumulated solar yield kWh. This alert is the main sign of a recharge imbalance, whether that's a degraded array, persistent cloud cover, or load growth the system can no longer recover from overnight. The firmware doesn't count Equalize/Recondition as a deficit. Those cycles run after the battery has already reached Float, so seeing one confirms a healthy charge cycle finished.

9. Validation and Testing

Expected steady-state behavior. A healthy site with solar in bulk or float mode sends one solar_summary.qo every 4 hours and no solar_alert.qo events. During the day bat_a will be slightly positive (the battery is taking a maintenance trickle), and at night it'll be slightly negative (the load is drawing from the battery). On a well-sized system, soc_pct should be above 80% by mid-morning.

Using Mojo to validate the power profile. Notecard's published standalone idle draw is ~8–18 µA @ 5V, and the low-power firmware design guide has the authoritative figures by SKU. The assembled monitor draws a little more at its +VBAT input, from the host and board quiescent. The voltage dividers and the DC-DC converter add more standing current on the battery bus, but none of it passes through +VBAT, so Mojo doesn't see it. The table below breaks out both figures so the Mojo trace makes sense:

NOTE-MBGLW path (LTE Cat-1 bis cellular)

PhaseNotecard-onlyMeasured by Mojo at +VBAT
Between wakes (host in STOP2, waiting for ATTN)~8–18 µA (+ ~1–2 µA host)~16–44 µA (Notecard ~8–18 µA + host ~1–2 µA + board quiescent; the dividers' ~334 µA comes from the Victron TX lines, not +VBAT)
Host active (VE.Direct reads + I²C)N/A~6–8 mA for ~6–8 seconds per wake
Notecard cellular session (LTE Cat-1 bis outbound)~250 mA avg, ≤2 A peak, for ~15–30 secondsSame, since the radio dominates

Mojo measurement boundary. Mojo sits between the Traco 5 V output and the Notecarrier CX +VBAT pin, so it only measures current on the 5 V output rail. The Traco TSR 1's no-load input quiescent (~1–2 mA) comes from the battery bus at the converter's VIN pin, upstream of the 5 V rail, where Mojo can't see it. To measure total battery-bus draw including converter quiescent, put a separate shunt or clamp meter on the converter's VIN supply wire.

The two always-energized 10 kΩ/20 kΩ divider networks (~5 V ÷ 30 kΩ = 167 µA each, × 2 = ~334 µA) don't show up in the Mojo trace, because the Victron TX lines drive them, not the +VBAT rail. They're still a standing load on the site's battery, by way of the Victron devices. If you need to lower the site's total standing draw, go after the dividers and the converter quiescent. Switching to a level-shifting stage that powers off would remove ~334 µA.

Here's a rough 24-hour Mojo estimate at default settings (6 cellular sessions/day, 96 host wakes/day). Six LTE Cat-1 bis radio sessions dominate at roughly 8–10 mAh, 96 host wakes add roughly 1 mAh, and the idle floor adds about 0.4–1 mAh (16–44 µA × 24 h). That's about 10–12 mAh per 24 hours measured by Mojo at the +VBAT rail. (Mojo doesn't measure the battery-bus draw from the voltage dividers, ~334 µA × 24 h ≈ 8 mAh, or from converter input quiescent, ~1–2 mA × 24 h ≈ 24–48 mAh at the battery bus voltage.) Check it once with Mojo at bench bring-up. If the daily Mojo total is more than 3× above ~12 mAh, usually either the host isn't sleeping (a continuous multi-mA rise above the ~44 µA floor) or the radio is struggling with marginal signal (correctly spaced sessions that each run 60+ seconds).

To use Mojo, splice it inline between the DC-DC converter's 5 V output and the Notecarrier CX +VBAT pin, connect its Qwiic output to the Notecarrier CX I²C header, and leave the unit running for 24 hours. A healthy LTE Cat-1 bis trace shows a flat ~16–44 µA baseline (the +VBAT system idle floor), brief blips to ~6–8 mA every 15 minutes (host wake), and one 15–30-second burst to ~250 mA every 4 hours (cellular session). If the trace sits above the ~44 µA floor and the rise doesn't line up with a host wake or radio burst, there's an unexpected active current path somewhere.

NOTE-NBGLWX path (Skylo NTN satellite + LTE-M)

The satellite path's power profile looks very different from cellular. A satellite session needs the GNSS receiver to confirm the device's position, and then it drives the NTN modem through an uplink/downlink sequence that usually runs 2–5 minutes, compared to 15–30 seconds for an LTE Cat-1 bis session. Each session takes longer and uses more energy, but the recommended satellite cadence is once a day instead of six sessions a day on LTE Cat-1 bis. Those six LTE Cat-1 bis sessions use roughly 8 mAh/day combined, and the single daily satellite session is estimated at ~3–12 mAh (the phase table has the per-phase breakdown). Either way, the continuous ~1–3 mA standing current floor dominates the whole-system daily budget. The same voltage dividers and DC-DC converter quiescent are on both paths, so the daily total is comparable to the LTE Cat-1 bis path, not a lot lower. The breakdown below covers only what Mojo sees at +VBAT, where the satellite session is the biggest item.

PhaseMeasured by Mojo at +VBAT
Between sessions (host in STOP2, waiting for ATTN)~16–44 µA (Notecard + host ~1–2 µA + board quiescent; the dividers' ~334 µA and the converter's ~1–2 mA input quiescent are on the battery bus where Mojo can't see them, the same boundary as the LTE Cat-1 bis path)
Host active (VE.Direct reads + I²C)~6–8 mA for ~6–8 seconds per wake (96 wakes/day)
GPS acquisition with fixed location (warm fix)~30 mA for ~60 seconds ≈ 0.5 mAh
NTN satellite session (transmit + receive)~250–500 mA peak during the NTN data burst (~20–45 seconds), then ~30–80 mA average over the rest of the session overhead, for 2–5 minutes total. Estimated at ~3–12 mAh/session (validate with Mojo).
LTE-M cellular fallback (when available)Similar profile to the NOTE-MBGLW above

Per-day Mojo energy estimate at the recommended daily satellite cadence (1 NTN session/day, report_interval_min = 1440):

  • Notecard + board quiescent (24 h × ~16–44 µA): ~0.4–1 mAh
  • 96 host wakes (~7 mA × ~7 seconds each): ~1.3 mAh
  • GPS fix (warm, fixed location): ~0.5 mAh
  • 1 NTN satellite session: ~3–12 mAh (a peak 250–500 mA burst for ~20–45 seconds plus session overhead; see the phase table)

The satellite session dominates, and the total comes to about 5–15 mAh/day measured by Mojo at the +VBAT rail. Mojo can't see the dividers (~334 µA × 24 h ≈ 8 mAh) or the converter's input-side quiescent (~1–2 mA at the battery bus for 24 h), which are additional battery-bus draw. Those two standing loads are the main levers for total battery-bus draw. For reference, Blues published data shows Starnote for Skylo at a 60-minute NTN sync cadence using ~27 mAh over 12 hours. That's a full-system measurement that includes all the idle time between sessions at that product's quiescent draw, not an isolated per-session energy figure. The NOTE-NBGLWX uses a similar NTN radio subsystem, so treat this as an order-of-magnitude sanity check, and validate the actual per-session energy with Mojo on the bench before field deployment. The NOTE-NBGLWX datasheet has SKU-specific power curves.

A healthy Skylo trace on Mojo shows a flat ~16–44 µA baseline (the +VBAT system idle floor) and brief blips to ~6–8 mA every 15 minutes (host wake). Once a day there's a multi-stage radio burst. First comes a ~30 mA plateau for ~60 seconds (GPS fix), then a 2–5 minute NTN session. Inside that session, a peak ~250–500 mA transmit burst (~20–45 seconds) shows up as a sharp spike within a lower-current envelope (~30–80 mA for the rest of the session). If the GPS acquisition stage takes minutes instead of seconds, the device probably isn't in fixed-location mode (see the Skylo deployment guidance above).

Mojo is a bench bring-up tool, not a production sensor. Once a firmware revision passes the trace check, deployed units don't need it.

A Notecarrier CX and Notecard, paired with two Victron VE.Direct devices, turn an off-grid power system nobody could see into one you can watch from anywhere. The device wakes every 15 minutes, reads battery SoC, current, temperature, and solar harvest in a few seconds, and goes back to sleep, building up a window of averages that goes to Notehub every four hours. A site that's going dark sends a warning Note over cellular before the battery drops too far to communicate, and that gives the operations team a fighting chance to dispatch before the outage. None of this needs site networking, IT coordination, or any infrastructure that wasn't already there. All it takes is a 5V supply and two short VE.Direct cables.

For truly remote sites where terrestrial cellular coverage is marginal or absent, Notecard for Skylo (NOTE-NBGLWX) drops into the same M.2 carrier and uses the same API as Notecard Cell+WiFi. Built with -DSKYLO_BUILD, it falls back to satellite on its own, and the compact templates carry both summaries and alerts over that link (see §6). The Skylo variant also needs both antennas mounted outdoors with an unobstructed view of the sky, as covered in §4 and §5. The same pattern works for a single off-grid cabin or a fleet of hundreds of remote towers, and Notehub handles per-fleet thresholds and per-device overrides.

10. Troubleshooting and Common Issues

SymptomLikely CauseWhat to Check
No solar_summary.qo or _session.qo Notes appear in Notehub after first power-onProductUID is unset, the antenna is disconnected, or the Notecard has no network coverageConfirm that the PRODUCT_UID macro in solar_battery_controller.ino matches the value on your Notehub project page. Check that the cellular antenna is connected to the MAIN u.FL pigtail (NOTE-MBGLW), or that both the satellite and GPS antennas are mounted outdoors with a clear view of the sky (NOTE-NBGLWX). Open the blues.dev In-Browser Terminal and send {"req":"hub.status"} to see the current connection state.
solar_summary.qo Notes arrive but every metric is -9999 (the float sentinel)VE.Direct frames aren't parsing because of a wiring fault, the wrong pin, or a checksum mismatchCheck the resistor divider on each RX line (10 kohm in series, 20 kohm to GND), and confirm the divider midpoint reaches the right CX header pin (RX for the SmartShunt, D9 for the MPPT). Use a multimeter to confirm that pin 1 on the JST PH connector is GND and pin 3 is the device TX, since wire colors aren't standardized. Watch the serial monitor at 115200 baud for the per-cycle [summary] line. A parse failure prints a checksum or timeout warning.
soc_pct reads implausibly high or low (for example, stuck at 100 percent or jumping by tens of percent between samples)The SmartShunt's internal SoC calibration hasn't converged yet, or the shunt configuration doesn't match the installed battery bankUse the Victron VictronConnect mobile app to confirm that the SmartShunt's battery capacity (Ah), charged voltage, and Peukert exponent match the installed bank. Give SoC at least one full charge cycle to converge after the shunt is first installed. The shunt computes SoC itself, and the firmware only reports the value broadcast on the wire.
pv_w and yield_kwh show zero throughout the daytime hoursThe MPPT has no PV input, the MPPT VE.Direct cable isn't connected, or the SoftwareSerial RX pin is miswiredMake sure the solar array breaker is closed and the MPPT reports nonzero panel voltage in VictronConnect. Confirm that the MPPT VE.Direct cable lands on CX header D9 through its own divider, not on the SmartShunt UART (Serial1). A persistent cs value of -1 in summary Notes confirms no MPPT frames were parsed in the window.
Cellular session repeatedly fails (_session.qo shows non-zero voltage errors or failure_reason populated)Marginal LTE Cat-1 bis signal at the install location, antenna placement inside a metallic enclosure, or insufficient peak current at the +VBAT railCheck the bars and rssi fields in _session.qo. For metallic enclosures, route the antenna outside the enclosure on a u.FL-to-SMA pigtail with an IP67 bulkhead. Confirm that the DC-DC converter can supply the 750 mA sustained draw the Notecard datasheet calls for (the TSR 1's 1 A rating covers it), and that the input fuse is sized to match.
Skylo satellite session times out, or no NTN session ever completesGPS fix unavailable or antenna obstructedConfirm that both the satellite and GNSS antennas are mounted outdoors, since satellite signals don't get through a metal enclosure. If you like, send {"req":"card.location.mode","mode":"fixed","lat":...,"lon":...} and then {"req":"ntn.gps","on":true} once at commissioning so the satellite module skips its own GPS fix each session. Allow 5 to 15 minutes on first power-on for the initial NTN registration. The satellite best practices guide has more.
Intermittent VE.Direct parse failures (some windows produce sentinels, others succeed)Long cable runs, ground loops between the battery shunt and the Notecarrier, or excessive noise coupling onto the RX lineKeep VE.Direct cables under the supplied 1.8 meter length, don't run them parallel to high-current DC bus bars, and connect the battery-bus return to signal ground at one point only (not both at the converter and at the shunt). If runs can't be shortened, add a small ferrite bead on the RX line near the Notecarrier CX header.
Device goes silent after several days, never reappears in NotehubBattery bank discharged below the converter's input cutoff, or the input fuse openedThe Traco TSR 1-2450 holds regulation down to 6.5 volts input, and the TSR 1-4850 down to 18 volts. Below that cutoff, the 5 volt rail collapses and the Notecarrier CX powers off. Once solar brings the bus back up, the device cold-starts and rejoins. Inspect the inline fuse and check that the battery bus is above the converter's minimum input. The last _session.qo timestamp tells you when the radio went silent.

If you hit an issue that isn't covered above, the Blues community forum is the fastest way to a working answer. Blues engineers and other field deployers both keep an eye on it.

11. Limitations and Next Steps

This reference design stays narrow on purpose. It answers one site-uptime question, "is this off-grid site healthy enough that I don't need to send a truck?", and leaves the harder problems (cell-level diagnostics, MPPT control, multi-bank aggregation) to companion designs where they belong.

Simplified for the POC

Each of these was a deliberate scope choice. Together they mark where this site-uptime monitor stops and where a companion design or firmware extension would pick up.

Read-only over VE.Direct. The firmware listens to the broadcast text protocol but never writes a command back to either Victron device. You set SmartShunt or MPPT parameters (battery capacity, absorption voltage, equalization schedule) with the Victron VictronConnect app at commissioning, not from Notehub. Write support would mean handling the proprietary VE.Direct HEX framing on top of the text protocol covered here.

Single battery bank, single MPPT. The reference design assumes one SmartShunt watching one battery bank and one SmartSolar MPPT watching one solar array. Sites with parallel banks or multiple charge controllers need either a Victron Cerbo GX (which aggregates over Victron's CAN bus) or a firmware extension that opens more VE.Direct serial ports on the Cygnet host.

No MPPT control. The firmware reports what the MPPT is doing, but it can't command a different absorption voltage, force a manual equalize, or turn off charging. Remote control of the charge algorithm is a different kind of problem with safety implications, and it's out of scope on purpose.

Bank-level signal scope only. Per-cell voltages and cell imbalance aren't on the VE.Direct wire. As §1 points out, they travel over CAN bus and need dedicated hardware. This design targets bank-level uptime failure modes and leaves cell-level telemetry to a companion design.

Sensor selection is fixed at the firmware level. The two VE.Direct ports are hard-coded to a SmartShunt on Serial1 and an MPPT on the SoftwareSerial pin. To swap in a different VE.Direct device family (such as a Phoenix inverter), you'd have to extend the parser to recognize its label set.

No local sample history. The host keeps the current summary window in the AppState struct in RAM between sleeps (STOP2 retains RAM), but a power interruption or reset wipes the in-progress accumulator. Long-term storage lives in Notehub, not on the device.

Production Next Steps

Getting this monitor ready for a production fleet means handling multi-bank sites, adding local alerting and field provisioning, and supporting over-the-air updates for devices that may go years between physical visits. These extensions are the natural next moves.

Multi-bank support. Open more SoftwareSerial ports on free host GPIO pins and parse a second SmartShunt and MPPT pair. Add a per-bank index to each Note so downstream analytics can attribute readings to the right bank.

Alarm output relays. Drive a host GPIO to a small SSR or low-side switch, so a harvest_deficit or soc_low alert can trigger a local audible or visual indicator at the site for technicians on the ground.

Weather-data overlay. Route solar_summary.qo events into a server-side process that joins each window's yield_kwh against a weather feed (cloud cover, GHI) for the site coordinates. A real recharge-deficit problem looks very different from a stretch of cloudy weather, and the overlay tells the two apart automatically.

BLE-based field provisioning. A Bluetooth provisioning page on the Notecarrier CX would let a technician set the site identifier, fixed GPS coordinates, and initial thresholds from a phone in the field, without a laptop or USB cable. Notecard supports BLE on supported SKUs.

Notecard Outboard DFU for fleet firmware updates. Notecard Outboard DFU lets Notehub push a new STM32 firmware image to every device in a fleet over the air. A device that may sit on a remote tower or backcountry array for years between physical visits really needs that.

Multi-phase or DC-coupled load shunting. Sites that include AC inverter loads (Victron MultiPlus or similar) can add a Quattro or MultiPlus VE.Bus integration to capture inverter input, output, and inverter-internal alarms. That gives a fuller picture of total site load than the algebraic pv_w - bat_w computation used here.

12. Summary

The on-call engineer responsible for a remote tower or backcountry array now gets days of warning, which they never had before. A harvest_deficit Note arrives in the NOC with enough context (consecutive deficit windows, average SoC, today's yield) to send a truck before the bank empties, not after the site has already gone dark. The Notecarrier CX, one Notecard, and the two Victron VE.Direct devices already on the wall do all the work. The same firmware reaches Notehub over cellular where it can, and on a Notecard for Skylo built with -DSKYLO_BUILD, over Skylo NTN satellite where it can't.

The same pattern works anywhere a VE.Direct device, a Modbus-RTU sensor, or a simple analog signal has to reach the cloud from a site with no IT infrastructure of its own.

On This Page

  • 1. Project Overview
  • 2. System Architecture
  • 3. Technical Summary
  • 4. Hardware Requirements
  • 5. Wiring and Assembly
    • VE.Direct cable pinout
    • Pin-by-pin wiring
  • 6. Notehub Setup
    • Skylo (NOTE-NBGLWX) deployment guidance
    • What you should see in Notehub
  • 7. Firmware Design
    • 7.1 Installing and flashing
    • 7.2 Modules
    • 7.3 VE.Direct reading strategy
    • 7.4 Event payload design
    • 7.5 Low-power strategy
    • 7.6 Retry and error handling
    • 7.7 Key code snippet 1: Note template registration
    • 7.8 Key code snippet 2: immediate-sync alert
    • 7.9 Key code snippet 3: sleep between samples
    • 7.10 Key code snippet 4: VE.Direct load computation
  • 8. Data Flow
  • 9. Validation and Testing
    • NOTE-MBGLW path (LTE Cat-1 bis cellular)
    • NOTE-NBGLWX path (Skylo NTN satellite + LTE-M)
  • 10. Troubleshooting and Common Issues
  • 11. Limitations and Next Steps
    • Simplified for the POC
    • Production Next Steps
  • 12. Summary

Resources

  • GitHub
  • Request More Info
Can we improve this page? Send us feedback
© 2026 Blues Inc.
© 2026 Blues Inc.
AboutDocsAPI ReferenceTermsPrivacy