[Ovmsdev] New Metric Units

Michael Geddes frog at bunyip.wheelycreek.net
Tue Dec 13 07:00:20 HKT 2022


Michael,

I'll go back and revise the topic subscriptions stuff wrt metrics etc. with
the stuff I have done.

I have implemented metric subscriptions and also implemented async getters
in the dashboard just to see what the burden is, and tbh it's not too bad!
I had no real expectation that this would end up in the final code and it
was more just to further my understanding.   What I've done does require a
'get and subscribe' function added to the websocket.

*unit getsub  CallID  metricName*
Which subscribes to the metric, but also returns a result something like
{ result : {  ID: *CallID*, Metric: *metricName*, Value: *metricValue*,
Units : { native: *unitcode*, unit: *userunitcode*, label: *label*} }}

So I can create a Promise and put the Accept function agains the (random)
*CallID* on an object, (and a timeout that calls reject) -so when the
result comes in I can call the accept.

It still doesn't get over the need for plugins to either subscribe to the
metrics they want to access through metrics[]... or use the ametrics[]
collection in an async function.  Having a tag (as you mentioned) that says
which metrics to subscribe to would be at least backwards compatible.  I
guess we could make it so that if a plugin was searched for such a tag and
none existed, we could subscribe all?

//.ichael

On Sun, 11 Dec 2022 at 19:54, Michael Balzer <dexter at expeedo.de> wrote:

> Michael,
>
> yes, while async getters seem to be possible by some JS voodoo, they
> probably won't work without changes to the applications.
>
> I also don't think they would form an application code pattern we should
> encourage, as they would try to hide asynchronous operations and would lead
> to each individual metric needing to be requested (at least initially) by a
> separate async call.
>
> Instead, please reconsider:
>
> Btw, in case you didn't see this already: I implemented an auto
> subscription scheme for the 'stream' notifications -- these are by default
> very transmission intense and can cause substantial load on the module side
> as well. These subscriptions are managed automatically for all components
> and plugins by the framework (which btw also takes care of initializing all
> fragments added in the '#' container).
>
>
> I designed this subscription scheme to follow the MQTT topic subscription
> scheme and be usable for any kind of subscription. Different data sources
> can be identified by the root topic. For notifications, it's `notify/`, for
> metrics, we can naturally assign `metrics/`. A subscription pattern
> `metrics/v/p/#` would for example subscribe to all `v.p.*` metrics.
>
> Auto subscribing & unsubscribing is managed by the framework via the
> `data-subscriptions` attribute of a `.receiver`.
>
> Examples:
>
>    -
>    https://docs.openvehicles.com/en/latest/components/ovms_webserver/docs/notifications.html
>    -
>    https://github.com/openvehicles/Open-Vehicle-Monitoring-System-3/tree/master/plugins/retools
>
>
> Btw, if (!) we keep the units dictionary subscription necessity, that
> could be a subscription to `units/#`.
>
> Regards,
> Michael
>
>
> Am 08.12.22 um 04:29 schrieb Michael Geddes:
>
> I've implemented a way of having user units of metrics come through  (I
> have metrics_user[] metrics_label[] and metrics_all[] implemented)..
> *however as far as dynamic subscription to metrics goes, I now believe I
> can't do what I want in a backwards compatible way.*
>
> This is where I'm at:
>
> I haven't used Promises before or async/wait etc, so in my naivity, I had
> (mistakenly it seems) thought I could define a function taking a *metric
> code* that
> * Sends a command to the websocket  eg  *metric fetchsub 1234 v.p.trip*
> * Waits for the result coming in over the web-socket or times out
> * Returns the result to the user *directly*
> so  *metric['* *v.p.trip**'] *  (or whatever) could have an
> accessor function that (for unsubscribed metrics) silently fetched and
> subscribed to the metric over the websocket and returned the value.
>
> I have actually implemented the fetching bit mostly .. (a Promise resolve
> call-back is put in a collection against a random id and when the event
> comes back it uses the id to grab the promise function and resolve with the
> result - and yes it has a timeout) ... except that in the end, the
> javascript still requires a Promise to be created that needs to execute its
> result as a call-back.  I also believe I now understand why that is not
> possible, and that fundamentally the only way of doing async stuff at all
> ends up in some kind of call back (via a Promise directly or an async
> function Promise)... and that the only exceptions to this are within an
> async function (which in the end still returns a Promise).
>
> This makes blocking on a metric['v.p.trip']  proxy get; function not
> possible afaict.  Unless somebody knows a mechansim that I could use?
> (outside of an async function, of course)
>
> I have implement the subscription model for metrics[]  (which supports,
> for example, v.p.* ) .. but without the auto-subscription it is going to be
> less useful for plugins as far as backwards compatibility goes!
>
> you could have a call like this:
>    applyMetric('v.p.trip', (value) => {  ... put the value somewhere } )
>
>
>
> //.ichael
>
>
> On Sat, 3 Dec 2022 at 02:29, Michael Balzer <dexter at expeedo.de> wrote:
>
>> Michael,
>>
>> the unit conversion JS code scheme is fine. Btw, you can optimize convert
>> to…
>>
>>   convert = function (from, to, value) {
>>     return (unit_conversions[from + ">" + to] || unit_conversions.native
>> )(value);
>>   }
>>
> Yeah - I'm assuming that the gain from not looking up unnecessarily is
> lost from all the checking. Got it.
>
>
>> I knew we had to keep the default webstream as it was.  I was thinking of
>> having a different websocket Uri to trigger filtered mode (starting without
>> sending all units).
>>
>> Especially, I was also considering the plugins that run from the '#'
>> container that may not know the container had switched to user units! (so
>> I'm not sure about option a.), so from what I can tell, the base metrics[]
>> needs to maintain native units imho. This is also why I was looking at the
>> 'auto-subscribe' idea since the outside container doesn't know which
>> metrics a plugin might use, and a plugin wouldn't know to subscribe to the
>> messages.
>>
>>
>> I think plugins will need to be updated anyway, as will all our standard
>> pages & components, as up to now all units have been fixed in the UI.
>>
>> In both approaches, all metrics displays (simple markup, tables, charts)
>> will need to be reworked to use the user units. Only the basic markup type
>> displays could partially be modified automatically (by walking through
>> their '.unit' elements), but any extended use, even the range & energy
>> displays in the dashboard's speed gauge, will need a config-aware approach.
>>
>> Charts will need to fully reconfigure, as unit labels are used within
>> different chart features, and axis limits & plotbands will need to be
>> adjusted. For this, scripts can subscribe to a new 'msg:units' event sent
>> when a (re-)configuration of units is received.
>>
> Yeah - I've got some classes so that different cars can specify all the
> ranges in native units and it will generate code for the current user
> units. I've already put this into use for all vehicle classes that return a
> custom guage configuration.
>
>   At the moment it is still static code and we'll need to sink on a (not
> yet implmeneted) untits changed event to  reload it.. but it would be
> simple enough to change the code generated to be dynamic (especially that
> we now have that conversion function). Even just having the user refresh
> the page is better than nothing at the moment (it's not like the user is
> going to be changing the units all the time).
>
>>
>> Btw, in case you didn't see this already: I implemented an auto
>> subscription scheme for the 'stream' notifications -- these are by default
>> very transmission intense and can cause substantial load on the module side
>> as well. These subscriptions are managed automatically for all components
>> and plugins by the framework (which btw also takes care of initializing all
>> fragments added in the '#' container).
>>
>> We could maintain a units collection exactly as above with some proxy
>> arrays to get at values without exceptions.  For eg:
>> m.label["v.p.speed"]  could look up the units_ collection being
>> maintained and return blank if the entry doesn't exist. Then
>> m.value["v.p.speed"] would give the user unit and
>> m.nativevalue["v.p.speed"] the native value. The latter two would use the
>> metrics[] array or whatever mechanism we had. We could add
>> m.text["v.p.speed"] that would give a text version with the value and unit
>> if it had one.
>>
>>
>> That leads us to another option: we could keep the metrics transmission
>> in native values, add the units dictionary and provide all conversions in
>> Javascript using this proxy getter scheme.
>>
>
> Yep - already decided to do that. It makes more sense.  At the moment I'm
> also implementing a way of starting off with no metrics supplied and then
> adding in which ones we need.  Probably the JS is the bit I need to work
> out on that.
>
>>
>> That would keep the current metrics[] access scheme intact and unchanged,
>> so all current frontend code & plugins would continue to work, using the
>> native values as before.
>>
>> The new proxy getters then can be used as the new way to access metrics
>> by anyone interested in using user units, and we can go ahead by applying
>> this to the standard pages and components.
>>
>> This in combination with the 'msg:units' event to signal reconfiguration
>> should provide all we need.
>>
>> We can even easily combine this with providing a command or socket URL /
>> parameter to switch the metrics into user mode. The standard web frontend
>> won't need this then, but it would make using user units easy for devices
>> without Javascript support.
>>
>> Regards,
>> Michael
>>
>>
>> Am 28.11.22 um 00:36 schrieb Michael Geddes:
>>
>> Solved a couple of things.
>> I have a 'unit conversion' code - which I current have put into a
>> separate cpp file along with the two other C++ conversion functions.
>> I wanted to do it this way so they are all in there together. (Does this
>> make sense to do?).
>>
>>   mi_to_km = function(mi) { return mi * 1.609347; }
>>   km_to_mi = function(km) { return km * 0.6213700; }
>>   pkm_to_pmi = function(pkm) { return pkm * 1.609347; }
>>   pmi_to_pkm  = function(pmi) { return pmi * 0.6213700; }
>>   const feet_per_mile = 5280;
>>   var unit_conversions = {
>>         "native":             function (value) { return value;},
>>         "km>miles":           km_to_mi,
>>         "km>meters":          function (value) { return value*1000; },
>>         "km>feet":            function (value) { return km_to_mi(value) *
>> feet_per_mile; },
>> ......
>>         "percent>permille":   function (value) { return value*10.0; },
>>         "percent>percent":    function (value) { return value*0.10; }
>>   }
>>   convert_function = function (from, to) {
>>     var fn = undefined;
>>     if (from !== to && to !== "")
>>       fn = unit_conversions[from + ">" + to];
>>     if (fn == undefined)
>>       fn = unit_conversions.native;
>>     return fn;
>>   }
>>   convert = function (from, to, value) {
>>     var fn = convert_function(from, to);
>>     return fn(value);
>>   }
>>
>> The other problem of looking at the uri of the websocket I have solved by
>> creating a 'SocketCreator'  MgHandler class in
>> the MG_EV_WEBSOCKET_HANDSHAKE_REQUEST event (and looks at the uri)... that
>> waits for the
>> MG_EV_WEBSOCKET_HANDSHAKE_DONE  and deletes itself.
>>
>> int OvmsSocketCreator::HandleEvent(int ev, void *p)
>> {
>>   if ( ev != MG_EV_WEBSOCKET_HANDSHAKE_DONE)
>>     return ev;
>>   // new websocket connection
>>   MyWebServer.CreateWebSocketHandler(m_nc, m_socket_type );
>>   m_nc = NULL;
>>   delete this;
>>   return 0;
>> }
>>
>> Thoughts?
>>
>> //.ichael
>>
>> On Sun, 27 Nov 2022 at 07:18, Michael Geddes <frog at bunyip.wheelycreek.net>
>> wrote:
>>
>>> I knew we had to keep the default webstream as it was.  I was thinking
>>> of having a different websocket Uri to trigger filtered mode (starting
>>> without sending all units).. Though I am struggling with how to get and
>>> then pass the Uri information into the web socket constructor! The event
>>> that currently creates it doesn't seem to have access to the Uri. (see
>>> below - p is NULL for HANDSHAKE_DONE).
>>>   case MG_EV_WEBSOCKET_HANDSHAKE_REQUEST:
>>>       {
>>>         struct http_message *hm = (struct http_message *) p; // how to
>>> pass uri info to the event below!?
>>>       }
>>>       break;
>>>     case MG_EV_WEBSOCKET_HANDSHAKE_DONE:    // new websocket connection
>>>       {
>>>         MyWebServer.CreateWebSocketHandler(nc);
>>>       }
>>>       break;
>>>
>>>
>>> Especially, I was also considering the plugins that run from the '#'
>>> container that may not know the container had switched to user units! (so
>>> I'm not sure about option a.), so from what I can tell, the base metrics[]
>>> needs to maintain native units imho. This is also why I was looking at the
>>> 'auto-subscribe' idea since the outside container doesn't know which
>>> metrics a plugin might use, and a plugin wouldn't know to subscribe to the
>>> messages.
>>>
>>> I do like the separate units description message - though we would
>>> probably need to add the 'native' if we want to do conversions.
>>>
>>> { units: {
>>>   "v.p.speed": { code: "kmph", *native "kmph",* label: "km/h" }, …
>>>
>>> We could maintain a units collection exactly as above with some proxy
>>> arrays to get at values without exceptions.  For eg:
>>> m.label["v.p.speed"]  could look up the units_ collection being
>>> maintained and return blank if the entry doesn't exist. Then
>>> m.value["v.p.speed"] would give the user unit and
>>> m.nativevalue["v.p.speed"] the native value. The latter two would use the
>>> metrics[] array or whatever mechanism we had. We could add
>>> m.text["v.p.speed"] that would give a text version with the value and unit
>>> if it had one.
>>>
>>> I had contemplated the idea of providing a JavaScript unit conversion
>>> and was working around it. Downside is it's a third set of conversion
>>> functions to maintain... On the other hand using that we could just keep
>>> the metrics being sent as it is now, have the groups sent as you proposed
>>> (along with the native unit code) , and have the above m.value[] proxy
>>> collection use a 'touser' function assigned to the units_ collection above
>>> that provided native to user conversion.
>>>
>>> We could send a javascript library  constructed with just the necessary
>>> functions for the required conversions of native to user (or the whole
>>> lot.. whichever). .
>>> --
>>>   var conversions = {
>>>     unit: function ( value) { return value }
>>>     km_miles: function (value) { return value * 0.6213700; }
>>>   }
>>> We could perform the lookup when constructing the units and assign the
>>> touser property. (And have a 'unit' function that does no conversion).
>>>
>>> //.ichael
>>>
>>> On Sat, 26 Nov 2022, 10:43 pm Michael Balzer, <dexter at expeedo.de> wrote:
>>>
>>>> The metrics subscription scheme is an option, and the auto-subscribe
>>>> feature via a getter is a nice idea. But I wouldn't apply that to metrics
>>>> value conversions and units.
>>>>
>>>> Also we would still need the current set of metrics to be subscribed by
>>>> default, as there are also non Javascript devices (e.g. smart buttons, Wifi
>>>> displays) reading the WebSocket stream.
>>>>
>>>> My thoughts on this so far:
>>>>
>>>> Basically the web UI, as any frontend, should adapt to unit
>>>> configurations seamlessly. The web UI includes many command outputs, which
>>>> already automatically switch units as configured.
>>>>
>>>> For all practical purposes, the web UI needs to interact with users in
>>>> their preferred units. Only some plugins and functions will need certain
>>>> values in metric (native) units for calculations, and these will also need
>>>> a simple way to convert calculation results back to user units for
>>>> displaying. So I think we need to provide the unit configuration and value
>>>> conversion tools in the web framework as well.
>>>>
>>>> Proposal:
>>>>
>>>> a) We provide a config option and a WebSocket command to switch the
>>>> WebSocket metrics transmission mode to user / native units. To keep plugin
>>>> compatibility, the default is 'native'.
>>>>
>>>> b) We introduce a separate units dictionary object containing the user
>>>> units for both metrics and the unit groups in their code & label
>>>> representation. The units dictionary only needs to be sent initially, when
>>>> new metrics are registered, when the metrics mode is changed for the
>>>> current connection, and when some user unit configuration is changed,
>>>> keeping the bandwidth and processing requirements low.
>>>>
>>>> The units dictionary can combine both metrics and group units, as the
>>>> unit group names are fully distinct from the metrics namespace. The
>>>> transport scheme could be:
>>>>
>>>> { units: {
>>>>   "v.p.speed": { code: "kmph", label: "km/h" }, …
>>>>   "units.distance": { code: "miles", label: "M" }, …
>>>> }
>>>>
>>>> c) In the web framework, accessing units should be as simple as
>>>> possible and avoid throwing exceptions for undefined entries, so we could
>>>> e.g. split these into separate code & label objects:
>>>>
>>>> units["v.p.speed"]          = "km/h"    // consistently accompanies
>>>> metrics["v.p.speed"]
>>>> unitcodes["v.p.speed"]      = "kmph"
>>>>
>>>> units["units.distance"]     = "M"
>>>> unitcodes["units.distance"] = "miles"
>>>>
>>>> …or provide a getter that tests for the key existence and returns an
>>>> object with empty fields as necessary.
>>>>
>>>> With this, all metrics displays can easily be changed to display the
>>>> actual unit labels instead of using fixed strings.
>>>>
>>>> d) To provide value conversion we implement UnitConvert() in Javascript
>>>> plus some wrappers that automatically look up the unit for a given
>>>> metrics/group name and do the conversion to/from native units, something
>>>> like…
>>>>
>>>> var speed_kph    = toNativeValue("v.p.speed");   // optional second arg
>>>> to convert any data
>>>> var speed_kph    = metrics_native["v.p.speed"];  // using a getter
>>>>
>>>> var trip_display = toUserValue("units.distance", 1234);
>>>>
>>>>
>>>> Plugins for scientific/technical applications that depend on native
>>>> (metric) units can use the new metrics transmission mode control command to
>>>> force native mode. Or they can choose to migrate from "metrics[]" to
>>>> "metrics_native[]".
>>>>
>>>> The metrics mode config option can come with a note informing users
>>>> that there may be some old plugins not compatible with non-native units.
>>>> They can then check their plugins for this and make an informed decision on
>>>> wether to enable user units and/or wether to install a specific plugin.
>>>>
>>>> Thoughts, comments?
>>>>
>>>> Regards,
>>>> Michael
>>>>
>>>>
>>>> Am 25.11.22 um 03:13 schrieb Michael Geddes:
>>>>
>>>> I have an idea which might reduce traffic for maintaining the metrics[]
>>>> array in the browser and cope with the user units.
>>>> I'll start by saying I'm not a JS developer per se.. so a newb in JS
>>>> really. Still, it's mainly just another language so .. we'll give it a go.
>>>>
>>>> Firstly:
>>>> * Implement the 'changed' filters as below for the web-socket.. for
>>>> both normal and 'user' values.
>>>> * Add a function that subscribes to a value (and returns the current
>>>> value of it)..including to 'user' value/unitlabel.
>>>>
>>>> Subscribing the normal way to the metrics over the websocket would have
>>>> the normal effect.. but we would have a new way that would subscribe in a
>>>> filtered way.
>>>>
>>>> I've had a little play with the Proxy object .. so at least I know this
>>>> should work:
>>>>
>>>> Have a metrics_ array that is the real associative array for metrics[]
>>>> and then define a Proxy that has (at the least)  'get' and 'has' defined
>>>> (giving us the ability to overload *metrics['prop']*  and *"prop" in
>>>> metrics operations*).
>>>>
>>>> The *get *function would return the underlying value if it exists in
>>>> the *metrics_ *array (which  is maintained through the websocket from
>>>> currently subscribed values in the current manner).
>>>> If the value is not in the *metrics_* array - it would then do a
>>>> subscribe+query on the websocket getting the current value and adding it
>>>> into the  *metrics_*  container. If it was unavailable then it would
>>>> put  *undefined* into the array.
>>>> The 'has' would do the get() and return true if the value was not ==
>>>> *undefined*.
>>>>
>>>> For the 'query the websocket' bit, I'm assuming I would be working with
>>>> promises or futures or some such: I'll do the research and do it properly
>>>> unless somebody can help me out with it. That's the bit I was going to work
>>>> on next for the proof-of-concept.
>>>>
>>>> Any immediate thoughts? Dangers?
>>>>
>>>> I also noticed there was a bit that went through html element
>>>> properties and looked for metrics .. this could be used to bulk subscribe
>>>> to any metric values required there.
>>>>
>>>> //.ichael
>>>>
>>>>
>>>> On Thu, 17 Nov 2022 at 07:52, Michael Geddes <
>>>> frog at bunyip.wheelycreek.net> wrote:
>>>>
>>>>> Yeah, ok.
>>>>>
>>>>> I will get all the other 'user unit' stuff done as a line in the sand,
>>>>> and then move to working out the web stuff.  I'm still finding my way
>>>>> though all the client side javascript, which looks very cool.. but I've not
>>>>> really done jQuery before (just enough to recognise it).
>>>>>
>>>>> Subscribing to metrics with/without user units makes a lot of sense.
>>>>> Obviously the default needs to be 'Subscribe to all metrics but not
>>>>> user units' to maintain compatibility... but I was also thinking it might
>>>>> be nice if we could filter down even the normal subscribed events.
>>>>> We could have:
>>>>> * Web socket command to  filter units (flag on websocket to say
>>>>> 'filtered' + flag bitset on each  metric  similar to 'dirty')
>>>>> Then either:
>>>>> * Web socket command to turn on user units (single flag on that
>>>>> websocket)
>>>>> or
>>>>> * Web socket command to turn on user units for specific metrics (flag
>>>>> bitset on each metric)
>>>>>
>>>>> A parameter to the URI for the websocket could start the socket in
>>>>> 'filtered' mode to avoid the initial rush of metrics.
>>>>>
>>>>> This could drastically reduce traffic and time for the metrics command
>>>>> to execute.  It would be possible to also check (on a 'filtered' websocket)
>>>>> for any changes to metrics for that websocket slot before queueing the
>>>>> 'metric update' socket command.
>>>>>
>>>>> //.ichael
>>>>>
>>>>>
>>>>> On Thu, 17 Nov 2022 at 00:35, Michael Balzer <dexter at expeedo.de>
>>>>> wrote:
>>>>>
>>>>>> Michael,
>>>>>>
>>>>>> I don't have much spare time currently, just some quick first
>>>>>> comments: it's important to implement this as lightweight as possible, both
>>>>>> in terms of network load and client CPU & memory requirements. Some devices
>>>>>> already have issues, which can be seen by the "websocket overflow"
>>>>>> messages. The web UI also should stay usable via cellular.
>>>>>>
>>>>>> My impression is the new scheme, while only slightly raising the
>>>>>> client requirements, adds substantially to the network requirements.
>>>>>>
>>>>>> An option could be to separate the units -- or more, back when
>>>>>> implementing this I thought about separating the names later on. Another
>>>>>> question is if we normally generally need both the native and the converted
>>>>>> values in the web UI. We maybe could provide an option to switch to
>>>>>> converted values, or add an option to retreive or subscribe to a set of
>>>>>> converted metrics on demand.
>>>>>>
>>>>>> Standard plugins like ABRP and PwrMon rely on getting metric (native)
>>>>>> units, and there probably are non-public plugins, e.g. for engineering &
>>>>>> scientific projects, that depend on metric units to do their calculations
>>>>>> and don't need anything else. We shouldn't make life harder for these
>>>>>> applications without good reason.
>>>>>>
>>>>>> Regards,
>>>>>> Michael
>>>>>>
>>>>>>
>>>>>> Am 15.11.22 um 01:26 schrieb Michael Geddes:
>>>>>>
>>>>>> If you're ok with the [default] option I'll stick with that. I mean
>>>>>> in some ways it would be nice to have a button choice
>>>>>> metric | usa | europe | asia | custom   etc and I kind of considered
>>>>>> something like that but figured it's only a handful of choices.. and it's
>>>>>> an embedded device.. so simpler is better.
>>>>>>
>>>>>> On a related note - I was thinking how it would be nice if the
>>>>>> dashboard (etc) had access to the 'user' units, so went hunting down that
>>>>>> little rabbit hole. Quite a nice mechanism with the web socket updating the
>>>>>> "metrics" object in the UI.
>>>>>> This is a snippet of one idea, which is that for any metric that has
>>>>>> the possibility of a user unit, we set the extra values of the metric with
>>>>>> '#unit' and '#user' appended - see below. (I've chosen '#' arbitrarily..
>>>>>> but it could be '/' or ':' or '>'  but maybe not '.' )
>>>>>>
>>>>>> v.p.odometer#unit: "M"
>>>>>> v.p.odometer#user: 6754.91
>>>>>> v.p.satcount: 13
>>>>>> v.p.speed: 0
>>>>>> v.p.speed#unit: "km/h"
>>>>>> v.p.speed#user: null
>>>>>> *v.p.trip: 28*
>>>>>>
>>>>>> *v.p.trip#unit: "M" v.p.trip#user: 17.3984*
>>>>>>
>>>>>> Then we can use this in the dials to populate the values and
>>>>>> captions! (not that I like Miles).
>>>>>> I
>>>>>>
>>>>>> [image: image.png]
>>>>>>
>>>>>> The other (similar) way was to have something like the following:
>>>>>> "v.p.trip#user" : { "value": 17.3984, "unit": "M" }
>>>>>> It wouldn't make the total message any shorter.. soo.. dunno.
>>>>>>
>>>>>> There's also some complications with setting up the dials (for
>>>>>> min/max values) - like for the speed.
>>>>>>
>>>>>> Notice also that I'm returning null for undefined values. It's nice -
>>>>>> but I'm not sure how javascript handles null when used / printed etc.
>>>>>>
>>>>>> //.ichael
>>>>>>
>>>>>> On Sun, 13 Nov 2022 at 21:06, Michael Balzer <dexter at expeedo.de>
>>>>>> wrote:
>>>>>>
>>>>>>> Michael,
>>>>>>>
>>>>>>> looks good.
>>>>>>>
>>>>>>> I think having an explicit 'default' option is better than taking
>>>>>>> the 'Metric' equivalent for that, as in your example you already show unit
>>>>>>> alternatives within the metric system to support different scalings (kW /
>>>>>>> W, kWh / Wh). (Btw… waiting for someone to miss Horsepower & BTU here ;-))
>>>>>>>
>>>>>>> @Patrick, I think that also answers your implicit question:
>>>>>>>
>>>>>>> The default button makes it unclear what the actual setting is.
>>>>>>>
>>>>>>>
>>>>>>> The default (native unit) is always metric, but you may have a mix
>>>>>>> of scalings, as we try to find the one that fits best for the given
>>>>>>> application when defining a metric. For example the current driving energy
>>>>>>> consumption is stored natively in Wh/km, while the energy used or
>>>>>>> regenerated is in kWh, and the odometer & trip counters are in km, while
>>>>>>> the altitude ist in m.
>>>>>>>
>>>>>>> Regards,
>>>>>>> Michael
>>>>>>>
>>>>>>>
>>>>>>> Am 13.11.22 um 08:42 schrieb Michael Geddes:
>>>>>>>
>>>>>>> Greetings,
>>>>>>> so this is my idea of being able to select which units various
>>>>>>> groups use (in addition to Distance).
>>>>>>> This can be then accessed by the special 'user' unit code.  (or
>>>>>>> 'metrics list -u ' )
>>>>>>> The idea of [Default] selection below  simply means storing the
>>>>>>> value to blank - meaning use whatever unit the particular metric uses.  The
>>>>>>> other idea I had was to actually default it to the equivalent of 'Metric'
>>>>>>> special unit code and not have the [Default] button.
>>>>>>>
>>>>>>>
>>>>>>> [image: image.png]
>>>>>>>
>>>>>>> Currently I've made it so that if there are more than 3 choices
>>>>>>> other than [default] that it uses the choice/combo box rather than the
>>>>>>> Radio buttons. (ie this list is auto-generated from the Metric Units table
>>>>>>> and the Metric Groups table).
>>>>>>>
>>>>>>> Thoughts / comments?
>>>>>>>
>>>>>>> //.ichael
>>>>>>>
>>>>>>> On Sat, 12 Nov 2022 at 17:35, Michael Geddes <
>>>>>>> frog at bunyip.wheelycreek.net> wrote:
>>>>>>>
>>>>>>>>
>>>>>>>> https://github.com/openvehicles/Open-Vehicle-Monitoring-System-3/pull/771
>>>>>>>>
>>>>>>>> I'm hoping this P/R is ok in this form (made of 5 separate commits).
>>>>>>>>
>>>>>>>> I will have a look at implementing the "user" unit code.  The base
>>>>>>>> for how it would work is already a part of the above pull request.  I'll
>>>>>>>> just look at the module configuration for distance.
>>>>>>>>
>>>>>>>> The 'power consumption' is one where it's not just a check-box..
>>>>>>>> there're 5 possible choice!
>>>>>>>>
>>>>>>>> I should also add 'bar' for pressure given that for some reason
>>>>>>>> that's still a thing people want.
>>>>>>>>
>>>>>>>> //.ichael
>>>>>>>>
>>>>>>>> On Sat, 12 Nov 2022 at 16:24, Michael Balzer <dexter at expeedo.de>
>>>>>>>> wrote:
>>>>>>>>
>>>>>>>>> I think this is pretty decent & complete now.
>>>>>>>>>
>>>>>>>>> I also like the approach of the 'user' unit code. Moving all user
>>>>>>>>> unit prefs into the module configuration is an old todo. Currently only the
>>>>>>>>> distance unit is defined at the module side, temperature and pressure are
>>>>>>>>> App prefs.
>>>>>>>>>
>>>>>>>>> Regards,
>>>>>>>>> Michael
>>>>>>>>>
>>>>>>>>>
>>>>>>>>> Am 11.11.22 um 09:54 schrieb Michael Geddes:
>>>>>>>>>
>>>>>>>>> Ok - so here's what I have implemented for Duktape and Metrics. (I
>>>>>>>>> added IsDefined() as well).
>>>>>>>>> Any thoughts on this?
>>>>>>>>>
>>>>>>>>> Noting
>>>>>>>>>    OvmsMetrics.Float( {metric} ) -> Outputs metric as float (same)
>>>>>>>>>    OvmsMetrics.Float( {metric}, {unit}) -> Outputs metric as float
>>>>>>>>> converted to given unit  (new)
>>>>>>>>>    OvmsMetrics.Value( {metric} )   -> Outputs Metric in native
>>>>>>>>> value (same)
>>>>>>>>>    OvmsMetrics.Value( {metric} , false)   -> Outputs Metric as
>>>>>>>>> string and no units (same)
>>>>>>>>>    OvmsMetrics.Value( {metric} ,  {unit})  -> Outputs Metric
>>>>>>>>> converted to given unit as native value. (new)
>>>>>>>>>   OvmsMetrics.Value( {metric} ,  {unit}, false )  -> Outputs
>>>>>>>>> Metric converted to given unit as string including any unit specifier.
>>>>>>>>> (new)
>>>>>>>>> also  OvmsMetric.GetValues( {metric} [,{unit}] [, {converted} ] )
>>>>>>>>> Adds similar behaviour to Value() above.
>>>>>>>>> also the special units '*imperial*' and '*metric*' will convert
>>>>>>>>> to the associated imperial / metric version of the units as appropriate.
>>>>>>>>>
>>>>>>>>> (function() {
>>>>>>>>>    dump = function (metric) { print( metric+ " ["+(typeof
>>>>>>>>> metric)+"]\n"  ); }
>>>>>>>>>    dump_obj = function (obj )  {
>>>>>>>>>      print('--- Object ----\n')
>>>>>>>>>      for (var k in obj) {
>>>>>>>>>        xk = obj[k];
>>>>>>>>>        print( k+':'+ xk + ' ['+typeof xk+ "]\n");
>>>>>>>>>      }
>>>>>>>>>    }
>>>>>>>>>    dump(OvmsMetrics.Value("xiq.v.trip.consumption"));
>>>>>>>>>    dump(OvmsMetrics.Value("xiq.v.trip.consumption", false));
>>>>>>>>>    dump(OvmsMetrics.Value("xiq.v.trip.consumption","kmpkwh"));
>>>>>>>>>    dump(OvmsMetrics.Value("xiq.v.trip.consumption", "mipkwh",
>>>>>>>>> false));
>>>>>>>>>    dump(OvmsMetrics.AsFloat("xiq.v.trip.consumption"));
>>>>>>>>>    dump(OvmsMetrics.AsFloat("xiq.v.trip.consumption","kmpkwh"));
>>>>>>>>>    dump(OvmsMetrics.Value("xiq.v.trip.consumption","imperial"))
>>>>>>>>>    dump(OvmsMetrics.Value("xiq.v.trip.consumption","imperial",
>>>>>>>>> false))
>>>>>>>>>    dump_obj(OvmsMetrics.GetValues("trip", "metric"))
>>>>>>>>>    dump_obj(OvmsMetrics.GetValues("trip", "imperial", false))
>>>>>>>>> })();
>>>>>>>>>
>>>>>>>>> With this output:
>>>>>>>>>
>>>>>>>>> 19.2308 [number]
>>>>>>>>> 19.2308 [string]
>>>>>>>>> 5.2 [number]
>>>>>>>>> 3.23112mi/kWh [string]
>>>>>>>>> 19.2308 [number]
>>>>>>>>> 5.2 [number]
>>>>>>>>> 309.49 [number]
>>>>>>>>> 309.49Wh/mi [string]
>>>>>>>>> --- Object ----
>>>>>>>>> v.p.trip:13 [number]
>>>>>>>>> xiq.e.trip:0 [number]
>>>>>>>>> xiq.e.trip.energy.recuperated:0 [number]
>>>>>>>>> xiq.e.trip.energy.used:0 [number]
>>>>>>>>> xiq.v.trip.consumption:19.2308 [number]
>>>>>>>>> --- Object ----
>>>>>>>>> v.p.trip:8.07781M [string]
>>>>>>>>> xiq.e.trip:0M [string]
>>>>>>>>> xiq.e.trip.energy.recuperated:0kWh [string]
>>>>>>>>> xiq.e.trip.energy.used:0kWh [string]
>>>>>>>>> xiq.v.trip.consumption:309.49Wh/mi [string]
>>>>>>>>>
>>>>>>>>>
>>>>>>>>> On Wed, 9 Nov 2022 at 05:47, Michael Geddes <
>>>>>>>>> frog at bunyip.wheelycreek.net> wrote:
>>>>>>>>>
>>>>>>>>>> Yeah - I like HasValue.  I implemented IsDefined() but I will
>>>>>>>>>> rename it.. that's a much clearer name.
>>>>>>>>>>
>>>>>>>>>> Another thought. How about if we did this (but also with
>>>>>>>>>> GetValues() as well - see the special values below)
>>>>>>>>>>
>>>>>>>>>> OvmsMetrics.Value("xiq.v.trip.consumption",  true)  -> 17.0582
>>>>>>>>>> (Number)
>>>>>>>>>> OvmsMetrics.Value("xiq.v.trip.consumption",  false)  -> 17.0582
>>>>>>>>>> (String)
>>>>>>>>>> OvmsMetrics.Value("xiq.v.trip.consumption", "mipkwh", true)  ->
>>>>>>>>>> 3.64264  (Number)
>>>>>>>>>> OvmsMetrics.Value("xiq.v.trip.consumption", "mipkwh", false)  ->
>>>>>>>>>> 3.64264Mi/kWh  (String)
>>>>>>>>>>  OvmsMetrics.Value("xiq.v.trip.consumption", "native", false)  ->
>>>>>>>>>> 17.0582km/kWh  (String)
>>>>>>>>>>
>>>>>>>>>> and
>>>>>>>>>> OvmsMetrics.Value("xiq.v.trip.consumption", "imperial", false)
>>>>>>>>>> -> 3.64264Mi/kWh  (String)
>>>>>>>>>>
>>>>>>>>>> I have already implemented the special values 'native'
>>>>>>>>>> (existing), 'imperial' and 'metric'.
>>>>>>>>>>
>>>>>>>>>> I was also thinking that in the future you could have 'user'.
>>>>>>>>>> Where for each group of values:
>>>>>>>>>> 'temperature', 'distance', 'shortdistance', 'power' etc.. you
>>>>>>>>>> could have a user preference. I probably won't implement it now,.but it
>>>>>>>>>> could be cool that any UI could just ask for the user defined units (rather
>>>>>>>>>> than having a separate choice).
>>>>>>>>>>
>>>>>>>>>>
>>>>>>>>>>
>>>>>>>>>> //.ichael
>>>>>>>>>>
>>>>>>>>>> On Tue, 8 Nov 2022 at 21:57, Mark Webb-Johnson <
>>>>>>>>>> mark at webb-johnson.net> wrote:
>>>>>>>>>>
>>>>>>>>>>> Or perhaps something more specific?
>>>>>>>>>>>
>>>>>>>>>>>     HasValue()
>>>>>>>>>>>
>>>>>>>>>>> Mark
>>>>>>>>>>>
>>>>>>>>>>> On 8 Nov 2022, at 9:01 PM, Michael Balzer <dexter at expeedo.de>
>>>>>>>>>>> wrote:
>>>>>>>>>>>
>>>>>>>>>>> Signed PGP part
>>>>>>>>>>> That's basically a good approach, but be aware 'IsDefined()' has
>>>>>>>>>>> an ambiguous meaning here, as with the API stem "OvmsMetrics" it would
>>>>>>>>>>> naturally be expected to mean "is this metric defined", not "does this
>>>>>>>>>>> metric have a defined value".
>>>>>>>>>>>
>>>>>>>>>>> An undefined metric currently can be derived from 'Values()'
>>>>>>>>>>> returning undefined, but that's more an undocumented side effect than
>>>>>>>>>>> intended.
>>>>>>>>>>>
>>>>>>>>>>> Maybe 'GetDefined()' could be a better name, leveraging this
>>>>>>>>>>> behaviour, i.e. returning 'undefined' for an actually undefined metric, and
>>>>>>>>>>> 'null' for a defined metric without a value.
>>>>>>>>>>>
>>>>>>>>>>> Regards,
>>>>>>>>>>> Michael
>>>>>>>>>>>
>>>>>>>>>>>
>>>>>>>>>>> Am 08.11.22 um 13:46 schrieb Michael Geddes:
>>>>>>>>>>>
>>>>>>>>>>> Ah yes. Arrays - will check those.  Yeah, how about we add a
>>>>>>>>>>> 'IsDefined' method to metrics instead of the null thing (it does sound like
>>>>>>>>>>> it will upset too many applecarts).
>>>>>>>>>>>
>>>>>>>>>>> //.
>>>>>>>>>>>
>>>>>>>>>>> On Tue, 8 Nov 2022 at 20:35, Michael Balzer <dexter at expeedo.de>
>>>>>>>>>>> wrote:
>>>>>>>>>>>
>>>>>>>>>>>> Michael,
>>>>>>>>>>>>
>>>>>>>>>>>> looks all good to me, once again nice find with the decode
>>>>>>>>>>>> argument. Adding decode to the Value() call was only for symmetry IIRC, the
>>>>>>>>>>>> main use was with GetValues() (
>>>>>>>>>>>> https://docs.openvehicles.com/en/latest/userguide/scripting.html#ovmsmetrics
>>>>>>>>>>>> ).
>>>>>>>>>>>>
>>>>>>>>>>>> Don't forget to test arrays, e.g. "v.t.pressure" & "v.t.temp".
>>>>>>>>>>>>
>>>>>>>>>>>> Returning null for an undefined metric seems like a natural
>>>>>>>>>>>> choice, but is a rather deep change, as for consistency not only the
>>>>>>>>>>>> Duktape metrics API but also the Web UI metrics API would need to be
>>>>>>>>>>>> changed accordingly. Unless you've got a real use case that needs that, we
>>>>>>>>>>>> should be careful.
>>>>>>>>>>>>
>>>>>>>>>>>> Regards,
>>>>>>>>>>>> Michael
>>>>>>>>>>>>
>>>>>>>>>>>>
>>>>>>>>>>>> Am 07.11.22 um 15:00 schrieb Michael Geddes:
>>>>>>>>>>>>
>>>>>>>>>>>> I have figured out a bunch of stuff and have implemented the
>>>>>>>>>>>> following: (having done away with needing AsFloatUnit)
>>>>>>>>>>>>
>>>>>>>>>>>> OvmsMetrics.Value( {metric} [, {decode}])
>>>>>>>>>>>> OvmsMetrics.Value( {metric}, {unit} [,{decode}])
>>>>>>>>>>>>
>>>>>>>>>>>> It turns out that the [decode] flag wasn't working anyway
>>>>>>>>>>>> (since the function was being registered as only having 1 param)...
>>>>>>>>>>>> This way it is still really 1 function.. but I check it the
>>>>>>>>>>>> second parameter is a 'boolean', and if not.. try the second form.
>>>>>>>>>>>>
>>>>>>>>>>>> OvmsMetrics.AsFloat( {metric} [,{unit}] )
>>>>>>>>>>>>
>>>>>>>>>>>> and add the function
>>>>>>>>>>>>
>>>>>>>>>>>> Ovms.Metrics.ValueUnit( {metric} [,{unit}])
>>>>>>>>>>>> This prints the value and the unit.
>>>>>>>>>>>>
>>>>>>>>>>>> Here's a sample function and the output! This also shows the
>>>>>>>>>>>> types of the output.
>>>>>>>>>>>>
>>>>>>>>>>>> (function() {
>>>>>>>>>>>>    x = OvmsMetrics.Value("xiq.v.trip.consumption");
>>>>>>>>>>>>    print( (typeof x) + ": "+  x+"\n"  );
>>>>>>>>>>>>    x = OvmsMetrics.Value("xiq.v.trip.consumption", false);
>>>>>>>>>>>>    print( (typeof x) + ": "+  x +"\n" );
>>>>>>>>>>>>    x =  OvmsMetrics.Value("xiq.v.trip.consumption","kmpkwh")
>>>>>>>>>>>>    print( (typeof x) + ": "+ x +"\n");
>>>>>>>>>>>>    x =  OvmsMetrics.Value("xiq.v.trip.consumption", "mipkwh",
>>>>>>>>>>>> false)
>>>>>>>>>>>>    print( (typeof x) + ": "+ x +"\n");
>>>>>>>>>>>>    x =  OvmsMetrics.ValueUnit("xiq.v.trip.consumption")
>>>>>>>>>>>>    print( (typeof x) + ": "+ x +"\n");
>>>>>>>>>>>>    x =  OvmsMetrics.ValueUnit("xiq.v.trip.consumption","mipkwh")
>>>>>>>>>>>>    print( (typeof x) + ": "+ x +"\n");
>>>>>>>>>>>>    x =  OvmsMetrics.AsFloat("xiq.v.trip.consumption")
>>>>>>>>>>>>    print( (typeof x) + ": "+ x +"\n");
>>>>>>>>>>>>    x =  OvmsMetrics.AsFloat("xiq.v.trip.consumption","kmpkwh")
>>>>>>>>>>>>    print( (typeof x) + ": "+ x +"\n");
>>>>>>>>>>>> })();
>>>>>>>>>>>>
>>>>>>>>>>>> number: 17.0582
>>>>>>>>>>>> string: 17.0582
>>>>>>>>>>>> number: 5.86227
>>>>>>>>>>>> string: 3.64264
>>>>>>>>>>>> string: 17.0582kWh/100km
>>>>>>>>>>>> string: 3.64264mi/kWh
>>>>>>>>>>>> number: 17.0582
>>>>>>>>>>>> number: 5.86227
>>>>>>>>>>>>
>>>>>>>>>>>>
>>>>>>>>>>>>
>>>>>>>>>>>> It still might be an idea to use 'null' as a return value if
>>>>>>>>>>>> the metrics is !IsDefined() but that would be changing the
>>>>>>>>>>>> existing behaviour slightly.
>>>>>>>>>>>>
>>>>>>>>>>>> //.ichael
>>>>>>>>>>>>
>>>>>>>>>>>> On Mon, 7 Nov 2022 at 08:12, Michael Geddes <
>>>>>>>>>>>> frog at bunyip.wheelycreek.net> wrote:
>>>>>>>>>>>>
>>>>>>>>>>>>> I've worked out what the decode flag is for and how it works,
>>>>>>>>>>>>> and I think how optional params work.
>>>>>>>>>>>>> I'm pretty sure I won't  need the 'AsFloatUnit' function; the
>>>>>>>>>>>>> unit would be an option to AsFloat(); I'll know that soon.
>>>>>>>>>>>>>
>>>>>>>>>>>>> The 'Value' function is more complicated because of the
>>>>>>>>>>>>> optional decode bool. I guess I could add the Unit to the end of that.
>>>>>>>>>>>>>
>>>>>>>>>>>>> ValueUnit could be still useful then to provide a 'Value +
>>>>>>>>>>>>> Unit'.
>>>>>>>>>>>>>
>>>>>>>>>>>>> Question:  Is there a reason we shouldn't be returning with
>>>>>>>>>>>>> duk_push_null    if the metric !IsDefined()  in both
>>>>>>>>>>>>> AsFloat() and Value(metric,true) cases?
>>>>>>>>>>>>>
>>>>>>>>>>>>> //.ichael
>>>>>>>>>>>>>
>>>>>>>>>>>>> On Sun, 6 Nov 2022 at 11:22, Michael Geddes <
>>>>>>>>>>>>> frog at bunyip.wheelycreek.net> wrote:
>>>>>>>>>>>>>
>>>>>>>>>>>>>> Right, so I've implemented some stuff that seems to work
>>>>>>>>>>>>>> quite well.
>>>>>>>>>>>>>>
>>>>>>>>>>>>>>
>>>>>>>>>>>>>> https://github.com/openvehicles/Open-Vehicle-Monitoring-System-3/pull/764
>>>>>>>>>>>>>> should be ready now after a couple of stupid mistakes slipped through.
>>>>>>>>>>>>>>  This absolutely needs somebody to review it please! (There's a reason why
>>>>>>>>>>>>>> I've converted some if()'s to switch() - which is that it will be used in
>>>>>>>>>>>>>> the follow-up commit).
>>>>>>>>>>>>>>
>>>>>>>>>>>>>> The commit that will follow on from that it implements the
>>>>>>>>>>>>>> new Units: kWh/100km, km/kWh  and  mi/kWh.
>>>>>>>>>>>>>>
>>>>>>>>>>>>>> This is a summary of what I've implemented for scripting -
>>>>>>>>>>>>>> including showing the unit codes I have so far.  I've considered a few
>>>>>>>>>>>>>> things:
>>>>>>>>>>>>>>   * Should some of the longer unit codes be shortened  (eg
>>>>>>>>>>>>>> mi, mins, m, ft, deg, perc)
>>>>>>>>>>>>>>   * The unit codes could be much more regular and separated
>>>>>>>>>>>>>> by dots  eg:
>>>>>>>>>>>>>>         watthours -> w.h
>>>>>>>>>>>>>>         kwhp100km -> kw.h_100km or kw.h/100km
>>>>>>>>>>>>>>         miph ->  mi_h or mi/h  (or should it be mph).
>>>>>>>>>>>>>>         psi -> p_in.in or p/in.in or lb_in.in (yes, slightly
>>>>>>>>>>>>>> weird, but predictable)
>>>>>>>>>>>>>>
>>>>>>>>>>>>>> *OVMS# metric units*
>>>>>>>>>>>>>>           km : km
>>>>>>>>>>>>>>        miles : M
>>>>>>>>>>>>>>       meters : m
>>>>>>>>>>>>>>         feet : ft
>>>>>>>>>>>>>>      celcius : °C
>>>>>>>>>>>>>>   fahrenheit : °F
>>>>>>>>>>>>>>          kpa : kPa
>>>>>>>>>>>>>>           pa : Pa
>>>>>>>>>>>>>>          psi : psi
>>>>>>>>>>>>>>        volts : V
>>>>>>>>>>>>>>         amps : A
>>>>>>>>>>>>>>     amphours : Ah
>>>>>>>>>>>>>>           kw : kW
>>>>>>>>>>>>>>          kwh : kWh
>>>>>>>>>>>>>>        watts : W
>>>>>>>>>>>>>>    watthours : Wh
>>>>>>>>>>>>>>      seconds : Sec
>>>>>>>>>>>>>>      minutes : Min
>>>>>>>>>>>>>>        hours : Hour
>>>>>>>>>>>>>>          utc : UTC
>>>>>>>>>>>>>>      degrees : °
>>>>>>>>>>>>>>         kmph : km/h
>>>>>>>>>>>>>>         miph : Mph
>>>>>>>>>>>>>>       kmphps : km/h/s
>>>>>>>>>>>>>>       miphps : Mph/s
>>>>>>>>>>>>>>         mpss : m/s²
>>>>>>>>>>>>>>          dbm : dBm
>>>>>>>>>>>>>>           sq : sq
>>>>>>>>>>>>>>      percent : %
>>>>>>>>>>>>>>        whpkm : Wh/km
>>>>>>>>>>>>>>        whpmi : Wh/mi
>>>>>>>>>>>>>>    kwhp100km : kWh/100km
>>>>>>>>>>>>>>       kmpkwh : km/kWh
>>>>>>>>>>>>>>       mipkwh : mi/kWh
>>>>>>>>>>>>>>           nm : Nm
>>>>>>>>>>>>>>
>>>>>>>>>>>>>> *OVMS# metric unit mi*
>>>>>>>>>>>>>>        miles : M
>>>>>>>>>>>>>>
>>>>>>>>>>>>>>
>>>>>>>>>>>>>
>> _______________________________________________
>> OvmsDev mailing listOvmsDev at lists.openvehicles.comhttp://lists.openvehicles.com/mailman/listinfo/ovmsdev
>>
>>
>> --
>> Michael Balzer * Helkenberger Weg 9 * D-58256 Ennepetal
>> Fon 02333 / 833 5735 * Handy 0176 / 206 989 26
>>
>> _______________________________________________
>> OvmsDev mailing list
>> OvmsDev at lists.openvehicles.com
>> http://lists.openvehicles.com/mailman/listinfo/ovmsdev
>>
>
> _______________________________________________
> OvmsDev mailing listOvmsDev at lists.openvehicles.comhttp://lists.openvehicles.com/mailman/listinfo/ovmsdev
>
>
> --
> Michael Balzer * Helkenberger Weg 9 * D-58256 Ennepetal
> Fon 02333 / 833 5735 * Handy 0176 / 206 989 26
>
> _______________________________________________
> OvmsDev mailing list
> OvmsDev at lists.openvehicles.com
> http://lists.openvehicles.com/mailman/listinfo/ovmsdev
>
-------------- next part --------------
An HTML attachment was scrubbed...
URL: <http://lists.openvehicles.com/pipermail/ovmsdev/attachments/20221213/6b4ec6dd/attachment-0001.htm>


More information about the OvmsDev mailing list