Skip to content

Commit

Permalink
tested new API; clean up .ino
Browse files Browse the repository at this point in the history
  • Loading branch information
tcsullivan committed May 12, 2024
1 parent d4654e6 commit 214c05b
Show file tree
Hide file tree
Showing 10 changed files with 381 additions and 230 deletions.
206 changes: 206 additions & 0 deletions noisemeter-device/api.cpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,206 @@
/* noisemeter-device - Firmware for CivicTechTO's Noisemeter Device
* Copyright (C) 2024 Clyne Sullivan
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
#include "api.h"
#include "board.h"
#include "certs.h"
#include "url-encode.h"

#include <HTTPClient.h>
#include <WiFiClientSecure.h>

#include <cmath>

API::Request::Request(const char endpoint[])
{
url.reserve(256);
url.concat(Base);
url.concat(endpoint);
url.concat('?');
}

API::Request& API::Request::addParam(const char param[], String value)
{
url.concat(param);
url.concat('=');
url.concat(urlEncode(value));
url.concat('&');
return *this;
}

std::optional<JsonDocument> API::sendAuthorizedRequest(const API::Request& req)
{
WiFiClientSecure client;
client.setCACert(rootCertificate());

#ifdef API_VERBOSE
SERIAL.print("[api] Authorized request: ");
SERIAL.println(req.url);
#endif

HTTPClient https;
if (https.begin(client, req.url)) {
https.addHeader("Authorization", String("Token ") + token);

if (const auto code = https.GET(); code > 0) {
if (code == HTTP_CODE_OK || code == HTTP_CODE_MOVED_PERMANENTLY) {
const auto response = https.getString();
#ifdef API_VERBOSE
SERIAL.print("[api] Response: ");
SERIAL.println(response);
#endif
https.end();

return responseToJson(response);
#ifdef API_VERBOSE
} else {
SERIAL.print("[api] HTTP error: ");
SERIAL.println(code);
#endif
}
#ifdef API_VERBOSE
} else {
SERIAL.print("[api] HTTP error: ");
SERIAL.println(code);
#endif
}
#ifdef API_VERBOSE
} else {
SERIAL.println("[api] Failed to https.begin()");
#endif
}

return {};
}

std::optional<JsonDocument> API::sendUnauthorizedRequest(const API::Request& req)
{
WiFiClientSecure client;
client.setCACert(rootCertificate());

#ifdef API_VERBOSE
SERIAL.print("[api] Unauthorized request: ");
SERIAL.println(req.url);
#endif

HTTPClient https;
if (https.begin(client, req.url)) {
if (const auto code = https.GET(); code > 0) {
if (code == HTTP_CODE_OK || code == HTTP_CODE_MOVED_PERMANENTLY) {
const auto response = https.getString();
#ifdef API_VERBOSE
SERIAL.print("[api] Response: ");
SERIAL.println(response);
#endif
https.end();

return responseToJson(response);
#ifdef API_VERBOSE
} else {
SERIAL.print("[api] HTTP error: ");
SERIAL.println(code);
#endif
}
#ifdef API_VERBOSE
} else {
SERIAL.print("[api] HTTP error: ");
SERIAL.println(code);
#endif
}
#ifdef API_VERBOSE
} else {
SERIAL.println("[api] Failed to https.begin()");
#endif
}

return {};
}

std::optional<JsonDocument> API::responseToJson(const String& response)
{
JsonDocument doc;
const auto error = deserializeJson(doc, response);

if (error) {
SERIAL.println(error.f_str());
return {};
} else {
return doc;
}
}

API::API(UUID id_, String token_):
id(id_), token(token_) {}

bool API::sendMeasurement(const DataPacket& packet)
{
const auto request = Request("measurement")
.addParam("device", id)
.addParam("timestamp", packet.timestamp)
.addParam("min", String(std::lround(packet.minimum)))
.addParam("max", String(std::lround(packet.maximum)))
.addParam("mean", String(std::lround(packet.average)));

const auto resp = sendAuthorizedRequest(request);
return resp && (*resp)["result"] == "ok";
}

bool API::sendDiagnostics(String version, String boottime)
{
const auto request = Request("measurement")
.addParam("device", id)
.addParam("version", version)
.addParam("boottime", boottime);

const auto resp = sendAuthorizedRequest(request);
return resp && (*resp)["result"] == "ok";
}

std::optional<String> API::sendRegister(String email)
{
const auto request = Request("device/register")
.addParam("device", id)
.addParam("email", email);

const auto resp = sendUnauthorizedRequest(request);
if (resp && (*resp)["result"] == "ok")
return (*resp)["token"];
else
return {};
}

std::optional<API::LatestSoftware> API::getLatestSoftware()
{
const auto request = Request("software/latest");

const auto resp = sendUnauthorizedRequest(request);
if (resp && (*resp)["result"] == "ok") {
LatestSoftware ls = {
(*resp)["version"],
(*resp)["url"]
};

return ls;
} else {
return {};
}
}

const char *API::rootCertificate()
{
return cert_ISRG_Root_X1;
}

67 changes: 67 additions & 0 deletions noisemeter-device/api.h
Original file line number Diff line number Diff line change
@@ -0,0 +1,67 @@
/// @file
/// @brief API implementation for communication with server
/* noisemeter-device - Firmware for CivicTechTO's Noisemeter Device
* Copyright (C) 2024 Clyne Sullivan
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
#ifndef API_H
#define API_H

#include "data-packet.h"
#include "UUID/UUID.h"

#include <ArduinoJson.h>
#include <WString.h>

#include <optional>

class API
{
constexpr static const char Base[] = "https://noisemeter.webcomand.com/api/v1/";

struct Request {
Request(const char endpoint[]);

Request& addParam(const char param[], String value);

String url;
};

public:
struct LatestSoftware {
String version;
String url;
};

API(UUID id_, String token_ = {});

bool sendMeasurement(const DataPacket& packet);
bool sendDiagnostics(String version, String boottime);
std::optional<String> sendRegister(String email);
std::optional<LatestSoftware> getLatestSoftware();

static const char *rootCertificate();

private:
UUID id;
String token;

std::optional<JsonDocument> responseToJson(const String& response);
std::optional<JsonDocument> sendAuthorizedRequest(const Request& req);
std::optional<JsonDocument> sendUnauthorizedRequest(const Request& req);
};

#endif // API_H

Empty file modified noisemeter-device/certs.py
100644 → 100755
Empty file.
3 changes: 3 additions & 0 deletions noisemeter-device/config.h.example
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,9 @@
// Uncomment to print credentials over serial (for debugging).
//#define STORAGE_SHOW_CREDENTIALS

// Uncomment to print verbose API logging over serial (for debugging).
//#define API_VERBOSE

// Define only *one* of the follwoing board options.
// If using PlatformIO, the selected 'env' will override this selection.
#if !defined(BOARD_ESP32_PCB) && \
Expand Down
Loading

0 comments on commit 214c05b

Please sign in to comment.