Perceptor API specifications

Application Version: 2.6


QBOID Confidential and Proprietary Information


QBOID Perceptor products are 3D computer vision based products to speed up or automate typical human perception tasks that are related to perceiving shape and dimension. The Perceptor series algorithmically generates a set of object metadata, for every scan event that is to be saved, quantifying shape and dimension, annotated image data, as well as a variety of other data manually input by the operator, such as barcode scanner reads (either onboard the QBOID M2 product, or third party scanners, as supported by the QBOID S1 Perceptor), weight of an object, and other future data types.

Perceptor products provide 2 different sets of APIs that would cover different use case scenarios:

In addition, Perceptor products place at the disposal the following features:

Table of content

Common definitions and structures

Term Definitions

Dimensioning Record

WiFi API

API Description

Code examples

Android Intent API

Intent Definition

Configuration Import/Export

Loading Settings/Preferences


Release version: 2.6.6_NS

Common definitions and structures

Term Definitions

Device

The QBOID equipment responsible for generating dimensioning records. It can be either a Perceptor M2 or S1.

Server

The system that receives and processes dimensioning records from the QBOID device.

Dimensioning Record

A dimensioning record is a structure containing the information about a single item for either the WiFi or the Intent APIs. It is generally formatted as a JSON string with a structure like the following:

{
  "timestamp": "2021\/02\/14 07:19:38",
  "l": 195,
  "w": 165,
  "h": 72,
  "weight": 7709,
  "barcode": "845973030506",
  "shape": "Cuboidal",
  "device": "639b707f",
  "note": "",
  "attributes": {
    "hazmat": "true"
  },
  "image": "data:image/jpeg;base64,/9j/4AAQSkZJRgABAQAAAQABAAD/4gIoSUNDX1BST0ZJTEU..."
}

Basic Message structure

Field Format Description
timestamp string yyyy/MM/dd HH:mm:ss UTC timestamp using the Device time. Hour format is (0-23).
l int Length of the item in mm. Length is defined as the biggest among the 2 measurements parallel to the ground plane.

When the option Mandatory dimensioning is disabled, this field will be set to -1 if any dimensional field (l,w,h) is missing.
w int Width of the item in mm. Width is defined as the smallest among the 2 measurements parallel to the ground plane.

When the option Mandatory dimensioning is disabled, this field will be set to -1 if any dimensional field (l,w,h) is missing.
h int Height of the item in mm. Height is defined as the measurement orthogonal to the ground plane.

When the option Mandatory dimensioning is disabled, this field will be set to -1 if any dimensional field (l,w,h) is missing.
weight int
(Optional)
Weight of the item in g (grams).
barcode string
shape string Currently supported values are Irregular and Cuboidal. Additional shapes will be supported in the future.

When the option Mandatory dimensioning is disabled, this field will be set to an empty String if dimensional information is missing.
device string Device serial number. Same as shown in Settings -> System -> About Phone -> Status -> Serial number.
note string User-composed alphanumeric note string.
attributes map(string->string)
{} when empty
Contains the values assigned using the attributes buttons. Attribute buttons are fully customizable; any arbitrary combination of key/value may be present.
image string
PNG/JPEG base64 image
(Optional)
Full resolution color image with bounding box drawn on top.
Can be enabled/disabled in settings. The default image format is JPEG, while PNG can be selected in the application settings. (See WiFi API Settings for more info)
imagecolor string
PNG/JPEG base64 image
(Optional)
Full resolution color image without bounding box drawn on top.
Can be enabled/disabled in settings. The default image format is JPEG, while PNG can be selected in the application settings. (See WiFi API Settings for more info)
imageseg string
PNG/JPEG base64 image
(Optional)
Color image cropped to show the dimensioned object.
Can be enabled/disabled in settings. The background can be configured to be transparent. The default image format is JPEG, but PNG may be used depending on the application settings for format and transparency. (See WiFi API Settings for more info)

Advanced Encoding Info

Escaping

String fields are escaped as per JSON standard, more specifically:

Input Output
New Line \n
\ \\
/ \/
Carriage return \r
Tab \t

Image encoding

Images are encoded in the JSON request as a Data URI scheme with a base64 encoding adhering RFC-2045.

Following is an example of an encoded image:

data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABQAAAAUCAYAAACNiR0NAAAAHklEQVR42mP8z8AARNQDjKMGjho4auCogaMGjlQDAUwCJ+0NBcXlAAAAAElFTkSuQmCC

Note: Previous string can be copied and pasted to browser address bar to visualize the image.

Note: RFC-2045 specification requires that: The encoded output stream must be represented in lines of no more than 76 characters each.. For that reason, the encoded images will contain line breaks (i.e.: \n) that must be ignored by the decoding software.


WiFi API

This is a REST-like API where a JSON message is sent to a local or internet HTTP endpoint, and is designed to be familiar to developers doing client server web development.

Once the scan event is completed, either manually by an operator specific key press, or automatically by QBOID software, the dimensioning record is immediately queued up for offloading to a remote resource, e.g. a WMS system or manifesting software.

API Description

The Device will send a POST request with a JSON body to the HTTP endpoint defined in its configuration expecting a 200-299 HTTP response code.

M2 API settings

There are two groups of settings that can impact the data transmitted through the WiFi API:

Acquisition settings

Added in 2.4.0

Acquisition parameters can be configured in the M2 app in the section: Settings > Data Settings > Acquisition.

Entry Default Description
Color Resolution 0.3MP Defines which camera should be used for acquiring the color image.
0.3MP: will collect a 640x480 image with the low-resolution sensor, which is also used for the preview. The camera is located in the middle of the main sensor bar.
13MP: will collect a 4160x3120 image with the high-resolution camera situated beneath the main sensor bar and near the flashlight.

Note: Sequences captured at a resolution of 13MP will consume approximately 6MB of storage space, whereas 0.3MP sequences will only use around 1MB of storage space.
Compute Alpha Channel enabled If enabled, a foreground/background image segmentation will be performed on the result and pixels that belong to the background will have the alpha channel set to 0 on the segmented image (i.e.: imageseg field).
The image format for imageseg will be forced to PNG when Compute Alpha Channel is enabled regardless of the value of Use JPEG format

WiFi API settings


