Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 3 additions & 0 deletions HISTORY.rst
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,9 @@ Release History
**IoT Central updates**

* az iot central device|device-template|api-token|diagnostic help strings updated with improved language.
* update parsing template logic to support DTDLV2 models.
Comment thread
valluriraj marked this conversation as resolved.
* remove deprecated commands 1) iot central app device-twin 2) iot central app monitor-events


**IoT Hub updates**

Expand Down
64 changes: 1 addition & 63 deletions azext_iot/central/_help.py
Original file line number Diff line number Diff line change
Expand Up @@ -39,8 +39,6 @@ def load_central_help():
_load_central_monitors_help()
_load_central_command_help()
_load_central_compute_device_key()
# TODO: Delete this by end of Dec 2020
_load_central_deprecated_commands()
Comment thread
valluriraj marked this conversation as resolved.


def _load_central_devices_help():
Expand Down Expand Up @@ -528,23 +526,6 @@ def _load_central_monitors_help():
az iot central diagnostics registration-summary --app-id {appid}
"""


# TODO: Delete this by end of Dec 2020
def _load_central_deprecated_commands():
helps[
"iot central app device-twin"
] = """
type: group
short-summary: Manage IoT Central device twins.
"""

helps[
"iot central app device-twin show"
] = """
type: command
short-summary: Get the device twin from IoT Hub.
"""

helps[
"iot central device twin"
] = """
Expand All @@ -556,48 +537,5 @@ def _load_central_deprecated_commands():
"iot central device twin show"
] = """
type: command
short-summary: Get the device twin from IoT Central application.
long-summary: Returns back the desired and reported device properties from the IoT Central application.
"""

helps[
"iot central app monitor-events"
] = """
type: command
short-summary: Monitor device telemetry & messages sent to the IoT Hub for an IoT Central app.
long-summary: |
EXPERIMENTAL requires Python 3.5+
This command relies on and may install dependent Cython package (uamqp) upon first execution.
https://github.com/Azure/azure-uamqp-python
examples:
- name: Basic usage
text: >
az iot central app monitor-events --app-id {app_id}
- name: Basic usage when filtering on target device
text: >
az iot central app monitor-events --app-id {app_id} -d {device_id}
- name: Basic usage when filtering targeted devices with a wildcard in the ID
text: >
az iot central app monitor-events --app-id {app_id} -d Device*d
- name: Basic usage when filtering on module.
text: >
az iot central app monitor-events --app-id {app_id} -m {module_id}
- name: Basic usage when filtering targeted modules with a wildcard in the ID
text: >
az iot central app monitor-events --app-id {app_id} -m Module*
- name: Filter device and specify an Event Hub consumer group to bind to.
text: >
az iot central app monitor-events --app-id {app_id} -d {device_id} --cg {consumer_group_name}
- name: Receive message annotations (message headers)
text: >
az iot central app monitor-events --app-id {app_id} -d {device_id} --properties anno
- name: Receive message annotations + system properties. Never time out.
text: >
az iot central app monitor-events --app-id {app_id} -d {device_id} --properties anno sys --timeout 0
- name: Receive all message attributes from all device messages
text: >
az iot central app monitor-events --app-id {app_id} --props all
- name: Receive all messages and parse message payload as JSON
text: >
az iot central app monitor-events --app-id {app_id} --output json
short-summary: Get the device twin from IoT Hub.
"""
25 changes: 0 additions & 25 deletions azext_iot/central/command_map.py
Original file line number Diff line number Diff line change
Expand Up @@ -109,28 +109,3 @@ def load_central_commands(self, _):
cmd_group.show_command(
"show", "device_twin_show",
)

# TODO: Delete this by end of Dec 2020
_load_central_deprecated_commands(self, _)


def _load_central_deprecated_commands(self, _):
with self.command_group(
"iot central app device-twin",
command_type=central_device_twin_ops,
deprecate_info=self.deprecate(redirect="iot central device twin"),
) as cmd_group:
cmd_group.show_command(
"show", "device_twin_show",
)

with self.command_group(
"iot central app", command_type=central_monitor_ops,
) as cmd_group:
cmd_group.command(
"monitor-events",
"monitor_events",
deprecate_info=self.deprecate(
redirect="iot central diagnostics monitor-events"
),
)
79 changes: 64 additions & 15 deletions azext_iot/central/models/template.py
Original file line number Diff line number Diff line change
Expand Up @@ -15,30 +15,79 @@ def __init__(self, template: dict):
self.name = template.get("displayName")
self.interfaces = self._extract_interfaces(template)
self.schema_names = self._extract_schema_names(self.interfaces)
self.components = self._extract_components(template)
Comment thread
valluriraj marked this conversation as resolved.
if self.components:
self.component_schema_names = self._extract_schema_names(
self.components
)

except:
raise CLIError("Could not parse iot central device template.")

def get_schema(self, name, interface_name=""):
# interface_name has been specified, do a pointed lookup
if interface_name:
interface = self.interfaces.get(interface_name, {})
return interface.get(name)
def get_schema(self, name, is_component=False, identifier="") -> dict:
entities = self.components if is_component else self.interfaces
if identifier:
# identifier specified, do a pointed lookup
entry = entities.get(identifier, {})
return entry.get(name)

# find first matching name in any interface
for interface in self.interfaces.values():
schema = interface.get(name)
# find first matching name in any component
for entry in entities.values():
schema = entry.get(name)
if schema:
return schema

# not found
return None

def _extract_components(self, template: dict) -> dict:
try:
dcm = template.get("capabilityModel", {})
if dcm.get("contents"):
Comment thread
valluriraj marked this conversation as resolved.
rootContents = dcm.get("contents", {})
components = [
entity
for entity in rootContents
if entity.get("@type") == "Component"
]

if components:
return {
component["name"]: self._extract_schemas(component)
for component in components
}
return {}
return {}
except Exception:
details = "Unable to extract schema for component from template '{}'.".format(
self.id
)
raise CLIError(details)

def _extract_root_interface_contents(self, dcm: dict):
rootContents = dcm.get("contents", {})
Comment thread
This conversation was marked as resolved.
contents = [
entity for entity in rootContents if entity.get("@type") != "Component"
]

return {"@id": dcm.get("@id", {}), "schema": {"contents": contents}}

def _extract_interfaces(self, template: dict) -> dict:
try:

interfaces = []
dcm = template.get("capabilityModel", {})
Comment thread
valluriraj marked this conversation as resolved.
interfaces = dcm.get("implements", {})

if dcm.get("contents"):
interfaces.append(self._extract_root_interface_contents(dcm))

if dcm.get("@type") == "CapabilityModel":
interfaces.extend(dcm.get("implements"))
else:
interfaces.extend(dcm.get("extends"))

return {
interface["name"]: self._extract_schemas(interface)
interface["@id"]: self._extract_schemas(interface)
for interface in interfaces
}
except Exception:
Expand All @@ -47,13 +96,13 @@ def _extract_interfaces(self, template: dict) -> dict:
)
raise CLIError(details)

def _extract_schemas(self, interface: dict) -> dict:
return {schema["name"]: schema for schema in interface["schema"]["contents"]}
def _extract_schemas(self, entity: dict) -> dict:
return {schema["name"]: schema for schema in entity["schema"]["contents"]}

def _extract_schema_names(self, interfaces: dict) -> dict:
def _extract_schema_names(self, entity: dict) -> dict:
return {
interface_name: list(interface_schemas.keys())
for interface_name, interface_schemas in interfaces.items()
entity_name: list(entity_schemas.keys())
for entity_name, entity_schemas in entity.items()
}

def _get_interface_list_property(self, property_name):
Expand Down
12 changes: 0 additions & 12 deletions azext_iot/central/params.py
Original file line number Diff line number Diff line change
Expand Up @@ -196,15 +196,3 @@ def load_central_arguments(self, _):
context.argument(
"module_id", options_list=["--module-id", "-m"], help="Provide IoT Edge Module ID if the device type is IoT Edge.",
)
# TODO: Delete this by end of Dec 2020
load_deprecated_params(self, _)


def load_deprecated_params(self, _):
with self.argument_context("iot central app monitor-events") as context:
context.argument("timeout", arg_type=event_timeout_type)
context.argument("properties", arg_type=event_msg_prop_type)
context.argument(
"module_id", options_list=["--module-id", "-m"], help="Iot Edge Module ID",
)
context.argument("minimum_severity", arg_type=severity_type)
2 changes: 1 addition & 1 deletion azext_iot/constants.py
Original file line number Diff line number Diff line change
Expand Up @@ -44,7 +44,7 @@
DEVICETWIN_MONITOR_TIME_SEC = 15
# (Lib name, minimum version (including), maximum version (excluding))
EVENT_LIB = ("uamqp", "1.2", "1.3")
CENTRAL_PNP_INTERFACE_PREFIX = "$iotin:"
PNP_DTDLV2_COMPONENT_MARKER = "__t"

