289 lines
9.3 KiB
ReStructuredText
289 lines
9.3 KiB
ReStructuredText
Example: Foglight
|
|
=================
|
|
|
|
.. image:: foglight/foglight.png
|
|
:scale: 50%
|
|
:align: right
|
|
|
|
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 :download:`foglight.js <foglight/foglight.js.txt>` as ``/store/scripts/lib/foglight.js``
|
|
(add the ``lib`` directory as necessary)
|
|
- Add :download:`foglight.htm <foglight/foglight.htm.txt>` as a web hook type plugin
|
|
at page ``/dashboard``, hook ``body.pre``
|
|
- Execute ``script eval 'foglight = require("lib/foglight")'``
|
|
|
|
Optionally:
|
|
|
|
- To test the module, execute ``script eval foglight.info()`` -- it should print config and state
|
|
- To 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
|
|
============================= ============================================
|
|
``foglight.set(onoff)`` …switch fog light on (1) / off (0)
|
|
``foglight.info()`` …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
|
|
========================= ========= ======================================
|
|
``foglight.port`` 1 …EGPIO output port number
|
|
``foglight.auto`` no …yes = speed automation
|
|
``foglight.speed.on`` 45 …auto turn on below this speed
|
|
``foglight.speed.off`` 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``:
|
|
|
|
.. code-block:: javascript
|
|
:caption: Module Plugin
|
|
:lineno-start: 28
|
|
|
|
var cfg = {
|
|
"foglight.port": "1",
|
|
"foglight.auto": "no",
|
|
"foglight.speed.on": "45",
|
|
"foglight.speed.off": "55",
|
|
};
|
|
|
|
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:
|
|
|
|
.. code-block:: javascript
|
|
:lineno-start: 42
|
|
:emphasize-lines: 3,4,6,7
|
|
|
|
function readconfig() {
|
|
var cmdres, lines, cols, i;
|
|
cmdres = OvmsCommand.Exec("config list vehicle");
|
|
lines = cmdres.split("\n");
|
|
for (i=0; i<lines.length; i++) {
|
|
if (lines[i].indexOf("foglight") >= 0) {
|
|
cols = lines[i].substr(2).split(": ");
|
|
cfg[cols[0]] = cols[1];
|
|
}
|
|
}
|
|
|
|
**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:
|
|
|
|
.. code-block:: javascript
|
|
|
|
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:
|
|
|
|
.. code-block:: javascript
|
|
:caption: Module Plugin
|
|
:lineno-start: 52
|
|
:emphasize-lines: 3,5
|
|
|
|
// update ticker subscription:
|
|
if (cfg["foglight.auto"] == "yes" && !state.ticker) {
|
|
state.ticker = PubSub.subscribe("ticker.1", checkspeed);
|
|
} else if (cfg["foglight.auto"] != "yes" && state.ticker) {
|
|
PubSub.unsubscribe(state.ticker);
|
|
state.ticker = false;
|
|
}
|
|
|
|
``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``:
|
|
|
|
.. code-block:: javascript
|
|
:caption: Module Plugin
|
|
:lineno-start: 61
|
|
:emphasize-lines: 6
|
|
|
|
// EGPIO port control:
|
|
function toggle(onoff) {
|
|
if (state.port != onoff) {
|
|
OvmsCommand.Exec("egpio output " + cfg["foglight.port"] + " " + onoff);
|
|
state.port = onoff;
|
|
OvmsCommand.Exec("event raise usr.foglight." + (onoff ? "on" : "off"));
|
|
}
|
|
}
|
|
|
|
The web plugin subscribes to the foglight events just as to any system event:
|
|
|
|
.. code-block:: javascript
|
|
:caption: Web Plugin
|
|
:lineno-start: 64
|
|
:emphasize-lines: 3,5
|
|
|
|
// Listen to foglight events:
|
|
$('#foglight').on('msg:event', function(e, event) {
|
|
if (event == "usr.foglight.on")
|
|
update({ state: { port: 1 } });
|
|
else if (event == "usr.foglight.off")
|
|
update({ state: { port: 0 } });
|
|
else if (event == "vehicle.off") {
|
|
update({ state: { on: 0 } });
|
|
$('#action-foglight-output').empty();
|
|
}
|
|
});
|
|
|
|
.. 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:
|
|
|
|
.. code-block:: javascript
|
|
:caption: Module Plugin
|
|
:lineno-start: 74
|
|
:emphasize-lines: 1
|
|
|
|
var 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.
|
|
|
|
.. code-block:: javascript
|
|
:caption: Module Plugin
|
|
:lineno-start: 99
|
|
:emphasize-lines: 2
|
|
|
|
// API method foglight.info():
|
|
exports.info = function() {
|
|
JSON.print({ "cfg": cfg, "state": state });
|
|
}
|
|
|
|
``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:
|
|
|
|
.. code-block:: javascript
|
|
:caption: Web Plugin
|
|
:lineno-start: 82
|
|
:emphasize-lines: 3,4
|
|
|
|
// Init & install:
|
|
$('#main').one('load', function(ev) {
|
|
loadcmd('script eval foglight.info()').then(function(output) {
|
|
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
|
|
-------------
|
|
|
|
:download:`foglight.js <foglight/foglight.js.txt>` (hint: right click, save as)
|
|
|
|
|clearfix|
|
|
|
|
.. literalinclude:: foglight/foglight.js.txt
|
|
:language: javascript
|
|
:linenos:
|
|
|
|
|
|
Web Plugin
|
|
----------
|
|
|
|
:download:`foglight.htm <foglight/foglight.htm.txt>` (hint: right click, save as)
|
|
|
|
|clearfix|
|
|
|
|
.. literalinclude:: foglight/foglight.htm.txt
|
|
:language: html
|
|
:linenos:
|
|
|