// Host tests of the example sketches of 2.0 (examples//.ino): the object each of them announces on // /discover_r, compared byte by byte with the text below. // The devices are built with the calls of the example's setup(). A change to the capabilities of an example is a // change here. The examples of 1.x (examples/legacy) are in test/test_discovery. // pio test -e native #include #include #include #include "AlexaDevice.h" #include "AlexaLog.h" #include "AlexaResources.h" #include "AlexaVersion.h" static std::string endpoint(const char *endpointId, const char *friendlyName, const char *displayCategory, const std::string &capabilities) { return std::string("{\"endpointId\":\"") + endpointId + "\",\"friendlyName\":\"" + friendlyName + "\",\"description\":\"Alexa2MQTT Default Device\",\"manufacturerName\":\"Alexa2MQTT\"," "\"displayCategories\":[\"" + displayCategory + "\"]," "\"additionalAttributes\":{\"manufacturer\":\"Alexa2MQTT\",\"model\":\"Alexa2MQTT\"," "\"serialNumber\":\"ESP2Alex\",\"firmwareVersion\":\"" ALEX2ESP_VERSION "\"," "\"softwareVersion\":\"" ALEX2ESP_VERSION "\",\"customIdentifier\":\"ESP2Alex\"}," "\"capabilities\":[" + capabilities + "]}"; } // A capability with one property that is not reported unasked #define CAPABILITY(interface, version, property) \ "{\"interface\":\"Alexa." interface "\",\"version\":\"" version "\",\"type\":\"AlexaInterface\"," \ "\"properties\":{\"retrievable\":true,\"proactivelyReported\":false,\"supported\":[{\"name\":\"" property "\"}]}}" // The same, reported unasked with a ChangeReport #define REPORTING_CAPABILITY(interface, version, property) \ "{\"interface\":\"Alexa." interface "\",\"version\":\"" version "\",\"type\":\"AlexaInterface\"," \ "\"properties\":{\"retrievable\":true,\"proactivelyReported\":true,\"supported\":[{\"name\":\"" property "\"}]}}" static const char POWER[] = CAPABILITY("PowerController", "3", "powerState"); static const char BRIGHTNESS[] = CAPABILITY("BrightnessController", "3", "brightness"); static const char COLOR_TEMPERATURE[] = CAPABILITY("ColorTemperatureController", "3", "colorTemperatureInKelvin"); static const char COLOR[] = CAPABILITY("ColorController", "3", "color"); static const char TEMPERATURE[] = CAPABILITY("TemperatureSensor", "3", "temperature"); static const char LOCK[] = CAPABILITY("LockController", "3", "lockState"); static const char HEALTH[] = CAPABILITY("EndpointHealth", "3.1", "connectivity"); static std::string list(std::initializer_list capabilities) { std::string text; for (const char *capability : capabilities) { text += text.empty() ? "" : ","; text += capability; } return text; } static void assertDiscovery(const std::string &expected, const AlexaDevice &device) { std::string announced; serializeJson(device.getDeviceJSON(), announced); TEST_ASSERT_EQUAL_STRING(expected.c_str(), announced.c_str()); } void setUp(void) { AlexaLog::setOutput(nullptr); // the lines about added capabilities are not what is tested } void tearDown(void) {} void test_light_discovery(void) { AlexaDevice lamp("Desk Lamp", "root", "esp-light"); lamp.setDisplayCategory(DisplayCategory::LIGHT); lamp.addCapability(AlexaInterfaces::PowerController); lamp.addCapability(AlexaInterfaces::EndpointHealth); assertDiscovery(endpoint("esp-light", "Desk Lamp", "LIGHT", list({POWER, HEALTH})), lamp); } void test_dimmable_light_discovery(void) { AlexaDevice lamp("Dimmable Lamp", "root", "esp-dimmable-light"); lamp.setDisplayCategory(DisplayCategory::LIGHT); lamp.addCapability(AlexaInterfaces::PowerController); lamp.addCapability(AlexaInterfaces::BrightnessController); lamp.addCapability(AlexaInterfaces::EndpointHealth); assertDiscovery(endpoint("esp-dimmable-light", "Dimmable Lamp", "LIGHT", list({POWER, BRIGHTNESS, HEALTH})), lamp); } void test_color_temperature_light_discovery(void) { AlexaDevice lamp("White Lamp", "root", "esp-white-light"); lamp.setDisplayCategory(DisplayCategory::LIGHT); lamp.addCapability(AlexaInterfaces::PowerController); lamp.addCapability(AlexaInterfaces::BrightnessController); lamp.addCapability(AlexaInterfaces::ColorTemperatureController); lamp.addCapability(AlexaInterfaces::EndpointHealth); assertDiscovery(endpoint("esp-white-light", "White Lamp", "LIGHT", list({POWER, BRIGHTNESS, COLOR_TEMPERATURE, HEALTH})), lamp); } void test_color_light_discovery(void) { AlexaDevice lamp("Color Lamp", "root", "esp-color-light"); lamp.setDisplayCategory(DisplayCategory::LIGHT); lamp.addCapability(AlexaInterfaces::PowerController); lamp.addCapability(AlexaInterfaces::BrightnessController); lamp.addCapability(AlexaInterfaces::ColorController); lamp.addCapability(AlexaInterfaces::EndpointHealth); assertDiscovery(endpoint("esp-color-light", "Color Lamp", "LIGHT", list({POWER, BRIGHTNESS, COLOR, HEALTH})), lamp); } void test_temperature_sensor_discovery(void) { AlexaDevice sensor("Room Temperature", "root", "esp-temperature"); sensor.setDisplayCategory(DisplayCategory::TEMPERATURE_SENSOR); sensor.addCapability(AlexaInterfaces::TemperatureSensor)->setProactivelyReported(true); sensor.addCapability(AlexaInterfaces::EndpointHealth); assertDiscovery(endpoint("esp-temperature", "Room Temperature", "TEMPERATURE_SENSOR", list({REPORTING_CAPABILITY("TemperatureSensor", "3", "temperature"), HEALTH})), sensor); } void test_contact_sensor_discovery(void) { AlexaDevice contact("Back Door", "root", "esp-contact"); contact.setDisplayCategory(DisplayCategory::CONTACT_SENSOR); contact.addCapability(AlexaInterfaces::ContactSensor)->setProactivelyReported(true); contact.addCapability(AlexaInterfaces::EndpointHealth); assertDiscovery(endpoint("esp-contact", "Back Door", "CONTACT_SENSOR", list({REPORTING_CAPABILITY("ContactSensor", "3", "detectionState"), HEALTH})), contact); } static void liftConfiguration(JsonObject configuration, void *) { JsonObject range = configuration["supportedRange"].to(); range["minimumValue"] = 0; range["maximumValue"] = 100; range["precision"] = 1; configuration["unitOfMeasure"] = FPSTR(AlexaUnits::Percent); } void test_blind_discovery(void) { static const char LIFT[] = "Blind.Lift"; AlexaDevice blind("Bedroom Blind", "root", "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); assertDiscovery( endpoint("esp-blind", "Bedroom Blind", "INTERIOR_BLIND", list({"{\"interface\":\"Alexa.RangeController\",\"version\":\"3\",\"type\":\"AlexaInterface\"," "\"properties\":{\"retrievable\":true,\"proactivelyReported\":false," "\"supported\":[{\"name\":\"rangeValue\"}]}," "\"semantics\":{\"actionMappings\":[" "{\"@type\":\"ActionsToDirective\",\"actions\":[\"Alexa.Actions.Close\"]," "\"directive\":{\"name\":\"SetRangeValue\",\"payload\":{\"rangeValue\":0}}}," "{\"@type\":\"ActionsToDirective\",\"actions\":[\"Alexa.Actions.Open\"]," "\"directive\":{\"name\":\"SetRangeValue\",\"payload\":{\"rangeValue\":100}}}," "{\"@type\":\"ActionsToDirective\",\"actions\":[\"Alexa.Actions.Lower\"]," "\"directive\":{\"name\":\"AdjustRangeValue\"," "\"payload\":{\"rangeValueDelta\":-10,\"rangeValueDeltaDefault\":false}}}," "{\"@type\":\"ActionsToDirective\",\"actions\":[\"Alexa.Actions.Raise\"]," "\"directive\":{\"name\":\"AdjustRangeValue\"," "\"payload\":{\"rangeValueDelta\":10,\"rangeValueDeltaDefault\":false}}}]," "\"stateMappings\":[" "{\"@type\":\"StatesToValue\",\"states\":[\"Alexa.States.Closed\"],\"value\":0}," "{\"@type\":\"StatesToRange\",\"states\":[\"Alexa.States.Open\"]," "\"range\":{\"minimumValue\":1,\"maximumValue\":100}}]}," "\"instance\":\"Blind.Lift\"," "\"capabilityResources\":{\"friendlyNames\":[" "{\"@type\":\"asset\",\"value\":{\"assetId\":\"Alexa.Setting.Opening\"}}]}," "\"configuration\":{\"supportedRange\":{\"minimumValue\":0,\"maximumValue\":100,\"precision\":1}," "\"unitOfMeasure\":\"Alexa.Unit.Percent\"}}", HEALTH})), blind); } static void thermostatConfiguration(JsonObject configuration, void *) { JsonArray modes = configuration["supportedModes"].to(); modes.add("HEAT"); modes.add("COOL"); modes.add("AUTO"); modes.add("OFF"); configuration["supportsScheduling"] = false; } void test_thermostat_discovery(void) { AlexaDevice thermostat("Hallway Thermostat", "root", "esp-thermostat"); thermostat.setDisplayCategory(DisplayCategory::THERMOSTAT); thermostat.addCapability(AlexaInterfaces::ThermostatController)->setConfiguration(thermostatConfiguration); thermostat.addCapability(AlexaInterfaces::TemperatureSensor); thermostat.addCapability(AlexaInterfaces::EndpointHealth); assertDiscovery( endpoint("esp-thermostat", "Hallway Thermostat", "THERMOSTAT", list({"{\"interface\":\"Alexa.ThermostatController\",\"version\":\"3.2\",\"type\":\"AlexaInterface\"," "\"properties\":{\"retrievable\":true,\"proactivelyReported\":false,\"supported\":[" "{\"name\":\"targetSetpoint\"},{\"name\":\"lowerSetpoint\"},{\"name\":\"upperSetpoint\"}," "{\"name\":\"thermostatMode\"}]}," "\"configuration\":{\"supportedModes\":[\"HEAT\",\"COOL\",\"AUTO\",\"OFF\"]," "\"supportsScheduling\":false}}", TEMPERATURE, HEALTH})), thermostat); } void test_lock_discovery(void) { AlexaDevice doorLock("Front Door Lock", "root", "esp-lock"); doorLock.setDisplayCategory(DisplayCategory::SMARTLOCK); doorLock.addCapability(AlexaInterfaces::LockController); doorLock.addCapability(AlexaInterfaces::EndpointHealth); assertDiscovery(endpoint("esp-lock", "Front Door Lock", "SMARTLOCK", list({LOCK, HEALTH})), doorLock); } void test_scene_discovery(void) { AlexaDevice scene("Movie Night", "root", "esp-scene"); scene.setDisplayCategory(DisplayCategory::SCENE_TRIGGER); scene.addCapability(AlexaInterfaces::SceneController); scene.addCapability(AlexaInterfaces::EndpointHealth); assertDiscovery(endpoint("esp-scene", "Movie Night", "SCENE_TRIGGER", list({"{\"interface\":\"Alexa.SceneController\",\"version\":\"3\",\"type\":\"AlexaInterface\"}", HEALTH})), scene); } void test_doorbell_discovery(void) { AlexaDevice doorbell("Front Door Bell", "root", "esp-doorbell"); doorbell.setDisplayCategory(DisplayCategory::DOORBELL); doorbell.addCapability(AlexaInterfaces::DoorbellEventSource)->setProactivelyReported(true); doorbell.addCapability(AlexaInterfaces::EndpointHealth); assertDiscovery( endpoint("esp-doorbell", "Front Door Bell", "DOORBELL", list({"{\"interface\":\"Alexa.DoorbellEventSource\",\"version\":\"3\",\"type\":\"AlexaInterface\"}", HEALTH})), doorbell); } void test_multi_device_discovery(void) { const char *const names[] = {"Left Lamp", "Right Lamp"}; const char *const ids[] = {"esp-multi-left", "esp-multi-right"}; for (int i = 0; i < 2; i++) { AlexaDevice lamp(names[i], "root", ids[i]); lamp.setDisplayCategory(DisplayCategory::LIGHT); lamp.addCapability(AlexaInterfaces::PowerController); lamp.addCapability(AlexaInterfaces::EndpointHealth); assertDiscovery(endpoint(ids[i], names[i], "LIGHT", list({POWER, HEALTH})), lamp); } } int main(int, char **) { UNITY_BEGIN(); RUN_TEST(test_light_discovery); RUN_TEST(test_dimmable_light_discovery); RUN_TEST(test_color_temperature_light_discovery); RUN_TEST(test_color_light_discovery); RUN_TEST(test_temperature_sensor_discovery); RUN_TEST(test_contact_sensor_discovery); RUN_TEST(test_blind_discovery); RUN_TEST(test_thermostat_discovery); RUN_TEST(test_lock_discovery); RUN_TEST(test_scene_discovery); RUN_TEST(test_doorbell_discovery); RUN_TEST(test_multi_device_discovery); return UNITY_END(); }