I've been attempting to establish two-way communications between two manually configured devices and bumped into a couple of issues on the way. The behaviour of the synced variables is not as I expected.
I put together a minimal, one-way test case to illustrate the issue as follows. There are two manually configured Things; one called Producer and the other Consumer, containing integers int_producer
and int_consumer
respectively. The integers are defined as Read/Write and update on change. They are also synced together. Producer Thing is controlled by a Python script called producer.py
and Consumer Thing by consumer.py
.
Producer.py
increments the value of int_producer
every 5 seconds. Consumer.py
simply logs any changes to int_consumer
to the console.
Both variables are displayed as read/write values on a dashboard.
Both scripts are running on the same Raspberry Pi Zero 2W running Python 3.9.2.
I start by running producer.py
and int_producer
increments as expected and can be observed in the console and in the dashboard.
I then try to run consumer.py
and I get inconsistent behaviour with two possible outcomes.
Fourteen runs out of 20, consumer.py
issues an error message in response to a change of value and has to reconnect. Something was expecting an int but got a float. The situation doesn't improve no matter how long I leave the script running. Consumer.py
console output is shown below.
Curiously, when failing if int_consumer
is display, it's as an integer. See "int_consumer = 8".
pi@RPZ2-001:~/shared/python_dev/arduino_manual $ python consumer.py
21:56:04.364 task: int_consumer created.
21:56:04.365 task: conn_task created.
21:56:04.366 Connecting to Arduino IoT cloud...
21:56:05.889 task: discovery created.
21:56:05.890 task: mqtt_task created.
21:56:05.891 Subscribe: b'/a/d/01f3a0fd-8847-4693-96ac-92c428fb2552/e/i'.
21:56:06.197 task: conn_task complete.
21:56:06.702 Subscribe: b'/a/t/54689861-5cc7-4b5d-9693-261b8787da19/e/i'.
21:56:06.938 Subscribe: b'/a/t/54689861-5cc7-4b5d-9693-261b8787da19/shadow/i'.
21:56:07.424 Device configured via discovery protocol.
21:56:07.526 task: discovery complete.
21:56:09.477 int_consumer = 8
21:56:10.864 task: mqtt_task raised exception: int_consumer set to invalid data type, expected: <class 'int'> got: <class 'float'>.
21:56:10.865 task: conn_task created.
21:56:10.866 Connecting to Arduino IoT cloud...
21:56:11.947 Subscribe: b'/a/t/54689861-5cc7-4b5d-9693-261b8787da19/e/i'.
21:56:12.144 task: mqtt_task created.
21:56:12.195 task: conn_task complete.
21:56:15.877 task: mqtt_task raised exception: int_consumer set to invalid data type, expected: <class 'int'> got: <class 'float'>.
21:56:15.877 task: conn_task created.
21:56:15.878 Connecting to Arduino IoT cloud...
21:56:16.949 Subscribe: b'/a/t/54689861-5cc7-4b5d-9693-261b8787da19/e/i'.
21:56:17.156 task: mqtt_task created.
21:56:17.208 task: conn_task complete.
21:56:20.929 task: mqtt_task raised exception: int_consumer set to invalid data type, expected: <class 'int'> got: <class 'float'>.
21:56:20.930 task: conn_task created.
21:56:20.931 Connecting to Arduino IoT cloud...
21:56:21.914 Subscribe: b'/a/t/54689861-5cc7-4b5d-9693-261b8787da19/e/i'.
21:56:22.090 task: mqtt_task created.
21:56:22.141 task: conn_task complete.
21:56:26.013 task: mqtt_task raised exception: int_consumer set to invalid data type, expected: <class 'int'> got: <class 'float'>.
21:56:26.014 task: conn_task created.
Six runs out of 20, consumer.py
behaves more as expected and logs changes to int_consumer
to the console, but it has changed to a float.
pi@RPZ2-001:~/shared/python_dev/arduino_manual $ python consumer.py
18:44:35.626 task: int_consumer created.
18:44:35.627 task: conn_task created.
18:44:35.628 Connecting to Arduino IoT cloud...
18:44:36.692 task: discovery created.
18:44:36.693 task: mqtt_task created.
18:44:36.695 Subscribe: b'/a/d/01f3a0fd-8847-4693-96ac-92c428fb2552/e/i'.
18:44:37.026 task: conn_task complete.
18:44:38.146 Subscribe: b'/a/t/54689861-5cc7-4b5d-9693-261b8787da19/e/i'.
18:44:38.365 Subscribe: b'/a/t/54689861-5cc7-4b5d-9693-261b8787da19/shadow/i'.
18:44:38.798 Device configured via discovery protocol.
18:44:38.900 task: discovery complete.
18:44:40.036 int_consumer = 65.0
18:44:40.263 Ignoring cloud initialization for record: int_consumer
18:44:45.078 int_consumer = 66.0
18:44:50.108 int_consumer = 67.0
18:44:55.151 int_consumer = 68.0
18:45:00.180 int_consumer = 69.0
With producer.py running, the values of int_producer
appears in its widget, but the int_consumer
only updates when I hit the refresh button on the browser.
With producer.py
not running, values entered into the int_producer
widget have no effect. However, values entered into the int_consumer
widget get logged by consumer.py
as a float. Further, if I enter a floating point number, say 123.456, into the int_consumer
widget, all the fractional digits get logged but not displayed in the widget.
Questions:
Why is int_consumer
changing to a float?
Why does a change to float sometimes cause the client to crash and other time not?
Why don't the dashboard value widgets track the changes together?
Why aren't manual changes to int_producer
seen by consumer.py
when changes typed into int_consumer
are?
Why are floating point values accepted by the value widget and passed to consumer.py
when the variable is an integer?
The code for the scripts is below should anyone want to chip in.
producer.py
"""
A simple producer.
Sends an incrementing integer to an AIoT Cloud variable, int_producer.
"""
import time
import logging
import threading
import sys
sys.path.append("lib")
from arduino_iot_cloud import ArduinoCloudClient
DEVICE_ID = b"PRODUCR_DEVICE_ID"
SECRET_KEY = b"PRODUCER_SECRET_KEY"
def logging_func():
logging.basicConfig(
datefmt="%H:%M:%S",
format="%(asctime)s.%(msecs)03d %(message)s",
level=logging.INFO,
)
def int_producer_changed(client, value):
""" This function is executed each time the cloud variable "int_producer" changes """
int1 = value
logging.info("int_producer = {}".format(value))
def client_thread_func(client):
""" This function defines the client thread functionality """
client.start()
if __name__ == "__main__":
# Set up stuff here
logging_func()
client = ArduinoCloudClient(device_id=DEVICE_ID, username=DEVICE_ID, password=SECRET_KEY)
client.register("int_producer", value=None, on_write=int_producer_changed)
client_thread = threading.Thread(target=client_thread_func, args=(client,), daemon=True)
client_thread.start()
number = 0
while True:
if client.started:
number += 1
client["int_producer"] = number
logging.info("int_producer set to {}".format(number))
time.sleep(5)
consumer.py
"""
A simple consumer.
Receives updates to a single AIoT Cloud variable, int_consumer.
"""
import time
import logging
import sys
sys.path.append("lib")
from arduino_iot_cloud import ArduinoCloudClient
DEVICE_ID = b"CONSUMER_DEVICE_ID"
SECRET_KEY = b"CONSUMER_SECRET_KEY"
def logging_func():
logging.basicConfig(
datefmt="%H:%M:%S",
format="%(asctime)s.%(msecs)03d %(message)s",
level=logging.INFO,
)
def int_consumer_changed(client, value):
""" This function is executed each time the cloud variable "int_consumer" changes """
logging.info("int_consumer = {}".format(value))
logging_func()
client = ArduinoCloudClient(device_id=DEVICE_ID, username=DEVICE_ID, password=SECRET_KEY)
client.register("int_consumer", value=None, on_write=int_consumer_changed)
client.start()