Optional fields, image format and endpoint can be configured in the M2 app in the section: Settings -> Data Settings -> WiFi API.

Entry Default Description
API endpoint http://192.168.7.100
:5000/newEntry
Define the URL for the HTTP/HTTPS endpoint.
Upload timeout 5 Define the connection and data read timeout in seconds.
Upload result image disabled Enable/Disable the image field.
Upload color image disabled Enable/Disable the imagecolor field.
Upload segmented image disabled Enable/Disable the imageseg field.
Use JPEG format enabled When enabled, JPEG format will be used for image, imagecolor and imageseg fields when transferred over WiFi.
PNG will instead be used if the option is disabled.
imageseg will be forced to PNG when Compute Alpha Channel is enabled, regardless of the value of Use JPEG format.

Note: To conserve resources and limit bandwidth usage, the resolution of PNG images will be capped at 1MP when transferred over WiFi. Such a limitation will be relevant only if 13MP is selected as Color Resolution. A pop-up will inform the user about the 1MP limitation when 13MP+PNG configuration is selected.
Custom request header settings

Added in 2.4.2
Under the section Settings -> Data Settings -> WiFi API -> Custom request header, the HTTP/HTTPS headers can be customized.

Entry Description
Authorization header Define the value for the Authorization HTTP/HTTPS header key. This parameter can be used to specify a Bearer Token used to authenticate and authorize access to a protected resource.
User-Agent header Define the value for the User-Agent HTTP/HTTPS header key.
Custom headers JSON Define a JSON string where a set of custom key-value HTTP/HTTPS headers can be defined. E.g.:
{"p1":"a", "p2":3}

HTTP request

HTTP request is configured as follows:

Property Value
Request Method POST
Content-Type application/json; utf-8
Accept application/json

A response code in the range 200-299 is expected as a response.

Sample RAW TCP Message:

POST /newEntry HTTP/1.1
Content-Type: application/json; utf-8
Accept: application/json
User-Agent: Dalvik/2.1.0 (Linux; U; Android 8.1.0; Scorpio Build/V008R001)
Host: 192.168.7.103:5000
Connection: Keep-Alive
Accept-Encoding: gzip
Content-Length: 199

{"timestamp":"2021\/02\/25 02:16:25","l":266,"w":203,"h":72,"weight":6807,"barcode":"X001SS387V","shape":"Irregular","device":"639b707f","note":"QWERTY","attributes":{"batt":"false","hazmat":"true"}}

HTTPS support

Added in 2.1.0

HTTP over TLS can be used by simply defining https as protocol in the URL of the endpoint.
If the server's SSL certificate was issued by a well-known Certificate Authority (CA), no further action is necessary.

Self-signed server certificate

A self-signed certificate is one that is signed with the servers own private key instead of being signed by a CA.
If the server is using a self-signed certificate, adding the certificate to the Perceptor M2 (see link) will be necessary. Failure to do so will throw the following error when the M2 is attempting to communicate with the server:

javax.net.ssl.SSLHandshakeException: java.security.cert.CertPathValidatorException: Trust anchor for certification path not found.
Generate the self-signed certificate

Following is an example on how to create a self-signed certificate using OpenSSL command line tool

openssl req -x509 -newkey rsa:4096 -keyout self_key.pem -out self_cert.pem -sha256 -days 365 -nodes \
 -subj "/C=US/ST=California/L=San Jose/O=Qboid/OU=TestingWebService/CN=testing.qboid.ai/emailAddress=info@qboid.ai" \
-addext "subjectAltName=DNS:testing.qboid.ai"
Add the self-signed certificate to Perceptor M2


Testing Server

QBOID provides a testing www server that can be used for experimenting with the different API configurations/protocols.
The test server can be reached through a reverse proxy that's exposing the same service with different combinations of protocols and certificates. All endpoints refer to the same service, so data pushed over https will be visible on the http page and vice versa.

Disclaimer
Before using the server, please review the following:

List of endpoints

Protocol Web Visualization URL M2 Endpoint
HTTP http://testing.qboid.ai:5001/ http://testing.qboid.ai:5001/newEntry
HTTPS (self signed) https://testing.qboid.ai:5002/ https://testing.qboid.ai:5002/newEntry
HTTPS (public CA) https://testing.qboid.ai:5003/ https://testing.qboid.ai:5003/newEntry

Web Visualization URLs can be opened with any PC or Phone browser. The page will wait for 120s for incoming data sent by any M2 device that's currently using a Testing Server Endpoint.

QR code link to HTTP Web Visualization URL

Device configuration

Testing server endpoints can be configured on the M2 settings in Data Settings -> API Endpoint.
Click on Use Testing Server and select the desired combination of Protocol and certificate.

For using the self-signed certificate:


Code examples

Disclaimer: The code is for illustration and learning purposes only. It prioritizes simplicity over production-level robustness and should not be used in a production environment.

Minimal Python app for receiving/parsing a message

Code

from flask import Flask
from flask import request
import json
app = Flask(__name__)

@app.route("/newEntry", methods=['POST'])
def newEntry():
    data = json.loads(request.data)
    print("Barcode: %s Size: %ix%ix%i" % (data["barcode"], data["l"], data["w"], data["h"]))
    return ""

if __name__ == "__main__":
    app.run(host = '0.0.0.0')

Output (2 dimensioning events of the same object)

Barcode: X001SS387V Size: 266x201x72
192.168.7.108 - - [24/Feb/2021 19:34:15] "POST /newEntry HTTP/1.1" 200 -
Barcode: X001SS387V Size: 267x200x72
192.168.7.108 - - [24/Feb/2021 19:36:00] "POST /newEntry HTTP/1.1" 200 -

For testing the previous code set the device end point to:

http://[HOST-IP]:5000/newEntry
where [HOST-IP] is the IP address of the PC running the code. E.g.: `192.168.7.100`

Python for generating sample messages

Code

import requests
import json
import random
import string
from datetime import datetime

# To be changed according to the setup
ENDPOINT = 'http://127.0.0.1:5000/newEntry'

def rndString(minsize, maxSize):
    return ''.join(random.choices(string.ascii_uppercase + string.digits, k = random.randint(minsize, maxSize))) 

