Difference between revisions of "WICE RP How-To"

From WICE Wiki v2.91
Jump to navigation Jump to search
 
(17 intermediate revisions by 3 users not shown)
Line 3: Line 3:
* A toolchain for building executable programs on the target WCU platform
* A toolchain for building executable programs on the target WCU platform
* A set of libraries providing APIs to the WCU hardware platform resources (e.g. CAN, diagnostics, logging, etc)
* A set of libraries providing APIs to the WCU hardware platform resources (e.g. CAN, diagnostics, logging, etc)
* A high-level, socket based, Signal Broker API simplifying access to in-vehicle signals.
* WICE Portal access to configure the RP module for one or more WCUs
* WICE Portal access to configure the RP module for one or more WCUs


Line 13: Line 14:


All toolchains are gcc-based.
All toolchains are gcc-based.
The MX-4 toolchain is availble here: http://releases.linaro.org/13.09/components/toolchain/gcc-linaro/4.7
The MX-4 toolchain is availble here: https://www.alkit.se/pub/gcc-linaro-arm-linux-gnueabihf-2012.09-20120921_linux.tar.gz


== WICE RP development API libraries ==
== WICE RP development API libraries ==
Line 23: Line 24:
* OBD II library
* OBD II library


The development libraries are available here: http://www.alkit.se/rp/libs/
The development libraries are available here: https://www.alkit.se/rp/libs/
 
Note that you do not need these libraries when using the high-level Signal Broker API (the preferred approach), described below.


= Building and executing the RP program =
= Building and executing the RP program =
Line 42: Line 45:
The <code>-logfile</code> parameter supplies the path to a debug log file which will be uploaded to WICE portal when the RP module exits.
The <code>-logfile</code> parameter supplies the path to a debug log file which will be uploaded to WICE portal when the RP module exits.


To get access to the information supplied on the command line at star-upt, the RP program must parse the command line.
To get access to the information supplied on the command line at start-up, the RP program must parse the command line.


= A simple example RP program =
= A simple example RP program =
Line 49: Line 52:
The example shows how to read a configuration file supplied on the command line, to write a CAN diagnostic request on a CAN bus and read a diagnostic reply, and to log data to a log file that will be uploaded to the WICE Portal.
The example shows how to read a configuration file supplied on the command line, to write a CAN diagnostic request on a CAN bus and read a diagnostic reply, and to log data to a log file that will be uploaded to the WICE Portal.


