Jason O
Jason O

Reputation: 833

Problem with RPC calls from Thingsboard to Arduino (ESP32)

I created a simple Arduino program that receives a position value from a KnobControl Widget on my thingsboard dashboard and updates a servo position. The program is based on the ESP32 Pico Kit GPIO control and DHT22 sensor monitor example from the ThingsBoard website and is mostly working.

So far, my code is able to connect to the dashboard and receive "setPos" and "getPos" RPC commands from the server, and so far, it is sucessfully running the associated RPC_Response function for the "setPos" call, and can move the servo.

But, when I refresh the dashboard and it sends out a "getPos" call to the controller to get the current servo value, I get an SDK message in the serial output indicating that the controller received the command, but the associated RPC_Response function is never called. I'm not sure what I'm missing but here is the complete code example I wrote so far:

#include <WiFi.h>
#include <ESP32Servo.h>
#include <ThingsBoard.h>

// Constants
#define SERVO_PIN               19      // Servo Output Pin
#define SERVO_UPDATE_INTERVAL   20      // Speed of servo position updates

// Helper macro to calculate array size
#define COUNT_OF(x) ((sizeof(x)/sizeof(0[x])) / ((size_t)(!(sizeof(x) % sizeof(0[x])))))

// WiFi Login Info
#define ssid                "IoT"
#define password            "password"

// MQTT Broker IP address:
#define THINGSBOARD_SERVER  "10.10.0.30"

// MQTT Client Info
#define ACCESS_TOKEN "ESP32_DEMO_TOKEN"

// Servo Variables
int minUs = 500;
int maxUs = 2400;
int SetPosition = 0; // ServoMotor Position Setpoint
int Position = 0;    // ServoMotor current position

// Control/Timing Variables
long lastServoTime = 0;       // keeps track of timestamp since the last servo update occured

// Objects
Servo ServoMotor;
WiFiClient espClient;
ThingsBoard client(espClient);

// RPC Callbacks
RPC_Callback callbacks[] = {
  { "setPos", setPosition },
  { "getPos", getPosition },
};

void setup() {
  Serial.begin(115200);

  // Initialize Servo
  ServoMotor.setPeriodHertz(50);                  // Standard 50hz servo
  ServoMotor.attach(SERVO_PIN, minUs, maxUs);

  // Initialize the WiFi and MQTT connections
  setup_wifi();
}

void loop() {
  // Update/refresh the Wifi/MQTT connection
  updateWirelessConnection();

  // Update Servo Positions
  updateServo();
}

void updateWirelessConnection()
{
  if (!client.connected()) {
    reconnect();
  }

  client.loop();
}

// Processes function for RPC call "getPos"
// RPC_Data is a JSON variant, that can be queried using operator[]
// See https://arduinojson.org/v5/api/jsonvariant/subscript/ for more details
RPC_Response getPosition(const RPC_Data &data)
{
  Serial.println("Received the get Position Method");
  return RPC_Response(NULL, SetPosition);
}

// Processes function for RPC call "setPos"
// RPC_Data is a JSON variant, that can be queried using operator[]
// See https://arduinojson.org/v5/api/jsonvariant/subscript/ for more details
RPC_Response setPosition(const RPC_Data &data)
{
  Serial.print("Received the Set Position method: ");
  SetPosition = data;
  Serial.println(SetPosition);
  return RPC_Response(NULL, SetPosition);
}

void updateServo()
{
  long currentTime = millis();

  if (currentTime - lastServoTime > SERVO_UPDATE_INTERVAL) {
    lastServoTime = currentTime;

    // Approach the Horizontal set point incrementally and update the servo if applicable
    if (Position != SetPosition) {
      Position = SetPosition;
      ServoMotor.write(Position);
    }
  }
}

