Skip to content

Commit

Permalink
Merge branch 'main' of github.com:CivicTechTO/proj-noisemeter-device …
Browse files Browse the repository at this point in the history
…into dns2
  • Loading branch information
tcsullivan committed Mar 9, 2024
2 parents e119ff6 + eade115 commit 9e41a2b
Show file tree
Hide file tree
Showing 3 changed files with 140 additions and 82 deletions.
27 changes: 27 additions & 0 deletions noisemeter-device/data-packet.h
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
#ifndef DATAPACKET_H
#define DATAPACKET_H

#include "timestamp.h"

#include <algorithm>

struct DataPacket
{
constexpr DataPacket() = default;

void add(float sample) noexcept {
count++;
minimum = std::min(minimum, sample);
maximum = std::max(maximum, sample);
average += (sample - average) / count;
}

int count = 0;
float minimum = 999.f;
float maximum = 0.f;
float average = 0.f;
Timestamp timestamp = Timestamp::invalidTimestamp();
};

#endif // DATAPACKET_H

149 changes: 67 additions & 82 deletions noisemeter-device/noisemeter-device.ino
Original file line number Diff line number Diff line change
Expand Up @@ -15,33 +15,32 @@
#include <UUID.h> // https://github.com/RobTillaart/UUID
#include <WiFi.h>
#include <WiFiClientSecure.h>
#include <WiFiMulti.h>
#include <dummy.h> // ESP32 core
#include <driver/i2s.h> // ESP32 core

#include "access-point.h"
#include "board.h"
#include "data-packet.h"
#include "sos-iir-filter.h"
#include "certs.h"
#include "secret.h"
#include "storage.h"

#include <cstdint>
#include <ctime>
#include <forward_list>
#include <iterator>

#if defined(BUILD_PLATFORMIO) && defined(BOARD_ESP32_PCB)
HWCDC USBSerial;
#endif

static Storage Creds;
static WiFiMulti WiFiMulti;

// Uncomment these to disable WiFi and/or data upload
//#define UPLOAD_DISABLED

const unsigned long UPLOAD_INTERVAL_MS = 60000 * 5; // Upload every 5 mins
// const unsigned long UPLOAD_INTERVAL_MS = 30000; // Upload every 30 secs
const unsigned long MIN_READINGS_BEFORE_UPLOAD = 20;
const unsigned long UPLOAD_INTERVAL_SEC = 60 * 5; // Upload every 5 mins
// const unsigned long UPLOAD_INTERVAL_SEC = 30; // Upload every 30 secs

//
// Constants & Config
Expand Down Expand Up @@ -91,43 +90,15 @@ static_assert(sizeof(float) == sizeof(int32_t));
using SampleBuffer alignas(4) = float[SAMPLES_SHORT];
static SampleBuffer samples;

// Not sure if WiFiClientSecure checks the validity date of the certificate.
// Setting clock just to be sure...
void setClock() {
configTime(0, 0, "pool.ntp.org");

SERIAL.print("Waiting for NTP time sync: ");
std::time_t nowSecs = std::time(nullptr);
while (nowSecs < 8 * 3600 * 2) {
delay(500);
SERIAL.print(".");
yield();
nowSecs = time(nullptr);
}
SERIAL.println();

char timebuf[32];

const auto timeinfo = std::gmtime(&nowSecs);
SERIAL.print("Current time: ");
if (std::strftime(timebuf, sizeof(timebuf), "%c", timeinfo) > 0)
SERIAL.println(timebuf);
else
SERIAL.println("(error)");
}

// Sampling Buffers & accumulators
sum_queue_t q;
uint32_t Leq_samples = 0;
double Leq_sum_sqr = 0;
double Leq_dB = 0;

// Noise Level Readings
int numberOfReadings = 0;
float minReading = MIC_OVERLOAD_DB;
float maxReading = MIC_NOISE_DB;
float sumReadings = 0;
unsigned long lastUploadMillis = 0;
static std::forward_list<DataPacket> packets;
static Timestamp lastUpload = Timestamp::invalidTimestamp();

