Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

W3C compliance #139

Closed
wants to merge 46 commits into from
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
46 commits
Select commit Hold shift + click to select a range
5dd83fa
Changed links in forms
trivo78 Jul 28, 2021
590f93a
Removed all "global" forms from TD
trivo78 Aug 5, 2021
e2413bc
removed global forms from server
trivo78 Aug 5, 2021
a2feda2
Fix misspelling of JsonObject
Citrullin Aug 11, 2021
1adc91b
Remove websocket
Citrullin Aug 11, 2021
30ebfd7
Move TD to /.well-known/wot-thing-description
Citrullin Aug 12, 2021
53d9023
Remove things array around TD
Citrullin Aug 12, 2021
924fe56
Remove commented out code
Citrullin Aug 17, 2021
1a674fa
Add w3c td v1 and webthings context
Citrullin Aug 17, 2021
123a053
Add things->forms for properties
Citrullin Aug 17, 2021
d5511fc
Remove Wifi101
Citrullin Aug 17, 2021
4f7a5a5
Update BPM280 example
Citrullin Aug 17, 2021
4350c1b
Fix Action POST
Citrullin Aug 18, 2021
1e1d8d7
Merge pull request #2 from Citrullin/migration
relu91 Aug 18, 2021
98320fe
Add 500 Error when not able to create ThinActionObject
Citrullin Aug 18, 2021
7d15182
LED on when connected in bmp280 example
Citrullin Aug 18, 2021
442bc97
Add #ifdef ESP32 for WebThings, instead of ESPWebThing
Citrullin Aug 18, 2021
23ccab0
Remove all generic /actions/* endpoints
Citrullin Aug 18, 2021
8c2704b
clean memory on 500 error and remove unnecessary code
Citrullin Aug 19, 2021
ad538bd
Add simple internal LED on/off example
Citrullin Aug 19, 2021
cee5954
Add OLED Display example
Citrullin Aug 20, 2021
235957f
use uri: format for id in TD
Citrullin Aug 23, 2021
e445abc
Fix context string
Citrullin Aug 23, 2021
4dabab8
Make security an array
Citrullin Aug 23, 2021
1acbb25
Add thing->forms only when properties are defined
Citrullin Aug 23, 2021
2646d73
Merge pull request #6 from Citrullin/bmp280_example
relu91 Aug 23, 2021
f4596c7
Merge pull request #7 from Citrullin/fix-action-post
relu91 Aug 23, 2021
aaed529
Use external LED
Citrullin Aug 25, 2021
0285086
Merge pull request #11 from Citrullin/simple-led-example
relu91 Aug 31, 2021
38375ac
Merge pull request #13 from Citrullin/OLED-display-example
relu91 Aug 31, 2021
3b923ed
Add matrixDisplay example
Citrullin Sep 21, 2021
088de47
Remove webthings in context
Citrullin Sep 29, 2021
ba0fb0c
Readme update
Citrullin Oct 7, 2021
a962e69
Remove screenshots
Citrullin Oct 7, 2021
064c974
Remove incorrect statements
Citrullin Oct 7, 2021
586b797
Add readme
Citrullin Oct 11, 2021
e605704
Matrix Display Readme
Citrullin Oct 11, 2021
19c9365
Add LED example readme
Citrullin Oct 11, 2021
4da14e6
Add beginner tutorials to README
Citrullin Oct 11, 2021
3aa2a46
Merge pull request #14 from Citrullin/dev
relu91 Oct 11, 2021
12551f6
Remove Webthings.io schemas in context
Citrullin Oct 11, 2021
b340faa
Merge pull request #17 from Citrullin/simple-led-example
relu91 Oct 11, 2021
ba8a111
Merge pull request #18 from Citrullin/bme280_readme
relu91 Oct 11, 2021
17e0334
Merge branch 'dev' into TD_fixes
relu91 Oct 11, 2021
3b12484
Merge pull request #19 from Citrullin/TD_fixes
relu91 Oct 11, 2021
a58d399
Merge pull request #16 from Citrullin/matrix_display
relu91 Oct 11, 2021
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
267 changes: 14 additions & 253 deletions ESPWebThingAdapter.h
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@

#pragma once

#if defined(ESP32) || defined(ESP8266)


#include <ArduinoJson.h>
#include <ESPAsyncWebServer.h>
Expand Down Expand Up @@ -55,7 +55,7 @@ class WebThingAdapter {
}

MDNS.addService("webthing", "tcp", port);
MDNS.addServiceTxt("webthing", "tcp", "path", "/");
MDNS.addServiceTxt("webthing", "tcp", "path", "/.well-known/wot-thing-description");

DefaultHeaders::Instance().addHeader("Access-Control-Allow-Origin", "*");
DefaultHeaders::Instance().addHeader("Access-Control-Allow-Methods",
Expand All @@ -70,7 +70,7 @@ class WebThingAdapter {
this->server.on("/*", HTTP_OPTIONS,
std::bind(&WebThingAdapter::handleOptions, this,
std::placeholders::_1));
this->server.on("/", HTTP_GET,
this->server.on("/.well-known/wot-thing-description", HTTP_GET,
std::bind(&WebThingAdapter::handleThings, this,
std::placeholders::_1));

Expand Down Expand Up @@ -131,17 +131,7 @@ class WebThingAdapter {
std::bind(&WebThingAdapter::handleThingPropertiesGet,
this, std::placeholders::_1,
device->firstProperty));
this->server.on((deviceBase + "/actions").c_str(), HTTP_GET,
std::bind(&WebThingAdapter::handleThingActionsGet, this,
std::placeholders::_1, device));
this->server.on((deviceBase + "/actions").c_str(), HTTP_POST,
std::bind(&WebThingAdapter::handleThingActionsPost, this,
std::placeholders::_1, device),
NULL,
std::bind(&WebThingAdapter::handleBody, this,
std::placeholders::_1, std::placeholders::_2,
std::placeholders::_3, std::placeholders::_4,
std::placeholders::_5));

this->server.on((deviceBase + "/events").c_str(), HTTP_GET,
std::bind(&WebThingAdapter::handleThingEventsGet, this,
std::placeholders::_1, device));
Expand All @@ -158,15 +148,6 @@ class WebThingAdapter {
void update() {
#ifdef ESP8266
MDNS.update();
#endif
#ifndef WITHOUT_WS
// * Send changed properties as defined in "4.5 propertyStatus message"
// Do this by looping over all devices and properties
ThingDevice *device = this->firstDevice;
while (device != nullptr) {
sendChangedProperties(device);
device = device->next;
}
#endif
}

Expand All @@ -178,19 +159,6 @@ class WebThingAdapter {
this->lastDevice->next = device;
this->lastDevice = device;
}

#ifndef WITHOUT_WS
// Initiate the websocket instance
AsyncWebSocket *ws = new AsyncWebSocket("/things/" + device->id);
device->ws = ws;
// AsyncWebSocket * server, AsyncWebSocketClient * client, AwsEventType
// type, void * arg, uint8_t *data, size_t len, ThingDevice* device
ws->onEvent(std::bind(
&WebThingAdapter::handleWS, this, std::placeholders::_1,
std::placeholders::_2, std::placeholders::_3, std::placeholders::_4,
std::placeholders::_5, std::placeholders::_6, device));
this->server.addHandler(ws);
#endif
}

private:
Expand Down Expand Up @@ -228,118 +196,6 @@ class WebThingAdapter {
return false;
}

#ifndef WITHOUT_WS
void sendErrorMsg(DynamicJsonDocument &prop, AsyncWebSocketClient &client,
int status, const char *msg) {
prop["error"] = msg;
prop["status"] = status;
String jsonStr;
serializeJson(prop, jsonStr);
client.text(jsonStr.c_str(), jsonStr.length());
}

void handleWS(AsyncWebSocket *server, AsyncWebSocketClient *client,
AwsEventType type, void *arg, const uint8_t *rawData,
size_t len, ThingDevice *device) {
if (type == WS_EVT_DISCONNECT || type == WS_EVT_ERROR) {
device->removeEventSubscriptions(client->id());
return;
}

// Ignore all others except data packets
if (type != WS_EVT_DATA)
return;

// Only consider non fragmented data
AwsFrameInfo *info = (AwsFrameInfo *)arg;
if (!info->final || info->index != 0 || info->len != len)
return;

// Web Thing only specifies text, not binary websocket transfers
if (info->opcode != WS_TEXT)
return;

// In theory we could just have one websocket for all Things and react on
// the server->url() to route data. Controllers will however establish a
// separate websocket connection for each Thing anyway as of in the spec.
// For now each Thing stores its own Websocket connection object therefore.

// Parse request
DynamicJsonDocument newProp(SMALL_JSON_DOCUMENT_SIZE);
auto error = deserializeJson(newProp, rawData, len);
if (error) {
sendErrorMsg(newProp, *client, 400, "Invalid json");
return;
}

String messageType = newProp["messageType"].as<String>();
JsonVariant dataVariant = newProp["data"];
if (!dataVariant.is<JsonObject>()) {
sendErrorMsg(newProp, *client, 400, "data must be an object");
return;
}

JsonObject data = dataVariant.as<JsonObject>();

if (messageType == "setProperty") {
for (JsonPair kv : data) {
device->setProperty(kv.key().c_str(), kv.value());
}
} else if (messageType == "requestAction") {
for (JsonPair kv : data) {
DynamicJsonDocument *actionRequest =
new DynamicJsonDocument(SMALL_JSON_DOCUMENT_SIZE);

JsonObject actionObj = actionRequest->to<JsonObject>();
JsonObject nested = actionObj.createNestedObject(kv.key());

for (JsonPair kvInner : kv.value().as<JsonObject>()) {
nested[kvInner.key()] = kvInner.value();
}

ThingActionObject *obj = device->requestAction(actionRequest);
if (obj != nullptr) {
obj->setNotifyFunction(std::bind(&ThingDevice::sendActionStatus,
device, std::placeholders::_1));
device->sendActionStatus(obj);

obj->start();
}
}
} else if (messageType == "addEventSubscription") {
for (JsonPair kv : data) {
ThingEvent *event = device->findEvent(kv.key().c_str());
if (event) {
device->addEventSubscription(client->id(), event->id);
}
}
}
}

void sendChangedProperties(ThingDevice *device) {
// Prepare one buffer per device
DynamicJsonDocument message(LARGE_JSON_DOCUMENT_SIZE);
message["messageType"] = "propertyStatus";
JsonObject prop = message.createNestedObject("data");
bool dataToSend = false;
ThingItem *item = device->firstProperty;
while (item != nullptr) {
ThingDataValue *value = item->changedValueOrNull();
if (value) {
dataToSend = true;
item->serializeValue(prop);
}
item = item->next;
}
if (dataToSend) {
String jsonStr;
serializeJson(message, jsonStr);
// Inform all connected ws clients of a Thing about changed properties
((AsyncWebSocket *)device->ws)->textAll(jsonStr);
}
}
#endif

void handleUnknown(AsyncWebServerRequest *request) {
if (!verifyHost(request)) {
return;
Expand All @@ -362,16 +218,15 @@ class WebThingAdapter {
request->beginResponseStream("application/json");

DynamicJsonDocument buf(LARGE_JSON_DOCUMENT_SIZE);
JsonArray things = buf.to<JsonArray>();
JsonObject thing = buf.to<JsonObject>();
ThingDevice *device = this->firstDevice;
while (device != nullptr) {
JsonObject descr = things.createNestedObject();
device->serialize(descr, ip, port);
descr["href"] = "/things/" + device->id;
device->serialize(thing, ip, port);
thing["href"] = "/things/" + device->id;
device = device->next;
}

serializeJson(things, *response);
serializeJson(thing, *response);
request->send(response);
}

Expand Down Expand Up @@ -474,6 +329,7 @@ class WebThingAdapter {
void handleThingActionPost(AsyncWebServerRequest *request,
ThingDevice *device, ThingAction *action) {
if (!verifyHost(request)) {
Serial.println("Invalid Host");
return;
}

Expand All @@ -486,38 +342,21 @@ class WebThingAdapter {
new DynamicJsonDocument(SMALL_JSON_DOCUMENT_SIZE);
auto error = deserializeJson(*newBuffer, (const char *)body_data);
if (error) { // unable to parse json
Serial.println("Unable to parse JSON");
b_has_body_data = false;
memset(body_data, 0, sizeof(body_data));
request->send(500);
delete newBuffer;
return;
}

JsonObject newAction = newBuffer->as<JsonObject>();

if (!newAction.containsKey(action->id)) {
b_has_body_data = false;
memset(body_data, 0, sizeof(body_data));
request->send(400);
delete newBuffer;
return;
}

ThingActionObject *obj = device->requestAction(newBuffer);

ThingActionObject *obj = action->create(newBuffer);
if (obj == nullptr) {
b_has_body_data = false;
memset(body_data, 0, sizeof(body_data));
request->send(500);
delete newBuffer;
return;
memset(body_data, 0, sizeof(body_data));
request->send(500);
return;
}

#ifndef WITHOUT_WS
obj->setNotifyFunction(std::bind(&ThingDevice::sendActionStatus, device,
std::placeholders::_1));
#endif

DynamicJsonDocument respBuffer(SMALL_JSON_DOCUMENT_SIZE);
JsonObject item = respBuffer.to<JsonObject>();
obj->serialize(item, device->id);
Expand Down Expand Up @@ -567,83 +406,6 @@ class WebThingAdapter {
request->send(response);
}

void handleThingActionsGet(AsyncWebServerRequest *request,
ThingDevice *device) {
if (!verifyHost(request)) {
return;
}
AsyncResponseStream *response =
request->beginResponseStream("application/json");

DynamicJsonDocument doc(LARGE_JSON_DOCUMENT_SIZE);
JsonArray queue = doc.to<JsonArray>();
device->serializeActionQueue(queue);
serializeJson(queue, *response);
request->send(response);
}

void handleThingActionsPost(AsyncWebServerRequest *request,
ThingDevice *device) {
if (!verifyHost(request)) {
return;
}

if (!b_has_body_data) {
request->send(422); // unprocessable entity (b/c no body)
return;
}

DynamicJsonDocument *newBuffer =
new DynamicJsonDocument(SMALL_JSON_DOCUMENT_SIZE);
auto error = deserializeJson(*newBuffer, (const char *)body_data);
if (error) { // unable to parse json
b_has_body_data = false;
memset(body_data, 0, sizeof(body_data));
request->send(500);
delete newBuffer;
return;
}

JsonObject newAction = newBuffer->as<JsonObject>();

if (newAction.size() != 1) {
b_has_body_data = false;
memset(body_data, 0, sizeof(body_data));
request->send(400);
delete newBuffer;
return;
}

ThingActionObject *obj = device->requestAction(newBuffer);

if (obj == nullptr) {
b_has_body_data = false;
memset(body_data, 0, sizeof(body_data));
request->send(500);
delete newBuffer;
return;
}

#ifndef WITHOUT_WS
obj->setNotifyFunction(std::bind(&ThingDevice::sendActionStatus, device,
std::placeholders::_1));
#endif

DynamicJsonDocument respBuffer(SMALL_JSON_DOCUMENT_SIZE);
JsonObject item = respBuffer.to<JsonObject>();
obj->serialize(item, device->id);
String jsonStr;
serializeJson(item, jsonStr);
AsyncWebServerResponse *response =
request->beginResponse(201, "application/json", jsonStr);
request->send(response);

b_has_body_data = false;
memset(body_data, 0, sizeof(body_data));

obj->start();
}

void handleThingEventsGet(AsyncWebServerRequest *request,
ThingDevice *device) {
if (!verifyHost(request)) {
Expand Down Expand Up @@ -709,4 +471,3 @@ class WebThingAdapter {
}
};

#endif // ESP
Loading