BeamNG / BeamNGpy

Python API for BeamNG.tech
https://beamng.tech
MIT License
258 stars 47 forks source link

Connect to a custom scenario #220

Closed Guaziboi closed 1 year ago

Guaziboi commented 1 year ago

So in the example codes, I'm seeing a new instance of BeamNG being opened and creating a brand new scenario. I was wondering if it's possible to connect to an already running instance of BeamNG or to open a specific custom scenario

aivora-beamng commented 1 year ago

Hi, it is possible to connect to an already running instance of BeamNG. However, that instance must be created with arguments that allow the BeamNGpy library to connect. BeamNGpy spawns the simulator with these command-line arguments:

Bin64\BeamNG.tech.x64.exe -rport 64256 -nosteam -console -physicsfps 4000 -lua registerCoreModule('tech/techCore')

Then, you will be able to connect to the existing simulator with the usual way:

from beamngpy import BeamNGpy

beamng = BeamNGpy('localhost', 64256)
beamng.open()

It is also possible to open a specific custom scenario. You use the path argument of the Scenario class for that. You can load either scenarios created with BeamNGpy before or also the ones already existing in the simulator:

from beamngpy import BeamNGpy, Scenario, ScenarioObject, Vehicle

beamng = BeamNGpy('localhost', 64256)
beamng.open()

scenario = Scenario('italy', 'electricHypermilling', path='/levels/italy/scenarios/electricHypermilling.json')
beamng.scenario.load(scenario)
beamng.scenario.start()

vehicles = scenario.vehicles
ego = next(iter(vehicles.values()))
print(f'Connected: {ego.is_connected()}')
ego.control(throttle=1.0)
Guaziboi commented 1 year ago

That works perfectly, thank you! Now I'm trying to implement this to a script that tracks the user's inputs. Right now, my script works as intended but when I try to make the changes so that it opens up the Hyper Milling scenario I keep getting errors such as "Unresolved attribute reference 'ai' for class 'dict'". This happens for Sensors, ai, state, and control.

Here's my code:

import time
import threading
import matplotlib.pyplot as plt
import numpy as np
import seaborn as sns
import pandas as pd
from beamngpy import BeamNGpy, Scenario, Vehicle, angle_to_quat
from beamngpy.sensors import Electrics
import openpyxl

sns.set()  # Make seaborn set matplotlib styling

# Instantiate a BeamNGpy instance the other classes use for reference & communication
# This is the host & port used to communicate over
# beamng = BeamNGpy('localhost', 64256)
beamng = BeamNGpy('localhost', 64256, home='C:/BeamNG.tech.v0.28.2.0', user='C:/Users/mtith2')
beamng.open()

scenario = Scenario('italy', 'electricHypermilling', path='/levels/italy/scenarios/electricHypermilling.json')

vehicle = scenario.vehicles
electrics = Electrics()
vehicle.sensors.attach('electrics', electrics)

beamng.scenario.load(scenario)
beamng.scenario.start()  # After loading, the simulator waits for further input to actually start

vehicle.ai.set_mode('disabled')

positions = []
directions = []
wheel_speeds = []
throttles = []
brakes = []
steerings = []  # New list to store steering values

vehicle.sensors.poll()
sensors = vehicle.sensors

print('The vehicle position is:')
print(vehicle.state['pos'])

print('The vehicle direction is:')
print(vehicle.state['dir'])

print('The wheel speed is:')
print(sensors['electrics']['wheelspeed'])

print('The throttle intensity is:')
print(sensors['electrics']['throttle'])

print('The brake intensity is:')
print(sensors['electrics']['brake'])

# Function to update vehicle controls based on user input
def update_controls():
    while True:
        user_input = input("Enter throttle, brake, and steering values (separated by space), or 'q' to quit: ")
        if user_input.lower() == 'q':
            # Quit the program
            beamng.close()  # End the scenario and close the connection to the simulator
            return False
        inputs = user_input.split()
        if len(inputs) != 3:
            print("Invalid input. Please enter throttle, brake, and steering values.")
            continue
        try:
            throttle = float(inputs[0])
            brake = float(inputs[1])
            steering = float(inputs[2])
            if throttle < 0 or throttle > 1 or brake < 0 or brake > 1:
                print("Throttle and brake values should be between 0 and 1.")
                continue
            if steering < -1 or steering > 1:
                print("Steering value should be between -1 and 1.")
                continue
            vehicle.control(throttle=throttle, brake=brake, steering=steering)
            break
        except ValueError:
            print("Invalid input. Throttle, brake, and steering values should be numeric.")

# Function to collect sensor data
def collect_data():
    while True:
        time.sleep(0.5)
        vehicle.sensors.poll()  # Polls the data of all sensors attached to the vehicle
        sensors = vehicle.sensors
        positions.append(vehicle.state['pos'])
        directions.append(vehicle.state['dir'])
        wheel_speeds.append(sensors['electrics']['wheelspeed'])
        throttles.append(sensors['electrics']['throttle'])
        brakes.append(sensors['electrics']['brake'])
        steerings.append(sensors['electrics']['steering'])

# Create separate threads for user input and data collection
input_thread = threading.Thread(target=update_controls)
data_thread = threading.Thread(target=collect_data)

# Start the threads
input_thread.start()
data_thread.start()

# Wait for both threads to finish
input_thread.join()
data_thread.join()

beamng.close()  # End the scenario and close the connection to the simulator

# Create a DataFrame using the collected data
data = {
    'Position': positions,
    'Direction': directions,
    'Wheel Speed': wheel_speeds,
    'Throttle': throttles,
    'Brake': brakes,
    'Steering': steerings
}
df = pd.DataFrame(data)

