Detect SolarXiao MPPT automatically and support Base self telemetry
This commit is contained in:
1 parent
0f7b898bcb
commit
804219e9fe
7 files changed
+318
-56
No files matched your search
@@ -8,17 +8,20 @@ namespace mesh { namespace solar {
|
||||
namespace {
|
||||
// Complete canonical records detect bad version, length, value or complement.
|
||||
// The filesystem adapter also verifies atomic publication and public readback.
|
||||
const uint8_t images[2][CarrierSelection::IMAGE_SIZE] = {
|
||||
const uint8_t images[4][CarrierSelection::IMAGE_SIZE] = {
|
||||
{'S','X','B',1,0,0xff,0xa5,0x5a},
|
||||
{'S','X','B',1,1,0xfe,0xa5,0x5a}
|
||||
{'S','X','B',1,1,0xfe,0xa5,0x5a},
|
||||
{'S','X','B',1,2,0xfd,0xa5,0x5a},
|
||||
{'S','X','B',1,3,0xfc,0xa5,0x5a}
|
||||
};
|
||||
}
|
||||
|
||||
void CarrierSelection::begin() {
|
||||
type=Mppt; saved=false; healthy=store.available();
|
||||
type=Auto; saved=false; healthy=store.available();
|
||||
verified_mppt=false; probed=false; next_probe=0;
|
||||
if (!healthy || !store.exists()) return;
|
||||
uint8_t image[IMAGE_SIZE];
|
||||
if (!store.load(image,sizeof(image)) || image[4]>1
|
||||
if (!store.load(image,sizeof(image)) || image[4]>Base
|
||||
|| memcmp(image,images[image[4]],sizeof(image))) {
|
||||
healthy=false;
|
||||
return;
|
||||
@@ -26,6 +29,17 @@ void CarrierSelection::begin() {
|
||||
type=static_cast<Type>(image[4]); saved=true;
|
||||
}
|
||||
|
||||
void CarrierSelection::service(Bus& bus,uint32_t now) {
|
||||
if (!isAuto() || verified_mppt || (probed && static_cast<int32_t>(now-next_probe)<0)) return;
|
||||
probed=true; next_probe=now+5000;
|
||||
uint16_t manufacturer,device;
|
||||
// These read-only identity registers do not configure the INA260 or charger.
|
||||
// A simple address ACK could be a different device (including an INA219).
|
||||
verified_mppt=bus.read(0x40,0xfe,manufacturer) && manufacturer==0x5449
|
||||
&& bus.read(0x40,0xff,device) && (device&0xfff0)==0x2270;
|
||||
// Latch a positive identity for this boot; transient loss must not relabel it.
|
||||
}
|
||||
|
||||
bool CarrierSelection::select(Type requested) {
|
||||
if (!store.available() || !store.save(images[requested],IMAGE_SIZE)) {
|
||||
// A failed publication/readback may leave either record live on disk.
|
||||
@@ -34,6 +48,7 @@ bool CarrierSelection::select(Type requested) {
|
||||
return false;
|
||||
}
|
||||
type=requested; saved=true; healthy=true;
|
||||
verified_mppt=false; probed=false; next_probe=0;
|
||||
return true;
|
||||
}
|
||||
|
||||
@@ -41,20 +56,31 @@ bool CarrierSelection::handle(const char* command,char* reply,size_t capacity,
|
||||
float battery_v,float mcu_c,bool history_available) {
|
||||
if (!command || !reply || !capacity) return false;
|
||||
if (!strcmp(command,"get solar.board")) {
|
||||
if (!healthy) snprintf(reply,capacity,"Err - carrier selection unconfirmed; set solar.board advanced|mppt");
|
||||
else snprintf(reply,capacity,"> %s source=%s (configured, not autodetected)",
|
||||
type==Advanced?"advanced":"mppt",saved?"saved":"default");
|
||||
if (!healthy) snprintf(reply,capacity,"Err - carrier selection unconfirmed; set solar.board auto|advanced|base|mppt");
|
||||
else if (type==Auto) snprintf(reply,capacity,"> auto source=%s result=%s",saved?"saved":"default",
|
||||
verified_mppt?"mppt (INA260 ID verified)":"self-only (MPPT unconfirmed; Base/Advanced indistinguishable)");
|
||||
else snprintf(reply,capacity,"> %s source=%s (manual override)",
|
||||
type==Advanced?"advanced":type==Base?"base":"mppt",saved?"saved":"default");
|
||||
return true;
|
||||
}
|
||||
if (!strncmp(command,"set solar.board",15) && (!command[15] || command[15]==' ')) {
|
||||
const char* value=command+15;
|
||||
while (*value==' ') ++value;
|
||||
if (strcmp(value,"advanced") && strcmp(value,"mppt")) {
|
||||
snprintf(reply,capacity,"Err - use set solar.board advanced|mppt");
|
||||
} else if (!select(!strcmp(value,"advanced")?Advanced:Mppt)) {
|
||||
Type requested=Auto;
|
||||
bool valid=true;
|
||||
if (!strcmp(value,"auto")) requested=Auto;
|
||||
else if (!strcmp(value,"advanced")) requested=Advanced;
|
||||
else if (!strcmp(value,"base")) requested=Base;
|
||||
else if (!strcmp(value,"mppt")) requested=Mppt;
|
||||
else valid=false;
|
||||
if (!valid) {
|
||||
snprintf(reply,capacity,"Err - use set solar.board auto|advanced|base|mppt");
|
||||
} else if (!select(requested)) {
|
||||
snprintf(reply,capacity,"Err - carrier save unconfirmed; native access blocked; repeat selection");
|
||||
} else if (type==Advanced) {
|
||||
snprintf(reply,capacity,"OK - Advanced saved; native LTC/INA access disabled; self ADC/MCU telemetry retained");
|
||||
} else if (type==Auto) {
|
||||
snprintf(reply,capacity,"OK - auto saved; verifying INA260; self ADC/MCU retained; verified MPPT reloads retained charger overrides");
|
||||
} else if (type==Advanced || type==Base) {
|
||||
snprintf(reply,capacity,"OK - %s saved; native LTC/INA access disabled; self ADC/MCU telemetry retained",type==Base?"Base":"Advanced");
|
||||
} else {
|
||||
snprintf(reply,capacity,"OK - MPPT saved; native access and retained charger overrides re-enabled");
|
||||
}
|
||||
@@ -74,16 +100,24 @@ bool CarrierSelection::handle(const char* command,char* reply,size_t capacity,
|
||||
? "> get telemetry.volt [1-7]; get telemetry.temp [1-7]; 30min samples, 7d RAM history; voltage trend does not measure charge current"
|
||||
: "Err - telemetry history is not included in this build");
|
||||
} else if (!healthy) {
|
||||
snprintf(reply,capacity,"Err - carrier selection unconfirmed; self readings available; set solar.board advanced|mppt");
|
||||
snprintf(reply,capacity,"Err - carrier selection unconfirmed; self readings available; set solar.board auto|advanced|base|mppt");
|
||||
} else if (!*key || !strcmp(key,"info")) {
|
||||
snprintf(reply,capacity,"> carrier=%s source=%s charger=%s; %s",
|
||||
type==Advanced?"advanced":"mppt",saved?"configured":"default",
|
||||
type==Advanced?"BQ24650":"LTC4162",
|
||||
type==Advanced?"ADC/MCU only; charger settings hardware-only":"use get mppt for live chip identity/readings");
|
||||
if (type==Auto) snprintf(reply,capacity,"> mode=auto source=%s %s",saved?"saved":"default",
|
||||
verified_mppt?"carrier=mppt (INA260 verified); use get mppt for LTC4162 identity/readings"
|
||||
:"result=self-only; INA260 not verified; Base/Advanced cannot be distinguished");
|
||||
else snprintf(reply,capacity,"> carrier=%s source=%s charger=%s; %s",
|
||||
type==Advanced?"advanced":type==Base?"base":"mppt",saved?"configured":"default",
|
||||
type==Advanced?"BQ24650":type==Base?"CN3791":"LTC4162",
|
||||
type==Mppt?"use get mppt for live chip identity/readings":"ADC/MCU only; charger settings hardware-only");
|
||||
} else if (!strcmp(key,"capabilities")) {
|
||||
snprintf(reply,capacity,type==Advanced
|
||||
const char* capabilities=type==Advanced
|
||||
? "> Advanced: battery ADC + MCU temp; solar V/I, battery I/W, NTC and charger flags unwired; settings hardware-only"
|
||||
: "> MPPT: self ADC/MCU + INA260 V/A/W + LTC4162 readings/settings when reachable; use get mppt fields 0");
|
||||
: type==Base
|
||||
? "> Base: battery ADC + MCU temp; CN3791 analog charger has no digital readings/settings; other power signals unwired"
|
||||
: nativeAllowed()
|
||||
? "> MPPT: self ADC/MCU + INA260 V/A/W + LTC4162 readings/settings when reachable; use get mppt fields 0"
|
||||
: "> Auto: self ADC/MCU only until INA260 ID verifies MPPT; Base/Advanced have no digital identity; retrying detection";
|
||||
snprintf(reply,capacity,"%s",capabilities);
|
||||
} else {
|
||||
snprintf(reply,capacity,"Err - use get solar info|readings|capabilities|history; get/set solar.board");
|
||||
}
|
||||
|
||||
@@ -4,25 +4,32 @@
|
||||
|
||||
namespace mesh { namespace solar {
|
||||
|
||||
// Carrier identity is selected by the operator. A missing/unpowered I2C
|
||||
// peripheral cannot distinguish the Advanced board from the MPPT board.
|
||||
// Only a verified INA260 identifies the MPPT carrier automatically. Missing
|
||||
// responses leave self-only telemetry; Base and Advanced have no digital ID.
|
||||
class CarrierSelection {
|
||||
public:
|
||||
enum Type : uint8_t { Mppt = 0, Advanced = 1 };
|
||||
// Preserve the original manual record values for installed selections.
|
||||
enum Type : uint8_t { Mppt = 0, Advanced = 1, Auto = 2, Base = 3 };
|
||||
static constexpr size_t IMAGE_SIZE = 8;
|
||||
|
||||
private:
|
||||
Store& store;
|
||||
Type type = Mppt;
|
||||
Type type = Auto;
|
||||
bool healthy = false;
|
||||
bool saved = false;
|
||||
bool verified_mppt = false;
|
||||
bool probed = false;
|
||||
uint32_t next_probe = 0;
|
||||
bool select(Type requested);
|
||||
|
||||
public:
|
||||
explicit CarrierSelection(Store& persistence) : store(persistence) {}
|
||||
void begin();
|
||||
bool nativeAllowed() const { return healthy && type == Mppt; }
|
||||
void service(Bus& bus, uint32_t now);
|
||||
bool nativeAllowed() const { return healthy && (type == Mppt || (type == Auto && verified_mppt)); }
|
||||
bool isAdvanced() const { return healthy && type == Advanced; }
|
||||
bool isBase() const { return healthy && type == Base; }
|
||||
bool isAuto() const { return healthy && type == Auto; }
|
||||
bool settingsHealthy() const { return healthy; }
|
||||
bool handle(const char* command, char* reply, size_t capacity,
|
||||
float xiao_battery_v, float mcu_c, bool history_available);
|
||||
|
||||
@@ -94,7 +94,12 @@ void beginSettings(MainBoard& board,FILESYSTEM* fs,bool telemetry_history_availa
|
||||
selection.begin();
|
||||
persistence.fs=fs; controller.begin();
|
||||
}
|
||||
void service() { if (selection.nativeAllowed()) controller.service(millis()); }
|
||||
void service() {
|
||||
const bool native_before=selection.nativeAllowed();
|
||||
selection.service(transport,millis());
|
||||
if (!native_before && selection.nativeAllowed()) controller.reload();
|
||||
if (selection.nativeAllowed()) controller.service(millis());
|
||||
}
|
||||
bool handleCommand(const char* command,char* reply,size_t capacity,bool local) {
|
||||
float battery=NAN,temperature=NAN;
|
||||
if (main_board && command && (!strcmp(command,"get solar readings") || !strcmp(command,"get solar battery"))) {
|
||||
@@ -110,9 +115,14 @@ bool handleCommand(const char* command,char* reply,size_t capacity,bool local) {
|
||||
const bool mppt=(!strncmp(command,"get mppt",8) && (!command[8] || command[8]==' '))
|
||||
|| !strncmp(command,"set mppt ",9) || !strncmp(command,"mppt ",5);
|
||||
if (mppt && !selection.nativeAllowed()) {
|
||||
snprintf(reply,capacity,selection.isAdvanced()
|
||||
const char* error=selection.isAdvanced()
|
||||
? "Err - BQ24650 Advanced has no digital charger/INA interface; use get solar readings"
|
||||
: "Err - carrier selection unconfirmed; set solar.board advanced|mppt");
|
||||
: selection.isBase()
|
||||
? "Err - CN3791 Base has no digital charger/INA interface; use get solar readings"
|
||||
: selection.isAuto()
|
||||
? "Err - auto has not verified INA260; self-only; use get solar.board or set solar.board mppt for manual override"
|
||||
: "Err - carrier selection unconfirmed; set solar.board auto|advanced|base|mppt";
|
||||
snprintf(reply,capacity,"%s",error);
|
||||
return true;
|
||||
}
|
||||
return controller.handle(command,reply,capacity,local);
|
||||
|
||||
Reference in new issue
Block a user