├── docs ├── flyer.docx ├── hardware-setup.png ├── solution-architecture.png └── solution-architecture.pptx ├── audio ├── xmas-show.mp3 ├── xmas-intro.mp3 └── xmas-color-change.mp3 ├── .gitignore ├── src └── main │ ├── resources │ ├── app.properties │ ├── log4j.properties │ ├── in │ │ ├── de-DE │ │ │ ├── customSlot-TreeColors.txt │ │ │ ├── schema.json │ │ │ └── utterances.txt │ │ ├── en-GB │ │ │ ├── customSlot-TreeColors.txt │ │ │ ├── schema.json │ │ │ └── utterances.txt │ │ └── en-US │ │ │ ├── customSlot-TreeColors.txt │ │ │ ├── schema.json │ │ │ └── utterances.txt │ └── out │ │ ├── en-GB │ │ ├── utterances.yml │ │ └── colors.yml │ │ ├── en-US │ │ ├── utterances.yml │ │ └── colors.yml │ │ └── de-DE │ │ ├── utterances.yml │ │ └── colors.yml │ └── java │ └── io │ └── klerch │ └── alexa │ └── xmastree │ └── skill │ ├── XmasSpeechletHandler.java │ ├── handler │ ├── TreeColorUnknownHandler.java │ ├── HelpHandler.java │ ├── AbstractIntentHandler.java │ ├── TreeOffHandler.java │ ├── StopCancelHandler.java │ ├── TreeOnHandler.java │ ├── XmasShowHandler.java │ ├── TreeColorHandler.java │ └── LaunchHandler.java │ ├── utils │ └── ColorUtil.java │ ├── model │ └── TreeState.java │ └── SkillConfig.java ├── arduino └── xmastree │ ├── aws_iot_config.h │ └── xmastree.ino ├── pom.xml ├── README.md └── LICENSE /docs/flyer.docx: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/KayLerch/alexa-xmas-tree/HEAD/docs/flyer.docx -------------------------------------------------------------------------------- /audio/xmas-show.mp3: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/KayLerch/alexa-xmas-tree/HEAD/audio/xmas-show.mp3 -------------------------------------------------------------------------------- /audio/xmas-intro.mp3: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/KayLerch/alexa-xmas-tree/HEAD/audio/xmas-intro.mp3 -------------------------------------------------------------------------------- /docs/hardware-setup.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/KayLerch/alexa-xmas-tree/HEAD/docs/hardware-setup.png -------------------------------------------------------------------------------- /audio/xmas-color-change.mp3: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/KayLerch/alexa-xmas-tree/HEAD/audio/xmas-color-change.mp3 -------------------------------------------------------------------------------- /docs/solution-architecture.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/KayLerch/alexa-xmas-tree/HEAD/docs/solution-architecture.png -------------------------------------------------------------------------------- /docs/solution-architecture.pptx: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/KayLerch/alexa-xmas-tree/HEAD/docs/solution-architecture.pptx -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | arduino/opkg.conf 2 | audio/xmas*-raw.mp3 3 | src/main/resources/my.app.properties 4 | src/test 5 | target/ 6 | .mvn/ 7 | .idea 8 | *.iml 9 | 10 | -------------------------------------------------------------------------------- /src/main/resources/app.properties: -------------------------------------------------------------------------------- 1 | AlexaAppId=amzn1.ask.skill.xxx 2 | S3BucketUrl=https://s3.amazonaws.com/xxxx/ 3 | Mp3FileIntro=xmas-intro.mp3 4 | Mp3FileShow=xmas-show.mp3 5 | Mp3FileColor=xmas-color-change.mp3 -------------------------------------------------------------------------------- /src/main/resources/log4j.properties: -------------------------------------------------------------------------------- 1 | log = . 2 | log4j.rootLogger = INFO, LAMBDA 3 | 4 | #Define the LAMBDA appender 5 | log4j.appender.LAMBDA=com.amazonaws.services.lambda.runtime.log4j.LambdaAppender 6 | log4j.appender.LAMBDA.layout=org.apache.log4j.PatternLayout 7 | log4j.appender.LAMBDA.layout.conversionPattern=%d{yyyy-MM-dd HH:mm:ss} <%X{AWSRequestId}> %-5p %c{1}:%L - %m%n -------------------------------------------------------------------------------- /src/main/resources/in/de-DE/customSlot-TreeColors.txt: -------------------------------------------------------------------------------- 1 | bunt 2 | schwarz 3 | weiss 4 | rot 5 | grün 6 | blau 7 | hellblau 8 | hell_blau 9 | hellgrün 10 | hell_grün 11 | gelb 12 | gold 13 | lila 14 | magenta 15 | orange 16 | pink 17 | rosa 18 | dunkelrot 19 | dunkel_rot 20 | dunkelgrün 21 | dunkel_grün 22 | dunkelblau 23 | dunkel_blau 24 | himmelblau 25 | himmel_blau 26 | babyblau 27 | baby_blau 28 | azurblau 29 | azur_blau 30 | -------------------------------------------------------------------------------- /src/main/resources/in/en-GB/customSlot-TreeColors.txt: -------------------------------------------------------------------------------- 1 | colourful 2 | coloured 3 | colorful 4 | colored 5 | black 6 | white 7 | red 8 | green 9 | blue 10 | light blue 11 | lightblue 12 | light_green 13 | lightgreen 14 | yellow 15 | gold 16 | purple 17 | lilac 18 | magenta 19 | orange 20 | pink 21 | rose 22 | darkred 23 | dark red 24 | darkgreen 25 | dark_green 26 | darkblue 27 | dark_blue 28 | azure 29 | skyblue 30 | sky blue 31 | sky-blue 32 | babyblue 33 | baby blue 34 | baby-blue 35 | -------------------------------------------------------------------------------- /src/main/resources/in/en-US/customSlot-TreeColors.txt: -------------------------------------------------------------------------------- 1 | colourful 2 | coloured 3 | colorful 4 | colored 5 | black 6 | white 7 | red 8 | green 9 | blue 10 | light blue 11 | lightblue 12 | light_green 13 | lightgreen 14 | yellow 15 | gold 16 | purple 17 | lilac 18 | magenta 19 | orange 20 | pink 21 | rose 22 | darkred 23 | dark red 24 | darkgreen 25 | dark_green 26 | darkblue 27 | dark_blue 28 | azure 29 | skyblue 30 | sky blue 31 | sky-blue 32 | babyblue 33 | baby blue 34 | baby-blue 35 | -------------------------------------------------------------------------------- /src/main/resources/in/de-DE/schema.json: -------------------------------------------------------------------------------- 1 | { 2 | "intents": [ 3 | { 4 | "intent": "AMAZON.HelpIntent" 5 | }, 6 | { 7 | "intent": "AMAZON.CancelIntent" 8 | }, 9 | { 10 | "intent": "AMAZON.StopIntent" 11 | }, 12 | { 13 | "intent": "XmasShow" 14 | }, 15 | { 16 | "intent": "TreeOn" 17 | }, 18 | { 19 | "intent": "TreeOff" 20 | }, 21 | { 22 | "intent": "TreeColor", 23 | "slots": [ 24 | { 25 | "name": "Color", 26 | "type": "TreeColors" 27 | } 28 | ] 29 | } 30 | ] 31 | } -------------------------------------------------------------------------------- /src/main/resources/in/en-GB/schema.json: -------------------------------------------------------------------------------- 1 | { 2 | "intents": [ 3 | { 4 | "intent": "AMAZON.HelpIntent" 5 | }, 6 | { 7 | "intent": "AMAZON.CancelIntent" 8 | }, 9 | { 10 | "intent": "AMAZON.StopIntent" 11 | }, 12 | { 13 | "intent": "XmasShow" 14 | }, 15 | { 16 | "intent": "TreeOn" 17 | }, 18 | { 19 | "intent": "TreeOff" 20 | }, 21 | { 22 | "intent": "TreeColor", 23 | "slots": [ 24 | { 25 | "name": "Color", 26 | "type": "TreeColors" 27 | } 28 | ] 29 | } 30 | ] 31 | } -------------------------------------------------------------------------------- /src/main/resources/in/en-US/schema.json: -------------------------------------------------------------------------------- 1 | { 2 | "intents": [ 3 | { 4 | "intent": "AMAZON.HelpIntent" 5 | }, 6 | { 7 | "intent": "AMAZON.CancelIntent" 8 | }, 9 | { 10 | "intent": "AMAZON.StopIntent" 11 | }, 12 | { 13 | "intent": "XmasShow" 14 | }, 15 | { 16 | "intent": "TreeOn" 17 | }, 18 | { 19 | "intent": "TreeOff" 20 | }, 21 | { 22 | "intent": "TreeColor", 23 | "slots": [ 24 | { 25 | "name": "Color", 26 | "type": "TreeColors" 27 | } 28 | ] 29 | } 30 | ] 31 | } -------------------------------------------------------------------------------- /src/main/java/io/klerch/alexa/xmastree/skill/XmasSpeechletHandler.java: -------------------------------------------------------------------------------- 1 | package io.klerch.alexa.xmastree.skill; 2 | 3 | import io.klerch.alexa.tellask.model.wrapper.AlexaRequestStreamHandler; 4 | import io.klerch.alexa.tellask.schema.UtteranceReader; 5 | import io.klerch.alexa.tellask.util.resource.ResourceUtteranceReader; 6 | 7 | import java.util.Collections; 8 | import java.util.Set; 9 | 10 | public class XmasSpeechletHandler extends AlexaRequestStreamHandler { 11 | @Override 12 | public Set getSupportedApplicationIds() { 13 | return Collections.singleton(SkillConfig.getAlexaAppId()); 14 | } 15 | 16 | @Override 17 | public UtteranceReader getUtteranceReader() { 18 | return new ResourceUtteranceReader("out/"); 19 | } 20 | } 21 | -------------------------------------------------------------------------------- /src/main/resources/out/en-GB/utterances.yml: -------------------------------------------------------------------------------- 1 | SayWelcome: 2 | - "Merry christmas! {mp3}" 3 | 4 | SayTreeOn: 5 | - "Merry christmas! {mp3}" 6 | 7 | SayTreeOff: 8 | - "[The light|The tree] turns off." 9 | 10 | SayColorChange: 11 | - "The christmas tree turns into {color}! {mp3}" 12 | - "{color} christmas ahead! {mp3}" 13 | 14 | SayColorUnknown: 15 | - "I don't support this color. Try another one." 16 | 17 | SayXmasShow: 18 | - "{mp3}" 19 | 20 | SayOk: 21 | - "Ok" 22 | 23 | SayHelp: 24 | Utterances: 25 | - "Provide this skill a color to change the tree lights. You could also und sage

