Descripción general de los scripts de Lua

Una descripción general del uso de Lua con eGauge

  • La creación de scripts de Lua es un tema avanzado. El soporte de eGauge no puede revisar el código y proporciona soporte limitado para solucionar problemas con los scripts de Lua.

Introducción

El soporte para eGauge Script (eScript) se introdujo en el firmware v1.1, lo que permite la creación de scripts limitados mediante el uso de registros de fórmulas. eScript fue diseñado para calcular fórmulas matemáticas básicas y comunes, como operaciones aritméticas básicas, comparaciones y condicionales, junto con la capacidad de llamar a rutinas de biblioteca (funciones).

El firmware v4.1 introdujo la compatibilidad con Lua v5.3 . Para garantizar un funcionamiento seguro, la ejecución de Lua se protege mediante tiempos de espera para evitar bucles infinitos o tiempos de ejecución excesivamente largos. Además, se admite un conjunto limitado de bibliotecas estándar.

Para mantener la compatibilidad con versiones anteriores, el firmware traduce de forma transparente las expresiones eScript a scripts Lua antes de la ejecución y el entorno de ejecución Lua proporciona todas las funciones disponibles dentro de eScript.


Introducción a Lua

Convenciones léxicas

Los nombres pueden constar de cualquier letra, dígito o guion bajo, pero no pueden comenzar con un dígito. Los valores numéricos utilizan las mismas convenciones que en la mayoría de los demás idiomas, incluida la capacidad de escribir valores hexadecimales con un prefijo de 0x . Los dos valores literales booleanos son true y false . Una variable con un valor indefinido es nil . Las cadenas pueden estar encerradas entre comillas dobles o simples. Los caracteres dentro de una cadena pueden escaparse con una barra invertida. Los comentarios comienzan con un doble guión ( -- ) y se extienden hasta el final de la línea. Las declaraciones pueden terminar con un punto y coma, pero, por lo general, no se requieren puntos y coma (incluso cuando hay varias declaraciones en una sola línea). La única excepción a esta regla es que se requiere un punto y coma si la siguiente declaración comienza con un paréntesis izquierdo.

Variables

Las variables son globales a menos que se declaren explícitamente como locales. Por ejemplo:

x = 10        -- global variable
local y = 42   -- local variable

Operadores

Lua proporciona un conjunto normal de operadores para operaciones aritméticas y comparaciones. Una característica inusual es que el operador de desigualdad es ~= en lugar del más típico != . Los operadores lógicos usan palabras clave como en Python: and , or y not . La longitud de una cadena o tabla (véase Datos Estructurados más adelante) se obtiene anteponiendo una almohadilla al nombre de la cadena o tabla. Por ejemplo, #'hi' devolvería 2. Las cadenas se pueden concatenar con el operador de doble punto. Por ejemplo , 'yell'..'ow' daría como resultado 'yellow' .

Estructuras de control

La sintaxis de las distintas estructuras de control es:

for index=initial,step,final do block end -- numeric for loop

for var in iterator do block end   -- iterator for loop

while cond do block end    -- while loop

repeat block until cond    -- do while loop

if cond then block else block end  -- conditional statement

Puede utilizar break para salir de un bucle pero, a diferencia de C y otros lenguajes, no existe una función continue que le permita saltar a la siguiente iteración.

Datos estructurados

Lua usa tablas para representar tanto matrices como tablas hash (diccionarios de Python, objetos de Javascript). Por ejemplo:

local a = {'a', 'b', 'c'}

Asigna una matriz que contiene las cadenas 'a' , 'b' y 'c' a la variable local a . A diferencia de la mayoría de los lenguajes, el índice base de la matriz es 1, por lo que a[1] generaría el primer elemento o 'a' en nuestro ejemplo.

Los valores literales del diccionario se pueden escribir así:

local d = {['k1']='v1', ['k2']='v2'}

e indexado con corchetes, de modo que d['k2'] genere 'v2' , por ejemplo. Si las claves de un diccionario son nombres válidos de Lua, se pueden omitir los corchetes y las comillas que encierran las cadenas de clave. Por ejemplo, el ejemplo anterior se podría simplificar a:

