martes, 19 de septiembre de 2017

Formado Satelital 2017 (Comando, control y telemetría satelital de próxima generación)

Formado Satelital 2017

Este video es un resumen del trabajo realizado por la Maestría en Desarrollos Informáticos de Aplicación Espacial para el proyecto integrador. La maestría es dictada por el instituto Gulich (CONAE) y su socio académico la Universidad Nacional de La Matanza. Parte del trabajo es explicado en el post:  Comando, control y telemetría satelital de próxima generación




lunes, 31 de julio de 2017

Comando, control y telemetría satelital de próxima generación

I. Introducción
En la industria cuando se trata de comandar un equipo remoto existen varios enfoques dependiendo de las necesidades. Desde sencillas implementaciones Maestro-Esclavo sin ningún tipo lógica de control asociada, pasando por sistemas algo mas complejos donde se aplican restricciones o sistemas basados en maquinas de estado. En el área espacial, una práctica común, es el uso de secuencias de comandos sobre estructuras de control. Empresas y agencias espaciales en todo el mundo han desarrollados sus propios lenguajes e intérpretes para cumplir con este objetivo:
  • STOL: Satellite Test and Operation Language. Desarrollado por la Nasa y ampliamente utilizado en varias misiones.
  • PLUTO: presente en algunas misiones de la
  • ESA (Satellite Control and Operation Sys- tem 2000).
  • Otros: desarrollados o utilizados por difer- entes compañias SOL(GMV), CCL(Harris), PIL(Astrium), SCL(ICS).

En el caso del segmento terreno del FS2017 se optó por usar un lenguaje de propósito general en lugar de crear un lenguaje especifico o utilizar los existentes en la agencia espacial. Este enfoque presenta múltiples ventajas,
  • Portabilidad: escogiendo correctamente la herramienta se puede lograr una buena portabilidad entre distintas plataformas.
  • Capacidad: las herramientas y capacidades generales de un buen lenguaje y entorno de desarrollo de propósito general su- peran ampliamente las posibilidades que puede ofrecer un entorno de propósito es- pecifico.
  • Base de usuarios: una importante base de usuarios implica soporte, documentación y mejoras además de una base de posibles recursos ya capacitados.

En el caso de este proyecto la opción de lenguaje propio de la misión se descartó por no disponer del tiempo que implica desarrollar y validar un intérprete de propósito específico. La opción de utilizar los interpretes disponibles en la agencia espacial quedó relegada por sobre la opción Python, con una base de usuarios mucho más grande, multiplataforma, con mejores capacidades de depuración, documentación disponible en la comunidad y de dominio de todos los estudi- antes.
II. Arquitectura
La solución propuesta propuesta para el segmento terreno se basa en una arquitectura cliente-servidor estándar y está influenciada por el framework de desarrollo Django, el que también influyo en un análisis y diseño orientado a objetos. Como repositorio de datos se utiliza un Postgresql 9.0 donde se almacenan la definición de datos y los datos mismos. Todo el acceso se realiza mediante el ORM (Object Relational Mapping) y no hay en todo el software un acceso directo al motor. Los componentes de software son los siguientes:
  • Database server (1): Un servidor SQL Post- gres donde se almacenan telemetría, tele- comandos y sus definiciones para todas las misiones.
  • Application server (1): Un servidor de apli- cación responsable de la generación de interfaces y lógica de operación.
  • Telemetry and Telecomand Processor (n): modulo encargado de decodificar, calibrar y transformar en variables de ingeniería la telemetría según su definición y de codificar y enviar los comandos al segmento de vuelo. En este caso existe un proceso por satélite incluso si son del mismo fabricante. Para el caso de FS2017


