diff --git a/.gitignore b/.gitignore index d78d7e0..c34e7ae 100644 --- a/.gitignore +++ b/.gitignore @@ -1,4 +1,4 @@ /.idea/ __pycache__ -pojo/__pycache__ +pojo/__pycache__/ .~contact.xlsx \ No newline at end of file diff --git a/ModemPool.py b/ModemPool.py new file mode 100644 index 0000000..0dcdcd3 --- /dev/null +++ b/ModemPool.py @@ -0,0 +1,72 @@ +import re +import time + +import serial +from serial import Serial + +from utils.excel_reader import ExcelHelper + + +class ModemPool: + BAUDRATE = 115200 + my_phone = "my_phone" + phone_number_position = 10 + + def __init__(self, port_list: list): + self._port_list = port_list + self._serial_list = [] + self._excel_helper = ExcelHelper() + for port in self._port_list: + ser = serial.Serial(port, self.BAUDRATE, timeout=1) + self._serial_list.append(ser) + + def reset_all_modems(self): + for ser in self._serial_list: + self._send_command("AT+CFUN=1,1\r", ser) + # send_command("AT+RESET\r", ser) + # wait for 20 second, so that the modem can init all the sims + time.sleep(20) + + def get_raw_phone_number(self, slot_position): + for index, ser in enumerate(self._serial_list): + print("will get phone number for slot({}) SIM({}), port:{}".format(slot_position, index + 1, ser.port)) + self._select_sim_storage(ser) + msg = self._execut_USSD_cmd("AT+CUSD=1, *132#\r", ser) + if "Unfortunately" in str(msg): + print("error for for slot({}) SIM({}), port:{}".format(slot_position, index + 1, ser.port)) + return + # find phone number + match = re.search(r'33\d{9}', str(msg)) + phone_number = match.group(0) + print("phone is " + phone_number) + if phone_number: + self._excel_helper.write_phone(phone_number) + # 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) + self.get_own_number(ser) + + def get_own_number(self, ser: Serial): + print("saved phone number: " + str(self._send_command(f'AT+CPBR={self.phone_number_position}\r', ser))) + + def _select_sim_storage(self, ser): + # use SIM Card storage + cmd_sm = "AT+CPBS=\"SM\"\r" + self._send_command(cmd_sm, ser) + + def _send_command(self, cmd: str, ser, wait_time_in_s: int = 0) -> bytes: + print("send command {}".format(cmd)) + ser.write(cmd.encode()) + 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 + + def _execut_USSD_cmd(self, cmd, ser) -> str: + # the timeout for ussd command can be 120 s in mac + return str(self._send_command(cmd, ser, 120)) diff --git a/SIMError.py b/SIMError.py new file mode 100644 index 0000000..64f0f60 --- /dev/null +++ b/SIMError.py @@ -0,0 +1,5 @@ +from enum import Enum + + +class SIMError(Enum): + SIM_DISABLED = "SIM_DISABLED" diff --git a/appointment.json b/appointment.json new file mode 100644 index 0000000..c93daab --- /dev/null +++ b/appointment.json @@ -0,0 +1,12 @@ +{ + "type": "service_account", + "project_id": "appointment-ba40a", + "private_key_id": "9906c564d9dd2e3fce879eebe94dfb47d4cd65a0", + "private_key": "-----BEGIN PRIVATE KEY-----\nMIIEvgIBADANBgkqhkiG9w0BAQEFAASCBKgwggSkAgEAAoIBAQDN2FTfsJYwwLzr\n+08udYl4IRzLss1ZLMp4gPjFjkawMzoEGPjrgeeqAgLk9faJGp6NiRciP5JRffd/\nNVvt0KnfmMjBNnMtyXqT/mCCTY/LPZag1cKkMHAuzHXwVXz7DNnndr6k3LQ5ixe/\nXgdUgcMKC/p6OPBGZB4OS+3Xt8N9gYsrkk9fi+wlJ/nzPVjHEVMFtYO1RngBpNmc\n9EOcHZzaM7+2G+hguJBG9IoNOCKVcG9xR732G+njkfUarFdK9FhT5V4dh/dE2/pT\nZrsrg2Bf4wShrWxjzSrX1+7oM0tPonmwaw19GfWnp6cpo8V1C27u3mRBCugqwzLu\nEvXDC/hrAgMBAAECggEABRyfsvRkLm7C4ktZ4on5sXmFCQv2LIY/uvFc/C71029a\nO/rQx6xwr9if8Mao6iu2j0Y9xFR20j5CFK8jCstZRJu7NI0hHBx6Rk2VYPcDIKV1\nZaYZUNGBH7BlJ2RAF83wZV6eCmMOuLUbEF4J6Y/VY5z7ieh7EwxucKVzER3XpXi0\nMMfXlJIpaUrg6/llwPRdPVdCFmNrtNi1D8exNS0T1/mt/CMOg13ZEkSJcaD7NOju\nckiU+IXigmLbADOcZ4ieGqO8rVfJsr1/y7EbVJoJjRtQq56tFT+G2mIJ5StCS0+u\nW2qsoPC3hGBM04OPsprsXjFzPgaItghDyB0zzfS0/QKBgQDloKf+qHP8LH+ot5qN\nEwo6ysRYzduCY0Bw4nDx5tUq7mTsvzbbeLqwE6x52y1BvhpKxqZMMZ+o9IlNL9wq\nJztHeuSqheBPl+43j5bYQrcqaG42ZdjrSXjO8a4KDWL1Ri2vz+QgWzarGLRY+0ni\nvasr2/mg14CPBfJSgSwH5oEx7QKBgQDlfHBaj1aLTwvaLsQbUYqkgaWtM3goTgb9\nTA0Ivkhc/gz2Yo6/6/IqvZtRhq4FRbyk/JPgfNwKx/eHRO0uS+igVxnRs7ziL3Qq\nPGUoPouJtE/MHtVCRlrzEz8Br5yXYfB5zDsaLgYjnOZgED8cp2Qy2pwuYLA4LgWF\nhu/oaO1otwKBgGPauBMqh71qUF068k9Ur0cfs8B2THVn2bb9EWZwHdScdHDrOdy4\npF47P+6BnC2RkHdh6SELF0XuiOJy5IfEJagQze1FaGTUSbgJjewfHu2nGf43zduL\nSKidOjSO27CTQvzIJ4jWgXBnvs1PATNDjXL2JpiF/haz3Et6dn49A4OFAoGBALyV\nZj8FS7lvW+4QQFeyypwlbmDGyxdUB6pftNZaiFzi6QQQOf69hmRZLCny406x4DQ0\n29C+ypSRf3hJzB8fgitBaJZLfgzhsjSDLR3FSCYBZxH2xImSB2t5hW19QtGkSlnM\n20TITYM2jJqkvzhs1opz26TBEA8awq7YFI6Iq5BBAoGBANFxoCqM6TmlMF32wRcW\nccLz7GiInHSsVvhORZW8fIVdgVWNIo4Xon83WmRBCOHr0QoViHlJqNTZZDKwXlc5\nKl5sdfIlKPLVsthQrV6PyQhRMH0BRYjldiqMSKe9Q3v0SjHDbOVqG9C5pRIqWuAE\nALQsXpH7Ze0e5amwGYJUYBeG\n-----END PRIVATE KEY-----\n", + "client_email": "appointment-ba40a@appspot.gserviceaccount.com", + "client_id": "117807159722779924783", + "auth_uri": "https://accounts.google.com/o/oauth2/auth", + "token_uri": "https://oauth2.googleapis.com/token", + "auth_provider_x509_cert_url": "https://www.googleapis.com/oauth2/v1/certs", + "client_x509_cert_url": "https://www.googleapis.com/robot/v1/metadata/x509/appointment-ba40a%40appspot.gserviceaccount.com" +} diff --git a/card_pool.py b/card_pool.py new file mode 100644 index 0000000..b42428c --- /dev/null +++ b/card_pool.py @@ -0,0 +1,51 @@ +import logging + +import serial + +PORT = "/dev/tty.usbmodem11301" + +BAUDRATE = 115200 + + +class CardPool: + + def __init__(self, port): + self.logger = logging.getLogger("CardPool") + self._serial = serial.Serial(port, BAUDRATE, timeout=1) + + def _send_command(self, cmd: str) -> bytes: + print("send command {}".format(cmd)) + self._serial.write(cmd.encode()) + msg = self._serial.read(100) + self.logger.info(msg) + return msg + + # info: after reset, we need to restart modem pool + def reset(self): + self._send_command("AT+NEXT00\r") + + def switch_to_next(self): + self._send_command("AT+NEXT11\r") + + def switch_to_slot(self, slot_number: int): + if slot_number < 10: + self._send_command("AT+SWIT00-000{}\r".format(slot_number)) + else: + self._send_command("AT+SWIT00-0{}\r".format(slot_number)) + + # not work for the pool + def find_current_slot(self): + self._send_command("AT+USIM\r") + + +if __name__ == '__main__': + card_pool = CardPool(PORT) + # print(card_pool.find_current_slot()) + card_pool.reset() + # card_pool.switch_to_next() + # reset modem pool + # for port in get_devices_ports(): + # ser = serial.Serial(port, BAUDRATE, timeout=1) + # send_command("AT+RESET\r", ser) + # ser.close() + # card_pool.switch_to_slot(12) diff --git a/commandor.py b/commandor.py index c87e7a4..7597e39 100644 --- a/commandor.py +++ b/commandor.py @@ -32,6 +32,6 @@ class Commandor: if __name__ == '__main__': commandor = Commandor() - contact = ContactPojo("0608090706", "1234567890", "Willy", "Arnold", "AZEER", "test@test.fr") - commandor.start_page(contact) - # commandor.send_otp("12345") + # contact = ContactPojo("0649614591", "EE6045381", "TANG", "Wenqian", "AZEER", "clench_groom02@icloud.com") + # commandor.start_page(contact) + commandor.send_otp("918116") diff --git a/db/DbManager.py b/db/DbManager.py new file mode 100644 index 0000000..542471a --- /dev/null +++ b/db/DbManager.py @@ -0,0 +1,42 @@ +import datetime +import json + +import firebase_admin +from firebase_admin import credentials, firestore + +from pojo.ReserveResultPojo import ReserveResultPojo, PublishType +from pojo.contact_pojo import ContactPojo + +ERROR_COLLECTION_NAME = "error_items" +TIMEOUT = "timeout_items" + + +class DataManager: + def __init__(self): + cred = credentials.Certificate("appointment.json") + self._app = firebase_admin.initialize_app(cred) + self._db = firestore.client() + + def get_all_error_items(self): + pass + + def save(self, result: ReserveResultPojo): + if result.type == PublishType.SUCCESS: + # get id + id = result.url.split("/")[-1] + result.id = id + document_name = str(datetime.date.today()) + doc_ref = self._db.collection(document_name).document(result.id) + doc_ref.set(result.to_firestore_dict()) + else: + doc_ref = self._db.collection(ERROR_COLLECTION_NAME).document(result.phone) + doc_ref.set(result.to_firestore_dict()) + + def save_timeout_contact(self, contact: ContactPojo): + doc_ref = self._db.collection(TIMEOUT).document(str(contact.phone)) + doc_ref.set(contact.to_firestore_dict()) + + +if __name__ == '__main__': + manager = DataManager() + manager.save() diff --git a/db/__init__.py b/db/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/main.py b/main.py index bb0acf7..03a86eb 100644 --- a/main.py +++ b/main.py @@ -1,4 +1,5 @@ import datetime +import json import logging import re import sys @@ -6,34 +7,47 @@ import time from gsmmodem import GsmModem +from ModemPool import ModemPool +from card_pool import CardPool from commandor import Commandor -from excel_reader import ExcelReader +from db.DbManager import DataManager +from pojo.ReserveResultPojo import ReserveResultPojo, PublishType +from utils.excel_reader import ExcelHelper from pojo.serial_modem import SerialModem from utils.logging import init_logger +from utils.message_receiver import MessageReceiver BAUDRATE = 115200 OTP_TIMEOUT = 60 -sms_received = False +is_finished = False commandor = Commandor() +timeout_contact_list = [] +PORT = "/dev/tty.usbmodem1301" + +# ser = serial.Serial(PORT, BAUDRATE, timeout=1) + +db_manager = DataManager() def get_devices_ports() -> list: - return ["/dev/tty.usbmodem121101", - "/dev/tty.usbmodem121103", - "/dev/tty.usbmodem121105", - "/dev/tty.usbmodem121107", - "/dev/tty.usbmodem121201", - "/dev/tty.usbmodem121203", - "/dev/tty.usbmodem121205", - "/dev/tty.usbmodem121207", - "/dev/tty.usbmodem121301", - # "/dev/tty.usbmodem1121303", - "/dev/tty.usbmodem121305", - "/dev/tty.usbmodem121307", - "/dev/tty.usbmodem121401", - "/dev/tty.usbmodem121403", - "/dev/tty.usbmodem121405", - "/dev/tty.usbmodem121407"] + return [ + "/dev/tty.usbmodem1121101", + "/dev/tty.usbmodem1121103", + "/dev/tty.usbmodem1121105", + "/dev/tty.usbmodem1121107", + "/dev/tty.usbmodem1121201", + "/dev/tty.usbmodem1121203", + "/dev/tty.usbmodem1121205", + "/dev/tty.usbmodem1121207", + "/dev/tty.usbmodem1121301", + ## "/dev/tty.usbmodem1121303", + "/dev/tty.usbmodem1121305", + "/dev/tty.usbmodem1121307", + "/dev/tty.usbmodem1121401", + "/dev/tty.usbmodem1121403", + "/dev/tty.usbmodem1121405", + "/dev/tty.usbmodem1121407" + ] def has_sim(ser) -> bool: @@ -49,21 +63,26 @@ def has_sim(ser) -> bool: def send_command(cmd: str, ser) -> bytes: print("send command {}".format(cmd)) ser.write(cmd.encode()) + time.sleep(10) msg = ser.read(100) - logger.info(msg) + print(msg) return msg -def get_phone_number(): +def get_phone_number(ser): cmd = "AT+CNUM\r" - send_command(cmd) + send_command(cmd, ser) + + +def execut_USSD_cmd(cmd, ser): + send_command(cmd, ser) def create_modem_for_port(port: str) -> SerialModem: logger.info('Initializing modem... for ' + port) # Uncomment the following line to see what the modem is doing: init_logger() - modem = GsmModem(port, BAUDRATE) + modem = GsmModem(port) modem.connect('0000') number = modem.ownNumber logger.info("The SIM card phone number is:") @@ -77,19 +96,26 @@ def create_modem_for_port(port: str) -> SerialModem: return serial_modem +def timeout_occurred(serial_modem: SerialModem): + timeout_contact_list.append(serial_modem.contact) + db_manager.save_timeout_contact(serial_modem.contact) + + def start_to_handle_sms(serial_modem: SerialModem): serial_modem.modem.smsReceivedCallback = handle_sms - global sms_received - sms_received = False + global is_finished + is_finished = False serial_modem.modem.smsTextMode = False logger.info('Waiting for SMS message, for phone number ' + str(serial_modem.phone_number)) listen_at = time.time() - while not sms_received: + while not is_finished: time.sleep(2) # check whether timeout now = time.time() if (listen_at + OTP_TIMEOUT) < now: logger.info("time out for {}, switch to next contact".format(serial_modem.phone_number)) + # save the contact in timeout + timeout_occurred(serial_modem) return return @@ -103,9 +129,11 @@ def handle_sms(sms): otp = match.group(0) logger.info("otp is " + otp) commandor.send_otp(otp) - time.sleep(20) - global sms_received - sms_received = True + # wait for the sms for 20 seconds + global is_finished + while not is_finished: + time.sleep(2) + is_finished = True def init_modems() -> list: @@ -113,24 +141,71 @@ def init_modems() -> list: for port in get_devices_ports(): modems.append(create_modem_for_port(port)) # read the contact, and contact the 2 objects together - excel_reader = ExcelReader() + excel_reader = ExcelHelper() contacts = excel_reader.read_contacts() - for modem in modems: - contact = [contact for contact in contacts if contact.ccid == modem.ccid][0] - modem.phone_number = contact.phone - modem.contact = contact + for index, modem in enumerate(modems): + contact = [contact for contact in contacts if contact.ccid == modem.ccid] + if len(contact) > 0: + modem.phone_number = contact[0].phone + modem.contact = contact[0] + else: + logger.info("contact not found for {}, position:{}".format(modem.ccid, index + 1)) return modems +def on_message_received(ch, method, properties, body): + logger.info(" [x] Received {} {}".format(body, datetime.datetime.now())) + # parse the received message + result = ReserveResultPojo.from_json(body) + print(result) + db_manager.save(result) + # set the flag to True + global is_finished + is_finished = True + + +# save the result to db + + +def start_listen(): + logger.info("start to listen to message queue") + receiver = MessageReceiver() + receiver.start_listener(on_message_received) + + +def read_all_the_phone_number(): + card_pool = CardPool("/dev/tty.usbmodem11301") + slot_number = 1 + slot_sum = 32 + card_pool.reset() + + for i in range(2, slot_sum + 1): + # i = 2 + card_pool.switch_to_slot(i) + modem_pool = ModemPool(get_devices_ports()) + modem_pool.reset_all_modems() + modem_pool.get_raw_phone_number(i) + + if __name__ == '__main__': - # enable verbose logs for all port init_logger() logger = logging.getLogger() logger.addHandler(logging.StreamHandler(stream=sys.stdout)) - # create modems for all the available port - modem_list = init_modems() - # create listeners for chaque modem + read_all_the_phone_number() + # start_listen() + # reset the sim card pool + # send_command("AT+SWIT01-0001\r", ser) - for modem in modem_list: - commandor.start_page(modem.contact) - start_to_handle_sms(modem) + # 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() diff --git a/pojo/ReserveResultPojo.py b/pojo/ReserveResultPojo.py new file mode 100644 index 0000000..981131f --- /dev/null +++ b/pojo/ReserveResultPojo.py @@ -0,0 +1,29 @@ +from dataclasses import dataclass +from enum import Enum +from dataclasses_json import dataclass_json + + +class PublishType(Enum): + SUCCESS = "SUCCESS" + ERROR = "ERROR" + + +@dataclass_json +@dataclass +class ReserveResultPojo: + type: PublishType + phone: str + message: str + url: str + id = None + + def to_firestore_dict(self): + dest = { + u'type': self.type.value, + u'id': self.id, + u'message': self.message, + u'phone': self.phone, + u'url': self.url + } + + return dest diff --git a/pojo/__pycache__/__init__.cpython-39.pyc b/pojo/__pycache__/__init__.cpython-39.pyc deleted file mode 100644 index c9fcfb6..0000000 Binary files a/pojo/__pycache__/__init__.cpython-39.pyc and /dev/null differ diff --git a/pojo/__pycache__/contact_pojo.cpython-39.pyc b/pojo/__pycache__/contact_pojo.cpython-39.pyc deleted file mode 100644 index edc9673..0000000 Binary files a/pojo/__pycache__/contact_pojo.cpython-39.pyc and /dev/null differ diff --git a/pojo/contact_pojo.py b/pojo/contact_pojo.py index da1e156..b438b18 100644 --- a/pojo/contact_pojo.py +++ b/pojo/contact_pojo.py @@ -17,3 +17,15 @@ class ContactPojo: self.first_name = first_name self.ccid = ccid self.mail = mail + + def to_firestore_dict(self): + dest = { + u'phone': self.phone, + u'passport': self.passport, + u'last_name': self.last_name, + u'first_name': self.first_name, + u'mail': self.mail, + u'ccid': self.ccid + } + + return dest diff --git a/excel_reader.py b/utils/excel_reader.py similarity index 74% rename from excel_reader.py rename to utils/excel_reader.py index 65996d3..16e30cc 100644 --- a/excel_reader.py +++ b/utils/excel_reader.py @@ -5,7 +5,16 @@ import pandas as pandas from pojo.contact_pojo import ContactPojo -class ExcelReader: +class ExcelHelper: + + def __init__(self): + self._df = pandas.Series() + + def write_phone(self, phone_number): + new_df = pandas.Series([phone_number]) + self._df = pandas.concat([self._df, new_df]) + self._df.to_excel("phone_list.xlsx") + # read the contact list from the exel file def read_contacts(self) -> list: contact_list_in_json = pandas.read_excel(r'./contact.xlsx').to_json(orient='records') @@ -26,6 +35,5 @@ class ExcelReader: if __name__ == '__main__': - reader = ExcelReader() - data = reader.read_contacts() - print(data) + helper = ExcelHelper() + helper.write_phone("88649614591") diff --git a/utils/logging.py b/utils/logging.py index dd59c98..8cc9fab 100644 --- a/utils/logging.py +++ b/utils/logging.py @@ -2,7 +2,7 @@ import logging def init_logger(): - logging.basicConfig(filename="scrapy.log", + logging.basicConfig(filename="appointment.log", filemode='a', format='%(asctime)s,%(msecs)d %(name)s %(levelname)s %(message)s', datefmt='%D:%H:%M:%S', diff --git a/utils/message_receiver.py b/utils/message_receiver.py new file mode 100644 index 0000000..3c4368c --- /dev/null +++ b/utils/message_receiver.py @@ -0,0 +1,27 @@ +import logging +import threading +from datetime import datetime + +import pika + +APPOINTMENT_QUEUE = "APPOINTMENT_QUEUE" + + +class MessageReceiver: + def __init__(self): + self._credentials = pika.PlainCredentials('scrapy_rabbitmq', '4x!hReCbA5v3heKWfPJV-Y') + + def start_listener(self, callback): + t = threading.Thread(target=self._run, args=(callback,)) + t.start() + + def _run(self, callback): + connection = pika.BlockingConnection( + pika.ConnectionParameters(host='rabbitmq.lpaconsulting.fr', port=6672, credentials=self._credentials)) + channel = connection.channel() + channel.queue_declare(queue=APPOINTMENT_QUEUE) + channel.basic_consume(queue=APPOINTMENT_QUEUE, + auto_ack=True, + on_message_callback=callback) + print(' [*] Waiting for messages. To exit press CTRL+C') + channel.start_consuming() diff --git a/utils/phone_list.xlsx b/utils/phone_list.xlsx new file mode 100644 index 0000000..02f318f Binary files /dev/null and b/utils/phone_list.xlsx differ