Starte Show

" 26 | Reprompts: 27 | - "For example you could say

Turn red

or

Start a Show

" 28 | 29 | SaySorry: 30 | - "[Sorry|Excuse me], something went wrong." -------------------------------------------------------------------------------- /src/main/resources/out/en-US/utterances.yml: -------------------------------------------------------------------------------- 1 | SayWelcome: 2 | - "Merry christmas! {mp3}" 3 | 4 | SayTreeOn: 5 | - "Merry christmas! {mp3}" 6 | 7 | SayTreeOff: 8 | - "[The light|The tree] turns off." 9 | 10 | SayColorChange: 11 | - "The christmas tree turns into {color}! {mp3}" 12 | - "{color} christmas ahead! {mp3}" 13 | 14 | SayColorUnknown: 15 | - "I don't support this color. Try another one." 16 | 17 | SayXmasShow: 18 | - "{mp3}" 19 | 20 | SayOk: 21 | - "Ok" 22 | 23 | SayHelp: 24 | Utterances: 25 | - "Provide this skill a color to change the tree lights. You could also und sage

Starte Show

" 26 | Reprompts: 27 | - "For example you could say

Turn red

or

Start a Show

" 28 | 29 | SaySorry: 30 | - "[Sorry|Excuse me], something went wrong." -------------------------------------------------------------------------------- /src/main/java/io/klerch/alexa/xmastree/skill/handler/TreeColorUnknownHandler.java: -------------------------------------------------------------------------------- 1 | package io.klerch.alexa.xmastree.skill.handler; 2 | 3 | import io.klerch.alexa.state.utils.AlexaStateException; 4 | import io.klerch.alexa.tellask.model.AlexaInput; 5 | import io.klerch.alexa.tellask.model.AlexaOutput; 6 | import io.klerch.alexa.tellask.schema.annotation.AlexaIntentListener; 7 | import io.klerch.alexa.tellask.util.AlexaRequestHandlerException; 8 | 9 | @AlexaIntentListener(customIntents = "TreeColor", priority = 1) 10 | public class TreeColorUnknownHandler extends AbstractIntentHandler { 11 | @Override 12 | public AlexaOutput handleRequest(AlexaInput input) throws AlexaRequestHandlerException, AlexaStateException { 13 | return AlexaOutput.tell("SayColorUnknown").build(); 14 | } 15 | } 16 | -------------------------------------------------------------------------------- /src/main/java/io/klerch/alexa/xmastree/skill/handler/HelpHandler.java: -------------------------------------------------------------------------------- 1 | package io.klerch.alexa.xmastree.skill.handler; 2 | 3 | import io.klerch.alexa.state.utils.AlexaStateException; 4 | import io.klerch.alexa.tellask.model.AlexaInput; 5 | import io.klerch.alexa.tellask.model.AlexaOutput; 6 | import io.klerch.alexa.tellask.schema.annotation.AlexaIntentListener; 7 | import io.klerch.alexa.tellask.schema.type.AlexaIntentType; 8 | import io.klerch.alexa.tellask.util.AlexaRequestHandlerException; 9 | 10 | @AlexaIntentListener(builtInIntents = AlexaIntentType.INTENT_HELP) 11 | public class HelpHandler extends AbstractIntentHandler { 12 | @Override 13 | public AlexaOutput handleRequest(AlexaInput input) throws AlexaRequestHandlerException, AlexaStateException { 14 | return AlexaOutput.ask("SayHelp").withReprompt(true).build(); 15 | } 16 | } 17 | -------------------------------------------------------------------------------- /src/main/resources/out/de-DE/utterances.yml: -------------------------------------------------------------------------------- 1 | SayWelcome: 2 | - "[Frohe|Gesegnete|Fröhliche] [Weihnachten|Weihnacht]! {mp3}" 3 | 4 | SayTreeOn: 5 | - "[Frohe|Gesegnete|Fröhliche] [Weihnachten|Weihnacht]! {mp3}" 6 | 7 | SayTreeOff: 8 | - "[Das Licht|Der Baum] [erlischt|geht aus]." 9 | 10 | SayColorChange: 11 | - "Es erscheint der Baum in {color}! {mp3}" 12 | - "Es werde {color}! {mp3}" 13 | - "Weihnachten wird {color}! {mp3}" 14 | 15 | SayColorUnknown: 16 | - "Auf diese Farbe bin ich nicht vorbereitet. Probiere etwas anderes." 17 | - "Diese Farbe habe ich nicht verstanden. Probiere etwas anderes." 18 | - "Da hast du mich auf dem falschen Fuß erwischt. Probiere etwas anderes." 19 | 20 | SayXmasShow: 21 | - "{mp3}" 22 | 23 | SayOk: 24 | - "Ok" 25 | 26 | SayHelp: 27 | Utterances: 28 | - "Gebe mir eine Farbe, um den Baum in einem anderen Licht erscheinen zu lassen. Oder erfreue dich an einer Lichtershow und sage

Starte Show

" 29 | Reprompts: 30 | - "Sage zum Beispiel

Schalte auf rot

oder aber

Starte Show