III. Python y Django
Python es un lenguaje portable, abierto, de alto nivel. Es dinámico y tiene orientación a objetos no estricta (Permite programación estructurada). Concebido durante los 80 se pop- ularizó en los 90, muestra una performance superior a otros interpretes, producto de su código intermedio (bytecode). Además de la portabilidad, que no mostró ninguna fisura, su punto fuerte es la gran comunidad que brinda soporte y la buena integración con entornos de desarrollo como Eclipse. El lenguaje tiene buena productividad [3], estructuras complejas de datos ya incorporadas, manejo de excepciones, recolector de basura y capacidad de reflexión, mandatoria para cumplir con el obje- tivo. Otros beneficios de trabajar con Python, o con otro lenguaje maduro de propósito general, son las capacidades de depuración, los test unitarios y la generación de documentación automatizada. Este enfoque además permite pleno acceso a herramientas de comunicación como Sockets, http, RPC (Remote Procedure Call), Web Services, email y a bibliotecas de manejo de archivos XML entre otros. Django es el framework web para python más popular, además de ofrecer herramientas para el desarrollo web incorpora un ro- busto ORM que se ha usado intensivamente en todos los módulos del segmento terreno, incluyendo los que no tienen interfaces web. Django propone un desarrollo basado en modelos, con una orientación a objetos estricta, pudiendo aprovechar las capacidades de abstracción, herencia, polimorfismo y las ventajas de un mejor modelo de reusabilidad. El acceso al motor de base de datos es transparente al de- sarrollador de la aplicación como al operador, ingeniero o equipo encargado de desarrollar los scripts de comandos, toda la responsabil- idad sobre la recuperación y persistencia de datos recae sobre el ORM.
IV. Telemetría
Si bien la arquitectura del segmento terreno es agnóstica de la fuente de datos se ha utilizado como segmento de vuelo el satélite denominado FS2017. El FS2017 envía la telemetría en tramas AX.25 por TCP/IP 1 puerto 3210 según especificación del fabricante. La trama tiene un fragmento de Payload donde viene codificada la telemetría. La definición de la telemetría esta persistida en una entidad (TlmyVarType) donde se establece el tipo, valor, rangos, límites, posición den- tro del fragmento de payload y la función que la transforma en una variable de ingeniería. En el área satelital y en algunos sistemas de control y automatización es común recibir la telemetría en valores crudos o raw. A estos val- ores crudos se les debe aplicar una función que los transforme en unidades de ingeniería. En algunas áreas, incluida la satelital, esta trans- formación también es utilizada para calibrar sensores o equipos que puedan sufrir desgaste o variaciones por las condiciones de uso. Estos ajustes pueden ir desde un simple ajuste por función lineal hasta una discretización de val- ores por tablas de look-up. El sistema debe ser lo suficientemente flexible como para permitir
Figure 2: Selección de método de calibración

aplicar cualquier función de transformación a toda variable de telemetría y permitir ajustarla a medida que el desgaste de los sensores lo requiera. La figura 2 muestra como al configurar una variable de telemetría se selecciona la función a aplicar para convertirla en variable de ingeniería.
Mediante técnicas de reflexión se carga en tiempo de ejecución la función seleccionada de calibración y se le aplica al valor raw extraído de la trama de telemetría proveniente del pa- quete AX.25. La función de calibración puede ser cualquier secuencia de comandos program- able en python sin ningún tipo de restricción mas allá del tiempo de procesamiento. To- das las bibliotecas y estructuras de datos están disponibles incluyendo funciones matemáticas e incluso el acceso al la base de datos completa mediante ORM. Al tener acceso a la base de datos se pueden obtener coeficientes actualiza- dos de calibración permitiendo:
  • Reutilizar funciones: es posible crear una única función para ajustes típicos (lineal, cuadrático) y reutilizarla con distintos co- eficientes según la variable de telemetría requiera.
  • Calibración fina: creada una función modifi- cando los coeficientes se puede realizar un ajuste fino, por ejemplo por desgaste de un sensor, sin necesidad de recodificar la misma.

