From 4ff1a14b889d35cb66bd2a15d2dcd831b6f685d5 Mon Sep 17 00:00:00 2001 From: ppescher Date: Fri, 18 Oct 2019 18:33:23 +0200 Subject: [PATCH] Add updated Fortebit and Polaris libraries --- fortebit/iot/http_client.py | 142 ++++++++++ fortebit/iot/iot.py | 131 +++++++++ fortebit/iot/mqtt_client.py | 137 ++++++++++ fortebit/iot/z.yml | 14 + fortebit/polaris/cloud.py | 91 +++++++ fortebit/polaris/polaris.py | 511 ++++++++++++++++++++++++++++++++++++ fortebit/polaris/z.yml | 13 + 7 files changed, 1039 insertions(+) create mode 100644 fortebit/iot/http_client.py create mode 100644 fortebit/iot/iot.py create mode 100644 fortebit/iot/mqtt_client.py create mode 100644 fortebit/iot/z.yml create mode 100644 fortebit/polaris/cloud.py create mode 100644 fortebit/polaris/polaris.py create mode 100644 fortebit/polaris/z.yml diff --git a/fortebit/iot/http_client.py b/fortebit/iot/http_client.py new file mode 100644 index 0000000..6186b87 --- /dev/null +++ b/fortebit/iot/http_client.py @@ -0,0 +1,142 @@ +import requests +import json +import threading + +debug = False + + +def print_d(*args): + if debug: + print(*args) + + +class HttpClient(): + + def __init__(self, endpoint, device_token, ctx=None): + self.token = device_token + self.ctx = ctx + self.connected = False + if ctx is not None: + self.endpoint = "https://" + endpoint + else: + self.endpoint = "http://" + endpoint + + def connect(self): + try: + res = requests.get(self._attributes_url() + '/updates?timeout=1000', ctx=self.ctx) + if res.status == 200 or res.status == 408: + self.connected = True + return True + except Exception as e: + print_d("connect", e) + self.connected = False + return False + print_d("connect failed", res.status) + self.connected = False + return False + + def is_connected(self): + return self.connected + + def on_rpc_request(self, id, method, params): + pass + + def _do_loop(self): + while True: + try: + rpc = self._get_rpc_request() + if rpc is not None: + self.on_rpc_request(rpc['id'], rpc['method'], rpc['params']) + except Exception as e: + print_d("http loop", e) + sleep(5000) + + def loop(self): + thread(self._do_loop) + + def _telemetry_url(self): + return self.endpoint + "/api/v1/" + self.token + "/telemetry" + + def _attributes_url(self): + return self.endpoint + "/api/v1/" + self.token + "/attributes" + + def _rpc_url(self, id=None): + if id is not None: + return self.endpoint + "/api/v1/" + self.token + "/rpc/" + str(id) + return self.endpoint + "/api/v1/" + self.token + "/rpc" + + def publish_telemetry(self, values, ts=None): + if ts is not None: + values = {'values': values, 'ts': ts} + try: + res = requests.post(self._telemetry_url(), json=values, ctx=self.ctx) + except Exception as e: + print_d(e) + return False + if res.status != 200: + return False + return True + + def publish_attributes(self, client): + try: + res = requests.post(self._attributes_url(), json=client, ctx=self.ctx) + except Exception as e: + print_d(e) + return False + if res.status != 200: + return False + return True + + def get_attributes(self, client, shared=None, timeout=20000): + obj = {} + if client and isinstance(client, PSTRING): + obj['clientKeys'] = client + else: + client = None + if shared and isinstance(shared, PSTRING): + obj['sharedKeys'] = shared + else: + shared = None + try: + res = requests.get(self._attributes_url(), params=obj, ctx=self.ctx) + if res.status != 200: + return None + obj = json.loads(res.content) + except Exception as e: + print_d(e) + return None + if obj is None: + return None + if 'client' in obj: + client = obj['client'] + else: + client = None + if 'shared' in obj: + shared = obj['shared'] + else: + shared = None + return (client, shared) + + def _get_rpc_request(self, timeout=20000): + obj = {'timeout': timeout} + try: + res = requests.get(self._rpc_url(), params=obj, ctx=self.ctx) + if res.status != 200: + return None + obj = json.loads(res.content) + self.connected = True + return obj + except Exception as e: + print_d(e) + self.connected = False + return None + + def publish_rpc_reply(self, id, result): + try: + res = requests.post(self._rpc_url(id), json=result, ctx=self.ctx) + except Exception as e: + print_d(e) + return False + if res.status != 200: + return False + return True diff --git a/fortebit/iot/iot.py b/fortebit/iot/iot.py new file mode 100644 index 0000000..973c60e --- /dev/null +++ b/fortebit/iot/iot.py @@ -0,0 +1,131 @@ +""" +.. module:: iot + +******************** +Fortebit IoT Library +******************** + +The Zerynth Fortebit IoT Library can be used to ease the connection to the `Fortebit IoT Cloud `_. + +It makes your device act as a Fortebit IoT Device that can be monitored and controlled on the Fortebit IoT Cloud dashboard. + +The device always send and receive data in the JSON format. + + """ + + +class Device(): + """ +================ +The Device class +================ + +.. class:: Device(device_token, client, ctx=None) + + Create a Device instance representing a Fortebit IoT device. + + The device is provisioned by the :samp:`device_token`, obtained from the Fortebit dashboard upon the creation of a new device. + The :samp:`client` parameter is a class that provides the implementation of the low level details for the connection. + It can be one of :samp:`MqttClient` in the :samp:`mqtt_client` module, or :samp:`HttpClient` in the :samp:`http_client` module. + The optional :samp:`ctx` parameter is an initialized secure socket context. + + """ + + def __init__(self, device_token, client, ctx=None): + self.device_token = device_token + self.endpoint = "cloud.fortebit.tech" + self.client = client(self.endpoint, device_token, ctx) + + def listen_rpc(self, callback): + """ +.. method:: listen_rpc(callback) + + Listen to incoming RPC requests that get reported to the specified :samp:`callback` function, + called as *callback(id, method, params)*: + + * :samp:`id` is the request identifier (number) + * :samp:`method` is the method identifier of the RPC (Remote Procedure Call) + * :samp:`params` is a dictionary containing the RPC method arguments (or parameters) + + Call :func:`send_rpc_reply` to provide the result of the RPC request. + + """ + self.client.on_rpc_request = callback + + def connect(self, retry=7): + """ +.. method:: connect() + + Setup a connection to the Fortebit Cloud. It can raise an exception in case of error. + + """ + if self.client.connect(): + return True + t = 3000 + while (retry > 0): + sleep(t) + if self.client.connect(): + return True + # increase (double) the delay between attempts (up to 1 minute) + if t < 30000: + t *= 2 + retry -= 1 + return False + + def is_connected(self): + """ +.. method:: is_connected() + + Returns the status of the connection to the Fortebit Cloud (reconnections are automatic). + + """ + return self.client.is_connected() + + def run(self): + """ +.. method:: run() + + Starts the device by executing the underlying client. It can start a new thread depending on the type of client (Mqtt vs Http) + + """ + self.client.loop() + + def publish_telemetry(self, values, ts=None): + """ +.. method:: publish_telemetry(values, ts) + + Publish :samp:`values` (dictionary) to the device telemetry, with optional timestamp :samp:`ts` (epoch in milliseconds). + + Return a boolean, *False* if the message cannot be sent. + """ + return self.client.publish_telemetry(values, ts) + + def publish_attributes(self, attributes): + """ +.. method:: publish_attributes(attributes) + + Publish :samp:`attributes` (dictionary) to the device *client* attributes. + + Return a boolean, *False* if the message cannot be sent. + """ + return self.client.publish_attributes(attributes) + + def get_attributes(self, client, shared=None, timeout=10000): + """ +.. method:: get_attributes(client, shared, timeout) + + Obtain the specified :samp:`client` and/or :samp:`shared` attributes from the device. + + Return a dictionary, *None* if the data could not be received. + """ + return self.client.get_attributes(client, shared, timeout) + + def send_rpc_reply(self, id, result): + """ +.. method:: send_rpc_reply(id, result) + + Publish :samp:`result` (dictionary) as a reply to the RPC request with identifier :samp:`id`. + + Return a boolean, *False* if the message cannot be sent. + """ + return self.client.send_rpc_reply(id, result) diff --git a/fortebit/iot/mqtt_client.py b/fortebit/iot/mqtt_client.py new file mode 100644 index 0000000..a90c25e --- /dev/null +++ b/fortebit/iot/mqtt_client.py @@ -0,0 +1,137 @@ +from lwmqtt import mqtt +import json +import streams +import threading +import mcu + +debug = False + + +def print_d(*args): + if debug: + print(*args) + + +class MqttClient(): + + def __init__(self, endpoint, device_token, ctx=None): + self.endpoint = endpoint + self.ctx = ctx + self.request_id = -1 + self.rpc_id = 10000 + self.driver = mqtt.Client("polaris", True) + self.driver.set_username_pw(device_token) + self.attr_ev = threading.Event() + self.attr_obj = None + + def _loop_failure(self, client): + while True: + try: + print_d("> reconnecting...") + client.reconnect() + break + except Exception as e: + print_d(e) + sleep(10000) + return mqtt.RECOVERED + + def _subscribe_cb(self, client): + # subscribe to attributes + client.subscribe("v1/devices/me/attributes/response/+", self._on_attributes, 1) + client.subscribe("v1/devices/me/rpc/request/+", self._on_rpc_request, 1) + + def connect(self): + port = 1883 if self.ctx is None else 8883 + try: + self.driver.connect(self.endpoint, 60, port=port, ssl_ctx=self.ctx, + loop_failure=self._loop_failure, aconnect_cb=self._subscribe_cb, start_loop=False) + except Exception as e: + print_d("failed", e) + return False + return True + + def is_connected(self): + return self.driver.connected() + + def loop(self): + self.driver.loop() + + def _on_attributes(self, client, payload, topic): + print_d("> got:", topic, payload) + # len("v1/devices/me/attributes/response/") = 34 + id = int(topic[34:]) + if self.request_id == id: + self.attr_obj = json.loads(payload) + self.attr_ev.set() + + def on_rpc_request(self, id, method, params): + pass + + def _on_rpc_request(self, client, payload, topic): + print_d("> got:", topic, payload) + # len("v1/devices/me/rpc/request/") = 26 + id = int(topic[26:]) + obj = json.loads(payload) + self.on_rpc_request(id, obj['method'], obj['params'] if 'params' in obj else None) + + def get_attributes(self, client, shared=None, timeout=10000): + self.request_id += 1 + self.attr_ev.clear() + obj = {} + if client and isinstance(client, PSTRING): + obj['clientKeys'] = client + else: + client = None + if shared and isinstance(shared, PSTRING): + obj['sharedKeys'] = shared + else: + shared = None + obj = json.dumps(obj) + ep = 'v1/devices/me/attributes/request/' + str(self.request_id) + print_d("publish:", ep, obj) + try: + self.driver.publish(ep, obj, qos=1) + obj = None + self.attr_ev.wait(timeout) + obj = self.attr_obj + except Exception as e: + print_d(e) + return None + if obj is None: + return None + if 'client' in obj: + client = obj['client'] + else: + client = None + if 'shared' in obj: + shared = obj['shared'] + else: + shared = None + return (client, shared) + + def publish_attributes(self, attributes): + try: + self.driver.publish('v1/devices/me/attributes', json.dumps(attributes), qos=1) + except Exception as e: + print_d(e) + return False + return True + + def publish_telemetry(self, values, ts=None): + if ts is not None: + values = {'values': values, 'ts': ts} + try: + self.driver.publish("v1/devices/me/telemetry", json.dumps(values), qos=1) + except Exception as e: + print_d(e) + return False + return True + + def send_rpc_reply(self, id, result): + print_d("rpc reply", id, result) + try: + self.driver.publish("v1/devices/me/rpc/response/" + str(id), json.dumps(result), qos=1) + except Exception as e: + print_d(e) + return False + return True diff --git a/fortebit/iot/z.yml b/fortebit/iot/z.yml new file mode 100644 index 0000000..2a5eca3 --- /dev/null +++ b/fortebit/iot/z.yml @@ -0,0 +1,14 @@ +--- +description: library for Fortebit IoT services +fullname: lib.fortebit.iot +info: + chip_manufacturer: fortebit + chip_name: '-' + name: Zerynth Fortebit IoT Library + type: Utility +keywords: +- fortebit +- iot +- cloud +title: Fortebit IoT Library +... diff --git a/fortebit/polaris/cloud.py b/fortebit/polaris/cloud.py new file mode 100644 index 0000000..12a9f57 --- /dev/null +++ b/fortebit/polaris/cloud.py @@ -0,0 +1,91 @@ +""" +.. module:: cloud + +************* +Polaris Cloud +************* + +This module provides easy access to the Fortebit Cloud features. + + """ + +import requests + + +def getAccessToken(imei, uid): + """ +.. function:: getAccessToken(imei, uid) + + Generates the board's own access token for Fortebit IoT cloud services. + + :param imei: The modem IMEI number (as a 15 characters string) + :param uid: The MCU unique identifier (as a 3 integers sequence) + """ + import struct + _cmap = 'bBcCdDeEfFgGkKmMnNpPrRsStTuUwWzZ' + a = int(imei[0:8]) + b = int(imei[3:11]) + c = int(imei[6:14]) + #print(len(uid),[hex(i) for i in mcu.uid()]) + u = struct.unpack("> 14)) & 0xFFFFF + b = ((a * b) + c) & 0xFFFFFFFF + b = (b ^ (b >> 14)) & 0xFFFFF + c = 'A' + c += _cmap[a & 0x1F] + _cmap[(a >> 5) & 0x1F] + _cmap[(a >> 10) & 0x1F] + _cmap[(a >> 15) & 0x1F] + c += _cmap[b & 0x1F] + _cmap[(b >> 5) & 0x1F] + _cmap[(b >> 10) & 0x1F] + _cmap[(b >> 15) & 0x1F] + c += '_' + for b in range(0, len(imei), 3): + a = int(imei[b:b + 3]) + c += _cmap[a & 0x1F] + _cmap[(a >> 5) & 0x1F] + return c + + +def isRegistered(device, email): + """ +.. function:: isRegistered(device, email) + + Check if the specified IoT device is registered to the Polaris Cloud services. + + :param device: a connected instance of :any:`fortebit.iot.Device` + :param email: the device owner's email address + """ + attributes = device.get_attributes(None, 'email') + if attributes and attributes[1] and 'email' in attributes[1] and attributes[1]['email'] == email: + return True + return False + + +def register(device, email, imei, ssl_ctx=None): + """ +.. function:: register(device, email, imei, ssl_ctx) + + Generates the board's own access token for Fortebit IoT cloud services. + + :param device: a connected instance of :any:`fortebit.iot.Device` + :param email: the device owner's email address + :param imei: the modem IMEI number (as a 15 characters string) + :param ssl_ctx: an optional SSL/TLS context (use HTTPS if present) + """ + if ssl_ctx: + url = "https://" + else: + url = "http://" + url += device.endpoint + "/script-polaris/register" + obj = {"email": email, "token": device.device_token, "imei": imei} + print("Register device", email, device.device_token, url, obj) + try: + res = requests.get(url, params=obj, ctx=ssl_ctx) + if res.status == 200: + return True + print("Registration error", res.status) + except Exception as e: + print(e) + return False diff --git a/fortebit/polaris/polaris.py b/fortebit/polaris/polaris.py new file mode 100644 index 0000000..8c1acad --- /dev/null +++ b/fortebit/polaris/polaris.py @@ -0,0 +1,511 @@ +""" +.. module:: polaris + +************** +Polaris Module +************** + +This module provides easy access to the Polaris board features, meaningful names +for MCU pins and peripherals, simplified initialization of on-board devices. + + """ + +# Main Connector +class main: + """ +.. class:: main + + Namespace for the **Main** connector signals and related peripherals: + + * ``PIN_VIN`` - analog input for main supply voltage + * ``PIN_IGNITION`` - digital input for ignition detection (active high) + * ``PIN_SOS`` - digital input for emergency button (active low) + * ``PIN_AIN1``, ``PIN_RANGE_IN1`` - analog input 1 and range selection pin + * ``PIN_AIN2``, ``PIN_RANGE_IN2`` - analog input 2 and range selection pin + * ``PIN_AIN3``, ``PIN_RANGE_IN3`` - analog input 3 and range selection pin + * ``PIN_AIN4``, ``PIN_RANGE_IN4`` - analog input 4 and range selection pin + * ``PIN_IOEXP_IN1`` - control input 1 for I/O Expander + * ``PIN_IOEXP_IN2`` - control input 2 for I/O Expander + + * ``ADC_VIN`` - ADC channel (using ``PIN_VIN``) + * ``ADC_IN1`` - ADC channel (using ``PIN_AIN1``) + * ``ADC_IN2`` - ADC channel (using ``PIN_AIN2``) + * ``ADC_IN3`` - ADC channel (using ``PIN_AIN3``) + * ``ADC_IN4`` - ADC channel (using ``PIN_AIN4``) + * ``PWM_IOEXP_IN1`` - PWM control input 1 for I/O Expander + * ``PWM_IOEXP_IN2`` - PWM control input 2 for I/O Expander + """ + PIN_VIN = D20 + + PIN_IGNITION = D44 + PIN_SOS = D71 + + PIN_AIN1 = D21 + PIN_AIN2 = D23 + PIN_AIN3 = D41 + PIN_AIN4 = D42 + + PIN_RANGE_IN4 = D62 + PIN_RANGE_IN3 = D64 + PIN_RANGE_IN2 = D72 + PIN_RANGE_IN1 = D73 + + PIN_IOEXP_IN1 = D22 + PIN_IOEXP_IN2 = D65 + + ADC_VIN = A4 + ADC_IN1 = A5 + ADC_IN2 = A6 + ADC_IN3 = A7 + ADC_IN4 = A8 + + PWM_IOEXP_IN1 = PWM13 + PWM_IOEXP_IN2 = PWM14 + +# MikroBus +class mikrobus: + """ +.. class:: mikrobus + + Namespace for the **mikroBUS** expansion interface signals and related peripherals: + + * ``PIN_MISO``, ``PIN_MOSI``, ``PIN_SCK``, ``PIN_CS`` - expansion SPI interface + * ``PIN_SDA``, ``PIN_SCL`` - expansion I2C interface + * ``PIN_TX``, ``PIN_RX`` - expansion UART pins + * ``PIN_RST``, ``PIN_INT`` - general purpose I/O pins (usually reset and interrupt) + * ``PIN_PWM`` - PWM capable pin + * ``PIN_AN`` - analog input pin + + * ``SERIAL`` - serial driver (using ``PIN_RX``,``PIN_TX``) + * ``SPI`` - SPI driver (using ``PIN_MISO``, ``PIN_MOSI``, ``PIN_SCK``) + * ``I2C`` - I2C driver (using ``PIN_SDA``, ``PIN_SCL``) + * ``PWM`` - PWM channel (using ``PIN_PWM``) + * ``ADC`` - ADC channel (using ``PIN_AN``) + """ + PIN_AN = D0 + PIN_RST = D1 + PIN_CS = D2 + PIN_SCK = D3 + PIN_MISO = D4 + PIN_MOSI = D5 + PIN_PWM = D6 + PIN_INT = D7 + PIN_RX = D8 + PIN_TX = D9 + PIN_SCL = D10 + PIN_SDA = D11 + + SERIAL = SERIAL1 + SPI = SPI0 + I2C = I2C0 + PWM = PWM2 + ADC = A0 + +# ExtBus +class extbus: + """ +.. class:: extbus + + Namespace for the **Ext-A** expansion interface signals and related peripherals: + + * ``PIN_GPIO1``, ``PIN_GPIO2`` - general purpose I/O pins + * ``PIN_TX``, ``PIN_RX`` - expansion UART pins + * ``PIN_PWM`` - PWM capable pin + * ``PIN_AN1`` - analog input pin + * ``PIN_DAC1``, ``PIN_DAC1`` - analog pins + + * ``SERIAL`` - serial driver (using ``PIN_RX``,``PIN_TX``) + * ``PWM`` - PWM channel (using ``PIN_PWM``) + * ``ADC`` - ADC channel (using ``PIN_AN1``) + """ + PIN_GPIO1 = D12 + PIN_GPIO2 = D13 + PIN_TX = D14 + PIN_RX = D15 + PIN_PWM = D16 + PIN_AN1 = D17 + PIN_DAC2 = D18 + PIN_DAC1 = D19 + + SERIAL = SERIAL2 + PWM = PWM5 + ADC = A1 + +# Internal signals +class internal: + """ +.. class:: internal + + Namespace for on-board devices signals and peripherals: + + * ``PIN_LED_RED``, ``PIN_LED_GREEN`` - LED control pins (active low) + * ``PIN_POWER_DIS`` - main power control pin (shutdown) + * ``PIN_5V_EN`` - control pin for 5V regulator + * ``PIN_BATT_EN`` - backup battery status pin + * ``PIN_IOEXP_CS`` - I/O Expander chip-select pin + * ``PIN_ACCEL_CS``, ``PIN_ACCEL_INT`` - accelerometer chip-select and interrupt pins + * ``PIN_CHARGE_PROG``, ``PIN_CHARGE_STAT`` - backup battery charger control and status pins + * ``PIN_BATT_ADC`` - analog input for backup battery voltage + + * ``SPI`` - on-board SPI driver (for accelerometer and I/O Expander) + * ``ADC_BATT`` - ADC channel (using ``PIN_BATT_ADC``) + """ + PIN_LED_RED = D35 + PIN_LED_GREEN = D63 + + PIN_POWER_DIS = D51 + PIN_BATT_EN = D69 + PIN_5V_EN = D70 + + PIN_CHARGE_PROG = D26 + PIN_CHARGE_STAT = D56 + PIN_BATT_ADC = D43 + + PIN_ACCEL_CS = D60 + PIN_ACCEL_INT = D61 + + PIN_IOEXP_CS = D76 + + SPI = SPI1 + PWM_CHARGE = PWM9 + ADC_BATT = A9 + +# Signals used for global positioning module +class gnss: + """ +.. class:: gnss + + Namespace for GNSS module signals and related peripherals: + + * ``PIN_STANDBY``, ``PIN_RESET`` - module control pins + * ``PIN_TX``, ``PIN_RX`` - module UART pins + * ``PIN_ANTON`` - control pin (used only in the NB-IoT variant with BG96) + + * ``SERIAL`` - serial driver (using ``PIN_RX``,``PIN_TX``) + """ + PIN_STANDBY = D45 + PIN_RESET = D59 + PIN_TX = D27 + PIN_RX = D28 + PIN_ANTON = D80 + + SERIAL = SERIAL4 + +# Signals used for GSM module +class gsm: + """ +.. class:: gsm + + Namespace for Modem signals and related peripherals: + + * ``PIN_TX``, ``PIN_RX`` - modem UART pins + * ``PIN_POWER``, ``PIN_KILL``, ``PIN_WAKE`` - modem control pins + * ``PIN_STATUS``, ``PIN_RING`` - modem status pins + + * ``SERIAL`` - serial driver (using ``PIN_RX``,``PIN_TX``) + """ + PIN_TX = D57 + PIN_RX = D58 + PIN_STATUS = D37 + PIN_POWER = D67 + PIN_WAKE = D68 + PIN_KILL = D38 + PIN_RING = D54 + + SERIAL = SERIAL3 + +# Public globals +CHARGE_NONE = 0 +CHARGE_BUSY = 1 +CHARGE_COMPLETE = 2 +IGNITION_OFF = 0 +IGNITION_ON = 1 +SOS_OFF = 0 +SOS_ON = 1 + +# Private globals +_PIN_NC = -1 +_ANALOG_VREF = 2.50 +_ANALOG_SCALE = 13.0154525 +_ANALOG_SCALE_LOW = 2.0 +_ADC2VOLT = _ANALOG_VREF * _ANALOG_SCALE / 4095 +_ADC2VOLT_LOW = _ANALOG_VREF * _ANALOG_SCALE_LOW / 4095 + +# Default initialization +import streams +streams.serial(SERIAL0) +import adc + +def init(): + """ +.. function:: init() + + Performs required initializion of Polaris pins and common functionalities. + It should be called at the start of your application. + """ + # Setup power + digitalWrite(internal.PIN_POWER_DIS, LOW) + pinMode(internal.PIN_POWER_DIS, OUTPUT) + digitalWrite(internal.PIN_POWER_DIS, LOW) + pinMode(internal.PIN_BATT_EN, INPUT) + pinMode(internal.PIN_5V_EN, OUTPUT) + digitalWrite(internal.PIN_5V_EN, HIGH) + pinMode(internal.PIN_CHARGE_STAT, INPUT) + pinMode(main.PIN_IGNITION, INPUT) + pinMode(main.PIN_SOS, INPUT) + pinMode(internal.PIN_CHARGE_PROG, OUTPUT) + digitalWrite(internal.PIN_CHARGE_PROG, HIGH) + # reboot on power supply input changes + if digitalRead(internal.PIN_BATT_EN) == LOW: + onPinRise(internal.PIN_BATT_EN, shutdown) + else: + onPinFall(internal.PIN_BATT_EN, shutdown) + +def isBatteryBackup(): + """ +.. function:: isBatteryBackup() + + Returns a boolean value to indicate whether the board is powered from the backup battery source. + """ + pinMode(internal.PIN_BATT_EN, INPUT) + return True if digitalRead(internal.PIN_BATT_EN) == HIGH else False + +def setBatteryCharger(enable): + """ +.. function:: setBatteryCharger(enable) + + Enables or disables the backup battery charger (5V required). + + :note: Do not enable the charger when the main power supply is not present + """ + pinMode(internal.PIN_CHARGE_PROG, OUTPUT) + digitalWrite(internal.PIN_CHARGE_PROG, LOW if enable else HIGH) + +def getChargerStatus(): + """ +.. function:: getChargerStatus() + + Returns the battery charger status (not charging, charging or fully charged). + + :returns: One of these values: ``CHARGE_NONE`` = 0, ``CHARGE_BUSY`` = 1, ``CHARGE_COMPLETE`` = 2 + """ + pinMode(internal.PIN_CHARGE_STAT, INPUT_PULLDOWN) + res = 1 if digitalRead(internal.PIN_CHARGE_STAT) == HIGH else 0 + pinMode(internal.PIN_CHARGE_STAT, INPUT_PULLUP) + res = (res << 1) + (1 if digitalRead(internal.PIN_CHARGE_STAT) == HIGH else 0) + if res == 0: + return CHARGE_BUSY + if res == 3: + return CHARGE_COMPLETE + return CHARGE_NONE + +def getIgnitionStatus(): + """ +.. function:: getIgnitionStatus() + + Reads the ignition status from digital input pin IGN/DIO5 (active high). + + :returns: An integer value to indicate whether the ignition switch is on/off: ``IGNITION_ON`` = 1 or ``IGNITION_OFF`` = 0 + """ + pinMode(main.PIN_IGNITION, INPUT_PULLUP) + if digitalRead(main.PIN_IGNITION) == LOW: + return IGNITION_ON + else: + return IGNITION_OFF + +def getEmergencyStatus(): + """ +.. function:: getEmergencyStatus() + + Reads the emergency button status from digital input pin SOS/DIO6 (active low). + + :returns: An integer value to indicate whether the emergency button is switched on/off: ``SOS_ON`` = 1 or ``SOS_OFF`` = 0 + """ + pinMode(main.PIN_SOS, INPUT_PULLDOWN) + if digitalRead(main.PIN_SOS) == HIGH: + return SOS_ON + else: + return SOS_OFF + +def shutdown(): + """ +.. function:: shutdown() + + Disables the main regulator or backup battery source, effectively power-cycling the board. + """ + pinMode(internal.PIN_POWER_DIS, OUTPUT) + digitalWrite(internal.PIN_POWER_DIS, HIGH) + +def GSM(): + """ +.. function:: GSM() + + Initializes the correct Modem library for the Polaris board variant and + returns the module object. + + :returns: ``UG96`` for *Polaris 3G*, ``M95`` for *Polaris 2G* (*Polaris NB-IoT* not supported yet) + """ + #-if TARGET == polaris_3g + from quectel.ug96 import ug96 + ug96.init(gsm.SERIAL, _PIN_NC, _PIN_NC, gsm.PIN_POWER, _PIN_NC, gsm.PIN_STATUS, gsm.PIN_KILL, 0) + return ug96 + #-else + ##-if TARGET == polaris_2g + from quectel.m95 import m95 + m95.init(gsm.SERIAL, _PIN_NC, _PIN_NC, gsm.PIN_POWER, gsm.PIN_KILL, gsm.PIN_STATUS, 1, 1, 0) + return m95 + ##-else + ###-if TARGET == polaris_nbiot + from quectel.bg96 import bg96 + bg96.init(gsm.SERIAL, _PIN_NC, _PIN_NC, gsm.PIN_POWER, gsm.PIN_KILL, gsm.PIN_STATUS, 0) + bg96.gnss_init(use_uart=1) + return bg96 + ###-else + raise UnsupportedError + ###-endif + ##-endif + #-endif + +def GNSS(): + """ +.. function:: GNSS() + + Creates an instance of the correct GNSS receiver class for the Polaris board variant. + + :returns: ``L76`` for *Polaris 3G* and *Polaris 2G* (*Polaris NB-IoT* not supported yet) + """ + #-if TARGET == polaris_3g + from quectel.l76 import l76 + l = l76.L76(gnss.SERIAL) + l.start(gnss.PIN_RESET) + return l + #-else + ##-if TARGET == polaris_2g + from quectel.l76 import l76 + l = l76.L76(gnss.SERIAL) + l.start(gnss.PIN_RESET) + return l + ##-else + ###-if TARGET == polaris_nbiot + from quectel.l76 import l76 + l = l76.L76(gnss.SERIAL,baud=115200) + pinMode(gnss.PIN_ANTON, OUTPUT) + digitalWrite(gnss.PIN_ANTON, HIGH) + l.start() + return l + ###-else + raise UnsupportedError + ###-endif + ##-endif + #-endif + +def QSpiFlash(): + """ +.. function:: QSpiFlash() + + Creates an instance of the on-board Quad-Spi Flash device class. + + :returns: ``QSpiFlash`` object configured for Polaris hardware + """ + import qspiflash + return qspiflash.QSpiFlash(D34,D33,D25,D24,D74,D75,0x100000,0x10000,0x8000,0x1000,0x100,8,4,4,2,4,0xA5,0x00,0x01,0x02,0x7C,0x80,0x02,0x80) + +def IOExpander(): + """ +.. function:: IOExpander() + + Creates an instance of the on-board I/O Expander device class (``NCV7240``). + + :returns: ``QSpiFlash`` object configured for Polaris hardware + """ + from onsemi.ncv7240 import ncv7240 + return ncv7240.NCV7240(internal.SPI, internal.PIN_IOEXP_CS) + +def Accelerometer(): + """ +.. function:: Accelerometer() + + Creates an instance of the on-board accelerometer device class (``LIS2HH12``). + + :returns: ``QSpiFlash`` object configured for Polaris hardware + """ + from stm.lis2hh12 import lis2hh12 + return lis2hh12.LIS2HH12(internal.SPI, internal.PIN_ACCEL_CS) + +def readMainVoltage(): + """ +.. function:: readMainVoltage() + + Returns the analog measure of the main supply voltage. + """ + pinMode(main.PIN_VIN, INPUT_ANALOG) + return 0.25 + analogRead(main.ADC_VIN) * _ADC2VOLT + +def readBattVoltage(): + """ +.. function:: readMainVoltage() + + Returns the analog measure of the backup battery voltage. + """ + pinMode(internal.PIN_BATT_ADC, INPUT_ANALOG) + return analogRead(internal.ADC_BATT) * _ADC2VOLT_LOW + +_AIN_ARRAY = ( + (main.ADC_IN1, main.PIN_RANGE_IN1), + (main.ADC_IN2, main.PIN_RANGE_IN2), + (main.ADC_IN3, main.PIN_RANGE_IN3), + (main.ADC_IN4, main.PIN_RANGE_IN4), +) + +def readAnalogInputVoltage(pin_num, range=HIGH): + """ +.. function:: readAnalogInputVoltage(pin_num, range=HIGH) + + Returns the voltage measure of an analog input pin on the **Main** connector. + + :param pin_num: Index of the analog pin (0-3 = corresponds to AIO1-4) + :param range: Full-scale range: *HIGH* (0-36V) or *LOW* (0-5V) + """ + pinMode(_AIN_ARRAY[pin_num][0], INPUT_ANALOG) + pinMode(_AIN_ARRAY[pin_num][1], OUTPUT) + digitalWrite(_AIN_ARRAY[pin_num][1], range) + a = analogRead(_AIN_ARRAY[pin_num][0]) + if range == HIGH: + return a * _ADC2VOLT + return a * _ADC2VOLT_LOW + +def ledRedOff(): + """ +.. function:: ledRedOff() + + Switch the red LED off. + """ + pinMode(internal.PIN_LED_RED, OUTPUT) + digitalWrite(internal.PIN_LED_RED, HIGH) + +def ledRedOn(): + """ +.. function:: ledRedOn() + + Switch the red LED on. + """ + pinMode(internal.PIN_LED_RED, OUTPUT) + digitalWrite(internal.PIN_LED_RED, LOW) + +def ledGreenOff(): + """ +.. function:: ledGreenOff() + + Switch the green LED off. + """ + pinMode(internal.PIN_LED_GREEN, OUTPUT) + digitalWrite(internal.PIN_LED_GREEN, HIGH) + +def ledGreenOn(): + """ +.. function:: ledGreenOn() + + Switch the green LED on. + """ + pinMode(internal.PIN_LED_GREEN, OUTPUT) + digitalWrite(internal.PIN_LED_GREEN, LOW) diff --git a/fortebit/polaris/z.yml b/fortebit/polaris/z.yml new file mode 100644 index 0000000..b7f316f --- /dev/null +++ b/fortebit/polaris/z.yml @@ -0,0 +1,13 @@ +--- +description: library for Fortebit Polaris boards +fullname: lib.fortebit.polaris +info: + chip_manufacturer: fortebit + chip_name: '-' + name: Zerynth Fortebit Polaris Library + type: Utility +keywords: +- fortebit +- polaris +title: Fortebit Polaris Library +...