Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
78 commits
Select commit Hold shift + click to select a range
0cd46b1
proto for real -- wip
alaurenzi Apr 12, 2025
6718f26
upd proto
alaurenzi Apr 12, 2025
2e77da2
proto
alaurenzi Apr 12, 2025
ee6b886
wip protobuf
alaurenzi Apr 14, 2025
3554e19
wip protobuf ui
alaurenzi Apr 14, 2025
b7aa4e0
fix launcher py
alaurenzi Apr 14, 2025
9efad97
better network stats layout
alaurenzi Apr 14, 2025
1463c53
refactor process consoles to support lazy loading
alaurenzi Apr 14, 2025
5802e58
restore server modules
alaurenzi Apr 14, 2025
6258890
2.7.0
alaurenzi Apr 14, 2025
7e98233
refresh sdo cache if slave not found
Apr 17, 2025
fe7c87c
fix clicked joints in monitoring, unify layouthelper
alaurenzi Apr 18, 2025
8e33910
gripper support
Apr 23, 2025
3215714
version
Apr 23, 2025
92904f2
fixes to Card1: hovering now works
alaurenzi Apr 22, 2025
1239edf
cleaner ecat without those read buttons..
alaurenzi Apr 22, 2025
1e29a56
rm commented out
alaurenzi Apr 22, 2025
32216a8
launcher: responsive columns
alaurenzi Apr 22, 2025
498df6e
restore hover bar plot items
alaurenzi Apr 22, 2025
9bba470
try: move slider on text edited
alaurenzi Apr 22, 2025
aef4be7
fix mon widget long joint name wrapping
alaurenzi Apr 22, 2025
3a66df6
restore per-page layout helper
alaurenzi Apr 23, 2025
2babfc3
gripper support ui
alaurenzi Apr 23, 2025
90a13aa
add link telemetry
alaurenzi Apr 23, 2025
7ffa698
fix monwidget align top
alaurenzi Apr 23, 2025
5f1e70c
fix jointcommand textinput
alaurenzi May 6, 2025
fa4af00
fix: stdout -> out for win32 compat
alaurenzi May 7, 2025
3ea4706
add webenginequick
alaurenzi May 7, 2025
1fdfcd3
server wasm support (no binary)
alaurenzi May 7, 2025
a3a4f60
wip wasm support (single threaded)
alaurenzi Jun 20, 2025
6c835e7
new xbot2_gui executable to download and run both ui and server
lucarossini-iit May 12, 2025
ae3803d
fix plot
alaurenzi May 13, 2025
28979f1
missing main python
lucarossini-iit May 19, 2025
da15b96
clearColumns in MCL1
alaurenzi May 23, 2025
731d790
horizon: rm enable gait, rm walk
alaurenzi May 23, 2025
4a95b9c
version 2.7.3
alaurenzi May 23, 2025
64245a0
fixed protobuf version range
lucarossini-iit Jun 11, 2025
c88267c
require 6.9
alaurenzi Jun 19, 2025
7953796
card with bordercolor and width options
alaurenzi Jun 19, 2025
53f6bab
add variable font for material symbols
alaurenzi Jun 19, 2025
4a69f30
joy: use layout class helpers
alaurenzi Jun 19, 2025
dbb2577
consolecard configurable font size
alaurenzi Jun 19, 2025
b3178ec
updated process and plugin cards (pulsating, muted)
alaurenzi Jun 19, 2025
5283779
launcher updates (muted procs, font size)
alaurenzi Jun 19, 2025
54a18a8
barplot fix joint de-select
alaurenzi Jun 19, 2025
ebc4651
update gradle for ndk 27 and android-35
alaurenzi Jun 19, 2025
a609458
ui version
alaurenzi Jun 19, 2025
32a9e27
dont findpackage webenginequick on android
alaurenzi Jun 20, 2025
a80d991
version
alaurenzi Jun 20, 2025
b475fab
try fix gh workflow
alaurenzi Jun 20, 2025
9bd9e98
ci
alaurenzi Jun 20, 2025
5af7554
try fix ci
alaurenzi Jun 20, 2025
6415dbd
try fix ci: clone recursive
alaurenzi Jun 20, 2025
fd7a6cf
[skip ci] decode keystore
alaurenzi Jun 20, 2025
c108f8e
Try fix ci
alaurenzi Jun 20, 2025
66f8c67
Update build-linux-android.yml
alaurenzi Jun 20, 2025
ed88de1
Update build_and_deploy_server.bash
alaurenzi Jun 20, 2025
7a5751e
launcher: organize procs by category
alaurenzi Jul 24, 2025
0bac940
avoid initial connection refused error
alaurenzi Jul 24, 2025
b1421b1
fix card title exceeding bounds
alaurenzi Jul 24, 2025
398605c
better placeholders when server uri not inserted
alaurenzi Jul 24, 2025
c01279b
configuration scrolls
alaurenzi Jul 24, 2025
64fa2ad
app page
alaurenzi Jul 24, 2025
b1822b1
fix proc layout ifor hidden processes
alaurenzi Jul 24, 2025
9d669a5
more symbols
alaurenzi Jul 24, 2025
e0c99bf
gamepad support via QtGamepadLegacy
alaurenzi Jul 24, 2025
517077e
recursive splitview
alaurenzi Jul 29, 2025
d1e5e8f
fixes for gamepad
alaurenzi Jul 29, 2025
88a480c
recursive splitview playground
alaurenzi Jul 29, 2025
e9b46a4
fix ambiguous imports
alaurenzi Jul 29, 2025
7296e16
no proc found
alaurenzi Jul 29, 2025
46f6adb
ci
alaurenzi Jul 29, 2025
d3ec517
towards plotjuggler
alaurenzi Aug 1, 2025
ee67200
launcher fixes && wait for xbot2
alaurenzi Aug 1, 2025
05d44a4
try fix ci
alaurenzi Aug 25, 2025
44a8bc3
check webui path exists
alaurenzi Sep 9, 2025
879904e
move detectObjectFields at the bottom of msg callback
alaurenzi Sep 12, 2025
628f788
[skip ci] almost nothing
alaurenzi Sep 12, 2025
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
38 changes: 38 additions & 0 deletions .github/workflows/build-linux-android.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,38 @@
name: Build Linux and Android