void setup_wifi() {
  delay(10);

  // We start by connecting to a WiFi network
  Serial.println();
  Serial.print("Connecting to ");
  Serial.println(ssid);

  WiFi.begin(ssid, password);

  while (WiFi.status() != WL_CONNECTED) {
    delay(500);
    Serial.print(".");
  }

  Serial.println("");
  Serial.println("WiFi connected");
  Serial.println("IP address: ");
  Serial.println(WiFi.localIP());
}

void reconnect() {
  // Loop until we're reconnected
  while (!client.connected())
  {
    Serial.print("Attempting MQTT connection...");

    // Attempt to connect
    if (client.connect(THINGSBOARD_SERVER, ACCESS_TOKEN)) {
      Serial.println("connected");

      // Perform a subscription. All consequent data processing will happen in
      // callbacks as denoted by callbacks[] array.
      if (!client.RPC_Subscribe(callbacks, COUNT_OF(callbacks))) {
        Serial.println("Failed to subscribe for RPC");
        return;
      }

      Serial.println("Subscribe done");
    } else {
      Serial.println("Failed to connect. Trying again in 5 seconds...");
      // Wait 5 seconds before retrying
      delay(5000);
    }
  }
}

Also, here's the response I see in the Serial Monitor when I refresh the dashboard:

{"method":"getPos"}
[SDK] received RPC getPos
[SDK] response {}

When I update the position of the knob control on the dashboard, here is what I receive in the Serial Monitor:

{"method":"setPos","params":"135"}
[SDK] received RPC setPos
[SDK] calling RPC setPos
Received the Set Position method: 135
[SDK] response 135

NOTE: I'm not having any problems with the setPos call, this calls the RPC function properly.

As one final note, when I refresh the dashboard, an error message that says, "Unable to parse response: [object Object]" appears on the top of the knob control widget.

So the main issue is that the right RPC function is not being called. What do you think the problem is here?

Upvotes: 0

Views: 4127

Answers (2)

Elmo
Elmo

Reputation: 1

I tried to address the issue in another post, not realizing you have already posted this. The response was not helpful. The link to my query is below.

https://github.com/thingsboard/ThingsBoard-Arduino-MQTT-SDK/issues/10#issuecomment-474368259

I also noticed the thingsboard.h wrapper essentially short circuits the sketch when no params key is issued from the knob control. Since the knob control does not issue any "params" key when it executes the getPos method, your sketch code never gets the chance to respond with the latest value.

I played with the New debug terminal widget and was able to test the knob control widget RPC calls by issuing the getPos method with a params value. If you enter just 'getPos' at the New debug terminal prompt, you get empty brackets as a response in the debug terminal and {"method":"getPos"} in the serial output. Just like the sketch.

If you enter 'getPos 1' at the prompt, you should get the SetPosition value as a response in the terminal window and '{"method":"getValue","params":1}' in the serial output. The value 1 does not matter just that there is some kind of parameter to trigger the wrapper.

Note that if you enter 'setPost 12' in the debug window, you will update the SetPosition variable value to 12.

My conclusion is the knob control widget is crippled. It needs to issue a 'params' key pair if it is to work with the wrapper.

Another issue, the numeric value displayed in the knob control does not update when you get the getPos to work in the debug terminal. Not sure if this is the best test. Refreshng the browser window with the dashboard yields similar result. I would think the getMethod should do this.

Thingsboard team: what is going on here and can it be improved?

Upvotes: 0

Jason O
Jason O

Reputation: 833

OK, I poked around inside the ThingsBoard wrapper library to try and better understand what is happening with the code that processes incoming RPC text strings from the server. Looking inside the sendDataArray function, I found this curious piece of code inside the for loop that scans the callbacks array and matches it with the incomming RPC string:

// Do not inform client, if parameter field is missing for some reason
if (!data.containsKey("params")) {
    continue;
}

If an RPC method is called which does not contain a params field, then the method call is ignored completely. Unfortinately, this is the case with the getPos RPC call. So to fix the problem, I simply commented out the above code and now it all works.

@thingsboard team, what was the original rationale for this code? How are the getValue RPC calls supposed to get communicated to the client for processing?

Upvotes: 0

Related Questions