adding validated services? patching forcad_local.py

This commit is contained in:
Your Name
2026-08-13 08:32:35 +07:00
parent d485f61169
commit e795614e45
1459 changed files with 420036 additions and 436 deletions

View File

@@ -0,0 +1,27 @@
services:
flysim:
restart: always
build: ./flysim
ports:
- "9000:9001"
depends_on:
- flysim-mongo
environment:
- MONGO_URI=mongodb://flysim-mongo:27017/mydatabase
- LD_LIBRARY_PATH=/root/.codon/lib/codon
# command: gunicorn -k gevent -w 1 --log-level debug -b 0.0.0.0:9001 server:app
command: gunicorn -k gevent -w 1 -b 0.0.0.0:9001 server:app
mem_limit: 2048m
cpu_count: 1
flysim-mongo:
restart: always
image: mongo:latest
volumes:
- mongodb_data:/data/db
mem_limit: 1024m
cpu_count: 1
volumes:
mongodb_data:

View File

@@ -0,0 +1,11 @@
FROM python:3.12.7-bookworm
WORKDIR /app
COPY requirements.txt .
RUN pip install --no-cache-dir -r requirements.txt
COPY install_codon_v0.17.0.sh .
RUN yes y | bash install_codon_v0.17.0.sh
COPY . .

View File

@@ -0,0 +1,31 @@
create drone:
./client create --ip 10.80.1.2
you can also specify label and (or) flight plan
./client create --ip 10.80.1.2 --label asdf --flight-plan "1 BOOSTX [drone] 200\n55 BOOSTY [drone] 400\n"
connect:
./client connect --ip 10.80.1.2 --drone-id="676306da9da3a3b7141f3000" --control-key="c786e71f"
connect with setting position and (or) velocity:
./client connect --ip 10.80.1.2 --drone-id="676306da9da3a3b7141f3000" --control-key="c786e71f" --position 0 0 --velocity 1 2
by default, client keeps connection for 30 seconds. if you need more, add --duration:
./client connect --ip 10.80.1.2 --drone-id="676306da9da3a3b7141f3000" --control-key="c786e71f" --duration 9999
list drones (only ids):
./client list-drones --ip 10.80.1.2
list drones (detailed):
./client list-drones-details --ip 10.80.1.2
list details but only for one drone (by label):
./client list-drones-details --ip 10.80.1.2 --label p4MUiB5oEF5EHAnkMefO8
do not forget to use --help:
./client --help
./client create --help
./client connect --help
./client list-drones --help
./client list-drones-details --help

Binary file not shown.

View File

@@ -0,0 +1,77 @@
import misc
get_var = misc.get_var
set_vars = misc.set_vars
def reinit():
global get_var, set_vars
get_var = misc.get_var
set_vars = misc.set_vars
def BOOSTX(drone, value):
drone["velocity"][0] += value
return f"Drone {drone['_id']} boosted at X axis by {value}!"
def BOOSTY(drone, value):
drone["velocity"][1] += value
return f"Drone {drone['_id']} boosted at Y axis by {value}!"
def FIRE(drone):
return f"Drone {drone['_id']} fired!"
def SETFREQ(drone, frequency):
return f"Drone {drone['_id']} freq set to {frequency}!"
def process_flight_plan(drone, curr_time):
reinit()
flight_plan = get_var(drone["_id"], "flight_plan")
new_log = get_var(drone["_id"], "flight_log")
if not flight_plan:
return
commands = flight_plan.split("\n")
new_commands = ""
for command in commands:
if not command.strip():
continue
parts = command.split()
if len(parts) == 0:
continue
expected_time = int(parts[0])
if not (curr_time > expected_time or abs(curr_time - expected_time) < 0.2):
new_commands += command + "\n"
continue
command_name = parts[1]
args = parts[2:]
func = globals().get(command_name)
if func and callable(func):
try:
typed_args = []
if len(args) > 0 and args[0] == "[drone]":
typed_args.append(drone)
args = args[1:]
typed_args.extend(
[
int(arg) if arg.replace("-", "", 1).isdigit() else arg
for arg in args
]
)
new_log += str(func(*typed_args)) + "\n"
except Exception as e:
print(f"Error calling {command_name}: {e}", flush=True)
else:
print(f"Unknown command: {command_name}", flush=True)
set_vars(drone["_id"], {"flight_plan": new_commands, "flight_log": new_log})

View File

