Build a small C application that writes hello world to the system log on a RobustOS Device. This article includes every source and packaging file, then shows how to export, install and verify the package.
Applies to: Application development for RobustOS Devices using the SDK package and build target supplied for the exact device model and variant.
Development testing: The example was built with SDK 5.0.3 and tested on an R1520 running RobustOS 5.6.0. Use the matching SDK and target for your device.
You need basic C and terminal skills. You can complete the build before connecting a device; installation and execution checks require a suitable development device and access to its Web interface.
Complete the Docker SDK workspace setup. You need a confirmed build target, a running SDK container, and an SDK workspace at /root/ROS-SDK. Replace all angle-bracket placeholders with your project values. Commands labelled host, container and device run in those separate environments.
The application sends hello world at debug level to the device's system log, then exits successfully. It is a short program, so a later process-status check may report that it is stopped. A new log entry is the expected execution result.
You will create a source file, a source Makefile, an SDK lifecycle script, and a package Makefile. The package Makefile tells the SDK how to build and place files in the device installation package.
Create a local working folder containing examples/helloworld/. All four complete files are provided below:
helloworld/
├── Makefile
├── src/
│ ├── helloworld.c
│ └── Makefile
└── files/
└── sdk.sh
Keep the source and scripts as plain text with LF line endings. A CRLF lifecycle script can build successfully but fail on the device; check its line endings before packaging. Preserve the leading tabs in Makefile recipe lines. The package name helloworld contains no hyphen and is shorter than 18 characters, as required by the package naming rules.
Create examples/helloworld/src/helloworld.c on the host with this content. It becomes package/helloworld/src/helloworld.c after copying:
#include <syslog.h>
int main(void)
{
syslog(LOG_DEBUG, "hello world");
return 0;
}
The program uses the system syslog() interface. It does not need a persistent loop to demonstrate compilation, packaging, and execution.
Create examples/helloworld/src/Makefile on the host with this content:
helloworld:
$(CC) helloworld.c -o helloworld
The command line begins with a tab. $(CC) lets the SDK build process select the compiler. Build the package through the SDK root Makefile, so that the target configuration is used.
package/helloworld/files/sdk.sh sets PROG=helloworld and loads /usr/lib/functions on the device. Its handlers support start, stop, restart, status, on_startup, on_reboot, on_link_up, on_link_down, and status_sync. The example starts on startup; a link-up event stops and starts it; link-down and reboot events invoke stop. status_sync returns without adding status data.
Create examples/helloworld/files/sdk.sh with this complete content:
#!/bin/sh
. /usr/lib/functions
RETVAL=0
PROG=helloworld
start()
{
${PROG}
return $?
}
stop()
{
killproc ${PROG}
RETVAL=$?
return $RETVAL
}
restart()
{
stop
start
}
status()
{
pidof -o %PPID ${PROG} >/dev/null 2>&1
RETVAL=$?
[ $RETVAL -eq 0 ] && echo ${PROG} is running || echo ${PROG} is stopped
return $RETVAL
}
status_sync()
{
return 0
}
case "$1" in
start)
start
;;
stop)
stop
;;
restart)
restart
;;
status)
status
;;
on_startup)
start
;;
on_link_up)
stop
start
;;
on_link_down)
stop
;;
on_reboot)
stop
;;
status_sync)
status_sync
;;
*)
echo "Usage: $0 {start|stop|restart|status|on_startup|on_link_up|on_link_down|on_reboot|status_sync}"
exit 2
esac
exit $?
The start handler invokes the program and returns its exit status. Its process-based status handler can report stopped after this one-shot program finishes.
Create examples/helloworld/Makefile on the host with this content:
include $(TOPDIR)/rules.mk
PKG_NAME:=helloworld
PKG_VERSION:=1.0
PKG_DESC:=helloworld example
PKG_BUILD_DIR:=$(BUILD_DIR)/$(PKG_NAME)-$(PKG_VERSION)
PKG_UNPACK:=cp -rf src/* $(PKG_BUILD_DIR)
PKG_BUILD_PARALLEL:=1
PKG_BUILD_DEPENDS:=
include $(INCLUDE_DIR)/package.mk
define Package/Install
$(INSTALL_DIR) $(1)/usr/bin/
$(INSTALL_BIN) $(PKG_BUILD_DIR)/helloworld $(1)/usr/bin/
$(INSTALL_DIR) $(1)/etc/sdk/
$(INSTALL_BIN) files/sdk.sh $(1)/etc/sdk/$(PKG_NAME)
endef
$(eval $(call BuildPackage))
This places the executable at /usr/bin/helloworld and the lifecycle script at /etc/sdk/helloworld in the package's device filesystem. The source Makefile compiles the program; this package Makefile integrates it with the SDK packaging system.
On the host, check that you will not overwrite an existing package:
docker exec -w /root/ROS-SDK "<SDK_CONTAINER>" sh -c "test ! -e package/helloworld"
Exit status zero means that the path is absent. If it exists, inspect it and keep any user changes before continuing. From the local folder containing your examples directory, copy the example on the host:
docker cp "./examples/helloworld" "<SDK_CONTAINER>:/root/ROS-SDK/package/"
Run the command from that folder in the environment where your Docker CLI runs; container paths use forward slashes. Open the SDK shell again to inspect the copied files.
In the SDK container, check the lifecycle script for carriage returns and inspect the source Makefile recipe tabs. No output from the first command means no CR characters were found; the second displays each tab as ^I. If the script contains CR characters, save it with LF line endings and copy it again before building.
grep -n "$(printf '\r')" package/helloworld/files/sdk.sh
cat -A package/helloworld/src/Makefile
Inside the container, from /root/ROS-SDK, run:
make package/helloworld/install
Check the exit status and build output. For the first build, confirm that the target compiler actually compiled helloworld.c and that packaging completed. A package left over from a previous attempt is not evidence that this build succeeded.
Find the resulting file inside the container:
find bin -type f -name '*helloworld*.rpk'
Match the result to the selected target and build output. Record its full container path. Create an output directory on your host using your file manager or terminal, then export the selected file from the host:
docker cp "<SDK_CONTAINER>:<ABSOLUTE_RPK_PATH_IN_CONTAINER>" "<EXISTING_HOST_OUTPUT_DIRECTORY>"
Check that the exported file exists and is non-empty. Calculate its SHA256 using the host-specific command in the Docker SDK setup guide, and retain it with the build log, SDK image ID, and target. The RPK hash identifies this particular file. SDK 5.0.3 packages can include a timestamp, so a rebuild may produce a different hash even when the executable is unchanged. The next steps verify the package on a device.
Confirm that the device model, variant, and firmware are compatible with the SDK target you used. Obtain access to its Web interface and check its application-signing requirements before uploading the package.
The SDK 5.0.3 build signs packages with its development key. An R1520 running RobustOS 5.6.0 accepted the resulting sample package without Developer Mode. If the device enforces application signatures, use a signed package or the authorized development workflow for that firmware. An unsigned local build may be rejected. Developer Mode permits unsigned development packages where supported; it is a device-side development setting, and production deployment requires the appropriate signed package and production settings. Follow the signing and Developer Mode procedure for your device.
Building in the SDK container does not require a root login to the device. A device root shell, if needed for debugging, requires the applicable Robustel authorization and access setup. Contact Robustel Support with the device serial number to obtain the device-specific license; import it through the device Web interface and set the root password before using SSH or serial access.
helloworld. Resolve any reported compatibility or signature error before proceeding.hello world message from this installation or execution attempt. Note the time window so that you do not count a message from an earlier run.Look for the message text hello world. The log prefix may include syslog:; timestamps and interface appearance depend on the device configuration and firmware.
If the package is installed but there is no new message, first determine whether the application ran and whether debug-level messages are visible. On a development device where you already have authorized shell access, you can explicitly invoke the installed example:
# Device shell
/etc/sdk/helloworld start
Then check for a new message in the Web interface or the device's /var/log/messages log. If the message is still absent, retain the command result, device firmware, target, and log settings for investigation. Do not change the example into a long-running process merely to make the status display say “running.”
| Symptom | Check before retrying |
|---|---|
| Docker client works but the server does not respond | Runtime service, selected context, account access and host prerequisites |
| SDK root is missing or empty | Loaded image, workspace path, named volume and whether an old volume obscures the image's files |
| Build target or sample package is unavailable | SDK release-to-device mapping and the actual contents of the loaded SDK |
| Make reports a recipe/formatting error | Makefile tabs, copied punctuation and LF line endings; compare with the supplied files |
| Build fails or no current RPK is produced | First failing command in the build log; target selection; do not export an old package as the result |
| Installation is rejected | Package target, compatible firmware and signature/development requirements |
| App shows stopped after execution | Expected for this one-shot example; check the new log message |
You have completed the first-application path when the target C build succeeds, its RPK is exported, the matching device accepts it, and a new hello world message confirms execution.