Files
HP_InHand_IG502/Pub_Sub/flowmeterskid/thingsboard/pub/sendData.py
2024-02-28 14:25:02 -06:00

405 lines
17 KiB
Python

import json, os, time, shutil
from datetime import datetime as dt
from common.Logger import logger
from quickfaas.remotebus import publish
from mobiuspi_lib.gps import GPS
from quickfaas.global_dict import get as get_params
from quickfaas.global_dict import _set_global_args
def reboot(reason="Rebooting for config file update"):
#basic = Basic()
logger.info("!" * 10 + "REBOOTING DEVICE" + "!"*10)
logger.info(reason)
r = os.popen("kill -s SIGHUP `cat /var/run/python/supervisord.pid`").read()
logger.info(f"REBOOT : {r}")
def checkFileExist(filename):
path = "/var/user/files"
try:
if not os.path.exists(path):
logger.debug("no folder making files folder in var/user")
os.makedirs(path)
with open(path + "/" + filename, "a") as f:
json.dump({}, f)
except Exception as e:
logger.error(f"Something went wrong in checkFileExist while making folder: {e}")
try:
if not os.path.exists(path + "/" + filename):
logger.debug("no creds file making creds file")
with open(path + "/" + filename, "a") as f:
json.dump({}, f)
except Exception as e:
logger.error(f"Something went wrong in checkFileExist wihle making file: {e}")
def convertDStoJSON(ds):
j = dict()
try:
for x in ds:
j[x["key"]] = x["value"]
except Exception as e:
logger.error(f"Something went wrong in convertDStoJSON: {e}")
return j
def convertJSONtoDS(j):
d = []
try:
for key in j.keys():
d.append({"key": key, "value": j[key]})
except Exception as e:
logger.error(f"Something went wrong in convertJSONtoDS: {e}")
return d
def checkCredentialConfig():
logger.debug("CHECKING CONFIG")
cfgpath = "/var/user/cfg/device_supervisor/device_supervisor.cfg"
credspath = "/var/user/files/creds.json"
cfg = dict()
with open(cfgpath, "r") as f:
try:
cfg = json.load(f)
clouds = cfg.get("clouds")
logger.debug(clouds)
#if not configured then try to configure from stored values
if clouds[0]["args"]["clientId"] == "unknown" or clouds[0]["args"]["username"] == "unknown" or not clouds[0]["args"]["passwd"] or clouds[0]["args"]["passwd"] == "unknown":
try:
checkFileExist("creds.json")
except Exception as e:
logger.error(f"Error in checkFileExist: {e}")
with open(credspath, "r") as c:
try:
creds = json.load(c)
if creds:
logger.debug("updating config with stored data")
clouds[0]["args"]["clientId"] = creds["clientId"]
clouds[0]["args"]["username"] = creds["userName"]
clouds[0]["args"]["passwd"] = creds["password"]
cfg["clouds"] = clouds
cfg = checkParameterConfig(cfg)
with open(cfgpath, "w", encoding='utf-8') as n:
json.dump(cfg, n, indent=1, ensure_ascii=False)
reboot()
except Exception as e:
logger.error(f"Error trying to load credentials from file: {e}")
else:
#assuming clouds is filled out, if data is different then assume someone typed in something new and store it, if creds is empty fill with clouds' data
checkFileExist("creds.json")
with open(credspath, "r") as c:
logger.debug("updating stored file with new data")
cfg = checkParameterConfig(cfg)
with open(cfgpath, "w", encoding='utf-8') as n:
json.dump(cfg, n, indent=1, ensure_ascii=False)
creds = json.load(c)
if creds:
if creds["clientId"] != clouds[0]["args"]["clientId"]:
creds["clientId"] = clouds[0]["args"]["clientId"]
if creds["userName"] != clouds[0]["args"]["username"]:
creds["userName"] = clouds[0]["args"]["username"]
if creds["password"] != clouds[0]["args"]["passwd"]:
creds["password"] = clouds[0]["args"]["passwd"]
else:
creds["clientId"] = clouds[0]["args"]["clientId"]
creds["userName"] = clouds[0]["args"]["username"]
creds["password"] = clouds[0]["args"]["passwd"]
with open(credspath, "w") as cw:
json.dump(creds,cw)
except Exception as e:
logger.error(f"Somethign went wrong in checkCredentialConfig: {e}")
def checkParameterConfig(cfg):
try:
logger.debug("Checking Parameters!!!!")
paramspath = "/var/user/files/params.json"
cfgparams = convertDStoJSON(cfg.get("labels"))
#check stored values
checkFileExist("params.json")
with open(paramspath, "r") as f:
logger.debug("Opened param storage file")
params = json.load(f)
if params:
if cfgparams != params:
#go through each param
#if not "unknown" and cfg and params aren't the same take from cfg likely updated manually
#if key in cfg but not in params copy to params
logger.debug("equalizing params between cfg and stored")
for key in cfgparams.keys():
try:
if cfgparams[key] != params[key] and cfgparams[key] != "unknown":
params[key] = cfgparams[key]
except:
params[key] = cfgparams[key]
cfg["labels"] = convertJSONtoDS(params)
_set_global_args(convertJSONtoDS(params))
with open(paramspath, "w") as p:
json.dump(params, p)
else:
with open(paramspath, "w") as p:
logger.debug("initializing param file with params in memory")
json.dump(convertDStoJSON(get_params()), p)
cfg["labels"] = get_params()
return cfg
except Exception as e:
logger.error(f"Something went wrong in checkParameterConfig: {e}")
os.system(f'rm {paramspath}')
return cfg
payload = {}
def initialize_totalizers():
return {
"day": 0,
"week": 0,
"month": 0,
"year": 0,
"lifetime": 0,
"dayHolding": 0,
"weekHolding": 0,
"monthHolding": 0,
"yearHolding": 0
}
def getTotalizers(file_path="/var/user/files/totalizers.json"):
"""
Retrieves totalizer data from a JSON file.
:param file_path: Path to the JSON file storing totalizer data.
:return: Dictionary containing totalizer values.
"""
try:
with open(file_path, "r") as t:
totalizers = json.load(t)
if not totalizers or not isinstance(totalizers, dict):
logger.info("Invalid data format in the file. Initializing totalizers.")
totalizers = initialize_totalizers()
except FileNotFoundError:
logger.info("File not found. Initializing totalizers.")
totalizers = initialize_totalizers()
except json.JSONDecodeError:
timestamp = dt.now().strftime("%Y%m%d_%H%M%S")
# Split the file path and insert the timestamp before the extension
file_name, file_extension = os.path.splitext(file_path)
backup_file_path = f"{file_name}_{timestamp}{file_extension}"
shutil.copyfile(file_path, backup_file_path)
logger.error(f"Error decoding JSON. A backup of the file is created at {backup_file_path}. Initializing totalizers.")
totalizers = initialize_totalizers()
return totalizers
# Helper function to split the payload into chunks
def chunk_payload(payload, chunk_size=20):
chunked_values = list(payload["values"].items())
for i in range(0, len(chunked_values), chunk_size):
yield {
"ts": payload["ts"],
"values": dict(chunked_values[i:i+chunk_size])
}
def sendData(message,wizard_api):
logger.debug(message)
checkCredentialConfig()
payload = {"ts": (round(dt.timestamp(dt.now())/600)*600)*1000, "values": {}}
resetPayload = {"ts": "", "values": {}}
dayReset, weekReset, monthReset, yearReset = False, False, False, False
for measure in message["values"]["flowmeter"].keys():
try:
if message["values"]["flowmeter"][measure]["status"] == 1:
if measure in ["totalizer_1"]:
payload["values"]["day_volume"], dayReset = totalizeDay(message["values"]["flowmeter"][measure]["raw_data"])
payload["values"]["week_volume"], weekReset = totalizeWeek(message["values"]["flowmeter"][measure]["raw_data"])
payload["values"]["month_volume"], monthReset = totalizeMonth(message["values"]["flowmeter"][measure]["raw_data"])
payload["values"]["year_volume"], yearReset = totalizeYear(message["values"]["flowmeter"][measure]["raw_data"])
payload["values"][measure] = message["values"]["flowmeter"][measure]["raw_data"]
except Exception as e:
logger.error(e)
try:
payload["values"]["latitude"], payload["values"]["longitude"], payload["values"]["speed"] = getGPS()
except:
logger.error("Could not get GPS coordinates")
for chunk in chunk_payload(payload=payload):
publish(__topic__, json.dumps(chunk), __qos__)
time.sleep(2)
publish("v1/devices/me/attributes", json.dumps({"latestReportTime": (round(dt.timestamp(dt.now())/600)*600)*1000}), __qos__)
if dayReset:
resetPayload["values"]["yesterday_volume"] = payload["values"]["day_volume"]
resetPayload["values"]["day_volume"] = 0
if weekReset:
resetPayload["values"]["last_week_volume"] = payload["values"]["week_volume"]
resetPayload["values"]["week_volume"] = 0
if monthReset:
resetPayload["values"]["last_month_volume"] = payload["values"]["month_volume"]
resetPayload["values"]["month_volume"] = 0
if yearReset:
resetPayload["values"]["last_year_volume"] = payload["values"]["year_volume"]
resetPayload["values"]["year_volume"] = 0
if resetPayload["values"]:
resetPayload["ts"] = 1 + (round(dt.timestamp(dt.now())/600)*600)*1000
publish(__topic__, json.dumps(resetPayload), __qos__)
def saveTotalizers(totalizers, file_path="/var/user/files/totalizers.json"):
"""
Saves totalizer data to a JSON file.
:param totalizers: Dictionary containing totalizer values to be saved.
:param file_path: Path to the JSON file where totalizer data will be saved.
"""
try:
with open(file_path, "w") as t:
json.dump(totalizers, t)
except (IOError, OSError, json.JSONEncodeError) as e:
logger.error(f"Error saving totalizers to {file_path}: {e}")
raise # Optionally re-raise the exception if it should be handled by the caller
def getGPS():
# Create a gps instance
gps = GPS()
# Retrieve GPS information
position_status = gps.get_position_status()
logger.debug("position_status: ")
logger.debug(position_status)
latitude = position_status["latitude"].split(" ")
longitude = position_status["longitude"].split(" ")
lat_dec = int(latitude[0][:-1]) + (float(latitude[1][:-1])/60)
lon_dec = int(longitude[0][:-1]) + (float(longitude[1][:-1])/60)
if latitude[2] == "S":
lat_dec = lat_dec * -1
if longitude[2] == "W":
lon_dec = lon_dec * -1
#lat_dec = round(lat_dec, 7)
#lon_dec = round(lon_dec, 7)
logger.info("HERE IS THE GPS COORDS")
logger.info(f"LATITUDE: {lat_dec}, LONGITUDE: {lon_dec}")
speedKnots = position_status["speed"].split(" ")
speedMPH = float(speedKnots[0]) * 1.151
return (f"{lat_dec:.8f}",f"{lon_dec:.8f}",f"{speedMPH:.2f}")
def totalizeDay(lifetime, max_retries=3, retry_delay=2):
"""
Update and save daily totalizers based on the lifetime value.
:param lifetime: The current lifetime total.
:param max_retries: Maximum number of save attempts.
:param retry_delay: Delay in seconds between retries.
:return: A tuple containing the calculated value and a boolean indicating if a reset occurred, or (None, False) if save fails.
"""
totalizers = getTotalizers()
now = dt.fromtimestamp(round(dt.timestamp(dt.now())/600)*600)
reset = False
value = lifetime - totalizers["dayHolding"]
if not int(now.strftime("%d")) == int(totalizers["day"]):
totalizers["dayHolding"] = lifetime
totalizers["day"] = int(now.strftime("%d"))
for attempt in range(max_retries):
try:
saveTotalizers(totalizers)
reset = True
return (value, reset)
except Exception as e:
logger.error(f"Attempt {attempt + 1} failed to save totalizers: {e}")
if attempt < max_retries - 1:
time.sleep(retry_delay)
else:
logger.error("All attempts to save totalizers failed.")
return (None, False)
return (value, reset)
def totalizeWeek(lifetime, max_retries=3, retry_delay=2):
"""
Update and save weekly totalizers based on the lifetime value.
:param lifetime: The current lifetime total.
:param max_retries: Maximum number of save attempts.
:param retry_delay: Delay in seconds between retries.
:return: A tuple containing the calculated value and a boolean indicating if a reset occurred, or (None, False) if save fails.
"""
totalizers = getTotalizers()
now = dt.fromtimestamp(round(dt.timestamp(dt.now())/600)*600)
reset = False
value = lifetime - totalizers["weekHolding"]
if (not now.strftime("%U") == totalizers["week"] and now.strftime("%a") == "Sun") or totalizers["week"] == 0:
totalizers["weekHolding"] = lifetime
totalizers["week"] = now.strftime("%U")
for attempt in range(max_retries):
try:
saveTotalizers(totalizers)
reset = True
return (value, reset)
except Exception as e:
logger.error(f"Attempt {attempt + 1} failed to save totalizers: {e}")
if attempt < max_retries - 1:
time.sleep(retry_delay)
else:
logger.error("All attempts to save totalizers failed.")
return (None, False)
return (value, reset)
def totalizeMonth(lifetime, max_retries=3, retry_delay=2):
"""
Update and save monthly totalizers based on the lifetime value.
:param lifetime: The current lifetime total.
:param max_retries: Maximum number of save attempts.
:param retry_delay: Delay in seconds between retries.
:return: A tuple containing the calculated value and a boolean indicating if a reset occurred, or (None, False) if save fails.
"""
totalizers = getTotalizers()
now = dt.fromtimestamp(round(dt.timestamp(dt.now())/600)*600)
reset = False
value = lifetime - totalizers["monthHolding"]
if not int(now.strftime("%m")) == int(totalizers["month"]):
totalizers["monthHolding"] = lifetime
totalizers["month"] = now.strftime("%m")
for attempt in range(max_retries):
try:
saveTotalizers(totalizers)
reset = True
return (value, reset)
except Exception as e:
logger.error(f"Attempt {attempt + 1} failed to save totalizers: {e}")
if attempt < max_retries - 1:
time.sleep(retry_delay)
else:
logger.error("All attempts to save totalizers failed.")
return (None, False)
return (value,reset)
def totalizeYear(lifetime, max_retries=3, retry_delay=2):
"""
Update and save yearly totalizers based on the lifetime value.
:param lifetime: The current lifetime total.
:param max_retries: Maximum number of save attempts.
:param retry_delay: Delay in seconds between retries.
:return: A tuple containing the calculated value and a boolean indicating if a reset occurred, or (None, False) if save fails.
"""
totalizers = getTotalizers()
now = dt.fromtimestamp(round(dt.timestamp(dt.now())/600)*600)
reset = False
value = lifetime - totalizers["yearHolding"]
if not int(now.strftime("%Y")) == int(totalizers["year"]):
totalizers["yearHolding"] = lifetime
totalizers["year"] = now.strftime("%Y")
for attempt in range(max_retries):
try:
saveTotalizers(totalizers)
reset = True
return (value, reset)
except Exception as e:
logger.error(f"Attempt {attempt + 1} failed to save totalizers: {e}")
if attempt < max_retries - 1:
time.sleep(retry_delay)
else:
logger.error("All attempts to save totalizers failed.")
return (None, False)
return (value, reset)