From 681ef8d281a0629d75bfdd6f8e986b6f52fa57bb Mon Sep 17 00:00:00 2001
From: Philippe Coval
Date: Wed, 25 Jul 2018 19:26:52 +0200
Subject: [PATCH] example: Add LevelSensor using Ethernet
It was tested on Arduino mega256 with W5100 shield and FC-28 soil moisture analog sensor
Change-Id: Ibb78e12d024df8bcbe522e013734e40a3a623b16
Origin: https://github.com/TizenTeam/webthing-esp8266
Bug: https://github.com/mozilla-iot/webthing-arduino/pull/23
Signed-off-by: Philippe Coval
---
EthernetWebThingAdapter.h | 450 +++++++++++++++++++++++++++
examples/LevelSensor/LevelSensor.ino | 75 +++++
2 files changed, 525 insertions(+)
create mode 100644 EthernetWebThingAdapter.h
create mode 100644 examples/LevelSensor/LevelSensor.ino
diff --git a/EthernetWebThingAdapter.h b/EthernetWebThingAdapter.h
new file mode 100644
index 0000000..3596a83
--- /dev/null
+++ b/EthernetWebThingAdapter.h
@@ -0,0 +1,450 @@
+// -*- mode: c++; c-basic-offset: 2 -*-
+/**
+ * EthernetThingAdapter.h
+ *
+ * Exposes the Web Thing API based on provided ThingDevices.
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/.
+ */
+
+#ifndef MOZILLA_IOT_ETHERNETWEBTHINGADAPTER_H
+#define MOZILLA_IOT_ETHERNETWEBTHINGADAPTER_H
+
+#if !defined(ESP32) && !defined(ESP8266)
+
+#include
+#include
+#include
+#include
+#include
+#include
+#include
+
+#include
+#include
+
+#include "Thing.h"
+
+static const bool DEBUG = false;
+
+enum HTTPMethod {
+ HTTP_ANY,
+ HTTP_GET,
+ HTTP_PUT,
+ HTTP_OPTIONS
+};
+
+enum ReadState {
+ STATE_READ_METHOD,
+ STATE_READ_URI,
+ STATE_DISCARD_HTTP11,
+ STATE_DISCARD_HEADERS_PRE_HOST,
+ STATE_READ_HOST,
+ STATE_DISCARD_HEADERS_POST_HOST,
+ STATE_READ_CONTENT
+};
+
+class WebThingAdapter {
+public:
+ WebThingAdapter(String _name, uint32_t _ip): name(_name), server(80), mdns(udp) {
+ ip = "";
+ for (int i = 0; i < 4; i++) {
+ ip += _ip & 0xff;
+ if (i < 3) {
+ ip += '.';
+ }
+ _ip >>= 8;
+ }
+ }
+
+ void begin() {
+ mdns.begin(Ethernet.localIP(), name.c_str());
+
+ mdns.addServiceRecord("_webthing",
+ 80,
+ MDNSServiceTCP,
+ "\x06path=/");
+
+ server.begin();
+ }
+
+ void update() {
+ mdns.run();
+
+ if (!client) {
+ EthernetClient client = server.available();
+ if (!client) {
+ return;
+ }
+ if (DEBUG) {
+ Serial.println("New client available");
+ }
+ this->client = client;
+ }
+
+ if (!client.connected()) {
+ if (DEBUG) {
+ Serial.println("Client disconnected");
+ }
+ resetParser();
+ client.stop();
+ return;
+ }
+
+ char c = client.read();
+ if (c == 255 || c == -1) {
+ if (state == STATE_READ_CONTENT) {
+ handleRequest();
+ resetParser();
+ }
+
+ retries += 1;
+ if (retries > 5000) {
+ if (DEBUG) {
+ Serial.println("Giving up on client");
+ }
+ resetParser();
+ client.stop();
+ }
+ return;
+ }
+
+ switch(state) {
+ case STATE_READ_METHOD:
+ if (c == ' ') {
+ if (methodRaw == "GET") {
+ method = HTTP_GET;
+ } else if (methodRaw == "PUT") {
+ method = HTTP_PUT;
+ } else if (methodRaw == "OPTIONS") {
+ method = HTTP_OPTIONS;
+ } else {
+ method = HTTP_ANY;
+ }
+ state = STATE_READ_URI;
+ } else {
+ methodRaw += c;
+ }
+ break;
+
+ case STATE_READ_URI:
+ if (c == ' ') {
+ state = STATE_DISCARD_HTTP11;
+ } else {
+ uri += c;
+ }
+ break;
+
+ case STATE_DISCARD_HTTP11:
+ if (c == '\r') {
+ state = STATE_DISCARD_HEADERS_PRE_HOST;
+ }
+ break;
+
+ case STATE_DISCARD_HEADERS_PRE_HOST:
+ if (c == '\r') {
+ break;
+ }
+ if (c == '\n') {
+ headerRaw = "";
+ break;
+ }
+ if (c == ':') {
+ if (headerRaw.equalsIgnoreCase("Host")) {
+ state = STATE_READ_HOST;
+ }
+ break;
+ }
+
+ headerRaw += c;
+ break;
+
+ case STATE_READ_HOST:
+ if (c == '\r') {
+ returnsAndNewlines = 1;
+ state = STATE_DISCARD_HEADERS_POST_HOST;
+ break;
+ }
+ if (c == ' ') {
+ break;
+ }
+ host += c;
+ break;
+
+ case STATE_DISCARD_HEADERS_POST_HOST:
+ if (c == '\r' || c == '\n') {
+ returnsAndNewlines += 1;
+ } else {
+ returnsAndNewlines = 0;
+ }
+ if (returnsAndNewlines == 4) {
+ state = STATE_READ_CONTENT;
+ }
+ break;
+
+ case STATE_READ_CONTENT:
+ content += c;
+ break;
+ }
+ }
+
+ void addDevice(ThingDevice* device) {
+ if (lastDevice == nullptr) {
+ firstDevice = device;
+ lastDevice = device;
+ } else {
+ lastDevice->next = device;
+ lastDevice = device;
+ }
+ }
+private:
+ String name, ip;
+ EthernetServer server;
+ EthernetClient client;
+ EthernetUDP udp;
+ MDNS mdns;
+
+ ReadState state = STATE_READ_METHOD;
+ String uri = "";
+ HTTPMethod method = HTTP_ANY;
+ String content = "";
+ String methodRaw = "";
+ String host = "";
+ String headerRaw = "";
+ int returnsAndNewlines = 0;
+ int retries = 0;
+
+ ThingDevice *firstDevice = nullptr, *lastDevice = nullptr;
+
+ bool verifyHost() {
+ int colonIndex = host.indexOf(':');
+ if (colonIndex >= 0) {
+ host.remove(colonIndex);
+ }
+ if (host == name + ".local") {
+ return true;
+ }
+ if (host == ip) {
+ return true;
+ }
+ return false;
+ }
+
+ void handleRequest() {
+ if (DEBUG) {
+ Serial.print("handleRequest: ");
+ Serial.print("method: ");
+ Serial.println(method);
+ Serial.print("uri: ");
+ Serial.println(uri);
+ Serial.print("host: ");
+ Serial.println(host);
+ Serial.print("content: ");
+ Serial.println(content);
+ }
+
+ if (!verifyHost()) {
+ client.println("HTTP/1.1 403 Forbidden");
+ client.println("Connection: close");
+ client.println();
+ delay(1);
+ client.stop();
+ return;
+ }
+
+ if (uri == "/") {
+ handleThings();
+ return;
+ }
+
+ ThingDevice* device = this->firstDevice;
+ while (device != nullptr) {
+ String deviceBase = "/things/" + device->id;
+
+ if (uri.startsWith(deviceBase)) {
+ if (uri == deviceBase) {
+ if (method == HTTP_GET || method == HTTP_OPTIONS) {
+ handleDeviceGet(device);
+ } else {
+ handleError();
+ }
+ return;
+ } else {
+ ThingProperty* property = device->firstProperty;
+ while (property != nullptr) {
+ String propertyBase = deviceBase + "/properties/" + property->id;
+ if (uri == propertyBase) {
+ if (method == HTTP_GET || method == HTTP_OPTIONS) {
+ handlePropertyGet(property);
+ } else if (method == HTTP_PUT) {
+ handlePropertyPut(property);
+ } else {
+ handleError();
+ }
+ return;
+ }
+ property = property->next;
+ }
+ }
+ }
+ device = device->next;
+ }
+ handleError();
+ }
+
+ void sendOk() {
+ client.println("HTTP/1.1 200 OK");
+ }
+
+ void sendHeaders() {
+ client.println("Access-Control-Allow-Origin: *");
+ client.println("Access-Control-Allow-Methods: PUT, GET, OPTIONS");
+ client.println("Content-Type: application/json");
+ client.println("Connection: close");
+ client.println();
+ }
+
+ void handleThings() {
+ sendOk();
+ sendHeaders();
+
+ StaticJsonBuffer<2048> buf;
+ JsonArray& things = buf.createArray();
+ ThingDevice* device = firstDevice;
+ while (device != nullptr) {
+ JsonObject& descr = things.createNestedObject();
+ serializeDevice(descr, device);
+ device = device->next;
+ }
+
+ things.printTo(client);
+ delay(1);
+ client.stop();
+ }
+
+
+ void handleDeviceGet(ThingDevice* device) {
+ sendOk();
+ sendHeaders();
+
+ StaticJsonBuffer<512> buf;
+ JsonObject& descr = buf.createObject();
+ serializeDevice(descr, device);
+
+ descr.printTo(client);
+ delay(1);
+ client.stop();
+ }
+
+ void handlePropertyGet(ThingProperty* property) {
+ sendOk();
+ sendHeaders();
+
+ StaticJsonBuffer<256> buf;
+ JsonObject& prop = buf.createObject();
+ switch (property->type) {
+ case BOOLEAN:
+ prop[property->id] = property->getValue().boolean;
+ break;
+ case NUMBER:
+ prop[property->id] = property->getValue().number;
+ break;
+ case STRING:
+ prop[property->id] = *property->getValue().string;
+ break;
+ }
+ prop.printTo(client);
+ delay(1);
+ client.stop();
+ }
+
+ void handlePropertyPut(ThingProperty* property) {
+ sendOk();
+ sendHeaders();
+ StaticJsonBuffer<256> newBuffer;
+ JsonObject& newProp = newBuffer.parseObject(content);
+ JsonVariant newValue = newProp[property->id];
+
+ switch (property->type) {
+ case BOOLEAN: {
+ ThingPropertyValue value;
+ value.boolean = newValue.as();
+ property->setValue(value);
+ break;
+ }
+ case NUMBER: {
+ ThingPropertyValue value;
+ value.number = newValue.as();
+ property->setValue(value);
+ break;
+ }
+ case STRING:
+ *property->getValue().string = newValue.as();
+ break;
+ }
+
+ client.print(content);
+ delay(1);
+ client.stop();
+ }
+
+ void handleError() {
+ client.println("HTTP/1.1 400 Bad Request");
+ sendHeaders();
+ delay(1);
+ client.stop();
+ }
+
+ void resetParser() {
+ state = STATE_READ_METHOD;
+ method = HTTP_ANY;
+ methodRaw = "";
+ headerRaw = "";
+ host = "";
+ uri = "";
+ content = "";
+ retries = 0;
+ }
+
+ void serializeDevice(JsonObject& descr, ThingDevice* device) {
+ descr["name"] = device->name;
+ descr["href"] = "/things/" + device->id;
+ descr["@context"] = "https://iot.mozilla.org/schemas";
+
+ JsonArray& typeJson = descr.createNestedArray("@type");
+ const char** type = device->type;
+ while ((*type) != nullptr) {
+ typeJson.add(*type);
+ type++;
+ }
+
+ JsonObject& props = descr.createNestedObject("properties");
+
+ ThingProperty* property = device->firstProperty;
+ while (property != nullptr) {
+ JsonObject& prop = props.createNestedObject(property->id);
+ switch (property->type) {
+ case BOOLEAN:
+ prop["type"] = "boolean";
+ break;
+ case NUMBER:
+ prop["type"] = "number";
+ break;
+ case STRING:
+ prop["type"] = "string";
+ break;
+ }
+ if (property->atType != nullptr) {
+ prop["@type"] = property->atType;
+}
+ prop["href"] = "/things/" + device->id + "/properties/" + property->id;
+ property = property->next;
+ }
+}
+};
+
+#endif // neither ESP32 nor ESP8266 defined
+
+#endif // MOZILLA_IOT_ETHERNETWEBTHINGADAPTER_H
diff --git a/examples/LevelSensor/LevelSensor.ino b/examples/LevelSensor/LevelSensor.ino
new file mode 100644
index 0000000..f5a7988
--- /dev/null
+++ b/examples/LevelSensor/LevelSensor.ino
@@ -0,0 +1,75 @@
+// -*- mode: c++; c-basic-offset: 2 -*-
+/**
+ * Simple server compliant with Mozilla's proposed WoT API
+ * Based on the RGBLamp example
+ * Tested on Arduino Mega with Ethernet Shield
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/.
+ */
+
+#include
+#include
+#include
+
+const char* deviceTypes[] = {"MultiLevelSensor", "Sensor", nullptr};
+ThingDevice device("AnalogSensorDevice", "Analog Sensor pluged in single pin", deviceTypes);
+ThingProperty property("level", "Analog Input pin", NUMBER, "LevelProperty");
+WebThingAdapter* adapter = NULL;
+
+const int sensorPin = A0;
+
+double lastValue = 0;
+
+int setupNetwork() {
+ Serial.println(__FUNCTION__);
+ //TODO: update with actual MAC address
+ uint8_t ETHERNET_MAC[] = { 0xDE, 0xAD, 0xBE, 0xEF, 0xFE, 0xED };
+ uint8_t error = Ethernet.begin(ETHERNET_MAC);
+ if (error == 0)
+ {
+ printf("error: %s\n",__FUNCTION__);
+ return -1;
+ }
+ return 0;
+}
+
+
+void setup(void) {
+ Serial.begin(115200);
+ Serial.println(__FUNCTION__);
+ while (0 != setupNetwork()){
+ delay(5000);
+ }
+ IPAddress ip = Ethernet.localIP();
+ Serial.print("log: IP=");
+ Serial.println(ip);
+ delay(3000);
+ adapter = new WebThingAdapter("analog-sensor", ip);
+ device.addProperty(&property);
+ adapter->addDevice(&device);
+ Serial.println("Starting HTTP server");
+ adapter->begin();
+ Serial.print("http://");
+ Serial.print(ip);
+ Serial.print("/things/");
+ Serial.println(device.id);
+}
+
+void loop(void) {
+ const int threshold = 1;
+ int value = analogRead(sensorPin);
+ double percent = (double) 100. - (value/1204.*100.);
+ if (abs(percent - lastValue) >= threshold) {
+ Serial.print("log: Value: ");
+ Serial.print(value);
+ Serial.print(" = ");
+ Serial.print(percent);
+ Serial.println("%");
+ ThingPropertyValue levelValue;
+ levelValue.number = percent;
+ property.setValue(levelValue);
+ lastValue = percent;
+ }
+ adapter->update();
+}