plugin - OscarMCY/bluesky GitHub Wiki
BlueSky plugins
The easiest way to extend BlueSky is to create a self-contained plugin. In BlueSky, a plugin can:
扩展BlueSky的最简单方法是创建一个独立的插件。在BlueSky,一个插件可以。
- Add new commands to the command stack, in addition to the standard commands. 除了标准命令外,在命令栈中添加新的命令。
- Define new per-aircraft states. 定义新的每架飞机的状态。
- Define functions that are periodically called by the simulation. 定义模拟程序周期性调用的函数。
- Re-implement parts of BlueSky by subclassing BlueSky classes. 通过子类化BlueSky类来重新实现BlueSky的部分功能。
Inside a plugin you have direct access to the Traffic, Simulation, Screen, and Navdb simulation objects. In a plugin, you can also import the stack module to interact with the simulation by stacking commands, import settings to get access to application-wide settings (and add your own, plugin-specific settings), and import tools to get access to BlueSky tools such as aerodynamic functions, geographic functions, datalogging, networking, and more. The example below is also available in the plugins
folder in the BlueSky distribution, and here.
在一个插件中,你可以直接访问交通、模拟、屏幕和Navdb模拟对象。在一个插件中,你还可以导入堆栈模块,通过堆栈命令与模拟进行交互,导入设置以获得对整个应用程序的设置(并添加你自己的、针对插件的设置),导入工具以获得对BlueSky工具的访问,如空气动力学功能、地理功能、数据记录、网络等。下面的例子也可以在BlueSky发行的插件文件夹中找到,还有这里。
Using existing plugins
To be able to use a plugin in BlueSky, put it in your plugins
directory (in the bluesky root folder when you run BlueSky from source, or in $home/bluesky/plugins
when you run a packaged version of BlueSky). On startup, BlueSky automatically detects the available plugins in this folder. You can enable a plugin from the BlueSky command line using the PLUGINS command. If you want to automatically load particular plugins on startup add the following to your bluesky settings file (settings.cfg, situated either in the bluesky root folder when you run from source, or in $home/bluesky
):
为了能够在BlueSky中使用一个插件,请把它放在你的插件目录下(当你从源码运行BlueSky时,在bluesky根目录下,或者当你运行BlueSky的打包版本时,在$home/bluesky/plugins)。启动时,BlueSky会自动检测此文件夹中的可用插件。你可以在BlueSky命令行中使用PLUGINS命令启用一个插件。如果你想在启动时自动加载特定的插件,请在你的bluesky设置文件(settings.cfg,位于从源码运行时的bluesky根目录下,或位于$home/bluesky)中添加以下内容。
enabled_plugins = ['plugin1', 'plugin2', ..., 'pluginn']
Here, 'pluginn'
is the name you give your plugin. These names are not case-sensitive.
Creating plugins 创建插件
BlueSky plugins are basically plain python modules, which have at least an init_plugin()
function that, in addition to your own plugin initialization, defines and returns two specific plugin configuration dicts. In addition, BlueSky can use the docstring of the module as a description text of the plugin in the BlueSky interface. Together with any other imports you may have, the first couple of lines of your plugin will look like this:
蓝天公司的插件基本上是普通的python模块,它至少有一个init_plugin()函数,除了你自己的插件初始化外,还定义并返回两个特定的插件配置dict。此外,BlueSky可以使用模块的docstring作为BlueSky界面中插件的描述文本。加上你可能有的任何其他导入,你的插件的前几行将看起来像这样。
""" BlueSky plugin template. The text you put here will be visible
in BlueSky as the description of your plugin. """
# Import the global bluesky objects. Uncomment the ones you need
from bluesky import core, stack, traf #, settings, navdb, sim, scr, tools
Here, the docstring should be used to describe your plugin. From BlueSky, you can import the navdb, traf, and sim objects as inputs, [stack]] and [scr as outputs. You can import the settings module to get access to the global BlueSky settings, and to specify default values for configuration parameters of your plugin that you want stored in the central settings.cfg
file. The tools module provides access to BlueSky functions and constants for aerodynamic calculations, geographic calculations, network access, and data logging.
这里,应该用docstring来描述你的插件。从BlueSky,你可以导入navdb,traf和sim对象作为输入,stack和scr作为输出。你可以导入设置模块来获取BlueSky的全局设置,并为你的插件的配置参数指定默认值,你希望这些参数存储在中央settings.cfg文件中。工具模块提供了访问BlueSky的功能和常数,用于空气动力学计算、地理计算、网络访问和数据记录。
Initialization of your plugin 插件初始化
BlueSky recognises a python script as a plugin if it contains an init_plugin()
funtion, which defines a configuration dict and a stack function dict, and returns those two when it is called.
BlueSky将一个包含init_plugin()函数的python脚本识别为插件,该函数定义了一个配置dict和一个堆栈函数dict,并在调用时返回这两个函数。
### Initialization function of your plugin. Do not change the name of this
### function, as it is the way BlueSky recognises this file as a plugin.
def init_plugin():
# Addtional initilisation code
The configuration dict specified in the init function is used to define the basic properties of the module, and to connect its update function(s). In this dict, plugin_name
and plugin_type
specify the name and type of the module, respectively. The plugin name can be anything, but is not case sensitive. The plugin type for now is limited to 'sim', but may in the future also include 'gui'.
init函数中指定的配置dict用于定义模块的基本属性,并连接其更新函数。在这个dict中,plugin_name和plugin_type分别指定了模块的名称和类型。插件的名字可以是任何东西,但不区分大小写。插件类型目前只限于 "sim",但将来也可能包括 "gui"。
# Configuration parameters
config = {
# The name of your plugin
'plugin_name': 'EXAMPLE',
# The type of this plugin.
'plugin_type': 'sim'
}
The init_plugin()
ends by returning the configuration dict:
init_plugin()以返回配置dict结束。
# init_plugin() should always return the config dict.
return config
After the init_plugin()
you can define the actual implementation of your plugin. Although you can make a plugin with only module-level functions, the most powerful way to create new implementations in BlueSky is by creating a new Entity: an object that is only created once (a singleton, like, e.g., the built-in Traffic object, the Simulation object, ...). You can specify its implementation in two ways:
在init_plugin()之后,你可以定义你的插件的实际实现。虽然你可以只用模块级的函数来制作一个插件,但在BlueSky中创建新的实现的最强大的方式是创建一个新的实体:一个只创建一次的对象(单子,例如,内置的交通对象,模拟对象等)。你可以通过两种方式指定其实现。
- By deriving from
core.Entity
. You do this when you are implementing new functionality for BlueSky.通过派生自core.Entity。当你为BlueSky实现新的功能时,你会这样做。 - By subclassing an existing implementation in BlueSky. You do this when you want to replace some functionality in BlueSky. (A good example is creating a new Conflict Resolution algorithm, by deriving from
bluesky.traffic.asas.ConflictResolution
. See the plugins/asas folder for examples).通过子类化BlueSky中的现有实现。当你想替换BlueSky中的一些功能时,你可以这样做。(一个很好的例子是通过派生自 bluesky.traffic.asas.ConflictResolution 创建一个新的冲突解决算法。参见插件/asas文件夹中的例子)。
In this example we will implement new functionality, by inheriting from core.Entity
. We will create a class that introduces a new state to each aircraft, storing the number of passengers on board.
在这个例子中,我们将通过继承 core.Entity 来实现新功能。我们将创建一个类,为每架飞机引入一个新的状态,存储飞机上的乘客数量。
class Example(core.Entity):
''' Example new entity object for BlueSky. '''
def __init__(self):
super().__init__()
Adding new traffic states in your plugin 在你的插件中添加新的交通状态
In the constructor of your entity you can introduce new per-aircraft states using the settrafarrays()
method:
在你的实体的构造函数中,你可以使用settrafarrays()方法引入新的每架飞机的状态。
# All classes deriving from Entity can register lists and numpy arrays
# that hold per-aircraft data. This way, their size is automatically
# updated when aircraft are created or deleted in the simulation.
with self.settrafarrays():
self.npassengers = np.array([])
In the constructor, we can use Entities settrafarrays()
to inform BlueSky that we are specifying one or more new states for each aircraft. We do this using a with
statement.
在构造函数中,我们可以使用Entities settrafarrays()来通知BlueSky,我们正在为每架飞机指定一个或多个新的状态。我们使用with语句来做这件事。
Detailed control over aircraft creation 对飞机创建的详细控制
An Entity class can implement a create
function to gain more control over the initialisation of states of new aircraft:
一个实体类可以实现一个创建函数以获得对新飞机状态初始化的更多控制。
def create(self, n=1):
''' This function gets called automatically when new aircraft are created. '''
# Don't forget to call the base class create when you reimplement this function!
super().create(n)
# After base creation we can change the values in our own states for the new aircraft
self.npassengers[-n:] = [randint(0, 150) for _ in range(n)]
In the create function, you generally start by calling the create function of the parent class, to appropriately resize the aircraft states. After that you can make changes to each state for the new aircraft.
在创建函数中,你一般先调用父类的创建函数,以适当地调整飞机状态的大小。之后,你可以为新的飞机对每个状态进行修改。
Periodically timed functions 定期定时的功能
In a simulation, often calculations have to be made every timestep, or at a lower periodic interval. You can indicate your own functions as periodic functions using the core.timed_function
decorator:
在模拟中,经常需要在每个时间段,或者在较低的周期性间隔进行计算。你可以使用core.timed_function装饰器将你自己的函数表示为周期性函数。
@core.timed_function(name='example', dt=5)
def update(self):
''' Periodic update function for our example entity. '''
stack.stack('ECHO Example update: creating a random aircraft')
stack.stack('MCRE 1')
Optional arguments to this decorator are the name by which the timer of this function becomes visible within bluesky, and the default update interval of this function. You can change this interval during the simulation with the DT
stack command.
此装饰器的可选参数是此函数的定时器在 bluesky 中可见的名称,以及此函数的默认更新时间间隔。您可以在模拟过程中使用 DT 堆栈命令更改此时间间隔。
Defining new stack functions 定义新的堆栈函数
Stack functions are the core of BlueSky, and provide the means to create and control a simulation. For a list of the default BlueSky stack functions, look here. In a plugin, you can also create new stack commands, using the stack.command
decorator:
堆栈函数是BlueSky的核心,它提供了创建和控制模拟的手段。关于蓝天的默认堆栈函数的列表,请看这里。在一个插件中,你也可以使用stack.command装饰器创建新的堆栈命令。
@stack.command
def passengers(self, acid: 'acid', count: int = -1):
''' Set the number of passengers on aircraft 'acid' to 'count'. '''
if count < 0:
return True, f'Aircraft {traf.id[acid]} currently has {self.npassengers[acid]} passengers on board.'
self.npassengers[acid] = count
return True, f'The number of passengers on board {traf.id[acid]} is set to {count}.'
Here, we create a stack command PASSENGERS
, which takes two arguments, of which the second argument is optional. We can indicate the type of each argument using python's argument annotations (the colon-syntax: count: int
). For this function these are the following annotations:
在这里,我们创建了一个堆栈命令PASSENGERS,它需要两个参数,其中第二个参数是可选的。我们可以使用python的参数注释(冒号语法:count: int)来表示每个参数的类型。对于这个函数来说,这些注解如下。
- The
acid
argument takes a bluesky-specific type, annotated with'acid'
: This tells bluesky to expect an aircraft callsign, which it converts to the corresponding traffic array index, which is passed on to the function.acid参数需要一个bluesky特定的类型,用 "酸 "来注释。这告诉bluesky期望一个飞机呼号,它将转换为相应的交通数组索引,并传递给函数。 - The
count
argument takes a default integer.count参数是一个默认的整数。
Inputs and outputs 输入和输出
Its likely that you are designing a plugin to interact in some way with objects in the simulation. By default, you can access data from the simulation as input by directly accessing the corresponding objects. For example, if I want to access the position of all aircraft in the simulation, I would do the following:
你可能正在设计一个插件,以某种方式与仿真中的对象进行交互。默认情况下,你可以通过直接访问相应的对象来访问仿真中的数据作为输入。例如,如果我想访问模拟中所有飞机的位置,我会做以下工作。
from bluesky import traf, scr
import numpy as np
# ...
def update(self):
# I want to print the average position of all aircraft periodically to the
# BlueSky console
scr.echo('Average position of traffic lat/lon is: %.2f, %.2f'
% (np.average(traf.lat), np.average(traf.lon)))
For all available inputs, have a look at Traffic, Simulation, and Navdb.
对于所有可用的输入,请看交通、模拟和Navdb。
Sending commands through the stack is the default way to output to the simulation. For instance, if the result of a plugin is to add a waypoint to the route of a particular aircraft, you can do the following:
通过堆栈发送命令是向模拟输出的默认方式。例如,如果一个插件的结果是在某架飞机的航线上增加一个航点,你可以这样做。
from bluesky import stack
# ...
def update(self):
# ... exiting stuff happens before here, where an aircraft ID is found, and a waypoint name
ac = 'MY_AC'
wpt = 'SPY'
stack.stack('ADDWPT %s %s' % (ac, wpt))
Of course it is perfectly possible to avoid going through the stack here, and instead directly modify the data in, e.g., the traf
object. In fact, there will be more advanced situations where this is even required. Nevertheless, going through the stack is preferred, as the syntax of stack commands doesn't change, while the layout of the code and objects behind it may, making direct modifications from inside a plugin a brittle implementation.
当然,在这里完全可以避免通过堆栈,而直接修改例如traf对象中的数据。事实上,在更高级的情况下甚至需要这样做。然而,通过堆栈是首选,因为堆栈命令的语法不会改变,而它背后的代码和对象的布局可能会改变,这使得从插件内部直接修改是一个脆弱的实现。
Subclassing BlueSky core implementations 子类化BlueSky核心实现
In BlueSky, several core classes can be subclassed to add or replace functionality. Currently, these are the performance model, Conflict Detection, Conflict Resolution, the Wind model, the Turbulence model, the ADSB model, the Route object, the Autopilot logic, and the ActiveWaypoint object.
在BlueSky中,几个核心类可以被子类化以增加或替换功能。目前,这些类是性能模型、冲突检测、冲突解决、风模型、湍流模型、ADSB模型、航线对象、自动驾驶逻辑和ActiveWaypoint对象。
Subclassing is performed in a regular manner. For example, subclassing the Route object in a plugin, your code could look like this:
子类化是以常规方式进行的。例如,在一个插件中对路线对象进行子类化,你的代码可以是这样的。
from bluesky.traffic import Route
class MyRoute(Route):
def __init__(self):
super().__init__()
# my initialisation here
def delwpt(self,delwpname,iac=None):
success = super().delwpt(delwpname, iac)
# do something with your own data
return success
When running BlueSky, and loading this plugin, the new route implementation becomes available, and can be selected with the IMPLEMENTATION
command:
当运行BlueSky并加载这个插件时,新的路线实现就变得可用了,可以用IMPLEMENTATION命令选择。
IMPL ROUTE MYROUTE
The complete example can be found here, and in the plugins
folder in your BlueSky installation.
完整的例子可以在这里找到,也可以在你的BlueSky安装中的插件文件夹中找到。