Source code for findylab.database

"""Parameter database and ring buffers.

Both classes below are **verbatim** from the device firmware
(``firmware_core.py`` of Logger Pro and the findyIoT module). The parameter registry itself
(``databaseParams``, aka ``db.params``) lives in the user-editable
``last.py`` and is passed into the :class:`database` constructor - see
:doc:`/datatypes/system-ids` for the entry format and the full ID table.

Note:
    Methods reference the firmware module globals (``modem``, ``db``,
    ``device``, ``setError``...) exactly as on the device.
"""

import time
import utime
import gc
import struct

import machine
import btree


[docs] class database(object): """Persistent key/value storage on top of btree (the "mydb" file). Verbatim class from ``firmware_core.py``. Keys are HiGPS parameter IDs registered in the dict passed to the constructor - on a device that dict is ``databaseParams`` from the user-editable ``last.py``:: db = database(databaseParams) Values are stored as bytes; :meth:`read` also returns the encoded default for a registered-but-missing key. """
[docs] def __init__(self, params): """Loads the existing "mydb" btree file into RAM. Args: params (dict): The parameter registry (``databaseParams`` from ``last.py``); stored as the class-level ``database.params``. """ self.newdata = False #key = higpsID #param[0] = Allowed operations - R (read), W (read-write), E (execute) #param[1] = Type: 1 - number, 0 - string, 2 - boolean, 3- buffer 9 - function #param[2] = Storage: 0 - file system, 1 - RAM, 2 - Function #param[3] = Path = 0 - default / function name #param[4] = Default value database.params = params try: try: f = open("mydb", "r+b") except OSError: f = open("mydb", "w+b") db = btree.open(f) self.data = {} for key in db: #if data[key][1] == 0 #if key self.data[key] = db[key] db.flush() db.close() f.close() except Exception as e: setError(" Error in Database "+(str(e))) return False
[docs] def getParameterData(self,uri): """Returns the registry row for a parameter ID. Args: uri (str): Parameter ID, e.g. ``'421'``. Returns: list: The registry entry, or False for unknown IDs. """ try: #if return self.params[uri] except: return False
[docs] def getParameterByHiGPS(self,higpsId): """Returns the registry row for a HiGPS parameter ID. Args: higpsId (str): Parameter ID. Returns: list: The registry entry, or False for unknown IDs. """ print("in get parameter by HiGPS") print(higpsId) try: return self.params[higpsId] except: return False
[docs] def initDefaults(self): """Factory-initializes the database. Deletes an existing "mydb" (and resets so the next boot starts clean), reads the IMEI from the modem, then writes every storage-type-0 parameter's default value. """ import os if len(self.data) > 0: os.remove('mydb') print("old db removed") utime.sleep(5) machine.reset() else: print("no previous DB ") n = 0 imei = '2111111111111' while n< 3 and len(imei) != 15: imei = modem.getImei() n +=1 self.newdata = True for higpsID in self.params: if self.params[higpsID][4] != None and self.params[higpsID][2] == 0: if higpsID == "200": self.write(higpsID,imei) else: self.write(higpsID,self.params[higpsID][4])
[docs] def read(self,property,echo=True): """Reads a parameter value. Args: property (str or bytes): Parameter ID. echo (bool): Print the value while reading (debug aid). Returns: bytes: The stored value; the encoded default when the key is missing but registered; False for unknown IDs. """ praw = property try: if type(property) == str : property = str.encode(property) # Check presence BEFORE the echo print: printing self.data[property] # for a missing key raised KeyError, which the except below turned # into False - so the default from params was never returned and # callers crashed with "'bool' object has no attribute 'decode'". if property in self.data: if echo: print("Reading "+str(property)+ " = " + str(self.data[property])) return self.data[property] elif praw in self.params: # encode: stored values are bytes, so the default must be too return self.params[praw][4].encode('utf-8') else : return False except Exception as e: #print(e) return False
[docs] def write(self,property,value): """Writes a parameter value into RAM. int/str values are converted to bytes. Call :meth:`store` to persist to flash. Args: property (str or bytes): Parameter ID. value (int or str or bytes): New value. Returns: bool: True on success. """ self.newdata = True try: if type(value) == int: value = str(value) if type(value) == str: value = str.encode(value) if type(property) == str: property = str.encode(property) print("Writing "+str(property)+ " = " + str(value)) self.data[property] = value return True except: return False
[docs] def store(self): """Persists the RAM changes to the "mydb" btree file. No-op when nothing was written since the last store. Buffer-type parameters (type 3) also store their child slots (``key0``..``key99``). Returns: bool: True. """ if self.newdata == False: print("No data to write") return True #try: print("Database store started") try: f = open("mydb", "r+b") except OSError: f = open("mydb", "w+b") db = btree.open(f) for key in self.data: if len(key) <= 3: #Buffer Handling db[key] = self.data[key] if key.decode('utf-8') in self.params and self.params[key.decode('utf-8')][1] == 3: #print("In buffer") n = 0 while n< 100: #print("Buffer n ",n) key_child = key+str(n).encode() if key_child in self.data and len(self.data[key_child]) > 0: #print("Storing child ",key_child) db[key_child] = self.data[key_child] n +=1 #elif self.params[key.decode('utf-8')][2] == 0: db.flush() db.close() f.close() print("Database store completed") return True
[docs] class buffer(object): """Ring buffers over :class:`database` (slots ``"540"+n``). Verbatim class from ``firmware_core.py``. Never instantiated - the methods take no ``self`` and are called directly:: buffer.create('540') Used to keep pulse-count series between successful reports; the current slot index lives in parameters 538/539. """
[docs] def create(bufferName): #data = b'\x00' """Opens a fresh buffer slot. Finds a free slot (0..99), writes the header (log period + timestamp + base counter value) and records the slot index in parameter 538/539. Args: bufferName (str): ``'540'`` or ``'541'``. Returns: int: The slot index used. """ print("In create") timestamp = device.getTimestamp() print("Timestamp ", timestamp) period = int(db.read('421')) print("Period ", period) data = struct.pack("H", period) data += struct.pack("L", timestamp) if bufferName == "540": data += struct.pack('L',int(db.read('437'))) elif bufferName == "541": data += struct.pack('L',int(db.read('439'))) print("Sturct Data ",data) #data = period.to_bytes(2,'big')+timestamp.to_bytes(4,"big") c=0 done = False while c< 100: buff = bufferName + str(c) currentBuffer = db.read(buff) if currentBuffer == False or len(currentBuffer) < 2 : #Empty buffer db.write(buff,data) done = True break c+=1 if done == False: buff = bufferName + "0" db.write(buff,data) c = 0 if bufferName == '540': db.write("538",str(c)) elif bufferName == '541': db.write("539",str(c)) return c
[docs] def get(bufferName): """Returns the first full buffer slot. Args: bufferName (str): ``'540'`` or ``'541'``. Returns: tuple: ``(period, timestamp, base_value, data)`` or False when no slot holds data. """ c =0 while c < 100 : buff = bufferName + str(c) buffData = db.read(buff) if buffData and len(buffData) > 10: #Full buffer print("Buffer found ", c) print("Buffer data ", buffData) #timestamp = buffData[] period = struct.unpack("H",buffData[0:2] ) timestamp = struct.unpack("L", buffData[2:6] ) baseValue = struct.unpack("L", buffData[6:10] ) data = buffData[10:] print("Period ",period) print("Timestamp ",timestamp) return (period[0],timestamp[0],baseValue[0],data) c +=1 return False
[docs] def put(bufferName,data): """Appends bytes to the current buffer slot. Args: bufferName (str): ``'540'`` or ``'541'``. data (bytes): Payload to append (e.g. a 2-byte pulse delta). """ if bufferName == '540': c = db.read("538").decode('utf-8') elif bufferName == '541': c = db.read("539").decode('utf-8') buff = bufferName + str(c) print("Buffer ready ",c) buffData = db.read(buff) if buffData != False: bufferData = buffData + data db.write(buff,bufferData)
[docs] def remove(bufferName): """Clears the first full buffer slot. Args: bufferName (str): ``'540'`` or ``'541'``. Returns: bool: True when a slot was cleared. """ c =0 while c < 100 : buff = bufferName + str(c) buffData = db.read(buff) if buffData and len(buffData) > 10 : #Full buffer print("Buffer to remove ", c) print("Buffer data ", buffData) db.write(buff,"0") return True c +=1 return False