Messages

Messages are data containers that carry information between agents. When interacting with Java/Groovy agents, messages are mapped to Java/Groovy message classes with fields with the same name as the keys in the message.

Message types are defined using the @message macro. For example:

@message "org.arl.fjage.shell.ShellExecReq" struct ShellExecReq
  command::Union{String,Nothing} = nothing
  ans::Bool = false
end

defines a ShellExecReq message type that maps to a Java class with the package org.arl.fjage.shell.ShellExecReq.

All messages are mutable. The @message macro also automatically adds a few fields:

  • performative::Symbol
  • messageID::String
  • inReplyTo::String
  • sender::AgentID
  • recipient::AgentID
  • sentAt::Int64

Messages can subtype other messages:

julia> abstract type MyAbstractMessage <: Message end

julia> @message "org.arl.fjage.demo.MyConcreteMessage" struct MyConcreteMessage <: MyAbstractMessage
        a::Int
      end

julia> MyConcreteMessage(a=1)
MyConcreteMessage:INFORM[a:1]

julia> MyConcreteMessage(a=1) isa MyAbstractMessage
true

It is also possible to have a concrete message type that can also be a supertype of another message:

julia> abstract type SomeMessage <: Message end

julia> @message "org.arl.fjage.demo.SomeMessage" struct SomeMessage <: SomeMessage
        a::Int
      end

julia> @message "org.arl.fjage.demo.SomeExtMessage" struct SomeExtMessage <: SomeMessage
        a::Int
        b::Int
      end

julia> SomeMessage(a=1) isa SomeMessage
true

julia> SomeExtMessage(a=1, b=2) isa SomeMessage
true

Performatives are guessed automatically based on message classname. By default, the performative is Performative.INFORM. If a message classname ends with a Req, the default performative changes to Performative.REQUEST. Performatives may be overridden at declaration or at construction (and are mutable):

julia> @message "org.arl.fjage.demo.SomeReq" struct SomeReq end;
julia> @message "org.arl.fjage.demo.SomeRsp" Performative.AGREE struct SomeRsp end;

julia> SomeReq().performative
:REQUEST

julia> SomeRsp().performative
:AGREE

julia> SomeRsp(performative=Performative.INFORM).performative
:INFORM

Message types defined with @message are automatically registered with Fjage, so that gateways and containers can serialize and deserialize them using the correct type. A message type defined by other means (e.g. with Base.@kwdef) can be registered explicitly using registermessage, provided it has the standard message fields and can be constructed with no arguments:

julia> Base.@kwdef mutable struct MyNtf <: Message
         value::Float64 = 0.0
         messageID::String = string(Fjage.uuid4())
         performative::Symbol = Performative.INFORM
         sender::Union{AgentID,Nothing} = nothing
         recipient::Union{AgentID,Nothing} = nothing
         inReplyTo::Union{String,Nothing} = nothing
         sentAt::Int64 = 0
       end;

julia> registermessage("org.arl.fjage.demo.MyNtf", MyNtf)
MyNtf

julia> classname(MyNtf())
"org.arl.fjage.demo.MyNtf"

Incoming messages whose class is not registered are deserialized as a GenericMessage that retains the original class name.

When strict typing is not required, one can use the dictionary-like GenericMessage message type:

julia> msg = GenericMessage("org.arl.fjage.demo.DynamicMessage")
DynamicMessage:INFORM

julia> msg.a = 1
1

julia> msg.b = "xyz"
"xyz"

julia> msg
DynamicMessage:INFORM[a:1 b:"xyz"]

julia> classname(msg)
"org.arl.fjage.demo.DynamicMessage"

julia> msg isa GenericMessage
true

API

Fjage.MessageType
msg = Message([perf])
msg = Message(inreplyto[, perf])

Create a message with just a performative (perf) and no data. If the performative is not specified, it defaults to INFORM. If the inreplyto is specified, the message inReplyTo and recipient fields are set accordingly.

source
Base.get!Method
get!(p::ParameterReq, param)

Request parameter param to be fetched.

Examples

julia> p = ParameterReq(index=1)
ParameterReq[index=1]
julia> get!(p, "modulation")
ParameterReq[index=1 modulation=?]
julia> get!(p, "fec")
ParameterReq[index=1 modulation=? ...]
source
Base.getMethod
get(p::ParameterRsp, param)

Extract parameter param from a parameter response message.

source
Fjage.classnameFunction
classname(msg::Message)

Return the fully qualified class name of a message.

source
Fjage.registermessageMethod
registermessage(classname, T)

Register message type T under the fully qualified classname, so that gateways and containers serialize instances of T with clazz set to classname, and deserialize incoming messages with that clazz as instances of T. Returns T.

Message types defined using @message are registered automatically, so this function is only needed for message types defined by other means. T must be a concrete subtype of Message, constructible with no arguments (e.g. via Base.@kwdef), and must have the standard message fields (messageID, performative, sender, recipient, inReplyTo and sentAt). The Julia type hierarchy of T is used to represent the message class hierarchy.

If classname is already registered to a different type, a warning is logged and the registration is replaced.

Examples

julia> Base.@kwdef mutable struct MyNtf <: Message
         value::Float64 = 0.0
         messageID::String = string(Fjage.uuid4())
         performative::Symbol = Performative.INFORM
         sender::Union{AgentID,Nothing} = nothing
         recipient::Union{AgentID,Nothing} = nothing
         inReplyTo::Union{String,Nothing} = nothing
         sentAt::Int64 = 0
       end;

julia> registermessage("org.arl.fjage.demo.MyNtf", MyNtf)
MyNtf

julia> classname(MyNtf())
"org.arl.fjage.demo.MyNtf"
source
Fjage.registermessagesFunction
registermessages()
registermessages(messageclasses)

Register message classes with Fjage. Usually message classes are automatically registered on creation with @message. However, when developing packages, if @message is used at the module level, the types may be precompiled and the code to register the classes may not get executed at runtime. In such cases, you may need to explicitly call registermessages() in the __init__() function for the module.

source
Fjage.set!Method
set!(p::ParameterReq, param, value)

Request parameter param to be set to value.

Examples

julia> p = ParameterReq(index=1)
ParameterReq[index=1]
julia> set!(p, "modulation", "ofdm")
ParameterReq[index=1 modulation=ofdm]
julia> set!(p, "fec", 1)
ParameterReq[index=1 modulation=ofdm ...]
source
Fjage.@messageMacro
@message classname [performative] struct mtype [<: supertype]
  fields...
end

Create a message class from a fully qualified class name. If a performative is not specified, it is guessed based on the class name. For class names ending with "Req", the performative is assumed to be REQUEST, and for all other messages, INFORM.

Examples

julia> @message "org.arl.fjage.shell.MyShellExecReq" struct MyShellExecReq
         command::String
       end
julia> req = MyShellExecReq(command="ps")
MyShellExecReq:REQUEST[command:"ps"]
source