Linux Daemons 

A (Linux) Daemon can be integrated into a system in the form of an app. After its activation, it is entered into the Linux system start and started independently of the runtime. A daemon that is independent of the runtime is called a "Linux Daemon".

 

The start time (priority) of the Linux Daemon in the Linux system start can be defined via corresponding settings in the app_info.json.

 

When the app is started, an initialization/start script is automatically created and entered in the /etc/init.d directory. The initialization script is created according to the default template unless a different template is specified in the app_info.json. The initialization script is then entered in the Linux system startup with the specified priority. The initialization script created under /etc/init.d is given the following name:

<Daemon executable binary name>-<App-Identifier>

e.g.:

/etc/init.d/SampleLinuxDaemon-60023450000002

The following attached init script template is used to create the init script.

#!/bin/sh
  NAME="@@@DAEMONNAME@@@"
  echo "name = $NAME"
  DESCRIPTION="App "
  # Path to Daemon binary
  DAEMON="@@@BINARYPATH@@@"
  DAEMON_ARGS="@@@ARGS@@@"
 
  # Exit if Daemon binary is not installed or has no execution permissions
  if ! [ -x $DAEMON ]
  then
  if [ -e $DAEMON ]
  then
     echo " Error - Daemon file $DAEMON has no execute permission. Exit"
  else
     echo " Error - Daemon file $DAEMON not found. Exit"
  fi
  exit 0
  fi
  # Read configuration variable file if it is present
  [ -r /etc/default/$NAME ] && . /etc/default/$NAME
  # Starting Daemon <NAME> <DAEMON_ARGS>
  dm_start()
  {
     start-stop-daemon --start --quiet --exec /usr/bin/env APPTEMPDIRECTORY="@@@APPTEMPDIRECTORY@@@" \ 
     APPDATADIRECTORY="@@@APPDATADIRECTORY@@@" $DAEMON -- $DAEMON_ARGS & 
  }
 
  # Stopping Daemon <NAME> <DAEMON_ARGS>
  dm_stop()
  {
     DAEMON_PID=$(pidof ${DAEMON})
     echo "PID of $DAEMON = " ${DAEMON_PID}
     start-stop-daemon --stop --quiet --retry=TERM/30/KILL/5 --exec $DAEMON &
     start-stop-daemon --stop --quiet --oknodo --retry=0/30/KILL/5 --exec $DAEMON &
     TIMEOUT_COUNTER=0
     TIMEOUT=3
     while [ ! -z "$DAEMON_PID" -a $TIMEOUT_COUNTER -lt $TIMEOUT ]; do
        sleep 1;
        TIMEOUT_COUNTER=$[$TIMEOUT_COUNTER + 1]
     done
  }
  # Sending a SIGHUP signal to Daemon <NAME> <DAEMON_ARGS>
  dm_reload() {
     start-stop-daemon --stop --signal 1 --quiet --name $NAME &
  }
  case "$1" in
 
     start)
        echo "Starting $DESCRIPTION " "$NAME"
        dm_start
        echo "$NAME" " daemon started"
        ;;
 
     stop)
        echo "Stopping $DESCRIPTION" "$NAME"
        dm_stop
        echo "$NAME" " daemon stopped"
        ;;
     restart)
        echo "Restarting $DESCRIPTION" "$NAME"
        dm_stop
        dm_start
        echo "$NAME" " daemon started"
        ;;
     *)
        echo "Usage: /etc/init.d/$NAME {start|stop|restart}" >&2
  esac

When the app is stopped, the Linux Daemon is stopped and removed from the Linux system startup.

From a shell the independent daemon:

  • To be started: /etc/init.d/<daemon init-script> start
  • To be stopped: /etc/init.d/<daemon init-script> stop
  • Restarted: /etc/init.d/<daemon init-script> restart

Necessary additions in the app description file

The "linuxdaemons" entry in the app_info.json file is structured as follows:

