main.py 10.4 KB
#!/usr/bin/env python

import logging
from logging.handlers import TimedRotatingFileHandler
import ConfigParser

config = ConfigParser.RawConfigParser()
config.read('config.ini')

PORT = config.get('globals','PORT')
BAUDRATE = config.getint('globals', 'BAUDRATE')
PIN = None # SIM card PIN (if any)
PIN_TRX = config.get('globals', 'PIN_TRX')

BASE_CHIPINFO = config.get('globals', 'BASE_CHIPINFO')
CHIPINFO = BASE_CHIPINFO

AAA_HOST = config.get('globals', 'AAA_HOST')
CITY = config.get('globals', 'CITY')
PRODUCTS = config.get('globals', 'PRODUCTS')

REDIS_HOST = config.get('globals', 'REDIS_HOST')
REDIS_PORT = config.getint('globals', 'REDIS_PORT')
REDIS_TTL = config.getint('globals', 'REDIS_TTL')
REDIS_DISABLE_PULL_TTL = config.getint('globals', 'REDIS_DISABLE_PULL_TTL')

PULL_INTERVAL = config.getint('globals', 'PULL_INTERVAL')
SLEEP_AFTER_TOPUP = config.getint('globals', 'SLEEP_AFTER_TOPUP')
SLEEP_BETWEEN_BALANCE_N_TOPUP = config.getint('globals', 'SLEEP_BETWEEN_BALANCE_N_TOPUP')
TOPUP_USSD_TIMEOUT = config.getint('globals', 'TOPUP_USSD_TIMEOUT')

MIN_BALANCE = config.getint('globals', 'MIN_BALANCE')

PULL_COUNT = 0
MSISDN = ''
BALANCE = 0

logger = None

from gsmmodem.modem import GsmModem
from gsmmodem.exceptions import TimeoutException

from time import sleep
from time import strftime

import redis
import requests

import sate24 
import xltunai

redis_client = redis.StrictRedis(host=REDIS_HOST, port=REDIS_PORT, db=0)

def handleSms(sms):
    logger.info(u'== SMS From: {0}; Time: {1}; Message: {2}'.format(sms.number, sms.time, sms.text))
    
    if not xltunai.isValidSender(sms.number):
        return
        
    if sms.text.find('Terimakasih, transaksi CASH IN ke akun') >= 0:
        logger.info('handleSms: CASH IN, aktivasi pull jika non aktif')
        enablePull()
        return
    
        
    destination = xltunai.getDestinationFromMessage(sms.text)
    if destination == '':
        logger.warning('handleSms: gagal parsing nomor tujuan')
        return
        
    nominal = xltunai.getNominalFromMessage(sms.text)
    if nominal == '':
        logger.warning('handleSms: gagal parsing nominal')
        return
    
    sn = xltunai.getSNFromMessage(sms.text)
    if sn == '':
        logger.warning('handleSms: gagal parsing SN')
        return
    
    
    response_code = xltunai.getResponseCodeByMessage(sms.text)
    
    request_id = getRequestIdByNominalDestination(nominal, destination)
    if not request_id:
        logger.info('Unknown request id for nominal:{0} destination:{1}'.format(nominal, destination))
        
    pushTopupStatus(request_id, response_code, sms.text)
    

def getRequestIdByNominalDestination(nominal, destination):
    redis_key = sate24.keyByNominalDestination(CHIPINFO, nominal, destination)
    return redis_client.spop(redis_key)
    
def pushTopupStatus(request_id, response_code, _message):
    global BALANCE
    
    message = "{0} -- Prev balance: {1}".format(_message, BALANCE)
    
    timestamp = strftime('%Y%m%d%H%M%S')
    
    if response_code == '00':
        sn = xltunai.getSNFromMessage(message)
        message = 'SN={0};{1}'.format(sn, message)
        
    push_message = CHIPINFO + '$' + message
    
    payload = {
        'trans_id': request_id,
        'trans_date': timestamp,
        'resp_code': response_code,
        'ussd_msg': push_message
    }
    
    url = AAA_HOST + '/topup'
    
    try:
        logger.info('Sending topup status to AAA')
        logger.info(payload)
        r = requests.get(url, params=payload)
    except:
        return
        
