From 65d3627619a784df017e0db16677c0e569bbfad3 Mon Sep 17 00:00:00 2001 From: Alex Bucknall Date: Sun, 17 Aug 2025 17:04:54 +0100 Subject: [PATCH 1/7] feat: adds generate_apis.py script to build fluent API from notecard schema. Backwards compatibility supported using aliases.json/ --- generate_apis.py | 479 ++++++++++++++++++++++++++++++++++++++++++ notecard/aliases.json | 32 +++ 2 files changed, 511 insertions(+) create mode 100644 generate_apis.py create mode 100644 notecard/aliases.json diff --git a/generate_apis.py b/generate_apis.py new file mode 100644 index 0000000..21aecaa --- /dev/null +++ b/generate_apis.py @@ -0,0 +1,479 @@ +#!/usr/bin/env python3 +"""Generate Notecard API functions from JSON schema.""" + +import json +import requests +import re +from pathlib import Path +from typing import Dict, List, Optional, Any +import argparse + + +class NotecardAPIGenerator: + """Generate Python API functions from Notecard JSON schema.""" + + def __init__(self, schema_url: str = "https://raw.githubusercontent.com/blues/notecard-schema/refs/heads/master/notecard.api.json", notecard_dir: str = "notecard"): + self.schema_url = schema_url + self.base_url = "/".join(schema_url.split("/")[:-1]) + "/" + self.apis = {} + self.notecard_dir = Path(notecard_dir) + self.meta_configs = self.load_meta_configs() + + def fetch_schema(self, url: str) -> Dict[str, Any]: + """Fetch JSON schema from URL.""" + try: + response = requests.get(url) + response.raise_for_status() + return response.json() + except Exception as e: + print(f"Error fetching {url}: {e}") + return {} + + def parse_main_schema(self) -> List[str]: + """Parse main schema and extract API references.""" + schema = self.fetch_schema(self.schema_url) + if not schema: + return [] + + refs = [] + if "oneOf" in schema: + for ref_obj in schema["oneOf"]: + if "$ref" in ref_obj: + refs.append(ref_obj["$ref"]) + + return refs + + def parse_api_schema(self, ref_url: str) -> Optional[Dict[str, Any]]: + """Parse individual API schema and extract information.""" + # Handle both relative and absolute URLs + if ref_url.startswith("https://"): + full_url = ref_url + else: + full_url = self.base_url + ref_url + schema = self.fetch_schema(full_url) + if not schema: + return None + + # Extract API name from filename + if "/" in ref_url: + api_name = ref_url.split("/")[-1].replace(".req.notecard.api.json", "") + else: + api_name = ref_url.replace(".req.notecard.api.json", "") + + # Parse properties + properties = schema.get("properties", {}) + required = schema.get("required", []) + title = schema.get("title", "") + description = schema.get("description", "") + + return { + "name": api_name, + "title": title, + "description": description, + "properties": properties, + "required": required, + "schema": schema + } + + def to_camel_case(self, snake_str: str) -> str: + """Convert snake_case or dot.separated string to camelCase.""" + # Split on both underscores and dots + components = snake_str.replace('.', '_').split('_') + # Keep first component lowercase, capitalize the rest + return components[0] + ''.join(word.capitalize() for word in components[1:]) + + def get_python_type_hint(self, prop: Dict[str, Any]) -> str: + """Convert JSON schema type to Python type hint.""" + json_type = prop.get("type", "string") + + # Handle case where type is a list of types + if isinstance(json_type, list): + # Use the first non-null type + for t in json_type: + if t != "null": + json_type = t + break + else: + json_type = "string" + + type_mapping = { + "string": "str", + "integer": "int", + "number": "float", + "boolean": "bool", + "array": "list", + "object": "dict" + } + + return type_mapping.get(json_type, "str") + + def clean_docstring_text(self, text: str) -> str: + """Clean up docstring text by removing formatting and compacting markdown.""" + if not text: + return text + + # Remove newline characters and replace with spaces + text = text.replace('\n', ' ').replace('\r', ' ') + + # Convert markdown links [text](url) -> text + import re + text = re.sub(r'\[([^\]]+)\]\([^\)]+\)', r'\1', text) + + # Collapse multiple spaces into single spaces + text = re.sub(r'\s+', ' ', text) + + # Strip leading/trailing whitespace + return text.strip() + + def load_meta_configs(self) -> Dict[str, Dict[str, Any]]: + """Load alias configuration files for backward compatibility.""" + aliases_file = self.notecard_dir / "aliases.json" + if not aliases_file.exists(): + return {} + + try: + with open(aliases_file, 'r') as f: + return json.load(f) + except Exception as e: + print(f"Warning: Could not load aliases config {aliases_file}: {e}") + return {} + + def get_meta_config(self, api_name: str) -> Dict[str, Any]: + """Get meta configuration for an API, or empty dict if none exists.""" + return self.meta_configs.get(api_name, {}) + + def generate_function_signature(self, api: Dict[str, Any]) -> str: + """Generate Python function signature.""" + api_name = api["name"] + + # Remove module prefix (e.g., "card.", "var.") and convert to camelCase + parts = api_name.split('.', 1) + if len(parts) > 1: + func_name = self.to_camel_case(parts[1]) # Convert remaining parts to camelCase + else: + func_name = self.to_camel_case(api_name) + + params = ["card"] + + # Get meta configuration for this API + meta_config = self.get_meta_config(api_name) + aliases = meta_config.get("aliases", {}) + + # Add parameters based on properties + properties = api["properties"] + required = api["required"] + + # Python reserved keywords that need to be handled specially + reserved_keywords = {"in", "out", "from", "import", "class", "def", "if", "else", "for", "while", "try", "except", "with", "as", "is", "not", "and", "or", "async", "await"} + + # Add parameters for schema properties + for prop_name, _ in properties.items(): + if prop_name in ["req", "cmd"]: # Skip these as they're auto-generated + continue + + # Check if this property has an alias - if so, make it optional + has_alias = False + for alias_config in aliases.values(): + if isinstance(alias_config, str): + if alias_config == prop_name: + has_alias = True + break + else: + if alias_config.get("field") == prop_name: + has_alias = True + break + + # Handle reserved keywords by appending underscore + param_name = prop_name + if param_name in reserved_keywords: + param_name = f"{param_name}_" + + # If the field is required but has an alias, make it optional since user can use alias + if prop_name in required and prop_name not in ["req", "cmd"] and not has_alias: + params.append(f"{param_name}") + else: + params.append(f"{param_name}=None") + + # Add alias parameters from meta config + for alias_name, alias_config in aliases.items(): + # Handle reserved keywords by appending underscore + param_name = alias_name + if param_name in reserved_keywords: + param_name = f"{param_name}_" + + # Always optional since aliases are for backward compatibility + params.append(f"{param_name}=None") + + + return f"def {func_name}({', '.join(params)}):" + + def generate_docstring(self, api: Dict[str, Any]) -> str: + """Generate function docstring.""" + # Clean the description text + clean_description = self.clean_docstring_text(api["description"]) + lines = [f' """{clean_description}'] + lines.append("") + lines.append(" Args:") + lines.append(" card (Notecard): The current Notecard object.") + + # Get meta configuration for this API + api_name = api["name"] + meta_config = self.get_meta_config(api_name) + aliases = meta_config.get("aliases", {}) + + properties = api["properties"] + reserved_keywords = {"in", "out", "from", "import", "class", "def", "if", "else", "for", "while", "try", "except", "with", "as", "is", "not", "and", "or", "async", "await"} + + # Process schema properties + for prop_name, prop_def in properties.items(): + if prop_name in ["req", "cmd"]: + continue + + # Handle reserved keywords by appending underscore for parameter name + param_name = prop_name + if param_name in reserved_keywords: + param_name = f"{param_name}_" + + prop_type = self.get_python_type_hint(prop_def) + prop_desc = self.clean_docstring_text(prop_def.get("description", f"The {prop_name} parameter.")) + lines.append(f" {param_name} ({prop_type}): {prop_desc}") + + # Add alias parameters from meta config + for alias_name, alias_config in aliases.items(): + # Handle reserved keywords by appending underscore + param_name = alias_name + if param_name in reserved_keywords: + param_name = f"{param_name}_" + + # Handle both simple and complex aliases + if isinstance(alias_config, str): + schema_field = alias_config + # Find the corresponding schema property for type information + schema_prop_def = properties.get(schema_field, {}) + prop_type = self.get_python_type_hint(schema_prop_def) if schema_prop_def else "str" + original_desc = schema_prop_def.get('description', '') + clean_desc = self.clean_docstring_text(original_desc) if original_desc else '' + prop_desc = f"Alias for {schema_field}. {clean_desc}" + else: + # Complex alias with potential value transformation + schema_field = alias_config.get("field") + schema_prop_def = properties.get(schema_field, {}) + prop_type = self.get_python_type_hint(schema_prop_def) if schema_prop_def else "str" + original_desc = schema_prop_def.get('description', '') + clean_desc = self.clean_docstring_text(original_desc) if original_desc else '' + prop_desc = f"Alias for {schema_field}. {clean_desc}" + + lines.append(f" {param_name} ({prop_type}): {prop_desc}") + + + lines.append("") + lines.append(" Returns:") + lines.append(" dict: The result of the Notecard request.") + lines.append(' """') + + return "\n".join(lines) + + def generate_function_body(self, api: Dict[str, Any]) -> str: + """Generate function body.""" + api_name = api["name"] + lines = [f' req = {{"req": "{api_name}"}}'] + + # Get meta configuration for this API + meta_config = self.get_meta_config(api_name) + aliases = meta_config.get("aliases", {}) + + properties = api["properties"] + reserved_keywords = {"in", "out", "from", "import", "class", "def", "if", "else", "for", "while", "try", "except", "with", "as", "is", "not", "and", "or", "async", "await"} + + # Process schema properties + for prop_name, prop_def in properties.items(): + if prop_name in ["req", "cmd"]: + continue + + # Handle reserved keywords by appending underscore for parameter name + param_name = prop_name + if param_name in reserved_keywords: + param_name = f"{param_name}_" + + json_type = prop_def.get("type", "string") + + # Handle case where type is a list of types + if isinstance(json_type, list): + # Use the first non-null type + for t in json_type: + if t != "null": + json_type = t + break + else: + json_type = "string" + + # Use 'is not None' for types that can have falsy but valid values (0, False, "", etc.) + if json_type in ["boolean", "integer", "number"]: + lines.append(f" if {param_name} is not None:") + lines.append(f' req["{prop_name}"] = {param_name}') + else: + lines.append(f" if {param_name}:") + lines.append(f' req["{prop_name}"] = {param_name}') + + # Handle alias parameters from meta config + for alias_name, alias_config in aliases.items(): + # Handle reserved keywords by appending underscore + param_name = alias_name + if param_name in reserved_keywords: + param_name = f"{param_name}_" + + # Check if this is a simple alias (string) or complex alias (dict) + if isinstance(alias_config, str): + # Simple alias: alias_name -> schema_field + schema_field = alias_config + # Get type info from the corresponding schema field + schema_prop_def = properties.get(schema_field, {}) + json_type = schema_prop_def.get("type", "string") + + # Handle case where type is a list of types + if isinstance(json_type, list): + # Use the first non-null type + for t in json_type: + if t != "null": + json_type = t + break + else: + json_type = "string" + + # Use 'is not None' for types that can have falsy but valid values (0, False, "", etc.) + lines.append(f" # Supported Alias") + if json_type in ["boolean", "integer", "number"]: + lines.append(f" if {param_name} is not None:") + lines.append(f' req["{schema_field}"] = {param_name}') + else: + lines.append(f" if {param_name}:") + lines.append(f' req["{schema_field}"] = {param_name}') + else: + # Complex alias with value transformation + schema_field = alias_config.get("field") + value_transform = alias_config.get("value_transform", {}) + + lines.append(f" # Supported Alias") + lines.append(f" if {param_name}:") + if value_transform and "true" in value_transform: + # Handle boolean transformation (e.g., compact=True -> format="compact") + lines.append(f' req["{schema_field}"] = "{value_transform["true"]}"') + else: + lines.append(f' req["{schema_field}"] = {param_name}') + + + lines.append(" return card.Transaction(req)") + + return "\n".join(lines) + + def generate_api_function(self, api: Dict[str, Any]) -> str: + """Generate complete API function.""" + lines = [] + lines.append("") + lines.append("@validate_card_object") + lines.append(self.generate_function_signature(api)) + lines.append(self.generate_docstring(api)) + lines.append(self.generate_function_body(api)) + + return "\n".join(lines) + + def group_apis_by_module(self, apis: List[Dict[str, Any]]) -> Dict[str, List[Dict[str, Any]]]: + """Group APIs by their module (first part of the name).""" + modules = {} + + for api in apis: + module_name = api["name"].split(".")[0] + if module_name not in modules: + modules[module_name] = [] + modules[module_name].append(api) + + return modules + + def generate_module_file(self, module_name: str, apis: List[Dict[str, Any]]) -> str: + """Generate complete module file content.""" + lines = [f'"""{module_name} Fluent API Helper."""'] + lines.append("") + lines.append("##") + lines.append(f"# @file {module_name}.py") + lines.append("#") + lines.append(f"# @brief {module_name} Fluent API Helper.") + lines.append("#") + lines.append("# @section description Description") + lines.append(f"# This module contains helper methods for calling {module_name}.* Notecard API commands.") + lines.append("# This module is optional and not required for use with the Notecard.") + lines.append("") + lines.append("from notecard.validators import validate_card_object") + lines.append("") + + for api in apis: + lines.append(self.generate_api_function(api)) + + return "\n".join(lines) + + def generate_all_apis(self, output_dir: str = "notecard"): + """Generate all API modules.""" + print("Fetching main schema...") + refs = self.parse_main_schema() + + if not refs: + print("No API references found in main schema") + return + + print(f"Found {len(refs)} API references") + + # Parse all API schemas + apis = [] + for ref in refs: + print(f"Processing {ref}...") + api = self.parse_api_schema(ref) + if api: + apis.append(api) + + print(f"Successfully parsed {len(apis)} APIs") + + # Group by module + modules = self.group_apis_by_module(apis) + + # Create output directory + output_path = Path(output_dir) + output_path.mkdir(exist_ok=True) + + # Generate each module file + for module_name, module_apis in modules.items(): + print(f"Generating {module_name}.py with {len(module_apis)} APIs...") + + file_content = self.generate_module_file(module_name, module_apis) + + # Write to file + file_path = output_path / f"{module_name}.py" + with open(file_path, "w") as f: + f.write(file_content) + + print(f"Generated {file_path}") + + print(f"\nGeneration complete! Generated {len(modules)} modules with {len(apis)} total APIs.") + print(f"Files created in: {output_path.absolute()}") + + +def main(): + """Main entry point.""" + parser = argparse.ArgumentParser(description="Generate Notecard API functions from JSON schema") + parser.add_argument( + "--output-dir", + default="generated_apis", + help="Output directory for generated API files (default: generated_apis)" + ) + parser.add_argument( + "--schema-url", + default="https://raw.githubusercontent.com/blues/notecard-schema/refs/heads/master/notecard.api.json", + help="URL to the main Notecard API schema" + ) + + args = parser.parse_args() + + generator = NotecardAPIGenerator(args.schema_url) + generator.generate_all_apis(args.output_dir) + + +if __name__ == "__main__": + main() diff --git a/notecard/aliases.json b/notecard/aliases.json new file mode 100644 index 0000000..77b9156 --- /dev/null +++ b/notecard/aliases.json @@ -0,0 +1,32 @@ +{ + "note.changes": { + "aliases": { + "maximum": "max" + } + }, + "note.delete": { + "aliases": { + "note_id": "note" + } + }, + "note.get": { + "aliases": { + "note_id": "note" + } + }, + "note.template": { + "aliases": { + "compact": { + "field": "format", + "value_transform": { + "true": "compact" + } + } + } + }, + "note.update": { + "aliases": { + "note_id": "note" + } + } +} From 80e740b689c94d1f0051874f2d7d474b055fea57 Mon Sep 17 00:00:00 2001 From: Alex Bucknall Date: Tue, 19 Aug 2025 22:39:24 +0100 Subject: [PATCH 2/7] feat: generate APIs using notecard-schema --- notecard/aliases.json | 32 - notecard/card.py | 1204 ++++++++---------- notecard/dfu.py | 70 + notecard/env.py | 57 +- notecard/file.py | 67 +- notecard/hub.py | 184 ++- notecard/note.py | 195 +-- notecard/ntn.py | 60 + notecard/var.py | 84 ++ notecard/web.py | 235 ++++ pytest.ini | 2 +- generate_apis.py => scripts/generate_apis.py | 258 ++-- test/fluent_api/test_card.py | 113 ++ test/fluent_api/test_dfu.py | 65 + test/fluent_api/test_env.py | 15 + test/fluent_api/test_file.py | 12 +- test/fluent_api/test_hub.py | 62 +- test/fluent_api/test_note.py | 88 +- test/fluent_api/test_ntn.py | 40 + test/fluent_api/test_var.py | 55 + test/fluent_api/test_web.py | 114 ++ 21 files changed, 1934 insertions(+), 1078 deletions(-) delete mode 100644 notecard/aliases.json create mode 100644 notecard/dfu.py create mode 100644 notecard/ntn.py create mode 100644 notecard/var.py create mode 100644 notecard/web.py rename generate_apis.py => scripts/generate_apis.py (61%) create mode 100644 test/fluent_api/test_dfu.py create mode 100644 test/fluent_api/test_ntn.py create mode 100644 test/fluent_api/test_var.py create mode 100644 test/fluent_api/test_web.py diff --git a/notecard/aliases.json b/notecard/aliases.json deleted file mode 100644 index 77b9156..0000000 --- a/notecard/aliases.json +++ /dev/null @@ -1,32 +0,0 @@ -{ - "note.changes": { - "aliases": { - "maximum": "max" - } - }, - "note.delete": { - "aliases": { - "note_id": "note" - } - }, - "note.get": { - "aliases": { - "note_id": "note" - } - }, - "note.template": { - "aliases": { - "compact": { - "field": "format", - "value_transform": { - "true": "compact" - } - } - } - }, - "note.update": { - "aliases": { - "note_id": "note" - } - } -} diff --git a/notecard/card.py b/notecard/card.py index d6533dd..ccbb3b3 100644 --- a/notecard/card.py +++ b/notecard/card.py @@ -13,380 +13,210 @@ @validate_card_object -def attn(card, mode=None, files=None, seconds=None, payload=None, start=None): - """Configure interrupt detection between a host and Notecard. +def attn(card, files=None, mode=None, off=None, on=None, payload=None, seconds=None, start=None, verify=None): + """Configure hardware notification from the Notecard to MCU host. NOTE: Requires a connection between the Notecard ATTN pin and a GPIO pin on the host MCU. Args: card (Notecard): The current Notecard object. - mode (string): The attn mode to set. - files (array): A collection of notefiles to watch. - seconds (int): A timeout to use when arming attn mode. - payload (int): When using sleep mode, a payload of data from the host - that the Notecard should hold in memory until retrieved by - the host. - start (bool): When using sleep mode and the host has reawakened, - request the Notecard to return the stored payload. + files (list): A list of Notefiles to watch for file-based interrupts. + mode (str): A comma-separated list of one or more of the following keywords. Some keywords are only supported on certain types of Notecards. + off (bool): When `true`, completely disables ATTN processing and sets the pin OFF. This setting is retained across device restarts. + on (bool): When `true`, enables ATTN processing. This setting is retained across device restarts. + payload (str): When using `sleep` mode, a payload of data from the host that the Notecard should hold in memory until retrieved by the host. + seconds (int): To set an ATTN timeout when arming, or when using `sleep`. NOTE: When the Notecard is in `continuous` mode, the `seconds` timeout is serviced by a routine that wakes every 15 seconds. You can predict when the device will wake, by rounding up to the nearest 15 second interval. + start (bool): When using `sleep` mode and the host has reawakened, request the Notecard to return the stored `payload`. + verify (bool): When `true`, returns the current attention mode configuration, if any. Returns: - string: The result of the Notecard request. + dict: The result of the Notecard request. """ req = {"req": "card.attn"} - if mode: - req["mode"] = mode if files: req["files"] = files - if seconds: - req["seconds"] = seconds + if mode: + req["mode"] = mode + if off is not None: + req["off"] = off + if on is not None: + req["on"] = on if payload: req["payload"] = payload - if start: + if seconds is not None: + req["seconds"] = seconds + if start is not None: req["start"] = start + if verify is not None: + req["verify"] = verify return card.Transaction(req) @validate_card_object -def time(card): - """Retrieve the current time and date from the Notecard. - - Args: - card (Notecard): The current Notecard object. - - Returns: - string: The result of the Notecard request. - """ - req = {"req": "card.time"} - return card.Transaction(req) - - -@validate_card_object -def status(card): - """Retrieve the status of the Notecard. - - Args: - card (Notecard): The current Notecard object. - - Returns: - string: The result of the Notecard request. - """ - req = {"req": "card.status"} - return card.Transaction(req) - - -@validate_card_object -def temp(card, minutes=None): - """Retrieve the current temperature from the Notecard. - - Args: - card (Notecard): The current Notecard object. - minutes (int): If specified, creates a templated _temp.qo file that - gathers Notecard temperature value at the specified interval. - - Returns: - string: The result of the Notecard request. - """ - req = {"req": "card.temp"} - if minutes: - req["minutes"] = minutes - return card.Transaction(req) - - -@validate_card_object -def version(card): - """Retrieve firmware version information from the Notecard. +def aux(card, mode=None, usage=None, seconds=None, max=None, start=None, gps=None, rate=None, sync=None, file=None, connected=None, limit=None, sensitivity=None, ms=None, count=None, offset=None): + """Configure various uses of the general-purpose I/O (GPIO) pins `AUX1`-`AUX4` on the Notecard edge connector for tracking applications and simple GPIO sensing and counting tasks. Args: card (Notecard): The current Notecard object. - - Returns: - string: The result of the Notecard request. - """ - req = {"req": "card.version"} - return card.Transaction(req) - - -@validate_card_object -def voltage(card, hours=None, offset=None, vmax=None, vmin=None): - """Retrieve current and historical voltage info from the Notecard. - - Args: - card (Notecard): The current Notecard object. - hours (int): Number of hours to analyze. - offset (int): Number of hours to offset. - vmax (decimal): max voltage level to report. - vmin (decimal): min voltage level to report. - - Returns: - string: The result of the Notecard request. - """ - req = {"req": "card.voltage"} - if hours: - req["hours"] = hours - if offset: - req["offset"] = offset - if vmax: - req["vmax"] = vmax - if vmin: - req["vmin"] = vmin - return card.Transaction(req) - - -@validate_card_object -def wireless(card, mode=None, apn=None): - """Retrieve wireless modem info or customize modem behavior. - - Args: - card (Notecard): The current Notecard object. - mode (string): The wireless module mode to set. Must be one of: - "-" to reset to the default mode - "auto" to perform automatic band scan mode (default) - "m" to restrict the modem to Cat-M1 - "nb" to restrict the modem to Cat-NB1 - "gprs" to restrict the modem to EGPRS - apn (string): Access Point Name (APN) when using an external SIM. - Use "-" to reset to the Notecard default APN. - - Returns: - dict: The result of the Notecard request containing network status and - signal information. - """ - req = {"req": "card.wireless"} - if mode: - req["mode"] = mode - if apn: - req["apn"] = apn - return card.Transaction(req) - - -@validate_card_object -def transport(card, method=None, allow=None): - """Configure the Notecard's connectivity method. - - Args: - card (Notecard): The current Notecard object. - method (string): The connectivity method to enable. Must be one of: - "-" to reset to device default - "wifi-cell" to prioritize WiFi with cellular fallback - "wifi" to enable WiFi only - "cell" to enable cellular only - "ntn" to enable Non-Terrestrial Network mode - "wifi-ntn" to prioritize WiFi with NTN fallback - "cell-ntn" to prioritize cellular with NTN fallback - "wifi-cell-ntn" to prioritize WiFi, then cellular, then NTN - allow (bool): When True, allows adding Notes to non-compact Notefiles - while connected over a non-terrestrial network. - - Returns: - dict: The result of the Notecard request. - """ - req = {"req": "card.transport"} - if method: - req["method"] = method - if allow is not None: - req["allow"] = allow - return card.Transaction(req) - - -@validate_card_object -def power(card, minutes=None, reset=None): - """Configure a connected Mojo device or request power consumption readings in firmware. - - Args: - card (Notecard): The current Notecard object. - minutes (int): The number of minutes to log power consumption. Default is 720 minutes (12 hours). - reset (bool): When True, resets the power consumption counter back to 0. - - Returns: - dict: The result of the Notecard request. The response will contain the following fields: - "voltage": The current voltage. - "milliamp_hours": The cumulative energy consumption in milliamp hours. - "temperature": The Notecard's internal temperature in degrees centigrade, including offset. - """ - req = {"req": "card.power"} - if minutes: - req["minutes"] = minutes - if reset: - req["reset"] = reset - return card.Transaction(req) - - -@validate_card_object -def location(card): - """Retrieve the last known location of the Notecard. - - Args: - card (Notecard): The current Notecard object. - - Returns: - dict: The result of the Notecard request containing location information including: - "status": The current status of the Notecard GPS/GNSS connection - "mode": The GPS/GNSS connection mode (continuous, periodic, or off) - "lat": The latitude in degrees of the last known location - "lon": The longitude in degrees of the last known location - "time": UNIX Epoch time of location capture - "max": If a geofence is enabled by card.location.mode, meters from the geofence center - "count": The number of consecutive recorded GPS/GNSS failures - "dop": The "Dilution of Precision" value from the latest GPS/GNSS reading - """ - req = {"req": "card.location"} - return card.Transaction(req) - - -@validate_card_object -def locationMode(card, mode=None, seconds=None, vseconds=None, lat=None, lon=None, max=None): - """Set location-related configuration settings. - - Args: - card (Notecard): The current Notecard object. - mode (string): The location mode to set. Must be one of: - - "" (empty string) to retrieve the current mode - - "off" to turn location mode off - - "periodic" to sample location at a specified interval - - "continuous" to enable the Notecard's GPS/GNSS module for continuous sampling - - "fixed" to report the location as a fixed location - seconds (int): When in periodic mode, location will be sampled at this interval, if the Notecard detects motion. - vseconds (string): In periodic mode, overrides seconds with a voltage-variable value. - lat (float): Used with fixed mode to specify the latitude coordinate. - lon (float): Used with fixed mode to specify the longitude coordinate. - max (int): Maximum number of seconds to wait for a GPS fix. + mode (str): The AUX mode. Must be one of the following keywords. Some keywords are only supported on certain types of Notecards. + usage (list): An ordered list of pin modes for each AUX pin when in GPIO mode. + seconds (int): When in `gpio` mode, if an `AUX` pin is configured as a `count` type, the count of rising edges can be broken into samples of this duration. Passing `0` or omitting this field will total into a single sample. + max (int): When in `gpio` mode, if an `AUX` pin is configured as a `count` type, the maximum number of samples of duration `seconds`, after which all subsequent counts are added to the final sample. Passing `0` or omitting this value will provide a single incrementing count of rising edges on the pin. + start (bool): When in `gpio` mode, if an `AUX` pin is configured as a `count` type, set to `true` to reset counters and start incrementing. + gps (bool): If `true`, along with `"mode":"track"` the Notecard supports the use of an external GPS module. This argument is deprecated. Use the `card.aux.serial` request with a `mode` of `"gps"` instead. + rate (int): The AUX UART baud rate for debug communication over the AUXRX and AUXTX pins. + sync (bool): If `true`, for pins set as `input` by `usage`, the Notecard will autonomously report any state changes as new notes in `file`. For pins used as `counter`, the Notecard will use an interrupt to count pulses and will report the total in a new note in `file` unless it has been noted in the previous second. + file (str): The name of the Notefile used to report state changes when used in conjunction with `"sync": true`. Default Notefile name is `_button.qo`. + connected (bool): If `true`, defers the sync of the state change Notefile to the next sync as configured by the `hub.set` request. + limit (bool): If `true`, along with `"mode":"track"` and `gps:true` the Notecard will disable concurrent modem use during GPS tracking. + sensitivity (int): When used with `"mode":"neo-monitor"` or `"mode":"track-neo-monitor"`, this controls the brightness of NeoPixel lights, where `100` is the maximum brightness and `1` is the minimum. + ms (int): When in `gpio` mode, this argument configures a debouncing interval. With a debouncing interval in place, the Notecard excludes all transitions with a shorter duration than the provided debounce time, in milliseconds. This interval only applies to GPIOs configured with a `usage` of `count`, `count-pulldown`, or `count-pullup`. + count (int): When used with `"mode":"neo-monitor"` or `"mode":"track-neo-monitor"`, this controls the number of NeoPixels to use in a strip. Possible values are `1`, `2`, or `5`. + offset (int): When used with `"mode":"neo-monitor"` or `"mode":"track-neo-monitor"`, this is the 1-based index in a strip of NeoPixels that determines which single NeoPixel the host can command. Returns: dict: The result of the Notecard request. """ - req = {"req": "card.location.mode"} - if mode is not None: + req = {"req": "card.aux"} + if mode: req["mode"] = mode - if seconds: + if usage: + req["usage"] = usage + if seconds is not None: req["seconds"] = seconds - if vseconds: - req["vseconds"] = vseconds - if lat is not None: - req["lat"] = lat - if lon is not None: - req["lon"] = lon - if max: + if max is not None: req["max"] = max - return card.Transaction(req) - - -@validate_card_object -def locationTrack(card, start=None, heartbeat=None, hours=None, sync=None, stop=None, file=None): - """Store location data in a Notefile at the periodic interval, or using a specified heartbeat. - - Args: - card (Notecard): The current Notecard object. - start (bool): Set to True to start Notefile tracking. - heartbeat (bool): When start is True, set to True to enable tracking even when motion is not detected. - hours (int): If heartbeat is True, add a heartbeat entry at this hourly interval. - Use a negative integer to specify a heartbeat in minutes instead of hours. - sync (bool): Set to True to perform an immediate sync to the Notehub each time a new Note is added. - stop (bool): Set to True to stop Notefile tracking. - file (string): The name of the Notefile to store location data in. Defaults to "track.qo". - - Returns: - dict: The result of the Notecard request. - """ - req = {"req": "card.location.track"} if start is not None: req["start"] = start - if heartbeat is not None: - req["heartbeat"] = heartbeat - if hours: - req["hours"] = hours + if gps is not None: + req["gps"] = gps + if rate is not None: + req["rate"] = rate if sync is not None: req["sync"] = sync - if stop is not None: - req["stop"] = stop if file: req["file"] = file + if connected is not None: + req["connected"] = connected + if limit is not None: + req["limit"] = limit + if sensitivity is not None: + req["sensitivity"] = sensitivity + if ms is not None: + req["ms"] = ms + if count is not None: + req["count"] = count + if offset is not None: + req["offset"] = offset return card.Transaction(req) @validate_card_object -def binary(card, delete=None): - """View the status of the binary storage area of the Notecard and optionally clear data. +def auxSerial(card, mode=None, duration=None, rate=None, limit=None, max=None, ms=None, minutes=None): + """Configure various uses of the AUXTX and AUXRX pins on the Notecard's edge connector. Args: card (Notecard): The current Notecard object. - delete (bool): Set to True to clear the COBS area on the Notecard and reset all related arguments. + mode (str): The AUX mode. Must be one of the following: + duration (int): If using `"mode": "accel"`, specify a sampling duration for the Notecard accelerometer. + rate (int): The baud rate or speed at which information is transmitted over AUX serial. The default is `115200` unless using GPS, in which case the default is `9600`. + limit (bool): If `true`, along with `"mode":"gps"` the Notecard will disable concurrent modem use during GPS tracking. + max (int): The maximum amount of data to send per session, in bytes. This is typically set to the size of the receive buffer on the host minus `1`. For example, `note-arduino` uses a buffer size of `(SERIALRXBUFFER_SIZE - 1)`. + ms (int): The delay in milliseconds before sending a buffer of `max` size. + minutes (int): When using `"mode": "notify,dfu"`, specify an interval for notifying the host. Returns: - dict: The result of the Notecard request containing binary storage information including: - "cobs": The size of COBS-encoded data stored in the reserved area - "connected": Returns True if the Notecard is connected to the network - "err": If present, a string describing the error that occurred during transmission - "length": The length of the binary data - "max": Available storage space - "status": MD5 checksum of unencoded buffer + dict: The result of the Notecard request. """ - req = {"req": "card.binary"} - if delete is not None: - req["delete"] = delete + req = {"req": "card.aux.serial"} + if mode: + req["mode"] = mode + if duration is not None: + req["duration"] = duration + if rate is not None: + req["rate"] = rate + if limit is not None: + req["limit"] = limit + if max is not None: + req["max"] = max + if ms is not None: + req["ms"] = ms + if minutes is not None: + req["minutes"] = minutes return card.Transaction(req) @validate_card_object def binaryGet(card, cobs=None, offset=None, length=None): - """Retrieve binary data stored in the binary storage area of the Notecard. + """Return binary data stored in the binary storage area of the Notecard. The response to this API command first returns the JSON-formatted response object, then the binary data. See the guide on Sending and Receiving Large Binary Objects for best practices when using `card.binary`. Args: card (Notecard): The current Notecard object. cobs (int): The size of the COBS-encoded data you are expecting to be returned (in bytes). - offset (int): Used along with length, the number of bytes to offset the binary payload from 0 when retrieving binary data. - length (int): Used along with offset, the number of bytes to retrieve from the binary storage area. + offset (int): Used along with `length`, the number of bytes to offset the binary payload from 0 when retrieving binary data from the binary storage area of the Notecard. Primarily used when retrieving multiple fragments of a binary payload from the Notecard. + length (int): Used along with `offset`, the number of bytes to retrieve from the binary storage area of the Notecard. Returns: - dict: The result of the Notecard request. The response returns the JSON-formatted response object, then the binary data. - "status": The MD5 checksum of the data returned, after it has been decoded - "err": If present, a string describing the error that occurred during transmission + dict: The result of the Notecard request. """ req = {"req": "card.binary.get"} - if cobs: + if cobs is not None: req["cobs"] = cobs if offset is not None: req["offset"] = offset - if length: + if length is not None: req["length"] = length return card.Transaction(req) @validate_card_object def binaryPut(card, offset=None, cobs=None, status=None): - """Add binary data to the binary storage area of the Notecard. + """Add binary data to the binary storage area of the Notecard. The Notecard expects to receive binary data immediately following the usage of this API command. See the guide on Sending and Receiving Large Binary Objects for best practices when using `card.binary`. Args: card (Notecard): The current Notecard object. - offset (int): The number of bytes to offset the binary payload from 0 when appending the binary data to the binary storage area. + offset (int): The number of bytes to offset the binary payload from 0 when appending the binary data to the binary storage area of the Notecard. Primarily used when sending multiple fragments of one binary payload to the Notecard. cobs (int): The size of the COBS-encoded data (in bytes). - status (string): The MD5 checksum of the data, before it has been encoded. + status (str): The MD5 checksum of the data, before it has been encoded. Returns: - dict: The result of the Notecard request. The Notecard expects to receive binary data immediately following the usage of this API command. - "err": If present, a string describing the error that occurred during transmission + dict: The result of the Notecard request. """ req = {"req": "card.binary.put"} if offset is not None: req["offset"] = offset - if cobs: + if cobs is not None: req["cobs"] = cobs if status: req["status"] = status return card.Transaction(req) +@validate_card_object +def binary(card, delete=None): + """View the status of the binary storage area of the Notecard and optionally clear any data and related `card.binary` variables. See the guide on Sending and Receiving Large Binary Objects for best practices when using `card.binary`. + + Args: + card (Notecard): The current Notecard object. + delete (bool): Clear the COBS area on the Notecard and reset all related arguments previously set by a card.binary request. + + Returns: + dict: The result of the Notecard request. + """ + req = {"req": "card.binary"} + if delete is not None: + req["delete"] = delete + return card.Transaction(req) + + @validate_card_object def carrier(card, mode=None): - """Configure the AUX_CHARGING pin to notify the Notecard about charging support on a Notecarrier. + """Use the `AUX_CHARGING` pin on the Notecard edge connector to notify the Notecard that the pin is connected to a Notecarrier that supports charging, using open-drain. Once set, `{"charging":true}` will appear in a response if the Notecarrier is currently indicating that charging is in progress. Args: card (Notecard): The current Notecard object. - mode (string): The AUX_CHARGING mode. Set to "charging" to tell the Notecard that AUX_CHARGING - is connected to a Notecarrier that supports charging. Set to "-" or "off" to turn off - the AUX_CHARGING detection. + mode (str): The `AUXCHARGING` mode. Set to `"charging"` to tell the Notecard that `AUXCHARGING` is connected to a Notecarrier that supports charging on `AUXCHARGING`. Set to `"-"` or `"off"` to turn off the `AUXCHARGING` detection. Returns: - dict: The result of the Notecard request containing: - "mode": The current AUX_CHARGING mode, or "off" if not set - "charging": Will display True when in AUX_CHARGING "charging" mode + dict: The result of the Notecard request. """ req = {"req": "card.carrier"} if mode: @@ -396,21 +226,17 @@ def carrier(card, mode=None): @validate_card_object def contact(card, name=None, org=None, role=None, email=None): - """Set or retrieve information about the Notecard maintainer. + """Use to set or retrieve information about the Notecard maintainer. Once set, this information is synced to Notehub. Args: card (Notecard): The current Notecard object. - name (string): Set the name of the Notecard maintainer. - org (string): Set the organization name of the Notecard maintainer. - role (string): Set the role of the Notecard maintainer. - email (string): Set the email address of the Notecard maintainer. + name (str): Set the name of the Notecard maintainer. + org (str): Set the organization name of the Notecard maintainer. + role (str): Set the role of the Notecard maintainer. + email (str): Set the email address of the Notecard maintainer. Returns: - dict: The result of the Notecard request containing: - "name": Name of the Notecard maintainer - "org": Organization name of the Notecard maintainer - "role": Role of the Notecard maintainer - "email": Email address of the Notecard maintainer + dict: The result of the Notecard request. """ req = {"req": "card.contact"} if name: @@ -425,244 +251,196 @@ def contact(card, name=None, org=None, role=None, email=None): @validate_card_object -def aux(card, mode=None, usage=None, seconds=None, max=None, start=None, gps=None, - rate=None, sync=None, file=None, connected=None, limit=None, sensitivity=None, - ms=None, count=None, offset=None): - """Configure various uses of the general-purpose I/O (GPIO) pins AUX1-AUX4 for tracking and sensing tasks. +def dfu(card, name=None, on=None, off=None, seconds=None, stop=None, start=None, mode=None): + """Use to configure a Notecard for Notecard Outboard Firmware Update. Args: card (Notecard): The current Notecard object. - mode (string): The AUX mode. Options include: "dfu", "gpio", "led", "monitor", "motion", - "neo", "neo-monitor", "off", "track", "track-monitor", "track-neo-monitor". - usage (array): An ordered list of pin modes for each AUX pin when in GPIO mode. - seconds (int): When in gpio mode, if an AUX pin is configured as a count type, - the count of rising edges can be broken into samples of this duration. - max (int): When in gpio mode, if an AUX pin is configured as a count type, - the maximum number of samples of duration seconds. - start (bool): When in gpio mode, if an AUX pin is configured as a count type, - set to True to reset counters and start incrementing. - gps (bool): Deprecated. If True, along with mode:track the Notecard supports - the use of an external GPS module. - rate (int): The AUX UART baud rate for debug communication over the AUXRX and AUXTX pins. - sync (bool): If True, for pins set as input by usage, the Notecard will autonomously - report any state changes as new notes in file. - file (string): The name of the Notefile used to report state changes when used - in conjunction with sync:True. - connected (bool): If True, defers the sync of the state change Notefile to the next - sync as configured by the hub.set request. - limit (bool): If True, along with mode:track and gps:True the Notecard will disable - concurrent modem use during GPS tracking. - sensitivity (int): When used with mode:neo-monitor or mode:track-neo-monitor, - this controls the brightness of NeoPixel lights. - ms (int): When in gpio mode, this argument configures a debouncing interval. - count (int): When used with mode:neo-monitor or mode:track-neo-monitor, - this controls the number of NeoPixels to use in a strip. - offset (int): When used with mode:neo-monitor or mode:track-neo-monitor, - this is the 1-based index in a strip of NeoPixels. + name (str): One of the supported classes of host MCU. Supported MCU classes are `"esp32"`, `"stm32"`, `"stm32-bi"`, `"mcuboot"` (added in v5.3.1), and `"-"`, which resets the configuration. The "bi" in `"stm32-bi"` stands for "boot inverted", and the `"stm32-bi"` option should be used on STM32 family boards where the hardware boot pin is assumed to be active low, instead of active high. Supported MCUs can be found on the Notecarrier F datasheet. + on (bool): Set to `true` to enable Notecard Outboard Firmware Update. + off (bool): Set to `true` to disable Notecard Outboard Firmware Update from occurring. + seconds (int): When used with `"off":true`, disable Notecard Outboard Firmware Update operations for the specified number of `seconds`. + stop (bool): Set to `true` to disable the host RESET that is normally performed on the host MCU when the Notecard starts up (in order to ensure a clean startup), and also when the Notecard wakes up the host MCU after the expiration of a `card.attn` "sleep" operation. If `true`, the host MCU will not be reset in these two conditions. + start (bool): Set to `true` to enable the host RESET if previously disabled with `"stop":true`. + mode (str): The `mode` argument allows you to control whether a Notecard's `AUX` pins (default) or `ALTDFU` pins are used for Notecard Outboard Firmware Update. This argument is only supported on Notecards that have `ALTDFU` pins, which includes all versions of Notecard Cell+WiFi, non-legacy versions of Notecard Cellular, and Notecard WiFi v2. Returns: - dict: The result of the Notecard request containing: - "mode": The current mode of the AUX interface - "text": Text received over the AUX interface - "binary": Binary data received over the AUX interface - "count": Number of bytes received + dict: The result of the Notecard request. """ - req = {"req": "card.aux"} - if mode: - req["mode"] = mode - if usage: - req["usage"] = usage - if seconds: + req = {"req": "card.dfu"} + if name: + req["name"] = name + if on is not None: + req["on"] = on + if off is not None: + req["off"] = off + if seconds is not None: req["seconds"] = seconds - if max: - req["max"] = max + if stop is not None: + req["stop"] = stop if start is not None: req["start"] = start - if gps is not None: - req["gps"] = gps - if rate: - req["rate"] = rate - if sync is not None: - req["sync"] = sync - if file: - req["file"] = file - if connected is not None: - req["connected"] = connected - if limit is not None: - req["limit"] = limit - if sensitivity: - req["sensitivity"] = sensitivity - if ms: - req["ms"] = ms - if count: - req["count"] = count - if offset: - req["offset"] = offset + if mode: + req["mode"] = mode return card.Transaction(req) @validate_card_object -def auxSerial(card, mode=None, duration=None, rate=None, limit=None, max=None, ms=None, minutes=None): - """Configure various uses of the AUXTX and AUXRX pins on the Notecard's edge connector. +def illumination(card): + """Use request returns an illumination reading (in lux) from an OPT3001 ambient light sensor connected to Notecard's I2C bus. If no OPT3001 sensor is detected, this request returns an “illumination sensor is not available” error. Args: card (Notecard): The current Notecard object. - mode (string): The AUX mode. Must be one of the following: - "req" - Request/response monitoring (default) - "gps" - Use external GPS/GNSS module - "notify" - Stream data or notifications - "notify,accel" - Stream accelerometer readings - "notify,signals" - Notify of Inbound Signals - "notify,env" - Notify of Environment Variable changes - "notify,dfu" - Notify of DFU events - duration (int): For mode "accel", specify sampling duration for accelerometer. - rate (int): Baud rate for transmission (default 115200, 9600 for GPS). - limit (bool): Disable concurrent modem use during GPS tracking. - max (int): Maximum data to send per session in bytes. - ms (int): Delay in milliseconds before sending buffer. - minutes (int): Interval for notifying host when using mode "dfu". Returns: dict: The result of the Notecard request. """ - req = {"req": "card.aux.serial"} + req = {"req": "card.illumination"} + return card.Transaction(req) + + +@validate_card_object +def io(card, i2c=None, mode=None): + """Can be used to override the Notecard's I2C address from its default of `0x17` and change behaviors of the onboard LED and USB port. + + Args: + card (Notecard): The current Notecard object. + i2c (int): The alternate address to use for I2C communication. Pass `-1` to reset to the default address + mode (str): The mode parameter. + + Returns: + dict: The result of the Notecard request. + """ + req = {"req": "card.io"} + if i2c is not None: + req["i2c"] = i2c if mode: req["mode"] = mode - if duration: - req["duration"] = duration - if rate: - req["rate"] = rate - if limit is not None: - req["limit"] = limit - if max: - req["max"] = max - if ms: - req["ms"] = ms - if minutes: - req["minutes"] = minutes return card.Transaction(req) @validate_card_object -def dfu(card, name=None, on=None, off=None, seconds=None, stop=None, start=None, mode=None): - """Configure a Notecard for Notecard Outboard Firmware Update. +def led(card, mode=None, on=None, off=None): + """Use along with the card.aux API to turn connected LEDs on/off or to manage a single connected NeoPixel. Args: card (Notecard): The current Notecard object. - name (string): One of the supported classes of host MCU. Supported MCU classes are - 'esp32', 'stm32', 'stm32-bi', 'mcuboot', '-'. - on (bool): Set to True to enable Notecard Outboard Firmware Update. - off (bool): Set to True to disable Notecard Outboard Firmware Update from occurring. - seconds (int): When used with 'off':True, disable Notecard Outboard Firmware Update - operations for the specified number of seconds. - stop (bool): Set to True to disable the host RESET that is normally performed on the - host MCU when the Notecard starts up. - start (bool): Set to True to enable the host RESET. - mode (string): Optional mode for alternative DFU configuration. + mode (str): Used to specify the color of the LED to turn on or off. For LEDs, possible values are `"red"`, `"green"`, and `"yellow"`. For NeoPixels, possible values are `"red"`, `"green"`, `"blue"`, `"yellow"`, `"cyan"`, `"magenta"`, `"orange"`, `"white"`, and `"gray"`. + on (bool): Set to `true` to turn the specified LED or NeoPixel on. + off (bool): Set to `true` to turn the specified LED or NeoPixel off. Returns: - dict: The result of the Notecard request containing: - "name": Current MCU class configured for DFU + dict: The result of the Notecard request. """ - req = {"req": "card.dfu"} - if name: - req["name"] = name + req = {"req": "card.led"} + if mode: + req["mode"] = mode if on is not None: req["on"] = on if off is not None: req["off"] = off - if seconds: - req["seconds"] = seconds - if stop is not None: - req["stop"] = stop - if start is not None: - req["start"] = start - if mode: - req["mode"] = mode return card.Transaction(req) @validate_card_object -def illumination(card): - """Retrieve an illumination reading from an OPT3001 ambient light sensor connected to Notecard's I2C bus. +def locationMode(card, mode=None, seconds=None, vseconds=None, lat=None, lon=None, max=None, delete=None, minutes=None, threshold=None): + """Set location-related configuration settings. Retrieves the current location mode when passed with no argument. Args: card (Notecard): The current Notecard object. + mode (str): Must be one of: `""` to retrieve the current mode. `"off"` to turn location mode off. Approximate location may still be ascertained from Notehub. `"periodic"` to sample location at a specified interval, if the device has moved. `"continuous"` to enable the Notecard's GPS/GNSS module for continuous sampling. When in continuous mode the Notecard samples a new GPS/GNSS reading for every new Note. `"fixed"` to report the location as a fixed location using the specified `lat` and `lon` coordinates. This is the only supported mode on Notecard LoRa. + seconds (int): When in `periodic` mode, location will be sampled at this interval, if the Notecard detects motion. If seconds is < 300, during periods of sustained movement the Notecard will leave its onboard GPS/GNSS on continuously to avoid powering the module on and off repeatedly. + vseconds (str): In `periodic` mode, overrides `seconds` with a voltage-variable value. + lat (float): When in periodic or continuous mode, providing this value enables geofencing. The value you provide for this argument should be the latitude of the center of the geofence, in degrees. When in fixed mode, the value you provide for this argument should be the latitude location of the device itself, in degrees. + lon (float): When in periodic or continuous mode, providing this value enables geofencing. The value you provide for this argument should be the longitude of the center of the geofence, in degrees. When in fixed mode, the value you provide for this argument should be the longitude location of the device itself, in degrees. + max (int): Meters from a geofence center. Used to enable geofence location tracking. + delete (bool): Set to `true` to delete the last known location stored in the Notecard. + minutes (int): When geofence is enabled, the number of minutes the device should be outside the geofence before the Notecard location is tracked. + threshold (int): When in `periodic` mode, the number of motion events (registered by the built-in accelerometer) required to trigger GPS to turn on. Returns: - dict: The result of the Notecard request containing: - "value": An illumination reading (in lux) from the attached OPT3001 sensor. - - Note: - If no OPT3001 sensor is detected, this request returns an "illumination sensor is not available" error. + dict: The result of the Notecard request. """ - req = {"req": "card.illumination"} + req = {"req": "card.location.mode"} + if mode: + req["mode"] = mode + if seconds is not None: + req["seconds"] = seconds + if vseconds: + req["vseconds"] = vseconds + if lat is not None: + req["lat"] = lat + if lon is not None: + req["lon"] = lon + if max is not None: + req["max"] = max + if delete is not None: + req["delete"] = delete + if minutes is not None: + req["minutes"] = minutes + if threshold is not None: + req["threshold"] = threshold return card.Transaction(req) @validate_card_object -def io(card, i2c=None, mode=None): - """Override the Notecard's I2C address and change behaviors of the onboard LED and USB port. +def location(card): + """Retrieve the last known location of the Notecard and the time at which it was acquired. Use card.location.mode to configure location settings. This request will return the cell tower location or triangulated location of the most recent session if a GPS/GNSS location is not available. On Notecard LoRa this request can only return a location set through the card.location.mode request's `"fixed"` mode. Args: card (Notecard): The current Notecard object. - i2c (int): The alternate address to use for I2C communication. Pass -1 to reset to the default address. - mode (string): Mode to change LED or USB behavior. Options include: - "-usb" - Disable the Notecard's USB port. Re-enable with "usb" or "+usb" - "+busy" - LED will be on when Notecard is awake, off when asleep - "-busy" - Reset "+busy" to default (LED blinks only during flash operations) - "i2c-master-disable" - Disable Notecard acting as an I2C master - "i2c-master-enable" - Re-enable I2C master functionality Returns: dict: The result of the Notecard request. """ - req = {"req": "card.io"} - if i2c is not None: - req["i2c"] = i2c - if mode: - req["mode"] = mode + req = {"req": "card.location"} return card.Transaction(req) @validate_card_object -def led(card, mode=None, on=None, off=None): - """Control connected LEDs or manage a single connected NeoPixel. +def locationTrack(card, start=None, heartbeat=None, hours=None, sync=None, stop=None, file=None, payload=None): + """Store location data in a Notefile at the `periodic` interval, or using a specified `heartbeat`. This request is only available when the `card.location.mode` request has been set to `periodic`—e.g. `{"req":"card.location.mode","mode":"periodic","seconds":300}`. If you want to track and transmit data simultaneously consider using an external GPS/GNSS module with the Notecard. If you connect a BME280 sensor on the I2C bus, Notecard will include a temperature, humidity, and pressure reading with each captured Note. If you connect an ENS210 sensor on the I2C bus, Notecard will include a temperature and pressure reading with each captured Note. Args: card (Notecard): The current Notecard object. - mode (string): Used to specify the color of the LED or NeoPixel to control. - For LEDs: 'red', 'green', 'yellow' - For NeoPixels: 'red', 'green', 'blue', 'yellow', 'cyan', 'magenta', 'orange', 'white', 'gray' - on (bool): Set to True to turn the specified LED or NeoPixel on. - off (bool): Set to True to turn the specified LED or NeoPixel off. + start (bool): Set to `true` to start Notefile tracking. + heartbeat (bool): When `start` is `true`, set to `true` to enable tracking even when motion is not detected. If using `heartbeat`, also set the `hours` below. + hours (int): If `heartbeat` is true, add a heartbeat entry at this hourly interval. Use a negative integer to specify a heartbeat in minutes instead of hours. + sync (bool): Set to `true` to perform an immediate sync to the Notehub each time a new Note is added. + stop (bool): Set to `true` to stop Notefile tracking. + file (str): The Notefile in which to store tracked location data. See the `_track.qo` Notefile's documentation for details on the format of the data captured. + payload (str): A base64-encoded binary payload to be included in the next `_track.qo` Note. See the guide on Sampling at Predefined Intervals for more details. Returns: dict: The result of the Notecard request. - - Note: - Requires the card.aux API to be configured in 'led' or 'neo' mode first. - Not supported by Notecard LoRa for regular LEDs. """ - req = {"req": "card.led"} - if mode: - req["mode"] = mode - if on is not None: - req["on"] = on - if off is not None: - req["off"] = off + req = {"req": "card.location.track"} + if start is not None: + req["start"] = start + if heartbeat is not None: + req["heartbeat"] = heartbeat + if hours is not None: + req["hours"] = hours + if sync is not None: + req["sync"] = sync + if stop is not None: + req["stop"] = stop + if file: + req["file"] = file + if payload: + req["payload"] = payload return card.Transaction(req) @validate_card_object def monitor(card, mode=None, count=None, usb=None): - """Configure AUX pins when in monitor mode. + """When a Notecard is in monitor mode, this API is used to configure the general-purpose `AUX1`-`AUX4` pins to test and monitor Notecard activity. Args: card (Notecard): The current Notecard object. - mode (string): Set LED color. Options: 'green', 'red', 'yellow'. - count (int): Number of pulses to send to AUX pin LED. Set this to 0 to return to default LED behavior. - usb (bool): Set to true to configure LED behavior so that it is only active when the Notecard is connected to USB power. + mode (str): Can be set to one of `green`, `red` or `yellow` to temporarily override the behavior of an AUX pin LED. See Using Monitor Mode for additional details. + count (int): The number of pulses to send to the overridden AUX pin LED. Set this value to `0` to return the LED to its default behavior. + usb (bool): Set to `true` to configure LED behavior so that it is only active when the Notecard is connected to USB power. Returns: dict: The result of the Notecard request. @@ -677,41 +455,17 @@ def monitor(card, mode=None, count=None, usb=None): return card.Transaction(req) -@validate_card_object -def motion(card, minutes=None): - """Retrieve information about the Notecard's accelerometer motion and orientation. - - Args: - card (Notecard): The current Notecard object. - minutes (int): Amount of time to sample for buckets of accelerometer-measured movement. - - Returns: - dict: The result of the Notecard request containing: - "count": Number of accelerometer motion events since the last card.motion request - "alert": Boolean indicating free-fall detection since the last request - "motion": UNIX Epoch time of the last accelerometer motion event - "status": Comma-separated list of orientation events (e.g., "face-up", "portrait-down") - "seconds": Duration of each bucket of sample accelerometer movements (when minutes is provided) - "movements": Base-36 characters representing motion counts in each bucket - "mode": Current motion status of the Notecard (e.g., "stopped" or "moving") - """ - req = {"req": "card.motion"} - if minutes is not None: - req["minutes"] = minutes - return card.Transaction(req) - - @validate_card_object def motionMode(card, start=None, stop=None, seconds=None, sensitivity=None, motion=None): - """Configure accelerometer motion monitoring parameters. + """Configure accelerometer motion monitoring parameters used when providing results to `card.motion`. Args: card (Notecard): The current Notecard object. - start (bool): Set to True to enable the Notecard accelerometer and start motion tracking. - stop (bool): Set to True to disable the Notecard accelerometer and stop motion tracking. - seconds (int): Period for each bucket of movements to be accumulated when minutes is used with card.motion. - sensitivity (int): Sets accelerometer sample rate with different sensitivity levels (default -1). - motion (int): Threshold for motion events to trigger motion status change between "moving" and "stopped". + start (bool): `true` to enable the Notecard accelerometer and start motion tracking. + stop (bool): `true` to disable the Notecard accelerometer and stop motion tracking. + seconds (int): Period for each bucket of movements to be accumulated when `minutes` is used with `card.motion`. + sensitivity (int): Used to set the accelerometer sample rate. The default sample rate of 1.6Hz could miss short-duration accelerations (e.g. bumps and jolts), and free fall detection may not work reliably with short falls. The penalty for increasing the sample rate to 25Hz is increased current consumption by ~1.5uA relative to the default `-1` setting. + motion (int): If `motion` is > 0, a card.motion request will return a `"mode"` of `"moving"` or `"stopped"`. The `motion` value is the threshold for how many motion events in a single bucket will trigger a motion status change. Returns: dict: The result of the Notecard request. @@ -730,18 +484,34 @@ def motionMode(card, start=None, stop=None, seconds=None, sensitivity=None, moti return card.Transaction(req) +@validate_card_object +def motion(card, minutes=None): + """Return information about the Notecard accelerometer's motion and orientation. Motion tracking must be enabled first with `card.motion.mode`. Otherwise, this request will return `{}`. + + Args: + card (Notecard): The current Notecard object. + minutes (int): Amount of time to sample for buckets of accelerometer-measured movement. For instance, `5` will sample motion events for the previous five minutes and return a `movements` string with motion counts in each bucket. + + Returns: + dict: The result of the Notecard request. + """ + req = {"req": "card.motion"} + if minutes is not None: + req["minutes"] = minutes + return card.Transaction(req) + + @validate_card_object def motionSync(card, start=None, stop=None, minutes=None, count=None, threshold=None): """Configure automatic sync triggered by Notecard movement. Args: card (Notecard): The current Notecard object. - start (bool): Set to True to start motion-triggered syncing. - stop (bool): Set to True to stop motion-triggered syncing. - minutes (int): Maximum frequency at which sync will be triggered. - count (int): Number of most recent motion buckets to examine. - threshold (int): Number of buckets that must indicate motion to trigger a sync. - If set to 0, sync occurs only on orientation changes. + start (bool): `true` to start motion-triggered syncing. + stop (bool): `true` to stop motion-triggered syncing. + minutes (int): The maximum frequency at which sync will be triggered. Even if a `threshold` is set and exceeded, there will only be a single sync for this amount of time. + count (int): The number of most recent motion buckets to examine. + threshold (int): The number of buckets that must indicate motion in order to trigger a sync. If set to `0`, the Notecard will only perform a sync when its orientation changes. Returns: dict: The result of the Notecard request. @@ -762,17 +532,17 @@ def motionSync(card, start=None, stop=None, minutes=None, count=None, threshold= @validate_card_object def motionTrack(card, start=None, stop=None, minutes=None, count=None, threshold=None, file=None, now=None): - """Configure automatic capture of accelerometer motion in a Notefile. + """Configure automatic capture of Notecard accelerometer motion in a Notefile. Args: card (Notecard): The current Notecard object. - start (bool): Set to True to start motion capture. - stop (bool): Set to True to stop motion capture. - minutes (int): Maximum period to capture Notes in the Notefile. - count (int): Number of most recent motion buckets to examine. - threshold (int): Number of buckets that must indicate motion to capture. - file (string): Notefile to use for motion capture Notes (default '_motion.qo'). - now (bool): Set to True to trigger immediate _motion.qo event on orientation change. + start (bool): `true` to start motion capture. + stop (bool): `true` to stop motion capture. + minutes (int): The maximum period to capture Notes in the Notefile. + count (int): The number of most recent motion buckets to examine. + threshold (int): The number of buckets that must indicate motion in order to capture. + file (str): The Notefile to use for motion capture Notes. See the `_motion.qo` Notefile's documentation for details on the format of the data captured. + now (bool): Set to `true` to trigger the immediate creation of a `_motion.qo` event if the orientation of the Notecard changes (overriding the `minutes` setting). Returns: dict: The result of the Notecard request. @@ -788,7 +558,7 @@ def motionTrack(card, start=None, stop=None, minutes=None, count=None, threshold req["count"] = count if threshold is not None: req["threshold"] = threshold - if file is not None: + if file: req["file"] = file if now is not None: req["now"] = now @@ -796,70 +566,72 @@ def motionTrack(card, start=None, stop=None, minutes=None, count=None, threshold @validate_card_object -def restart(card): - """Perform a firmware restart of the Notecard. +def random(card, mode=None, count=None): + """Obtain a single random 32 bit unsigned integer modulo `count` or `count` bytes of random data from the Notecard hardware random number generator. Args: card (Notecard): The current Notecard object. + mode (str): Accepts a single value `"payload"` and, if specified, uses the `count` value to determine the number of bytes of random data to generate and return to the host. + count (int): If the `mode` argument is excluded from the request, the Notecard uses this as an upper-limit parameter and returns a random unsigned 32 bit integer between zero and the value provided. If `"mode":"payload"` is used, this argument sets the number of random bytes of data to return in a base64-encoded buffer from the Notecard. Returns: dict: The result of the Notecard request. + """ + req = {"req": "card.random"} + if mode: + req["mode"] = mode + if count is not None: + req["count"] = count + return card.Transaction(req) + - Warning: - Not recommended for production applications due to potential increased - cellular data and consumption credit usage. +@validate_card_object +def power(card, minutes=None, reset=None): + """Use `card.power` API is used to configure a connected Mojo device or to manually request power consumption readings in firmware. + + Args: + card (Notecard): The current Notecard object. + minutes (int): How often, in minutes, Notecard should log power consumption in a `_log.qo` Note. The default value is `720` (12 hours). + reset (bool): Set to `true` to reset the power consumption counters back to 0. + + Returns: + dict: The result of the Notecard request. """ - req = {"req": "card.restart"} + req = {"req": "card.power"} + if minutes is not None: + req["minutes"] = minutes + if reset is not None: + req["reset"] = reset return card.Transaction(req) @validate_card_object -def restore(card, delete=None, connected=None): - """Reset Notecard configuration settings and/or deprovision from Notehub. +def restart(card): + """Perform a firmware restart of the Notecard. Args: card (Notecard): The current Notecard object. - delete (bool): Set to True to reset most Notecard configuration settings. - Does not reset Wi-Fi credentials or alternate I2C address. - Notecard will be unable to sync with Notehub until ProductUID is set again. - On Notecard LoRa, this parameter is required, though LoRaWAN configuration is retained. - connected (bool): Set to True to reset the Notecard on Notehub. - Will delete and deprovision the Notecard from Notehub on next connection. - Removes any Notefile templates used by the device. Returns: dict: The result of the Notecard request. """ - req = {"req": "card.restore"} - if delete is not None: - req["delete"] = delete - if connected is not None: - req["connected"] = connected + req = {"req": "card.restart"} return card.Transaction(req) @validate_card_object def sleep(card, on=None, off=None, seconds=None, mode=None): - """Configure sleep mode for Notecard WiFi v2. + """Only valid when used with Notecard WiFi v2. Allows the ESP32-based Notecard WiFi v2 to fall back to a low current draw when idle (this behavior differs from the STM32-based Notecards that have a `STOP` mode where UART and I2C may still operate). Note that the Notecard WiFi v2 will not enable a "sleep" mode while plugged in via USB. Read more in the guide on using Deep Sleep Mode on Notecard WiFi v2. Args: card (Notecard): The current Notecard object. - on (bool): Set to True to enable sleep mode after 30 seconds of idleness. - off (bool): Set to True to disable sleep mode. - seconds (int): Number of seconds before entering sleep mode (minimum 30). - mode (string): Accelerometer wake configuration. - Use "accel" to wake from deep sleep on accelerometer movement, - or "-accel" to reset to default setting. + on (bool): Set to `true` to enable the Notecard WiFi v2 to sleep once it is idle for >= 30 seconds. + off (bool): Set to `true` to disable the sleep mode on the Notecard WiFi v2. + seconds (int): The number of seconds the Notecard will wait before entering sleep mode (minimum value is 30). + mode (str): Set to `"accel"` to wake from deep sleep on any movement detected by the onboard accelerometer. Set to `"-accel"` to reset to the default setting. Returns: - dict: The result of the Notecard request containing: - "on": Boolean indicating if sleep mode is enabled - "off": Boolean indicating if sleep mode is disabled - "seconds": Configured sleep delay - "mode": Accelerometer wake configuration - - Note: - Only valid for Notecard WiFi v2. + dict: The result of the Notecard request. """ req = {"req": "card.sleep"} if on is not None: @@ -873,19 +645,90 @@ def sleep(card, on=None, off=None, seconds=None, mode=None): return card.Transaction(req) +@validate_card_object +def restore(card, delete=None, connected=None): + """Perform a factory reset on the Notecard and restarts. Sending this request without either of the optional arguments below will only reset the Notecard's file system, thus forcing a re-sync of all Notefiles from Notehub. On Notecard LoRa there is no option to retain configuration settings, and providing `"delete": true` is required. The Notecard LoRa retains LoRaWAN configuration after factory resets. + + Args: + card (Notecard): The current Notecard object. + delete (bool): Set to `true` to reset most Notecard configuration settings. Note that this does not reset stored Wi-Fi credentials or the alternate I2C address (if previously set) so the Notecard can still contact the network after a reset. The Notecard will be unable to sync with Notehub until the `ProductUID` is set again. + connected (bool): Set to `true` to reset the Notecard on Notehub. This will delete and deprovision the Notecard from Notehub the next time the Notecard connects. This also removes any Notefile templates used by this device. Conversely, if `connected` is `false` (or omitted), the Notecard's settings and data will be restored from Notehub the next time the Notecard connects to the previously used Notehub project. + + Returns: + dict: The result of the Notecard request. + """ + req = {"req": "card.restore"} + if delete is not None: + req["delete"] = delete + if connected is not None: + req["connected"] = connected + return card.Transaction(req) + + +@validate_card_object +def status(card): + """Return general information about the Notecard's operating status. + + Args: + card (Notecard): The current Notecard object. + + Returns: + dict: The result of the Notecard request. + """ + req = {"req": "card.status"} + return card.Transaction(req) + + +@validate_card_object +def temp(card, minutes=None, status=None, stop=None, sync=None): + """Get the current temperature from the Notecard's onboard calibrated temperature sensor. When using a Notecard Cellular or Notecard Cell+WiFi, if you connect a BME280 sensor on the I2C bus the Notecard will add `temperature`, `pressure`, and `humidity` fields to the response. If you connect an ENS210 sensor on the I2C bus the Notecard will add `temperature` and `pressure` fields to the response. + + Args: + card (Notecard): The current Notecard object. + minutes (int): If specified, creates a templated `temp.qo` file that gathers Notecard temperature value at the specified minutes interval. When using card.aux track mode, the sensor temperature, pressure, and humidity is also included with each Note._ + status (str): Overrides `minutes` with a voltage-variable value. For example: `"usb:15;high:30;normal:60;720"`. See Voltage-Variable Sync Behavior for more information on configuring these values. + stop (bool): If set to `true`, the Notecard will stop logging the temperature value at the interval specified with the `minutes` parameter (see above). + sync (bool): If set to `true`, the Notecard will immediately sync any pending `_temp.qo` Notes created with the `minutes` parameter (see above). + + Returns: + dict: The result of the Notecard request. + """ + req = {"req": "card.temp"} + if minutes is not None: + req["minutes"] = minutes + if status: + req["status"] = status + if stop is not None: + req["stop"] = stop + if sync is not None: + req["sync"] = sync + return card.Transaction(req) + + +@validate_card_object +def time(card): + """Retrieve current date and time information in UTC. Upon power-up, the Notecard must complete a sync to Notehub in order to obtain time and location data. Before the time is obtained, this request will return `{"zone":"UTC,Unknown"}`. + + Args: + card (Notecard): The current Notecard object. + + Returns: + dict: The result of the Notecard request. + """ + req = {"req": "card.time"} + return card.Transaction(req) + + @validate_card_object def trace(card, mode=None): """Enable and disable trace mode on a Notecard for debugging. Args: card (Notecard): The current Notecard object. - mode (string): Set to "on" to enable trace mode on a Notecard, or "off" to disable it. + mode (str): Set to `"on"` to enable trace mode on a Notecard, or `"off"` to disable it. Returns: dict: The result of the Notecard request. - - Note: - See: https://dev.blues.io/guides-and-tutorials/notecard-guides/using-notecard-trace-mode """ req = {"req": "card.trace"} if mode: @@ -893,38 +736,48 @@ def trace(card, mode=None): return card.Transaction(req) +@validate_card_object +def transport(card, method=None, seconds=None, allow=None, umin=None): + """Specify the connectivity protocol to prioritize on the Notecard Cell+WiFi, or when using NTN mode with Starnote and a compatible Notecard. + + Args: + card (Notecard): The current Notecard object. + method (str): The connectivity method to enable on the Notecard. + seconds (int): The amount of time a Notecard will spend on any fallback transport before retrying the first transport specified in the `method`. The default is `3600` or 60 minutes. + allow (bool): Set to `true` to allow adding Notes to non-compact Notefiles while connected over a non-terrestrial network. See Define NTN vs non-NTN Templates. + umin (bool): Set to `true` to force a longer network transport timeout when using Wideband Notecards. + + Returns: + dict: The result of the Notecard request. + """ + req = {"req": "card.transport"} + if method: + req["method"] = method + if seconds is not None: + req["seconds"] = seconds + if allow is not None: + req["allow"] = allow + if umin is not None: + req["umin"] = umin + return card.Transaction(req) + + @validate_card_object def triangulate(card, mode=None, on=None, usb=None, set=None, minutes=None, text=None, time=None): - """Enable or disable triangulation behavior for gathering cell tower and Wi-Fi access point information. + """Enable or disables a behavior by which the Notecard gathers information about surrounding cell towers and/or Wi-Fi access points with each new Notehub session. Args: card (Notecard): The current Notecard object. - mode (string): The triangulation approach to use. Keywords can be used separately or together - in a comma-delimited list: "cell", "wifi", or "-" to clear the mode. - on (bool): Set to True to triangulate even if the module has not moved. - Only takes effect when set is True. Default: False. - usb (bool): Set to True to perform triangulation only when connected to USB power. - Only takes effect when set is True. Default: False. - set (bool): Set to True to instruct the module to use the state of the on and usb arguments. - Default: False. - minutes (int): Minimum delay, in minutes, between triangulation attempts. - Use 0 for no time-based suppression. Default: 0. - text (string): When using Wi-Fi triangulation, a newline-terminated list of Wi-Fi access points. - Format should follow ESP32's AT+CWLAP command output. - time (int): UNIX Epoch time when the Wi-Fi access point scan was performed. - If not provided, Notecard time is used. + mode (str): The triangulation approach to use for determining the Notecard location. The following keywords can be used separately or together in a comma-delimited list, in any order. `cell` enables cell tower scanning to determine the position of the Device. `wifi` enables the use of nearby Wi-Fi access points to determine the position of the Device. To leverage this feature, the host will need to provide access point information to the Notecard via the `text` argument in subsequent requests. `-` to clear the currently-set triangulation mode. + on (bool): `true` to instruct the Notecard to triangulate even if the module has not moved. Only takes effect when `set` is `true`. + usb (bool): `true` to use perform triangulation only when the Notecard is connected to USB power. Only takes effect when `set` is `true`. + set (bool): `true` to instruct the module to use the state of the `on` and `usb` arguments. + minutes (int): Minimum delay, in minutes, between triangulation attempts. Use `0` for no time-based suppression. + text (str): When using Wi-Fi triangulation, a newline-terminated list of Wi-Fi access points obtained by the external module. Format should follow the ESP32's AT+CWLAP command output. + time (int): When passed with `text`, records the time that the Wi-Fi access point scan was performed. If not provided, Notecard time is used. Returns: - dict: The result of the Notecard request containing: - "motion": UNIX Epoch time of last detected Notecard movement - "time": UNIX Epoch time of last triangulation scan - "mode": Comma-separated list indicating active triangulation modes - "on": Boolean if triangulation scans will be performed even if device has not moved - "usb": Boolean if triangulation scans will be performed only when USB-powered - "length": Length of the text buffer provided in current or previous request - - Note: - See: https://dev.blues.io/notecard/notecard-walkthrough/time-and-location-requests/#using-cell-tower-and-wi-fi-triangulation + dict: The result of the Notecard request. """ req = {"req": "card.triangulate"} if mode: @@ -950,26 +803,11 @@ def usageGet(card, mode=None, offset=None): Args: card (Notecard): The current Notecard object. - mode (string): The time period to use for statistics. Must be one of: - "total" for all stats since activation (default), - "1hour", "1day", "30day". - offset (int): The number of time periods to look backwards, based on the specified mode. + mode (str): The time period to use for statistics. Must be one of: `"total"` for all stats since the Notecard was activated. `"1hour"` `"1day"` `"30day"` + offset (int): The number of time periods to look backwards, based on the specified `mode`. To accurately determine the start of the calculated time period when using `offset`, use the `time` value of the response. Likewise, to calculate the end of the time period, add the `seconds` value to the `time` value. Returns: - dict: The result of the Notecard request containing: - "seconds": Number of seconds in the analyzed period - "time": UNIX Epoch time of start of analyzed period (or activation time if mode="total") - "bytes_sent": Number of bytes sent by the Notecard to Notehub - "bytes_received": Number of bytes received by the Notecard from Notehub - "notes_sent": Approximate number of notes sent by the Notecard to Notehub - "notes_received": Approximate number of notes received by the Notecard from Notehub - "sessions_standard": Number of standard Notehub sessions - "sessions_secure": Number of secure Notehub sessions - - Note: - Usage data is updated at the end of each network connection. If connected in continuous mode, - usage data will not be updated until the current session ends. - See: https://dev.blues.io/notecard/notecard-walkthrough/low-bandwidth-design#measuring-data-usage + dict: The result of the Notecard request. """ req = {"req": "card.usage.get"} if mode: @@ -981,30 +819,16 @@ def usageGet(card, mode=None, offset=None): @validate_card_object def usageTest(card, days=None, hours=None, megabytes=None): - """Test and project data usage based on historical usage patterns. + """Calculate a projection of how long the available data quota will last based on the observed usage patterns. Args: card (Notecard): The current Notecard object. days (int): Number of days to use for the test. - hours (int): If analyzing a period shorter than one day, the number of hours to use for the test. - megabytes (int): The Notecard lifetime data quota (in megabytes) to use for the test. Default: 1024. + hours (int): If you want to analyze a period shorter than one day, the number of hours to use for the test. + megabytes (int): The Notecard lifetime data quota (in megabytes) to use for the test. Returns: - dict: The result of the Notecard request containing: - "max": Days of projected data available based on test - "days": Number of days used for the test - "bytes_per_day": Average bytes per day used during the test period - "seconds": Number of seconds in the analyzed period - "time": UNIX Epoch time of device activation - "bytes_sent": Number of bytes sent by the Notecard to Notehub - "bytes_received": Number of bytes received by the Notecard from Notehub - "notes_sent": Number of notes sent by the Notecard to Notehub - "notes_received": Number of notes received by the Notecard from Notehub - "sessions_standard": Number of standard Notehub sessions - "sessions_secure": Number of secure Notehub sessions - - Note: - See: https://dev.blues.io/notecard/notecard-walkthrough/low-bandwidth-design#projecting-the-lifetime-of-available-data + dict: The result of the Notecard request. """ req = {"req": "card.usage.test"} if days is not None: @@ -1017,78 +841,84 @@ def usageTest(card, days=None, hours=None, megabytes=None): @validate_card_object -def wifi(card, ssid=None, password=None, name=None, org=None, start=None, text=None): - """Set up a Notecard WiFi to connect to a Wi-Fi access point. +def version(card, api=None): + """Return firmware version information for the Notecard. Args: card (Notecard): The current Notecard object. - ssid (string): The SSID of the Wi-Fi access point. Use "-" to clear an already set SSID. - password (string): The network password of the Wi-Fi access point. - Use "-" to clear an already set password or to connect to an open access point. - name (string): Custom name for the SoftAP (software enabled access point). - Default is "Notecard". Use "-" suffix to append MAC address digits. - org (string): If specified, replaces the Blues logo on the SoftAP page with the provided name. - start (bool): Set to True to activate SoftAP mode on the Notecard programmatically. - text (string): String containing an array of access points in format: - '["FIRST-SSID","FIRST-PASSWORD"],["SECOND-SSID","SECOND-PASSWORD"]' + api (int): Host expected major version of the Notecard API Returns: - dict: The result of the Notecard request containing: - "secure": Boolean indicating if Wi-Fi access point uses Management Frame Protection - "version": Silicon Labs WF200 Wi-Fi Transceiver binary version - "ssid": SSID of the Wi-Fi access point - "security": Security protocol the Wi-Fi access point uses - - Note: - Updates to WiFi credentials cannot occur while Notecard is in continuous mode. - Change to periodic or off mode first using hub.set. - See: https://dev.blues.io/guides-and-tutorials/notecard-guides/connecting-to-a-wi-fi-access-point/ + dict: The result of the Notecard request. """ - req = {"req": "card.wifi"} - if ssid: - req["ssid"] = ssid - if password is not None: - req["password"] = password + req = {"req": "card.version"} + if api is not None: + req["api"] = api + return card.Transaction(req) + + +@validate_card_object +def voltage(card, hours=None, mode=None, offset=None, vmax=None, vmin=None, name=None, usb=None, alert=None, sync=None, calibration=None, set=None): + """Provide the current V+ voltage level on the Notecard, and provides information about historical voltage trends. When used with the mode argument, configures voltage thresholds based on how the device is powered. + + Args: + card (Notecard): The current Notecard object. + hours (int): The number of hours to analyze, up to 720 (30 days). + mode (str): Used to set voltage thresholds based on how the Notecard will be powered. NOTE: Setting voltage thresholds is not supported on the Notecard XP. + offset (int): Number of hours to move into the past before starting analysis. + vmax (float): Ignore voltage readings above this level when performing calculations. + vmin (float): Ignore voltage readings below this level when performing calculations. + name (str): Specifies an environment variable to override application default timing values. + usb (bool): When enabled, the Notecard will monitor for changes to USB power state. + alert (bool): When enabled and the `usb` argument is set to `true`, the Notecard will add an entry to the `health.qo` Notefile when USB power is connected or disconnected. + sync (bool): When enabled and the `usb` argument is set to `true`, the Notecard will perform a sync when USB power is connected or disconnected. + calibration (float): The offset, in volts, to account for the forward voltage drop of the diode used between the battery and Notecard in either Blues- or customer-designed Notecarriers. + set (bool): Used along with `calibration`, set to `true` to specify a new calibration value. + + Returns: + dict: The result of the Notecard request. + """ + req = {"req": "card.voltage"} + if hours is not None: + req["hours"] = hours + if mode: + req["mode"] = mode + if offset is not None: + req["offset"] = offset + if vmax is not None: + req["vmax"] = vmax + if vmin is not None: + req["vmin"] = vmin if name: req["name"] = name - if org is not None: - req["org"] = org - if start is not None: - req["start"] = start - if text: - req["text"] = text + if usb is not None: + req["usb"] = usb + if alert is not None: + req["alert"] = alert + if sync is not None: + req["sync"] = sync + if calibration is not None: + req["calibration"] = calibration + if set is not None: + req["set"] = set return card.Transaction(req) @validate_card_object def wirelessPenalty(card, reset=None, set=None, rate=None, add=None, max=None, min=None): - """View the current state of a Notecard Penalty Box, manually remove from penalty box, or override defaults. + """View the current state of a Notecard Penalty Box, manually remove the Notecard from a penalty box, or override penalty box defaults. Args: card (Notecard): The current Notecard object. - reset (bool): Set to True to remove the Notecard from certain types of penalty boxes. - set (bool): Set to True to override the default settings of the Network Registration Failure Penalty Box. - rate (float): The rate at which the penalty box time multiplier is increased over successive retries. - Default: 1.25. Used with set argument. - add (int): The number of minutes to add to successive retries. Default: 15. Used with set argument. - max (int): The maximum number of minutes that a device can be in a Network Registration Failure - Penalty Box. Default: 4320. Used with set argument. - min (int): The number of minutes of the first retry interval of a Network Registration Failure - Penalty Box. Default: 15. Used with set argument. + reset (bool): Set to `true` to remove the Notecard from certain types of penalty boxes. + set (bool): Set to `true` to override the default settings of the Network Registration Failure Penalty Box. + rate (float): The rate at which the penalty box time multiplier is increased over successive retries. Used with the `set` argument to override the Network Registration Failure Penalty Box defaults. + add (int): The number of minutes to add to successive retries. Used with the `set` argument to override the Network Registration Failure Penalty Box defaults. + max (int): The maximum number of minutes that a device can be in a Network Registration Failure Penalty Box. Used with the `set` argument to override the Network Registration Failure Penalty Box defaults. + min (int): The number of minutes of the first retry interval of a Network Registration Failure Penalty Box. Used with the `set` argument to override the Network Registration Failure Penalty Box defaults. Returns: - dict: The result of the Notecard request containing: - "minutes": Time since the first network registration failure - "count": Number of consecutive network registration failures - "status": If in a Penalty Box, provides associated Error and Status Codes - "seconds": If in a Penalty Box, number of seconds until the penalty condition ends - - Warning: - Misuse of this feature may result in the cellular carrier preventing future connections - or blacklisting devices for attempting to connect too frequently. - - Note: - See: https://dev.blues.io/guides-and-tutorials/notecard-guides/understanding-notecard-penalty-boxes + dict: The result of the Notecard request. """ req = {"req": "card.wireless.penalty"} if reset is not None: @@ -1104,3 +934,61 @@ def wirelessPenalty(card, reset=None, set=None, rate=None, add=None, max=None, m if min is not None: req["min"] = min return card.Transaction(req) + + +@validate_card_object +def wireless(card, mode=None, apn=None, method=None, hours=None): + """View the last known network state, or customize the behavior of the modem. Note: Be careful when using this mode with hardware not on hand as a mistake may cause loss of network and Notehub access. + + Args: + card (Notecard): The current Notecard object. + mode (str): Network scan mode. Must be one of: `"-"` to reset to the default mode, `"auto"` to perform automatic band scan mode (this is the default mode). The following values apply exclusively to Narrowband (NB) Notecard Cellular devices: `"m"` to restrict the modem to Cat-M1, `"nb"` to restrict the modem to Cat-NB1, `"gprs"` to restrict the modem to EGPRS. + apn (str): Access Point Name (APN) when using an external SIM. Use `"-"` to reset to the Notecard default APN. + method (str): Used when configuring a Notecard to failover to a different SIM. + hours (int): When using the `method` argument with `"dual-primary-secondary"` or `"dual-secondary-primary"`, this is the number of hours after which the Notecard will attempt to switch back to the preferred SIM. + + Returns: + dict: The result of the Notecard request. + """ + req = {"req": "card.wireless"} + if mode: + req["mode"] = mode + if apn: + req["apn"] = apn + if method: + req["method"] = method + if hours is not None: + req["hours"] = hours + return card.Transaction(req) + + +@validate_card_object +def wifi(card, ssid=None, password=None, name=None, org=None, start=None, text=None): + r"""Set up a Notecard WiFi to connect to a Wi-Fi access point. + + Args: + card (Notecard): The current Notecard object. + ssid (str): The SSID of the Wi-Fi access point. Alternatively, use `-` to clear an already set SSID. + password (str): The network password of the Wi-Fi access point. Alternatively, use `-` to clear an already set password or to connect to an open access point. + name (str): By default, the Notecard creates a SoftAP (software enabled access point) under the name "Notecard". You can use the `name` argument to change the name of the SoftAP to a custom name. If you include a `-` at the end of the `name` (for example `"name": "acme-"`), the Notecard will append the last four digits of the network's MAC address (for example `acme-025c`). This allows you to distinguish between multiple Notecards in SoftAP mode. + org (str): If specified, replaces the Blues logo on the SoftAP page with the provided name. + start (bool): Specify `true` to activate SoftAP mode on the Notecard programmatically. + text (str): A string containing an array of access points the Notecard should attempt to use. The access points should be provided in the following format: `["FIRST-SSID","FIRST-PASSWORD"],["SECOND-SSID","SECOND-PASSWORD"]`. You may need to escape any quotes used in this argument before passing it to the Notecard. For example, the following is a valid request to pass to a Notecard through the In-Browser Terminal. `{"req":"card.wifi", "text":"[\"FIRST-SSID\",\"FIRST-PASSWORD\"]"}` + + Returns: + dict: The result of the Notecard request. + """ + req = {"req": "card.wifi"} + if ssid: + req["ssid"] = ssid + if password: + req["password"] = password + if name: + req["name"] = name + if org: + req["org"] = org + if start is not None: + req["start"] = start + if text: + req["text"] = text + return card.Transaction(req) diff --git a/notecard/dfu.py b/notecard/dfu.py new file mode 100644 index 0000000..bc6de11 --- /dev/null +++ b/notecard/dfu.py @@ -0,0 +1,70 @@ +"""dfu Fluent API Helper.""" + +## +# @file dfu.py +# +# @brief dfu Fluent API Helper. +# +# @section description Description +# This module contains helper methods for calling dfu.* Notecard API commands. +# This module is optional and not required for use with the Notecard. + +from notecard.validators import validate_card_object + + +@validate_card_object +def get(card, length=None, offset=None): + """Retrieve downloaded firmware data from the Notecard for use with IAP host MCU firmware updates. + + Args: + card (Notecard): The current Notecard object. + length (int): The number of bytes of firmware data to read and return to the host. Set to `0` to verify that the Notecard is in DFU mode without attempting to retrieve data. + offset (int): The offset to use before performing a read of firmware data. + + Returns: + dict: The result of the Notecard request. + """ + req = {"req": "dfu.get"} + if length is not None: + req["length"] = length + if offset is not None: + req["offset"] = offset + return card.Transaction(req) + + +@validate_card_object +def status(card, name=None, stop=None, status=None, version=None, vvalue=None, on=None, off=None, err=None): + """Get and sets the background download status of MCU host or Notecard firmware updates. + + Args: + card (Notecard): The current Notecard object. + name (str): Determines which type of firmware update status to view. The value can be `"user"` (default), which gets the status of MCU host firmware updates, or `"card"`, which gets the status of Notecard firmware updates. + stop (bool): `true` to clear DFU state and delete the local firmware image from the Notecard. + status (str): When setting `stop` to `true`, an optional string synchronized to Notehub, which can be used for informational or diagnostic purposes. + version (str): Version information on the host firmware to pass to Notehub. You may pass a simple version number string (e.g. `"1.0.0.0"`), or an object with detailed information about the firmware image (recommended). + vvalue (str): A voltage-variable string that controls, by Notecard voltage, whether or not DFU is enabled. Use a boolean `1` (on) or `0` (off) for each source/voltage level: `usb:<1/0>;high:<1/0>;normal:<1/0>;low:<1/0>;dead:0`. + on (bool): `true` to allow firmware downloads from Notehub. + off (bool): `true` to disable firmware downloads from Notehub. + err (str): If `err` text is provided along with `"stop":true`, this sets the host DFU to an error state with the specified string. + + Returns: + dict: The result of the Notecard request. + """ + req = {"req": "dfu.status"} + if name: + req["name"] = name + if stop is not None: + req["stop"] = stop + if status: + req["status"] = status + if version: + req["version"] = version + if vvalue: + req["vvalue"] = vvalue + if on is not None: + req["on"] = on + if off is not None: + req["off"] = off + if err: + req["err"] = err + return card.Transaction(req) diff --git a/notecard/env.py b/notecard/env.py index 58de6b8..8635a55 100644 --- a/notecard/env.py +++ b/notecard/env.py @@ -9,21 +9,20 @@ # This module contains helper methods for calling env.* Notecard API commands. # This module is optional and not required for use with the Notecard. -import notecard from notecard.validators import validate_card_object @validate_card_object def default(card, name=None, text=None): - """Perform an env.default request against a Notecard. + """Use by the Notecard host to specify a default value for an environment variable until that variable is overridden by a device, project or fleet-wide setting at Notehub. Args: card (Notecard): The current Notecard object. - name (string): The name of an environment var to set a default for. - text (optional): The default value. Omit to delete the default. + name (str): The name of the environment variable (case-insensitive). + text (str): The value of the variable. Pass `""` or omit from the request to delete it. Returns: - string: The result of the Notecard request. + dict: The result of the Notecard request. """ req = {"req": "env.default"} if name: @@ -34,47 +33,56 @@ def default(card, name=None, text=None): @validate_card_object -def get(card, name=None): - """Perform an env.get request against a Notecard. +def get(card, name=None, names=None, time=None): + """Return a single environment variable, or all variables according to precedence rules. Args: card (Notecard): The current Notecard object. - name (string): The name of an environment variable to get. + name (str): The name of the environment variable (case-insensitive). Omit to return all environment variables known to the Notecard. + names (list): A list of one or more variables to retrieve, by name (case-insensitive). + time (int): Request a modified environment variable or variables from the Notecard, but only if modified after the time provided. Returns: - string: The result of the Notecard request. + dict: The result of the Notecard request. """ req = {"req": "env.get"} if name: req["name"] = name + if names: + req["names"] = names + if time is not None: + req["time"] = time return card.Transaction(req) @validate_card_object -def modified(card): - """Perform an env.modified request against a Notecard. +def modified(card, time=None): + """Get the time of the update to any environment variable managed by the Notecard. Args: card (Notecard): The current Notecard object. + time (int): Request whether the Notecard has detected an environment variable change since a known epoch time. Returns: - string: The result of the Notecard request. + dict: The result of the Notecard request. """ req = {"req": "env.modified"} + if time is not None: + req["time"] = time return card.Transaction(req) @validate_card_object def set(card, name=None, text=None): - """Perform an env.set request against a Notecard. + """Set a local environment variable on the Notecard. Local environment variables cannot be overridden by a Notehub variable of any scope. Args: card (Notecard): The current Notecard object. - name (string): The name of an environment variable to set. - text (optional): The variable value. Omit to delete. + name (str): The name of the environment variable (case-insensitive). + text (str): The value of the variable. Pass `""` or omit from the request to delete it. Returns: - string: The result of the Notecard request. + dict: The result of the Notecard request. """ req = {"req": "env.set"} if name: @@ -82,3 +90,20 @@ def set(card, name=None, text=None): if text: req["text"] = text return card.Transaction(req) + + +@validate_card_object +def template(card, body=None): + """Use env.template request allows developers to provide a schema for the environment variables the Notecard uses. The provided template allows the Notecard to store environment variables as fixed-length binary records rather than as flexible JSON objects that require much more memory. + + Args: + card (Notecard): The current Notecard object. + body (dict): A sample JSON body that specifies environment variables names and values as "hints" for the data type. Possible data types are: boolean, integer, float, and string. See Understanding Template Data Types for a full explanation of type hints. + + Returns: + dict: The result of the Notecard request. + """ + req = {"req": "env.template"} + if body: + req["body"] = body + return card.Transaction(req) diff --git a/notecard/file.py b/notecard/file.py index 91cb5a9..bc498c5 100644 --- a/notecard/file.py +++ b/notecard/file.py @@ -9,72 +9,89 @@ # This module contains helper methods for calling file.* Notecard API commands. # This module is optional and not required for use with the Notecard. -import notecard from notecard.validators import validate_card_object @validate_card_object -def changes(card, tracker=None, files=None): - """Perform individual or batch queries on Notefiles. +def changesPending(card): + """Return info about file changes that are pending upload to Notehub. Args: card (Notecard): The current Notecard object. - tracker (string): A developer-defined tracker ID. - files (array): A list of Notefiles to retrieve changes for. Returns: - string: The result of the Notecard request. + dict: The result of the Notecard request. """ - req = {"req": "file.changes"} - if tracker: - req["tracker"] = tracker - if files: - req["files"] = files + req = {"req": "file.changes.pending"} return card.Transaction(req) @validate_card_object -def delete(card, files=None): - """Delete individual notefiles and their contents. +def changes(card, files=None, tracker=None): + """Use file.changes request performs queries on a single or multiple files to determine if new Notes are available to read, or if there are unsynced Notes in local Notefiles. Note: This request is a Notefile API request, only. `.qo` Notes in Notehub are automatically ingested and stored, or sent to applicable Routes. Args: card (Notecard): The current Notecard object. - files (array): A list of Notefiles to delete. + files (list): An array of Notefile names to obtain change information from. If not specified, queries all Notefiles. + tracker (str): An ID string for a change tracker to use to determine changes to Notefiles. Each tracker maintains its own state for monitoring file changes. Returns: - string: The result of the Notecard request. + dict: The result of the Notecard request. """ - req = {"req": "file.delete"} + req = {"req": "file.changes"} if files: req["files"] = files + if tracker: + req["tracker"] = tracker return card.Transaction(req) @validate_card_object -def stats(card): - """Obtain statistics about local notefiles. +def clear(card, file=None): + """Use to clear the contents of a specified outbound (.qo/.qos) Notefile, deleting all pending Notes. Args: card (Notecard): The current Notecard object. + file (str): The name of the Notefile whose Notes you wish to delete. Returns: - string: The result of the Notecard request. + dict: The result of the Notecard request. """ - req = {"req": "file.stats"} - + req = {"req": "file.clear"} + if file: + req["file"] = file return card.Transaction(req) @validate_card_object -def pendingChanges(card): - """Retrieve information about pending Notehub changes. +def delete(card, files=None): + """Delete Notefiles and the Notes they contain. Args: card (Notecard): The current Notecard object. + files (list): One or more files to delete. Returns: - string: The result of the Notecard request. + dict: The result of the Notecard request. """ - req = {"req": "file.changes.pending"} + req = {"req": "file.delete"} + if files: + req["files"] = files + return card.Transaction(req) + + +@validate_card_object +def stats(card, file=None): + """Get resource statistics about local Notefiles. + + Args: + card (Notecard): The current Notecard object. + file (str): Returns the stats for the specified Notefile only. + Returns: + dict: The result of the Notecard request. + """ + req = {"req": "file.stats"} + if file: + req["file"] = file return card.Transaction(req) diff --git a/notecard/hub.py b/notecard/hub.py index 0403d24..bcfc73f 100644 --- a/notecard/hub.py +++ b/notecard/hub.py @@ -9,139 +9,183 @@ # This module contains helper methods for calling hub.* Notecard API commands. # This module is optional and not required for use with the Notecard. -import notecard from notecard.validators import validate_card_object @validate_card_object -def set(card, product=None, sn=None, mode=None, outbound=None, - inbound=None, duration=None, sync=False, align=None, voutbound=None, - vinbound=None, host=None): - """Configure Notehub behavior on the Notecard. +def get(card): + """Retrieve the current Notehub configuration for the Notecard. Args: card (Notecard): The current Notecard object. - product (string): The ProductUID of the project. - sn (string): The Serial Number of the device. - mode (string): The sync mode to use. - outbound (int): Max time to wait to sync outgoing data. - inbound (int): Max time to wait to sync incoming data. - duration (int): If in continuous mode, the amount of time, in minutes, - of each session. - sync (bool): If in continuous mode, whether to automatically - sync each time a change is detected on the device or Notehub. - align (bool): To align syncs to a regular time-interval, as opposed - to using max time values. - voutbound (string): Overrides "outbound" with a voltage-variable value. - vinbound (string): Overrides "inbound" with a voltage-variable value. - host (string): URL of an alternative or private Notehub instance. Returns: - string: The result of the Notecard request. + dict: The result of the Notecard request. """ - req = {"req": "hub.set"} - if product: - req["product"] = product - if sn: - req["sn"] = sn - if mode: - req["mode"] = mode - if outbound: - req["outbound"] = outbound - if inbound: - req["inbound"] = inbound - if duration: - req["duration"] = duration - if sync is not None: - req["sync"] = sync - if align is not None: - req["align"] = align - if voutbound: - req["voutbound"] = voutbound - if vinbound: - req["vinbound"] = vinbound - if host: - req["host"] = host - + req = {"req": "hub.get"} return card.Transaction(req) @validate_card_object -def sync(card): - """Initiate a sync of the Notecard to Notehub. +def log(card, alert=None, sync=None, text=None): + """Add a "device health" log message to send to Notehub on the next sync via the healthhost.qo Notefile. Args: card (Notecard): The current Notecard object. + alert (bool): `true` if the message is urgent. This doesn't change any functionality, but rather `alert` is provided as a convenient flag to use in your program logic. + sync (bool): `true` if a sync should be initiated immediately. Setting `true` will also remove the Notecard from certain types of penalty boxes. + text (str): Text to log. Returns: - string: The result of the Notecard request. + dict: The result of the Notecard request. """ - req = {"req": "hub.sync"} + req = {"req": "hub.log"} + if alert is not None: + req["alert"] = alert + if sync is not None: + req["sync"] = sync + if text: + req["text"] = text return card.Transaction(req) @validate_card_object -def syncStatus(card, sync=None): - """Retrieve the status of a sync request. +def set(card, align=None, details=None, duration=None, host=None, inbound=None, mode=None, off=None, on=None, outbound=None, product=None, seconds=None, sn=None, sync=None, umin=None, uoff=None, uperiodic=None, version=None, vinbound=None, voutbound=None): + """Use hub.set request is the primary method for controlling the Notecard's Notehub connection and sync behavior. Args: card (Notecard): The current Notecard object. - sync (bool): True if sync should be auto-initiated pending - outbound data. + align (bool): Use `true` to align syncs on a regular time-periodic cycle. + details (str): When using Notecard LoRa you can use this argument to provide information about an alternative LoRaWAN server or service you would like the Notecard to use. Use `"-"` to reset a Notecard's LoRaWAN details to its default values. + duration (int): When in `continuous` mode, the amount of time, in minutes, of each session (the minimum allowed value is `15`). + host (str): The URL of the Notehub service. Use `"-"` to reset to the default value. + inbound (int): The max wait time, in minutes, to sync inbound data from Notehub. Use `-1` to reset the value back to its default of `0`. A value of `0` means that the Notecard will never sync inbound data unless explicitly told to do so. + mode (str): The Notecard's synchronization mode. + off (bool): Set to `true` to manually instruct the Notecard to resume periodic mode after a web transaction has completed. + on (bool): If in `periodic` mode, used to temporarily switch the Notecard to `continuous` mode to perform a web transaction. + outbound (int): The max wait time, in minutes, to sync outbound data from the Notecard. Use `-1` to reset the value back to its default of `0`. A value of `0` means that the Notecard will never sync outbound data unless explicitly told to do so. + product (str): A Notehub-managed unique identifier that is used to match Devices with Projects. + seconds (int): If in `periodic` mode and using `on` above, the number of seconds to run in continuous mode before switching back to periodic mode. If not set, a default of 300 seconds is used. + sn (str): The end product's serial number. + sync (bool): If in `continuous` mode, automatically and immediately sync each time an inbound Notefile change is detected on Notehub. NOTE: The `sync` argument is not supported when a Notecard is in NTN mode. + umin (bool): Set to `true` to use USB/line power variable sync behavior, enabling the Notecard to stay in `continuous` mode when connected to USB/line power and fallback to `minimum` mode when disconnected. + uoff (bool): Set to `true` to use USB/line power variable sync behavior, enabling the Notecard to stay in `continuous` mode when connected to USB/line power and fallback to `off` mode when disconnected. + uperiodic (bool): Set to `true` to use USB/line power variable sync behavior, enabling the Notecard to stay in `continuous` mode when connected to USB/line power and fallback to `periodic` mode when disconnected. + version (str): The version of your host firmware. You may pass a simple version number string, or an object with detailed information about the firmware image. + vinbound (str): Overrides `inbound` with a voltage-variable value. Use `"-"` to clear this value. NOTE: Setting voltage-variable values is not supported on Notecard XP. + voutbound (str): Overrides `outbound` with a voltage-variable value. Use `"-"` to clear this value. NOTE: Setting voltage-variable values is not supported on Notecard XP. Returns: - string: The result of the Notecard request. + dict: The result of the Notecard request. """ - req = {"req": "hub.sync.status"} + req = {"req": "hub.set"} + if align is not None: + req["align"] = align + if details: + req["details"] = details + if duration is not None: + req["duration"] = duration + if host: + req["host"] = host + if inbound is not None: + req["inbound"] = inbound + if mode: + req["mode"] = mode + if off is not None: + req["off"] = off + if on is not None: + req["on"] = on + if outbound is not None: + req["outbound"] = outbound + if product: + req["product"] = product + if seconds is not None: + req["seconds"] = seconds + if sn: + req["sn"] = sn if sync is not None: req["sync"] = sync + if umin is not None: + req["umin"] = umin + if uoff is not None: + req["uoff"] = uoff + if uperiodic is not None: + req["uperiodic"] = uperiodic + if version: + req["version"] = version + if vinbound: + req["vinbound"] = vinbound + if voutbound: + req["voutbound"] = voutbound + return card.Transaction(req) + +@validate_card_object +def signal(card, seconds=None): + """Receive a Signal (a near-real-time note) from Notehub. + + Args: + card (Notecard): The current Notecard object. + seconds (int): The number of seconds to wait before timing out the request. + + Returns: + dict: The result of the Notecard request. + """ + req = {"req": "hub.signal"} + if seconds is not None: + req["seconds"] = seconds return card.Transaction(req) @validate_card_object def status(card): - """Retrieve the status of the Notecard's connection. + """Display the current status of the Notecard's connection to Notehub. Args: card (Notecard): The current Notecard object. Returns: - string: The result of the Notecard request. + dict: The result of the Notecard request. """ req = {"req": "hub.status"} return card.Transaction(req) @validate_card_object -def log(card, text, alert=False, sync=False): - """Send a log request to the Notecard. +def sync(card, allow=None, in_=None, out_=None): + """Manually initiates a sync with Notehub. Args: card (Notecard): The current Notecard object. - text (string): The ProductUID of the project. - alert (bool): True if the message is urgent. - sync (bool): Whether to sync right away. + allow (bool): Set to `true` to remove the Notecard from certain types of penalty boxes (the default is `false`). + in_ (bool): Set to `true` to only sync pending inbound Notefiles. Required when using NTN mode with Starnote to check for inbound Notefiles. + out_ (bool): Set to `true` to only sync pending outbound Notefiles. Returns: - string: The result of the Notecard request. + dict: The result of the Notecard request. """ - req = {"req": "hub.log"} - req["text"] = text - req["alert"] = alert - req["sync"] = sync + req = {"req": "hub.sync"} + if allow is not None: + req["allow"] = allow + if in_ is not None: + req["in"] = in_ + if out_ is not None: + req["out"] = out_ return card.Transaction(req) @validate_card_object -def get(card): - """Retrieve the current Notehub configuration parameters. +def syncStatus(card, sync=None): + """Check on the status of a recently triggered or previous sync. Args: card (Notecard): The current Notecard object. + sync (bool): `true` if this request should auto-initiate a sync pending outbound data. Returns: - string: The result of the Notecard request. + dict: The result of the Notecard request. """ - req = {"req": "hub.get"} + req = {"req": "hub.sync.status"} + if sync is not None: + req["sync"] = sync return card.Transaction(req) diff --git a/notecard/note.py b/notecard/note.py index 2716687..fec6821 100644 --- a/notecard/note.py +++ b/notecard/note.py @@ -9,66 +9,84 @@ # This module contains helper methods for calling note.* Notecard API commands. # This module is optional and not required for use with the Notecard. -import notecard from notecard.validators import validate_card_object @validate_card_object -def add(card, file=None, body=None, payload=None, sync=None, port=None): - """Add a Note to a Notefile. +def add(card, file=None, note=None, body=None, payload=None, sync=None, key=None, verify=None, binary=None, live=None, full=None, limit=None, max=None): + """Add a Note to a Notefile, creating the Notefile if it doesn't yet exist. Args: card (Notecard): The current Notecard object. - file (string): The name of the file. - body (JSON object): A developer-defined tracker ID. - payload (string): An optional base64-encoded string. - sync (bool): Perform an immediate sync after adding. - port (int): If provided, a unique number to represent a notefile. - Required for Notecard LoRa. + file (str): The name of the Notefile. On Notecard LoRa this argument is required. On all other Notecards this field is optional and defaults to `data.qo` if not provided. When using this request on the Notecard the Notefile name must end in one of: `.qo` for a queue outgoing (Notecard to Notehub) with plaintext transport, `.qos` for a queue outgoing with encrypted transport, `.db` for a bidirectionally synchronized database with plaintext transport, `.dbs` for a bidirectionally synchronized database with encrypted transport, `.dbx` for a local-only database. + note (str): If the Notefile has a `.db/.dbs/.dbx` extension, specifies a unique Note ID. If `note` string is "?", then a random unique Note ID is generated and returned as `{"note":"xxx"}`. If this argument is provided for a `.qo` Notefile, an error is returned. + body (dict): A JSON object to be enqueued. A note must have either a `body` or a `payload`, and can have both. + payload (str): A base64-encoded binary payload. A note must have either a `body` or a `payload`, and can have both. If a Note template is not in use, payloads are limited to 250 bytes. + sync (bool): Set to `true` to sync immediately. Only applies to outgoing Notecard requests, and only guarantees syncing the specified Notefile. Auto-syncing incoming Notes from Notehub is set on the Notecard with `{"req": "hub.set", "mode":"continuous", "sync": true}`. + key (str): The name of an environment variable in your Notehub.io project that contains the contents of a public key. Used when encrypting the Note body for transport. + verify (bool): If set to `true` and using a templated Notefile, the Notefile will be written to flash immediately, rather than being cached in RAM and written to flash later. + binary (bool): If `true`, the Notecard will send all the data in the binary buffer to Notehub. + live (bool): If `true`, bypasses saving the Note to flash on the Notecard. Required to be set to `true` if also using `"binary":true`. + full (bool): If set to `true`, and the Note is using a Notefile Template, the Note will bypass usage of omitempty and retain `null`, `0`, `false`, and empty string `""` values. + limit (bool): If set to `true`, the Note will not be created if Notecard is in a penalty box. + max (int): Defines the maximum number of queued Notes permitted in the specified Notefile (`"file"`). Any Notes added after this value will be rejected. When used with `"sync":true`, a sync will be triggered when the number of pending Notes matches the `max` value. Returns: - string: The result of the Notecard request. + dict: The result of the Notecard request. """ req = {"req": "note.add"} if file: req["file"] = file + if note: + req["note"] = note if body: req["body"] = body if payload: req["payload"] = payload - if port: - req["port"] = port if sync is not None: req["sync"] = sync + if key: + req["key"] = key + if verify is not None: + req["verify"] = verify + if binary is not None: + req["binary"] = binary + if live is not None: + req["live"] = live + if full is not None: + req["full"] = full + if limit is not None: + req["limit"] = limit + if max is not None: + req["max"] = max return card.Transaction(req) @validate_card_object -def changes(card, file=None, tracker=None, maximum=None, - start=None, stop=None, deleted=None, delete=None): - """Incrementally retrieve changes within a Notefile. +def changes(card, file, tracker=None, max=None, start=None, stop=None, deleted=None, delete=None, reset=None): + """Use to incrementally retrieve changes within a specific Notefile. Args: card (Notecard): The current Notecard object. - file (string): The name of the file. - tracker (string): A developer-defined tracker ID. - maximum (int): Maximum number of notes to return. - start (bool): Should tracker be reset to the beginning - before a get. - stop (bool): Should tracker be deleted after get. - deleted (bool): Should deleted notes be returned. - delete (bool): Should notes in a response be auto-deleted. + file (str): The Notefile ID. + tracker (str): The change tracker ID. This value is developer-defined and can be used across both the `note.changes` and `file.changes` requests. + max (int): The maximum number of Notes to return in the request. + start (bool): `true` to reset the tracker to the beginning. + stop (bool): `true` to delete the tracker. + deleted (bool): `true` to return deleted Notes with this request. Deleted notes are only persisted in a database notefile (`.db/.dbs`) between the time of note deletion on the Notecard and the time that a sync with notehub takes place. As such, this boolean will have no effect after a sync or on queue notefiles (`.q*`). + delete (bool): `true` to delete the Notes returned by the request. + reset (bool): `true` to reset a change tracker. Returns: - string: The result of the Notecard request. + dict: The result of the Notecard request. """ req = {"req": "note.changes"} if file: req["file"] = file if tracker: req["tracker"] = tracker - if maximum: - req["max"] = maximum + if max is not None: + req["max"] = max if start is not None: req["start"] = start if stop is not None: @@ -77,111 +95,122 @@ def changes(card, file=None, tracker=None, maximum=None, req["deleted"] = deleted if delete is not None: req["delete"] = delete + if reset is not None: + req["reset"] = reset return card.Transaction(req) @validate_card_object -def get(card, file="data.qi", note_id=None, delete=None, deleted=None): - """Retrieve a note from an inbound or DB Notefile. +def delete(card, file, note, verify=None): + """Delete a Note from a DB Notefile by its Note ID. To delete Notes from a `.qi` Notefile, use `note.get` or `note.changes` with `delete:true`. Args: card (Notecard): The current Notecard object. - file (string): The inbound or DB notefile to retrieve a - Notefile from. - note_id (string): (DB files only) The ID of the note to retrieve. - delete (bool): Whether to delete the note after retrieval. - deleted (bool): Whether to allow retrieval of a deleted note. + file (str): The Notefile from which to delete a Note. Must be a Notefile with a `.db` or `.dbx` extension. + note (str): The Note ID of the Note to delete. + verify (bool): If set to `true` and using a templated Notefile, the Notefile will be written to flash immediately, rather than being cached in RAM and written to flash later. Returns: - string: The result of the Notecard request. + dict: The result of the Notecard request. """ - req = {"req": "note.get"} - req["file"] = file - if note_id: - req["note"] = note_id - if delete is not None: - req["delete"] = delete - if deleted is not None: - req["deleted"] = deleted + req = {"req": "note.delete"} + if file: + req["file"] = file + if note: + req["note"] = note + if verify is not None: + req["verify"] = verify return card.Transaction(req) @validate_card_object -def delete(card, file=None, note_id=None): - """Delete a DB note in a Notefile by its ID. +def get(card, file=None, note=None, delete=None, deleted=None, decrypt=None): + """Retrieve a Note from a Notefile. The file must either be a DB Notefile or inbound queue file (see `file` argument below). Args: card (Notecard): The current Notecard object. - file (string): The file name of the DB notefile. - note_id (string): The id of the note to delete. + file (str): The Notefile name must end in `.qi` (for plaintext transport), `.qis` (for encrypted transport), `.db` or `.dbx` (for local-only DB Notefiles). + note (str): If the Notefile has a `.db` or `.dbx` extension, specifies a unique Note ID. Not applicable to `.qi` Notefiles. + delete (bool): `true` to delete the Note after retrieving it. + deleted (bool): `true` to allow retrieval of a deleted Note. + decrypt (bool): `true` to decrypt encrypted inbound Notefiles. Returns: - string: The result of the Notecard request. + dict: The result of the Notecard request. """ - req = {"req": "note.delete"} + req = {"req": "note.get"} if file: req["file"] = file - if note_id: - req["note"] = note_id + if note: + req["note"] = note + if delete is not None: + req["delete"] = delete + if deleted is not None: + req["deleted"] = deleted + if decrypt is not None: + req["decrypt"] = decrypt return card.Transaction(req) @validate_card_object -def update(card, file=None, note_id=None, body=None, payload=None): - """Update a note in a DB Notefile by ID. +def template(card, file, body=None, length=None, verify=None, format=None, port=None, delete=None): + """By using the `note.template` request with any `.qo`/`.qos` Notefile, developers can provide the Notecard with a schema of sorts to apply to future Notes added to the Notefile. This template acts as a hint to the Notecard that allows it to internally store data as fixed-length binary records rather than as flexible JSON objects which require much more memory. Using templated Notes in place of regular Notes increases the storage and sync capability of the Notecard by an order of magnitude. Args: card (Notecard): The current Notecard object. - file (string): The file name of the DB notefile. - note_id (string): The id of the note to update. - body (JSON): The JSON object to add to the note. - payload (string): The base64-encoded JSON payload to - add to the note. + file (str): The name of the Notefile to which the template will be applied. + body (dict): A sample JSON body that specifies field names and values as "hints" for the data type. Possible data types are: boolean, integer, float, and string. See Understanding Template Data Types for an explanation of type hints and explanations. + length (int): The maximum length of a `payload` (in bytes) that can be sent in Notes for the template Notefile. As of v3.2.1 `length` is not required, and payloads can be added to any template-based Note without specifying the payload length. + verify (bool): If `true`, returns the current template set on a given Notefile. + format (str): By default all Note templates automatically include metadata, including a timestamp for when the Note was created, various fields about a device's location, as well as a timestamp for when the device's location was determined. By providing a `format` of `"compact"` you tell the Notecard to omit this additional metadata to save on storage and bandwidth. The use of `format: "compact"` is required for Notecard LoRa and a Notecard paired with Starnote. When using `"compact"` templates, you may include the following keywords in your template to add in fields that would otherwise be omitted: `lat`, `lon`, `ltime`, `time`. See Creating Compact Templates to learn more. + port (int): This argument is required on Notecard LoRa and a Notecard paired with Starnote, but ignored on all other Notecards. A port is a unique integer in the range 1–100, where each unique number represents one Notefile. This argument allows the Notecard to send a numerical reference to the Notefile over the air, rather than the full Notefile name. The port you provide is also used in the "frame port" field on LoRaWAN gateways. + delete (bool): Set to `true` to delete all pending Notes using the template if one of the following scenarios is also true: Connecting via non-NTN (e.g. cellular or Wi-Fi) communications, but attempting to sync NTN-compatible Notefiles. or Connecting via NTN (e.g. satellite) communications, but attempting to sync non-NTN-compatible Notefiles. Read more about this feature in Starnote Best Practices. Returns: - string: The result of the Notecard request. + dict: The result of the Notecard request. """ - req = {"req": "note.update"} + req = {"req": "note.template"} if file: req["file"] = file - if note_id: - req["note"] = note_id if body: req["body"] = body - if payload: - req["payload"] = payload + if length is not None: + req["length"] = length + if verify is not None: + req["verify"] = verify + if format: + req["format"] = format + if port is not None: + req["port"] = port + if delete is not None: + req["delete"] = delete return card.Transaction(req) @validate_card_object -def template(card, file=None, body=None, length=None, port=None, compact=False): - """Create a template for new Notes in a Notefile. +def update(card, file, note, body=None, payload=None, verify=None): + """Update a Note in a DB Notefile by its ID, replacing the existing `body` and/or `payload`. Args: card (Notecard): The current Notecard object. - file (string): The file name of the notefile. - body (JSON): A sample JSON body that specifies field names and - values as "hints" for the data type. - length (int): If provided, the maximum length of a payload that - can be sent in Notes for the template Notefile. - port (int): If provided, a unique number to represent a notefile. - Required for Notecard LoRa. - compact (boolean): If true, sets the format to compact to tell the - Notecard to omit this additional metadata to save on storage - and bandwidth. Required for Notecard LoRa. + file (str): The name of the DB Notefile that contains the Note to update. + note (str): The unique Note ID. + body (dict): A JSON object to add to the Note. A Note must have either a `body` or `payload`, and can have both. + payload (str): A base64-encoded binary payload. A Note must have either a `body` or `payload`, and can have both. + verify (bool): If set to `true` and using a templated Notefile, the Notefile will be written to flash immediately, rather than being cached in RAM and written to flash later. Returns: - string: The result of the Notecard request. + dict: The result of the Notecard request. """ - req = {"req": "note.template"} + req = {"req": "note.update"} if file: req["file"] = file + if note: + req["note"] = note if body: req["body"] = body - if length: - req["length"] = length - if port: - req["port"] = port - if compact: - req["format"] = "compact" + if payload: + req["payload"] = payload + if verify is not None: + req["verify"] = verify return card.Transaction(req) diff --git a/notecard/ntn.py b/notecard/ntn.py new file mode 100644 index 0000000..2806e66 --- /dev/null +++ b/notecard/ntn.py @@ -0,0 +1,60 @@ +"""ntn Fluent API Helper.""" + +## +# @file ntn.py +# +# @brief ntn Fluent API Helper. +# +# @section description Description +# This module contains helper methods for calling ntn.* Notecard API commands. +# This module is optional and not required for use with the Notecard. + +from notecard.validators import validate_card_object + + +@validate_card_object +def gps(card, on=None, off=None): + """Determine whether a Notecard should override a paired Starnote's GPS/GNSS location with its own GPS/GNSS location. The paired Starnote uses its own GPS/GNSS location by default. + + Args: + card (Notecard): The current Notecard object. + on (bool): When `true`, a Starnote will use the GPS/GNSS location from its paired Notecard, instead of its own GPS/GNSS location. + off (bool): When `true`, a paired Starnote will use its own GPS/GNSS location. This is the default configuration. + + Returns: + dict: The result of the Notecard request. + """ + req = {"req": "ntn.gps"} + if on is not None: + req["on"] = on + if off is not None: + req["off"] = off + return card.Transaction(req) + + +@validate_card_object +def reset(card): + """Once a Notecard is connected to a Starnote device, the presence of a physical Starnote is stored in a permanent configuration that is not affected by a `card.restore` request. This request clears this configuration and allows you to return to testing NTN mode over cellular or Wi-Fi. + + Args: + card (Notecard): The current Notecard object. + + Returns: + dict: The result of the Notecard request. + """ + req = {"req": "ntn.reset"} + return card.Transaction(req) + + +@validate_card_object +def status(card): + """Display the current status of a Notecard's connection to a paired Starnote. + + Args: + card (Notecard): The current Notecard object. + + Returns: + dict: The result of the Notecard request. + """ + req = {"req": "ntn.status"} + return card.Transaction(req) diff --git a/notecard/var.py b/notecard/var.py new file mode 100644 index 0000000..6a652dd --- /dev/null +++ b/notecard/var.py @@ -0,0 +1,84 @@ +"""var Fluent API Helper.""" + +## +# @file var.py +# +# @brief var Fluent API Helper. +# +# @section description Description +# This module contains helper methods for calling var.* Notecard API commands. +# This module is optional and not required for use with the Notecard. + +from notecard.validators import validate_card_object + + +@validate_card_object +def delete(card, name=None, file=None): + """Delete a Note from a DB Notefile by its name. Provides a simpler interface to the note.delete API. + + Args: + card (Notecard): The current Notecard object. + name (str): The unique Note ID. + file (str): The name of the DB Notefile that contains the Note to delete. Default value is `vars.db`. + + Returns: + dict: The result of the Notecard request. + """ + req = {"req": "var.delete"} + if name: + req["name"] = name + if file: + req["file"] = file + return card.Transaction(req) + + +@validate_card_object +def get(card, name=None, file=None): + """Retrieve a Note from a DB Notefile. Provides a simpler interface to the note.get API. + + Args: + card (Notecard): The current Notecard object. + name (str): The unique Note ID. + file (str): The name of the DB Notefile that contains the Note to retrieve. Default value is `vars.db`. + + Returns: + dict: The result of the Notecard request. + """ + req = {"req": "var.get"} + if name: + req["name"] = name + if file: + req["file"] = file + return card.Transaction(req) + + +@validate_card_object +def set(card, name=None, file=None, text=None, value=None, flag=None, sync=None): + """Add or updates a Note in a DB Notefile, replacing the existing body with the specified key-value pair where text, value, or flag is the key. Provides a simpler interface to the note.update API. + + Args: + card (Notecard): The current Notecard object. + name (str): The unique Note ID. + file (str): The name of the DB Notefile that contains the Note to add or update. Default value is `vars.db`. + text (str): The string-based value to be stored in the DB Notefile. + value (int): The numeric value to be stored in the DB Notefile. + flag (bool): The boolean value to be stored in the DB Notefile. + sync (bool): Set to `true` to immediately sync any changes. + + Returns: + dict: The result of the Notecard request. + """ + req = {"req": "var.set"} + if name: + req["name"] = name + if file: + req["file"] = file + if text: + req["text"] = text + if value is not None: + req["value"] = value + if flag is not None: + req["flag"] = flag + if sync is not None: + req["sync"] = sync + return card.Transaction(req) diff --git a/notecard/web.py b/notecard/web.py new file mode 100644 index 0000000..a641efb --- /dev/null +++ b/notecard/web.py @@ -0,0 +1,235 @@ +"""web Fluent API Helper.""" + +## +# @file web.py +# +# @brief web Fluent API Helper. +# +# @section description Description +# This module contains helper methods for calling web.* Notecard API commands. +# This module is optional and not required for use with the Notecard. + +from notecard.validators import validate_card_object + + +@validate_card_object +def delete(card, route=None, name=None, content=None, seconds=None, async_=None, file=None, note=None): + """Perform a simple HTTP or HTTPS `DELETE` request against an external endpoint, and returns the response to the Notecard. + + Args: + card (Notecard): The current Notecard object. + route (str): Alias for a Proxy Route in Notehub. + name (str): A web URL endpoint relative to the host configured in the Proxy Route. URL parameters may be added to this argument as well (e.g. `/deleteReading?id=1`). + content (str): The MIME type of the body or payload of the response. Default is `application/json`. + seconds (int): If specified, overrides the default 90 second timeout. + async_ (bool): If `true`, the Notecard performs the web request asynchronously, and returns control to the host without waiting for a response from Notehub. + file (str): The name of the local-only Database Notefile (`.dbx`) to be used if the web request is issued asynchronously and you wish to store the response. + note (str): The unique Note ID for the local-only Database Notefile (`.dbx`). Only used with asynchronous web requests (see `file` argument above). + + Returns: + dict: The result of the Notecard request. + """ + req = {"req": "web.delete"} + if route: + req["route"] = route + if name: + req["name"] = name + if content: + req["content"] = content + if seconds is not None: + req["seconds"] = seconds + if async_ is not None: + req["async"] = async_ + if file: + req["file"] = file + if note: + req["note"] = note + return card.Transaction(req) + + +@validate_card_object +def get(card, route=None, name=None, body=None, content=None, seconds=None, async_=None, binary=None, offset=None, max=None, file=None, note=None): + """Perform a simple HTTP or HTTPS `GET` request against an external endpoint, and returns the response to the Notecard. + + Args: + card (Notecard): The current Notecard object. + route (str): Alias for a Proxy Route in Notehub. + name (str): A web URL endpoint relative to the host configured in the Proxy Route. URL parameters may be added to this argument as well (e.g. `/getLatest?id=1`). + body (dict): The JSON body to send with the request. + content (str): The MIME type of the body or payload of the response. Default is `application/json`. + seconds (int): If specified, overrides the default 90 second timeout. + async_ (bool): If `true`, the Notecard performs the web request asynchronously, and returns control to the host without waiting for a response from Notehub. + binary (bool): If `true`, the Notecard will return the response stored in its binary buffer. + offset (int): Used along with `binary:true` and `max`, sent as a URL parameter to the remote endpoint. Represents the number of bytes to offset the binary payload from 0 when retrieving binary data from the remote endpoint. + max (int): Used along with `binary:true` and `offset`, sent as a URL parameter to the remote endpoint. Represents the number of bytes to retrieve from the binary payload segment. + file (str): The name of the local-only Database Notefile (`.dbx`) to be used if the web request is issued asynchronously and you wish to store the response. + note (str): The unique Note ID for the local-only Database Notefile (`.dbx`). Only used with asynchronous web requests (see `file` argument above). + + Returns: + dict: The result of the Notecard request. + """ + req = {"req": "web.get"} + if route: + req["route"] = route + if name: + req["name"] = name + if body: + req["body"] = body + if content: + req["content"] = content + if seconds is not None: + req["seconds"] = seconds + if async_ is not None: + req["async"] = async_ + if binary is not None: + req["binary"] = binary + if offset is not None: + req["offset"] = offset + if max is not None: + req["max"] = max + if file: + req["file"] = file + if note: + req["note"] = note + return card.Transaction(req) + + +@validate_card_object +def post(card, route=None, name=None, body=None, payload=None, content=None, seconds=None, total=None, offset=None, status=None, max=None, verify=None, async_=None, binary=None, file=None, note=None): + """Perform a simple HTTP or HTTPS `POST` request against an external endpoint, and returns the response to the Notecard. + + Args: + card (Notecard): The current Notecard object. + route (str): Alias for a Proxy Route in Notehub. + name (str): A web URL endpoint relative to the host configured in the Proxy Route. URL parameters may be added to this argument as well (e.g. `/addReading?id=1`). + body (dict): The JSON body to send with the request. + payload (str): A base64-encoded binary payload. A `web.post` may have either a `body` or a `payload`, but may NOT have both. Be aware that Notehub will decode the payload as it is delivered to the endpoint. Learn more about sending large binary objects with the Notecard. + content (str): The MIME type of the body or payload of the response. Default is `application/json`. + seconds (int): If specified, overrides the default 90 second timeout. + total (int): When sending large payloads to Notehub in fragments across several `web.post` requests, the total size, in bytes, of the binary payload across all fragments. + offset (int): When sending payload fragments, the number of bytes of the binary payload to offset from 0 when reassembling on the Notehub once all fragments have been received. + status (str): A 32-character hex-encoded MD5 sum of the payload or payload fragment. Used by Notehub to perform verification upon receipt. + max (int): The maximum size of the response from the remote server, in bytes. Useful if a memory-constrained host wants to limit the response size. + verify (bool): `true` to request verification from Notehub once the payload or payload fragment is received. Automatically set to `true` when `status` is supplied. + async_ (bool): If `true`, the Notecard performs the web request asynchronously, and returns control to the host without waiting for a response from Notehub. + binary (bool): If `true`, the Notecard will send all the data in the binary buffer to the specified proxy route in Notehub. Learn more in this guide on Sending and Receiving Large Binary Objects. + file (str): The name of the local-only Database Notefile (`.dbx`) to be used if the web request is issued asynchronously and you wish to store the response. + note (str): The unique Note ID for the local-only Database Notefile (`.dbx`). Only used with asynchronous web requests (see `file` argument above). + + Returns: + dict: The result of the Notecard request. + """ + req = {"req": "web.post"} + if route: + req["route"] = route + if name: + req["name"] = name + if body: + req["body"] = body + if payload: + req["payload"] = payload + if content: + req["content"] = content + if seconds is not None: + req["seconds"] = seconds + if total is not None: + req["total"] = total + if offset is not None: + req["offset"] = offset + if status: + req["status"] = status + if max is not None: + req["max"] = max + if verify is not None: + req["verify"] = verify + if async_ is not None: + req["async"] = async_ + if binary is not None: + req["binary"] = binary + if file: + req["file"] = file + if note: + req["note"] = note + return card.Transaction(req) + + +@validate_card_object +def put(card, route=None, name=None, body=None, payload=None, content=None, seconds=None, total=None, offset=None, status=None, max=None, verify=None, async_=None, file=None, note=None): + """Perform a simple HTTP or HTTPS `PUT` request against an external endpoint, and returns the response to the Notecard. + + Args: + card (Notecard): The current Notecard object. + route (str): Alias for a Proxy Route in Notehub. + name (str): A web URL endpoint relative to the host configured in the Proxy Route. URL parameters may be added to this argument as well (e.g. `/updateReading?id=1`). + body (dict): The JSON body to send with the request. + payload (str): A base64-encoded binary payload. A `web.put` may have either a `body` or a `payload`, but may NOT have both. Be aware that Notehub will decode the payload as it is delivered to the endpoint. Learn more about sending large binary objects with the Notecard. + content (str): The MIME type of the body or payload of the response. Default is `application/json`. + seconds (int): If specified, overrides the default 90 second timeout. + total (int): When sending large payloads to Notehub in fragments across several `web.put` requests, the total size, in bytes, of the binary payload across all fragments. + offset (int): When sending payload fragments, the number of bytes of the binary payload to offset from 0 when reassembling on the Notehub once all fragments have been received. + status (str): A 32-character hex-encoded MD5 sum of the payload or payload fragment. Used by Notehub to perform verification upon receipt. + max (int): The maximum size of the response from the remote server, in bytes. Useful if a memory-constrained host wants to limit the response size. Default (and maximum value) is 8192. + verify (bool): `true` to request verification from Notehub once the payload or payload fragment is received. Automatically set to `true` when `status` is supplied. + async_ (bool): If `true`, the Notecard performs the web request asynchronously, and returns control to the host without waiting for a response from Notehub. + file (str): The name of the local-only Database Notefile (`.dbx`) to be used if the web request is issued asynchronously and you wish to store the response. + note (str): The unique Note ID for the local-only Database Notefile (`.dbx`). Only used with asynchronous web requests (see `file` argument above). + + Returns: + dict: The result of the Notecard request. + """ + req = {"req": "web.put"} + if route: + req["route"] = route + if name: + req["name"] = name + if body: + req["body"] = body + if payload: + req["payload"] = payload + if content: + req["content"] = content + if seconds is not None: + req["seconds"] = seconds + if total is not None: + req["total"] = total + if offset is not None: + req["offset"] = offset + if status: + req["status"] = status + if max is not None: + req["max"] = max + if verify is not None: + req["verify"] = verify + if async_ is not None: + req["async"] = async_ + if file: + req["file"] = file + if note: + req["note"] = note + return card.Transaction(req) + + +@validate_card_object +def web(card, route=None, method=None, name=None, content=None): + """Perform an HTTP or HTTPS request against an external endpoint, with the ability to specify any valid HTTP method. + + Args: + card (Notecard): The current Notecard object. + route (str): Alias for a Proxy Route in Notehub. + method (str): The HTTP method of the request. Must be one of GET, PUT, POST, DELETE, PATCH, HEAD, OPTIONS, TRACE, or CONNECT. + name (str): A web URL endpoint relative to the host configured in the Proxy Route. URL parameters may be added to this argument as well (e.g. `/getLatest?id=1`). + content (str): The MIME type of the body or payload of the response. Default is `application/json`. + + Returns: + dict: The result of the Notecard request. + """ + req = {"req": "web"} + if route: + req["route"] = route + if method: + req["method"] = method + if name: + req["name"] = name + if content: + req["content"] = content + return card.Transaction(req) diff --git a/pytest.ini b/pytest.ini index 3ebe93f..b577431 100644 --- a/pytest.ini +++ b/pytest.ini @@ -1,2 +1,2 @@ [pytest] -addopts = --ignore=test/hitl/ +addopts = --ignore=test/hitl/ --ignore=scripts/ diff --git a/generate_apis.py b/scripts/generate_apis.py similarity index 61% rename from generate_apis.py rename to scripts/generate_apis.py index 21aecaa..d22aa71 100644 --- a/generate_apis.py +++ b/scripts/generate_apis.py @@ -1,9 +1,8 @@ #!/usr/bin/env python3 -"""Generate Notecard API functions from JSON schema.""" +"""Generate Notecard API functions from notecard-schema.""" -import json import requests -import re +import time from pathlib import Path from typing import Dict, List, Optional, Any import argparse @@ -12,22 +11,30 @@ class NotecardAPIGenerator: """Generate Python API functions from Notecard JSON schema.""" + reserved_keywords = {"in", "out", "from", "import", "class", "def", "if", "else", "for", "while", "try", "except", "with", "as", "is", "not", "and", "or", "async", "await"} + def __init__(self, schema_url: str = "https://raw.githubusercontent.com/blues/notecard-schema/refs/heads/master/notecard.api.json", notecard_dir: str = "notecard"): self.schema_url = schema_url self.base_url = "/".join(schema_url.split("/")[:-1]) + "/" self.apis = {} self.notecard_dir = Path(notecard_dir) - self.meta_configs = self.load_meta_configs() - - def fetch_schema(self, url: str) -> Dict[str, Any]: - """Fetch JSON schema from URL.""" - try: - response = requests.get(url) - response.raise_for_status() - return response.json() - except Exception as e: - print(f"Error fetching {url}: {e}") - return {} + + def fetch_schema(self, url: str, max_retries: int = 3, delay: float = 1.0) -> Dict[str, Any]: + """Fetch JSON schema from URL with retry logic.""" + for attempt in range(max_retries): + try: + response = requests.get(url, timeout=30) + response.raise_for_status() + return response.json() + except Exception as e: + if attempt < max_retries - 1: + print(f"Error fetching {url} (attempt {attempt + 1}/{max_retries}): {e}") + print(f"Retrying in {delay} seconds...") + time.sleep(delay) + delay *= 2 # Exponential backoff + else: + print(f"Failed to fetch {url} after {max_retries} attempts: {e}") + return {} def parse_main_schema(self) -> List[str]: """Parse main schema and extract API references.""" @@ -119,28 +126,61 @@ def clean_docstring_text(self, text: str) -> str: import re text = re.sub(r'\[([^\]]+)\]\([^\)]+\)', r'\1', text) + # Remove markdown emphasis formatting (* and _) + # Handle both single and double emphasis markers + text = re.sub(r'\*\*([^*]+)\*\*', r'\1', text) # **bold** -> bold + text = re.sub(r'\*([^*]+)\*', r'\1', text) # *italic* -> italic + text = re.sub(r'__([^_]+)__', r'\1', text) # __bold__ -> bold + text = re.sub(r'_([^_]+)_', r'\1', text) # _italic_ -> italic + # Collapse multiple spaces into single spaces text = re.sub(r'\s+', ' ', text) # Strip leading/trailing whitespace return text.strip() - def load_meta_configs(self) -> Dict[str, Dict[str, Any]]: - """Load alias configuration files for backward compatibility.""" - aliases_file = self.notecard_dir / "aliases.json" - if not aliases_file.exists(): - return {} + def convert_to_imperative_mood(self, text: str) -> str: + """Convert docstring text to imperative mood using built-in replacements (Pydocstyle D402).""" + if not text: + return text - try: - with open(aliases_file, 'r') as f: - return json.load(f) - except Exception as e: - print(f"Warning: Could not load aliases config {aliases_file}: {e}") - return {} + # Built-in replacements for converting docstrings to imperative mood + replacements = { + "Returns": "Return", + "Configures": "Configure", + "Performs": "Perform", + "Used": "Use", + "Uses": "Use", + "Sets": "Set", + "Gets": "Get", + "Retrieves": "Retrieve", + "Displays": "Display", + "Adds": "Add", + "Enables": "Enable", + "Provides": "Provide", + "Deletes": "Delete", + "Updates": "Update", + "Calculates": "Calculate", + "Specifies": "Specify", + "Determines": "Determine", + "The": "Use", + "This": "Use" + } - def get_meta_config(self, api_name: str) -> Dict[str, Any]: - """Get meta configuration for an API, or empty dict if none exists.""" - return self.meta_configs.get(api_name, {}) + # Apply replacements - only replace at the start of sentences + for non_imperative, imperative in replacements.items(): + # Replace at the beginning of the text + if text.startswith(non_imperative + " "): + text = imperative + text[len(non_imperative):] + break + # Also handle cases where the non-imperative word starts the text + elif text.startswith(non_imperative): + # Make sure we're not replacing part of a larger word + if len(text) == len(non_imperative) or not text[len(non_imperative)].isalpha(): + text = imperative + text[len(non_imperative):] + break + + return text def generate_function_signature(self, api: Dict[str, Any]) -> str: """Generate Python function signature.""" @@ -155,74 +195,70 @@ def generate_function_signature(self, api: Dict[str, Any]) -> str: params = ["card"] - # Get meta configuration for this API - meta_config = self.get_meta_config(api_name) - aliases = meta_config.get("aliases", {}) - # Add parameters based on properties properties = api["properties"] required = api["required"] - # Python reserved keywords that need to be handled specially - reserved_keywords = {"in", "out", "from", "import", "class", "def", "if", "else", "for", "while", "try", "except", "with", "as", "is", "not", "and", "or", "async", "await"} - # Add parameters for schema properties for prop_name, _ in properties.items(): if prop_name in ["req", "cmd"]: # Skip these as they're auto-generated continue - # Check if this property has an alias - if so, make it optional - has_alias = False - for alias_config in aliases.values(): - if isinstance(alias_config, str): - if alias_config == prop_name: - has_alias = True - break - else: - if alias_config.get("field") == prop_name: - has_alias = True - break - # Handle reserved keywords by appending underscore param_name = prop_name - if param_name in reserved_keywords: + if param_name in self.reserved_keywords: param_name = f"{param_name}_" - # If the field is required but has an alias, make it optional since user can use alias - if prop_name in required and prop_name not in ["req", "cmd"] and not has_alias: + # Required parameters come first without default values + if prop_name in required and prop_name not in ["req", "cmd"]: params.append(f"{param_name}") else: params.append(f"{param_name}=None") - # Add alias parameters from meta config - for alias_name, alias_config in aliases.items(): - # Handle reserved keywords by appending underscore - param_name = alias_name - if param_name in reserved_keywords: - param_name = f"{param_name}_" - # Always optional since aliases are for backward compatibility - params.append(f"{param_name}=None") + return f"def {func_name}({', '.join(params)}):" + def _build_docstring_content(self, api: Dict[str, Any], imperative_description: str) -> str: + """Build complete docstring content to check for backslashes.""" + lines = [imperative_description, ""] - return f"def {func_name}({', '.join(params)}):" + properties = api["properties"] + + # Process schema properties + for prop_name, prop_def in properties.items(): + if prop_name in ["req", "cmd"]: + continue + + # Handle reserved keywords by appending underscore for parameter name + param_name = prop_name + if param_name in self.reserved_keywords: + param_name = f"{param_name}_" + + prop_desc = self.clean_docstring_text(prop_def.get("description", f"The {prop_name} parameter.")) + lines.append(f" {param_name} (type): {prop_desc}") + + return "\n".join(lines) def generate_docstring(self, api: Dict[str, Any]) -> str: """Generate function docstring.""" # Clean the description text clean_description = self.clean_docstring_text(api["description"]) - lines = [f' """{clean_description}'] + # Convert to imperative mood (Pydocstyle D402) + imperative_description = self.convert_to_imperative_mood(clean_description) + + # Check if docstring contains backslashes and use raw string if needed + docstring_content = self._build_docstring_content(api, imperative_description) + has_backslashes = '\\' in docstring_content + + if has_backslashes: + lines = [f' r"""{imperative_description}'] + else: + lines = [f' """{imperative_description}'] lines.append("") lines.append(" Args:") lines.append(" card (Notecard): The current Notecard object.") - # Get meta configuration for this API - api_name = api["name"] - meta_config = self.get_meta_config(api_name) - aliases = meta_config.get("aliases", {}) - properties = api["properties"] - reserved_keywords = {"in", "out", "from", "import", "class", "def", "if", "else", "for", "while", "try", "except", "with", "as", "is", "not", "and", "or", "async", "await"} # Process schema properties for prop_name, prop_def in properties.items(): @@ -231,40 +267,13 @@ def generate_docstring(self, api: Dict[str, Any]) -> str: # Handle reserved keywords by appending underscore for parameter name param_name = prop_name - if param_name in reserved_keywords: + if param_name in self.reserved_keywords: param_name = f"{param_name}_" prop_type = self.get_python_type_hint(prop_def) prop_desc = self.clean_docstring_text(prop_def.get("description", f"The {prop_name} parameter.")) lines.append(f" {param_name} ({prop_type}): {prop_desc}") - # Add alias parameters from meta config - for alias_name, alias_config in aliases.items(): - # Handle reserved keywords by appending underscore - param_name = alias_name - if param_name in reserved_keywords: - param_name = f"{param_name}_" - - # Handle both simple and complex aliases - if isinstance(alias_config, str): - schema_field = alias_config - # Find the corresponding schema property for type information - schema_prop_def = properties.get(schema_field, {}) - prop_type = self.get_python_type_hint(schema_prop_def) if schema_prop_def else "str" - original_desc = schema_prop_def.get('description', '') - clean_desc = self.clean_docstring_text(original_desc) if original_desc else '' - prop_desc = f"Alias for {schema_field}. {clean_desc}" - else: - # Complex alias with potential value transformation - schema_field = alias_config.get("field") - schema_prop_def = properties.get(schema_field, {}) - prop_type = self.get_python_type_hint(schema_prop_def) if schema_prop_def else "str" - original_desc = schema_prop_def.get('description', '') - clean_desc = self.clean_docstring_text(original_desc) if original_desc else '' - prop_desc = f"Alias for {schema_field}. {clean_desc}" - - lines.append(f" {param_name} ({prop_type}): {prop_desc}") - lines.append("") lines.append(" Returns:") @@ -278,12 +287,7 @@ def generate_function_body(self, api: Dict[str, Any]) -> str: api_name = api["name"] lines = [f' req = {{"req": "{api_name}"}}'] - # Get meta configuration for this API - meta_config = self.get_meta_config(api_name) - aliases = meta_config.get("aliases", {}) - properties = api["properties"] - reserved_keywords = {"in", "out", "from", "import", "class", "def", "if", "else", "for", "while", "try", "except", "with", "as", "is", "not", "and", "or", "async", "await"} # Process schema properties for prop_name, prop_def in properties.items(): @@ -292,7 +296,7 @@ def generate_function_body(self, api: Dict[str, Any]) -> str: # Handle reserved keywords by appending underscore for parameter name param_name = prop_name - if param_name in reserved_keywords: + if param_name in self.reserved_keywords: param_name = f"{param_name}_" json_type = prop_def.get("type", "string") @@ -315,52 +319,6 @@ def generate_function_body(self, api: Dict[str, Any]) -> str: lines.append(f" if {param_name}:") lines.append(f' req["{prop_name}"] = {param_name}') - # Handle alias parameters from meta config - for alias_name, alias_config in aliases.items(): - # Handle reserved keywords by appending underscore - param_name = alias_name - if param_name in reserved_keywords: - param_name = f"{param_name}_" - - # Check if this is a simple alias (string) or complex alias (dict) - if isinstance(alias_config, str): - # Simple alias: alias_name -> schema_field - schema_field = alias_config - # Get type info from the corresponding schema field - schema_prop_def = properties.get(schema_field, {}) - json_type = schema_prop_def.get("type", "string") - - # Handle case where type is a list of types - if isinstance(json_type, list): - # Use the first non-null type - for t in json_type: - if t != "null": - json_type = t - break - else: - json_type = "string" - - # Use 'is not None' for types that can have falsy but valid values (0, False, "", etc.) - lines.append(f" # Supported Alias") - if json_type in ["boolean", "integer", "number"]: - lines.append(f" if {param_name} is not None:") - lines.append(f' req["{schema_field}"] = {param_name}') - else: - lines.append(f" if {param_name}:") - lines.append(f' req["{schema_field}"] = {param_name}') - else: - # Complex alias with value transformation - schema_field = alias_config.get("field") - value_transform = alias_config.get("value_transform", {}) - - lines.append(f" # Supported Alias") - lines.append(f" if {param_name}:") - if value_transform and "true" in value_transform: - # Handle boolean transformation (e.g., compact=True -> format="compact") - lines.append(f' req["{schema_field}"] = "{value_transform["true"]}"') - else: - lines.append(f' req["{schema_field}"] = {param_name}') - lines.append(" return card.Transaction(req)") @@ -370,6 +328,7 @@ def generate_api_function(self, api: Dict[str, Any]) -> str: """Generate complete API function.""" lines = [] lines.append("") + lines.append("") lines.append("@validate_card_object") lines.append(self.generate_function_signature(api)) lines.append(self.generate_docstring(api)) @@ -403,12 +362,12 @@ def generate_module_file(self, module_name: str, apis: List[Dict[str, Any]]) -> lines.append("# This module is optional and not required for use with the Notecard.") lines.append("") lines.append("from notecard.validators import validate_card_object") - lines.append("") for api in apis: lines.append(self.generate_api_function(api)) - return "\n".join(lines) + # Ensure the file ends with a newline + return "\n".join(lines) + "\n" def generate_all_apis(self, output_dir: str = "notecard"): """Generate all API modules.""" @@ -434,17 +393,14 @@ def generate_all_apis(self, output_dir: str = "notecard"): # Group by module modules = self.group_apis_by_module(apis) - # Create output directory output_path = Path(output_dir) output_path.mkdir(exist_ok=True) - # Generate each module file for module_name, module_apis in modules.items(): print(f"Generating {module_name}.py with {len(module_apis)} APIs...") file_content = self.generate_module_file(module_name, module_apis) - # Write to file file_path = output_path / f"{module_name}.py" with open(file_path, "w") as f: f.write(file_content) diff --git a/test/fluent_api/test_card.py b/test/fluent_api/test_card.py index 8ccd7e7..430024d 100644 --- a/test/fluent_api/test_card.py +++ b/test/fluent_api/test_card.py @@ -16,6 +16,13 @@ 'start': True } ), + ( + card.attn, + 'card.attn', + { + 'verify': True + } + ), ( card.status, 'card.status', @@ -31,11 +38,25 @@ 'card.temp', {'minutes': 5} ), + ( + card.temp, + 'card.temp', + { + 'status': 'usb:15;high:30;normal:60;720', + 'stop': True, + 'sync': True + } + ), ( card.version, 'card.version', {} ), + ( + card.version, + 'card.version', + {'api': True} + ), ( card.voltage, 'card.voltage', @@ -46,6 +67,15 @@ 'vmin': 1.2 } ), + ( + card.voltage, + 'card.voltage', + { + 'mode': 'charging', + 'name': 'voltage_sensor', + 'calibration': 1.1 + } + ), ( card.wireless, 'card.wireless', @@ -54,6 +84,13 @@ 'apn': 'myapn.nb' } ), + ( + card.wireless, + 'card.wireless', + { + 'method': 'dual-primary-secondary' + } + ), ( card.transport, 'card.transport', @@ -62,6 +99,14 @@ 'allow': True } ), + ( + card.transport, + 'card.transport', + { + 'seconds': 300, + 'umin': True + } + ), ( card.power, 'card.power', @@ -87,6 +132,14 @@ 'max': 60 } ), + ( + card.locationMode, + 'card.location.mode', + { + 'minutes': 10, + 'threshold': 100 + } + ), ( card.locationTrack, 'card.location.track', @@ -99,6 +152,13 @@ 'file': 'location.qo' } ), + ( + card.locationTrack, + 'card.location.track', + { + 'payload': 'ewogICJkYXRhIjogImV4YW1wbGUiCn0=' + } + ), ( card.binary, 'card.binary', @@ -266,6 +326,11 @@ 'card.restart', {} ), + ( + card.random, + 'card.random', + {'mode': 'entropy', 'count': 16} + ), ( card.restore, 'card.restore', @@ -344,6 +409,54 @@ 'max': 720, 'min': 5 } + ), + ( + card.attn, + 'card.attn', + { + 'off': True, + 'on': False + } + ), + ( + card.dfu, + 'card.dfu', + { + 'off': True, + 'on': False + } + ), + ( + card.locationMode, + 'card.location.mode', + { + 'delete': True + } + ), + ( + card.temp, + 'card.temp', + { + 'stop': True, + 'sync': False + } + ), + ( + card.voltage, + 'card.voltage', + { + 'usb': True, + 'alert': False, + 'sync': True, + 'set': False + } + ), + ( + card.wireless, + 'card.wireless', + { + 'hours': 24 + } ) ] ) diff --git a/test/fluent_api/test_dfu.py b/test/fluent_api/test_dfu.py new file mode 100644 index 0000000..3d3d49f --- /dev/null +++ b/test/fluent_api/test_dfu.py @@ -0,0 +1,65 @@ +import pytest +from notecard import dfu + + +@pytest.mark.parametrize( + 'fluent_api,notecard_api,req_params', + [ + ( + dfu.get, + 'dfu.get', + {} + ), + ( + dfu.get, + 'dfu.get', + {'length': 1024} + ), + ( + dfu.get, + 'dfu.get', + {'length': 1024, 'offset': 512} + ), + ( + dfu.status, + 'dfu.status', + {} + ), + ( + dfu.status, + 'dfu.status', + {'name': 'firmware.bin'} + ), + ( + dfu.status, + 'dfu.status', + {'stop': True, 'on': True} + ), + ( + dfu.status, + 'dfu.status', + {'status': 'ready', 'version': '1.0.0', 'vvalue': 100} + ), + ( + dfu.status, + 'dfu.status', + {'err': 'error message'} + ), + ( + dfu.status, + 'dfu.status', + {'off': True} + ) + ] +) +class TestDfu: + def test_fluent_api_maps_notecard_api_correctly( + self, fluent_api, notecard_api, req_params, + run_fluent_api_notecard_api_mapping_test): + run_fluent_api_notecard_api_mapping_test(fluent_api, notecard_api, + req_params) + + def test_fluent_api_fails_with_invalid_notecard( + self, fluent_api, notecard_api, req_params, + run_fluent_api_invalid_notecard_test): + run_fluent_api_invalid_notecard_test(fluent_api, req_params) diff --git a/test/fluent_api/test_env.py b/test/fluent_api/test_env.py index 963af6b..21717f0 100644 --- a/test/fluent_api/test_env.py +++ b/test/fluent_api/test_env.py @@ -24,6 +24,21 @@ env.set, 'env.set', {'name': 'my_var', 'text': 'my_text'} + ), + ( + env.template, + 'env.template', + {'body': {'temperature': 25.5, 'enabled': True, 'counter': 42, 'name': 'sensor'}} + ), + ( + env.get, + 'env.get', + {'names': ['var1', 'var2'], 'time': 1640995200} + ), + ( + env.modified, + 'env.modified', + {'time': 1640995200} ) ] ) diff --git a/test/fluent_api/test_file.py b/test/fluent_api/test_file.py index 81ff0c0..b674175 100644 --- a/test/fluent_api/test_file.py +++ b/test/fluent_api/test_file.py @@ -26,9 +26,19 @@ {} ), ( - file.pendingChanges, + file.stats, + 'file.stats', + {'file': 'data.qo'} + ), + ( + file.changesPending, 'file.changes.pending', {} + ), + ( + file.clear, + 'file.clear', + {'file': 'data.qo'} ) ] ) diff --git a/test/fluent_api/test_hub.py b/test/fluent_api/test_hub.py index 9200ada..03c8e8c 100644 --- a/test/fluent_api/test_hub.py +++ b/test/fluent_api/test_hub.py @@ -3,12 +3,13 @@ @pytest.mark.parametrize( - 'fluent_api,notecard_api,req_params', + 'fluent_api,notecard_api,req_params,rename_key_map', [ ( hub.get, 'hub.get', - {} + {}, + None ), ( hub.log, @@ -17,7 +18,8 @@ 'text': 'com.blues.tester', 'alert': True, 'sync': True - } + }, + None ), ( hub.set, @@ -34,33 +36,71 @@ 'voutbound': '2.3', 'vinbound': '3.3', 'host': 'http://hub.blues.foo' - } + }, + None ), ( hub.status, 'hub.status', - {} + {}, + None ), ( hub.sync, 'hub.sync', - {} + {}, + None ), ( hub.syncStatus, 'hub.sync.status', - {'sync': True} + {'sync': True}, + None + ), + ( + hub.signal, + 'hub.signal', + {'seconds': 30}, + None + ), + ( + hub.set, + 'hub.set', + { + 'details': 'LoRaWAN details', + 'off': True, + 'on': False, + 'seconds': 300, + 'umin': True, + 'uoff': False, + 'uperiodic': True, + 'version': '1.0.0' + }, + None + ), + ( + hub.sync, + 'hub.sync', + { + 'allow': True, + 'in_': False, + 'out_': True + }, + { + 'in_': 'in', + 'out_': 'out' + } ) ] ) class TestHub: def test_fluent_api_maps_notecard_api_correctly( - self, fluent_api, notecard_api, req_params, + self, fluent_api, notecard_api, req_params, rename_key_map, run_fluent_api_notecard_api_mapping_test): run_fluent_api_notecard_api_mapping_test(fluent_api, notecard_api, - req_params) + req_params, rename_key_map) def test_fluent_api_fails_with_invalid_notecard( - self, fluent_api, notecard_api, req_params, + self, fluent_api, notecard_api, req_params, rename_key_map, run_fluent_api_invalid_notecard_test): - run_fluent_api_invalid_notecard_test(fluent_api, req_params) + run_fluent_api_invalid_notecard_test(fluent_api, req_params, rename_key_map) diff --git a/test/fluent_api/test_note.py b/test/fluent_api/test_note.py index abfbe3f..1c7b1ee 100644 --- a/test/fluent_api/test_note.py +++ b/test/fluent_api/test_note.py @@ -3,7 +3,7 @@ @pytest.mark.parametrize( - 'fluent_api,notecard_api,req_params,rename_key_map,rename_value_map', + 'fluent_api,notecard_api,req_params', [ ( note.add, @@ -12,11 +12,22 @@ 'file': 'data.qo', 'body': {'key_a:', 'val_a', 'key_b', 42}, 'payload': 'ewogICJpbnRlcnZhbHMiOiI2MCwxMiwxNCIKfQ==', - 'port': 50, 'sync': True }, - None, - None + ), + ( + note.add, + 'note.add', + { + 'note': 'test_note_id', + 'key': 'encryption_key', + 'verify': True, + 'binary': True, + 'live': True, + 'full': True, + 'limit': True, + 'max': 100 + }, ), ( note.changes, @@ -24,42 +35,55 @@ { 'file': 'my-settings.db', 'tracker': 'inbound-tracker', - 'maximum': 2, + 'max': 2, 'start': True, 'stop': True, 'delete': True, 'deleted': True }, + ), + ( + note.changes, + 'note.changes', { - 'maximum': 'max' + 'file': 'my-settings.db', + 'reset': True }, - None ), ( note.delete, 'note.delete', { 'file': 'my-settings.db', - 'note_id': 'my_note', + 'note': 'my_note', }, + ), + ( + note.delete, + 'note.delete', { - 'note_id': 'note' + 'file': 'my-settings.db', + 'note': 'my_note', + 'verify': True }, - None ), ( note.get, 'note.get', { 'file': 'my-settings.db', - 'note_id': 'my_note', + 'note': 'my_note', 'delete': True, 'deleted': True }, + ), + ( + note.get, + 'note.get', { - 'note_id': 'note' + 'file': 'my-settings.db', + 'decrypt': True }, - None ), ( note.template, @@ -68,42 +92,46 @@ 'file': 'my-settings.db', 'body': {'key_a:', 'val_a', 'key_b', 42}, 'length': 42, - 'port': 50, - 'compact': True + 'format': "compact" }, + ), + ( + note.template, + 'note.template', { - 'compact': 'format' + 'file': 'my-settings.db', + 'verify': True, + 'port': 1, + 'delete': True }, - { - 'format': 'compact' - } ), ( note.update, 'note.update', { 'file': 'my-settings.db', - 'note_id': 'my_note', + 'note': 'my_note', 'body': {'key_a:', 'val_a', 'key_b', 42}, 'payload': 'ewogICJpbnRlcnZhbHMiOiI2MCwxMiwxNCIKfQ==' }, + ), + ( + note.update, + 'note.update', { - 'note_id': 'note' + 'file': 'my-settings.db', + 'note': 'my_note', + 'verify': True }, - None ) ] ) class TestNote: def test_fluent_api_maps_notecard_api_correctly( - self, fluent_api, notecard_api, req_params, rename_key_map, - rename_value_map, run_fluent_api_notecard_api_mapping_test): + self, fluent_api, notecard_api, req_params, run_fluent_api_notecard_api_mapping_test): run_fluent_api_notecard_api_mapping_test(fluent_api, notecard_api, - req_params, rename_key_map, - rename_value_map) + req_params) def test_fluent_api_fails_with_invalid_notecard( - self, fluent_api, notecard_api, req_params, rename_key_map, - rename_value_map, run_fluent_api_invalid_notecard_test): - run_fluent_api_invalid_notecard_test(fluent_api, req_params, - rename_key_map, rename_value_map) + self, fluent_api, notecard_api, req_params, run_fluent_api_invalid_notecard_test): + run_fluent_api_invalid_notecard_test(fluent_api, req_params) diff --git a/test/fluent_api/test_ntn.py b/test/fluent_api/test_ntn.py new file mode 100644 index 0000000..538e6a7 --- /dev/null +++ b/test/fluent_api/test_ntn.py @@ -0,0 +1,40 @@ +import pytest +from notecard import ntn + + +@pytest.mark.parametrize( + 'fluent_api,notecard_api,req_params', + [ + ( + ntn.gps, + 'ntn.gps', + {'on': True} + ), + ( + ntn.gps, + 'ntn.gps', + {'off': True} + ), + ( + ntn.reset, + 'ntn.reset', + {} + ), + ( + ntn.status, + 'ntn.status', + {} + ) + ] +) +class TestNtn: + def test_fluent_api_maps_notecard_api_correctly( + self, fluent_api, notecard_api, req_params, + run_fluent_api_notecard_api_mapping_test): + run_fluent_api_notecard_api_mapping_test(fluent_api, notecard_api, + req_params) + + def test_fluent_api_fails_with_invalid_notecard( + self, fluent_api, notecard_api, req_params, + run_fluent_api_invalid_notecard_test): + run_fluent_api_invalid_notecard_test(fluent_api, req_params) diff --git a/test/fluent_api/test_var.py b/test/fluent_api/test_var.py new file mode 100644 index 0000000..876f6c4 --- /dev/null +++ b/test/fluent_api/test_var.py @@ -0,0 +1,55 @@ +import pytest +from notecard import var + + +@pytest.mark.parametrize( + 'fluent_api,notecard_api,req_params', + [ + ( + var.delete, + 'var.delete', + {'name': 'my_var'} + ), + ( + var.delete, + 'var.delete', + {'name': 'my_var', 'file': 'config.db'} + ), + ( + var.get, + 'var.get', + {'name': 'my_var'} + ), + ( + var.get, + 'var.get', + {'name': 'my_var', 'file': 'config.db'} + ), + ( + var.set, + 'var.set', + {'name': 'my_var', 'text': 'my_value'} + ), + ( + var.set, + 'var.set', + {'name': 'my_var', 'value': 42, 'file': 'config.db'} + ), + ( + var.set, + 'var.set', + {'name': 'my_var', 'flag': True, 'sync': True} + ) + ] +) +class TestVar: + def test_fluent_api_maps_notecard_api_correctly( + self, fluent_api, notecard_api, req_params, + run_fluent_api_notecard_api_mapping_test): + run_fluent_api_notecard_api_mapping_test(fluent_api, notecard_api, + req_params) + + def test_fluent_api_fails_with_invalid_notecard( + self, fluent_api, notecard_api, req_params, + run_fluent_api_invalid_notecard_test): + run_fluent_api_invalid_notecard_test(fluent_api, req_params) diff --git a/test/fluent_api/test_web.py b/test/fluent_api/test_web.py new file mode 100644 index 0000000..ba4b2f4 --- /dev/null +++ b/test/fluent_api/test_web.py @@ -0,0 +1,114 @@ +import pytest +from notecard import web + + +@pytest.mark.parametrize( + 'fluent_api,notecard_api,req_params,rename_key_map', + [ + ( + web.delete, + 'web.delete', + { + 'route': 'my-proxy-route', + 'name': '/api/delete', + 'content': 'application/json', + 'seconds': 30, + 'async_': True, + 'file': 'responses.dbx', + 'note': 'delete_response_1' + }, + { + 'async_': 'async' + } + ), + ( + web.get, + 'web.get', + { + 'route': 'my-proxy-route', + 'name': '/api/data', + 'body': {'key': 'value'}, + 'content': 'application/json', + 'seconds': 60, + 'async_': False, + 'binary': True, + 'offset': 0, + 'max': 1024, + 'file': 'responses.dbx', + 'note': 'get_response_1' + }, + { + 'async_': 'async' + } + ), + ( + web.post, + 'web.post', + { + 'route': 'my-proxy-route', + 'name': '/api/submit', + 'body': {'sensor': 'temperature', 'value': 23.5}, + 'payload': 'ewogICJkYXRhIjogImV4YW1wbGUiCn0=', + 'content': 'application/json', + 'seconds': 45, + 'total': 2048, + 'offset': 512, + 'status': 'pending', + 'max': 1024, + 'verify': True, + 'async_': False, + 'binary': False, + 'file': 'responses.dbx', + 'note': 'post_response_1' + }, + { + 'async_': 'async' + } + ), + ( + web.put, + 'web.put', + { + 'route': 'my-proxy-route', + 'name': '/api/update', + 'body': {'id': 123, 'status': 'updated'}, + 'payload': 'ewogICJ1cGRhdGVkIjogdHJ1ZQp9', + 'content': 'application/json', + 'seconds': 90, + 'total': 4096, + 'offset': 1024, + 'status': 'complete', + 'max': 2048, + 'verify': False, + 'async_': True, + 'file': 'responses.dbx', + 'note': 'put_response_1' + }, + { + 'async_': 'async' + } + ), + ( + web.web, + 'web', + { + 'route': 'my-proxy-route', + 'method': 'GET', + 'name': '/api/status', + 'content': 'text/plain' + }, + None + ) + ] +) +class TestWeb: + def test_fluent_api_maps_notecard_api_correctly( + self, fluent_api, notecard_api, req_params, rename_key_map, + run_fluent_api_notecard_api_mapping_test): + run_fluent_api_notecard_api_mapping_test(fluent_api, notecard_api, + req_params, rename_key_map) + + def test_fluent_api_fails_with_invalid_notecard( + self, fluent_api, notecard_api, req_params, rename_key_map, + run_fluent_api_invalid_notecard_test): + run_fluent_api_invalid_notecard_test(fluent_api, req_params, rename_key_map) From 3df73fe5da262b1777dd05f655c4085f916c6b86 Mon Sep 17 00:00:00 2001 From: Alex Bucknall Date: Fri, 22 Aug 2025 11:42:12 +0100 Subject: [PATCH 3/7] fix: corrections with schemas --- notecard/card.py | 12 ++++++------ notecard/dfu.py | 2 +- notecard/env.py | 2 +- notecard/file.py | 8 ++++---- notecard/hub.py | 20 ++++++++++---------- notecard/note.py | 8 ++++---- notecard/var.py | 2 +- notecard/web.py | 2 +- 8 files changed, 28 insertions(+), 28 deletions(-) diff --git a/notecard/card.py b/notecard/card.py index ccbb3b3..da7d45a 100644 --- a/notecard/card.py +++ b/notecard/card.py @@ -321,7 +321,7 @@ def io(card, i2c=None, mode=None): @validate_card_object def led(card, mode=None, on=None, off=None): - """Use along with the card.aux API to turn connected LEDs on/off or to manage a single connected NeoPixel. + """Use along with the card.aux API to turn connected LEDs on/off or to manage a single connected NeoPixel. If using monochromatic LEDs, they must be wired according to the instructions provided in the guide on Using Monitor Mode. Please note that the use of monochromatic LEDs is not supported by Notecard for LoRa. If using NeoPixels, the NeoPixel (or strip of NeoPixels) must be wired according to the instructions provided in the guide on Using Neo-Monitor Mode. Args: card (Notecard): The current Notecard object. @@ -399,7 +399,7 @@ def location(card): @validate_card_object def locationTrack(card, start=None, heartbeat=None, hours=None, sync=None, stop=None, file=None, payload=None): - """Store location data in a Notefile at the `periodic` interval, or using a specified `heartbeat`. This request is only available when the `card.location.mode` request has been set to `periodic`—e.g. `{"req":"card.location.mode","mode":"periodic","seconds":300}`. If you want to track and transmit data simultaneously consider using an external GPS/GNSS module with the Notecard. If you connect a BME280 sensor on the I2C bus, Notecard will include a temperature, humidity, and pressure reading with each captured Note. If you connect an ENS210 sensor on the I2C bus, Notecard will include a temperature and pressure reading with each captured Note. + """Store location data in a Notefile at the `periodic` interval, or using a specified `heartbeat`. This request is only available when the `card.location.mode` request has been set to `periodic`—e.g. `{"req":"card.location.mode","mode":"periodic","seconds":300}`. If you want to track and transmit data simultaneously consider using an external GPS/GNSS module with the Notecard. If you connect a BME280 sensor on the I2C bus, Notecard will include a temperature, humidity, and pressure reading with each captured Note. If you connect an ENS210 sensor on the I2C bus, Notecard will include a temperature and pressure reading with each captured Note. Learn more in _track.qo. Args: card (Notecard): The current Notecard object. @@ -768,7 +768,7 @@ def triangulate(card, mode=None, on=None, usb=None, set=None, minutes=None, text Args: card (Notecard): The current Notecard object. - mode (str): The triangulation approach to use for determining the Notecard location. The following keywords can be used separately or together in a comma-delimited list, in any order. `cell` enables cell tower scanning to determine the position of the Device. `wifi` enables the use of nearby Wi-Fi access points to determine the position of the Device. To leverage this feature, the host will need to provide access point information to the Notecard via the `text` argument in subsequent requests. `-` to clear the currently-set triangulation mode. + mode (str): The triangulation approach to use for determining the Notecard location. The following keywords can be used separately or together in a comma-delimited list, in any order. See Using Cell Tower & Wi-Fi Triangulation for more information. on (bool): `true` to instruct the Notecard to triangulate even if the module has not moved. Only takes effect when `set` is `true`. usb (bool): `true` to use perform triangulation only when the Notecard is connected to USB power. Only takes effect when `set` is `true`. set (bool): `true` to instruct the module to use the state of the `on` and `usb` arguments. @@ -803,7 +803,7 @@ def usageGet(card, mode=None, offset=None): Args: card (Notecard): The current Notecard object. - mode (str): The time period to use for statistics. Must be one of: `"total"` for all stats since the Notecard was activated. `"1hour"` `"1day"` `"30day"` + mode (str): The time period to use for statistics. Must be one of: offset (int): The number of time periods to look backwards, based on the specified `mode`. To accurately determine the start of the calculated time period when using `offset`, use the `time` value of the response. Likewise, to calculate the end of the time period, add the `seconds` value to the `time` value. Returns: @@ -846,7 +846,7 @@ def version(card, api=None): Args: card (Notecard): The current Notecard object. - api (int): Host expected major version of the Notecard API + api (int): Specify a major version of the Notecard firmware that a host expects to use. Returns: dict: The result of the Notecard request. @@ -942,7 +942,7 @@ def wireless(card, mode=None, apn=None, method=None, hours=None): Args: card (Notecard): The current Notecard object. - mode (str): Network scan mode. Must be one of: `"-"` to reset to the default mode, `"auto"` to perform automatic band scan mode (this is the default mode). The following values apply exclusively to Narrowband (NB) Notecard Cellular devices: `"m"` to restrict the modem to Cat-M1, `"nb"` to restrict the modem to Cat-NB1, `"gprs"` to restrict the modem to EGPRS. + mode (str): Network scan mode. Must be one of: apn (str): Access Point Name (APN) when using an external SIM. Use `"-"` to reset to the Notecard default APN. method (str): Used when configuring a Notecard to failover to a different SIM. hours (int): When using the `method` argument with `"dual-primary-secondary"` or `"dual-secondary-primary"`, this is the number of hours after which the Notecard will attempt to switch back to the preferred SIM. diff --git a/notecard/dfu.py b/notecard/dfu.py index bc6de11..f52340b 100644 --- a/notecard/dfu.py +++ b/notecard/dfu.py @@ -41,7 +41,7 @@ def status(card, name=None, stop=None, status=None, version=None, vvalue=None, o name (str): Determines which type of firmware update status to view. The value can be `"user"` (default), which gets the status of MCU host firmware updates, or `"card"`, which gets the status of Notecard firmware updates. stop (bool): `true` to clear DFU state and delete the local firmware image from the Notecard. status (str): When setting `stop` to `true`, an optional string synchronized to Notehub, which can be used for informational or diagnostic purposes. - version (str): Version information on the host firmware to pass to Notehub. You may pass a simple version number string (e.g. `"1.0.0.0"`), or an object with detailed information about the firmware image (recommended). + version (str): Version information on the host firmware to pass to Notehub. You may pass a simple version number string (e.g. `"1.0.0.0"`), or an object with detailed information about the firmware image (recommended). If you provide an object it must take the following form. `{"org":"my-organization","product":"My Product","description":"A description of the image","version":"1.2.4","built":"Jan 01 2025 01:02:03","vermajor":1,"verminor":2,"verpatch":4,"verbuild": 5,"builder":"The Builder"}` Code to help you generate a version with the correct formatting is available in Enabling Notecard Outboard Firmware Update. vvalue (str): A voltage-variable string that controls, by Notecard voltage, whether or not DFU is enabled. Use a boolean `1` (on) or `0` (off) for each source/voltage level: `usb:<1/0>;high:<1/0>;normal:<1/0>;low:<1/0>;dead:0`. on (bool): `true` to allow firmware downloads from Notehub. off (bool): `true` to disable firmware downloads from Notehub. diff --git a/notecard/env.py b/notecard/env.py index 8635a55..e97e247 100644 --- a/notecard/env.py +++ b/notecard/env.py @@ -94,7 +94,7 @@ def set(card, name=None, text=None): @validate_card_object def template(card, body=None): - """Use env.template request allows developers to provide a schema for the environment variables the Notecard uses. The provided template allows the Notecard to store environment variables as fixed-length binary records rather than as flexible JSON objects that require much more memory. + """Use `env.template` request allows developers to provide a schema for the environment variables the Notecard uses. The provided template allows the Notecard to store environment variables as fixed-length binary records rather than as flexible JSON objects that require much more memory. Using templated environment variables also allows the Notecard to optimize the network traffic related to sending and receiving environment variable updates. Args: card (Notecard): The current Notecard object. diff --git a/notecard/file.py b/notecard/file.py index bc498c5..6e309d1 100644 --- a/notecard/file.py +++ b/notecard/file.py @@ -28,12 +28,12 @@ def changesPending(card): @validate_card_object def changes(card, files=None, tracker=None): - """Use file.changes request performs queries on a single or multiple files to determine if new Notes are available to read, or if there are unsynced Notes in local Notefiles. Note: This request is a Notefile API request, only. `.qo` Notes in Notehub are automatically ingested and stored, or sent to applicable Routes. + """Use to perform queries on a single or multiple files to determine if new Notes are available to read, or if there are unsynced Notes in local Notefiles. Note: This request is a Notefile API request, only. `.qo` Notes in Notehub are automatically ingested and stored, or sent to applicable Routes. Args: card (Notecard): The current Notecard object. - files (list): An array of Notefile names to obtain change information from. If not specified, queries all Notefiles. - tracker (str): An ID string for a change tracker to use to determine changes to Notefiles. Each tracker maintains its own state for monitoring file changes. + files (list): One or more files to obtain change information from. Omit to return changes for all Notefiles. + tracker (str): ID of a change tracker to use to determine changes to Notefiles. Returns: dict: The result of the Notecard request. @@ -48,7 +48,7 @@ def changes(card, files=None, tracker=None): @validate_card_object def clear(card, file=None): - """Use to clear the contents of a specified outbound (.qo/.qos) Notefile, deleting all pending Notes. + """Use to clear the contents of a specified outbound (`.qo`/`.qos`) Notefile, deleting all pending Notes. Args: card (Notecard): The current Notecard object. diff --git a/notecard/hub.py b/notecard/hub.py index bcfc73f..20327a0 100644 --- a/notecard/hub.py +++ b/notecard/hub.py @@ -51,27 +51,27 @@ def log(card, alert=None, sync=None, text=None): @validate_card_object def set(card, align=None, details=None, duration=None, host=None, inbound=None, mode=None, off=None, on=None, outbound=None, product=None, seconds=None, sn=None, sync=None, umin=None, uoff=None, uperiodic=None, version=None, vinbound=None, voutbound=None): - """Use hub.set request is the primary method for controlling the Notecard's Notehub connection and sync behavior. + r"""Use hub.set request is the primary method for controlling the Notecard's Notehub connection and sync behavior. Args: card (Notecard): The current Notecard object. align (bool): Use `true` to align syncs on a regular time-periodic cycle. - details (str): When using Notecard LoRa you can use this argument to provide information about an alternative LoRaWAN server or service you would like the Notecard to use. Use `"-"` to reset a Notecard's LoRaWAN details to its default values. - duration (int): When in `continuous` mode, the amount of time, in minutes, of each session (the minimum allowed value is `15`). + details (str): When using Notecard LoRa you can use this argument to provide information about an alternative LoRaWAN server or service you would like the Notecard to use. The argument you provide must be a JSON object with three keys, "deveui", "appeui", and "appkey", all of which are hexadecimal strings with no leading 0x. For example: `{"deveui":"0080E11500088B37","appeui":"6E6F746563617264","appkey":"00088B37"}` The LoRaWAN details you send to a Notecard become part of its permanent configuration, and survive factory resets. You can reset a Notecard's LoRaWAN details to its default values by providing a `"-"` for the details argument. + duration (int): When in `continuous` mode, the amount of time, in minutes, of each session (the minimum allowed value is `15`). When this time elapses, the Notecard gracefully ends the current session and starts a new one in order to sync session-specific data to Notehub. host (str): The URL of the Notehub service. Use `"-"` to reset to the default value. - inbound (int): The max wait time, in minutes, to sync inbound data from Notehub. Use `-1` to reset the value back to its default of `0`. A value of `0` means that the Notecard will never sync inbound data unless explicitly told to do so. + inbound (int): The max wait time, in minutes, to sync inbound data from Notehub. Explicit syncs (e.g. using `hub.sync`) do not affect this cadence. When in `periodic` or `continuous` mode this argument is required, otherwise the Notecard will function as if it is in `minimum` mode as it pertains to syncing behavior. Use `-1` to reset the value back to its default of `0`. A value of `0` means that the Notecard will never sync inbound data unless explicitly told to do so (e.g. using `hub.sync`). mode (str): The Notecard's synchronization mode. off (bool): Set to `true` to manually instruct the Notecard to resume periodic mode after a web transaction has completed. - on (bool): If in `periodic` mode, used to temporarily switch the Notecard to `continuous` mode to perform a web transaction. - outbound (int): The max wait time, in minutes, to sync outbound data from the Notecard. Use `-1` to reset the value back to its default of `0`. A value of `0` means that the Notecard will never sync outbound data unless explicitly told to do so. - product (str): A Notehub-managed unique identifier that is used to match Devices with Projects. - seconds (int): If in `periodic` mode and using `on` above, the number of seconds to run in continuous mode before switching back to periodic mode. If not set, a default of 300 seconds is used. + on (bool): If in `periodic` mode, used to temporarily switch the Notecard to `continuous` mode to perform a web transaction.\n\nIgnored if the Notecard is already in `continuous` mode or if the Notecard is NOT performing a web transaction. + outbound (int): The max wait time, in minutes, to sync outbound data from the Notecard. Explicit syncs (e.g. using `hub.sync`) do not affect this cadence. When in `periodic` or `continuous` mode this argument is required, otherwise the Notecard will function as if it is in `minimum` mode as it pertains to syncing behavior. Use `-1` to reset the value back to its default of `0`. A value of `0` means that the Notecard will never sync outbound data unless explicitly told to do so (e.g. using `hub.sync`). + product (str): A Notehub-managed unique identifier that is used to match Devices with Projects. This string is used during a device's auto-provisioning to find the Notehub Project that, once provisioned, will securely manage the device and its data. + seconds (int): If in `periodic` mode and using `on` above, the number of seconds to run in continuous mode before switching back to periodic mode. If not set, a default of 300 seconds is used. Ignored if the Notecard is already in continuous mode. sn (str): The end product's serial number. sync (bool): If in `continuous` mode, automatically and immediately sync each time an inbound Notefile change is detected on Notehub. NOTE: The `sync` argument is not supported when a Notecard is in NTN mode. umin (bool): Set to `true` to use USB/line power variable sync behavior, enabling the Notecard to stay in `continuous` mode when connected to USB/line power and fallback to `minimum` mode when disconnected. uoff (bool): Set to `true` to use USB/line power variable sync behavior, enabling the Notecard to stay in `continuous` mode when connected to USB/line power and fallback to `off` mode when disconnected. uperiodic (bool): Set to `true` to use USB/line power variable sync behavior, enabling the Notecard to stay in `continuous` mode when connected to USB/line power and fallback to `periodic` mode when disconnected. - version (str): The version of your host firmware. You may pass a simple version number string, or an object with detailed information about the firmware image. + version (str): The version of your host firmware. The value provided will appear on your device in Notehub under the "Host Firmware" tab. You may pass a simple version number string (e.g. "1.0.0.0"), or an object with detailed information about the firmware image. If you provide an object it must take the following form. `{"org":"my-organization","product":"My Product","description":"A description of the image","version":"1.2.4","built":"Jan 01 2025 01:02:03","vermajor":1,"verminor":2,"verpatch":4,"verbuild": 5,"builder":"The Builder"}` If your project uses Notecard Outboard Firmware Update, you can alternatively use the `dfu.status` request to set your host firmware version. vinbound (str): Overrides `inbound` with a voltage-variable value. Use `"-"` to clear this value. NOTE: Setting voltage-variable values is not supported on Notecard XP. voutbound (str): Overrides `outbound` with a voltage-variable value. Use `"-"` to clear this value. NOTE: Setting voltage-variable values is not supported on Notecard XP. @@ -122,7 +122,7 @@ def set(card, align=None, details=None, duration=None, host=None, inbound=None, @validate_card_object def signal(card, seconds=None): - """Receive a Signal (a near-real-time note) from Notehub. + """Receive a Signal (a near-real-time note) from Notehub. This request checks for an inbound signal from Notehub. If it finds a signal, this request returns the signal's body and deletes the signal. If there are multiple signals to receive, this request reads and deletes signals in FIFO (first in first out) order. Args: card (Notecard): The current Notecard object. diff --git a/notecard/note.py b/notecard/note.py index fec6821..eaf0a92 100644 --- a/notecard/note.py +++ b/notecard/note.py @@ -18,14 +18,14 @@ def add(card, file=None, note=None, body=None, payload=None, sync=None, key=None Args: card (Notecard): The current Notecard object. - file (str): The name of the Notefile. On Notecard LoRa this argument is required. On all other Notecards this field is optional and defaults to `data.qo` if not provided. When using this request on the Notecard the Notefile name must end in one of: `.qo` for a queue outgoing (Notecard to Notehub) with plaintext transport, `.qos` for a queue outgoing with encrypted transport, `.db` for a bidirectionally synchronized database with plaintext transport, `.dbs` for a bidirectionally synchronized database with encrypted transport, `.dbx` for a local-only database. + file (str): The name of the Notefile. On Notecard LoRa this argument is required. On all other Notecards this field is optional and defaults to `data.qo` if not provided. When using this request on the Notecard the Notefile name must end in one of: `.qo` for a queue outgoing (Notecard to Notehub) with plaintext transport `.qos` for a queue outgoing with encrypted transport `.db` for a bidirectionally synchronized database with plaintext transport `.dbs` for a bidirectionally synchronized database with encrypted transport `.dbx` for a local-only database note (str): If the Notefile has a `.db/.dbs/.dbx` extension, specifies a unique Note ID. If `note` string is "?", then a random unique Note ID is generated and returned as `{"note":"xxx"}`. If this argument is provided for a `.qo` Notefile, an error is returned. body (dict): A JSON object to be enqueued. A note must have either a `body` or a `payload`, and can have both. payload (str): A base64-encoded binary payload. A note must have either a `body` or a `payload`, and can have both. If a Note template is not in use, payloads are limited to 250 bytes. sync (bool): Set to `true` to sync immediately. Only applies to outgoing Notecard requests, and only guarantees syncing the specified Notefile. Auto-syncing incoming Notes from Notehub is set on the Notecard with `{"req": "hub.set", "mode":"continuous", "sync": true}`. key (str): The name of an environment variable in your Notehub.io project that contains the contents of a public key. Used when encrypting the Note body for transport. verify (bool): If set to `true` and using a templated Notefile, the Notefile will be written to flash immediately, rather than being cached in RAM and written to flash later. - binary (bool): If `true`, the Notecard will send all the data in the binary buffer to Notehub. + binary (bool): If `true`, the Notecard will send all the data in the binary buffer to Notehub. Learn more in this guide on Sending and Receiving Large Binary Objects. live (bool): If `true`, bypasses saving the Note to flash on the Notecard. Required to be set to `true` if also using `"binary":true`. full (bool): If set to `true`, and the Note is using a Notefile Template, the Note will bypass usage of omitempty and retain `null`, `0`, `false`, and empty string `""` values. limit (bool): If set to `true`, the Note will not be created if Notecard is in a penalty box. @@ -125,7 +125,7 @@ def delete(card, file, note, verify=None): @validate_card_object def get(card, file=None, note=None, delete=None, deleted=None, decrypt=None): - """Retrieve a Note from a Notefile. The file must either be a DB Notefile or inbound queue file (see `file` argument below). + """Retrieve a Note from a Notefile. The file must either be a DB Notefile or inbound queue file (see `file` argument below). `.qo`/`.qos` Notes must be read from the Notehub event table using the Notehub Event API. Args: card (Notecard): The current Notecard object. @@ -154,7 +154,7 @@ def get(card, file=None, note=None, delete=None, deleted=None, decrypt=None): @validate_card_object def template(card, file, body=None, length=None, verify=None, format=None, port=None, delete=None): - """By using the `note.template` request with any `.qo`/`.qos` Notefile, developers can provide the Notecard with a schema of sorts to apply to future Notes added to the Notefile. This template acts as a hint to the Notecard that allows it to internally store data as fixed-length binary records rather than as flexible JSON objects which require much more memory. Using templated Notes in place of regular Notes increases the storage and sync capability of the Notecard by an order of magnitude. + """By using the `note.template` request with any `.qo`/`.qos` Notefile, developers can provide the Notecard with a schema of sorts to apply to future Notes added to the Notefile. This template acts as a hint to the Notecard that allows it to internally store data as fixed-length binary records rather than as flexible JSON objects which require much more memory. Using templated Notes in place of regular Notes increases the storage and sync capability of the Notecard by an order of magnitude. Read about Working with Note Templates for additional information. Args: card (Notecard): The current Notecard object. diff --git a/notecard/var.py b/notecard/var.py index 6a652dd..607f17d 100644 --- a/notecard/var.py +++ b/notecard/var.py @@ -14,7 +14,7 @@ @validate_card_object def delete(card, name=None, file=None): - """Delete a Note from a DB Notefile by its name. Provides a simpler interface to the note.delete API. + """Delete a Note from a DB Notefile by its `name`. Provides a simpler interface to the note.delete API. Args: card (Notecard): The current Notecard object. diff --git a/notecard/web.py b/notecard/web.py index a641efb..60dd5b0 100644 --- a/notecard/web.py +++ b/notecard/web.py @@ -59,7 +59,7 @@ def get(card, route=None, name=None, body=None, content=None, seconds=None, asyn content (str): The MIME type of the body or payload of the response. Default is `application/json`. seconds (int): If specified, overrides the default 90 second timeout. async_ (bool): If `true`, the Notecard performs the web request asynchronously, and returns control to the host without waiting for a response from Notehub. - binary (bool): If `true`, the Notecard will return the response stored in its binary buffer. + binary (bool): If `true`, the Notecard will return the response stored in its binary buffer. Learn more in this guide on Sending and Receiving Large Binary Objects. offset (int): Used along with `binary:true` and `max`, sent as a URL parameter to the remote endpoint. Represents the number of bytes to offset the binary payload from 0 when retrieving binary data from the remote endpoint. max (int): Used along with `binary:true` and `offset`, sent as a URL parameter to the remote endpoint. Represents the number of bytes to retrieve from the binary payload segment. file (str): The name of the local-only Database Notefile (`.dbx`) to be used if the web request is issued asynchronously and you wish to store the response. From 7582e01f2357a1abfe4e8b48a92c00351ded7ce5 Mon Sep 17 00:00:00 2001 From: Alex Bucknall Date: Fri, 22 Aug 2025 11:46:16 +0100 Subject: [PATCH 4/7] chore: bump version --- pyproject.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pyproject.toml b/pyproject.toml index fe7f4d3..9a8d1da 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -4,7 +4,7 @@ build-backend = "setuptools.build_meta" [project] name = "note-python" -version = "1.6.1" +version = "2.1.1" description = "Cross-platform Python Library for the Blues Wireless Notecard" authors = [ {name = "Blues Inc.", email = "support@blues.com"}, From 963691d61d75062e36d56d0f6227cc7a77c9e974 Mon Sep 17 00:00:00 2001 From: Alex Bucknall Date: Wed, 27 Aug 2025 16:05:38 +0100 Subject: [PATCH 5/7] fix: updates to latest schema --- notecard/card.py | 412 +++++++++++++++++++++++------------------------ notecard/dfu.py | 26 +-- notecard/hub.py | 2 +- notecard/note.py | 126 +++++++-------- notecard/ntn.py | 8 +- notecard/var.py | 36 ++--- notecard/web.py | 202 +++++++++++------------ 7 files changed, 406 insertions(+), 406 deletions(-) diff --git a/notecard/card.py b/notecard/card.py index da7d45a..920cd24 100644 --- a/notecard/card.py +++ b/notecard/card.py @@ -14,7 +14,7 @@ @validate_card_object def attn(card, files=None, mode=None, off=None, on=None, payload=None, seconds=None, start=None, verify=None): - """Configure hardware notification from the Notecard to MCU host. NOTE: Requires a connection between the Notecard ATTN pin and a GPIO pin on the host MCU. + """Configure hardware notifications from a Notecard to a host MCU. NOTE: Requires a connection between the Notecard ATTN pin and a GPIO pin on the host MCU. Args: card (Notecard): The current Notecard object. @@ -51,108 +51,108 @@ def attn(card, files=None, mode=None, off=None, on=None, payload=None, seconds=N @validate_card_object -def aux(card, mode=None, usage=None, seconds=None, max=None, start=None, gps=None, rate=None, sync=None, file=None, connected=None, limit=None, sensitivity=None, ms=None, count=None, offset=None): +def aux(card, connected=None, count=None, file=None, gps=None, limit=None, max=None, mode=None, ms=None, offset=None, rate=None, seconds=None, sensitivity=None, start=None, sync=None, usage=None): """Configure various uses of the general-purpose I/O (GPIO) pins `AUX1`-`AUX4` on the Notecard edge connector for tracking applications and simple GPIO sensing and counting tasks. Args: card (Notecard): The current Notecard object. - mode (str): The AUX mode. Must be one of the following keywords. Some keywords are only supported on certain types of Notecards. - usage (list): An ordered list of pin modes for each AUX pin when in GPIO mode. - seconds (int): When in `gpio` mode, if an `AUX` pin is configured as a `count` type, the count of rising edges can be broken into samples of this duration. Passing `0` or omitting this field will total into a single sample. - max (int): When in `gpio` mode, if an `AUX` pin is configured as a `count` type, the maximum number of samples of duration `seconds`, after which all subsequent counts are added to the final sample. Passing `0` or omitting this value will provide a single incrementing count of rising edges on the pin. - start (bool): When in `gpio` mode, if an `AUX` pin is configured as a `count` type, set to `true` to reset counters and start incrementing. - gps (bool): If `true`, along with `"mode":"track"` the Notecard supports the use of an external GPS module. This argument is deprecated. Use the `card.aux.serial` request with a `mode` of `"gps"` instead. - rate (int): The AUX UART baud rate for debug communication over the AUXRX and AUXTX pins. - sync (bool): If `true`, for pins set as `input` by `usage`, the Notecard will autonomously report any state changes as new notes in `file`. For pins used as `counter`, the Notecard will use an interrupt to count pulses and will report the total in a new note in `file` unless it has been noted in the previous second. - file (str): The name of the Notefile used to report state changes when used in conjunction with `"sync": true`. Default Notefile name is `_button.qo`. connected (bool): If `true`, defers the sync of the state change Notefile to the next sync as configured by the `hub.set` request. + count (int): When used with `"mode":"neo-monitor"` or `"mode":"track-neo-monitor"`, this controls the number of NeoPixels to use in a strip. Possible values are `1`, `2`, or `5`. + file (str): The name of the Notefile used to report state changes when used in conjunction with `"sync": true`. Default Notefile name is `_button.qo`. + gps (bool): If `true`, along with `"mode":"track"` the Notecard supports the use of an external GPS module. This argument is deprecated. Use the `card.aux.serial` request with a `mode` of `"gps"` instead. limit (bool): If `true`, along with `"mode":"track"` and `gps:true` the Notecard will disable concurrent modem use during GPS tracking. - sensitivity (int): When used with `"mode":"neo-monitor"` or `"mode":"track-neo-monitor"`, this controls the brightness of NeoPixel lights, where `100` is the maximum brightness and `1` is the minimum. + max (int): When in `gpio` mode, if an `AUX` pin is configured as a `count` type, the maximum number of samples of duration `seconds`, after which all subsequent counts are added to the final sample. Passing `0` or omitting this value will provide a single incrementing count of rising edges on the pin. + mode (str): The AUX mode. Must be one of the following keywords. Some keywords are only supported on certain types of Notecards. ms (int): When in `gpio` mode, this argument configures a debouncing interval. With a debouncing interval in place, the Notecard excludes all transitions with a shorter duration than the provided debounce time, in milliseconds. This interval only applies to GPIOs configured with a `usage` of `count`, `count-pulldown`, or `count-pullup`. - count (int): When used with `"mode":"neo-monitor"` or `"mode":"track-neo-monitor"`, this controls the number of NeoPixels to use in a strip. Possible values are `1`, `2`, or `5`. offset (int): When used with `"mode":"neo-monitor"` or `"mode":"track-neo-monitor"`, this is the 1-based index in a strip of NeoPixels that determines which single NeoPixel the host can command. + rate (int): The AUX UART baud rate for debug communication over the AUXRX and AUXTX pins. + seconds (int): When in `gpio` mode, if an `AUX` pin is configured as a `count` type, the count of rising edges can be broken into samples of this duration. Passing `0` or omitting this field will total into a single sample. + sensitivity (int): When used with `"mode":"neo-monitor"` or `"mode":"track-neo-monitor"`, this controls the brightness of NeoPixel lights, where `100` is the maximum brightness and `1` is the minimum. + start (bool): When in `gpio` mode, if an `AUX` pin is configured as a `count` type, set to `true` to reset counters and start incrementing. + sync (bool): If `true`, for pins set as `input` by `usage`, the Notecard will autonomously report any state changes as new notes in `file`. For pins used as `counter`, the Notecard will use an interrupt to count pulses and will report the total in a new note in `file` unless it has been noted in the previous second. + usage (list): An ordered list of pin modes for each AUX pin when in GPIO mode. Returns: dict: The result of the Notecard request. """ req = {"req": "card.aux"} - if mode: - req["mode"] = mode - if usage: - req["usage"] = usage - if seconds is not None: - req["seconds"] = seconds - if max is not None: - req["max"] = max - if start is not None: - req["start"] = start - if gps is not None: - req["gps"] = gps - if rate is not None: - req["rate"] = rate - if sync is not None: - req["sync"] = sync - if file: - req["file"] = file if connected is not None: req["connected"] = connected + if count is not None: + req["count"] = count + if file: + req["file"] = file + if gps is not None: + req["gps"] = gps if limit is not None: req["limit"] = limit - if sensitivity is not None: - req["sensitivity"] = sensitivity + if max is not None: + req["max"] = max + if mode: + req["mode"] = mode if ms is not None: req["ms"] = ms - if count is not None: - req["count"] = count if offset is not None: req["offset"] = offset + if rate is not None: + req["rate"] = rate + if seconds is not None: + req["seconds"] = seconds + if sensitivity is not None: + req["sensitivity"] = sensitivity + if start is not None: + req["start"] = start + if sync is not None: + req["sync"] = sync + if usage: + req["usage"] = usage return card.Transaction(req) @validate_card_object -def auxSerial(card, mode=None, duration=None, rate=None, limit=None, max=None, ms=None, minutes=None): +def auxSerial(card, duration=None, limit=None, max=None, minutes=None, mode=None, ms=None, rate=None): """Configure various uses of the AUXTX and AUXRX pins on the Notecard's edge connector. Args: card (Notecard): The current Notecard object. - mode (str): The AUX mode. Must be one of the following: duration (int): If using `"mode": "accel"`, specify a sampling duration for the Notecard accelerometer. - rate (int): The baud rate or speed at which information is transmitted over AUX serial. The default is `115200` unless using GPS, in which case the default is `9600`. limit (bool): If `true`, along with `"mode":"gps"` the Notecard will disable concurrent modem use during GPS tracking. max (int): The maximum amount of data to send per session, in bytes. This is typically set to the size of the receive buffer on the host minus `1`. For example, `note-arduino` uses a buffer size of `(SERIALRXBUFFER_SIZE - 1)`. - ms (int): The delay in milliseconds before sending a buffer of `max` size. minutes (int): When using `"mode": "notify,dfu"`, specify an interval for notifying the host. + mode (str): The AUX mode. Must be one of the following: + ms (int): The delay in milliseconds before sending a buffer of `max` size. + rate (int): The baud rate or speed at which information is transmitted over AUX serial. The default is `115200` unless using GPS, in which case the default is `9600`. Returns: dict: The result of the Notecard request. """ req = {"req": "card.aux.serial"} - if mode: - req["mode"] = mode if duration is not None: req["duration"] = duration - if rate is not None: - req["rate"] = rate if limit is not None: req["limit"] = limit if max is not None: req["max"] = max - if ms is not None: - req["ms"] = ms if minutes is not None: req["minutes"] = minutes + if mode: + req["mode"] = mode + if ms is not None: + req["ms"] = ms + if rate is not None: + req["rate"] = rate return card.Transaction(req) @validate_card_object -def binaryGet(card, cobs=None, offset=None, length=None): +def binaryGet(card, cobs=None, length=None, offset=None): """Return binary data stored in the binary storage area of the Notecard. The response to this API command first returns the JSON-formatted response object, then the binary data. See the guide on Sending and Receiving Large Binary Objects for best practices when using `card.binary`. Args: card (Notecard): The current Notecard object. cobs (int): The size of the COBS-encoded data you are expecting to be returned (in bytes). - offset (int): Used along with `length`, the number of bytes to offset the binary payload from 0 when retrieving binary data from the binary storage area of the Notecard. Primarily used when retrieving multiple fragments of a binary payload from the Notecard. length (int): Used along with `offset`, the number of bytes to retrieve from the binary storage area of the Notecard. + offset (int): Used along with `length`, the number of bytes to offset the binary payload from 0 when retrieving binary data from the binary storage area of the Notecard. Primarily used when retrieving multiple fragments of a binary payload from the Notecard. Returns: dict: The result of the Notecard request. @@ -160,31 +160,31 @@ def binaryGet(card, cobs=None, offset=None, length=None): req = {"req": "card.binary.get"} if cobs is not None: req["cobs"] = cobs - if offset is not None: - req["offset"] = offset if length is not None: req["length"] = length + if offset is not None: + req["offset"] = offset return card.Transaction(req) @validate_card_object -def binaryPut(card, offset=None, cobs=None, status=None): +def binaryPut(card, cobs=None, offset=None, status=None): """Add binary data to the binary storage area of the Notecard. The Notecard expects to receive binary data immediately following the usage of this API command. See the guide on Sending and Receiving Large Binary Objects for best practices when using `card.binary`. Args: card (Notecard): The current Notecard object. - offset (int): The number of bytes to offset the binary payload from 0 when appending the binary data to the binary storage area of the Notecard. Primarily used when sending multiple fragments of one binary payload to the Notecard. cobs (int): The size of the COBS-encoded data (in bytes). + offset (int): The number of bytes to offset the binary payload from 0 when appending the binary data to the binary storage area of the Notecard. Primarily used when sending multiple fragments of one binary payload to the Notecard. status (str): The MD5 checksum of the data, before it has been encoded. Returns: dict: The result of the Notecard request. """ req = {"req": "card.binary.put"} - if offset is not None: - req["offset"] = offset if cobs is not None: req["cobs"] = cobs + if offset is not None: + req["offset"] = offset if status: req["status"] = status return card.Transaction(req) @@ -225,63 +225,63 @@ def carrier(card, mode=None): @validate_card_object -def contact(card, name=None, org=None, role=None, email=None): +def contact(card, email=None, name=None, org=None, role=None): """Use to set or retrieve information about the Notecard maintainer. Once set, this information is synced to Notehub. Args: card (Notecard): The current Notecard object. + email (str): Set the email address of the Notecard maintainer. name (str): Set the name of the Notecard maintainer. org (str): Set the organization name of the Notecard maintainer. role (str): Set the role of the Notecard maintainer. - email (str): Set the email address of the Notecard maintainer. Returns: dict: The result of the Notecard request. """ req = {"req": "card.contact"} + if email: + req["email"] = email if name: req["name"] = name if org: req["org"] = org if role: req["role"] = role - if email: - req["email"] = email return card.Transaction(req) @validate_card_object -def dfu(card, name=None, on=None, off=None, seconds=None, stop=None, start=None, mode=None): +def dfu(card, mode=None, name=None, off=None, on=None, seconds=None, start=None, stop=None): """Use to configure a Notecard for Notecard Outboard Firmware Update. Args: card (Notecard): The current Notecard object. + mode (str): The `mode` argument allows you to control whether a Notecard's `AUX` pins (default) or `ALTDFU` pins are used for Notecard Outboard Firmware Update. This argument is only supported on Notecards that have `ALTDFU` pins, which includes all versions of Notecard Cell+WiFi, non-legacy versions of Notecard Cellular, and Notecard WiFi v2. name (str): One of the supported classes of host MCU. Supported MCU classes are `"esp32"`, `"stm32"`, `"stm32-bi"`, `"mcuboot"` (added in v5.3.1), and `"-"`, which resets the configuration. The "bi" in `"stm32-bi"` stands for "boot inverted", and the `"stm32-bi"` option should be used on STM32 family boards where the hardware boot pin is assumed to be active low, instead of active high. Supported MCUs can be found on the Notecarrier F datasheet. - on (bool): Set to `true` to enable Notecard Outboard Firmware Update. off (bool): Set to `true` to disable Notecard Outboard Firmware Update from occurring. + on (bool): Set to `true` to enable Notecard Outboard Firmware Update. seconds (int): When used with `"off":true`, disable Notecard Outboard Firmware Update operations for the specified number of `seconds`. - stop (bool): Set to `true` to disable the host RESET that is normally performed on the host MCU when the Notecard starts up (in order to ensure a clean startup), and also when the Notecard wakes up the host MCU after the expiration of a `card.attn` "sleep" operation. If `true`, the host MCU will not be reset in these two conditions. start (bool): Set to `true` to enable the host RESET if previously disabled with `"stop":true`. - mode (str): The `mode` argument allows you to control whether a Notecard's `AUX` pins (default) or `ALTDFU` pins are used for Notecard Outboard Firmware Update. This argument is only supported on Notecards that have `ALTDFU` pins, which includes all versions of Notecard Cell+WiFi, non-legacy versions of Notecard Cellular, and Notecard WiFi v2. + stop (bool): Set to `true` to disable the host RESET that is normally performed on the host MCU when the Notecard starts up (in order to ensure a clean startup), and also when the Notecard wakes up the host MCU after the expiration of a `card.attn` "sleep" operation. If `true`, the host MCU will not be reset in these two conditions. Returns: dict: The result of the Notecard request. """ req = {"req": "card.dfu"} + if mode: + req["mode"] = mode if name: req["name"] = name - if on is not None: - req["on"] = on if off is not None: req["off"] = off + if on is not None: + req["on"] = on if seconds is not None: req["seconds"] = seconds - if stop is not None: - req["stop"] = stop if start is not None: req["start"] = start - if mode: - req["mode"] = mode + if stop is not None: + req["stop"] = stop return card.Transaction(req) @@ -306,7 +306,7 @@ def io(card, i2c=None, mode=None): Args: card (Notecard): The current Notecard object. i2c (int): The alternate address to use for I2C communication. Pass `-1` to reset to the default address - mode (str): The mode parameter. + mode (str): Used to control the Notecard's IO behavior, including USB port, LED, I2C master, NTN fallback. Returns: dict: The result of the Notecard request. @@ -320,14 +320,14 @@ def io(card, i2c=None, mode=None): @validate_card_object -def led(card, mode=None, on=None, off=None): +def led(card, mode=None, off=None, on=None): """Use along with the card.aux API to turn connected LEDs on/off or to manage a single connected NeoPixel. If using monochromatic LEDs, they must be wired according to the instructions provided in the guide on Using Monitor Mode. Please note that the use of monochromatic LEDs is not supported by Notecard for LoRa. If using NeoPixels, the NeoPixel (or strip of NeoPixels) must be wired according to the instructions provided in the guide on Using Neo-Monitor Mode. Args: card (Notecard): The current Notecard object. - mode (str): Used to specify the color of the LED to turn on or off. For LEDs, possible values are `"red"`, `"green"`, and `"yellow"`. For NeoPixels, possible values are `"red"`, `"green"`, `"blue"`, `"yellow"`, `"cyan"`, `"magenta"`, `"orange"`, `"white"`, and `"gray"`. - on (bool): Set to `true` to turn the specified LED or NeoPixel on. + mode (str): Used to specify the color of the LED to turn on or off. Note: Notecard LoRa does not support monochromatic LED modes, only NeoPixels. off (bool): Set to `true` to turn the specified LED or NeoPixel off. + on (bool): Set to `true` to turn the specified LED or NeoPixel on. Returns: dict: The result of the Notecard request. @@ -335,51 +335,51 @@ def led(card, mode=None, on=None, off=None): req = {"req": "card.led"} if mode: req["mode"] = mode - if on is not None: - req["on"] = on if off is not None: req["off"] = off + if on is not None: + req["on"] = on return card.Transaction(req) @validate_card_object -def locationMode(card, mode=None, seconds=None, vseconds=None, lat=None, lon=None, max=None, delete=None, minutes=None, threshold=None): +def locationMode(card, delete=None, lat=None, lon=None, max=None, minutes=None, mode=None, seconds=None, threshold=None, vseconds=None): """Set location-related configuration settings. Retrieves the current location mode when passed with no argument. Args: card (Notecard): The current Notecard object. - mode (str): Must be one of: `""` to retrieve the current mode. `"off"` to turn location mode off. Approximate location may still be ascertained from Notehub. `"periodic"` to sample location at a specified interval, if the device has moved. `"continuous"` to enable the Notecard's GPS/GNSS module for continuous sampling. When in continuous mode the Notecard samples a new GPS/GNSS reading for every new Note. `"fixed"` to report the location as a fixed location using the specified `lat` and `lon` coordinates. This is the only supported mode on Notecard LoRa. - seconds (int): When in `periodic` mode, location will be sampled at this interval, if the Notecard detects motion. If seconds is < 300, during periods of sustained movement the Notecard will leave its onboard GPS/GNSS on continuously to avoid powering the module on and off repeatedly. - vseconds (str): In `periodic` mode, overrides `seconds` with a voltage-variable value. + delete (bool): Set to `true` to delete the last known location stored in the Notecard. lat (float): When in periodic or continuous mode, providing this value enables geofencing. The value you provide for this argument should be the latitude of the center of the geofence, in degrees. When in fixed mode, the value you provide for this argument should be the latitude location of the device itself, in degrees. lon (float): When in periodic or continuous mode, providing this value enables geofencing. The value you provide for this argument should be the longitude of the center of the geofence, in degrees. When in fixed mode, the value you provide for this argument should be the longitude location of the device itself, in degrees. max (int): Meters from a geofence center. Used to enable geofence location tracking. - delete (bool): Set to `true` to delete the last known location stored in the Notecard. minutes (int): When geofence is enabled, the number of minutes the device should be outside the geofence before the Notecard location is tracked. + mode (str): Sets the location mode. + seconds (int): When in `periodic` mode, location will be sampled at this interval, if the Notecard detects motion. If seconds is < 300, during periods of sustained movement the Notecard will leave its onboard GPS/GNSS on continuously to avoid powering the module on and off repeatedly. threshold (int): When in `periodic` mode, the number of motion events (registered by the built-in accelerometer) required to trigger GPS to turn on. + vseconds (str): In `periodic` mode, overrides `seconds` with a voltage-variable value. Returns: dict: The result of the Notecard request. """ req = {"req": "card.location.mode"} - if mode: - req["mode"] = mode - if seconds is not None: - req["seconds"] = seconds - if vseconds: - req["vseconds"] = vseconds + if delete is not None: + req["delete"] = delete if lat is not None: req["lat"] = lat if lon is not None: req["lon"] = lon if max is not None: req["max"] = max - if delete is not None: - req["delete"] = delete if minutes is not None: req["minutes"] = minutes + if mode: + req["mode"] = mode + if seconds is not None: + req["seconds"] = seconds if threshold is not None: req["threshold"] = threshold + if vseconds: + req["vseconds"] = vseconds return card.Transaction(req) @@ -398,89 +398,89 @@ def location(card): @validate_card_object -def locationTrack(card, start=None, heartbeat=None, hours=None, sync=None, stop=None, file=None, payload=None): +def locationTrack(card, file=None, heartbeat=None, hours=None, payload=None, start=None, stop=None, sync=None): """Store location data in a Notefile at the `periodic` interval, or using a specified `heartbeat`. This request is only available when the `card.location.mode` request has been set to `periodic`—e.g. `{"req":"card.location.mode","mode":"periodic","seconds":300}`. If you want to track and transmit data simultaneously consider using an external GPS/GNSS module with the Notecard. If you connect a BME280 sensor on the I2C bus, Notecard will include a temperature, humidity, and pressure reading with each captured Note. If you connect an ENS210 sensor on the I2C bus, Notecard will include a temperature and pressure reading with each captured Note. Learn more in _track.qo. Args: card (Notecard): The current Notecard object. - start (bool): Set to `true` to start Notefile tracking. + file (str): The Notefile in which to store tracked location data. See the `_track.qo` Notefile's documentation for details on the format of the data captured. heartbeat (bool): When `start` is `true`, set to `true` to enable tracking even when motion is not detected. If using `heartbeat`, also set the `hours` below. hours (int): If `heartbeat` is true, add a heartbeat entry at this hourly interval. Use a negative integer to specify a heartbeat in minutes instead of hours. - sync (bool): Set to `true` to perform an immediate sync to the Notehub each time a new Note is added. - stop (bool): Set to `true` to stop Notefile tracking. - file (str): The Notefile in which to store tracked location data. See the `_track.qo` Notefile's documentation for details on the format of the data captured. payload (str): A base64-encoded binary payload to be included in the next `_track.qo` Note. See the guide on Sampling at Predefined Intervals for more details. + start (bool): Set to `true` to start Notefile tracking. + stop (bool): Set to `true` to stop Notefile tracking. + sync (bool): Set to `true` to perform an immediate sync to the Notehub each time a new Note is added. Returns: dict: The result of the Notecard request. """ req = {"req": "card.location.track"} - if start is not None: - req["start"] = start + if file: + req["file"] = file if heartbeat is not None: req["heartbeat"] = heartbeat if hours is not None: req["hours"] = hours - if sync is not None: - req["sync"] = sync - if stop is not None: - req["stop"] = stop - if file: - req["file"] = file if payload: req["payload"] = payload + if start is not None: + req["start"] = start + if stop is not None: + req["stop"] = stop + if sync is not None: + req["sync"] = sync return card.Transaction(req) @validate_card_object -def monitor(card, mode=None, count=None, usb=None): +def monitor(card, count=None, mode=None, usb=None): """When a Notecard is in monitor mode, this API is used to configure the general-purpose `AUX1`-`AUX4` pins to test and monitor Notecard activity. Args: card (Notecard): The current Notecard object. - mode (str): Can be set to one of `green`, `red` or `yellow` to temporarily override the behavior of an AUX pin LED. See Using Monitor Mode for additional details. count (int): The number of pulses to send to the overridden AUX pin LED. Set this value to `0` to return the LED to its default behavior. + mode (str): Can be set to one of `green`, `red` or `yellow` to temporarily override the behavior of an AUX pin LED. See Using Monitor Mode for additional details. usb (bool): Set to `true` to configure LED behavior so that it is only active when the Notecard is connected to USB power. Returns: dict: The result of the Notecard request. """ req = {"req": "card.monitor"} - if mode: - req["mode"] = mode if count is not None: req["count"] = count + if mode: + req["mode"] = mode if usb is not None: req["usb"] = usb return card.Transaction(req) @validate_card_object -def motionMode(card, start=None, stop=None, seconds=None, sensitivity=None, motion=None): +def motionMode(card, motion=None, seconds=None, sensitivity=None, start=None, stop=None): """Configure accelerometer motion monitoring parameters used when providing results to `card.motion`. Args: card (Notecard): The current Notecard object. - start (bool): `true` to enable the Notecard accelerometer and start motion tracking. - stop (bool): `true` to disable the Notecard accelerometer and stop motion tracking. + motion (int): If `motion` is > 0, a card.motion request will return a `"mode"` of `"moving"` or `"stopped"`. The `motion` value is the threshold for how many motion events in a single bucket will trigger a motion status change. Learn how to configure this feature in this guide. seconds (int): Period for each bucket of movements to be accumulated when `minutes` is used with `card.motion`. sensitivity (int): Used to set the accelerometer sample rate. The default sample rate of 1.6Hz could miss short-duration accelerations (e.g. bumps and jolts), and free fall detection may not work reliably with short falls. The penalty for increasing the sample rate to 25Hz is increased current consumption by ~1.5uA relative to the default `-1` setting. - motion (int): If `motion` is > 0, a card.motion request will return a `"mode"` of `"moving"` or `"stopped"`. The `motion` value is the threshold for how many motion events in a single bucket will trigger a motion status change. + start (bool): `true` to enable the Notecard accelerometer and start motion tracking. + stop (bool): `true` to disable the Notecard accelerometer and stop motion tracking. Returns: dict: The result of the Notecard request. """ req = {"req": "card.motion.mode"} - if start is not None: - req["start"] = start - if stop is not None: - req["stop"] = stop + if motion is not None: + req["motion"] = motion if seconds is not None: req["seconds"] = seconds if sensitivity is not None: req["sensitivity"] = sensitivity - if motion is not None: - req["motion"] = motion + if start is not None: + req["start"] = start + if stop is not None: + req["stop"] = stop return card.Transaction(req) @@ -502,86 +502,86 @@ def motion(card, minutes=None): @validate_card_object -def motionSync(card, start=None, stop=None, minutes=None, count=None, threshold=None): +def motionSync(card, count=None, minutes=None, start=None, stop=None, threshold=None): """Configure automatic sync triggered by Notecard movement. Args: card (Notecard): The current Notecard object. + count (int): The number of most recent motion buckets to examine. + minutes (int): The maximum frequency at which sync will be triggered. Even if a `threshold` is set and exceeded, there will only be a single sync for this amount of time. start (bool): `true` to start motion-triggered syncing. stop (bool): `true` to stop motion-triggered syncing. - minutes (int): The maximum frequency at which sync will be triggered. Even if a `threshold` is set and exceeded, there will only be a single sync for this amount of time. - count (int): The number of most recent motion buckets to examine. threshold (int): The number of buckets that must indicate motion in order to trigger a sync. If set to `0`, the Notecard will only perform a sync when its orientation changes. Returns: dict: The result of the Notecard request. """ req = {"req": "card.motion.sync"} + if count is not None: + req["count"] = count + if minutes is not None: + req["minutes"] = minutes if start is not None: req["start"] = start if stop is not None: req["stop"] = stop - if minutes is not None: - req["minutes"] = minutes - if count is not None: - req["count"] = count if threshold is not None: req["threshold"] = threshold return card.Transaction(req) @validate_card_object -def motionTrack(card, start=None, stop=None, minutes=None, count=None, threshold=None, file=None, now=None): +def motionTrack(card, count=None, file=None, minutes=None, now=None, start=None, stop=None, threshold=None): """Configure automatic capture of Notecard accelerometer motion in a Notefile. Args: card (Notecard): The current Notecard object. - start (bool): `true` to start motion capture. - stop (bool): `true` to stop motion capture. - minutes (int): The maximum period to capture Notes in the Notefile. count (int): The number of most recent motion buckets to examine. - threshold (int): The number of buckets that must indicate motion in order to capture. file (str): The Notefile to use for motion capture Notes. See the `_motion.qo` Notefile's documentation for details on the format of the data captured. + minutes (int): The maximum period to capture Notes in the Notefile. now (bool): Set to `true` to trigger the immediate creation of a `_motion.qo` event if the orientation of the Notecard changes (overriding the `minutes` setting). + start (bool): `true` to start motion capture. + stop (bool): `true` to stop motion capture. + threshold (int): The number of buckets that must indicate motion in order to capture. Returns: dict: The result of the Notecard request. """ req = {"req": "card.motion.track"} - if start is not None: - req["start"] = start - if stop is not None: - req["stop"] = stop - if minutes is not None: - req["minutes"] = minutes if count is not None: req["count"] = count - if threshold is not None: - req["threshold"] = threshold if file: req["file"] = file + if minutes is not None: + req["minutes"] = minutes if now is not None: req["now"] = now + if start is not None: + req["start"] = start + if stop is not None: + req["stop"] = stop + if threshold is not None: + req["threshold"] = threshold return card.Transaction(req) @validate_card_object -def random(card, mode=None, count=None): - """Obtain a single random 32 bit unsigned integer modulo `count` or `count` bytes of random data from the Notecard hardware random number generator. +def random(card, count=None, mode=None): + """Obtain a single random 32 bit unsigned integer modulo or `count` number of bytes of random data from the Notecard hardware random number generator. Args: card (Notecard): The current Notecard object. - mode (str): Accepts a single value `"payload"` and, if specified, uses the `count` value to determine the number of bytes of random data to generate and return to the host. count (int): If the `mode` argument is excluded from the request, the Notecard uses this as an upper-limit parameter and returns a random unsigned 32 bit integer between zero and the value provided. If `"mode":"payload"` is used, this argument sets the number of random bytes of data to return in a base64-encoded buffer from the Notecard. + mode (str): Accepts a single value `"payload"` and, if specified, uses the `count` value to determine the number of bytes of random data to generate and return to the host. Returns: dict: The result of the Notecard request. """ req = {"req": "card.random"} - if mode: - req["mode"] = mode if count is not None: req["count"] = count + if mode: + req["mode"] = mode return card.Transaction(req) @@ -620,48 +620,48 @@ def restart(card): @validate_card_object -def sleep(card, on=None, off=None, seconds=None, mode=None): - """Only valid when used with Notecard WiFi v2. Allows the ESP32-based Notecard WiFi v2 to fall back to a low current draw when idle (this behavior differs from the STM32-based Notecards that have a `STOP` mode where UART and I2C may still operate). Note that the Notecard WiFi v2 will not enable a "sleep" mode while plugged in via USB. Read more in the guide on using Deep Sleep Mode on Notecard WiFi v2. +def sleep(card, mode=None, off=None, on=None, seconds=None): + """Allows the ESP32-based Notecard WiFi v2 to fall back to a low current draw when idle (this behavior differs from the STM32-based Notecards that have a `STOP` mode where UART and I2C may still operate). Note that the Notecard WiFi v2 will not enable a "sleep" mode while plugged in via USB. Read more in the guide on using Deep Sleep Mode on Notecard WiFi v2. Args: card (Notecard): The current Notecard object. - on (bool): Set to `true` to enable the Notecard WiFi v2 to sleep once it is idle for >= 30 seconds. - off (bool): Set to `true` to disable the sleep mode on the Notecard WiFi v2. - seconds (int): The number of seconds the Notecard will wait before entering sleep mode (minimum value is 30). mode (str): Set to `"accel"` to wake from deep sleep on any movement detected by the onboard accelerometer. Set to `"-accel"` to reset to the default setting. + off (bool): Set to `true` to disable the sleep mode on Notecard. + on (bool): Set to `true` to enable Notecard to sleep once it is idle for >= 30 seconds. + seconds (int): The number of seconds the Notecard will wait before entering sleep mode (minimum value is 30). Returns: dict: The result of the Notecard request. """ req = {"req": "card.sleep"} - if on is not None: - req["on"] = on + if mode: + req["mode"] = mode if off is not None: req["off"] = off + if on is not None: + req["on"] = on if seconds is not None: req["seconds"] = seconds - if mode: - req["mode"] = mode return card.Transaction(req) @validate_card_object -def restore(card, delete=None, connected=None): +def restore(card, connected=None, delete=None): """Perform a factory reset on the Notecard and restarts. Sending this request without either of the optional arguments below will only reset the Notecard's file system, thus forcing a re-sync of all Notefiles from Notehub. On Notecard LoRa there is no option to retain configuration settings, and providing `"delete": true` is required. The Notecard LoRa retains LoRaWAN configuration after factory resets. Args: card (Notecard): The current Notecard object. - delete (bool): Set to `true` to reset most Notecard configuration settings. Note that this does not reset stored Wi-Fi credentials or the alternate I2C address (if previously set) so the Notecard can still contact the network after a reset. The Notecard will be unable to sync with Notehub until the `ProductUID` is set again. connected (bool): Set to `true` to reset the Notecard on Notehub. This will delete and deprovision the Notecard from Notehub the next time the Notecard connects. This also removes any Notefile templates used by this device. Conversely, if `connected` is `false` (or omitted), the Notecard's settings and data will be restored from Notehub the next time the Notecard connects to the previously used Notehub project. + delete (bool): Set to `true` to reset most Notecard configuration settings. Note that this does not reset stored Wi-Fi credentials or the alternate I2C address (if previously set) so the Notecard can still contact the network after a reset. The Notecard will be unable to sync with Notehub until the `ProductUID` is set again. Returns: dict: The result of the Notecard request. """ req = {"req": "card.restore"} - if delete is not None: - req["delete"] = delete if connected is not None: req["connected"] = connected + if delete is not None: + req["delete"] = delete return card.Transaction(req) @@ -737,63 +737,63 @@ def trace(card, mode=None): @validate_card_object -def transport(card, method=None, seconds=None, allow=None, umin=None): +def transport(card, allow=None, method=None, seconds=None, umin=None): """Specify the connectivity protocol to prioritize on the Notecard Cell+WiFi, or when using NTN mode with Starnote and a compatible Notecard. Args: card (Notecard): The current Notecard object. + allow (bool): Set to `true` to allow adding Notes to non-compact Notefiles while connected over a non-terrestrial network. See Define NTN vs non-NTN Templates. method (str): The connectivity method to enable on the Notecard. seconds (int): The amount of time a Notecard will spend on any fallback transport before retrying the first transport specified in the `method`. The default is `3600` or 60 minutes. - allow (bool): Set to `true` to allow adding Notes to non-compact Notefiles while connected over a non-terrestrial network. See Define NTN vs non-NTN Templates. umin (bool): Set to `true` to force a longer network transport timeout when using Wideband Notecards. Returns: dict: The result of the Notecard request. """ req = {"req": "card.transport"} + if allow is not None: + req["allow"] = allow if method: req["method"] = method if seconds is not None: req["seconds"] = seconds - if allow is not None: - req["allow"] = allow if umin is not None: req["umin"] = umin return card.Transaction(req) @validate_card_object -def triangulate(card, mode=None, on=None, usb=None, set=None, minutes=None, text=None, time=None): +def triangulate(card, minutes=None, mode=None, on=None, set=None, text=None, time=None, usb=None): """Enable or disables a behavior by which the Notecard gathers information about surrounding cell towers and/or Wi-Fi access points with each new Notehub session. Args: card (Notecard): The current Notecard object. + minutes (int): Minimum delay, in minutes, between triangulation attempts. Use `0` for no time-based suppression. mode (str): The triangulation approach to use for determining the Notecard location. The following keywords can be used separately or together in a comma-delimited list, in any order. See Using Cell Tower & Wi-Fi Triangulation for more information. on (bool): `true` to instruct the Notecard to triangulate even if the module has not moved. Only takes effect when `set` is `true`. - usb (bool): `true` to use perform triangulation only when the Notecard is connected to USB power. Only takes effect when `set` is `true`. set (bool): `true` to instruct the module to use the state of the `on` and `usb` arguments. - minutes (int): Minimum delay, in minutes, between triangulation attempts. Use `0` for no time-based suppression. text (str): When using Wi-Fi triangulation, a newline-terminated list of Wi-Fi access points obtained by the external module. Format should follow the ESP32's AT+CWLAP command output. time (int): When passed with `text`, records the time that the Wi-Fi access point scan was performed. If not provided, Notecard time is used. + usb (bool): `true` to use perform triangulation only when the Notecard is connected to USB power. Only takes effect when `set` is `true`. Returns: dict: The result of the Notecard request. """ req = {"req": "card.triangulate"} + if minutes is not None: + req["minutes"] = minutes if mode: req["mode"] = mode if on is not None: req["on"] = on - if usb is not None: - req["usb"] = usb if set is not None: req["set"] = set - if minutes is not None: - req["minutes"] = minutes if text: req["text"] = text if time is not None: req["time"] = time + if usb is not None: + req["usb"] = usb return card.Transaction(req) @@ -858,120 +858,120 @@ def version(card, api=None): @validate_card_object -def voltage(card, hours=None, mode=None, offset=None, vmax=None, vmin=None, name=None, usb=None, alert=None, sync=None, calibration=None, set=None): +def voltage(card, alert=None, calibration=None, hours=None, mode=None, name=None, offset=None, set=None, sync=None, usb=None, vmax=None, vmin=None): """Provide the current V+ voltage level on the Notecard, and provides information about historical voltage trends. When used with the mode argument, configures voltage thresholds based on how the device is powered. Args: card (Notecard): The current Notecard object. + alert (bool): When enabled and the `usb` argument is set to `true`, the Notecard will add an entry to the `health.qo` Notefile when USB power is connected or disconnected. + calibration (float): The offset, in volts, to account for the forward voltage drop of the diode used between the battery and Notecard in either Blues- or customer-designed Notecarriers. hours (int): The number of hours to analyze, up to 720 (30 days). - mode (str): Used to set voltage thresholds based on how the Notecard will be powered. NOTE: Setting voltage thresholds is not supported on the Notecard XP. + mode (str): Used to set voltage thresholds based on how the Notecard will be powered, and which can be used to configure voltage-variable Notecard behavior. Each value is shorthand that assigns a battery voltage reading to a given device state like `high`, `normal`, `low`, and `dead`. NOTE: Setting voltage thresholds is not supported on the Notecard XP. + name (str): Specifies an environment variable to override application default timing values. offset (int): Number of hours to move into the past before starting analysis. + set (bool): Used along with `calibration`, set to `true` to specify a new calibration value. + sync (bool): When enabled and the `usb` argument is set to `true`, the Notecard will perform a sync when USB power is connected or disconnected. + usb (bool): When enabled, the Notecard will monitor for changes to USB power state. vmax (float): Ignore voltage readings above this level when performing calculations. vmin (float): Ignore voltage readings below this level when performing calculations. - name (str): Specifies an environment variable to override application default timing values. - usb (bool): When enabled, the Notecard will monitor for changes to USB power state. - alert (bool): When enabled and the `usb` argument is set to `true`, the Notecard will add an entry to the `health.qo` Notefile when USB power is connected or disconnected. - sync (bool): When enabled and the `usb` argument is set to `true`, the Notecard will perform a sync when USB power is connected or disconnected. - calibration (float): The offset, in volts, to account for the forward voltage drop of the diode used between the battery and Notecard in either Blues- or customer-designed Notecarriers. - set (bool): Used along with `calibration`, set to `true` to specify a new calibration value. Returns: dict: The result of the Notecard request. """ req = {"req": "card.voltage"} + if alert is not None: + req["alert"] = alert + if calibration is not None: + req["calibration"] = calibration if hours is not None: req["hours"] = hours if mode: req["mode"] = mode + if name: + req["name"] = name if offset is not None: req["offset"] = offset + if set is not None: + req["set"] = set + if sync is not None: + req["sync"] = sync + if usb is not None: + req["usb"] = usb if vmax is not None: req["vmax"] = vmax if vmin is not None: req["vmin"] = vmin - if name: - req["name"] = name - if usb is not None: - req["usb"] = usb - if alert is not None: - req["alert"] = alert - if sync is not None: - req["sync"] = sync - if calibration is not None: - req["calibration"] = calibration - if set is not None: - req["set"] = set return card.Transaction(req) @validate_card_object -def wirelessPenalty(card, reset=None, set=None, rate=None, add=None, max=None, min=None): +def wirelessPenalty(card, add=None, max=None, min=None, rate=None, reset=None, set=None): """View the current state of a Notecard Penalty Box, manually remove the Notecard from a penalty box, or override penalty box defaults. Args: card (Notecard): The current Notecard object. - reset (bool): Set to `true` to remove the Notecard from certain types of penalty boxes. - set (bool): Set to `true` to override the default settings of the Network Registration Failure Penalty Box. - rate (float): The rate at which the penalty box time multiplier is increased over successive retries. Used with the `set` argument to override the Network Registration Failure Penalty Box defaults. add (int): The number of minutes to add to successive retries. Used with the `set` argument to override the Network Registration Failure Penalty Box defaults. max (int): The maximum number of minutes that a device can be in a Network Registration Failure Penalty Box. Used with the `set` argument to override the Network Registration Failure Penalty Box defaults. min (int): The number of minutes of the first retry interval of a Network Registration Failure Penalty Box. Used with the `set` argument to override the Network Registration Failure Penalty Box defaults. + rate (float): The rate at which the penalty box time multiplier is increased over successive retries. Used with the `set` argument to override the Network Registration Failure Penalty Box defaults. + reset (bool): Set to `true` to remove the Notecard from certain types of penalty boxes. + set (bool): Set to `true` to override the default settings of the Network Registration Failure Penalty Box. Returns: dict: The result of the Notecard request. """ req = {"req": "card.wireless.penalty"} - if reset is not None: - req["reset"] = reset - if set is not None: - req["set"] = set - if rate is not None: - req["rate"] = rate if add is not None: req["add"] = add if max is not None: req["max"] = max if min is not None: req["min"] = min + if rate is not None: + req["rate"] = rate + if reset is not None: + req["reset"] = reset + if set is not None: + req["set"] = set return card.Transaction(req) @validate_card_object -def wireless(card, mode=None, apn=None, method=None, hours=None): +def wireless(card, apn=None, hours=None, method=None, mode=None): """View the last known network state, or customize the behavior of the modem. Note: Be careful when using this mode with hardware not on hand as a mistake may cause loss of network and Notehub access. Args: card (Notecard): The current Notecard object. - mode (str): Network scan mode. Must be one of: apn (str): Access Point Name (APN) when using an external SIM. Use `"-"` to reset to the Notecard default APN. - method (str): Used when configuring a Notecard to failover to a different SIM. hours (int): When using the `method` argument with `"dual-primary-secondary"` or `"dual-secondary-primary"`, this is the number of hours after which the Notecard will attempt to switch back to the preferred SIM. + method (str): Used when configuring a Notecard to failover to a different SIM. + mode (str): Network scan mode. Must be one of: Returns: dict: The result of the Notecard request. """ req = {"req": "card.wireless"} - if mode: - req["mode"] = mode if apn: req["apn"] = apn - if method: - req["method"] = method if hours is not None: req["hours"] = hours + if method: + req["method"] = method + if mode: + req["mode"] = mode return card.Transaction(req) @validate_card_object -def wifi(card, ssid=None, password=None, name=None, org=None, start=None, text=None): +def wifi(card, name=None, org=None, password=None, ssid=None, start=None, text=None): r"""Set up a Notecard WiFi to connect to a Wi-Fi access point. Args: card (Notecard): The current Notecard object. - ssid (str): The SSID of the Wi-Fi access point. Alternatively, use `-` to clear an already set SSID. - password (str): The network password of the Wi-Fi access point. Alternatively, use `-` to clear an already set password or to connect to an open access point. name (str): By default, the Notecard creates a SoftAP (software enabled access point) under the name "Notecard". You can use the `name` argument to change the name of the SoftAP to a custom name. If you include a `-` at the end of the `name` (for example `"name": "acme-"`), the Notecard will append the last four digits of the network's MAC address (for example `acme-025c`). This allows you to distinguish between multiple Notecards in SoftAP mode. org (str): If specified, replaces the Blues logo on the SoftAP page with the provided name. + password (str): The network password of the Wi-Fi access point. Alternatively, use `-` to clear an already set password or to connect to an open access point. + ssid (str): The SSID of the Wi-Fi access point. Alternatively, use `-` to clear an already set SSID. start (bool): Specify `true` to activate SoftAP mode on the Notecard programmatically. text (str): A string containing an array of access points the Notecard should attempt to use. The access points should be provided in the following format: `["FIRST-SSID","FIRST-PASSWORD"],["SECOND-SSID","SECOND-PASSWORD"]`. You may need to escape any quotes used in this argument before passing it to the Notecard. For example, the following is a valid request to pass to a Notecard through the In-Browser Terminal. `{"req":"card.wifi", "text":"[\"FIRST-SSID\",\"FIRST-PASSWORD\"]"}` @@ -979,14 +979,14 @@ def wifi(card, ssid=None, password=None, name=None, org=None, start=None, text=N dict: The result of the Notecard request. """ req = {"req": "card.wifi"} - if ssid: - req["ssid"] = ssid - if password: - req["password"] = password if name: req["name"] = name if org: req["org"] = org + if password: + req["password"] = password + if ssid: + req["ssid"] = ssid if start is not None: req["start"] = start if text: diff --git a/notecard/dfu.py b/notecard/dfu.py index f52340b..6d12f3e 100644 --- a/notecard/dfu.py +++ b/notecard/dfu.py @@ -33,38 +33,38 @@ def get(card, length=None, offset=None): @validate_card_object -def status(card, name=None, stop=None, status=None, version=None, vvalue=None, on=None, off=None, err=None): +def status(card, err=None, name=None, off=None, on=None, status=None, stop=None, version=None, vvalue=None): """Get and sets the background download status of MCU host or Notecard firmware updates. Args: card (Notecard): The current Notecard object. + err (str): If `err` text is provided along with `"stop":true`, this sets the host DFU to an error state with the specified string. name (str): Determines which type of firmware update status to view. The value can be `"user"` (default), which gets the status of MCU host firmware updates, or `"card"`, which gets the status of Notecard firmware updates. - stop (bool): `true` to clear DFU state and delete the local firmware image from the Notecard. + off (bool): `true` to disable firmware downloads from Notehub. + on (bool): `true` to allow firmware downloads from Notehub. status (str): When setting `stop` to `true`, an optional string synchronized to Notehub, which can be used for informational or diagnostic purposes. + stop (bool): `true` to clear DFU state and delete the local firmware image from the Notecard. version (str): Version information on the host firmware to pass to Notehub. You may pass a simple version number string (e.g. `"1.0.0.0"`), or an object with detailed information about the firmware image (recommended). If you provide an object it must take the following form. `{"org":"my-organization","product":"My Product","description":"A description of the image","version":"1.2.4","built":"Jan 01 2025 01:02:03","vermajor":1,"verminor":2,"verpatch":4,"verbuild": 5,"builder":"The Builder"}` Code to help you generate a version with the correct formatting is available in Enabling Notecard Outboard Firmware Update. vvalue (str): A voltage-variable string that controls, by Notecard voltage, whether or not DFU is enabled. Use a boolean `1` (on) or `0` (off) for each source/voltage level: `usb:<1/0>;high:<1/0>;normal:<1/0>;low:<1/0>;dead:0`. - on (bool): `true` to allow firmware downloads from Notehub. - off (bool): `true` to disable firmware downloads from Notehub. - err (str): If `err` text is provided along with `"stop":true`, this sets the host DFU to an error state with the specified string. Returns: dict: The result of the Notecard request. """ req = {"req": "dfu.status"} + if err: + req["err"] = err if name: req["name"] = name - if stop is not None: - req["stop"] = stop + if off is not None: + req["off"] = off + if on is not None: + req["on"] = on if status: req["status"] = status + if stop is not None: + req["stop"] = stop if version: req["version"] = version if vvalue: req["vvalue"] = vvalue - if on is not None: - req["on"] = on - if off is not None: - req["off"] = off - if err: - req["err"] = err return card.Transaction(req) diff --git a/notecard/hub.py b/notecard/hub.py index 20327a0..c915f89 100644 --- a/notecard/hub.py +++ b/notecard/hub.py @@ -122,7 +122,7 @@ def set(card, align=None, details=None, duration=None, host=None, inbound=None, @validate_card_object def signal(card, seconds=None): - """Receive a Signal (a near-real-time note) from Notehub. This request checks for an inbound signal from Notehub. If it finds a signal, this request returns the signal's body and deletes the signal. If there are multiple signals to receive, this request reads and deletes signals in FIFO (first in first out) order. + """Receive a Signal (a near-real-time Note) from Notehub. This request checks for an inbound signal from Notehub. If it finds a signal, this request returns the signal's body and deletes the signal. If there are multiple signals to receive, this request reads and deletes signals in FIFO (first in first out) order. Args: card (Notecard): The current Notecard object. diff --git a/notecard/note.py b/notecard/note.py index eaf0a92..6b96e49 100644 --- a/notecard/note.py +++ b/notecard/note.py @@ -13,90 +13,90 @@ @validate_card_object -def add(card, file=None, note=None, body=None, payload=None, sync=None, key=None, verify=None, binary=None, live=None, full=None, limit=None, max=None): +def add(card, binary=None, body=None, file=None, full=None, key=None, limit=None, live=None, max=None, note=None, payload=None, sync=None, verify=None): """Add a Note to a Notefile, creating the Notefile if it doesn't yet exist. Args: card (Notecard): The current Notecard object. - file (str): The name of the Notefile. On Notecard LoRa this argument is required. On all other Notecards this field is optional and defaults to `data.qo` if not provided. When using this request on the Notecard the Notefile name must end in one of: `.qo` for a queue outgoing (Notecard to Notehub) with plaintext transport `.qos` for a queue outgoing with encrypted transport `.db` for a bidirectionally synchronized database with plaintext transport `.dbs` for a bidirectionally synchronized database with encrypted transport `.dbx` for a local-only database - note (str): If the Notefile has a `.db/.dbs/.dbx` extension, specifies a unique Note ID. If `note` string is "?", then a random unique Note ID is generated and returned as `{"note":"xxx"}`. If this argument is provided for a `.qo` Notefile, an error is returned. - body (dict): A JSON object to be enqueued. A note must have either a `body` or a `payload`, and can have both. - payload (str): A base64-encoded binary payload. A note must have either a `body` or a `payload`, and can have both. If a Note template is not in use, payloads are limited to 250 bytes. - sync (bool): Set to `true` to sync immediately. Only applies to outgoing Notecard requests, and only guarantees syncing the specified Notefile. Auto-syncing incoming Notes from Notehub is set on the Notecard with `{"req": "hub.set", "mode":"continuous", "sync": true}`. - key (str): The name of an environment variable in your Notehub.io project that contains the contents of a public key. Used when encrypting the Note body for transport. - verify (bool): If set to `true` and using a templated Notefile, the Notefile will be written to flash immediately, rather than being cached in RAM and written to flash later. binary (bool): If `true`, the Notecard will send all the data in the binary buffer to Notehub. Learn more in this guide on Sending and Receiving Large Binary Objects. - live (bool): If `true`, bypasses saving the Note to flash on the Notecard. Required to be set to `true` if also using `"binary":true`. + body (dict): A JSON object to be enqueued. A Note must have either a `body` or a `payload`, and can have both. + file (str): The name of the Notefile. On Notecard LoRa this argument is required. On all other Notecards this field is optional and defaults to `data.qo` if not provided. When using this request on the Notecard the Notefile name must end in one of: `.qo` for a queue outgoing (Notecard to Notehub) with plaintext transport `.qos` for a queue outgoing with encrypted transport `.db` for a bidirectionally synchronized database with plaintext transport `.dbs` for a bidirectionally synchronized database with encrypted transport `.dbx` for a local-only database full (bool): If set to `true`, and the Note is using a Notefile Template, the Note will bypass usage of omitempty and retain `null`, `0`, `false`, and empty string `""` values. + key (str): The name of an environment variable in your Notehub.io project that contains the contents of a public key. Used when encrypting the Note body for transport. limit (bool): If set to `true`, the Note will not be created if Notecard is in a penalty box. + live (bool): If `true`, bypasses saving the Note to flash on the Notecard. Required to be set to `true` if also using `"binary":true`. max (int): Defines the maximum number of queued Notes permitted in the specified Notefile (`"file"`). Any Notes added after this value will be rejected. When used with `"sync":true`, a sync will be triggered when the number of pending Notes matches the `max` value. + note (str): If the Notefile has a `.db/.dbs/.dbx` extension, specifies a unique Note ID. If `note` string is `"?"`, then a random unique Note ID is generated and returned as `{"note":"xxx"}`. If this argument is provided for a `.qo` Notefile, an error is returned. + payload (str): A base64-encoded binary payload. A Note must have either a `body` or a `payload`, and can have both. If a Note template is not in use, payloads are limited to 250 bytes. + sync (bool): Set to `true` to sync immediately. Only applies to outgoing Notecard requests, and only guarantees syncing the specified Notefile. Auto-syncing incoming Notes from Notehub is set on the Notecard with `{"req": "hub.set", "mode":"continuous", "sync": true}`. + verify (bool): If set to `true` and using a templated Notefile, the Notefile will be written to flash immediately, rather than being cached in RAM and written to flash later. Returns: dict: The result of the Notecard request. """ req = {"req": "note.add"} + if binary is not None: + req["binary"] = binary + if body: + req["body"] = body if file: req["file"] = file + if full is not None: + req["full"] = full + if key: + req["key"] = key + if limit is not None: + req["limit"] = limit + if live is not None: + req["live"] = live + if max is not None: + req["max"] = max if note: req["note"] = note - if body: - req["body"] = body if payload: req["payload"] = payload if sync is not None: req["sync"] = sync - if key: - req["key"] = key if verify is not None: req["verify"] = verify - if binary is not None: - req["binary"] = binary - if live is not None: - req["live"] = live - if full is not None: - req["full"] = full - if limit is not None: - req["limit"] = limit - if max is not None: - req["max"] = max return card.Transaction(req) @validate_card_object -def changes(card, file, tracker=None, max=None, start=None, stop=None, deleted=None, delete=None, reset=None): +def changes(card, delete=None, deleted=None, file, max=None, reset=None, start=None, stop=None, tracker=None): """Use to incrementally retrieve changes within a specific Notefile. Args: card (Notecard): The current Notecard object. + delete (bool): `true` to delete the Notes returned by the request. + deleted (bool): `true` to return deleted Notes with this request. Deleted Notes are only persisted in a database notefile (`.db/.dbs`) between the time of Note deletion on the Notecard and the time that a sync with Notehub takes place. As such, this boolean will have no effect after a sync or on queue notefiles (`.q*`). file (str): The Notefile ID. - tracker (str): The change tracker ID. This value is developer-defined and can be used across both the `note.changes` and `file.changes` requests. max (int): The maximum number of Notes to return in the request. + reset (bool): `true` to reset a change tracker. start (bool): `true` to reset the tracker to the beginning. stop (bool): `true` to delete the tracker. - deleted (bool): `true` to return deleted Notes with this request. Deleted notes are only persisted in a database notefile (`.db/.dbs`) between the time of note deletion on the Notecard and the time that a sync with notehub takes place. As such, this boolean will have no effect after a sync or on queue notefiles (`.q*`). - delete (bool): `true` to delete the Notes returned by the request. - reset (bool): `true` to reset a change tracker. + tracker (str): The change tracker ID. This value is developer-defined and can be used across both the `note.changes` and `file.changes` requests. Returns: dict: The result of the Notecard request. """ req = {"req": "note.changes"} + if delete is not None: + req["delete"] = delete + if deleted is not None: + req["deleted"] = deleted if file: req["file"] = file - if tracker: - req["tracker"] = tracker if max is not None: req["max"] = max + if reset is not None: + req["reset"] = reset if start is not None: req["start"] = start if stop is not None: req["stop"] = stop - if deleted is not None: - req["deleted"] = deleted - if delete is not None: - req["delete"] = delete - if reset is not None: - req["reset"] = reset + if tracker: + req["tracker"] = tracker return card.Transaction(req) @@ -124,78 +124,78 @@ def delete(card, file, note, verify=None): @validate_card_object -def get(card, file=None, note=None, delete=None, deleted=None, decrypt=None): +def get(card, decrypt=None, delete=None, deleted=None, file=None, note=None): """Retrieve a Note from a Notefile. The file must either be a DB Notefile or inbound queue file (see `file` argument below). `.qo`/`.qos` Notes must be read from the Notehub event table using the Notehub Event API. Args: card (Notecard): The current Notecard object. - file (str): The Notefile name must end in `.qi` (for plaintext transport), `.qis` (for encrypted transport), `.db` or `.dbx` (for local-only DB Notefiles). - note (str): If the Notefile has a `.db` or `.dbx` extension, specifies a unique Note ID. Not applicable to `.qi` Notefiles. + decrypt (bool): `true` to decrypt encrypted inbound Notefiles. delete (bool): `true` to delete the Note after retrieving it. deleted (bool): `true` to allow retrieval of a deleted Note. - decrypt (bool): `true` to decrypt encrypted inbound Notefiles. + file (str): The Notefile name must end in `.qi` (for plaintext transport), `.qis` (for encrypted transport), `.db` or `.dbx` (for local-only DB Notefiles). + note (str): If the Notefile has a `.db` or `.dbx` extension, specifies a unique Note ID. Not applicable to `.qi` Notefiles. Returns: dict: The result of the Notecard request. """ req = {"req": "note.get"} - if file: - req["file"] = file - if note: - req["note"] = note + if decrypt is not None: + req["decrypt"] = decrypt if delete is not None: req["delete"] = delete if deleted is not None: req["deleted"] = deleted - if decrypt is not None: - req["decrypt"] = decrypt + if file: + req["file"] = file + if note: + req["note"] = note return card.Transaction(req) @validate_card_object -def template(card, file, body=None, length=None, verify=None, format=None, port=None, delete=None): +def template(card, body=None, delete=None, file, format=None, length=None, port=None, verify=None): """By using the `note.template` request with any `.qo`/`.qos` Notefile, developers can provide the Notecard with a schema of sorts to apply to future Notes added to the Notefile. This template acts as a hint to the Notecard that allows it to internally store data as fixed-length binary records rather than as flexible JSON objects which require much more memory. Using templated Notes in place of regular Notes increases the storage and sync capability of the Notecard by an order of magnitude. Read about Working with Note Templates for additional information. Args: card (Notecard): The current Notecard object. - file (str): The name of the Notefile to which the template will be applied. body (dict): A sample JSON body that specifies field names and values as "hints" for the data type. Possible data types are: boolean, integer, float, and string. See Understanding Template Data Types for an explanation of type hints and explanations. - length (int): The maximum length of a `payload` (in bytes) that can be sent in Notes for the template Notefile. As of v3.2.1 `length` is not required, and payloads can be added to any template-based Note without specifying the payload length. - verify (bool): If `true`, returns the current template set on a given Notefile. + delete (bool): Set to `true` to delete all pending Notes using the template if one of the following scenarios is also true: Connecting via non-NTN (e.g. cellular or Wi-Fi) communications, but attempting to sync NTN-compatible Notefiles. or Connecting via NTN (e.g. satellite) communications, but attempting to sync non-NTN-compatible Notefiles. Read more about this feature in Starnote Best Practices. + file (str): The name of the Notefile to which the template will be applied. format (str): By default all Note templates automatically include metadata, including a timestamp for when the Note was created, various fields about a device's location, as well as a timestamp for when the device's location was determined. By providing a `format` of `"compact"` you tell the Notecard to omit this additional metadata to save on storage and bandwidth. The use of `format: "compact"` is required for Notecard LoRa and a Notecard paired with Starnote. When using `"compact"` templates, you may include the following keywords in your template to add in fields that would otherwise be omitted: `lat`, `lon`, `ltime`, `time`. See Creating Compact Templates to learn more. + length (int): The maximum length of a `payload` (in bytes) that can be sent in Notes for the template Notefile. As of v3.2.1 `length` is not required, and payloads can be added to any template-based Note without specifying the payload length. port (int): This argument is required on Notecard LoRa and a Notecard paired with Starnote, but ignored on all other Notecards. A port is a unique integer in the range 1–100, where each unique number represents one Notefile. This argument allows the Notecard to send a numerical reference to the Notefile over the air, rather than the full Notefile name. The port you provide is also used in the "frame port" field on LoRaWAN gateways. - delete (bool): Set to `true` to delete all pending Notes using the template if one of the following scenarios is also true: Connecting via non-NTN (e.g. cellular or Wi-Fi) communications, but attempting to sync NTN-compatible Notefiles. or Connecting via NTN (e.g. satellite) communications, but attempting to sync non-NTN-compatible Notefiles. Read more about this feature in Starnote Best Practices. + verify (bool): If `true`, returns the current template set on a given Notefile. Returns: dict: The result of the Notecard request. """ req = {"req": "note.template"} - if file: - req["file"] = file if body: req["body"] = body - if length is not None: - req["length"] = length - if verify is not None: - req["verify"] = verify + if delete is not None: + req["delete"] = delete + if file: + req["file"] = file if format: req["format"] = format + if length is not None: + req["length"] = length if port is not None: req["port"] = port - if delete is not None: - req["delete"] = delete + if verify is not None: + req["verify"] = verify return card.Transaction(req) @validate_card_object -def update(card, file, note, body=None, payload=None, verify=None): +def update(card, body=None, file, note, payload=None, verify=None): """Update a Note in a DB Notefile by its ID, replacing the existing `body` and/or `payload`. Args: card (Notecard): The current Notecard object. + body (dict): A JSON object to add to the Note. A Note must have either a `body` or `payload`, and can have both. file (str): The name of the DB Notefile that contains the Note to update. note (str): The unique Note ID. - body (dict): A JSON object to add to the Note. A Note must have either a `body` or `payload`, and can have both. payload (str): A base64-encoded binary payload. A Note must have either a `body` or `payload`, and can have both. verify (bool): If set to `true` and using a templated Notefile, the Notefile will be written to flash immediately, rather than being cached in RAM and written to flash later. @@ -203,12 +203,12 @@ def update(card, file, note, body=None, payload=None, verify=None): dict: The result of the Notecard request. """ req = {"req": "note.update"} + if body: + req["body"] = body if file: req["file"] = file if note: req["note"] = note - if body: - req["body"] = body if payload: req["payload"] = payload if verify is not None: diff --git a/notecard/ntn.py b/notecard/ntn.py index 2806e66..74e31f5 100644 --- a/notecard/ntn.py +++ b/notecard/ntn.py @@ -13,22 +13,22 @@ @validate_card_object -def gps(card, on=None, off=None): +def gps(card, off=None, on=None): """Determine whether a Notecard should override a paired Starnote's GPS/GNSS location with its own GPS/GNSS location. The paired Starnote uses its own GPS/GNSS location by default. Args: card (Notecard): The current Notecard object. - on (bool): When `true`, a Starnote will use the GPS/GNSS location from its paired Notecard, instead of its own GPS/GNSS location. off (bool): When `true`, a paired Starnote will use its own GPS/GNSS location. This is the default configuration. + on (bool): When `true`, a Starnote will use the GPS/GNSS location from its paired Notecard, instead of its own GPS/GNSS location. Returns: dict: The result of the Notecard request. """ req = {"req": "ntn.gps"} - if on is not None: - req["on"] = on if off is not None: req["off"] = off + if on is not None: + req["on"] = on return card.Transaction(req) diff --git a/notecard/var.py b/notecard/var.py index 607f17d..0659507 100644 --- a/notecard/var.py +++ b/notecard/var.py @@ -13,72 +13,72 @@ @validate_card_object -def delete(card, name=None, file=None): +def delete(card, file=None, name=None): """Delete a Note from a DB Notefile by its `name`. Provides a simpler interface to the note.delete API. Args: card (Notecard): The current Notecard object. - name (str): The unique Note ID. file (str): The name of the DB Notefile that contains the Note to delete. Default value is `vars.db`. + name (str): The unique Note ID. Returns: dict: The result of the Notecard request. """ req = {"req": "var.delete"} - if name: - req["name"] = name if file: req["file"] = file + if name: + req["name"] = name return card.Transaction(req) @validate_card_object -def get(card, name=None, file=None): +def get(card, file=None, name=None): """Retrieve a Note from a DB Notefile. Provides a simpler interface to the note.get API. Args: card (Notecard): The current Notecard object. - name (str): The unique Note ID. file (str): The name of the DB Notefile that contains the Note to retrieve. Default value is `vars.db`. + name (str): The unique Note ID. Returns: dict: The result of the Notecard request. """ req = {"req": "var.get"} - if name: - req["name"] = name if file: req["file"] = file + if name: + req["name"] = name return card.Transaction(req) @validate_card_object -def set(card, name=None, file=None, text=None, value=None, flag=None, sync=None): +def set(card, file=None, flag=None, name=None, sync=None, text=None, value=None): """Add or updates a Note in a DB Notefile, replacing the existing body with the specified key-value pair where text, value, or flag is the key. Provides a simpler interface to the note.update API. Args: card (Notecard): The current Notecard object. - name (str): The unique Note ID. file (str): The name of the DB Notefile that contains the Note to add or update. Default value is `vars.db`. - text (str): The string-based value to be stored in the DB Notefile. - value (int): The numeric value to be stored in the DB Notefile. flag (bool): The boolean value to be stored in the DB Notefile. + name (str): The unique Note ID. sync (bool): Set to `true` to immediately sync any changes. + text (str): The string-based value to be stored in the DB Notefile. + value (int): The numeric value to be stored in the DB Notefile. Returns: dict: The result of the Notecard request. """ req = {"req": "var.set"} - if name: - req["name"] = name if file: req["file"] = file - if text: - req["text"] = text - if value is not None: - req["value"] = value if flag is not None: req["flag"] = flag + if name: + req["name"] = name if sync is not None: req["sync"] = sync + if text: + req["text"] = text + if value is not None: + req["value"] = value return card.Transaction(req) diff --git a/notecard/web.py b/notecard/web.py index 60dd5b0..d712dd7 100644 --- a/notecard/web.py +++ b/notecard/web.py @@ -13,223 +13,223 @@ @validate_card_object -def delete(card, route=None, name=None, content=None, seconds=None, async_=None, file=None, note=None): +def delete(card, async_=None, content=None, file=None, name=None, note=None, route=None, seconds=None): """Perform a simple HTTP or HTTPS `DELETE` request against an external endpoint, and returns the response to the Notecard. Args: card (Notecard): The current Notecard object. - route (str): Alias for a Proxy Route in Notehub. - name (str): A web URL endpoint relative to the host configured in the Proxy Route. URL parameters may be added to this argument as well (e.g. `/deleteReading?id=1`). - content (str): The MIME type of the body or payload of the response. Default is `application/json`. - seconds (int): If specified, overrides the default 90 second timeout. async_ (bool): If `true`, the Notecard performs the web request asynchronously, and returns control to the host without waiting for a response from Notehub. + content (str): The MIME type of the body or payload of the response. Default is `application/json`. file (str): The name of the local-only Database Notefile (`.dbx`) to be used if the web request is issued asynchronously and you wish to store the response. + name (str): A web URL endpoint relative to the host configured in the Proxy Route. URL parameters may be added to this argument as well (e.g. `/deleteReading?id=1`). note (str): The unique Note ID for the local-only Database Notefile (`.dbx`). Only used with asynchronous web requests (see `file` argument above). + route (str): Alias for a Proxy Route in Notehub. + seconds (int): If specified, overrides the default 90 second timeout. Returns: dict: The result of the Notecard request. """ req = {"req": "web.delete"} - if route: - req["route"] = route - if name: - req["name"] = name - if content: - req["content"] = content - if seconds is not None: - req["seconds"] = seconds if async_ is not None: req["async"] = async_ + if content: + req["content"] = content if file: req["file"] = file + if name: + req["name"] = name if note: req["note"] = note + if route: + req["route"] = route + if seconds is not None: + req["seconds"] = seconds return card.Transaction(req) @validate_card_object -def get(card, route=None, name=None, body=None, content=None, seconds=None, async_=None, binary=None, offset=None, max=None, file=None, note=None): +def get(card, async_=None, binary=None, body=None, content=None, file=None, max=None, name=None, note=None, offset=None, route=None, seconds=None): """Perform a simple HTTP or HTTPS `GET` request against an external endpoint, and returns the response to the Notecard. Args: card (Notecard): The current Notecard object. - route (str): Alias for a Proxy Route in Notehub. - name (str): A web URL endpoint relative to the host configured in the Proxy Route. URL parameters may be added to this argument as well (e.g. `/getLatest?id=1`). - body (dict): The JSON body to send with the request. - content (str): The MIME type of the body or payload of the response. Default is `application/json`. - seconds (int): If specified, overrides the default 90 second timeout. async_ (bool): If `true`, the Notecard performs the web request asynchronously, and returns control to the host without waiting for a response from Notehub. binary (bool): If `true`, the Notecard will return the response stored in its binary buffer. Learn more in this guide on Sending and Receiving Large Binary Objects. - offset (int): Used along with `binary:true` and `max`, sent as a URL parameter to the remote endpoint. Represents the number of bytes to offset the binary payload from 0 when retrieving binary data from the remote endpoint. - max (int): Used along with `binary:true` and `offset`, sent as a URL parameter to the remote endpoint. Represents the number of bytes to retrieve from the binary payload segment. + body (dict): The JSON body to send with the request. + content (str): The MIME type of the body or payload of the response. Default is `application/json`. file (str): The name of the local-only Database Notefile (`.dbx`) to be used if the web request is issued asynchronously and you wish to store the response. + max (int): Used along with `binary:true` and `offset`, sent as a URL parameter to the remote endpoint. Represents the number of bytes to retrieve from the binary payload segment. + name (str): A web URL endpoint relative to the host configured in the Proxy Route. URL parameters may be added to this argument as well (e.g. `/getLatest?id=1`). note (str): The unique Note ID for the local-only Database Notefile (`.dbx`). Only used with asynchronous web requests (see `file` argument above). + offset (int): Used along with `binary:true` and `max`, sent as a URL parameter to the remote endpoint. Represents the number of bytes to offset the binary payload from 0 when retrieving binary data from the remote endpoint. + route (str): Alias for a Proxy Route in Notehub. + seconds (int): If specified, overrides the default 90 second timeout. Returns: dict: The result of the Notecard request. """ req = {"req": "web.get"} - if route: - req["route"] = route - if name: - req["name"] = name - if body: - req["body"] = body - if content: - req["content"] = content - if seconds is not None: - req["seconds"] = seconds if async_ is not None: req["async"] = async_ if binary is not None: req["binary"] = binary - if offset is not None: - req["offset"] = offset - if max is not None: - req["max"] = max + if body: + req["body"] = body + if content: + req["content"] = content if file: req["file"] = file + if max is not None: + req["max"] = max + if name: + req["name"] = name if note: req["note"] = note + if offset is not None: + req["offset"] = offset + if route: + req["route"] = route + if seconds is not None: + req["seconds"] = seconds return card.Transaction(req) @validate_card_object -def post(card, route=None, name=None, body=None, payload=None, content=None, seconds=None, total=None, offset=None, status=None, max=None, verify=None, async_=None, binary=None, file=None, note=None): +def post(card, async_=None, binary=None, body=None, content=None, file=None, max=None, name=None, note=None, offset=None, payload=None, route=None, seconds=None, status=None, total=None, verify=None): """Perform a simple HTTP or HTTPS `POST` request against an external endpoint, and returns the response to the Notecard. Args: card (Notecard): The current Notecard object. - route (str): Alias for a Proxy Route in Notehub. - name (str): A web URL endpoint relative to the host configured in the Proxy Route. URL parameters may be added to this argument as well (e.g. `/addReading?id=1`). + async_ (bool): If `true`, the Notecard performs the web request asynchronously, and returns control to the host without waiting for a response from Notehub. + binary (bool): If `true`, the Notecard will send all the data in the binary buffer to the specified proxy route in Notehub. Learn more in this guide on Sending and Receiving Large Binary Objects. body (dict): The JSON body to send with the request. - payload (str): A base64-encoded binary payload. A `web.post` may have either a `body` or a `payload`, but may NOT have both. Be aware that Notehub will decode the payload as it is delivered to the endpoint. Learn more about sending large binary objects with the Notecard. content (str): The MIME type of the body or payload of the response. Default is `application/json`. - seconds (int): If specified, overrides the default 90 second timeout. - total (int): When sending large payloads to Notehub in fragments across several `web.post` requests, the total size, in bytes, of the binary payload across all fragments. + file (str): The name of the local-only Database Notefile (`.dbx`) to be used if the web request is issued asynchronously and you wish to store the response. + max (int): The maximum size of the response from the remote server, in bytes. Useful if a memory-constrained host wants to limit the response size. + name (str): A web URL endpoint relative to the host configured in the Proxy Route. URL parameters may be added to this argument as well (e.g. `/addReading?id=1`). + note (str): The unique Note ID for the local-only Database Notefile (`.dbx`). Only used with asynchronous web requests (see `file` argument above). offset (int): When sending payload fragments, the number of bytes of the binary payload to offset from 0 when reassembling on the Notehub once all fragments have been received. + payload (str): A base64-encoded binary payload. A `web.post` may have either a `body` or a `payload`, but may NOT have both. Be aware that Notehub will decode the payload as it is delivered to the endpoint. Learn more about sending large binary objects with the Notecard. + route (str): Alias for a Proxy Route in Notehub. + seconds (int): If specified, overrides the default 90 second timeout. status (str): A 32-character hex-encoded MD5 sum of the payload or payload fragment. Used by Notehub to perform verification upon receipt. - max (int): The maximum size of the response from the remote server, in bytes. Useful if a memory-constrained host wants to limit the response size. + total (int): When sending large payloads to Notehub in fragments across several `web.post` requests, the total size, in bytes, of the binary payload across all fragments. verify (bool): `true` to request verification from Notehub once the payload or payload fragment is received. Automatically set to `true` when `status` is supplied. - async_ (bool): If `true`, the Notecard performs the web request asynchronously, and returns control to the host without waiting for a response from Notehub. - binary (bool): If `true`, the Notecard will send all the data in the binary buffer to the specified proxy route in Notehub. Learn more in this guide on Sending and Receiving Large Binary Objects. - file (str): The name of the local-only Database Notefile (`.dbx`) to be used if the web request is issued asynchronously and you wish to store the response. - note (str): The unique Note ID for the local-only Database Notefile (`.dbx`). Only used with asynchronous web requests (see `file` argument above). Returns: dict: The result of the Notecard request. """ req = {"req": "web.post"} - if route: - req["route"] = route - if name: - req["name"] = name + if async_ is not None: + req["async"] = async_ + if binary is not None: + req["binary"] = binary if body: req["body"] = body - if payload: - req["payload"] = payload if content: req["content"] = content - if seconds is not None: - req["seconds"] = seconds - if total is not None: - req["total"] = total + if file: + req["file"] = file + if max is not None: + req["max"] = max + if name: + req["name"] = name + if note: + req["note"] = note if offset is not None: req["offset"] = offset + if payload: + req["payload"] = payload + if route: + req["route"] = route + if seconds is not None: + req["seconds"] = seconds if status: req["status"] = status - if max is not None: - req["max"] = max + if total is not None: + req["total"] = total if verify is not None: req["verify"] = verify - if async_ is not None: - req["async"] = async_ - if binary is not None: - req["binary"] = binary - if file: - req["file"] = file - if note: - req["note"] = note return card.Transaction(req) @validate_card_object -def put(card, route=None, name=None, body=None, payload=None, content=None, seconds=None, total=None, offset=None, status=None, max=None, verify=None, async_=None, file=None, note=None): +def put(card, async_=None, body=None, content=None, file=None, max=None, name=None, note=None, offset=None, payload=None, route=None, seconds=None, status=None, total=None, verify=None): """Perform a simple HTTP or HTTPS `PUT` request against an external endpoint, and returns the response to the Notecard. Args: card (Notecard): The current Notecard object. - route (str): Alias for a Proxy Route in Notehub. - name (str): A web URL endpoint relative to the host configured in the Proxy Route. URL parameters may be added to this argument as well (e.g. `/updateReading?id=1`). + async_ (bool): If `true`, the Notecard performs the web request asynchronously, and returns control to the host without waiting for a response from Notehub. body (dict): The JSON body to send with the request. - payload (str): A base64-encoded binary payload. A `web.put` may have either a `body` or a `payload`, but may NOT have both. Be aware that Notehub will decode the payload as it is delivered to the endpoint. Learn more about sending large binary objects with the Notecard. content (str): The MIME type of the body or payload of the response. Default is `application/json`. - seconds (int): If specified, overrides the default 90 second timeout. - total (int): When sending large payloads to Notehub in fragments across several `web.put` requests, the total size, in bytes, of the binary payload across all fragments. + file (str): The name of the local-only Database Notefile (`.dbx`) to be used if the web request is issued asynchronously and you wish to store the response. + max (int): The maximum size of the response from the remote server, in bytes. Useful if a memory-constrained host wants to limit the response size. Default (and maximum value) is 8192. + name (str): A web URL endpoint relative to the host configured in the Proxy Route. URL parameters may be added to this argument as well (e.g. `/updateReading?id=1`). + note (str): The unique Note ID for the local-only Database Notefile (`.dbx`). Only used with asynchronous web requests (see `file` argument above). offset (int): When sending payload fragments, the number of bytes of the binary payload to offset from 0 when reassembling on the Notehub once all fragments have been received. + payload (str): A base64-encoded binary payload. A `web.put` may have either a `body` or a `payload`, but may NOT have both. Be aware that Notehub will decode the payload as it is delivered to the endpoint. Learn more about sending large binary objects with the Notecard. + route (str): Alias for a Proxy Route in Notehub. + seconds (int): If specified, overrides the default 90 second timeout. status (str): A 32-character hex-encoded MD5 sum of the payload or payload fragment. Used by Notehub to perform verification upon receipt. - max (int): The maximum size of the response from the remote server, in bytes. Useful if a memory-constrained host wants to limit the response size. Default (and maximum value) is 8192. + total (int): When sending large payloads to Notehub in fragments across several `web.put` requests, the total size, in bytes, of the binary payload across all fragments. verify (bool): `true` to request verification from Notehub once the payload or payload fragment is received. Automatically set to `true` when `status` is supplied. - async_ (bool): If `true`, the Notecard performs the web request asynchronously, and returns control to the host without waiting for a response from Notehub. - file (str): The name of the local-only Database Notefile (`.dbx`) to be used if the web request is issued asynchronously and you wish to store the response. - note (str): The unique Note ID for the local-only Database Notefile (`.dbx`). Only used with asynchronous web requests (see `file` argument above). Returns: dict: The result of the Notecard request. """ req = {"req": "web.put"} - if route: - req["route"] = route - if name: - req["name"] = name + if async_ is not None: + req["async"] = async_ if body: req["body"] = body - if payload: - req["payload"] = payload if content: req["content"] = content - if seconds is not None: - req["seconds"] = seconds - if total is not None: - req["total"] = total + if file: + req["file"] = file + if max is not None: + req["max"] = max + if name: + req["name"] = name + if note: + req["note"] = note if offset is not None: req["offset"] = offset + if payload: + req["payload"] = payload + if route: + req["route"] = route + if seconds is not None: + req["seconds"] = seconds if status: req["status"] = status - if max is not None: - req["max"] = max + if total is not None: + req["total"] = total if verify is not None: req["verify"] = verify - if async_ is not None: - req["async"] = async_ - if file: - req["file"] = file - if note: - req["note"] = note return card.Transaction(req) @validate_card_object -def web(card, route=None, method=None, name=None, content=None): +def web(card, content=None, method=None, name=None, route=None): """Perform an HTTP or HTTPS request against an external endpoint, with the ability to specify any valid HTTP method. Args: card (Notecard): The current Notecard object. - route (str): Alias for a Proxy Route in Notehub. + content (str): The MIME type of the body or payload of the response. Default is `application/json`. method (str): The HTTP method of the request. Must be one of GET, PUT, POST, DELETE, PATCH, HEAD, OPTIONS, TRACE, or CONNECT. name (str): A web URL endpoint relative to the host configured in the Proxy Route. URL parameters may be added to this argument as well (e.g. `/getLatest?id=1`). - content (str): The MIME type of the body or payload of the response. Default is `application/json`. + route (str): Alias for a Proxy Route in Notehub. Returns: dict: The result of the Notecard request. """ req = {"req": "web"} - if route: - req["route"] = route + if content: + req["content"] = content if method: req["method"] = method if name: req["name"] = name - if content: - req["content"] = content + if route: + req["route"] = route return card.Transaction(req) From ba2d715128dbd70d31e29706c9d5741e9c60266c Mon Sep 17 00:00:00 2001 From: Alex Bucknall Date: Wed, 27 Aug 2025 16:15:41 +0100 Subject: [PATCH 6/7] chore: update function signatures in note.py to ensure 'file' is the first parameter; adjust test cases for consistency --- notecard/note.py | 6 +++--- scripts/generate_apis.py | 14 +++++++++++--- test/fluent_api/test_note.py | 6 +++--- 3 files changed, 17 insertions(+), 9 deletions(-) diff --git a/notecard/note.py b/notecard/note.py index 6b96e49..774b1cd 100644 --- a/notecard/note.py +++ b/notecard/note.py @@ -63,7 +63,7 @@ def add(card, binary=None, body=None, file=None, full=None, key=None, limit=None @validate_card_object -def changes(card, delete=None, deleted=None, file, max=None, reset=None, start=None, stop=None, tracker=None): +def changes(card, file, delete=None, deleted=None, max=None, reset=None, start=None, stop=None, tracker=None): """Use to incrementally retrieve changes within a specific Notefile. Args: @@ -153,7 +153,7 @@ def get(card, decrypt=None, delete=None, deleted=None, file=None, note=None): @validate_card_object -def template(card, body=None, delete=None, file, format=None, length=None, port=None, verify=None): +def template(card, file, body=None, delete=None, format=None, length=None, port=None, verify=None): """By using the `note.template` request with any `.qo`/`.qos` Notefile, developers can provide the Notecard with a schema of sorts to apply to future Notes added to the Notefile. This template acts as a hint to the Notecard that allows it to internally store data as fixed-length binary records rather than as flexible JSON objects which require much more memory. Using templated Notes in place of regular Notes increases the storage and sync capability of the Notecard by an order of magnitude. Read about Working with Note Templates for additional information. Args: @@ -188,7 +188,7 @@ def template(card, body=None, delete=None, file, format=None, length=None, port= @validate_card_object -def update(card, body=None, file, note, payload=None, verify=None): +def update(card, file, note, body=None, payload=None, verify=None): """Update a Note in a DB Notefile by its ID, replacing the existing `body` and/or `payload`. Args: diff --git a/scripts/generate_apis.py b/scripts/generate_apis.py index d22aa71..e1c8e50 100644 --- a/scripts/generate_apis.py +++ b/scripts/generate_apis.py @@ -199,6 +199,10 @@ def generate_function_signature(self, api: Dict[str, Any]) -> str: properties = api["properties"] required = api["required"] + # Separate required and optional parameters + required_params = [] + optional_params = [] + # Add parameters for schema properties for prop_name, _ in properties.items(): if prop_name in ["req", "cmd"]: # Skip these as they're auto-generated @@ -209,11 +213,15 @@ def generate_function_signature(self, api: Dict[str, Any]) -> str: if param_name in self.reserved_keywords: param_name = f"{param_name}_" - # Required parameters come first without default values + # Separate required and optional parameters if prop_name in required and prop_name not in ["req", "cmd"]: - params.append(f"{param_name}") + required_params.append(f"{param_name}") else: - params.append(f"{param_name}=None") + optional_params.append(f"{param_name}=None") + + # Add required parameters first, then optional parameters + params.extend(required_params) + params.extend(optional_params) return f"def {func_name}({', '.join(params)}):" diff --git a/test/fluent_api/test_note.py b/test/fluent_api/test_note.py index 1c7b1ee..637a762 100644 --- a/test/fluent_api/test_note.py +++ b/test/fluent_api/test_note.py @@ -10,7 +10,7 @@ 'note.add', { 'file': 'data.qo', - 'body': {'key_a:', 'val_a', 'key_b', 42}, + 'body': {'key_a': 'val_a', 'key_b': 42}, 'payload': 'ewogICJpbnRlcnZhbHMiOiI2MCwxMiwxNCIKfQ==', 'sync': True }, @@ -90,7 +90,7 @@ 'note.template', { 'file': 'my-settings.db', - 'body': {'key_a:', 'val_a', 'key_b', 42}, + 'body': {'key_a': 'val_a', 'key_b': 42}, 'length': 42, 'format': "compact" }, @@ -111,7 +111,7 @@ { 'file': 'my-settings.db', 'note': 'my_note', - 'body': {'key_a:', 'val_a', 'key_b', 42}, + 'body': {'key_a': 'val_a', 'key_b': 42}, 'payload': 'ewogICJpbnRlcnZhbHMiOiI2MCwxMiwxNCIKfQ==' }, ), From 3982175b581556c6730359daddde8d146b37983d Mon Sep 17 00:00:00 2001 From: Alex Bucknall Date: Wed, 27 Aug 2025 16:40:25 +0100 Subject: [PATCH 7/7] doc: update docstring in sleep function for clarity and consistency; add replacement for 'Allows' in API generator --- notecard/card.py | 2 +- scripts/generate_apis.py | 3 ++- 2 files changed, 3 insertions(+), 2 deletions(-) diff --git a/notecard/card.py b/notecard/card.py index 920cd24..95cfaf4 100644 --- a/notecard/card.py +++ b/notecard/card.py @@ -621,7 +621,7 @@ def restart(card): @validate_card_object def sleep(card, mode=None, off=None, on=None, seconds=None): - """Allows the ESP32-based Notecard WiFi v2 to fall back to a low current draw when idle (this behavior differs from the STM32-based Notecards that have a `STOP` mode where UART and I2C may still operate). Note that the Notecard WiFi v2 will not enable a "sleep" mode while plugged in via USB. Read more in the guide on using Deep Sleep Mode on Notecard WiFi v2. + """Allow the ESP32-based Notecard WiFi v2 to fall back to a low current draw when idle (this behavior differs from the STM32-based Notecards that have a `STOP` mode where UART and I2C may still operate). Note that this power state is not available if the Notecard is plugged in via USB. Read more in the guide on using Deep Sleep Mode on Notecard WiFi v2. Args: card (Notecard): The current Notecard object. diff --git a/scripts/generate_apis.py b/scripts/generate_apis.py index e1c8e50..4fca246 100644 --- a/scripts/generate_apis.py +++ b/scripts/generate_apis.py @@ -164,7 +164,8 @@ def convert_to_imperative_mood(self, text: str) -> str: "Specifies": "Specify", "Determines": "Determine", "The": "Use", - "This": "Use" + "This": "Use", + "Allows": "Allow" } # Apply replacements - only replace at the start of sentences