Examples as Arduino sketches: one folder per device type, each its own endpointId, Wi-Fi with a timeout
Light, DimmableLight, ColorTemperatureLight, ColorLight, TemperatureSensor, ContactSensor, Blind, Thermostat, Lock, Scene, Doorbell and MultiDevice on the 2.0 API: addCapability(row, instance), one onDirective() handler, the report helpers, ChangeReports from loop(), a deferred answer for the lock. Wi-Fi is waited for 30 s, then the sketch says what to check and carries on. examples/README.md has what each sketch does, its limits and the measured sizes for d1_mini: Light takes 30,616 bytes of static RAM and 332,565 of flash; all 17 sketches build with 0 warnings. test/test_examples compares the discovery object of every new sketch: 147 host tests (were 135). The sketches were compiled, not run on a board. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
This commit is contained in:
parent
7369883479
commit
69b4d59896
15 changed files with 2061 additions and 1 deletions
297
test/test_examples/test_main.cpp
Normal file
297
test/test_examples/test_main.cpp
Normal file
|
|
@ -0,0 +1,297 @@
|
|||
// Host tests of the example sketches of 2.0 (examples/<Name>/<Name>.ino): the object each of them announces on
|
||||
// <root>/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 <unity.h>
|
||||
#include <ArduinoJson.h>
|
||||
#include <string>
|
||||
#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<const char *> 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<JsonObject>();
|
||||
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<JsonArray>();
|
||||
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();
|
||||
}
|
||||
Loading…
Add table
Add a link
Reference in a new issue