Simplifies spot class and add threaded websocket controller interface
This commit is contained in:
@@ -1,10 +0,0 @@
|
|||||||
# SCRIPT FOR RUNNING THE RPI ROBOT
|
|
||||||
|
|
||||||
from .src.spot import Spot
|
|
||||||
# Server
|
|
||||||
|
|
||||||
# Bluetooth controller
|
|
||||||
|
|
||||||
# Motion planing
|
|
||||||
|
|
||||||
# Hardware connections
|
|
||||||
@@ -0,0 +1,33 @@
|
|||||||
|
# SCRIPT FOR RUNNING THE RPI ROBOT
|
||||||
|
|
||||||
|
import time
|
||||||
|
from src.spot import Spot
|
||||||
|
from src.kinematics.SpotKinematics import SpotModel
|
||||||
|
from src.camera.WebCamera import WebCamera
|
||||||
|
from src.imu.IMU import IMU
|
||||||
|
from src.hardware_interface.HardwareInterface import HardwareInterface
|
||||||
|
from src.controller_interface.WebsocketController import WebsocketController
|
||||||
|
from src.controller_interface.SharedControllerState import SharedState, shared_state
|
||||||
|
|
||||||
|
|
||||||
|
def main():
|
||||||
|
kinematics = SpotModel()
|
||||||
|
camera = WebCamera()
|
||||||
|
imu = IMU()
|
||||||
|
hardware_interface = HardwareInterface()
|
||||||
|
# shared_controller_state = SharedState()
|
||||||
|
controller_interface = WebsocketController() # shared_controller_state)
|
||||||
|
|
||||||
|
spot = Spot(kinematics, camera, imu, hardware_interface, controller_interface)
|
||||||
|
|
||||||
|
spot.start()
|
||||||
|
|
||||||
|
try:
|
||||||
|
while True:
|
||||||
|
spot.run(shared_state.get_latest_state().__dict__)
|
||||||
|
time.sleep(1)
|
||||||
|
except KeyboardInterrupt:
|
||||||
|
spot.stop()
|
||||||
|
|
||||||
|
if __name__ == '__main__':
|
||||||
|
main()
|
||||||
@@ -1,5 +1,5 @@
|
|||||||
import numpy as np
|
import numpy as np
|
||||||
from .IMUBase import IMUBase
|
from src.imu.IMUBase import IMUBase
|
||||||
|
|
||||||
class IMU(IMUBase):
|
class IMU(IMUBase):
|
||||||
def __init__(self) -> None:
|
def __init__(self) -> None:
|
||||||
|
|||||||
@@ -1,10 +1,11 @@
|
|||||||
#!/usr/bin/env python
|
#!/usr/bin/env python
|
||||||
|
|
||||||
import numpy as np
|
import numpy as np
|
||||||
from .LegKinematics import LegIK
|
|
||||||
from .LieAlgebra import RpToTrans, TransToRp, TransInv, RPY, TransformVector
|
|
||||||
from collections import OrderedDict
|
from collections import OrderedDict
|
||||||
|
|
||||||
|
from src.kinematics.LegKinematics import LegIK
|
||||||
|
from src.kinematics.LieAlgebra import RpToTrans, TransToRp, TransInv, RPY
|
||||||
|
|
||||||
|
|
||||||
class SpotModel:
|
class SpotModel:
|
||||||
def __init__(
|
def __init__(
|
||||||
|
|||||||
@@ -0,0 +1,10 @@
|
|||||||
|
class ControllerState:
|
||||||
|
def __init__(self) -> None:
|
||||||
|
self.command = 0
|
||||||
|
self.estop = 0
|
||||||
|
self.lx = 0
|
||||||
|
self.ly = 0
|
||||||
|
self.rx = 0
|
||||||
|
self.ry = 0
|
||||||
|
self.height = 0
|
||||||
|
self.speed = 0
|
||||||
@@ -0,0 +1,18 @@
|
|||||||
|
import threading
|
||||||
|
|
||||||
|
from src.controller_interface.ControllerState import ControllerState
|
||||||
|
|
||||||
|
class SharedState:
|
||||||
|
def __init__(self):
|
||||||
|
self.lock = threading.Lock()
|
||||||
|
self.state = ControllerState()
|
||||||
|
|
||||||
|
def update_state(self, new_state):
|
||||||
|
with self.lock:
|
||||||
|
self.state = new_state
|
||||||
|
|
||||||
|
def get_latest_state(self):
|
||||||
|
with self.lock:
|
||||||
|
return self.state
|
||||||
|
|
||||||
|
shared_state = SharedState()
|
||||||
@@ -0,0 +1,108 @@
|
|||||||
|
import asyncio
|
||||||
|
from enum import Enum
|
||||||
|
import struct
|
||||||
|
import threading
|
||||||
|
import websockets
|
||||||
|
import json
|
||||||
|
|
||||||
|
from src.controller_interface.ControllerState import ControllerState
|
||||||
|
from src.controller_interface.SharedControllerState import shared_state
|
||||||
|
|
||||||
|
clients = {}
|
||||||
|
|
||||||
|
class Command(Enum):
|
||||||
|
ESTOP = 0
|
||||||
|
CONTROLLER = 1
|
||||||
|
|
||||||
|
def get_controller(buffer):
|
||||||
|
buffer = struct.unpack("<8b", buffer)
|
||||||
|
state = ControllerState()
|
||||||
|
state.command = buffer[0]
|
||||||
|
state.estop = buffer[1]
|
||||||
|
state.lx = buffer[2]
|
||||||
|
state.ly = buffer[3]
|
||||||
|
state.rx = buffer[4]
|
||||||
|
state.ry = buffer[5]
|
||||||
|
state.height = buffer[6]
|
||||||
|
state.speed = buffer[7]
|
||||||
|
|
||||||
|
return state
|
||||||
|
|
||||||
|
class WebsocketController:
|
||||||
|
def __init__(self, shared_controller_state=shared_state, host="localhost", port=2096):
|
||||||
|
self.shared_controller_state = shared_controller_state
|
||||||
|
self.host = host
|
||||||
|
self.port = port
|
||||||
|
self.loop = None
|
||||||
|
self.thread = None
|
||||||
|
|
||||||
|
async def handle_message(self, websocket, path):
|
||||||
|
client_id = id(websocket)
|
||||||
|
clients[client_id] = {
|
||||||
|
"websocket": websocket,
|
||||||
|
}
|
||||||
|
print("Got a new connection")
|
||||||
|
try:
|
||||||
|
async for message in websocket:
|
||||||
|
if isinstance(message, bytes):
|
||||||
|
await self.handle_binary_message(clients[client_id], message)
|
||||||
|
else:
|
||||||
|
await self.handle_json_message(clients[client_id], message)
|
||||||
|
finally:
|
||||||
|
del clients[client_id]
|
||||||
|
|
||||||
|
async def handle_binary_message(self, client, data):
|
||||||
|
message = get_controller(data)
|
||||||
|
shared_state.update_state(message)
|
||||||
|
command = Command(message.command)
|
||||||
|
if command == Command.ESTOP:
|
||||||
|
await client["websocket"].send(
|
||||||
|
json.dumps({"type": "stop", "data": "Servos stopped"})
|
||||||
|
)
|
||||||
|
|
||||||
|
if command == Command.CONTROLLER:
|
||||||
|
await client["websocket"].send(json.dumps({"type": "echo", "data": message.__dict__}))
|
||||||
|
|
||||||
|
|
||||||
|
async def handle_json_message(self, client, message):
|
||||||
|
data = json.loads(message)
|
||||||
|
if data["type"] in ("stop", "mode_change"):
|
||||||
|
client["model"][data["type"]] = data.get("data", False)
|
||||||
|
await client["websocket"].send(
|
||||||
|
json.dumps(
|
||||||
|
{"type": data["type"], "data": data.get("data", "Servos stopped")}
|
||||||
|
)
|
||||||
|
)
|
||||||
|
|
||||||
|
def start_server(self):
|
||||||
|
asyncio.set_event_loop(self.loop)
|
||||||
|
start_server = websockets.serve(self.handle_message, self.host, self.port)
|
||||||
|
self.loop.run_until_complete(start_server)
|
||||||
|
self.loop.run_forever()
|
||||||
|
|
||||||
|
def start(self):
|
||||||
|
# Create a new event loop in a new thread
|
||||||
|
if not self.thread or not self.thread.is_alive():
|
||||||
|
self.loop = asyncio.new_event_loop()
|
||||||
|
self.thread = threading.Thread(target=self.start_server)
|
||||||
|
self.thread.start()
|
||||||
|
|
||||||
|
def stop(self):
|
||||||
|
if self.loop:
|
||||||
|
self.loop.call_soon_threadsafe(self.loop.stop)
|
||||||
|
if self.thread and self.thread.is_alive():
|
||||||
|
self.thread.join()
|
||||||
|
|
||||||
|
if __name__ == "__main__":
|
||||||
|
server = WebsocketController("localhost", 2096)
|
||||||
|
server.run()
|
||||||
|
|
||||||
|
try:
|
||||||
|
# Your main thread logic here
|
||||||
|
print("WebSocket server is running...")
|
||||||
|
# Example: Keep the main thread doing something or waiting
|
||||||
|
while True:
|
||||||
|
pass
|
||||||
|
except KeyboardInterrupt:
|
||||||
|
print("Stopping WebSocket server...")
|
||||||
|
server.stop()
|
||||||
@@ -0,0 +1,10 @@
|
|||||||
|
from src.hardware_interface.HardwareInterfaceBase import HardwareInterfaceBase
|
||||||
|
|
||||||
|
|
||||||
|
class HardwareInterface(HardwareInterfaceBase):
|
||||||
|
def __init__(self):
|
||||||
|
super().__init__()
|
||||||
|
|
||||||
|
def set_actuator_positions(self, joint_angles):
|
||||||
|
pass
|
||||||
|
|
||||||
@@ -0,0 +1,11 @@
|
|||||||
|
from abc import abstractmethod
|
||||||
|
|
||||||
|
class HardwareInterfaceBase:
|
||||||
|
def __init__(self):
|
||||||
|
pass
|
||||||
|
|
||||||
|
@abstractmethod
|
||||||
|
def set_actuator_positions(self, joint_angles):
|
||||||
|
raise NotImplementedError
|
||||||
|
|
||||||
|
|
||||||
+19
-16
@@ -9,15 +9,25 @@ from .State import BodyState, GaitState
|
|||||||
sys.path.append("../../..")
|
sys.path.append("../../..")
|
||||||
|
|
||||||
class Spot:
|
class Spot:
|
||||||
def __init__(self, kinematic, IMU=None):
|
def __init__(self, kinematic, camera, imu, hardware_interface, controller_interface):
|
||||||
|
self.kinematic = kinematic
|
||||||
|
self.camera = camera
|
||||||
|
self.imu = imu
|
||||||
|
self.hardware_interface = hardware_interface
|
||||||
|
self.controller_interface = controller_interface
|
||||||
|
|
||||||
self.body_state = BodyState()
|
self.body_state = BodyState()
|
||||||
self.gait_state = GaitState()
|
self.gait_state = GaitState()
|
||||||
self.kinematic = kinematic
|
|
||||||
self.gait_controller = BezierGait()
|
self.gait_controller = BezierGait()
|
||||||
self.IMU = IMU
|
|
||||||
|
|
||||||
self.body_state.worldFeetPositions = copy.deepcopy(self.kinematic.WorldToFoot)
|
self.body_state.worldFeetPositions = copy.deepcopy(self.kinematic.WorldToFoot)
|
||||||
|
|
||||||
|
def start(self):
|
||||||
|
self.controller_interface.start()
|
||||||
|
|
||||||
|
def stop(self):
|
||||||
|
self.controller_interface.stop()
|
||||||
|
|
||||||
def joint_angles(self):
|
def joint_angles(self):
|
||||||
return self.kinematic.IK(
|
return self.kinematic.IK(
|
||||||
self.body_state.rotation,
|
self.body_state.rotation,
|
||||||
@@ -25,27 +35,20 @@ class Spot:
|
|||||||
self.body_state.worldFeetPositions,
|
self.body_state.worldFeetPositions,
|
||||||
)
|
)
|
||||||
|
|
||||||
def run(self, state, command):
|
def run(self, dt=.01):
|
||||||
self.gait_state.update_gait_state(self.dt)
|
self.gait_state.update_gait_state(dt)
|
||||||
self.gui.UserInput(self.body_state, self.gait_state)
|
# self.gui.UserInput(self.body_state, self.gait_state)
|
||||||
self.gait_state.contacts = self.state[-4:]
|
|
||||||
self.body_state.worldFeetPositions = copy.deepcopy(self.spot.WorldToFoot)
|
|
||||||
|
|
||||||
self.bezier_gait.generate_trajectory(self.body_state, self.gait_state, self.dt)
|
self.gait_controller.generate_trajectory(self.body_state, self.gait_state, dt)
|
||||||
|
|
||||||
self.update_environment()
|
self.update_environment()
|
||||||
|
|
||||||
self.state, _, done, _ = self.env.step(self.action)
|
|
||||||
if done:
|
|
||||||
print("DONE")
|
|
||||||
return True
|
|
||||||
|
|
||||||
def update_environment(self):
|
def update_environment(self):
|
||||||
joint_angles = self.spot.IK(
|
joint_angles = self.kinematic.IK(
|
||||||
self.body_state.rotation,
|
self.body_state.rotation,
|
||||||
self.body_state.position,
|
self.body_state.position,
|
||||||
self.body_state.worldFeetPositions,
|
self.body_state.worldFeetPositions,
|
||||||
)
|
)
|
||||||
self.env.pass_joint_angles(joint_angles.reshape(-1))
|
self.hardware_interface.set_actuator_positions(joint_angles.reshape(-1))
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user