Как подключить UnetSocket Python к агенту, который я создал с помощью agentForService? - PullRequest
2 голосов
/ 27 февраля 2020

Я пытаюсь установить связь между скриптом Python и симуляцией, запущенной на UnetSim. Я хочу отправлять сообщения из Python и получать их в UnetStack, используя Python_Agent. groovy, который я создал и добавил в контейнер каждого узла в симуляции. Я также хочу сделать обратное.

Я использовал документацию fjage (https://buildmedia.readthedocs.org/media/pdf/fjage/dev/fjage.pdf), чтобы помочь мне. Проблема в том, что в службах класса шлюза нет службы PYTHON_AGENT , которую я создал. Я могу понять, что, поскольку мои enum Services не изменяют классы Services, где есть NODE_INFO, PHYSICAL et c ... Тогда у меня вопрос, как работает пример в документации 1.6.3? И применимо ли это к моему случаю?

Вот мой код:

PythonSocketExample.py

from unetpy import *
from fjagepy import *
import socket

node_address = '001'
host = socket.gethostname()

sock = UnetSocket(host, int(node_address) + 1100)
gw = sock.getGateway()

py_agent = gw.agentForService(Services.PYTHON_AGENT)
py_agent << DatagramReq(data = '$A001')
rsp = py_agent.receive()
print (rsp)

UnetSimulation. groovy

//! Simulation : Initialisation python

import org.arl.fjage.RealTimePlatform
import org.arl.fjage.*
import org.arl.unet.*
import org.arl.unet.phy.*
import org.arl.unet.sim.*
import org.arl.unet.sim.channels.*
import static org.arl.unet.Services.*
import static org.arl.unet.phy.Physical.*
//import java.net.ServerSocket

///////////////////////////////////////////////////////////////////////////////
// simulation settings

platform = RealTimePlatform           // use real-time mode

///////////////////////////////////////////////////////////////////////////////
// channel and modem settings

channel = [
  model:                ProtocolChannelModel,
  soundSpeed:           1500.mps,
  communicationRange:   3.km,
  interferenceRange:    3.km
]
modem.model = USMARTModem

modem.dataRate =        [640.bps, 6400.bps]
modem.frameLength =     [16.bytes, 64.bytes]
modem.powerLevel =      [0.dB, -10.dB]
modem.headerLength = 0
modem.preambleDuration = 0
modem.txDelay = 0

///////////////////////////////////////////////////////////////////////////////
// nodes settings and geometry

def beacons = 2..4          // 3 anchors from 2 to 4
def sensors = 5..104          // 100 sensors from 5 to 104
def nodeLocation = [:]

def D = 4000.m  // side of the simulation area
def L = 400.m  // distance between two node

nodeLocation[1] = [D/2-L, D/2 -L, -5.m]     //masterAnchor
nodeLocation[2] = [D/2+L, D/2 -L, -5.m]
nodeLocation[3] = [D/2, D/2+L, -5.m]
nodeLocation[4] = [D/2, D/2, -500.m]

sensors.each { myAddr ->
  nodeLocation[myAddr] = [rnd(0, D), rnd(0, D), rnd(-480.m, -500.m)]
}

///////////////////////////////////////////////////////////////////////////////
// simulation details

simulate {

  node '1', address: 1, location: nodeLocation[1], web: 8101, api: 1101, shell: true, stack: {
      container -> container.add 'py_agent' + 1, new Python_Agent()
  }

  beacons.each { myAddr ->
      def myNode = node("${myAddr}", address: myAddr, location: nodeLocation[myAddr], web: 8100 + myAddr , api: 1100 + myAddr, 
        stack: {container ->
  container.add 'py_agent' + myAddr, new Python_Agent()})
  }   

  sensors.each { myAddr ->
      def myNode = node("${myAddr}", address: myAddr, location: nodeLocation[myAddr], web: 8100 + myAddr, api: 1100 + myAddr,
        stack: {container ->
  container.add 'py_agent' + myAddr, new Python_Agent()})
  }

Python_Agent. groovy

import org.arl.fjage.*
import org.arl.unet.*

enum Services {
    PYTHON_AGENT
}

class Python_Agent extends UnetAgent {

    String fromNode;
    String toNode;
    String toClient;

    def nodeInfo;
    def myLocation;
    def myAddress;
    def phy;

    void setup() {
        register Services.PYTHON_AGENT
    }

    void startup() {
        // TODO
        nodeInfo = agentForService(Services.NODE_INFO)
        myLocation = nodeInfo.location
        myAddress = nodeInfo.address
        println('pyAgent ' + myAddress + ' works')
    }

    void processMessage(Message msg) {

        if (msg instanceof DatagramNtf /*&& msg.protocol == NODE_STATUS_PROTOCOL*/) { 

        println("Node "+ myAddress+ ' receiving ' + msg.text +' from ' + msg.from +" protocol is "+ msg.protocol)
        toNode = phy.energy

    }
  }
}

Первая ошибка, которую я получаю:

1 error
org.codehaus.groovy.control.MultipleCompilationErrorsException: startup failed:
C:\Users\mathi\OneDrive\Bureau\unet-3.0.0\FakeModem\python.groovy: 85: Enum constructor calls are only allowed inside the enum class
. At [85:50]  @ line 85, column 50.
   container.add 'py_agent' + 1, new Python
                                 ^

затем, если я комментирую часть enum и модифицирую ie часть установки, симуляция работает

    void setup() {
        register 'PYTHON_AGENT'
    }

Когда я запускаю PythonSocketExample.py, я получаю ошибку

Traceback (most recent call last):
  File "PythonSocketExample.py", line 11, in <module>
    py_agent = gw.agentForService(Services.PYTHON_AGENT)
AttributeError: type object 'Services' has no attribute 'PYTHON_AGENT'

Конец из журнала на UnetStack здесь:

1582820223131   INFO    Python_Agent/84@1903:println    pyAgent 84 works
1582820223132   INFO    Python_Agent/39@1633:println    pyAgent 39 works
1582820415798   INFO    org.arl.unet.sim.SimulationMasterContainer@48:connected Incoming connection tcp:///137.195.214.230:1101//137.195.214.230.62913
1582820415875   INFO    org.arl.unet.sim.SimulationMasterContainer@2131:connectionClosed    Connection tcp:///137.195.214.230:1101//137.195.214.230.62913 closed

Спасибо за вашу помощь

РЕДАКТИРОВАТЬ

Спасибо за ваше сообщение и некоторые исследования я Теперь можно отправлять и получать сообщения между UnetStack и Python, используя MessageBehavior и GenericMessage. Я хочу, чтобы моя симуляция получала более одного сообщения, но поскольку мой add new MessageBehavior находится в запуске () моего PythonAgent.groovy, мне нужно столько же add new MessageBehavior, сколько и сообщение, которое я отправляю.

Я проверил, чтобы поместить его в processMessage (Message msg), но, похоже, этот метод не распознает GenericMessage ().

Может возникнуть вопрос, как использовать MessageBehavior более одного раза Вот мой код:

Python

ping_test.py

# Need to run the script two times. First time UnetStack don't get the Message
# I don't know where the bug come from

serport = serial.Serial()

## SET SELF ADDRESS
nodeID = '001'
nodeID_ping = '002'
command = b'$A' + nodeID.encode()
serport.write(command)

ack_msg = serport.readline()
print('ack_msg : ', ack_msg)

ping_command = b'$P' + nodeID_ping.encode()
serport.write(ping_command)

ack_msg = serport.readline()
print('ack_msg :', ack_msg)
rsp_msg = serport.readline()
print('rsp_msg :', rsp_msg)

FakeSerial_V2.py

from unetpy import *
import socket
import clientSocket

# a Serial class emulator
class Serial:

    ## init(): the constructor.  Many of the arguments have default values
    # and can be skipped when calling the constructor.
    def __init__( self, port='5000', baudrate = 19200, timeout=1, write_timeout=1,
                  bytesize = 8, parity = 'N', stopbits = 1, xonxoff=0,
                  rtscts = 0):

        self.last_instruction = ''
        self.nodeID = ''
        self.remote_nodeID = ''
        self.command = ''
        self._isOpen  = True
        self._receivedData = ''
        self._data = 'It was the best of times.\nIt was the worst of times.\n'
        self.phy = ''
        self.pySocket = ''

    ## write()
    # writes a string of characters to the Arduino
    def write( self, string):
        self.command = string.decode()
        _type = None
        print( 'FakeSerial got: ' + self.command)
        # SET_ADDRESS
        if (self.command[0:2] == '$A' and len(self.command) == 5):
            _type = 'set_address'
            self.nodeID = string[2:]
            self.pySocket = clientSocket.clientSocket(self.nodeID)  # initialize the clientSocket class
            self.pySocket.sendData(_type)    # need to fix the rsp Generic Message on UnetStack
            self.last_instruction = 'SET_ADDRESS_INSTRUCTION'
        # PING
        elif (self.command[0:2] == '$P' and len(self.command) == 5):
            _type = 'ping'
            to_addr = self.command[2:]
            # print(to_addr, type(to_addr))
            self.pySocket.sendData(_type, to_addr)
            self.last_instruction = "PING_INSTRUCTION"

        else:
            print("write FAILURE")


    ## readline()
    # reads characters from the fake Arduino until a \n is found.
    def readline( self ):
        self._receivedData = self.pySocket.receiveData()
        return self._receivedData

clientSocket.py

import socket
from unetpy import *
from fjagepy import *

class clientSocket:

    def __init__(self, nodeID = '001'):

        self.host = socket.gethostname()
        self.nodeID = int(nodeID)
        self.port = int(nodeID) + 1100
        self.sock = UnetSocket(self.host, self.port)
        self.gw = self.sock.getGateway()
        self.pyagent = 'pyagent' + str(self.nodeID)

    def sendData(self, _type, to_addr = '000' , data = 'None'):
        IDreq = 1

        # gmsg = GenericMessage(perf = Performative.REQUEST, recipient = pyagent)
        # gmsg.IDreq = IDreq
        # self.gw.send(gmsg)

        IDreq = IDreq + 1

        gmsg2 = GenericMessage(perf = Performative.REQUEST, recipient = self.pyagent)
        gmsg2.type = _type
        gmsg2.from_addr = self.nodeID
        gmsg2.to_addr = int(to_addr)
        gmsg2.data = data
        gmsg2.IDreq = IDreq
        self.gw.send(gmsg2)

        IDreq = 0

    def receiveData( self ):
        rgmsg = self.gw.receive(GenericMessage, 4000)
        print ('UnetStack state :', rgmsg.state)
        # print ('rsp :', rgmsg.data)
        # print('Ping time is', rgmsg.time_ping, 'ms')

        return rgmsg.data

Groovy

sim1.groovy

import org.arl.fjage.RealTimePlatform

import org.arl.fjage.*
import org.arl.unet.*
import org.arl.unet.sim.channels.*

platform = RealTimePlatform           // use real-time mode

///////////////////////////////////////////////////////////////////////////////
// channel and modem settings

channel = [
  model:                ProtocolChannelModel,
  soundSpeed:           1500.mps,
  communicationRange:   3.km,
  interferenceRange:    3.km
]

modem.model = USMARTModem

modem.dataRate =        [640.bps, 6400.bps]
modem.frameLength =     [16.bytes, 64.bytes]
modem.powerLevel =      [0.dB, -10.dB]
modem.headerLength = 0
modem.preambleDuration = 0
modem.txDelay = 0

simulate {
    node '1', address: 1, web: 8101, api: 1101, stack: {
        container -> container.add 'pyagent1', new PythonAgent()
    }

    node '2', address: 2,location: [500.m ,500.m, -500.m], web: 8102, api: 1102, stack: {
        container -> container.add 'pyagent2', new PythonAgent()
    }
}

PythonAgent.groovy

import org.arl.fjage.*
import org.arl.unet.*
import org.arl.unet.phy.RxFrameNtf
import org.arl.unet.phy.TxFrameNtf

class PythonAgent extends UnetAgent {

    final static int PING_PROTOCOL = 10;
    final static int NODE_STATUS_PROTOCOL = 11;
    final static int BROADCAST_PROTOCOL = 12;
    final static int UNICAST_PROTOCOL = 13;
    final static int UNICAST_ACK_PROTOCOL = 14;
    final static int TEST_MSG_PROTOCOL = 15;
    final static int ECHO_PROTOCOL = 16;
    final static int QUALITY_PROTOCOL = 17;

    def nodeInfo;
    def phy;
    def myLocation;
    def myAddress;
    def IDreq = 0;
    def time_ping = null;
    def function_state = null;
    def data_to_py = null;

    void startup() {
        println(agentID.name + ' running')

    nodeInfo = agentForService Services.NODE_INFO
    phy = agentForService Services.PHYSICAL
    myLocation = nodeInfo.location
    myAddress = nodeInfo.address

    subscribe topic(phy)

    add new MessageBehavior(GenericMessage, { req ->
        println("In PythonAgent::MessageBehavior req ="+req)
        if (req.performative) println("req.performative is " + req.performative)
        else println("req.performative is null")

        def ack = new GenericMessage(req, Performative.INFORM)
        def rsp = new GenericMessage(req, Performative.INFORM)
        println('IDreq = ' + req.IDreq)

        if ((req.performative == Performative.REQUEST) && (req.IDreq == 2)) {
            // IDreq = req.IDreq
            // println('IDreq = ' + IDreq)
            //log.info "Generic message request of type ${req.type}"
            function_state = 'None';
            data_to_py = 'None';

            switch (req.type) {
                case 'set_address':
                    println("Handling set_address")
                    ack.state = "Handling set_address"
                    ack.data = '#A' + corrected_address(myAddress);
                    send ack;
                    rsp.data = ack.data; break;
            }
        }
    })

    add new MessageBehavior(GenericMessage, { req ->
        println("In PythonAgent::MessageBehavior req ="+req)
        if (req.performative) println("req.performative is " + req.performative)
        else println("req.performative is null")

        def ack = new GenericMessage(req, Performative.INFORM)
        def rsp = new GenericMessage(req, Performative.INFORM)
        println('IDreq = ' + req.IDreq)

        if ((req.performative == Performative.REQUEST) && (req.IDreq == 2)) {
            // IDreq = req.IDreq
            // println('IDreq = ' + IDreq)
            //log.info "Generic message request of type ${req.type}"
            function_state = 'None';
            data_to_py = 'None';

            switch (req.type) {
                case 'set_address':
                    println("Handling set_address")
                    ack.state = "Handling set_address"
                    ack.data = '#A' + corrected_address(myAddress);
                    send ack;
                    rsp.data = ack.data; break;
                case 'loc':
                    //println("Handling localisation request");
                    sendUPSBeacon(); break;
                case 'ping':
                    println("Handling ping request");
                    ack.state = "Handling ping request"; ack.data = '$P' + corrected_address(req.to_addr);
                    send ack;
                    ping(req.to_addr);
                    rsp.time_ping = time_ping; break;
                case 'exe':
                    //println("Handling exe request"); 
                    exe(); break;
                case 'sense':
                    //println("Handling sense request"); 
                    sense(); break;
                default: println "Unknown request";
            }
            //println "In USMARTBaseAnchorDaemon::MessageBehavior, just after exe"

            rsp.state = function_state

            rsp.data = data_to_py
            println "In PythonAgent::MessageBehavior, rsp is " + rsp     
            send rsp
        }
    })

    }

    void ping(to_addr) {

        println "Pinging ${to_addr} at ${nanoTime()}"

        DatagramReq req = new DatagramReq(to: to_addr, protocol: PING_PROTOCOL)
        phy << req
        def txNtf = receive(TxFrameNtf, 10000) // TO-DO:check protocol
        def rxNtf = receive({ it instanceof RxFrameNtf && it.from == req.to}, 10000)
        if (txNtf && rxNtf && rxNtf.from == req.to) {
            time_ping = (rxNtf.rxTime-txNtf.txTime)/1000 //in ms
            println("Response from ${rxNtf.from}: ")
            println("rxTime=${rxNtf.rxTime}")
            println("txTime=${txNtf.txTime}")
            println("Response from ${rxNtf.from}: time = ${time_ping}ms")
            function_state = 'Ping processed'
            data_to_py = "#R" + corrected_address(to_addr) + 'T' + rxNtf.data
        }
        else {
            function_state = 'Ping Request timeout'
            println (function_state)

        }

    }

    @Override
    void processMessage(Message msg) {
        // pong
        if (msg instanceof DatagramNtf && msg.protocol == PING_PROTOCOL) {
            println("pong : Node "+ myAddress + ' from ' + msg.from +" protocol is "+ msg.protocol)
            send new DatagramReq(recipient: msg.sender, to: msg.from, data: phy.energy as byte[], protocol: PING_PROTOCOL)
            println ('processMessage energy : ' + phy.energy)
        }
    }

    String corrected_address(address) {
        address = address.toString()
        if (address.size() == 1) address = '00' + address
        if (address.size() == 2) address = '0' + address
        return address
    }
}

USMARTModem.groovy

import org.arl.fjage.Message
import org.arl.unet.sim.HalfDuplexModem

import org.arl.fjage.*
import org.arl.unet.*
import org.arl.unet.phy.*
import org.arl.unet.sim.*
import org.arl.unet.sim.channels.*
import static org.arl.unet.Services.*
import static org.arl.unet.phy.Physical.*

/*

Ptx=  V*Itx //power consumed in transmission in watt
    Prx = V*Irx //power consumed in receiving packets in watt
    Etx = Math.floor(avgSent)*(Ptx*0.3675)
    energyAll =  (Math.floor(avgSent)*(Ptx*0.3675)) + (Math.floor(avgReceived)*(Prx*0.3675)) // total energy consumed for all the packets sent and received throughout the simulation
   // EtxSubset = Math.floor(avgTxCountNs)*(Ptx*0.3675) // energy consumed in transmitiing 25% of packets in Joul
    bytesDelivered = Math.floor(avgReceived)* modem.frameLength[1]
    JPerBit = energyAll/(bytesDelivered * 8)

*/
//Duration of data packet in seconds = data packet size (in bits)/bandwidth (in bps) = (15*8)/50000 = 0.0024


class USMARTModem extends HalfDuplexModem {

  static final def txPower = -17.dB
  static final def acousticDataRate = 640.bps
  static final def payLoadSize = 5.bytes
  static final def headerDuration = (30+75+200)/1000  //in seconds --> in our case nanomodem v3 provides us with the header (in ms) to add to the actual payload in the frame length.. refer to the modem datasheet
  static final def V = 5 // supply voltage in volt
  static final def Itx = 0.3, Irx = 0.005, Iidle =  0.0025 //current in Am

  float payLoadDuration = (payLoadSize*8)/acousticDataRate //in seconds
  float dataPacketDuration = payLoadDuration +headerDuration //in seconds
  float energy = 2000 //initial energy in Joule
  float test = energy+1
  float Ptx = V*Itx, Prx=V*Irx, Pidle = V*Iidle //power in watt
  float totalEtx =0
  float totalErx =0
  float totalEidle =0
  float totalEnergyConsumed =0 
  float Etx = Ptx * dataPacketDuration //Energy in Joul
  float Erx = Prx * dataPacketDuration
  float Eidle = Pidle * dataPacketDuration



 // float power = 0.00001995262315 //in watt (-17 in db=10log(p/1mw)  .. so p = 10to the power -1.7  = 0.00001995262315
 // BigDecimal Ptx = (Math.pow(10.0,(txPower/10) ))/1000 //????
 // BigDecimal Etx= Ptx *((frameLength[1]*8)/640) // This is consumed energy (in transmission) Etx = Ptx*time it takes to tramsnit tha packet
 //float Etx =10


  @Override
  boolean send(Message m) {
    if (m instanceof TxFrameNtf)
        {
          energy -= Etx// Remaining energy
          totalEtx += Etx  //total energy consumed in tx
        }
    if (m instanceof  RxFrameNtf) 
        {
          energy -= Erx   // Remaining energy
          totalErx += Erx  //total energy consumed in rx
         }

    if(!busy) 
     { 
       energy-= Eidle //Remaining energy
       totalEidle += Eidle //total energy consumed while Eidle
     } 


     totalEnergyConsumed = totalEtx+totalErx+totalEidle
    return super.send(m)
  }

}

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

Ответы [ 2 ]

2 голосов
/ 28 февраля 2020

Несколько проблем в исходном коде:

  1. Вам не нужно создавать службу, поскольку вы можете обратиться к агенту по его имени. Этого должно быть достаточно для вашего примера.
  2. Для обработки запроса (DatagramReq из вашего Python кода) необходимо переопределить метод processRequest() в агенте.

Вот упрощенный пример, основанный на вашем исходном коде:

PythonAgent.groovy:

import org.arl.fjage.*
import org.arl.unet.*

class PythonAgent extends UnetAgent {

    void startup() {
        println('pyAgent running')
    }

    @Override
    Message processRequest(Message msg) {
        if (msg instanceof DatagramReq) { 
            println('Got a DatagramNtf')
            // do whatever you want with the request
            return new Message(msg, Performative.AGREE)
        }
        return null
    }
}

sim1.groovy:

import org.arl.fjage.RealTimePlatform

platform = RealTimePlatform           // use real-time mode

simulate {
    node '1', address: 1, web: 8101, api: 1101, stack: {
        container -> container.add 'pyagent', new PythonAgent()
    }
}

и test1.py:

from unetpy import *
from fjagepy import *

sock = UnetSocket('localhost', 1101)   # node 1's API port as per sim script
gw = sock.getGateway()
pyagent = gw.agent('pyagent')          # agent name as per sim script
rsp = pyagent << DatagramReq()
print(rsp)
0 голосов
/ 02 марта 2020

Спасибо, я не знал, что мне нужно @ Overide. У меня все еще есть вопрос, как я могу поместить данные в мой DatagramReq, которые я могу извлечь в UnetStack? Я попробовал это как первое решение, глядя на Справочник, но он не работает ..

PythonAgent.groovy

import org.arl.fjage.*
import org.arl.unet.*

class PythonAgent extends UnetAgent {

    void startup() {
        println('pyAgent running')
    }

    @Override
    Message processRequest(Message msg) {
        if (msg instanceof DatagramReq) { 
            println('Got a DatagramNtf')
            println(msg.data)
            // do whatever you want with the request
            return new Message(msg, Performative.AGREE)
        }
        return null
    }
}

test1.py

from unetpy import *
from fjagepy import *

sock = UnetSocket('localhost', 1101)   # node 1's API port as per sim script
gw = sock.getGateway()
pyagent = gw.agent('pyagent')         # agent name as per sim script
rsp1 = pyagent << DatagramReq( data = [42])
rsp2 = pyagent << DatagramReq( data = 'data_string')
print(rsp1, rsp2)

На терминале Python я получу Agree None. Я могу передать массив, но не строку?

Печать журнала

Incoming connection tcp:///127.0.0.1:1101//127.0.0.1.51208
1583166206032   INFO    PythonAgent/1@2643:println  Got a DatagramNtf
1583166206032   INFO    PythonAgent/1@2643:println  [B@4e3d88df
1583166206033   WARNING org.arl.fjage.remote.ConnectionHandler@2670:run Bad JSON request: java.lang.IllegalArgumentException: Illegal base64 character 5f in {"action": "send", "relay": true, "message": { "clazz": "org.arl.unet.DatagramReq", "data": {"msgID":"492ac9dd-c2bf-4c0c-9198-3b32fb416f33","perf":"REQUEST","recipient":"pyagent","sender":"PythonGW-c8e66e0f-b5d5-433b-bfa9-09be708ab4c9","inReplyTo":null,"data":"data_string"} }}
1583166207081   INFO    org.arl.unet.sim.SimulationMasterContainer@2670:connectionClosed    Connection tcp:///127.0.0.1:1101//127.0.0.1.51208 closed

[B@4e3d88df соответствует [42], но я не знаю, как его расшифровать. И на самом деле меня больше интересует отправка строки, а не массива. У меня есть трек об использовании PDU, но как он может работать с Python?

...