j-woo commited on
Commit
8589c22
·
2 Parent(s): ca76fda 705d0f3

Merge branch 'main' of hf.co:spaces/AccelerationConsortium/cobot280pi

Browse files
Files changed (5) hide show
  1. .gitignore +2 -0
  2. app.py +72 -164
  3. client.py +170 -0
  4. requirements.txt +2 -1
  5. utils.py +43 -0
.gitignore ADDED
@@ -0,0 +1,2 @@
 
 
 
1
+ __pycache__/
2
+ runner.py
app.py CHANGED
@@ -1,181 +1,89 @@
1
  import streamlit as st
2
- import paho.mqtt.client as mqtt
3
- import queue, json, base64, io, os
4
- from PIL import Image
5
-
6
- def create_paho_client(tls=True):
7
- client = mqtt.Client(client_id="", userdata=None, protocol=mqtt.MQTTv5)
8
- if tls:
9
- client.tls_set(tls_version=mqtt.ssl.PROTOCOL_TLS_CLIENT)
10
- return client
11
-
12
- def setup_paho_client(client, username, password, cloud, port, device_id, response_queue):
13
- def on_message(client, userdata, msg):
14
- payload_dict = json.loads(msg.payload)
15
- response_queue.put(payload_dict)
16
-
17
- client.username_pw_set(username, password)
18
- client.connect(cloud, port)
19
- client.subscribe(device_id + "/response")
20
- client.on_message = on_message
21
- client.loop_start()
22
-
23
- def publish_and_wait(client, device_id, payload, response_queue):
24
- client.publish(device_id, payload=json.dumps(payload), qos=2)
25
- try:
26
- response = response_queue.get(block=True, timeout=10)
27
- except queue.Empty:
28
- response = None
29
- return response
30
-
31
- def handle_image_display(image):
32
- b64_bytes = base64.b64decode(image)
33
- img_bytes = io.BytesIO(b64_bytes)
34
- img = Image.open(img_bytes)
35
- st.image(img, caption="Cobot camera view")
36
 
37
  st.title("MyCobot280 Pi control demo")
38
 
39
  st.markdown("""
40
- This app is a public demo of ...
41
  """)
42
 
43
- use_own_creds = st.checkbox("Use custom broker", value=False)
 
 
 
 
 
 
44
 
45
- if use_own_creds:
46
- HIVEMQ_USERNAME = st.text_input("HIVEMQ Username", type="password")
47
- HIVEMQ_PASSWORD = st.text_input("HIVEMQ Password", type="password")
48
- HIVEMQ_HOST = st.text_input("HIVEMQ Host", type="password")
49
- DEVICE_ID = st.text_input("Device ID", type="password")
50
- PORT = st.number_input("Port", min_value=1, step=1, value=8883)
51
- else:
52
- HIVEMQ_USERNAME = os.environ.get("HIVEMQ_USERNAME")
53
- HIVEMQ_PASSWORD = os.environ.get("HIVEMQ_PASSWORD")
54
- HIVEMQ_HOST = os.environ.get("HIVEMQ_HOST")
55
- DEVICE_ID = os.environ.get("DEVICE_ID")
56
- PORT = int(os.environ.get("PORT", 8883))
57
 
58
- if 'client' not in st.session_state:
59
- st.session_state['client'] = None
60
- st.session_state['response_queue'] = None
61
- st.session_state['HIVEMQ_USERNAME'] = None
62
- st.session_state['HIVEMQ_PASSWORD'] = None
63
- st.session_state['HIVEMQ_HOST'] = None
64
- st.session_state['DEVICE_ID'] = None
65
- st.session_state['PORT'] = None
66
 
67
  st.markdown("## Commands")
68
  commands = [
69
- "query/angles",
70
- "query/coords",
71
- "query/gripper",
72
- "query/camera",
73
- "control/angles",
74
- "control/coords",
75
- "control/gripper"
76
  ]
77
  selected = st.selectbox("Select command: ", commands, key="selected_command", disabled=False)
78
 
79
  args = {}