Como ejemplo, el segmento de vuelo FS2017 requiere que a muchos de sus valores crudos se les aplique una ganancia (GAIN) y un de- splazamiento (OFFSET). En todos los casos se realiza mediante una única función llamada linealCalibration aunque cada tipo de variable de telemetría posea sus propios valores para ambos coeficientes.
Para que un método sea considerado de calibración o ajuste debe estar desarrollado como método de una clase heredada de BaseCalibration. El software realiza periódicamente una exploración de todos los métodos públicos de clases derivadas de BaseCalibration y los disponibiliza para su aplicación a las distintas variables de telemetría 2. La figura 6 muestra la clase GCalibration, heredada de BaseCalibration, donde se implementan algunos métodos de calibración incluido el seleccionado en la figura 2.
self.tmlyVarType.calibrationLogic(self.tmlyVarType, raw )
elif self.tmlyVarType.varType==self.tmlyVarType.FLOAT:
self.tmlyVarType.lastCalFValue =
self.tmlyVarType.calibrationLogic(self.tmlyVarType, raw ) else:
self.tmlyVarType.lastCalSValue =
self.tmlyVarType.calibrationLogic(self.tmlyVarType, raw ) else:
if self.tmlyVarType.varType==self.tmlyVarType.INTEGER:
self.tmlyVarType.lastCalIValue = raw elif self.tmlyVarType.varType==self.tmlyVarType.FLOAT:
self.tmlyVarType.lastCalFValue = raw else:
self.tmlyVarType.lastCalSValue = raw
""" Si el tipo no es cadena llevo el dato a cadena """ value = self.tmlyVarType.getValue()
if self.tmlyVarType.varType!=self.tmlyVarType.STRING: if (value>=self.tmlyVarType.limitMaxValue and
value<=self.tmlyVarType.limitMinValue): raise Exception("Invalid value in var "+
self.tmlyVarType.code)
if saveifchange:
self.tmlyVarType.lastUpdate = datetime.now(utc) self.tmlyVarType.save()
if self.tmlyVarType.varType==self.tmlyVarType.INTEGER:
self.calIValue = self.tmlyVarType.lastCalIValue self.calSValue = str(self.calIValue) elif self.tmlyVarType.varType==self.tmlyVarType.FLOAT:
self.calFValue = self.tmlyVarType.lastCalFValue self.calSValue = str(self.calFValue) else:
self.calSValue = self.tmlyVarType.lastCalSValue
return self.tmlyVarType.getValue()
Cuando se recibe un nuevo valor de telemetría se persiste mediante una instancia de TlmyVar. Toda recepción de nueva telemetría se registra en el histórico, haya cam- biado o no su valor. También se deja el último valor en la variable TlmyVarType, esta desnormalización tiene como objetivo ganar eficiencia al consultar los valores de tiempo real. En lugar de consultar a la tabla de histórica de valores recibidos, se consulta a la tabla de tipos que tiene un tamaño acotado. Si el valor raw para una variable determinada no cam- bió desde la ultima actualización entonces se guarda el registro de la recepción pero no se aplican las calibraciones. Esto ultimo hace ganar eficiencia ya que no se vuelve a transformar cuando se sabe que el resultado sera el mismo, se debe tener en cuenta, sin embargo, que ante un cambio en las funciones de calibración se debe forzar mediante software a una nueva evaluación aunque el raw no haya cambiado.
Figure 3: Clase TlmyVarType

Figure 4: Clase CommandType



El siguiente código muestra parte de la implementación del método setValue de la clase TlmyVar.
class TlmyVar(models.Model):
... if raw!=self.tmlyVarType.lastRawValue:
self.tmlyVarType.lastRawValue = raw if self.tmlyVarType.calibrationMethod:
if not self.tmlyVarType.calibrationLogic:
klass =
globals()[self.tmlyVarType.calibrationMethod.aClass] instance = klass() methodToCall =
getattr(instance,
self.tmlyVarType.calibrationMethod.aMethod) self.tmlyVarType.calibrationLogic =
methodToCall else:
pass #Calibracion ya cargada
if self.tmlyVarType.varType==self.tmlyVarType.INTEGER:
self.tmlyVarType.lastCalIValue =
self.tmlyVarType.calibrationLogic solo si no fue cargada anteriormente.
La carga del método de calibración a una atributo del tipo de variable de telemetría se realiza, si no ocurren cambios, una única vez durante la ejecución. Esto también permite mejorar los tiempos de ejecución ya que evita la carga por cada valor de telemetría recibido. Con una frecuencia configurable, el software analiza si alguna función de calibración fue ac- tualizada, si asi ocurriera se procede al limpiar el atributo que contiene la función para forzar su recarga 3. La figura 5 muestra los tiempos de procesamiento para un bloque de 15 pa- quetes donde se decodifican 5000 variables de telemetría, cantidad compatible con un satélite científico de envergadura.
La primera decodificación demora mas que las siguientes dado que tiene que realizar la carga de las funciones de transformación. La primera telemetría recibida difiere de la ante- rior y al ser la primera calibración la función no esta precargada. Luego el tiempo se estabiliza entre 3 y 3.5 segundos para el conjunto de las 5000 variables con una probabilidad de cambio de 10% entre muestra y muestra. El tiempo de procesamiento incluye además la verificación de que los valores de las variables de ingeniería estén dentro del rango de seguridad y el alma- cenamiento en el histórico. Los pruebas han sido realizadas en maquinas virtuales sobre equipos de escritorio.
El siguiente código muestra una calibración lineal (que puede ser usada en muchas tipos de variable de telemetría) y que obtiene sus coeficientes utilizando el ORM.

Figure 5: Tiempo de decodificación

