got the wemos udp strip working
This commit is contained in:
		
							parent
							
								
									e738fde787
								
							
						
					
					
						commit
						1cf70b19a9
					
				
							
								
								
									
										3
									
								
								.gitignore
									
									
									
									
										vendored
									
									
								
							
							
						
						
									
										3
									
								
								.gitignore
									
									
									
									
										vendored
									
									
								
							| @ -12,6 +12,9 @@ | ||||
| node_modules | ||||
| clients/controller-html/old | ||||
| clients/strip-wemosd1-websocket/settings.h | ||||
| clients/strip-wemosd1-websocket/websocket.old | ||||
| 
 | ||||
| .gitignore | ||||
| 
 | ||||
| .directory | ||||
| 
 | ||||
|  | ||||
| @ -3,5 +3,15 @@ | ||||
| #define LED_TYPE      WS2811 | ||||
| #define COLOR_ORDER   GRB | ||||
| 
 | ||||
| #define MILLI_AMPS         2000 // IMPORTANT: set the max milli-Amps of your power supply (4A = 4000mA)
 | ||||
| #define FRAMES_PER_SECOND  120  // here you can control the speed. With the Access Point / Web Server the animations run a bit slower.
 | ||||
| 
 | ||||
| const char* ssid     = "none"; | ||||
| const char* password = "none"; | ||||
| 
 | ||||
| //RecipientIP is overridden after wl connect, getting localip and set last octet to 255 (Broadcast)
 | ||||
| //this will only work for /24 networks. If you want to set an other BC or a sinle IP adress set overriderecipient to true
 | ||||
| bool overriderecipient = false; | ||||
| IPAddress RecipientIP = IPAddress(0, 0, 0, 0); | ||||
| 
 | ||||
| unsigned int RecipientPort = 8002; | ||||
|  | ||||
							
								
								
									
										92
									
								
								clients/strip-wemosd1-websocket/strip-wemosd1-websocket.ino
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										92
									
								
								clients/strip-wemosd1-websocket/strip-wemosd1-websocket.ino
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1,92 @@ | ||||
| 
 | ||||
| //#define FASTLED_ALLOW_INTERRUPTS 0
 | ||||
| //#define FASTLED_INTERRUPT_RETRY_COUNT 0
 | ||||
| //#define WEBSOCKETS_NETWORK_TYPE NETWORK_ESP8266_ASYNC
 | ||||
| #define UDP_TX_PACKET_MAX_SIZE = 50*12+1; | ||||
| 
 | ||||
| #include <FastLED.h> | ||||
| #include <ESP8266WiFi.h> | ||||
| #include <WiFiUdp.h> | ||||
| 
 | ||||
| #include "settings.h" | ||||
| 
 | ||||
| CRGB leds[NUM_LEDS]; | ||||
| 
 | ||||
| WiFiClient client; | ||||
| 
 | ||||
| // buffers for receiving and sending data
 | ||||
| char packetBuffer[UDP_TX_PACKET_MAX_SIZE]; | ||||
| WiFiUDP Udp; | ||||
| 
 | ||||
| int c = -1; | ||||
| 
 | ||||
