WICE RP How-To

From WICE Wiki v2.89
Jump to navigation Jump to search

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)
  • 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: http://releases.linaro.org/13.09/components/toolchain/gcc-linaro/4.7

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: http://www.alkit.se/rp/libs/

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 star-upt, 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);

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.

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

In the main server setup script, add the following statement to include the script used to handle the 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 vnames and values. The data is then transferred to the client by socket.io communication in order to present the information. An example of the socketHandler script is presented 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 represented signals (Optional command)
          tcpSocket.write('list\n'); 
		
          // Subscribe to specified 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 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.

Send data to client:

client.emit('speed', speed);

Receive data in html 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);
});