/* ; 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. */ #include "ovms_log.h" static const char *TAG = "metrics"; #include #include #include #include #include #include "ovms.h" #include "ovms_metrics.h" #include "ovms_command.h" #include "ovms_events.h" #include "ovms_script.h" #include "rom/rtc.h" #include "string.h" using namespace std; #define PERSISTENT_METRICS_MAGIC (('O' << 24) | ('V' << 16) | ('M' << 8) | '3') #define PERSISTENT_VERSION 3 // increment when struct is changed RTC_NOINIT_ATTR persistent_metrics pmetrics; // persistent storage container #define NUM_PERSISTENT_VALUES sizeof_array(pmetrics.values) static const char* pmetrics_reason; // reason pmetrics was zeroed std::map pmetrics_keymap // hash key → metric name map (registry) __attribute__ ((init_priority (1800))); OvmsMetrics MyMetrics __attribute__ ((init_priority (1800))); typedef enum : uint8_t { GrpNone, GrpOther, GrpDistance, GrpTemp, GrpPressure, GrpPower, GrpEnergy, GrpTime, GrpDirection, GrpSpeed, GrpAccel, GrpSignal, GrpConsumption, GrpTorque, // These are where a dimension group is split and allows // easily folding the 'short distances' back onto their equivalents. GrpDistanceShort = GrpDistance + 0x80, GrpAccelShort = GrpAccel + 0x80 } metric_group_t; struct OvmsUnitInfo { const char *UnitCode; //< The UnitCode identifying the unit const char *Label; //< The suffix to print against the value metric_unit_t MetricUnit; //< The Metric equivalent if there is one. metric_unit_t ImperialUnit; //< The Imperial equivalent if there is one. metric_group_t Group; //< The conversion group it belongs to. }; #define UNIT_GAP {NULL,NULL, UnitNotFound, UnitNotFound, GrpNone} // Mapping for information on metric info static const OvmsUnitInfo unit_info[int(MetricUnitLast)+1] = { // Unit Code Label Metric Unt Imperial Unt Unit Group {"native", "", Native, Native, GrpNone }, // 0 {"metric", "", Native, Native, GrpNone }, // 1 {"imperial", "", Native, Native, GrpNone }, // 2 UNIT_GAP, // 3 UNIT_GAP, // 4 UNIT_GAP, // 5 UNIT_GAP, // 6 UNIT_GAP, // 7 UNIT_GAP, // 8 UNIT_GAP, // 9 {"km", "km", Native, Miles, GrpDistance }, // 10 {"miles", "M", Kilometers, Native, GrpDistance }, // 11 {"meters", "m", Native, Feet, GrpDistanceShort }, // 12 {"feet", "ft", Meters, Native, GrpDistanceShort }, // 13 UNIT_GAP, // 14 UNIT_GAP, // 15 UNIT_GAP, // 16 UNIT_GAP, // 17 UNIT_GAP, // 18 UNIT_GAP, // 19 {"celcius", "°C", Native, Fahrenheit, GrpTemp }, // 20 {"fahrenheit","°F", Celcius, Native, GrpTemp }, // 21 UNIT_GAP, // 22 UNIT_GAP, // 23 UNIT_GAP, // 24 UNIT_GAP, // 25 UNIT_GAP, // 26 UNIT_GAP, // 27 UNIT_GAP, // 28 UNIT_GAP, // 29 {"kpa", "kPa", Native, PSI, GrpPressure}, // 30 {"pa", "Pa", Native, PSI, GrpPressure}, // 31 {"psi", "psi", kPa, Native, GrpPressure}, // 32 UNIT_GAP, // 33 UNIT_GAP, // 34 UNIT_GAP, // 35 UNIT_GAP, // 36 UNIT_GAP, // 37 UNIT_GAP, // 38 UNIT_GAP, // 39 {"volts", "V", Native, Native, GrpOther }, // 40 {"amps", "A", Native, Native, GrpOther }, // 41 {"amphours", "Ah", Native, Native, GrpOther }, // 42 {"kw", "kW", Native, Native, GrpPower }, // 43 {"kwh", "kWh", Native, Native, GrpEnergy}, // 44 {"watts", "W", Native, Native, GrpPower }, // 45 {"watthours","Wh", Native, Native, GrpEnergy}, // 46 UNIT_GAP, // 47 UNIT_GAP, // 48 UNIT_GAP, // 49 {"seconds", "Sec", Native, Native, GrpTime}, // 50 {"minutes", "Min", Native, Native, GrpTime}, // 51 {"hours", "Hour", Native, Native, GrpTime}, // 52 {"utc", "UTC", Native, Native, GrpTime}, // 53 {"localtz", "local", Native, Native, GrpTime}, // 54, UNIT_GAP,// 55 UNIT_GAP,// 56 UNIT_GAP,// 57 UNIT_GAP,// 58 UNIT_GAP,// 59 {"degrees", "°", Native, Native, GrpDirection}, // 60 {"kmph", "km/h", Native, Mph, GrpSpeed}, // 61 {"miph", "Mph", Kph, Native, GrpSpeed}, // 62 UNIT_GAP,// 63 UNIT_GAP,// 64 UNIT_GAP,// 65 UNIT_GAP,// 66 UNIT_GAP,// 67 UNIT_GAP,// 68 UNIT_GAP,// 69 UNIT_GAP,// 70 // Acceleration: {"kmphps", "km/h/s", Native, MphPS, GrpAccel}, // 71 {"miphps", "Mph/s", KphPS, Native, GrpAccel}, // 72 {"mpss", "m/s²", Native, FeetPSS, GrpAccelShort}, // 73 {"ftpss", "ft/s²", MetersPSS, Native, GrpAccelShort}, // 74 UNIT_GAP,// 75 UNIT_GAP,// 76 UNIT_GAP,// 77 UNIT_GAP,// 78 UNIT_GAP,// 79 {"dbm", "dBm", Native, sq, GrpSignal}, // 80 {"sq", "sq", dbm, Native, GrpSignal}, // 81 UNIT_GAP,// 82 UNIT_GAP,// 83 UNIT_GAP,// 84 UNIT_GAP,// 85 UNIT_GAP,// 86 UNIT_GAP,// 87 UNIT_GAP,// 88 UNIT_GAP,// 89 {"percent", "%", Native, Native, GrpOther}, // 90 UNIT_GAP,// 91 UNIT_GAP,// 92 UNIT_GAP,// 93 UNIT_GAP,// 94 UNIT_GAP,// 95 UNIT_GAP,// 96 UNIT_GAP,// 97 UNIT_GAP,// 98 UNIT_GAP,// 99 // Energy consumption: {"whpkm", "Wh/km", Native, WattHoursPM, GrpConsumption}, // 100 {"whpmi", "Wh/mi", WattHoursPK,Native, GrpConsumption}, // 101 {"kwhp100km","kWh/100km",Native, WattHoursPM, GrpConsumption}, // 102 {"kmpkwh", "km/kWh", Native, MPkWh, GrpConsumption}, // 103 {"mipkwh", "mi/kWh", KPkWh, Native, GrpConsumption}, // 104 UNIT_GAP,// 105 UNIT_GAP,// 106 UNIT_GAP,// 107 UNIT_GAP,// 108 UNIT_GAP,// 109 // Torque: {"nm", "Nm", Native, Native, GrpTorque} // 110 }; #undef UNIT_GAP static inline int mi_to_km(int mi) { return mi * 4023 / 2500; // mi * 1.6092 } static inline float mi_to_km(float mi) { return mi * 1.609347; } static inline double mi_to_km(double mi) { return mi * 1.609347; } static inline int km_to_mi(int km) { return km * 2500 / 4023; // km / 1.6092 } static inline float km_to_mi(float km) { return km * 0.6213700; // km / 1.609347; } static inline double km_to_mi(double km) { return km * 0.6213700; // 1 / 1.609347; } const int feet_per_mile = 5280; // Alias for reading clarity. template T pmi_to_pkm(T pmi) { return km_to_mi(pmi); } // Alias for reading clarity. template T pkm_to_pmi(T pkm) { return mi_to_km(pkm); } /* * Returns the group of the metric. * simplify - Means those separated for (eventual) user config * are folded to one metric. */ static metric_group_t GetMetricGroup(metric_unit_t unit) { uint8_t unit_i = static_cast(unit); if (unit_i <= uint8_t(MetricUnitLast)) return unit_info[unit_i].Group; return GrpNone; } static inline metric_group_t GetMetricGroupSimplify(metric_unit_t unit) { // Removes High-bit to fold the 'Short' metrics back onto their equivalents. return static_cast(static_cast(GetMetricGroup(unit)) & 0x7f); } /* * Modifies the 'to' target to match the real target (or Native for no change). * Handles ToMetric/ToImperial conversion types. * * full_check takes into account whether a conversion CAN be done (used for * printing correct labels) */ static void CheckTargetUnit(metric_unit_t from, metric_unit_t &to, bool full_check) { if (from == Other) { to = from; return; } switch (to) { case Native: break; case ToMetric: { uint8_t unit_i = static_cast(from); if (unit_i <= uint8_t(MetricUnitLast)) to = unit_info[unit_i].MetricUnit; break; } case ToImperial: { uint8_t unit_i = static_cast(from); if (unit_i <= uint8_t(MetricUnitLast)) to = unit_info[unit_i].ImperialUnit; break; } default: if (to == from) to = Native; else if (full_check) { metric_group_t from_grp = GetMetricGroupSimplify(from); if (from_grp == GrpNone || from_grp == GrpOther) to = Native; else if (from_grp != GetMetricGroupSimplify(to)) to = Native; } break; } } void metrics_list(int verbosity, OvmsWriter* writer, OvmsCommand* cmd, int argc, const char* const* argv) { bool found = false; bool show_staleness = false; bool show_set = false; bool only_persist = false; bool display_strings = false; metric_unit_t def_unit = Native; const char* show_only = NULL; int i; for (i=0;iPutUsage(writer); return; } show_only = cp; continue; } for (++cp; *cp != '\0'; ++cp) { switch (*cp) { case 'c': show_set = true; break; case 'i': def_unit = ToImperial; break; case 'm': def_unit = ToMetric; break; case 'p': only_persist = true; break; case 's': show_staleness = true; break; case 't': display_strings = true; break; default: cmd->PutUsage(writer); return; } } } for (OvmsMetric* m=MyMetrics.m_first; m != NULL; m=m->m_next) { if (only_persist && !m->m_persist) continue; const char *k = m->m_name; if (show_only != NULL && strstr(k, show_only) == NULL) continue; found = true; if (show_set) { if (m->IsDefined()) writer->printf("metrics set %s %s\n", k, m->AsString().c_str()); continue; } metric_unit_t use_unit = def_unit; metric_unit_t my_unit = m->GetUnits(); if (my_unit == TimeUTC) use_unit = TimeLocal; else { CheckTargetUnit(my_unit, use_unit, true); if (use_unit == Native) use_unit = my_unit; } std::string v = m->AsUnitString("", use_unit); if (show_staleness) { int age = m->Age(); if (age>99) age=99; if (v.empty()) writer->printf("[---] ",k); else writer->printf("[%02d%c] ", age, (m->IsStale() ? 'S' : '-' )); } if (v.empty()) { writer->printf("%s\n",k); continue; } // Apply (linux) "cat -t" semantics to strings const char *s; if (!display_strings || !m->IsString()) s = v.c_str(); else s = display_encode(v).c_str(); writer->printf("%-40.40s %s\n", k, s); } if (show_only && !found) writer->puts("Unrecognised metric name"); } void metrics_persist(int verbosity, OvmsWriter* writer, OvmsCommand* cmd, int argc, const char* const* argv) { if (argc > 0) { if (strcmp(argv[0], "-r") != 0) { cmd->PutUsage(writer); return; } pmetrics.magic = 0; } if (pmetrics.magic != PERSISTENT_METRICS_MAGIC) writer->puts("Persistent metrics will be reset on the next boot"); writer->printf("version %d, ", pmetrics.version); writer->printf("serial %d, ", pmetrics.serial); if (pmetrics_reason != NULL) writer->printf("%s caused reset, ", pmetrics_reason); writer->printf("%d bytes, and ", pmetrics.size); writer->printf("%d of %d slots used\n", pmetrics.used, NUM_PERSISTENT_VALUES); } void metrics_set(int verbosity, OvmsWriter* writer, OvmsCommand* cmd, int argc, const char* const* argv) { const char *unit = NULL; if (argc > 2) unit = argv[2]; if (MyMetrics.Set(argv[0],argv[1], unit)) writer->puts("Metric set"); else writer->puts("Metric could not be set"); } void metrics_get(int verbosity, OvmsWriter* writer, OvmsCommand* cmd, int argc, const char* const* argv) { const char *unit = NULL; if (argc > 1) unit = argv[1]; std::string str = MyMetrics.GetUnitStr(argv[0], unit); writer->puts(str.c_str()); } bool pmetrics_check() { bool ret = true; if (pmetrics.magic != PERSISTENT_METRICS_MAGIC) { ESP_LOGE(TAG, "pmetrics_check: bad magic"); ret = false; } if (pmetrics.version != PERSISTENT_VERSION) { ESP_LOGE(TAG, "pmetrics_check: bad version"); ret = false; } if (pmetrics.size != sizeof(pmetrics)) { ESP_LOGE(TAG, "pmetrics_check: bad size"); ret = false; } if ((pmetrics.used < 0 || pmetrics.used > NUM_PERSISTENT_VALUES)) { ESP_LOGE(TAG, "pmetrics_check: out of range used"); ret = false; } for (OvmsMetric* m = MyMetrics.m_first; m != NULL; m = m->m_next) { if (m->m_persist && !m->CheckPersist()) { ret = false; break; } } return ret; } persistent_values *pmetrics_find(const char *name) { int i; persistent_values *vp; std::size_t namehash = std::hash{}(name); for (i = 0, vp = pmetrics.values; i < pmetrics.used; ++i, ++vp) { if (vp->namehash == namehash) return vp; } return NULL; } void pmetrics_init(bool refresh = false) { memset(&pmetrics, 0, sizeof(pmetrics)); pmetrics.magic = PERSISTENT_METRICS_MAGIC; pmetrics.version = PERSISTENT_VERSION; pmetrics.size = sizeof(persistent_metrics); if (refresh) { for (OvmsMetric* m = MyMetrics.m_first; m != NULL; m = m->m_next) m->RefreshPersist(); } } persistent_values *pmetrics_register(const char *name) { int i; persistent_values *vp; std::size_t namehash = std::hash{}(name); // check for hash collision: auto it = pmetrics_keymap.find(namehash); if (it != pmetrics_keymap.end() && strcmp(it->second, name) != 0) { ESP_LOGE(TAG, "pmetrics_register: cannot persist '%s' due to hash collision with '%s'", name, it->second); return NULL; } // find slot: for (i = 0, vp = pmetrics.values; i < pmetrics.used; ++i, ++vp) { if (vp->namehash == namehash) break; } // not found? assign to next free slot: if (i >= pmetrics.used) { if (i >= NUM_PERSISTENT_VALUES) { ESP_LOGE(TAG, "pmetrics_register: no free slots, cannot persist '%s'", name); return NULL; } vp->namehash = namehash; memset(&vp->value, 0, sizeof(vp->value)); ++pmetrics.used; } ESP_LOGD(TAG, "pmetrics_register: '%s' => slot=%d, used %d/%d", name, i, pmetrics.used, NUM_PERSISTENT_VALUES); pmetrics_keymap[namehash] = name; return vp; } void OvmsMetrics::EventSystemShutDown(std::string event, void* data) { /* Check for corruption and repair of possible before shutting down */ if (!pmetrics_check()) { ESP_LOGI(TAG, "Persistent metrics shutdown check failed"); pmetrics_init(true); } } void metrics_trace(int verbosity, OvmsWriter* writer, OvmsCommand* cmd, int argc, const char* const* argv) { if (strcmp(cmd->GetName(),"on")==0) MyMetrics.m_trace = true; else MyMetrics.m_trace = false; writer->printf("Metric tracing is now %s\n",cmd->GetName()); } void metrics_units(int verbosity, OvmsWriter* writer, OvmsCommand* cmd, int argc, const char* const* argv) { const char* show_only = NULL; int i; for (i=0;iPutUsage(writer); return; } show_only = cp; continue; } } bool found = false; for (metric_unit_t unit = MetricUnitFirst; unit <= MetricUnitLast; unit = metric_unit_t(1+(uint8_t)unit)) { const char *metric_name = OvmsMetricUnitName(unit); if (metric_name == NULL) continue; if (show_only != NULL && strstr(metric_name, show_only) == NULL) continue; const char *metric_label = OvmsMetricUnitLabel(unit); writer->printf("%12s : %s\n", metric_name, metric_label); found = true; } if (show_only && !found) writer->puts("Unrecognised unit name"); } #ifdef CONFIG_OVMS_SC_JAVASCRIPT_DUKTAPE static duk_ret_t DukOvmsMetricHasValue(duk_context *ctx) { DukContext dc(ctx); const char *mn = duk_to_string(ctx,0); OvmsMetric *m = MyMetrics.Find(mn); if (!m) return 0; dc.Push(m->IsDefined()); return 1; } static duk_ret_t DukOvmsMetricValue(duk_context *ctx) { DukContext dc(ctx); const char *mn = duk_to_string(ctx,0); OvmsMetric *m = MyMetrics.Find(mn); if (!m) return 0; bool decode = true; const char *un = NULL; bool has_unit = false; if (duk_check_type_mask(ctx, 1, DUK_TYPE_MASK_BOOLEAN)) decode = duk_opt_boolean(ctx, 1, true); else { un = duk_opt_string(ctx, 1, NULL); decode = duk_opt_boolean(ctx, 2, true); has_unit = un != NULL; } metric_unit_t unit = OvmsMetricUnitFromName(un); if (m && unit != UnitNotFound) { if (decode) m->DukPush(dc, unit); else if (has_unit) dc.Push(m->AsUnitString("", unit)); else dc.Push(m->AsString("")); return 1; /* one return value */ } else return 0; } static duk_ret_t DukOvmsMetricJSON(duk_context *ctx) { const char *mn = duk_to_string(ctx,0); OvmsMetric *m = MyMetrics.Find(mn); if (m) { duk_push_string(ctx, m->AsJSON().c_str()); return 1; /* one return value */ } else return 0; } static duk_ret_t DukOvmsMetricFloat(duk_context *ctx) { const char *mn = duk_to_string(ctx,0); OvmsMetric *m = MyMetrics.Find(mn); const char *un = duk_opt_string(ctx,1,NULL); metric_unit_t unit = OvmsMetricUnitFromName(un); if (m && unit != UnitNotFound) { duk_push_number(ctx, float2double(m->AsFloat(0, unit))); return 1; /* one return value */ } else return 0; } static duk_ret_t DukOvmsMetricGetValues(duk_context *ctx) { OvmsMetric *m; DukContext dc(ctx); bool has_unit = false; bool decode = true; const char *un = NULL; if (duk_check_type_mask(ctx, 1, DUK_TYPE_MASK_BOOLEAN)) decode = duk_opt_boolean(ctx, 1, true); else { un = duk_opt_string(ctx, 1, NULL); has_unit = un != NULL; decode = duk_opt_boolean(ctx, 2, true); } metric_unit_t unit = OvmsMetricUnitFromName(un); duk_idx_t obj_idx = dc.PushObject(); // helper: set object property from metric auto set_metric = [&dc, obj_idx, decode, unit, has_unit](OvmsMetric *m) { if (decode) m->DukPush(dc, unit); else if (has_unit) dc.Push(m->AsUnitString("", unit)); else dc.Push(m->AsString()); dc.PutProp(obj_idx, m->m_name); }; if (duk_is_array(ctx, 0)) { // get metric names from array: for (int i=0; duk_get_prop_index(ctx, 0, i); i++) { m = MyMetrics.Find(duk_to_string(ctx, -1)); if (m) set_metric(m); duk_pop(ctx); } duk_pop(ctx); } else if (duk_is_object(ctx, 0)) { // get metric names from object properties: duk_enum(ctx, 0, 0); while (duk_next(ctx, -1, true)) { m = MyMetrics.Find(duk_to_string(ctx, -2)); if (m) set_metric(m); duk_pop_2(ctx); } duk_pop(ctx); } else { // simple metric name substring filter: const char *filter = duk_opt_string(ctx, 0, ""); for (m = MyMetrics.m_first; m; m = m->m_next) { if (*filter && !strstr(m->m_name, filter)) continue; set_metric(m); } } return 1; } #endif //#ifdef CONFIG_OVMS_SC_JAVASCRIPT_DUKTAPE MetricCallbackEntry::MetricCallbackEntry(std::string caller, MetricCallback callback) { m_caller = caller; m_callback = callback; } MetricCallbackEntry::~MetricCallbackEntry() { } OvmsMetrics::OvmsMetrics() { ESP_LOGI(TAG, "Initialising METRICS (1810)"); m_nextmodifier = 1; m_first = NULL; m_trace = false; // Register our commands OvmsCommand* cmd_metric = MyCommandApp.RegisterCommand("metrics","METRICS framework"); cmd_metric->RegisterCommand("list","Show all metrics", metrics_list, "[-cimpst] []\n" "Display a metric, show all by default\n" "-c = display persistent metrics set commands\n" "-i = display imperial units where possible\n" "-m = display metric units where possible\n" "-p = display only persistent metrics\n" "-s = show metric staleness\n" "-t = display non-printing characters and tabs in string metrics", 0, 2); cmd_metric->RegisterCommand("persist","Show persistent metrics info", metrics_persist, "[-r]\n" "-r = reset persistent metrics", 0, 1); cmd_metric->RegisterCommand("set","Set the value of a metric",metrics_set, " []", 2, 3); cmd_metric->RegisterCommand("get","Get the value of a metric",metrics_get, " []", 1, 2); cmd_metric->RegisterCommand("units","List available units",metrics_units, "[]",0,1); OvmsCommand* cmd_metrictrace = cmd_metric->RegisterCommand("trace","METRIC trace framework"); cmd_metrictrace->RegisterCommand("on","Turn metric tracing ON",metrics_trace); cmd_metrictrace->RegisterCommand("off","Turn metric tracing OFF",metrics_trace); #ifdef CONFIG_OVMS_SC_JAVASCRIPT_DUKTAPE ESP_LOGI(TAG, "Expanding DUKTAPE javascript engine"); DuktapeObjectRegistration* dto = new DuktapeObjectRegistration("OvmsMetrics"); dto->RegisterDuktapeFunction(DukOvmsMetricHasValue, 1, "HasValue"); dto->RegisterDuktapeFunction(DukOvmsMetricValue, 3, "Value"); dto->RegisterDuktapeFunction(DukOvmsMetricJSON, 1, "AsJSON"); dto->RegisterDuktapeFunction(DukOvmsMetricFloat, 2, "AsFloat"); dto->RegisterDuktapeFunction(DukOvmsMetricGetValues, 3, "GetValues"); MyDuktape.RegisterDuktapeObject(dto); #endif //#ifdef CONFIG_OVMS_SC_JAVASCRIPT_DUKTAPE /* Initialize persistent metrics on cold boot or corruption */ if (rtc_get_reset_reason(0) == POWERON_RESET || !pmetrics_check()) pmetrics_init(); ESP_LOGI(TAG, "Persistent metrics serial %u using %d bytes, %d/%d slots used", ++pmetrics.serial, sizeof(pmetrics), pmetrics.used, NUM_PERSISTENT_VALUES); // Register our event #undef bind // Kludgy, but works using std::placeholders::_1; using std::placeholders::_2; MyEvents.RegisterEvent(TAG, "system.shutdown", std::bind(&OvmsMetrics::EventSystemShutDown, this, _1, _2)); } OvmsMetrics::~OvmsMetrics() { for (OvmsMetric* m = m_first; m!=NULL;) { OvmsMetric* c = m; m = m->m_next; delete c; } } void OvmsMetrics::RegisterMetric(OvmsMetric* metric) { // Quick simple check for if we are the first metric. if (m_first == NULL) { m_first = metric; return; } // Now, check if we are before the first if (strcmp(m_first->m_name,metric->m_name)>=0) { metric->m_next = m_first; m_first = metric; return; } // Search for the correct place to insert for (OvmsMetric*m = m_first; m!=NULL; m=m->m_next) { if (m->m_next == NULL) { m->m_next = metric; return; } if (strcmp(m->m_next->m_name,metric->m_name)>=0) { metric->m_next = m->m_next; m->m_next = metric; return; } } } void OvmsMetrics::DeregisterMetric(OvmsMetric* metric) { if (m_first == metric) { m_first = metric->m_next; delete metric; return; } for (OvmsMetric* m=m_first;m!=NULL;m=m->m_next) { if (m->m_next == metric) { m->m_next = metric->m_next; delete metric; return; } } } std::string OvmsMetrics::GetUnitStr(const char* metric, const char *unit) { OvmsMetric* m = Find(metric); if (m == NULL) return "(not found)"; metric_unit_t metric_unit = Native; if (unit != NULL) { metric_unit_t found_unit = OvmsMetricUnitFromName(unit); if (found_unit == UnitNotFound) return "(invalid unit)"; metric_unit = found_unit; } return m->AsUnitString("(not set)", metric_unit); } bool OvmsMetrics::Set(const char* metric, const char* value, const char *unit) { OvmsMetric* m = Find(metric); if (m == NULL) return false; metric_unit_t metric_unit = Native; if (unit != NULL) { metric_unit_t found_unit = OvmsMetricUnitFromName(unit); if (found_unit == UnitNotFound) return false; metric_unit = found_unit; } m->SetValue(std::string(value), metric_unit); return true; } bool OvmsMetrics::SetInt(const char* metric, int value) { OvmsMetricInt* m = (OvmsMetricInt*)Find(metric); if (m == NULL) return false; m->SetValue(value); return true; } bool OvmsMetrics::SetBool(const char* metric, bool value) { OvmsMetricBool* m = (OvmsMetricBool*)Find(metric); if (m == NULL) return false; m->SetValue(value); return true; } bool OvmsMetrics::SetFloat(const char* metric, float value) { OvmsMetricFloat* m = (OvmsMetricFloat*)Find(metric); if (m == NULL) return false; m->SetValue(value); return true; } OvmsMetric* OvmsMetrics::Find(const char* metric) { for (OvmsMetric* m=m_first; m != NULL; m=m->m_next) { if (strcmp(m->m_name,metric)==0) return m; } return NULL; } OvmsMetricInt* OvmsMetrics::InitInt(const char* metric, uint16_t autostale, int value, metric_unit_t units, bool persist) { OvmsMetricInt *m = (OvmsMetricInt*)Find(metric); if (m==NULL) m = new OvmsMetricInt(metric, autostale, units, persist); if (!m->IsDefined()) m->SetValue(value); return m; } OvmsMetricBool* OvmsMetrics::InitBool(const char* metric, uint16_t autostale, bool value, metric_unit_t units, bool persist) { OvmsMetricBool *m = (OvmsMetricBool*)Find(metric); if (m==NULL) m = new OvmsMetricBool(metric, autostale, units, persist); if (!m->IsDefined()) m->SetValue(value); return m; } OvmsMetricFloat* OvmsMetrics::InitFloat(const char* metric, uint16_t autostale, float value, metric_unit_t units, bool persist) { OvmsMetricFloat *m = (OvmsMetricFloat*)Find(metric); if (m==NULL) m = new OvmsMetricFloat(metric, autostale, units, persist); if (!m->IsDefined()) m->SetValue(value); return m; } OvmsMetricString* OvmsMetrics::InitString(const char* metric, uint16_t autostale, const char* value, metric_unit_t units) { OvmsMetricString *m = (OvmsMetricString*)Find(metric); if (m==NULL) m = new OvmsMetricString(metric, autostale, units); if (value && !m->IsDefined()) m->SetValue(value); return m; } void OvmsMetrics::RegisterListener(std::string caller, std::string name, MetricCallback callback) { auto k = m_listeners.find(name); if (k == m_listeners.end()) { m_listeners[name] = new MetricCallbackList(); k = m_listeners.find(name); } if (k == m_listeners.end()) { ESP_LOGE(TAG, "Problem registering metric %s for caller %s",name.c_str(),caller.c_str()); return; } MetricCallbackList *ml = k->second; ml->push_back(new MetricCallbackEntry(caller,callback)); } void OvmsMetrics::DeregisterListener(std::string caller) { MetricCallbackMap::iterator itm=m_listeners.begin(); while (itm!=m_listeners.end()) { MetricCallbackList* ml = itm->second; MetricCallbackList::iterator itc=ml->begin(); while (itc!=ml->end()) { MetricCallbackEntry* ec = *itc; if (ec->m_caller == caller) { itc = ml->erase(itc); delete ec; } else { ++itc; } } if (ml->empty()) { itm = m_listeners.erase(itm); delete ml; } else { ++itm; } } } void OvmsMetrics::NotifyModified(OvmsMetric* metric) { if (m_trace && strcmp(metric->m_name, "m.monotonic") != 0 && strcmp(metric->m_name, "m.time.utc") != 0 && strcmp(metric->m_name, "v.e.parktime") != 0 && strcmp(metric->m_name, "v.e.drivetime") != 0 && strcmp(metric->m_name, "v.c.time") != 0) { ESP_LOGI(TAG, "Modified metric %s: %s", metric->m_name, metric->AsUnitString().c_str()); } auto k = m_listeners.find("*"); for (int x=0;x<2;x++) { if (k != m_listeners.end()) { MetricCallbackList* ml = k->second; if (ml) { for (MetricCallbackList::iterator itc=ml->begin(); itc!=ml->end(); ++itc) { MetricCallbackEntry* ec = *itc; ec->m_callback(metric); } } } k = m_listeners.find(metric->m_name); } } size_t OvmsMetrics::RegisterModifier() { return m_nextmodifier++; } OvmsMetric::OvmsMetric(const char* name, uint16_t autostale, metric_unit_t units, bool persist) { m_defined = NeverDefined; m_modified = 0; m_name = name; m_lastmodified = 0; m_autostale = autostale; m_stale = false; m_units = units; m_next = NULL; m_persist = false; // only set by metrics supporting persistence MyMetrics.RegisterMetric(this); } OvmsMetric::~OvmsMetric() { MyMetrics.DeregisterMetric(this); // Warning: pointers to a deleted OvmsMetric can still be held locally in // other modules. If you delete metrics, take care to inform all readers // (i.e. by broadcasting a module shutdown event). } std::string OvmsMetric::AsString(const char* defvalue, metric_unit_t units, int precision) { return std::string(defvalue); } std::string OvmsMetric::AsUnitString(const char* defvalue, metric_unit_t units, int precision) { if (!IsDefined()) return std::string(defvalue); // Need the converted unit for putting the label. auto currentUnits = GetUnits(); CheckTargetUnit(currentUnits, units, true); return AsString(defvalue, units, precision) + OvmsMetricUnitLabel(units==Native ? currentUnits : units); } std::string OvmsMetric::AsJSON(const char* defvalue, metric_unit_t units, int precision) { std::string buf = "\""; buf.append(json_encode(AsString(defvalue, units, precision))); buf.append("\""); return buf; } float OvmsMetric::AsFloat(const float defvalue, metric_unit_t units) { return defvalue; } #ifdef CONFIG_OVMS_SC_JAVASCRIPT_DUKTAPE void OvmsMetric::DukPush(DukContext &dc, metric_unit_t units) { dc.Push(AsString("", units)); } #endif bool OvmsMetric::SetValue(std::string value, metric_unit_t units) { return false; } bool OvmsMetric::SetValue(dbcNumber& value) { return false; } void OvmsMetric::operator=(std::string value) { } uint32_t OvmsMetric::LastModified() { return m_lastmodified; } uint32_t OvmsMetric::Age() { return monotonictime - m_lastmodified; } void OvmsMetric::SetModified(bool changed) { if (m_defined == NeverDefined) m_defined = FirstDefined; else m_defined = Defined; m_stale = false; m_lastmodified = monotonictime; if (changed) { m_modified = ULONG_MAX; MyMetrics.NotifyModified(this); } } bool OvmsMetric::IsDefined() { return (m_defined != NeverDefined); } bool OvmsMetric::IsFirstDefined() { return (m_defined == FirstDefined); } bool OvmsMetric::IsPersistent() { return m_persist; } bool OvmsMetric::CheckPersist() { return true; } void OvmsMetric::RefreshPersist() { } /** * IsStale: check if metric value has not been set within the staleness period / since marked stale * Note: a persistent metric won't be stale immediately after a reboot, because * it's unknown when the value was set before the reboot. If you need to assert * freshness, use IsFresh(). */ bool OvmsMetric::IsStale() { if (m_autostale > 0) { if (m_lastmodified + m_autostale < monotonictime) m_stale = true; else m_stale = false; } return m_stale; } /** * IsFresh: check if metric value has been set explicitly within the staleness period / since marked stale * Note: a persistent metric won't be fresh immediately after a reboot, because * it's unknown when the value was set before the reboot. It needs to receive a * live value to become fresh. */ bool OvmsMetric::IsFresh() { if (m_defined == NeverDefined) return false; else if (m_persist && m_defined == FirstDefined) return false; else return !IsStale(); } void OvmsMetric::SetStale(bool stale) { m_stale = stale; } void OvmsMetric::SetAutoStale(uint16_t seconds) { m_autostale = seconds; } metric_unit_t OvmsMetric::GetUnits() { return m_units; } bool OvmsMetric::IsModified(size_t modifier) { return m_modified & 1ul << modifier; } bool OvmsMetric::IsModifiedAndClear(size_t modifier) { unsigned long bit = 1ul << modifier; unsigned long mod = m_modified.fetch_and(~bit); return mod & bit; } void OvmsMetric::ClearModified(size_t modifier) { m_modified &= ~(1ul << modifier); } void OvmsMetric::Clear() { SetValue(""); m_defined = NeverDefined; m_stale = true; } OvmsMetricInt::OvmsMetricInt(const char* name, uint16_t autostale, metric_unit_t units, bool persist) : OvmsMetric(name, autostale, units, persist) { m_value = 0; m_valuep = NULL; m_persist = persist; if (m_persist) { persistent_values *vp = pmetrics_register(name); if (!vp) { m_persist = false; } else { m_valuep = reinterpret_cast(&vp->value); if (m_value != *m_valuep) { m_value = *m_valuep; SetModified(true); ESP_LOGI(TAG, "persist %s = %s", name, AsUnitString().c_str()); } } } } OvmsMetricInt::~OvmsMetricInt() { } bool OvmsMetricInt::CheckPersist() { if (!m_persist || !m_valuep || !IsDefined()) return true; if (*m_valuep != m_value) { 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 != reinterpret_cast(&vp->value)) { ESP_LOGE(TAG, "CheckPersist: bad address for %s", m_name); return false; } return true; } void OvmsMetricInt::RefreshPersist() { if (m_persist && m_valuep && IsDefined()) *m_valuep = m_value; } std::string OvmsMetricInt::AsString(const char* defvalue, metric_unit_t units, int precision) { if (IsDefined()) { char buffer[33]; int value = m_value; if ((units != Native)&&(units != m_units)) value = UnitConvert(m_units,units,m_value); if (units == TimeUTC || units == TimeLocal) { int seconds = value % 60; value /= 60; int minutes = value % 60; value /= 60; int hours = value; snprintf(buffer, sizeof(buffer), "%02u:%02u:%02u", hours, minutes, seconds); } else itoa (value,buffer,10); return buffer; } else { return std::string(defvalue); } } std::string OvmsMetricInt::AsJSON(const char* defvalue, metric_unit_t units, int precision) { if (IsDefined()) return AsString(defvalue, units, precision); else return std::string((defvalue && *defvalue) ? defvalue : "0"); } float OvmsMetricInt::AsFloat(const float defvalue, metric_unit_t units) { return (float)AsInt((int)defvalue, units); } int OvmsMetricInt::AsInt(const int defvalue, metric_unit_t units) { if (IsDefined()) { if ((units != Native)&&(units != m_units)) return UnitConvert(m_units,units,m_value); else return m_value; } else return defvalue; } #ifdef CONFIG_OVMS_SC_JAVASCRIPT_DUKTAPE void OvmsMetricInt::DukPush(DukContext &dc, metric_unit_t units) { dc.Push(AsInt(0, units)); } #endif bool OvmsMetricInt::SetValue(int value, metric_unit_t units) { int nvalue = value; if ((units != Other)&&(units != m_units)) nvalue=UnitConvert(units,m_units,value); if (m_value != nvalue) { m_value = nvalue; if (m_valuep) *m_valuep = m_value; SetModified(true); return true; } else { SetModified(false); return false; } } bool OvmsMetricInt::SetValue(std::string value, metric_unit_t units) { int nvalue = atoi(value.c_str()); return SetValue(nvalue, units); } bool OvmsMetricInt::SetValue(dbcNumber& value) { return SetValue(value.GetSignedInteger()); } void OvmsMetricInt::Clear() { SetValue(0); OvmsMetric::Clear(); } OvmsMetricBool::OvmsMetricBool(const char* name, uint16_t autostale, metric_unit_t units, bool persist) : OvmsMetric(name, autostale, units, persist) { m_value = false; m_valuep = NULL; m_persist = persist; if (m_persist) { persistent_values *vp = pmetrics_register(name); if (!vp) { m_persist = false; } else { m_valuep = reinterpret_cast(&vp->value); if (m_value != *m_valuep) { m_value = *m_valuep; SetModified(true); ESP_LOGI(TAG, "persist %s = %s", name, AsUnitString().c_str()); } } } } OvmsMetricBool::~OvmsMetricBool() { } bool OvmsMetricBool::CheckPersist() { if (!m_persist || !m_valuep || !IsDefined()) return true; if (*m_valuep != m_value) { 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 != reinterpret_cast(&vp->value)) { ESP_LOGE(TAG, "CheckPersist: bad address for %s", m_name); return false; } return true; } void OvmsMetricBool::RefreshPersist() { if (m_persist && m_valuep && IsDefined()) *m_valuep = m_value; } std::string OvmsMetricBool::AsString(const char* defvalue, metric_unit_t units, int precision) { if (IsDefined()) { if (m_value) return std::string("yes"); else return std::string("no"); } else { return std::string(defvalue); } } std::string OvmsMetricBool::AsJSON(const char* defvalue, metric_unit_t units, int precision) { if (IsDefined()) { if (m_value) return std::string("true"); else return std::string("false"); } else { if (strtobool(defvalue) == true) return std::string("true"); else return std::string("false"); } } float OvmsMetricBool::AsFloat(const float defvalue, metric_unit_t units) { return (float)AsBool((bool)defvalue); } int OvmsMetricBool::AsBool(const bool defvalue) { if (IsDefined()) return m_value; else return defvalue; } #ifdef CONFIG_OVMS_SC_JAVASCRIPT_DUKTAPE void OvmsMetricBool::DukPush(DukContext &dc, metric_unit_t units) { dc.Push(m_value); } #endif bool OvmsMetricBool::SetValue(bool value) { if (m_value != value) { m_value = value; if (m_valuep) *m_valuep = m_value; SetModified(true); return true; } else { SetModified(false); return false; } } bool OvmsMetricBool::SetValue(std::string value, metric_unit_t units) { bool nvalue = strtobool(value); if (m_value != nvalue) { m_value = nvalue; if (m_valuep) *m_valuep = m_value; SetModified(true); return true; } else { SetModified(false); return false; } } bool OvmsMetricBool::SetValue(dbcNumber& value) { return SetValue((bool)value.GetUnsignedInteger()); } void OvmsMetricBool::Clear() { SetValue(false); OvmsMetric::Clear(); } OvmsMetricFloat::OvmsMetricFloat(const char* name, uint16_t autostale, metric_unit_t units, bool persist) : OvmsMetric(name, autostale, units, persist) { m_value = 0.0; m_valuep = NULL; m_persist = persist; if (m_persist) { persistent_values *vp = pmetrics_register(name); if (!vp) { m_persist = false; } else { m_valuep = reinterpret_cast(&vp->value); if (m_value != *m_valuep) { m_value = *m_valuep; SetModified(true); ESP_LOGI(TAG, "persist %s = %s", name, AsUnitString().c_str()); } } } } OvmsMetricFloat::~OvmsMetricFloat() { } bool OvmsMetricFloat::CheckPersist() { if (!m_persist || !m_valuep || !IsDefined()) return true; if (*m_valuep != m_value) { 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 != reinterpret_cast(&vp->value)) { ESP_LOGE(TAG, "CheckPersist: bad address for %s", m_name); return false; } return true; } void OvmsMetricFloat::RefreshPersist() { if (m_persist && m_valuep && IsDefined()) *m_valuep = m_value; } std::string OvmsMetricFloat::AsString(const char* defvalue, metric_unit_t units, int precision) { if (IsDefined()) { std::ostringstream ss; if (precision >= 0) { ss.precision(precision); // Set desired precision ss << fixed; } if ((units != Other)&&(units != m_units)) ss << UnitConvert(m_units,units,m_value); else ss << m_value; std::string s(ss.str()); return s; } else { return std::string(defvalue); } } std::string OvmsMetricFloat::AsJSON(const char* defvalue, metric_unit_t units, int precision) { if (IsDefined()) return AsString(defvalue, units, precision); else return std::string((defvalue && *defvalue) ? defvalue : "0"); } float OvmsMetricFloat::AsFloat(const float defvalue, metric_unit_t units) { if (IsDefined()) { if ((units != Other)&&(units != m_units)) return UnitConvert(m_units,units,m_value); else return m_value; } else return defvalue; } int OvmsMetricFloat::AsInt(const int defvalue, metric_unit_t units) { return (int) AsFloat((float) defvalue, units); } #ifdef CONFIG_OVMS_SC_JAVASCRIPT_DUKTAPE void OvmsMetricFloat::DukPush(DukContext &dc, metric_unit_t units) { dc.Push(AsFloat(0, units)); } #endif bool OvmsMetricFloat::SetValue(float value, metric_unit_t units) { float nvalue = value; if ((units != Other)&&(units != m_units)) nvalue=UnitConvert(units,m_units,value); if (m_value != nvalue) { m_value = nvalue; if (m_valuep) *m_valuep = m_value; SetModified(true); return true; } else { SetModified(false); return false; } } bool OvmsMetricFloat::SetValue(std::string value, metric_unit_t units) { float nvalue = atof(value.c_str()); return SetValue(nvalue, units); } bool OvmsMetricFloat::SetValue(dbcNumber& value) { return SetValue((float)value.GetDouble()); } void OvmsMetricFloat::Clear() { SetValue(0); OvmsMetric::Clear(); } OvmsMetricString::OvmsMetricString(const char* name, uint16_t autostale, metric_unit_t units, bool persist) : OvmsMetric(name, autostale, units, persist) { } OvmsMetricString::~OvmsMetricString() { } std::string OvmsMetricString::AsString(const char* defvalue, metric_unit_t units, int precision) { if (IsDefined()) { OvmsMutexLock lock(&m_mutex); return m_value; } else { return std::string(defvalue); } } #ifdef CONFIG_OVMS_SC_JAVASCRIPT_DUKTAPE void OvmsMetricString::DukPush(DukContext &dc, metric_unit_t units) { OvmsMutexLock lock(&m_mutex); dc.Push(m_value); } #endif bool OvmsMetricString::SetValue(std::string value, metric_unit_t units) { if (m_mutex.Lock()) { bool modified = false; if (m_value.compare(value)!=0) { m_value = value; modified = true; } m_mutex.Unlock(); SetModified(modified); return modified; } return false; } void OvmsMetricString::Clear() { SetValue(""); OvmsMetric::Clear(); } const char* OvmsMetricUnitLabel(metric_unit_t units) { uint8_t unit_i = static_cast(units); if (unit_i > uint8_t(MetricUnitLast)) return ""; const char *res = unit_info[unit_i].Label; if (res == NULL) return ""; return res; } const char* OvmsMetricUnitName(metric_unit_t units) { uint8_t unit_i = static_cast(units); if (unit_i > uint8_t(MetricUnitLast)) return NULL; return unit_info[unit_i].UnitCode; } metric_unit_t OvmsMetricUnitFromName(const char* unit) { if (unit == NULL || unit[0] == '\0') return Native; for (metric_unit_t metric = MetricUnitFirst; metric <= MetricUnitLast; metric = metric_unit_t(1+(uint8_t)metric)) { const char * name = unit_info[(uint8_t)metric].UnitCode; if (name != NULL && strcasecmp(name,unit) == 0) return metric; } return UnitNotFound; } int UnitConvert(metric_unit_t from, metric_unit_t to, int value) { CheckTargetUnit(from, to, false); if (to == Native) return value; switch (from) { case Kilometers: switch (to) { case Miles: return km_to_mi(value); case Meters: return value*1000; case Feet: return km_to_mi(value) * feet_per_mile; default: break; } case Miles: switch (to) { case Kilometers: return mi_to_km(value); case Meters: return mi_to_km(value*1000); case Feet: return value * feet_per_mile; default: break; } case Meters: switch (to) { case Miles: return km_to_mi(value)/1000; case Kilometers: return value/1000; case Feet: return km_to_mi( value * feet_per_mile)/ 1000; default: break; } case Feet: switch (to) { case Kilometers: return mi_to_km(value)/feet_per_mile; case Meters: return (mi_to_km(value*1000)/feet_per_mile); case Miles: return value / feet_per_mile; default: break; } case KphPS: switch (to) { case MphPS: return km_to_mi(value); case MetersPSS: return value * 10 /36; case FeetPSS: return km_to_mi(value*feet_per_mile)/3600; default: break; } case MphPS: switch (to) { case KphPS: return mi_to_km(value); case MetersPSS: return mi_to_km(value*10)/36; case FeetPSS: return value*feet_per_mile/3600; default: break; } case MetersPSS: switch (to) { case KphPS: return (value*36) / 10; case MphPS: return km_to_mi(value * 36) / 10; case FeetPSS: return km_to_mi(value*feet_per_mile); default: break; } case FeetPSS: switch (to) { case KphPS: return (mi_to_km(value * 36 )/(feet_per_mile*10)); case MphPS: return value *3600/feet_per_mile; case MetersPSS: return mi_to_km(value/feet_per_mile)*1000; default: break; } case kW: if (to == Watts) return (value*1000); break; case Watts: if (to == kW) return (value/1000); break; case kWh: if (to == WattHours) return (value*1000); break; case WattHours: if (to == kWh) return (value/1000); break; case WattHoursPK: switch (to) { case WattHoursPM: return pkm_to_pmi(value); case kWhP100K: return value / 10; case KPkWh: return value ? static_cast(1000.0 / value) : 0; case MPkWh: return value ? static_cast(km_to_mi(1000.0 / value)) : 0; default: break; } case WattHoursPM: switch (to) { case WattHoursPK: return pmi_to_pkm(value); case kWhP100K: return pmi_to_pkm(value) / 10; case KPkWh: return value ? static_cast(mi_to_km(1000.0 / value)) : 0; case MPkWh: return value ? static_cast(1000.0 / value) : 0; default: break; } break; case kWhP100K: switch (to) { case WattHoursPM: return pkm_to_pmi(value * 10); case WattHoursPK: return value * 10; case KPkWh: return value ? static_cast(100.0 / value) : 0; case MPkWh: return value ? static_cast(km_to_mi(100.0 / value)) : 0; default: break; } case KPkWh: switch (to) { case WattHoursPM: return value ? static_cast(1000.0 / km_to_mi(float(value))) : 0; case WattHoursPK: return value ? static_cast(1/(1000.0 * value)) : 0; case kWhP100K: return value ? static_cast(100.0/value) : 0; case MPkWh: return km_to_mi(value); default: break; } case MPkWh: switch (to) { case WattHoursPM: return value ? 1000/value : 0; case WattHoursPK: return value ? static_cast(1000 / mi_to_km(float(value))) : 0; case kWhP100K: return value ? static_cast(100.0/mi_to_km(float(value))) : 0; case KPkWh: return mi_to_km(value); default: break; } case Celcius: if (to == Fahrenheit) return ((value*9)/5) + 32; break; case Fahrenheit: if (to == Celcius) return ((value-32)*5)/9; break; case kPa: if (to == Pa) return value*1000; else if (to == PSI) return int((float)value * 0.14503773773020923); break; case Pa: if (to == kPa) return value/1000; else if (to == PSI) return int((float)value * 0.00014503773773020923); break; case PSI: if (to == kPa) return int((float)value * 6.894757293168361); else if (to == Pa) return int((float)value * 0.006894757293168361); break; case Seconds: if (to == Minutes) return value/60; else if (to == Hours) return value/3600; break; case Minutes: if (to == Seconds || to == TimeUTC || to == TimeLocal) return value*60; else if (to == Hours) return value/60; break; case Hours: if (to == Seconds || to == TimeUTC || to == TimeLocal) return value*3600; else if (to == Minutes) return value*60; break; case TimeUTC: if (to == TimeLocal) { time_t now; time(&now); now -= now % (24*60*60); // Back to midnight UTC now += value; // The target time today struct tm* tmu = localtime(&now); return (tmu->tm_hour * 60 + tmu->tm_min) * 60 + tmu->tm_sec; } else if (to == Minutes) return value/60; else if (to == Hours) return value/3600; break; case Kph: if (to == Mph) return km_to_mi(value); break; case Mph: if (to == Kph) return mi_to_km(value); break; case dbm: if (to == sq) return (value <= -51) ? ((value + 113)/2) : 0; break; case sq: if (to == dbm) return (value <= 31) ? (-113 + (value*2)) : 0; break; default: return value; } return value; } float UnitConvert(metric_unit_t from, metric_unit_t to, float value) { CheckTargetUnit(from, to, false); if (to == Native) return value; switch (from) { case Kilometers: switch (to) { case Miles: return km_to_mi(value); case Meters: return value*1000; case Feet: return km_to_mi(value) * feet_per_mile; default: break; } case Miles: switch (to) { case Kilometers: return mi_to_km(value); case Meters: return (mi_to_km(value)*1000); case Feet: return value * feet_per_mile; default: break; } case Meters: switch (to) { case Miles: return km_to_mi(value/1000); case Kilometers: return value/1000; case Feet: return km_to_mi(value/1000) * feet_per_mile; default: break; } case Feet: switch (to) { case Kilometers: return mi_to_km(value/feet_per_mile); case Meters: return (mi_to_km(value/feet_per_mile)*1000); case Miles: return value / feet_per_mile; default: break; } case KphPS: switch (to) { case MphPS: return km_to_mi(value); case MetersPSS: return value/3.6; case FeetPSS: return km_to_mi(value)*feet_per_mile/3600; default: break; } case MphPS: switch (to) { case KphPS: return mi_to_km(value); case MetersPSS: return mi_to_km(value)/3.6; case FeetPSS: return value*feet_per_mile/3600; default: break; } case MetersPSS: switch (to) { case KphPS: return (value*3.6); case MphPS: return km_to_mi(value)*3.6; case FeetPSS: return km_to_mi(value)*feet_per_mile; default: break; } case FeetPSS: switch (to) { case KphPS: return (mi_to_km(value/feet_per_mile)*3.6); case MphPS: return value *3600/feet_per_mile; case MetersPSS: return mi_to_km(value/feet_per_mile)*1000; default: break; } case kW: if (to == Watts) return (value*1000); break; case Watts: if (to == kW) return (value/1000); break; case kWh: if (to == WattHours) return (value*1000); break; case WattHours: if (to == kWh) return (value/1000); break; case WattHoursPK: switch (to) { case WattHoursPM: return pkm_to_pmi(value); case kWhP100K: return value / 10; case KPkWh: return value ? 1000.0 / value : 0; case MPkWh: return value ? (km_to_mi(1000.0 / value)) : 0; default: break; } case WattHoursPM: switch (to) { case WattHoursPK: return pmi_to_pkm(value); case kWhP100K: return pmi_to_pkm(value) / 10; case KPkWh: return value ? (mi_to_km(1000.0 / value)) : 0; case MPkWh: return value ? (1000.0 / value) : 0; default: break; } break; case kWhP100K: switch (to) { case WattHoursPM: return pkm_to_pmi(value * 10); case WattHoursPK: return value * 10; case KPkWh: return value ? (100.0 / value) : 0; case MPkWh: return value ? km_to_mi(100.0 / value) : 0; default: break; } case Celcius: if (to == Fahrenheit) return ((value*9)/5) + 32; break; case Fahrenheit: if (to == Celcius) return ((value-32)*5)/9; break; case kPa: if (to == Pa) return value*1000; else if (to == PSI) return value * 0.14503773773020923; break; case Pa: if (to == kPa) return value/1000; else if (to == PSI) return value * 0.00014503773773020923; break; case PSI: if (to == kPa) return value * 6.894757293168361; else if (to == Pa) return value * 0.006894757293168361; break; case Seconds: if (to == Minutes) return value/60; else if (to == Hours) return value/3600; break; case Minutes: if (to == Seconds) return value*60; else if (to == Hours) return value/60; break; case Hours: if (to == Seconds) return value*3600; else if (to == Minutes) return value*60; break; case Kph: if (to == Mph) return km_to_mi(value); break; case Mph: if (to == Kph) return mi_to_km(value); break; case dbm: if (to == sq) return int((value <= -51) ? ((value + 113)/2) : 0); break; case sq: if (to == dbm) return int((value <= 31) ? (-113 + (value*2)) : 0); break; default: return value; } return value; }