/* ; Project: Open Vehicle Monitor System ; Date: 14th March 2017 ; ; Changes: ; 1.0 Initial release ; ; (C) 2011 Michael Stegen / Stegen Electronics ; (C) 2011-2017 Mark Webb-Johnson ; (C) 2011 Sonny Chen @ EPRO/DX ; ; Permission is hereby granted, free of charge, to any person obtaining a copy ; of this software and associated documentation files (the "Software"), to deal ; in the Software without restriction, including without limitation the rights ; to use, copy, modify, merge, publish, distribute, sublicense, and/or sell ; copies of the Software, and to permit persons to whom the Software is ; furnished to do so, subject to the following conditions: ; ; The above copyright notice and this permission notice shall be included in ; all copies or substantial portions of the Software. ; ; THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR ; IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, ; FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE ; AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER ; LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, ; OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN ; THE SOFTWARE. */ #ifndef __METRICS_H__ #define __METRICS_H__ #include #include #include #include #include #include #include #include #include #include #include "ovms_utils.h" #include "ovms_mutex.h" #include "dbc_number.h" #ifdef CONFIG_OVMS_SC_JAVASCRIPT_DUKTAPE #include "ovms_script.h" #endif #include "ovms_log.h" #define TAG ((const char*)"metric") #define METRICS_MAX_MODIFIERS 32 using namespace std; typedef enum : uint8_t { Other = 0, Native = Other, Kilometers = 10, Miles = 11, Meters = 12, Feet = 13, Celcius = 20, Fahrenheit = 21, kPa = 30, Pa = 31, PSI = 32, Bar = 33, mBar = 34, Volts = 40, Amps = 41, AmpHours = 42, kW = 43, kWh = 44, Watts = 45, WattHours = 46, kVA = 47, Seconds = 50, Minutes = 51, Hours = 52, TimeUTC = 53, TimeLocal = 54, Degrees = 60, Kph = 61, Mph = 62, // Acceleration: KphPS = 71, // Kph per second MphPS = 72, // Mph per second MetersPSS = 73, // Meters per second^2 dbm = 80, // Signal Quality (in dBm) sq = 81, // Signal Quality (in SQ units) Percentage = 90, rpm = 91, // Energy consumption: WattHoursPK = 100, // Wh/km WattHoursPM = 101, // Wh/mi // Torque: Nm = 110, } metric_unit_t; typedef enum : uint8_t { NeverDefined = 0, FirstDefined, Defined } metric_defined_t; extern const char* OvmsMetricUnitLabel(metric_unit_t units); extern int UnitConvert(metric_unit_t from, metric_unit_t to, int value); extern float UnitConvert(metric_unit_t from, metric_unit_t to, float value); typedef uint32_t persistent_value_t; struct persistent_values { std::size_t namehash; persistent_value_t value; }; struct persistent_metrics { u_long magic; int version; unsigned int serial; size_t size; int used; persistent_values values[100]; }; extern persistent_values *pmetrics_find(const char *name); extern persistent_values *pmetrics_register(const char *name); class OvmsMetric { public: OvmsMetric(const char* name, uint16_t autostale = 0, metric_unit_t units = Other, bool persist = false); virtual ~OvmsMetric(); public: virtual std::string AsString(const char* defvalue = "", metric_unit_t units = Other, int precision = -1); std::string AsUnitString(const char* defvalue = "", metric_unit_t units = Other, int precision = -1); virtual std::string AsJSON(const char* defvalue = "", metric_unit_t units = Other, int precision = -1); virtual float AsFloat(const float defvalue = 0, metric_unit_t units = Other); #ifdef CONFIG_OVMS_SC_JAVASCRIPT_DUKTAPE virtual void DukPush(DukContext &dc); #endif virtual bool SetValue(std::string value); virtual bool SetValue(dbcNumber& value); virtual void operator=(std::string value); virtual uint32_t LastModified(); virtual uint32_t Age(); virtual bool CheckPersist(); virtual bool IsDefined(); virtual bool IsFirstDefined(); virtual bool IsPersistent(); virtual bool IsStale(); virtual bool IsString() { return false; }; virtual bool IsFresh(); virtual void RefreshPersist(); virtual void SetStale(bool stale); virtual void SetAutoStale(uint16_t seconds); virtual metric_unit_t GetUnits(); virtual bool IsModified(size_t modifier); virtual bool IsModifiedAndClear(size_t modifier); virtual void ClearModified(size_t modifier); virtual void SetModified(bool changed=true); virtual void Clear(); public: OvmsMetric* m_next; const char* m_name; std::atomic_ulong m_modified; uint32_t m_lastmodified; uint16_t m_autostale; metric_unit_t m_units; metric_defined_t m_defined; bool m_stale; bool m_persist; }; class OvmsMetricBool : public OvmsMetric { public: OvmsMetricBool(const char* name, uint16_t autostale=0, metric_unit_t units = Other, bool persist = false); virtual ~OvmsMetricBool(); public: std::string AsString(const char* defvalue = "", metric_unit_t units = Other, int precision = -1); virtual std::string AsJSON(const char* defvalue = "", metric_unit_t units = Other, int precision = -1); float AsFloat(const float defvalue = 0, metric_unit_t units = Other); int AsBool(const bool defvalue = false); #ifdef CONFIG_OVMS_SC_JAVASCRIPT_DUKTAPE void DukPush(DukContext &dc); #endif bool SetValue(bool value); void operator=(bool value) { SetValue(value); } bool SetValue(std::string value); bool SetValue(dbcNumber& value); void operator=(std::string value) { SetValue(value); } void Clear(); bool CheckPersist(); void RefreshPersist(); protected: bool m_value; bool* m_valuep; }; class OvmsMetricInt : public OvmsMetric { public: OvmsMetricInt(const char* name, uint16_t autostale=0, metric_unit_t units = Other, bool persist = false); virtual ~OvmsMetricInt(); public: std::string AsString(const char* defvalue = "", metric_unit_t units = Other, int precision = -1); virtual std::string AsJSON(const char* defvalue = "", metric_unit_t units = Other, int precision = -1); float AsFloat(const float defvalue = 0, metric_unit_t units = Other); int AsInt(const int defvalue = 0, metric_unit_t units = Other); #ifdef CONFIG_OVMS_SC_JAVASCRIPT_DUKTAPE void DukPush(DukContext &dc); #endif bool SetValue(int value, metric_unit_t units = Other); void operator=(int value) { SetValue(value); } bool SetValue(std::string value); bool SetValue(dbcNumber& value); void operator=(std::string value) { SetValue(value); } void Clear(); bool CheckPersist(); void RefreshPersist(); protected: int m_value; int* m_valuep; }; class OvmsMetricFloat : public OvmsMetric { public: OvmsMetricFloat(const char* name, uint16_t autostale=0, metric_unit_t units = Other, bool persist = false); virtual ~OvmsMetricFloat(); public: std::string AsString(const char* defvalue = "", metric_unit_t units = Other, int precision = -1); virtual std::string AsJSON(const char* defvalue = "", metric_unit_t units = Other, int precision = -1); float AsFloat(const float defvalue = 0, metric_unit_t units = Other); int AsInt(const int defvalue = 0, metric_unit_t units = Other); #ifdef CONFIG_OVMS_SC_JAVASCRIPT_DUKTAPE void DukPush(DukContext &dc); #endif bool SetValue(float value, metric_unit_t units = Other); void operator=(float value) { SetValue(value); } bool SetValue(std::string value); bool SetValue(dbcNumber& value); void operator=(std::string value) { SetValue(value); } void Clear(); virtual bool CheckPersist(); virtual void RefreshPersist(); protected: float m_value; float* m_valuep; }; class OvmsMetricString : public OvmsMetric { public: OvmsMetricString(const char* name, uint16_t autostale=0, metric_unit_t units = Other, bool persist = false); virtual ~OvmsMetricString(); public: std::string AsString(const char* defvalue = "", metric_unit_t units = Other, int precision = -1); #ifdef CONFIG_OVMS_SC_JAVASCRIPT_DUKTAPE void DukPush(DukContext &dc); #endif bool SetValue(std::string value); void operator=(std::string value) { SetValue(value); } void Clear(); virtual bool IsString() { return true; }; protected: OvmsMutex m_mutex; std::string m_value; }; /** * OvmsMetricBitset: metric wrapper for std::bitset * - string representation as comma separated bit positions (beginning at startpos) of set bits */ template class OvmsMetricBitset : public OvmsMetric { public: OvmsMetricBitset(const char* name, uint16_t autostale=0, metric_unit_t units = Other) : OvmsMetric(name, autostale, units, false) { } virtual ~OvmsMetricBitset() { } public: std::string AsString(const char* defvalue = "", metric_unit_t units = Other, int precision = -1) { if (!IsDefined()) return std::string(defvalue); std::ostringstream ss; OvmsMutexLock lock(&m_mutex); for (int i = 0; i < N; i++) { if (m_value[i]) { if (ss.tellp() > 0) ss << ','; ss << startpos + i; } } return ss.str(); } virtual std::string AsJSON(const char* defvalue = "", metric_unit_t units = Other, int precision = -1) { std::string json = "["; json += AsString(defvalue, units, precision); json += "]"; return json; } bool SetValue(std::string value) { std::bitset n_value; std::istringstream vs(value); std::string token; int elem; while(std::getline(vs, token, ',')) { std::istringstream ts(token); ts >> elem; elem -= startpos; if (elem >= 0 && elem < N) n_value[elem] = 1; } return SetValue(n_value); } void operator=(std::string value) { SetValue(value); } std::bitset AsBitset(const std::bitset defvalue = std::bitset(0), metric_unit_t units = Other) { if (!IsDefined()) return defvalue; OvmsMutexLock lock(&m_mutex); return m_value; } #ifdef CONFIG_OVMS_SC_JAVASCRIPT_DUKTAPE void DukPush(DukContext &dc) { std::bitset value; { OvmsMutexLock lock(&m_mutex); value = m_value; } dc.PushArray(); int cnt = 0; for (int i = 0; i < N; i++) { if (value[i]) { dc.Push(i); dc.PutProp(-2, cnt++); } } } #endif bool SetValue(std::bitset value, metric_unit_t units = Other) { if (m_mutex.Lock()) { bool modified = false; if (m_value != value) { m_value = value; modified = true; } m_mutex.Unlock(); SetModified(modified); return modified; } return false; } void operator=(std::bitset value) { SetValue(value); } protected: OvmsMutex m_mutex; std::bitset m_value; }; /** * OvmsMetricSet: metric wrapper for std::set * - string representation as comma separated values * - be aware this is a memory eater, only use if necessary */ template class OvmsMetricSet : public OvmsMetric { public: OvmsMetricSet(const char* name, uint16_t autostale=0, metric_unit_t units = Other) : OvmsMetric(name, autostale, units, false) { } virtual ~OvmsMetricSet() { } public: std::string AsString(const char* defvalue = "", metric_unit_t units = Other, int precision = -1) { if (!IsDefined()) return std::string(defvalue); std::ostringstream ss; OvmsMutexLock lock(&m_mutex); for (auto i = m_value.begin(); i != m_value.end(); i++) { if (ss.tellp() > 0) ss << ','; ss << *i; } return ss.str(); } virtual std::string AsJSON(const char* defvalue = "", metric_unit_t units = Other, int precision = -1) { std::string json = "["; json += AsString(defvalue, units, precision); json += "]"; return json; } bool SetValue(std::string value) { std::set n_value; std::istringstream vs(value); std::string token; ElemType elem; while(std::getline(vs, token, ',')) { std::istringstream ts(token); ts >> elem; n_value.insert(elem); } return SetValue(n_value); } void operator=(std::string value) { SetValue(value); } std::set AsSet(const std::set defvalue = std::set(), metric_unit_t units = Other) { if (!IsDefined()) return defvalue; OvmsMutexLock lock(&m_mutex); return m_value; } #ifdef CONFIG_OVMS_SC_JAVASCRIPT_DUKTAPE void DukPush(DukContext &dc) { std::set value; { OvmsMutexLock lock(&m_mutex); value = m_value; } dc.PushArray(); int cnt = 0; for (auto i = value.begin(); i != value.end(); i++) { dc.Push(*i); dc.PutProp(-2, cnt++); } } #endif bool SetValue(std::set value, metric_unit_t units = Other) { if (m_mutex.Lock()) { bool modified = false; if (m_value != value) { m_value = value; modified = true; } m_mutex.Unlock(); SetModified(modified); return modified; } return false; } void operator=(std::set value) { SetValue(value); } protected: OvmsMutex m_mutex; std::set m_value; }; /** * OvmsMetricVector: metric wrapper for std::vector * - string representation as comma separated values * - TODO: escaping / string encoding for non-numeric types * - TODO: unit conversions * * Usage example: * OvmsMetricVector* vf = new OvmsMetricVector("test.volts", SM_STALE_MIN, Volts); * vf->SetElemValue(3, 1.23); * vf->SetElemValue(17, 2.34); * float myvals[3] = { 5.5, 6.6, 7.7 }; * vf->SetElemValues(10, 3, myvals); * * Note: use ExtRamAllocator for large vectors (= use SPIRAM) * * Persistence can only be used on ElemTypes fitting into a pmetrics storage container. * A persistent vector will need 1+size pmetrics slots. It will allocate new slots as * needed when growing, but the slots will remain used when shrinking the vector. If any * new element cannot allocate a pmetrics slot, the whole vector loses its persistence. * * Unit conversion currently casts to and from float for the conversion, it's assumed to * only be necessary for floating point values here. If you need int conversion, rework * UnitConvert() into a template. */ template < typename ElemType, class Allocator = std::allocator > class OvmsMetricVector : public OvmsMetric { public: OvmsMetricVector(const char* name, uint16_t autostale=0, metric_unit_t units = Other, bool persist = false) : OvmsMetric(name, autostale, units, persist) { m_valuep_size = NULL; if (!persist) return; // Ensure ElemType fits into persistent value: if (sizeof(ElemType) > sizeof(persistent_value_t)) return; // Vector persistence is currently implemented by a size entry with the metric name // + one additional entry per element using the metric name extended by the element index. // This may be optimized when the persistence system supports arrays. struct persistent_values *vp = pmetrics_register(m_name); if (!vp) return; m_valuep_size = reinterpret_cast(&vp->value); std::size_t psize = *m_valuep_size; if (SetPersistSize(psize)) { SetModified(true); ESP_LOGI(TAG, "persist %s = %s", m_name, AsUnitString().c_str()); } } virtual ~OvmsMetricVector() { } private: bool SetPersistSize(std::size_t new_size) { // Used by the constructor for initial read and by later SetValue() calls // on vector size changes: // m_persist false => register & read persistent values // m_persist true => only register new elements // // Persistent element pointers will be retained on shrinking, and reused // when the size grows again. Overall vector persistence is cancelled if any // new element fails to register. if (!m_valuep_size) return false; std::size_t old_size = m_valuep_elem.size(); if (new_size <= old_size) { *m_valuep_size = new_size; m_persist = true; return false; } m_valuep_elem.resize(new_size); if (!m_persist) m_value.resize(new_size); struct persistent_values *vp; char elem_name[100]; for (std::size_t i = old_size; i < new_size; i++) { snprintf(elem_name, sizeof(elem_name), "%s_%u", m_name, i); vp = pmetrics_register(elem_name); if (!vp) { // if any element fails to register, the whole vector persistence fails: ESP_LOGE(TAG, "%s persistence lost: can't register slot for elem index %u", m_name, i); *m_valuep_size = 0; m_valuep_size = NULL; m_valuep_elem.resize(0); if (!m_persist) m_value.resize(0); m_persist = false; return false; } else { m_valuep_elem[i] = reinterpret_cast(&vp->value); if (!m_persist) m_value[i] = *m_valuep_elem[i]; } } *m_valuep_size = new_size; m_persist = true; return true; } public: bool CheckPersist() { if (!m_persist || !m_valuep_size || !IsDefined()) return true; if (*m_valuep_size != m_value.size()) { ESP_LOGE(TAG, "CheckPersist: bad value for %s", m_name); return false; } persistent_values *vp = pmetrics_find(m_name); if (vp == NULL) { ESP_LOGE(TAG, "CheckPersist: can't find %s", m_name); return false; } if (m_valuep_size != reinterpret_cast(&vp->value)) { ESP_LOGE(TAG, "CheckPersist: bad address for %s", m_name); return false; } for (int i = 0; i < m_value.size(); i++) { if (*m_valuep_elem[i] != m_value[i]) { ESP_LOGE(TAG, "CheckPersist: bad value for %s[%d]", m_name, i); return false; } } return true; } void RefreshPersist() { if (m_persist && m_valuep_size && IsDefined()) { *m_valuep_size = m_value.size(); for (int i = 0; i < m_value.size(); i++) *m_valuep_elem[i] = m_value[i]; } } public: virtual std::string AsString(const char* defvalue = "", metric_unit_t units = Other, int precision = -1) { if (!IsDefined()) return std::string(defvalue); std::ostringstream ss; if (precision >= 0) { ss.precision(precision); ss << fixed; } OvmsMutexLock lock(&m_mutex); for (auto i = m_value.begin(); i != m_value.end(); i++) { if (ss.tellp() > 0) ss << ','; if (units != Other && units != m_units) ss << (ElemType) UnitConvert(m_units, units, (float)*i); else ss << *i; } return ss.str(); } virtual std::string ElemAsString(size_t n, const char* defvalue = "", metric_unit_t units = Other, int precision = -1, bool addunitlabel = false) { OvmsMutexLock lock(&m_mutex); if (!IsDefined() || m_value.size() <= n) return std::string(defvalue); std::ostringstream ss; if (precision >= 0) { ss.precision(precision); ss << fixed; } if (units != Other && units != m_units) ss << (ElemType) UnitConvert(m_units, units, (float)m_value[n]); else ss << m_value[n]; if (addunitlabel) ss << OvmsMetricUnitLabel(units == Native ? GetUnits() : units); return ss.str(); } std::string ElemAsUnitString(size_t n, const char* defvalue = "", metric_unit_t units = Other, int precision = -1) { return ElemAsString(n, defvalue, units, precision, true); } virtual std::string AsJSON(const char* defvalue = "", metric_unit_t units = Other, int precision = -1) { std::string json = "["; json += AsString(defvalue, units, precision); json += "]"; return json; } #ifdef CONFIG_OVMS_SC_JAVASCRIPT_DUKTAPE void DukPush(DukContext &dc) { std::vector value; { OvmsMutexLock lock(&m_mutex); value = m_value; } dc.PushArray(); int cnt = 0; for (auto i = value.begin(); i != value.end(); i++) { dc.Push(*i); dc.PutProp(-2, cnt++); } } #endif virtual bool SetValue(std::string value) { std::vector n_value; std::istringstream vs(value); std::string token; ElemType elem; while(std::getline(vs, token, ',')) { std::istringstream ts(token); ts >> elem; n_value.push_back(elem); } return SetValue(n_value); } void operator=(std::string value) { SetValue(value); } bool SetValue(const std::vector& value, metric_unit_t units = Other) { bool modified = false, resized = false; if (m_mutex.Lock()) { if (m_value.size() != value.size()) { m_value.resize(value.size()); if (m_persist) SetPersistSize(value.size()); resized = true; } for (size_t i = 0; i < value.size(); i++) { ElemType ivalue; if (units != Other && units != m_units) ivalue = (ElemType) UnitConvert(units, m_units, (float)value[i]); else ivalue = value[i]; if (resized || m_value[i] != ivalue) { m_value[i] = ivalue; modified = true; if (m_persist) *m_valuep_elem[i] = ivalue; } } m_mutex.Unlock(); SetModified(modified); } return modified; } void operator=(std::vector value) { SetValue(value); } void ClearValue() { if (IsDefined()) { if (m_mutex.Lock()) { if (m_persist) SetPersistSize(0); m_value.clear(); m_mutex.Unlock(); SetModified(true); } } } std::vector AsVector(const std::vector defvalue = std::vector(), metric_unit_t units = Other) { if (!IsDefined()) return defvalue; OvmsMutexLock lock(&m_mutex); return m_value; } ElemType GetElemValue(size_t n) { ElemType val{}; OvmsMutexLock lock(&m_mutex); if (m_value.size() > n) val = m_value[n]; return val; } void SetElemValue(size_t n, const ElemType nvalue, metric_unit_t units = Other) { ElemType value; if (units != Other && units != m_units) value = (ElemType) UnitConvert(units, m_units, (float)nvalue); else value = nvalue; bool modified = false, resized = false; if (m_mutex.Lock()) { if (m_value.size() < n+1) { m_value.resize(n+1); if (m_persist) SetPersistSize(n+1); resized = true; } if (resized || m_value[n] != value) { m_value[n] = value; modified = true; if (m_persist) *m_valuep_elem[n] = value; } m_mutex.Unlock(); } SetModified(modified); } void SetElemValues(size_t start, size_t cnt, const ElemType* values, metric_unit_t units = Other) { bool modified = false, resized = false; if (m_mutex.Lock()) { if (m_value.size() < start+cnt) { m_value.resize(start+cnt); if (m_persist) SetPersistSize(start+cnt); resized = true; } for (size_t i = 0; i < cnt; i++) { ElemType ivalue; if (units != Other && units != m_units) ivalue = (ElemType) UnitConvert(units, m_units, (float)values[i]); else ivalue = values[i]; if (resized || m_value[start+i] != ivalue) { m_value[start+i] = ivalue; modified = true; if (m_persist) *m_valuep_elem[start+i] = ivalue; } } m_mutex.Unlock(); } SetModified(modified); } uint32_t GetSize() { return m_value.size(); } protected: OvmsMutex m_mutex; std::vector m_value; std::size_t* m_valuep_size; std::vector m_valuep_elem; }; typedef std::function MetricCallback; class MetricCallbackEntry { public: MetricCallbackEntry(const char* caller, MetricCallback callback); virtual ~MetricCallbackEntry(); public: const char *m_caller; MetricCallback m_callback; }; typedef std::list MetricCallbackList; typedef std::map MetricCallbackMap; class OvmsMetrics { public: OvmsMetrics(); virtual ~OvmsMetrics(); public: void RegisterMetric(OvmsMetric* metric); void DeregisterMetric(OvmsMetric* metric); public: bool Set(const char* metric, const char* value); bool SetInt(const char* metric, int value); bool SetBool(const char* metric, bool value); bool SetFloat(const char* metric, float value); OvmsMetric* Find(const char* metric); OvmsMetricInt *InitInt(const char* metric, uint16_t autostale=0, int value=0, metric_unit_t units = Other, bool persist = false); OvmsMetricBool *InitBool(const char* metric, uint16_t autostale=0, bool value=0, metric_unit_t units = Other, bool persist = false); OvmsMetricFloat *InitFloat(const char* metric, uint16_t autostale=0, float value=0, metric_unit_t units = Other, bool persist = false); OvmsMetricString *InitString(const char* metric, uint16_t autostale=0, const char* value=NULL, metric_unit_t units = Other); template OvmsMetricBitset *InitBitset(const char* metric, uint16_t autostale=0, const char* value=NULL, metric_unit_t units = Other) { OvmsMetricBitset *m = (OvmsMetricBitset *)Find(metric); if (m==NULL) m = new OvmsMetricBitset(metric, autostale, units); if (value && !m->IsDefined()) m->SetValue(value); return m; } template OvmsMetricSet *InitSet(const char* metric, uint16_t autostale=0, const char* value=NULL, metric_unit_t units = Other) { OvmsMetricSet *m = (OvmsMetricSet *)Find(metric); if (m==NULL) m = new OvmsMetricSet(metric, autostale, units); if (value && !m->IsDefined()) m->SetValue(value); return m; } template OvmsMetricVector *InitVector(const char* metric, uint16_t autostale=0, const char* value=NULL, metric_unit_t units = Other, bool persist = false) { OvmsMetricVector *m = (OvmsMetricVector *)Find(metric); if (m==NULL) m = new OvmsMetricVector(metric, autostale, units, persist); if (value && !m->IsDefined()) m->SetValue(value); return m; } public: void RegisterListener(const char* caller, const char* name, MetricCallback callback); void DeregisterListener(const char* caller); void NotifyModified(OvmsMetric* metric); protected: MetricCallbackMap m_listeners; public: size_t RegisterModifier(); public: void EventSystemShutDown(std::string event, void* data); protected: size_t m_nextmodifier; public: OvmsMetric* m_first; bool m_trace; }; extern OvmsMetrics MyMetrics; #undef TAG #endif //#ifndef __METRICS_H__