IoT: In the Cloud

Enter Your Project for a chance to win an Arduino MKR WAN 1300 Kit with a Compatable Lora Gateway or an MKR 1400 GSM with a Shield Combo + a $100 shopping cart!

Back to The Project14 homepage

Project14 Home
Monthly Themes
Monthly Theme Poll


I have used the myDevices Cayenne IoT platform on a couple of previous projects and I like its capability but I've discovered that for the Sprinkler Control that it has a number of shortcomings.  One of the major differences is that because I am interfacing with the tkinter GUI that I need to use MQTT to control and get status.  On previous projects I used their device "agents" that interfaced directly to the device hardware.  The other major difference is that I am using many more control and display objects ("widgets") than on previous projects.


Here's an example of a dashboard that I had used for an ESP8266:


and one I used for a Raspberry Pi:


When I went to implement the dashboard for the tkinter interface I discovered a few issues:

  1. I can't shrink the widget size down enough to get all the controls to fit nicely on the dashboard (this will be especially bad in the mobile app as it uses the same layout)
  2. I can't customize the widget icons (there is a small selection of icons but you can't create your own image)
  3. There is no "Text" widget.  This is surprising since it has been on their roadmap since 2017.  Disappointing since I won't be able to report status items like current day.


I'm a bit late in the project to switch horses so I'll do the best I can with what's available.

Here's my first cut at a dashboard:


Not too pretty but it should be functional except not sure what to do about current day reporting.


Cayenne and MQTT

MQTT is a lightweight messaging protocol that is used frequently for sensor data.  MQTT uses a broker - client interface to send (publish) and receive (subscribe to) data.  I've tried MQTT before but never used it with a project.  The Cayenne dashboard provides both a broker and client and a Python client is also provided to use on the Raspberry Pi.


The basic syntax to send data from the RPi to the dashboard is v1/username/things/clientID/data/channel where username and clientID are keys provided by the Cayenne interface to identify the specific device (RPiSprinklers) and channel identifies the specific widget on the dashboard.  Data indicates that the device is publishing data for that widget.  The payload is of the form type,unit=value.


The basic syntax to receive data to the RPi (tkinter GUI) from the dashboard is v1/username/things/clientID/cmd/channel.  Here cmd indicates that the device is subscribing to data from the dashboard widget.


Cayenne recommends that you manually test the dashboard MQTT interface before deployinq the client to the device.  One of the recommended clients for testing is MQTT.fx.  I've made a couple of brief videos showing the MQTT client interacting with the dashboard.


Here is data being sent (published) to the dashboard by the MQTT.fx client.  When a "1" is published as the data, the Sunday1 button turns green and resets when a "0" is published.



Here is dashboard data being received (subscribed to) by the MQTT.fx client.  In this case the Cayenne dashboard expects an acknowledgement from the MQTT.fx client so that the button widget will not turn green until the received data is published back to the dashboard client, but you can see that the data was immediately received in the lower right window.  The odd string before the data is a unique sequence identifier that is used to notify the dashboard that the hardware or GUI was actually updated correctly - a valid response would be "ok" or "error" + error message.


The good news is that the Cayenne Python client will handle the handshaking and message formatting for us.


Here is an example of the deployed python code segment to notify the Cayenne dashboard that the Sunday1 button state changed in the GUI.

# Import the MQTT client code
import cayenne.client
import ast

# Provide Cayenne dashboard authentication info

# The callback for when a message is received from Cayenne.
def on_message(message):
  print("message received: " + str(message))
  msg_list = ast.literal_eval(str(message))
  msg_value = msg_list['value']
  msg_channel = msg_list['channel']
  print("value is " + msg_value)
  print("channel is %s" % msg_channel)
  switcher = {
      4: setw1d1Button,
      5: setw1d2Button,
      6: setw1d3Button,
      7: setw1d4Button,
      8: setw1d5Button,
      9: setw1d6Button,
      10: setw1d7Button,
      11: setw2d1Button,
      12: setw2d2Button,
      13: setw2d3Button,
      14: setw2d4Button,
      15: setw2d5Button,
      16: setw2d6Button,
      17: setw2d7Button

  func = switcher.get(int(msg_channel), invalidChannel)
# If there is an error processing the message return an error string, otherwise return nothing.

# Initialize the client
client = cayenne.client.CayenneMQTTClient()
client.on_message = on_message

# Publish the change of state of the Sunday1 button which is on dashboard channel 4
client.virtualWrite(4, state)

# Keepalive for the client connection
def run_client():
    threading.Timer(1.0, run_client).start()



To send data (publish) to the Cayenne dashboard the virtualWrite is used with channel and data arguments.


To receive data (messages are subscribed to).  The messages come in a JSON-like format. This confused me initially because the messages use single quotes(') rather than double quotes(") that are expected with JSON strings.  Here is an example of a message: {'topic': 'cmd', 'value': '30', 'client_id': '12e63e50-5047-11e9-9622-9b9aeccba453', 'msg_id': 'r4ytLEsGrsyDvtu', 'channel': 21}

I used the literal_eval function to extract the channel and value info to use with the GUI.


I made a short video to demonstrate the tracking between the dashboard and the GUI controls.


My next steps are to implement the controller logic and to finish wiring the hardware.


Mobile app

Just a quick note about the mobile app.  Because I can't manipulate the widgets the way that I want, the dashboard will not fit cleanly even on my larger iPad screen.  I've never used the app using MQTT before.  I assumed that the communication between the dashboard and the app would be unchanged.  I noticed that although the widgets are displaying correctly, the app is not updating correctly.  I've decided that I'm not going to try to fix it for now.


Here is what the screen looks like on my iPad.  I can scroll to see all the widgets.

Dashboard in mobile app on iPad