local d = {k1='v1', k2='v2'}

Para estos valores de clave, también es posible acceder a sus valores mediante la sintaxis de nombre de miembro. Por ejemplo, d.k1 generaría 'v1' , al igual que d['k1'] .

Migración de eScript a Lua

La mayor parte de eScript tiene un equivalente directo en Lua. eScript admite un solo tipo numérico (coma flotante de doble precisión IEEE754) y admite cadenas de caracteres de forma limitada. Según la configuración del firmware eGauge, Lua tiene el mismo tipo numérico, pero también admite enteros de 32 bits y es totalmente compatible con cadenas de caracteres, valores booleanos y tablas.

Las diferencias más importantes entre eScript y Lua son las siguientes:

  • En eScript, el valor de un registro se obtiene con $"register_name" , mientras que en Lua, la expresión equivalente es __r("register_name") .
  • Los valores booleanos de Lua no se pueden usar directamente como valores numéricos, mientras que eScript usa 0 para representar falso y cualquier valor distinto de cero para verdadero.
  • Lua no proporciona un análogo directo para el operador condicional

cond ? if_true_expr : if_false_expr

En su lugar, Lua utiliza la expresión lógica:

cond and if_true_expr or if_false_expr

Esto funciona de forma bastante similar al condicional de eScript debido a la forma en que se definen los operadores "and" y "or". Específicamente, " and" devuelve "false" si el lado izquierdo es nulo o falso , o el valor del lado derecho en caso contrario. El operador " or" devuelve el valor del lado izquierdo si no es nulo o falso , y el valor del lado derecho en caso contrario. Ambos operadores acortan la evaluación del operador "and" y tienen mayor precedencia que "or" .


eScript propaga automáticamente valores NaN (no es un número). Por ejemplo, si se llama a una función con un valor NaN, el resultado devuelto también es NaN. De igual forma, si la condición del operador condicional es NaN, el resultado de la expresión condicional también es NaN.

Dadas las similitudes entre eScript y Lua, la mayoría de las expresiones de eScript se traducen fácilmente a Lua. Las traducciones no triviales se muestran en la siguiente tabla:

expresión eScript Equivalente de Lua
a < b __lt(a, b)
a <= b __le(a, b)
a > b __gt(a,b)
a >= b __ge(a,b)
a = b __eq(a,b)
taxi

(función(__c)

si __c ~= __c entonces devuelve __c fin

devuelve __c~=0 y (a) o (b)

fin)(c)

En palabras, los operadores de comparación se traducen en llamadas a las funciones auxiliares __lt() , __le() , etc. Estas funciones auxiliares comprueban si a o b es un NaN y devuelven NaN, si es así. Si no, realizan la comparación y devuelven 0 para falso y 1 para verdadero. La traducción del operador condicional es más complicada porque se debe tener cuidado de manejar NaN correctamente y evaluar a y b solo cuando sea necesario. Esto se logra en Lua con la ayuda de una función anónima en línea que comprueba si la condición es NaN y devuelve NaN si ese es el caso. De lo contrario, la función comprueba si la condición tiene un valor distinto de cero y, si es así, devuelve el valor de a . De lo contrario, devuelve el valor de b .

Entorno Lua proporcionado por el firmware eGauge

Entorno estándar

Por razones de seguridad, el firmware de eGauge proporciona un entorno Lua restringido (el entorno de pruebas de Lua). Las funciones y variables básicas se limitan al siguiente subconjunto (consulte el manual de Lua 5.3 para obtener una descripción detallada):

_VERSION, assert, error, getmetatable, ipairs, load, next, pairs, pcall, print, rawequal, rawget, rawlen, rawset, select, setmetatable, tonumber, tostring, type, xpcall

Las siguientes bibliotecas Lua estándar están disponibles:

cadena

matemáticas

mesa

Adiciones proporcionadas por el firmware eGauge

Funciones básicas y de alerta

Todas las funciones disponibles para eScript también están disponibles para scripts de Lua. Consulte la documentación en línea de un medidor eGauge para obtener una lista completa ( Ayuda → Funciones básicas o Ayuda → Funciones de alerta ). Al llamar a estas funciones desde Lua, también propagarán valores NaN, al igual que en eScript. Es decir, si se llama a alguna de las funciones con un argumento NaN, el valor de retorno también será NaN.

Módulo json

Este módulo proporciona la capacidad de codificar un objeto Lua en una cadena y convertir de forma segura nuevamente una cadena en un objeto.

string = json.encode ( value ):

Esta función acepta un valor Lua y lo serializa en la cadena JSON correspondiente, que devuelve. Las tablas que contienen referencias cíclicas no se pueden codificar en JSON y generarán un error. El tamaño máximo de la cadena codificada en JSON está limitado actualmente a 4095 bytes. Las tablas Lua se pueden indexar mediante una combinación de valores numéricos, booleanos y de cadena, mientras que los objetos JSON siempre se indexan mediante cadenas. Esta función convierte las tablas Lua que no están vacías y cuyos índices consisten únicamente en números en el rango de 1 a N (donde N es la longitud de la tabla) en matrices JSON, y todo lo demás en objetos JSON. En este último caso, los índices numéricos y booleanos se convierten en sus cadenas equivalentes.

value = json.decode ( string ):

Esta función acepta una cadena y la deserializa al objeto Lua correspondiente. Solo se permiten comillas dobles para el entrecomillado de cadenas. Se ignoran los espacios en blanco o tabulaciones.


Módulo persistente

Este módulo proporciona variables cuyos valores persisten durante cortes de energía y reinicios del dispositivo.

obj = persistent:new( name , initial , description ):

Declara una variable persistente con el nombre especificado. A diferencia de los nombres de Lua, este nombre puede ser una cadena arbitraria. El nombre debe ser único, ya que cualquier script de Lua que declare una variable persistente con el mismo nombre accederá al mismo objeto subyacente. Si es la primera vez que se declara la variable persistente, su valor se establece en initial . La función de la variable debe describirse mediante la cadena pasada para description . El valor de retorno es un objeto que representa la variable persistente.

obj :get()

Devuelve el valor actual de la variable persistente representada por el objeto obj .

obj :set( value )

Establece el valor de la variable persistente representada por el objeto obj como valor . Se puede establecer cualquier valor aceptable para json.encode() .

Entorno Lua para scripts de control

Los scripts de control tienen acceso al entorno estándar, disponible como se describe en la sección anterior. También tienen acceso a todas las funciones básicas y de alerta, así como a la biblioteca de corrutinas . También están disponibles varias funciones de bajo nivel, así como varios módulos de conveniencia, como se describe a continuación.

Funciones de bajo nivel

La mayoría de estas funciones no suelen utilizarse directamente. Proporcionan los mecanismos de bajo nivel necesarios para implementar las abstracciones de alto nivel proporcionadas por los módulos descritos en las secciones siguientes.

tid , err = __ctrl_submit( attrs , method , args… )

Envíe una llamada al método llamado method en el dispositivo identificado por attrs , pasando argumentos args . El nombre del método debe ser una cadena, attrs una tabla de pares nombre/valor y args debe ser una secuencia de cero o más valores de argumentos de Lua compatibles con los tipos de argumentos esperados por el método nombrado. El nombre del método puede ser un nombre de método completo que consiste en un nombre de interfaz, seguido inmediatamente por un punto (.) , seguido inmediatamente por el nombre del método apropiado o un nombre de método apropiado por sí solo. En este último caso, el método se invoca a través de la primera interfaz registrada para el dispositivo que implementa el método nombrado. De lo contrario, el método se invoca a través de la interfaz nombrada.

__ctrl_submit devuelve dos valores: un ID de transacción (tid) y una cadena de error ( err) . En caso de éxito, tid es un número positivo que identifica de forma única el objeto de llamada recién creado, y err es nulo . En caso de error, tid es un código de error negativo (véase ctrl.Error a continuación) y err es una cadena opcional que puede explicar el fallo de la llamada. La cadena de error, si se proporciona, suele traducirse a la configuración regional del medidor.

status , result = __ctrl_result( tid ):

Obtener el resultado de la llamada al método identificado por el ID de transacción tid . El tid debe ser positivo y debe haber sido devuelto por una llamada previa a __ctrl_submit .

__ctrl_result devuelve dos valores: un entero llamado "status" y "result" . En caso de éxito, el estado es cero; en este caso, "result" es el resultado devuelto por la llamada al método, convertido a un valor Lua. En caso de error, el estado es un código de error negativo (véase ctrl.Error a continuación) y "result" es nulo . En particular, si una llamada al método sigue en curso, se devuelve el código de error ctrl.Error.AGAIN (-8) . En este caso, el usuario debe esperar un momento y volver a intentar la llamada a __ctrl_result hasta que tenga éxito.

status , err = __ctrl_cancel( tid ):

Se intenta cancelar la llamada al método identificado por el ID de transacción tid . Este tid debe ser positivo y debe haber sido devuelto por una llamada previa a __ctrl_submit .

__ctrl_cancel devuelve dos valores: un entero "status" y una cadena de error opcional "err" . El estado es cero en caso de éxito, en cuyo caso se garantiza que "tid" será un ID de transacción no válido hasta que se reutilice y se devuelva mediante otra llamada a __ctrl_submit . En caso de error, "status" es un código de error negativo (véase ctrl.Error a continuación) y "err" es una cadena opcional que puede explicar el fallo de la llamada. La cadena de error, si se proporciona, suele traducirse a la configuración regional del medidor.

ret , err = __ctrl_get_devices( attrs ):

Obtenga una lista de dispositivos que coinciden con los atributos opcionales especificados en la tabla attrs . Si se omite attrs o es nulo , se devuelve una lista de todos los dispositivos conocidos (registrados).

__ctrl_get_devices devuelve dos valores: la tabla ret y una cadena de error opcional, err . En caso de éxito, ret es una lista de tablas y err es nulo . Cada tabla de la lista devuelta corresponde a un dispositivo coincidente sin ningún orden en particular. La tabla contiene los pares nombre-valor registrados para ese dispositivo.

Por error, ret es nulo y err puede ser una cadena distinta de nula que explica qué salió mal, normalmente traducida a la configuración regional establecida en el medidor.

ret = __ctrl_get_interface( name ):

Obtenga una interfaz específica o una lista de todas las interfaces conocidas. El nombre del argumento debe ser una cadena que nombre la interfaz deseada; si no, debe omitirse o tener valor nulo para obtener una lista completa.

__ctrl_get_interface devuelve un único valor ret . Si se produjo un error o no se encuentra la interfaz solicitada, se devuelve nil . De lo contrario, ret es una única interfaz (si se especificó name) o una lista de interfaces (si se omitió name o se incluyó nil ). Cada interfaz se describe mediante una tabla con los siguientes miembros:

  • nombre : el nombre de la interfaz como una cadena.
  • métodos : una lista de métodos implementados por la interfaz.

Cada método se describe mediante una tabla con los siguientes miembros:

  • nombre : el nombre del método como una cadena.
  • arg_types : La firma de tipo DBus de los argumentos como una cadena.
  • ret_type : la firma de tipo DBus del valor de retorno como una cadena.
  • doc : Una descripción de la función del método en forma de cadena. Esta cadena puede contener referencias a los argumentos entre las etiquetas <arg>/</arg> . Por ejemplo, la cadena "<arg>foo</arg>" haría referencia al argumento del método llamado "foo" .
  • arg_names : Una cadena de nombres de argumentos separados por comas, en orden de aparición. Se utiliza únicamente para fines de documentación, ya que, salvo en la cadena de documentación, los nombres de los argumentos no tienen relevancia. Por ejemplo, la cadena "foo,bar" indicaría que el método espera dos argumentos, a los que se hace referencia en la cadena de documentación como foo y bar , respectivamente.



ret = __sleep( time ):

