eclipse-hono / hono

Eclipse Hono™ Project
https://eclipse.dev/hono
Eclipse Public License 2.0
452 stars 137 forks source link

Command and Control issue #2257

Closed roullli closed 3 years ago

roullli commented 4 years ago

Hi,

I am trying to control a rover by Hono's Command and Control functionality. I have created a Python code which subscribes to command/+/+/req/# topic successfully import tkinter import paho.mqtt.client as mqtt

port = 1883
host = "192.168.1.152" # please change the IP address based on the address of Hono instance.
username = "rover2@rover"
password = "rover-secret"
speed = 380
client = mqtt.Client(client_id="python_client", clean_session=True, userdata=None, protocol=4, transport="tcp")
topic_to_publish = "rover/2/RoverDriving/command"
topic_to_subscribe = "command/+/+/req/#"

# The callback for when the client receives a CONNACK response from the server.
def on_connect(client, userdata, flags, rc):
    print("Connected with result code "+ str(rc))
    # added only to test.
    client.subscribe(topic_to_subscribe)
    print("Subscribed to topic "+ str(topic_to_subscribe))

# The callback for when a PUBLISH message is received from the server.
def on_message(client, userdata, msg):
    print(msg.topic+" "+ str(msg.payload))

def connect():
    client.reinitialise(client_id="python_client",clean_session=True, userdata=None)
    client.username_pw_set(username,password)
    client.on_connect = on_connect
    client.on_message = on_message
    client.on_subscribe = on_subscribe
    client.connect(host, port, 60)
    client.loop_start()
    print("Trying to connect to the broker.")

def disconnect():
    client.disconnect()
    client.loop_stop()
    print("Disconnected from Broker")

def on_subscribe(mqttc, obj, mid, granted_qos):
    print("Subscribed: " + str(mid) + " " + str(granted_qos))

def turn_right():
    client.publish(topic_to_publish,"{\"mode\":0,\"command\":\"E\",\"speed\":"+ str(speed_var.get() + 360) +" }", 0 ,False)
    print("turn right clicked")

def turn_right_back():
    client.publish(topic_to_publish,"{\"mode\":0,\"command\":\"D\",\"speed\": "+ str(speed_var.get() + 360) +" }", 0 ,False)
    print("turn right back clicked")

def turn_left():
   client.publish(topic_to_publish,"{\"mode\":0,\"command\":\"Q\",\"speed\": "+ str(speed_var.get() + 360) +" }", 0 ,False)
   print("turn left clicked")

def turn_left_back():
   client.publish(topic_to_publish,"{\"mode\":0,\"command\":\"A\",\"speed\": "+ str(speed_var.get() + 360) +" }", 0 ,False)
   print("turn left back clicked")

def move_forward():
   client.publish(topic_to_publish,"{\"mode\":0,\"command\":\"W\",\"speed\": "+ str(speed_var.get() + 360) +" }", 0 ,False)
   print("move forward clicked")

def move_back():
   client.publish(topic_to_publish,"{\"mode\":0,\"command\":\"S\",\"speed\": "+ str(speed_var.get() + 360) +" }", 0 ,False)
   print("move back clicked")

def spot_right():
   client.publish(topic_to_publish,"{\"mode\":0,\"command\":\"K\",\"speed\": "+ str(speed_var.get() + 360) +" }", 0 ,False)
   print("move spot right clicked")

def spot_left():
   client.publish(topic_to_publish,"{\"mode\":0,\"command\":\"J\",\"speed\": "+ str(speed_var.get() + 360) +" }", 0 ,False)
   print("move spot left clicked")

def stop_move():
   client.publish(topic_to_publish,"{\"mode\":0,\"command\":\"F\",\"speed\": "+ str(speed_var.get() + 360) +" }", 0 ,False)
   print("Stop clicked")

def set_speed(event):
   speed = speed_var.get() + 360
   print("set speed to %d", speed)

# UI widgets definition below.
if __name__ == "__main__":
   parent = tkinter.Tk()
   #variable for speed setting
   speed_var = tkinter.DoubleVar()
   parent.title("Rover MQTT mocker")

   B_connect = tkinter.Button(parent, text ="Connect" , command= connect)
   B_disconnect = tkinter.Button(parent, text ="Disconnect" , command= disconnect)

   B_connect.place(x = 100 , y = 30 , height = 25 , width = 75)
   B_disconnect.place(x = 100 , y = 60 , height = 25 , width = 75)

   parent.mainloop()

and I tested it with Hono Command Line Client, which also was successful. java -jar hono-cli-1.3.0-exec.jar --hono.client.host=192.168.1.154 --hono.client.port=15672 --hono.client.username=consumer@HONO --hono.client.password=verysecret --tenant.id=rover --device.id=rover2 --spring.profiles.active=command

But know I am trying to send commands through another python code which uses Proton library to directly send commands through AMQP broker to the device. it seems it is not able to create the link or deliver the message. here is the code :

import tkinter
import proton
from cli_proton_python import sender
import ssl
import uuid
from proton._reactor import _generate_uuid

device = "rover2"
tenant = "rover"
hostAndPort = "192.168.1.150:5672"
username = "consumer@HONO"
password = "verysecret"
speed = 380
topic_to_publish = "command/"+tenant+"/"+device

def sendControlMsg( msg ):
    print ("sending control msg " + str(msg))
    parser = sender.options.SenderOptions()
    opts, _= parser.parse_args()
    opts.broker_url = username + ":" + password +"@"+ hostAndPort +"/"+ topic_to_publish
    opts.msg_id = "0"
    opts.msg_subject = "RoverDriving"
    opts.msg_content = str(msg)
    opts.msg_reply_to = topic_to_publish+"/"+str(_generate_uuid())
    opts.msg_correlation_id = "null"
    opts.msg_content_type = "application/json"
    opts.log_lib = "TRANSPORT_DRV"
    opts.log_msgs = 'dict'

    container = proton.reactor.Container(sender.Send(opts))
    container.run()

def turn_right():
    sendControlMsg( "{\"command\":\"E\",\"speed\":"+ str(speed_var.get() + 360) +" }" )
    print("turn right clicked")

def turn_right_back():
    sendControlMsg( "{\"command\":\"D\",\"speed\": "+ str(speed_var.get() + 360) +" }" )
    print("turn right back clicked")

def turn_left():
   sendControlMsg("{\"command\":\"Q\",\"speed\": "+ str(speed_var.get() + 360) +" }")
   print("turn left clicked")

def turn_left_back():
   sendControlMsg("{\"command\":\"A\",\"speed\": "+ str(speed_var.get() + 360) +" }")
   print("turn left back clicked")

def move_forward():
   sendControlMsg("{\"command\":\"W\",\"speed\": "+ str(speed_var.get() + 360) +" }")
   print("move forward clicked")

def move_back():
   sendControlMsg("{\"command\":\"S\",\"speed\": "+ str(speed_var.get() + 360) +" }")
   print("move back clicked")

def spot_right():
   sendControlMsg("{\"command\":\"K\",\"speed\": "+ str(speed_var.get() + 360) +" }")
   print("move spot right clicked")

def spot_left():
   sendControlMsg("{\"command\":\"J\",\"speed\": "+ str(speed_var.get() + 360) +" }")
   print("move spot left clicked")

def stop_move():
   sendControlMsg("{\"command\":\"F\",\"speed\": "+ str(speed_var.get() + 360) +" }")
   print("Stop clicked")

def set_speed(event):
   speed = speed_var.get() + 360
   print("set speed to", speed)

if __name__ == "__main__":
   parent = tkinter.Tk()
   speed_var = tkinter.DoubleVar()
   parent.title("Rover Controller")

   B_right = tkinter.Button(parent, text =">" , command= turn_right)
   B_right.place(x = 65, y= 25, height=25, width=25)

   B_right_back = tkinter.Button(parent, text =">" , command= turn_right_back)
   B_right_back.place(x = 65, y= 55, height=25, width=25)

   B_left  = tkinter.Button(parent, text ="<", command= turn_left)
   B_left.place(x= 15,y = 25, height=25, width=25)

   B_left_back  = tkinter.Button(parent, text ="<", command= turn_left_back)
   B_left_back.place(x= 15,y = 55, height=25, width=25)

   B_up = tkinter.Button(parent, text ="^", command= move_forward)
   B_up.place(x= 40, y = 10 ,  height=25, width=25)

   B_down = tkinter.Button(parent, text ="v", command= move_back)
   B_down.place(x= 40, y = 70, height=25, width=25)

   B_stop = tkinter.Button(parent, text ="Stop", command= stop_move)
   B_stop.place(x = 10,y = 160 , height = 30, width=85)

   B_spot_left = tkinter.Button(parent, text ="<)", command= spot_left)
   B_spot_left.place(x = 100,y = 160 , height = 30, width=35)

   B_spot_right = tkinter.Button(parent, text =">(", command= spot_right)
   B_spot_right.place(x = 140,y = 160 , height = 30, width=35)

   speed_label = tkinter.Label(parent , text = "Speed")
   speed_label.place(x= 10, y = 105, height=50, width=50)

   speed_scale = tkinter.Scale(parent, variable = speed_var,orient = tkinter.HORIZONTAL)
   speed_scale.bind("<ButtonRelease-1>", set_speed)
   speed_scale.place(x= 60, y = 105, height=50, width=85)

   parent.mainloop()

First of all, am I doing it in the right way? is it possible to send commands directly through the AMQP adapter or it should be going only through the dispatch router? If the answer is yes? what is wrong with my code? do I need any kind of certificate?

Thanks in advance.

sophokles73 commented 4 years ago

You need to connect to Hono's north bound Command & Control API. If you have installed Hono using the Helm chart, then the north bound C&C API is exposed by the Dispatch Router service. You cannot send a command to a device by means of any of the protocol adapters.

roullli commented 4 years ago

Thanks for your reply. is there any example Python code (or anything else) for sending commands? I want to see whats the problem with my code. since it connects to the AMQP network but the MQTT client can not receive the command. I used command/rover address to send command for my device(rover2) in tenant "rover" which is subscribed to command/+/+/req/#. This is the response from the sender code: image

sophokles73 commented 4 years ago

you need to also set the address and other properties in the command message. See https://www.eclipse.org/hono/docs/api/command-and-control/ for details...

sophokles73 commented 4 years ago

There is no python code, but you can take a look at https://github.com/eclipse/hono/blob/master/client/src/main/java/org/eclipse/hono/client/impl/CommandClientImpl.java for a Java based implementation ...

roullli commented 4 years ago

I tried to fix the msg format which is sending by the code. The message goes reaches Hono but it seems that AMQP Network rejects the msg. I sniffed the packets and here is the result: image How can I find what is wrong with the msg? Is it somewhere in documentation?

sophokles73 commented 4 years ago

Can you post the code that you use for sending the command?

roullli commented 4 years ago
import Tkinter as tkinter
import proton
from cli_proton_python import sender
import ssl
import uuid
from proton._reactor import _generate_uuid
#imports end

# Receive commands without TLS via MQTT
# mosquitto_sub -v -h 192.168.1.152 -u rover2@rover -P rover-secret -t command/+/+/req/#

#const
scheme = "amqp://"
username = "consumer@HONO"
password = "verysecret"
hostAndPort = "192.168.1.154:15672"
tenant = "rover"
device = "rover2"
topic_to_publish = "command/"+tenant 
speed = 380
#const end

def sendControlMsg( msg ):
    print ("sending control msg " + str(msg))
    parser = sender.options.SenderOptions()
    opts, _= parser.parse_args()
    opts.broker_url = scheme + username + ":" + password +"@"+ hostAndPort +"/"+ topic_to_publish
    opts.msg_id = "0" # will be generated automatically
    opts.msg_subject = "RoverDriving"
    opts.msg_content = str(msg)
    opts.msg_reply_to = "command_response/"+tenant+"/"+str(_generate_uuid())
    opts.msg_correlation_id = "null"
    opts.msg_content_type = "application/json"
        opts.msg_address = "command/"+tenant+"/"+device    

    container = proton.reactor.Container(sender.Send(opts))
    container.run()

#funcs

def turn_right():
    sendControlMsg( "{\"command\":\"E\",\"speed\":"+ str(speed_var.get() + 360) +" }" )
    print("turn right clicked")

def turn_right_back():
    sendControlMsg( "{\"command\":\"D\",\"speed\": "+ str(speed_var.get() + 360) +" }" )
    print("turn right back clicked")

def turn_left():
   sendControlMsg("{\"command\":\"Q\",\"speed\": "+ str(speed_var.get() + 360) +" }")
   print("turn left clicked")

def turn_left_back():
   sendControlMsg("{\"command\":\"A\",\"speed\": "+ str(speed_var.get() + 360) +" }")
   print("turn left back clicked")

def move_forward():
   sendControlMsg("{\"command\":\"W\",\"speed\": "+ str(speed_var.get() + 360) +" }")
   print("move forward clicked")

def move_back():
   sendControlMsg("{\"command\":\"S\",\"speed\": "+ str(speed_var.get() + 360) +" }")
   print("move back clicked")

def spot_right():
   sendControlMsg("{\"command\":\"K\",\"speed\": "+ str(speed_var.get() + 360) +" }")
   print("move spot right clicked")

