Guide: Creating custom services

It is possible to create custom services that runs on the device, including custom PIP package requirements, managed directly from my.autopi.io.
These services are started when the device boots, and if they crash, they will be restarted automatically.

Steps to create a new service

Note: Make sure that your device is updated, as that will otherwise prevent you from syncing the modules.

  1. Go to the Advanced -> Custom Module section and click Create.
    image

  2. Fill out the fields, and change the type to Service
    image

  3. After changing the type, the code editor will show the sample code for a basic service, like this:
    This is a basic service, which logs when it starts and stops, you can then modify this to do whatever you like.
    Just remember that the code inside the try except must be blocking, so the below example will simply start and stop.

image

  1. When you click save, the custom module will of course be saved, and it will also create a new service for you. This service is by default not enabled. To enable the service, click the icon to navigate to the service.

And then check the enabled checkbox in the settings tab, and click save.

  1. Now sync the changes by clicking the sync button.
  2. When asked if you want to restart the salt-minion process, you should click yes, as the service is not loaded until the process has been restarted.
    You can also restart the minion process by running the following commands.

… In Web terminal

minionutil.restart

… In SSH terminal

systemctl restart salt-minion

PIP Requirements

You can add PIP requirements to custom code modules like so.
image
(Follow the requirements.txt convention)

Pass settings into the service.

To pass settings into the service, you should set the settings field to a valid JSON object.


Which can then be accessed inside the service like so:

some_setting = settings.get("some_setting", "default_value")

A few examples

Log hello world every 5 seconds

So lets try creating a service that simply outputs “Hello World” to the log every 5 seconds.

import logging
import time

log = logging.getLogger(__name__)

def start(**settings):
    try:
        log.info("Starting SERVICE with settings: {:}".format(settings))

        while True:
            log.info('HELLO WORLD, anything new in the last 5 seconds?')
            time.sleep(5)

    except Exception:
        log.exception("Failed to start SERVICE")

        raise
    finally:
        log.info("Stopping SERVICE")

        # Stop everything, close connections etc.

And if you have info level debugging configured on your device, you should now see it logging hello world every 5 seconds.
NOTE: The log level of the device can be changed from the advanced settings, under System -> Logging level

Listen to MQTT topics

This service will simply subscribe to all topics and log the messages.

Remember to add the following PIP requirements to the module

paho-mqtt==1.5.0

Code:

import logging
import paho.mqtt.client as mqtt

log = logging.getLogger(__name__)

connect_results = {
    0: "Connection accepted",
    1: "The Server does not support the level of the MQTT protocol requested by the Client",
    2: "The Client identifier is correct UTF-8 but not allowed by the Server",
    3: "The Network Connection has been made but the MQTT service is unavailable",
    4: "The data in the user name or password is malformed",
    5: "The Client is not authorized to connect",
}

def on_message(mqttc, obj, msg):
    log.info(msg.topic + " " + str(msg.qos) + " " + str(msg.payload))

def on_subscribe(mqttc, obj, mid, granted_qos):
    log.info("Subscribed: " + str(mid) + " " + str(granted_qos))

def on_log(mqttc, obj, level, string):
    log.info(string)

def on_connect(mqttc, obj, flags, rc):
    result = "{:}: {:}".format(rc, connect_results.get(rc, "Unknown result code"))    
    if rc == 0:
        log.info(result)
    else:
        raise Exception(result)

    try:
        mqttc.subscribe("#", qos) # Subscribes to all topics.
    except Exception as ex:
        log.exception("Exception occurred when setting up registers")

# glocal settings
qos = 0

def start(**settings):
    global qos

    usetls = True
    tlsVersion = None # will use most recent version
    cacerts = None

    port = 8883
    host = ""
    username = "username"
    password = "password"

    debug = False
    keepalive = 60

    # Setup
    mqttc = mqtt.Client(None, clean_session=True)
    if usetls:
        mqttc.tls_set(ca_certs=cacerts, certfile=None, keyfile=None, cert_reqs=ssl.CERT_REQUIRED, tls_version=tlsVersion)
    mqttc.username_pw_set(username, password)

    mqttc.on_message = on_message
    mqttc.on_connect = on_connect
    mqttc.on_subscribe = on_subscribe

    if debug:
        mqttc.on_log = on_log

    try:
        log.info("Starting MQTT engine")

        log.info("Connecting to {:} port: {:}".format(host, port))
        mqttc.connect(host, port, keepalive)
        mqttc.loop_forever(retry_first_connection=True)

    except Exception:
        log.exception("Failed to start MQTT engine")

        raise
    finally:
        log.info("Stopping MQTT engine")
        mqttc.disconnect()

Further inspiration on how to write custom services (or engines as they are also called).

Not working?

Check the log file on the device to see if there are warnings and/or errors: https://community.autopi.io/t/guide-how-to-retrieve-logs-from-your-device/

Need help?

Please visit our documentation site http://docs.autopi.io/ or contact support@autopi.io.

1 Like

There is no option for setting the log level

Hi James!

To change the log level of the device, you can go to my.autopi.io and navigate to the Settings page. From there, go to the advanced system settings for your devices (Devices > Advanced > System). You should be able to see a Logging entry, where you’ll see a dropdown menu with all available log levels.

Please note that changing the logging setting to anything more than “Warning” might slow down the device.

Best
Nikola

I am trying to test the pip requirement. I have configured my custom code like this

. The syncing is failing without any details of what the problem is.

Fixed. The pip requirement will not install for some reason. But the messages are not helpful at all. If the pip is failing, then the error should be clear that that is the problem.

Hi James.

You can view the errors for the service installation if you go over to Advanced > Change History after you’ve synchronized the new changes with your device. There you should be able to see the failed change, which shows some feedback on why it’s failing.


In this case, it seems to be an issue with pip not being able to find and install the module for python2.7.

Best
Nikola

When I try this exact code. It fails with an error that protocol not supported.

When creating service, can I make sure or setup the logger for my_ service to write into particular file?

I have the same question as Dima. I would like to route my logging to a particular file for my service for debugging. Does anyone know how to do this?