diff --git a/notecard/card.py b/notecard/card.py index d6533dd..95cfaf4 100644 --- a/notecard/card.py +++ b/notecard/card.py @@ -13,934 +13,787 @@ @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 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. - 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. +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. + 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. + 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`. + 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: - string: The result of the Notecard request. + dict: The result of the Notecard request. """ - req = {"req": "card.time"} + req = {"req": "card.aux"} + 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 max is not None: + req["max"] = max + if mode: + req["mode"] = mode + if ms is not None: + req["ms"] = ms + 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 status(card): - """Retrieve the status of the Notecard. +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. + duration (int): If using `"mode": "accel"`, specify a sampling duration for the Notecard accelerometer. + 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)`. + 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: - string: The result of the Notecard request. + dict: The result of the Notecard request. """ - req = {"req": "card.status"} + req = {"req": "card.aux.serial"} + if duration is not None: + req["duration"] = duration + if limit is not None: + req["limit"] = limit + if max is not None: + req["max"] = max + 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 temp(card, minutes=None): - """Retrieve the current temperature from the Notecard. +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. - minutes (int): If specified, creates a templated _temp.qo file that - gathers Notecard temperature value at the specified interval. + cobs (int): The size of the COBS-encoded data you are expecting to be returned (in bytes). + 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: - string: The result of the Notecard request. + dict: The result of the Notecard request. """ - req = {"req": "card.temp"} - if minutes: - req["minutes"] = minutes + req = {"req": "card.binary.get"} + if cobs is not None: + req["cobs"] = cobs + if length is not None: + req["length"] = length + if offset is not None: + req["offset"] = offset return card.Transaction(req) @validate_card_object -def version(card): - """Retrieve firmware version information from the Notecard. +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. + 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: - string: The result of the Notecard request. + dict: The result of the Notecard request. """ - req = {"req": "card.version"} + req = {"req": "card.binary.put"} + 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) @validate_card_object -def voltage(card, hours=None, offset=None, vmax=None, vmin=None): - """Retrieve current and historical voltage info from the Notecard. +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. - 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. + delete (bool): Clear the COBS area on the Notecard and reset all related arguments previously set by a card.binary request. Returns: - string: The result of the Notecard request. + dict: 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 + req = {"req": "card.binary"} + if delete is not None: + req["delete"] = delete return card.Transaction(req) @validate_card_object -def wireless(card, mode=None, apn=None): - """Retrieve wireless modem info or customize modem behavior. +def carrier(card, mode=None): + """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 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. + 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 network status and - signal information. + dict: The result of the Notecard request. """ - req = {"req": "card.wireless"} + req = {"req": "card.carrier"} 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. +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. - 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. + 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. 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 + req = {"req": "card.contact"} + if email: + req["email"] = email + if name: + req["name"] = name + if org: + req["org"] = org + if role: + req["role"] = role 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. +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. - 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. + 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. + 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`. + start (bool): Set to `true` to enable the host RESET if previously disabled with `"stop":true`. + 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. 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. + dict: The result of the Notecard request. """ - req = {"req": "card.power"} - if minutes: - req["minutes"] = minutes - if reset: - req["reset"] = reset + req = {"req": "card.dfu"} + if mode: + req["mode"] = mode + if name: + req["name"] = name + 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 start is not None: + req["start"] = start + if stop is not None: + req["stop"] = stop return card.Transaction(req) @validate_card_object -def location(card): - """Retrieve the last known location of the Notecard. +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. 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 + dict: The result of the Notecard request. """ - req = {"req": "card.location"} + req = {"req": "card.illumination"} 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. +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. - 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. + i2c (int): The alternate address to use for I2C communication. Pass `-1` to reset to the default address + 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. """ - req = {"req": "card.location.mode"} - if mode is not None: + req = {"req": "card.io"} + if i2c is not None: + req["i2c"] = i2c + if mode: req["mode"] = mode - if seconds: - 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: - 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. +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. - 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". + 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. """ - 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 sync is not None: - req["sync"] = sync - if stop is not None: - req["stop"] = stop - if file: - req["file"] = file + req = {"req": "card.led"} + if mode: + req["mode"] = mode + if off is not None: + req["off"] = off + if on is not None: + req["on"] = on 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 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. - delete (bool): Set to True to clear the COBS area on the Notecard and reset all related arguments. + 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. + 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 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"} + req = {"req": "card.location.mode"} 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 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) @validate_card_object -def binaryGet(card, cobs=None, offset=None, length=None): - """Retrieve binary data stored in the binary storage area of the Notecard. +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. - 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. 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: - req["cobs"] = cobs - if offset is not None: - req["offset"] = offset - if length: - req["length"] = length + req = {"req": "card.location"} 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. +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. - offset (int): The number of bytes to offset the binary payload from 0 when appending the binary data to the binary storage area. - cobs (int): The size of the COBS-encoded data (in bytes). - status (string): The MD5 checksum of the data, before it has been encoded. + 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. + 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. 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: - req["cobs"] = cobs - if status: - req["status"] = status + req = {"req": "card.location.track"} + if file: + req["file"] = file + if heartbeat is not None: + req["heartbeat"] = heartbeat + if hours is not None: + req["hours"] = hours + 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 carrier(card, mode=None): - """Configure the AUX_CHARGING pin to notify the Notecard about charging support on a Notecarrier. +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 (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. + 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 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"} + req = {"req": "card.monitor"} + 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 contact(card, name=None, org=None, role=None, email=None): - """Set or retrieve information about the Notecard maintainer. - - 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. - - 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 - """ - req = {"req": "card.contact"} - 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 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 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. - 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. + 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. + 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 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.motion.mode"} + if motion is not None: + req["motion"] = motion + if seconds is not None: req["seconds"] = seconds - if max: - req["max"] = max + if sensitivity is not None: + req["sensitivity"] = sensitivity 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 stop is not None: + req["stop"] = stop 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 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. - 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". + 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.aux.serial"} - 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 = {"req": "card.motion"} + if minutes is not None: 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. - - 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. - - Returns: - dict: The result of the Notecard request containing: - "name": Current MCU class configured for DFU - """ - 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: - 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 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. + 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 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.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 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 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. - 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 + count (int): The number of most recent motion buckets to examine. + 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.io"} - if i2c is not None: - req["i2c"] = i2c - if mode: - req["mode"] = mode + req = {"req": "card.motion.track"} + if count is not None: + req["count"] = count + 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 led(card, mode=None, on=None, off=None): - """Control connected LEDs or manage a single connected NeoPixel. +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 (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. + 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. - - 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"} + req = {"req": "card.random"} + if count is not None: + req["count"] = count if mode: req["mode"] = mode - if on is not None: - req["on"] = on - if off is not None: - req["off"] = off return card.Transaction(req) @validate_card_object -def monitor(card, mode=None, count=None, usb=None): - """Configure AUX pins when in monitor mode. +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. - 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. + 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.monitor"} - if mode: - req["mode"] = mode - if count is not None: - req["count"] = count - if usb is not None: - req["usb"] = usb + 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 motion(card, minutes=None): - """Retrieve information about the Notecard's accelerometer motion and orientation. +def restart(card): + """Perform a firmware restart of the Notecard. 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") + dict: The result of the Notecard request. """ - req = {"req": "card.motion"} - if minutes is not None: - req["minutes"] = minutes + req = {"req": "card.restart"} 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. +def sleep(card, mode=None, off=None, on=None, seconds=None): + """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. - 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". + 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.motion.mode"} - if start is not None: - req["start"] = start - if stop is not None: - req["stop"] = stop + req = {"req": "card.sleep"} + 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 sensitivity is not None: - req["sensitivity"] = sensitivity - if motion is not None: - req["motion"] = motion 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. +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. - 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. + 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.motion.sync"} - 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 + req = {"req": "card.restore"} + if connected is not None: + req["connected"] = connected + if delete is not None: + req["delete"] = delete return card.Transaction(req) @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. +def status(card): + """Return general information about the Notecard's operating status. 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. 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 is not None: - req["file"] = file - if now is not None: - req["now"] = now + req = {"req": "card.status"} return card.Transaction(req) @validate_card_object -def restart(card): - """Perform a firmware restart of the Notecard. +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. - - Warning: - Not recommended for production applications due to potential increased - cellular data and consumption credit usage. """ - req = {"req": "card.restart"} + 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 restore(card, delete=None, connected=None): - """Reset Notecard configuration settings and/or deprovision from Notehub. +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. - 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.time"} 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. +def trace(card, mode=None): + """Enable and disable trace mode on a Notecard for debugging. 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. + 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 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: - req["on"] = on - if off is not None: - req["off"] = off - if seconds is not None: - req["seconds"] = seconds + req = {"req": "card.trace"} if mode: req["mode"] = mode return card.Transaction(req) @validate_card_object -def trace(card, mode=None): - """Enable and disable trace mode on a Notecard for debugging. +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. - mode (string): Set to "on" to enable trace mode on a Notecard, or "off" to disable it. + 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. + umin (bool): Set to `true` to force a longer network transport timeout when using Wideband Notecards. 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: - req["mode"] = mode + 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 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. +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. - 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. + 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`. + set (bool): `true` to instruct the module to use the state of the `on` and `usb` arguments. + 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 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 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) @@ -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: + 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,90 +841,154 @@ 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): Specify a major version of the Notecard firmware that a host expects to use. 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, 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, 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. + + 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 org is not None: - req["org"] = org - if start is not None: - req["start"] = start - if text: - req["text"] = text + 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 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. +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. - 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. + 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 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: - 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, 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. + apn (str): Access Point Name (APN) when using an external SIM. Use `"-"` to reset to the Notecard default APN. + 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 apn: + req["apn"] = apn + 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, 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. + 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\"]"}` + + Returns: + dict: The result of the Notecard request. + """ + req = {"req": "card.wifi"} + 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: + req["text"] = text return card.Transaction(req) diff --git a/notecard/dfu.py b/notecard/dfu.py new file mode 100644 index 0000000..6d12f3e --- /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, 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. + 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`. + + Returns: + dict: The result of the Notecard request. + """ + req = {"req": "dfu.status"} + if err: + req["err"] = err + if name: + req["name"] = name + 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 + return card.Transaction(req) diff --git a/notecard/env.py b/notecard/env.py index 58de6b8..e97e247 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. 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. + 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..6e309d1 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 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 (array): A list of Notefiles to delete. + 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: - 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..c915f89 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): + 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. - 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. 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. 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.\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. 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. 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. 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. + 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..774b1cd 100644 --- a/notecard/note.py +++ b/notecard/note.py @@ -9,179 +9,208 @@ # 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, 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 (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. + 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. + 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: - string: The result of the Notecard request. + dict: The result of the Notecard request. """ req = {"req": "note.add"} - if file: - req["file"] = file + 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 payload: req["payload"] = payload - if port: - req["port"] = port if sync is not None: req["sync"] = sync + if verify is not None: + req["verify"] = verify 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, delete=None, deleted=None, 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. - 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. + 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. + 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. + 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: - string: The result of the Notecard request. + 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 maximum: - req["max"] = maximum + 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 tracker: + req["tracker"] = tracker 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, 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 (string): The file name of the DB notefile. - note_id (string): The id of the note to delete. + 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. + 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: - string: The result of the Notecard request. + dict: The result of the Notecard request. """ - req = {"req": "note.delete"} + req = {"req": "note.get"} + 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 file: req["file"] = file - if note_id: - req["note"] = note_id + if note: + req["note"] = note 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, 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: 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. + 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. + 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. + verify (bool): If `true`, returns the current template set on a given Notefile. Returns: - string: The result of the Notecard request. + dict: The result of the Notecard request. """ - req = {"req": "note.update"} - if file: - req["file"] = file - if note_id: - req["note"] = note_id + req = {"req": "note.template"} if body: req["body"] = body - if payload: - req["payload"] = payload + 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 verify is not None: + req["verify"] = verify 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. + 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. + 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"} - if file: - req["file"] = file + req = {"req": "note.update"} if body: req["body"] = body - if length: - req["length"] = length - if port: - req["port"] = port - if compact: - req["format"] = "compact" + if file: + req["file"] = file + if note: + req["note"] = note + 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..74e31f5 --- /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, 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. + 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 off is not None: + req["off"] = off + if on is not None: + req["on"] = on + 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..0659507 --- /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, 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. + 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 file: + req["file"] = file + if name: + req["name"] = name + return card.Transaction(req) + + +@validate_card_object +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. + 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 file: + req["file"] = file + if name: + req["name"] = name + return card.Transaction(req) + + +@validate_card_object +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. + file (str): The name of the DB Notefile that contains the Note to add or update. Default value is `vars.db`. + 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 file: + req["file"] = file + 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 new file mode 100644 index 0000000..d712dd7 --- /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, 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. + 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 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, 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. + 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. + 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 async_ is not None: + req["async"] = async_ + if binary is not None: + req["binary"] = binary + 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, 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. + 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. + 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): 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. + 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. + + Returns: + dict: The result of the Notecard request. + """ + req = {"req": "web.post"} + if async_ is not None: + req["async"] = async_ + if binary is not None: + req["binary"] = binary + 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 payload: + req["payload"] = payload + if route: + req["route"] = route + if seconds is not None: + req["seconds"] = seconds + if status: + req["status"] = status + if total is not None: + req["total"] = total + if verify is not None: + req["verify"] = verify + return card.Transaction(req) + + +@validate_card_object +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. + 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. + 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): 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. + 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. + + Returns: + dict: The result of the Notecard request. + """ + req = {"req": "web.put"} + if async_ is not None: + req["async"] = async_ + 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 payload: + req["payload"] = payload + if route: + req["route"] = route + if seconds is not None: + req["seconds"] = seconds + if status: + req["status"] = status + if total is not None: + req["total"] = total + if verify is not None: + req["verify"] = verify + return card.Transaction(req) + + +@validate_card_object +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. + 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`). + route (str): Alias for a Proxy Route in Notehub. + + Returns: + dict: The result of the Notecard request. + """ + req = {"req": "web"} + if content: + req["content"] = content + if method: + req["method"] = method + if name: + req["name"] = name + if route: + req["route"] = route + return card.Transaction(req) diff --git a/pyproject.toml b/pyproject.toml index 0ec4c52..9a8d1da 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -4,7 +4,7 @@ build-backend = "setuptools.build_meta" [project] name = "note-python" -version = "1.7.1" +version = "2.1.1" description = "Cross-platform Python Library for the Blues Wireless Notecard" authors = [ {name = "Blues Inc.", email = "support@blues.com"}, 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/scripts/generate_apis.py b/scripts/generate_apis.py new file mode 100644 index 0000000..4fca246 --- /dev/null +++ b/scripts/generate_apis.py @@ -0,0 +1,444 @@ +#!/usr/bin/env python3 +"""Generate Notecard API functions from notecard-schema.""" + +import requests +import time +from pathlib import Path +from typing import Dict, List, Optional, Any +import argparse + + +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) + + 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.""" + 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) + + # 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 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 + + # 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", + "Allows": "Allow" + } + + # 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.""" + 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"] + + # Add parameters based on properties + 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 + continue + + # Handle reserved keywords by appending underscore + param_name = prop_name + if param_name in self.reserved_keywords: + param_name = f"{param_name}_" + + # Separate required and optional parameters + if prop_name in required and prop_name not in ["req", "cmd"]: + required_params.append(f"{param_name}") + else: + 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)}):" + + def _build_docstring_content(self, api: Dict[str, Any], imperative_description: str) -> str: + """Build complete docstring content to check for backslashes.""" + lines = [imperative_description, ""] + + 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"]) + # 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.") + + 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_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}") + + + 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}"}}'] + + 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}_" + + 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}') + + + 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("") + 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") + + for api in apis: + lines.append(self.generate_api_function(api)) + + # 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.""" + 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) + + output_path = Path(output_dir) + output_path.mkdir(exist_ok=True) + + 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) + + 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/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..637a762 100644 --- a/test/fluent_api/test_note.py +++ b/test/fluent_api/test_note.py @@ -3,20 +3,31 @@ @pytest.mark.parametrize( - 'fluent_api,notecard_api,req_params,rename_key_map,rename_value_map', + 'fluent_api,notecard_api,req_params', [ ( note.add, 'note.add', { 'file': 'data.qo', - 'body': {'key_a:', 'val_a', 'key_b', 42}, + '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,86 +35,103 @@ { '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, 'note.template', { 'file': 'my-settings.db', - 'body': {'key_a:', 'val_a', 'key_b', 42}, + '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', - 'body': {'key_a:', 'val_a', 'key_b', 42}, + '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)