Metric Displays
OVMS V3 is based on metrics. Metrics can be single numerical or textual values or complex values
like sets and arrays. The web framework keeps all metrics in a global object, which can be read
simply by e.g. metrics["v.b.soc"]
.
In addition to the raw metric values, there are 2 main proxy arrays that give access to the
user-configured versions of the raw metric values. The metrics_user
array
converts the ‘metrics’ value to user-configured value and the metrics_label
array
provides the corresponding label for that metric.
So for example the user could configure distance values to be in miles, and in this case
metrics["v.p.odometer"]
would still contain the value in km (the default) but
metrics_user["v.p.odometer"]
would give the value converted to miles and
metrics_label["v.p.odometer"]
would return “M”.
The user conversion information is contained in another object units
. units.metrics
has the user configuration for each metric and units.prefs
has the user configuration
for each group of metrics (distance, temperature, consumption, pressure etc). There also some methods
for general conversions allowing user preferences:
The method
units.unitLabelToUser(unitType,name)
will return the user defined label for that ‘unitType’, defaulting toname
.The method
units.unitValueToUser(unitType,value)
will convertvalue
to the user defined unit (if set) for the group.
Metrics updates (as well as other updates) are sent to all DOM elements having the
receiver
class. To hook into these updates, simply add an event listener for
msg:metrics:
. The event msg:units:metrics
is called when units.metrics
is change
and msg:units:prefs
when units.prefs
are changed.
Listening to the event is not necessary though if all you need is some metrics
display. This is covered by the metric
widget class family as shown here.
Single Values & Charts

