Advertisement
Guest User

Untitled

a guest
Jun 19th, 2019
91
0
Never
Not a member of Pastebin yet? Sign Up, it unlocks many cool features!
text 14.81 KB | None | 0 0
  1. import os # Paquete necesario para las funciones que requieren de recursos del sistema operativo
  2.  
  3. import threading # Paquete para generar hilos
  4. import subprocess # Paquete necesario para crear y llamar a subprocesos del sistema
  5.  
  6. import paho.mqtt.client as mqtt # Paquete con las funciones necesarias para el servidor MQTT
  7.  
  8. import kivy # Paquete general para crear la interfaz
  9.  
  10. from kivy.app import App # Funciones para implementar nuestra ventana o App
  11. from kivy.uix.boxlayout import BoxLayout # Funciones para implementar una capa base donde se colocarán los elementos
  12. from kivy.uix.anchorlayout import AnchorLayout # Funciones para colocar elementos en sitios concretos del layout
  13. from kivy.uix.gridlayout import GridLayout # Funciones para organizar elementos en matrices
  14. from kivy.uix.button import Button # Funciones para manejar botones
  15. from kivy.uix.scrollview import ScrollView # Paquete de funciones para realizar scroll
  16. from kivy.uix.label import Label # Paquete con funciones para añadir etiquetas
  17. from kivy.uix.listview import ListItemButton # Funciones para manejar listas
  18. from kivy.uix.togglebutton import ToggleButton
  19. from kivy.clock import Clock, mainthread # Necesario para actualizar los elementos
  20. from kivy.event import EventDispatcher # Necesario para crear propiedades y eventos
  21. from kivy.properties import ObjectProperty, StringProperty, BooleanProperty, NumericProperty # Importación de propiedades de lista Kivy
  22. from kivy.config import Config # Para las configuraciones que sean necesarias
  23.  
  24. # Configuración del tamaño de pantalla
  25. Config.set('graphics', 'width', 1024)
  26. Config.set('graphics', 'height', 600)
  27.  
  28.  
  29. # Los botones, widgets, layout y demás son todos widgets
  30.  
  31.  
  32. def ping_scan(): # Función para detectar dispositivos en la red
  33. with open(os.devnull, "wb") as limbo: # devnull es como un pozo sin fondo del que no se puede recuperar nada
  34. # y elimina el error por pantalla en tiempo de ejecución
  35. for n in range(200, 210 + 1): # Se añade el + 1 para que alcance el límite máximo introducido
  36. ip = "192.168.1.{0}".format(n)
  37. res = subprocess.Popen(['ping', '-n', '1', '-w', '200', ip], stdout=limbo, stderr=limbo).wait()
  38. if res:
  39. print("INACTIVA => " + ip)
  40. else:
  41. print("ACTIVA => " + ip)
  42. yield ip
  43.  
  44.  
  45. class DropDownItem(GridLayout): # Clase para crear nuestro propio widget
  46. container_title = ObjectProperty()
  47. container = ObjectProperty()
  48. title = StringProperty("")
  49. hidden = BooleanProperty(False)
  50. title_height = NumericProperty()
  51.  
  52. def __init__(self, title="", title_height='50dp', **kwargs):
  53. super().__init__(**kwargs)
  54. self.cols = 1
  55. self.size_hint_y = None
  56. self.title = title
  57. self.title_height = title_height
  58.  
  59. container_title = ToggleButton(height=title_height, size_hint_y=None, text=title)
  60. container_title.bind(state=self.on_drop_down)
  61.  
  62. container = GridLayout(size_hint_y=None, cols=1)
  63. self.add_widget(container_title)
  64. self.add_widget(container)
  65.  
  66. self.bind(minimum_height=self.setter('height'))
  67. self.bind(title=container_title.setter('text'))
  68. self.bind(title_height=container_title.setter('height'))
  69. container.bind(minimum_height=container.setter('height'))
  70. container.padding = ('10dp', '5dp', '10dp', '10dp')
  71.  
  72. self.container = container
  73. self.container_title = container_title
  74. self.hidden = True
  75.  
  76. def add_widget(self, widget, **kargs):
  77. if self.container is not None:
  78. self.container.add_widget(widget, **kargs)
  79. else:
  80. super().add_widget(widget, **kargs)
  81.  
  82. def on_drop_down(self, obj, value):
  83. # print(value)
  84. if value == 'down':
  85. self.hidden = False
  86. else:
  87. self.hidden = True
  88.  
  89. def on_hidden(self, obj, hidden):
  90. if hidden:
  91. self.container.opacity = 0
  92. self.container.size_hint = 0, 0
  93. self.container.size = 0, 0
  94. self.container.disabled = True
  95. else:
  96. self.container.opacity = 1
  97. self.container.size_hint = 1, None
  98. self.container.disabled = False
  99. self.container.height = self.container.minimum_height
  100.  
  101.  
  102. class DropDownList(BoxLayout):
  103. container_layout = ObjectProperty(None)
  104. scroll_view = ObjectProperty(None)
  105. only_one = BooleanProperty(False)
  106.  
  107. def __init__(self, only_one=False, **kwargs):
  108. super().__init__(**kwargs)
  109. self.orientation = 'vertical'
  110. self.spacing = 10, 10
  111.  
  112. self.scroll_view = ScrollView()
  113. self.bind(size=self.scroll_view.setter('size'))
  114. self.add_widget(self.scroll_view)
  115.  
  116. self.container_layout = GridLayout(cols=1, size_hint_y=None)
  117. self.container_layout.bind(minimum_height=self.container_layout.setter('height'))
  118. self.scroll_view.add_widget(self.container_layout)
  119.  
  120. self.only_one = only_one
  121. self._items = []
  122.  
  123. def add_widget(self, widget, **kargs):
  124. if self.container_layout is not None:
  125. self.container_layout.add_widget(widget, **kargs)
  126. self._items.append(widget)
  127. else:
  128. super().add_widget(widget, **kargs)
  129.  
  130. def on_only_one(self, obj, value):
  131. for item in self._items:
  132. item.container_title.group = "_ddlist" if value else None
  133.  
  134. def clear(self):
  135. for item in self._items:
  136. self.container_layout.remove_widget(item)
  137. self._items.clear()
  138.  
  139.  
  140. class Contenedor(BoxLayout): # Crea una clase Contenedor que hereda las funciones de BoxLayout
  141. scanning = threading.Event() # Crea un evento de threads
  142.  
  143. def __init__(self):
  144. super().__init__()
  145. # Con super se heredan todas las propiedades de Contenedor. Esto es necesario para añadir nuevas propiedades
  146.  
  147. self.button_box = ButtonBox() # Instanciación a caja para botones
  148. self.info_box = InfoBox() # Instanciación a caja para información
  149. self.logo_box = LogoBox() # Instanciación a caja para logo
  150.  
  151. self.add_widget(self.button_box) # Añade la caja al layout
  152. self.button_box.add_widget(self.logo_box) # Añade la caja para el logo en la caja de botones
  153. self.add_widget(self.info_box) # Añade la caja para info
  154.  
  155. self.button_box.btn_buscar.bind(on_press=self.start_ping_scan)
  156. # 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
  157. # si ha sido apretado un botón y hace una acción si se da la condición
  158.  
  159. def start_ping_scan(self, event=None):
  160. if self.scanning.is_set():
  161. self.scanning.clear()
  162. # self.button_box.btn_buscar.text = "Buscar dispositivos"
  163. else:
  164. self.scanning.set()
  165. threading.Thread(target=self._ping_scan).start()
  166. # self.button_box.btn_buscar.text = "Cancelar búsqueda"
  167.  
  168. def _ping_scan(self): # El primer guion bajo es una convención para denotar variables o métodos
  169. self.info_box.limpiar_info()
  170. self.button_box.cambiar_texto_btn_buscar("Cancelar búsqueda")
  171. for ip in ping_scan():
  172. if not self.scanning.is_set():
  173. break
  174. self.info_box.agregar_dispositivo(ip)
  175. self.scanning.clear()
  176. self.button_box.cambiar_texto_btn_buscar("Buscar")
  177.  
  178.  
  179. class ButtonBox(BoxLayout): # LayOut para añadir los botones
  180.  
  181. def __init__(self):
  182. super().__init__() # Hace referencia a la clase padre de forma directa y hace referencia a sus atributos o
  183. # llamar a sus métodos. En este caso, super().__init__() llama al método __init__ de la clase padre
  184. self.btn_buscar = Button(text="Buscar dispositivos") # Crea un botón para buscar dispositivos
  185. self.btn_conectar = Button(text="Conectar") # Crea un botón para conectarse a los dispositivos. Pone en marcha
  186. # el servidor MQTT
  187. self.btn_desconectar = Button(text="Desconectar") # Crea un botón para desconectarse de los dispositivos y
  188. # parar el servidor MQTT // También se debe usar para cerrar la aplicación
  189. self.add_widget(self.btn_buscar) # Añade el botón de buscar al LayOut
  190. self.add_widget(self.btn_conectar) # Añade el botón de conectar al LayOut
  191. self.add_widget(self.btn_desconectar) # Añade el botón de desconectar al LayOut
  192.  
  193. @mainthread # Método llamado desde el hilo
  194. def cambiar_texto_btn_buscar(self, texto):
  195. self.btn_buscar.text = texto
  196.  
  197.  
  198. class Info(BoxLayout):
  199. """Clase con las opciones de cada dispositivo, ver .kv"""
  200. info_label = ObjectProperty()
  201.  
  202.  
  203. class Dispositivo(DropDownItem):
  204. def __init__(self, *args, **kwargs):
  205. super().__init__(*args, **kwargs)
  206. self._info_is_loaded = False
  207.  
  208. def on_drop_down(self, obj, value):
  209. self.cargar_informacion() # AQUÍ ES DONDE SE CARGA LA INFORMACIÓN
  210. super().on_drop_down(obj, value)
  211.  
  212. def cargar_informacion(self, **kwargs):
  213. """Método que obtiene la información de cada dispositivo"""
  214. if not self._info_is_loaded:
  215. info = Info()
  216. # nombre = "Dispositivo"
  217. if len(kwargs) == 0:
  218. nombre = 'default'
  219. else:
  220. for elem, data in kwargs.items():
  221. print(elem + '=>' + data)
  222. nombre = data
  223. mac = "1A:E2:36:56:7C:BD"
  224. info.info_label.text = ("[size=24][color=#ff6600]Nombre: [/color][/size]"
  225. f"[size=18][color=#2db300]{nombre}[/color][/size]n"
  226. "[size=24][color=#ff6600]MAC: [/color][/size]"
  227. f"[size=18][color=#2db300]{mac}[/color][/size]n"
  228. )
  229. self.add_widget(info)
  230. self._info_is_loaded = True
  231. self.btn_update = Button(text='Actualizar')
  232. self.add_widget(self.btn_update)
  233.  
  234.  
  235.  
  236.  
  237. class InfoBox(DropDownList): # LayOut para añadir la información de los dispositivos
  238. # ips = ObjectProperty(None)
  239.  
  240. # def __init__(self):
  241. # super().__init__()
  242. # self._btn_disp = [] # Lista con las instancias de cada botón
  243.  
  244. @mainthread # Método llamado desde el hilo hijo
  245. def agregar_dispositivo(self, ip):
  246. # btn = Button(text=ip)
  247. dispositivo = Dispositivo(title=ip)
  248. self.add_widget(dispositivo)
  249. # btn = ToggleButton(text=ip)
  250. # self._btn_disp.append(btn)
  251. # self.ips.add_widget(btn)
  252.  
  253. # @ es un decorador de funciones. Un decorador es una función de orden superior que recibe otra función como
  254. # argumento y extiende su foncionalidad retornando, también, una función. mainthread extiende la funcionalidad de
  255. # un método/función para que sea llamado desde un hilo hijo pero que se ejecute en el hilo principal. Esto es
  256. # necesario porque no se puede interactuar directamente jamás con la interfaz y sus widgets desde un hilo hijo,
  257. # como ocurre con muchos otros frameworks gráficos, derivado generalmente de cómo funciona OpenGl.
  258.  
  259. @mainthread # Método llamado desde el hilo hijo
  260. def limpiar_info(self):
  261. self.clear()
  262. # for btn in self._btn_disp:
  263. # self.ips.remove_widget(btn)
  264. # self._btn_disp.clear()
  265.  
  266.  
  267. class LogoBox(AnchorLayout): # LayOut para añadir el logo del programa en una esquina
  268. None
  269.  
  270.  
  271. class InterfazApp(App): # Creación de la aplicación como tal. Debe llevar el mismo nombre que el archivo .kv
  272. title = 'Centro de control' # Nombre del programa
  273.  
  274. def build(self): # Función para que se ponga en marcha nuestra App
  275. return Contenedor()
  276.  
  277. def on_stop(self):
  278. # Si se cierra la App mientras está escaneando hay que detener el hilo
  279. self.root.scanning.clear()
  280.  
  281. def on_start(self):
  282. client_id = 'master'
  283. broker_ip = '192.168.1.100' # Solo vale para la Raspberry
  284. broker_port = 1883
  285. topic = 'RPi'
  286.  
  287. mqttc = mqtt.Client(client_id=client_id, clean_session=True)
  288.  
  289. mqttc.connect(broker_ip, broker_port, keepalive=60, bind_address="")
  290.  
  291. mqttc.loop_start() # Con loop_start() no bloqueamos el código como haría loop() o loop_forever()
  292.  
  293. def on_connect(client, userdata, flags, rc):
  294. mqttc.subscribe(topic, 0)
  295.  
  296. def on_disconnect(client, userdata, message):
  297. print("Desconectado del broker")
  298.  
  299. def on_subscribe(client, userdata, mid, granted_qos):
  300. print("Subscrito al topico")
  301.  
  302. def on_message(client, userdata, message):
  303. global mensaje
  304. mensaje = message.payload.decode('utf-8')
  305. # print(message.topic + " " + str(message.qos) + " " + str(message.payload))
  306. print(mensaje) # Tenemos el mensaje que recibe el broker
  307.  
  308. mqttc.on_connect = on_connect
  309. mqttc.on_disconnect = on_disconnect
  310. mqttc.on_subscribe = on_subscribe
  311. mqttc.on_message = on_message
  312.  
  313.  
  314. if __name__ == "__main__": # Obligatorio, aungue no necesario, para Android y Kivy, es un convencionalismo
  315. InterfazApp().run()
  316.  
  317. # Los comentarios no pueden ir en línea con la línea del comando o sentencia
  318. <Contenedor>:
  319. orientation: 'vertical'
  320. spacing: 10
  321. # spacing es el espacio que hay entre widgets
  322. padding: 10
  323. # padding es el espacio entre el borde de la ventana y el contenido => iz - a - de - ab => Lista para distintos
  324.  
  325. canvas:
  326. # Las instrucciones canvas son instrucciones gráficas para personalizar los widgets
  327. Color:
  328. rgb: 0, 0, 0
  329. # Son valores en tanto por uno. Con rgba añadimos el alfa
  330. Rectangle:
  331. size: self.size
  332. pos: self.pos
  333. # self hace referencia al widget o layout máx póximo a la indentación
  334. # En este caso, mismo tamaño y misma posición que Contenedor
  335.  
  336. <ButtonBox>:
  337. # Por defecto, las BoxLayout vienen orientadas de forma horizontal
  338. spacing: 10
  339. padding: 10
  340. size_hint: 1, None
  341. # Deshabilitación del tamaño relativo en X e Y
  342. # width: 650
  343. height: 50
  344. canvas:
  345. Color:
  346. # rgb: 0.78, 0.78, 0.78
  347. rgb: 0.65, 0.65, 0.65
  348. Rectangle:
  349. size: self.size
  350. pos: self.pos
  351.  
  352. <Info>:
  353. info_label: info_label
  354.  
  355. orientation: 'vertical'
  356. size_hint_y: None
  357. height: '200dp'
  358.  
  359. canvas:
  360. Color:
  361. rgba: 0.52, 0, 0.7, 0.5
  362. Rectangle:
  363. pos: self.pos
  364. size: self.size
  365.  
  366. Label:
  367. id: info_label
  368. markup: True
  369.  
  370. BoxLayout:
  371. size_hint_y: None
  372. height: '50dp'
  373. # Button:
  374. # text: "Conectar"
  375. # Button:
  376. # text: "Desconectar"
  377.  
  378. <LogoBox>:
  379. spacing: 2
  380. padding: 5
  381. size_hint: None, None
  382. width: 40
  383. height: 32
  384. canvas:
  385. Rectangle:
  386. source: 'UNIT_n.png'
  387. size: self.size
  388. pos: self.pos
Advertisement
Add Comment
Please, Sign In to add comment
Advertisement