Поясню программу Лунохода1, которую я представил в сообщении:
http://roboforum.ru/forum102/topic17000-180.html#p366813Язык CLIPS позволяет с помощью специального bat файла выполнять некоторые операции.
Ниже представлен файл Lunochod1NoBarrier.bat:
Код:
(defmodule MAIN (export deftemplate ?ALL))
(load Lunochod1/Main.ecl)
(load Lunochod1/SimulatorNoBarrier.ecl)
(load Lunochod1/Mission1.ecl)
(load Lunochod1/Sensor.ecl)
(load Lunochod1/Solver.ecl)
(load Lunochod1/Executor.ecl)
(reset)
(run )
в котором первая строка сообщает, что все шаблоны фактов модуля MAIN экспортируются и становятся доступными для использования в других модулях. В каждом модуле работает отдельный движок продукционных правил, поэтому чтобы передать что-то из одного модуля в другой, нужно экспортировать требуемые конструкции.
Затем последовательно загружаются все файлы с модулями программы.
В конце командой reset очищается память, а командой run программа стартует начиная с модуля MAIN.
Файл Main.ecl с модулем MAIN
Код:
;;;======================================================
;;; Lunochod V 1.0.0
;;; MAIN Module
;;; For ECLIPS Version 6.3
;;; 2016.12.02 Victor Kazarinov
;;;======================================================
;;; Главный модуль программы.
;;;======================================================
; Система должна работать циклически, поэтому нужен факт cycle, обозначающий текущий номер цикла
(deffacts MAIN::cycle-start
(cycle -1) ; Начальное значение номера цикла
)
;;;======================================================
(defrule MAIN::Cycle-Start
(cycle -1)
=>
(printout t "MAIN::Program Lunochod started" crlf)
)
(defrule MAIN::Cycle-Next ; Правило, определяющее последовательность работы всей программы.
?f <- (cycle ?current-cycle)
=>
(retract ?f)
(bind ?next-cycle (+ ?current-cycle 1))
(assert (cycle ?next-cycle))
(printout t "MAIN::Cycle=" ?next-cycle crlf)
(focus SIMULATOR MISSION SENSOR SOLVER EXECUTOR)
)
Язык CLIPS позволяет писать модульные программы, чем я и воспользовался. В программе Lunochod1 имеются следующие модули: SIMULATOR MISSION SENSOR SOLVER EXECUTOR.
Начнем анализ программы с модуля MAIN.
Перед стартом программы в памяти размещается факт cycle со значением -1 в конструкции deffacts MAIN::cycle-start.
Рассмотрим на примере самого первого правила программы как устроены правила и как они работают:
Код:
(defrule MAIN::Cycle-Start
(cycle -1)
=>
(printout t "MAIN::Program Lunochod started" crlf)
)
Шаблон правила задается ключевым словом defrule. MAIN:: определяет принадлежность правила какому-то модулю. В нашем случае это модуль MAIN. Далее следует название правила: Cycle-Start.
После названия следует условная часть правила (ЕСЛИ). (cycle -1) означает, что если в памяти будет обнаружен факт cycle со значением -1, то правило сработает.
Знак => означает ТО и разделяет условия и действия, которые требуется совершить в случае срабатывания правила.
В нашем случае происходит вывод в консоль (символ t) текста "MAIN::Program Lunochod started", который завершается переводом строки (символ crlf).
Как только программа обнаружит в рабочей памяти факт cycle со значением -1, то по шаблону правила Cycle-Start будет создан объект - экземпляр правила, у которого условная часть заполнена конкретными значениями условий.
Правило Cycle-Next:
Код:
(defrule MAIN::Cycle-Next ; Правило, определяющее последовательность работы всей программы.
?f <- (cycle ?current-cycle)
=>
(retract ?f)
(bind ?next-cycle (+ ?current-cycle 1))
(assert (cycle ?next-cycle))
(printout t "MAIN::Cycle=" ?next-cycle crlf)
(focus SIMULATOR MISSION SENSOR SOLVER EXECUTOR)
)
Правило Cycle-Start после использования факта cycle не уничтожает его, а правило Cycle-Next командой (retract ?f) делает это. После этого рабочая память фактов становится пустой. Однако с помощью (assert (cycle ?next-cycle)) правило Cycle-Next создает другой факт cycle со значением, большим на 1, чем было у предыдущего только что удаленного факта cycle.
Следующей строкой (printout t "MAIN::Cycle=" ?next-cycle crlf) осуществляется вывод текста в терминал.
Оператор focus устанавливает последовательность выполнения модулей после того, как в текущем модуле будут исчерпаны все экземпляры правил. Правила могут сработать только один раз для данного конкретного сочетания фактов.
Следующим модулем, который будет исполняться после модуля MAIN - модуль SIMULATOR.
Файл SimulatorNoBarrier.ecl с модулем SIMULATOR для случая, когда на пути лунохода нет препятствий, предельно прост:
Код:
;;;======================================================
;;; Lunochod V 1.0.0
;;; SIMULATOR Module
;;; For ECLIPS Version 6.3
;;; 2016.12.02 Victor Kazarinov
;;;======================================================
;;; Модуль симулятора внешней среды, сенсоров и пользователя.
;;;======================================================
(defmodule SIMULATOR (export deftemplate ?ALL) (import MAIN deftemplate ?ALL))
(defrule SIMULATOR::Mission1-Start ; Правило старта Миссии1
?c <- (cycle 0)
=>
(assert (mission mission1))
(printout t "SIMULATOR::Mission1:Start." crlf)
)
Он состоит всего лишь из одного правила: когда будет получен факт cycle со значением 0, то создать факт mission со значением mission1. Однажды созданный, этот факт будет существовать в системе до окончания миссии1 и будет являться флагом для работы робота над выполнением этой миссии.
Для нашего удобства в терминал командой (printout t "SIMULATOR::Mission1:Start." crlf) выводится контрольное сообщение.
Файл SimulatorBarrier.ecl с модулем SIMULATOR для случая, когда на пути лунохода имеется препятствие:
Код:
;;;======================================================
;;; Lunochod V 1.0.0
;;; SIMULATOR Module
;;; For ECLIPS Version 6.3
;;; 2016.12.02 Victor Kazarinov
;;;======================================================
;;; Модуль симулятора внешней среды, сенсоров и пользователя.
;;;======================================================
(defmodule SIMULATOR (export deftemplate ?ALL) (import MAIN deftemplate ?ALL))
(defrule SIMULATOR::Mission1-Start ; Правило старта Миссии1
?c <- (cycle 1)
=>
(assert (mission mission1))
(printout t "SIMULATOR::Mission1:Start." crlf)
)
(defrule SIMULATOR::Barrier-Set ; Правило, устанавливающее препятствие на пути робота.
?c <- (cycle 2)
=>
(assert (barrier detected))
(printout t "SIMULATOR::Barrier Set." crlf)
)
(defrule SIMULATOR::Barrier-Removed ; Правило, устраняющее препятствие на пути робота.
?c <- (cycle 4)
?f <- (barrier detected)
=>
(retract ?f)
(printout t "SIMULATOR::Barrier Removed." crlf)
)
В дополнение к тому, что делает модуль SIMULATOR для случая без препятствий, правило SIMULATOR::Barrier-Set устанавливает препятствие на пути робота в цикле 2, а правило SIMULATOR::Barrier-Removed удаляет это препятствие в цикле 4.
Файл Mission1.ecl с модулем MISSION:
Код:
;;;======================================================
;;; Lunochod V 1.0.0
;;; MISSION Module
;;; For ECLIPS Version 6.3
;;; 2016.12.02 Victor Kazarinov
;;;======================================================
;;; Модуль пользовательского сценария.
;;;======================================================
(defmodule MISSION (export deftemplate ?ALL) (import MAIN deftemplate ?ALL) (import SIMULATOR deftemplate ?ALL))
(deftemplate MISSION::command ; Шаблон факта команды, поступающей в сенсорный модуль
(slot name)
(slot param (type INTEGER) (default 0))
)
(deftemplate MISSION::answer ; Шаблон факта ответа, поступающего из модуля EXECUTOR
(slot name)
(slot param)
)
(deffacts MISSION::mission1-facts ; Шаблон факта фаз выполнения миссии.
(phase forward1)
)
;;;======================================================
(defrule MISSION::Mission1-Forward1 ; Правило первой фазы Миссии1
?c <- (cycle ?current-cycle)
?f <- (mission mission1)
?m <- (phase forward1)
=>
(retract ?m)
(printout t "MISSION::Mission1.Command.Forward1(5)" crlf)
(assert (command (name forward) (param 5))) ; Для модуля SOLVER
(assert (phase right1))
)
(defrule MISSION::Mission1-Right1 ; Правило второй фазы Миссии1
?c <- (cycle ?current-cycle)
?f <- (mission mission1)
?m <- (phase right1)
?n <- (answer (name forward) (param OK))
=>
(retract ?m)
(retract ?n)
(printout t "MISSION::Mission1.Command.Right1(3)." crlf)
(assert (command (name right) (param 3))) ; Для модуля SOLVER
(assert (phase left1))
)
(defrule MISSION::Mission1-Left1 ; Правило третьей фазы Миссии1
?c <- (cycle ?current-cycle)
?f <- (mission mission1)
?m <- (phase left1)
?n <- (answer (name right) (param OK))
=>
(retract ?m)
(retract ?n)
(printout t "MISSION::Mission1.Command.Left1(8)." crlf)
(assert (command (name left) (param 8))) ; Для модуля SOLVER
(assert (phase end1))
)
(defrule MISSION::Mission1-End1 ; Правило четвертой фазы Миссии1
?c <- (cycle ?current-cycle)
?f <- (mission mission1)
?m <- (phase end1)
?n <- (answer (name left) (param OK))
=>
(retract ?m)
(retract ?n)
(retract ?f)
(printout t "MISSION::Mission1.Command.End()." crlf)
(halt)
)
Конструкция deftemplate MISSION::command создает шаблон факта команды, поступающей в модуль SOLVER, а шаблон факта deftemplate MISSION::answer позволяет получить ответ модуля SOLVER о результате выполнения команды.
Конструкция deffacts MISSION::mission1-facts перед началом выполнения модуля MISSION размещает в рабочей памяти факт phase со значением forward1.
Правило MISSION::Mission1-Forward1 срабатывает сразу же, как только управление переходит в этот модуль, т.к. в наличии будут все условия для его срабатывания. Оно удаляет факт фазы, выводит в теминал лог, а также создает факт command для модуля SOLVER с указанием направления движения робота и величиной перемещения. Это правило создает еще факт phase со значением right1, что указывает на следующий этап работы конечного автомата сценария.
Правило MISSION::Mission1-Right1 сработает, если появится дополнительное условие -факт answer от модуля SOLVER с подтверждением полного выполнения предыдущего этапа сценария.
Аналогично работают и все остальные правила. Последнее правило Mission1-End1 не создает новой команды, а завершает работу программы.
Теперь переходим к рассмотрению модулей, описывающим внутреннее устройство робота.
Файл Sensor.ecl с модулем SENSOR:
Код:
;;;======================================================
;;; Lunochod V 1.0.0
;;; SENSOR Module
;;; For ECLIPS Version 6.3
;;; 2016.12.02 Victor Kazarinov
;;;======================================================
;;; Модуль обработки сенсорных данных.
;;;======================================================
(defmodule SENSOR (export deftemplate ?ALL) (import MAIN deftemplate ?ALL) (import MISSION deftemplate ?ALL))
(defrule SENSOR::Barrier-Detected ; Правило обнаружения препятствия.
?c <- (cycle ?current-cycle)
?f <- (barrier detected)
=>
(assert (sensor barrier)) ; Для модуля SOLVER
(printout t "SENSOR::Error: Barrier Detected!" crlf)
)
(defrule SENSOR::Barrier-Removed ; Правило определения отстутсвия препятствия.
?c <- (cycle ?current-cycle)
(not (barrier detected))
=>
(assert (sensor nobarrier)) ; Для модуля SOLVER
(printout t "SENSOR::Message: No Barrier." crlf)
)
Правило Barrier-Detected позволяет обнаружить препятствие, если оборудование робота создаст факт barrier со значением detected и внесет его в рабочую память этого модуля. Как видно из кода, во всех правилах здесь, как и в других модулях, присутствует конструкция вида ?c <- (cycle ?current-cycle), которая позволяет привязать выполнение всех правил к текущему циклу работы всей программы.
Если препятствие обнаружено правилом Barrier-Detected, то командой (assert (sensor barrier)) будет создан факт sensor со значением barrier. Этот факт предназначен модулю SOLVER.
Правило Barrier-Removed создает аналогичный факт для модуля SOLVER со значением nobarrier в каждом цикле, если препятствия на пути робота нет.
Файл Solver.ecl с модулем SOLVER:
Код:
;;;======================================================
;;; Lunochod V 1.0.0
;;; EXECUTOR Module
;;; For ECLIPS Version 6.3
;;; 2016.12.02 Victor Kazarinov
;;;======================================================
;;; Модуль принятия решений.
;;;======================================================
(defmodule SOLVER (export deftemplate ?ALL) (import MAIN deftemplate ?ALL) (import MISSION deftemplate ?ALL) (import SENSOR deftemplate ?ALL))
(deftemplate SOLVER::action ; Шаблон факта действия, поступающего в модуль EXECUTOR
(slot name) ; Название действия.
(slot param) ; Параметр действия.
(slot status) ; Статус действия.
)
;;;======================================================
(defrule SOLVER::Decision-Motion-Start
?c <- (cycle ?current-cycle)
?f <- (sensor nobarrier)
?n <- (command (name ?motion) (param ?p))
=>
(retract ?f)
(retract ?n)
(printout t "SOLVER::" ?motion ".Start()." crlf)
(assert (action (name ?motion) (param (- ?p 1)) (status request))) ; Для модуля EXECUTOR
)
(defrule SOLVER::Decision-Motion-Cycle
?c <- (cycle ?current-cycle)
?f <- (sensor nobarrier)
?n <- (action (name ?motion) (param ?p) (status OK))
=>
(retract ?f)
(retract ?n)
(if (> ?p 0)
then
(printout t "SOLVER::" ?motion ".Continue()." crlf)
(assert (action (name ?motion) (param (- ?p 1)) (status request))) ; Для модуля EXECUTOR
else
(printout t "SOLVER::" ?motion ".End()." crlf)
(assert (answer (name ?motion) (param OK))) ; Для модуля MISSION
)
)
В этом модуле конструкция deftemplate SOLVER::action создает шаблон факта, который позволяет модулю SOLVER отправлять команды в модуль EXECUTOR и получать от него подтверждение выполнения команд в слоте status факта action.
Правило SOLVER::Decision-Motion-Start в случае, если нет препятствия и имеется факт command с параметрами срабатывает и среди прочего создает факт action для модуля EXECUTOR. Наследники этого факта затем будут циркулировать в программе до тех пор, пока не выполнятся все шаги. Количество шагов указано в слоте param этого факта.
Циклическое выполнение обеспечивает правило SOLVER::Decision-Motion-Cycle, которое при поступлении в последующих циклах от модуля EXECUTOR факта action со слотом status равным OK вырабатывает факт-наследник action с уменьшенным на 1 значением параметра param.
Файл Executor.ecl с модулем EXECUTOR:
Код:
;;;======================================================
;;; Lunochod V 1.0.0
;;; EXECUTOR Module
;;; For ECLIPS Version 6.3
;;; 2016.12.02 Victor Kazarinov
;;;======================================================
;;; Модуль выполнения действий.
;;;======================================================
(defmodule EXECUTOR (import MAIN deftemplate ?ALL) (import SOLVER deftemplate ?ALL))
(defrule EXECUTOR::Action-Forward
?c <- (cycle ?current-cycle)
?f <- (action (name forward) (param ?p) (status request))
=>
(retract ?f)
(printout t "EXECUTOR::Action=Forward.Step()." crlf)
(assert (action (name forward) (param ?p) (status OK))) ; Для модуля SOLVER
)
(defrule EXECUTOR::Action-Left
?c <- (cycle ?current-cycle)
?f <- (action (name left) (param ?p) (status request))
=>
(retract ?f)
(printout t "EXECUTOR::Action=Left.Step()." crlf)
(assert (action (name left) (param ?p) (status OK))) ; Для модуля SOLVER
)
(defrule EXECUTOR::Action-Right
?c <- (cycle ?current-cycle)
?f <- (action (name right) (param ?p) (status request))
=>
(retract ?f)
(printout t "EXECUTOR::Action=Right.Step()." crlf)
(assert (action (name right) (param ?p) (status OK))) ; Для модуля SOLVER
)
Этот модуль совсем прост. Для каждого направления движения имеется по одному правилу, которое срабатывает при получении из модуля SOLVER факта action с соответствующим значением слота name. При срабатывании каждого правила робот делает элементарный шажок в требуемом направлении, что выражается пока лишь в выводе на теминал логирующего сообщения. После этого правила создают ответный факт action в модуль SOLVER. У этого факта все слоты кроме последнего совпадают с тем фактом action, по которому это правило сработало.