Example: Foglight
This is an example for combining a script module implementing some functional extension with a web plugin to provide a user interface.
The plugin realizes a fog light with automatic speed adaption, i.e. the light will be turned off above a configurable speed and on again when the vehicle slows down again. It also switches off automatically when the vehicle is parked.
The plugin shows how to…
read custom config variables
use module state variables
react to events
send custom events
read metrics
execute system commands
provide new commands
provide a web UI
Installation
Save
foglight.js
as/store/scripts/lib/foglight.js
(add thelib
directory as necessary)Add
foglight.htm
as a web hook type plugin at page/dashboard
, hookbody.pre
Execute
script eval 'foglight = require("lib/foglight")'
Optionally:
To test the module, execute
script eval foglight.info()
– it should print config and stateTo automatically load the module on boot, add the line
foglight = require("lib/foglight");
to/store/scripts/ovmsmain.js
Commands / Functions
The module provides two public API functions:
Function |
Description |
---|---|
|
…switch fog light on (1) / off (0) |
|
…output config & state in JSON format |
To call these from a shell, use script eval
. Example:
script eval foglight.set(1)
Configuration
We’ll add the configuration for this to the vehicle
section:
Config |
Default |
Description |
---|---|---|
|
1 |
…EGPIO output port number |
|
no |
…yes = speed automation |
|
45 |
…auto turn on below this speed |
|
55 |
…auto turn off above this speed |
Note
You can add arbitrary config instances to defined sections
simply by setting them: config set vehicle foglight.auto yes
Update:
Beginning with firmware release 3.2.009, a general configuration section usr
is provided
for plugins. We recommend using this for all custom parameters now. Keep in mind to prefix
all instances introduced by the plugin name, so your plugin can nicely coexist with others.
To store the config for simple & quick script access and implement the defaults, we
introduce an internal module member object cfg
:
28var cfg = {
29 "foglight.port": "1",
30 "foglight.auto": "no",
31 "foglight.speed.on": "45",
32 "foglight.speed.off": "55",
33};
By foglight = require(…)
, the module is added to the global name space as a javascript object.
This object can contain any internal standard javascript variables and functions.
Internal members are hidden by default, if you would like to expose the cfg
object, you would simply
add a reference to it to the exports
object as is done below for the API methods.
Reading OVMS config variables from a script currently needs to be done by executing config list
and parsing the output. This is done by the readconfig()
function:
42function readconfig() {
43 var cmdres, lines, cols, i;
44 cmdres = OvmsCommand.Exec("config list vehicle");
45 lines = cmdres.split("\n");
46 for (i=0; i<lines.length; i++) {
47 if (lines[i].indexOf("foglight") >= 0) {
48 cols = lines[i].substr(2).split(": ");
49 cfg[cols[0]] = cols[1];
50 }
51 }
Update: OVMS release 3.2.009 adds the OvmsConfig.GetValues()
API. To use this, we would
now omit the “foglight.” prefix from our cfg
properties. Reading the foglight configuration
can then be reduced to a single line:
Object.assign(cfg, OvmsConfig.GetValues("vehicle", "foglight."));
Listen to Events
The module needs to listen to three events:
config.changed
triggers reloading the configuration
ticker.1
is used to check the speed once per second
vehicle.off
automatically also turns off the fog light
The per second ticker is only necessary when the speed adaption is enabled, so we can use this to show
how to dynamically add and remove event handlers through the PubSub
API:
52// update ticker subscription:
53if (cfg["foglight.auto"] == "yes" && !state.ticker) {
54 state.ticker = PubSub.subscribe("ticker.1", checkspeed);
55} else if (cfg["foglight.auto"] != "yes" && state.ticker) {
56 PubSub.unsubscribe(state.ticker);
57 state.ticker = false;
58}
state
is another internal object for our state variables.
Send Events
Sending custom events is a lightweight method to inform the web UI (or other plugins) about simple state changes. In this case we’d like to inform listeners when the fog light actually physically is switched on or off, so the web UI can give visual feedback to the driver on this.
Beginning with firmware release 3.2.006 there is a native API OvmsEvents.Raise()
available to send events.
Before 3.2.006 we simply use the standard command event raise
:
61// EGPIO port control:
62function toggle(onoff) {
63 if (state.port != onoff) {
64 OvmsCommand.Exec("egpio output " + cfg["foglight.port"] + " " + onoff);
65 state.port = onoff;
66 OvmsCommand.Exec("event raise usr.foglight." + (onoff ? "on" : "off"));
67 }
68}
The web plugin subscribes to the foglight events just as to any system event:
64// Listen to foglight events:
65$('#foglight').on('msg:event', function(e, event) {
66 if (event == "usr.foglight.on")
67 update({ state: { port: 1 } });
68 else if (event == "usr.foglight.off")
69 update({ state: { port: 0 } });
70 else if (event == "vehicle.off") {
71 update({ state: { on: 0 } });
72 $('#action-foglight-output').empty();
73 }
74});
Note
You can raise any event you like, but you shouldn’t raise system events without
good knowledge of their effects. Event codes are simply strings, so you’re free
to extend them. Use the prefix usr.
for custom events to avoid potential
conflicts with future system event additions.
Read Metrics
Reading metrics is straight forward through the OvmsMetrics API:
74var speed = OvmsMetrics.AsFloat("v.p.speed");
Use Value()
instead of AsFloat()
for non-numerical metrics.
Note
You cannot subscribe to metrics changes directly (yet). Metrics can change thousands of times per second, which would overload the scripting capabilities. To periodically check a metric, register for a ticker event (as shown here).
Provide Commands
To add commands, simply expose their handler functions through the exports
object. By this, users will be
able to call these functions using the script eval
command from any shell, or from any script by
referencing them via the global module variable, foglight
in our case.
99// API method foglight.info():
100exports.info = function() {
101 JSON.print({ "cfg": cfg, "state": state });
102}
JSON.print()
is a convenient way to communicate with a web plugin, as that won’t need to parse some
potentially ambigous textual output but can simply use JSON.parse()
to read it into a variable:
82// Init & install:
83$('#main').one('load', function(ev) {
84 loadcmd('script eval foglight.info()').then(function(output) {
85 update(JSON.parse(output));
Note
Keep in mind commands should always output some textual response indicating their action and result. If a command does nothing, it should tell the user so. If a command is not intended for shell use, it should still provide some clue about this when called from the shell.
Module Plugin
foglight.js
(hint: right click, save as)
1/**
2 * /store/scripts/lib/foglight.js
3 *
4 * Module plugin:
5 * Foglight control with speed adaption and auto off on vehicle off.
6 *
7 * Version 1.0 Michael Balzer <dexter@dexters-web.de>
8 *
9 * Enable:
10 * - install at above path
11 * - add to /store/scripts/ovmsmain.js:
12 * foglight = require("lib/foglight");
13 * - script reload
14 *
15 * Config:
16 * - vehicle foglight.port …EGPIO output port number
17 * - vehicle foglight.auto …yes = speed automation
18 * - vehicle foglight.speed.on …auto turn on below this speed
19 * - vehicle foglight.speed.off …auto turn off above this speed
20 *
21 * Usage:
22 * - script eval foglight.set(1) …toggle foglight on
23 * - script eval foglight.set(0) …toggle foglight off
24 * - script eval foglight.info() …show config & state (JSON)
25 *
26 */
27
28var cfg = {
29 "foglight.port": "1",
30 "foglight.auto": "no",
31 "foglight.speed.on": "45",
32 "foglight.speed.off": "55",
33};
34
35var state = {
36 on: 0, // foglight on/off
37 port: 0, // current port output state
38 ticker: false, // ticker subscription
39};
40
41// Read config:
42function readconfig() {
43 var cmdres, lines, cols, i;
44 cmdres = OvmsCommand.Exec("config list vehicle");
45 lines = cmdres.split("\n");
46 for (i=0; i<lines.length; i++) {
47 if (lines[i].indexOf("foglight") >= 0) {
48 cols = lines[i].substr(2).split(": ");
49 cfg[cols[0]] = cols[1];
50 }
51 }
52 // update ticker subscription:
53 if (cfg["foglight.auto"] == "yes" && !state.ticker) {
54 state.ticker = PubSub.subscribe("ticker.1", checkspeed);
55 } else if (cfg["foglight.auto"] != "yes" && state.ticker) {
56 PubSub.unsubscribe(state.ticker);
57 state.ticker = false;
58 }
59}
60
61// EGPIO port control:
62function toggle(onoff) {
63 if (state.port != onoff) {
64 OvmsCommand.Exec("egpio output " + cfg["foglight.port"] + " " + onoff);
65 state.port = onoff;
66 OvmsCommand.Exec("event raise usr.foglight." + (onoff ? "on" : "off"));
67 }
68}
69
70// Check speed:
71function checkspeed() {
72 if (!state.on)
73 return;
74 var speed = OvmsMetrics.AsFloat("v.p.speed");
75 if (speed <= cfg["foglight.speed.on"])
76 toggle(1);
77 else if (speed >= cfg["foglight.speed.off"])
78 toggle(0);
79}
80
81// API method foglight.set(onoff):
82exports.set = function(onoff) {
83 if (onoff) {
84 state.on = 1;
85 if (cfg["foglight.auto"] == "yes") {
86 checkspeed();
87 print("Foglight AUTO mode\n");
88 } else {
89 toggle(1);
90 print("Foglight ON\n");
91 }
92 } else {
93 state.on = 0;
94 toggle(0);
95 print("Foglight OFF\n");
96 }
97}
98
99// API method foglight.info():
100exports.info = function() {
101 JSON.print({ "cfg": cfg, "state": state });
102}
103
104// Init:
105readconfig();
106PubSub.subscribe("config.changed", readconfig);
107PubSub.subscribe("vehicle.off", function(){ exports.set(0); });
Web Plugin
foglight.htm
(hint: right click, save as)
1<!--
2 foglight.htm: Web plugin for hook /dashboard:body.pre
3 - add button to activate/deactivate foglight
4 - add indicator to show current foglight state
5
6 Requires module plugin: foglight.js
7
8 Version 1.0 Michael Balzer <dexter@dexters-web.de>
9-->
10
11<style>
12#foglight {
13 margin: 10px 8px 0;
14}
15#foglight .indicator > .label {
16 font-size: 130%;
17 line-height: 160%;
18 margin: 0px;
19 padding: 10px;
20 display: block;
21 border-radius: 50px;
22}
23</style>
24
25<div class="receiver" id="foglight" style="display:none">
26 <form>
27 <div class="form-group">
28 <div class="col-xs-6">
29 <div class="indicator indicator-foglight">
30 <span class="label label-default">FOGLIGHT</span>
31 </div>
32 </div>
33 <div class="col-xs-6">
34 <div class="btn-group btn-group-justified action-foglight-set" data-toggle="buttons">
35 <label class="btn btn-default action-foglight-0"><input type="radio" name="foglight" value="0">OFF</label>
36 <label class="btn btn-default action-foglight-1"><input type="radio" name="foglight" value="1">ON/AUTO</label>
37 </div>
38 <samp id="action-foglight-output" class="text-center"></samp>
39 </div>
40 </div>
41 </form>
42</div>
43
44<script>
45(function(){
46
47 var foglight = { cfg: {}, state: { on: 0, port: 0 } };
48 var $indicator = $('#foglight .indicator-foglight > .label');
49 var $actionset = $('#foglight .action-foglight-set > label');
50
51 // State & UI update:
52 function update(data) {
53 $.extend(true, foglight, data);
54 // update indicator:
55 if (foglight.state.port)
56 $indicator.removeClass('label-default').addClass('label-danger');
57 else
58 $indicator.removeClass('label-danger').addClass('label-default');
59 // update buttons:
60 $actionset.removeClass('active');
61 $actionset.find('input[value='+foglight.state.on+']').prop('checked', true).parent().addClass('active');
62 }
63
64 // Listen to foglight events:
65 $('#foglight').on('msg:event', function(e, event) {
66 if (event == "usr.foglight.on")
67 update({ state: { port: 1 } });
68 else if (event == "usr.foglight.off")
69 update({ state: { port: 0 } });
70 else if (event == "vehicle.off") {
71 update({ state: { on: 0 } });
72 $('#action-foglight-output').empty();
73 }
74 });
75
76 // Button action:
77 $('#foglight .action-foglight-set input').on('change', function(e) {
78 foglight.state.on = $(this).val();
79 loadcmd('script eval foglight.set('+foglight.state.on+')', '#action-foglight-output');
80 });
81
82 // Init & install:
83 $('#main').one('load', function(ev) {
84 loadcmd('script eval foglight.info()').then(function(output) {
85 update(JSON.parse(output));
86 $('#foglight').appendTo('#panel-dashboard .panel-body').show();
87 });
88 });
89
90})();
91</script>