# Cómo ampliar Medulla

<div id="bkmrk-first-of-all%2C-there-">En primer lugar, hay algunas cosas importantes que hay que tener en cuenta al desarrollar en torno al agente:  
  
El agente de la máquina se actualiza automáticamente cuando el código no es idéntico 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 a agentconf.ini y reiniciar el agente:</div>```
[updateagent]
updating = 0
```

<div id="bkmrk-">  
</div><div id="bkmrk-the-plugins-are-also">Los complementos también se actualizan automáticamente si ha cambiado la versión. Si es necesario modificar un complemento, no actualice la versión hasta que se hayan realizado todas las pruebas.  
  
Hay cuatro tipos de complementos para el agente de máquina: complementos de inicio, complementos de actualización, complementos de acción y complementos programados.</div>1. Los complementos de inicio son aquellos que se ejecutan cuando se inicia el agente y se definen en start\_machine.ini;
2. Los complementos de actualización se utilizan para instalar o actualizar componentes externos utilizados por el agente;
3. Los complementos de acción son invocados por una acción recibida por el agente;
4. Los complementos programados son aquellos que se invocan a una hora o intervalo específicos.

<div id="bkmrk-each-plugin-can-have">  
Cada plugin puede tener su propio archivo de configuración con el mismo nombre que el plugin y debe añadirse al siguiente parámetro en agentconf.ini para que se cargue la configuración:</div>```
[plugin]
pluginlist = xxxxxxx, yyyyyyy
```

<div id="bkmrk-the-scheduled-plugin">  
Los complementos programados tienen su propia programación definida en el complemento, en el parámetro SCHEDULE. Sin embargo, esto se puede anular en el archivo manage_scheduler_machine.ini  
<figure class="attachment attachment--content attachment--horizontal-rule">---

</figure>  
  
Hay tres formas de ampliar Medulla:</div>1. interacción con el agente de la máquina a través de un socket TCP
2. interacción con el agente de la máquina a través de tuberías con nombre
3. complementos de acción del agente de máquina
4. complementos programados del agente de máquina

<div id="bkmrk--1"><figure class="attachment attachment--content attachment--horizontal-rule">---

</figure></div>##### Cómo interactuar con el agente a través de un socket TCP

<div id="bkmrk-a-new-action-needs-t">  
Es necesario definir una nueva acción en**server\_kiosk.py**, en la función denominada**handle\_client\_connection**, en la condición </div>```
if 'action' in result:
```

<div id="bkmrk-and-this-action-adde">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:</div>```
{
    "action": "myNewAction", 
    "sessionid": "mysessionid", 
    "base64": false, 
    "data": {
        "date": "2020-06-24T15:45:02.000Z", 
        "family1": {
            "field1": "value1", 
            "field2": "value2"
        }, 
        "family2": {
            "field1": "value1", 
            "field2": "value2"
        }
    }
}
```

<div id="bkmrk-the-above-content-is">El contenido anterior debe guardarse en un archivo llamado**json\_file**para enviarlo a través de un socket TCP o añadirse a una variable llamada**json\_message**en 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:</div>```
try:
    _result = json.loads(minifyjsonstringrecv(msg))
```

<div id="bkmrk-add">Añade</div>```
if _result['action'] == "myNewAction":
    substitute_recv = self.objectxmpp.sub_monitoring
    logging.getLogger().warning("send to %s to %s" % (_result,substitute_recv ))
    self.objectxmpp.send_message(mbody=json.dumps(_result),
                                 mto=substitute_recv,
                                 mtype='chat')
    return
```

<div id="bkmrk-and-in-the-following">Y en el siguiente bucle:</div>```
if 'action' in result:
    if result['action'] == "kioskinterface":
...
```

<div id="bkmrk-add-1">Añadir</div>```
elif result['action'] == "myNewAction":
    datasend['action'] = "myNewSubstituteAction"
    subs_recv = self.objectxmpp.sub_monitoring
    datasend['sessionid'] = getRandomName(6, "mynewsubstituteaction")
    datasend['data'] = result['data']
```

<div id="bkmrk-the-above-example-wi">  
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 en**el parámetrokiosk/am\_local\_portdel archivo agentconf.ini**. El valor predeterminado es 8765.  
  
A continuación se muestra un ejemplo de emisor TCP escrito en Python para el siguiente paso, que es el envío real de los datos al socket TCP: </div>```
#!/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; puede redistribuirlo y/o modificarlo
# bajo los términos de la Licencia Pública General de GNU tal y como la publica
# la Free Software Foundation; ya sea la versión 2 de la Licencia, o
# (a su elección) cualquier versión posterior.
#
# Pulse 2 se distribuye con la esperanza de que sea ú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; si no es así, escriba a la Free Software
# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston,
# MA 02110-1301, EE. UU.
# archivo clientTCPcli.py

# Ejecute python clientTCPcli.py  -p ./file.json en la máquina cliente para 
# inyectar 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 en la interfaz de comando ' + host + ' 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)

        # Buscar 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 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="port connection")

    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 "opción faltante < -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)
```

<div id="bkmrk-to-send-the-message%3A">  
Para enviar el mensaje: </div>```
python clientTCPcli.py -p <archivo_json>
```

<div id="bkmrk--2">  
<figure class="attachment attachment--content attachment--horizontal-rule">---

</figure></div>##### Cómo interactuar con el agente a través de tuberías con nombre

<div id="bkmrk-the-way-to-interact-">  
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, excepto en lo que respecta al envío del mensaje.  
  
Para enviar el mensaje a la tubería con nombre, aquí hay un código de ejemplo escrito en Python:</div>```
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)
```

<div id="bkmrk--3">  
<figure class="attachment attachment--content attachment--horizontal-rule">---

</figure></div>##### Cómo escribir complementos de acción para el agente

<div id="bkmrk-below-is-a-template-">  
A continuación se muestra una plantilla que se puede utilizar para escribir complementos de acción:</div>```
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')
```

<div id="bkmrk-please-note-the-foll">  
Ten en cuenta lo siguiente: </div>- 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

<div id="bkmrk--4"><figure class="attachment attachment--content attachment--horizontal-rule">---

</figure></div>##### Cómo escribir complementos programados para el agente

<div id="bkmrk-below-is-a-template--1">  
A continuación se muestra una plantilla que se puede utilizar para escribir complementos programados:</div>```
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"
    
    if 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 en el archivo %s" % configfilename)
    
    #parámetros predeterminados
    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(configfilename,
                          "[mynewscheduledaction]\n" \
                          "enable = 1\n" \
                          "forward = 0\n")
                            
    # Cargar la configuración desde el archivo
    Config = ConfigParser.ConfigParser()
    Config.read(nombre_archivo_conf)
    if os.path.exists(nombre_archivo_conf + ".local"):
        Config.read(nombre_archivo_conf + ".local")
    if Config.has_section("mynewscheduledaction"):
        if Config.has_option("mynewscheduledaction", "enable"):
            xmppobject.config.mynewscheduledaction_enable = Config.getboolean('mynewscheduledaction','enable')
        if Config.has_option("mynewscheduledaction", "forward"):
            xmppobject.config.mynewscheduledaction_forward = Config.getboolean('mynewscheduledaction','forward')
```

<div id="bkmrk-please-note-the-foll-1">Ten en cuenta lo siguiente: </div>- 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 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 se debe llevar a cabo: myNewSubstituteAction