#Clase GCalibration hereda de BaseCalibration class GCalibration(BaseCalibration):
...
#Metodo generico de calibración lineal def linealCalibration(self, obj, raw):
#Multiplica el valor raw por la ganancia y #offset configurado para ese tipo de #de variable de ingeniería. Los obj tiene #el tipo de variable de ingeniería y #por medio del ORM se accede a los #valores configurados. return raw*
obj.coefficients.get(code="GAIN").value + obj.coefficients.get(code="OFFSET").value
V. Telecomandos
En el caso del FS2017 los telecomandos deben ser enviados al segmento de vuelo por el mismo canal en donde se recibe la telemetría, TCP/IP puerto 3210. Los comandos deben ser codificados en una trama AX.25. Para permitir el la creación de scripts de comandos, de la misma forma que con la telemetría IV se utilizaran las capacidades de reflexión para analizar, en tiempo de ejecución los scripts a ejecutar. Los scripts de comandos pueden ejecutarse por acción explicita de un operador o porque fueron aplicados a una pasada. El operador tiene pleno acceso al ORM de donde se

Figure 6: GCalibration



puede obtener el diccionario de comandos y los valores de telemetría si necesitara aplicar condicionales que dependieran del estado del segmento de vuelo u otro valor disponible en el sistema. El siguiente código muestra como se envía un comando de encendido de heater si la temperatura de la OBC esta por debajo de un valor determinado.
#Instancio el satélite FS2019 sat = Satellite.objects.get(code="FS2019") ...
if not sat.isConnected():
raise Exception("Abort, flight segment offline")
#Consulto si la temperatura de la OBC esta por #debajo de 10 grados y tambien #Verifico que el heater este apagado if(sat.tmlyVarType.get(code="obcT1").getValue()<
sat.parameters.get(code="MinTolOBCTemp")) and (sat.tmlyVarType.get(code="HeaterOn").getValue()
==False):
#Creo un nuevo comando para el satelite, con fecha #de vencimiento 5 minutos desde la #fecha de creación. #Es un comando de ejecución en tiempo real #y por tanto no se agrega un tercer parametro #de fecha de ejecución cmd = sat.SendRTCommand("HeaterTurnOn",
datetime.utcnow()+ timedelta(minutes=5))
Por consola se obtiene el satélite con el que se desea trabajar. Se consulta por la temperatura de la OBC mediante una lectura del valor en tiempo real de la variable de telemetría obcT1. Si ese valor es inferior a limite parametrizado (El método getValue() siempre retorna variable de ingeniería) y los heaters esta apagados entonces se envía un comando de encendido. Sentencias como sat.tmlyVarType.get(code="obcT1").getValue() que retornan la instancias de TlmyVarType son provistas por el ORM de django a pesar de ser utilizadas por consola.
VI. Resultados
i. Interprete y Framework de desarrollo
El lenguaje (python) es sencillo aprender, pero por sobre todo, esta disponible una extensa comunidad con ayudas ante los problemas o desafíos que puedan aparecer. Para las posibles limitaciones es fácil encontrar soluciones alternativas (workarounds). Las experiencias realizadas con lenguajes de propósito especifico requirieron de la asistencia constante de un experto. Trabajando sobre python esto no fue necesario y la mayoría de los problemas pueden ser resueltos mediante información disponible en la comunidad. Las herramientas de depuración y análisis se mostraron poderosas, incluso superando las expectativas. Como contrapartida el hecho de ser un lenguaje dinámico limita las capacidades de Code Insight/IntelliSense que el entorno de desarrollo puede ofrecer (comparando con lenguajes compilados) aunque sigue superando las capacidades que la mayoría de las herramientas propietarias tiene al alcance ofrece.
ii. Desarrollo multiplatafoma
La solución fue ejecutada en ubuntu linux 15.10 como en windows 10.0 y windows 7.0. Se re- alizaron pruebas sobre dispositivos físicos y virtuales. En todos casos el software se ejecuto sin cambios. En este punto tanto el interprete como el framework mostraron uno de sus atributos mas destacables.
iii. ORM

El ORM de Django se mostró estable, se registraron pocos problemas o bugs y cuando ocurrieron se encontró información disponible que lo documentaba. Para la creación y modificación de modelos, su flujo divido en dos etapas Make Migrations y Migrate permitió el trabajo colaborativo y con múltiples servidores. El acceso a datos requiere de la práctica de convenciones propias que, aunque muy bien documentadas, afectan la curva de aprendizaje por poseer particularidades propias de la filosofía de convención por sobre configuración (Convention over configuration/coding by convention). Por otro lado el modelo no acepta atributos privados, cuestión que afecta el encapsulamiento. La capacidad del ORM para trabajar con polimorfismo esta limitada y se requiere añadir paquetes especiales, este últimos puntos se pueden considerar como una limitación de relevancia.

