diff --git a/README.md b/README.md index 662df38..4197efa 100644 --- a/README.md +++ b/README.md @@ -55,11 +55,16 @@ MariMapper support the following pre-made backends: - `fadecandy` - [`wled`](https://kno.wled.ge/) - [`fcmega`](https://github.com/TheMariday/FC-Mega) +- [`pixelblaze`](https://electromage.com/docs) - `custom` If you choose a pre-built backend, remember to install its dependencies using `pip install -r backends/fadecandy/requirements.txt` +When using Fadecandy, WLED, or Pixelblaze backends, pass the server IP or URI with the `--server` flag. + +Using Pixelblaze as a backend requires you to upload the [`marimapper.epe`](backends/pixelblaze/marimapper.epe) pattern to your pixelblaze before running Marimapper. + However, your LEDs are as unique as you are, so it's super simple to implemenet your own `custom` backend by filling in the blanks in [backends/custom/custom_backend.py](backends/custom/custom_backend.py): diff --git a/backends/pixelblaze/marimapper.epe b/backends/pixelblaze/marimapper.epe new file mode 100644 index 0000000..0085cea --- /dev/null +++ b/backends/pixelblaze/marimapper.epe @@ -0,0 +1,8 @@ +{ + "name": "marimapper", + "id": "GihLfEwy523cFgomM", + "sources": { + "main": "export var pixel_to_light = -1\nexport var turn_on=true\n\nexport function render(index) {\n if(index == pixel_to_light && turn_on){\n rgb(1,1,1)\n } else{\n rgb(0,0,0)\n }\n}" + }, + "preview": "/9j/4AAQSkZJRgABAQAAAQABAAD/2wBDAAMCAgMCAgMDAwMEAwMEBQgFBQQEBQoHBwYIDAoMDAsKCwsNDhIQDQ4RDgsLEBYQERMUFRUVDA8XGBYUGBIUFRT/2wBDAQMEBAUEBQkFBQkUDQsNFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBT/wAARCACWAGQDAREAAhEBAxEB/8QAHwAAAQUBAQEBAQEAAAAAAAAAAAECAwQFBgcICQoL/8QAtRAAAgEDAwIEAwUFBAQAAAF9AQIDAAQRBRIhMUEGE1FhByJxFDKBkaEII0KxwRVS0fAkM2JyggkKFhcYGRolJicoKSo0NTY3ODk6Q0RFRkdISUpTVFVWV1hZWmNkZWZnaGlqc3R1dnd4eXqDhIWGh4iJipKTlJWWl5iZmqKjpKWmp6ipqrKztLW2t7i5usLDxMXGx8jJytLT1NXW19jZ2uHi4+Tl5ufo6erx8vP09fb3+Pn6/8QAHwEAAwEBAQEBAQEBAQAAAAAAAAECAwQFBgcICQoL/8QAtREAAgECBAQDBAcFBAQAAQJ3AAECAxEEBSExBhJBUQdhcRMiMoEIFEKRobHBCSMzUvAVYnLRChYkNOEl8RcYGRomJygpKjU2Nzg5OkNERUZHSElKU1RVVldYWVpjZGVmZ2hpanN0dXZ3eHl6goOEhYaHiImKkpOUlZaXmJmaoqOkpaanqKmqsrO0tba3uLm6wsPExcbHyMnK0tPU1dbX2Nna4uPk5ebn6Onq8vP09fb3+Pn6/9oADAMBAAIRAxEAPwD8qqACgAoAKACgAoAKACgAoAKACgAoAKACgAoAKACgAoAKACgAoAKACgAoAKACgAoAKACgAoAKACgAoAKACgAoAKACgAoAKACgAoAKACgAoAKACgAoAKACgAoAKACgAoAKACgAoAKACgAoAKACgAoAKACgAoAKACgAoAKACgAoAKACgAoAKACgAoAKACgAoAKACgAoAKACgAoAKACgAoAKACgAoAKACgAoAKACgAoAKACgAoAKACgAoAKACgAoAKACgAoAKACgAoAKACgAoAKACgAoAKACgAoAKACgAoAKACgAoAKACgAoAKACgAoAKACgAoAKACgAoAKACgAoAKACgAoAKACgAoAKACgAoAKACgAoAKACgAoAKACgAoAKACgAoAKACgAoAKACgAoAKACgAoAKACgAoAKACgAoAKACgAoAKACgAoAKACgAoAKACgAoAKACgAoAKACgAoAKACgAoAKACgAoAKACgAoAKACgAoAKACgAoAKACgAoAKACgAoAKACgAoAKACgAoAKACgAoAKACgAoAKACgAoAKACgAoAKACgAoAKACgAoAKACgAoAKAP/Z" +} \ No newline at end of file diff --git a/backends/pixelblaze/marimapper.js b/backends/pixelblaze/marimapper.js new file mode 100644 index 0000000..a08b946 --- /dev/null +++ b/backends/pixelblaze/marimapper.js @@ -0,0 +1,10 @@ +export var pixel_to_light = -1 +export var turn_on=true + +export function render(index) { + if(index == pixel_to_light && turn_on){ + rgb(1,1,1) + } else{ + rgb(0,0,0) + } +} \ No newline at end of file diff --git a/backends/pixelblaze/pixelblaze_backend.py b/backends/pixelblaze/pixelblaze_backend.py new file mode 100644 index 0000000..10681ab --- /dev/null +++ b/backends/pixelblaze/pixelblaze_backend.py @@ -0,0 +1,27 @@ +import pixelblaze + + +class Backend: + + def __init__(self, pixelblaze_ip="4.3.2.1"): + self.pb = pixelblaze.Pixelblaze(pixelblaze_ip) + self.pb.setActivePatternByName( + "marimapper" + ) # Need to install marimapper.js to your pixelblaze + + def get_led_count(self): + pixel_count = self.pb.getPixelCount() + print(f"Pixelblaze reports {pixel_count} pixels") + return pixel_count + + def set_led(self, led_index: int, on: bool): + self.pb.setActiveVariables({"pixel_to_light": led_index, "turn_on": on}) + + def set_map_coordinates(self, pixelmap: list): + result = self.pb.setMapCoordinates(pixelmap) + if result is False: + raise RuntimeError("Pixelblaze Backend failed to upload map coordinates.") + self.pb.wsSendJson({"mapperFit": 0}) + + def set_current_map(self, pixelmap_name: str): + self.pb.setActivePatternByName(pixelmap_name) diff --git a/backends/pixelblaze/requirements.txt b/backends/pixelblaze/requirements.txt new file mode 100644 index 0000000..b0fa3b4 --- /dev/null +++ b/backends/pixelblaze/requirements.txt @@ -0,0 +1,3 @@ +# This file defines the requirements for the Pixelblaze backend + +pixelblaze-client \ No newline at end of file diff --git a/backends/pixelblaze/upload_map_to_pixelblaze.py b/backends/pixelblaze/upload_map_to_pixelblaze.py new file mode 100644 index 0000000..9d02836 --- /dev/null +++ b/backends/pixelblaze/upload_map_to_pixelblaze.py @@ -0,0 +1,64 @@ +import argparse +import sys +import csv + +sys.path.append("./") + +from lib import utils + + +def read_coordinates_from_csv(csv_file_name): + print(f"Loading coordinates from {csv_file_name}") + with open(csv_file_name, newline="") as csvfile: + csv_reader = csv.DictReader(csvfile) + list_of_leds = [] + for row in csv_reader: + list_of_leds.append(row) + + # Find the largest index in the list + num_leds = int(max(list_of_leds, key=list_of_leds.index)["index"]) + + final_coordinate_list = [] + + for i in range(num_leds): + # Either find the list with the matching index + # or default to [0,0,0] if we never saw the pixel + coords = next( + (item for item in list_of_leds if int(item["index"]) == i), + {"x": 0, "y": 0, "z": 0}, + ) + final_coordinate_list.append( + [float(coords["x"]), float(coords["y"]), float(coords["z"])] + ) + + return final_coordinate_list + + +def main_function(cli_args): + final_coordinate_list = read_coordinates_from_csv(cli_args.csv_file) + print(final_coordinate_list) + + upload_coordinates = utils.get_user_confirmation( + "Upload coordinates to Pixelblaze? [y/n]: " + ) + if not upload_coordinates: + return + + print(f"Uploading coordinates to pixelblaze {cli_args.server}") + led_backend = utils.get_backend("pixelblaze", cli_args.server) + led_backend.set_map_coordinates(final_coordinate_list) + print("Finished") + + +if __name__ == "__main__": + parser = argparse.ArgumentParser(description="Upload led_map_3d.csv to pixelblaze") + parser.add_argument("--server", type=str, help="pixelblaze server ip") + parser.add_argument( + "--csv_file", + type=str, + help="The csv file to convert", + default="my_scan/led_map_3d.csv", + ) + args = parser.parse_args() + + main_function(args) diff --git a/lib/utils.py b/lib/utils.py index cd7be62..5ac5043 100644 --- a/lib/utils.py +++ b/lib/utils.py @@ -41,7 +41,7 @@ def add_backend_args(parser): "--backend", type=str, help="The backend used for led communication", - choices=["custom", "fadecandy", "wled", "fcmega"], + choices=["custom", "fadecandy", "wled", "fcmega", "pixelblaze"], required=True, ) @@ -90,6 +90,11 @@ def get_backend(backend_name, server=""): return fcmega_backend.Backend() + if backend_name == "pixelblaze": + from backends.pixelblaze import pixelblaze_backend + + return pixelblaze_backend.Backend(server) + raise RuntimeError("Invalid backend name") except NotImplementedError: