Custom Python code must be implemented inside the simulation.py file in the root of the simulation directory and user_app object must be properly configured in the simulation.json file, see 2. Define simulation parameters.

Custom code is required if any profile in the simulation uses:

You can skip this step if your simulation does not implement device requests nor data streams values generated by code.

This example uses both, so a simulation.py file is required to properly execute the simulation.

Follow these steps to implement the required code:

For more information see Custom code implementation.

To know how to launch a simulation go to Launch a simulation.

Here you have the complete Python code in the simulation.py file:

import logging

STATUS_OK = 0
STATUS_ERROR = 1

logger = logging.getLogger("user_app")


current_on_status = {}
last_on_status = {}


def process_request(device_id, profile_id, target, request_data):
    """
    Process the provided request.

    Args:
        device_id (String): Device identifier.
        profile_id (String): Identifier of the profile the device is using.
        target (String): Request target.
        request_data (String): Request data.

    Returns:
        Tuple: Tuple with response to send to Remote Manager:
                - status (Integer): Status value after processing the request included in
                                    the 'status' attribute of the response.
                                    Usually 0 for success, any other value is an error code.
                                    Not implemented yet, reserved for future use.
                - data (String): Response data for the request included as text content.
                                 `None` for empty response.

                Returns `(None, None)` if not supported target.
    """
    if target == "set_status":
        if request_data.lower() == "on":
            new_status = True
        elif request_data.lower() == "off":
            new_status = False
        else:
            return STATUS_ERROR, "[%s:%s] Invalid status '%s'" % (device_id, profile_id, request_data)

        current = _get_stored_status(device_id, current_on_status)
        if current == new_status:
            return STATUS_OK, "[%s:%s] Light already '%s'" % (device_id, profile_id, request_data)

        # Save current and last status value, to only upload changes.
        last_on_status.update({device_id: current})
        current_on_status.update({device_id: new_status})
        return STATUS_OK, "[%s:%s] Light '%s'" % (device_id, profile_id, request_data)

    logger.error("Not supported request '%s'", target)
    return None, None

def generate_data_point(device_id, _profile_id, stream_name, _ptype, _last_value, tick):
    """
    Generates a data point value.

    Args:
        device_id (String): Device identifier.
        _profile_id (String): Identifier of the profile the device is using.
        stream_name (String): Name of the stream without the device ID.
        _ptype (Class): Python type of the value.
        _last_value (?): Last generated value. Its type must be `_ptype`.
        tick (Integer): Number of times this method has been called.

    Returns:
        Tuple: Tuple with data point information:
                - upload_now (Boolean): `True` to upload now, `False` otherwise.
                - new_value (?): The new generated value.

               Returns `(None, None)` if not supported stream.
    """
    if stream_name == "on_status":
        current = _get_stored_status(device_id, current_on_status)
        last = _get_stored_status(device_id, last_on_status)

        # Upload immediately the first value and a status change
        if tick == 0 or last != current:
            last_on_status.update({device_id: current})
            return True, 1 if current else 0
        # Do not upload anything if there is no change
        return False, None

    logger.error("Not supported stream '%s'", stream_name)
    return None, None


def _get_stored_status(device_id, status):
    """
    Get stored status for the provided device id in the given status dictionary.

    Args:
        device_id (String): Device identifier.
        status (Dict): Dictionary with stored status per device id.

    Returns:
        Boolean: The stored value, `False` if it is not stored.
    """
    current = status.get(device_id, None)
    if current is None:
        current = False
        status.update({device_id: current})

    return current

1. Process received requests from Remote Manager

When the IoT Device Simulator receives a supported request for a simulated device the method process_requests() of the simulation.py file is called with the received information.

For the example, you must handle the set_status target to manage the new status, see 5. Configure supported remote requests.

import logging

STATUS_OK = 0
STATUS_ERROR = 1

logger = logging.getLogger("user_app")

current_on_status = {}
last_on_status = {}


def process_request(device_id, profile_id, target, request_data):
    """
    Process the provided request.

    Args:
        device_id (String): Device identifier.
        profile_id (String): Identifier of the profile the device is using.
        target (String): Request target.
        request_data (String): Request data.

    Returns:
        Tuple: Tuple with response to send to Remote Manager:
                - status (Integer): Status value after processing the request included in
                                    the 'status' attribute of the response.
                                    Usually 0 for success, any other value is an error code.
                                    Not implemented yet, reserved for future use.
                - data (String): Response data for the request included as text content.
                                 `None` for empty response.

                Returns `(None, None)` if not supported target.
    """
    if target == "set_status":
        if request_data.lower() == "on":
            new_status = True
        elif request_data.lower() == "off":
            new_status = False
        else:
            return STATUS_ERROR, "[%s:%s] Invalid status '%s'" % (device_id, profile_id, request_data)

        current = _get_stored_status(device_id, current_on_status)
        if current == new_status:
            return STATUS_OK, "[%s:%s] Light already '%s'" % (device_id, profile_id, request_data)

        # Save current and last status value, to only upload changes.
        last_on_status.update({device_id: current})
        current_on_status.update({device_id: new_status})
        return STATUS_OK, "[%s:%s] Light '%s'" % (device_id, profile_id, request_data)

    logger.error("Not supported request '%s'", target)
    return None, None

2. Generate required data samples

For a data stream with data source mode of command, the method generate_data_point() of the simulation.py file is called with the required information to get a new sample every configured sampling_rate.

For the example, generate_data_point() method must provide a value for on_status data stream, see 4. Specify data to upload, if the current status changes.

def generate_data_point(device_id, _profile_id, stream_name, _ptype, _last_value, tick):
    """
    Generates a data point value.

    Args:
        device_id (String): Device identifier.
        _profile_id (String): Identifier of the profile the device is using.
        stream_name (String): Name of the stream without the device ID.
        _ptype (Class): Python type of the value.
        _last_value (?): Last generated value. Its type must be `_ptype`.
        tick (Integer): Number of times this method has been called.

    Returns:
        Tuple: Tuple with data point information:
                - upload_now (Boolean): `True` to upload now, `False` otherwise.
                - new_value (?): The new generated value.

               Returns `(None, None)` if not supported stream.
    """
    if stream_name == "on_status":
        current = _get_stored_status(device_id, current_on_status)
        last = _get_stored_status(device_id, last_on_status)

        # Upload immediately the first value and a status change
        if tick == 0 or last != current:
            last_on_status.update({device_id: current})
            return True, 1 if current else 0
        # Do not upload anything if there is no change
        return False, None

    logger.error("Not supported stream '%s'", stream_name)
    return None, None