diff --git a/.gencode_hash.txt b/.gencode_hash.txt index 436bbf4596..d1b9dbf071 100644 --- a/.gencode_hash.txt +++ b/.gencode_hash.txt @@ -1,21 +1,21 @@ 23553972c4b777768f0f70fc0f89a0ec81f580b64031169a96379ea22bb95e79 gencode/docs/building_config_entity.html c21266e8de23b6e8acfac16a177db782375bf71df470c343c3f8eb8b822ef56a gencode/docs/commands_discovery.html -8aa5b5f48f97771deaf5585f8ee74c605c2a43568af04996ff8439843274cce9 gencode/docs/config.html +18c7a15e09ec8de5c7508e077c4baf60b9c9abb4f1919551d2c1a93042a40348 gencode/docs/config.html 0dcc1e8245bf0b2791a4bf9be15d8647acd89124ade475345ce4a9c56d62a824 gencode/docs/configuration_endpoint.html 40f078753386e07bfc200eaf3c35cb4362eb413101f4f00d87c88cde3ee53080 gencode/docs/configuration_execution.html 0e2f9b56fd0d3762ff7ce14e0f9fc119cba17a3c2eeb2dcdbf1ff2fad5c6c9d1 gencode/docs/configuration_pod.html 94bb566ac6a4b6b7f89cd2cf1911f658446cc0bea405014275104623f9964dd3 gencode/docs/configuration_pubber.html 1057fa40fb7a31a23bb2773d21c38cf4590a935bd8b5ea4218e695c6204f5dd9 gencode/docs/data_template.html 1d5294b4a1fea6555e23144e594523e4793c795069b6880ce607ad09a6015581 gencode/docs/equipment_translation.html -cc29a77c12a615e23d070c4bd6337c32f0fa057efa12df771a78c4be465aac9e gencode/docs/events.html +761b5aeae3b11377feb9b39d3be9a4383c5ed894a119268f44507d894f1b15f0 gencode/docs/events.html 70e57ad6ef39330d958727ebf9dcd61ef6ea30e4c8653eac412bf1867fdb3a70 gencode/docs/events_alarmset.html -2734f2b1fd0d22cdc2381d728376b031aecc4a3653a1faf5f743b973d43872dc gencode/docs/events_discovery.html +aa6e65cb3fbaa45e7e51013d579b0e643933886f1ecd61672ee3ffed8830e552 gencode/docs/events_discovery.html eafcc3c48189f605f114cde051fea9d13fc5f1a3e395d64fb0a91cb53d4c9aeb gencode/docs/events_pointset.html b65ce6e8061e61ba676a72c624f1c425bd238e152ce8e3ebb1e9c23f0e502bcb gencode/docs/events_streams.html 81043ba732caab75704d38e6977b32b1c2bcdad89d94ad2a259d0e70ca4aa40e gencode/docs/events_system.html 46fd37b577c655965346d1e3ececf559c4384791214900eeeea1ad8fc72a02b8 gencode/docs/events_udmi.html baf59590b1a71a49e59d8f499ae5112a1b7a555294dbd72bc821203aecceefa3 gencode/docs/events_validation.html -8d77357337531f7df7b88c332d9cac934723095c5be45ca410e31c9d1f665ee1 gencode/docs/metadata.html +b5ae61a5246f37bb2c94fff7ed7c90c05fb9e3b926c420307d4c732c5a31521f gencode/docs/metadata.html 18178a591940c176371fb9196048eaacd40f1323997cf907ca11eb879a095c7e gencode/docs/monitoring.html 64d21cf916e20566171cf9d723e59c297b576bcba89e2a73aec2b018398cc7da gencode/docs/mosquitto_client_response.html 6f332da9cc64230df3551f9748a4eaa4060146e767cae967fc0e73b1f348be5b gencode/docs/persistent_device.html @@ -26,7 +26,7 @@ eb6c623142496b61e3c9b00c9ead0ba8541af2050d76ce652e1721c84aa3a096 gencode/docs/r 165a29291d7d66653e46fd1ac5868f63e18f9d67c06ea9ebcb95d4830a528e68 gencode/docs/schema_doc.min.js 7ed934930aee763e0beebc349725ba3909115e8d346bb762f28bcbe745bb163a gencode/docs/schema_extras.js 7a1bbe014c739078d354ec484147325e1d0bd19468f275a53da38add43ebe089 gencode/docs/site_metadata.html -0993d21a1a0ae485bdb2f1794f279d13f782e841d07832f795624cc9f158ea93 gencode/docs/state.html +9bf177ae485395a95661f7deedbbefa54779de8d06337c039cafce145fc59b5f gencode/docs/state.html 275171b2329d64d497fa54f8877656920be7c4f79bc90f67a0ff1bae6700399b gencode/docs/state_validation.html 46347b31bc2a56bc74af9bb702ad40c7823887d5e9b1427903a4892cde3cd169 gencode/java/udmi/schema/AlarmAlarmsetConfig.java 520b12eb8cec4f043f81f898c4be12def09b7de138533922e17c038415fb8902 gencode/java/udmi/schema/AlarmAlarmsetEvents.java @@ -66,7 +66,7 @@ c04302f973508295258d4d3a29b5da70bf3cd3a6e2c63a958fb70dd3d370276c gencode/java/u 52ae2afb2dcb7863259e7eea71888811663c010429c09760493f5f006a660c84 gencode/java/udmi/schema/Dimension.java 2bb1513817eea9c0486b28f3316ead16d53885a8bad9f6a42ee3d5f762335a68 gencode/java/udmi/schema/DiscoveryCommand.java a572f17a61584a32ff19968356f7f7d7bcc8430131ac3701878d0bd221e1a1fa gencode/java/udmi/schema/DiscoveryConfig.java -9217b2c963003ca51ec61b9aadd40877634f2bea08d7fd412b51521e8ec61cfb gencode/java/udmi/schema/DiscoveryEvents.java +c3ff2e775880e6ec1965d74db5ef85bd640cf543e06226090b7a3f3fd83a3491 gencode/java/udmi/schema/DiscoveryEvents.java af16026c143a592562b77012dd739c02364bd1a9d6bbd6c7ede4f3a5a38b7f4a gencode/java/udmi/schema/DiscoveryModel.java 0fd348086bfd8e38a54a06b0fd205f6d01fdc6236f07ccc8bef49a1832f8c767 gencode/java/udmi/schema/DiscoveryState.java baef666f5f58f790b73ba7bed8f60eacdd75ba3151b9abc32265954a9da84faa gencode/java/udmi/schema/Electricity.java @@ -77,9 +77,9 @@ f890a14567657f5c97c2d3c1c753d751025ee094700ef0659f63f448529cc529 gencode/java/u 9df1e0a689390bda0847a0acab5a13fd7ced8c09b8563c13a7ce000ef1ea4999 gencode/java/udmi/schema/Events.java 8aed152c906c51a72a9224e7aec14bcdbd9ac2e00c2b1fed6bd5515082f79488 gencode/java/udmi/schema/ExecutionConfiguration.java a5a41a2bc809988efbbc121cdf730cbc1eb066372e046747fb6ae3f30e96a8b1 gencode/java/udmi/schema/FamilyDiscovery.java -0de804fb208fdf2ef198539ed5b828b780bc6c5cdd9e31b10067372165c4b7d4 gencode/java/udmi/schema/FamilyDiscoveryConfig.java +60286ff934d1ad721d90b2691538700073b73376207b06badfbd3f65e9d51b29 gencode/java/udmi/schema/FamilyDiscoveryConfig.java 6f9eaf4b56d3666caa65be4ded3344cb5b59aa9f838b454b5a4983d68061163e gencode/java/udmi/schema/FamilyDiscoveryModel.java -1d1f5a6ebf1db5f06976c6e4fb14f1404eec5460b9eb34f0508e1f95aa69e81b gencode/java/udmi/schema/FamilyDiscoveryState.java +9a786b1f9a4815f108e805ce0ffadea01a1577a4d051ba70d391f82a726c5f2f gencode/java/udmi/schema/FamilyDiscoveryState.java 180a65a6c61b76cbec7bb45bbfdfe0d4f26170d7f7e74aef9bd129f1dd451edf gencode/java/udmi/schema/FamilyLocalnetConfig.java d6782eef0f60aa8dc9f57219ba6dd336a1a9d68e7ba174e59c7dfd6f0f05a4f4 gencode/java/udmi/schema/FamilyLocalnetModel.java 9ca698d84b784b87d52d1f6cbaaa08964a7f050dfefe338ea95afc440ccc9a9f gencode/java/udmi/schema/FamilyLocalnetState.java @@ -176,7 +176,7 @@ e92d040fe84cc1bbc6e6b2d81a39dae156e1501e1c07a0b6cfc42e1599403e64 gencode/python 0feda2aadb7b5cbfbadc1ea6950292f428b765f8f27e874414d3ca9941f3746c gencode/python/udmi/schema/config_blobset.py 234b237a299d31b92dd873af6713c56e388345faeb79cbf2b46090c7d10ef00a gencode/python/udmi/schema/config_blobset_blob.py 246f6878e5a4b7f5e1a27d1c51aadd42bef07ee2113b9cd568bb623b24bc85de gencode/python/udmi/schema/config_discovery.py -5815f7f1ec7e5a2d5b7e3065060872d109ebb64f595c004a4b5b490c918684c2 gencode/python/udmi/schema/config_discovery_family.py +5445564598da1d534158603b7a0e42f1528629b8fdfcba3b4807e84510ff79e9 gencode/python/udmi/schema/config_discovery_family.py 326edbdbb90e376375294ba645fd789d1ae1809e8a98f3b32c3d04c2bc316ac1 gencode/python/udmi/schema/config_discovery_trace.py 0d100d1fec28c4e59cba704e5a512549972f427ea4da268c7472208fdccd96a6 gencode/python/udmi/schema/config_gateway.py 8119455e323f686da48ccf65d494d0dc2a26ad2a2a02d0f0167dc9a020740225 gencode/python/udmi/schema/config_localnet.py @@ -203,7 +203,7 @@ ab1a8ee1f12d04dd88837b6f026030b5ed3550ef3bfbbfbcb4f77cc4df39e93a gencode/python 3dcf4daa94b8450b7360e58500e20e1fbe5701502ac28358112ee1a6bd2a87f1 gencode/python/udmi/schema/events.py af48a2d32b4ec326df6e3f165d8944b994fb426bd4e663c42c1d592f5cb41f83 gencode/python/udmi/schema/events_alarmset.py b58371ffa7979d1598502a35859f3010ae7a2ec2d851454c010c835e426c7a14 gencode/python/udmi/schema/events_alarmset_alarm.py -3996175098735e889730c87ffc9ce1648e6624d801694458f6d4bc147ff29741 gencode/python/udmi/schema/events_discovery.py +584e3482138fb2210043bf6b7ca553455391ed678b4988406e247e348a7834d9 gencode/python/udmi/schema/events_discovery.py 84700f9d8a8975130ff9c62891003f7a12f367fa7dd1ca1db5f5fae7225bf005 gencode/python/udmi/schema/events_pointset.py 9f568bd573d4992e3efa3cc3cb8cdefecb95b15675b0442873abdc7850d108f9 gencode/python/udmi/schema/events_pointset_point.py 282439e4983ae9f310fda69dd6ff979567fa983ebdefcde407df42d631fe1bad gencode/python/udmi/schema/events_streams.py @@ -212,7 +212,7 @@ e4cdab07942e52cae243b4dddbc72676794914947dd66df8add0f5969f31fd11 gencode/python 72559779680c79dace3c5903022709852cbad7b77ed42af97debc8d852d61cb7 gencode/python/udmi/schema/events_validation.py a3ea178f98a4993da708e2ec41f5ede74aa92d742ffbe201a77adef803d4143a gencode/python/udmi/schema/events_validation_device.py 5660433b409bdaeacdce6b128660914586cca434e5373b3f94c04e89952642b4 gencode/python/udmi/schema/level.py -9577d11c5110af169aafd0e083d115c849ce17dfdc2b6b23b7186c69c1c333db gencode/python/udmi/schema/metadata.py +7595bc97beb84246bed974ec210c3c80f82926342c3cd062aa71d6805999eb35 gencode/python/udmi/schema/metadata.py cd81df64d7db21ab06f84cdbd07a808a641612c7c1124f9f2d928a898c3c767e gencode/python/udmi/schema/model_alarmset.py b80b7f580fd7527da0c2caff063218488cf5bbd091daa64a6a5f9a5d45b135c2 gencode/python/udmi/schema/model_alarmset_alarm.py 6d19eb94063acbc8e5432b9827e8b20f4f8cd02efa6ae760fabe60652c69542d gencode/python/udmi/schema/model_cloud.py @@ -249,7 +249,7 @@ bc7fae9084e9511001e87409867ea5c1edd29892b21cd611d7d3689afa39ed68 gencode/python c3aafb1bcd85fc43f50aaba1f30f51657108d70ef62ffd95444112be508973bb gencode/python/udmi/schema/state_blobset.py a3b9848f8887b75e013276ebd353afad899f147bec7c1ea0d70d7833f5dbe212 gencode/python/udmi/schema/state_blobset_blob.py 450d9ef8aca6ac904a8231ab4b0d1c268f0f5d24693fd4757664bb4c71e4a7f6 gencode/python/udmi/schema/state_discovery.py -9332d44d29f5c6d5642aadc5564f83c39556eb94bfc55559eca955d2d53ea4ab gencode/python/udmi/schema/state_discovery_family.py +b9ec341b956f7b71b28b72ad372eca66e4d728670756b1c535fceb47993cdbc6 gencode/python/udmi/schema/state_discovery_family.py ea2dc5ef443f91f2f6a6b5a0acc09e791870a275840a41acbb9bff82c3a4b8ee gencode/python/udmi/schema/state_gateway.py 2ca909da04032af58498600234f0a0c677c0f83b4705f4899bfb630b7aec05af gencode/python/udmi/schema/state_localnet.py e3763677989966c0884163bb726094c759ec4f2eb5c103880cecd895a3315fd8 gencode/python/udmi/schema/state_localnet_family.py diff --git a/bin/gencode_py b/bin/gencode_py index 6de1a7bd84..901100df08 100755 --- a/bin/gencode_py +++ b/bin/gencode_py @@ -69,6 +69,50 @@ bin/gencode_py_categories # create bucket.py from docs/specs/buckets.md bin/gencode_py_buckets +# Resolve mutual circular imports by moving them to the bottom of the module (after class definitions) +python3 - "$PYTHON_OUTPUT_DIR" << 'EOF' +import os, re, sys + +out_dir = sys.argv[1] +import_re = re.compile(r"^from \.([a-zA-Z0-9_]+) import .+$") +deps = {} +lines_by_mod = {} + +for fname in sorted(os.listdir(out_dir)): + if not fname.endswith(".py") or fname == "__init__.py": + continue + mod = fname[:-3] + path = os.path.join(out_dir, fname) + with open(path, "r", encoding="utf-8") as f: + lines = f.readlines() + lines_by_mod[mod] = (path, lines) + deps[mod] = set() + for line in lines: + m = import_re.match(line.strip()) + if m: + deps[mod].add(m.group(1)) + +for mod, (path, lines) in lines_by_mod.items(): + cyclic_targets = {target for target in deps[mod] if mod in deps.get(target, set())} + if not cyclic_targets: + continue + kept = [] + deferred = [] + for line in lines: + m = import_re.match(line.strip()) + if m and m.group(1) in cyclic_targets: + deferred.append(line.rstrip("\n") + "\n") + else: + kept.append(line) + if deferred: + if kept and not kept[-1].endswith("\n"): + kept[-1] += "\n" + kept.append("\n") + kept.extend(deferred) + with open(path, "w", encoding="utf-8") as f: + f.writelines(kept) +EOF + # add import statements to udmi/schema/__init__.py find "$PYTHON_OUTPUT_DIR" -name "*.py" -not -name "__init__.py" -print0 | while IFS= read -r -d $'\0' file; do module_name=$(basename "$file" .py) diff --git a/bin/gencode_root_schemas b/bin/gencode_root_schemas index b3f227680e..e785aa3cc0 100755 --- a/bin/gencode_root_schemas +++ b/bin/gencode_root_schemas @@ -21,7 +21,8 @@ IGNORE_LIST = ['events.json'] # These are sometimes referenced (e.g. by pubber) but should be considered root ALWAYS_ROOT = ['configuration_endpoint.json', 'events_system.json', - 'events_discovery.json'] + 'events_discovery.json', + 'metadata.json'] def parse_command_line_args(): parser = argparse.ArgumentParser() diff --git a/bin/gendocs.py b/bin/gendocs.py index dea71e8a90..ff5f0a5d5c 100644 --- a/bin/gendocs.py +++ b/bin/gendocs.py @@ -4,6 +4,34 @@ from json_schema_for_humans.generate import generate_from_filename from json_schema_for_humans.generation_configuration import GenerationConfiguration +import json_schema_for_humans.schema.intermediate_representation as ir +from json_schema_for_humans.schema.schema_node import SchemaNode + +# pylint: disable=protected-access,invalid-name +orig_has_ref_to_parent = ir._has_reference_to_parent +orig_node_is_parent = SchemaNode.node_is_parent + + +def _has_reference_to_parent(referenced_schema_path, anchor_part, current_node): + node = current_node + while node: + if orig_has_ref_to_parent(referenced_schema_path, anchor_part, node): + return True + node = node.parent + return False + + +def _node_is_parent(self, node_to_check): + node = self + while node: + if orig_node_is_parent(node, node_to_check): + return True + node = node.parent + return False + + +ir._has_reference_to_parent = _has_reference_to_parent +SchemaNode.node_is_parent = _node_is_parent config = GenerationConfiguration(copy_css=True, expand_buttons=True, diff --git a/bin/test_butler b/bin/test_butler index 0a58c98b54..db3960b0d1 100755 --- a/bin/test_butler +++ b/bin/test_butler @@ -21,17 +21,18 @@ echo "========================================================================== PG_PORT=${POSTGRES_PORT:-5432} PG_USER=${POSTGRES_USER:-postgres} PG_DB=${POSTGRES_DB:-postgres} +export PGPASSWORD=${POSTGRES_PASSWORD:-postgres} INF_PORT=${INFLUX_PORT:-8086} # Check if PostgreSQL and InfluxDB are running -if psql -h 127.0.0.1 -p "$PG_PORT" -U "$PG_USER" -d "$PG_DB" -c "SELECT 1;" >/dev/null 2>&1; then +if psql -w -h 127.0.0.1 -p "$PG_PORT" -U "$PG_USER" -d "$PG_DB" -c "SELECT 1;" >/dev/null 2>&1; then echo "PostgreSQL is active on port $PG_PORT." echo "Piping simple trace into Butler..." bin/pull_files tests/traces/simple | butler/bin/butler --stdin --always-save-raw echo "Verifying PostgreSQL records in udmi_messages..." - msg_count=$(psql -h 127.0.0.1 -p "$PG_PORT" -U "$PG_USER" -d "$PG_DB" -t -A -c "SELECT count(*) FROM udmi_messages;" 2>/dev/null || echo 0) + msg_count=$(psql -w -h 127.0.0.1 -p "$PG_PORT" -U "$PG_USER" -d "$PG_DB" -t -A -c "SELECT count(*) FROM udmi_messages;" 2>/dev/null || echo 0) echo "udmi_messages count: $msg_count" if [[ $msg_count -gt 0 ]]; then @@ -42,10 +43,10 @@ if psql -h 127.0.0.1 -p "$PG_PORT" -U "$PG_USER" -d "$PG_DB" -c "SELECT 1;" >/de fi echo "Verifying PostgreSQL records in specialized tables..." - sys_count=$(psql -h 127.0.0.1 -p "$PG_PORT" -U "$PG_USER" -d "$PG_DB" -t -A -c "SELECT count(*) FROM udmi_system_state;" 2>/dev/null || echo 0) + sys_count=$(psql -w -h 127.0.0.1 -p "$PG_PORT" -U "$PG_USER" -d "$PG_DB" -t -A -c "SELECT count(*) FROM udmi_system_state;" 2>/dev/null || echo 0) echo "udmi_system_state count: $sys_count" - pt_count=$(psql -h 127.0.0.1 -p "$PG_PORT" -U "$PG_USER" -d "$PG_DB" -t -A -c "SELECT count(*) FROM udmi_point_state;" 2>/dev/null || echo 0) + pt_count=$(psql -w -h 127.0.0.1 -p "$PG_PORT" -U "$PG_USER" -d "$PG_DB" -t -A -c "SELECT count(*) FROM udmi_point_state;" 2>/dev/null || echo 0) echo "udmi_point_state count: $pt_count" else echo "PostgreSQL is not running locally; skipping live database verification (tested via unit tests with mocks)." diff --git a/bin/test_uufi_client b/bin/test_uufi_client index 773eea59f3..162cad48bd 100755 --- a/bin/test_uufi_client +++ b/bin/test_uufi_client @@ -196,5 +196,114 @@ restore_metadata trap - EXIT echo "System Model Update SUCCESS: Received matching model update message on bus" +# 4. Site Model Discovery Test +echo "Starting Site Model Discovery test..." +DISC_LOG_FILE=$(mktemp) +DISC_GEN=$(date -u +%Y-%m-%dT%H:%M:%SZ) +echo "Subscribing to /r/$REGISTRY_ID/d/+/+/discovery for Model Discovery sequence (gen: $DISC_GEN)..." +mosquitto_sub $MQTT_ARGS -i "${CLIENT_ID}_disc_sub" -t "/r/$REGISTRY_ID/d/+/+/discovery" -F "%t %p" | while read -r topic payload; do + GEN=$(echo "$payload" | jq -r '.generation // .families.iot.generation' 2>/dev/null) + if [[ "$GEN" == "$DISC_GEN" ]]; then + echo "$topic $payload" >> "$DISC_LOG_FILE" + if grep -q '"phase":"active"' "$DISC_LOG_FILE" && \ + grep -q '"event_no":0' "$DISC_LOG_FILE" && \ + grep -q "/r/$REGISTRY_ID/d/$DUT_ID/events/discovery" "$DISC_LOG_FILE" && \ + grep -q '"phase":"stopped"' "$DISC_LOG_FILE"; then + kill -USR1 $$ 2>/dev/null || true + exit 0 + fi + fi +done & +SUB_DISC_PID=$! + +GOT_DISC=0 +trap "GOT_DISC=1" USR1 +sleep 2 + +DISC_CONFIG="{\"subType\":\"config\",\"subFolder\":\"discovery\",\"deviceRegistryId\":\"$REGISTRY_ID\",\"payload\":{\"version\":\"1.5.7\",\"timestamp\":\"$DISC_GEN\",\"generation\":\"$DISC_GEN\",\"families\":{\"iot\":{\"generation\":\"$DISC_GEN\",\"depth\":\"details\",\"source\":\"model\"}}}}" +echo "Publishing Model Discovery config to /uufi/r/$REGISTRY_ID/c/config/discovery: $DISC_CONFIG" +mosquitto_pub $MQTT_ARGS -i "${CLIENT_ID}_disc_pub" -t "/uufi/r/$REGISTRY_ID/c/config/discovery" -m "$DISC_CONFIG" + +TIMEOUT=30 +while [ $GOT_DISC -eq 0 ] && [ $TIMEOUT -gt 0 ]; do + echo "Waiting for Model Discovery completion... ($TIMEOUT)" + sleep 1 + TIMEOUT=$((TIMEOUT - 1)) +done + +kill $SUB_DISC_PID 2>/dev/null || true + +if [ $GOT_DISC -eq 0 ]; then + echo "ERROR: Timed out waiting for Model Discovery sequence completion" >&2 + cat "$DISC_LOG_FILE" || true + exit 1 +fi + +# Verify active state, event_no=0 count, AHU-1 model event, source:model, and stopped state on target stream +grep -q '"phase":"active"' "$DISC_LOG_FILE" || { echo "ERROR: Missing active DiscoveryState in Model Discovery" >&2; cat "$DISC_LOG_FILE"; exit 1; } +grep -q '"event_no":0' "$DISC_LOG_FILE" || { echo "ERROR: Missing event_no=0 DiscoveryEvents in Model Discovery" >&2; cat "$DISC_LOG_FILE"; exit 1; } +grep -q "/r/$REGISTRY_ID/d/$DUT_ID/events/discovery" "$DISC_LOG_FILE" || { echo "ERROR: Missing $DUT_ID DiscoveryEvents in Model Discovery" >&2; cat "$DISC_LOG_FILE"; exit 1; } +grep -q '"source":"model"' "$DISC_LOG_FILE" || { echo "ERROR: Missing source:model in Model Discovery messages" >&2; cat "$DISC_LOG_FILE"; exit 1; } +grep -q '"phase":"stopped"' "$DISC_LOG_FILE" || { echo "ERROR: Missing stopped DiscoveryState in Model Discovery" >&2; cat "$DISC_LOG_FILE"; exit 1; } +echo "Site Model Discovery SUCCESS: Verified start state, event_no=0 count, device model events, and stop state" + +# 5. Partial (Shard) Site Model Discovery & Closure Expansion Test +echo "Starting Partial (Shard) Site Model Discovery & Closure test..." +SHARD_LOG_FILE=$(mktemp) +SHARD_GEN=$(date -u -d "+1 second" +%Y-%m-%dT%H:%M:%SZ 2>/dev/null || date -u +%Y-%m-%dT%H:%M:%SZ) +if [[ "$SHARD_GEN" == "$DISC_GEN" ]]; then + sleep 1 + SHARD_GEN=$(date -u +%Y-%m-%dT%H:%M:%SZ) +fi +echo "Subscribing to /r/$REGISTRY_ID/d/+/+/discovery for Sharded Model Discovery (gen: $SHARD_GEN, shard: [AHU-22])..." +mosquitto_sub $MQTT_ARGS -i "${CLIENT_ID}_shard_sub" -t "/r/$REGISTRY_ID/d/+/+/discovery" -F "%t %p" | while read -r topic payload; do + GEN=$(echo "$payload" | jq -r '.generation // .families.iot.generation' 2>/dev/null) + if [[ "$GEN" == "$SHARD_GEN" ]]; then + echo "$topic $payload" >> "$SHARD_LOG_FILE" + if grep -q '"phase":"active"' "$SHARD_LOG_FILE" && \ + grep -q '"event_no":0' "$SHARD_LOG_FILE" && \ + grep -q "/r/$REGISTRY_ID/d/AHU-22/events/discovery" "$SHARD_LOG_FILE" && \ + grep -q "/r/$REGISTRY_ID/d/GAT-123/events/discovery" "$SHARD_LOG_FILE" && \ + grep -q "/r/$REGISTRY_ID/d/SNS-4/events/discovery" "$SHARD_LOG_FILE" && \ + grep -q '"phase":"stopped"' "$SHARD_LOG_FILE"; then + kill -USR1 $$ 2>/dev/null || true + exit 0 + fi + fi +done & +SUB_SHARD_PID=$! + +GOT_SHARD=0 +trap "GOT_SHARD=1" USR1 +sleep 2 + +SHARD_CONFIG="{\"subType\":\"config\",\"subFolder\":\"discovery\",\"deviceRegistryId\":\"$REGISTRY_ID\",\"payload\":{\"version\":\"1.5.7\",\"timestamp\":\"$SHARD_GEN\",\"generation\":\"$SHARD_GEN\",\"families\":{\"iot\":{\"generation\":\"$SHARD_GEN\",\"depth\":\"details\",\"source\":\"model\",\"addrs\":[\"AHU-22\"]}}}}" +echo "Publishing Sharded Model Discovery config to /uufi/r/$REGISTRY_ID/c/config/discovery: $SHARD_CONFIG" +mosquitto_pub $MQTT_ARGS -i "${CLIENT_ID}_shard_pub" -t "/uufi/r/$REGISTRY_ID/c/config/discovery" -m "$SHARD_CONFIG" + +TIMEOUT=30 +while [ $GOT_SHARD -eq 0 ] && [ $TIMEOUT -gt 0 ]; do + echo "Waiting for Sharded Model Discovery completion... ($TIMEOUT)" + sleep 1 + TIMEOUT=$((TIMEOUT - 1)) +done + +kill $SUB_SHARD_PID 2>/dev/null || true + +if [ $GOT_SHARD -eq 0 ]; then + echo "ERROR: Timed out waiting for Sharded Model Discovery sequence completion" >&2 + cat "$SHARD_LOG_FILE" || true + exit 1 +fi + +grep -q '"addrs":\["AHU-22"\]' "$SHARD_LOG_FILE" || { echo "ERROR: Missing addrs:[AHU-22] on event_no=0 in Sharded Model Discovery" >&2; cat "$SHARD_LOG_FILE"; exit 1; } +grep -q '"active_count":3' "$SHARD_LOG_FILE" || { echo "ERROR: Expected active_count:3 (Closure of AHU-22 -> GAT-123 -> SNS-4) in Sharded Model Discovery" >&2; cat "$SHARD_LOG_FILE"; exit 1; } +if grep -q "/r/$REGISTRY_ID/d/AHU-1/events/discovery" "$SHARD_LOG_FILE"; then + echo "ERROR: Unrelated device AHU-1 should not be in Closure([AHU-22])" >&2 + cat "$SHARD_LOG_FILE" + exit 1 +fi +echo "Sharded Site Model Discovery SUCCESS: Verified addrs:[AHU-22], active_count:3, and Closure expansion [AHU-22, GAT-123, SNS-4]" + echo "UUFI Comprehensive Integration Test PASSED" -rm -f "$REPLY_FILE" "$STATE_REPLY_FILE" "$MODEL_REPLY_FILE" +rm -f "$REPLY_FILE" "$STATE_REPLY_FILE" "$MODEL_REPLY_FILE" "$DISC_LOG_FILE" "$SHARD_LOG_FILE" diff --git a/butler/src/connection.py b/butler/src/connection.py index 4bd33a898a..33741a777e 100644 --- a/butler/src/connection.py +++ b/butler/src/connection.py @@ -241,8 +241,13 @@ def _resolve_endpoint(self): self.username = f"/r/{ns_prefix}UDMI-REFLECT/d/{self.actual_registry}" self.password = derived_pass - def get_discovery_topics(self, device_id): - """Returns list of topics to publish discovery config for target device.""" + def get_discovery_topics(self, device_id=None): + """Returns list of topics to publish discovery config for target device or registry.""" + if not device_id: + uufi_topic = f"/uufi/r/{self.actual_registry}/c/config/discovery" + if self.prefix: + uufi_topic = f"/{self.prefix}{uufi_topic}" + return [uufi_topic] if self.provider in ("gbos", "clearblade"): return [ f"/devices/{device_id}/commands/discovery", diff --git a/butler/src/discovery.py b/butler/src/discovery.py index 71d98f2ae7..f15a34ed31 100644 --- a/butler/src/discovery.py +++ b/butler/src/discovery.py @@ -10,16 +10,30 @@ from butler.src.connection import ButlerConnection -def run_discovery(conn_spec, registry_id, device_id, target_families, site_model=None): +def run_discovery(conn_spec, registry_id, device_id=None, target_families=None, site_model=None, addrs=None): """Dispatches discovery configuration message over the configured project spec / broker.""" - connection = ButlerConnection(conn_spec, registry_id, site_model, device_id) + is_registry_discovery = not device_id + actual_device_id = None if is_registry_discovery else device_id + connection = ButlerConnection(conn_spec, registry_id, site_model, actual_device_id) actual_registry = connection.actual_registry now = datetime.now(timezone.utc).strftime("%Y-%m-%dT%H:%M:%SZ") families_dict = {} + if is_registry_discovery and not target_families: + target_families = ["iot"] + if target_families: for f in target_families: - families_dict[f] = {"generation": now} + if f == "iot": + families_dict[f] = { + "generation": now, + "depth": "details", + "source": "model", + } + if addrs: + families_dict[f]["addrs"] = list(addrs) + else: + families_dict[f] = {"generation": now} else: families_dict = { "vendor": {"generation": now}, @@ -30,17 +44,21 @@ def run_discovery(conn_spec, registry_id, device_id, target_families, site_model tx_id = f"TXN-{uuid.uuid4().hex[:8]}" source_id = "butler" payload = { - "version": "1.5.2", + "version": "1.5.7", "timestamp": now, "generation": now, "families": families_dict, - "enumerations": {"families": "entries", "points": "entries", "features": "entries"}, } + if not is_registry_discovery: + payload["enumerations"] = { + "families": "entries", + "points": "entries", + "features": "entries", + } msg = { "subType": "config", "subFolder": "discovery", "deviceRegistryId": actual_registry, - "deviceId": device_id, "projectId": connection.project or "vibrant", "transactionId": tx_id, "publishTime": now, @@ -48,7 +66,9 @@ def run_discovery(conn_spec, registry_id, device_id, target_families, site_model "principal": source_id, "payload": payload, } + if actual_device_id: + msg["deviceId"] = actual_device_id - topics = connection.get_discovery_topics(device_id) + topics = connection.get_discovery_topics(actual_device_id) topic_message_pairs = [(t, msg) for t in topics] connection.publish_messages(topic_message_pairs) diff --git a/butler/src/handlers/discovery.py b/butler/src/handlers/discovery.py index 5370102172..33403616f8 100644 --- a/butler/src/handlers/discovery.py +++ b/butler/src/handlers/discovery.py @@ -2,28 +2,87 @@ from typing import Any, Dict, List, Optional from butler.src.handlers.base import BaseHandler +from butler.src.handlers.metadata import MetadataHandler from udmi.common.db.influx import InfluxManager from udmi.common.db.postgres import PostgresManager def get_discovered_family_addr(payload: Dict[str, Any], family: str) -> Optional[str]: """Returns the discovered address for a given family.""" - try: - if family == payload.get("family"): - return str(payload.get("addr")) - families = payload.get("families", {}) - if isinstance(families, dict) and family in families: - return str(families[family].get("addr")) - except Exception: - pass + if not isinstance(payload, dict): + return None + if family == payload.get("family") and payload.get("addr") is not None: + return str(payload.get("addr")) + families = payload.get("families") + if isinstance(families, dict): + family_block = families.get(family) + if isinstance(family_block, dict) and family_block.get("addr") is not None: + return str(family_block.get("addr")) + model = payload.get("model") + if isinstance(model, dict): + localnet = model.get("localnet") + model_families = localnet.get("families") if isinstance(localnet, dict) else None + if isinstance(model_families, dict): + family_block = model_families.get(family) + if isinstance(family_block, dict) and family_block.get("addr") is not None: + return str(family_block.get("addr")) return None class DiscoveryHandler(BaseHandler): """Processes discovery events and saves rows into PostgreSQL udmi_discovery table.""" + def __init__(self) -> None: + super().__init__() + self._iot_batches: Dict[tuple, Dict[str, Any]] = {} + def can_handle(self, sub_folder: Optional[str], sub_type: Optional[str], payload: Dict[str, Any]) -> bool: - return sub_folder == "discovery" + return sub_folder == "discovery" and sub_type in (None, "events") + + def _reconcile_iot_batch( + self, + registry_id: Optional[str], + generation: Optional[str], + postgres_manager: PostgresManager, + ) -> None: + if not registry_id or not generation: + return + key = (registry_id, generation) + batch = self._iot_batches.get(key) + if not batch or not batch.get("has_start"): + return + active_count = batch.get("active_count") + seen_events: Dict[int, str] = batch.get("seen_events", {}) + if not isinstance(active_count, int) or len(seen_events) < active_count: + return + + closure_ids = set(seen_events.values()) + addrs = batch.get("addrs") + self._iot_batches.pop(key, None) + + if isinstance(addrs, list): + deleted_ids = sorted(set(str(a) for a in addrs if a) - closure_ids) + if deleted_ids: + placeholders = ", ".join(["%s"] * len(deleted_ids)) + sql = ( + f"DELETE FROM udmi_metadata " + f"WHERE device_registry_id = %s AND device_id IN ({placeholders})" + ) + postgres_manager.execute_sql(sql, tuple([registry_id] + deleted_ids)) + else: + if closure_ids: + sorted_closure = sorted(closure_ids) + placeholders = ", ".join(["%s"] * len(sorted_closure)) + sql = ( + f"DELETE FROM udmi_metadata " + f"WHERE device_registry_id = %s AND device_id NOT IN ({placeholders})" + ) + postgres_manager.execute_sql(sql, tuple([registry_id] + sorted_closure)) + else: + postgres_manager.execute_sql( + "DELETE FROM udmi_metadata WHERE device_registry_id = %s", + (registry_id,), + ) def process_message( self, @@ -35,6 +94,30 @@ def process_message( if isinstance(payload, dict) and "payload" in payload and isinstance(payload.get("payload"), dict): payload = payload["payload"] family = payload.get("family") + event_no = payload.get("event_no") + generation = payload.get("generation") + registry_id = envelope.get("deviceRegistryId") + model_data = payload.get("model") if isinstance(payload.get("model"), dict) else {} + + if isinstance(event_no, int) and event_no <= 0 and not payload.get("addr") and not model_data: + res: Dict[str, Any] = { + "target": "postgres", + "table": "udmi_discovery", + "count": 0, + } + if payload.get("addrs") is not None: + res["addrs"] = payload.get("addrs") + if family == "iot" and event_no == 0 and registry_id and generation: + batch = self._iot_batches.setdefault( + (registry_id, generation), + {"has_start": False, "active_count": 0, "addrs": None, "seen_events": {}}, + ) + batch["has_start"] = True + batch["active_count"] = payload.get("active_count", 0) + batch["addrs"] = payload.get("addrs") + self._reconcile_iot_batch(registry_id, generation, postgres_manager) + return res + ports: List[Dict[str, Any]] = [] if family == "ether" and (refs := payload.get("refs")): if isinstance(refs, dict): @@ -45,16 +128,21 @@ def process_message( if isinstance(v, dict) ] - system = payload.get("system", {}) if isinstance(payload.get("system"), dict) else {} + raw_system = payload.get("system") if isinstance(payload.get("system"), dict) else model_data.get("system", {}) + system = raw_system if isinstance(raw_system, dict) else {} hardware = system.get("hardware", {}) if isinstance(system.get("hardware"), dict) else {} ancillary = system.get("ancillary", {}) if isinstance(system.get("ancillary"), dict) else {} status = payload.get("status", {}) if isinstance(payload.get("status"), dict) else {} + device_id = envelope.get("deviceId") + if not device_id and family == "iot" and payload.get("addr"): + device_id = str(payload.get("addr")) + row = { "timestamp": payload.get("timestamp") or envelope.get("publishTime"), - "generation": payload.get("generation"), - "device_registry_id": envelope.get("deviceRegistryId"), - "device_id": envelope.get("deviceId"), + "generation": generation, + "device_registry_id": registry_id, + "device_id": device_id, "message_id": envelope.get("messageId"), "scan_family": family, "ether_addr": get_discovered_family_addr(payload, "ether"), @@ -74,6 +162,19 @@ def process_message( postgres_manager.insert_row("udmi_discovery", row) + if family == "iot" and model_data: + model_env = dict(envelope) + if device_id: + model_env["deviceId"] = device_id + MetadataHandler().process_message(model_env, model_data, postgres_manager, influx_manager) + if registry_id and generation and isinstance(event_no, int) and event_no > 0 and device_id: + batch = self._iot_batches.setdefault( + (registry_id, generation), + {"has_start": False, "active_count": 0, "addrs": None, "seen_events": {}}, + ) + batch["seen_events"][event_no] = device_id + self._reconcile_iot_batch(registry_id, generation, postgres_manager) + return { "target": "postgres", "table": "udmi_discovery", diff --git a/butler/src/mapping.py b/butler/src/mapping.py index 8f8777589e..b02cbc4d28 100644 --- a/butler/src/mapping.py +++ b/butler/src/mapping.py @@ -58,7 +58,7 @@ def run_mapping( butler = butler_client or get_butler_interface(conn_spec, butler_port) # 1. Get most recent model for each device_id in this registry - models = [] + models_by_id = {} if site_model: devices_dir = os.path.join(site_model, "devices") if os.path.exists(devices_dir): @@ -67,9 +67,19 @@ def run_mapping( if os.path.exists(meta_path): try: with open(meta_path, "r", encoding="utf-8") as mf: - models.append((device_id, json.load(mf))) + models_by_id[device_id] = json.load(mf) except Exception as e: print(f"err: {e}", file=sys.stderr) + if butler and hasattr(butler, "get_discovered_models"): + try: + discovered_models = butler.get_discovered_models(registry_id) + if isinstance(discovered_models, dict): + for dev_id, model_payload in discovered_models.items(): + if isinstance(model_payload, dict) and dev_id not in models_by_id: + models_by_id[dev_id] = model_payload + except Exception as e: + print(f"Error querying discovered models via Butler: {e}", file=sys.stderr) + models = list(models_by_id.items()) print(f"Found {len(models)} models", file=sys.stderr) # 2. Get discovered devices via Butler interface diff --git a/butler/tests/test_handlers.py b/butler/tests/test_handlers.py index 18df34746b..bb0d9c43af 100644 --- a/butler/tests/test_handlers.py +++ b/butler/tests/test_handlers.py @@ -181,6 +181,178 @@ def test_raw_fallback_handler(self): self.assertEqual(row["project_id"], "proj-1") self.assertEqual(row["payload"], payload) + def test_model_discovery_handler(self): + handler = DiscoveryHandler() + self.assertTrue(handler.can_handle("discovery", "events", {})) + self.assertFalse(handler.can_handle("discovery", "state", {})) + + start_env = {"deviceRegistryId": "reg-1"} + start_payload = { + "family": "iot", + "source": "model", + "generation": "2026-08-21T00:00:00Z", + "event_no": 0, + "addrs": ["AHU-1", "DEL-1"], + "active_count": 1, + } + start_res = handler.process_message(start_env, start_payload, self.mock_pg, self.mock_influx) + self.assertEqual(start_res["count"], 0) + self.assertEqual(start_res["addrs"], ["AHU-1", "DEL-1"]) + self.mock_pg.insert_row.assert_not_called() + + envelope = {"deviceId": "AHU-1", "deviceRegistryId": "reg-1"} + payload = { + "family": "iot", + "generation": "2026-08-21T00:00:00Z", + "event_no": 1, + "addr": "AHU-1", + "model": { + "version": "1.5.7", + "timestamp": "2026-08-21T00:00:00Z", + "system": { + "hardware": {"make": "Acme", "model": "AHU-Pro"}, + "serial_no": "SN-AHU-1", + "location": {"site": "reg-1", "section": "Floor-2", "position": {"x": 1, "y": 2}}, + }, + "localnet": { + "families": { + "bacnet": {"addr": "41122"}, + "ipv4": {"addr": "10.0.0.10"}, + } + }, + }, + } + + res = handler.process_message(envelope, payload, self.mock_pg, self.mock_influx) + self.assertEqual(res["target"], "postgres") + self.assertEqual(res["table"], "udmi_discovery") + self.assertEqual(self.mock_pg.insert_row.call_count, 2) + disc_table, disc_row = self.mock_pg.insert_row.call_args_list[0][0] + meta_table, meta_row = self.mock_pg.insert_row.call_args_list[1][0] + self.assertEqual(disc_table, "udmi_discovery") + self.assertEqual(disc_row["scan_family"], "iot") + self.assertEqual(disc_row["bacnet_addr"], "41122") + self.assertEqual(disc_row["ipv4_addr"], "10.0.0.10") + self.assertEqual(disc_row["hardware_make"], "Acme") + self.assertEqual(meta_table, "udmi_metadata") + self.assertEqual(meta_row["device_id"], "AHU-1") + self.assertEqual(meta_row["metadata"]["system"]["serial_no"], "SN-AHU-1") + self.mock_pg.execute_sql.assert_called_once_with( + "DELETE FROM udmi_metadata WHERE device_registry_id = %s AND device_id IN (%s)", + ("reg-1", "DEL-1"), + ) + + def test_provider_discovered_models_deletion_reconciliation(self): + from mcp.butler.provider import ButlerProvider + + mock_conn = MagicMock() + mock_cur = MagicMock() + mock_conn.cursor.return_value.__enter__.return_value = mock_cur + mock_cur.fetchall.side_effect = [ + [ + ("AHU-1", {"system": {"serial_no": "SN-OLD"}}), + ("DEL-1", {"system": {"serial_no": "SN-DEL"}}), + ("VAV-1", {"system": {"serial_no": "SN-VAV"}}), + ], + [ + ( + None, + "events", + "discovery", + { + "family": "iot", + "generation": "2026-08-21T01:00:00Z", + "event_no": 0, + "addrs": ["AHU-1", "DEL-1"], + "active_count": 1, + }, + ), + ( + "AHU-1", + "events", + "discovery", + { + "family": "iot", + "generation": "2026-08-21T01:00:00Z", + "event_no": 1, + "addr": "AHU-1", + "model": {"system": {"serial_no": "SN-NEW"}}, + }, + ), + ( + None, + "events", + "discovery", + { + "family": "iot", + "generation": "2026-08-21T02:00:00Z", + "event_no": 0, + "active_count": 2, + }, + ), + ], + ] + self.mock_pg.get_connection.return_value = mock_conn + provider = ButlerProvider(pg_manager=self.mock_pg, influx_manager=self.mock_influx) + models = provider.get_discovered_models("reg-1") + self.assertEqual(set(models.keys()), {"AHU-1", "VAV-1"}) + self.assertEqual(models["AHU-1"]["system"]["serial_no"], "SN-NEW") + + def test_registry_model_discovery_topics(self): + from butler.src.connection import ButlerConnection + conn = ButlerConnection("//mqtt/localhost:18833", registry_id="ZZ-TRI-FECTA") + self.assertEqual( + conn.get_discovery_topics(None), + ["/uufi/r/ZZ-TRI-FECTA/c/config/discovery"], + ) + self.assertEqual( + conn.get_discovery_topics("GAT-123"), + ["/uufi/r/ZZ-TRI-FECTA/d/GAT-123/c/config/discovery"], + ) + + def test_mapping_with_discovered_models(self): + from unittest.mock import patch + from butler.src.mapping import run_mapping + + mock_butler = MagicMock() + mock_butler.get_discovered_models.return_value = { + "AHU-1": { + "version": "1.5.2", + "timestamp": "2026-08-21T00:00:00Z", + "system": {"serial_no": "SN-AHU-1"}, + "localnet": { + "families": { + "bacnet": {"addr": "41122"}, + } + }, + } + } + mock_butler.get_discovered_devices.return_value = [ + { + "gateway_id": "GAT-123", + "generation": "2026-08-21T00:00:00Z", + "bacnet": "41122", + "ipv4": "192.168.1.50", + "vendor": None, + "timestamp": "2026-08-21T00:00:01Z", + } + ] + + with patch("butler.src.mapping.ButlerConnection") as mock_conn_cls: + mock_conn = MagicMock() + mock_conn.actual_registry = "ZZ-TRI-FECTA" + mock_conn.project = "localhost" + mock_conn.get_propose_topics.side_effect = ( + lambda dev_id, sf="system": [f"/uufi/r/ZZ-TRI-FECTA/d/{dev_id}/c/propose/{sf}"] + ) + mock_conn_cls.return_value = mock_conn + + run_mapping("//mqtt/localhost:18833", "ZZ-TRI-FECTA", site_model=None, butler_client=mock_butler) + mock_conn.publish_messages.assert_called_once() + published_pairs = mock_conn.publish_messages.call_args[0][0] + topics = [t for t, _ in published_pairs] + self.assertIn("/uufi/r/ZZ-TRI-FECTA/d/AHU-1/c/propose/localnet", topics) + if __name__ == "__main__": unittest.main() diff --git a/docs/specs/discovery.md b/docs/specs/discovery.md index fd2f9c28ea..c0ab14f80a 100644 --- a/docs/specs/discovery.md +++ b/docs/specs/discovery.md @@ -79,3 +79,27 @@ be reported: * `blobs`: A listing of all the data blobs that a device knows how to handle. This could be components like firmware updates, key rotation, etc... Some blobs will be standardized across the system, while others will be device-specific. + +## Site Model Discovery + +Site Model Discovery exposes the cloud-registered device models of a registry over the standard UDMI discovery pipeline (`family: "iot"`, `source: "model"`). It is triggered automatically when a registry model update is processed by UDMIS, or on-demand by publishing a registry-scoped [`config`](../../gencode/docs/config.html) discovery message with `families.iot.source` set to `"model"`. + +To support both full-site snapshots and partial/incremental updates, Site Model Discovery distinguishes three sets of devices: + +* **`Site` (Declared Set):** All active (non-blocked) devices registered in the building (`deviceRegistryId`). +* **`Shard` (Declared Set):** The working set of changed or targeted device IDs (`addrs`), specified via `families.iot.addrs` in `config/discovery` (or via a targeted `registrar` update, including `-T` transitive proxy expansion and `-b` / `-d` / `-x` targeted device blocking/deletion) and echoed on the `event_no = 0` start event (`addrs`). When `addrs` is omitted/null on `event_no = 0`, the scan is a full-site scan (`Shard == Closure == Site`). +* **`Closure` (Calculated Set):** The set of active devices emitted in `event_no = 1..N` (`active_count = |Closure|`), comprising: + 1. All active devices in the `Shard` ($S \cap \text{Site}$), + 2. All active devices in the `Site` that directly reference any device in the `Shard` (including deleted/blocked devices in the `Shard`) via `model.relationships` or `model.gateway` (`proxy_ids` / `gateway_id`, treated bidirectionally so gateway-proxy pairs are always kept together), + 3. All active devices in the `Site` that transitively reference any device in the `Shard` (inbound) via `model.pointset.points.*.expr` (`:`, excluding `$SELF` self-references), and + 4. All active devices in the `Site` transitively referenced (outbound) from the above set across `model.relationships`, `model.pointset.points.*.expr`, and `model.gateway`. + +The Site Model Discovery sequence is serialized per registry and published to the `udmi_target` stream with envelope attributes `source: "model"` and `transactionId` set to the `generation` timestamp: + +1. **Start State (`state/discovery`):** Published at the registry level with `families.iot.source: "model"`, `families.iot.phase: "active"`, and `families.iot.active_count: 0`. +2. **Start, Count & Shard Scope Event (`events/discovery`):** Published at the registry level with `source: "model"`, `event_no: 0`, `family: "iot"`, `active_count` set to the size of the `Closure`, and optional `addrs` declaring the `Shard` for partial scans. +3. **Device Model Events (`events/discovery`):** When `depth` is `details` (or `parts`, or omitted), one event per device in the `Closure` (`event_no: 1..N`) is published with `source: "model"`, `family: "iot"`, `addr` set to the device ID, and `model` populated with the device's [`metadata`](../../gencode/docs/metadata.html) object. When `depth` is `entries`, individual device events are omitted and only the `event_no: 0` count is emitted. +4. **Stop State (`state/discovery`):** Published at the registry level with `families.iot.source: "model"`, `families.iot.phase: "stopped"`, and `families.iot.active_count` set to the total number of discovered devices in the `Closure`. If an unrecoverable error occurs during the scan (such as malformed cloud metadata), UDMIS fails fast and publishes a terminal `phase: "stopped"` state containing a `status` error entry (`category: "discovery.family.scan"`, `level: 500`). + +Because `state/discovery` and `events/discovery` may traverse independent transport queues, consumers that subscribe to both streams should treat batch completion as an **unordered barrier** (`event_no = 0` received AND all `1..active_count` device events received AND, if monitoring state, `phase == "stopped"` received) rather than assuming strict message arrival order. + diff --git a/docs/specs/uufi.md b/docs/specs/uufi.md index 0617f6ddd9..1124ba74bb 100644 --- a/docs/specs/uufi.md +++ b/docs/specs/uufi.md @@ -50,7 +50,7 @@ Format: `scheme://[user@]host[:port][/path]` - The connection MUST utilize SSL/TLS with mutual certificate-based authentication (mTLS) where applicable. - Implementations MUST treat `mqtts://` with the same routing, parsing, and topic mapping rules as `mqtt://`, differing only in the enforcement of SSL/TLS. - **Topic Structure:** `[/{prefix}]/uufi/[r/{deviceRegistryId}/[d/{deviceId}/]]c/{subType}/{subFolder}` - - **Rigid Segment Presence:** Every UUFI topic path MUST include both a subtype (`subType`) and a subfolder (`subFolder`) segment without exception. Topic paths MUST be formatted strictly as `[/{prefix}]/uufi/c/{subType}/{subFolder}` (for common channels) or `[/{prefix}]/uufi/r/{deviceRegistryId}/d/{deviceId}/c/{subType}/{subFolder}` (for registry/device scoped channels). Omitting the subfolder segment or truncating suffixes (e.g. `/c/{subType}`) is strictly prohibited. + - **Rigid Segment Presence:** Every UUFI topic path MUST include both a subtype (`subType`) and a subfolder (`subFolder`) segment without exception. Topic paths MUST be formatted strictly as `[/{prefix}]/uufi/c/{subType}/{subFolder}` (for common channels), `[/{prefix}]/uufi/r/{deviceRegistryId}/c/{subType}/{subFolder}` (for registry-scoped channels such as Site Model Discovery), or `[/{prefix}]/uufi/r/{deviceRegistryId}/d/{deviceId}/c/{subType}/{subFolder}` (for device-scoped channels). Omitting the subfolder segment or truncating suffixes (e.g. `/c/{subType}`) is strictly prohibited. - **Handshake Subfolder Suffix:** For standard registry-less handshakes, the subfolder segment MUST be explicitly set to `"udmi"`. Thus, the handshake topics MUST be exactly `[/{prefix}]/uufi/c/state/udmi` and `[/{prefix}]/uufi/c/config/udmi`. - **Prohibition of Alternative Topic Structures:** Principal-scoped patterns (such as `[/{prefix}]/uufi/p/{principal}/...`) and any other custom or arbitrary routing hierarchies are strictly prohibited. All system components MUST standardize exclusively on the common and registry-scoped topic channels to ensure interoperability. - **Prefix Isolation:** The `prefix` MUST be used to isolate different environments sharing the same broker. If provided, it MUST be the leading part of the topic path (e.g. matching all segments of the path provided in the connection string). Implementations MUST support multi-segment prefixes and MUST NOT omit the prefix if provided in the connection string. All active subscriptions MUST be scoped to the provided prefix to ensure environmental isolation. Prefix enforcement MUST be strict: implementations MUST NOT publish to or subscribe from topics outside their designated prefix tree. To avoid collisions and support standard broker authorization structures, implementations MUST construct unique MQTT Client IDs adhering to a standardized format: diff --git a/gencode/docs/config.html b/gencode/docs/config.html index 8378828bfd..9660a5d2d5 100644 --- a/gencode/docs/config.html +++ b/gencode/docs/config.html @@ -2148,6 +2148,61 @@

Must be one of:

+ + + + +
+
+
+

+ +

+
+ +
+
+ + Type: string
+

Optional source qualifier for the discovery scan (e.g., 'model')

+
+ + + + + +
diff --git a/gencode/docs/events.html b/gencode/docs/events.html index 618c455d31..9600e0ae9e 100644 --- a/gencode/docs/events.html +++ b/gencode/docs/events.html @@ -6623,6 +6623,47 @@

+

+ + + +
+
+
+

+ +

+
+ +
+
+ + Type: string
+

Optional source qualifier for the discovery scan (e.g., 'model')

+
+ + + + + +
@@ -6746,6 +6787,123 @@

+

+ + + +
+
+
+

+ +

+
+ +
+
+ + Type: integer
+

Total number of records expected for this active scan generation (only populated on the event_no=0 start marker event)

+
+ + + +

Value must be greater or equal to 0

+ + +
+
+
+
+
+
+
+

+ +

+
+ +
+
+ + Type: array of string
+

List of target addresses for a partial discovery scan shard (only populated on the event_no=0 start marker event)

+
+ + + + + + No Additional Items

Each item of this array must be:

+
+
+ + + Type: string
+ + + + + + + +
+
@@ -17951,16 +18109,18 @@

expr

Type: string

Virtual equipment mapping linking this local point to a remote point in another device

-
Must match regular expression: ^[-0-9a-zA-Z$]+:[a-z0-9_]+$ +Must match regular expression: .*[-_0-9a-zA-Z$]+:[a-z0-9_]+.*
-
Example:
+
Examples:

"VAV-3:supply_air_flowrate_sensor"
 
+
"VAV-101:zone_air_temp_sensor - VAV-102:zone_air_temp_sensor"
+
@@ -25538,6 +25698,27007 @@

"1"
 
+
+ + + + + + + + + + + + +
+
+
+

+ +

+
+ +
+
+ +

Metadata

Type: object
+

Device metadata model (for model discovery)

+
+ + No Additional Properties + + + + + + +
+
+
+

+ +

+
+ +
+
+ + Type: stringFormat: date-time
+

RFC 3339 timestamp UTC the data was generated

+
+ + + + + +
+
Example:
+
"2019-01-17T14:02:29.364Z"
+
+
+
+
+
+
+
+
+
+

+ +

+
+ +
+
+ + Type: string
+

Version of the UDMI schema for this file

+
+ + + + + + +
+
+
+
+
+
+
+

+ +

+
+ +
+
+ + Type: string
+

Original version of the UDMI schema for this file

+
+ + + + + + +
+
+
+
+
+
+
+

+ +

+
+ +
+
+ + Type: string
+

DEPRECATED

+
Must match regular expression: ^[0-9a-z]{8}$ + + + + + + +
+
+
+
+
+
+
+

+ +

+
+ +
+
+ +

Model Operation

Type: enum (of string)
+

Used for model update operations (so not a model proper), e.g. DELETE or BIND operations

+
+ +
+

Must be one of:

+
  • "ERROR"
  • "CREATE"
  • "READ"
  • "REPLY"
  • "UPDATE"
  • "DELETE"
  • "MODIFY"
  • "PREVIEW"
  • "BIND"
  • "UNBIND"
  • "BOUND"
  • "ALLOW"
  • "BLOCK"
+
+ + + + + + +
+
+
+
+
+
+
+

+ +

+
+ +
+
+ +

Cloud Model

Type: object
+

Information specific to how the device communicates with the cloud.

+
+ + No Additional Properties + + + + + + +
+
+
+

+ +

+
+ +
+
+ + Type: integer
+

Function version of this request

+
+ + + + + + +
+
+
+
+
+
+
+

+ +

+
+ +
+
+ + Type: enum (of string)
+

The key type used for cloud communication.

+
+

Must be one of:

+
  • "ES256"
  • "ES256_X509"
  • "RS256"
  • "RS256_X509"
+
+ + + + + + +
+
+
+
+
+
+
+

+ +

+
+ +
+
+ + Type: boolean
+

Indicates if the device private key is provided in the site model.

+
+ + + + + + +
+
+
+
+
+
+
+

+ +

+
+ +
+
+ + Type: enum (of string)
+

The cloud resource type

+
+

Must be one of:

+
  • "DIRECT"
  • "GATEWAY"
  • "FUNNEL"
  • "PROXIED"
  • "REGISTRY"
+
+ + + + + + +
+
+
+
+
+
+
+

+ +

+
+ +
+
+ +

Gateway Model

Type: object
+

Gateway Documentation

+
+ + No Additional Properties + + + + + + +
+
+
+

+ +

+
+ +
+
+ + Type: string
+

The device ID of the gateway the device is bound to

+
Must match regular expression: ^[A-Z]{2,6}-[1-9][0-9]*$ + + + + + +
+
Example:
+
"GAT-100"
+
+
+
+
+
+
+
+
+
+

+ +

+
+ +
+
+ +

Family Localnet Model

Type: object
+

The type of network

+
+ + No Additional Properties + + + + + + +
+
+
+

+ +

+
+ +
+
+ + Type: string
+

The address of a device on the fieldbus/local network

+
+ + + + + +
+
Examples:
+
"192.168.1.3"
+
+
"41489"
+
+
+
+
+
+
+
+
+
+

+ +

+
+ +
+
+ + Type: string
+

The slave unit ID of the device for protocol families like modbus

+
+ + + + + +
+
Examples:
+
"1"
+
+
"255"
+
+
+
+
+
+
+
+
+
+

+ +

+
+ +
+
+ + Type: string
+

The network designator for this device in the family-defined format

+
+ + + + + +
+
Examples:
+
"192.168.1.0/24"
+
+
"12732"
+
+
+
+
+
+
+
+
+
+

+ +

+
+ +
+
+ + Type: object
+ No Additional Properties + + + + + + +
+
+
+

+ +

+
+ +
+

+ +

+

All properties whose name matches the following regular expression must respect the following conditions

+ Property name regular expression: ^[a-z_]+$ +
+ + Type: string
+ + + + + + + +
+
+
+
+
+
+
+
+
+
+
+

+ +

+
+ +
+
+ + Type: string
+

Specifies that this is a shadow of the indicated device

+
+ + + + + + +
+
+
+
+
+
+
+

+ +

+
+ +
+
+ + Type: string
+

The device id of the node's parent

+
+ + + + + + +
+
+
+
+
+
+
+

+ +

+
+ +
+
+ + Type: boolean
+

If true, then the system should not try to validate the refs

+
+ + + + + + +
+
+
+
+
+
+
+

+ +

+
+ +
+
+ + Type: string
+

The family designator, used only when the entry is not keyed in a family map

+
+ + + + + +
+
Examples:
+
"ipv4"
+
+
"bacnet"
+
+
+
+
+
+
+
+
+
+
+
+
+
+

+ +

+
+ +
+
+ + Type: array of string
+

An array of all the device IDs which are bound to the device

+
+ + + + + + No Additional Items

Each item of this array must be:

+
+
+ + + Type: string
+Must match regular expression: ^[A-Z]{2,6}-[1-9][0-9]*$ + + + + + + +
+

+
Example:
+
[
+    "AHU-22"
+]
+
+
+
+
+
+
+
+
+
+
+
+
+
+

+ +

+
+ +
+
+ +

Config Cloud Model

Type: object
+

Information specific to how the device communicates with the cloud.

+
+ + No Additional Properties + + + + + + +
+
+
+

+ +

+
+ +
+
+ + Type: string
+

Config file to use. Within the config directory in the device's metadata directory

+
+ + + + + + +
+
+
+
+
+
+
+
+
+
+
+

+ +

+
+ +
+
+ + Type: boolean
+ + + + + + + +
+
+
+
+
+
+
+

+ +

+
+ +
+
+ + Type: string
+ + + + + + + +
+
+
+
+
+
+
+

+ +

+
+ +
+
+ + Type: string
+ + + + + + + +
+
+
+
+
+
+
+

+ +

+
+ +
+
+ + Type: stringFormat: date-time
+ + + + + + + +
+
+
+
+
+
+
+

+ +

+
+ +
+
+ + Type: stringFormat: date-time
+ + + + + + + +
+
+
+
+
+
+
+

+ +

+
+ +
+
+ + Type: stringFormat: date-time
+ + + + + + + +
+
+
+
+
+
+
+

+ +

+
+ +
+
+ + Type: stringFormat: date-time
+ + + + + + + +
+
+
+
+
+
+
+

+ +

+
+ +
+
+ + Type: string
+ + + + + + + +
+
+
+
+
+
+
+

+ +

+
+ +
+
+ +

Model Operation

Type: enum (of string)
+ + +
+

Must be one of:

+
  • "ERROR"
  • "CREATE"
  • "READ"
  • "REPLY"
  • "UPDATE"
  • "DELETE"
  • "MODIFY"
  • "PREVIEW"
  • "BIND"
  • "UNBIND"
  • "BOUND"
  • "ALLOW"
  • "BLOCK"
+
+ + + + + + +
+
+
+
+
+
+
+

+ +

+
+ +
+
+ + Type: string
+

Strigified version of the metadata object, used for internal backend processing

+
+ + + + + + +
+
+
+
+
+
+
+

+ +

+
+ +
+
+ + Type: object
+

If operating on the entire registry, then this contains results for multiple devices (batched operation).

+
+ + + + + + +
+
+
+

+ +

+
+ +
+

+ +

+

All properties whose name matches the following regular expression must respect the following conditions

+ Property name regular expression: ^[-._a-zA-Z0-9]+$ +
+ + Type: object
+ + + + + + + +
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+

+ +

+
+ +
+
+ +

System Model

Type: object
+

High-level system information about the device. System Model Documentation

+
+ + No Additional Properties + + + + + + +
+
+
+

+ +

+
+ +
+
+ + Type: string
+

Friendly name describing this device

+
+ + + + + + +
+
+
+
+
+
+
+

+ +

+
+ +
+
+ + Type: string
+

Generic human-readable text describing the device

+
+ + + + + + +
+
+
+
+
+
+
+

+ +

+
+ +
+
+ + Type: string
+

Version of schema supported by the device

+
+ + + + + +
+
Example:
+
"1.5.0"
+
+
+
+
+
+
+
+
+
+

+ +

+
+ +
+
+ + Type: string
+

Aggregation of spaces, partial spaces or other zones that the device is serving

+
Must match regular expression: ^[-_a-zA-Z0-9]+$ + + + + + +
+
Examples:
+
"Zone1"
+
+
"Kitchen_101"
+
+
+
+
+
+
+
+
+
+

+ +

+
+ +
+
+ + Type: string
+

The description of what kind of node this is.

+
Must match regular expression: ^[a-z][_a-z0-9]+$ + + + + + +
+
Examples:
+
"vitual_device"
+
+
"field_group"
+
+
"field_controller"
+
+
"network_scan_agent"
+
+
+
+
+
+
+
+
+
+

+ +

+
+ +
+
+ + Type: array
+

Tags associated with the device

+
+ + + + + +

All items must be unique

No Additional Items

Each item of this array must be:

+
+
+ + + Type: object
+Must match regular expression: ^[a-z][_a-z0-9]+$ + + + + + + +
+

+
Examples:
+
[
+    "lighting"
+]
+
+
[
+    "energy",
+    "building",
+    "vendor_managed"
+]
+
+
+
+
+
+
+
+
+
+

+ +

+
+ +
+
+ + Type: object
+

Properties of the expected physical location of the device

+
No Additional Properties + + + + + + +
+
+
+

+ +

+
+ +
+
+ + Type: string
+

The site name according to the site model in which the device is installed in

+
Must match regular expression: ^[A-Z]{2}-[A-Z]{3,4}-[A-Z0-9]{2,9}$ + + + + + +
+
Example:
+
"US-SFO-XYY"
+
+
+
+
+
+
+
+
+
+

+ +

+
+ +
+
+ + Type: string
+

The reference of the panel where the device is installed in

+
Must match regular expression: ^[-_a-zA-Z0-9]+$ + + + + + +
+
Examples:
+
"Panel123"
+
+
"CTRP-567"
+
+
+
+
+
+
+
+
+
+

+ +

+
+ +
+
+ + Type: string
+Must match regular expression: ^[A-Z0-9-]+$ + + + + + + +
+
+
+
+
+
+
+

+ +

+
+ +
+
+ + Type: string
+Must match regular expression: ^[-_a-zA-Z0-9]+$ + + + + + + +
+
+
+
+
+
+
+

+ +

+
+ +
+
+ + Type: string
+

Name of floor level

+
Must match regular expression: ^[A-Z0-9-]+$ + + + + + +
+
Examples:
+
"1"
+
+
"1A"
+
+
"G"
+
+
"B1M"
+
+
"RF"
+
+
+
+
+
+
+
+
+
+

+ +

+
+ +
+
+ + Type: integer
+

Sequential integer representation for a floor, primarily for comparisons when non integer floors are used, e.g. 1 and 1M

+
+ + + + + + +
+
+
+
+
+
+
+

+ +

+
+ +
+
+ + Type: object
+ No Additional Properties + + + + + + +
+
+
+

+ +

+
+ +
+
+ + Type: number
+

The x coordinate of the device location in a project specific coordinate system

+
+ + + + + + +
+
+
+
+
+
+
+

+ +

+
+ +
+
+ + Type: number
+

The y coordinate of the device location in a project specific coordinate system

+
+ + + + + + +
+
+
+
+
+
+
+

+ +

+
+ +
+
+ + Type: number
+

The z (height) coordinate of the device location in a project specific coordinate system

+
+ + + + + + +
+
+
+
+
+
+
+
+
+
+
+

+ +

+
+ +
+
+ + Type: object
+ No Additional Properties + + + + + + +
+
+
+

+ +

+
+ +
+
+ + Type: number
+

The latitude coordinate of the device in the WGS84 coordinate system

+
+ + + + + + +
+
+
+
+
+
+
+

+ +

+
+ +
+
+ + Type: number
+

The longitude coordinate of the device in the WGS84 coordinate system

+
+ + + + + + +
+
+
+
+
+
+
+

+ +

+
+ +
+
+ + Type: number
+

The altitude coordinate of the device in meters

+
+ + + + + + +
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+

+ +

+
+ +
+
+ + Type: string
+

The serial number of the physical device

+
+ + + + + +
+
Examples:
+
"A1B2C3D4"
+
+
"00001"
+
+
+
+
+
+
+
+
+
+

+ +

+
+ +
+
+ +

SystemHardware

Type: object
+

A collection of fields which describe the physical hardware of the device.

+
+ + No Additional Properties + + + + + + +
+
+
+

+ +

+
+ +
+
+ + Type: string
+

The manufacturer of the device

+
+ + + + + +
+
Example:
+
"Google"
+
+
+
+
+
+
+
+
+
+

+ +

+
+ +
+
+ + Type: string
+

The model of the device

+
+ + + + + +
+
Examples:
+
"Pixel 5"
+
+
"Pixel 6"
+
+
"Pixel 6 Pro"
+
+
+
+
+
+
+
+
+
+

+ +

+
+ +
+
+ + Type: string
+

A stock keeping unit which identifies the unique composition of a device, for example those with different hardware configurations

+
+ + + + + +
+
Examples:
+
"GF5KQ"
+
+
"4215612"
+
+
+
+
+
+
+
+
+
+

+ +

+
+ +
+
+ + Type: string
+

Hardware revision of the device

+
+ + + + + +
+
Examples:
+
"A"
+
+
"B"
+
+
"1"
+
+
+
+
+
+
+
+
+
+
+
+
+
+

+ +

+
+ +
+
+ + Type: object
+

A collection of items which can be used to describe version of software running on a device

+
No Additional Properties + + + + + +
+
Examples:
+
"firmware"
+
+
"os"
+
+
"driver"
+
+
+
+
+
+

+ +

+
+ +
+

+ +

+

All properties whose name matches the following regular expression must respect the following conditions

+ Property name regular expression: ^[a-z_]+$ +
+ + Type: string
+ + + + + + + +
+
+
+
+
+
+
+
+
+
+
+

+ +

+
+ +
+
+ + Type: object
+

Information used to print a physical QR code label.

+
No Additional Properties + + + + + + +
+
+
+

+ +

+
+ +
+
+ + Type: object
+ No Additional Properties + + + + + + +
+
+
+

+ +

+
+ +
+
+ + Type: string
+Must match regular expression: ^[a-z]+://[-0-9a-zA-Z_$]+$ + + + + + + +
+
+
+
+
+
+
+

+ +

+
+ +
+
+ + Type: string
+Must match regular expression: ^[A-Z]{2}-[A-Z]{3,4}-[A-Z0-9]{2,9}$ + + + + + + +
+
+
+
+
+
+
+

+ +

+
+ +
+
+ + Type: string
+Must match regular expression: ^[A-Z]{2,6}-[1-9][0-9]*$ + + + + + + +
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+

+ +

+
+ +
+
+ + Type: object
+ No Additional Properties + + + + + + +
+
+
+

+ +

+
+ +
+

+ +

+

All properties whose name matches the following regular expression must respect the following conditions

+ Property name regular expression: ^[a-z_]+$ +
+ + Type: string
+ + + + + + + +
+
+
+
+
+
+
+
+
+
+
+

+ +

+
+ +
+
+ + Type: integer Default: 300
+

The minimum loglevel for reporting log messages below which log entries should not be sent. Default to 300.

+
+ + + +

Value must be greater or equal to 100 and lesser or equal to 800

+ + +
+
+
+
+
+
+
+

+ +

+
+ +
+
+ + Type: integer
+

The rate at which the system should send system event updates. 0 indicates no updates.

+
+ + + +

Value must be greater or equal to 0 and lesser or equal to 86400

+ + +
+
+
+
+
+
+
+
+
+
+
+

+ +

+
+ +
+
+ +

Externals Model

Type: object
+

Information specific to how the device links with external models

+
+ + No Additional Properties + + + + + + +
+
+
+

+ +

+
+ +
+

+ +

+

All properties whose name matches the following regular expression must respect the following conditions

+ Property name regular expression: ^[a-z]+(_[a-z]+)*$ +
+ +

Link Externals Model

Type: object
+

Information about how this device links to a specific external model

+
+ + No Additional Properties + + + + + + +
+
+
+

+ +

+
+ +
+
+ + Type: string
+

Id of this device in the external model namespace

+
+ + + + + + +
+
+
+
+
+
+
+

+ +

+
+ +
+
+ + Type: string
+

Type of this device in the external model namespace

+
+ + + + + + +
+
+
+
+
+
+
+

+ +

+
+ +
+
+ + Type: string
+

Etag for referencing this external entity

+
Must match regular expression: ^[0-9a-f]+$ + + + + + + +
+
+
+
+
+
+
+

+ +

+
+ +
+
+ + Type: string
+

Descriptive label for this entity

+
+ + + + + + +
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+

+ +

+
+ +
+
+ +

Entity Relationships

Type: object
+

Entity relationships to other devices, keyed by target device ID

+
+ + + + + + + + +
+
+
+

+ +

+
+ +
+

+ +

+

All properties whose name matches the following regular expression must respect the following conditions

+ Property name regular expression: ^[-0-9a-zA-Z]+$ +
+ + Type: object
+ + + + + + + +
+
+
+

+ +

+
+ +
+

+ +

+

All properties whose name matches the following regular expression must respect the following conditions

+ Property name regular expression: ^[-0-9a-zA-Z_]+$ +
+ + Type: array of object
+ + + + + + + No Additional Items

Each item of this array must be:

+
+
+ + + Type: object
+ + + + + + + +
+
+
+

+ +

+
+ +
+
+ + Type: array of string
+ + + + + + + No Additional Items

Each item of this array must be:

+
+
+ + + Type: string
+ + + + + + + +
+

+
Example:
+
[
+    "primary"
+]
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+

+ +

+
+ +
+
+ +

Gateway Model

Type: object
+

Gateway Documentation

+
+ + No Additional Properties + + + + + + +
+
+
+

+ +

+
+ +
+
+ + Type: string
+

The device ID of the gateway the device is bound to

+
Must match regular expression: ^[A-Z]{2,6}-[1-9][0-9]*$ + + + + + +
+
Example:
+
"GAT-100"
+
+
+
+
+
+
+
+
+
+

+ +

+
+ +
+
+ +

Family Localnet Model

Type: object
+

The type of network

+
+ + No Additional Properties + + + + + + +
+
+
+

+ +

+
+ +
+
+ + Type: string
+

The address of a device on the fieldbus/local network

+
+ + + + + +
+
Examples:
+
"192.168.1.3"
+
+
"41489"
+
+
+
+
+
+
+
+
+
+

+ +

+
+ +
+
+ + Type: string
+

The slave unit ID of the device for protocol families like modbus

+
+ + + + + +
+
Examples:
+
"1"
+
+
"255"
+
+
+
+
+
+
+
+
+
+

+ +

+
+ +
+
+ + Type: string
+

The network designator for this device in the family-defined format

+
+ + + + + +
+
Examples:
+
"192.168.1.0/24"
+
+
"12732"
+
+
+
+
+
+
+
+
+
+

+ +

+
+ +
+
+ + Type: object
+ No Additional Properties + + + + + + +
+
+
+

+ +

+
+ +
+

+ +

+

All properties whose name matches the following regular expression must respect the following conditions

+ Property name regular expression: ^[a-z_]+$ +
+ + Type: string
+ + + + + + + +
+
+
+
+
+
+
+
+
+
+
+

+ +

+
+ +
+
+ + Type: string
+

Specifies that this is a shadow of the indicated device

+
+ + + + + + +
+
+
+
+
+
+
+

+ +

+
+ +
+
+ + Type: string
+

The device id of the node's parent

+
+ + + + + + +
+
+
+
+
+
+
+

+ +

+
+ +
+
+ + Type: boolean
+

If true, then the system should not try to validate the refs

+
+ + + + + + +
+
+
+
+
+
+
+

+ +

+
+ +
+
+ + Type: string
+

The family designator, used only when the entry is not keyed in a family map

+
+ + + + + +
+
Examples:
+
"ipv4"
+
+
"bacnet"
+
+
+
+
+
+
+
+
+
+
+
+
+
+

+ +

+
+ +
+
+ + Type: array of string
+

An array of all the device IDs which are bound to the device

+
+ + + + + + No Additional Items

Each item of this array must be:

+
+
+ + + Type: string
+Must match regular expression: ^[A-Z]{2,6}-[1-9][0-9]*$ + + + + + + +
+

+
Example:
+
[
+    "AHU-22"
+]
+
+
+
+
+
+
+
+
+
+
+
+
+
+

+ +

+
+ +
+
+ +

Discovery Model

Type: object
+

Discovery target parameters

+
+ + No Additional Properties + + + + + + +
+
+
+

+ +

+
+ +
+
+ + Type: object
+ + + + + + + +
+
+
+

+ +

+
+ +
+

+ +

+

All properties whose name matches the following regular expression must respect the following conditions

+ Property name regular expression: ^[a-z][a-z0-9]*(_[a-z0-9]+)*$ +
+ +

Family Discovery Model

Type: object
+ + + No Additional Properties + + + + + + +
+
+
+

+ +

+
+ +
+
+ + Type: stringFormat: date-time
+

Generational marker for controlling discovery

+
+ + + + + +
+
Example:
+
"2018-08-26T21:00:13Z"
+
+
+
+
+
+
+
+
+
+

+ +

+
+ +
+
+ + Type: integer
+

Period, in seconds, for automatic scanning

+
+ + + +

Value must be greater or equal to 0

+ + +
+
+
+
+
+
+
+

+ +

+
+ +
+
+ + Type: integer
+

Scan duration, in seconds

+
+ + + +

Value must be greater or equal to 0

+ + +
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+

+ +

+
+ +
+
+ +

Localnet Model

Type: object
+

Used to describe device local network parameters

+
+ + No Additional Properties + + + + + + +
+
+
+

+ +

+
+ +
+
+ + Type: object
+

Parent device to which the device is physically connected

+
No Additional Properties + + + + + + +
+
+
+

+ +

+
+ +
+
+ + Type: string
+

ID of the parent device to which the device is physically connected

+
Must match regular expression: ^[A-Z]{2,6}-[1-9][0-9]*$ + + + + + +
+
Examples:
+
"DDC-1"
+
+
"CGW-2"
+
+
+
+
+
+
+
+
+
+

+ +

+
+ +
+
+ + Type: string
+

Connection family/protocol of the parent device to which the device is physically connected

+
+ + + + + +
+
Examples:
+
"bacnet_mstp"
+
+
"modbus_tcp"
+
+
"mqtt"
+
+
+
+
+
+
+
+
+
+
+
+
+
+

+ +

+
+ +
+
+ + Type: object
+ No Additional Properties + + + + + +
+
Examples:
+
"bacnet"
+
+
"modbus"
+
+
"mbus"
+
+
"ether"
+
+
"ipv4"
+
+
"ipv6"
+
+
"iot"
+
+
+
+
+
+

+ +

+
+ +
+

+ +

+

All properties whose name matches the following regular expression must respect the following conditions

+ Property name regular expression: ^[a-z0-9-]+$ +
+ +

Family Localnet Model

Type: object
+

The type of network

+
+ + No Additional Properties + + + + + + +
+
+
+

+ +

+
+ +
+
+ + Type: string
+

The address of a device on the fieldbus/local network

+
+ + + + + +
+
Examples:
+
"192.168.1.3"
+
+
"41489"
+
+
+
+
+
+
+
+
+
+

+ +

+
+ +
+
+ + Type: string
+

The slave unit ID of the device for protocol families like modbus

+
+ + + + + +
+
Examples:
+
"1"
+
+
"255"
+
+
+
+
+
+
+
+
+
+

+ +

+
+ +
+
+ + Type: string
+

The network designator for this device in the family-defined format

+
+ + + + + +
+
Examples:
+
"192.168.1.0/24"
+
+
"12732"
+
+
+
+
+
+
+
+
+
+

+ +

+
+ +
+
+ + Type: object
+ No Additional Properties + + + + + + +
+
+
+

+ +

+
+ +
+

+ +

+

All properties whose name matches the following regular expression must respect the following conditions

+ Property name regular expression: ^[a-z_]+$ +
+ + Type: string
+ + + + + + + +
+
+
+
+
+
+
+
+
+
+
+

+ +

+
+ +
+
+ + Type: string
+

Specifies that this is a shadow of the indicated device

+
+ + + + + + +
+
+
+
+
+
+
+

+ +

+
+ +
+
+ + Type: string
+

The device id of the node's parent

+
+ + + + + + +
+
+
+
+
+
+
+

+ +

+
+ +
+
+ + Type: boolean
+

If true, then the system should not try to validate the refs

+
+ + + + + + +
+
+
+
+
+
+
+

+ +

+
+ +
+
+ + Type: string
+

The family designator, used only when the entry is not keyed in a family map

+
+ + + + + +
+
Examples:
+
"ipv4"
+
+
"bacnet"
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+

+ +

+
+ +
+
+ + Type: object
+ No Additional Properties + + + + + + +
+
+
+

+ +

+
+ +
+

+ +

+

All properties whose name matches the following regular expression must respect the following conditions

+ Property name regular expression: ^[a-z][a-z0-9]*(_[a-z0-9]+)*$ +
+ +

Family Localnet Model

Type: object
+

The type of network

+
+ + No Additional Properties + + + + + + +
+
+
+

+ +

+
+ +
+
+ + Type: string
+

The address of a device on the fieldbus/local network

+
+ + + + + +
+
Examples:
+
"192.168.1.3"
+
+
"41489"
+
+
+
+
+
+
+
+
+
+

+ +

+
+ +
+
+ + Type: string
+

The slave unit ID of the device for protocol families like modbus

+
+ + + + + +
+
Examples:
+
"1"
+
+
"255"
+
+
+
+
+
+
+
+
+
+

+ +

+
+ +
+
+ + Type: string
+

The network designator for this device in the family-defined format

+
+ + + + + +
+
Examples:
+
"192.168.1.0/24"
+
+
"12732"
+
+
+
+
+
+
+
+
+
+

+ +

+
+ +
+
+ + Type: object
+ No Additional Properties + + + + + + +
+
+
+

+ +

+
+ +
+

+ +

+

All properties whose name matches the following regular expression must respect the following conditions

+ Property name regular expression: ^[a-z_]+$ +
+ + Type: string
+ + + + + + + +
+
+
+
+
+
+
+
+
+
+
+

+ +

+
+ +
+
+ + Type: string
+

Specifies that this is a shadow of the indicated device

+
+ + + + + + +
+
+
+
+
+
+
+

+ +

+
+ +
+
+ + Type: string
+

The device id of the node's parent

+
+ + + + + + +
+
+
+
+
+
+
+

+ +

+
+ +
+
+ + Type: boolean
+

If true, then the system should not try to validate the refs

+
+ + + + + + +
+
+
+
+
+
+
+

+ +

+
+ +
+
+ + Type: string
+

The family designator, used only when the entry is not keyed in a family map

+
+ + + + + +
+
Examples:
+
"ipv4"
+
+
"bacnet"
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+

+ +

+
+ +
+
+ +

Testing Model

Type: object
+

Testing target parameters

+
+ + No Additional Properties + + + + + + +
+
+
+

+ +

+
+ +
+
+ + Type: boolean
+ + + + + + + +
+
+
+
+
+
+
+

+ +

+
+ +
+
+ + Type: object
+ No Additional Properties + + + + + + +
+
+
+

+ +

+
+ +
+

+ +

+

All properties whose name matches the following regular expression must respect the following conditions

+ Property name regular expression: ^[a-z][a-z0-9]*(_[a-z0-9]+)*$ +
+ +

Target Testing Model

Type: object
+ + + No Additional Properties + + + + + + +
+
+
+

+ +

+
+ +
+
+ + Type: string
+

Point name used for testing

+
+ + + + + + +
+
+
+
+
+
+
+

+ +

+
+ +
+
+ + Type: object
+

Value used for testing

+
+ + + + + + +
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+

+ +

+
+ +
+
+ + Type: object
+ No Additional Properties + + + + + + +
+
+
+

+ +

+
+ +
+

+ +

+

All properties whose name matches the following regular expression must respect the following conditions

+ Property name regular expression: ^[a-z][a-z0-9]*(_[a-z0-9]+)*$ +
+ +

Blob Update Testing Model

Type: object
+ + + No Additional Properties + + + + + + +
+
+
+

+ +

+
+ +
+
+ + Type: string
+

Blob name for the payload

+
+ + + + + + +
+
+
+
+
+
+
+

+ +

+
+ +
+
+ + Type: string
+

URL for OTA update

+
+ + + + + + +
+
+
+
+
+
+
+

+ +

+
+ +
+
+ + Type: string
+

SHA256 hash of the payload

+
+ + + + + + +
+
+
+
+
+
+
+

+ +

+
+ +
+
+ + Type: string
+

Expected software version

+
+ + + + + + +
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+

+ +

+
+ +
+
+ +

Testing Model

Type: object
+

Model of supported features

+
+ + No Additional Properties + + + + + + +
+
+
+

+ +

+
+ +
+

+ +

+

All properties whose name matches the following regular expression must respect the following conditions

+ Property name regular expression: ^[._a-zA-Z]+$ +
+ +

Feature Discovery

Type: object
+

Object representation for for a single feature

+
+ + No Additional Properties + + + + + + +
+
+
+

+ +

+
+ +
+
+ +

FeatureStage

Type: enum (of string)
+

Stage of a feature implemenation

+
+ +
+

Must be one of:

+
  • "disabled"
  • "alpha"
  • "preview"
  • "beta"
  • "stable"
+
+ + + + + + +
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+

+ +

+
+ +
+
+ +

Alarmset Model

Type: object
+

Alarmset representing the abstract system expectation for what the device should be doing, and how it should be configured and operated. This block specifies the expected alarms that a device holds

+
+ + No Additional Properties + + + + + + +
+
+
+

+ +

+
+ +
+
+ + Type: object
+

Information about a specific alarm name of the device.

+
No Additional Properties + + + + + + +
+
+
+

+ +

+
+ +
+

+ +

+

All properties whose name matches the following regular expression must respect the following conditions

+ Property name regular expression: ^[a-z][a-z0-9]*(_[a-z0-9]+)*$ +
+ +

Alarm Alarmset Model

Type: object
+

Information about a specific alarm name of the device.

+
+ + No Additional Properties + + + + + + +
+
+
+

+ +

+
+ +
+
+ + Type: string
+

Expected unit configuration for the alarm

+
+ + + + + + +
+
+
+
+
+
+
+

+ +

+
+ +
+
+ + Type: enum (of string)
+

Expected data type for the alarm

+
+

Must be one of:

+
  • "string"
  • "boolean"
  • "number"
+
+ + + + + + +
+
+
+
+
+
+
+

+ +

+
+ +
+
+ + Type: string
+

Detailed description of this alarm

+
+ + + + + + +
+
+
+
+
+
+
+

+ +

+
+ +
+
+ + Type: boolean
+

Indicates if this alarm is writable (else read-only)

+
+ + + + + + +
+
+
+
+
+
+
+

+ +

+
+ +
+
+ + Type: object
+

Represents the expected baseline value of the alarm

+
+ + + + + +
+
Example:
+
22
+
+
+
+
+
+
+
+
+
+

+ +

+
+ +
+
+ + Type: number
+

Maximum deviation from baseline_value

+
+ + + + + +
+
Example:
+
2
+
+
+
+
+
+
+
+
+
+

+ +

+
+ +
+
+ + Type: enum (of string)
+

Expected state when baseline_value is set as the set_value for this alarm the config message

+
+

Must be one of:

+
  • "applied"
  • "updating"
  • "overridden"
  • "invalid"
  • "failure"
+
+ + + + + + +
+
+
+
+
+
+
+

+ +

+
+ +
+
+ + Type: number
+

Represents the lower bound of the error threshold for a alarm

+
+ + + + + +
+
Example:
+
0
+
+
+
+
+
+
+
+
+
+

+ +

+
+ +
+
+ + Type: number
+

Represents the upper bound of the error threshold for a alarm

+
+ + + + + +
+
Example:
+
100
+
+
+
+
+
+
+
+
+
+

+ +

+
+ +
+
+ + Type: integer
+

Represents the limit in seconds that a alarm can be unchanged for

+
+ + + + + +
+
Example:
+
3600
+
+
+
+
+
+
+
+
+
+

+ +

+
+ +
+
+ + Type: number
+

Triggering threshold for partial cov update publishing

+
+ + + + + +
+
Example:
+
1
+
+
+
+
+
+
+
+
+
+

+ +

+
+ +
+
+ + Type: string
+

Mapping for the alarm to an internal resource (e.g. BACnet object reference)

+
+ + + + + +
+
Examples:
+
"AI3"
+
+
"400070"
+
+
+
+
+
+
+
+
+
+

+ +

+
+ +
+
+ + Type: object
+ No Additional Properties + + + + + + +
+
+
+

+ +

+
+ +
+

+ +

+

All properties whose name matches the following regular expression must respect the following conditions

+ Property name regular expression: ^[a-z_]+$ +
+ + Type: string
+ + + + + + + +
+
+
+
+
+
+
+
+
+
+
+

+ +

+
+ +
+
+ + Type: array
+

Tags assosciated with the alarm

+
+ + + + + +

All items must be unique

No Additional Items

Each item of this array must be:

+
+
+ + + Type: object
+Must match regular expression: ^[a-z0-9]+$ + + + + + + +
+

+
Examples:
+
[
+    "lighting"
+]
+
+
[
+    "energy",
+    "hvac"
+]
+
+
+
+
+
+
+
+
+
+

+ +

+
+ +
+
+ + Type: object
+

Collection of family alarm information

+
No Additional Properties + + + + + + +
+
+
+

+ +

+
+ +
+

+ +

+

All properties whose name matches the following regular expression must respect the following conditions

+ Property name regular expression: ^[-_.:/a-zA-Z0-9]+$ +
+ +

Ref Discovery

Type: object
+

Object representation for for a single point reference discovery

+
+ + No Additional Properties + + + + + + +
+
+
+

+ +

+
+ +
+
+ + Type: string
+

Point descriptor for this point

+
Must match regular expression: ^[a-z][a-z0-9]*(_[a-z0-9]+)*$ + + + + + + +
+
+
+
+
+
+
+

+ +

+
+ +
+
+ + Type: string
+

Friendly name for the reference, if known

+
+ + + + + + +
+
+
+
+
+
+
+

+ +

+
+ +
+
+ + Type: array of string
+

List of possible enumerated values for the point

+
+ + + + + + No Additional Items

Each item of this array must be:

+
+
+ + + Type: string
+ + + + + + + +
+
+
+
+
+
+
+
+
+

+ +

+
+ +
+
+ + Type: string
+

Current or default unit for this point

+
+ + + + + + +
+
+
+
+
+
+
+

+ +

+
+ +
+
+ + Type: string
+

Family reference for structures

+
+ + + + + + +
+
+
+
+
+
+
+

+ +

+
+ +
+
+ + Type: string
+

Current or default type for this point

+
+ + + + + + +
+
+
+
+
+
+
+

+ +

+
+ +
+
+ + Type: boolean
+

Indicates if this point is writable or not

+
+ + + + + + +
+
+
+
+
+
+
+

+ +

+
+ +
+
+ + Type: string
+

Human-readable description of this point

+
+ + + + + + +
+
+
+
+
+
+
+

+ +

+
+ +
+
+ +

Entry

Type: object
+ + + No Additional Properties + + + + + + +
+
+
+

+ +

+
+ +
+
+ + Type: string
+

A human-readable one-line description of the entry

+
+ + + + + +
+
Example:
+
"Point is not writable"
+
+
+
+
+
+
+
+
+
+

+ +

+
+ +
+
+ + Type: string
+

An optional extensive entry which can include more detail, e.g. a complete program stack-trace

+
+ + + + + + +
+
+
+
+
+
+
+

+ +

+
+ +
+
+ +

Category

Type: string
+ + +

+ +

+
+ + + Type: object
+Must match regular expression: ^system\.base\.start$ + + + + + + +
+ + + Type: object
+Must match regular expression: ^system\.base\.shutdown$ + + + + + + +
+ + + Type: object
+Must match regular expression: ^system\.base\.ready$ + + + + + + +
+ + + Type: object
+Must match regular expression: ^system\.config\.receive$ + + + + + + +
+ + + Type: object
+Must match regular expression: ^system\.config\.parse$ + + + + + + +
+ + + Type: object
+Must match regular expression: ^system\.config\.apply$ + + + + + + +
+ + + Type: object
+Must match regular expression: ^system\.auth\.login$ + + + + + + +
+ + + Type: object
+Must match regular expression: ^system\.auth\.logout$ + + + + + + +
+ + + Type: object
+Must match regular expression: ^system\.auth\.fail$ + + + + + + +
+ + + Type: object
+Must match regular expression: ^pointset\.point\.nominal$ + + + + + + +
+ + + Type: object
+Must match regular expression: ^pointset\.point\.applied$ + + + + + + +
+ + + Type: object
+Must match regular expression: ^pointset\.point\.updating$ + + + + + + +
+ + + Type: object
+Must match regular expression: ^pointset\.point\.overridden$ + + + + + + +
+ + + Type: object
+Must match regular expression: ^pointset\.point\.failure$ + + + + + + +
+ + + Type: object
+Must match regular expression: ^pointset\.point\.invalid$ + + + + + + +
+ + + Type: object
+Must match regular expression: ^localnet\.network$ + + + + + + +
+ + + Type: object
+Must match regular expression: ^localnet\.network\.connect$ + + + + + + +
+ + + Type: object
+Must match regular expression: ^gateway\.setup\.attach$ + + + + + + +
+ + + Type: object
+Must match regular expression: ^gateway\.proxy\.target$ + + + + + + +
+ + + Type: object
+Must match regular expression: ^gateway\.proxy\.connect$ + + + + + + +
+ + + Type: object
+Must match regular expression: ^discovery\.family\.scan$ + + + + + + +
+ + + Type: object
+Must match regular expression: ^discovery\.device\.enumerate$ + + + + + + +
+ + + Type: object
+Must match regular expression: ^discovery\.point\.describe$ + + + + + + +
+ + + Type: object
+Must match regular expression: ^mapping\.device\.apply$ + + + + + + +
+ + + Type: object
+Must match regular expression: ^blobset\.blob$ + + + + + + +
+ + + Type: object
+Must match regular expression: ^blobset\.blob\.update$ + + + + + + +
+ + + Type: object
+Must match regular expression: ^blobset\.blob\.receive$ + + + + + + +
+ + + Type: object
+Must match regular expression: ^blobset\.blob\.fetch$ + + + + + + +
+ + + Type: object
+Must match regular expression: ^blobset\.blob\.parse$ + + + + + + +
+ + + Type: object
+Must match regular expression: ^blobset\.blob\.apply$ + + + + + + +
+ + + Type: object
+Must match regular expression: ^blobset\.blob\.abort$ + + + + + + +
+ + + Type: object
+Must match regular expression: ^blobset\.blob\.rollback$ + + + + + + +
+ + + Type: object
+Must match regular expression: ^validation\.device\.receive$ + + + + + + +
+ + + Type: object
+Must match regular expression: ^validation\.device\.schema$ + + + + + + +
+ + + Type: object
+Must match regular expression: ^validation\.device\.content$ + + + + + + +
+ + + Type: object
+Must match regular expression: ^validation\.device\.multiple$ + + + + + + +
+ + + Type: object
+Must match regular expression: ^validation\.device\.extra$ + + + + + + +
+ + + Type: object
+Must match regular expression: ^validation\.summary\.report$ + + + + + + +
+ + + Type: object
+Must match regular expression: ^validation\.feature\.sequence$ + + + + + + +
+ + + Type: object
+Must match regular expression: ^validation\.feature\.schema$ + + + + + + +
+ + + Type: object
+Must match regular expression: ^validation\.feature\.capability$ + + + + + + +
+ + + Type: object
+Must match regular expression: ^device(\.[a-z]+((_[a-z]+))*)+$ + + + + + + +
+ + + + + + +
+
+
+
+
+
+
+

+ +

+
+ +
+
+ + Type: stringFormat: date-time
+

FC 3339 UTC timestamp the condition was triggered, or most recently updated

+
+ + + + + +
+
Example:
+
"2018-08-26T21:39:28.364Z"
+
+
+
+
+
+
+
+
+
+

+ +

+
+ +
+
+ + Type: integer
+

The status level should conform to the numerical Stackdriver LogEntry levels. The DEFAULT value of 0 is not allowed (lowest value is 100, maximum 800).

+
+ + + +

Value must be greater or equal to 100 and lesser or equal to 800 and a multiple of 1

+ +
+
Example:
+
600
+
+
+
+
+
+
+
+
+
+
+
+
+
+

+ +

+
+ +
+
+ +

Ancillary Properties

Type: object
+

Arbitrary blob of json associated with this point

+
+ + + + + + + + +
+
+
+
+
+
+
+

+ +

+
+ +
+
+ + Type: object
+ No Additional Properties + + + + + + +
+
+
+

+ +

+
+ +
+

+ +

+

All properties whose name matches the following regular expression must respect the following conditions

+ Property name regular expression: ^[a-z_]+$ +
+ + Type: string
+ + + + + + + +
+
+
+
+
+
+
+
+
+
+
+

+ +

+
+ +
+
+ + Type: object
+ + + + + + + +
+
+
+

+ +

+
+ +
+
+ +

Family Discovery

Type: object
+

Discovery information for a protocol family.

+
+ + No Additional Properties + + + + + + +
+
+
+

+ +

+
+ +
+
+ + Type: string
+

Device addr in the namespace of the given family

+
Must match regular expression: ^[-_.:0-9A-Za-z]+$ + + + + + + +
+
+
+
+
+
+
+

+ +

+
+ +
+
+ + Type: integer
+

Port number for the family connection (e.g. UDP port for BACnet/IP)

+
+ + + + + + +
+
+
+
+
+
+
+

+ +

+
+ +
+
+ + Type: string
+

Point reference in the namespace of the given family

+
Must match regular expression: ^[-_.:/#0-9A-Za-z]+$ + + + + + + +
+
+
+
+
+
+
+
+
+
+
+

+ +

+
+ +
+
+ + Type: object
+

Reference links to alternate families

+
No Additional Properties + + + + + + +
+
+
+

+ +

+
+ +
+

+ +

+

All properties whose name matches the following regular expression must respect the following conditions

+ Property name regular expression: ^[a-z][a-z0-9]*(_[a-z0-9]+)*$ +
+ +

Family Discovery

Type: object
+

Discovery information for a protocol family.

+
+ + No Additional Properties + + + + + + +
+
+
+

+ +

+
+ +
+
+ + Type: string
+

Device addr in the namespace of the given family

+
Must match regular expression: ^[-_.:0-9A-Za-z]+$ + + + + + + +
+
+
+
+
+
+
+

+ +

+
+ +
+
+ + Type: integer
+

Port number for the family connection (e.g. UDP port for BACnet/IP)

+
+ + + + + + +
+
+
+
+
+
+
+

+ +

+
+ +
+
+ + Type: string
+

Point reference in the namespace of the given family

+
Must match regular expression: ^[-_.:/#0-9A-Za-z]+$ + + + + + + +
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+

+ +

+
+ +
+
+ + Type: boolean
+ + + + + + + +
+
+
+
+
+
+
+

+ +

+
+ +
+
+ + Type: boolean
+ + + + + + + +
+
+
+
+
+
+
+

+ +

+
+ +
+
+ + Type: integer
+

Minimum time between sample updates for the device (including complete and COV updates). Updates more frequent than this should be coalesced into one update.

+
+ + + +

Value must be greater or equal to 0 and lesser or equal to 86400

+ + +
+
+
+
+
+
+
+

+ +

+
+ +
+
+ + Type: integer
+

Maximum time between samples for the device to send out a complete update. It can send out updates more frequently than this. Default to 600.

+
+ + + +

Value must be greater or equal to 1 and lesser or equal to 86400

+ + +
+
+
+
+
+
+
+
+
+
+
+

+ +

+
+ +
+
+ +

Pointset Model

Type: object
+

Pointset representing the abstract system expectation for what the device should be doing, and how it should be configured and operated. This block specifies the expected points that a device holds

+
+ + No Additional Properties + + + + + + +
+
+
+

+ +

+
+ +
+
+ + Type: object
+

Information about a specific point name of the device.

+
No Additional Properties + + + + + + +
+
+
+

+ +

+
+ +
+

+ +

+

All properties whose name matches the following regular expression must respect the following conditions

+ Property name regular expression: ^[a-z][a-z0-9]*(_[a-z0-9]+)*$ +
+ +

Point Pointset Model

Type: object
+

Information about a specific point name of the device.

+
+ + No Additional Properties + + + + + + +
+
+
+

+ +

+
+ +
+
+ + Type: string
+

Expected unit configuration for the point

+
+ + + + + + +
+
+
+
+
+
+
+

+ +

+
+ +
+
+ + Type: string
+

Actual input unit that is sent from the device

+
+ + + + + + +
+
+
+
+
+
+
+

+ +

+
+ +
+
+ + Type: enum (of string)
+

Expected data type for the point

+
+

Must be one of:

+
  • "string"
  • "boolean"
  • "number"
+
+ + + + + + +
+
+
+
+
+
+
+

+ +

+
+ +
+
+ + Type: string
+

Detailed description of this point

+
+ + + + + + +
+
+
+
+
+
+
+

+ +

+
+ +
+
+ + Type: boolean
+

Indicates if this point is writable (else read-only)

+
+ + + + + + +
+
+
+
+
+
+
+

+ +

+
+ +
+
+ + Type: object
+

Represents the expected baseline value of the point

+
+ + + + + +
+
Example:
+
22
+
+
+
+
+
+
+
+
+
+

+ +

+
+ +
+
+ + Type: number
+

Maximum deviation from baseline_value

+
+ + + + + +
+
Example:
+
2
+
+
+
+
+
+
+
+
+
+

+ +

+
+ +
+
+ + Type: enum (of string)
+

Expected state when baseline_value is set as the set_value for this point the config message

+
+

Must be one of:

+
  • "applied"
  • "updating"
  • "overridden"
  • "invalid"
  • "failure"
+
+ + + + + + +
+
+
+
+
+
+
+

+ +

+
+ +
+
+ + Type: number
+

Represents the lower bound of the error threshold for a point

+
+ + + + + +
+
Example:
+
0
+
+
+
+
+
+
+
+
+
+

+ +

+
+ +
+
+ + Type: number
+

Represents the upper bound of the error threshold for a point

+
+ + + + + +
+
Example:
+
100
+
+
+
+
+
+
+
+
+
+

+ +

+
+ +
+
+ + Type: integer
+

Represents the limit in seconds that a point can be unchanged for

+
+ + + + + +
+
Example:
+
3600
+
+
+
+
+
+
+
+
+
+

+ +

+
+ +
+
+ + Type: number
+

Triggering threshold for partial cov update publishing

+
+ + + + + +
+
Example:
+
1
+
+
+
+
+
+
+
+
+
+

+ +

+
+ +
+
+ + Type: string
+

Mapping for the point to an internal resource (e.g. BACnet object reference)

+
+ + + + + +
+
Examples:
+
"AI3"
+
+
"400070"
+
+
+
+
+
+
+
+
+
+

+ +

+
+ +
+
+ + Type: string
+

Complete resolved URL of this point as determined by the gateway and ref blocks

+
+ + + + + +
+
Example:
+
"bacnet://1234/AV/1"
+
+
+
+
+
+
+
+
+
+

+ +

+
+ +
+
+ + Type: object
+ No Additional Properties + + + + + + +
+
+
+

+ +

+
+ +
+

+ +

+

All properties whose name matches the following regular expression must respect the following conditions

+ Property name regular expression: ^[a-z_]+$ +
+ + Type: string
+ + + + + + + +
+
+
+
+
+
+
+
+
+
+
+

+ +

+
+ +
+
+ + Type: array
+

Tags assosciated with the point

+
+ + + + + +

All items must be unique

No Additional Items

Each item of this array must be:

+
+
+ + + Type: object
+Must match regular expression: ^[a-z0-9]+$ + + + + + + +
+

+
Examples:
+
[
+    "lighting"
+]
+
+
[
+    "energy",
+    "hvac"
+]
+
+
+
+
+
+
+
+
+
+

+ +

+
+ +
+
+ + Type: object
+

Collection of family point information

+
No Additional Properties + + + + + + +
+
+
+

+ +

+
+ +
+

+ +

+

All properties whose name matches the following regular expression must respect the following conditions

+ Property name regular expression: ^[-_.:/a-zA-Z0-9]+$ +
+ +

Ref Discovery

Type: object
+

Object representation for for a single point reference discovery

+
+ + No Additional Properties + + + + + + +
+
+
+

+ +

+
+ +
+
+ + Type: string
+

Point descriptor for this point

+
Must match regular expression: ^[a-z][a-z0-9]*(_[a-z0-9]+)*$ + + + + + + +
+
+
+
+
+
+
+

+ +

+
+ +
+
+ + Type: string
+

Friendly name for the reference, if known

+
+ + + + + + +
+
+
+
+
+
+
+

+ +

+
+ +
+
+ + Type: array of string
+

List of possible enumerated values for the point

+
+ + + + + + No Additional Items

Each item of this array must be:

+
+
+ + + Type: string
+ + + + + + + +
+
+
+
+
+
+
+
+
+

+ +

+
+ +
+
+ + Type: string
+

Current or default unit for this point

+
+ + + + + + +
+
+
+
+
+
+
+

+ +

+
+ +
+
+ + Type: string
+

Family reference for structures

+
+ + + + + + +
+
+
+
+
+
+
+

+ +

+
+ +
+
+ + Type: string
+

Current or default type for this point

+
+ + + + + + +
+
+
+
+
+
+
+

+ +

+
+ +
+
+ + Type: boolean
+

Indicates if this point is writable or not

+
+ + + + + + +
+
+
+
+
+
+
+

+ +

+
+ +
+
+ + Type: string
+

Human-readable description of this point

+
+ + + + + + +
+
+
+
+
+
+
+

+ +

+
+ +
+
+ +

Entry

Type: object
+ + + No Additional Properties + + + + + + +
+
+
+

+ +

+
+ +
+
+ + Type: string
+

A human-readable one-line description of the entry

+
+ + + + + +
+
Example:
+
"Point is not writable"
+
+
+
+
+
+
+
+
+
+

+ +

+
+ +
+
+ + Type: string
+

An optional extensive entry which can include more detail, e.g. a complete program stack-trace

+
+ + + + + + +
+
+
+
+
+
+
+

+ +

+
+ +
+
+ +

Category

Type: string
+ + +

+ +

+
+ + + Type: object
+Must match regular expression: ^system\.base\.start$ + + + + + + +
+ + + Type: object
+Must match regular expression: ^system\.base\.shutdown$ + + + + + + +
+ + + Type: object
+Must match regular expression: ^system\.base\.ready$ + + + + + + +
+ + + Type: object
+Must match regular expression: ^system\.config\.receive$ + + + + + + +
+ + + Type: object
+Must match regular expression: ^system\.config\.parse$ + + + + + + +
+ + + Type: object
+Must match regular expression: ^system\.config\.apply$ + + + + + + +
+ + + Type: object
+Must match regular expression: ^system\.auth\.login$ + + + + + + +
+ + + Type: object
+Must match regular expression: ^system\.auth\.logout$ + + + + + + +
+ + + Type: object
+Must match regular expression: ^system\.auth\.fail$ + + + + + + +
+ + + Type: object
+Must match regular expression: ^pointset\.point\.nominal$ + + + + + + +
+ + + Type: object
+Must match regular expression: ^pointset\.point\.applied$ + + + + + + +
+ + + Type: object
+Must match regular expression: ^pointset\.point\.updating$ + + + + + + +
+ + + Type: object
+Must match regular expression: ^pointset\.point\.overridden$ + + + + + + +
+ + + Type: object
+Must match regular expression: ^pointset\.point\.failure$ + + + + + + +
+ + + Type: object
+Must match regular expression: ^pointset\.point\.invalid$ + + + + + + +
+ + + Type: object
+Must match regular expression: ^localnet\.network$ + + + + + + +
+ + + Type: object
+Must match regular expression: ^localnet\.network\.connect$ + + + + + + +
+ + + Type: object
+Must match regular expression: ^gateway\.setup\.attach$ + + + + + + +
+ + + Type: object
+Must match regular expression: ^gateway\.proxy\.target$ + + + + + + +
+ + + Type: object
+Must match regular expression: ^gateway\.proxy\.connect$ + + + + + + +
+ + + Type: object
+Must match regular expression: ^discovery\.family\.scan$ + + + + + + +
+ + + Type: object
+Must match regular expression: ^discovery\.device\.enumerate$ + + + + + + +
+ + + Type: object
+Must match regular expression: ^discovery\.point\.describe$ + + + + + + +
+ + + Type: object
+Must match regular expression: ^mapping\.device\.apply$ + + + + + + +
+ + + Type: object
+Must match regular expression: ^blobset\.blob$ + + + + + + +
+ + + Type: object
+Must match regular expression: ^blobset\.blob\.update$ + + + + + + +
+ + + Type: object
+Must match regular expression: ^blobset\.blob\.receive$ + + + + + + +
+ + + Type: object
+Must match regular expression: ^blobset\.blob\.fetch$ + + + + + + +
+ + + Type: object
+Must match regular expression: ^blobset\.blob\.parse$ + + + + + + +
+ + + Type: object
+Must match regular expression: ^blobset\.blob\.apply$ + + + + + + +
+ + + Type: object
+Must match regular expression: ^blobset\.blob\.abort$ + + + + + + +
+ + + Type: object
+Must match regular expression: ^blobset\.blob\.rollback$ + + + + + + +
+ + + Type: object
+Must match regular expression: ^validation\.device\.receive$ + + + + + + +
+ + + Type: object
+Must match regular expression: ^validation\.device\.schema$ + + + + + + +
+ + + Type: object
+Must match regular expression: ^validation\.device\.content$ + + + + + + +
+ + + Type: object
+Must match regular expression: ^validation\.device\.multiple$ + + + + + + +
+ + + Type: object
+Must match regular expression: ^validation\.device\.extra$ + + + + + + +
+ + + Type: object
+Must match regular expression: ^validation\.summary\.report$ + + + + + + +
+ + + Type: object
+Must match regular expression: ^validation\.feature\.sequence$ + + + + + + +
+ + + Type: object
+Must match regular expression: ^validation\.feature\.schema$ + + + + + + +
+ + + Type: object
+Must match regular expression: ^validation\.feature\.capability$ + + + + + + +
+ + + Type: object
+Must match regular expression: ^device(\.[a-z]+((_[a-z]+))*)+$ + + + + + + +
+ + + + + + +
+
+
+
+
+
+
+

+ +

+
+ +
+
+ + Type: stringFormat: date-time
+

FC 3339 UTC timestamp the condition was triggered, or most recently updated

+
+ + + + + +
+
Example:
+
"2018-08-26T21:39:28.364Z"
+
+
+
+
+
+
+
+
+
+

+ +

+
+ +
+
+ + Type: integer
+

The status level should conform to the numerical Stackdriver LogEntry levels. The DEFAULT value of 0 is not allowed (lowest value is 100, maximum 800).

+
+ + + +

Value must be greater or equal to 100 and lesser or equal to 800 and a multiple of 1

+ +
+
Example:
+
600
+
+
+
+
+
+
+
+
+
+
+
+
+
+

+ +

+
+ +
+
+ +

Ancillary Properties

Type: object
+

Arbitrary blob of json associated with this point

+
+ + + + + + + + +
+
+
+
+
+
+
+

+ +

+
+ +
+
+ + Type: object
+ No Additional Properties + + + + + + +
+
+
+

+ +

+
+ +
+

+ +

+

All properties whose name matches the following regular expression must respect the following conditions

+ Property name regular expression: ^[a-z_]+$ +
+ + Type: string
+ + + + + + + +
+
+
+
+
+
+
+
+
+
+
+

+ +

+
+ +
+
+ + Type: object
+ + + + + + + +
+
+
+

+ +

+
+ +
+
+ +

Family Discovery

Type: object
+

Discovery information for a protocol family.

+
+ + No Additional Properties + + + + + + +
+
+
+

+ +

+
+ +
+
+ + Type: string
+

Device addr in the namespace of the given family

+
Must match regular expression: ^[-_.:0-9A-Za-z]+$ + + + + + + +
+
+
+
+
+
+
+

+ +

+
+ +
+
+ + Type: integer
+

Port number for the family connection (e.g. UDP port for BACnet/IP)

+
+ + + + + + +
+
+
+
+
+
+
+

+ +

+
+ +
+
+ + Type: string
+

Point reference in the namespace of the given family

+
Must match regular expression: ^[-_.:/#0-9A-Za-z]+$ + + + + + + +
+
+
+
+
+
+
+
+
+
+
+

+ +

+
+ +
+
+ + Type: object
+

Reference links to alternate families

+
No Additional Properties + + + + + + +
+
+
+

+ +

+
+ +
+

+ +

+

All properties whose name matches the following regular expression must respect the following conditions

+ Property name regular expression: ^[a-z][a-z0-9]*(_[a-z0-9]+)*$ +
+ +

Family Discovery

Type: object
+

Discovery information for a protocol family.

+
+ + No Additional Properties + + + + + + +
+
+
+

+ +

+
+ +
+
+ + Type: string
+

Device addr in the namespace of the given family

+
Must match regular expression: ^[-_.:0-9A-Za-z]+$ + + + + + + +
+
+
+
+
+
+
+

+ +

+
+ +
+
+ + Type: integer
+

Port number for the family connection (e.g. UDP port for BACnet/IP)

+
+ + + + + + +
+
+
+
+
+
+
+

+ +

+
+ +
+
+ + Type: string
+

Point reference in the namespace of the given family

+
Must match regular expression: ^[-_.:/#0-9A-Za-z]+$ + + + + + + +
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+

+ +

+
+ +
+
+ + Type: string
+

Virtual equipment mapping linking this local point to a remote point in another device

+
Must match regular expression: .*[-_0-9a-zA-Z$]+:[a-z0-9_]+.* + + + + + +
+
Examples:
+
"VAV-3:supply_air_flowrate_sensor"
+
+
"VAV-101:zone_air_temp_sensor - VAV-102:zone_air_temp_sensor"
+
+
+
+
+
+
+
+
+
+

+ +

+
+ +
+
+ + Type: object
+

State value mapping for this point (e.g. 0.0: OFF)

+
+ + + + + + +
+
+
+

+ +

+
+ +
+

Each additional property must conform to the following schema

+ + Type: string
+ + + + + + + +
+
+
+
+
+
+
+
+
+
+
+

+ +

+
+ +
+
+ +

Building Translation

Type: object
+

Discovery result with implicit enumeration

+
+ + No Additional Properties + + + + + + +
+
+
+

+ +

+
+ +
+
+ + Type: string
+

dotted path to present_value field

+
+ + + + + + +
+
+
+
+
+
+
+

+ +

+
+ +
+
+ + Type: object
+ + + + + + + +
+
+
+

+ +

+
+ +
+
+ + Type: string
+ + + + + + + +
+
+
+
+
+
+
+
+
+
+
+

+ +

+
+ +
+
+ + Type: string
+

Actual input unit that is sent from the device

+
+ + + + + + +
+
+
+
+
+
+
+

+ +

+
+ +
+
+ + Type: object
+ + + + + + + +
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+

+ +

+
+ +
+
+ + Type: boolean
+ + + + + + + +
+
+
+
+
+
+
+

+ +

+
+ +
+
+ + Type: boolean
+ + + + + + + +
+
+
+
+
+
+
+

+ +

+
+ +
+
+ + Type: integer
+

Minimum time between sample updates for the device (including complete and COV updates). Updates more frequent than this should be coalesced into one update.

+
+ + + +

Value must be greater or equal to 0 and lesser or equal to 86400

+ + +
+
+
+
+
+
+
+

+ +

+
+ +
+
+ + Type: integer
+

Maximum time between samples for the device to send out a complete update. It can send out updates more frequently than this. Default to 600.

+
+ + + +

Value must be greater or equal to 1 and lesser or equal to 86400

+ + +
+
+
+
+
+
+
+
+
+
+
+

+ +

+
+ +
+
+ + Type: object
+ + + + + + + +
+
+
+

+ +

+
+ +
+

+ +

+

All properties whose name matches the following regular expression must respect the following conditions

+ Property name regular expression: ^[a-z][a-z0-9]*(_[a-z0-9]+)*$ +
+ +

Discovery Events

Type: object
+

Discovery result with implicit discovery

+
Same definition as discovery +
+
+
+
+
+
+
+
+
+
+
+

+ +

+
+ +
+
+ +

Policy Model

Type: object
+ + + No Additional Properties + + + + + + +
+
+
+

+ +

+
+ +
+
+ + Type: array of string
+

An array of rule sets which are applicable to the device

+
+ + + + + + No Additional Items

Each item of this array must be:

+
+
+ + + Type: string
+Must match regular expression: ^[a-z_0-9]*$ + + + + + + +
+

+
Example:
+
[
+    "ip",
+    "mqtt"
+]
+
diff --git a/gencode/docs/events_discovery.html b/gencode/docs/events_discovery.html index 174c0a7c08..603ade8b25 100644 --- a/gencode/docs/events_discovery.html +++ b/gencode/docs/events_discovery.html @@ -2314,6 +2314,40 @@

+

+
+
+
+
+
+
+

+ +

+
+ +
+
+ + Type: string
+

Optional source qualifier for the discovery scan (e.g., 'model')

+
+ + + + + +
@@ -2416,6 +2450,102 @@

+

+
+
+
+
+
+
+

+ +

+
+ +
+
+ + Type: integer
+

Total number of records expected for this active scan generation (only populated on the event_no=0 start marker event)

+
+ + + +

Value must be greater or equal to 0

+ + +
+
+
+
+
+
+
+

+ +

+
+ +
+
+ + Type: array of string
+

List of target addresses for a partial discovery scan shard (only populated on the event_no=0 start marker event)

+
+ + + + + + No Additional Items

Each item of this array must be:

+
+
+ + + Type: string
+ + + + + + + +
+
@@ -12459,16 +12589,18 @@

expr

Type: string

Virtual equipment mapping linking this local point to a remote point in another device

-
Must match regular expression: ^[-0-9a-zA-Z$]+:[a-z0-9_]+$ +Must match regular expression: .*[-_0-9a-zA-Z$]+:[a-z0-9_]+.*
-
Example:
+
Examples:

"VAV-3:supply_air_flowrate_sensor"
 
+
"VAV-101:zone_air_temp_sensor - VAV-102:zone_air_temp_sensor"
+
@@ -19164,6 +19296,51209 @@

"1"
 
+
+ + + + + + + + + + + + +
+
+
+

+ +

+
+ +
+
+ +

Metadata

Type: object
+

Device metadata model (for model discovery)

+
+ + No Additional Properties + + + + + + +
+
+
+

+ +

+
+ +
+
+ + Type: stringFormat: date-time
+

RFC 3339 timestamp UTC the data was generated

+
+ + + + + +
+
Example:
+
"2019-01-17T14:02:29.364Z"
+
+
+
+
+
+
+
+
+
+

+ +

+
+ +
+
+ + Type: string
+

Version of the UDMI schema for this file

+
+ + + + + + +
+
+
+
+
+
+
+

+ +

+
+ +
+
+ + Type: string
+

Original version of the UDMI schema for this file

+
+ + + + + + +
+
+
+
+
+
+
+

+ +

+
+ +
+
+ + Type: string
+

DEPRECATED

+
Must match regular expression: ^[0-9a-z]{8}$ + + + + + + +
+
+
+
+
+
+
+

+ +

+
+ +
+
+ +

Model Operation

Type: enum (of string)
+

Used for model update operations (so not a model proper), e.g. DELETE or BIND operations

+
+ +
+

Must be one of:

+
  • "ERROR"
  • "CREATE"
  • "READ"
  • "REPLY"
  • "UPDATE"
  • "DELETE"
  • "MODIFY"
  • "PREVIEW"
  • "BIND"
  • "UNBIND"
  • "BOUND"
  • "ALLOW"
  • "BLOCK"
+
+ + + + + + +
+
+
+
+
+
+
+

+ +

+
+ +
+
+ +

Cloud Model

Type: object
+

Information specific to how the device communicates with the cloud.

+
+ + No Additional Properties + + + + + + +
+
+
+

+ +

+
+ +
+
+ + Type: integer
+

Function version of this request

+
+ + + + + + +
+
+
+
+
+
+
+

+ +

+
+ +
+
+ + Type: enum (of string)
+

The key type used for cloud communication.

+
+

Must be one of:

+
  • "ES256"
  • "ES256_X509"
  • "RS256"
  • "RS256_X509"
+
+ + + + + + +
+
+
+
+
+
+
+

+ +

+
+ +
+
+ + Type: boolean
+

Indicates if the device private key is provided in the site model.

+
+ + + + + + +
+
+
+
+
+
+
+

+ +

+
+ +
+
+ + Type: enum (of string)
+

The cloud resource type

+
+

Must be one of:

+
  • "DIRECT"
  • "GATEWAY"
  • "FUNNEL"
  • "PROXIED"
  • "REGISTRY"
+
+ + + + + + +
+
+
+
+
+
+
+

+ +

+
+ +
+
+ +

Gateway Model

Type: object
+

Gateway Documentation

+
+ + No Additional Properties + + + + + + +
+
+
+

+ +

+
+ +
+
+ + Type: string
+

The device ID of the gateway the device is bound to

+
Must match regular expression: ^[A-Z]{2,6}-[1-9][0-9]*$ + + + + + +
+
Example:
+
"GAT-100"
+
+
+
+
+
+
+
+
+
+

+ +

+
+ +
+
+ +

Family Localnet Model

Type: object
+

The type of network

+
+ + No Additional Properties + + + + + + +
+
+
+

+ +

+
+ +
+
+ + Type: string
+

The address of a device on the fieldbus/local network

+
+ + + + + +
+
Examples:
+
"192.168.1.3"
+
+
"41489"
+
+
+
+
+
+
+
+
+
+

+ +

+
+ +
+
+ + Type: string
+

The slave unit ID of the device for protocol families like modbus

+
+ + + + + +
+
Examples:
+
"1"
+
+
"255"
+
+
+
+
+
+
+
+
+
+

+ +

+
+ +
+
+ + Type: string
+

The network designator for this device in the family-defined format

+
+ + + + + +
+
Examples:
+
"192.168.1.0/24"
+
+
"12732"
+
+
+
+
+
+
+
+
+
+

+ +

+
+ +
+
+ + Type: object
+ No Additional Properties + + + + + + +
+
+
+

+ +

+
+ +
+

+ +

+

All properties whose name matches the following regular expression must respect the following conditions

+ Property name regular expression: ^[a-z_]+$ +
+ + Type: string
+ + + + + + + +
+
+
+
+
+
+
+
+
+
+
+

+ +

+
+ +
+
+ + Type: string
+

Specifies that this is a shadow of the indicated device

+
+ + + + + + +
+
+
+
+
+
+
+

+ +

+
+ +
+
+ + Type: string
+

The device id of the node's parent

+
+ + + + + + +
+
+
+
+
+
+
+

+ +

+
+ +
+
+ + Type: boolean
+

If true, then the system should not try to validate the refs

+
+ + + + + + +
+
+
+
+
+
+
+

+ +

+
+ +
+
+ + Type: string
+

The family designator, used only when the entry is not keyed in a family map

+
+ + + + + +
+
Examples:
+
"ipv4"
+
+
"bacnet"
+
+
+
+
+
+
+
+
+
+
+
+
+
+

+ +

+
+ +
+
+ + Type: array of string
+

An array of all the device IDs which are bound to the device

+
+ + + + + + No Additional Items

Each item of this array must be:

+
+
+ + + Type: string
+Must match regular expression: ^[A-Z]{2,6}-[1-9][0-9]*$ + + + + + + +
+

+
Example:
+
[
+    "AHU-22"
+]
+
+
+
+
+
+
+
+
+
+
+
+
+
+

+ +

+
+ +
+
+ +

Config Cloud Model

Type: object
+

Information specific to how the device communicates with the cloud.

+
+ + No Additional Properties + + + + + + +
+
+
+

+ +

+
+ +
+
+ + Type: string
+

Config file to use. Within the config directory in the device's metadata directory

+
+ + + + + + +
+
+
+
+
+
+
+
+
+
+
+

+ +

+
+ +
+
+ + Type: boolean
+ + + + + + + +
+
+
+
+
+
+
+

+ +

+
+ +
+
+ + Type: string
+ + + + + + + +
+
+
+
+
+
+
+

+ +

+
+ +
+
+ + Type: string
+ + + + + + + +
+
+
+
+
+
+
+

+ +

+
+ +
+
+ + Type: stringFormat: date-time
+ + + + + + + +
+
+
+
+
+
+
+

+ +

+
+ +
+
+ + Type: stringFormat: date-time
+ + + + + + + +
+
+
+
+
+
+
+

+ +

+
+ +
+
+ + Type: stringFormat: date-time
+ + + + + + + +
+
+
+
+
+
+
+

+ +

+
+ +
+
+ + Type: stringFormat: date-time
+ + + + + + + +
+
+
+
+
+
+
+

+ +

+
+ +
+
+ + Type: string
+ + + + + + + +
+
+
+
+
+
+
+

+ +

+
+ +
+
+ +

Model Operation

Type: enum (of string)
+ + +
+

Must be one of:

+
  • "ERROR"
  • "CREATE"
  • "READ"
  • "REPLY"
  • "UPDATE"
  • "DELETE"
  • "MODIFY"
  • "PREVIEW"
  • "BIND"
  • "UNBIND"
  • "BOUND"
  • "ALLOW"
  • "BLOCK"
+
+ + + + + + +
+
+
+
+
+
+
+

+ +

+
+ +
+
+ + Type: string
+

Strigified version of the metadata object, used for internal backend processing

+
+ + + + + + +
+
+
+
+
+
+
+

+ +

+
+ +
+
+ + Type: object
+

If operating on the entire registry, then this contains results for multiple devices (batched operation).

+
+ + + + + + +
+
+
+

+ +

+
+ +
+

+ +

+

All properties whose name matches the following regular expression must respect the following conditions

+ Property name regular expression: ^[-._a-zA-Z0-9]+$ +
+ + Type: object
+ + + + + + + +
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+

+ +

+
+ +
+
+ +

System Model

Type: object
+

High-level system information about the device. System Model Documentation

+
+ + No Additional Properties + + + + + + +
+
+
+

+ +

+
+ +
+
+ + Type: string
+

Friendly name describing this device

+
+ + + + + + +
+
+
+
+
+
+
+

+ +

+
+ +
+
+ + Type: string
+

Generic human-readable text describing the device

+
+ + + + + + +
+
+
+
+
+
+
+

+ +

+
+ +
+
+ + Type: string
+

Version of schema supported by the device

+
+ + + + + +
+
Example:
+
"1.5.0"
+
+
+
+
+
+
+
+
+
+

+ +

+
+ +
+
+ + Type: string
+

Aggregation of spaces, partial spaces or other zones that the device is serving

+
Must match regular expression: ^[-_a-zA-Z0-9]+$ + + + + + +
+
Examples:
+
"Zone1"
+
+
"Kitchen_101"
+
+
+
+
+
+
+
+
+
+

+ +

+
+ +
+
+ + Type: string
+

The description of what kind of node this is.

+
Must match regular expression: ^[a-z][_a-z0-9]+$ + + + + + +
+
Examples:
+
"vitual_device"
+
+
"field_group"
+
+
"field_controller"
+
+
"network_scan_agent"
+
+
+
+
+
+
+
+
+
+

+ +

+
+ +
+
+ + Type: array
+

Tags associated with the device

+
+ + + + + +

All items must be unique

No Additional Items

Each item of this array must be:

+
+
+ + + Type: object
+Must match regular expression: ^[a-z][_a-z0-9]+$ + + + + + + +
+

+
Examples:
+
[
+    "lighting"
+]
+
+
[
+    "energy",
+    "building",
+    "vendor_managed"
+]
+
+
+
+
+
+
+
+
+
+

+ +

+
+ +
+
+ + Type: object
+

Properties of the expected physical location of the device

+
No Additional Properties + + + + + + +
+
+
+

+ +

+
+ +
+
+ + Type: string
+

The site name according to the site model in which the device is installed in

+
Must match regular expression: ^[A-Z]{2}-[A-Z]{3,4}-[A-Z0-9]{2,9}$ + + + + + +
+
Example:
+
"US-SFO-XYY"
+
+
+
+
+
+
+
+
+
+

+ +

+
+ +
+
+ + Type: string
+

The reference of the panel where the device is installed in

+
Must match regular expression: ^[-_a-zA-Z0-9]+$ + + + + + +
+
Examples:
+
"Panel123"
+
+
"CTRP-567"
+
+
+
+
+
+
+
+
+
+

+ +

+
+ +
+
+ + Type: string
+Must match regular expression: ^[A-Z0-9-]+$ + + + + + + +
+
+
+
+
+
+
+

+ +

+
+ +
+
+ + Type: string
+Must match regular expression: ^[-_a-zA-Z0-9]+$ + + + + + + +
+
+
+
+
+
+
+

+ +

+
+ +
+
+ + Type: string
+

Name of floor level

+
Must match regular expression: ^[A-Z0-9-]+$ + + + + + +
+
Examples:
+
"1"
+
+
"1A"
+
+
"G"
+
+
"B1M"
+
+
"RF"
+
+
+
+
+
+
+
+
+
+

+ +

+
+ +
+
+ + Type: integer
+

Sequential integer representation for a floor, primarily for comparisons when non integer floors are used, e.g. 1 and 1M

+
+ + + + + + +
+
+
+
+
+
+
+

+ +

+
+ +
+
+ + Type: object
+ No Additional Properties + + + + + + +
+
+
+

+ +

+
+ +
+
+ + Type: number
+

The x coordinate of the device location in a project specific coordinate system

+
+ + + + + + +
+
+
+
+
+
+
+

+ +

+
+ +
+
+ + Type: number
+

The y coordinate of the device location in a project specific coordinate system

+
+ + + + + + +
+
+
+
+
+
+
+

+ +

+
+ +
+
+ + Type: number
+

The z (height) coordinate of the device location in a project specific coordinate system

+
+ + + + + + +
+
+
+
+
+
+
+
+
+
+
+

+ +

+
+ +
+
+ + Type: object
+ No Additional Properties + + + + + + +
+
+
+

+ +

+
+ +
+
+ + Type: number
+

The latitude coordinate of the device in the WGS84 coordinate system

+
+ + + + + + +
+
+
+
+
+
+
+

+ +

+
+ +
+
+ + Type: number
+

The longitude coordinate of the device in the WGS84 coordinate system

+
+ + + + + + +
+
+
+
+
+
+
+

+ +

+
+ +
+
+ + Type: number
+

The altitude coordinate of the device in meters

+
+ + + + + + +
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+

+ +

+
+ +
+
+ + Type: string
+

The serial number of the physical device

+
+ + + + + +
+
Examples:
+
"A1B2C3D4"
+
+
"00001"
+
+
+
+
+
+
+
+
+
+

+ +

+
+ +
+
+ +

SystemHardware

Type: object
+

A collection of fields which describe the physical hardware of the device.

+
+ + No Additional Properties + + + + + + +
+
+
+

+ +

+
+ +
+
+ + Type: string
+

The manufacturer of the device

+
+ + + + + +
+
Example:
+
"Google"
+
+
+
+
+
+
+
+
+
+

+ +

+
+ +
+
+ + Type: string
+

The model of the device

+
+ + + + + +
+
Examples:
+
"Pixel 5"
+
+
"Pixel 6"
+
+
"Pixel 6 Pro"
+
+
+
+
+
+
+
+
+
+

+ +

+
+ +
+
+ + Type: string
+

A stock keeping unit which identifies the unique composition of a device, for example those with different hardware configurations

+
+ + + + + +
+
Examples:
+
"GF5KQ"
+
+
"4215612"
+
+
+
+
+
+
+
+
+
+

+ +

+
+ +
+
+ + Type: string
+

Hardware revision of the device

+
+ + + + + +
+
Examples:
+
"A"
+
+
"B"
+
+
"1"
+
+
+
+
+
+
+
+
+
+
+
+
+
+

+ +

+
+ +
+
+ + Type: object
+

A collection of items which can be used to describe version of software running on a device

+
No Additional Properties + + + + + +
+
Examples:
+
"firmware"
+
+
"os"
+
+
"driver"
+
+
+
+
+
+

+ +

+
+ +
+

+ +

+

All properties whose name matches the following regular expression must respect the following conditions

+ Property name regular expression: ^[a-z_]+$ +
+ + Type: string
+ + + + + + + +
+
+
+
+
+
+
+
+
+
+
+

+ +

+
+ +
+
+ + Type: object
+

Information used to print a physical QR code label.

+
No Additional Properties + + + + + + +
+
+
+

+ +

+
+ +
+
+ + Type: object
+ No Additional Properties + + + + + + +
+
+
+

+ +

+
+ +
+
+ + Type: string
+Must match regular expression: ^[a-z]+://[-0-9a-zA-Z_$]+$ + + + + + + +
+
+
+
+
+
+
+

+ +

+
+ +
+
+ + Type: string
+Must match regular expression: ^[A-Z]{2}-[A-Z]{3,4}-[A-Z0-9]{2,9}$ + + + + + + +
+
+
+
+
+
+
+

+ +

+
+ +
+
+ + Type: string
+Must match regular expression: ^[A-Z]{2,6}-[1-9][0-9]*$ + + + + + + +
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+

+ +

+
+ +
+
+ + Type: object
+ No Additional Properties + + + + + + +
+
+
+

+ +

+
+ +
+

+ +

+

All properties whose name matches the following regular expression must respect the following conditions

+ Property name regular expression: ^[a-z_]+$ +
+ + Type: string
+ + + + + + + +
+
+
+
+
+
+
+
+
+
+
+

+ +

+
+ +
+
+ + Type: integer Default: 300
+

The minimum loglevel for reporting log messages below which log entries should not be sent. Default to 300.

+
+ + + +

Value must be greater or equal to 100 and lesser or equal to 800

+ + +
+
+
+
+
+
+
+

+ +

+
+ +
+
+ + Type: integer
+

The rate at which the system should send system event updates. 0 indicates no updates.

+
+ + + +

Value must be greater or equal to 0 and lesser or equal to 86400

+ + +
+
+
+
+
+
+
+
+
+
+
+

+ +

+
+ +
+
+ +

Externals Model

Type: object
+

Information specific to how the device links with external models

+
+ + No Additional Properties + + + + + + +
+
+
+

+ +

+
+ +
+

+ +

+

All properties whose name matches the following regular expression must respect the following conditions

+ Property name regular expression: ^[a-z]+(_[a-z]+)*$ +
+ +

Link Externals Model

Type: object
+

Information about how this device links to a specific external model

+
+ + No Additional Properties + + + + + + +
+
+
+

+ +

+
+ +
+
+ + Type: string
+

Id of this device in the external model namespace

+
+ + + + + + +
+
+
+
+
+
+
+

+ +

+
+ +
+
+ + Type: string
+

Type of this device in the external model namespace

+
+ + + + + + +
+
+
+
+
+
+
+

+ +

+
+ +
+
+ + Type: string
+

Etag for referencing this external entity

+
Must match regular expression: ^[0-9a-f]+$ + + + + + + +
+
+
+
+
+
+
+

+ +

+
+ +
+
+ + Type: string
+

Descriptive label for this entity

+
+ + + + + + +
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+

+ +

+
+ +
+
+ +

Entity Relationships

Type: object
+

Entity relationships to other devices, keyed by target device ID

+
+ + + + + + + + +
+
+
+

+ +

+
+ +
+

+ +

+

All properties whose name matches the following regular expression must respect the following conditions

+ Property name regular expression: ^[-0-9a-zA-Z]+$ +
+ + Type: object
+ + + + + + + +
+
+
+

+ +

+
+ +
+

+ +

+

All properties whose name matches the following regular expression must respect the following conditions

+ Property name regular expression: ^[-0-9a-zA-Z_]+$ +
+ + Type: array of object
+ + + + + + + No Additional Items

Each item of this array must be:

+
+
+ + + Type: object
+ + + + + + + +
+
+
+

+ +

+
+ +
+
+ + Type: array of string
+ + + + + + + No Additional Items

Each item of this array must be:

+
+
+ + + Type: string
+ + + + + + + +
+

+
Example:
+
[
+    "primary"
+]
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+

+ +

+
+ +
+
+ +

Gateway Model

Type: object
+

Gateway Documentation

+
+ + No Additional Properties + + + + + + +
+
+
+

+ +

+
+ +
+
+ + Type: string
+

The device ID of the gateway the device is bound to

+
Must match regular expression: ^[A-Z]{2,6}-[1-9][0-9]*$ + + + + + +
+
Example:
+
"GAT-100"
+
+
+
+
+
+
+
+
+
+

+ +

+
+ +
+
+ +

Family Localnet Model

Type: object
+

The type of network

+
+ + No Additional Properties + + + + + + +
+
+
+

+ +

+
+ +
+
+ + Type: string
+

The address of a device on the fieldbus/local network

+
+ + + + + +
+
Examples:
+
"192.168.1.3"
+
+
"41489"
+
+
+
+
+
+
+
+
+
+

+ +

+
+ +
+
+ + Type: string
+

The slave unit ID of the device for protocol families like modbus

+
+ + + + + +
+
Examples:
+
"1"
+
+
"255"
+
+
+
+
+
+
+
+
+
+

+ +

+
+ +
+
+ + Type: string
+

The network designator for this device in the family-defined format

+
+ + + + + +
+
Examples:
+
"192.168.1.0/24"
+
+
"12732"
+
+
+
+
+
+
+
+
+
+

+ +

+
+ +
+
+ + Type: object
+ No Additional Properties + + + + + + +
+
+
+

+ +

+
+ +
+

+ +

+

All properties whose name matches the following regular expression must respect the following conditions

+ Property name regular expression: ^[a-z_]+$ +
+ + Type: string
+ + + + + + + +
+
+
+
+
+
+
+
+
+
+
+

+ +

+
+ +
+
+ + Type: string
+

Specifies that this is a shadow of the indicated device

+
+ + + + + + +
+
+
+
+
+
+
+

+ +

+
+ +
+
+ + Type: string
+

The device id of the node's parent

+
+ + + + + + +
+
+
+
+
+
+
+

+ +

+
+ +
+
+ + Type: boolean
+

If true, then the system should not try to validate the refs

+
+ + + + + + +
+
+
+
+
+
+
+

+ +

+
+ +
+
+ + Type: string
+

The family designator, used only when the entry is not keyed in a family map

+
+ + + + + +
+
Examples:
+
"ipv4"
+
+
"bacnet"
+
+
+
+
+
+
+
+
+
+
+
+
+
+

+ +

+
+ +
+
+ + Type: array of string
+

An array of all the device IDs which are bound to the device

+
+ + + + + + No Additional Items

Each item of this array must be:

+
+
+ + + Type: string
+Must match regular expression: ^[A-Z]{2,6}-[1-9][0-9]*$ + + + + + + +
+

+
Example:
+
[
+    "AHU-22"
+]
+
+
+
+
+
+
+
+
+
+
+
+
+
+

+ +

+
+ +
+
+ +

Discovery Model

Type: object
+

Discovery target parameters

+
+ + No Additional Properties + + + + + + +
+
+
+

+ +

+
+ +
+
+ + Type: object
+ + + + + + + +
+
+
+

+ +

+
+ +
+

+ +

+

All properties whose name matches the following regular expression must respect the following conditions

+ Property name regular expression: ^[a-z][a-z0-9]*(_[a-z0-9]+)*$ +
+ +

Family Discovery Model

Type: object
+ + + No Additional Properties + + + + + + +
+
+
+

+ +

+
+ +
+
+ + Type: stringFormat: date-time
+

Generational marker for controlling discovery

+
+ + + + + +
+
Example:
+
"2018-08-26T21:00:13Z"
+
+
+
+
+
+
+
+
+
+

+ +

+
+ +
+
+ + Type: integer
+

Period, in seconds, for automatic scanning

+
+ + + +

Value must be greater or equal to 0

+ + +
+
+
+
+
+
+
+

+ +

+
+ +
+
+ + Type: integer
+

Scan duration, in seconds

+
+ + + +

Value must be greater or equal to 0

+ + +
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+

+ +

+
+ +
+
+ +

Localnet Model

Type: object
+

Used to describe device local network parameters

+
+ + No Additional Properties + + + + + + +
+
+
+

+ +

+
+ +
+
+ + Type: object
+

Parent device to which the device is physically connected

+
No Additional Properties + + + + + + +
+
+
+

+ +

+
+ +
+
+ + Type: string
+

ID of the parent device to which the device is physically connected

+
Must match regular expression: ^[A-Z]{2,6}-[1-9][0-9]*$ + + + + + +
+
Examples:
+
"DDC-1"
+
+
"CGW-2"
+
+
+
+
+
+
+
+
+
+

+ +

+
+ +
+
+ + Type: string
+

Connection family/protocol of the parent device to which the device is physically connected

+
+ + + + + +
+
Examples:
+
"bacnet_mstp"
+
+
"modbus_tcp"
+
+
"mqtt"
+
+
+
+
+
+
+
+
+
+
+
+
+
+

+ +

+
+ +
+
+ + Type: object
+ No Additional Properties + + + + + +
+
Examples:
+
"bacnet"
+
+
"modbus"
+
+
"mbus"
+
+
"ether"
+
+
"ipv4"
+
+
"ipv6"
+
+
"iot"
+
+
+
+
+
+

+ +

+
+ +
+

+ +

+

All properties whose name matches the following regular expression must respect the following conditions

+ Property name regular expression: ^[a-z0-9-]+$ +
+ +

Family Localnet Model

Type: object
+

The type of network

+
+ + No Additional Properties + + + + + + +
+
+
+

+ +

+
+ +
+
+ + Type: string
+

The address of a device on the fieldbus/local network

+
+ + + + + +
+
Examples:
+
"192.168.1.3"
+
+
"41489"
+
+
+
+
+
+
+
+
+
+

+ +

+
+ +
+
+ + Type: string
+

The slave unit ID of the device for protocol families like modbus

+
+ + + + + +
+
Examples:
+
"1"
+
+
"255"
+
+
+
+
+
+
+
+
+
+

+ +

+
+ +
+
+ + Type: string
+

The network designator for this device in the family-defined format

+
+ + + + + +
+
Examples:
+
"192.168.1.0/24"
+
+
"12732"
+
+
+
+
+
+
+
+
+
+

+ +

+
+ +
+
+ + Type: object
+ No Additional Properties + + + + + + +
+
+
+

+ +

+
+ +
+

+ +

+

All properties whose name matches the following regular expression must respect the following conditions

+ Property name regular expression: ^[a-z_]+$ +
+ + Type: string
+ + + + + + + +
+
+
+
+
+
+
+
+
+
+
+

+ +

+
+ +
+
+ + Type: string
+

Specifies that this is a shadow of the indicated device

+
+ + + + + + +
+
+
+
+
+
+
+

+ +

+
+ +
+
+ + Type: string
+

The device id of the node's parent

+
+ + + + + + +
+
+
+
+
+
+
+

+ +

+
+ +
+
+ + Type: boolean
+

If true, then the system should not try to validate the refs

+
+ + + + + + +
+
+
+
+
+
+
+

+ +

+
+ +
+
+ + Type: string
+

The family designator, used only when the entry is not keyed in a family map

+
+ + + + + +
+
Examples:
+
"ipv4"
+
+
"bacnet"
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+

+ +

+
+ +
+
+ + Type: object
+ No Additional Properties + + + + + + +
+
+
+

+ +

+
+ +
+

+ +

+

All properties whose name matches the following regular expression must respect the following conditions

+ Property name regular expression: ^[a-z][a-z0-9]*(_[a-z0-9]+)*$ +
+ +

Family Localnet Model

Type: object
+

The type of network

+
+ + No Additional Properties + + + + + + +
+
+
+

+ +

+
+ +
+
+ + Type: string
+

The address of a device on the fieldbus/local network

+
+ + + + + +
+
Examples:
+
"192.168.1.3"
+
+
"41489"
+
+
+
+
+
+
+
+
+
+

+ +

+
+ +
+
+ + Type: string
+

The slave unit ID of the device for protocol families like modbus

+
+ + + + + +
+
Examples:
+
"1"
+
+
"255"
+
+
+
+
+
+
+
+
+
+

+ +

+
+ +
+
+ + Type: string
+

The network designator for this device in the family-defined format

+
+ + + + + +
+
Examples:
+
"192.168.1.0/24"
+
+
"12732"
+
+
+
+
+
+
+
+
+
+

+ +

+
+ +
+
+ + Type: object
+ No Additional Properties + + + + + + +
+
+
+

+ +

+
+ +
+

+ +

+

All properties whose name matches the following regular expression must respect the following conditions

+ Property name regular expression: ^[a-z_]+$ +
+ + Type: string
+ + + + + + + +
+
+
+
+
+
+
+
+
+
+
+

+ +

+
+ +
+
+ + Type: string
+

Specifies that this is a shadow of the indicated device

+
+ + + + + + +
+
+
+
+
+
+
+

+ +

+
+ +
+
+ + Type: string
+

The device id of the node's parent

+
+ + + + + + +
+
+
+
+
+
+
+

+ +

+
+ +
+
+ + Type: boolean
+

If true, then the system should not try to validate the refs

+
+ + + + + + +
+
+
+
+
+
+
+

+ +

+
+ +
+
+ + Type: string
+

The family designator, used only when the entry is not keyed in a family map

+
+ + + + + +
+
Examples:
+
"ipv4"
+
+
"bacnet"
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+

+ +

+
+ +
+
+ +

Testing Model

Type: object
+

Testing target parameters

+
+ + No Additional Properties + + + + + + +
+
+
+

+ +

+
+ +
+
+ + Type: boolean
+ + + + + + + +
+
+
+
+
+
+
+

+ +

+
+ +
+
+ + Type: object
+ No Additional Properties + + + + + + +
+
+
+

+ +

+
+ +
+

+ +

+

All properties whose name matches the following regular expression must respect the following conditions

+ Property name regular expression: ^[a-z][a-z0-9]*(_[a-z0-9]+)*$ +
+ +

Target Testing Model

Type: object
+ + + No Additional Properties + + + + + + +
+
+
+

+ +

+
+ +
+
+ + Type: string
+

Point name used for testing

+
+ + + + + + +
+
+
+
+
+
+
+

+ +

+
+ +
+
+ + Type: object
+

Value used for testing

+
+ + + + + + +
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+

+ +

+
+ +
+
+ + Type: object
+ No Additional Properties + + + + + + +
+
+
+

+ +

+
+ +
+

+ +

+

All properties whose name matches the following regular expression must respect the following conditions

+ Property name regular expression: ^[a-z][a-z0-9]*(_[a-z0-9]+)*$ +
+ +

Blob Update Testing Model

Type: object
+ + + No Additional Properties + + + + + + +
+
+
+

+ +

+
+ +
+
+ + Type: string
+

Blob name for the payload

+
+ + + + + + +
+
+
+
+
+
+
+

+ +

+
+ +
+
+ + Type: string
+

URL for OTA update

+
+ + + + + + +
+
+
+
+
+
+
+

+ +

+
+ +
+
+ + Type: string
+

SHA256 hash of the payload

+
+ + + + + + +
+
+
+
+
+
+
+

+ +

+
+ +
+
+ + Type: string
+

Expected software version

+
+ + + + + + +
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+

+ +

+
+ +
+
+ +

Testing Model

Type: object
+

Model of supported features

+
+ + No Additional Properties + + + + + + +
+
+
+

+ +

+
+ +
+

+ +

+

All properties whose name matches the following regular expression must respect the following conditions

+ Property name regular expression: ^[._a-zA-Z]+$ +
+ +

Feature Discovery

Type: object
+

Object representation for for a single feature

+
+ + No Additional Properties + + + + + + +
+
+
+

+ +

+
+ +
+
+ +

FeatureStage

Type: enum (of string)
+

Stage of a feature implemenation

+
+ +
+

Must be one of:

+
  • "disabled"
  • "alpha"
  • "preview"
  • "beta"
  • "stable"
+
+ + + + + + +
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+

+ +

+
+ +
+
+ +

Alarmset Model

Type: object
+

Alarmset representing the abstract system expectation for what the device should be doing, and how it should be configured and operated. This block specifies the expected alarms that a device holds

+
+ + No Additional Properties + + + + + + +
+
+
+

+ +

+
+ +
+
+ + Type: object
+

Information about a specific alarm name of the device.

+
No Additional Properties + + + + + + +
+
+
+

+ +

+
+ +
+

+ +

+

All properties whose name matches the following regular expression must respect the following conditions

+ Property name regular expression: ^[a-z][a-z0-9]*(_[a-z0-9]+)*$ +
+ +

Alarm Alarmset Model

Type: object
+

Information about a specific alarm name of the device.

+
+ + No Additional Properties + + + + + + +
+
+
+

+ +

+
+ +
+
+ + Type: string
+

Expected unit configuration for the alarm

+
+ + + + + + +
+
+
+
+
+
+
+

+ +

+
+ +
+
+ + Type: enum (of string)
+

Expected data type for the alarm

+
+

Must be one of:

+
  • "string"
  • "boolean"
  • "number"
+
+ + + + + + +
+
+
+
+
+
+
+

+ +

+
+ +
+
+ + Type: string
+

Detailed description of this alarm

+
+ + + + + + +
+
+
+
+
+
+
+

+ +

+
+ +
+
+ + Type: boolean
+

Indicates if this alarm is writable (else read-only)

+
+ + + + + + +
+
+
+
+
+
+
+

+ +

+
+ +
+
+ + Type: object
+

Represents the expected baseline value of the alarm

+
+ + + + + +
+
Example:
+
22
+
+
+
+
+
+
+
+
+
+

+ +

+
+ +
+
+ + Type: number
+

Maximum deviation from baseline_value

+
+ + + + + +
+
Example:
+
2
+
+
+
+
+
+
+
+
+
+

+ +

+
+ +
+
+ + Type: enum (of string)
+

Expected state when baseline_value is set as the set_value for this alarm the config message

+
+

Must be one of:

+
  • "applied"
  • "updating"
  • "overridden"
  • "invalid"
  • "failure"
+
+ + + + + + +
+
+
+
+
+
+
+

+ +

+
+ +
+
+ + Type: number
+

Represents the lower bound of the error threshold for a alarm

+
+ + + + + +
+
Example:
+
0
+
+
+
+
+
+
+
+
+
+

+ +

+
+ +
+
+ + Type: number
+

Represents the upper bound of the error threshold for a alarm

+
+ + + + + +
+
Example:
+
100
+
+
+
+
+
+
+
+
+
+

+ +

+
+ +
+
+ + Type: integer
+

Represents the limit in seconds that a alarm can be unchanged for

+
+ + + + + +
+
Example:
+
3600
+
+
+
+
+
+
+
+
+
+

+ +

+
+ +
+
+ + Type: number
+

Triggering threshold for partial cov update publishing

+
+ + + + + +
+
Example:
+
1
+
+
+
+
+
+
+
+
+
+

+ +

+
+ +
+
+ + Type: string
+

Mapping for the alarm to an internal resource (e.g. BACnet object reference)

+
+ + + + + +
+
Examples:
+
"AI3"
+
+
"400070"
+
+
+
+
+
+
+
+
+
+

+ +

+
+ +
+
+ + Type: object
+ No Additional Properties + + + + + + +
+
+
+

+ +

+
+ +
+

+ +

+

All properties whose name matches the following regular expression must respect the following conditions

+ Property name regular expression: ^[a-z_]+$ +
+ + Type: string
+ + + + + + + +
+
+
+
+
+
+
+
+
+
+
+

+ +

+
+ +
+
+ + Type: array
+

Tags assosciated with the alarm

+
+ + + + + +

All items must be unique

No Additional Items

Each item of this array must be:

+
+
+ + + Type: object
+Must match regular expression: ^[a-z0-9]+$ + + + + + + +
+

+
Examples:
+
[
+    "lighting"
+]
+
+
[
+    "energy",
+    "hvac"
+]
+
+
+
+
+
+
+
+
+
+

+ +

+
+ +
+
+ + Type: object
+

Collection of family alarm information

+
No Additional Properties + + + + + + +
+
+
+

+ +

+
+ +
+

+ +

+

All properties whose name matches the following regular expression must respect the following conditions

+ Property name regular expression: ^[-_.:/a-zA-Z0-9]+$ +
+ +

Ref Discovery

Type: object
+

Object representation for for a single point reference discovery

+
+ + No Additional Properties + + + + + + +
+
+
+

+ +

+
+ +
+
+ + Type: string
+

Point descriptor for this point

+
Must match regular expression: ^[a-z][a-z0-9]*(_[a-z0-9]+)*$ + + + + + + +
+
+
+
+
+
+
+

+ +

+
+ +
+
+ + Type: string
+

Friendly name for the reference, if known

+
+ + + + + + +
+
+
+
+
+
+
+

+ +

+
+ +
+
+ + Type: array of string
+

List of possible enumerated values for the point

+
+ + + + + + No Additional Items

Each item of this array must be:

+
+
+ + + Type: string
+ + + + + + + +
+
+
+
+
+
+
+
+
+

+ +

+
+ +
+
+ + Type: string
+

Current or default unit for this point

+
+ + + + + + +
+
+
+
+
+
+
+

+ +

+
+ +
+
+ + Type: string
+

Family reference for structures

+
+ + + + + + +
+
+
+
+
+
+
+

+ +

+
+ +
+
+ + Type: string
+

Current or default type for this point

+
+ + + + + + +
+
+
+
+
+
+
+

+ +

+
+ +
+
+ + Type: boolean
+

Indicates if this point is writable or not

+
+ + + + + + +
+
+
+
+
+
+
+

+ +

+
+ +
+
+ + Type: string
+

Human-readable description of this point

+
+ + + + + + +
+
+
+
+
+
+
+

+ +

+
+ +
+
+ +

Entry

Type: object
+ + + No Additional Properties + + + + + + +
+
+
+

+ +

+
+ +
+
+ + Type: string
+

A human-readable one-line description of the entry

+
+ + + + + +
+
Example:
+
"Point is not writable"
+
+
+
+
+
+
+
+
+
+

+ +

+
+ +
+
+ + Type: string
+

An optional extensive entry which can include more detail, e.g. a complete program stack-trace

+
+ + + + + + +
+
+
+
+
+
+
+

+ +

+
+ +
+
+ +

Category

Type: string
+ + +

+ +

+
+ + + Type: object
+Must match regular expression: ^system\.base\.start$ + + + + + + +
+ + + Type: object
+Must match regular expression: ^system\.base\.shutdown$ + + + + + + +
+ + + Type: object
+Must match regular expression: ^system\.base\.ready$ + + + + + + +
+ + + Type: object
+Must match regular expression: ^system\.config\.receive$ + + + + + + +
+ + + Type: object
+Must match regular expression: ^system\.config\.parse$ + + + + + + +
+ + + Type: object
+Must match regular expression: ^system\.config\.apply$ + + + + + + +
+ + + Type: object
+Must match regular expression: ^system\.auth\.login$ + + + + + + +
+ + + Type: object
+Must match regular expression: ^system\.auth\.logout$ + + + + + + +
+ + + Type: object
+Must match regular expression: ^system\.auth\.fail$ + + + + + + +
+ + + Type: object
+Must match regular expression: ^pointset\.point\.nominal$ + + + + + + +
+ + + Type: object
+Must match regular expression: ^pointset\.point\.applied$ + + + + + + +
+ + + Type: object
+Must match regular expression: ^pointset\.point\.updating$ + + + + + + +
+ + + Type: object
+Must match regular expression: ^pointset\.point\.overridden$ + + + + + + +
+ + + Type: object
+Must match regular expression: ^pointset\.point\.failure$ + + + + + + +
+ + + Type: object
+Must match regular expression: ^pointset\.point\.invalid$ + + + + + + +
+ + + Type: object
+Must match regular expression: ^localnet\.network$ + + + + + + +
+ + + Type: object
+Must match regular expression: ^localnet\.network\.connect$ + + + + + + +
+ + + Type: object
+Must match regular expression: ^gateway\.setup\.attach$ + + + + + + +
+ + + Type: object
+Must match regular expression: ^gateway\.proxy\.target$ + + + + + + +
+ + + Type: object
+Must match regular expression: ^gateway\.proxy\.connect$ + + + + + + +
+ + + Type: object
+Must match regular expression: ^discovery\.family\.scan$ + + + + + + +
+ + + Type: object
+Must match regular expression: ^discovery\.device\.enumerate$ + + + + + + +
+ + + Type: object
+Must match regular expression: ^discovery\.point\.describe$ + + + + + + +
+ + + Type: object
+Must match regular expression: ^mapping\.device\.apply$ + + + + + + +
+ + + Type: object
+Must match regular expression: ^blobset\.blob$ + + + + + + +
+ + + Type: object
+Must match regular expression: ^blobset\.blob\.update$ + + + + + + +
+ + + Type: object
+Must match regular expression: ^blobset\.blob\.receive$ + + + + + + +
+ + + Type: object
+Must match regular expression: ^blobset\.blob\.fetch$ + + + + + + +
+ + + Type: object
+Must match regular expression: ^blobset\.blob\.parse$ + + + + + + +
+ + + Type: object
+Must match regular expression: ^blobset\.blob\.apply$ + + + + + + +
+ + + Type: object
+Must match regular expression: ^blobset\.blob\.abort$ + + + + + + +
+ + + Type: object
+Must match regular expression: ^blobset\.blob\.rollback$ + + + + + + +
+ + + Type: object
+Must match regular expression: ^validation\.device\.receive$ + + + + + + +
+ + + Type: object
+Must match regular expression: ^validation\.device\.schema$ + + + + + + +
+ + + Type: object
+Must match regular expression: ^validation\.device\.content$ + + + + + + +
+ + + Type: object
+Must match regular expression: ^validation\.device\.multiple$ + + + + + + +
+ + + Type: object
+Must match regular expression: ^validation\.device\.extra$ + + + + + + +
+ + + Type: object
+Must match regular expression: ^validation\.summary\.report$ + + + + + + +
+ + + Type: object
+Must match regular expression: ^validation\.feature\.sequence$ + + + + + + +
+ + + Type: object
+Must match regular expression: ^validation\.feature\.schema$ + + + + + + +
+ + + Type: object
+Must match regular expression: ^validation\.feature\.capability$ + + + + + + +
+ + + Type: object
+Must match regular expression: ^device(\.[a-z]+((_[a-z]+))*)+$ + + + + + + +
+ + + + + + +
+
+
+
+
+
+
+

+ +

+
+ +
+
+ + Type: stringFormat: date-time
+

FC 3339 UTC timestamp the condition was triggered, or most recently updated

+
+ + + + + +
+
Example:
+
"2018-08-26T21:39:28.364Z"
+
+
+
+
+
+
+
+
+
+

+ +

+
+ +
+
+ + Type: integer
+

The status level should conform to the numerical Stackdriver LogEntry levels. The DEFAULT value of 0 is not allowed (lowest value is 100, maximum 800).

+
+ + + +

Value must be greater or equal to 100 and lesser or equal to 800 and a multiple of 1

+ +
+
Example:
+
600
+
+
+
+
+
+
+
+
+
+
+
+
+
+

+ +

+
+ +
+
+ +

Ancillary Properties

Type: object
+

Arbitrary blob of json associated with this point

+
+ + + + + + + + +
+
+
+
+
+
+
+

+ +

+
+ +
+
+ + Type: object
+ No Additional Properties + + + + + + +
+
+
+

+ +

+
+ +
+

+ +

+

All properties whose name matches the following regular expression must respect the following conditions

+ Property name regular expression: ^[a-z_]+$ +
+ + Type: string
+ + + + + + + +
+
+
+
+
+
+
+
+
+
+
+

+ +

+
+ +
+
+ + Type: object
+ + + + + + + +
+
+
+

+ +

+
+ +
+
+ +

Family Discovery

Type: object
+

Discovery information for a protocol family.

+
+ + No Additional Properties + + + + + + +
+
+
+

+ +

+
+ +
+
+ + Type: string
+

Device addr in the namespace of the given family

+
Must match regular expression: ^[-_.:0-9A-Za-z]+$ + + + + + + +
+
+
+
+
+
+
+

+ +

+
+ +
+
+ + Type: integer
+

Port number for the family connection (e.g. UDP port for BACnet/IP)

+
+ + + + + + +
+
+
+
+
+
+
+

+ +

+
+ +
+
+ + Type: string
+

Point reference in the namespace of the given family

+
Must match regular expression: ^[-_.:/#0-9A-Za-z]+$ + + + + + + +
+
+
+
+
+
+
+
+
+
+
+

+ +

+
+ +
+
+ + Type: object
+

Reference links to alternate families

+
No Additional Properties + + + + + + +
+
+
+

+ +

+
+ +
+

+ +

+

All properties whose name matches the following regular expression must respect the following conditions

+ Property name regular expression: ^[a-z][a-z0-9]*(_[a-z0-9]+)*$ +
+ +

Family Discovery

Type: object
+

Discovery information for a protocol family.

+
+ + No Additional Properties + + + + + + +
+
+
+

+ +

+
+ +
+
+ + Type: string
+

Device addr in the namespace of the given family

+
Must match regular expression: ^[-_.:0-9A-Za-z]+$ + + + + + + +
+
+
+
+
+
+
+

+ +

+
+ +
+
+ + Type: integer
+

Port number for the family connection (e.g. UDP port for BACnet/IP)

+
+ + + + + + +
+
+
+
+
+
+
+

+ +

+
+ +
+
+ + Type: string
+

Point reference in the namespace of the given family

+
Must match regular expression: ^[-_.:/#0-9A-Za-z]+$ + + + + + + +
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+

+ +

+
+ +
+
+ + Type: boolean
+ + + + + + + +
+
+
+
+
+
+
+

+ +

+
+ +
+
+ + Type: boolean
+ + + + + + + +
+
+
+
+
+
+
+

+ +

+
+ +
+
+ + Type: integer
+

Minimum time between sample updates for the device (including complete and COV updates). Updates more frequent than this should be coalesced into one update.

+
+ + + +

Value must be greater or equal to 0 and lesser or equal to 86400

+ + +
+
+
+
+
+
+
+

+ +

+
+ +
+
+ + Type: integer
+

Maximum time between samples for the device to send out a complete update. It can send out updates more frequently than this. Default to 600.

+
+ + + +

Value must be greater or equal to 1 and lesser or equal to 86400

+ + +
+
+
+
+
+
+
+
+
+
+
+

+ +

+
+ +
+
+ +

Pointset Model

Type: object
+

Pointset representing the abstract system expectation for what the device should be doing, and how it should be configured and operated. This block specifies the expected points that a device holds

+
+ + No Additional Properties + + + + + + +
+
+
+

+ +

+
+ +
+
+ + Type: object
+

Information about a specific point name of the device.

+
No Additional Properties + + + + + + +
+
+
+

+ +

+
+ +
+

+ +

+

All properties whose name matches the following regular expression must respect the following conditions

+ Property name regular expression: ^[a-z][a-z0-9]*(_[a-z0-9]+)*$ +
+ +

Point Pointset Model

Type: object
+

Information about a specific point name of the device.

+
+ + No Additional Properties + + + + + + +
+
+
+

+ +

+
+ +
+
+ + Type: string
+

Expected unit configuration for the point

+
+ + + + + + +
+
+
+
+
+
+
+

+ +

+
+ +
+
+ + Type: string
+

Actual input unit that is sent from the device

+
+ + + + + + +
+
+
+
+
+
+
+

+ +

+
+ +
+
+ + Type: enum (of string)
+

Expected data type for the point

+
+

Must be one of:

+
  • "string"
  • "boolean"
  • "number"
+
+ + + + + + +
+
+
+
+
+
+
+

+ +

+
+ +
+
+ + Type: string
+

Detailed description of this point

+
+ + + + + + +
+
+
+
+
+
+
+

+ +

+
+ +
+
+ + Type: boolean
+

Indicates if this point is writable (else read-only)

+
+ + + + + + +
+
+
+
+
+
+
+

+ +

+
+ +
+
+ + Type: object
+

Represents the expected baseline value of the point

+
+ + + + + +
+
Example:
+
22
+
+
+
+
+
+
+
+
+
+

+ +

+
+ +
+
+ + Type: number
+

Maximum deviation from baseline_value

+
+ + + + + +
+
Example:
+
2
+
+
+
+
+
+
+
+
+
+

+ +

+
+ +
+
+ + Type: enum (of string)
+

Expected state when baseline_value is set as the set_value for this point the config message

+
+

Must be one of:

+
  • "applied"
  • "updating"
  • "overridden"
  • "invalid"
  • "failure"
+
+ + + + + + +
+
+
+
+
+
+
+

+ +

+
+ +
+
+ + Type: number
+

Represents the lower bound of the error threshold for a point

+
+ + + + + +
+
Example:
+
0
+
+
+
+
+
+
+
+
+
+

+ +

+
+ +
+
+ + Type: number
+

Represents the upper bound of the error threshold for a point

+
+ + + + + +
+
Example:
+
100
+
+
+
+
+
+
+
+
+
+

+ +

+
+ +
+
+ + Type: integer
+

Represents the limit in seconds that a point can be unchanged for

+
+ + + + + +
+
Example:
+
3600
+
+
+
+
+
+
+
+
+
+

+ +

+
+ +
+
+ + Type: number
+

Triggering threshold for partial cov update publishing

+
+ + + + + +
+
Example:
+
1
+
+
+
+
+
+
+
+
+
+

+ +

+
+ +
+
+ + Type: string
+

Mapping for the point to an internal resource (e.g. BACnet object reference)

+
+ + + + + +
+
Examples:
+
"AI3"
+
+
"400070"
+
+
+
+
+
+
+
+
+
+

+ +

+
+ +
+
+ + Type: string
+

Complete resolved URL of this point as determined by the gateway and ref blocks

+
+ + + + + +
+
Example:
+
"bacnet://1234/AV/1"
+
+
+
+
+
+
+
+
+
+

+ +

+
+ +
+
+ + Type: object
+ No Additional Properties + + + + + + +
+
+
+

+ +

+
+ +
+

+ +

+

All properties whose name matches the following regular expression must respect the following conditions

+ Property name regular expression: ^[a-z_]+$ +
+ + Type: string
+ + + + + + + +
+
+
+
+
+
+
+
+
+
+
+

+ +

+
+ +
+
+ + Type: array
+

Tags assosciated with the point

+
+ + + + + +

All items must be unique

No Additional Items

Each item of this array must be:

+
+
+ + + Type: object
+Must match regular expression: ^[a-z0-9]+$ + + + + + + +
+

+
Examples:
+
[
+    "lighting"
+]
+
+
[
+    "energy",
+    "hvac"
+]
+
+
+
+
+
+
+
+
+
+

+ +

+
+ +
+
+ + Type: object
+

Collection of family point information

+
No Additional Properties + + + + + + +
+
+
+

+ +

+
+ +
+

+ +

+

All properties whose name matches the following regular expression must respect the following conditions

+ Property name regular expression: ^[-_.:/a-zA-Z0-9]+$ +
+ +

Ref Discovery

Type: object
+

Object representation for for a single point reference discovery

+
+ + No Additional Properties + + + + + + +
+
+
+

+ +

+
+ +
+
+ + Type: string
+

Point descriptor for this point

+
Must match regular expression: ^[a-z][a-z0-9]*(_[a-z0-9]+)*$ + + + + + + +
+
+
+
+
+
+
+

+ +

+
+ +
+
+ + Type: string
+

Friendly name for the reference, if known

+
+ + + + + + +
+
+
+
+
+
+
+

+ +

+
+ +
+
+ + Type: array of string
+

List of possible enumerated values for the point

+
+ + + + + + No Additional Items

Each item of this array must be:

+
+
+ + + Type: string
+ + + + + + + +
+
+
+
+
+
+
+
+
+

+ +

+
+ +
+
+ + Type: string
+

Current or default unit for this point

+
+ + + + + + +
+
+
+
+
+
+
+

+ +

+
+ +
+
+ + Type: string
+

Family reference for structures

+
+ + + + + + +
+
+
+
+
+
+
+

+ +

+
+ +
+
+ + Type: string
+

Current or default type for this point

+
+ + + + + + +
+
+
+
+
+
+
+

+ +

+
+ +
+
+ + Type: boolean
+

Indicates if this point is writable or not

+
+ + + + + + +
+
+
+
+
+
+
+

+ +

+
+ +
+
+ + Type: string
+

Human-readable description of this point

+
+ + + + + + +
+
+
+
+
+
+
+

+ +

+
+ +
+
+ +

Entry

Type: object
+ + + No Additional Properties + + + + + + +
+
+
+

+ +

+
+ +
+
+ + Type: string
+

A human-readable one-line description of the entry

+
+ + + + + +
+
Example:
+
"Point is not writable"
+
+
+
+
+
+
+
+
+
+

+ +

+
+ +
+
+ + Type: string
+

An optional extensive entry which can include more detail, e.g. a complete program stack-trace

+
+ + + + + + +
+
+
+
+
+
+
+

+ +

+
+ +
+
+ +

Category

Type: string
+ + +

+ +

+
+ + + Type: object
+Must match regular expression: ^system\.base\.start$ + + + + + + +
+ + + Type: object
+Must match regular expression: ^system\.base\.shutdown$ + + + + + + +
+ + + Type: object
+Must match regular expression: ^system\.base\.ready$ + + + + + + +
+ + + Type: object
+Must match regular expression: ^system\.config\.receive$ + + + + + + +
+ + + Type: object
+Must match regular expression: ^system\.config\.parse$ + + + + + + +
+ + + Type: object
+Must match regular expression: ^system\.config\.apply$ + + + + + + +
+ + + Type: object
+Must match regular expression: ^system\.auth\.login$ + + + + + + +
+ + + Type: object
+Must match regular expression: ^system\.auth\.logout$ + + + + + + +
+ + + Type: object
+Must match regular expression: ^system\.auth\.fail$ + + + + + + +
+ + + Type: object
+Must match regular expression: ^pointset\.point\.nominal$ + + + + + + +
+ + + Type: object
+Must match regular expression: ^pointset\.point\.applied$ + + + + + + +
+ + + Type: object
+Must match regular expression: ^pointset\.point\.updating$ + + + + + + +
+ + + Type: object
+Must match regular expression: ^pointset\.point\.overridden$ + + + + + + +
+ + + Type: object
+Must match regular expression: ^pointset\.point\.failure$ + + + + + + +
+ + + Type: object
+Must match regular expression: ^pointset\.point\.invalid$ + + + + + + +
+ + + Type: object
+Must match regular expression: ^localnet\.network$ + + + + + + +
+ + + Type: object
+Must match regular expression: ^localnet\.network\.connect$ + + + + + + +
+ + + Type: object
+Must match regular expression: ^gateway\.setup\.attach$ + + + + + + +
+ + + Type: object
+Must match regular expression: ^gateway\.proxy\.target$ + + + + + + +
+ + + Type: object
+Must match regular expression: ^gateway\.proxy\.connect$ + + + + + + +
+ + + Type: object
+Must match regular expression: ^discovery\.family\.scan$ + + + + + + +
+ + + Type: object
+Must match regular expression: ^discovery\.device\.enumerate$ + + + + + + +
+ + + Type: object
+Must match regular expression: ^discovery\.point\.describe$ + + + + + + +
+ + + Type: object
+Must match regular expression: ^mapping\.device\.apply$ + + + + + + +
+ + + Type: object
+Must match regular expression: ^blobset\.blob$ + + + + + + +
+ + + Type: object
+Must match regular expression: ^blobset\.blob\.update$ + + + + + + +
+ + + Type: object
+Must match regular expression: ^blobset\.blob\.receive$ + + + + + + +
+ + + Type: object
+Must match regular expression: ^blobset\.blob\.fetch$ + + + + + + +
+ + + Type: object
+Must match regular expression: ^blobset\.blob\.parse$ + + + + + + +
+ + + Type: object
+Must match regular expression: ^blobset\.blob\.apply$ + + + + + + +
+ + + Type: object
+Must match regular expression: ^blobset\.blob\.abort$ + + + + + + +
+ + + Type: object
+Must match regular expression: ^blobset\.blob\.rollback$ + + + + + + +
+ + + Type: object
+Must match regular expression: ^validation\.device\.receive$ + + + + + + +
+ + + Type: object
+Must match regular expression: ^validation\.device\.schema$ + + + + + + +
+ + + Type: object
+Must match regular expression: ^validation\.device\.content$ + + + + + + +
+ + + Type: object
+Must match regular expression: ^validation\.device\.multiple$ + + + + + + +
+ + + Type: object
+Must match regular expression: ^validation\.device\.extra$ + + + + + + +
+ + + Type: object
+Must match regular expression: ^validation\.summary\.report$ + + + + + + +
+ + + Type: object
+Must match regular expression: ^validation\.feature\.sequence$ + + + + + + +
+ + + Type: object
+Must match regular expression: ^validation\.feature\.schema$ + + + + + + +
+ + + Type: object
+Must match regular expression: ^validation\.feature\.capability$ + + + + + + +
+ + + Type: object
+Must match regular expression: ^device(\.[a-z]+((_[a-z]+))*)+$ + + + + + + +
+ + + + + + +
+
+
+
+
+
+
+

+ +

+
+ +
+
+ + Type: stringFormat: date-time
+

FC 3339 UTC timestamp the condition was triggered, or most recently updated

+
+ + + + + +
+
Example:
+
"2018-08-26T21:39:28.364Z"
+
+
+
+
+
+
+
+
+
+

+ +

+
+ +
+
+ + Type: integer
+

The status level should conform to the numerical Stackdriver LogEntry levels. The DEFAULT value of 0 is not allowed (lowest value is 100, maximum 800).

+
+ + + +

Value must be greater or equal to 100 and lesser or equal to 800 and a multiple of 1

+ +
+
Example:
+
600
+
+
+
+
+
+
+
+
+
+
+
+
+
+

+ +

+
+ +
+
+ +

Ancillary Properties

Type: object
+

Arbitrary blob of json associated with this point

+
+ + + + + + + + +
+
+
+
+
+
+
+

+ +

+
+ +
+
+ + Type: object
+ No Additional Properties + + + + + + +
+
+
+

+ +

+
+ +
+

+ +

+

All properties whose name matches the following regular expression must respect the following conditions

+ Property name regular expression: ^[a-z_]+$ +
+ + Type: string
+ + + + + + + +
+
+
+
+
+
+
+
+
+
+
+

+ +

+
+ +
+
+ + Type: object
+ + + + + + + +
+
+
+

+ +

+
+ +
+
+ +

Family Discovery

Type: object
+

Discovery information for a protocol family.

+
+ + No Additional Properties + + + + + + +
+
+
+

+ +

+
+ +
+
+ + Type: string
+

Device addr in the namespace of the given family

+
Must match regular expression: ^[-_.:0-9A-Za-z]+$ + + + + + + +
+
+
+
+
+
+
+

+ +

+
+ +
+
+ + Type: integer
+

Port number for the family connection (e.g. UDP port for BACnet/IP)

+
+ + + + + + +
+
+
+
+
+
+
+

+ +

+
+ +
+
+ + Type: string
+

Point reference in the namespace of the given family

+
Must match regular expression: ^[-_.:/#0-9A-Za-z]+$ + + + + + + +
+
+
+
+
+
+
+
+
+
+
+

+ +

+
+ +
+
+ + Type: object
+

Reference links to alternate families

+
No Additional Properties + + + + + + +
+
+
+

+ +

+
+ +
+

+ +

+

All properties whose name matches the following regular expression must respect the following conditions

+ Property name regular expression: ^[a-z][a-z0-9]*(_[a-z0-9]+)*$ +
+ +

Family Discovery

Type: object
+

Discovery information for a protocol family.

+
+ + No Additional Properties + + + + + + +
+
+
+

+ +

+
+ +
+
+ + Type: string
+

Device addr in the namespace of the given family

+
Must match regular expression: ^[-_.:0-9A-Za-z]+$ + + + + + + +
+
+
+
+
+
+
+

+ +

+
+ +
+
+ + Type: integer
+

Port number for the family connection (e.g. UDP port for BACnet/IP)

+
+ + + + + + +
+
+
+
+
+
+
+

+ +

+
+ +
+
+ + Type: string
+

Point reference in the namespace of the given family

+
Must match regular expression: ^[-_.:/#0-9A-Za-z]+$ + + + + + + +
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+

+ +

+
+ +
+
+ + Type: string
+

Virtual equipment mapping linking this local point to a remote point in another device

+
Must match regular expression: .*[-_0-9a-zA-Z$]+:[a-z0-9_]+.* + + + + + +
+
Examples:
+
"VAV-3:supply_air_flowrate_sensor"
+
+
"VAV-101:zone_air_temp_sensor - VAV-102:zone_air_temp_sensor"
+
+
+
+
+
+
+
+
+
+

+ +

+
+ +
+
+ + Type: object
+

State value mapping for this point (e.g. 0.0: OFF)

+
+ + + + + + +
+
+
+

+ +

+
+ +
+

Each additional property must conform to the following schema

+ + Type: string
+ + + + + + + +
+
+
+
+
+
+
+
+
+
+
+

+ +

+
+ +
+
+ +

Building Translation

Type: object
+

Discovery result with implicit enumeration

+
+ + No Additional Properties + + + + + + +
+
+
+

+ +

+
+ +
+
+ + Type: string
+

dotted path to present_value field

+
+ + + + + + +
+
+
+
+
+
+
+

+ +

+
+ +
+
+ + Type: object
+ + + + + + + +
+
+
+

+ +

+
+ +
+
+ + Type: string
+ + + + + + + +
+
+
+
+
+
+
+
+
+
+
+

+ +

+
+ +
+
+ + Type: string
+

Actual input unit that is sent from the device

+
+ + + + + + +
+
+
+
+
+
+
+

+ +

+
+ +
+
+ + Type: object
+ + + + + + + +
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+

+ +

+
+ +
+
+ + Type: boolean
+ + + + + + + +
+
+
+
+
+
+
+

+ +

+
+ +
+
+ + Type: boolean
+ + + + + + + +
+
+
+
+
+
+
+

+ +

+
+ +
+
+ + Type: integer
+

Minimum time between sample updates for the device (including complete and COV updates). Updates more frequent than this should be coalesced into one update.

+
+ + + +

Value must be greater or equal to 0 and lesser or equal to 86400

+ + +
+
+
+
+
+
+
+

+ +

+
+ +
+
+ + Type: integer
+

Maximum time between samples for the device to send out a complete update. It can send out updates more frequently than this. Default to 600.

+
+ + + +

Value must be greater or equal to 1 and lesser or equal to 86400

+ + +
+
+
+
+
+
+
+
+
+
+
+

+ +

+
+ +
+
+ + Type: object
+ + + + + + + +
+
+
+

+ +

+
+ +
+

+ +

+

All properties whose name matches the following regular expression must respect the following conditions

+ Property name regular expression: ^[a-z][a-z0-9]*(_[a-z0-9]+)*$ +
+ +

Discovery Events

Type: object
+

Discovery result with implicit discovery

+
+ + No Additional Properties + + + + + + +
+
+
+

+ +

+
+ +
+
+ + Type: stringFormat: date-time
+

RFC 3339 UTC timestamp the discover telemetry event was generated

+
+ + + + + +
+
Example:
+
"2019-01-17T14:02:29.364Z"
+
+
+
+
+
+
+
+
+
+

+ +

+
+ +
+
+ + Type: string
+

Version of the UDMI schema

+
+ + + + + + +
+
+
+
+
+
+
+

+ +

+
+ +
+
+ + Type: stringFormat: date-time
+

The event's discovery scan trigger's generation timestamp

+
+ + + + + +
+
Example:
+
"2019-01-17T14:02:29.364Z"
+
+
+
+
+
+
+
+
+
+

+ +

+
+ +
+
+ +

Entry

Type: object
+ + + No Additional Properties + + + + + + +
+
+
+

+ +

+
+ +
+
+ + Type: string
+

A human-readable one-line description of the entry

+
+ + + + + +
+
Example:
+
"Point is not writable"
+
+
+
+
+
+
+
+
+
+

+ +

+
+ +
+
+ + Type: string
+

An optional extensive entry which can include more detail, e.g. a complete program stack-trace

+
+ + + + + + +
+
+
+
+
+
+
+

+ +

+
+ +
+
+ +

Category

Type: string
+ + +

+ +

+
+ + + Type: object
+Must match regular expression: ^system\.base\.start$ + + + + + + +
+ + + Type: object
+Must match regular expression: ^system\.base\.shutdown$ + + + + + + +
+ + + Type: object
+Must match regular expression: ^system\.base\.ready$ + + + + + + +
+ + + Type: object
+Must match regular expression: ^system\.config\.receive$ + + + + + + +
+ + + Type: object
+Must match regular expression: ^system\.config\.parse$ + + + + + + +
+ + + Type: object
+Must match regular expression: ^system\.config\.apply$ + + + + + + +
+ + + Type: object
+Must match regular expression: ^system\.auth\.login$ + + + + + + +
+ + + Type: object
+Must match regular expression: ^system\.auth\.logout$ + + + + + + +
+ + + Type: object
+Must match regular expression: ^system\.auth\.fail$ + + + + + + +
+ + + Type: object
+Must match regular expression: ^pointset\.point\.nominal$ + + + + + + +
+ + + Type: object
+Must match regular expression: ^pointset\.point\.applied$ + + + + + + +
+ + + Type: object
+Must match regular expression: ^pointset\.point\.updating$ + + + + + + +
+ + + Type: object
+Must match regular expression: ^pointset\.point\.overridden$ + + + + + + +
+ + + Type: object
+Must match regular expression: ^pointset\.point\.failure$ + + + + + + +
+ + + Type: object
+Must match regular expression: ^pointset\.point\.invalid$ + + + + + + +
+ + + Type: object
+Must match regular expression: ^localnet\.network$ + + + + + + +
+ + + Type: object
+Must match regular expression: ^localnet\.network\.connect$ + + + + + + +
+ + + Type: object
+Must match regular expression: ^gateway\.setup\.attach$ + + + + + + +
+ + + Type: object
+Must match regular expression: ^gateway\.proxy\.target$ + + + + + + +
+ + + Type: object
+Must match regular expression: ^gateway\.proxy\.connect$ + + + + + + +
+ + + Type: object
+Must match regular expression: ^discovery\.family\.scan$ + + + + + + +
+ + + Type: object
+Must match regular expression: ^discovery\.device\.enumerate$ + + + + + + +
+ + + Type: object
+Must match regular expression: ^discovery\.point\.describe$ + + + + + + +
+ + + Type: object
+Must match regular expression: ^mapping\.device\.apply$ + + + + + + +
+ + + Type: object
+Must match regular expression: ^blobset\.blob$ + + + + + + +
+ + + Type: object
+Must match regular expression: ^blobset\.blob\.update$ + + + + + + +
+ + + Type: object
+Must match regular expression: ^blobset\.blob\.receive$ + + + + + + +
+ + + Type: object
+Must match regular expression: ^blobset\.blob\.fetch$ + + + + + + +
+ + + Type: object
+Must match regular expression: ^blobset\.blob\.parse$ + + + + + + +
+ + + Type: object
+Must match regular expression: ^blobset\.blob\.apply$ + + + + + + +
+ + + Type: object
+Must match regular expression: ^blobset\.blob\.abort$ + + + + + + +
+ + + Type: object
+Must match regular expression: ^blobset\.blob\.rollback$ + + + + + + +
+ + + Type: object
+Must match regular expression: ^validation\.device\.receive$ + + + + + + +
+ + + Type: object
+Must match regular expression: ^validation\.device\.schema$ + + + + + + +
+ + + Type: object
+Must match regular expression: ^validation\.device\.content$ + + + + + + +
+ + + Type: object
+Must match regular expression: ^validation\.device\.multiple$ + + + + + + +
+ + + Type: object
+Must match regular expression: ^validation\.device\.extra$ + + + + + + +
+ + + Type: object
+Must match regular expression: ^validation\.summary\.report$ + + + + + + +
+ + + Type: object
+Must match regular expression: ^validation\.feature\.sequence$ + + + + + + +
+ + + Type: object
+Must match regular expression: ^validation\.feature\.schema$ + + + + + + +
+ + + Type: object
+Must match regular expression: ^validation\.feature\.capability$ + + + + + + +
+ + + Type: object
+Must match regular expression: ^device(\.[a-z]+((_[a-z]+))*)+$ + + + + + + +
+ + + + + + +
+
+
+
+
+
+
+

+ +

+
+ +
+
+ + Type: stringFormat: date-time
+

FC 3339 UTC timestamp the condition was triggered, or most recently updated

+
+ + + + + +
+
Example:
+
"2018-08-26T21:39:28.364Z"
+
+
+
+
+
+
+
+
+
+

+ +

+
+ +
+
+ + Type: integer
+

The status level should conform to the numerical Stackdriver LogEntry levels. The DEFAULT value of 0 is not allowed (lowest value is 100, maximum 800).

+
+ + + +

Value must be greater or equal to 100 and lesser or equal to 800 and a multiple of 1

+ +
+
Example:
+
600
+
+
+
+
+
+
+
+
+
+
+
+
+
+

+ +

+
+ +
+
+ + Type: object
+ + + + + + + +
+
+
+
+
+
+
+

+ +

+
+ +
+
+ + Type: string
+

Optional source qualifier for the discovery scan (e.g., 'model')

+
+ + + + + + +
+
+
+
+
+
+
+

+ +

+
+ +
+
+ + Type: string
+

The primary address of the device (for indicated family)

+
+ + + + + + +
+
+
+
+
+
+
+

+ +

+
+ +
+
+ + Type: string
+

The family network designator of the discovered device

+
+ + + + + + +
+
+
+
+
+
+
+

+ +

+
+ +
+
+ + Type: integer
+

The active or passive series number of this result (matches reported state values)

+
+ + + + + + +
+
+
+
+
+
+
+

+ +

+
+ +
+
+ + Type: integer
+

Total number of records expected for this active scan generation (only populated on the event_no=0 start marker event)

+
+ + + +

Value must be greater or equal to 0

+ + +
+
+
+
+
+
+
+

+ +

+
+ +
+
+ + Type: array of string
+

List of target addresses for a partial discovery scan shard (only populated on the event_no=0 start marker event)

+
+ + + + + + No Additional Items

Each item of this array must be:

+
+
+ + + Type: string
+ + + + + + + +
+
+
+
+
+
+
+
+
+

+ +

+
+ +
+
+ + Type: object
+

Links to other address families (family and id)

+
No Additional Properties + + + + + + +
+
+
+

+ +

+
+ +
+

+ +

+

All properties whose name matches the following regular expression must respect the following conditions

+ Property name regular expression: ^[a-z][a-z0-9]*(_[a-z0-9]+)*$ +
+ +

Family Discovery

Type: object
+

Discovery information for a protocol family.

+
+ + No Additional Properties + + + + + + +
+
+
+

+ +

+
+ +
+
+ + Type: string
+

Device addr in the namespace of the given family

+
Must match regular expression: ^[-_.:0-9A-Za-z]+$ + + + + + + +
+
+
+
+
+
+
+

+ +

+
+ +
+
+ + Type: integer
+

Port number for the family connection (e.g. UDP port for BACnet/IP)

+
+ + + + + + +
+
+
+
+
+
+
+

+ +

+
+ +
+
+ + Type: string
+

Point reference in the namespace of the given family

+
Must match regular expression: ^[-_.:/#0-9A-Za-z]+$ + + + + + + +
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+

+ +

+
+ +
+
+ + Type: object
+

Registry iot cloud models

+
No Additional Properties + + + + + + +
+
+
+

+ +

+
+ +
+

+ +

+

All properties whose name matches the following regular expression must respect the following conditions

+ Property name regular expression: ^[A-Z]{2,6}-[1-9][0-9]*$ +
+ +

Cloud Model

Type: object
+

Information specific to how the device communicates with the cloud.

+
+ + No Additional Properties + + + + + + +
+
+
+

+ +

+
+ +
+
+ + Type: integer
+

Function version of this request

+
+ + + + + + +
+
+
+
+
+
+
+

+ +

+
+ +
+
+ + Type: enum (of string)
+

The key type used for cloud communication.

+
+

Must be one of:

+
  • "ES256"
  • "ES256_X509"
  • "RS256"
  • "RS256_X509"
+
+ + + + + + +
+
+
+
+
+
+
+

+ +

+
+ +
+
+ + Type: boolean
+

Indicates if the device private key is provided in the site model.

+
+ + + + + + +
+
+
+
+
+
+
+

+ +

+
+ +
+
+ + Type: enum (of string)
+

The cloud resource type

+
+

Must be one of:

+
  • "DIRECT"
  • "GATEWAY"
  • "FUNNEL"
  • "PROXIED"
  • "REGISTRY"
+
+ + + + + + +
+
+
+
+
+
+
+

+ +

+
+ +
+
+ +

Gateway Model

Type: object
+

Gateway Documentation

+
+ + No Additional Properties + + + + + + +
+
+
+

+ +

+
+ +
+
+ + Type: string
+

The device ID of the gateway the device is bound to

+
Must match regular expression: ^[A-Z]{2,6}-[1-9][0-9]*$ + + + + + +
+
Example:
+
"GAT-100"
+
+
+
+
+
+
+
+
+
+

+ +

+
+ +
+
+ +

Family Localnet Model

Type: object
+

The type of network

+
+ + No Additional Properties + + + + + + +
+
+
+

+ +

+
+ +
+
+ + Type: string
+

The address of a device on the fieldbus/local network

+
+ + + + + +
+
Examples:
+
"192.168.1.3"
+
+
"41489"
+
+
+
+
+
+
+
+
+
+

+ +

+
+ +
+
+ + Type: string
+

The slave unit ID of the device for protocol families like modbus

+
+ + + + + +
+
Examples:
+
"1"
+
+
"255"
+
+
+
+
+
+
+
+
+
+

+ +

+
+ +
+
+ + Type: string
+

The network designator for this device in the family-defined format

+
+ + + + + +
+
Examples:
+
"192.168.1.0/24"
+
+
"12732"
+
+
+
+
+
+
+
+
+
+

+ +

+
+ +
+
+ + Type: object
+ No Additional Properties + + + + + + +
+
+
+

+ +

+
+ +
+

+ +

+

All properties whose name matches the following regular expression must respect the following conditions

+ Property name regular expression: ^[a-z_]+$ +
+ + Type: string
+ + + + + + + +
+
+
+
+
+
+
+
+
+
+
+

+ +

+
+ +
+
+ + Type: string
+

Specifies that this is a shadow of the indicated device

+
+ + + + + + +
+
+
+
+
+
+
+

+ +

+
+ +
+
+ + Type: string
+

The device id of the node's parent

+
+ + + + + + +
+
+
+
+
+
+
+

+ +

+
+ +
+
+ + Type: boolean
+

If true, then the system should not try to validate the refs

+
+ + + + + + +
+
+
+
+
+
+
+

+ +

+
+ +
+
+ + Type: string
+

The family designator, used only when the entry is not keyed in a family map

+
+ + + + + +
+
Examples:
+
"ipv4"
+
+
"bacnet"
+
+
+
+
+
+
+
+
+
+
+
+
+
+

+ +

+
+ +
+
+ + Type: array of string
+

An array of all the device IDs which are bound to the device

+
+ + + + + + No Additional Items

Each item of this array must be:

+
+
+ + + Type: string
+Must match regular expression: ^[A-Z]{2,6}-[1-9][0-9]*$ + + + + + + +
+

+
Example:
+
[
+    "AHU-22"
+]
+
+
+
+
+
+
+
+
+
+
+
+
+
+

+ +

+
+ +
+
+ +

Config Cloud Model

Type: object
+

Information specific to how the device communicates with the cloud.

+
+ + No Additional Properties + + + + + + +
+
+
+

+ +

+
+ +
+
+ + Type: string
+

Config file to use. Within the config directory in the device's metadata directory

+
+ + + + + + +
+
+
+
+
+
+
+
+
+
+
+

+ +

+
+ +
+
+ + Type: boolean
+ + + + + + + +
+
+
+
+
+
+
+

+ +

+
+ +
+
+ + Type: string
+ + + + + + + +
+
+
+
+
+
+
+

+ +

+
+ +
+
+ + Type: string
+ + + + + + + +
+
+
+
+
+
+
+

+ +

+
+ +
+
+ + Type: stringFormat: date-time
+ + + + + + + +
+
+
+
+
+
+
+

+ +

+
+ +
+
+ + Type: stringFormat: date-time
+ + + + + + + +
+
+
+
+
+
+
+

+ +

+
+ +
+
+ + Type: stringFormat: date-time
+ + + + + + + +
+
+
+
+
+
+
+

+ +

+
+ +
+
+ + Type: stringFormat: date-time
+ + + + + + + +
+
+
+
+
+
+
+

+ +

+
+ +
+
+ + Type: string
+ + + + + + + +
+
+
+
+
+
+
+

+ +

+
+ +
+
+ +

Model Operation

Type: enum (of string)
+ + +
+

Must be one of:

+
  • "ERROR"
  • "CREATE"
  • "READ"
  • "REPLY"
  • "UPDATE"
  • "DELETE"
  • "MODIFY"
  • "PREVIEW"
  • "BIND"
  • "UNBIND"
  • "BOUND"
  • "ALLOW"
  • "BLOCK"
+
+ + + + + + +
+
+
+
+
+
+
+

+ +

+
+ +
+
+ + Type: string
+

Strigified version of the metadata object, used for internal backend processing

+
+ + + + + + +
+
+
+
+
+
+
+

+ +

+
+ +
+
+ + Type: object
+

If operating on the entire registry, then this contains results for multiple devices (batched operation).

+
+ + + + + + +
+
+
+

+ +

+
+ +
+

+ +

+

All properties whose name matches the following regular expression must respect the following conditions

+ Property name regular expression: ^[-._a-zA-Z0-9]+$ +
+ + Type: object
+ + + + + + + +
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+

+ +

+
+ +
+
+ + Type: object
+

Device iot cloud models

+
No Additional Properties + + + + + + +
+
+
+

+ +

+
+ +
+

+ +

+

All properties whose name matches the following regular expression must respect the following conditions

+ Property name regular expression: ^[A-Z]{2,6}-[1-9][0-9]*$ +
+ +

Cloud Model

Type: object
+

Information specific to how the device communicates with the cloud.

+
+ + No Additional Properties + + + + + + +
+
+
+

+ +

+
+ +
+
+ + Type: integer
+

Function version of this request

+
+ + + + + + +
+
+
+
+
+
+
+

+ +

+
+ +
+
+ + Type: enum (of string)
+

The key type used for cloud communication.

+
+

Must be one of:

+
  • "ES256"
  • "ES256_X509"
  • "RS256"
  • "RS256_X509"
+
+ + + + + + +
+
+
+
+
+
+
+

+ +

+
+ +
+
+ + Type: boolean
+

Indicates if the device private key is provided in the site model.

+
+ + + + + + +
+
+
+
+
+
+
+

+ +

+
+ +
+
+ + Type: enum (of string)
+

The cloud resource type

+
+

Must be one of:

+
  • "DIRECT"
  • "GATEWAY"
  • "FUNNEL"
  • "PROXIED"
  • "REGISTRY"
+
+ + + + + + +
+
+
+
+
+
+
+

+ +

+
+ +
+
+ +

Gateway Model

Type: object
+

Gateway Documentation

+
+ + No Additional Properties + + + + + + +
+
+
+

+ +

+
+ +
+
+ + Type: string
+

The device ID of the gateway the device is bound to

+
Must match regular expression: ^[A-Z]{2,6}-[1-9][0-9]*$ + + + + + +
+
Example:
+
"GAT-100"
+
+
+
+
+
+
+
+
+
+

+ +

+
+ +
+
+ +

Family Localnet Model

Type: object
+

The type of network

+
+ + No Additional Properties + + + + + + +
+
+
+

+ +

+
+ +
+
+ + Type: string
+

The address of a device on the fieldbus/local network

+
+ + + + + +
+
Examples:
+
"192.168.1.3"
+
+
"41489"
+
+
+
+
+
+
+
+
+
+

+ +

+
+ +
+
+ + Type: string
+

The slave unit ID of the device for protocol families like modbus

+
+ + + + + +
+
Examples:
+
"1"
+
+
"255"
+
+
+
+
+
+
+
+
+
+

+ +

+
+ +
+
+ + Type: string
+

The network designator for this device in the family-defined format

+
+ + + + + +
+
Examples:
+
"192.168.1.0/24"
+
+
"12732"
+
+
+
+
+
+
+
+
+
+

+ +

+
+ +
+
+ + Type: object
+ No Additional Properties + + + + + + +
+
+
+

+ +

+
+ +
+

+ +

+

All properties whose name matches the following regular expression must respect the following conditions

+ Property name regular expression: ^[a-z_]+$ +
+ + Type: string
+ + + + + + + +
+
+
+
+
+
+
+
+
+
+
+

+ +

+
+ +
+
+ + Type: string
+

Specifies that this is a shadow of the indicated device

+
+ + + + + + +
+
+
+
+
+
+
+

+ +

+
+ +
+
+ + Type: string
+

The device id of the node's parent

+
+ + + + + + +
+
+
+
+
+
+
+

+ +

+
+ +
+
+ + Type: boolean
+

If true, then the system should not try to validate the refs

+
+ + + + + + +
+
+
+
+
+
+
+

+ +

+
+ +
+
+ + Type: string
+

The family designator, used only when the entry is not keyed in a family map

+
+ + + + + +
+
Examples:
+
"ipv4"
+
+
"bacnet"
+
+
+
+
+
+
+
+
+
+
+
+
+
+

+ +

+
+ +
+
+ + Type: array of string
+

An array of all the device IDs which are bound to the device

+
+ + + + + + No Additional Items

Each item of this array must be:

+
+
+ + + Type: string
+Must match regular expression: ^[A-Z]{2,6}-[1-9][0-9]*$ + + + + + + +
+

+
Example:
+
[
+    "AHU-22"
+]
+
+
+
+
+
+
+
+
+
+
+
+
+
+

+ +

+
+ +
+
+ +

Config Cloud Model

Type: object
+

Information specific to how the device communicates with the cloud.

+
+ + No Additional Properties + + + + + + +
+
+
+

+ +

+
+ +
+
+ + Type: string
+

Config file to use. Within the config directory in the device's metadata directory

+
+ + + + + + +
+
+
+
+
+
+
+
+
+
+
+

+ +

+
+ +
+
+ + Type: boolean
+ + + + + + + +
+
+
+
+
+
+
+

+ +

+
+ +
+
+ + Type: string
+ + + + + + + +
+
+
+
+
+
+
+

+ +

+
+ +
+
+ + Type: string
+ + + + + + + +
+
+
+
+
+
+
+

+ +

+
+ +
+
+ + Type: stringFormat: date-time
+ + + + + + + +
+
+
+
+
+
+
+

+ +

+
+ +
+
+ + Type: stringFormat: date-time
+ + + + + + + +
+
+
+
+
+
+
+

+ +

+
+ +
+
+ + Type: stringFormat: date-time
+ + + + + + + +
+
+
+
+
+
+
+

+ +

+
+ +
+
+ + Type: stringFormat: date-time
+ + + + + + + +
+
+
+
+
+
+
+

+ +

+
+ +
+
+ + Type: string
+ + + + + + + +
+
+
+
+
+
+
+

+ +

+
+ +
+
+ +

Model Operation

Type: enum (of string)
+ + +
+

Must be one of:

+
  • "ERROR"
  • "CREATE"
  • "READ"
  • "REPLY"
  • "UPDATE"
  • "DELETE"
  • "MODIFY"
  • "PREVIEW"
  • "BIND"
  • "UNBIND"
  • "BOUND"
  • "ALLOW"
  • "BLOCK"
+
+ + + + + + +
+
+
+
+
+
+
+

+ +

+
+ +
+
+ + Type: string
+

Strigified version of the metadata object, used for internal backend processing

+
+ + + + + + +
+
+
+
+
+
+
+

+ +

+
+ +
+
+ + Type: object
+

If operating on the entire registry, then this contains results for multiple devices (batched operation).

+
+ + + + + + +
+
+
+

+ +

+
+ +
+

+ +

+

All properties whose name matches the following regular expression must respect the following conditions

+ Property name regular expression: ^[-._a-zA-Z0-9]+$ +
+ + Type: object
+ + + + + + + +
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+

+ +

+
+ +
+
+ + Type: object
+

Information about a specific point name of the device.

+
No Additional Properties + + + + + + +
+
+
+

+ +

+
+ +
+

+ +

+

All properties whose name matches the following regular expression must respect the following conditions

+ Property name regular expression: ^[a-z][a-z0-9]*(_[a-z0-9]+)*$ +
+ +

Point Pointset Model

Type: object
+

Information about a specific point name of the device.

+
+ + No Additional Properties + + + + + + +
+
+
+

+ +

+
+ +
+
+ + Type: string
+

Expected unit configuration for the point

+
+ + + + + + +
+
+
+
+
+
+
+

+ +

+
+ +
+
+ + Type: string
+

Actual input unit that is sent from the device

+
+ + + + + + +
+
+
+
+
+
+
+

+ +

+
+ +
+
+ + Type: enum (of string)
+

Expected data type for the point

+
+

Must be one of:

+
  • "string"
  • "boolean"
  • "number"
+
+ + + + + + +
+
+
+
+
+
+
+

+ +

+
+ +
+
+ + Type: string
+

Detailed description of this point

+
+ + + + + + +
+
+
+
+
+
+
+

+ +

+
+ +
+
+ + Type: boolean
+

Indicates if this point is writable (else read-only)

+
+ + + + + + +
+
+
+
+
+
+
+

+ +

+
+ +
+
+ + Type: object
+

Represents the expected baseline value of the point

+
+ + + + + +
+
Example:
+
22
+
+
+
+
+
+
+
+
+
+

+ +

+
+ +
+
+ + Type: number
+

Maximum deviation from baseline_value

+
+ + + + + +
+
Example:
+
2
+
+
+
+
+
+
+
+
+
+

+ +

+
+ +
+
+ + Type: enum (of string)
+

Expected state when baseline_value is set as the set_value for this point the config message

+
+

Must be one of:

+
  • "applied"
  • "updating"
  • "overridden"
  • "invalid"
  • "failure"
+
+ + + + + + +
+
+
+
+
+
+
+

+ +

+
+ +
+
+ + Type: number
+

Represents the lower bound of the error threshold for a point

+
+ + + + + +
+
Example:
+
0
+
+
+
+
+
+
+
+
+
+

+ +

+
+ +
+
+ + Type: number
+

Represents the upper bound of the error threshold for a point

+
+ + + + + +
+
Example:
+
100
+
+
+
+
+
+
+
+
+
+

+ +

+
+ +
+
+ + Type: integer
+

Represents the limit in seconds that a point can be unchanged for

+
+ + + + + +
+
Example:
+
3600
+
+
+
+
+
+
+
+
+
+

+ +

+
+ +
+
+ + Type: number
+

Triggering threshold for partial cov update publishing

+
+ + + + + +
+
Example:
+
1
+
+
+
+
+
+
+
+
+
+

+ +

+
+ +
+
+ + Type: string
+

Mapping for the point to an internal resource (e.g. BACnet object reference)

+
+ + + + + +
+
Examples:
+
"AI3"
+
+
"400070"
+
+
+
+
+
+
+
+
+
+

+ +

+
+ +
+
+ + Type: string
+

Complete resolved URL of this point as determined by the gateway and ref blocks

+
+ + + + + +
+
Example:
+
"bacnet://1234/AV/1"
+
+
+
+
+
+
+
+
+
+

+ +

+
+ +
+
+ + Type: object
+ No Additional Properties + + + + + + +
+
+
+

+ +

+
+ +
+

+ +

+

All properties whose name matches the following regular expression must respect the following conditions

+ Property name regular expression: ^[a-z_]+$ +
+ + Type: string
+ + + + + + + +
+
+
+
+
+
+
+
+
+
+
+

+ +

+
+ +
+
+ + Type: array
+

Tags assosciated with the point

+
+ + + + + +

All items must be unique

No Additional Items

Each item of this array must be:

+
+
+ + + Type: object
+Must match regular expression: ^[a-z0-9]+$ + + + + + + +
+

+
Examples:
+
[
+    "lighting"
+]
+
+
[
+    "energy",
+    "hvac"
+]
+
+
+
+
+
+
+
+
+
+

+ +

+
+ +
+
+ + Type: object
+

Collection of family point information

+
No Additional Properties + + + + + + +
+
+
+

+ +

+
+ +
+

+ +

+

All properties whose name matches the following regular expression must respect the following conditions

+ Property name regular expression: ^[-_.:/a-zA-Z0-9]+$ +
+ +

Ref Discovery

Type: object
+

Object representation for for a single point reference discovery

+
+ + No Additional Properties + + + + + + +
+
+
+

+ +

+
+ +
+
+ + Type: string
+

Point descriptor for this point

+
Must match regular expression: ^[a-z][a-z0-9]*(_[a-z0-9]+)*$ + + + + + + +
+
+
+
+
+
+
+

+ +

+
+ +
+
+ + Type: string
+

Friendly name for the reference, if known

+
+ + + + + + +
+
+
+
+
+
+
+

+ +

+
+ +
+
+ + Type: array of string
+

List of possible enumerated values for the point

+
+ + + + + + No Additional Items

Each item of this array must be:

+
+
+ + + Type: string
+ + + + + + + +
+
+
+
+
+
+
+
+
+

+ +

+
+ +
+
+ + Type: string
+

Current or default unit for this point

+
+ + + + + + +
+
+
+
+
+
+
+

+ +

+
+ +
+
+ + Type: string
+

Family reference for structures

+
+ + + + + + +
+
+
+
+
+
+
+

+ +

+
+ +
+
+ + Type: string
+

Current or default type for this point

+
+ + + + + + +
+
+
+
+
+
+
+

+ +

+
+ +
+
+ + Type: boolean
+

Indicates if this point is writable or not

+
+ + + + + + +
+
+
+
+
+
+
+

+ +

+
+ +
+
+ + Type: string
+

Human-readable description of this point

+
+ + + + + + +
+
+
+
+
+
+
+

+ +

+
+ +
+
+ +

Entry

Type: object
+ + + No Additional Properties + + + + + + +
+
+
+

+ +

+
+ +
+
+ + Type: string
+

A human-readable one-line description of the entry

+
+ + + + + +
+
Example:
+
"Point is not writable"
+
+
+
+
+
+
+
+
+
+

+ +

+
+ +
+
+ + Type: string
+

An optional extensive entry which can include more detail, e.g. a complete program stack-trace

+
+ + + + + + +
+
+
+
+
+
+
+

+ +

+
+ +
+
+ +

Category

Type: string
+ + +

+ +

+
+ + + Type: object
+Must match regular expression: ^system\.base\.start$ + + + + + + +
+ + + Type: object
+Must match regular expression: ^system\.base\.shutdown$ + + + + + + +
+ + + Type: object
+Must match regular expression: ^system\.base\.ready$ + + + + + + +
+ + + Type: object
+Must match regular expression: ^system\.config\.receive$ + + + + + + +
+ + + Type: object
+Must match regular expression: ^system\.config\.parse$ + + + + + + +
+ + + Type: object
+Must match regular expression: ^system\.config\.apply$ + + + + + + +
+ + + Type: object
+Must match regular expression: ^system\.auth\.login$ + + + + + + +
+ + + Type: object
+Must match regular expression: ^system\.auth\.logout$ + + + + + + +
+ + + Type: object
+Must match regular expression: ^system\.auth\.fail$ + + + + + + +
+ + + Type: object
+Must match regular expression: ^pointset\.point\.nominal$ + + + + + + +
+ + + Type: object
+Must match regular expression: ^pointset\.point\.applied$ + + + + + + +
+ + + Type: object
+Must match regular expression: ^pointset\.point\.updating$ + + + + + + +
+ + + Type: object
+Must match regular expression: ^pointset\.point\.overridden$ + + + + + + +
+ + + Type: object
+Must match regular expression: ^pointset\.point\.failure$ + + + + + + +
+ + + Type: object
+Must match regular expression: ^pointset\.point\.invalid$ + + + + + + +
+ + + Type: object
+Must match regular expression: ^localnet\.network$ + + + + + + +
+ + + Type: object
+Must match regular expression: ^localnet\.network\.connect$ + + + + + + +
+ + + Type: object
+Must match regular expression: ^gateway\.setup\.attach$ + + + + + + +
+ + + Type: object
+Must match regular expression: ^gateway\.proxy\.target$ + + + + + + +
+ + + Type: object
+Must match regular expression: ^gateway\.proxy\.connect$ + + + + + + +
+ + + Type: object
+Must match regular expression: ^discovery\.family\.scan$ + + + + + + +
+ + + Type: object
+Must match regular expression: ^discovery\.device\.enumerate$ + + + + + + +
+ + + Type: object
+Must match regular expression: ^discovery\.point\.describe$ + + + + + + +
+ + + Type: object
+Must match regular expression: ^mapping\.device\.apply$ + + + + + + +
+ + + Type: object
+Must match regular expression: ^blobset\.blob$ + + + + + + +
+ + + Type: object
+Must match regular expression: ^blobset\.blob\.update$ + + + + + + +
+ + + Type: object
+Must match regular expression: ^blobset\.blob\.receive$ + + + + + + +
+ + + Type: object
+Must match regular expression: ^blobset\.blob\.fetch$ + + + + + + +
+ + + Type: object
+Must match regular expression: ^blobset\.blob\.parse$ + + + + + + +
+ + + Type: object
+Must match regular expression: ^blobset\.blob\.apply$ + + + + + + +
+ + + Type: object
+Must match regular expression: ^blobset\.blob\.abort$ + + + + + + +
+ + + Type: object
+Must match regular expression: ^blobset\.blob\.rollback$ + + + + + + +
+ + + Type: object
+Must match regular expression: ^validation\.device\.receive$ + + + + + + +
+ + + Type: object
+Must match regular expression: ^validation\.device\.schema$ + + + + + + +
+ + + Type: object
+Must match regular expression: ^validation\.device\.content$ + + + + + + +
+ + + Type: object
+Must match regular expression: ^validation\.device\.multiple$ + + + + + + +
+ + + Type: object
+Must match regular expression: ^validation\.device\.extra$ + + + + + + +
+ + + Type: object
+Must match regular expression: ^validation\.summary\.report$ + + + + + + +
+ + + Type: object
+Must match regular expression: ^validation\.feature\.sequence$ + + + + + + +
+ + + Type: object
+Must match regular expression: ^validation\.feature\.schema$ + + + + + + +
+ + + Type: object
+Must match regular expression: ^validation\.feature\.capability$ + + + + + + +
+ + + Type: object
+Must match regular expression: ^device(\.[a-z]+((_[a-z]+))*)+$ + + + + + + +
+ + + + + + +
+
+
+
+
+
+
+

+ +

+
+ +
+
+ + Type: stringFormat: date-time
+

FC 3339 UTC timestamp the condition was triggered, or most recently updated

+
+ + + + + +
+
Example:
+
"2018-08-26T21:39:28.364Z"
+
+
+
+
+
+
+
+
+
+

+ +

+
+ +
+
+ + Type: integer
+

The status level should conform to the numerical Stackdriver LogEntry levels. The DEFAULT value of 0 is not allowed (lowest value is 100, maximum 800).

+
+ + + +

Value must be greater or equal to 100 and lesser or equal to 800 and a multiple of 1

+ +
+
Example:
+
600
+
+
+
+
+
+
+
+
+
+
+
+
+
+

+ +

+
+ +
+
+ +

Ancillary Properties

Type: object
+

Arbitrary blob of json associated with this point

+
+ + + + + + + + +
+
+
+
+
+
+
+

+ +

+
+ +
+
+ + Type: object
+ No Additional Properties + + + + + + +
+
+
+

+ +

+
+ +
+

+ +

+

All properties whose name matches the following regular expression must respect the following conditions

+ Property name regular expression: ^[a-z_]+$ +
+ + Type: string
+ + + + + + + +
+
+
+
+
+
+
+
+
+
+
+

+ +

+
+ +
+
+ + Type: object
+ + + + + + + +
+
+
+

+ +

+
+ +
+
+ +

Family Discovery

Type: object
+

Discovery information for a protocol family.

+
+ + No Additional Properties + + + + + + +
+
+
+

+ +

+
+ +
+
+ + Type: string
+

Device addr in the namespace of the given family

+
Must match regular expression: ^[-_.:0-9A-Za-z]+$ + + + + + + +
+
+
+
+
+
+
+

+ +

+
+ +
+
+ + Type: integer
+

Port number for the family connection (e.g. UDP port for BACnet/IP)

+
+ + + + + + +
+
+
+
+
+
+
+

+ +

+
+ +
+
+ + Type: string
+

Point reference in the namespace of the given family

+
Must match regular expression: ^[-_.:/#0-9A-Za-z]+$ + + + + + + +
+
+
+
+
+
+
+
+
+
+
+

+ +

+
+ +
+
+ + Type: object
+

Reference links to alternate families

+
No Additional Properties + + + + + + +
+
+
+

+ +

+
+ +
+

+ +

+

All properties whose name matches the following regular expression must respect the following conditions

+ Property name regular expression: ^[a-z][a-z0-9]*(_[a-z0-9]+)*$ +
+ +

Family Discovery

Type: object
+

Discovery information for a protocol family.

+
+ + No Additional Properties + + + + + + +
+
+
+

+ +

+
+ +
+
+ + Type: string
+

Device addr in the namespace of the given family

+
Must match regular expression: ^[-_.:0-9A-Za-z]+$ + + + + + + +
+
+
+
+
+
+
+

+ +

+
+ +
+
+ + Type: integer
+

Port number for the family connection (e.g. UDP port for BACnet/IP)

+
+ + + + + + +
+
+
+
+
+
+
+

+ +

+
+ +
+
+ + Type: string
+

Point reference in the namespace of the given family

+
Must match regular expression: ^[-_.:/#0-9A-Za-z]+$ + + + + + + +
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+

+ +

+
+ +
+
+ + Type: string
+

Virtual equipment mapping linking this local point to a remote point in another device

+
Must match regular expression: .*[-_0-9a-zA-Z$]+:[a-z0-9_]+.* + + + + + +
+
Examples:
+
"VAV-3:supply_air_flowrate_sensor"
+
+
"VAV-101:zone_air_temp_sensor - VAV-102:zone_air_temp_sensor"
+
+
+
+
+
+
+
+
+
+

+ +

+
+ +
+
+ + Type: object
+

State value mapping for this point (e.g. 0.0: OFF)

+
+ + + + + + +
+
+
+

+ +

+
+ +
+

Each additional property must conform to the following schema

+ + Type: string
+ + + + + + + +
+
+
+
+
+
+
+
+
+
+
+

+ +

+
+ +
+
+ +

Building Translation

Type: object
+

Discovery result with implicit enumeration

+
+ + No Additional Properties + + + + + + +
+
+
+

+ +

+
+ +
+
+ + Type: string
+

dotted path to present_value field

+
+ + + + + + +
+
+
+
+
+
+
+

+ +

+
+ +
+
+ + Type: object
+ + + + + + + +
+
+
+

+ +

+
+ +
+
+ + Type: string
+ + + + + + + +
+
+
+
+
+
+
+
+
+
+
+

+ +

+
+ +
+
+ + Type: string
+

Actual input unit that is sent from the device

+
+ + + + + + +
+
+
+
+
+
+
+

+ +

+
+ +
+
+ + Type: object
+ + + + + + + +
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+

+ +

+
+ +
+
+ + Type: object
+

Collection of point references

+
No Additional Properties + + + + + + +
+
+
+

+ +

+
+ +
+

+ +

+

All properties whose name matches the following regular expression must respect the following conditions

+ Property name regular expression: ^[-_.:/#a-zA-Z0-9]+$ +
+ +

Ref Discovery

Type: object
+

Object representation for for a single point reference discovery

+
+ + No Additional Properties + + + + + + +
+
+
+

+ +

+
+ +
+
+ + Type: string
+

Point descriptor for this point

+
Must match regular expression: ^[a-z][a-z0-9]*(_[a-z0-9]+)*$ + + + + + + +
+
+
+
+
+
+
+

+ +

+
+ +
+
+ + Type: string
+

Friendly name for the reference, if known

+
+ + + + + + +
+
+
+
+
+
+
+

+ +

+
+ +
+
+ + Type: array of string
+

List of possible enumerated values for the point

+
+ + + + + + No Additional Items

Each item of this array must be:

+
+
+ + + Type: string
+ + + + + + + +
+
+
+
+
+
+
+
+
+

+ +

+
+ +
+
+ + Type: string
+

Current or default unit for this point

+
+ + + + + + +
+
+
+
+
+
+
+

+ +

+
+ +
+
+ + Type: string
+

Family reference for structures

+
+ + + + + + +
+
+
+
+
+
+
+

+ +

+
+ +
+
+ + Type: string
+

Current or default type for this point

+
+ + + + + + +
+
+
+
+
+
+
+

+ +

+
+ +
+
+ + Type: boolean
+

Indicates if this point is writable or not

+
+ + + + + + +
+
+
+
+
+
+
+

+ +

+
+ +
+
+ + Type: string
+

Human-readable description of this point

+
+ + + + + + +
+
+
+
+
+
+
+

+ +

+
+ +
+
+ +

Entry

Type: object
+ + + No Additional Properties + + + + + + +
+
+
+

+ +

+
+ +
+
+ + Type: string
+

A human-readable one-line description of the entry

+
+ + + + + +
+
Example:
+
"Point is not writable"
+
+
+
+
+
+
+
+
+
+

+ +

+
+ +
+
+ + Type: string
+

An optional extensive entry which can include more detail, e.g. a complete program stack-trace

+
+ + + + + + +
+
+
+
+
+
+
+

+ +

+
+ +
+
+ +

Category

Type: string
+ + +

+ +

+
+ + + Type: object
+Must match regular expression: ^system\.base\.start$ + + + + + + +
+ + + Type: object
+Must match regular expression: ^system\.base\.shutdown$ + + + + + + +
+ + + Type: object
+Must match regular expression: ^system\.base\.ready$ + + + + + + +
+ + + Type: object
+Must match regular expression: ^system\.config\.receive$ + + + + + + +
+ + + Type: object
+Must match regular expression: ^system\.config\.parse$ + + + + + + +
+ + + Type: object
+Must match regular expression: ^system\.config\.apply$ + + + + + + +
+ + + Type: object
+Must match regular expression: ^system\.auth\.login$ + + + + + + +
+ + + Type: object
+Must match regular expression: ^system\.auth\.logout$ + + + + + + +
+ + + Type: object
+Must match regular expression: ^system\.auth\.fail$ + + + + + + +
+ + + Type: object
+Must match regular expression: ^pointset\.point\.nominal$ + + + + + + +
+ + + Type: object
+Must match regular expression: ^pointset\.point\.applied$ + + + + + + +
+ + + Type: object
+Must match regular expression: ^pointset\.point\.updating$ + + + + + + +
+ + + Type: object
+Must match regular expression: ^pointset\.point\.overridden$ + + + + + + +
+ + + Type: object
+Must match regular expression: ^pointset\.point\.failure$ + + + + + + +
+ + + Type: object
+Must match regular expression: ^pointset\.point\.invalid$ + + + + + + +
+ + + Type: object
+Must match regular expression: ^localnet\.network$ + + + + + + +
+ + + Type: object
+Must match regular expression: ^localnet\.network\.connect$ + + + + + + +
+ + + Type: object
+Must match regular expression: ^gateway\.setup\.attach$ + + + + + + +
+ + + Type: object
+Must match regular expression: ^gateway\.proxy\.target$ + + + + + + +
+ + + Type: object
+Must match regular expression: ^gateway\.proxy\.connect$ + + + + + + +
+ + + Type: object
+Must match regular expression: ^discovery\.family\.scan$ + + + + + + +
+ + + Type: object
+Must match regular expression: ^discovery\.device\.enumerate$ + + + + + + +
+ + + Type: object
+Must match regular expression: ^discovery\.point\.describe$ + + + + + + +
+ + + Type: object
+Must match regular expression: ^mapping\.device\.apply$ + + + + + + +
+ + + Type: object
+Must match regular expression: ^blobset\.blob$ + + + + + + +
+ + + Type: object
+Must match regular expression: ^blobset\.blob\.update$ + + + + + + +
+ + + Type: object
+Must match regular expression: ^blobset\.blob\.receive$ + + + + + + +
+ + + Type: object
+Must match regular expression: ^blobset\.blob\.fetch$ + + + + + + +
+ + + Type: object
+Must match regular expression: ^blobset\.blob\.parse$ + + + + + + +
+ + + Type: object
+Must match regular expression: ^blobset\.blob\.apply$ + + + + + + +
+ + + Type: object
+Must match regular expression: ^blobset\.blob\.abort$ + + + + + + +
+ + + Type: object
+Must match regular expression: ^blobset\.blob\.rollback$ + + + + + + +
+ + + Type: object
+Must match regular expression: ^validation\.device\.receive$ + + + + + + +
+ + + Type: object
+Must match regular expression: ^validation\.device\.schema$ + + + + + + +
+ + + Type: object
+Must match regular expression: ^validation\.device\.content$ + + + + + + +
+ + + Type: object
+Must match regular expression: ^validation\.device\.multiple$ + + + + + + +
+ + + Type: object
+Must match regular expression: ^validation\.device\.extra$ + + + + + + +
+ + + Type: object
+Must match regular expression: ^validation\.summary\.report$ + + + + + + +
+ + + Type: object
+Must match regular expression: ^validation\.feature\.sequence$ + + + + + + +
+ + + Type: object
+Must match regular expression: ^validation\.feature\.schema$ + + + + + + +
+ + + Type: object
+Must match regular expression: ^validation\.feature\.capability$ + + + + + + +
+ + + Type: object
+Must match regular expression: ^device(\.[a-z]+((_[a-z]+))*)+$ + + + + + + +
+ + + + + + +
+
+
+
+
+
+
+

+ +

+
+ +
+
+ + Type: stringFormat: date-time
+

FC 3339 UTC timestamp the condition was triggered, or most recently updated

+
+ + + + + +
+
Example:
+
"2018-08-26T21:39:28.364Z"
+
+
+
+
+
+
+
+
+
+

+ +

+
+ +
+
+ + Type: integer
+

The status level should conform to the numerical Stackdriver LogEntry levels. The DEFAULT value of 0 is not allowed (lowest value is 100, maximum 800).

+
+ + + +

Value must be greater or equal to 100 and lesser or equal to 800 and a multiple of 1

+ +
+
Example:
+
600
+
+
+
+
+
+
+
+
+
+
+
+
+
+

+ +

+
+ +
+
+ +

Ancillary Properties

Type: object
+

Arbitrary blob of json associated with this point

+
+ + + + + + + + +
+
+
+
+
+
+
+

+ +

+
+ +
+
+ + Type: object
+ No Additional Properties + + + + + + +
+
+
+

+ +

+
+ +
+

+ +

+

All properties whose name matches the following regular expression must respect the following conditions

+ Property name regular expression: ^[a-z_]+$ +
+ + Type: string
+ + + + + + + +
+
+
+
+
+
+
+
+
+
+
+

+ +

+
+ +
+
+ + Type: object
+ + + + + + + +
+
+
+

+ +

+
+ +
+
+ +

Family Discovery

Type: object
+

Discovery information for a protocol family.

+
+ + No Additional Properties + + + + + + +
+
+
+

+ +

+
+ +
+
+ + Type: string
+

Device addr in the namespace of the given family

+
Must match regular expression: ^[-_.:0-9A-Za-z]+$ + + + + + + +
+
+
+
+
+
+
+

+ +

+
+ +
+
+ + Type: integer
+

Port number for the family connection (e.g. UDP port for BACnet/IP)

+
+ + + + + + +
+
+
+
+
+
+
+

+ +

+
+ +
+
+ + Type: string
+

Point reference in the namespace of the given family

+
Must match regular expression: ^[-_.:/#0-9A-Za-z]+$ + + + + + + +
+
+
+
+
+
+
+
+
+
+
+

+ +

+
+ +
+
+ + Type: object
+

Reference links to alternate families

+
No Additional Properties + + + + + + +
+
+
+

+ +

+
+ +
+

+ +

+

All properties whose name matches the following regular expression must respect the following conditions

+ Property name regular expression: ^[a-z][a-z0-9]*(_[a-z0-9]+)*$ +
+ +

Family Discovery

Type: object
+

Discovery information for a protocol family.

+
+ + No Additional Properties + + + + + + +
+
+
+

+ +

+
+ +
+
+ + Type: string
+

Device addr in the namespace of the given family

+
Must match regular expression: ^[-_.:0-9A-Za-z]+$ + + + + + + +
+
+
+
+
+
+
+

+ +

+
+ +
+
+ + Type: integer
+

Port number for the family connection (e.g. UDP port for BACnet/IP)

+
+ + + + + + +
+
+
+
+
+
+
+

+ +

+
+ +
+
+ + Type: string
+

Point reference in the namespace of the given family

+
Must match regular expression: ^[-_.:/#0-9A-Za-z]+$ + + + + + + +
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+

+ +

+
+ +
+
+ + Type: object
+

Discovery of features supported by this device.

+
No Additional Properties + + + + + + +
+
+
+

+ +

+
+ +
+

+ +

+

All properties whose name matches the following regular expression must respect the following conditions

+ Property name regular expression: ^[._a-zA-Z]+$ +
+ +

Feature Discovery

Type: object
+

Object representation for for a single feature

+
+ + No Additional Properties + + + + + + +
+
+
+

+ +

+
+ +
+
+ +

FeatureStage

Type: enum (of string)
+

Stage of a feature implemenation

+
+ +
+

Must be one of:

+
  • "disabled"
  • "alpha"
  • "preview"
  • "beta"
  • "stable"
+
+ + + + + + +
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+

+ +

+
+ +
+
+ +

Cloud Model

Type: object
+

Information specific to how the device communicates with the cloud.

+
+ + No Additional Properties + + + + + + +
+
+
+

+ +

+
+ +
+
+ + Type: integer
+

Function version of this request

+
+ + + + + + +
+
+
+
+
+
+
+

+ +

+
+ +
+
+ + Type: enum (of string)
+

The key type used for cloud communication.

+
+

Must be one of:

+
  • "ES256"
  • "ES256_X509"
  • "RS256"
  • "RS256_X509"
+
+ + + + + + +
+
+
+
+
+
+
+

+ +

+
+ +
+
+ + Type: boolean
+

Indicates if the device private key is provided in the site model.

+
+ + + + + + +
+
+
+
+
+
+
+

+ +

+
+ +
+
+ + Type: enum (of string)
+

The cloud resource type

+
+

Must be one of:

+
  • "DIRECT"
  • "GATEWAY"
  • "FUNNEL"
  • "PROXIED"
  • "REGISTRY"
+
+ + + + + + +
+
+
+
+
+
+
+

+ +

+
+ +
+
+ +

Gateway Model

Type: object
+

Gateway Documentation

+
+ + No Additional Properties + + + + + + +
+
+
+

+ +

+
+ +
+
+ + Type: string
+

The device ID of the gateway the device is bound to

+
Must match regular expression: ^[A-Z]{2,6}-[1-9][0-9]*$ + + + + + +
+
Example:
+
"GAT-100"
+
+
+
+
+
+
+
+
+
+

+ +

+
+ +
+
+ +

Family Localnet Model

Type: object
+

The type of network

+
+ + No Additional Properties + + + + + + +
+
+
+

+ +

+
+ +
+
+ + Type: string
+

The address of a device on the fieldbus/local network

+
+ + + + + +
+
Examples:
+
"192.168.1.3"
+
+
"41489"
+
+
+
+
+
+
+
+
+
+

+ +

+
+ +
+
+ + Type: string
+

The slave unit ID of the device for protocol families like modbus

+
+ + + + + +
+
Examples:
+
"1"
+
+
"255"
+
+
+
+
+
+
+
+
+
+

+ +

+
+ +
+
+ + Type: string
+

The network designator for this device in the family-defined format

+
+ + + + + +
+
Examples:
+
"192.168.1.0/24"
+
+
"12732"
+
+
+
+
+
+
+
+
+
+

+ +

+
+ +
+
+ + Type: object
+ No Additional Properties + + + + + + +
+
+
+

+ +

+
+ +
+

+ +

+

All properties whose name matches the following regular expression must respect the following conditions

+ Property name regular expression: ^[a-z_]+$ +
+ + Type: string
+ + + + + + + +
+
+
+
+
+
+
+
+
+
+
+

+ +

+
+ +
+
+ + Type: string
+

Specifies that this is a shadow of the indicated device

+
+ + + + + + +
+
+
+
+
+
+
+

+ +

+
+ +
+
+ + Type: string
+

The device id of the node's parent

+
+ + + + + + +
+
+
+
+
+
+
+

+ +

+
+ +
+
+ + Type: boolean
+

If true, then the system should not try to validate the refs

+
+ + + + + + +
+
+
+
+
+
+
+

+ +

+
+ +
+
+ + Type: string
+

The family designator, used only when the entry is not keyed in a family map

+
+ + + + + +
+
Examples:
+
"ipv4"
+
+
"bacnet"
+
+
+
+
+
+
+
+
+
+
+
+
+
+

+ +

+
+ +
+
+ + Type: array of string
+

An array of all the device IDs which are bound to the device

+
+ + + + + + No Additional Items

Each item of this array must be:

+
+
+ + + Type: string
+Must match regular expression: ^[A-Z]{2,6}-[1-9][0-9]*$ + + + + + + +
+

+
Example:
+
[
+    "AHU-22"
+]
+
+
+
+
+
+
+
+
+
+
+
+
+
+

+ +

+
+ +
+
+ +

Config Cloud Model

Type: object
+

Information specific to how the device communicates with the cloud.

+
+ + No Additional Properties + + + + + + +
+
+
+

+ +

+
+ +
+
+ + Type: string
+

Config file to use. Within the config directory in the device's metadata directory

+
+ + + + + + +
+
+
+
+
+
+
+
+
+
+
+

+ +

+
+ +
+
+ + Type: boolean
+ + + + + + + +
+
+
+
+
+
+
+

+ +

+
+ +
+
+ + Type: string
+ + + + + + + +
+
+
+
+
+
+
+

+ +

+
+ +
+
+ + Type: string
+ + + + + + + +
+
+
+
+
+
+
+

+ +

+
+ +
+
+ + Type: stringFormat: date-time
+ + + + + + + +
+
+
+
+
+
+
+

+ +

+
+ +
+
+ + Type: stringFormat: date-time
+ + + + + + + +
+
+
+
+
+
+
+

+ +

+
+ +
+
+ + Type: stringFormat: date-time
+ + + + + + + +
+
+
+
+
+
+
+

+ +

+
+ +
+
+ + Type: stringFormat: date-time
+ + + + + + + +
+
+
+
+
+
+
+

+ +

+
+ +
+
+ + Type: string
+ + + + + + + +
+
+
+
+
+
+
+

+ +

+
+ +
+
+ +

Model Operation

Type: enum (of string)
+ + +
+

Must be one of:

+
  • "ERROR"
  • "CREATE"
  • "READ"
  • "REPLY"
  • "UPDATE"
  • "DELETE"
  • "MODIFY"
  • "PREVIEW"
  • "BIND"
  • "UNBIND"
  • "BOUND"
  • "ALLOW"
  • "BLOCK"
+
+ + + + + + +
+
+
+
+
+
+
+

+ +

+
+ +
+
+ + Type: string
+

Strigified version of the metadata object, used for internal backend processing

+
+ + + + + + +
+
+
+
+
+
+
+

+ +

+
+ +
+
+ + Type: object
+

If operating on the entire registry, then this contains results for multiple devices (batched operation).

+
+ + + + + + +
+
+
+

+ +

+
+ +
+

+ +

+

All properties whose name matches the following regular expression must respect the following conditions

+ Property name regular expression: ^[-._a-zA-Z0-9]+$ +
+ + Type: object
+ + + + + + + +
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+

+ +

+
+ +
+
+ +

System Discovery Data

Type: object
+ No Additional Properties + + + + + + +
+
+
+

+ +

+
+ +
+
+ + Type: string
+

Full textual desctiiption of this device

+
+ + + + + + +
+
+
+
+
+
+
+

+ +

+
+ +
+
+ + Type: string
+

Friendly name of this device

+
+ + + + + + +
+
+
+
+
+
+
+

+ +

+
+ +
+
+ + Type: string
+

The serial number of the physical device

+
+ + + + + + +
+
+
+
+
+
+
+

+ +

+
+ +
+
+ +

Ancillary Properties

Type: object
+

Arbitrary blob of json associated with this point

+
+ + + + + + + + +
+
+
+
+
+
+
+

+ +

+
+ +
+
+ +

State System Hardware

Type: object
+

A collection of fields which describe the physical hardware of the device.

+
+ + No Additional Properties + + + + + + +
+
+
+

+ +

+
+ +
+
+ + Type: string
+

The manufacturer of the device

+
+ + + + + +
+
Example:
+
"Google"
+
+
+
+
+
+
+
+
+
+

+ +

+
+ +
+
+ + Type: string
+

The model of the device

+
+ + + + + +
+
Examples:
+
"Pixel 5"
+
+
"Pixel 6"
+
+
"Pixel 6 Pro"
+
+
+
+
+
+
+
+
+
+

+ +

+
+ +
+
+ + Type: string
+

A stock keeping unit which identifies the unique composition of a device, for example those with different hardware configurations

+
+ + + + + +
+
Examples:
+
"GF5KQ"
+
+
"4215612"
+
+
+
+
+
+
+
+
+
+

+ +

+
+ +
+
+ + Type: string
+

Hardware revision of the device

+
+ + + + + +
+
Examples:
+
"A"
+
+
"B"
+
+
"1"
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+

+ +

+
+ +
+
+ +

Metadata

Type: object
+

Device metadata model (for model discovery)

+
Same definition as model +
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+

+ +

+
+ +
+
+ +

Policy Model

Type: object
+ + + No Additional Properties + + + + + + +
+
+
+

+ +

+
+ +
+
+ + Type: array of string
+

An array of rule sets which are applicable to the device

+
+ + + + + + No Additional Items

Each item of this array must be:

+
+
+ + + Type: string
+Must match regular expression: ^[a-z_0-9]*$ + + + + + + +
+

+
Example:
+
[
+    "ip",
+    "mqtt"
+]
+
diff --git a/gencode/docs/metadata.html b/gencode/docs/metadata.html index 3d7650f58f..4f0afe9f41 100644 --- a/gencode/docs/metadata.html +++ b/gencode/docs/metadata.html @@ -21120,16 +21120,18 @@

expr

Type: string

Virtual equipment mapping linking this local point to a remote point in another device

-
Must match regular expression: ^[-0-9a-zA-Z$]+:[a-z0-9_]+$ +Must match regular expression: .*[-_0-9a-zA-Z$]+:[a-z0-9_]+.*
-
Example:
+
Examples:

"VAV-3:supply_air_flowrate_sensor"
 
+
"VAV-101:zone_air_temp_sensor - VAV-102:zone_air_temp_sensor"
+
@@ -24882,6 +24884,54 @@

+

+
+
+
+
+
+
+

+ +

+
+ +
+
+ + Type: string
+

Optional source qualifier for the discovery scan (e.g., 'model')

+
+ + + + + +
@@ -25026,6 +25076,144 @@

+

+
+
+
+
+
+
+

+ +

+
+ +
+
+ + Type: integer
+

Total number of records expected for this active scan generation (only populated on the event_no=0 start marker event)

+
+ + + +

Value must be greater or equal to 0

+ + +
+
+
+
+
+
+
+

+ +

+
+ +
+
+ + Type: array of string
+

List of target addresses for a partial discovery scan shard (only populated on the event_no=0 start marker event)

+
+ + + + + + No Additional Items

Each item of this array must be:

+
+
+ + + Type: string
+ + + + + + + +
+
@@ -37393,16 +37581,18 @@

expr

Type: string

Virtual equipment mapping linking this local point to a remote point in another device

-
Must match regular expression: ^[-0-9a-zA-Z$]+:[a-z0-9_]+$ +Must match regular expression: .*[-_0-9a-zA-Z$]+:[a-z0-9_]+.*
-
Example:
+
Examples:

"VAV-3:supply_air_flowrate_sensor"
 
+
"VAV-101:zone_air_temp_sensor - VAV-102:zone_air_temp_sensor"
+
@@ -45874,6 +46064,48 @@

+ +
+
+
+

+ +

+
+ +
+
+ +

Metadata

Type: object
+

Device metadata model (for model discovery)

+
Same definition as Metadata +
+
+
diff --git a/gencode/docs/state.html b/gencode/docs/state.html index 99a913102d..0a52457c03 100644 --- a/gencode/docs/state.html +++ b/gencode/docs/state.html @@ -6389,6 +6389,61 @@

Value must be greater or equal to 0

+ + + + +
+
+
+

+ +

+
+ +
+
+ + Type: string
+

Optional source qualifier for the discovery scan (e.g., 'model')

+
+ + + + + +
diff --git a/gencode/java/udmi/schema/DiscoveryEvents.java b/gencode/java/udmi/schema/DiscoveryEvents.java index 9839f93ef8..808a4368e7 100644 --- a/gencode/java/udmi/schema/DiscoveryEvents.java +++ b/gencode/java/udmi/schema/DiscoveryEvents.java @@ -2,6 +2,7 @@ package udmi.schema; import java.util.Date; +import java.util.List; import java.util.Map; import com.fasterxml.jackson.annotation.JsonInclude; import com.fasterxml.jackson.annotation.JsonProperty; @@ -49,6 +50,13 @@ public class DiscoveryEvents { public Entry status; @JsonProperty("family") public String family; + /** + * Optional source qualifier for the discovery scan (e.g., 'model') + * + */ + @JsonProperty("source") + @JsonPropertyDescription("Optional source qualifier for the discovery scan (e.g., 'model')") + public java.lang.String source; /** * The primary address of the device (for indicated family) * @@ -70,6 +78,20 @@ public class DiscoveryEvents { @JsonProperty("event_no") @JsonPropertyDescription("The active or passive series number of this result (matches reported state values)") public Integer event_no; + /** + * Total number of records expected for this active scan generation (only populated on the event_no=0 start marker event) + * + */ + @JsonProperty("active_count") + @JsonPropertyDescription("Total number of records expected for this active scan generation (only populated on the event_no=0 start marker event)") + public Integer active_count; + /** + * List of target addresses for a partial discovery scan shard (only populated on the event_no=0 start marker event) + * + */ + @JsonProperty("addrs") + @JsonPropertyDescription("List of target addresses for a partial discovery scan shard (only populated on the event_no=0 start marker event)") + public List addrs; /** * Links to other address families (family and id) * @@ -129,12 +151,24 @@ public class DiscoveryEvents { */ @JsonProperty("system") public SystemDiscoveryData system; + /** + * Metadata + *

+ * [Metadata](../docs/specs/metadata.md) is a description about the device: a specification about how the device should be configured and expectations about what the device should be doing. Defined by `metadata.json` + * + */ + @JsonProperty("model") + @JsonPropertyDescription("[Metadata](../docs/specs/metadata.md) is a description about the device: a specification about how the device should be configured and expectations about what the device should be doing. Defined by `metadata.json`") + public Metadata model; @Override public int hashCode() { int result = 1; result = ((result* 31)+((this.generation == null)? 0 :this.generation.hashCode())); result = ((result* 31)+((this.devices == null)? 0 :this.devices.hashCode())); + result = ((result* 31)+((this.addrs == null)? 0 :this.addrs.hashCode())); + result = ((result* 31)+((this.source == null)? 0 :this.source.hashCode())); + result = ((result* 31)+((this.active_count == null)? 0 :this.active_count.hashCode())); result = ((result* 31)+((this.registries == null)? 0 :this.registries.hashCode())); result = ((result* 31)+((this.families == null)? 0 :this.families.hashCode())); result = ((result* 31)+((this.version == null)? 0 :this.version.hashCode())); @@ -144,6 +178,7 @@ public int hashCode() { result = ((result* 31)+((this.system == null)? 0 :this.system.hashCode())); result = ((result* 31)+((this.event_no == null)? 0 :this.event_no.hashCode())); result = ((result* 31)+((this.refs == null)? 0 :this.refs.hashCode())); + result = ((result* 31)+((this.model == null)? 0 :this.model.hashCode())); result = ((result* 31)+((this.family == null)? 0 :this.family.hashCode())); result = ((result* 31)+((this.addr == null)? 0 :this.addr.hashCode())); result = ((result* 31)+((this.cloud_model == null)? 0 :this.cloud_model.hashCode())); @@ -161,7 +196,7 @@ public boolean equals(Object other) { return false; } DiscoveryEvents rhs = ((DiscoveryEvents) other); - return (((((((((((((((((this.generation == rhs.generation)||((this.generation!= null)&&this.generation.equals(rhs.generation)))&&((this.devices == rhs.devices)||((this.devices!= null)&&this.devices.equals(rhs.devices))))&&((this.registries == rhs.registries)||((this.registries!= null)&&this.registries.equals(rhs.registries))))&&((this.families == rhs.families)||((this.families!= null)&&this.families.equals(rhs.families))))&&((this.version == rhs.version)||((this.version!= null)&&this.version.equals(rhs.version))))&&((this.network == rhs.network)||((this.network!= null)&&this.network.equals(rhs.network))))&&((this.points == rhs.points)||((this.points!= null)&&this.points.equals(rhs.points))))&&((this.features == rhs.features)||((this.features!= null)&&this.features.equals(rhs.features))))&&((this.system == rhs.system)||((this.system!= null)&&this.system.equals(rhs.system))))&&((this.event_no == rhs.event_no)||((this.event_no!= null)&&this.event_no.equals(rhs.event_no))))&&((this.refs == rhs.refs)||((this.refs!= null)&&this.refs.equals(rhs.refs))))&&((this.family == rhs.family)||((this.family!= null)&&this.family.equals(rhs.family))))&&((this.addr == rhs.addr)||((this.addr!= null)&&this.addr.equals(rhs.addr))))&&((this.cloud_model == rhs.cloud_model)||((this.cloud_model!= null)&&this.cloud_model.equals(rhs.cloud_model))))&&((this.timestamp == rhs.timestamp)||((this.timestamp!= null)&&this.timestamp.equals(rhs.timestamp))))&&((this.status == rhs.status)||((this.status!= null)&&this.status.equals(rhs.status)))); + return (((((((((((((((((((((this.generation == rhs.generation)||((this.generation!= null)&&this.generation.equals(rhs.generation)))&&((this.devices == rhs.devices)||((this.devices!= null)&&this.devices.equals(rhs.devices))))&&((this.addrs == rhs.addrs)||((this.addrs!= null)&&this.addrs.equals(rhs.addrs))))&&((this.source == rhs.source)||((this.source!= null)&&this.source.equals(rhs.source))))&&((this.active_count == rhs.active_count)||((this.active_count!= null)&&this.active_count.equals(rhs.active_count))))&&((this.registries == rhs.registries)||((this.registries!= null)&&this.registries.equals(rhs.registries))))&&((this.families == rhs.families)||((this.families!= null)&&this.families.equals(rhs.families))))&&((this.version == rhs.version)||((this.version!= null)&&this.version.equals(rhs.version))))&&((this.network == rhs.network)||((this.network!= null)&&this.network.equals(rhs.network))))&&((this.points == rhs.points)||((this.points!= null)&&this.points.equals(rhs.points))))&&((this.features == rhs.features)||((this.features!= null)&&this.features.equals(rhs.features))))&&((this.system == rhs.system)||((this.system!= null)&&this.system.equals(rhs.system))))&&((this.event_no == rhs.event_no)||((this.event_no!= null)&&this.event_no.equals(rhs.event_no))))&&((this.refs == rhs.refs)||((this.refs!= null)&&this.refs.equals(rhs.refs))))&&((this.model == rhs.model)||((this.model!= null)&&this.model.equals(rhs.model))))&&((this.family == rhs.family)||((this.family!= null)&&this.family.equals(rhs.family))))&&((this.addr == rhs.addr)||((this.addr!= null)&&this.addr.equals(rhs.addr))))&&((this.cloud_model == rhs.cloud_model)||((this.cloud_model!= null)&&this.cloud_model.equals(rhs.cloud_model))))&&((this.timestamp == rhs.timestamp)||((this.timestamp!= null)&&this.timestamp.equals(rhs.timestamp))))&&((this.status == rhs.status)||((this.status!= null)&&this.status.equals(rhs.status)))); } } diff --git a/gencode/java/udmi/schema/FamilyDiscoveryConfig.java b/gencode/java/udmi/schema/FamilyDiscoveryConfig.java index d74ddf2990..990c2fbd31 100644 --- a/gencode/java/udmi/schema/FamilyDiscoveryConfig.java +++ b/gencode/java/udmi/schema/FamilyDiscoveryConfig.java @@ -52,6 +52,13 @@ public class FamilyDiscoveryConfig { public Integer passive_sec; @JsonProperty("depth") public udmi.schema.Enumerations.Depth depth; + /** + * Optional source qualifier for the discovery scan (e.g., 'model') + * + */ + @JsonProperty("source") + @JsonPropertyDescription("Optional source qualifier for the discovery scan (e.g., 'model')") + public String source; /** * Trace Discovery Config *

@@ -72,6 +79,7 @@ public int hashCode() { result = ((result* 31)+((this.passive_sec == null)? 0 :this.passive_sec.hashCode())); result = ((result* 31)+((this.scan_duration_sec == null)? 0 :this.scan_duration_sec.hashCode())); result = ((result* 31)+((this.addrs == null)? 0 :this.addrs.hashCode())); + result = ((result* 31)+((this.source == null)? 0 :this.source.hashCode())); result = ((result* 31)+((this.networks == null)? 0 :this.networks.hashCode())); return result; } @@ -85,7 +93,7 @@ public boolean equals(Object other) { return false; } FamilyDiscoveryConfig rhs = ((FamilyDiscoveryConfig) other); - return (((((((((this.generation == rhs.generation)||((this.generation!= null)&&this.generation.equals(rhs.generation)))&&((this.scan_interval_sec == rhs.scan_interval_sec)||((this.scan_interval_sec!= null)&&this.scan_interval_sec.equals(rhs.scan_interval_sec))))&&((this.trace == rhs.trace)||((this.trace!= null)&&this.trace.equals(rhs.trace))))&&((this.depth == rhs.depth)||((this.depth!= null)&&this.depth.equals(rhs.depth))))&&((this.passive_sec == rhs.passive_sec)||((this.passive_sec!= null)&&this.passive_sec.equals(rhs.passive_sec))))&&((this.scan_duration_sec == rhs.scan_duration_sec)||((this.scan_duration_sec!= null)&&this.scan_duration_sec.equals(rhs.scan_duration_sec))))&&((this.addrs == rhs.addrs)||((this.addrs!= null)&&this.addrs.equals(rhs.addrs))))&&((this.networks == rhs.networks)||((this.networks!= null)&&this.networks.equals(rhs.networks)))); + return ((((((((((this.generation == rhs.generation)||((this.generation!= null)&&this.generation.equals(rhs.generation)))&&((this.scan_interval_sec == rhs.scan_interval_sec)||((this.scan_interval_sec!= null)&&this.scan_interval_sec.equals(rhs.scan_interval_sec))))&&((this.trace == rhs.trace)||((this.trace!= null)&&this.trace.equals(rhs.trace))))&&((this.depth == rhs.depth)||((this.depth!= null)&&this.depth.equals(rhs.depth))))&&((this.passive_sec == rhs.passive_sec)||((this.passive_sec!= null)&&this.passive_sec.equals(rhs.passive_sec))))&&((this.scan_duration_sec == rhs.scan_duration_sec)||((this.scan_duration_sec!= null)&&this.scan_duration_sec.equals(rhs.scan_duration_sec))))&&((this.addrs == rhs.addrs)||((this.addrs!= null)&&this.addrs.equals(rhs.addrs))))&&((this.source == rhs.source)||((this.source!= null)&&this.source.equals(rhs.source))))&&((this.networks == rhs.networks)||((this.networks!= null)&&this.networks.equals(rhs.networks)))); } } diff --git a/gencode/java/udmi/schema/FamilyDiscoveryState.java b/gencode/java/udmi/schema/FamilyDiscoveryState.java index b60f9e9e45..b406d3ad3f 100644 --- a/gencode/java/udmi/schema/FamilyDiscoveryState.java +++ b/gencode/java/udmi/schema/FamilyDiscoveryState.java @@ -49,6 +49,13 @@ public class FamilyDiscoveryState { @JsonProperty("passive_count") @JsonPropertyDescription("Number of passive scan results currently 'on hold'") public Integer passive_count; + /** + * Optional source qualifier for the discovery scan (e.g., 'model') + * + */ + @JsonProperty("source") + @JsonPropertyDescription("Optional source qualifier for the discovery scan (e.g., 'model')") + public String source; /** * Entry *

@@ -64,6 +71,7 @@ public int hashCode() { result = ((result* 31)+((this.generation == null)? 0 :this.generation.hashCode())); result = ((result* 31)+((this.phase == null)? 0 :this.phase.hashCode())); result = ((result* 31)+((this.active_count == null)? 0 :this.active_count.hashCode())); + result = ((result* 31)+((this.source == null)? 0 :this.source.hashCode())); result = ((result* 31)+((this.passive_count == null)? 0 :this.passive_count.hashCode())); result = ((result* 31)+((this.status == null)? 0 :this.status.hashCode())); return result; @@ -78,7 +86,7 @@ public boolean equals(Object other) { return false; } FamilyDiscoveryState rhs = ((FamilyDiscoveryState) other); - return ((((((this.generation == rhs.generation)||((this.generation!= null)&&this.generation.equals(rhs.generation)))&&((this.phase == rhs.phase)||((this.phase!= null)&&this.phase.equals(rhs.phase))))&&((this.active_count == rhs.active_count)||((this.active_count!= null)&&this.active_count.equals(rhs.active_count))))&&((this.passive_count == rhs.passive_count)||((this.passive_count!= null)&&this.passive_count.equals(rhs.passive_count))))&&((this.status == rhs.status)||((this.status!= null)&&this.status.equals(rhs.status)))); + return (((((((this.generation == rhs.generation)||((this.generation!= null)&&this.generation.equals(rhs.generation)))&&((this.phase == rhs.phase)||((this.phase!= null)&&this.phase.equals(rhs.phase))))&&((this.active_count == rhs.active_count)||((this.active_count!= null)&&this.active_count.equals(rhs.active_count))))&&((this.source == rhs.source)||((this.source!= null)&&this.source.equals(rhs.source))))&&((this.passive_count == rhs.passive_count)||((this.passive_count!= null)&&this.passive_count.equals(rhs.passive_count))))&&((this.status == rhs.status)||((this.status!= null)&&this.status.equals(rhs.status)))); } diff --git a/gencode/python/udmi/schema/config_discovery_family.py b/gencode/python/udmi/schema/config_discovery_family.py index df10c41dcf..c7cd0b640a 100644 --- a/gencode/python/udmi/schema/config_discovery_family.py +++ b/gencode/python/udmi/schema/config_discovery_family.py @@ -24,4 +24,5 @@ class FamilyDiscoveryConfig(DataModel): networks: Optional[List[str]] = None passive_sec: Optional[int] = None depth: Optional[Depth] = None + source: Optional[str] = None trace: Optional[TraceDiscoveryConfig] = None diff --git a/gencode/python/udmi/schema/events_discovery.py b/gencode/python/udmi/schema/events_discovery.py index 6fe9294118..cdca6c2c3b 100644 --- a/gencode/python/udmi/schema/events_discovery.py +++ b/gencode/python/udmi/schema/events_discovery.py @@ -4,7 +4,7 @@ from __future__ import annotations from dataclasses import dataclass -from typing import Any, Dict, Optional +from typing import Any, Dict, List, Optional from ._base import DataModel from .ancillary_properties import AncillaryProperties @@ -37,9 +37,12 @@ class DiscoveryEvents(DataModel): generation: Optional[str] = None status: Optional[Entry] = None family: Optional[Any] = None + source: Optional[str] = None addr: Optional[str] = None network: Optional[str] = None event_no: Optional[int] = None + active_count: Optional[int] = None + addrs: Optional[List[str]] = None families: Optional[Dict[str, FamilyDiscovery]] = None registries: Optional[Dict[str, CloudModel]] = None devices: Optional[Dict[str, CloudModel]] = None @@ -48,3 +51,6 @@ class DiscoveryEvents(DataModel): features: Optional[Dict[str, FeatureDiscovery]] = None cloud_model: Optional[CloudModel] = None system: Optional[System] = None + model: Optional[Metadata] = None + +from .metadata import Metadata diff --git a/gencode/python/udmi/schema/metadata.py b/gencode/python/udmi/schema/metadata.py index 1f8ac5e28a..38e1e6af58 100644 --- a/gencode/python/udmi/schema/metadata.py +++ b/gencode/python/udmi/schema/metadata.py @@ -8,7 +8,6 @@ from ._base import DataModel from .common import Operation -from .events_discovery import DiscoveryEvents from .model_alarmset import AlarmsetModel from .model_cloud import CloudModel from .model_discovery import DiscoveryModel @@ -47,3 +46,5 @@ class Metadata(DataModel): pointset: Optional[PointsetModel] = None structure: Optional[Dict[str, DiscoveryEvents]] = None policy: Optional[PolicyModel] = None + +from .events_discovery import DiscoveryEvents diff --git a/gencode/python/udmi/schema/state_discovery_family.py b/gencode/python/udmi/schema/state_discovery_family.py index 85ccf0d4e9..29228ed4dd 100644 --- a/gencode/python/udmi/schema/state_discovery_family.py +++ b/gencode/python/udmi/schema/state_discovery_family.py @@ -31,4 +31,5 @@ class FamilyDiscoveryState(DataModel): phase: Optional[Phase] = None active_count: Optional[int] = None passive_count: Optional[int] = None + source: Optional[str] = None status: Optional[Entry] = None diff --git a/mcp/butler/client.py b/mcp/butler/client.py index a70f88c51f..228ef2da9b 100644 --- a/mcp/butler/client.py +++ b/mcp/butler/client.py @@ -93,6 +93,10 @@ def get_discovered_devices(self, registry_id: str) -> List[Dict[str, Any]]: """Fetch normalized discovered devices with addresses for reconciliation mapping.""" return self.call_rpc("get_discovered_devices", {"registry_id": registry_id}) + def get_discovered_models(self, registry_id: str) -> Dict[str, Dict[str, Any]]: + """Fetch discovered device models (Metadata) for a registry from Model Discovery events.""" + return self.call_rpc("get_discovered_models", {"registry_id": registry_id}) + def get_device_messages(self, registry_id: str, device_id: str) -> List[Dict[str, Any]]: """Fetch chronological lifecycle messages (models, discovery, proposals) for a device.""" return self.call_rpc("get_device_messages", {"registry_id": registry_id, "device_id": device_id}) diff --git a/mcp/butler/provider.py b/mcp/butler/provider.py index da364a7818..a5b736404f 100644 --- a/mcp/butler/provider.py +++ b/mcp/butler/provider.py @@ -256,6 +256,9 @@ def get_discovered_devices(self, registry_id: str) -> List[Dict[str, Any]]: if isinstance(payload, dict) and "payload" in payload and isinstance(payload.get("payload"), dict): payload = payload["payload"] + if not isinstance(payload, dict) or payload.get("family") == "iot": + continue + bacnet_addr = None ipv4_addr = None vendor_addr = None @@ -288,6 +291,140 @@ def get_discovered_devices(self, registry_id: str) -> List[Dict[str, Any]]: return devices + def get_discovered_models(self, registry_id: str) -> Dict[str, Dict[str, Any]]: + """Retrieves discovered device models (Metadata) for a registry from Model Discovery events or metadata store. + + Args: + registry_id: Target device registry identifier. + + Returns: + Dictionary mapping device_id to its latest Metadata dictionary. + """ + models: Dict[str, Dict[str, Any]] = {} + if not self.pg_manager: + return models + + try: + conn = self.pg_manager.get_connection() + with conn.cursor() as cur: + cur.execute( + """ + SELECT DISTINCT ON (device_id) device_id, metadata + FROM udmi_metadata + WHERE device_registry_id = %s AND metadata IS NOT NULL + ORDER BY device_id, timestamp DESC; + """, + (registry_id,), + ) + meta_rows = cur.fetchall() + for dev_id, meta_payload in meta_rows: + if isinstance(meta_payload, str): + try: + meta_payload = json.loads(meta_payload) + except Exception: + continue + if dev_id and isinstance(meta_payload, dict): + models[str(dev_id)] = meta_payload + + cur.execute( + """ + SELECT device_id, sub_type, sub_folder, payload + FROM udmi_messages + WHERE registry_id = %s + AND ( + (sub_folder = 'discovery' AND sub_type = 'events') + OR sub_type = 'model' + ) + ORDER BY id ASC; + """, + (registry_id,), + ) + msg_rows = cur.fetchall() + conn.close() + + gen_batches: Dict[str, Dict[str, Any]] = {} + + def _reconcile_gen(gen_key: str) -> None: + batch = gen_batches.get(gen_key) + if not batch or not batch.get("has_start"): + return + active_count = batch.get("active_count") + seen = batch.get("seen", set()) + if not isinstance(active_count, int) or len(seen) < active_count: + return + gen_batches.pop(gen_key, None) + addrs = batch.get("addrs") + if isinstance(addrs, list): + for addr in addrs: + addr_str = str(addr) + if addr_str not in seen: + models.pop(addr_str, None) + else: + for existing_id in list(models.keys()): + if existing_id not in seen: + models.pop(existing_id, None) + + for dev_id, sub_type, sub_folder, payload in msg_rows: + if isinstance(payload, str): + try: + payload = json.loads(payload) + except Exception: + continue + if isinstance(payload, dict) and "payload" in payload and isinstance(payload.get("payload"), dict): + payload = payload["payload"] + if not isinstance(payload, dict): + continue + + if sub_folder == "discovery" and sub_type == "events": + if payload.get("family") == "iot": + gen = str(payload.get("generation") or "") + event_no = payload.get("event_no") + if ( + isinstance(event_no, int) + and event_no == 0 + and not payload.get("addr") + and not isinstance(payload.get("model"), dict) + ): + if gen: + batch = gen_batches.setdefault( + gen, + { + "has_start": False, + "active_count": 0, + "addrs": None, + "seen": set(), + }, + ) + batch["has_start"] = True + batch["active_count"] = payload.get("active_count", 0) + batch["addrs"] = payload.get("addrs") + _reconcile_gen(gen) + elif isinstance(payload.get("model"), dict): + target_dev = payload.get("addr") or dev_id + if target_dev: + target_str = str(target_dev) + models[target_str] = payload["model"] + if gen: + batch = gen_batches.setdefault( + gen, + { + "has_start": False, + "active_count": 0, + "addrs": None, + "seen": set(), + }, + ) + batch["seen"].add(target_str) + _reconcile_gen(gen) + elif sub_type == "model": + if dev_id: + models[str(dev_id)] = payload + + return models + except Exception as e: + print(f"ButlerProvider error fetching discovered models: {e}", file=sys.stderr) + return models + def get_device_messages( self, registry_id: str, @@ -562,6 +699,11 @@ def clear_registry_mapping_data(self, registry_id: str) -> Dict[str, Any]: (registry_id,), ) deleted_count += cur.rowcount + cur.execute( + "DELETE FROM udmi_metadata WHERE device_registry_id = %s;", + (registry_id,), + ) + deleted_count += cur.rowcount conn.commit() finally: diff --git a/mcp/butler/server.py b/mcp/butler/server.py index 3e04c733cd..f7e2ff57a0 100644 --- a/mcp/butler/server.py +++ b/mcp/butler/server.py @@ -59,6 +59,22 @@ "required": ["registry_id"], }, }, + { + "name": "get_discovered_models", + "description": ( + "Retrieve discovered device models (Metadata) for a registry from Model Discovery events." + ), + "inputSchema": { + "type": "object", + "properties": { + "registry_id": { + "type": "string", + "description": "The target device registry identifier", + } + }, + "required": ["registry_id"], + }, + }, { "name": "get_device_messages", "description": ( @@ -439,6 +455,8 @@ def _dispatch_tool(self, name: str, args: Dict[str, Any]) -> Any: return self.provider.get_discovery_events(registry_id=args["registry_id"]) if name == "get_discovered_devices": return self.provider.get_discovered_devices(registry_id=args["registry_id"]) + if name == "get_discovered_models": + return self.provider.get_discovered_models(registry_id=args["registry_id"]) if name == "get_device_messages": return self.provider.get_device_messages( registry_id=args["registry_id"], device_id=args["device_id"] @@ -656,6 +674,10 @@ def main() -> None: devs_parser.add_argument("registry_id", help="Registry ID") devs_parser.add_argument("--pg-port", type=int, default=None) + models_parser = subparsers.add_parser("discovered-models", help="List discovered models for registry") + models_parser.add_argument("registry_id", help="Registry ID") + models_parser.add_argument("--pg-port", type=int, default=None) + msgs_parser = subparsers.add_parser("device-messages", help="List lifecycle messages for a device") msgs_parser.add_argument("registry_id", help="Registry ID") msgs_parser.add_argument("device_id", help="Device ID") @@ -694,6 +716,11 @@ def main() -> None: print(json.dumps(devs, indent=2)) return + if args.command == "discovered-models": + models = provider.get_discovered_models(args.registry_id) + print(json.dumps(models, indent=2)) + return + if args.command == "device-messages": msgs = provider.get_device_messages(args.registry_id, args.device_id) print(json.dumps(msgs, indent=2)) diff --git a/mcp/uufi/provider.py b/mcp/uufi/provider.py index acc68835b6..a9c858d752 100644 --- a/mcp/uufi/provider.py +++ b/mcp/uufi/provider.py @@ -105,6 +105,8 @@ def get_topic( prefix_seg = f"/{self.prefix}" if self.prefix else "" if registry_id and device_id: return f"{prefix_seg}/uufi/r/{registry_id}/d/{device_id}/c/{sub_type}/{sub_folder}" + if registry_id: + return f"{prefix_seg}/uufi/r/{registry_id}/c/{sub_type}/{sub_folder}" return f"{prefix_seg}/uufi/c/{sub_type}/{sub_folder}" def create_envelope( diff --git a/schema/category.json b/schema/category.json index d6ea72c022..54d9a76d53 100644 --- a/schema/category.json +++ b/schema/category.json @@ -1,7 +1,6 @@ { "$udmi_version": "1.5.7", "title": "Category", - "$schema": "http://json-schema.org/draft-07/schema#", "$comment": "Auto-generated category mappings from bin/gencode_categories.", "type": "string", "oneOf": [ diff --git a/schema/config_discovery_family.json b/schema/config_discovery_family.json index d8e3304eda..61423b9a75 100644 --- a/schema/config_discovery_family.json +++ b/schema/config_discovery_family.json @@ -44,6 +44,10 @@ "description": "Indicates implicit enumeration of discovered devices", "$ref": "file:common.json#/definitions/depth" }, + "source": { + "description": "Optional source qualifier for the discovery scan (e.g., 'model')", + "type": "string" + }, "trace": { "description": "Trace discovery parameters (when depth is 'trace')", "$ref": "file:config_discovery_trace.json" diff --git a/schema/events_discovery.json b/schema/events_discovery.json index 50d432083d..857de42dcb 100644 --- a/schema/events_discovery.json +++ b/schema/events_discovery.json @@ -29,6 +29,10 @@ "family": { "existingJavaType": "String" }, + "source": { + "description": "Optional source qualifier for the discovery scan (e.g., 'model')", + "type": "string" + }, "addr": { "description": "The primary address of the device (for indicated family)", "type": "string" @@ -41,6 +45,18 @@ "description": "The active or passive series number of this result (matches reported state values)", "type": "integer" }, + "active_count": { + "description": "Total number of records expected for this active scan generation (only populated on the event_no=0 start marker event)", + "minimum": 0, + "type": "integer" + }, + "addrs": { + "description": "List of target addresses for a partial discovery scan shard (only populated on the event_no=0 start marker event)", + "type": "array", + "items": { + "type": "string" + } + }, "families": { "description": "Links to other address families (family and id)", "type": "object", @@ -131,6 +147,10 @@ "$ref": "file:state_system_hardware.json" } } + }, + "model": { + "description": "Device metadata model (for model discovery)", + "$ref": "file:metadata.json" } } } diff --git a/schema/model_pointset_point.json b/schema/model_pointset_point.json index e152f80c6c..c5dcf8aed2 100644 --- a/schema/model_pointset_point.json +++ b/schema/model_pointset_point.json @@ -107,8 +107,8 @@ "expr": { "type": "string", "description": "Virtual equipment mapping linking this local point to a remote point in another device", - "pattern": "^[-0-9a-zA-Z$]+:[a-z0-9_]+$", - "examples": ["VAV-3:supply_air_flowrate_sensor"] + "pattern": ".*[-_0-9a-zA-Z$]+:[a-z0-9_]+.*", + "examples": ["VAV-3:supply_air_flowrate_sensor", "VAV-101:zone_air_temp_sensor - VAV-102:zone_air_temp_sensor"] }, "values": { "type": "object", diff --git a/schema/state_discovery_family.json b/schema/state_discovery_family.json index c6eb4d821b..498532ac29 100644 --- a/schema/state_discovery_family.json +++ b/schema/state_discovery_family.json @@ -29,6 +29,10 @@ "minimum": 0, "type": "integer" }, + "source": { + "description": "Optional source qualifier for the discovery scan (e.g., 'model')", + "type": "string" + }, "status": { "description": "Status information about the discovery operation", "$ref": "file:entry.json" diff --git a/tests/schemas/config/model_discovery.json b/tests/schemas/config/model_discovery.json new file mode 100644 index 0000000000..d70ab9e666 --- /dev/null +++ b/tests/schemas/config/model_discovery.json @@ -0,0 +1,13 @@ +{ + "version": "1.5.7", + "timestamp": "2026-04-06T20:10:00Z", + "discovery": { + "families": { + "iot": { + "generation": "2026-04-06T20:10:00Z", + "depth": "details", + "source": "model" + } + } + } +} diff --git a/tests/schemas/config/model_discovery.out b/tests/schemas/config/model_discovery.out new file mode 100644 index 0000000000..e69de29bb2 diff --git a/tests/schemas/events_discovery/model_discovery_device.json b/tests/schemas/events_discovery/model_discovery_device.json new file mode 100644 index 0000000000..071e4e14ce --- /dev/null +++ b/tests/schemas/events_discovery/model_discovery_device.json @@ -0,0 +1,33 @@ +{ + "version": "1.5.7", + "timestamp": "2026-04-06T20:10:01Z", + "generation": "2026-04-06T20:10:00Z", + "event_no": 1, + "family": "iot", + "addr": "AHU-1", + "model": { + "version": "1.5.7", + "timestamp": "2026-04-06T20:00:00Z", + "system": { + "location": { + "site": "US-SFO-X1", + "section": "1F" + }, + "physical_tag": { + "asset": { + "guid": "drw://1234", + "site": "US-SFO-X1", + "name": "AHU-1" + } + } + }, + "pointset": { + "points": { + "filter_alarm_pressure_status": { + "units": "No-units", + "ref": "BV11.present_value" + } + } + } + } +} diff --git a/tests/schemas/events_discovery/model_discovery_device.out b/tests/schemas/events_discovery/model_discovery_device.out new file mode 100644 index 0000000000..e69de29bb2 diff --git a/tests/schemas/events_discovery/model_discovery_start.json b/tests/schemas/events_discovery/model_discovery_start.json new file mode 100644 index 0000000000..dfa7d26c6f --- /dev/null +++ b/tests/schemas/events_discovery/model_discovery_start.json @@ -0,0 +1,10 @@ +{ + "version": "1.5.7", + "timestamp": "2026-04-06T20:10:01Z", + "generation": "2026-04-06T20:10:00Z", + "event_no": 0, + "family": "iot", + "source": "model", + "addrs": ["AHU-1", "AHU-2"], + "active_count": 3 +} diff --git a/tests/schemas/events_discovery/model_discovery_start.out b/tests/schemas/events_discovery/model_discovery_start.out new file mode 100644 index 0000000000..e69de29bb2 diff --git a/udmis/src/main/java/com/google/bos/udmi/service/core/ControlProcessor.java b/udmis/src/main/java/com/google/bos/udmi/service/core/ControlProcessor.java index 96cd7ca001..79abf0310a 100644 --- a/udmis/src/main/java/com/google/bos/udmi/service/core/ControlProcessor.java +++ b/udmis/src/main/java/com/google/bos/udmi/service/core/ControlProcessor.java @@ -8,6 +8,7 @@ import java.util.Map.Entry; import java.util.Set; import udmi.schema.CloudQuery; +import udmi.schema.DiscoveryConfig; import udmi.schema.EndpointConfiguration; import udmi.schema.Envelope; import udmi.schema.Envelope.SubFolder; @@ -40,6 +41,15 @@ public void cloudQueryHandler(CloudQuery query) { CloudQueryHandler.processQuery(this, query); } + /** + * Handle a discovery config command. + */ + @MessageHandler + public void discoveryConfigHandler(DiscoveryConfig config) { + Envelope envelope = getContinuation(config).getEnvelope(); + ModelDiscoveryHandler.processDiscoveryConfig(this, envelope, config); + } + /** * Handle a udmi config command. */ diff --git a/udmis/src/main/java/com/google/bos/udmi/service/core/ModelDiscoveryHandler.java b/udmis/src/main/java/com/google/bos/udmi/service/core/ModelDiscoveryHandler.java new file mode 100644 index 0000000000..17abe93723 --- /dev/null +++ b/udmis/src/main/java/com/google/bos/udmi/service/core/ModelDiscoveryHandler.java @@ -0,0 +1,427 @@ +package com.google.bos.udmi.service.core; + +import static com.google.udmi.util.GeneralUtils.friendlyStackTrace; +import static com.google.udmi.util.GeneralUtils.isTrue; +import static com.google.udmi.util.JsonUtil.fromString; +import static com.google.udmi.util.JsonUtil.isoConvert; +import static java.util.Optional.ofNullable; + +import com.google.bos.udmi.service.access.IotAccessBase; +import com.google.bos.udmi.service.pod.UdmiServicePod; +import com.google.udmi.util.MetadataMapKeys; +import java.util.ArrayDeque; +import java.util.ArrayList; +import java.util.Collections; +import java.util.Date; +import java.util.Deque; +import java.util.HashMap; +import java.util.HashSet; +import java.util.LinkedHashSet; +import java.util.List; +import java.util.Map; +import java.util.Map.Entry; +import java.util.Objects; +import java.util.Set; +import java.util.TreeSet; +import java.util.concurrent.ConcurrentHashMap; +import java.util.regex.Matcher; +import java.util.regex.Pattern; +import java.util.stream.Collectors; +import udmi.lib.ProtocolFamily; +import udmi.schema.Category; +import udmi.schema.CloudModel; +import udmi.schema.DiscoveryConfig; +import udmi.schema.DiscoveryEvents; +import udmi.schema.DiscoveryState; +import udmi.schema.Enumerations.Depth; +import udmi.schema.Envelope; +import udmi.schema.Envelope.SubFolder; +import udmi.schema.Envelope.SubType; +import udmi.schema.FamilyDiscoveryConfig; +import udmi.schema.FamilyDiscoveryState; +import udmi.schema.FamilyDiscoveryState.Phase; +import udmi.schema.GatewayModel; +import udmi.schema.Level; +import udmi.schema.Metadata; +import udmi.schema.PointPointsetModel; + +/** + * Handler for executing Site Model Discovery scans for a device registry. + */ +public class ModelDiscoveryHandler { + + public static final String MODEL_SOURCE = "model"; + private static final Pattern EXPR_DEVICE_PATTERN = + Pattern.compile("([-_0-9a-zA-Z$]+):[a-z0-9_]+"); + private static final ConcurrentHashMap REGISTRY_LOCKS = + new ConcurrentHashMap<>(); + + private final ProcessorBase processor; + private final IotAccessBase iotAccess; + private final String registryId; + private final FamilyDiscoveryConfig familyConfig; + private final Date generation; + + /** + * Create a new ModelDiscoveryHandler instance. + */ + public ModelDiscoveryHandler(ProcessorBase processor, String registryId, + FamilyDiscoveryConfig familyConfig, Date generation) { + this.processor = processor; + this.iotAccess = processor.iotAccess; + this.registryId = registryId; + this.familyConfig = familyConfig; + this.generation = generation; + } + + /** + * Determine whether the given envelope and discovery config target Site Model Discovery. + */ + public static boolean isModelDiscoveryConfig(Envelope envelope, DiscoveryConfig config) { + if (envelope == null || config == null + || envelope.subType != SubType.CONFIG || envelope.subFolder != SubFolder.DISCOVERY) { + return false; + } + FamilyDiscoveryConfig iotConfig = config.families != null + ? config.families.get(ProtocolFamily.IOT) + : null; + return iotConfig != null && MODEL_SOURCE.equals(iotConfig.source); + } + + /** + * Determine whether a target message (event or state) originated from Site Model Discovery. + */ + public static boolean isModelDiscoveryMessage(Envelope envelope, Object message) { + if (envelope != null && MODEL_SOURCE.equals(envelope.source)) { + return true; + } + if (message instanceof DiscoveryEvents de) { + return MODEL_SOURCE.equals(de.source); + } + if (message instanceof DiscoveryState ds && ds.families != null) { + return ds.families.values().stream() + .anyMatch(family -> family != null && MODEL_SOURCE.equals(family.source)); + } + return false; + } + + /** + * Process a DiscoveryConfig targeting model discovery. + */ + public static void processDiscoveryConfig(ProcessorBase processor, Envelope envelope, + DiscoveryConfig config) { + if (!isModelDiscoveryConfig(envelope, config) || envelope.deviceRegistryId == null) { + return; + } + FamilyDiscoveryConfig iotConfig = config.families.get(ProtocolFamily.IOT); + processDiscovery(processor, envelope.deviceRegistryId, iotConfig, config.generation); + } + + /** + * Execute a model discovery scan for the given registry and family configuration. + */ + public static void processDiscovery(ProcessorBase processor, String registryId, + FamilyDiscoveryConfig familyConfig, Date fallbackGeneration) { + if (registryId == null) { + return; + } + Date generation = ofNullable(familyConfig).map(c -> c.generation).orElse(fallbackGeneration); + if (generation == null) { + return; + } + new ModelDiscoveryHandler(processor, registryId, familyConfig, generation).process(); + } + + /** + * Extract target device IDs referenced in pointset point expressions (excluding $SELF). + */ + public static Set extractExpressionReferences(Metadata metadata) { + if (metadata == null || metadata.pointset == null || metadata.pointset.points == null) { + return Set.of(); + } + Set refs = new HashSet<>(); + for (PointPointsetModel point : metadata.pointset.points.values()) { + if (point != null && point.expr != null) { + Matcher matcher = EXPR_DEVICE_PATTERN.matcher(point.expr); + while (matcher.find()) { + String refDevice = matcher.group(1); + if (!refDevice.startsWith("$")) { + refs.add(refDevice); + } + } + } + } + return refs; + } + + /** + * Extract all target device IDs referenced in a device's Metadata model across + * relationships, pointset point expressions, and gateway bindings. + */ + public static Set extractReferencedDevices(Metadata metadata) { + if (metadata == null) { + return Set.of(); + } + Set refs = new HashSet<>(); + if (metadata.relationships != null) { + refs.addAll(metadata.relationships.keySet()); + } + refs.addAll(extractExpressionReferences(metadata)); + if (metadata.gateway != null) { + if (metadata.gateway.gateway_id != null && !metadata.gateway.gateway_id.isEmpty()) { + refs.add(metadata.gateway.gateway_id); + } + if (metadata.gateway.proxy_ids != null) { + metadata.gateway.proxy_ids.stream() + .filter(Objects::nonNull) + .forEach(refs::add); + } + } + return refs; + } + + /** + * Run the complete model discovery lifecycle: start state bookend, start/count event + * (event_no = 0), individual device model events (event_no = 1..N), and stop state bookend. + * Synchronized per registry across all UDMIS processor instances. + */ + public void process() { + synchronized (REGISTRY_LOCKS.computeIfAbsent(registryId, k -> new Object())) { + executeProcess(); + } + } + + private void executeProcess() { + processor.info("Starting model discovery for registry %s (generation %s)", + registryId, generation); + + publishState(Phase.ACTIVE, 0, null); + int activeCount = 0; + + try { + CloudModel registryListing = iotAccess != null + ? iotAccess.listDevices(registryId, null) + : null; + Set siteDevices = (registryListing == null || registryListing.device_ids == null) + ? Set.of() + : registryListing.device_ids.entrySet().stream() + .filter(entry -> !isTrue(entry.getValue().blocked)) + .map(Entry::getKey) + .collect(Collectors.toCollection(TreeSet::new)); + + List shardAddrs = getShardAddrs(); + Map metadataCache = new HashMap<>(); + List closureDevices = resolveClosure(siteDevices, shardAddrs, metadataCache); + + activeCount = closureDevices.size(); + publishStartEvent(activeCount, shardAddrs); + + if (shouldDetailEntries()) { + int eventNo = 0; + for (String deviceId : closureDevices) { + publishDeviceEvent(deviceId, ++eventNo, metadataCache); + } + } + + publishState(Phase.STOPPED, activeCount, null); + + processor.info("Completed model discovery for registry %s (%d active devices)", + registryId, activeCount); + } catch (Exception e) { + udmi.schema.Entry status = new udmi.schema.Entry(); + status.category = Category.DISCOVERY_FAMILY_SCAN; + status.level = Level.ERROR.value(); + status.message = friendlyStackTrace(e); + status.timestamp = new Date(); + publishState(Phase.STOPPED, activeCount, status); + throw new RuntimeException( + String.format("While executing model discovery for registry %s", registryId), e); + } + } + + private boolean shouldDetailEntries() { + Depth depth = familyConfig != null ? familyConfig.depth : null; + return depth == null || depth == Depth.DETAILS || depth == Depth.PARTS; + } + + private List getShardAddrs() { + if (familyConfig == null || familyConfig.addrs == null || familyConfig.addrs.isEmpty()) { + return null; + } + List addrs = familyConfig.addrs.stream() + .filter(Objects::nonNull) + .distinct() + .toList(); + return addrs.isEmpty() ? null : addrs; + } + + private List resolveClosure(Set siteDevices, List shardAddrs, + Map metadataCache) { + if (shardAddrs == null) { + return new ArrayList<>(siteDevices); + } + Set shardSet = new HashSet<>(shardAddrs); + Map> deviceRefs = new HashMap<>(); + Map> exprRefs = new HashMap<>(); + for (String deviceId : siteDevices) { + Metadata metadata = fetchDeviceMetadata(deviceId); + metadataCache.put(deviceId, metadata); + deviceRefs.put(deviceId, new HashSet<>(extractReferencedDevices(metadata))); + exprRefs.put(deviceId, extractExpressionReferences(metadata)); + } + + // Ensure gateway <-> proxy edges are bidirectional even if gateway metadata omitted proxy_ids + for (String deviceId : siteDevices) { + Metadata metadata = metadataCache.get(deviceId); + String gatewayId = (metadata != null && metadata.gateway != null) + ? metadata.gateway.gateway_id + : null; + if (gatewayId != null && !gatewayId.isEmpty() && siteDevices.contains(gatewayId)) { + deviceRefs.computeIfAbsent(gatewayId, k -> new HashSet<>()).add(deviceId); + Metadata gwMetadata = metadataCache.get(gatewayId); + if (gwMetadata != null) { + if (gwMetadata.gateway == null) { + gwMetadata.gateway = new GatewayModel(); + } + Set mergedProxies = new LinkedHashSet<>( + gwMetadata.gateway.proxy_ids != null ? gwMetadata.gateway.proxy_ids : List.of()); + mergedProxies.add(deviceId); + gwMetadata.gateway.proxy_ids = new ArrayList<>(mergedProxies); + } + } + } + + Set closure = new TreeSet<>(); + Deque queue = new ArrayDeque<>(); + + // 1. Seed with active devices in Shard + for (String shardId : shardAddrs) { + if (siteDevices.contains(shardId) && closure.add(shardId)) { + queue.add(shardId); + } + } + + // 2. Seed with 1-hop inbound referrers across all reference types (including to deleted shard + // devices) + for (String deviceId : siteDevices) { + if (!Collections.disjoint(deviceRefs.getOrDefault(deviceId, Set.of()), shardSet)) { + if (closure.add(deviceId)) { + queue.add(deviceId); + } + } + } + + // 3. Seed with transitive inbound referrers across pointset expressions (C -> B -> A) + boolean addedExprInbound = true; + Set exprTargets = new HashSet<>(shardSet); + exprTargets.addAll(closure); + while (addedExprInbound) { + addedExprInbound = false; + for (String deviceId : siteDevices) { + if (!closure.contains(deviceId) + && !Collections.disjoint(exprRefs.getOrDefault(deviceId, Set.of()), exprTargets)) { + closure.add(deviceId); + queue.add(deviceId); + exprTargets.add(deviceId); + addedExprInbound = true; + } + } + } + + // 4. Transitive outbound BFS across all reference types + while (!queue.isEmpty()) { + String current = queue.poll(); + for (String target : deviceRefs.getOrDefault(current, Set.of())) { + if (siteDevices.contains(target) && closure.add(target)) { + queue.add(target); + } + } + } + + return new ArrayList<>(closure); + } + + private Envelope createDiscoveryEnvelope(SubType subType, String deviceId) { + Envelope envelope = new Envelope(); + envelope.deviceRegistryId = registryId; + envelope.deviceId = deviceId; + envelope.subType = subType; + envelope.subFolder = SubFolder.DISCOVERY; + envelope.source = MODEL_SOURCE; + envelope.transactionId = isoConvert(generation); + return envelope; + } + + private DiscoveryEvents createDiscoveryEvent(int eventNo) { + DiscoveryEvents event = new DiscoveryEvents(); + event.version = UdmiServicePod.UDMI_VERSION; + event.timestamp = new Date(); + event.generation = generation; + event.event_no = eventNo; + event.family = ProtocolFamily.IOT; + event.source = MODEL_SOURCE; + return event; + } + + private void publishState(Phase phase, int activeCount, udmi.schema.Entry status) { + FamilyDiscoveryState familyState = new FamilyDiscoveryState(); + familyState.generation = generation; + familyState.phase = phase; + familyState.active_count = activeCount; + familyState.source = MODEL_SOURCE; + familyState.status = status; + + DiscoveryState discoveryState = new DiscoveryState(); + discoveryState.version = UdmiServicePod.UDMI_VERSION; + discoveryState.timestamp = new Date(); + discoveryState.generation = generation; + discoveryState.families = new HashMap<>(); + discoveryState.families.put(ProtocolFamily.IOT, familyState); + + processor.publish(createDiscoveryEnvelope(SubType.STATE, null), discoveryState); + } + + private void publishStartEvent(int activeCount, List shardAddrs) { + DiscoveryEvents countEvent = createDiscoveryEvent(0); + countEvent.active_count = activeCount; + countEvent.addrs = shardAddrs; + processor.publish(createDiscoveryEnvelope(SubType.EVENTS, null), countEvent); + } + + private void publishDeviceEvent(String deviceId, int eventNo, + Map metadataCache) { + DiscoveryEvents deviceEvent = createDiscoveryEvent(eventNo); + deviceEvent.addr = deviceId; + deviceEvent.model = metadataCache.containsKey(deviceId) + ? metadataCache.get(deviceId) + : fetchDeviceMetadata(deviceId); + processor.publish(createDiscoveryEnvelope(SubType.EVENTS, deviceId), deviceEvent); + } + + private Metadata fetchDeviceMetadata(String deviceId) { + CloudModel deviceModel = iotAccess != null ? iotAccess.fetchDevice(registryId, deviceId) : null; + if (deviceModel == null) { + return null; + } + String metadataStr = deviceModel.metadata != null + ? deviceModel.metadata.get(MetadataMapKeys.UDMI_METADATA) + : null; + Metadata metadata = (metadataStr != null && !metadataStr.isBlank()) + ? fromString(Metadata.class, metadataStr) + : null; + if (deviceModel.gateway != null && deviceModel.gateway.proxy_ids != null + && !deviceModel.gateway.proxy_ids.isEmpty()) { + if (metadata == null) { + metadata = new Metadata(); + } + if (metadata.gateway == null) { + metadata.gateway = new GatewayModel(); + } + Set mergedProxies = new LinkedHashSet<>( + metadata.gateway.proxy_ids != null ? metadata.gateway.proxy_ids : List.of()); + mergedProxies.addAll(deviceModel.gateway.proxy_ids); + metadata.gateway.proxy_ids = new ArrayList<>(mergedProxies); + } + return metadata; + } +} diff --git a/udmis/src/main/java/com/google/bos/udmi/service/core/ReflectProcessor.java b/udmis/src/main/java/com/google/bos/udmi/service/core/ReflectProcessor.java index 84c3e0f9e5..92fd38d0de 100644 --- a/udmis/src/main/java/com/google/bos/udmi/service/core/ReflectProcessor.java +++ b/udmis/src/main/java/com/google/bos/udmi/service/core/ReflectProcessor.java @@ -22,6 +22,7 @@ import static com.google.udmi.util.GeneralUtils.ifNotNullThen; import static com.google.udmi.util.GeneralUtils.ifNullThen; import static com.google.udmi.util.GeneralUtils.ifTrueThen; +import static com.google.udmi.util.GeneralUtils.isNotEmpty; import static com.google.udmi.util.GeneralUtils.multiTrim; import static com.google.udmi.util.GeneralUtils.requireNull; import static com.google.udmi.util.GeneralUtils.stackTraceString; @@ -61,11 +62,13 @@ import java.util.stream.Collectors; import udmi.schema.CloudModel; import udmi.schema.CloudModel.ModelOperation; +import udmi.schema.DiscoveryConfig; import udmi.schema.EndpointConfiguration; import udmi.schema.Entry; import udmi.schema.Envelope; import udmi.schema.Envelope.SubFolder; import udmi.schema.Envelope.SubType; +import udmi.schema.FamilyDiscoveryConfig; import udmi.schema.Level; import udmi.schema.SystemModel; import udmi.schema.UdmiConfig; @@ -298,11 +301,31 @@ private CloudModel queryDeviceState(Envelope attributes) { private CloudModel reflectModel(Envelope attributes, CloudModel request) { CloudModel modelResult = updateModel(attributes, request); - ifNotNullThen(extractModel(request), + Object extracted = extractModel(request); + ifNotNullThen(extracted, model -> publish(makeTargetEnvelope(attributes), model)); + if (request.resource_type == REGISTRY && request.operation != PREVIEW) { + String targetRegistry = getTargetRegistryId(attributes.deviceRegistryId, attributes.deviceId); + Date siteTimestamp = (extracted instanceof SiteMetadataUpdate smu) + ? smu.timestamp : request.timestamp; + Date generation = ofNullable(siteTimestamp).orElseGet(Date::new); + FamilyDiscoveryConfig familyConfig = null; + if (request.device_ids != null && !request.device_ids.isEmpty()) { + familyConfig = new FamilyDiscoveryConfig(); + familyConfig.addrs = request.device_ids.keySet().stream().sorted().toList(); + } + ModelDiscoveryHandler.processDiscovery(this, targetRegistry, familyConfig, generation); + } return modelResult; } + private String getTargetRegistryId(String registryId, String deviceId) { + if (isNotEmpty(deviceId) && (!isNotEmpty(registryId) || reflectRegistry.equals(registryId))) { + return deviceId; + } + return isNotEmpty(registryId) ? registryId : null; + } + private CloudModel updateModel(Envelope attributes, CloudModel request) { if (request.operation == PREVIEW) { // Nothing more to do in this case since this is just preview, meaning no impact. @@ -359,6 +382,16 @@ private CloudModel reflectProcess(Envelope attributes, Object payload) { if (payload == null) { return reply; } + if (attributes.subType == SubType.CONFIG && attributes.subFolder == SubFolder.DISCOVERY) { + DiscoveryConfig discoveryConfig = convertTo(DiscoveryConfig.class, payload); + if (ModelDiscoveryHandler.isModelDiscoveryConfig(attributes, discoveryConfig)) { + Envelope discEnv = deepCopy(attributes); + discEnv.deviceRegistryId = getTargetRegistryId( + attributes.deviceRegistryId, attributes.deviceId); + ModelDiscoveryHandler.processDiscoveryConfig(this, discEnv, discoveryConfig); + return reply; + } + } if (attributes.subType == SubType.CONFIG) { // If there was actually a config change, then the transaction will be acknowledged that way. ifNotNullThen(processConfigChange(attributes, payload, null), updated -> diff --git a/udmis/src/main/java/com/google/bos/udmi/service/core/TargetProcessor.java b/udmis/src/main/java/com/google/bos/udmi/service/core/TargetProcessor.java index 04556a85c0..30a29c4de9 100644 --- a/udmis/src/main/java/com/google/bos/udmi/service/core/TargetProcessor.java +++ b/udmis/src/main/java/com/google/bos/udmi/service/core/TargetProcessor.java @@ -46,6 +46,11 @@ protected void defaultHandler(Object defaultedMessage) { publish(defaultedMessage); + if (ModelDiscoveryHandler.isModelDiscoveryMessage(envelope, defaultedMessage)) { + debug("Skipping reflector forwarding for model discovery message"); + return; + } + if (deviceId == null) { notice("Dropping message with no deviceId: " + stringifyTerse(envelope)); return; diff --git a/udmis/src/main/java/com/google/bos/udmi/service/core/UufiProcessor.java b/udmis/src/main/java/com/google/bos/udmi/service/core/UufiProcessor.java index dbae389406..5486859ad0 100644 --- a/udmis/src/main/java/com/google/bos/udmi/service/core/UufiProcessor.java +++ b/udmis/src/main/java/com/google/bos/udmi/service/core/UufiProcessor.java @@ -16,6 +16,7 @@ import java.util.Objects; import java.util.Optional; import udmi.schema.CloudModel; +import udmi.schema.DiscoveryConfig; import udmi.schema.EndpointConfiguration; import udmi.schema.Envelope; import udmi.schema.Envelope.SubFolder; @@ -166,6 +167,14 @@ private void handleUufiInbound(Envelope envelope, Map messageMap // Standard UDMI devices expect a complete config merged with previous state. if (innerEnvelope.subType == SubType.CONFIG) { + if (innerEnvelope.subFolder == SubFolder.DISCOVERY) { + DiscoveryConfig discoveryConfig = + catchToNull(() -> convertTo(DiscoveryConfig.class, innerPayload)); + if (ModelDiscoveryHandler.isModelDiscoveryConfig(innerEnvelope, discoveryConfig)) { + ModelDiscoveryHandler.processDiscoveryConfig(this, innerEnvelope, discoveryConfig); + return; + } + } String configUpdate = processConfigChange(innerEnvelope, innerPayload, null); if (configUpdate != null) { innerEnvelope.subFolder = null; diff --git a/udmis/src/main/java/com/google/bos/udmi/service/messaging/impl/SimpleMqttPipe.java b/udmis/src/main/java/com/google/bos/udmi/service/messaging/impl/SimpleMqttPipe.java index d65f96d5dc..dd9789d7bf 100644 --- a/udmis/src/main/java/com/google/bos/udmi/service/messaging/impl/SimpleMqttPipe.java +++ b/udmis/src/main/java/com/google/bos/udmi/service/messaging/impl/SimpleMqttPipe.java @@ -176,13 +176,15 @@ private static Map parseUufiTopic(String topic) { int start = Strings.isNullOrEmpty(parts[0]) ? 1 : 0; Envelope envelope = new Envelope(); - // UUFI topic: [/namespace]/uufi/[r/REG/d/DEV/]c/TYPE/FOLDER + // UUFI topic: [/namespace]/uufi/[r/REG/[d/DEV/]]c/TYPE/FOLDER int base = start + 1; if ("r".equals(parts[base])) { envelope.deviceRegistryId = nullAsNull(parts[base + 1]); - checkState("d".equals(parts[base + 2]), "expected devices"); - envelope.deviceId = nullAsNull(parts[base + 3]); - base += 4; + base += 2; + if ("d".equals(parts[base])) { + envelope.deviceId = nullAsNull(parts[base + 1]); + base += 2; + } } checkState("c".equals(parts[base]), "expected commands"); envelope.subType = convertSubType(parts[base + 1]); @@ -385,7 +387,9 @@ private String makeTopic(Envelope envelope) { if (envelope.subType != null || !topic.isEmpty()) { topic = "/" + envelope.subType + topic; } - String channel = IMPLICIT_CHANNEL.equals(envelope.source) ? "" : sendTopicChannel; + boolean isTargetChannel = IMPLICIT_CHANNEL.equals(envelope.source) + || "model".equals(envelope.source); + String channel = isTargetChannel ? "" : sendTopicChannel; return format("/r/%s/d/%s%s%s", envelope.deviceRegistryId, envelope.deviceId, channel, topic); } diff --git a/udmis/src/test/java/com/google/bos/udmi/service/core/ReflectProcessorTest.java b/udmis/src/test/java/com/google/bos/udmi/service/core/ReflectProcessorTest.java index 71e2e91923..507de43374 100644 --- a/udmis/src/test/java/com/google/bos/udmi/service/core/ReflectProcessorTest.java +++ b/udmis/src/test/java/com/google/bos/udmi/service/core/ReflectProcessorTest.java @@ -180,12 +180,85 @@ public void modelDeviceTest() { @Test public void modelRegistryUpdateTest() { + CloudModel registryDevices = new CloudModel(); + registryDevices.device_ids = Map.of("AHU-1", new CloudModel()); + when(provider.listDevices(eq(TEST_REGISTRY), any())).thenReturn(registryDevices); + + CloudModel deviceModel = new CloudModel(); + deviceModel.metadata = Map.of("udmi_metadata", + "{\"version\":\"1.5.2\",\"timestamp\":\"2026-07-28T00:00:00Z\"," + + "\"system\":{\"serial_no\":\"SN-AHU-1\"}}"); + when(provider.fetchDevice(eq(TEST_REGISTRY), eq("AHU-1"))).thenReturn(deviceModel); + CloudModel requestModel = new CloudModel(); requestModel.operation = ModelOperation.UPDATE; requestModel.resource_type = Resource_type.REGISTRY; activeTestInstance(() -> getReverseDispatcher().publish(makeModelBundle(requestModel))); verify(provider, times(1)).modelRegistry(eq(TEST_REGISTRY), any(), eq(requestModel)); + verify(provider, times(1)).listDevices(eq(TEST_REGISTRY), any()); + verify(provider, times(1)).fetchDevice(eq(TEST_REGISTRY), eq("AHU-1")); + + // Verify 1 SiteMetadataUpdate + 4 Model Discovery messages emitted to udmi_target + assertEquals(5, captured.size(), "Expected SiteMetadataUpdate + 4 model discovery messages"); + udmi.schema.DiscoveryEvents startEvent = captured.stream() + .filter(o -> o instanceof udmi.schema.DiscoveryEvents de + && Integer.valueOf(0).equals(de.event_no)) + .map(o -> (udmi.schema.DiscoveryEvents) o) + .findFirst() + .orElseThrow(() -> new AssertionError("Missing event_no=0 DiscoveryEvents")); + org.junit.jupiter.api.Assertions.assertNull(startEvent.addrs); + assertEquals(1, startEvent.active_count); + udmi.schema.DiscoveryEvents devEvent = captured.stream() + .filter(o -> o instanceof udmi.schema.DiscoveryEvents de && "AHU-1".equals(de.addr)) + .map(o -> (udmi.schema.DiscoveryEvents) o) + .findFirst() + .orElseThrow(() -> new AssertionError("Missing AHU-1 DiscoveryEvents")); + assertEquals("AHU-1", devEvent.addr); + assertEquals("SN-AHU-1", devEvent.model.system.serial_no); + } + + @Test + public void modelRegistryPartialShardUpdateTest() { + CloudModel registryDevices = new CloudModel(); + registryDevices.device_ids = Map.of( + "AHU-1", new CloudModel(), + "VAV-101", new CloudModel(), + "FCU-99", new CloudModel() + ); + when(provider.listDevices(eq(TEST_REGISTRY), any())).thenReturn(registryDevices); + + CloudModel ahuModel = new CloudModel(); + ahuModel.metadata = Map.of("udmi_metadata", + "{\"version\":\"1.5.7\",\"timestamp\":\"2026-07-28T00:00:00Z\"," + + "\"system\":{\"serial_no\":\"SN-AHU-1\"}," + + "\"relationships\":{\"VAV-101\":{\"FEEDS\":[{}]}}}"); + CloudModel vavModel = new CloudModel(); + vavModel.metadata = Map.of("udmi_metadata", + "{\"version\":\"1.5.7\",\"timestamp\":\"2026-07-28T00:00:00Z\"," + + "\"system\":{\"serial_no\":\"SN-VAV-101\"}}"); + CloudModel fcuModel = new CloudModel(); + fcuModel.metadata = Map.of("udmi_metadata", + "{\"version\":\"1.5.7\",\"timestamp\":\"2026-07-28T00:00:00Z\"," + + "\"system\":{\"serial_no\":\"SN-FCU-99\"}}"); + when(provider.fetchDevice(eq(TEST_REGISTRY), eq("AHU-1"))).thenReturn(ahuModel); + when(provider.fetchDevice(eq(TEST_REGISTRY), eq("VAV-101"))).thenReturn(vavModel); + when(provider.fetchDevice(eq(TEST_REGISTRY), eq("FCU-99"))).thenReturn(fcuModel); + + CloudModel requestModel = new CloudModel(); + requestModel.operation = ModelOperation.UPDATE; + requestModel.resource_type = Resource_type.REGISTRY; + requestModel.device_ids = Map.of("AHU-1", new CloudModel()); + activeTestInstance(() -> getReverseDispatcher().publish(makeModelBundle(requestModel))); + + udmi.schema.DiscoveryEvents startEvent = captured.stream() + .filter(o -> o instanceof udmi.schema.DiscoveryEvents de + && Integer.valueOf(0).equals(de.event_no)) + .map(o -> (udmi.schema.DiscoveryEvents) o) + .findFirst() + .orElseThrow(() -> new AssertionError("Missing event_no=0 DiscoveryEvents")); + assertEquals(java.util.List.of("AHU-1"), startEvent.addrs); + assertEquals(2, startEvent.active_count); } @Test diff --git a/udmis/src/test/java/com/google/bos/udmi/service/core/TargetProcessorTest.java b/udmis/src/test/java/com/google/bos/udmi/service/core/TargetProcessorTest.java index 5942333d64..c5176bf050 100644 --- a/udmis/src/test/java/com/google/bos/udmi/service/core/TargetProcessorTest.java +++ b/udmis/src/test/java/com/google/bos/udmi/service/core/TargetProcessorTest.java @@ -94,4 +94,46 @@ public void simpleReceive() { verifyCommand(commandCaptor, false); } + private final java.util.List notices = new java.util.concurrent.CopyOnWriteArrayList<>(); + + @Override + protected ProcessorBase getProcessor(udmi.schema.EndpointConfiguration config, + Class clazz) { + if (clazz.equals(TargetProcessor.class)) { + return new TargetProcessor(config) { + @Override + public void notice(String message) { + notices.add(message); + super.notice(message); + } + }; + } + return super.getProcessor(config, clazz); + } + + @Test + public void modelDiscoveryStateSkippedTest() { + notices.clear(); + initializeTestInstance(); + + udmi.schema.FamilyDiscoveryState familyState = new udmi.schema.FamilyDiscoveryState(); + familyState.source = ModelDiscoveryHandler.MODEL_SOURCE; + udmi.schema.DiscoveryState state = new udmi.schema.DiscoveryState(); + state.families = new java.util.HashMap<>(); + state.families.put("iot", familyState); + + Bundle bundle = new Bundle(state); + bundle.envelope.deviceRegistryId = TEST_REGISTRY; + bundle.envelope.deviceId = null; + bundle.envelope.subType = SubType.STATE; + bundle.envelope.subFolder = SubFolder.DISCOVERY; + bundle.envelope.source = "implicit"; + + getReverseDispatcher().publish(bundle); + terminateAndWait(); + + assertTrue(notices.isEmpty(), + "Registry-level model discovery state should not emit a notice: " + notices); + } + } \ No newline at end of file diff --git a/udmis/src/test/java/com/google/bos/udmi/service/core/UufiProcessorTest.java b/udmis/src/test/java/com/google/bos/udmi/service/core/UufiProcessorTest.java index 69c3620b0a..0862594297 100644 --- a/udmis/src/test/java/com/google/bos/udmi/service/core/UufiProcessorTest.java +++ b/udmis/src/test/java/com/google/bos/udmi/service/core/UufiProcessorTest.java @@ -323,6 +323,361 @@ public void outboundMonolithicStateShardingTest() { assertEquals(expectedSubFolders, subFolders, "sharded subfolders mismatch"); } + /** + * Test on-demand Model Discovery via UUFI config/discovery. + */ + @Test + public void modelDiscoveryTest() { + udmi.schema.CloudModel registryModel = new udmi.schema.CloudModel(); + registryModel.device_ids = Map.of( + "AHU-1", new udmi.schema.CloudModel(), + "GAT-123", new udmi.schema.CloudModel() + ); + org.mockito.Mockito.when(provider.listDevices(org.mockito.ArgumentMatchers.eq("reg-1"), any())) + .thenReturn(registryModel); + + udmi.schema.CloudModel ahuModel = new udmi.schema.CloudModel(); + ahuModel.metadata = Map.of("udmi_metadata", + "{\"version\":\"1.5.2\",\"timestamp\":\"2026-07-28T00:00:00Z\"," + + "\"system\":{\"serial_no\":\"SN-AHU-1\"}}"); + udmi.schema.CloudModel gatModel = new udmi.schema.CloudModel(); + gatModel.metadata = Map.of("udmi_metadata", + "{\"version\":\"1.5.2\",\"timestamp\":\"2026-07-28T00:00:00Z\"," + + "\"system\":{\"serial_no\":\"SN-GAT-123\"}}"); + org.mockito.Mockito.when(provider.fetchDevice("reg-1", "AHU-1")).thenReturn(ahuModel); + org.mockito.Mockito.when(provider.fetchDevice("reg-1", "GAT-123")).thenReturn(gatModel); + + Envelope innerEnvelope = new Envelope(); + innerEnvelope.subType = SubType.CONFIG; + innerEnvelope.subFolder = SubFolder.DISCOVERY; + innerEnvelope.deviceRegistryId = "reg-1"; + + Map uufiWrapper = toMap(innerEnvelope); + uufiWrapper.put("payload", Map.of( + "version", "1.5.2", + "timestamp", "2026-07-28T00:00:00Z", + "families", Map.of("iot", Map.of( + "generation", "2026-07-28T00:00:00Z", + "depth", "details", + "source", "model" + )) + )); + + Envelope transportEnvelope = new Envelope(); + transportEnvelope.source = "test-client"; + transportEnvelope.gatewayId = "uufi"; + transportEnvelope.deviceRegistryId = "reg-1"; + transportEnvelope.subType = SubType.CONFIG; + transportEnvelope.subFolder = SubFolder.DISCOVERY; + + activeTestInstance(() -> getReverseDispatcher().publish( + new Bundle(transportEnvelope, uufiWrapper))); + + // 5 messages published by ModelDiscoveryHandler to udmi_target: + // - DiscoveryState (active, active_count=0) + // - DiscoveryEvents (event_no=0, active_count=2) + // - DiscoveryEvents (event_no=1, addr=AHU-1, model=...) + // - DiscoveryEvents (event_no=2, addr=GAT-123, model=...) + // - DiscoveryState (stopped, active_count=2) + assertEquals(5, captured.size(), "Expected 5 model discovery messages"); + + List states = captured.stream() + .filter(o -> o instanceof udmi.schema.DiscoveryState) + .map(o -> (udmi.schema.DiscoveryState) o) + .toList(); + assertEquals(2, states.size(), "Expected 2 DiscoveryState bookend messages"); + + udmi.schema.DiscoveryState startState = states.stream() + .filter(s -> s.families.get("iot").phase == udmi.schema.FamilyDiscoveryState.Phase.ACTIVE) + .findFirst() + .orElseThrow(() -> new AssertionError("Missing ACTIVE DiscoveryState")); + assertEquals(0, startState.families.get("iot").active_count); + assertEquals("model", startState.families.get("iot").source); + + udmi.schema.DiscoveryState stopState = states.stream() + .filter(s -> s.families.get("iot").phase == udmi.schema.FamilyDiscoveryState.Phase.STOPPED) + .findFirst() + .orElseThrow(() -> new AssertionError("Missing STOPPED DiscoveryState")); + assertEquals(2, stopState.families.get("iot").active_count); + assertEquals("model", stopState.families.get("iot").source); + + List events = captured.stream() + .filter(o -> o instanceof udmi.schema.DiscoveryEvents) + .map(o -> (udmi.schema.DiscoveryEvents) o) + .toList(); + assertEquals(3, events.size(), "Expected 3 DiscoveryEvents messages"); + + udmi.schema.DiscoveryEvents startEvent = events.stream() + .filter(e -> Integer.valueOf(0).equals(e.event_no)) + .findFirst() + .orElseThrow(() -> new AssertionError("Missing event_no=0 DiscoveryEvents")); + assertEquals(2, startEvent.active_count); + assertEquals("iot", startEvent.family); + assertEquals("model", startEvent.source); + org.junit.jupiter.api.Assertions.assertNull(startEvent.addrs); + + udmi.schema.DiscoveryEvents devEvent1 = events.stream() + .filter(e -> Integer.valueOf(1).equals(e.event_no)) + .findFirst() + .orElseThrow(() -> new AssertionError("Missing event_no=1 DiscoveryEvents")); + assertEquals("AHU-1", devEvent1.addr); + assertEquals("model", devEvent1.source); + assertNotNull(devEvent1.model); + assertEquals("SN-AHU-1", devEvent1.model.system.serial_no); + + udmi.schema.DiscoveryEvents devEvent2 = events.stream() + .filter(e -> Integer.valueOf(2).equals(e.event_no)) + .findFirst() + .orElseThrow(() -> new AssertionError("Missing event_no=2 DiscoveryEvents")); + assertEquals("GAT-123", devEvent2.addr); + assertEquals("model", devEvent2.source); + assertNotNull(devEvent2.model); + assertEquals("SN-GAT-123", devEvent2.model.system.serial_no); + } + + /** + * Test Model Discovery with depth=entries (count only) and addrs filter. + */ + @Test + public void modelDiscoveryEntriesAndAddrsTest() { + udmi.schema.CloudModel registryModel = new udmi.schema.CloudModel(); + registryModel.device_ids = Map.of( + "AHU-1", new udmi.schema.CloudModel(), + "GAT-123", new udmi.schema.CloudModel() + ); + org.mockito.Mockito.when(provider.listDevices(org.mockito.ArgumentMatchers.eq("reg-1"), any())) + .thenReturn(registryModel); + + Envelope innerEnvelope = new Envelope(); + innerEnvelope.subType = SubType.CONFIG; + innerEnvelope.subFolder = SubFolder.DISCOVERY; + innerEnvelope.deviceRegistryId = "reg-1"; + + Map uufiWrapper = toMap(innerEnvelope); + uufiWrapper.put("payload", Map.of( + "version", "1.5.2", + "timestamp", "2026-07-28T00:00:00Z", + "families", Map.of("iot", Map.of( + "generation", "2026-07-28T00:00:00Z", + "depth", "entries", + "addrs", List.of("AHU-1"), + "source", "model" + )) + )); + + Envelope transportEnvelope = new Envelope(); + transportEnvelope.source = "test-client"; + transportEnvelope.gatewayId = "uufi"; + transportEnvelope.deviceRegistryId = "reg-1"; + transportEnvelope.subType = SubType.CONFIG; + transportEnvelope.subFolder = SubFolder.DISCOVERY; + + activeTestInstance(() -> getReverseDispatcher().publish( + new Bundle(transportEnvelope, uufiWrapper))); + + // With depth=entries, only 3 messages: start state, event_no=0 count, stop state + assertEquals(3, captured.size(), "Expected 3 messages for depth=entries"); + udmi.schema.DiscoveryEvents startEvent = captured.stream() + .filter(o -> o instanceof udmi.schema.DiscoveryEvents) + .map(o -> (udmi.schema.DiscoveryEvents) o) + .findFirst() + .orElseThrow(() -> new AssertionError("Missing DiscoveryEvents for depth=entries")); + assertEquals(0, startEvent.event_no); + assertEquals(1, startEvent.active_count); + assertEquals(List.of("AHU-1"), startEvent.addrs); + } + + /** + * Test Partial (Shard) Model Discovery and Closure expansion across relationships, + * multi-hop inbound/outbound pointset expressions ($SELF excluded), gateway proxy_ids merging, + * and inbound referrers (including referrers to deleted shard devices). + */ + @Test + public void modelDiscoveryShardAndClosureTest() { + udmi.schema.CloudModel registryModel = new udmi.schema.CloudModel(); + registryModel.device_ids = Map.of( + "AHU-1", new udmi.schema.CloudModel(), + "VAV-101", new udmi.schema.CloudModel(), + "VAV-102", new udmi.schema.CloudModel(), + "CHW-1", new udmi.schema.CloudModel(), + "CALC-1", new udmi.schema.CloudModel(), + "CALC-2", new udmi.schema.CloudModel(), + "GAT-1", new udmi.schema.CloudModel(), + "FCU-99", new udmi.schema.CloudModel() + ); + org.mockito.Mockito.when(provider.listDevices(org.mockito.ArgumentMatchers.eq("reg-1"), any())) + .thenReturn(registryModel); + + // AHU-1 -> VAV-101 via relationships, plus $SELF:return_air_temp self-reference + udmi.schema.CloudModel ahuModel = new udmi.schema.CloudModel(); + ahuModel.metadata = Map.of("udmi_metadata", + "{\"version\":\"1.5.7\",\"timestamp\":\"2026-07-28T00:00:00Z\"," + + "\"system\":{\"serial_no\":\"SN-AHU-1\"}," + + "\"pointset\":{\"points\":{\"delta_temp\":{\"expr\":\"$SELF:return_air_temp\"}}}," + + "\"relationships\":{\"VAV-101\":{\"FEEDS\":[{}]}}}"); + // VAV-101 -> VAV-102 via pointset.points.*.expr (2-hop transitive outbound), proxied by GAT-1 + udmi.schema.CloudModel vav101Model = new udmi.schema.CloudModel(); + vav101Model.metadata = Map.of("udmi_metadata", + "{\"version\":\"1.5.7\",\"timestamp\":\"2026-07-28T00:00:00Z\"," + + "\"system\":{\"serial_no\":\"SN-VAV-101\"}," + + "\"gateway\":{\"gateway_id\":\"GAT-1\"}," + + "\"pointset\":{\"points\":{\"zone_temp\":{\"expr\":\"VAV-102:supply_air_temp\"}}}}"); + udmi.schema.CloudModel vav102Model = new udmi.schema.CloudModel(); + vav102Model.metadata = Map.of("udmi_metadata", + "{\"version\":\"1.5.7\",\"timestamp\":\"2026-07-28T00:00:00Z\"," + + "\"system\":{\"serial_no\":\"SN-VAV-102\"}}"); + // CHW-1 references DELETED-DEV (inbound reference to a deleted device in Shard) + udmi.schema.CloudModel chwModel = new udmi.schema.CloudModel(); + chwModel.metadata = Map.of("udmi_metadata", + "{\"version\":\"1.5.7\",\"timestamp\":\"2026-07-28T00:00:00Z\"," + + "\"system\":{\"serial_no\":\"SN-CHW-1\"}," + + "\"relationships\":{\"DELETED-DEV\":{\"FEEDS\":[{}]}}}"); + // 2-hop transitive inbound expr chain: CALC-2 -> CALC-1 -> AHU-1 + udmi.schema.CloudModel calc1Model = new udmi.schema.CloudModel(); + calc1Model.metadata = Map.of("udmi_metadata", + "{\"version\":\"1.5.7\",\"timestamp\":\"2026-07-28T00:00:00Z\"," + + "\"system\":{\"serial_no\":\"SN-CALC-1\"}," + + "\"pointset\":{\"points\":{\"avg_temp\":{\"expr\":\"AHU-1:supply_air_temp\"}}}}"); + udmi.schema.CloudModel calc2Model = new udmi.schema.CloudModel(); + calc2Model.metadata = Map.of("udmi_metadata", + "{\"version\":\"1.5.7\",\"timestamp\":\"2026-07-28T00:00:00Z\"," + + "\"system\":{\"serial_no\":\"SN-CALC-2\"}," + + "\"pointset\":{\"points\":{\"rollup_temp\":{\"expr\":\"CALC-1:avg_temp\"}}}}"); + // GAT-1 omits proxy_ids in static metadata, but CloudModel.gateway.proxy_ids has VAV-101 + udmi.schema.CloudModel gatModel = new udmi.schema.CloudModel(); + gatModel.gateway = new udmi.schema.GatewayModel(); + gatModel.gateway.proxy_ids = List.of("VAV-101"); + gatModel.metadata = Map.of("udmi_metadata", + "{\"version\":\"1.5.7\",\"timestamp\":\"2026-07-28T00:00:00Z\"," + + "\"system\":{\"serial_no\":\"SN-GAT-1\"}}"); + // FCU-99 is completely unrelated to the Shard + udmi.schema.CloudModel fcuModel = new udmi.schema.CloudModel(); + fcuModel.metadata = Map.of("udmi_metadata", + "{\"version\":\"1.5.7\",\"timestamp\":\"2026-07-28T00:00:00Z\"," + + "\"system\":{\"serial_no\":\"SN-FCU-99\"}}"); + + org.mockito.Mockito.when(provider.fetchDevice("reg-1", "AHU-1")).thenReturn(ahuModel); + org.mockito.Mockito.when(provider.fetchDevice("reg-1", "VAV-101")).thenReturn(vav101Model); + org.mockito.Mockito.when(provider.fetchDevice("reg-1", "VAV-102")).thenReturn(vav102Model); + org.mockito.Mockito.when(provider.fetchDevice("reg-1", "CHW-1")).thenReturn(chwModel); + org.mockito.Mockito.when(provider.fetchDevice("reg-1", "CALC-1")).thenReturn(calc1Model); + org.mockito.Mockito.when(provider.fetchDevice("reg-1", "CALC-2")).thenReturn(calc2Model); + org.mockito.Mockito.when(provider.fetchDevice("reg-1", "GAT-1")).thenReturn(gatModel); + org.mockito.Mockito.when(provider.fetchDevice("reg-1", "FCU-99")).thenReturn(fcuModel); + + Envelope innerEnvelope = new Envelope(); + innerEnvelope.subType = SubType.CONFIG; + innerEnvelope.subFolder = SubFolder.DISCOVERY; + innerEnvelope.deviceRegistryId = "reg-1"; + + Map uufiWrapper = toMap(innerEnvelope); + uufiWrapper.put("payload", Map.of( + "version", "1.5.7", + "timestamp", "2026-07-28T00:00:00Z", + "families", Map.of("iot", Map.of( + "generation", "2026-07-28T00:00:00Z", + "depth", "details", + "addrs", List.of("AHU-1", "DELETED-DEV"), + "source", "model" + )) + )); + + Envelope transportEnvelope = new Envelope(); + transportEnvelope.source = "test-client"; + transportEnvelope.gatewayId = "uufi"; + transportEnvelope.deviceRegistryId = "reg-1"; + transportEnvelope.subType = SubType.CONFIG; + transportEnvelope.subFolder = SubFolder.DISCOVERY; + + activeTestInstance(() -> getReverseDispatcher().publish( + new Bundle(transportEnvelope, uufiWrapper))); + + List events = captured.stream() + .filter(o -> o instanceof udmi.schema.DiscoveryEvents) + .map(o -> (udmi.schema.DiscoveryEvents) o) + .toList(); + // event_no=0 + 7 Closure devices (AHU-1, CALC-1, CALC-2, CHW-1, GAT-1, VAV-101, VAV-102); + // FCU-99 is excluded. + assertEquals(8, events.size(), "Expected event_no=0 plus 7 Closure device events"); + + udmi.schema.DiscoveryEvents startEvent = events.stream() + .filter(e -> Integer.valueOf(0).equals(e.event_no)) + .findFirst() + .orElseThrow(); + assertEquals(List.of("AHU-1", "DELETED-DEV"), startEvent.addrs); + assertEquals(7, startEvent.active_count); + + List discoveredAddrs = events.stream() + .filter(e -> e.event_no > 0) + .sorted(java.util.Comparator.comparingInt(e -> e.event_no)) + .map(e -> e.addr) + .toList(); + assertEquals( + List.of("AHU-1", "CALC-1", "CALC-2", "CHW-1", "GAT-1", "VAV-101", "VAV-102"), + discoveredAddrs); + + udmi.schema.DiscoveryEvents gatEvent = events.stream() + .filter(e -> "GAT-1".equals(e.addr)) + .findFirst() + .orElseThrow(); + assertNotNull(gatEvent.model.gateway); + assertEquals(List.of("VAV-101"), gatEvent.model.gateway.proxy_ids); + } + + /** + * Test that corrupt udmi_metadata fails fast and publishes a terminal STOPPED state/discovery + * bookend with an error status entry. + */ + @Test + public void modelDiscoveryFailurePublishesStoppedErrorStateTest() { + udmi.schema.CloudModel registryModel = new udmi.schema.CloudModel(); + registryModel.device_ids = Map.of("AHU-BAD", new udmi.schema.CloudModel()); + org.mockito.Mockito.when(provider.listDevices(org.mockito.ArgumentMatchers.eq("reg-1"), any())) + .thenReturn(registryModel); + + udmi.schema.CloudModel badModel = new udmi.schema.CloudModel(); + badModel.metadata = Map.of("udmi_metadata", "{not-valid-json"); + org.mockito.Mockito.when(provider.fetchDevice("reg-1", "AHU-BAD")).thenReturn(badModel); + + Envelope innerEnvelope = new Envelope(); + innerEnvelope.subType = SubType.CONFIG; + innerEnvelope.subFolder = SubFolder.DISCOVERY; + innerEnvelope.deviceRegistryId = "reg-1"; + + Map uufiWrapper = toMap(innerEnvelope); + uufiWrapper.put("payload", Map.of( + "version", "1.5.7", + "timestamp", "2026-07-28T00:00:00Z", + "families", Map.of("iot", Map.of( + "generation", "2026-07-28T00:00:00Z", + "depth", "details", + "source", "model" + )) + )); + + Envelope transportEnvelope = new Envelope(); + transportEnvelope.source = "test-client"; + transportEnvelope.gatewayId = "uufi"; + transportEnvelope.deviceRegistryId = "reg-1"; + transportEnvelope.subType = SubType.CONFIG; + transportEnvelope.subFolder = SubFolder.DISCOVERY; + + activeTestInstance(() -> getReverseDispatcher().publish( + new Bundle(transportEnvelope, uufiWrapper))); + + List states = captured.stream() + .filter(o -> o instanceof udmi.schema.DiscoveryState) + .map(o -> (udmi.schema.DiscoveryState) o) + .toList(); + assertEquals(2, states.size(), "Expected ACTIVE and terminal STOPPED DiscoveryState on error"); + udmi.schema.DiscoveryState stopState = states.get(1); + assertEquals(udmi.schema.FamilyDiscoveryState.Phase.STOPPED, + stopState.families.get("iot").phase); + assertNotNull(stopState.families.get("iot").status); + assertEquals(udmi.schema.Category.DISCOVERY_FAMILY_SCAN, + stopState.families.get("iot").status.category); + } + private UufiProcessor getProcessor() { return (UufiProcessor) UdmiServicePod.getComponent(UufiProcessor.class); } diff --git a/udmis/src/test/java/com/google/bos/udmi/service/messaging/impl/SimpleMqttPipeTest.java b/udmis/src/test/java/com/google/bos/udmi/service/messaging/impl/SimpleMqttPipeTest.java index 3f714fcd4f..d9b4436bec 100644 --- a/udmis/src/test/java/com/google/bos/udmi/service/messaging/impl/SimpleMqttPipeTest.java +++ b/udmis/src/test/java/com/google/bos/udmi/service/messaging/impl/SimpleMqttPipeTest.java @@ -112,5 +112,20 @@ public void staticEnvelopeTopic() { assertEquals("events", legacyPointset.get(SUBTYPE_PROPERTY_KEY)); assertEquals("pointset", legacyPointset.get(SUBFOLDER_PROPERTY_KEY)); assertEquals(3, legacyPointset.keySet().size()); + + Map uufiRegistry = parseEnvelopeTopic("/uufi/r/my-reg/c/config/discovery"); + assertEquals("my-reg", uufiRegistry.get(REGISTRY_ID_PROPERTY_KEY)); + assertNull(uufiRegistry.get(DEVICE_ID_KEY)); + assertEquals("config", uufiRegistry.get(SUBTYPE_PROPERTY_KEY)); + assertEquals("discovery", uufiRegistry.get(SUBFOLDER_PROPERTY_KEY)); + assertEquals("uufi", uufiRegistry.get(GATEWAY_ID_KEY)); + + Map uufiDevice = + parseEnvelopeTopic("/uufi/r/my-reg/d/my-dev/c/events/discovery"); + assertEquals("my-reg", uufiDevice.get(REGISTRY_ID_PROPERTY_KEY)); + assertEquals("my-dev", uufiDevice.get(DEVICE_ID_KEY)); + assertEquals("events", uufiDevice.get(SUBTYPE_PROPERTY_KEY)); + assertEquals("discovery", uufiDevice.get(SUBFOLDER_PROPERTY_KEY)); + assertEquals("uufi", uufiDevice.get(GATEWAY_ID_KEY)); } } \ No newline at end of file diff --git a/validator/src/main/java/com/google/daq/mqtt/registrar/Registrar.java b/validator/src/main/java/com/google/daq/mqtt/registrar/Registrar.java index b00bb6cb28..1ba4a34319 100644 --- a/validator/src/main/java/com/google/daq/mqtt/registrar/Registrar.java +++ b/validator/src/main/java/com/google/daq/mqtt/registrar/Registrar.java @@ -410,8 +410,8 @@ protected void execute(Runnable modelMunger) { if (createRegistries >= 0) { createRegistries(); } else { - processSiteMetadata(); processAllDevices(modelMunger); + processSiteMetadata(); } writeErrors(); } catch (ExceptionMap em) { @@ -461,11 +461,14 @@ protected Map getWorkingDevices() { return workingDevices; } + private Set targetShardDevices; + private void processSiteMetadata() { SiteMetadata siteMetadata = siteModel.loadSiteMetadata(); siteMetadata.name = ofNullable(siteMetadata.name).orElse(siteModel.getSiteName()); ifTrueThen(updateCloudIoT, - () -> cloudIotManager.updateRegistry(getSiteMetadata(), ModelOperation.UPDATE)); + () -> cloudIotManager.updateRegistry(getSiteMetadata(), ModelOperation.UPDATE, + targetShardDevices)); } private SiteMetadata getSiteMetadata() { @@ -508,7 +511,9 @@ private void setValidateMetadata() { private void setDeviceList(List deviceList) { this.deviceList = deviceList; - blockUnknown = false; + if (blockUnknown == null) { + blockUnknown = false; + } } @CommandLineOption(short_form = "-f", arg_name = "topic", description = "Set PubSub feed topic") @@ -665,17 +670,27 @@ private void processAllDevices(Runnable modelMunger) { ifNotNullThen(modelMunger, Runnable::run); initializeLocalDevices(); updateExplicitDevices(explicitDevices, workingDevices); + targetShardDevices = explicitDevices != null ? new HashSet<>(explicitDevices) : null; cloudModels = ifNotNullGet(fetchCloudModels(), devices -> new ConcurrentHashMap<>(devices)); if (deleteDevices || expungeDevices) { deleteCloudDevices(); return; } + Set targetedExtras = ImmutableSet.of(); if (explicitDevices != null) { - Set unknownLocals = difference(explicitDevices, workingDevices.keySet()); + Set unknownLocals = + new HashSet<>(difference(explicitDevices, workingDevices.keySet())); if (!unknownLocals.isEmpty()) { - throw new RuntimeException( - "Unknown specified devices: " + JOIN_CSV.join(unknownLocals)); + if (isTrue(blockUnknown) && cloudModels != null) { + targetedExtras = new HashSet<>(intersection(unknownLocals, cloudModels.keySet())); + unknownLocals.removeAll(targetedExtras); + explicitDevices.removeAll(targetedExtras); + } + if (!unknownLocals.isEmpty()) { + throw new RuntimeException( + "Unknown specified devices: " + JOIN_CSV.join(unknownLocals)); + } } } @@ -709,8 +724,12 @@ private void processAllDevices(Runnable modelMunger) { finalizeLocalDevices(); - if (cloudModels != null && !isTargeted && !instantiateExtras) { - extraDevices = processExtraDevices(difference(cloudModels.keySet(), targetDevices)); + if (cloudModels != null && !instantiateExtras) { + if (!isTargeted) { + extraDevices = processExtraDevices(difference(cloudModels.keySet(), targetDevices)); + } else if (!targetedExtras.isEmpty()) { + extraDevices = processExtraDevices(targetedExtras); + } } } catch (Exception e) { throw new RuntimeException("While processing devices", e); @@ -826,6 +845,10 @@ private void deleteCloudDevices() { throw new IllegalStateException("Neither delete nor expunge indicated"); } + if (targetShardDevices == null && !(deleteDevices && expungeDevices) && !toDelete.isEmpty()) { + targetShardDevices = new HashSet<>(toDelete); + } + executeDelete(toDelete, reason); if (expungeDevices) { @@ -1130,7 +1153,9 @@ private Set processExtraDevices(Set extraDevices) { dynamicTerminate(extraDevices.size()); System.err.printf("There were %d/%d already blocked devices.%n", alreadyBlocked.get(), extraDevices.size()); - reapExtraDevices(difference(siteModel.getExtraDevices(), extraDevices)); + if (targetShardDevices == null) { + reapExtraDevices(difference(siteModel.getExtraDevices(), extraDevices)); + } return extras.keySet(); } catch (Exception e) { throw new RuntimeException(format("Processing %d extra devices", extraDevices.size()), e); diff --git a/validator/src/main/java/com/google/daq/mqtt/util/CloudIotManager.java b/validator/src/main/java/com/google/daq/mqtt/util/CloudIotManager.java index d83fbd8eba..37eb4dd714 100644 --- a/validator/src/main/java/com/google/daq/mqtt/util/CloudIotManager.java +++ b/validator/src/main/java/com/google/daq/mqtt/util/CloudIotManager.java @@ -32,6 +32,7 @@ import java.util.Map; import java.util.Set; import java.util.concurrent.ConcurrentHashMap; +import java.util.stream.Collectors; import udmi.schema.CloudModel; import udmi.schema.CloudModel.ModelOperation; import udmi.schema.CloudModel.Resource_type; @@ -450,6 +451,14 @@ public String createRegistry(String suffix) { * Update the cloud site metadata for the current registry. */ public void updateRegistry(SiteMetadata siteMetadata, ModelOperation operation) { + updateRegistry(siteMetadata, operation, null); + } + + /** + * Update the cloud site metadata for the current registry with optional explicit device shard. + */ + public void updateRegistry(SiteMetadata siteMetadata, ModelOperation operation, + Set explicitDevices) { CloudModel registryModel = new CloudModel(); registryModel.resource_type = Resource_type.REGISTRY; registryModel.operation = operation; @@ -461,6 +470,10 @@ MetadataMapKeys.UDMI_METADATA, toJsonString(siteMetadata) MetadataMapKeys.UDMI_PROVISIONED, isoConvert(ofNullable(siteMetadata.timestamp).orElseGet(Date::new)) ); + if (explicitDevices != null && !explicitDevices.isEmpty()) { + registryModel.device_ids = explicitDevices.stream() + .collect(Collectors.toMap(id -> id, id -> new CloudModel())); + } getIotProvider().updateRegistry(registryModel); } diff --git a/validator/src/main/java/com/google/daq/mqtt/util/IotMockProvider.java b/validator/src/main/java/com/google/daq/mqtt/util/IotMockProvider.java index b938efbf10..1ff14777df 100644 --- a/validator/src/main/java/com/google/daq/mqtt/util/IotMockProvider.java +++ b/validator/src/main/java/com/google/daq/mqtt/util/IotMockProvider.java @@ -48,7 +48,10 @@ public IotMockProvider(ExecutionConfiguration executionConfiguration) { siteModel = new SiteModel(executionConfiguration); siteModel.initialize(); ifNotTrueThen(executionConfiguration.project_id.equals(SiteModel.MOCK_CLEAN), - () -> siteModel.forEachDeviceId(this::populateCloudModel)); + () -> { + siteModel.forEachDeviceId(this::populateCloudModel); + populateCloudModel(MOCK_DEVICE_ID); + }); client = mockClientString(executionConfiguration.project_id, siteModel.getRegistryId(), siteModel.getCloudRegion()); } @@ -141,7 +144,9 @@ public Map fetchCloudModels(String forGatewayId) { Map siteDeviceModels = siteModel.allMetadata().entrySet().stream().collect( Collectors.toMap(Entry::getKey, model -> makeCloudModel(model.getValue()))); Map deviceModels = new HashMap<>(siteDeviceModels); - deviceModels.put(MOCK_DEVICE_ID, makeMockModel(MOCK_DEVICE_ID)); + if (cloudDevices.containsKey(MOCK_DEVICE_ID)) { + deviceModels.put(MOCK_DEVICE_ID, makeMockModel(MOCK_DEVICE_ID)); + } if (forGatewayId != null) { deviceModels.remove(forGatewayId); deviceModels.remove(PROXY_DEVICE_ID); diff --git a/validator/src/test/java/com/google/daq/mqtt/registrar/RegistrarTest.java b/validator/src/test/java/com/google/daq/mqtt/registrar/RegistrarTest.java index 4930f9716a..3b4f8275c3 100644 --- a/validator/src/test/java/com/google/daq/mqtt/registrar/RegistrarTest.java +++ b/validator/src/test/java/com/google/daq/mqtt/registrar/RegistrarTest.java @@ -11,6 +11,7 @@ import static com.google.daq.mqtt.util.IotMockProvider.ActionType.CREATE_DEVICE_ACTION; import static com.google.daq.mqtt.util.IotMockProvider.ActionType.DELETE_DEVICE_ACTION; import static com.google.daq.mqtt.util.IotMockProvider.ActionType.UPDATE_DEVICE_ACTION; +import static com.google.daq.mqtt.util.IotMockProvider.ActionType.UPDATE_REGISTRY_ACTION; import static com.google.daq.mqtt.util.IotMockProvider.MOCK_DEVICE_ID; import static com.google.udmi.util.GeneralUtils.friendlyStackTrace; import static com.google.udmi.util.GeneralUtils.ifNotNullThen; @@ -115,6 +116,27 @@ public void blockDevicesTest() { Collectors.toSet()).size()); blockActions.forEach(action -> assertEquals("device blocked " + action.deviceId, action.deviceId.equals(MOCK_DEVICE_ID), action.data)); + + List targetedActions = executeRegistrarPopulated( + ImmutableList.of("-b", DEVICE_ID, MOCK_DEVICE_ID)); + List targetedBlocks = filterActions(targetedActions, BLOCK_DEVICE_ACTION); + assertEquals("targeted block action count", 1, targetedBlocks.size()); + assertEquals(MOCK_DEVICE_ID, targetedBlocks.get(0).deviceId); + List registryUpdates = filterActions(targetedActions, UPDATE_REGISTRY_ACTION); + assertEquals(1, registryUpdates.size()); + CloudModel regModel = (CloudModel) registryUpdates.get(0).data; + assertEquals(ImmutableSet.of(DEVICE_ID, MOCK_DEVICE_ID), regModel.device_ids.keySet()); + + List expungeActions = executeRegistrarPopulated(ImmutableList.of("-x")); + List expungeDeletes = filterActions(expungeActions, DELETE_DEVICE_ACTION); + assertEquals("expunge delete action count", 1, expungeDeletes.size()); + assertEquals(MOCK_DEVICE_ID, expungeDeletes.get(0).deviceId); + List expungeRegUpdates = filterActions(expungeActions, UPDATE_REGISTRY_ACTION); + assertEquals(1, expungeRegUpdates.size()); + CloudModel expungeRegModel = (CloudModel) expungeRegUpdates.get(0).data; + assertNotNull("Unscoped -x should populate device_ids shard with expunged devices", + expungeRegModel.device_ids); + assertEquals(ImmutableSet.of(MOCK_DEVICE_ID), expungeRegModel.device_ids.keySet()); } @Test @@ -197,6 +219,12 @@ public void transitiveUpdate() { int transitiveCreates = transitiveCreate.stream() .filter(mock -> !mock.deviceId.equals(GATEWAY_ID)).toList().size(); assertEquals("Transitive devices", 3, transitiveCreates); + + List registryUpdates = filterActions(transitiveMocked, UPDATE_REGISTRY_ACTION); + assertEquals(1, registryUpdates.size()); + CloudModel regModel = (CloudModel) registryUpdates.get(0).data; + assertEquals("Registry update shard should include transitive devices", + 4, regModel.device_ids.size()); } @Test