def createJsonEntry():
    data = {}
    data['timestamp'] = datetime.utcnow().strftime('%Y/%m/%d %H:%M:%S')
    data['l'] = random.randint(10,500)
    data['w'] = random.randint(10,500)
    data['h'] = random.randint(10,500)
    data['weight'] = random.randint(10,50000)
    data['barcode'] = rndString(2,20)
    data['shape'] = 'Irregular' 
    data['device'] = rndString(8,8)
    data['note'] = rndString(0,20)
    attributes = {}
    for i in range(random.randint(0, 4)):
        attributes[rndString(3,5)] = rndString(3,5)
    data['attributes'] = attributes
    strjson = json.dumps(data)
    # Explicitly escaping "/" since python json library is not doing it by default
    strjson = strjson.replace('/', '\/')
    return strjson

if __name__ == "__main__":
    jsonMsg = createJsonEntry()
    print(jsonMsg)
    headers = {'content-type': 'application/json; utf-8', 'Accept' : 'application/json'}
    try:
        response = requests.post(ENDPOINT, data=jsonMsg, headers=headers)
        if (response.ok):
            print('Success')
        else:
            print('Error. Endpoint %s returned code: %i' % (ENDPOINT, response.status_code))    
    except Exception as e:
        print('Error while sending request to endpoint: %s' % ENDPOINT)

Output (Success)

{"timestamp": "2021\/02\/25 05:02:08", "l": 56, "w": 419, "h": 68, "weight": 46405, "barcode": "KPGVETAS", "shape": "Irregular", "device": "Y3S9JH1D", "note": "3RI9CKE2N", "attributes": {"FJ4": "U1HC6"}}
Success

Output (Endpoint returned an error)

{"timestamp": "2021\/02\/25 05:04:52", "l": 494, "w": 430, "h": 290, "weight": 31464, "barcode": "GE7WJ83B3PNJBN", "shape": "Irregular", "device": "DO1KF5ZI", "note": "A3KYJ", "attributes": {"SVRAJ": "HMXK"}}
Error. Endpoint http://127.0.0.1:5000/newEntry returned code: 405

Output (Endpoint unreachable)

{"timestamp": "2021\/02\/25 05:06:08", "l": 449, "w": 129, "h": 432, "weight": 2339, "barcode": "UUYXJ9Y447M", "shape": "Irregular", "device": "BO2X6TD2", "note": "B71U", "attributes": {"AF7Y": "BX44"}}
Error while sending request to endpoint: http://127.0.0.1:5000/newEntry

Python sample app with Authorization check and custom headers

Code

from flask import Flask
from flask import request
import json
app = Flask(__name__)

@app.route("/newEntry", methods=['POST'])
def newEntry():
    print("Authorization: %s" % request.headers.get('Authorization', ""))
    if request.headers.get("Authorization", "") != "qboid":
        return "Authorization error!", 401
    data = json.loads(request.data)
    print("User-Agent: %s" % request.headers.get('User-Agent'))
    print("Headers: %s" % str(dict(request.headers)))
    print("Barcode: %s Size: %ix%ix%i" % (data["barcode"], data["l"], data["w"], data["h"]))
    return ""

if __name__ == "__main__":
    app.run(host = '0.0.0.0')

Output (Success)

Barcode: X001SS387V Size: 266x201x72
192.168.7.108 - - [13/Mar/2023 14:19:57] "POST /newEntry HTTP/1.1" 200 -

Output (Wrong Authorization)

192.168.7.108 - - [13/Mar/2023 14:19:21] "POST /newEntry HTTP/1.1" 401 -

For testing the previous code set the device end point to:

http://[HOST-IP]:5000/newEntry
where [HOST-IP] is the IP address of the PC running the code. E.g.: `192.168.7.100`

And set the Authorization header to:

qboid

Android Intent API

Added in 2.3.0

This is an Android Intent based API where a local application can invoke the QBOID M2 Dimensioning application and optionally retrieve a dimensioning record.

The API is designed to work with Android Applications following the Android's Intent paradigm, but it also provides equivalent functionalities even to Web Applications running in a common Web Browser.

Intent Definition

QBOID M2 Dimensioning application can be invoked using an explicit intent with the following parameters:

Optionally, an action and a JSON configuration for that action can be specified in the Extra Bundle

Intent Extra Bundle Structure

Field Format Description
action string
(optional)
Action to be performed by the app. If not specified, the app will simply open the main activity
config JSON string (optional) Configuration to be used for the requested action. If not specified, default values would be used as specified in the documentation for each action

Supported actions

dim

M2 Dimensioning application will be invoked for acquiring a single Dimensioning Record. The workflow will return to the caller once the user confirms/saves the data for the current item. Application settings, upload queue and history won't be accessible until the action is completed or aborted.

Configuration

Field Format Default Description
target string queue Possible values are queue, callback or none.

queue: The record will be added to the standard QBOID application upload queue. The default WiFi API settings will be used. The upload may not happen immediately, depending on settings and network availability

callback: The record won't be added to the QBOID application upload queue nor the History but it will instead be sent immediately to the endpoint and the application won't return to the caller until the record is fully transmitted or an error occurred

none: The record won't be added to the QBOID application upload queue nor to the History.
endpoint string As defined in Settings > Data Settings > WiFi API > API endpoint Used when target=callback. Define the URL for the HTTP/HTTPS endpoint.
usejpg boolean true Used when target=callback. If true, JPEG format will be used for image, imagecolor and imageseg fields, PNG will be used otherwise. Additional constraints apply depending on the other Acquisition and WiFi API settings. (See WiFi API Settings for more info)
enableimage boolean false Used when target=callback. Enable/Disable the image field for the callback to the endpoint.
enableimageseg boolean false Used when target=callback. Enable/Disable the imageseg field for the callback to the endpoint
enableimagecolor boolean false Used when target=callback. Enable/Disable the imagecolor field for the callback to the endpoint
headerauthorization string As defined in Settings > Data Settings > WiFi API > API endpoint > Custom request header Used when target=callback. Defines the value for the Authorization HTTP header field.
headeruseragent string As defined in Settings > Data Settings > WiFi API > API endpoint > Custom request header Used when target=callback. Defines the value for the User-Agent HTTP header field.
headercustom JSON string As defined in Settings > Data Settings > WiFi API > API endpoint > Custom request header Used when target=callback. Defines a set of key-value pairs of strings for sending custom HTTP header fields.
Example: {"Proxy-Authorization":"Basic QWxhZGRpbjpvcGVuIHNlc2FtZQ==","Cache-Control":"no-cache"}
prefill JSON string null Added in 2.6.6
Pre-fills the specified fields in the M2 interface upon opening.
The required format is a JSON string structured according to the Dimensioning Record. Although the full dimensioning record can be provided, only the following fields will be processed::
  • barcode
  • note
  • l (length)
  • w (width)
  • h (height)
  • weight
