diff --git a/lib/MPPTLib/powerSupplies.cpp b/lib/MPPTLib/powerSupplies.cpp index 02e2afc..5eaf8bc 100644 --- a/lib/MPPTLib/powerSupplies.cpp +++ b/lib/MPPTLib/powerSupplies.cpp @@ -3,6 +3,23 @@ #include // ModbusMaster #include "utils.h" +PowerSupply* PowerSupply::make(const String &type) { + PowerSupply* ret = NULL; + String typeUp = type; + typeUp.toUpperCase(); + if (typeUp.startsWith("DP")) { + ret = new DPS(&Serial2); + Serial2.begin(19200, SERIAL_8N1, -1, -1, false, 1000); + } else if (typeUp.startsWith("DROK")) { + ret = new Drok(&Serial2); + Serial2.begin(4800, SERIAL_8N1, -1, -1, false, 1000); + } else { //default + ret = NULL; + } + if (ret) ret->type_ = type; + return ret; +} + // ----------------------- // // ----- PowerSupply ----- // @@ -31,7 +48,7 @@ void PowerSupply::doTotals() { // -------- Drok -------- // // ---------------------- // -Drok::Drok(Stream* port) : PowerSupply(), port_(port) { } +Drok::Drok(Stream* port) : PowerSupply() { port_ = port; } Drok::~Drok() { } bool Drok::begin() { @@ -39,8 +56,6 @@ bool Drok::begin() { return doUpdate(); } -String Drok::getType() const { return "Drok"; } - bool Drok::doUpdate() { bool res = readVoltage() && readCurrent() && @@ -136,11 +151,9 @@ bool Drok::setCurrent(float v) { // --------- DPS --------- // // ----------------------- // -DPS::DPS(Stream* port) : PowerSupply(), port_(port), bus_(new ModbusMaster) { } +DPS::DPS(Stream* port) : PowerSupply(), bus_(new ModbusMaster) { port_ = port; } DPS::~DPS() { } -String DPS::getType() const { return "DPS"; } - bool DPS::begin() { bus_->begin(1, *port_); return doUpdate(); diff --git a/lib/MPPTLib/powerSupplies.h b/lib/MPPTLib/powerSupplies.h index 3fca54f..1b465f0 100644 --- a/lib/MPPTLib/powerSupplies.h +++ b/lib/MPPTLib/powerSupplies.h @@ -1,11 +1,13 @@ #pragma once #include +#include -class String; class Stream; class PowerSupply { public: + String type_; + Stream *port_ = NULL; bool debug_ = false; float outVolt_ = 0, outCurr_ = 0; float limitVolt_ = 0, limitCurr_ = 0; @@ -13,6 +15,7 @@ class PowerSupply { bool outEn_ = false; uint32_t lastSuccess_ = 0, lastAmpUpdate_ = 0; + static PowerSupply* make(const String &type); PowerSupply(); virtual ~PowerSupply(); virtual bool begin() = 0; @@ -28,18 +31,17 @@ class PowerSupply { virtual bool isCollapsed() const; virtual bool getInputVolt(float* v) { return false; } virtual String toString() const; - virtual String getType() const = 0; + String getType() const { return type_; } + virtual bool isDrok() const { return true; } protected: void doTotals(); }; class Drok : public PowerSupply { - Stream *port_; public: - Drok(Stream* port); + Drok(Stream*); ~Drok(); bool begin() override; - virtual String getType() const; String cmdReply(const String &cmd); bool setVoltage(float) override; @@ -60,16 +62,14 @@ class Drok : public PowerSupply { class ModbusMaster; class DPS : public PowerSupply { - Stream *port_; ModbusMaster* bus_; public: float inputVolts_ = 0; bool cc_ = false; - DPS(Stream* port); + DPS(Stream*); ~DPS(); bool begin() override; - virtual String getType() const; bool setVoltage(float) override; bool setCurrent(float) override; @@ -79,4 +79,5 @@ class DPS : public PowerSupply { bool isCC() const override; bool getInputVolt(float* v) override; + bool isDrok() const override { return false; } }; diff --git a/lib/MPPTLib/solar.cpp b/lib/MPPTLib/solar.cpp index 6c10f3f..21caec3 100644 --- a/lib/MPPTLib/solar.cpp +++ b/lib/MPPTLib/solar.cpp @@ -9,6 +9,7 @@ #include using namespace std::placeholders; +#define ckPSUs() if (!psu_) { return String("no psu"); } WiFiClient espClient; @@ -62,11 +63,11 @@ void Solar::setup() { pub_.add("inPin", pinInvolt_).pref(); pub_.add("lvProtect", std::bind(&Solar::setLVProtect, this, _1)).pref(); pub_.add("psu", std::bind(&Solar::setPSU, this, _1)).pref(); - pub_.add("outputEN",[=](String s){ if (s.length()) psu_->enableOutput(s == "on"); return String(psu_->outEn_); }); - pub_.add("outvolt", [=](String s){ if (s.length()) psu_->setVoltage(s.toFloat()); return String(psu_->outVolt_); }); - pub_.add("outcurr", [=](String s){ if (s.length()) psu_->setCurrent(s.toFloat()); return String(psu_->outCurr_); }); - pub_.add("outpower",[=](String){ return String(psu_->outVolt_ * psu_->outCurr_); }); - pub_.add("currFilt",[=](String){ return String(psu_->currFilt_); }); + pub_.add("outputEN",[=](String s){ ckPSUs(); if (s.length()) psu_->enableOutput(s == "on"); return String(psu_->outEn_); }); + pub_.add("outvolt", [=](String s){ ckPSUs(); if (s.length()) psu_->setVoltage(s.toFloat()); return String(psu_->outVolt_); }); + pub_.add("outcurr", [=](String s){ ckPSUs(); if (s.length()) psu_->setCurrent(s.toFloat()); return String(psu_->outCurr_); }); + pub_.add("outpower",[=](String){ ckPSUs(); return String(psu_->outVolt_ * psu_->outCurr_); }); + pub_.add("currFilt",[=](String){ ckPSUs(); return String(psu_->currFilt_); }); pub_.add("state", state_ ); pub_.add("pgain", pgain_ ).pref(); pub_.add("ramplimit", ramplimit_ ).pref(); @@ -79,14 +80,14 @@ void Solar::setup() { pub_.add("autosweep", autoSweep_ ).pref(); pub_.add("currentcap", currentCap_ ).pref(); pub_.add("involt", inVolt_); - pub_.add("wh", [=](String s) { if (s.length()) psu_->wh_ = s.toFloat(); return String(psu_->wh_); }); + pub_.add("wh", [=](String s) { ckPSUs(); if (s.length()) psu_->wh_ = s.toFloat(); return String(psu_->wh_); }); pub_.add("collapses", [=](String) { return String(getCollapses()); }); pub_.add("sweep",[=](String){ startSweep(); return "starting sweep"; }).hide(); pub_.add("connect",[=](String s){ doConnect(); return "connected"; }).hide(); pub_.add("disconnect",[=](String s){ db_.client.disconnect(); WiFi.disconnect(); return "dissed"; }).hide(); pub_.add("restart",[](String s){ ESP.restart(); return ""; }).hide(); pub_.add("clear",[=](String s){ pub_.clearPrefs(); return "cleared"; }).hide(); - pub_.add("debug",[=](String s){ psu_->debug_ = !(s == "off"); return String(psu_->debug_); }).hide(); + pub_.add("debug",[=](String s){ ckPSUs(); psu_->debug_ = !(s == "off"); return String(psu_->debug_); }).hide(); pub_.add("version",[=](String){ log("Version " + version_); return version_; }).hide(); pub_.add("update",[=](String s){ doOTAUpdate_ = s; return "OK, will try "+s; }).hide(); pub_.add("uptime",[=](String){ String ret = "Uptime " + timeAgo(millis()/1000); log(ret); return ret; }).hide(); @@ -145,10 +146,12 @@ void Solar::setup() { //fn, name, stack size, parameter, priority, handle xTaskCreate(runPubt, "publish", 10000, this, 1, NULL); - if (!psu_->begin()) - log("PSU begin failed"); - newDesiredCurr_ = psu_->currFilt_ = psu_->limitCurr_ = psu_->outCurr_; - log(str("startup current is %0.3fAdes/%0.3fAfilt/%0.3fAout", newDesiredCurr_, psu_->currFilt_, psu_->outCurr_)); + if (!psu_) log("no PSU set"); + else if (!psu_->begin()) log("PSU begin failed"); + else if (psu_) { + newDesiredCurr_ = psu_->currFilt_ = psu_->limitCurr_ = psu_->outCurr_; + log(str("startup current is %0.3fAdes/%0.3fAfilt/%0.3fAout", newDesiredCurr_, psu_->currFilt_, psu_->outCurr_)); + } nextAutoSweep_ = millis() + 10000; log("finished setup"); log("OSPController Version " + version_); @@ -167,16 +170,10 @@ String Solar::setPSU(String s) { log("setPSU " + s); if (s.length() || !psu_) { //TODO Parse softserial pins, bluetooth comms, and moar. - s.toUpperCase(); - if (s.startsWith("DP")) { - psu_.reset(new DPS(&Serial2)); - Serial2.begin(19200, SERIAL_8N1, -1, -1, false, 1000); - if (measperiod_ == 200) //default - measperiod_ = 500; - } else { //default - psu_.reset(new Drok(&Serial2)); - Serial2.begin(4800, SERIAL_8N1, -1, -1, false, 1000); - } + psu_.reset(PowerSupply::make(s)); + if (psu_ && ! psu_->isDrok() && (measperiod_ == 200)) //default + measperiod_ = 500; //slow down, DSP5005 meas does full update() + ckPSUs(); psu_->begin(); return "created PSU=" + psu_->getType(); } @@ -216,7 +213,7 @@ String SPoint::toString() const { } void Solar::applyAdjustment() { - if (newDesiredCurr_ != psu_->limitCurr_) { + if (psu_ && newDesiredCurr_ != psu_->limitCurr_) { if (psu_->setCurrent(newDesiredCurr_)) pub_.logNote(str("[adjusting %0.3fA (from %0.3fA)]", newDesiredCurr_ - psu_->limitCurr_, psu_->limitCurr_)); else log("error setting current"); @@ -231,19 +228,20 @@ void Solar::startSweep() { if (state_ == States::error) return log("can't sweep, system is in error state"); log(str("SWEEP START c=%0.3f, (setpoint was %0.3f)", newDesiredCurr_, setpoint_)); - if (state_ == States::collapsemode) { + if (psu_ && state_ == States::collapsemode) { psu_->enableOutput(false); psu_->setCurrent(psu_->currFilt_* 0.75); delay(200); log(str("First coming out of collapse-mode to clim of %0.2fA", psu_->limitCurr_)); } setState(States::sweeping); - if (!psu_->outEn_) + if (psu_ && !psu_->outEn_) psu_->enableOutput(true); lastAutoSweep_ = millis(); } void Solar::doSweepStep() { + if (!psu_) return; if (!psu_->outEn_) return setState(States::mppt); @@ -306,7 +304,7 @@ void Solar::doSweepStep() { } bool Solar::hasCollapsed() const { - if (!psu_->outEn_) return false; + if (!psu_ || !psu_->outEn_) return false; bool simpleClps = (inVolt_ < (psu_->outVolt_ * 1.11)); //simple voltage match method float collapsePct = (inVolt_ - psu_->outVolt_) / psu_->outVolt_; if (simpleClps && psu_->isCollapsed()) @@ -326,7 +324,7 @@ void Solar::loop() { return delay(100); if (now > nextVmeas_) { - if (psu_->getInputVolt(&inVolt_)) { + if (psu_ && psu_->getInputVolt(&inVolt_)) { psu_->doUpdate(); psu_->getInputVolt(&inVolt_); } else { @@ -336,7 +334,7 @@ void Solar::loop() { pub_.setDirtyAddr(&inVolt_); if (state_ == States::sweeping) { doSweepStep(); - } else if (setpoint_ > 0 && psu_->outEn_) { //corrections enabled + } else if (setpoint_ > 0 && psu_ && psu_->outEn_) { //corrections enabled double error = inVolt_ - setpoint_; double dcurr = constrain(error * pgain_, -ramplimit_ * 2, ramplimit_); //limit ramping speed if (error > 0.3 || (-error > 0.2)) { //adjustment deadband, more sensitive when needing to ramp down @@ -349,7 +347,9 @@ void Solar::loop() { } //update system states: - if (state_ != States::sweeping && state_ != States::collapsemode) { + if (!psu_) { + setState(States::error); + } else if (state_ != States::sweeping && state_ != States::collapsemode) { int lastPSUsecs = (millis() - psu_->lastSuccess_) / 1000; if (psu_->outEn_) { if (lastPSUsecs > 11) setState(States::error, "enabled but no PSU comms"); @@ -373,7 +373,7 @@ void Solar::loop() { if (now > nextSolarAdjust_) { try { if (state_ == States::error) { - if ((now - psu_->lastSuccess_) < 30000) { //for 30s after failure try and shut it down + if (psu_ && (now - psu_->lastSuccess_) < 30000) { //for 30s after failure try and shut it down psu_->enableOutput(false); psu_->setCurrent(0); throw Backoff("PSU failure, disabling"); @@ -386,7 +386,7 @@ void Solar::loop() { log(str("collapsed! %0.2fV set recovery to %0.1fA ", inVolt_, newDesiredCurr_) + psu_->toString()); psu_->enableOutput(false); psu_->setCurrent(newDesiredCurr_); - } else if (!psu_->outEn_) { //power supply is off. let's check about turning it on + } else if (psu_ && !psu_->outEn_) { //power supply is off. let's check about turning it on if (inVolt_ < psu_->outVolt_ || psu_->outVolt_ < 0.1) { throw Backoff("not starting up, input voltage too low (is it dark?)"); } else if ((psu_->outVolt_ > psu_->limitVolt_) || (psu_->outVolt_ < (psu_->limitVolt_ * 0.60) && psu_->outVolt_ > 1)) { @@ -398,7 +398,7 @@ void Solar::loop() { psu_->enableOutput(true); } } - if (psu_->outEn_ && state_ != States::collapsemode) { + if (psu_ && psu_->outEn_ && state_ != States::collapsemode) { applyAdjustment(); } } @@ -420,7 +420,7 @@ void Solar::loop() { nextPrint_ = now + printPeriod_; } - if (now > nextPSUpdate_) { + if (psu_ && now > nextPSUpdate_) { bool res = psu_->doUpdate(); if (res) { pub_.setDirty({"outvolt", "outcurr", "outputEN", "outpower", "currFilt"}); @@ -433,13 +433,13 @@ void Solar::loop() { } if (lvProtect_ && now > lvProtect_->nextCheck_) { - if (!lvProtect_->isTriggered() && psu_->outVolt_ < lvProtect_->threshold_) { + if (!lvProtect_->isTriggered() && psu_ && psu_->outVolt_ < lvProtect_->threshold_) { log(str("LOW VOLTAGE PROTECT TRIGGERED (now at %0.2fV)", psu_->outVolt_)); sendOutgoingLogs(); //send logs, tripping this relay may power us down delay(200); lvProtect_->trigger(true); lvProtect_->nextCheck_ = now + 5 * 1000; - } else if (lvProtect_->isTriggered() && psu_->outVolt_ > lvProtect_->threshRecovery_) { + } else if (lvProtect_->isTriggered() && psu_ && psu_->outVolt_ > lvProtect_->threshRecovery_) { log("low voltage recovery, re-enabling."); lvProtect_->trigger(false); lvProtect_->nextCheck_ = now + 10000; @@ -475,7 +475,7 @@ void Solar::publishTask() { db_.client.setCallback([=](char*topicbuf, uint8_t*buf, unsigned int len){ String topic(topicbuf), val = str(std::string((char*)buf, len)); log("got sub value " + topic + " -> " + val); - if (topic == (db_.feed + "/wh")) { + if (topic == (db_.feed + "/wh") && psu_) { psu_->wh_ = (psu_->wh_ > 2.0)? val.toFloat() : psu_->wh_ + val.toFloat(); log("restored wh value to " + val); db_.client.unsubscribe((db_.feed + "/wh").c_str()); @@ -509,7 +509,7 @@ void Solar::publishTask() { } sendOutgoingLogs(); heap_caps_check_integrity_all(true); - nextPub_ = now + (psu_->outEn_? db_.period : db_.period * 4); //slower when disabled + nextPub_ = now + ((psu_ && psu_->outEn_)? db_.period : db_.period * 4); //slower when disabled } db_.client.loop(); pub_.poll(&Serial); @@ -521,10 +521,10 @@ void Solar::publishTask() { void Solar::printStatus() { String s = state_; s.toUpperCase(); - s += str(" %0.1fVin -> %0.2fWh ", inVolt_, psu_->wh_) + psu_->toString(); + s += str(" %0.1fVin -> %0.2fWh ", inVolt_, psu_? psu_->wh_ : 0) + (psu_? psu_->toString() : "[no PSU]"); if (lvProtect_ && lvProtect_->isTriggered()) s += " [LV PROTECTED]"; s += pub_.popNotes(); - if (psu_->debug_) log(s); + if (psu_ && psu_->debug_) log(s); else Serial.println(s); }