#!/usr/bin/env python
# -*- coding: utf-8 -*-
#
#  Copyright 2020 Rolf Dahl-Skog <rolfds@gmail.com>
#  
#  This program is free software; you can redistribute it and/or modify
#  it under the terms of the GNU General Public License as published by
#  the Free Software Foundation; either version 3 of the License, or
#  (at your option) any later version.
#  
#  This program is distributed in the hope that it will be useful,
#  but WITHOUT ANY WARRANTY; without even the implied warranty of
#  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
#  GNU General Public License for more details.
#  
#  You should have received a copy of the GNU General Public License
#  along with this program; if not, write to the Free Software
#  Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston,
#  MA 02110-1301, USA.
#  
#________________________________________________________________
config = {}
def configuracion():
    """ Lee configuracion desde archivo "config.txt"
    """
    archivo = "config.txt"
    ruta = os.getcwd() + os.path.sep
    global config

    # valores por defecto, si no estan en el archivo.
    config["mb_ip"] = '192.168.0.100'  # ip del PLC
    config["mb_port"] = 502
    config["mb_timeout"] = 0.25   # segundos
    config["DI_ini"] = 0      # direccion de primer discreto leido
    config["DI_cant"] = 128    # cuantos discretos leer
    config["AI_ini"] = 0       # direccion de primer analogo leido
    config["AI_cant"] = 24     # cuantos analogos leer
    config["DO_ini"] = 500     # direccion primer discreto para escribir
    config["DO_cant"] = 8      # cuantos discretos para escribir
    config["AO_ini"] = 100     # direccion primer analogo para escribir
    config["AO_cant"] = 4      # cuantos analogos para escribir

    if (os.path.exists(ruta + archivo) != True):
        tc = "Error:  No se encuentra el archivo  '"+ archivo +"'\nen la ruta actual\n"+ ruta
        tc =tc + "\nSe usan valores por defecto."
    else:
        # si existe archivo lo lee y analiza
        conf = configparser.ConfigParser()
        conf.read(ruta + archivo)
        print(ruta + archivo)
        try:
            config["mb_ip"] = conf.get('Modbus', 'ip')
            print(" get  ip",config["mb_ip"])
        except:
            print(" default ip",config["mb_ip"])
        try:
            config["mb_port"] = conf.getint('Modbus', 'port')
            print(" get  port",config["mb_port"])
        except:
            print(" default port",config["mb_port"])
        try:
            config["mb_timeout"] = conf.getfloat('Modbus', 'timeout')
            print(" get  timeout",config["mb_timeout"])
        except:
            print(" default timeout",config["mb_timeout"])
        try:
            config["DI_ini"] = conf.getint('Digital Read Only', 'init')
            print(" get  DI_ini",config["DI_ini"])
        except:
            print(" default DI_ini",config["DI_ini"])
        try:
            config["DI_cant"] = conf.getint('Digital Read Only', 'count')
            print(" get  DI_cant",config["DI_cant"])
        except:
            print(" default DI_cant",config["DI_cant"])
        try:
            config["AI_ini"] = conf.getint('Analog Read Only', 'init')
            print(" get  AI_ini",config["AI_ini"])
        except:
            print(" default AI_ini",config["AI_ini"])
        try:
            config["AI_cant"] = conf.getint('Analog Read Only', 'count')
            print(" get  AI_cant",config["AI_cant"])
        except:
            print(" default AI_cant",config["AI_cant"])
        try:
            config["DO_ini"] = conf.getint('Digital Read And Write', 'init')
            print(" get  DO_ini",config["DO_ini"])
        except:
            print(" default DO_ini",config["DO_ini"])
        try:
            config["DO_cant"] = conf.getint('Digital Read And Write', 'count')
            print(" get  DO_cant",config["DO_cant"])
        except:
            print(" default DO_cant",config["DO_cant"])
        try:
            config["AO_ini"] = conf.getint('Analog Read And Write', 'init')
            print(" get  AO_ini",config["AO_ini"])
        except:
            print(" default AO_ini",config["AO_ini"])
        try:
            config["AO_cant"] = conf.getint('Analog Read And Write', 'count')
            print(" get  AO_cant",config["AO_cant"])
        except:
            print(" default AO_cant",config["AO_cant"])
        tc = "La configuracion se leyo desde  '"+ archivo +"'\nen la ruta actual\n"+ ruta
    return tc
#___________________________________________________________
try:
    import serial
except:
    print("Error: falta libreria pyserial")
import socket
try:
    from tkinter import *
    from tkinter import messagebox
except:
    print("Error: falta libreria tkinter")
import configparser
import os
try:
    import pymodbus
    from pymodbus.client.sync import ModbusTcpClient
except:
    messagebox.showerror('Faltan dependencias',"""Error:
Faltan dependencias, no se encuentra  pymodbus
https://github.com/riptideio/pymodbus/
https://pypi.org/project/pymodbus/

En un terminal ejecuta
 pip install pymodbus""", icon='error')

#___________________________________________________________
class dialogAbout():
    def __init__(self, parent, texto):
        ventAbout = self.ventAbout = Toplevel(parent)
        self.parent = parent
        ventAbout.title("About")
        ventAbout.resizable(width=False, height=False)
        ventAbout.config(bg="#E5E5E5")
        ventAbout.transient(parent) # esta ventana por sobre ventMain
        ventAbout.takefocus = True
        ventAbout.focus_set()

        LabTit = Label(ventAbout, text="Modbus test")
        LabTit.config(font=('Verdana', 10, 'bold'), fg="black")
        LabTit.config(justify=LEFT, anchor='w', bg="#E5E5E5")
        LabTit.grid(row=0, column=0, sticky="ew", padx=10, pady=2)

        t = """Esta herramienta es un simple cliente Modbus/Tcp
puede leer y escribir, desde/hacia un esclavo (servidor).

Es software libre, puedes copiarlo y usarlo, bajo los terminos
de la licencia GNU General Public License, version 3.
Rolf  Dahl-skog Stade.       (Abril 2020)"""
        LabAbout = Label(ventAbout, text= t)
        LabAbout.config(font=('Verdana', 10, 'normal'), fg="black")
        LabAbout.config(justify=LEFT, anchor='w', bg="#E5E5E5")
        LabAbout.grid(row=1, column=0, sticky="ew", padx=10, pady=2)

        LabConf = Label(ventAbout, text= texto)
        if "Error" in texto:
            LabConf.config(font=('Verdana', 10, 'normal'), fg="red")
        else:
            LabConf.config(font=('Verdana', 10, 'normal'), fg="black")
        LabConf.config(justify=LEFT, anchor='w', bg="#E5E5E5")
        LabConf.grid(row=2, column=0, sticky="ew", padx=10, pady=2)

        botCancel = Button(ventAbout, text= 'Ok', command= self.BotonCerrar)
        botCancel.config(borderwidth=3, relief="raised", padx=5, pady=5)
        botCancel.config(font=('Verdana', 10, 'normal'), anchor='center')
        botCancel.config(fg="black", bg="#E5E5E5")
        botCancel.grid(row=10, column=0, sticky="ew", padx=10, pady=2)
        self.ventAbout.after(180000, self.BotonCerrar)

    def BotonCerrar(self):
        self.ventAbout.destroy()

    def show(self):
        self.parent.takefocus = True
        self.parent.focus_set()
        self.ventAbout.takefocus = True
        self.ventAbout.focus_set()
        self.ventAbout.wait_window()