Any additional fields will be ignored.
The accepted data types and units for each field are consistent with those defined in the Dimensioning Record.
Intent Returned value

When the caller Activity is resumed, an Intent Result will be returned with a resultCode.

If the user returned to the caller activity without scanning an item (i.e.: Some of the mandatory fields needed for record creation, as indicated by the application configuration, are missing), the returned values would be resultCode = 0 and a null Intent.

If the user correctly scanned an item, the caller will receive a resultCode = -1 and an Intent with the Extra Bundle containing the result of the task.

Returned Extra Bundle Structure

Field Format Description
res int 0: If an object record creation task succeeded
<0: If an error occurred
sent boolean Present only when target is set to callback. If true, the dimensioning record has been successfully sent via the API, false otherwise.
record JSON string The Dimensioning Record as defined here, with the difference that the image fields (i.e.:image,imagecolor and imageseg) will contain the absolute path on the sdcard for the images instead of the base64 encoding of the content.

Note: The WiFi API settings and the action configuration will not affect the content of the record. The path to the images will always be returned, if available, in PNG format.

Example of record field in the Extra Bundle

{
  "timestamp": "2022\/10\/05 23:06:45",
  "l": 319,
  "w": 203,
  "h": 421,
  "barcode": "TBA065003602504",
  "shape": "Irregular",
  "device": "FH0402211700146",
  "note": "Comment",
  "attributes": {
    "ovpk": "true",
    "batt": "false",
    "frgl": "true",
    "hazmat": "true"
  },
  "image": "\/sdcard\/qboid\/data\/2022_10_05\/2022_10_05_23_06_15\/Frames\/result_img_1.png",
  "imageseg": "\/sdcard\/qboid\/data\/2022_10_05\/2022_10_05_23_06_15\/Frames\/color0_segmented_1.png",
  "imagecolor": "\/sdcard\/qboid\/data\/2022_10_05\/2022_10_05_23_06_15\/Frames\/color0_1.png"
}

Typical use cases

Following is a description of the most common use cases for the Android Intent API integration and how they can be implemented using the provided API.

A) Open M2 Dimensioning App

Scenario
The customer uses several apps and has his own Launcher Application. Once the M2 Dimensioning App is opened, multiple sequences may be collected, settings may be changed, and data offloading can be performed over WiFi using the settings defined in the application.

Implementation
M2 Dimensioning App will be launched with an Intent with no action and no other Extra defined in the Bundle.

B) Trigger a single dimensioning operation


Scenario
The main workflow is directed by a Third Party app that may require the collection of dimensions, picture and/or other metadata for an item during the workflow. The local Third Party app doesn't require immediate feedback, and there is no hard constraint on when the data needs to be sent over WiFi. Instead, we want to be able to buffer the network requests in case of a slow network or no connection.

Implementation
We want to invoke an intent with action=dim and a config with target=queue (Note: queue is the default value so the config field can be omitted).
The M2 API settings needs to be configured with the correct endpoint.
The endpoint needs to be able to associate the received record to the correct entry in the workflow.

C) Request a dimensioning operation and retrieve the result


Scenario
The main workflow is directed by a Third Party app that may require the collection of dimensions, picture and/or other metadata for an item during the workflow. The app would need to use and/or display the information generated by the M2 Dimensioning App as soon as it regains focus. Additionally, the caller App wants to bypass the default Qboid configuration and keep the acquired sequences away from the standard Upload Queue and History defined in the Qboid app.

Implementation
We want to invoke an intent with action=dim and we need to properly construct the config attribute.

If the caller is an Android app, the target would generally be set to none (or callback if a remote endpoint has to be notified) and the Dimensioning Record would be read from the Intent Returned Value.

If the caller is a Web App, the target has to be set to callback and the proper endpoint should be set to the wanted URL. Generally the endpoint will be defined on the same Web Server that is running the Web Application and would contain the information to identify the current request and/or session. Once the focus is returned to the Web App, it will take care of retrieving the Dimensioning Record for the Web Server.

Usage with an Android Application

This section provides sample code on how to invoke the Explicit Intent, and how to receive the Intent Result for the different typical use cases. Examples are in Java using Activity.onActivityResult. Equivalent code may be written in Kotlin and/or using ActivityResultContracts

A) Open M2 Dimensioning App
For Invoking the M2 Dimensioning App with no action we can simply define the Explicit Intent and call it with startActivity

// A) Open M2 Dimensioning App
Intent intent = new Intent();
ComponentName cName = new ComponentName("ai.qboid.glapp", "ai.qboid.glapp.DimActivity");
intent.setComponent(cName);
context.startActivity(intent);

B) Trigger a single dimensioning operation
An action=dim can be set as Extra simply adding intent.putExtra("action", "dim"); to the previous example

// B) Trigger a single dimensioning operation
Intent intent = new Intent();
ComponentName cName = new ComponentName("ai.qboid.glapp", "ai.qboid.glapp.DimActivity");
intent.setComponent(cName);
intent.putExtra("action", "dim"); // Select dim as action
context.startActivity(intent);

C) Request a dimensioning operation and retrieve the result
Setting the target=none for the config field can be done using a JSONObject instance.

Additionally, for receiving the result, the M2 Dimensioning Activity needs to be started with startActivityForResult, that will take a requestCode argument to help identify the request that triggered the result being returned.

onActivityResult will be invoked when the called Activity is closed and it will contain the data generated by the M2 Dimensioning App.

// C) Request a dimensioning operation and retrieve the result
Intent intent = new Intent();
ComponentName cName = new ComponentName("ai.qboid.glapp", "ai.qboid.glapp.DimActivity");
intent.setComponent(cName);
intent.putExtra("action", "dim"); // Select dim as action
// Add config field with target->none to the Intent Extras
JSONObject json = new JSONObject();
try {
    json.put("target", "none");
    intent.putExtra("config", json.toString());
} catch (JSONException e) {}
// Start activity and wait for result
context.startActivityForResult(intent, REQUEST_CODE); // REQUEST_CODE can be any number and it's used to identify the result.