The following example covers…
Text (String) displays
Number displays
Progress bars (horizontal light weight bar charts)
Gauges
Charts
Where a number element of class ‘metric’ contains both elements of class ‘value’ and ‘unit’, these will be automatically displayed in the units selected in the user preferences. Having a ‘data-user’ attribute will also cause the ‘value’ element to be displayed in user units (unless ‘data-scale’ attribute is present).
Gauges & charts use the HighCharts library, which is included in the web server. The other widgets are simple standard Bootstrap widgets extended by an automatic metrics value update mechanism.
Highcharts is a highly versatile charting system. For inspiration, have a look at:
We’re using styled mode so some options don’t apply, but everything can be styled by standard CSS.
Install the example as a web page plugin:
metrics.htm
(hint: right click, save as)
1<!--
2 Test/Development/Documentation page; install as plugin to test
3-->
4
5<div class="panel panel-primary">
6 <div class="panel-heading">Metrics Displays Test/Demo</div>
7 <div class="panel-body">
8
9 <p>OVMS V3 is based on metrics. Metrics can be single numerical or textual values or complex values
10 like sets and arrays. The web framework keeps all metrics in a global object, which can be read
11 simply by e.g. <code>metrics["v.b.soc"]</code>.</p>
12
13 <p>Metrics updates (as well as other updates) are sent to all DOM elements having the
14 <code>receiver</code> class. To hook into these updates, simply add an event listener for
15 <code>msg:metrics</code>. Listening to the event is not necessary if all you need is some metrics
16 display. This is covered by the <code>metric</code> class family as shown here.</p>
17
18 <p>
19 <button type="button" class="btn btn-default action-gendata">Generate random data</button>
20 <button type="button" class="btn btn-default action-showsrc">Show page source</button>
21 </p>
22
23 <hr/>
24
25 <div class="receiver">
26
27 <h4>Basic usage</h4>
28
29 <p>All elements of class <code>metric</code> in a <code>receiver</code> are checked for the
30 <code>data-metric</code> attribute. If no specific metric class is given, the metric value
31 is simply set as the element text: <span class="metric" data-metric="m.net.provider">?</span>
32 is your current network provider.</p>
33
34 <h4>Text & Number</h4>
35
36 <p><code>number</code> & <code>text</code> displays get the metric value set in their child of
37 class <code>value</code>. They may additionally have labels and units. <code>data-prec</code> can
38 be used on <code>number</code> to set the precision, <code>data-scale</code> to scale the raw
39 values by a factor. They have fixed min widths and float by default, so you can simply put
40 multiple displays into the same container:</p>
41
42 <div class="clearfix">
43 <div class="metric number" data-metric="v.e.throttle" data-prec="0">
44 <span class="label">Throttle:</span>
45 <span class="value">?</span>
46 <span class="unit">%</span>
47 </div>
48 <div class="metric number" data-metric="v.b.12v.voltage.ref" data-prec="1">
49 <span class="value">?</span>
50 <span class="unit">V<sub>ref</sub></span>
51 </div>
52 <div class="metric text" data-metric="m.net.provider">
53 <span class="label">Network:</span>
54 <span class="value">?</span>
55 </div>
56 </div>
57
58 <h4>Progress Bar</h4>
59
60 <p>Bootstrap <code>progress</code> bars can be used as lightweight graphical indicators.
61 Labels and units are available, also <code>data-prec</code> and <code>data-scale</code>.
62 Again, all you need is a bit of markup:</p>
63
64 <div class="clearfix">
65 <div class="metric progress" data-metric="v.e.throttle" data-prec="0">
66 <div class="progress-bar progress-bar-success value-low text-left" role="progressbar"
67 aria-valuenow="0" aria-valuemin="0" aria-valuemax="100" style="width:0%">
68 <div>
69 <span class="label">Throttle:</span>
70 <span class="value">?</span>
71 <span class="unit">%</span>
72 </div>
73 </div>
74 </div>
75 <div class="metric progress" data-metric="v.b.12v.voltage.ref" data-prec="1">
76 <div class="progress-bar progress-bar-info value-low text-left" role="progressbar"
77 aria-valuenow="0" aria-valuemin="5" aria-valuemax="15" style="width:0%">
78 <div>
79 <span class="label">12V ref:</span>
80 <span class="value">?</span>
81 <span class="unit">V</span>
82 </div>
83 </div>
84 </div>
85 </div>
86
87 <h4>Gauges & Charts</h4>
88
89 <p>The OVMS web framework has builtin support for the highly versatile <b>Highcharts library</b>
90 with loads of chart types and options. <code>chart</code> metric examples:</p>
91
92 <div class="row">
93 <div class="col-sm-6">
94 <div class="metric chart" data-metric="v.e.throttle" style="height:220px">
95 <div class="chart-box gaugechart" id="throttle-gauge"/>
96 </div>
97 </div>
98 <div class="col-sm-6">
99 <div class="metric chart" data-metric="v.b.c.voltage,v.b.c.voltage.min" style="height:220px">
100 <div class="chart-box barchart" id="cell-voltages"/>
101 </div>
102 </div>
103 </div>
104
105 <p><button type="button" class="btn btn-default action-gendata">Generate random data</button></p>
106
107 <p>For charts, a little bit of scripting is necessary.
108 The scripts for these charts contain the chart configuration, part of which is the update
109 function you need to define. The update function translates metrics data into chart data.
110 This is trivial for single values like the throttle, the cell voltage chart is an example
111 on basic array processing.</p>
112
113 <p>Also, while charts <em>can</em> be defined with few options, you'll <em>love</em> to explore
114 all the features and fine tuning options provided by Highcharts. For inspiration,
115 have a look at the <a target="_blank" href="https://www.highcharts.com/demo">Highcharts demos</a>
116 and the <a target="_blank" href="https://www.highcharts.com/docs/">Highcharts documentation</a>.
117 We're using <a target="_blank" href="https://www.highcharts.com/docs/chart-design-and-style/style-by-css">
118 styled mode</a>, so some options don't apply, but everything can be styled by standard CSS.</p>
119
120 </div>
121
122 </div>
123</div>
124
125<script>
126(function(){
127
128 /* Get page source before chart rendering: */
129 var pagesrc = $('#main').html();
130
131 /* Init throttle gauge: */
132 $("#throttle-gauge").chart({
133 chart: {
134 type: 'gauge',
135 spacing: [0, 0, 0, 0],
136 margin: [0, 0, 0, 0],
137 animation: { duration: 500, easing: 'easeOutExpo' },
138 },
139 title: { text: "Throttle", verticalAlign: "middle", y: 75 },
140 credits: { enabled: false },
141 tooltip: { enabled: false },
142 plotOptions: {
143 gauge: { dataLabels: { enabled: false }, overshoot: 1 }
144 },
145 pane: [{
146 startAngle: -125, endAngle: 125, size: '100%', center: ['50%', '60%']
147 }],
148 yAxis: [{
149 title: { text: '%' },
150 className: 'throttle',
151 reversed: false,
152 min: 0, max: 100,
153 plotBands: [
154 { from: 0, to: 60, className: 'green-band' },
155 { from: 60, to: 80, className: 'yellow-band' },
156 { from: 80, to: 100, className: 'red-band' },
157 ],
158 minorTickInterval: 'auto', minorTickLength: 5, minorTickPosition: 'inside',
159 tickPixelInterval: 40, tickPosition: 'inside', tickLength: 13,
160 labels: { step: 2, distance: -28, x: 0, y: 5, zIndex: 2 },
161 }],
162 series: [{
163 name: 'Throttle', data: [0],
164 className: 'throttle',
165 animation: { duration: 0 },
166 pivot: { radius: '10' },
167 dial: { radius: '88%', topWidth: 1, baseLength: '20%', baseWidth: 10, rearLength: '20%' },
168 }],
169 /* Update method: */
170 onUpdate: function(update) {
171 // Create gauge data set from metric:
172 var data = [ metrics["v.e.throttle"] ];
173 // Update chart:
174 this.series[0].setData(data);
175 },
176 });
177
178 /* Init cell voltages chart */
179 $("#cell-voltages").chart({
180 chart: {
181 type: 'column',
182 animation: { duration: 500, easing: 'easeOutExpo' },
183 },
184 title: { text: "Cell Voltages" },
185 credits: { enabled: false },
186 tooltip: {
187 enabled: true,
188 shared: true,
189 headerFormat: 'Cell #{point.key}:<br/>',
190 pointFormat: '{series.name}: <b>{point.y}</b><br/>',
191 valueSuffix: " V"
192 },
193 legend: { enabled: true },
194 xAxis: {
195 categories: []
196 },
197 yAxis: [{
198 title: { text: null },
199 labels: { format: "{value:.2f}V" },
200 tickAmount: 4, startOnTick: false, endOnTick: false,
201 floor: 3.3, ceiling: 4.2,
202 minorTickInterval: 'auto',
203 }],
204 series: [{
205 name: 'Current', data: [],
206 className: 'cell-voltage',
207 animation: { duration: 0 },
208 },{
209 name: 'Minimum', data: [],
210 className: 'cell-voltage-min',
211 animation: { duration: 0 },
212 }],
213 /* Update method: */
214 onUpdate: function(update) {
215 // Note: the 'update' parameter contains the actual update set.
216 // You can use this to reduce chart updates to the actual changes.
217 // For this demo, we just use the global metrics object:
218 var
219 m_vlt = metrics["v.b.c.voltage"] || [],
220 m_min = metrics["v.b.c.voltage.min"] || [];
221 // Create categories (cell numbers) & rounded values:
222 var cat = [], val0 = [], val1 = [];
223 for (var i = 0; i < m_vlt.length; i++) {
224 cat.push(i+1);
225 val0.push(Number((m_vlt[i]||0).toFixed(3)));
226 val1.push(Number((m_min[i]||0).toFixed(3)));
227 }
228 // Update chart:
229 this.xAxis[0].setCategories(cat);
230 this.series[0].setData(val0);
231 this.series[1].setData(val1);
232 },
233 });
234
235 /* Test metrics generator: */
236 $('.action-gendata').on('click', function() {
237 var td = {};
238 td["m.net.provider"] = ["hologram","Vodafone","Telekom"][Math.floor(Math.random()*3)];
239 td["v.e.throttle"] = Math.random() * 100;
240 td["v.b.12v.voltage.ref"] = 10 + Math.random() * 4;
241 var m_vlt = [], m_min = [];
242 for (var i = 1; i <= 16; i++) {
243 m_vlt.push(3.6 + Math.random() * 0.5);
244 m_min.push(3.4 + Math.random() * 0.2);
245 }
246 td["v.b.c.voltage"] = m_vlt;
247 td["v.b.c.voltage.min"] = m_min;
248 $('.receiver').trigger('msg:metrics', $.extend(metrics, td));
249 });
250
251 /* Display page source: */
252 $('.action-showsrc').on('click', function() {
253 $('<div/>').dialog({
254 title: 'Source Code',
255 body: '<pre style="font-size:85%; height:calc(100vh - 230px);">'
256 + encode_html(pagesrc) + '</pre>',
257 size: 'lg',
258 });
259 });
260
261})();
262</script>
Vector Tables

