Update servidor/servidor.py

#! /usr/bin/python
# -*- coding: utf-8 -*-




import sys
import socket
import threading
from decimal import Decimal
import datetime
import psycopg2
import db
import time








class Servidor(threading.Thread):
    """Hilo principal para escuchar puerto"""
    def __init__(self):
        super(Servidor, self).__init__()
        port = 22222
        self.puertos = (port, port + 1)
        self.socket = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
        #ip = '192.168.0.222'
        ip = '65.111.166.252'
        for puerto in self.puertos:
            try:
                self.socket.bind((ip, puerto))
                self.socket.listen(100)
            except:
                pass
            else:
                print 'Escuchando el puerto %d' % puerto
                break
        self.start()




    def run(self):
        while(True):
            sc, addr = self.socket.accept()
            Unidad(sc, addr)








class Unidad(threading.Thread):
    """Hilos secundarios por cada unidad"""
    def __init__(self, socket, addr):
        super(Unidad, self).__init__()
        self.addr = addr
        self.s = socket
        self.datetime = datetime.datetime.now()
        print "recibo conexión de "+ str(addr[0]) + ':' + str(addr[1])
        print self.datetime
        self.siguiente_back = None
        self.instr = ''
        self.params = ()
        self.conectado = True
        self.start()




    def run(self):
        timeout = 30
        minutos = 5
        repeat = minutos * 60 / timeout
        self.s.settimeout(timeout)  # segundos
        params = db.conexion()
        conexion = psycopg2.connect(params)
        conexion.set_isolation_level(
            psycopg2.extensions.ISOLATION_LEVEL_AUTOCOMMIT)
        self.cursor = conexion.cursor()
        i = 0
        self.salida = None
        if self.recibir('I', False):
            self.config_unidad()
            while self.conectado:
                if not self.recibir():
                    i += 1
                    if i >= repeat:
                        break
                    else:
                        self.revisar_estado()
                else:
                    self.leer_recibido_unidad()
                    i = 0
            self.cursor.execute("""UPDATE tracking_unidad SET conectado='0'
                WHERE ID=%d""" % self.id)
        elif self.instr == 'PC':
            self.config_servidor()
            while self.conectado:
                self.recibir()
                self.leer_recibido_servidor()
        self.cerrar("ERR00 TIMEOUT")




    def config_servidor(self):
        self.pc = True
        self.instr = ''
        self.celular = self.pop()
        sql = """SELECT id,empresa_id FROM tracking_despacho
            WHERE celular = '%s'""" % (self.celular)
        print sql
        self.cursor.execute(sql)
        despacho_id = self.cursor.fetchone()
        try:
            self.id = despacho_id[0]
            self.empresa = despacho_id[1]
            sql = """SELECT ruta_id,lado FROM tracking_despachoruta WHERE
            despacho_id=%d""" % self.id
            print sql
            self.cursor.execute(sql)
            self.rutas = self.cursor.fetchall()
        except:
            self.cerrar('E=ERR02 Despacho')  # No se encontró
            return
        self.enviar('Celular OK')




    def config_unidad(self):
        self.pc = False
        self.instr = ''
        imei = self.pop()
        sql = """SELECT id,empresa_id,padron,ruta_id,estado_id,frecuencia,
            salida_id FROM tracking_unidad WHERE imei='%s'""" % imei
        print sql
        self.cursor.execute(sql)
        unidad = self.cursor.fetchone()
        try:
            self.id = unidad[0]
            self.empresa = unidad[1]
            self.padron = unidad[2]
            sql = "SELECT codigo FROM tracking_ruta WHERE id=%d" % unidad[3]
            self.cursor.execute(sql)
            self.ruta = self.cursor.fetchone()[0]
            self.estado = unidad[4]
            self.frecuencia = unidad[5]
            self.salida = unidad[6]
        except:
            self.cerrar('E=ERR02 Unidad')  # No se encontró
            return
        sql = "UPDATE tracking_unidad SET conectado='1' WHERE id=%d" % self.id
        self.cursor.execute(sql)
        self.enviar('Unidad OK')
        self.geocercas = []
        self.siguiente = -1
        self.desconectado = False
        self.buscar_timeout()




    def buscar_timeout(self):
        print 'Salida:', self.salida
        if self.estado == 1:  # Despachado
            self.buscar_despacho()
        elif self.estado == 2:  # En Ruta
            self.cargar_geocercas()
        elif self.estado == 3:  # Llegó
            pass
        elif self.estado == 4:  # Calificado
            self.buscar_calificacion()
        elif self.estado == 5:  # Fuera de Ruta
            self.salida = None
            self.revisar_estado()




    def cargar_geocercas(self):
        if self.salida is None:
            self.geocercas = []
            self.siguiente = -1
            return
        sql = """SELECT latitud,longitud,radio,nombre,orden,tipo_id,hora,
            llego, id,salida_id FROM tracking_llegada WHERE salida_id=%d
            ORDER BY orden ASC""" % self.salida
        self.cursor.execute(sql)
        self.siguiente = -1
        self.geocercas = self.cursor.fetchall()
        for i, g in enumerate(self.geocercas):
            if g[7] or g[7] is None:
                self.siguiente = i
        if self.siguiente != len(self.geocercas):
            self.siguiente += 1




    def revisar_estado(self):
        print 'revisar estado'
        sql = """SELECT estado_id,salida_id FROM tracking_unidad WHERE
            id=%d""" % self.id
        print sql
        self.cursor.execute(sql)
        salida = self.cursor.fetchone()
        print salida
        estado = salida[0]
        self.salida = salida[1]
        print estado, self.estado
        if estado == 5 and self.estado != 5: #Fuera de Ruta
            self.estado = 2
            self.enviar_cancelado()
        elif estado == 1:  # Despachado
            self.estado = 1
            self.buscar_despacho()
        elif estado == 4:  # Calificado
            self.estado = 4
            self.buscar_calificacion()




    def buscar_despacho(self):
        """Si la unidad se encuentra en Despachada=2"""
        self.cargar_geocercas()
        latitud = self.decimal_a_nmea(self.geocercas[0][0])
        longitud = self.decimal_a_nmea(self.geocercas[0][1],
            False)
        radio = self.geocercas[0][2] * 60
        self.enviar('D=%d,%s,%s,%s,%s,%d' % (self.padron, self.ruta,
        longitud, latitud, radio, self.frecuencia))
        if self.recibir_ok():
            sql = """UPDATE tracking_unidad SET estado_id=2 WHERE
                id=%d""" % self.id
            self.cursor.execute(sql)
            self.estado = 2
            print 'OK recibido'  # Ya está en Ruta
        else:
            print 'No recibido OK'




    def se_desconecto(self):
        sql = """UPDATE tracking_unidad SET conectado='0' WHERE
            id=%d""" % (self.id)
        self.cursor.execute(sql)
        self.desconectado = True




    def se_conecto(self):
        sql = """UPDATE tracking_unidad SET conectado='1' WHERE
            id=%d""" % (self.id)
        self.cursor.execute(sql)
        self.desconectado = False




    def leer_recibido_unidad(self):
        """'P,0,%s,%s,0.45,%s,20,6,0.1,0.0' % (pos, t)"""
        print '********************************'
        print self.instr, self.params
        if self.instr == 'P':
            self.instr = ''
            try:
                self.pop()  # manda siempre 0
                x = Decimal(self.pop())
                y = Decimal(self.pop())
                alt = int(Decimal(self.pop()))
                t = str(Decimal(self.pop()))[:-4]
                t = self.datetime.strptime(str(t), '%Y%m%d%H%M%S')
                self.pop()  # no sé
                self.pop()  # nº de satélites
                v = int(Decimal(self.pop()) * Decimal('1.852')) * 2
                c = int(Decimal(self.pop()))
                self.pop()  # estado del PIC
            except:
                self.enviar('E=Falta Params')
                self.se_desconecto()
                return
            if x == Decimal("0.000000"):
                self.se_desconecto()
                return
            if self.desconectado:
                self.se_conecto()
            x = self.nmea_a_decimal(x, False)
            y = self.nmea_a_decimal(y)
            x = x.quantize(Decimal("0.0000001"))
            y = y.quantize(Decimal("0.0000001"))
            sql = """UPDATE tracking_unidad SET latitud='%s',longitud='%s',
                altura=%d,hora='%s-00',velocidad=%d,direccion=%d WHERE
                id=%d""" % (y, x, alt, t, v, c, self.id)
            self.cursor.execute(sql)
            if self.salida is None:
                # Si es nuevo
                self.enviar('OK')
                self.revisar_estado()
                return
            if self.estado != 2:
                # Si está fuera de ruta
                self.buscar_timeout()
                return
            self.cursor.execute("""INSERT INTO tracking_historial (
                unidad_id,latitud,longitud,altura,hora,velocidad,direccion,
                salida_id) VALUES (%d,'%s','%s',%d,'%s-00',%d,%d,%d)""" % (
                self.id, y, x, alt, t, v, c, self.salida))
            geocerca = False
            for i, g in enumerate(self.geocercas):
                if g[0] - g[2] < y and y < g[0] + g[2]:
                    if g[1] - g[2] < x and x < g[1] + g[2]:
                        geocerca = True
                        break
            if geocerca:
                self.siguiente = i + 1
                g = self.geocercas[i]
                print 'Dentro de Geocerca'
                prog = self.local_time(g[6])
                vol = t - prog
                if prog > t:
                    vol = -vol
                    m = round(-vol.seconds / 60.0)
                else:
                    m = round(vol.seconds / 60.0)
                sql = """UPDATE tracking_llegada SET volada=%d, llego='1'
                    WHERE id=%d""" % (m, g[8])
                self.cursor.execute(sql)
                if g[5] == 3:  # Última Geocerca
                    sql = """UPDATE tracking_unidad SET estado_id=3 WHERE
                    id=%d""" % self.id
                    self.cursor.execute(sql)
                    latitud = 0
                    longitud = 0
                    radio = 0
                    self.estado = 3
                else:
                # G=Nombre geocerca,voladas,historial,latitud,longitud,radio
                    latitud = self.decimal_a_nmea(
                        self.geocercas[self.siguiente][0])
                    longitud = self.decimal_a_nmea(
                        self.geocercas[self.siguiente][1], False)
                    radio = self.geocercas[self.siguiente][2] * 60
                self.enviar('G=%s,%d,0,%s,%s,%s,anteriores,posteriores' % (
                    g[3], m, longitud, latitud, radio))
            else:
                if self.siguiente == len(self.geocercas):
                    self.enviar("M=Fin de Ruta,a,0,%d" % self.frecuencia)
                else:
                    g = self.geocercas[self.siguiente]
                    hora = self.get_siguiente_hora(g[6])
                    self.enviar("M=%s,%s,0" % (g[3], hora))
            # Por si acaso se han juntado varios mensajes
            nueva = self.pop()
            if nueva != '':
                self.instr = nueva
                self.leer_recibido_unidad()
        elif self.instr == 'I':
            self.instr = ''
            self.enviar('Ya registrada')
        elif self.instr == '':
            sql = """UPDATE tracking_unidad SET conectado='0' WHERE
                id=%d""" % (self.id)
            self.cursor.execute(sql)
            self.se_desconecto()
        else:
            self.instr = ''
            print 'Comando equivocado'
            self.enviar((self.instr + ',' + str(self.params)))




    def buscar_voladas(self, unidades):
        # El servidor lo califica
        u = unidades[0]
        unidad = u[0]
        padron = u[1]
        salida = u[2]
        ruta = u[3]
        lado = u[4]
        print unidades
        if (ruta, lado) in self.rutas:
            sql = """SELECT hora FROM tracking_salida WHERE
                id=%d""" % salida
            self.cursor.execute(sql)
            salidas = self.cursor.fetchone()
            hora = salidas[0]
            sql = """SELECT l.llego,l.volada FROM tracking_llegada as l
                JOIN tracking_geocerca as g ON l.geocerca_id = g.id
                WHERE l.salida_id=%d ORDER BY g.orden ASC""" % salida
            self.cursor.execute(sql)
            print sql
            llegadas = self.cursor.fetchall()
            # F,[padron],[hora],[v1],[v2];,...
            mensaje = 'F,%d,%s,%d,' % (padron, ruta, lado)
            for l in llegadas:
                if l[0]:
                    mensaje += str(l[1]) + ','
                elif l[0] is None:
                    mensaje += '*,'
                else:
                    mensaje += '-,'
            self.enviar(mensaje)
            if self.recibir_ok():  # SR+R,record,OK
                try:
                    record = self.pop()
                    ok = self.pop()
                except:
                    return
                if 'OK' in ok:
                    sql = """UPDATE tracking_salida SET volada=%s WHERE
                        id=%d""" % (record, salida)
                    self.cursor.execute(sql)
                    sql = """UPDATE tracking_unidad SET estado_id=4 WHERE
                        id=%d""" % unidad
                    self.cursor.execute(sql)
                    self.estado = 4
        else:
            self.enviar('N,OK')




    def buscar_calificacion(self):
        """La unidad busca su calificación"""
        print 'buscar calificacion'
        sql = """SELECT volada FROM tracking_salida WHERE
            id=%d""" % self.salida
        print sql
        self.cursor.execute(sql)
        volada = self.cursor.fetchone()[0]
        mensaje = "F=%d" % volada
        self.enviar(mensaje)
        if self.recibir_ok():
            sql = """UPDATE tracking_unidad SET estado_id=5 WHERE
                id=%s""" % (self.id)
            self.cursor.execute(sql)
            self.estado = 5  #




    def enviar_cancelado(self):
        sql = """SELECT cancelado FROM tracking_salida WHERE
            unidad_id=%d""" % self.id
        self.cursor.execute(sql)
        cancelado = self.cursor.fetchone()[0]
        if cancelado:
            self.enviar('C')




    def leer_recibido_servidor(self):
        """padron,ruta,lado,hora,orden,1ºf,2ºf,3ºf,"""
        if self.instr == 'D':
            self.instr = ''
            padron = self.pop()
            sql = """SELECT id FROM tracking_unidad WHERE empresa_id=%d AND
                padron=%s""" % (self.empresa, padron)
            self.cursor.execute(sql)
            unidad_id = self.cursor.fetchone()
            try:
                unidad = unidad_id[0]
            except:
                self.enviar('E=ERR02 Padron')  # No se encontró
                return
            ruta = self.pop()
            sql = """SELECT id FROM tracking_ruta WHERE empresa_id=%d AND
                codigo='%s'""" % (self.empresa, ruta)
            print sql
            self.cursor.execute(sql)
            ruta_id = self.cursor.fetchone()
            try:
                self.ruta = ruta_id[0]
            except:
                self.enviar('E=ERR02 Ruta')  # No se encontró
                return
            lado = self.pop()
            d = self.pop()
            h = self.pop()
            hora = d + ' ' + h
            h = self.datetime.strptime(hora, '%Y-%m-%d %H:%M:%S')
            orden = int(self.pop())
            sql = """SELECT g.latitud,g.longitud,g.radio,g.nombre,g.orden,g.id,
                g.tipo_id FROM tracking_geocerca as g JOIN tracking_ruta as r
                ON g.ruta_id = r.id WHERE r.id=%d AND lado='%s' ORDER BY
                orden ASC""" % (self.ruta, lado)
            self.cursor.execute(sql)
            geocercas = self.cursor.fetchall()
            print len(geocercas)
            self.cursor.execute("""INSERT INTO tracking_salida (unidad_id,
                ruta_id,hora,lado,orden,cancelado,volada,llego) VALUES (%d,%d,
                '%s-05','%s',%s,'0','0','0')""" % (unidad, self.ruta,
                hora, lado, orden))
            self.cursor.execute("""SELECT MAX(id) FROM tracking_salida WHERE
                unidad_id=%d""" % unidad)
            salida = self.cursor.fetchone()[0]
            for i, geo in enumerate(geocercas):
                par = self.pop()
                i = geo[5]
                n = geo[3]
                s = int(par)
                h += datetime.timedelta(0, s * 60)
                la = geo[0]
                lo = geo[1]
                r = geo[2]
                o = geo[4]
                t = geo[6]
                self.cursor.execute("""INSERT INTO tracking_llegada (unidad_id,
                    geocerca_id,hora,volada,llego,latitud,longitud,radio,
                    nombre,orden,salida_id,tipo_id) VALUES (%d,%d,'%s-05',0,
                    '0','%s','%s','%s','%s',%d,%d,%d)""" % (unidad, i, h, la,
                    lo, r, n, o, salida, t))
            sql = """UPDATE tracking_unidad SET estado_id=1,
                ruta_id=%d,lado='%s',salida_id=%d WHERE id=%d""" % (self.ruta,
                lado,salida,unidad)
            print sql
            self.cursor.execute(sql)
            self.enviar('Despacho OK')
        elif self.instr == 'C':
            self.instr = ''
            """C,padron"""
            padron = self.pop()
            sql = """SELECT id,estado_id,salida_id FROM tracking_unidad WHERE
                empresa_id=%d AND padron=%s""" % (self.empresa, padron)
            self.cursor.execute(sql)
            unidad_id = self.cursor.fetchone()
            try:
                unidad = unidad_id[0]
                estado = unidad_id[1]
                salida = unidad_id[2]
            except:
                self.enviar('E=ERR02 Padron')  # No se encontró
                return
            if estado > 2:
                self.enviar('ERROR: No está en Ruta')
                return
            sql = """SELECT llego FROM tracking_llegada WHERE
                salida_id=%d""" % salida
            self.cursor.execute(sql)
            llegadas = self.cursor.execute(sql)
            marco = False
            for l in llegadas:
                marco = marco or l
            if marco:
                sql = """UPDATE tracking_unidad SET estado_id=4
                     WHERE id=%d""" % unidad
                self.cursor.execute(sql)
                self.enviar('Finalización OK')
            else:
                sql = """UPDATE tracking_unidad SET estado_id=5
                     WHERE id=%d""" % unidad
                self.cursor.execute(sql)
                self.enviar('Cancelación OK')
        elif self.instr == 'N':
            print self.params
            sql = """SELECT id,padron,salida_id,ruta_id,lado FROM tracking_unidad
                WHERE empresa_id=%d AND estado_id=3""" % self.empresa
            self.cursor.execute(sql)
            unidades = self.cursor.fetchall()
            if len(unidades) > 0:
                print "revisar llegadas"
                print sql
                self.buscar_voladas(unidades)
            else:
                self.enviar('N,OK')




    def recibir(self, com=None, obligatorio=True):
        try:
            recibido = self.s.recv(1024)
            #if not 'N,OK' in recibido:
            print '->', recibido
        except:
            return False
        if recibido == '':
#            if self.pc:
                self.conectado = False
                return
        try:
            params = recibido.split(',')
            self.instr = params[0]
            self.params = params[1:]
            self.params.reverse()
        except:
            self.S('E=ERR00 %s' % com) # Código inválido
            return False
        if com is not None and self.instr != com:
            if obligatorio:
                self.cerrar('E=ERR01 %s' % com) # Código inesperado
            return False
        return True




    def cerrar(self, mensaje='None'):
        self.conectado = False
        try:
            self.s.send("SR+" + mensaje + chr(13) + chr(10))
            self.s.close()
        except:
            print 'Cerrado por cliente %s:%d' % (self.addr[0], self.addr[1])
            print 'Se envió:', mensaje
        else:
            print 'Cerrando: ', mensaje, self.addr[0], self.addr[1]




    def enviar(self, mensaje):
        try:
            self.s.send('SR+' + mensaje + chr(13) + chr(10))
            if 'N,OK' != mensaje:
                print 'Enviando: ', mensaje
        except:
            self.cerrar('E=ERR04 %s' % mensaje)  # No se pudo enviar mensaje




    def nmea_a_decimal(self, a, x=True):
        if x:
            d = Decimal('4.666662367')
        else:
            d = Decimal('30.666664393')
        g = int(a / 100)
        m = a - g * 100
        return g + m / 60 - d




    def decimal_a_nmea(self, a, x=True):
        if x:
            d = Decimal('4.666662367')
        else:
            d = Decimal('30.666664393')
        a +=  d
        g = int(a)
        m = (a - g) * 60
        dec = g * 100 + m
        return dec.quantize(Decimal("0.00001"))




    def actualizar_despacho(self):
        sql = """SELECT dominio, puerto FROM tracking_empresa
            WHERE id = '%d'""" % self.empresa
        self.cursor.execute(sql)
        result = self.cursor.fetchone()
        print result




    def pop(self):
        return self.params.pop()




    def local_time(self, hora):
        utc = hora.replace(tzinfo=None)
        delta = hora.tzinfo.utcoffset(None)
        return utc - delta




    def get_siguiente_hora(self, hora):
        if self.siguiente == self.siguiente_back:
            return self.hora_siguiente
        self.siguiente_back = self.siguiente
        local = hora.replace(tzinfo=None)
        self.hora_siguiente = str(local).split(' ')[1][:-3]
        return self.hora_siguiente




    def recibir_ok(self):
        try:
            time.sleep(1)
            recibido = self.s.recv(1024)
            print 'recibido', recibido
        except:
            return False
        return 'OK' in recibido




try:
    Servidor()
except KeyboardInterrupt:
    print 'Presionaste Ctrl+C'
    sys.exit(0)