//! Process the result obtained once the called Activity is closed
protected void onActivityResult(int requestCode, int resultCode, Intent data) {
    super.onActivityResult(requestCode, resultCode, data);
    if (requestCode == REQUEST_CODE && data != null && resultCode == -1){
        String recordJson = data.getStringExtra("record");
        // -- PROCESS HERE THE DIMENSIONING RECORD --
    }
}

Usage with a Web Application

A Web Application can launch an Android Application with an Intent as described in: Android Intents with Chrome

The M2 Dimensioning App is configured to receive a BROWSABLE Intent with scheme intentapi on its class ai.qboid.glapp.DimActivity.

Below are a few examples on how to invoke the Explicit Intent, and how to receive the Intent Result for the different typical use cases. Examples are provided in Python with Flask + Javascript. Equivalent code may be written in different programming languages and different Web Server.

A) Open M2 Dimensioning App
This use case may be simply implemented adding a link (or a button) pointing to the following URI: intent://#Intent;scheme=intentapi;package=ai.qboid.glapp;end

HTML example:

<a href="intent://#Intent;scheme=intentapi;package=ai.qboid.glapp;end">
    A) Open M2 Dimensioning App</a>

B) Trigger a single dimensioning operation
The URI from the previous example can be altered using the syntax for defining String extras to configure dim as action: S.action=dim

HTML example:

<a href="intent://#Intent;scheme=intentapi;package=ai.qboid.glapp;S.action=dim;end">
    B) Trigger a single dimensioning operation</a>

C) Request a dimensioning operation and retrieve the result
Web Applications are unable to receive the Intent Returned value when running in a Web Browser, so a result can be obtained instructing the M2 Dimensioning App on how to send data to the Web Server and then retrieving that information when the Browser Tab is resumed. This workflow can be split in the following steps (diagram):

The goal would be to generate an intent URI with a configuration where target=callback and the endpoint is set to a value generated by the current Web Application.

Here is an example of the config JSON string where the server is running at the address http://192.168.1.164:5000 and /callback is the endpoint handling the record coming from M2 Dimensioning App:

{"target":"callback","endpoint":"http://192.168.1.164:5000/callback"}

The config String would then need to be converted to the correct URI encoding using a library like urllib, obtaining something like:

%7B%22target%22%3A%22callback%22%2C%22endpoint%22%3A%22http%3A//192.168.1.164%3A5000/callback%22%7D

The encoded config can then be added to the URI in the previous example prepending it with the attribute: S.config=. The final result would look like:

intent://#Intent;scheme=intentapi;package=ai.qboid.glapp;S.action=dim;S.config=%7B%22target%22%3A%22callback%22%2C%22endpoint%22%3A%22http%3A//192.168.1.164%3A5000/callback%22%7D;end

Following is the sample Python + Flask code for generating the URI:

config = '{"target":"callback","endpoint":"%scallback"}' % request.url_root
intent = "intent://#Intent;scheme=intentapi;package=ai.qboid.glapp;S.action=dim;S.config=%s;end" % urllib.parse.quote(config)

Once the user confirms/saves the data for the current item, the dimensioning record will be sent to the endpoint as defined by the caller. The application would then close and return the focus to the browser running the Web App.

Once the Tab with the Web Application is resumed, the server needs to be queried to retrieve the information sent by the M2 Dimensioning App. This can be done by fetching the data from an endpoint on the server as soon as the Web Document becomes visible again.

Following is a javascript example that detects when document.visibilityState is changed to visible and invokes the update() function that will be implemented to retrieve the information from the server.

<script>
    document.addEventListener("visibilitychange", () => {
        if (document.visibilityState === 'visible')
            update();
    });
    function update() {
       // Query the server for the result
    }
</script>

Web Application use case C) diagram

|

Python code example

We are presenting here an example in Python with Flask + Javascript, summarizing the 3 uses cases discussed before. It includes code to be used in an HTML template and the endpoints to be defined in Flask. We finally provide a fully self-contained Python app.

Disclaimer: The code is for illustration and learning purposes only. It prioritizes simplicity over production-level robustness and should not be used in a production environment.

HTML Template (main.html)

<a href="intent://#Intent;scheme=intentapi;package=ai.qboid.glapp;end">A) Open M2 Dimensioning App</a>
<hr>
<a href="intent://#Intent;scheme=intentapi;package=ai.qboid.glapp;S.action=dim;end">B) Trigger a single dimensioning operation</a>
<hr>
<a href="{{intent_c}}">C) Request a dimensioning operation and retrieve the result</a><br>
<pre id="result"></pre>
<script>
    document.addEventListener("visibilitychange", () => {
        if (document.visibilityState === 'visible')
            update();
    });
    function update() {
        var xhttp = new XMLHttpRequest();
        xhttp.open("GET", "size", true);
        xhttp.onload = function () {
            let responseTxt = xhttp.responseText;
            document.getElementById('result').innerHTML = responseTxt;
        };
        xhttp.onerror = function () {
            document.getElementById('result').innerHTML = "Request Error";
        };
        xhttp.send();
    }
</script>

Python Web Server

latestResponse = "---"

@app.route("/", methods=['GET'])
def main():
    config = '{"target":"callback","endpoint":"%scallback"}' % request.url_root
    intent = "intent://#Intent;scheme=intentapi;package=ai.qboid.glapp;S.action=dim;S.config=%s;end" % urllib.parse.quote(config)
    res = render_template("main.html", intent_c = intent)
    return res

@app.route("/callback", methods=['POST'])
def callback():
    global latestResponse
    data = json.dumps(json.loads(request.data), indent = 4)
    latestResponse = data
    return ""

@app.route("/size", methods=['GET'])
def size():
    global latestResponse
    retSize = latestResponse
    latestResponse = "---"
    return retSize

Note: In this example, for simplicity, the case where multiple sessions can be established with the server is not supported. In an actual production implementation the /callback and /size endpoints would require additional variables to properly identify a device and/or session.

Full python code with embedded HTML