Some metrics, for example the battery cell voltages or the TPMS tyre health data, may contain vectors of arbitrary size. Besides rendering into charts, these can also be displayed by their textual values in form of a table.
The following example shows a live view of the battery cell voltages along with their recorded minimum, maximum, maximum deviation and current warning/alert state. Alert states 0-2 are translated into icons.
The metric table widget uses the DataTables library, which is included in the web server. The DataTables Javascript library offers a wide range of options to create tabular views into datasets.
Install the example as a web page plugin:
metrics-table.htm
(hint: right click, save as)
1<!--
2 Web UI page plugin: DataTables metrics widget demonstration
3-->
4
5<style>
6td i {
7 font-style: normal;
8 font-size: 140%;
9 line-height: 90%;
10 font-weight: bold;
11}
12td i.warning { color: orange; }
13td i.danger { color: red; }
14</style>
15
16
17<div class="panel panel-primary panel-single receiver" id="my-receiver">
18 <div class="panel-heading">Metrics Table Widget Example</div>
19 <div class="panel-body">
20
21 <p>The following table shows a live view of the battery cell voltages along with their recorded
22 minimum, maximum, maximum deviation and current warning/alert state.</p>
23 <p>Try resizing the window or using a mobile phone to see how the table adapts to the screen
24 width. The table will also keep the selected sorting over data updates.</p>
25 <p>Hint: if you don't have live battery cell data, click the generator button to create
26 some random values. The random data is only generated in your browser, not on the module.</p>
27
28 <div class="metric table"
29 data-metric="v.b.c.voltage,v.b.c.voltage.min,v.b.c.voltage.max,v.b.c.voltage.dev.max,v.b.c.voltage.alert">
30 <table class="table table-striped table-bordered table-hover" id="v-table" />
31 </div>
32
33 <p>See <a target="_blank" href="https://datatables.net/manual/">DataTables manual</a> for all
34 options and API methods available.</p>
35
36 </div>
37 <div class="panel-footer">
38 <p><button type="button" class="btn btn-default action-gendata">Generate random data</button></p>
39 </div>
40</div>
41
42
43<script>
44(function(){
45
46 // Utilities:
47 var alertMap = {
48 0: '',
49 1: '<i class="warning">⚐</i>',
50 2: '<i class="danger">⚑</i>',
51 };
52
53 function fmtCode(value, map) {
54 return (map[value] !== undefined) ? map[value] : null;
55 }
56 function fmtNumber(value, prec) {
57 return (value !== undefined) ? Number(value).toFixed(prec) : null;
58 }
59
60 // Init table:
61 $('#v-table').table({
62 responsive: true,
63 paging: true,
64 searching: false,
65 info: false,
66 autoWidth: false,
67 columns: [
68 { title: "#", className: "dt-body-center", width: "6%", responsivePriority: 1 },
69 { title: "Voltage", className: "dt-body-right", width: "22%", responsivePriority: 3 },
70 { title: "Minimum", className: "dt-body-right", width: "22%", responsivePriority: 4 },
71 { title: "Maximum", className: "dt-body-right", width: "22%", responsivePriority: 5 },
72 { title: "Max.Dev.", className: "dt-body-right", width: "22%", responsivePriority: 2 },
73 { title: "Alert", className: "dt-body-center", width: "6%", responsivePriority: 1 },
74 ],
75 rowId: 0,
76 onUpdate: function(update) {
77 // Get vector metrics to display:
78 var v = [
79 metrics["v.b.c.voltage"] || [],
80 metrics["v.b.c.voltage.min"] || [],
81 metrics["v.b.c.voltage.max"] || [],
82 metrics["v.b.c.voltage.dev.max"] || [],
83 metrics["v.b.c.voltage.alert"] || [],
84 ];
85 var lcnt = 0;
86 v.map(el => lcnt = Math.max(lcnt, el.length));
87 // Transpose vectors to columns:
88 var l, d = [];
89 for (l = 0; l < lcnt; l++) {
90 d.push([
91 l+1,
92 fmtNumber(v[0][l], 2),
93 fmtNumber(v[1][l], 2),
94 fmtNumber(v[2][l], 2),
95 fmtNumber(v[3][l], 3),
96 fmtCode(v[4][l], alertMap),
97 ]);
98 }
99 // Display new data:
100 this.clear().rows.add(d).draw();
101 },
102 });
103
104
105 // Test data generator:
106 $('.action-gendata').on('click', function() {
107 var td = {};
108 var m_vlt = [], m_min = [], m_max = [], m_devmax = [], m_alert = [];
109 for (var i = 1; i <= 16; i++) {
110 m_vlt.push(3.6 + Math.random() * 0.5);
111 m_min.push(3.4 + Math.random() * 0.2);
112 m_max.push(3.8 + Math.random() * 0.2);
113 m_devmax.push(-0.2 + Math.random() * 0.4);
114 m_alert.push(Math.floor(Math.random() * 3));
115 }
116 td["v.b.c.voltage"] = m_vlt;
117 td["v.b.c.voltage.min"] = m_min;
118 td["v.b.c.voltage.max"] = m_max;
119 td["v.b.c.voltage.dev.max"] = m_devmax;
120 td["v.b.c.voltage.alert"] = m_alert;
121 $('.receiver').trigger('msg:metrics', $.extend(metrics, td));
122 });
123
124})();
125</script>