diff --git a/examples/FirebaseTranscriber_ESP8266/FirebaseTranscriber_ESP8266.ino b/examples/FirebaseTranscriber_ESP8266/FirebaseTranscriber_ESP8266.ino new file mode 100644 index 00000000..e3aee423 --- /dev/null +++ b/examples/FirebaseTranscriber_ESP8266/FirebaseTranscriber_ESP8266.ino @@ -0,0 +1,73 @@ +// +// Copyright 2016 Google Inc. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// + +// FirebaseDemo_ESP8266 is a sample that binds several pins to paths in +// firebase using the transcriber object. + +#include +#include + +// Set these to run example. +#define FIREBASE_HOST "example.firebaseio.com" +#define FIREBASE_AUTH "token_or_secret" +#define WIFI_SSID "SSID" +#define WIFI_PASSWORD "PASSWORD" + +#define FIREBASE_PATH "/fthing" +#define DIGITAL_IN D1 +#define DIGITAL_OUT BUILTIN_LED +#define ANALOG_IN A0 +#define ANALOG_OUT D8 +#define ANALOG_ACTIVATION_THRESHOLD 0.1 + + +thing::Config config = { + FIREBASE_HOST, + FIREBASE_AUTH, + FIREBASE_PATH, + WIFI_SSID, + WIFI_PASSWORD, + ANALOG_ACTIVATION_THRESHOLD, + DIGITAL_IN, + DIGITAL_OUT, + ANALOG_IN, + ANALOG_OUT}; + +void setup() { + Serial.begin(9600); + + // connect to wifi. + WiFi.begin(WIFI_SSID, WIFI_PASSWORD); + Serial.print("connecting"); + while (WiFi.status() != WL_CONNECTED) { + Serial.print("."); + delay(500); + } + Serial.println(); + Serial.print("connected: "); + Serial.println(WiFi.localIP()); + + pinMode(DIGITAL_IN, INPUT); + pinMode(DIGITAL_OUT, OUTPUT); + pinMode(ANALOG_IN, INPUT); + pinMode(ANALOG_OUT, OUTPUT); +} + +void loop() { + static thing::Transcriber tscribe(config); + tscribe.Loop(); + delay(200); +} diff --git a/src/Firebase.cpp b/src/Firebase.cpp index 2e925067..95cbecc8 100644 --- a/src/Firebase.cpp +++ b/src/Firebase.cpp @@ -133,6 +133,12 @@ FirebaseCall::FirebaseCall(const std::string& host, const std::string& auth, } } +FirebaseCall::~FirebaseCall() { + if (http_) { + http_->end(); + } +} + const JsonObject& FirebaseCall::json() { //TODO(edcoyne): This is not efficient, we should do something smarter with //the buffers. @@ -157,6 +163,7 @@ FirebaseSet::FirebaseSet(const std::string& host, const std::string& auth, json_ = response(); } } + // FirebasePush FirebasePush::FirebasePush(const std::string& host, const std::string& auth, const std::string& path, const std::string& value, diff --git a/src/Firebase.h b/src/Firebase.h index dcf0ff1f..8a352045 100644 --- a/src/Firebase.h +++ b/src/Firebase.h @@ -77,7 +77,7 @@ class FirebaseCall { const char* method, const std::string& path, const std::string& data = "", FirebaseHttpClient* http = NULL); - virtual ~FirebaseCall() {} + virtual ~FirebaseCall(); virtual const FirebaseError& error() const { return error_; diff --git a/src/FirebaseObject.cpp b/src/FirebaseObject.cpp index 47ef074d..44d07f00 100644 --- a/src/FirebaseObject.cpp +++ b/src/FirebaseObject.cpp @@ -25,7 +25,7 @@ FirebaseObject::FirebaseObject(const char* data) : data_{data} { // See: https://github.com/bblanchon/ArduinoJson/issues/279 } -bool FirebaseObject::getBool(const String& path) { +bool FirebaseObject::getBool(const String& path) const { JsonVariant variant = getJsonVariant(path); if (!variant.is()) { error_ = "failed to convert to bool"; @@ -34,7 +34,7 @@ bool FirebaseObject::getBool(const String& path) { return static_cast(variant); } -int FirebaseObject::getInt(const String& path) { +int FirebaseObject::getInt(const String& path) const { JsonVariant variant = getJsonVariant(path); if (!variant.is() && !variant.is()) { error_ = "failed to convert to number"; @@ -43,7 +43,7 @@ int FirebaseObject::getInt(const String& path) { return static_cast(variant); } -float FirebaseObject::getFloat(const String& path) { +float FirebaseObject::getFloat(const String& path) const { JsonVariant variant = getJsonVariant(path); if (!variant.is() && !variant.is()) { error_ = "failed to convert to number"; @@ -52,7 +52,7 @@ float FirebaseObject::getFloat(const String& path) { return static_cast(variant); } -String FirebaseObject::getString(const String& path) { +String FirebaseObject::getString(const String& path) const { JsonVariant variant = getJsonVariant(path); if (!variant.is()) { error_ = "failed to convert to string"; @@ -61,7 +61,7 @@ String FirebaseObject::getString(const String& path) { return static_cast(variant); } -JsonVariant FirebaseObject::getJsonVariant(const String& path) { +JsonVariant FirebaseObject::getJsonVariant(const String& path) const { String key(path); char* start = &key[0]; char* end = start + key.length(); diff --git a/src/FirebaseObject.h b/src/FirebaseObject.h index 3f23db7c..2e1aae44 100644 --- a/src/FirebaseObject.h +++ b/src/FirebaseObject.h @@ -41,35 +41,35 @@ class FirebaseObject { * \param optional path in the JSON object. * \return result as a bool. */ - bool getBool(const String& path = ""); + bool getBool(const String& path = "") const; /** * Return the value as an int. * \param optional path in the JSON object. * \return result as an integer. */ - int getInt(const String& path = ""); + int getInt(const String& path = "") const; /** * Return the value as a float. * \param optional path in the JSON object. * \return result as a float. */ - float getFloat(const String& path = ""); + float getFloat(const String& path = "") const; /** * Return the value as a String. * \param optional path in the JSON object. * \return result as a String. */ - String getString(const String& path = ""); + String getString(const String& path = "") const; /** * Return the value as a JsonVariant. * \param optional path in the JSON object. * \return result as a JsonVariant. */ - JsonVariant getJsonVariant(const String& path = ""); + JsonVariant getJsonVariant(const String& path = "") const; /** @@ -94,7 +94,7 @@ class FirebaseObject { String data_; StaticJsonBuffer buffer_; JsonVariant json_; - String error_; + mutable String error_; }; #endif // FIREBASE_OBJECT_H diff --git a/src/Portal.h b/src/Portal.h new file mode 100644 index 00000000..90a29176 --- /dev/null +++ b/src/Portal.h @@ -0,0 +1 @@ +#include "thing/Portal.h" diff --git a/src/Transcriber.h b/src/Transcriber.h new file mode 100644 index 00000000..0fd0ece7 --- /dev/null +++ b/src/Transcriber.h @@ -0,0 +1 @@ +#include "thing/Transcriber.h" diff --git a/src/thing/Config.h b/src/thing/Config.h new file mode 100644 index 00000000..3c3d5af3 --- /dev/null +++ b/src/thing/Config.h @@ -0,0 +1,26 @@ +#ifndef THING_CONFIG_H +#define THING_CONFIG_H + +namespace thing { + +struct Config { + std::string host; + std::string auth; + std::string path; + + std::string wifi_ssid; + std::string wifi_key; + + // If the change is analog value is less than this amount we don't send an + // update. + float analog_activation_threshold; + + int pin_digital_in; + int pin_digital_out; + int pin_analog_in; + int pin_analog_out; +}; + +} // namespace thing + +#endif // THING_CONFIG_H diff --git a/src/thing/Portal.cpp b/src/thing/Portal.cpp new file mode 100644 index 00000000..37bfc44d --- /dev/null +++ b/src/thing/Portal.cpp @@ -0,0 +1,112 @@ +#include "thing/Portal.h" +#include "third-party/arduino-json-5.6.7/include/ArduinoJson.h" + +namespace thing { + +Portal::Portal(const Config& config) : config_(config), callback_([](const Config&){}) {} + +void Portal::Start() { + server_.on("/", [&] () { + static const PROGMEM char page[] = R"( + + + + +
Host:
+
Auth:
+
Path:
+
Wifi SSID:
+
Wifi Key:
+
+
Loading....
+ + + + )"; + static const PROGMEM char type[] = "text/html"; + + server_.send_P(200, type, page); + }); + + server_.on("/config", [&] () { + DynamicJsonBuffer jsonBuffer; + if (server_.method() == HTTP_GET) { + JsonObject& root = jsonBuffer.createObject(); + root["host"] = config_.host.c_str(); + root["auth"] = config_.auth.c_str(); + root["path"] = config_.path.c_str(); + root["wifi_ssid"] = config_.wifi_ssid.c_str(); + root["wifi_key"] = config_.wifi_key.c_str(); + + String buffer; + root.printTo(buffer); + server_.send(200, "application/json", buffer); + } else if (server_.method() == HTTP_POST) { + if (!server_.hasArg("config")) { + server_.send(500, "text/plain", "Missing config.\r\n"); + return; + } + String config = server_.arg("config"); + JsonObject& root = jsonBuffer.parseObject(config.c_str()); + config_.host = root["host"].asString(); + config_.auth = root["auth"].asString(); + config_.path = root["path"].asString(); + config_.wifi_ssid = root["wifi_ssid"].asString(); + config_.wifi_key = root["wifi_key"].asString(); + callback_(config_); + server_.send(200, "text/plain", ""); + } + }); + server_.begin(); +} + +void Portal::Loop() { + server_.handleClient(); +} + +void Portal::NotifyOnUpdate(std::function callback) { + callback_ = callback; +} + +}; diff --git a/src/thing/Portal.h b/src/thing/Portal.h new file mode 100644 index 00000000..340902e7 --- /dev/null +++ b/src/thing/Portal.h @@ -0,0 +1,26 @@ +#ifndef THING_PORTAL_H +#define THING_PORTAL_H + +#include "ESP8266WebServer.h" +#include "thing/Config.h" + +namespace thing { + +class Portal { + public: + Portal(const Config& config); + + void Start(); + void Loop(); + void NotifyOnUpdate(std::function cb); + + private: + Config config_; + ESP8266WebServer server_; + std::function callback_; + +}; + +} // namespace thing + +#endif // THING_PORTAL_H diff --git a/src/thing/Transcriber.cpp b/src/thing/Transcriber.cpp new file mode 100644 index 00000000..4c1e19ea --- /dev/null +++ b/src/thing/Transcriber.cpp @@ -0,0 +1,97 @@ + +#include "thing/Transcriber.h" + +namespace thing { +namespace { + const char* kSubPathDigitalOut = "/digital_out"; + const char* kSubPathDigitalIn = "/digital_in"; + const char* kSubPathAnalogOut = "/analog_out"; + const char* kSubPathAnalogIn = "/analog_in"; +} // namespace + + +Transcriber::Transcriber(const Config& config) { + Init(config); +} + +void Transcriber::UpdateConfig(const Config& config) { + Init(config); +} + +void Transcriber::Init(const Config& config) { + path_ = config.path; + analog_activation_threshold_ = config.analog_activation_threshold; + pin_digital_out_ = config.pin_digital_out; + pin_digital_in_ = config.pin_digital_in; + pin_analog_out_ = config.pin_analog_out; + pin_analog_in_ = config.pin_analog_in; + + fbase_.reset(new Firebase(config.host, config.auth)); + stream_ = fbase_->streamPtr(path_); +} + +void Transcriber::SetValue(const std::string& path, const std::string& value) { + stream_.reset(nullptr); + fbase_->set(path, value); + stream_ = fbase_->streamPtr(path_); +} + +void Transcriber::Loop() { + // Read incoming data + if (stream_->available()) { + std::string event_str; + if (stream_->read(event_str) == FirebaseStream::PUT) { + FirebaseObject update(event_str.c_str()); + if (update.success()) { + if (update.getString("path") == "/") { + ProcessInitialUpdate(update); + } else { + ProcessIncrementalUpdate(update); + } + } + } + } + + // Send values to cloud + int new_digital_in = digitalRead(pin_digital_in_); + if (new_digital_in != digital_in_) { + SetValue(path_ + kSubPathDigitalIn, String(new_digital_in).c_str()); + digital_in_ = new_digital_in; + } + + float new_analog_in = analogRead(pin_analog_in_); + if (abs(new_analog_in - analog_in_) > analog_activation_threshold_) { + SetValue(path_ + kSubPathAnalogIn, String(new_analog_in).c_str()); + analog_in_ = new_analog_in; + } +} + +void Transcriber::ProcessInitialUpdate(const FirebaseObject& update) { + int digital_out = update.getInt((std::string("data") + kSubPathDigitalOut).c_str()); + if (!update.failed()) { + digitalWrite(pin_digital_out_, digital_out); + } + + float analog_out = update.getFloat((std::string("data") + kSubPathAnalogOut).c_str()); + if (!update.failed()) { + analogWrite(pin_analog_out_, analog_out); + } +} + +void Transcriber::ProcessIncrementalUpdate(const FirebaseObject& update) { + if (update.getString("path").equals(kSubPathDigitalOut)) { + int digital_out = update.getInt("data"); + if (!update.failed()) { + digitalWrite(pin_digital_out_, update.getInt("data")); + } + } + + if (update.getString("path").equals(kSubPathAnalogOut)) { + float analog_out = update.getFloat("data"); + if (!update.failed()) { + analogWrite(pin_analog_out_, analog_out); + } + } +} + +} // thing diff --git a/src/thing/Transcriber.h b/src/thing/Transcriber.h new file mode 100644 index 00000000..0727c17e --- /dev/null +++ b/src/thing/Transcriber.h @@ -0,0 +1,65 @@ +// +// Copyright 2016 Google Inc. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// + +#ifndef transcriber_h +#define transcriber_h + +#include +#include +#include +#include +#include "Firebase.h" +#include "FirebaseObject.h" +#include "thing/Config.h" + +namespace thing { + +class Transcriber { + public: + Transcriber(const Config& config); + void UpdateConfig(const Config& config); + void Loop(); + + private: + void Init(const Config& config); + + void ProcessInitialUpdate(const FirebaseObject& update); + void ProcessIncrementalUpdate(const FirebaseObject& update); + + // Wrapper around Firebase::Set that stops the stream before setting, + // Currently we have memory issues if we try to have two simultanious + // SSL connections. + void SetValue(const std::string& path, const std::string& value); + + std::unique_ptr fbase_; + std::unique_ptr stream_; + + int digital_in_ = 0; + float analog_activation_threshold_ = 0.0f; + float analog_in_ = 0.0f; + + int pin_digital_out_; + int pin_digital_in_; + int pin_analog_out_; + int pin_analog_in_; + + std::string path_; +}; + +}; // thing + + +#endif // transcriber_h diff --git a/test/modem/begin-command_test.cpp b/test/modem/begin-command_test.cpp index c8ec71bb..e8682cfe 100644 --- a/test/modem/begin-command_test.cpp +++ b/test/modem/begin-command_test.cpp @@ -50,7 +50,7 @@ TEST_F(BeginCommandTest, hostOnly) { ExpectOutput("+OK"); BeginCommand command; - ASSERT_TRUE(command.execute("BEGIN", &in_, &out_)); + ASSERT_TRUE(command.execute("BEGIN_DB", &in_, &out_)); std::unique_ptr firebase(command.firebase()); EXPECT_EQ("", firebase->auth()); @@ -64,7 +64,7 @@ TEST_F(BeginCommandTest, hostAndAuth) { ExpectOutput("+OK"); BeginCommand command; - ASSERT_TRUE(command.execute("BEGIN", &in_, &out_)); + ASSERT_TRUE(command.execute("BEGIN_DB", &in_, &out_)); std::unique_ptr firebase(command.firebase()); EXPECT_EQ(auth, firebase->auth()); @@ -75,7 +75,7 @@ TEST_F(BeginCommandTest, neitherHostNorAuth) { ExpectOutputStartsWith("-FAIL"); BeginCommand command; - ASSERT_FALSE(command.execute("BEGIN", &in_, &out_)); + ASSERT_FALSE(command.execute("BEGIN_DB", &in_, &out_)); std::unique_ptr firebase(command.firebase()); EXPECT_FALSE(firebase);