#___________________________________________________________
class Aplicacion():
    def __init__(self, ventMain):
        self.ventMain = ventMain
        self.ventMain.title("Modbus Test")
        #self.ventMain.geometry('700x360')
        self.ventMain.minsize(550,100)
        self.ventMain.maxsize(1300,900)
        self.ventMain.resizable(width=True,height=True)
        self.colorFrente = "black"
        self.colorFondo = "#E5E5E5"
        self.fuente = ('Verdana', 9, 'normal')
        self.ventMain.config(bg=self.colorFondo)
        self.ventMain.grid_rowconfigure(2,weight=1)
        
        tc = configuracion()
        crear_basedatos()
        # si discretos < 230 usar 10 columnas, sino usar 20 columnas
        Dcolum = 10 if config["DI_cant"] < 230 else 20
        # si analogas < 49 usar 2 columnas, sino usar 4 columnas
        Acolum = 2 if config["AI_cant"] < 49 else 4
        
        self.LabMsg = Label(self.ventMain, anchor='w')
        self.LabMsg.config(fg=self.colorFrente, bg=self.colorFondo)
        self.LabMsg.config(font=self.fuente, justify=CENTER)
        self.LabMsg.config(borderwidth=0, relief="solid",padx=4, pady=1)
        self.LabMsg.grid(row=0, column=0, columnspan=2, sticky="nsew", padx=1, pady=1)
        self.msg= " Leer  M"+str(config["DI_ini"]) \
        +" a "+str(config["DI_ini"]+config["DI_cant"]-1) \
        +"  y  MW"+str(config["AI_ini"])+" a "+str(config["AI_ini"]+config["AI_cant"]-1) \
        +"  desde Modbus/TCP "+config["mb_ip"]+"    "
        self.LabMsg.config(text= self.msg)

        self.LabCom = Label(self.ventMain, anchor='center')
        self.LabCom.config(fg="red", bg=self.colorFondo)
        self.LabCom.config(font=('Verdana', 10, 'bold'), justify=CENTER)
        self.LabCom.config(borderwidth=0, relief="solid",padx=4, pady=1)
        self.LabCom.grid(row=0, column=2, sticky="nsew", padx=1, pady=1)
        self.LabCom.config(text= "Sin com.")

        # arreglo de botones para discretas que se pueden escribir
        self.cajaDO = Frame(self.ventMain)
        self.cajaDO.config( borderwidth=0, relief="solid", bg=self.colorFondo)
        self.cajaDO.grid(row=1, column=0, sticky="w", padx=3, pady=2)
        self.labDO =  [0 for i in range(config["DO_cant"])]
        for i in range(len(self.labDO)):
            self.labDO[i] = Label(self.cajaDO)
            self.labDO[i].config(font=self.fuente, padx=1, pady=0)
            self.labDO[i].config(text= str(i + config["DO_ini"]))
            self.labDO[i].config(foreground="black", bg="#E5E5E5")
            self.labDO[i].config(borderwidth=3, relief="raised", anchor='center')
            self.labDO[i].bind("<Button-1>", lambda event, num=i: self.DOclick(event,num) )
            f = int( i / Dcolum)  # en ... columnas
            c = i % Dcolum
            self.labDO[i].grid(row=f, column=c, sticky="nsew", padx=2, pady=2)
            self.cajaDO.grid_rowconfigure(f,weight=0)
            self.cajaDO.grid_columnconfigure(c,weight=1)

        # arreglo de luces para discretas que solo se pueden leer
        self.cajaDI = Frame(self.ventMain)
        self.cajaDI.config(borderwidth=0, relief="solid", bg=self.colorFondo)
        self.cajaDI.grid(row=2, column=0, sticky="nsew", padx=3, pady=1)
        self.labDI =  [0 for i in range(config["DI_cant"])]
        for i in range(len(self.labDI)):
            self.labDI[i] = Label(self.cajaDI)
            self.labDI[i].config(font=self.fuente, padx=2, pady=0)
            self.labDI[i].config(text= str(i + config["DI_ini"]))
            self.labDI[i].config(foreground="black", bg="#E5E5E5")
            self.labDI[i].config(borderwidth=1, relief="solid", anchor='center')
            f = int( i / Dcolum)  # en ... columnas
            c = i % Dcolum
            self.labDI[i].grid(row=f, column=c, sticky="nsew", padx=1, pady=2)
            self.cajaDI.grid_rowconfigure(f,weight=0)
            self.cajaDI.grid_columnconfigure(c,weight=1)

        self.cajaA = Frame(self.ventMain)
        self.cajaA.config(borderwidth=0, relief="solid", bg=self.colorFondo)
        self.cajaA.grid(row=1, column=1, rowspan=2, columnspan=2, sticky="nsew", padx=3, pady=3)
        # arreglo valores analogas que se pueden escribir
        self.labAO =  [0 for a in range(len(AO))]
        for i in range(len(self.labAO)):
            self.labAO[i] = Label(self.cajaA)
            self.labAO[i].config(font=self.fuente, padx=4, pady=2 )
            self.labAO[i].config(text= str(i + config["AO_ini"])+"= "+ str(AO[i]).zfill(5) )
            self.labAO[i].config(borderwidth=3, relief="raised", anchor='center')
            self.labAO[i].config(foreground="black", bg="#E5E5E5")
            self.labAO[i].bind("<Button-1>", lambda event, num=i: self.AOclick(event,num) )
            f1 = int( i / Acolum)  # en ... columnas
            c = i % Acolum
            self.labAO[i].grid(row=f1, column=c, sticky="nsew", padx=2, pady=2)
            self.cajaA.grid_rowconfigure(f,weight=0)
            self.cajaA.grid_columnconfigure(c,weight=1)
        # arreglo valores analogas que solo se pueden leer
        self.labAI =  [0 for a in range(len(AI))]
        for i in range(len(self.labAI)):
            self.labAI[i] = Label(self.cajaA)
            self.labAI[i].config(font=self.fuente, padx=5, pady=1 )
            self.labAI[i].config(text= str(i + config["AI_ini"])+"= "+ str(AI[i]).zfill(5) )
            self.labAI[i].config(borderwidth=0, relief="solid", anchor='center')
            self.labAI[i].config(foreground="black", bg="#E5E5E5")
            f2 = int( i / Acolum)  # en ... columnas
            c = i % Acolum
            self.labAI[i].grid(row=(f1+f2+1), column=c, sticky="nsew", padx=4, pady=1)
            self.cajaA.grid_rowconfigure((f1+f2+1),weight=0)
            self.cajaA.grid_columnconfigure(c,weight=1)

        d = dialogAbout(self.ventMain, tc).show()
        ventMain.after(100, self.Actualizar)
    #___________________________________________
    def DOclick(self,event, num):
        boton = num
        direccion = num + config["DO_ini"]
        donde, resp = dialogDI(self.ventMain, direcc=direccion).show()
        if resp=='1':
            r = Escribir_Modbus_coil(donde, True)
            if not (r is None):
                print(r)
        if resp=='0':
            r = Escribir_Modbus_coil(donde, False)
            if not (r is None):
                print(r)
    #___________________________________________
    def AOclick(self,event, num):
        boton = num
        direccion = num + config["AO_ini"]
        if num < len(AO):
            val = AO[num]
        else:
            val = 5000
        donde, resp = dialogAI(self.ventMain, direc=direccion, valor=val).show()
        if resp != None:
            if resp < 0:
                num = 32766 - resp
            else:
                num = resp
            r = Escribir_Modbus_Reg(donde, num)
            if not (r is None):
                print(r)
    #___________________________________________
    def Actualizar(self):
        global DI, AI, DO, AO
        # pedir nuevos datos
        comOk = Leer_Modbus()

        if comOk:  # si hay comunicacion
            self.LabCom.config(text= "Com Ok", fg="green")
            # mostrar estados discretas que se pueden escribir
            for i in range(len(self.labDO)):
                if i < len(DO):
                    if DO[i] == "1":
                        self.labDO[i].config(bg="#99FE99") # verde
                    else:
                        self.labDO[i].config(bg="#FFD5C0") # rojo
            # mostrar estados discretas que solo se pueden leer
            for i in range(len(self.labDI)):
                if i < len(DI):
                    if DI[i] == "1":
                        self.labDI[i].config(bg="#99FE99") # verde
                    else:
                        self.labDI[i].config(bg="#FFD5C0") # rojo
            # mostrar valores analogas que se pueden escribir
            for i in range(len(self.labAO)):
                if i < len(AO):
                    self.labAO[i].config(text= str(i + config["AO_ini"]) \
                    +"= "+ str(AO[i]).zfill(5) )
            # mostrar valores analogas que solo se pueden leer
            for i in range(len(self.labAI)):
                if i < len(AI):
                    self.labAI[i].config(text= str(i + config["AI_ini"]) \
                    +"= "+ str(AI[i]).zfill(5) )

        else:  # si NO hay comunicacion
            self.LabCom.config(text= "Sin Com.", fg="red")

        # repetir
        self.ventMain.after( 250, self.Actualizar)

