Webportal

emmbedded apps

Tutorial (Advanced)

    MANAGING EMBEDDED #

    RECEIVING MESSAGES #

    When receiving data from your server, if:

    • The NAC module is active
    • Your application is launched (not in background)

    Then we are in the Foreground case.

    If at least one of those 2 conditions is not fulfilled then we are in the Background case.

    FOREGROUND

    When both WebPortal and your application are running, your messages are transmitted directly by WebPortal using the HTML5 API window.postMessage()

    To retrieve your messages you have to implement the "message" event handler and filter on the type WebPortal.onCurrentNotificationReceived

    Implementation example:

    window.addEventListener('message', function(messageEvent) {
    	var webportalMessage = messageEvent.data;
    	
    	if (webportalMessage.type === 'WebPortal.onCurrentNotificationReceived') {
    	    var mqttMessagePayload = webportalMessage.value;
    	    var appServerMessage = mqttMessagePayload.content;
    	
    	    // Use your own application message handler
    	    myHandleServerMessage(appServerMessage);
    	}
    });
    

    BACKGROUND

    When WebPortal is in background or your application isn’t running, WebPortal keeps in cache memory the last 10 data messages sent to your application.

    To retrieve those pending messages you have to use the WebPortal.getPendingNotifications() method that is directly injected in your application’s window object and so can be called directly.

    In order to retrieve the messages over time, you can call this method:

    • at fixed interval, using setTimeout or setInterval
    • when detecting the following WebPortal events being triggered:
      • WebPortal.onApplicationShow
      • WebPortal.onApplicationHide

    Implementation example:

    /**
     * Get the buffered messages for application 'appId'
     * and clean/reset the WebPortal's buffer
     * 
     * @param {string} Application id
     * @return {array} List of buffered MQTT messages or empty array if none
     */
    WebPortal.getPendingNotifications = function(appId) {};
    
    const MY_APP_ID = 'some_id';
    
    (function processWebportalPendingNotifications() {
    	var pendingNotifications = WebPortal.getPendingNotifications(MY_APP_ID);
    	for (var i = 0; i < pendingNotifications.length; ++i) {
    		var mqttMessage = pendingNotifications[i];
    
    		/*
    		mqttMessage = {
    			"content": {
    				// application data here
    			}
    		}
    		*/
    
    		//Use your own function to process the message
    		processMqttMessage(mqttMessage);
    	}
    
    	setTimeout(processWebportalPendingNotifications, 5000);
    })();
    


    RECEIVING NOTIFICATION #

    Receiving notification messages is very similar to Receiving data messages, the only difference being the presence of the property popupAction that indicates the action type corresponding to the notification message.

    MQTT message structure

    {
    
        /**
         * @type {string} WebPortal.okBtnPopupRequestClicked
         *             or WebPortal.cancelBtnPopupRequestClicked
         *             or WebPortal.closeTimeoutPopupRequest
         */
        "popupAction": "WebPortal.okBtnPopupRequestClicked",
    
        /**
         * @type {object} The data payload sent by your application server
         */
        "content": {
            "someKey": "someData"
        }
    }
    
    

    HANDLING MESSAGES

    window.addEventListener('message', function(messageEvent) {
        var webportalMessage = messageEvent.data;
    
        if (webportalMessage.type === 'WebPortal.onCurrentNotificationReceived') {
            var mqttMessagePayload = webportalMessage.value;
            var appServerMessage = mqttMessagePayload.content;
    
            // Your application message handler
            handleServerMessage(mqttMessagePayload);
        }
    });
    
    function handleServerMessage(mqttMessagePayload) {
        if ('popupAction' in mqttMessagePayload) {
            // Action related to a notification message (popup)
            handlePopupAction(mqttMessagePayload.popupAction, mqttMessagePayload.content);
        } else {
            // Application data message
            handleDataMessage(mqttMessagePayload.content);
        }
    }
    

    POSSIBLE VALUES FOR popupAction

    Type Triggered by
    okBtnPopupRequestClicked Pressing on the OK button
    cancelBtnPopupRequestClicked Pressing on the Cancel button
    closeTimeoutPopupRequest The notification’s display duration has expired (15s)


    SEND MESSAGE TO SERVER #

    WHAT YOU NEED

    SENDING A MESSAGE

    The Application generates a message that is encapsulated inside the content field then sent to the WebPortal before being forwarded to the Application server through the PSA Messaging System.

    To send a message you can do the following:

    window.parent.postMessage({
        'type' : 'WebPortal.onSendNotificationRequest',
        'value' : yourDataPayload
    }, '*');
    

    Note: the max size for a MQTT message is 128 Kio


    RESOURCES #

    Your Application has at its disposition multiple resources given by the WebPortal or it can even use its own resources.
    You can access these different resources with a simple XMLHttpRequest.

    const req = new XMLHttpRequest();
    req.open('GET', '/apps/MyApp/fonts/NAC-Icons.ttf', true);
    req.send();
    // In order to reuse a cached resource, you need to make a call to the exact same url or it will try to load it again.
    


    IMAGES, AUDIO, VIDEO #

    There are 2 different screens sizes for the NAC:

    • SD: 800 x 480

    • Wide HD: 1280 x 720

    However, some space at the top and the bottom are reserved by WebPortal for the controls. This amounts to a total height of 55px that will not be available.

    IMAGE FILES

    You need to keep in mind the size of the screen your application is delivered on.

    You will also have to consider the size of your file as the connection’s speed can vary greatly depending on the location of the user, this implies that to display your picture within a reasonable timeframe:

    • its size should ideally not exceed 150kB
    • its dimensions should respect the ones cited above.

    Information on Image limitations:

    Compatible image formats: JPEG, SVG Tiny 1.2, PNG, GIF, BMP

    Image weight (KB) Time ATB2 (~30KB/s) Time ATB3 (~100KB/s)
    100 3s 1s
    150 5s 1,5s
    200 7s 2s
    1000 35s 10s
    2000 110s 20s

    Note: For the GIF format, animations are not smooth.

    Note 2: When targeting a SD NAC, it is advised to encode graphic assets in 18 bits instead of 24/32 bits as the device is only able to display in 18 bits.

    Note 3: In order to limit the weight of your pictures, it is better to use SVG format. If not possible you should prioritize using JPEG.

    AUDIO FILES

    Information on Image limitations:

    Compatible audio format: MP3

    Audio Duration Audio weight (KB) Download Time ATB2 Download Time ATB3
    26s 350 12s 3s
    30s 400 14s 3s
    38s 500 17s 5s
    1:15 1000 34s 10s
    1:52 1500 50s 15s
    2:30 2000 70s 20s
    6:15 5000 170s 50s

    When reading audio files, you have to manually implement and handle the html 5 events triggered by the <audio> player such as play/stop… If you do not handle them, the file will not be read.

    // Wait for the browser to fully load
    HMI.WebBrowser.addEventListener("show", activateSource);
    
    // Configure media
    var activateSource = function(){
        Media.Audio.configure("MEDIA_PLAYER_SOURCE", "Active");
    };
    
    // Handle player and events
    var player = document.getElementById('player')[0];
    player.src = './sounds/louvre-les-voyages-de-champollion.mp3';
    document.getElementById('play').onclick = function(){
        player.play();
    };
    document.getElementById('pause').onclick = function(){
        player.pause();
    };
    

    AUDIO GUIDANCE

    About audio playing, the webportal have lower priority than guidance instructions of the vehicle infotainment system.

    If the guidance has to play an instruction while your app is already playing a sound in the webportal, the infotainment system will use ducking effect to play both sounds together.

    It means that your audio file will continue playing but with a reduced volume and the instruction will be the main audio canal.

    VIDEO FILES

    Videos are currently not compatible and will not be read by the WebPortal.


    OPEN POPUP #

    Follow this example to open a native popup from your app. See nativePopup reference.

    
    (function(w) {
        var WebPortal = {
            MESSAGE_NATIVE_POPUP_OPEN: 'WebPortal.nativePopup.open',
            MESSAGE_NATIVE_POPUP_EVT_OPENED : 'WebPortal.nativePopup.evt.opened',
            MESSAGE_NATIVE_POPUP_EVT_OK     : 'WebPortal.nativePopup.evt.ok',
            MESSAGE_NATIVE_POPUP_EVT_CANCEL : 'WebPortal.nativePopup.evt.cancel',
            MESSAGE_NATIVE_POPUP_EVT_TIMEOUT: 'WebPortal.nativePopup.evt.timeout',
        };
        var _messageListenerInitialized = false;
        
        /** 
         *@param {String} text - Text to display un the popup
         *@param {callback} okCallback - The OK button callback to execute when the OK button is clicked
         *@param {callback} cancelCallback - The Cancel callback to execute when the Cancel button is clicked
         *@param {callback} timeoutCallback - The timeout callback to execute when the popup close timeout is reached
        */ 
        w.openNativePopup = function(text, okCallback, cancelCallback, timeoutCallback) {
            var popupMessageHandler = function(e) {
                var eventMap = {};
                eventMap[WebPortal.MESSAGE_NATIVE_POPUP_EVT_OK] = okCallback;
                eventMap[WebPortal.MESSAGE_NATIVE_POPUP_EVT_CANCEL] = cancelCallback;
                eventMap[WebPortal.MESSAGE_NATIVE_POPUP_EVT_TIMEOUT] = timeoutCallback;
    
                var msg = e.data;
                console.log("[openNativePopup] message:");
                console.log(msg);
                // Ignore unknown message type
                if (!msg || !msg.type || !eventMap[msg.type])
                    return;
    
                _messageListenerInitialized = false;
                console.log("[openNativePopup] calling callback for msg.type: "+ msg.type);
                eventMap[msg.type]();
    
                console.log("[openNativePopup] removing web message handler");
                this.removeEventListener('message', popupMessageHandler);
            };
    
            // Init web message event listener
            if (!_messageListenerInitialized) {
                _messageListenerInitialized = true;
                console.log("[openNativePopup] adding web message listener");
                this.addEventListener('message', popupMessageHandler);
            }
    
            console.log("[openNativePopup] request to open native popup");
            var msg = {
                type: WebPortal.MESSAGE_NATIVE_POPUP_OPEN,
                text: text
            };
            console.log("[openNativePopup] msg: ");
            console.log(msg);
            parent.postMessage(msg, '*');
        };
    
      // Web message listener
      w.addEventListener('message', function(e) {
        var msg = {origin: e.origin, data: e.data};
          console.log("MessageEvent received: "+ JSON.stringify(msg));
      });
    
      // Popup button handler
      openNativePopup(
        "popup from app",
          function() { console.log("OK callback called") },
          function() { console.log("Cancel callback called") },
          function() { console.log("Timeout callback called") }
      );
    })(window);
    


    GUIDELINES #

    Here are the main criteria your app has to meet before deploying it on the WebPortal:

    Type Description
    Driver Attention

    The app must not display heavy animated elements.

    The app must not include games or other features outside the intended app types.

    Layout

    The app must not display text scrolling automatically.

    Visual Contrast

    The app must provide colors that the system can optimize for easy in-vehicle readability.

    Interaction

    User interactions must be processed with no more than a two-second delay.

    Buttons must be big enough so the user can easily interact with them.


    Type Description
    General

    The app must load content and launch in no more than 10 seconds.

    The App functionalities must work as expected.

    When the app is relaunched from the home screen, the state of the app must be restored.

    Interactive elements that are intentionally grayed-out must be non-functional.

    Notifications

    The app displays only relevant notifications.


    MANAGING BACKEND #

    MQTT TOPIC #

    To communicate with your application you need to indicate whom to publish your message to.

    Your publishing topic looks as follows:

    psa/OVIPPartners-Dev/to/uid/{UID}/opa/{VIN}/

    What’s more, your subscribing topic looks like this:

    psa/OVIPPartners-Dev/from/uid/{UID}/opa/{VIN}/

    Where:

    • UID is your login
    • VIN is the Vehicle ID Number corresponding to the vehicle the message is destined to.


    MQTT CONNECTION #

    WHAT YOU NEED BEFORE BEGINNING

    • Your SSL client certificate to secure the connection
    • Your credentials: login (UID) and MQTT password

    They will be created by PSA and given to you.

    WHAT YOU NEED TO DO

    You have to connect to the environment corresponding to your case by using MQTTClient.connect().

    2 different environments exist:

    • Preprod: iot-partners-preprod.mpsa.com:8886
    • Production: iot-partners-mpsa.com:8886

    EXAMPLE

    const mqttConfig = {
    	"uri": "iot-partners-preprod.mpsa.com:8886",
    	"sslClientCert": "./mqtt.client.crt",
    	"username": "MY_UID",
    	"password": "MY_PASSWORD",
    }
    
    var mqttClient = new MQTTClient(mqttConfig);
    mqttClient.connect();
    

    You are now connected to the MQTT Broker, you can start sending messages to your application.


    SEND MESSAGE TO APP #

    WHAT YOU NEED

    SENDING AN APPLICATIVE MESSAGE

    The Applicative message lets you send data to your application.

    STRUCTURE OF MQTT MESSAGE

    {
    	/**
    	 * Your application identifier (provided by PSA)
    	 * @type {string}
    	 */
    	"idApp": "yourAppIdentifier",
    
    	/**
    	 * Flag to allow the WebPortal to recognize the message type
    	 * @type {boolean}
    	 */
    	"popup": false,
    
    	/**
    	 * Arbitrary data for your application
    	 * @type {object}
    	 */
    	"content": {
    		"type": "action",
    		"action": "doSomething",
    		"params": {
    			"arg1": 42,
    			"arg2": "a string",
    			"arg3": [1, 2, 3, 4],
    			"arg4": {"firstname": "John", "lastname": "Doe"}
    		}
    	}
    }
    

    HANDLING MESSAGE

    var targetVin = "0123456789ABCDEFG";
    var mqttPublishTopic = `psa/OVIPPartners-Dev/from/uid/${mqttConfig.username}/opa/${targetVin}`;
    var payload = {
    	"idApp": "MY_APP_ID",
    	"popup": false,
    	"content": {
    		"type": "action",
    		"action": "doSomething",
    		"params": {
    			"arg1": 42,
    			"arg2": "a string",
    			"arg3": [1, 2, 3, 4],
    			"arg4": {"firstname": "John", "lastname": "Doe"}
    		}
    	}
    };
    
    var mqttMessage = new MQTTMessage();
    mqttMessage.payload = JSON.stringify(payload);
    mqttMessage.qos = 0;
    
    mqttClient.publish(mqttPublishTopic, mqttMessage);
    

    Note: The QOS 0, QOS 1 and QOS 2 connections will all be downgraded to QOS 0. This means that if acknowledgment of a message is needed then this functionality must be implemented directly by the application.

    Note: the max size for a MQTT message is 128 Kio


    SENDING POPUP #

    WHAT YOU NEED

    SENDING NOTIFICATIONS (POPUP)

    The Notification message or popup lets you display a modal window on the NAC in order to redirect the user’s attention over a precise action or information

    This notification will be displayed regardless of the current active module (Navigation, Media, …) and thus isn’t limited to the NAC’s Web module.

    The user is able to:

    The action done by the user is transferred to the embedded Application for it to react accordingly.

    IMPORTANT

    Caution: Popups (either MQTT or nativePopup) must not be asked more often than once every 20 seconds.

    Caution 2: Navigation.LaunchGuidance() and Navigation.LaunchGuidanceWaypoints() can not be launched when a popup (either MQTT or nativePopup) is displayed. To make sure the guidance have actually been launched please check that LaunchGuidance function return is True.

    STRUCTURE OF MQTT MESSAGE

    {
    	/**
    	 * Your application identifier (provided by PSA)
    	 * @type {string}
    	 */
    	"idApp": "yourAppIdentifier",
    
    	/**
    	 * Flag to allow the WebPortal to recognize the message type
    	 * @type {boolean}
    	 */
    	"popup": true,
    
    	/**
    	 * Flag to allow the WebPortal to recognize the popup type, displaying the Ok button or not
    	 * @type {boolean}
    	 */
    	"popupBtn": true,
    
    	/**
    	 * The text to be displayed in the modal popup
    	 * @type {string}
    	 */
    	"popupText": "An example UTF-8 encoded string\nwith multiple lines\nPSA Group"
    
    	/**
    	 * Arbitrary data for your application
    	 * @type {object}
    	 */
    	"content": {
    		"type": "action",
    		"action": "doSomething",
    		"params": {
    			"arg1": 42,
    			"arg2": "a string",
    			"arg3": [1, 2, 3, 4],
    			"arg4": {"firstname": "John", "lastname": "Doe"}
    		}
    	}
    }
    

    HANDLING POPUP

    var targetVin = "0123456789ABCDEFG"; // VIN is an alphanumeric string of 17 characters
    var mqttPublishTopic = `psa/OVIPPartners-Dev/from/uid/${mqttConfig.username}/opa/${targetVin}`;
    var payload = {
    	"idApp": "MY_APP_ID",
    	"popup": true,
    	"popupText": "You have a new message"
    };
    
    var mqttMessage = new MQTTMessage();
    mqttMessage.payload = JSON.stringify(payload);
    mqttMessage.qos = 1;
    
    mqttClient.publish(mqttPublishTopic, mqttMessage);
    

    Note: the max size for a MQTT message is 128 Kio


    RECEIVING MESSAGE #

    WHAT YOU NEED

    RECEIVING AN APPLICATIVE MESSAGE

    The Application generates a message that is encapsulated inside the content field then sent to the Application server through the PSA Messaging System.

    STRUCTURE OF MQTT MESSAGE

    {
    	/**
    	 * Arbitrary data from your application
    	 * @type {object|string}
    	 */
    	"content": {
    		"type": "action",
    		"action": "doSomething",
    		"params": {
    			"arg1": 42,
    			"arg2": "a string",
    			"arg3": [1, 2, 3, 4],
    			"arg4": {"firstname": "John", "lastname": "Doe"}
    		}
    	}
    }
    

    HANDLING MESSAGES

    const mqttSubscribeTopic = `psa/OVIPPartners-Dev/to/uid/${mqttConfig.username}/opa/#`;
    mqttClient.subscribe(mqttSubscribeTopic);
    
    mqttClient.onMessage(function(topic, payload) {
    	const myAppMessage = JSON.parse(payload).content;
    	MyAppDomain.processMessage(myAppMessage);
    });
    

    Note: the max size for a MQTT message is 128 Kio


    MANAGING ELIGIBILITY #

    VIN ELIGIBILITY

    GET baseURL/eligibility?vin={vin}

    EXAMPLE:
    (GET) https://api-cert-preprod.groupe-psa.com/applications/portail-ovip/v2/eligibility?client_id=12ab345c-6789-01d2-e345-6f78ghijk901&vin=VF000000000000001 
    
    var req = new XMLHttpRequest();
    req.open('GET', 'https://api-cert-preprod.groupe-psa.com/applications/portail-ovip/v2/eligibility?client_id=12ab345c-6789-01d2-e345-6f78ghijk901&vin=VF000000000000001', true);
    req.send(null);
    
    Return code Description
    HTTP 200 Request Successful. The response body will contain the answer.
    HTTP 400 Bad request, VIN is not syntactically correct (17 chars with letters and numbers is the correct syntax)
    HTTP 401 Not authorized

    Response Body

    {
        "vin": "VF000000000000001",
        "eligible": true
    }
    

    ADD SUBSCRIBERS

    This service enables to add a list of VIN as subscribers to the given service(s). In return, the response will give the list of successful requests.

    POST baseURL/subscriptions {"Content-Type":"application/json"}

    Request Body

    [
      {
        "code": "SERVICE1_CODE",
        "subscribers": [
          "VF000000000000001",
          "VF000000000000002",
          "VF000000000000003"
        ]
      },
      {
        "code": "SERVICE2_CODE",
        "subscribers": [
          "VF000000000000001",
          "VF000000000000002"
        ]
      }
    ]
    

    Note: Services must already exist in Portail OVIP

    Response Body

    [
      {
        "code": "SERVICE1_CODE",
        "subscribers": [
          "VF000000000000001",
          "VF000000000000003"
        ]
      },
      {
        "code": "SERVICE2_CODE",
        "subscribers": [
          "VF000000000000001",
          "VF000000000000002"
        ]
      }
    ]
    
    EXAMPLE
    (POST) https://api-cert-preprod.groupe-psa.com/applications/portail-ovip/v2/subscriptions?client_id=12ab345c-6789-01d2-e345-6f78ghijk901
    
    var req = new XMLHttpRequest();
    req.open('POST', 'https://api-cert-preprod.groupe-psa.com/applications/portail-ovip/v2/subscriptions?client_id=12ab345c-6789-01d2-e345-6f78ghijk901', true);
    req.onload = function () {
        // do something to response
        console.log(this.responseText);
    };
    req.send(data);
    

    Request payload:

    [
      {
        "code": "SERVICE_TEST",
        "subscribers": [
          "VF77777777777777"
        ]
      }
    ]
    
    Return code Description
    HTTP 200 Request Successful
    HTTP 202 Request Successful, but no subscription added (services do not exist)
    HTTP 400 Bad Request, VIN is not syntactically correct (17 chars with letters and numbers is the correct syntax) if there was only one VIN requested or global syntax is not correct
    HTTP 401 Not authorized

    REMOVE SUBSCRIPTION

    DELETE baseURL/subscriptions {"Content-Type":"application/json"}

    Request Body

    [
      {
        "code": "SERVICE1_CODE",
        "subscribers": [
          "VF000000000000001",
          "VF000000000000002",
          "VF000000000000003"
        ]
      },
      {
        "code": "SERVICE2_CODE",
        "subscribers": [
          "VF000000000000001",
          "VF000000000000002"
        ]
      }
    ]
    
    EXAMPLE
    (DELETE) https://api-cert-preprod.groupe-psa.com/applications/portail-ovip/v2/subscriptions?client_id=12ab345c-6789-01d2-e345-6f78ghijk901
    
    var req = new XMLHttpRequest();
    req.open('DELETE', 'https://api-cert-preprod.groupe-psa.com/applications/portail-ovip/v2/subscriptions?client_id=12ab345c-6789-01d2-e345-6f78ghijk901', true);
    req.send(data);
    

    Request payload:

    [
      {
        "code": "SERVICE_TEST",
        "subscribers": [
          "VF77777777777777"
        ]
      }
    ]
    
    Return code Desription
    HTTP 200 Request Successful
    HTTP 400 Bad Request, VIN is not syntactically correct (17 chars with letters and numbers is the correct syntax) if there was only one VIN requested or global syntax is not correct
    HTTP 401 Not authorized

    SUBSCRIBERS LIST

    GET baseURL/subscriptions?service={serviceCode}

    EXAMPLE
    (GET) https://api-cert-preprod.groupe-psa.com/applications/portail-ovip/v2/subscriptions?client_id=12ab345c-6789-01d2-e345-6f78ghijk901&service=SERVICE_TEST
    
    var req = new XMLHttpRequest();
    req.open('GET', 'https://api-cert-preprod.groupe-psa.com/applications/portail-ovip/v2/subscriptions?client_id=12ab345c-6789-01d2-e345-6f78ghijk901&service=SERVICE_TEST', true);
    req.send(data);
    
    Return code Description
    HTTP 200 Request Successful
    HTTP 204 Request Successful, but no subscribers were found for the specified service
    HTTP 400 Bad Request
    HTTP 401 Not authorized

    Response Body

    {
        "code": "SERVICE_CODE",
        "name": "Service One",
        "subscribers": [
          "VF000000000000001",
          "VF000000000000002",
          "VF000000000000003"
        ]
    }
    


    SUPPORT #

    The WebPortal Partner Support (WPS) is an important tool meant to simplify our partnership by regrouping:

    • Support requests
    • Questions
    • Deployment requests
    • Evolution requests
    • Bug reporting