| void setup() { | ||||
|   Serial.begin(115200); | ||||
|   FastLED.addLeds<LED_TYPE, DATA_PIN, COLOR_ORDER>(leds, NUM_LEDS);         // for WS2812 (Neopixel)
 | ||||
|   //FastLED.addLeds<LED_TYPE,DATA_PIN,CLK_PIN,COLOR_ORDER>(leds, NUM_LEDS); // for APA102 (Dotstar)
 | ||||
|   FastLED.setDither(false); | ||||
|   FastLED.setCorrection(TypicalLEDStrip); | ||||
|   FastLED.setBrightness(255); | ||||
|   FastLED.setMaxPowerInVoltsAndMilliamps(5, MILLI_AMPS); | ||||
|   fill_solid(leds, NUM_LEDS, CRGB::Black); | ||||
|   wdt_enable(WDTO_4S);   // Watchdog auf 4 s stellen
 | ||||
|   FastLED.show(); | ||||
|   Udp.begin(random(5000,5500)); | ||||
|    | ||||
| } | ||||
|   | ||||
| void loop() { | ||||
|   EVERY_N_MILLISECONDS( 250 ) { | ||||
|     sendUDP(String("s:ping")); | ||||
|   } | ||||
|   EVERY_N_MILLISECONDS( 15 ) {  | ||||
|     wdt_reset(); | ||||
|     if (WiFi.status() != WL_CONNECTED) { // FIX FOR USING 2.3.0 CORE (only .begin if not connected)
 | ||||
|       WiFi.begin(ssid, password); // connect to the network
 | ||||
|       while (WiFi.status() != WL_CONNECTED) { | ||||
|         delay(500); | ||||
|       } | ||||
|       if (WiFi.status() == WL_CONNECTED) { | ||||
|         // Start UDP
 | ||||
|         Serial.println("start udp"); | ||||
|         if(!overriderecipient){ | ||||
|           RecipientIP = WiFi.localIP(); | ||||
|           RecipientIP[3] = 255; | ||||
|         } | ||||
|       } | ||||
|     } | ||||
|      | ||||
|   } | ||||
|   int packetSize = Udp.parsePacket(); | ||||
|   if(packetSize) | ||||
|   { | ||||
|     // read the packet into packetBufffer
 | ||||
|     Udp.read(packetBuffer,UDP_TX_PACKET_MAX_SIZE); | ||||
|     if(packetBuffer[0] == 's' && packetBuffer[1] == 'r'){ | ||||
|       sendUDP("r:1:"+StripName+":"+String(NUM_LEDS)+":0"); | ||||
|     } | ||||
|     if(packetBuffer[0] == 's' && packetBuffer[1] == 'u'){ | ||||
|         FastLED.show();  | ||||
|     } | ||||
|     if(packetBuffer[0] == 'd'){ | ||||
|       c=1; | ||||
|       while(c<strlen(packetBuffer)){ | ||||
|         leds[getledvalue(c)] = CRGB( getledvalue(c+3), getledvalue(c+6), getledvalue(c+9)); | ||||
|         c=c+12; | ||||
|       } | ||||
|     } | ||||
|   } | ||||
| } | ||||
| 
 | ||||
| int getledvalue(int startpoint){ | ||||
|   return 100 * int(packetBuffer[startpoint] - '0') + 10 * int(packetBuffer[startpoint+1] - '0') + int(packetBuffer[startpoint+2] - '0'); | ||||
| } | ||||
|   | ||||
| // Function to send UDP packets
 | ||||
| void sendUDP(String text) | ||||
| { | ||||
|   //Serial.println(text);
 | ||||
|   Udp.beginPacket(RecipientIP, RecipientPort); | ||||
|   Udp.print(text); | ||||
|   Udp.endPacket(); | ||||
| } | ||||
| @ -1,107 +0,0 @@ | ||||
| #include settings.h | ||||
| #define FASTLED_ALLOW_INTERRUPTS 0 | ||||
| //#define FASTLED_INTERRUPT_RETRY_COUNT 0
 | ||||
| #define WEBSOCKETS_NETWORK_TYPE NETWORK_ESP8266_ASYNC | ||||
| 
 | ||||
| #include <FastLED.h> | ||||
| #include <ESP8266WiFi.h> | ||||
| #include <ArduinoJson.h> | ||||
| #include <WiFiUdp.h> | ||||
| 
 | ||||
| #define MILLI_AMPS         2000 // IMPORTANT: set the max milli-Amps of your power supply (4A = 4000mA)
 | ||||
| #define FRAMES_PER_SECOND  120  // here you can control the speed. With the Access Point / Web Server the animations run a bit slower.
 | ||||
| 
 | ||||
| 
 | ||||
| CRGB leds[NUM_LEDS]; | ||||
|   | ||||
| 
 | ||||
| WiFiUDP Udp; | ||||
| unsigned int localUdpPort = 4210;  // local port to listen on
 | ||||
| char incomingPacket[255];  // buffer for incoming packets
 | ||||
| 
 | ||||
| WiFiClient client; | ||||
| 
 | ||||
