gradlegroovyunetstack

Running UnetStack simulations using a build script (gradle)


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.


Solution

  • 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.