### Install and configure overlayroot Source: https://github.com/normalframework/nf-sdk/blob/master/UBUNTU.md Installs and configures overlayroot to create an immutable OS base using tmpfs overlay filesystem. This is critical for embedded deployments requiring a read-only root filesystem with volatile data storage. ```shell $ sudo apt-get update $ sudo apt-get install overlayroot # Configure in /etc/overlayroot.conf: overlayroot="tmpfs" ``` -------------------------------- ### Manage NF Bulk Operations via CLI (Bash) Source: https://context7.com/normalframework/nf-sdk/llms.txt Provides Bash examples for using nfcli.py CLI tool to perform backup, restore, find, modify, delete, and error examination operations on NF instances. Requires Python, nfcli.py installed, API credentials, and access to NF URL; accepts inputs like backup files, property values, UUID lists, and time periods. Outputs backups, query results, modifications, or error logs in various formats; suitable for automated scripting but limited to NF API endpoints and authentication. ```bash #!/bin/bash # nfcli.py usage examples # Full system backup to ZIP python nfcli.py --url http://localhost:8080 \ --client-id your-id --client-secret your-secret \ backup output.zip # Restore from backup python restore.py backup.zip # Find points by property python nfcli.py --url http://localhost:8080 \ find --property device_id --value 260001 # Modify polling period in bulk python nfcli.py --url http://localhost:8080 \ modify --uuid-file uuids.txt --set-period 300 # Delete points python nfcli.py --url http://localhost:8080 \ delete --uuid-file uuids.txt # Examine recent errors python nfcli.py --url http://localhost:8080 \ errors --hours 24 # Export structured query from Object Explorer UI # Click "..." menu -> "Export cURL" to get query JSON # Use this JSON with API calls for consistent queries ``` -------------------------------- ### OAuth2 Authentication in Python Source: https://context7.com/normalframework/nf-sdk/llms.txt This snippet demonstrates API authentication using the OAuth2 client credentials flow in Python. It utilizes the `requests` library to handle authentication and sends a GET request to a protected endpoint. ```Python import requests import base64 class JsonOauth(requests.auth.AuthBase): def __init__(self, base, oauth=None, creds=None): self.token = None self.base = base self.oauth = oauth self.creds = creds def handle_401(self, r, **kwargs): r.content r.close() res = requests.post(self.base + "/api/v1/auth/token", json={"client_id": self.oauth[0], "client_secret": self.oauth[1], "grant_type": "client_credentials"}) if res.status_code != 200: raise Exception("Invalid authentication") info = res.json() self.token = info.get("accessToken", info.get("access_token")) prep = r.request.copy() prep.headers["Authorization"] = "Bearer " + self.token _r = r.connection.send(prep, **kwargs) _r.history.append(r) _r.request = prep return _r def __call__(self, r): if self.oauth is not None: if self.token is None: r.register_hook("response", self.handle_401) else: r.headers["Authorization"] = "Bearer " + self.token elif self.creds is not None: r.headers["Authorization"] = "Basic " + base64.b64encode(self.creds.encode("utf-8")).decode("utf-8") return r # Usage auth = JsonOauth( base="http://localhost:8080", oauth=("your-client-id", "your-client-secret") ) response = requests.get("http://localhost:8080/api/v1/point/query", auth=auth) ``` -------------------------------- ### Query Point Database with Structured Queries (Python) Source: https://context7.com/normalframework/nf-sdk/llms.txt Searches the point database using structured queries with field filters. This example demonstrates how to retrieve point metadata like UUID and name. It utilizes the 'helpers' module for NfClient and specifies 'LAYERS_SPLIT' for response format. ```python from helpers import NfClient client = NfClient() res = client.post("/api/v1/point/query", json={ "layer": "default", "structuredQuery": { "and": [ { "field": { "property": "device_id", "text": "260001" } }, { "field": { "property": "type", "text": "OBJECT_ANALOG_VALUE" } } ] }, # LAYERS_SPLIT returns attributes from different layers separately # Without this, all attributes merge into one dictionary "responseFormat": "LAYERS_SPLIT", "pageSize": "100" }) # Response includes point metadata for point in res.json()["points"]: print(f"UUID: {point['uuid']}, Name: {point.get('name', 'N/A')}") ``` -------------------------------- ### Configure Ansible Hosts for NF High Availability Cluster (YAML) Source: https://context7.com/normalframework/nf-sdk/llms.txt Defines inventory for a three-node NF cluster using Ansible, supporting GlusterFS and Redis Sentinel for replication and failover. Requires Ansible installed, target nodes with specified IPs, and playbooks for system dependencies, GlusterFS, and Normal deployment. Configures host groups for automated provisioning across multiple servers; ensures even distribution for load balancing but depends on manual playbook execution. ```yaml all: children: nf_cluster: hosts: nf1: ansible_host: 192.168.1.101 nf2: ansible_host: 192.168.1.102 nf3: ansible_host: 192.168.1.103 ``` -------------------------------- ### Deploy NF on Balena IoT Edge Devices (YAML) Source: https://context7.com/normalframework/nf-sdk/llms.txt Deploys NormalFramework on Balena edge devices with privileged network access for seamless integration. Requires Balena platform, Docker images from normalframework.azurecr.io, and host network mode for connectivity. Outputs configured services for Redis and NF with volumes for data persistence; limited to environments with host network privileges. ```yaml version: "2.2" services: redis: image: normalframework.azurecr.io/normalframework/redis:3.8.2-0 restart: always volumes: - redis-data:/data nf: image: normalframework.azurecr.io/normalframework/nf-full:3.8.2-0 network_mode: host privileged: true restart: always depends_on: - redis environment: - REDIS_ADDRESS=localhost:6379 volumes: - nf-data:/var/nf volumes: redis-data: nf-data: ``` -------------------------------- ### Configure systemd watchdog settings Source: https://github.com/normalframework/nf-sdk/blob/master/UBUNTU.md Edits systemd configuration to enable hardware watchdog monitoring. This helps automatically reboot the system in case of kernel panics or systemd failures. The RuntimeWatchdogSec must be less than hardware watchdog timeout. Requires superuser privileges to apply changes. ```bash sudo systemctl edit system.conf # add or uncomment these lines: [Manager] # Time systemd gives itself to prove it's alive before HW reset # RuntimeWatchdogSec must be less than the hardware watchdog timeout RuntimeWatchdogSec=30s # Optional: reboot automatically if systemd fails to start services RebootWatchdogSec=1min # then sudo systemctl daemon-reexec ``` -------------------------------- ### Configure GRUB kernel boot parameters Source: https://github.com/normalframework/nf-sdk/blob/master/UBUNTU.md Updates GRUB configuration to enable automatic fsck repair and kernel panic handling during boot. This ensures the system can recover from filesystem issues automatically without manual intervention, which is critical for embedded deployments. ```shell # Edit /etc/default/grub: GRUB_CMDLINE_LINUX_DEFAULT="quiet fsck.repair=yes panic=10" # Apply changes: sudo update-grub ``` -------------------------------- ### Configure Docker daemon logging Source: https://github.com/normalframework/nf-sdk/blob/master/UBUNTU.md Configures Docker to use json-file logging driver with size-based rotation to prevent log files from filling up the filesystem. Replace the default logging configuration in /etc/docker/daemon.json and restart the service to prevent system lockups from disk exhaustion. ```json { "log-driver": "json-file", "log-opts": { "max-size": "10m", "max-file": "3" } } ``` ```shell # After saving the configuration: sudo systemctl restart docker ``` -------------------------------- ### Docker Compose Deployment with Host Networking Source: https://context7.com/normalframework/nf-sdk/llms.txt Production deployment configuration using Docker Compose with host networking for BACnet/IP support. Configures resource limits, security hardening with read-only filesystems, firewall settings via NET_ADMIN capability, and persistent volumes for data storage. Includes Redis dependency with memory and CPU constraints. ```yaml version: "2.2" services: nf: image: normal.azurecr.io/normalframework/nf-full:3.9 network_mode: host depends_on: - redis volumes: - /var/nf:/var/nf - /var/nf-redis:/data - /etc/timezone:/etc/timezone:ro - /etc/localtime:/etc/localtime:ro restart: unless-stopped # Resource limits prevent system lockup mem_limit: 1024m cpus: 2.0 # Firewall configuration to protect Redis cap_add: - NET_ADMIN environment: - NORMAL_FIREWALL_INTERFACE=enp1s0 - PORT=8080 - REDIS_ADDRESS=redis:6379 - CONSOLE_USERNAME=admin - CONSOLE_PASSWORD=pw # Read-only security hardening read_only: true tmpfs: - /run:exec - /etc/nginx/conf.d/ - /tmp environment: - S6_READ_ONLY_ROOT=1 redis: image: normal.azurecr.io/normalframework/redis:3.9 ports: - "6379:6379" volumes: - /var/nf-redis:/data restart: unless-stopped mem_limit: 1024m cpus: 2.0 ``` -------------------------------- ### Initiate Sparkplug Recovery Request with Python Source: https://context7.com/normalframework/nf-sdk/llms.txt This Python script demonstrates how to initiate a historical data replay via MQTT NCMD topic. It includes functions for parsing recovery time, building log positions, and constructing Sparkplug Payload protobuf messages. The script supports various payload formats and compression options. ```python #!/usr/bin/env python3 import argparse import gzip import json import paho.mqtt.client as mqtt from datetime import datetime, timezone from sparkplug_b.sparkplug_b_pb2 import Payload def parse_recovery_time(s): """Parse ISO 8601, 'now', epoch seconds, or epoch milliseconds""" if s.lower() == "now": return datetime.now(timezone.utc) # Epoch milliseconds (13+ digits) if s.isdigit() and len(s) >= 13: return datetime.fromtimestamp(int(s) / 1000.0, tz=timezone.utc) # Epoch seconds if s.replace(".", "", 1).isdigit(): return datetime.fromtimestamp(float(s), tz=timezone.utc) # ISO 8601 if s.endswith("Z"): s = s[:-1] + "+00:00" dt = datetime.fromisoformat(s) if dt.tzinfo is None: dt = dt.replace(tzinfo=timezone.utc) return dt.astimezone(timezone.utc) def build_log_position(dt): """Log Position = (unix_ms << 10)""" return int(dt.timestamp() * 1000) << 10 def build_payload_proto(log_position, payload_uuid, timestamp_ms): """Build Sparkplug Payload protobuf""" msg = Payload() msg.timestamp = timestamp_ms msg.uuid = payload_uuid msg.seq = 0 m = msg.metrics.add() m.name = "Log Position" m.datatype = 4 # Int64 m.long_value = int(log_position) return msg.SerializeToString() # Usage example parser = argparse.ArgumentParser() parser.add_argument("--recovery_time", required=True) parser.add_argument("--command_topic", required=True) parser.add_argument("--payload_format", default="proto+gzip", choices=["proto", "proto+gzip", "json", "json+gzip"]) parser.add_argument("--mqtt_broker", default="localhost") parser.add_argument("--mqtt_port", type=int, default=1883) args = parser.parse_args() dt = parse_recovery_time(args.recovery_time) log_position = build_log_position(dt) timestamp_ms = int(dt.timestamp() * 1000) payload_uuid = "unique-request-id" wire = build_payload_proto(log_position, payload_uuid, timestamp_ms) if args.payload_format.endswith("+gzip"): wire = gzip.compress(wire) client = mqtt.Client(client_id="recovery_client") client.connect(args.mqtt_broker, args.mqtt_port) client.publish(args.command_topic, payload=wire, qos=1) # Command line usage: # python initiate-recovery.py \ ``` -------------------------------- ### Configure /etc/fstab for filesystem checks Source: https://github.com/normalframework/nf-sdk/blob/master/UBUNTU.md Updates the filesystem table configuration to set fsck.repair=yes for all persistent filesystems, enabling automatic repair during boot without blocking for user input. ```shell # Edit /etc/fstab and add option 'fsck.repair=yes' for each persistent filesystem ``` -------------------------------- ### Remount root filesystem read-write Source: https://github.com/normalframework/nf-sdk/blob/master/UBUNTU.md Changes the root filesystem mount status from read-only to read-write. Useful when making changes to the root filesystem that require write permissions. Requires superuser privileges to execute. ```bash sudo mount -o remount,rw / ``` -------------------------------- ### Ensure network stickiness on embedded systems Source: https://github.com/normalframework/nf-sdk/blob/master/UBUNTU.md Masks the systemd service that blocks boot until network is online, allowing network connections to persist even when DHCP servers are temporarily unavailable. This prevents connectivity outages on embedded systems. Requires superuser privileges to execute. ```bash sudo systemctl mask systemd-networkd-wait-online.service ``` -------------------------------- ### Create PostgreSQL Schema for Sparkplug B Data Source: https://context7.com/normalframework/nf-sdk/llms.txt This SQL script defines tables and indexes for storing MQTT Sparkplug B data in a PostgreSQL database. It includes tables for node metadata, metric metadata, and time-series data, with constraints and indexes for efficient querying. ```sql -- Node metadata with Last Good Sequence Number tracking CREATE TABLE IF NOT EXISTS nodes ( group_name text NOT NULL, node_name text NOT NULL, lgsn bigint, PRIMARY KEY (group_name, node_name) ); -- Metric metadata with device/metric hierarchy CREATE TABLE IF NOT EXISTS metadata ( id serial UNIQUE, group_name text NOT NULL, node_name text NOT NULL, device_name text NOT NULL, metric_name text NOT NULL, metric_alias bigint, last_dbirth TIMESTAMPTZ, last_ddeath TIMESTAMPTZ, dbirth_seq INT, properties json DEFAULT '{}', -- PropertySet from DBIRTH PRIMARY KEY (group_name, node_name, device_name, metric_name) ); CREATE INDEX metadata_index ON metadata (group_name, node_name, device_name, metric_alias); -- Time-series metric data CREATE TABLE IF NOT EXISTS metrics ( metric_id INT REFERENCES metadata(id), time TIMESTAMPTZ, insert_time TIMESTAMPTZ DEFAULT NOW(), value double precision ); -- Prevent duplicate data insertion CREATE UNIQUE INDEX metrics_index ON metrics (metric_id, time); -- Query example: Get last 24 hours for specific metric SELECT m.time, m.value, md.metric_name, md.device_name FROM metrics m JOIN metadata md ON m.metric_id = md.id WHERE md.group_name = 'normalgw' AND md.device_name = 'Device01' AND m.time > NOW() - INTERVAL '24 hours' ORDER BY m.time DESC; ``` -------------------------------- ### Deploy Independent Data Layer (IDL) with Docker Compose Source: https://context7.com/normalframework/nf-sdk/llms.txt This YAML configuration defines a Docker Compose stack for deploying the Normal Framework with Redis, TimescaleDB, and Grafana. It includes network configurations, environment variables, and volume mounts for persistent data and configurations. ```yaml version: "2.2" services: nf: image: normal.azurecr.io/normalframework/nf-full:3.8 network_mode: host depends_on: - redis tmpfs: - /run:exec - /etc/nginx/conf.d/ - /tmp environment: - PGDATABASE=postgres - PGUSER=postgres - PGPASSWORD=password - PGHOST=localhost redis: image: normal.azurecr.io/normalframework/redis:3.8 ports: - "6379:6379" networks: - idlnet timescaledb: image: timescale/timescaledb:latest-pg16 ports: - "5432:5432" environment: - POSTGRES_PASSWORD=password networks: - idlnet grafana: image: grafana/grafana-enterprise volumes: - ./provisioning:/etc/grafana/provisioning - ./dashboards:/etc/dashboards - ./grafana.ini:/etc/grafana/grafana.ini ports: - "3000:3000" networks: - idlnet networks: idlnet: name: idlnet ``` -------------------------------- ### Regenerate initramfs Source: https://github.com/normalframework/nf-sdk/blob/master/UBUNTU.md Updates the initial ramdisk image used during the boot process. This is necessary after making changes to the system that affect the early boot sequence. Requires superuser privileges to execute. ```bash sudo update-initramfs -u ``` -------------------------------- ### Download Time-Series Data as CSV using Python Source: https://context7.com/normalframework/nf-sdk/llms.txt Exports historical time-series data with aggregation and windowing capabilities. Uses the NfClient helper to query point data in 1-day chunks to avoid timeouts, processes JSON responses into a pandas DataFrame, and exports to CSV format. Implements FIRST aggregation with 15-minute windows for equipment data filtered by polling period. ```python from helpers import NfClient import pandas as pd from datetime import datetime, timezone, timedelta from urllib.parse import urlencode client = NfClient() def download_csv(client, uuids, headers, start, end): # Ensure UTC timezone if start.tzinfo is None: start = start.replace(tzinfo=timezone.utc) if end.tzinfo is None: end = end.replace(tzinfo=timezone.utc) all_data = {} current = start # Download in 1-day chunks to avoid timeout while current < end: chunk_end = min(current + timedelta(days=1), end) params = { "uuids": uuids, "from": current.strftime('%Y-%m-%dT%H:%M:%SZ'), "to": chunk_end.strftime('%Y-%m-%dT%H:%M:%SZ'), "method": "FIRST", # Aggregation method: FIRST, LAST, AVG, MIN, MAX "window": '900s', # 15-minute windows } url = f"/api/v1/point/data?{urlencode(params, doseq=True)}" resp = client.get(url) if resp.status_code != 200: raise RuntimeError(f"Failed: {resp.status_code} - {resp.text}") json_data = resp.json() for series in json_data.get("data", []): uuid = series["uuid"] col_name = dict(zip(uuids, headers)).get(uuid, uuid) for entry in series.get("values", []): ts = entry["ts"] val = entry.get("double", None) all_data.setdefault(ts, {})[col_name] = val current = chunk_end # Create DataFrame df = pd.DataFrame.from_dict(all_data, orient="index") df.index.name = "timestamp" df.sort_index(inplace=True) df = df.reindex(columns=headers) return df # Query points with equipment and polling period filters res = client.post("/api/v1/point/query", json={ "page_size": 500, "structuredQuery": { "and": [ { "field": { "property": "equipRef", "text": "Diggs_RTU7" } }, { "or": [ {"field": {"property": "period", "numeric": {"minValue": 30, "maxValue": 30}}}, {"field": {"property": "period", "numeric": {"minValue": 60, "maxValue": 60}}}, {"field": {"property": "period", "numeric": {"minValue": 300, "maxValue": 300}}}, {"field": {"property": "period", "numeric": {"minValue": 900, "maxValue": 900}}}, ] } ] } }) points = res.json()["points"] headers = [p["name"] for p in points] uuids = [p["uuid"] for p in points] df = download_csv(client, uuids, headers, start=datetime(2025, 5, 10), end=datetime(2025, 5, 16)) df.to_csv("export.csv", na_rep="") ``` -------------------------------- ### POST /api/v2/command/read Source: https://context7.com/normalframework/nf-sdk/llms.txt Read multiple points using the Command API regardless of underlying protocol. This endpoint allows you to read values from multiple points simultaneously with automatic protocol handling. ```APIDOC ## POST /api/v2/command/read ### Description Read multiple points using the Command API regardless of underlying protocol ### Method POST ### Endpoint /api/v2/command/read ### Parameters #### Request Body - **reads** (array) - Required - Array of read operations containing point UUIDs and optional protocol-specific options ### Request Example { "reads": [ { "point": { "uuid": "point-uuid-here", "layer": "hpl:bacnet:1" } } ] } ### Response #### Success Response (200) - **results** (array) - Array of read results containing point information and values - **errors** (array) - Array of any errors encountered during the read operations #### Response Example { "results": [ { "point": {"uuid": "...", "layer": "hpl:bacnet:1"}, "value": { "@type": "type.googleapis.com/normalgw.bacnet.v2.ApplicationDataValue", "real": 50 }, "scalar": "50" } ], "errors": [] } ``` -------------------------------- ### POST /api/v2/command/write Source: https://context7.com/normalframework/nf-sdk/llms.txt Write values to points using the Command API with automatic type conversion. The API automatically converts values between different data types as needed for the target protocol. ```APIDOC ## POST /api/v2/command/write ### Description Write values to points using the Command API with automatic type conversion ### Method POST ### Endpoint /api/v2/command/write ### Parameters #### Request Body - **writes** (array) - Required - Array of write operations containing point UUIDs and values to write ### Request Example { "writes": [ { "point": { "uuid": "point-uuid-here", "layer": "hpl:bacnet:1" }, "value": { "real": 50 } } ] } ### Response #### Success Response (200) - **results** (array) - Array of write results - **errors** (array) - Array of any errors encountered during the write operations #### Response Example { "results": [ { "point": {"uuid": "...", "layer": "hpl:bacnet:1"}, "status": "SUCCESS" } ], "errors": [] } ``` -------------------------------- ### BACnet Device Discovery with Python Source: https://context7.com/normalframework/nf-sdk/llms.txt This snippet shows how to discover BACnet devices on a network using device ID range scanning in Python. It uses the NfClient helper class and involves initiating a scan and then polling for its completion. ```Python import requests import time from helpers import NfClient client = NfClient() # Initiate device scan res = client.post("/api/v1/bacnet/scan", json={"autoImport": False, # set to True to update object database "device": {"autoScan": True, # force new scan even if previously scanned "targets": [{"lowLimit": 260001, # set to 0 for global broadcast scan "highLimit": 260001},],}, }) scan_id = res.json()["id"] # Poll for completion for _ in range(0, 100): time.sleep(0.1) res = client.get("/api/v1/bacnet/scan", params={"idFilter": scan_id, "full": True,}) scan = res.json() if scan["results"][0]["status"] not in ["PENDING", "RUNNING"]: print(scan["results"][0]) break ``` -------------------------------- ### Write Point Values with Command API (Python) Source: https://context7.com/normalframework/nf-sdk/llms.txt Writes values to points using the Command API, which handles automatic type conversion. The script first queries for writable points and then sends write requests. It uses the 'sys' module to accept a value from command-line arguments. Dependencies include the 'helpers' module for NfClient. ```python from helpers import NfClient import sys client = NfClient() # Query for writable points res = client.post("/api/v1/point/query", json={ "structured_query": { "and": [ { "field": { "property": "device_id", "text": "260001" } }, { "field": { "property": "type", "text": "OBJECT_ANALOG_VALUE", }, }, ], }, "layer": "hpl:bacnet:1", "page_size": "15", }) uuids = [p["uuid"] for p in res.json()["points"]] # Write to all points (Command API performs automatic type conversion) res = client.post("/api/v2/command/write", json={ "writes": [{ "point": { "uuid": u, "layer": "hpl:bacnet:1", }, "value": { # Can use real, double, or unsigned - API converts automatically "real": 50 if len(sys.argv) < 2 else float(sys.argv[1]), }, } for u in uuids], }) # Response structure mirrors read response print(res.json()) ``` -------------------------------- ### BACnet Write Property with Python Source: https://context7.com/normalframework/nf-sdk/llms.txt This snippet demonstrates writing values to BACnet objects using the protocol-specific API in Python. It utilizes the NfClient helper class and sends a POST request to write a present value to an analog output object. ```Python from helpers import NfClient client = NfClient() res = client.post("/api/v2/bacnet/confirmed-service", json={ "device_address": {"device_id": 260001,}, "request": {"write_property": {"object_identifier": {"object_type": "OBJECT_TYPE_ANALOG_OUTPUT", "instance": 1,}, "property_identifier": "PROPERTY_IDENTIFIER_PRESENT_VALUE", "property_array_index": 4294967295, "property_value": {"@type": "type.googleapis.com/normalgw.bacnet.v2.ApplicationDataValue", "real": 1}, "priority": 16,}, }, }) if res.status_code == 200: print("Write successful:", res.json()) else: print("Error:", res.headers.get("x-nf-unauthorized-reason")) ``` -------------------------------- ### Disable ext4 filesystem checks Source: https://github.com/normalframework/nf-sdk/blob/master/UBUNTU.md Prevents Ubuntu from blocking at boot with console input for filesystem checks on embedded systems. The tune2fs command disables time-based and mount count checks for the specified ext4 filesystem to ensure uninterrupted operation. ```shell sudo tune2fs -i 0 -c 0 /dev/sdX ``` -------------------------------- ### Read Multiple Points with Command API (Python) Source: https://context7.com/normalframework/nf-sdk/llms.txt Reads multiple points by their UUIDs using the Command API, regardless of the underlying protocol. It first queries for point UUIDs and then reads their values. Dependencies include the 'helpers' module for NfClient. ```python from helpers import NfClient client = NfClient() # First, query points to get UUIDs res = client.post("/api/v1/point/query", json={ "structured_query": { "and": [ { "field": { "property": "device_id", "text": "260001", } }, { "field": { "property": "type", "text": "OBJECT_ANALOG_VALUE", }, }, ], }, "layer": "hpl:bacnet:1", "page_size": "25", }) uuids = [p["uuid"] for p in res.json()["points"]] # Read values from all points res = client.post("/api/v2/command/read", json={ "reads": [{ "point": { "uuid": u, "layer": "hpl:bacnet:1", }, # Optional: read different BACnet property # "bacnet_options": { # "property_identifier": "PROPERTY_IDENTIFIER_UNITS", # } } for u in uuids], }) # Response structure: # { # "results": [{ # "point": {"uuid": "...", "layer": "hpl:bacnet:1"}, # "value": { # "@type": "type.googleapis.com/normalgw.bacnet.v2.ApplicationDataValue", # "real": 50 # }, # "scalar": "50" # }], # "errors": [] # } print(res.json()) ``` -------------------------------- ### POST /api/v1/point/query Source: https://context7.com/normalframework/nf-sdk/llms.txt Search the point database using structured queries with field filters. This endpoint allows complex queries to find points based on various properties and supports different response formats. ```APIDOC ## POST /api/v1/point/query ### Description Search the point database using structured queries with field filters ### Method POST ### Endpoint /api/v1/point/query ### Parameters #### Request Body - **layer** (string) - Required - The layer to query points from - **structuredQuery** (object) - Required - Query structure with field filters - **responseFormat** (string) - Optional - Format for response data (e.g., LAYERS_SPLIT) - **pageSize** (string) - Optional - Number of results per page ### Request Example { "layer": "default", "structuredQuery": { "and": [ { "field": { "property": "device_id", "text": "260001" } }, { "field": { "property": "type", "text": "OBJECT_ANALOG_VALUE" } } ] }, "responseFormat": "LAYERS_SPLIT", "pageSize": "100" } ### Response #### Success Response (200) - **points** (array) - Array of point objects matching the query #### Response Example { "points": [ { "uuid": "point-uuid-here", "name": "Point Name", "device_id": "260001", "type": "OBJECT_ANALOG_VALUE" } ] } ``` -------------------------------- ### Decode Offline WAL Segments in NF (Bash) Source: https://context7.com/normalframework/nf-sdk/llms.txt Decodes Write-Ahead Logging (WAL) segments from offline NF instances to extract buffered data in NDJSON or CSV formats. Depends on Python 3, virtualenv, pip3, and requirements.txt for dependencies like segment decoding libraries; input is segment file path and format flag. Outputs structured data lines with UUID, timestamp, and value; automates replay on connectivity, but requires file access to /var/nf/data and handles buffered data only when upstream connections are lost. ```bash #!/bin/bash # Install dependencies virtualenv venv source venv/bin/activate pip3 install -r requirements.txt # Decode segment to NDJSON (newline-delimited JSON) python3 decode.py --file /var/nf/data/segment-001.sz --ndjson # Output format: # {"uuid":"point-uuid","ts":"2025-11-10T12:00:00Z","value":72.5} # {"uuid":"point-uuid","ts":"2025-11-10T12:15:00Z","value":73.2} # Decode to CSV python3 decode.py --file /var/nf/data/segment-001.sz --csv # Segments are automatically created when NF loses connectivity # to upstream systems or when publish buffers fill up # Once connectivity restores, segments replay automatically ``` === COMPLETE CONTENT === This response contains all available snippets from this library. No additional content exists. Do not make further requests.