More MQTT Configurable Parameters via App Inventor
My last post on this subject introduced a few configurable setting to an MQTT App Inventor project. It was a no frills, bare bones version. But a few essential parameters were left out. This article fills in the some of the gaps, including connection authentication, websocket port assignment, timeout, last will and testimonial, and the clientID. With this update, the following MQTT settings become configurable:
- MQTT Broker domain name
- Request Topic
- Reply Topic
- User Name (Leave Blank if not used)
- Password (Leave Blank if not used)
- MQTT Websocket Port
- Client ID
- Keep Alive Timeout (seconds)
- Last Will Topic
- Last Will Message
- Last Will QoS
NOTE: If you want to skip the implementation details presented below and simply use this project now, it is available on GitHub here.
A New Client Library
My initial App Inventor MQTT project used the Mosquitto client library. While that was great for developing the “proof-of-concept”, demonstrating that you could indeed link an App Inventor project to an MQTT broker, some serious shortcomings soon became evident.
When attempting to refine the project to support broker connections with authentication, I discovered that the Mosquitto library did not support this basic feature. So a new library was needed. Fortunately, an existing open-source client library is available with full support for password-enabled logins.
The library is called Eclipse Paho. And just as with my initial application, the API provides a JavaScript interface. This interface supports all the features identified above, configurable through App Inventor.
Expanding The Configurable Parameters
Here is how the expanded App Inventor configuration screen looks. When user name/password credentials are not used, the fields should remain blank. Just as before, the configuration settings are stored in an Android device file. Upon start-up, the system initializes with the values stored in that file (cfg.txt).
A last will message has been added to the set of App Inventor configurable parameters. As per the MQTT specification, the last will message is sent when the MQTT connection is closing.
Test Case
The HiveMQ broker “broker.mqttdashboard.com” was used to demonstrate the capabilities of this project. And the on-line client used is available at: http://www.hivemq.com/demos/websocket-client/
A simple test: Open a browser to the MQTT client: http://www.hivemq.com/demos/websocket-client/
Enter the Host broker as shown below and click “Connect”.
Subscribe to the App Inventor MQTT Request and Last Will topics:
Request Topic: mqtt_request
Last Will Topic: lwt
Open the updated MQTT App on your Android device. Click on the “Configure MQTT” icon.
Edit or take note of the last will topic and message. Close the App Inventor app and verify the HiveMQ client displays the last will message.
Note: Use a broker that supports username/password logins to verify that new configurable parameter pair. Refer to this post if you would like to setup your own MQTT broker with login authentication enabled.
JavaScript Updates
The App Inventor WebViewString is used to communicate between the application and the Paho MQTT JavaScript library. Here are the highlights of the revised JavaScript interface.
Paho MQTT API Fork
I started this project update with paho-javascript version 1.0.2. After debugging the changes using a web browser, the code was moved to the target Android device. Unfortunately, after much troubleshooting, an unsupported AppInventor “WebViewer” component feature was identified. The required feature for the Paho library is called “localStorage”.
Fortunately, there is an alternative to localStorage. I have modified the Paho library to use Cookies instead of localStorage. With this change, the Paho library/App Inventor communication has been verified to function properly.
Three sections of the library had to be modified:
- Verify localStorage is supported (Comment out this check)
- // Check dependencies are satisfied in this browser.
- if (!("WebSocket" in global && global["WebSocket"] !== null)) {
- throw new Error(format(ERROR.UNSUPPORTED, ["WebSocket"]));
- }
- //cookies used since localstorage not supported with appinventor
- /*
- if (!("localStorage" in global && global["localStorage"] !== null)) {
- throw new Error(format(ERROR.UNSUPPORTED, ["localStorage"]));
- }
- */
2. Replace localStorage with cookies
- //localStorage.setItem(prefix+this._localKey+wireMessage.messageIdentifier, JSON.stringify(storedMessage));
- setCookie(prefix+this._localKey+wireMessage.messageIdentifier, JSON.stringify(storedMessage), 1);
- };
- ClientImpl.prototype.restore = function(key) {
- //var value = localStorage.getItem(key);
- var value = getCookie(key);
3. Add Get/Set cookies functions
- function setCookie(cname, cvalue, exdays) {
- var d = new Date();
- d.setTime(d.getTime() + (exdays*24*60*60*1000));
- var expires = "expires="+d.toUTCString();
- document.cookie = cname + "=" + cvalue + "; " + expires;
- }
- function getCookie(cname) {
- var name = cname + "=";
- var ca = document.cookie.split(';');
- for(var i = 0; i < ca.length; i++) {
- var c = ca[i];
- while (c.charAt(0) == ' ') {
- c = c.substring(1);
- }
- if (c.indexOf(name) == 0) {
- return c.substring(name.length, c.length);
- }
- }
- return "";
- }
The AppInventor to PAHO MQTT API JavaScript
The MQTT broker is no longer automatically loaded after the webview page is loaded by the App Inventor application:
- //executes once after window is loaded --------------------------------------------->
- function windowloaded() {
- //client.connect(connectOptions); // Connect to MQTT broker
- AppInventorServer(); // Start polling WebViewString
- }
- window.onload = windowloaded; // Launch windowloaded()
The JavaScript must now receive a command to connect to the configured MQTT broker. The command received is called CFG, which loads the current connection settings stored in a file on the android device. The ensures the most current configuration is used for the connection.
There are 4 commands the JavaScript recognizes from the App Inventor App via the WebViewString.
- GET: Used to send an MQTT request message from the AppInventor App
- CFG: Update configurable parameters, disconnect and reconnect to MQTT broker
- CNX: Connect to MQTT broker
- KILLCNX: Disconnect from MQTT broker
- //GET: Send MQTT message -------------------------------------------------------------
- //------------------------------------------------------------------------------------
- if(request.substring(0, 4)=="GET:") { // Validate request
- window.AppInventor.setWebViewString(""); // Reset String (process once)
- $("#request").val(request.substring(4, request.length)); //set request html textbox
- SendMqttRequest(); // Send Mqtt Request
- }
- //CFG: Update configurable files in Android Device File ------------------------------
- //------------------------------------------------------------------------------------
- if(request.substring(0, 4) == "CFG:") { // Validate request
- window.AppInventor.setWebViewString(""); // Reset String (process once)
- if(typeof(client) !== 'undefined') { // Disconnect if connected
- client.disconnect();
- }
- var cfgpar = JSON.parse(request.substring(4, request.length));
- txtopic = cfgpar.mqtt_txtopic;
- rxtopic = cfgpar.mqtt_rxtopic;
- mqtt_url = cfgpar.mqtt_broker;
- mqtt_port = Number(cfgpar.mqtt_port);
- mqtt_clientId = cfgpar.mqtt_clientId;
- mqtt_keepalive = cfgpar.mqtt_keepalive;
- mqtt_lastwilltopic = cfgpar.mqtt_lastWillTopic;
- mqtt_lastwillmessage = cfgpar.mqtt_lastWillMessage;
- mqtt_lastwillqos = cfgpar.mqtt_lastWillQoS;
- connectOptions.userName = cfgpar.mqtt_un;
- connectOptions.password = cfgpar.mqtt_pw;
- connectOptions.keepAliveInterval= Number(mqtt_keepalive);
- // Create a Last-Will-and-Testament
- var lwt = new Paho.MQTT.Message(mqtt_lastwillmessage);
- lwt.destinationName = mqtt_lastwilltopic;
- lwt.qos = Number(mqtt_lastwillqos);
- lwt.retained = false;
- connectOptions.willMessage = lwt;
- client = new Paho.MQTT.Client(mqtt_url, mqtt_port, mqtt_clientId);
- client.onConnectionLost = onConnectionLost;
- client.onMessageArrived = onMessageArrived;
- client.connect(connectOptions); // Connect to MQTT broker
- }
- //CNX: Connect to MQTT broker --------------------------------------------------------
- //------------------------------------------------------------------------------------
- if(request.substring(0, 4)=="CNX:") { // Validate request
- window.AppInventor.setWebViewString(""); // Reset String (process once)
- if(typeof(client) == 'undefined') {
- // Create MQTT client instance -------------------------------------------------->
- client = new Paho.MQTT.Client(mqtt_url, mqtt_port, mqtt_clientId);
- // set callback handlers
- client.onConnectionLost = onConnectionLost;
- client.onMessageArrived = onMessageArrived;
- }
- client.connect(connectOptions); // Connect to MQTT broker
- }
- //KILLCNX: Diconnect from MQTT broker ------------------------------------------------
- //------------------------------------------------------------------------------------
- if(request.substring(0, 7)=="KILLCNX") { // Validate request
- window.AppInventor.setWebViewString(""); // Reset String (process once)
- conn_kill = "Y";
- client.disconnect();
- }
- hAppInvSvr = setTimeout(AppInventorServer, 100); // run AppInventorServer() in 100 ms
The callbacks executed upon MQTT broker connection, disconnection, and receipt of subscribed message can easily be understood through a review of the JavaScript file (mqtt_appinventor_paho.html).
App Inventor Updates
Changes to the App Inventor code simply expand the scope of the configurable parameters. Again, a review of the code should make these changes self-evident.
Here is the code
The AppInventor and JavaScript code is available on GitHub here. Installation instructions are included in the readme.md file.
In Conclusion
There you have it. It is with great pleasure to present this update which supports username/password MQTT connections using the AppInventor. I hope you find this information useful.