@@ -0,0 +1,65 @@
#!/usr/bin/env bash
set -e
set -o pipefail
CODON_INSTALL_DIR=~/.codon
OS=$(uname -s | awk '{print tolower($0)}')
ARCH=$(uname -m)
if [ "$OS" != "linux" ] && [ "$OS" != "darwin" ]; then
echo "error: Pre-built binaries only exist for Linux and macOS." >&2
exit 1
fi
CODON_BUILD_ARCHIVE=codon-$OS-$ARCH.tar.gz
mkdir -p $CODON_INSTALL_DIR
cd $CODON_INSTALL_DIR
curl -L https://github.com/exaloop/codon/releases/download/v0.17.0/"$CODON_BUILD_ARCHIVE" | tar zxvf - --strip-components=1
EXPORT_COMMAND="export PATH=$(pwd)/bin:\$PATH"
echo "PATH export command:"
echo " $EXPORT_COMMAND"
update_profile () {
if ! grep -F -q "$EXPORT_COMMAND" "$1"; then
read -p "Update PATH in $1? [y/n] " -n 1 -r
echo
if [[ $REPLY =~ ^[Yy]$ ]]; then
echo "Updating $1"
echo >> $1
echo "# Codon compiler path (added by install script)" >> $1
echo $EXPORT_COMMAND >> $1
else
echo "Skipping."
fi
else
echo "PATH already updated in $1; skipping update."
fi
}
if [[ "$SHELL" == *zsh ]]; then
if [ -e ~/.zshenv ]; then
update_profile ~/.zshenv
elif [ -e ~/.zshrc ]; then
update_profile ~/.zshrc
else
echo "Could not find zsh configuration file to update PATH"
fi
elif [[ "$SHELL" == *bash ]]; then
if [ -e ~/.bash_profile ]; then
update_profile ~/.bash_profile
elif [ -e ~/.bash_login ]; then
update_profile ~/.bash_login
elif [ -e ~/.profile ]; then
update_profile ~/.profile
else
echo "Could not find bash configuration file to update PATH"
fi
else
echo "Don't know how to update configuration file for shell $SHELL"
fi
echo "Codon successfully installed at: $(pwd)"
echo "Open a new terminal session or update your PATH to use codon"

View File

@@ -0,0 +1,28 @@
from functools import partial
from bson import ObjectId
def set_vars_(drones_collection, _id, what):
drones_collection.update_one(
{"_id": _id},
{"$set": what},
)
def get_var_(drones_collection, _id, what):
drone = drones_collection.find_one({"_id": ObjectId(_id)})
return drone[what]
def set_vars(*args, **kwargs):
raise RuntimeError("Function not initialized. Call initialize() first")
def get_var(*args, **kwargs):
raise RuntimeError("Function not initialized. Call initialize() first")
def initialize(drones_collection):
global set_vars, get_var
set_vars = partial(set_vars_, drones_collection)
get_var = partial(get_var_, drones_collection)

View File

@@ -0,0 +1,5 @@
Flask==3.0.3
Flask-SocketIO==5.4.1
pymongo==4.10.1
gunicorn==23.0.0
gevent==24.11.1

View File