| void setup() { | ||||
|   //Serial.begin(115200);
 | ||||
|   FastLED.addLeds<LED_TYPE, DATA_PIN, COLOR_ORDER>(leds, NUM_LEDS);         // for WS2812 (Neopixel)
 | ||||
|   //FastLED.addLeds<LED_TYPE,DATA_PIN,CLK_PIN,COLOR_ORDER>(leds, NUM_LEDS); // for APA102 (Dotstar)
 | ||||
|   FastLED.setDither(false); | ||||
|   FastLED.setCorrection(TypicalLEDStrip); | ||||
|   FastLED.setBrightness(255); | ||||
|   FastLED.setMaxPowerInVoltsAndMilliamps(5, MILLI_AMPS); | ||||
|   fill_solid(leds, NUM_LEDS, CRGB::Black); | ||||
|   FastLED.show(); | ||||
|    | ||||
|   Udp.begin(localUdpPort); | ||||
| } | ||||
|   | ||||
| void loop() { | ||||
|   EVERY_N_MILLISECONDS( 15 ) {  | ||||
|     if (WiFi.status() != WL_CONNECTED) { // FIX FOR USING 2.3.0 CORE (only .begin if not connected)
 | ||||
|       WiFi.begin(ssid, password); // connect to the network
 | ||||
|       while (WiFi.status() != WL_CONNECTED) { | ||||
|         delay(500); | ||||
|       } | ||||
|     } | ||||
| 
 | ||||
|     //Kompletter Strip in einer farbe
 | ||||
|     //for future use, when ws1812b strips are usable.
 | ||||
|     //int root_length = root.size(); 
 | ||||
|     //for(int i=0; i<root_length;i++){
 | ||||
|     //  leds[i].r = root["data"][String(i)]["red"]; // 0
 | ||||
|     //  leds[i].g = root["data"][String(i)]["green"]; // 0
 | ||||
|     //  leds[i].b = root["data"][String(i)]["blue"]; // 0
 | ||||
|     //}
 | ||||
|      | ||||
|   } | ||||
| 
 | ||||
|   int packetSize = Udp.parsePacket(); | ||||
|   if (packetSize){ | ||||
|     int len = Udp.read(incomingPacket, 255); | ||||
|     if (len > 0) | ||||
|     { | ||||
|       incomingPacket[len] = 0; | ||||
|     } | ||||
|     Serial.printf("UDP packet contents: %s\n", incomingPacket); | ||||
| 
 | ||||
|     // send back a reply, to the IP address and port we got the packet from
 | ||||
|     Udp.beginPacket(Udp.remoteIP(), Udp.remotePort()); | ||||
|     Udp.write(replyPacket); | ||||
|     Udp.endPacket(); | ||||
|   } | ||||
| 
 | ||||
|   fill_solid( leds, NUM_LEDS, CRGB( red, green, blue) ); | ||||
|   FastLED.show(); | ||||
|    | ||||
|    | ||||
| } | ||||
| void webSocketEvent(WStype_t type, uint8_t * payload, size_t length) { | ||||
| 
 | ||||
|   switch(type) { | ||||
|     case WStype_DISCONNECTED: | ||||
|       //USE_SERIAL.printf("[WSc] Disconnected!\n");
 | ||||
|       break; | ||||
|     case WStype_CONNECTED: { | ||||
|       //USE_SERIAL.printf("[WSc] Connected to url: %s\n", payload);
 | ||||
| 
 | ||||
|       // send message to server when Connected
 | ||||
|       webSocket.sendTXT("{\"register_client_type\":1,\"client_name\":\"wemosd1\"}"); | ||||
|     } | ||||
|       break; | ||||
|     case WStype_TEXT: | ||||
|       //USE_SERIAL.printf("[WSc] get text: %s\n", payload);
 | ||||
|       JsonObject& root = jsonBuffer.parseObject(payload); | ||||
|       if (!root.success()) { | ||||
|         jsonBuffer.clear(); | ||||
|       }else{ | ||||
|         red = root["data"]["0"]["red"]; | ||||
|         green = root["data"]["0"]["green"]; | ||||
|         blue = root["data"]["0"]["blue"]; | ||||
|       } | ||||
|       // send message to server
 | ||||
|       // webSocket.sendTXT("message here");
 | ||||
|       break; | ||||
|   } | ||||
| 
 | ||||
| } | ||||
|   | ||||
| @ -7,9 +7,10 @@ services: | ||||
|     image: ${USER}/ledserver-controller-html | ||||
|   ledserver: | ||||
|     build: server | ||||
|     ports: | ||||
|       - "8001:8001" | ||||
|       - "8002:8002/udp" | ||||
|     #ports: | ||||
|     #  - "8001:8001" | ||||
|     #  - "8002:8002/udp" | ||||
|     network_mode: host #docker is changing the source port of the udp packages on the bridge | ||||
|     image: ${USER}/ledserver | ||||
|     user: "${UID}:${GID}" | ||||
|     #volumes:  | ||||
|  | ||||
| @ -49,7 +49,7 @@ class HTTPWebSocketsHandler(WebSocket): | ||||
|             # Client Registration on the Websocket Server | ||||
|             # maybe it would be better to use a websocket server thread for each client type, | ||||
|             # can be done in future if there is too much latency | ||||
|             if "register_client_type" in data: | ||||
|             if "register_client_type" in self.data: | ||||
|                 # the controler type, add handler on RGBStripContoller and send the current state of the controller | ||||
|                 if int(data['register_client_type']) is CLIENT_TYPE_CONTROLLER: | ||||
|                     self.client_type = CLIENT_TYPE_CONTROLLER | ||||
| @ -60,10 +60,20 @@ class HTTPWebSocketsHandler(WebSocket): | ||||
|                 # register new Stripes | ||||
|                 elif int(data['register_client_type']) is CLIENT_TYPE_STRIPE and "client_name" in data: | ||||
|                     self.client_type = CLIENT_TYPE_STRIPE | ||||
|                     ledcount=1 | ||||
|                     if "led_count" in data: | ||||
|                         ledcount = int(data["led_count"]) | ||||
|                     self.nojson=0 | ||||
|                     if "nojson" in data: | ||||
|                         self.nojson = int(data["nojson"]) | ||||
|                     self.nosend=0 | ||||
|                     if "nosend" in data: | ||||
|                         self.nosend = int(data["nosend"]) | ||||
|                      | ||||
|                     # registers the strip with websocket object and name. the onRGBStripValueUpdate(rgbStrip) is called by  | ||||
|                     # by the rgbStrip when an effectThread updates it | ||||
|                     # the self.rgbStrip variable is used to unregister the strip only | ||||
|                     self.rgbStrip = self.rgbStripController.registerRGBStrip(data["client_name"],self.onRGBStripValueUpdate) | ||||
|                     self.rgbStrip = self.rgbStripController.registerRGBStrip(data["client_name"],self.onRGBStripValueUpdate,ledcount) | ||||
|                 # register new Audio Recorders | ||||
|                 elif int(data['register_client_type']) is CLIENT_TYPE_RECORDER: | ||||
|                     self.client_type = CLIENT_TYPE_RECORDER | ||||
| @ -79,6 +89,11 @@ class HTTPWebSocketsHandler(WebSocket): | ||||
|                 return | ||||
|             # the stripe should usualy not send any data, i do not know why it should... | ||||
|             elif self.client_type is CLIENT_TYPE_STRIPE: | ||||
|                 if self.nosend == 1: | ||||
|                     respdata = "d" | ||||
|                     for i in range(self.rgbStrip.STRIP_LENGHT): | ||||
|                         respdata += ":" + str(i) + ":"+str(self.rgbStrip.red[i])+":"+str(self.rgbStrip.green[i])+":"+str(self.rgbStrip.blue[i]) | ||||
|                     self.sendMessage(respdata) | ||||
|                 return | ||||
|             # audio recorder responses are handled by the effectControllerJsonHandler | ||||
|             elif self.client_type is CLIENT_TYPE_RECORDER: | ||||
| @ -124,8 +139,15 @@ class HTTPWebSocketsHandler(WebSocket): | ||||
| 
 | ||||
|     # when a rgbStrip value is changed, send json data to client | ||||
|     def onRGBStripValueUpdate(self,rgbStrip): | ||||
|         self.sendMessage( | ||||
|             json.dumps({ | ||||
|                 'data': rgbStripControllerJsonHelper.getRGBData(rgbStrip) | ||||
|             }) | ||||
|         ) | ||||
|         if self.nosend == 0: | ||||
|             if self.nojson == 0: | ||||
|                 self.sendMessage( | ||||
|                     json.dumps({ | ||||
|                         'data': rgbStripControllerJsonHelper.getRGBData(rgbStrip) | ||||
|                     }) | ||||
|                 ) | ||||
|             if self.nojson == 1: | ||||
|                 respdata = "d" | ||||
|                 for i in range(rgbStrip.STRIP_LENGHT): | ||||
|                     respdata += ":" + str(i) + ":"+str(rgbStrip.red[i])+":"+str(rgbStrip.green[i])+":"+str(rgbStrip.blue[i]) | ||||
|                 self.sendMessage(respdata) | ||||
|  | ||||
| @ -41,6 +41,7 @@ class ThreadedUDPServer(threading.Thread): | ||||
|             while not self.stopped: | ||||
|                 for key in list(UDPClients.keys()): | ||||
|                     if UDPClients[key].lastping + 2 < time(): | ||||
|                         print("ping missing, last ping: ",UDPClients[key].lastping," now is: ",time()) | ||||
|                         UDPClients[key].handleClose() | ||||
|                         del UDPClients[key] | ||||
|                 sleep(0.5) | ||||
| @ -75,6 +76,7 @@ class UDPClient(): | ||||
|         self.effectController = effectController | ||||
|         self.rgbStripController = rgbStripController | ||||
|         self.sendToClientLock = False | ||||
|         self.registered = False | ||||
|         self.lastping = time() | ||||
| 
 | ||||
|     def handle(self, request): | ||||
| @ -85,27 +87,57 @@ class UDPClient(): | ||||
|         #print(time(),"clientdata -> ", clientdata) | ||||
|         # socket.sendto(bytes("pong","utf-8"),self.client_address) | ||||
| 
 | ||||
|         # Client Types: | ||||
|         # CLIENT_TYPE_CONTROLLER = 0 | ||||
|         # CLIENT_TYPE_STRIPE = 1 | ||||
|         # CLIENT_TYPE_RECORDER = 2 | ||||
| 
 | ||||
|         # UDP Registrierung Stripe: (nosend definiert, dass der stripe sich seine daten selbst anfordert) | ||||
|         # r:1:NUM_LEDS[:nosend] | ||||
|         # UDP Stripe sendet ping, woran festgemacht wird, ob er nocht lebt | ||||
|         # s:ping | ||||
|         # UDP | ||||
| 
 | ||||
|         try: | ||||
|             data = clientdata.split(':') | ||||
|             # print(data) | ||||
|             print(data) | ||||
|             # r:1:srg strip name | ||||
|             if data[0] == "r" and int(data[1]) == CLIENT_TYPE_STRIPE and data[2] != None: | ||||
|             if data[0] == "r" and int(data[1]) == CLIENT_TYPE_STRIPE and data[2] != None and self.registered is False: | ||||
|                 self.registered = True | ||||
|                 self.client_type = CLIENT_TYPE_STRIPE | ||||
|                 # registers the strip with websocket object and name. the onRGBStripValueUpdate(rgbStrip) is called by | ||||
|                 # by the rgbStrip when an effectThread updates it | ||||
|                 # the self.rgbStrip variable is used to unregister the strip only | ||||
| 
 | ||||
|                 ledcount = 1 | ||||
|                 if data[3] != None: | ||||
|                     ledcount = int(data[3]) | ||||
| 
 | ||||
|                 self.nosend = 0 | ||||
|                 if data[4] != None: | ||||
|                     self.nosend = int(data[4]) | ||||
| 
 | ||||
|                 self.rgbStrip = self.rgbStripController.registerRGBStrip( | ||||
|                     data[2], self.onRGBStripValueUpdate) | ||||
|                     data[2], self.onRGBStripValueUpdate, ledcount) | ||||
|             # s:ping | ||||
|             if data[0] == "s" and data[1] == "ping": | ||||
|                 # if we got a ping and the client has no client type defined, send status unregistered, so the client knows that he has to register | ||||
|                 if self.client_type is None and self.socket is not None: | ||||
|                     self.sendToClient('s:unregistered') | ||||
|                     self.sendToClient('sr') | ||||
|                 self.lastping = time() | ||||
|             if data[0] == "u" and self.client_type == CLIENT_TYPE_STRIPE: | ||||
|                 led = int(data[1]) | ||||
|                 self.sendToClient('d:'+str(led)+':'+str(self.rgbStrip.red[led])+':'+str( | ||||
|                     self.rgbStrip.green[led])+':'+str(self.rgbStrip.blue[led])+'') | ||||
|                 #respdata = "d" | ||||
|                 #for i in range(self.rgbStrip.STRIP_LENGHT): | ||||
|                 #    respdata += ":" + str(i) + ":"+str(self.rgbStrip.red[i])+":"+str(self.rgbStrip.green[i])+":"+str(self.rgbStrip.blue[i]) | ||||
|                 #self.sendToClient(respdata) | ||||
|                 for i in range(self.rgbStrip.STRIP_LENGHT): | ||||
|                     self.sendToClient("d"+ | ||||
|                         "{0:03}".format(i) + | ||||
|                         "{0:03}".format(self.rgbStrip.red[i]) +  | ||||
|                         "{0:03}".format(self.rgbStrip.green[i]) + | ||||
|                         "{0:03}".format(self.rgbStrip.blue[i]) | ||||
|                     ) | ||||
|                 #self.sendToClient('su') | ||||
|         except Exception as e: | ||||
|             print(e, traceback.format_exc()) | ||||
| 
 | ||||
