QBOID Confidential and Proprietary Information
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:
The QBOID equipment responsible for generating dimensioning records. It can be either a Perceptor M2 or S1.
The system that receives and processes dimensioning records from the QBOID device.
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..." }
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) |
String fields are escaped as per JSON standard, more specifically:
Input | Output |
---|---|
New Line |
\n |
\ |
\\ |
/ |
\/ |
Carriage return |
\r |
Tab |
\t |
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.
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.
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.
There are two groups of settings that can impact the data transmitted through the WiFi API:
Acquisition
is a collection of parameters that will affect the data acquisition during the dimensioning process. Adjusting these settings will not affect the sequences already collected, unless otherwise specified.WiFi API
is a collection of parameters for configuring the data transmitted over WiFi. Changing these settings will affect the sequences already collected, unless otherwise specified.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
settingsAdded 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 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.
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"}}
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.
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.
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"
self_cert.pem
) to the M2 using the M2's Web Browser app, or from a PC over the M2's USB-C port.Settings -> Security -> Encryption & Credential -> Install from storage
Certificate Name
VPN and apps
is selected as Credential use
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:
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
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:
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.
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`
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
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
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.
QBOID M2 Dimensioning application can be invoked using an explicit intent with the following parameters:
ai.qboid.glapp
ai.qboid.glapp.DimActivity
Optionally, an action
and a JSON configuration
for that action
can be specified in the Extra Bundle
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 |
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.
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 availabilitycallback : 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 occurrednone : 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::
The accepted data types and units for each field are consistent with those defined in the Dimensioning Record. |
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" }
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.
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.
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.
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.
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 -- } }
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
|
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)
, B)
, and C)
/size
when the Tab is resumed and print the result in a <pre>
element.<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
main.html
at the endpoint /
./callback
and stores it locally./size
.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
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')
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 |
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.
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
.
There are two main workflows to import a previously saved configuration into an M2 device:
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.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
preferences.json
or a QR code are marked as compatible if:
preferences.json
file is manually edited incorrectly, the generated QR code might not be properly loadable by the M2 application.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:
true
, all preferences NOT defined in the QR-Code/file will be reset to their default values.false
, preferences not defined in the QR-Code/file will remain unchanged.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.
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:
When exporting an attribute, if any value (key
, type
, or values
) has been changed from the default, the entire configuration of that attribute will be exported, regardless of whether some values are still set to default.
When importing an older QR-Code/file (version <2.6.6
), if any attribute is modified, the other preferences for that attribute that were not explicitly set will be restored to their default values.
Name | Type | Default value | Min version | Description |
---|---|---|---|---|
allow_empty_trash Settings pathApplication settings → Data Settings → History → Allow Empty Trash |
boolean |
false |
2.5.0 |
Allow to empty the trash from records |
api_custom_headers Settings pathApplication settings → Data Settings → WiFi API → Custom request header → Custom 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 settings → Data Settings → WiFi API → API endpoint |
string |
http://192.168.7.100:5000/newEntry |
2.5.0 |
The API endpoint to upload the results |
api_header_authorization Settings pathApplication settings → Data Settings → WiFi API → Custom request header → Authorization header |
string |
2.5.0 |
The Authorization header to use when sending an API request | |
api_header_useragent Settings pathApplication settings → Data Settings → WiFi API → Custom request header → User-Agent header |
string |
2.5.0 |
The User-Agent header to use when sending an API request | |
api_upload_format_jpeg Settings pathApplication settings → Data Settings → WiFi API → Use 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 settings → Data Settings → WiFi API → Upload color image |
boolean |
false |
2.5.0 |
Upload the color image when sending a record via API |
api_upload_img_result Settings pathApplication settings → Data Settings → WiFi API → Upload result image |
boolean |
false |
2.5.0 |
Upload the result image when sending a record via API |
api_upload_img_seg Settings pathApplication settings → Data Settings → WiFi API → Upload segmented image |
boolean |
false |
2.5.0 |
Upload the segmented image when sending a record via API |
api_upload_results Settings pathApplication settings → Data Settings → WiFi API → Realtime WMS upload |
boolean |
false |
2.5.0 |
Realtime WMS upload after a completed dimensioning |
api_upload_timeout Settings pathApplication settings → Data Settings → WiFi API → Upload timeout |
numeric |
5 |
2.5.0 |
The timeout in seconds to be used when sending an API request |
api_upload_unmetered Settings pathApplication settings → Data Settings → WiFi API → Upload on unmetered connections only |
boolean |
false |
2.5.7 |
Upload on unmetered connections only |
attribute1_key Settings pathApplication settings → Data Settings → Configure Attributes → Attribute 1 → Key |
string |
2.5.0 |
The key of the first attribute | |
attribute1_show_values Settings pathApplication settings → Data Settings → Configure Attributes → Attribute 1 → Show Values |
boolean |
false |
2.5.0 |
Show the values of the first attribute |
attribute1_type Settings pathApplication settings → Data Settings → Configure Attributes → Attribute 1 → Type |
string |
softkey |
2.6.6 |
The type of the first attribute. Allowed values: softkey , list , number |
attribute1_values Settings pathApplication settings → Data Settings → Configure Attributes → Attribute 1 → Values |
string |
2.5.0 |
The values of the first attribute | |
attribute2_key Settings pathApplication settings → Data Settings → Configure Attributes → Attribute 2 → Key |
string |
2.5.0 |
The key of the second attribute | |
attribute2_show_values Settings pathApplication settings → Data Settings → Configure Attributes → Attribute 2 → Show Values |
boolean |
false |
2.5.0 |
Show the values of the second attribute |
attribute2_type Settings pathApplication settings → Data Settings → Configure Attributes → Attribute 2 → Type |
string |
softkey |
2.6.6 |
The type of the second attribute. Allowed values: softkey , list , number |
attribute2_values Settings pathApplication settings → Data Settings → Configure Attributes → Attribute 2 → Values |
string |
2.5.0 |
The values of the second attribute | |
attribute3_key Settings pathApplication settings → Data Settings → Configure Attributes → Attribute 3 → Key |
string |
2.5.0 |
The key of the third attribute | |
attribute3_show_values Settings pathApplication settings → Data Settings → Configure Attributes → Attribute 3 → Show Values |
boolean |
false |
2.5.0 |
Show the values of the third attribute |
attribute3_type Settings pathApplication settings → Data Settings → Configure Attributes → Attribute 3 → Type |
string |
softkey |
2.6.6 |
The type of the third attribute. Allowed values: softkey , list , number |
attribute3_values Settings pathApplication settings → Data Settings → Configure Attributes → Attribute 3 → Values |
string |
2.5.0 |
The values of the third attribute | |
attribute4_key Settings pathApplication settings → Data Settings → Configure Attributes → Attribute 4 → Key |
string |
2.5.0 |
The key of the fourth attribute | |
attribute4_show_values Settings pathApplication settings → Data Settings → Configure Attributes → Attribute 4 → Show Values |
boolean |
false |
2.5.0 |
Show the values of the fourth attribute |
attribute4_type Settings pathApplication settings → Data Settings → Configure Attributes → Attribute 4 → Type |
string |
softkey |
2.6.6 |
The type of the fourth attribute. Allowed values: softkey , list , number |
attribute4_values Settings pathApplication settings → Data Settings → Configure Attributes → Attribute 4 → Values |
string |
2.5.0 |
The values of the fourth attribute | |
auto_cleanup_days Settings pathApplication settings → Data Settings → Data cleanup → Automatic data cleanup → `` |
numeric |
30 |
2.5.0 |
Duration to retain data in days |
auto_cleanup_enabled Settings pathApplication settings → Data Settings → Data cleanup → Automatic data cleanup → `` |
boolean |
false |
2.5.0 |
Enable automatic cleanup, to automatically delete sequences older than a specified value |
barcode_mandatory Settings pathApplication settings → Workflow Settings → Scan Constraints → Mandatory Barcode |
boolean |
false |
2.6.0 |
Acquiring barcode is mandatory to save a result |
btscale_enabled Settings pathApplication settings → Workflow Settings → Bluetooth Scale → Settings → Use Bluetooth Scale |
boolean |
false |
2.5.0 |
Use bluetooth scale |
btscale_name_prefix Settings pathApplication settings → Workflow Settings → Bluetooth Scale → Settings → `` |
string |
Adam |
2.5.0 |
The prefix for the name of the bluetooth scale |
color_resolution Settings pathApplication settings → Data Settings → Acquisition → Color Resolution |
string |
2 |
2.5.0 |
The color resolution to use. Possible values: 2 (0.3MP), 3 (13MP) |
compute_alpha_channel Settings pathApplication settings → Data Settings → Acquisition → Compute Alpha Channel |
boolean |
false |
2.5.0 |
Compute the alpha channel on the segmented images |
csv_output_utc Settings pathApplication settings → Data Settings → USB API → CSV Filename UTC |
boolean |
false |
2.5.0 |
UTC time zone is used for the CSV file naming |
custom_field_enabled Settings pathApplication settings → Data Settings → Custom Fields → `` |
boolean |
false |
2.5.0 |
Enable the custom field. If disabled, the Sum is computed |
custom_field_formula Settings pathApplication settings → Data Settings → Custom 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 settings → Data Settings → Custom 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 settings → Workflow Settings → Scan Constraints → Mandatory dimensioning |
boolean |
true |
2.5.2 |
A successful dimensioning is mandatory to save a result |
note_only_numbers Settings pathApplication settings → Data Settings → Others → Use numpad for Note entry |
boolean |
false |
2.5.0 |
Only numbers can be inserted into the Note entry |
preview_timeout Settings pathApplication settings → Workflow Settings → Flow and Interface → Preview 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 settings → Workflow Settings → Flow and Interface → Enable Plane Overlay |
boolean |
true |
2.6.5 |
Enable the AR plane overlay feedback |
rt_feedback_haptic Settings pathApplication settings → Workflow Settings → Flow and Interface → Haptic feedback |
boolean |
true |
2.6.5 |
Enable the haptic feedback to alert the operator of suboptimal scanning conditions |
rt_feedback_profile Settings pathApplication settings → Workflow Settings → Flow and Interface → Real-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 settings → Workflow Settings → Flow and Interface → Display Tips |
boolean |
true |
2.5.0 |
Display the tips to the user |
show_title_bar Settings pathApplication settings → Workflow Settings → Flow and Interface → Show title bar |
boolean |
true |
2.5.0 |
Display the title bar of the application |
size_precision Settings pathApplication settings → Data Settings → Displayed dimensions → Dimension precision |
string |
1 |
2.5.0 |
Dimension precision. Possible values: 10, 1, 0.5, 0.25, 0.1 |
size_rounding Settings pathApplication settings → Data Settings → Displayed dimensions → Dimension rounding method |
string |
0 |
2.5.0 |
Default rounding method. Possible values: 0 (nearest), 1 (truncate down), 2 (clamp up) |
size_unit Settings pathApplication settings → Data Settings → Displayed dimensions → Default dimension unit |
string |
mm |
2.5.0 |
Default dimension unit. Possible values: mm, in, cm |
upload_on_barcode Settings pathApplication settings → Workflow Settings → Flow and Interface → Upload on barcode event |
boolean |
false |
2.5.2 |
Upload the current record immediately after a barcode event |
weight_mandatory Settings pathApplication settings → Workflow Settings → Scan Constraints → Mandatory Weight |
boolean |
false |
2.6.0 |
Acquiring weight is mandatory to save a result |
weight_precision Settings pathApplication settings → Data Settings → Displayed weight → Weight precision |
string |
1 |
2.5.0 |
Weight precision. Possible values: 10, 1, 0.5, 0.25, 0.1 |
weight_rounding Settings pathApplication settings → Data Settings → Displayed weight → Weight rounding method |
string |
0 |
2.5.0 |
Weight rounding method. Possible values: 0 (nearest), 1 (truncate down), 2 (clamp up) |
weight_unit Settings pathApplication settings → Data Settings → Displayed weight → Default weight unit |
string |
g |
2.5.0 |
Default weight unit. Possible values: g, Kg, lb, oz |
wifi_manual_upload Settings pathApplication settings → Data Settings → History → Allow Manual Upload |
boolean |
false |
2.5.0 |
Allow to manually upload records from Pending/History |
{ "jsonver": 0, "restoredefault": true, "prefs": [] }
{ "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" } ] }
{ "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 } ] }
{ "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" } ] }
{ "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 } ] }
{ "jsonver": 0, "restoredefault": false, "prefs": [ { "key": "btscale_enabled", "minver": "2.5.0", "type": "boolean", "value": true } ] }
{ "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" } ] }
{ "jsonver": 0, "restoredefault": false, "prefs": [ { "key": "preferences_import_qr_ask", "minver": "2.5.0", "type": "boolean", "value": false } ] }