소스 검색

Reasonably complete REST server. Will probably refactor sloppy messy token routes later

Chris Mullins 8 년 전
부모
커밋
c1a57eb398
7개의 변경된 파일346개의 추가작업 그리고 44개의 파일을 삭제
  1. 39 0
      lib/Helpers/IntParsing.h
  2. 0 11
      lib/MiLight/MiLightClient.cpp
  3. 70 0
      lib/Vector/Vector.h
  4. 104 0
      lib/WebServer/WebServer.cpp
  5. 65 0
      lib/WebServer/WebServer.h
  6. 1 0
      platformio.ini
  7. 67 33
      src/main.cpp

+ 39 - 0
lib/Helpers/IntParsing.h

@@ -0,0 +1,39 @@
+#ifndef _INTPARSING_H
+#define _INTPARSING_H
+
+#include <Arduino.h>
+
+template <typename T>
+const T strToHex(const String& s) {
+  T value = 0;
+  T base = 1;
+  
+  for (int i = s.length() - 1; i >= 0; i--) {
+    const char c = s.charAt(i);
+    
+    if (c >= '0' && c <= '9') {
+      value += ((c - '0') * base);
+    } else if (c >= 'a' && c <= 'f') {
+      value += ((c - 'a' + 10) * base);
+    } else if (c >= 'A' && c <= 'F') {
+      value += ((c - 'A' + 10) * base);
+    } else {
+      break;
+    }
+    
+    base <<= 4;
+  }
+  
+  return value;
+}
+
+template <typename T>
+const T parseInt(const String& s) {
+  if (s.startsWith("0x")) {
+    return strToHex<T>(s.substring(2));
+  } else {
+    return s.toInt();
+  }
+}
+
+#endif

+ 0 - 11
lib/MiLight/MiLightClient.cpp

@@ -48,20 +48,9 @@ void MiLightClient::write(MiLightPacket& packet, const unsigned int resendCount)
   uint8_t packetBytes[MILIGHT_PACKET_LENGTH];
   serializePacket(packetBytes, packet);
   
-  Serial.print("Packet bytes (");
-  Serial.print(MILIGHT_PACKET_LENGTH);
-  Serial.print(" bytes): ");
-  for (int i = 0; i < MILIGHT_PACKET_LENGTH; i++) {
-    Serial.print(packetBytes[i], HEX);
-    Serial.print(" ");
-  }
-  Serial.println();
-  
   for (int i = 0; i < resendCount; i++) {
-    Serial.print(".");
     radio.write(packetBytes, MILIGHT_PACKET_LENGTH);
   }
-  Serial.println();
 }
 
 

+ 70 - 0
lib/Vector/Vector.h

@@ -0,0 +1,70 @@
+#ifndef _VECTOR_H
+#define _VECTOR_H
+
+// Minimal class to replace std::vector
+template<typename Data>
+class Vector {
+
+    size_t d_size; // Stores no. of actually stored objects
+    size_t d_capacity; // Stores allocated capacity
+    Data *d_data; // Stores data this is this "heap" we need a function that returns a pointer to this value, to print it
+public:
+    Vector() : d_size(0), d_capacity(0), d_data(0) {}; // Default constructor
+
+    Vector(Vector const &other) : d_size(other.d_size), d_capacity(other.d_capacity), d_data(0) //for when you set 1 vector = to another
+    {
+        d_data = (Data *)malloc(d_capacity*sizeof(Data));
+        memcpy(d_data, other.d_data, d_size*sizeof(Data));
+    }; // Copy constuctor
+
+    ~Vector() //this gets called
+    {
+        free(d_data);
+    }; // Destructor
+
+    Vector &operator=(Vector const &other)
+    {
+        free(d_data);
+        d_size = other.d_size;
+        d_capacity = other.d_capacity;
+        d_data = (Data *)malloc(d_capacity*sizeof(Data));
+        memcpy(d_data, other.d_data, d_size*sizeof(Data));
+        return *this;
+    }; // Needed for memory management
+
+    void push_back(Data const &x)
+    {
+        if (d_capacity == d_size) //when he pushes data onto the heap, he checks to see if the storage is full
+            resize();  //if full - resize
+
+        d_data[d_size++] = x;
+    }; // Adds new value. If needed, allocates more space
+
+    void Clear() //here
+    {
+        memset(d_data, 0, d_size);
+        d_capacity = 0;
+        d_size = 0;
+        free(d_data);
+    }
+
+    size_t size() const { return d_size; }; // Size getter
+
+    Data const &operator[](size_t idx) const { return d_data[idx]; }; // Const getter
+
+    Data &operator[](size_t idx) { return d_data[idx]; }; // Changeable getter
+
+    Data *pData() { return (Data*)d_data; }
+
+private:
+    void resize()
+    {
+        d_capacity = d_capacity ? d_capacity * 2 : 1;
+        Data *newdata = (Data *)malloc(d_capacity*sizeof(Data)); //allocates new memory
+        memcpy(newdata, d_data, d_size * sizeof(Data));  //copies all the old memory over
+        free(d_data);                                          //free old
+        d_data = newdata;
+    };// Allocates double the old space
+};
+
+#endif