# Config Key's
CONFIG_KEY_UAMQP_EXT_VERSION = "uamqp_ext_version"
45 changes: 31 additions & 14 deletions azext_iot/monitor/parsers/central_parser.py
Original file line number Diff line number Diff line change
Expand Up @@ -92,17 +92,29 @@ def _perform_dynamic_validations(self, payload: dict):
if not isinstance(template, Template):
return

# pnp device is sending data to an unrecognized interface
if self.interface_name and (self.interface_name not in template.interfaces):
details = strings.invalid_interface_name(
self.interface_name, list(template.interfaces.keys())
# if component name is not defined then data should be mapped to root/inherited interfaces
if not self.component_name:
self._validate_payload(
payload=payload, template=template, is_component=False
)
return

if not template.components:
# template does not have any valid components
details = strings.invalid_component_name(self.component_name, list())
Comment thread
This conversation was marked as resolved.
self._add_central_issue(severity=Severity.warning, details=details)
return

self._validate_payload_against_interfaces(
payload=payload, template=template,
)
# if component name is defined check to see if its a valid name
if self.component_name not in template.components:
details = strings.invalid_component_name(
self.component_name, list(template.components.keys())
)
self._add_central_issue(severity=Severity.warning, details=details)
return

# if component name is valid check to see if payload is valid
self._validate_payload(payload=payload, template=template, is_component=True)

def _get_template(self):
try:
Expand All @@ -119,23 +131,28 @@ def _get_template(self):
# currently validates:
# 1) primitive types match (e.g. boolean is indeed bool etc)
# 2) names match (i.e. Humidity vs humidity etc)
def _validate_payload_against_interfaces(
self, payload: dict, template: Template,
):
def _validate_payload(self, payload: dict, template: Template, is_component: bool):
name_miss = []
for telemetry_name, telemetry in payload.items():
schema = template.get_schema(
name=telemetry_name, interface_name=self.interface_name
name=telemetry_name,
identifier=self.component_name,
is_component=is_component,
)
if not schema:
name_miss.append(telemetry_name)
else:
self._process_telemetry(telemetry_name, schema, telemetry)

if name_miss:
details = strings.invalid_field_name_mismatch_template(
name_miss, template.schema_names
)
if is_component:
details = strings.invalid_field_name_component_mismatch_template(
name_miss, template.component_schema_names
)
else:
details = strings.invalid_field_name_mismatch_template(
name_miss, template.schema_names,
)
self._add_central_issue(severity=Severity.warning, details=details)

def _process_telemetry(self, telemetry_name: str, schema, telemetry):
Expand Down
17 changes: 14 additions & 3 deletions azext_iot/monitor/parsers/strings.py
Original file line number Diff line number Diff line change
Expand Up @@ -56,10 +56,10 @@ def invalid_custom_headers():


# warning
def invalid_interface_name(interface_name: str, allowed_interfaces: list):
def invalid_component_name(component_name: str, allowed_components: list):
return (
"Device is specifying an interface that is unknown. Device specified interface: '{}'. Allowed interfaces: '{}'."
).format(interface_name, allowed_interfaces)
"Device is specifying a component that is unknown. Device specified component: '{}'. Allowed components: '{}'."
).format(component_name, allowed_components)


# warning
Expand All @@ -73,6 +73,17 @@ def invalid_field_name_mismatch_template(
).format(unmodeled_capabilities, modeled_capabilities)


# warning
def invalid_field_name_component_mismatch_template(
unmodeled_capabilities: list, modeled_capabilities: list
):
return (
"Device is sending data that has not been defined in the device template. "
"Following capabilities have NOT been defined in the device template '{}'. "
"Following capabilities have been defined in the device template (grouped by components) '{}'. "
).format(unmodeled_capabilities, modeled_capabilities)


# warning
def duplicate_property_name(duplicate_prop_name, interfaces: list):
return (
Expand Down
Loading