View on GitHub

WebSocketGaugeClientNeo

Renewal version of websocket gauge client, with using typescript and pixi.js(webgl).

Making custom meter application.

Table of contents

Introduction

Application diagram Meter application (html5 front panel view of gauge) have mainly two kind of object, one is websocket communication(client) class, and the other is meter (graphical) parts class. Websocket client classes manage the communication with Websocket server programs, with parsing JSON data from Websocket server. The parsed data are sent to meter parts classes by every animation frame. The meter parts classes changes its view (rotating needle, changing progressbar, changing digital indictaor, etc..) corresponding the data received from websocket communication(client) classes.

This document will explain how to costruct meter application class. During making the application class, you can setup,

If you want to make your own meter parts (with your new design), please refer CustomMeterParts.md and MeterPrimitive.md.

Meter application source files.

Meter application (top level deifition of meter panel that consist of meter parts and websocket communication) is stored in WebSocketGaugeClientNeo/application directory.

One meter application consists of one html file and one typescript file.

The *.html file simply calls javascript file. The main part of application logic is described in typescript file. (The typescript file finally will be compiled to javascript file by typescirpt oompiler.)

In this document, DigitalMFD-ELM327DemoApp.ts and DigitalMFD-ELM327DemoApp.html will be used to explain the application coding.

Bundle html file with typescript file.

DigitalMFD-ELM327DemApp.ts

// Set entry point html file to bundle by webpack
require("./DigitalMFD-ELM327DemoApp.html");

The html file is need to be bundled with the application typescirpt file as the require() statement above. By using sthis information, webpack will bundle html file and type script file, and build to WebSocketGaugeClientNeo/public_html directory.

Import dependent modules

DigitalMFD-ELM327DemoApp.ts

//Import application base class
import {MeterApplicationBase} from "../lib/MeterAppBase/MeterApplicationBase";

//Import meter parts
import {WaterTempGaugePanel} from "../parts/CircularGauges/SemiCircularGaugePanel";
import {ThrottleGaugePanel} from "../parts/CircularGauges/SemiCircularGaugePanel";
import {DigiTachoPanel} from "../parts/DigiTachoPanel/DigiTachoPanel";
import {BoostGaugePanel} from "../parts/CircularGauges/FullCircularGaugePanel";

//Import enumuator of parameter code
import {OBDIIParameterCode} from "../lib/WebSocket/WebSocketCommunication";
import {ReadModeCode} from "../lib/WebSocket/WebSocketCommunication";

Import several modules by import{} statement. In meter application source, following modules should be imported.

Application entry point

DigitalMFD-ELM327DemoApp.ts

window.onload = function()
{
    const meterapp = new DigitalMFD_ELM327DemoApp(720, 1280);
    meterapp.run();
}

This entry point method (window.onload()) is called when the browser finish loading html files. (But it should be noted that browser do not wait to finish loading picture and font files. Because of that, preload setting should be defined in application class). Construct application class (described below), and start application class with run() method.

Define application class

DigitalMFD-ELM327DemoApp.ts

