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