* VE.Direct: return non-nullptr as a fallback
the changed return statement was supposed to return a shared_ptr to a
new and valid MPPT data struct as a fallback. however, it did return a
new shared_ptr that was initialized to nullptr.
* VE.Direct: make liveview total use total MPPT values
this change makes the call to VictronMppt.getData() obsolete, which in
turn will therefore not cause an error message on the console if
VE.Direct (MPPT) is not enabled. this change also takes care that once
multiple VE.Direct MPPT charge controllers are supported, the sums of
the respective total values are used in the web app totals.
* introduce VictronMpptClass
this solves a design issue where the loop() method of a static instance
of VeDirectMpptController, which is part of library code, is called as
part of the main loop() implementation. that is a problem because the
call to this loop() must be handled differently from all other calls:
the lib does not know whether or not the feature is enabled at all.
also, the instance would not be initialized when enabling the feature
during normal operation. that would even lead to a nullptr exception
since the pointer to the serial implementation is still uninitialized.
this new intermediate class is implemented with the support for multiple
Victron charge controllers in mind. adding support for more charge
controllers should be more viable than ever.
fixes#481.
related to #397#129.
* VE.Direct: move get.*AsString methods to respective structs
those structs, which hold the data to be translated into strings, know
best how to translate them. this change also simplifies access to those
translation, as no parameter must be handed to the respective methods:
they now act upon the data of the instance they are called for. adds
constness to those methods.
* VE.Direct: simplify and clean up get.*AsString methods
use a map, which is much easier to maintain and which reads much easier.
move the strings to flash memory to save RAM.
* DPL: use VictronMpptClass::getPowerOutputWatts method
remove redundant calculation of output power from DPL. consider
separation of concern: VictronMpptClass will provide the total solar
output power. the DPL shall not concern itself about how that value is
calculated and it certainly should be unaware about how many MPPT charge
controllers there actually are.
* VE.Direct: avoid shadowing struct member "P"
P was part of the base struct for both MPPT and SmartShunt controller.
however, P was also part of the SmartShunt controller data struct,
shadowing the member in the base struct.
since P has slightly different meaning in MPPT versus SmartShunt, and
since P is calculated for MPPT controllers but read from SmartShunts, P
now lives in both derived structs, but not in the base struct.
* VE.Direct: isDataValid(): avoid copying data structs
pass a const reference to the base class implementation of isDataValid()
rather than a copy of the whole struct.
* VE.Direct: unify logging of text events
* VE.Direct: stop processing text event if handled by base
in case the base class processed a text event, do not try to match it
against values that are only valid in the derived class -- none will
match.
* VE.Direct MPPT: manage data in a shared_ptr
instead of handing out a reference to a struct which is part of a class
instance that may disappear, e.g., on a config change, we now manage the
lifetime of said data structure using a shared_ptr and hand out copies
of that shared_ptr. this makes sure that users have a valid copy of the
data as long as they hold the shared_ptr.
* VE.Direct MPPT: implement getDataAgeMillis()
this works even if millis() wraps around.
* VE.Direct: process frame end event only for valid frames
save a parameters, save a level of indention, save a function call for
invalid frames.
avoid staleness in case the same power limit is calculated over and over
again, hence no new power limit value is calculated and hence no power
limit command is sent to the inverter. staleness occurs in this case if
the first power limit command to establish the respective limit was not
received by the inverter. one can easily simulate a situation where the
same power limit is caluclated over and over again: with a battery above
the start threshold, set a very low upper power limit for the inverter
(DPL setting). that value will be used as the limit as long as the power
meter reading is larger than that.
we could also check the limit reported by the inverter. however, that
value is in percent of the inverter's max AC output, and is often not
the same value as we requested as the limit, but slightly off. we then
would have to decide how much deviation is okay, which is unreasonably
complicated.
closes#478.
Sunrise and -set must recomputed if one of the following conditions is met:
* The date changed (based on the selected timezone)
* Location (Lat/Lon) changed
* Sunset type changed
So instead of calculating that every minute just do it on update via web interface or date change.
If a new config is uploaded, the DTU gets restarted. There is no need to initiate a recalculation in this case.
* VE.Direct MPPT MQTT: remove trailing whitespace
* VE.Direct MPPT MQTT: publish P, IPV and E to MQTT
those values are calculated by OpenDTU-OnBatery and are part of the web
application live view, but were previously not published through MQTT.
closes#376.
* Move Mppt logic to subclass
* Added Definitions for Shunts and restructering
* First integration of SmartShunt data into Web Interface
* Code cleanup
* VE.Direct: whitespace cleanup
* VE.Direct: manage HardwareSerial in unique_ptr
* VE.Direct: _efficiency is only needed by MPPT
* VE.Direct: keep as many members private as possible
* VE.Direct: use int8_t for pins (as before)
* VictronSmartShunt: _verboseLogging is not used
* VE.Direct: OR (off reason) is MPPT specific
it also applies to Phoenix inverters and Smart BuckBoost, but since
there is no support for those, the code is moved to the MPPT controller.
* Added Shunt alarms to liveview
Changed from double to int for several readings
* Update build.yml to allow manual builds
---------
Co-authored-by: Philipp Sandhaus <philipp.sandhaus@cewe.de>
Co-authored-by: Bernhard Kirchen <schlimmchen@posteo.net>
for the MQTT integration, JkBms::DataPointContainer::updateFrom() was
changed such that the data points timestamp reflects the last change of
the data point value. that timestamp was used to set the SoC last update
timestamp. however, that timestampt must reflect the last time the SoC
was successfully received from the JK BMS so we could make sure the
value was up to date.
* JK BMS: avoid trailing whitespace in debug output
* JK BMS: publish data points through MQTT
* JK BMS: updateFrom: skip data points with equal value
this changes the interpretation of the timestamp in data containers that
are merely updated from other data containers: this is the oldest
timestamp known where the value was as recorded by the data point in its
respective container.
the data container constructed from an answer will -- naturally -- have
the timetamps of its data points set to the time they were constructed.
* JK BMS: only publish changed values to MQTT broker
all values are still published once every minute if the MQTT retain flag
is NOT set. otherwise, the constant values are only published once on
startup.
* JK BMS: avoid trailing whitespace in debug output
* JK BMS: publish data points through MQTT
* JK BMS: updateFrom: skip data points with equal value
this changes the interpretation of the timestamp in data containers that
are merely updated from other data containers: this is the oldest
timestamp known where the value was as recorded by the data point in its
respective container.
the data container constructed from an answer will -- naturally -- have
the timetamps of its data points set to the time they were constructed.
* JK BMS: only publish changed values to MQTT broker
all values are still published once every minute if the MQTT retain flag
is NOT set. otherwise, the constant values are only published once on
startup.
* thread-safety and dynamic memory for MessageOutput
* use dynamic memory to allow handling of arbitrary message lenghts.
* keep a message buffer for every task so no task ever mangles the
message of another task.
* every complete line is written to the serial console and moved to
a line buffer for sending them through the websocket.
* the websocket is always fed complete lines.
* make sure to feed only as many lines as possible to the websocket
handler, so that no lines are dropped.
* lock all MessageOutput state against concurrent access.
* MessageOutput: respect HardwareSerial buffer size
the MessageOutput class buffers whole lines of output printed by any
task in order to avoid mangling of text. that means we hand over full
lines to the HardwareSerial instance, which might be too much in one
call to write(buffer, size). we now check the return value of
write(buffer, size) and call the function again with the part of the
message that could not yet be written by HardwareSerial.
* VE.Direct: reset state machine on timeout
there will never be a large gap between two bytes of the same frame.
if such a large gap is observed, reset the state machine so it tries
to decode a new frame once more data arrives.
this is helpful in case of corrupted data that prevents the state
machine of transitioning to the final state even though the VE.Direct
data producer is done sending bytes that belong to the same frame.
* VE.Direct: print problems to MessageOutput
this includes the web console in particular, where many users have
access to while the serial console is not attached or monitored.
* VE.Direct: collect serial input into buffer and print
should help debug issues for users.
* VE.Direct: implement and use verbose logging switch
* add Icons for Battery and Victron device sensors in Home Assistant
overriding the boring default icon for many sensors
Signed-off-by: Martin Dummer <martin.dummer@gmx.net>
* fix: homeassistant autodiscovery topics
homeassistant autodiscovery topics contain not allowed characters, which
are now fixed.
Sidenote: when autodiscovery messages were retained, the badly formatted messages
must be removed from the mqtt server manually. Otherwise the error
messages in homeassistant will persist.
Closes: https://github.com/helgeerbe/OpenDTU-OnBattery/issues/378
Signed-off-by: Martin Dummer <martin.dummer@gmx.net>
* Fix: some homeassistant autoconf messages are wrong
Misc fixes for HA autoconfiguration:
* Entity sensor.panel_yield_today now uses state class "total"
* Entity sensor.panel_yield_yesterday now uses state class "total"
* Entity sensor.mppt_day_sequence_number_0364 now shows correct value
* sensor.panel_power duplicate removed
Closes: https://github.com/helgeerbe/OpenDTU-OnBattery/issues/379
Signed-off-by: Martin Dummer <martin.dummer@gmx.net>
---------
Signed-off-by: Martin Dummer <martin.dummer@gmx.net>
* DPL MQTT handler: modernize
* there is no need to tokenize and check the topic of a received MQTT
message if we only subscribe to a single topic. all messages will be
for that topic. avoid testing the topic in the callback alltogether.
* use std::string and std::stoi over allocating and deleting a buffer
and copying charactes around.
* use a switch statement to process the actual payload.
* break a long line.
* DPL: fix getMode() return value
getMode() returned a bool. probably its return type was not adjusted
when the third mode was introduced. this lead to mode 2 being cast to
true implicitly, which in turn was used to construct a String, such that
"1" was published as the DPL mode when in fact it was 2.
make the mode an enum class to avoid such problems in the future.
inline getMode() and setMode().
fix indention.
the size allocated for the HTTP request response was too little, while
the size for the buffer of the websocket output was increased already.
add a new member variable and use it in both context, such that
increasing the buffer size to accomodate more space (for the JSON data
in particular) will benefit both contexts in the future.
* DPL: improve verbose logging
* shorten DPL log prefix
* canUseDirectSolarPower() was printed two times
* _batteryDischargeEnabled was printed two times
* convert boolean values to human-readable strings
* add units where possible
* split messages into block "before calculating new limit" and "after
calculating new limit", as the latter cannot rely on _inverter being
available.
* order messages such that variables whose value is derived from other
variables are printed later than their dependencies.
* merge output into blocks (one instance near "Printout some stats")
* remove more redundant info (produced in functions outside loop())
* print target grid consumption
* DPL: inhibit solar passthrough while stop threshold reached
* DPL: implement and use isBelowStopThreshold()
we only want to inhibit solar passthrough if the SoC is *below* the stop
threshold, not if it is equal to the stop threshold. otherwise, when
discharging, we would discharge until the battery reached the stop
threshold, then we would also inhibit solar passthrough, until the
battery is charged to the SoC stop threshold plus one percent.
processing a published valued on a subscribed topic is currently running
in a task that is not the task executing the main loop(). that's because
the espMqttClient(Secure) was constructed without arguments, which
selects the constructor with two arguments priority and core, both of
which have default values. that constructor selects
espMqttClientTypes::UseInternalTask::YES, causing a task to be created
in which context the MQTT client loop is executed.
MQTT subscribers assume they are running in the same context as the main
loop(). most code assumes exactly that. as the scheduler is preemptive
and very little (none at all?) code is interlocked, we have to make sure
to meet the programmer's expectations.
this changeset calls the MQTT client loop in the context of the main
loop() and enforces the use of espMqttClientTypes::UseInternalTask::NO.
* MQTT verbose logging: fix typo
this typo caused that verbose logging was always disabled for MQTT and
could not be enabled.
* webapp status: show MQTT verbose logging setting
* PowerMeter: gracefully handle non-float MQTT values
* PowerMeter: update _lastPowerMeterUpdate conservatively
update the timestampt only if the topic actually matched any
subscription and if the value could be parsed as a float.
* PowerMeter: unsubscribe before subscribing
* PowerMeter: organize subscriptions in a map
this allows for a slightly more elegant code and reduced amount of code
overall.
* PowerMeter: clean up header
* move private methods to private section of class declaration.
* remove unused member variable.
Make the administrative accesspoint timeout configurable. The default
value is 3 minutes, values from 0-99999 are possible, where 0 means
infinite (no timeout).
Signed-off-by: Martin Dummer <martin.dummer@gmx.net>