@@ -0,0 +1,220 @@
from flask import Flask, request, jsonify, abort
from flask_socketio import SocketIO, join_room
from pymongo import MongoClient
from bson import ObjectId
import os
import time
from datetime import datetime, timedelta
import gevent
import json
import session_authenticator
from flight_plans import process_flight_plan
from functools import partial
import misc
from gevent.lock import BoundedSemaphore
db_lock = BoundedSemaphore(1)
app = Flask(__name__)
socketio = SocketIO(app)
client = MongoClient(os.getenv("MONGO_URI"))
db = client["drone_db"]
drones_collection = db["drones"]
misc.initialize(drones_collection)
DRONE_LIFETIME = 8 * 60 # in seconds
@app.route("/create_drone", methods=["POST"])
def create_drone():
with db_lock:
label = request.json.get("label", "NO_LABEL")
secret_data = request.json.get("secret_data", "")
flight_plan = request.json.get("flight_plan", "")
creation_time = datetime.now()
drone = {
"label": label,
"position": [0, 0],
"velocity": [0, 0],
"control_key": "",
"flight_plan": flight_plan,
"flight_log": "",
"secret_data": secret_data,
"created_at": creation_time,
"expires_at": creation_time + timedelta(seconds=DRONE_LIFETIME),
}
result = drones_collection.insert_one(drone)
drone_id = str(result.inserted_id)
control_key = session_authenticator.generate(drone_id, label)
drones_collection.update_one(
{"_id": result.inserted_id}, {"$set": {"control_key": control_key}}
)
return jsonify(
{
"id": drone_id,
"control_key": control_key,
"expires_at": drone["expires_at"].isoformat(),
}
)
def filter_sensitive_data(
data, filtered_fields=["control_key", "secret_data", "flight_plan", "flight_log"]
):
if isinstance(data, list):
return [filter_sensitive_data(item) for item in data]
elif isinstance(data, dict):
return {k: v for k, v in data.items() if k not in filtered_fields}
return data
@app.route("/get_drones", methods=["GET"])
def get_drones():
with db_lock:
with_field = request.args.get("with")
if with_field:
projection = json.loads(with_field)
if len(projection[0]) == 1 and list(projection[0].keys())[0] == "$match":
drones_response = list(drones_collection.aggregate(projection))
drones_response_final = []
for item in drones_response:
new_dict = item.copy()
new_dict["id"] = str(new_dict["_id"])
del new_dict["_id"]
drones_response_final.append(new_dict)
return json.dumps(filter_sensitive_data(drones_response_final), default=str)
return abort(403)
else:
drones_cursor = drones_collection.find({}, {"_id": 1})
return json.dumps(
[{"id": str(drone["_id"])} for drone in drones_cursor], default=str
)
@socketio.on("connect")
def connect():
print("Client connected")
@socketio.on("disconnect")
def disconnect():
print("Client disconnected")
@socketio.on("join_drone")
def join_drone(data):
with db_lock:
drone_id = data["drone_id"]
control_key = data["control_key"]
drone = drones_collection.find_one({"_id": ObjectId(drone_id)})
if drone and drone["control_key"] == control_key:
join_room(drone_id)
socketio.emit(
"drone_connected", {"data": f"Connected to drone {drone_id}"}, room=drone_id
)
else:
socketio.emit("error", {"data": "Invalid drone ID or control key"})
@socketio.on("set_position")
def set_position(data):
with db_lock:
drone_id = data["drone_id"]
position = data["position"]
drones_collection.update_one(
{"_id": ObjectId(drone_id)}, {"$set": {"position": position}}
)
socketio.emit(
"position_updated",
{"id": drone_id, "new_position": position},
room=drone_id,
)
@socketio.on("set_velocity")
def set_velocity(data):
with db_lock:
drone_id = data["drone_id"]
velocity = data["velocity"]
drones_collection.update_one(
{"_id": ObjectId(drone_id)}, {"$set": {"velocity": velocity}}
)
socketio.emit(
"velocity_updated",
{"id": drone_id, "new_velocity": velocity},
room=drone_id,
)
def update_positions():
with db_lock:
current_time = datetime.now()
expired_drones = drones_collection.find({"expires_at": {"$lt": current_time}})
for drone in expired_drones:
drone_id = str(drone["_id"])
drones_collection.delete_one({"_id": drone["_id"]})
socketio.emit("drone_expired", {"id": drone_id}, room=str(drone["_id"]))
active_drones = drones_collection.find({"expires_at": {"$gt": current_time}})
for drone in active_drones:
cur_time = (datetime.now() - drone["created_at"]).total_seconds()
process_flight_plan(drone, cur_time)
new_position = [
drone["position"][0] + drone["velocity"][0],
drone["position"][1] + drone["velocity"][1],
]
new_velocity = drone["velocity"].copy()
for i in range(2):
if abs(new_position[i]) > 100:
new_velocity[i] = -new_velocity[i]
new_position[i] = 100 if new_position[i] > 0 else -100
drones_collection.update_one(
{"_id": drone["_id"]},
{"$set": {"position": new_position, "velocity": new_velocity}},
)
socketio.emit(
"data_updated",
{
"id": str(drone["_id"]),
"label": drone["label"],
"position": new_position,
"velocity": new_velocity,
"cur_time": int(cur_time),
"control_key": drone["control_key"],
"flight_plan": drone["flight_plan"],
"flight_log": drone["flight_log"],
"secret_data": drone["secret_data"],
"created_at": str(drone["created_at"]),
"expires_at": str(drone["expires_at"]),
},
room=str(drone["_id"]),
)
def run_update_positions():
print("start_background_task")
while True:
update_positions()
gevent.sleep(1)
socketio.start_background_task(run_update_positions)
if __name__ == "__main__":
socketio.run(app, host="0.0.0.0", port=9001)