def getIsDisableRedisKey():
    return CHIPINFO + '.pulldisable'
    
def isPullEnable():
    redis_key = getIsDisableRedisKey()
    result = 'FALSE'
    
    try:
        result = redis_client.get(redis_key)
        redis_client.expire(redis_key, REDIS_DISABLE_PULL_TTL)
    except:
        return False
        
    if not result:
        return True
    
    return result == 'FALSE'

def enablePull():
    logger.info('Enabling Pull')
    redis_key = getIsDisableRedisKey()
    try:
        redis_client.set(redis_key, 'FALSE')
        redis_client.expire(redis_key, REDIS_DISABLE_PULL_TTL)
    except:
        return

def disablePull():
    logger.info('Disabling Pull')
    redis_key = getIsDisableRedisKey()
    try:
        redis_client.set(redis_key, 'TRUE')
        redis_client.expire(redis_key, REDIS_DISABLE_PULL_TTL)
    except:
        return
            
def topupTask(task, modem):
    if not task:
        return
    
    if task['status'] != 'OK':
        return
    
    checkSignal(modem)
    
    redis_key = sate24.keyByRequestId(CHIPINFO, task['requestId'])
    redis_client.set(redis_key, task)
    redis_client.expire(redis_key, REDIS_TTL)
    
    
    nominal = xltunai.getNominalFromProduct(task['product'])
    intl_destination = xltunai.toInternationalNumber(task['destination'])
    
    redis_key = sate24.keyByNominalDestination(CHIPINFO, nominal, intl_destination)
    redis_client.sadd(redis_key, task['requestId'])
    redis_client.expire(redis_key, REDIS_TTL)
    
    pushTopupStatus(task['requestId'], '68', 'Siap mengirimkan trx ke operator')

    message = 'Executing USSD'
    response_code = '68'
    
    ussd_command = xltunai.getTopupUSSDCommand(task['destination'], task['product'], PIN_TRX)
    
    logger.info(u'Executing {0}'.format(ussd_command))
    try:
        response = modem.sendUssd(ussd_command, TOPUP_USSD_TIMEOUT) 
        
        message = response.message.strip()
        logger.info(u'USSD response: {0}'.format(message))
        
        response_code = xltunai.getResponseCodeByUSSDResponse(message)
        
        if response.sessionActive:
            response.cancel()
            
    except TimeoutException:
        message = "USSD Error: Timeout when executing USSD topup"
        logger.warning(message)
    except:
        message = "USSD Error: Something wrong when executing USSD topup"
        logger.warning(message)
    
    pushTopupStatus(task['requestId'], response_code, message)

def pull(modem):
    global PULL_COUNT
    global BALANCE
    
    if not isPullEnable():
        return
        
    r = None
    try:
        r = requests.get(AAA_HOST + '/pull?city=' + CITY + '&nom=' + PRODUCTS)
    except:
        logger.warning("Error requesting to AAA")
        return

    if not r:
        return
        
    task = sate24.parsePullMessage(r.text)
    
    if task['status'] == 'OK':
        
        logger.info(r.text)
        if not checkBalance(modem) or BALANCE == 0:
            pushTopupStatus(task['requestId'], '40', 'Transaksi dibatalkan karena gagal check balance')
            sleep(SLEEP_BETWEEN_BALANCE_N_TOPUP)
            return
            
        
        sleep(SLEEP_BETWEEN_BALANCE_N_TOPUP)
        
        topupTask(task, modem)
        sleep(SLEEP_AFTER_TOPUP)

def pullLoop(modem):
    while (True):        
        pull(modem)
        sleep(PULL_INTERVAL)