def spot_left():
   sendControlMsg("{\"command\":\"J\",\"speed\": "+ str(speed_var.get() + 360) +" }")
   print("move spot left clicked")

def stop_move():
   sendControlMsg("{\"command\":\"F\",\"speed\": "+ str(speed_var.get() + 360) +" }")
   print("Stop clicked")

def set_speed(event):
   speed = speed_var.get() + 360
   print("set speed to", speed)

def showPosEvent(event):
    print("posevt")

def onArrowUp(event):
    move_forward() 
    print("Arrow up")

def onArrowDown(event):
    move_back()
    print("Arrow down")

def onArrowLeft(event):
    turn_left()
    print("Arrow left")

def onArrowRight(event):
    turn_right()
    print("Arrow right")

def onSpaceBar(event):
    stop_move()
    print("Spacebar pressed")

if __name__=="__main__":

    tkroot = tkinter.Tk()
    tkroot.title("Rover Controller")
    speed_var = tkinter.DoubleVar() #tkinter.DoubleVar()
    labelfont = ('arial', 15, 'bold')     

    #Keyboard Input           
    tkroot.bind('<Up>',onArrowUp)
    tkroot.bind('<Down>',onArrowDown)
    tkroot.bind('<Left>',onArrowLeft)
    tkroot.bind('<Right>',onArrowRight)
    tkroot.bind('<space>',onSpaceBar)
    tkroot.focus()
    #Keyboard Input End

    #Buttons
    B_right = tkinter.Button(tkroot, text =">" , command= turn_right)
    B_right.place(x = 65, y= 25, height=25, width=25)

    B_right_back = tkinter.Button(tkroot, text =">" , command= turn_right_back)
    B_right_back.place(x = 65, y= 55, height=25, width=25)

    B_left  = tkinter.Button(tkroot, text ="<", command= turn_left)
    B_left.place(x= 15,y = 25, height=25, width=25)

    B_left_back  = tkinter.Button(tkroot, text ="<", command= turn_left_back)
    B_left_back.place(x= 15,y = 55, height=25, width=25)

    B_up = tkinter.Button(tkroot, text ="^", command= move_forward)
    B_up.place(x= 40, y = 10 ,  height=25, width=25)

    B_down = tkinter.Button(tkroot, text ="v", command= move_back)
    B_down.place(x= 40, y = 70, height=25, width=25)

    B_stop = tkinter.Button(tkroot, text ="Stop", command= stop_move)
    B_stop.place(x = 10,y = 160 , height = 30, width=85)

    B_spot_left = tkinter.Button(tkroot, text ="<)", command= spot_left)
    B_spot_left.place(x = 100,y = 160 , height = 30, width=35)

    B_spot_right = tkinter.Button(tkroot, text =">(", command= spot_right)
    B_spot_right.place(x = 140,y = 160 , height = 30, width=35)

    speed_label = tkinter.Label(tkroot , text = "Speed")
    speed_label.place(x= 10, y = 105, height=50, width=50)

    speed_scale = tkinter.Scale(tkroot, variable = speed_var,orient = tkinter.HORIZONTAL)
    speed_scale.bind("<ButtonRelease-1>", set_speed)
    speed_scale.place(x= 60, y = 105, height=50, width=85)
    #Buttons end

    tkroot.mainloop()
sophokles73 commented 4 years ago

The code looks good. There is one thing that might cause the problem, however. I am no expert in Python, but the line

opts.msg_content = str(msg)

suggests to me that the JSON payload is being added to the message as a an AMQP Value section instead of a Data section, as required by Hono's Command & Control API. You will need to check with the Python library that you are using for sending the AQMP message. Which is it? And BTW, which version of Hono are you using?

roullli commented 4 years ago

I'm using Qpid Proton library to send AMQP message: http://qpid.apache.org/releases/qpid-proton-0.32.0/proton/python/docs/overview.html I used Helm Chart to deploy Hono and currently, I'm using the latest version (1.4)

sophokles73 commented 4 years ago

You might want to try to set the message body as bytes instead of a string. However, I have no clue how this works in Python ...

sophokles73 commented 3 years ago

@roullli have you been able to send a command using your Python client?

roullli commented 3 years ago

Unfortunately not! still working on it!

sophokles73 commented 3 years ago

I have posted an answer to your question on stackoverflow. Hope that helps with getting it working ...

roullli commented 3 years ago

Great, Thanks! So I'll close this issue and follow the discussion over there.