Skip to content

Commit 0a51100

Browse files
authored
Merge pull request #15 from AmedeeBulle/mdt
Add Python SDK and Data API sample
2 parents 5dac523 + 13d0e71 commit 0a51100

File tree

16 files changed

+1798
-7
lines changed

16 files changed

+1798
-7
lines changed

.gitignore

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,8 @@
55
# Config files
66
environ.sh
77
config.py
8+
iot_config.yaml
9+
data_access.json
810

911
# Certificates
1012
*.pem

README.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -52,7 +52,7 @@ as well as the messages received.
5252
| Description | Command Line | Python |
5353
|------------------------------------------------------|:---------------------:|:------------:|
5454
| IoT from scratch (Setup IoT environment from command line) | [Sample](./samples/script/iot-from-scratch/) | |
55-
| Manage Digital Twins (Create, delete, ...) | [Sample](./samples/script/manage-dt/) | |
55+
| Manage Digital Twins (Create, query, delete). The Python sample demonstrates the use of the OCI Python SDK and the IoT Platform Data API | [Sample](./samples/script/manage-dt/) | [Sample](./samples/python/manage-dt/) |
5656
| Publish telemetry (HTTPS - REST API) | [Sample](./samples/script/publish-https/) | [Sample](./samples/python/publish-https/) |
5757
| Publish telemetry (MQTTS - Secure MQTT) | | [Sample](./samples/python/publish-mqtt/) |
5858
| Publish telemetry (WSS - Secure MQTT over WebSocket) | | [Sample](./samples/python/publish-websockets/) |

samples/python/manage-dt/README.md

