diff --git a/.gitignore b/.gitignore index 8c029e8..7778f8b 100644 --- a/.gitignore +++ b/.gitignore @@ -1,8 +1,12 @@ -application/src-gen -application/bin -application/build +application/*/src-gen +application/*/fed-gen +application/*/bin +application/*/build +application/*/include +application/*/*build/** +.vscode scripts/__pycache__ build/ -deps/ \ No newline at end of file +deps/ diff --git a/README.md b/README.md index 3fb1b92..e75fde5 100644 --- a/README.md +++ b/README.md @@ -26,40 +26,231 @@ If `west` is installed in a virtual environment, this environment is assumed act 1. Clone this template and remove old git history: ``` -git clone https://github.com/lf-lang/lf-zephyr-template/ lf-zephyr-app -cd lf-zephyr-app +$ git clone https://github.com/lf-lang/lf-zephyr-template/ lf-zephyr-app +$ cd lf-zephyr-app ``` 2. Clone the Zephyr project into `deps/zephyr`: ``` -west update +$ west update ``` 3. Install Zephyr python dependencies: ``` -pip3 install -r deps/zephyr/scripts/requirements.txt +$ pip3 install -r deps/zephyr/scripts/requirements.txt ``` ### Build & Run #### QEMU emulation ``` -cd application -west lf-build src/HelloWorld.lf -w "-t run" +$ cd application/HelloWorld +$ west lf-build src/HelloWorld.lf -w "-t run" ``` -#### Nrf52 blinky +#### Blinky example -This requires that the `nrfjprog` utility is installed. See installation guide [here](https://www.nordicsemi.com/Products/Development-tools/nrf-command-line-tools/download) +To run the blinky example on Nrf52 requires that the `nrfjprog` utility is installed. See installation guide [here](https://www.nordicsemi.com/Products/Development-tools/nrf-command-line-tools/download) ``` -cd application -west lf-build src/NrfBlinky.lf -w "-b nrf52dk_nrf52832 -p always" -west flash +$ cd application/Blinky +$ west lf-build src/Blinky.lf -w "-b nrf52dk_nrf52832 -p always" +$ west flash ``` The custom `lf-build` west command can be inspected in `scripts/lf_build.py`. It invokes `lfc` on the provided LF source file. It then invokes `west build` on the generated sources. See `west lf-build -h` for more information. + + +## Federated Execution +Experimental federated execution is available for the Zephyr platform, tested with the board mimxrt1170_evk_cm7. + +### Network Setup +To connect the sockets of an external federate (running on MCUs) and the RTI (running on the linux machine) both Zephyr configuration and network interface configuration with linux is necessary. The ethernet interface connecting the physical units must be initialized with an IP addresses on both ends. + +#### Linux-side Setup +Check all network interface addresses on the linux machine by running + +``` +$ ip address +``` + +The relevant ethernet interface should start with an 'e'. If several possible interfaces appear, then it is possible to check further hardware information (like connection info) on the suspected interface with the command + +``` +$ sudo dmesg | grep -i +``` + +This should make it possible to differentiate different ethernet interfaces. The linux machine side can then be assigned an address (in this example: 192.0.2.2) as follows + +``` +$ sudo ip address add dev 192.0.2.2 +``` + +To verify the interface address, check the interface addresses again. This address must match the IP address supplied in the federated LF code, ie. the RTI address specified in the code as + +``` +federated reactor at 192.0.2.2 {...} +``` + +Then, add an entry to the routing table with gateway and netmask information, with the following command. This is needed in order to correctly route the data traffic. + +``` +$ sudo ip route add 192.0.2.0/24 dev +``` + +The routing table entries can be checked with + +``` +$ ip route +``` + +Take special note that these changes are not necessarily persistent, and will be lost with a restart, unless the commands are added to the user's script file or the linux distributions configuration files. + +#### Board-side Setup +On the federate application side, networking and the POSIX API must be enabled and configured with Zephyr configuration options in the file `prj_.conf`. The following is a base configuration file to work from, and is provided in the federated program examples. In this example, the federate is assigned the IP address 192.0.2.1. + +``` +# General config +CONFIG_MAIN_STACK_SIZE=4096 +CONFIG_TEST_RANDOM_GENERATOR=y +CONFIG_INIT_STACKS=y +CONFIG_POSIX_API=y +CONFIG_POSIX_MAX_FDS=32 + +# Generic networking options +CONFIG_NETWORKING=y +CONFIG_NET_UDP=y +CONFIG_NET_TCP=y +CONFIG_NET_IPV4=y +CONFIG_NET_SOCKETS=y +CONFIG_NET_L2_ETHERNET=y +CONFIG_NET_MAX_CONN=32 +CONFIG_NET_MAX_CONTEXTS=32 + +# Network buffers +CONFIG_NET_PKT_RX_COUNT=32 +CONFIG_NET_PKT_TX_COUNT=32 +CONFIG_NET_BUF_RX_COUNT=128 +CONFIG_NET_BUF_TX_COUNT=128 +CONFIG_NET_BUF_FIXED_DATA_SIZE=y +CONFIG_NET_BUF_DATA_SIZE=1024 + +# Network socket timeout +CONFIG_NET_CONTEXT_RCVTIMEO=y +CONFIG_NET_CONTEXT_SNDTIMEO=y + +# Network application options and configuration +CONFIG_NET_CONFIG_SETTINGS=y +CONFIG_NET_CONFIG_NEED_IPV4=y +CONFIG_NET_CONFIG_MY_IPV4_ADDR="192.0.2.1" +CONFIG_NET_CONFIG_MY_IPV4_GW="192.0.2.0" +CONFIG_NET_CONFIG_MY_IPV4_NETMASK="255.255.255.0" +``` + +Additionally, it can be useful for debugging purposes to add the Zephyr network shell functionality. This makes it possible to use the serial communication client as input for some select commands, for example, to print network configuration info from the board. Also setting the option the receive network debugging info is useful. These options are available by including `debug.conf` when building the application. + +``` +# Network Shell +CONFIG_NET_SHELL=y +CONFIG_SHELL=y + +# Debug info +CONFIG_NET_LOG=y +CONFIG_LOG=y +CONFIG_NET_STATISTICS=y +CONFIG_NET_SOCKETS_LOG_LEVEL_DBG=y +``` + +Furthermore, the MAC addresses of the boards can be configured using DTS overlay files (`_.overlay`). This is important to be aware of as the boards might get the same MAC address by default. Overlay-files that do this are provided in the example federated programs. The MAC address is set with + +``` +&enet { + local-mac-address = [00 0a 35 00 00 01]; + status = "okay"; +}; +``` + +Other application-specific hardware configurations can be added in this file. + +### Build +To build a federated LF application, run + +``` +$ cd application/ +$ west lf-fed-build src/.lf -w "-b mimxrt1170_evk_cm7 --pristine" +``` + +This command will generate a build folder per federate with name "zephyr--build". + +It is possible to specify federates to build by adding + +``` +$ west lf-fed-build src/.lf -w "-b mimxrt1170_evk_cm7 --pristine" -f +``` + +To build with debugging configurations in `debug.conf` run + +``` +$ west lf-fed-build src/.lf -w "-b mimxrt1170_evk_cm7 --pristine" -c debug.conf +``` + +To build without running the `lfc` command can be useful if modifying the auto-generated code. This can be done by passing the command-line option `-n` to the lf-fed-build command. + +The lf-fed-build can be inspected in `scripts/lf_build.py`. By default, it invokes `lfc` on the provided LF source file. It then copies over necessary Zephyr configuration files to each generated federate. Then `west build` is invoked on each federate, resulting in a build folder per federate. + +### Flash + +To flash n federates to at least n connected boards, a custom west command called `lf-fed-flash` is supplied. The only command-line option is `-n` which accepts an integer representing the number of federates to flash. It must match the number of build folders in the current directory (where the command is called from). + +For example, if three boards are connected and three federates should be flashed, run + +``` +$ west lf-fed-flash -n 3 +``` +To use this command, it is necessary that debugging is set up properly, and specifically using the `pyocd` runner. + +### Debugger Setup +Test debugging with `pyocd` by building and flashing a basic Zephyr sample +``` +$ cd deps/zephyr/samples/hello_world +$ west build src/main.c +$ west flash -r pyocd +``` +PyOCD is the preferred runner since it is compatible with the out-of-the-box firmware on NXP MIMXRT1170-EVK, which is CMSIS-DAP. It is also possible to use JLink tools, but then the firmware must be changed. + +If the following error occurs +``` +[Errno 13] Access denied (insufficient permissions) while trying to interrogate a USB device. This can probably be remedied with a udev rule. See for help. +``` +Then a udev rule must be set up, as indicated by the error itself. To add a udev rule, do the following +``` +$ cd /etc/udev/rules.d +$ sudo touch 50-cmsis-dap.rules +``` +Then, copy the contents from the file `https://github.com/pyocd/pyOCD/blob/main/udev/50-cmsis-dap.rules` into the newly created file. Reload the new rules by +``` +$ sudo udevadm control --reload +$ sudo udevadm trigger +``` + +It should then be possible to run the `west flash -r pyocd` command. + +If PyOCD is setup, then debugging with GDB can be done by simply running + +``` +$ west debug -r pyocd +``` + +### Run + +To run a federation, start the RTI with ID "Unidentified Federation" and supplying the number of federates to connect. This can be federates running from boards or also from a linux computer. For example, run + +``` +$ RTI -n 3 -i "Unidentified Federation" +``` + +Then each board federate must be restarted. diff --git a/application/Kconfig b/application/Blinky/Kconfig similarity index 100% rename from application/Kconfig rename to application/Blinky/Kconfig diff --git a/application/debug.conf b/application/Blinky/debug.conf similarity index 100% rename from application/debug.conf rename to application/Blinky/debug.conf diff --git a/application/prj.conf b/application/Blinky/prj.conf similarity index 99% rename from application/prj.conf rename to application/Blinky/prj.conf index 3fe63dd..428e12a 100644 --- a/application/prj.conf +++ b/application/Blinky/prj.conf @@ -2,4 +2,3 @@ # source directory `src-gen`. Be careful to not set configuration parameters # that are conflicting with the default required parameters found in # `prj_lf.conf` - diff --git a/application/src/NrfBlinky.lf b/application/Blinky/src/Blinky.lf similarity index 100% rename from application/src/NrfBlinky.lf rename to application/Blinky/src/Blinky.lf diff --git a/application/Blinky/src/ExampleProgram.lf b/application/Blinky/src/ExampleProgram.lf new file mode 100644 index 0000000..e69de29 diff --git a/application/ClockSyncTest/Kconfig b/application/ClockSyncTest/Kconfig new file mode 100644 index 0000000..e36a5f7 --- /dev/null +++ b/application/ClockSyncTest/Kconfig @@ -0,0 +1,11 @@ +# Copyright (c) 2021 Nordic Semiconductor ASA +# SPDX-License-Identifier: Apache-2.0 +# +# This file is the application Kconfig entry point. All application Kconfig +# options can be defined here or included via other application Kconfig files. +# You can browse these options using the west targets menuconfig (terminal) or +# guiconfig (GUI). + +menu "Zephyr" +source "Kconfig.zephyr" +endmenu diff --git a/application/ClockSyncTest/debug.conf b/application/ClockSyncTest/debug.conf new file mode 100644 index 0000000..2c1f1cb --- /dev/null +++ b/application/ClockSyncTest/debug.conf @@ -0,0 +1,16 @@ +# Copyright (c) 2021 Nordic Semiconductor ASA +# SPDX-License-Identifier: Apache-2.0 +# +# This is a Kconfig fragment which can be used to enable debug-related options +# in the application. See the README for more details. + +# compiler +CONFIG_DEBUG_OPTIMIZATIONS=y + +# Network shell +CONFIG_NET_SHELL=y +CONFIG_SHELL=y + +# Logging +CONFIG_NET_LOG=y +CONFIG_LOG=y \ No newline at end of file diff --git a/application/ClockSyncTest/mimxrt1170_evk_cm7_federate__d.overlay b/application/ClockSyncTest/mimxrt1170_evk_cm7_federate__d.overlay new file mode 100644 index 0000000..39561e3 --- /dev/null +++ b/application/ClockSyncTest/mimxrt1170_evk_cm7_federate__d.overlay @@ -0,0 +1,20 @@ +/** +* This overlay file (name: _.overlay) will override +* default hardware configurations in .dts. The overlay file must +* specify a unique MAC address when there are more than one board federate +* to be depolyed. +*/ + +&{/leds} { + compatible = "gpio-leds"; + led-2 { + gpios = < &gpio9 0x19 GPIO_ACTIVE_HIGH >; + label = "User LED D34, GPIO9 AD 26"; + status = "okay"; + }; +}; + +&enet { + local-mac-address = [00 0a 35 00 00 01]; + status = "okay"; +}; \ No newline at end of file diff --git a/application/ClockSyncTest/mimxrt1170_evk_cm7_federate__s.overlay b/application/ClockSyncTest/mimxrt1170_evk_cm7_federate__s.overlay new file mode 100644 index 0000000..bd3cdf0 --- /dev/null +++ b/application/ClockSyncTest/mimxrt1170_evk_cm7_federate__s.overlay @@ -0,0 +1,20 @@ +/** +* This overlay file (name: _.overlay) will override +* default hardware configurations in .dts. The overlay file must +* specify a unique MAC address when there are more than one board federate +* to be depolyed. +*/ + +&{/leds} { + compatible = "gpio-leds"; + led-2 { + gpios = < &gpio9 0x19 GPIO_ACTIVE_HIGH >; + label = "User LED D34, GPIO9 AD 26"; + status = "okay"; + }; +}; + +&enet { + local-mac-address = [00 0a 35 00 00 02]; + status = "okay"; +}; \ No newline at end of file diff --git a/application/ClockSyncTest/prj_federate__d.conf b/application/ClockSyncTest/prj_federate__d.conf new file mode 100644 index 0000000..09e7f72 --- /dev/null +++ b/application/ClockSyncTest/prj_federate__d.conf @@ -0,0 +1,42 @@ +# This federate-specific configuration file (name: prj_.conf) +# will be merged with `prj_lf.conf` in the generated source directory `src-gen`. +# Be careful to not set configuration parameters that are conflicting with +# the default required parameters found in `prj_lf.conf` + +# General config +CONFIG_MAIN_STACK_SIZE=4096 +CONFIG_TEST_RANDOM_GENERATOR=y +CONFIG_INIT_STACKS=y +CONFIG_POSIX_API=y +CONFIG_POSIX_MAX_FDS=32 + +# Generic networking options +CONFIG_NETWORKING=y +CONFIG_NET_UDP=y +CONFIG_NET_TCP=y +CONFIG_NET_IPV4=y +CONFIG_NET_SOCKETS=y +CONFIG_NET_L2_ETHERNET=y +CONFIG_NET_CONNECTION_MANAGER=y +CONFIG_NET_MAX_CONN=32 + +CONFIG_COUNTER_MCUX_GPT=y + +# Network buffers +CONFIG_NET_PKT_RX_COUNT=32 +CONFIG_NET_PKT_TX_COUNT=32 +CONFIG_NET_BUF_RX_COUNT=128 +CONFIG_NET_BUF_TX_COUNT=128 +CONFIG_NET_BUF_FIXED_DATA_SIZE=y +CONFIG_NET_BUF_DATA_SIZE=1024 + +# Network socket timeout +CONFIG_NET_CONTEXT_RCVTIMEO=y +CONFIG_NET_CONTEXT_SNDTIMEO=y + +# Network application options and configuration +CONFIG_NET_CONFIG_SETTINGS=y +CONFIG_NET_CONFIG_NEED_IPV4=y +CONFIG_NET_CONFIG_MY_IPV4_ADDR="192.0.2.3" +CONFIG_NET_CONFIG_MY_IPV4_GW="192.0.2.0" +CONFIG_NET_CONFIG_MY_IPV4_NETMASK="255.255.255.0" \ No newline at end of file diff --git a/application/ClockSyncTest/prj_federate__s.conf b/application/ClockSyncTest/prj_federate__s.conf new file mode 100644 index 0000000..513455d --- /dev/null +++ b/application/ClockSyncTest/prj_federate__s.conf @@ -0,0 +1,42 @@ +# This federate-specific configuration file (name: prj_.conf) +# will be merged with `prj_lf.conf` in the generated source directory `src-gen`. +# Be careful to not set configuration parameters that are conflicting with +# the default required parameters found in `prj_lf.conf` + +# General config +CONFIG_MAIN_STACK_SIZE=4096 +CONFIG_TEST_RANDOM_GENERATOR=y +CONFIG_INIT_STACKS=y +CONFIG_POSIX_API=y +CONFIG_POSIX_MAX_FDS=32 + +# Generic networking options +CONFIG_NETWORKING=y +CONFIG_NET_UDP=y +CONFIG_NET_TCP=y +CONFIG_NET_IPV4=y +CONFIG_NET_SOCKETS=y +CONFIG_NET_L2_ETHERNET=y +CONFIG_NET_CONNECTION_MANAGER=y +CONFIG_NET_MAX_CONN=32 + +CONFIG_COUNTER_MCUX_GPT=y + +# Network buffers +CONFIG_NET_PKT_RX_COUNT=32 +CONFIG_NET_PKT_TX_COUNT=32 +CONFIG_NET_BUF_RX_COUNT=128 +CONFIG_NET_BUF_TX_COUNT=128 +CONFIG_NET_BUF_FIXED_DATA_SIZE=y +CONFIG_NET_BUF_DATA_SIZE=1024 + +# Network socket timeout +CONFIG_NET_CONTEXT_RCVTIMEO=y +CONFIG_NET_CONTEXT_SNDTIMEO=y + +# Network application options and configuration +CONFIG_NET_CONFIG_SETTINGS=y +CONFIG_NET_CONFIG_NEED_IPV4=y +CONFIG_NET_CONFIG_MY_IPV4_ADDR="192.0.2.1" +CONFIG_NET_CONFIG_MY_IPV4_GW="192.0.2.0" +CONFIG_NET_CONFIG_MY_IPV4_NETMASK="255.255.255.0" \ No newline at end of file diff --git a/application/ClockSyncTest/src/ClockSyncTest.lf b/application/ClockSyncTest/src/ClockSyncTest.lf new file mode 100644 index 0000000..597d0d6 --- /dev/null +++ b/application/ClockSyncTest/src/ClockSyncTest.lf @@ -0,0 +1,41 @@ +// This test connects a simple counting source to tester that checks against its +// own count. +target C { + platform: "Zephyr", + threading: true, + workers: 2, + no-compile: true, +} + +preamble {= + #include + #include + #define LED0_NODE DT_ALIAS(led0) + static const struct gpio_dt_spec led = GPIO_DT_SPEC_GET(LED0_NODE, gpios); +=} + +reactor Source(period: time(1 sec)) { + timer t(1 sec, period) + state count:int(0) + reaction(t) {= + gpio_pin_toggle_dt(&led); + self->count++; + if (self->count == 120) { + lf_request_stop(); + } + =} + reaction(shutdown) {= + printf("Final count was: %d", self->count); + =} +} + +federated reactor ClockSyncTest at 192.0.2.2:15047{ + s = new Source(period = 500 msec); + d = new Source(period = 500 msec); + + reaction(startup) {= + assert(device_is_ready(led.port)); + gpio_pin_configure_dt(&led, GPIO_OUTPUT_ACTIVE); + gpio_pin_set_dt(&led, 1); + =} +} diff --git a/application/DistributedMessageTest/Kconfig b/application/DistributedMessageTest/Kconfig new file mode 100644 index 0000000..e36a5f7 --- /dev/null +++ b/application/DistributedMessageTest/Kconfig @@ -0,0 +1,11 @@ +# Copyright (c) 2021 Nordic Semiconductor ASA +# SPDX-License-Identifier: Apache-2.0 +# +# This file is the application Kconfig entry point. All application Kconfig +# options can be defined here or included via other application Kconfig files. +# You can browse these options using the west targets menuconfig (terminal) or +# guiconfig (GUI). + +menu "Zephyr" +source "Kconfig.zephyr" +endmenu diff --git a/application/DistributedMessageTest/debug.conf b/application/DistributedMessageTest/debug.conf new file mode 100644 index 0000000..2c1f1cb --- /dev/null +++ b/application/DistributedMessageTest/debug.conf @@ -0,0 +1,16 @@ +# Copyright (c) 2021 Nordic Semiconductor ASA +# SPDX-License-Identifier: Apache-2.0 +# +# This is a Kconfig fragment which can be used to enable debug-related options +# in the application. See the README for more details. + +# compiler +CONFIG_DEBUG_OPTIMIZATIONS=y + +# Network shell +CONFIG_NET_SHELL=y +CONFIG_SHELL=y + +# Logging +CONFIG_NET_LOG=y +CONFIG_LOG=y \ No newline at end of file diff --git a/application/DistributedMessageTest/mimxrt1170_evk_cm7_federate__d.overlay b/application/DistributedMessageTest/mimxrt1170_evk_cm7_federate__d.overlay new file mode 100644 index 0000000..a2ff987 --- /dev/null +++ b/application/DistributedMessageTest/mimxrt1170_evk_cm7_federate__d.overlay @@ -0,0 +1,14 @@ + +&{/leds} { + compatible = "gpio-leds"; + led-2 { + gpios = < &gpio9 0x19 GPIO_ACTIVE_HIGH >; + label = "User LED D34, GPIO9 AD 26"; + status = "okay"; + }; +}; + +&enet { + local-mac-address = [00 0a 35 00 00 01]; + status = "okay"; +}; diff --git a/application/DistributedMessageTest/mimxrt1170_evk_cm7_federate__s.overlay b/application/DistributedMessageTest/mimxrt1170_evk_cm7_federate__s.overlay new file mode 100644 index 0000000..bd3cdf0 --- /dev/null +++ b/application/DistributedMessageTest/mimxrt1170_evk_cm7_federate__s.overlay @@ -0,0 +1,20 @@ +/** +* This overlay file (name: _.overlay) will override +* default hardware configurations in .dts. The overlay file must +* specify a unique MAC address when there are more than one board federate +* to be depolyed. +*/ + +&{/leds} { + compatible = "gpio-leds"; + led-2 { + gpios = < &gpio9 0x19 GPIO_ACTIVE_HIGH >; + label = "User LED D34, GPIO9 AD 26"; + status = "okay"; + }; +}; + +&enet { + local-mac-address = [00 0a 35 00 00 02]; + status = "okay"; +}; \ No newline at end of file diff --git a/application/DistributedMessageTest/prj_federate__d.conf b/application/DistributedMessageTest/prj_federate__d.conf new file mode 100644 index 0000000..307cd64 --- /dev/null +++ b/application/DistributedMessageTest/prj_federate__d.conf @@ -0,0 +1,41 @@ +# This federate-specific configuration file (name: prj_.conf) +# will be merged with `prj_lf.conf` in the generated source directory `src-gen`. +# Be careful to not set configuration parameters that are conflicting with +# the default required parameters found in `prj_lf.conf` + +# General config +CONFIG_MAIN_STACK_SIZE=4096 +CONFIG_TEST_RANDOM_GENERATOR=y +CONFIG_INIT_STACKS=y +CONFIG_POSIX_API=y +CONFIG_POSIX_MAX_FDS=32 + +# Generic networking options +CONFIG_NETWORKING=y +CONFIG_NET_UDP=y +CONFIG_NET_TCP=y +CONFIG_NET_IPV4=y +CONFIG_NET_SOCKETS=y +CONFIG_NET_L2_ETHERNET=y +CONFIG_NET_CONNECTION_MANAGER=y +CONFIG_NET_MAX_CONN=32 +CONFIG_NET_MAX_CONTEXTS=32 + +# Network buffers +CONFIG_NET_PKT_RX_COUNT=64 +CONFIG_NET_PKT_TX_COUNT=64 +CONFIG_NET_BUF_RX_COUNT=128 +CONFIG_NET_BUF_TX_COUNT=128 +CONFIG_NET_BUF_FIXED_DATA_SIZE=y +CONFIG_NET_BUF_DATA_SIZE=2048 + +# Network socket timeout +CONFIG_NET_CONTEXT_RCVTIMEO=y +CONFIG_NET_CONTEXT_SNDTIMEO=y + +# Network application options and configuration +CONFIG_NET_CONFIG_SETTINGS=y +CONFIG_NET_CONFIG_NEED_IPV4=y +CONFIG_NET_CONFIG_MY_IPV4_ADDR="192.0.2.3" +CONFIG_NET_CONFIG_MY_IPV4_GW="192.0.2.0" +CONFIG_NET_CONFIG_MY_IPV4_NETMASK="255.255.255.0" \ No newline at end of file diff --git a/application/DistributedMessageTest/prj_federate__s.conf b/application/DistributedMessageTest/prj_federate__s.conf new file mode 100644 index 0000000..58689ea --- /dev/null +++ b/application/DistributedMessageTest/prj_federate__s.conf @@ -0,0 +1,40 @@ +# This federate-specific configuration file (name: prj_.conf) +# will be merged with `prj_lf.conf` in the generated source directory `src-gen`. +# Be careful to not set configuration parameters that are conflicting with +# the default required parameters found in `prj_lf.conf` + +# General config +CONFIG_MAIN_STACK_SIZE=4096 +CONFIG_TEST_RANDOM_GENERATOR=y +CONFIG_INIT_STACKS=y +CONFIG_POSIX_API=y +CONFIG_POSIX_MAX_FDS=32 + +# Generic networking options +CONFIG_NETWORKING=y +CONFIG_NET_UDP=y +CONFIG_NET_TCP=y +CONFIG_NET_IPV4=y +CONFIG_NET_SOCKETS=y +CONFIG_NET_L2_ETHERNET=y +CONFIG_NET_CONNECTION_MANAGER=y +CONFIG_NET_MAX_CONN=32 + +# Network buffers +CONFIG_NET_PKT_RX_COUNT=64 +CONFIG_NET_PKT_TX_COUNT=64 +CONFIG_NET_BUF_RX_COUNT=128 +CONFIG_NET_BUF_TX_COUNT=128 +CONFIG_NET_BUF_FIXED_DATA_SIZE=y +CONFIG_NET_BUF_DATA_SIZE=2048 + +# Network socket timeout +CONFIG_NET_CONTEXT_RCVTIMEO=y +CONFIG_NET_CONTEXT_SNDTIMEO=y + +# Network application options and configuration +CONFIG_NET_CONFIG_SETTINGS=y +CONFIG_NET_CONFIG_NEED_IPV4=y +CONFIG_NET_CONFIG_MY_IPV4_ADDR="192.0.2.1" +CONFIG_NET_CONFIG_MY_IPV4_GW="192.0.2.0" +CONFIG_NET_CONFIG_MY_IPV4_NETMASK="255.255.255.0" \ No newline at end of file diff --git a/application/DistributedMessageTest/src/DistributedMessageTest.lf b/application/DistributedMessageTest/src/DistributedMessageTest.lf new file mode 100644 index 0000000..28348d6 --- /dev/null +++ b/application/DistributedMessageTest/src/DistributedMessageTest.lf @@ -0,0 +1,77 @@ +target C { + platform: { + name: "Zephyr", + }, + threading: true, + workers: 2, + no-compile: true, + coordination: decentralized, +} + +reactor Source(period: time(2 sec), iterations:int(10)) { + preamble{= + #include + #include + + static const struct gpio_dt_spec led1 = GPIO_DT_SPEC_GET(DT_ALIAS(led0), gpios); + =} + output y: int + timer t(1 sec, period) + state count: int(0) + + reaction(startup) {= + assert(device_is_ready(led1.port)); + gpio_pin_configure_dt(&led1, GPIO_OUTPUT_ACTIVE); + gpio_pin_set_dt(&led1, 1); + =} + + reaction(t) -> y {= + (self->count)++; + gpio_pin_toggle_dt(&led1); + lf_set(y, self->count); + if (self->count == self->iterations) { + lf_request_stop(); + } + =} + + reaction(shutdown) {= + printf("Finished %u iterations. Requesting stop.\n", self->count); + =} +} + +reactor Drain { + preamble{= + #include + #include + + static const struct gpio_dt_spec led1 = GPIO_DT_SPEC_GET(DT_ALIAS(led0), gpios); + =} + input x: int + state count: int(0) + + reaction(startup) {= + assert(device_is_ready(led1.port)); + gpio_pin_configure_dt(&led1, GPIO_OUTPUT_ACTIVE); + gpio_pin_set_dt(&led1, 1); + =} + + reaction(x) {= + (self->count)++; + if (self->count != x->value) { + printf("Received wrong message: %u. Expected: %u. Requesting stop.\n", x->value, self->count); + lf_request_stop(); + } else { + gpio_pin_toggle_dt(&led1); + } + =} + + reaction(shutdown) {= + printf("Finished %u iterations in %u. Stopping.\n", self->count, lf_time_physical_elapsed()); + =} +} + +federated reactor DistributedMessageTest at 192.0.2.3:15047 { + s = new Source(period = 100 msec, iterations = 300); + d = new Drain(); + s.y -> d.x; +} diff --git a/application/HelloWorld/Kconfig b/application/HelloWorld/Kconfig new file mode 100644 index 0000000..e36a5f7 --- /dev/null +++ b/application/HelloWorld/Kconfig @@ -0,0 +1,11 @@ +# Copyright (c) 2021 Nordic Semiconductor ASA +# SPDX-License-Identifier: Apache-2.0 +# +# This file is the application Kconfig entry point. All application Kconfig +# options can be defined here or included via other application Kconfig files. +# You can browse these options using the west targets menuconfig (terminal) or +# guiconfig (GUI). + +menu "Zephyr" +source "Kconfig.zephyr" +endmenu diff --git a/application/HelloWorld/debug.conf b/application/HelloWorld/debug.conf new file mode 100644 index 0000000..8d2860d --- /dev/null +++ b/application/HelloWorld/debug.conf @@ -0,0 +1,8 @@ +# Copyright (c) 2021 Nordic Semiconductor ASA +# SPDX-License-Identifier: Apache-2.0 +# +# This is a Kconfig fragment which can be used to enable debug-related options +# in the application. See the README for more details. + +# compiler +CONFIG_DEBUG_OPTIMIZATIONS=y diff --git a/application/HelloWorld/prj.conf b/application/HelloWorld/prj.conf new file mode 100644 index 0000000..428e12a --- /dev/null +++ b/application/HelloWorld/prj.conf @@ -0,0 +1,4 @@ +# This configuration file will be merged with `prj_lf.conf` in the generated +# source directory `src-gen`. Be careful to not set configuration parameters +# that are conflicting with the default required parameters found in +# `prj_lf.conf` diff --git a/application/src/HelloWorld.lf b/application/HelloWorld/src/HelloWorld.lf similarity index 97% rename from application/src/HelloWorld.lf rename to application/HelloWorld/src/HelloWorld.lf index bd0c106..ffb17df 100644 --- a/application/src/HelloWorld.lf +++ b/application/HelloWorld/src/HelloWorld.lf @@ -1,11 +1,10 @@ target C { platform: "Zephyr", threading: false -}; +} main reactor { reaction(startup) {= printf("Hello World\n"); =} -} - +} \ No newline at end of file diff --git a/application/SmartGrid/Kconfig b/application/SmartGrid/Kconfig new file mode 100644 index 0000000..e36a5f7 --- /dev/null +++ b/application/SmartGrid/Kconfig @@ -0,0 +1,11 @@ +# Copyright (c) 2021 Nordic Semiconductor ASA +# SPDX-License-Identifier: Apache-2.0 +# +# This file is the application Kconfig entry point. All application Kconfig +# options can be defined here or included via other application Kconfig files. +# You can browse these options using the west targets menuconfig (terminal) or +# guiconfig (GUI). + +menu "Zephyr" +source "Kconfig.zephyr" +endmenu diff --git a/application/SmartGrid/debug.conf b/application/SmartGrid/debug.conf new file mode 100644 index 0000000..3327586 --- /dev/null +++ b/application/SmartGrid/debug.conf @@ -0,0 +1,19 @@ +# Copyright (c) 2021 Nordic Semiconductor ASA +# SPDX-License-Identifier: Apache-2.0 +# +# This is a Kconfig fragment which can be used to enable debug-related options +# in the application. See the README for more details. + +# compiler +CONFIG_DEBUG_OPTIMIZATIONS=y + +# Network shell +CONFIG_NET_SHELL=y +CONFIG_SHELL=y + +# Logging +CONFIG_NET_LOG=y +CONFIG_LOG=y + +# Stack +CONFIG_INIT_STACKS=y \ No newline at end of file diff --git a/application/SmartGrid/mimxrt1170_evk_cm7_federate__unitB.overlay b/application/SmartGrid/mimxrt1170_evk_cm7_federate__unitB.overlay new file mode 100644 index 0000000..39561e3 --- /dev/null +++ b/application/SmartGrid/mimxrt1170_evk_cm7_federate__unitB.overlay @@ -0,0 +1,20 @@ +/** +* This overlay file (name: _.overlay) will override +* default hardware configurations in .dts. The overlay file must +* specify a unique MAC address when there are more than one board federate +* to be depolyed. +*/ + +&{/leds} { + compatible = "gpio-leds"; + led-2 { + gpios = < &gpio9 0x19 GPIO_ACTIVE_HIGH >; + label = "User LED D34, GPIO9 AD 26"; + status = "okay"; + }; +}; + +&enet { + local-mac-address = [00 0a 35 00 00 01]; + status = "okay"; +}; \ No newline at end of file diff --git a/application/SmartGrid/mimxrt1170_evk_cm7_federate__unitC.overlay b/application/SmartGrid/mimxrt1170_evk_cm7_federate__unitC.overlay new file mode 100644 index 0000000..bd3cdf0 --- /dev/null +++ b/application/SmartGrid/mimxrt1170_evk_cm7_federate__unitC.overlay @@ -0,0 +1,20 @@ +/** +* This overlay file (name: _.overlay) will override +* default hardware configurations in .dts. The overlay file must +* specify a unique MAC address when there are more than one board federate +* to be depolyed. +*/ + +&{/leds} { + compatible = "gpio-leds"; + led-2 { + gpios = < &gpio9 0x19 GPIO_ACTIVE_HIGH >; + label = "User LED D34, GPIO9 AD 26"; + status = "okay"; + }; +}; + +&enet { + local-mac-address = [00 0a 35 00 00 02]; + status = "okay"; +}; \ No newline at end of file diff --git a/application/SmartGrid/prj_federate__unitB.conf b/application/SmartGrid/prj_federate__unitB.conf new file mode 100644 index 0000000..017ed10 --- /dev/null +++ b/application/SmartGrid/prj_federate__unitB.conf @@ -0,0 +1,40 @@ +# This federate-specific configuration file (name: prj_.conf) +# will be merged with `prj_lf.conf` in the generated source directory `src-gen`. +# Be careful to not set configuration parameters that are conflicting with +# the default required parameters found in `prj_lf.conf` + +# General config +CONFIG_MAIN_STACK_SIZE=4096 +CONFIG_TEST_RANDOM_GENERATOR=y +CONFIG_POSIX_API=y +CONFIG_POSIX_MAX_FDS=32 + +# Generic networking options +CONFIG_NETWORKING=y +CONFIG_NET_UDP=y +CONFIG_NET_TCP=y +CONFIG_NET_IPV4=y +CONFIG_NET_SOCKETS=y +CONFIG_NET_L2_ETHERNET=y +CONFIG_NET_MAX_CONN=16 + +# Network buffers +CONFIG_NET_PKT_RX_COUNT=32 +CONFIG_NET_PKT_TX_COUNT=32 +CONFIG_NET_BUF_RX_COUNT=128 +CONFIG_NET_BUF_TX_COUNT=128 +CONFIG_NET_BUF_FIXED_DATA_SIZE=y +CONFIG_NET_BUF_DATA_SIZE=1024 + +CONFIG_NET_MAX_CONTEXTS=16 + +# Network socket timeout +CONFIG_NET_CONTEXT_RCVTIMEO=y +CONFIG_NET_CONTEXT_SNDTIMEO=y + +# Network application options and configuration +CONFIG_NET_CONFIG_SETTINGS=y +CONFIG_NET_CONFIG_NEED_IPV4=y +CONFIG_NET_CONFIG_MY_IPV4_ADDR="192.0.2.1" +CONFIG_NET_CONFIG_MY_IPV4_GW="192.0.2.0" +CONFIG_NET_CONFIG_MY_IPV4_NETMASK="255.255.255.0" diff --git a/application/SmartGrid/prj_federate__unitC.conf b/application/SmartGrid/prj_federate__unitC.conf new file mode 100644 index 0000000..9f6e639 --- /dev/null +++ b/application/SmartGrid/prj_federate__unitC.conf @@ -0,0 +1,40 @@ +# This federate-specific configuration file (name: prj_.conf) +# will be merged with `prj_lf.conf` in the generated source directory `src-gen`. +# Be careful to not set configuration parameters that are conflicting with +# the default required parameters found in `prj_lf.conf` + +# General config +CONFIG_MAIN_STACK_SIZE=4096 +CONFIG_TEST_RANDOM_GENERATOR=y +CONFIG_POSIX_API=y +CONFIG_POSIX_MAX_FDS=32 + +# Generic networking options +CONFIG_NETWORKING=y +CONFIG_NET_UDP=y +CONFIG_NET_TCP=y +CONFIG_NET_IPV4=y +CONFIG_NET_SOCKETS=y +CONFIG_NET_L2_ETHERNET=y +CONFIG_NET_MAX_CONN=16 + +# Network buffers +CONFIG_NET_PKT_RX_COUNT=32 +CONFIG_NET_PKT_TX_COUNT=32 +CONFIG_NET_BUF_RX_COUNT=128 +CONFIG_NET_BUF_TX_COUNT=128 +CONFIG_NET_BUF_FIXED_DATA_SIZE=y +CONFIG_NET_BUF_DATA_SIZE=1024 + +CONFIG_NET_MAX_CONTEXTS=16 + +# Network socket timeout +CONFIG_NET_CONTEXT_RCVTIMEO=y +CONFIG_NET_CONTEXT_SNDTIMEO=y + +# Network application options and configuration +CONFIG_NET_CONFIG_SETTINGS=y +CONFIG_NET_CONFIG_NEED_IPV4=y +CONFIG_NET_CONFIG_MY_IPV4_ADDR="192.0.2.3" +CONFIG_NET_CONFIG_MY_IPV4_GW="192.0.2.0" +CONFIG_NET_CONFIG_MY_IPV4_NETMASK="255.255.255.0" \ No newline at end of file diff --git a/application/SmartGrid/src/smartgrid-linux.lf b/application/SmartGrid/src/smartgrid-linux.lf new file mode 100644 index 0000000..6555c09 --- /dev/null +++ b/application/SmartGrid/src/smartgrid-linux.lf @@ -0,0 +1,254 @@ +target C { + coordination: decentralized, +} + +reactor DistAlg(ownId:int(0), STP_offset:time(0 msec)) { + preamble {= + void findIndexSmallestValues(int* arr, int* closestArr, int n) { + int min1 = arr[0]; + int pos1 = 0; + int min2 = arr[1]; + int pos2 = 1; + + if (min2 < min1) { + min1 = arr[1]; + pos1 = 1; + min2 = arr[0]; + pos2 = 0; + } + for (int i = 2; i < n; i++) { + if (arr[i] < min1) { + min2 = min1; + pos2 = pos1; + min1 = arr[i]; + pos1 = i; + } else if (arr[i] < min2) { + min2 = arr[i]; + pos2 = i; + } + } + closestArr[0] = pos1; + closestArr[1] = pos2; + } + + int localAction(int closestRelays[], int ownId) { + if (ownId == closestRelays[0] || ownId == closestRelays[1]) { + return 1; + } else { + return 0; + } + } + + int decideLocalAction(int* arr, int ownId, int numUnits) { + int closestRelays[2]; + findIndexSmallestValues(arr, closestRelays, numUnits); + return localAction(closestRelays, ownId); + } + =} + + output distributedDecision:int + input voltageReportAIn:int + input voltageReportBIn:int + input voltageReportCIn:int + input voltageReportDIn:int + state voltages:int[] = {0, 0, 0, 0} + state numReports:int(0) + state result:bool (false) + reaction(voltageReportAIn, voltageReportBIn, voltageReportCIn, voltageReportDIn) -> distributedDecision {= + if (voltageReportAIn->is_present && voltageReportBIn->is_present && voltageReportCIn->is_present && voltageReportDIn->is_present) { + self->voltages[0] = voltageReportAIn->value; + self->voltages[1] = voltageReportBIn->value; + self->voltages[2] = voltageReportCIn->value; + self->voltages[3] = voltageReportDIn->value; + self->result = decideLocalAction(self->voltages, self->ownId, 4); + //printf("Setting relay %u to %u (1=open,0=close)\n", self->ownId, result); + lf_set(distributedDecision, self->result); + } + =} STP(0) {= + printf("STP violation: Did not receive inputs in time, opening circuit as default.\n"); + + //debugging + if (((lf_time_logical_elapsed() - (voltageReportAIn->intended_tag.time - lf_time_start()) > 0) || + ((lf_time_logical_elapsed() - (voltageReportAIn->intended_tag.time - lf_time_start()) == 0) && + ((lf_tag().microstep - voltageReportAIn->intended_tag.microstep) > 0))) && + (voltageReportAIn->is_present)) { + printf("Input from A was tardy.\n"); + } + + if (((lf_time_logical_elapsed() - (voltageReportBIn->intended_tag.time - lf_time_start()) > 0) || + ((lf_time_logical_elapsed() - (voltageReportBIn->intended_tag.time - lf_time_start()) == 0) && + ((lf_tag().microstep - voltageReportBIn->intended_tag.microstep) > 0))) && + (voltageReportBIn->is_present)) { + printf("Input from B was tardy.\n"); + } + + if (((lf_time_logical_elapsed() - (voltageReportCIn->intended_tag.time - lf_time_start()) > 0) || + ((lf_time_logical_elapsed() - (voltageReportCIn->intended_tag.time - lf_time_start()) == 0) && + ((lf_tag().microstep - voltageReportCIn->intended_tag.microstep) > 0))) && + (voltageReportCIn->is_present)) { + printf("Input from C was tardy.\n"); + } + + if (((lf_time_logical_elapsed() - (voltageReportDIn->intended_tag.time - lf_time_start()) > 0) || + ((lf_time_logical_elapsed() - (voltageReportDIn->intended_tag.time - lf_time_start()) == 0) && + ((lf_tag().microstep - voltageReportDIn->intended_tag.microstep) > 0))) && + (voltageReportDIn->is_present)) { + printf("Input from D was tardy.\n"); + } + =} +} + +reactor CircuitStatus(STP_offset:time(0 msec)) { + preamble {= + void openCircuit() { + // led turns on + } + + void closeCircuit() { + // led turns off + } + =} + + input distributedDecision:int + input countdown:int + logical action breakCircuit; + state faultHandled:int; + + reaction(startup){= + =} + + reaction(countdown) -> breakCircuit {= + self->faultHandled = 0; + lf_schedule(breakCircuit, MSEC(150)); + =} STP(0) {= + //printf("STP violation for countdown\n"); + =} + + reaction(breakCircuit) {= + if (self->faultHandled){ + // This relay is not the closest to the fault + //printf("FAult already handled.\n"); + } else { + //printf("Opening circuit since 150 msec has passed.\n"); + openCircuit(); + } + =} STP(0) {= + //printf("STP violation for breakcircuit.\n"); + =} + + reaction(distributedDecision) -> breakCircuit {= + if (distributedDecision->value) { + //printf("Distributed decision decided to open this circuit.\n"); + openCircuit(); + self->faultHandled = 1; + } else { + //printf("Distributed decision decided to close this circuit.\n"); + closeCircuit(); + self->faultHandled = 1; + } + =} STP(0) {= + //printf("STP violation for distributed decision.\n"); + =} +} + +reactor VoltageSensor(id:int(0)) { + + preamble{= + #define VOLTAGE_LIMIT 14000U + + // Voltage measurement timeseries + int simMeas[4][14] ={{15000,15000,13100,15000,15000,15000,15000,15000,15000,15000,15000,15000,15000,15000,15000}, + {15000,15000,11000,15000,15000,15000,15000,15000,15000,15000,15000,15000,15000,15000,15000}, + {15000,15000,11200,15000,15000,15000,15000,15000,15000,15000,15000,15000,15000,15000,15000}, + {15000,15000,12500,15000,15000,15000,15000,15000,15000,15000,15000,15000,15000,15000,15000}}; + + int readSensor(int id, int count) { + return simMeas[id][count]; + } + =} + + timer t(1 sec, 70 msec); + output[4] measOut:int + output startCountdown:int + state measCount:int(0) + + reaction(startup){= + =} + + reaction(t) -> measOut, startCountdown {= + + // Read simulated measurement + int voltageMeasurement = readSensor(self->id, self->measCount); + if (self->measCount == 13) { + self->measCount = 0; + lf_request_stop(); + } else { + self->measCount++; + } + + // Check if short circuit has occured on the line + if (voltageMeasurement < VOLTAGE_LIMIT) { + //printf("SEND\n"); + // A fault has occured, let other nodes know + lf_set(startCountdown, 1); + for (int i = 0; i < 4; i++) { + lf_set(measOut[i], voltageMeasurement); + } + } + =} +} + +reactor RelayUnit(id:int(0), STP_offset:time(0 msec)) { + output[4] voltageReportOut:int; + input voltageReportAIn:int; + input voltageReportBIn:int; + input voltageReportCIn:int; + input voltageReportDIn:int; + + circuitStatus = new CircuitStatus(); + voltageSensor = new VoltageSensor(id = id); + distAlg = new DistAlg(ownId = id); + + voltageSensor.measOut -> voltageReportOut; + voltageSensor.startCountdown -> circuitStatus.countdown; + + voltageReportAIn -> distAlg.voltageReportAIn; + voltageReportBIn -> distAlg.voltageReportBIn; + voltageReportCIn -> distAlg.voltageReportCIn; + voltageReportDIn -> distAlg.voltageReportDIn; + + distAlg.distributedDecision -> circuitStatus.distributedDecision; + +} + +federated reactor at 192.0.2.2:15047 { + unitA = new RelayUnit(id = 0); + unitB = new RelayUnit(id = 1); + unitC = new RelayUnit(id = 2); + unitD = new RelayUnit(id = 3); + + unitA.voltageReportOut -> unitA.voltageReportAIn, + unitB.voltageReportAIn, + unitC.voltageReportAIn, + unitD.voltageReportAIn after 15 msec; + + unitB.voltageReportOut -> unitA.voltageReportBIn, + unitB.voltageReportBIn, + unitC.voltageReportBIn, + unitD.voltageReportBIn after 15 msec; + + unitC.voltageReportOut -> unitA.voltageReportCIn, + unitB.voltageReportCIn, + unitC.voltageReportCIn, + unitD.voltageReportCIn after 15 msec; + + unitD.voltageReportOut -> unitA.voltageReportDIn, + unitB.voltageReportDIn, + unitC.voltageReportDIn, + unitD.voltageReportDIn after 15 msec; + + // reaction(startup){= + // interval_t stp = 00000000LL; + // lf_set_stp_offset(stp); + // =} +} diff --git a/application/SmartGrid/src/smartgrid-nxp.lf b/application/SmartGrid/src/smartgrid-nxp.lf new file mode 100644 index 0000000..cdfc72e --- /dev/null +++ b/application/SmartGrid/src/smartgrid-nxp.lf @@ -0,0 +1,280 @@ +target C { + platform: "Zephyr", + no-compile: true, + coordination: decentralized, + workers: 2, +} +// 20 40 STP fungerte avogtil, men B og C kunne være sene (after 0) res:12ms. Da var linux 0 og 0 (after 0). +// Men det gir nok inherently ikke mening. Det er nok ikke poeng med distAlg STP. Og relay STP gir sirkulærtidsavhengiget +reactor DistAlg(ownId:int(0), STP_offset:time(15 msec)) { + preamble {= + void findIndexSmallestValues(int* arr, int* closestArr, int n) { + int min1 = arr[0]; + int pos1 = 0; + int min2 = arr[1]; + int pos2 = 1; + + if (min2 < min1) { + min1 = arr[1]; + pos1 = 1; + min2 = arr[0]; + pos2 = 0; + } + for (int i = 2; i < n; i++) { + if (arr[i] < min1) { + min2 = min1; + pos2 = pos1; + min1 = arr[i]; + pos1 = i; + } else if (arr[i] < min2) { + min2 = arr[i]; + pos2 = i; + } + } + closestArr[0] = pos1; + closestArr[1] = pos2; + } + + int localAction(int closestRelays[], int ownId) { + if (ownId == closestRelays[0] || ownId == closestRelays[1]) { + return 1; + } else { + return 0; + } + } + + int decideLocalAction(int* arr, int ownId, int numUnits) { + int closestRelays[2]; + findIndexSmallestValues(arr, closestRelays, numUnits); + return localAction(closestRelays, ownId); + } + =} + + output distributedDecision:int + input voltageReportAIn:int + input voltageReportBIn:int + input voltageReportCIn:int + input voltageReportDIn:int + state voltages:int[]// = {0, 0, 0, 0} + state numReports:int(0) + state result:bool (false) + reaction(voltageReportAIn, voltageReportBIn, voltageReportCIn, voltageReportDIn) -> distributedDecision {= + if (voltageReportAIn->is_present && voltageReportBIn->is_present && voltageReportCIn->is_present && voltageReportDIn->is_present) { + self->voltages[0] = voltageReportAIn->value; + self->voltages[1] = voltageReportBIn->value; + self->voltages[2] = voltageReportCIn->value; + self->voltages[3] = voltageReportDIn->value; + self->result = decideLocalAction(self->voltages, self->ownId, 4); + //printf("Setting relay %u to %u (1=open,0=close)\n", self->ownId, result); + lf_set(distributedDecision, self->result); + } else { + printf("Not all inputs present.\n"); + } + =} STP(0) {= + printf("STP violation: Did not receive inputs in time, opening circuit as default.\n"); + + //debugging + if (((lf_time_logical_elapsed() - (voltageReportAIn->intended_tag.time - lf_time_start()) > 0) || + ((lf_time_logical_elapsed() - (voltageReportAIn->intended_tag.time - lf_time_start()) == 0) && + ((lf_tag().microstep - voltageReportAIn->intended_tag.microstep) > 0))) && + (voltageReportAIn->is_present)) { + printf("Input from A was tardy.\n"); + } + + if (((lf_time_logical_elapsed() - (voltageReportBIn->intended_tag.time - lf_time_start()) > 0) || + ((lf_time_logical_elapsed() - (voltageReportBIn->intended_tag.time - lf_time_start()) == 0) && + ((lf_tag().microstep - voltageReportBIn->intended_tag.microstep) > 0))) && + (voltageReportBIn->is_present)) { + printf("Input from B was tardy.\n"); + } + + if (((lf_time_logical_elapsed() - (voltageReportCIn->intended_tag.time - lf_time_start()) > 0) || + ((lf_time_logical_elapsed() - (voltageReportCIn->intended_tag.time - lf_time_start()) == 0) && + ((lf_tag().microstep - voltageReportCIn->intended_tag.microstep) > 0))) && + (voltageReportCIn->is_present)) { + printf("Input from C was tardy.\n"); + } + + if (((lf_time_logical_elapsed() - (voltageReportDIn->intended_tag.time - lf_time_start()) > 0) || + ((lf_time_logical_elapsed() - (voltageReportDIn->intended_tag.time - lf_time_start()) == 0) && + ((lf_tag().microstep - voltageReportDIn->intended_tag.microstep) > 0))) && + (voltageReportDIn->is_present)) { + printf("Input from D was tardy.\n"); + } + =} +} + +reactor CircuitStatus(STP_offset:time(0 msec)) { + preamble {= + #include + #include + #define LED0_NODE DT_ALIAS(led0) + static const struct gpio_dt_spec led = GPIO_DT_SPEC_GET(LED0_NODE, gpios); + + void openCircuit() { + // led turns on + gpio_pin_set_dt(&led, 0); + } + + void closeCircuit() { + // led turns off + gpio_pin_set_dt(&led, 1); + } + =} + + input distributedDecision:int + input countdown:int + logical action breakCircuit; + state faultHandled:int; + + reaction(startup){= + assert(device_is_ready(led.port)); + gpio_pin_configure_dt(&led, GPIO_OUTPUT_ACTIVE); + =} + + reaction(countdown) -> breakCircuit {= + self->faultHandled = 0; + lf_schedule(breakCircuit, MSEC(150)); + =} STP(0) {= + printf("STP violation for countdown\n"); + =} + + reaction(breakCircuit) {= + if (self->faultHandled){ + // This relay is not the closest to the fault + } else { + openCircuit(); + } + =} STP(0) {= + printf("STP violation for breakcircuit.\n"); + =} + + reaction(distributedDecision) -> breakCircuit {= + if (distributedDecision->value) { + openCircuit(); + self->faultHandled = 1; + } else { + closeCircuit(); + self->faultHandled = 1; + } + =} STP(0) {= + printf("STP violation for distributed decision.\n"); + =} +} + +reactor VoltageSensor(id:int(0)) { + + preamble{= + + #include + #include + static const struct gpio_dt_spec led2 = GPIO_DT_SPEC_GET(DT_PATH(leds,led_2), gpios); + const struct device *const gpio_dev = DEVICE_DT_GET(DT_NODELABEL(gpio9)); + + #define VOLTAGE_LIMIT 14000U + + // Voltage measurement timeseries + int simMeas[4][14] ={{15000,15000,13100,15000,15000,15000,15000,15000,15000,15000,15000,15000,15000,15000,15000}, + {15000,15000,11000,15000,15000,15000,15000,15000,15000,15000,15000,15000,15000,15000,15000}, + {15000,15000,11200,15000,15000,15000,15000,15000,15000,15000,15000,15000,15000,15000,15000}, + {15000,15000,12500,15000,15000,15000,15000,15000,15000,15000,15000,15000,15000,15000,15000}}; + + int readSensor(int id, int count) { + return simMeas[id][count]; + } + =} + + timer t(1 sec, 70 msec); + output[4] measOut:int + output startCountdown:int + state measCount:int(0) + + reaction(startup){= + assert(device_is_ready(led2.port)); + assert(device_is_ready(gpio_dev)); + gpio_pin_configure_dt(&led2, GPIO_OUTPUT_ACTIVE); + gpio_pin_configure(gpio_dev, 5, GPIO_OUTPUT); + gpio_pin_set_dt(&led2, 0); + gpio_pin_set(gpio_dev, 5, 1); + =} + + reaction(t) -> measOut, startCountdown {= + + // Read simulated measurement + int voltageMeasurement = readSensor(self->id, self->measCount); + if (self->measCount == 13) { + self->measCount = 0; + lf_request_stop(); + } else { + self->measCount++; + } + + // Check if short circuit has occured on the line + if (voltageMeasurement < VOLTAGE_LIMIT) { + //printk("curr logical time at send a: %lld, %d\n", lf_time_logical() - lf_time_start(), lf_tag().microstep); + //printk("curr logical time at send b: %lld, %d\n", lf_tag().time, lf_tag().microstep); + gpio_pin_set_dt(&led2, 1); + gpio_pin_set(gpio_dev, 5, 0); + // A fault has occured, let other nodes know + lf_set(startCountdown, 1); + for (int i = 0; i < 4; i++) { + lf_set(measOut[i], voltageMeasurement); + } + } + =} +} + +reactor RelayUnit(id:int(0), STP_offset:time(5 msec)) { + output[4] voltageReportOut:int; + input voltageReportAIn:int; + input voltageReportBIn:int; + input voltageReportCIn:int; + input voltageReportDIn:int; + + circuitStatus = new CircuitStatus(); + voltageSensor = new VoltageSensor(id = id); + distAlg = new DistAlg(ownId = id); + + voltageSensor.measOut -> voltageReportOut; + voltageSensor.startCountdown -> circuitStatus.countdown; + + voltageReportAIn -> distAlg.voltageReportAIn; + voltageReportBIn -> distAlg.voltageReportBIn; + voltageReportCIn -> distAlg.voltageReportCIn; + voltageReportDIn -> distAlg.voltageReportDIn; + + distAlg.distributedDecision -> circuitStatus.distributedDecision; + +} + +federated reactor at 192.0.2.2:15047 { + unitA = new RelayUnit(id = 0); + unitB = new RelayUnit(id = 1); + unitC = new RelayUnit(id = 2); + unitD = new RelayUnit(id = 3); + + unitA.voltageReportOut -> unitA.voltageReportAIn, + unitB.voltageReportAIn, + unitC.voltageReportAIn, + unitD.voltageReportAIn after 15 msec; + + unitB.voltageReportOut -> unitA.voltageReportBIn, + unitB.voltageReportBIn, + unitC.voltageReportBIn, + unitD.voltageReportBIn after 15 msec; + + unitC.voltageReportOut -> unitA.voltageReportCIn, + unitB.voltageReportCIn, + unitC.voltageReportCIn, + unitD.voltageReportCIn after 15 msec; + + unitD.voltageReportOut -> unitA.voltageReportDIn, + unitB.voltageReportDIn, + unitC.voltageReportDIn, + unitD.voltageReportDIn after 15 msec; + + // reaction(startup){= + // interval_t stp = 30000000LL; + // lf_set_stp_offset(stp); + // =} +} diff --git a/application/UserThreads/Kconfig b/application/UserThreads/Kconfig new file mode 100644 index 0000000..e36a5f7 --- /dev/null +++ b/application/UserThreads/Kconfig @@ -0,0 +1,11 @@ +# Copyright (c) 2021 Nordic Semiconductor ASA +# SPDX-License-Identifier: Apache-2.0 +# +# This file is the application Kconfig entry point. All application Kconfig +# options can be defined here or included via other application Kconfig files. +# You can browse these options using the west targets menuconfig (terminal) or +# guiconfig (GUI). + +menu "Zephyr" +source "Kconfig.zephyr" +endmenu diff --git a/application/UserThreads/debug.conf b/application/UserThreads/debug.conf new file mode 100644 index 0000000..8d2860d --- /dev/null +++ b/application/UserThreads/debug.conf @@ -0,0 +1,8 @@ +# Copyright (c) 2021 Nordic Semiconductor ASA +# SPDX-License-Identifier: Apache-2.0 +# +# This is a Kconfig fragment which can be used to enable debug-related options +# in the application. See the README for more details. + +# compiler +CONFIG_DEBUG_OPTIMIZATIONS=y diff --git a/application/UserThreads/prj.conf b/application/UserThreads/prj.conf new file mode 100644 index 0000000..428e12a --- /dev/null +++ b/application/UserThreads/prj.conf @@ -0,0 +1,4 @@ +# This configuration file will be merged with `prj_lf.conf` in the generated +# source directory `src-gen`. Be careful to not set configuration parameters +# that are conflicting with the default required parameters found in +# `prj_lf.conf` diff --git a/application/UserThreads/src/UserThreads.lf b/application/UserThreads/src/UserThreads.lf new file mode 100644 index 0000000..7bd543a --- /dev/null +++ b/application/UserThreads/src/UserThreads.lf @@ -0,0 +1,40 @@ +// Test user threads platform option for Zephyr. The application should be able +// to create exactly three threads. +target C { + platform: { + name: Zephyr, + user-threads: 3 + }, + threading: true, + workers: 2, + no-compile: true +} + +main reactor { + preamble {= + #include "platform.h" + void func(void* arg) { + lf_print("Hello from user thread"); + } + + lf_thread_t thread_ids[4]; + =} + + reaction(startup) {= + int res; + + for (int i = 0; i < 3; i++) { + res = lf_thread_create(&thread_ids[i], &func, NULL); + if (res != 0) { + lf_print_error_and_exit("Could not create thread"); + } + } + + res = lf_thread_create(&thread_ids[3], &func, NULL); + if (res == 0) { + lf_print_error_and_exit("Could create more threads than specified."); + } else { + printf("SUCCESS: Created exactly three user threads.\n"); + } + =} +} \ No newline at end of file diff --git a/scripts/lf_build.py b/scripts/lf_build.py index cd7f130..0f8018a 100644 --- a/scripts/lf_build.py +++ b/scripts/lf_build.py @@ -1,7 +1,10 @@ from west.commands import WestCommand # your extension must subclass this from west import log # use this for user output +import os import subprocess +import re +import fnmatch class LfBuild(WestCommand): def __init__(self): @@ -91,4 +94,195 @@ def do_run(self, args, unknown_args): res = subprocess.Popen(westCmd, shell=True) ret = res.wait() if ret != 0: - exit(1) \ No newline at end of file + exit(1) + +class LfFedBuild(WestCommand): + def __init__(self): + super().__init__( + 'lf-fed-build', # gets stored as self.name + 'Compile federated LF program and then run west build', # self.help + "" + ) + # To use a specific lfc binary the following variable can be modified + # or the path to the desired binary can be passed to `--lfc` + self.lfcPath = "lfc" + + def do_add_parser(self, parser_adder): + # This is a bit of boilerplate, which allows you full control over the + # type of argparse handling you want. The "parser_adder" argument is + # the return value of an argparse.ArgumentParser.add_subparsers() call. + parser = parser_adder.add_parser(self.name, + help=self.help, + description=self.description) + + # Add some example options using the standard argparse module API. + # parser.add_argument('-o', '--optional', help='an optional argument') + # parser.add_argument('project_root', help='Path to root of project') + parser.add_argument('main_lf', help='Name of main LF file') + parser.add_argument('-w', '--west-commands', help='Arguments to forward to west') + parser.add_argument('-c', '--conf-overlays', help='Additional configuration overlays') + parser.add_argument('-n', '--no-lfc', action='store_true', help='Do not generate new code using lfc') + parser.add_argument('-f', '--federate', nargs='+', help='Build only specified federates. Example useage: -f fed1 fed2') + parser.add_argument('--lfc', help='Path to LFC binary') + + return parser # gets stored as self.parser + + def do_run(self, args, unknown_args): + + if "src" not in args.main_lf.split("/"): + print("ERROR: West lf-fed-build must be invoked outside `src` folder") + + srcGenPath = args.main_lf.split(".")[0].replace("src", "fed-gen")+"/src-gen" + + appPath = args.main_lf.split("src")[0] + if appPath == "": + appPath = "." + + if args.lfc: + self.lfcPath = args.lfc + + if args.no_lfc: + print("Not executing lfc command for this build.") + else: + lfcCmd = f"{self.lfcPath} -n {args.main_lf}" + print(f"Executing lfc command: `{lfcCmd}`") + res = subprocess.Popen(lfcCmd, shell=True) + ret = res.wait() + if ret != 0: + exit(1) + + if not args.west_commands: + args.west_commands = "" + + # FIXME: This is a not-intuitive limitation from the users prespective. + # But we use `-DOVERLAY_CONFIG` to mix in the prj.conf from the app + # directory with the prj_lf.conf in the `src-gen` + if "-DOVERLAY_CONFIG" in args.west_commands: + print("Error: Use `--conf-overlays` option to pass config overlays to west") + + federateNames = [name for name in os.listdir("./"+srcGenPath)] + + # Extract board name from west command + westCmds = args.west_commands.split(" ") + if "-b" in westCmds: + boardName = westCmds[westCmds.index("-b") + 1] + elif "--board" in westCmds: + boardName = westCmds[westCmds.index("--board") + 1] + else: + print("Error: board must be specified for lf-fed-build command.") + + # Copy project configuration files into correct folders + for fedName in federateNames: + # Skip federate if federates to compile have been specified, and this federate is not included + if args.federate and fedName not in args.federate: + continue + + userConfigPaths=f"prj_{fedName}.conf" + res = subprocess.Popen(f"cp {appPath}/prj_{fedName}.conf {srcGenPath}/{fedName}/", shell=True) + ret = res.wait() + if ret != 0: + print(f"Warning: Could not find federate config file {userConfigPaths}, using default config file only. \ + If there are multiple board federates, then the IP addresses might not be configured correctly.") + userConfigPaths="" + + res = subprocess.Popen(f"cp {appPath}/{boardName}_{fedName}.overlay {srcGenPath}/{fedName}/boards/{boardName}.overlay", shell=True) + ret = res.wait() + if ret != 0: + print(f"Warning: Could not find federate dts overlay file. If there are multiple board federates, \ + then the board MAC addresses might become equal.") + + if args.conf_overlays: + res = subprocess.Popen(f"cp {appPath}/{args.conf_overlays} {srcGenPath}/{fedName}/", shell=True) + userConfigPaths += f" {args.conf_overlays}" + + res = subprocess.Popen(f"cp {appPath}/Kconfig {srcGenPath}/{fedName}/", shell=True) + ret = res.wait() + if ret != 0: + exit(1) + + compileDefs = "" + with open(f"{srcGenPath}/{fedName}/CompileDefinitions.txt") as f: + for line in f: + line = line.replace("\n", "") + compileDefs += f"-D{line} " + + print(compileDefs) + + # Invoke west in the `src-gen` directory. Pass in + westCmd = f"west build {srcGenPath}/{fedName} --build-dir ./zephyr-{fedName}-build {args.west_commands} -- -DCMAKE_BUILD_TYPE=Test -DOVERLAY_CONFIG=\"{userConfigPaths}\" {compileDefs}" + print(f"Executing west command: `{westCmd}`") + res = subprocess.Popen(westCmd, shell=True) + ret = res.wait() + if ret != 0: + exit(1) + +class LfFedFlash(WestCommand): + def __init__(self): + super().__init__( + 'lf-fed-flash', # gets stored as self.name + 'Flash multiple LF program binaries to boards', # self.help + "" + ) + + def do_add_parser(self, parser_adder): + # This is a bit of boilerplate, which allows you full control over the + # type of argparse handling you want. The "parser_adder" argument is + # the return value of an argparse.ArgumentParser.add_subparsers() call. + parser = parser_adder.add_parser(self.name, + help=self.help, + description=self.description) + + # Add some example options using the standard argparse module API. + parser.add_argument('-n', '--num-federates', type=int, help='Number of federates to flash. This should match the number of connected boards.') + + return parser # gets stored as self.parser + + def do_run(self, args, unknown_args): + + matching_dirs = [elem for elem in os.listdir(os.getcwd()) if "build" in elem] + + if not matching_dirs: + print("\nERROR: West lf-fed-flash must be invoked outside the build folder(s)\n") + else: + print(f"\nFound {len(matching_dirs)} build directories:") + print(*matching_dirs, sep=", ") + + output = subprocess.getoutput("pyocd list") + + # String manipulation to extract board info + outputList = output.split("\n")[2:] + boardEntries = [] + + for entry in outputList: + refinedEntry = ' '.join(entry.split(" ")).split() + if len(refinedEntry)==7: # Board entry length + boardEntries.append(refinedEntry) + + if len(boardEntries) == 0: + print("\nError: No boards found. Check your connection or the board's debug setup. Exiting.\n") + exit(1) + + # Print human readable board info + print(f"\nFound {len(boardEntries)} board(s): ") + for boardEntry in boardEntries: + print(f"{boardEntry[6]} with unique ID {boardEntry[4]}") + + # Information about number of federates versus number of connected boards + if len(matching_dirs) != args.num_federates: + print(f"\nError: Attempting to flash {args.num_federates} federates, with {len(matching_dirs)} federate build directories. Expected exactly {args.num_federates}. Exiting.") + exit(1) + if len(boardEntries) < args.num_federates: + print(f"\nError: Attempting to flash {args.num_federates} federates, with only {len(boardEntries)} connected boards. Not enough. Exiting.") + exit(1) + if len(boardEntries) > args.num_federates: + print(f"\nWarning: Attempting to flash {args.num_federates} federates, with too many ({len(boardEntries)}) connected boards. Extra board(s) will be unused.") + + # Start flashing + for fedBuildNum in range(0, len(matching_dirs)): + westCmd = f"west flash -r pyocd --build-dir {matching_dirs[fedBuildNum]} -i {boardEntries[fedBuildNum][4]}" + print(f"\nExecuting west command [{fedBuildNum+1}/{len(matching_dirs)}]: `{westCmd}`") + + res = subprocess.run(westCmd, shell=True) + if res.returncode != 0: + print(f"\nError: flash failed") + exit(1) \ No newline at end of file diff --git a/scripts/west-commands.yml b/scripts/west-commands.yml index f48567a..661f8ee 100644 --- a/scripts/west-commands.yml +++ b/scripts/west-commands.yml @@ -3,4 +3,10 @@ west-commands: commands: - name: lf-build class: LfBuild - help: Invoke lfc and then west build \ No newline at end of file + help: Invoke lfc and then west build + - name: lf-fed-build + class: LfFedBuild + help: Invoke lfc and then west build, tweaked for federated + - name: lf-fed-flash + class: LfFedFlash + help: Flash LF program binaries for different federation topology modes