iv. Reflexión
Quizá por ser un lenguaje híbrido y su definición algo mas antigua, se juzga a los mecanismos de reflexión disponibles en python mas complejos que los disponibles en otras herramientas como C#.Net. Estos, sin embargo, fueron suficientes para cumplir el objetivos. Los tiempos de respuesta no presentan una limitación a su implementación. La reutilización de métodos previamente cargados colabora en mantener los tiempos entre margenes aceptables.

VII. Discusión
Los lenguajes específicos del sector espacial fueron creados hace décadas. Durante los 70, si bien existían opciones interpretadas , están no eran de uso extendido. En la actualidad existen varias opciones con una amplia cantidad de usuarios de base y múltiples proyectos que avalan su robustez. Python, Perl, VB- Script, JavaScript son solo algunas opciones. Incluso la técnica aplicada en este trabajo es implementable, con algunas restricciones, en lenguajes compilados, siempre y cuando ten- gan capacidades de reflexión avanzadas. Java o C#, herramientas que generan código interme- dio son aplicables así como compilados puros. Delphi mediante su RTTI (Run-time type information) para citar un ejemplo, requeriría que los métodos de calibración sean compilados previamente para luego ser cargados dinámica- mente como complementos o plug-ins durante la ejecución del modulo principal. A esta op- ción se la juzga mas compleja y si bien puede ofrecer mejoras en cuanto a rendimiento, los resultados, como los mostrados en 5, no justifi- can su implementación. Las opciones amplia- mente probadas de la actualidad no justifican el desarrollo de un lenguaje propietario, no
tener una base de usuarios implica ausencia de documentación, soporte, herramientas y por sobre todo recursos como explica [4] refiriéndose a ADA. “Using Ada could potentially offer lower life cycle costs compared to other programming languages, but it seems more likely that using Ada would raise life cycle costs due to a dearth of tools and compilers and lack of trained, experienced programmers”. La creación de una herramienta propietaria limita las capacidades disponibles en términos de es- tructuras de datos y bibliotecas en general y obliga a su desarrollo con el costo que esto conlleva y su aprendizaje resta mucho valor al ser inaplicable por fuera del ámbito donde se implemente.

Referencias
[1] Garcia, Gonzalo, Use of Python as a Satel- lite Operations and Testing Automation Lan- guage, GSAW2008 Conference, Redondo Beach, California, 2008.
[2] Galal, Ken and Hogan, Roger P, Satellite Mission Operations Best Practices, Space op- erations and support technical committe american institute of aeronautics and as- tronautics, 2001.
[3] Prechelt, Lutz, An empirical comparison of
seven programming languages, IEEE, 2000.
[4] Smith, II and others, What About Ada? The State of the Technology in 2003, Software Engineering Institute, 2003.





martes, 23 de mayo de 2017

ISIS cubesat Telemetry (Part 2)

Introduccion:

En la parte uno de esta entrada ISIS Cubesat Telemetry (Part 1) explicamos brevemente algunas particularidades de la telemetría en el área satelital. Encontramos un XML que describía parcialmente los paquetes que llegan del satelite por TCP/IP y decimos parcialmente porque en la practica si intentamos desempaquetar la trama utilizando unicamente el XML el proceso falla.
La razón es que ese XML describe el paquete (packet) de una trama mayor que viene dada de la siguiente manera.


En las siguientes lineas vamos a desarmar completamente la trama hasta llegar a las variables de telemetria.
Primero establecemos hasta donde nos es posible posición y tamaño de las cosas. Como se observa en la tabla 7.1 existen campos de tamaño variable (Ejemplo: modulation name con tamaño indicado por modulation name length) que impiden presetear todo de antemano.

PosFrameCommand         = 0

LenFrameCommand         = 1

   

PosFrameLen             = PosFrameCommand+LenFrameCommand

LenFrameLen             = 4

   

PosDataRate             = PosFrameLen+LenFrameLen

LenDataRate             = 4

   

PosModuluationNameLen   = PosDataRate+LenDataRate

LenModuluationNameLen   = 1

   

PosModulationName       = PosModuluationNameLen+LenModuluationNameLen

LenModulationName       = 0

   

PosRSSI                 = PosModulationName+LenModulationName

LenRSSI                 = 8

   

PosFrequency            = PosRSSI+LenRSSI

LenFrequency            = 8

   

PosPktLen               = PosFrequency+LenFrequency

LenPktLen               = 2