def checkSignal(modem):
    logger.info('Signal: {0}'.format(modem.signalStrength))
    try:
        redis_client.set(CHIPINFO + '.signal', modem.signalStrength)
        redis_client.expire(CHIPINFO + '.signal', 3600*24)
    except:
        logger.warning("Can not save signal strength to redis")
    
def checkBalance(modem):
    global BALANCE
    
    #BALANCE = 0
    try:
        
        ussd_string = '*123*120#'
        response = modem.sendUssd(ussd_string, 30) 
        BALANCE = xltunai.getBalanceFromUSSDResponse(response.message)
        logger.info('Balance: {0}'.format(BALANCE))
        if response.sessionActive:
            response.cancel()
            
        if BALANCE != 0 and BALANCE < MIN_BALANCE:
            logger.info('Disabling pull, balance {0} < {1}'.format(BALANCE, MIN_BALANCE))
            disablePull()
            
    except:
        logger.warning('Error when requesting BALANCE by USSD')
        return False
    
    try:
        redis_client.set(CHIPINFO + '.balance', BALANCE)
    except:
        logger.warning('Failed to save balance to redis')
        
    return True
    
def getSIMCardInfo(modem):
    try:
        ussd_string = xltunai.getSIMCardInfoUSSDCommand()
        response = modem.sendUssd(ussd_string)
        
        message = response.message.strip()
        logger.info('SIM INFO: {0}'.format(message))
        if response.sessionActive:
            response.cancel()
        
        return message
        
    except:
        logger.warning('Error when requesting SIM card info by USSD')
        return ''

def updateChipInfo(msisdn):
    global BASE_CHIPINFO
    global CHIPINFO
    global MSISDN
    
    MSISDN = msisdn
    if CHIPINFO.find(msisdn) == -1:
        CHIPINFO = BASE_CHIPINFO + '_' + msisdn
        
    logger.info('CHIPINFO: {0}'.format(CHIPINFO))
        
def main():
    global logger
    
    log_format = '%(asctime)s %(levelname)s: %(message)s'
    
    logging.basicConfig(format=log_format, level=logging.INFO)
    logger = logging.getLogger(__name__)
    
    logger_formatter = logging.Formatter(log_format)
    logger_handler = TimedRotatingFileHandler('logs/log', when='midnight')
    logger_handler.setFormatter(logger_formatter)
    logger.addHandler(logger_handler)
    
    requests_logger = logging.getLogger('requests')
    requests_logger.setLevel(logging.WARNING)
    
    logger.info('Initializing modem...')
    
    modem = GsmModem(PORT, BAUDRATE, smsReceivedCallbackFunc=handleSms)
    modem.smsTextMode = True 
    modem.connect(PIN)
    
    logger.info('Waiting for network coverage...')
    modem.waitForNetworkCoverage(10)
    
    logger.info('Modem ready')
    
    simcard_info = getSIMCardInfo(modem)
    msisdn = xltunai.getMSISDNFromSIMCardInfo(simcard_info)
    imsi = xltunai.getIMSIFromSIMCardInfo(simcard_info) 
    logger.info('MSISDN: {0} -- IMSI: {1}'.format(msisdn, imsi))
    
    updateChipInfo(msisdn)
    
    sleep(2)
    
    checkSignal(modem)
    
    logger.info('Process stored SMS')
    try:
        modem.processStoredSms()
    except:
        logger.warning('Failed on Process stored SMS')
    
    logger.info('Delete stored SMS')
    try:
        modem.deleteMultipleStoredSms()
    except:
        logger.warning('Failed on delete SMS')
    
    sleep(2)
    
    checkBalance(modem)
    sleep(SLEEP_BETWEEN_BALANCE_N_TOPUP)
    
    enablePull()
    pullLoop(modem)
    logger.info('Waiting for SMS message...')    
    try:    
        modem.rxThread.join(2**31) # Specify a (huge) timeout so that it essentially blocks indefinitely, but still receives CTRL+C interrupt signal
    finally:
        modem.close();

if __name__ == '__main__':
    main()