def __init__(self, usage: str, ip: str, rsa_key: KeyRSA, expire: str): self.__usage = usage self.__ip = ip self.__rsa_key = rsa_key self.__create = datetime.now() self.__expire = self.__create + timedelta(seconds=int(expire)) self.__status = 0 self.__cfg = Config() self.__dbmgr = DBMgr() self.__rsa_id = self.__insert_to_db()
def __init__(self, rsa_id: str, usage: str, ip: str, rsa_key: KeyRSA, create: datetime, expire: datetime, status: int): self.__rsa_id = rsa_id self.__usage = usage self.__ip = ip self.__rsa_key = rsa_key self.__create = create self.__expire = expire self.__status = status self.__cfg = Config() self.__dbmgr = DBMgr()
class SensorDataHelper(): dbmgr = DBMgr() @staticmethod def get_sensor_from_db(): sql = "SELECT * FROM `iot`.`sensor`" args = {} while True: status, row, record = SensorDataHelper.dbmgr.query(sql, args) if status: break return record
class AccessHelper(): dbmgr = DBMgr() @staticmethod def get_by_name(account): sql = " SELECT * \ FROM `iot`.`access` \ WHERE `access`.`account` = %(account)s \ ORDER BY `access`.`id` DESC" args = {'account': account} while True: status, row, data = AccessHelper.dbmgr.query(sql, args) if status: break if row == 0: return False, {} elif row == 1: return True, data[0] # 為系統發生例外狀況 else: return True, data[0] def get_all(): sql = " SELECT * \ FROM `iot`.`access` \ ORDER BY `access`.`id` DESC" args = {} while True: status, row, data = AccessHelper.dbmgr.query(sql, args) if status: break return status, row, data @staticmethod def get_records_by_name(account): sql = " SELECT * \ FROM `iot`.`access_record` \ WHERE `access_record`.`account` = %(account)s \ ORDER BY `access_record`.`id` DESC" args = {'account': account} while True: status, row, data = AccessHelper.dbmgr.query(sql, args) if status: break return status, row, data @staticmethod def entry(account): sql = "INSERT INTO `iot`.`access`(`access`.`account`) VALUES(%(account)s)" args = {'account': account} while True: status, row, result_id = AccessHelper.dbmgr.insert(sql, args, multiple=False) if status: break @staticmethod def delete(account): sql = "DELETE FROM `iot`.`access` WHERE `access`.`account` = %(account)s" args = {'account': account} while True: status, row, result_id = AccessHelper.dbmgr.delete(sql, args) if status: break @staticmethod def add_record(account, entry, exit): sql = " INSERT INTO `iot`.`access_record`(`access_record`.`account`, `access_record`.`entry`, `access_record`.`exit`) \ VALUES(%(account)s, %(entry)s, %(exit)s)" args = { 'account': account, 'entry': entry, 'exit': exit, } while True: status, row, result_id = AccessHelper.dbmgr.insert(sql, args, multiple=False) if status: break @staticmethod def exit(account, entry_dt, trigger=None): entry_dt_str = entry_dt.isoformat(' ') if trigger == 'system': years = entry_dt.year months = entry_dt.month days = entry_dt.day exit_dt = datetime.datetime(years, months, days, 23, 59, 59) exit_dt_str = exit_dt.isoformat(' ') else: exit_dt_str = datetime.datetime.today().isoformat(' ') AccessHelper.add_record(account, entry_dt_str, exit_dt_str) AccessHelper.delete(account)
class MemberHelper(): dbmgr = DBMgr() @staticmethod def check_duplicate_by_account(account): sql = "SELECT * FROM `iot`.`member` WHERE `member`.`account` = %(account)s" args = {'account': account} status, row, result = MemberHelper.dbmgr.query(sql, args) if not status: result = { 'error_type': "DatabaseError", 'error_msg': "[" + str(result[0]) + "]" + str(result[1]), 'error_code': 503 } return False, result, 503 elif row != 0: result = { 'error_type': "AccountDuplocatedError", 'error_msg': "此帳號資料庫已經存在,請勿重複註冊", 'error_code': 400 } return False, result, 400 else: result = { 'type': "AccountPassVerify", 'msg': "此帳號為新使用者,可以使用", 'code': 200 } return True, result, 200 @staticmethod def create(member): # 取得資料表欄位 table_col = MemberHelper.dbmgr.get_db_column('iot', 'member') sql = MemberHelper.dbmgr.insert_sql('member', table_col, False) args = member.get_all_parameter() status, row, result = MemberHelper.dbmgr.insert(sql, args) # 看新增狀態 if status: return True, { 'type': "AccountRegisterSuccess", 'msg': "帳號新增至資料庫成功", 'code': 200 }, 200 else: return False, { 'error_type': "DatabaseError", 'error_msg': "[" + str(result[0]) + "]" + str(result[1]), 'error_code': 500 }, 500 @staticmethod def get(account): sql = "SELECT * FROM `iot`.`member` WHERE `account`=%(account)s" args = {'account': account} status, row, result = MemberHelper.dbmgr.query(sql, args) if not status: return False, { 'error_type': "DatabaseError", 'error_msg': "[" + str(result[0]) + "]" + str(result[1]), 'error_code': 500 }, 500 # 發生未預期錯誤(帳號超過一個例外狀況) elif row > 1: return False, { 'error_type': "AccountMoreThanOneException", 'error_msg': "資料庫存在該帳號之資料超過一筆異常", 'error_code': 500 }, 500 # 找不到帳號 elif row == 0: return False, { 'error_type': "AccountVerifyError", 'error_msg': "帳號不存在或密碼錯誤,請確認後重新輸入", 'error_code': 400 }, 400 else: m = Member(result[0]['id'], result[0]['account'], result[0]['name'], result[0]['email'], result[0]['password'], result[0]['identity'], result[0]['create'], result[0]['modify']) return True, m, 200 @staticmethod def update(member): sql = " UPDATE `iot`.`member` \ SET `member`.`name`=%(name)s, `member`.`email`=%(email)s, \ `member`.`password`=%(password)s, `member`.`modify`=%(modify)s \ WHERE `member`.`account`=%(account)s" args = member.get_all_parameter() status, row, result = MemberHelper.dbmgr.update(sql, args) if not status: return False, { 'error_type': "DatabaseError", 'error_msg': "[" + str(result[0]) + "]" + str(result[1]), 'error_code': 500 }, 500 # 發生未預期錯誤(帳號超過一個例外狀況) elif row > 1: return False, { 'error_type': "AccountMoreThanOneException", 'error_msg': "資料庫存在該帳號之資料超過一筆異常", 'error_code': 500 }, 500 # 找不到帳號 elif row == 0: return False, { 'error_type': "AccountVerifyError", 'error_msg': "帳號不存在或密碼錯誤,請確認後重新輸入", 'error_code': 400 }, 400 else: return True, { 'type': "AccountUpdateSuccess", 'msg': "帳號更新成功", 'code': 200 }, 200 @staticmethod def verify(account, password): sql = "SELECT `password`, `identity` FROM `iot`.`member` WHERE `account`=%(account)s" args = {'account': account} status, row, result = MemberHelper.dbmgr.query(sql, args) if not status: return False, { 'error_type': "DatabaseError", 'error_msg': "[" + str(result[0]) + "]" + str(result[1]), 'error_code': 500 }, 500 # 發生未預期錯誤(帳號超過一個例外狀況) elif row > 1: return False, { 'error_type': "AccountMoreThanOneException", 'error_msg': "資料庫存在該帳號之資料超過一筆異常", 'error_code': 500 }, 500 # 找不到帳號 elif row == 0: return False, { 'error_type': "AccountVerifyError", 'error_msg': "帳號不存在或密碼錯誤,請確認後重新輸入", 'error_code': 400 }, 400 # 自資料庫取回該帳號之加密密碼成功 else: # 編碼成utf-8以進行檢查 password = password.encode("utf-8") hash_password = result[0]["password"].encode("utf-8") # 嘗試進行bcrypt金鑰檢查 if (bcrypt.checkpw(password, hash_password)): return True, { 'type': "AccountVerifySucess", 'msg': "帳號密碼驗證成功", 'code': 200 }, 200 else: return False, { 'error_type': "AccountVerifyError", 'error_msg': "帳號不存在或密碼錯誤,請確認後重新輸入", 'error_code': 400 }, 400
class RSAHelper(): dbmgr = DBMgr() @staticmethod def check_key_vaild(key_id, request_ip, usage): ''' 驗證金鑰有效性 ''' curr_datetime = datetime.now() sql = "SELECT * FROM `iot`.`rsa` WHERE `rsa`.`id` = %(id)s" args = {'id': key_id} status, row, result = RSAHelper.dbmgr.query(sql, args) # Case 1: 資料庫檢索失敗 if not status: result = { 'error_type': "DatabaseError", 'error_msg': "[" + str(result[0]) + "]" + str(result[1]), 'error_code': 503 } # Case 2: 金鑰超過一把(伺服器500發生未預期錯誤) elif row > 1: result = { 'error_type': "KeyMoreThanOne", 'error_msg': "系統發生未預期錯誤,金鑰超過一把", 'error_code': 500 } # Case 3: 金鑰不存在 elif row == 0: result = { 'error_type': "KeyNotExist", 'error_msg': "請求之金鑰不存在", 'error_code': 400 } # Case 4: 金鑰與請求IP不相符(正確只會有一筆,直接取用list第一筆dict資料) elif result[0]['ip'] != request_ip: result = { 'error_type': "KeyNotMatchRequestIP", 'error_msg': "請求之金鑰與IP位置不相符", 'error_code': 400 } # Case 5: 金鑰請求與用途不相符 elif result[0]['usage'] != usage: result = { 'error_type': "KeyNotMatchUsage", 'error_msg': "請求之金鑰與金鑰用途不相符", 'error_code': 400 } # Case 6: 金鑰已經過期 elif result[0]['expire'] < curr_datetime: result = { 'error_type': "KeyExpired", 'error_msg': "請求之金鑰已經失效", 'error_code': 400 } # Case 7: 金鑰已經被使用 elif result[0]['status'] == 1: result = { 'error_type': "KeyHasBeenUsed", 'error_msg': "請求之金鑰已經被使用", 'error_code': 400 } # Case 8: 金鑰驗證成功 else: # 將金鑰重新封裝 rsa_key = KeyRSA(result[0]['public_key'], result[0]['private_key'], result[0]['bits']) # 建立一個RSA物件回傳使用 rsa = RSA(result[0]['id'], result[0]['usage'], result[0]['ip'], rsa_key, result[0]['create'], result[0]['expire'], result[0]['status']) return True, row, rsa return False, row, result
class RSA(metaclass=MultipleMeta): def __init__(self, rsa_id: str, usage: str, ip: str, rsa_key: KeyRSA, create: datetime, expire: datetime, status: int): self.__rsa_id = rsa_id self.__usage = usage self.__ip = ip self.__rsa_key = rsa_key self.__create = create self.__expire = expire self.__status = status self.__cfg = Config() self.__dbmgr = DBMgr() def __init__(self, usage: str, ip: str, rsa_key: KeyRSA, expire: str): self.__usage = usage self.__ip = ip self.__rsa_key = rsa_key self.__create = datetime.now() self.__expire = self.__create + timedelta(seconds=int(expire)) self.__status = 0 self.__cfg = Config() self.__dbmgr = DBMgr() self.__rsa_id = self.__insert_to_db() def __gen_rsa_key_id(self): # 取得現在日期 time = strftime("%Y%m%d%H%M%S", localtime()) # 產生三位數隨機亂碼 rand = random.randint(0, 999) # 不足位數前方補0 rand = "%03d" % rand return (time + rand) def __insert_to_db(self): # 如果設定檔指定不留下金鑰歷史紀錄 if not strtobool( self.__cfg.getValue('history_record', 'rsa_key_history')): sql = "DELETE FROM `iot`.`rsa` WHERE `ip` = %(ip)s AND `usage` = %(usage)s AND `bits` = %(bits)s" args = { 'ip': self.__ip, 'usage': self.__usage, 'bits': self.__rsa_key.get_bits() } status, row, result = self.__dbmgr.delete(sql, args) # TODO: 檢查刪除有無異常(通常只會有連線異常) while 1: # 產生一組隨機號碼 key_id = self.__gen_rsa_key_id() # 檢查是否有重複 if not self.__check_duplicate(key_id): break self.__rsa_id = key_id column = self.__dbmgr.get_db_column('iot', 'rsa') sql = self.__dbmgr.insert_sql('rsa', column, True) args = self.get_parameter() result, row, error_msg = self.__dbmgr.insert(sql, args) return key_id def __check_duplicate(self, key_id): if (self.__dbmgr.conn()): try: with self.__dbmgr.cursor() as cursor: sql = "SELECT * FROM `iot`.`rsa` WHERE `id` = %(id)s" args = {'id': key_id} num_of_rows = int(cursor.execute(sql, args)) result = cursor.fetchall() self.__dbmgr.commit() except self.__dbmgr.mysql_error() as e: print('[@RSA] 【{}】{!r}'.format(e.args[0], e.args[1])) else: print("[@RSA] Fails to connect to MySQL Server!!") self.__dbmgr.close() return True if num_of_rows != 0 else False def get_parameter(self): return { 'usage': self.__usage, 'ip': self.__ip, 'bits': self.__rsa_key.get_bits(), 'public_key': self.__rsa_key.get_public_key(), 'private_key': self.__rsa_key.get_private_key(), 'create': self.__create, 'expire': self.__expire, 'status': 0, 'id': self.__rsa_id } def __remove_all_expire_keys(self): sql = "DELETE FROM `iot`.`rsa` WHERE `iot`.`expire` < '%(date_time)s';" args = {'date_time': datetime.datetime.now()} status, row, result = self.__dbmgr.delete(sql, args) def get_rsa_key_id(self): return self.__rsa_id
class SensorHelper(): dbmgr = DBMgr() SENSOR_LIST = SensorDataHelper.get_sensor_from_db() # TODO: 若資料庫出錯是否要印出或做其他處置 @staticmethod def get_new_record_id(promotor='system'): sql = "INSERT INTO `iot`.`record`(`promotor`) VALUES(%(promotor)s)" args = {'promotor': promotor} while True: status, row, result_id = SensorHelper.dbmgr.insert(sql, args) if status: break return result_id @staticmethod def insert_sensor_data(data, record_id): sql = "INSERT INTO `iot`.`data`(`record_id`, `item`, `value`) VALUES(%(record_id)s, %(sensor)s, %(value)s)" args = list() # 進來的資料為{sensor: value} for k, v in data.items(): args.append({'sensor': k, 'value': v, 'record_id': record_id}) while True: status, row, result_id = SensorHelper.dbmgr.insert(sql, args, multiple=True) if status: break else: print(result_id) print("[@SensorHelper] Insert all this record data into db finish.") @staticmethod def update_fail_list(data_list, record_id): sql = "UPDATE `iot`.`record` SET `fail_list`=%(fail_list)s WHERE `record`.`id`=%(id)s" args = { 'fail_list': SensorHelper.dbmgr.list_to_string(data_list), 'id': record_id } while True: status, row, result = SensorHelper.dbmgr.update(sql, args) if status: break print("[@SensorHelper] Update fail sensor list finish.") @staticmethod def get_latest_data(): sql = "SELECT * FROM `iot`.`record` ORDER BY `record`.`id` DESC LIMIT 1" args = {} while True: status, row, record = SensorHelper.dbmgr.query(sql, args, fetch='one') if status: break # 資料庫無任何資料 if row == 0: return False, {} else: # 取得資料庫內所有該次監測的資料 record_id = record['id'] sql = "SELECT * FROM `iot`.`data` WHERE `data`.`record_id`=%(id)s" args = {'id': record_id} data_dict = dict() while True: status, row, data = SensorHelper.dbmgr.query(sql, args) if status: break for datum in data: data_dict.update({ datum['item']: None if datum['value'] is None else round( float(datum['value']), 1) }) # 組回原本格式 result = { 'id': record_id, 'datetime': record['datetime'].strftime('%Y-%m-%d %H:%M:%S'), 'fail': SensorHelper.dbmgr.string_to_list(record['fail_list']), 'data': data_dict } return True, result @staticmethod def get_sensor_his_data(sensor, day=30, is_resample=True, remain=-1): # 今天日期 today = datetime.date.today() # 最多獲取資料天數 limit_day = today - datetime.timedelta(day) # 取得符合監測記錄 sql = "SELECT * FROM `iot`.`record` WHERE `record`.`datetime` >= %(date)s ORDER BY `record`.`id` ASC LIMIT 1" args = {'date': limit_day.strftime("%Y-%m-%d")} status, row, record = SensorHelper.dbmgr.query(sql, args, fetch='one') day_info = { 'start': limit_day.strftime("%Y-%m-%d"), 'end': today.strftime("%Y-%m-%d") } # if status and row != 0: sql = " SELECT `record`.`datetime`, `data`.`value` \ FROM `iot`.`record`, `iot`.`data` \ WHERE `record`.`id` = `data`.`record_id` AND `data`.`record_id` >= %(record_id)s AND `data`.`item` = %(sensor)s" args = {'record_id': record['id'], 'sensor': sensor} status, row, data = SensorHelper.dbmgr.query(sql, args) # 成功取回至少一筆資料 if status and row != 0: for datum in data: try: datum['value'] = round(float(datum['value']), 3) except: datum['value'] = None df, df_h = SensorHelper.data_convert_to_dataframe( data, is_resample=is_resample) # 判斷最後df要保留多少長度資料 if remain > 0 and len(df.index) >= remain: df = df[0:remain] return True, df, df_h, row, day_info # 失敗或沒有取得資料 else: return False, pd.DataFrame(), pd.DataFrame(), 0, day_info # 資料庫沒有任何紀錄 else: return False, pd.DataFrame(), pd.DataFrame(), 0, day_info @staticmethod def data_convert_to_dataframe(data, resample='60Min', sort=False, is_resample=True): # TODO: 檢查參數是否型態與格式正確 df = pd.DataFrame(data) df.set_index("datetime", inplace=True) df = df.dropna() # 新增統計高低數值欄位 df['high'] = df['value'] df['low'] = df['value'] if is_resample: # 重新進行取樣 df = df.resample(resample).agg({ 'high': 'max', 'low': 'min', 'value': 'mean' }) df = df.asfreq(resample) if len(df.index) != 0: # 以小時進行合併 s_low = df['low'].groupby(df['low'].index.hour).min() s_high = df['high'].groupby(df['high'].index.hour).max() s_mean = df['value'].groupby(df['value'].index.hour).mean() df_h = pd.concat([s_low, s_high, s_mean], axis=1) else: df_h = df else: if len(df.index) != 0: # 以分鐘進行合併 s_low = df['low'].groupby(df['low'].index.minute).min() s_high = df['high'].groupby(df['high'].index.minute).max() s_mean = df['value'].groupby(df['value'].index.minute).mean() df_h = pd.concat([s_low, s_high, s_mean], axis=1) else: df_h = df # 取小數點至第二位,並且重設index df = df.round(2).reset_index() df['datetime'] = df['datetime'].apply( lambda x: x.strftime("%Y-%m-%d %H:%M:%S")) df = df.sort_values(by=["datetime"], ascending=[sort]) df = df.dropna() df_h = df_h.round(2).reset_index() df_h = df_h.dropna() return df, df_h @staticmethod def get_statist_data(sensor, period, header): ''' 不能傳入年,要使用要用relative ''' # TODO: 若傳入資料格式錯誤之異常處理(包含key值) time_idx = ['hours', 'minutes', 'seconds'] result = list() for item in period: # 判斷是否要抓取到時間部分 get_all = False if 'all' in item.keys(): get_all = True elif list(set(time_idx) & set(item.keys())): today = datetime.datetime.now() limit_day = ( today - datetime.timedelta(**item)).strftime("%Y-%m-%d %H:%M:%S") today = today.strftime("%Y-%m-%d %H:%M:%S") else: today = datetime.date.today() limit_day = (today - datetime.timedelta(**item)).strftime("%Y-%m-%d") today = today.strftime("%Y-%m-%d") # 使用SQL指令取回資料庫筆數和平均數 # 取得符合監測記錄 if get_all: sql = "SELECT * FROM `iot`.`record` ORDER BY `record`.`id` ASC LIMIT 1" else: sql = "SELECT * FROM `iot`.`record` WHERE `record`.`datetime` >= %(date)s ORDER BY `record`.`id` ASC LIMIT 1" args = {'date': limit_day} status, row, record = SensorHelper.dbmgr.query(sql, args, fetch='one') # 將該筆之日期資訊儲存 temp_result = {'start': limit_day, 'end': today} if status and row != 0: sql = " SELECT SUM(`data`.`value`) as `sum`, COUNT(`data`.`value`) as `count`, SUM(`data`.`value`)/COUNT(`data`.`value`) as `avg` \ FROM `iot`.`record`, `iot`.`data` \ WHERE `record`.`id` = `data`.`record_id` AND `data`.`record_id` >= %(record_id)s AND `data`.`item` >= %(sensor)s\ GROUP BY `data`.`item`" args = {'record_id': record['id'], 'sensor': sensor} status, row, data = SensorHelper.dbmgr.query(sql, args, fetch='one') print('row', row) # 成功取回至少一筆資料 if status and row != 0: temp_result.update({ 'sum': round(data['sum'], 2) if data['sum'] != None else 0.00, 'count': int(data['count']), 'avg': round(data['avg'], 2) if data['avg'] != None else 0.00 }) # 失敗或沒有取得資料 else: temp_result.update({'sum': 0.00, 'count': 0, 'avg': 0.00}) # 資料庫沒有任何紀錄 else: temp_result.update({'sum': 0.00, 'count': 0, 'avg': 0.00}) result.append(temp_result) return header, result
class MemberPreferenceHelper(): # TODO: 新增預設檔案之前可能需要先檢查是否存在 dbmgr = DBMgr() DEFAULT_ITEM = [ item for item in SensorHelper.SENSOR_LIST if item['default'] ] @staticmethod def add_default_value(account): # TODO: 需要改成OO寫法 sql = " INSERT INTO `iot`.`member_preference`(`account`, `item`, `min`, `max`) \ VALUES(%(account)s, %(item)s, %(min)s, %(max)s)" args = list() for item in MemberPreferenceHelper.DEFAULT_ITEM: args.append({ 'account': account, 'min': item['min'], 'max': item['max'], 'item': item['id'] }) while True: status, row, result_id = MemberPreferenceHelper.dbmgr.insert( sql, args, multiple=True) if status: break @staticmethod def get_by_account(account): sql = "SELECT * FROM `iot`.`member_preference` WHERE `member_preference`.`account` = %(account)s" args = {'account': account} item_list = list() while True: status, row, result = MemberPreferenceHelper.dbmgr.query(sql, args) if status: break for i in result: item_list.append(i['item']) return result, item_list @staticmethod def calc_avg_pref_value(): """ Use for to calculate the averger value of preference in this room """ # 取得目前在環境中的人員數量 status, row, data = AccessHelper.get_all() people = [i['account'] for i in data] if status else list() result = dict() for item in MemberPreferenceHelper.DEFAULT_ITEM: if not status or row <= 0: result.update( {item['id']: { 'min': item['min'], 'max': item['max'], }}) else: sql = " SELECT * \ FROM `iot`.`member_preference` \ WHERE `member_preference`.`item`=%(item)s AND \ `member_preference`.`account` in %(people)s" args = {'item': item['id'], 'people': tuple(people)} while True: status, row, data = MemberPreferenceHelper.dbmgr.query( sql, args) if status: break # 判斷是否有抓取到該項目偏好設定檔案 if row == 0: result.update({ item['id']: { 'min': item['min'], 'max': item['max'], } }) else: min_value = float(sum(d['min'] for d in data)) / len(data) max_value = float(sum(d['max'] for d in data)) / len(data) result.update({ item['id']: { 'min': round(min_value, 2), 'max': round(max_value, 2), } }) return result, people, len(people) @staticmethod def edit(args): sql = " UPDATE `iot`.`member_preference` \ SET `member_preference`.`min` = %(min)s, \ `member_preference`.`max` = %(max)s \ WHERE `member_preference`.`item` = %(item)s AND \ `member_preference`.`account` = %(account)s" while True: status, row, result = MemberPreferenceHelper.dbmgr.update( sql, args, multiple=True) if status: break return row