80
- if st.session_state.selected_command == "query/angles":
81
- pass
82
- elif st.session_state.selected_command == "query/coords":
83
- pass
84
- elif st.session_state.selected_command == "query/gripper":
85
- pass
86
- elif st.session_state.selected_command == "query/camera":
87
- quality = st.slider("Image quality", 1, 100, 50)
88
- args = {"quality": quality}
89
- elif st.session_state.selected_command == "control/angles":
90
- col1, col2, col3 = st.columns(3)
91
- with col1:
92
- angle_1 = st.number_input("Angle 1", format="%.2f", step=5.0)
93
- angle_4 = st.number_input("Angle 4", format="%.2f", step=5.0)
94
- with col2:
95
- angle_2 = st.number_input("Angle 2", format="%.2f", step=5.0)
96
- angle_5 = st.number_input("Angle 5", format="%.2f", step=5.0)
97
- with col3:
98
- angle_3 = st.number_input("Angle 3", format="%.2f", step=5.0)
99
- angle_6 = st.number_input("Angle 6", format="%.2f", step=5.0)
100
- speed = st.slider("Speed", 1, 100, 50)
101
- args = {"angles": [angle_1, angle_2, angle_3, angle_4, angle_5, angle_6], "speed": speed}
102
- elif st.session_state.selected_command == "control/coords":
103
- col1, col2, col3 = st.columns(3)
104
- with col1:
105
- x = st.number_input("x", format="%.2f", step=5.0)
106
- rx = st.number_input("rx (Rotation x)", format="%.2f", step=5.0)
107
- with col2:
108
- y = st.number_input("y", format="%.2f", step=5.0)
109
- ry = st.number_input("ry (Rotation y)", format="%.2f", step=5.0)
110
- with col3:
111
- z = st.number_input("z", format="%.2f", step=5.0)
112
- rz = st.number_input("rz (Rotation z)", format="%.2f", step=5.0)
113
- speed = st.slider("Speed", 1, 100, 50)
114
- args = {"coords": [x, y, z, rx, ry, rz], "speed": speed}
115
- elif st.session_state.selected_command == "control/gripper":
116
- gripper_value = st.slider("Gripper value", 1, 100, 50)
117
- speed = st.slider("Speed", 1, 100, 50)
118
- args = {"gripper_value": gripper_value, "speed": speed}
119
-
120
  with st.form("mqtt_form"):
121
- submitted = st.form_submit_button("Send Command")
122
- if submitted:
123
- if st.session_state['client'] is not None:
124
- credentials_changed = (
125
- st.session_state.get('HIVEMQ_USERNAME') != HIVEMQ_USERNAME or
126
- st.session_state.get('HIVEMQ_PASSWORD') != HIVEMQ_PASSWORD or
127
- st.session_state.get('HIVEMQ_HOST') != HIVEMQ_HOST or
128
- st.session_state.get('DEVICE_ID') != DEVICE_ID or
129
- st.session_state.get('PORT') != PORT
130
- )
131
- if credentials_changed:
132
- st.session_state['client'].disconnect()
133
- st.session_state['client'] = None
134
-
135
- if st.session_state['client'] is None:
136
- st.session_state['response_queue'] = queue.Queue()
137
- st.session_state['client'] = create_paho_client()
138
- setup_paho_client(
139
- st.session_state['client'],
140
- HIVEMQ_USERNAME,
141
- HIVEMQ_PASSWORD,
142
- HIVEMQ_HOST,
143
- PORT,
144
- DEVICE_ID,
145
- st.session_state['response_queue']
146
- )
147
- st.session_state['HIVEMQ_USERNAME'] = HIVEMQ_USERNAME
148
- st.session_state['HIVEMQ_PASSWORD'] = HIVEMQ_PASSWORD
149
- st.session_state['HIVEMQ_HOST'] = HIVEMQ_HOST
150
- st.session_state['DEVICE_ID'] = DEVICE_ID
151
- st.session_state['PORT'] = PORT
152
-
153
- payload = {"command": st.session_state.selected_command, "args": args}
154
- st.markdown("### Payload")
155
- st.write(payload)
156
-
157
- response = publish_and_wait(
158
- st.session_state['client'],
159
- DEVICE_ID,
160
- payload,
161
- st.session_state['response_queue']
162
- )
163
- st.markdown("### Response")
164
- if response is not None:
165
- if "image" in response:
166
- image = response.pop("image")
167
- st.write(response)
168
- handle_image_display(image)
169
- else:
170
- st.write(response)
171
- else:
172
- st.write("Timed out waiting for response. Is the on-device server running?")
173
-
174
- cobot_stream_url = "https://youtube.com/live/fF4zEp6LSkg?feature=share"
175
-
176
- st.video(
177
- cobot_stream_url
178
- # Optionally enable autoplay which requires muting as browsers don't allow autoplay of unmuted video
179
- # muted=True,
180
- # autoplay=True
181
- )
 
1
  import streamlit as st
2
+ from utils import *
3
+ from client import CobotController
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
4
 
5
  st.title("MyCobot280 Pi control demo")
6
 
7
  st.markdown("""
8
+ This app is a public demo of remote control of the MyCobot280 Pi through the MQTT Communication Protocol.
9
  """)
10
 
11
+ cobot_stream_url = "https://youtube.com/live/w7zn-Sk3pG0?feature=share"
12
+ st.video(
13
+ cobot_stream_url
14
+ # Optionally enable autoplay which requires muting as browsers don't allow autoplay of unmuted video
15
+ # muted=True,
16
+ # autoplay=True
17
+ )
18
 
19
+ render_log_window()
 
 
 
 
 
 
 
 
 
 
 
20
 
21
+ user_id = get_user_id()
22
+ user, passwd, host, endpoint, port = get_credentials(False)
23
+ client = CobotController(user, passwd, host, port, endpoint, user_id)
24
+ # refresh_once()
 
 
 
 
25
 
26
  st.markdown("## Commands")
27
  commands = [
28
+ "query/angles",
29
+ "query/coords",
30
+ "query/gripper",
31
+ "query/camera",
32
+ "control/angles",
33
+ "control/coords",
34
+ "control/gripper"
35
  ]
36
  selected = st.selectbox("Select command: ", commands, key="selected_command", disabled=False)
37
 
38
  args = {}
39
+ command = None
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
40
  with st.form("mqtt_form"):
41
+ if st.session_state.selected_command == "query/angles":
42
+ command = client.get_angles
43
+ elif st.session_state.selected_command == "query/coords":
44
+ command = client.get_coords
45
+ elif st.session_state.selected_command == "query/gripper":
46
+ command = client.get_gripper_value
47
+ elif st.session_state.selected_command == "query/camera":
48
+ quality = st.slider("Image quality", 1, 100, 100)
49
+ command = client.get_camera
50
+ args = {"quality": quality}
51
+ elif st.session_state.selected_command == "control/angles":
52
+ angle_1 = st.number_input("Angle 1", format="%.2f", step=5.0)
53
+ angle_2 = st.number_input("Angle 2", format="%.2f", step=5.0)
54
+ angle_3 = st.number_input("Angle 3", format="%.2f", step=5.0)
55
+ angle_4 = st.number_input("Angle 4", format="%.2f", step=5.0)
56
+ angle_5 = st.number_input("Angle 5", format="%.2f", step=5.0)
57
+ angle_6 = st.number_input("Angle 6", format="%.2f", step=5.0)
58
+ speed = st.slider("Speed", 1, 100, 50)
59
+ command = client.send_angles
60
+ args = {"angle_list": [angle_1, angle_2, angle_3, angle_4, angle_5, angle_6], "speed": speed}
61
+ elif st.session_state.selected_command == "control/coords":
62
+ x = st.number_input("x", format="%.2f", step=5.0)
63
+ y = st.number_input("y", format="%.2f", step=5.0)
64
+ z = st.number_input("z", format="%.2f", step=5.0)
65
+ rx = st.number_input("rx (Rotation x)", format="%.2f", step=5.0)
66
+ ry = st.number_input("ry (Rotation y)", format="%.2f", step=5.0)
67
+ rz = st.number_input("rz (Rotation z)", format="%.2f", step=5.0)
68
+ speed = st.slider("Speed", 1, 100, 50)
69
+ command = client.send_coords
70
+ args = {"coord_list": [x, y, z, rx, ry, rz], "speed": speed}
71
+ elif st.session_state.selected_command == "control/gripper":
72
+ gripper_value = st.slider("Gripper value", 1, 100, 50)
73
+ speed = st.slider("Speed", 1, 100, 50)
74
+ command = client.send_gripper_value
75
+ args = {"value": gripper_value, "speed": speed}
76
+
77
+ submitted = st.form_submit_button("Send Command")
78
+ if submitted:
79
+ response = command(**args)
80
+ st.markdown("### Response")
81
+ if response is not None:
82
+ if "image" in response:
83
+ image = response.pop("image")
84
+ st.write(response)
85
+ st.image(image, caption="Cobot camera view")
86
+ else:
87
+ st.write(response)
88
+ else:
89
+ st.write("Timed out waiting for response. Is the on-device server running?")
 
 
 
 
 
 
 
 
 
 
 
 
client.py ADDED
@@ -0,0 +1,170 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ # installed packages
2
+ from PIL import Image
3
+ import paho.mqtt.client as paho
4
+
5
+ # base python packages
6
+ import json, io, base64
7
+ from queue import Queue
8
+ from datetime import datetime
9
+ import uuid
10
+
11
+ def print_with_timestamp(message):
12
+ print(f"[{datetime.now().strftime('%b %d, %H:%M:%S')}] {message}")
13
+
14
+ class CobotController:
15
+ user_id = str(uuid.uuid4())
16
+
17
+ def __init__(
18
+ self,
19
+ hive_mq_username: str,
20
+ hive_mq_password: str,
21
+ hive_mq_cloud: str,
22
+ port: int,
23
+ device_endpoint: str,
24
+ user_id: str = None
25
+ ):
26
+ # setup client and response queues
27
+ self.client = paho.Client(client_id="", userdata=None, protocol=paho.MQTTv5)
28
+ self.client.tls_set()
29
+ self.client.username_pw_set(hive_mq_username, hive_mq_password)
30
+ self.client.connect(hive_mq_cloud, port)
31
+
32
+ self.response_queue = Queue()
33
+ def on_message(client, userdata, msg):
34
+ payload_dict = json.loads(msg.payload)
35
+ self.response_queue.put(payload_dict)
36
+
37
+ def on_connect(client, userdata, flags, rc, properties=None):
38
+ print_with_timestamp("Connected to HiveMQ broker...")
39
+
40
+ self.client.on_connect = on_connect
41
+ self.client.on_message = on_message
42
+ self.client.loop_start()
43
+
44
+ # initialize user id and endpoints
45
+ if user_id is not None:
46
+ CobotController.user_id = user_id
47
+ self.user_id = CobotController.user_id
48
+
49
+ self.device_endpoint = device_endpoint
50
+ self.init_endpoint = self.device_endpoint + "/init"
51
+ self.publish_endpoint = self.device_endpoint + "/" + self.user_id
52
+ self.incoming_endpoint = self.publish_endpoint + "/response"
53
+ self.client.subscribe(self.incoming_endpoint, qos=2)
54
+
55
+ connected = self.check_connection_status()
56
+ if connected:
57
+ return
58
+
59
+ # send an init request
60
+ print_with_timestamp("Sending a connection request...")
61
+ pub_handle = self.client.publish(
62
+ self.init_endpoint,
63
+ payload=json.dumps({"id": self.user_id}),
64
+ qos=2
65
+ )
66
+ pub_handle.wait_for_publish()
67
+
68
+ # get a response for the init message, if no response, have to wait for current users time to end
69
+ print_with_timestamp("Waiting for cobot access...")
70
+ prev_pos = None
71
+ while True:
72
+ try:
73
+ payload = self.response_queue.get(timeout=10)
74
+ if payload["status"] == "ready":
75
+ self.client.publish(
76
+ self.publish_endpoint,
77
+ payload=json.dumps({"yeehaw": []}),
78
+ qos=2
79
+ )
80
+ print_with_timestamp("Connected to server successfully.")
81
+ break
82
+
83
+ except Exception as e:
84
+ resp = self.handle_publish_and_response(
85
+ payload=json.dumps({"id": self.user_id}),
86
+ custom_endpoint=self.device_endpoint + "/queuequery"
87
+ )
88
+ if "queue_pos" not in resp:
89
+ break
90
+ pos = resp["queue_pos"]
91
+ if prev_pos == None:
92
+ prev_pos = pos
93
+ elif prev_pos == pos:
94
+ continue
95
+ prev_pos = pos
96
+ print_with_timestamp(f"Waiting for cobot access. There are {pos - 1} users ahead of you...")
97
+
98
+ def check_connection_status(self):
99
+ self.client.publish(
100
+ self.publish_endpoint,
101
+ payload=json.dumps({"command":"query/angles", "args": {}}),
102
+ qos=2
103
+ )
104
+ try: # if we recieve any response, it means the server is currently servicing our requests
105
+ _ = self.response_queue.get(timeout=5)
106
+ return True
107
+ except Exception as _:
108
+ return False
109
+
110
+ def handle_publish_and_response(self, payload, custom_endpoint=None):
111
+ if custom_endpoint is None:
112
+ self.client.publish(self.publish_endpoint, payload=payload, qos=2)
113
+ else:
114
+ self.client.publish(custom_endpoint, payload=payload, qos=2)
115
+ return self.response_queue.get(block=True)
116
+
117
+ def send_angles(
118
+ self,
119
+ angle_list: list[float] = [0.0] * 6,
120
+ speed: int = 50
121
+ ):
122
+ payload = json.dumps({"command": "control/angles",
123
+ "args": {"angles": angle_list, "speed": speed}})
124
+ return self.handle_publish_and_response(payload)
125
+
126
+ def send_coords(
127
+ self,
128
+ coord_list: list[float] = [0.0] * 6,
129
+ speed: int = 50
130
+ ):
131
+ payload = json.dumps({"command": "control/coords",
132
+ "args": {"coords": coord_list, "speed": speed}})
133
+ return self.handle_publish_and_response(payload)
134
+
135
+ def send_gripper_value(
136
+ self,
137
+ value: int = 100,
138
+ speed: int = 50
139
+ ):
140
+ payload = json.dumps({"command": "control/gripper",
141
+ "args": {"gripper_value": value, "speed": speed}})
142
+ return self.handle_publish_and_response(payload)
143
+
144
+ def get_angles(self):
145
+ payload = json.dumps({"command": "query/angles", "args": {}})
146
+ return self.handle_publish_and_response(payload)
147
+
148
+ def get_coords(self):
149
+ payload = json.dumps({"command": "query/coords", "args": {}})
150
+ return self.handle_publish_and_response(payload)
151
+
152
+ def get_gripper_value(self):
153
+ payload = json.dumps({"command": "query/gripper", "args": {}})
154
+ return self.handle_publish_and_response(payload)
155
+
156
+ def get_camera(self, quality=100, save_path=None):
157
+ payload = json.dumps({"command": "query/camera", "args": {"quality": quality}})
158
+ response = self.handle_publish_and_response(payload)
159
+ if not response["success"]:
160
+ return response
161
+
162
+ b64_bytes = base64.b64decode(response["image"])
163
+ img_bytes = io.BytesIO(b64_bytes)
164
+ img = Image.open(img_bytes)
165
+
166
+ response["image"] = img
167
+ if save_path is not None:
168
+ img.save(save_path)
169
+
170
+ return response
requirements.txt CHANGED
@@ -1,4 +1,5 @@
1
  paho-mqtt==2.1.0
2
  pillow==10.4.0
3
  setuptools==75.1.0
4
- wheel==0.44.0
 
 
1
  paho-mqtt==2.1.0
2
  pillow==10.4.0
3
  setuptools==75.1.0
4
+ wheel==0.44.0
5
+ streamlit-js-eval==0.1.7
utils.py ADDED
@@ -0,0 +1,43 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import streamlit as st
2
+ from streamlit_js_eval import streamlit_js_eval
3
+ import uuid, io, sys, os
4
+
5
+ def get_user_id():
6
+ if "user_id" not in st.session_state:
7
+ query_params = st.query_params
8
+ user_id = query_params.get("user_id", None)
9
+ if not user_id:
10
+ user_id = str(uuid.uuid4())
11
+ st.query_params["user_id"] = user_id
12
+ st.session_state.user_id = user_id
13
+ return st.session_state.user_id
14
+
15
+ def get_credentials(use_own_creds: bool):
16
+ if use_own_creds:
17
+ HIVEMQ_USERNAME = st.text_input("HIVEMQ Username", type="password")
18
+ HIVEMQ_PASSWORD = st.text_input("HIVEMQ Password", type="password")
19
+ HIVEMQ_HOST = st.text_input("HIVEMQ Host", type="password")
20
+ DEVICE_ENDPOINT = st.text_input("Device ID", type="password")
21
+ PORT = st.number_input("Port", min_value=1, step=1, value=8883)
22
+ else:
23
+ HIVEMQ_USERNAME = os.environ.get("HIVEMQ_USERNAME")
24
+ HIVEMQ_PASSWORD = os.environ.get("HIVEMQ_PASSWORD")
25
+ HIVEMQ_HOST = os.environ.get("HIVEMQ_HOST")
26
+ DEVICE_ENDPOINT = os.environ.get("DEVICE_ID")
27
+ PORT = int(os.environ.get("PORT", 8883))
28
+ return HIVEMQ_USERNAME, HIVEMQ_PASSWORD, HIVEMQ_HOST, DEVICE_ENDPOINT, PORT
29
+
30
+ def render_log_window():
31
+ class StreamlitRedirector:
32
+ def write(self, message):
33
+ if message.strip():
34
+ st.write(message)
35
+
36
+ sys.stdout = StreamlitRedirector()
37
+
38
+ def refresh_once():
39
+ has_refreshed = st.query_params.get("has_refreshed", None)
40
+ if not has_refreshed:
41
+ st.query_params["has_refreshed"] = True
42
+ sys.stdout = sys.__stdout__
43
+ streamlit_js_eval(js_expressions="parent.window.location.reload()")