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
+...