Expand
from flask import Flask, render_template_string,request,render_template
import json
import urllib.parse
app = Flask(__name__)

main_html = """
<!DOCTYPE html>
<html>
    <head>
        <meta name="viewport" content="width=device-width, initial-scale=1.0">
    </head>
<body>
    
    <h3>Perceptor Android API Test</h3>
    <a href="intent://#Intent;scheme=intentapi;package=ai.qboid.glapp;end">A) Open M2 Dimensioning App</a>
    <hr>
    <a href="intent://#Intent;scheme=intentapi;package=ai.qboid.glapp;S.action=dim;end">B) Trigger a single dimensioning operation</a>
    <hr>
    <a href="{{intent_c}}">C) Request a dimensioning operation and retrieve the result</a><br>
    <pre id="result"></pre>
    <script>
        document.addEventListener("visibilitychange", () => {
            if (document.visibilityState === 'visible')
                update();
        });
        function update() {
            var xhttp = new XMLHttpRequest();
            xhttp.open("GET", "size", true);
            xhttp.onload = function () {
                let responseTxt = xhttp.responseText;
                document.getElementById('result').innerHTML = responseTxt;
            };
            xhttp.onerror = function () {
                document.getElementById('result').innerHTML = "Request Error";
            };
            xhttp.send();
        }
    </script>
</body>
</html>
"""

latestResponse = "---"

@app.route("/", methods=['GET'])
def main():
    config = '{"target":"callback","endpoint":"%scallback"}' % request.url_root
    intent = "intent://#Intent;scheme=intentapi;package=ai.qboid.glapp;S.action=dim;S.config=%s;end" % urllib.parse.quote(config)
    res = render_template_string(main_html, intent = intent)
    return res

@app.route("/callback", methods=['POST'])
def callback():
    global latestResponse
    data = json.dumps(json.loads(request.data), indent = 4)
    latestResponse = data
    return ""

@app.route("/size", methods=['GET'])
def size():
    global latestResponse
    retSize = latestResponse
    latestResponse = "---"
    return retSize

if __name__ == "__main__":
    app.run(host = '0.0.0.0')

Testing server

QBOID provides a testing server with http and https endpoints running a live version of the sample Web Application described in this section.

Disclaimer
Before using the server, please review the following:

Testing Server URLs

Protocol URL QR code
HTTP http://testing.qboid.ai:5001/api
HTTPS https://testing.qboid.ai:5003/api

Configuration Import/Export

Added in 2.5.0

The purpose of this functionality is to facilitate the export and import of M2 settings, also referred to as preferences, allowing them to be easily applied across multiple devices as well.

Saving Settings/Preferences

To export the configuration of an M2, the specific button placed under Settings -> Import/Export Settings -> Export settings to local file can be used. Once clicked, this will save the current device configuration to the file /sdcard/qboid/preferences.json.

Loading Settings/Preferences

There are two main workflows to import a previously saved configuration into an M2 device:

  1. From local file
    A previously saved preferences.json file can be placed in the /sdcard/qboid/preferences.json location. To load it within the M2 application, navigate to Settings -> Import/Export Settings and click the Import settings from local file button.
  2. From QR code
    If it is not feasible or desired to move files between devices, QR codes can be used instead. To generate a QR code containing a device configuration, a previously saved preferences.json file is necessary. Visit the link https://testing.qboid.ai/qr_code_config_generator.html and click on Choose File to select the preferences.json file that needs to be encoded as a QR code. If the file is a valid configuration, a QR code will be displayed on the webpage. The generated QR code can then be downloaded as a PNG or SVG image. The configuration can be loaded into the M2 device simply scanning the QR code from the main M2 application window or from Settings -> Import/Export Settings

General warnings

Export/Import Behavior

When exporting to a file or generating a QR-Code, only the settings that differ from the default values are exported.

The behavior when importing preferences is determined by the restoredefault entry in the JSON:

There is one exception to this rule for the configuration of Attributes (i.e., the 4 soft keys defined on the home screen). This exception will be discussed in more detail in the next section.

Attribute Configuration Exception

Since version 2.6.6, with the introduction of different types for the four attributes on the home screen, more advanced logic has been introduced for exporting and importing attribute configurations to fully support the use case where a QR-Code is used to reconfigure all attribute entries on the fly.

Here’s how the logic works:

Supported settings

