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:
-
Device requests. See Digi IoT Device Simulator User Guide and Device request configuration.
-
Or data streams with samples generated by custom code, see Data streams structure.
| 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