+ 104 - 0
lib/WebServer/WebServer.cpp

@@ -0,0 +1,104 @@
+#include <WebServer.h>
+#include <Vector.h>
+
+struct StringToken {
+  StringToken(const int start, const int end) 
+    : start(start), end(end) { }
+  
+  int start;
+  int end;
+  
+  const String extract(const String& s) {
+    return s.substring(start, end);
+  }
+};
+
+Vector<StringToken> tokenize(const String& path) {
+  Vector<StringToken> tokenPositions;
+  int lastStart = 0;
+  int currentPosition = 0;
+  
+  for (int i = 0; i < path.length(); i++) {
+    if (path.charAt(i) == '/' || i == path.length()-1) {
+      // If we're in the last position, include the last character if it isn't
+      // a '/'
+      if (path.charAt(i) != '/') {
+        currentPosition++;
+      }
+      
+      if (lastStart > 0 && currentPosition > lastStart) {
+        StringToken token(lastStart, currentPosition);
+        tokenPositions.push_back(token);
+      }
+      
+      lastStart = i+1;
+    }
+      
+    currentPosition++;
+  }
+  
+  return tokenPositions;
+}
+
+void WebServer::resetPathMatches() {
+  delete buffer;
+  buffer = new DynamicJsonBuffer();
+  pathMatches = &buffer->createObject();
+}
+
+bool WebServer::matchesPattern(const String& pattern, const String& url) {
+  Vector<StringToken> patternTokens = tokenize(pattern);
+  Vector<StringToken> urlTokens = tokenize(url);
+  
+  for (int i = 0; i < patternTokens.size(); i++) {
+    String a = patternTokens[i].extract(pattern);
+    String b = urlTokens.size() <= i ? "" : urlTokens[i].extract(url);
+  }
+  
+  if (patternTokens.size() != urlTokens.size()) {
+    return false;
+  }
+  
+  for (int i = 0; i < patternTokens.size(); i++) {
+    const String& pT = patternTokens[i].extract(pattern);
+    const String& uT = urlTokens[i].extract(url);
+    
+    if (!pT.startsWith(":") && pT != uT) {
+      return false;
+    }
+  }
+  
+  resetPathMatches();
+  
+  for (int i = 0; i < patternTokens.size(); i++) {
+    // Trim off leading ':'
+    const String& pT = patternTokens[i].extract(pattern).substring(1);
+    const String& uT = urlTokens[i].extract(url);
+    
+    (*pathMatches)[pT] = uT;
+  }
+  
+  return true;
+}
+
+void WebServer::checkPatterns() {
+  for (int i = 0; i < handlers.size(); i++) {
+    const PatternHandler* handler = handlers[i];
+    
+    if (method() == handler->method 
+      && matchesPattern(handler->pattern, uri())) {
+      handler->fn();
+      return;
+    }
+  }
+  
+  if (notFoundHandler) {
+    notFoundHandler();
+  } else {
+    send(404, "text/plain", String("Not found: ") + _currentUri);
+  }
+}
+
+void WebServer::onPattern(const String& pattern, const HTTPMethod method, const THandlerFunction fn) {
+  handlers.push_back(new PatternHandler(pattern, method, fn));
+}

+ 65 - 0
lib/WebServer/WebServer.h

@@ -0,0 +1,65 @@
+#ifndef _WEBSERVER_H
+#define _WEBSERVER_H
+
+#include <Arduino.h>
+#include <ArduinoJson.h>
+#include <ESP8266WebServer.h>
+#include <Vector.h>
+
+struct PatternHandler {
+  PatternHandler(const String& pattern, const HTTPMethod method, const ESP8266WebServer::THandlerFunction fn)
+    : pattern(pattern),
+      method(method),
+      fn(fn)
+    { }
+    
+  const String pattern;
+  const HTTPMethod method;
+  const ESP8266WebServer::THandlerFunction fn;
+};
+
+class WebServer : public ESP8266WebServer {
+public:
+  WebServer(int port) : 
+    ESP8266WebServer(port) { 
+    
+    ESP8266WebServer::onNotFound([&]() { checkPatterns(); });
+  }
+  
+  ~WebServer() {
+    delete buffer;
+  }
+  
+  bool matchesPattern(const String& pattern, const String& url);
+  
+  void onPattern(const String& pattern, const HTTPMethod method, const THandlerFunction fn);
+  
+  String arg(String key) {
+    if (pathMatches && pathMatches->containsKey(key)) {
+      return pathMatches->get<String>(key);
+    }
+    
+    return ESP8266WebServer::arg(key);
+  }
+  
+  inline bool clientConnected() { 
+    return _currentClient && _currentClient.connected();
+  }
+  
+  inline void onNotFound(THandlerFunction fn) {
+    notFoundHandler = fn;
+  }
+  
+private:
+  
+  void resetPathMatches();
+  void checkPatterns();
+
+  DynamicJsonBuffer* buffer;
+  JsonObject* pathMatches;
+  Vector<PatternHandler*> handlers;
+  THandlerFunction notFoundHandler;
+
+};
+
+#endif

+ 1 - 0
platformio.ini

@@ -17,3 +17,4 @@ lib_deps =
   SPI
   WiFiManager
   ArduinoJson
+  http://github.com/zacsketches/Arduino_Vector.git

+ 67 - 33
src/main.cpp

@@ -4,60 +4,51 @@
 #include <WiFiManager.h>
 #include <ArduinoJson.h>
 #include <stdlib.h>
+#include <fs.h>
 
 #include <PL1167_nRF24.h>
 #include <MiLightRadio.h>
 #include <MiLightClient.h>
-#include <ESP8266WebServer.h>
+#include <WebServer.h>
+#include <IntParsing.h>
 
 #define CE_PIN D0 
 #define CSN_PIN D8
 
+#define WEB_INDEX_FILENAME "/index.html"
+
 RF24 radio(CE_PIN, CSN_PIN);
 PL1167_nRF24 prf(radio);
 MiLightRadio mlr(prf);
 MiLightClient milightClient(mlr);
 
 WiFiManager wifiManager;
-ESP8266WebServer server(80);
+WebServer server(80);
+File updateFile;
 