Name Type Default value Min version Description
allow_empty_trash
Settings pathApplication settingsData SettingsHistoryAllow Empty Trash
boolean
false
2.5.0 Allow to empty the trash from records
api_custom_headers
Settings pathApplication settingsData SettingsWiFi APICustom request headerCustom headers JSON
JSON string 2.5.0 The custom headers (specified as JSON key-value entries) to be used when sending an API request
api_endpoint
Settings pathApplication settingsData SettingsWiFi APIAPI endpoint
string
http://192.168.7.100:5000/newEntry
2.5.0 The API endpoint to upload the results
api_header_authorization
Settings pathApplication settingsData SettingsWiFi APICustom request headerAuthorization header
string 2.5.0 The Authorization header to use when sending an API request
api_header_useragent
Settings pathApplication settingsData SettingsWiFi APICustom request headerUser-Agent header
string 2.5.0 The User-Agent header to use when sending an API request
api_upload_format_jpeg
Settings pathApplication settingsData SettingsWiFi APIUse JPEG format
boolean
true
2.5.0 Upload all the images in JPEG format when sending a record via API
api_upload_img_color
Settings pathApplication settingsData SettingsWiFi APIUpload color image
boolean
false
2.5.0 Upload the color image when sending a record via API
api_upload_img_result
Settings pathApplication settingsData SettingsWiFi APIUpload result image
boolean
false
2.5.0 Upload the result image when sending a record via API
api_upload_img_seg
Settings pathApplication settingsData SettingsWiFi APIUpload segmented image
boolean
false
2.5.0 Upload the segmented image when sending a record via API
api_upload_results
Settings pathApplication settingsData SettingsWiFi APIRealtime WMS upload
boolean
false
2.5.0 Realtime WMS upload after a completed dimensioning
api_upload_timeout
Settings pathApplication settingsData SettingsWiFi APIUpload timeout
numeric
5
2.5.0 The timeout in seconds to be used when sending an API request
api_upload_unmetered
Settings pathApplication settingsData SettingsWiFi APIUpload on unmetered connections only
boolean
false
2.5.7 Upload on unmetered connections only
attribute1_key
Settings pathApplication settingsData SettingsConfigure AttributesAttribute 1Key
string 2.5.0 The key of the first attribute
attribute1_show_values
Settings pathApplication settingsData SettingsConfigure AttributesAttribute 1Show Values
boolean
false
2.5.0 Show the values of the first attribute
attribute1_type
Settings pathApplication settingsData SettingsConfigure AttributesAttribute 1Type
string
softkey
2.6.6 The type of the first attribute. Allowed values: softkey, list, number
attribute1_values
Settings pathApplication settingsData SettingsConfigure AttributesAttribute 1Values
string 2.5.0 The values of the first attribute
attribute2_key
Settings pathApplication settingsData SettingsConfigure AttributesAttribute 2Key
string 2.5.0 The key of the second attribute
attribute2_show_values
Settings pathApplication settingsData SettingsConfigure AttributesAttribute 2Show Values
boolean
false
2.5.0 Show the values of the second attribute
attribute2_type
Settings pathApplication settingsData SettingsConfigure AttributesAttribute 2Type
string
softkey
2.6.6 The type of the second attribute. Allowed values: softkey, list, number
attribute2_values
Settings pathApplication settingsData SettingsConfigure AttributesAttribute 2Values
string 2.5.0 The values of the second attribute
attribute3_key
Settings pathApplication settingsData SettingsConfigure AttributesAttribute 3Key
string 2.5.0 The key of the third attribute
attribute3_show_values
Settings pathApplication settingsData SettingsConfigure AttributesAttribute 3Show Values
boolean
false
2.5.0 Show the values of the third attribute
attribute3_type
Settings pathApplication settingsData SettingsConfigure AttributesAttribute 3Type
string
softkey
2.6.6 The type of the third attribute. Allowed values: softkey, list, number
attribute3_values
Settings pathApplication settingsData SettingsConfigure AttributesAttribute 3Values
string 2.5.0 The values of the third attribute
attribute4_key
Settings pathApplication settingsData SettingsConfigure AttributesAttribute 4Key
string 2.5.0 The key of the fourth attribute
attribute4_show_values
Settings pathApplication settingsData SettingsConfigure AttributesAttribute 4Show Values
boolean
false
2.5.0 Show the values of the fourth attribute
attribute4_type
Settings pathApplication settingsData SettingsConfigure AttributesAttribute 4Type
string
softkey
2.6.6 The type of the fourth attribute. Allowed values: softkey, list, number
attribute4_values
Settings pathApplication settingsData SettingsConfigure AttributesAttribute 4Values
string 2.5.0 The values of the fourth attribute
auto_cleanup_days
Settings pathApplication settingsData SettingsData cleanupAutomatic data cleanup → ``
numeric
30
2.5.0 Duration to retain data in days
auto_cleanup_enabled
Settings pathApplication settingsData SettingsData cleanupAutomatic data cleanup → ``
boolean
false
2.5.0 Enable automatic cleanup, to automatically delete sequences older than a specified value
barcode_mandatory
Settings pathApplication settingsWorkflow SettingsScan ConstraintsMandatory Barcode
boolean
false
2.6.0 Acquiring barcode is mandatory to save a result
btscale_enabled
Settings pathApplication settingsWorkflow SettingsBluetooth ScaleSettingsUse Bluetooth Scale
boolean
false
2.5.0 Use bluetooth scale
btscale_name_prefix
Settings pathApplication settingsWorkflow SettingsBluetooth ScaleSettings → ``
string
Adam
2.5.0 The prefix for the name of the bluetooth scale
color_resolution
Settings pathApplication settingsData SettingsAcquisitionColor Resolution
string
2
2.5.0 The color resolution to use. Possible values: 2 (0.3MP), 3 (13MP)
compute_alpha_channel
Settings pathApplication settingsData SettingsAcquisitionCompute Alpha Channel
boolean
false
2.5.0 Compute the alpha channel on the segmented images
csv_output_utc
Settings pathApplication settingsData SettingsUSB APICSV Filename UTC
boolean
false
2.5.0 UTC time zone is used for the CSV file naming
custom_field_enabled
Settings pathApplication settingsData SettingsCustom Fields → ``
boolean
false
2.5.0 Enable the custom field. If disabled, the Sum is computed
custom_field_formula
Settings pathApplication settingsData SettingsCustom Fields → ``
string
L,W,H,+,+
2.5.0 Formula for computing the custom field. The formula is in Reverse Polish Notation (RPN) using a , delimiter
custom_field_label
Settings pathApplication settingsData SettingsCustom Fields → ``
string
@string/sum
2.5.0 Label for the custom field. Note: Strings exceeding 3 characters in length may affect UI formatting
dimensioning_mandatory
Settings pathApplication settingsWorkflow SettingsScan ConstraintsMandatory dimensioning
boolean
true
2.5.2 A successful dimensioning is mandatory to save a result
note_only_numbers
Settings pathApplication settingsData SettingsOthersUse numpad for Note entry
boolean
false
2.5.0 Only numbers can be inserted into the Note entry
preview_timeout
Settings pathApplication settingsWorkflow SettingsFlow and InterfacePreview display timeout
string
8000
2.5.0 The preview timeout in milliseconds. Possible values: 5000, 8000, 10000, 20000, 30000
rt_feedback_ar_plane
Settings pathApplication settingsWorkflow SettingsFlow and InterfaceEnable Plane Overlay
boolean
true
2.6.5 Enable the AR plane overlay feedback
rt_feedback_haptic
Settings pathApplication settingsWorkflow SettingsFlow and InterfaceHaptic feedback
boolean
true
2.6.5 Enable the haptic feedback to alert the operator of suboptimal scanning conditions
rt_feedback_profile
Settings pathApplication settingsWorkflow SettingsFlow and InterfaceReal-time feedback
string
1
2.6.5 The real-time feedback profile. Possible values: 1 (Standard), 2 (Warning only) and 0 (Unrestricted)
show_tips
Settings pathApplication settingsWorkflow SettingsFlow and InterfaceDisplay Tips
boolean
true
2.5.0 Display the tips to the user
show_title_bar
Settings pathApplication settingsWorkflow SettingsFlow and InterfaceShow title bar
boolean
true
2.5.0 Display the title bar of the application
size_precision
Settings pathApplication settingsData SettingsDisplayed dimensionsDimension precision
string
1
2.5.0 Dimension precision. Possible values: 10, 1, 0.5, 0.25, 0.1
size_rounding
Settings pathApplication settingsData SettingsDisplayed dimensionsDimension rounding method
string
0
2.5.0 Default rounding method. Possible values: 0 (nearest), 1 (truncate down), 2 (clamp up)
size_unit
Settings pathApplication settingsData SettingsDisplayed dimensionsDefault dimension unit
string
mm
2.5.0 Default dimension unit. Possible values: mm, in, cm
upload_on_barcode
Settings pathApplication settingsWorkflow SettingsFlow and InterfaceUpload on barcode event
boolean
false
2.5.2 Upload the current record immediately after a barcode event
weight_mandatory
Settings pathApplication settingsWorkflow SettingsScan ConstraintsMandatory Weight
boolean
false
2.6.0 Acquiring weight is mandatory to save a result
weight_precision
Settings pathApplication settingsData SettingsDisplayed weightWeight precision
string
1
2.5.0 Weight precision. Possible values: 10, 1, 0.5, 0.25, 0.1
weight_rounding
Settings pathApplication settingsData SettingsDisplayed weightWeight rounding method
string
0
2.5.0 Weight rounding method. Possible values: 0 (nearest), 1 (truncate down), 2 (clamp up)
weight_unit
Settings pathApplication settingsData SettingsDisplayed weightDefault weight unit
string
g
2.5.0 Default weight unit. Possible values: g, Kg, lb, oz
wifi_manual_upload
Settings pathApplication settingsData SettingsHistoryAllow Manual Upload
boolean
false
2.5.0 Allow to manually upload records from Pending/History