Lines changed: 263 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,263 @@
1+
# OCI IoT Platform Device Workflow Demo Python script
2+
3+
Sample Python application to create, query, and delete devices to illustrate the use of the
4+
[OCI Python SDK](https://docs.oracle.com/en-us/iaas/tools/python/latest/), in particular
5+
the bindings for the [OCI IoT Platform](https://docs.oracle.com/en-us/iaas/tools/python/latest/api/iot.html)
6+
as well as the [ORDS API](https://docs.oracle.com/iaas/tools/internet-of-things/data-api/index.html)
7+
for data access.
8+
9+
The `manage-dt` package consists of several source files and can be installed with `pip`
10+
(see below). If you are only interested in looking at the sample code:
11+
12+
- [mdt_iot_oci.py](./manage_dt/mdt_iot_oci.py) contains the calls to the OCI Python SDK
13+
- [mdt_iot_data.py](./manage_dt/mdt_iot_data.py) focuses on the ORDS data API
14+
15+
## Prerequisites
16+
17+
The application assumes that you have already set up an OCI IoT Platform environment in
18+
your OCI tenancy:
19+
20+
- an OCI IoT Domain Group and Domain
21+
- OCI Vault secrets and/or OCI Certificates for your device credentials
22+
- a Confidential Application to issue tokens for querying telemetry via ORDS. This step is
23+
only required for the `query` command of `manage-dt`.
24+
25+
## Installation
26+
27+
To install and run the `manage-dt` application, you need **Python 3.12 or higher**.
28+
29+
We recommend installing in an isolated virtual environment:
30+
31+
```sh
32+
# Ensure Python 3.12+ is available
33+
python3 --version
34+
35+
# Create and activate a new virtual environment
36+
python3 -m venv venv
37+
source venv/bin/activate
38+
39+
# Upgrade pip (optional but recommended)
40+
python -m pip install --upgrade pip
41+
42+
# Install the application and dependencies
43+
pip install .
44+
```
45+
46+
You can now use the console command:
47+
48+
```sh
49+
manage-dt --help
50+
```
51+
52+
## Configuration file
53+
54+
The application gets its configuration from the `iot_config.yaml` file in the `data`
55+
directory. Copy the `iot_config.distr.yaml` template to `iot_config.yaml` and enter the
56+
required data.
57+
58+
### `iot` section
59+
60+
This section is for the IoT Platform parameters:
61+
62+
- `domain_id`: the OCID of your IoT Domain
63+
64+
### `identity` section
65+
66+
This section contains the OCI Identity parameters and relates to the OAuth configuration
67+
needed to access the ORDS API.
68+
69+
This section is needed for the `query` command of the application.
70+
71+
- `app_client_id` and `app_client_secret`: the Confidential Application Client ID and
72+
Secret. You can find these in the OCI Console: `Identity > Domain > Your Domain >
73+
Integrated App > OAuth`
74+
- `user` and `password`: the OCI User and Password in the Identity Domain where the
75+
Confidential Application is created. Typically, this will be your OCI username and
76+
password, unless the application is created in a separate Identity Domain.
77+
78+
### `environ` section
79+
80+
Define here environment variables which can be referenced in the `digital_twins`
81+
section. The sample data defines a `device_key` variable, used to name your digital
82+
twins and avoid duplicate names if different users run the same sample application.
83+
It also defines `auth_id`, the OCID of a Vault Secret containing the password for the device.
84+
85+
### `digital_twins` section
86+
87+
Describes the Digital Twins that can be managed by the application. The sample data
88+
provides 3 different digital twins:
89+
90+
- `unstructured`: a Digital Twin with unstructured telemetry
91+
- `default-adapter`: a Digital Twin with structured telemetry using the default Adapter
92+
- `custom-adapter`: a Digital Twin with structured telemetry with a custom Adapter
93+
94+
You can use these definitions as-is, customize them, or create your own Digital Twin
95+
definitions.
96+
97+
For each Digital Twin, you must have:
98+
99+
- `device_name`: the name of the device
100+
- `external_key`: the username the device will use to authenticate
101+
- `auth_id`: the OCID of a Vault Secret containing the password for the device
102+
(Basic authentication) or the OCID of a Certificate for the device (Certificate
103+
authentication)
104+
105+
These parameters are sufficient for a Digital Twin with unstructured telemetry.
106+
107+
Additionally, for a Digital Twin with structured telemetry using the default Adapter, you
108+
also need to provide a model:
109+
110+
- `model_name`: the Digital Twin Model Identifier (DTMI) for your model
111+
- `model_description`: a plain text description of the model
112+
- `model_dtdl`: the name of a file containing the model definition (relative to the data
113+
directory)
114+
- `adapter_name`: name for the adapter that will be generated
115+
116+
Finally, for a Digital Twin with structured telemetry with a custom Adapter you will need
117+
to provide the Adapter definition:
118+
119+
- `adapter_name`: the name of the Adapter
120+
- `adapter_envelope`: the name of a file containing the inbound envelope definition
121+
(relative to the data directory)
122+
- `adapter_routes`: the name of a file containing the inbound routes definition (relative
123+
to the data directory)
124+
125+
The sample definitions in this repository are for an Environmental Sensor reporting two
126+
temperatures in degrees Celsius, pressure in hectoPascals, and relative humidity in
127+
percent.
128+
129+
Note that while the OCI CLI uses hyphen-separated words for the Adapter definitions, the
130+
API uses camelCase notation. For example, `envelope-mapping` for the OCI CLI becomes
131+
`envelopeMapping` when using the Python SDK or the API.
132+
133+
**Note:** The structured telemetry defined in this script is based on the payload sent by
134+
an [M5Stack CoreS3](https://docs.m5stack.com/en/core/CoreS3) device with an
135+
[Env-III](https://docs.m5stack.com/en/unit/envIII) environmental sensor.
136+
The expected payload is:
137+
138+
```json
139+
{
140+
"sht_temperature": 23.8,
141+
"qmp_temperature": 24.4,
142+
"humidity": 56.1,
143+
"pressure": 1012.2,
144+
"count": 5479
145+
}
146+
```
147+
148+
This payload can be emulated by the various sample scripts in this repository.
149+
150+
## Usage
151+
152+
```text
153+
$ Usage: manage-dt [OPTIONS] COMMAND [ARGS]...
154+
155+
Manage Digital Twins.
156+
157+
Sample application to create/query/delete Digital Twins for the OCI Iot
158+
Platform.
159+
160+
Options:
161+
-v, --verbose Verbose mode
162+
-d, --debug Debug mode
163+
--profile TEXT The profile in the config file to load.
164+
[default: wim-iot-fra]
165+
--auth [api_key|instance_principal|resource_principal]
166+
The type of auth to use for the API request.
167+
[default: api_key]
168+
--data-dir DIRECTORY Data directory [default: ./data]
169+
--iot-config-file FILE The path to the IoT config file. [default:
170+
iot_config.yaml]
171+
--version Show the version and exit.
172+
--help Show this message and exit.
173+
174+
Commands:
175+
create Create a new Digital Twin.
176+
delete Delete a Digital Twin.
177+
query Query recent data for a Digital Twin.
178+
```
179+
180+
The `verbose`/`debug` options control the verbosity of the tool.
181+
182+
The `profile` and `auth` options are similar to those used with the OCI CLI. Similarly,
183+
they can be set with environment variables: `OCI_CLI_PROFILE` and `OCI_CLI_AUTH`
184+
respectively.
185+
186+
`data-dir` allows you to specify the location of the data directory, and `iot-config-file`
187+
can be used to specify an alternative configuration file.
188+
189+
The script supports three basic commands:
190+
191+
- `create`: creates the Digital Twin (with the Model and Adapter if applicable)
192+
- `query`: queries the content of the Digital Twin (structured telemetry) as well as the
193+
Raw Data, Historized Data, and Rejected Data using the ORDS Data API. By default, data
194+
for the last 5 minutes is retrieved. You can use the `last-minutes` parameter to
195+
specify a different duration.
196+
- `delete`: deletes the Digital Twin (with the Model and Adapter if applicable)
197+
198+
## Sample session
199+
200+
```text
201+
$ manage-dt --debug create custom-adapter
202+
2025-09-18 20:30:59,503 - DEBUG - cli.py - Loading configuration
203+
2025-09-18 20:30:59,505 - DEBUG - mdt_oci.py - OCI authentication: Config file
204+
2025-09-18 20:30:59,506 - DEBUG - config.py - Config file found at ~/.oci/config
205+
2025-09-18 20:30:59,575 - INFO - mdt_iot_oci.py - Create Digital Twin Model 'dtmi:com:oracle:pvhd:m5:env:cstm;1'
206+
2025-09-18 20:30:59,795 - INFO - mdt_iot_oci.py - Digital Twin Model created
207+
2025-09-18 20:30:59,798 - INFO - mdt_iot_oci.py - Create custom Digital Twin Adapter 'pvhd-m5-env-cstm-adapter'
208+
2025-09-18 20:31:01,116 - INFO - mdt_iot_oci.py - Digital Twin Adapter created
209+
2025-09-18 20:31:01,116 - INFO - mdt_iot_oci.py - Create Digital Twin Instance pvhd-m5-env-cstm-01 - Structured Telemetry with custom Adapter
210+
2025-09-18 20:31:02,947 - INFO - mdt_iot_oci.py - Digital Twin Instance created
211+
$ # Send some data and query:
212+
$ manage-dt --debug query custom-adapter
213+
2025-09-18 20:32:39,566 - DEBUG - cli.py - Loading configuration
214+
2025-09-18 20:32:39,569 - DEBUG - mdt_oci.py - OCI authentication: Config file
215+
2025-09-18 20:32:39,569 - DEBUG - config.py - Config file found at ~/.oci/config
216+
2025-09-18 20:32:40,356 - INFO - mdt_iot_oci.py - Digital Twin Instance retrieved
217+
2025-09-18 20:32:40,357 - INFO - mdt_iot_oci.py - Query Digital Twin Instance 'custom-adapter'
218+
2025-09-18 20:32:40,556 - INFO - mdt_iot_oci.py - Digital Twin Instance content retrieved
219+
╭────────────────────────────────────────── Digital Twin Instance content ────────────────────────────╮
220+
│ {'humidity': 73, 'count': 2, 'pressure': 1023, 'sht_temperature': 22.3, 'qmp_temperature': 22.1} │
221+
╰─────────────────────────────────────────────────────────────────────────────────────────────────────╯
222+
2025-09-18 20:32:40,573 - DEBUG - mdt_iot_data.py - Using cached data access configuration and token
223+
Recent raw data - 2 record(s)
224+
┏━━━━━┳━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┳━━━━━━━━━━┓
225+
┃ Id ┃ Time received (UTC) ┃ Endpoint ┃
226+
┡━━━━━╇━━━━━━━━━━━━━━━━━━━━━━━━━━━━━╇━━━━━━━━━━┩
227+
│ 535 │ 2025-09-18T18:32:28.847244Z │ iot/pvhd │
228+
│ 536 │ 2025-09-18T18:32:34.621067Z │ iot/pvhd │
229+
└─────┴─────────────────────────────┴──────────┘
230+
Recent historized data - 4 record(s)
231+
┏━━━━━━┳━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┳━━━━━━━━━━━━━━━━━┳━━━━━━━┓
232+
┃ Id ┃ Time observed (UTC) ┃ Content path ┃ Value ┃
233+
┡━━━━━━╇━━━━━━━━━━━━━━━━━━━━━━━━━━━━━╇━━━━━━━━━━━━━━━━━╇━━━━━━━┩
234+
│ 1852 │ 2025-09-18T18:32:28.847244Z │ sht_temperature │ 22.3 │
235+
│ 1853 │ 2025-09-18T18:32:28.847244Z │ humidity │ 73 │
236+
│ 1854 │ 2025-09-18T18:32:28.847244Z │ pressure │ 1023 │
237+
│ 1855 │ 2025-09-18T18:32:28.847244Z │ qmp_temperature │ 22.1 │
238+
└──────┴─────────────────────────────┴─────────────────┴───────┘
239+
Recent rejected data - 1 record(s)
240+
┏━━━━┳━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┳━━━━━━━━━━┳━━━━━━━━━━━━━┳━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┓
241+
┃ Id ┃ Time received (UTC) ┃ Endpoint ┃ Reason code ┃ Reason message ┃
242+
┡━━━━╇━━━━━━━━━━━━━━━━━━━━━━━━━━━━━╇━━━━━━━━━━╇━━━━━━━━━━━━━╇━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┩
243+
│ 48 │ 2025-09-18T18:32:34.621067Z │ iot/pvhd │ 500 │ Normalization output is empty │
244+
└────┴─────────────────────────────┴──────────┴─────────────┴───────────────────────────────┘
245+
$ manage-dt --debug delete custom-adapter
246+
2025-09-18 20:33:33,603 - DEBUG - cli.py - Loading configuration
247+
2025-09-18 20:33:33,605 - DEBUG - mdt_oci.py - OCI authentication: Config file
248+
2025-09-18 20:33:33,605 - DEBUG - config.py - Config file found at ~/.oci/config
249+
2025-09-18 20:33:33,673 - INFO - mdt_iot_oci.py - Delete Digital Twin Instance 'custom-adapter'
250+
2025-09-18 20:33:34,514 - INFO - mdt_iot_oci.py - Digital Twin Instance deleted
251+
2025-09-18 20:33:34,514 - INFO - mdt_iot_oci.py - Delete Digital Twin Adapter 'pvhd-m5-env-cstm-adapter'
252+
2025-09-18 20:33:35,434 - INFO - mdt_iot_oci.py - Digital Twin Adapter deleted
253+
2025-09-18 20:33:35,434 - INFO - mdt_iot_oci.py - Delete Digital Twin Model 'dtmi:com:oracle:pvhd:m5:env:cstm;1'
254+
2025-09-18 20:33:36,204 - INFO - mdt_iot_oci.py - Digital Twin Model deleted
255+
$
256+
```
257+
258+
## Caveat
259+
260+
As we do not store OCIDs of the IoT objects created, identification is done based on
261+
_Display Name_. However, as with most OCI resources, the _Display Name_ is not a unique
262+
identifier! While it is not a recommended practice, it is possible to have two Digital
263+
Twins with the same name. This sample application might not handle such cases properly.
Lines changed: 63 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,63 @@
1+
# Manage-dt: Configuration file.
2+
#
3+
# Copyright (c) 2025 Oracle and/or its affiliates.
4+
# Licensed under the Universal Permissive License v 1.0 as shown at
5+
# https://oss.oracle.com/licenses/upl
6+
#
7+
# DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER.
8+
9+
iot:
10+
# IoT Domain OCID
11+
domain_id: <IoT Domain OCID>
12+
13+
identity:
14+
# Confidential Application Client ID and Secret
15+
# You can find these in the OCI Console:
16+
# Identity > Domain > Your Domain > Integrated App > OAuth
17+
app_client_id: <Application Client Id>
18+
app_client_secret: <Application Client Secret>
19+
20+
# The OCI user and password in the Identity Domain where the Confidential
21+
# Application is created. (Typically, this will be your OCI username and password,
22+
# unless the application is created in a separate Identity domain.)
23+
user: <OCI User>
24+
password: <OCI Password>
25+
26+
# Declare "environment variables" which can be used for the digital twins definitions below
27+
environ:
28+
# A short ID used to uniquely identify the resources created
29+
device_key: <My Id>
30+
# The OCID of a Vault Secret which can be used for all Digital Twins
31+
auth_id: <Secret OCID>
32+
33+
digital_twins:
34+
unstructured:
35+
# Unstructured Digital Twin
36+
# You can send any telemetry to this Digital Twin.
37+
device_name: ${device_key}-01
38+
external_key: ${device_key}-01
39+
auth_id: ${auth_id}
40+
41+
default-adapter:
42+
# M5 Stack with EnvIII sensor - Structured telemetry with Adapter in default format
43+
# Payload should match the expected telemetry format as described by the model.
44+
device_name: ${device_key}-m5-env-dflt-01
45+
model_name: dtmi:com:oracle:${device_key}:m5:env:dflt;1
46+
model_description: M5 with EnvIII sensors (${device_key}-default)
47+
model_dtdl: m5_model_default.json
48+
adapter_name: ${device_key}-m5-env-dflt-adapter
49+
external_key: ${device_key}-m5-env-dflt-01
50+
auth_id: ${auth_id}
51+
52+
custom-adapter:
53+
# M5 Stack with EnvIII sensor - Structured telemetry with Adapter in custom format
54+
# Payload should match the expected telemetry format as described by the adapter.
55+
device_name: ${device_key}-m5-env-cstm-01
56+
model_name: dtmi:com:oracle:${device_key}:m5:env:cstm;1
57+
model_description: M5 with EnvIII sensors (${device_key}-custom)
58+
model_dtdl: m5_model_custom.json
59+
adapter_name: ${device_key}-m5-env-cstm-adapter
60+
adapter_envelope: m5_adapter_envelope.json
61+
adapter_routes: m5_adapter_routes.json
62+
external_key: ${device_key}-m5-env-cstm-01
63+
auth_id: ${auth_id}
Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,17 @@
1+
{
2+
"envelopeMapping": {
3+
"timeObserved": "$.time"
4+
},
5+
"referenceEndpoint": "iot/environ",
6+
"referencePayload": {
7+
"data": {
8+
"count": 0,
9+
"humidity": 0.0,
10+
"pressure": 0.0,
11+
"qmp_temperature": 0.0,
12+
"sht_temperature": 0.0,
13+
"time": 0
14+
},
15+
"dataFormat": "JSON"
16+
}
17+
}
Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,20 @@
1+
[
2+
{
3+
"condition": "${endpoint(1) == \"iot\"}",
4+
"description": "Environment data",
5+
"payloadMapping": {
6+
"$.count": "$.count",
7+
"$.humidity": "$.humidity",
8+
"$.pressure": "$.pressure",
9+
"$.qmp_temperature": "$.qmp_temperature",
10+
"$.sht_temperature": "$.sht_temperature"
11+
}
12+
},
13+
{
14+
"condition": "*",
15+
"description": "Default condition",
16+
"payloadMapping": {
17+
"$.system": "$"
18+
}
19+
}
20+
]

0 commit comments

Comments
 (0)