# Save the DataFrame to an Excel file using openpyxl explicitly
with pd.ExcelWriter('vehicle_data.xlsx', engine='openpyxl') as writer:
    df.to_excel(writer, index=False)

plt.plot(throttles, 'b-', label='Throttle')
plt.plot(brakes, 'r-', label='Brake')
plt.plot(steerings, 'g-', label='Steering')
plt.legend()
plt.show()
plt.clf()

x = [p[0] for p in positions]
y = [p[1] for p in positions]
plt.plot(x, y, '.')
plt.axis('square')
plt.show()
plt.clf()

angles = [np.arctan2(d[1], d[0]) for d in directions]
r = wheel_speeds
plt.subplot(111, projection='polar')
plt.scatter(angles, r)
plt.show()
aivora-beamng commented 1 year ago

Hi, scenario.vehicles is a dictionary of all the vehicles found in the scenario. You should change the line vehicle = scenario.vehicles to vehicle = next(iter(scenario.vehicles.values())) and I think the problem you are having should be gone.

Guaziboi commented 1 year ago

Thank you for the quick response. Unfortunately getting an error "StopIteration" at line 33, the warning messages do get resolved though.

aivora-beamng commented 1 year ago

Yes, one other small thing, scenario.vehicles contains the list of vehicles of an existing scenario only after it gets loaded. I tested this code and it seems to behave correctly:

import threading
import time

import matplotlib.pyplot as plt
import numpy as np
import openpyxl
import pandas as pd
import seaborn as sns
from beamngpy import BeamNGpy, Scenario, Vehicle, angle_to_quat
from beamngpy.sensors import Electrics

sns.set()  # Make seaborn set matplotlib styling

# Instantiate a BeamNGpy instance the other classes use for reference & communication
# This is the host & port used to communicate over
# beamng = BeamNGpy('localhost', 64256)
beamng = BeamNGpy('localhost', 64256, home='T:/BeamNG/BeamNG.tech.v0.28.2.0')
beamng.open()

scenario = Scenario('italy', 'electricHypermilling', path='/levels/italy/scenarios/electricHypermilling.json')

beamng.scenario.load(scenario)
beamng.scenario.start()  # After loading, the simulator waits for further input to actually start

vehicle = next(iter(scenario.vehicles.values()))
electrics = Electrics()
vehicle.sensors.attach('electrics', electrics)

vehicle.ai.set_mode('disabled')

positions = []
directions = []
wheel_speeds = []
throttles = []
brakes = []
steerings = []  # New list to store steering values

vehicle.sensors.poll()
sensors = vehicle.sensors

print('The vehicle position is:')
print(vehicle.state['pos'])

print('The vehicle direction is:')
print(vehicle.state['dir'])

print('The wheel speed is:')
print(sensors['electrics']['wheelspeed'])

print('The throttle intensity is:')
print(sensors['electrics']['throttle'])

print('The brake intensity is:')
print(sensors['electrics']['brake'])

# Function to update vehicle controls based on user input
def update_controls():
    while True:
        user_input = input("Enter throttle, brake, and steering values (separated by space), or 'q' to quit: ")
        if user_input.lower() == 'q':
            # Quit the program
            beamng.close()  # End the scenario and close the connection to the simulator
            return False
        inputs = user_input.split()
        if len(inputs) != 3:
            print("Invalid input. Please enter throttle, brake, and steering values.")
            continue
        try:
            throttle = float(inputs[0])
            brake = float(inputs[1])
            steering = float(inputs[2])
            if throttle < 0 or throttle > 1 or brake < 0 or brake > 1:
                print("Throttle and brake values should be between 0 and 1.")
                continue
            if steering < -1 or steering > 1:
                print("Steering value should be between -1 and 1.")
                continue
            vehicle.control(throttle=throttle, brake=brake, steering=steering)
            break
        except ValueError:
            print("Invalid input. Throttle, brake, and steering values should be numeric.")

# Function to collect sensor data
def collect_data():
    while True:
        time.sleep(0.5)
        vehicle.sensors.poll()  # Polls the data of all sensors attached to the vehicle
        sensors = vehicle.sensors
        positions.append(vehicle.state['pos'])
        directions.append(vehicle.state['dir'])
        wheel_speeds.append(sensors['electrics']['wheelspeed'])
        throttles.append(sensors['electrics']['throttle'])
        brakes.append(sensors['electrics']['brake'])
        steerings.append(sensors['electrics']['steering'])

# Create separate threads for user input and data collection
input_thread = threading.Thread(target=update_controls)
data_thread = threading.Thread(target=collect_data)

# Start the threads
input_thread.start()
data_thread.start()

# Wait for both threads to finish
input_thread.join()
data_thread.join()

beamng.close()  # End the scenario and close the connection to the simulator

# Create a DataFrame using the collected data
data = {
    'Position': positions,
    'Direction': directions,
    'Wheel Speed': wheel_speeds,
    'Throttle': throttles,
    'Brake': brakes,
    'Steering': steerings
}
df = pd.DataFrame(data)

# Save the DataFrame to an Excel file using openpyxl explicitly
with pd.ExcelWriter('vehicle_data.xlsx', engine='openpyxl') as writer:
    df.to_excel(writer, index=False)

plt.plot(throttles, 'b-', label='Throttle')
plt.plot(brakes, 'r-', label='Brake')
plt.plot(steerings, 'g-', label='Steering')
plt.legend()
plt.show()
plt.clf()

x = [p[0] for p in positions]
y = [p[1] for p in positions]
plt.plot(x, y, '.')
plt.axis('square')
plt.show()
plt.clf()

angles = [np.arctan2(d[1], d[0]) for d in directions]
r = wheel_speeds
plt.subplot(111, projection='polar')
plt.scatter(angles, r)
plt.show()
Guaziboi commented 1 year ago

This works perfectly, thank you!