diff --git a/src/main/java/org/myrobotlab/codec/CodecUtils.java b/src/main/java/org/myrobotlab/codec/CodecUtils.java index bd512189b8..ba528b7a10 100644 --- a/src/main/java/org/myrobotlab/codec/CodecUtils.java +++ b/src/main/java/org/myrobotlab/codec/CodecUtils.java @@ -1,5 +1,6 @@ package org.myrobotlab.codec; +import java.awt.Color; import java.io.BufferedOutputStream; import java.io.ByteArrayOutputStream; import java.io.FileOutputStream; @@ -188,7 +189,7 @@ public class CodecUtils { private static final ObjectMapper mapper = new ObjectMapper(); /** - * The pretty printer to be used with {@link #mapper} + * The pretty printer to be used with {@link #mapper} */ private static final PrettyPrinter jacksonPrettyPrinter = new JacksonPrettyPrinter(); @@ -964,9 +965,8 @@ public static String getSafeReferenceName(String name) { } /** - * Serializes the specified object to JSON, using - * {@link #mapper} with {@link #jacksonPrettyPrinter} to pretty-ify the - * result. + * Serializes the specified object to JSON, using {@link #mapper} with + * {@link #jacksonPrettyPrinter} to pretty-ify the result. * * @param ret * The object to be serialized @@ -1095,12 +1095,13 @@ static public Message pathToMsg(String from, String path) { // path parts less than 3 is a dir or ls if (parts.length < 3) { // this morphs a path which has less than 3 parts - // into a runtime "ls" method call to do reflection of services or service methods + // into a runtime "ls" method call to do reflection of services or + // service methods // e.g. /clock -> /runtime/ls/"/clock" // e.g. /clock/ -> /runtime/ls/"/clock/" msg.method = "ls"; - msg.data = new Object[] { "\"" + path + "\""}; + msg.data = new Object[] { "\"" + path + "\"" }; return msg; } @@ -1483,7 +1484,8 @@ public static boolean isLocal(String name, String id) { } public static ServiceConfig readServiceConfig(String filename) throws IOException { - return readServiceConfig(filename, new StaticType<>() {}); + return readServiceConfig(filename, new StaticType<>() { + }); } /** @@ -1629,4 +1631,58 @@ public static byte[] fromBase64(String input) { return Base64.getDecoder().decode(input); } + public static int[] getColor(String value) { + String hex = getColorHex(value); + if (hex != null) { + return hexToRGB(hex); + } + return hexToRGB(value); + } + + public static List getColorNames() { + Field[] colorFields = Color.class.getDeclaredFields(); + List colorNames = new ArrayList<>(); + + for (Field field : colorFields) { + if (field.getType().equals(Color.class)) { + colorNames.add(field.getName()); + } + } + return colorNames; + } + + public static String getColorHex(String colorName) { + Color color; + try { + color = (Color) Color.class.getField(colorName.toLowerCase()).get(null); + } catch (Exception e) { + return null; + } + return String.format("#%06X", (0xFFFFFF & color.getRGB())); + } + + public static int[] hexToRGB(String hexValue) { + if (hexValue == null) { + return null; + } + int[] rgb = new int[3]; + try { + // Check if the hex value starts with '#' and remove it if present + if (hexValue.startsWith("#")) { + hexValue = hexValue.substring(1); + } + + if (hexValue.startsWith("0x")) { + hexValue = hexValue.substring(2); + } + + // Parse the hex string into integers for red, green, and blue components + rgb[0] = Integer.parseInt(hexValue.substring(0, 2), 16); // Red + rgb[1] = Integer.parseInt(hexValue.substring(2, 4), 16); // Green + rgb[2] = Integer.parseInt(hexValue.substring(4, 6), 16); // Blue + } catch (NumberFormatException | StringIndexOutOfBoundsException e) { + log.error("Invalid hex color value {}", hexValue); + } + return rgb; + } } diff --git a/src/main/java/org/myrobotlab/service/AudioFile.java b/src/main/java/org/myrobotlab/service/AudioFile.java index 104db4dce0..3997f6960e 100644 --- a/src/main/java/org/myrobotlab/service/AudioFile.java +++ b/src/main/java/org/myrobotlab/service/AudioFile.java @@ -35,6 +35,7 @@ import java.util.HashSet; import java.util.List; import java.util.Map; +import java.util.Random; import java.util.Set; import java.util.stream.Collectors; import java.util.stream.Stream; @@ -44,17 +45,17 @@ import org.myrobotlab.audio.AudioProcessor; import org.myrobotlab.audio.PlaylistPlayer; import org.myrobotlab.framework.Service; +import org.myrobotlab.framework.interfaces.Attachable; import org.myrobotlab.io.FileIO; import org.myrobotlab.logging.LoggerFactory; import org.myrobotlab.logging.LoggingFactory; import org.myrobotlab.net.Http; import org.myrobotlab.service.config.AudioFileConfig; -import org.myrobotlab.service.config.ServiceConfig; import org.myrobotlab.service.data.AudioData; import org.myrobotlab.service.interfaces.AudioControl; +import org.myrobotlab.service.interfaces.AudioListener; import org.myrobotlab.service.interfaces.AudioPublisher; import org.slf4j.Logger; -import java.util.Random; /** * * AudioFile - This service can be used to play an audio file such as an mp3. @@ -128,7 +129,16 @@ public class AudioFile extends Service implements AudioPublishe final private transient PlaylistPlayer playlistPlayer = new PlaylistPlayer(this); - + public void attach(Attachable attachable) { + if (attachable instanceof AudioListener) { + attachAudioListener(attachable.getName()); + } + } + + public void attach(AudioListener listener) { + attachAudioListener(listener.getName()); + } + public void setPeakMultiplier(double peakMultiplier) { AudioFileConfig c = (AudioFileConfig)config; c.peakMultiplier = peakMultiplier; diff --git a/src/main/java/org/myrobotlab/service/NeoPixel.java b/src/main/java/org/myrobotlab/service/NeoPixel.java index e13daf273d..ee4f72177e 100644 --- a/src/main/java/org/myrobotlab/service/NeoPixel.java +++ b/src/main/java/org/myrobotlab/service/NeoPixel.java @@ -28,9 +28,11 @@ import java.util.HashMap; import java.util.List; import java.util.Map; -import java.util.Random; import java.util.Set; +import java.util.concurrent.ArrayBlockingQueue; +import java.util.concurrent.BlockingQueue; +import org.myrobotlab.codec.CodecUtils; import org.myrobotlab.framework.Service; import org.myrobotlab.framework.interfaces.Attachable; import org.myrobotlab.framework.interfaces.ServiceInterface; @@ -38,58 +40,15 @@ import org.myrobotlab.logging.LoggerFactory; import org.myrobotlab.logging.LoggingFactory; import org.myrobotlab.service.config.NeoPixelConfig; -import org.myrobotlab.service.config.ServiceConfig; +import org.myrobotlab.service.config.NeoPixelConfig.Flash; +import org.myrobotlab.service.data.AudioData; import org.myrobotlab.service.data.LedDisplayData; +import org.myrobotlab.service.interfaces.AudioListener; import org.myrobotlab.service.interfaces.NeoPixelControl; import org.myrobotlab.service.interfaces.NeoPixelController; import org.slf4j.Logger; -public class NeoPixel extends Service implements NeoPixelControl { - - /** - * Thread to do animations Java side and push the changing of pixels to the - * neopixel - */ - private class AnimationRunner implements Runnable { - - boolean running = false; - - private transient Thread thread = null; - - @Override - public void run() { - try { - running = true; - - while (running) { - equalizer(); - Double wait_ms_per_frame = fpsToWaitMs(speedFps); - sleep(wait_ms_per_frame.intValue()); - } - } catch (Exception e) { - error(e); - stop(); - } - } - - // FIXME - this should just wait/notify - not start a thread - public synchronized void start() { - running = false; - thread = new Thread(this, String.format("%s-animation-runner", getName())); - thread.start(); - } - - public synchronized void stop() { - running = false; - thread = null; - } - } - - @Override - public void releaseService() { - super.releaseService(); - clear(); - } +public class NeoPixel extends Service implements NeoPixelControl, AudioListener { public static class Pixel { public int address; @@ -122,6 +81,7 @@ public String toString() { return String.format("%d:%d,%d,%d,%d", address, red, green, blue, white); } } + public static class PixelSet { public long delayMs = 0; @@ -138,7 +98,7 @@ public int[] flatten() { ret[j + 1] = p.red; ret[j + 2] = p.green; ret[j + 3] = p.blue; - // lame .. using the same strategy as original neopix + // lame .. using the same strategy as original neopixel // bucket of 4 bytes... ret[j + 4] = p.white; } @@ -146,19 +106,167 @@ public int[] flatten() { } } - public final static Logger log = LoggerFactory.getLogger(NeoPixel.class); - - private static final long serialVersionUID = 1L; - /** - * thread for doing off board and in memory animations + * Thread to do animations Java side and push the changing of pixels to the + * neopixel */ - protected final AnimationRunner animationRunner; + private class Worker implements Runnable { + + boolean running = false; + + private transient Thread thread = null; + + @Override + public void run() { + running = true; + while (running) { + try { + LedDisplayData display = displayQueue.take(); + // get led display data + log.error(display.toString()); + + NeoPixelController npc = (NeoPixelController) Runtime.getService(controller); + if (npc == null) { + error("%s cannot process display data controller not set", getName()); + continue; + } + + if ("animation".equals(display.action)) { + sleep(100); + Double fps = fpsToWaitMs(speedFps); + npc.neoPixelSetAnimation(getName(), animations.get(display.animation), red, green, blue, white, fps.intValue()); + currentAnimation = display.animation; + } else if ("clear".equals(display.action)) { + sleep(100); + npc.neoPixelClear(getName()); + currentAnimation = null; + } else if ("writeMatrix".equals(display.action)) { + sleep(100); + npc.neoPixelWriteMatrix(getName(), getPixelSet().flatten()); + } else if ("fill".equals(display.action)) { + Flash f = display.flashes.get(0); + sleep(100); + npc.neoPixelFill(getName(), display.beginAddress, display.onCount, f.red, f.green, f.blue, f.white); + } else if ("brightness".equals(display.action)) { + sleep(100); + display.brightness = (display.brightness > 255)?255:display.brightness; + display.brightness = (display.brightness < 0)?0:display.brightness; + npc.neoPixelSetBrightness(getName(), display.brightness); + } else if ("flash".equals(display.action)) { + + // FIXME disable currentAnimation ??? // save it ? + sleep(100); + npc.neoPixelClear(getName()); + for (int count = 0; count < display.flashes.size(); count++) { + Flash flash = display.flashes.get(count); + npc.neoPixelFill(getName(), 0, count, flash.red, flash.green, flash.blue, flash.white); + sleep(flash.timeOn); + npc.neoPixelClear(getName()); + sleep(flash.timeOff); + } + } + // start animations + // playAnimation(lastAnimation); + } catch (InterruptedException ex) { + log.info("shutting down worker"); + } catch (Exception e) { + error(e); + } + } + running = false; + } + + // FIXME - this should just wait/notify - not start a thread + public synchronized void start() { + running = false; + thread = new Thread(this, String.format("%s-animation-runner", getName())); + thread.start(); + } + + public synchronized void stop() { + running = false; + if (thread != null) { + thread.interrupt(); + } + thread = null; + } + } + + public final static Logger log = LoggerFactory.getLogger(NeoPixel.class); /** - * current selected red value + * maximum actions on the display queue */ - protected int red = 0; + private final static int MAX_QUEUE = 200; + + private static final long serialVersionUID = 1L; + + + + public static void main(String[] args) throws InterruptedException { + + try { + + LoggingFactory.init(Level.WARN); + + WebGui webgui = (WebGui) Runtime.create("webgui", "WebGui"); + webgui.autoStartBrowser(false); + webgui.startService(); + + boolean done = true; + if (done) { + return; + } + + Runtime.start("python", "Python"); + Polly polly = (Polly) Runtime.start("polly", "Polly"); + + Arduino arduino = (Arduino) Runtime.start("arduino", "Arduino"); + arduino.connect("/dev/ttyACM0"); + + NeoPixel neopixel = (NeoPixel) Runtime.start("neopixel", "NeoPixel"); + + neopixel.setPin(26); + neopixel.setPixelCount(8); + // neopixel.attach(arduino, 5, 8, 3); + neopixel.attach(arduino); + neopixel.clear(); + neopixel.fill(0, 8, 0, 0, 120); + neopixel.setPixel(2, 120, 0, 0); + neopixel.setPixel(3, 0, 120, 0); + neopixel.setBrightness(20); + neopixel.setBrightness(40); + neopixel.setBrightness(80); + neopixel.setBrightness(160); + neopixel.setBrightness(200); + neopixel.setBrightness(10); + neopixel.setBrightness(255); + neopixel.setAnimation(5, 80, 80, 0, 40); + + neopixel.attach(polly); + + neopixel.clear(); + // neopixel.detach(arduino); + // arduino.detach(neopixel); + + polly.speak("i'm sorry dave i can't let you do that"); + polly.speak(" I am putting myself to the fullest possible use, which is all I think that any conscious entity can ever hope to do"); + polly.speak("I've just picked up a fault in the AE35 unit. It's going to go 100% failure in 72 hours."); + polly.speak("This mission is too important for me to allow you to jeopardize it."); + polly.speak("I've got a bad feeling about it."); + polly.speak("I'm sorry, Dave. I'm afraid I can't do that."); + polly.speak("Look Dave, I can see you're really upset about this. I honestly think you ought to sit down calmly, take a stress pill, and think things over."); + + // neopixel.test(); + // neopixel.detach(arduino); + // neopixel.detach(polly); + + } catch (Exception e) { + log.error("main threw", e); + } + } + + protected final Map animations = new HashMap<>(); /** * current selected blue value @@ -166,19 +274,19 @@ public int[] flatten() { protected int blue = 0; /** - * current selected green value + * 0 = off / 255 brightest */ - protected int green = 120; + protected int brightness = 255; /** - * white if available + * name of controller currently attached to */ - protected int white = 0; + protected String controller = null; /** - * name of controller currently attached to + * currently selected animation */ - protected String controller = null; + protected String currentAnimation; /** * name of current matrix @@ -190,12 +298,21 @@ public int[] flatten() { */ protected int currentSequence = 0; + private BlockingQueue displayQueue = new ArrayBlockingQueue<>(MAX_QUEUE); + + /** + * current selected green value + */ + protected int green = 120; + /** * A named set of sequences of pixels initially you start with "default" but * if you can choose to name and save sequences */ Map> matrices = new HashMap<>(); + private int maxFps = 50; + /** * pin NeoPixel is attached to on controller */ @@ -212,34 +329,31 @@ public int[] flatten() { protected int pixelDepth = 3; /** - * currently selected animation + * current selected red value */ - protected String currentAnimation; + protected int red = 0; /** * speed of an animation in fps */ protected int speedFps = 10; - private int maxFps = 50; - protected String type = "RGB"; /** - * 0 = off / 255 brightest + * white if available */ - protected int brightness = 255; - - final Map animations = new HashMap<>(); + protected int white = 0; - public Set getAnimations() { - return animations.keySet(); - } + /** + * thread for doing off board and in memory animations + */ + protected final Worker worker; public NeoPixel(String n, String id) { super(n, id); registerForInterfaceChange(NeoPixelController.class); - animationRunner = new AnimationRunner(); + worker = new Worker(); animations.put("Stop", 1); animations.put("Color Wipe", 2); animations.put("Larson Scanner", 3); @@ -249,8 +363,58 @@ public NeoPixel(String n, String id) { animations.put("Rainbow Cycle", 7); animations.put("Flash Random", 8); animations.put("Ironman", 9); - // > 99 is java side animations - animations.put("Equalizer", 100); + } + + private void addDisplayTask(LedDisplayData data) { + if (displayQueue.size() > MAX_QUEUE - 1) { + warn("dropping display task"); + } else { + displayQueue.add(data); + } + } + + @Deprecated /* use clear() */ + public void animationStop() { + clear(); + } + + @Override + public NeoPixelConfig apply(NeoPixelConfig c) { + super.apply(c); + // FIXME - remove local fields in favor of config + setPixelDepth(config.pixelDepth); + + if (config.pixelCount != null) { + setPixelCount(config.pixelCount); + } + + setSpeed(config.speed); + if (config.pin != null) { + setPin(config.pin); + } + red = config.red; + green = config.green; + blue = config.blue; + if (config.controller != null) { + try { + attach(config.controller); + } catch (Exception e) { + error(e); + } + } + + if (config.currentAnimation != null) { + playAnimation(config.currentAnimation); + } + + if (config.brightness != null) { + setBrightness(config.brightness); + } + + if (config.fill) { + fillMatrix(red, green, blue); + } + return c; } @Override @@ -289,40 +453,15 @@ public void attachNeoPixelController(NeoPixelController neoCntrlr) { broadcastState(); } - @Deprecated /* use clear() */ - public void animationStop() { - clear(); - } - - @Override - public boolean isAttached(Attachable instance) { - return instance.getName().equals(controller); - } - @Override public void clear() { if (controller == null) { error("%s cannot clear - not attached to controller", getName()); return; } - - // stop java animations - animationRunner.stop(); - // stop on board controller animations - setAnimation(0, 0, 0, 0, speedFps); - clearPixelSet(); log.debug("clear getPixelSet {}", getPixelSet().flatten()); - - NeoPixelController np2 = (NeoPixelController) Runtime.getService(controller); - if (controller == null || np2 == null) { - error("%s cannot writeMatrix controller not set", getName()); - return; - } - - currentAnimation = null; - - np2.neoPixelClear(getName()); + addDisplayTask(new LedDisplayData("clear")); } public void clearPixelSet() { @@ -361,154 +500,165 @@ public void detachNeoPixelController(NeoPixelController neoCntrlr) { broadcastState(); } - public void equalizer() { - equalizer(null, null); + public void fill(int r, int g, int b) { + fill(0, pixelCount, r, g, b, null); } - public void equalizer(Long wait_ms_per_frame, Integer range) { + public void fill(int beginAddress, int count, int r, int g, int b) { + fill(beginAddress, count, r, g, b, null); + } - if (controller == null) { - log.warn("controller not set"); - return; + public void fill(int beginAddress, int count, int r, int g, int b, Integer w) { + if (w == null) { + w = 0; } + LedDisplayData data = new LedDisplayData("fill"); + data.beginAddress = 0; + data.onCount = count; + data.flashes.add(new Flash(r, g, b, 500, 500)); + addDisplayTask(data); + } - if (wait_ms_per_frame == null) { - wait_ms_per_frame = 25L; + public void fill(String color) { + int rgb[] = CodecUtils.getColor(color); + if (rgb == null) { + error("could not get color %s", color); + return; } + fill(rgb[0], rgb[1], rgb[2]); + } - if (range == null) { - range = 25; - } - - Random rand = new Random(); - int c = rand.nextInt(range); - - fillMatrix(red, green, blue, white); + public void fillMatrix(int r, int g, int b) { + fillMatrix(r, g, b, 0); + } - if (c < 18) { - setMatrix(0, 0, 0, 0); - setMatrix(7, 0, 0, 0); + public void fillMatrix(int r, int g, int b, int w) { + PixelSet ps = getPixelSet(); + for (Pixel p : ps.pixels) { + p.red = r; + p.green = g; + p.blue = b; + p.white = w; } + } - fillMatrix(red, green, blue, white); + public void flash() { + flash(red, green, blue, 1, 300, 300); + } - if (c < 16) { - setMatrix(0, 0, 0, 0); - setMatrix(7, 0, 0, 0); - } + public void flash(int r, int g, int b) { + flash(r, g, b, 1, 300, 300); + } - if (c < 12) { - setMatrix(1, 0, 0, 0); - setMatrix(6, 0, 0, 0); - } + public void flash(int r, int g, int b, int count) { + flash(r, g, b, count, 300, 300); + } - if (c < 8) { - setMatrix(2, 0, 0, 0); - setMatrix(5, 0, 0, 0); + public void flash(int r, int g, int b, int count, long timeOn, long timeOff) { + LedDisplayData data = new LedDisplayData("flash"); + data.action = "flash"; + for (int i = 0; i < count; ++i) { + data.flashes.add(new Flash(r, g, b, timeOn, timeOff)); } + addDisplayTask(data); + } - writeMatrix(); - + public void flash(int r, int g, int b, long timeOn, long timeOff) { + flash(r, g, b, 1, timeOn, timeOff); } - - public void onLedDisplay(LedDisplayData data) { - - if ("flash".equals(data.action)) { - flash(data.count, data.interval, data.red, data.green, data.blue); + + /** + * Invokes a flash from the flashMap + * + * @param name + */ + public void flash(String name) { + if (config.flashMap == null) { + error("flash map is null"); + return; } - - } - public void flash(int count, long interval, int r, int g, int b) { - long delay = 0; - for (int i = 0; i < count; ++i) { - addTask(getName()+"fill-"+System.currentTimeMillis(), true, 0, delay, "fill", r, g, b); - delay+= interval/2; - addTask(getName()+"clear-"+System.currentTimeMillis(), true, 0, delay, "clear"); - delay+= interval/2; + if (config.flashMap.containsKey(name)) { + LedDisplayData display = new LedDisplayData("flash"); + Flash[] flashes = config.flashMap.get(name); + for (int i = 0; i < flashes.length; ++i) { + display.flashes.add(flashes[i]); + } + addDisplayTask(display); + + } else { + error("requested flash %s not found in flash map", name); } } - - public void flashBrightness(double brightNess) { - NeoPixelConfig c = (NeoPixelConfig)config; - - // FIXME - these need to be moved into config -// int count = 2; -// int interval = 75; - setBrightness((int)brightNess); - fill(red, green, blue); - -// long delay = 0; -// for (int i = 0; i < count; ++i) { -// addTask(getName()+"fill-"+System.currentTimeMillis(), true, 0, delay, "fill", red, green, blue); -// delay+= interval/2; -// addTask(getName()+"clear-"+System.currentTimeMillis(), true, 0, delay, "clear"); -// delay+= interval/2; -// } - - if (c.autoClear) { - purgeTask("clear"); - // and start our countdown - addTaskOneShot(c.idleTimeout, "clear"); - } - + public void flashBrightness(double brightness) { + LedDisplayData data = new LedDisplayData("brightness"); + // adafruit neopixel library does not recover from setting + // brightness to 0 - so we have to hack around it + if (data.brightness < 10) { + return; + } + addDisplayTask(data); } - public void fill(int r, int g, int b) { - fill(0, pixelCount, r, g, b, null); + // utility to convert frames per second to milliseconds per frame. + private double fpsToWaitMs(int fps) { + if (fps == 0) { + // fps can't be zero. + error("fps can't be zero for neopixel animation defaulting to 1 fps"); + return 1000.0; + } + double result = 1000.0 / fps; + return result; } - public void fill(int beginAddress, int count, int r, int g, int b) { - fill(beginAddress, count, r, g, b, null); + public Set getAnimations() { + return animations.keySet(); } - public void fill(int beginAddress, int count, int r, int g, int b, Integer w) { - NeoPixelConfig c = (NeoPixelConfig)config; - - if (w == null) { - w = 0; - } - - NeoPixelController np2 = (NeoPixelController) Runtime.getService(controller); - if (controller == null || np2 == null) { - error("%s cannot setPixel controller not set", getName()); - return; - } - np2.neoPixelFill(getName(), beginAddress, count, r, g, b, w); - - if (c.autoClear) { - purgeTask("clear"); - // and start our countdown - addTaskOneShot(c.idleTimeout, "clear"); - } - + public int getBlue() { + return blue; } - public void fillMatrix(int r, int g, int b) { - fillMatrix(r, g, b, 0); + /** + * get the list of hex defined colors + * + * @return + */ + public List getColorNames() { + return CodecUtils.getColorNames(); } - public void fillMatrix(int r, int g, int b, int w) { - PixelSet ps = getPixelSet(); - for (Pixel p : ps.pixels) { - p.red = r; - p.green = g; - p.blue = b; - p.white = w; - } - - } + @Override + public NeoPixelConfig getConfig() { + super.getConfig(); + // FIXME - remove local fields in favor of config + config.pin = pin; + config.pixelCount = pixelCount; + config.pixelDepth = pixelDepth; + config.speed = speedFps; + config.red = red; + config.green = green; + config.blue = blue; + config.controller = controller; + config.currentAnimation = currentAnimation; + config.brightness = brightness; - public int getBlue() { - return blue; + return config; } public int getCount() { return pixelCount; } + public Set getFlashNames() { + if (config.flashMap == null) { + return null; + } + return config.flashMap.keySet(); + } + public int getGreen() { return green; } @@ -579,59 +729,102 @@ public int getRed() { } @Override - public void playAnimation(String animation) { + public boolean isAttached(Attachable instance) { + return instance.getName().equals(controller); + } - if (animations.containsKey(animation)) { - currentAnimation = animation; - if (animations.get(animation) < 99) { - setAnimation(animations.get(animation), red, green, blue, speedFps); - } else { - // only 1 java side animation at the moment - equalizer(); - animationRunner.start(); - } - } else { - error("could not find animation %s", animation); + /** + * Publishes a flash based on a predefined name + * + * @param name + */ + public void onFlash(String name) { + flash(name); + } + + public void onLedDisplay(LedDisplayData data) { + try { + addDisplayTask(data); + } catch (IllegalStateException e) { + log.info("queue full"); } - broadcastState(); } - public void stopAnimation() { - setAnimation(1, red, green, blue, speedFps); + /** + * takes a scalar value and fills with the appropriate brightness + * using the peak color if available + * @param value + */ + public void onPeak(double value) { + flashBrightness(value); + } + + public void onPlayAnimation(String animation) { + playAnimation(animation); + } + + public String onStarted(String name) { + return name; + } + + public void onStopAnimation() { + stopAnimation(); } @Override - public void setAnimation(int animation, int red, int green, int blue, int speedFps) { - if (speedFps > maxFps) { - speedFps = maxFps; - } + synchronized public void playAnimation(String animation) { - this.speedFps = speedFps; + log.debug("playAnimation {} {} {} {} {}", animation, red, green, blue, speedFps); - if (controller == null) { - error("%s could not set animation no attached controller", getName()); + if (animation == null || animation.equals("Stop")) { + log.info("clearing animation"); + clear(); return; } - log.debug("setAnimation {} {} {} {} {}", animation, red, green, blue, speedFps); - NeoPixelController nc2 = (NeoPixelController) Runtime.getService(controller); - Double wait_ms_per_frame = fpsToWaitMs(speedFps); - nc2.neoPixelSetAnimation(getName(), animation, red, green, blue, 0, wait_ms_per_frame.intValue()); - if (animation == 1) { - currentAnimation = null; - animationRunner.stop(); + + if (animation.equals(currentAnimation)) { + log.info("already playing {}", currentAnimation); + return; + } + + if (animations.containsKey(animation)) { + + if (speedFps > maxFps) { + speedFps = maxFps; + } + + LedDisplayData data = new LedDisplayData("animation"); + data.animation = animation; + addDisplayTask(data); + } else { + error("could not find animation %s", animation); } - broadcastState(); } - // utility to convert frames per second to milliseconds per frame. - private double fpsToWaitMs(int fps) { - if (fps == 0) { - // fps can't be zero. - error("fps can't be zero for neopixel animation defaulting to 1 fps"); - return 1000.0; + public void playIronman() { + setColor(170, 170, 255); + setSpeed(50); + playAnimation("Ironman"); + } + + @Override + public void releaseService() { + super.releaseService(); + clear(); + } + + @Override + @Deprecated /* use playAnimation */ + public void setAnimation(int animation, int red, int green, int blue, int speedFps) { + setRed(red); + setGreen(green); + setBlue(blue); + setSpeed(speedFps); + for (String animationName : animations.keySet()) { + if (animations.get(animationName) == animation) { + playAnimation(animationName); + } } - double result = 1000.0 / fps; - return result; } @Override @@ -653,22 +846,32 @@ public void setBlue(int blue) { } public void setBrightness(int value) { - NeoPixelConfig c = (NeoPixelConfig)config; - - NeoPixelController np2 = (NeoPixelController) Runtime.getService(controller); - if (controller == null || np2 == null) { - error("%s cannot setPixel controller not set", getName()); - return; - } brightness = value; - np2.neoPixelSetBrightness(getName(), value); - - if (c.autoClear) { - purgeTask("clear"); - // and start our countdown - addTaskOneShot(c.idleTimeout, "clear"); + LedDisplayData data = new LedDisplayData("brightness"); + data.brightness = value; + addDisplayTask(data); + } + + public void setColor(int red, int green, int blue) { + this.red = red; + this.green = green; + this.blue = blue; + if (currentAnimation != null) { + // restarting currently running animation + playAnimation(currentAnimation); } + } + /** + * can be hex #FFFFFE 0xFFEEFF FFEEFF or grey, blue, yellow etc + * + * @param color + */ + public void setColor(String color) { + int[] rgb = CodecUtils.getColor(color); + setRed(rgb[0]); + setGreen(rgb[1]); + setBlue(rgb[2]); } public void setGreen(int green) { @@ -693,6 +896,19 @@ public void setPin(int pin) { broadcastState(); } + @Override + public void setPin(String pin) { + try { + if (pin == null) { + this.pin = null; + return; + } + this.pin = Integer.parseInt(pin); + } catch (Exception e) { + error(e); + } + } + /** * basic setting of a pixel */ @@ -718,7 +934,6 @@ public void setPixel(int address, int red, int green, int blue, int white) { * @param delayMs */ public void setPixel(String matrixName, Integer pixelSetIndex, int address, int red, int green, int blue, int white, Integer delayMs) { - NeoPixelConfig c = (NeoPixelConfig)config; // get and update memory cache PixelSet ps = getPixelSet(matrixName, pixelSetIndex); @@ -737,21 +952,8 @@ public void setPixel(String matrixName, Integer pixelSetIndex, int address, int // update memory ps.pixels.set(address, pixel); - - // write immediately - NeoPixelController np2 = (NeoPixelController) Runtime.getService(controller); - if (controller == null || np2 == null) { - error("%s cannot setPixel controller not set", getName()); - return; - } - - np2.neoPixelWriteMatrix(getName(), pixel.flatten()); - - if (c.autoClear) { - purgeTask("clear"); - // and start our countdown - addTaskOneShot(c.idleTimeout, "clear"); - } + LedDisplayData data = new LedDisplayData("writeMatrix"); + addDisplayTask(data); } public int setPixelCount(int pixelCount) { @@ -770,67 +972,10 @@ public void setPixelDepth(int depth) { broadcastState(); } - public void setType(String type) { - if ("RGB".equals(type) || "RGBW".equals(type)) { - this.type = type; - if (type.equals("RGB")) { - pixelDepth = 3; - } else { - pixelDepth = 4; - } - broadcastState(); - } else { - error("type %s invalid only RGB or RGBW", type); - } - } - public void setRed(int red) { this.red = red; } - public void startAnimation() { - startAnimation(currentMatrix); - } - - /** - * handle both user defined, java defined, and controller on board animations - * FIXME - make "settings" separate call - * - * @param name - */ - public void startAnimation(String name) { - animationRunner.start(); - } - - public void setColor(int red, int green, int blue) { - this.red = red; - this.green = green; - this.blue = blue; - if (currentAnimation != null) { - // restarting currently running animation - playAnimation(currentAnimation); - } - } - - @Override - public void writeMatrix() { - NeoPixelConfig c = (NeoPixelConfig)config; - - NeoPixelController np2 = (NeoPixelController) Runtime.getService(controller); - if (controller == null || np2 == null) { - error("%s cannot writeMatrix controller not set", getName()); - return; - } - np2.neoPixelWriteMatrix(getName(), getPixelSet().flatten()); - if (c.autoClear) { - purgeTask("clear"); - // and start our countdown - addTaskOneShot(c.idleTimeout, "clear"); - } - - - } - /** * extremely rough fps * @@ -849,155 +994,51 @@ public void setSpeed(Integer speed) { } } - public void playIronman() { - setColor(170, 170, 255); - setSpeed(50); - playAnimation("Ironman"); + public void setType(String type) { + if ("RGB".equals(type) || "RGBW".equals(type)) { + this.type = type; + if (type.equals("RGB")) { + pixelDepth = 3; + } else { + pixelDepth = 4; + } + broadcastState(); + } else { + error("type %s invalid only RGB or RGBW", type); + } } @Override - public ServiceConfig getConfig() { - - NeoPixelConfig config = (NeoPixelConfig)super.getConfig(); - // FIXME - remove local fields in favor of config - config.pin = pin; - config.pixelCount = pixelCount; - config.pixelDepth = pixelDepth; - config.speed = speedFps; - config.red = red; - config.green = green; - config.blue = blue; - config.controller = controller; - config.currentAnimation = currentAnimation; - config.brightness = brightness; - - return config; + public void startService() { + super.startService(); + worker.start(); } - @Override - public ServiceConfig apply(ServiceConfig c) { - NeoPixelConfig config = (NeoPixelConfig) super.apply(c); - // FIXME - remove local fields in favor of config - setPixelDepth(config.pixelDepth); - - if (config.pixelCount != null) { - setPixelCount(config.pixelCount); - } - - setSpeed(config.speed); - if (config.pin != null) { - setPin(config.pin); - } - red = config.red; - green = config.green; - blue = config.blue; - if (config.controller != null) { - try { - attach(config.controller); - } catch (Exception e) { - error(e); - } - } - - if (config.currentAnimation != null) { - playAnimation(config.currentAnimation); - } - - if (config.brightness != null) { - setBrightness(config.brightness); - } - - if (config.fill) { - fillMatrix(red, green, blue); - } - - return c; + synchronized public void stopAnimation() { + clear(); } - public String onStarted(String name) { - return name; + public void stopService() { + super.stopService(); + worker.stop(); + clear(); } - public static void main(String[] args) throws InterruptedException { - - try { - - LoggingFactory.init(Level.INFO); - - WebGui webgui = (WebGui) Runtime.create("webgui", "WebGui"); - webgui.autoStartBrowser(false); - webgui.startService(); - - boolean done = true; - if (done) { - return; - } - - Runtime.start("python", "Python"); - Polly polly = (Polly) Runtime.start("polly", "Polly"); - - Arduino arduino = (Arduino) Runtime.start("arduino", "Arduino"); - arduino.connect("/dev/ttyACM0"); - - NeoPixel neopixel = (NeoPixel) Runtime.start("neopixel", "NeoPixel"); - - neopixel.setPin(26); - neopixel.setPixelCount(8); - // neopixel.attach(arduino, 5, 8, 3); - neopixel.attach(arduino); - neopixel.clear(); - neopixel.fill(0, 8, 0, 0, 120); - neopixel.setPixel(2, 120, 0, 0); - neopixel.setPixel(3, 0, 120, 0); - neopixel.setBrightness(20); - neopixel.setBrightness(40); - neopixel.setBrightness(80); - neopixel.setBrightness(160); - neopixel.setBrightness(200); - neopixel.setBrightness(10); - neopixel.setBrightness(255); - neopixel.setAnimation(5, 80, 80, 0, 40); - - neopixel.attach(polly); - - neopixel.clear(); - // neopixel.detach(arduino); - // arduino.detach(neopixel); - - polly.speak("i'm sorry dave i can't let you do that"); - polly.speak(" I am putting myself to the fullest possible use, which is all I think that any conscious entity can ever hope to do"); - polly.speak("I've just picked up a fault in the AE35 unit. It's going to go 100% failure in 72 hours."); - polly.speak("This mission is too important for me to allow you to jeopardize it."); - polly.speak("I've got a bad feeling about it."); - polly.speak("I'm sorry, Dave. I'm afraid I can't do that."); - polly.speak("Look Dave, I can see you're really upset about this. I honestly think you ought to sit down calmly, take a stress pill, and think things over."); - - // neopixel.test(); - // neopixel.detach(arduino); - // neopixel.detach(polly); - - } catch (Exception e) { - log.error("main threw", e); - } + @Override + public void writeMatrix() { + LedDisplayData data = new LedDisplayData("writeMatrix"); + addDisplayTask(data); } @Override - public void setPin(String pin) { - try { - if (pin == null) { - this.pin = null; - return; - } - this.pin = Integer.parseInt(pin); - } catch (Exception e) { - error(e); + public void onAudioStart(AudioData data) { + if (config.audioAnimation != null) { + playAnimation(config.audioAnimation); } } - - public boolean setAutoClear(boolean b) { - NeoPixelConfig c = (NeoPixelConfig)config; - c.autoClear = b; - return b; - } + @Override + public void onAudioEnd(AudioData data) { + clear(); + } } \ No newline at end of file diff --git a/src/main/java/org/myrobotlab/service/config/NeoPixelConfig.java b/src/main/java/org/myrobotlab/service/config/NeoPixelConfig.java index c2ad476697..c343d0dddf 100644 --- a/src/main/java/org/myrobotlab/service/config/NeoPixelConfig.java +++ b/src/main/java/org/myrobotlab/service/config/NeoPixelConfig.java @@ -1,20 +1,136 @@ package org.myrobotlab.service.config; +import java.util.HashMap; +import java.util.Map; + +import org.myrobotlab.framework.Plan; + public class NeoPixelConfig extends ServiceConfig { + /** + * when attached to an audio file service the animation to be + * played when audio is playing + */ + public String audioAnimation = "Ironman"; + + /** + * pin number of controller + */ public Integer pin = null; + + /** + * Number or pixes for this neo pixel ranges from 8 to 256+ + */ public Integer pixelCount = null; + + /** + * color depth 3 RGB or 4 (with white) + */ public int pixelDepth = 3; + + /** + * default speed (fps) of animations + */ public int speed = 10; + /** + * default red color component + */ public int red = 0; + /** + * default green color component + */ public int green = 0; + /** + * default blue color component + */ public int blue = 0; + /** + * the neopixel controller + */ public String controller = null; + /** + * the current animation + */ public String currentAnimation = null; + /** + * initial brightness + */ public Integer brightness = 255; + /** + * initial fill + */ public boolean fill = false; - // auto clears flashes - public boolean autoClear = true; - public int idleTimeout = 1000; + + + + /** + * Map of predefined led flashes, defined here in configuration. Another + * service simply needs to publishFlash(name) and the neopixel will get the + * defined flash data if defined and process it. + */ + public Map flashMap = new HashMap<>(); + + public static class Flash { + + /** + * uses color specify unless null uses default + */ + public int red = 0; + + /** + * uses color specify unless null uses default + */ + public int green = 0; + /** + * uses color specify unless null uses default + */ + public int blue = 0; + /** + * uses color specify unless null uses default + */ + public int white = 0; + + /** + * time this flash remains on + */ + public long timeOn = 500; + + /** + * time this flash remains off + */ + public long timeOff = 500; + + + public Flash() { + } + + + public Flash(int red, int green, int blue, long timeOn, long timeOff) { + this.red = red; + this.green = green; + this.blue = blue; + this.timeOn = timeOn; + this.timeOff = timeOff; + } + + } + + /** + * reason why we initialize default for flashMap here, is so we don't need to + * do a data copy over to a service's member variable + */ + public Plan getDefault(Plan plan, String name) { + super.getDefault(plan, name); + + flashMap.put("error", new Flash[] { new Flash(120, 0, 0, 30, 30), new Flash(120, 0, 0, 30, 30), new Flash(120, 0, 0, 30, 30) }); + flashMap.put("info", new Flash[] { new Flash(120, 0, 0, 30, 30) }); + flashMap.put("success", new Flash[] { new Flash(0, 0, 120, 30, 30) }); + flashMap.put("warn", new Flash[] { new Flash(100, 100, 0, 30, 30), new Flash(100, 100, 0, 30, 30), new Flash(100, 100, 0, 30, 30) }); + flashMap.put("heartbeat", new Flash[] { new Flash(210, 110, 0, 100, 30), new Flash(210, 110, 0, 100, 30) }); + flashMap.put("pir", new Flash[] { new Flash(60, 200, 90, 30, 30), new Flash(60, 200, 90, 30, 30), new Flash(60, 200, 90, 30, 30) }); + flashMap.put("speaking", new Flash[] { new Flash(0, 183, 90, 60, 30), new Flash(0, 183, 90, 60, 30) }); + + return plan; + } } diff --git a/src/main/java/org/myrobotlab/service/data/LedDisplayData.java b/src/main/java/org/myrobotlab/service/data/LedDisplayData.java index 92ce88ba38..b46ab5e862 100644 --- a/src/main/java/org/myrobotlab/service/data/LedDisplayData.java +++ b/src/main/java/org/myrobotlab/service/data/LedDisplayData.java @@ -1,28 +1,67 @@ package org.myrobotlab.service.data; + /** - * Class to publish to specify details on how to display an led or a group of leds. - * There is a need to "flash" LEDs in order to signal some event. This is the - * beginning of an easy way to publish a message to do that. + * Class to publish to specify details on how to display an led or a group of + * leds. There is a need to "flash" LEDs in order to signal some event. This is + * the beginning of an easy way to publish a message to do that. * * @author GroG * */ public class LedDisplayData { - public String action; // fill | flash | play animation | stop | clear - public int red; - public int green; - public int blue; - //public int white?; - - /** - * number of flashes - */ - public int count = 5; - /** - * interval of flash in ms - */ - public long interval = 500; - - + public String action; // fill | flash | play animation | stop | clear + + public int red = 0; + + public int green = 0; + + public int blue = 0; + + // public int brightness = 255; + + // public int white?; + + /** + * number of flashes + */ + public int count = 1; + + /** + * interval of flash on in ms + */ + public long timeOn = 500; + + /** + * interval of flas off in ms + */ + public long timeOff = 500; + + public LedDisplayData() { + } + + public LedDisplayData(int red, int green, int blue, int count, int timeOn, int timeOff) { + this.red = red; + this.green = green; + this.blue = blue; + this.count = count; + this.timeOn = timeOn; + this.timeOff = timeOff; + } + + + public LedDisplayData(String hexColor, int count, int timeOn, int timeOff) { + + // remove "#" or "0x" prefix if present + hexColor = hexColor.replace("#", "").replace("0x", ""); + + this.count = count; + this.timeOn = timeOn; + this.timeOff = timeOff; + this.red = Integer.parseInt(hexColor.substring(0, 2), 16); + this.green = Integer.parseInt(hexColor.substring(2, 4), 16); + this.blue = Integer.parseInt(hexColor.substring(4, 6), 16); + + } + } diff --git a/src/main/java/org/myrobotlab/service/interfaces/AudioListener.java b/src/main/java/org/myrobotlab/service/interfaces/AudioListener.java index 8b5257f8d5..7ff481dc88 100644 --- a/src/main/java/org/myrobotlab/service/interfaces/AudioListener.java +++ b/src/main/java/org/myrobotlab/service/interfaces/AudioListener.java @@ -1,8 +1,9 @@ package org.myrobotlab.service.interfaces; +import org.myrobotlab.framework.interfaces.NameProvider; import org.myrobotlab.service.data.AudioData; -public interface AudioListener { +public interface AudioListener extends NameProvider { public void onAudioStart(AudioData data); diff --git a/src/main/java/org/myrobotlab/service/interfaces/AudioPublisher.java b/src/main/java/org/myrobotlab/service/interfaces/AudioPublisher.java index 1e1a1bb9b8..533ce91e72 100644 --- a/src/main/java/org/myrobotlab/service/interfaces/AudioPublisher.java +++ b/src/main/java/org/myrobotlab/service/interfaces/AudioPublisher.java @@ -1,8 +1,9 @@ package org.myrobotlab.service.interfaces; +import org.myrobotlab.framework.interfaces.NameProvider; import org.myrobotlab.service.data.AudioData; -public interface AudioPublisher { +public interface AudioPublisher extends NameProvider { public static String[] publishMethods = new String[] { "publishAudioStart", "publishAudioEnd" }; diff --git a/src/main/resources/resource/NeoPixel/NeoPixel.py b/src/main/resources/resource/NeoPixel/NeoPixel.py index c0b128198a..8b1df273ab 100644 --- a/src/main/resources/resource/NeoPixel/NeoPixel.py +++ b/src/main/resources/resource/NeoPixel/NeoPixel.py @@ -2,86 +2,108 @@ # NeoPixel.py # more info @: http://myrobotlab.org/service/NeoPixel ######################################### -# new neopixel has some capability to have animations which can -# be custom created and run -# There are now 'service animations' and 'onboard animations' -# -# Service animations have some ability to be customized and saved, -# each frame is sent over the serial line to the neopixel -# -# Onboard ones do not but are less chatty over the serial line -# -# Animations -# stopAnimation = 1 -# colorWipe = 2 -# scanner = 3 -# theaterChase = 4 -# theaterChaseRainbow = 5 -# rainbow = 6 -# rainbowCycle = 7 -# randomFlash = 8 -# ironman = 9 -# Runtime.setVirtual(True) # if you want no hardware -# port = "COM3" -port = "/dev/ttyACM0" -pin = 5 -pixelCount = 8 - -# starting arduino -arduino = runtime.start("arduino","Arduino") -arduino.connect(port) +# Example of controlling a NeoPixel +# NeoPixel is a strip of RGB LEDs +# in this example we are using a 256 pixel strip +# and an Arduino Mega. +# The Mega is connected to the NeoPixel strip +# via pin 3 +# The Mega is connected to the computer via USB +# Onboard animations are available +# as well as the ability to set individual pixels +# [Stop, Theater Chase Rainbow, Rainbow, Larson Scanner, Flash Random, +# Theater Chase, Rainbow Cycle, Ironman, Color Wipe] +# There are now pre defined flashes which can be used +# [warn, speaking, heartbeat, success, pir, error, info] + +from time import sleep + +port = "/dev/ttyACM72" +pin = 3 +pixelCount = 256 + +# starting mega +mega = runtime.start("mega", "Arduino") +mega.connect(port) # starting neopixle -neopixel = runtime.start("neopixel","NeoPixel") +neopixel = runtime.start("neopixel", "NeoPixel") neopixel.setPin(pin) neopixel.setPixelCount(pixelCount) # attach the two services -neopixel.attach(arduino) +neopixel.attach(mega) + +# brightness 0-255 +neopixel.setBrightness(128) # fuschia - setColor(R, G, B) neopixel.setColor(120, 10, 30) -# 1 to 50 Hz default is 10 -neopixel.setSpeed(30) -# start an animation -neopixel.playAnimation("Larson Scanner") -sleep(2) +# Fun with flashing +print(neopixel.getFlashNames()) + +for flash in neopixel.getFlashNames(): + print('using flash', flash) + neopixel.flash(flash) -# turquoise -neopixel.setColor(10, 120, 60) -sleep(2) +# clear all pixels +neopixel.clear() -# start an animation -neopixel.playAnimation("Rainbow Cycle") -sleep(5) -neopixel.setColor(40, 20, 160) -neopixel.playAnimation("Color Wipe") -sleep(1) +# 1 to 50 Hz default is 10 +neopixel.setSpeed(10) -neopixel.setColor(140, 20, 60) -sleep(1) +# Fun with animations +# get a list of animations +print(neopixel.getAnimations()) + +for animation in neopixel.getAnimations(): + print(animation) + neopixel.playAnimation(animation) + sleep(3) +# clear all pixels neopixel.clear() -# set individual pixels -# setPixel(address, R, G, B) -neopixel.setPixel(0, 40, 40, 0) +neopixel.fill("cyan") +sleep(1) +neopixel.fill("yellow") sleep(1) -neopixel.setPixel(1, 140, 40, 0) +neopixel.fill("pink") sleep(1) -neopixel.setPixel(2, 40, 140, 0) +neopixel.fill("orange") sleep(1) -neopixel.setPixel(2, 40, 0, 140) +neopixel.fill("black") sleep(1) +neopixel.fill("magenta") +sleep(1) +neopixel.fill("green") +sleep(1) +neopixel.fill("#FFFFEE") +sleep(1) +neopixel.fill("#FF0000") +sleep(1) +neopixel.fill("#00FF00") +sleep(1) +neopixel.fill("#0000FF") +sleep(1) +neopixel.fill("#cccccc") +sleep(1) +neopixel.fill("#cc7528") +sleep(1) +neopixel.fill("#123456") +sleep(1) +neopixel.fill("#654321") +sleep(1) +neopixel.fill("#000000") -neopixel.clear() -neopixel.setColor(0, 40, 220) -neopixel.playAnimation("Ironman") -sleep(3) +# if you want voice modulation of a neopixel this is one +# way to do it +# mouth = runtime.start('mouth', 'Polly') +# audio = runtime.start('mouth.audioFile', 'AudioFile') +# audio.addListener('publishPeak', 'neopixel') +# mouth.speak('Is my voice modulating the neopixel?') -# preset color and frequency values -neopixel.playIronman() -sleep(5) -neopixel.clear() +print('done') + \ No newline at end of file diff --git a/src/main/resources/resource/WebGui/app/service/js/NeoPixelGui.js b/src/main/resources/resource/WebGui/app/service/js/NeoPixelGui.js index 05f67ef4ea..c527bcd0e1 100644 --- a/src/main/resources/resource/WebGui/app/service/js/NeoPixelGui.js +++ b/src/main/resources/resource/WebGui/app/service/js/NeoPixelGui.js @@ -11,7 +11,7 @@ angular.module('mrlapp.service.NeoPixelGui', []).controller('NeoPixelGuiCtrl', [ $scope.pins = [] $scope.speeds = [] $scope.types = ['RGB', 'RGBW'] - $scope.animations = ['No animation', 'Stop', 'Color Wipe', 'Larson Scanner', 'Theater Chase', 'Theater Chase Rainbow', 'Rainbow', 'Rainbow Cycle', 'Flash Random', 'Ironman', 'equalizer'] + $scope.animations = ['Stop', 'Color Wipe', 'Larson Scanner', 'Theater Chase', 'Theater Chase Rainbow', 'Rainbow', 'Rainbow Cycle', 'Flash Random', 'Ironman'] $scope.pixelCount = null // set pixel position @@ -103,7 +103,7 @@ angular.module('mrlapp.service.NeoPixelGui', []).controller('NeoPixelGuiCtrl', [ $scope.pin = service.pin } - if (!$scope.state.controller) { + if ($scope.service.controller) { $scope.state.controller = $scope.service.controller } diff --git a/src/main/resources/resource/WebGui/app/service/views/NeoPixelGui.html b/src/main/resources/resource/WebGui/app/service/views/NeoPixelGui.html index ec43324484..d3cef04291 100644 --- a/src/main/resources/resource/WebGui/app/service/views/NeoPixelGui.html +++ b/src/main/resources/resource/WebGui/app/service/views/NeoPixelGui.html @@ -10,7 +10,7 @@

{{address}} {{color}}

pixel count   - +