#___________________________________________________________
class dialogDI(): # cambiar una discreta
    def __init__(self, parent, direcc):
        dialog = self.dialog = Toplevel(parent)
        dialog.title('%M'+ str(direcc) +' ?')
        dialog.minsize(250, 150)
        dialog.resizable(width=False, height=False)
        dialog.config(bg="#E5E5E5")
        dialog.grid_rowconfigure([0,1,2],weight=1)
        dialog.grid_columnconfigure([0,1],weight=1)
        dialog.transient(parent) # por sobre...
        dialog.takefocus = True
        dialog.focus_set()
        self.direcc = direcc
        self.resp = ""

        self.labTexto = Label(dialog, text='Escribir en %M'+ str(direcc) +' ?')
        self.labTexto.config( wraplength=190, font=('Verdana', 11, 'normal') )
        self.labTexto.config( foreground='black', bg='#E5E5E5')
        self.labTexto.config( justify=LEFT, anchor='w', padx=5, pady=0)
        self.labTexto.grid(row=0, column=0, columnspan=2, sticky="nsew", padx=8, pady=10)

        self.bot1 = Button(dialog, text="1", command= self.Bot_1 )
        self.bot1.config(borderwidth=3, relief="raised", padx=1, pady=5)
        self.bot1.config( font=('Verdana', 11, 'normal'), anchor='center')
        self.bot1.config(fg="black", bg="#E5E5E5")
        self.bot1.grid(row=1, column=0, sticky="nsew", padx=8, pady=10)

        self.bot0 = Button(dialog, text="0", command= self.Bot_0 )
        self.bot0.config(borderwidth=3, relief="raised", padx=1, pady=5)
        self.bot0.config( font=('Verdana', 11, 'normal'), anchor='center')
        self.bot0.config(fg="black", bg="#E5E5E5")
        self.bot0.grid(row=1, column=1, sticky="nsew", padx=8, pady=10)

        self.botCancelar = Button(dialog, text="Cancelar", command= self.Bot_Cancelar )
        self.botCancelar.config(borderwidth=3, relief="raised", padx=1, pady=5)
        self.botCancelar.config( font=('Verdana', 10, 'normal'), anchor='center')
        self.botCancelar.config(fg="black", bg="#E5E5E5")
        self.botCancelar.grid(row=2, column=0, columnspan=2, sticky="nsew", padx=8, pady=10)
        self.botCancelar.focus()

        dialog.after(30000, self.Bot_Cancelar)

    def show(self):
        self.dialog.wait_window()
        return self.direcc, self.resp

    def Bot_1(self):
        self.resp = "1"
        self.dialog.destroy()

    def Bot_0(self):
        self.resp = "0"
        self.dialog.destroy()

    def Bot_Cancelar(self):
        self.resp = ""
        self.dialog.destroy()