| @ -117,21 +149,29 @@ class UDPClient(): | ||||
|     def handleClose(self): | ||||
|         if self.client_type is CLIENT_TYPE_STRIPE: | ||||
|             self.rgbStripController.unregisterRGBStrip(self.rgbStrip) | ||||
|         #print(self.client_address, 'closed') | ||||
|         print(self.client_address, 'closed') | ||||
| 
 | ||||
|     # when a rgbStrip value is changed, send not json data but a formated string to client | ||||
|     # d:[id off the LED, always 0 on RGB strips]:[red value 0-255]:[green value 0-255]:[blue value 0-255] | ||||
|     def onRGBStripValueUpdate(self, rgbStrip, led=0): | ||||
|         return  # we send the update as requested, to prevent flooding the module | ||||
|         #self.sendToClient('d:'+str(led)+':'+str(rgbStrip.red[led])+':'+str( | ||||
|         #    rgbStrip.green[led])+':'+str(rgbStrip.blue[led])+'') | ||||
|     def onRGBStripValueUpdate(self, rgbStrip): | ||||
|         if self.nosend == 0: | ||||
|             respdata = "d" | ||||
|             tmplen = 0 | ||||
|             for i in range(rgbStrip.STRIP_LENGHT): | ||||
|                 if tmplen is 49: | ||||
|                     self.sendToClient(respdata) | ||||
|                     respdata = "d" | ||||
|                     tmplen = 0 | ||||
|                 respdata = respdata + "{0:03}".format(i) + "{0:03}".format(rgbStrip.red[i]) + "{0:03}".format(rgbStrip.green[i]) + "{0:03}".format(rgbStrip.blue[i]) | ||||
|                 #self.sendToClient("d"+"{0:03}".format(i) + "{0:03}".format(rgbStrip.red[i]) + "{0:03}".format(rgbStrip.green[i]) + "{0:03}".format(rgbStrip.blue[i])) | ||||
|                 #self.sendToClient("d:"+ str(i) + ":"+str(rgbStrip.red[i])+":"+str(rgbStrip.green[i])+":"+str(rgbStrip.blue[i])) | ||||
|                 #respdata += ":" + str(i) + ":"+str(rgbStrip.red[i])+":"+str(rgbStrip.green[i])+":"+str(rgbStrip.blue[i]) | ||||
|                 tmplen = tmplen+1 | ||||
|             self.sendToClient(respdata) | ||||
|             self.sendToClient('su') | ||||
| 
 | ||||
|     def sendToClient(self, message): | ||||
|         while self.sendToClientLock is True: | ||||
|             sleep(1) | ||||
|         self.sendToClientLock = True | ||||
|         if self.socket is not None: | ||||
|             self.socket.sendto( | ||||
|                 message.encode(), self.client_address | ||||
|             ) | ||||
|         self.sendToClientLock = False | ||||
|         print("SendToClient:",self.client_address, message) | ||||
|         self.socket.sendto( | ||||
|             message.encode(), self.client_address | ||||
|         ) | ||||
|  | ||||
| @ -76,7 +76,7 @@ class RGBStrip: | ||||
|             self.green[id] = int(green/100*brightness) | ||||
|             self.blue[id] = int(blue/100*brightness) | ||||
| 
 | ||||
|         self.onValuesUpdateHandler(self,id) | ||||
|         self.onValuesUpdateHandler(self) | ||||
|              | ||||
|      | ||||
|     def off(self): | ||||
|  | ||||
		Loading…
	
	
			
			x
			
			
		
	
		Reference in New Issue
	
	Block a user