如何将 Python UnetSocket 连接到我使用 agentForService 创建的代理?

How to connect a Python UnetSocket to an Agent that I have created using agentForService?

我正在尝试在 Python 脚本和 UnetSim 上的模拟 运行ning 之间创建一个 link。 我想使用我创建并添加到模拟中每个节点的容器的 Python_Agent.groovy 从 Python 发送消息并在 UnetStack 中接收它们。我也想反过来

我使用 fjage 文档 (https://buildmedia.readthedocs.org/media/pdf/fjage/dev/fjage.pdf) 来帮助我。问题是在网关 class 服务中没有我创建的 PYTHON_AGENT 服务。我可以理解,因为我的枚举服务没有修改 class 有 NODE_INFO、PHYSICAL 等的服务... 那么我的问题是文档 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
                                 ^

然后如果我注释枚举部分并修改设置部分,模拟就可以工作

    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

感谢您的帮助

编辑

感谢您的消息和一些研究,我现在可以使用 MessageBehavior 和 GenericMessage 在 UnetStack 和 Python 之间发送和接收消息。 我希望我的模拟接收到不止一条消息,但是因为我的 add new MessageBehavior 在我的 PythonAgent.groovy 的 startup() 中,所以我需要的 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)
  }
      
}

抱歉拖了很长很长的时间post...我认为一切都是理解代码所必需的

你原代码中的几个问题:

  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)

谢谢,我不知道我需要@Override。我还有一个问题,如何将可以在 UnetStack 中提取的数据放入 DatagramReq 中? 我尝试将此作为查看手册的第一个解决方案,但它不起作用..

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 一起使用?