Luego en un bucle infinito desarmamos los paquetes que van llegando hasta llegar al payload que es lo que nos interesa. Esta variable es la que en futuras entradas vamos a desarmar en función del xml de configuración. Por lo pronto el siguiente código recibe en un bucle infinito la trama (chunk) del socket y la desarma hasta llegar al payload, el código tiene una breve descripción de lo que hace antes de cada linea y se puede observar que el mismo es no bloqueante porque por el mismo canal debe enviarse los comandos....

s = socket.socket(socket.AF_INET, socket.SOCK_STREAM)

try:

    s.connect( (uhfServerIp, int(uhfServerPort)) )

    print("Successfully connection to..", uhfServerPort)

        while True:

            try:

                """

                Establezco un timeout para la bajada, con o sin bajada los comandos deben ser enviados

                """

                s.settimeout(5.0)

                   

                """

                Me quedo esperando recibir informacion del socket (IPC)

                """                       

                chunk = s.recv(int(BUFFER_SIZE))

                             

                unconnectionLimit = 0

                               

                """

                Buena o mala la telemetria fue recibida, reseteo el watchdog

                """

                wd.reset()

                               

                """

                Si recibo telemetria, defenitivamente estoy en contacto

                """

                sat.setInContact(True)

                               

                """

                Si la informacion es una trama de bits completa la proceso

                """

                if chunk == b'':

                    raise RuntimeError("socket connection broken")

                else:

                    
                    os.system('cls||clear')

                    print(bcolors.OKGREEN+"--------------------Data Received-------------------"+bcolors.OKGREEN)

                    print("Data length:", len(chunk))

                    #Me guardo el crudo tal cual llego antes de procesarlo, la tabla donde se guarda es UHFRawData


                    data = UHFRawData()

                    data.source = source

                    data.data = chunk

                    data.processed = False

                    data.save()

                                 

                    framecommand = unpack("<B",chunk[PosFrameCommand:PosFrameCommand+LenFrameCommand])[0]

                    frameLength  = unpack("<I",chunk[PosFrameLen:PosFrameLen+LenFrameLen])

                    datarate     = unpack("<I",chunk[PosDataRate:PosDataRate+LenDataRate])

                    modulationnamelen = (unpack("<B",chunk[PosModuluationNameLen:PosModuluationNameLen+LenModuluationNameLen]))[0]

                    modulationname    = chunk[PosModulationName:PosModulationName+modulationnamelen]

                    PosRSSI           = PosModulationName+modulationnamelen

                    rssi              = unpack("<d", chunk[PosRSSI:PosRSSI+LenRSSI])

                    PosFrequency        = PosRSSI+LenRSSI

                    freq                = unpack("<d", chunk[PosFrequency:PosFrequency+LenFrequency])

                    PosPktLen           = PosFrequency+LenFrequency

                    pktLen             = unpack("<H",  chunk[PosPktLen:PosPktLen+LenPktLen])

                    PosUtcTime = PosPktLen+pktLen[0]

                    LenUtcTime = 4

                                   

                    PosPayload = PosPktLen+int(LenPktLen)

                    ax25 = chunk[PosPayload:PosPayload+pktLen[0]]

                                   

                                   

                    destination  = ax25[0:7]

                    asource      = ax25[7:7+7]

                    control      = ax25[7+7:7+7+1]

                    protocol     = ax25[7+7+1:7+7+1+1]

                                   

                           

                                        

                    vardataoffset = 7+7+1+1

                    payload = ax25[ vardataoffset: ]

                                   

                    pn = unpack("<H",  payload[1:3])

                    print("Packet number:", pn)

                                   

                    frameTypeId = payload[0]

                                   

                    dl = DownlinkFrame()

                                   

                    dl.frameCommand     = framecommand

                    dl.frameLength      = frameLength[0]

                    dl.dataRate         = datarate[0]

                    dl.modulationName   = str(modulationname)

                    dl.rssi             = rssi[0]

                    dl.frequency        = freq[0]

                    dl.packetLength     = pktLen[0]

                    dl.satellite        = sat

                    dl.ax25Destination  = "Pending"#destination.decode("utf-8")

                    dl.ax25Source       = "Pending"#asource.decode("utf-8")

                    dl.ax25Protocol     = "Pending"#protocol.decode("utf-8")

                    dl.ax25Control      = "Pending"#control.decode("utf-8")

                    dl.packetNumber     = pn[0]

                    dl.frameTypeId      = frameTypeId

                                                               


            

domingo, 14 de mayo de 2017

ISIS cubesat Telemetry (Part 1)

Introducción

