Record SolarXiao charger control and LoRa acceptance [skip ci]
This commit is contained in:
1 parent
70b8db9388
commit
204fc5265b
5 files changed
+1324
No files matched your search
@@ -96,6 +96,9 @@ A failed/corrupt settings file blocks configuration saves and automatic replay.
|
||||
These actions acknowledge the request, not completion of charging, resistance
|
||||
measurement or entry into ship mode. Read the corresponding status afterward.
|
||||
|
||||
See the [bench acceptance report](test-results/solarxiao_power_2026-10-21.md)
|
||||
for exact firmware identity, measured readouts and test limits.
|
||||
|
||||
## Conversion defaults and carrier revisions
|
||||
|
||||
The supplied [Standard schematic](https://github.com/wehooper4/Meshtastic-Hardware/blob/main/XaioSeries/SolarXaio/MPPT/Standard/Schematic_SolarXaio_MPPT_2025-10-23.pdf)
|
||||
|
||||
@@ -11,6 +11,8 @@ Completed validation reports, experiments, and test guides are collected here. O
|
||||
|
||||
## Radio and RF
|
||||
|
||||
- [SolarXiao native MPPT and battery controls, 2026-10-21](solarxiao_power_2026-10-21.md)
|
||||
|
||||
- [SolarXiao INA260 battery telemetry and carrier I2C fix, 2026-10-21](solarxiao_ina260_2026-10-21.md)
|
||||
- [SolarXiao 33S HEAD bootloader, LoRa OTA and repeater acceptance, 2026-10-21](solarxiao_33s_head_bootloader_lora_ota_2026-10-21.md)
|
||||
- [SX1262 channel-scanning hardware results](cad_scan_validation.md)
|
||||
|
||||
@@ -0,0 +1,104 @@
|
||||
<!-- meshcore-hosted-doc-link:start -->
|
||||
<p class="meshcore-hosted-doc-link"><a href="https://mikecarper.github.io/MeshCore/test-results/solarxiao_power_2026-10-21/">View this page on MeshCore Docs</a>.</p>
|
||||
<!-- meshcore-hosted-doc-link:end -->
|
||||
|
||||
# SolarXiao MPPT and battery controls, 2026-10-21
|
||||
|
||||
The Mercerwood SolarXiao 33S repeater now has a native LTC4162-FST charger
|
||||
interface and INA260 battery monitor in both Full and Reduced firmware.
|
||||
All 75 named register fields were read on the board. Supported setting changes
|
||||
passed live readback and persisted across a warm restart. The original charger
|
||||
register values were restored and all test overrides/calibration flags cleared.
|
||||
Three nonce-bound repeater forwarding tests passed at -6 dBm.
|
||||
|
||||
[Machine-readable acceptance record](solarxiao_power_2026-10-21.safe.json)
|
||||
contains exact build identities, readouts, command replies, fault-test coverage,
|
||||
RF samples, cleanup and limits. See the
|
||||
[SolarXiao controls guide](../solarxiao_power.md) for commands, units, permissions,
|
||||
persistence, calibration and telemetry channels.
|
||||
|
||||
## Qualified firmware and hardware
|
||||
|
||||
| Item | Result |
|
||||
| --- | --- |
|
||||
| Source | [`ea8176ddc6b2caa7e52f8dcba46178f69f6cbbea`](https://github.com/mikecarper/MeshCore/commit/ea8176ddc6b2caa7e52f8dcba46178f69f6cbbea) |
|
||||
| Target / installed profile | `solarxiao_33S_repeater` / Full |
|
||||
| Version | `v1.18.0.1-halo-keymind-cascade-dev-ea8176dd` |
|
||||
| Application body hash | `5A46FDAABF34C62D` |
|
||||
| Application size | 595,040 bytes |
|
||||
| Application SHA-256 | `d6068baf837c0348ea841a04a2412bd4724a93022f2d8b750af15ca1fb4fbe76` |
|
||||
| Full / Reduced qualification | PASS: build, runtime memory, DFU, UF2 and linked capabilities |
|
||||
| Native power marker in both images | PASS |
|
||||
| Native host test methods | 3 PASS, including eight core scenarios and real Wire/atomic-store boundaries; ASan/UBSan |
|
||||
| XIAO startup/shutdown checks | 6 PASS, including GPIO safety negative controls |
|
||||
| Powered USB readings | 75 named register fields; FST chemistry code 6, four cells |
|
||||
| Settings / persistence | 37 commands including 10 rejected writes; readback, restart and exact register restoration PASS |
|
||||
| LoRa carrier telemetry | PASS: three replies with all carrier channels 2..13 |
|
||||
| Repeater forwarding | 3 PASS; exact nonce, four-byte route, one target hop |
|
||||
| Bootloader | HEAD `b07255d4cdc601f1313e4cbe020caad3b5544821`, CRC `76EBABBF`, preserved |
|
||||
|
||||
Full has 150,608 internal bytes available against 76,960 required by the runtime
|
||||
memory check; Reduced has 152,256 available. The installed public key, primary
|
||||
radio, RX power saving and bridge settings remained unchanged. Repeating stayed
|
||||
on and TX stayed -6 dBm. Each trace added one direct RX and TX with zero receive
|
||||
errors. The cumulative receive-error counter was 2 after the trace run and 17
|
||||
after all telemetry work; core error flags remained zero. The trace partner's settings were preserved and endpoints closed.
|
||||
|
||||
## Powered bench acceptance
|
||||
|
||||
The user supplied 13.2 V at SOLAR/VIN with BAT disconnected and USB connected.
|
||||
Initial native readouts were approximately 13.2926 V input, 13.2785 V output,
|
||||
27.76 C die and 27.73 C modeled NTC temperature. BAT was open: its changing
|
||||
floating/capacitor voltage is not a battery measurement or a charger load test.
|
||||
The charger reported `battery_short` while BAT was disconnected.
|
||||
|
||||
The test exercised force/fast telemetry, MPPT, input-current limiting, manual
|
||||
input-voltage floor, suspend/resume, JEITA selection, manual charge current and
|
||||
cell voltage, C/x threshold, CV/total timers and saved calibration metadata.
|
||||
For example, 13000 mV input floor read back as 13078.1 mV, matching the hardware
|
||||
step. Manual targets of 1000 mA and 3500 mV/cell read back correctly with JEITA
|
||||
off. These are register-interface tests without a connected battery.
|
||||
|
||||
Saved policy reloaded after restart with nine register overrides and the
|
||||
explicit calibration-source flag. Every modified register was restored to its
|
||||
captured value, then `mppt reset` cleared saved overrides and calibration flags.
|
||||
Final MPPT and JEITA were on; force/fast telemetry were off. Host fault tests
|
||||
also simulated a charger reset, read/write faults, ignored writes, corrupt or
|
||||
wrong-size storage, failed atomic publication, identity mismatch, masked-bit
|
||||
preservation, thermal ordering and one-shot/W0C behavior.
|
||||
|
||||
## Supply state, radio checks and scope
|
||||
|
||||
The first install check found the charger unavailable. It restored the verified
|
||||
previous firmware with identity/settings intact; the user then confirmed the
|
||||
bench supply had been off and switched it on. The final firmware installed and
|
||||
passed powered reading/control tests.
|
||||
|
||||
During a later interval when the charger was unavailable again, authenticated
|
||||
LoRa CLI returned an explicit unavailable status while INA260 readings remained
|
||||
available. Three telemetry replies carried channel-2 voltage/current/power;
|
||||
charger channels were omitted. Temporary contacts and forwarding connections
|
||||
were removed. One remote version read required a retry.
|
||||
|
||||
After the supply was switched on again, authenticated LoRa commands identified
|
||||
FST/four cells and read charger/battery/status values. Three subsequent telemetry
|
||||
replies contained every carrier channel 2..13: input 13.29 V, output 13.27..13.28 V,
|
||||
die 25.4..25.5 C and modeled NTC 24.9 C. Two telemetry attempts missed replies before a successful retry. An earlier powered CLI session also needed retries and
|
||||
missed its first telemetry response; all temporary contacts/forwards were cleaned
|
||||
up. Final USB checks confirmed VIN 13.2926 V, MPPT/JEITA on, zero saved
|
||||
overrides, zero calibration flags and the same firmware/bootloader identities.
|
||||
These results establish successful readout, not a loss-free radio link.
|
||||
|
||||
No actual battery charging, loaded-current calibration, resistance measurement
|
||||
or ship-mode entry was tested. The default 10 milliohm sense resistors and
|
||||
10 kohm / 3490 K NTC model come from reference documentation; the supplied
|
||||
Standard schematic uses a single-cell L42M, not this exact 12 V FSTM revision.
|
||||
Actual carrier component values and the temperature model remain configurable.
|
||||
|
||||
The new application was installed by USB DFU. This run does not repeat a full
|
||||
LoRa firmware transfer; the earlier successful HEAD bootloader/OTA transfer is
|
||||
in the [separate OTA report](solarxiao_33s_head_bootloader_lora_ota_2026-10-21.md).
|
||||
Reduced was build-checked, while live tests used Full. An exploratory Room
|
||||
Server build exceeded its flash/UF2 limits; native power controls are therefore
|
||||
scoped to the requested SolarXiao repeater targets. No Room Server or Companion
|
||||
package is qualified by this report.
|
||||
File diff suppressed because it is too large.
Load diff
@@ -301,5 +301,76 @@ int main() {
|
||||
self.assertEqual(run.returncode, 0, run.stdout + run.stderr)
|
||||
|
||||
|
||||
class SolarXiaoAdapterTests(unittest.TestCase):
|
||||
def test_real_transport_and_atomic_settings_boundary(self):
|
||||
from test_regulator_settings import FILESYSTEM
|
||||
source = (ROOT / "src/helpers/sensors/SolarXiaoPower.cpp").read_text()
|
||||
classes = "\n".join(extract_braced(source, name) + ";" for name in
|
||||
("class CarrierBus", "class CarrierStore"))
|
||||
harness = r'''#include <cassert>
|
||||
#include <vector>
|
||||
#include <cstring>
|
||||
#include "SolarXiaoPowerCore.h"
|
||||
#define ATOMIC_FILE_WRITER_IMPLEMENTATION
|
||||
#include <helpers/CheckedSettingsFile.h>
|
||||
using namespace mesh;
|
||||
using namespace mesh::solar;
|
||||
#define HIGH 1
|
||||
#define PIN_BOARD_SDA 30
|
||||
#define PIN_BOARD_SCL 31
|
||||
int lines=1;
|
||||
int digitalRead(int p) {assert(p==30 || p==31);return lines;}
|
||||
struct FakeWire {
|
||||
std::vector<uint8_t> tx, rx;int reads=0, calls=0, ack=0, reported=-1;bool stop=true;
|
||||
void beginTransmission(uint8_t a) {assert(a==0x68 || a==0x40);++calls;tx.clear();}
|
||||
void write(uint8_t b) {tx.push_back(b);}
|
||||
int endTransmission(bool s=true) {stop=s;return ack;}
|
||||
int requestFrom(uint8_t,uint8_t n) {assert(n==2);reads=0;return reported<0?int(rx.size()):reported;}
|
||||
int available() {return rx.size()-reads;}
|
||||
int read() {return reads<int(rx.size())?rx[reads++]:-1;}
|
||||
} Wire;
|
||||
@CLASSES@
|
||||
constexpr const char* CarrierStore::path;
|
||||
int main() {
|
||||
CarrierBus bus;uint16_t word=0;
|
||||
Wire.rx={0x34,0x12};assert(bus.read(0x68,0x43,word));assert(word==0x1234 && !Wire.stop);
|
||||
Wire.rx={0x12,0x34};assert(bus.read(0x40,2,word));assert(word==0x1234);
|
||||
Wire.rx={0x12};assert(!bus.read(0x68,0x43,word));assert(!Wire.available());
|
||||
Wire.reported=2;Wire.rx.clear();assert(!bus.read(0x68,0x43,word));
|
||||
Wire.reported=-1;Wire.ack=2;assert(!bus.read(0x68,0x43,word));Wire.ack=0;
|
||||
assert(bus.write(0x68,0x14,0x8052));assert(Wire.stop && Wire.tx==std::vector<uint8_t>({0x14,0x52,0x80}));
|
||||
int calls=Wire.calls;assert(!bus.write(0x40,0,0) && !bus.read(0x42,0,word));assert(calls==Wire.calls);
|
||||
lines=0;assert(!bus.read(0x68,0,word) && !bus.write(0x68,0x14,2));assert(calls==Wire.calls);
|
||||
CarrierStore store;uint8_t image[Settings::IMAGE_SIZE],read[Settings::IMAGE_SIZE];Settings settings;settings.defaults();settings.encode(image);
|
||||
assert(!store.available() && !store.exists() && !store.save(image,sizeof(image)));
|
||||
MockFS fs;store.fs=&fs;assert(store.available() && !store.exists());
|
||||
assert(store.save(image,sizeof(image)) && store.exists() && store.load(read,sizeof(read)));
|
||||
assert(!memcmp(image,read,sizeof(image)));const auto baseline=fs.files[CarrierStore::path];
|
||||
settings.calibration_user_mask=1;settings.rsnsb_mohm=20;settings.encode(image);
|
||||
fs.fail_write=true;assert(!store.save(image,sizeof(image)));assert(fs.files[CarrierStore::path]==baseline);fs.fail_write=false;
|
||||
fs.fail_rename_from="/solar_power.tmp";assert(!store.save(image,sizeof(image)));assert(fs.files[CarrierStore::path]==baseline);fs.fail_rename_from.clear();
|
||||
assert(store.save(image,sizeof(image)));assert(store.load(read,sizeof(read)) && !memcmp(image,read,sizeof(image)));
|
||||
fs.files[CarrierStore::path].push_back(0);assert(!store.load(read,sizeof(read)));
|
||||
assert(!store.save(image,sizeof(image)-1));
|
||||
}
|
||||
'''.replace("@CLASSES@", classes)
|
||||
with tempfile.TemporaryDirectory(prefix="solarxiao-adapter-test-") as temp:
|
||||
directory = Path(temp)
|
||||
(directory / "helpers").mkdir()
|
||||
(directory / "helpers/IdentityStore.h").write_text(FILESYSTEM, encoding="ascii")
|
||||
cpp = directory / "test.cpp"
|
||||
binary = directory / "test"
|
||||
cpp.write_text(harness, encoding="ascii")
|
||||
build = subprocess.run([shutil.which("g++"), "-std=c++11", "-DNRF52_PLATFORM",
|
||||
"-DMESH_SOLARXIAO_POWER=1", "-fsanitize=address,undefined", "-fno-pie", "-no-pie",
|
||||
"-I" + str(directory), "-I" + str(ROOT / "src"),
|
||||
"-I" + str(ROOT / "src/helpers/sensors"), str(cpp),
|
||||
str(ROOT / "src/helpers/sensors/SolarXiaoPowerCore.cpp"), "-o", str(binary)],
|
||||
capture_output=True, text=True, timeout=30)
|
||||
self.assertEqual(build.returncode, 0, build.stdout + build.stderr)
|
||||
run = subprocess.run([str(binary)], capture_output=True, text=True, timeout=10)
|
||||
self.assertEqual(run.returncode, 0, run.stdout + run.stderr)
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
unittest.main()
|
||||
Reference in new issue
Block a user