" 31 | 32 | SaySorry: 33 | - "[Entschuldige|Sorry|Tut mir leid], etwas ist schief hier [gegangen|gelaufen|gewickelt]." -------------------------------------------------------------------------------- /src/main/resources/out/de-DE/colors.yml: -------------------------------------------------------------------------------- 1 | bunt: "1_1_1" 2 | 1_1_1: "bunt" 3 | 4 | schwarz: "0_0_0" 5 | 0_0_0: "schwarz" 6 | 7 | weiss: "255_255_255" 8 | 255_255_255: "schwarz" 9 | 10 | rot: "255_0_0" 11 | 255_0_0: "rot" 12 | 13 | grün: "0_255_0" 14 | 0_255_0: "grün" 15 | 16 | blau: "0_0_255" 17 | 0_0_255: "blau" 18 | 19 | hellblau: "30_98_232" 20 | hell_blau: "30_98_232" 21 | 30_98_232: "hellblau" 22 | 23 | hellgrün: "157_230_3" 24 | hell_grün: "157_230_3" 25 | 157_230_3: "hellgrün" 26 | 27 | gelb: "124_114_32" 28 | 124_114_32: "gelb" 29 | 30 | gold: "216_199_29" 31 | 216_199_29: "gold" 32 | 33 | lila: "232_19_158" 34 | 232_19_158: "lila" 35 | 36 | magenta: "237_0_185" 37 | 237_0_185: "magenta" 38 | 39 | orange: "160_52_3" 40 | 160_52_3: "orange" 41 | 42 | pink: "200_14_30" 43 | rosa: "200_14_30" 44 | 200_14_30: "rosa" 45 | 46 | dunkelrot: "90_0_0" 47 | dunkel_rot: "90_0_0" 48 | 90_0_0: "dunkelrot" 49 | 50 | dunkelgrün: "0_90_0" 51 | dunkel_grün: "0_90_0" 52 | 0_90_0: "dunkelgrün" 53 | 54 | dunkelblau: "0_0_90" 55 | dunkel_blau: "0_0_90" 56 | 0_0_90: "dunkelblau" 57 | 58 | himmelblau: "123_116_190" 59 | himmel_blau: "123_116_190" 60 | babyblau: "123_116_190" 61 | baby_blau: "123_116_190" 62 | azurblau: "123_116_190" 63 | azur_blau: "123_116_190" 64 | 123_116_190: "himmelblau" 65 | -------------------------------------------------------------------------------- /src/main/resources/in/en-US/utterances.txt: -------------------------------------------------------------------------------- 1 | TreeOn on 2 | TreeOn to turn on 3 | TreeOn to switch on 4 | TreeOn to activate 5 | TreeOn to wake up 6 | 7 | TreeOff off 8 | TreeOff to turn off 9 | TreeOff to switch off 10 | TreeOff to deactivate 11 | TreeOff to sleep 12 | 13 | TreeColor do {Color} 14 | TreeColor {Color} 15 | TreeColor he should turn {Color} 16 | TreeColor he should turn to {Color} 17 | TreeColor he should turn to color {Color} 18 | TreeColor to {Color} 19 | TreeColor to color {Color} 20 | TreeColor he should be {Color} 21 | TreeColor color {Color} 22 | TreeColor with color {Color} 23 | TreeColor change to {Color} 24 | TreeColor he should change to {Color} 25 | TreeColor to change to {Color} 26 | TreeColor switch to {Color} 27 | TreeColor he should switch to {Color} 28 | TreeColor to switch to {Color} 29 | TreeColor switch to color {Color} 30 | TreeColor he should switch to color {Color} 31 | TreeColor to switch to color {Color} 32 | TreeColor set to {Color} 33 | TreeColor he should set to {Color} 34 | TreeColor to set to {Color} 35 | TreeColor set to color {Color} 36 | TreeColor he should set to color {Color} 37 | TreeColor to set to color {Color} 38 | 39 | XmasShow show 40 | XmasShow light show 41 | XmasShow revue 42 | XmasShow for a show 43 | XmasShow for a light show 44 | XmasShow with show 45 | XmasShow with light show -------------------------------------------------------------------------------- /src/main/resources/in/en-GB/utterances.txt: -------------------------------------------------------------------------------- 1 | TreeOn on 2 | TreeOn to turn on 3 | TreeOn to switch on 4 | TreeOn to activate 5 | TreeOn to wake up 6 | 7 | TreeOff off 8 | TreeOff to turn off 9 | TreeOff to switch off 10 | TreeOff to deactivate 11 | TreeOff to sleep 12 | 13 | TreeColor do {Color} 14 | TreeColor {Color} 15 | TreeColor he should turn {Color} 16 | TreeColor he should turn to {Color} 17 | TreeColor he should turn to colour {Color} 18 | TreeColor to {Color} 19 | TreeColor to colour {Color} 20 | TreeColor he should be {Color} 21 | TreeColor colour {Color} 22 | TreeColor with colour {Color} 23 | TreeColor change to {Color} 24 | TreeColor he should change to {Color} 25 | TreeColor to change to {Color} 26 | TreeColor switch to {Color} 27 | TreeColor he should switch to {Color} 28 | TreeColor to switch to {Color} 29 | TreeColor switch to colour {Color} 30 | TreeColor he should switch to colour {Color} 31 | TreeColor to switch to colour {Color} 32 | TreeColor set to {Color} 33 | TreeColor he should set to {Color} 34 | TreeColor to set to {Color} 35 | TreeColor set to colour {Color} 36 | TreeColor he should set to colour {Color} 37 | TreeColor to set to colour {Color} 38 | 39 | XmasShow show 40 | XmasShow light show 41 | XmasShow revue 42 | XmasShow for a show 43 | XmasShow for a light show 44 | XmasShow with show 45 | XmasShow with light show -------------------------------------------------------------------------------- /src/main/java/io/klerch/alexa/xmastree/skill/handler/AbstractIntentHandler.java: -------------------------------------------------------------------------------- 1 | package io.klerch.alexa.xmastree.skill.handler; 2 | 3 | import io.klerch.alexa.state.handler.AWSIotStateHandler; 4 | import io.klerch.alexa.state.utils.AlexaStateException; 5 | import io.klerch.alexa.tellask.model.AlexaInput; 6 | import io.klerch.alexa.tellask.model.AlexaOutput; 7 | import io.klerch.alexa.tellask.schema.AlexaIntentHandler; 8 | import io.klerch.alexa.tellask.util.AlexaRequestHandlerException; 9 | import io.klerch.alexa.xmastree.skill.model.TreeState; 10 | 11 | abstract class AbstractIntentHandler implements AlexaIntentHandler { 12 | @Override 13 | public boolean verify(final AlexaInput input) { 14 | return true; 15 | } 16 | 17 | @Override 18 | public abstract AlexaOutput handleRequest(final AlexaInput input) throws AlexaRequestHandlerException, AlexaStateException; 19 | 20 | @Override 21 | public AlexaOutput handleError(final AlexaRequestHandlerException exception) { 22 | return AlexaOutput.tell("SaySorry").build(); 23 | } 24 | 25 | static void sendIotHook(final AlexaInput input, final TreeState treeState) throws AlexaStateException { 26 | final AWSIotStateHandler iotStateHandler = new AWSIotStateHandler(input.getSessionStateHandler().getSession()); 27 | treeState.withHandler(iotStateHandler).saveState(); 28 | } 29 | } 30 | -------------------------------------------------------------------------------- /src/main/resources/out/en-GB/colors.yml: -------------------------------------------------------------------------------- 1 | colourful: "1_1_1" 2 | coloured: "1_1_1" 3 | colorful: "1_1_1" 4 | colored: "1_1_1" 5 | 1_1_1: "colourful" 6 | 7 | black: "0_0_0" 8 | 0_0_0: "black" 9 | 10 | white: "255_255_255" 11 | 255_255_255: "white" 12 | 13 | red: "255_0_0" 14 | 255_0_0: "red" 15 | 16 | green: "0_255_0" 17 | 0_255_0: "green" 18 | 19 | blue: "0_0_255" 20 | 0_0_255: "blue" 21 | 22 | light_blue: "30_98_232" 23 | lightblue: "30_98_232" 24 | 30_98_232: "light blue" 25 | 26 | light_green: "157_230_3" 27 | lightgreen: "157_230_3" 28 | 157_230_3: "light green" 29 | 30 | yellow: "124_114_32" 31 | 124_114_32: "yellow" 32 | 33 | gold: "216_199_29" 34 | 216_199_29: "gold" 35 | 36 | purple: "232_19_158" 37 | lilac: "232_19_158" 38 | 232_19_158: "purple" 39 | 40 | magenta: "237_0_185" 41 | 237_0_185: "magenta" 42 | 43 | orange: "160_52_3" 44 | 160_52_3: "orange" 45 | 46 | pink: "200_14_30" 47 | rose: "200_14_30" 48 | 200_14_30: "pink" 49 | 50 | darkred: "90_0_0" 51 | dark_red: "90_0_0" 52 | 90_0_0: "dark red" 53 | 54 | darkgreen: "0_90_0" 55 | dark_green: "0_90_0" 56 | 0_90_0: "dark green" 57 | 58 | darkblue: "0_0_90" 59 | dark_blue: "0_0_90" 60 | 0_0_90: "dark blue" 61 | 62 | azure: "123_116_190" 63 | skyblue: "123_116_190" 64 | sky_blue: "123_116_190" 65 | sky-blue: "123_116_190" 66 | babyblue: "123_116_190" 67 | baby-blue: "123_116_190" 68 | baby_blue: "123_116_190" 69 | 123_116_190: "sky-blue" 70 | -------------------------------------------------------------------------------- /src/main/resources/out/en-US/colors.yml: -------------------------------------------------------------------------------- 1 | colourful: "1_1_1" 2 | coloured: "1_1_1" 3 | colorful: "1_1_1" 4 | colored: "1_1_1" 5 | 1_1_1: "colourful" 6 | 7 | black: "0_0_0" 8 | 0_0_0: "black" 9 | 10 | white: "255_255_255" 11 | 255_255_255: "white" 12 | 13 | red: "255_0_0" 14 | 255_0_0: "red" 15 | 16 | green: "0_255_0" 17 | 0_255_0: "green" 18 | 19 | blue: "0_0_255" 20 | 0_0_255: "blue" 21 | 22 | light_blue: "30_98_232" 23 | lightblue: "30_98_232" 24 | 30_98_232: "light blue" 25 | 26 | light_green: "157_230_3" 27 | lightgreen: "157_230_3" 28 | 157_230_3: "light green" 29 | 30 | yellow: "124_114_32" 31 | 124_114_32: "yellow" 32 | 33 | gold: "216_199_29" 34 | 216_199_29: "gold" 35 | 36 | purple: "232_19_158" 37 | lilac: "232_19_158" 38 | 232_19_158: "purple" 39 | 40 | magenta: "237_0_185" 41 | 237_0_185: "magenta" 42 | 43 | orange: "160_52_3" 44 | 160_52_3: "orange" 45 | 46 | pink: "200_14_30" 47 | rose: "200_14_30" 48 | 200_14_30: "pink" 49 | 50 | darkred: "90_0_0" 51 | dark_red: "90_0_0" 52 | 90_0_0: "dark red" 53 | 54 | darkgreen: "0_90_0" 55 | dark_green: "0_90_0" 56 | 0_90_0: "dark green" 57 | 58 | darkblue: "0_0_90" 59 | dark_blue: "0_0_90" 60 | 0_0_90: "dark blue" 61 | 62 | azure: "123_116_190" 63 | skyblue: "123_116_190" 64 | sky_blue: "123_116_190" 65 | sky-blue: "123_116_190" 66 | babyblue: "123_116_190" 67 | baby-blue: "123_116_190" 68 | baby_blue: "123_116_190" 69 | 123_116_190: "sky-blue" 70 | -------------------------------------------------------------------------------- /src/main/java/io/klerch/alexa/xmastree/skill/utils/ColorUtil.java: -------------------------------------------------------------------------------- 1 | package io.klerch.alexa.xmastree.skill.utils; 2 | 3 | import io.klerch.alexa.tellask.util.resource.ResourceUtteranceReader; 4 | import io.klerch.alexa.tellask.util.resource.YamlReader; 5 | 6 | import java.awt.*; 7 | import java.util.Optional; 8 | 9 | public class ColorUtil { 10 | private final String locale; 11 | private final YamlReader yamlReader; 12 | 13 | public ColorUtil(final String locale) { 14 | this.locale = locale; 15 | final ResourceUtteranceReader reader = new ResourceUtteranceReader("/out", "/colors.yml"); 16 | this.yamlReader = new YamlReader(reader, locale); 17 | } 18 | 19 | public Optional getColor(final String color) { 20 | return this.yamlReader.getRandomUtterance(color.replace(" ", "_")).map(colorCode -> { 21 | final String[] colorCodes = colorCode.split("_"); 22 | return new Color(Integer.valueOf(colorCodes[0]), Integer.valueOf(colorCodes[1]), Integer.valueOf(colorCodes[2])); 23 | }); 24 | } 25 | 26 | public Optional getColorName(final Color rgb) { 27 | return this.yamlReader.getRandomUtterance(String.format("%1s_%2s_%3s", rgb.getRed(), rgb.getGreen(), rgb.getBlue())) 28 | .map(color -> color.replace("_", " ")); 29 | } 30 | 31 | public String getLocale() { 32 | return locale; 33 | } 34 | } 35 | -------------------------------------------------------------------------------- /src/main/java/io/klerch/alexa/xmastree/skill/handler/TreeOffHandler.java: -------------------------------------------------------------------------------- 1 | package io.klerch.alexa.xmastree.skill.handler; 2 | 3 | import io.klerch.alexa.state.handler.AWSDynamoStateHandler; 4 | import io.klerch.alexa.state.utils.AlexaStateException; 5 | import io.klerch.alexa.tellask.model.AlexaInput; 6 | import io.klerch.alexa.tellask.model.AlexaOutput; 7 | import io.klerch.alexa.tellask.schema.annotation.AlexaIntentListener; 8 | import io.klerch.alexa.tellask.util.AlexaRequestHandlerException; 9 | import io.klerch.alexa.xmastree.skill.model.TreeState; 10 | 11 | @AlexaIntentListener(customIntents = "TreeOff") 12 | public class TreeOffHandler extends AbstractIntentHandler { 13 | @Override 14 | public AlexaOutput handleRequest(final AlexaInput input) throws AlexaRequestHandlerException, AlexaStateException { 15 | final AWSDynamoStateHandler dynamoHandler = new AWSDynamoStateHandler(input.getSessionStateHandler().getSession()); 16 | final TreeState treeState = dynamoHandler.readModel(TreeState.class).orElse(new TreeState(input.getLocale(), TreeState.MODE.ON)); 17 | 18 | // set state off 19 | treeState.setState(TreeState.MODE.OFF); 20 | 21 | // send state of tree to AWS IoT thing shadow 22 | sendIotHook(input, treeState); 23 | 24 | return AlexaOutput.tell("SayTreeOff") 25 | .putState(treeState.withHandler(dynamoHandler)) 26 | .build(); 27 | } 28 | } 29 | -------------------------------------------------------------------------------- /src/main/java/io/klerch/alexa/xmastree/skill/handler/StopCancelHandler.java: -------------------------------------------------------------------------------- 1 | package io.klerch.alexa.xmastree.skill.handler; 2 | 3 | import io.klerch.alexa.state.handler.AWSDynamoStateHandler; 4 | import io.klerch.alexa.state.utils.AlexaStateException; 5 | import io.klerch.alexa.tellask.model.AlexaInput; 6 | import io.klerch.alexa.tellask.model.AlexaOutput; 7 | import io.klerch.alexa.tellask.schema.annotation.AlexaIntentListener; 8 | import io.klerch.alexa.tellask.schema.type.AlexaIntentType; 9 | import io.klerch.alexa.tellask.util.AlexaRequestHandlerException; 10 | import io.klerch.alexa.xmastree.skill.model.TreeState; 11 | 12 | @AlexaIntentListener(builtInIntents = {AlexaIntentType.INTENT_CANCEL, AlexaIntentType.INTENT_STOP}) 13 | public class StopCancelHandler extends AbstractIntentHandler { 14 | @Override 15 | public AlexaOutput handleRequest(final AlexaInput input) throws AlexaRequestHandlerException, AlexaStateException { 16 | final AWSDynamoStateHandler dynamoHandler = new AWSDynamoStateHandler(input.getSessionStateHandler().getSession()); 17 | final TreeState treeState = dynamoHandler.readModel(TreeState.class).orElse(new TreeState(input.getLocale(), TreeState.MODE.ON)); 18 | 19 | // set state stop 20 | treeState.setState(TreeState.MODE.STOP); 21 | 22 | // send state of tree to AWS IoT thing shadow 23 | sendIotHook(input, treeState); 24 | 25 | return AlexaOutput.tell("SayOk").build(); 26 | } 27 | } 28 | -------------------------------------------------------------------------------- /src/main/java/io/klerch/alexa/xmastree/skill/handler/TreeOnHandler.java: -------------------------------------------------------------------------------- 1 | package io.klerch.alexa.xmastree.skill.handler; 2 | 3 | import io.klerch.alexa.state.handler.AWSDynamoStateHandler; 4 | import io.klerch.alexa.state.utils.AlexaStateException; 5 | import io.klerch.alexa.tellask.model.AlexaInput; 6 | import io.klerch.alexa.tellask.model.AlexaOutput; 7 | import io.klerch.alexa.tellask.model.AlexaOutputSlot; 8 | import io.klerch.alexa.tellask.schema.annotation.AlexaIntentListener; 9 | import io.klerch.alexa.tellask.schema.type.AlexaOutputFormat; 10 | import io.klerch.alexa.tellask.util.AlexaRequestHandlerException; 11 | import io.klerch.alexa.xmastree.skill.SkillConfig; 12 | import io.klerch.alexa.xmastree.skill.model.TreeState; 13 | 14 | @AlexaIntentListener(customIntents = "TreeOn") 15 | public class TreeOnHandler extends AbstractIntentHandler { 16 | @Override 17 | public AlexaOutput handleRequest(final AlexaInput input) throws AlexaRequestHandlerException, AlexaStateException { 18 | final AWSDynamoStateHandler dynamoHandler = new AWSDynamoStateHandler(input.getSessionStateHandler().getSession()); 19 | final TreeState treeState = dynamoHandler.readModel(TreeState.class).orElse(new TreeState(input.getLocale(), TreeState.MODE.ON)); 20 | 21 | // set state on 22 | treeState.setState(TreeState.MODE.ON); 23 | 24 | // send state of tree to AWS IoT thing shadow 25 | sendIotHook(input, treeState); 26 | 27 | return AlexaOutput.tell("SayTreeOn") 28 | .putState(treeState.withHandler(dynamoHandler)) 29 | .putSlot(new AlexaOutputSlot("mp3", SkillConfig.getMp3IntroUrl()).formatAs(AlexaOutputFormat.AUDIO)) 30 | .build(); 31 | } 32 | } 33 | -------------------------------------------------------------------------------- /src/main/java/io/klerch/alexa/xmastree/skill/handler/XmasShowHandler.java: -------------------------------------------------------------------------------- 1 | package io.klerch.alexa.xmastree.skill.handler; 2 | 3 | import io.klerch.alexa.state.handler.AWSDynamoStateHandler; 4 | import io.klerch.alexa.state.utils.AlexaStateException; 5 | import io.klerch.alexa.tellask.model.AlexaInput; 6 | import io.klerch.alexa.tellask.model.AlexaOutput; 7 | import io.klerch.alexa.tellask.model.AlexaOutputSlot; 8 | import io.klerch.alexa.tellask.schema.annotation.AlexaIntentListener; 9 | import io.klerch.alexa.tellask.schema.type.AlexaOutputFormat; 10 | import io.klerch.alexa.tellask.util.AlexaRequestHandlerException; 11 | import io.klerch.alexa.xmastree.skill.SkillConfig; 12 | import io.klerch.alexa.xmastree.skill.model.TreeState; 13 | 14 | @AlexaIntentListener(customIntents = "XmasShow") 15 | public class XmasShowHandler extends AbstractIntentHandler { 16 | @Override 17 | public AlexaOutput handleRequest(final AlexaInput input) throws AlexaRequestHandlerException, AlexaStateException { 18 | final AWSDynamoStateHandler dynamoHandler = new AWSDynamoStateHandler(input.getSessionStateHandler().getSession()); 19 | // get last set color in order to reset it once the show is done 20 | final TreeState treeState = dynamoHandler.readModel(TreeState.class).orElse(new TreeState(input.getLocale(), TreeState.MODE.ON)); 21 | 22 | // set state show 23 | treeState.setState(TreeState.MODE.SHOW); 24 | 25 | // send state of tree to AWS IoT thing shadow 26 | sendIotHook(input, treeState); 27 | 28 | return AlexaOutput.tell("SayXmasShow") 29 | .putState(treeState.withHandler(dynamoHandler)) 30 | .putSlot(new AlexaOutputSlot("mp3", SkillConfig.getMp3ShowUrl()).formatAs(AlexaOutputFormat.AUDIO)) 31 | .build(); 32 | } 33 | } 34 | -------------------------------------------------------------------------------- /src/main/resources/in/de-DE/utterances.txt: -------------------------------------------------------------------------------- 1 | TreeOn an 2 | TreeOn er soll an sein 3 | TreeOn an sein 4 | TreeOn er soll an gehen 5 | TreeOn an gehen 6 | TreeOn anschalten 7 | TreeOn einschalten 8 | 9 | TreeOff aus 10 | TreeOff er soll aus sein 11 | TreeOff er soll aus gehen 12 | TreeOff aus gehen 13 | TreeOff aus sein 14 | TreeOff ausschalten 15 | 16 | TreeColor mach {Color} 17 | TreeColor {Color} 18 | TreeColor er soll {Color} werden 19 | TreeColor er soll auf {Color} schalten 20 | TreeColor er soll {Color} scheinen 21 | TreeColor nach {Color} 22 | TreeColor nach Farbe {Color} 23 | TreeColor {Color} sein 24 | TreeColor Farbe {Color} 25 | TreeColor mit Farbe {Color} 26 | TreeColor wechsele zu {Color} 27 | TreeColor wechsele nach {Color} 28 | TreeColor wechsele auf {Color} 29 | TreeColor wechsele Farbe zu {Color} 30 | TreeColor wechsele Farbe nach {Color} 31 | TreeColor wechsele Farbe auf {Color} 32 | TreeColor schalte zu {Color} 33 | TreeColor schalte nach {Color} 34 | TreeColor schalte auf {Color} 35 | TreeColor schalte Farbe zu {Color} 36 | TreeColor schalte Farbe nach {Color} 37 | TreeColor schalte Farbe auf {Color} 38 | TreeColor stelle Farbe zu {Color} 39 | TreeColor stelle Farbe nach {Color} 40 | TreeColor stelle Farbe auf {Color} 41 | TreeColor stelle zu {Color} 42 | TreeColor stelle nach {Color} 43 | TreeColor stelle auf {Color} 44 | TreeColor setze Farbe zu {Color} 45 | TreeColor setze Farbe nach {Color} 46 | TreeColor setze Farbe auf {Color} 47 | TreeColor setze zu {Color} 48 | TreeColor setze nach {Color} 49 | TreeColor setze auf {Color} 50 | 51 | XmasShow Show 52 | XmasShow Lichtershow 53 | XmasShow Revue 54 | XmasShow nach einer Show 55 | XmasShow nach einer Lichtershow 56 | XmasShow nach einer Revue 57 | XmasShow mit Show 58 | XmasShow mit Lichtershow 59 | XmasShow mit Revue 60 | XmasShow mit einer Show 61 | XmasShow mit einer Lichtershow 62 | XmasShow mit einer Revue -------------------------------------------------------------------------------- /src/main/java/io/klerch/alexa/xmastree/skill/handler/TreeColorHandler.java: -------------------------------------------------------------------------------- 1 | package io.klerch.alexa.xmastree.skill.handler; 2 | 3 | import io.klerch.alexa.state.handler.AWSDynamoStateHandler; 4 | import io.klerch.alexa.state.utils.AlexaStateException; 5 | import io.klerch.alexa.tellask.model.AlexaInput; 6 | import io.klerch.alexa.tellask.model.AlexaOutput; 7 | import io.klerch.alexa.tellask.model.AlexaOutputSlot; 8 | import io.klerch.alexa.tellask.schema.annotation.AlexaIntentListener; 9 | import io.klerch.alexa.tellask.schema.type.AlexaOutputFormat; 10 | import io.klerch.alexa.tellask.util.AlexaRequestHandlerException; 11 | import io.klerch.alexa.xmastree.skill.SkillConfig; 12 | import io.klerch.alexa.xmastree.skill.model.TreeState; 13 | 14 | @AlexaIntentListener(customIntents = "TreeColor", priority = 100) 15 | public class TreeColorHandler extends AbstractIntentHandler { 16 | private TreeState treeState; 17 | 18 | @Override 19 | public boolean verify(AlexaInput input) { 20 | treeState = new TreeState(input.getLocale(), input.getSlotValue("Color")); 21 | return treeState.getColor() != null; 22 | } 23 | 24 | @Override 25 | public AlexaOutput handleRequest(final AlexaInput input) throws AlexaRequestHandlerException, AlexaStateException { 26 | // send iot hook 27 | final AWSDynamoStateHandler dynamoHandler = new AWSDynamoStateHandler(input.getSessionStateHandler().getSession()); 28 | 29 | treeState.setState(TreeState.MODE.COLOR); 30 | 31 | // send state of tree to AWS IoT thing shadow 32 | sendIotHook(input, treeState); 33 | 34 | return AlexaOutput.tell("SayColorChange") 35 | .putState(treeState.withHandler(dynamoHandler)) 36 | .putSlot(new AlexaOutputSlot("mp3", SkillConfig.getMp3IntroUrl()).formatAs(AlexaOutputFormat.AUDIO)) 37 | .build(); 38 | } 39 | } 40 | -------------------------------------------------------------------------------- /arduino/xmastree/aws_iot_config.h: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2010-2016 Amazon.com, Inc. or its affiliates. All Rights Reserved. 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"). 5 | * You may not use this file except in compliance with the License. 6 | * A copy of the License is located at 7 | * 8 | * http://aws.amazon.com/apache2.0 9 | * 10 | * or in the "license" file accompanying this file. This file is distributed 11 | * on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either 12 | * express or implied. See the License for the specific language governing 13 | * permissions and limitations under the License. 14 | */ 15 | 16 | #ifndef config_usr_h 17 | #define config_usr_h 18 | 19 | // Copy and paste your configuration into this file 20 | //=============================================================== 21 | #define AWS_IOT_MQTT_HOST "a1b71mlxknck3o.iot.us-east-1.amazonaws.com" // your endpoint 22 | #define AWS_IOT_MQTT_PORT 443 // your port 23 | #define AWS_IOT_CLIENT_ID "ArduinoYun" // your client ID 24 | #define AWS_IOT_MY_THING_NAME "amzn1-ask-skill-0284eae8-a8b4-4a68-9965-2ee5ae52f910" // your thing name 25 | #define AWS_IOT_ROOT_CA_FILENAME "root-certificate.pem" // your root-CA filename 26 | #define AWS_IOT_CERTIFICATE_FILENAME "" // your certificate filename 27 | #define AWS_IOT_PRIVATE_KEY_FILENAME "" // your private key filename 28 | //=============================================================== 29 | // SDK config, DO NOT modify it 30 | #define AWS_IOT_PATH_PREFIX "../certs/" 31 | #define AWS_IOT_ROOT_CA_PATH AWS_IOT_PATH_PREFIX AWS_IOT_ROOT_CA_FILENAME // use this in config call 32 | #define AWS_IOT_CERTIFICATE_PATH AWS_IOT_PATH_PREFIX AWS_IOT_CERTIFICATE_FILENAME // use this in config call 33 | #define AWS_IOT_PRIVATE_KEY_PATH AWS_IOT_PATH_PREFIX AWS_IOT_PRIVATE_KEY_FILENAME // use this in config call 34 | 35 | #endif 36 | -------------------------------------------------------------------------------- /src/main/java/io/klerch/alexa/xmastree/skill/handler/LaunchHandler.java: -------------------------------------------------------------------------------- 1 | package io.klerch.alexa.xmastree.skill.handler; 2 | 3 | import io.klerch.alexa.state.handler.AWSDynamoStateHandler; 4 | import io.klerch.alexa.state.utils.AlexaStateException; 5 | import io.klerch.alexa.tellask.model.AlexaInput; 6 | import io.klerch.alexa.tellask.model.AlexaOutput; 7 | import io.klerch.alexa.tellask.model.AlexaOutputSlot; 8 | import io.klerch.alexa.tellask.schema.AlexaLaunchHandler; 9 | import io.klerch.alexa.tellask.schema.annotation.AlexaLaunchListener; 10 | import io.klerch.alexa.tellask.schema.type.AlexaOutputFormat; 11 | import io.klerch.alexa.tellask.util.AlexaRequestHandlerException; 12 | import io.klerch.alexa.xmastree.skill.SkillConfig; 13 | import io.klerch.alexa.xmastree.skill.model.TreeState; 14 | 15 | import static io.klerch.alexa.xmastree.skill.handler.AbstractIntentHandler.sendIotHook; 16 | 17 | @AlexaLaunchListener 18 | public class LaunchHandler implements AlexaLaunchHandler { 19 | 20 | public AlexaOutput handleRequest(final AlexaInput input) throws AlexaRequestHandlerException, AlexaStateException { 21 | final AWSDynamoStateHandler dynamoHandler = new AWSDynamoStateHandler(input.getSessionStateHandler().getSession()); 22 | final TreeState treeState = dynamoHandler.readModel(TreeState.class).orElse(new TreeState(input.getLocale(), TreeState.MODE.ON)); 23 | 24 | // set state on 25 | treeState.setState(TreeState.MODE.ON); 26 | 27 | // send state of tree to AWS IoT thing shadow 28 | sendIotHook(input, treeState); 29 | 30 | return AlexaOutput.ask("SayWelcome") 31 | .putState(treeState.withHandler(dynamoHandler)) 32 | .putSlot(new AlexaOutputSlot("mp3", SkillConfig.getMp3IntroUrl()).formatAs(AlexaOutputFormat.AUDIO)) 33 | .withReprompt(true) 34 | .build(); 35 | } 36 | 37 | public AlexaOutput handleError(final AlexaRequestHandlerException exception) { 38 | return AlexaOutput.tell("SaySorry").build(); 39 | } 40 | } 41 | -------------------------------------------------------------------------------- /src/main/java/io/klerch/alexa/xmastree/skill/model/TreeState.java: -------------------------------------------------------------------------------- 1 | package io.klerch.alexa.xmastree.skill.model; 2 | 3 | import io.klerch.alexa.state.model.AlexaScope; 4 | import io.klerch.alexa.state.model.AlexaStateIgnore; 5 | import io.klerch.alexa.state.model.AlexaStateModel; 6 | import io.klerch.alexa.state.model.AlexaStateSave; 7 | import io.klerch.alexa.tellask.schema.annotation.AlexaSlotSave; 8 | import io.klerch.alexa.xmastree.skill.utils.ColorUtil; 9 | 10 | import java.awt.*; 11 | 12 | @AlexaStateSave(Scope = AlexaScope.APPLICATION) 13 | public class TreeState extends AlexaStateModel { 14 | public enum MODE { 15 | ON, OFF, SHOW, STOP, COLOR 16 | } 17 | 18 | private Integer r; 19 | private Integer g; 20 | private Integer b; 21 | private String mode; 22 | @AlexaStateIgnore 23 | private Color rgb; 24 | @AlexaSlotSave(slotName = "color") 25 | private String color; 26 | @AlexaStateIgnore 27 | private final ColorUtil colorUtil; 28 | 29 | public TreeState() { 30 | this("de-DE"); 31 | } 32 | 33 | public TreeState(final String locale) { 34 | this(locale, "de-DE".equals(locale) ? "weiss" : "white"); 35 | } 36 | 37 | public TreeState(final String locale, final MODE mode) { 38 | this(locale); 39 | setState(mode); 40 | } 41 | 42 | public TreeState(final String locale, final String color) { 43 | this.colorUtil = new ColorUtil(locale); 44 | this.rgb = colorUtil.getColor(color).orElse(null); 45 | if (this.rgb != null) { 46 | this.r = rgb.getRed(); 47 | this.b = rgb.getBlue(); 48 | this.g = rgb.getGreen(); 49 | this.color = color; 50 | } 51 | setState(MODE.COLOR); 52 | } 53 | 54 | public Color getRgb() { 55 | if (rgb == null) { 56 | rgb = new Color(r, g, b); 57 | } 58 | return rgb; 59 | } 60 | 61 | public String getColor() { 62 | return color; 63 | } 64 | 65 | public void setState(final MODE mode) { 66 | this.mode = mode.toString(); 67 | } 68 | } 69 | -------------------------------------------------------------------------------- /src/main/java/io/klerch/alexa/xmastree/skill/SkillConfig.java: -------------------------------------------------------------------------------- 1 | package io.klerch.alexa.xmastree.skill; 2 | 3 | import java.io.IOException; 4 | import java.io.InputStream; 5 | import java.util.Properties; 6 | 7 | /** 8 | * Encapsulates access to application-wide property values 9 | */ 10 | public class SkillConfig { 11 | private static Properties properties = new Properties(); 12 | private static final String defaultPropertiesFile = "app.properties"; 13 | private static final String customPropertiesFile = "my.app.properties"; 14 | 15 | /** 16 | * Static block does the bootstrapping of all configuration properties with 17 | * reading out values from different resource files 18 | */ 19 | static { 20 | final String propertiesFile = 21 | SkillConfig.class.getClassLoader().getResource(customPropertiesFile) != null ? 22 | customPropertiesFile : defaultPropertiesFile; 23 | final InputStream propertiesStream = SkillConfig.class.getClassLoader().getResourceAsStream(propertiesFile); 24 | try { 25 | properties.load(propertiesStream); 26 | } catch (IOException e) { 27 | e.printStackTrace(); 28 | } finally { 29 | if (propertiesStream != null) { 30 | try { 31 | propertiesStream.close(); 32 | } catch (IOException e) { 33 | e.printStackTrace(); 34 | } 35 | } 36 | } 37 | } 38 | 39 | public static String getAlexaAppId() { 40 | return properties.getProperty("AlexaAppId"); 41 | } 42 | 43 | public static String getS3BucketUrl() { 44 | return properties.getProperty("S3BucketUrl"); 45 | } 46 | 47 | public static String getMp3IntroUrl() { 48 | return getS3BucketUrl() + properties.getProperty("Mp3FileIntro"); 49 | } 50 | 51 | public static String getMp3ColorUrl() { 52 | return getS3BucketUrl() + properties.getProperty("Mp3FileColor"); 53 | } 54 | 55 | public static String getMp3ShowUrl() { 56 | return getS3BucketUrl() + properties.getProperty("Mp3FileShow"); 57 | } 58 | } 59 | -------------------------------------------------------------------------------- /pom.xml: -------------------------------------------------------------------------------- 1 | 2 | 5 | 4.0.0 6 | 7 | io.klerch 8 | alexa-xmas-tree 9 | 1.0.0 10 | 11 | 12 | 2.16 13 | UTF-8 14 | 0.2.3 15 | 1.11.236 16 | 1.1.0 17 | 1.0.0 18 | 4.12 19 | 2.8.2 20 | 21 | 22 | 23 | 24 | io.lerch.repo 25 | s3://io.klerch.maven.repo/snapshot 26 | 27 | 28 | io.lerch.repo 29 | s3://io.klerch.maven.repo/release 30 | 31 | 32 | 33 | 34 | 35 | 36 | org.kuali.maven.wagons 37 | maven-s3-wagon 38 | 1.2.1 39 | 40 | 41 | 42 | 43 | src/main/resources 44 | true 45 | 46 | 47 | 48 | 49 | maven-compiler-plugin 50 | 3.5.1 51 | 52 | 1.8 53 | 1.8 54 | 55 | 56 | 57 | org.apache.maven.plugins 58 | maven-shade-plugin 59 | 2.4.3 60 | 61 | false 62 | 63 | 64 | 65 | package 66 | 67 | shade 68 | 69 | 70 | 71 | 72 | 73 | 74 | 75 | 76 | 77 | com.fasterxml.jackson.core 78 | jackson-core 79 | ${jackson.version} 80 | 81 | 82 | com.fasterxml.jackson.core 83 | jackson-databind 84 | ${jackson.version} 85 | 86 | 87 | com.fasterxml.jackson.core 88 | jackson-annotations 89 | ${jackson.version} 90 | 91 | 92 | com.amazonaws 93 | aws-lambda-java-core 94 | ${aws.lambda.version} 95 | 96 | 97 | io.klerch 98 | alexa-skills-kit-tellask-java 99 | ${io.klerch.tellask.version} 100 | 101 | 102 | com.amazonaws 103 | aws-java-sdk-iot 104 | ${aws.version} 105 | 106 | 107 | com.amazonaws 108 | aws-lambda-java-log4j 109 | ${aws.log4j.version} 110 | 111 | 112 | com.amazonaws 113 | aws-java-sdk-dynamodb 114 | ${aws.version} 115 | 116 | 117 | com.amazonaws 118 | aws-java-sdk-kms 119 | 120 | 121 | 122 | 123 | junit 124 | junit 125 | ${junit.version} 126 | test 127 | 128 | 129 | -------------------------------------------------------------------------------- /arduino/xmastree/xmastree.ino: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include 4 | #include "aws_iot_config.h" 5 | 6 | #define PIN 6 7 | #define NUM_LEDS 100 8 | #define BRIGHTNESS 100 9 | 10 | Adafruit_NeoPixel strip = Adafruit_NeoPixel(NUM_LEDS, PIN, NEO_RGB + NEO_KHZ800); 11 | aws_iot_mqtt_client myClient; // init iot_mqtt_client 12 | 13 | char msg[32]; // read-write buffer 14 | int cnt = 0; // loop counts 15 | int numYieldFailed = 0; 16 | int rc = -100; // return value placeholder 17 | bool success_connect = false; // whether it is connected 18 | char JSON_buf[100]; 19 | 20 | void setup() { 21 | // Start Serial for print-out and wait until it's ready 22 | Serial.begin(115200); 23 | //while(!Serial); 24 | 25 | strip.setBrightness(BRIGHTNESS); 26 | strip.begin(); 27 | strip.show(); 28 | // initial color set is orange 29 | changeColorBackwards(160, 52, 3, 30); 30 | strip.show(); 31 | 32 | char curr_version[80]; 33 | snprintf_P(curr_version, 80, PSTR("AWS IoT SDK Version(dev) %d.%d.%d-%s\n"), VERSION_MAJOR, VERSION_MINOR, VERSION_PATCH, VERSION_TAG); 34 | Serial.println(curr_version); 35 | 36 | while(success_connect == false) { 37 | connect(); 38 | } 39 | } 40 | 41 | void reconnect() { 42 | myClient.disconnect(); 43 | success_connect = false; 44 | while(success_connect == false) { 45 | connect(); 46 | } 47 | } 48 | 49 | void connect() { 50 | // append random number to client id so that it won't clash with the last connection 51 | Serial.println("Try connect with client-id: " + String(AWS_IOT_CLIENT_ID)); 52 | 53 | if((rc = myClient.setup(AWS_IOT_CLIENT_ID, true, MQTTv311, true)) == 0) { 54 | if((rc = myClient.configWss(AWS_IOT_MQTT_HOST, AWS_IOT_MQTT_PORT, AWS_IOT_ROOT_CA_PATH)) == 0) { 55 | if((rc = myClient.connect()) == 0) { 56 | success_connect = true; 57 | 58 | light_status_led(0, 255, 0, 1000); 59 | 60 | print_log("shadow init", myClient.shadow_init(AWS_IOT_MY_THING_NAME)); 61 | print_log("register thing shadow delta function", myClient.shadow_register_delta_func(AWS_IOT_MY_THING_NAME, msg_callback_delta)); 62 | } 63 | else { 64 | // blink tw0 times red to indiciate config failed 65 | light_status_led(255, 0, 0, 330); 66 | delay(330); 67 | light_status_led(255, 0, 0, 330); 68 | Serial.println(F("Connect failed!")); 69 | Serial.println(rc); 70 | } 71 | } 72 | else { 73 | // blink three times red to indiciate config failed 74 | light_status_led(255, 0, 0, 200); 75 | delay(200); 76 | light_status_led(255, 0, 0, 200); 77 | delay(200); 78 | light_status_led(255, 0, 0, 200); 79 | 80 | Serial.println(F("Config failed!")); 81 | Serial.println(rc); 82 | } 83 | } 84 | else { 85 | light_status_led(255, 0, 0, 1000); 86 | Serial.println(F("Setup failed!")); 87 | Serial.println(rc); 88 | } 89 | // Delay to make sure SUBACK is received, delay time could vary according to the server 90 | delay(2000); 91 | } 92 | 93 | void loop() { 94 | if(success_connect) { 95 | if(myClient.yield()) { 96 | light_status_led(124, 114, 32, 500); // indicate with yellow 97 | Serial.println(F("Yield failed.")); 98 | if (numYieldFailed++ > 9) { 99 | // only reconnect if ten invalid attempts in a row 100 | reconnect(); 101 | } 102 | } 103 | else { 104 | light_status_led(0, 0, 255, 500); 105 | // reset error counter 106 | numYieldFailed = 0; 107 | } 108 | delay(500); 109 | } 110 | } 111 | 112 | // the first led of the strand is to show runtime state 113 | void light_status_led(int r, int g, int b, int delayMs) { 114 | strip.setPixelColor(0, strip.Color(r, g, b)); 115 | strip.show(); 116 | delay(delayMs); 117 | strip.setPixelColor(0, strip.Color(0, 0, 0)); 118 | strip.show(); 119 | } 120 | 121 | bool print_log(const char* src, int code) { 122 | bool ret = true; 123 | if(code == 0) { 124 | #ifdef AWS_IOT_DEBUG 125 | Serial.print(F("[LOG] command: ")); 126 | Serial.print(src); 127 | Serial.println(F(" completed.")); 128 | #endif 129 | ret = true; 130 | } 131 | else { 132 | #ifdef AWS_IOT_DEBUG 133 | Serial.print(F("[ERR] command: ")); 134 | Serial.print(src); 135 | Serial.print(F(" code: ")); 136 | Serial.println(code); 137 | #endif 138 | ret = false; 139 | } 140 | Serial.flush(); 141 | return ret; 142 | } 143 | 144 | void msg_callback_delta(char* src, unsigned int len, Message_status_t flag) { 145 | Serial.println(F("Message arrived.")); 146 | if(flag == STATUS_NORMAL) { 147 | print_log("getDeltaKeyValue", myClient.getDeltaValueByKey(src, "io.klerch.alexa.xmastree.skill.model.TreeState\"r", JSON_buf, 50)); 148 | int r = (String(JSON_buf)).toInt(); 149 | Serial.println(r); 150 | 151 | print_log("getDeltaKeyValue", myClient.getDeltaValueByKey(src, "io.klerch.alexa.xmastree.skill.model.TreeState\"g", JSON_buf, 50)); 152 | int g = (String(JSON_buf)).toInt(); 153 | Serial.println(g); 154 | 155 | print_log("getDeltaKeyValue", myClient.getDeltaValueByKey(src, "io.klerch.alexa.xmastree.skill.model.TreeState\"b", JSON_buf, 50)); 156 | int b = (String(JSON_buf)).toInt(); 157 | Serial.println(b); 158 | 159 | print_log("getDeltaKeyValue", myClient.getDeltaValueByKey(src, "io.klerch.alexa.xmastree.skill.model.TreeState\"mode", JSON_buf, 50)); 160 | String mode = String(JSON_buf); 161 | Serial.println(mode); 162 | 163 | //print_log("getDeltaKeyValue", myClient.getDeltaValueByKey(src, "", JSON_buf, 100)); 164 | //String payload = "{\"state\":{\"reported\":"; 165 | //payload += JSON_buf; 166 | //payload += "}}"; 167 | //payload.toCharArray(JSON_buf, 100); 168 | //print_log("update thing shadow", myClient.shadow_update(AWS_IOT_MY_THING_NAME, JSON_buf, strlen(JSON_buf), NULL, 5)); 169 | 170 | if (mode == "COLOR") { 171 | //changeColorBackwards(0, 0, 0, 30); 172 | changeColor(r, g, b, 300); 173 | } 174 | else if (mode == "ON") { 175 | changeColor(r, g, b, 300); 176 | } 177 | else if (mode == "OFF") { 178 | changeColorBackwards(0, 0, 0, 50); 179 | } 180 | else if (mode == "SHOW") { 181 | long showTime = 0; 182 | while(showTime < 50000) { 183 | int to = random(5, strip.numPixels()); 184 | showTime = showTime + (10 * to); 185 | changeColorPartial(random(0, 255), random(0, 255), random(0, 255), 10, to); 186 | 187 | Serial.println(showTime); 188 | 189 | int to2 = random(5, strip.numPixels()); 190 | showTime = showTime + (10 * to2); 191 | changeColorBackwardsPartial(random(0, 255), random(0, 255), random(0, 255), 10, to2); 192 | 193 | Serial.println(showTime); 194 | } 195 | Serial.println(showTime); 196 | changeColor(r, g, b, 15); 197 | } 198 | else if (mode == "STOP") { 199 | Serial.println("Would stop show because SHOW."); 200 | changeColor(r, g, b, 0); 201 | } 202 | else { 203 | Serial.println("Unexpected mode given - do nothing."); 204 | } 205 | } 206 | } 207 | 208 | void changeColor(uint8_t r, uint8_t g, uint8_t b, int delayMs) 209 | { 210 | changeColorPartial(r, g, b, delayMs, strip.numPixels()); 211 | } 212 | 213 | void changeColorPartial(uint8_t r, uint8_t g, uint8_t b, int delayMs, int to) 214 | { 215 | for(uint8_t i=0; i < to; i++) { 216 | // look for colorful 217 | if (r == 1 && g == 1 && b == 1) { 218 | strip.setPixelColor(i, strip.Color(random(0, 255), random(0, 255), random(0, 255))); 219 | } 220 | else { 221 | strip.setPixelColor(i, strip.Color(r, g, b)); 222 | } 223 | if (delayMs > 0) { 224 | delay(delayMs); 225 | strip.show(); 226 | } 227 | } 228 | strip.show(); 229 | } 230 | 231 | void randomColors() 232 | { 233 | for(uint8_t i=0; i to; i--) { 251 | // look for colorful 252 | if (r == 1 && g == 1 && b == 1) { 253 | strip.setPixelColor(i, strip.Color(random(0, 255), random(0, 255), random(0, 255))); 254 | } 255 | else { 256 | strip.setPixelColor(i, strip.Color(r, g, b)); 257 | } 258 | 259 | if (delayMs > 0) { 260 | delay(delayMs); 261 | strip.show(); 262 | } 263 | } 264 | strip.show(); 265 | } 266 | 267 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | ## An Alexa-controlled Christmas Tree 2 | 3 | See it live in action: [Demo video](https://www.youtube.com/watch?v=acqHYu_rtrA) 4 | ![](docs/hardware-setup.png) 5 | 6 | Here you can find the sources of a custom Alexa skill that controls an LED strand on a Christmas 7 | tree. This repo contains: 8 | * Java code for an AWS Lambda function which is the endpoint for the Alexa skill 9 | * An Arduino sketch which sits between AWS IoT and a WS2811 LED strand. The sketch is optimized for running on an Arduino Yun. 10 | 11 | #### The Hardware setup 12 | If you want to build your own Alexa-controlled christmas tree with help of these sources you need specific hardware. This project used the 13 | following components: 14 | * 1 x [Arduino Yun with Linino OS](https://www.arduino.cc/en/Main/ArduinoBoardYun) 15 | * 2 x [WS2811 LED Strand with 50 LEDs each](https://www.amazon.com/dp/B013FU2PZ4) 16 | * 3 x [Jumper wires](https://www.amazon.com/dp/B01EV70C78) 17 | * 1 x [Power Supply Adapter and 2.1mm x 5.5mm DC Connector](https://www.amazon.com/dp/B00ZU9MZ1S) 18 | * 1 x [Micro-USB to USB Cable](https://www.amazon.com/dp/B00NH13O7K) 19 | * 1 x [Amazon Echo](https://www.amazon.com/dp/B00X4WHP5E) or [Amazon Dot](https://www.amazon.com/dp/B015TJD0Y4) or [Amazon Tap](https://www.amazon.com/dp/B01BH83OOM) 20 | 21 | #### The Software solution 22 | The following image illustrates a typical roundtrip to handle a voice user request. 23 | 24 | ![](docs/solution-architecture.png) 25 | 26 | The solution leverages a bunch of AWS cloud services to communicate with the hardware backend - the 27 | christmas tree. The only things you really need to set up is the Lambda function, an S3 bucket containing 28 | the MP3 files and an IAM role with AWS IoT and Dynamo permissions. The table in Dynamo as well as 29 | the thing shadow in AWS IoT will be created on the first skill invocation on the fly. 30 | 31 | Understand what happens on a voice user request given to an Alexa device: 32 | 33 | 1. User speaks to Alexa to _"open the christmas tree"_. ASR and NLU magic happens in the Alexa cloud service. 34 | 35 | 2. An intent is given to the skill code hosted in AWS Lambda. You can find the code in this repo. 36 | 37 | 3. If the user just desires an action like _"turn on the tree"_ or _"start the show"_ without giving 38 | this skill a color for the tree it looks up the last set color in Dynamo DB. If there's a color 39 | given the skill will persist the information in the same table. This is how Alexa keeps in mind the last set color 40 | of the tree. Secondly, the action and the color command is written to a thing shadow in AWS IoT. 41 | 42 | 4. If the shadow is updated an MQTT message is exposed to the delta topic of the corresponding thing. The Arduino Yun 43 | is subscribed to that topic. Side note: The name of the thing being created by the skill code is equal 44 | to the skill-id coming in (all dots replaced with a dash). This might help you if you want to rebuild the project. 45 | 46 | 5. The Arduino is polling on the Delta topic so it receives the commands as an MQTT message in JSON format. 47 | The information is extracted and the Arduino sketch performs an action with the LED strand according to what is given in the message (new color, christmas show, on, off). 48 | 49 | 6. Finally, the Arduino sends an MQTT message to the Update topic of the AWS IoT thing in order to let the world know 50 | that the action was performed. 51 | 52 | 7. The message is consumed by the AWS IoT service and the contained state information 53 | is written back to the thing shadow as a _reported_ state. It would be possible to also have the skill read the last tree state from the thing shadow 54 | instead of looking it up in Dynamo DB. The reason for this fallback approach is MQTT is asynchronous and we cannot rely on 55 | the Arduino to give an immediate response. 56 | 57 | 8. Actually this step happens right after step 3) as the skill is decoupled from the hardware backend on purpose. 58 | So right after updating the thing shadow in AWS IoT the skill code returns output speech text and optionally an 59 | _SSML_ tag with audio contents. The MP3s which are part of Alexa's playback (christmas sounds) are stored in an AWS S3 bucket. 60 | 61 | 9. Alexa reads out the text returned by the skill and plays back the audio in the response. 62 | 63 | #### Status indication 64 | While Arduino does its work it lets you know of its current state over the first LED in the strand. 65 | * a one time red blinking light indicates a AWS IoT connection setup failure 66 | * a two times red blinking light indicates a failed AWS IoT connection attempt 67 | * a three times red blinking light indicates a failed AWS IoT connection configuration 68 | * a green flashlight indicates a successful connection to AWS IoT 69 | * a blue flashlight indicates constant polling to the AWS IoT topic 70 | * a yellow flashlight indicates an error while polling the AWS IoT topic 71 | 72 | On startup you might see red flashlights for the period of time it takes for the Arduino to connect to 73 | the Wifi. If Wifi is connected there's the green flashlight followed by a constantly blinking blue light to 74 | indicate the tree is ready for commands. 75 | 76 | If yellow is blinking the AWS IoT topic could not be reached. If that happens (e.g. Arduino lost Wifi connection) 77 | it keeps trying for nine more times until it automatically tries to reconnect. That said, after ten times yellow flashlight 78 | there should be red / green flashlight for reconnection progress. Once the Arduino reconnects to the Wifi and AWS IoT 79 | is reached again, the blue flashlights come up. 80 | 81 | ### How to rebuild 82 | If you have any question reach out to the author via 83 | 84 | [![Join the chat at https://gitter.im/alexa-xmas-tree/Lobby](https://badges.gitter.im/alexa-xmas-tree/Lobby.svg)](https://gitter.im/alexa-xmas-tree/Lobby?utm_source=badge&utm_medium=badge&utm_campaign=pr-badge&utm_content=badge) 85 | 86 | These are the requirements in order to be able to rebuild the solution 87 | * All the hardware listed in above section 88 | * An [AWS account](https://aws.amazon.com) for the backend resources 89 | * An [Amazon developer account](https://developer.amazon.com) 90 | 91 | 1. Check out this repository using git 92 | 93 | 2. In [Amazon developer account](https://developer.amazon.com) after you logged in go to Alexa > Alexa Skills Kit > Add a new skill 94 | 95 | 3. Set up the _Skill information_ with 96 | * __Skill type__: Custom interaction model 97 | * __Language__: Of your choice (you could add more later on) 98 | * __Name__: Christmas Tree (but feel free to choose another name) 99 | * __Invocation Name__: christmas tree (for English) or weihnachtsbaum (for German) 100 | 101 | 4. Click _Next_ and go back to _Skill information_ to copy the _ApplicationId_. You will need it in the next steps 102 | 103 | 5. In your [AWS account](https://aws.amazon.com) go to S3 and create a new bucket and name it as you like 104 | 105 | 6. Upload all the MP3 files you can find in the _audio_ folder to the newly created bucket. Secondly, select the freshly uploaded files in the S3 web interface, go to _Actions_ and _Make Public_. Finally, 106 | in _Properties_ of your bucket go to _Permissions_ and _Add CORS configuration_ where you would paste and save this xml portion: 107 | 108 | ```xml 109 | 110 | 111 | http://ask-ifr-download.s3.amazonaws.com 112 | GET 113 | 114 | 115 | ``` 116 | 117 | 7. Next, in AWS go to IAM and create a new role called something like _"Xmas-Lambda-Execution"_. To keep things simple just add these three managed policies to it (later you would 118 | go for the least privileges approach and adjust permission as needed) 119 | * AmazonS3FullAccess 120 | * AmazonDynamoDBFullAccess 121 | * AWSIoTFullAccess 122 | 123 | 8. Create a group in IAM with name _"Xmas-Arduino-Access"_ and add AWSIoTFullAccess as the managed policy. 124 | 125 | 9. Create a user in IAM for programmatic access and add it to the _"Xmas-Arduino-Access"_ group. Save the credentials (AccessId and AccessSecret) provided at the end of the creation process. 126 | 127 | 10. Now edit the first two configuration items in the [app.properties](/src/main/resources/app.properties) file in your local repo. 128 | * __AlexaAppId__ should be the the applicationId you got in step 4. 129 | * __S3BucketUrl__ should be the url to the bucket you created in step 5. 130 | 131 | 11. Now _mvn package_ the project and you should get a JAR file. 132 | 133 | 12. In AWS create a new Lambda function for Java8 and choose the existing execution role you created in step 7. Upload the JAR file to this function. The Lambda event trigger must be set to _Alexa skills Kit_. If you don't see it in the list you are in the wrong AWS region. Make sure to create the Lambda function in either Ireland or N. Virginia. If you're using my code the handler must be set to _io.klerch.alexa.xmastree.skill.XmasSpeechletHandler_. 134 | 135 | 13. Go back to your skill in the [Amazon developer console](https://developer.amazon.com) and proceed with the skill configuration. 136 | * The _Interaction model_ section you set up just be copy and paste what you can find in the [resources/in](/src/main/java/resources/in)-folder in this repo. Don't forget to create a custom slot with name _TreeColors_ and add values of one of the customSlot-TreeColors.txt files. 137 | * In the _Configuration_ section you point to your Lambda function you created in step 12. The ARN can be obtained in the top right corner in AWS Lambda details page. 138 | * In _Test_ section just make sure test is _enabled_ 139 | * Execute a test in the _Service simulator_ in the _Test_ section. Enter the utterance _Start christmas tree_ (_Starte Weihnachtsbaum_ for German). The first invocation takes 140 | its time because what happenes in the background is an AWS Dynamo-table is built and an AWS IoT thing is created as well. This initial test 141 | is mandatory for the last step because the Arduino needs to have a connection to an MQTT topic which is created with the IoT thing. 142 | 143 | 14. Next, set up your Arduino Yun and connect it to a Wifi (Howto: https://www.arduino.cc/en/Guide/ArduinoYun) 144 | 145 | 15. Connect your WS2811 LED strand to the Arduino. Make sure you use the data-pin 6. If you have any question on this please contact me. 146 | 147 | 16. Follow the installation instructions at https://github.com/aws/aws-iot-device-sdk-arduino-yun. This project uses 148 | MQTT over WebSockets. Keep that in mind when you are reading and doing the instructions. The Access key and secret they are 149 | talking about are the ones you got in step 9. 150 | 151 | 17. Edit the [aws_iot_config.h](/arduino/xmastree/aws_iot_config.h) in your local repo and adjust values for: 152 | * __AWS_IOT_MQTT_HOST__ is specific to your account. The easiest way to obtain your endpoint is to use the following command in [AWS CLI](https://aws.amazon.com/cli/): 153 | ```bash 154 | $ aws iot describe-endpoint 155 | ``` 156 | * __AWS_IOT_MY_THING_NAME__ needs to be the applicationId you got in step 4. Make sure you replace all dots in that id with dashes. 157 | * __AWS_IOT_ROOT_CA_FILENAME__ name of the root certificate you uploaded to the Arduino as instructed in step 14. 158 | 159 | 18. With Arduino IDE upload the sketch you can find in the [arduino](/arduino)-folder in this repo. 160 | * You should see progress and status looking at the first LED on the strand. If your Arduino is still connected 161 | to your computer you could also use the _Service Monitor_ tool to get detailed information and serial output. 162 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | Apache License 2 | Version 2.0, January 2004 3 | http://www.apache.org/licenses/ 4 | 5 | TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION 6 | 7 | 1. Definitions. 8 | 9 | "License" shall mean the terms and conditions for use, reproduction, 10 | and distribution as defined by Sections 1 through 9 of this document. 11 | 12 | "Licensor" shall mean the copyright owner or entity authorized by 13 | the copyright owner that is granting the License. 14 | 15 | "Legal Entity" shall mean the union of the acting entity and all 16 | other entities that control, are controlled by, or are under common 17 | control with that entity. For the purposes of this definition, 18 | "control" means (i) the power, direct or indirect, to cause the 19 | direction or management of such entity, whether by contract or 20 | otherwise, or (ii) ownership of fifty percent (50%) or more of the 21 | outstanding shares, or (iii) beneficial ownership of such entity. 22 | 23 | "You" (or "Your") shall mean an individual or Legal Entity 24 | exercising permissions granted by this License. 25 | 26 | "Source" form shall mean the preferred form for making modifications, 27 | including but not limited to software source code, documentation 28 | source, and configuration files. 29 | 30 | "Object" form shall mean any form resulting from mechanical 31 | transformation or translation of a Source form, including but 32 | not limited to compiled object code, generated documentation, 33 | and conversions to other media types. 34 | 35 | "Work" shall mean the work of authorship, whether in Source or 36 | Object form, made available under the License, as indicated by a 37 | copyright notice that is included in or attached to the work 38 | (an example is provided in the Appendix below). 39 | 40 | "Derivative Works" shall mean any work, whether in Source or Object 41 | form, that is based on (or derived from) the Work and for which the 42 | editorial revisions, annotations, elaborations, or other modifications 43 | represent, as a whole, an original work of authorship. For the purposes 44 | of this License, Derivative Works shall not include works that remain 45 | separable from, or merely link (or bind by name) to the interfaces of, 46 | the Work and Derivative Works thereof. 47 | 48 | "Contribution" shall mean any work of authorship, including 49 | the original version of the Work and any modifications or additions 50 | to that Work or Derivative Works thereof, that is intentionally 51 | submitted to Licensor for inclusion in the Work by the copyright owner 52 | or by an individual or Legal Entity authorized to submit on behalf of 53 | the copyright owner. For the purposes of this definition, "submitted" 54 | means any form of electronic, verbal, or written communication sent 55 | to the Licensor or its representatives, including but not limited to 56 | communication on electronic mailing lists, source code control systems, 57 | and issue tracking systems that are managed by, or on behalf of, the 58 | Licensor for the purpose of discussing and improving the Work, but 59 | excluding communication that is conspicuously marked or otherwise 60 | designated in writing by the copyright owner as "Not a Contribution." 61 | 62 | "Contributor" shall mean Licensor and any individual or Legal Entity 63 | on behalf of whom a Contribution has been received by Licensor and 64 | subsequently incorporated within the Work. 65 | 66 | 2. Grant of Copyright License. Subject to the terms and conditions of 67 | this License, each Contributor hereby grants to You a perpetual, 68 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 69 | copyright license to reproduce, prepare Derivative Works of, 70 | publicly display, publicly perform, sublicense, and distribute the 71 | Work and such Derivative Works in Source or Object form. 72 | 73 | 3. Grant of Patent License. Subject to the terms and conditions of 74 | this License, each Contributor hereby grants to You a perpetual, 75 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 76 | (except as stated in this section) patent license to make, have made, 77 | use, offer to sell, sell, import, and otherwise transfer the Work, 78 | where such license applies only to those patent claims licensable 79 | by such Contributor that are necessarily infringed by their 80 | Contribution(s) alone or by combination of their Contribution(s) 81 | with the Work to which such Contribution(s) was submitted. If You 82 | institute patent litigation against any entity (including a 83 | cross-claim or counterclaim in a lawsuit) alleging that the Work 84 | or a Contribution incorporated within the Work constitutes direct 85 | or contributory patent infringement, then any patent licenses 86 | granted to You under this License for that Work shall terminate 87 | as of the date such litigation is filed. 88 | 89 | 4. Redistribution. You may reproduce and distribute copies of the 90 | Work or Derivative Works thereof in any medium, with or without 91 | modifications, and in Source or Object form, provided that You 92 | meet the following conditions: 93 | 94 | (a) You must give any other recipients of the Work or 95 | Derivative Works a copy of this License; and 96 | 97 | (b) You must cause any modified files to carry prominent notices 98 | stating that You changed the files; and 99 | 100 | (c) You must retain, in the Source form of any Derivative Works 101 | that You distribute, all copyright, patent, trademark, and 102 | attribution notices from the Source form of the Work, 103 | excluding those notices that do not pertain to any part of 104 | the Derivative Works; and 105 | 106 | (d) If the Work includes a "NOTICE" text file as part of its 107 | distribution, then any Derivative Works that You distribute must 108 | include a readable copy of the attribution notices contained 109 | within such NOTICE file, excluding those notices that do not 110 | pertain to any part of the Derivative Works, in at least one 111 | of the following places: within a NOTICE text file distributed 112 | as part of the Derivative Works; within the Source form or 113 | documentation, if provided along with the Derivative Works; or, 114 | within a display generated by the Derivative Works, if and 115 | wherever such third-party notices normally appear. The contents 116 | of the NOTICE file are for informational purposes only and 117 | do not modify the License. You may add Your own attribution 118 | notices within Derivative Works that You distribute, alongside 119 | or as an addendum to the NOTICE text from the Work, provided 120 | that such additional attribution notices cannot be construed 121 | as modifying the License. 122 | 123 | You may add Your own copyright statement to Your modifications and 124 | may provide additional or different license terms and conditions 125 | for use, reproduction, or distribution of Your modifications, or 126 | for any such Derivative Works as a whole, provided Your use, 127 | reproduction, and distribution of the Work otherwise complies with 128 | the conditions stated in this License. 129 | 130 | 5. Submission of Contributions. Unless You explicitly state otherwise, 131 | any Contribution intentionally submitted for inclusion in the Work 132 | by You to the Licensor shall be under the terms and conditions of 133 | this License, without any additional terms or conditions. 134 | Notwithstanding the above, nothing herein shall supersede or modify 135 | the terms of any separate license agreement you may have executed 136 | with Licensor regarding such Contributions. 137 | 138 | 6. Trademarks. This License does not grant permission to use the trade 139 | names, trademarks, service marks, or product names of the Licensor, 140 | except as required for reasonable and customary use in describing the 141 | origin of the Work and reproducing the content of the NOTICE file. 142 | 143 | 7. Disclaimer of Warranty. Unless required by applicable law or 144 | agreed to in writing, Licensor provides the Work (and each 145 | Contributor provides its Contributions) on an "AS IS" BASIS, 146 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or 147 | implied, including, without limitation, any warranties or conditions 148 | of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A 149 | PARTICULAR PURPOSE. You are solely responsible for determining the 150 | appropriateness of using or redistributing the Work and assume any 151 | risks associated with Your exercise of permissions under this License. 152 | 153 | 8. Limitation of Liability. In no event and under no legal theory, 154 | whether in tort (including negligence), contract, or otherwise, 155 | unless required by applicable law (such as deliberate and grossly 156 | negligent acts) or agreed to in writing, shall any Contributor be 157 | liable to You for damages, including any direct, indirect, special, 158 | incidental, or consequential damages of any character arising as a 159 | result of this License or out of the use or inability to use the 160 | Work (including but not limited to damages for loss of goodwill, 161 | work stoppage, computer failure or malfunction, or any and all 162 | other commercial damages or losses), even if such Contributor 163 | has been advised of the possibility of such damages. 164 | 165 | 9. Accepting Warranty or Additional Liability. While redistributing 166 | the Work or Derivative Works thereof, You may choose to offer, 167 | and charge a fee for, acceptance of support, warranty, indemnity, 168 | or other liability obligations and/or rights consistent with this 169 | License. However, in accepting such obligations, You may act only 170 | on Your own behalf and on Your sole responsibility, not on behalf 171 | of any other Contributor, and only if You agree to indemnify, 172 | defend, and hold each Contributor harmless for any liability 173 | incurred by, or claims asserted against, such Contributor by reason 174 | of your accepting any such warranty or additional liability. 175 | 176 | END OF TERMS AND CONDITIONS 177 | 178 | APPENDIX: How to apply the Apache License to your work. 179 | 180 | To apply the Apache License to your work, attach the following 181 | boilerplate notice, with the fields enclosed by brackets "{}" 182 | replaced with your own identifying information. (Don't include 183 | the brackets!) The text should be enclosed in the appropriate 184 | comment syntax for the file format. We also recommend that a 185 | file or class name and description of purpose be included on the 186 | same "printed page" as the copyright notice for easier 187 | identification within third-party archives. 188 | 189 | Copyright 2017 Kay Lerch 190 | 191 | Licensed under the Apache License, Version 2.0 (the "License"); 192 | you may not use this file except in compliance with the License. 193 | You may obtain a copy of the License at 194 | 195 | http://www.apache.org/licenses/LICENSE-2.0 196 | 197 | Unless required by applicable law or agreed to in writing, software 198 | distributed under the License is distributed on an "AS IS" BASIS, 199 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 200 | See the License for the specific language governing permissions and 201 | limitations under the License. 202 | --------------------------------------------------------------------------------