El los últimos meses estuve cursando una maestría en desarrollo software de aplicación espacial. Una de las asignaturas exige algunas practicas mínimas sobre un pequeño satélite. El satélite fue comprado hace algunos años por la agencia espacial donde se desarrollan las practicas, pero nunca había sido utilizado. El problema es que también había caducado el periodo de soporte y no teníamos documentación para nuestro primer objetivo que era procesar la telemetría de este diminuto satélite.

Telemetría vs Telemetría Satelital

Se denomina telemetria a la medición y transmisión remota de magnitudes, físicas o lógicas. ¿Que diferencia hay entre la telemetría de cualquier equipo remoto y de un satélite? Las diferencias son muy pocas pero existen y son generadas por los siguientes problemas:

Eficiencia de la transmisión:

En el area espacial existe un obsesión (Relativamente justificada) por el ahorro de bytes a transferir. Las capacidades y tiempos de transmisión son limitadas. El hardware en los sistemas de vuelo suele ser viejo, muy viejo o con limitaciones en términos de capacidad de computo. Esto se traduce en que pueden no ser aplicables técnicas de compresión o protocolos que utilicen exhaustivamente la lógica para reducir la cantidad de bytes como por ejemplo  Google Protocols Buffers.

Desgaste de componentes:

Los componentes en el espacio sufren un desgaste y ya no es posible remplazarlos. Los valores de telemetria recibidos pueden estar modificados por ese desgaste y no representan el verdadero valor.

Estas cuestiones definen y acotan el margen de acción, en contraste con el mundo informático actual donde nos damos el lujo de transferir por protocolos autocontenidos, de transferir texto, XML etc, acá se opta por trabajar, como antaño, con protocolos binarios  y los datos transferidos no representan el valor final de la variable que se pretende analizar tanto por la necesidad de reducir la cantidad de bytes transferidos al mínimo como la necesidad de ajustar los valores.

RF checkout box

Afortunadamente el kit del satélite disponía de un equipo de radio conectable a un PC por USB y un sencillo software de test que alcanzaba a mostrar algunos valores de telemetria. El manual indicaba que ese software publicaba la trama en un server TCP/IP pero no indicaba el formato completo de la trama. Con estos escasos recursos algunos compañeros comenzaron a decompilar los .class del software y llegaron a la conclusion de que utilizaba un XML para desarmar parte de la trama...buena parte del problema esta resuelto.

De un golpe de vista se entiende el formato de este paquete (o subpaquete). Por ejemplo, el frame type 1 es el de toda la telemetria (allTelemetry), esto viene indicado en un valor char en la posición 0. En la posición 8 tenemos la temperatura 1 de la OBC, viene en un short (2 bytes calculados a prueba y error) y offset y gain indican los valores por los que hay que multiplicar y sumar ese short para transformarlo en el valor real de temperatura Celsius.
Como dijimos RF checkout box publica las tramas por un server TCP/IP puerto default 3210, si el satélite esta conectado y funcionando con conectarse al server se comienzan a recibir las tramas.

 s = socket.socket(socket.AF_INET, socket.SOCK_STREAM)

 s.connect( (uhfServerIp, int(uhfServerPort)) )

 ...

 chunk = s.recv(int(BUFFER_SIZE))


Sin embargo estas tramas contienen información de cabecera previa al paquete que describe el XML. Esto lo iremos desarmando en la Parte 2 (Part 2) de esta entrada.

Continua en...Parte 2



miércoles, 10 de mayo de 2017

Misión Cubesat FS2017

La función del software del segmento terreno es la operación y mantenimiento del estado de salud del satélite (bajada y análisis de Telemetría -TM- y subida de Telecomandos -TC) y la bajada, procesamiento y publicación de datos de ciencia. A tal fin se ha estudiado el diseño y desarrollo de un SW multimisión que, ademas de contar con un alto nivel de robustez, posea atributos de generalidad que permitan su aplicación a futuras misiones.  El sistema esta desarrollado sobre una arquitectura cliente-servidor clásica utilizando Python sobre el framework Django, el cual fuerza el uso del paradigma orientado a objetos y un diseño del tipo Model-Driven Architecture (MDA). La aplicación esta hosteada sobre apache en Linux Ubuntu 15.10. El sistema intenta ser una alternativa a otros desarrollos ad-hoc ofreciendo una variante multiplataforma y multimisión que maximiza los atributos de: interoperabilidad, eficiencia, accesibilidad/disponibilidad, integridad, seguridad y recuperación.

jueves, 22 de septiembre de 2016

Django: Test en eclipse

El problema

