Skip to content

Commit f2cf4df

Browse files
author
zhoufeiy
committed
Add files.
1 parent 178566c commit f2cf4df

11 files changed

+272
-0
lines changed

.gitmodules

+3
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
[submodule "blossom-public"]
2+
path = blossom-public
3+
url = https://github.com/interaction-lab/blossom-public

README.md

+21
Original file line numberDiff line numberDiff line change
@@ -1,2 +1,23 @@
11
# Blossom-Controller
22
Blossom controller scripts that contain a interface to control blossom robot either via USB or via network.
3+
4+
## TLDR;
5+
- Setup `.env` file for server IP and port.
6+
- Import `BlossomInterface` from `blossom_interface.py` and implement your logic.
7+
- Run `blossom_client.py` on the machine that is connected to blossom via USB.
8+
- Run `blossom_server.py` on the server.
9+
- Run your script.
10+
11+
## Notes:
12+
- `blossom_interface.py`: Entry point of the interface, import this file to use the interface.
13+
- `blossom_client.py`: Client class for the interface. Run this script on the machine connects to Blossom.
14+
- `blossom_server.py`: Server class for the interface. Run this script on cloud server.
15+
- `blossom_local_interface.py`: Wrapper class for the Blossom API, used in `blossom_interface.py`.
16+
- `blossom_network_interface.py`: This class sends data to server, used in `blossom_interface.py`.
17+
- `blossom_sequence_comb.py`: Utility class for combining sequences. See comments in the file for more details.
18+
- `note.py`: Initially used as `__init__.py` for the package, but because of the blossom-public package, it is better to
19+
keep it as a separate file.
20+
- `.env`: Server IP and port goes in here.
21+
- ```
22+
SERVER_IP=192.168.0.1
23+
SERVER_PORT=5000

blossom-public

Submodule blossom-public added at 24b345f

blossom_client.py

+62
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,62 @@
1+
# Description: This is the client script that runs on the machine connected to Blossom via USB.
2+
# This is only needed if you want to use network controller.
3+
# This is the standalone script that runs on the machine connected to Blossom via USB.
4+
blossom_id_ = 0
5+
# Define blossom ID above
6+
7+
import socketio
8+
import os
9+
import json
10+
import dotenv
11+
import logging
12+
from blossom_local_interface import BlossomLocalInterface
13+
14+
logger = logging.getLogger(__name__)
15+
logger.info("Client Started.")
16+
17+
example_data = {
18+
"function": "do_start_sequence",
19+
"kwargs": {
20+
"blossom_id": 0,
21+
"delay_time": 0.5,
22+
"audio_length": 20,
23+
"seq": "reset"
24+
}
25+
}
26+
27+
28+
class BlossomClient:
29+
def __init__(self, server_ip, server_port, blossom_id=0):
30+
self.bl = BlossomLocalInterface()
31+
self.blossom_id = blossom_id
32+
self.sio = socketio.Client()
33+
self.sio.connect(f"http://{server_ip}:{server_port}")
34+
self.sio.on('data_update', self.on_data_update)
35+
self.sio.on('connect', self.on_connect)
36+
self.sio.on('disconnect', self.on_disconnect)
37+
# self.sio.wait()
38+
39+
def on_data_update(self, data):
40+
logger.info(f"Received data: {json.dumps(data, indent=2)}")
41+
if data["kwargs"]["blossom_id"] == self.blossom_id:
42+
if data["function"] == "do_sequence":
43+
self.bl.do_sequence(data["kwargs"]["seq"], data["kwargs"]["delay_time"])
44+
elif data["function"] == "reset":
45+
self.bl.reset()
46+
else:
47+
logger.error(f"Function {data['function']} not found.")
48+
49+
def wait(self):
50+
self.sio.wait()
51+
52+
def on_connect(self):
53+
logger.info("Connected to server.")
54+
55+
def on_disconnect(self):
56+
logger.info("Disconnected from server.")
57+
58+
59+
if __name__ == '__main__':
60+
dotenv.load_dotenv()
61+
client = BlossomClient(os.getenv("SERVER_IP"), os.getenv("SERVER_PORT"), blossom_id_)
62+
client.wait()

blossom_interface.py

+20
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,20 @@
1+
# Description: This file is the main interface to the Blossom robot.
2+
# Note: Set use_network_interface to False if you want to control blossom from the same machine.
3+
# Import this file into your project
4+
from blossom_network_interface import BlossomNetworkInterface
5+
from blossom_local_interface import BlossomLocalInterface
6+
7+
8+
class BlossomInterface:
9+
def __init__(self, use_network_interface=True, server_ip=None, server_port=None):
10+
self.bli = None
11+
if use_network_interface:
12+
self.bli = BlossomNetworkInterface(server_ip, server_port)
13+
else:
14+
self.bli = BlossomLocalInterface()
15+
16+
def do_sequence(self, seq, delay_time=0, blossom_id=0):
17+
self.bli.do_sequence(seq, delay_time, blossom_id)
18+
19+
def reset(self, blossom_id=0):
20+
self.bli.reset(blossom_id)

blossom_local_interface.py

+29
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,29 @@
1+
# Description: This file is used to control Blossom robot using the Blossom API.
2+
# This is not a standalone script. Use BlossomInterface instead.
3+
import random
4+
import sys
5+
import time
6+
import logging
7+
8+
sys.path.append("./blossom-public")
9+
from blossompy import Blossom
10+
11+
logger = logging.getLogger(__name__)
12+
13+
14+
class BlossomLocalInterface:
15+
def __init__(self):
16+
self.bl = Blossom(sequence_dir='./blossom-public/blossompy/src/sequences')
17+
self.bl.connect() # safe init and connects to blossom and puts blossom in reset position
18+
self.bl.load_sequences()
19+
self.bl.do_sequence("reset")
20+
logger.info("Blossom Connected & Initialized.")
21+
22+
def reset(self, blossom_id=0):
23+
self.bl.do_sequence("reset")
24+
25+
def do_sequence(self, seq="reset", delay_time=0, blossom_id=0):
26+
logger.info(f"Blossom start playing sequence {seq} with {delay_time}s of delay.")
27+
time.sleep(delay_time)
28+
logger.info(f"Blossom playing sequence {seq}")
29+
self.bl.do_sequence(seq)

blossom_network_interface.py

+44
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,44 @@
1+
# Description: This file contains the BlossomNetworkInterface class which is used to send data to the server.
2+
# This is not a standalone script. Use BlossomInterface instead.
3+
import logging
4+
import requests
5+
import json
6+
7+
logger = logging.getLogger(__name__)
8+
9+
10+
class BlossomNetworkInterface:
11+
def __init__(self, server_ip, server_port):
12+
# dotenv.load_dotenv()
13+
# self.server_ip = os.getenv("SERVER_IP")
14+
# self.server_port = os.getenv("SERVER_PORT")
15+
self.server_ip = server_ip
16+
self.server_port = server_port
17+
# self.blossom_id = blossom_id
18+
19+
self.url = f"http://{self.server_ip}:{self.server_port}/data"
20+
self.data = {
21+
"function": "do_start_sequence",
22+
"kwargs": {
23+
"blossom_id": 0,
24+
"delay_time": 0.5,
25+
"audio_length": 20,
26+
"seq": "reset"
27+
}
28+
}
29+
30+
def do_sequence(self, seq, delay_time=0, blossom_id=0):
31+
self.data["function"] = "do_sequence"
32+
self.data["kwargs"]["seq"] = seq
33+
self.data["kwargs"]["delay_time"] = delay_time
34+
self.data["kwargs"]["blossom_id"] = blossom_id
35+
response = requests.post(self.url, json=self.data)
36+
logger.info(f"Sending data to server: {json.dumps(self.data, indent=2)}")
37+
logger.info(f"Response from server: {response.text}")
38+
39+
def reset(self, blossom_id=0):
40+
self.data["function"] = "reset"
41+
self.data["kwargs"]["blossom_id"] = blossom_id
42+
response = requests.post(self.url, json=self.data)
43+
logger.info(f"Sending data to server: {json.dumps(self.data, indent=2)}")
44+
logger.info(f"Response from server: {response.text}")

blossom_sequence_comb.py

+34
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,34 @@
1+
# Description: This script is used to combine multiple sequences into a single sequence.
2+
# Usage: combine_sequences(list_of_sequences, new_sequence_name)
3+
# Example: combine_sequences(["yes", "no"], "yes_no")
4+
# Note: change output sequence directory to the desired directory, but make sure new directory is a sub directory of
5+
# the original sequence directory
6+
import json
7+
8+
sequence_dir = "./blossom-public/blossompy/src/sequences/woody/"
9+
10+
output_sequence_dir = "./blossom-public/blossompy/src/sequences/woody/cognitive/"
11+
12+
13+
def combine_sequences(sequences: list, sequence_name=None):
14+
output_sequence = {
15+
"animation": "",
16+
"frame_list": []
17+
}
18+
last_sequence_duration = 0.0
19+
for seq in sequences:
20+
with open(sequence_dir + seq + "_sequence.json") as seq_file:
21+
sequence_data = json.load(seq_file)
22+
current_frame_list = sequence_data["frame_list"]
23+
for frame in current_frame_list:
24+
frame["millis"] += last_sequence_duration
25+
last_sequence_duration = current_frame_list[-1]["millis"]
26+
output_sequence["frame_list"].extend(current_frame_list)
27+
output_sequence["animation"] += sequence_data["animation"]
28+
if sequence_name is None:
29+
with open(output_sequence_dir + output_sequence["animation"] + "_sequence.json", "w") as outfile:
30+
json.dump(output_sequence, outfile, indent=2)
31+
else:
32+
with open(output_sequence_dir + sequence_name + "_sequence.json", "w") as outfile:
33+
output_sequence["animation"] = sequence_name
34+
json.dump(output_sequence, outfile, indent=2)

blossom_server.py

+18
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,18 @@
1+
# Description: This is the server script that will receive data from network controller and send it to client.
2+
from flask import Flask, request
3+
from flask_socketio import SocketIO, emit
4+
5+
app = Flask(__name__)
6+
socketio = SocketIO(app)
7+
8+
9+
@app.route('/data', methods=['POST'])
10+
def receive_data():
11+
data = request.json
12+
print(f"Received data: {data}")
13+
socketio.emit('data_update', data)
14+
return "Data received", 200
15+
16+
17+
if __name__ == '__main__':
18+
socketio.run(app, host='0.0.0.0', port=5000)

example.py

+30
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,30 @@
1+
import os
2+
from time import sleep
3+
4+
import dotenv
5+
from blossom_interface import BlossomInterface
6+
7+
import random
8+
9+
if __name__ == '__main__':
10+
dotenv.load_dotenv()
11+
bl = BlossomInterface(False, os.getenv("SERVER_IP"), os.getenv("SERVER_PORT"))
12+
13+
# Control blossom with bl object
14+
bl.reset()
15+
bl.do_sequence("yes", 0, 1)
16+
bl.do_sequence("no", 0, 0)
17+
sleep(1)
18+
bl.do_sequence("grand/grand1", 1.0, 0)
19+
bl.do_sequence("grand/grand2", 1.0, 1)
20+
sleep(5)
21+
22+
# More complex behavior
23+
print("Random selection of idle sequences")
24+
idle_sequences = ["breathing/exhale", "breathing/inhale", "fear/fear_startled", "happy/happy_lookingup",
25+
"sad/sad_downcast"]
26+
random.shuffle(idle_sequences)
27+
bl.do_sequence(idle_sequences[0], 0, 0)
28+
random.shuffle(idle_sequences)
29+
bl.do_sequence(idle_sequences[0], 0, 1)
30+
input()

note.py

+10
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,10 @@
1+
from .blossom_interface import BlossomInterface
2+
import logging
3+
4+
__version__ = '1.0.0'
5+
__description__ = 'A interface to control blossom via network or local usb.'
6+
7+
logging.basicConfig(level=logging.INFO)
8+
logger = logging.getLogger(__name__)
9+
10+
__all__ = [BlossomInterface]

0 commit comments

Comments
 (0)