The full source code of rpex is available [http://www.alkit.se/rp/example here].
The full source code of rpex is available [https://www.alkit.se/rp/example/ here].


The first thing the example program does is to parse the command line parameters:
The first thing the example program does is to parse the command line parameters:
Line 127: Line 130:
To avoid the intricacies of reading CAN or FlexRay signals directly from the device, having to parse and interpret the signals, there is a high-level socket based API for reading in-vehicle and WICE-internal signals. The API is based on a publish/subscribe model, wherein the RP client subscribes to signals by name, and periodically gets signal values for the selected set of signals. The communication between the RP client and the WICE signal broker component is done over a socket.
To avoid the intricacies of reading CAN or FlexRay signals directly from the device, having to parse and interpret the signals, there is a high-level socket based API for reading in-vehicle and WICE-internal signals. The API is based on a publish/subscribe model, wherein the RP client subscribes to signals by name, and periodically gets signal values for the selected set of signals. The communication between the RP client and the WICE signal broker component is done over a socket.


The example code <code>rpex-pubsub.c</code> illustrates how to use the publish/subscribe API. The full source code is available [http://www.alkit.se/rp/example here].
The example code <code>rpex-pubsub.c</code> illustrates how to use the publish/subscribe API. The full source code is available [https://www.alkit.se/rp/example/ here].


To set up the communication between the RP module and the signal broker, the example program calls a function called <code>setup_pubsub_connection()</code> after parsing the command line parameters:
To set up the communication between the RP module and the signal broker, the example program calls a function called <code>setup_pubsub_connection()</code> after parsing the command line parameters:
Line 190: Line 193:
  <code>send(pubsub_socket, "quit", 4, 0);
  <code>send(pubsub_socket, "quit", 4, 0);
   close(pubsub_socket);</code>
   close(pubsub_socket);</code>
 
As another example, the following Python code illustrates how to set up a socket connection th the Signal Broker, list the available signals, and subscribe to them.
<code>
# Example showing how to read signals from WICE Signal Broker in Python
import socket
HOST = "127.0.0.1"
PORT = 31415
def read_line(sock):
    "read a line from a socket"
    chars = []
    while True:
        a = sock.recv(1)
        chars.append(a)   
        if a == "\n" or a == "":
            return "".join(chars)
def read_signals(sock):
    "read available signals"
    sigs = []
    while True:
        line = read_line(sock)
        if line == "\n" or line == "\r\n" or line == "":
            return sigs
        sigs.append(dict(name = line.strip(), value = 0.0))
def parse_signal(str):
    "parse  string into signal name tuple"
    x = str.split()
    t = (x[0], x[1])
    return t
print("Setting up socket connection to port %d..." % PORT)
sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
sock.connect((HOST, PORT))
# Get list of available signals
sock.send("list\n")
signals = read_signals(sock)
print("Available signals:")
print(signals)
# Subscribe to signals
for signal in signals:
    sock.send("subscribe " + signal["name"] + "\n")
 
while True:
    line = read_line(sock)
    t = parse_signal(line)
    for s in signals:
        if s["name"] == t[0]:
            s.update({"value": t[1]})
            break
    print("Signal " + s["name"] + ", value = " + str(s["value"]))
s.close()
</code>
You can find more information about the signal broker API in [[WICE Signal Broker API]] section.
You can find more information about the signal broker API in [[WICE Signal Broker API]] section.


= Rapid Prototyping using Node.js =
= Rapid Prototyping using Node.js =
To develop Rapid Prototyping applications with graphical user interfaces, the Node.js framework can be used. Node.js is a cross-platform JavaScript run-time environment that executes JavaScript code on the server side. In the WICE RP concept, a Node.js application can be downloaded to a WCU where it is executed. The user interface developed can be accessed from a web browser connected to the WCU through Ethernet or WiFi. The setup described here is built with Node.js Express. However, other frameworks can be utilized using the same methodology, with small modifications.  
To develop Rapid Prototyping applications with graphical user interfaces, the [https://nodejs.org Node.js] framework can be used. Node.js is a cross-platform JavaScript run-time environment that executes JavaScript code on the server side. In the WICE RP concept, a Node.js application can be downloaded to a WCU where it is executed. The user interface developed can be accessed from a web browser connected to the WCU through Ethernet or WiFi. The setup described here is built with Node.js Express. However, other frameworks can be utilized using the same methodology, with small modifications.
 
All written to consol.log() will be in a log-file called rp-webapp-<date>.log and uploaded and accessible in the WICE back end.  


The following example demonstrates how to access vehicle signals from a Node.js application running on a WCU.
The following example demonstrates how to access vehicle signals from a Node.js application running on a WCU.
Line 209: Line 270:
  require('/path/to/script/socketHandler.js')(io)</code>
  require('/path/to/script/socketHandler.js')(io)</code>


The <code>socketHandler.js</code> script contains the code that handles the socket communication, both with the web-client and with the WICE Signal Broker. The function waits for a client to connect, by accessing the web page. It then connects to the Signal Broker and subscribes to the desired signals (in this example Engine Speed and Vehicle Speed on the MS CAN bus). The application then listens for incoming messages on the connected socket and processes the data, i.e. signal names and values. The data is then transferred to the client by <code>socket.io</code> communication in order to present the information in the web-browser. An example of the <code>socketHandler</code> script is presented here:
The <code>socketHandler.js</code> script contains the code that handles the socket communication, both with the web-client and with the WICE Signal Broker. The function waits for a client to connect, by accessing the web page. It then connects to the Signal Broker and subscribes to the desired signals (in this example Engine Speed and Vehicle Speed on the MS CAN bus). The application then listens for incoming messages on the connected socket and processes the data, i.e. signal names and values. The data is then transferred to the client by <code>socket.io</code> communication in order to present the information in the web-browser. An example <code>socketHandler.js</code> script is outlined here:


  <code>const net = require('net');
  <code>const net = require('net');
Line 223: Line 284:
           console.log('Connected to signal broker... ');
           console.log('Connected to signal broker... ');
   
   
           // Get a list of represented signals (Optional command)
           // Get a list of available signals (optional)
           tcpSocket.write('list\n');  
           tcpSocket.write('list\n');  
 
 
           // Subscribe to specified signals.  
           // Subscribe to desired signals.  
           tcpSocket.write('subscribe CAN_MS.EngineSpeed CAN_MS.VehicleSpeed\n');
           tcpSocket.write('subscribe CAN_MS.EngineSpeed CAN_MS.VehicleSpeed\n');
       });
       });
Line 254: Line 315:
  }</code>
  }</code>