Ejecutar un test automático desde el eclipse sin necesidad de trabajar con una consola aparte. Existen librarias que mejoran el soporte a los test automáticos (seran tema de otro post), pero si no queremos llegar a tanto y pretendemos realizar la ejecución o, también imprescindible, el debug de algunos test no es tan directo como seleccionar simplemente Run As->Python unit-test


Debería ser así, pero la realidad es que no funciona y obtendrán el mensaje...

raise AppRegistryNotReady("Apps aren't loaded yet.")
django.core.exceptions.AppRegistryNotReady: Apps aren't loaded yet.

Seguramente se ejecutan los unit-test sin antes cargar cuestiones básicas del ORM y demás, todavía no me queda claro porque.


La solución


Para resolver este tema, sin instalar librerias adicionales tenemos el siguiente workaround:

1.Ir a Run Configurations...

2.Crear una nueva configuración de ejecución, muy importante estar parado sobre PyDev Django como indica la siguiente imagen.


3.Luego colocar un nombre descriptivo, tener en cuenta que esta es una configuración para correr test, luego seguramente necesitemos una para depurar, por tanto no esta demás indicar esta característica en el nombre


4.Luego indicar para que proyecto estamos armando la configuración


5.Indicar la ubicación del manage.py del proyecto


6.En argumentos colocar la palabra "test", como si lo hiciéramos por consola


7.Salvar y ejecutar el test....




Para debug:


Para debug realizar los mismos pasos pero al utilizar el debug configuration en lugar del run configuration...


Conclusion

Rápidamente se puede acceder a los test automáticos sin necesidad de componentes de terceros y cuando se entiende la idea se configura muy rápido.
Espero haya servido.




martes, 20 de septiembre de 2016

Django: Log de consultas SQL

El problema

La verdad que en el poco tiempo que llevo jugando con Django no he tenido grandes problemas con código SQL generado automáticamente por el ORM de Django, y cuando tuve algun inconveniente el mensaje de error fue lo suficientemente descriptivo como para resolverlo sin mas. 
Esto no me hace creer que no pueden aparecer problemas un poco mas complejos y que no estaría de mas tener un buen registro de todo lo que el ORM esta ejecutando sobre el motor relacional. Tiempo atrás, trabajando con Delphi/DataSnap procuraba guardar toda consulta generada en un tabla para posterior analisis/auditoria. ¿Como hacer los mismo en Django?

La solución

Yo no he pedido hacer en django que las consultas SQL se persistan en una tabla de la base de datos, pero al menos si que se guarden en un archivo del proyecto.
Tan simple como agregar la siguiente entrada en el settings.py

LOGGING = {
    'version': 1,
    'disable_existing_loggers': False,
    'handlers': {
        'console': {
            'level': 'DEBUG',
            'class': 'logging.StreamHandler',
        },
        'file': {
            'level': 'DEBUG',
            #'class': 'logging.FileHandler',
            'class': 'logging.handlers.RotatingFileHandler',
            'filename': os.path.join(BASE_DIR, 'logs/django.log'), #'/path/to/django/debug.log',
            'maxBytes': 16745#16777216, # 16megabytes
            #'formatter': 'verbose'
        },
                 
    },
    'loggers': {
        'django.db.backends': {
            'handlers': ['file'],
            'level': 'DEBUG',
        },
    }

Revisar tener la carpeta en el proyecto, en mi caso llamada logs.


Algunas pruebas

Ahora para verificar vamos a realizar algunas pruebas por consola, si aun no sabes como hacerlo en eclipse te recomiendo un post anterior Django: Testeo de escritorio sobre consola de eclipse.

Desde ORM:
Plato.objects.all()
[<Plato: Flan Casero>, <Plato: Omelette>, <Plato: Milanesa de soja>, <Plato: Tortilla>]

Consulta SQL:
(0.001)

SELECT "djSisitia_plato"."id", "djSisitia_plato"."descripcion", "djSisitia_plato"."tipo_id", "djSisitia_plato"."imagen" 
FROM "djSisitia_plato" LIMIT 21; args=()


Plato.objects.all().filter(tipo__descripcion="Postre")

(0.000) 
SELECT "djSisitia_plato"."id", "djSisitia_plato"."descripcion", "djSisitia_plato"."tipo_id", "djSisitia_plato"."imagen" 
FROM "djSisitia_plato" INNER JOIN "djSisitia_tipoplato" ON ("djSisitia_plato"."tipo_id" = "djSisitia_tipoplato"."id") 
WHERE "djSisitia_tipoplato"."descripcion" = 'Postre' 
LIMIT 21; args=('Postre',)