> For the complete documentation index, see [llms.txt](https://docs.robpod.cloud/llms.txt). Markdown versions of documentation pages are available by appending `.md` to page URLs; this page is available as [Markdown](https://docs.robpod.cloud/advanced-scripting/background-task/learn-by-examples.md).

# Learn by Examples

In this section, you’ll learn how to use the python background library through practical examples. The Background library is used to create [background tasks](/advanced-scripting/background-task.md) that run in parallel with the robot’s main program.

### 01 Basic API

In this example the background task is used to control the color of a LED light based on the robot state.

{% tabs %}
{% tab title="Universal Robots" %}
{% code overflow="wrap" lineNumbers="true" expandable="true" %}

```python
from robpod.background import *

RED = 1
YELLOW = 1
GREEN = 1

# wait for robot to complete startup
wait_boot_completed()


def blink(red=OFF, yellow=OFF, green=OFF):
    # use log function to print message on log file
    log("Blink red={}, yellow={}, green={}".format(red, yellow, green))

    # operate digital outputs
    set_digital_output(RED, red)
    set_digital_output(YELLOW, yellow)
    set_digital_output(GREEN, green)
    time.sleep(0.5)

    set_digital_output(RED, OFF)
    set_digital_output(YELLOW, OFF)
    set_digital_output(GREEN, OFF)
    time.sleep(0.5)


while True:
    # get current robot state
    robot_state = get_state()

    # blink with LED color based on robot status
    if robot_state == RobotStatus.EMERGENCY_STOP:
        blink(red=ON)
    elif robot_state in [RobotStatus.RECOVERY, RobotStatus.SAFETY_STOP]:
        blink(yellow=ON)
    elif robot_state in [RobotStatus.SERVO_ON, RobotStatus.SERVO_ON_REDUCED]:
        blink(green=ON)
    else:
        time.sleep(1)
```

{% endcode %}
{% endtab %}

{% tab title="Doosan Robotics" %}
{% code overflow="wrap" lineNumbers="true" expandable="true" %}

```python
from robpod.background import *

RED = 1
YELLOW = 1
GREEN = 1

# wait for robot to complete startup
wait_boot_completed()


def blink(red=OFF, yellow=OFF, green=OFF):
    # use log function to print message on log file
    log("Blink red={}, yellow={}, green={}".format(red, yellow, green))

    # operate digital outputs
    set_digital_output(RED, red)
    set_digital_output(YELLOW, yellow)
    set_digital_output(GREEN, green)
    time.sleep(0.5)

    set_digital_output(RED, OFF)
    set_digital_output(YELLOW, OFF)
    set_digital_output(GREEN, OFF)
    time.sleep(0.5)


while True:
    # get current robot state
    robot_state = get_state()

    # blink with LED color based on robot status
    if robot_state == RobotStatus.EMERGENCY_STOP:
        blink(red=ON)
    elif robot_state in [RobotStatus.RECOVERY, RobotStatus.SAFETY_STOP]:
        blink(yellow=ON)
    elif robot_state in [RobotStatus.SERVO_ON, RobotStatus.SERVO_ON_REDUCED]:
        blink(green=ON)
    else:
        time.sleep(1)
```

{% endcode %}
{% endtab %}
{% endtabs %}

#### Key Points to Note:

* The Background Task is developed as standard python script.
* At the beginning of every background task, always call `wait_boot_completed()`. This ensures that the robot controller has completed its startup procedure before the task proceeds. This is particularly important when a background task starts automatically at startup, as the controller may launch it before initialization is complete.
* Use functions `set_digital_output(index, value)` and `get_digital_input(index)` to control the robot digital I/O.
* Use functions `set_analog_output(index, value)` and `get_analog_input(index)`  to control the robot analog I/O.
* Use function `get_state()` to get the current status of the robot.
* Use functions `get_program_state()` and `get_program_name()` to get information of running robot program.&#x20;
* Use functions `get_tcp_pose()` and `get_joint_pose()` to get current robot position.
* **Allways use sleeps in loops to avoid high CPU load on robot controller.**

### 02 Rule Scheduler

The previous example could also re-writted using the rule scheduler syntax.

Use **when** construction

`when(condition).do(action).start()`

to automatically and continually trigger *action* if *condition* is True.

Use **on** construction

`on(condition).do(action).start()`

to trigger *action* only on rising edge of *condition*. i.e. *action* is triggered just one time when *condition* becomes true.

{% tabs %}
{% tab title="Universal Robots" %}
{% code overflow="wrap" lineNumbers="true" expandable="true" %}

```python
from robpod.background import *

RED = 1
YELLOW = 1
GREEN = 1

# wait for robot to complete startup
wait_boot_completed()


def blink(red=OFF, yellow=OFF, green=OFF):
    # use log function to print message on log file
    log("Blink red={}, yellow={}, green={}".format(red, yellow, green))

    # operate digital outputs
    set_digital_output(RED, red)
    set_digital_output(YELLOW, yellow)
    set_digital_output(GREEN, green)
    time.sleep(0.5)

    set_digital_output(RED, OFF)
    set_digital_output(YELLOW, OFF)
    set_digital_output(GREEN, OFF)
    time.sleep(0.5)

# rule conditions
is_emergency = lambda: get_state() == RobotStatus.EMERGENCY_STOP
is_safety_stop = lambda: get_state() in [RobotStatus.RECOVERY, RobotStatus.SAFETY_STOP]
is_servo_on = lambda: get_state() in [RobotStatus.SERVO_ON, RobotStatus.SERVO_ON_REDUCED]

# rule actions
blink_red = lambda: blink(red=ON)
blink_yellow = lambda: blink(yellow=ON)
blink_green = lambda: blink(green=ON)

# rules definition
when(is_emergency).do(blink_red).start()
when(is_safety_stop).do(blink_yellow).start()
when(is_servo_on).do(blink_green).start()
```

{% endcode %}
{% endtab %}

{% tab title="Doosan Robotics" %}
{% code overflow="wrap" lineNumbers="true" expandable="true" %}

```python
from robpod.background import *

RED = 1
YELLOW = 1
GREEN = 1

# wait for robot to complete startup
wait_boot_completed()


def blink(red=OFF, yellow=OFF, green=OFF):
    # use log function to print message on log file
    log("Blink red={}, yellow={}, green={}".format(red, yellow, green))

    # operate digital outputs
    set_digital_output(RED, red)
    set_digital_output(YELLOW, yellow)
    set_digital_output(GREEN, green)
    time.sleep(0.5)

    set_digital_output(RED, OFF)
    set_digital_output(YELLOW, OFF)
    set_digital_output(GREEN, OFF)
    time.sleep(0.5)

# rule conditions
is_emergency = lambda: get_state() == RobotStatus.EMERGENCY_STOP
is_safety_stop = lambda: get_state() in [RobotStatus.RECOVERY, RobotStatus.SAFETY_STOP]
is_servo_on = lambda: get_state() in [RobotStatus.SERVO_ON, RobotStatus.SERVO_ON_REDUCED]

# rule actions
blink_red = lambda: blink(red=ON)
blink_yellow = lambda: blink(yellow=ON)
blink_green = lambda: blink(green=ON)

# rules definition
when(is_emergency).do(blink_red).start()
when(is_safety_stop).do(blink_yellow).start()
when(is_servo_on).do(blink_green).start()
```

{% endcode %}
{% endtab %}
{% endtabs %}

### 03 Error Handling

Use Python’s standard **try / except** block for error handling. This is important because if an exception or error is raised outside the script, the background task will terminate and will not restart automatically.

{% tabs %}
{% tab title="Universal Robots" %}
{% code overflow="wrap" lineNumbers="true" expandable="true" %}

```python
from robpod.background import *

RED = 1
YELLOW = 1
GREEN = 1

# wait for robot to complete startup
wait_boot_completed()


while True:
    # use try catch block for error handling
    try:
        # get current robot cartesian position
        robot_pose = get_tcp_pose()

        if robot_pose[2] > 0:
            log("Robot z above 0")
        else:
            raise Exception("Robot z below 0")

    except Exception as e:
        log(e)
    finally:
        # allways use sleeps in loops to avoid high CPU load on robot controller
        time.sleep(1)


```

{% endcode %}
{% endtab %}

{% tab title="Doosan Robotics" %}
{% code overflow="wrap" lineNumbers="true" expandable="true" %}

```python
from robpod.background import *

RED = 1
YELLOW = 1
GREEN = 1

# wait for robot to complete startup
wait_boot_completed()


while True:
    # use try catch block for error handling
    try:
        # get current robot cartesian position
        robot_pose = get_tcp_pose()

        if robot_pose[2] > 0:
            log("Robot z above 0")
        else:
            raise Exception("Robot z below 0")

    except Exception as e:
        log(e)
    finally:
        # allways use sleeps in loops to avoid high CPU load on robot controller
        time.sleep(1)
```

{% endcode %}
{% endtab %}
{% endtabs %}

Alternatively, you can use the `@loop` and `@repeat` annotations for error handling.

* `@loop` continuously repeats a function. The function terminates if an exception is raised.
* `@repeat` retries a function if an exception is raised, repeating execution until it succeeds.

So the previous example can be re-written also in this way:

{% tabs %}
{% tab title="Universal Robots" %}
{% code overflow="wrap" lineNumbers="true" expandable="true" %}

```python
from robpod.background import *

# wait for robot to complete startup
wait_boot_completed()


@loop(delay=1)
@retry
def monitor_z():
    # get current robot cartesian position
    robot_pose = get_tcp_pose()

    if robot_pose[2] > 0:
        log("Robot z above 0")
    else:
        log("Robot z below 0")
        raise Exception("Robot z below 0")


monitor_z()
```

{% endcode %}
{% endtab %}

{% tab title="Doosan Robotics" %}
{% code overflow="wrap" lineNumbers="true" expandable="true" %}

```python
from robpod.background import *

# wait for robot to complete startup
wait_boot_completed()


@loop(delay=1)
@retry
def monitor_z():
    # get current robot cartesian position
    robot_pose = get_tcp_pose()

    if robot_pose[2] > 0:
        log("Robot z above 0")
    else:
        log("Robot z below 0")
        raise Exception("Robot z below 0")


monitor_z()
```

{% endcode %}
{% endtab %}
{% endtabs %}

### **04 Threads**

You can use Python threads inside the background task:

`threading.Thread(target=thread_function).start()`

{% tabs %}
{% tab title="Universal Robots" %}
{% code overflow="wrap" lineNumbers="true" expandable="true" %}

```python
from robpod.background import *

# wait for robot to complete startup
wait_boot_completed()


@loop(delay=5)
@retry
def monitor_z():
    # get current robot cartesian position
    robot_pose = get_tcp_pose()

    if robot_pose[2] > 0:
        log("Robot z above 0")
    else:
        log("Robot z above 0")


@loop(delay=2)
@retry
def monitor_program():
    # get current program state
    program_state = get_program_state()

    if program_state == ProgramStatus.RUNNING:
        program_name = get_program_name()
        log("Program running: "+program_name)
    else:
        log("Program not running")


# start monitor functions as parallel threads
threading.Thread(target=monitor_z).start()
threading.Thread(target=monitor_program).start()

```

{% endcode %}
{% endtab %}

{% tab title="Doosan Robotics" %}
{% code overflow="wrap" lineNumbers="true" expandable="true" %}

```python
from robpod.background import *

# wait for robot to complete startup
wait_boot_completed()


@loop(delay=5)
@retry
def monitor_z():
    # get current robot cartesian position
    robot_pose = get_tcp_pose()

    if robot_pose[2] > 0:
        log("Robot z above 0")
    else:
        log("Robot z above 0")


@loop(delay=2)
@retry
def monitor_program():
    # get current program state
    program_state = get_program_state()

    if program_state == ProgramStatus.RUNNING:
        program_name = get_program_name()
        log("Program running: "+program_name)
    else:
        log("Program not running")


# start monitor functions as parallel threads
threading.Thread(target=monitor_z).start()
threading.Thread(target=monitor_program).start()
```

{% endcode %}
{% endtab %}
{% endtabs %}

### **05 Interacting with the Robot Program**

#### Modbus

To interact with the robot main program modbus general purpose registers can be used.

{% tabs %}
{% tab title="Universal Robots" %}
{% code title="Background Task" overflow="wrap" lineNumbers="true" expandable="true" %}

```python
from robpod.background import *

# wait for robot to complete startup
wait_boot_completed()

while True:
    # read general purpose modbus register
    val = get_register(128)

    # write general purpose modbus register
    set_register(129, val + 1)

    time.sleep(1)

```

{% endcode %}

{% code title="Robot Program" overflow="wrap" lineNumbers="true" expandable="true" %}

```python
while True:
    # read general purpose modbus register
    val = read_port_register(129)
	
	# write general purpose modbus register
	write_port_register(128, val)
	sleep(1)
end

```

{% endcode %}
{% endtab %}

{% tab title="Doosan Robotics" %}
{% code title="Background Task" overflow="wrap" lineNumbers="true" expandable="true" %}

```python
from robpod.background import *

# wait for robot to complete startup
wait_boot_completed()

while True:
    # read general purpose modbus register
    val = get_register(128)

    # write general purpose modbus register
    set_register(129, val + 1)

    time.sleep(1)
```

{% endcode %}

{% code title="Robot Program" overflow="wrap" lineNumbers="true" expandable="true" %}

```python
from DRCF import *

while True:
    # read general purpose modbus register
    val = get_modbus_slave(129)

    # write general purpose modbus register
    set_modbus_slave(128, val)
    wait(1)

```

{% endcode %}

{% endtab %}
{% endtabs %}

#### RPC - Remote Procedure Call

Also xmlrpc protocol can be used to call functions defined in the background task from the robot main program.

{% tabs %}
{% tab title="Universal Robots" %}
{% code title="Background Task" overflow="wrap" lineNumbers="true" expandable="true" %}

```python
from robpod.background import *
from SimpleXMLRPCServer import SimpleXMLRPCServer

# wait for robot to complete startup
wait_boot_completed()

# variables to simulate ERP values
job1 = {"id": "JOB0001",
       "name": "Assembly PART1",
       "parts": 25,
       "parts_completed": 0,
       "status": "PENDING"}

job2 = {"id": "JOB0002",
       "name": "Assembly PART0",
       "parts": 12,
       "parts_completed": 0,
       "status": "PENDING"}

pending_jobs = [job1, job2]
completed_jobs = []


# remote functions to be called by the robot program
def get_job(job_id):
    for job in pending_jobs:
        if job_id == job["id"]:
            return job


def get_current_job():
    if len(pending_jobs) > 0:
        return pending_jobs[0]
    else:
        return None


def set_completed_parts(job_id, parts_completed):
    job = get_job(job_id)
    job["parts_completed"] = parts_completed
    log("Completed part: {}".format(parts_completed))


def complete_job(job_id):
    job = get_job(job_id)
    job["parts_completed"] = job["parts"]
    job["status"] = "COMPLETED"
    pending_jobs.remove(job)
    completed_jobs.append(job)
    log("Completed job: {}".format(job_id))


# set up the RPC server
server = SimpleXMLRPCServer(("0.0.0.0", 8000), allow_none=True)

# register remote functions
server.register_function(get_current_job, "get_current_job")
server.register_function(set_completed_parts, "set_completed_parts")
server.register_function(complete_job, "complete_job")

# start the server
log("XML-RPC server listening on port 8000...")
server.serve_forever()

```

{% endcode %}

{% code title="Robot Program" overflow="wrap" lineNumbers="true" expandable="true" %}

```python
# define the RPC client
background = rpc_factory("xmlrpc", "http://127.0.0.1:8000/")

# call remote function and retrieve data
job = background.get_current_job()

if job == None:
    textmsg("No pending jobs")
    halt
end

textmsg("Working on job: "+job.id)

count = job.parts_completed
while count < job.parts:

    count = count + 1
    
    # use remote function to update date in the background task
    background.set_completed_parts(job.id, count)
    textmsg("Completed part: "+to_str(count))
end

background.complete_job(job.id)
textmsg("Job completed: "+job.id)


```

{% endcode %}
{% endtab %}

{% tab title="Doosan Robotics" %}
{% code title="Background Task" overflow="wrap" lineNumbers="true" expandable="true" %}

```python
from robpod.background import *
from xmlrpc.server import SimpleXMLRPCServer

# Wait for the robot to complete startup
wait_boot_completed()

# Variables to simulate ERP values
job1 = {
    "id": "JOB0001",
    "name": "Assembly PART1",
    "parts": 25,
    "parts_completed": 0,
    "status": "PENDING"
}

job2 = {
    "id": "JOB0002",
    "name": "Assembly PART0",
    "parts": 12,
    "parts_completed": 0,
    "status": "PENDING"
}

pending_jobs = [job1, job2]
completed_jobs = []


# Remote functions to be called by the robot program
def get_job(job_id):
    for job in pending_jobs:
        if job_id == job["id"]:
            return job
    return None


def get_current_job():
    if len(pending_jobs) > 0:
        return pending_jobs[0]
    return None


def set_completed_parts(job_id, parts_completed):
    job = get_job(job_id)

    if job is not None:
        job["parts_completed"] = parts_completed
        log("Completed part: {}".format(parts_completed))


def complete_job(job_id):
    job = get_job(job_id)

    if job is not None:
        job["parts_completed"] = job["parts"]
        job["status"] = "COMPLETED"

        pending_jobs.remove(job)
        completed_jobs.append(job)

        log("Completed job: {}".format(job_id))


# Set up the XML-RPC server
server = SimpleXMLRPCServer(
    ("0.0.0.0", 8000),
    allow_none=True
)

# Register remote functions
server.register_function(get_job, "get_job")
server.register_function(get_current_job, "get_current_job")
server.register_function(set_completed_parts, "set_completed_parts")
server.register_function(complete_job, "complete_job")

# Start the server
log("XML-RPC server listening on port 8000...")
server.serve_forever()
```

{% endcode %}

{% code title="Robot Program" overflow="wrap" lineNumbers="true" expandable="true" %}

```python
from DRCF import *
import xmlrpc.client

# define the RPC client
background = xmlrpc.client.ServerProxy("http://127.0.0.1:8000/")

# call remote function and retrieve data
job = background.get_current_job()

if job is None:
    tp_log("No pending jobs")
    exit()


tp_log("Working on job: " + job["id"])

count = job["parts_completed"]
while count < job["parts"]:
    count = count + 1

    # use remote function to update date in the background task
    background.set_completed_parts(job["id"], count)
    tp_log("Completed part: " + str(count))


background.complete_job(job["id"])
tp_log("Job completed: " + job["id"])
```

{% endcode %}
{% endtab %}
{% endtabs %}

### **06 Interacting with external services**

**HTTP Client**

The Background Task can be used to call external web services such as REST API.

A valid approach could be:

*Main program — RPC —> Background Task — HTTP client —> REST API* &#x20;

{% tabs %}
{% tab title="Universal Robots" %}
{% code title="Background Task" overflow="wrap" lineNumbers="true" expandable="true" %}

```python
from robpod.background import *
from SimpleXMLRPCServer import SimpleXMLRPCServer
import httplib
import json

# wait for robot to complete startup
wait_boot_completed()


# remote functions to be called by the robot program
def get_job(job_id):
    # use HTTP connection to get data from REST API
    conn = httplib.HTTPConnection("my.warehouse.com")

    headers = {
        "Accept": "application/json"
    }
    conn.request("GET", "/jobs/{}".format(job_id), headers=headers)
    response = conn.getresponse()
    body = response.read()
    conn.close()
    data = json.loads(body)
    return data


def complete_job(job_id):
    job = get_job(job_id)
    job["parts_completed"] = job["parts"]
    job["status"] = "COMPLETED"

    # use HTTP connection to post data to REST API
    conn = httplib.HTTPConnection("my.warehouse.com")

    body = json.dumps(job)

    headers = {
        "Content-Type": "application/json",
        "Accept": "application/json"
    }

    conn.request("POST", "/jobs/{}".format(job_id), body, headers)

    response = conn.getresponse()
    response_body = response.read()
    data = json.loads(response_body)
    conn.close()


# set up the RPC server
server = SimpleXMLRPCServer(("0.0.0.0", 8000), allow_none=True)

# register remote functions
server.register_function(get_job, "get_job")
server.register_function(complete_job, "complete_job")

# start the server
log("XML-RPC server listening on port 8000...")
server.serve_forever()

```

{% endcode %}
{% endtab %}

{% tab title="Doosan Robotics" %}
{% code title="" overflow="wrap" lineNumbers="true" expandable="true" %}

```python

from robpod.background import *
from xmlrpc.server import SimpleXMLRPCServer
import http.client
import json

# Wait for robot to complete startup
wait_boot_completed()


# Remote functions to be called by the robot program
def get_job(job_id):
    # Use HTTP connection to get data from REST API
    conn = http.client.HTTPConnection("my.warehouse.com")

    try:
        headers = {
            "Accept": "application/json"
        }

        conn.request(
            "GET",
            "/jobs/{}".format(job_id),
            headers=headers
        )

        response = conn.getresponse()
        body = response.read()

        if response.status != 200:
            raise Exception(
                "GET /jobs/{} failed: {} {}".format(
                    job_id, response.status, response.reason
                )
            )

        data = json.loads(body.decode("utf-8"))
        return data

    finally:
        conn.close()


def complete_job(job_id):
    job = get_job(job_id)

    job["parts_completed"] = job["parts"]
    job["status"] = "COMPLETED"

    # Use HTTP connection to post updated data to REST API
    conn = http.client.HTTPConnection("my.warehouse.com")

    try:
        body = json.dumps(job).encode("utf-8")

        headers = {
            "Content-Type": "application/json",
            "Accept": "application/json"
        }

        conn.request(
            "POST",
            "/jobs/{}".format(job_id),
            body,
            headers
        )

        response = conn.getresponse()
        response_body = response.read()

        if response.status not in (200, 201, 202, 204):
            raise Exception(
                "POST /jobs/{} failed: {} {}".format(
                    job_id, response.status, response.reason
                )
            )

        # Some APIs return an empty response for successful POST requests
        if response_body:
            return json.loads(response_body.decode("utf-8"))

        return None

    finally:
        conn.close()


# Set up the RPC server
server = SimpleXMLRPCServer(
    ("0.0.0.0", 8000),
    allow_none=True
)

# Register remote functions
server.register_function(get_job, "get_job")
server.register_function(complete_job, "complete_job")

# Start the server
log("XML-RPC server listening on port 8000...")
server.serve_forever()

```

{% endcode %}
{% endtab %}
{% endtabs %}

**HTTP Server**

The Background task can also be used to expose HTTP REST API to external clients.

{% tabs %}
{% tab title="Universal Robots" %}
{% code title="" overflow="wrap" lineNumbers="true" expandable="true" %}

```python
from robpod.background import *
from BaseHTTPServer import HTTPServer, BaseHTTPRequestHandler
import json

# wait for robot to complete startup
wait_boot_completed()


# utility functions
def get_inputs():
    inputs = []
    for i in range(8):
        inputs.append(get_digital_input(i))
    return inputs


def get_outputs():
    outputs = []
    for i in range(8):
        outputs.append(get_digital_output(i))
    return outputs


def set_outputs(outputs):
    for i in range(8):
        set_digital_output(i, outputs[i])

# http server
class JSONHandler(BaseHTTPRequestHandler):

    # GET method to return robot status
    def do_GET(self):
        if self.path == "/api/robot/status":
            data = {
                "robot_status": get_state(),
                "program_status": get_program_state(),
                "program_running": get_program_name(),
                "inputs": get_inputs(),
                "outputs": get_outputs()
            }
            status = 200
        else:
            data = {"error": "Not found"}
            status = 404

        self.send_response(status)
        self.send_header("Content-Type", "application/json")
        self.end_headers()
        self.wfile.write(json.dumps(data))

    # POST method to control robot outputs
    def do_POST(self):

        if self.path == "/api/robot/status/outputs":

            length = int(self.headers.getheader("Content-Length", 0))
            body = self.rfile.read(length)

            try:
                data = json.loads(body)

                set_outputs(data["outputs"])

                response = {
                    "status": "ok"
                }
                status = 200

            except ValueError:
                response = {"error": "Invalid JSON"}
                status = 400
        else:
            response = {
                "status": "ok"
            }
            status = 404

        self.send_response(status)
        self.send_header("Content-Type", "application/json")
        self.end_headers()
        self.wfile.write(json.dumps(response))


# server start
server = HTTPServer(("127.0.0.1", 8000), JSONHandler)
log("Server running at http://127.0.0.1:8000")
server.serve_forever()

```

{% endcode %}
{% endtab %}

{% tab title="Doosan Robotics" %}
{% code title="" overflow="wrap" lineNumbers="true" expandable="true" %}

```python
from robpod.background import *
from http.server import HTTPServer, BaseHTTPRequestHandler
import json

# Wait for robot to complete startup
wait_boot_completed()


# Utility functions
def get_inputs():
    inputs = []
    for i in range(8):
        inputs.append(get_digital_input(i))
    return inputs


def get_outputs():
    outputs = []
    for i in range(8):
        outputs.append(get_digital_output(i))
    return outputs


def set_outputs(outputs):
    for i in range(8):
        set_digital_output(i, outputs[i])


# HTTP server
class JSONHandler(BaseHTTPRequestHandler):

    # Send JSON response
    def send_json(self, status, data):
        response = json.dumps(data).encode("utf-8")

        self.send_response(status)
        self.send_header("Content-Type", "application/json; charset=utf-8")
        self.send_header("Content-Length", str(len(response)))
        self.end_headers()
        self.wfile.write(response)

    # GET method to return robot status
    def do_GET(self):
        if self.path == "/api/robot/status":
            data = {
                "robot_status": get_state(),
                "program_status": get_program_state(),
                "program_running": get_program_name(),
                "inputs": get_inputs(),
                "outputs": get_outputs()
            }
            self.send_json(200, data)
        else:
            self.send_json(404, {"error": "Not found"})

    # POST method to control robot outputs
    def do_POST(self):
        if self.path != "/api/robot/status/outputs":
            self.send_json(404, {"error": "Not found"})
            return

        try:
            length = int(self.headers.get("Content-Length", "0"))
            body = self.rfile.read(length).decode("utf-8")
            data = json.loads(body)

            outputs = data["outputs"]

            # Validate output array
            if not isinstance(outputs, list) or len(outputs) != 8:
                self.send_json(
                    400,
                    {"error": "outputs must be a list of 8 values"}
                )
                return

            set_outputs(outputs)

            self.send_json(200, {"status": "ok"})

        except (ValueError, KeyError, TypeError):
            self.send_json(400, {"error": "Invalid JSON or output data"})


# Server start
server = HTTPServer(("127.0.0.1", 8000), JSONHandler)
log("Server running at http://127.0.0.1:8000")
server.serve_forever()

```

{% endcode %}
{% endtab %}
{% endtabs %}

**Sending E-mails**

The background task can also be used for senging emails with SMTP protocol.

{% tabs %}
{% tab title="Universal Robots" %}
{% code title="" overflow="wrap" lineNumbers="true" expandable="true" %}

```python
from robpod.background import *
import smtplib
from email.mime.text import MIMEText
from SimpleXMLRPCServer import SimpleXMLRPCServer


# wait for robot to complete startup
wait_boot_completed()


# remote functions to be called by the robot program
def send_email(destination):
    sender = "info@my.domain"
    receiver = destination
    password = "mypassword"

    msg = MIMEText("Hello! This email is sent from UR robot. Current Job completed!")
    msg["Subject"] = "Robot Email"
    msg["From"] = sender
    msg["To"] = receiver

    email = smtplib.SMTP("smtp.my.domain", 587, timeout=30)
    email.ehlo()
    email.starttls()
    email.login(sender, password)
    email.sendmail(sender, [receiver], msg.as_string())
    email.quit()

    log("Email sent successfully!")


# set up the RPC server
server = SimpleXMLRPCServer(("0.0.0.0", 8000), allow_none=True)

# register remote functions
server.register_function(send_email, "send_email")

# start the server
log("XML-RPC server listening on port 8000...")
server.serve_forever()

```

{% endcode %}
{% endtab %}

{% tab title="Doosan Robotics" %}
{% code title="" overflow="wrap" lineNumbers="true" expandable="true" %}

```python
from robpod.background import *
import smtplib
from email.mime.text import MIMEText
from SimpleXMLRPCServer import SimpleXMLRPCServer


# wait for robot to complete startup
wait_boot_completed()


# remote functions to be called by the robot program
def send_email(destination):
    sender = "info@my.domain"
    receiver = destination
    password = "mypassword"

    msg = MIMEText("Hello! This email is sent from Doosan robot. Current Job completed!")
    msg["Subject"] = "Robot Email"
    msg["From"] = sender
    msg["To"] = receiver

    email = smtplib.SMTP("smtp.my.domain", 587, timeout=30)
    email.ehlo()
    email.starttls()
    email.login(sender, password)
    email.sendmail(sender, [receiver], msg.as_string())
    email.quit()

    log("Email sent successfully!")


# set up the RPC server
server = SimpleXMLRPCServer(("0.0.0.0", 8000), allow_none=True)

# register remote functions
server.register_function(send_email, "send_email")

# start the server
log("XML-RPC server listening on port 8000...")
server.serve_forever()
```

{% endcode %}
{% endtab %}
{% endtabs %}