"linuxdaemons":
  [
     {
        "path" : "/bin/LinuxDaemonDemo",
        "cmdargs" : "<Arg1 Arg2 ... ArgN>",
        "starttime" : "99"
     },
     {
        "path" : "<Path to daemon executable binary file>",
        "cmdargs" : "<Arg1 Arg2 ... ArgN>",
        "starttime" : "<priority>">
        "initScriptTemplate":<Path to app specific init script template file>
     }
  ]

"path":

Specifies the local path to the executable binary file of the daemon in the app container.

 

"cmdargs":

Command line arguments passed at daemon startup.

 

"starttime":

Start priority of the daemon script.

 

"initScriptTemplate":

(optional field) A custom init script template can be used to extend the script functionality instead of the provided one. Please note that all variables initialized by AppManagement when starting the app must be retained.

 

The service/daemon gets the value "defaults" for the runlevel when registering. Therefore it starts in the runlevels 0,1,2,3 and stops in 4, 5, 6.

Variable  Description
@@@DAEMONNAME@@@ Name of the daemon, formed from the file­name of the binary
@@@BINARYPATH@@@ The absolute path of the binary of the dae­mon after the app is mounted.
@@@ARGS@@@ Arguments from the part configuration li­nuxdaemons.cmdargs

 

The following variables can also be used if required:

Variable  Description

@@@APPNAME@@@

The appname from the app_info.json
(plcnextapp.name)

@@@APPIDENTIFIER@@@

The identifier from the app_info.json
(plcnextapp.identifier)

@@@APPVERSION@@@

The version from the app_info.json
(plcnextapp.version)

@@@APPMANUFACTURER@@@

The manufacturer from the app_info.json (plcnextapp.manufacturer)

@@@APPDIRECTORY@@@

Directory in which the app is mounted

@@@APPTEMPDIRECTORY@@@

Temp. directory for the app
(volatile memory in RAM)

@@@APPDATADIRECTORY@@@

Data directory for the app
(persistent memory in Flash)

 

Steps to create and integrate Linux Daemons

Create the Linux Daemons:

  • Program your Linux Daemons (or Daemon).
  • If necessary, license your Linux Daemons.
  • Build your Linux Daemons using the PLCnext Software Development Kit.
  • If you want to use a custom initialization/start script template you have to supply that as well.
  • Test your Linux Daemons.

 

Integrate the Linux Daemons to your app:

  • The files you are going to need in the app folder are:
    • The executable binary file(s) for the daemon process(es) you want to run
    • The custom initialization/start scripts for the daemon(s) if needed.
    • Configuration and licensing files if necessary.

Specifications and restrictions

  • Several Linux Daemons may be active simultaneously in the system.
  • An app container can contain several Linux Daemons.
  • The names of the activated Linux Daemons (binaries) within a container must not occur more than once.
  • Linux Daemons are activated/deactivated when you start/stop the app. 
  • The activation/deactivation of a Linux Daemon does not require a restart of the firmware.
  • By default, the process of a Linux daemon is started with root rights. This can be changed in the dm_start() function of the corresponding init script.
    For this purpose the command start-stop-daemon supports the option --chuid.

    E.g. start-stop-daemon --start --quiet --chuid admin --exec /usr/bin/env ...
Note:
  • Ensure that the created linux daemon does not affect the PLCnext processes. 
  • Ensure that the init script or daemon has built-in error handling and never blocks the Linux init process in any way. For example, if a file needs to be downloaded from the internet, the internet connection must be checked first. 

Demo Linux Daemon App

In the SampleLinuxDaemon app the simple program "SampleLinuxDaemon" is started as a Linux Daemon, which permanently reads the current time stamp in an endless loop every second and writes it into a log file linuxDaemonOut.log.

