Index: cloud_sync.py =================================================================== diff -u -re2b5bba1ace2613e8cd3ca6d997756b1b61d77a4 -rae065dc96f33fc1946785ee833356dae959be2d9 --- cloud_sync.py (.../cloud_sync.py) (revision e2b5bba1ace2613e8cd3ca6d997756b1b61d77a4) +++ cloud_sync.py (.../cloud_sync.py) (revision ae065dc96f33fc1946785ee833356dae959be2d9) @@ -1,9 +1,7 @@ -"""Implementation of CloudSync controller""" - -import argparse -import logging -import socket +import os import sys +import logging +from logging.handlers import TimedRotatingFileHandler import werkzeug werkzeug.cached_property = werkzeug.utils.cached_property @@ -22,39 +20,51 @@ from cloudsync.utils.heartbeat import HeartBeatProvider from cloudsync.handlers.error import Error -VERSION = "0.4.4" +VERSION = "0.4.6" -try: - arguments = sys.argv - log_level = int(arguments[1]) +arguments = sys.argv +log_level = int(arguments[1]) - if not os.path.exists(CS_LOG_PATH): - os.makedirs(CS_LOG_PATH) +if not os.path.exists(CS_LOG_PATH): + os.makedirs(CS_LOG_PATH) - logging.basicConfig(filename=CS_LOG_FILE, - level=log_level, - format='[%(asctime)s]: %(levelname)s - %(message)s | {%(pathname)s:%(lineno)d}', - ) +app = Flask(__name__) +api = Api(app=app, version=VERSION, title="CloudSync Registration API", + description="Interface with DIA Manufacturing Tool * DCS") - app = Flask(__name__) - api = Api(app=app, version=VERSION, title="CloudSync Registration API", - description="Interface with DIA Manufacturing Tool * DCS") +# Remove existing handlers +for handler in app.logger.handlers: + app.logger.removeHandler(handler) - for handler in app.logger.handlers: - app.logger.removeHandler(handler) - handler = logging.FileHandler(CS_LOG_FILE) - handler.setLevel(log_level) - default_formatter = logging.Formatter('[%(asctime)s] %(levelname)s in %(module)s: %(message)s') - handler.setFormatter(default_formatter) +# Create a TimedRotatingFileHandler that writes logs to a new file every midnight, in UTC time +handler = TimedRotatingFileHandler(CS_LOG_FILE, when="midnight", interval=1, utc=True) +handler.suffix = "%m-%d-%Y" - app.logger.setLevel(log_level) +# Set the log level +handler.setLevel(log_level) - g_utils.add_logger(app.logger) +# Add a formatter +default_formatter = logging.Formatter('[%(asctime)s] %(levelname)s in %(module)s: %(message)s | {%(pathname)s:%(lineno)d}') +handler.setFormatter(default_formatter) +# Add the handler to the logger +app.logger.addHandler(handler) +app.logger.setLevel(log_level) + +# Get the root logger +root_logger = logging.getLogger() + +# Add the handlers to the root logger +root_logger.addHandler(handler) +root_logger.setLevel(log_level) + +g_utils.add_logger(app.logger) + +try: g_config = helpers_read_config(CONFIG_PATH) if g_config[CONFIG_DEVICE][CONFIG_DEVICE_MODE] == 'operation': - helpers_read_config(OPERATION_CONFIG_FILE_PATH) + g_config = helpers_read_config(OPERATION_CONFIG_FILE_PATH) CONFIG_PATH = OPERATION_CONFIG_FILE_PATH reachability_provider = ReachabilityProvider(logger=app.logger) @@ -68,10 +78,12 @@ network_request_handler = NetworkRequestHandler(logger=app.logger, max_size=1, output_channel=output_channel, reachability_provider=reachability_provider, error_handler=error_handler) - message_handler = UICSMessageHandler(logger=app.logger, max_size=20, network_request_handler=network_request_handler, + message_handler = UICSMessageHandler(logger=app.logger, max_size=20, + network_request_handler=network_request_handler, output_channel=output_channel, reachability_provider=reachability_provider, error_handler=error_handler) - ui_cs_bus = FileInputBus(logger=app.logger, file_channels_path=UI2CS_FILE_CHANNELS_PATH, input_channel_name="inp.buf", + ui_cs_bus = FileInputBus(logger=app.logger, file_channels_path=UI2CS_FILE_CHANNELS_PATH, + input_channel_name="inp.buf", g_config=g_config, message_handler=message_handler) heartbeat_provider = HeartBeatProvider(logger=app.logger, network_request_handler=network_request_handler, @@ -86,10 +98,8 @@ parser = reqparse.RequestParser() except Exception as e: - error = Error("{0},2,{1},Failed to start CS - {2}".format(OutboundMessageIDs.CS2UI_ERROR.value, - ErrorIDs.GENERIC_ERROR.value, - e)) - self.error_handler.enqueue_error(error=error) + g_utils.logger.error("Failed to start CS - {0}".format(e)) + sys.exit(0) @api.route("/version") @@ -139,8 +149,8 @@ invalid_params.append("public_key") if len(invalid_params) > 0: error = Error("{0},2,{1}, invalid credentials: {2}".format(OutboundMessageIDs.CS2UI_ERROR.value, - ErrorIDs.CS_SAVE_CREDENTIALS_ERROR.value, - invalid_params)) + ErrorIDs.CS_SAVE_CREDENTIALS_ERROR.value, + invalid_params)) error_handler.enqueue_error(error=error) return {"invalidAttributes": invalid_params}, BAD_REQUEST Index: cloudsync/busses/file_input_bus.py =================================================================== diff -u -rcc7bbd932684e69aa456c114596625546630903e -rae065dc96f33fc1946785ee833356dae959be2d9 --- cloudsync/busses/file_input_bus.py (.../file_input_bus.py) (revision cc7bbd932684e69aa456c114596625546630903e) +++ cloudsync/busses/file_input_bus.py (.../file_input_bus.py) (revision ae065dc96f33fc1946785ee833356dae959be2d9) @@ -56,7 +56,7 @@ try: f = open(self.file_channels_path + "/" + filename) except IOError as er: - self.logger.error('Opening input file error: {0}'.format(' '.join(er.args))) + self.logger.error('Opening input file error: {0}'.format(' '.join(str(er)))) new_input_messages = [] Index: cloudsync/busses/file_output_bus.py =================================================================== diff -u -rcc7bbd932684e69aa456c114596625546630903e -rae065dc96f33fc1946785ee833356dae959be2d9 --- cloudsync/busses/file_output_bus.py (.../file_output_bus.py) (revision cc7bbd932684e69aa456c114596625546630903e) +++ cloudsync/busses/file_output_bus.py (.../file_output_bus.py) (revision ae065dc96f33fc1946785ee833356dae959be2d9) @@ -80,4 +80,4 @@ f.close() self.last_output_message_id += 1 except IOError as er: - self.logger.error('Opening and/or writing to output file error: {0}'.format(' '.join(er.args))) + self.logger.error('Opening and/or writing to output file error: {0}'.format(' '.join(str(er)))) Index: cloudsync/common/enums.py =================================================================== diff -u -re2b5bba1ace2613e8cd3ca6d997756b1b61d77a4 -rae065dc96f33fc1946785ee833356dae959be2d9 --- cloudsync/common/enums.py (.../enums.py) (revision e2b5bba1ace2613e8cd3ca6d997756b1b61d77a4) +++ cloudsync/common/enums.py (.../enums.py) (revision ae065dc96f33fc1946785ee833356dae959be2d9) @@ -72,6 +72,7 @@ # OPERATION UI2CS_SEND_DEVICE_STATE = 1006 UI2CS_SEND_TREATMENT_REPORT = 1007 + UI2CS_REQ_DECOMMISSION = 1009 # INCOMING ERROR UI2CS_ERROR = 1999 @@ -88,6 +89,7 @@ # OPERATION CS2UI_REQ_DEVICE_STATE = 2006 CS2UI_REQ_TX_CODE_DISPLAY = 2008 + CS2UI_REQ_DEVICE_DECOMMISSIONED = 2009 # OUTGOING ERROR CS2UI_ERROR = 2999 @@ -107,6 +109,7 @@ CS_REQ_REGISTRATION_ERROR = 901 CS_SEND_DEVICE_STATE_ERROR = 906 CS_SEND_TREATMENT_REPORT_ERROR = 907 + CS_REQ_DECOMMISSION_ERROR = 909 CS_BAD_CRC_ERROR = 910 CS_DEVICE_VALIDATION_RESULT_ERROR = 920 CS_SET_PATIENT_DEVICE_ASSOCIATION_ERROR = 921 Index: cloudsync/config/treatment_report_template.json =================================================================== diff -u -rcc7bbd932684e69aa456c114596625546630903e -rae065dc96f33fc1946785ee833356dae959be2d9 --- cloudsync/config/treatment_report_template.json (.../treatment_report_template.json) (revision cc7bbd932684e69aa456c114596625546630903e) +++ cloudsync/config/treatment_report_template.json (.../treatment_report_template.json) (revision ae065dc96f33fc1946785ee833356dae959be2d9) @@ -7,6 +7,7 @@ "id": "" }, "treatment": { + "treatmentCode": "", "parameters": { "bloodFlowRate": 0, "dialysateFlowRate": 0, Index: cloudsync/handlers/cs_mft_dcs_request_handler.py =================================================================== diff -u -re2b5bba1ace2613e8cd3ca6d997756b1b61d77a4 -rae065dc96f33fc1946785ee833356dae959be2d9 --- cloudsync/handlers/cs_mft_dcs_request_handler.py (.../cs_mft_dcs_request_handler.py) (revision e2b5bba1ace2613e8cd3ca6d997756b1b61d77a4) +++ cloudsync/handlers/cs_mft_dcs_request_handler.py (.../cs_mft_dcs_request_handler.py) (revision ae065dc96f33fc1946785ee833356dae959be2d9) @@ -162,6 +162,7 @@ try: treatment_log_json = req.payload patient_emr_id = treatment_log_json['data']['patient']['id'] + treatment_id = treatment_log_json['data']['treatment']['treatmentCode'] base_url = req.g_config[CONFIG_KEBORMED][CONFIG_KEBORMED_DCS_URL] identity_url = req.g_config[CONFIG_KEBORMED][CONFIG_KEBORMED_DEVICE_IDENTITY_URL] @@ -239,11 +240,18 @@ error_handler=self.error_handler) # Step #4 - Send treatment report - treatment_log_json['generatedAt'] = int(round( - time() * S_MS_CONVERSION_FACTOR)) + 30000 # TEMPORARY DELAY TO COMPENSATE FOR DEVICE INTERNAL CLOCK ERROR - TODO REMOVE AFTER FIX - treatment_log_json['completedAt'] = int(round( - time() * S_MS_CONVERSION_FACTOR)) + 31000 # TEMPORARY DELAY TO COMPENSATE FOR DEVICE INTERNAL CLOCK ERROR - TODO REMOVE AFTER FIX + association_time = int(round(time() * S_MS_CONVERSION_FACTOR)) + if response.status_code == SUCCESS: + association_time = int(response.headers.get('X-Timestamp')) + generated_at_time = association_time + 1000 + completed_at_time = association_time + 2000 + else: + generated_at_time = association_time + 301000 # TEMPORARY DELAY TO COMPENSATE FOR DEVICE INTERNAL CLOCK ERROR - TODO REMOVE AFTER FIX + completed_at_time = association_time + 302000 # TEMPORARY DELAY TO COMPENSATE FOR DEVICE INTERNAL CLOCK ERROR - TODO REMOVE AFTER FIX + treatment_log_json['generatedAt'] = generated_at_time + treatment_log_json['completedAt'] = completed_at_time + treatment_log_json = json.dumps(treatment_log_json) self.logger.debug("treatment log: {0}".format(treatment_log_json)) @@ -256,7 +264,7 @@ if response is not None: self.logger.debug("Treatment upload response: {0}".format(response.json())) - treatment_id = response.json().get("reference", None) + # treatment_id = response.json().get("reference", None) # Step #5 - Remove patient/device association Index: cloudsync/handlers/ui_cs_request_handler.py =================================================================== diff -u -re2b5bba1ace2613e8cd3ca6d997756b1b61d77a4 -rae065dc96f33fc1946785ee833356dae959be2d9 --- cloudsync/handlers/ui_cs_request_handler.py (.../ui_cs_request_handler.py) (revision e2b5bba1ace2613e8cd3ca6d997756b1b61d77a4) +++ cloudsync/handlers/ui_cs_request_handler.py (.../ui_cs_request_handler.py) (revision ae065dc96f33fc1946785ee833356dae959be2d9) @@ -1,6 +1,3 @@ -"""Message handler for inbound messages""" - -from time import sleep from logging import Logger from threading import Event, Thread from collections import deque @@ -15,6 +12,7 @@ import urllib.parse import os import shutil +import time class UICSMessageHandler: @@ -90,8 +88,9 @@ if (len(message.parameters) != 3) or (message.parameters[0] is None) or (message.parameters[2] is None): error = Error( - "{0},2,{1},invalid # of parameters for registration".format(OutboundMessageIDs.CS2UI_ERROR.value, - ErrorIDs.CS_REQ_REGISTRATION_ERROR.value)) + "{0},2,{1},invalid # of parameters for registration".format( + OutboundMessageIDs.CS2UI_ERROR.value, + ErrorIDs.CS_REQ_REGISTRATION_ERROR.value)) self.error_handler.enqueue_error(error=error) else: try: @@ -125,8 +124,8 @@ self.error_handler.enqueue_error(error=error) except Exception as e: error = Error(GENERAL_EXCEPTION_HOLDER.format(OutboundMessageIDs.CS2UI_ERROR.value, - ErrorIDs.CS_REQ_REGISTRATION_ERROR.value, - e)) + ErrorIDs.CS_REQ_REGISTRATION_ERROR.value, + e)) self.error_handler.enqueue_error(error=error) # OPERATION MODE @@ -202,9 +201,26 @@ e)) self.error_handler.enqueue_error(error=error) + # DECOMMISSIONING REQUEST + elif InboundMessageIDs.mapped_str_value(message.ID) == InboundMessageIDs.UI2CS_REQ_DECOMMISSION and \ + (message.g_config[CONFIG_DEVICE][CONFIG_DEVICE_MODE] == 'operation'): + self.logger.info("UI2CS_REQ_DECOMMISSION request received") + + try: + helpers_decommission_device() + message_body = str( + OutboundMessageIDs.CS2UI_REQ_DEVICE_DECOMMISSIONED.value) + ',0' + self.output_channel.enqueue_message(message_body) + except Exception as e: + error = Error("{0},2,{1},{2}".format(OutboundMessageIDs.CS2UI_ERROR.value, + ErrorIDs.CS_REQ_DECOMMISSION_ERROR.value, + e)) + self.error_handler.enqueue_error(error=error) + # ERROR MESSAGE RECEIVED FROM UI elif InboundMessageIDs.mapped_str_value(message.ID) == InboundMessageIDs.UI2CS_ERROR: - error_body = "{0},{1},{2}".format(InboundMessageIDs.UI2CS_ERROR.value, message.size, message.parameters[0]) + error_body = "{0},{1},{2}".format(InboundMessageIDs.UI2CS_ERROR.value, message.size, + message.parameters[0]) if len(message.parameters) > 1: for parameter in message.parameters[1:]: error_body += ",{0}".format(parameter) Index: cloudsync/utils/globals.py =================================================================== diff -u -re2b5bba1ace2613e8cd3ca6d997756b1b61d77a4 -rae065dc96f33fc1946785ee833356dae959be2d9 --- cloudsync/utils/globals.py (.../globals.py) (revision e2b5bba1ace2613e8cd3ca6d997756b1b61d77a4) +++ cloudsync/utils/globals.py (.../globals.py) (revision ae065dc96f33fc1946785ee833356dae959be2d9) @@ -25,6 +25,9 @@ # CONFIG CONFIG_PATH = os.path.join(PATH_HOME, "cloudsync/config/config.json") +DECOMMISSION_CS_PATH = "/var/configurations/CloudSync/" +DECOMMISSION_FOLDERS = ['config', 'jwt', 'credentials'] + OPERATION_CONFIG_PATH = "/var/configurations/CloudSync/config/" OPERATION_CONFIG_FILE_PATH = os.path.join(OPERATION_CONFIG_PATH, "config.json") @@ -49,10 +52,8 @@ CREDENTIALS_PRIVATE_KEY = os.path.join(CREDENTIALS_PATH, PRIVATE_KEY_FILE_NAME) CREDENTIALS_PUBLIC_KEY = os.path.join(CREDENTIALS_PATH, PUBLIC_KEY_FILE_NAME) - - # UI2CS VALUES -UI2CS_FILE_CHANNELS_PATH = "/media/sd-card/cloudsync/" +UI2CS_FILE_CHANNELS_PATH = "/media/sd-card/cloudsync" # PATH for running off device # UI2CS_FILE_CHANNELS_PATH = "data/busses" @@ -72,6 +73,7 @@ SECTION_STOP_CHARACTER = "]" # UI APP - TREATMENT REPORT VALUES +TREATMENT_CODE = "Tx Code" PATIENT_ID = "Patient ID" TREATMENT_DURATION = "Treatment Duration" BLOOD_FLOW_RATE = "Blood Flow Rate" @@ -116,6 +118,7 @@ # HTTP CODES OK = 200 +SUCCESS = 204 BAD_REQUEST = 400 UNAUTHORIZED = 401 NOT_FOUND = 404 Index: cloudsync/utils/helpers.py =================================================================== diff -u -re2b5bba1ace2613e8cd3ca6d997756b1b61d77a4 -rae065dc96f33fc1946785ee833356dae959be2d9 --- cloudsync/utils/helpers.py (.../helpers.py) (revision e2b5bba1ace2613e8cd3ca6d997756b1b61d77a4) +++ cloudsync/utils/helpers.py (.../helpers.py) (revision ae065dc96f33fc1946785ee833356dae959be2d9) @@ -1,6 +1,7 @@ """Implementation of helper methods""" import os +import shutil import json import datetime import hashlib @@ -199,8 +200,8 @@ config = json.load(f) return config else: - g_utils.logger.error("Configuration file does not exist: {0}".format(path)) - return {} + g_utils.logger.error("Operation configuration file does not exist: {0}".format(path)) + raise FileNotFoundError(f"Operation configuration file does not exist: {path}") def helpers_write_config(folder_path: str, file_path: str, config: dict) -> None: @@ -336,7 +337,9 @@ else: # print('regular line') - if line.startswith(PATIENT_ID): + if line.startswith(TREATMENT_CODE): + treatment_data['data']['treatment']['treatmentCode'] = line.split(',')[1] + elif line.startswith(PATIENT_ID): treatment_data['data']['patient']['id'] = line.split(',')[1] elif line.startswith(TREATMENT_DURATION): treatment_data['data']['treatment']['parameters']['treatmentDuration'] = int(line.split(',')[1]) @@ -481,3 +484,15 @@ hostname = socket.gethostname() ip_address = socket.gethostbyname(hostname) return ip_address + + +def helpers_decommission_device(): + parent_folder = DECOMMISSION_CS_PATH + subfolders_to_delete = DECOMMISSION_FOLDERS + + for subfolder in subfolders_to_delete: + path_to_delete = os.path.join(parent_folder, subfolder) + if os.path.exists(path_to_delete) and os.path.isdir(path_to_delete): + shutil.rmtree(path_to_delete) + else: + raise FileNotFoundError(f"{path_to_delete} not found or not a directory!")