/**
* Initialization routine.
Expand Down Expand Up @@ -157,6 +128,8 @@ void setup() {

initMicrophone();

packets.emplace_front();

#ifndef UPLOAD_DISABLED
// Run the access point if it is requested or if there are no valid credentials.
bool resetPressed = !digitalRead(PIN_BUTTON);
Expand All @@ -177,19 +150,23 @@ void setup() {
SERIAL.println(ssid);

WiFi.mode(WIFI_STA);
WiFiMulti.addAP(ssid.c_str(), Creds.get(Storage::Entry::Passkey).c_str());
WiFi.begin(ssid.c_str(), Creds.get(Storage::Entry::Passkey).c_str());

// wait for WiFi connection
SERIAL.print("Waiting for WiFi to connect...");
while ((WiFiMulti.run() != WL_CONNECTED)) {
while (WiFi.status() != WL_CONNECTED) {
SERIAL.print(".");
delay(500);
}
SERIAL.println("\nConnected to the WiFi network");
SERIAL.print("Local ESP32 IP: ");
SERIAL.println(WiFi.localIP());

setClock();
SERIAL.println("Waiting for NTP time sync...");
Timestamp::synchronize();
lastUpload = Timestamp();
SERIAL.print("Current time: ");
SERIAL.println(lastUpload);
#endif // !UPLOAD_DISABLED

digitalWrite(PIN_LED1, HIGH);
Expand All @@ -199,12 +176,34 @@ void loop() {
readMicrophoneData();

#ifndef UPLOAD_DISABLED
if (canUploadData()) {
WiFiClientSecure* client = new WiFiClientSecure;
float average = sumReadings / numberOfReadings;
String payload = createJSONPayload(DEVICE_ID, minReading, maxReading, average);
uploadData(client, payload);
delete client;
// Has it been at least the upload interval since we uploaded data?
const auto now = Timestamp();
if (lastUpload.secondsBetween(now) >= UPLOAD_INTERVAL_SEC) {
lastUpload = now;
packets.front().timestamp = now;

if (WiFi.status() != WL_CONNECTED) {
SERIAL.println("Attempting WiFi reconnect...");
WiFi.reconnect();
delay(5000);
}

if (WiFi.status() == WL_CONNECTED) {
packets.remove_if([](const auto& pkt) {
const auto payload = createJSONPayload(DEVICE_ID, pkt);

WiFiClientSecure client;
return uploadData(&client, payload) == 0;
});
}

if (!packets.empty()) {
SERIAL.print(std::distance(packets.cbegin(), packets.cend()));
SERIAL.println(" packets still need to be sent!");
}

// Create new packet for next measurements
packets.emplace_front();
}
#endif // !UPLOAD_DISABLED
}
Expand All @@ -223,8 +222,10 @@ void printReadingToConsole(double reading) {
String output = "";
output += reading;
output += "dB";
if (numberOfReadings > 1) {
output += " [+" + String(numberOfReadings - 1) + " more]";

const auto currentCount = packets.front().count;
if (currentCount > 1) {
output += " [+" + String(currentCount - 1) + " more]";
}
SERIAL.println(output);
}
Expand Down Expand Up @@ -256,39 +257,33 @@ void saveNetworkCreds(WebServer& httpServer) {
SERIAL.println("Error: Invalid network credentials!");
}

String createJSONPayload(String deviceId, float min, float max, float average) {
String createJSONPayload(String deviceId, const DataPacket& dp)
{
#ifdef BUILD_PLATFORMIO
JsonDocument doc;
#else
DynamicJsonDocument doc (2048);
#endif

doc["parent"] = "/Bases/nm1";
doc["data"]["type"] = "comand";
doc["data"]["version"] = "1.0";
doc["data"]["contents"][0]["Type"] = "Noise";
doc["data"]["contents"][0]["Min"] = min;
doc["data"]["contents"][0]["Max"] = max;
doc["data"]["contents"][0]["Mean"] = average;
doc["data"]["contents"][0]["Min"] = dp.minimum;
doc["data"]["contents"][0]["Max"] = dp.maximum;
doc["data"]["contents"][0]["Mean"] = dp.average;
doc["data"]["contents"][0]["DeviceID"] = deviceId; // TODO
doc["data"]["contents"][0]["Timestamp"] = String(dp.timestamp);

// Serialize JSON document
String json;
serializeJson(doc, json);
return json;
}

bool canUploadData() {
// Has it been at least the upload interval since we uploaded data?
long now = millis();
long msSinceLastUpload = now - lastUploadMillis;
if (msSinceLastUpload < UPLOAD_INTERVAL_MS) return false;

// Do we have the minimum number of readings stored to form a resonable average?
if (numberOfReadings < MIN_READINGS_BEFORE_UPLOAD) return false;
return true;
}

void uploadData(WiFiClientSecure* client, String json) {
// Given a serialized JSON payload, upload the data to webcomand
int uploadData(WiFiClientSecure* client, String json)
{
if (client) {
client->setCACert(cert_ISRG_Root_X1);
{
Expand Down Expand Up @@ -321,14 +316,19 @@ void uploadData(WiFiClientSecure* client, String json) {
if (httpCode == HTTP_CODE_OK || httpCode == HTTP_CODE_MOVED_PERMANENTLY) {
String payload = https.getString();
SERIAL.println(payload);
} else {
SERIAL.printf("[HTTPS] POST... failed, error: %s\n", https.errorToString(httpCode).c_str());
return -1;
}
} else {
SERIAL.printf("[HTTPS] POST... failed, error: %s\n", https.errorToString(httpCode).c_str());
return -1;
}

https.end();
} else {
SERIAL.printf("[HTTPS] Unable to connect\n");
return -1;
}

// End extra scoping block
Expand All @@ -337,21 +337,11 @@ void uploadData(WiFiClientSecure* client, String json) {
// delete client;
} else {
SERIAL.println("Unable to create client");
return -1;
}

long now = millis();
lastUploadMillis = now;
resetReading();
}; // Given a serialized JSON payload, upload the data to webcomand

void resetReading() {
SERIAL.println("Resetting readings cache...");
numberOfReadings = 0;
minReading = MIC_OVERLOAD_DB;
maxReading = MIC_NOISE_DB;
sumReadings = 0;
SERIAL.println("Reset complete");
};
return 0;
}

//
// I2S Microphone sampling setup
Expand Down Expand Up @@ -440,13 +430,8 @@ void readMicrophoneData() {
Leq_sum_sqr = 0;
Leq_samples = 0;

// SERIAL.printf("Calculated dB: %.1fdB\n", Leq_dB);
printReadingToConsole(Leq_dB);

if (Leq_dB < minReading) minReading = Leq_dB;
if (Leq_dB > maxReading) maxReading = Leq_dB;
sumReadings += Leq_dB;
numberOfReadings++;
packets.front().add(Leq_dB);

digitalWrite(PIN_LED2, LOW);
delay(30);
Expand Down
46 changes: 46 additions & 0 deletions noisemeter-device/timestamp.h
Original file line number Diff line number Diff line change
@@ -0,0 +1,46 @@
#ifndef TIMESTAMP_H
#define TIMESTAMP_H

#include <Arduino.h>
#include <ctime>

class Timestamp
{
public:
Timestamp(std::time_t tm_ = std::time(nullptr)):
tm(tm_) {}

bool valid() const noexcept {
return tm >= 8 * 3600 * 2;
}

operator String() const noexcept {
char tsbuf[32];
const auto timeinfo = std::gmtime(&tm);
const auto success = std::strftime(tsbuf, sizeof(tsbuf), "%c", timeinfo) > 0;

return success ? tsbuf : "(error)";
}

auto secondsBetween(Timestamp ts) const noexcept {
return std::difftime(ts.tm, tm);
}

static void synchronize() {
configTime(0, 0, "pool.ntp.org");

do {
delay(1000);
} while (!Timestamp().valid());
}

static Timestamp invalidTimestamp() {
return Timestamp(0);
}

private:
std::time_t tm;
};

#endif // TIMESTAMP_H

0 comments on commit 9e41a2b

Please sign in to comment.