Examples

Restore settings to default

Expand
Json Format
{
  "jsonver": 0,
  "restoredefault": true,
  "prefs": []
}
QR code

Size in Inches with 1/4" precision and weight in lb

Expand
Json Format
{
  "jsonver": 0,
  "restoredefault": false,
  "prefs": [
    {
      "key": "size_unit",
      "minver": "2.5.0",
      "type": "string",
      "value": "in"
    },
    {
      "key": "size_precision",
      "minver": "2.5.0",
      "type": "string",
      "value": "0.25"
    },
    {
      "key": "weight_unit",
      "minver": "2.5.0",
      "type": "string",
      "value": "lb"
    }
  ]
}
QR code

Testing server with auto-upload and all images selected

Expand
Json Format
{
  "jsonver": 0,
  "restoredefault": false,
  "prefs": [
    {
      "key": "api_endpoint",
      "minver": "2.5.0",
      "type": "string",
      "value": "https:\/\/testing.qboid.ai:5003\/newEntry"
    },
    {
      "key": "api_upload_img_result",
      "minver": "2.5.0",
      "type": "boolean",
      "value": true
    },
    {
      "key": "api_upload_img_seg",
      "minver": "2.5.0",
      "type": "boolean",
      "value": true
    },
    {
      "key": "api_upload_img_color",
      "minver": "2.5.0",
      "type": "boolean",
      "value": true
    },
    {
      "key": "api_upload_results",
      "minver": "2.5.0",
      "type": "boolean",
      "value": true
    }
  ]
}
QR code

Set custom attributes

Expand
Json Format
{
  "jsonver": 0,
  "restoredefault": false,
  "prefs": [
    {
      "key": "attribute1_key",
      "minver": "2.5.0",
      "type": "string",
      "value": "CASE"
    },
    {
      "key": "attribute1_show_values",
      "minver": "2.5.0",
      "type": "boolean",
      "value": true
    },
    {
      "key": "attribute1_values",
      "minver": "2.5.0",
      "type": "string",
      "value": "CASE,0,EACH,0"
    },
    {
      "key": "attribute2_key",
      "minver": "2.5.0",
      "type": "string",
      "value": "3481"
    },
    {
      "key": "attribute2_values",
      "minver": "2.5.0",
      "type": "string",
      "value": ",0,true,1,false,2"
    },
    {
      "key": "attribute3_key",
      "minver": "2.5.0",
      "type": "string",
      "value": "FRAGILITY"
    },
    {
      "key": "attribute3_show_values",
      "minver": "2.5.0",
      "type": "boolean",
      "value": true
    },
    {
      "key": "attribute3_values",
      "minver": "2.5.0",
      "type": "string",
      "value": "F-1,0,F-2,0,F-3,0"
    }
  ]
}
QR code

Enable 90 days auto-cleanup

Expand
Json Format
{
  "jsonver": 0,
  "restoredefault": false,
  "prefs": [
    {
      "key": "auto_cleanup_days",
      "minver": "2.5.0",
      "type": "numeric",
      "value": 90
    },
    {
      "key": "auto_cleanup_enabled",
      "minver": "2.5.0",
      "type": "boolean",
      "value": true
    }
  ]
}
QR code

Enable BT Scale

Expand
Json Format
{
  "jsonver": 0,
  "restoredefault": false,
  "prefs": [
    {
      "key": "btscale_enabled",
      "minver": "2.5.0",
      "type": "boolean",
      "value": true
    }
  ]
}
QR code

Enable custom field as girth

Expand
Json Format
{
  "jsonver": 0,
  "restoredefault": false,
  "prefs": [
    {
      "key": "custom_field_enabled",
      "minver": "2.5.0",
      "type": "boolean",
      "value": true
    },
    {
      "key": "custom_field_formula",
      "minver": "2.5.0",
      "type": "string",
      "value": "W,H,+,2,*"
    },
    {
      "key": "custom_field_label",
      "minver": "2.5.0",
      "type": "string",
      "value": "GRH"
    }
  ]
}
QR code

Disable QR code cofiguration confirmation

Expand
Json Format
{
  "jsonver": 0,
  "restoredefault": false,
  "prefs": [
    {
      "key": "preferences_import_qr_ask",
      "minver": "2.5.0",
      "type": "boolean",
      "value": false
    }
  ]
}
QR code

Table of content

Common definitions and structures

Term Definitions

Dimensioning Record

WiFi API

API Description

Code examples

Android Intent API

Intent Definition

Configuration Import/Export

Loading Settings/Preferences