on:
push:
tags:
- 'v[0-9]+.[0-9]+.[0-9]+'
branches: [ "2.0-master", "wasm_devel" ]
pull_request:
branches: [ "2.0-master" ]

jobs:

build:

runs-on: ubuntu-latest

steps:
- uses: actions/checkout@v4
with:
submodules: recursive
- name: Detect tag
if: startsWith(github.ref, 'refs/tags/')
run: echo "TRAVIS_TAG=$GITHUB_REF_NAME" >> $GITHUB_ENV
- name: Retrieve the secret and decode it to a file
env:
MY_KEYSTORE_BASE64: ${{ secrets.MY_KEYSTORE_BASE64 }}
run: |
echo $MY_KEYSTORE_BASE64 | base64 --decode > my.keystore
- name: Install dependencies
run: bash travis/install_dependencies.bash
- name: Build client
env:
KEYSTORE_PWD: ${{ secrets.KEYSTORE_PWD }}
run: bash travis/build_client.bash
- name: Build server
run: source env/bin/activate; bash travis/build_and_deploy_server.bash
- name: Deploy client
run: bash travis/deploy_client.bash
2 changes: 1 addition & 1 deletion CMakeLists.txt
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
cmake_minimum_required(VERSION 3.16)
project(robot_monitoring VERSION 2.6.0)
project(robot_monitoring VERSION 2.7.5)

add_subdirectory(xbot2_gui)
16 changes: 16 additions & 0 deletions proto/generic.proto
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
syntax = "proto3";

import "jointstate.proto";
import "text.proto";
import "process_output.proto";
import "theora_packet.proto";

message Message {

int32 seq = 1;
JointState jointstate = 2;
Text text = 3;
ProcessOutput process_output = 4;
TheoraPacket theora_packet = 5;

}
27 changes: 27 additions & 0 deletions proto/jointstate.proto
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
syntax = "proto3";

message FloatArray {
repeated float value = 1;
}

message JointState {

repeated float linkPos = 1;
repeated float motPos = 2;
repeated float linkVel = 3;
repeated float motVel = 4;
repeated float tor = 5;
repeated float motorTemp = 6;
repeated float driverTemp = 7;

repeated float posRef = 8;
repeated float velRef = 9;
repeated float torRef = 10;
repeated float k = 11;
repeated float d = 12;

map<string, FloatArray> aux = 13;

float vbatt = 14;
float ibatt = 15;
}
8 changes: 8 additions & 0 deletions proto/process_output.proto
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
syntax = "proto3";

message ProcessOutput {

string name = 1;
string out = 2;
string err = 3;
}
6 changes: 6 additions & 0 deletions proto/text.proto
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
syntax = "proto3";

message Text {

string text = 1;
}
11 changes: 11 additions & 0 deletions proto/theora_packet.proto
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
syntax = "proto3";

message TheoraPacket {
string stream_name = 1;
bytes data = 2;
int32 b_o_s = 3;
int32 e_o_s = 4;
int32 granulepos = 5;
int32 packetno = 6;
}

1 change: 1 addition & 0 deletions proto/update_proto.bash
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
protoc *.proto --python_out=../server/src/xbot2_gui_server/proto
4 changes: 3 additions & 1 deletion server/setup.cfg
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
[metadata]
name = xbot2_gui_server
version = 2.6.0
version = 2.7.3
author = Arturo Laurenzi
author_email = arturo.laurenzi@iit.it
description = Backend for XBot2 QML GUI
Expand All @@ -23,6 +23,7 @@ python_requires = >=3.8
install_requires =
aiohttp
asyncudp
protobuf>=5.29.0,<5.29.9


[options.packages.find]
Expand All @@ -31,3 +32,4 @@ where = src
[options.entry_points]
console_scripts =
xbot2_gui_server = xbot2_gui_server.main:main
xbot2_gui = xbot2_gui_server.xbot2_gui:main
5 changes: 5 additions & 0 deletions server/src/xbot2_gui_server/cosecosecose.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
from ecat_repl.stuff import read_sdo, set_uri

set_uri('amax-5580.local:5555')
an = read_sdo(['Assigned_name'], [1])[1]['Assigned_name']
print(an)
7 changes: 7 additions & 0 deletions server/src/xbot2_gui_server/ecat.py
Original file line number Diff line number Diff line change
Expand Up @@ -100,6 +100,13 @@ async def get_sdo_list(self, req: web.Request):
print('cache is empty, refreshing')
self.ctx.update_cache()

for id in ids:
if id not in self.ctx.sdo_dict.keys():
print(f'esc id {id} not found in cache, refreshing...')
await utils.to_thread(self.ctx.update_cache)
print('...done')
break

# fill sdo list
sdos = []
for id in ids:
Expand Down
114 changes: 95 additions & 19 deletions server/src/xbot2_gui_server/joint_states.py
Original file line number Diff line number Diff line change
Expand Up @@ -9,14 +9,15 @@


import rospy
from sensor_msgs.msg import JointState as StdJointState
from xbot_msgs.msg import JointState, Fault, JointCommand, CustomState
from std_msgs.msg import Float32
from urdf_parser_py import urdf as urdf_parser

from .server import ServerBase
from . import utils

# from .proto import joint_states_pb2
from .proto import jointstate_pb2, generic_pb2

## limit float precision in json serialization
class RoundingFloat(float):
Expand All @@ -40,6 +41,7 @@ def __init__(self, srv: ServerBase, config=dict()) -> None:
self.srv.schedule_task(self.run())
self.srv.register_ws_coroutine(self.handle_ws_msg)
self.srv.add_route('GET', '/joint_states/info', self.get_joint_info_handler, 'get_joint_info')
self.srv.add_route('GET', '/joint_states/grippers', self.get_grippers_handler, 'get_grippers')
self.srv.add_route('GET', '/joint_states/urdf', self.get_urdf_handler, 'get_urdf')
self.srv.add_route('GET', '/joint_states/connected', self.robot_connected_handler, 'get_connected')
self.srv.add_route('PUT', '/joint_command/goto/{joint_name}', self.command_handler, 'command')
Expand Down Expand Up @@ -76,6 +78,11 @@ def __init__(self, srv: ServerBase, config=dict()) -> None:
self.cmd_guard = JointStateHandler.CommandGuard(self.command_acquire, self.command_release)
self.cmd_should_stop = True

# grippers
self.gripper_state_sub = dict()
self.gripper_cmd_pub = dict()
self.gripper_state_msg = dict()

# config
self.rate = config.get('rate', 30.0)

Expand All @@ -101,7 +108,11 @@ def on_aux_recv(self, msg: CustomState, aux: list):
if not math.isnan(msg.value[i]):
aux[i] = msg.value[i]



def on_gripper_state_recv(self, msg: StdJointState, gname):
self.gripper_state_msg[gname] = msg


@utils.handle_exceptions
async def get_urdf_handler(self, request: web.Request):
print('retrieving robot description..')
Expand Down Expand Up @@ -184,6 +195,43 @@ async def get_joint_info_handler(self, request: web.Request):
return web.Response(text=json.dumps(joint_info))


@utils.handle_exceptions
async def get_grippers_handler(self, req: web.Request):

# get topic names from ros master
topic_name_type_list = await utils.to_thread(rospy.get_published_topics)

# filter /xbotcore/gripper/GRIPPERNAME/state
gripper_names = set()
for tname, ttype in topic_name_type_list:
print(tname, ttype)
if ttype != 'sensor_msgs/JointState':
continue
tokens = tname.strip('/').split('/')
if len(tokens) == 4 and tokens[1] == 'gripper' and tokens[3] == 'state':
gripper_names.add(tokens[2])

# register topics
for gname in gripper_names:
state = f'xbotcore/gripper/{gname}/state'
cmd = f'xbotcore/gripper/{gname}/command'
self.gripper_state_msg[gname] = None
self.gripper_state_sub[gname] = rospy.Subscriber(state, StdJointState,
self.on_gripper_state_recv, gname, queue_size=1)
self.gripper_cmd_pub[gname] = rospy.Publisher(cmd, StdJointState, queue_size=1)
print(f'connecting to gripper {gname}...')


# reply
res = {
'success': True,
'message': '',
'gripper_names': sorted(list(gripper_names))
}

return web.Response(text=json.dumps(res))


async def run(self):

t0 = time.time()
Expand All @@ -207,30 +255,48 @@ async def run(self):
# check js received
if self.msg is None:
continue

# convert to dict
js_msg_to_send = self.js_msg_to_dict(self.msg)

# test: avoid sending names to save bw
del js_msg_to_send['name']

# add pow data and seq id
js_msg_to_send['vbatt'] = self.vbatt
js_msg_to_send['iload'] = self.iload
js_msg_to_send['seq'] = self.js_seq
self.js_seq += 1

# serialize msg to json
js_str = json.dumps(js_msg_to_send)

# send to all connected clients
await self.srv.udp_send_to_all(js_str)
# pb js
# TBD aux support
try:
msgpb = generic_pb2.Message()
msgpb.jointstate.linkPos.extend(self.msg.link_position)
msgpb.jointstate.motPos.extend(self.msg.motor_position)
msgpb.jointstate.motVel.extend(self.msg.motor_velocity)
msgpb.jointstate.velRef.extend(self.msg.velocity_reference)
msgpb.jointstate.torRef.extend(self.msg.effort_reference)
msgpb.jointstate.tor.extend(self.msg.effort)
msgpb.jointstate.posRef.extend(self.msg.position_reference)
msgpb.jointstate.k.extend(self.msg.stiffness)
msgpb.jointstate.d.extend(self.msg.damping)
msgpb.jointstate.motorTemp.extend(self.msg.temperature_motor)
msgpb.jointstate.driverTemp.extend(self.msg.temperature_driver)
msgpb.jointstate.vbatt = self.vbatt
msgpb.jointstate.ibatt = self.iload
await self.srv.udp_send_to_all(msgpb)
except Exception as e:
# print traceback
import traceback
traceback.print_exc()


# clear to avoid sending duplicates
self.msg = None
for v in self.aux_map.values():
v.clear()

# grippers
for gname, gmsg in self.gripper_state_msg.items():
if gmsg is None:
continue
gmsg = {
'type': 'gripper_state',
'q': gmsg.position[0],
'tau': gmsg.effort
}
await self.srv.ws_send_to_all(json.dumps(gmsg))
self.gripper_state_msg[gname] = None


def command_acquire(self):
if self.cmd_busy:
Expand Down Expand Up @@ -376,6 +442,7 @@ def js_msg_to_dict(self, msg: JointState):


async def handle_ws_msg(self, msg, proto, ws):

if msg['type'] == 'joint_cmd':
cmdmsg = JointCommand()
cmdmsg.name = msg['joint_names']
Expand All @@ -388,3 +455,12 @@ async def handle_ws_msg(self, msg, proto, ws):
else:
raise ValueError(f'unknown control mode {msg["ctrl"]}')
self.cmd_pub.publish(cmdmsg)

if msg['type'] == 'gripper_cmd':
cmdmsg = StdJointState()
gname = msg['name']
if msg['action'] == 'open':
cmdmsg.position = [0.0]
elif msg['action'] == 'close':
cmdmsg.effort = [msg['effort']]
self.gripper_cmd_pub[gname].publish(cmdmsg)
Loading