From 96c5e26abdfd053343232ca333ac1f4c4c2b5f91 Mon Sep 17 00:00:00 2001 From: Lei PAN Date: Mon, 28 Feb 2022 20:09:02 +0100 Subject: [PATCH] try to add sim info to fb --- ModemPool.py | 15 ++++- card_pool.py | 2 +- db/DbManager.py | 8 ++- SIMError.py => error/SIMError.py | 0 main.py | 92 +++++++++++++++---------------- params.py | 19 +++++++ pojo/SimInfoPojo.py | 15 +++++ utils/ccid_list.xlsx | Bin 0 -> 6405 bytes utils/excel_reader.py | 29 +++++++--- utils/phone_list.xlsx | Bin 4972 -> 5669 bytes 10 files changed, 118 insertions(+), 62 deletions(-) rename SIMError.py => error/SIMError.py (100%) create mode 100644 params.py create mode 100644 pojo/SimInfoPojo.py create mode 100644 utils/ccid_list.xlsx diff --git a/ModemPool.py b/ModemPool.py index 1739f91..3e8b0b8 100644 --- a/ModemPool.py +++ b/ModemPool.py @@ -3,9 +3,11 @@ import time import serial from serial import Serial +from db.DbManager import DataManager -from SIMError import SIMError +from error.SIMError import SIMError from logs.LogSender import LogSender, LOG_ERROR +from pojo.SimInfoPojo import SimInfoPojo from utils.excel_reader import ExcelHelper @@ -19,6 +21,7 @@ class ModemPool: self._serial_list = [] self._excel_helper = ExcelHelper() self._log_sender = LogSender() + self._db_manager = DataManager() for port in self._port_list: ser = serial.Serial(port, self.BAUDRATE, timeout=1) @@ -26,13 +29,15 @@ class ModemPool: def reset_all_modems(self): for ser in self._serial_list: + # may encontre exception here, multi-access to serial port + time.sleep(2) self._send_command("AT+CFUN=1,1\r", ser) # wait for 20 second, so that the modem can init all the sims time.sleep(20) def _generate_error_msg(self, slot_position, index, error: SIMError): msg = "slot({}) SIM({}), error:{}".format(slot_position, index + 1, - error.value) + error.value) self._log_sender.send_log(msg, subject=self.TAG, type=LOG_ERROR) return msg @@ -56,8 +61,12 @@ class ModemPool: match = re.search(r'33\d{9}', str(msg)) phone_number = match.group(0) print("phone is " + phone_number) + cmd = "AT+CCID\r" + response = str(self._send_command(cmd, ser)) + ccid_group = re.search("[0-9F]+", response) + ccid = ccid_group.group(0) if phone_number: - self._excel_helper.write_phone(phone_number) + self._db_manager.save_sim_info(SimInfoPojo(phone=phone_number, ccid=ccid)) # write the number to sim card's phonebook cmd = f'AT+CPBW={self.phone_number_position},\"{phone_number}\"\r' self._send_command(cmd, ser, wait_time_in_s=2) diff --git a/card_pool.py b/card_pool.py index 7a90882..1d1446d 100644 --- a/card_pool.py +++ b/card_pool.py @@ -2,7 +2,7 @@ import logging import serial -PORT = "/dev/tty.usbmodem112101" +PORT = "/dev/tty.usbmodem1432101" BAUDRATE = 115200 diff --git a/db/DbManager.py b/db/DbManager.py index da56216..0d85a47 100644 --- a/db/DbManager.py +++ b/db/DbManager.py @@ -1,13 +1,13 @@ import datetime -import json - import firebase_admin from firebase_admin import credentials, firestore from pojo.ReserveResultPojo import ReserveResultPojo, PublishType +from pojo.SimInfoPojo import SimInfoPojo from pojo.contact_pojo import ContactPojo ERROR_COLLECTION_NAME = "error_items" +SIM_INFOS = "sim_infos" TIMEOUT = "timeout_items" @@ -24,6 +24,10 @@ class DataManager: doc_ref = self._db.collection(u'2022-02-25') return doc_ref + def save_sim_info(self, simInfoPojo: SimInfoPojo): + doc_ref = self._db.collection(SIM_INFOS).document(simInfoPojo.phone) + doc_ref.set(simInfoPojo.to_firestore_dict()) + def save(self, result: ReserveResultPojo): if result.type == PublishType.SUCCESS: # get id diff --git a/SIMError.py b/error/SIMError.py similarity index 100% rename from SIMError.py rename to error/SIMError.py diff --git a/main.py b/main.py index 0386d0b..40d5567 100644 --- a/main.py +++ b/main.py @@ -7,9 +7,10 @@ import time from gsmmodem import GsmModem from ModemPool import ModemPool -from card_pool import CardPool, PORT +from card_pool import CardPool from commandor import Commandor from db.DbManager import DataManager +from params import MODEM_POOL_PORTS, CARD_POOL_PORT from pojo.ReserveResultPojo import ReserveResultPojo from utils.excel_reader import ExcelHelper from pojo.serial_modem import SerialModem @@ -20,32 +21,13 @@ BAUDRATE = 115200 OTP_TIMEOUT = 60 is_finished = False commandor = Commandor() -timeout_contact_list = [] - -# ser = serial.Serial(PORT, BAUDRATE, timeout=1) db_manager = DataManager() +card_pool = CardPool(CARD_POOL_PORT) def get_devices_ports() -> list: - return [ - "/dev/tty.usbmodem111101", - "/dev/tty.usbmodem111103", - "/dev/tty.usbmodem111105", - "/dev/tty.usbmodem111107", - "/dev/tty.usbmodem111201", - "/dev/tty.usbmodem111203", - "/dev/tty.usbmodem111205", - "/dev/tty.usbmodem111207", - "/dev/tty.usbmodem111301", - ## "/dev/tty.usbmodem111303", - "/dev/tty.usbmodem111305", - "/dev/tty.usbmodem111307", - "/dev/tty.usbmodem111401", - "/dev/tty.usbmodem111403", - "/dev/tty.usbmodem111405", - "/dev/tty.usbmodem111407" - ] + return MODEM_POOL_PORTS def has_sim(ser) -> bool: @@ -58,11 +40,15 @@ def has_sim(ser) -> bool: return False -def send_command(cmd: str, ser) -> bytes: - print("send command {}".format(cmd)) +def send_command(cmd: str, ser, wait_time_in_s: int = 0) -> bytes: ser.write(cmd.encode()) - time.sleep(10) msg = ser.read(100) + count = 0 + while 'OK' not in str(msg) and count < wait_time_in_s: + time.sleep(1) + count = count + 1 + msg = ser.read(100) + # msg = ser.read(100) print(msg) return msg @@ -95,7 +81,6 @@ def create_modem_for_port(port: str) -> SerialModem: def timeout_occurred(serial_modem: SerialModem): - timeout_contact_list.append(serial_modem.contact) db_manager.save_timeout_contact(serial_modem.contact) @@ -134,6 +119,16 @@ def handle_sms(sms): is_finished = True +def select_sim_storage(ser) -> bool: + # use SIM Card storage + cmd_sm = "AT+CPBS=\"SM\"\r" + result = send_command(cmd_sm, ser) + if "ERROR" in str(result): + return False + else: + return True + + def init_modems() -> list: modems = [] for port in get_devices_ports(): @@ -169,12 +164,11 @@ def start_listen(): def read_all_the_phone_number(): - card_pool = CardPool(PORT) slot_number = 1 - slot_sum = 28 - # card_pool.switch_to_slot(10) + slot_sum = 3 + # card_pool.switch_to_slot(3) - for i in range(1, slot_sum + 1): + for i in range(slot_number, slot_sum + 1): card_pool.reset() print("will switch to " + str(i)) card_pool.switch_to_slot(i) @@ -183,25 +177,27 @@ def read_all_the_phone_number(): modem_pool.get_raw_phone_number(i) +def start_book(): + slot_number = 3 + slot_sum = 3 + for i in range(slot_number, slot_sum + 1): + card_pool.reset() + print("will switch to " + str(i)) + card_pool.switch_to_slot(i) + modem_pool = ModemPool(get_devices_ports()) + modem_pool.reset_all_modems() + modem_list = init_modems() + # create listeners for chaque modem + for modem in modem_list: + if modem.contact: + commandor.start_page(modem.contact) + start_to_handle_sms(modem) + + if __name__ == '__main__': init_logger() logger = logging.getLogger() logger.addHandler(logging.StreamHandler(stream=sys.stdout)) - read_all_the_phone_number() - # start_listen() - # reset the sim card pool - # send_command("AT+SWIT01-0001\r", ser) - - # enable verbose logs for all port - - # # create modems for all the available port - # modem_list = init_modems() - # create listeners for chaque modem - # for modem in modem_list: - # commandor.start_page(modem.contact) - # start_to_handle_sms(modem) - # # save the timeout contacts - # timeout_list = json.dumps(timeout_contact_list) - # f = open("timeout_list.json", "a") - # f.write(str(timeout_list)) - # f.close() + # read_all_the_phone_number() + start_listen() + start_book() diff --git a/params.py b/params.py new file mode 100644 index 0000000..f75ba03 --- /dev/null +++ b/params.py @@ -0,0 +1,19 @@ +MODEM_POOL_PORTS = [ + "/dev/tty.usbmodem1431101", + "/dev/tty.usbmodem1431103", + "/dev/tty.usbmodem1431105", + "/dev/tty.usbmodem1431107", + "/dev/tty.usbmodem1431201", + "/dev/tty.usbmodem1431203", + "/dev/tty.usbmodem1431205", + "/dev/tty.usbmodem1431207", + "/dev/tty.usbmodem1431301", + ## "/dev/tty.usbmodem1431303", + "/dev/tty.usbmodem1431305", + "/dev/tty.usbmodem1431307", + "/dev/tty.usbmodem1431401", + "/dev/tty.usbmodem1431403", + "/dev/tty.usbmodem1431405", + "/dev/tty.usbmodem1431407" +] +CARD_POOL_PORT = "/dev/tty.usbmodem1432101" diff --git a/pojo/SimInfoPojo.py b/pojo/SimInfoPojo.py new file mode 100644 index 0000000..4598933 --- /dev/null +++ b/pojo/SimInfoPojo.py @@ -0,0 +1,15 @@ +class SimInfoPojo: + phone: str + ccid: str + + def __init__(self, phone: str, ccid: str): + self.phone = phone + self.ccid = ccid + + def to_firestore_dict(self): + dest = { + u'phone': self.phone, + u'ccid': self.ccid, + } + + return dest diff --git a/utils/ccid_list.xlsx b/utils/ccid_list.xlsx new file mode 100644 index 0000000000000000000000000000000000000000..b6c2f8833dae0be90caf78dbe187fb5782b67a5a GIT binary patch literal 6405 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 VH5{}o4ge6KzcT0*yz2I~^*?kgbv6J1 literal 0 HcmV?d00001 diff --git a/utils/excel_reader.py b/utils/excel_reader.py index e26fdfb..84f712d 100644 --- a/utils/excel_reader.py +++ b/utils/excel_reader.py @@ -10,13 +10,30 @@ class ExcelHelper: def __init__(self): self._df = pandas.Series() - def write_phone(self, phone_number): - new_df = pandas.Series([phone_number]) + def write_phone(self, phone_ccid): + (phone, ccid) = phone_ccid f = open("phone_list.txt", "a") + f.write("{},".format(phone[2:len(phone)])) + f.close() + + def write_ccid(self, phone_number): + f = open("ccid_list.txt", "a") f.write("{},".format(phone_number)) f.close() + + def write_to_exel(self, file_name, data_list: list): + new_df = pandas.Series(data_list) self._df = pandas.concat([self._df, new_df]) - self._df.to_excel("phone_list.xlsx") + self._df.to_excel(file_name) + + def generate_exel_from_txt(self): + f = open("../phone_list.txt") + lines = f.read() + self.write_to_exel("phone_list.xlsx", lines.split(",")) + ccid_file = open("../ccid_list.txt") + ccids = ccid_file.read() + self.write_to_exel("ccid_list.xlsx", ccids.split(",")) + print(lines) # read the contact list from the exel file def read_contacts(self) -> list: @@ -39,8 +56,4 @@ class ExcelHelper: if __name__ == '__main__': helper = ExcelHelper() - helper.write_phone("88649614591") - helper.write_phone("88649614591") - helper.write_phone("88649614591") - helper.write_phone("8864961591") - helper.write_phone("88649614591") + helper.generate_exel_from_txt() diff --git a/utils/phone_list.xlsx b/utils/phone_list.xlsx index 96d24c445d613cb5a4b04edaec878432fc05c201..e4812bc3968bdb0deccb4d45c5d72bda038b0af1 100644 GIT binary patch delta 3596 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 delta 2829 zcmZ8jc{G&m8=gU9wfXG%efY!0Yw1*>ciB?$-Ec{-_OQK>UW-kq<`@MhQ>%y?9F}xt5y%+HfVGPJYfL}3bU{)qa}HYj zxQPAT;=&o!8#a{sUS;$Y`vIC>c7y+daQ+}FFAs!?27^E_7C`*4C^>zPY{X584GfyLlmsZcy)JvPXkKzue*D@=#VqSe0bk9go;OOvqA`n( z3qa9pn{Bd!vU`3b*(`+#6VNNp>Wt&O%Gw@M8~#cLWB9-jJAK;qd=Fe?d!w~ZLs+AWZiNMLVY0Cr-DeiT9`la9t( z24Y^mnAe#3#Q@@__c(@>E)#n+_(INXXO+r|&J~<{zpV;6aNuyu)M@R{L^){VaOc_n zV&`D_iu8YFHKS8K<(1f2^uE}Ee|COk(ei9AL(lv`I)?x`YET)r&o>Iq0UlAx z8$(er*;0bz=F>tG9=RXAX%ugNzSB=xQ$f4>e=|uc|IzSCyA}u7Cj25O9y+_-8>)* zEzU_n6W6y2V_!YEvY6flxAPvY7v0pua-5X1Tz0AA*3q9v3AGXa?-`g4*A* zy%ZiVJ%@;`kd}|S3mNc>fmr}U>g5w$X@Kx zphTBL&P{ZbpI}ZoySrXkud(86poj?$%99C_-${w!4F2LfyJy#Y+ucB8SB$;j!d?SD zrg)X_vnLyQa|DKRJnNV`l*tO8XuY2I9&SFl(p0IiH$fozw@k2pG4{14RO!mgte0PP zwmz~?rNh3gh+N#M+A6B$Ru@YQOs-R{82D-+9bWdx@3f=zJKTj`1Z_ko-CfDW7#9iT zpvF5z{k}Lmgv@TOtnG%l?aSP^ObKb?YLpd`XO1HC*e24x`=m+xr-fKmC}w_0m_Qm^ zI5fq@d_Sk?7zH&k|rsb$mBB%U5?!}CI7tbnP*z$Cc zhh7fQ2^gg&7N|?CRElTHjtx_N;&gpODI%X^=SIw$H0|!5FAORjKdxGWgBdh0qx^H6lIWFTR4#I6~`{*e_Dro zNR)_P(8wx_&K2B;og>N-Kndq06spocW(! z&$3$=|9Zw}} z`6&j?T^SinOJN-l%EDU>m_)*qgTM36K4$x7588N1Z;s zo?T%H&~Z=w#G?higblqyEokzbCY&?~;KNTlQt*-NbWF_oUfTvr3r#<+_$;Is!BJd~ zqDa|7D}<1op+4?;Dk(Y7pXZT6O_r(feRHaOjtY96s)e%hQp_3h?u^y_wy_wz@x;J~ zc3OTz^is3UKksJ9HfIja}|~# zTdCWp=A5>eoROkK*Z1)qCf?N8FVR?rDp%vCa}Av3O!(jvW(_W7sQPnbt!7CXIjTZY zn(|4W_Q%GWk6k0$1QlT{O27FFoZ2)fXd6FL@Rh@pdtLT&<9S_x_Ugo~ z3RlBo?U~~09t${mDth$HB{G9FFcUBH??eZT@M!-`D*umkfE=$1Gb6kJI9>#}4e;lc z5IY!UKp;VeN`9@7P)?wdSLWCc%0WpKWF_HDg=Cwaqu6L|3^>fe+!u*2l%fq88rkv!wFP#2?I1fj-OZgtr=#9 XVIBa|pukgZJ^;!Og9!dexS;<5d`S61