Advertisement
Not a member of Pastebin yet?
Sign Up,
it unlocks many cool features!
- import os # Paquete necesario para las funciones que requieren de recursos del sistema operativo
- import threading # Paquete para generar hilos
- import subprocess # Paquete necesario para crear y llamar a subprocesos del sistema
- import paho.mqtt.client as mqtt # Paquete con las funciones necesarias para el servidor MQTT
- import kivy # Paquete general para crear la interfaz
- from kivy.app import App # Funciones para implementar nuestra ventana o App
- from kivy.uix.boxlayout import BoxLayout # Funciones para implementar una capa base donde se colocarán los elementos
- from kivy.uix.anchorlayout import AnchorLayout # Funciones para colocar elementos en sitios concretos del layout
- from kivy.uix.gridlayout import GridLayout # Funciones para organizar elementos en matrices
- from kivy.uix.button import Button # Funciones para manejar botones
- from kivy.uix.scrollview import ScrollView # Paquete de funciones para realizar scroll
- from kivy.uix.label import Label # Paquete con funciones para añadir etiquetas
- from kivy.uix.listview import ListItemButton # Funciones para manejar listas
- from kivy.uix.togglebutton import ToggleButton
- from kivy.clock import Clock, mainthread # Necesario para actualizar los elementos
- from kivy.event import EventDispatcher # Necesario para crear propiedades y eventos
- from kivy.properties import ObjectProperty, StringProperty, BooleanProperty, NumericProperty # Importación de propiedades de lista Kivy
- from kivy.config import Config # Para las configuraciones que sean necesarias
- # Configuración del tamaño de pantalla
- Config.set('graphics', 'width', 1024)
- Config.set('graphics', 'height', 600)
- # Los botones, widgets, layout y demás son todos widgets
- def ping_scan(): # Función para detectar dispositivos en la red
- with open(os.devnull, "wb") as limbo: # devnull es como un pozo sin fondo del que no se puede recuperar nada
- # y elimina el error por pantalla en tiempo de ejecución
- for n in range(200, 210 + 1): # Se añade el + 1 para que alcance el límite máximo introducido
- ip = "192.168.1.{0}".format(n)
- res = subprocess.Popen(['ping', '-n', '1', '-w', '200', ip], stdout=limbo, stderr=limbo).wait()
- if res:
- print("INACTIVA => " + ip)
- else:
- print("ACTIVA => " + ip)
- yield ip
- class DropDownItem(GridLayout): # Clase para crear nuestro propio widget
- container_title = ObjectProperty()
- container = ObjectProperty()
- title = StringProperty("")
- hidden = BooleanProperty(False)
- title_height = NumericProperty()
- def __init__(self, title="", title_height='50dp', **kwargs):
- super().__init__(**kwargs)
- self.cols = 1
- self.size_hint_y = None
- self.title = title
- self.title_height = title_height
- container_title = ToggleButton(height=title_height, size_hint_y=None, text=title)
- container_title.bind(state=self.on_drop_down)
- container = GridLayout(size_hint_y=None, cols=1)
- self.add_widget(container_title)
- self.add_widget(container)
- self.bind(minimum_height=self.setter('height'))
- self.bind(title=container_title.setter('text'))
- self.bind(title_height=container_title.setter('height'))
- container.bind(minimum_height=container.setter('height'))
- container.padding = ('10dp', '5dp', '10dp', '10dp')
- self.container = container
- self.container_title = container_title
- self.hidden = True
- def add_widget(self, widget, **kargs):
- if self.container is not None:
- self.container.add_widget(widget, **kargs)
- else:
- super().add_widget(widget, **kargs)
- def on_drop_down(self, obj, value):
- # print(value)
- if value == 'down':
- self.hidden = False
- else:
- self.hidden = True
- def on_hidden(self, obj, hidden):
- if hidden:
- self.container.opacity = 0
- self.container.size_hint = 0, 0
- self.container.size = 0, 0
- self.container.disabled = True
- else:
- self.container.opacity = 1
- self.container.size_hint = 1, None
- self.container.disabled = False
- self.container.height = self.container.minimum_height
- class DropDownList(BoxLayout):
- container_layout = ObjectProperty(None)
- scroll_view = ObjectProperty(None)
- only_one = BooleanProperty(False)
- def __init__(self, only_one=False, **kwargs):
- super().__init__(**kwargs)
- self.orientation = 'vertical'
- self.spacing = 10, 10
- self.scroll_view = ScrollView()
- self.bind(size=self.scroll_view.setter('size'))
- self.add_widget(self.scroll_view)
- self.container_layout = GridLayout(cols=1, size_hint_y=None)
- self.container_layout.bind(minimum_height=self.container_layout.setter('height'))
- self.scroll_view.add_widget(self.container_layout)
- self.only_one = only_one
- self._items = []
- def add_widget(self, widget, **kargs):
- if self.container_layout is not None:
- self.container_layout.add_widget(widget, **kargs)
- self._items.append(widget)
- else:
- super().add_widget(widget, **kargs)
- def on_only_one(self, obj, value):
- for item in self._items:
- item.container_title.group = "_ddlist" if value else None
- def clear(self):
- for item in self._items:
- self.container_layout.remove_widget(item)
- self._items.clear()
- class Contenedor(BoxLayout): # Crea una clase Contenedor que hereda las funciones de BoxLayout
- scanning = threading.Event() # Crea un evento de threads
- def __init__(self):
- super().__init__()
- # Con super se heredan todas las propiedades de Contenedor. Esto es necesario para añadir nuevas propiedades
- self.button_box = ButtonBox() # Instanciación a caja para botones
- self.info_box = InfoBox() # Instanciación a caja para información
- self.logo_box = LogoBox() # Instanciación a caja para logo
- self.add_widget(self.button_box) # Añade la caja al layout
- self.button_box.add_widget(self.logo_box) # Añade la caja para el logo en la caja de botones
- self.add_widget(self.info_box) # Añade la caja para info
- self.button_box.btn_buscar.bind(on_press=self.start_ping_scan)
- # on_press añade argumentos a la función. Hay que añadir *arg a la función. Con la función on_press detectamos
- # si ha sido apretado un botón y hace una acción si se da la condición
- def start_ping_scan(self, event=None):
- if self.scanning.is_set():
- self.scanning.clear()
- # self.button_box.btn_buscar.text = "Buscar dispositivos"
- else:
- self.scanning.set()
- threading.Thread(target=self._ping_scan).start()
- # self.button_box.btn_buscar.text = "Cancelar búsqueda"
- def _ping_scan(self): # El primer guion bajo es una convención para denotar variables o métodos
- self.info_box.limpiar_info()
- self.button_box.cambiar_texto_btn_buscar("Cancelar búsqueda")
- for ip in ping_scan():
- if not self.scanning.is_set():
- break
- self.info_box.agregar_dispositivo(ip)
- self.scanning.clear()
- self.button_box.cambiar_texto_btn_buscar("Buscar")
- class ButtonBox(BoxLayout): # LayOut para añadir los botones
- def __init__(self):
- super().__init__() # Hace referencia a la clase padre de forma directa y hace referencia a sus atributos o
- # llamar a sus métodos. En este caso, super().__init__() llama al método __init__ de la clase padre
- self.btn_buscar = Button(text="Buscar dispositivos") # Crea un botón para buscar dispositivos
- self.btn_conectar = Button(text="Conectar") # Crea un botón para conectarse a los dispositivos. Pone en marcha
- # el servidor MQTT
- self.btn_desconectar = Button(text="Desconectar") # Crea un botón para desconectarse de los dispositivos y
- # parar el servidor MQTT // También se debe usar para cerrar la aplicación
- self.add_widget(self.btn_buscar) # Añade el botón de buscar al LayOut
- self.add_widget(self.btn_conectar) # Añade el botón de conectar al LayOut
- self.add_widget(self.btn_desconectar) # Añade el botón de desconectar al LayOut
- @mainthread # Método llamado desde el hilo
- def cambiar_texto_btn_buscar(self, texto):
- self.btn_buscar.text = texto
- class Info(BoxLayout):
- """Clase con las opciones de cada dispositivo, ver .kv"""
- info_label = ObjectProperty()
- class Dispositivo(DropDownItem):
- def __init__(self, *args, **kwargs):
- super().__init__(*args, **kwargs)
- self._info_is_loaded = False
- def on_drop_down(self, obj, value):
- self.cargar_informacion() # AQUÍ ES DONDE SE CARGA LA INFORMACIÓN
- super().on_drop_down(obj, value)
- def cargar_informacion(self, **kwargs):
- """Método que obtiene la información de cada dispositivo"""
- if not self._info_is_loaded:
- info = Info()
- # nombre = "Dispositivo"
- if len(kwargs) == 0:
- nombre = 'default'
- else:
- for elem, data in kwargs.items():
- print(elem + '=>' + data)
- nombre = data
- mac = "1A:E2:36:56:7C:BD"
- info.info_label.text = ("[size=24][color=#ff6600]Nombre: [/color][/size]"
- f"[size=18][color=#2db300]{nombre}[/color][/size]n"
- "[size=24][color=#ff6600]MAC: [/color][/size]"
- f"[size=18][color=#2db300]{mac}[/color][/size]n"
- )
- self.add_widget(info)
- self._info_is_loaded = True
- self.btn_update = Button(text='Actualizar')
- self.add_widget(self.btn_update)
- class InfoBox(DropDownList): # LayOut para añadir la información de los dispositivos
- # ips = ObjectProperty(None)
- # def __init__(self):
- # super().__init__()
- # self._btn_disp = [] # Lista con las instancias de cada botón
- @mainthread # Método llamado desde el hilo hijo
- def agregar_dispositivo(self, ip):
- # btn = Button(text=ip)
- dispositivo = Dispositivo(title=ip)
- self.add_widget(dispositivo)
- # btn = ToggleButton(text=ip)
- # self._btn_disp.append(btn)
- # self.ips.add_widget(btn)
- # @ es un decorador de funciones. Un decorador es una función de orden superior que recibe otra función como
- # argumento y extiende su foncionalidad retornando, también, una función. mainthread extiende la funcionalidad de
- # un método/función para que sea llamado desde un hilo hijo pero que se ejecute en el hilo principal. Esto es
- # necesario porque no se puede interactuar directamente jamás con la interfaz y sus widgets desde un hilo hijo,
- # como ocurre con muchos otros frameworks gráficos, derivado generalmente de cómo funciona OpenGl.
- @mainthread # Método llamado desde el hilo hijo
- def limpiar_info(self):
- self.clear()
- # for btn in self._btn_disp:
- # self.ips.remove_widget(btn)
- # self._btn_disp.clear()
- class LogoBox(AnchorLayout): # LayOut para añadir el logo del programa en una esquina
- None
- class InterfazApp(App): # Creación de la aplicación como tal. Debe llevar el mismo nombre que el archivo .kv
- title = 'Centro de control' # Nombre del programa
- def build(self): # Función para que se ponga en marcha nuestra App
- return Contenedor()
- def on_stop(self):
- # Si se cierra la App mientras está escaneando hay que detener el hilo
- self.root.scanning.clear()
- def on_start(self):
- client_id = 'master'
- broker_ip = '192.168.1.100' # Solo vale para la Raspberry
- broker_port = 1883
- topic = 'RPi'
- mqttc = mqtt.Client(client_id=client_id, clean_session=True)
- mqttc.connect(broker_ip, broker_port, keepalive=60, bind_address="")
- mqttc.loop_start() # Con loop_start() no bloqueamos el código como haría loop() o loop_forever()
- def on_connect(client, userdata, flags, rc):
- mqttc.subscribe(topic, 0)
- def on_disconnect(client, userdata, message):
- print("Desconectado del broker")
- def on_subscribe(client, userdata, mid, granted_qos):
- print("Subscrito al topico")
- def on_message(client, userdata, message):
- global mensaje
- mensaje = message.payload.decode('utf-8')
- # print(message.topic + " " + str(message.qos) + " " + str(message.payload))
- print(mensaje) # Tenemos el mensaje que recibe el broker
- mqttc.on_connect = on_connect
- mqttc.on_disconnect = on_disconnect
- mqttc.on_subscribe = on_subscribe
- mqttc.on_message = on_message
- if __name__ == "__main__": # Obligatorio, aungue no necesario, para Android y Kivy, es un convencionalismo
- InterfazApp().run()
- # Los comentarios no pueden ir en línea con la línea del comando o sentencia
- <Contenedor>:
- orientation: 'vertical'
- spacing: 10
- # spacing es el espacio que hay entre widgets
- padding: 10
- # padding es el espacio entre el borde de la ventana y el contenido => iz - a - de - ab => Lista para distintos
- canvas:
- # Las instrucciones canvas son instrucciones gráficas para personalizar los widgets
- Color:
- rgb: 0, 0, 0
- # Son valores en tanto por uno. Con rgba añadimos el alfa
- Rectangle:
- size: self.size
- pos: self.pos
- # self hace referencia al widget o layout máx póximo a la indentación
- # En este caso, mismo tamaño y misma posición que Contenedor
- <ButtonBox>:
- # Por defecto, las BoxLayout vienen orientadas de forma horizontal
- spacing: 10
- padding: 10
- size_hint: 1, None
- # Deshabilitación del tamaño relativo en X e Y
- # width: 650
- height: 50
- canvas:
- Color:
- # rgb: 0.78, 0.78, 0.78
- rgb: 0.65, 0.65, 0.65
- Rectangle:
- size: self.size
- pos: self.pos
- <Info>:
- info_label: info_label
- orientation: 'vertical'
- size_hint_y: None
- height: '200dp'
- canvas:
- Color:
- rgba: 0.52, 0, 0.7, 0.5
- Rectangle:
- pos: self.pos
- size: self.size
- Label:
- id: info_label
- markup: True
- BoxLayout:
- size_hint_y: None
- height: '50dp'
- # Button:
- # text: "Conectar"
- # Button:
- # text: "Desconectar"
- <LogoBox>:
- spacing: 2
- padding: 5
- size_hint: None, None
- width: 40
- height: 32
- canvas:
- Rectangle:
- source: 'UNIT_n.png'
- size: self.size
- pos: self.pos
Advertisement
Add Comment
Please, Sign In to add comment
Advertisement