"""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