diff --git a/.gitignore b/.gitignore index beced4e..e943765 100644 --- a/.gitignore +++ b/.gitignore @@ -5,3 +5,15 @@ *.conf .DS_Store +pyscan/* +pysense/* +pysense2/* +pytrack/* +pytrack2/* + +pyscan.zip +pysense.zip +pysense2.zip +pytrack.zip +pytrack2.zip + diff --git a/Makefile b/Makefile new file mode 100644 index 0000000..17034ef --- /dev/null +++ b/Makefile @@ -0,0 +1,102 @@ +release: pyscan pysense pysense2 pytrack pytrack2 + + +pyscan: + rm -rf pyscan + rm -f pyscan.zip + @echo "Making Pyscan" + mkdir pyscan + mkdir pyscan/lib + #sensors + cp shields/lib/LIS2HH12.py pyscan/lib/ + cp shields/lib/MFRC630.py pyscan/lib/ + cp shields/lib/SI7006A20.py pyscan/lib/ + cp shields/lib/LTR329ALS01.py pyscan/lib/ + #pycoproc + cp shields/lib/pycoproc_1.py pyscan/lib/ + #example + cp shields/pyscan_1.py pyscan/main.py + + zip -r pyscan.zip pyscan + +pysense: + rm -rf pysense + rm -f pysense.zip + @echo "Making Pysense" + mkdir pysense + mkdir pysense/lib + # sensors + cp shields/lib/LIS2HH12.py pysense/lib/ + cp shields/lib/LTR329ALS01.py pysense/lib/ + cp shields/lib/MPL3115A2.py pysense/lib/ + cp shields/lib/SI7006A20.py pysense/lib/ + # pycoproc + cp shields/lib/pycoproc_1.py pysense/lib/ + # example + cp shields/pysense_1.py pysense/main.py + + zip -r pysense.zip pysense + +pysense2: + rm -rf pysense2 + rm -f pysense2.zip + @echo "Making Pysense 2" + mkdir pysense2 + mkdir pysense2/lib + # sensors + cp shields/lib/LIS2HH12.py pysense2/lib/ + cp shields/lib/LTR329ALS01.py pysense2/lib/ + cp shields/lib/MPL3115A2.py pysense2/lib/ + cp shields/lib/SI7006A20.py pysense2/lib/ + # pycoproc + cp shields/lib/pycoproc_2.py pysense2/lib/ + # example + cp shields/pysense_2.py pysense2/main.py + + zip -r pysense2.zip pysense2 + +pytrack: + rm -rf pytrack + rm -f pytrack.zip + @echo "Making Pytrack" + mkdir pytrack + mkdir pytrack/lib + #sensors + cp shields/lib/L76GNSS.py pytrack/lib/ + cp shields/lib/LIS2HH12.py pytrack/lib/ + #pycoproc + cp shields/lib/pycoproc_1.py pytrack/lib/ + #example + cp shields/pytrack_1.py pytrack/main.py + + zip -r pytrack.zip pytrack + +pytrack2: + rm -rf pytrack2 + rm -f pytrack2.zip + @echo "Making Pytrack2" + mkdir pytrack2 + mkdir pytrack2/lib + #sensors + cp shields/lib/L76GNSS.py pytrack2/lib/ + cp shields/lib/LIS2HH12.py pytrack2/lib/ + #pycoproc + cp shields/lib/pycoproc_2.py pytrack2/lib/ + #example + cp shields/pytrack_2.py pytrack2/main.py + + zip -r pytrack2.zip pytrack2 + +clean: + @echo "Cleaning up files" + rm -rf pyscan + rm -rf pysense + rm -rf pysense2 + rm -rf pytrack + rm -rf pytrack2 + + rm -f pyscan.zip + rm -f pysense.zip + rm -f pysense2.zip + rm -f pytrack.zip + rm -f pytrack2.zip \ No newline at end of file diff --git a/README.md b/README.md index 178f449..ce4e46c 100644 --- a/README.md +++ b/README.md @@ -3,19 +3,12 @@ # Pycom Libraries and Examples ## Introduction -This repository contains out of the box examples for Pycom devices, including Pysense, Pytrack, and Pyscan. +This repository contains libraries and out of the box examples for Pycom devices, including the Shields: Pysense, Pytrack, and Pyscan. ## Table of Contents * [Examples](/examples) * [Libraries](/lib) -* [Pysense](/pysense) -* [Pytrack](/pytrack) -* [Pyscan](/pyscan) -* [Pybytes](/pybytes) - -## Pysense, Pytrack, Pyscan - -To install the required libraries for Pysense, Pytrack, and Pyscan, please download this repo as a ZIP file. You will then be able to extract and upload the library files to your Pycom device (via FTP or Pymakr Plugin). Please see [docs.pycom.io](https://docs.pycom.io) for more information. +* [Shields](/shields) ## Links * [Pycom](https://pycom.io) diff --git a/examples/OTA-lorawan/LoraServer.py b/examples/OTA-lorawan/LoraServer.py index 263947d..8382ee1 100644 --- a/examples/OTA-lorawan/LoraServer.py +++ b/examples/OTA-lorawan/LoraServer.py @@ -1,6 +1,6 @@ #!/usr/bin/env python # -# Copyright (c) 2019, Pycom Limited. +# Copyright (c) 2020, Pycom Limited. # # This software is licensed under the GNU GPL version 3 or any # later version, with permitted additional terms. For more information @@ -17,9 +17,9 @@ login_payload = { "password": "string", - "username": "string" + "email": "string" } - + mcGroup_payload = { "multicastGroup": { "dr": 0, @@ -35,7 +35,7 @@ "serviceProfileID": "string" } } - + mcQueue_payload = { "multicastQueueItem": { "data": "string", @@ -44,43 +44,43 @@ "multicastGroupID": "string" } } - + mcAddDevice_payload = { "devEUI": "string", "multicastGroupID": "string" } - + class LoraServerClient: - + def __init__(self): self.server = config.LORASERVER_URL self.port = config.LORASERVER_API_PORT - self.username = config.LORASERVER_USER + self.email = config.LORASERVER_EMAIL self.passwd = config.LORASERVER_PASS - + def login(self): url = self.server + ':' + str(self.port) + '/api/internal/login' - + login_payload["password"] = self.passwd - login_payload["username"] = self.username - + login_payload["email"] = self.email + payload = bytes(json.dumps(login_payload),'utf-8') - + try: r = urllib.request.Request(url, data= payload, method= 'POST') r.add_header("Content-Type", "application/json") r.add_header("Accept", "application/json") - + with urllib.request.urlopen(r) as f: return json.loads(f.read().decode('utf-8'))['jwt'] - + except Exception as ex: print("Error getting the jwt: {}".format(ex)) - + return None - + def parse_service_profile_list(self, response, profile_name): - + try: json_obj = json.loads(response) for sp_obj in json_obj["result"]: @@ -88,52 +88,52 @@ def parse_service_profile_list(self, response, profile_name): return sp_obj["id"] except Exception as ex: print("Error parsing service profile list: {}".format(ex)) - + return None - + def request_service_profile_id(self, profile_name, jwt): url = self.server + ':' + str(self.port) + '/api/service-profiles?limit=100' - + try: r = urllib.request.Request(url, method= 'GET') r.add_header("Accept", "application/json") r.add_header("Grpc-Metadata-Authorization", "Bearer " + jwt) - + with urllib.request.urlopen(r) as f: return self.parse_service_profile_list(f.read().decode('utf-8'), profile_name) - + except Exception as ex: print("Error getting service profile id: {}".format(ex)) - + return None - + def create_multicast_group(self, dr, freq, group_name, serviceProfileID, jwt): - + group_id = self.generate_random_id() mcAddr = self.generate_randon_addr() mcAppSKey = self.generate_random_key() mcNwkSKey = self.generate_random_key() - + url = self.server + ':' + str(self.port) + '/api/multicast-groups' - + mcGroup_payload["multicastGroup"]["dr"] = dr mcGroup_payload["multicastGroup"]["frequency"] = freq mcGroup_payload["multicastGroup"]["id"] = group_id.decode("utf-8") - mcGroup_payload["multicastGroup"]["mcAddr"] = mcAddr.decode("utf-8") - mcGroup_payload["multicastGroup"]["mcAppSKey"] = mcAppSKey.decode("utf-8") - mcGroup_payload["multicastGroup"]["mcNwkSKey"] = mcNwkSKey.decode("utf-8") + mcGroup_payload["multicastGroup"]["mcAddr"] = mcAddr.decode("utf-8") + mcGroup_payload["multicastGroup"]["mcAppSKey"] = mcAppSKey.decode("utf-8") + mcGroup_payload["multicastGroup"]["mcNwkSKey"] = mcNwkSKey.decode("utf-8") mcGroup_payload["multicastGroup"]["name"] = group_name mcGroup_payload["multicastGroup"]["serviceProfileID"] = serviceProfileID - + payload = bytes(json.dumps(mcGroup_payload),'utf-8') - - + + try: r = urllib.request.Request(url, data= payload, method= 'POST') r.add_header("Content-Type", "application/json") r.add_header("Accept", "application/json") r.add_header("Grpc-Metadata-Authorization", "Bearer " + jwt) - + with urllib.request.urlopen(r) as f: resp = f.read().decode('utf-8') if '"id":' in resp: @@ -143,56 +143,56 @@ def create_multicast_group(self, dr, freq, group_name, serviceProfileID, jwt): return None except Exception as ex: print("Error creating multicast data: {}".format(ex)) - + return None - + def delete_multicast_group(self, group_id): - + url = self.server + ':' + str(self.port) + '/api/multicast-groups/' + group_id - + try: r = urllib.request.Request(url, method = 'DELETE') - + with urllib.request.urlopen(r) as f: return f.getcode() == 200 - + except Exception as ex: print("Error deleting multicast group: {}".format(ex)) - + return False - + def add_device_multicast_group(self, devEUI, group_id, jwt): - + url = self.server + ':' + str(self.port) + '/api/multicast-groups/' + group_id + '/devices' - + mcAddDevice_payload["devEUI"] = devEUI mcAddDevice_payload["multicastGroupID"] = group_id - + payload = json.dumps(mcAddDevice_payload).encode('utf-8') - + try: r = urllib.request.Request(url, data= payload, method= 'POST') r.add_header("Content-Type", "application/json") r.add_header("Accept", "application/json") r.add_header("Grpc-Metadata-Authorization", "Bearer " + jwt) - + with urllib.request.urlopen(r) as f: return f.getcode() == 200 - + except Exception as ex: print("Error adding device to multicast group: {}".format(ex)) - + return False - + def request_multicast_keys(self, group_id, jwt): - + url = self.server + ':' + str(self.port) + '/api/multicast-groups/' + group_id - + try: r = urllib.request.Request(url, method= 'GET') r.add_header("Accept", "application/json") r.add_header("Grpc-Metadata-Authorization", "Bearer " + jwt) - + with urllib.request.urlopen(r) as f: resp = f.read().decode('utf-8') if "mcNwkSKey" in resp: @@ -202,27 +202,27 @@ def request_multicast_keys(self, group_id, jwt): return None except Exception as ex: print("Error getting multicast keys: {}".format(ex)) - + return None - + def generate_randon_addr(self): return binascii.hexlify(os.urandom(4)) - + def generate_random_key(self): return binascii.hexlify(os.urandom(16)) - + def generate_random_id(self): return binascii.hexlify(os.urandom(4)) + b'-' + binascii.hexlify(os.urandom(2)) + b'-' + binascii.hexlify(os.urandom(2)) \ + b'-' + binascii.hexlify(os.urandom(2)) + b'-' + binascii.hexlify(os.urandom(6)) - + def multicast_queue_length(self, jwt, multicast_group): url = self.server + ':' + str(self.port) + '/api/multicast-groups/' + multicast_group + '/queue' - + try: r = urllib.request.Request(url, method= 'GET') r.add_header("Content-Type", "application/json") r.add_header("Grpc-Metadata-Authorization", "Bearer " + jwt) - + with urllib.request.urlopen(r) as f: resp = f.read().decode('utf-8') if "multicastQueueItems" in resp: @@ -232,35 +232,31 @@ def multicast_queue_length(self, jwt, multicast_group): return len(json_resp) else: return -1 - + except Exception as ex: print("Error getting multicast queue length: {}".format(ex)) - + return -1 - - + + def send(self, jwt, multicast_group, data): url = self.server + ':' + str(self.port) + '/api/multicast-groups/' + multicast_group + '/queue' - + mcQueue_payload["multicastQueueItem"]["data"] = base64.b64encode(data).decode("utf-8") mcQueue_payload["multicastQueueItem"]["multicastGroupID"] = multicast_group - + payload = bytes(json.dumps(mcQueue_payload),'utf-8') - + try: r = urllib.request.Request(url, data= payload, method= 'POST') r.add_header("Content-Type", "application/json") r.add_header("Accept", "application/json") r.add_header("Grpc-Metadata-Authorization", "Bearer " + jwt) - + with urllib.request.urlopen(r) as f: return f.getcode() == 200 - + except Exception as ex: print("Error sending multicast data: {}".format(ex)) - - return False - - - + return False diff --git a/examples/OTA-lorawan/config.py b/examples/OTA-lorawan/config.py index 31ac593..b4e1239 100644 --- a/examples/OTA-lorawan/config.py +++ b/examples/OTA-lorawan/config.py @@ -1,6 +1,6 @@ #!/usr/bin/env python # -# Copyright (c) 2019, Pycom Limited. +# Copyright (c) 2020, Pycom Limited. # # This software is licensed under the GNU GPL version 3 or any # later version, with permitted additional terms. For more information @@ -13,7 +13,7 @@ LORASERVER_URL = 'http://localhost/' LORASERVER_MQTT_PORT = 1883 LORASERVER_API_PORT = 8080 -LORASERVER_USER = 'admin' +LORASERVER_EMAIL = 'admin' LORASERVER_PASS = 'admin' LORASERVER_SERVICE_PROFILE = 'ota_sp' @@ -23,4 +23,3 @@ #update configuration UPDATE_DELAY = 300 - diff --git a/examples/OTA-lorawan/diff_match_patch.py b/examples/OTA-lorawan/diff_match_patch.py index 0d2f225..7404534 100644 --- a/examples/OTA-lorawan/diff_match_patch.py +++ b/examples/OTA-lorawan/diff_match_patch.py @@ -1,6 +1,6 @@ #!/usr/bin/env python # -# Copyright (c) 2019, Pycom Limited. +# Copyright (c) 2020, Pycom Limited. # # This software is licensed under the GNU GPL version 3 or any # later version, with permitted additional terms. For more information diff --git a/examples/OTA-lorawan/groupUpdater.py b/examples/OTA-lorawan/groupUpdater.py index 2de44df..98bca0a 100644 --- a/examples/OTA-lorawan/groupUpdater.py +++ b/examples/OTA-lorawan/groupUpdater.py @@ -1,6 +1,6 @@ #!/usr/bin/env python # -# Copyright (c) 2019, Pycom Limited. +# Copyright (c) 2020, Pycom Limited. # # This software is licensed under the GNU GPL version 3 or any # later version, with permitted additional terms. For more information diff --git a/examples/OTA-lorawan/ota.py b/examples/OTA-lorawan/ota.py index f6b2642..f9482ad 100644 --- a/examples/OTA-lorawan/ota.py +++ b/examples/OTA-lorawan/ota.py @@ -1,6 +1,6 @@ #!/usr/bin/env python # -# Copyright (c) 2019, Pycom Limited. +# Copyright (c) 2020, Pycom Limited. # # This software is licensed under the GNU GPL version 3 or any # later version, with permitted additional terms. For more information @@ -24,7 +24,7 @@ class OTAHandler: MSG_HEADER = b'$OTA' MSG_TAIL = b'*' MSG_END = b'' - + FULL_UPDATE = b'F' DIFF_UPDATE = b'D' NO_UPDATE = b'N' @@ -51,41 +51,41 @@ def __init__(self): self._latest_version = '0.0.0' self._v_lock = threading.Lock() self.firmware_dir = './firmware' - + self._next_update = -1 self._update_timer = None self._update_delay = config.UPDATE_DELAY self._device_dict = dict() self._keys_dict = dict() - + self._clientApp = LoraServerClient() self._loraserver_jwt = None - + self._service_profile = config.LORASERVER_SERVICE_PROFILE self._downlink_datarate = config.LORASERVER_DOWNLINK_DR self._downlink_freq = config.LORASERVER_DOWNLINK_FREQ - + self._m_th = threading.Thread(target=self._firmware_monitor) self._m_th.start() - + self.multicast_updaters = [] self._updater_lock = threading.Lock() - + def stop(self): self._exit = True def set_mqtt_client(self, client): self.p_client = client - + def _firmware_monitor(self): self._loraserver_jwt = self._clientApp.login() while not self._exit: with self._v_lock: self._latest_version = self._check_version() - + time.sleep(5) - + def process_rx_msg(self, payload): dev_eui = self.get_device_eui(payload) @@ -98,36 +98,36 @@ def process_rx_msg(self, payload): self._send_multicast_keys(dev_eui) elif msg_type == self.LISTENING_MSG: self._send_listening_reply(dev_eui) - + def _send_listening_reply(self, dev_eui): - + msg = bytearray() msg.extend(self.MSG_HEADER) msg.extend(b',' + str(self.LISTENING_REPLY).encode()) msg.extend(b',' + self.MSG_TAIL) - + self.send_payload(dev_eui, msg) - + def _send_multicast_keys(self, dev_eui): - + msg = bytearray() msg.extend(self.MSG_HEADER) msg.extend(b',' + str(self.MULTICAST_KEY_REPLY).encode()) - + if dev_eui in self._device_dict: multicast_param_key = self._device_dict[dev_eui] multicast_param = self._keys_dict[multicast_param_key] - + msg.extend(b',' + multicast_param[1]) msg.extend(b',' + multicast_param[2]) msg.extend(b',' + multicast_param[3]) else: msg.extend(b',,,') - + msg.extend(b',' + self.MSG_TAIL) - + self.send_payload(dev_eui, msg) - + def get_device_eui(self, payload): dev_eui = None try: @@ -136,7 +136,7 @@ def get_device_eui(self, payload): print("Exception extracting device eui") return dev_eui - + def get_msg_type(self, msg): msg_type = -1 @@ -146,7 +146,7 @@ def get_msg_type(self, msg): print("Exception getting message type") return msg_type - + def decode_device_msg(self, payload): dev_msg = None try: @@ -155,28 +155,28 @@ def decode_device_msg(self, payload): except Exception as ex: print("Exception decoding device message") return dev_msg - + def _create_multicast_group(self, update_info): service_id = self._clientApp.request_service_profile_id(self._service_profile, self._loraserver_jwt) - + group_name = update_info.replace(',','-') multicast_param = self._clientApp.create_multicast_group(self._downlink_datarate, self._downlink_freq, group_name, service_id, self._loraserver_jwt) - + return multicast_param - + def _init_update_params(self, dev_eui, dev_version, latest_version): if self._next_update <= 0: self._next_update = int(time.time()) + self._update_delay self._update_timer = threading.Timer(self._update_delay, self.update_proc) self._update_timer.start() - + update_info = dev_version.strip() + ',' + latest_version.strip() self._device_dict[dev_eui] = update_info if update_info not in self._keys_dict: multicast_param = self._create_multicast_group(update_info) self._keys_dict[update_info] = multicast_param self._clientApp.add_device_multicast_group(dev_eui, multicast_param[0], self._loraserver_jwt) - + def _send_update_info(self, dev_eui, msg): print(msg) dev_version = self.get_device_version(msg) @@ -188,7 +188,7 @@ def _send_update_info(self, dev_eui, msg): self._init_update_params(dev_eui, dev_version, version) msg = self._create_update_info_msg(version, dev_version) self.send_payload(dev_eui, msg) - + def get_device_version(self, msg): dev_version = None try: @@ -197,7 +197,7 @@ def get_device_version(self, msg): print("Exception extracting device version") return dev_version - + def _check_version(self): latest = '0.0.0' for d in os.listdir(self.firmware_dir): @@ -205,47 +205,47 @@ def _check_version(self): continue if latest is None or LooseVersion(latest) < LooseVersion(d): latest = d - + return latest - + def get_latest_version(self): with self._v_lock: return self._latest_version - + def is_empty_multicast_queue(self, jwt, multicast_group_id): queue_length =self._clientApp.multicast_queue_length(jwt, multicast_group_id) if queue_length > 0: return False else: return True - + def clear_multicast_group(self, dict_key): with self._updater_lock: if dict_key in self._keys_dict: group_id = self._keys_dict[dict_key][0] self._clientApp.delete_multicast_group(group_id) del self._keys_dict[dict_key] - + self._device_dict = {key:val for key, val in self._device_dict.items() if val != dict_key} - + for updater in self.multicast_updaters: if updater.tag == dict_key: self.multicast_updaters.remove(updater) - + if len(self.multicast_updaters) == 0: self._next_update = -1 self._update_timer = None - + def update_proc(self): - + for dict_key in self._keys_dict: dev_version = dict_key.split(',')[0] latest_version = dict_key.split(',')[1] multicast_group_id = self._keys_dict[dict_key][0] upater = updateHandler(dev_version, latest_version, self._clientApp, self._loraserver_jwt, multicast_group_id, self) - + self.multicast_updaters.append(upater) - + def _get_update_type(self, need_updating, device_version): update_type = b',' + self.NO_UPDATE print(os.path.isdir(self.firmware_dir + '/' + device_version)) @@ -254,9 +254,9 @@ def _get_update_type(self, need_updating, device_version): return b',' + self.DIFF_UPDATE else: return b',' + self.FULL_UPDATE - + return update_type - + def _create_update_info_msg(self, version, device_version): msg = bytearray() msg.extend(self.MSG_HEADER) @@ -272,8 +272,10 @@ def _create_update_info_msg(self, version, device_version): msg.extend(b',' + str(int(time.time())).encode()) msg.extend(b',' + self.MSG_TAIL) return msg - + def send_payload(self, dev_eui, data): b64Data = base64.b64encode(data) payload = '{"reference": "abcd1234" ,"fPort":1,"data": "' + b64Data.decode() + '"}' - self.p_client.publish(topic="application/" + str(config.LORASERVER_APP_ID) + "/device/" + dev_eui + "/tx",payload=payload) + topic = "application/" + str(config.LORASERVER_APP_ID) + "/device/" + dev_eui + "/command/down" + print('Publish to {} : {}'.format(topic, payload)) + self.p_client.publish(topic=topic,payload=payload) diff --git a/examples/OTA-lorawan/requirements.txt b/examples/OTA-lorawan/requirements.txt new file mode 100644 index 0000000..620ac93 --- /dev/null +++ b/examples/OTA-lorawan/requirements.txt @@ -0,0 +1 @@ +paho_mqtt==1.5.1 diff --git a/examples/OTA-lorawan/updaterService.py b/examples/OTA-lorawan/updaterService.py index 54aa8cb..9da347a 100644 --- a/examples/OTA-lorawan/updaterService.py +++ b/examples/OTA-lorawan/updaterService.py @@ -1,6 +1,6 @@ #!/usr/bin/env python # -# Copyright (c) 2019, Pycom Limited. +# Copyright (c) 2020, Pycom Limited. # # This software is licensed under the GNU GPL version 3 or any # later version, with permitted additional terms. For more information @@ -22,7 +22,7 @@ def sigint_handler(signum, frame): global exit exit = True print("Terminating Lora OTA updater") - + def on_message(mosq, ota, msg): print("{} {} {}".format(msg.topic, msg.qos, msg.payload)) ota.process_rx_msg(msg.payload.decode()) @@ -32,19 +32,19 @@ def on_publish(mosq, obj, mid): if __name__ == '__main__': signal.signal(signal.SIGINT, sigint_handler) - + ota = OTAHandler() client = paho.Client(userdata=ota) client.connect(config.LORASERVER_IP, config.LORASERVER_MQTT_PORT, 60) - + client.on_message = on_message client.on_publish = on_publish - - client.subscribe("application/+/device/+/rx", 0) + + client.subscribe("application/+/device/+/event/up", 0) ota.set_mqtt_client(client) - + while client.loop() == 0 and not exit: pass diff --git a/examples/lorawan-nano-gateway/nanogateway.py b/examples/lorawan-nano-gateway/nanogateway.py index 76d6c92..1810cd5 100644 --- a/examples/lorawan-nano-gateway/nanogateway.py +++ b/examples/lorawan-nano-gateway/nanogateway.py @@ -442,7 +442,7 @@ def _udp_thread(self): except usocket.timeout: pass except OSError as ex: - if ex.errno != errno.EAGAIN: + if ex.args[0] != errno.EAGAIN: self._log('UDP recv OSError Exception: {}', ex) except Exception as ex: self._log('UDP recv Exception: {}', ex) diff --git a/lib/lora_mesh/loramesh.py b/lib/lora_mesh/loramesh.py deleted file mode 100644 index 571c6e8..0000000 --- a/lib/lora_mesh/loramesh.py +++ /dev/null @@ -1,164 +0,0 @@ -#!/usr/bin/env python -# -# Copyright (c) 2019, Pycom Limited. -# -# This software is licensed under the GNU GPL version 3 or any -# later version, with permitted additional terms. For more information -# see the Pycom Licence v1.0 document supplied with this file, or -# available at https://www.pycom.io/opensource/licensing -# - -from network import LoRa -import socket -import time -import utime -import ubinascii -import pycom - -__version__ = '2' - -class Loramesh: - """ Class for using Lora Mesh - openThread """ - - STATE_DISABLED = const(0) - STATE_DETACHED = const(1) - STATE_CHILD = const(2) - STATE_ROUTER = const(3) - STATE_LEADER = const(4) - STATE_LEADER_SINGLE = const(5) - - # rgb LED color for each state: disabled, detached, child, router, leader and single leader - RGBLED = [0x0A0000, 0x0A0000, 0x0A0A0A, 0x000A00, 0x0A000A, 0x000A0A] - - # address to be used for multicasting - MULTICAST_MESH_ALL = 'ff03::1' - MULTICAST_MESH_FTD = 'ff03::2' - - MULTICAST_LINK_ALL = 'ff02::1' - MULTICAST_LINK_FTD = 'ff02::2' - - def __init__(self, lora=None): - """ Constructor """ - if lora is None: - self.lora = LoRa(mode=LoRa.LORA, region=LoRa.EU868, bandwidth=LoRa.BW_125KHZ, sf=7) - else: - self.lora = lora - self.mesh = self.lora.Mesh() - self.rloc = '' - self.ip_eid = '' - self.ip_link = '' - self.single = True - self.state = STATE_DISABLED - self.ip_others = [] - - def _state_update(self): - """ Returns the Thread role """ - self.state = self.mesh.state() - if self.state < 0: - self.state = self.STATE_DISABLED - return self.state - - def _rloc_ip_net_addr(self): - """ returns the family part of RLOC IPv6, without last word (2B) """ - self.net_addr = ':'.join(self.rloc.split(':')[:-1]) + ':' - return self.net_addr - - def _update_ips(self): - """ Updates all the unicast IPv6 of the Thread interface """ - self.ip_others = [] - ips = self.mesh.ipaddr() - self.rloc16 = self.mesh.rloc() - for line in ips: - if line.startswith('fd'): - # Mesh-Local unicast IPv6 - try: - addr = int(line.split(':')[-1], 16) - except Exception: - continue - if addr == self.rloc16: - # found RLOC - # RLOC IPv6 has x:x:x:x:0:ff:fe00:RLOC16 - self.rloc = line - elif ':0:ff:fe00:' not in line: - # found Mesh-Local EID - self.ip_eid = line - elif line.startswith('fe80'): - # Link-Local - self.ip_link = line - else: - self.ip_others.append(line) - - def is_connected(self): - """ Returns true if it is connected as either Child, Router or Leader """ - connected = False - self.state = self.mesh.state() - if self.state in (STATE_CHILD, STATE_ROUTER, STATE_LEADER, STATE_LEADER_SINGLE): - connected = True - return connected - - def led_state(self): - """ Sets the LED according to the Thread role """ - if self.state == STATE_LEADER and self.mesh.single(): - pycom.rgbled(self.RGBLED[self.STATE_LEADER_SINGLE]) - else: - pycom.rgbled(self.RGBLED[self.state]) - - # returns the IP ML-EID or the ip having this prefix - def ip(self, prefix = None): - """ Returns the IPv6 RLOC """ - ip = self._update_ips() - if prefix is None: - return self.ip_eid - # we need to check al IPs from self.ip_others that may start with prefix - p = prefix.split("::")[0] - for ip in self.ip_others: - if ip.startswith(p): - return ip - return None - - def neighbors(self): - """ Returns a list with all properties of the neighbors """ - return self.mesh.neighbors() - - def neighbors_ip(self): - """ Returns a list with IPv6 (as strings) of the neighbors """ - neighbors = self.neighbors() - nei_list = [] - net_ip = self._rloc_ip_net_addr() - if neighbors is not None: - for nei_rec in neighbors: - nei_ip = net_ip + hex(nei_rec.rloc16)[2:] - nei_list.append(nei_ip) - return nei_list - - def ipaddr(self): - """ Returns a list with all unicast IPv6 """ - return self.mesh.ipaddr() - - def cli(self, command): - """ Simple wrapper for OpenThread CLI """ - return self.mesh.cli(command) - - def ping(self, ip): - """ Returns ping return time, to an IP """ - res = self.cli('ping ' + ip) - # '8 bytes from fdde:ad00:beef:0:0:ff:fe00:e000: icmp_seq=2 hlim=64 time=236ms\r\n' - # 'Error 6: Parse\r\n' - # no answer - ret_time = -1 - try: - ret_time = int(res.split('time=')[1].split('ms')[0]) - except Exception: - pass - return ret_time - - def blink(self, num = 3, period = .5, color = None): - """ LED blink """ - if color is None: - color = self.RGBLED[self.state] - for i in range(0, num): - pycom.rgbled(0) - time.sleep(period) - pycom.rgbled(color) - time.sleep(period) - self.led_state() diff --git a/lib/lora_mesh/main.py b/lib/lora_mesh/main.py deleted file mode 100644 index 15b127a..0000000 --- a/lib/lora_mesh/main.py +++ /dev/null @@ -1,106 +0,0 @@ -#!/usr/bin/env python -# -# Copyright (c) 2019, Pycom Limited. -# -# This software is licensed under the GNU GPL version 3 or any -# later version, with permitted additional terms. For more information -# see the Pycom Licence v1.0 document supplied with this file, or -# available at https://www.pycom.io/opensource/licensing -# - -from network import LoRa -import socket -import time -import utime -import ubinascii -import pycom -import machine - -from loramesh import Loramesh - -pycom.wifi_on_boot(False) -pycom.heartbeat(False) - -lora = LoRa(mode=LoRa.LORA, region=LoRa.EU868, bandwidth=LoRa.BW_125KHZ, sf=7) -MAC = str(ubinascii.hexlify(lora.mac()))[2:-1] -print("LoRa MAC: %s"%MAC) - -mesh = Loramesh(lora) - -# waiting until it connected to Mesh network -while True: - mesh.led_state() - print("%d: State %s, single %s"%(time.time(), mesh.cli('state'), mesh.cli('singleton'))) - time.sleep(2) - if not mesh.is_connected(): - continue - - print('Neighbors found: %s'%mesh.neighbors()) - break - -# create UDP socket -sockets = [] -s = socket.socket(socket.AF_LORA, socket.SOCK_RAW) -myport = 1234 -s.bind(myport) -sockets.append(s) - -# handler responisble for receiving packets on UDP Pymesh socket -def receive_pack(sockets): - # listen for incomming packets - while True: - rcv_data, rcv_addr = s.recvfrom(128) - if len(rcv_data) == 0: - break - rcv_ip = rcv_addr[0] - rcv_port = rcv_addr[1] - print('Incomming %d bytes from %s (port %d)'%(len(rcv_data), rcv_ip, rcv_port)) - print(rcv_data) - # could send some ACK pack: - if rcv_data.startswith("Hello"): - try: - s.sendto('ACK ' + MAC + ' ' + str(rcv_data)[2:-1], (rcv_ip, rcv_port)) - except Exception: - pass - mesh.blink(7, .3) - -pack_num = 1 -msg = "Hello World! MAC: " + MAC + ", pack: " -ip = mesh.ip() -mesh.mesh.rx_cb(receive_pack,sockets) - -# infinite main loop -while True: - mesh.led_state() - print("%d: State %s, single %s, IP %s"%(time.time(), mesh.cli('state'), mesh.cli('singleton'), mesh.ip())) - - # check if topology changes, maybe RLOC IPv6 changed - new_ip = mesh.ip() - if ip != new_ip: - print("IP changed from: %s to %s"%(ip, new_ip)) - ip = new_ip - - # update neighbors list - neigbors = mesh.neighbors_ip() - print("%d neighbors, IPv6 list: %s"%(len(neigbors), neigbors)) - - # send PING and UDP packets to all neighbors - for neighbor in neigbors: - if mesh.ping(neighbor) > 0: - print('Ping OK from neighbor %s'%neighbor) - mesh.blink(10, .1) - else: - print('Ping not received from neighbor %s'%neighbor) - - time.sleep(10) - - pack_num = pack_num + 1 - try: - s.sendto(msg + str(pack_num), (neighbor, myport)) - print('Sent message to %s'%(neighbor)) - except Exception: - pass - time.sleep(20 + machine.rng()%20) - - # random sleep time - time.sleep(30 + machine.rng()%30) diff --git a/lib/lora_mesh/main_border_router.py b/lib/lora_mesh/main_border_router.py deleted file mode 100644 index e86ed01..0000000 --- a/lib/lora_mesh/main_border_router.py +++ /dev/null @@ -1,123 +0,0 @@ -#!/usr/bin/env python -# -# Copyright (c) 2019, Pycom Limited. -# -# This software is licensed under the GNU GPL version 3 or any -# later version, with permitted additional terms. For more information -# see the Pycom Licence v1.0 document supplied with this file, or -# available at https://www.pycom.io/opensource/licensing -# - -from network import LoRa -import ubinascii -from loramesh import Loramesh -import pycom -import time -import socket -import struct - -BORDER_ROUTER_HEADER_FORMAT = '!BHHHHHHHHH' -BORDER_ROUTER_MAGIC_BYTE = 0xBB - -pycom.wifi_on_boot(False) -pycom.heartbeat(False) -border_router_net = "2001:dead:beef:cafe::/64" - -lora = LoRa(mode=LoRa.LORA, region=LoRa.EU868, bandwidth=LoRa.BW_125KHZ, sf=7) -#mesh = lora.Mesh() -MAC = str(ubinascii.hexlify(lora.mac()))[2:-1] -print("LoRa MAC: %s"%MAC) - -mesh = Loramesh(lora) - -# waiting until it connected to Mesh network -while True: - mesh.led_state() - print("%d: State %s, single %s"%(time.time(), mesh.cli('state'), mesh.cli('singleton'))) - time.sleep(2) - if not mesh.is_connected(): - continue - - print('Neighbors found: %s'%mesh.neighbors()) - print('IPs: %s'%mesh.mesh.ipaddr()) - break - -sockets = [] -#add BR for a certain MAC address -# or in a certain condition (Wifi/BLE/cellular connection to Internet) -if int(MAC, 16) == 8: - if len(mesh.mesh.border_router()) == 0: - mesh.mesh.border_router(border_router_net, 0) - print("Set Border Router with prefix %s"%border_router_net) - - # create UDP socket for Border Router interface - br_socket = socket.socket(socket.AF_LORA, socket.SOCK_RAW) - myport = 1234 - print("Please wait until BR gets propagated to the Leader ...") - while True: - ip = mesh.ip(border_router_net) - if ip is not None: - br_socket.bind((ip, myport)) - print("Created socked for (%s, %d)"%(ip, myport)) - break - time.sleep(1) - sockets.append(br_socket) - -eid_socket = socket.socket(socket.AF_LORA, socket.SOCK_RAW) -myport = 1235 -#ip = mesh.ip()# ipv6 EID -ip = "::" # in this case, socket can be bind just on a port, like: eid_socket.bind(myport) -if ip is not None: - eid_socket.bind((ip, myport)) - #eid_socket.bind(myport) - print("Created socked for (%s, %d)"%(ip, myport)) -sockets.append(eid_socket) - -# handler responsible for receiving packets on UDP Pymesh socket -def receive_pack(sockets): - # listen for incoming packets on all sockets - while True: - is_new_data = False - for sock in sockets: - # check if data received on all sockets - rcv_data, rcv_addr = sock.recvfrom(128) - if len(rcv_data) > 0: - is_new_data = True - break # out of for sock - if not is_new_data: - break # out of while True - rcv_ip = rcv_addr[0] - rcv_port = rcv_addr[1] - print('Incoming %d bytes from %s (port %d)'%(len(rcv_data), rcv_ip, rcv_port)) - - #check if data is for the external of the Pymesh (for The Cloud) - if rcv_data[0] == BORDER_ROUTER_MAGIC_BYTE and len(rcv_data) >= struct.calcsize(BORDER_ROUTER_HEADER_FORMAT): - br_header = struct.unpack(BORDER_ROUTER_HEADER_FORMAT, rcv_data) - print("IP dest: %x:%x:%x:%x:%x:%x:%x:%x (port %d)"%( - br_header[1],br_header[2],br_header[3],br_header[4], - br_header[5],br_header[6],br_header[7],br_header[8], br_header[9])) - rcv_data = rcv_data[struct.calcsize(BORDER_ROUTER_HEADER_FORMAT):] - - print(rcv_data) - - # send some ACK - if not rcv_data.startswith("ACK"): - print("Sent ACK back") - sock.sendto('ACK', (rcv_ip, rcv_port)) - -mesh.mesh.rx_cb(receive_pack, sockets) - -print('IPs: %s'%mesh.mesh.ipaddr()) -print('BRs: %s'%mesh.mesh.border_router()) - -""" -Example of usage: -* send data to the cloud -eid_socket.sendto("01234567890123456789", ("1::2", 1235)) -* send data to the EID ip of another Node inside Pymesh -eid_socket.sendto("0123456789", ("fdde:ad00:beef:0:4623:91c8:64b2:d9ec", 1235)) -* send data to the Leader -eid_socket.sendto("0123456789", ("fdde:ad00:beef:0:0:ff:fe00:fc00", 1235)) -* send data to the RLOC of another Node inside Pymesh -eid_socket.sendto("0123456789", ("fdde:ad00:beef:0:0:ff:fe00:6800", 1235)) -""" diff --git a/lib/mqtt_aws/MQTTClient.py b/lib/mqtt_aws/MQTTClient.py new file mode 100644 index 0000000..f1a5bff --- /dev/null +++ b/lib/mqtt_aws/MQTTClient.py @@ -0,0 +1,426 @@ +import MQTTConst as mqttConst +import MQTTMsgHandler as msgHandler +import time +import struct +import _thread + +class MQTTMessage: + def __init__(self): + self.timestamp = 0 + self.state = 0 + self.dup = False + self.mid = 0 + self.topic = "" + self.payload = None + self.qos = 0 + self.retain = False + +class MQTTClient: + + def __init__(self, clientID, cleanSession, protocol): + self.client_id = clientID + self._cleanSession = cleanSession + self._protocol = protocol + self._userdata = None + self._user = "" + self._password = "" + self._keepAliveInterval = 60 + self._will = False + self._will_topic = "" + self._will_message= None + self._will_qos = 0 + self._will_retain = False + self._connectdisconnectTimeout = 30 + self._mqttOperationTimeout = 5 + self._topic_callback_queue=[] + self._callback_mutex=_thread.allocate_lock() + self._pid = 0 + self._subscribeSent = False + self._unsubscribeSent = False + self._baseReconnectTimeSecond=1 + self._maximumReconnectTimeSecond=32 + self._minimumConnectTimeSecond=20 + self._msgHandler=msgHandler.MsgHandler(self._recv_callback, self.connect) + + def getClientID(self): + return self.client_id + + def configEndpoint(self, srcHost, srcPort): + self._msgHandler.setEndpoint(srcHost, srcPort) + + def configCredentials(self, srcCAFile, srcKey, srcCert): + self._msgHandler.setCredentials(srcCAFile, srcKey, srcCert) + + def setConnectDisconnectTimeoutSecond(self, srcConnectDisconnectTimeout): + self._connectdisconnectTimeout = srcConnectDisconnectTimeout + + def setMQTTOperationTimeoutSecond(self, srcMQTTOperationTimeout): + self._mqttOperationTimeout = srcMQTTOperationTimeout + self._msgHandler.setOperationTimeout(srcMQTTOperationTimeout) + + def clearLastWill(self): + self._will = False + self._will_topic = "" + self._will_message= None + self._will_qos = 0 + self._will_retain = False + + def setLastWill(self, topic, payload=None, QoS=0, retain=False): + self._will=True + self._will_qos = QoS + self._will_retain = retain + self._will_topic = topic.encode('utf-8') + + if isinstance(payload, bytearray): + self._will_message=payload + elif isinstance(payload, str): + self._will_message=payload.encode('utf-8') + elif isinstance(payload, int) or isinstance(payload, float): + self._will_message=str(payload) + + def configIAMCredentials(self, srcAWSAccessKeyID, srcAWSSecretAccessKey, srcAWSSessionToken): + raise NotImplementedError ('Websockets not supported') + + def setOfflinePublishQueueing(self, srcQueueSize, srcDropBehavior): + if srcDropBehavior != mqttConst.DROP_OLDEST and srcDropBehavior != mqttConst.DROP_NEWEST: + raise ValueError("Invalid packet drop behavior") + self._msgHandler.setOfflineQueueConfiguration(srcQueueSize, srcDropBehavior) + + def setDrainingIntervalSecond(self, srcDrainingIntervalSecond): + self._msgHandler.setDrainingInterval(srcDrainingIntervalSecond) + + def setBackoffTiming(self, srcBaseReconnectTimeSecond, srcMaximumReconnectTimeSecond, srcMinimumConnectTimeSecond): + self._baseReconnectTimeSecond=srcBaseReconnectTimeSecond + self._maximumReconnectTimeSecond=srcMaximumReconnectTimeSecond + self._minimumConnectTimeSecond=srcMinimumConnectTimeSecond + + def connect(self, keepAliveInterval=30): + self._keepAliveInterval = keepAliveInterval + + if not self._msgHandler.createSocketConnection(): + return False + + self._send_connect(self._keepAliveInterval, self._cleanSession) + + # delay to check the state + count_10ms = 0 + while(count_10ms <= self._connectdisconnectTimeout * 100 and not self._msgHandler.isConnected()): + count_10ms += 1 + time.sleep(0.01) + + return True if self._msgHandler.isConnected() else False + + def subscribe(self, topic, qos, callback): + if (topic is None or callback is None): + raise TypeError("Invalid subscribe values.") + topic = topic.encode('utf-8') + + header = mqttConst.MSG_SUBSCRIBE | (1<<1) + pkt = bytearray([header]) + + pkt_len = 2 + 2 + len(topic) + 1 # packet identifier + len of topic (16 bits) + topic len + QOS + pkt.extend(self._encode_varlen_length(pkt_len)) # len of the remaining + + self._pid += 1 + pkt.extend(self._encode_16(self._pid)) + pkt.extend(self._pascal_string(topic)) + pkt.append(qos) + + self._subscribeSent = False + self._msgHandler.push_on_send_queue(pkt) + + count_10ms = 0 + while(count_10ms <= self._mqttOperationTimeout * 100 and not self._subscribeSent): + count_10ms += 1 + time.sleep(0.01) + + if self._subscribeSent: + self._callback_mutex.acquire() + self._topic_callback_queue.append((topic, callback)) + self._callback_mutex.release() + return True + + return False + + def publish(self, topic, payload, qos, retain, dup=False): + topic = topic.encode('utf-8') + payload = payload.encode('utf-8') + + header = mqttConst.MSG_PUBLISH | (dup << 3) | (qos << 1) | retain + pkt_len = (2 + len(topic) + + (2 if qos else 0) + + (len(payload))) + + pkt = bytearray([header]) + pkt.extend(self._encode_varlen_length(pkt_len)) # len of the remaining + pkt.extend(self._pascal_string(topic)) + if qos: + self._pid += 1 #todo: I don't think this is the way to deal with the packet id + pkt.extend(self._encode_16(self._pid)) + + pkt = pkt + payload + self._msgHandler.push_on_send_queue(pkt) + + def _encode_16(self, x): + return struct.pack("!H", x) + + def _pascal_string(self, s): + return struct.pack("!H", len(s)) + s + + def _encode_varlen_length(self, length): + i = 0 + buff = bytearray() + while 1: + buff.append(length % 128) + length = length // 128 + if length > 0: + buff[i] = buff[i] | 0x80 + i += 1 + else: + break + + return buff + + def _topic_matches_sub(self, sub, topic): + result = True + multilevel_wildcard = False + + slen = len(sub) + tlen = len(topic) + + if slen > 0 and tlen > 0: + if (sub[0] == '$' and topic[0] != '$') or (topic[0] == '$' and sub[0] != '$'): + return False + + spos = 0 + tpos = 0 + + while spos < slen and tpos < tlen: + if sub[spos] == topic[tpos]: + if tpos == tlen-1: + # Check for e.g. foo matching foo/# + if spos == slen-3 and sub[spos+1] == '/' and sub[spos+2] == '#': + result = True + multilevel_wildcard = True + break + + spos += 1 + tpos += 1 + + if tpos == tlen and spos == slen-1 and sub[spos] == '+': + spos += 1 + result = True + break + else: + if sub[spos] == '+': + spos += 1 + while tpos < tlen and topic[tpos] != '/': + tpos += 1 + if tpos == tlen and spos == slen: + result = True + break + + elif sub[spos] == '#': + multilevel_wildcard = True + if spos+1 != slen: + result = False + break + else: + result = True + break + + else: + result = False + break + + if not multilevel_wildcard and (tpos < tlen or spos < slen): + result = False + + return result + + def _remove_topic_callback(self, topic): + deleted=False + + self._callback_mutex.acquire() + for i in range(0, len(self._topic_callback_queue)): + if self._topic_callback_queue[i][0] == topic: + self._topic_callback_queue.pop(i) + deleted=True + self._callback_mutex.release() + + return deleted + + def unsubscribe(self, topic): + self._unsubscribeSent = False + self._send_unsubscribe(topic, False) + + count_10ms = 0 + while(count_10ms <= self._mqttOperationTimeout * 100 and not self._unsubscribeSent): + count_10ms += 1 + time.sleep(0.01) + + if self._unsubscribeSent: + topic = topic.encode('utf-8') + return self._remove_topic_callback(topic) + + return False + + def disconnect(self): + pkt = struct.pack('!BB', mqttConst.MSG_DISCONNECT, 0) + self._msgHandler.push_on_send_queue(pkt) + + time.sleep(self._connectdisconnectTimeout) + self._msgHandler.disconnect() + + return True + + def _send_connect(self, keepalive, clean_session): + msg_sent = False + + pkt_len = (12 + len(self.client_id) + # 10 + 2 + len(client_id) + (2 + len(self._user) if self._user else 0) + + (2 + len(self._password) if self._password else 0)) + + flags = (0x80 if self._user else 0x00) | (0x40 if self._password else 0x00) | (0x02 if clean_session else 0x00) + + if self._will_message: + flags |= (self._will_retain << 3 | self._will_qos << 1 | 1) << 2 + pkt_len += 4 + len(self._will_topic) + len(self._will_message) + + pkt = bytearray([mqttConst.MSG_CONNECT]) # connect + pkt.extend(self._encode_varlen_length(pkt_len)) # len of the remaining + pkt.extend(b'\x00\x04MQTT\x04') # len of "MQTT" (16 bits), protocol name, and protocol version + pkt.append(flags) + pkt.extend(b'\x00\x00') # disable keepalive + pkt.extend(self._pascal_string(self.client_id)) + if self._will_message: + pkt.extend(self._pascal_string(self._will_topic)) + pkt.extend(self._pascal_string(self._will_message)) + if self._user: + pkt.extend(self._pascal_string(self._user)) + if self._password: + pkt.extend(self._pascal_string(self._password)) + + return self._msgHandler.priority_send(pkt) + + def _send_unsubscribe(self, topic, dup=False): + pkt = bytearray() + msg_type = mqttConst.MSG_UNSUBSCRIBE | (dup<<3) | (1<<1) + pkt.extend(struct.pack("!B", msg_type)) + + remaining_length = 2 + 2 + len(topic) + pkt.extend(self._encode_varlen_length(remaining_length)) + + self._pid += 1 + pkt.extend(self._encode_16(self._pid)) + pkt.extend(self._pascal_string(topic)) + + return self._msgHandler.push_on_send_queue(pkt) + + def _send_puback(self, msg_id): + remaining_length = 2 + pkt = struct.pack('!BBH', mqttConst.MSG_PUBACK, remaining_length, msg_id) + + return self._msgHandler.push_on_send_queue(pkt) + + def _send_pubrec(self, msg_id): + remaining_length = 2 + pkt = struct.pack('!BBH', mqttConst.MSG_PUBREC, remaining_length, msg_id) + + return self._msgHandler.push_on_send_queue(pkt) + + def _parse_connack(self, payload): + if len(payload) != 2: + return False + + (flags, result) = struct.unpack("!BB", payload) + + if result == 0: + self._msgHandler.setConnectionState(mqttConst.STATE_CONNECTED) + return True + else: + self._msgHandler.setConnectionState(mqttConst.STATE_DISCONNECTED) + return False + + def _parse_suback(self, payload): + self._subscribeSent = True + print('Subscribed to topic') + + return True + + def _parse_puback(self, payload): + return True + + def _notify_message(self, message): + notified = False + self._callback_mutex.acquire() + for t_obj in self._topic_callback_queue: + if self._topic_matches_sub(t_obj[0], message.topic): + t_obj[1](self, self._userdata, message) + notified = True + self._callback_mutex.release() + + return notified + + def _parse_publish(self, cmd, packet): + msg = MQTTMessage() + msg.dup = (cmd & 0x08)>>3 + msg.qos = (cmd & 0x06)>>1 + msg.retain = (cmd & 0x01) + + pack_format = "!H" + str(len(packet)-2) + 's' + (slen, packet) = struct.unpack(pack_format, packet) + pack_format = '!' + str(slen) + 's' + str(len(packet)-slen) + 's' + (msg.topic, packet) = struct.unpack(pack_format, packet) + + if len(msg.topic) == 0: + return False + + if msg.qos > 0: + pack_format = "!H" + str(len(packet)-2) + 's' + (msg.mid, packet) = struct.unpack(pack_format, packet) + + msg.payload = packet + + if msg.qos == 0: + self._notify_message(msg) + elif msg.qos == 1: + self._send_puback(msg.mid) + self._notify_message(msg) + elif msg.qos == 2: + self._send_pubrec(msg.mid) + self._notify_message(msg) + else: + return False + + return True + + def _parse_unsuback(self, payload): + self._unsubscribeSent = True + return True + + def _parse_pingresp(self): + self._msgHandler.setPingFlag(True) + return True + + def _recv_callback(self, cmd, payload): + msg_type = cmd & 0xF0 + + if msg_type == mqttConst.MSG_CONNACK: + return self._parse_connack(payload) + elif msg_type == mqttConst.MSG_SUBACK: + return self._parse_suback(payload) + elif msg_type == mqttConst.MSG_PUBACK: + return self._parse_puback(payload) + elif msg_type == mqttConst.MSG_PUBLISH: + return self._parse_publish(cmd, payload) + elif msg_type == mqttConst.MSG_UNSUBACK: + return self._parse_unsuback(payload) + elif msg_type == mqttConst.MSG_PINGRESP: + return self._parse_pingresp() + else: + print('Unknown message type: %d' % msg_type) + return False + + def insertShadowCallback(self, callback, payload, status, token): + self._msgHandler.insertShadowCallback(callback, payload, status, token) diff --git a/lib/mqtt_aws/MQTTConst.py b/lib/mqtt_aws/MQTTConst.py new file mode 100644 index 0000000..4c6d07b --- /dev/null +++ b/lib/mqtt_aws/MQTTConst.py @@ -0,0 +1,46 @@ + +# - Protocol types +MQTTv3_1 = 3 +MQTTv3_1_1 = 4 + +# - OfflinePublishQueueing drop behavior +DROP_OLDEST = 0 +DROP_NEWEST = 1 + +# Message types +MSG_CONNECT = 0x10 +MSG_CONNACK = 0x20 +MSG_PUBLISH = 0x30 +MSG_PUBACK = 0x40 +MSG_PUBREC = 0x50 +MSG_PUBREL = 0x60 +MSG_PUBCOMP = 0x70 +MSG_SUBSCRIBE = 0x80 +MSG_SUBACK = 0x90 +MSG_UNSUBSCRIBE = 0xA0 +MSG_UNSUBACK = 0xB0 +MSG_PINGREQ = 0xC0 +MSG_PINGRESP = 0xD0 +MSG_DISCONNECT = 0xE0 + +# Connection state +STATE_CONNECTED = 0x01 +STATE_CONNECTING = 0x02 +STATE_DISCONNECTED = 0x03 + +class UUID: + int_ = int + bytes_ = bytes + + def __init__(self, bytes=None, version=None): + + self._int = UUID.int_.from_bytes(bytes, 'big') + + self._int &= ~(0xc000 << 48) + self._int |= 0x8000 << 48 + self._int &= ~(0xf000 << 64) + self._int |= version << 76 + + @property + def urn(self): + return 'urn:uuid:' + str(self) diff --git a/lib/mqtt_aws/MQTTDeviceShadow.py b/lib/mqtt_aws/MQTTDeviceShadow.py new file mode 100644 index 0000000..6ea1489 --- /dev/null +++ b/lib/mqtt_aws/MQTTDeviceShadow.py @@ -0,0 +1,233 @@ +import MQTTConst as mqttConst +from machine import Timer +import json +import os +import _thread + +class _basicJSONParser: + + def setString(self, srcString): + self._rawString = srcString + self._dictionObject = None + + def regenerateString(self): + return json.dumps(self._dictionaryObject) + + def getAttributeValue(self, srcAttributeKey): + return self._dictionaryObject.get(srcAttributeKey) + + def setAttributeValue(self, srcAttributeKey, srcAttributeValue): + self._dictionaryObject[srcAttributeKey] = srcAttributeValue + + def validateJSON(self): + try: + self._dictionaryObject = json.loads(self._rawString) + except ValueError: + return False + return True + +class deviceShadow: + def __init__(self, srcShadowName, srcIsPersistentSubscribe, srcShadowManager): + + if srcShadowName is None or srcIsPersistentSubscribe is None or srcShadowManager is None: + raise TypeError("None type inputs detected.") + self._shadowName = srcShadowName + # Tool handler + self._shadowManagerHandler = srcShadowManager + self._basicJSONParserHandler = _basicJSONParser() + # Properties + self._isPersistentSubscribe = srcIsPersistentSubscribe + self._lastVersionInSync = -1 # -1 means not initialized + self._isGetSubscribed = False + self._isUpdateSubscribed = False + self._isDeleteSubscribed = False + self._shadowSubscribeCallbackTable = dict() + self._shadowSubscribeCallbackTable["get"] = None + self._shadowSubscribeCallbackTable["delete"] = None + self._shadowSubscribeCallbackTable["update"] = None + self._shadowSubscribeCallbackTable["delta"] = None + self._shadowSubscribeStatusTable = dict() + self._shadowSubscribeStatusTable["get"] = 0 + self._shadowSubscribeStatusTable["delete"] = 0 + self._shadowSubscribeStatusTable["update"] = 0 + self._tokenPool = dict() + self._dataStructureLock = _thread.allocate_lock() + + def _doNonPersistentUnsubscribe(self, currentAction): + self._shadowManagerHandler.shadowUnsubscribe(self._shadowName, currentAction) + + def _generalCallback(self, client, userdata, message): + # In Py3.x, message.payload comes in as a bytes(string) + # json.loads needs a string input + self._dataStructureLock.acquire() + currentTopic = message.topic + currentAction = self._parseTopicAction(currentTopic) # get/delete/update/delta + currentType = self._parseTopicType(currentTopic) # accepted/rejected/delta + payloadUTF8String = message.payload.decode('utf-8') + # get/delete/update: Need to deal with token, timer and unsubscribe + if currentAction in ["get", "delete", "update"]: + # Check for token + self._basicJSONParserHandler.setString(payloadUTF8String) + if self._basicJSONParserHandler.validateJSON(): # Filter out invalid JSON + currentToken = self._basicJSONParserHandler.getAttributeValue(u"clientToken") + if currentToken is not None and currentToken in self._tokenPool.keys(): # Filter out JSON without the desired token + # Sync local version when it is an accepted response + if currentType == "accepted": + incomingVersion = self._basicJSONParserHandler.getAttributeValue(u"version") + # If it is get/update accepted response, we need to sync the local version + if incomingVersion is not None and incomingVersion > self._lastVersionInSync and currentAction != "delete": + self._lastVersionInSync = incomingVersion + # If it is a delete accepted, we need to reset the version + else: + self._lastVersionInSync = -1 # The version will always be synced for the next incoming delta/GU-accepted response + # Cancel the timer and clear the token + self._tokenPool[currentToken].cancel() + del self._tokenPool[currentToken] + # Need to unsubscribe? + self._shadowSubscribeStatusTable[currentAction] -= 1 + if not self._isPersistentSubscribe and self._shadowSubscribeStatusTable.get(currentAction) <= 0: + self._shadowSubscribeStatusTable[currentAction] = 0 + self._doNonPersistentUnsubscribe(currentAction) + # Custom callback + if self._shadowSubscribeCallbackTable.get(currentAction) is not None: + self._shadowManagerHandler.insertShadowCallback(self._shadowSubscribeCallbackTable[currentAction], payloadUTF8String, currentType, currentToken) + # delta: Watch for version + else: + currentType += "/" + self._parseTopicShadowName(currentTopic) + # Sync local version + self._basicJSONParserHandler.setString(payloadUTF8String) + if self._basicJSONParserHandler.validateJSON(): # Filter out JSON without version + incomingVersion = self._basicJSONParserHandler.getAttributeValue(u"version") + if incomingVersion is not None and incomingVersion > self._lastVersionInSync: + self._lastVersionInSync = incomingVersion + # Custom callback + if self._shadowSubscribeCallbackTable.get(currentAction) is not None: + self._shadowManagerHandler.insertShadowCallback(self._shadowSubscribeCallbackTable[currentAction], payloadUTF8String, currentType, None) + self._dataStructureLock.release() + + def _parseTopicAction(self, srcTopic): + ret = None + fragments = srcTopic.decode('utf-8').split('/') + if fragments[5] == "delta": + ret = "delta" + else: + ret = fragments[4] + return ret + + def _parseTopicType(self, srcTopic): + fragments = srcTopic.decode('utf-8').split('/') + return fragments[5] + + def _parseTopicShadowName(self, srcTopic): + fragments = srcTopic.decode('utf-8').split('/') + return fragments[2] + + def _timerHandler(self, args): + srcActionName = args[0] + srcToken = args[1] + + self._dataStructureLock.acquire() + # Remove the token + del self._tokenPool[srcToken] + # Need to unsubscribe? + self._shadowSubscribeStatusTable[srcActionName] -= 1 + if not self._isPersistentSubscribe and self._shadowSubscribeStatusTable.get(srcActionName) <= 0: + self._shadowSubscribeStatusTable[srcActionName] = 0 + self._shadowManagerHandler.shadowUnsubscribe(self._shadowName, srcActionName) + # Notify time-out issue + if self._shadowSubscribeCallbackTable.get(srcActionName) is not None: + self._shadowSubscribeCallbackTable[srcActionName]("REQUEST TIME OUT", "timeout", srcToken) + self._dataStructureLock.release() + + def shadowGet(self, srcCallback, srcTimeout): + self._dataStructureLock.acquire() + # Update callback data structure + self._shadowSubscribeCallbackTable["get"] = srcCallback + # Update number of pending feedback + self._shadowSubscribeStatusTable["get"] += 1 + # clientToken + currentToken = mqttConst.UUID(bytes=os.urandom(16), version=4).urn[9:] + self._tokenPool[currentToken] = None + self._basicJSONParserHandler.setString("{}") + self._basicJSONParserHandler.validateJSON() + self._basicJSONParserHandler.setAttributeValue("clientToken", currentToken) + currentPayload = self._basicJSONParserHandler.regenerateString() + self._dataStructureLock.release() + # Two subscriptions + if not self._isPersistentSubscribe or not self._isGetSubscribed: + self._shadowManagerHandler.shadowSubscribe(self._shadowName, "get", self._generalCallback) + self._isGetSubscribed = True + # One publish + self._shadowManagerHandler.shadowPublish(self._shadowName, "get", currentPayload) + # Start the timer + self._tokenPool[currentToken] = Timer.Alarm(self._timerHandler, srcTimeout,arg=("get", currentToken),periodic=False) + return currentToken + + def shadowDelete(self, srcCallback, srcTimeout): + self._dataStructureLock.acquire() + # Update callback data structure + self._shadowSubscribeCallbackTable["delete"] = srcCallback + # Update number of pending feedback + self._shadowSubscribeStatusTable["delete"] += 1 + # clientToken + currentToken = mqttConst.UUID(bytes=os.urandom(16), version=4).urn[9:] + self._tokenPool[currentToken] = None + self._basicJSONParserHandler.setString("{}") + self._basicJSONParserHandler.validateJSON() + self._basicJSONParserHandler.setAttributeValue("clientToken", currentToken) + currentPayload = self._basicJSONParserHandler.regenerateString() + self._dataStructureLock.release() + # Two subscriptions + if not self._isPersistentSubscribe or not self._isDeleteSubscribed: + self._shadowManagerHandler.shadowSubscribe(self._shadowName, "delete", self._generalCallback) + self._isDeleteSubscribed = True + # One publish + self._shadowManagerHandler.shadowPublish(self._shadowName, "delete", currentPayload) + # Start the timer + self._tokenPool[currentToken] = Timer.Alarm(self._timerHandler,srcTimeout, arg=("delete", currentToken), periodic=False) + return currentToken + + def shadowUpdate(self, srcJSONPayload, srcCallback, srcTimeout): + # Validate JSON + JSONPayloadWithToken = None + currentToken = None + self._basicJSONParserHandler.setString(srcJSONPayload) + if self._basicJSONParserHandler.validateJSON(): + self._dataStructureLock.acquire() + # clientToken + currentToken = mqttConst.UUID(bytes=os.urandom(16), version=4).urn[9:] + self._tokenPool[currentToken] = None + self._basicJSONParserHandler.setAttributeValue("clientToken", currentToken) + JSONPayloadWithToken = self._basicJSONParserHandler.regenerateString() + # Update callback data structure + self._shadowSubscribeCallbackTable["update"] = srcCallback + # Update number of pending feedback + self._shadowSubscribeStatusTable["update"] += 1 + self._dataStructureLock.release() + # Two subscriptions + if not self._isPersistentSubscribe or not self._isUpdateSubscribed: + self._shadowManagerHandler.shadowSubscribe(self._shadowName, "update", self._generalCallback) + self._isUpdateSubscribed = True + # One publish + self._shadowManagerHandler.shadowPublish(self._shadowName, "update", JSONPayloadWithToken) + # Start the timer + self._tokenPool[currentToken] = Timer.Alarm(self._timerHandler, srcTimeout, arg=("update", currentToken), periodic=False) + else: + raise ValueError("Invalid JSON file.") + return currentToken + + def shadowRegisterDeltaCallback(self, srcCallback): + self._dataStructureLock.acquire() + # Update callback data structure + self._shadowSubscribeCallbackTable["delta"] = srcCallback + self._dataStructureLock.release() + # One subscription + self._shadowManagerHandler.shadowSubscribe(self._shadowName, "delta", self._generalCallback) + + def shadowUnregisterDeltaCallback(self): + self._dataStructureLock.acquire() + # Update callback data structure + del self._shadowSubscribeCallbackTable["delta"] + self._dataStructureLock.release() + # One unsubscription + self._shadowManagerHandler.shadowUnsubscribe(self._shadowName, "delta") diff --git a/lib/mqtt_aws/MQTTLib.py b/lib/mqtt_aws/MQTTLib.py new file mode 100644 index 0000000..49dc0a5 --- /dev/null +++ b/lib/mqtt_aws/MQTTLib.py @@ -0,0 +1,119 @@ +import MQTTConst as mqttConst +import MQTTClient as mqttClient +import MQTTShadowManager as shadowManager +import MQTTDeviceShadow as deviceShadow + +class AWSIoTMQTTClient: + + def __init__(self, clientID, protocolType=mqttConst.MQTTv3_1_1, useWebsocket=False, cleanSession=True): + self._mqttClient = mqttClient.MQTTClient(clientID, cleanSession, protocolType) + + # Configuration APIs + def configureLastWill(self, topic, payload, QoS): + self._mqttClient.setLastWill(topic, payload, QoS) + + def clearLastWill(self): + self._mqttClient.clearLastWill() + + def configureEndpoint(self, hostName, portNumber): + self._mqttClient.configEndpoint(hostName, portNumber) + + def configureIAMCredentials(self, AWSAccessKeyID, AWSSecretAccessKey, AWSSessionToken=""): + self._mqttClient.configIAMCredentials(AWSAccessKeyID, AWSSecretAccessKey, AWSSessionToken) + + def configureCredentials(self, CAFilePath, KeyPath="", CertificatePath=""): # Should be good for MutualAuth certs config and Websocket rootCA config + self._mqttClient.configCredentials(CAFilePath, KeyPath, CertificatePath) + + def configureAutoReconnectBackoffTime(self, baseReconnectQuietTimeSecond, maxReconnectQuietTimeSecond, stableConnectionTimeSecond): + self._mqttClient.setBackoffTiming(baseReconnectQuietTimeSecond, maxReconnectQuietTimeSecond, stableConnectionTimeSecond) + + def configureOfflinePublishQueueing(self, queueSize, dropBehavior=mqttConst.DROP_NEWEST): + self._mqttClient.setOfflinePublishQueueing(queueSize, dropBehavior) + + def configureDrainingFrequency(self, frequencyInHz): + self._mqttClient.setDrainingIntervalSecond(1/float(frequencyInHz)) + + def configureConnectDisconnectTimeout(self, timeoutSecond): + self._mqttClient.setConnectDisconnectTimeoutSecond(timeoutSecond) + + def configureMQTTOperationTimeout(self, timeoutSecond): + self._mqttClient.setMQTTOperationTimeoutSecond(timeoutSecond) + + # MQTT functionality APIs + def connect(self, keepAliveIntervalSecond=30): + return self._mqttClient.connect(keepAliveIntervalSecond) + + def disconnect(self): + return self._mqttClient.disconnect() + + def publish(self, topic, payload, QoS): + return self._mqttClient.publish(topic, payload, QoS, False) # Disable retain for publish by now + + def subscribe(self, topic, QoS, callback): + return self._mqttClient.subscribe(topic, QoS, callback) + + def unsubscribe(self, topic): + return self._mqttClient.unsubscribe(topic) + + +class AWSIoTMQTTShadowClient: + + def __init__(self, clientID, protocolType=mqttConst.MQTTv3_1_1, useWebsocket=False, cleanSession=True): + # AWSIOTMQTTClient instance + self._AWSIoTMQTTClient = AWSIoTMQTTClient(clientID, protocolType, useWebsocket, cleanSession) + # Configure it to disable offline Publish Queueing + self._AWSIoTMQTTClient.configureOfflinePublishQueueing(0) # Disable queueing, no queueing for time-sentive shadow messages + self._AWSIoTMQTTClient.configureDrainingFrequency(10) + # Now retrieve the configured mqttCore and init a shadowManager instance + self._shadowManager = shadowManager.shadowManager(self._AWSIoTMQTTClient._mqttClient) + + # Configuration APIs + def configureLastWill(self, topic, payload, QoS): + self._AWSIoTMQTTClient.configureLastWill(topic, payload, QoS) + + def clearLastWill(self): + self._AWSIoTMQTTClient.clearLastWill() + + def configureEndpoint(self, hostName, portNumber): + self._AWSIoTMQTTClient.configureEndpoint(hostName, portNumber) + + def configureIAMCredentials(self, AWSAccessKeyID, AWSSecretAccessKey, AWSSTSToken=""): + # AWSIoTMQTTClient.configureIAMCredentials + self._AWSIoTMQTTClient.configureIAMCredentials(AWSAccessKeyID, AWSSecretAccessKey, AWSSTSToken) + + def configureCredentials(self, CAFilePath, KeyPath="", CertificatePath=""): # Should be good for MutualAuth and Websocket + self._AWSIoTMQTTClient.configureCredentials(CAFilePath, KeyPath, CertificatePath) + + def configureAutoReconnectBackoffTime(self, baseReconnectQuietTimeSecond, maxReconnectQuietTimeSecond, stableConnectionTimeSecond): + self._AWSIoTMQTTClient.configureAutoReconnectBackoffTime(baseReconnectQuietTimeSecond, maxReconnectQuietTimeSecond, stableConnectionTimeSecond) + + def configureConnectDisconnectTimeout(self, timeoutSecond): + self._AWSIoTMQTTClient.configureConnectDisconnectTimeout(timeoutSecond) + + def configureMQTTOperationTimeout(self, timeoutSecond): + self._AWSIoTMQTTClient.configureMQTTOperationTimeout(timeoutSecond) + + # Start the MQTT connection + def connect(self, keepAliveIntervalSecond=30): + return self._AWSIoTMQTTClient.connect(keepAliveIntervalSecond) + + # End the MQTT connection + def disconnect(self): + return self._AWSIoTMQTTClient.disconnect() + + # Shadow management API + def createShadowHandlerWithName(self, shadowName, isPersistentSubscribe): + # Create and return a deviceShadow instance + return deviceShadow.deviceShadow(shadowName, isPersistentSubscribe, self._shadowManager) + # Shadow APIs are accessible in deviceShadow instance": + ### + # deviceShadow.shadowGet + # deviceShadow.shadowUpdate + # deviceShadow.shadowDelete + # deviceShadow.shadowRegisterDelta + # deviceShadow.shadowUnregisterDelta + + # MQTT connection management API + def getMQTTConnection(self): + # Return the internal AWSIoTMQTTClient instance + return self._AWSIoTMQTTClient diff --git a/lib/mqtt_aws/MQTTMsgHandler.py b/lib/mqtt_aws/MQTTMsgHandler.py new file mode 100644 index 0000000..273f739 --- /dev/null +++ b/lib/mqtt_aws/MQTTMsgHandler.py @@ -0,0 +1,279 @@ +import MQTTConst as mqttConst +import time +import socket +import ssl +import _thread +import select +import struct + +class MsgHandler: + + def __init__(self, receive_callback, connect_helper): + self._host = "" + self._port = -1 + self._cafile = "" + self._key = "" + self._cert = "" + self._sock = None + self._output_queue_size=-1 + self._output_queue_dropbehavior=-1 + self._mqttOperationTimeout = 5 + self._connection_state = mqttConst.STATE_DISCONNECTED + self._conn_state_mutex=_thread.allocate_lock() + self._poll = select.poll() + self._output_queue=[] + self._out_packet_mutex=_thread.allocate_lock() + _thread.stack_size(8192) + _thread.start_new_thread(self._io_thread_func, ()) + self._recv_callback = receive_callback + self._connect_helper = connect_helper + self._pingSent=False + self._ping_interval=20 + self._waiting_ping_resp=False + self._ping_cutoff=3 + self._receive_timeout=3000 + self._draining_interval=2 + self._draining_cutoff=3 + self._shadow_cb_queue=[] + self._shadow_cb_mutex=_thread.allocate_lock() + + def setOfflineQueueConfiguration(self, queueSize, dropBehavior): + self._output_queue_size = queueSize + self._output_queue_dropbehavior = dropBehavior + + def setCredentials(self, srcCAFile, srcKey, srcCert): + self._cafile = srcCAFile + self._key = srcKey + self._cert = srcCert + + def setEndpoint(self, srcHost, srcPort): + self._host = srcHost + self._port = srcPort + + def setOperationTimeout(self, timeout): + self._mqttOperationTimeout=timeout + + def setDrainingInterval(self, srcDrainingIntervalSecond): + self._draining_interval=srcDrainingIntervalSecond + + def insertShadowCallback(self, callback, payload, status, token): + self._shadow_cb_mutex.acquire() + self._shadow_cb_queue.append((callback, payload, status, token)) + self._shadow_cb_mutex.release() + + def _callShadowCallback(self): + self._shadow_cb_mutex.acquire() + if len(self._shadow_cb_queue) > 0: + cbObj = self._shadow_cb_queue.pop(0) + cbObj[0](cbObj[1],cbObj[2], cbObj[3]) + self._shadow_cb_mutex.release() + + def createSocketConnection(self): + self._conn_state_mutex.acquire() + self._connection_state = mqttConst.STATE_CONNECTING + self._conn_state_mutex.release() + try: + if self._sock: + self._poll.unregister(self._sock) + self._sock.close() + self._sock = None + + self._sock = socket.socket() + self._sock.settimeout(30) + if self._cafile: + self._sock = ssl.wrap_socket( + self._sock, + certfile=self._cert, + keyfile=self._key, + ca_certs=self._cafile, + cert_reqs=ssl.CERT_REQUIRED) + + self._sock.connect(socket.getaddrinfo(self._host, self._port)[0][-1]) + self._poll.register(self._sock, select.POLLIN) + except socket.error as err: + print("Socket create error: {0}".format(err)) + + self._conn_state_mutex.acquire() + self._connection_state = mqttConst.STATE_DISCONNECTED + self._conn_state_mutex.release() + + return False + + return True + + def disconnect(self): + if self._sock: + self._sock.close() + self._sock = None + + def isConnected(self): + connected=False + self._conn_state_mutex.acquire() + if self._connection_state == mqttConst.STATE_CONNECTED: + connected = True + self._conn_state_mutex.release() + + return connected + + def setConnectionState(self, state): + self._conn_state_mutex.acquire() + self._connection_state = state + self._conn_state_mutex.release() + + def _drop_message(self): + if self._output_queue_size == -1: + return False + elif (self._output_queue_size == 0) and (self._connection_state == mqttConst.STATE_CONNECTED): + return False + else: + return True if len(self._output_queue) >= self._output_queue_size else False + + def push_on_send_queue(self, packet): + if self._drop_message(): + if self._output_queue_dropbehavior == mqttConst.DROP_OLDEST: + self._out_packet_mutex.acquire() + if self._out_packet_mutex.locked(): + self._output_queue.pop(0) + self._out_packet_mutex.release() + else: + return False + + self._out_packet_mutex.acquire() + if self._out_packet_mutex.locked(): + self._output_queue.append(packet) + self._out_packet_mutex.release() + + return True + + def priority_send(self, packet): + msg_sent = False + self._out_packet_mutex.acquire() + msg_sent = self._send_packet(packet) + self._out_packet_mutex.release() + + return msg_sent + + def _receive_packet(self): + try: + if not self._poll.poll(self._receive_timeout): + return False + except Exception as err: + print("Poll error: {0}".format(err)) + return False + + # Read message type + try: + self._sock.setblocking(False) + msg_type = self._sock.recv(1) + except socket.error as err: + print("Socket receive error: {0}".format(err)) + return False + else: + if len(msg_type) == 0: + return False + msg_type = struct.unpack("!B", msg_type)[0] + self._sock.setblocking(True) + + # Read payload length + multiplier = 1 + bytes_read = 0 + bytes_remaining = 0 + while True: + try: + if self._sock: + byte = self._sock.recv(1) + except socket.error as err: + print("Socket receive error: {0}".format(err)) + return False + else: + bytes_read = bytes_read + 1 + if bytes_read > 4: + return False + + byte = struct.unpack("!B", byte)[0] + bytes_remaining += (byte & 127) * multiplier + multiplier += 128 + + if (byte & 128) == 0: + break + + # Read payload + try: + if self._sock: + if bytes_remaining > 0: + payload = self._sock.recv(bytes_remaining) + else: + payload = b'' + except socket.error as err: + print("Socket receive error: {0}".format(err)) + return False + + return self._recv_callback(msg_type, payload) + + def _send_pingreq(self): + pkt = struct.pack('!BB', mqttConst.MSG_PINGREQ, 0) + return self.priority_send(pkt) + + def setPingFlag(self, flag): + self._pingSent=flag + + def _send_packet(self, packet): + written = -1 + try: + if self._sock: + written = self._sock.write(packet) + if(written == None): + written = -1 + else: + print('Packet sent. (Length: %d)' % written) + except socket.error as err: + print('Socket send error {0}'.format(err)) + return False + + return True if len(packet) == written else False + + def _verify_connection_state(self): + elapsed = time.time() - self._start_time + if not self._waiting_ping_resp and elapsed > self._ping_interval: + if self._connection_state == mqttConst.STATE_CONNECTED: + self._pingSent=False + self._send_pingreq() + self._waiting_ping_resp=True + elif self._connection_state == mqttConst.STATE_DISCONNECTED: + self._connect_helper() + + self._start_time = time.time() + elif self._waiting_ping_resp and (self._connection_state == mqttConst.STATE_CONNECTED or elapsed > self._mqttOperationTimeout): + if not self._pingSent: + if self._ping_failures <= self._ping_cutoff: + self._ping_failures+=1 + else: + self._connect_helper() + else: + self._ping_failures=0 + + self._start_time = time.time() + self._waiting_ping_resp = False + + def _io_thread_func(self): + time.sleep(5.0) + + self._start_time = time.time() + self._ping_failures=0 + while True: + + self._verify_connection_state() + + self._out_packet_mutex.acquire() + if self._ping_failures == 0: + if self._out_packet_mutex.locked() and len(self._output_queue) > 0: + packet=self._output_queue[0] + if self._send_packet(packet): + self._output_queue.pop(0) + self._out_packet_mutex.release() + + self._receive_packet() + self._callShadowCallback() + + if len(self._output_queue) >= self._draining_cutoff: + time.sleep(self._draining_interval) \ No newline at end of file diff --git a/lib/mqtt_aws/MQTTShadowManager.py b/lib/mqtt_aws/MQTTShadowManager.py new file mode 100644 index 0000000..a846317 --- /dev/null +++ b/lib/mqtt_aws/MQTTShadowManager.py @@ -0,0 +1,55 @@ +import _thread +import time + +class shadowManager: + + def __init__(self, MQTTClient): + if MQTTClient is None: + raise ValueError("MQTT Client is none") + + self._mqttClient = MQTTClient + self._subscribe_mutex = _thread.allocate_lock() + + def getClientID(self): + return self._mqttClient.getClientID() + + def _getDeltaTopic(self, shadowName): + return "$aws/things/" + str(shadowName) + "/shadow/update/delta" + + def _getNonDeltaTopics(self, shadowName, actionName): + generalTopic = "$aws/things/" + str(shadowName) + "/shadow/" + str(actionName) + acceptTopic = "$aws/things/" + str(shadowName) + "/shadow/" + str(actionName) + "/accepted" + rejectTopic = "$aws/things/" + str(shadowName) + "/shadow/" + str(actionName) + "/rejected" + + return (generalTopic, acceptTopic, rejectTopic) + + def shadowPublish(self, shadowName, shadowAction, payload): + (generalTopic, acceptTopic, rejectTopic) = self._getNonDeltaTopics(shadowName, shadowAction) + self._mqttClient.publish(generalTopic, payload, 0, False) + + def shadowSubscribe(self, shadowName, shadowAction, callback): + self._subscribe_mutex.acquire() + if shadowAction == "delta": + deltaTopic = self._getDeltaTopic(shadowName) + self._mqttClient.subscribe(deltaTopic, 0, callback) + else: + (generalTopic, acceptTopic, rejectTopic) = self._getNonDeltaTopics(shadowName, shadowAction) + self._mqttClient.subscribe(acceptTopic, 0, callback) + self._mqttClient.subscribe(rejectTopic, 0, callback) + time.sleep(2) + self._subscribe_mutex.release() + + def shadowUnsubscribe(self, srcShadowName, srcShadowAction): + self._subscribe_mutex.acquire() + currentShadowAction = _shadowAction(srcShadowName, srcShadowAction) + if shadowAction == "delta": + deltaTopic = self._getDeltaTopic(shadowName) + self._mqttClient.unsubscribe(deltaTopic) + else: + (generalTopic, acceptTopic, rejectTopic) = self._getNonDeltaTopics(shadowName, shadowAction) + self._mqttClient.unsubscribe(acceptTopic) + self._mqttClient.unsubscribe(rejectTopic) + self._subscribe_mutex.release() + + def insertShadowCallback(self, callback, payload, status, token): + self._mqttClient.insertShadowCallback(callback, payload, status, token) diff --git a/lib/pycoproc/pycoproc.py b/lib/pycoproc/pycoproc.py deleted file mode 100644 index 59eda6a..0000000 --- a/lib/pycoproc/pycoproc.py +++ /dev/null @@ -1,294 +0,0 @@ -#!/usr/bin/env python -# -# Copyright (c) 2019, Pycom Limited. -# -# This software is licensed under the GNU GPL version 3 or any -# later version, with permitted additional terms. For more information -# see the Pycom Licence v1.0 document supplied with this file, or -# available at https://www.pycom.io/opensource/licensing -# - -from machine import Pin -from machine import I2C -import time -import pycom - -__version__ = '0.0.2' - -""" PIC MCU wakeup reason types """ -WAKE_REASON_ACCELEROMETER = 1 -WAKE_REASON_PUSH_BUTTON = 2 -WAKE_REASON_TIMER = 4 -WAKE_REASON_INT_PIN = 8 - -class Pycoproc: - """ class for handling the interaction with PIC MCU """ - - I2C_SLAVE_ADDR = const(8) - - CMD_PEEK = const(0x0) - CMD_POKE = const(0x01) - CMD_MAGIC = const(0x02) - CMD_HW_VER = const(0x10) - CMD_FW_VER = const(0x11) - CMD_PROD_ID = const(0x12) - CMD_SETUP_SLEEP = const(0x20) - CMD_GO_SLEEP = const(0x21) - CMD_CALIBRATE = const(0x22) - CMD_BAUD_CHANGE = const(0x30) - CMD_DFU = const(0x31) - - REG_CMD = const(0) - REG_ADDRL = const(1) - REG_ADDRH = const(2) - REG_AND = const(3) - REG_OR = const(4) - REG_XOR = const(5) - - ANSELA_ADDR = const(0x18C) - ANSELB_ADDR = const(0x18D) - ANSELC_ADDR = const(0x18E) - - ADCON0_ADDR = const(0x9D) - ADCON1_ADDR = const(0x9E) - - IOCAP_ADDR = const(0x391) - IOCAN_ADDR = const(0x392) - - INTCON_ADDR = const(0x0B) - OPTION_REG_ADDR = const(0x95) - - _ADCON0_CHS_POSN = const(0x02) - _ADCON0_ADON_MASK = const(0x01) - _ADCON1_ADCS_POSN = const(0x04) - _ADCON0_GO_nDONE_MASK = const(0x02) - - ADRESL_ADDR = const(0x09B) - ADRESH_ADDR = const(0x09C) - - TRISC_ADDR = const(0x08E) - - PORTA_ADDR = const(0x00C) - PORTC_ADDR = const(0x00E) - - WPUA_ADDR = const(0x20C) - - WAKE_REASON_ADDR = const(0x064C) - MEMORY_BANK_ADDR = const(0x0620) - - PCON_ADDR = const(0x096) - STATUS_ADDR = const(0x083) - - EXP_RTC_PERIOD = const(7000) - - def __init__(self, i2c=None, sda='P22', scl='P21'): - if i2c is not None: - self.i2c = i2c - else: - self.i2c = I2C(0, mode=I2C.MASTER, pins=(sda, scl), baudrate=100000) - - self.sda = sda - self.scl = scl - self.clk_cal_factor = 1 - self.reg = bytearray(6) - self.wake_int = False - self.wake_int_pin = False - self.wake_int_pin_rising_edge = True - - # Make sure we are inserted into the - # correct board and can talk to the PIC - try: - self.read_fw_version() - except Exception as e: - raise Exception('Board not detected: {}'.format(e)) - - # init the ADC for the battery measurements - self.poke_memory(ANSELC_ADDR, 1 << 2) - self.poke_memory(ADCON0_ADDR, (0x06 << _ADCON0_CHS_POSN) | _ADCON0_ADON_MASK) - self.poke_memory(ADCON1_ADDR, (0x06 << _ADCON1_ADCS_POSN)) - # enable the pull-up on RA3 - self.poke_memory(WPUA_ADDR, (1 << 3)) - # make RC5 an input - self.set_bits_in_memory(TRISC_ADDR, 1 << 5) - # set RC6 and RC7 as outputs and enable power to the sensors and the GPS - self.mask_bits_in_memory(TRISC_ADDR, ~(1 << 6)) - self.mask_bits_in_memory(TRISC_ADDR, ~(1 << 7)) - - if self.read_fw_version() < 6: - raise ValueError('Firmware out of date') - - - def _write(self, data, wait=True): - self.i2c.writeto(I2C_SLAVE_ADDR, data) - if wait: - self._wait() - - def _read(self, size): - return self.i2c.readfrom(I2C_SLAVE_ADDR, size + 1)[1:(size + 1)] - - def _wait(self): - count = 0 - time.sleep_us(10) - while self.i2c.readfrom(I2C_SLAVE_ADDR, 1)[0] != 0xFF: - time.sleep_us(100) - count += 1 - if (count > 500): # timeout after 50ms - raise Exception('Board timeout') - - def _send_cmd(self, cmd): - self._write(bytes([cmd])) - - def read_hw_version(self): - self._send_cmd(CMD_HW_VER) - d = self._read(2) - return (d[1] << 8) + d[0] - - def read_fw_version(self): - self._send_cmd(CMD_FW_VER) - d = self._read(2) - return (d[1] << 8) + d[0] - - def read_product_id(self): - self._send_cmd(CMD_PROD_ID) - d = self._read(2) - return (d[1] << 8) + d[0] - - def peek_memory(self, addr): - self._write(bytes([CMD_PEEK, addr & 0xFF, (addr >> 8) & 0xFF])) - return self._read(1)[0] - - def poke_memory(self, addr, value): - self._write(bytes([CMD_POKE, addr & 0xFF, (addr >> 8) & 0xFF, value & 0xFF])) - - def magic_write_read(self, addr, _and=0xFF, _or=0, _xor=0): - self._write(bytes([CMD_MAGIC, addr & 0xFF, (addr >> 8) & 0xFF, _and & 0xFF, _or & 0xFF, _xor & 0xFF])) - return self._read(1)[0] - - def toggle_bits_in_memory(self, addr, bits): - self.magic_write_read(addr, _xor=bits) - - def mask_bits_in_memory(self, addr, mask): - self.magic_write_read(addr, _and=mask) - - def set_bits_in_memory(self, addr, bits): - self.magic_write_read(addr, _or=bits) - - def get_wake_reason(self): - """ returns the wakeup reason, a value out of constants WAKE_REASON_* """ - return self.peek_memory(WAKE_REASON_ADDR) - - def get_sleep_remaining(self): - """ returns the remaining time from sleep, as an interrupt (wakeup source) might have triggered """ - c3 = self.peek_memory(WAKE_REASON_ADDR + 3) - c2 = self.peek_memory(WAKE_REASON_ADDR + 2) - c1 = self.peek_memory(WAKE_REASON_ADDR + 1) - time_device_s = (c3 << 16) + (c2 << 8) + c1 - # this time is from PIC internal oscilator, so it needs to be adjusted with the calibration value - try: - self.calibrate_rtc() - except Exception: - pass - time_s = int((time_device_s / self.clk_cal_factor) + 0.5) # 0.5 used for round - return time_s - - def setup_sleep(self, time_s): - try: - self.calibrate_rtc() - except Exception: - pass - time_s = int((time_s * self.clk_cal_factor) + 0.5) # round to the nearest integer - if time_s >= 2**(8*3): - time_s = 2**(8*3)-1 - self._write(bytes([CMD_SETUP_SLEEP, time_s & 0xFF, (time_s >> 8) & 0xFF, (time_s >> 16) & 0xFF])) - - def go_to_sleep(self, gps=True): - # enable or disable back-up power to the GPS receiver - if gps: - self.set_bits_in_memory(PORTC_ADDR, 1 << 7) - else: - self.mask_bits_in_memory(PORTC_ADDR, ~(1 << 7)) - # disable the ADC - self.poke_memory(ADCON0_ADDR, 0) - - if self.wake_int: - # Don't touch RA3, RA5 or RC1 so that interrupt wake-up works - self.poke_memory(ANSELA_ADDR, ~((1 << 3) | (1 << 5))) - self.poke_memory(ANSELC_ADDR, ~((1 << 6) | (1 << 7) | (1 << 1))) - else: - # disable power to the accelerometer, and don't touch RA3 so that button wake-up works - self.poke_memory(ANSELA_ADDR, ~(1 << 3)) - self.poke_memory(ANSELC_ADDR, ~(1 << 7)) - - self.poke_memory(ANSELB_ADDR, 0xFF) - - # check if INT pin (PIC RC1), should be used for wakeup - if self.wake_int_pin: - if self.wake_int_pin_rising_edge: - self.set_bits_in_memory(OPTION_REG_ADDR, 1 << 6) # rising edge of INT pin - else: - self.mask_bits_in_memory(OPTION_REG_ADDR, ~(1 << 6)) # falling edge of INT pin - self.mask_bits_in_memory(ANSELC_ADDR, ~(1 << 1)) # disable analog function for RC1 pin - self.set_bits_in_memory(TRISC_ADDR, 1 << 1) # make RC1 input pin - self.mask_bits_in_memory(INTCON_ADDR, ~(1 << 1)) # clear INTF - self.set_bits_in_memory(INTCON_ADDR, 1 << 4) # enable interrupt; set INTE) - - self._write(bytes([CMD_GO_SLEEP]), wait=False) - # kill the run pin - Pin('P3', mode=Pin.OUT, value=0) - - def calibrate_rtc(self): - # the 1.024 factor is because the PIC LF operates at 31 KHz - # WDT has a frequency divider to generate 1 ms - # and then there is a binary prescaler, e.g., 1, 2, 4 ... 512, 1024 ms - # hence the need for the constant - self._write(bytes([CMD_CALIBRATE]), wait=False) - self.i2c.deinit() - Pin('P21', mode=Pin.IN) - pulses = pycom.pulses_get('P21', 100) - self.i2c.init(mode=I2C.MASTER, pins=(self.sda, self.scl), baudrate=100000) - idx = 0 - for i in range(len(pulses)): - if pulses[i][1] > EXP_RTC_PERIOD: - idx = i - break - try: - period = pulses[idx][1] - pulses[(idx - 1)][1] - except: - period = 0 - if period > 0: - self.clk_cal_factor = (EXP_RTC_PERIOD / period) * (1000 / 1024) - if self.clk_cal_factor > 1.25 or self.clk_cal_factor < 0.75: - self.clk_cal_factor = 1 - - def button_pressed(self): - button = self.peek_memory(PORTA_ADDR) & (1 << 3) - return not button - - def read_battery_voltage(self): - self.set_bits_in_memory(ADCON0_ADDR, _ADCON0_GO_nDONE_MASK) - time.sleep_us(50) - while self.peek_memory(ADCON0_ADDR) & _ADCON0_GO_nDONE_MASK: - time.sleep_us(100) - adc_val = (self.peek_memory(ADRESH_ADDR) << 2) + (self.peek_memory(ADRESL_ADDR) >> 6) - return (((adc_val * 3.3 * 280) / 1023) / 180) + 0.01 # add 10mV to compensate for the drop in the FET - - def setup_int_wake_up(self, rising, falling): - """ rising is for activity detection, falling for inactivity """ - wake_int = False - if rising: - self.set_bits_in_memory(IOCAP_ADDR, 1 << 5) - wake_int = True - else: - self.mask_bits_in_memory(IOCAP_ADDR, ~(1 << 5)) - - if falling: - self.set_bits_in_memory(IOCAN_ADDR, 1 << 5) - wake_int = True - else: - self.mask_bits_in_memory(IOCAN_ADDR, ~(1 << 5)) - self.wake_int = wake_int - - def setup_int_pin_wake_up(self, rising_edge = True): - """ allows wakeup to be made by the INT pin (PIC -RC1) """ - self.wake_int_pin = True - self.wake_int_pin_rising_edge = rising_edge diff --git a/lib/pymesh/README.md b/lib/pymesh/README.md deleted file mode 100644 index fac8322..0000000 --- a/lib/pymesh/README.md +++ /dev/null @@ -1,150 +0,0 @@ -# Pymesh micropython code - -This project exemplifies the use of Pycom's proprietary LoRa Mesh network - **Pymesh**. -These scripts were created and tested on Lopy4 and Fipy, using the minimum release as [1.20.0.rc8](https://forum.pycom.io/topic/4499/firmware-release-candidate-v1-20-0-rc8). - -Official Pymesh docs: https://docs.pycom.io/firmwareapi/pycom/network/lora/pymesh.html - -Simple Pymesh example: https://docs.pycom.io/tutorials/lora/lora-mesh.html - -Forum Pymesh announcements: https://forum.pycom.io/topic/4449/pymesh-updates - -## Overview -These scripts were created to prototype different features of Pymesh. - -They are quite monolithic, as they were developed on par with firmware Pymesh development. Multiple improvement are foreseen to be further performed, into areas like modularity (BLE RPC is the main candidate). Also lots of code is executed in micropython threads, which have limited memory (they should be moved in the *main loop*). Maybe queues for RX/TX on Pymesh should be added, with automatic retransmissions until ACK. - -### Important features -* Start Pymesh over LoRa on 863Mhz, bandwidth 250kHz, spreading-factor 7 (check MeshInternal constructor). -* Pymesh parameters are automatically saved in NVM, so in the next restart/deepsleep, the node will try to maintain its IP addresses and connections with neighbour nodes. -* Start BLE server with name `PyGo (mac: ` - * BLE is used with an RPC protocol, packed using `msgpack` library . -* Internal CLI for controlling/triggering Pymesh features, as explained bellow. - -## Color coding LED - -The LED color represents the state of the node in the Mesh network. - - Magenta - LEADER - Green - ROUTER - White - CHILD, - Red - Searching / Detached from any Pymesh - Cyan - SINGLE LEADER (no other Router in the same Pymesh) - -## Internal CLI -``` ->mac -1 -``` -Shows LoRa MAC, this address is used as unique identifier in the Pymesh. Bellow there's a section on how to set MAC specific MAC address (useful for debugging, the MAC could be consecutive small numbers like `0x1`, `0x2`, `...`) - -``` ->mml -mesh_mac_list [1, 6, 2] -``` -Shows the list of all MAC Nodes included into Pymesh. Inquires Leader if doesn't have this info (or if too old). In about 5 sec, a new call will return the latest list. - -``` ->mp -Send pack: 0xF3 to IP fdde:ad00:beef:0:0:ff:fe00:fc00 -last_mesh_pairs [[2, 6, -87], [1, 6, -77]] -``` -Shows Mesh Pairs list, with each direct connected nodes (by their MAC address) and the averaged RSSI value. - -``` ->mni -last_mesh_node_info {1: {"ip": 2048, "l": {"lng": 5.45313, "lat": 51.45}, "a": 10, "r": 3, "nn": 1, "nei": [[6, 55296, 3, -76, 23]]}, 6: {"ip": 55296, "l": {"lng": 5.45313, "lat": 51.45}, "a": 7, "r": 3, "nn": 2, "nei": [[2, 50176, 3, -89, 28], [1, 2048, 3, -77, 23]]}, 2: {"ip": 50176, "l": {"lng": 5.45313, "lat": 51.45}, "a": 7, "r": 3, "nn": 1, "nei": [[6, 55296, 3, -86, 25]]}} -``` -Shows the properties for all the nodes in this Pymesh, together with its neighbors. - -``` ->s -(to)<1 -(txt)>>>>>>> -Added new message for 1: Hello World! -Send pack: 0x10 to IP fdde:ad00:beef:0::1 -True ->Incoming 13 bytes from fdde:ad00:beef:0:f67b:3d1e:f07:8341 (port 1234): -PACK_MESSAGE_ACK received -1945883 ================= ACK RECEIVED :) :) :) -``` -Sends text messages to another Node inside Pymesh. The messaging waits for ACK from the destination node. If not received, it's resent after minimum 15 seconds. - -*Sorry for the messy output and debug info.* - -``` -ws -(to)<1 -ACK? mac 1, id 12345 => 1 -True -``` -Shows if a message was acknowledged by the destination Node. - -``` ->rm -{'b': (b'Hello World!',), 'id': 12345, 'ts': 3301, 'from': 6} -``` -Shows the received messages. - -``` -f -(MAC to)<1 -(packsize)<500 -(filename, Enter for dog.jpg)< -... -Incoming 6 bytes from fdde:ad00:beef:0:160b:8542:2190:c864 (port 1234): -PACK_FILE_SEND_ACK received -6165 Bytes sent, time: 27 sec -Done sending 6165 B in 27 sec -``` -Sends a file already stored in `/flash` (by default `/flash/dog.jpg`), specifying to which Node and in what chunk size (it can't be bigger than 500 Bytes, limit set in firmware). - -At destination, the file is stored as `/flash/dog_rcv.jpg`. -Picture files could be stored on Lopy4/Fipy using either Pymakr (over USB) or FTP. - -*The file transfer is done naively, the file is not checked at destination, nor individual chunks right order is not verified. These should be addressed in further improvements.* - -``` ->gs -(lat)<2.3 -(lon)<4.5 -Gps: (2.3, 4.5) -``` -Sets localisation coordinates; useful where no Pytrack is used. - -``` ->gg -Gps: (2.2, 1.1) -``` -Shows latest GPS coordinates. - -``` -> rst -1 -``` -Resets the Pymesh parameters saved in NVM, and resets the Node. - -``` -> rb -``` -Resets BLE RPC buffer. - -## LoRa MAC address Set/Read - -### Set LoRa Mac -```python -fo = open("/flash/sys/lpwan.mac", "wb") -mac_write=bytes([0,0,0,0,0,0,0,20]) -fo.write(mac_write) -fo.close() -``` - -### Read LoRa MAC address -```python ->>> from network import LoRa ->>> lora = LoRa(mode=LoRa.LORA, region=LoRa.EU868, frequency = 863000000, bandwidth=LoRa.BW_125KHZ, sf=7) ->>> import ubinascii ->>> ubinascii.hexlify(lora.mac()) -b'0000000000000006' -``` diff --git a/lib/pymesh/lib/L76GNSS.py b/lib/pymesh/lib/L76GNSS.py deleted file mode 100644 index 77f2931..0000000 --- a/lib/pymesh/lib/L76GNSS.py +++ /dev/null @@ -1,94 +0,0 @@ - -# Copyright (c) 2019, Pycom Limited. -# -# This software is licensed under the GNU GPL version 3 or any -# later version, with permitted additional terms. For more information -# see the Pycom Licence v1.0 document supplied with this file, or -# available at https://www.pycom.io/opensource/licensing - -from machine import Timer -import time -import gc -import binascii - - -class L76GNSS: - - GPS_I2CADDR = const(0x10) - - def __init__(self, pytrack=None, sda='P22', scl='P21', timeout=None): - if pytrack is not None: - self.i2c = pytrack.i2c - else: - from machine import I2C - self.i2c = I2C(0, mode=I2C.MASTER, pins=(sda, scl)) - - self.chrono = Timer.Chrono() - - self.timeout = timeout - self.timeout_status = True - - self.reg = bytearray(1) - self.i2c.writeto(GPS_I2CADDR, self.reg) - - def _read(self): - self.reg = self.i2c.readfrom(GPS_I2CADDR, 64) - return self.reg - - def _convert_coords(self, gngll_s): - lat = gngll_s[1] - lat_d = (float(lat) // 100) + ((float(lat) % 100) / 60) - lon = gngll_s[3] - lon_d = (float(lon) // 100) + ((float(lon) % 100) / 60) - if gngll_s[2] == 'S': - lat_d *= -1 - if gngll_s[4] == 'W': - lon_d *= -1 - return(lat_d, lon_d) - - def coordinates(self, debug=False): - lat_d, lon_d, debug_timeout = None, None, False - if self.timeout is not None: - self.chrono.reset() - self.chrono.start() - nmea = b'' - while True: - if self.timeout is not None and self.chrono.read() >= self.timeout: - self.chrono.stop() - chrono_timeout = self.chrono.read() - self.chrono.reset() - self.timeout_status = False - debug_timeout = True - if not self.timeout_status: - gc.collect() - break - nmea += self._read().lstrip(b'\n\n').rstrip(b'\n\n') - gngll_idx = nmea.find(b'GNGLL') - gpgll_idx = nmea.find(b'GPGLL') - if gngll_idx < 0 and gpgll_idx >= 0: - gngll_idx = gpgll_idx - if gngll_idx >= 0: - gngll = nmea[gngll_idx:] - e_idx = gngll.find(b'\r\n') - if e_idx >= 0: - try: - gngll = gngll[:e_idx].decode('ascii') - gngll_s = gngll.split(',') - lat_d, lon_d = self._convert_coords(gngll_s) - except Exception: - pass - finally: - nmea = nmea[(gngll_idx + e_idx):] - gc.collect() - break - else: - gc.collect() - if len(nmea) > 410: # i suppose it can be safely changed to 82, which is longest NMEA frame - nmea = nmea[-5:] # $GNGL without last L - time.sleep(0.1) - self.timeout_status = True - if debug and debug_timeout: - print('GPS timed out after %f seconds' % (chrono_timeout)) - return(None, None) - else: - return(lat_d, lon_d) diff --git a/lib/pymesh/lib/cli.py b/lib/pymesh/lib/cli.py deleted file mode 100644 index 1e7a3ea..0000000 --- a/lib/pymesh/lib/cli.py +++ /dev/null @@ -1,147 +0,0 @@ - -# Copyright (c) 2019, Pycom Limited. -# -# This software is licensed under the GNU GPL version 3 or any -# later version, with permitted additional terms. For more information -# see the Pycom Licence v1.0 document supplied with this file, or -# available at https://www.pycom.io/opensource/licensing - -import time -import json - -__version__ = '1' -""" -* initial draft -""" - -class Cli: - """ class for CLI commands """ - - def __init__(self, mesh, rpc_handler, ble_comm): - self.mesh = mesh - self.rpc_handler = rpc_handler - self.ble_comm = ble_comm - - # lamda functions - self.sleep = None - return - - def process(self, arg1, arg2): - last_mesh_pairs = [] - last_mesh_mac_list = [] - last_mesh_node_info = {} - - while True: - time.sleep(.1) - cmd = input('>') - # print(cmd) - - # if cmd == 'rb': - # print('resetting unpacker buffer') - # self.rpc_handler = RPCHandler(rx_worker, tx_worker, mesh, ble_comm) - - if cmd == 'mac': - print(self.mesh.mesh.mesh.MAC) - - elif cmd == 'mml': - mesh_mac_list = self.rpc_handler.get_mesh_mac_list() - if len(mesh_mac_list) > 0: - last_mesh_mac_list = mesh_mac_list - print('mesh_mac_list ', json.dumps(last_mesh_mac_list)) - - elif cmd == 'self': - node_info = self.rpc_handler.get_node_info() - print("self info:", node_info) - - elif cmd == 'mni': - for mac in last_mesh_mac_list: - node_info = self.rpc_handler.get_node_info(mac) - time.sleep(.5) - if len(node_info) > 0: - last_mesh_node_info[mac] = node_info - print('last_mesh_node_info', json.dumps(last_mesh_node_info)) - - elif cmd == 'mp': - mesh_pairs = self.rpc_handler.get_mesh_pairs() - if len(mesh_pairs) > 0: - last_mesh_pairs = mesh_pairs - print('last_mesh_pairs', json.dumps(last_mesh_pairs)) - - elif cmd == 's': - try: - to = int(input('(to)<')) - typ = input('(type, 0=text, 1=file, Enter for text)<') - if not typ: - typ = 0 - else: - typ = int(typ) - txt = input('(text/filename)<') - except: - print("Command parsing failed") - continue - data = { - 'to': to, - 'ty': typ, - 'b': txt, - 'id': 12345, - 'ts': int(time.time()), - } - print(self.rpc_handler.send_message(data)) - - elif cmd == 'ws': - to = int(input('(to)<')) - print(self.rpc_handler.send_message_was_sent(to, 12345)) - - elif cmd == 'rm': - print(self.rpc_handler.receive_message()) - - # elif cmd == 'gg': - # print("Gps:", (Gps.lat, Gps.lon)) - - # elif cmd == 'gs': - # lat = float(input('(lat)<')) - # lon = float(input('(lon)<')) - # Gps.set_location(lat, lon) - # print("Gps:", (Gps.lat, Gps.lon)) - - elif cmd == 'sleep': - try: - timeout = int(input('(time[sec])<')) - except: - continue - if self.sleep: - self.sleep(timeout) - - elif cmd == "ble": - # reset BLE connection - self.ble_comm.restart() - - elif cmd == "stat": - # do some statistics - # data = [] - # data[0] = {'mac':6, 'n':3, 't':30, 's1':0, 's2':0} - # data[0] = {'mac':6, 'n':3, 't':30, 's1':5, 's2':10} - # data[2] = {'mac':6, 'n':30, 't':60, 's1':10, 's2':45} - # for line in data: - # print() - # print("1 = {'mac':6, 'n':30, 't':60, 's1':10, 's2':45}<'") - # print("2 = {'mac':6, 'n':30, 't':60, 's1':10, 's2':45}<'") - # print("3 = {'mac':6, 'n':30, 't':60, 's1':10, 's2':45}<'") - # id = int(input('(choice 1-..)<')) - data = {'mac':6, 'n':3, 't':60, 's1':3, 's2':8} - res = self.mesh.statistics_start(data) - print("ok? ", res) - - elif cmd == "stat?": - try: - id = int(input('(id [Enter for all])<')) - except: - id = 0 - res = self.mesh.statistics_get(id) - print("ok? ", res) - - elif cmd == "rst": - print("Mesh Reset NVM settings ... ") - self.mesh.mesh.mesh.mesh.deinit() - if self.sleep: - self.sleep(1) \ No newline at end of file diff --git a/lib/pymesh/lib/gps.py b/lib/pymesh/lib/gps.py deleted file mode 100644 index 5f4c8c2..0000000 --- a/lib/pymesh/lib/gps.py +++ /dev/null @@ -1,83 +0,0 @@ - -# Copyright (c) 2019, Pycom Limited. -# -# This software is licensed under the GNU GPL version 3 or any -# later version, with permitted additional terms. For more information -# see the Pycom Licence v1.0 document supplied with this file, or -# available at https://www.pycom.io/opensource/licensing - -__version__ = '1' -""" -* initial version -""" - -import time -from pytrack import Pytrack -from L76GNSS import L76GNSS -from machine import Timer - -class Gps: - # Pycom office GPS coordinates - lat = 51.45 - lon = 5.45313 - - l76 = None - _timer = None - #is_set = False - - @staticmethod - def set_location(latitude, longitude): - dlat = str(type(latitude)) - dlon = str(type(longitude)) - if dlat == dlon == "": - Gps.lon = longitude - Gps.lat = latitude - is_set = True - else: - print("Error parsing ", latitude, longitude) - - @staticmethod - def get_location(): - return (Gps.lat, Gps.lon) - - @staticmethod - def init_static(): - is_pytrack = True - try: - py = Pytrack() - Gps.l76 = L76GNSS(py, timeout=30) - #l76.coordinates() - Gps._timer = Timer.Alarm(Gps.gps_periodic, 30, periodic=True) - print("Pytrack detected") - except: - is_pytrack = False - print("Pytrack NOT detected") - #TODO: how to check if GPS is conencted - return is_pytrack - - @staticmethod - def gps_periodic(alarm): - t0 = time.ticks_ms() - coord = Gps.l76.coordinates() - if coord[0] != None: - Gps.lat, Gps.lon = coord - print("New coord ", coord) - dt = time.ticks_ms() - t0 - print(" =====>>>> gps_periodic ", dt) - - @staticmethod - def terminate(): - if Gps._timer is not None: - Gps._timer.cancel() - pass - -""" -from pytrack import Pytrack -from L76GNSS import L76GNSS -py = Pytrack() -l76 = L76GNSS(py, timeout=30) -t0 = time.ticks_ms() -l76.coordinates() -y = time.ticks_ms() - t0 -y -""" diff --git a/lib/pymesh/lib/mesh_interface.py b/lib/pymesh/lib/mesh_interface.py deleted file mode 100644 index d3a579f..0000000 --- a/lib/pymesh/lib/mesh_interface.py +++ /dev/null @@ -1,199 +0,0 @@ - -# Copyright (c) 2019, Pycom Limited. -# -# This software is licensed under the GNU GPL version 3 or any -# later version, with permitted additional terms. For more information -# see the Pycom Licence v1.0 document supplied with this file, or -# available at https://www.pycom.io/opensource/licensing - -import time -from machine import Timer -#import _thread - -from mesh_internal import MeshInternal -from statistics import Statistics - -__version__ = '3' -""" -* added file send/receive debug - -__version__ = '2' -* add sending messages - -__version__ = '1' -* initial version, lock and get_mesh_members(), get_mesh_pairs -""" - -class MeshInterface: - """ Class for Mesh interface, - all modules that uses Mesh should call only this class methods """ - - INTERVAL = const(10) - - def __init__(self, meshaging, lock): - self.lock = lock #_thread.allocate_lock() - self.meshaging = meshaging - self.mesh = MeshInternal(self.meshaging) - self.sleep_function = None - self.single_leader_ts = 0 - - self.statistics = Statistics(self.meshaging) - self._timer = Timer.Alarm(self.periodic_cb, self.INTERVAL, periodic=True) - - # just run this ASAP - self.periodic_cb(None) - - pass - - def periodic_cb(self, alarm): - # wait lock forever - if self.lock.acquire(): - print("============ MESH THREAD >>>>>>>>>>> ") - t0 = time.ticks_ms() - - self.mesh.process() - if self.mesh.is_connected(): - self.statistics.process() - self.mesh.process_messages() - - # if Single Leader for 3 mins should reset - if self.mesh.mesh.state == self.mesh.mesh.STATE_LEADER and self.mesh.mesh.mesh.single(): - if self.single_leader_ts == 0: - # first time Single Leader, record time - self.single_leader_ts = time.time() - print("Single Leader", self.mesh.mesh.state, self.mesh.mesh.mesh.single(), - time.time() - self.single_leader_ts) - - if time.time() - self.single_leader_ts > 180: - print("Single Leader, just reset") - if self.sleep_function: - self.sleep_function(1) - else: - # print("Not Single Leader", self.mesh.mesh.state, self.mesh.mesh.mesh.single()) - self.single_leader_ts = 0 - - self.lock.release() - - print(">>>>>>>>>>> DONE MESH THREAD ============ %d\n"%(time.ticks_ms() - t0)) - - pass - - def timer_kill(self): - # with self.lock: - self._timer.cancel() - - def get_mesh_mac_list(self): - mac_list = list() - if self.lock.acquire(): - # mac_list = list(self.mesh.get_all_macs_set()) - # mac_list.sort() - mac_list = {0:list(self.mesh.get_all_macs_set())} - self.lock.release() - print("get_mesh_mac_list:", str(mac_list)) - return mac_list - - def get_mesh_pairs(self): - mesh_pairs = [] - if self.lock.acquire(): - mesh_pairs = self.mesh.get_mesh_pairs() - self.lock.release() - #print("get_mesh_pairs: %s"%str(mesh_pairs)) - return mesh_pairs - - def set_gps(self, lng, lat): - with open('/flash/gps', 'w') as fh: - fh.write('%d;%d'.format(lng, lat)) - - def is_connected(self): - is_connected = None - if self.lock.acquire(): - is_connected = self.mesh.is_connected() - self.lock.release() - return is_connected - - def ip(self): - ip = None - if self.lock.acquire(): - ip = self.mesh.mesh.mesh.ipaddr() - self.lock.release() - return ip - - def get_node_info(self, mac_id): - data = {} - try: - mac = int(mac_id) - except: - mac = self.mesh.MAC - print("get_node_info own mac") - if self.lock.acquire(): - data = self.mesh.node_info(mac) - self.lock.release() - return data - - def send_message(self, data): - ## WARNING: is locking required for just adding - ret = False - - # check input parameters - try: - mac = int(data['to']) - msg_type = data.get('ty', 0) # text type, by default - payload = data['b'] - id = int(data['id']) - ts = int(data['ts']) - except: - print('send_message: wrong input params') - return False - - if self.lock.acquire(): - print("Send message to %d, typ %d, load %s"%(mac, msg_type, payload)) - ret = self.meshaging.send_message(mac, msg_type, payload, id, ts) - # send messages ASAP - self.mesh.process_messages() - self.lock.release() - - return ret - - def mesage_was_ack(self, mac, id): - ret = False - if self.lock.acquire(): - ret = self.meshaging.mesage_was_ack(mac, id) - self.lock.release() - #print("mesage_was_ack (%X, %d): %d"%(mac, id, ret)) - return ret - - def get_rcv_message(self): - """ returns a message that was received, {} if none is received """ - message = None - if self.lock.acquire(): - message = self.meshaging.get_rcv_message() - self.lock.release() - if message is not None: - (mac, id, ts, payload) = message - return {'from':mac, - 'b':payload, - 'id':id, - 'ts':ts} - return {} - - def statistics_start(self, data): - """ starts to do statistics based on message send/ack """ - # data = {'mac':6, 'n':3, 't':30, 's1':10, 's2':30} - try: - # validate input params - mac = int(data['mac']) - num_mess = int(data['n']) - timeout = int(data['t']) - except: - print("statistics_start failed") - print(data) - return 0 - if mac == self.mesh.MAC: - data['mac'] = 2 - res = self.statistics.add_stat_mess(data) - return res - - def statistics_get(self, id): - res = self.statistics.status(id) - print(res) - return res diff --git a/lib/pymesh/lib/pycoproc.py b/lib/pymesh/lib/pycoproc.py deleted file mode 100644 index f06c46a..0000000 --- a/lib/pymesh/lib/pycoproc.py +++ /dev/null @@ -1,292 +0,0 @@ - -# Copyright (c) 2019, Pycom Limited. -# -# This software is licensed under the GNU GPL version 3 or any -# later version, with permitted additional terms. For more information -# see the Pycom Licence v1.0 document supplied with this file, or -# available at https://www.pycom.io/opensource/licensing - -from machine import Pin -from machine import I2C -import time -import pycom - -__version__ = '0.0.2' - -""" PIC MCU wakeup reason types """ -WAKE_REASON_ACCELEROMETER = 1 -WAKE_REASON_PUSH_BUTTON = 2 -WAKE_REASON_TIMER = 4 -WAKE_REASON_INT_PIN = 8 - -class Pycoproc: - """ class for handling the interaction with PIC MCU """ - - I2C_SLAVE_ADDR = const(8) - - CMD_PEEK = const(0x0) - CMD_POKE = const(0x01) - CMD_MAGIC = const(0x02) - CMD_HW_VER = const(0x10) - CMD_FW_VER = const(0x11) - CMD_PROD_ID = const(0x12) - CMD_SETUP_SLEEP = const(0x20) - CMD_GO_SLEEP = const(0x21) - CMD_CALIBRATE = const(0x22) - CMD_BAUD_CHANGE = const(0x30) - CMD_DFU = const(0x31) - - REG_CMD = const(0) - REG_ADDRL = const(1) - REG_ADDRH = const(2) - REG_AND = const(3) - REG_OR = const(4) - REG_XOR = const(5) - - ANSELA_ADDR = const(0x18C) - ANSELB_ADDR = const(0x18D) - ANSELC_ADDR = const(0x18E) - - ADCON0_ADDR = const(0x9D) - ADCON1_ADDR = const(0x9E) - - IOCAP_ADDR = const(0x391) - IOCAN_ADDR = const(0x392) - - INTCON_ADDR = const(0x0B) - OPTION_REG_ADDR = const(0x95) - - _ADCON0_CHS_POSN = const(0x02) - _ADCON0_ADON_MASK = const(0x01) - _ADCON1_ADCS_POSN = const(0x04) - _ADCON0_GO_nDONE_MASK = const(0x02) - - ADRESL_ADDR = const(0x09B) - ADRESH_ADDR = const(0x09C) - - TRISC_ADDR = const(0x08E) - - PORTA_ADDR = const(0x00C) - PORTC_ADDR = const(0x00E) - - WPUA_ADDR = const(0x20C) - - WAKE_REASON_ADDR = const(0x064C) - MEMORY_BANK_ADDR = const(0x0620) - - PCON_ADDR = const(0x096) - STATUS_ADDR = const(0x083) - - EXP_RTC_PERIOD = const(7000) - - def __init__(self, i2c=None, sda='P22', scl='P21'): - if i2c is not None: - self.i2c = i2c - else: - self.i2c = I2C(0, mode=I2C.MASTER, pins=(sda, scl)) - - self.sda = sda - self.scl = scl - self.clk_cal_factor = 1 - self.reg = bytearray(6) - self.wake_int = False - self.wake_int_pin = False - self.wake_int_pin_rising_edge = True - - # Make sure we are inserted into the - # correct board and can talk to the PIC - try: - self.read_fw_version() - except Exception as e: - raise Exception('Board not detected: {}'.format(e)) - - # init the ADC for the battery measurements - self.poke_memory(ANSELC_ADDR, 1 << 2) - self.poke_memory(ADCON0_ADDR, (0x06 << _ADCON0_CHS_POSN) | _ADCON0_ADON_MASK) - self.poke_memory(ADCON1_ADDR, (0x06 << _ADCON1_ADCS_POSN)) - # enable the pull-up on RA3 - self.poke_memory(WPUA_ADDR, (1 << 3)) - # make RC5 an input - self.set_bits_in_memory(TRISC_ADDR, 1 << 5) - # set RC6 and RC7 as outputs and enable power to the sensors and the GPS - self.mask_bits_in_memory(TRISC_ADDR, ~(1 << 6)) - self.mask_bits_in_memory(TRISC_ADDR, ~(1 << 7)) - - if self.read_fw_version() < 6: - raise ValueError('Firmware out of date') - - - def _write(self, data, wait=True): - self.i2c.writeto(I2C_SLAVE_ADDR, data) - if wait: - self._wait() - - def _read(self, size): - return self.i2c.readfrom(I2C_SLAVE_ADDR, size + 1)[1:(size + 1)] - - def _wait(self): - count = 0 - time.sleep_us(10) - while self.i2c.readfrom(I2C_SLAVE_ADDR, 1)[0] != 0xFF: - time.sleep_us(100) - count += 1 - if (count > 500): # timeout after 50ms - raise Exception('Board timeout') - - def _send_cmd(self, cmd): - self._write(bytes([cmd])) - - def read_hw_version(self): - self._send_cmd(CMD_HW_VER) - d = self._read(2) - return (d[1] << 8) + d[0] - - def read_fw_version(self): - self._send_cmd(CMD_FW_VER) - d = self._read(2) - return (d[1] << 8) + d[0] - - def read_product_id(self): - self._send_cmd(CMD_PROD_ID) - d = self._read(2) - return (d[1] << 8) + d[0] - - def peek_memory(self, addr): - self._write(bytes([CMD_PEEK, addr & 0xFF, (addr >> 8) & 0xFF])) - return self._read(1)[0] - - def poke_memory(self, addr, value): - self._write(bytes([CMD_POKE, addr & 0xFF, (addr >> 8) & 0xFF, value & 0xFF])) - - def magic_write_read(self, addr, _and=0xFF, _or=0, _xor=0): - self._write(bytes([CMD_MAGIC, addr & 0xFF, (addr >> 8) & 0xFF, _and & 0xFF, _or & 0xFF, _xor & 0xFF])) - return self._read(1)[0] - - def toggle_bits_in_memory(self, addr, bits): - self.magic_write_read(addr, _xor=bits) - - def mask_bits_in_memory(self, addr, mask): - self.magic_write_read(addr, _and=mask) - - def set_bits_in_memory(self, addr, bits): - self.magic_write_read(addr, _or=bits) - - def get_wake_reason(self): - """ returns the wakeup reason, a value out of constants WAKE_REASON_* """ - return self.peek_memory(WAKE_REASON_ADDR) - - def get_sleep_remaining(self): - """ returns the remaining time from sleep, as an interrupt (wakeup source) might have triggered """ - c3 = self.peek_memory(WAKE_REASON_ADDR + 3) - c2 = self.peek_memory(WAKE_REASON_ADDR + 2) - c1 = self.peek_memory(WAKE_REASON_ADDR + 1) - time_device_s = (c3 << 16) + (c2 << 8) + c1 - # this time is from PIC internal oscilator, so it needs to be adjusted with the calibration value - try: - self.calibrate_rtc() - except Exception: - pass - time_s = int((time_device_s / self.clk_cal_factor) + 0.5) # 0.5 used for round - return time_s - - def setup_sleep(self, time_s): - try: - self.calibrate_rtc() - except Exception: - pass - time_s = int((time_s * self.clk_cal_factor) + 0.5) # round to the nearest integer - if time_s >= 2**(8*3): - time_s = 2**(8*3)-1 - self._write(bytes([CMD_SETUP_SLEEP, time_s & 0xFF, (time_s >> 8) & 0xFF, (time_s >> 16) & 0xFF])) - - def go_to_sleep(self, gps=True): - # enable or disable back-up power to the GPS receiver - if gps: - self.set_bits_in_memory(PORTC_ADDR, 1 << 7) - else: - self.mask_bits_in_memory(PORTC_ADDR, ~(1 << 7)) - # disable the ADC - self.poke_memory(ADCON0_ADDR, 0) - - if self.wake_int: - # Don't touch RA3, RA5 or RC1 so that interrupt wake-up works - self.poke_memory(ANSELA_ADDR, ~((1 << 3) | (1 << 5))) - self.poke_memory(ANSELC_ADDR, ~((1 << 6) | (1 << 7) | (1 << 1))) - else: - # disable power to the accelerometer, and don't touch RA3 so that button wake-up works - self.poke_memory(ANSELA_ADDR, ~(1 << 3)) - self.poke_memory(ANSELC_ADDR, ~(1 << 7)) - - self.poke_memory(ANSELB_ADDR, 0xFF) - - # check if INT pin (PIC RC1), should be used for wakeup - if self.wake_int_pin: - if self.wake_int_pin_rising_edge: - self.set_bits_in_memory(OPTION_REG_ADDR, 1 << 6) # rising edge of INT pin - else: - self.mask_bits_in_memory(OPTION_REG_ADDR, ~(1 << 6)) # falling edge of INT pin - self.mask_bits_in_memory(ANSELC_ADDR, ~(1 << 1)) # disable analog function for RC1 pin - self.set_bits_in_memory(TRISC_ADDR, 1 << 1) # make RC1 input pin - self.mask_bits_in_memory(INTCON_ADDR, ~(1 << 1)) # clear INTF - self.set_bits_in_memory(INTCON_ADDR, 1 << 4) # enable interrupt; set INTE) - - self._write(bytes([CMD_GO_SLEEP]), wait=False) - # kill the run pin - Pin('P3', mode=Pin.OUT, value=0) - - def calibrate_rtc(self): - # the 1.024 factor is because the PIC LF operates at 31 KHz - # WDT has a frequency divider to generate 1 ms - # and then there is a binary prescaler, e.g., 1, 2, 4 ... 512, 1024 ms - # hence the need for the constant - self._write(bytes([CMD_CALIBRATE]), wait=False) - self.i2c.deinit() - Pin('P21', mode=Pin.IN) - pulses = pycom.pulses_get('P21', 100) - self.i2c.init(mode=I2C.MASTER, pins=(self.sda, self.scl)) - idx = 0 - for i in range(len(pulses)): - if pulses[i][1] > EXP_RTC_PERIOD: - idx = i - break - try: - period = pulses[idx][1] - pulses[(idx - 1)][1] - except: - period = 0 - if period > 0: - self.clk_cal_factor = (EXP_RTC_PERIOD / period) * (1000 / 1024) - if self.clk_cal_factor > 1.25 or self.clk_cal_factor < 0.75: - self.clk_cal_factor = 1 - - def button_pressed(self): - button = self.peek_memory(PORTA_ADDR) & (1 << 3) - return not button - - def read_battery_voltage(self): - self.set_bits_in_memory(ADCON0_ADDR, _ADCON0_GO_nDONE_MASK) - time.sleep_us(50) - while self.peek_memory(ADCON0_ADDR) & _ADCON0_GO_nDONE_MASK: - time.sleep_us(100) - adc_val = (self.peek_memory(ADRESH_ADDR) << 2) + (self.peek_memory(ADRESL_ADDR) >> 6) - return (((adc_val * 3.3 * 280) / 1023) / 180) + 0.01 # add 10mV to compensate for the drop in the FET - - def setup_int_wake_up(self, rising, falling): - """ rising is for activity detection, falling for inactivity """ - wake_int = False - if rising: - self.set_bits_in_memory(IOCAP_ADDR, 1 << 5) - wake_int = True - else: - self.mask_bits_in_memory(IOCAP_ADDR, ~(1 << 5)) - - if falling: - self.set_bits_in_memory(IOCAN_ADDR, 1 << 5) - wake_int = True - else: - self.mask_bits_in_memory(IOCAN_ADDR, ~(1 << 5)) - self.wake_int = wake_int - - def setup_int_pin_wake_up(self, rising_edge = True): - """ allows wakeup to be made by the INT pin (PIC -RC1) """ - self.wake_int_pin = True - self.wake_int_pin_rising_edge = rising_edge diff --git a/lib/pymesh/lib/pytrack.py b/lib/pymesh/lib/pytrack.py deleted file mode 100644 index 929c225..0000000 --- a/lib/pymesh/lib/pytrack.py +++ /dev/null @@ -1,16 +0,0 @@ - -# Copyright (c) 2019, Pycom Limited. -# -# This software is licensed under the GNU GPL version 3 or any -# later version, with permitted additional terms. For more information -# see the Pycom Licence v1.0 document supplied with this file, or -# available at https://www.pycom.io/opensource/licensing - -from pycoproc import Pycoproc - -__version__ = '1.4.0' - -class Pytrack(Pycoproc): - - def __init__(self, i2c=None, sda='P22', scl='P21'): - Pycoproc.__init__(self, i2c, sda, scl) diff --git a/lib/sqnsupgrade/README.md b/lib/sqnsupgrade/README.md index 7646d50..e6ec433 100644 --- a/lib/sqnsupgrade/README.md +++ b/lib/sqnsupgrade/README.md @@ -12,7 +12,7 @@ Please start with the following steps: 1. Upgrade the Pycom Firmware Updater tool to latest version 2. Select Firmware Type `stable` in the communication window to upgrade to latest stable version -You can find the different versions of firmwares available here: http://software.pycom.io/downloads/sequans2.html​ +You can find the different versions of firmwares available here: http://software.pycom.io/downloads/sequans2.html These files are password protected, to download them you should be a forum.pycom.io member and access to: Announcements & News --> Announcements only for members --> Firmware Files for Sequans LTE modem now are secured, or clicking Here diff --git a/lib/sqnsupgrade/sqnsupgrade.py b/lib/sqnsupgrade/sqnsupgrade.py index b488787..7c8db01 100644 --- a/lib/sqnsupgrade/sqnsupgrade.py +++ b/lib/sqnsupgrade/sqnsupgrade.py @@ -1,7 +1,7 @@ #!/usr/bin/env python -VERSION = "1.2.5" +VERSION = "1.2.7" -# Copyright (c) 2019, Pycom Limited. +# Copyright (c) 2021, Pycom Limited. # # This software is licensed under the GNU GPL version 3 or any # later version, with permitted additional terms. For more information @@ -129,6 +129,7 @@ def return_code(self, rsp, debug=False): def wait_for_modem(self, send=True, expected=b'OK', echo_char=None): + self.__serial.read() rsp = b'' start = time.time() while True: @@ -339,16 +340,17 @@ def __get_wait_msg(self, load_fff=True): - def __run(self, file_path=None, baudrate=921600, port=None, resume=False, load_ffh=False, mirror=False, switch_ffh=False, bootrom=False, rgbled=0x050505, debug=False, pkgdebug=False, atneg=True, max_try=10, direct=True, atneg_only=False, info_only=False, expected_smod=None, verbose=False, load_fff=False, mtools=False): + def __run(self, file_path=None, baudrate=921600, port=None, resume=False, load_ffh=False, mirror=False, switch_ffh=False, bootrom=False, rgbled=0x050505, debug=False, pkgdebug=False, atneg=True, max_try=10, direct=True, atneg_only=False, info_only=False, expected_smod=None, verbose=False, load_fff=False, mtools=False, fc=False, force_fff=False): self.__wait_msg = False mirror = True if atneg_only else mirror recover = True if atneg_only else load_ffh resume = True if mirror or recover or atneg_only or info_only else resume verbose = True if debug else verbose load_fff = False if bootrom and switch_ffh else load_fff + load_fff = True if force_fff else load_fff target_baudrate = baudrate baudrate = self.__modem_speed if self.__speed_detected else baudrate - if debug: print('mirror? {} recover? {} resume? {} direct? {} atneg_only? {} bootrom? {} load_fff? {}'.format(mirror, recover, resume, direct, atneg_only, bootrom, load_fff)) + if debug: print('file_path? {} mirror? {} recover? {} resume? {} direct? {} atneg_only? {} bootrom? {} load_fff? {}'.format(file_path, mirror, recover, resume, direct, atneg_only, bootrom, load_fff)) if debug: print('baudrate: {} target_baudrate: {}'.format(baudrate, target_baudrate)) abort = True external = False @@ -365,7 +367,7 @@ def __run(self, file_path=None, baudrate=921600, port=None, resume=False, load_f external = True br = 115200 if recover and not direct else baudrate if debug: print('Setting baudrate to {}'.format(br)) - self.__serial = serial.Serial(port, br, bytesize=serial.EIGHTBITS, timeout=1 if info_only else 0.1) + self.__serial = serial.Serial(port, br, bytesize=serial.EIGHTBITS, timeout=1 if info_only else 0.1, rtscts=fc) self.__serial.reset_input_buffer() self.__serial.reset_output_buffer() @@ -399,7 +401,11 @@ def __run(self, file_path=None, baudrate=921600, port=None, resume=False, load_f from sqnsbrz import bootrom except: # fallback to uncompressed - from sqnsbr import bootrom + try: + from sqnsbr import bootrom + except: + print('This firmware does not contain a recovery bootrom.') + return False blob = bootrom() blobsize = blob.get_size() else: @@ -408,11 +414,11 @@ def __run(self, file_path=None, baudrate=921600, port=None, resume=False, load_f if blobsize < 128: print('Firmware file is too small!') reconnect_uart() - sys.exit(1) + return False if blobsize > 4194304: - if load_fff: + if load_fff and not force_fff: print("Firmware file is too big to load via FFF method. Using ON_THE_FLY") - load_fff = False + load_fff = False blob = open(file_path, "rb") if not load_ffh: @@ -445,12 +451,6 @@ def __run(self, file_path=None, baudrate=921600, port=None, resume=False, load_f response = self.return_pretty_response(self.read_rsp(size=7)) if debug: print("AT+SMOD? returned {}".format(response)) - if verbose: print('Sending AT+FSRDFILE="/fs/crashdump"') - self.__serial.write(b'AT+FSRDFILE="/fs/crashdump"\r\n') - response = self.read_rsp(size=100) - if verbose: print('AT+FSRDFILE="/fs/crashdump" returned {}'.format(response)) - self.__serial.read() - self.__serial.write(b"AT+SQNSUPGRADENTF=\"started\"\r\n") response = self.read_rsp(size=100) if verbose: print('AT+SQNSUPGRADENTF="started" returned {}'.format(response)) @@ -484,7 +484,7 @@ def __run(self, file_path=None, baudrate=921600, port=None, resume=False, load_f else: print('Received ERROR from AT+SMSWBOOT=3,1! Aborting!') reconnect_uart() - sys.exit(1) + return False time.sleep(3) resp = self.__serial.read() if debug: print("Response after reset: {}".format(resp)) @@ -501,12 +501,6 @@ def __run(self, file_path=None, baudrate=921600, port=None, resume=False, load_f response = self.read_rsp(size=100) if verbose: print("AT+SMLOG? returned {}".format(response)) - if verbose: print('Sending AT+FSRDFILE="/fs/crashdump"') - self.__serial.write(b'AT+FSRDFILE="/fs/crashdump"\r\n') - response = self.read_rsp(size=100) - if verbose: print('AT+FSRDFILE="/fs/crashdump" returned {}'.format(response)) - self.__serial.read() - else: self.__serial.read(100) @@ -525,10 +519,10 @@ def __run(self, file_path=None, baudrate=921600, port=None, resume=False, load_f time.sleep(.5) self.__serial.read(100) print('Going into MIRROR mode... please close this terminal to resume the upgrade via UART') - self.uart_mirror(rgbled) + return self.uart_mirror(rgbled) elif bootrom: - if verbose: print('Starting STP') + if verbose: print('Starting STP [BR]') else: if verbose: if load_fff: @@ -548,8 +542,11 @@ def __run(self, file_path=None, baudrate=921600, port=None, resume=False, load_f response = self.read_rsp(size=4) if response != b'OK\r\n' and response != b'\r\nOK' and response != b'\nOK': raise OSError("Invalid answer '%s' from the device" % response) - blob.close() - + try: + blob.close() + except Exception as ex: + if debug: print('Exception: {}'.format(ex)) + pass self.__serial.read() elif recover and (not direct): if atneg: @@ -564,31 +561,40 @@ def __run(self, file_path=None, baudrate=921600, port=None, resume=False, load_f time.sleep(.5) self.__serial.read(100) print('Going into MIRROR mode... please close this terminal to resume the upgrade via UART') - self.uart_mirror(rgbled) + return self.uart_mirror(rgbled) else: self.__serial.write(b"AT+STP\n") - response = self.read_rsp(size=6) + response = self.read_rsp(size=2) if not b'OK' in response: print('Failed to start STP mode!') reconnect_uart() - sys.exit(1) + return False else: print('AT auto-negotiation failed! Exiting.') return False else: if debug: print('Starting STP mode...') self.__serial.write(b"AT+STP\n") - response = self.read_rsp(size=6) + response = self.read_rsp(size=4) if not b'OK' in response: print('Failed to start STP mode!') reconnect_uart() - sys.exit(1) + return False try: if debug: - if verbose: print('Starting STP code upload') - if stp.start(blob, blobsize, self.__serial, baudrate, AT=False, debug=debug, pkgdebug=pkgdebug): - blob.close() + if verbose: print('Starting STP code upload with pkgdebug={}'.format(pkgdebug)) + time.sleep(.1) + self.__serial.read() + time.sleep(.1) + start = stp.start(blob, blobsize, self.__serial, baudrate, AT=False, debug=debug, pkgdebug=pkgdebug) + if debug: print('start returned {} type {}'.format(start, type(start))) + if start == True: + try: + blob.close() + except Exception as ex: + if debug: print('Exception: {}'.format(ex)) + pass self.__serial.read() if switch_ffh: if verbose: print('Bootrom updated successfully, switching to recovery mode') @@ -602,12 +608,22 @@ def __run(self, file_path=None, baudrate=921600, port=None, resume=False, load_f if verbose: print('Code download done, returning to user mode') abort = recover else: - blob.close() - print('Code download failed, aborting!') + try: + blob.close() + except Exception as ex: + if debug: print('Exception: {}'.format(ex)) + pass + print('Code download failed[1], aborting!') return False - except: - blob.close() - print('Code download failed, aborting!') + except Exception as ex: + try: + blob.close() + except Exception as ex: + if debug: print('Exception: {}'.format(ex)) + pass + + print('Exception: {}'.format(ex)) + print('Code download failed [2], aborting!') abort = True time.sleep(1.5) @@ -638,7 +654,7 @@ def __run(self, file_path=None, baudrate=921600, port=None, resume=False, load_f if not self.wakeup_modem(baudrate, port, 100, 1, debug, self.__get_wait_msg(load_fff=load_fff)): print("Timeout while waiting for modem to finish updating!") reconnect_uart() - sys.exit(1) + return False start = time.time() while True: @@ -708,7 +724,7 @@ def __run(self, file_path=None, baudrate=921600, port=None, resume=False, load_f else: print("Invalid response after upgrade... aborting.") reconnect_uart() - sys.exit(1) + return False self.__serial.write(b"AT\r\n") self.__serial.write(b"AT\r\n") @@ -720,11 +736,6 @@ def __run(self, file_path=None, baudrate=921600, port=None, resume=False, load_f resonse = self.read_rsp(100) if verbose: print('AT+SQNSUPGRADENTF="success" returned {}'.format(response)) time.sleep(.25) - if verbose: print('Sending AT+FSRDFILE="/fs/crashdump"') - self.__serial.write(b'AT+FSRDFILE="/fs/crashdump"\r\n') - resonse = self.read_rsp(100) - if verbose: print('AT+FSRDFILE="/fs/crashdump" returned {}'.format(response)) - self.__serial.read() return True elif sqnup_result is None: print('Modem upgrade was unsucessfull. Please check your firmware file(s)') @@ -819,7 +830,7 @@ def at_negotiation(self, baudrate, port, max_try, mirror, atneg_only, debug, tar self.__serial = UART(1, baudrate=target_baudrate, pins=self.__pins, timeout_chars=100) else: self.__serial = None - self.__serial = serial.Serial(port, target_baudrate, bytesize=serial.EIGHTBITS, timeout=0.1) + self.__serial = serial.Serial(port, target_baudrate, bytesize=serial.EIGHTBITS, timeout=0.1, rtscts=fc) self.__serial.reset_input_buffer() self.__serial.reset_output_buffer() self.__serial.flush() @@ -849,13 +860,14 @@ def uart_mirror(self, color): time.sleep(.5) pycom.rgbled(color) LTE.modem_upgrade_mode() + return True def success_message(self, port=None, verbose=False, debug=False): print("Your modem has been successfully updated.") print("Here is the current firmware version:\n") self.show_info(port=port, verbose=verbose, debug=debug) - def upgrade(self, ffile, mfile=None, baudrate=921600, retry=False, resume=False, debug=False, pkgdebug=False, verbose=False, load_fff=True, load_only=False, mtools=False): + def upgrade(self, ffile, mfile=None, baudrate=921600, retry=False, resume=False, debug=False, pkgdebug=False, verbose=False, load_fff=True, load_only=False, mtools=False, force_fff=False): success = True if not retry and mfile is not None: if resume or self.__check_br(br_only=True, verbose=verbose, debug=debug): @@ -880,12 +892,13 @@ def upgrade(self, ffile, mfile=None, baudrate=921600, retry=False, resume=False, print('Unable to upgrade bootrom.') if debug: print('Success2? {}'.format(success)) if success: - if self.__run(file_path=ffile, resume=True if mfile is not None else resume, baudrate=baudrate, direct=False, debug=debug, pkgdebug=pkgdebug, verbose=verbose, load_fff=False if mfile else load_fff, mtools=mtools): + if self.__run(file_path=ffile, resume=True if mfile is not None else resume, baudrate=baudrate, direct=False, debug=debug, pkgdebug=pkgdebug, verbose=verbose, load_fff=False if mfile else load_fff, mtools=mtools, force_fff=force_fff): if self.__check_br(verbose=verbose, debug=debug): - self.__run(bootrom=True, debug=debug, direct=False, pkgdebug=pkgdebug, verbose=verbose, load_fff=True) + success = self.__run(bootrom=True, debug=debug, direct=False, pkgdebug=pkgdebug, verbose=verbose, load_fff=True) self.success_message(verbose=verbose, debug=debug) else: print('Unable to load updater from {}'.format(mfile)) + return success def upgrade_uart(self, ffh_mode=False, mfile=None, retry=False, resume=False, color=0x050505, debug=False, pkgdebug=False, verbose=False, load_fff=True): success = False @@ -896,11 +909,11 @@ def upgrade_uart(self, ffh_mode=False, mfile=None, retry=False, resume=False, co if not success: print('Firmware does not support LTE.modem_upgrade_mode()!') reconnect_uart() - sys.exit(1) + return False print('Preparing modem for upgrade...') if not retry and ffh_mode: success = False - if self.__check_br(verbose=verbose, debug=debug): + if self.__check_br(br_only=True, verbose=verbose, debug=debug): success = self.__run(bootrom=True, resume=resume, switch_ffh=True, direct=False, debug=debug, pkgdebug=pkgdebug, verbose=verbose) time.sleep(1) else: @@ -923,16 +936,16 @@ def upgrade_uart(self, ffh_mode=False, mfile=None, retry=False, resume=False, co else: print('Unable to upgrade bootrom.') - def show_info(self, port=None, debug=False, verbose=False): - self.__run(port=port, debug=debug, info_only=True, verbose=verbose) + def show_info(self, port=None, debug=False, verbose=False, fc=False): + self.__run(port=port, debug=debug, info_only=True, verbose=verbose, fc=fc) - def upgrade_ext(self, port, ffile, mfile, resume=False, debug=False, pkgdebug=False, verbose=False, load_fff=True): + def upgrade_ext(self, port, ffile, mfile, resume=False, debug=False, pkgdebug=False, verbose=False, load_fff=True, fc=False, force_fff=False): success = True if mfile is not None: success = False - success = self.__run(file_path=mfile, load_ffh=True, port=port, debug=debug, pkgdebug=pkgdebug, verbose=verbose) + success = self.__run(file_path=mfile, load_ffh=True, port=port, debug=debug, pkgdebug=pkgdebug, verbose=verbose, fc=fc) if success: - if self.__run(file_path=ffile, resume=True if mfile is not None else resume, direct=False, port=port, debug=debug, pkgdebug=pkgdebug, verbose=verbose, load_fff=load_fff): + if self.__run(file_path=ffile, resume=True if mfile is not None else resume, direct=False, port=port, debug=debug, pkgdebug=pkgdebug, verbose=verbose, load_fff=load_fff, fc=fc, force_fff=force_fff): self.success_message(port=port, verbose=verbose, debug=debug) else: print('Unable to load updater from {}'.format(mfile)) @@ -941,7 +954,7 @@ def detect_error(): print('Could not detect your modem!') print('Please try to power off your device and restart in safeboot mode.') reconnect_uart() - sys.exit(1) + return False def print_welcome(): print('<<< Welcome to the SQN3330 firmware updater [{}] >>>'.format(VERSION)) @@ -968,11 +981,12 @@ def load(mfile, baudrate=921600, verbose=False, debug=False, hangup=False): print('Modem must be in recovery mode!') reconnect_uart() - def run(ffile, mfile=None, baudrate=921600, verbose=False, debug=False, load_fff=True, hangup=True): + def run(ffile, mfile=None, baudrate=921600, verbose=False, debug=False, load_fff=True, hangup=True, force_fff=False): print_welcome() retry = False resume = False mtools = False + success = False sqnup = sqnsupgrade() if sqnup.check_files(ffile, mfile, debug): state = sqnup.detect_modem_state(debug=debug, hangup=hangup) @@ -984,15 +998,16 @@ def run(ffile, mfile=None, baudrate=921600, verbose=False, debug=False, load_fff if mfile is None: print('Your modem is in recovery mode. Please specify updater.elf file') reconnect_uart() - sys.exit(1) + return False elif state == 4: resume = True elif state == 1: mtools = True elif state == -1: detect_error() - sqnup.upgrade(ffile=ffile, mfile=mfile, baudrate=baudrate, retry=retry, resume=resume, debug=debug, pkgdebug=False, verbose=verbose, load_fff=load_fff, mtools=mtools) + success = sqnup.upgrade(ffile=ffile, mfile=mfile, baudrate=baudrate, retry=retry, resume=resume, debug=debug, pkgdebug=False, verbose=verbose, load_fff=load_fff, mtools=mtools, force_fff=force_fff) reconnect_uart() + return success def uart(ffh_mode=False, mfile=None, color=0x050505, verbose=False, debug=False, hangup=True): print_welcome() @@ -1054,12 +1069,12 @@ def state(verbose=False, debug=False, retry=5, hangup=False): return sqnup.detect_modem_state(debug=debug, hangup=hangup, retry=retry) else: - def run(port, ffile, mfile=None, resume=False, debug=False, verbose=False, load_fff=True): + def run(port, ffile, mfile=None, resume=False, debug=False, verbose=False, load_fff=True, fc=False, force_fff=False): print_welcome() sqnup = sqnsupgrade() if sqnup.check_files(ffile, mfile, debug): - sqnup.upgrade_ext(port=port, ffile=ffile, mfile=mfile, resume=resume, debug=debug, pkgdebug=False, verbose=verbose, load_fff=load_fff) + sqnup.upgrade_ext(port=port, ffile=ffile, mfile=mfile, resume=resume, debug=debug, pkgdebug=False, verbose=verbose, load_fff=load_fff, force_fff=force_fff) - def version(port, verbose=False, debug=False): + def version(port, verbose=False, debug=False, fc=False): sqnup = sqnsupgrade() sqnup.show_info(port=port, debug=debug, verbose=verbose) diff --git a/pybytes/README.md b/pybytes/README.md deleted file mode 100644 index 406b228..0000000 --- a/pybytes/README.md +++ /dev/null @@ -1,23 +0,0 @@ -

- -# Pybytes Examples - -Please note that these examples only work when using the `pybytes` firmware. - -# Pytrack, Pysense, Pyscan -- To run pytrack on individuals module you will need to create one pybytes_config.json file on root which usually contains data related to device e.g Network preferance, device id, server, username, password etc. - -- You will needs to upload all the files to module in order to get data on pybytes dashboard. - -Note: Note: For using Pyscan, you need to upload either MFRC630.mpy or MFRC630.py. -It is always recommended to use MFRC630.mpy as it takes less space on your module. - -- Pytrack: -GPS coordinate and Acceleration would give result in tuples. - -- Pysense: -Humidity and temperature output would be in float32. -Light sensor output would be in tuples. - -- Pyscan: -Light sensor and Acceleration would give output in tuples. diff --git a/pybytes/pyscan/lib/LIS2HH12.py b/pybytes/pyscan/lib/LIS2HH12.py deleted file mode 100644 index 49a6b5a..0000000 --- a/pybytes/pyscan/lib/LIS2HH12.py +++ /dev/null @@ -1,178 +0,0 @@ -#!/usr/bin/env python -# -# Copyright (c) 2019, Pycom Limited. -# -# This software is licensed under the GNU GPL version 3 or any -# later version, with permitted additional terms. For more information -# see the Pycom Licence v1.0 document supplied with this file, or -# available at https://www.pycom.io/opensource/licensing -# - -import math -import time -import struct -from machine import Pin - - -FULL_SCALE_2G = const(0) -FULL_SCALE_4G = const(2) -FULL_SCALE_8G = const(3) - -ODR_POWER_DOWN = const(0) -ODR_10_HZ = const(1) -ODR_50_HZ = const(2) -ODR_100_HZ = const(3) -ODR_200_HZ = const(4) -ODR_400_HZ = const(5) -ODR_800_HZ = const(6) - -ACC_G_DIV = 1000 * 65536 - - -class LIS2HH12: - - ACC_I2CADDR = const(30) - - PRODUCTID_REG = const(0x0F) - CTRL1_REG = const(0x20) - CTRL2_REG = const(0x21) - CTRL3_REG = const(0x22) - CTRL4_REG = const(0x23) - CTRL5_REG = const(0x24) - ACC_X_L_REG = const(0x28) - ACC_X_H_REG = const(0x29) - ACC_Y_L_REG = const(0x2A) - ACC_Y_H_REG = const(0x2B) - ACC_Z_L_REG = const(0x2C) - ACC_Z_H_REG = const(0x2D) - ACT_THS = const(0x1E) - ACT_DUR = const(0x1F) - - SCALES = {FULL_SCALE_2G: 4000, FULL_SCALE_4G: 8000, FULL_SCALE_8G: 16000} - ODRS = [0, 10, 50, 100, 200, 400, 800] - - def __init__(self, pysense = None, sda = 'P22', scl = 'P21'): - if pysense is not None: - self.i2c = pysense.i2c - else: - from machine import I2C - self.i2c = I2C(0, mode=I2C.MASTER, pins=(sda, scl)) - - self.odr = 0 - self.full_scale = 0 - self.x = 0 - self.y = 0 - self.z = 0 - self.int_pin = None - self.act_dur = 0 - self.debounced = False - - whoami = self.i2c.readfrom_mem(ACC_I2CADDR , PRODUCTID_REG, 1) - if (whoami[0] != 0x41): - raise ValueError("LIS2HH12 not found") - - # enable acceleration readings at 50Hz - self.set_odr(ODR_50_HZ) - - # change the full-scale to 4g - self.set_full_scale(FULL_SCALE_4G) - - # set the interrupt pin as active low and open drain - self.set_register(CTRL5_REG, 3, 0, 3) - - # make a first read - self.acceleration() - - def acceleration(self): - x = self.i2c.readfrom_mem(ACC_I2CADDR , ACC_X_L_REG, 2) - self.x = struct.unpack(' self.SCALES[self.full_scale]: - error = "threshold %d exceeds full scale %d" % (thresold, self.SCALES[self.full_scale]) - print(error) - raise ValueError(error) - - if threshold < self.SCALES[self.full_scale] / 128: - error = "threshold %d below resolution %d" % (thresold, self.SCALES[self.full_scale]/128) - print(error) - raise ValueError(error) - - if duration > 255 * 1000 * 8 / self.ODRS[self.odr]: - error = "duration %d exceeds max possible value %d" % (duration, 255 * 1000 * 8 / self.ODRS[self.odr]) - print(error) - raise ValueError(error) - - if duration < 1000 * 8 / self.ODRS[self.odr]: - error = "duration %d below resolution %d" % (duration, 1000 * 8 / self.ODRS[self.odr]) - print(error) - raise ValueError(error) - - _ths = int(127 * threshold / self.SCALES[self.full_scale]) & 0x7F - _dur = int((duration * self.ODRS[self.odr]) / 1000 / 8) - - self.i2c.writeto_mem(ACC_I2CADDR, ACT_THS, _ths) - self.i2c.writeto_mem(ACC_I2CADDR, ACT_DUR, _dur) - - # enable the activity/inactivity interrupt - self.set_register(CTRL3_REG, 1, 5, 1) - - self._user_handler = handler - self.int_pin = Pin('P13', mode=Pin.IN) - self.int_pin.callback(trigger=Pin.IRQ_FALLING | Pin.IRQ_RISING, handler=self._int_handler) - - # return actual used thresold and duration - return (_ths * self.SCALES[self.full_scale] / 128, _dur * 8 * 1000 / self.ODRS[self.odr]) - - def activity(self): - if not self.debounced: - time.sleep_ms(self.act_dur) - self.debounced = True - if self.int_pin(): - return True - return False - - def _int_handler(self, pin_o): - if self._user_handler is not None: - self._user_handler(pin_o) - else: - if pin_o(): - print('Activity interrupt') - else: - print('Inactivity interrupt') diff --git a/pybytes/pyscan/lib/LTR329ALS01.py b/pybytes/pyscan/lib/LTR329ALS01.py deleted file mode 100644 index 49e3439..0000000 --- a/pybytes/pyscan/lib/LTR329ALS01.py +++ /dev/null @@ -1,80 +0,0 @@ -#!/usr/bin/env python -# -# Copyright (c) 2019, Pycom Limited. -# -# This software is licensed under the GNU GPL version 3 or any -# later version, with permitted additional terms. For more information -# see the Pycom Licence v1.0 document supplied with this file, or -# available at https://www.pycom.io/opensource/licensing -# - -import time -from machine import I2C - -class LTR329ALS01: - ALS_I2CADDR = const(0x29) # The device's I2C address - - ALS_CONTR_REG = const(0x80) - ALS_MEAS_RATE_REG = const(0x85) - - ALS_DATA_CH1_LOW = const(0x88) - ALS_DATA_CH1_HIGH = const(0x89) - ALS_DATA_CH0_LOW = const(0x8A) - ALS_DATA_CH0_HIGH = const(0x8B) - - ALS_GAIN_1X = const(0x00) - ALS_GAIN_2X = const(0x01) - ALS_GAIN_4X = const(0x02) - ALS_GAIN_8X = const(0x03) - ALS_GAIN_48X = const(0x06) - ALS_GAIN_96X = const(0x07) - - ALS_INT_50 = const(0x01) - ALS_INT_100 = const(0x00) - ALS_INT_150 = const(0x04) - ALS_INT_200 = const(0x02) - ALS_INT_250 = const(0x05) - ALS_INT_300 = const(0x06) - ALS_INT_350 = const(0x07) - ALS_INT_400 = const(0x03) - - ALS_RATE_50 = const(0x00) - ALS_RATE_100 = const(0x01) - ALS_RATE_200 = const(0x02) - ALS_RATE_500 = const(0x03) - ALS_RATE_1000 = const(0x04) - ALS_RATE_2000 = const(0x05) - - def __init__(self, pysense = None, sda = 'P22', scl = 'P21', gain = ALS_GAIN_1X, integration = ALS_INT_100, rate = ALS_RATE_500): - if pysense is not None: - self.i2c = pysense.i2c - else: - self.i2c = I2C(0, mode=I2C.MASTER, pins=(sda, scl)) - - contr = self._getContr(gain) - self.i2c.writeto_mem(ALS_I2CADDR, ALS_CONTR_REG, bytearray([contr])) - - measrate = self._getMeasRate(integration, rate) - self.i2c.writeto_mem(ALS_I2CADDR, ALS_MEAS_RATE_REG, bytearray([measrate])) - - time.sleep(0.01) - - def _getContr(self, gain): - return ((gain & 0x07) << 2) + 0x01 - - def _getMeasRate(self, integration, rate): - return ((integration & 0x07) << 3) + (rate & 0x07) - - def _getWord(self, high, low): - return ((high & 0xFF) << 8) + (low & 0xFF) - - def light(self): - ch1low = self.i2c.readfrom_mem(ALS_I2CADDR , ALS_DATA_CH1_LOW, 1) - ch1high = self.i2c.readfrom_mem(ALS_I2CADDR , ALS_DATA_CH1_HIGH, 1) - data1 = int(self._getWord(ch1high[0], ch1low[0])) - - ch0low = self.i2c.readfrom_mem(ALS_I2CADDR , ALS_DATA_CH0_LOW, 1) - ch0high = self.i2c.readfrom_mem(ALS_I2CADDR , ALS_DATA_CH0_HIGH, 1) - data0 = int(self._getWord(ch0high[0], ch0low[0])) - - return (data0, data1) diff --git a/pybytes/pyscan/lib/MFRC630.mpy b/pybytes/pyscan/lib/MFRC630.mpy deleted file mode 100644 index 81ee92f..0000000 Binary files a/pybytes/pyscan/lib/MFRC630.mpy and /dev/null differ diff --git a/pybytes/pyscan/lib/MFRC630.py b/pybytes/pyscan/lib/MFRC630.py deleted file mode 100644 index 20ad25d..0000000 --- a/pybytes/pyscan/lib/MFRC630.py +++ /dev/null @@ -1,756 +0,0 @@ -''' -Pyscan NFC library -Copyright (c) 2019, Pycom Limited. -Based on a library for NXP's MFRC630 NFC IC https://github.com/iwanders/MFRC630 -The MIT License (MIT) -Copyright (c) 2016 Ivor Wanders -Permission is hereby granted, free of charge, to any person obtaining a copy -of this software and associated documentation files (the "Software"), to deal -in the Software without restriction, including without limitation the rights -to use, copy, modify, merge, publish, distribute, sublicense, and/or sell -copies of the Software, and to permit persons to whom the Software is -furnished to do so, subject to the following conditions: -The above copyright notice and this permission notice shall be included in all -copies or substantial portions of the Software. -THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR -IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, -FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE -AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER -LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, -OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE -SOFTWARE. -''' - -import time, binascii - -class MFRC630: - - NFC_I2CADDR = const(0x28) - # commands - MFRC630_CMD_IDLE = const(0x00) # (no arguments) ; no action, cancels current command execution. */ - MFRC630_CMD_LPCD = const(0x01) # (no arguments) ; low-power card detection. */ - MFRC630_CMD_LOADKEY = const(0x02) # (keybyte1), (keybyte2), (keybyte3), (keybyte4), (keybyte5), - MFRC630_CMD_MFAUTHENT = const(0x03) # 60h or 61h, (block address), (card serial number byte0), (card - MFRC630_CMD_RECEIVE = const(0x05) # (no arguments) ; activates the receive circuit. */ - MFRC630_CMD_TRANSMIT = const(0x06) # bytes to send: byte1, byte2, ...; transmits data from the FIFO - MFRC630_CMD_TRANSCEIVE = const(0x07) # bytes to send: byte1, byte2, ....; transmits data from the FIFO - MFRC630_CMD_WRITEE2 = const(0x08) # addressH, addressL, data; gets one byte from FIFO buffer and - MFRC630_CMD_WRITEE2PAGE = const(0x09) # (page Address), data0, [data1..data63]; gets up to 64 bytes (one - MFRC630_CMD_READE2 = const(0x0A) # addressH, address L, length; reads data from the EEPROM and copies - MFRC630_CMD_LOADREG = const(0x0C) # (EEPROM addressH), (EEPROM addressL), RegAdr, (number of Register - MFRC630_CMD_LOADPROTOCOL = const(0x0D) # (Protocol number RX), (Protocol number TX) reads data from the - MFRC630_CMD_LOADKEYE2 = const(0x0E) # KeyNr; copies a key from the EEPROM into the key buffer. */ - MFRC630_CMD_STOREKEYE2 = const(0x0F) # KeyNr, byte1, byte2, byte3, byte4, byte5, byte6; stores a MIFARE - MFRC630_CMD_READRNR = const(0x1C) # (no arguments) ; Copies bytes from the Random Number generator - MFRC630_CMD_SOFTRESET = const(0x1F) # (no arguments) ; resets the MFRC630. */ - - MFRC630_STATUS_STATE_IDLE = const(0b000) # Status register; Idle - MFRC630_STATUS_STATE_TXWAIT = const(0b001) # Status register; Tx wait - MFRC630_STATUS_STATE_TRANSMITTING = const(0b011) # Status register; Transmitting. - MFRC630_STATUS_STATE_RXWAIT = const(0b101) # Status register; Rx wait. - MFRC630_STATUS_STATE_WAIT_FOR_DATA = const(0b110) # Status register; Waiting for data. - MFRC630_STATUS_STATE_RECEIVING = const(0b111) # Status register; Receiving data. - MFRC630_STATUS_STATE_NOT_USED = const(0b100) # Status register; Not used. - MFRC630_STATUS_CRYPTO1_ON = const(1 << 5) # Status register; Crypto1 (MIFARE authentication) is on. - - MFRC630_PROTO_ISO14443A_106_MILLER_MANCHESTER = const(0) - MFRC630_PROTO_ISO14443A_212_MILLER_BPSK = const(1) - MFRC630_PROTO_ISO14443A_424_MILLER_BPSK = const(2) - MFRC630_PROTO_ISO14443A_848_MILLER_BPSK = const(3) - MFRC630_PROTO_ISO14443B_106_NRZ_BPSK = const(4) - MFRC630_PROTO_ISO14443B_212_NRZ_BPSK = const(5) - MFRC630_PROTO_ISO14443B_424_NRZ_BPSK = const(6) - MFRC630_PROTO_ISO14443B_848_NRZ_BPSK = const(7) - MFRC630_PROTO_FELICA_212_MANCHESTER_MANCHESTER = const(8) - MFRC630_PROTO_FELICA_424_MANCHESTER_MANCHESTER = const(9) - MFRC630_PROTO_ISO15693_1_OF_4_SSC = const(10) - MFRC630_PROTO_ISO15693_1_OF_4_DSC = const(11) - MFRC630_PROTO_ISO15693_1_OF_256_SSC = const(12) - MFRC630_PROTO_EPC_UID_UNITRAY_SSC = const(13) - MFRC630_PROTO_ISO18000_MODE_3 = const(14) - MFRC630_RECOM_14443A_ID1_106 = [ 0x8A, 0x08, 0x21, 0x1A, 0x18, 0x18, 0x0F, 0x27, 0x00, 0xC0, 0x12, 0xCF, 0x00, 0x04, 0x90, 0x32, 0x12, 0x0A ] - MFRC630_RECOM_14443A_ID1_212 = [ 0x8E, 0x12, 0x11, 0x06, 0x18, 0x18, 0x0F, 0x10, 0x00, 0xC0, 0x12, 0xCF, 0x00, 0x05, 0x90, 0x3F, 0x12, 0x02 ] - MFRC630_RECOM_14443A_ID1_424 = [ 0x8E, 0x12, 0x11, 0x06, 0x18, 0x18, 0x0F, 0x08, 0x00, 0xC0, 0x12, 0xCF, 0x00, 0x06, 0x90, 0x3F, 0x12, 0x0A ] - MFRC630_RECOM_14443A_ID1_848 = [ 0x8F, 0xDB, 0x11, 0x06, 0x18, 0x18, 0x0F, 0x02, 0x00, 0xC0, 0x12, 0xCF, 0x00, 0x07, 0x90, 0x3F, 0x12, 0x02 ] - MFRC630_ISO14443_CMD_REQA = const(0x26) # request (idle -> ready) - MFRC630_ISO14443_CMD_WUPA = const(0x52) # wake up type a (idle / halt -> ready) - MFRC630_ISO14443_CAS_LEVEL_1 = const(0x93) # Cascade level 1 for select. - MFRC630_ISO14443_CAS_LEVEL_2 = const(0x95) # Cascade level 2 for select. - MFRC630_ISO14443_CAS_LEVEL_3 = const(0x97) # Cascade level 3 for select. - MFRC630_MF_AUTH_KEY_A = const(0x60) # A key_type for mifare auth. - MFRC630_MF_AUTH_KEY_B = const(0x61) # A key_type for mifare auth. - MFRC630_MF_CMD_READ = const(0x30) # To read a block from mifare card. - MFRC630_MF_CMD_WRITE = const(0xA0) # To write a block to a mifare card. - MFRC630_MF_ACK = const(0x0A) # Sent by cards to acknowledge an operation. - - # registers - MFRC630_REG_COMMAND = const(0x00) # Starts and stops command execution - MFRC630_REG_HOSTCTRL = const(0x01) # Host control register - MFRC630_REG_FIFOCONTROL = const(0x02) # Control register of the FIFO - MFRC630_REG_WATERLEVEL = const(0x03) # Level of the FIFO underflow and overflow warning - MFRC630_REG_FIFOLENGTH = const(0x04) # Length of the FIFO - MFRC630_REG_FIFODATA = const(0x05) # Data In/Out exchange register of FIFO buffer - MFRC630_REG_IRQ0 = const(0x06) # Interrupt register 0 - MFRC630_REG_IRQ1 = const(0x07) # Interrupt register 1 - MFRC630_REG_IRQ0EN = const(0x08) # Interrupt enable register 0 - MFRC630_REG_IRQ1EN = const(0x09) # Interrupt enable register 1 - MFRC630_REG_ERROR = const(0x0A) # Error bits showing the error status of the last command execution - MFRC630_REG_STATUS = const(0x0B) # Contains status of the communication - MFRC630_REG_RXBITCTRL = const(0x0C) # Control for anticoll. adjustments for bit oriented protocols - MFRC630_REG_RXCOLL = const(0x0D) # Collision position register - MFRC630_REG_TCONTROL = const(0x0E) # Control of Timer 0..3 - MFRC630_REG_T0CONTROL = const(0x0F) # Control of Timer0 - MFRC630_REG_T0RELOADHI = const(0x10) # High register of the reload value of Timer0 - MFRC630_REG_T0RELOADLO = const(0x11) # Low register of the reload value of Timer0 - MFRC630_REG_T0COUNTERVALHI = const(0x12) # Counter value high register of Timer0 - MFRC630_REG_T0COUNTERVALLO = const(0x13) # Counter value low register of Timer0 - MFRC630_REG_T1CONTROL = const(0x14) # Control of Timer1 - MFRC630_REG_T1RELOADHI = const(0x15) # High register of the reload value of Timer1 - MFRC630_REG_T1COUNTERVALHI = const(0x17) # Counter value high register of Timer1 - MFRC630_REG_T1COUNTERVALLO = const(0x18) # Counter value low register of Timer1 - MFRC630_REG_T2CONTROL = const(0x19) # Control of Timer2 - MFRC630_REG_T2RELOADHI = const(0x1A) # High byte of the reload value of Timer2 - MFRC630_REG_T2RELOADLO = const(0x1B) # Low byte of the reload value of Timer2 - MFRC630_REG_T2COUNTERVALHI = const(0x1C) # Counter value high byte of Timer2 - MFRC630_REG_T2COUNTERVALLO = const(0x1D) # Counter value low byte of Timer2 - MFRC630_REG_T3CONTROL = const(0x1E) # Control of Timer3 - MFRC630_REG_T3RELOADHI = const(0x1F) # High byte of the reload value of Timer3 - MFRC630_REG_T3RELOADLO = const(0x20) # Low byte of the reload value of Timer3 - MFRC630_REG_T3COUNTERVALHI = const(0x21) # Counter value high byte of Timer3 - MFRC630_REG_T3COUNTERVALLO = const(0x22) # Counter value low byte of Timer3 - MFRC630_REG_T4CONTROL = const(0x23) # Control of Timer4 - MFRC630_REG_T4RELOADHI = const(0x24) # High byte of the reload value of Timer4 - MFRC630_REG_T4RELOADLO = const(0x25) # Low byte of the reload value of Timer4 - MFRC630_REG_T4COUNTERVALHI = const(0x26) # Counter value high byte of Timer4 - MFRC630_REG_T4COUNTERVALLO = const(0x27) # Counter value low byte of Timer4 - MFRC630_REG_DRVMOD = const(0x28) # Driver mode register - MFRC630_REG_TXAMP = const(0x29) # Transmitter amplifier register - MFRC630_REG_DRVCON = const(0x2A) # Driver configuration register - MFRC630_REG_TXL = const(0x2B) # Transmitter register - MFRC630_REG_TXCRCPRESET = const(0x2C) # Transmitter CRC control register, preset value - MFRC630_REG_RXCRCCON = const(0x2D) # Receiver CRC control register, preset value - MFRC630_REG_TXDATANUM = const(0x2E) # Transmitter data number register - MFRC630_REG_TXMODWIDTH = const(0x2F) # Transmitter modulation width register - MFRC630_REG_TXSYM10BURSTLEN = const(0x30) # Transmitter symbol 1 + symbol 0 burst length register - MFRC630_REG_TXWAITCTRL = const(0x31) # Transmitter wait control - MFRC630_REG_TXWAITLO = const(0x32) # Transmitter wait low - MFRC630_REG_FRAMECON = const(0x33) # Transmitter frame control - MFRC630_REG_RXSOFD = const(0x34) # Receiver start of frame detection - MFRC630_REG_RXCTRL = const(0x35) # Receiver control register - MFRC630_REG_RXWAIT = const(0x36) # Receiver wait register - MFRC630_REG_RXTHRESHOLD = const(0x37) # Receiver threshold register - MFRC630_REG_RCV = const(0x38) # Receiver register - MFRC630_REG_RXANA = const(0x39) # Receiver analog register - MFRC630_REG_RFU = const(0x3A) # (Reserved for future use) - MFRC630_REG_SERIALSPEED = const(0x3B) # Serial speed register - MFRC630_REG_LFO_TRIMM = const(0x3C) # Low-power oscillator trimming register - MFRC630_REG_PLL_CTRL = const(0x3D) # IntegerN PLL control register, for mcu clock output adjustment - MFRC630_REG_PLL_DIVOUT = const(0x3E) # IntegerN PLL control register, for mcu clock output adjustment - MFRC630_REG_LPCD_QMIN = const(0x3F) # Low-power card detection Q channel minimum threshold - MFRC630_REG_LPCD_QMAX = const(0x40) # Low-power card detection Q channel maximum threshold - MFRC630_REG_LPCD_IMIN = const(0x41) # Low-power card detection I channel minimum threshold - MFRC630_REG_LPCD_I_RESULT = const(0x42) # Low-power card detection I channel result register - MFRC630_REG_LPCD_Q_RESULT = const(0x43) # Low-power card detection Q channel result register - MFRC630_REG_PADEN = const(0x44) # PIN enable register - MFRC630_REG_PADOUT = const(0x45) # PIN out register - MFRC630_REG_PADIN = const(0x46) # PIN in register - MFRC630_REG_SIGOUT = const(0x47) # Enables and controls the SIGOUT Pin - MFRC630_REG_VERSION = const(0x7F) # Version and subversion register - - MFRC630_TXDATANUM_DATAEN = const(1 << 3) - MFRC630_RECOM_14443A_CRC = const(0x18) - - MFRC630_ERROR_EE_ERR = const(1 << 7) - MFRC630_ERROR_FIFOWRERR = const(1 << 6) - MFRC630_ERROR_FIFOOVL = const(1 << 5) - MFRC630_ERROR_MINFRAMEERR = const(1 << 4) - MFRC630_ERROR_NODATAERR = const(1 << 3) - MFRC630_ERROR_COLLDET = const(1 << 2) - MFRC630_ERROR_PROTERR = const(1 << 1) - MFRC630_ERROR_INTEGERR = const(1 << 0) - - MFRC630_CRC_ON = const(1) - MFRC630_CRC_OFF = const(0) - - MFRC630_IRQ0EN_IRQ_INV = const(1 << 7) - MFRC630_IRQ0EN_HIALERT_IRQEN = const(1 << 6) - MFRC630_IRQ0EN_LOALERT_IRQEN = const(1 << 5) - MFRC630_IRQ0EN_IDLE_IRQEN = const(1 << 4) - MFRC630_IRQ0EN_TX_IRQEN = const(1 << 3) - MFRC630_IRQ0EN_RX_IRQEN = const(1 << 2) - MFRC630_IRQ0EN_ERR_IRQEN = const(1 << 1) - MFRC630_IRQ0EN_RXSOF_IRQEN = const(1 << 0) - - MFRC630_IRQ1EN_TIMER0_IRQEN = const(1 << 0) - - MFRC630_TCONTROL_CLK_211KHZ = const(0b01) - MFRC630_TCONTROL_START_TX_END = const(0b01 << 4) - - MFRC630_IRQ1_GLOBAL_IRQ = const(1 << 6) - - MFRC630_IRQ0_ERR_IRQ = const(1 << 1) - MFRC630_IRQ0_RX_IRQ = const(1 << 2) - - def __init__(self, pyscan=None, sda='P22', scl='P21', timeout=None, debug=False): - if pyscan is not None: - self.i2c = pyscan.i2c - else: - from machine import I2C - self.i2c = I2C(0, mode=I2C.MASTER, pins=(sda, scl)) - self._DEBUG = debug - self.mfrc630_cmd_reset() - - # ToDo: Timeout not yet implemented! - # self.chrono = Timer.Chrono() - # self.timeout = timeout - # self.timeout_status = True - - def print_debug(self, msg): - if self._DEBUG: - print(msg) - - def mfrc630_read_reg(self, reg): - return self.i2c.readfrom_mem(NFC_I2CADDR, reg, 1)[0] - - def mfrc630_write_reg(self, reg, data): - self.i2c.writeto_mem(NFC_I2CADDR, reg, bytes([data & 0xFF])) - - def mfrc630_write_regs(self, reg, data): - self.i2c.writeto_mem(NFC_I2CADDR, reg, bytes(data)) - - def mfrc630_read_fifo(self, len): - if len > 0: - return self.i2c.readfrom_mem(NFC_I2CADDR, MFRC630_REG_FIFODATA, len) - else: - return None - - def mfrc630_cmd_idle(self): - self.mfrc630_write_reg(MFRC630_REG_COMMAND, MFRC630_CMD_IDLE) - - def mfrc630_flush_fifo(self): - self.mfrc630_write_reg(MFRC630_REG_FIFOCONTROL, 1 << 4) - - def mfrc630_setup_fifo(self): - self.mfrc630_write_reg(MFRC630_REG_FIFOCONTROL, 0x90) - self.mfrc630_write_reg(MFRC630_REG_WATERLEVEL, 0xFE) - - def mfrc630_write_fifo(self, data): - self.mfrc630_write_regs(MFRC630_REG_FIFODATA, data) - - def mfrc630_cmd_load_protocol(self, rx, tx): - self.mfrc630_flush_fifo() - self.mfrc630_write_fifo([rx, tx]) - self.mfrc630_write_reg(MFRC630_REG_COMMAND, MFRC630_CMD_LOADPROTOCOL) - - def mfrc630_cmd_transceive(self, data): - self.mfrc630_cmd_idle() - self.mfrc630_flush_fifo() - self.mfrc630_setup_fifo() - self.mfrc630_write_fifo(data) - self.mfrc630_write_reg(MFRC630_REG_COMMAND, MFRC630_CMD_TRANSCEIVE) - - def mfrc630_cmd_init(self): - self.mfrc630_write_regs(MFRC630_REG_DRVMOD, self.MFRC630_RECOM_14443A_ID1_106) - self.mfrc630_write_reg(0x28, 0x8E) - self.mfrc630_write_reg(0x29, 0x15) - self.mfrc630_write_reg(0x2A, 0x11) - self.mfrc630_write_reg(0x2B, 0x06) - - def mfrc630_cmd_reset(self): - self.mfrc630_cmd_idle() - self.mfrc630_write_reg(MFRC630_REG_COMMAND, MFRC630_CMD_SOFTRESET) - - def mfrc630_clear_irq0(self): - self.mfrc630_write_reg(MFRC630_REG_IRQ0, ~(1 << 7)) - - def mfrc630_clear_irq1(self): - self.mfrc630_write_reg(MFRC630_REG_IRQ1, ~(1 << 7)) - - def mfrc630_irq0(self): - return self.mfrc630_read_reg(MFRC630_REG_IRQ0) - - def mfrc630_irq1(self): - return self.mfrc630_read_reg(MFRC630_REG_IRQ1) - - def mfrc630_timer_set_control(self, timer, value): - self.mfrc630_write_reg(MFRC630_REG_T0CONTROL + (5 * timer), value) - - def mfrc630_timer_set_reload(self, timer, value): - self.mfrc630_write_reg(MFRC630_REG_T0RELOADHI + (5 * timer), value >> 8) - self.mfrc630_write_reg(MFRC630_REG_T0RELOADLO + (5 * timer), 0xFF) - - def mfrc630_timer_set_value(self, timer, value): - self.mfrc630_write_reg(MFRC630_REG_T0COUNTERVALHI + (5 * timer), value >> 8) - self.mfrc630_write_reg(MFRC630_REG_T0COUNTERVALLO + (5 * timer), 0xFF) - - def mfrc630_fifo_length(self): - # should do 512 byte fifo handling here - return self.mfrc630_read_reg(MFRC630_REG_FIFOLENGTH) - - def mfrc630_status(self): - return self.mfrc630_read_reg(MFRC630_REG_STATUS) - - def mfrc630_error(self): - return self.mfrc630_read_reg(MFRC630_REG_ERROR) - - def mfrc630_cmd_load_key(self, key): - self.mfrc630_cmd_idle() - self.mfrc630_flush_fifo() - self.mfrc630_write_fifo(key) - self.mfrc630_write_reg(MFRC630_REG_COMMAND, MFRC630_CMD_LOADKEY) - - def mfrc630_cmd_auth(self, key_type, block_address, card_uid): - self.mfrc630_cmd_idle() - parameters = [ key_type, block_address, card_uid[0], card_uid[1], card_uid[2], card_uid[3] ] - self.mfrc630_flush_fifo() - self.mfrc630_write_fifo(parameters) - self.mfrc630_write_reg(MFRC630_REG_COMMAND, MFRC630_CMD_MFAUTHENT) - - def mfrc630_MF_read_block(self, block_address, dest): - self.mfrc630_flush_fifo() - - self.mfrc630_write_reg(MFRC630_REG_TXCRCPRESET, MFRC630_RECOM_14443A_CRC | MFRC630_CRC_ON) - self.mfrc630_write_reg(MFRC630_REG_RXCRCCON, MFRC630_RECOM_14443A_CRC | MFRC630_CRC_ON) - - send_req = [ MFRC630_MF_CMD_READ, block_address ] - - # configure a timeout timer. - timer_for_timeout = 0 # should match the enabled interupt. - - # enable the global IRQ for idle, errors and timer. - self.mfrc630_write_reg(MFRC630_REG_IRQ0EN, MFRC630_IRQ0EN_IDLE_IRQEN | MFRC630_IRQ0EN_ERR_IRQEN) - self.mfrc630_write_reg(MFRC630_REG_IRQ1EN, MFRC630_IRQ1EN_TIMER0_IRQEN) - - - # Set timer to 221 kHz clock, start at the end of Tx. - self.mfrc630_timer_set_control(timer_for_timeout, MFRC630_TCONTROL_CLK_211KHZ | MFRC630_TCONTROL_START_TX_END) - # Frame waiting time: FWT = (256 x 16/fc) x 2 FWI - # FWI defaults to four... so that would mean wait for a maximum of ~ 5ms - self.mfrc630_timer_set_reload(timer_for_timeout, 2000) # 2000 ticks of 5 usec is 10 ms. - self.mfrc630_timer_set_value(timer_for_timeout, 2000) - - irq1_value = 0 - irq0_value = 0 - - self.mfrc630_clear_irq0() # clear irq0 - self.mfrc630_clear_irq1() # clear irq1 - - # Go into send, then straight after in receive. - self.mfrc630_cmd_transceive(send_req) - - # block until we are done - while not (irq1_value & (1 << timer_for_timeout)): - irq1_value = self.mfrc630_irq1() - if (irq1_value & MFRC630_IRQ1_GLOBAL_IRQ): - self.print_debug("irq1: %x" % irq1_value) - break # stop polling irq1 and quit the timeout loop. - - self.mfrc630_cmd_idle() - - if irq1_value & (1 << timer_for_timeout): - self.print_debug("this indicates a timeout") - # this indicates a timeout - return 0 - - irq0_value = self.mfrc630_irq0() - if (irq0_value & MFRC630_IRQ0_ERR_IRQ): - self.print_debug("some error") - # some error - return 0 - - self.print_debug("all seems to be well...") - # all seems to be well... - buffer_length = self.mfrc630_fifo_length() - rx_len = buffer_length if (buffer_length <= 16) else 16 - dest = self.mfrc630_read_fifo(rx_len) - return rx_len - - - def mfrc630_iso14443a_WUPA_REQA(self, instruction): - self.mfrc630_cmd_idle() - - self.mfrc630_flush_fifo() - - #Set register such that we sent 7 bits, set DataEn such that we can send data - self.mfrc630_write_reg(MFRC630_REG_TXDATANUM, 7 | MFRC630_TXDATANUM_DATAEN) - - # disable the CRC registers - self.mfrc630_write_reg(MFRC630_REG_TXCRCPRESET, MFRC630_RECOM_14443A_CRC | MFRC630_CRC_OFF) - self.mfrc630_write_reg(MFRC630_REG_RXCRCCON, MFRC630_RECOM_14443A_CRC | MFRC630_CRC_OFF) - self.mfrc630_write_reg(MFRC630_REG_RXBITCTRL, 0) - - # clear interrupts - self.mfrc630_clear_irq0() - self.mfrc630_clear_irq1() - - # enable the global IRQ for Rx done and Errors. - self.mfrc630_write_reg(MFRC630_REG_IRQ0EN, MFRC630_IRQ0EN_RX_IRQEN | MFRC630_IRQ0EN_ERR_IRQEN) - self.mfrc630_write_reg(MFRC630_REG_IRQ1EN, MFRC630_IRQ1EN_TIMER0_IRQEN) - - # configure timer - timer_for_timeout = 0 - # Set timer to 221 kHz clock, start at the end of Tx. - self.mfrc630_timer_set_control(timer_for_timeout, MFRC630_TCONTROL_CLK_211KHZ | MFRC630_TCONTROL_START_TX_END) - - # Frame waiting time: FWT = (256 x 16/fc) x 2 FWI - # FWI defaults to four... so that would mean wait for a maximum of ~ 5ms - self.mfrc630_timer_set_reload(timer_for_timeout, 1000) # 1000 ticks of 5 usec is 5 ms. - self.mfrc630_timer_set_value(timer_for_timeout, 1000) - - # Go into send, then straight after in receive. - self.mfrc630_cmd_transceive([instruction]) - self.print_debug('Sending REQA') - - # block until we are done - irq1_value = 0 - while not (irq1_value & (1 << timer_for_timeout)): - irq1_value = self.mfrc630_irq1() - if irq1_value & MFRC630_IRQ1_GLOBAL_IRQ: # either ERR_IRQ or RX_IRQ - break # stop polling irq1 and quit the timeout loop - - self.print_debug('After waiting for answer') - self.mfrc630_cmd_idle() - - # if no Rx IRQ, or if there's an error somehow, return 0 - irq0 = self.mfrc630_irq0() - if (not (irq0 & MFRC630_IRQ0_RX_IRQ)) or (irq0 & MFRC630_IRQ0_ERR_IRQ): - self.print_debug('No RX, irq1: %x irq0: %x' % (irq1_value, irq0)) - return 0 - - return self.mfrc630_fifo_length() - self.print_debug("rx_len:", rx_len) - if rx_len == 2: # ATQA should answer with 2 bytes - res = self.mfrc630_read_fifo(rx_len) - self.print_debug('ATQA answer:', res) - return res - return 0 - - def mfrc630_print_block(self, data, len): - if self._DEBUG: - print(self.mfrc630_format_block(data, len)) - - def mfrc630_format_block(self, data, len): - if type(data) == bytearray: - len_i = 0 - try: - len_i = int(len) - except: - pass - if (len_i > 0): - return ' '.join('{:02x}'.format(x) for x in data[:len_i]).upper() - else: - return ' '.join('{:02x}'.format(x) for x in data).upper() - else: - self.print_debug("DATA has type: " + str(type(data))) - try: - return "Length: %d Data: %s" % (len,binascii.hexlify(data,' ')) - except: - return "Data: %s with Length: %s" % (str(data), len) - - - def mfrc630_iso14443a_select(self, uid): - - self.print_debug("Starting select") - - self.mfrc630_cmd_idle() - self.mfrc630_flush_fifo() - - # enable the global IRQ for Rx done and Errors. - self.mfrc630_write_reg(MFRC630_REG_IRQ0EN, MFRC630_IRQ0EN_RX_IRQEN | MFRC630_IRQ0EN_ERR_IRQEN) - self.mfrc630_write_reg(MFRC630_REG_IRQ1EN, MFRC630_IRQ1EN_TIMER0_IRQEN) # only trigger on timer for irq1 - - # configure a timeout timer, use timer 0. - timer_for_timeout = 0 - - # Set timer to 221 kHz clock, start at the end of Tx. - self.mfrc630_timer_set_control(timer_for_timeout, MFRC630_TCONTROL_CLK_211KHZ | MFRC630_TCONTROL_START_TX_END) - # Frame waiting time: FWT = (256 x 16/fc) x 2 FWI - # FWI defaults to four... so that would mean wait for a maximum of ~ 5ms - - self.mfrc630_timer_set_reload(timer_for_timeout, 1000) # 1000 ticks of 5 usec is 5 ms. - self.mfrc630_timer_set_value(timer_for_timeout, 1000) - - for cascade_level in range(1, 4): - self.print_debug("Starting cascade level: %d" % cascade_level) - cmd = 0 - known_bits = 0 # known bits of the UID at this level so far. - send_req = bytearray(7) # used as Tx buffer. - uid_this_level = send_req[2:] - message_length = 0 - if cascade_level == 1: - cmd = MFRC630_ISO14443_CAS_LEVEL_1; - elif cascade_level == 2: - cmd = MFRC630_ISO14443_CAS_LEVEL_2; - elif cascade_level == 3: - cmd = MFRC630_ISO14443_CAS_LEVEL_3; - - # disable CRC in anticipation of the anti collision protocol - self.mfrc630_write_reg(MFRC630_REG_TXCRCPRESET, MFRC630_RECOM_14443A_CRC | MFRC630_CRC_OFF) - self.mfrc630_write_reg(MFRC630_REG_RXCRCCON, MFRC630_RECOM_14443A_CRC | MFRC630_CRC_OFF) - - # max 32 loops of the collision loop. - for collision_n in range(0, 33): - self.print_debug("CL: %d, coll loop: %d, kb %d long" % (cascade_level, collision_n, known_bits)) - self.mfrc630_print_block(uid_this_level, (known_bits + 8 - 1) / 8) - # clear interrupts - self.mfrc630_clear_irq0() - self.mfrc630_clear_irq1() - - send_req[0] = cmd; - send_req[1] = 0x20 + known_bits - send_req[2:5] = uid_this_level[0:3] - - # Only transmit the last 'x' bits of the current byte we are discovering - # First limit the txdatanum, such that it limits the correct number of bits. - self.mfrc630_write_reg(MFRC630_REG_TXDATANUM, (known_bits % 8) | MFRC630_TXDATANUM_DATAEN) - - # ValuesAfterColl: If cleared, every received bit after a collision is - # replaced by a zero. This function is needed for ISO/IEC14443 anticollision (0<<7). - # We want to shift the bits with RxAlign - rxalign = known_bits % 8; - self.print_debug("Setting rx align to: %d" % rxalign) - self.mfrc630_write_reg(MFRC630_REG_RXBITCTRL, (0 << 7) | (rxalign << 4)) - - # then sent the send_req to the hardware, - # (known_bits / 8) + 1): The ceiled number of bytes by known bits. - # +2 for cmd and NVB. - if ((known_bits % 8) == 0): - message_length = ((known_bits / 8)) + 2; - else: - message_length = ((known_bits / 8) + 1) + 2; - - # Send message - self.mfrc630_cmd_transceive(send_req[:int(message_length)]) - - # block until we are done - irq1_value = 0 - while not (irq1_value & (1 << timer_for_timeout)): - irq1_value = self.mfrc630_irq1() - # either ERR_IRQ or RX_IRQ or Timer - if (irq1_value & MFRC630_IRQ1_GLOBAL_IRQ): - break # stop polling irq1 and quit the timeout loop. - - self.mfrc630_cmd_idle() - - # next up, we have to check what happened. - irq0 = self.mfrc630_irq0() - error = self.mfrc630_read_reg(MFRC630_REG_ERROR) - coll = self.mfrc630_read_reg(MFRC630_REG_RXCOLL) - self.print_debug("irq0: %x coll: %x error: %x " % (irq0, coll, error)) - collision_pos = 0 - if irq0 and MFRC630_IRQ0_ERR_IRQ: # some error occured. - self.print_debug("some error occured.") - # Check what kind of error. - if (error & MFRC630_ERROR_COLLDET): - # A collision was detected... - if (coll & (1 << 7)): - collision_pos = coll & (~(1 << 7)) - self.print_debug("Collision at %x", collision_pos) - # This be a true collision... we have to select either the address - # with 1 at this position or with zero - # ISO spec says typically a 1 is added, that would mean: - # uint8_t selection = 1; - - # However, it makes sense to allow some kind of user input for this, so we use the - # current value of uid at this position, first index right byte, then shift such - # that it is in the rightmost position, ten select the last bit only. - # We cannot compensate for the addition of the cascade tag, so this really - # only works for the first cascade level, since we only know whether we had - # a cascade level at the end when the SAK was received. - choice_pos = known_bits + collision_pos - selection = (uid[((choice_pos + (cascade_level - 1) * 3) / 8)] >> ((choice_pos) % 8)) & 1 - - # We just OR this into the UID at the right position, later we - # OR the UID up to this point into uid_this_level. - uid_this_level[((choice_pos) / 8)] |= selection << ((choice_pos) % 8) - known_bits = known_bits + 1 # add the bit we just decided. - self.print_debug("Known Bits: %d" % known_bits) - - self.print_debug("uid_this_level now kb %d long: " % known_bits) - self.mfrc630_print_block(uid_this_level, 10) - else: - # Datasheet of mfrc630: - # bit 7 (CollPosValid) not set: - # Otherwise no collision is detected or - # the position of the collision is out of the range of bits CollPos. - self.print_debug("Collision but no valid collpos.") - collision_pos = 0x20 - known_bits - else: - # we got data despite an error, and no collisions, that means we can still continue. - collision_pos = 0x20 - known_bits - self.print_debug("Got data despite error: %x, setting collision_pos to: %x" % (error, collision_pos)) - elif (irq0 & MFRC630_IRQ0_RX_IRQ): - # we got data, and no collisions, that means all is well. - self.print_debug("we got data, and no collisions, that means all is well.") - collision_pos = 0x20 - known_bits - self.print_debug("Got data, no collision, setting to: %x" % collision_pos) - else: - # We have no error, nor received an RX. No response, no card? - self.print_debug("We have no error, nor received an RX. No response, no card?") - return 0 - - self.print_debug("collision_pos: %x" % collision_pos) - # read the UID Cln so far from the buffer. - rx_len = self.mfrc630_fifo_length() - buf = self.mfrc630_read_fifo(rx_len if rx_len < 5 else 5) - - self.print_debug("Fifo %d long" % rx_len) - self.mfrc630_print_block(buf, rx_len) - - self.print_debug("uid_this_level kb %d long: " % known_bits) - self.mfrc630_print_block(uid_this_level, (known_bits + 8 - 1) / 8) - - # move the buffer into the uid at this level, but OR the result such that - # we do not lose the bit we just set if we have a collision. - for rbx in range(0, rx_len): - uid_this_level[int(known_bits / 8) + rbx] = uid_this_level[int(known_bits / 8) + rbx] | buf[rbx] - self.print_debug("uid_this_level after reading buffer (known_bits=%d):" % known_bits) - self.mfrc630_print_block(uid_this_level, 0) - self.print_debug("known_bits: %x + collision_pos: %x = %x" % (known_bits, collision_pos, known_bits + collision_pos)) - known_bits = known_bits + collision_pos - self.print_debug("known_bits: %x" % known_bits) - - if known_bits >= 32: - self.print_debug("exit collision loop: uid_this_level kb %d long: " % known_bits); - self.mfrc630_print_block(uid_this_level, 10) - break; # done with collision loop - # end collission loop - - # check if the BCC matches - bcc_val = uid_this_level[4] # always at position 4, either with CT UID[0-2] or UID[0-3] in front. - bcc_calc = uid_this_level[0] ^ uid_this_level[1] ^ uid_this_level[2] ^ uid_this_level[3] - self.print_debug("BCC calc: %x" % bcc_calc) - if (bcc_val != bcc_calc): - self.print_debug("Something went wrong, BCC does not match.") - return 0 - - # clear interrupts - self.mfrc630_clear_irq0() - self.mfrc630_clear_irq1() - - send_req[0] = cmd - send_req[1] = 0x70 - send_req[2] = uid_this_level[0] - send_req[3] = uid_this_level[1] - send_req[4] = uid_this_level[2] - send_req[5] = uid_this_level[3] - send_req[6] = bcc_calc - message_length = 7 - - # Ok, almost done now, we re-enable the CRC's - self.mfrc630_write_reg(MFRC630_REG_TXCRCPRESET, MFRC630_RECOM_14443A_CRC | MFRC630_CRC_ON) - self.mfrc630_write_reg(MFRC630_REG_RXCRCCON, MFRC630_RECOM_14443A_CRC | MFRC630_CRC_ON) - - # reset the Tx and Rx registers (disable alignment, transmit full bytes) - self.mfrc630_write_reg(MFRC630_REG_TXDATANUM, (known_bits % 8) | MFRC630_TXDATANUM_DATAEN) - rxalign = 0 - self.mfrc630_write_reg(MFRC630_REG_RXBITCTRL, (0 << 7) | (rxalign << 4)) - - # actually send it! - self.mfrc630_cmd_transceive(send_req) - self.print_debug("send_req %d long: " % message_length) - self.mfrc630_print_block(send_req, message_length) - - # Block until we are done... - irq1_value = 0 - while not (irq1_value & (1 << timer_for_timeout)): - irq1_value = self.mfrc630_irq1() - if (irq1_value & MFRC630_IRQ1_GLOBAL_IRQ): # either ERR_IRQ or RX_IRQ - break # stop polling irq1 and quit the timeout loop. - self.mfrc630_cmd_idle() - - # Check the source of exiting the loop. - irq0_value = self.mfrc630_irq0() - self.print_debug("irq0: %x" % irq0_value) - if irq0_value & MFRC630_IRQ0_ERR_IRQ: - # Check what kind of error. - error = self.mfrc630_read_reg(MFRC630_REG_ERROR) - self.print_debug("error: %x" % error) - if error & MFRC630_ERROR_COLLDET: - # a collision was detected with NVB=0x70, should never happen. - self.print_debug("a collision was detected with NVB=0x70, should never happen.") - return 0 - # Read the sak answer from the fifo. - sak_len = self.mfrc630_fifo_length() - self.print_debug("sak_len: %x" % sak_len) - if sak_len != 1: - return 0 - - sak_value = self.mfrc630_read_fifo(sak_len) - - self.print_debug("SAK answer: ") - self.mfrc630_print_block(sak_value, 1) - - if (sak_value[0] & (1 << 2)): - # UID not yet complete, continue with next cascade. - # This also means the 0'th byte of the UID in this level was CT, so we - # have to shift all bytes when moving to uid from uid_this_level. - for UIDn in range(0, 3): - # uid_this_level[UIDn] = uid_this_level[UIDn + 1]; - uid[(cascade_level - 1) * 3 + UIDn] = uid_this_level[UIDn + 1] - else: - # Done according so SAK! - # Add the bytes at this level to the UID. - for UIDn in range(0, 4): - uid[(cascade_level - 1) * 3 + UIDn] = uid_this_level[UIDn]; - - # Finally, return the length of the UID that's now at the uid "pointer". - return cascade_level * 3 + 1; - - self.print_debug("Exit cascade loop nr. %d: " % cascade_level) - self.mfrc630_print_block(uid, 10) - - return 0 # getting a UID failed. - - def mfrc630_MF_auth(self, uid, key_type, block): - # Enable the right interrupts. - - # configure a timeout timer. - timer_for_timeout = 0 # should match the enabled interrupt. - - # According to datasheet Interrupt on idle and timer with MFAUTHENT, but lets - # include ERROR as well. - self.mfrc630_write_reg(MFRC630_REG_IRQ0EN, MFRC630_IRQ0EN_IDLE_IRQEN | MFRC630_IRQ0EN_ERR_IRQEN) - self.mfrc630_write_reg(MFRC630_REG_IRQ1EN, MFRC630_IRQ1EN_TIMER0_IRQEN) # only trigger on timer for irq1 - - # Set timer to 221 kHz clock, start at the end of Tx. - self.mfrc630_timer_set_control(timer_for_timeout, MFRC630_TCONTROL_CLK_211KHZ | MFRC630_TCONTROL_START_TX_END) - # Frame waiting time: FWT = (256 x 16/fc) x 2 FWI - # FWI defaults to four... so that would mean wait for a maximum of ~ 5ms - - self.mfrc630_timer_set_reload(timer_for_timeout, 2000) # 2000 ticks of 5 usec is 10 ms. - self.mfrc630_timer_set_value(timer_for_timeout, 2000) - - irq1_value = 0 - - self.mfrc630_clear_irq0() # clear irq0 - self.mfrc630_clear_irq1() # clear irq1 - - # start the authentication procedure. - self.mfrc630_cmd_auth(key_type, block, uid) - - # block until we are done - while not (irq1_value & (1 << timer_for_timeout)): - irq1_value = self.mfrc630_irq1() - if (irq1_value & MFRC630_IRQ1_GLOBAL_IRQ): - break # stop polling irq1 and quit the timeout loop. - - if (irq1_value & (1 << timer_for_timeout)): - # this indicates a timeout - return 0 # we have no authentication - - # status is always valid, it is set to 0 in case of authentication failure. - status = self.mfrc630_read_reg(MFRC630_REG_STATUS) - return (status & MFRC630_STATUS_CRYPTO1_ON) - - def mfrc630_MF_deauth(self): - self.mfrc630_write_reg(MFRC630_REG_STATUS, 0) - - def format_block(self, block, length): - ret_val = "" - for i in range(0, length): - if (block[i] < 16): - ret_val += ("0%x " % block[i]) - else: - ret_val += ("%x " % block[i]) - return ret_val.upper() diff --git a/pybytes/pyscan/lib/pycoproc.py b/pybytes/pyscan/lib/pycoproc.py deleted file mode 100644 index 0b7772b..0000000 --- a/pybytes/pyscan/lib/pycoproc.py +++ /dev/null @@ -1,294 +0,0 @@ -#!/usr/bin/env python -# -# Copyright (c) 2019, Pycom Limited. -# -# This software is licensed under the GNU GPL version 3 or any -# later version, with permitted additional terms. For more information -# see the Pycom Licence v1.0 document supplied with this file, or -# available at https://www.pycom.io/opensource/licensing -# - -from machine import Pin -from machine import I2C -import time -import pycom - -__version__ = '0.0.2' - -""" PIC MCU wakeup reason types """ -WAKE_REASON_ACCELEROMETER = 1 -WAKE_REASON_PUSH_BUTTON = 2 -WAKE_REASON_TIMER = 4 -WAKE_REASON_INT_PIN = 8 - -class Pycoproc: - """ class for handling the interaction with PIC MCU """ - - I2C_SLAVE_ADDR = const(8) - - CMD_PEEK = const(0x0) - CMD_POKE = const(0x01) - CMD_MAGIC = const(0x02) - CMD_HW_VER = const(0x10) - CMD_FW_VER = const(0x11) - CMD_PROD_ID = const(0x12) - CMD_SETUP_SLEEP = const(0x20) - CMD_GO_SLEEP = const(0x21) - CMD_CALIBRATE = const(0x22) - CMD_BAUD_CHANGE = const(0x30) - CMD_DFU = const(0x31) - - REG_CMD = const(0) - REG_ADDRL = const(1) - REG_ADDRH = const(2) - REG_AND = const(3) - REG_OR = const(4) - REG_XOR = const(5) - - ANSELA_ADDR = const(0x18C) - ANSELB_ADDR = const(0x18D) - ANSELC_ADDR = const(0x18E) - - ADCON0_ADDR = const(0x9D) - ADCON1_ADDR = const(0x9E) - - IOCAP_ADDR = const(0x391) - IOCAN_ADDR = const(0x392) - - INTCON_ADDR = const(0x0B) - OPTION_REG_ADDR = const(0x95) - - _ADCON0_CHS_POSN = const(0x02) - _ADCON0_ADON_MASK = const(0x01) - _ADCON1_ADCS_POSN = const(0x04) - _ADCON0_GO_nDONE_MASK = const(0x02) - - ADRESL_ADDR = const(0x09B) - ADRESH_ADDR = const(0x09C) - - TRISC_ADDR = const(0x08E) - - PORTA_ADDR = const(0x00C) - PORTC_ADDR = const(0x00E) - - WPUA_ADDR = const(0x20C) - - WAKE_REASON_ADDR = const(0x064C) - MEMORY_BANK_ADDR = const(0x0620) - - PCON_ADDR = const(0x096) - STATUS_ADDR = const(0x083) - - EXP_RTC_PERIOD = const(7000) - - def __init__(self, i2c=None, sda='P22', scl='P21'): - if i2c is not None: - self.i2c = i2c - else: - self.i2c = I2C(0, mode=I2C.MASTER, pins=(sda, scl)) - - self.sda = sda - self.scl = scl - self.clk_cal_factor = 1 - self.reg = bytearray(6) - self.wake_int = False - self.wake_int_pin = False - self.wake_int_pin_rising_edge = True - - # Make sure we are inserted into the - # correct board and can talk to the PIC - try: - self.read_fw_version() - except Exception as e: - raise Exception('Board not detected: {}'.format(e)) - - # init the ADC for the battery measurements - self.poke_memory(ANSELC_ADDR, 1 << 2) - self.poke_memory(ADCON0_ADDR, (0x06 << _ADCON0_CHS_POSN) | _ADCON0_ADON_MASK) - self.poke_memory(ADCON1_ADDR, (0x06 << _ADCON1_ADCS_POSN)) - # enable the pull-up on RA3 - self.poke_memory(WPUA_ADDR, (1 << 3)) - # make RC5 an input - self.set_bits_in_memory(TRISC_ADDR, 1 << 5) - # set RC6 and RC7 as outputs and enable power to the sensors and the GPS - self.mask_bits_in_memory(TRISC_ADDR, ~(1 << 6)) - self.mask_bits_in_memory(TRISC_ADDR, ~(1 << 7)) - - if self.read_fw_version() < 6: - raise ValueError('Firmware out of date') - - - def _write(self, data, wait=True): - self.i2c.writeto(I2C_SLAVE_ADDR, data) - if wait: - self._wait() - - def _read(self, size): - return self.i2c.readfrom(I2C_SLAVE_ADDR, size + 1)[1:(size + 1)] - - def _wait(self): - count = 0 - time.sleep_us(10) - while self.i2c.readfrom(I2C_SLAVE_ADDR, 1)[0] != 0xFF: - time.sleep_us(100) - count += 1 - if (count > 500): # timeout after 50ms - raise Exception('Board timeout') - - def _send_cmd(self, cmd): - self._write(bytes([cmd])) - - def read_hw_version(self): - self._send_cmd(CMD_HW_VER) - d = self._read(2) - return (d[1] << 8) + d[0] - - def read_fw_version(self): - self._send_cmd(CMD_FW_VER) - d = self._read(2) - return (d[1] << 8) + d[0] - - def read_product_id(self): - self._send_cmd(CMD_PROD_ID) - d = self._read(2) - return (d[1] << 8) + d[0] - - def peek_memory(self, addr): - self._write(bytes([CMD_PEEK, addr & 0xFF, (addr >> 8) & 0xFF])) - return self._read(1)[0] - - def poke_memory(self, addr, value): - self._write(bytes([CMD_POKE, addr & 0xFF, (addr >> 8) & 0xFF, value & 0xFF])) - - def magic_write_read(self, addr, _and=0xFF, _or=0, _xor=0): - self._write(bytes([CMD_MAGIC, addr & 0xFF, (addr >> 8) & 0xFF, _and & 0xFF, _or & 0xFF, _xor & 0xFF])) - return self._read(1)[0] - - def toggle_bits_in_memory(self, addr, bits): - self.magic_write_read(addr, _xor=bits) - - def mask_bits_in_memory(self, addr, mask): - self.magic_write_read(addr, _and=mask) - - def set_bits_in_memory(self, addr, bits): - self.magic_write_read(addr, _or=bits) - - def get_wake_reason(self): - """ returns the wakeup reason, a value out of constants WAKE_REASON_* """ - return self.peek_memory(WAKE_REASON_ADDR) - - def get_sleep_remaining(self): - """ returns the remaining time from sleep, as an interrupt (wakeup source) might have triggered """ - c3 = self.peek_memory(WAKE_REASON_ADDR + 3) - c2 = self.peek_memory(WAKE_REASON_ADDR + 2) - c1 = self.peek_memory(WAKE_REASON_ADDR + 1) - time_device_s = (c3 << 16) + (c2 << 8) + c1 - # this time is from PIC internal oscilator, so it needs to be adjusted with the calibration value - try: - self.calibrate_rtc() - except Exception: - pass - time_s = int((time_device_s / self.clk_cal_factor) + 0.5) # 0.5 used for round - return time_s - - def setup_sleep(self, time_s): - try: - self.calibrate_rtc() - except Exception: - pass - time_s = int((time_s * self.clk_cal_factor) + 0.5) # round to the nearest integer - if time_s >= 2**(8*3): - time_s = 2**(8*3)-1 - self._write(bytes([CMD_SETUP_SLEEP, time_s & 0xFF, (time_s >> 8) & 0xFF, (time_s >> 16) & 0xFF])) - - def go_to_sleep(self, gps=True): - # enable or disable back-up power to the GPS receiver - if gps: - self.set_bits_in_memory(PORTC_ADDR, 1 << 7) - else: - self.mask_bits_in_memory(PORTC_ADDR, ~(1 << 7)) - # disable the ADC - self.poke_memory(ADCON0_ADDR, 0) - - if self.wake_int: - # Don't touch RA3, RA5 or RC1 so that interrupt wake-up works - self.poke_memory(ANSELA_ADDR, ~((1 << 3) | (1 << 5))) - self.poke_memory(ANSELC_ADDR, ~((1 << 6) | (1 << 7) | (1 << 1))) - else: - # disable power to the accelerometer, and don't touch RA3 so that button wake-up works - self.poke_memory(ANSELA_ADDR, ~(1 << 3)) - self.poke_memory(ANSELC_ADDR, ~(1 << 7)) - - self.poke_memory(ANSELB_ADDR, 0xFF) - - # check if INT pin (PIC RC1), should be used for wakeup - if self.wake_int_pin: - if self.wake_int_pin_rising_edge: - self.set_bits_in_memory(OPTION_REG_ADDR, 1 << 6) # rising edge of INT pin - else: - self.mask_bits_in_memory(OPTION_REG_ADDR, ~(1 << 6)) # falling edge of INT pin - self.mask_bits_in_memory(ANSELC_ADDR, ~(1 << 1)) # disable analog function for RC1 pin - self.set_bits_in_memory(TRISC_ADDR, 1 << 1) # make RC1 input pin - self.mask_bits_in_memory(INTCON_ADDR, ~(1 << 1)) # clear INTF - self.set_bits_in_memory(INTCON_ADDR, 1 << 4) # enable interrupt; set INTE) - - self._write(bytes([CMD_GO_SLEEP]), wait=False) - # kill the run pin - Pin('P3', mode=Pin.OUT, value=0) - - def calibrate_rtc(self): - # the 1.024 factor is because the PIC LF operates at 31 KHz - # WDT has a frequency divider to generate 1 ms - # and then there is a binary prescaler, e.g., 1, 2, 4 ... 512, 1024 ms - # hence the need for the constant - self._write(bytes([CMD_CALIBRATE]), wait=False) - self.i2c.deinit() - Pin('P21', mode=Pin.IN) - pulses = pycom.pulses_get('P21', 100) - self.i2c.init(mode=I2C.MASTER, pins=(self.sda, self.scl)) - idx = 0 - for i in range(len(pulses)): - if pulses[i][1] > EXP_RTC_PERIOD: - idx = i - break - try: - period = pulses[idx][1] - pulses[(idx - 1)][1] - except: - period = 0 - if period > 0: - self.clk_cal_factor = (EXP_RTC_PERIOD / period) * (1000 / 1024) - if self.clk_cal_factor > 1.25 or self.clk_cal_factor < 0.75: - self.clk_cal_factor = 1 - - def button_pressed(self): - button = self.peek_memory(PORTA_ADDR) & (1 << 3) - return not button - - def read_battery_voltage(self): - self.set_bits_in_memory(ADCON0_ADDR, _ADCON0_GO_nDONE_MASK) - time.sleep_us(50) - while self.peek_memory(ADCON0_ADDR) & _ADCON0_GO_nDONE_MASK: - time.sleep_us(100) - adc_val = (self.peek_memory(ADRESH_ADDR) << 2) + (self.peek_memory(ADRESL_ADDR) >> 6) - return (((adc_val * 3.3 * 280) / 1023) / 180) + 0.01 # add 10mV to compensate for the drop in the FET - - def setup_int_wake_up(self, rising, falling): - """ rising is for activity detection, falling for inactivity """ - wake_int = False - if rising: - self.set_bits_in_memory(IOCAP_ADDR, 1 << 5) - wake_int = True - else: - self.mask_bits_in_memory(IOCAP_ADDR, ~(1 << 5)) - - if falling: - self.set_bits_in_memory(IOCAN_ADDR, 1 << 5) - wake_int = True - else: - self.mask_bits_in_memory(IOCAN_ADDR, ~(1 << 5)) - self.wake_int = wake_int - - def setup_int_pin_wake_up(self, rising_edge = True): - """ allows wakeup to be made by the INT pin (PIC -RC1) """ - self.wake_int_pin = True - self.wake_int_pin_rising_edge = rising_edge diff --git a/pybytes/pyscan/lib/pyscan.py b/pybytes/pyscan/lib/pyscan.py deleted file mode 100644 index 40a1830..0000000 --- a/pybytes/pyscan/lib/pyscan.py +++ /dev/null @@ -1,18 +0,0 @@ -#!/usr/bin/env python -# -# Copyright (c) 2019, Pycom Limited. -# -# This software is licensed under the GNU GPL version 3 or any -# later version, with permitted additional terms. For more information -# see the Pycom Licence v1.0 document supplied with this file, or -# available at https://www.pycom.io/opensource/licensing -# - -from pycoproc import Pycoproc - -__version__ = '1.0.0' - -class Pyscan(Pycoproc): - - def __init__(self, i2c=None, sda='P22', scl='P21'): - Pycoproc.__init__(self, i2c, sda, scl) \ No newline at end of file diff --git a/pybytes/pyscan/main.py b/pybytes/pyscan/main.py deleted file mode 100644 index 26a58e9..0000000 --- a/pybytes/pyscan/main.py +++ /dev/null @@ -1,98 +0,0 @@ -#!/usr/bin/env python -# -# Copyright (c) 2019, Pycom Limited. -# -# This software is licensed under the GNU GPL version 3 or any -# later version, with permitted additional terms. For more information -# see the Pycom Licence v1.0 document supplied with this file, or -# available at https://www.pycom.io/opensource/licensing -# - -''' -Simple Pyscan NFC / MiFare Classic Example -Copyright (c) 2019, Pycom Limited. -This example runs the NFC discovery loop in a thread. -If a card is detected it will read the UID and compare it to VALID_CARDS -RGB LED is BLUE while waiting for card, -GREEN if card is valid, RED if card is invalid -''' - -DEBUG = False # change to True to see debug messages - -from pyscan import Pyscan -from MFRC630 import MFRC630 -from LIS2HH12 import LIS2HH12 -from LTR329ALS01 import LTR329ALS01 -import binascii -import time -import pycom -import _thread - -VALID_CARDS = [[0x43, 0x95, 0xDD, 0xF8], - [0x43, 0x95, 0xDD, 0xF9]] - -py = Pyscan() -nfc = MFRC630(py) -lt = LTR329ALS01(py) -li = LIS2HH12(py) - -RGB_BRIGHTNESS = 0x8 - -RGB_RED = (RGB_BRIGHTNESS << 16) -RGB_GREEN = (RGB_BRIGHTNESS << 8) -RGB_BLUE = (RGB_BRIGHTNESS) - -# Make sure heartbeat is disabled before setting RGB LED -pycom.heartbeat(False) - -# Initialise the MFRC630 with some settings -nfc.mfrc630_cmd_init() - -def check_uid(uid, len): - return VALID_CARDS.count(uid[:len]) - -def print_debug(msg): - if DEBUG: - print(msg) - -def send_sensor_data(name, timeout): - while(pybytes): - pybytes.send_virtual_pin_value(True, 2, lt.light()) - pybytes.send_virtual_pin_value(True, 3, li.acceleration()) - time.sleep(timeout) - -def discovery_loop(arg1, arg2): - while(pybytes): - # Send REQA for ISO14443A card type - print_debug('Sending REQA for ISO14443A card type...') - atqa = nfc.mfrc630_iso14443a_WUPA_REQA(nfc.MFRC630_ISO14443_CMD_REQA) - print_debug('Response: {}'.format(atqa)) - if (atqa != 0): - # A card has been detected, read UID - print_debug('A card has been detected, read UID...') - uid = bytearray(10) - uid_len = nfc.mfrc630_iso14443a_select(uid) - print_debug('UID has length: {}'.format(uid_len)) - if (uid_len > 0): - print_debug('Checking if card with UID: [{:s}] is listed in VALID_CARDS...'.format(binascii.hexlify(uid[:uid_len],' ').upper())) - if (check_uid(list(uid), uid_len)) > 0: - print_debug('Card is listed, turn LED green') - pycom.rgbled(RGB_GREEN) - pybytes.send_virtual_pin_value(True, 1, ('Card is listed, Access granted', uid)) - else: - print_debug('Card is not listed, turn LED red') - pycom.rgbled(RGB_RED) - pybytes.send_virtual_pin_value(True, 1, ('Card is not listed, Access denied', uid)) - else: - # No card detected - print_debug('Did not detect any card...') - pycom.rgbled(RGB_BLUE) - pybytes.send_virtual_pin_value(True, 1, ('Did not detect any card', 0)) - nfc.mfrc630_cmd_reset() - time.sleep(5) - nfc.mfrc630_cmd_init() - - -# This is the start of our main execution... start the thread -_thread.start_new_thread(discovery_loop, (nfc, 0)) -_thread.start_new_thread(send_sensor_data, ('Thread 2',10)) diff --git a/pybytes/pysense/lib/LIS2HH12.py b/pybytes/pysense/lib/LIS2HH12.py deleted file mode 100644 index 49a6b5a..0000000 --- a/pybytes/pysense/lib/LIS2HH12.py +++ /dev/null @@ -1,178 +0,0 @@ -#!/usr/bin/env python -# -# Copyright (c) 2019, Pycom Limited. -# -# This software is licensed under the GNU GPL version 3 or any -# later version, with permitted additional terms. For more information -# see the Pycom Licence v1.0 document supplied with this file, or -# available at https://www.pycom.io/opensource/licensing -# - -import math -import time -import struct -from machine import Pin - - -FULL_SCALE_2G = const(0) -FULL_SCALE_4G = const(2) -FULL_SCALE_8G = const(3) - -ODR_POWER_DOWN = const(0) -ODR_10_HZ = const(1) -ODR_50_HZ = const(2) -ODR_100_HZ = const(3) -ODR_200_HZ = const(4) -ODR_400_HZ = const(5) -ODR_800_HZ = const(6) - -ACC_G_DIV = 1000 * 65536 - - -class LIS2HH12: - - ACC_I2CADDR = const(30) - - PRODUCTID_REG = const(0x0F) - CTRL1_REG = const(0x20) - CTRL2_REG = const(0x21) - CTRL3_REG = const(0x22) - CTRL4_REG = const(0x23) - CTRL5_REG = const(0x24) - ACC_X_L_REG = const(0x28) - ACC_X_H_REG = const(0x29) - ACC_Y_L_REG = const(0x2A) - ACC_Y_H_REG = const(0x2B) - ACC_Z_L_REG = const(0x2C) - ACC_Z_H_REG = const(0x2D) - ACT_THS = const(0x1E) - ACT_DUR = const(0x1F) - - SCALES = {FULL_SCALE_2G: 4000, FULL_SCALE_4G: 8000, FULL_SCALE_8G: 16000} - ODRS = [0, 10, 50, 100, 200, 400, 800] - - def __init__(self, pysense = None, sda = 'P22', scl = 'P21'): - if pysense is not None: - self.i2c = pysense.i2c - else: - from machine import I2C - self.i2c = I2C(0, mode=I2C.MASTER, pins=(sda, scl)) - - self.odr = 0 - self.full_scale = 0 - self.x = 0 - self.y = 0 - self.z = 0 - self.int_pin = None - self.act_dur = 0 - self.debounced = False - - whoami = self.i2c.readfrom_mem(ACC_I2CADDR , PRODUCTID_REG, 1) - if (whoami[0] != 0x41): - raise ValueError("LIS2HH12 not found") - - # enable acceleration readings at 50Hz - self.set_odr(ODR_50_HZ) - - # change the full-scale to 4g - self.set_full_scale(FULL_SCALE_4G) - - # set the interrupt pin as active low and open drain - self.set_register(CTRL5_REG, 3, 0, 3) - - # make a first read - self.acceleration() - - def acceleration(self): - x = self.i2c.readfrom_mem(ACC_I2CADDR , ACC_X_L_REG, 2) - self.x = struct.unpack(' self.SCALES[self.full_scale]: - error = "threshold %d exceeds full scale %d" % (thresold, self.SCALES[self.full_scale]) - print(error) - raise ValueError(error) - - if threshold < self.SCALES[self.full_scale] / 128: - error = "threshold %d below resolution %d" % (thresold, self.SCALES[self.full_scale]/128) - print(error) - raise ValueError(error) - - if duration > 255 * 1000 * 8 / self.ODRS[self.odr]: - error = "duration %d exceeds max possible value %d" % (duration, 255 * 1000 * 8 / self.ODRS[self.odr]) - print(error) - raise ValueError(error) - - if duration < 1000 * 8 / self.ODRS[self.odr]: - error = "duration %d below resolution %d" % (duration, 1000 * 8 / self.ODRS[self.odr]) - print(error) - raise ValueError(error) - - _ths = int(127 * threshold / self.SCALES[self.full_scale]) & 0x7F - _dur = int((duration * self.ODRS[self.odr]) / 1000 / 8) - - self.i2c.writeto_mem(ACC_I2CADDR, ACT_THS, _ths) - self.i2c.writeto_mem(ACC_I2CADDR, ACT_DUR, _dur) - - # enable the activity/inactivity interrupt - self.set_register(CTRL3_REG, 1, 5, 1) - - self._user_handler = handler - self.int_pin = Pin('P13', mode=Pin.IN) - self.int_pin.callback(trigger=Pin.IRQ_FALLING | Pin.IRQ_RISING, handler=self._int_handler) - - # return actual used thresold and duration - return (_ths * self.SCALES[self.full_scale] / 128, _dur * 8 * 1000 / self.ODRS[self.odr]) - - def activity(self): - if not self.debounced: - time.sleep_ms(self.act_dur) - self.debounced = True - if self.int_pin(): - return True - return False - - def _int_handler(self, pin_o): - if self._user_handler is not None: - self._user_handler(pin_o) - else: - if pin_o(): - print('Activity interrupt') - else: - print('Inactivity interrupt') diff --git a/pybytes/pysense/lib/LTR329ALS01.py b/pybytes/pysense/lib/LTR329ALS01.py deleted file mode 100644 index 49e3439..0000000 --- a/pybytes/pysense/lib/LTR329ALS01.py +++ /dev/null @@ -1,80 +0,0 @@ -#!/usr/bin/env python -# -# Copyright (c) 2019, Pycom Limited. -# -# This software is licensed under the GNU GPL version 3 or any -# later version, with permitted additional terms. For more information -# see the Pycom Licence v1.0 document supplied with this file, or -# available at https://www.pycom.io/opensource/licensing -# - -import time -from machine import I2C - -class LTR329ALS01: - ALS_I2CADDR = const(0x29) # The device's I2C address - - ALS_CONTR_REG = const(0x80) - ALS_MEAS_RATE_REG = const(0x85) - - ALS_DATA_CH1_LOW = const(0x88) - ALS_DATA_CH1_HIGH = const(0x89) - ALS_DATA_CH0_LOW = const(0x8A) - ALS_DATA_CH0_HIGH = const(0x8B) - - ALS_GAIN_1X = const(0x00) - ALS_GAIN_2X = const(0x01) - ALS_GAIN_4X = const(0x02) - ALS_GAIN_8X = const(0x03) - ALS_GAIN_48X = const(0x06) - ALS_GAIN_96X = const(0x07) - - ALS_INT_50 = const(0x01) - ALS_INT_100 = const(0x00) - ALS_INT_150 = const(0x04) - ALS_INT_200 = const(0x02) - ALS_INT_250 = const(0x05) - ALS_INT_300 = const(0x06) - ALS_INT_350 = const(0x07) - ALS_INT_400 = const(0x03) - - ALS_RATE_50 = const(0x00) - ALS_RATE_100 = const(0x01) - ALS_RATE_200 = const(0x02) - ALS_RATE_500 = const(0x03) - ALS_RATE_1000 = const(0x04) - ALS_RATE_2000 = const(0x05) - - def __init__(self, pysense = None, sda = 'P22', scl = 'P21', gain = ALS_GAIN_1X, integration = ALS_INT_100, rate = ALS_RATE_500): - if pysense is not None: - self.i2c = pysense.i2c - else: - self.i2c = I2C(0, mode=I2C.MASTER, pins=(sda, scl)) - - contr = self._getContr(gain) - self.i2c.writeto_mem(ALS_I2CADDR, ALS_CONTR_REG, bytearray([contr])) - - measrate = self._getMeasRate(integration, rate) - self.i2c.writeto_mem(ALS_I2CADDR, ALS_MEAS_RATE_REG, bytearray([measrate])) - - time.sleep(0.01) - - def _getContr(self, gain): - return ((gain & 0x07) << 2) + 0x01 - - def _getMeasRate(self, integration, rate): - return ((integration & 0x07) << 3) + (rate & 0x07) - - def _getWord(self, high, low): - return ((high & 0xFF) << 8) + (low & 0xFF) - - def light(self): - ch1low = self.i2c.readfrom_mem(ALS_I2CADDR , ALS_DATA_CH1_LOW, 1) - ch1high = self.i2c.readfrom_mem(ALS_I2CADDR , ALS_DATA_CH1_HIGH, 1) - data1 = int(self._getWord(ch1high[0], ch1low[0])) - - ch0low = self.i2c.readfrom_mem(ALS_I2CADDR , ALS_DATA_CH0_LOW, 1) - ch0high = self.i2c.readfrom_mem(ALS_I2CADDR , ALS_DATA_CH0_HIGH, 1) - data0 = int(self._getWord(ch0high[0], ch0low[0])) - - return (data0, data1) diff --git a/pybytes/pysense/lib/pycoproc.py b/pybytes/pysense/lib/pycoproc.py deleted file mode 100644 index 0b7772b..0000000 --- a/pybytes/pysense/lib/pycoproc.py +++ /dev/null @@ -1,294 +0,0 @@ -#!/usr/bin/env python -# -# Copyright (c) 2019, Pycom Limited. -# -# This software is licensed under the GNU GPL version 3 or any -# later version, with permitted additional terms. For more information -# see the Pycom Licence v1.0 document supplied with this file, or -# available at https://www.pycom.io/opensource/licensing -# - -from machine import Pin -from machine import I2C -import time -import pycom - -__version__ = '0.0.2' - -""" PIC MCU wakeup reason types """ -WAKE_REASON_ACCELEROMETER = 1 -WAKE_REASON_PUSH_BUTTON = 2 -WAKE_REASON_TIMER = 4 -WAKE_REASON_INT_PIN = 8 - -class Pycoproc: - """ class for handling the interaction with PIC MCU """ - - I2C_SLAVE_ADDR = const(8) - - CMD_PEEK = const(0x0) - CMD_POKE = const(0x01) - CMD_MAGIC = const(0x02) - CMD_HW_VER = const(0x10) - CMD_FW_VER = const(0x11) - CMD_PROD_ID = const(0x12) - CMD_SETUP_SLEEP = const(0x20) - CMD_GO_SLEEP = const(0x21) - CMD_CALIBRATE = const(0x22) - CMD_BAUD_CHANGE = const(0x30) - CMD_DFU = const(0x31) - - REG_CMD = const(0) - REG_ADDRL = const(1) - REG_ADDRH = const(2) - REG_AND = const(3) - REG_OR = const(4) - REG_XOR = const(5) - - ANSELA_ADDR = const(0x18C) - ANSELB_ADDR = const(0x18D) - ANSELC_ADDR = const(0x18E) - - ADCON0_ADDR = const(0x9D) - ADCON1_ADDR = const(0x9E) - - IOCAP_ADDR = const(0x391) - IOCAN_ADDR = const(0x392) - - INTCON_ADDR = const(0x0B) - OPTION_REG_ADDR = const(0x95) - - _ADCON0_CHS_POSN = const(0x02) - _ADCON0_ADON_MASK = const(0x01) - _ADCON1_ADCS_POSN = const(0x04) - _ADCON0_GO_nDONE_MASK = const(0x02) - - ADRESL_ADDR = const(0x09B) - ADRESH_ADDR = const(0x09C) - - TRISC_ADDR = const(0x08E) - - PORTA_ADDR = const(0x00C) - PORTC_ADDR = const(0x00E) - - WPUA_ADDR = const(0x20C) - - WAKE_REASON_ADDR = const(0x064C) - MEMORY_BANK_ADDR = const(0x0620) - - PCON_ADDR = const(0x096) - STATUS_ADDR = const(0x083) - - EXP_RTC_PERIOD = const(7000) - - def __init__(self, i2c=None, sda='P22', scl='P21'): - if i2c is not None: - self.i2c = i2c - else: - self.i2c = I2C(0, mode=I2C.MASTER, pins=(sda, scl)) - - self.sda = sda - self.scl = scl - self.clk_cal_factor = 1 - self.reg = bytearray(6) - self.wake_int = False - self.wake_int_pin = False - self.wake_int_pin_rising_edge = True - - # Make sure we are inserted into the - # correct board and can talk to the PIC - try: - self.read_fw_version() - except Exception as e: - raise Exception('Board not detected: {}'.format(e)) - - # init the ADC for the battery measurements - self.poke_memory(ANSELC_ADDR, 1 << 2) - self.poke_memory(ADCON0_ADDR, (0x06 << _ADCON0_CHS_POSN) | _ADCON0_ADON_MASK) - self.poke_memory(ADCON1_ADDR, (0x06 << _ADCON1_ADCS_POSN)) - # enable the pull-up on RA3 - self.poke_memory(WPUA_ADDR, (1 << 3)) - # make RC5 an input - self.set_bits_in_memory(TRISC_ADDR, 1 << 5) - # set RC6 and RC7 as outputs and enable power to the sensors and the GPS - self.mask_bits_in_memory(TRISC_ADDR, ~(1 << 6)) - self.mask_bits_in_memory(TRISC_ADDR, ~(1 << 7)) - - if self.read_fw_version() < 6: - raise ValueError('Firmware out of date') - - - def _write(self, data, wait=True): - self.i2c.writeto(I2C_SLAVE_ADDR, data) - if wait: - self._wait() - - def _read(self, size): - return self.i2c.readfrom(I2C_SLAVE_ADDR, size + 1)[1:(size + 1)] - - def _wait(self): - count = 0 - time.sleep_us(10) - while self.i2c.readfrom(I2C_SLAVE_ADDR, 1)[0] != 0xFF: - time.sleep_us(100) - count += 1 - if (count > 500): # timeout after 50ms - raise Exception('Board timeout') - - def _send_cmd(self, cmd): - self._write(bytes([cmd])) - - def read_hw_version(self): - self._send_cmd(CMD_HW_VER) - d = self._read(2) - return (d[1] << 8) + d[0] - - def read_fw_version(self): - self._send_cmd(CMD_FW_VER) - d = self._read(2) - return (d[1] << 8) + d[0] - - def read_product_id(self): - self._send_cmd(CMD_PROD_ID) - d = self._read(2) - return (d[1] << 8) + d[0] - - def peek_memory(self, addr): - self._write(bytes([CMD_PEEK, addr & 0xFF, (addr >> 8) & 0xFF])) - return self._read(1)[0] - - def poke_memory(self, addr, value): - self._write(bytes([CMD_POKE, addr & 0xFF, (addr >> 8) & 0xFF, value & 0xFF])) - - def magic_write_read(self, addr, _and=0xFF, _or=0, _xor=0): - self._write(bytes([CMD_MAGIC, addr & 0xFF, (addr >> 8) & 0xFF, _and & 0xFF, _or & 0xFF, _xor & 0xFF])) - return self._read(1)[0] - - def toggle_bits_in_memory(self, addr, bits): - self.magic_write_read(addr, _xor=bits) - - def mask_bits_in_memory(self, addr, mask): - self.magic_write_read(addr, _and=mask) - - def set_bits_in_memory(self, addr, bits): - self.magic_write_read(addr, _or=bits) - - def get_wake_reason(self): - """ returns the wakeup reason, a value out of constants WAKE_REASON_* """ - return self.peek_memory(WAKE_REASON_ADDR) - - def get_sleep_remaining(self): - """ returns the remaining time from sleep, as an interrupt (wakeup source) might have triggered """ - c3 = self.peek_memory(WAKE_REASON_ADDR + 3) - c2 = self.peek_memory(WAKE_REASON_ADDR + 2) - c1 = self.peek_memory(WAKE_REASON_ADDR + 1) - time_device_s = (c3 << 16) + (c2 << 8) + c1 - # this time is from PIC internal oscilator, so it needs to be adjusted with the calibration value - try: - self.calibrate_rtc() - except Exception: - pass - time_s = int((time_device_s / self.clk_cal_factor) + 0.5) # 0.5 used for round - return time_s - - def setup_sleep(self, time_s): - try: - self.calibrate_rtc() - except Exception: - pass - time_s = int((time_s * self.clk_cal_factor) + 0.5) # round to the nearest integer - if time_s >= 2**(8*3): - time_s = 2**(8*3)-1 - self._write(bytes([CMD_SETUP_SLEEP, time_s & 0xFF, (time_s >> 8) & 0xFF, (time_s >> 16) & 0xFF])) - - def go_to_sleep(self, gps=True): - # enable or disable back-up power to the GPS receiver - if gps: - self.set_bits_in_memory(PORTC_ADDR, 1 << 7) - else: - self.mask_bits_in_memory(PORTC_ADDR, ~(1 << 7)) - # disable the ADC - self.poke_memory(ADCON0_ADDR, 0) - - if self.wake_int: - # Don't touch RA3, RA5 or RC1 so that interrupt wake-up works - self.poke_memory(ANSELA_ADDR, ~((1 << 3) | (1 << 5))) - self.poke_memory(ANSELC_ADDR, ~((1 << 6) | (1 << 7) | (1 << 1))) - else: - # disable power to the accelerometer, and don't touch RA3 so that button wake-up works - self.poke_memory(ANSELA_ADDR, ~(1 << 3)) - self.poke_memory(ANSELC_ADDR, ~(1 << 7)) - - self.poke_memory(ANSELB_ADDR, 0xFF) - - # check if INT pin (PIC RC1), should be used for wakeup - if self.wake_int_pin: - if self.wake_int_pin_rising_edge: - self.set_bits_in_memory(OPTION_REG_ADDR, 1 << 6) # rising edge of INT pin - else: - self.mask_bits_in_memory(OPTION_REG_ADDR, ~(1 << 6)) # falling edge of INT pin - self.mask_bits_in_memory(ANSELC_ADDR, ~(1 << 1)) # disable analog function for RC1 pin - self.set_bits_in_memory(TRISC_ADDR, 1 << 1) # make RC1 input pin - self.mask_bits_in_memory(INTCON_ADDR, ~(1 << 1)) # clear INTF - self.set_bits_in_memory(INTCON_ADDR, 1 << 4) # enable interrupt; set INTE) - - self._write(bytes([CMD_GO_SLEEP]), wait=False) - # kill the run pin - Pin('P3', mode=Pin.OUT, value=0) - - def calibrate_rtc(self): - # the 1.024 factor is because the PIC LF operates at 31 KHz - # WDT has a frequency divider to generate 1 ms - # and then there is a binary prescaler, e.g., 1, 2, 4 ... 512, 1024 ms - # hence the need for the constant - self._write(bytes([CMD_CALIBRATE]), wait=False) - self.i2c.deinit() - Pin('P21', mode=Pin.IN) - pulses = pycom.pulses_get('P21', 100) - self.i2c.init(mode=I2C.MASTER, pins=(self.sda, self.scl)) - idx = 0 - for i in range(len(pulses)): - if pulses[i][1] > EXP_RTC_PERIOD: - idx = i - break - try: - period = pulses[idx][1] - pulses[(idx - 1)][1] - except: - period = 0 - if period > 0: - self.clk_cal_factor = (EXP_RTC_PERIOD / period) * (1000 / 1024) - if self.clk_cal_factor > 1.25 or self.clk_cal_factor < 0.75: - self.clk_cal_factor = 1 - - def button_pressed(self): - button = self.peek_memory(PORTA_ADDR) & (1 << 3) - return not button - - def read_battery_voltage(self): - self.set_bits_in_memory(ADCON0_ADDR, _ADCON0_GO_nDONE_MASK) - time.sleep_us(50) - while self.peek_memory(ADCON0_ADDR) & _ADCON0_GO_nDONE_MASK: - time.sleep_us(100) - adc_val = (self.peek_memory(ADRESH_ADDR) << 2) + (self.peek_memory(ADRESL_ADDR) >> 6) - return (((adc_val * 3.3 * 280) / 1023) / 180) + 0.01 # add 10mV to compensate for the drop in the FET - - def setup_int_wake_up(self, rising, falling): - """ rising is for activity detection, falling for inactivity """ - wake_int = False - if rising: - self.set_bits_in_memory(IOCAP_ADDR, 1 << 5) - wake_int = True - else: - self.mask_bits_in_memory(IOCAP_ADDR, ~(1 << 5)) - - if falling: - self.set_bits_in_memory(IOCAN_ADDR, 1 << 5) - wake_int = True - else: - self.mask_bits_in_memory(IOCAN_ADDR, ~(1 << 5)) - self.wake_int = wake_int - - def setup_int_pin_wake_up(self, rising_edge = True): - """ allows wakeup to be made by the INT pin (PIC -RC1) """ - self.wake_int_pin = True - self.wake_int_pin_rising_edge = rising_edge diff --git a/pybytes/pysense/lib/pysense.py b/pybytes/pysense/lib/pysense.py deleted file mode 100644 index 04d31e7..0000000 --- a/pybytes/pysense/lib/pysense.py +++ /dev/null @@ -1,18 +0,0 @@ -#!/usr/bin/env python -# -# Copyright (c) 2019, Pycom Limited. -# -# This software is licensed under the GNU GPL version 3 or any -# later version, with permitted additional terms. For more information -# see the Pycom Licence v1.0 document supplied with this file, or -# available at https://www.pycom.io/opensource/licensing -# - -from pycoproc import Pycoproc - -__version__ = '1.4.0' - -class Pysense(Pycoproc): - - def __init__(self, i2c=None, sda='P22', scl='P21'): - Pycoproc.__init__(self, i2c, sda, scl) diff --git a/pybytes/pysense/main.py b/pybytes/pysense/main.py deleted file mode 100644 index babb9c3..0000000 --- a/pybytes/pysense/main.py +++ /dev/null @@ -1,36 +0,0 @@ -#!/usr/bin/env python -# -# Copyright (c) 2019, Pycom Limited. -# -# This software is licensed under the GNU GPL version 3 or any -# later version, with permitted additional terms. For more information -# see the Pycom Licence v1.0 document supplied with this file, or -# available at https://www.pycom.io/opensource/licensing -# - -# See https://docs.pycom.io for more information regarding library specifics - -from pysense import Pysense -from LIS2HH12 import LIS2HH12 -from SI7006A20 import SI7006A20 -from LTR329ALS01 import LTR329ALS01 -from MPL3115A2 import MPL3115A2,ALTITUDE,PRESSURE - -py = Pysense() -mp = MPL3115A2(py,mode=ALTITUDE) # Returns height in meters. Mode may also be set to PRESSURE, returning a value in Pascals -si = SI7006A20(py) -lt = LTR329ALS01(py) -li = LIS2HH12(py) - -import _thread -from time import sleep -import uos - -def send_sensor_data(): - while (pybytes): - pybytes.send_virtual_pin_value(True, 1, si.humidity()) - pybytes.send_virtual_pin_value(True, 2, si.temperature()) - pybytes.send_virtual_pin_value(True, 3, lt.light()) - sleep(10) - -_thread.start_new_thread(send_sensor_data, ()) diff --git a/pybytes/pytrack/lib/LIS2HH12.py b/pybytes/pytrack/lib/LIS2HH12.py deleted file mode 100644 index 49a6b5a..0000000 --- a/pybytes/pytrack/lib/LIS2HH12.py +++ /dev/null @@ -1,178 +0,0 @@ -#!/usr/bin/env python -# -# Copyright (c) 2019, Pycom Limited. -# -# This software is licensed under the GNU GPL version 3 or any -# later version, with permitted additional terms. For more information -# see the Pycom Licence v1.0 document supplied with this file, or -# available at https://www.pycom.io/opensource/licensing -# - -import math -import time -import struct -from machine import Pin - - -FULL_SCALE_2G = const(0) -FULL_SCALE_4G = const(2) -FULL_SCALE_8G = const(3) - -ODR_POWER_DOWN = const(0) -ODR_10_HZ = const(1) -ODR_50_HZ = const(2) -ODR_100_HZ = const(3) -ODR_200_HZ = const(4) -ODR_400_HZ = const(5) -ODR_800_HZ = const(6) - -ACC_G_DIV = 1000 * 65536 - - -class LIS2HH12: - - ACC_I2CADDR = const(30) - - PRODUCTID_REG = const(0x0F) - CTRL1_REG = const(0x20) - CTRL2_REG = const(0x21) - CTRL3_REG = const(0x22) - CTRL4_REG = const(0x23) - CTRL5_REG = const(0x24) - ACC_X_L_REG = const(0x28) - ACC_X_H_REG = const(0x29) - ACC_Y_L_REG = const(0x2A) - ACC_Y_H_REG = const(0x2B) - ACC_Z_L_REG = const(0x2C) - ACC_Z_H_REG = const(0x2D) - ACT_THS = const(0x1E) - ACT_DUR = const(0x1F) - - SCALES = {FULL_SCALE_2G: 4000, FULL_SCALE_4G: 8000, FULL_SCALE_8G: 16000} - ODRS = [0, 10, 50, 100, 200, 400, 800] - - def __init__(self, pysense = None, sda = 'P22', scl = 'P21'): - if pysense is not None: - self.i2c = pysense.i2c - else: - from machine import I2C - self.i2c = I2C(0, mode=I2C.MASTER, pins=(sda, scl)) - - self.odr = 0 - self.full_scale = 0 - self.x = 0 - self.y = 0 - self.z = 0 - self.int_pin = None - self.act_dur = 0 - self.debounced = False - - whoami = self.i2c.readfrom_mem(ACC_I2CADDR , PRODUCTID_REG, 1) - if (whoami[0] != 0x41): - raise ValueError("LIS2HH12 not found") - - # enable acceleration readings at 50Hz - self.set_odr(ODR_50_HZ) - - # change the full-scale to 4g - self.set_full_scale(FULL_SCALE_4G) - - # set the interrupt pin as active low and open drain - self.set_register(CTRL5_REG, 3, 0, 3) - - # make a first read - self.acceleration() - - def acceleration(self): - x = self.i2c.readfrom_mem(ACC_I2CADDR , ACC_X_L_REG, 2) - self.x = struct.unpack(' self.SCALES[self.full_scale]: - error = "threshold %d exceeds full scale %d" % (thresold, self.SCALES[self.full_scale]) - print(error) - raise ValueError(error) - - if threshold < self.SCALES[self.full_scale] / 128: - error = "threshold %d below resolution %d" % (thresold, self.SCALES[self.full_scale]/128) - print(error) - raise ValueError(error) - - if duration > 255 * 1000 * 8 / self.ODRS[self.odr]: - error = "duration %d exceeds max possible value %d" % (duration, 255 * 1000 * 8 / self.ODRS[self.odr]) - print(error) - raise ValueError(error) - - if duration < 1000 * 8 / self.ODRS[self.odr]: - error = "duration %d below resolution %d" % (duration, 1000 * 8 / self.ODRS[self.odr]) - print(error) - raise ValueError(error) - - _ths = int(127 * threshold / self.SCALES[self.full_scale]) & 0x7F - _dur = int((duration * self.ODRS[self.odr]) / 1000 / 8) - - self.i2c.writeto_mem(ACC_I2CADDR, ACT_THS, _ths) - self.i2c.writeto_mem(ACC_I2CADDR, ACT_DUR, _dur) - - # enable the activity/inactivity interrupt - self.set_register(CTRL3_REG, 1, 5, 1) - - self._user_handler = handler - self.int_pin = Pin('P13', mode=Pin.IN) - self.int_pin.callback(trigger=Pin.IRQ_FALLING | Pin.IRQ_RISING, handler=self._int_handler) - - # return actual used thresold and duration - return (_ths * self.SCALES[self.full_scale] / 128, _dur * 8 * 1000 / self.ODRS[self.odr]) - - def activity(self): - if not self.debounced: - time.sleep_ms(self.act_dur) - self.debounced = True - if self.int_pin(): - return True - return False - - def _int_handler(self, pin_o): - if self._user_handler is not None: - self._user_handler(pin_o) - else: - if pin_o(): - print('Activity interrupt') - else: - print('Inactivity interrupt') diff --git a/pybytes/pytrack/lib/pytrack.py b/pybytes/pytrack/lib/pytrack.py deleted file mode 100644 index 3e4d39b..0000000 --- a/pybytes/pytrack/lib/pytrack.py +++ /dev/null @@ -1,18 +0,0 @@ -#!/usr/bin/env python -# -# Copyright (c) 2019, Pycom Limited. -# -# This software is licensed under the GNU GPL version 3 or any -# later version, with permitted additional terms. For more information -# see the Pycom Licence v1.0 document supplied with this file, or -# available at https://www.pycom.io/opensource/licensing -# - -from pycoproc import Pycoproc - -__version__ = '1.4.0' - -class Pytrack(Pycoproc): - - def __init__(self, i2c=None, sda='P22', scl='P21'): - Pycoproc.__init__(self, i2c, sda, scl) diff --git a/pymesh/mobile_app/README.md b/pymesh/mobile_app/README.md new file mode 100644 index 0000000..9d6e426 --- /dev/null +++ b/pymesh/mobile_app/README.md @@ -0,0 +1,18 @@ + +# Pymesh mobile application + +For demonstrating **Pymesh** features, a mobile application was created. + +It is available for both Android and iOS platforms. + +## Android application + +It is available as `.apk` packet in this directory. + +# iOS mobile application + +Pymesh mobile application is released currently (v0.) as Beta testing, on Apple TestFlight (to be installed for free from App Store). + +Pymesh app can be added afterwards, using the link: https://testflight.apple.com/join/PIxwbTKp + +Requirements: iOS minimum version v12.1 diff --git a/pymesh/mobile_app/app-release.apk b/pymesh/mobile_app/app-release.apk new file mode 100644 index 0000000..f48718c Binary files /dev/null and b/pymesh/mobile_app/app-release.apk differ diff --git a/pymesh/pymesh_frozen/README.md b/pymesh/pymesh_frozen/README.md new file mode 100644 index 0000000..a9f0fa6 --- /dev/null +++ b/pymesh/pymesh_frozen/README.md @@ -0,0 +1,8 @@ +# Pymesh micropython code + +This project exemplifies the use of Pycom's proprietary LoRa Mesh network - **Pymesh**. +These scripts were created and tested on Lopy4 and Fipy, using the Pymesh binary release. + +Official Pymesh docs: https://docs.pycom.io/pymesh/ + +Forum Pymesh announcements: https://forum.pycom.io/topic/4449/pymesh-updates diff --git a/pymesh/pymesh_frozen/copy_fw.sh b/pymesh/pymesh_frozen/copy_fw.sh new file mode 100755 index 0000000..33d2e6a --- /dev/null +++ b/pymesh/pymesh_frozen/copy_fw.sh @@ -0,0 +1,35 @@ +#!/bin/bash +set -e +#set -x +SOURCE="$(dirname $0)" +if [ -z $1 ]; then + echo "usage: $0 micropython_firmware_directory" + exit 1 +fi +if [ ! -d $1/esp32/frozen/Common ]; then + echo "Need to specify valid micropython firmware directory!" + exit 1 +fi +if [ ! -d $SOURCE ]; then + echo "Can't find source directory $SOURCE" + exit 1 +fi + +# moving main +# if [ -d $1/esp32/frozen/Pybytes ]; then +# cp $SOURCE/main.py $1/esp32/frozen/Pybytes/_main.py +# cp $1/esp32/frozen/Base/_boot.py $1/esp32/frozen/Pybytes/ +# elif [ -d $1/esp32/frozen/Base ]; then +# cp $SOURCE/main.py $1/esp32/frozen/Base/_main.py +# else +# cp $SOURCE/main.py $1/esp32/frozen/_main.py +# fi + +for i in $SOURCE/lib/*.py; do + SRC=$i + FN=$(basename $i) + cp $SRC $1/esp32/frozen/Common/_$FN +done + +cp -r $SOURCE/lib/msgpack $1/esp32/frozen/Common/ +echo "Done copying Pymesh library to $1/esp32/frozen/Common/" \ No newline at end of file diff --git a/lib/pymesh/main.py b/pymesh/pymesh_frozen/lib/ble_rpc.py similarity index 56% rename from lib/pymesh/main.py rename to pymesh/pymesh_frozen/lib/ble_rpc.py index 34a0a61..78cffd2 100644 --- a/lib/pymesh/main.py +++ b/pymesh/pymesh_frozen/lib/ble_rpc.py @@ -1,34 +1,86 @@ -VERSION = "1.0.0" - -# Copyright (c) 2019, Pycom Limited. -# -# This software is licensed under the GNU GPL version 3 or any -# later version, with permitted additional terms. For more information -# see the Pycom Licence v1.0 document supplied with this file, or -# available at https://www.pycom.io/opensource/licensing - -import time -import json -import pycom -import _thread -import sys -import select +''' +Copyright (c) 2020, Pycom Limited. +This software is licensed under the GNU GPL version 3 or any +later version, with permitted additional terms. For more information +see the Pycom Licence v1.0 document supplied with this file, or +available at https://www.pycom.io/opensource/licensing +''' import _thread -import machine +import time from machine import Timer -from machine import Pin - from network import Bluetooth +import sys +import json import msgpack -import ble -from mesh_interface import MeshInterface -from gps import Gps +try: + from ble_services import BleServices +except: + from _ble_services import BleServices -from meshaging import Meshaging -import _thread -from cli import Cli +try: + from pymesh_config import PymeshConfig +except: + from _pymesh_config import PymeshConfig + +try: + from gps import Gps +except: + from _gps import Gps + +class BleRpc: + + def __init__(self, config, mesh): + self.config = config + self.mesh = mesh + self.ble_comm = BleServices(config.get('ble_name_prefix', PymeshConfig.BLE_NAME_PREFIX) + str(config.get('MAC'))) + + self.rx_worker = RXWorker(self.ble_comm) + self.tx_worker = TXWorker(self.ble_comm) + + self.rpc_handler = RPCHandler(self.rx_worker, self.tx_worker, self.mesh, self.ble_comm) + + # setting hooks for triggering when new message was received and ACK + self.mesh.meshaging.on_rcv_message = self.on_rcv_message + self.mesh.meshaging.on_rcv_ack = self.on_rcv_ack + + self.ble_comm.on_disconnect = self.ble_on_disconnect + + + def terminate(self): + ''' kill all, to exit nicely ''' + self.rx_worker.timer_kill() + self.ble_comm.close() + + def on_rcv_message(self, message): + ''' hook triggered when a new message arrived ''' + message_data = { + 'mac' : message.mac, + 'payload' : message.payload, + 'ts' : message.ts, + 'id' : message.id, + } + + msg = msgpack.packb(['notify', 'msg', message_data]) + self.rx_worker.put(msg) + print(message_data['payload']) + print("%d ================= RECEIVED :) :) :) "%time.ticks_ms()) + + + def on_rcv_ack(self, message): + ''' hook triggered when the ACK arrived ''' + message_data = { + 'id' : message.id, + } + + msg = msgpack.packb(['notify', 'msg-ack', message_data]) + self.rx_worker.put(msg) + print("%d ================= ACK RECEIVED :) :) :) "%time.ticks_ms()) + + def ble_on_disconnect(self): + ''' if BLE disconnected, it's better to re-instantiate RPC handler ''' + self.rpc_handler = RPCHandler(self.rx_worker, self.tx_worker, self.mesh, self.ble_comm) class RXWorker: def __init__(self, ble_comm): @@ -151,6 +203,7 @@ def resolve(self, obj): message = msgpack.packb(['call_result', uuid, result]) except Exception as e: + sys.print_exception(e) print('could not send result: {}'.format(result)) return @@ -159,11 +212,8 @@ def resolve(self, obj): #print('message', message) self.rx_worker.put(message) - def demo_fn(self, *args): - return { 'res': 'demo_fn' } - - def demo_echo_fn(self, *args): - return args + # def demo_echo_fn(self, *args): + # return args def mesh_is_connected(self): # True if Node is connected to Mesh; False otherwise @@ -175,9 +225,9 @@ def mesh_ip(self): ip = self.mesh.ip() return ip - def set_gps(self, lng, lat): + def set_gps(self, latitude, longitude): print('settings gps!') - Gps.set_location(lat, lng) + Gps.set_location(latitude, longitude) # with open('/flash/gps', 'w') as fh: # fh.write('{};{}'.format(lng, lat)) @@ -220,7 +270,7 @@ def get_node_info(self, mac_id = ' '): 'n': '', # name, max. 16 chars } } """ - node_info = mesh.get_node_info(mac_id) + node_info = self.mesh.get_node_info(mac_id) return node_info def send_message(self, data): @@ -252,172 +302,57 @@ def send_message_was_sent(self, mac, msg_id): def receive_message(self): """ return { - 'b': 'text', - 'from': 'ble_device_id', - 'ts': 123123123, - 'id': '', + 'b': 'text', + 'from': 'ble_device_id', + 'ts': 123123123, + 'id': '', } """ return self.mesh.get_rcv_message() - def send_image(self, data): - """ sends an image - return True if there is buffer to store it (to be sent)""" - print("Send Image ---------------------->>>>>>>> ", data) - start = 0 - filename = 'dog_2.jpg' - to = 0 - # packsize = 500 - image = list() - - try: - filename = data.get('fn', "image.jpg") - start = int(data['start']) - image = bytes(data['image']) - except: - print('parsing failed') - return False - - print("Image chunk size: %d"%len(image)) - file_handling = "ab" # append, by default - if start == 0: - file_handling = "wb" # write/create new - - with open("/flash/" + filename, file_handling) as file: - print("file open") - file.write(image) - print("file written") - - print("done") - return True - - def stat_start(self, data): - # do some statistics - #data = {'mac':6, 'n':3, 't':30} - res = self.mesh.statistics_start(data) - print("rpc stat_start? ", res) - return res - - def stat_status(self, data): - print("rpc stat_status ", data) - try: - id = int(data) - except: - id = 0 - res = mesh.statistics_get(id) - print("rpc stat_status id:"+ str(id) + ", res: " + str(res)) - return res - -class Watchdog: - def __init__(self, meshaging, mesh): - self.INTERVAL = 10 - self.meshaging = meshaging - self.mesh = mesh - self._timer = Timer.Alarm(self.interval_cb, self.INTERVAL, periodic=True) - - def timer_kill(self): - self._timer.cancel() - - def interval_cb(self, *args, **kwargs): - global rpc_handler, rx_worker, tx_worker, mesh, ble_comm - - #print("watchdog!") - - if rpc_handler.error: - rpc_handler = RPCHandler(rx_worker, tx_worker, mesh, ble_comm) - print("********** Restarted RPC Handler") - - -################################################################################ -# Main code - -pycom.heartbeat(False) -mesh_lock = _thread.allocate_lock() -meshaging = Meshaging(mesh_lock) -mesh = MeshInterface(meshaging, mesh_lock) - -def on_rcv_message(message): - message_data = { - 'mac' : message.mac, - 'payload' : message.payload, - 'ts' : message.ts, - 'id' : message.id, - } - - msg = msgpack.packb(['notify', 'msg', message_data]) - rx_worker.put(msg) - print(message_data['payload']) - print("%d ================= RECEIVED :) :) :) "%time.ticks_ms()) -mesh.meshaging.on_rcv_message = on_rcv_message - -def on_rcv_ack(message): - message_data = { - 'id' : message.id, - } - - msg = msgpack.packb(['notify', 'msg-ack', message_data]) - rx_worker.put(msg) - print("%d ================= ACK RECEIVED :) :) :) "%time.ticks_ms()) -mesh.meshaging.on_rcv_ack = on_rcv_ack - -ble_comm = ble.BleCommunication(mesh.mesh.mesh.MAC) - -def ble_on_disconnect(): - rpc_handler = RPCHandler(rx_worker, tx_worker, mesh, ble_comm) -ble_comm.on_disconnect = ble_on_disconnect - -rx_worker = RXWorker(ble_comm) -tx_worker = TXWorker(ble_comm) - -rpc_handler = RPCHandler(rx_worker, tx_worker, mesh, ble_comm) - -Gps.init_static() - -kill_all = False -deepsleep_timeout = 0 - -watchdog = Watchdog(meshaging, mesh) - -def deepsleep_now(): - """ prepare scripts for graceful exit, deepsleeps if case """ - mesh.timer_kill() - watchdog.timer_kill() - rx_worker.timer_kill() - ble_comm.close() - Gps.terminate() - mesh.statistics.save_all() - print('Cleanup code, all Alarms cb should be stopped') - if deepsleep_timeout > 0: - print('Going to deepsleep for %d seconds'%deepsleep_timeout) - time.sleep(1) - machine.deepsleep(deepsleep_timeout * 1000) - -def deepsleep_init(timeout): - """ initializes an deeppsleep sequence, that will be performed later """ - global deepsleep_timeout, kill_all - deepsleep_timeout = timeout - kill_all = True - return - -mesh.statistics.sleep_function = deepsleep_init -mesh.sleep_function = deepsleep_init - -cli = Cli(mesh, rpc_handler, ble_comm) -cli.sleep = deepsleep_init - -_thread.start_new_thread(cli.process, (1, 2)) - -try: - while True: - if kill_all: - deepsleep_now() - - time.sleep(.1) - pass - -except KeyboardInterrupt: - print('Got Ctrl-C') -except Exception as e: - sys.print_exception(e) -finally: - deepsleep_now() - + # def send_image(self, data): + # """ sends an image + # return True if there is buffer to store it (to be sent)""" + # print("Send Image ---------------------->>>>>>>> ", data) + # start = 0 + # filename = 'dog_2.jpg' + # to = 0 + # # packsize = 500 + # image = list() + + # try: + # filename = data.get('fn', "image.jpg") + # start = int(data['start']) + # image = bytes(data['image']) + # except: + # print('parsing failed') + # return False + + # print("Image chunk size: %d"%len(image)) + # file_handling = "ab" # append, by default + # if start == 0: + # file_handling = "wb" # write/create new + + # with open("/flash/" + filename, file_handling) as file: + # print("file open") + # file.write(image) + # print("file written") + + # print("done") + # return True + + # def stat_start(self, data): + # # do some statistics + # #data = {'mac':6, 'n':3, 't':30} + # res = self.mesh.statistics_start(data) + # print("rpc stat_start? ", res) + # return res + + # def stat_status(self, data): + # print("rpc stat_status ", data) + # try: + # id = int(data) + # except: + # id = 0 + # res = self.mesh.statistics_get(id) + # print("rpc stat_status id:"+ str(id) + ", res: " + str(res)) + # return res diff --git a/lib/pymesh/lib/ble.py b/pymesh/pymesh_frozen/lib/ble_services.py similarity index 83% rename from lib/pymesh/lib/ble.py rename to pymesh/pymesh_frozen/lib/ble_services.py index 831ef60..019bf12 100644 --- a/lib/pymesh/lib/ble.py +++ b/pymesh/pymesh_frozen/lib/ble_services.py @@ -1,4 +1,4 @@ -# Copyright (c) 2019, Pycom Limited. +# Copyright (c) 2020, Pycom Limited. # # This software is licensed under the GNU GPL version 3 or any # later version, with permitted additional terms. For more information @@ -11,10 +11,12 @@ VERSION = "1.0.0" -class BleCommunication: +class BleServices: - def __init__(self, mesh_mac): - self.mesh_mac = mesh_mac + def __init__(self, ble_name): + # self.mesh_mac = mesh_mac + self.ble_name = ble_name + self.on_disconnect = None self._init() def _init(self): @@ -22,9 +24,10 @@ def _init(self): 'connected' : False } - #bluetooth = Bluetooth(modem_sleep=False) - bluetooth = Bluetooth() - bluetooth.set_advertisement(name='PyGo (mac:' + str(self.mesh_mac) + ')', service_uuid=0xec00) + bluetooth = Bluetooth(modem_sleep=False) + adv_name = self.ble_name + bluetooth.set_advertisement(name=adv_name, service_uuid=0xec00) + print("BLE name:", adv_name) bluetooth.callback(trigger=Bluetooth.CLIENT_CONNECTED | Bluetooth.CLIENT_DISCONNECTED, handler=self.conn_cb) bluetooth.advertise(True) diff --git a/pymesh/pymesh_frozen/lib/cli.py b/pymesh/pymesh_frozen/lib/cli.py new file mode 100644 index 0000000..69242aa --- /dev/null +++ b/pymesh/pymesh_frozen/lib/cli.py @@ -0,0 +1,332 @@ + +# Copyright (c) 2020, Pycom Limited. +# +# This software is licensed under the GNU GPL version 3 or any +# later version, with permitted additional terms. For more information +# see the Pycom Licence v1.0 document supplied with this file, or +# available at https://www.pycom.io/opensource/licensing + +import time +import json +import sys +import _thread + +try: + from gps import Gps +except: + from _gps import Gps + +__version__ = '3' +""" +__version__ = '3' +* added dynamic start/stop CLI +* h = help, listing all commands +* added tx_pow and s(send) packets with repetitions +* debug can also read the current level + +__version__ = '2' +* added pause/resume and factory reset + +__version__ = '1' +* initial draft +""" + +class Cli: + """ class for CLI commands """ + + def __init__(self, mesh, pymesh): + self.mesh = mesh + self.pymesh = pymesh + # self.rpc_handler = rpc_handler + # self.ble_comm = ble_comm + + # lamda functions + self.sleep = None + return + + def process(self, arg1, arg2): + last_mesh_pairs = [] + last_mesh_mac_list = [] + last_mesh_node_info = {} + + try: + while True: + time.sleep(.1) + cmd = input('>') + + if cmd == 'ip': + print(self.mesh.ip()) + + elif cmd == 'mac': + # read/write LoRa MAC address + try: + id = int(input('(new LoRa MAC (0-64k) [Enter for read])<')) + except: + print(self.mesh.mesh.mesh.MAC) + continue + id = id & 0xFFFF # just 2B value + # it's actually set in main.py (main thread) + print("LoRa MAC set to", id) + self.sleep(1, id) # force restart + + elif cmd == 'mml': + mesh_mac_list = self.mesh.get_mesh_mac_list() + if len(mesh_mac_list) > 0: + last_mesh_mac_list = mesh_mac_list + print('mesh_mac_list ', json.dumps(last_mesh_mac_list)) + + elif cmd == 'self': + node_info = self.mesh.get_node_info() + print("self info:", node_info) + + elif cmd == 'mni': + for mac in last_mesh_mac_list: + node_info = self.mesh.get_node_info(mac) + time.sleep(.5) + if len(node_info) > 0: + last_mesh_node_info[mac] = node_info + print('last_mesh_node_info', json.dumps(last_mesh_node_info)) + + elif cmd == 'mp': + mesh_pairs = self.mesh.get_mesh_pairs() + if len(mesh_pairs) > 0: + last_mesh_pairs = mesh_pairs + print('last_mesh_pairs', json.dumps(last_mesh_pairs)) + + elif cmd == 's': + interval = 0 + repetitions = 0 + try: + to = int(input('(to)<')) + # typ = input('(type, 0=text, 1=file, Enter for text)<') + # if not typ: + # typ = 0 + # else: + # typ = int(typ) + txt = input('(message)<') + repetitions = int(input('(repetitions)')) + if repetitions > 1: + interval = int(input('(interval in seconds)')) + except: + continue + data = { + 'to': to, + # 'ty': 0, + 'b': txt, + 'id': 12345, + 'ts': int(time.time()), + } + while repetitions > 0: + print(self.mesh.send_message(data)) + repetitions = repetitions - 1 + if repetitions > 0: + print("Remaining TX packets:", repetitions) + time.sleep(interval) + + + elif cmd == 'ws': + to = int(input('(to)<')) + try: + id = int(input('(id, default 12345)<')) + except: + id = 12345 + print(self.mesh.mesage_was_ack(to, id)) + + elif cmd == 'rm': + print(self.mesh.get_rcv_message()) + + elif cmd == 'gps': + try: + lat = float(input('(lat [Enter for read])<')) + lon = float(input('(lon)<')) + except: + print("Gps:", (Gps.lat, Gps.lon)) + continue + + Gps.set_location(lat, lon) + print("Gps:", (Gps.lat, Gps.lon)) + + elif cmd == 'sleep': + try: + timeout = int(input('(time[sec])<')) + except: + continue + if self.sleep: + self.sleep(timeout) + + # elif cmd == "ble": + # # reset BLE connection + # self.ble_comm.restart() + + # elif cmd == "stat": + # # do some statistics + # # data = [] + # # data[0] = {'mac':6, 'n':3, 't':30, 's1':0, 's2':0} + # # data[0] = {'mac':6, 'n':3, 't':30, 's1':5, 's2':10} + # # data[2] = {'mac':6, 'n':30, 't':60, 's1':10, 's2':45} + # # for line in data: + # # print() + # # print("1 = {'mac':6, 'n':30, 't':60, 's1':10, 's2':45}<'") + # # print("2 = {'mac':6, 'n':30, 't':60, 's1':10, 's2':45}<'") + # # print("3 = {'mac':6, 'n':30, 't':60, 's1':10, 's2':45}<'") + # # id = int(input('(choice 1-..)<')) + # data = {'mac':6, 'n':3, 't':60, 's1':3, 's2':8} + # res = self.mesh.statistics_start(data) + # print("ok? ", res) + + # elif cmd == "stat?": + # try: + # id = int(input('(id [Enter for all])<')) + # except: + # id = 0 + # res = self.mesh.statistics_get(id) + # print("ok? ", res) + + elif cmd == "rst": + print("Mesh Reset NVM settings ... ") + self.mesh.mesh.mesh.mesh.deinit(reset=True) + if self.sleep: + self.sleep(1) + + # elif cmd == "pyb": + # # print("Pybytes debug menu, Pybytes connection is ", Pybytes_wrap.is_connected()) + # state = 1 + # timeout = 120 + # try: + # state = int(input('(Debug 0=stop, 1=start [Default start])<')) + # except: + # pass + # try: + # timeout = int(input('(Pybytes timeout [Default 120 sec])<')) + # except: + # pass + # self.mesh.pybytes_config((state == 1), timeout) + + elif cmd == "br": + state = 2 # default display BR + try: + state = int(input('(state 0=Disable, 1=Enable, 2=Display [Default Display])<')) + except: + pass + + if state == 2: + print("Border Router state: ", self.mesh.mesh.mesh.mesh.border_router()) + elif state == 1: + # Enable BR + prio = 0 # default normal priority + try: + prio = int(input('(priority -1=Low, 0=Normal or 1=High [Default Normal])<')) + except: + pass + self.mesh.br_set(True, prio, self.new_br_message_cb) + else: + # disable BR function + self.mesh.br_set(False) + + elif cmd == "brs": + """ send data to BR """ + ip_default = "1:2:3::4" + port = 5555 + try: + payload = input("(message<)") + ip = input("(IP destination, Mesh-external [Default: 1:2:3::4])<") + if len(ip) == 0: + ip = ip_default + port = int(input("(port destination [Default: 5555])<")) + except: + pass + data = { + 'ip': ip, + 'port': port, + 'b': payload + } + print("Send BR message:", data) + self.mesh.send_message(data) + + elif cmd == "buf": + print("Buffer info:",self.mesh.mesh.mesh.mesh.cli("bufferinfo")) + + elif cmd == "ot": + cli = input('(openthread cli)<') + print(self.mesh.mesh.mesh.mesh.cli(cli)) + + elif cmd == "debug": + ret = input('(debug level[0-5])<') + try: + level = int(ret) + self.pymesh.debug_level(level) + except: + print(self.pymesh.debug_level()) + + elif cmd == "config": + print(self.mesh.config) + + elif cmd == "pause": + self.pymesh.pause() + + elif cmd == "resume": + self.pymesh.resume() + + elif cmd == "tx_pow": + print("LoRa stats:", self.pymesh.mesh.mesh.mesh.lora.stats()) + tx_str = input('(tx_pow[2-20])<') + try: + tx_pow = int(tx_str) + self.pymesh.pause() + print("Change TX power to", tx_pow) + time.sleep(1) + self.pymesh.resume(tx_pow) + except: + print("Invalid value") + + elif cmd == "stop": + self.pymesh.cli = None + _thread.exit() + + elif cmd == "h": + print("List of available commands") + print("br - enable/disable or display the current Border Router functionality") + print("brs - send packet for Mesh-external, to BR, if any") + print("buf - display buffer info") + print("config - print config file contents") + print("debug - set debug level") + print("gps - get/set location coordinates") + print("h - help, list of commands") + print("ip - display current IPv6 unicast addresses") + print("mac - set or display the current LoRa MAC address") + print("mml - display the Mesh Mac List (MAC of all nodes inside this Mesh), also inquires Leader") + print("mp - display the Mesh Pairs (Pairs of all nodes connections), also inquires Leader") + print("ot - sends command to openthread internal CLI") + print("pause - suspend Pymesh") + print("resume - resume Pymesh") + print("rm - verifies if any message was received") + print("rst - reset NOW, including NVM Pymesh IPv6") + print("s - send message") + print("self - display all info about current node") + print("sleep - deep-sleep") + # print("stat - start statistics") + # print("stat? - display statistics") + print("stop - close this CLI") + print("tx_pow - set LoRa TX power in dBm (2-20)") + print("ws - verifies if message sent was acknowledged") + + except KeyboardInterrupt: + print('CLI Ctrl-C') + except Exception as e: + sys.print_exception(e) + finally: + print('CLI stopped') + if self.pymesh.cli is not None: + self.sleep(0) + + def new_br_message_cb(self, rcv_ip, rcv_port, rcv_data, dest_ip, dest_port): + ''' callback triggered when a new packet arrived for the current Border Router, + having destination an IP which is external from Mesh ''' + print('CLI BR default handler') + print('Incoming %d bytes from %s (port %d), to external IPv6 %s (port %d)' % + (len(rcv_data), rcv_ip, rcv_port, dest_ip, dest_port)) + print(rcv_data) + + # user code to be inserted, to send packet to the designated Mesh-external interface + # ... + return diff --git a/pymesh/pymesh_frozen/lib/gps.py b/pymesh/pymesh_frozen/lib/gps.py new file mode 100644 index 0000000..cd30b9c --- /dev/null +++ b/pymesh/pymesh_frozen/lib/gps.py @@ -0,0 +1,88 @@ + +# Copyright (c) 2020, Pycom Limited. +# +# This software is licensed under the GNU GPL version 3 or any +# later version, with permitted additional terms. For more information +# see the Pycom Licence v1.0 document supplied with this file, or +# available at https://www.pycom.io/opensource/licensing + +try: + from pymesh_debug import print_debug +except: + from _pymesh_debug import print_debug + +import time +# from pytrack import Pytrack +# from L76GNSS import L76GNSS +from machine import Timer + +__version__ = '1' +""" +* initial version +""" + +class Gps: + # Pycom office GPS coordinates + lat = 51.45 + lon = 5.45313 + + l76 = None + _timer = None + #is_set = False + + @staticmethod + def set_location(latitude, longitude): + dlat = str(type(latitude)) + dlon = str(type(longitude)) + if dlat == dlon == "": + Gps.lat = latitude + Gps.lon = longitude + is_set = True + else: + print_debug(3, "Error parsing ", latitude, longitude) + + @staticmethod + def get_location(): + return (Gps.lat, Gps.lon) + + # @staticmethod + # def init_static(): + # is_pytrack = True + # try: + # py = Pytrack() + # Gps.l76 = L76GNSS(py, timeout=30) + # #l76.coordinates() + # Gps._timer = Timer.Alarm(Gps.gps_periodic, 30, periodic=True) + # print_debug(3, "Pytrack detected") + # except: + # is_pytrack = False + # print_debug(3, "Pytrack NOT detected") + # #TODO: how to check if GPS is conencted + # return is_pytrack + + # @staticmethod + # def gps_periodic(alarm): + # t0 = time.ticks_ms() + # coord = Gps.l76.coordinates() + # if coord[0] != None: + # Gps.lat, Gps.lon = coord + # print_debug(3, "New coord ", coord) + # dt = time.ticks_ms() - t0 + # print_debug(3, " =====>>>> gps_periodic ", dt) + + # @staticmethod + # def terminate(): + # if Gps._timer is not None: + # Gps._timer.cancel() + # pass + +""" +from pytrack import Pytrack +from L76GNSS import L76GNSS +py = Pytrack() +l76 = L76GNSS(py, timeout=30) +t0 = time.ticks_ms() +l76.coordinates() +y = time.ticks_ms() - t0 +y +""" diff --git a/lib/pymesh/lib/loramesh.py b/pymesh/pymesh_frozen/lib/loramesh.py similarity index 86% rename from lib/pymesh/lib/loramesh.py rename to pymesh/pymesh_frozen/lib/loramesh.py index b499daf..5f21c09 100644 --- a/lib/pymesh/lib/loramesh.py +++ b/pymesh/pymesh_frozen/lib/loramesh.py @@ -1,5 +1,5 @@ -# Copyright (c) 2019, Pycom Limited. +# Copyright (c) 2020, Pycom Limited. # # This software is licensed under the GNU GPL version 3 or any # later version, with permitted additional terms. For more information @@ -14,10 +14,22 @@ import pycom from struct import * -from gps import Gps -__version__ = '6' +try: + from pymesh_debug import print_debug +except: + from _pymesh_debug import print_debug + +try: + from gps import Gps +except: + from _gps import Gps + +__version__ = '7' """ +__version__ = '7' +* added pause/resume + __version__ = '6' * added IPv6 unicast addresses as fdde:ad00:beef:0:: """ @@ -35,10 +47,10 @@ class Loramesh: # rgb LED color for each state: disabled, detached, child, router, leader and single leader #RGBLED = [0x0A0000, 0x0A0000, 0x0A0A0A, 0x000A00, 0x00000A, 0x0A000A] RGBLED = [0x0A0000, 0x0A0000, 0x0A0A0A, 0x000A00, 0x0A000A, 0x000A0A] - + # TTN conf mode #RGBLED = [0x200505, 0x200505, 0x202020, 0x052005, 0x200020, 0x001818] - + # for outside/bright sun #RGBLED = [0xFF0000, 0xFF0000, 0x808080, 0x00FF00, 0x0000FF, 0xFF00FF] @@ -55,18 +67,18 @@ class Loramesh: # Leader has an unicast IPv6: fdde:ad00:beef:0:0:ff:fe00:fc00 LEADER_DEFAULT_RLOC = 'fc00' - def __init__(self, lora): + def __init__(self, config): """ Constructor """ - self.lora = lora - self.mesh = lora.Mesh() #start Mesh + self.config_lora = config.get('LoRa') + self._lora_init() # get Lora MAC address #self.MAC = str(ubinascii.hexlify(lora.mac()))[2:-1] - self.MAC = int(str(ubinascii.hexlify(lora.mac()))[2:-1], 16) + self.MAC = int(str(ubinascii.hexlify(self.lora.mac()))[2:-1], 16) #last 2 letters from MAC, as integer self.mac_short = self.MAC & 0xFFFF #int(self.MAC[-4:], 16) - print("LoRa MAC: %s, short: %s"%(hex(self.MAC), self.mac_short)) + print_debug(5, "LoRa MAC: %s, short: %s"%(hex(self.MAC), self.mac_short)) self.rloc16 = 0 self.rloc = '' @@ -98,14 +110,33 @@ def __init__(self, lora): self.connections_ts = -65535 # very old # set a new unicast address - self.unique_ip_prefix = "fdde:ad00:beef:0::" - command = "ipaddr add " + self.ip_mac_unique(self.mac_short) - self.mesh.cli(command) + self._add_ipv6_unicast() + + def _lora_init(self, tx_dBm = 14): + self.lora = LoRa(mode=LoRa.LORA, + region = self.config_lora.get("region"), + frequency = self.config_lora.get("freq"), + bandwidth = self.config_lora.get("bandwidth"), + sf = self.config_lora.get("sf"), + tx_power = tx_dBm) + self.mesh = self.lora.Mesh() #start Mesh + + def pause(self): + self.mesh.deinit() + + def resume(self, tx_dBm = 14): + self._lora_init(tx_dBm) + self._add_ipv6_unicast() def ip_mac_unique(self, mac): ip = self.unique_ip_prefix + hex(mac & 0xFFFF)[2:] return ip + def _add_ipv6_unicast(self): + self.unique_ip_prefix = "fdde:ad00:beef:0::" + command = "ipaddr add " + self.ip_mac_unique(self.mac_short) + self.mesh.cli(command) + def update_internals(self): self._state_update() self._rloc16_update() @@ -218,18 +249,20 @@ def ipaddr(self): """ returns all unicast IPv6 addr """ return self.mesh.ipaddr() - def ping(self, ip): - """ Returns ping return time, to an IP """ - res = self.cli('ping ' + str(ip)) - # '8 bytes from fdde:ad00:beef:0:0:ff:fe00:e000: icmp_seq=2 hlim=64 time=236ms\r\n' - # 'Error 6: Parse\r\n' - # no answer - ret_time = -1 - try: - ret_time = int(res.split('time=')[1].split('ms')[0]) - except Exception: - pass - return ret_time + # def ping(self, ip): + # """ Returns ping return time, to an IP """ + # res = self.cli('ping ' + str(ip)) + # """ + # '8 bytes from fdde:ad00:beef:0:0:ff:fe00:e000: icmp_seq=2 hlim=64 time=236ms\r\n' + # 'Error 6: Parse\r\n' + # no answer + # """ + # ret_time = -1 + # try: + # ret_time = int(res.split('time=')[1].split('ms')[0]) + # except Exception: + # pass + # return ret_time def blink(self, num = 3, period = .5, color = None): """ LED blink """ @@ -244,7 +277,7 @@ def blink(self, num = 3, period = .5, color = None): def neighbors_update(self): """ update neigh_dict from cli:'neighbor table' """ - """ >>> print(lora.cli("neighbor table")) + """ >>> print_debug(3, lora.cli("neighbor table")) | Role | RLOC16 | Age | Avg RSSI | Last RSSI |R|S|D|N| Extended MAC | +------+--------+-----+----------+-----------+-+-+-+-+------------------+ | C | 0x2801 | 219 | 0 | 0 |1|1|1|1| 0000000000000005 | @@ -252,7 +285,7 @@ def neighbors_update(self): """ x = self.mesh.neighbors() - print("Neighbors Table: %s"%x) + print_debug(3,"Neighbors Table: %s"%x) if x is None: # bad read, just keep previous neigbors @@ -280,12 +313,12 @@ def neighbors_update(self): mac = nei_rec.mac neighbor = NeighborData((mac, age, rloc16, role, rssi,)) self.router_data.add_neighbor(neighbor) - #print("new Neighbor: %s"%(neighbor.to_string())) + #print_debug(3, "new Neighbor: %s"%(neighbor.to_string())) #except: # pass # add own info in dict #self.neigh_dict[self.MAC] = (0, self.rloc16, self.state, 0) - print("Neighbors: %s"%(self.router_data.to_string())) + print_debug(3, "Neighbors: %s"%(self.router_data.to_string())) return def leader_add_own_neigh(self): @@ -309,9 +342,9 @@ def routers_neigh_update(self, data_pack): def leader_dict_cleanup(self): """ cleanup the leader_dict for old entries """ - #print("Leader Data before cleanup: %s"%self.leader_data.to_string()) + #print_debug(3, "Leader Data before cleanup: %s"%self.leader_data.to_string()) self.leader_data.cleanup() - print("Leader Data : %s"%self.leader_data.to_string()) + print_debug(3, "Leader Data : %s"%self.leader_data.to_string()) def routers_rloc_list(self, age_min, resolve_mac = None): """ return list of all routers IPv6 RLOC16 @@ -319,8 +352,8 @@ def routers_rloc_list(self, age_min, resolve_mac = None): """ mac_ip = None data = self.mesh.routers() - print("Routers Table: ", data) - '''>>> print(lora.cli('router table')) + print_debug(3, "Routers Table: "+ str(data)) + '''>>> print_debug(3, lora.cli('router table')) | ID | RLOC16 | Next Hop | Path Cost | LQ In | LQ Out | Age | Extended MAC | +----+--------+----------+-----------+-------+--------+-----+------------------+ | 12 | 0x3000 | 63 | 0 | 0 | 0 | 0 | 0000000000000002 |''' @@ -346,7 +379,7 @@ def routers_rloc_list(self, age_min, resolve_mac = None): if resolve_mac == line.mac: mac_ip = rloc16 break - + # look for this router in Leader Data # if doesn't exist, add it to routers_list with max ts # if it exists, just add it with its ts @@ -358,13 +391,13 @@ def routers_rloc_list(self, age_min, resolve_mac = None): routers_list.append((last_ts, ipv6)) if resolve_mac is not None: - print("Mac found in Router %s"%str(mac_ip)) + print_debug(3, "Mac found in Router %s"%str(mac_ip)) return mac_ip # sort the list in the ascending values of timestamp routers_list.sort() - print("Routers list %s"%str(routers_list)) + print_debug(3, "Routers list %s"%str(routers_list)) return routers_list def leader_data_pack(self): @@ -375,7 +408,7 @@ def leader_data_pack(self): def leader_data_unpack(self, data): self.leader_data = LeaderData(data) - print("Leader Data : %s"%self.leader_data.to_string()) + print_debug(3, "Leader Data : %s"%self.leader_data.to_string()) return self.leader_data.ok def neighbor_resolve_mac(self, mac): @@ -384,27 +417,27 @@ def neighbor_resolve_mac(self, mac): def resolve_mac_from_leader_data(self, mac): mac_ip = self.leader_data.resolve_mac(mac) - print("Mac %x found as IP %s"%(mac, str(mac_ip))) + print_debug(3, "Mac %x found as IP %s"%(mac, str(mac_ip))) return mac_ip def macs_get(self): """ returns the set of the macs, hopefully it was received from Leader """ - #print("Macs: %s"%(str(self.macs))) + #print_debug(3, "Macs: %s"%(str(self.macs))) return (self.macs, self.macs_ts) def macs_set(self, data): MACS_FMT = '!H' field_size = calcsize(MACS_FMT) - #print("Macs pack: %s"%(str(data))) + #print_debug(3, "Macs pack: %s"%(str(data))) n, = unpack(MACS_FMT, data) - #print("Macs pack(%d): %s"%(n, str(data))) + #print_debug(3, "Macs pack(%d): %s"%(n, str(data))) index = field_size self.macs = set() for _ in range(n): mac, = unpack(MACS_FMT, data[index:]) self.macs.add(mac) - #print("Macs %d, %d: %s"%(index, mac, str(self.macs))) + #print_debug(3, "Macs %d, %d: %s"%(index, mac, str(self.macs))) index = index + field_size self.macs_ts = time.time() @@ -442,7 +475,7 @@ def node_info_get(self, mac): data['a'] = node.age elif role is self.STATE_ROUTER: data['a'] = time.time() - node.ts - data['l'] = {'lng':node.coord[1], 'lat':node.coord[0]} + data['l'] = {'lat':node.coord[0], 'lng':node.coord[1]} data['nn'] = node.neigh_num() nei_macs = node.get_macs_set() data['nei'] = list() @@ -457,12 +490,12 @@ def node_info_set(self, data): if role is self.STATE_ROUTER: router = RouterData(data[1:]) self.leader_data.add_router(router) - print("Added as router %s"%router.to_string()) + print_debug(3, "Added as router %s"%router.to_string()) elif role is self.STATE_CHILD: node = NeighborData(data[1:]) router = RouterData(node) self.leader_data.add_router(router) - print("Added as Router-Neigh %s"%router.to_string()) + print_debug(3, "Added as Router-Neigh %s"%router.to_string()) pass class NeighborData: @@ -481,20 +514,20 @@ def __init__(self, data = None): return datatype = str(type(data)) - #print('NeighborData __init__ %s'%str(data)) + #print_debug(3, 'NeighborData __init__ %s'%str(data)) if datatype == "": self._init_tuple(data) elif datatype == "": self._init_bytes(data) - #print('NeighborData done __init__') + #print_debug(3, 'NeighborData done __init__') def _init_tuple(self, data): - #print('_init_tuple %s'%str(data)) + #print_debug(3, '_init_tuple %s'%str(data)) (self.mac, self.age, self.rloc16, self.role, self.rssi) = data return def _init_bytes(self, data): - #print('NeighborData._init_bytes %s'%str(data)) + #print_debug(3, 'NeighborData._init_bytes %s'%str(data)) self.mac, self.rloc16, self.role, self.rssi, self.age = unpack(self.PACKING_FMT, data[:self.pack_fmt_size()]) return @@ -524,7 +557,7 @@ def __init__(self, data = None): self.ts = 0 self.dict = {} self.pack_index_last = 0 - self.coord = (0.0, 0.0) + self.coord = Gps.get_location() if data is None: return @@ -538,11 +571,11 @@ def __init__(self, data = None): def _init_bytes(self, data_pack): - #print('RouterData._init_bytes %s'%str(data_pack)) + #print_debug(3, 'RouterData._init_bytes %s'%str(data_pack)) index = calcsize(self.PACK_HEADER_FMT) (self.mac, self.rloc16, lat, lon, neigh_num) = \ unpack(self.PACK_HEADER_FMT, data_pack[: index]) - + self.coord = (lat, lon) self.role = Loramesh.STATE_ROUTER # forcer role as Router @@ -575,7 +608,7 @@ def _init_neighbordata(self, data): def add_neighbor(self, neighbor): self.dict[neighbor.mac] = neighbor - #print("add_neighbor type: %s"%str(type(neighbor))) + #print_debug(3, "add_neighbor type: %s"%str(type(neighbor))) return def neigh_num(self): @@ -596,7 +629,7 @@ def to_string(self): x = 'Router MAC 0x%X, rloc16 0x%x, coord %s, neigh_num %d, ts %d\n'\ %(self.mac, self.rloc16, str(self.coord), len(self.dict), self.ts) for mac, nei in self.dict.items(): - #print("type: %s, %s"%(str(type(nei)),str(nei))) + #print_debug(3, "type: %s, %s"%(str(type(nei)),str(nei))) x = x + nei.to_string() + '\n' return x @@ -614,8 +647,8 @@ def as_dict(self): dict['ip'] = self.rloc16 dict['role'] = self.role dict['age'] = time.time() - self.ts - dict['loc'] = self.coord[0] - dict['ble'] = self.coord[1] + dict['loc'] = {"lat":self.coord[0], "lng":self.coord[1]} + dict['ble'] = "ble" return dict def get_all_pairs(self): @@ -658,7 +691,7 @@ def __init__(self, data = None): def _init_bytes(self, data_pack): - #print('LeaderData._init_bytes %s'%str(data_pack)) + #print_debug(3, 'LeaderData._init_bytes %s'%str(data_pack)) index = calcsize(self.PACK_HEADER_FMT) (self.mac, self.rloc16, routers_num) = unpack(self.PACK_HEADER_FMT, data_pack[: index]) self.ts = time.time() @@ -679,7 +712,7 @@ def add_router(self, router_data): def cleanup(self): for mac, router in self.dict.items(): if time.time() - router.ts > 300: - print("Deleted old Router %d"%mac) + print_debug(3, "Deleted old Router %d"%mac) del self.dict[mac] return @@ -712,7 +745,7 @@ def node_info_mac(self, mac): def node_info_mac_pack(self, mac): node, role = self.node_info_mac(mac) if node is None: - print("Node is None %d"%mac) + print_debug(3, "Node is None %d"%mac) return bytes() # pack type: RouterData or Child (basically NeighborData) data = pack('!B', role) @@ -746,7 +779,7 @@ def get_mesh_connections(self): def get_connections_pack(self): connections = self.get_mesh_connections() - print("Connections ", connections) + print_debug(3, "Connections "+ str(connections)) data = pack('!H', len(connections)) for record in connections: (mac1, mac2, rssi) = record @@ -765,7 +798,7 @@ def get_macs_pack(self): data = pack('!H', len(macs)) for mac in macs: data = data + pack('!H', mac) - #print("Macs pack:%s"%(str(data))) + #print_debug(3, "Macs pack:%s"%(str(data))) return data def get_mac_ts(self, mac): @@ -775,4 +808,3 @@ def get_mac_ts(self, mac): # if this mac is not a router, just return ts as the oldest return 0 return router.ts - diff --git a/pymesh/pymesh_frozen/lib/mesh_interface.py b/pymesh/pymesh_frozen/lib/mesh_interface.py new file mode 100644 index 0000000..0287b5e --- /dev/null +++ b/pymesh/pymesh_frozen/lib/mesh_interface.py @@ -0,0 +1,292 @@ + +# Copyright (c) 2020, Pycom Limited. +# +# This software is licensed under the GNU GPL version 3 or any +# later version, with permitted additional terms. For more information +# see the Pycom Licence v1.0 document supplied with this file, or +# available at https://www.pycom.io/opensource/licensing + +import time +from machine import Timer +import _thread + +try: + from mesh_internal import MeshInternal +except: + from _mesh_internal import MeshInternal + +try: + from meshaging import Meshaging +except: + from _meshaging import Meshaging + +try: + from pymesh_debug import * +except: + from _pymesh_debug import * + +__version__ = '6' +""" +__version__ = '6' +* added loRa_mac field to get_node_info + +__version__ = '5' +* added pause/resume + +__version__ = '3' +* added file send/receive debug + +__version__ = '2' +* add sending messages + +__version__ = '1' +* initial version, lock and get_mesh_members(), get_mesh_pairs +""" + +class MeshInterface: + """ Class for Mesh interface, + all modules that uses Mesh should call only this class methods """ + + INTERVAL = const(10) + + def __init__(self, config, message_cb): + self.lock = _thread.allocate_lock() + self.meshaging = Meshaging(self.lock) + self.config = config + self.mesh = MeshInternal(self.meshaging, config, message_cb) + self.sleep_function = None + self.single_leader_ts = 0 + + self.end_device_m = False + self.is_paused = False + self._start_timer() + # self.statistics = Statistics(self.meshaging) + + pass + + def _start_timer(self): + self._timer = Timer.Alarm(self.periodic_cb, self.INTERVAL, periodic=True) + + # just run this ASAP + self.periodic_cb(None) + + def periodic_cb(self, alarm): + # wait lock forever + if self.lock.acquire(): + print_debug(2, "============ MESH THREAD >>>>>>>>>>> ") + t0 = time.ticks_ms() + + self.mesh.process() + if self.mesh.is_connected(): + # self.statistics.process() + self.mesh.process_messages() + + # if Single Leader for 3 mins should reset + # if self.mesh.mesh.state == self.mesh.mesh.STATE_LEADER and self.mesh.mesh.mesh.single(): + # if self.single_leader_ts == 0: + # # first time Single Leader, record time + # self.single_leader_ts = time.time() + # print_debug(3, "Single Leader", self.mesh.mesh.state, self.mesh.mesh.mesh.single(), + # time.time() - self.single_leader_ts) + + # if time.time() - self.single_leader_ts > 180: + # print_debug(3, "Single Leader, just reset") + # if self.sleep_function: + # self.sleep_function(1) + # else: + # # print_debug(3, "Not Single Leader", self.mesh.mesh.state, self.mesh.mesh.mesh.single()) + # self.single_leader_ts = 0 + + self.lock.release() + + print_debug(2, ">>>>>>>>>>> DONE MESH THREAD ============ %d\n"%(time.ticks_ms() - t0)) + + pass + + def pause(self): + # with self.lock: + self._timer.cancel() + self.mesh.pause() + + def resume(self, tx_dBm = 14): + self.mesh.resume(tx_dBm) + self._start_timer() + + def get_mesh_mac_list(self): + mac_list = list() + if self.lock.acquire(): + # mac_list = list(self.mesh.get_all_macs_set()) + # mac_list.sort() + mac_list = {0:list(self.mesh.get_all_macs_set())} + self.lock.release() + print_debug(3, "get_mesh_mac_list:" + str(mac_list)) + return mac_list + + def get_mesh_pairs(self): + mesh_pairs = [] + if self.lock.acquire(): + mesh_pairs = self.mesh.get_mesh_pairs() + self.lock.release() + #print_debug(3, "get_mesh_pairs: %s"%str(mesh_pairs)) + return mesh_pairs + + def set_gps(self, lng, lat): + with open('/flash/gps', 'w') as fh: + fh.write('%d;%d'.format(lng, lat)) + + def is_connected(self): + is_connected = None + if self.lock.acquire(): + is_connected = self.mesh.is_connected() + self.lock.release() + return is_connected + + def ip(self): + ip = None + if self.lock.acquire(): + ip = self.mesh.mesh.mesh.ipaddr() + self.lock.release() + return ip + + def get_node_info(self, mac_id = ""): + data = {} + try: + mac = int(mac_id) + except: + mac = self.mesh.MAC + print_debug(3, "get_node_info own mac") + if self.lock.acquire(): + data = self.mesh.node_info(mac) + data['loRa_mac'] = mac + self.lock.release() + return data + + def send_message(self, data): + ## WARNING: is locking required for just adding + ret = False + + # check if message is for BR + if len(data.get('ip','')) > 0: + with self.lock: + self.mesh.br_send(data) + return + # check input parameters + try: + mac = int(data['to']) + msg_type = data.get('ty', 0) # text type, by default + payload = data['b'] + id = int(data['id']) + ts = int(data['ts']) + except: + print_debug(1, 'send_message: wrong input params') + return False + + if self.lock.acquire(): + print_debug(3, "Send message to %d, typ %d, load %s"%(mac, msg_type, payload)) + ret = self.meshaging.send_message(mac, msg_type, payload, id, ts) + # send messages ASAP + self.mesh.process_messages() + self.lock.release() + + return ret + + def mesage_was_ack(self, mac, id): + ret = False + if self.lock.acquire(): + ret = self.meshaging.mesage_was_ack(mac, id) + self.lock.release() + #print_debug(3, "mesage_was_ack (%X, %d): %d"%(mac, id, ret)) + return ret + + def get_rcv_message(self): + """ returns a message that was received, {} if none is received """ + message = None + if self.lock.acquire(): + message = self.meshaging.get_rcv_message() + self.lock.release() + if message is not None: + (mac, id, ts, payload) = message + return {'from':mac, + 'b':payload, + 'id':id, + 'ts':ts} + return {} + + # def statistics_start(self, data): + # """ starts to do statistics based on message send/ack """ + # # data = {'mac':6, 'n':3, 't':30, 's1':10, 's2':30} + # try: + # # validate input params + # mac = int(data['mac']) + # num_mess = int(data['n']) + # timeout = int(data['t']) + # except: + # print_debug(3, "statistics_start failed") + # print_debug(3, data) + # return 0 + # if mac == self.mesh.MAC: + # data['mac'] = 2 + # res = self.statistics.add_stat_mess(data) + # return res + + # def statistics_get(self, id): + # res = self.statistics.status(id) + # print_debug(3, res) + # return res + + def br_set(self, enable, prio = 0, br_mess_cb = None): + with self.lock: + self.mesh.border_router(enable, prio, br_mess_cb) + + def ot_cli(self, command): + """ Executes commands in Openthread CLI, + see https://github.com/openthread/openthread/tree/master/src/cli """ + return self.mesh.mesh.mesh.cli(command) + + def end_device(self, state = None): + if state is None: + # read status of end_device + state = self.ot_cli('routerrole') + return state == 'Disabled' + self.end_device_m = False + state_str = 'enable' + if state == True: + self.end_device_m = True + state_str = 'disable' + ret = self.ot_cli('routerrole '+ state_str) + return ret == '' + + def leader_priority(self, weight = None): + if weight is None: + # read status of end_device + ret = self.ot_cli('leaderweight') + try: + weight = int(ret) + except: + weight = -1 + return weight + try: + x = int(weight) + except: + return False + # weight should be uint8, positive and <256 + if weight > 0xFF: + weight = 0xFF + elif weight < 0: + weight = 0 + ret = self.ot_cli('leaderweight '+ str(weight)) + return ret == '' + + # def parent(self): + # """ Returns the Parent MAC for the current Child node + # Returns 0 if node is not Child """ + + # if self.mesh.mesh.mesh.state() != self.mesh.mesh.STATE_CHILD: + # print_debug(3, "Not Child, no Parent") + # return 0 + # # try: + # parent_mac = int(self.mesh.mesh.mesh.cli('parent').split('\r\n')[0].split('Ext Addr: ')[1], 16) + # # except: + # # parent_mac = 0 + # print_debug(3, 'Parent mac is: %s'%parent_mac) + # return parent_mac diff --git a/lib/pymesh/lib/mesh_internal.py b/pymesh/pymesh_frozen/lib/mesh_internal.py similarity index 54% rename from lib/pymesh/lib/mesh_internal.py rename to pymesh/pymesh_frozen/lib/mesh_internal.py index e2625ef..4457239 100644 --- a/lib/pymesh/lib/mesh_internal.py +++ b/pymesh/pymesh_frozen/lib/mesh_internal.py @@ -1,12 +1,11 @@ -# Copyright (c) 2019, Pycom Limited. +# Copyright (c) 2020, Pycom Limited. # # This software is licensed under the GNU GPL version 3 or any # later version, with permitted additional terms. For more information # see the Pycom Licence v1.0 document supplied with this file, or # available at https://www.pycom.io/opensource/licensing -from network import LoRa import socket import time import utime @@ -15,11 +14,26 @@ from machine import Timer from struct import * -from loramesh import Loramesh -from meshaging import Meshaging, Message +try: + from loramesh import Loramesh +except: + from _loramesh import Loramesh -__version__ = '6' +try: + from meshaging import * +except: + from _meshaging import * + +try: + from pymesh_debug import print_debug +except: + from _pymesh_debug import print_debug + +__version__ = '7' """ +__version__ = '7' +* added pause/resume + __version__ = '6' * refactorized file send/receive in state machines @@ -37,6 +51,16 @@ class MeshInternal: """ Class for internal protocol inside Mesh network """ +################################################################################ + # Border router constants + + BR_NET_ADDRESS = '2001:cafe:cafe:cafe::/64' + + EXTERNAL_NET = '1:2:3:4::' + BR_HEADER_FMT = '!BHHHHHHHHH' + BR_MAGIC_BYTE = const(0xBB) + PACK_BR = const(0x90) + ################################################################################ # port number opened by all nodes for communicating neighbors @@ -83,8 +107,8 @@ class MeshInternal: # constants for file sending #FILE_SEND_PACKSIZE = const(750) - PACK_FILE_SEND = const(0x20) - PACK_FILE_SEND_ACK = const(0x21) + # PACK_FILE_SEND = const(0x20) + # PACK_FILE_SEND_ACK = const(0x21) ################################################################################ @@ -93,16 +117,10 @@ class MeshInternal: ################################################################################ - def __init__(self, meshaging, lora=None): + def __init__(self, meshaging, config, message_cb): """ Constructor """ - if lora is None: - # lora = LoRa(mode=LoRa.LORA, region=LoRa.EU868, - # bandwidth=LoRa.BW_125KHZ, sf=7) - lora = LoRa(mode=LoRa.LORA, region=LoRa.EU868, frequency = 863000000, bandwidth=LoRa.BW_500KHZ, sf=7) - - self.lora = lora # enable Thread interface - self.mesh = Loramesh(self.lora) + self.mesh = Loramesh(config) self.MAC = self.mesh.MAC self.sock = None @@ -116,13 +134,26 @@ def __init__(self, meshaging, lora=None): self.file_packsize = 0 self.file_size = 0 self.send_f = None - pass + self.br_handler = None + self.EXTERNAL_IP = self.EXTERNAL_NET + hex(self.MAC & 0xFFFF)[2:] + self.ext_mesh_ts = -30 + self.message_cb = message_cb + self.br_message_cb = None + pass + + def pause(self): + self.rx_cb_registered = False + self.sock = None + self.mesh.pause() + + def resume(self, tx_dBm = 14): + self.mesh.resume(tx_dBm) def create_socket(self): """ create UDP socket """ self.sock = socket.socket(socket.AF_LORA, socket.SOCK_RAW) self.sock.bind(self.PORT_MESH_INTERNAL) - print("Socket created on port %d" % self.PORT_MESH_INTERNAL) + print_debug(5, "Socket created on port %d" % self.PORT_MESH_INTERNAL) def process_messages(self): """ consuming message queue """ @@ -136,7 +167,7 @@ def process_messages(self): # elif mess.state == Message.MESS_STATE_SENT: # # try to resend # if time.time() - mess.last_tx_ts > 15: - # print("Re-transmit %x %s" % (mac, mess.ip)) + # print_debug(3, "Re-transmit %x %s" % (mac, mess.ip)) # mess.last_tx_ts = time.time() # self.send_message(mess) pass @@ -145,12 +176,12 @@ def send_message(self, message, answer = None): """ actual sending of a message on socket """ payload = message.pack(self.MAC, answer) pack_type = self.PACK_MESSAGE - if message.type == message.TYPE_IMAGE: - pack_type = self.PACK_FILE_SEND + # if message.type == message.TYPE_IMAGE: + # pack_type = self.PACK_FILE_SEND if payload: - print("Send message ", payload) + print_debug(4, "Send message " + str(payload)) self.send_pack(pack_type, payload, message.ip) - pass + pass def is_connected(self): # if detached erase all leader_data @@ -164,14 +195,14 @@ def ip(self): return self.mesh.ip() # def _process_router(self): - # print("Process Router") + # print_debug(3, "Process Router") # # just update internal neighbor table # self.mesh.neighbors_update() # # add itself and neighbors in the leader data # self.mesh.leader_add_own_neigh() def _process_leader(self): - print("Process Leader") + print_debug(3, "Process Leader") """ if state == self.mesh.STATE_LEADER_SINGLE: @@ -192,20 +223,52 @@ def _process_leader(self): for router_pair in router_list[:router_num]: (age, router) = router_pair self.send_pack(self.PACK_LEADER_ASK_NEIGH, '', router) - print("Leader inquire Router %s" % router) + print_debug(3, "Leader inquire Router %s" % router) idx = idx + 1 if idx < router_num: time.sleep(.5) + def br_send(self, data): + """ if BR is available in whole Mesh, send some data """ + ret = False + # first, make sure this node is not BR (BR data is sent directly) + if len(self.mesh.mesh.border_router()) > 0: + print_debug(3, "Node is BR, so shouldn't send data to another BR") + return False + + # check if we have a BR network prefix in ipaddr + for ip in self.mesh.ipaddr(): + if ip.startswith(self.BR_NET_ADDRESS[0:-4]): + print_debug(3, "found BR address: %s"%ip) + if time.time() - self.ext_mesh_ts >= 0: + ret = True + try: + ip = data['ip'] + port = int(data['port']) + payload = data['b'] + except: + print_debug(3, "Error parsing packet for Mesh-external") + ret = False + if ret: + self.send_pack(self.PACK_BR, payload, ip, port) + # self.send_pack(self.PACK_BR, self.debug_data(False), self.EXTERNAL_IP) + self.ext_mesh_ts = time.time() + else: + print_debug(3, "BR sending too fast") + ret = False + if not ret: + print_debug(3, "no BR (mesh-external IPv6) found") + return ret + def process(self): self.mesh.update_internals() self.mesh.led_state() - print("%d: MAC %s, State %s, Single %s" % (time.time(), - hex(self.MAC), self.mesh.state_string(), str(self.mesh.mesh.single()))) - print(self.mesh.ipaddr()) + print_debug(3, "%d: MAC %s(%d), State %s, Single %s" % (time.time(), + hex(self.MAC), self.MAC, self.mesh.state_string(), str(self.mesh.mesh.single()))) + print_debug(3, self.mesh.ipaddr()) leader = self.mesh.mesh.leader() if leader is not None: - print("Leader: mac %s, rloc %s, net: %s" % + print_debug(3,"Leader: mac %s, rloc %s, net: %s" % (hex(leader.mac), hex(leader.rloc16), hex(leader.part_id))) if not self.mesh.is_connected(): return # nothing to do @@ -233,6 +296,53 @@ def process(self): # self._process_leader() return + def debug_data(self, br = True): + """ Creating a debug string """ + if br: + # BR can send more data + data = "%d: MAC %s(%d), State %s, Single %s" % (time.time(), + hex(self.MAC), self.MAC, self.mesh.state_string(), str(self.mesh.mesh.single())) + data = data + "\n" + str(self.mesh.ipaddr()) + data = data + "\n" + str(self.mesh.mesh.routers()) + data = data + "\n" + str(self.mesh.mesh.neighbors()) + else: + # normal node sends data over Mesh to BR, so less/compressed data + data = "%d: M=%d, %s," % (time.time(), self.MAC, self.mesh.state_string()) + data = data +" nei:" + str(self.mesh.mesh.neighbors()) + return data + + def border_router(self, enable, prio = 0, br_mess_cb = None): + """ Disables/Enables the Border Router functionality, with priority and callback """ + net_list = self.mesh.mesh.border_router() + print_debug(3, "State:" + str(enable) + "BR: "+ str(net_list)) + + if not enable: + # disable all BR network registrations (possible multiple) + self.br_handler = None + for net in net_list: + self.mesh.mesh.border_router_del(net.net) + print_debug(3, "Done remove BR") + else: + self.br_handler = br_mess_cb + # check if BR already added + try: + # print_debug(3, net[0].net) + # print_debug(3, self.BR_NET_ADDRESS) + # if net[0].net != self.BR_NET_ADDRESS: + if not net_list[0].net.startswith(self.BR_NET_ADDRESS[0:-3]): + # enable BR + self.mesh.mesh.border_router(self.BR_NET_ADDRESS, prio) + print_debug(3, "Done add BR") + except: + # enable BR + self.mesh.mesh.border_router(self.BR_NET_ADDRESS, prio) + print_debug(3, "Force add BR") + + # print again the BR, to confirm + net_list = self.mesh.mesh.border_router() + print_debug(3, "BR: " + str(net_list)) + pass + def _check_to_send(self, pack_type, ip): send_it = True try: @@ -240,7 +350,7 @@ def _check_to_send(self, pack_type, ip): key = (100 * pack_type) + int(ip[-4:], 16) except: # just send it - #print("just send it, ? ", ip) + #print_debug(3, "just send it, ? ", ip) send_it = False if not send_it: return True @@ -253,24 +363,24 @@ def _check_to_send(self, pack_type, ip): else: self.send_table[key] = now except: - #print("%s not in send_table"%str(key)) + #print_debug(3, "%s not in send_table"%str(key)) send_it = True if send_it: # mark packet as sent now self.send_table[key] = now - #print("Packet sent now") + #print_debug(3, "Packet sent now") return send_it # packet already send def send_pack(self, pack_type, data, ip, port=PORT_MESH_INTERNAL): if self.sock is None: return False - print("Send pack: 0x%X to IP %s" % (pack_type, ip)) + print_debug(3, "Send pack: 0x%X to IP %s" % (pack_type, ip)) # check not to send same (packet, destination) too often # if not self._check_to_send(pack_type, ip): - # print("NO send") + # print_debug(3, "NO send") # return False sent_ok = True @@ -280,7 +390,7 @@ def send_pack(self, pack_type, data, ip, port=PORT_MESH_INTERNAL): self.sock.sendto(header + data, (ip, port)) #self.mesh.blink(2, .1) except Exception as ex: - print("Socket.sendto exception: {}".format(ex)) + print_debug(3, "Socket.sendto exception: {}".format(ex)) sent_ok = False return sent_ok @@ -292,8 +402,8 @@ def get_type(self, data): len2 = len(data) if len1 != len2: - print("PACK_HEADER lenght not ok %d %d" % (len1, len2)) - print(data) + print_debug(3, "PACK_HEADER length not ok %d %d" % (len1, len2)) + print_debug(3, str(data)) return return (pack_type, data) @@ -346,133 +456,154 @@ def receive_all_data(self, arg): break # out of while, no packet rcv_ip = rcv_addr[0] rcv_port = rcv_addr[1] - print('Incoming %d bytes from %s (port %d):' % + print_debug(4, 'Incoming %d bytes from %s (port %d):' % (len(rcv_data), rcv_ip, rcv_port)) - # print(rcv_data) + # print_debug(3, rcv_data) + print_debug(5, str(self.mesh.lora.stats())) + + # check if Node is BR + if self.br_handler: + #check if data is for the external of the Pymesh (for Pybytes) + if rcv_data[0] == self.BR_MAGIC_BYTE and len(rcv_data) >= calcsize(self.BR_HEADER_FMT): + br_header = unpack(self.BR_HEADER_FMT, rcv_data) + print_debug(3, "BR pack, IP dest: %x:%x:%x:%x:%x:%x:%x:%x (port %d)"%( + br_header[1],br_header[2],br_header[3],br_header[4], + br_header[5],br_header[6],br_header[7],br_header[8], br_header[9])) + rcv_data = rcv_data[calcsize(self.BR_HEADER_FMT):] + + dest_ip = "%x:%x:%x:%x:%x:%x:%x:%x"%( + br_header[1],br_header[2],br_header[3],br_header[4], + br_header[5],br_header[6],br_header[7],br_header[8]) + + dest_port = br_header[9] + + print_debug(3, rcv_data) + (type, rcv_data) = self.get_type(rcv_data) + print_debug(3, rcv_data) + + self.br_handler(rcv_ip, rcv_port, rcv_data, dest_ip, dest_port) + return # done, no more parsing as this pack was for BR # check packet type (type, rcv_data) = self.get_type(rcv_data) # LEADER if type == self.PACK_ROUTER_NEIGHBORS: - print("PACK_ROUTER_NEIGHBORS received") + print_debug(3, "PACK_ROUTER_NEIGHBORS received") self.mesh.routers_neigh_update(rcv_data) # no answer # elif type == self.PACK_ROUTER_ASK_LEADER_DATA: - # print("PACK_ROUTER_ASK_LEADER_DATA received") + # print_debug(3, "PACK_ROUTER_ASK_LEADER_DATA received") # # send answer with Leader data # pack = self.mesh.leader_data_pack() # self.send_pack(self.PACK_LEADER_DATA, pack, rcv_ip) # ROUTER elif type == self.PACK_LEADER_ASK_NEIGH: - print("PACK_LEADER_ASK_NEIGH received") + print_debug(3, "PACK_LEADER_ASK_NEIGH received") payload = self.mesh.neighbors_pack() #time.sleep(.2) self.send_pack(self.PACK_ROUTER_NEIGHBORS, payload, rcv_ip) # elif type == self.PACK_LEADER_DATA: - # print("PACK_LEADER_DATA received") + # print_debug(3, "PACK_LEADER_DATA received") # if self.mesh.leader_data_unpack(rcv_data): # self.interrogate_leader_ts = time.time() # ALL NODES elif type == self.PACK_MESSAGE: - print("PACK_MESSAGE received") + print_debug(3, "PACK_MESSAGE received") # add new pack received message = Message(rcv_data) - print(message.payload) + # print_debug(3, message.payload) message.ip = rcv_ip self.messages.add_rcv_message(message) + # send back ACK self.send_pack(self.PACK_MESSAGE_ACK, message.pack_ack(self.MAC), rcv_ip) + # forward message to user-application layer + if self.message_cb: + self.message_cb(rcv_ip, rcv_port, message.payload) + elif type == self.PACK_MESSAGE_ACK: - print("PACK_MESSAGE_ACK received") + print_debug(3, "PACK_MESSAGE_ACK received") # mark message as received self.messages.rcv_ack(rcv_data) elif type == self.PACK_ROUTER_ASK_MACS: - print("PACK_ROUTER_ASK_MACS received") + print_debug(3, "PACK_ROUTER_ASK_MACS received") payload = self.mesh.leader_data.get_macs_pack() self.send_pack(self.PACK_LEADER_MACS, payload, rcv_ip) elif type == self.PACK_LEADER_MACS: - print("PACK_LEADER_MACS received") + print_debug(3, "PACK_LEADER_MACS received") self.mesh.macs_set(rcv_data) elif type == self.PACK_ROUTER_ASK_CONNECTIONS: - print("PACK_ROUTER_ASK_CONNECTIONS received") + print_debug(3, "PACK_ROUTER_ASK_CONNECTIONS received") payload = self.mesh.leader_data.get_connections_pack() self.send_pack(self.PACK_LEADER_CONNECTIONS, payload, rcv_ip) elif type == self.PACK_LEADER_CONNECTIONS: - print("PACK_LEADER_CONNECTIONS received") + print_debug(3, "PACK_LEADER_CONNECTIONS received") self.mesh.connections_set(rcv_data) elif type == self.PACK_ROUTER_ASK_MAC_DETAILS: - print("PACK_ROUTER_ASK_MAC_DETAILS received") + print_debug(3, "PACK_ROUTER_ASK_MAC_DETAILS received") (mac_req, ) = unpack('!H', rcv_data) - print(mac_req) + print_debug(3, str(mac_req)) payload = self.mesh.leader_data.node_info_mac_pack(mac_req) if len(payload) > 0: self.send_pack(self.PACK_LEADER_MAC_DETAILS, payload, rcv_ip) else: - print("No info found about MAC %d"%mac_req) + print_debug(3, "No info found about MAC %d"%mac_req) elif type == self.PACK_LEADER_MAC_DETAILS: - print("PACK_LEADER_MAC_DETAILS received") + print_debug(3, "PACK_LEADER_MAC_DETAILS received") self.mesh.node_info_set(rcv_data) - elif type == self.PACK_FILE_SEND: - print("PACK_FILE_SEND received") - payload = pack("!Q", self.MAC) - self.send_pack(self.PACK_FILE_SEND_ACK, payload, rcv_ip) - # rcv data contains '!QHH' as header - chunk = len(rcv_data) -12 - self.file_size += chunk - print("size: %d, chunk %d" % (self.file_size, chunk)) - file_handler = "ab" # append, by default - if chunk > self.file_packsize: - # started receiving a new file - print("started receiving a new image") - file_handler = "wb" # write/create new file - self.file_packsize = chunk - elif chunk < self.file_packsize: - print("DONE receiving the image") - # done receiving the file - self.file_packsize = 0 - self.file_size = 0 - self.messages.file_transfer_done(rcv_data[:12]) - # else: - # #middle of the file, just write data - # self.file.write(rcv_data) - with open('/flash/dog_rcv.jpg', file_handler) as file: - file.write(rcv_data[12:]) - print("writing the image") - - - elif type == self.PACK_FILE_SEND_ACK: - mac_rcv = unpack("!Q", rcv_data) - print("PACK_FILE_SEND_ACK received from MAC %d"%mac_rcv) - mac_rcv = 6 - message = self.messages.dict.get(mac_rcv, None) - if message: - print("message found") - self.send_message(message, rcv_data) - else: - print("message NOT found ", mac_rcv, self.messages.dict) + # elif type == self.PACK_FILE_SEND: + # print_debug(3, "PACK_FILE_SEND received") + # payload = pack("!Q", self.MAC) + # self.send_pack(self.PACK_FILE_SEND_ACK, payload, rcv_ip) + # # rcv data contains '!QHH' as header + # chunk = len(rcv_data) -12 + # self.file_size += chunk + # print_debug(3, "size: %d, chunk %d" % (self.file_size, chunk)) + # file_handler = "ab" # append, by default + # if chunk > self.file_packsize: + # # started receiving a new file + # print_debug(3, "started receiving a new image") + # file_handler = "wb" # write/create new file + # self.file_packsize = chunk + # elif chunk < self.file_packsize: + # print_debug(3, "DONE receiving the image") + # # done receiving the file + # self.file_packsize = 0 + # self.file_size = 0 + # self.messages.file_transfer_done(rcv_data[:12]) + # # else: + # # #middle of the file, just write data + # # self.file.write(rcv_data) + # with open('/flash/dog_rcv.jpg', file_handler) as file: + # file.write(rcv_data[12:]) + # print_debug(3, "writing the image") + + + # elif type == self.PACK_FILE_SEND_ACK: + # mac_rcv = unpack("!Q", rcv_data) + # print_debug(3, "PACK_FILE_SEND_ACK received from MAC %d"%mac_rcv) + # mac_rcv = 6 + # message = self.messages.dict.get(mac_rcv, None) + # if message: + # print_debug(3, "message found") + # self.send_message(message, rcv_data) + # else: + # print_debug(3, "message NOT found ", mac_rcv, self.messages.dict) else: - print("Unknown packet, type: 0x%X" % (type)) - print(rcv_data) + print_debug(3, "Unknown packet, type: 0x%X" % (type)) + print_debug(3, str(rcv_data)) - # blink some LEDs - #self.mesh.blink(3, .1) pass - - # def send_file(self, ip, packsize, filename): - # self.send_f = Send_File(packsize, filename, ip) - # data, _ = self.send_f.process(None) - # self.send_pack(self.PACK_FILE_SEND, data, ip) - diff --git a/lib/pymesh/lib/meshaging.py b/pymesh/pymesh_frozen/lib/meshaging.py similarity index 86% rename from lib/pymesh/lib/meshaging.py rename to pymesh/pymesh_frozen/lib/meshaging.py index 241eb8c..7eef06d 100644 --- a/lib/pymesh/lib/meshaging.py +++ b/pymesh/pymesh_frozen/lib/meshaging.py @@ -1,5 +1,5 @@ -# Copyright (c) 2019, Pycom Limited. +# Copyright (c) 2020, Pycom Limited. # # This software is licensed under the GNU GPL version 3 or any # later version, with permitted additional terms. For more information @@ -9,12 +9,16 @@ import time from struct import * +try: + from pymesh_debug import print_debug +except: + from _pymesh_debug import print_debug + __version__ = '1' """ * initial version """ - class Meshaging: """ class that manages sending/receiving messages inside Mesh network """ @@ -34,10 +38,10 @@ def send_message(self, mac, msg_type, payload, id, ts): """ send a new message """ already = self.dict.get(mac, None) if already: - print('old message deleted for %X' % mac) + print_debug(3, 'old message deleted for %X' % mac) message = Message((mac, msg_type, payload, id, ts)) self.dict[mac] = message - print("Added new message for %X: %s" % (mac, str(payload))) + print_debug(3, "Added new message for %X: %s" % (mac, str(payload))) return True @@ -49,9 +53,9 @@ def add_rcv_message(self, message): if message.payload == b'dog':#🐕': message.payload = 'Picture started receiving' - print('Rcv mess about dog, so we start receiving picture') - else: - print('payload is not dog') + print_debug(3, 'Rcv mess about dog, so we start receiving picture') + # else: + # print_debug(3, 'payload is not dog') if self.on_rcv_message: self.on_rcv_message(message) @@ -73,7 +77,7 @@ def rcv_ack(self, data): # check if message was about picture sending, to start actual file sending mess = self.dict[message.mac] if mess.payload == 'dog': - print('ACK from dog message, start picture sending') + print_debug(3, 'ACK from dog message, start picture sending') del self.dict[message.mac] self.send_message(message.mac, message.TYPE_IMAGE, 'dog.jpg', message.id, time.time()) @@ -81,7 +85,7 @@ def rcv_ack(self, data): mess = Message((message.mac, message.TYPE_TEXT, 'Receiving the picture', message.id+1, time.time())) self.on_rcv_message(mess) else: - print(message.mac, self.dict) + print_debug(3, str(message.mac) + str(self.dict)) pass def mesage_was_ack(self, mac, id): @@ -94,7 +98,7 @@ def mesage_was_ack(self, mac, id): done = True except: pass - print("ACK? mac %x, id %d => %d" % (mac, id, done)) + print_debug(3, "ACK? mac %x, id %d => %d" % (mac, id, done)) return done def get_rcv_message(self): @@ -109,7 +113,7 @@ def get_rcv_message(self): def file_transfer_done(self, rcv_data): message = Message(rcv_data) message.payload = 'Picture was received' - print('Picture done receiving from %d', message.mac) + print_debug(3, 'Picture done receiving from %d', message.mac) message.id = message.id + 1 if self.on_rcv_message: self.on_rcv_message(message) @@ -162,7 +166,7 @@ def _init_tuple(self, data): return def _init_bytes(self, data): - #print('NeighborData._init_bytes %s'%str(data)) + #print_debug(3, 'NeighborData._init_bytes %s'%str(data)) self.mac, self.id, n = unpack(self.PACK_MESSAGE, data) self.payload = data[calcsize(self.PACK_MESSAGE):] #self.payload = unpack('!' + str(n) + 's', data[calcsize(self.PACK_MESSAGE):]) @@ -209,7 +213,7 @@ def __init__(self, filename): try: self.file = open(filename, "rb") except: - print("File %s can't be opened !!!!"%filename) + print_debug(3, "File %s can't be opened !!!!"%filename) self.state = DONE return self.size = 0 @@ -231,16 +235,16 @@ def process(self, last_response): # got ACK, send next chunk self.chunk = self.file.readinto(self.buffer) self.size = self.size + self.chunk - print("%d Bytes sent, time: %4d sec" % (self.size, time.time() - self.start)) + print_debug(3, "%d Bytes sent, time: %4d sec" % (self.size, time.time() - self.start)) if self.chunk == 0: self._end_transfer() self.retries = 0 else: - print("No answer, so retry?") + print_debug(3, "No answer, so retry?") if time.time() - self.last_ts < 5: #if we just sent the retry, don't resend anything, still wait for answer - print("No retry, too soon") + print_debug(3, "No retry, too soon") return '' self.retries = self.retries + 1 @@ -255,5 +259,5 @@ def process(self, last_response): def _end_transfer(self): self.state = DONE - print("Done sending %d B in %s sec"%(self.size, time.time() - self.start)) + print_debug(3, "Done sending %d B in %s sec"%(self.size, time.time() - self.start)) self.file.close() diff --git a/lib/pymesh/lib/msgpack/__init__.py b/pymesh/pymesh_frozen/lib/msgpack/__init__.py similarity index 100% rename from lib/pymesh/lib/msgpack/__init__.py rename to pymesh/pymesh_frozen/lib/msgpack/__init__.py diff --git a/lib/pymesh/lib/msgpack/_version.py b/pymesh/pymesh_frozen/lib/msgpack/_version.py similarity index 100% rename from lib/pymesh/lib/msgpack/_version.py rename to pymesh/pymesh_frozen/lib/msgpack/_version.py diff --git a/lib/pymesh/lib/msgpack/exceptions.py b/pymesh/pymesh_frozen/lib/msgpack/exceptions.py similarity index 100% rename from lib/pymesh/lib/msgpack/exceptions.py rename to pymesh/pymesh_frozen/lib/msgpack/exceptions.py diff --git a/lib/pymesh/lib/msgpack/fallback.py b/pymesh/pymesh_frozen/lib/msgpack/fallback.py similarity index 100% rename from lib/pymesh/lib/msgpack/fallback.py rename to pymesh/pymesh_frozen/lib/msgpack/fallback.py diff --git a/pymesh/pymesh_frozen/lib/pymesh.py b/pymesh/pymesh_frozen/lib/pymesh.py new file mode 100644 index 0000000..cf7ddd2 --- /dev/null +++ b/pymesh/pymesh_frozen/lib/pymesh.py @@ -0,0 +1,247 @@ +''' +Copyright (c) 2020, Pycom Limited. +This software is licensed under the GNU GPL version 3 or any +later version, with permitted additional terms. For more information +see the Pycom Licence v1.0 document supplied with this file, or +available at https://www.pycom.io/opensource/licensing +''' + +import os +import machine +from machine import Timer +import _thread +import sys +import time + +try: + from mesh_interface import MeshInterface +except: + from _mesh_interface import MeshInterface + +try: + from cli import Cli +except: + from _cli import Cli + +try: + from pymesh_debug import * +except: + from _pymesh_debug import * + +try: + from pymesh_config import PymeshConfig +except: + from _pymesh_config import PymeshConfig + + +__version__ = '3' +""" +__version__ = '3' +* CLI can start/stop dynamically +* replaced all print with print_debug + +__version__ = '2' +* added pause/resume + +__version__ = '1' +* not-versioned prior 5th of Febr 2020 + +""" + +class Pymesh: + + def __init__(self, config, message_cb): + # print MAC, set MAC is given and restart + + self.config = config + self.mesh = MeshInterface(self.config, message_cb) + + self.kill_all = False + self.deepsleep_timeout = 0 + self.new_lora_mac = None + + # self.mesh.statistics.sleep_function = self.deepsleep_init + self.mesh.sleep_function = self.deepsleep_init + + self.is_paused = False + self._threads_start() + + self.cli = None + + self.ble_rpc = None + if config.get("ble_api", False): + try: + from ble_rpc import BleRpc + except: + from _ble_rpc import BleRpc + + self.ble_rpc = BleRpc(self.config, self.mesh) + + def cli_start(self): + if self.cli is None: + self.cli = Cli(self.mesh, self) + self.cli.sleep = self.deepsleep_init + # self.cli_thread = _thread.start_new_thread(self.cli.process, (1, 2)) + self.cli.process(None, None) + + def deepsleep_now(self): + """ prepare scripts for graceful exit, deepsleeps if case """ + print_debug(1, "deepsleep_now") + self.mesh.pause() + if self.ble_rpc: + self.ble_rpc.terminate() + # watchdog.timer_kill() + # Gps.terminate() + # self.mesh.statistics.save_all() + print_debug(1, 'Cleanup code, all Alarms cb should be stopped') + if self.new_lora_mac: + fo = open("/flash/sys/lpwan.mac", "wb") + mac_write=bytes([0, 0, 0, 0, 0, 0, (self.new_lora_mac >> 8) & 0xFF, self.new_lora_mac & 0xFF]) + fo.write(mac_write) + fo.close() + print_debug(1, "Really LoRa MAC set to " + str(self.new_lora_mac)) + if self.deepsleep_timeout > 0: + print_debug(1, 'Going to deepsleep for %d seconds'%self.deepsleep_timeout) + time.sleep(1) + machine.deepsleep(self.deepsleep_timeout * 1000) + else: + raise Exception("Pymesh done") + sys.exit() + + def deepsleep_init(self, timeout, new_MAC = None): + """ initializes an deep-sleep sequence, that will be performed later """ + print_debug(3, "deepsleep_init") + self.deepsleep_timeout = timeout + self.kill_all = True + if new_MAC: + self.new_lora_mac = new_MAC + return + + def process(self, arg1, arg2): + try: + while True: + if self.kill_all: + self.deepsleep_now() + if self.is_paused: + # break + _thread.exit() + time.sleep(.5) + pass + + except KeyboardInterrupt: + print_debug(1, 'Got Ctrl-C') + except Exception as e: + sys.print_exception(e) + + def _threads_start(self): + _thread.start_new_thread(self.process, (1,2)) + + def pause(self): + if self.is_paused: + # print_debug(5, "Pymesh already paused") + return + + print_debug(3, "Pymesh pausing") + + self.mesh.pause() + if self.ble_rpc: + self.ble_rpc.terminate() + + self.is_paused = True + return + + def resume(self, tx_dBm = 14): + if not self.is_paused: + # print_debug(5, "Pymesh can't be resumed, not paused") + return + + print_debug(3, "Pymesh resuming") + self.is_paused = False + self._threads_start() + self.mesh.resume(tx_dBm) + + return + + def send_mess(self, mac, mess): + """ send mess to specified MAC address + data is dictionary data = { + 'to': 0x5, + 'b': 'text', + 'id': 12345, + 'ts': 123123123, + } """ + data = { + 'to': mac, + 'b': mess, + 'id': 12345, + 'ts': time.time(), + } + return self.mesh.send_message(data) + + def br_set(self, prio, br_mess_cb): + """ Enable BR functionality on this Node, with priority and callback """ + return self.mesh.br_set(True, prio, br_mess_cb) + + def br_remove(self): + """ Disable BR functionality on this Node """ + return self.mesh.br_set(False) + + def status_str(self): + message = "Role " + str(self.mesh.mesh.mesh.mesh.state()) + \ + ", Single " + str(self.mesh.mesh.mesh.mesh.single()) + \ + ", IPv6: " + str(self.mesh.mesh.mesh.mesh.ipaddr()) + return message + + def is_connected(self): + return self.mesh.is_connected() + + def send_mess_external(self, ip, port, payload): + """ send mess to specified IP+port address + data is dictionary data = { + 'ip': '1:2:3::4', + 'port': 12345, + 'to': 0x5, + 'b': 'text', + 'id': 12345, + 'ts': 123123123, + } """ + data = { + 'ip': ip, + 'port': port, + 'b': payload + } + return self.mesh.send_message(data) + + def config_get(self): + return self.config + + def config_set(self, config_json_dict): + PymeshConfig.write_config(config_json_dict) + return self.config + + def mac(self): + return self.mesh.mesh.MAC + + def ot_cli(self, command): + """ Call OpenThread internal CLI """ + return self.mesh.ot_cli(command) + + def end_device(self, state = None): + """ Set current node and End (Sleepy) Device, always a Child """ + return self.mesh.end_device(state) + + def leader_priority(self, weight = None): + """ Set for the current node the Leader Weight; + it's a 0 to 255 value, which increases/decreases probability to become Leader; + by default any node has weight of 64 """ + return self.mesh.leader_priority(weight) + + def debug_level(self, level = None): + """ Set the debug level, 0 - off; recommended levels are: + DEBUG_DEBG = const(5) + DEBUG_INFO = const(4) + DEBUG_NOTE = const(3) + DEBUG_WARN = const(2) + DEBUG_CRIT = const(1) + DEBUG_NONE = const(0) """ + return debug_level(level) diff --git a/pymesh/pymesh_frozen/lib/pymesh_config.py b/pymesh/pymesh_frozen/lib/pymesh_config.py new file mode 100644 index 0000000..74a0292 --- /dev/null +++ b/pymesh/pymesh_frozen/lib/pymesh_config.py @@ -0,0 +1,143 @@ +''' +Copyright (c) 2020, Pycom Limited. +This software is licensed under the GNU GPL version 3 or any +later version, with permitted additional terms. For more information +see the Pycom Licence v1.0 document supplied with this file, or +available at https://www.pycom.io/opensource/licensing +''' +import json + +from network import LoRa +import machine +import time + +try: + from pymesh_debug import print_debug +except: + from _pymesh_debug import print_debug + +__version__ = '2' +""" +__version__ = '2' +* added BR_ena, BR_prio + +__version__ = '' +* first release + +""" + +class PymeshConfig: + + CONFIG_FILENAME = "/flash/pymesh_config.json" + + ############################################################ + # DEFAULT SETTINGS + + # LoRa region is one of LoRa.US915, LoRa.EU868, LoRa.AS923, LoRa.AU915 + LORA_REGION = LoRa.EU868 + + # frequency expressed in Hz, for EU868 863000000 Hz, for US915 904600000 Hz + LORA_FREQ = const(869000000) + + # bandwidth options are: LoRa.BW_125KHZ, LoRa.BW_250KHZ or LoRa.BW_500KHZ + LORA_BW = LoRa.BW_500KHZ + + # spreading factor options are 7 to 12 + LORA_SF = const(7) + + # Pymesh 128b key, used for auth. and encryption + KEY = "112233" + + # if true, Pymesh is auto-started + AUTOSTART = True + DEBUG_LEVEL = 5 + + # if true, it will start as BLE Server, to be connected with mobile app + BLE_API = False + BLE_NAME_PREFIX = "PyGo " + + ############################################################ + # Border router preference priority + BR_PRIORITY_NORM = const(0) + BR_PRIORITY_LOW = const(-1) + BR_PRIORITY_HIGH = const(1) + + # if true then this node is Border Router + BR_ENABLE = False + # the default BR priority + BR_PRIORITY = BR_PRIORITY_NORM + + def write_config(pymesh_config, force_restart = False): + cf = open(PymeshConfig.CONFIG_FILENAME, 'w') + cf.write(json.dumps(pymesh_config)) + cf.close() + + if force_restart: + print_debug(3, "write_config force restart") + time.sleep(1) + machine.deepsleep(1000) + + def check_mac(pymesh_config, MAC): + if pymesh_config.get('MAC') is None: + # if MAC config unspecified, set it to LoRa MAC + print_debug(3, "Set MAC in config file as " + str(MAC)) + pymesh_config['MAC'] = MAC + PymeshConfig.write_config(pymesh_config, False) + else: + mac_from_config = pymesh_config.get('MAC') + if mac_from_config != MAC: + print_debug(3, "MAC different"+ str(mac_from_config) + str(MAC)) + pymesh_config['MAC'] = MAC + # if MAC in config different than LoRa MAC, set LoRa MAC as in config file + fo = open("/flash/sys/lpwan.mac", "wb") + mac_write=bytes([(MAC >> 56) & 0xFF, (MAC >> 48) & 0xFF, (MAC >> 40) & 0xFF, (MAC >> 32) & 0xFF, (MAC >> 24) & 0xFF, (MAC >> 16) & 0xFF, (MAC >> 8) & 0xFF, MAC & 0xFF]) + fo.write(mac_write) + fo.close() + # print_debug(3, "reset") + PymeshConfig.write_config(pymesh_config, False) + + print_debug(3, "MAC ok" + str(MAC)) + + def read_config(MAC): + file = PymeshConfig.CONFIG_FILENAME + pymesh_config = {} + error_file = True + + try: + import json + f = open(file, 'r') + jfile = f.read() + f.close() + try: + pymesh_config = json.loads(jfile.strip()) + # pymesh_config['cfg_msg'] = "Pymesh configuration read from {}".format(file) + error_file = False + except Exception as ex: + print_debug(1, "Error reading {} file!\n Exception: {}".format(file, ex)) + except Exception as ex: + print_debug(1, "Final error reading {} file!\n Exception: {}".format(file, ex)) + + if error_file: + # config file can't be read, so it needs to be created and saved + pymesh_config = {} + print_debug(3, "Can't find" +str(file) + ", or can't be parsed as json; Set default settings and reset") + # don't write MAC, just to use the hardware one + pymesh_config['LoRa'] = {"region": PymeshConfig.LORA_REGION, + "freq": PymeshConfig.LORA_FREQ, + "bandwidth": PymeshConfig.LORA_BW, + "sf": PymeshConfig.LORA_SF} + pymesh_config['Pymesh'] = {"key": PymeshConfig.KEY} + pymesh_config['autostart'] = PymeshConfig.AUTOSTART + pymesh_config['debug'] = PymeshConfig.DEBUG_LEVEL + pymesh_config['ble_api'] = PymeshConfig.BLE_API + pymesh_config['ble_name_prefix'] = PymeshConfig.BLE_NAME_PREFIX + pymesh_config['br_ena'] = PymeshConfig.BR_ENABLE + pymesh_config['br_prio'] = PymeshConfig.BR_PRIORITY + + PymeshConfig.check_mac(pymesh_config, MAC) + print_debug(3, "Default settings:" + str(pymesh_config)) + PymeshConfig.write_config(pymesh_config, True) + + PymeshConfig.check_mac(pymesh_config, MAC) + print_debug(3, "Settings:" + str(pymesh_config)) + return pymesh_config diff --git a/pymesh/pymesh_frozen/lib/pymesh_debug.py b/pymesh/pymesh_frozen/lib/pymesh_debug.py new file mode 100644 index 0000000..ee1e515 --- /dev/null +++ b/pymesh/pymesh_frozen/lib/pymesh_debug.py @@ -0,0 +1,45 @@ + +# Copyright (c) 2020, Pycom Limited. +# +# This software is licensed under the GNU GPL version 3 or any +# later version, with permitted additional terms. For more information +# see the Pycom Licence v1.0 document supplied with this file, or +# available at https://www.pycom.io/opensource/licensing + +import pycom + +# recommended debug levels, from the most verbose to off +DEBUG_DEBG = const(5) +DEBUG_INFO = const(4) +DEBUG_NOTE = const(3) +DEBUG_WARN = const(2) +DEBUG_CRIT = const(1) +DEBUG_NONE = const(0) + +try: + DEBUG = pycom.nvs_get('pymesh_debug') +except: + DEBUG = None + + +def print_debug(level, msg): + """Print log messages into console.""" + if DEBUG is not None and level <= DEBUG: + print(msg) + +def debug_level(level): + global DEBUG + if level is None: + try: + ret = pycom.nvs_get('pymesh_debug') + except: + ret = None + return ret + try: + ret = int(level) + except: + return + + DEBUG = ret + pycom.nvs_set('pymesh_debug', DEBUG) + \ No newline at end of file diff --git a/lib/pymesh/lib/statistics.py b/pymesh/pymesh_frozen/lib/statistics.py similarity index 99% rename from lib/pymesh/lib/statistics.py rename to pymesh/pymesh_frozen/lib/statistics.py index 9991a38..640694e 100644 --- a/lib/pymesh/lib/statistics.py +++ b/pymesh/pymesh_frozen/lib/statistics.py @@ -1,4 +1,4 @@ -# Copyright (c) 2019, Pycom Limited. +# Copyright (c) 2020, Pycom Limited. # # This software is licensed under the GNU GPL version 3 or any # later version, with permitted additional terms. For more information diff --git a/pymesh/pymesh_frozen/lorawan/lorawan.py b/pymesh/pymesh_frozen/lorawan/lorawan.py new file mode 100644 index 0000000..4199f20 --- /dev/null +++ b/pymesh/pymesh_frozen/lorawan/lorawan.py @@ -0,0 +1,59 @@ +""" OTAA Node example compatible with the LoPy Nano Gateway """ + +from network import LoRa +from network import WLAN +import socket +import binascii +import struct +import time +from machine import RTC + +class Lorawan: + + def __init__(self): + # create an OTA authentication params + self.dev_eui = binascii.unhexlify('007926C9EAE4C922') + self.app_eui = binascii.unhexlify('70B3D57ED001D8C8') + self.app_key = binascii.unhexlify('2C4D6AE9CEBA8B0EB4430C33C17750CB') + + def send(self): + t0 = time.time() + print("LoRaWAN start") + lora = LoRa(mode=LoRa.LORAWAN, region=LoRa.EU868) + + # set the 3 default channels to the same frequency (must be before sending the OTAA join request) + lora.add_channel(0, frequency=config.LORA_FREQUENCY, dr_min=0, dr_max=5) + lora.add_channel(1, frequency=config.LORA_FREQUENCY, dr_min=0, dr_max=5) + lora.add_channel(2, frequency=config.LORA_FREQUENCY, dr_min=0, dr_max=5) + + # join a network using OTAA + lora.join(activation=LoRa.OTAA, auth=(self.dev_eui, self.app_eui, self.app_key), timeout=0, dr=config.LORA_NODE_DR) + + # wait until the module has joined the network + while not lora.has_joined(): + time.sleep(2.5) + print('Not joined yet...', time.localtime()) + + # remove all the non-default channels + for i in range(3, 16): + lora.remove_channel(i) + + # create a LoRa socket + s = socket.socket(socket.AF_LORA, socket.SOCK_RAW) + + # set the LoRaWAN data rate + s.setsockopt(socket.SOL_LORA, socket.SO_DR, config.LORA_NODE_DR) + + # make the socket non-blocking + s.setblocking(False) + + pkt = b'PKT #' + bytes([i]) + print('Sending:', pkt) + s.send(pkt) + print("LoRaWAN done in ", time.time() - t0, " seconds") + +class config: + # for EU868 + LORA_FREQUENCY = 867500000 + LORA_GW_DR = "SF7BW125" # DR_5 + LORA_NODE_DR = 5 diff --git a/pymesh/pymesh_frozen/lorawan/main.py b/pymesh/pymesh_frozen/lorawan/main.py new file mode 100644 index 0000000..33f33da --- /dev/null +++ b/pymesh/pymesh_frozen/lorawan/main.py @@ -0,0 +1,66 @@ +import time +import ubinascii +import pycom +from network import LoRa + +try: + from pymesh_config import PymeshConfig +except: + from _pymesh_config import PymeshConfig + +try: + from pymesh import Pymesh +except: + from _pymesh import Pymesh + +def new_message_cb(rcv_ip, rcv_port, rcv_data): + ''' callback triggered when a new packet arrived ''' + print('Incoming %d bytes from %s (port %d):' % + (len(rcv_data), rcv_ip, rcv_port)) + print(rcv_data) + + # user code to be inserted, to send packet to the designated Mesh-external interface + for _ in range(3): + pycom.rgbled(0x888888) + time.sleep(.2) + pycom.rgbled(0) + time.sleep(.1) + return + + +pycom.heartbeat(False) + +lora = LoRa(mode=LoRa.LORA, region= LoRa.EU868) +lora_mac = int(str(ubinascii.hexlify(lora.mac()))[2:-1], 16) + +# read config file, or set default values +pymesh_config = PymeshConfig.read_config(lora_mac) + +#initialize Pymesh +pymesh = Pymesh(pymesh_config, new_message_cb) + +while not pymesh.is_connected(): + print(pymesh.status_str()) + time.sleep(3) + +# send message to the Node having MAC address 5 +pymesh.send_mess(20, "Hello World") + +print("done Pymesh init, forever loop, exit/stop with Ctrl+C multiple times") + +from lorawan import Lorawan +lorawan = Lorawan() +t0 = time.time() + +while True: + if time.time() - t0 > 60: + pymesh.pause() + + lorawan.send() + + pymesh.resume() + t0 = time.time() + if time.time() - t0 > 35: + pymesh.send_mess(20, "heloo again, #22 here, " + str(time.time())) + + time.sleep(5) diff --git a/pymesh/pymesh_frozen/main-pybytes.py b/pymesh/pymesh_frozen/main-pybytes.py new file mode 100644 index 0000000..f38d127 --- /dev/null +++ b/pymesh/pymesh_frozen/main-pybytes.py @@ -0,0 +1,22 @@ +import time +import pycom + +# todo: add try/except for checking pybytes object exists +pymesh = pybytes.__pymesh.__pymesh + +print("Set maximum debug level, disable debug using pymesh.debug_level(0)") +pymesh.debug_level(5) + +while not pymesh.is_connected(): + print(pymesh.status_str()) + time.sleep(3) + +print(pymesh.status_str()) +# send message to the Node having MAC address 5 +pymesh.send_mess(18, "Hello World") + +print("done Pymesh init, CLI is started, h - help/command list, stop - CLI will be stopped") +pymesh.cli_start() + +# send a packet to Pybytes, thru the BR (if any enabled) +# pybytes.send_signal(100, "Hello from device" + str(pymesh.mac())) diff --git a/pymesh/pymesh_frozen/main.py b/pymesh/pymesh_frozen/main.py new file mode 100644 index 0000000..5d7c17a --- /dev/null +++ b/pymesh/pymesh_frozen/main.py @@ -0,0 +1,81 @@ +import time +import ubinascii +import pycom +from network import LoRa + +try: + from pymesh_config import PymeshConfig +except: + from _pymesh_config import PymeshConfig + +try: + from pymesh import Pymesh +except: + from _pymesh import Pymesh + +def new_message_cb(rcv_ip, rcv_port, rcv_data): + ''' callback triggered when a new packet arrived ''' + print('Incoming %d bytes from %s (port %d):' % + (len(rcv_data), rcv_ip, rcv_port)) + print(rcv_data) + + # user code to be inserted, to send packet to the designated Mesh-external interface + for _ in range(3): + pycom.rgbled(0x888888) + time.sleep(.2) + pycom.rgbled(0) + time.sleep(.1) + return + + +pycom.heartbeat(False) + +lora = LoRa(mode=LoRa.LORA, region= LoRa.EU868) +lora_mac = int(str(ubinascii.hexlify(lora.mac()))[2:-1], 16) + +# read config file, or set default values +pymesh_config = PymeshConfig.read_config(lora_mac) + +#initialize Pymesh +pymesh = Pymesh(pymesh_config, new_message_cb) + +# mac = pymesh.mac() +# if mac > 10: +# pymesh.end_device(True) +# elif mac == 5: +# pymesh.leader_priority(255) + +while not pymesh.is_connected(): + print(pymesh.status_str()) + time.sleep(3) + +# send message to the Node having MAC address 5 +pymesh.send_mess(5, "Hello World") + +# def new_br_message_cb(rcv_ip, rcv_port, rcv_data, dest_ip, dest_port): +# ''' callback triggered when a new packet arrived for the current Border Router, +# having destination an IP which is external from Mesh ''' +# print('Incoming %d bytes from %s (port %d), to external IPv6 %s (port %d)' % +# (len(rcv_data), rcv_ip, rcv_port, dest_ip, dest_port)) +# print(rcv_data) + +# # user code to be inserted, to send packet to the designated Mesh-external interface +# # ... +# return + +# add current node as Border Router, with a priority and a message handler callback +# pymesh.br_set(PymeshConfig.BR_PRIORITY_NORM, new_br_message_cb) + +# remove Border Router function from current node +# pymesh.br_remove() + +# send data for Mesh-external, basically to the BR +# ip = "1:2:3::4" +# port = 5555 +# pymesh.send_mess_external(ip, port, "Hello World") + +print("done Pymesh init, CLI is started, h - help/command list, stop - CLI will be stopped") +pymesh.cli_start() + +# while True: +# time.sleep(3) diff --git a/pymesh/pymesh_frozen/main_BR.py b/pymesh/pymesh_frozen/main_BR.py new file mode 100644 index 0000000..fdb9780 --- /dev/null +++ b/pymesh/pymesh_frozen/main_BR.py @@ -0,0 +1,135 @@ +import time +import ubinascii +import pycom +from network import LoRa + +# 2 = test pybytes OTA feature +# 4 = added device_id (pybytes token) in the packets to BR +__VERSION__ = 4 + +try: + from pymesh_config import PymeshConfig +except: + from _pymesh_config import PymeshConfig + +try: + from pymesh import Pymesh +except: + from _pymesh import Pymesh + +PACK_TOCKEN_PREFIX = "tkn" +PACK_TOCKEN_SEP = "#" + +print("Scripts version ", __VERSION__) + +if 'pybytes' not in globals(): + pybytes = None + +def new_message_cb(rcv_ip, rcv_port, rcv_data): + ''' callback triggered when a new packet arrived ''' + print('Incoming %d bytes from %s (port %d):' % + (len(rcv_data), rcv_ip, rcv_port)) + print(rcv_data) + + # user code to be inserted, to send packet to the designated Mesh-external interface + for _ in range(3): + pycom.rgbled(0x888888) + time.sleep(.2) + pycom.rgbled(0) + time.sleep(.1) + return + +def new_br_message_cb(rcv_ip, rcv_port, rcv_data, dest_ip, dest_port): + ''' callback triggered when a new packet arrived for the current Border Router, + having destination an IP which is external from Mesh ''' + print('Incoming %d bytes from %s (port %d), to external IPv6 %s (port %d)' % + (len(rcv_data), rcv_ip, rcv_port, dest_ip, dest_port)) + print(rcv_data) + + for _ in range(2): + pycom.rgbled(0x0) + time.sleep(.1) + # pycom.rgbled(0x001010) + pycom.rgbled(0x663300) + # time.sleep(.2) + + if pybytes is not None and pybytes.isconnected(): + # try to find Pybytes Token if include in rcv_data + token = "" + if rcv_data.startswith(PACK_TOCKEN_PREFIX): + x = rcv_data.split(PACK_TOCKEN_SEP.encode()) + if len(x)>2: + token = x[1] + rcv_data = rcv_data[len(PACK_TOCKEN_PREFIX) + len(token) + len(PACK_TOCKEN_SEP):] + pkt = 'BR %d B from %s (%s), to %s ( %d): %s'%(len(rcv_data), token, rcv_ip, dest_ip, dest_port, str(rcv_data)) + pybytes.send_signal(1, pkt) + + return + +pycom.heartbeat(False) + +lora = LoRa(mode=LoRa.LORA, region= LoRa.EU868) +lora_mac = int(str(ubinascii.hexlify(lora.mac()))[2:-1], 16) + +# read config file, or set default values +pymesh_config = PymeshConfig.read_config(lora_mac) + +#initialize Pymesh +pymesh = Pymesh(pymesh_config, new_message_cb) + +# mac = pymesh.mac() +# if mac > 10: +# pymesh.end_device(True) +# elif mac == 5: +# pymesh.leader_priority(255) + +while not pymesh.is_connected(): + print(pymesh.status_str()) + time.sleep(3) + +# send message to the Node having MAC address 5 +pymesh.send_mess(2, "Hello World") + + +print("done Pymesh init, forever loop, exit/stop with Ctrl+C multiple times") +# set BR with callback +if pybytes is not None and pybytes.isconnected(): + pybytes.send_signal(1, "RESTART") + +pyb_port = pymesh.mac() & 0xFFFF +pyb_ip = '1:2:3::' + hex(pyb_port)[2:] +pkt_start = "" +# add pybytes token +if pybytes is not None: + pyb_dev_id = pybytes.get_config().get("device_id", "None") + pkt_start = PACK_TOCKEN_PREFIX + PACK_TOCKEN_SEP + pyb_dev_id + PACK_TOCKEN_SEP +pkt_start = pkt_start + "Hello, from " + str(pymesh.mac()) + ", time " + + +br_enabled = False + +while True: + # add current node as Border Router, with a priority and a message handler callback + + free_mem = pycom.get_free_heap() + + if pymesh_config.get("br_ena", False): + if pybytes is not None and pybytes.isconnected(): + if not br_enabled: + br_enabled = True + print("Set as BR") + pymesh.br_set(PymeshConfig.BR_PRIORITY_NORM, new_br_message_cb) + + pybytes.send_signal(1, str(pymesh.mac()) +" : " + str(time.time()) + "s, "+ str(free_mem)) + print("Send to Pyb,", free_mem) + else: # not connected anymore to pybytes + if br_enabled: + br_enabled = False + print("Remove as BR") + pymesh.br_remove() + else: # not MAC_BR + pkt = pkt_start + str(time.time()) + ", mem " + str(free_mem) + pymesh.send_mess_external(pyb_ip, pyb_port, pkt) + print("Sending to BR: ", pkt) + + time.sleep(20) diff --git a/pymesh/readme.md b/pymesh/readme.md new file mode 100644 index 0000000..0be1a7a --- /dev/null +++ b/pymesh/readme.md @@ -0,0 +1,3 @@ +# For reference only + +This library is the open-source part of the Pymesh LoRa Firmware which can be provisioned through Pybytes and cannot be used as stand-alone on a Pybytes or Pygate type firmware. Please check the [Pycom Documentation](https://docs.pycom.io/pybytes/pymeshintegration/provisioning/) on how to get Pymesh type firmware provisioned to your device. You will not have to use this library in your project or flash it to your device separately, as it is already included in the firmware. diff --git a/pyscan/lib/LIS2HH12.py b/pyscan/lib/LIS2HH12.py deleted file mode 100644 index 49a6b5a..0000000 --- a/pyscan/lib/LIS2HH12.py +++ /dev/null @@ -1,178 +0,0 @@ -#!/usr/bin/env python -# -# Copyright (c) 2019, Pycom Limited. -# -# This software is licensed under the GNU GPL version 3 or any -# later version, with permitted additional terms. For more information -# see the Pycom Licence v1.0 document supplied with this file, or -# available at https://www.pycom.io/opensource/licensing -# - -import math -import time -import struct -from machine import Pin - - -FULL_SCALE_2G = const(0) -FULL_SCALE_4G = const(2) -FULL_SCALE_8G = const(3) - -ODR_POWER_DOWN = const(0) -ODR_10_HZ = const(1) -ODR_50_HZ = const(2) -ODR_100_HZ = const(3) -ODR_200_HZ = const(4) -ODR_400_HZ = const(5) -ODR_800_HZ = const(6) - -ACC_G_DIV = 1000 * 65536 - - -class LIS2HH12: - - ACC_I2CADDR = const(30) - - PRODUCTID_REG = const(0x0F) - CTRL1_REG = const(0x20) - CTRL2_REG = const(0x21) - CTRL3_REG = const(0x22) - CTRL4_REG = const(0x23) - CTRL5_REG = const(0x24) - ACC_X_L_REG = const(0x28) - ACC_X_H_REG = const(0x29) - ACC_Y_L_REG = const(0x2A) - ACC_Y_H_REG = const(0x2B) - ACC_Z_L_REG = const(0x2C) - ACC_Z_H_REG = const(0x2D) - ACT_THS = const(0x1E) - ACT_DUR = const(0x1F) - - SCALES = {FULL_SCALE_2G: 4000, FULL_SCALE_4G: 8000, FULL_SCALE_8G: 16000} - ODRS = [0, 10, 50, 100, 200, 400, 800] - - def __init__(self, pysense = None, sda = 'P22', scl = 'P21'): - if pysense is not None: - self.i2c = pysense.i2c - else: - from machine import I2C - self.i2c = I2C(0, mode=I2C.MASTER, pins=(sda, scl)) - - self.odr = 0 - self.full_scale = 0 - self.x = 0 - self.y = 0 - self.z = 0 - self.int_pin = None - self.act_dur = 0 - self.debounced = False - - whoami = self.i2c.readfrom_mem(ACC_I2CADDR , PRODUCTID_REG, 1) - if (whoami[0] != 0x41): - raise ValueError("LIS2HH12 not found") - - # enable acceleration readings at 50Hz - self.set_odr(ODR_50_HZ) - - # change the full-scale to 4g - self.set_full_scale(FULL_SCALE_4G) - - # set the interrupt pin as active low and open drain - self.set_register(CTRL5_REG, 3, 0, 3) - - # make a first read - self.acceleration() - - def acceleration(self): - x = self.i2c.readfrom_mem(ACC_I2CADDR , ACC_X_L_REG, 2) - self.x = struct.unpack(' self.SCALES[self.full_scale]: - error = "threshold %d exceeds full scale %d" % (thresold, self.SCALES[self.full_scale]) - print(error) - raise ValueError(error) - - if threshold < self.SCALES[self.full_scale] / 128: - error = "threshold %d below resolution %d" % (thresold, self.SCALES[self.full_scale]/128) - print(error) - raise ValueError(error) - - if duration > 255 * 1000 * 8 / self.ODRS[self.odr]: - error = "duration %d exceeds max possible value %d" % (duration, 255 * 1000 * 8 / self.ODRS[self.odr]) - print(error) - raise ValueError(error) - - if duration < 1000 * 8 / self.ODRS[self.odr]: - error = "duration %d below resolution %d" % (duration, 1000 * 8 / self.ODRS[self.odr]) - print(error) - raise ValueError(error) - - _ths = int(127 * threshold / self.SCALES[self.full_scale]) & 0x7F - _dur = int((duration * self.ODRS[self.odr]) / 1000 / 8) - - self.i2c.writeto_mem(ACC_I2CADDR, ACT_THS, _ths) - self.i2c.writeto_mem(ACC_I2CADDR, ACT_DUR, _dur) - - # enable the activity/inactivity interrupt - self.set_register(CTRL3_REG, 1, 5, 1) - - self._user_handler = handler - self.int_pin = Pin('P13', mode=Pin.IN) - self.int_pin.callback(trigger=Pin.IRQ_FALLING | Pin.IRQ_RISING, handler=self._int_handler) - - # return actual used thresold and duration - return (_ths * self.SCALES[self.full_scale] / 128, _dur * 8 * 1000 / self.ODRS[self.odr]) - - def activity(self): - if not self.debounced: - time.sleep_ms(self.act_dur) - self.debounced = True - if self.int_pin(): - return True - return False - - def _int_handler(self, pin_o): - if self._user_handler is not None: - self._user_handler(pin_o) - else: - if pin_o(): - print('Activity interrupt') - else: - print('Inactivity interrupt') diff --git a/pyscan/lib/LTR329ALS01.py b/pyscan/lib/LTR329ALS01.py deleted file mode 100644 index 49e3439..0000000 --- a/pyscan/lib/LTR329ALS01.py +++ /dev/null @@ -1,80 +0,0 @@ -#!/usr/bin/env python -# -# Copyright (c) 2019, Pycom Limited. -# -# This software is licensed under the GNU GPL version 3 or any -# later version, with permitted additional terms. For more information -# see the Pycom Licence v1.0 document supplied with this file, or -# available at https://www.pycom.io/opensource/licensing -# - -import time -from machine import I2C - -class LTR329ALS01: - ALS_I2CADDR = const(0x29) # The device's I2C address - - ALS_CONTR_REG = const(0x80) - ALS_MEAS_RATE_REG = const(0x85) - - ALS_DATA_CH1_LOW = const(0x88) - ALS_DATA_CH1_HIGH = const(0x89) - ALS_DATA_CH0_LOW = const(0x8A) - ALS_DATA_CH0_HIGH = const(0x8B) - - ALS_GAIN_1X = const(0x00) - ALS_GAIN_2X = const(0x01) - ALS_GAIN_4X = const(0x02) - ALS_GAIN_8X = const(0x03) - ALS_GAIN_48X = const(0x06) - ALS_GAIN_96X = const(0x07) - - ALS_INT_50 = const(0x01) - ALS_INT_100 = const(0x00) - ALS_INT_150 = const(0x04) - ALS_INT_200 = const(0x02) - ALS_INT_250 = const(0x05) - ALS_INT_300 = const(0x06) - ALS_INT_350 = const(0x07) - ALS_INT_400 = const(0x03) - - ALS_RATE_50 = const(0x00) - ALS_RATE_100 = const(0x01) - ALS_RATE_200 = const(0x02) - ALS_RATE_500 = const(0x03) - ALS_RATE_1000 = const(0x04) - ALS_RATE_2000 = const(0x05) - - def __init__(self, pysense = None, sda = 'P22', scl = 'P21', gain = ALS_GAIN_1X, integration = ALS_INT_100, rate = ALS_RATE_500): - if pysense is not None: - self.i2c = pysense.i2c - else: - self.i2c = I2C(0, mode=I2C.MASTER, pins=(sda, scl)) - - contr = self._getContr(gain) - self.i2c.writeto_mem(ALS_I2CADDR, ALS_CONTR_REG, bytearray([contr])) - - measrate = self._getMeasRate(integration, rate) - self.i2c.writeto_mem(ALS_I2CADDR, ALS_MEAS_RATE_REG, bytearray([measrate])) - - time.sleep(0.01) - - def _getContr(self, gain): - return ((gain & 0x07) << 2) + 0x01 - - def _getMeasRate(self, integration, rate): - return ((integration & 0x07) << 3) + (rate & 0x07) - - def _getWord(self, high, low): - return ((high & 0xFF) << 8) + (low & 0xFF) - - def light(self): - ch1low = self.i2c.readfrom_mem(ALS_I2CADDR , ALS_DATA_CH1_LOW, 1) - ch1high = self.i2c.readfrom_mem(ALS_I2CADDR , ALS_DATA_CH1_HIGH, 1) - data1 = int(self._getWord(ch1high[0], ch1low[0])) - - ch0low = self.i2c.readfrom_mem(ALS_I2CADDR , ALS_DATA_CH0_LOW, 1) - ch0high = self.i2c.readfrom_mem(ALS_I2CADDR , ALS_DATA_CH0_HIGH, 1) - data0 = int(self._getWord(ch0high[0], ch0low[0])) - - return (data0, data1) diff --git a/pyscan/lib/pyscan.py b/pyscan/lib/pyscan.py deleted file mode 100644 index 9e373f2..0000000 --- a/pyscan/lib/pyscan.py +++ /dev/null @@ -1,8 +0,0 @@ -from pycoproc import Pycoproc - -__version__ = '1.0.0' - -class Pyscan(Pycoproc): - - def __init__(self, i2c=None, sda='P22', scl='P21'): - Pycoproc.__init__(self, i2c, sda, scl) diff --git a/pyscan/main.py b/pyscan/main.py deleted file mode 100644 index c0d7b4f..0000000 --- a/pyscan/main.py +++ /dev/null @@ -1,85 +0,0 @@ -''' -Simple Pyscan NFC / MiFare Classic Example -Copyright (c) 2019, Pycom Limited. - -This example runs the NFC discovery loop in a thread. -If a card is detected it will read the UID and compare it to VALID_CARDS -RGB LED is BLUE while waiting for card, -GREEN if card is valid, RED if card is invalid -''' - -DEBUG = False # change to True to see debug messages - -from pyscan import Pyscan -from MFRC630 import MFRC630 -from LIS2HH12 import LIS2HH12 -from LTR329ALS01 import LTR329ALS01 -import binascii -import time -import pycom -import _thread - -VALID_CARDS = [[0x43, 0x95, 0xDD, 0xF8], - [0x43, 0x95, 0xDD, 0xF9]] - -py = Pyscan() -nfc = MFRC630(py) -lt = LTR329ALS01(py) -li = LIS2HH12(py) - -RGB_BRIGHTNESS = 0x8 - -RGB_RED = (RGB_BRIGHTNESS << 16) -RGB_GREEN = (RGB_BRIGHTNESS << 8) -RGB_BLUE = (RGB_BRIGHTNESS) - -# Make sure heartbeat is disabled before setting RGB LED -pycom.heartbeat(False) - -# Initialise the MFRC630 with some settings -nfc.mfrc630_cmd_init() - -def check_uid(uid, len): - return VALID_CARDS.count(uid[:len]) - -def print_debug(msg): - if DEBUG: - print(msg) - -def send_sensor_data(name, timeout): - while(True): - print(lt.light()) - print(li.acceleration()) - time.sleep(timeout) - -def discovery_loop(nfc, id): - while True: - # Send REQA for ISO14443A card type - print_debug('Sending REQA for ISO14443A card type...') - atqa = nfc.mfrc630_iso14443a_WUPA_REQA(nfc.MFRC630_ISO14443_CMD_REQA) - print_debug('Response: {}'.format(atqa)) - if (atqa != 0): - # A card has been detected, read UID - print_debug('A card has been detected, read UID...') - uid = bytearray(10) - uid_len = nfc.mfrc630_iso14443a_select(uid) - print_debug('UID has length: {}'.format(uid_len)) - if (uid_len > 0): - print_debug('Checking if card with UID: [{:s}] is listed in VALID_CARDS...'.format(binascii.hexlify(uid[:uid_len],' ').upper())) - if (check_uid(list(uid), uid_len)) > 0: - print_debug('Card is listed, turn LED green') - pycom.rgbled(RGB_GREEN) - else: - print_debug('Card is not listed, turn LED red') - pycom.rgbled(RGB_RED) - else: - # No card detected - print_debug('Did not detect any card...') - pycom.rgbled(RGB_BLUE) - nfc.mfrc630_cmd_reset() - time.sleep(.5) - nfc.mfrc630_cmd_init() - -# This is the start of our main execution... start the thread -_thread.start_new_thread(discovery_loop, (nfc, 0)) -_thread.start_new_thread(send_sensor_data, ('Thread 2', 10)) diff --git a/pysense/lib/MPL3115A2.py b/pysense/lib/MPL3115A2.py deleted file mode 100644 index 28a4297..0000000 --- a/pysense/lib/MPL3115A2.py +++ /dev/null @@ -1,129 +0,0 @@ -#!/usr/bin/env python -# -# Copyright (c) 2019, Pycom Limited. -# -# This software is licensed under the GNU GPL version 3 or any -# later version, with permitted additional terms. For more information -# see the Pycom Licence v1.0 document supplied with this file, or -# available at https://www.pycom.io/opensource/licensing -# - -import time -from machine import I2C - -ALTITUDE = const(0) -PRESSURE = const(1) - -class MPL3115A2exception(Exception): - pass - -class MPL3115A2: - MPL3115_I2CADDR = const(0x60) - MPL3115_STATUS = const(0x00) - MPL3115_PRESSURE_DATA_MSB = const(0x01) - MPL3115_PRESSURE_DATA_CSB = const(0x02) - MPL3115_PRESSURE_DATA_LSB = const(0x03) - MPL3115_TEMP_DATA_MSB = const(0x04) - MPL3115_TEMP_DATA_LSB = const(0x05) - MPL3115_DR_STATUS = const(0x06) - MPL3115_DELTA_DATA = const(0x07) - MPL3115_WHO_AM_I = const(0x0c) - MPL3115_FIFO_STATUS = const(0x0d) - MPL3115_FIFO_DATA = const(0x0e) - MPL3115_FIFO_SETUP = const(0x0e) - MPL3115_TIME_DELAY = const(0x10) - MPL3115_SYS_MODE = const(0x11) - MPL3115_INT_SORCE = const(0x12) - MPL3115_PT_DATA_CFG = const(0x13) - MPL3115_BAR_IN_MSB = const(0x14) - MPL3115_P_ARLARM_MSB = const(0x16) - MPL3115_T_ARLARM = const(0x18) - MPL3115_P_ARLARM_WND_MSB = const(0x19) - MPL3115_T_ARLARM_WND = const(0x1b) - MPL3115_P_MIN_DATA = const(0x1c) - MPL3115_T_MIN_DATA = const(0x1f) - MPL3115_P_MAX_DATA = const(0x21) - MPL3115_T_MAX_DATA = const(0x24) - MPL3115_CTRL_REG1 = const(0x26) - MPL3115_CTRL_REG2 = const(0x27) - MPL3115_CTRL_REG3 = const(0x28) - MPL3115_CTRL_REG4 = const(0x29) - MPL3115_CTRL_REG5 = const(0x2a) - MPL3115_OFFSET_P = const(0x2b) - MPL3115_OFFSET_T = const(0x2c) - MPL3115_OFFSET_H = const(0x2d) - - def __init__(self, pysense = None, sda = 'P22', scl = 'P21', mode = PRESSURE): - if pysense is not None: - self.i2c = pysense.i2c - else: - self.i2c = I2C(0, mode=I2C.MASTER, pins=(sda, scl)) - - self.STA_reg = bytearray(1) - self.mode = mode - - if self.mode is PRESSURE: - self.i2c.writeto_mem(MPL3115_I2CADDR, MPL3115_CTRL_REG1, bytes([0x38])) # barometer mode, not raw, oversampling 128, minimum time 512 ms - self.i2c.writeto_mem(MPL3115_I2CADDR, MPL3115_PT_DATA_CFG, bytes([0x07])) # no events detected - self.i2c.writeto_mem(MPL3115_I2CADDR, MPL3115_CTRL_REG1, bytes([0x39])) # active - elif self.mode is ALTITUDE: - self.i2c.writeto_mem(MPL3115_I2CADDR, MPL3115_CTRL_REG1, bytes([0xB8])) # altitude mode, not raw, oversampling 128, minimum time 512 ms - self.i2c.writeto_mem(MPL3115_I2CADDR, MPL3115_PT_DATA_CFG, bytes([0x07])) # no events detected - self.i2c.writeto_mem(MPL3115_I2CADDR, MPL3115_CTRL_REG1, bytes([0xB9])) # active - else: - raise MPL3115A2exception("Invalid Mode MPL3115A2") - - if self._read_status(): - pass - else: - raise MPL3115A2exception("Error with MPL3115A2") - - def _read_status(self): - while True: - self.i2c.readfrom_mem_into(MPL3115_I2CADDR, MPL3115_STATUS, self.STA_reg) - - if(self.STA_reg[0] == 0): - time.sleep(0.01) - pass - elif(self.STA_reg[0] & 0x04) == 4: - return True - else: - return False - - def pressure(self): - if self.mode == ALTITUDE: - raise MPL3115A2exception("Incorrect Measurement Mode MPL3115A2") - - OUT_P_MSB = self.i2c.readfrom_mem(MPL3115_I2CADDR, MPL3115_PRESSURE_DATA_MSB,1) - OUT_P_CSB = self.i2c.readfrom_mem(MPL3115_I2CADDR, MPL3115_PRESSURE_DATA_CSB,1) - OUT_P_LSB = self.i2c.readfrom_mem(MPL3115_I2CADDR, MPL3115_PRESSURE_DATA_LSB,1) - - return float((OUT_P_MSB[0] << 10) + (OUT_P_CSB[0] << 2) + ((OUT_P_LSB[0] >> 6) & 0x03) + ((OUT_P_LSB[0] >> 4) & 0x03) / 4.0) - - def altitude(self): - if self.mode == PRESSURE: - raise MPL3115A2exception("Incorrect Measurement Mode MPL3115A2") - - OUT_P_MSB = self.i2c.readfrom_mem(MPL3115_I2CADDR, MPL3115_PRESSURE_DATA_MSB,1) - OUT_P_CSB = self.i2c.readfrom_mem(MPL3115_I2CADDR, MPL3115_PRESSURE_DATA_CSB,1) - OUT_P_LSB = self.i2c.readfrom_mem(MPL3115_I2CADDR, MPL3115_PRESSURE_DATA_LSB,1) - - alt_int = (OUT_P_MSB[0] << 8) + (OUT_P_CSB[0]) - alt_frac = ((OUT_P_LSB[0] >> 4) & 0x0F) - - if alt_int > 32767: - alt_int -= 65536 - - return float(alt_int + alt_frac / 16.0) - - def temperature(self): - OUT_T_MSB = self.i2c.readfrom_mem(MPL3115_I2CADDR, MPL3115_TEMP_DATA_MSB,1) - OUT_T_LSB = self.i2c.readfrom_mem(MPL3115_I2CADDR, MPL3115_TEMP_DATA_LSB,1) - - temp_int = OUT_T_MSB[0] - temp_frac = OUT_T_LSB[0] - - if temp_int > 127: - temp_int -= 256 - - return float(temp_int + temp_frac / 256.0) diff --git a/pysense/lib/SI7006A20.py b/pysense/lib/SI7006A20.py deleted file mode 100644 index d9a4025..0000000 --- a/pysense/lib/SI7006A20.py +++ /dev/null @@ -1,118 +0,0 @@ -#!/usr/bin/env python -# -# Copyright (c) 2019, Pycom Limited. -# -# This software is licensed under the GNU GPL version 3 or any -# later version, with permitted additional terms. For more information -# see the Pycom Licence v1.0 document supplied with this file, or -# available at https://www.pycom.io/opensource/licensing -# - -import time -from machine import I2C -import math - -__version__ = '0.0.2' - -class SI7006A20: - """ class for handling the temperature sensor SI7006-A20 - +/- 1 deg C error for temperature - +/- 5% error for relative humidity - datasheet available at https://www.silabs.com/documents/public/data-sheets/Si7006-A20.pdf """ - - SI7006A20_I2C_ADDR = const(0x40) - - TEMP_NOHOLDMASTER = const(0xF3) - HUMD_NOHOLDMASTER = const(0xF5) - - def __init__(self, pysense = None, sda = 'P22', scl = 'P21'): - if pysense is not None: - self.i2c = pysense.i2c - else: - self.i2c = I2C(0, mode=I2C.MASTER, pins=(sda, scl)) - - def _getWord(self, high, low): - return ((high & 0xFF) << 8) + (low & 0xFF) - - def temperature(self): - """ obtaining the temperature(degrees Celsius) measured by sensor """ - self.i2c.writeto(SI7006A20_I2C_ADDR, bytearray([0xF3])) - time.sleep(0.5) - data = self.i2c.readfrom(SI7006A20_I2C_ADDR, 3) - #print("CRC Raw temp data: " + hex(data[0]*65536 + data[1]*256 + data[2])) - data = self._getWord(data[0], data[1]) - temp = ((175.72 * data) / 65536.0) - 46.85 - return temp - - def humidity(self): - """ obtaining the relative humidity(%) measured by sensor """ - self.i2c.writeto(SI7006A20_I2C_ADDR, bytearray([0xF5])) - time.sleep(0.5) - data = self.i2c.readfrom(SI7006A20_I2C_ADDR, 2) - data = self._getWord(data[0], data[1]) - humidity = ((125.0 * data) / 65536.0) - 6.0 - return humidity - - def read_user_reg(self): - """ reading the user configuration register """ - self.i2c.writeto(SI7006A20_I2C_ADDR, bytearray([0xE7])) - time.sleep(0.5) - data = self.i2c.readfrom(SI7006A20_I2C_ADDR, 1) - return data[0] - - def read_heater_reg(self): - """ reading the heater configuration register """ - self.i2c.writeto(SI7006A20_I2C_ADDR, bytearray([0x11])) - time.sleep(0.5) - data = self.i2c.readfrom(SI7006A20_I2C_ADDR, 1) - return data[0] - - def read_electronic_id(self): - """ reading electronic identifier """ - self.i2c.writeto(SI7006A20_I2C_ADDR, bytearray([0xFA]) + bytearray([0x0F])) - time.sleep(0.5) - sna = self.i2c.readfrom(SI7006A20_I2C_ADDR, 4) - time.sleep(0.1) - self.i2c.writeto(SI7006A20_I2C_ADDR, bytearray([0xFC]) + bytearray([0xC9])) - time.sleep(0.5) - snb = self.i2c.readfrom(SI7006A20_I2C_ADDR, 4) - return [sna[0], sna[1], sna[2], sna[3], snb[0], snb[1], snb[2], snb[3]] - - def read_firmware(self): - """ reading firmware version """ - self.i2c.writeto(SI7006A20_I2C_ADDR, bytearray([0x84])+ bytearray([0xB8])) - time.sleep(0.5) - fw = self.i2c.readfrom(SI7006A20_I2C_ADDR, 1) - return fw[0] - - def read_reg(self, reg_addr): - """ reading a register """ - self.i2c.writeto(SI7006A20_I2C_ADDR, bytearray([reg_addr])) - time.sleep(0.5) - data = self.i2c.readfrom(SI7006A20_I2C_ADDR, 1) - return data[0] - - def write_reg(self, reg_addr, value): - """ writing a register """ - self.i2c.writeto(SI7006A20_I2C_ADDR, bytearray([reg_addr])+bytearray([value])) - time.sleep(0.1) - - def dew_point(self): - """ computing the dew pointe temperature (deg C) for the current Temperature and Humidity measured pair - at dew-point temperature the relative humidity is 100% """ - temp = self.temperature() - humid = self.humidity() - h = (math.log(humid, 10) - 2) / 0.4343 + (17.62 * temp) / (243.12 + temp) - dew_p = 243.12 * h / (17.62 - h) - return dew_p - - def humid_ambient(self, t_ambient, dew_p = None): - """ returns the relative humidity compensated for the current Ambient temperature - for ex: T-Ambient is 24.4 degC, but sensor indicates Temperature = 31.65 degC and Humidity = 47.3% - -> then the actual Relative Humidity is 72.2% - this is computed because the dew-point should be the same """ - if dew_p is None: - dew_p = self.dew_point() - h = 17.62 * dew_p / (243.12 + dew_p) - h_ambient = math.pow(10, (h - (17.62 * t_ambient) / (243.12 + t_ambient)) * 0.4343 + 2) - return h_ambient diff --git a/pysense/lib/pysense.py b/pysense/lib/pysense.py deleted file mode 100644 index 04d31e7..0000000 --- a/pysense/lib/pysense.py +++ /dev/null @@ -1,18 +0,0 @@ -#!/usr/bin/env python -# -# Copyright (c) 2019, Pycom Limited. -# -# This software is licensed under the GNU GPL version 3 or any -# later version, with permitted additional terms. For more information -# see the Pycom Licence v1.0 document supplied with this file, or -# available at https://www.pycom.io/opensource/licensing -# - -from pycoproc import Pycoproc - -__version__ = '1.4.0' - -class Pysense(Pycoproc): - - def __init__(self, i2c=None, sda='P22', scl='P21'): - Pycoproc.__init__(self, i2c, sda, scl) diff --git a/pytrack/lib/L76GNSS.py b/pytrack/lib/L76GNSS.py deleted file mode 100644 index 8b6fca6..0000000 --- a/pytrack/lib/L76GNSS.py +++ /dev/null @@ -1,96 +0,0 @@ -#!/usr/bin/env python -# -# Copyright (c) 2019, Pycom Limited. -# -# This software is licensed under the GNU GPL version 3 or any -# later version, with permitted additional terms. For more information -# see the Pycom Licence v1.0 document supplied with this file, or -# available at https://www.pycom.io/opensource/licensing -# - -from machine import Timer -import time -import gc -import binascii - - -class L76GNSS: - - GPS_I2CADDR = const(0x10) - - def __init__(self, pytrack=None, sda='P22', scl='P21', timeout=None): - if pytrack is not None: - self.i2c = pytrack.i2c - else: - from machine import I2C - self.i2c = I2C(0, mode=I2C.MASTER, pins=(sda, scl)) - - self.chrono = Timer.Chrono() - - self.timeout = timeout - self.timeout_status = True - - self.reg = bytearray(1) - self.i2c.writeto(GPS_I2CADDR, self.reg) - - def _read(self): - self.reg = self.i2c.readfrom(GPS_I2CADDR, 64) - return self.reg - - def _convert_coords(self, gngll_s): - lat = gngll_s[1] - lat_d = (float(lat) // 100) + ((float(lat) % 100) / 60) - lon = gngll_s[3] - lon_d = (float(lon) // 100) + ((float(lon) % 100) / 60) - if gngll_s[2] == 'S': - lat_d *= -1 - if gngll_s[4] == 'W': - lon_d *= -1 - return(lat_d, lon_d) - - def coordinates(self, debug=False): - lat_d, lon_d, debug_timeout = None, None, False - if self.timeout is not None: - self.chrono.reset() - self.chrono.start() - nmea = b'' - while True: - if self.timeout is not None and self.chrono.read() >= self.timeout: - self.chrono.stop() - chrono_timeout = self.chrono.read() - self.chrono.reset() - self.timeout_status = False - debug_timeout = True - if not self.timeout_status: - gc.collect() - break - nmea += self._read().lstrip(b'\n\n').rstrip(b'\n\n') - gngll_idx = nmea.find(b'GNGLL') - gpgll_idx = nmea.find(b'GPGLL') - if gngll_idx < 0 and gpgll_idx >= 0: - gngll_idx = gpgll_idx - if gngll_idx >= 0: - gngll = nmea[gngll_idx:] - e_idx = gngll.find(b'\r\n') - if e_idx >= 0: - try: - gngll = gngll[:e_idx].decode('ascii') - gngll_s = gngll.split(',') - lat_d, lon_d = self._convert_coords(gngll_s) - except Exception: - pass - finally: - nmea = nmea[(gngll_idx + e_idx):] - gc.collect() - break - else: - gc.collect() - if len(nmea) > 410: # i suppose it can be safely changed to 82, which is longest NMEA frame - nmea = nmea[-5:] # $GNGL without last L - time.sleep(0.1) - self.timeout_status = True - if debug and debug_timeout: - print('GPS timed out after %f seconds' % (chrono_timeout)) - return(None, None) - else: - return(lat_d, lon_d) diff --git a/pytrack/lib/LIS2HH12.py b/pytrack/lib/LIS2HH12.py deleted file mode 100644 index 49a6b5a..0000000 --- a/pytrack/lib/LIS2HH12.py +++ /dev/null @@ -1,178 +0,0 @@ -#!/usr/bin/env python -# -# Copyright (c) 2019, Pycom Limited. -# -# This software is licensed under the GNU GPL version 3 or any -# later version, with permitted additional terms. For more information -# see the Pycom Licence v1.0 document supplied with this file, or -# available at https://www.pycom.io/opensource/licensing -# - -import math -import time -import struct -from machine import Pin - - -FULL_SCALE_2G = const(0) -FULL_SCALE_4G = const(2) -FULL_SCALE_8G = const(3) - -ODR_POWER_DOWN = const(0) -ODR_10_HZ = const(1) -ODR_50_HZ = const(2) -ODR_100_HZ = const(3) -ODR_200_HZ = const(4) -ODR_400_HZ = const(5) -ODR_800_HZ = const(6) - -ACC_G_DIV = 1000 * 65536 - - -class LIS2HH12: - - ACC_I2CADDR = const(30) - - PRODUCTID_REG = const(0x0F) - CTRL1_REG = const(0x20) - CTRL2_REG = const(0x21) - CTRL3_REG = const(0x22) - CTRL4_REG = const(0x23) - CTRL5_REG = const(0x24) - ACC_X_L_REG = const(0x28) - ACC_X_H_REG = const(0x29) - ACC_Y_L_REG = const(0x2A) - ACC_Y_H_REG = const(0x2B) - ACC_Z_L_REG = const(0x2C) - ACC_Z_H_REG = const(0x2D) - ACT_THS = const(0x1E) - ACT_DUR = const(0x1F) - - SCALES = {FULL_SCALE_2G: 4000, FULL_SCALE_4G: 8000, FULL_SCALE_8G: 16000} - ODRS = [0, 10, 50, 100, 200, 400, 800] - - def __init__(self, pysense = None, sda = 'P22', scl = 'P21'): - if pysense is not None: - self.i2c = pysense.i2c - else: - from machine import I2C - self.i2c = I2C(0, mode=I2C.MASTER, pins=(sda, scl)) - - self.odr = 0 - self.full_scale = 0 - self.x = 0 - self.y = 0 - self.z = 0 - self.int_pin = None - self.act_dur = 0 - self.debounced = False - - whoami = self.i2c.readfrom_mem(ACC_I2CADDR , PRODUCTID_REG, 1) - if (whoami[0] != 0x41): - raise ValueError("LIS2HH12 not found") - - # enable acceleration readings at 50Hz - self.set_odr(ODR_50_HZ) - - # change the full-scale to 4g - self.set_full_scale(FULL_SCALE_4G) - - # set the interrupt pin as active low and open drain - self.set_register(CTRL5_REG, 3, 0, 3) - - # make a first read - self.acceleration() - - def acceleration(self): - x = self.i2c.readfrom_mem(ACC_I2CADDR , ACC_X_L_REG, 2) - self.x = struct.unpack(' self.SCALES[self.full_scale]: - error = "threshold %d exceeds full scale %d" % (thresold, self.SCALES[self.full_scale]) - print(error) - raise ValueError(error) - - if threshold < self.SCALES[self.full_scale] / 128: - error = "threshold %d below resolution %d" % (thresold, self.SCALES[self.full_scale]/128) - print(error) - raise ValueError(error) - - if duration > 255 * 1000 * 8 / self.ODRS[self.odr]: - error = "duration %d exceeds max possible value %d" % (duration, 255 * 1000 * 8 / self.ODRS[self.odr]) - print(error) - raise ValueError(error) - - if duration < 1000 * 8 / self.ODRS[self.odr]: - error = "duration %d below resolution %d" % (duration, 1000 * 8 / self.ODRS[self.odr]) - print(error) - raise ValueError(error) - - _ths = int(127 * threshold / self.SCALES[self.full_scale]) & 0x7F - _dur = int((duration * self.ODRS[self.odr]) / 1000 / 8) - - self.i2c.writeto_mem(ACC_I2CADDR, ACT_THS, _ths) - self.i2c.writeto_mem(ACC_I2CADDR, ACT_DUR, _dur) - - # enable the activity/inactivity interrupt - self.set_register(CTRL3_REG, 1, 5, 1) - - self._user_handler = handler - self.int_pin = Pin('P13', mode=Pin.IN) - self.int_pin.callback(trigger=Pin.IRQ_FALLING | Pin.IRQ_RISING, handler=self._int_handler) - - # return actual used thresold and duration - return (_ths * self.SCALES[self.full_scale] / 128, _dur * 8 * 1000 / self.ODRS[self.odr]) - - def activity(self): - if not self.debounced: - time.sleep_ms(self.act_dur) - self.debounced = True - if self.int_pin(): - return True - return False - - def _int_handler(self, pin_o): - if self._user_handler is not None: - self._user_handler(pin_o) - else: - if pin_o(): - print('Activity interrupt') - else: - print('Inactivity interrupt') diff --git a/pytrack/lib/pytrack.py b/pytrack/lib/pytrack.py deleted file mode 100644 index 3e4d39b..0000000 --- a/pytrack/lib/pytrack.py +++ /dev/null @@ -1,18 +0,0 @@ -#!/usr/bin/env python -# -# Copyright (c) 2019, Pycom Limited. -# -# This software is licensed under the GNU GPL version 3 or any -# later version, with permitted additional terms. For more information -# see the Pycom Licence v1.0 document supplied with this file, or -# available at https://www.pycom.io/opensource/licensing -# - -from pycoproc import Pycoproc - -__version__ = '1.4.0' - -class Pytrack(Pycoproc): - - def __init__(self, i2c=None, sda='P22', scl='P21'): - Pycoproc.__init__(self, i2c, sda, scl) diff --git a/shields/README.md b/shields/README.md new file mode 100644 index 0000000..0586192 --- /dev/null +++ b/shields/README.md @@ -0,0 +1,13 @@ +

+ +# Pytrack, Pysense and Pyscan libraries + +## Introduction +This directory contains libraries and out of the box examples for Pysense, Pytrack, and Pyscan, both versions 1 and 2.0x of these shields. + +1. Upload all the files to your module +2. Open Pymakr +3. Run an example that matches to your shield + +Note: For using Pyscan, you need to upload either MFRC630.mpy or MFRC630.py. +It is always recommended to use MFRC630.mpy as it takes less space on your module. diff --git a/pybytes/pytrack/lib/L76GNSS.py b/shields/lib/L76GNSS.py similarity index 76% rename from pybytes/pytrack/lib/L76GNSS.py rename to shields/lib/L76GNSS.py index 8b6fca6..c493eb1 100644 --- a/pybytes/pytrack/lib/L76GNSS.py +++ b/shields/lib/L76GNSS.py @@ -18,7 +18,7 @@ class L76GNSS: GPS_I2CADDR = const(0x10) - def __init__(self, pytrack=None, sda='P22', scl='P21', timeout=None): + def __init__(self, pytrack=None, sda='P22', scl='P21', timeout=None, buffer=64): if pytrack is not None: self.i2c = pytrack.i2c else: @@ -29,12 +29,13 @@ def __init__(self, pytrack=None, sda='P22', scl='P21', timeout=None): self.timeout = timeout self.timeout_status = True + self.buffer = buffer self.reg = bytearray(1) self.i2c.writeto(GPS_I2CADDR, self.reg) def _read(self): - self.reg = self.i2c.readfrom(GPS_I2CADDR, 64) + self.reg = self.i2c.readfrom(GPS_I2CADDR, self.buffer) return self.reg def _convert_coords(self, gngll_s): @@ -94,3 +95,25 @@ def coordinates(self, debug=False): return(None, None) else: return(lat_d, lon_d) + + def dump_nmea(self): + nmea = b'' + while True: + nmea = self._read().lstrip(b'\n\n').rstrip(b'\n\n') + start_idx = nmea.find(b'$') + #print('raw[{}]: {}'.format(start_idx, nmea)) + if nmea is not None and len(nmea) > 0: + if start_idx != 0: + if len(nmea[:start_idx]) > 1: + print('{}'.format(nmea[:start_idx].decode('ASCII')), end='') + if len(nmea[start_idx:]) > 1: + print('{}'.format(nmea[start_idx:].decode('ASCII')), end='') + + def _checksum(self, nmeadata): + calc_cksum = 0 + for s in nmeadata: + calc_cksum ^= ord(s) + return('{:02X}'.format(calc_cksum)) + + def write(self, data): + self.i2c.writeto(GPS_I2CADDR, '${}*{}\r\n'.format(data, self._checksum(data)) ) diff --git a/pysense/lib/LIS2HH12.py b/shields/lib/LIS2HH12.py similarity index 99% rename from pysense/lib/LIS2HH12.py rename to shields/lib/LIS2HH12.py index 0dfefe8..fffa757 100644 --- a/pysense/lib/LIS2HH12.py +++ b/shields/lib/LIS2HH12.py @@ -1,6 +1,6 @@ #!/usr/bin/env python # -# Copyright (c) 2019, Pycom Limited. +# Copyright (c) 2020, Pycom Limited. # # This software is licensed under the GNU GPL version 3 or any # later version, with permitted additional terms. For more information diff --git a/pysense/lib/LTR329ALS01.py b/shields/lib/LTR329ALS01.py similarity index 64% rename from pysense/lib/LTR329ALS01.py rename to shields/lib/LTR329ALS01.py index 49e3439..8915ac4 100644 --- a/pysense/lib/LTR329ALS01.py +++ b/shields/lib/LTR329ALS01.py @@ -1,6 +1,6 @@ #!/usr/bin/env python # -# Copyright (c) 2019, Pycom Limited. +# Copyright (c) 2020, Pycom Limited. # # This software is licensed under the GNU GPL version 3 or any # later version, with permitted additional terms. For more information @@ -28,6 +28,14 @@ class LTR329ALS01: ALS_GAIN_8X = const(0x03) ALS_GAIN_48X = const(0x06) ALS_GAIN_96X = const(0x07) + ALS_GAIN_VALUES = { + ALS_GAIN_1X: 1, + ALS_GAIN_2X: 2, + ALS_GAIN_4X: 4, + ALS_GAIN_8X: 8, + ALS_GAIN_48X: 48, + ALS_GAIN_96X: 96 + } ALS_INT_50 = const(0x01) ALS_INT_100 = const(0x00) @@ -37,6 +45,16 @@ class LTR329ALS01: ALS_INT_300 = const(0x06) ALS_INT_350 = const(0x07) ALS_INT_400 = const(0x03) + ALS_INT_VALUES = { + ALS_INT_50: 0.5, + ALS_INT_100: 1, + ALS_INT_150: 1.5, + ALS_INT_200: 2, + ALS_INT_250: 2.5, + ALS_INT_300: 3, + ALS_INT_350: 3.5, + ALS_INT_400: 4 + } ALS_RATE_50 = const(0x00) ALS_RATE_100 = const(0x01) @@ -51,6 +69,9 @@ def __init__(self, pysense = None, sda = 'P22', scl = 'P21', gain = ALS_GAIN_1X, else: self.i2c = I2C(0, mode=I2C.MASTER, pins=(sda, scl)) + self.gain = gain + self.integration = integration + contr = self._getContr(gain) self.i2c.writeto_mem(ALS_I2CADDR, ALS_CONTR_REG, bytearray([contr])) @@ -78,3 +99,19 @@ def light(self): data0 = int(self._getWord(ch0high[0], ch0low[0])) return (data0, data1) + + def lux(self): + # Calculate Lux value from formular in Appendix A of the datasheet + light_level = self.light() + if light_level[0]+light_level[1] > 0: + ratio = light_level[1]/(light_level[0]+light_level[1]) + if ratio < 0.45: + return (1.7743 * light_level[0] + 1.1059 * light_level[1]) / self.ALS_GAIN_VALUES[self.gain] / self.ALS_INT_VALUES[self.integration] + elif ratio < 0.64 and ratio >= 0.45: + return (4.2785 * light_level[0] - 1.9548 * light_level[1]) / self.ALS_GAIN_VALUES[self.gain] / self.ALS_INT_VALUES[self.integration] + elif ratio < 0.85 and ratio >= 0.64: + return (0.5926 * light_level[0] + 0.1185 * light_level[1]) / self.ALS_GAIN_VALUES[self.gain] / self.ALS_INT_VALUES[self.integration] + else: + return 0 + else: + return 0 diff --git a/shields/lib/MFRC630.mpy b/shields/lib/MFRC630.mpy new file mode 100644 index 0000000..f6af944 Binary files /dev/null and b/shields/lib/MFRC630.mpy differ diff --git a/pyscan/lib/MFRC630.mpy b/shields/lib/MFRC630.mpy-1.18 similarity index 99% rename from pyscan/lib/MFRC630.mpy rename to shields/lib/MFRC630.mpy-1.18 index 51076eb..7baa005 100644 Binary files a/pyscan/lib/MFRC630.mpy and b/shields/lib/MFRC630.mpy-1.18 differ diff --git a/lib/MFRC630/MFRC630.py b/shields/lib/MFRC630.py similarity index 97% rename from lib/MFRC630/MFRC630.py rename to shields/lib/MFRC630.py index dc4aff8..0d6c2f2 100644 --- a/lib/MFRC630/MFRC630.py +++ b/shields/lib/MFRC630.py @@ -370,7 +370,7 @@ def mfrc630_MF_read_block(self, block_address, dest): buffer_length = self.mfrc630_fifo_length() rx_len = buffer_length if (buffer_length <= 16) else 16 dest = self.mfrc630_read_fifo(rx_len) - return rx_len + return dest def mfrc630_iso14443a_WUPA_REQA(self, instruction): @@ -702,7 +702,7 @@ def mfrc630_iso14443a_select(self, uid): uid[(cascade_level - 1) * 3 + UIDn] = uid_this_level[UIDn]; # Finally, return the length of the UID that's now at the uid "pointer". - return cascade_level * 3 + 1; + return cascade_level * 3 + 1 self.print_debug("Exit cascade loop nr. %d: " % cascade_level) self.mfrc630_print_block(uid, 10) diff --git a/pybytes/pysense/lib/MPL3115A2.py b/shields/lib/MPL3115A2.py similarity index 91% rename from pybytes/pysense/lib/MPL3115A2.py rename to shields/lib/MPL3115A2.py index 28a4297..ce90631 100644 --- a/pybytes/pysense/lib/MPL3115A2.py +++ b/shields/lib/MPL3115A2.py @@ -1,6 +1,6 @@ #!/usr/bin/env python # -# Copyright (c) 2019, Pycom Limited. +# Copyright (c) 2020, Pycom Limited. # # This software is licensed under the GNU GPL version 3 or any # later version, with permitted additional terms. For more information @@ -79,8 +79,10 @@ def __init__(self, pysense = None, sda = 'P22', scl = 'P21', mode = PRESSURE): raise MPL3115A2exception("Error with MPL3115A2") def _read_status(self): - while True: + read_attempts = 0 + while read_attempts < 500: self.i2c.readfrom_mem_into(MPL3115_I2CADDR, MPL3115_STATUS, self.STA_reg) + read_attempts += 1 if(self.STA_reg[0] == 0): time.sleep(0.01) @@ -90,6 +92,11 @@ def _read_status(self): else: return False + # If we get here the sensor isn't responding. Reset it so next time in it should work + self.i2c.writeto_mem(MPL3115_I2CADDR, MPL3115_CTRL_REG1, bytes([0x00])) # put into standby + self.i2c.writeto_mem(MPL3115_I2CADDR, MPL3115_CTRL_REG1, bytes([0x04])) # reset + return False + def pressure(self): if self.mode == ALTITUDE: raise MPL3115A2exception("Incorrect Measurement Mode MPL3115A2") diff --git a/pybytes/pysense/lib/SI7006A20.py b/shields/lib/SI7006A20.py similarity index 79% rename from pybytes/pysense/lib/SI7006A20.py rename to shields/lib/SI7006A20.py index d9a4025..f49a302 100644 --- a/pybytes/pysense/lib/SI7006A20.py +++ b/shields/lib/SI7006A20.py @@ -22,8 +22,18 @@ class SI7006A20: SI7006A20_I2C_ADDR = const(0x40) + # I2C commands TEMP_NOHOLDMASTER = const(0xF3) HUMD_NOHOLDMASTER = const(0xF5) + WRITE_USER_REG1 = const(0xE6) + READ_USER_REG1 = const(0xE7) + WRITE_HEATER_CTRL_REG = const(0x51) + READ_HEATER_CTRL_REG = const(0x11) + + # Register masks and offsets + USER_REG1_HTR_ENABLE_MASK = const(0b00000100) + USER_REG1_HTR_ENABLE_OFFSET = const(0x02) + HTR_CTRL_REG_MASK = const(0b00001111) def __init__(self, pysense = None, sda = 'P22', scl = 'P21'): if pysense is not None: @@ -55,18 +65,32 @@ def humidity(self): def read_user_reg(self): """ reading the user configuration register """ - self.i2c.writeto(SI7006A20_I2C_ADDR, bytearray([0xE7])) + self.i2c.writeto(SI7006A20_I2C_ADDR, bytearray([READ_USER_REG1])) time.sleep(0.5) data = self.i2c.readfrom(SI7006A20_I2C_ADDR, 1) return data[0] def read_heater_reg(self): """ reading the heater configuration register """ - self.i2c.writeto(SI7006A20_I2C_ADDR, bytearray([0x11])) + self.i2c.writeto(SI7006A20_I2C_ADDR, bytearray([READ_HEATER_CTRL_REG])) time.sleep(0.5) data = self.i2c.readfrom(SI7006A20_I2C_ADDR, 1) return data[0] + def write_heater_reg(self, heater_value): + """ writing the heater configuration register """ + # We should only set the bottom four bits of this register + heater_setting = heater_value & HTR_CTRL_REG_MASK + self.write_reg(WRITE_HEATER_CTRL_REG, heater_setting) + + def heater_control(self, on_off): + """ turn the heater on or off """ + # Get current settings for everything else + user_reg = self.read_user_reg() + # Set the heater bit + user_reg = (user_reg & ~USER_REG1_HTR_ENABLE_MASK) | (on_off << USER_REG1_HTR_ENABLE_OFFSET) + self.write_reg(WRITE_USER_REG1, user_reg) + def read_electronic_id(self): """ reading electronic identifier """ self.i2c.writeto(SI7006A20_I2C_ADDR, bytearray([0xFA]) + bytearray([0x0F])) diff --git a/pybytes/pytrack/lib/pycoproc.py b/shields/lib/pycoproc_1.py similarity index 92% rename from pybytes/pytrack/lib/pycoproc.py rename to shields/lib/pycoproc_1.py index 0b7772b..a8f7e82 100644 --- a/pybytes/pytrack/lib/pycoproc.py +++ b/shields/lib/pycoproc_1.py @@ -1,6 +1,6 @@ #!/usr/bin/env python # -# Copyright (c) 2019, Pycom Limited. +# Copyright (c) 2020, Pycom Limited. # # This software is licensed under the GNU GPL version 3 or any # later version, with permitted additional terms. For more information @@ -8,6 +8,8 @@ # available at https://www.pycom.io/opensource/licensing # +# See https://docs.pycom.io for more information regarding library specifics + from machine import Pin from machine import I2C import time @@ -26,6 +28,12 @@ class Pycoproc: I2C_SLAVE_ADDR = const(8) + PYSENSE = const(1) + PYTRACK = const(2) + PYSCAN = const(3) + + BOARD_TYPE_SET = (PYSENSE, PYTRACK, PYSCAN) + CMD_PEEK = const(0x0) CMD_POKE = const(0x01) CMD_MAGIC = const(0x02) @@ -81,14 +89,18 @@ class Pycoproc: EXP_RTC_PERIOD = const(7000) - def __init__(self, i2c=None, sda='P22', scl='P21'): + def __init__(self, board_type, i2c=None, sda='P22', scl='P21'): if i2c is not None: self.i2c = i2c else: self.i2c = I2C(0, mode=I2C.MASTER, pins=(sda, scl)) + if board_type not in self.BOARD_TYPE_SET: + raise Exception('Board type not in the set {}'.format(self.BOARD_TYPE_SET)) + self.sda = sda self.scl = scl + self.board_type = board_type self.clk_cal_factor = 1 self.reg = bytearray(6) self.wake_int = False @@ -115,7 +127,7 @@ def __init__(self, i2c=None, sda='P22', scl='P21'): self.mask_bits_in_memory(TRISC_ADDR, ~(1 << 7)) if self.read_fw_version() < 6: - raise ValueError('Firmware out of date') + raise ValueError('Firmware for Shield1 out of date') def _write(self, data, wait=True): @@ -202,11 +214,14 @@ def setup_sleep(self, time_s): self._write(bytes([CMD_SETUP_SLEEP, time_s & 0xFF, (time_s >> 8) & 0xFF, (time_s >> 16) & 0xFF])) def go_to_sleep(self, gps=True): - # enable or disable back-up power to the GPS receiver - if gps: + # if we have a Pytrack then enable or disable back-up power to the GPS receiver + if self.board_type == self.PYTRACK and gps: + # disable GPS only if Pytrack self.set_bits_in_memory(PORTC_ADDR, 1 << 7) else: + # Pysense or Pyscan or no GPS self.mask_bits_in_memory(PORTC_ADDR, ~(1 << 7)) + # disable the ADC self.poke_memory(ADCON0_ADDR, 0) diff --git a/shields/lib/pycoproc_2.py b/shields/lib/pycoproc_2.py new file mode 100644 index 0000000..a1ead51 --- /dev/null +++ b/shields/lib/pycoproc_2.py @@ -0,0 +1,381 @@ +#!/usr/bin/env python +# +# Copyright (c) 2020, Pycom Limited. +# +# This software is licensed under the GNU GPL version 3 or any +# later version, with permitted additional terms. For more information +# see the Pycom Licence v1.0 document supplied with this file, or +# available at https://www.pycom.io/opensource/licensing +# + +# See https://docs.pycom.io for more information regarding library specifics + +from machine import Pin +from machine import I2C +import time +import pycom + +__version__ = '0.0.5' + +""" PIC MCU wakeup reason types """ +WAKE_REASON_ACCELEROMETER = 1 +WAKE_REASON_PUSH_BUTTON = 2 +WAKE_REASON_TIMER = 4 +WAKE_REASON_INT_PIN = 8 + +class Pycoproc: + """ class for handling the interaction with PIC MCU """ + + I2C_SLAVE_ADDR = const(8) + + CMD_PEEK = const(0x0) + CMD_POKE = const(0x01) + CMD_MAGIC = const(0x02) + CMD_HW_VER = const(0x10) + CMD_FW_VER = const(0x11) #define SW_VERSION (15) + CMD_PROD_ID = const(0x12) #USB product ID, e.g. PYSENSE (0xF012) + CMD_SETUP_SLEEP = const(0x20) + CMD_GO_SLEEP = const(0x21) + CMD_CALIBRATE = const(0x22) + CMD_GO_NAP = const(0x23) + CMD_BAUD_CHANGE = const(0x30) + CMD_DFU = const(0x31) + CMD_RESET = const(0x40) + # CMD_GO_NAP options + SD_CARD_OFF = const(0x1) + SENSORS_OFF = const(0x2) + ACCELEROMETER_OFF = const(0x4) + FIPY_OFF = const(0x8) + + + REG_CMD = const(0) + REG_ADDRL = const(1) + REG_ADDRH = const(2) + REG_AND = const(3) + REG_OR = const(4) + REG_XOR = const(5) + + ANSELA_ADDR = const(0x18C) + ANSELB_ADDR = const(0x18D) + ANSELC_ADDR = const(0x18E) + + ADCON0_ADDR = const(0x9D) + ADCON1_ADDR = const(0x9E) + + IOCAP_ADDR = const(0x391) + IOCAN_ADDR = const(0x392) + + INTCON_ADDR = const(0x0B) + OPTION_REG_ADDR = const(0x95) + + _ADCON0_CHS_POSN = const(0x02) + _ADCON0_CHS_AN5 = const(0x5) # AN5 / RC1 + _ADCON0_CHS_AN6 = const(0x6) # AN6 / RC2 + _ADCON0_ADON_MASK = const(0x01) + _ADCON0_ADCS_F_OSC_64 = const(0x6) # A/D Conversion Clock + _ADCON0_GO_nDONE_MASK = const(0x02) + _ADCON1_ADCS_POSN = const(0x04) + + ADRESL_ADDR = const(0x09B) + ADRESH_ADDR = const(0x09C) + + TRISA_ADDR = const(0x08C) + TRISB_ADDR = const(0x08D) + TRISC_ADDR = const(0x08E) + + LATA_ADDR = const(0x10C) + LATB_ADDR = const(0x10D) + LATC_ADDR = const(0x10E) + + PORTA_ADDR = const(0x00C) + PORTB_ADDR = const(0x00C) + PORTC_ADDR = const(0x00E) + + WPUA_ADDR = const(0x20C) + + WAKE_REASON_ADDR = const(0x064C) + MEMORY_BANK_ADDR = const(0x0620) + + PCON_ADDR = const(0x096) + STATUS_ADDR = const(0x083) + + EXP_RTC_PERIOD = const(7000) + + USB_PID_PYSENSE = const(0xf012) + USB_PID_PYTRACK = const(0xf013) + + @staticmethod + def wake_up(): + # P9 is connected to RC1, make P9 an output + p9 = Pin("P9", mode=Pin.OUT) + # toggle rc1 to trigger wake up + p9(1) + time.sleep(0.1) + p9(0) + time.sleep(0.1) + + def __init__(self, i2c=None, sda='P22', scl='P21'): + if i2c is not None: + self.i2c = i2c + else: + self.i2c = I2C(0, mode=I2C.MASTER, pins=(sda, scl), baudrate=100000) + + self.sda = sda + self.scl = scl + self.clk_cal_factor = 1 + self.reg = bytearray(6) + + # Make sure we are inserted into the + # correct board and can talk to the PIC + retry = 0 + while True: + try: + self.read_fw_version() + break + except Exception as e: + if retry > 10: + raise Exception('Board not detected: {}'.format(e)) + print("Couldn't init Pycoproc. Maybe the PIC is still napping. Try to wake it. ({}, {})".format(retry, e)) + Pycoproc.wake_up() + # # p9 is connected to RC1, toggle it to wake PIC + # p9 = Pin("P9", mode=Pin.OUT) + # p9(1) + # time.sleep(0.1) + # p9(0) + # time.sleep(0.1) + # Pin("P9", mode=Pin.IN) + retry += 1 + + usb_pid=self.read_product_id() + if usb_pid != USB_PID_PYSENSE and usb_pid != USB_PID_PYTRACK: + raise ValueError('Not a Pysense2/Pytrack2 ({})'.format(hex(usb_pid))) + # for Pysense/Pytrack 2.0, the minimum firmware version is 15 + fw = self.read_fw_version() + if fw < 16: + raise ValueError('Firmware for Shield2 out of date', fw) + + # init the ADC for the battery measurements + self.write_byte(ANSELC_ADDR, 1 << 2) # RC2 analog input + self.write_byte(ADCON0_ADDR, (_ADCON0_CHS_AN6 << _ADCON0_CHS_POSN) | _ADCON0_ADON_MASK) # select analog channel and enable ADC + self.write_byte(ADCON1_ADDR, (_ADCON0_ADCS_F_OSC_64 << _ADCON1_ADCS_POSN)) # ADC conversion clock + + # enable the pull-up on RA3 + self.write_byte(WPUA_ADDR, (1 << 3)) + + # set RC6 and RC7 as outputs + self.write_bit(TRISC_ADDR, 6, 0) # 3V3SENSOR_A, power to Accelerometer + self.write_bit(TRISC_ADDR, 7, 0) # PWR_CTRL power to other sensors + + # enable power to the sensors and the GPS + self.gps_standby(False) # GPS, RC4 + self.sensor_power() # PWR_CTRL, RC7 + self.sd_power() # LP_CTRL, RA5 + + + def _write(self, data, wait=True): + self.i2c.writeto(I2C_SLAVE_ADDR, data) + if wait: + self._wait() + + def _read(self, size): + return self.i2c.readfrom(I2C_SLAVE_ADDR, size + 1)[1:(size + 1)] + + def _wait(self): + count = 0 + time.sleep_us(10) + while self.i2c.readfrom(I2C_SLAVE_ADDR, 1)[0] != 0xFF: + time.sleep_us(100) + count += 1 + if (count > 500): # timeout after 50ms + raise Exception('Board timeout') + + def _send_cmd(self, cmd): + self._write(bytes([cmd])) + + def read_hw_version(self): + self._send_cmd(CMD_HW_VER) + d = self._read(2) + return (d[1] << 8) + d[0] + + def read_fw_version(self): + self._send_cmd(CMD_FW_VER) + d = self._read(2) + return (d[1] << 8) + d[0] + + def read_product_id(self): + self._send_cmd(CMD_PROD_ID) + d = self._read(2) + return (d[1] << 8) + d[0] + + def read_byte(self, addr): + self._write(bytes([CMD_PEEK, addr & 0xFF, (addr >> 8) & 0xFF])) + return self._read(1)[0] + + def write_byte(self, addr, value): + self._write(bytes([CMD_POKE, addr & 0xFF, (addr >> 8) & 0xFF, value & 0xFF])) + + def magic_write_read(self, addr, _and=0xFF, _or=0, _xor=0): + self._write(bytes([CMD_MAGIC, addr & 0xFF, (addr >> 8) & 0xFF, _and & 0xFF, _or & 0xFF, _xor & 0xFF])) + return self._read(1)[0] + + def toggle_bits_in_memory(self, addr, bits): + self.magic_write_read(addr, _xor=bits) + + def mask_bits_in_memory(self, addr, mask): + self.magic_write_read(addr, _and=mask) + + def set_bits_in_memory(self, addr, bits): + self.magic_write_read(addr, _or=bits) + + def read_bit(self, address, bit): + b = self.read_byte(address) + # print("{0:08b}".format(b)) + mask = (1<= 2**(8*3): + time_s = 2**(8*3)-1 + self._write(bytes([CMD_SETUP_SLEEP, time_s & 0xFF, (time_s >> 8) & 0xFF, (time_s >> 16) & 0xFF])) + + def go_to_sleep(self, gps=True, pycom_module_off=True, accelerometer_off=True, wake_interrupt=False): + # enable or disable back-up power to the GPS receiver + self.gps_standby(gps) + + # disable the ADC + self.write_byte(ADCON0_ADDR, 0) + + # RC0, RC1, RC2, analog input + self.set_bits_in_memory(TRISC_ADDR, (1<<2) | (1<<1) | (1<<0) ) + self.set_bits_in_memory(ANSELC_ADDR, (1<<2) | (1<<1) | (1<<0) ) + + # RA4 analog input + self.set_bits_in_memory(TRISA_ADDR, (1<<4) ) + self.set_bits_in_memory(ANSELA_ADDR, (1<<4) ) + + # RB4, RB5 analog input + self.set_bits_in_memory(TRISB_ADDR, (1<<5) | (1<<4) ) + self.set_bits_in_memory(ANSELB_ADDR, (1<<5) | (1<<4) ) + + if wake_interrupt: + # print("enable wake up PIC from RC1") + self.set_bits_in_memory(OPTION_REG_ADDR, 1 << 6) # rising edge of INT pin + self.mask_bits_in_memory(ANSELC_ADDR, ~(1 << 1)) # disable analog function for RC1 pin + self.set_bits_in_memory(TRISC_ADDR, 1 << 1) # make RC1 input pin + self.mask_bits_in_memory(INTCON_ADDR, ~(1 << 1)) # clear INTF + self.set_bits_in_memory(INTCON_ADDR, 1 << 4) # enable interrupt; set INTE) + + nap_options = SD_CARD_OFF | SENSORS_OFF + if pycom_module_off: + nap_options |= FIPY_OFF + if accelerometer_off: + nap_options |= ACCELEROMETER_OFF + + # print("CMD_GO_NAP {0:08b}".format(nap_options)) + self._write(bytes([CMD_GO_NAP, nap_options]), wait=False) + + def calibrate_rtc(self): + # the 1.024 factor is because the PIC LF operates at 31 KHz + # WDT has a frequency divider to generate 1 ms + # and then there is a binary prescaler, e.g., 1, 2, 4 ... 512, 1024 ms + # hence the need for the constant + self._write(bytes([CMD_CALIBRATE]), wait=False) + self.i2c.deinit() + Pin('P21', mode=Pin.IN) + pulses = pycom.pulses_get('P21', 100) + self.i2c.init(mode=I2C.MASTER, pins=(self.sda, self.scl), baudrate=100000) + idx = 0 + for i in range(len(pulses)): + if pulses[i][1] > EXP_RTC_PERIOD: + idx = i + break + try: + period = pulses[idx][1] - pulses[(idx - 1)][1] + except: + period = 0 + if period > 0: + self.clk_cal_factor = (EXP_RTC_PERIOD / period) * (1000 / 1024) + if self.clk_cal_factor > 1.25 or self.clk_cal_factor < 0.75: + self.clk_cal_factor = 1 + time.sleep(0.5) + + def button_pressed(self): + retry = 0 + while True: + try: + button = self.read_bit(PORTA_ADDR, 3) + return not button + except Exception as e: + if retry > 10: + raise Exception('Failed to read button state: {}'.format(e)) + print("Failed to read button state, retry ... ({}, {})".format(retry, e)) + retry += 1 + + def read_battery_voltage(self): + self.set_bits_in_memory(ADCON0_ADDR, _ADCON0_GO_nDONE_MASK) + time.sleep_us(50) + while self.read_byte(ADCON0_ADDR) & _ADCON0_GO_nDONE_MASK: + time.sleep_us(100) + adc_val = (self.read_byte(ADRESH_ADDR) << 2) + (self.read_byte(ADRESL_ADDR) >> 6) + return (((adc_val * 3.3 * 280) / 1023) / 180) + 0.01 # add 10mV to compensate for the drop in the FET + + def gps_standby(self, enabled=True): + if enabled: + # make RC4 input + self.set_bits_in_memory(TRISC_ADDR, 1 << 4) + else: + # make RC4 an output + self.mask_bits_in_memory(TRISC_ADDR, ~(1 << 4)) + # drive RC4 high + self.set_bits_in_memory(PORTC_ADDR, 1 << 4) + time.sleep(0.2) + # drive RC4 low + self.mask_bits_in_memory(PORTC_ADDR, ~(1 << 4)) + time.sleep(0.2) + # drive RC4 high + self.set_bits_in_memory(PORTC_ADDR, 1 << 4) + time.sleep(0.2) + + def sensor_power(self, enabled=True): + # make RC7 an output + self.write_bit(TRISC_ADDR, 7, 0) + if enabled: + # drive RC7 high + self.write_bit(LATC_ADDR, 7, 1) + else: + # drive RC7 low + self.write_bit(LATC_ADDR, 7, 0) + + def sd_power(self, enabled=True): + # make RA5 an output + self.write_bit(TRISA_ADDR, 5, 0) + if enabled: + # drive RA5 high + self.write_bit(LATA_ADDR, 5, 1) + else: + # drive RA5 low + self.write_bit(LATA_ADDR, 5, 0) + + def reset_cmd(self): + self._send_cmd(CMD_RESET) + return diff --git a/pyscan/nfc.py b/shields/pyscan_1.py similarity index 63% rename from pyscan/nfc.py rename to shields/pyscan_1.py index d4ccc4f..9e9c263 100644 --- a/pyscan/nfc.py +++ b/shields/pyscan_1.py @@ -8,17 +8,33 @@ If authentication succeeds will attempt to read sectors from the card ''' -from pyscan import Pyscan +from pycoproc_1 import Pycoproc from MFRC630 import MFRC630 +from LIS2HH12 import LIS2HH12 +from LTR329ALS01 import LTR329ALS01 import time import pycom +#add your card UID here +VALID_CARDS = [[0x43, 0x95, 0xDD, 0xF8], + [0x43, 0x95, 0xDD, 0xF9], + [0x46, 0x5A, 0xEB, 0x7D, 0x8A, 0x08, 0x04]] + + # This is the default key for an unencrypted MiFare card CARDkey = [ 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF ] DECODE_CARD = False -py = Pyscan() +py = Pycoproc(Pycoproc.PYSCAN) nfc = MFRC630(py) +lt = LTR329ALS01(py) +li = LIS2HH12(py) + +pybytes_enabled = False +if 'pybytes' in globals(): + if(pybytes.isconnected()): + print('Pybytes is connected, sending signals to Pybytes') + pybytes_enabled = True RGB_BRIGHTNESS = 0x8 @@ -28,19 +44,32 @@ counter = 0 +def check_uid(uid, len): + return VALID_CARDS.count(uid[:len]) + +def send_sensor_data(name, timeout): + if(pybytes_enabled): + while(True): + pybytes.send_signal(2, lt.light()) + pybytes.send_signal(3, li.acceleration()) + time.sleep(timeout) + # Make sure heartbeat is disabled before setting RGB LED pycom.heartbeat(False) # Initialise the MFRC630 with some settings nfc.mfrc630_cmd_init() +print('Scanning for cards') while True: # Send REQA for ISO14443A card type atqa = nfc.mfrc630_iso14443a_WUPA_REQA(nfc.MFRC630_ISO14443_CMD_REQA) if (atqa != 0): # A card has been detected, read UID + print('A card has been detected, reading its UID ...') uid = bytearray(10) uid_len = nfc.mfrc630_iso14443a_select(uid) + print('UID has length {}'.format(uid_len)) if (uid_len > 0): # A valid UID has been detected, print details counter += 1 @@ -64,8 +93,18 @@ # Although this is also handled by the reset / init cycle nfc.mfrc630_MF_deauth() else: - # If we're not trying to authenticate, show green when a UID > 0 has been detected - pycom.rgbled(RGB_GREEN) + #check if card uid is listed in VALID_CARDS + if (check_uid(list(uid), uid_len)) > 0: + print('Card is listed, turn LED green') + pycom.rgbled(RGB_GREEN) + if(pybytes_enabled): + pybytes.send_signal(1, ('Card is listed', uid)) + else: + print('Card is not listed, turn LED red') + pycom.rgbled(RGB_RED) + if(pybytes_enabled): + pybytes.send_signal(1, ('Unauthorized card detected', uid)) + else: pycom.rgbled(RGB_BLUE) # We could go into power saving mode here... to be investigated diff --git a/pysense/main.py b/shields/pysense_1.py similarity index 59% rename from pysense/main.py rename to shields/pysense_1.py index 15b29b3..2a4e126 100644 --- a/pysense/main.py +++ b/shields/pysense_1.py @@ -1,6 +1,6 @@ #!/usr/bin/env python # -# Copyright (c) 2019, Pycom Limited. +# Copyright (c) 2020, Pycom Limited. # # This software is licensed under the GNU GPL version 3 or any # later version, with permitted additional terms. For more information @@ -10,32 +10,62 @@ # See https://docs.pycom.io for more information regarding library specifics -from pysense import Pysense +import time +import pycom +from pycoproc_1 import Pycoproc +import machine + from LIS2HH12 import LIS2HH12 from SI7006A20 import SI7006A20 from LTR329ALS01 import LTR329ALS01 from MPL3115A2 import MPL3115A2,ALTITUDE,PRESSURE -py = Pysense() -mp = MPL3115A2(py,mode=ALTITUDE) # Returns height in meters. Mode may also be set to PRESSURE, returning a value in Pascals -si = SI7006A20(py) -lt = LTR329ALS01(py) -li = LIS2HH12(py) +pycom.heartbeat(False) +pycom.rgbled(0x0A0A08) # white + +py = Pycoproc(Pycoproc.PYSENSE) + +pybytes_enabled = False +if 'pybytes' in globals(): + if(pybytes.isconnected()): + print('Pybytes is connected, sending signals to Pybytes') + pybytes_enabled = True +mp = MPL3115A2(py,mode=ALTITUDE) # Returns height in meters. Mode may also be set to PRESSURE, returning a value in Pascals print("MPL3115A2 temperature: " + str(mp.temperature())) print("Altitude: " + str(mp.altitude())) mpp = MPL3115A2(py,mode=PRESSURE) # Returns pressure in Pa. Mode may also be set to ALTITUDE, returning a value in meters print("Pressure: " + str(mpp.pressure())) +si = SI7006A20(py) print("Temperature: " + str(si.temperature())+ " deg C and Relative Humidity: " + str(si.humidity()) + " %RH") print("Dew point: "+ str(si.dew_point()) + " deg C") t_ambient = 24.4 print("Humidity Ambient for " + str(t_ambient) + " deg C is " + str(si.humid_ambient(t_ambient)) + "%RH") + +lt = LTR329ALS01(py) print("Light (channel Blue lux, channel Red lux): " + str(lt.light())) +li = LIS2HH12(py) print("Acceleration: " + str(li.acceleration())) print("Roll: " + str(li.roll())) print("Pitch: " + str(li.pitch())) -print("Battery voltage: " + str(py.read_battery_voltage())) +# set your battery voltage limits here +vmax = 4.2 +vmin = 3.3 +battery_voltage = py.read_battery_voltage() +battery_percentage = (battery_voltage - vmin / (vmax - vmin))*100 +print("Battery voltage: " + str(py.read_battery_voltage()), " percentage: ", battery_percentage) +if(pybytes_enabled): + pybytes.send_signal(1, mpp.pressure()) + pybytes.send_signal(2, si.temperature()) + pybytes.send_signal(3, lt.light()) + pybytes.send_signal(4, li.acceleration()) + pybytes.send_battery_level(int(battery_percentage)) + print("Sent data to pybytes") + +time.sleep(5) +py.setup_sleep(10) +py.go_to_sleep() diff --git a/shields/pysense_2.py b/shields/pysense_2.py new file mode 100644 index 0000000..ff793e7 --- /dev/null +++ b/shields/pysense_2.py @@ -0,0 +1,72 @@ +#!/usr/bin/env python +# +# Copyright (c) 2020, Pycom Limited. +# +# This software is licensed under the GNU GPL version 3 or any +# later version, with permitted additional terms. For more information +# see the Pycom Licence v1.0 document supplied with this file, or +# available at https://www.pycom.io/opensource/licensing +# + +# See https://docs.pycom.io for more information regarding library specifics + +import time +import pycom +from pycoproc_2 import Pycoproc +import machine + +from LIS2HH12 import LIS2HH12 +from SI7006A20 import SI7006A20 +from LTR329ALS01 import LTR329ALS01 +from MPL3115A2 import MPL3115A2,ALTITUDE,PRESSURE + +pycom.heartbeat(False) +pycom.rgbled(0x0A0A08) # white + +py = Pycoproc() +if py.read_product_id() != Pycoproc.USB_PID_PYSENSE: + raise Exception('Not a Pysense') + +pybytes_enabled = False +if 'pybytes' in globals(): + if(pybytes.isconnected()): + print('Pybytes is connected, sending signals to Pybytes') + pybytes_enabled = True + +mp = MPL3115A2(py,mode=ALTITUDE) # Returns height in meters. Mode may also be set to PRESSURE, returning a value in Pascals +print("MPL3115A2 temperature: " + str(mp.temperature())) +print("Altitude: " + str(mp.altitude())) +mpp = MPL3115A2(py,mode=PRESSURE) # Returns pressure in Pa. Mode may also be set to ALTITUDE, returning a value in meters +print("Pressure: " + str(mpp.pressure())) + + +si = SI7006A20(py) +print("Temperature: " + str(si.temperature())+ " deg C and Relative Humidity: " + str(si.humidity()) + " %RH") +print("Dew point: "+ str(si.dew_point()) + " deg C") +t_ambient = 24.4 +print("Humidity Ambient for " + str(t_ambient) + " deg C is " + str(si.humid_ambient(t_ambient)) + "%RH") + + +lt = LTR329ALS01(py) +print("Light (channel Blue, channel Red): " + str(lt.light())," Lux: ", str(lt.lux()), "lx") + +li = LIS2HH12(py) +print("Acceleration: " + str(li.acceleration())) +print("Roll: " + str(li.roll())) +print("Pitch: " + str(li.pitch())) + +print("Battery voltage: " + str(py.read_battery_voltage())) + +# set your battery voltage limits here +vmax = 4.2 +vmin = 3.3 +battery_voltage = py.read_battery_voltage() +battery_percentage = (battery_voltage - vmin / (vmax - vmin))*100 +print("Battery voltage: " + str(py.read_battery_voltage()), " percentage: ", battery_percentage) +if(pybytes_enabled): + pybytes.send_signal(1, mpp.pressure()) + pybytes.send_signal(2, si.temperature()) + pybytes.send_signal(3, lt.light()) + pybytes.send_signal(4, li.acceleration()) + pybytes.send_battery_level(int(battery_percentage)) + print("Sent data to pybytes") diff --git a/pytrack/main.py b/shields/pytrack_1.py similarity index 68% rename from pytrack/main.py rename to shields/pytrack_1.py index 171a0ba..cab1cb9 100644 --- a/pytrack/main.py +++ b/shields/pytrack_1.py @@ -1,6 +1,6 @@ #!/usr/bin/env python # -# Copyright (c) 2019, Pycom Limited. +# Copyright (c) 2020, Pycom Limited. # # This software is licensed under the GNU GPL version 3 or any # later version, with permitted additional terms. For more information @@ -18,7 +18,7 @@ from machine import RTC from machine import SD from L76GNSS import L76GNSS -from pytrack import Pytrack +from pycoproc_1 import Pycoproc time.sleep(2) gc.enable() @@ -31,9 +31,15 @@ utime.timezone(7200) print('Adjusted from UTC to EST timezone', utime.localtime(), '\n') -py = Pytrack() +py = Pycoproc(Pycoproc.PYTRACK) l76 = L76GNSS(py, timeout=30) +pybytes_enabled = False +if 'pybytes' in globals(): + if(pybytes.isconnected()): + print('Pybytes is connected, sending signals to Pybytes') + pybytes_enabled = True + # sd = SD() # os.mount(sd, '/sd') # f = open('/sd/gps-record.txt', 'w') @@ -42,3 +48,13 @@ coord = l76.coordinates() #f.write("{} - {}\n".format(coord, rtc.now())) print("{} - {} - {}".format(coord, rtc.now(), gc.mem_free())) + if(pybytes_enabled): + pybytes.send_signal(1, coord) + time.sleep(10) + +""" +# sleep procedure +time.sleep(3) +py.setup_sleep(10) +py.go_to_sleep() +""" diff --git a/pybytes/pytrack/main.py b/shields/pytrack_2.py similarity index 50% rename from pybytes/pytrack/main.py rename to shields/pytrack_2.py index 33361d6..52fe547 100644 --- a/pybytes/pytrack/main.py +++ b/shields/pytrack_2.py @@ -1,6 +1,6 @@ #!/usr/bin/env python # -# Copyright (c) 2019, Pycom Limited. +# Copyright (c) 2020, Pycom Limited. # # This software is licensed under the GNU GPL version 3 or any # later version, with permitted additional terms. For more information @@ -14,15 +14,15 @@ import os import time import utime +import gc +import pycom from machine import RTC from machine import SD -from machine import Timer from L76GNSS import L76GNSS -from pytrack import Pytrack -from LIS2HH12 import LIS2HH12 -# setup as a station +from pycoproc_2 import Pycoproc -import gc +pycom.heartbeat(False) +pycom.rgbled(0x0A0A08) # white time.sleep(2) gc.enable() @@ -34,18 +34,35 @@ print('\nRTC Set from NTP to UTC:', rtc.now()) utime.timezone(7200) print('Adjusted from UTC to EST timezone', utime.localtime(), '\n') -py = Pytrack() -l76 = L76GNSS(py, timeout=30) -chrono = Timer.Chrono() -chrono.start() -li = LIS2HH12(py) -#sd = SD() -#os.mount(sd, '/sd') -#f = open('/sd/gps-record.txt', 'w') -while (pybytes): + +py = Pycoproc() +if py.read_product_id() != Pycoproc.USB_PID_PYTRACK: + raise Exception('Not a Pytrack') + +time.sleep(1) +l76 = L76GNSS(py, timeout=30, buffer=512) + +pybytes_enabled = False +if 'pybytes' in globals(): + if(pybytes.isconnected()): + print('Pybytes is connected, sending signals to Pybytes') + pybytes_enabled = True + +# sd = SD() +# os.mount(sd, '/sd') +# f = open('/sd/gps-record.txt', 'w') + +while (True): coord = l76.coordinates() #f.write("{} - {}\n".format(coord, rtc.now())) - print('Sending data') - pybytes.send_virtual_pin_value(True, 1, coord) - pybytes.send_virtual_pin_value(True, 2, li.acceleration()) + print("{} - {} - {}".format(coord, rtc.now(), gc.mem_free())) + if(pybytes_enabled): + pybytes.send_signal(1, coord) time.sleep(10) + +""" +# sleep procedure +time.sleep(3) +py.setup_sleep(10) +py.go_to_sleep() +""" diff --git a/shields/shield_2.py b/shields/shield_2.py new file mode 100644 index 0000000..8fb74e0 --- /dev/null +++ b/shields/shield_2.py @@ -0,0 +1,163 @@ +#!/usr/bin/env python +# +# Copyright (c) 2020, Pycom Limited. +# +# This software is licensed under the GNU GPL version 3 or any +# later version, with permitted additional terms. For more information +# see the Pycom Licence v1.0 document supplied with this file, or +# available at https://www.pycom.io/opensource/licensing +# + +# This script support Pysense 2 and Pytrack 2 +# It demonstrates two examples: +# * go to ultra low power mode (~10uA @3.75V) with all sensors, incl accelerometer and also pycom module (Fipy, Gpy, etc) off - tap the MCLR button for this +# * go to low power mode (~165uA @3.75V) with accelerometer on, pycom module in deepsleep and wake from accelerometer interrupt - hold the MCLR button down for this + +# See https://docs.pycom.io for more information regarding library specifics + +import time +import pycom +import struct +from machine import Pin +from pycoproc_2 import Pycoproc +import machine +from LIS2HH12 import LIS2HH12 + +def accelerometer(): + print("ACCELEROMETER:", "accel:", accelerometer_sensor.acceleration(), "roll:", accelerometer_sensor.roll(), "pitch:", accelerometer_sensor.pitch(), "x/y/z:", accelerometer_sensor.x, accelerometer_sensor.y, accelerometer_sensor.z ) + +def activity_int_handler(pin_o): + if pin_o(): + print('[Activity]') + pycom.rgbled(0x00000A) # blue + else: + print('[Inactivity]') + pycom.rgbled(0x0A0A00) # yellow + +def activity_int_handler_none(pin_o): + pass + +def blink(color=0x0a0a0a, ct=5, on_ms=100, off_ms=100 ): + while ct >= 0 : + ct -= 1 + pycom.rgbled(color) + time.sleep_ms(on_ms) + pycom.rgbled(0x000000) + time.sleep_ms(off_ms) + +def wait(color=0x0a0a0a, hold_timeout_ms=3000): + print(" - tap MCLR button to go to ultra low power mode (everything off)") + print(" - hold MCLR button down for", round(hold_timeout_ms/1000,1), "sec to go to low power mode and wake from accelerometer") + print("wait for button ...") + ct = 0 + pressed_time_ms = 0 + dot = '.' + while True: + if pycoproc.button_pressed(): + if pressed_time_ms == 0: + # the button just started to be pressed + pressed_time_ms = time.ticks_ms() + print("button pressed") + pycom.rgbled(color) + dot = '*' + else: + # the button is still being held down + if time.ticks_ms() - pressed_time_ms > hold_timeout_ms: + pycom.rgbled(0) + dot = '_' + else: + if pressed_time_ms != 0: + # the button was released + print("button released") + if time.ticks_ms() - pressed_time_ms > hold_timeout_ms: + return True + else: + return False + time.sleep(0.1) + ct += 1 + if ct % 10 == 0: + print(dot, end='') + +def pretty_reset_cause(): + mrc = machine.reset_cause() + print('reset_cause', mrc, end=' ') + if mrc == machine.PWRON_RESET: + print("PWRON_RESET") + # plug in + # press reset button on module + # reset button on JTAG board + # core dump + elif mrc == machine.HARD_RESET: + print("HARD_RESET") + elif mrc == machine.WDT_RESET: + print("WDT_RESET") + # machine.reset() + elif mrc == machine.DEEPSLEEP_RESET: + print("DEEPSLEEP_RESET") + # machine.deepsleep() + elif mrc == machine.SOFT_RESET: + print("SOFT_RESET") + # Ctrl-D + elif mrc == machine.BROWN_OUT_RESET: + print("BROWN_OUT_RESET") + +def pretty_wake_reason(): + mwr = machine.wake_reason() + print("wake_reason", mwr, end=' ') + if mwr[0] == machine.PWRON_WAKE: + print("PWRON_WAKE") + # reset button + elif mwr[0] == machine.PIN_WAKE: + print("PIN_WAKE") + elif mwr[0] == machine.RTC_WAKE: + print("RTC_WAKE") + # from deepsleep + elif mwr[0] == machine.ULP_WAKE: + print("ULP_WAKE") + + +############################################################### +sleep_time_s = 300 # 5 min +pycom.heartbeat(False) +pycom.rgbled(0x0a0a0a) # white +import binascii +import machine +print(os.uname().sysname.lower() + '-' + binascii.hexlify(machine.unique_id()).decode("utf-8")[-4:], "pysense2") + +pretty_wake_reason() +pretty_reset_cause() +print("pycoproc init") +pycoproc = Pycoproc() +print("battery {:.2f} V".format(pycoproc.read_battery_voltage())) + +# init accelerometer +accelerometer_sensor = LIS2HH12() +# read accelerometer sensor values +accelerometer() +print("enable accelerometer interrupt") + +# enable_activity_interrupt( [mG], [ms], callback) +# accelerometer_sensor.enable_activity_interrupt(8000, 200, activity_int_handler) # low sensitivty +# accelerometer_sensor.enable_activity_interrupt(2000, 200, activity_int_handler) # medium sensitivity +accelerometer_sensor.enable_activity_interrupt( 100, 200, activity_int_handler) # high sensitivity +# accelerometer_sensor.enable_activity_interrupt(63, 160, activity_int_handler) # ultra sensitivty + +if wait(0x0A000A): # purple + print("button was held") + blink(0x000a00) # green + print("enable pycom module to wake up from accelerometer interrupt") + wake_pins = [Pin('P13', mode=Pin.IN, pull=Pin.PULL_DOWN)] + machine.pin_sleep_wakeup(wake_pins, machine.WAKEUP_ANY_HIGH, True) + + print("put pycoproc to sleep and pycom module to deepsleep for", round(sleep_time_s/60,1), "minutes") + pycoproc.setup_sleep(sleep_time_s) + pycoproc.go_to_sleep(pycom_module_off=False, accelerometer_off=False, wake_interrupt=True) + machine.deepsleep(sleep_time_s * 1000) +else: + print("button was tapped") + blink(0x100600) # orange + print("put pycoproc to sleep and turn pycom module off for", round(sleep_time_s/60,1), "minutes") + pycoproc.setup_sleep(sleep_time_s) + pycoproc.go_to_sleep() + +print("we never reach here!")