#___________________________________________________________
class dialogAI(): # cambiar una analoga
    def __init__(self, parent, direc, valor):
        dialog = self.dialog = Toplevel(parent)
        dialog.title('%MW'+ str(direc) +' ?')
        dialog.minsize(300, 100)
        dialog.resizable(width=False, height=False)
        dialog.config(bg="#E5E5E5")
        dialog.grid_rowconfigure([0,1,2],weight=1)
        dialog.grid_columnconfigure([0,1],weight=1)
        dialog.transient(parent) # por sobre...
        dialog.takefocus = True
        dialog.focus_set()
        self.direc = direc
        self.resp = valor

        self.labTexto = Label(dialog, text='Valor para escribir en %MW'+ str(direc) +' ?')
        self.labTexto.config( wraplength=290, font=('Verdana', 11, 'normal') )
        self.labTexto.config( foreground='black', bg='#E5E5E5')
        self.labTexto.config( justify=LEFT, anchor='w', padx=5, pady=0)
        self.labTexto.grid(row=0, column=0, columnspan=2, sticky="nsew", padx=8, pady=10)

        self.entrada1 = Entry(dialog, width=8, justify="left")
        self.entrada1.insert(0,str(self.resp))
        self.entrada1.config(font=('Verdana', 12, 'normal'))
        self.entrada1.grid(row=1, column=0, padx=20, pady=10)

        self.lab1 = Label(dialog, justify=LEFT, anchor='w')
        self.lab1.config(font=('Verdana', 9, 'normal'),foreground='black', bg='#E5E5E5')
        self.lab1.config(text='Entero 16 bits con signo\nRango valido:\ndesde -32768 hasta +32767')
        self.lab1.grid(row=1, column=1, padx=2, pady=3)

        self.botGo = Button(dialog, text="enviar", command= self.Bot_Go )
        self.botGo.config(borderwidth=3, relief="raised", padx=1, pady=5)
        self.botGo.config( font=('Verdana', 11, 'normal'), anchor='center')
        self.botGo.config(fg="black", bg="#E5E5E5")
        self.botGo.grid(row=2, column=0, sticky="nsew", padx=8, pady=10)

        self.botCancelar = Button(dialog, text="Cancelar", command= self.Bot_Cancelar )
        self.botCancelar.config(borderwidth=3, relief="raised", padx=1, pady=5)
        self.botCancelar.config( font=('Verdana', 10, 'normal'), anchor='center')
        self.botCancelar.config(fg="black", bg="#E5E5E5")
        self.botCancelar.grid(row=2, column=1, columnspan=2, sticky="nsew", padx=8, pady=10)
        self.botCancelar.focus()

        dialog.after(60000, self.Bot_Cancelar)

    def show(self):
        self.dialog.wait_window()
        return self.direc, self.resp

    def Bot_Go(self):
        txt = self.entrada1.get().strip()
        if txt[0] == '-':
            neg = True
            txt = txt[1:]
        else:
            neg = False
        if txt.isdigit():
            num = int(txt)
            if neg:
                num = 65536 - num
            if num < 65536:
                self.resp = num
                self.dialog.destroy()

    def Bot_Cancelar(self):
        self.resp = None
        self.dialog.destroy()
#___________________________________________________________
def Leer_Modbus():
    global DI, AI, DO, AO
    # leer coils y registros desde PLC
    ip = config["mb_ip"]
    port = config["mb_port"]
    timeout = config["mb_timeout"]
    bitsIn = []
    regsIn = []
    bitsOut = []
    regsOut = []

    PLC = ModbusTcpClient(host=ip, port=port, timeout=timeout, retries=1)
    if PLC.connect():
        try:
            if config["DI_cant"] > 0:
                resp = PLC.read_coils(address=config['DI_ini'], \
                count=config["DI_cant"])
                bitsIn = resp.bits[0:]

            if config["AI_cant"] > 0:
                resp = PLC.read_holding_registers(address=config["AI_ini"], \
                count=config["AI_cant"])
                regsIn = resp.registers[0:]

            if config["DO_cant"] > 0:
                resp = PLC.read_coils(address=config['DO_ini'], \
                count=config["DO_cant"])
                bitsOut = resp.bits[0:]

            if config["AO_cant"] > 0:
                resp = PLC.read_holding_registers(address=config["AO_ini"], \
                count=config["AO_cant"])
                regsOut = resp.registers[0:]

            PLC.close()
        except:
            pass

    if len(bitsIn) > 0:
        t = ""
        for i in range(len(bitsIn)):
            if bitsIn[i]:
                t = t + '1'
            else:
                t = t + '0'
        DI = t
    if len(regsIn) > 0:
        analogos = []
        for i in range(len(regsIn)):
            num = regsIn[i]
            if num > 32767:
                num = num - 65536
            analogos.append( num )
        AI = analogos
    if len(bitsOut) > 0:
        t = ""
        for i in range(len(bitsOut)):
            if bitsOut[i]:
                t = t + '1'
            else:
                t = t + '0'
        DO = t
    if len(regsOut) > 0:
        analogos = []
        for i in range(len(regsOut)):
            num = regsOut[i]
            if num > 32767:
                num = num - 65536
            analogos.append( num )
        AO = analogos
    if (len(bitsIn) > 0) or (len(regsIn) > 0):
        return True
    else:
        return False
#___________________________________________________________
def Escribir_Modbus_coil(direccion, valor):
    datos = [valor]
    r = None
    if direccion >= 0 and direccion < 10000:
        ip = config["mb_ip"]
        port = config["mb_port"]
        timeout = config["mb_timeout"]
        PLC = ModbusTcpClient(host=ip, port=port, timeout=timeout, retries=1)
        if PLC.connect():
            try:
                resp = PLC.write_coils(direccion, datos)
                PLC.close()
                r = ' send '+ str(valor) +' to coil '+ str(direccion)
            except:
                pass
    return r
#___________________________________________________________
def Escribir_Modbus_Reg(direccion, valores):
    # valores es una lista de valores, o un unico valor.
    r = None
    if direccion >= 0 and direccion < 10000:
        ip = config["mb_ip"]
        port = config["mb_port"]
        timeout = config["mb_timeout"]
        PLC = ModbusTcpClient(host=ip, port=port, timeout=timeout, retries=1)
        if PLC.connect():
            try:
                resp = PLC.write_registers(direccion, valores )
                PLC.close()
                r = ' send '+ str(valores) +' to reg '+ str(direccion)
            except:
                pass
    return r
#___________________________________________________________
DI = []
AI = []
DO = []
AO = []
def crear_basedatos():
    """crear arreglos para guardar los datos"""
    global DI, AI, DO, AO
    for i in range(config["DI_cant"]):
        DI.append("?")
    for i in range(config["AI_cant"]):
        AI.append(0)
    for i in range(config["DO_cant"]):
        DO.append("?")
    for i in range(config["AO_cant"]):
        AO.append(0)
#___________________________________________________________
root = Tk()
def main():
    app = Aplicacion(root)
    root.mainloop()
if __name__ == '__main__':
    main()
#____________________________________________________________