// Blind: a roller blind that Alexa opens, closes and sets to a position (Alexa.RangeController with semantics). // // The capability is one instance of the RangeController, "Blind.Lift": 0 is closed, 100 is open. Its semantics // tell Alexa what "open", "close", "raise" and "lower" mean for it, and which values count as open and closed. // SetRangeValue outside of 0 to 100 is answered with VALUE_OUT_OF_RANGE; AdjustRangeValue stops at the ends. // // The blind of this sketch is a number. moveTo() is the place for the motor. #include #include #include // Wi-Fi and the credentials of your Alex2MQTT account const char *WIFI_SSID = ""; const char *WIFI_PASSWORD = ""; const char *ALEXA_USERNAME = ""; const char *ALEXA_PASSWORD = ""; const char *ALEXA_ROOT_TOPIC = ""; const char LIFT[] = "Blind.Lift"; Alex2ESP alexa; int lift = 0; // percent open // Joins the Wi-Fi network and returns after 30 s at the latest. Without a connection the sketch carries on: the // ESP8266 keeps trying, and alexa.loop() opens the MQTT session once Wi-Fi is up. void connectWiFi() { WiFi.mode(WIFI_STA); WiFi.begin(WIFI_SSID, WIFI_PASSWORD); Serial.printf("\n[WIFI] Connecting to \"%s\"\n", WIFI_SSID); unsigned long started = millis(); while (WiFi.status() != WL_CONNECTED && millis() - started < 30000) { delay(100); } if (WiFi.status() == WL_CONNECTED) { Serial.printf("[WIFI] Connected, IP address %s\n", WiFi.localIP().toString().c_str()); } else { Serial.printf("[WIFI] No connection after 30 s (status %d): check WIFI_SSID and WIFI_PASSWORD. Still trying.\n", WiFi.status()); } } void moveTo(int position) { lift = position; Serial.printf("[BLIND] %d percent open\n", lift); } // The "configuration" of the capability in discovery: the range of the value and its unit void liftConfiguration(JsonObject configuration, void *) { JsonObject range = configuration["supportedRange"].to(); range["minimumValue"] = 0; range["maximumValue"] = 100; range["precision"] = 1; configuration["unitOfMeasure"] = FPSTR(AlexaUnits::Percent); } // The state of the blind: what the answer to a directive and the answer to ReportState carry void sendState(AlexaStatusMessage message) { message.addHealthProp(EndpointHealth::OK).addRangeControllerProp(LIFT, lift).send(); } void onDirective(AlexaDirective &directive) { if (directive.isReportState()) { sendState(directive.stateReport()); return; } if (directive.is("SetRangeValue")) { int position = directive.payload["rangeValue"] | -1; if (position < 0 || position > 100) { AlexaStatusMessage error = directive.error(AlexaErrorType::VALUE_OUT_OF_RANGE, "The blind takes 0 to 100"); error.payload()["validRange"]["minimumValue"] = 0; error.payload()["validRange"]["maximumValue"] = 100; error.send(); return; } moveTo(position); } else if (directive.is("AdjustRangeValue")) { moveTo(constrain(lift + (directive.payload["rangeValueDelta"] | 0), 0, 100)); } else { directive.error(AlexaErrorType::INVALID_DIRECTIVE, "This blind takes a position").send(); return; } sendState(directive.response()); } void setup() { Serial.begin(74880); connectWiFi(); // MQTT user name, MQTT password, root topic alexa.begin(ALEXA_USERNAME, ALEXA_PASSWORD, ALEXA_ROOT_TOPIC); // The name Alexa shows, and the id of the endpoint: every device of an account has its own AlexaDevice *blind = alexa.getDevice("Bedroom Blind", "esp-blind"); blind->setDisplayCategory(DisplayCategory::INTERIOR_BLIND); blind->addCapability(AlexaInterfaces::RangeController, LIFT) ->addFriendlyAsset(AlexaAssets::Setting_Opening) .setConfiguration(liftConfiguration) .addActionMapping(ActionMapping({AlexaAction::Close}, "SetRangeValue", "{\"rangeValue\":0}")) .addActionMapping(ActionMapping({AlexaAction::Open}, "SetRangeValue", "{\"rangeValue\":100}")) .addActionMapping(ActionMapping({AlexaAction::Lower}, "AdjustRangeValue", "{\"rangeValueDelta\":-10,\"rangeValueDeltaDefault\":false}")) .addActionMapping(ActionMapping({AlexaAction::Raise}, "AdjustRangeValue", "{\"rangeValueDelta\":10,\"rangeValueDeltaDefault\":false}")) .addStateMapping({AlexaState::Closed}, 0) .addStateMapping({AlexaState::Open}, 1, 100); blind->addCapability(AlexaInterfaces::EndpointHealth); blind->onDirective(onDirective); } void loop() { alexa.loop(); }