class DigitalMFD_ELM327DemoApp extends MeterApplicationBase
{
    protected setWebSocketOptions()
    {
    ...
    }
    protected setTextureFontPreloadOptions()
    {
    ...
    }
    protected setPIXIMeterPanel()
    {
    ...
    }

The application class of DigitalMFD_ELM327DemoApp extends the base (abstract) class of MeterApplicationBase. The MeterApplicationBase class requires its subclass to implement follwoing method.

Setup websocket communication (by implementing setWebSocketOptions())

DigitalMFD-ELM327DemoApp.ts

protected setWebSocketOptions()
{
    //Enable ELM327 websocket client
    this.IsELM327WSEnabled = true;

    this.registerELM327ParameterCode(OBDIIParameterCode.Engine_Speed, ReadModeCode.SLOWandFAST, true);
    this.registerELM327ParameterCode(OBDIIParameterCode.Vehicle_Speed, ReadModeCode.SLOWandFAST, true);
    this.registerELM327ParameterCode(OBDIIParameterCode.Throttle_Opening_Angle, ReadModeCode.SLOWandFAST, true);
    this.registerELM327ParameterCode(OBDIIParameterCode.Coolant_Temperature, ReadModeCode.SLOW, true);
    this.registerELM327ParameterCode(OBDIIParameterCode.Manifold_Absolute_Pressure, ReadModeCode.SLOWandFAST, true);
}

The first part of above code sets the websocket enable flag. By setting IsELM327WSEnabled flag to true, ELM327 websocket client is enabled (this also enables websocket indicator on ELM327). Same kind of flags are also available on other websocket clents, such as..

The second part of above code registers the parameter code to communicate, by the method of registerELM327ParameterCode(code : string, readMode : string, interpolate : boolean).

Of course, code registration method is also available on Arduino, Defi and SSM.

Setup picture and font preloading (by implementing setTextureFontPreloadOptions())

DigitalMFD-ELM327DemoApp.ts

protected setTextureFontPreloadOptions()
{
    this.registerWebFontFamilyNameToPreload(WaterTempGaugePanel.RequestedFontFamily);
    this.registerWebFontFamilyNameToPreload(DigiTachoPanel.RequestedFontFamily);
    this.registerWebFontFamilyNameToPreload(BoostGaugePanel.RequestedFontFamily);
    this.registerWebFontFamilyNameToPreload(ThrottleGaugePanel.RequestedFontFamily);

    this.registerWebFontCSSURLToPreload(WaterTempGaugePanel.RequestedFontCSSURL);
    this.registerWebFontCSSURLToPreload(DigiTachoPanel.RequestedFontCSSURL);
    this.registerWebFontCSSURLToPreload(BoostGaugePanel.RequestedFontCSSURL);
    this.registerWebFontCSSURLToPreload(ThrottleGaugePanel.RequestedFontCSSURL);

    this.registerTexturePathToPreload(WaterTempGaugePanel.RequestedTexturePath);
    this.registerTexturePathToPreload(DigiTachoPanel.RequestedTexturePath);
    this.registerTexturePathToPreload(BoostGaugePanel.RequestedTexturePath);
    this.registerTexturePathToPreload(ThrottleGaugePanel.RequestedTexturePath);
}

In setTextureFontPreloadOptions() method, WebFontFamilyName, WebFontCSSURL and TexturePath shold be registered for each parts. These parameters are implemented as static properties of meter parts classes.

Setup meter panel configuration (by implementing setPIXIMeterPanel())

DigitalMFD-ELM327DemoApp.ts

protected setPIXIMeterPanel()
{
    // Construct meter panel parts.
    const digiTachoPanel = new DigiTachoPanel();
    digiTachoPanel.position.set(0,0);
    digiTachoPanel.scale.set(1.15);

    const boostPanel = new BoostGaugePanel();
    boostPanel.position.set(90,360);
    boostPanel.scale.set(1.3);

    const waterTempPanel = new WaterTempGaugePanel();
    waterTempPanel.position.set(0,890);
    waterTempPanel.scale.set(0.85);

    const throttlePanel = new ThrottleGaugePanel();
    throttlePanel.position.set(360,890);
    throttlePanel.scale.set(0.85);

    // Add meter panel parts to stage.
    this.stage.addChild(digiTachoPanel);
    this.stage.addChild(boostPanel);
    this.stage.addChild(waterTempPanel);
    this.stage.addChild(throttlePanel);

    // Define ticker method to update meter view (this ticker method will be called every frame).
    this.ticker.add(() =>
    {
        // Take timestamp of animation frame. (This time stamp is needed to interpolate meter sensor reading).
        const timestamp = PIXI.ticker.shared.lastTime;
        // Get sensor information from websocket communication objects.
        const tacho = this.ELM327WS.getVal(OBDIIParameterCode.Engine_Speed, timestamp);
        const speed = this.ELM327WS.getVal(OBDIIParameterCode.Vehicle_Speed, timestamp);
        const neutralSw = false;
        const gearPos = this.calculateGearPosition(tacho, speed, neutralSw);

        const boost = this.ELM327WS.getVal(OBDIIParameterCode.Manifold_Absolute_Pressure, timestamp)  * 0.0101972 - 1; //convert kPa to kg[](http://)f/cm2 and relative pressure

        const waterTemp = this.ELM327WS.getRawVal(OBDIIParameterCode.Coolant_Temperature);
        const throttle = this.ELM327WS.getVal(OBDIIParameterCode.Throttle_Opening_Angle, timestamp);

        // Update meter panel value by sensor data.
        digiTachoPanel.Speed = speed;
        digiTachoPanel.Tacho = tacho;
        digiTachoPanel.GearPos = gearPos;
        waterTempPanel.Value = waterTemp;
        throttlePanel.Value = throttle;
        boostPanel.Value = boost;
   });
}

The first half of above code create meter parts instances. Created instances are added to stage (after setting scale (by scale.set()) and position (by position.set())). (Since meter parts class extends PIXI.Container class, parts instances can be treated like pixi.js Container instances.)

The latter half of above code (after this.ticker.add()) defines the ticker method. This part of code will be called every animation frame of application (like requestAnimationFrame()). By using following methods, sensor data is obtaind by every animation frame, and transferred to meter parts instances.

The html file

html file (in this example, DigitalMFD-ELM327DemoApp.html) simply calls the javascript file (this javascript file is complied and deployed by webpack).

<html>
    <head>
        <title>DigitalMFDApp</title>
        <meta charset="UTF-8">

    </head>
    <body style="background: black">
        <script type="text/javascript" src="js/DigitalMFD-ArduinoDemoApp.js"></script>
    </body>
</html>

Build custom meter application

By referring sample source files and this document, you can make custom meter application. To build your custom meter application, webpack.config.js need to be modified to register your custom meter application. Please refer MeterAppBuild.md