I wish to run Unet simulations with a build tool (gradle), and I have tried to replicate this UnetStack blogpost where a description of running simulations using the IntelliJ IDE is given: https://blog.unetstack.net/using-idea-with-unetstack
When I run the example using IntelliJ, the example code prints "Hello world" in the log-file and leaves the shell agent running, as expected. When I compile Unet and insert the simulation script using gradle, the Unet shell agent initiates shutdown after the simulation is started.
Minimal working example. -----------------
Initiate a gradle groovy application project by running "gradle init" with the following inputs:
application -> groovy -> no -> groovy -> helloworld -> helloworld -> 21 -> no.
I have used gradle version 8.5, groovy 3.0.17, and Java 21.
The root project structure is now
app/
gradle/
.gradle/
gradlew
gradlew.bat
settings.gradle
.gitattributes
.gitignore
Download Unet (https://unetstack.net/) and copy "unet-3.4.0" from "unet-community-3.4.0/" to the project root, and create the folder "app/logs/".
Delete "app/src/main/groovy/helloworld/App.groovy" and "app/src/test/groovy/helloworld/AppTest.groovy".
Add "AwesomeAgent.groovy" and "simple_sim.groovy" to "app/src/main/groovy/helloworld/", containing the following code (copied from the blogpost):
AwesomeAgent.groovy
import org.arl.fjage.Message
import org.arl.unet.UnetAgent
class AwesomeAgent extends UnetAgent {
void setup() {
log.info("Hello world!")
}
void startup() {
// this method is called just after the stack is running
// look up other agents and services here, as needed
// subscribe to topics of interest here, to get notifications
}
Message processRequest(Message msg) {
// process requests supported by the agent, and return responses
// if request is not processed, return null
return null
}
void processMessage(Message msg) {
// process other messages, such as notifications here
}
}
simple_sim.groovy
import org.arl.fjage.RealTimePlatform
import org.arl.unet.link.ReliableLink
import org.arl.unet.sim.channels.ProtocolChannelModel
platform = RealTimePlatform
channel.model = ProtocolChannelModel
simulate {
node '1', address: 1, location: [0, 0, 0], shell: true, stack: { container ->
container.add 'da', new AwesomeAgent()
container.add 'link', new ReliableLink()
}
}
Replace the contents of "app/build.gradle" with
plugins {
// Apply the groovy Plugin to add support for Groovy.
id 'groovy'
// Apply the application plugin to add support for building a CLI application in Java.
id 'application'
}
java {
toolchain {
languageVersion.set(JavaLanguageVersion.of(21))
}
}
repositories {
// Use Maven Central for resolving dependencies.
mavenCentral()
}
dependencies {
// Resolve unet version (folder name) and add library files to project ---------
String unet_dir=''
String root_dir=((String)projectDir)+'/..' // Project root
new File(root_dir).eachFile { file ->
if ( file.name.contains('unet') ){
unet_dir=file.name
}
}
if (unet_dir==''){
println 'unet is missing in project root directory'
}
String unet_lib=unet_dir+'/lib'
// Add all jar files from unet except groovy
println 'looking for unet jars in: ' + root_dir+'/'+unet_lib
new File(root_dir+'/'+unet_lib).eachFile { file ->
if ( file.name.contains('.jar') && !file.name.contains('groovy') ){
implementation files('../'+unet_lib+'/'+file.name) // Relative to this build script
}
}
// Use the latest Groovy version for building this library
implementation libs.groovy.all
// This dependency is used by the application.
implementation libs.guava
// Use the awesome Spock testing and specification framework even with Java
testImplementation libs.spock.core
testImplementation libs.junit
testRuntimeOnly 'org.junit.platform:junit-platform-launcher'
}
tasks.named('test') {
// Use JUnit Platform for unit tests.
useJUnitPlatform()
}
application { // "gradle run"
// Define the main class for the application (using run).
mainClass = 'org.arl.fjage.shell.GroovyBoot' // Boots sim script as input
}
run {
standardInput = System.in
// Insert these arguments into the simulator
args=["cls://org.arl.unet.sim.initrc", "src/main/groovy/helloworld/simple_sim.groovy"]
}
The difference from the blogpost is that the build script finds the unet/lib jar-files and adds them as implementation dependencies, and that the file input is defined in the run configuration.
When I run the above example ("gradle run" from the project root), unet succesfully loads the AwesomeAgent and prints the shell prompt ">" once in the terminal; however, the shell agent then initiates shutdown unexpectedly. Here are the contents of the log-file:
1704206651962|INFO|org.arl.fjage.shell.GroovyBoot@1:main|fjage Build: fjage-1.9.1/37c49259/5-09-2021_13:26:12
1704206652270|INFO|org.arl.fjage.shell.GroovyBoot@1:main|Running cls://org.arl.unet.sim.initrc
1704206652273|INFO|org.arl.fjage.remote.EnumTypeAdapter@1:<clinit>|Groovy detected, using GroovyClassLoader
1704206652280|INFO|org.arl.fjage.remote.MessageAdapterFactory@1:<clinit>|Groovy detected, using GroovyClassLoader
1704206652313|INFO|org.arl.fjage.shell.GroovyBoot@1:main|Running src/main/groovy/helloworld/simple_sim.groovy
1704206652419|INFO|org.arl.unet.nodeinfo.NodeInfo@1:setAddress|Node address changed to 1
1704206652423|INFO|Script1@1:call|Starting console shell
1704206652496|INFO|Script1@1:doInvoke|Created static node 1 (1) @ [0, 0, 0]
1704206652506|INFO|Script1@1:doInvoke| --- BEGIN SIMULATION #1 ---
1704206652508|INFO|org.arl.unet.sim.SimulationContainer@1:init|Initializing agents...
1704206652508|INFO|org.arl.fjage.shell.ShellAgent/1@33:init|Agent shell init
1704206652508|INFO|AwesomeAgent/1@34:init|Loading agent da [AwesomeAgent] on 1
1704206652509|INFO|AwesomeAgent/1@34:call|Hello world!
1704206652509|INFO|org.arl.unet.sim.SimulationAgent/1@38:doInvoke|Loading simulator : SimulationAgent
1704206652509|INFO|org.arl.unet.nodeinfo.NodeInfo/1@37:init|Loading agent node v3.4.0/93008cfa/30-10-2021_02:33:22 [org.arl.unet.nodeinfo.NodeInfo] on 1
1704206652509|INFO|org.arl.unet.link.ReliableLink/1@36:init|Loading agent link v3.4.0/93008cfa/30-10-2021_02:33:22 [org.arl.unet.link.ReliableLink] on 1
1704206652509|INFO|org.arl.unet.sim.HalfDuplexModem/1@35:init|Loading agent phy v3.4.0/93008cfa/30-10-2021_02:33:22 [org.arl.unet.sim.HalfDuplexModem] on 1
1704206652609|INFO|org.arl.unet.sim.SimulationContainer@1:init|Agents ready...
1704206652609|INFO|org.arl.unet.sim.SimulationContainer@1:start|Starting container...
1704206652610|INFO|org.arl.unet.link.ReliableLink/1@36:startup|No PHY specified, auto detecting...
1704206652610|INFO|org.arl.unet.nodeinfo.NodeInfo/1@37:obtainAddress|Node name is 1, address is 1, address size is 8 bits
1704206652610|INFO|org.arl.unet.link.ReliableLink/1@36:startup|Using agent 'phy' for PHY
1704206652610|INFO|org.arl.unet.link.ReliableLink/1@36:startup|No MAC specified, auto detecting...
1704206652610|INFO|org.arl.unet.link.ReliableLink/1@36:startup|No MAC detected, continuing without MAC
1704206652614|INFO|org.arl.fjage.shell.ShellAgent/1@33:shutdown|Agent shell shutdown
1704206652614|INFO|org.arl.unet.sim.SimulationContainer@43:shutdown|Initiating shutdown...
1704206652614|INFO|org.arl.unet.sim.SimulationContainer@43:shutdown|All agents have shutdown
Since I am using platform = RealTimePlatform
in "simple_sim.groovy", the simulation should not terminate. Any help with resolving this issue would be much appreciated.
The run
task does not connect your terminal to the started process.
Due to that, I guess the started process sees the end of stdin and thus terminates.
If you configure
tasks.run {
standardInput = System.in
}
it should most probably work as you intend.