Skip to content

How do I get a Python program to run at boot?

Hello all,

I'm primarily a PLC programmer trying to get my head wrapped around Linux OS programming and execution. I've written a Python program that simply watches for a flag on a PLCNext Engineer PLC variable, does some epic math, and writes a value into another PLC variable. I'd like the python script to run at boot along with the PLC so that it's effectively on whenever the PLC is running.

However, I've tried many stackExchange recommended processes for getting a python program to run at boot on a Linux system and I've found that many of the commands for accomplishing that are simply missing from the PLCNext OS.

Help would be really appreciated

Comments

  • Hi potatoSalad.

    One of my colleagues with an interest in python found this blog post useful:

    It uses a different Linux distribution, but most of it can be applied to PLCnext Control devices, I think.

    I hope this helps.

  • edited May 2022

    Thank you for your suggestion, Martin. In following this process, I've found I'm not able to create a new shell script to the init.d folder. Attempting to add testApp_Service.sh to the folder through my ssh VSCode gives me an error message indicating that I don't have permission, logged in as admin, to create said file. I attempted to open permissions with chmod and change ownership to admin with chown, but those were both not allowed commands.

  • The directory /etc/init.d is owned by root and can only be written to by the owner:

    admin@axcf2152:/opt/plcnext$ ls -lsah /etc | grep init.d
    4.0K drwxr-xr-x  1 root             root             4.0K Apr 28 10:48 init.d
    

    ... so you will need to log in as root to write a file to that directory.

  • Ah! I see that I can log in as root. I was not aware of that- I assumed that admin would have all permissions. So, this is confusing.

    Activating SSH login as root user

    Step 1: Connect to the controller and log in as the root user.


  • This link was more helpful. I was able to set the root password and log in. Attempting to create boot script now.

  • No, never mind. I'm not able to log in as root.

  • Ok, I've come a long way (I think) toward actually making this work. Now, I'm getting a few errors.

    Firstly, the service script in the article you linked has this line:

    . /lib/lsb/init-functions
    

    This is, I think, meant to evoke the daemon functions used?

    root@axcf2152:/lib# sudo /etc/init.d/bootService_testApp.sh start
    /etc/init.d/bootService_testApp.sh: line 32: log_daemon_msg: command not found
    /etc/init.d/bootService_testApp.sh: line 34: log_end_msg: command not found
    

    Help would be really really appreciated. I feel like I'm close. *fingers crossed*

  • So, I commented out the daemon logging and the init-functions call. I was able to execute my bootService_testApp.sh and the python script apparently executed as a result. Awesome! Bad news, it wasn't able to find the PyPlcnextRsc module.

    Traceback (most recent call last):
     File "/opt/plcnext/testApp//main.py", line 4, in <module>
      from PyPlcnextRsc import Device, RscVariant, RscType
    ModuleNotFoundError: No module named 'PyPlcnextRsc'
    


  • My PyPlcnextRsc module is installed at:

    PyPlcnextRsc in /opt/plcnext/.local/lib/python3.8/site-packages (0.1.12)
    

    I get the feeling that this install location may be making my service execution in /etc/init.d/ fail?

  • I don't know much about python or anything about that package, but does this help?

    https://stackoverflow.com/questions/36936212/how-do-i-install-a-pip-package-globally-instead-of-locally

  • edited May 2022

    Running a pip -H install to the global root did allow the script to run. Now, as root user, I can execute the

    sudo /etc/init.d/bootService_testApp.sh start 
    

    This is resulting in my python program running in the background. However, it isn't running with a power cycle of the PLCNext controller.

    ~~~~~~~~~~~~~~~~~~~~

    Running the update-rc command from the article, I'm getting:

    root@axcf2152:/etc# sudo update-rc.d bootService_testApp.sh defaults
    System startup links for /etc/init.d/bootService_testApp.sh already exist.
    


  • Hello,

    please make sure that there is no linebreak or space at the start of your script - init.d does not like that.

    e.g.

    "

    #! /bin/bash

    "

    Also please check /var/log/boot and other log files for a indicator if the script is started - but crashes.


    How do you call your binary in the Init.d Script?

    python mypy.py

    python mypy.py &

    start-stop-daemon ....


    kind regards,

    Oliver

  • edited May 2022

    Hi Oliver,

    This is the script I'm using:

    #!/bin/sh
    ### BEGIN INIT INFO
    # Provides:          myservice
    # Required-Start:    $remote_fs $syslog
    # Required-Stop:     $remote_fs $syslog
    # Default-Start:     2 3 4 5
    # Default-Stop:      0 1 6
    # Short-Description: Put a short description of the service here
    # Description:       Put a long description of the service here
    ### END INIT INFO
    # Change the next 3 lines to suit where you install your script and what you want to call it
    DIR=/opt/plcnext/testApp/
    DAEMON=$DIR/main.py
    DAEMON_NAME=main
    # Add any command line options for your daemon here
    DAEMON_OPTS=""
    # This next line determines what user the script runs as.
    # Root generally not recommended but necessary if you are using the Raspberry Pi GPIO from Python.
    DAEMON_USER=root
    # The process ID of the script when it runs is stored here:
    PIDFILE=/var/run/$DAEMON_NAME.pid
    # removed because it doesn't exist, hoping this works
    #. /lib/lsb/init-functions
    do_start () {
        #log_daemon_msg "Starting system $DAEMON_NAME daemon"
        start-stop-daemon --start --background --pidfile $PIDFILE --make-pidfile --chuid $DAEMON_USER --startas $DAEMON -- $DAEMON_OPTS
        #log_end_msg $?
    }
    do_stop () {
        #log_daemon_msg "Stopping system $DAEMON_NAME daemon"
        start-stop-daemon --stop --pidfile $PIDFILE --retry 10
        #log_end_msg $?
    }
    case "$1" in
        start|stop)
            do_${1}
            ;;
        restart|reload|force-reload)
            do_stop
            do_start
            ;;
        status)
            status_of_proc "$DAEMON_NAME" "$DAEMON" && exit 0 || exit $?
            ;;
        *)
            echo "Usage: /etc/init.d/$DAEMON_NAME {start|stop|restart|status}"
            exit 1
            ;;
    esac
    exit 0
    

    So I have two directories that I'm working with...

    First, obviously, is /etc/init.d/

    My "working" directory is /opt/plcnext/testApp/

    Inside the testApp folder I have a copy of the bootService_testApp.sh that I edit while logged in as admin. I am unable to ssh with root login. So, my process is this:

    1. Edit bootService_testApp.sh in my working directory as admin
    2. su
    3. root@axcf2152:/opt/plcnext/testApp# cp /opt/plcnext/testApp/bootService_testApp.sh /etc/init.d/
    4. root@axcf2152:/opt/plcnext/testApp# chmod 755 /etc/init.d/bootService_testApp.sh
    5. root@axcf2152:/opt/plcnext/testApp# sudo /etc/init.d/bootService_testApp.sh start

    The python program main.py in /testApp/ does then run (I have it toggling a bit in the PLC which I can view live in PLCnext Engineer.

    Thanks for taking a look at this, Oliver. My much more linux savvy coworker took a look at this with me over the weekend and we could NOT figure it out.

  • And, I did reboot the controller after eliminating all spaces in the .sh, as you can see, and the python program did not run.

  • As you can see, I commented out all of the log_daemon_msg and log-end-msg stuff because Phoenix Contact's implementation of Linux just doesn't have the /lib/lsb/init-functions source file. 🤷‍♂️

  • Hello potatoSalad,

    how is your app connecting to a PLCnext Service and what is the behavior when this service is not yet available?

    PLCnext Runtime and the Services are starting very late during boot.

    Maybe that service is not started yet resulting in a crash of your Py application.

    Maybe $local_fs $network is required for start?


    For some debugging in /var/log you can substitute

    log_daemon_msg 
    

    with echo or 'echo "..." >> /var/log/yourlog.'


    or start -stop daemon with

    -C, --no-close

              Do not close any file descriptor when forcing the daemon into
              the background (since version 1.16.5).  Used for debugging
              purposes to see the process output, or to redirect file
              descriptors to log the process output.  Only relevant when
              using --background.
    

    and

    > /var/log/python.log 2>&1
    


  • This is the package I'm using for PLC interface:

    Here:

    you can see under Create Connection:

    Use with-block , which will automatically connect and dispose the connection
    with Device('192.168.1.10', secureInfoSupplier=GUISupplierExample) as device:
        ...  # do rsc operation
    

    This is the python program main.py:

    #!/usr/bin/env python3.8
    
    from PyPlcnextRsc import Device, RscVariant, RscType
    from PyPlcnextRsc.Arp.Plc.Gds.Services import IDataAccessService, WriteItem
    import time
    
    secureInfoSupplier = lambda:("admin","password")
    
    #flip between writing values
    flipper = 0
    
    while True:
        
        with Device('192.168.0.10', secureInfoSupplier=secureInfoSupplier) as device:
    
            data_access_service = IDataAccessService(device)
    
            if flipper == 0:
                rscv = RscVariant(1,RscType.Int16)
                flipper = 1
    
            else:
                rscv = RscVariant(3,RscType.Int16)
                flipper = 0
    
            wi = WriteItem("Arp.Plc.Eclr/testInt", rscv)
            data_access_service.WriteSingle(wi)
    

    Because the with-block style instantiation of a connected device is supposed to automatically handle connections, I didn't think booting before the PLC start would be an issue.

  • I don't think that will work if the PLCnext Runtime is not running when the while True loop is entered.

    You could try putting a delay (maybe 30-60 seconds) in your code, before the while True loop, to see if that makes a difference.

  • Well, I put a 60 second timer on it and it actually worked! It must have been that the python program was crashing. I'll look into how to bulletproof that process. There must be a way to query whether the PLCnext Runtime is running before attempting variable read/write.

    Also, someone had asked about whether I could run

    sudo /etc/init.d/bootService_testApp.sh status
    

    which I cannot. The status command calls the case block

    status)
            status_of_proc "$DAEMON_NAME" "$DAEMON" && exit 0 || exit $?
            ;;
    

    And fails on

    status_of_proc: command not found
    

    I'm guessing status_of_proc is part of the /lib/lsb/init-functions source file that PLCnext's Linux implementation does not support.

  • edited June 2022

    So, I figured it all out. And I made a tutorial explaining how to do it, start to finish.

    https://youtu.be/PgFZlK38kKc

  • And, the timer is no longer necessary because I wrapped the RSC connection in a Try Except block so that the PLC will attempt to connect once per second upon boot until the PLC Runtime is able to connect.

Sign In or Register to comment.