class TimeBoxNotificationService(BaseNotificationService): """Implement the notification service for TimeBox.""" def __init__(self, mac, image_dir): from timebox import TimeBox self._mac = mac self._image_dir = image_dir self._timebox = TimeBox() self._timebox.connect(host=mac) if not os.path.isdir(image_dir): _LOGGER.error( "image_dir {0} does not exist, timebox will not work".format( image_dir)) from timeboximage import TimeBoxImage self._state_image = TimeBoxImage() self.display_image_file('ha-logo') def display_image_file(self, fn): image_data = self.load_image_file(fn) if image_data is not None: self.display_image(image_data) def display_image(self, image_data): if self.valid_image(image_data): from timeboximage import TimeBoxImage image = TimeBoxImage() image.image = image_data self._timebox.set_static_image(image) else: _LOGGER.error("Invalid image data received") def valid_color(self, color): """Verifies a color is valid (Array of three ints, range 0-15)""" valid = False if (isinstance(color, list) and len(color) == 3): valid = True for chan in color: valid = valid and (0 <= chan <= 15) if not valid: _LOGGER.warn("{0} was not a valid color".format(color)) return valid def valid_image(self, image): """Verifies an image array is valid. An image should consist of a 2D array, 11x11. Each array element is again an arry, containing a valid colour (see valid_color()).""" valid = False if (isinstance(image, list) and len(image) == 11): valid = True for row in image: if (isinstance(row, list) and len(row) == 11): for pixel in row: if not self.valid_color(pixel): valid = False break else: valid = False break if not valid: _LOGGER.error("Invalid image data received") return valid def load_image_file(self, image_file_name): """Loads image data from a file and returns it.""" fn = os.path.join(self._image_dir, "{0}.json".format(image_file_name)) try: fh = open(fn) except: _LOGGER.error("Unable to open {0}".format(fn)) return None try: image = json.load(fh) return image except Exception as e: _LOGGER.error( "{0} does not contain a valid image in JSON format".format(fn)) _LOGGER.error(e) return None def convert_color(self, color): """We expect all colors passed in to be in the range 0-15. But some parts of the timebox API expect 0-255. This function converts a passed in color array to something the API can work with. Does not do validation itself.""" return [color[0] * 16, color[1] * 16, color[2] * 16] def send_message(self, message="", **kwargs): if kwargs.get(ATTR_DATA) is None: _LOGGER.error("Service call needs a message type") return False data = kwargs.get(ATTR_DATA) mode = data.get(PARAM_MODE) # HA used to internally cast "off" to a boolean False. # Apparently it doesn't any more? if mode == False or mode == 'off': self.display_image_file('blank') elif mode == "clock": color = data.get(PARAM_COLOR) if self.valid_color(color): color = self.convert_color(color) else: color = [255, 255, 255] self._timebox.show_clock(color=color) elif mode == "temp": color = data.get(PARAM_COLOR) if self.valid_color(color): color = self.convert_color(color) else: color = [255, 255, 255] self._timebox.show_temperature(color=color) elif mode == "image": image_data = data.get(PARAM_IMAGE) self.display_image(image_data) elif mode == "image-file": image_filename = data.get(PARAM_FILE_NAME) self.display_image_file(image_filename) elif mode == "sync-clock": dt = datetime.datetime.now() head = [ 0x0A, 0x00, 0x18, dt.year % 100, int(dt.year / 100), dt.month, dt.day, dt.hour, dt.minute, dt.second ] self._timebox.send_payload(head) elif mode == "show-states": self._timebox.set_static_image(self._state_image) elif mode == "set-state": color = data.get(PARAM_COLOR) x = data.get(PARAM_X) y = data.get(PARAM_Y) show_state = data.get(PARAM_SHOW_STATE) self._state_image.put_pixel(x, y, color[0], color[1], color[2]) if show_state == True: self._timebox.set_static_image(self._state_image) elif mode == "set-state-lerp": x = data.get(PARAM_X) x = int(x) y = data.get(PARAM_Y) y = int(y) _LOGGER.info("x: " + str(x) + " y:" + str(y)) start_color = data.get(PARAM_START_COLOR) end_color = data.get(PARAM_END_COLOR) start_color = [int(x) for x in start_color] end_color = [int(x) for x in end_color] value = data.get(PARAM_VALUE) value = float(value) min_value = data.get(PARAM_MIN_VALUE) min_value = float(min_value) max_value = data.get(PARAM_MAX_VALUE) max_value = float(max_value) _LOGGER.debug("val: " + str(value) + " min:" + str(min_value) + " max:" + str(max_value)) _LOGGER.debug("start_col: " + str(start_color) + " end: " + str(end_color)) show_state = data.get(PARAM_SHOW_STATE) color = calculate_lerp_color(start_color, end_color, value, min_value, max_value) _LOGGER.info("calc_col: " + str(color)) self._state_image.put_pixel(x, y, color[0], color[1], color[2]) if show_state == True or show_state.lower() == "true": self._timebox.set_static_image(self._state_image) elif mode == "animation": # TODO pass else: _LOGGER.error( "Invalid mode '{0}', must be one of 'off', 'clock', 'temp', 'image', 'animation'" .format(mode)) return False return True
""" Test TimeBox interface""" #import sys import random from time import sleep from timeboximage import TimeBoxImage from timebox import TimeBox IMAGE = TimeBoxImage() TIMEBOX = TimeBox() # create some image TIMEBOX.connect() for i in range(1000): rand_r = random.randrange(0, 16) rand_g = random.randrange(0, 16) rand_b = random.randrange(0, 16) IMAGE.put_pixel(random.randrange(0, 11), random.randrange(0, 11), rand_r, rand_g, rand_b) TIMEBOX.set_static_image(IMAGE) sleep(1.0) TIMEBOX.close()
if self.pos_y > 10: self.vel_y = -self.vel_y - self._rand() self.pos_y = 20 - self.pos_y if self.pos_y < 0: self.vel_y = -self.vel_y + self._rand() self.pos_y = -self.pos_y def image_add(self, imag): """Add the ball to an existing timebox image.""" imag.put_pixel(round(self.pos_x), 10 - round(self.pos_y), self.col_r, self.col_g, self.col_b) return imag TIMEBOX = TimeBox() TIMEBOX.connect() BALLS = [Ball(0xf, 0x0, 0x0), Ball(0x0, 0xf, 0x0), Ball(0x0, 0x0, 0xf)] BALLS.extend([Ball(0xf, 0xf, 0x0), Ball(0x0, 0xf, 0xf), Ball(0xf, 0x0, 0xf)]) while True: IMAGE = TimeBoxImage() for b in BALLS: b.image_add(IMAGE) TIMEBOX.set_static_image(IMAGE) for b in BALLS: b.update(DELAY) sleep(DELAY) TIMEBOX.close()