Skip to content

Instantly share code, notes, and snippets.

@ssmereka
Last active December 14, 2016 19:12
Show Gist options
  • Save ssmereka/4a4f923c19106a207f4a29a63c1b6212 to your computer and use it in GitHub Desktop.
Save ssmereka/4a4f923c19106a207f4a29a63c1b6212 to your computer and use it in GitHub Desktop.
Generic HMI Broker Design

Generic HMI & Broker

The following architecture shows how multiple Generic HMI components, each with their own websocket connection, can share SDL Core's single websocket connection through the use of a Broker.

HMI-Broker-Diagram

Diagram Actors

  • User - a person interacting with the HMI's user interface(s).
  • HMI Display - React application emulating the main screen in a vehicle.
  • Hard Buttons - React application emulating the hard buttons in a vehicle's infotainment system, including driver steering wheel controls.
  • Vehicle Data - React application allowing the user to emulate vehicle data being sent to the infotainment system.
  • SDL Core - Instance of the embedded portion of SDL called SDL Core.

Assumptions

  • The Generic HMI, emulation of hard buttons, and emulation of vehicle data can be one or more react applications.
  • The UI components for emulation of hard buttons and emulation of vehicle data can be toggle on/off (e.g. with a build flag).
  • The Broker can handle one to many HMI components over a single websocket connection to core.
  • The Broker can be easily added into other HMI projects (e.g. for a Toyota or Ford specific HMI)
  • SDL Core can only handle a single websocket connection.
  • When subscribing to a component in SDL Core it is understood that the subscriber is asking for all messages related to that component (e.g. subscription to "Buttons" will return all messages related to soft and hard buttons).

How it works

  1. HMI connects to Broker via websocket and starts listening for events. The HMI is considered a client to the Broker.

    let brokerUrl = "ws://localhost:8086";
    this.socket = new WebSocket(brokerUrl)
    this.socket.onopen = this.onopen.bind(this)
    this.socket.onclose = this.onclose.bind(this)
    this.socket.onmessage = this.onmessage.bind(this)

View code on Github

  1. Broker connects to Core via a new websocket and starts listening for events.

    let coreUrl = "ws://localhost:8087";
    this.socket = new WebSocket(coreUrl)
    this.socket.onopen = this.onopen.bind(this)
    this.socket.onclose = this.onclose.bind(this)
    this.socket.onmessage = this.onmessage.bind(this)
  2. Broker forwards all messages from a client (e.g. HMI) to Core.

    onMessage(evt) {
        this.sendToCore(evt.data);
    }
  3. Broker sends all messages it recieves from Core to each client.

    onMessageFromCore(evt) {
        this.sendMessageToAllClients(evt.data);
    }
  4. Core cannot have more than one type of component registered. For example, sending a message to register the Buttons component twice would result in error(s).

    {
        "jsonrpc": "2.0",
        "id": -1,
        "method": "MB.registerComponent",
        "params": {
            "componentName": "Buttons"
        }
    }
  5. The broker filters registration messages and ensures only a single registration message is sent for each type of component.

    onMessage(evt) {
    	let rpc = JSON.parse(evt.data);
    	switch(rpc.message) {
    		case "MB.registerComponent":
    			if(  ! isComponentRegistered(rpc.params.componentName)) {
    				// TODO: Handle this senario
    				return;
    			}
    
    		// Otherwise, forward the message to core.
    		default:
    			this.sendToCore(evt.data);
    			break;
    	}
    }
  6. If a client (e.g. HMI) tries to register a component that has already been registered the Broker will add the client as an observer for these types of messages.

    onMessage(evt) {
    	let rpc = JSON.parse(evt.data);
    	switch(rpc.message) {
    		case "MB.registerComponent":
    			if(  ! isComponentRegistered(rpc.params.componentName)) {
    				// Now we handle this senario
    				
    				addObserverToComponent(evt);
    				
    				return;
    			}
    
    		// Otherwise, forward the message to core.
    		default:
    			this.sendToCore(evt.data);
    			break;
    	}
    }
  7. I lied earlier, the Broker does not send all messages back to all clients. The Broker will filter messages returned from Core. It will only return component messages to clients who have registered for that component's messages.

    onMessageFromCore(evt) {
    	// I lied
    	//this.sendMessageToAllClients(evt.data);    
       
       if(isComponentMessage(evt)) {
    	   for(let i = clients.length-1; i >= 0; --i) {  
    		   if(isClientRegisteredForComponent(client[i], rpc))
    				this.sendToClient(client[i], rpc);
    			}
    		}
    	} else {
    		this.sendMessageToAllClients(evt.data);
    	}
  8. Further filting of the messages can be done, by the client or Broker, using the correlation ID. This ID is unique and attached to each message sent to core. When Core responds to a message it will contain the same correlation ID.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment