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