Skip to content

Commit

Permalink
Merge pull request #710 from NextCenturyCorporation/MCS-1815-MCS-1930
Browse files Browse the repository at this point in the history
MCS-1815 MCS-1930 Webenabled Updates: Production-Ready Server and Configurable Command Line Settings
  • Loading branch information
ThomasSchellenbergNextCentury authored Dec 13, 2023
2 parents 9fbbdad + 876e86a commit e2f3f0e
Show file tree
Hide file tree
Showing 9 changed files with 780 additions and 120 deletions.
750 changes: 679 additions & 71 deletions docs/source/schema.rst

Large diffs are not rendered by default.

2 changes: 1 addition & 1 deletion integration_tests/data/107.lava.oracle.outputs.json
Original file line number Diff line number Diff line change
Expand Up @@ -166,7 +166,7 @@
],
"objects_count": 0,
"position_x": 0.0,
"position_z": 0.5,
"position_z": 0.51,
"return_status": "SUCCESSFUL",
"reward": -100.005,
"rotation_y": 0.0,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -76,7 +76,7 @@
"lava": [[-0.5, 0.5, 0.5, 1.5], [0.5, -0.5, 1.5, 0.5]],
"objects_count": 0,
"position_x": 0.0,
"position_z": 0.5,
"position_z": 0.51,
"return_status": "SUCCESSFUL",
"reward": -100.005,
"rotation_y": 0.0,
Expand Down
7 changes: 2 additions & 5 deletions webenabled/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -4,13 +4,10 @@

First, "git clone" this repository. Then run the following commands from this folder to setup your python environment:

_(TODO: Remove the 4th line once 0.7.0 is released)_

```
python3 -m venv --prompt webenabled venv
source venv/bin/activate
python -m pip install --upgrade pip setuptools wheel
python -m pip install -e ../
python -m pip install -r requirements.txt
```

Expand All @@ -29,9 +26,9 @@ scene files will appear on the web page

First, run `cache_addressables` to cache all addressable assets and prevent possible timeout issues. You should do this each time you reboot your machine.

For development, run `python mcsweb.py` to start the flask server with host `0.0.0.0` (so the page will be accessable from any machine on the network) and port `8080`.
Then, run `python mcsweb.py` to start the flask server with host `0.0.0.0` (so the page will be accessable from any machine on the network) and port `8080`.

For production, use a WSGI server (not sure what to put here....)
Alternatively, run `python mcsweb.py` to see all of the available options. The `--debug` flag is very helpful for development.

## Use

Expand Down
23 changes: 14 additions & 9 deletions webenabled/mcs_interface.py
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
import datetime
import glob
import json
import logging
import os
import sys
import time
Expand Down Expand Up @@ -35,9 +36,9 @@
def convert_key_to_action(key: str, logger):
for action in mcs.Action:
if key.lower() == action.key:
logger.info(f"Converting '{key}' into {action.value}")
logger.debug(f"Converting '{key}' into {action.value}")
return action.value
logger.info(f"Unable to convert '{key}'. Returning Pass...")
logger.debug(f"Unable to convert '{key}'. Returning Pass...")
return "Pass"


Expand All @@ -52,7 +53,7 @@ class MCSInterface:

def __init__(self, user: str):
self.logger = current_app.logger
self.logger.info(f'MCS interface directory: {TMP_DIR_FULL_PATH}')
self.logger.debug(f'MCS interface directory: {TMP_DIR_FULL_PATH}')
self.step_number = 0
self.scene_id = None
self.scene_filename = None
Expand Down Expand Up @@ -91,7 +92,11 @@ def get_latest_step_output(self):
def start_mcs(self):
# Start the unity controller. (the function is in a different
# file so we can pickle / store MCSInterface in the session)
self.pid = start_subprocess(self.command_out_dir, self.step_output_dir)
self.pid = start_subprocess(
self.command_out_dir,
self.step_output_dir,
self.logger.isEnabledFor(logging.DEBUG)
)