The source code for the SampleLinuxDaemon:

 <stdio.h>
  #include <iostream>
  #include <string>
  #include <fstream>
  #include <chrono>
  #include <stdlib.h>
  #include <unistd.h>
  #include "cppformat/format.h"
 
  const std::string appIdentifier = "SampleLinuxDaemon";
  std::string GetCurrentTimeStamp()
  {
      std::string result = "";
 
      auto now = std::chrono::system_clock::now();
      time_t cnow = std::chrono::system_clock::to_time_t(now);
      struct tm* tmDateTime = std::localtime(&cnow);
 
     const char * timeFormatStr = "%02d%02d%d-%02d%02d%02d";
  
      result = fmt::sprintf(timeFormatStr,
        (tmDateTime->tm_year + 1900),
        (tmDateTime->tm_mon + 1),
        tmDateTime->tm_mday,
        tmDateTime->tm_hour,
        tmDateTime->tm_min,
        tmDateTime->tm_sec);
      return result;
  }
  int main(void)
  {
      std::cout << "Starting Sample Linux Daemon" << std::endl;
  
     char* plcnextAppsDataDir = getenv ("APPDATADIRECTORY");
      std::string appPersistentDataDir;
  
     // Get Apps persistent Data directory
     if(plcnextAppsDataDir != NULL)
     {      // Variant 1 to get persistent data storage path (via app identifier)
            std::cout << "Couldn’t get persistent data dir env variable, using default path"
                      << std::endl;
            appPersistentDataDir = "/opt/plcnext/appshome/data/" + appIdentifier;
      }
     else
     {
          // Variant 2 to get persistent data storage path 
          // (via environment variable exported/passed in the init script)
          appPersistentDataDir = std::string(plcnextAppDataDir);
     }
      std::cout << " Daemon persistent Data dir = " << appPersistentDataDir << std::endl;
  
      std::string logFile = appPersistentDataDir + "/linuxDaemonOut.log";
      std::ofstream outfile(logFile, std::ofstream::out | std::ofstream::app);
      std::cout << " Daemon log data file = " << logFile << std::endl;
 
     unsigned int counterValue = 0;
     while(1)
     {
         outfile << GetCurrentTimeStamp() << " - counter = " << counterValue++ << std::endl;
         //std::cout << "Current Time Stamp = " << GetCurrentTimeStamp() << std::endl;
         sleep(1);
     }
      outfile.close();
     return 0;
  }

For the daemon, persistent data/file storage must be enabled. The log file can then be created in the persistent storage directory of the app "ARP_PATH_APPS_DATA_DIR/<App Identifier>" and the data can be logged.

 

Note: App identifiers and app-specific data can also be determined automatically in a separate initScript template and passed to the daemon e.g. via command line arguments. See “Necessary additions in the app description file”.

 

The app description for the SampleLinuxDaemon is as follows:

{
     "plcnextapp": {
        "name": "Sample Linux Daemon App",
        "identifier": "60023450000002",
        "version": "11.6.0",
        "target": "AXC F 2152",
        "minfirmware_version": "19.3.0",
        "manufacturer": "PhoenixContact"
     },
     "datastorage": {
        "persistentdata": true,
        "temporarydata" : true
     },
     "linuxdaemons" :
     [
           {
           "path" : "/bin/SampleLinuxDaemon",
           "cmdargs" : "",
           "starttime": "99"
           }
     ]
}

The following sources for the sample linux daemon app are available on GitHub HERE:

  • App container folder:
    • App description file: app_info.json
    • Demo linux daemon binary file (bin/SampleLinuxDaemon)
  • Sources folder:
    • Source file of the linux daemon: SampleLinuxDaemon.cpp
    • Linux start script for the daemon created automatically using the template init script: SampleLinuxDaemon60023450000002
  • Demo App container file: SampleLinuxDaemon.app

 

Note: The identifiers used in the app descriptions of the illustrated demo applications have only demo values. The app identifier generated in the PLCnext Store on app creation must be used!

Support in the firmware

The Linux Daemons app part type is supported in the firmware from version 19.3.0.

 

 

 

 


•  Web browser recommendation: Chrome/Edge 88 or newer, Firefox ESR 90 or neweror Safari  • 
• Published/reviewed: 2023-11-17 • Revision 14 •