Each received message is a newline-terminated text line consist ing on signal name followed by signal value, separeded by a space character. The message is transferred to the client through socket communication established in the first part of the script above.
Each received message is a newline-terminated text line consisting of signal name followed by signal value, separated by a space character (see [[#Using the high-level API for reading in-vehicle signals|Signal Broker]] section above). The message is transferred to the client through socket communication established in the first part of the script above.


Send data to client:
Send data to client:
  <code>client.emit('speed', speed);</code>
  <code>client.emit('speed', speed);</code>


Receive data in html script:
Receive data in HTML-embedded client script:
  <code>// Setup client socket.
  <code>// Setup client socket.
  var socket = io.connect('/'); // url path for the current page
  var socket = io.connect('/'); // url path for the current page
Line 270: Line 331:
               console.log(value);
               console.log(value);
  });</code>
  });</code>
To transfer a Node web-app to one or more WCUs, it should be put in a zip file and uploaded to the WICE Portal as an RP module.
The zip file must have a top level directory called <code>rp</code>, and in this directory a Node application entry point file called <code>server.js</code>.
A complete example of a Node web-application RP zip-file, including source code, is available for download [https://www.alkit.se/rp/node-example/ here].

Latest revision as of 13:11, 21 August 2023

Getting Started with WICE Rapid Prototyping

To get started developing an RP component to be run on a WICE WCU you need the following:

  • A toolchain for building executable programs on the target WCU platform
  • A set of libraries providing APIs to the WCU hardware platform resources (e.g. CAN, diagnostics, logging, etc)
  • A high-level, socket based, Signal Broker API simplifying access to in-vehicle signals.
  • WICE Portal access to configure the RP module for one or more WCUs

Software Development Toolchain

The development toolchain is typically installed on a desktop Linux system and then executables are built by cross-compilation. The following WCU platform architectures are supported:

  • Host Mobility MX-4: ARM Cortex A9 or Cortex A5 processor, 32 bit
  • Fältcom Committo: ARM 926EJ-S processor, 32 bit
  • Actia Platform: MIPS processor, 32 bit

All toolchains are gcc-based. The MX-4 toolchain is availble here: https://www.alkit.se/pub/gcc-linaro-arm-linux-gnueabihf-2012.09-20120921_linux.tar.gz

WICE RP development API libraries

The following libraries are available:

  • Canwrapper CAN library
  • Debug logging library
  • Timers and utilities library
  • Tactalib Diagnostics library
  • OBD II library

The development libraries are available here: https://www.alkit.se/rp/libs/

Note that you do not need these libraries when using the high-level Signal Broker API (the preferred approach), described below.

Building and executing the RP program

The binary file resulting from compiling the program for the target WCU platform is transferred to one or more in-vehicle WCUs using the WICE Portal. The binary will be executed when the WCU boots, and will be terminated when the WCU shuts down.

When executed, an number of command line arguments are given to the RP binary, as follows: -vin <VIN number> -settings <filename> -logfile <filename>

The -vin parameter supplies the Vehicle Identification Number of the vehicle the WCU is installed in.

The -settings parameter supplies the path to a configuration file the user can supply through the WICE portal.

The -logfile parameter supplies the path to a debug log file which will be uploaded to WICE portal when the RP module exits.

To get access to the information supplied on the command line at start-up, the RP program must parse the command line.

A simple example RP program

The example code rpex.c illustrates how to write a small program that can be executed as an RP module on the WICE in-vehicle WCU platform.

The example shows how to read a configuration file supplied on the command line, to write a CAN diagnostic request on a CAN bus and read a diagnostic reply, and to log data to a log file that will be uploaded to the WICE Portal.

The full source code of rpex is available here.

The first thing the example program does is to parse the command line parameters:

ret = parse_options(argc, argv);
 if (ret != 1) {
   return EXIT_FAILURE;
 }

The parse_options function sets the variables settings_filename, log_filename and vin to the strings supplied on the command line.

Next, debug output is enabled, and the output filename is set to log_filename, as specified on the command line. This ensures that the log file will be uploaded to the WICE Portal when rpex exits. The debug_enable and debug_msg functions are defined in the alkit-debug development library.

if (log_filename != NULL ) {
   debug_enable(log_filename);
}
debug_msg(1, "rpex version is %s\n", RPEX_VERSION_STRING);

Then, after some more log output, the settings file is read:

 if(settings_filename)
   read_settings(settings_filename);

The settings file is the main way (except for the command line parameters) to provide input to the RP program. It can contain any type of input data needed by the program. In the example, rpex only reads text from the settings file and prints it to the output logfile.

The program is now ready to do its main work,in this case to send a diagnostic request on a CAN bus, and read a response. This is done using the alkit-canwrapper library. The following code block shows how to set up the CAN interface, prepare a CAN frame for transmission, set up a CAN filter matching the expected response, and then send the CAN frame.

 // Initialize CAN interface
 ret = init_can(canbus, DEFAULT_CAN_DEVICE);
 can_device_fd = get_can_fd(canbus);
 
// Set up CAN filter install_CAN_filter(canbus, 0, CAN_FILTER_TYPE_PASS, can_filter_id, can_filter_mask);
// Prepare CAN frame for request memset(&req_frame, 0, sizeof(candata_t)); req_frame.id = ecu_id; req_frame.bus_nr = canbus; req_frame.frame_type = CAN_FRAME_11_BIT; memset(req_frame.databytes, 0, 8); req_frame.databytes[0] = 0x02; // SingleFrame, length = 2 req_frame.databytes[1] = 0x10; // Service 10 req_frame.databytes[2] = 0x01; // Default Diagnostic Session req_frame.databyte_count = 8; // With padding
// Send CAN frame debug_msg(1, "Sending diagnostic request on CAN bus %d...\n", canbus); n = write_can(canbus, &req_frame, &t); if(n<0) { debug_msg(1, "Error writing CAN frame\n"); return -1; }

The following code block shows how to read a response CAN frame.

// Read CAN response
 FD_ZERO(&readfds);
 FD_SET(can_device_fd, &readfds);
 timeout.tv_sec = 0;
 timeout.tv_usec = 200000; // 200 ms
 
ready = select(FD_SETSIZE, &readfds, NULL, NULL, &timeout); if (ready > 0) { ret = read_can(canbus, &resp_frame); if (ret == CAN_STATUS_OK) { // Read successful handle_can_frame(&resp_frame); } else { debug_msg(1, "Error reading CAN frame\n"); } } else { debug_msg(1, "No response.\n"); }

The function handle_can_framewhich is called when a frame is read simply logs the CAN identifier and payload to the logfile.

Using the high-level API for reading in-vehicle signals

To avoid the intricacies of reading CAN or FlexRay signals directly from the device, having to parse and interpret the signals, there is a high-level socket based API for reading in-vehicle and WICE-internal signals. The API is based on a publish/subscribe model, wherein the RP client subscribes to signals by name, and periodically gets signal values for the selected set of signals. The communication between the RP client and the WICE signal broker component is done over a socket.

The example code rpex-pubsub.c illustrates how to use the publish/subscribe API. The full source code is available here.

To set up the communication between the RP module and the signal broker, the example program calls a function called setup_pubsub_connection() after parsing the command line parameters:

pubsub_socket = setup_pubsub_connection(ip_addr, port_num);
 if(pubsub_socket<0) {
   debug_msg(1, "Pubsub connection failed\n");
   exit(-1);
 }

The setup_pubsub_connection() function sets up a TCP socket to use for the communication.

To this socket, a subscribe command is sent with a list of names of the signals to subscribe to. The syntax of the subscribe command is as follows:

subscribe <signal name 1> <signal name 2> ...<signal name n><CRLF>

Note that the separator character has to be space, and the line terminated with Carriage Return + Line Feed (or optionally only Line Feed).

sprintf(str, "subscribe %s %s\n", RPEX_SIGNAL1, RPEX_SIGNAL2);
 n = send(pubsub_socket, str, strlen(str), 0);
 if(n <= 0) {
   debug_msg(1, "Error subscribing to signals\n");
   exit(-1);
 }

Once the signals have been subscribed, the signal broker will send the signal values on the socket, to be read by the RP program, with the following syntax:

<signal name> <value><CRLF>

Again, the separator character between signal name and value is space.

The following code segment show an example of how to read signals as the bcome available on the socket using the select() system call.

while(count < 100) {
   FD_ZERO(&readfds);
   FD_SET(pubsub_socket, &readfds);
   timeout.tv_sec = 1;
   timeout.tv_usec = 0;
   ready = select(FD_SETSIZE, &readfds, NULL, NULL, &timeout);
   if (ready > 0) {
     n = read_line(pubsub_socket, &buf);
     if (n > 0) {
       // Read successful
       handle_signal(buf);
       free(buf);
       count++;
     } else {
       debug_msg(1, "Error reading signal\n");
       break;
     }
   }
 }

The read_line() helper function handles the actual reading of characters from the socket, and then the handle_signal() function parses the signal names and values, as follows.

void handle_signal(char *str) {
 char name[1000];
 float value;
 sscanf(str, "%s %f", name, &value);
 if(strcmp(name, RPEX_SIGNAL1)==0)
   debug_msg(1, "The value of signal 1 is %f\n", value);
 if(strcmp(name, RPEX_SIGNAL2)==0)
   debug_msg(1, "The value of signal 2 is %f\n", value);
}

The RP program can at any time unsubscribe to signals using the unsubscribe command, which has the same syntax as the subscribe command.

unsubscribe <signal name 1> <signal name 2> ...<signal name n><CRLF>

After unsubscribing to a (subset) of the subscribed signals, those signals' values will no longer be transmitted on the socket. (This is not shown in the example.)

The final part of the example shows how to terminate the subscription using the quit command.

send(pubsub_socket, "quit", 4, 0);
 close(pubsub_socket);

As another example, the following Python code illustrates how to set up a socket connection th the Signal Broker, list the available signals, and subscribe to them.

# Example showing how to read signals from WICE Signal Broker in Python

import socket

HOST = "127.0.0.1"
PORT = 31415

def read_line(sock):
   "read a line from a socket"
   chars = []
   while True:
       a = sock.recv(1)
       chars.append(a)     
       if a == "\n" or a == "":
           return "".join(chars)

def read_signals(sock):
   "read available signals"
   sigs = []
   while True:
       line = read_line(sock)
       if line == "\n" or line == "\r\n" or line == "":
           return sigs
       sigs.append(dict(name = line.strip(), value = 0.0))

def parse_signal(str):
   "parse  string into signal name tuple"
   x = str.split()
   t = (x[0], x[1])
   return t

print("Setting up socket connection to port %d..." % PORT)
sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
sock.connect((HOST, PORT))
# Get list of available signals
sock.send("list\n")
signals = read_signals(sock)

print("Available signals:")
print(signals)

# Subscribe to signals
for signal in signals:
   sock.send("subscribe " + signal["name"] + "\n")
 
while True:
   line = read_line(sock)
   t = parse_signal(line)
   for s in signals:
       if s["name"] == t[0]:
           s.update({"value": t[1]})
           break
   print("Signal " + s["name"] + ", value = " + str(s["value"]))
s.close()

You can find more information about the signal broker API in WICE Signal Broker API section.

Rapid Prototyping using Node.js

To develop Rapid Prototyping applications with graphical user interfaces, the Node.js framework can be used. Node.js is a cross-platform JavaScript run-time environment that executes JavaScript code on the server side. In the WICE RP concept, a Node.js application can be downloaded to a WCU where it is executed. The user interface developed can be accessed from a web browser connected to the WCU through Ethernet or WiFi. The setup described here is built with Node.js Express. However, other frameworks can be utilized using the same methodology, with small modifications.

All written to consol.log() will be in a log-file called rp-webapp-<date>.log and uploaded and accessible in the WICE back end.

The following example demonstrates how to access vehicle signals from a Node.js application running on a WCU.

In the Node.js application server set-up script, add the following code segment to include the Express framework and socket.io handling socket communication.

// Setup server environment
const express = require('express');
const app = express();
const server = require('http').createServer(app);
const io = require('socket.io')(server);

// Run internal util
require('/path/to/script/socketHandler.js')(io)

The socketHandler.js script contains the code that handles the socket communication, both with the web-client and with the WICE Signal Broker. The function waits for a client to connect, by accessing the web page. It then connects to the Signal Broker and subscribes to the desired signals (in this example Engine Speed and Vehicle Speed on the MS CAN bus). The application then listens for incoming messages on the connected socket and processes the data, i.e. signal names and values. The data is then transferred to the client by socket.io communication in order to present the information in the web-browser. An example socketHandler.js script is outlined here:

const net = require('net');
const PORT = 31415;

module.exports = function(io) {      
  io.on('connection', function(client) {  // Await client connection
      console.log('Client connected...');

      // Setup TCP socket to Signal Broker.
      var tcpSocket = new net.Socket();
      tcpSocket.connect(PORT, 'localhost', function () {
          console.log('Connected to signal broker... ');

          // Get a list of available signals (optional)
          tcpSocket.write('list\n'); 
		
          // Subscribe to desired signals. 
          tcpSocket.write('subscribe CAN_MS.EngineSpeed CAN_MS.VehicleSpeed\n');
      });
	
     // Listener triggered by messages from sigread.      
    tcpSocket.on('data', function(message) {       
       console.log(message.toString());  
       // Handle received messages here...
    });

    // Triggered if the tcpSocket is closed.
    tcpSocket.on('close', function () {
          console.log('TCP connection closed!');
     });

    // Triggered upon error events.
    tcpSocket.on('error', function(error) {
          console.log('TCP error: %s', error);
     });

    // Triggered if the client refreshes or closes the page
     client.on('disconnect', function (){
          console.log("Client disconnected");  
          tcpSocket.destroy(); // Disconnect from Signal Broker
     });
  });  
}

Each received message is a newline-terminated text line consisting of signal name followed by signal value, separated by a space character (see Signal Broker section above). The message is transferred to the client through socket communication established in the first part of the script above.

Send data to client:

client.emit('speed', speed);

Receive data in HTML-embedded client script:

// Setup client socket.
var socket = io.connect('/'); // url path for the current page
socket.on('connect', function (data) {
     console.log("Client connected to server");
});
	
// Triggered by ‘speed’ tagged messages from server.
socket.on('speed', function (value) {
              console.log(value);
});

To transfer a Node web-app to one or more WCUs, it should be put in a zip file and uploaded to the WICE Portal as an RP module. The zip file must have a top level directory called rp, and in this directory a Node application entry point file called server.js.

A complete example of a Node web-application RP zip-file, including source code, is available for download here.