# Read in the image
images, _ = self.get_images_and_step_output(startup=True)
Expand Down Expand Up @@ -218,7 +223,7 @@ def get_images_and_step_output(self, startup=False, init_scene=False):
timenow = time.time()
elapsed = (timenow - timestart)
if (startup and elapsed > UNITY_STARTUP_WAIT_TIMEOUT):
self.logger.info(
self.logger.debug(
"Display blank image on default when starting up.")
self.img_name = self.blank_path
return [self.img_name], self.step_output
Expand All @@ -230,7 +235,7 @@ def get_images_and_step_output(self, startup=False, init_scene=False):
if (quick_error_check):
log_message = "Error returned from MCS controller."

self.logger.info(log_message)
self.logger.warn(log_message)

list_of_error_files = glob.glob(
self.step_output_dir + "/error_*.json")
Expand Down Expand Up @@ -363,18 +368,18 @@ def get_goal_info(self, scene_filename):

def get_task_desc(self, scene_filename):
"""Get task description based on filename from mcs_task_desc.py"""
self.logger.info(
self.logger.debug(
f"Attempt to get task description based"
f"on scene_filename: {scene_filename}")
scene_type = scene_filename.split('/')[-1].split('0')[0][:-1].upper()

for description in TaskDescription:
if (description.name == scene_type):
self.logger.info(
self.logger.debug(
f"Scene type identified: {description.name}")
return description.value

self.logger.info("Scene type not found, returning 'N/A'")
self.logger.warn("Scene type not found, returning 'N/A'")
return "N/A"

def get_controller_pid(self):
Expand Down
78 changes: 60 additions & 18 deletions webenabled/mcsweb.py
Original file line number Diff line number Diff line change
@@ -1,9 +1,11 @@
import argparse
import logging
import random
import string

import psutil
import typeguard
import waitress


# Override the typechecked decorator used in machine_common_sense to do nothing
Expand Down Expand Up @@ -74,22 +76,22 @@ def get_mcs_interface(request, label, on_exit=False):

# If old user, get stored mcs interface
if uniq_id_str is not None:
app.logger.info(f"{label}: existing user: {uniq_id_str}")
app.logger.debug(f"{label}: existing user: {uniq_id_str}")
mcs_interface = session.get(uniq_id_str)
if mcs_interface is not None:
controller_alive = mcs_interface.is_controller_alive()
if controller_alive:
return mcs_interface, uniq_id_str
app.logger.info("MCS controller is unavailable")
app.logger.debug("MCS controller is unavailable")
else:
app.logger.info("MCS interface is unavailable")
app.logger.debug("MCS interface is unavailable")

# skip for exit_unity route, since in that case, we don't
# need to start a new interface/controller if one isn't found
if (on_exit is False):
letters = string.ascii_lowercase
uniq_id_str = ''.join(random.choice(letters) for i in range(10))
app.logger.info(f"{label}: new user: {uniq_id_str}")
app.logger.debug(f"{label}: new user: {uniq_id_str}")

# Don't recognize, create new mcs interface
mcs_interface = MCSInterface(uniq_id_str)
Expand All @@ -101,8 +103,8 @@ def get_mcs_interface(request, label, on_exit=False):

@app.route('/mcs')
def show_mcs_page():
app.logger.info("=" * 30)
app.logger.info(
app.logger.debug("=" * 30)
app.logger.debug(
"Initialize page before checking for "
"controller and existing user session...")
rendered_template = render_template(
Expand All @@ -116,7 +118,7 @@ def show_mcs_page():

@app.route('/load_controller', methods=["POST"])
def handle_load_controller():
app.logger.info("=" * 30)
app.logger.debug("=" * 30)
mcs_interface, uniq_id_str = get_mcs_interface(request, "Load page")
if mcs_interface is None:
app.logger.warn("Cannot load MCS interface")
Expand All @@ -135,7 +137,7 @@ def handle_load_controller():

@app.route("/keypress", methods=["POST"])
def handle_keypress():
app.logger.info("=" * 30)
app.logger.debug("=" * 30)
mcs_interface, _ = get_mcs_interface(request, "Key press")
if mcs_interface is None:
app.logger.warn("Cannot load MCS interface")
Expand All @@ -151,12 +153,12 @@ def handle_keypress():
img = images[0]
step_number = mcs_interface.step_number
if key:
app.logger.info(
app.logger.debug(
f"Key press: '{key}', action string: {action_string}, "
f"step: {step_number}, img: {img}, output: {step_output}"
)
else:
app.logger.info(
app.logger.debug(
f"Action: '{action}', "
f"step: {step_number}, img: {img}, output: {step_output}"
)
Expand All @@ -172,7 +174,7 @@ def handle_keypress():

@app.route("/exit_unity", methods=["POST"])
def exit_unity():
app.logger.info("=" * 30)
app.logger.debug("=" * 30)
mcs_interface, unique_id = get_mcs_interface(
request, "Exit Unity", on_exit=True)
if mcs_interface is None:
Expand All @@ -188,26 +190,26 @@ def exit_unity():

controller_pid = mcs_interface.get_controller_pid()

app.logger.info(
app.logger.debug(
"Attempting to clean up processes after browser has been closed.")

for p in psutil.process_iter(['pid']):
if p.info['pid'] == controller_pid:
children = p.children(recursive=True)
for c_process in children:
app.logger.info(
app.logger.debug(
f"Found child process of controller: {c_process}, "
f"will attempt to end.")
c_process.kill()

app.logger.info(
app.logger.debug(
f"Found controller process: {p}, will attempt to end.")
p.kill()

if (unique_id is None):
unique_id = request.cookies.get("uniq_id")

app.logger.info(
app.logger.debug(
f"Clear user session for: {unique_id}")
del session[unique_id]

Expand All @@ -222,7 +224,7 @@ def exit_unity():

@app.route("/scene_selection", methods=["POST"])
def handle_scene_selection():
app.logger.info("=" * 30)
app.logger.debug("=" * 30)
mcs_interface, _ = get_mcs_interface(request, "Start scene")
if mcs_interface is None:
app.logger.warn("Cannot load MCS interface")
Expand All @@ -233,7 +235,7 @@ def handle_scene_selection():
load_output = mcs_interface.load_scene("scenes/" + scene_filename)
images, step_output, action_list, goal_info, task_desc = load_output
img = convert_image_path(images[0])
app.logger.info(f"Start scene: {scene_filename}, output: {img}")
app.logger.debug(f"Start scene: {scene_filename}, output: {img}")
resp = jsonify(
last_action="Initialize",
action_list=action_list,
Expand All @@ -249,4 +251,44 @@ def handle_scene_selection():


if __name__ == "__main__":
app.run(host='0.0.0.0', port=8080, debug=True)
parser = argparse.ArgumentParser(description=(
'Machine Common Sense Web Interface'
))
parser.add_argument(
'--host',
type=str,
default='0.0.0.0',
help='Host'
)
parser.add_argument(
'--port',
type=int,
default=8080,
help='Port'
)
parser.add_argument(
'--dev',
default=False,
action='store_true',
help='Development server'
)
parser.add_argument(
'--debug',
default=False,
action='store_true',
help='Debug logging'
)
args = parser.parse_args()
app.logger.info(
f'Starting MCS web interface: host={args.host} port={args.port} '
f'dev={args.dev} debug={args.debug}'
)

if args.dev:
app.run(host=args.host, port=args.port, debug=True)
else:
waitress.serve(app, host=args.host, port=args.port)
waitress_logger = logging.getLogger('waitress')
waitress_logger.setLevel(logging.DEBUG if args.debug else logging.INFO)

app.logger.setLevel(logging.DEBUG if args.debug else logging.INFO)
1 change: 1 addition & 0 deletions webenabled/requirements.txt
Original file line number Diff line number Diff line change
Expand Up @@ -8,3 +8,4 @@ isort==5.11.5; python_version<"3.8"
isort==5.12.0; python_version>="3.8"
psutil==5.9.5
pytest==7.3.0
waitress==2.1.2
Loading

0 comments on commit e2f3f0e

Please sign in to comment.