-template <typename T>
-const T strToHex(const String& s) {
-  T value = 0;
-  uint32_t base = 1;
+void handleUpdateGateway() {
+  DynamicJsonBuffer buffer;
+  JsonObject& request = buffer.parse(server.arg("plain"));
   
-  for (int i = s.length() - 1; i >= 0; i--) {
-    const char c = s.charAt(i);
-    
-    if (c >= '0' && c <= '9') {
-      value += ((c - '0') * base);
-    } else if (c >= 'a' && c <= 'f') {
-      value += ((c - 'a' + 10) * base);
-    } else if (c >= 'A' && c <= 'F') {
-      value += ((c - 'A' + 10) * base);
-    } else {
-      break;
+  const uint16_t gatewayId = parseInt<uint16_t>(server.arg("gateway_id"));
+  
+  if (request.containsKey("status")) {
+    if (request["status"] == "on") {
+      milightClient.allOn(gatewayId);
+    } else if (request["status"] == "off") {
+      milightClient.allOff(gatewayId);
     }
-    
-    base <<= 4;
   }
   
-  return value;
+  server.send(200, "application/json", "true");
 }
 
-void handleUpdateGateway() {
+void handleUpdateGroup() {
   DynamicJsonBuffer buffer;
   JsonObject& request = buffer.parse(server.arg("plain"));
   
-  const String gatewayIdStr = request["gateway_id"];
-  const uint8_t groupId = request["group_id"];
-  uint16_t gatewayId;
-  
-  if (gatewayIdStr.startsWith("0x")) {
-    gatewayId = strToHex<uint16_t>(gatewayIdStr.substring(2));
-  } else {
-    gatewayId = request["gateway_id"];
-  }
+  const uint16_t gatewayId = parseInt<uint16_t>(server.arg("gateway_id"));
+  const uint8_t groupId = server.arg("group_id").toInt();
   
   if (request.containsKey("status")) {
     MiLightStatus status = (request.get<String>("status") == "on") ? ON : OFF;
@@ -99,6 +90,10 @@ void handleUpdateGateway() {
 
 void handleListenGateway() {
   while (!mlr.available()) {
+    if (!server.clientConnected()) {
+      return;
+    }
+    
     yield();
   }
   
@@ -120,14 +115,53 @@ void handleListenGateway() {
   server.send(200, "text/plain", response);
 }
 
+void serveFile(const String& file, const char* contentType = "text/html") {
+  if (SPIFFS.exists(file)) {
+    File f = SPIFFS.open(file, "r");
+    server.send(200, "text/html", f.readString());
+    f.close();
+  } else {
+    server.send(404);
+  }
+}
+
+void handleIndex() {
+  serveFile(WEB_INDEX_FILENAME);
+}
+
+void handleWebUpdate() {
+  HTTPUpload& upload = server.upload();
+  
+  if (upload.status == UPLOAD_FILE_START) {
+    updateFile = SPIFFS.open(WEB_INDEX_FILENAME, "w");
+  } else if(upload.status == UPLOAD_FILE_WRITE){
+    if (updateFile.write(upload.buf, upload.currentSize)) {
+      Serial.println("Error updating web file");
+    }
+  } else if (upload.status == UPLOAD_FILE_END) {
+    updateFile.close();
+  }
+  
+  yield();
+}
+
+void onWebUpdated() {
+  server.sendHeader("Location", "/");
+  server.send(302);
+}
+
 void setup() {
   Serial.begin(9600);
   wifiManager.autoConnect();
   mlr.begin();
+  SPIFFS.begin();
   
-  server.on("/gateway", HTTP_PUT, handleUpdateGateway);
-  server.on("/gateway", HTTP_GET, handleListenGateway);
-  server.on("/update", HTTP_POST, 
+  server.on("/", HTTP_GET, handleIndex);
+  server.on("/gateway_traffic", HTTP_GET, handleListenGateway);
+  server.onPattern("/gateway/:gateway_id/:group_id", HTTP_PUT, handleUpdateGroup);
+  server.onPattern("/gateway/:gateway_id", HTTP_PUT, handleUpdateGateway);
+  server.on("/web", HTTP_POST, onWebUpdated, handleWebUpdate);
+  server.on("/firmware", HTTP_POST, 
     [](){
       server.sendHeader("Connection", "close");
       server.sendHeader("Access-Control-Allow-Origin", "*");