Suspender la ejecución de la llamada durante segundos. El tiempo especificado puede ser una fracción, no solo un entero. Si el programa Lua que llama a __sleep tiene otras corrutinas ejecutables, estas se ejecutan. Si no quedan corrutinas ejecutables, la ejecución del programa se suspende durante el tiempo mínimo necesario hasta que la primera corrutina vuelva a ser ejecutable.

La función devuelve un entero ret que es cero si tiene éxito o negativo si ocurrió un error.

ret = sleep( time ):

Este es un alias de __sleep y puede ser usado por programas Lua de usuario final para su comodidad. Las bibliotecas siempre deben llamar a __sleep en su lugar para garantizar que la función deseada se ejecute, incluso si un programa Lua redefine el nombre sleep con otro valor.

Control del módulo

Este módulo proporciona una interfaz de alto nivel para invocar métodos de control. Generalmente, es preferible usar este módulo en lugar de las funciones de bajo nivel documentadas en la sección anterior.

dev = ctrl:dev( attrs , obj ):

Crea un objeto de dispositivo de control con los atributos attrs. El argumento opcional obj se puede especificar para implementar una clase de dispositivo de control extendida, pero normalmente se omite (o se le puede pasar nulo ) para crear un nuevo objeto. Esta operación no se comunica con el dispositivo remoto identificado por attrs y, por lo tanto, retorna inmediatamente.

iface = dev:interface( name ):

Crea un objeto de interfaz para la interfaz identificada por el nombre del dispositivo dev . El objeto de interfaz devuelto contendrá un método proxy para cada método definido por la interfaz nombrada. Los métodos proxy se pueden llamar como cualquier otro método y reenvían automáticamente la llamada al dispositivo de control, esperando a que el resultado esté disponible en el dispositivo. Por lo tanto, estas operaciones pueden tardar mucho tiempo en completarse y llamarán a __sleep() según sea necesario. Por ello, es posible que se ejecuten otras corrutinas mientras se llama a un método proxy.


status , result = dev:call( method , args… )

Se llama al método llamado method en el dispositivo dev , se le pasan argumentos args y se devuelve el resultado. El nombre del método debe ser una cadena y args debe ser una secuencia de cero o más valores de argumentos de Lua compatibles con los tipos de argumentos esperados por el método nombrado. El nombre del método puede ser un nombre de método completo, compuesto por el nombre de una interfaz, seguido inmediatamente de un punto (.) , seguido inmediatamente del nombre del método correspondiente, o bien, un nombre de método correspondiente por sí solo. En este último caso, el método se invoca a través de la primera interfaz registrada para el dispositivo que implementa dicho método. De lo contrario, se invoca a través de la interfaz nombrada.

Se devuelven dos valores: un entero, status y result . El status es cero en caso de éxito, en cuyo caso result es el resultado devuelto por el método nombrado, convertido a un valor Lua. En caso de error, status es un código de error negativo (ver ctrl.Error a continuación) y result es nulo .

Error:

Esta tabla declara nombres simbólicos para varios errores de control, a saber:

UNSPEC (-1) : Ocurrió un error no especificado.

INVAL (-2) : Se pasó un argumento no válido o incompatible a un método.

NODEV (-3) : Los atributos del dispositivo especificaron una ruta de dispositivo no válida.

ATTRS (-4) : Los atributos no coinciden con el dispositivo seleccionado.

NOMETHOD (-5) : No se pudo encontrar el nombre del método.

NOENT (-6) : No se pudo encontrar el ID de transacción especificado.

OCUPADO (-7) : El dispositivo está ocupado (demasiadas llamadas pendientes).

OTRA VEZ (-8) : La llamada sigue pendiente.

Tareas del módulo

Este módulo proporciona una interfaz conveniente para crear varias tareas que pueden ejecutarse casi simultáneamente y luego ejecutarlas hasta que se hayan completado todas.

tasks:add( fun , args… ):

Añade una tarea que, al ejecutarse, ejecuta la función "fun" con argumentos " args" hasta que la función retorna. La función puede llamar a __sleep o a coroutine.yield para suspender la ejecución temporalmente y permitir que otras tareas se ejecuten.

tasks:run():

Ejecutar tareas agregadas previamente hasta que se hayan completado todas.