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
enddefines 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::SymbolmessageID::StringinReplyTo::Stringsender::AgentIDrecipient::AgentIDsentAt::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
trueIt 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
truePerformatives 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
:INFORMMessage 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
trueAPI
Fjage.GenericMessage — Type
Generic message type that can carry arbitrary name-value pairs as data.
Fjage.Message — Type
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.
Fjage.Message — Type
Base class for messages transmitted by one agent to another.
Fjage.ParameterReq — Type
Parameter request message.
Fjage.ParameterRsp — Type
Parameter response message.
Fjage.classname — Function
classname(msg::Message)Return the fully qualified class name of a message.
Fjage.registermessage — Method
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"Fjage.registermessages — Function
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.
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 ...]Fjage.@message — Macro
@message classname [performative] struct mtype [<: supertype]
fields...
endCreate 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"]