From fabe37225cec159d29836f1555abba85b12e476b Mon Sep 17 00:00:00 2001 From: Leandro Lucarella Date: Fri, 28 Oct 2005 06:32:11 +0000 Subject: [PATCH] =?utf8?q?Primer=20draft=20de=20la=20aplicaci=C3=B3n=20cli?= =?utf8?q?ente.=20Por=20ahora=20s=C3=B3lo=20responde=20a=20los=20eventos?= =?utf8?q?=20de=20dibujado.=20Falta=20hacer=20la=20parte=20de=20cliente-se?= =?utf8?q?rvidor=20para=20que=20reciba=20y=20envie=20las=20cosas.=20En=20e?= =?utf8?q?l=20futuro=20habr=C3=A1=20que=20extenderlo=20(o=20no=3F)=20para?= =?utf8?q?=20que=20se=20pueda=20cambiar=20el=20tama=C3=B1o=20de=20la=20mat?= =?utf8?q?riz=20(depende=20de=20si=20llegamos=20a=20implementar=20esto=20e?= =?utf8?q?n=20el=20dispositivo=20o=20no).?= MIME-Version: 1.0 Content-Type: text/plain; charset=utf8 Content-Transfer-Encoding: 8bit --- cliente/etherled.glade | 281 +++++++++++++++++++++++++++++ cliente/etherled.py | 75 ++++++++ cliente/led-off.png | Bin 0 -> 1330 bytes cliente/led-on.png | Bin 0 -> 1357 bytes cliente/led.py | 55 ++++++ cliente/simplegladeapp.py | 341 +++++++++++++++++++++++++++++++++++ cliente/svg/led-off.svg | 317 +++++++++++++++++++++++++++++++++ cliente/svg/led-on.svg | 326 ++++++++++++++++++++++++++++++++++ cliente/svg/leds.svg | 361 ++++++++++++++++++++++++++++++++++++++ 9 files changed, 1756 insertions(+) create mode 100644 cliente/etherled.glade create mode 100755 cliente/etherled.py create mode 100644 cliente/led-off.png create mode 100644 cliente/led-on.png create mode 100644 cliente/led.py create mode 100644 cliente/simplegladeapp.py create mode 100644 cliente/svg/led-off.svg create mode 100644 cliente/svg/led-on.svg create mode 100644 cliente/svg/leds.svg diff --git a/cliente/etherled.glade b/cliente/etherled.glade new file mode 100644 index 0000000..3a720df --- /dev/null +++ b/cliente/etherled.glade @@ -0,0 +1,281 @@ + + + + + + + 10 + True + Etherled + GTK_WINDOW_TOPLEVEL + GTK_WIN_POS_NONE + False + True + False + True + False + False + GDK_WINDOW_TYPE_HINT_NORMAL + GDK_GRAVITY_NORTH_WEST + True + + + + + True + False + 10 + + + + True + False + 5 + + + + True + IP: + False + False + GTK_JUSTIFY_LEFT + False + False + 0.5 + 0.5 + 0 + 0 + PANGO_ELLIPSIZE_NONE + -1 + False + 0 + + + 0 + False + False + + + + + + True + True + True + True + 0 + + True + * + False + + + 0 + True + True + + + + + 0 + True + True + + + + + + True + 16 + 16 + False + 0 + 0 + + + 0 + True + True + + + + + + True + GTK_BUTTONBOX_END + 0 + + + + True + True + True + gtk-quit + True + GTK_RELIEF_NORMAL + True + + + + + + + True + True + True + GTK_RELIEF_NORMAL + True + + + + + True + 0.5 + 0.5 + 0 + 0 + 0 + 0 + 0 + 0 + + + + True + False + 2 + + + + True + gtk-go-down + 4 + 0.5 + 0.5 + 0 + 0 + + + 0 + False + False + + + + + + True + _Recibir + True + False + GTK_JUSTIFY_LEFT + False + False + 0.5 + 0.5 + 0 + 0 + PANGO_ELLIPSIZE_NONE + -1 + False + 0 + + + 0 + False + False + + + + + + + + + + + + True + True + True + GTK_RELIEF_NORMAL + True + + + + + True + 0.5 + 0.5 + 0 + 0 + 0 + 0 + 0 + 0 + + + + True + False + 2 + + + + True + gtk-go-up + 4 + 0.5 + 0.5 + 0 + 0 + + + 0 + False + False + + + + + + True + _Enviar + True + False + GTK_JUSTIFY_LEFT + False + False + 0.5 + 0.5 + 0 + 0 + PANGO_ELLIPSIZE_NONE + -1 + False + 0 + + + 0 + False + False + + + + + + + + + + + 0 + True + True + + + + + + + diff --git a/cliente/etherled.py b/cliente/etherled.py new file mode 100755 index 0000000..d766768 --- /dev/null +++ b/cliente/etherled.py @@ -0,0 +1,75 @@ +#!/usr/bin/env python +# -*- coding: iso-8859-1 -*- +# vim: set expandtab tabstop=4 shiftwidth=4 : +#---------------------------------------------------------------------------- +# Etherled +#---------------------------------------------------------------------------- +# This file is part of etherled. +# +# etherled 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 2 of the License, or (at your option) +# any later version. +# +# etherled 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 etherled; if not, write to the Free Software Foundation, Inc., 59 +# Temple Place, Suite 330, Boston, MA 02111-1307 USA +#---------------------------------------------------------------------------- +# Creado: vie oct 27 22:16:20 ART 2005 +# Autores: Leandro Lucarella +#---------------------------------------------------------------------------- + +import os +import gtk +from simplegladeapp import SimpleGladeApp +from simplegladeapp import bindtextdomain +from dispatcher import Dispatcher +from led import Led + +app_name = "etherled" +app_version = "1.0" +glade_dir = "" +locale_dir = "" + +bindtextdomain(app_name, locale_dir) + + +class MainWindow(SimpleGladeApp): + + def __init__(self, path="etherled.glade", root="main_window", + domain=app_name, **kwargs): + #notificar = Dispatcher(self.actualizar) + path = os.path.join(glade_dir, path) + SimpleGladeApp.__init__(self, path, root, domain, **kwargs) + + def new(self): + for i in xrange(16): + for j in xrange(16): + led = Led() + self.table_leds.attach(led, i, i+1, j, j+1) + led.show() + + def on_btn_salir_clicked(self, widget, *args): + self.quit() + + def on_main_window_delete_event(self, widget, event, *args): + self.quit() + + + +def main(): + gtk.threads_init() + main_window = MainWindow() + gtk.threads_enter() + main_window.run() + gtk.threads_leave() + + +if __name__ == '__main__': + main() + diff --git a/cliente/led-off.png b/cliente/led-off.png new file mode 100644 index 0000000000000000000000000000000000000000..c1fa097af0a7990b2cb981fc0f2da9faaf2f6c2b GIT binary patch literal 1330 zcmV-21f03X_Vs^&;S4g%t=H+R7i=nRb6ZpRTTd2 z&&=#ByW3q1fflORrcfe(7Jq^o6U0E;v`B(HfPFFH!Ni|OeNz)(>rYH1sgFQne6nDK zKqLlHLqb&obTI}Ziz$JYHtf<(XXgGKALj0|Eg{ASZ*np>bLXD#JLfy+4DdfM_OJsY zVgQ0lrIHyQ9)4EU>wA46)(9!v=c8!)&dki^za}TYveupjp#Kv95rrcoBOS-b#@=7o z-97Nkh7I}l<;w%l^WX`AL~F!Jg4@&6>X-5H+SzmGe)ywOc?-bpe+3|-{O-ZQJ;gBm zc>mB)sZ=O1Ap|&MV2pu?V6BC=7Fug4V^Eu$!`R7_)$dN9e&_b=?4eeFVzKj~ojc!s zZsW#}_V)L;wdHaw@O=cn58wCU2?5z+f^%@j;7N(zo}Nt4>eU04N+my2t$zOi01<_| z1_p*-?Cbkr`_`>X!XUr`=TgFx5>m?h&N&BbNyE}oNp^R2J$-3nVzySVpKk&X5kGL? z!1{uehhN#TqYwrGe9uD=1gUf1hv#{aQbI`CWX=G9u?9m}A}q=0MX{}I+fV1u9|bVY z9e8kcVq#?f;9xQ1nEH;n9|Q<8nbg9t3F`ZPNR4gZ$F$Pv^m^e;ZO9=~rlxm(?W72Qk-R2X3Awm?# zMgqXh>})Nuc4c4;wAL`3Lkj_+R9Z1aut@^us>T=tqZE`<>G#G!JE4suM?|<=t0fWu zYSrquQN6w{BLqAlptJ^05^x}`6whlm77@WZBFcd%W1zH#HYVL$3owSs$;ojJ05_(l z4qYmj=MrPmNvSnrrH~{EVxFfB#vpR!ED?^KIyG&r{joXhYW4b`k9TzJ zS<&9^H9C|4kaO0^v(h@XMug63q!c0-MZH?pUmiK~My%AuCV;W_+O_M~d!O&?dqM;O zC)cH>QB_()S)1m(kw&c%Ip*_9A=VnOmOeRn@bryax87SYu3Z$zE|$yNw{F_BihCY! zl%RIPDXozhgV?1ea$3zhp_wuC+34t{->zKQ2EaT30Dxv|wL{|eJEDyf|(Zzlq3ed7P_pDs`W@l$-|0AVRW=Sq5=jZ2jwN^|1 ozIpS^#MP^x12_kyQ)2P@2V#LP^Ra^COaK4?07*qoM6N<$g1qH-3;+NC literal 0 HcmV?d00001 diff --git a/cliente/led-on.png b/cliente/led-on.png new file mode 100644 index 0000000000000000000000000000000000000000..97b033caa1228b0947534d2e8c690979f3671efe GIT binary patch literal 1357 zcmV-T1+w~yP)f03X_Vs^&;S4g=Sf6CR7i=nl}(6cM-|6^ zb*oTTT)1)*aUoeo z5RnAQs0cE;n2C-f#t@sf6ronO`Y*Z&m!pToS} zgsK{#+}zw8o;`c^jNjUN)U@qAh4=4`l3(BH{9iU>{F%V-fb>5BRJA&P{`~P@JoVJK zPaMoP?oV;@uHef;1%nbqX|k}Ja=Pr}?2^cZf56v)*WMAJs*^_^e)vn}%P;@nk#4>= zQ8A^nSYt2-RFsr6T^8CHIZTONANhUE2NzYJcolwfb3Xgu$v4iN`PQj_ZGCT}J6u^? za%Oc@F{y@(%OQ1HQY{S_x&cF1;4lmTYYewTd0cd37gl%R*|z{xwfgLX51zg6+AH6A zz&GR7QN{A8VqA|Hk1FcXh-%mamYicy6gXoj41l7dIIy>ieEb^h&EVn@Kvj!#=gytB zmoNSF)9vBP%BW&xR57V*CiR%QuBqypVKt;2EHQ8nS1gh%po*%JB_m2P7X9R(;Td4t zEiO?0{lbOsf3s`WMoR<6!;-olF|KQ>aZOo|ab*cbfi`nu)8MS7Fy=^;5EHE%Fi*<5 z$-_@5PhW(OSb*tt`hoSoUit1l604PJ$fT+mk4MzY6RKmkVeh^dbMJ@X{UW?!ySFhyBmjlmWLg|*nh609D> ztiOli)G1_dAMHBWo_2gx^tc2JuyaN zjD#%2oC%@??FkWb5AgmVl#VVX+7KCrK-sl8TVNEHUi=%yD>cSCXdCk30lsPIx|Ys+ z+LZ7q(dGUHbkg`CU#S1 zCujDv^oej5S+yE?%K P00000NkvXXu0mjfO}v2P literal 0 HcmV?d00001 diff --git a/cliente/led.py b/cliente/led.py new file mode 100644 index 0000000..6bc338a --- /dev/null +++ b/cliente/led.py @@ -0,0 +1,55 @@ +#!/usr/bin/env python +# -*- coding: iso-8859-1 -*- +# vim: set expandtab tabstop=4 shiftwidth=4 : +#---------------------------------------------------------------------------- +# Etherled +#---------------------------------------------------------------------------- +# This file is part of etherled. +# +# etherled 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 2 of the License, or (at your option) +# any later version. +# +# etherled 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 etherled; if not, write to the Free Software Foundation, Inc., 59 +# Temple Place, Suite 330, Boston, MA 02111-1307 USA +#---------------------------------------------------------------------------- +# Creado: vie oct 27 23:36:22 ART 2005 +# Autores: Leandro Lucarella +#---------------------------------------------------------------------------- + +import gtk + +class Led(gtk.DrawingArea): + def __init__(self, prendido=False, file_on='led-on.png', + file_off='led-off.png', width = 24, height = 24): + self.prendido = prendido + self.width = width + self.height = height + self.pixbuf_on = gtk.gdk.pixbuf_new_from_file(file_on) + self.pixbuf_off = gtk.gdk.pixbuf_new_from_file(file_off) + gtk.DrawingArea.__init__(self) + self.set_size_request(width, height) + self.add_events(gtk.gdk.BUTTON_PRESS_MASK) + self.connect("expose-event", self.on_expose_event) + self.connect("button-press-event", self.on_button_press_event) + + def on_expose_event(self, widget, event): + gc = self.style.fg_gc[gtk.STATE_NORMAL] + if self.prendido: + pixbuf = self.pixbuf_on + else: + pixbuf = self.pixbuf_off + self.window.draw_pixbuf(gc, pixbuf, 0, 0, 0, 0) + return False + + def on_button_press_event(self, widget, event): + self.prendido = not self.prendido + self.queue_draw() + diff --git a/cliente/simplegladeapp.py b/cliente/simplegladeapp.py new file mode 100644 index 0000000..90c598c --- /dev/null +++ b/cliente/simplegladeapp.py @@ -0,0 +1,341 @@ +""" + SimpleGladeApp.py + Module that provides an object oriented abstraction to pygtk and libglade. + Copyright (C) 2004 Sandino Flores Moreno +""" + +# This library is free software; you can redistribute it and/or +# modify it under the terms of the GNU Lesser General Public +# License as published by the Free Software Foundation; either +# version 2.1 of the License, or (at your option) any later version. +# +# This library 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 +# Lesser General Public License for more details. +# +# You should have received a copy of the GNU Lesser General Public +# License along with this library; if not, write to the Free Software +# Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 +# USA + +import os +import sys +import re + +import tokenize +import gtk +import gtk.glade +import weakref +import inspect + +__version__ = "1.0" +__author__ = 'Sandino "tigrux" Flores-Moreno' + +def bindtextdomain(app_name, locale_dir=None): + """ + Bind the domain represented by app_name to the locale directory locale_dir. + It has the effect of loading translations, enabling applications for different + languages. + + app_name: + a domain to look for translations, tipically the name of an application. + + locale_dir: + a directory with locales like locale_dir/lang_isocode/LC_MESSAGES/app_name.mo + If omitted or None, then the current binding for app_name is used. + """ + try: + import locale + import gettext + locale.setlocale(locale.LC_ALL, "") + gtk.glade.bindtextdomain(app_name, locale_dir) + gettext.install(app_name, locale_dir, unicode=1) + except (IOError,locale.Error), e: + print "Warning", app_name, e + __builtins__.__dict__["_"] = lambda x : x + + +class SimpleGladeApp: + + def __init__(self, path, root=None, domain=None, **kwargs): + """ + Load a glade file specified by glade_filename, using root as + root widget and domain as the domain for translations. + + If it receives extra named arguments (argname=value), then they are used + as attributes of the instance. + + path: + path to a glade filename. + If glade_filename cannot be found, then it will be searched in the + same directory of the program (sys.argv[0]) + + root: + the name of the widget that is the root of the user interface, + usually a window or dialog (a top level widget). + If None or ommited, the full user interface is loaded. + + domain: + A domain to use for loading translations. + If None or ommited, no translation is loaded. + + **kwargs: + a dictionary representing the named extra arguments. + It is useful to set attributes of new instances, for example: + glade_app = SimpleGladeApp("ui.glade", foo="some value", bar="another value") + sets two attributes (foo and bar) to glade_app. + """ + if os.path.isfile(path): + self.glade_path = path + else: + glade_dir = os.path.dirname( sys.argv[0] ) + self.glade_path = os.path.join(glade_dir, path) + for key, value in kwargs.items(): + try: + setattr(self, key, weakref.proxy(value) ) + except TypeError: + setattr(self, key, value) + self.glade = None + self.install_custom_handler(self.custom_handler) + self.glade = self.create_glade(self.glade_path, root, domain) + if root: + self.main_widget = self.get_widget(root) + else: + self.main_widget = None + self.normalize_names() + self.add_callbacks(self) + self.new() + + def __repr__(self): + class_name = self.__class__.__name__ + if self.main_widget: + root = gtk.Widget.get_name(self.main_widget) + repr = '%s(path="%s", root="%s")' % (class_name, self.glade_path, root) + else: + repr = '%s(path="%s")' % (class_name, self.glade_path) + return repr + + def new(self): + """ + Method called when the user interface is loaded and ready to be used. + At this moment, the widgets are loaded and can be refered as self.widget_name + """ + pass + + def add_callbacks(self, callbacks_proxy): + """ + It uses the methods of callbacks_proxy as callbacks. + The callbacks are specified by using: + Properties window -> Signals tab + in glade-2 (or any other gui designer like gazpacho). + + Methods of classes inheriting from SimpleGladeApp are used as + callbacks automatically. + + callbacks_proxy: + an instance with methods as code of callbacks. + It means it has methods like on_button1_clicked, on_entry1_activate, etc. + """ + self.glade.signal_autoconnect(callbacks_proxy) + + def normalize_names(self): + """ + It is internally used to normalize the name of the widgets. + It means a widget named foo:vbox-dialog in glade + is refered self.vbox_dialog in the code. + + It also sets a data "prefixes" with the list of + prefixes a widget has for each widget. + """ + for widget in self.get_widgets(): + widget_name = gtk.Widget.get_name(widget) + prefixes_name_l = widget_name.split(":") + prefixes = prefixes_name_l[ : -1] + widget_api_name = prefixes_name_l[-1] + widget_api_name = "_".join( re.findall(tokenize.Name, widget_api_name) ) + gtk.Widget.set_name(widget, widget_api_name) + if hasattr(self, widget_api_name): + raise AttributeError("instance %s already has an attribute %s" % (self,widget_api_name)) + else: + setattr(self, widget_api_name, widget) + if prefixes: + gtk.Widget.set_data(widget, "prefixes", prefixes) + + def add_prefix_actions(self, prefix_actions_proxy): + """ + By using a gui designer (glade-2, gazpacho, etc) + widgets can have a prefix in theirs names + like foo:entry1 or foo:label3 + It means entry1 and label3 has a prefix action named foo. + + Then, prefix_actions_proxy must have a method named prefix_foo which + is called everytime a widget with prefix foo is found, using the found widget + as argument. + + prefix_actions_proxy: + An instance with methods as prefix actions. + It means it has methods like prefix_foo, prefix_bar, etc. + """ + prefix_s = "prefix_" + prefix_pos = len(prefix_s) + + is_method = lambda t : callable( t[1] ) + is_prefix_action = lambda t : t[0].startswith(prefix_s) + drop_prefix = lambda (k,w): (k[prefix_pos:],w) + + members_t = inspect.getmembers(prefix_actions_proxy) + methods_t = filter(is_method, members_t) + prefix_actions_t = filter(is_prefix_action, methods_t) + prefix_actions_d = dict( map(drop_prefix, prefix_actions_t) ) + + for widget in self.get_widgets(): + prefixes = gtk.Widget.get_data(widget, "prefixes") + if prefixes: + for prefix in prefixes: + if prefix in prefix_actions_d: + prefix_action = prefix_actions_d[prefix] + prefix_action(widget) + + def custom_handler(self, + glade, function_name, widget_name, + str1, str2, int1, int2): + """ + Generic handler for creating custom widgets, internally used to + enable custom widgets (custom widgets of glade). + + The custom widgets have a creation function specified in design time. + Those creation functions are always called with str1,str2,int1,int2 as + arguments, that are values specified in design time. + + Methods of classes inheriting from SimpleGladeApp are used as + creation functions automatically. + + If a custom widget has create_foo as creation function, then the + method named create_foo is called with str1,str2,int1,int2 as arguments. + """ + try: + handler = getattr(self, function_name) + return handler(str1, str2, int1, int2) + except AttributeError: + return None + + def gtk_widget_show(self, widget, *args): + """ + Predefined callback. + The widget is showed. + Equivalent to widget.show() + """ + widget.show() + + def gtk_widget_hide(self, widget, *args): + """ + Predefined callback. + The widget is hidden. + Equivalent to widget.hide() + """ + widget.hide() + + def gtk_widget_grab_focus(self, widget, *args): + """ + Predefined callback. + The widget grabs the focus. + Equivalent to widget.grab_focus() + """ + widget.grab_focus() + + def gtk_widget_destroy(self, widget, *args): + """ + Predefined callback. + The widget is destroyed. + Equivalent to widget.destroy() + """ + widget.destroy() + + def gtk_window_activate_default(self, window, *args): + """ + Predefined callback. + The default widget of the window is activated. + Equivalent to window.activate_default() + """ + widget.activate_default() + + def gtk_true(self, *args): + """ + Predefined callback. + Equivalent to return True in a callback. + Useful for stopping propagation of signals. + """ + return True + + def gtk_false(self, *args): + """ + Predefined callback. + Equivalent to return False in a callback. + """ + return False + + def gtk_main_quit(self, *args): + """ + Predefined callback. + Equivalent to self.quit() + """ + self.quit() + + def main(self): + """ + Starts the main loop of processing events. + The default implementation calls gtk.main() + + Useful for applications that needs a non gtk main loop. + For example, applications based on gstreamer needs to override + this method with gst.main() + + Do not directly call this method in your programs. + Use the method run() instead. + """ + gtk.main() + + def quit(self): + """ + Quit processing events. + The default implementation calls gtk.main_quit() + + Useful for applications that needs a non gtk main loop. + For example, applications based on gstreamer needs to override + this method with gst.main_quit() + """ + gtk.main_quit() + + def run(self): + """ + Starts the main loop of processing events checking for Control-C. + + The default implementation checks wheter a Control-C is pressed, + then calls on_keyboard_interrupt(). + + Use this method for starting programs. + """ + try: + self.main() + except KeyboardInterrupt: + self.on_keyboard_interrupt() + + def on_keyboard_interrupt(self): + """ + This method is called by the default implementation of run() + after a program is finished by pressing Control-C. + """ + pass + + def install_custom_handler(self, custom_handler): + gtk.glade.set_custom_handler(custom_handler) + + def create_glade(self, glade_path, root, domain): + return gtk.glade.XML(self.glade_path, root, domain) + + def get_widget(self, widget_name): + return self.glade.get_widget(widget_name) + + def get_widgets(self): + return self.glade.get_widget_prefix("") diff --git a/cliente/svg/led-off.svg b/cliente/svg/led-off.svg new file mode 100644 index 0000000..2c4c69b --- /dev/null +++ b/cliente/svg/led-off.svg @@ -0,0 +1,317 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + image/svg+xml + + + + + + + + diff --git a/cliente/svg/led-on.svg b/cliente/svg/led-on.svg new file mode 100644 index 0000000..728862a --- /dev/null +++ b/cliente/svg/led-on.svg @@ -0,0 +1,326 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + image/svg+xml + + + + + + + + + + diff --git a/cliente/svg/leds.svg b/cliente/svg/leds.svg new file mode 100644 index 0000000..35c8f7e --- /dev/null +++ b/cliente/svg/leds.svg @@ -0,0 +1,361 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + image/svg+xml + + + + + + + + + + + + + + + -- 2.43.0