JADE ContractNet и проблемы с графическим интерфейсом

У меня возникли проблемы с использованием ContractNet (протокол взаимодействия) и графического интерфейса пользователя с использованием мультиагентной среды JADE.

В частности, в переопределении метода handlePropose. Я знаю, что моя проблема связана с использованием графического интерфейса. Позволь мне объяснить:

Мой агент (инициатор) использует первый графический интерфейс, и после щелчка разговор начинается со вторым агентом (ответчиком). Таким образом, в соответствии с протоколом Инициатор отправил ответчику CFP. Агент-ответчик отвечает ПРЕДЛОЖЕНИЕМ, которое содержит другие данные.

Так как здесь все ок. В настоящее время...

Я хочу, чтобы агент-инициатор, ПЕРЕД возвратом ответа, мог изучить данные... т.е. опубликовать их на JTable для пользователя! Пользователь изучит предложение через графический интерфейс и выберет, принять его или нет, нажав кнопку.

  • Если принимается, инициатор отправляет ACCEPT_PROPOSAL.
  • Если не принимается, Инициатор отправляет REJECT_PROPOSAL.

Это нужно сделать в методе handleProposal. Это мой код:

@Override
protected void handlePropose(final ACLMessage propose, final Vector acceptances) {
    try {
        System.out.println("Agent "+getLocalName()
            +": receive PROPOSE from "+propose.getSender().getLocalName());

        final ACLMessage reply = propose.createReply();

        Vector<Goods> goods = (Vector<Goods>) propose.getContentObject();

        // the JTable's GUI for visualize the list of data:
        final GoodsChoiceBox gcb = new GoodsChoiceBox(propose.getSender().getName(), goods);

        // the problem:
        gcb.getExecuteJButton().addActionListener(new ActionListener() {
            @Override
            public void actionPerformed(ActionEvent e) {
               reply.setPerformative(ACLMessage.ACCEPT_PROPOSAL);
               System.out.println("Agent "+getLocalName()+": send ACCEPT PROPOSAL ");
               acceptances.addElement(reply);
            }
        });

        // similar case, but for REJECT:
        // gcb.getAbortJButton().addActionListener(... bla bla

        gcb.setVisible(true);

    } catch (UnreadableException e){
        e.printStackTrace();
    }
}

..... Но, очевидно, не работает.

В агенте-инициаторе поведение ContractNet прерывается... так что handleInform, handleRefuse и handleFailure (для обработки ответов) не работают. Основной графический интерфейс Инициатора заблокирован. И другие проблемы...

Вместо этого, если я сделаю это (БЕЗ JButton, другого графического интерфейса и ActionListener):

@Override
protected void handlePropose(final ACLMessage propose, final Vector acceptances) {
    try {
        System.out.println("Agent "+getLocalName()
            +": received PROPOSE from "+propose.getSender().getLocalName());
        final ACLMessage reply = propose.createReply();

        Vector<Goods> goods = (Vector<Goods>) propose.getContentObject();

        // the JTable's GUI for visualize the list of data:
        final GoodsChoiceBox gcb = new GoodsChoiceBox(propose.getSender().getName(), goods);

        reply.setPerformative(ACLMessage.ACCEPT_PROPOSAL);
        System.out.println("Agente "+getLocalName()+": ACCEPT PROPOSAL di "+propose.getSender().getLocalName());
        acceptances.addElement(reply);

    } catch (UnreadableException e){
        e.printStackTrace();
    }
}

.... работает.

Я знаю, что проблема в ActionListener и его многопоточном характере. Но мне нужен графический интерфейс там.

Как исправить?


person Gioce90    schedule 02.12.2014    source источник


Ответы (3)


Я пытаюсь ответить себе. Я не уверен, что это лучшее решение, но, безусловно, работает.

Обратите внимание, что прежде чем перейти к этому решению, я хорошо задокументирован с найденными руководствами и учебными пособиями (на http://jade.tilab.com/) и столкнулся с другими разработчиками JADE (в списках рассылки http://jade.tilab.com/pipermail/jade-develop/)

Ответ сложен, поэтому я постараюсь быть исчерпывающим.

В моем проекте мне приходится иметь дело с двумя разными типами агентов.

  • Агент ShipperAgent, который представляет одного грузоотправителя: он отслеживает транспортные средства, принадлежащие грузоотправителю, доступные и товары, "зарезервированные" у него.

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

  • Агент BuyerAgent, представляющий клиентов (или покупателей): у каждого покупателя есть список товаров, которые нужно переместить из точки А в точку Б.

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

Два агента зарегистрированы в службе желтых страниц.

В ShipperAgent, нажав кнопку "ПОИСК", вы начнете поиск: запустите Протокол взаимодействия с сетью контрактов.


Объясните Протокол взаимодействия с сетью контрактов и мой случай

В стандарте FIPA: http://www.fipa.org/specs/fipa00029/SC00029H.html введите здесь описание изображения

В JADE руководство можно найти здесь: http://jade.tilab.com/doc/programmersguide.pdf (стр. 35)

Далее вы заметите изменения, которые мне пришлось предпринять.

  1. ShipperAgent отправляет CFP каждому BuyerAgent.

  2. Каждый агент покупателя:

    2.1 если у него есть товар, отправить ПРЕДЛОЖЕНИЕ ShipperAgent.

    2.2 если товара нет, направить в ShipperAgent ОТКАЗ. А для покупателя протокол заканчивается.

Так как здесь легко. С помощью сниффера мы можем наблюдать:

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

В настоящее время:

  1. Агент грузоотправителя:

    3.1 получает одно или несколько ПРЕДЛОЖЕНИЙ от покупателей и отображает (см. изображение ниже).

    3.2 в случае получения ОТКАЗА (или не получения чего-либо по прошествии определенного времени), прекращение связи с данным покупателем.

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

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

Теперь пользователь сам выбирает, какие товары хочет, а какие нет.

Чтобы добиться этого, мне пришлось создать своего рода «внутреннюю связь» с самим агентом: графический интерфейс (в 3.1) после нажатия «Выполнить» отправляет сообщение агенту. Это может показаться неэлегантным, но, похоже, это единственный способ избежать сбоя ShipperAgent на стороне протокола.

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

  1. Агент грузоотправителя:

    4.1, если пользователь выбрал одно или несколько предложений товаров (и нажал «Выполнить»), отправляет соответствующему BuyerAgent сообщение ACCEPT_PROPOSAL, где указаны конкретные товары, которые он хочет (подмножество предыдущего предложения).

    4.2, если пользователь не выбирает товар (или нажимает «Отмена»), отправляет соответствующему агенту покупателя сообщение REJECT_PROPOSAL. Прекращает общение с этим покупателем.

  2. Агент покупателя:

    5.1 если получает ACCEPT_PROPOSAL, проверяет, что товары все еще доступны (любые другие грузоотправители могут тем временем «зарезервировать» их) и, если это так, отправляет INFORM.

    5.2 если получает ACCEPT_PROPOSAL ma один или несколько товаров больше не доступны, отправляет FAILURE.

    5.3, если он получает REJECT_PROPOSAL, прекращает связь с агентом-отправителем.

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

Вкратце это (например):

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


Код

BuyerAgent.java Я создаю диспетчера, который всегда готов принять CFP. Как только он получит и запустит протокол, на стороне покупателя: запустите SearchJobResponder.

/*
 * ...
 */

final MessageTemplate template = MessageTemplate.and(
    MessageTemplate.MatchProtocol(FIPANames.InteractionProtocol.FIPA_CONTRACT_NET),
    MessageTemplate.MatchPerformative(ACLMessage.CFP) );

// SSResponderDispatcher:
SSResponderDispatcher dispatcher = new SSResponderDispatcher(this, template) {
    BuyerAgent b = (BuyerAgent) this.myAgent;
    protected Behaviour createResponder(ACLMessage initiationMsg) {
        // SearchJobResponder for single cfp:
        return new SearchJobResponder(b, initiationMsg);
    }
};

addBehaviour(dispatcher);

/*
 * ...
 */

ShipperAgent.java Поиск всех покупателей, создание CFP и запуск протокола на стороне грузоотправителя: запуск SearchJobInitiator.

/*
 * ...
 */

ACLMessage cfp = new ACLMessage(ACLMessage.CFP);
AID[] buyerAgents = searchBuyers(); // search all buyerAgents
for (AID buyer : buyerAgents)
    cfp.addReceiver(buyer);
addBehaviour(new SearchJobInitiator(this, cfp));

/*
 * ...
 */

SearchJobInitiator.java Это была сложная часть...

/*
 * ...
 */

public class SearchJobInitiator extends ContractNetInitiator {
    ShipperAgent shipperAgent;

    public SearchJobInitiator(ShipperAgent a, ACLMessage cfp) {
        super(a, cfp);
        shipperAgent=a;
        // Very important:
        registerHandleAllResponses(new HandleProposes());
    }

    @Override
    protected Vector<?> prepareCfps(ACLMessage cfp) {

        long now = System.currentTimeMillis();

        cfp.setConversationId("contractNet-by-"
                +shipperAgent.getAID().getLocalName()+now);

        cfp.setContent("Fammi delle proposte di lavoro");

        /* 
         * filtering... 
         */

        cfp.setProtocol(FIPANames.InteractionProtocol.FIPA_CONTRACT_NET);

        cfp.setReplyByDate(new Date(now+10000));

        //cfp.setReplyWith("cfp"+System.currentTimeMillis()) //useless, is overwrited at the end

        return super.prepareCfps(cfp);
    }

    //inner class for handling a single proposal
    public class HandleProposes extends Behaviour {
        private static final long serialVersionUID = 1L;
        private Vector<ACLMessage> proposes;
        private Vector<ACLMessage> acceptances;
        private int numberOfProposes;

        public void onStart() {
            proposes = (Vector<ACLMessage>) getDataStore().get(ALL_RESPONSES_KEY);
            acceptances = (Vector<ACLMessage>) getDataStore().get(ALL_ACCEPTANCES_KEY);

            numberOfProposes=proposes.size();

            for (Iterator I=proposes.iterator(); I.hasNext();) {
                ACLMessage propose = (ACLMessage) I.next();

                // Very important:
                if (propose.getPerformative()==ACLMessage.PROPOSE)
                    myAgent.addBehaviour(new HandleSinglePropose(propose, acceptances));
                else
                    numberOfProposes--;
            }

        }

        public void action() {
            if (!done())
                block();
        }

        public boolean done() {
            return (acceptances.size()==numberOfProposes);
        }


        /*
         * Inner class for handle a single proposal and display it:
         */
        public class HandleSinglePropose extends Behaviour {
            private ACLMessage propose;
            private Vector<ACLMessage> acceptances;
            private boolean finish=false;

            public HandleSinglePropose (ACLMessage propose, Vector<ACLMessage> acceptances) {
                this.propose=propose;
                this.acceptances=acceptances;

                // This is GUI in 3.1 point
                GoodsChoiceBox gcb = new GoodsChoiceBox(shipperAgent, this, propose); // fill the JTable
                gcb.setVisible(true);
            }

            @Override
            public void action() {
                MessageTemplate mt = MessageTemplate.and(
                        MessageTemplate.MatchSender(shipperAgent.getAID()),
                        MessageTemplate.and(
                            MessageTemplate.MatchReplyWith("response"+propose.getReplyWith()),
                            MessageTemplate.or(
                                MessageTemplate.MatchPerformative(ACLMessage.ACCEPT_PROPOSAL),
                                MessageTemplate.MatchPerformative(ACLMessage.REJECT_PROPOSAL)
                ) ) ) ;

                // Read data from GUI. The user accept or reject:
                ACLMessage decisionFromGUI = shipperAgent.receive(mt);
                if (decisionFromGUI != null) {
                    ACLMessage reply = propose.createReply();
                    // bla bla...
                    finish=true;
                    HandleProposes.this.restart();
                } else {
                    block();
                }
            }

            public boolean done() {
                return finish;
            }


            public void handleChoice(ACLMessage propose, boolean bool, Vector<Goods> selectedGoods) {
                ACLMessage reply;
                if (bool){
                    reply = new ACLMessage(ACLMessage.ACCEPT_PROPOSAL);
                    //...
                } else {
                    reply = new ACLMessage(ACLMessage.REJECT_PROPOSAL);
                    //...
                }
                reply.addReceiver(shipperAgent.getAID());
                reply.setReplyWith("response"+propose.getReplyWith());
                shipperAgent.send(reply);
            }

        } // closes HandleSinglePropose

    } // closes HandleProposes

}

SearchJobResponder.java Ответчик прост. Единственное замечание: я расширяю SSContractNetResponder, а не расширяю ContractNetResponder.

public class SearchJobResponder extends SSContractNetResponder {
    BuyerAgent buyerAgent;

    public SearchJobResponder(BuyerAgent a, ACLMessage cfp) {
        super(a, cfp);
        buyerAgent = a;
    }

    /*
     * override methods...
     */
}

GoodsChoiceBox.java Графический интерфейс для отображения предложений...

public GoodsChoiceBox(final Agent agent, final HandleSinglePropose behaviour, final ACLMessage propose){
    /*
     * graphics stuff
     */

    // if goods selected and press Execute
    behaviour.handleChoice(propose,true,selectedGoods);
    //else
    behaviour.handleChoice(propose,false,null); 

    /*
     * bla bla
     */
}

Я знаю, я много размышлял, но я не знал, как еще объяснить. Тем не менее, сейчас мой проект работает. Но я открыт для любых предложений.

person Gioce90    schedule 13.01.2015

Я часто сталкиваюсь с подобными проблемами. Это поведение конечного автомата, поэтому вы должны иметь возможность приостанавливать и возобновлять поведение, но я не уверен, как это сделать. Что я делаю, так это создаю два отдельных поведения взаимодействия на стороне инициатора и одно на стороне ответчика.

Initiator                      Responder
|                                |
|                                |
| First behaviour                |The responder only has 1 behaviour
||        CFP->                 ||
||        <-Proposal            ||
|                               ||
| Second behaviour              ||
||        Accept prop->         ||
||        <-Response            ||
|                                |

Два момента, которые нужно помнить

(1)

Убедитесь, что вы сохранили идентификатор беседы

msgRecieved.getConversationID

из первого поведения и использовать его во втором поведении.

msg.setConversationID().

(2) Второе поведение — это другой сетевой инициатор контракта, но в методе prepareCFPs установлено перформативное СООБЩЕНИЕ, чтобы принять предложение.

класс ContractServiceList расширяет ContractNetInitiator {

    protected Vector prepareCfps(ACLMessage cfp) {

        ACLmessage AP= new ACLmessage(ACLmessage.ACCEPT_PROPOSAL)
.....

Эти вещи трудно объяснить, поэтому я попытался прикрепить изображение, но у меня есть 2 маленькие точки повторения.

Теперь у меня достаточно точек повторения, чтобы прикрепить картинку, которую я делаю.

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

person Clintus    schedule 21.12.2014
comment
таким образом, я не использую обычный протокол взаимодействия с сетью контрактов ... или я ошибаюсь? Я не думаю, что это хорошо работает. Вернее, я думаю, что он придерживается концепции ContractNet. - person Gioce90; 24.12.2014
comment
Да, вы правильно используете взаимодействия CNP. Дальше я не понимаю, что вы пытаетесь сказать. - person Clintus; 06.01.2015

Я только что понял, что есть другое решение этой проблемы. Второе решение предполагает использование ChildBehaviours и хранилищ данных. Дочернее поведение можно инициировать, приостановив родительское поведение. Затем родительское поведение должно быть возобновлено после завершения дочернего процесса.

Я прикрепляю картинку, чтобы лучше объяснить это взаимодействие.

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

Таким образом, в точке A родительского поведения CNI (ContractNetInitiator) вы хотите инициировать дочернее поведение. Вы бы сделали это, используя CNI.registerHandlePropose(new Childbehaviour).

Вот как должен выглядеть метод setup():

 protected void setup()
{
  ContractNetInitiator parentBehave= new ContractNetInitiator (null, null, GlobDataStore);
  ContractNetInitiator.registerHandlePropose(new ChildBehavoiur (GlobDataStore));
  addBehaviour(CNI);    
}

В вашем дочернем поведении вам придется проверять данные от родителя (GlobDataStore) и возвращать сообщение для передачи обратно. Код для подражания:

class ChildBehaviour extends OneShotBehaviour{

    @Override
    public void action() {
        //evaluate globalestore here;
        ACLMessage CNIresponse=new ACLMessage();

        if(true)
        {
            storeNotification(ACLMessage.ACCEPT_PROPOSAL, CNIresponse);
        }
        else
        {
            storeNotification(ACLMessage.REJECT_PROPOSAL, CNIresponse);
        }
    }



           public void storeNotification(int performative, ACLMessage original) 
      {                         
         // Retrieve the incoming request from the DataStore
         String incomingCFPkey = (String) ((ContractNetResponder) parent).CFP_KEY;
          incomingCFPkey = (String) ((ContractNetResponder) parent).CFP_KEY;

         ACLMessage incomingCFP = (ACLMessage) getDataStore().get(incomingCFPkey);
         // Prepare the notification to the request originator and store it in the DataStore
         ACLMessage notification = incomingCFP.createReply();
         notification.setPerformative(performative);
         notification.setContent(original.getContent());
         String notificationkey = (String) ((ContractNetResponder) parent).PROPOSE_KEY;
         getDataStore().put(notificationkey, notification);
      } 

    }
person Clintus    schedule 06.01.2015
comment
Я думаю, вы не совсем поняли суть моей проблемы. Я полностью понял протокол взаимодействия ContractNet, а также понял, как его реализовать. Если вы перечитаете мой первоначальный вопрос, вы заметите, что я поставил два конкретных случая. Один, в котором программа работает (без графического интерфейса), и другой, в котором она не работает (с графическим интерфейсом). Проблема вызвана тем, что мой код не работает, если я хочу использовать графические интерфейсы... я хочу, чтобы пользователь ПРИНЯЛ или ОТКЛОНИЛ (через GUI) ПРЕДЛОЖЕНИЕ каждого ответившего. - person Gioce90; 08.01.2015
comment
Я понимаю вашу проблему и знаю, что это решение может показаться непосильным. Я предлагаю вам выполнить проверку GUI в дочернем поведении, используя registerHandlePropose, но более простым решением может быть блокировка поведения CNI. В любом случае вам нужно выполнить оценку графического интерфейса вне поведения CNI. - person Clintus; 12.01.2015
comment
Ну, это работает, но вы не используете все преимущества уже доступных функций. В любом случае вам необходимо установить идентификатор беседы сообщений, а также accpetProposal.setConversationID(String getConvIDfromCFP), иначе сетевой ответчик контракта на стороне продавца не распознает его. - person Clintus; 15.01.2015
comment
Я делаю это в методе prepareCfps в SearchJobInitiator.java. См. мое редактирование, изначально я не сообщал по причинам синтеза :) ... Я не сообщал о других методах handlerXXX по той же причине. Если у вас есть другие предложения, вы можете прокомментировать этот ответ? Благодарю вас :) - person Gioce90; 15.01.2015