Документация Engee
Notebook

Возврат дрона по снижению заряда батареи

В этом проекте мы применим несколько способов расчета агентных систем при помощи конечных автоматов.

Почему конечные автоматы?

Конечные автоматы позволяют создавать отладочное окружение или тестировать алгоритм принятия решений на абстрактном уровне. Чтобы избежать затрат на разработку и поддержку решения "с нуля" мы опробуем пару других способов:

  • моделирование при помощи штатной библиотеки StateMachines и
  • при помощи графического языка Engee.

Простейший пример конечного автомата

Для начала построим простой детерминистический конечный автомат (DFSM).

In [ ]:
]add StateMachines

Создадим автомат a1, в котором задана логика возврата квадрокоптера на базу при низкой батарейке.

In [ ]:
using StateMachines

a1 = Automaton([
    Transition("landed", "flying", :takeoff),         # Взлет по команде
    Transition("flying", "returning", :low_battery),  # Авто-возврат
    Transition("flying", "returning", :return_cmd),   # Возврат по команде
    Transition("returning", "landed", :returned),     # Посадка
], start="landed")

states = []
events = [ :idle, :takeoff, :low_battery, :returned ]

for event in events
    prev_state = a1.state
    StateMachines.exec!( a1, event )
    append!( states, [a1.state] )
    println( "Переход $(String(prev_state)) -> $(String(a1.state)) по событию: $(String(event))" )
end
Переход landed -> landed по событию: idle
Переход landed -> flying по событию: takeoff
Переход flying -> returning по событию: low_battery
Переход returning -> landed по событию: returned

Другой вариант вывода - графический. Для такого вывода мы переведем массивы states и events в строчный вид и зададим им определенный порядок, чтобы вывод выполнятся не в алфавитном, а в более логичном порядке.

In [ ]:
]add CategoricalArrays Measures
In [ ]:
using CategoricalArrays, Measures
gr()

# Зададим порядок событий и состояний (в противном случае они выводятся в алфавитном порядке)
events_ordered = levels!( categorical(string.(events)), ["idle", "takeoff", "low_battery", "returned"])
states_ordered = levels!( categorical(string.(states)), ["landed", "flying", "returning"])

plot(
    plot( events_ordered, 1:length(events_ordered), title="События",
          st=:steppre, yflip=true, lw=repeat([ 12; 2 ], length(events_ordered))[1:end-1] ),
    plot( states_ordered, 1:length(states_ordered), title="Состояния",
          st=:steppre, yflip=true, yaxis=nothing, c=2,
          lw=repeat([ 12; 2 ], length(states_ordered))[1:end-1] ),
    legend=false, xmirror=true, top_margin=5mm, right_margin = [10mm 0mm],
)
plot!( size=(800,300) )
Out[0]:

Мы построили довольно типичную диаграмму, которая в UML называется Sequence Diagram (Диаграмма последовательности).

Если удобен другой вид интерпретации, можно обратить оси графика.

In [ ]:
# Зададим порядок событий и состояний (в противном случае они выводятся в алфавитном порядке)
events_ordered = levels!( categorical(string.(events)), ["idle", "takeoff", "low_battery", "returned"])
states_ordered = levels!( categorical(string.(states)), ["landed", "flying", "returning"])

plot(
    plot( 1:length(events_ordered), events_ordered, title="События",
          st=:steppre, lw=repeat([ 2; 12 ], length(events_ordered))[1:end-1] ),
    plot( 1:length(states_ordered), states_ordered, title="Состояния",
          st=:steppre, c=2, lw=repeat([ 2; 12 ], length(states_ordered))[1:end-1] ),
    legend=false, xmirror=true, top_margin=5mm, right_margin = [10mm 0mm],
)
plot!( size=(800,300) )
Out[0]:

Применение компонента Chart в среде Engee

Программное задание конечного автомата удобно для моделирования больших систем или для генерируемых сценариев. Для представление небольшого алгоритма принятия решений хорошо подходит и графический формализм, представленный в Engee компонентом Chart (Конечный автомат).

image.png

Мы задаем сценарий, в котором наступление событий моделируется блоками Ступенчатая функция (Step), пропущенными через блоки Разность (Difference). Так на входы конечного автомата Логика возврата будут поступать не ступени, а дискретные импульсы.

Под блоками добавлены элементы аннотации, чтобы с первого взгляда на модель был виден весь сценарий тестирования.

image.png

Осталось только запустить модель в режиме командного управления и построить графики:

In [ ]:
model_name = "drone_rtb_state_machine";
model_name in [m.name for m in engee.get_all_models()] ? engee.open(model_name) : engee.load( "$(@__DIR__)/$(model_name).engee");
res = engee.run( model_name );

plot( res["Состояние"].time, res["Состояние"].value, label="Состояние",
      yticks=((1,2,3), ("На базе","Полет","Возвращение")), lw=3, size=(600,300) )
Out[0]:

Из этой диаграммы можно сгенерировать код (при этом, поскольку в Си не поддерживаются кириллические символы, лучше выбрать название для блока Chart, состоящее из латинских символов).

Заключение

Мы создали модель одной из простейших функций квадрокоптера - "возврат на базу" при низком уровне заряда батарейки. Можно использовать эту модель в качестве тестового сценария для более сложной системы, чтобы сверять очередность и наступление тех или иных состояний в контролируемом окружении.

Блоки, использованные в примере