Cómo ampliar Medulla
En primer lugar, hay algunos aspectos importantes que hay que tener en cuenta a la hora de desarrollar en torno al agente:
El agente de la máquina se actualiza automáticamente cuando el código no coincide entre el agente y el código base del agente que se encuentra en el servidor. Para desactivar este comportamiento, es necesario añadir la siguiente configuración al archivo agentconf.ini y reiniciar el agente:
El agente de la máquina se actualiza automáticamente cuando el código no coincide entre el agente y el código base del agente que se encuentra en el servidor. Para desactivar este comportamiento, es necesario añadir la siguiente configuración al archivo agentconf.ini y reiniciar el agente:
[updateagent] updating = 0
Los complementos también se actualizan automáticamente si ha cambiado la versión. Si es necesario modificar un complemento, no actualices la versión hasta que se hayan realizado todas las pruebas.
Existen cuatro tipos de complementos para el agente de máquina: complementos de inicio, complementos de actualización, complementos de acción y complementos programados.
Existen cuatro tipos de complementos para el agente de máquina: complementos de inicio, complementos de actualización, complementos de acción y complementos programados.
- Los complementos de inicio son aquellos que se ejecutan al iniciar el agente y se definen en start_machine.ini;
- Los complementos de actualización se utilizan para instalar o actualizar componentes externos que utiliza el agente;
- Los complementos de acción son los que se activan mediante una acción recibida por el agente;
- Los complementos programados son aquellos que se invocan a una hora o intervalo específicos.
Cada plugin puede tener su propio archivo de configuración, cuyo nombre coincide con el del plugin, y debe añadirse al siguiente parámetro en agentconf.ini para que se cargue la configuración:
[plugin] pluginlist = xxxxxxx, yyyyyyy
Los complementos programados tienen su propia programación definida en el complemento, en el parámetro SCHEDULE. No obstante, esto se puede anular en el archivo manage_scheduler_machine.ini
Hay tres formas de ampliar Medulla:
- interacción con el agente de la máquina a través de un socket TCP
- interacción con el agente de la máquina a través de tuberías con nombre
- complementos de acción del agente de máquina
- complementos programados del agente de máquina
Cómo interactuar con el agente a través de un socket TCP
Es necesario definir una nueva acción en elarchivo server_kiosk.py, dentro de la función denominadahandle_client_connection, en la condición
if 'action' in result:
y esta acción debe añadirse al mensaje JSON que se envía a través del socket TCP.
A continuación se muestra un ejemplo del mensaje enviado:
A continuación se muestra un ejemplo del mensaje enviado:
{
"action": "myNewAction",
"sessionid": "mysessionid",
"base64": false,
"data": {
"date": "2020-06-24T15:45:02.000Z",
"family1": {
"field1": "value1",
"field2": "value2"
},
"family2": {
"field1": "value1",
"field2": "value2"
}
}
}
El contenido anterior debe guardarse en un archivo llamadojson_filepara enviarlo a través de un socket TCP o añadirse a una variable llamadajson_messageen tu código para enviarlo a través de tuberías con nombre
Y su equivalente en la función `handle_client_connection` de la clase `manage_kiosk_message`.
En la siguiente sección:
Y su equivalente en la función `handle_client_connection` de la clase `manage_kiosk_message`.
En la siguiente sección:
try:
_result = json.loads(minifyjsonstringrecv(msg))
Añade
if _result['action'] == "myNewAction":
substitute_recv = self.objectxmpp.sub_monitoring
logging.getLogger().warning("enviar a %s a %s" % (_result, substitute_recv))
self.objectxmpp.send_message(mbody=json.dumps(_result),
mto=substitute_recv,
mtype='chat')
return
Y en el siguiente bucle:
if 'action' in result:
if result['action'] == "kioskinterface":
...
Añadir
elif result['action'] == "myNewAction":
datasend['action'] = "myNewSubstituteAction"
subs_recv = self.objectxmpp.sub_monitoring
datasend['sessionid'] = getRandomName(6, "mynewsubstituteaction")
datasend['data'] = result['data']
El ejemplo anterior enviará a su vez el mensaje al JID de sub_monitoring con una nueva acción que debe llevarse a cabo: myNewSubstituteAction
El puerto TCP de escucha se define enel parámetrokiosk/am_local_portdel archivo agentconf.ini. El valor por defecto es 8765.
A continuación se muestra un ejemplo de programa de envío TCP escrito en Python para el siguiente paso, que consiste en el envío propiamente dicho de los datos al socket TCP:
#!/usr/bin/env python
# -*- coding: utf-8; -*-
#
# (c) 2023 siveo, http://www.siveo.net
#
# Este archivo forma parte de Medulla, http://www.siveo.net
#
# Pulse 2 es software libre; puedes redistribuirlo y/o modificarlo
# bajo los términos de la Licencia Pública General de GNU, tal y como la ha publicado
# la Fundación para el Software Libre; ya sea la versión 2 de la Licencia o
# (a tu elección) cualquier versión posterior.
#
# Pulse 2 se distribuye con la esperanza de que resulte útil,
# pero SIN NINGUNA GARANTÍA; ni siquiera la garantía implícita de
# COMERCIABILIDAD o IDONEIDAD PARA UN FIN DETERMINADO. Consulte la
# Licencia Pública General de GNU para obtener más detalles.
#
# Debería haber recibido una copia de la Licencia Pública General de GNU
# junto con Medulla; de no ser así, escriba a la Free Software
# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston,
# MA 02110-1301, EE. UU.
# archivo clientTCPcli.py
# Ejecuta en Python clientTCPcli.py -p ./file.json en el equipo cliente para
# introducir los datos
from optparse import OptionParser
import socket
import sys
import os
import select
def send_message(message, host, port, timeout_in_seconds = 5):
# Crear un socket TCP/IP
sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
# Conectar el socket al puerto en el que el servidor está a la escucha
try:
server_address = (host, port)
print >>sys.stderr, 'conectando a %s puerto %s' % server_address
sock.connect(server_address)
except socket.error, msgerror:
print 'Error al vincular la interfaz de comando ' + host + ' en el puerto ' + str(port) + ' Código de error: ' + str(msgerror[0]) + ' Mensaje: ' + msgerror[1] + '\n'
sys.exit(str(msgerror[0]))
try:
# Enviar datos
print >>sys.stderr, 'enviando "%s"' % mensaje
sock.sendall(mensaje)
# Esperar la respuesta
cantidad_recibida = 0
cantidad_esperada = len(mensaje)
ready = select.select([sock], [], [], timeout_in_seconds)
data = ""
if ready[0]:
data = sock.recv(4096)
return 0, data
return -1,""
finally:
print >>sys.stderr, 'cerrando el socket'
sock.close()
if __name__ == '__main__':
optp = OptionParser()
#optp.add_option("-h", "--help", action="store_true",
#dest="help", default=False,
#help="host")
optp.add_option("-H", "--host", action="store_true",
dest="host", default="localhost",
help="host")
optp.add_option("-P", "--port",
dest="port", default=8765,
help="puerto de conexión")
optp.add_option("-T", "--timeout",
dest="timeout_in_seconds", default=2,
help="Tiempo de espera de recepción en segundos")
optp.add_option("-m", "--msg",
dest="msg", default = "",
help="mensaje para enviar al servidor TCP")
optp.add_option("-p", "--pathfile",
dest="pathfile", default = None,
help="archivo de contenido para enviar al servidor TCP")
opts, args = optp.parse_args()
#if opts.help:
#print "uso del comando"
#os._exit(0)
message = ""
if opts.pathfile is None:
if opts.msg == "":
print "falta la opción < -m | -p> "
sys.exit(-1)
message = opts.msg
else:
if os.path.exists(opts.pathfile):
with open(opts.pathfile, 'r') as f:
message = f.read()
if message !="":
code, msg = send_message(message, opts.host, opts.port, opts.timeout_in_seconds)
print "código de error %s, respuesta del servidor %s"%(code,msg)
Para enviar el mensaje:
python clientTCPcli.py -p <archivo_json>
Cómo interactuar con el agente a través de tuberías con nombre
La forma de interactuar con el agente a través de tuberías con nombre es la misma que para interactuar con el agente a través de un socket TCP, salvo en lo que respecta al envío del mensaje.
Para enviar el mensaje a la tubería con nombre, aquí tienes un ejemplo de código escrito en Python:
import win32file
def send_message(json_message):
fileHandle = win32file.CreateFile("\\\\.\\pipe\\interfacechang",
win32file.GENERIC_READ | win32file.GENERIC_WRITE,
0,
None,
win32file.OPEN_EXISTING,
0,
None)
win32file.WriteFile(fileHandle, json_message)
win32file.CloseHandle(fileHandle)
Cómo escribir complementos de acción para el agente
A continuación se muestra una plantilla que se puede utilizar para escribir complementos de acción:
import logging
import json
plugin = {"VERSION": "1.0", "NAME": "mynewaction", "TYPE": "machine"}
logger = logging.getLogger()
def action( objectxmpp, action, sessionid, data, message, dataerror):
logger.debug("###################################################")
logger.debug("llamada %s desde %s, ID de sesión %s" % (plugin, message['from'], sessionid))
logger.debug("###################################################")
datasend = {"action" : "myNewSubstituteAction",
"data" : data,
"sessionid": sessionid,
"ret": 0,
"base64": False
}
objectxmpp.send_message(mto=objectxmpp.sub_monitoring,
mbody=json.dumps(datasend),
mtype='chat')
Ten en cuenta lo siguiente:
- NAME debe coincidir con el nombre del archivo del complemento. En este caso, el archivo se llamará plugin_mynewaction.py
- TYPE debe definirse como machine, relayserver o all, dependiendo de su destino
- La función de acción será el código que se ejecutará por defecto. El ejemplo anterior enviará, a su vez, el mensaje al JID «sub_monitoring» con una nueva acción que se llevará a cabo: «myNewSubstituteAction»
Cómo escribir complementos programados para el agente
A continuación se muestra una plantilla que se puede utilizar para escribir complementos programados:
import logging
import json
import os
import ConfigParser
from pulse_xmpp_agent.lib.agentconffile import directoryconffile
from pulse_xmpp_agent.lib.utils import file_put_contents
plugin = {"VERSION": "1.0", "NAME": "scheduling_mynewscheduledaction", "TYPE": "machine", "SCHEDULED": True}
SCHEDULE = {"schedule": "*/15 * * * *", "nb": -1}
logger = logging.getLogger()
def schedule_main(xmppobject):
logger.debug("===================================================")
logger.debug(plugin)
logger.debug("===================================================")
if xmppobject.num_call_scheduling_mynewscheduledaction == 0:
__read_conf(xmppobject)
if xmppobject.config.mynewscheduledaction_enable:
data = {}
data['family1'] = {}
data['family1']['field1'] = "value1"
data['family1']['field2'] = "value2"
data['family2'] = {}
data['family2']['field1'] = "value1"
data['family2']['field2'] = "value2"
si xmppobject.config.mynewscheduledaction_forward:
datasend = {"action" : "myNewSubstituteAction",
"data" : data,
"sessionid": "mysessionid",
"base64": False
}
objectxmpp.send_message(mto=objectxmpp.sub_monitoring,
mbody=json.dumps(datasend),
mtype='chat')
def __read_conf(xmppobject):
"""
Lee la configuración del complemento
"""
configfilename = os.path.join(directoryconffile(), "%s.ini" % plugin['NAME'])
logger.debug("Leyendo la configuración del archivo %s" % configfilename)
#parámetros por defecto
xmppobject.config.mynewscheduledaction_enable = True
xmppobject.config.mynewscheduledaction_forward = False
if not os.path.isfile(configfilename):
logger.warning("Falta el archivo de configuración %s del complemento %s" % (plugin['NAME'], configfilename))
logger.warning("El archivo de configuración que falta se creará automáticamente.")
file_put_contents(nombre_archivo_configuración,
"[mynewscheduledaction]\n" \
"enable = 1\n" \
"forward = 0\n")
# Cargar la configuración desde el archivo
Config = ConfigParser.ConfigParser()
Config.read(nombre_archivo_configuración)
if os.path.exists(nombre_archivo_configuración + «.local»):
Config.read(nombre_archivo_configuración + «.local»)
if Config.has_section(«mynewscheduledaction»):
if Config.has_option(«mynewscheduledaction», «enable»):
xmppobject.config.mynewscheduledaction_enable = Config.getboolean('mynewscheduledaction','enable')
si Config.has_option("mynewscheduledaction", "forward"):
xmppobject.config.mynewscheduledaction_forward = Config.getboolean('mynewscheduledaction','forward')
Ten en cuenta lo siguiente:
- NAME debe coincidir con el nombre del archivo del complemento. En este caso, el archivo se llamará plugin_mynewaction.py
- TYPE debe definirse como «machine», «relayserver» o «all», dependiendo de su destino
- La notación de SCHEDULE es similar a la notación cron. El parámetro adicional nb define cuántas veces debe ejecutarse el complemento. Si es -1, se ejecutará indefinidamente
- La función `schedule_main` será el código que se ejecutará por defecto. El ejemplo anterior leerá un archivo de configuración o lo creará si no existe, y enviará un mensaje al JID `sub_monitoring` con una nueva acción que debe llevarse a cabo: `myNewSubstituteAction`