Wgranie zmian do repozytorium

This commit is contained in:
2026-05-10 16:46:04 +02:00
commit a68b81ed4a
1608 changed files with 254964 additions and 0 deletions

Binary file not shown.

View File

@@ -0,0 +1,79 @@
#pragma once
#include <Arduino.h>
#include <SPI.h>
#include "ADXL345FreshSPI.h"
class ADXL345ArraySPI {
public:
static constexpr uint8_t MAX_SENSORS = 4;
explicit ADXL345ArraySPI(const uint8_t cs_pins[MAX_SENSORS]) {
for (uint8_t i=0;i<MAX_SENSORS;i++){ cs_[i]=cs_pins[i]; }
}
// Inicjalizacja wszystkich (SPI MODE3, STREAM FIFO, full-res, wybrany ODR/Range).
// Zwraca liczbę poprawnie wykrytych czujników.
uint8_t beginAll(SPIClass *spi, uint32_t spiHz, float odr_hz, ADXL345FreshSPI::Range range = ADXL345FreshSPI::Range::G16) {
presentMask_ = 0;
// Zbezpieczenie: inicjalizacja CS na HIGH przed startem.
for (uint8_t i=0;i<MAX_SENSORS;i++){
pinMode(cs_[i], OUTPUT);
digitalWrite(cs_[i], HIGH);
}
delayMicroseconds(5);
for (uint8_t i=0;i<MAX_SENSORS;i++){
if (!acc_[i].begin(spi, cs_[i], spiHz)) continue;
acc_[i].write8(0x2D, 0x00); // POWER_CTL = standby (MEASURE=0)
if (!acc_[i].setRange(range, true)) continue;
acc_[i].write8(0x2D, 0x08); // POWER_CTL = measure
if (!acc_[i].setODR_Hz(odr_hz)) continue;
// Tryb STREAM najbezpieczniejszy dla jitteru CPU
acc_[i].enableFIFO(ADXL345FreshSPI::FIFOmode::STREAM, 32);
presentMask_ |= (1u << i);
}
return countPresent();
}
inline uint8_t size() const { return MAX_SENSORS; }
inline bool isPresent(uint8_t i) const { return (presentMask_ & (1u << i)) != 0; }
inline uint8_t countPresent() const { return __builtin_popcount(presentMask_); }
// Sprawdza, czy KAŻDY obecny sensor ma >=1 nową próbkę (DATA_READY).
bool availableAll() {
for (uint8_t i=0;i<MAX_SENSORS;i++){
if (!isPresent(i)) continue;
if (!acc_[i].available()) return false;
}
return true;
}
// Zdejmuje po 1 NAJSTARSZEJ próbce z FIFO każdego obecnego sensora.
// Zwraca liczbę faktycznie zebranych próbek (== liczbie obecnych sensorów, jeśli sukces).
uint8_t readAlignedOnce(int16_t x[], int16_t y[], int16_t z[], uint32_t ts_us[]) {
uint8_t got = 0;
for (uint8_t i=0;i<MAX_SENSORS;i++){
if (!isPresent(i)) continue;
ADXL345FreshSPI::SampleI16 one[1];
size_t n = acc_[i].readFIFOBurst(one, 1); // NAJSTARSZA próbka z FIFO
if (n == 0) {
// sporadycznie: awaryjnie dociągnij świeżą (rzadkie)
ADXL345FreshSPI::SampleI16 s;
if (!acc_[i].readFresh(s, 1)) return got; // przerwij zbieranie ramki
one[0] = s;
}
x[i] = one[0].x; y[i] = one[0].y; z[i] = one[0].z; ts_us[i] = one[0].ts_us;
got++;
}
return got;
}
ADXL345FreshSPI& at(uint8_t i) { return acc_[i]; }
private:
uint8_t cs_[MAX_SENSORS]{};
ADXL345FreshSPI acc_[MAX_SENSORS];
uint32_t presentMask_ = 0;
};

View File

@@ -0,0 +1,91 @@
#pragma once
#include <Arduino.h>
#include <SPI.h>
#include "ADXL345FreshSPI.h"
#include <Logger.h>
class ADXL345FastSPI {
public:
enum Rate { RATE_100HZ, RATE_200HZ, RATE_400HZ, RATE_800HZ, RATE_1600HZ, RATE_3200HZ };
enum Range { RANGE_2G, RANGE_4G, RANGE_8G, RANGE_16G };
ADXL345FastSPI(const uint8_t* csPins, uint8_t count) : count_(count > MAX_NUM ? MAX_NUM : count){
for (uint8_t i = 0; i < count_; ++i) cs_[i] = csPins[i];
}
/* Pobierz konfigurację zakres akcelerometru */
uint8_t getRange(uint8_t accel);
/* Zwraca czy jest FUL_RES*/
bool getFullRes(uint8_t accel);
// Wersja podstawowa: podaj SPIClass* (np. &SPI)
bool begin(SPIClass *spi, uint32_t spiHz, Rate rate, Range range, uint8_t options = 0);
// Wersja wygodna: deleguje do powyższej, używając globalnego SPI
inline bool begin(uint32_t spiHz, Rate rate, Range range, uint8_t options = 0) {
return begin(&SPI, spiHz, rate, range, options);
}
// Informacje / dostępność
inline uint8_t size() const { return presentCnt_; }
inline bool isPresent(uint8_t i) const { return (i < count_) && present_[i]; }
bool availableAll();
// Wariant B: zdejmuje po 1 NAJSTARSZEJ próbce z FIFO każdego obecnego sensora
// Zwraca liczbę zebranych próbek (== size(), jeśli pełna ramka).
uint8_t readAlignedOnce(int16_t* x, int16_t* y, int16_t* z, uint32_t* ts_us);
// Zgodność: pojedynczy sensor 1 próbka
bool readNewSample(uint8_t idx, int16_t& x, int16_t& y, int16_t& z, bool& ready);
// Zwraca ilośc podłączonych czujników po inicjalizacji - ale nie jest dynamiczna!
inline uint8_t connectedSensorsCount() const { return presentCnt_; }
// dynamiczne odświeżenie liczby działających czujników zawsze sprawdzi na bieżąco i zwróci przy działającym urządzeniu
uint8_t refreshConnectedSensorsCount();
// Maska aktywnych sensorów wg bieżącego stanu (bit i = sensor i)
inline uint8_t activeMask() const {
uint8_t m = 0;
for (uint8_t i = 0; i < count_; ++i) if (present_[i]) m |= (1u << i);
return m;
}
// Odświeża stan (ping) i zwraca zaktualizowaną maskę aktywnych sensorów
uint8_t refreshActiveMask();
private:
static constexpr uint8_t MAX_NUM = 4; // MAX Ilość ADXL345
SPIClass* spi_ = &SPI;
ADXL345FreshSPI dev_[MAX_NUM];
uint8_t cs_[MAX_NUM]{};
bool present_[MAX_NUM]{};
uint8_t count_ = 0;
uint8_t presentCnt_ = 0;
uint32_t spiHz_ = 5000000;
float odrHz_ = 3200.0f;
static float mapRateToHz(Rate r) {
switch (r) {
case RATE_100HZ: return 100.0f;
case RATE_200HZ: return 200.0f;
case RATE_400HZ: return 400.0f;
case RATE_800HZ: return 800.0f;
case RATE_1600HZ: return 1600.0f;
case RATE_3200HZ: return 3200.0f;
default: return 3200.0f;
}
}
static ADXL345FreshSPI::Range mapRange(Range r) {
switch (r) {
case RANGE_2G: return ADXL345FreshSPI::Range::G2;
case RANGE_4G: return ADXL345FreshSPI::Range::G4;
case RANGE_8G: return ADXL345FreshSPI::Range::G8;
case RANGE_16G: return ADXL345FreshSPI::Range::G16;
default: return ADXL345FreshSPI::Range::G16;
}
}
};

View File

@@ -0,0 +1,87 @@
#pragma once
#include <Arduino.h>
#include <SPI.h>
#include "ADXL345Registers.h"
#include <Logger.h>
class ADXL345FreshSPI {
public:
enum class Range { G2=0, G4=1, G8=2, G16=3 };
enum class FIFOmode { BYPASS, FIFO, STREAM, TRIGGER };
struct SampleI16 {
int16_t x, y, z;
uint32_t ts_us;
};
struct SampleSI {
float ax_g, ay_g, az_g;
float ax_ms2, ay_ms2, az_ms2;
uint32_t ts_us;
};
ADXL345FreshSPI() = default;
// --- Init (SPI) ---
// Uwaga: ADXL345 wymaga SPI MODE3, zegar ≤ ~5 MHz.
bool begin(SPIClass *spi, uint8_t csPin, uint32_t clockHz = 5000000);
// --- Konfiguracja ---
bool setODR_Hz(float odr_hz);
bool setRange(Range r, bool fullRes=true);
bool enableFIFO(FIFOmode mode, uint8_t triggerLevel=16);
bool enableDataReadyInterrupt(bool enable=true); // przy pollingu pozostaw false
// --- Status ---
bool ping(); // DEVID == 0xE5 ?
bool available(); // DATA_READY z INT_SOURCE
// --- Odczyty „świeże” (blokujące do timeout_ms) ---
bool readFresh(SampleI16& out, uint32_t timeout_ms = 100);
bool readFresh(SampleSI& out, uint32_t timeout_ms = 100);
// --- Zrzut FIFO (do n elementów) ---
size_t readFIFOBurst(SampleI16* buf, size_t maxCount);
size_t readFIFOBurst(SampleSI* buf, size_t maxCount);
// --- Parametry pomocnicze ---
void setGConstant(float g = 9.80665f) { g_ms2 = g; }
float lsb_per_g() const { return 1.0f / scale_g_per_lsb; }
//bool write8(uint8_t reg, uint8_t val);
//bool read8(uint8_t reg, uint8_t& val);
/*
Zwraca tryb pracy akcelerometru
*/
uint8_t getADXLRange();
/* Zwraca czy jest FULL_RES w akcelerometrze */
bool getADXLFullRes();
void showRangeFull(String txt="");
// niskopoziomowe
bool write8(uint8_t reg, uint8_t val);
bool read8(uint8_t reg, uint8_t& val);
private:
SPIClass* spi = nullptr;
uint8_t cs = 255;
uint32_t spiHz = 5000000;
float scale_g_per_lsb = 0.0039f; // full-res ~3.9 mg/LSB
float g_ms2 = 9.80665f;
bool fullRes = true;
// niskopoziomowe
//bool write8(uint8_t reg, uint8_t val);
//bool read8(uint8_t reg, uint8_t& val);
bool readMulti(uint8_t reg, uint8_t* dst, size_t n);
void spiSelect();
void spiDeselect();
bool configurePowerMeasure();
uint8_t odrCodeFromHz(float hz);
void countsToSI(const SampleI16& in, SampleSI& out);
};

View File

@@ -0,0 +1,27 @@
#pragma once
#define ADXL345_REG_DEVID 0x00
#define ADXL345_REG_BW_RATE 0x2C
#define ADXL345_REG_POWER_CTL 0x2D
#define ADXL345_REG_INT_ENABLE 0x2E
#define ADXL345_REG_INT_MAP 0x2F
#define ADXL345_REG_INT_SOURCE 0x30
#define ADXL345_REG_DATA_FORMAT 0x31
#define ADXL345_REG_DATAX0 0x32
#define ADXL345_REG_DATAX1 0x33
#define ADXL345_REG_DATAY0 0x34
#define ADXL345_REG_DATAY1 0x35
#define ADXL345_REG_DATAZ0 0x36
#define ADXL345_REG_DATAZ1 0x37
#define ADXL345_REG_FIFO_CTL 0x38
#define ADXL345_REG_FIFO_STATUS 0x39
#define ADXL345_POWER_MEASURE 0x08
#define ADXL345_DATA_READY_BIT 0x80 // INT_SOURCE[7]
#define ADXL345_DATA_FORMAT_FULL_RES 0x08
#define ADXL345_DATA_FORMAT_RANGE_MASK 0x03
#define ADXL345_FIFO_BYPASS 0x00
#define ADXL345_FIFO_FIFO 0x40
#define ADXL345_FIFO_STREAM 0x80
#define ADXL345_FIFO_TRIGGER 0xC0

View File

@@ -0,0 +1,19 @@
#ifndef APICLIENT_H
#define APICLIENT_H
#include <Arduino.h>
#include <WiFiClient.h>
#include <HTTPClient.h>
#include <FS.h>
#include <SD.h>
#include "Config.h"
#include "Watchdog.h"
class APIClient {
public:
APIClient();
bool uploadMeasurement(const String& filePath);
};
#endif

View File

@@ -0,0 +1,71 @@
#ifndef CONFIG_H
#define CONFIG_H
#include <Logger.h>
#include <Arduino.h>
#include <EEPROM.h>
#define EEPROM_SIZE 1024
//constexpr size_t EEPROM_SIZE = sizeof(Config) + 32; // z lekkim zapasem
extern bool isRebootRequired;
extern bool isClearLog;
extern bool connected;
extern long countConnect;
extern long countDisconnect;
extern String act_rssi_percent;
extern int8_t rssi;
extern String actDate;
extern String actTime;
struct Config {
bool connect; // czy łączyć z Internetem?
bool measure; // true - pomiary ciągłe, false - nie rób nic (tryb konfiguracji)
char ip[16];
char subnet[16];
char gateway[16];
char dns[16];
char ssid[32];
char ntp[50];
char password[32];
char hostname[32];
char place[100]; // miejsce instalacji
bool dhcp; // czy włączyć DHCP?
char user[10]; // użytkownik konfiguracji
char pass[20]; // hasło użytkownika konfiguracji
char updateUrl[150]; // adres pliku aktualizacji
char restURL[150]; // adres Rest API
int restPort; // Port systemu Api na serwerze
char restUser[30]; // login RestAPI
char restPass[50]; // hasło RestAPi
uint8_t apiKey[32]; // Klucz API KEY
uint16_t pause; // Pomiar co sekund
uint8_t duration; // Czas pomiaru w sekundach 1-25
char S0[12]; // nazwy czujników 1-8
char S1[12];
char S2[12];
char S3[12];
char S4[12];
char S5[12];
char S6[12];
char S7[12];
};
// Global config declaration
extern Config config;
class ConfigManager {
public:
ConfigManager();
void begin(); // EEPROM initialization
void readConfig(); // Odczyt konfiguracji z EEPROM
void saveConfig(); // Zapis konfiguracji do EEPROM
void resetToDefaults(); // Reset do ustawień domyślnych
void showConfig();
void generateApiKey(uint8_t *buf, size_t len);
private:
bool isEEPROMEmpty();
};
#endif

View File

@@ -0,0 +1,93 @@
#ifndef DISPLAY_H
#define DISPLAY_H
#pragma once
#include "esp_log.h"
#include <Arduino.h>
#include <LiquidCrystal_I2C.h>
#include <Wire.h>
#include <Version.h>
#include <RTClib.h>
#include <Config.h>
#define SCREEN_WIDTH 40
#define SCREEN_HEIGHT 4
#define SCREEN_ADDRESS 0x27
class Display {
public:
Display(RTC_DS3231 &rtc,
uint8_t address = SCREEN_ADDRESS,
uint8_t columns = SCREEN_WIDTH,
uint8_t rows = SCREEN_HEIGHT);
// Zwraca true, jeśli urządzenie na I2C odpowiada
bool begin(TwoWire *wire = &Wire);
/* Ekran startowy urządzenia */
void welcomeScreen();
/* Czyście wiersz */
void clearRow(uint16_t line);
/* Wyświetla wyśrodkowany tekst w danym wierszu */
void textCenter(uint8_t line, const char *txt);
/* Wyświetla tekst w danym wierszu od lewej, bez centrowania */
void text(uint8_t line, const char *text);
/* Status - najniższy wiersz. Czyści przed zapisaniem */
void textStatus(const char *text);
void clear();
/* Ekran po uruchomieniu */
void mainScreen();
/* Ekran jeśli offline miga co 1 sek */
void displayOffline(bool measure, uint8_t count, float freeSpace, long licznik, bool refresh = false);
/* Podsumowanie po pomiarze: ramki, sampling, etc. */
void displaySampleRateSummary(uint32_t reccount, uint32_t captureSeconds, float khz, String filename);
void updateNetwork(String ip, bool connected);
void updateBarWiFi(long signal, bool connected);
void textStyle(const uint8_t st, String text);
void message(String text);
void print(String text);
void println(String text);
void setCursor(int16_t x, int16_t y);
void showAccel(float a, float b, float c);
void displayOnOffM(bool measure, String myDir, String myFile);
void initMeasure(
bool measure, // czy pomiar ciągły?
bool run, // czy uruchomiony?
bool runmes,
uint16_t pause, // Czas (s) pomiędzy próbkami
uint8_t duration, // Czas (s) trwania próbki
bool connect, // Czy połączenie Network
long counter, // główny licznik long
String gdate, // Data aktualna
String gtime); // Czas aktualny
private:
LiquidCrystal_I2C *_lcd;
RTC_DS3231 &rtc_;
uint8_t _columns;
uint8_t _rows;
uint8_t _address;
// Poprzednie
uint16_t oyear;
uint8_t omonth;
uint8_t oday;
uint8_t ohour;
uint8_t omin;
uint8_t osec;
float ospace;
uint8_t oadxlcnt;
bool omode;
};
#endif

View File

@@ -0,0 +1,15 @@
#ifndef LOGGER_H
#define LOGGER_H
#include "esp_log.h"
// Wszystkie tagi logowania w jednym miejscu
extern const char *TAG_MAIN;
extern const char *TAG_DISP;
extern const char *TAG_ADXL;
extern const char *TAG_CONF;
// Funkcja inicjalizacji poziomów logowania
void init_log_levels();
#endif

View File

@@ -0,0 +1,123 @@
#ifndef MEASURE_H
#define MEASURE_H
#include <Arduino.h>
#include <Watchdog.h>
#include <SPI.h>
#include <SD.h>
#include <FS.h>
#include <RTClib.h>
#include <Display.h>
#include "ADXL345FastSPI.h" // <-- zgodnie z main.cpp
#include <Logger.h>
#include <Pinout.h>
// Domyślne parametry akwizycji ADXL345 (mogą być nadpisane w ADXL345FastSPI::begin)
static constexpr uint32_t SPI_HZ = 5000000; // 5 MHz (MODE3)
static constexpr float ODR_HZ = 3200.0f; // maks. ODR
// Zakres ustawiany w main.cpp przez ADXL345FastSPI::begin(..., RANGE_2G, ...)
struct FileInfo {
String path; // np. "/3/00000057.wmt"
uint64_t size; // bajty
bool exists; // true, jeśli ostatni plik istnieje
};
struct SpaceInfo {
double value;
const char *unit; // "GB" | "MB" | "UNKNOWN"
};
class DataCapture {
// --- Nagłówek pliku WMT ---
struct FileHeader {
char magic[3]; // "WMT"
uint16_t version; // 1
uint16_t headerSize; // sizeof(FileHeader)
uint32_t sampleSize; // sizeof(Sample)
uint32_t timestamp; // UNIX startu akwizycji
uint32_t reccount; // liczba rekordów Sample w pliku
} __attribute__((packed));
public:
// --- Rekord próbki ---
struct Sample {
uint32_t offset; // µs od startu akwizycji (wspólny dla ramki)
uint8_t sensor_id; // 0..3
int16_t x, y, z; // surowe ADXL345
bool ready; // 1 = obecna
} __attribute__((packed));
// Konstruktor dopasowany do main.cpp przyjmuje ADXL345FastSPI
DataCapture(ADXL345FastSPI &adxl, Display &display, RTC_DS3231 &rtc, fs::FS &storage, size_t bufferSize = 131072 /* 128 KB */);
~DataCapture();
bool capture(uint32_t captureSeconds, const char *filename);
bool captureAuto(uint32_t captureSeconds, const char *baseDirectory = "/logs");
void printSamplingRate(uint32_t reccount, uint32_t captureSeconds, String filename);
void readHeaderAndPrint(const char *path);
void stop();
bool isActive() const { return measurementActive_; }
// SD utils
SpaceInfo freeSpaceMB();
float freeSpaceFloat(bool *isGB = nullptr);
String unixToDateTime(uint32_t ts);
// plik z najwyższym indeksem
FileInfo getLastFileInfo();
bool deleteAllOnSD();
bool isExit = false; // true oznacza przerwanie pomiaru
private:
ADXL345FastSPI &adxl_;
Display &display_;
RTC_DS3231 &rtc_;
fs::FS &_fs;
// Katalogowanie
const String _baseDir = "/";
const String _ext = ".wmt";
const uint8_t _digits = 8;
const uint16_t _maxFilesPerDir = 400;
// Bufor zapisu (PSRAM)
uint8_t *buffer_ = nullptr;
size_t bufferSize_ = 0;
size_t bufferIndex_ = 0;
bool measurementActive_ = false;
bool flushToFile(File &f);
static uint8_t crc8(const uint8_t *data, size_t len);
void setTestingIndicator_(bool on, String myDir, String myFile);
bool isEscape(){
bool ispress = (GPIO.in & (1UL << BTN_OK)) == 0;
if(ispress) {
isExit = true;
measurementActive_;
display_.textStatus("Cancelling. Wait!");
}
return ispress;
} // szybkidigitalRead : czy BTN stop???
public:
bool isAllDigits(const char *s);
uint32_t toUint(const char *s);
static const char *basenameFromPath(const char* full);
bool isWmtWithDigits(const char *name, uint32_t &idxOut) const;
String makeIndexedName(uint32_t idx) const;
static String joinPath(const String &a, const String &b);
bool ensureDir(uint32_t dirNum);
uint32_t findHighestNumericDir();
void scanDirForWmt(uint32_t dirNum, uint32_t &count, uint32_t &highestIdx);
bool recursiveDelete(const String &path);
String dirPath(uint32_t dirNum) const;
String allocateNextFilePath();
String generateNextFilename();
void printLastFileInfoSerial();
};
#endif // MEASURE_H

View File

@@ -0,0 +1,66 @@
#ifndef NETWORK_H
#define NETWORK_H
#include "esp_log.h"
#include <Arduino.h>
#include <Config.h>
#ifdef ESP32
#include <WiFi.h>
#include <WebServer.h>
#include <ESPmDNS.h>
#elif defined(ESP8266)
#include <ESP8266WiFi.h>
#include <ESP8266mDNS.h>
#endif
//#include <Pinout.h>
//#include <IPAddress.h>
//#include <WiFiUdp.h>
//#include <WiFiClientSecureBearSSL.h>
// OTA
#include <HTTPUpdate.h>
#include <WiFiClientSecure.h>
#include <functional>
// OTA
class WiFiManager {
public:
WiFiManager();
void begin();
void connectToWiFi();
void setupAccessPoint(const char *newSSID, const char *newPassword);
void checkWiFiConnection();
void updateLED();
void setupMDNS();
void ReadConnection();
bool isConnected(); // Zwraca stan połączenia
bool convertCharToIPAddress(const char *str, IPAddress& ip);
void ledBlink(); // Z innego projektu - niepotrzebne, bo nie ma LED WiFi...
bool isWiFiOK(); // True, gdy WiFi połączone i false, gdy brak połączenia
int rssiToPercent(int rssi); // rssi na procenty
int8_t getRSSI();
/**
* Aktualizacja systemu przez internet z adresu config.updateUrl.
* @param allowInsecureTLS true => dla https wyłącz weryfikację certyfikatu.
* @param progressCb callback progress (opcjonalnie) (bytes, total).
* @return true, jeśli update zakończony sukcesem (urządzenie się zrestartuje).
*/
bool performOTAUpdate(bool allowInsecureTLS = true, std::function<void(int,int)> progressCb = nullptr);
int8_t rssi = 0;
bool useDHCP = true;
IPAddress local_IP;
IPAddress gateway;
IPAddress subnet;
IPAddress dns;
String ssidAP = "ACCEL666";
String passwordAP = "12345678";
private:
bool isAccessPoint = false;
};
#endif // WIFIMANAGER_H

View File

@@ -0,0 +1,52 @@
#ifndef PINOUT_H
#define PINOUT_H
#if defined(ESP32)
// SPI3 (HSPI) - SD Card nie kolidują z PSRAM
#define SD_SCK 16 // 36 //18
#define SD_MOSI 17 // 35 //17
#define SD_MISO 18 // 37 //16
#define SD_CS 15 // 34 //15 ?? 34
// SPI2 (VSPI) - ADXL345
#define MOSI_ADSX 11 // SDA
#define CLK_ADSX 12 // SCL
#define MISO_ADSX 13 // SDO
//I2C C3
#define PIN_SDA 47 // szary
#define PIN_SCL 48 // niebieski
// Przycisk
#define BTN_UP 5
#define BTN_OK 6
#define BTN_DOWN 7
#endif
/*
CS {5, 6, 7, 10, 14, 21}
SPI2 (VSPI) — preferowane do ADXL345 (wysoka prędkość, stabilność). ASXL345
SPI3 (HSPI) — dowolne piny do SD (niższa prędkość, ale elastyczność). SD
Nie używać ESP32-S3:
integrated SPI flash: 26, 27, 28, 29, 30, 31, 32,
USB: 19, 20, 43, 44
PSRAM 35, 37
*/
#if defined(ARDUINO_RASPBERRY_PI_PICO)
#define CLK_ADSX 18
#define MOSI_ADSX 19
#define MISO_ADSX 16
#define SD_MISO 12
#define SD_CS 13
#define SD_SCK 14
#define SD_MOSI 15
#define I2C_SDA 20
#define I2C_SCL 21
#endif
#endif

View File

@@ -0,0 +1,52 @@
#ifndef SETTINGS_H
#define SETTINGS_H
#include <Arduino.h>
#include <Pinout.h>
#include <Watchdog.h>
#include <RTClib.h>
#include <Display.h>
#include <Logger.h>
#include <Config.h>
class Settings {
public:
Settings(Display &display, RTC_DS3231 &rtc);
~Settings();
void setTimeRTC();
void setConfigDevice();
void finishConfigDevice();
void begin();
bool isPressed(uint8_t btnIndex);
// bool is1and3();
bool isBtnReset();
bool isSetClock();
bool readBtnUp() { return digitalRead(BTN_UP) == LOW; }
bool readBtnOk() { return digitalRead(BTN_OK) == LOW; }
bool readBtnDown() { return digitalRead(BTN_DOWN) == LOW; }
// Struktura na datę i czas
struct RtcDateTime {
uint16_t year;
uint8_t month;
uint8_t day;
uint8_t hour;
uint8_t minute;
uint8_t second;
};
private:
Display &display_;
RTC_DS3231 &rtc_;
int editField(int value, int minVal, int maxVal, const char *label);
void constrainValue(int &value, int minVal, int maxVal);
void printField(const char *label, int value);
};
#endif

View File

@@ -0,0 +1,14 @@
#ifndef TOOL_H
#define TOOL_H
#include <Arduino.h>
#include <Wire.h>
#include "esp_log.h"
#include "Watchdog.h"
void scanI2C();
/* Funkcja przyjmuje adres jako argument i zwraca true gdy urządzenie odpowiada, w przeciwnym wypadku false */
bool isI2CDevPresent(uint8_t address);
#endif

View File

@@ -0,0 +1,31 @@
#ifndef UPLOADMANAGER_H
#define UPLOADMANAGER_H
#include <Arduino.h>
#include <FS.h>
#include <SD.h>
#include "APIClient.h"
#include "RTClib.h"
#include "Measure.h"
class UploadManager {
public:
UploadManager(APIClient& client, RTC_DS3231& rtc, DataCapture& capture);
// Call this to upload a specific file immediately
void uploadFile(const String& filePath);
// Call this in the background when WiFi is connected
void processPendingUploads();
private:
APIClient& apiClient;
RTC_DS3231& rtc_;
DataCapture& capture_;
bool isAlreadyUploaded(const String& filePath);
void appendLog(const String& filePath, const String& status);
String getCurrentTimestamp();
};
#endif

View File

@@ -0,0 +1,9 @@
#ifndef VERSION_H
#define VERSION_H
#define VERSION "1.3.4.1"
// 1: graphical 128x64, 2: LCD I2C Text 4x20
#define LCD_TYPE 2
#endif

View File

@@ -0,0 +1,38 @@
#pragma once
/**
* Watchdog — prosty interfejs do inicjalizacji i karmienia WDT z dowolnego modułu.
*
* Obsługiwane środowiska:
* - ESP32 Arduino Core / ESP-IDF (esp_task_wdt)
* - Fallback: no-op na innych platformach
*
* Użycie:
* Watchdog::init(5, true);
* Watchdog::addThisTask();
* ...
* Watchdog::feed();
*/
#include <stdint.h>
namespace Watchdog {
/** Inicjalizacja Task Watchdog (idempotentna). */
bool init(int timeout_seconds = 5, bool panic_on_trigger = true);
/** Dodaje bieżący task (wątki FreeRTOS: wołaj w ciele tego taska). */
bool addThisTask();
/** Usuwa bieżący task z nadzoru WDT. */
bool removeThisTask();
/** Karmi watchdog (reset licznika). */
void feed();
/** Zmienia timeout (wykonuje re-init wewnętrznie, jeśli trzeba). */
bool setTimeout(int timeout_seconds);
/** Czy watchdog jest aktywny (zainicjalizowany)? */
bool isActive();
} // namespace Watchdog

View File

@@ -0,0 +1,147 @@
#include "ADXL345FastSPI.h"
#include "Watchdog.h"
static const char *TAG_FRESH = "ADXLFAST";
bool ADXL345FastSPI::begin(SPIClass *spi, uint32_t spiHz, Rate rate, Range range, uint8_t /*options*/){
spi_ = spi;
spiHz_ = spiHz;
odrHz_ = mapRateToHz(rate);
auto r = mapRange(range);
//spi_->begin(); 28.01.2026
presentCnt_ = 0;
for (uint8_t i = 0; i < count_; ++i) {
bool ok = dev_[i].begin(spi_, cs_[i], spiHz_)
&& dev_[i].setRange(r, true)
&& dev_[i].setODR_Hz(odrHz_);
dev_[i].showRangeFull(String(i));
if (ok) {
dev_[i].enableFIFO(ADXL345FreshSPI::FIFOmode::STREAM, 32); // wariant B
present_[i] = true;
presentCnt_++;
} else {
present_[i] = false;
}
}
return presentCnt_ > 0;
}
bool ADXL345FastSPI::availableAll() {
for (uint8_t i = 0; i < count_; ++i) {
if (!present_[i]) continue;
// else {
// dev_[i].showRangeFull("AVAILABLE ADXL");
// }
if (!dev_[i].available()) return false;
}
return true;
}
uint8_t ADXL345FastSPI::readAlignedOnce(int16_t* x, int16_t* y, int16_t* z, uint32_t* ts_us){
// 1) Czekaj aż każdy ma ≥1 próbkę (DATA_READY => FIFO>0)
uint32_t start_wait = millis();
while (!availableAll()) {
if (millis() - start_wait > 100) {
ESP_LOGE(TAG_FRESH, "Timeout waiting for sensors data!");
return 0;
}
Watchdog::feed();
yield();
}
// 2) Zdejmij najstarszą z każdego sensora
uint8_t got = 0;
for (uint8_t i = 0; i < count_; ++i) {
if (!present_[i]) continue;
ADXL345FreshSPI::SampleI16 one[1];
size_t n = dev_[i].readFIFOBurst(one, 1);
if (n == 0) {
ADXL345FreshSPI::SampleI16 s;
if (!dev_[i].readFresh(s, 1)) break;
one[0] = s;
}
if (x) x[i] = one[0].x;
if (y) y[i] = one[0].y;
if (z) z[i] = one[0].z;
if (ts_us) ts_us[i] = one[0].ts_us;
got++;
}
return got;
}
bool ADXL345FastSPI::readNewSample(uint8_t idx, int16_t& x, int16_t& y, int16_t& z, bool& ready)
{
if (idx >= count_ || !present_[idx]) { ready = false; return false; }
ADXL345FreshSPI::SampleI16 one[1];
size_t n = dev_[idx].readFIFOBurst(one, 1);
if (n == 0) {
ADXL345FreshSPI::SampleI16 s;
if (!dev_[idx].readFresh(s, 1)) { ready = false; return false; }
one[0] = s;
}
x = one[0].x; y = one[0].y; z = one[0].z;
ready = true;
return true;
}
uint8_t ADXL345FastSPI::refreshConnectedSensorsCount(){
uint8_t cnt = 0;
for (uint8_t i = 0; i < count_; ++i) {
// ping() czyta DEVID (0xE5) wewnętrznie i zwraca true/false
if (dev_[i].ping()) {
present_[i] = true;
cnt++;
} else {
present_[i] = false;
}
}
presentCnt_ = cnt;
return cnt;
}
/*
Zwraca maskę bitową aktywnych sensorów (np. do szybkiej diagnostyki: który CS nie odpowiada)
Uzycie:
uint8_t mask = adxl.refreshActiveMask();
ESP_LOGI(TAG_MAIN, "Aktywne sensory (bitmask): 0x%02X, count=%u",
mask, adxl.connectedSensorsCount());
// Sprawdzenie konkretnego sensora:
if (mask & (1u << 3)) {
ESP_LOGI(TAG_MAIN, "Sensor #3 online");
} else {
ESP_LOGW(TAG_MAIN, "Sensor #3 offline");
}
*/
uint8_t ADXL345FastSPI::refreshActiveMask(){
uint8_t m = 0;
uint8_t cnt = 0;
for (uint8_t i = 0; i < count_; ++i) {
if (dev_[i].ping()) {
present_[i] = true;
m |= (1u << i);
cnt++;
} else {
present_[i] = false;
}
}
presentCnt_ = cnt;
return m;
}
// Pobiera zakres wybranego akcelerometru
uint8_t ADXL345FastSPI::getRange(uint8_t accel){
return dev_[accel].getADXLRange();
}
bool ADXL345FastSPI::getFullRes(uint8_t accel){
return dev_[accel].getADXLFullRes();
}

View File

@@ -0,0 +1,242 @@
#include "ADXL345FreshSPI.h"
static const char *TAG_FRESH = "ADXLFRESH";
static inline int16_t u8pair_to_i16(uint8_t lo, uint8_t hi) {
return (int16_t)((hi << 8) | lo);
}
bool ADXL345FreshSPI::begin(SPIClass* s, uint8_t csPin, uint32_t clockHz) {
spi = s; cs = csPin; spiHz = clockHz;
pinMode(cs, OUTPUT); digitalWrite(cs, HIGH);
//spi->begin(); // tymczas
delay(1);
if (!ping()) return false;
// 1. Wymuś STANDBY przed jakąkolwiek konfiguracją
write8(ADXL345_REG_POWER_CTL, 0x00);
delay(1);
// 2. Skonfiguruj format danych (Range i Full_Res)
if (!setRange(Range::G16, true)) { // było if (!setRange(Range::G2, true)) return false;
ESP_LOGI(TAG_FRESH, "Range G16 ERROR!");
return false;
}
// 3. Skonfiguruj ODR
if (!setODR_Hz(100.0f)) return false; // domyślnie 100 Hz
// 4. Dopiero teraz włącz pomiar
if (!configurePowerMeasure()) return false;
showRangeFull();
return true;
}
bool ADXL345FreshSPI::ping() {
uint8_t id=0; if (!read8(ADXL345_REG_DEVID, id)) return false;
return id == 0xE5;
}
bool ADXL345FreshSPI::configurePowerMeasure() {
return write8(ADXL345_REG_POWER_CTL, ADXL345_POWER_MEASURE);
}
bool ADXL345FreshSPI::setRange(Range r, bool fullRes_) {
fullRes = fullRes_;
// Wymuś STANDBY (MEASURE=0) kluczowe dla zmiany RANGE/FULL_RES
write8(ADXL345_REG_POWER_CTL, 0x00);
delayMicroseconds(5);
uint8_t fmt = 0;
if (!read8(ADXL345_REG_DATA_FORMAT, fmt)) return false;
fmt &= ~ADXL345_DATA_FORMAT_RANGE_MASK; // bity 1:0
fmt |= (uint8_t)r;
if (fullRes) fmt |= ADXL345_DATA_FORMAT_FULL_RES; // bit 3
else fmt &= ~ADXL345_DATA_FORMAT_FULL_RES;
if (!write8(ADXL345_REG_DATA_FORMAT, fmt)) return false;
// Kontrola: odczyt po zapisie
//uint8_t verify = 0;
//read8(ADXL345_REG_DATA_FORMAT, verify);
// tu możesz logować verify
// Wróć do MEASURE
write8(ADXL345_REG_POWER_CTL, ADXL345_POWER_MEASURE);
scale_g_per_lsb = fullRes ? 0.0039f : (1.0f/256.0f) * (2 << (uint8_t)r);
return true;
}
uint8_t ADXL345FreshSPI::odrCodeFromHz(float hz) {
struct { uint8_t code; float f; } map[] = {
{0x06, 6.25f},{0x07,12.5f},{0x08,25.f},{0x09,50.f},{0x0A,100.f},
{0x0B,200.f},{0x0C,400.f},{0x0D,800.f},{0x0E,1600.f},{0x0F,3200.f}
};
uint8_t best=0x0A; float bestErr=1e9f;
for (auto &e: map){ float err=fabsf(e.f-hz); if (err<bestErr){bestErr=err; best=e.code;}}
return best;
}
bool ADXL345FreshSPI::setODR_Hz(float hz) {
return write8(ADXL345_REG_BW_RATE, odrCodeFromHz(hz));
}
bool ADXL345FreshSPI::enableFIFO(FIFOmode mode, uint8_t triggerLevel) {
triggerLevel = constrain(triggerLevel, (uint8_t)1, (uint8_t)32);
uint8_t m = ADXL345_FIFO_BYPASS;
switch(mode){
case FIFOmode::BYPASS: m = ADXL345_FIFO_BYPASS; break;
case FIFOmode::FIFO: m = ADXL345_FIFO_FIFO; break;
case FIFOmode::STREAM: m = ADXL345_FIFO_STREAM; break;
case FIFOmode::TRIGGER: m = ADXL345_FIFO_TRIGGER;break;
}
return write8(ADXL345_REG_FIFO_CTL, (uint8_t)(m | ((triggerLevel-1) & 0x1F)));
}
bool ADXL345FreshSPI::enableDataReadyInterrupt(bool enable) {
uint8_t ie=0; if (!read8(ADXL345_REG_INT_ENABLE, ie)) return false;
if (enable) ie |= ADXL345_DATA_READY_BIT;
else ie &= ~ADXL345_DATA_READY_BIT;
return write8(ADXL345_REG_INT_ENABLE, ie);
}
bool ADXL345FreshSPI::available() {
uint8_t src=0; if (!read8(ADXL345_REG_INT_SOURCE, src)) return false;
return (src & ADXL345_DATA_READY_BIT) != 0;
}
bool ADXL345FreshSPI::readFresh(SampleI16& out, uint32_t timeout_ms) {
uint32_t start = millis();
while (!available()) {
if ((millis() - start) > timeout_ms) return false;
delayMicroseconds(200);
}
uint8_t buf[6];
if (!readMulti(ADXL345_REG_DATAX0, buf, 6)) return false;
out.x = u8pair_to_i16(buf[0], buf[1]);
out.y = u8pair_to_i16(buf[2], buf[3]);
out.z = u8pair_to_i16(buf[4], buf[5]);
out.ts_us = micros();
return true;
}
bool ADXL345FreshSPI::readFresh(SampleSI& out, uint32_t timeout_ms) {
SampleI16 raw;
if (!readFresh(raw, timeout_ms)) return false;
countsToSI(raw, out);
return true;
}
size_t ADXL345FreshSPI::readFIFOBurst(SampleI16* buf, size_t maxCount) {
if (!buf || maxCount==0) return 0;
uint8_t status=0; if (!read8(ADXL345_REG_FIFO_STATUS, status)) return 0;
uint8_t entries = status & 0x3F; // 0..32
size_t n = min<size_t>(entries, maxCount);
for (size_t i=0;i<n;i++){
uint8_t d[6];
if (!readMulti(ADXL345_REG_DATAX0, d, 6)) return i;
buf[i].x = u8pair_to_i16(d[0], d[1]);
buf[i].y = u8pair_to_i16(d[2], d[3]);
buf[i].z = u8pair_to_i16(d[4], d[5]);
buf[i].ts_us = micros();
}
return n;
}
size_t ADXL345FreshSPI::readFIFOBurst(SampleSI* buf, size_t maxCount) {
if (!buf || maxCount==0) return 0;
size_t n = 0;
while (n < maxCount) {
uint8_t status=0; if (!read8(ADXL345_REG_FIFO_STATUS, status)) break;
uint8_t entries = status & 0x3F;
if (entries == 0) break;
uint8_t d[6];
uint32_t t = micros();
if (!readMulti(ADXL345_REG_DATAX0, d, 6)) break;
SampleI16 s;
s.x = u8pair_to_i16(d[0], d[1]);
s.y = u8pair_to_i16(d[2], d[3]);
s.z = u8pair_to_i16(d[4], d[5]);
s.ts_us = t;
countsToSI(s, buf[n]);
n++;
}
return n;
}
void ADXL345FreshSPI::countsToSI(const SampleI16& in, SampleSI& out) {
out.ax_g = in.x * scale_g_per_lsb;
out.ay_g = in.y * scale_g_per_lsb;
out.az_g = in.z * scale_g_per_lsb;
out.ax_ms2 = out.ax_g * g_ms2;
out.ay_ms2 = out.ay_g * g_ms2;
out.az_ms2 = out.az_g * g_ms2;
out.ts_us = in.ts_us;
}
// ---------- Low-level SPI -----------
bool ADXL345FreshSPI::write8(uint8_t reg, uint8_t val) {
spi->beginTransaction(SPISettings(spiHz, MSBFIRST, SPI_MODE3));
spiSelect();
spi->transfer(reg & 0x3F); // write, single
spi->transfer(val);
spiDeselect();
spi->endTransaction();
return true;
}
bool ADXL345FreshSPI::read8(uint8_t reg, uint8_t& val) {
spi->beginTransaction(SPISettings(spiHz, MSBFIRST, SPI_MODE3));
spiSelect();
spi->transfer(0x80 | (reg & 0x3F)); // read, single
val = spi->transfer(0x00);
spiDeselect();
spi->endTransaction();
return true;
}
bool ADXL345FreshSPI::readMulti(uint8_t reg, uint8_t *dst, size_t n) {
if (n==0) return true;
spi->beginTransaction(SPISettings(spiHz, MSBFIRST, SPI_MODE3));
spiSelect();
spi->transfer(0xC0 | (reg & 0x3F)); // read, multi (MB=1, R/W=1)
for (size_t i=0;i<n;i++) dst[i] = spi->transfer(0x00);
spiDeselect();
spi->endTransaction();
return true;
}
void ADXL345FreshSPI::spiSelect() { digitalWrite(cs, LOW); }
void ADXL345FreshSPI::spiDeselect() { digitalWrite(cs, HIGH); }
uint8_t ADXL345FreshSPI::getADXLRange() {
uint8_t format = 0;
if (!read8(0x31, format)) return 255; // albo 0
switch (format & 0x03) {
case 0: return 2;
case 1: return 4;
case 2: return 8;
case 3: return 16;
}
return 255;
}
bool ADXL345FreshSPI::getADXLFullRes() {
uint8_t format = 0;
if (!read8(0x31, format)) return false;
//bool ok = read8(0x31, format);
//ESP_LOGI(TAG_FRESH, "read8 ok=%d DATA_FORMAT=0x%02X", ok, format);
return (format & 0x08) != 0;
}
void ADXL345FreshSPI::showRangeFull(String txt) {
uint8_t format = getADXLRange();
bool full_res = getADXLFullRes();
ESP_LOGI(TAG_FRESH, "%s ADXL345: RANGE=%dG FULL_RES=%d", txt, format, full_res);
}

View File

@@ -0,0 +1,124 @@
#include "APIClient.h"
#include <WiFiClient.h>
#include <base64.h>
static const char *TAG_API = "API";
APIClient::APIClient() {}
bool APIClient::uploadMeasurement(const String &filePath) {
if (WiFi.status() != WL_CONNECTED) {
ESP_LOGE(TAG_API, "No WiFi connection.");
return false;
}
File file = SD.open(filePath, FILE_READ);
if (!file) {
ESP_LOGE(TAG_API, "Failed to open file %s", filePath.c_str());
return false;
}
// Wyciągnij samą nazwę pliku (bez ścieżki)
String filename = filePath;
int slashIndex = filename.lastIndexOf('/');
if (slashIndex >= 0) {
filename = filename.substring(slashIndex + 1);
}
// multipart/form-data boundary
String boundary = "----ESP32WMTBoundary";
String head = "--" + boundary + "\r\n" +
"Content-Disposition: form-data; name=\"file\"; filename=\"" +
filename + "\"\r\n" +
"Content-Type: application/octet-stream\r\n\r\n";
String tail = "\r\n--" + boundary + "--\r\n";
size_t fileSize = file.size();
size_t totalLength = head.length() + fileSize + tail.length();
// Połączenie z hostem
WiFiClient client;
String host = String(config.restURL);
int port = config.restPort;
// Usuń prefiks protokołu z hosta
host.replace("http://", "");
host.replace("https://", "");
ESP_LOGI(TAG_API, "Connecting to %s:%d for upload", host.c_str(), port);
if (!client.connect(host.c_str(), port)) {
ESP_LOGE(TAG_API, "Connection failed to host %s:%d", host.c_str(), port);
file.close();
return false;
}
ESP_LOGI(TAG_API, "Uploading %s (%d bytes)", filePath.c_str(), fileSize);
// --- HTTP Request ---
// Endpoint wg dokumentacji API IoT
client.println("POST /api/v1/measurements/measurements/upload HTTP/1.1");
client.println("Host: " + host + ":" + String(port));
// Basic Auth: base64(serial:password)
String auth = String(config.restUser) + ":" + String(config.restPass);
String authBase64 = base64::encode(auth);
client.println("Authorization: Basic " + authBase64);
client.println("Content-Length: " + String(totalLength));
client.println("Content-Type: multipart/form-data; boundary=" + boundary);
client.println("Connection: close");
client.println();
// Multipart body: head + file data + tail
client.print(head);
uint8_t buffer[2048];
while (file.available()) {
size_t len = file.read(buffer, sizeof(buffer));
client.write(buffer, len);
Watchdog::feed();
}
client.print(tail);
file.close();
// --- Parsowanie odpowiedzi ---
int httpCode = 0;
String responseLine;
unsigned long timeout = millis();
while (client.connected() && millis() - timeout < 15000) {
if (client.available()) {
responseLine = client.readStringUntil('\n');
responseLine.trim();
if (responseLine.startsWith("HTTP/1.1 ")) {
httpCode = responseLine.substring(9, 12).toInt();
}
if (responseLine.length() == 0)
break;
}
Watchdog::feed();
}
String responseBody = "";
while (client.available()) {
responseBody += client.readString();
}
client.stop();
// --- Obsługa kodów odpowiedzi wg dokumentacji API ---
if (httpCode == 201) {
ESP_LOGI(TAG_API, "Upload successful: 201 Created");
return true;
} else if (httpCode == 401) {
ESP_LOGE(TAG_API, "Unauthorized 401: Wrong serial or password!");
} else if (httpCode == 403) {
ESP_LOGE(TAG_API, "Forbidden 403: Device not authorized or user tried upload.");
} else if (httpCode == 400) {
ESP_LOGE(TAG_API, "Bad Request 400: %s", responseBody.c_str());
} else {
ESP_LOGE(TAG_API, "Upload failed with code %d. Response: %s", httpCode,
responseBody.c_str());
}
return false;
}

View File

@@ -0,0 +1,184 @@
#include <Config.h>
Config config;
ConfigManager::ConfigManager() {}
// EEPROM initialize
void ConfigManager::begin() {
ESP_LOGI(TAG_CONF, "Begin config");
EEPROM.begin(EEPROM_SIZE);
if (isEEPROMEmpty()) {
ESP_LOGI(TAG_CONF, "EEPROM Empty");
resetToDefaults();
generateApiKey(config.apiKey, sizeof(config.apiKey));
saveConfig();
readConfig();
} else {
//Logger::getInstance().log(LOG_INFO, "READ Config");
readConfig();
}
}
// Check is EEPROM is empty
bool ConfigManager::isEEPROMEmpty() {
if (EEPROM.read(0) != 253){
ESP_LOGI(TAG_CONF, "EEPROM is new!");
return true;
} else {
return false;
}
}
// Read configuration from EEPROM
void ConfigManager::readConfig() {
ESP_LOGI(TAG_CONF, "Read config from EEPROM");
EEPROM.get(1, config);
}
// Save config to EEPROM
void ConfigManager::saveConfig() {
ESP_LOGI(TAG_CONF, "SAVE CONFIG");
EEPROM.put(1, config);
EEPROM.write(0, 253);
if (EEPROM.commit()) {
ESP_LOGI(TAG_CONF, "Config saved");
} else {
ESP_LOGE(TAG_CONF, "Error save config");
}
EEPROM.end();
}
void ConfigManager::generateApiKey(uint8_t *buf, size_t len) {
for (size_t i = 0; i < len; i += 4) {
uint32_t r = esp_random(); // losowe 32 bity z TRNG ESP32
size_t chunk = (len - i >= 4) ? 4 : (len - i); // ostatnia iteracja może być < 4 bajtów
memcpy(buf + i, &r, chunk);
}
}
// Factory reset EEPROM
void ConfigManager::resetToDefaults() {
ESP_LOGI(TAG_CONF, "EEPROM RESET FACTORY");
EEPROM.begin(EEPROM_SIZE);
for (int i = 0; i < EEPROM_SIZE; i++) {
EEPROM.write(i, 0);
}
EEPROM.write(0, 0);
strcpy(config.ssid, "politechnika");
strcpy(config.password, "");
strcpy(config.hostname, "WMT001");
strcpy(config.place, "WMT Stalowa Wola");
config.dhcp = 1;
strcpy(config.ip, "192.168.0.10");
strcpy(config.subnet, "255.255.255.0");
strcpy(config.gateway, "192.168.0.1");
strcpy(config.dns, "8.8.8.8");
strcpy(config.user, "admin");
strcpy(config.pass, "admin");
strcpy(config.ntp, "pl.pool.ntp.org");
config.connect = 1; // urządzenie połączone z siecią lub 0 offline
config.measure = 1; // włącz automatyczny pomiar co x sekunt (pause)
config.duration = 5; // czas trwania pomiaru 5 sekund
config.pause = 10000; // odstęp pomiędzy pomiarami w ms
//strcpy(config.ntp, "0.pl.pool.ntp.org");
strcpy(config.restURL, "http://62.93.60.19");
config.restPort = 5004;
strcpy(config.restUser, "SN001234ABCD56789012");
strcpy(config.restPass, "device001");
strcpy(config.S0, "ACCEL1");
strcpy(config.S1, "ACCEL2");
strcpy(config.S2, "ACCEL3");
strcpy(config.S3, "ACCEL4");
strcpy(config.S4, "ACCEL5");
strcpy(config.S5, "ACCEL6");
strcpy(config.S6, "ACCEL7");
strcpy(config.S7, "ACCEL8");
EEPROM.put(1, config);
EEPROM.write(0, 253);
saveConfig();
readConfig();
isRebootRequired = true;
}
void ConfigManager::showConfig(){
ESP_LOGI(TAG_CONF, "Config size: %d bytes max %d", sizeof(config), EEPROM_SIZE);
String ii;
if(config.connect)
ii = "online";
else
ii = "offline";
ESP_LOGI(TAG_CONF, "Mode: %s", ii.c_str());
if(config.measure)
ii = "auto";
else
ii = "manual";
ESP_LOGI(TAG_CONF, "MEASURE: %s", ii.c_str());
ii = "PLACE: " + String(config.place);
ESP_LOGI(TAG_CONF, "%s", ii.c_str());
ii = "HOSTNAME: " + String(config.hostname);
ESP_LOGI(TAG_CONF, "%s", ii.c_str());
ii = "WIFI SSID: " + String(config.ssid);
ESP_LOGI(TAG_CONF, "%s", ii.c_str());
ii = "WIFI PASS: " + String(config.password);
ESP_LOGI(TAG_CONF, "%s", ii.c_str());
if(config.dhcp)
ii = "yes";
else
ii = "no";
ESP_LOGI(TAG_CONF, "DHCP: %s", ii.c_str());
ii = "IP: " + String(config.ip);
ESP_LOGI(TAG_CONF, "%s", ii.c_str());
ii = "MASK: " + String(config.subnet);
ESP_LOGI(TAG_CONF, "%s", ii.c_str());
ii = "GATEWAY: " + String(config.gateway);
ESP_LOGI(TAG_CONF, "%s", ii.c_str());
ii = "DNS: " + String(config.dns);
ESP_LOGI(TAG_CONF, "%s", ii.c_str());
ii = "USER: " + String(config.user);
ESP_LOGI(TAG_CONF, "%s", ii.c_str());
ii = "USER PASS: " + String(config.pass);
ESP_LOGI(TAG_CONF, "%s", ii.c_str());
if(config.connect) {
ii = "URL: " + String(config.restURL);
ESP_LOGI(TAG_CONF, "%s", ii.c_str());
ii = "PORT: " + String(config.restPort);
ESP_LOGI(TAG_CONF, "%s", ii.c_str());
ii = "USER: " + String(config.restUser);
ESP_LOGI(TAG_CONF, "%s", ii.c_str());
ii = "PASS: " + String(config.restPass);
ESP_LOGI(TAG_CONF, "%s", ii.c_str());
}
for (size_t i = 0; i < 32; i++) {
if (config.apiKey[i] < 0x10) Serial.print("0");
Serial.print(config.apiKey[i], HEX);
}
Serial.println();
ii = "Delay: " + String(config.pause) + "ms";
ESP_LOGI(TAG_CONF, "%s", ii.c_str());
ii = "S1: " + String(config.S0);
ESP_LOGI(TAG_CONF, "%s", ii.c_str());
ii = "S2: " + String(config.S1);
ESP_LOGI(TAG_CONF, "%s", ii.c_str());
ii = "S3: " + String(config.S2);
ESP_LOGI(TAG_CONF, "%s", ii.c_str());
ii = "S4: " + String(config.S3);
ESP_LOGI(TAG_CONF, "%s", ii.c_str());
ii = "S5: " + String(config.S4);
ESP_LOGI(TAG_CONF, "%s", ii.c_str());
ii = "S6: " + String(config.S5);
ESP_LOGI(TAG_CONF, "%s", ii.c_str());
ii = "S7: " + String(config.S6);
ESP_LOGI(TAG_CONF, "%s", ii.c_str());
ii = "S8: " + String(config.S7);
ESP_LOGI(TAG_CONF, "%s", ii.c_str());
}

View File

@@ -0,0 +1,279 @@
#include <Arduino.h>
#include "Display.h"
static const char *DISP = "display";
Display::Display(RTC_DS3231 &rtc, uint8_t address, uint8_t columns, uint8_t rows):rtc_(rtc) {
_lcd = new LiquidCrystal_I2C(address, columns, rows);
_address = address;
_columns = columns;
_rows = rows;
}
bool Display::begin(TwoWire *wire) {
wire->begin();
wire->beginTransmission(_address);
if (wire->endTransmission() != 0) {
return false; // brak odpowiedzi — error
}
// Inicjalizacja LCD
_lcd->begin(_columns, _rows);
_lcd->backlight();
_lcd->clear();
delay(100);
return true;
}
void Display::initMeasure(bool measure, bool run, bool runmes, uint16_t pause, uint8_t duration,
bool connect, long counter, String gdate, String gtime) {
clear();
textCenter(0, "** Measure params **");
String conting = measure ? "yes":"no";
String st2 = "Continous:";
String allt = st2 + conting;
textCenter(1, allt.c_str());
String type = "Sample:" + String(duration) + "s Pause:" + String(pause/1000)+"s";
textCenter(2, type.c_str());
textCenter(3, String("Time:" + gtime).c_str());
textStatus("initialisation");
}
void Display::clear() {
_lcd->clear();
}
void Display::textCenter(uint8_t line, const char *txt){
if (line >= _rows) {
ESP_LOGE(DISP, "Line >= max rows!");
return;
}
_lcd->setCursor(0, line);
int len = strlen(txt);
if (len < _columns) {
int pad = (_columns - len) / 2;
for (int i = 0; i < pad; i++) {
_lcd->print(" ");
}
}
_lcd->print(txt);
}
void Display::text(uint8_t line, const char *text) {
if (line >= _rows) {
ESP_LOGE(DISP, "Line >= max rows!");
return;
}
_lcd->setCursor(0, line);
_lcd->print(text);
}
void Display::welcomeScreen() {
ESP_LOGI(DISP, "Info screen");
clear();
textCenter(0, "** Vibra Dude **");
textCenter(1, "WMT Stalowa Wola");
String firm = String(VERSION);
firm.trim();
textCenter(2, firm.c_str());
//_lcd->print(VERSION);
}
void Display::mainScreen(){
ESP_LOGI(DISP, "Main screen");
clear();
textCenter(0, "** Vibra Dude **");
textCenter(1, "WMT Stalowa Wola");
//String firm = String(VERSION).trim();
//textCenter(2, firm.c_str());
//_lcd->print(VERSION);
ESP_LOGI(DISP, "Finish main screen");
}
void Display::clearRow(uint16_t line){
_lcd->setCursor(0, line);
_lcd->print(" ");
_lcd->setCursor(0, line);
}
void Display::textStatus(const char *text){
clearRow(3);
_lcd->setCursor(0, 3);
_lcd->print(text);
}
void Display::updateNetwork(String ip, bool connected) {
// przykładowa prosta implementacja:
// clear();
// text(0, connected ? "NET: OK" : "NET: OFF");
// _lcd->setCursor(0, 1);
// _lcd->print(ip);
}
void Display::updateBarWiFi(long signal, bool connected) {
// tu możesz wykorzystać np. init_bargraph / draw_horizontal_graph
// (void)signal;
// (void)connected;
}
void Display::textStyle(const uint8_t st, String text) {
// miejsce na różne style tekstu
//(void)st;
// clear();
//text(0, text.c_str());
}
void Display::message(String text) {
// clear();
//text(0, text.c_str());
}
void Display::print(String text) {
_lcd->print(text);
}
void Display::println(String text) {
_lcd->print(text);
_lcd->print("\n");
}
void Display::setCursor(int16_t x, int16_t y) {
_lcd->setCursor((uint8_t)x, (uint8_t)y);
}
void Display::showAccel(float a, float b, float c) {
clear();
// _lcd->setCursor(0, 0);
// _lcd->print("Ax: ");
// _lcd->print(a, 2);
// _lcd->setCursor(0, 1);
// _lcd->print("Ay: ");
// _lcd->print(b, 2);
// _lcd->setCursor(0, 2);
// _lcd->print("Az: ");
// _lcd->print(c, 2);
}
void Display::displayOffline(bool measure, uint8_t count, float freeSpace, long licznik, bool refresh){
if(refresh){
oyear, omonth, oday, ohour, omin, osec, ospace, oadxlcnt = 100;
_lcd->clear();
_lcd->setCursor(0, 0);
}
DateTime now = rtc_.now();
uint16_t year = now.year();
uint8_t month = now.month();
uint8_t day = now.day();
uint8_t hour = now.hour();
uint8_t min = now.minute();
uint8_t sec = now.second();
// DEBUG
//ESP_LOGI(DISP, "RTC: %d:%d:%d %d:%d:%d", now.day(), now.month(), now.year(), now.hour(), now.minute(), now.second());
//ESP_LOGI(DISP, "RTC: %d:%d:%d %d:%d:%d", oday, omonth, oyear, ohour, omin, osec);
if ((hour != ohour) || (refresh)) {
ohour = hour;
_lcd->setCursor(0, 0);
if (hour < 10) _lcd->print('0');
_lcd->print(hour);
_lcd->setCursor(2, 0);
_lcd->print(':');
}
if ((min != omin) || (refresh)) {
omin = min;
_lcd->setCursor(3, 0);
if (min < 10) _lcd->print('0');
_lcd->print(min);
_lcd->print(':');
}
if ((sec != osec) || (refresh)) {
_lcd->setCursor(6, 0);
osec = sec;
if (sec < 10) _lcd->print('0');
_lcd->print(sec);
}
if ((day != oday || month != omonth || year != oyear) || (refresh)){
_lcd->setCursor(10, 0);
if (day < 10) _lcd->print('0');
_lcd->print(day);
_lcd->print('.');
if (month < 10) _lcd->print('0');
_lcd->print(month);
_lcd->print('.');
_lcd->print(year);
oday = day;
omonth = month;
oyear = year;
if(!config.measure)
textStatus("OK: Start MEASURE");
else
textStatus("Wait for NEXT");
}
if((freeSpace != ospace) || (refresh)){
ospace = freeSpace;
_lcd->setCursor(0, 1);
_lcd->print("FREE:");
_lcd->print(freeSpace);
_lcd->print("GB");
}
if((count != oadxlcnt) || (omode != config.measure) || (refresh)){
oadxlcnt = count;
omode = config.measure;
_lcd->setCursor(0, 2);
_lcd->print("ADXL:");
_lcd->print(count);
_lcd->print(" MODE:");
_lcd->print(config.measure ? "AUTO ":"MANUAL ");
}
}
// Podczas pomiaru
void Display::displayOnOffM(bool measure, String myDir, String myFile) {
clear();
_lcd->setCursor(0, 0);
_lcd->print(measure ? "MEASURE STARTED" : "MEASURE STOPPED");
_lcd->setCursor(0, 1);
_lcd->print("DIR: " + myDir);
//_lcd->print(myDir);
//_lcd->setCursor(0, 2);
//_lcd->print("FILE:");
//_lcd->setCursor(0, 3);
_lcd->setCursor(0, 2);
_lcd->print(myFile);
}
/* Podsumowanie po pomiarze: ramki, sampling, etc. */
void Display::displaySampleRateSummary(uint32_t reccount, uint32_t captureSeconds, float khz, String filename){
clear();
_lcd->setCursor(0, 0);
if (captureSeconds == 0) {
_lcd->print("Sampling time 0!");
delay(1000);
return;
}
float fs = (float)reccount / (float)captureSeconds; // Hz (ramki/s)
float fs_kHz = fs / 1000.0f;
_lcd->setCursor(0,0);
_lcd->print("Frames:");
_lcd->print((unsigned)reccount);
_lcd->setCursor(0,1);
_lcd->print("Time:");
_lcd->print((unsigned)captureSeconds);
_lcd->print("s.");
_lcd->setCursor(0,2);
_lcd->print("Rate:");
_lcd->print(fs_kHz);
_lcd->print("kHz");
_lcd->setCursor(0,3);
_lcd->print(filename);
}

View File

@@ -0,0 +1,16 @@
#include <Logger.h>
const char *TAG_MAIN = "MAIN";
const char *TAG_DISPLAY = "DISPLAY";
const char *TAG_WIFI = "WiFi";
const char *TAG_ADXL = "ADXL345";
const char *TAG_CONF = "CONFIG";
void init_log_levels() {
esp_log_level_set(TAG_MAIN, ESP_LOG_INFO);
esp_log_level_set(TAG_DISPLAY, ESP_LOG_INFO);
esp_log_level_set(TAG_WIFI, ESP_LOG_INFO);
esp_log_level_set(TAG_ADXL, ESP_LOG_INFO);
esp_log_level_set(TAG_CONF, ESP_LOG_INFO);
}

View File

@@ -0,0 +1,604 @@
#include <Measure.h>
static const char *TAG_CAPTURE = "CAPTURE";
// Był na sztywno przydzielony bufor 128 KB. Zmieniam tak, aby zaalokować 4 MB lub 6 MB (trzeba zostawić resztę na potrzeby systemu).
DataCapture::DataCapture(ADXL345FastSPI &adxl, Display &display, RTC_DS3231 &rtc, fs::FS &storage, size_t bufferSize)
: adxl_(adxl), display_(display), rtc_(rtc), _fs(storage){
// Ustawiamy duży bufor dla PSRAM (np. 4 MB = 4194304 bajty)
// 4 * 1024 * 1024 / 12 bajtów = ~349 525 próbek
// Możesz to przekazać jako parametr lub wpisać na sztywno:
if (bufferSize < 4 * 1024 * 1024) bufferSize = 4 * 1024 * 1024;
bufferSize_ = bufferSize;
#if defined(ESP32)
// ps_malloc jest kluczowy dla PSRAM. Wymuszamy alokację w zewnętrznym PSRAM
buffer_ = (uint8_t*)ps_malloc(bufferSize_);
#endif
if (!buffer_) buffer_ = (uint8_t*)malloc(bufferSize_);
if (!buffer_) {
ESP_LOGE(TAG_CAPTURE, "No mem for buffer (%u B)", (unsigned)bufferSize_);
} else {
ESP_LOGI(TAG_CAPTURE, "Capture buffer: %u B", (unsigned)bufferSize_);
}
}
DataCapture::~DataCapture() {
if (buffer_) { free(buffer_); buffer_ = nullptr; }
}
void DataCapture::stop() { measurementActive_ = false; }
// --- ZAPIS BUFORA NA SD ---
bool DataCapture::flushToFile(File& f) {
if (bufferIndex_ == 0) return true;
Watchdog::feed();
size_t written = f.write(buffer_, bufferIndex_);
if (written != bufferIndex_) {
ESP_LOGE(TAG_CAPTURE, "SD save error (written=%u, expected=%u)", (unsigned)written, (unsigned)bufferIndex_);
display_.textStatus("SD save error");
bufferIndex_ = 0;
return false;
}
bufferIndex_ = 0;
return true;
}
// CRC8 (zostawione nieużywane domyślnie)
uint8_t DataCapture::crc8(const uint8_t* data, size_t len) {
uint8_t crc = 0x00;
for (size_t i = 0; i < len; ++i) {
crc ^= data[i];
for (uint8_t b = 0; b < 8; ++b) {
if (crc & 0x80) crc = (crc << 1) ^ 0x07; else crc <<= 1;
}
}
return crc;
}
void DataCapture::setTestingIndicator_(bool on, String myDir, String myFile) {
display_.displayOnOffM(on, myDir, myFile);
}
// --- automatyczna nazwa pliku ---
bool DataCapture::captureAuto(uint32_t captureSeconds, const char * /*baseDirectory*/) {
isExit = false;
Watchdog::feed();
String path = allocateNextFilePath();
if (path.isEmpty()) {
ESP_LOGE(TAG_CAPTURE, "Error generate file name in %s", _baseDir.c_str());
display_.textStatus("Error file");
return false;
}
ESP_LOGI(TAG_CAPTURE, "Autogenerate file: %s", path.c_str());
display_.textStatus(path.c_str());
Watchdog::feed();
return capture(captureSeconds, path.c_str());
}
// --- main measure function ---
// bool DataCapture::capture(uint32_t captureSeconds, const char *filename) {
// Watchdog::feed();
// if (!buffer_) {
// ESP_LOGE(TAG_CAPTURE, "No buffer - cancel.");
// display_.textStatus("Buffer error");
// return false;
// }
// ESP_LOGI(TAG_CAPTURE, "Capture %u sec. -> %s", (unsigned)captureSeconds, filename);
// File dataFile = _fs.open(filename, FILE_WRITE);
// if (!dataFile) {
// ESP_LOGE(TAG_CAPTURE, "Can't open file: %s", filename);
// char buf[100];
// snprintf(buf, sizeof(buf), "Can't open: %s", filename);
// display_.textStatus(buf);
// //s2etTestingIndicator_(false, _baseDir, filename);
// return false;
// }
// // Bufory RAMKI (po 1 próbce z każdego sensora)
// static const uint8_t MAXN = 7; // zgodnie z projektem
// int16_t X[MAXN]{}, Y[MAXN]{}, Z[MAXN]{};
// uint32_t TS[MAXN]{};
// Watchdog::feed();
// const uint32_t tStart_us = micros();
// const uint32_t captureDuration_us = captureSeconds * 1000000UL;
// // Czas startu (UTC) — tylko w nagłówku!
// const DateTime now = rtc_.now();
// const int32_t unix_start = now.unixtime();
// // Nagłówek
// FileHeader hdr;
// memcpy(hdr.magic, "WMT", 3);
// hdr.version = 1;
// hdr.headerSize = sizeof(FileHeader);
// hdr.sampleSize = sizeof(Sample);
// hdr.timestamp = unix_start;
// hdr.reccount = 0;
// if (dataFile.write(reinterpret_cast<const uint8_t*>(&hdr), sizeof(hdr)) != sizeof(hdr)) {
// ESP_LOGE(TAG_CAPTURE, "Header write failed");
// display_.textStatus("Header SD failed");
// dataFile.close();
// return false;
// }
// measurementActive_ = true;
// bufferIndex_ = 0;
// setTestingIndicator_(true, _baseDir, filename);
// uint32_t frames = 0; // liczba zebranych „ramek”
// const uint8_t presentCnt = adxl_.size(); // wykryte sensory (wg begin)
// // --- Pętla akwizycji wyrównanych ramek ---
// while (measurementActive_) {
// if(isEscape()) break; // wyjście z pętli gdy OK przerwie
// uint32_t now_us = micros();
// if ((now_us - tStart_us) >= captureDuration_us) break;
// // 1) Czekamy aż KAŻDY obecny sensor ma >= 1 próbkę w FIFO (DATA_READY)
// while (!adxl_.availableAll()) {
// if(isEscape()) break; //????? sprawdź kHz pomiaru!
// // krótki spin; unikamy delay(1), aby nie zrywać 3.2 kHz
// // (opcjonalnie yield(); jeśli system wymaga)
// }
// // 2) Zdejmij po 1 NAJSTARSZEJ próbce z każdego sensora (STREAM FIFO)
// const uint8_t got = adxl_.readAlignedOnce(X, Y, Z, TS);
// if (got != presentCnt) {
// // rzadki przypadek niepełna ramka; pomiń
// continue;
// }
// // 3) Wspólny timestamp ramki: minimalny z TS[i]
// uint32_t tmin = UINT32_MAX;
// for (uint8_t i = 0; i < MAXN; ++i) {
// if (!adxl_.isPresent(i)) continue;
// if (TS[i] < tmin) tmin = TS[i];
// }
// const uint32_t frame_offset_us = tmin - tStart_us;
// // 4) Zapis 7 rekordów Sample do bufora
// for (uint8_t i = 0; i < MAXN; ++i) {
// if (!adxl_.isPresent(i)) continue;
// // Konstruuj rekord bezpośrednio w buforze (bez memcpy)
// if (bufferIndex_ + sizeof(Sample) > bufferSize_) {
// if (!flushToFile(dataFile)) { measurementActive_ = false; break; }
// }
// Sample *dst = reinterpret_cast<Sample*>(buffer_ + bufferIndex_);
// dst->offset = frame_offset_us;
// dst->sensor_id = i;
// dst->x = X[i]; dst->y = Y[i]; dst->z = Z[i];
// dst->ready = true;
// bufferIndex_ += sizeof(Sample);
// }
// if (!measurementActive_) break;
// frames++;
// // Watchdog rzadziej, żeby nie zwiększać jittera
// if ((frames & 0xFF) == 0) Watchdog::feed();
// } // while
// // Statystyki
// ESP_LOGI(TAG_CAPTURE, "Frames: %u, sensors: %u", (unsigned)frames, (unsigned)presentCnt);
// printSamplingRate(frames, captureSeconds); // liczymy ramki/sek.
// // Domknij bufor
// if (bufferIndex_ > 0) {
// if (!flushToFile(dataFile)) {
// Watchdog::feed();
// ESP_LOGE(TAG_CAPTURE, "Finish save error.");
// display_.textStatus("Save SD error");
// delay(1000);
// }
// }
// ESP_LOGI(TAG_CAPTURE, "Saved to SD successful");
// display_.textStatus("Saved SD OK");
// delay(700);
// // Uzupełnij nagłówek o liczbę rekordów (Sample)
// hdr.reccount = frames * presentCnt;
// dataFile.seek(0);
// dataFile.write(reinterpret_cast<const uint8_t*>(&hdr), sizeof(hdr));
// dataFile.flush();
// dataFile.close();
// //setTestingIndicator_(false, _baseDir, filename);
// bool ok = measurementActive_;
// measurementActive_ = false;
// if (ok) {
// ESP_LOGI(TAG_CAPTURE, "Successful");
// display_.textStatus("Successfull");
// Watchdog::feed();
// return true;
// } else {
// ESP_LOGE(TAG_CAPTURE, "Measurement aborted");
// display_.textStatus("Aborted");
// Watchdog::feed();
// return false;f
// }
// }
// --- main measure function --- V2
bool DataCapture::capture(uint32_t captureSeconds, const char *filename) {
Watchdog::feed();
if (!buffer_) {
ESP_LOGE(TAG_CAPTURE, "No buffer - cancel.");
display_.textStatus("Buffer error");
return false;
}
// Obliczamy ile danych maksymalnie może wejść do bufora
const uint8_t presentCnt = adxl_.size();
const size_t frameSize = presentCnt * sizeof(Sample);
ESP_LOGI(TAG_CAPTURE, "Start RAM capture: %u sec.", (unsigned)captureSeconds);
display_.textStatus("Sampling...");
static const uint8_t MAXN = 7;
int16_t X[MAXN]{}, Y[MAXN]{}, Z[MAXN]{};
uint32_t TS[MAXN]{};
measurementActive_ = true;
bufferIndex_ = 0;
uint32_t frames = 0;
const uint32_t tStart_us = micros();
const uint32_t captureDuration_us = captureSeconds * 1000000UL;
const DateTime now_start = rtc_.now(); // Czas dla nagłówka
// --- KRYTYCZNA PĘTLA POMIAROWA (ZERO SD) ---
while (measurementActive_) {
if(isEscape()) break;
uint32_t now_us = micros();
if ((now_us - tStart_us) >= captureDuration_us) break;
// Sprawdzenie czy mamy miejsce w PSRAM na kolejną pełną ramkę
if (bufferIndex_ + frameSize > bufferSize_) {
ESP_LOGW(TAG_CAPTURE, "PSRAM Buffer Full! Stopping.");
break;
}
// Czekamy na dane z sensora
uint32_t start_wait = millis();
while (!adxl_.availableAll()) {
if (isEscape()) break;
if (millis() - start_wait > 500) {
ESP_LOGE(TAG_CAPTURE, "Sensor data timeout - aborting capture.");
measurementActive_ = false;
break;
}
Watchdog::feed();
yield();
}
if (!measurementActive_) break;
const uint8_t got = adxl_.readAlignedOnce(X, Y, Z, TS);
if (got == 0 || got != presentCnt) continue;
// Wspólny timestamp ramki
uint32_t tmin = UINT32_MAX;
for (uint8_t i = 0; i < MAXN; ++i) {
if (!adxl_.isPresent(i)) continue;
if (TS[i] < tmin) tmin = TS[i];
}
const uint32_t frame_offset_us = tmin - tStart_us;
// Zapis do PSRAM
for (uint8_t i = 0; i < MAXN; ++i) {
if (!adxl_.isPresent(i)) continue;
Sample *dst = reinterpret_cast<Sample*>(buffer_ + bufferIndex_);
dst->offset = frame_offset_us;
dst->sensor_id = i;
dst->x = X[i]; dst->y = Y[i]; dst->z = Z[i];
dst->ready = true;
bufferIndex_ += sizeof(Sample);
}
frames++;
if ((frames & 0x3FF) == 0) Watchdog::feed(); // Rzadziej, by nie siać jittera
} // --- KONIEC PĘTLI POMIAROWEJ ---
measurementActive_ = false;
ESP_LOGI(TAG_CAPTURE, "Capture finished. Samples in RAM: %u. Writing to SD...", (unsigned)frames);
display_.textStatus("Saving to SD...");
// Dopiero teraz otwieramy plik na SD
File dataFile = _fs.open(filename, FILE_WRITE);
if (!dataFile) {
ESP_LOGE(TAG_CAPTURE, "Can't open SD file!");
display_.textStatus("SD Open Error");
return false;
}
// Przygotowanie nagłówka
FileHeader hdr;
memcpy(hdr.magic, "WMT", 3);
hdr.version = 1;
hdr.headerSize = sizeof(FileHeader);
hdr.sampleSize = sizeof(Sample);
hdr.timestamp = now_start.unixtime();
hdr.reccount = frames * presentCnt;
// Zapis nagłówka
dataFile.write(reinterpret_cast<const uint8_t*>(&hdr), sizeof(hdr));
// Zapis całego bufora PSRAM jednym ciągiem
size_t written = dataFile.write(buffer_, bufferIndex_);
if (written == bufferIndex_) {
ESP_LOGI(TAG_CAPTURE, "SD Save Successful: %u bytes", (unsigned)written);
//display_.textStatus("Save OK");
display_.textStatus(basenameFromPath(filename));
delay(2000); // Tutaj zrob to inaczej
} else {
ESP_LOGE(TAG_CAPTURE, "SD Write Error!");
display_.textStatus("SD Write Err");
}
dataFile.close();
printSamplingRate(frames, captureSeconds, filename);
return (written == bufferIndex_);
}
// --- Narzędzia SD i pierdoły ---
String DataCapture::unixToDateTime(uint32_t ts) {
DateTime dt(ts);
char buf[25];
snprintf(buf, sizeof(buf), "%04u-%02u-%02u %02u:%02u:%02u",
dt.year(), dt.month(), dt.day(), dt.hour(), dt.minute(), dt.second());
return String(buf);
}
void DataCapture::readHeaderAndPrint(const char *path) {
Watchdog::feed();
File f = SD.open(path, FILE_READ);
if (!f) { ESP_LOGE(TAG_CAPTURE,"Error open file %s", path); return; }
FileHeader hdr;
size_t rd = f.read((uint8_t*)&hdr, sizeof(hdr));
f.close();
if (rd != sizeof(hdr)) { ESP_LOGE(TAG_CAPTURE,"Header error"); return; }
if (memcmp(hdr.magic, "WMT", 3) != 0) { ESP_LOGE(TAG_CAPTURE,"File signature error"); return; }
ESP_LOGI(TAG_CAPTURE,"===== WMT file header =====");
ESP_LOGI(TAG_CAPTURE,"Signature: %.3s", hdr.magic);
ESP_LOGI(TAG_CAPTURE,"Version: %u", hdr.version);
ESP_LOGI(TAG_CAPTURE,"Header size:%u B", hdr.headerSize);
ESP_LOGI(TAG_CAPTURE,"Sample size:%u B", hdr.sampleSize);
ESP_LOGI(TAG_CAPTURE,"Timestamp: %d", hdr.timestamp);
ESP_LOGI(TAG_CAPTURE,"Reccount: %u", hdr.reccount);
ESP_LOGI(TAG_CAPTURE,"===========================");
}
void DataCapture::printSamplingRate(uint32_t reccount, uint32_t captureSeconds, String filename) {
Watchdog::feed();
if (captureSeconds == 0) { ESP_LOGW(TAG_CAPTURE, "Sampling time = 0!"); return; }
float fs = (float)reccount / (float)captureSeconds; // Hz (ramki/s)
float fs_kHz = fs / 1000.0f;
ESP_LOGI(TAG_CAPTURE,"Frames: %u", (unsigned)reccount);
ESP_LOGI(TAG_CAPTURE,"Time: %u s", (unsigned)captureSeconds);
ESP_LOGI(TAG_CAPTURE,"Rate: %.3f kHz (frames)", fs_kHz);
display_.displaySampleRateSummary(reccount, captureSeconds, fs_kHz, filename);
display_.textStatus("Summary");
delay(2000);
}
// --- Zarządzanie katalogami/plikiem ---
String DataCapture::allocateNextFilePath() {
Watchdog::feed();
uint32_t highestDir = findHighestNumericDir();
if (highestDir == 0) {
highestDir = 1;
if (!ensureDir(highestDir)) return String();
}
uint32_t count = 0, highestIdx = 0;
scanDirForWmt(highestDir, count, highestIdx);
uint32_t targetDir = highestDir;
uint32_t nextIdx = 0;
if (count > _maxFilesPerDir) {
targetDir = highestDir + 1;
if (!ensureDir(targetDir)) return String();
nextIdx = 1;
} else {
nextIdx = (highestIdx == 0) ? 1 : (highestIdx + 1);
}
return joinPath(dirPath(targetDir), makeIndexedName(nextIdx));
}
String DataCapture::generateNextFilename() {
Watchdog::feed();
uint32_t highestDir = findHighestNumericDir();
if (highestDir == 0) return String();
uint32_t count = 0, highestIdx = 0;
scanDirForWmt(highestDir, count, highestIdx);
ESP_LOGI(TAG_CAPTURE, "highestDir %u, count %u, highestIdx %u", (unsigned)highestDir, (unsigned)count, (unsigned)highestIdx);
if (highestIdx == 0) return String();
return joinPath(dirPath(highestDir), makeIndexedName(highestIdx));
}
FileInfo DataCapture::getLastFileInfo() {
FileInfo info{String(), 0u, false};
Watchdog::feed();
String lastPath = generateNextFilename();
if (lastPath.isEmpty()) return info;
if (_fs.exists(lastPath)) {
File f = _fs.open(lastPath, FILE_READ);
if (f) {
info.exists = true;
info.path = lastPath;
info.size = f.size();
f.close();
}
}
return info;
}
bool DataCapture::deleteAllOnSD() {
Watchdog::feed();
File root = _fs.open(_baseDir);
if (!root || !root.isDirectory()) return false;
for (File e = root.openNextFile(); e; e = root.openNextFile()) {
Watchdog::feed();
String child = String(e.name());
e.close();
if (!recursiveDelete(child)) return false;
}
root.close();
return true;
}
SpaceInfo DataCapture::freeSpaceMB() {
Watchdog::feed();
SpaceInfo si{0.0, "UNKNOWN"};
#if defined(ESP32)
uint64_t total = SD.totalBytes();
uint64_t used = SD.usedBytes();
if (total >= used) {
uint64_t freeB = total - used;
const double oneGB = 1024.0 * 1024.0 * 1024.0;
const double oneMB = 1024.0 * 1024.0;
if (freeB >= (uint64_t)oneGB) {
si.value = (double)freeB / oneGB; si.unit = "GB";
} else {
si.value = (double)freeB / oneMB; si.unit = "MB";
}
}
#endif
return si;
}
float DataCapture::freeSpaceFloat(bool* isGB) {
Watchdog::feed();
SpaceInfo si = freeSpaceMB();
if (!si.unit || strcmp(si.unit, "UNKNOWN") == 0) {
if (isGB) *isGB = false;
return -1.0f;
}
if (isGB) *isGB = (strcmp(si.unit, "GB") == 0);
return static_cast<float>(si.value);
}
// --- helpery pomocnicze ---
bool DataCapture::isAllDigits(const char *s) {
if (!s || !*s) return false;
while (*s) { if (*s < '0' || *s > '9') return false; ++s; }
return true;
}
uint32_t DataCapture::toUint(const char *s) {
uint32_t v = 0; while (*s) { v = v*10u + uint32_t(*s - '0'); ++s; } return v;
}
String DataCapture::dirPath(uint32_t dirNum) const { return joinPath(_baseDir, String(dirNum)); }
const char *DataCapture::basenameFromPath(const char *full) { const char *slash = strrchr(full, '/'); return slash ? slash + 1 : full; }
bool DataCapture::isWmtWithDigits(const char* name, uint32_t& idxOut) const {
size_t nlen = strlen(name), extLen = _ext.length();
if (nlen != (size_t)_digits + extLen) return false;
if (strncmp(name + _digits, _ext.c_str(), extLen) != 0) return false;
for (uint8_t i = 0; i < _digits; ++i) if (name[i] < '0' || name[i] > '9') return false;
char buf[16]; memcpy(buf, name, _digits); buf[_digits] = '\0';
idxOut = (uint32_t)strtoul(buf, nullptr, 10); return true;
}
String DataCapture::makeIndexedName(uint32_t idx) const {
char fmt[8]; snprintf(fmt, sizeof(fmt), "%%0%ulu", (unsigned long)_digits);
char num[24]; snprintf(num, sizeof(num), fmt, (unsigned long)idx);
return String(num) + _ext;
}
String DataCapture::joinPath(const String &a, const String &b) { if (a.endsWith("/")) return a + b; return a + "/" + b; }
bool DataCapture::ensureDir(uint32_t dirNum) {
String path = dirPath(dirNum);
if (_fs.exists(path)) { File f = _fs.open(path); bool ok = f && f.isDirectory(); if (f) f.close(); return ok; }
return _fs.mkdir(path);
}
uint32_t DataCapture::findHighestNumericDir() {
File root = _fs.open(_baseDir);
if (!root || !root.isDirectory()) return 0;
uint32_t maxDir = 0;
for (File f = root.openNextFile(); f; f = root.openNextFile()) {
if (f.isDirectory()) {
const char *nm = basenameFromPath(f.name());
if (isAllDigits(nm)) { uint32_t v = toUint(nm); if (v > maxDir) maxDir = v; }
}
}
return maxDir;
}
void DataCapture::scanDirForWmt(uint32_t dirNum, uint32_t &count, uint32_t &highestIdx) {
Watchdog::feed();
count = 0; highestIdx = 0;
String path = dirPath(dirNum);
File dir = _fs.open(path);
if (!dir || !dir.isDirectory()) return;
for (File f = dir.openNextFile(); f; f = dir.openNextFile()) {
if (f.isDirectory()) { Watchdog::feed(); f.close(); continue; }
const char* base = basenameFromPath(f.name());
uint32_t idx = 0;
if (isWmtWithDigits(base, idx)) { count++; if (idx > highestIdx) highestIdx = idx; }
f.close();
}
}
bool DataCapture::recursiveDelete(const String &path) {
File e = _fs.open(path);
if (!e) return false;
if (!e.isDirectory()) { e.close(); return _fs.remove(path); }
// katalog
for (File c = e.openNextFile(); c; c = e.openNextFile()) {
String child = String(c.name()); c.close();
if (!recursiveDelete(child)) { e.close(); return false; }
}
e.close();
if (path == _baseDir) return true; // nie usuwamy katalogu bazowego
return _fs.rmdir(path);
}
void DataCapture::printLastFileInfoSerial() {
Watchdog::feed();
const FileInfo info = getLastFileInfo();
if (!info.exists) {
ESP_LOGE(TAG_CAPTURE, "No .wmt file exists. Last file not exists");
return;
}
const double kb = static_cast<double>(info.size) / 1024.0;
const double mb = kb / 1024.0;
#if defined(ESP32)
// ESP32 wspiera Serial.printf z %llu
ESP_LOGI(TAG_CAPTURE, "Last file: %s", info.path.c_str());
ESP_LOGI(TAG_CAPTURE, "Size: %llu B (%.2f KB, %.2f MB)", static_cast<unsigned long long>(info.size), kb, mb);
char buf[100];
snprintf(buf, sizeof(buf), "Size:%.2f KB", kb);
display_.textStatus(buf);
#else
// Wariant zachowawczy dla platform bez %llu; pokazujemy rozmiar w MB/KB.
ESP_LOGI(TAG_CAPTURE, "Last file info: "));
ESP_LOGI(TAG_CAPTURE, info.path);
ESP_LOGI(TAG_CAPTURE, "Size: "));
ESP_LOGI(TAG_CAPTURE, mb);
//ESP_LOGI(TAG_CAPTURE, " MB"));
//Serial.print(F(" ("));
//Serial.print(kb, 2);
//Serial.println(F(" KB)"));
#endif
}

View File

@@ -0,0 +1,275 @@
#include <Network.h>
#include <SD.h>
static const char *WIFI = "wifi";
extern ConfigManager configManager;
WiFiManager::WiFiManager() {}
void WiFiManager::begin() {
ESP_LOGI(WIFI, "Start network");
isAccessPoint = config.connect;
if (!isAccessPoint) {
setupAccessPoint(ssidAP.c_str(), passwordAP.c_str());
} else {
ESP_LOGI(WIFI, "WiFi client");
ReadConnection();
connectToWiFi();
}
getRSSI();
setupMDNS();
}
// Konwersja char na IPAddress
bool WiFiManager::convertCharToIPAddress(const char *str, IPAddress& ip) {
uint8_t octets[4];
int parsed = sscanf(str, "%hhu.%hhu.%hhu.%hhu", &octets[0], &octets[1], &octets[2], &octets[3]);
if (parsed == 4) {
ip = IPAddress(octets[0], octets[1], octets[2], octets[3]);
return true;
}
return false;
}
void WiFiManager::ReadConnection() {
ESP_LOGE(WIFI, "Network config error");
isAccessPoint = config.connect;
useDHCP = config.dhcp;
convertCharToIPAddress(config.ip, local_IP);
convertCharToIPAddress(config.gateway, gateway);
convertCharToIPAddress(config.subnet, subnet);
convertCharToIPAddress(config.dns, dns);
}
void WiFiManager::connectToWiFi() {
ESP_LOGI(WIFI, "WiFi STA mode");
WiFi.mode(WIFI_STA);
WiFi.begin(config.ssid, config.password);
if (!useDHCP) {
if (!WiFi.config(local_IP, gateway, subnet, dns)) {
ESP_LOGE(WIFI, "Network static IP failed");
}
}
ESP_LOGI(WIFI, "WiFi connecting to last SSID: %s", config.ssid);
int retries = 0;
while (WiFi.status() != WL_CONNECTED && retries < 20) {
delay(500);
retries++;
updateLED();
}
if (WiFi.status() != WL_CONNECTED) {
ESP_LOGI(WIFI, "Failed. Reading wifi.txt from SD card...");
File f = SD.open("/wifi.txt");
if (f) {
while (f.available()) {
String line = f.readStringUntil('\n');
line.trim();
if (line.isEmpty()) continue;
int sep = line.indexOf(';');
if (sep > 0) {
String s = line.substring(0, sep);
String p = line.substring(sep + 1);
s.trim();
p.trim();
ESP_LOGI(WIFI, "Trying SSID from list: '%s'", s.c_str());
WiFi.disconnect();
WiFi.begin(s.c_str(), p.c_str());
int tr = 0;
while (WiFi.status() != WL_CONNECTED && tr < 20) {
delay(500);
tr++;
updateLED();
}
if (WiFi.status() == WL_CONNECTED) {
ESP_LOGI(WIFI, "Connected to %s. Saving to config.", s.c_str());
strncpy(config.ssid, s.c_str(), sizeof(config.ssid)-1);
strncpy(config.password, p.c_str(), sizeof(config.password)-1);
configManager.saveConfig();
break;
}
}
}
f.close();
} else {
ESP_LOGW(WIFI, "wifi.txt not found on SD card.");
}
}
if (WiFi.status() == WL_CONNECTED) {
ESP_LOGI(WIFI, "SSID: %s", config.ssid);
String ipString = "IP: " + WiFi.localIP().toString();
ESP_LOGI(WIFI, "%s", ipString.c_str());
String gatewayInfo = "GATEWAY: " + WiFi.gatewayIP().toString();
ESP_LOGI(WIFI, "%s", gatewayInfo.c_str());
String dnsInfo = "DNS: " + WiFi.dnsIP().toString();
ESP_LOGI(WIFI, "%s", dnsInfo.c_str());
} else {
String infoWiFi = "WIFI CONNECTION ERROR: ALL FAILED";
ESP_LOGI(WIFI, "%s", infoWiFi.c_str());
}
updateLED();
}
void WiFiManager::setupAccessPoint(const char *newSSID, const char *newPassword) {
ESP_LOGI(WIFI, "Start AP mode");
// Wyłączenie zapisywania konfiguracji do flash
//WiFi.persistent(false);
// Usunięcie zapisanej konfiguracji trybu stacji
//WiFi.disconnect(true);
//delay(1000);
//WiFi.eraseAP();
//WiFi.enableAP(false);
//WiFi.enableAP(true);
WiFi.mode(WIFI_AP);
WiFi.softAPsetHostname(config.hostname);
WiFi.softAP(newSSID, newPassword);
delay(1000);
String ssi = "AP SSID: " + String(WiFi.softAPSSID());
ESP_LOGI(WIFI, "%s", ssi.c_str());
String sspass = "AP PASS: " + String(newPassword);
ESP_LOGI(WIFI, "%s", sspass.c_str());
IPAddress IP = WiFi.softAPIP();
String ipString = "AP IP: " + IP.toString();
ESP_LOGI(WIFI, "%s", ipString.c_str());
setupMDNS();
getRSSI();
}
bool WiFiManager::isWiFiOK(){
if (WiFi.status() != WL_CONNECTED)
return false;
else
return true;
}
void WiFiManager::checkWiFiConnection() {
if (!isAccessPoint) {
getRSSI();
if (WiFi.status() != WL_CONNECTED) {
String infoWiFi = "WiFi reconnecting: " + String(config.ssid);
ESP_LOGI(WIFI, "%s", infoWiFi.c_str());
WiFi.reconnect();
int retries = 0;
while (WiFi.status() != WL_CONNECTED && retries < 20) {
delay(500);
retries++;
updateLED();
}
if (WiFi.status() == WL_CONNECTED) {
//String infoWiFi = "WiFi connected: " + String(config.ssid);
//ESP_LOGI(WIFI, "%s", infoWiFi.c_str());
//String ipString = "IP: " + WiFi.localIP().toString();
//ESP_LOGI(WIFI, "%s", ipString.c_str());
getRSSI();
setupMDNS();
} else {
String infoWiFi = "WiFi reconnect error: " + String(config.ssid);
ESP_LOGI(WIFI, "%s", infoWiFi.c_str());
getRSSI();
}
}
updateLED();
}
}
int WiFiManager::rssiToPercent(int rssi) {
if (rssi <= -100) {
return 0;
} else
if (rssi >= -50) {
return 100;
}
// Dla wartości pomiędzy -100 a -50 dBm stosujemy prostą liniową skalę
else {
return 2 * (rssi + 100); // Przykładowa liniowa zależność
}
}
void WiFiManager::updateLED() {
getRSSI();
if (WiFi.status() == WL_CONNECTED) {
int8_t rssi = WiFi.RSSI();
if (rssi > -70) {
;
//digitalWrite(LED_PIN, HIGH); // Silny sygnał
} else {
String signalInfo = "WIFI WEAK SIGNAL: " + String(rssi) + " " + rssiToPercent(rssi) + "%";
ESP_LOGW(WIFI, "%s", signalInfo.c_str());
}
}
}
int8_t WiFiManager::getRSSI() {
if (WiFi.status() == WL_CONNECTED) {
rssi = WiFi.RSSI();
return rssi;
}
return 0;
}
void WiFiManager::setupMDNS() {
ESP_LOGI(WIFI, "mDNS start");
if (!MDNS.begin(config.hostname)) {
ESP_LOGE(WIFI, "mDNS error");
} else {
String mdnsstr = "MDNS: http://" + String(config.hostname) + ".local";
ESP_LOGI(WIFI, "%s", mdnsstr.c_str());
}
}
bool WiFiManager::performOTAUpdate(bool allowInsecureTLS, std::function<void(int,int)> progressCb){
// 1) Pobierz i sprawdź URL
String url = String(config.updateUrl); // z Config.h globalny 'config'
url.trim();
if (url.isEmpty()) {
ESP_LOGE(WIFI, "[OTA] Pusty config.updateUrl przerwano.");
return false;
}
ESP_LOGI(WIFI, "[OTA] URL: %s", url.c_str());
// 2) Wymagamy aktywnego Wi-Fi w trybie klienta
if (WiFi.status() != WL_CONNECTED) {
ESP_LOGE(WIFI, "[OTA] Brak połączenia Wi-Fi przerwano.");
return false;
}
// 3) Callback postępu (opcjonalny)
if (progressCb) {
httpUpdate.onProgress([&](int cur, int total){ progressCb(cur, total); });
}
// 4) Konfiguracja klienta i wywołanie aktualizacji
httpUpdate.rebootOnUpdate(true); // po sukcesie reboot
t_httpUpdate_return ret;
if (url.startsWith("https://")) {
WiFiClientSecure client;
if (allowInsecureTLS) {
client.setInsecure(); // UWAGA: testy/dev; w produkcji lepiej setCACert(...)
}
client.setTimeout(15000);
ret = httpUpdate.update(client, url); // HTTPS
} else {
WiFiClient client;
client.setTimeout(15000);
ret = httpUpdate.update(client, url); // HTTP
}
// 5) Obsługa rezultatów
switch (ret) {
case HTTP_UPDATE_OK:
ESP_LOGI(WIFI, "[OTA] Sukces restart nastąpi za chwilę.");
return true; // reboot i tak zaraz nastąpi
case HTTP_UPDATE_NO_UPDATES:
ESP_LOGW(WIFI, "[OTA] Brak nowej wersji (304/Not Modified).");
return false;
case HTTP_UPDATE_FAILED:
default:
ESP_LOGE(WIFI, "[OTA] Błąd (%d): %s", httpUpdate.getLastError(), httpUpdate.getLastErrorString().c_str());
return false;
}
}

View File

@@ -0,0 +1,172 @@
#include <Settings.h>
// Lokalny tag logów
static const char *TAG_SETTINGS = "SETTINGS";
Settings::Settings(Display &display, RTC_DS3231 &rtc): display_(display), rtc_(rtc){}
Settings::~Settings() {}
void Settings::begin() {
pinMode(BTN_UP, INPUT_PULLUP);
pinMode(BTN_OK, INPUT_PULLUP);
pinMode(BTN_DOWN, INPUT_PULLUP);
}
// Zwraca true, jeśli przycisk jest wciśnięty
bool Settings::isPressed(uint8_t btnIndex) {
//ESP_LOGI(TAG_SETTINGS, "BTN check");
switch (btnIndex) {
case 1: return digitalRead(BTN_UP) == LOW;
case 2: return digitalRead(BTN_OK) == LOW;
case 3: return digitalRead(BTN_DOWN) == LOW;
default: return false;
}
}
// Kombinacja 1 i 3 jednocześnie
bool Settings::isBtnReset() {
ESP_LOGI(TAG_SETTINGS, "BTN reset check");
return (digitalRead(BTN_UP) == LOW && digitalRead(BTN_DOWN) == LOW);
}
bool Settings::isSetClock() {
ESP_LOGI(TAG_SETTINGS, "BTN set clock");
return (digitalRead(BTN_OK) == LOW);
}
/*
Ustawienie opcji
*/
void Settings::setConfigDevice() {
ESP_LOGI(TAG_SETTINGS, "Set config device");
display_.clear();
delay(300);
config.duration = editField(config.duration, 1, 20, "Test duration");
uint16_t dur = config.pause/1000;
dur = editField(dur, 1, 20, "Pause between");
config.pause = dur * 1000;
config.measure = editField(config.measure, 0, 1, "Autorun test");
ESP_LOGI(TAG_SETTINGS, "Config finish");
}
void Settings::finishConfigDevice(){
display_.clear();
display_.textCenter(1, "CONFIG UPDATED");
display_.textCenter(2, "RESTARTING");
delay(1000);
display_.clear();
ESP.restart();
}
/* --------------------------------------------------------------------
* Ustawianie czasu RTC:
* Kolejność: rok -> miesiąc -> dzień -> godzina -> minuta -> sekunda
* ------------------------------------------------------------------*/
void Settings::setTimeRTC() {
DateTime now = rtc_.now();
RtcDateTime t;
t.year = now.year();
t.month = now.month();
t.day = now.day();
t.hour = now.hour();
t.minute = now.minute();
t.second = now.second();
ESP_LOGI(TAG_SETTINGS,
"Start RTC edit: %04u-%02u-%02u %02u:%02u:%02u",
t.year, t.month, t.day, t.hour, t.minute, t.second);
display_.clear();
delay(300);
// Kolejne pola
t.year = editField(t.year, 2024, 2099, "YEAR");
t.month = editField(t.month, 1, 12, "MONTH");
// uproszczenie: 131 bez sprawdzania długości miesiąca
t.day = editField(t.day, 1, 31, "DAY");
t.hour = editField(t.hour, 0, 23, "HOUR");
t.minute = editField(t.minute, 0, 59, "MINUTE");
t.second = editField(t.second, 0, 59, "SECOND");
// Zapis do RTC
rtc_.adjust(DateTime(t.year, t.month, t.day, t.hour, t.minute, t.second));
ESP_LOGI(TAG_SETTINGS,
"RTC set to: %04u-%02u-%02u %02u:%02u:%02u",
t.year, t.month, t.day, t.hour, t.minute, t.second);
display_.clear();
display_.textCenter(1, "RTC UPDATED");
display_.textCenter(2, "RESTARTING");
//display_.textCenter(2, "OK TO CONTINUE");
delay(1000);
display_.clear();
ESP.restart();
}
// Edycja jednej wartości (rok/miesiąc/dzień/godz/min/sek)
int Settings::editField(int value, int minVal, int maxVal, const char *label) {
bool lastUp = false;
bool lastDown = false;
bool lastOk = false;
constrainValue(value, minVal, maxVal);
printField(label, value);
while (true) {
bool up = readBtnUp();
bool ok = readBtnOk();
bool down = readBtnDown();
// Zmiana przy puszczeniu/wciśnięciu (zbocze narastające)
if (up && !lastUp) {
value++;
if (value > maxVal) value = minVal;
printField(label, value);
}
if (down && !lastDown) {
value--;
if (value < minVal) value = maxVal;
printField(label, value);
}
if (ok && !lastOk) {
ESP_LOGI(TAG_SETTINGS, "[OK] %s = %d", label, value);
// mały debounce na wyjście z pola
delay(200);
return value;
}
lastUp = up;
lastDown = down;
lastOk = ok;
delay(80); //debouncing
}
}
void Settings::constrainValue(int &value, int minVal, int maxVal) {
if (value < minVal) value = minVal;
if (value > maxVal) value = maxVal;
}
// Wyświetlanie aktualnie edytowanego pola na LCD
void Settings::printField(const char *label, int value) {
char buf[21];
display_.clear();
// Linia 0: nazwa pola
snprintf(buf, sizeof(buf), "%s:", label);
display_.text(0, buf);
// Linia 1: wartość
snprintf(buf, sizeof(buf), "%d", value);
display_.text(1, buf);
// Linia 3: podpowiedź
display_.textStatus("UP/DOWN, OK=Next");
ESP_LOGI(TAG_SETTINGS, "%s = %d", label, value);
}

View File

@@ -0,0 +1,48 @@
#include <Tool.h>
static const char *TOOL = "tool";
bool isI2CDevPresent(uint8_t address) {
Wire.beginTransmission(address);
uint8_t error = Wire.endTransmission();
if (error == 0) {
ESP_LOGI(TOOL, "I2C response from 0x%02X", address);
return true;
} else {
if (error == 4) {
ESP_LOGW(TOOL, "I2C unknown error at 0x%02X", address);
} else {
ESP_LOGI(TOOL, "No I2C device at 0x%02X", address);
}
return false;
}
}
void scanI2C() {
byte error, address;
int nDevices = 0;
ESP_LOGI(TOOL, "I2C start scan");
for (address = 1; address < 127; address++) {
Wire.beginTransmission(address);
error = Wire.endTransmission();
if (error == 0) {
char buf[32];
snprintf(buf, sizeof(buf), "I2C device: 0x%02X", address);
ESP_LOGI(TOOL, "%s", buf);
nDevices++;
Watchdog::feed();
}
else if (error == 4) {
ESP_LOGW(TOOL, "I2C error at address: 0x%02X", address);
}
}
if (nDevices == 0) {
ESP_LOGE(TOOL, "I2C no devices found");
} else {
ESP_LOGI(TOOL, "I2C scan finished. Found %d device(s)", nDevices);
}
Watchdog::feed();
}

View File

@@ -0,0 +1,117 @@
#include "UploadManager.h"
#include "Watchdog.h"
static const char* TAG_UPLOAD = "UPLOAD";
static const char* LOG_FILE = "/uploaded.csv";
UploadManager::UploadManager(APIClient& client, RTC_DS3231& rtc, DataCapture& capture)
: apiClient(client), rtc_(rtc), capture_(capture) {}
String UploadManager::getCurrentTimestamp() {
DateTime now = rtc_.now();
char buf[25];
snprintf(buf, sizeof(buf), "%04u-%02u-%02u %02u:%02u:%02u",
now.year(), now.month(), now.day(), now.hour(), now.minute(), now.second());
return String(buf);
}
void UploadManager::appendLog(const String& filePath, const String& status) {
File f = SD.open(LOG_FILE, FILE_APPEND);
if (f) {
f.printf("%s,%s,%s\n", getCurrentTimestamp().c_str(), filePath.c_str(), status.c_str());
f.close();
}
}
bool UploadManager::isAlreadyUploaded(const String& filePath) {
File f = SD.open(LOG_FILE, FILE_READ);
if (!f) return false;
bool found = false;
while (f.available()) {
String line = f.readStringUntil('\n');
line.trim();
if (line.length() == 0) continue;
int firstComma = line.indexOf(',');
if (firstComma < 0) continue;
int secondComma = line.indexOf(',', firstComma + 1);
if (secondComma < 0) continue;
String logPath = line.substring(firstComma + 1, secondComma);
String logStatus = line.substring(secondComma + 1);
if (logPath == filePath) {
if (logStatus == "OK") found = true;
else found = false; // A retry might be needed if last status wasn't OK
}
Watchdog::feed();
}
f.close();
return found;
}
void UploadManager::uploadFile(const String& filePath) {
if (isAlreadyUploaded(filePath)) {
ESP_LOGI(TAG_UPLOAD, "File %s is already uploaded.", filePath.c_str());
return;
}
if (WiFi.status() != WL_CONNECTED) {
ESP_LOGE(TAG_UPLOAD, "No WiFi. Cannot upload %s", filePath.c_str());
appendLog(filePath, "ERROR: No WiFi");
return;
}
bool success = apiClient.uploadMeasurement(filePath);
if (success) {
appendLog(filePath, "OK");
} else {
if (WiFi.status() != WL_CONNECTED) {
appendLog(filePath, "ERROR: WiFi lost during upload");
} else {
appendLog(filePath, "ERROR: Upload Failed");
}
}
}
void UploadManager::processPendingUploads() {
if (WiFi.status() != WL_CONNECTED) return;
ESP_LOGI(TAG_UPLOAD, "Checking for pending uploads...");
uint32_t highestDir = capture_.findHighestNumericDir();
if (highestDir == 0) return;
for (uint32_t d = 1; d <= highestDir; d++) {
String path = capture_.dirPath(d);
File dir = SD.open(path);
if (!dir || !dir.isDirectory()) {
if(dir) dir.close();
continue;
}
for (File f = dir.openNextFile(); f; f = dir.openNextFile()) {
if (f.isDirectory()) { f.close(); continue; }
String childPath = String(f.name());
if (!childPath.startsWith("/")) {
childPath = path + "/" + childPath;
}
if (childPath.endsWith(".wmt")) {
if (!isAlreadyUploaded(childPath)) {
ESP_LOGI(TAG_UPLOAD, "Found pending file: %s", childPath.c_str());
uploadFile(childPath);
delay(1000);
}
}
f.close();
Watchdog::feed();
if (WiFi.status() != WL_CONNECTED) {
dir.close();
return;
}
}
dir.close();
}
ESP_LOGI(TAG_UPLOAD, "Pending uploads check complete.");
}

View File

@@ -0,0 +1,81 @@
#include "Watchdog.h"
// Wykrywanie środowiska
#if defined(ESP_PLATFORM) || defined(ESP32)
#include <esp_task_wdt.h>
#define WDOG_HAS_ESP 1
#else
#define WDOG_HAS_ESP 0
#endif
namespace Watchdog {
static bool s_initialized = false;
bool init(int timeout_seconds, bool panic_on_trigger) {
#if WDOG_HAS_ESP
if (s_initialized) return true;
esp_err_t err = esp_task_wdt_init(timeout_seconds, panic_on_trigger);
if (err == ESP_OK || err == ESP_ERR_INVALID_STATE) {
// ESP_ERR_INVALID_STATE: już zainicjalizowany — traktujemy jako OK
s_initialized = true;
return true;
}
return false;
#else
(void)timeout_seconds; (void)panic_on_trigger;
s_initialized = true; // no-op, aby nie blokować wywołań w kodzie
return true;
#endif
}
bool addThisTask() {
#if WDOG_HAS_ESP
if (!s_initialized) return false;
esp_err_t err = esp_task_wdt_add(nullptr); // nullptr = bieżący task
return (err == ESP_OK || err == ESP_ERR_INVALID_STATE);
#else
return true; // no-op
#endif
}
bool removeThisTask() {
#if WDOG_HAS_ESP
if (!s_initialized) return false;
esp_err_t err = esp_task_wdt_delete(nullptr); // bieżący task
return (err == ESP_OK || err == ESP_ERR_INVALID_STATE);
#else
return true; // no-op
#endif
}
void feed() {
#if WDOG_HAS_ESP
esp_task_wdt_reset();
#else
// no-op
#endif
}
bool setTimeout(int timeout_seconds) {
#if WDOG_HAS_ESP
if (!s_initialized) {
// jeśli ktoś nie zainicjalizował — zrób to teraz
return init(timeout_seconds, true);
}
// W ESP-IDF/Arduino brak prostego API na „live update” — re-init:
esp_err_t err = esp_task_wdt_deinit();
(void)err; // nie każdy port raportuje OK/INVALID_STATE spójnie
s_initialized = false;
return init(timeout_seconds, true);
#else
(void)timeout_seconds;
return true; // no-op
#endif
}
bool isActive() {
return s_initialized;
}
} // namespace Watchdog

View File

@@ -0,0 +1,447 @@
#include <Logger.h>
#include "Watchdog.h"
#include <Arduino.h>
#include <Config.h>
#include <Pinout.h>
#include <Version.h>
#include <Display.h>
#include <SPI.h>
#include <SD.h>
#include "ADXL345FastSPI.h"
#include "RTClib.h"
#include <Wire.h>
#include <Network.h>
#include <Thread.h>
#include <Measure.h>
#include <Tool.h>
#include <Settings.h>
#include "APIClient.h"
#include "UploadManager.h"
#define WDT_TIMEOUT 60 // Czas watchdoga do restartu
#define MAX_ADXL345_SENSORS 4 // Maksymalna ilość podłączanych sensorów
SPIClass SPI_ADXL(FSPI); // SPI2 (VSPI)
SPIClass SPI_SD(HSPI); // SPI3 (HSPI)
float x, y, z = 0; // Dane odczytane z akcelerometru
String name;
bool isRebootRequired = false;
bool isAccelExists = false; // Czy istnieje jakiś podłączony do SPI czujnik
bool testingNow = false; // Czy trwa test (gdy tak, trzeba wszystko inne wyłączyć)
bool runMeasure = false; // czy włączyć pomiar?
// Buttony: 5, 6, 7
// piny SPI CS dla ASXL345
const uint8_t csPins[MAX_ADXL345_SENSORS] = {9, 10, 14, 21};
long licznik = 0;
ConfigManager configManager;
RTC_DS3231 rtc;
ADXL345FastSPI adxl(csPins, MAX_ADXL345_SENSORS);
Display display(rtc, 0x27, 20, 4);
Settings settings(display, rtc); // obsługa przycisków
WiFiManager wifi;
DataCapture capture(adxl, display, rtc, SD, 8192); // NEW !!! MEASURE!!!
APIClient apiClient;
UploadManager uploadManager(apiClient, rtc, capture);
Thread wifiTestThread = Thread(); // Cykliczny test WiFi
Thread offlineThread = Thread(); // Jeśli offline
Thread measureThread = Thread(); // Pomiar i zapis na SD
Thread uploadThread = Thread(); // Background upload
//////// PROTOTYPY /////////////////
void setup();
void loop();
void reboot();
void resetBtnClick();
void checkWiFi();
void showOfflineScreen();
void measure();
void setClockRTCBtn();
/* ******************* SETUP() ************************* */
void setup() {
Serial.begin(115200);
delay(500);
// przy USB-CDC warto poczekać chwilę na enumerację (z timeoutem):
unsigned long t0 = millis();
while (!Serial && millis()-t0 < 2000) { delay(10); }
settings.begin();
esp_log_level_set("*", ESP_LOG_INFO); // _ERROR, _WARN, _INFO, _DEBUG, _VERBOSE
delay(500); // było 1000
ESP_LOGI(TAG_MAIN, "----------------------");
ESP_LOGI(TAG_MAIN, "WMT Stalowa Wola A.Chmielowiec & L.Klich");
ESP_LOGI(TAG_MAIN, "Rejestrator parametrow");
ESP_LOGI(TAG_MAIN, "Firmware: %s", VERSION);
ESP_LOGI(TAG_MAIN, "----------------------");
ESP_LOGI(TAG_MAIN, "ESP32 model: %s Rev %d", ESP.getChipModel(), ESP.getChipRevision());
ESP_LOGI(TAG_MAIN, "Chip cores: %d", ESP.getChipCores());
// Inicjalizacja Watchdoga na 5 sek, panic_on_trigger = true
if (Watchdog::init(25, true)) {
ESP_LOGI(TAG_MAIN, "Watchdog init ok.");
} else {
ESP_LOGE(TAG_MAIN, "Watchdog init error.");
}
Wire.begin(PIN_SDA, PIN_SCL, 100000);
scanI2C(); // Skanowanie magistrali I2C na UART (RTC-0x68, LCD-0x27)
configManager.begin(); // konfiguracja EEPROM urządzenia
//configManager.showConfig();
// Test LCD I2C
if (!isI2CDevPresent(0x27)) {
ESP_LOGE(TAG_MAIN, "LCD 0x27 wire error!");
}
ESP_LOGI(TAG_MAIN, "Display init");
if (!display.begin()) {
ESP_LOGE(TAG_MAIN, "Display init failed");
while (true) delay(1000);
}
ESP_LOGI(TAG_MAIN, "Display OK");
display.welcomeScreen();
delay(1000);
// Przycisk reset w przypadku factory reset
if(settings.isBtnReset()) resetBtnClick();
// MCU Info
ESP_LOGI(TAG_MAIN, "MCU info");
display.textStatus(ESP.getChipModel());
delay(500);
char buf[20];
sprintf(buf, "Freq: %d MHz", ESP.getCpuFreqMHz());
display.textStatus(buf);
delay(500);
// Test PSRAM
display.textStatus("PSRAM:");
ESP_LOGI(TAG_MAIN, "PSRAM init");
if (!psramFound()) {
ESP_LOGE(TAG_MAIN, "PSRAM not found");
display.print("FAILED");
while (true) delay(1000);
}
display.print("OK");
delay(500);
// Test RTC
ESP_LOGI(TAG_MAIN, "RTC test");
if (!isI2CDevPresent(0x68)) {
ESP_LOGE(TAG_MAIN, "RTC 0x68 wire error!");
display.textStatus("RTC wire error!");
while (true) delay(1000);
}
display.textStatus("RTC:");
ESP_LOGI(TAG_MAIN, "RTC init");
if (!rtc.begin()) {
display.print("FAILED");
ESP_LOGE(TAG_MAIN, "Can't find RTC");
while (true) delay(1000);
} else {
display.print("OK");
ESP_LOGI(TAG_MAIN, "RTC OK");
if (rtc.lostPower()) {
ESP_LOGE(TAG_MAIN, "RTC power lost! Set clock");
display.textStatus("RTC: SET TIME");
delay(1000);
settings.setTimeRTC();
}
}
// Przycisk ustawianai czasu: przytrzymaj OK przy starcie systemu
if(settings.isSetClock()) setClockRTCBtn();
delay(500);
// Karta SD
ESP_LOGI(TAG_MAIN, "SD Card init");
ESP_LOGI(TAG_MAIN, "SPI2 SD SCK: %d, MISO: %d, MOSI: %d, CS: %d", SD_SCK, SD_MISO, SD_MOSI, SD_CS);
SPI_SD.begin(SD_SCK, SD_MISO, SD_MOSI);
display.textStatus("SD CARD:");
while(!SD.begin(SD_CS, SPI_SD, 4000000)) { // 10 MHz na start; w razie czego 4 MHz (bez tego często SD Failed)
ESP_LOGE(TAG_MAIN, "SD mount failed");
display.print("FAILED");
delay(4000);
display.textStatus("SD CARD:");
delay(500);
}
ESP_LOGI(TAG_MAIN, "SD Card OK");
display.print("OK");
ESP_LOGI(TAG_MAIN, "ADXL345 SPI3 SCK: %d, MISO: %d, MOSI: %d", CLK_ADSX, MISO_ADSX, MOSI_ADSX);
SPI_ADXL.begin(CLK_ADSX, MISO_ADSX, MOSI_ADSX);
// Inicjalizacja ADXL345
ESP_LOGI(TAG_MAIN, "ADXL345 init");
display.textStatus("ADXL345: ");
//if(!adxl.begin(&SPI_ADXL, 5000000, ADXL345FastSPI::RATE_3200HZ, ADXL345FastSPI::RANGE_16G, 1)){
if(!adxl.begin(&SPI_ADXL, 2000000, ADXL345FastSPI::RATE_3200HZ, ADXL345FastSPI::RANGE_16G, 1)){
ESP_LOGE(TAG_MAIN, "ADXL345 Error");
display.print("FAILED");
isAccelExists = false;
uint8_t counter = 0;
while(counter<5){ counter++; delay(1000); }
display.clear();
display.textCenter(0, "PLEASE CONNECT");
display.textCenter(1, "SENSOR MODULE");
display.textCenter(2, "ANY KEY RESTART");
display.textCenter(3, "GURU MEDITATION");
while(true){
if(digitalRead(BTN_UP) == LOW) reboot();
if(digitalRead(BTN_OK) == LOW) reboot();
if(digitalRead(BTN_DOWN) == LOW) reboot();
}
} else {
display.print(String(adxl.size()));
ESP_LOGI(TAG_MAIN, "ADXL345 OK: %d COUNT", adxl.size()); // liczba wykrytych
isAccelExists = true;
}
DateTime now = rtc.now();
ESP_LOGI(TAG_MAIN, "RTC TIME: %d:%d:%d %d:%d:%d", now.day(), now.month(), now.year(), now.hour(), now.minute(), now.second());
delay(1000);
if(config.connect){
wifi.begin();
wifiTestThread.onRun(checkWiFi);
wifiTestThread.setInterval(5000); // Test WiFi co 3 sekundy
} else {
offlineThread.onRun(showOfflineScreen);
offlineThread.setInterval(1000); // Jeśli offline, to wyświetl ekran co 1 sek
}
measureThread.onRun(measure);
measureThread.setInterval(config.pause); // Test co X sekund
if(config.connect){
uploadThread.onRun([]() { uploadManager.processPendingUploads(); });
uploadThread.setInterval(60000); // Co 1 minutę sprawdzaj zaległe
}
// Dodanie taska loop do WDT
if (Watchdog::addThisTask()) {
ESP_LOGI(TAG_MAIN, "Added main task to Watchdog");
}
ESP_LOGI(TAG_MAIN, "System ready");
display.clear();
}
// Factory reset
void setClockRTCBtn(){
ESP_LOGI(TAG_MAIN, "SET DATE TIME");
while(settings.isSetClock()){
display.clear();
display.textCenter(0, " PLEASE ");
display.textCenter(1, "release key");
display.textCenter(3, "TO SET DATE TIME");
delay(500);
}
settings.setTimeRTC();
}
// Factory reset
void resetBtnClick(){
uint8_t counter = 10; // ile sekund trzymać przycisk na factory reset
ESP_LOGI(TAG_MAIN, "RESET BTN to 10 sec.");
while(settings.isBtnReset()){
Serial.print(counter); Serial.print(",");
counter--;
delay(1000);
display.clear();
display.textCenter(0,"WARNING!!!");
display.textCenter(1, "Factory reset");
display.textCenter(2, "keep holding for");
display.textStatus(String(counter).c_str());
if (counter <= 1) {
display.clear();
display.textCenter(0, "RESET CONFIG");
display.textCenter(1, "release key");
ESP_LOGI(TAG_MAIN, "RESET CONFIG");
while(settings.isBtnReset()){;}
configManager.resetToDefaults();
display.textStatus("RESTARTING!");
delay(500);
isRebootRequired = true;
} else {
isRebootRequired = true;
}
}
}
///////////////////////////////////////////////////////////////////////////////////////////////////////////////
void showError(String err){
Watchdog::feed();
display.clear();
display.println(err);
ESP_LOGE(TAG_MAIN, "%s", err.c_str());
delay(3000);
}
void checkWiFi(){
Watchdog::feed();
bool isConnected = WiFi.isConnected();
String ip = WiFi.localIP().toString();
wifi.checkWiFiConnection();
display.updateBarWiFi(WiFi.RSSI(), isConnected);
display.updateNetwork(ip, isConnected);
}
////// Ekran główny tylko gdy brak pomiaru /////////////
void showOfflineScreen(){
if((!config.connect) && (!testingNow)) {
bool isGB;
Watchdog::feed();
float freeSpace = capture.freeSpaceFloat(&isGB);
display.displayOffline(testingNow, adxl.connectedSensorsCount(), capture.freeSpaceFloat(), licznik, false);
}
}
void reboot() {
display.clear();
display.textCenter(1, "SYSTEM");
display.textCenter(2, "RESTARTING");
#if defined(ARDUINO_RASPBERRY_PI_PICO)
watchdog_enable(1, 1); // 1 ms timeout, restart po upływie
while (true); // czekaj na watchdog reset
#endif
#if defined(ARDUINO_ARCH_ESP8266) || defined(ARDUINO_ARCH_ESP32)
ESP_LOGI(TAG_MAIN, "RESTART");
ESP.restart(); // restart ESP
#endif
#if defined(ARDUINO_ARCH_STM32)
NVIC_SystemReset(); // restartuj STM32
#endif
}
// pomiar z Thread START POMIARU TUTAJ
void measure(){
Watchdog::feed();
testingNow = true;
offlineThread.enabled = false;
DateTime now = rtc.now();
ESP_LOGI(TAG_MAIN, "RTC TIME: %d:%d:%d %d:%d:%d", now.day(), now.month(), now.year(), now.hour(), now.minute(), now.second());
char bdate[15]; char btime[15];
snprintf(bdate, sizeof(bdate), "%04d-%02d-%02d", now.year(), now.month(), now.day());
snprintf(btime, sizeof(btime), "%02d:%02d:%02d", now.hour(), now.minute(), now.second());
display.initMeasure(config.measure, testingNow, runMeasure, config.pause, config.duration, config.connect, licznik, bdate, btime);
ESP_LOGI(TAG_MAIN, "MEASURE RUNNING");
//delay(1000);
capture.captureAuto(config.duration, "/");
testingNow = false;
Watchdog::feed();
if(!capture.isExit){
capture.printLastFileInfoSerial();
capture.readHeaderAndPrint(capture.generateNextFilename().c_str());
ESP_LOGI(TAG_MAIN, "MEASURE FINISH");
} else {
ESP_LOGI(TAG_MAIN, "MEASURE INTERRUPT");
//runMeasure = false;
}
if (config.connect && WiFi.status() == WL_CONNECTED) {
ESP_LOGI(TAG_MAIN, "TRIGGER UPLOAD PENDING...");
uploadManager.processPendingUploads();
}
runMeasure = false;
ESP_LOGI(TAG_MAIN, "DISPLAY Offline");
display.displayOffline(testingNow, adxl.connectedSensorsCount(), capture.freeSpaceFloat(), licznik, true);
offlineThread.enabled = true;
}
void toogleMode(){ // tryb ciągłego, pojedynczego pomiaru
config.measure = !config.measure;
configManager.saveConfig();
display.textStatus("Mode changed");
delay(500);
display.textStatus("");
}
void settingsDevice(){
settings.setConfigDevice();
configManager.saveConfig();
settings.finishConfigDevice();
}
///////////// LOOP ////////////////////////////////////////////
void loop() {
if (settings.isPressed(2)){
Watchdog::feed();
if(runMeasure) {
capture.isExit = true;
runMeasure = false;
display.textStatus("Stopped");
delay(3000);
} else runMeasure = true;
if(runMeasure) ESP_LOGI(TAG_MAIN, "BTN MEASURE: START"); else ESP_LOGI(TAG_MAIN, "BTN MEASURE: STOP");
Watchdog::feed();
delay(300);
if (runMeasure) measureThread.run();
}
if(settings.isPressed(3)) settingsDevice(); // DOWN
if(settings.isPressed(1)) toogleMode(); // UP
if(testingNow) {
if((runMeasure) && (settings.isPressed(2))){
runMeasure = false;
display.textStatus("STOPPING. WAIT");
}
return; // jeśli trwa akurat test, nic nie rób
}
if(wifiTestThread.shouldRun() && (config.connect))
wifiTestThread.run();
if(uploadThread.shouldRun() && config.connect)
uploadThread.run();
if(offlineThread.shouldRun() && (!config.connect)){
offlineThread.run();
}
if(!isAccelExists) {
ESP_LOGE(TAG_MAIN, "ADXL module error:halt");
display.clear();
delay(500);
display.textCenter(0, "SENSORS");
display.textCenter(1, "NOT EXISTS");
display.textCenter(2, "SYSTEM STOPPED");
Watchdog::feed();
delay(2000);
} else {
if(measureThread.shouldRun() && (config.measure) && (!runMeasure)){
Watchdog::feed();
ESP_LOGI(TAG_MAIN, "Measure thread run");
measureThread.run();
}
}
if (isRebootRequired) {
ESP_LOGI(TAG_MAIN, "Reboot required");
Watchdog::feed();
delay(1000);
reboot();
}
Watchdog::feed();
delay(20); // 100
licznik ++;
}

Binary file not shown.

View File

@@ -0,0 +1,79 @@
#pragma once
#include <Arduino.h>
#include <SPI.h>
#include "ADXL345FreshSPI.h"
class ADXL345ArraySPI {
public:
static constexpr uint8_t MAX_SENSORS = 4;
explicit ADXL345ArraySPI(const uint8_t cs_pins[MAX_SENSORS]) {
for (uint8_t i=0;i<MAX_SENSORS;i++){ cs_[i]=cs_pins[i]; }
}
// Inicjalizacja wszystkich (SPI MODE3, STREAM FIFO, full-res, wybrany ODR/Range).
// Zwraca liczbę poprawnie wykrytych czujników.
uint8_t beginAll(SPIClass *spi, uint32_t spiHz, float odr_hz, ADXL345FreshSPI::Range range = ADXL345FreshSPI::Range::G16) {
presentMask_ = 0;
// Zbezpieczenie: inicjalizacja CS na HIGH przed startem.
for (uint8_t i=0;i<MAX_SENSORS;i++){
pinMode(cs_[i], OUTPUT);
digitalWrite(cs_[i], HIGH);
}
delayMicroseconds(5);
for (uint8_t i=0;i<MAX_SENSORS;i++){
if (!acc_[i].begin(spi, cs_[i], spiHz)) continue;
acc_[i].write8(0x2D, 0x00); // POWER_CTL = standby (MEASURE=0)
if (!acc_[i].setRange(range, true)) continue;
acc_[i].write8(0x2D, 0x08); // POWER_CTL = measure
if (!acc_[i].setODR_Hz(odr_hz)) continue;
// Tryb STREAM najbezpieczniejszy dla jitteru CPU
acc_[i].enableFIFO(ADXL345FreshSPI::FIFOmode::STREAM, 32);
presentMask_ |= (1u << i);
}
return countPresent();
}
inline uint8_t size() const { return MAX_SENSORS; }
inline bool isPresent(uint8_t i) const { return (presentMask_ & (1u << i)) != 0; }
inline uint8_t countPresent() const { return __builtin_popcount(presentMask_); }
// Sprawdza, czy KAŻDY obecny sensor ma >=1 nową próbkę (DATA_READY).
bool availableAll() {
for (uint8_t i=0;i<MAX_SENSORS;i++){
if (!isPresent(i)) continue;
if (!acc_[i].available()) return false;
}
return true;
}
// Zdejmuje po 1 NAJSTARSZEJ próbce z FIFO każdego obecnego sensora.
// Zwraca liczbę faktycznie zebranych próbek (== liczbie obecnych sensorów, jeśli sukces).
uint8_t readAlignedOnce(int16_t x[], int16_t y[], int16_t z[], uint32_t ts_us[]) {
uint8_t got = 0;
for (uint8_t i=0;i<MAX_SENSORS;i++){
if (!isPresent(i)) continue;
ADXL345FreshSPI::SampleI16 one[1];
size_t n = acc_[i].readFIFOBurst(one, 1); // NAJSTARSZA próbka z FIFO
if (n == 0) {
// sporadycznie: awaryjnie dociągnij świeżą (rzadkie)
ADXL345FreshSPI::SampleI16 s;
if (!acc_[i].readFresh(s, 1)) return got; // przerwij zbieranie ramki
one[0] = s;
}
x[i] = one[0].x; y[i] = one[0].y; z[i] = one[0].z; ts_us[i] = one[0].ts_us;
got++;
}
return got;
}
ADXL345FreshSPI& at(uint8_t i) { return acc_[i]; }
private:
uint8_t cs_[MAX_SENSORS]{};
ADXL345FreshSPI acc_[MAX_SENSORS];
uint32_t presentMask_ = 0;
};

View File

@@ -0,0 +1,91 @@
#pragma once
#include <Arduino.h>
#include <SPI.h>
#include "ADXL345FreshSPI.h"
#include <Logger.h>
class ADXL345FastSPI {
public:
enum Rate { RATE_100HZ, RATE_200HZ, RATE_400HZ, RATE_800HZ, RATE_1600HZ, RATE_3200HZ };
enum Range { RANGE_2G, RANGE_4G, RANGE_8G, RANGE_16G };
ADXL345FastSPI(const uint8_t* csPins, uint8_t count) : count_(count > MAX_NUM ? MAX_NUM : count){
for (uint8_t i = 0; i < count_; ++i) cs_[i] = csPins[i];
}
/* Pobierz konfigurację zakres akcelerometru */
uint8_t getRange(uint8_t accel);
/* Zwraca czy jest FUL_RES*/
bool getFullRes(uint8_t accel);
// Wersja podstawowa: podaj SPIClass* (np. &SPI)
bool begin(SPIClass *spi, uint32_t spiHz, Rate rate, Range range, uint8_t options = 0);
// Wersja wygodna: deleguje do powyższej, używając globalnego SPI
inline bool begin(uint32_t spiHz, Rate rate, Range range, uint8_t options = 0) {
return begin(&SPI, spiHz, rate, range, options);
}
// Informacje / dostępność
inline uint8_t size() const { return presentCnt_; }
inline bool isPresent(uint8_t i) const { return (i < count_) && present_[i]; }
bool availableAll();
// Wariant B: zdejmuje po 1 NAJSTARSZEJ próbce z FIFO każdego obecnego sensora
// Zwraca liczbę zebranych próbek (== size(), jeśli pełna ramka).
uint8_t readAlignedOnce(int16_t* x, int16_t* y, int16_t* z, uint32_t* ts_us);
// Zgodność: pojedynczy sensor 1 próbka
bool readNewSample(uint8_t idx, int16_t& x, int16_t& y, int16_t& z, bool& ready);
// Zwraca ilośc podłączonych czujników po inicjalizacji - ale nie jest dynamiczna!
inline uint8_t connectedSensorsCount() const { return presentCnt_; }
// dynamiczne odświeżenie liczby działających czujników zawsze sprawdzi na bieżąco i zwróci przy działającym urządzeniu
uint8_t refreshConnectedSensorsCount();
// Maska aktywnych sensorów wg bieżącego stanu (bit i = sensor i)
inline uint8_t activeMask() const {
uint8_t m = 0;
for (uint8_t i = 0; i < count_; ++i) if (present_[i]) m |= (1u << i);
return m;
}
// Odświeża stan (ping) i zwraca zaktualizowaną maskę aktywnych sensorów
uint8_t refreshActiveMask();
private:
static constexpr uint8_t MAX_NUM = 4; // MAX Ilość ADXL345
SPIClass* spi_ = &SPI;
ADXL345FreshSPI dev_[MAX_NUM];
uint8_t cs_[MAX_NUM]{};
bool present_[MAX_NUM]{};
uint8_t count_ = 0;
uint8_t presentCnt_ = 0;
uint32_t spiHz_ = 5000000;
float odrHz_ = 3200.0f;
static float mapRateToHz(Rate r) {
switch (r) {
case RATE_100HZ: return 100.0f;
case RATE_200HZ: return 200.0f;
case RATE_400HZ: return 400.0f;
case RATE_800HZ: return 800.0f;
case RATE_1600HZ: return 1600.0f;
case RATE_3200HZ: return 3200.0f;
default: return 3200.0f;
}
}
static ADXL345FreshSPI::Range mapRange(Range r) {
switch (r) {
case RANGE_2G: return ADXL345FreshSPI::Range::G2;
case RANGE_4G: return ADXL345FreshSPI::Range::G4;
case RANGE_8G: return ADXL345FreshSPI::Range::G8;
case RANGE_16G: return ADXL345FreshSPI::Range::G16;
default: return ADXL345FreshSPI::Range::G16;
}
}
};

View File

@@ -0,0 +1,87 @@
#pragma once
#include <Arduino.h>
#include <SPI.h>
#include "ADXL345Registers.h"
#include <Logger.h>
class ADXL345FreshSPI {
public:
enum class Range { G2=0, G4=1, G8=2, G16=3 };
enum class FIFOmode { BYPASS, FIFO, STREAM, TRIGGER };
struct SampleI16 {
int16_t x, y, z;
uint32_t ts_us;
};
struct SampleSI {
float ax_g, ay_g, az_g;
float ax_ms2, ay_ms2, az_ms2;
uint32_t ts_us;
};
ADXL345FreshSPI() = default;
// --- Init (SPI) ---
// Uwaga: ADXL345 wymaga SPI MODE3, zegar ≤ ~5 MHz.
bool begin(SPIClass *spi, uint8_t csPin, uint32_t clockHz = 5000000);
// --- Konfiguracja ---
bool setODR_Hz(float odr_hz);
bool setRange(Range r, bool fullRes=true);
bool enableFIFO(FIFOmode mode, uint8_t triggerLevel=16);
bool enableDataReadyInterrupt(bool enable=true); // przy pollingu pozostaw false
// --- Status ---
bool ping(); // DEVID == 0xE5 ?
bool available(); // DATA_READY z INT_SOURCE
// --- Odczyty „świeże” (blokujące do timeout_ms) ---
bool readFresh(SampleI16& out, uint32_t timeout_ms = 100);
bool readFresh(SampleSI& out, uint32_t timeout_ms = 100);
// --- Zrzut FIFO (do n elementów) ---
size_t readFIFOBurst(SampleI16* buf, size_t maxCount);
size_t readFIFOBurst(SampleSI* buf, size_t maxCount);
// --- Parametry pomocnicze ---
void setGConstant(float g = 9.80665f) { g_ms2 = g; }
float lsb_per_g() const { return 1.0f / scale_g_per_lsb; }
//bool write8(uint8_t reg, uint8_t val);
//bool read8(uint8_t reg, uint8_t& val);
/*
Zwraca tryb pracy akcelerometru
*/
uint8_t getADXLRange();
/* Zwraca czy jest FULL_RES w akcelerometrze */
bool getADXLFullRes();
void showRangeFull(String txt="");
// niskopoziomowe
bool write8(uint8_t reg, uint8_t val);
bool read8(uint8_t reg, uint8_t& val);
private:
SPIClass* spi = nullptr;
uint8_t cs = 255;
uint32_t spiHz = 5000000;
float scale_g_per_lsb = 0.0039f; // full-res ~3.9 mg/LSB
float g_ms2 = 9.80665f;
bool fullRes = true;
// niskopoziomowe
//bool write8(uint8_t reg, uint8_t val);
//bool read8(uint8_t reg, uint8_t& val);
bool readMulti(uint8_t reg, uint8_t* dst, size_t n);
void spiSelect();
void spiDeselect();
bool configurePowerMeasure();
uint8_t odrCodeFromHz(float hz);
void countsToSI(const SampleI16& in, SampleSI& out);
};

View File

@@ -0,0 +1,27 @@
#pragma once
#define ADXL345_REG_DEVID 0x00
#define ADXL345_REG_BW_RATE 0x2C
#define ADXL345_REG_POWER_CTL 0x2D
#define ADXL345_REG_INT_ENABLE 0x2E
#define ADXL345_REG_INT_MAP 0x2F
#define ADXL345_REG_INT_SOURCE 0x30
#define ADXL345_REG_DATA_FORMAT 0x31
#define ADXL345_REG_DATAX0 0x32
#define ADXL345_REG_DATAX1 0x33
#define ADXL345_REG_DATAY0 0x34
#define ADXL345_REG_DATAY1 0x35
#define ADXL345_REG_DATAZ0 0x36
#define ADXL345_REG_DATAZ1 0x37
#define ADXL345_REG_FIFO_CTL 0x38
#define ADXL345_REG_FIFO_STATUS 0x39
#define ADXL345_POWER_MEASURE 0x08
#define ADXL345_DATA_READY_BIT 0x80 // INT_SOURCE[7]
#define ADXL345_DATA_FORMAT_FULL_RES 0x08
#define ADXL345_DATA_FORMAT_RANGE_MASK 0x03
#define ADXL345_FIFO_BYPASS 0x00
#define ADXL345_FIFO_FIFO 0x40
#define ADXL345_FIFO_STREAM 0x80
#define ADXL345_FIFO_TRIGGER 0xC0

View File

@@ -0,0 +1,22 @@
#ifndef APICLIENT_H
#define APICLIENT_H
#include <Arduino.h>
#include <WiFiClient.h>
#include <HTTPClient.h>
#include <FS.h>
#include <SD.h>
#include "Config.h"
#include "Watchdog.h"
class APIClient {
public:
APIClient();
bool login();
bool uploadMeasurement(const String& filePath);
private:
String bearerToken;
};
#endif

View File

@@ -0,0 +1,71 @@
#ifndef CONFIG_H
#define CONFIG_H
#include <Logger.h>
#include <Arduino.h>
#include <EEPROM.h>
#define EEPROM_SIZE 1024
//constexpr size_t EEPROM_SIZE = sizeof(Config) + 32; // z lekkim zapasem
extern bool isRebootRequired;
extern bool isClearLog;
extern bool connected;
extern long countConnect;
extern long countDisconnect;
extern String act_rssi_percent;
extern int8_t rssi;
extern String actDate;
extern String actTime;
struct Config {
bool connect; // czy łączyć z Internetem?
bool measure; // true - pomiary ciągłe, false - nie rób nic (tryb konfiguracji)
char ip[16];
char subnet[16];
char gateway[16];
char dns[16];
char ssid[32];
char ntp[50];
char password[32];
char hostname[32];
char place[100]; // miejsce instalacji
bool dhcp; // czy włączyć DHCP?
char user[10]; // użytkownik konfiguracji
char pass[20]; // hasło użytkownika konfiguracji
char updateUrl[150]; // adres pliku aktualizacji
char restURL[150]; // adres Rest API
int restPort; // Port systemu Api na serwerze
char restUser[30]; // login RestAPI
char restPass[50]; // hasło RestAPi
uint8_t apiKey[32]; // Klucz API KEY
uint16_t pause; // Pomiar co sekund
uint8_t duration; // Czas pomiaru w sekundach 1-25
char S0[12]; // nazwy czujników 1-8
char S1[12];
char S2[12];
char S3[12];
char S4[12];
char S5[12];
char S6[12];
char S7[12];
};
// Global config declaration
extern Config config;
class ConfigManager {
public:
ConfigManager();
void begin(); // EEPROM initialization
void readConfig(); // Odczyt konfiguracji z EEPROM
void saveConfig(); // Zapis konfiguracji do EEPROM
void resetToDefaults(); // Reset do ustawień domyślnych
void showConfig();
void generateApiKey(uint8_t *buf, size_t len);
private:
bool isEEPROMEmpty();
};
#endif

View File

@@ -0,0 +1,93 @@
#ifndef DISPLAY_H
#define DISPLAY_H
#pragma once
#include "esp_log.h"
#include <Arduino.h>
#include <LiquidCrystal_I2C.h>
#include <Wire.h>
#include <Version.h>
#include <RTClib.h>
#include <Config.h>
#define SCREEN_WIDTH 40
#define SCREEN_HEIGHT 4
#define SCREEN_ADDRESS 0x27
class Display {
public:
Display(RTC_DS3231 &rtc,
uint8_t address = SCREEN_ADDRESS,
uint8_t columns = SCREEN_WIDTH,
uint8_t rows = SCREEN_HEIGHT);
// Zwraca true, jeśli urządzenie na I2C odpowiada
bool begin(TwoWire *wire = &Wire);
/* Ekran startowy urządzenia */
void welcomeScreen();
/* Czyście wiersz */
void clearRow(uint16_t line);
/* Wyświetla wyśrodkowany tekst w danym wierszu */
void textCenter(uint8_t line, const char *txt);
/* Wyświetla tekst w danym wierszu od lewej, bez centrowania */
void text(uint8_t line, const char *text);
/* Status - najniższy wiersz. Czyści przed zapisaniem */
void textStatus(const char *text);
void clear();
/* Ekran po uruchomieniu */
void mainScreen();
/* Ekran jeśli offline miga co 1 sek */
void displayOffline(bool measure, uint8_t count, float freeSpace, long licznik, bool refresh = false);
/* Podsumowanie po pomiarze: ramki, sampling, etc. */
void displaySampleRateSummary(uint32_t reccount, uint32_t captureSeconds, float khz, String filename);
void updateNetwork(String ip, bool connected);
void updateBarWiFi(long signal, bool connected);
void textStyle(const uint8_t st, String text);
void message(String text);
void print(String text);
void println(String text);
void setCursor(int16_t x, int16_t y);
void showAccel(float a, float b, float c);
void displayOnOffM(bool measure, String myDir, String myFile);
void initMeasure(
bool measure, // czy pomiar ciągły?
bool run, // czy uruchomiony?
bool runmes,
uint16_t pause, // Czas (s) pomiędzy próbkami
uint8_t duration, // Czas (s) trwania próbki
bool connect, // Czy połączenie Network
long counter, // główny licznik long
String gdate, // Data aktualna
String gtime); // Czas aktualny
private:
LiquidCrystal_I2C *_lcd;
RTC_DS3231 &rtc_;
uint8_t _columns;
uint8_t _rows;
uint8_t _address;
// Poprzednie
uint16_t oyear;
uint8_t omonth;
uint8_t oday;
uint8_t ohour;
uint8_t omin;
uint8_t osec;
float ospace;
uint8_t oadxlcnt;
bool omode;
};
#endif

View File

@@ -0,0 +1,15 @@
#ifndef LOGGER_H
#define LOGGER_H
#include "esp_log.h"
// Wszystkie tagi logowania w jednym miejscu
extern const char *TAG_MAIN;
extern const char *TAG_DISP;
extern const char *TAG_ADXL;
extern const char *TAG_CONF;
// Funkcja inicjalizacji poziomów logowania
void init_log_levels();
#endif

View File

@@ -0,0 +1,123 @@
#ifndef MEASURE_H
#define MEASURE_H
#include <Arduino.h>
#include <Watchdog.h>
#include <SPI.h>
#include <SD.h>
#include <FS.h>
#include <RTClib.h>
#include <Display.h>
#include "ADXL345FastSPI.h" // <-- zgodnie z main.cpp
#include <Logger.h>
#include <Pinout.h>
// Domyślne parametry akwizycji ADXL345 (mogą być nadpisane w ADXL345FastSPI::begin)
static constexpr uint32_t SPI_HZ = 5000000; // 5 MHz (MODE3)
static constexpr float ODR_HZ = 3200.0f; // maks. ODR
// Zakres ustawiany w main.cpp przez ADXL345FastSPI::begin(..., RANGE_2G, ...)
struct FileInfo {
String path; // np. "/3/00000057.wmt"
uint64_t size; // bajty
bool exists; // true, jeśli ostatni plik istnieje
};
struct SpaceInfo {
double value;
const char *unit; // "GB" | "MB" | "UNKNOWN"
};
class DataCapture {
// --- Nagłówek pliku WMT ---
struct FileHeader {
char magic[3]; // "WMT"
uint16_t version; // 1
uint16_t headerSize; // sizeof(FileHeader)
uint32_t sampleSize; // sizeof(Sample)
uint32_t timestamp; // UNIX startu akwizycji
uint32_t reccount; // liczba rekordów Sample w pliku
} __attribute__((packed));
public:
// --- Rekord próbki ---
struct Sample {
uint32_t offset; // µs od startu akwizycji (wspólny dla ramki)
uint8_t sensor_id; // 0..3
int16_t x, y, z; // surowe ADXL345
bool ready; // 1 = obecna
} __attribute__((packed));
// Konstruktor dopasowany do main.cpp przyjmuje ADXL345FastSPI
DataCapture(ADXL345FastSPI &adxl, Display &display, RTC_DS3231 &rtc, fs::FS &storage, size_t bufferSize = 131072 /* 128 KB */);
~DataCapture();
bool capture(uint32_t captureSeconds, const char *filename);
bool captureAuto(uint32_t captureSeconds, const char *baseDirectory = "/logs");
void printSamplingRate(uint32_t reccount, uint32_t captureSeconds, String filename);
void readHeaderAndPrint(const char *path);
void stop();
bool isActive() const { return measurementActive_; }
// SD utils
SpaceInfo freeSpaceMB();
float freeSpaceFloat(bool *isGB = nullptr);
String unixToDateTime(uint32_t ts);
// plik z najwyższym indeksem
FileInfo getLastFileInfo();
bool deleteAllOnSD();
bool isExit = false; // true oznacza przerwanie pomiaru
private:
ADXL345FastSPI &adxl_;
Display &display_;
RTC_DS3231 &rtc_;
fs::FS &_fs;
// Katalogowanie
const String _baseDir = "/";
const String _ext = ".wmt";
const uint8_t _digits = 8;
const uint16_t _maxFilesPerDir = 400;
// Bufor zapisu (PSRAM)
uint8_t *buffer_ = nullptr;
size_t bufferSize_ = 0;
size_t bufferIndex_ = 0;
bool measurementActive_ = false;
bool flushToFile(File &f);
static uint8_t crc8(const uint8_t *data, size_t len);
void setTestingIndicator_(bool on, String myDir, String myFile);
bool isEscape(){
bool ispress = (GPIO.in & (1UL << BTN_OK)) == 0;
if(ispress) {
isExit = true;
measurementActive_;
display_.textStatus("Cancelling. Wait!");
}
return ispress;
} // szybkidigitalRead : czy BTN stop???
public:
bool isAllDigits(const char *s);
uint32_t toUint(const char *s);
static const char *basenameFromPath(const char* full);
bool isWmtWithDigits(const char *name, uint32_t &idxOut) const;
String makeIndexedName(uint32_t idx) const;
static String joinPath(const String &a, const String &b);
bool ensureDir(uint32_t dirNum);
uint32_t findHighestNumericDir();
void scanDirForWmt(uint32_t dirNum, uint32_t &count, uint32_t &highestIdx);
bool recursiveDelete(const String &path);
String dirPath(uint32_t dirNum) const;
String allocateNextFilePath();
String generateNextFilename();
void printLastFileInfoSerial();
};
#endif // MEASURE_H

View File

@@ -0,0 +1,66 @@
#ifndef NETWORK_H
#define NETWORK_H
#include "esp_log.h"
#include <Arduino.h>
#include <Config.h>
#ifdef ESP32
#include <WiFi.h>
#include <WebServer.h>
#include <ESPmDNS.h>
#elif defined(ESP8266)
#include <ESP8266WiFi.h>
#include <ESP8266mDNS.h>
#endif
//#include <Pinout.h>
//#include <IPAddress.h>
//#include <WiFiUdp.h>
//#include <WiFiClientSecureBearSSL.h>
// OTA
#include <HTTPUpdate.h>
#include <WiFiClientSecure.h>
#include <functional>
// OTA
class WiFiManager {
public:
WiFiManager();
void begin();
void connectToWiFi();
void setupAccessPoint(const char *newSSID, const char *newPassword);
void checkWiFiConnection();
void updateLED();
void setupMDNS();
void ReadConnection();
bool isConnected(); // Zwraca stan połączenia
bool convertCharToIPAddress(const char *str, IPAddress& ip);
void ledBlink(); // Z innego projektu - niepotrzebne, bo nie ma LED WiFi...
bool isWiFiOK(); // True, gdy WiFi połączone i false, gdy brak połączenia
int rssiToPercent(int rssi); // rssi na procenty
int8_t getRSSI();
/**
* Aktualizacja systemu przez internet z adresu config.updateUrl.
* @param allowInsecureTLS true => dla https wyłącz weryfikację certyfikatu.
* @param progressCb callback progress (opcjonalnie) (bytes, total).
* @return true, jeśli update zakończony sukcesem (urządzenie się zrestartuje).
*/
bool performOTAUpdate(bool allowInsecureTLS = true, std::function<void(int,int)> progressCb = nullptr);
int8_t rssi = 0;
bool useDHCP = true;
IPAddress local_IP;
IPAddress gateway;
IPAddress subnet;
IPAddress dns;
String ssidAP = "ACCEL666";
String passwordAP = "12345678";
private:
bool isAccessPoint = false;
};
#endif // WIFIMANAGER_H

View File

@@ -0,0 +1,52 @@
#ifndef PINOUT_H
#define PINOUT_H
#if defined(ESP32)
// SPI3 (HSPI) - SD Card nie kolidują z PSRAM
#define SD_SCK 16 // 36 //18
#define SD_MOSI 17 // 35 //17
#define SD_MISO 18 // 37 //16
#define SD_CS 15 // 34 //15 ?? 34
// SPI2 (VSPI) - ADXL345
#define MOSI_ADSX 11 // SDA
#define CLK_ADSX 12 // SCL
#define MISO_ADSX 13 // SDO
//I2C C3
#define PIN_SDA 47 // szary
#define PIN_SCL 48 // niebieski
// Przycisk
#define BTN_UP 5
#define BTN_OK 6
#define BTN_DOWN 7
#endif
/*
CS {5, 6, 7, 10, 14, 21}
SPI2 (VSPI) — preferowane do ADXL345 (wysoka prędkość, stabilność). ASXL345
SPI3 (HSPI) — dowolne piny do SD (niższa prędkość, ale elastyczność). SD
Nie używać ESP32-S3:
integrated SPI flash: 26, 27, 28, 29, 30, 31, 32,
USB: 19, 20, 43, 44
PSRAM 35, 37
*/
#if defined(ARDUINO_RASPBERRY_PI_PICO)
#define CLK_ADSX 18
#define MOSI_ADSX 19
#define MISO_ADSX 16
#define SD_MISO 12
#define SD_CS 13
#define SD_SCK 14
#define SD_MOSI 15
#define I2C_SDA 20
#define I2C_SCL 21
#endif
#endif

View File

@@ -0,0 +1,52 @@
#ifndef SETTINGS_H
#define SETTINGS_H
#include <Arduino.h>
#include <Pinout.h>
#include <Watchdog.h>
#include <RTClib.h>
#include <Display.h>
#include <Logger.h>
#include <Config.h>
class Settings {
public:
Settings(Display &display, RTC_DS3231 &rtc);
~Settings();
void setTimeRTC();
void setConfigDevice();
void finishConfigDevice();
void begin();
bool isPressed(uint8_t btnIndex);
// bool is1and3();
bool isBtnReset();
bool isSetClock();
bool readBtnUp() { return digitalRead(BTN_UP) == LOW; }
bool readBtnOk() { return digitalRead(BTN_OK) == LOW; }
bool readBtnDown() { return digitalRead(BTN_DOWN) == LOW; }
// Struktura na datę i czas
struct RtcDateTime {
uint16_t year;
uint8_t month;
uint8_t day;
uint8_t hour;
uint8_t minute;
uint8_t second;
};
private:
Display &display_;
RTC_DS3231 &rtc_;
int editField(int value, int minVal, int maxVal, const char *label);
void constrainValue(int &value, int minVal, int maxVal);
void printField(const char *label, int value);
};
#endif

View File

@@ -0,0 +1,14 @@
#ifndef TOOL_H
#define TOOL_H
#include <Arduino.h>
#include <Wire.h>
#include "esp_log.h"
#include "Watchdog.h"
void scanI2C();
/* Funkcja przyjmuje adres jako argument i zwraca true gdy urządzenie odpowiada, w przeciwnym wypadku false */
bool isI2CDevPresent(uint8_t address);
#endif

View File

@@ -0,0 +1,31 @@
#ifndef UPLOADMANAGER_H
#define UPLOADMANAGER_H
#include <Arduino.h>
#include <FS.h>
#include <SD.h>
#include "APIClient.h"
#include "RTClib.h"
#include "Measure.h"
class UploadManager {
public:
UploadManager(APIClient& client, RTC_DS3231& rtc, DataCapture& capture);
// Call this to upload a specific file immediately
void uploadFile(const String& filePath);
// Call this in the background when WiFi is connected
void processPendingUploads();
private:
APIClient& apiClient;
RTC_DS3231& rtc_;
DataCapture& capture_;
bool isAlreadyUploaded(const String& filePath);
void appendLog(const String& filePath, const String& status);
String getCurrentTimestamp();
};
#endif

View File

@@ -0,0 +1,9 @@
#ifndef VERSION_H
#define VERSION_H
#define VERSION "1.4.0"
// 1: graphical 128x64, 2: LCD I2C Text 4x20
#define LCD_TYPE 2
#endif

View File

@@ -0,0 +1,38 @@
#pragma once
/**
* Watchdog — prosty interfejs do inicjalizacji i karmienia WDT z dowolnego modułu.
*
* Obsługiwane środowiska:
* - ESP32 Arduino Core / ESP-IDF (esp_task_wdt)
* - Fallback: no-op na innych platformach
*
* Użycie:
* Watchdog::init(5, true);
* Watchdog::addThisTask();
* ...
* Watchdog::feed();
*/
#include <stdint.h>
namespace Watchdog {
/** Inicjalizacja Task Watchdog (idempotentna). */
bool init(int timeout_seconds = 5, bool panic_on_trigger = true);
/** Dodaje bieżący task (wątki FreeRTOS: wołaj w ciele tego taska). */
bool addThisTask();
/** Usuwa bieżący task z nadzoru WDT. */
bool removeThisTask();
/** Karmi watchdog (reset licznika). */
void feed();
/** Zmienia timeout (wykonuje re-init wewnętrznie, jeśli trzeba). */
bool setTimeout(int timeout_seconds);
/** Czy watchdog jest aktywny (zainicjalizowany)? */
bool isActive();
} // namespace Watchdog

View File

@@ -0,0 +1,30 @@
; PlatformIO Project Configuration File
;
; Build options: build flags, source filter
; Upload options: custom upload port, speed and extra flags
; Library options: dependencies, extra library storages
; Advanced options: extra scripting
;
; Please visit documentation for the other options and examples
; https://docs.platformio.org/page/projectconf.html
[env:freenove_esp32_s3_wroom]
;platform = espressif32
platform = platformio/espressif32
board = freenove_esp32_s3_wroom
framework = arduino
monitor_speed = 115200
;monitor_port = /dev/cu.usbmodem5A671676821
;monitor_port = /dev/cu.usbmodem5A671676561
; jedyne źródło prawdy dla USB-CDC:
;board_build.arduino.usb_mode = cdc
;board_build.arduino.usb_cdc_on_boot = enable
build_flags =
-DBOARD_HAS_PSRAM
-DCORE_DEBUG_LEVEL=3
-DLOG_LOCAL_LEVEL=ESP_LOG_INFO ; potrzebne, by ESP_LOGx nie były wycięte
;-D ARDUINO_USB_CDC_ON_BOOT=1 ; UART print
;-D ARDUINO_USB_MODE=1 ; UART print
lib_deps =
adafruit/RTClib@^2.1.4
bblanchon/ArduinoJson@^7.0.4

View File

@@ -0,0 +1,140 @@
#include "ADXL345FastSPI.h"
static const char *TAG_FRESH = "ADXLFAST";
bool ADXL345FastSPI::begin(SPIClass *spi, uint32_t spiHz, Rate rate, Range range, uint8_t /*options*/){
spi_ = spi;
spiHz_ = spiHz;
odrHz_ = mapRateToHz(rate);
auto r = mapRange(range);
//spi_->begin(); 28.01.2026
presentCnt_ = 0;
for (uint8_t i = 0; i < count_; ++i) {
bool ok = dev_[i].begin(spi_, cs_[i], spiHz_)
&& dev_[i].setRange(r, true)
&& dev_[i].setODR_Hz(odrHz_);
dev_[i].showRangeFull(String(i));
if (ok) {
dev_[i].enableFIFO(ADXL345FreshSPI::FIFOmode::STREAM, 32); // wariant B
present_[i] = true;
presentCnt_++;
} else {
present_[i] = false;
}
}
return presentCnt_ > 0;
}
bool ADXL345FastSPI::availableAll() {
for (uint8_t i = 0; i < count_; ++i) {
if (!present_[i]) continue;
// else {
// dev_[i].showRangeFull("AVAILABLE ADXL");
// }
if (!dev_[i].available()) return false;
}
return true;
}
uint8_t ADXL345FastSPI::readAlignedOnce(int16_t* x, int16_t* y, int16_t* z, uint32_t* ts_us){
// 1) Czekaj aż każdy ma ≥1 próbkę (DATA_READY => FIFO>0)
while (!availableAll()) {
// micro-spin; w razie potrzeby można dodać yield() / feed watchdog poza hot-path
}
// 2) Zdejmij najstarszą z każdego sensora
uint8_t got = 0;
for (uint8_t i = 0; i < count_; ++i) {
if (!present_[i]) continue;
ADXL345FreshSPI::SampleI16 one[1];
size_t n = dev_[i].readFIFOBurst(one, 1);
if (n == 0) {
ADXL345FreshSPI::SampleI16 s;
if (!dev_[i].readFresh(s, 1)) break;
one[0] = s;
}
if (x) x[i] = one[0].x;
if (y) y[i] = one[0].y;
if (z) z[i] = one[0].z;
if (ts_us) ts_us[i] = one[0].ts_us;
got++;
}
return got;
}
bool ADXL345FastSPI::readNewSample(uint8_t idx, int16_t& x, int16_t& y, int16_t& z, bool& ready)
{
if (idx >= count_ || !present_[idx]) { ready = false; return false; }
ADXL345FreshSPI::SampleI16 one[1];
size_t n = dev_[idx].readFIFOBurst(one, 1);
if (n == 0) {
ADXL345FreshSPI::SampleI16 s;
if (!dev_[idx].readFresh(s, 1)) { ready = false; return false; }
one[0] = s;
}
x = one[0].x; y = one[0].y; z = one[0].z;
ready = true;
return true;
}
uint8_t ADXL345FastSPI::refreshConnectedSensorsCount(){
uint8_t cnt = 0;
for (uint8_t i = 0; i < count_; ++i) {
// ping() czyta DEVID (0xE5) wewnętrznie i zwraca true/false
if (dev_[i].ping()) {
present_[i] = true;
cnt++;
} else {
present_[i] = false;
}
}
presentCnt_ = cnt;
return cnt;
}
/*
Zwraca maskę bitową aktywnych sensorów (np. do szybkiej diagnostyki: który CS nie odpowiada)
Uzycie:
uint8_t mask = adxl.refreshActiveMask();
ESP_LOGI(TAG_MAIN, "Aktywne sensory (bitmask): 0x%02X, count=%u",
mask, adxl.connectedSensorsCount());
// Sprawdzenie konkretnego sensora:
if (mask & (1u << 3)) {
ESP_LOGI(TAG_MAIN, "Sensor #3 online");
} else {
ESP_LOGW(TAG_MAIN, "Sensor #3 offline");
}
*/
uint8_t ADXL345FastSPI::refreshActiveMask(){
uint8_t m = 0;
uint8_t cnt = 0;
for (uint8_t i = 0; i < count_; ++i) {
if (dev_[i].ping()) {
present_[i] = true;
m |= (1u << i);
cnt++;
} else {
present_[i] = false;
}
}
presentCnt_ = cnt;
return m;
}
// Pobiera zakres wybranego akcelerometru
uint8_t ADXL345FastSPI::getRange(uint8_t accel){
return dev_[accel].getADXLRange();
}
bool ADXL345FastSPI::getFullRes(uint8_t accel){
return dev_[accel].getADXLFullRes();
}

View File

@@ -0,0 +1,242 @@
#include "ADXL345FreshSPI.h"
static const char *TAG_FRESH = "ADXLFRESH";
static inline int16_t u8pair_to_i16(uint8_t lo, uint8_t hi) {
return (int16_t)((hi << 8) | lo);
}
bool ADXL345FreshSPI::begin(SPIClass* s, uint8_t csPin, uint32_t clockHz) {
spi = s; cs = csPin; spiHz = clockHz;
pinMode(cs, OUTPUT); digitalWrite(cs, HIGH);
//spi->begin(); // tymczas
delay(1);
if (!ping()) return false;
// 1. Wymuś STANDBY przed jakąkolwiek konfiguracją
write8(ADXL345_REG_POWER_CTL, 0x00);
delay(1);
// 2. Skonfiguruj format danych (Range i Full_Res)
if (!setRange(Range::G16, true)) { // było if (!setRange(Range::G2, true)) return false;
ESP_LOGI(TAG_FRESH, "Range G16 ERROR!");
return false;
}
// 3. Skonfiguruj ODR
if (!setODR_Hz(100.0f)) return false; // domyślnie 100 Hz
// 4. Dopiero teraz włącz pomiar
if (!configurePowerMeasure()) return false;
showRangeFull();
return true;
}
bool ADXL345FreshSPI::ping() {
uint8_t id=0; if (!read8(ADXL345_REG_DEVID, id)) return false;
return id == 0xE5;
}
bool ADXL345FreshSPI::configurePowerMeasure() {
return write8(ADXL345_REG_POWER_CTL, ADXL345_POWER_MEASURE);
}
bool ADXL345FreshSPI::setRange(Range r, bool fullRes_) {
fullRes = fullRes_;
// Wymuś STANDBY (MEASURE=0) kluczowe dla zmiany RANGE/FULL_RES
write8(ADXL345_REG_POWER_CTL, 0x00);
delayMicroseconds(5);
uint8_t fmt = 0;
if (!read8(ADXL345_REG_DATA_FORMAT, fmt)) return false;
fmt &= ~ADXL345_DATA_FORMAT_RANGE_MASK; // bity 1:0
fmt |= (uint8_t)r;
if (fullRes) fmt |= ADXL345_DATA_FORMAT_FULL_RES; // bit 3
else fmt &= ~ADXL345_DATA_FORMAT_FULL_RES;
if (!write8(ADXL345_REG_DATA_FORMAT, fmt)) return false;
// Kontrola: odczyt po zapisie
//uint8_t verify = 0;
//read8(ADXL345_REG_DATA_FORMAT, verify);
// tu możesz logować verify
// Wróć do MEASURE
write8(ADXL345_REG_POWER_CTL, ADXL345_POWER_MEASURE);
scale_g_per_lsb = fullRes ? 0.0039f : (1.0f/256.0f) * (2 << (uint8_t)r);
return true;
}
uint8_t ADXL345FreshSPI::odrCodeFromHz(float hz) {
struct { uint8_t code; float f; } map[] = {
{0x06, 6.25f},{0x07,12.5f},{0x08,25.f},{0x09,50.f},{0x0A,100.f},
{0x0B,200.f},{0x0C,400.f},{0x0D,800.f},{0x0E,1600.f},{0x0F,3200.f}
};
uint8_t best=0x0A; float bestErr=1e9f;
for (auto &e: map){ float err=fabsf(e.f-hz); if (err<bestErr){bestErr=err; best=e.code;}}
return best;
}
bool ADXL345FreshSPI::setODR_Hz(float hz) {
return write8(ADXL345_REG_BW_RATE, odrCodeFromHz(hz));
}
bool ADXL345FreshSPI::enableFIFO(FIFOmode mode, uint8_t triggerLevel) {
triggerLevel = constrain(triggerLevel, (uint8_t)1, (uint8_t)32);
uint8_t m = ADXL345_FIFO_BYPASS;
switch(mode){
case FIFOmode::BYPASS: m = ADXL345_FIFO_BYPASS; break;
case FIFOmode::FIFO: m = ADXL345_FIFO_FIFO; break;
case FIFOmode::STREAM: m = ADXL345_FIFO_STREAM; break;
case FIFOmode::TRIGGER: m = ADXL345_FIFO_TRIGGER;break;
}
return write8(ADXL345_REG_FIFO_CTL, (uint8_t)(m | ((triggerLevel-1) & 0x1F)));
}
bool ADXL345FreshSPI::enableDataReadyInterrupt(bool enable) {
uint8_t ie=0; if (!read8(ADXL345_REG_INT_ENABLE, ie)) return false;
if (enable) ie |= ADXL345_DATA_READY_BIT;
else ie &= ~ADXL345_DATA_READY_BIT;
return write8(ADXL345_REG_INT_ENABLE, ie);
}
bool ADXL345FreshSPI::available() {
uint8_t src=0; if (!read8(ADXL345_REG_INT_SOURCE, src)) return false;
return (src & ADXL345_DATA_READY_BIT) != 0;
}
bool ADXL345FreshSPI::readFresh(SampleI16& out, uint32_t timeout_ms) {
uint32_t start = millis();
while (!available()) {
if ((millis() - start) > timeout_ms) return false;
delayMicroseconds(200);
}
uint8_t buf[6];
if (!readMulti(ADXL345_REG_DATAX0, buf, 6)) return false;
out.x = u8pair_to_i16(buf[0], buf[1]);
out.y = u8pair_to_i16(buf[2], buf[3]);
out.z = u8pair_to_i16(buf[4], buf[5]);
out.ts_us = micros();
return true;
}
bool ADXL345FreshSPI::readFresh(SampleSI& out, uint32_t timeout_ms) {
SampleI16 raw;
if (!readFresh(raw, timeout_ms)) return false;
countsToSI(raw, out);
return true;
}
size_t ADXL345FreshSPI::readFIFOBurst(SampleI16* buf, size_t maxCount) {
if (!buf || maxCount==0) return 0;
uint8_t status=0; if (!read8(ADXL345_REG_FIFO_STATUS, status)) return 0;
uint8_t entries = status & 0x3F; // 0..32
size_t n = min<size_t>(entries, maxCount);
for (size_t i=0;i<n;i++){
uint8_t d[6];
if (!readMulti(ADXL345_REG_DATAX0, d, 6)) return i;
buf[i].x = u8pair_to_i16(d[0], d[1]);
buf[i].y = u8pair_to_i16(d[2], d[3]);
buf[i].z = u8pair_to_i16(d[4], d[5]);
buf[i].ts_us = micros();
}
return n;
}
size_t ADXL345FreshSPI::readFIFOBurst(SampleSI* buf, size_t maxCount) {
if (!buf || maxCount==0) return 0;
size_t n = 0;
while (n < maxCount) {
uint8_t status=0; if (!read8(ADXL345_REG_FIFO_STATUS, status)) break;
uint8_t entries = status & 0x3F;
if (entries == 0) break;
uint8_t d[6];
uint32_t t = micros();
if (!readMulti(ADXL345_REG_DATAX0, d, 6)) break;
SampleI16 s;
s.x = u8pair_to_i16(d[0], d[1]);
s.y = u8pair_to_i16(d[2], d[3]);
s.z = u8pair_to_i16(d[4], d[5]);
s.ts_us = t;
countsToSI(s, buf[n]);
n++;
}
return n;
}
void ADXL345FreshSPI::countsToSI(const SampleI16& in, SampleSI& out) {
out.ax_g = in.x * scale_g_per_lsb;
out.ay_g = in.y * scale_g_per_lsb;
out.az_g = in.z * scale_g_per_lsb;
out.ax_ms2 = out.ax_g * g_ms2;
out.ay_ms2 = out.ay_g * g_ms2;
out.az_ms2 = out.az_g * g_ms2;
out.ts_us = in.ts_us;
}
// ---------- Low-level SPI -----------
bool ADXL345FreshSPI::write8(uint8_t reg, uint8_t val) {
spi->beginTransaction(SPISettings(spiHz, MSBFIRST, SPI_MODE3));
spiSelect();
spi->transfer(reg & 0x3F); // write, single
spi->transfer(val);
spiDeselect();
spi->endTransaction();
return true;
}
bool ADXL345FreshSPI::read8(uint8_t reg, uint8_t& val) {
spi->beginTransaction(SPISettings(spiHz, MSBFIRST, SPI_MODE3));
spiSelect();
spi->transfer(0x80 | (reg & 0x3F)); // read, single
val = spi->transfer(0x00);
spiDeselect();
spi->endTransaction();
return true;
}
bool ADXL345FreshSPI::readMulti(uint8_t reg, uint8_t *dst, size_t n) {
if (n==0) return true;
spi->beginTransaction(SPISettings(spiHz, MSBFIRST, SPI_MODE3));
spiSelect();
spi->transfer(0xC0 | (reg & 0x3F)); // read, multi (MB=1, R/W=1)
for (size_t i=0;i<n;i++) dst[i] = spi->transfer(0x00);
spiDeselect();
spi->endTransaction();
return true;
}
void ADXL345FreshSPI::spiSelect() { digitalWrite(cs, LOW); }
void ADXL345FreshSPI::spiDeselect() { digitalWrite(cs, HIGH); }
uint8_t ADXL345FreshSPI::getADXLRange() {
uint8_t format = 0;
if (!read8(0x31, format)) return 255; // albo 0
switch (format & 0x03) {
case 0: return 2;
case 1: return 4;
case 2: return 8;
case 3: return 16;
}
return 255;
}
bool ADXL345FreshSPI::getADXLFullRes() {
uint8_t format = 0;
if (!read8(0x31, format)) return false;
//bool ok = read8(0x31, format);
//ESP_LOGI(TAG_FRESH, "read8 ok=%d DATA_FORMAT=0x%02X", ok, format);
return (format & 0x08) != 0;
}
void ADXL345FreshSPI::showRangeFull(String txt) {
uint8_t format = getADXLRange();
bool full_res = getADXLFullRes();
ESP_LOGI(TAG_FRESH, "%s ADXL345: RANGE=%dG FULL_RES=%d", txt, format, full_res);
}

View File

@@ -0,0 +1,146 @@
#include "APIClient.h"
#include <ArduinoJson.h>
static const char* TAG_API = "API";
APIClient::APIClient() {
}
bool APIClient::login() {
if (WiFi.status() != WL_CONNECTED) return false;
HTTPClient http;
String baseUrl = String(config.restURL);
int port = config.restPort;
if (baseUrl.indexOf("/accels/api1") >= 0) {
baseUrl = "http://62.93.60.19";
port = 5004;
}
String url = baseUrl + ":" + String(port) + "/api/v1/auth/token";
ESP_LOGI(TAG_API, "Login to %s", url.c_str());
http.begin(url);
http.addHeader("Content-Type", "application/x-www-form-urlencoded");
String payload = "grant_type=password&username=" + String(config.restUser) + "&password=" + String(config.restPass);
int httpCode = http.POST(payload);
if (httpCode == 200) {
String response = http.getString();
JsonDocument doc;
DeserializationError error = deserializeJson(doc, response);
if (!error && doc["access_token"].is<String>()) {
bearerToken = doc["access_token"].as<String>();
ESP_LOGI(TAG_API, "Token retrieved successfully.");
http.end();
return true;
}
} else {
ESP_LOGE(TAG_API, "Login failed: %d - %s", httpCode, http.getString().c_str());
}
http.end();
return false;
}
bool APIClient::uploadMeasurement(const String& filePath) {
if (WiFi.status() != WL_CONNECTED) {
ESP_LOGE(TAG_API, "No WiFi connection.");
return false;
}
if (bearerToken.isEmpty()) {
if (!login()) return false;
}
File file = SD.open(filePath, FILE_READ);
if (!file) {
ESP_LOGE(TAG_API, "Failed to open file %s", filePath.c_str());
return false;
}
String filename = filePath;
int slashIndex = filename.lastIndexOf('/');
if (slashIndex >= 0) {
filename = filename.substring(slashIndex + 1);
}
String boundary = "----WebKitFormBoundaryESP32WMT";
String head = "--" + boundary + "\r\n"
+ "Content-Disposition: form-data; name=\"file\"; filename=\"" + filename + "\"\r\n"
+ "Content-Type: application/octet-stream\r\n\r\n";
String tail = "\r\n--" + boundary + "--\r\n";
size_t fileSize = file.size();
size_t totalLength = head.length() + fileSize + tail.length();
WiFiClient client;
String host = String(config.restURL);
int port = config.restPort;
if (host.indexOf("/accels/api1") >= 0) {
host = "http://62.93.60.19";
port = 5004;
}
host.replace("http://", "");
host.replace("https://", "");
ESP_LOGI(TAG_API, "Connecting to %s:%d for upload", host.c_str(), port);
if (!client.connect(host.c_str(), port)) {
ESP_LOGE(TAG_API, "Connection failed to host %s:%d", host.c_str(), port);
file.close();
return false;
}
ESP_LOGI(TAG_API, "Uploading %s (%d bytes)", filePath.c_str(), fileSize);
client.print(String("POST /api/v1/measurements/measurements/upload HTTP/1.1\r\n"));
client.print(String("Host: ") + host + "\r\n");
client.print(String("Authorization: Bearer ") + bearerToken + "\r\n");
client.print(String("Content-Length: ") + String(totalLength) + "\r\n");
client.print(String("Content-Type: multipart/form-data; boundary=") + boundary + "\r\n");
client.print(String("Connection: close\r\n\r\n"));
client.print(head);
uint8_t buffer[2048];
while (file.available()) {
size_t len = file.read(buffer, sizeof(buffer));
client.write(buffer, len);
Watchdog::feed();
}
client.print(tail);
file.close();
int httpCode = 0;
String responseLine;
unsigned long timeout = millis();
while (client.connected() && millis() - timeout < 15000) {
if (client.available()) {
responseLine = client.readStringUntil('\n');
responseLine.trim();
if (responseLine.startsWith("HTTP/1.1 ")) {
httpCode = responseLine.substring(9, 12).toInt();
}
if (responseLine.length() == 0) break;
}
Watchdog::feed();
}
String responseBody = "";
while(client.available()){
responseBody += client.readString();
}
client.stop();
if (httpCode == 201) {
ESP_LOGI(TAG_API, "Upload successful: 201");
return true;
} else if (httpCode == 401) {
ESP_LOGW(TAG_API, "Token expired or invalid, clearing token.");
bearerToken = "";
} else {
ESP_LOGE(TAG_API, "Upload failed with code %d. Response: %s", httpCode, responseBody.c_str());
}
return false;
}

View File

@@ -0,0 +1,184 @@
#include <Config.h>
Config config;
ConfigManager::ConfigManager() {}
// EEPROM initialize
void ConfigManager::begin() {
ESP_LOGI(TAG_CONF, "Begin config");
EEPROM.begin(EEPROM_SIZE);
if (isEEPROMEmpty()) {
ESP_LOGI(TAG_CONF, "EEPROM Empty");
resetToDefaults();
generateApiKey(config.apiKey, sizeof(config.apiKey));
saveConfig();
readConfig();
} else {
//Logger::getInstance().log(LOG_INFO, "READ Config");
readConfig();
}
}
// Check is EEPROM is empty
bool ConfigManager::isEEPROMEmpty() {
if (EEPROM.read(0) != 251){
ESP_LOGI(TAG_CONF, "EEPROM is new!");
return true;
} else {
return false;
}
}
// Read configuration from EEPROM
void ConfigManager::readConfig() {
ESP_LOGI(TAG_CONF, "Read config from EEPROM");
EEPROM.get(1, config);
}
// Save config to EEPROM
void ConfigManager::saveConfig() {
ESP_LOGI(TAG_CONF, "SAVE CONFIG");
EEPROM.put(1, config);
EEPROM.write(0, 251);
if (EEPROM.commit()) {
ESP_LOGI(TAG_CONF, "Config saved");
} else {
ESP_LOGE(TAG_CONF, "Error save config");
}
EEPROM.end();
}
void ConfigManager::generateApiKey(uint8_t *buf, size_t len) {
for (size_t i = 0; i < len; i += 4) {
uint32_t r = esp_random(); // losowe 32 bity z TRNG ESP32
size_t chunk = (len - i >= 4) ? 4 : (len - i); // ostatnia iteracja może być < 4 bajtów
memcpy(buf + i, &r, chunk);
}
}
// Factory reset EEPROM
void ConfigManager::resetToDefaults() {
ESP_LOGI(TAG_CONF, "EEPROM RESET FACTORY");
EEPROM.begin(EEPROM_SIZE);
for (int i = 0; i < EEPROM_SIZE; i++) {
EEPROM.write(i, 0);
}
EEPROM.write(0, 0);
strcpy(config.ssid, "politechnika");
strcpy(config.password, "");
strcpy(config.hostname, "WMT001");
strcpy(config.place, "WMT Stalowa Wola");
config.dhcp = 1;
strcpy(config.ip, "192.168.0.10");
strcpy(config.subnet, "255.255.255.0");
strcpy(config.gateway, "192.168.0.1");
strcpy(config.dns, "8.8.8.8");
strcpy(config.user, "admin");
strcpy(config.pass, "admin");
strcpy(config.ntp, "pl.pool.ntp.org");
config.connect = 0; // urządzenie połączone z siecią lub 0 offline
config.measure = 1; // włącz automatyczny pomiar co x sekunt (pause)
config.duration = 5; // czas trwania pomiaru 5 sekund
config.pause = 10000; // odstęp pomiędzy pomiarami w ms
//strcpy(config.ntp, "0.pl.pool.ntp.org");
strcpy(config.restURL, "http://62.93.60.19");
config.restPort = 5004;
strcpy(config.restUser, "wmt");
strcpy(config.restPass, "Zaq12wsx");
strcpy(config.S0, "ACCEL1");
strcpy(config.S1, "ACCEL2");
strcpy(config.S2, "ACCEL3");
strcpy(config.S3, "ACCEL4");
strcpy(config.S4, "ACCEL5");
strcpy(config.S5, "ACCEL6");
strcpy(config.S6, "ACCEL7");
strcpy(config.S7, "ACCEL8");
EEPROM.put(1, config);
EEPROM.write(0, 251);
saveConfig();
readConfig();
isRebootRequired = true;
}
void ConfigManager::showConfig(){
ESP_LOGI(TAG_CONF, "Config size: %d bytes max %d", sizeof(config), EEPROM_SIZE);
String ii;
if(config.connect)
ii = "online";
else
ii = "offline";
ESP_LOGI(TAG_CONF, "Mode: %s", ii.c_str());
if(config.measure)
ii = "auto";
else
ii = "manual";
ESP_LOGI(TAG_CONF, "MEASURE: %s", ii.c_str());
ii = "PLACE: " + String(config.place);
ESP_LOGI(TAG_CONF, "%s", ii.c_str());
ii = "HOSTNAME: " + String(config.hostname);
ESP_LOGI(TAG_CONF, "%s", ii.c_str());
ii = "WIFI SSID: " + String(config.ssid);
ESP_LOGI(TAG_CONF, "%s", ii.c_str());
ii = "WIFI PASS: " + String(config.password);
ESP_LOGI(TAG_CONF, "%s", ii.c_str());
if(config.dhcp)
ii = "yes";
else
ii = "no";
ESP_LOGI(TAG_CONF, "DHCP: %s", ii.c_str());
ii = "IP: " + String(config.ip);
ESP_LOGI(TAG_CONF, "%s", ii.c_str());
ii = "MASK: " + String(config.subnet);
ESP_LOGI(TAG_CONF, "%s", ii.c_str());
ii = "GATEWAY: " + String(config.gateway);
ESP_LOGI(TAG_CONF, "%s", ii.c_str());
ii = "DNS: " + String(config.dns);
ESP_LOGI(TAG_CONF, "%s", ii.c_str());
ii = "USER: " + String(config.user);
ESP_LOGI(TAG_CONF, "%s", ii.c_str());
ii = "USER PASS: " + String(config.pass);
ESP_LOGI(TAG_CONF, "%s", ii.c_str());
if(config.connect) {
ii = "URL: " + String(config.restURL);
ESP_LOGI(TAG_CONF, "%s", ii.c_str());
ii = "PORT: " + String(config.restPort);
ESP_LOGI(TAG_CONF, "%s", ii.c_str());
ii = "USER: " + String(config.restUser);
ESP_LOGI(TAG_CONF, "%s", ii.c_str());
ii = "PASS: " + String(config.restPass);
ESP_LOGI(TAG_CONF, "%s", ii.c_str());
}
for (size_t i = 0; i < 32; i++) {
if (config.apiKey[i] < 0x10) Serial.print("0");
Serial.print(config.apiKey[i], HEX);
}
Serial.println();
ii = "Delay: " + String(config.pause) + "ms";
ESP_LOGI(TAG_CONF, "%s", ii.c_str());
ii = "S1: " + String(config.S0);
ESP_LOGI(TAG_CONF, "%s", ii.c_str());
ii = "S2: " + String(config.S1);
ESP_LOGI(TAG_CONF, "%s", ii.c_str());
ii = "S3: " + String(config.S2);
ESP_LOGI(TAG_CONF, "%s", ii.c_str());
ii = "S4: " + String(config.S3);
ESP_LOGI(TAG_CONF, "%s", ii.c_str());
ii = "S5: " + String(config.S4);
ESP_LOGI(TAG_CONF, "%s", ii.c_str());
ii = "S6: " + String(config.S5);
ESP_LOGI(TAG_CONF, "%s", ii.c_str());
ii = "S7: " + String(config.S6);
ESP_LOGI(TAG_CONF, "%s", ii.c_str());
ii = "S8: " + String(config.S7);
ESP_LOGI(TAG_CONF, "%s", ii.c_str());
}

View File

@@ -0,0 +1,279 @@
#include <Arduino.h>
#include "Display.h"
static const char *DISP = "display";
Display::Display(RTC_DS3231 &rtc, uint8_t address, uint8_t columns, uint8_t rows):rtc_(rtc) {
_lcd = new LiquidCrystal_I2C(address, columns, rows);
_address = address;
_columns = columns;
_rows = rows;
}
bool Display::begin(TwoWire *wire) {
wire->begin();
wire->beginTransmission(_address);
if (wire->endTransmission() != 0) {
return false; // brak odpowiedzi — error
}
// Inicjalizacja LCD
_lcd->begin(_columns, _rows);
_lcd->backlight();
_lcd->clear();
delay(100);
return true;
}
void Display::initMeasure(bool measure, bool run, bool runmes, uint16_t pause, uint8_t duration,
bool connect, long counter, String gdate, String gtime) {
clear();
textCenter(0, "** Measure params **");
String conting = measure ? "yes":"no";
String st2 = "Continous:";
String allt = st2 + conting;
textCenter(1, allt.c_str());
String type = "Sample:" + String(duration) + "s Pause:" + String(pause/1000)+"s";
textCenter(2, type.c_str());
textCenter(3, String("Time:" + gtime).c_str());
textStatus("initialisation");
}
void Display::clear() {
_lcd->clear();
}
void Display::textCenter(uint8_t line, const char *txt){
if (line >= _rows) {
ESP_LOGE(DISP, "Line >= max rows!");
return;
}
_lcd->setCursor(0, line);
int len = strlen(txt);
if (len < _columns) {
int pad = (_columns - len) / 2;
for (int i = 0; i < pad; i++) {
_lcd->print(" ");
}
}
_lcd->print(txt);
}
void Display::text(uint8_t line, const char *text) {
if (line >= _rows) {
ESP_LOGE(DISP, "Line >= max rows!");
return;
}
_lcd->setCursor(0, line);
_lcd->print(text);
}
void Display::welcomeScreen() {
ESP_LOGI(DISP, "Info screen");
clear();
textCenter(0, "** Vibra Dude **");
textCenter(1, "WMT Stalowa Wola");
String firm = String(VERSION);
firm.trim();
textCenter(2, firm.c_str());
//_lcd->print(VERSION);
}
void Display::mainScreen(){
ESP_LOGI(DISP, "Main screen");
clear();
textCenter(0, "** Vibra Dude **");
textCenter(1, "WMT Stalowa Wola");
//String firm = String(VERSION).trim();
//textCenter(2, firm.c_str());
//_lcd->print(VERSION);
ESP_LOGI(DISP, "Finish main screen");
}
void Display::clearRow(uint16_t line){
_lcd->setCursor(0, line);
_lcd->print(" ");
_lcd->setCursor(0, line);
}
void Display::textStatus(const char *text){
clearRow(3);
_lcd->setCursor(0, 3);
_lcd->print(text);
}
void Display::updateNetwork(String ip, bool connected) {
// przykładowa prosta implementacja:
// clear();
// text(0, connected ? "NET: OK" : "NET: OFF");
// _lcd->setCursor(0, 1);
// _lcd->print(ip);
}
void Display::updateBarWiFi(long signal, bool connected) {
// tu możesz wykorzystać np. init_bargraph / draw_horizontal_graph
// (void)signal;
// (void)connected;
}
void Display::textStyle(const uint8_t st, String text) {
// miejsce na różne style tekstu
//(void)st;
// clear();
//text(0, text.c_str());
}
void Display::message(String text) {
// clear();
//text(0, text.c_str());
}
void Display::print(String text) {
_lcd->print(text);
}
void Display::println(String text) {
_lcd->print(text);
_lcd->print("\n");
}
void Display::setCursor(int16_t x, int16_t y) {
_lcd->setCursor((uint8_t)x, (uint8_t)y);
}
void Display::showAccel(float a, float b, float c) {
clear();
// _lcd->setCursor(0, 0);
// _lcd->print("Ax: ");
// _lcd->print(a, 2);
// _lcd->setCursor(0, 1);
// _lcd->print("Ay: ");
// _lcd->print(b, 2);
// _lcd->setCursor(0, 2);
// _lcd->print("Az: ");
// _lcd->print(c, 2);
}
void Display::displayOffline(bool measure, uint8_t count, float freeSpace, long licznik, bool refresh){
if(refresh){
oyear, omonth, oday, ohour, omin, osec, ospace, oadxlcnt = 100;
_lcd->clear();
_lcd->setCursor(0, 0);
}
DateTime now = rtc_.now();
uint16_t year = now.year();
uint8_t month = now.month();
uint8_t day = now.day();
uint8_t hour = now.hour();
uint8_t min = now.minute();
uint8_t sec = now.second();
// DEBUG
//ESP_LOGI(DISP, "RTC: %d:%d:%d %d:%d:%d", now.day(), now.month(), now.year(), now.hour(), now.minute(), now.second());
//ESP_LOGI(DISP, "RTC: %d:%d:%d %d:%d:%d", oday, omonth, oyear, ohour, omin, osec);
if ((hour != ohour) || (refresh)) {
ohour = hour;
_lcd->setCursor(0, 0);
if (hour < 10) _lcd->print('0');
_lcd->print(hour);
_lcd->setCursor(2, 0);
_lcd->print(':');
}
if ((min != omin) || (refresh)) {
omin = min;
_lcd->setCursor(3, 0);
if (min < 10) _lcd->print('0');
_lcd->print(min);
_lcd->print(':');
}
if ((sec != osec) || (refresh)) {
_lcd->setCursor(6, 0);
osec = sec;
if (sec < 10) _lcd->print('0');
_lcd->print(sec);
}
if ((day != oday || month != omonth || year != oyear) || (refresh)){
_lcd->setCursor(10, 0);
if (day < 10) _lcd->print('0');
_lcd->print(day);
_lcd->print('.');
if (month < 10) _lcd->print('0');
_lcd->print(month);
_lcd->print('.');
_lcd->print(year);
oday = day;
omonth = month;
oyear = year;
if(!config.measure)
textStatus("OK: Start MEASURE");
else
textStatus("Wait for NEXT");
}
if((freeSpace != ospace) || (refresh)){
ospace = freeSpace;
_lcd->setCursor(0, 1);
_lcd->print("FREE:");
_lcd->print(freeSpace);
_lcd->print("GB");
}
if((count != oadxlcnt) || (omode != config.measure) || (refresh)){
oadxlcnt = count;
omode = config.measure;
_lcd->setCursor(0, 2);
_lcd->print("ADXL:");
_lcd->print(count);
_lcd->print(" MODE:");
_lcd->print(config.measure ? "AUTO ":"MANUAL ");
}
}
// Podczas pomiaru
void Display::displayOnOffM(bool measure, String myDir, String myFile) {
clear();
_lcd->setCursor(0, 0);
_lcd->print(measure ? "MEASURE STARTED" : "MEASURE STOPPED");
_lcd->setCursor(0, 1);
_lcd->print("DIR: " + myDir);
//_lcd->print(myDir);
//_lcd->setCursor(0, 2);
//_lcd->print("FILE:");
//_lcd->setCursor(0, 3);
_lcd->setCursor(0, 2);
_lcd->print(myFile);
}
/* Podsumowanie po pomiarze: ramki, sampling, etc. */
void Display::displaySampleRateSummary(uint32_t reccount, uint32_t captureSeconds, float khz, String filename){
clear();
_lcd->setCursor(0, 0);
if (captureSeconds == 0) {
_lcd->print("Sampling time 0!");
delay(1000);
return;
}
float fs = (float)reccount / (float)captureSeconds; // Hz (ramki/s)
float fs_kHz = fs / 1000.0f;
_lcd->setCursor(0,0);
_lcd->print("Frames:");
_lcd->print((unsigned)reccount);
_lcd->setCursor(0,1);
_lcd->print("Time:");
_lcd->print((unsigned)captureSeconds);
_lcd->print("s.");
_lcd->setCursor(0,2);
_lcd->print("Rate:");
_lcd->print(fs_kHz);
_lcd->print("kHz");
_lcd->setCursor(0,3);
_lcd->print(filename);
}

View File

@@ -0,0 +1,16 @@
#include <Logger.h>
const char *TAG_MAIN = "MAIN";
const char *TAG_DISPLAY = "DISPLAY";
const char *TAG_WIFI = "WiFi";
const char *TAG_ADXL = "ADXL345";
const char *TAG_CONF = "CONFIG";
void init_log_levels() {
esp_log_level_set(TAG_MAIN, ESP_LOG_INFO);
esp_log_level_set(TAG_DISPLAY, ESP_LOG_INFO);
esp_log_level_set(TAG_WIFI, ESP_LOG_INFO);
esp_log_level_set(TAG_ADXL, ESP_LOG_INFO);
esp_log_level_set(TAG_CONF, ESP_LOG_INFO);
}

View File

@@ -0,0 +1,594 @@
#include <Measure.h>
static const char *TAG_CAPTURE = "CAPTURE";
// Był na sztywno przydzielony bufor 128 KB. Zmieniam tak, aby zaalokować 4 MB lub 6 MB (trzeba zostawić resztę na potrzeby systemu).
DataCapture::DataCapture(ADXL345FastSPI &adxl, Display &display, RTC_DS3231 &rtc, fs::FS &storage, size_t bufferSize)
: adxl_(adxl), display_(display), rtc_(rtc), _fs(storage){
// Ustawiamy duży bufor dla PSRAM (np. 4 MB = 4194304 bajty)
// 4 * 1024 * 1024 / 12 bajtów = ~349 525 próbek
// Możesz to przekazać jako parametr lub wpisać na sztywno:
if (bufferSize < 4 * 1024 * 1024) bufferSize = 4 * 1024 * 1024;
bufferSize_ = bufferSize;
#if defined(ESP32)
// ps_malloc jest kluczowy dla PSRAM. Wymuszamy alokację w zewnętrznym PSRAM
buffer_ = (uint8_t*)ps_malloc(bufferSize_);
#endif
if (!buffer_) buffer_ = (uint8_t*)malloc(bufferSize_);
if (!buffer_) {
ESP_LOGE(TAG_CAPTURE, "No mem for buffer (%u B)", (unsigned)bufferSize_);
} else {
ESP_LOGI(TAG_CAPTURE, "Capture buffer: %u B", (unsigned)bufferSize_);
}
}
DataCapture::~DataCapture() {
if (buffer_) { free(buffer_); buffer_ = nullptr; }
}
void DataCapture::stop() { measurementActive_ = false; }
// --- ZAPIS BUFORA NA SD ---
bool DataCapture::flushToFile(File& f) {
if (bufferIndex_ == 0) return true;
Watchdog::feed();
size_t written = f.write(buffer_, bufferIndex_);
if (written != bufferIndex_) {
ESP_LOGE(TAG_CAPTURE, "SD save error (written=%u, expected=%u)", (unsigned)written, (unsigned)bufferIndex_);
display_.textStatus("SD save error");
bufferIndex_ = 0;
return false;
}
bufferIndex_ = 0;
return true;
}
// CRC8 (zostawione nieużywane domyślnie)
uint8_t DataCapture::crc8(const uint8_t* data, size_t len) {
uint8_t crc = 0x00;
for (size_t i = 0; i < len; ++i) {
crc ^= data[i];
for (uint8_t b = 0; b < 8; ++b) {
if (crc & 0x80) crc = (crc << 1) ^ 0x07; else crc <<= 1;
}
}
return crc;
}
void DataCapture::setTestingIndicator_(bool on, String myDir, String myFile) {
display_.displayOnOffM(on, myDir, myFile);
}
// --- automatyczna nazwa pliku ---
bool DataCapture::captureAuto(uint32_t captureSeconds, const char * /*baseDirectory*/) {
isExit = false;
Watchdog::feed();
String path = allocateNextFilePath();
if (path.isEmpty()) {
ESP_LOGE(TAG_CAPTURE, "Error generate file name in %s", _baseDir.c_str());
display_.textStatus("Error file");
return false;
}
ESP_LOGI(TAG_CAPTURE, "Autogenerate file: %s", path.c_str());
display_.textStatus(path.c_str());
Watchdog::feed();
return capture(captureSeconds, path.c_str());
}
// --- main measure function ---
// bool DataCapture::capture(uint32_t captureSeconds, const char *filename) {
// Watchdog::feed();
// if (!buffer_) {
// ESP_LOGE(TAG_CAPTURE, "No buffer - cancel.");
// display_.textStatus("Buffer error");
// return false;
// }
// ESP_LOGI(TAG_CAPTURE, "Capture %u sec. -> %s", (unsigned)captureSeconds, filename);
// File dataFile = _fs.open(filename, FILE_WRITE);
// if (!dataFile) {
// ESP_LOGE(TAG_CAPTURE, "Can't open file: %s", filename);
// char buf[100];
// snprintf(buf, sizeof(buf), "Can't open: %s", filename);
// display_.textStatus(buf);
// //s2etTestingIndicator_(false, _baseDir, filename);
// return false;
// }
// // Bufory RAMKI (po 1 próbce z każdego sensora)
// static const uint8_t MAXN = 7; // zgodnie z projektem
// int16_t X[MAXN]{}, Y[MAXN]{}, Z[MAXN]{};
// uint32_t TS[MAXN]{};
// Watchdog::feed();
// const uint32_t tStart_us = micros();
// const uint32_t captureDuration_us = captureSeconds * 1000000UL;
// // Czas startu (UTC) — tylko w nagłówku!
// const DateTime now = rtc_.now();
// const int32_t unix_start = now.unixtime();
// // Nagłówek
// FileHeader hdr;
// memcpy(hdr.magic, "WMT", 3);
// hdr.version = 1;
// hdr.headerSize = sizeof(FileHeader);
// hdr.sampleSize = sizeof(Sample);
// hdr.timestamp = unix_start;
// hdr.reccount = 0;
// if (dataFile.write(reinterpret_cast<const uint8_t*>(&hdr), sizeof(hdr)) != sizeof(hdr)) {
// ESP_LOGE(TAG_CAPTURE, "Header write failed");
// display_.textStatus("Header SD failed");
// dataFile.close();
// return false;
// }
// measurementActive_ = true;
// bufferIndex_ = 0;
// setTestingIndicator_(true, _baseDir, filename);
// uint32_t frames = 0; // liczba zebranych „ramek”
// const uint8_t presentCnt = adxl_.size(); // wykryte sensory (wg begin)
// // --- Pętla akwizycji wyrównanych ramek ---
// while (measurementActive_) {
// if(isEscape()) break; // wyjście z pętli gdy OK przerwie
// uint32_t now_us = micros();
// if ((now_us - tStart_us) >= captureDuration_us) break;
// // 1) Czekamy aż KAŻDY obecny sensor ma >= 1 próbkę w FIFO (DATA_READY)
// while (!adxl_.availableAll()) {
// if(isEscape()) break; //????? sprawdź kHz pomiaru!
// // krótki spin; unikamy delay(1), aby nie zrywać 3.2 kHz
// // (opcjonalnie yield(); jeśli system wymaga)
// }
// // 2) Zdejmij po 1 NAJSTARSZEJ próbce z każdego sensora (STREAM FIFO)
// const uint8_t got = adxl_.readAlignedOnce(X, Y, Z, TS);
// if (got != presentCnt) {
// // rzadki przypadek niepełna ramka; pomiń
// continue;
// }
// // 3) Wspólny timestamp ramki: minimalny z TS[i]
// uint32_t tmin = UINT32_MAX;
// for (uint8_t i = 0; i < MAXN; ++i) {
// if (!adxl_.isPresent(i)) continue;
// if (TS[i] < tmin) tmin = TS[i];
// }
// const uint32_t frame_offset_us = tmin - tStart_us;
// // 4) Zapis 7 rekordów Sample do bufora
// for (uint8_t i = 0; i < MAXN; ++i) {
// if (!adxl_.isPresent(i)) continue;
// // Konstruuj rekord bezpośrednio w buforze (bez memcpy)
// if (bufferIndex_ + sizeof(Sample) > bufferSize_) {
// if (!flushToFile(dataFile)) { measurementActive_ = false; break; }
// }
// Sample *dst = reinterpret_cast<Sample*>(buffer_ + bufferIndex_);
// dst->offset = frame_offset_us;
// dst->sensor_id = i;
// dst->x = X[i]; dst->y = Y[i]; dst->z = Z[i];
// dst->ready = true;
// bufferIndex_ += sizeof(Sample);
// }
// if (!measurementActive_) break;
// frames++;
// // Watchdog rzadziej, żeby nie zwiększać jittera
// if ((frames & 0xFF) == 0) Watchdog::feed();
// } // while
// // Statystyki
// ESP_LOGI(TAG_CAPTURE, "Frames: %u, sensors: %u", (unsigned)frames, (unsigned)presentCnt);
// printSamplingRate(frames, captureSeconds); // liczymy ramki/sek.
// // Domknij bufor
// if (bufferIndex_ > 0) {
// if (!flushToFile(dataFile)) {
// Watchdog::feed();
// ESP_LOGE(TAG_CAPTURE, "Finish save error.");
// display_.textStatus("Save SD error");
// delay(1000);
// }
// }
// ESP_LOGI(TAG_CAPTURE, "Saved to SD successful");
// display_.textStatus("Saved SD OK");
// delay(700);
// // Uzupełnij nagłówek o liczbę rekordów (Sample)
// hdr.reccount = frames * presentCnt;
// dataFile.seek(0);
// dataFile.write(reinterpret_cast<const uint8_t*>(&hdr), sizeof(hdr));
// dataFile.flush();
// dataFile.close();
// //setTestingIndicator_(false, _baseDir, filename);
// bool ok = measurementActive_;
// measurementActive_ = false;
// if (ok) {
// ESP_LOGI(TAG_CAPTURE, "Successful");
// display_.textStatus("Successfull");
// Watchdog::feed();
// return true;
// } else {
// ESP_LOGE(TAG_CAPTURE, "Measurement aborted");
// display_.textStatus("Aborted");
// Watchdog::feed();
// return false;f
// }
// }
// --- main measure function --- V2
bool DataCapture::capture(uint32_t captureSeconds, const char *filename) {
Watchdog::feed();
if (!buffer_) {
ESP_LOGE(TAG_CAPTURE, "No buffer - cancel.");
display_.textStatus("Buffer error");
return false;
}
// Obliczamy ile danych maksymalnie może wejść do bufora
const uint8_t presentCnt = adxl_.size();
const size_t frameSize = presentCnt * sizeof(Sample);
ESP_LOGI(TAG_CAPTURE, "Start RAM capture: %u sec.", (unsigned)captureSeconds);
display_.textStatus("Sampling...");
static const uint8_t MAXN = 7;
int16_t X[MAXN]{}, Y[MAXN]{}, Z[MAXN]{};
uint32_t TS[MAXN]{};
measurementActive_ = true;
bufferIndex_ = 0;
uint32_t frames = 0;
const uint32_t tStart_us = micros();
const uint32_t captureDuration_us = captureSeconds * 1000000UL;
const DateTime now_start = rtc_.now(); // Czas dla nagłówka
// --- KRYTYCZNA PĘTLA POMIAROWA (ZERO SD) ---
while (measurementActive_) {
if(isEscape()) break;
uint32_t now_us = micros();
if ((now_us - tStart_us) >= captureDuration_us) break;
// Sprawdzenie czy mamy miejsce w PSRAM na kolejną pełną ramkę
if (bufferIndex_ + frameSize > bufferSize_) {
ESP_LOGW(TAG_CAPTURE, "PSRAM Buffer Full! Stopping.");
break;
}
// Czekamy na dane z sensora
while (!adxl_.availableAll()) {
if(isEscape()) break;
}
const uint8_t got = adxl_.readAlignedOnce(X, Y, Z, TS);
if (got != presentCnt) continue;
// Wspólny timestamp ramki
uint32_t tmin = UINT32_MAX;
for (uint8_t i = 0; i < MAXN; ++i) {
if (!adxl_.isPresent(i)) continue;
if (TS[i] < tmin) tmin = TS[i];
}
const uint32_t frame_offset_us = tmin - tStart_us;
// Zapis do PSRAM
for (uint8_t i = 0; i < MAXN; ++i) {
if (!adxl_.isPresent(i)) continue;
Sample *dst = reinterpret_cast<Sample*>(buffer_ + bufferIndex_);
dst->offset = frame_offset_us;
dst->sensor_id = i;
dst->x = X[i]; dst->y = Y[i]; dst->z = Z[i];
dst->ready = true;
bufferIndex_ += sizeof(Sample);
}
frames++;
if ((frames & 0x3FF) == 0) Watchdog::feed(); // Rzadziej, by nie siać jittera
} // --- KONIEC PĘTLI POMIAROWEJ ---
measurementActive_ = false;
ESP_LOGI(TAG_CAPTURE, "Capture finished. Samples in RAM: %u. Writing to SD...", (unsigned)frames);
display_.textStatus("Saving to SD...");
// Dopiero teraz otwieramy plik na SD
File dataFile = _fs.open(filename, FILE_WRITE);
if (!dataFile) {
ESP_LOGE(TAG_CAPTURE, "Can't open SD file!");
display_.textStatus("SD Open Error");
return false;
}
// Przygotowanie nagłówka
FileHeader hdr;
memcpy(hdr.magic, "WMT", 3);
hdr.version = 1;
hdr.headerSize = sizeof(FileHeader);
hdr.sampleSize = sizeof(Sample);
hdr.timestamp = now_start.unixtime();
hdr.reccount = frames * presentCnt;
// Zapis nagłówka
dataFile.write(reinterpret_cast<const uint8_t*>(&hdr), sizeof(hdr));
// Zapis całego bufora PSRAM jednym ciągiem
size_t written = dataFile.write(buffer_, bufferIndex_);
if (written == bufferIndex_) {
ESP_LOGI(TAG_CAPTURE, "SD Save Successful: %u bytes", (unsigned)written);
//display_.textStatus("Save OK");
display_.textStatus(basenameFromPath(filename));
delay(2000); // Tutaj zrob to inaczej
} else {
ESP_LOGE(TAG_CAPTURE, "SD Write Error!");
display_.textStatus("SD Write Err");
}
dataFile.close();
printSamplingRate(frames, captureSeconds, filename);
return (written == bufferIndex_);
}
// --- Narzędzia SD i pierdoły ---
String DataCapture::unixToDateTime(uint32_t ts) {
DateTime dt(ts);
char buf[25];
snprintf(buf, sizeof(buf), "%04u-%02u-%02u %02u:%02u:%02u",
dt.year(), dt.month(), dt.day(), dt.hour(), dt.minute(), dt.second());
return String(buf);
}
void DataCapture::readHeaderAndPrint(const char *path) {
Watchdog::feed();
File f = SD.open(path, FILE_READ);
if (!f) { ESP_LOGE(TAG_CAPTURE,"Error open file %s", path); return; }
FileHeader hdr;
size_t rd = f.read((uint8_t*)&hdr, sizeof(hdr));
f.close();
if (rd != sizeof(hdr)) { ESP_LOGE(TAG_CAPTURE,"Header error"); return; }
if (memcmp(hdr.magic, "WMT", 3) != 0) { ESP_LOGE(TAG_CAPTURE,"File signature error"); return; }
ESP_LOGI(TAG_CAPTURE,"===== WMT file header =====");
ESP_LOGI(TAG_CAPTURE,"Signature: %.3s", hdr.magic);
ESP_LOGI(TAG_CAPTURE,"Version: %u", hdr.version);
ESP_LOGI(TAG_CAPTURE,"Header size:%u B", hdr.headerSize);
ESP_LOGI(TAG_CAPTURE,"Sample size:%u B", hdr.sampleSize);
ESP_LOGI(TAG_CAPTURE,"Timestamp: %d", hdr.timestamp);
ESP_LOGI(TAG_CAPTURE,"Reccount: %u", hdr.reccount);
ESP_LOGI(TAG_CAPTURE,"===========================");
}
void DataCapture::printSamplingRate(uint32_t reccount, uint32_t captureSeconds, String filename) {
Watchdog::feed();
if (captureSeconds == 0) { ESP_LOGW(TAG_CAPTURE, "Sampling time = 0!"); return; }
float fs = (float)reccount / (float)captureSeconds; // Hz (ramki/s)
float fs_kHz = fs / 1000.0f;
ESP_LOGI(TAG_CAPTURE,"Frames: %u", (unsigned)reccount);
ESP_LOGI(TAG_CAPTURE,"Time: %u s", (unsigned)captureSeconds);
ESP_LOGI(TAG_CAPTURE,"Rate: %.3f kHz (frames)", fs_kHz);
display_.displaySampleRateSummary(reccount, captureSeconds, fs_kHz, filename);
display_.textStatus("Summary");
delay(2000);
}
// --- Zarządzanie katalogami/plikiem ---
String DataCapture::allocateNextFilePath() {
Watchdog::feed();
uint32_t highestDir = findHighestNumericDir();
if (highestDir == 0) {
highestDir = 1;
if (!ensureDir(highestDir)) return String();
}
uint32_t count = 0, highestIdx = 0;
scanDirForWmt(highestDir, count, highestIdx);
uint32_t targetDir = highestDir;
uint32_t nextIdx = 0;
if (count > _maxFilesPerDir) {
targetDir = highestDir + 1;
if (!ensureDir(targetDir)) return String();
nextIdx = 1;
} else {
nextIdx = (highestIdx == 0) ? 1 : (highestIdx + 1);
}
return joinPath(dirPath(targetDir), makeIndexedName(nextIdx));
}
String DataCapture::generateNextFilename() {
Watchdog::feed();
uint32_t highestDir = findHighestNumericDir();
if (highestDir == 0) return String();
uint32_t count = 0, highestIdx = 0;
scanDirForWmt(highestDir, count, highestIdx);
ESP_LOGI(TAG_CAPTURE, "highestDir %u, count %u, highestIdx %u", (unsigned)highestDir, (unsigned)count, (unsigned)highestIdx);
if (highestIdx == 0) return String();
return joinPath(dirPath(highestDir), makeIndexedName(highestIdx));
}
FileInfo DataCapture::getLastFileInfo() {
FileInfo info{String(), 0u, false};
Watchdog::feed();
String lastPath = generateNextFilename();
if (lastPath.isEmpty()) return info;
if (_fs.exists(lastPath)) {
File f = _fs.open(lastPath, FILE_READ);
if (f) {
info.exists = true;
info.path = lastPath;
info.size = f.size();
f.close();
}
}
return info;
}
bool DataCapture::deleteAllOnSD() {
Watchdog::feed();
File root = _fs.open(_baseDir);
if (!root || !root.isDirectory()) return false;
for (File e = root.openNextFile(); e; e = root.openNextFile()) {
Watchdog::feed();
String child = String(e.name());
e.close();
if (!recursiveDelete(child)) return false;
}
root.close();
return true;
}
SpaceInfo DataCapture::freeSpaceMB() {
Watchdog::feed();
SpaceInfo si{0.0, "UNKNOWN"};
#if defined(ESP32)
uint64_t total = SD.totalBytes();
uint64_t used = SD.usedBytes();
if (total >= used) {
uint64_t freeB = total - used;
const double oneGB = 1024.0 * 1024.0 * 1024.0;
const double oneMB = 1024.0 * 1024.0;
if (freeB >= (uint64_t)oneGB) {
si.value = (double)freeB / oneGB; si.unit = "GB";
} else {
si.value = (double)freeB / oneMB; si.unit = "MB";
}
}
#endif
return si;
}
float DataCapture::freeSpaceFloat(bool* isGB) {
Watchdog::feed();
SpaceInfo si = freeSpaceMB();
if (!si.unit || strcmp(si.unit, "UNKNOWN") == 0) {
if (isGB) *isGB = false;
return -1.0f;
}
if (isGB) *isGB = (strcmp(si.unit, "GB") == 0);
return static_cast<float>(si.value);
}
// --- helpery pomocnicze ---
bool DataCapture::isAllDigits(const char *s) {
if (!s || !*s) return false;
while (*s) { if (*s < '0' || *s > '9') return false; ++s; }
return true;
}
uint32_t DataCapture::toUint(const char *s) {
uint32_t v = 0; while (*s) { v = v*10u + uint32_t(*s - '0'); ++s; } return v;
}
String DataCapture::dirPath(uint32_t dirNum) const { return joinPath(_baseDir, String(dirNum)); }
const char *DataCapture::basenameFromPath(const char *full) { const char *slash = strrchr(full, '/'); return slash ? slash + 1 : full; }
bool DataCapture::isWmtWithDigits(const char* name, uint32_t& idxOut) const {
size_t nlen = strlen(name), extLen = _ext.length();
if (nlen != (size_t)_digits + extLen) return false;
if (strncmp(name + _digits, _ext.c_str(), extLen) != 0) return false;
for (uint8_t i = 0; i < _digits; ++i) if (name[i] < '0' || name[i] > '9') return false;
char buf[16]; memcpy(buf, name, _digits); buf[_digits] = '\0';
idxOut = (uint32_t)strtoul(buf, nullptr, 10); return true;
}
String DataCapture::makeIndexedName(uint32_t idx) const {
char fmt[8]; snprintf(fmt, sizeof(fmt), "%%0%ulu", (unsigned long)_digits);
char num[24]; snprintf(num, sizeof(num), fmt, (unsigned long)idx);
return String(num) + _ext;
}
String DataCapture::joinPath(const String &a, const String &b) { if (a.endsWith("/")) return a + b; return a + "/" + b; }
bool DataCapture::ensureDir(uint32_t dirNum) {
String path = dirPath(dirNum);
if (_fs.exists(path)) { File f = _fs.open(path); bool ok = f && f.isDirectory(); if (f) f.close(); return ok; }
return _fs.mkdir(path);
}
uint32_t DataCapture::findHighestNumericDir() {
File root = _fs.open(_baseDir);
if (!root || !root.isDirectory()) return 0;
uint32_t maxDir = 0;
for (File f = root.openNextFile(); f; f = root.openNextFile()) {
if (f.isDirectory()) {
const char *nm = basenameFromPath(f.name());
if (isAllDigits(nm)) { uint32_t v = toUint(nm); if (v > maxDir) maxDir = v; }
}
}
return maxDir;
}
void DataCapture::scanDirForWmt(uint32_t dirNum, uint32_t &count, uint32_t &highestIdx) {
Watchdog::feed();
count = 0; highestIdx = 0;
String path = dirPath(dirNum);
File dir = _fs.open(path);
if (!dir || !dir.isDirectory()) return;
for (File f = dir.openNextFile(); f; f = dir.openNextFile()) {
if (f.isDirectory()) { Watchdog::feed(); f.close(); continue; }
const char* base = basenameFromPath(f.name());
uint32_t idx = 0;
if (isWmtWithDigits(base, idx)) { count++; if (idx > highestIdx) highestIdx = idx; }
f.close();
}
}
bool DataCapture::recursiveDelete(const String &path) {
File e = _fs.open(path);
if (!e) return false;
if (!e.isDirectory()) { e.close(); return _fs.remove(path); }
// katalog
for (File c = e.openNextFile(); c; c = e.openNextFile()) {
String child = String(c.name()); c.close();
if (!recursiveDelete(child)) { e.close(); return false; }
}
e.close();
if (path == _baseDir) return true; // nie usuwamy katalogu bazowego
return _fs.rmdir(path);
}
void DataCapture::printLastFileInfoSerial() {
Watchdog::feed();
const FileInfo info = getLastFileInfo();
if (!info.exists) {
ESP_LOGE(TAG_CAPTURE, "No .wmt file exists. Last file not exists");
return;
}
const double kb = static_cast<double>(info.size) / 1024.0;
const double mb = kb / 1024.0;
#if defined(ESP32)
// ESP32 wspiera Serial.printf z %llu
ESP_LOGI(TAG_CAPTURE, "Last file: %s", info.path.c_str());
ESP_LOGI(TAG_CAPTURE, "Size: %llu B (%.2f KB, %.2f MB)", static_cast<unsigned long long>(info.size), kb, mb);
char buf[100];
snprintf(buf, sizeof(buf), "Size:%.2f KB", kb);
display_.textStatus(buf);
#else
// Wariant zachowawczy dla platform bez %llu; pokazujemy rozmiar w MB/KB.
ESP_LOGI(TAG_CAPTURE, "Last file info: "));
ESP_LOGI(TAG_CAPTURE, info.path);
ESP_LOGI(TAG_CAPTURE, "Size: "));
ESP_LOGI(TAG_CAPTURE, mb);
//ESP_LOGI(TAG_CAPTURE, " MB"));
//Serial.print(F(" ("));
//Serial.print(kb, 2);
//Serial.println(F(" KB)"));
#endif
}

View File

@@ -0,0 +1,273 @@
#include <Network.h>
#include <SD.h>
static const char *WIFI = "wifi";
extern ConfigManager configManager;
WiFiManager::WiFiManager() {}
void WiFiManager::begin() {
ESP_LOGI(WIFI, "Start network");
isAccessPoint = config.connect;
if (!isAccessPoint) {
setupAccessPoint(ssidAP.c_str(), passwordAP.c_str());
} else {
ESP_LOGI(WIFI, "WiFi client");
ReadConnection();
connectToWiFi();
}
getRSSI();
setupMDNS();
}
// Konwersja char na IPAddress
bool WiFiManager::convertCharToIPAddress(const char *str, IPAddress& ip) {
uint8_t octets[4];
int parsed = sscanf(str, "%hhu.%hhu.%hhu.%hhu", &octets[0], &octets[1], &octets[2], &octets[3]);
if (parsed == 4) {
ip = IPAddress(octets[0], octets[1], octets[2], octets[3]);
return true;
}
return false;
}
void WiFiManager::ReadConnection() {
ESP_LOGE(WIFI, "Network config error");
isAccessPoint = config.connect;
useDHCP = config.dhcp;
convertCharToIPAddress(config.ip, local_IP);
convertCharToIPAddress(config.gateway, gateway);
convertCharToIPAddress(config.subnet, subnet);
convertCharToIPAddress(config.dns, dns);
}
void WiFiManager::connectToWiFi() {
ESP_LOGI(WIFI, "WiFi STA mode");
WiFi.mode(WIFI_STA);
WiFi.begin(config.ssid, config.password);
if (!useDHCP) {
if (!WiFi.config(local_IP, gateway, subnet, dns)) {
ESP_LOGE(WIFI, "Network static IP failed");
}
}
ESP_LOGI(WIFI, "WiFi connecting to last SSID: %s", config.ssid);
int retries = 0;
while (WiFi.status() != WL_CONNECTED && retries < 20) {
delay(500);
retries++;
updateLED();
}
if (WiFi.status() != WL_CONNECTED) {
ESP_LOGI(WIFI, "Failed. Reading wifi.txt from SD card...");
File f = SD.open("/wifi.txt");
if (f) {
while (f.available()) {
String line = f.readStringUntil('\n');
line.trim();
if (line.isEmpty()) continue;
int sep = line.indexOf(';');
if (sep > 0) {
String s = line.substring(0, sep);
String p = line.substring(sep + 1);
ESP_LOGI(WIFI, "Trying SSID from list: %s", s.c_str());
WiFi.disconnect();
WiFi.begin(s.c_str(), p.c_str());
int tr = 0;
while (WiFi.status() != WL_CONNECTED && tr < 20) {
delay(500);
tr++;
updateLED();
}
if (WiFi.status() == WL_CONNECTED) {
ESP_LOGI(WIFI, "Connected to %s. Saving to config.", s.c_str());
strncpy(config.ssid, s.c_str(), sizeof(config.ssid)-1);
strncpy(config.password, p.c_str(), sizeof(config.password)-1);
configManager.saveConfig();
break;
}
}
}
f.close();
} else {
ESP_LOGW(WIFI, "wifi.txt not found on SD card.");
}
}
if (WiFi.status() == WL_CONNECTED) {
ESP_LOGI(WIFI, "SSID: %s", config.ssid);
String ipString = "IP: " + WiFi.localIP().toString();
ESP_LOGI(WIFI, "%s", ipString.c_str());
String gatewayInfo = "GATEWAY: " + WiFi.gatewayIP().toString();
ESP_LOGI(WIFI, "%s", gatewayInfo.c_str());
String dnsInfo = "DNS: " + WiFi.dnsIP().toString();
ESP_LOGI(WIFI, "%s", dnsInfo.c_str());
} else {
String infoWiFi = "WIFI CONNECTION ERROR: ALL FAILED";
ESP_LOGI(WIFI, "%s", infoWiFi.c_str());
}
updateLED();
}
void WiFiManager::setupAccessPoint(const char *newSSID, const char *newPassword) {
ESP_LOGI(WIFI, "Start AP mode");
// Wyłączenie zapisywania konfiguracji do flash
//WiFi.persistent(false);
// Usunięcie zapisanej konfiguracji trybu stacji
//WiFi.disconnect(true);
//delay(1000);
//WiFi.eraseAP();
//WiFi.enableAP(false);
//WiFi.enableAP(true);
WiFi.mode(WIFI_AP);
WiFi.softAPsetHostname(config.hostname);
WiFi.softAP(newSSID, newPassword);
delay(1000);
String ssi = "AP SSID: " + String(WiFi.softAPSSID());
ESP_LOGI(WIFI, "%s", ssi.c_str());
String sspass = "AP PASS: " + String(newPassword);
ESP_LOGI(WIFI, "%s", sspass.c_str());
IPAddress IP = WiFi.softAPIP();
String ipString = "AP IP: " + IP.toString();
ESP_LOGI(WIFI, "%s", ipString.c_str());
setupMDNS();
getRSSI();
}
bool WiFiManager::isWiFiOK(){
if (WiFi.status() != WL_CONNECTED)
return false;
else
return true;
}
void WiFiManager::checkWiFiConnection() {
if (!isAccessPoint) {
getRSSI();
if (WiFi.status() != WL_CONNECTED) {
String infoWiFi = "WiFi reconnecting: " + String(config.ssid);
ESP_LOGI(WIFI, "%s", infoWiFi.c_str());
WiFi.reconnect();
int retries = 0;
while (WiFi.status() != WL_CONNECTED && retries < 20) {
delay(500);
retries++;
updateLED();
}
if (WiFi.status() == WL_CONNECTED) {
//String infoWiFi = "WiFi connected: " + String(config.ssid);
//ESP_LOGI(WIFI, "%s", infoWiFi.c_str());
//String ipString = "IP: " + WiFi.localIP().toString();
//ESP_LOGI(WIFI, "%s", ipString.c_str());
getRSSI();
setupMDNS();
} else {
String infoWiFi = "WiFi reconnect error: " + String(config.ssid);
ESP_LOGI(WIFI, "%s", infoWiFi.c_str());
getRSSI();
}
}
updateLED();
}
}
int WiFiManager::rssiToPercent(int rssi) {
if (rssi <= -100) {
return 0;
} else
if (rssi >= -50) {
return 100;
}
// Dla wartości pomiędzy -100 a -50 dBm stosujemy prostą liniową skalę
else {
return 2 * (rssi + 100); // Przykładowa liniowa zależność
}
}
void WiFiManager::updateLED() {
getRSSI();
if (WiFi.status() == WL_CONNECTED) {
int8_t rssi = WiFi.RSSI();
if (rssi > -70) {
;
//digitalWrite(LED_PIN, HIGH); // Silny sygnał
} else {
String signalInfo = "WIFI WEAK SIGNAL: " + String(rssi) + " " + rssiToPercent(rssi) + "%";
ESP_LOGW(WIFI, "%s", signalInfo.c_str());
}
}
}
int8_t WiFiManager::getRSSI() {
if (WiFi.status() == WL_CONNECTED) {
rssi = WiFi.RSSI();
return rssi;
}
return 0;
}
void WiFiManager::setupMDNS() {
ESP_LOGI(WIFI, "mDNS start");
if (!MDNS.begin(config.hostname)) {
ESP_LOGE(WIFI, "mDNS error");
} else {
String mdnsstr = "MDNS: http://" + String(config.hostname) + ".local";
ESP_LOGI(WIFI, "%s", mdnsstr.c_str());
}
}
bool WiFiManager::performOTAUpdate(bool allowInsecureTLS, std::function<void(int,int)> progressCb){
// 1) Pobierz i sprawdź URL
String url = String(config.updateUrl); // z Config.h globalny 'config'
url.trim();
if (url.isEmpty()) {
ESP_LOGE(WIFI, "[OTA] Pusty config.updateUrl przerwano.");
return false;
}
ESP_LOGI(WIFI, "[OTA] URL: %s", url.c_str());
// 2) Wymagamy aktywnego Wi-Fi w trybie klienta
if (WiFi.status() != WL_CONNECTED) {
ESP_LOGE(WIFI, "[OTA] Brak połączenia Wi-Fi przerwano.");
return false;
}
// 3) Callback postępu (opcjonalny)
if (progressCb) {
httpUpdate.onProgress([&](int cur, int total){ progressCb(cur, total); });
}
// 4) Konfiguracja klienta i wywołanie aktualizacji
httpUpdate.rebootOnUpdate(true); // po sukcesie reboot
t_httpUpdate_return ret;
if (url.startsWith("https://")) {
WiFiClientSecure client;
if (allowInsecureTLS) {
client.setInsecure(); // UWAGA: testy/dev; w produkcji lepiej setCACert(...)
}
client.setTimeout(15000);
ret = httpUpdate.update(client, url); // HTTPS
} else {
WiFiClient client;
client.setTimeout(15000);
ret = httpUpdate.update(client, url); // HTTP
}
// 5) Obsługa rezultatów
switch (ret) {
case HTTP_UPDATE_OK:
ESP_LOGI(WIFI, "[OTA] Sukces restart nastąpi za chwilę.");
return true; // reboot i tak zaraz nastąpi
case HTTP_UPDATE_NO_UPDATES:
ESP_LOGW(WIFI, "[OTA] Brak nowej wersji (304/Not Modified).");
return false;
case HTTP_UPDATE_FAILED:
default:
ESP_LOGE(WIFI, "[OTA] Błąd (%d): %s", httpUpdate.getLastError(), httpUpdate.getLastErrorString().c_str());
return false;
}
}

View File

@@ -0,0 +1,172 @@
#include <Settings.h>
// Lokalny tag logów
static const char *TAG_SETTINGS = "SETTINGS";
Settings::Settings(Display &display, RTC_DS3231 &rtc): display_(display), rtc_(rtc){}
Settings::~Settings() {}
void Settings::begin() {
pinMode(BTN_UP, INPUT_PULLUP);
pinMode(BTN_OK, INPUT_PULLUP);
pinMode(BTN_DOWN, INPUT_PULLUP);
}
// Zwraca true, jeśli przycisk jest wciśnięty
bool Settings::isPressed(uint8_t btnIndex) {
//ESP_LOGI(TAG_SETTINGS, "BTN check");
switch (btnIndex) {
case 1: return digitalRead(BTN_UP) == LOW;
case 2: return digitalRead(BTN_OK) == LOW;
case 3: return digitalRead(BTN_DOWN) == LOW;
default: return false;
}
}
// Kombinacja 1 i 3 jednocześnie
bool Settings::isBtnReset() {
ESP_LOGI(TAG_SETTINGS, "BTN reset check");
return (digitalRead(BTN_UP) == LOW && digitalRead(BTN_DOWN) == LOW);
}
bool Settings::isSetClock() {
ESP_LOGI(TAG_SETTINGS, "BTN set clock");
return (digitalRead(BTN_OK) == LOW);
}
/*
Ustawienie opcji
*/
void Settings::setConfigDevice() {
ESP_LOGI(TAG_SETTINGS, "Set config device");
display_.clear();
delay(300);
config.duration = editField(config.duration, 1, 20, "Test duration");
uint16_t dur = config.pause/1000;
dur = editField(dur, 1, 20, "Pause between");
config.pause = dur * 1000;
config.measure = editField(config.measure, 0, 1, "Autorun test");
ESP_LOGI(TAG_SETTINGS, "Config finish");
}
void Settings::finishConfigDevice(){
display_.clear();
display_.textCenter(1, "CONFIG UPDATED");
display_.textCenter(2, "RESTARTING");
delay(1000);
display_.clear();
ESP.restart();
}
/* --------------------------------------------------------------------
* Ustawianie czasu RTC:
* Kolejność: rok -> miesiąc -> dzień -> godzina -> minuta -> sekunda
* ------------------------------------------------------------------*/
void Settings::setTimeRTC() {
DateTime now = rtc_.now();
RtcDateTime t;
t.year = now.year();
t.month = now.month();
t.day = now.day();
t.hour = now.hour();
t.minute = now.minute();
t.second = now.second();
ESP_LOGI(TAG_SETTINGS,
"Start RTC edit: %04u-%02u-%02u %02u:%02u:%02u",
t.year, t.month, t.day, t.hour, t.minute, t.second);
display_.clear();
delay(300);
// Kolejne pola
t.year = editField(t.year, 2024, 2099, "YEAR");
t.month = editField(t.month, 1, 12, "MONTH");
// uproszczenie: 131 bez sprawdzania długości miesiąca
t.day = editField(t.day, 1, 31, "DAY");
t.hour = editField(t.hour, 0, 23, "HOUR");
t.minute = editField(t.minute, 0, 59, "MINUTE");
t.second = editField(t.second, 0, 59, "SECOND");
// Zapis do RTC
rtc_.adjust(DateTime(t.year, t.month, t.day, t.hour, t.minute, t.second));
ESP_LOGI(TAG_SETTINGS,
"RTC set to: %04u-%02u-%02u %02u:%02u:%02u",
t.year, t.month, t.day, t.hour, t.minute, t.second);
display_.clear();
display_.textCenter(1, "RTC UPDATED");
display_.textCenter(2, "RESTARTING");
//display_.textCenter(2, "OK TO CONTINUE");
delay(1000);
display_.clear();
ESP.restart();
}
// Edycja jednej wartości (rok/miesiąc/dzień/godz/min/sek)
int Settings::editField(int value, int minVal, int maxVal, const char *label) {
bool lastUp = false;
bool lastDown = false;
bool lastOk = false;
constrainValue(value, minVal, maxVal);
printField(label, value);
while (true) {
bool up = readBtnUp();
bool ok = readBtnOk();
bool down = readBtnDown();
// Zmiana przy puszczeniu/wciśnięciu (zbocze narastające)
if (up && !lastUp) {
value++;
if (value > maxVal) value = minVal;
printField(label, value);
}
if (down && !lastDown) {
value--;
if (value < minVal) value = maxVal;
printField(label, value);
}
if (ok && !lastOk) {
ESP_LOGI(TAG_SETTINGS, "[OK] %s = %d", label, value);
// mały debounce na wyjście z pola
delay(200);
return value;
}
lastUp = up;
lastDown = down;
lastOk = ok;
delay(80); //debouncing
}
}
void Settings::constrainValue(int &value, int minVal, int maxVal) {
if (value < minVal) value = minVal;
if (value > maxVal) value = maxVal;
}
// Wyświetlanie aktualnie edytowanego pola na LCD
void Settings::printField(const char *label, int value) {
char buf[21];
display_.clear();
// Linia 0: nazwa pola
snprintf(buf, sizeof(buf), "%s:", label);
display_.text(0, buf);
// Linia 1: wartość
snprintf(buf, sizeof(buf), "%d", value);
display_.text(1, buf);
// Linia 3: podpowiedź
display_.textStatus("UP/DOWN, OK=Next");
ESP_LOGI(TAG_SETTINGS, "%s = %d", label, value);
}

View File

@@ -0,0 +1,48 @@
#include <Tool.h>
static const char *TOOL = "tool";
bool isI2CDevPresent(uint8_t address) {
Wire.beginTransmission(address);
uint8_t error = Wire.endTransmission();
if (error == 0) {
ESP_LOGI(TOOL, "I2C response from 0x%02X", address);
return true;
} else {
if (error == 4) {
ESP_LOGW(TOOL, "I2C unknown error at 0x%02X", address);
} else {
ESP_LOGI(TOOL, "No I2C device at 0x%02X", address);
}
return false;
}
}
void scanI2C() {
byte error, address;
int nDevices = 0;
ESP_LOGI(TOOL, "I2C start scan");
for (address = 1; address < 127; address++) {
Wire.beginTransmission(address);
error = Wire.endTransmission();
if (error == 0) {
char buf[32];
snprintf(buf, sizeof(buf), "I2C device: 0x%02X", address);
ESP_LOGI(TOOL, "%s", buf);
nDevices++;
Watchdog::feed();
}
else if (error == 4) {
ESP_LOGW(TOOL, "I2C error at address: 0x%02X", address);
}
}
if (nDevices == 0) {
ESP_LOGE(TOOL, "I2C no devices found");
} else {
ESP_LOGI(TOOL, "I2C scan finished. Found %d device(s)", nDevices);
}
Watchdog::feed();
}

View File

@@ -0,0 +1,117 @@
#include "UploadManager.h"
#include "Watchdog.h"
static const char* TAG_UPLOAD = "UPLOAD";
static const char* LOG_FILE = "/uploaded.csv";
UploadManager::UploadManager(APIClient& client, RTC_DS3231& rtc, DataCapture& capture)
: apiClient(client), rtc_(rtc), capture_(capture) {}
String UploadManager::getCurrentTimestamp() {
DateTime now = rtc_.now();
char buf[25];
snprintf(buf, sizeof(buf), "%04u-%02u-%02u %02u:%02u:%02u",
now.year(), now.month(), now.day(), now.hour(), now.minute(), now.second());
return String(buf);
}
void UploadManager::appendLog(const String& filePath, const String& status) {
File f = SD.open(LOG_FILE, FILE_APPEND);
if (f) {
f.printf("%s,%s,%s\n", getCurrentTimestamp().c_str(), filePath.c_str(), status.c_str());
f.close();
}
}
bool UploadManager::isAlreadyUploaded(const String& filePath) {
File f = SD.open(LOG_FILE, FILE_READ);
if (!f) return false;
bool found = false;
while (f.available()) {
String line = f.readStringUntil('\n');
line.trim();
if (line.length() == 0) continue;
int firstComma = line.indexOf(',');
if (firstComma < 0) continue;
int secondComma = line.indexOf(',', firstComma + 1);
if (secondComma < 0) continue;
String logPath = line.substring(firstComma + 1, secondComma);
String logStatus = line.substring(secondComma + 1);
if (logPath == filePath) {
if (logStatus == "OK") found = true;
else found = false; // A retry might be needed if last status wasn't OK
}
Watchdog::feed();
}
f.close();
return found;
}
void UploadManager::uploadFile(const String& filePath) {
if (isAlreadyUploaded(filePath)) {
ESP_LOGI(TAG_UPLOAD, "File %s is already uploaded.", filePath.c_str());
return;
}
if (WiFi.status() != WL_CONNECTED) {
ESP_LOGE(TAG_UPLOAD, "No WiFi. Cannot upload %s", filePath.c_str());
appendLog(filePath, "ERROR: No WiFi");
return;
}
bool success = apiClient.uploadMeasurement(filePath);
if (success) {
appendLog(filePath, "OK");
} else {
if (WiFi.status() != WL_CONNECTED) {
appendLog(filePath, "ERROR: WiFi lost during upload");
} else {
appendLog(filePath, "ERROR: Upload Failed");
}
}
}
void UploadManager::processPendingUploads() {
if (WiFi.status() != WL_CONNECTED) return;
ESP_LOGI(TAG_UPLOAD, "Checking for pending uploads...");
uint32_t highestDir = capture_.findHighestNumericDir();
if (highestDir == 0) return;
for (uint32_t d = 1; d <= highestDir; d++) {
String path = capture_.dirPath(d);
File dir = SD.open(path);
if (!dir || !dir.isDirectory()) {
if(dir) dir.close();
continue;
}
for (File f = dir.openNextFile(); f; f = dir.openNextFile()) {
if (f.isDirectory()) { f.close(); continue; }
String childPath = String(f.name());
if (!childPath.startsWith("/")) {
childPath = path + "/" + childPath;
}
if (childPath.endsWith(".wmt")) {
if (!isAlreadyUploaded(childPath)) {
ESP_LOGI(TAG_UPLOAD, "Found pending file: %s", childPath.c_str());
uploadFile(childPath);
delay(1000);
}
}
f.close();
Watchdog::feed();
if (WiFi.status() != WL_CONNECTED) {
dir.close();
return;
}
}
dir.close();
}
ESP_LOGI(TAG_UPLOAD, "Pending uploads check complete.");
}

View File

@@ -0,0 +1,81 @@
#include "Watchdog.h"
// Wykrywanie środowiska
#if defined(ESP_PLATFORM) || defined(ESP32)
#include <esp_task_wdt.h>
#define WDOG_HAS_ESP 1
#else
#define WDOG_HAS_ESP 0
#endif
namespace Watchdog {
static bool s_initialized = false;
bool init(int timeout_seconds, bool panic_on_trigger) {
#if WDOG_HAS_ESP
if (s_initialized) return true;
esp_err_t err = esp_task_wdt_init(timeout_seconds, panic_on_trigger);
if (err == ESP_OK || err == ESP_ERR_INVALID_STATE) {
// ESP_ERR_INVALID_STATE: już zainicjalizowany — traktujemy jako OK
s_initialized = true;
return true;
}
return false;
#else
(void)timeout_seconds; (void)panic_on_trigger;
s_initialized = true; // no-op, aby nie blokować wywołań w kodzie
return true;
#endif
}
bool addThisTask() {
#if WDOG_HAS_ESP
if (!s_initialized) return false;
esp_err_t err = esp_task_wdt_add(nullptr); // nullptr = bieżący task
return (err == ESP_OK || err == ESP_ERR_INVALID_STATE);
#else
return true; // no-op
#endif
}
bool removeThisTask() {
#if WDOG_HAS_ESP
if (!s_initialized) return false;
esp_err_t err = esp_task_wdt_delete(nullptr); // bieżący task
return (err == ESP_OK || err == ESP_ERR_INVALID_STATE);
#else
return true; // no-op
#endif
}
void feed() {
#if WDOG_HAS_ESP
esp_task_wdt_reset();
#else
// no-op
#endif
}
bool setTimeout(int timeout_seconds) {
#if WDOG_HAS_ESP
if (!s_initialized) {
// jeśli ktoś nie zainicjalizował — zrób to teraz
return init(timeout_seconds, true);
}
// W ESP-IDF/Arduino brak prostego API na „live update” — re-init:
esp_err_t err = esp_task_wdt_deinit();
(void)err; // nie każdy port raportuje OK/INVALID_STATE spójnie
s_initialized = false;
return init(timeout_seconds, true);
#else
(void)timeout_seconds;
return true; // no-op
#endif
}
bool isActive() {
return s_initialized;
}
} // namespace Watchdog

View File

@@ -0,0 +1,447 @@
#include <Logger.h>
#include "Watchdog.h"
#include <Arduino.h>
#include <Config.h>
#include <Pinout.h>
#include <Version.h>
#include <Display.h>
#include <SPI.h>
#include <SD.h>
#include "ADXL345FastSPI.h"
#include "RTClib.h"
#include <Wire.h>
#include <Network.h>
#include <Thread.h>
#include <Measure.h>
#include <Tool.h>
#include <Settings.h>
#include "APIClient.h"
#include "UploadManager.h"
#define WDT_TIMEOUT 60 // Czas watchdoga do restartu
#define MAX_ADXL345_SENSORS 4 // Maksymalna ilość podłączanych sensorów
SPIClass SPI_ADXL(FSPI); // SPI2 (VSPI)
SPIClass SPI_SD(HSPI); // SPI3 (HSPI)
float x, y, z = 0; // Dane odczytane z akcelerometru
String name;
bool isRebootRequired = false;
bool isAccelExists = false; // Czy istnieje jakiś podłączony do SPI czujnik
bool testingNow = false; // Czy trwa test (gdy tak, trzeba wszystko inne wyłączyć)
bool runMeasure = false; // czy włączyć pomiar?
// Buttony: 5, 6, 7
// piny SPI CS dla ASXL345
const uint8_t csPins[MAX_ADXL345_SENSORS] = {9, 10, 14, 21};
long licznik = 0;
ConfigManager configManager;
RTC_DS3231 rtc;
ADXL345FastSPI adxl(csPins, MAX_ADXL345_SENSORS);
Display display(rtc, 0x27, 20, 4);
Settings settings(display, rtc); // obsługa przycisków
WiFiManager wifi;
DataCapture capture(adxl, display, rtc, SD, 8192); // NEW !!! MEASURE!!!
APIClient apiClient;
UploadManager uploadManager(apiClient, rtc, capture);
Thread wifiTestThread = Thread(); // Cykliczny test WiFi
Thread offlineThread = Thread(); // Jeśli offline
Thread measureThread = Thread(); // Pomiar i zapis na SD
Thread uploadThread = Thread(); // Background upload
//////// PROTOTYPY /////////////////
void setup();
void loop();
void reboot();
void resetBtnClick();
void checkWiFi();
void showOfflineScreen();
void measure();
void setClockRTCBtn();
/* ******************* SETUP() ************************* */
void setup() {
Serial.begin(115200);
delay(500);
// przy USB-CDC warto poczekać chwilę na enumerację (z timeoutem):
unsigned long t0 = millis();
while (!Serial && millis()-t0 < 2000) { delay(10); }
settings.begin();
esp_log_level_set("*", ESP_LOG_INFO); // _ERROR, _WARN, _INFO, _DEBUG, _VERBOSE
delay(500); // było 1000
ESP_LOGI(TAG_MAIN, "----------------------");
ESP_LOGI(TAG_MAIN, "WMT Stalowa Wola A.Chmielowiec & L.Klich");
ESP_LOGI(TAG_MAIN, "Rejestrator parametrow");
ESP_LOGI(TAG_MAIN, "Firmware: %s", VERSION);
ESP_LOGI(TAG_MAIN, "----------------------");
ESP_LOGI(TAG_MAIN, "ESP32 model: %s Rev %d", ESP.getChipModel(), ESP.getChipRevision());
ESP_LOGI(TAG_MAIN, "Chip cores: %d", ESP.getChipCores());
// Inicjalizacja Watchdoga na 5 sek, panic_on_trigger = true
if (Watchdog::init(25, true)) {
ESP_LOGI(TAG_MAIN, "Watchdog init ok.");
} else {
ESP_LOGE(TAG_MAIN, "Watchdog init error.");
}
Wire.begin(PIN_SDA, PIN_SCL, 100000);
scanI2C(); // Skanowanie magistrali I2C na UART (RTC-0x68, LCD-0x27)
configManager.begin(); // konfiguracja EEPROM urządzenia
//configManager.showConfig();
// Test LCD I2C
if (!isI2CDevPresent(0x27)) {
ESP_LOGE(TAG_MAIN, "LCD 0x27 wire error!");
}
ESP_LOGI(TAG_MAIN, "Display init");
if (!display.begin()) {
ESP_LOGE(TAG_MAIN, "Display init failed");
while (true) delay(1000);
}
ESP_LOGI(TAG_MAIN, "Display OK");
display.welcomeScreen();
delay(1000);
// Przycisk reset w przypadku factory reset
if(settings.isBtnReset()) resetBtnClick();
// MCU Info
ESP_LOGI(TAG_MAIN, "MCU info");
display.textStatus(ESP.getChipModel());
delay(500);
char buf[20];
sprintf(buf, "Freq: %d MHz", ESP.getCpuFreqMHz());
display.textStatus(buf);
delay(500);
// Test PSRAM
display.textStatus("PSRAM:");
ESP_LOGI(TAG_MAIN, "PSRAM init");
if (!psramFound()) {
ESP_LOGE(TAG_MAIN, "PSRAM not found");
display.print("FAILED");
while (true) delay(1000);
}
display.print("OK");
delay(500);
// Test RTC
ESP_LOGI(TAG_MAIN, "RTC test");
if (!isI2CDevPresent(0x68)) {
ESP_LOGE(TAG_MAIN, "RTC 0x68 wire error!");
display.textStatus("RTC wire error!");
while (true) delay(1000);
}
display.textStatus("RTC:");
ESP_LOGI(TAG_MAIN, "RTC init");
if (!rtc.begin()) {
display.print("FAILED");
ESP_LOGE(TAG_MAIN, "Can't find RTC");
while (true) delay(1000);
} else {
display.print("OK");
ESP_LOGI(TAG_MAIN, "RTC OK");
if (rtc.lostPower()) {
ESP_LOGE(TAG_MAIN, "RTC power lost! Set clock");
display.textStatus("RTC: SET TIME");
delay(1000);
settings.setTimeRTC();
}
}
// Przycisk ustawianai czasu: przytrzymaj OK przy starcie systemu
if(settings.isSetClock()) setClockRTCBtn();
delay(500);
// Karta SD
ESP_LOGI(TAG_MAIN, "SD Card init");
ESP_LOGI(TAG_MAIN, "SPI2 SD SCK: %d, MISO: %d, MOSI: %d, CS: %d", SD_SCK, SD_MISO, SD_MOSI, SD_CS);
SPI_SD.begin(SD_SCK, SD_MISO, SD_MOSI);
display.textStatus("SD CARD:");
while(!SD.begin(SD_CS, SPI_SD, 4000000)) { // 10 MHz na start; w razie czego 4 MHz (bez tego często SD Failed)
ESP_LOGE(TAG_MAIN, "SD mount failed");
display.print("FAILED");
delay(4000);
display.textStatus("SD CARD:");
delay(500);
}
ESP_LOGI(TAG_MAIN, "SD Card OK");
display.print("OK");
ESP_LOGI(TAG_MAIN, "ADXL345 SPI3 SCK: %d, MISO: %d, MOSI: %d", CLK_ADSX, MISO_ADSX, MOSI_ADSX);
SPI_ADXL.begin(CLK_ADSX, MISO_ADSX, MOSI_ADSX);
// Inicjalizacja ADXL345
ESP_LOGI(TAG_MAIN, "ADXL345 init");
display.textStatus("ADXL345: ");
//if (!adxl.begin(&SPI, 5000000, ADXL345FastSPI::RATE_3200HZ, ADXL345FastSPI::RANGE_16G, 1)) {
if(!adxl.begin(&SPI_ADXL, 5000000, ADXL345FastSPI::RATE_3200HZ, ADXL345FastSPI::RANGE_16G, 1)){
ESP_LOGE(TAG_MAIN, "ADXL345 Error");
display.print("FAILED");
isAccelExists = false;
uint8_t counter = 0;
while(counter<5){ counter++; delay(1000); }
display.clear();
display.textCenter(0, "PLEASE CONNECT");
display.textCenter(1, "SENSOR MODULE");
display.textCenter(2, "ANY KEY RESTART");
display.textCenter(3, "GURU MEDITATION");
while(true){
if(digitalRead(BTN_UP) == LOW) reboot();
if(digitalRead(BTN_OK) == LOW) reboot();
if(digitalRead(BTN_DOWN) == LOW) reboot();
}
} else {
display.print(String(adxl.size()));
ESP_LOGI(TAG_MAIN, "ADXL345 OK: %d COUNT", adxl.size()); // liczba wykrytych
isAccelExists = true;
}
DateTime now = rtc.now();
ESP_LOGI(TAG_MAIN, "RTC TIME: %d:%d:%d %d:%d:%d", now.day(), now.month(), now.year(), now.hour(), now.minute(), now.second());
delay(1000);
if(config.connect){
wifi.begin();
wifiTestThread.onRun(checkWiFi);
wifiTestThread.setInterval(5000); // Test WiFi co 3 sekundy
} else {
offlineThread.onRun(showOfflineScreen);
offlineThread.setInterval(1000); // Jeśli offline, to wyświetl ekran co 1 sek
}
measureThread.onRun(measure);
measureThread.setInterval(config.pause); // Test co X sekund
if(config.connect){
uploadThread.onRun([]() { uploadManager.processPendingUploads(); });
uploadThread.setInterval(60000); // Co 1 minutę sprawdzaj zaległe
}
// Dodanie taska loop do WDT
if (Watchdog::addThisTask()) {
ESP_LOGI(TAG_MAIN, "Added main task to Watchdog");
}
ESP_LOGI(TAG_MAIN, "System ready");
display.clear();
}
// Factory reset
void setClockRTCBtn(){
ESP_LOGI(TAG_MAIN, "SET DATE TIME");
while(settings.isSetClock()){
display.clear();
display.textCenter(0, " PLEASE ");
display.textCenter(1, "release key");
display.textCenter(3, "TO SET DATE TIME");
delay(500);
}
settings.setTimeRTC();
}
// Factory reset
void resetBtnClick(){
uint8_t counter = 10; // ile sekund trzymać przycisk na factory reset
ESP_LOGI(TAG_MAIN, "RESET BTN to 10 sec.");
while(settings.isBtnReset()){
Serial.print(counter); Serial.print(",");
counter--;
delay(1000);
display.clear();
display.textCenter(0,"WARNING!!!");
display.textCenter(1, "Factory reset");
display.textCenter(2, "keep holding for");
display.textStatus(String(counter).c_str());
if (counter <= 1) {
display.clear();
display.textCenter(0, "RESET CONFIG");
display.textCenter(1, "release key");
ESP_LOGI(TAG_MAIN, "RESET CONFIG");
while(settings.isBtnReset()){;}
configManager.resetToDefaults();
display.textStatus("RESTARTING!");
delay(500);
isRebootRequired = true;
} else {
isRebootRequired = true;
}
}
}
///////////////////////////////////////////////////////////////////////////////////////////////////////////////
void showError(String err){
Watchdog::feed();
display.clear();
display.println(err);
ESP_LOGE(TAG_MAIN, "%s", err.c_str());
delay(3000);
}
void checkWiFi(){
Watchdog::feed();
bool isConnected = WiFi.isConnected();
String ip = WiFi.localIP().toString();
wifi.checkWiFiConnection();
display.updateBarWiFi(WiFi.RSSI(), isConnected);
display.updateNetwork(ip, isConnected);
}
////// Ekran główny tylko gdy brak pomiaru /////////////
void showOfflineScreen(){
if((!config.connect) && (!testingNow)) {
bool isGB;
Watchdog::feed();
float freeSpace = capture.freeSpaceFloat(&isGB);
display.displayOffline(testingNow, adxl.connectedSensorsCount(), capture.freeSpaceFloat(), licznik, false);
}
}
void reboot() {
display.clear();
display.textCenter(1, "SYSTEM");
display.textCenter(2, "RESTARTING");
#if defined(ARDUINO_RASPBERRY_PI_PICO)
watchdog_enable(1, 1); // 1 ms timeout, restart po upływie
while (true); // czekaj na watchdog reset
#endif
#if defined(ARDUINO_ARCH_ESP8266) || defined(ARDUINO_ARCH_ESP32)
ESP_LOGI(TAG_MAIN, "RESTART");
ESP.restart(); // restart ESP
#endif
#if defined(ARDUINO_ARCH_STM32)
NVIC_SystemReset(); // restartuj STM32
#endif
}
// pomiar z Thread START POMIARU TUTAJ
void measure(){
Watchdog::feed();
testingNow = true;
offlineThread.enabled = false;
DateTime now = rtc.now();
ESP_LOGI(TAG_MAIN, "RTC TIME: %d:%d:%d %d:%d:%d", now.day(), now.month(), now.year(), now.hour(), now.minute(), now.second());
char bdate[15]; char btime[15];
snprintf(bdate, sizeof(bdate), "%04d-%02d-%02d", now.year(), now.month(), now.day());
snprintf(btime, sizeof(btime), "%02d:%02d:%02d", now.hour(), now.minute(), now.second());
display.initMeasure(config.measure, testingNow, runMeasure, config.pause, config.duration, config.connect, licznik, bdate, btime);
ESP_LOGI(TAG_MAIN, "MEASURE RUNNING");
//delay(1000);
capture.captureAuto(config.duration, "/");
testingNow = false;
Watchdog::feed();
if(!capture.isExit){
capture.printLastFileInfoSerial();
capture.readHeaderAndPrint(capture.generateNextFilename().c_str());
ESP_LOGI(TAG_MAIN, "MEASURE FINISH");
} else {
ESP_LOGI(TAG_MAIN, "MEASURE INTERRUPT");
//runMeasure = false;
}
if (config.connect && WiFi.status() == WL_CONNECTED) {
ESP_LOGI(TAG_MAIN, "TRIGGER UPLOAD PENDING...");
uploadManager.processPendingUploads();
}
runMeasure = false;
ESP_LOGI(TAG_MAIN, "DISPLAY Offline");
display.displayOffline(testingNow, adxl.connectedSensorsCount(), capture.freeSpaceFloat(), licznik, true);
offlineThread.enabled = true;
}
void toogleMode(){ // tryb ciągłego, pojedynczego pomiaru
config.measure = !config.measure;
configManager.saveConfig();
display.textStatus("Mode changed");
delay(500);
display.textStatus("");
}
void settingsDevice(){
settings.setConfigDevice();
configManager.saveConfig();
settings.finishConfigDevice();
}
///////////// LOOP ////////////////////////////////////////////
void loop() {
if (settings.isPressed(2)){
Watchdog::feed();
if(runMeasure) {
capture.isExit = true;
runMeasure = false;
display.textStatus("Stopped");
delay(3000);
} else runMeasure = true;
if(runMeasure) ESP_LOGI(TAG_MAIN, "BTN MEASURE: START"); else ESP_LOGI(TAG_MAIN, "BTN MEASURE: STOP");
Watchdog::feed();
delay(300);
if (runMeasure) measureThread.run();
}
if(settings.isPressed(3)) settingsDevice(); // DOWN
if(settings.isPressed(1)) toogleMode(); // UP
if(testingNow) {
if((runMeasure) && (settings.isPressed(2))){
runMeasure = false;
display.textStatus("STOPPING. WAIT");
}
return; // jeśli trwa akurat test, nic nie rób
}
if(wifiTestThread.shouldRun() && (config.connect))
wifiTestThread.run();
if(uploadThread.shouldRun() && config.connect)
uploadThread.run();
if(offlineThread.shouldRun() && (!config.connect)){
offlineThread.run();
}
if(!isAccelExists) {
ESP_LOGE(TAG_MAIN, "ADXL module error:halt");
display.clear();
delay(500);
display.textCenter(0, "SENSORS");
display.textCenter(1, "NOT EXISTS");
display.textCenter(2, "SYSTEM STOPPED");
Watchdog::feed();
delay(2000);
} else {
if(measureThread.shouldRun() && (config.measure) && (!runMeasure)){
Watchdog::feed();
ESP_LOGI(TAG_MAIN, "Measure thread run");
measureThread.run();
}
}
if (isRebootRequired) {
ESP_LOGI(TAG_MAIN, "Reboot required");
Watchdog::feed();
delay(1000);
reboot();
}
Watchdog::feed();
delay(20); // 100
licznik ++;
}

View File

@@ -0,0 +1,243 @@
Language: Cpp
# BasedOnStyle: LLVM
AccessModifierOffset: -2
AlignAfterOpenBracket: Align
AlignArrayOfStructures: None
AlignConsecutiveAssignments:
Enabled: false
AcrossEmptyLines: false
AcrossComments: false
AlignCompound: false
AlignFunctionPointers: false
PadOperators: true
AlignConsecutiveBitFields:
Enabled: false
AcrossEmptyLines: false
AcrossComments: false
AlignCompound: false
AlignFunctionPointers: false
PadOperators: false
AlignConsecutiveDeclarations:
Enabled: false
AcrossEmptyLines: false
AcrossComments: false
AlignCompound: false
AlignFunctionPointers: false
PadOperators: false
AlignConsecutiveMacros:
Enabled: false
AcrossEmptyLines: false
AcrossComments: false
AlignCompound: false
AlignFunctionPointers: false
PadOperators: false
AlignConsecutiveShortCaseStatements:
Enabled: false
AcrossEmptyLines: false
AcrossComments: false
AlignCaseColons: false
AlignEscapedNewlines: Right
AlignOperands: Align
AlignTrailingComments:
Kind: Always
OverEmptyLines: 0
AllowAllArgumentsOnNextLine: true
AllowAllParametersOfDeclarationOnNextLine: true
AllowBreakBeforeNoexceptSpecifier: Never
AllowShortBlocksOnASingleLine: Never
AllowShortCaseLabelsOnASingleLine: false
AllowShortCompoundRequirementOnASingleLine: true
AllowShortEnumsOnASingleLine: true
AllowShortFunctionsOnASingleLine: All
AllowShortIfStatementsOnASingleLine: Never
AllowShortLambdasOnASingleLine: All
AllowShortLoopsOnASingleLine: false
AlwaysBreakAfterDefinitionReturnType: None
AlwaysBreakAfterReturnType: None
AlwaysBreakBeforeMultilineStrings: false
AlwaysBreakTemplateDeclarations: MultiLine
AttributeMacros:
- __capability
BinPackArguments: true
BinPackParameters: true
BitFieldColonSpacing: Both
BraceWrapping:
AfterCaseLabel: false
AfterClass: false
AfterControlStatement: Never
AfterEnum: false
AfterExternBlock: false
AfterFunction: false
AfterNamespace: false
AfterObjCDeclaration: false
AfterStruct: false
AfterUnion: false
BeforeCatch: false
BeforeElse: false
BeforeLambdaBody: false
BeforeWhile: false
IndentBraces: false
SplitEmptyFunction: true
SplitEmptyRecord: true
SplitEmptyNamespace: true
BreakAdjacentStringLiterals: true
BreakAfterAttributes: Leave
BreakAfterJavaFieldAnnotations: false
BreakArrays: true
BreakBeforeBinaryOperators: None
BreakBeforeConceptDeclarations: Always
BreakBeforeBraces: Attach
BreakBeforeInlineASMColon: OnlyMultiline
BreakBeforeTernaryOperators: true
BreakConstructorInitializers: BeforeColon
BreakInheritanceList: BeforeColon
BreakStringLiterals: true
ColumnLimit: 80
CommentPragmas: '^ IWYU pragma:'
CompactNamespaces: false
ConstructorInitializerIndentWidth: 4
ContinuationIndentWidth: 4
Cpp11BracedListStyle: true
DerivePointerAlignment: false
DisableFormat: false
EmptyLineAfterAccessModifier: Never
EmptyLineBeforeAccessModifier: LogicalBlock
ExperimentalAutoDetectBinPacking: false
FixNamespaceComments: true
ForEachMacros:
- foreach
- Q_FOREACH
- BOOST_FOREACH
IfMacros:
- KJ_IF_MAYBE
IncludeBlocks: Preserve
IncludeCategories:
- Regex: '^"(llvm|llvm-c|clang|clang-c)/'
Priority: 2
SortPriority: 0
CaseSensitive: false
- Regex: '^(<|"(gtest|gmock|isl|json)/)'
Priority: 3
SortPriority: 0
CaseSensitive: false
- Regex: '.*'
Priority: 1
SortPriority: 0
CaseSensitive: false
IncludeIsMainRegex: '(Test)?$'
IncludeIsMainSourceRegex: ''
IndentAccessModifiers: false
IndentCaseBlocks: false
IndentCaseLabels: false
IndentExternBlock: AfterExternBlock
IndentGotoLabels: true
IndentPPDirectives: None
IndentRequiresClause: true
IndentWidth: 2
IndentWrappedFunctionNames: false
InsertBraces: false
InsertNewlineAtEOF: false
InsertTrailingCommas: None
IntegerLiteralSeparator:
Binary: 0
BinaryMinDigits: 0
Decimal: 0
DecimalMinDigits: 0
Hex: 0
HexMinDigits: 0
JavaScriptQuotes: Leave
JavaScriptWrapImports: true
KeepEmptyLinesAtTheStartOfBlocks: true
KeepEmptyLinesAtEOF: false
LambdaBodyIndentation: Signature
LineEnding: DeriveLF
MacroBlockBegin: ''
MacroBlockEnd: ''
MaxEmptyLinesToKeep: 1
NamespaceIndentation: None
ObjCBinPackProtocolList: Auto
ObjCBlockIndentWidth: 2
ObjCBreakBeforeNestedBlockParam: true
ObjCSpaceAfterProperty: false
ObjCSpaceBeforeProtocolList: true
PackConstructorInitializers: BinPack
PenaltyBreakAssignment: 2
PenaltyBreakBeforeFirstCallParameter: 19
PenaltyBreakComment: 300
PenaltyBreakFirstLessLess: 120
PenaltyBreakOpenParenthesis: 0
PenaltyBreakScopeResolution: 500
PenaltyBreakString: 1000
PenaltyBreakTemplateDeclaration: 10
PenaltyExcessCharacter: 1000000
PenaltyIndentedWhitespace: 0
PenaltyReturnTypeOnItsOwnLine: 60
PointerAlignment: Right
PPIndentWidth: -1
QualifierAlignment: Leave
ReferenceAlignment: Pointer
ReflowComments: true
RemoveBracesLLVM: false
RemoveParentheses: Leave
RemoveSemicolon: false
RequiresClausePosition: OwnLine
RequiresExpressionIndentation: OuterScope
SeparateDefinitionBlocks: Leave
ShortNamespaceLines: 1
SkipMacroDefinitionBody: false
SortIncludes: CaseSensitive
SortJavaStaticImport: Before
SortUsingDeclarations: LexicographicNumeric
SpaceAfterCStyleCast: false
SpaceAfterLogicalNot: false
SpaceAfterTemplateKeyword: true
SpaceAroundPointerQualifiers: Default
SpaceBeforeAssignmentOperators: true
SpaceBeforeCaseColon: false
SpaceBeforeCpp11BracedList: false
SpaceBeforeCtorInitializerColon: true
SpaceBeforeInheritanceColon: true
SpaceBeforeJsonColon: false
SpaceBeforeParens: ControlStatements
SpaceBeforeParensOptions:
AfterControlStatements: true
AfterForeachMacros: true
AfterFunctionDefinitionName: false
AfterFunctionDeclarationName: false
AfterIfMacros: true
AfterOverloadedOperator: false
AfterPlacementOperator: true
AfterRequiresInClause: false
AfterRequiresInExpression: false
BeforeNonEmptyParentheses: false
SpaceBeforeRangeBasedForLoopColon: true
SpaceBeforeSquareBrackets: false
SpaceInEmptyBlock: false
SpacesBeforeTrailingComments: 1
SpacesInAngles: Never
SpacesInContainerLiterals: true
SpacesInLineCommentPrefix:
Minimum: 1
Maximum: -1
SpacesInParens: Never
SpacesInParensOptions:
InCStyleCasts: false
InConditionalStatements: false
InEmptyParentheses: false
Other: false
SpacesInSquareBrackets: false
Standard: Latest
StatementAttributeLikeMacros:
- Q_EMIT
StatementMacros:
- Q_UNUSED
- QT_REQUIRE_VERSION
TabWidth: 8
UseTab: Never
VerilogBreakBetweenInstancePorts: true
WhitespaceSensitiveMacros:
- BOOST_PP_STRINGIZE
- CF_SWIFT_NAME
- NS_SWIFT_NAME
- PP_STRINGIZE
- STRINGIZE

View File

@@ -0,0 +1,46 @@
Thank you for opening an issue on an Adafruit Arduino library repository. To
improve the speed of resolution please review the following guidelines and
common troubleshooting steps below before creating the issue:
- **Do not use GitHub issues for troubleshooting projects and issues.** Instead use
the forums at http://forums.adafruit.com to ask questions and troubleshoot why
something isn't working as expected. In many cases the problem is a common issue
that you will more quickly receive help from the forum community. GitHub issues
are meant for known defects in the code. If you don't know if there is a defect
in the code then start with troubleshooting on the forum first.
- **If following a tutorial or guide be sure you didn't miss a step.** Carefully
check all of the steps and commands to run have been followed. Consult the
forum if you're unsure or have questions about steps in a guide/tutorial.
- **For Arduino projects check these very common issues to ensure they don't apply**:
- For uploading sketches or communicating with the board make sure you're using
a **USB data cable** and **not** a **USB charge-only cable**. It is sometimes
very hard to tell the difference between a data and charge cable! Try using the
cable with other devices or swapping to another cable to confirm it is not
the problem.
- **Be sure you are supplying adequate power to the board.** Check the specs of
your board and plug in an external power supply. In many cases just
plugging a board into your computer is not enough to power it and other
peripherals.
- **Double check all soldering joints and connections.** Flakey connections
cause many mysterious problems. See the [guide to excellent soldering](https://learn.adafruit.com/adafruit-guide-excellent-soldering/tools) for examples of good solder joints.
- **Ensure you are using an official Arduino or Adafruit board.** We can't
guarantee a clone board will have the same functionality and work as expected
with this code and don't support them.
If you're sure this issue is a defect in the code and checked the steps above
please fill in the following fields to provide enough troubleshooting information.
You may delete the guideline and text above to just leave the following details:
- Arduino board: **INSERT ARDUINO BOARD NAME/TYPE HERE**
- Arduino IDE version (found in Arduino -> About Arduino menu): **INSERT ARDUINO
VERSION HERE**
- List the steps to reproduce the problem below (if possible attach a sketch or
copy the sketch code in too): **LIST REPRO STEPS BELOW**

View File

@@ -0,0 +1,26 @@
Thank you for creating a pull request to contribute to Adafruit's GitHub code!
Before you open the request please review the following guidelines and tips to
help it be more easily integrated:
- **Describe the scope of your change--i.e. what the change does and what parts
of the code were modified.** This will help us understand any risks of integrating
the code.
- **Describe any known limitations with your change.** For example if the change
doesn't apply to a supported platform of the library please mention it.
- **Please run any tests or examples that can exercise your modified code.** We
strive to not break users of the code and running tests/examples helps with this
process.
Thank you again for contributing! We will try to test and integrate the change
as soon as we can, but be aware we have many GitHub repositories to manage and
can't immediately respond to every request. There is no need to bump or check in
on a pull request (it will clutter the discussion of the request).
Also don't be worried if the request is closed or not integrated--sometimes the
priorities of Adafruit's GitHub code (education, ease of use) might not match the
priorities of the pull request. Don't fret, the open source community thrives on
forks and GitHub makes it easy to keep your changes in a forked repo.
After reviewing the guidelines above you can delete this text from the pull request.

View File

@@ -0,0 +1,33 @@
name: Arduino Library CI
on: [pull_request, push, repository_dispatch]
jobs:
build:
runs-on: ubuntu-latest
steps:
- uses: actions/setup-python@v4
with:
python-version: '3.x'
- uses: actions/checkout@v3
- uses: actions/checkout@v3
with:
repository: adafruit/ci-arduino
path: ci
- name: Install the prerequisites
run: bash ci/actions_install.sh
- name: Check for correct code formatting with clang-format
run: python3 ci/run-clang-format.py -e "ci/*" -e "bin/*" -r .
- name: Check for correct documentation with doxygen
env:
GH_REPO_TOKEN: ${{ secrets.GH_REPO_TOKEN }}
PRETTYNAME : "Adafruit Bus IO Library"
run: bash ci/doxy_gen_and_deploy.sh
- name: Test the code on supported platforms
run: python3 ci/build_platform.py main_platforms zero feather32u4

View File

@@ -0,0 +1 @@
{"type": "library", "name": "Adafruit BusIO", "version": "1.17.4", "spec": {"owner": "adafruit", "id": 6214, "name": "Adafruit BusIO", "requirements": null, "uri": null}}

View File

@@ -0,0 +1,384 @@
#include <Adafruit_BusIO_Register.h>
#if !defined(SPI_INTERFACES_COUNT) || \
(defined(SPI_INTERFACES_COUNT) && (SPI_INTERFACES_COUNT > 0))
/*!
* @brief Create a register we access over an I2C Device (which defines the
* bus and address)
* @param i2cdevice The I2CDevice to use for underlying I2C access
* @param reg_addr The address pointer value for the I2C/SMBus register, can
* be 8 or 16 bits
* @param width The width of the register data itself, defaults to 1 byte
* @param byteorder The byte order of the register (used when width is > 1),
* defaults to LSBFIRST
* @param address_width The width of the register address itself, defaults
* to 1 byte
*/
Adafruit_BusIO_Register::Adafruit_BusIO_Register(Adafruit_I2CDevice *i2cdevice,
uint16_t reg_addr,
uint8_t width,
uint8_t byteorder,
uint8_t address_width) {
_i2cdevice = i2cdevice;
_spidevice = nullptr;
_addrwidth = address_width;
_address = reg_addr;
_byteorder = byteorder;
_width = width;
}
/*!
* @brief Create a register we access over an SPI Device (which defines the
* bus and CS pin)
* @param spidevice The SPIDevice to use for underlying SPI access
* @param reg_addr The address pointer value for the SPI register, can
* be 8 or 16 bits
* @param type The method we use to read/write data to SPI (which is not
* as well defined as I2C)
* @param width The width of the register data itself, defaults to 1 byte
* @param byteorder The byte order of the register (used when width is > 1),
* defaults to LSBFIRST
* @param address_width The width of the register address itself, defaults
* to 1 byte
*/
Adafruit_BusIO_Register::Adafruit_BusIO_Register(Adafruit_SPIDevice *spidevice,
uint16_t reg_addr,
Adafruit_BusIO_SPIRegType type,
uint8_t width,
uint8_t byteorder,
uint8_t address_width) {
_spidevice = spidevice;
_spiregtype = type;
_i2cdevice = nullptr;
_addrwidth = address_width;
_address = reg_addr;
_byteorder = byteorder;
_width = width;
}
/*!
* @brief Create a register we access over an I2C or SPI Device. This is a
* handy function because we can pass in nullptr for the unused interface,
* allowing libraries to mass-define all the registers
* @param i2cdevice The I2CDevice to use for underlying I2C access, if
* nullptr we use SPI
* @param spidevice The SPIDevice to use for underlying SPI access, if
* nullptr we use I2C
* @param reg_addr The address pointer value for the I2C/SMBus/SPI register,
* can be 8 or 16 bits
* @param type The method we use to read/write data to SPI (which is not
* as well defined as I2C)
* @param width The width of the register data itself, defaults to 1 byte
* @param byteorder The byte order of the register (used when width is > 1),
* defaults to LSBFIRST
* @param address_width The width of the register address itself, defaults
* to 1 byte
*/
Adafruit_BusIO_Register::Adafruit_BusIO_Register(
Adafruit_I2CDevice *i2cdevice, Adafruit_SPIDevice *spidevice,
Adafruit_BusIO_SPIRegType type, uint16_t reg_addr, uint8_t width,
uint8_t byteorder, uint8_t address_width) {
_spidevice = spidevice;
_i2cdevice = i2cdevice;
_spiregtype = type;
_addrwidth = address_width;
_address = reg_addr;
_byteorder = byteorder;
_width = width;
}
/*!
* @brief Create a register we access over a GenericDevice
* @param genericdevice Generic device to use
* @param reg_addr Register address we will read/write
* @param width Width of the register in bytes (1-4)
* @param byteorder Byte order of register data (LSBFIRST or MSBFIRST)
* @param address_width Width of the register address in bytes (1 or 2)
*/
Adafruit_BusIO_Register::Adafruit_BusIO_Register(
Adafruit_GenericDevice *genericdevice, uint16_t reg_addr, uint8_t width,
uint8_t byteorder, uint8_t address_width) {
_i2cdevice = nullptr;
_spidevice = nullptr;
_genericdevice = genericdevice;
_addrwidth = address_width;
_address = reg_addr;
_byteorder = byteorder;
_width = width;
}
/*!
* @brief Write a buffer of data to the register location
* @param buffer Pointer to data to write
* @param len Number of bytes to write
* @return True on successful write (only really useful for I2C as SPI is
* uncheckable)
*/
bool Adafruit_BusIO_Register::write(uint8_t *buffer, uint8_t len) {
uint8_t addrbuffer[2] = {(uint8_t)(_address & 0xFF),
(uint8_t)(_address >> 8)};
if (_i2cdevice) {
return _i2cdevice->write(buffer, len, true, addrbuffer, _addrwidth);
}
if (_spidevice) {
if (_spiregtype == ADDRESSED_OPCODE_BIT0_LOW_TO_WRITE) {
// very special case!
// pass the special opcode address which we set as the high byte of the
// regaddr
addrbuffer[0] =
(uint8_t)(_address >> 8) & ~0x01; // set bottom bit low to write
// the 'actual' reg addr is the second byte then
addrbuffer[1] = (uint8_t)(_address & 0xFF);
// the address appears to be a byte longer
return _spidevice->write(buffer, len, addrbuffer, _addrwidth + 1);
}
if (_spiregtype == ADDRBIT8_HIGH_TOREAD) {
addrbuffer[0] &= ~0x80;
}
if (_spiregtype == ADDRBIT8_HIGH_TOWRITE) {
addrbuffer[0] |= 0x80;
}
if (_spiregtype == AD8_HIGH_TOREAD_AD7_HIGH_TOINC) {
addrbuffer[0] &= ~0x80;
addrbuffer[0] |= 0x40;
}
return _spidevice->write(buffer, len, addrbuffer, _addrwidth);
}
if (_genericdevice) {
return _genericdevice->writeRegister(addrbuffer, _addrwidth, buffer, len);
}
return false;
}
/*!
* @brief Write up to 4 bytes of data to the register location
* @param value Data to write
* @param numbytes How many bytes from 'value' to write
* @return True on successful write (only really useful for I2C as SPI is
* uncheckable)
*/
bool Adafruit_BusIO_Register::write(uint32_t value, uint8_t numbytes) {
if (numbytes == 0) {
numbytes = _width;
}
if (numbytes > 4) {
return false;
}
// store a copy
_cached = value;
for (int i = 0; i < numbytes; i++) {
if (_byteorder == LSBFIRST) {
_buffer[i] = value & 0xFF;
} else {
_buffer[numbytes - i - 1] = value & 0xFF;
}
value >>= 8;
}
return write(_buffer, numbytes);
}
/*!
* @brief Read data from the register location. This does not do any error
* checking!
* @return Returns 0xFFFFFFFF on failure, value otherwise
*/
uint32_t Adafruit_BusIO_Register::read(void) {
if (!read(_buffer, _width)) {
return -1;
}
uint32_t value = 0;
for (int i = 0; i < _width; i++) {
value <<= 8;
if (_byteorder == LSBFIRST) {
value |= _buffer[_width - i - 1];
} else {
value |= _buffer[i];
}
}
return value;
}
/*!
* @brief Read cached data from last time we wrote to this register
* @return Returns 0xFFFFFFFF on failure, value otherwise
*/
uint32_t Adafruit_BusIO_Register::readCached(void) { return _cached; }
/*!
@brief Read a number of bytes from a register into a buffer
@param buffer Buffer to read data into
@param len Number of bytes to read into the buffer
@return true on successful read, otherwise false
*/
bool Adafruit_BusIO_Register::read(uint8_t *buffer, uint8_t len) {
uint8_t addrbuffer[2] = {(uint8_t)(_address & 0xFF),
(uint8_t)(_address >> 8)};
if (_i2cdevice) {
return _i2cdevice->write_then_read(addrbuffer, _addrwidth, buffer, len);
}
if (_spidevice) {
if (_spiregtype == ADDRESSED_OPCODE_BIT0_LOW_TO_WRITE) {
// very special case!
// pass the special opcode address which we set as the high byte of the
// regaddr
addrbuffer[0] =
(uint8_t)(_address >> 8) | 0x01; // set bottom bit high to read
// the 'actual' reg addr is the second byte then
addrbuffer[1] = (uint8_t)(_address & 0xFF);
// the address appears to be a byte longer
return _spidevice->write_then_read(addrbuffer, _addrwidth + 1, buffer,
len);
}
if (_spiregtype == ADDRBIT8_HIGH_TOREAD) {
addrbuffer[0] |= 0x80;
}
if (_spiregtype == ADDRBIT8_HIGH_TOWRITE) {
addrbuffer[0] &= ~0x80;
}
if (_spiregtype == AD8_HIGH_TOREAD_AD7_HIGH_TOINC) {
addrbuffer[0] |= 0x80 | 0x40;
}
return _spidevice->write_then_read(addrbuffer, _addrwidth, buffer, len);
}
if (_genericdevice) {
return _genericdevice->readRegister(addrbuffer, _addrwidth, buffer, len);
}
return false;
}
/*!
* @brief Read 2 bytes of data from the register location
* @param value Pointer to uint16_t variable to read into
* @return True on successful write (only really useful for I2C as SPI is
* uncheckable)
*/
bool Adafruit_BusIO_Register::read(uint16_t *value) {
if (!read(_buffer, 2)) {
return false;
}
if (_byteorder == LSBFIRST) {
*value = _buffer[1];
*value <<= 8;
*value |= _buffer[0];
} else {
*value = _buffer[0];
*value <<= 8;
*value |= _buffer[1];
}
return true;
}
/*!
* @brief Read 1 byte of data from the register location
* @param value Pointer to uint8_t variable to read into
* @return True on successful write (only really useful for I2C as SPI is
* uncheckable)
*/
bool Adafruit_BusIO_Register::read(uint8_t *value) {
if (!read(_buffer, 1)) {
return false;
}
*value = _buffer[0];
return true;
}
/*!
* @brief Pretty printer for this register
* @param s The Stream to print to, defaults to &Serial
*/
void Adafruit_BusIO_Register::print(Stream *s) {
uint32_t val = read();
s->print("0x");
s->print(val, HEX);
}
/*!
* @brief Pretty printer for this register
* @param s The Stream to print to, defaults to &Serial
*/
void Adafruit_BusIO_Register::println(Stream *s) {
print(s);
s->println();
}
/*!
* @brief Create a slice of the register that we can address without
* touching other bits
* @param reg The Adafruit_BusIO_Register which defines the bus/register
* @param bits The number of bits wide we are slicing
* @param shift The number of bits that our bit-slice is shifted from LSB
*/
Adafruit_BusIO_RegisterBits::Adafruit_BusIO_RegisterBits(
Adafruit_BusIO_Register *reg, uint8_t bits, uint8_t shift) {
_register = reg;
_bits = bits;
_shift = shift;
}
/*!
* @brief Read 4 bytes of data from the register
* @return data The 4 bytes to read
*/
uint32_t Adafruit_BusIO_RegisterBits::read(void) {
uint32_t val = _register->read();
val >>= _shift;
return val & ((1 << (_bits)) - 1);
}
/*!
* @brief Write 4 bytes of data to the register
* @param data The 4 bytes to write
* @return True on successful write (only really useful for I2C as SPI is
* uncheckable)
*/
bool Adafruit_BusIO_RegisterBits::write(uint32_t data) {
uint32_t val = _register->read();
// mask off the data before writing
uint32_t mask = (1 << (_bits)) - 1;
data &= mask;
mask <<= _shift;
val &= ~mask; // remove the current data at that spot
val |= data << _shift; // and add in the new data
return _register->write(val, _register->width());
}
/*!
* @brief The width of the register data, helpful for doing calculations
* @returns The data width used when initializing the register
*/
uint8_t Adafruit_BusIO_Register::width(void) { return _width; }
/*!
* @brief Set the default width of data
* @param width the default width of data read from register
*/
void Adafruit_BusIO_Register::setWidth(uint8_t width) { _width = width; }
/*!
* @brief Set register address
* @param address the address from register
*/
void Adafruit_BusIO_Register::setAddress(uint16_t address) {
_address = address;
}
/*!
* @brief Set the width of register address
* @param address_width the width for register address
*/
void Adafruit_BusIO_Register::setAddressWidth(uint16_t address_width) {
_addrwidth = address_width;
}
#endif // SPI exists

View File

@@ -0,0 +1,117 @@
#ifndef Adafruit_BusIO_Register_h
#define Adafruit_BusIO_Register_h
#include <Arduino.h>
#if !defined(SPI_INTERFACES_COUNT) || \
(defined(SPI_INTERFACES_COUNT) && (SPI_INTERFACES_COUNT > 0))
#include <Adafruit_GenericDevice.h>
#include <Adafruit_I2CDevice.h>
#include <Adafruit_SPIDevice.h>
typedef enum _Adafruit_BusIO_SPIRegType {
ADDRBIT8_HIGH_TOREAD = 0,
/*!<
* ADDRBIT8_HIGH_TOREAD
* When reading a register you must actually send the value 0x80 + register
* address to the device. e.g. To read the register 0x0B the register value
* 0x8B is sent and to write 0x0B is sent.
*/
AD8_HIGH_TOREAD_AD7_HIGH_TOINC = 1,
/*!<
* ADDRBIT8_HIGH_TOWRITE
* When writing to a register you must actually send the value 0x80 +
* the register address to the device. e.g. To write to the register 0x19 the
* register value 0x99 is sent and to read 0x19 is sent.
*/
ADDRBIT8_HIGH_TOWRITE = 2,
/*!<
* ADDRESSED_OPCODE_LOWBIT_TO_WRITE
* Used by the MCP23S series, we send 0x40 |'rd with the opcode
* Then set the lowest bit to write
*/
ADDRESSED_OPCODE_BIT0_LOW_TO_WRITE = 3,
} Adafruit_BusIO_SPIRegType;
/*!
* @brief The class which defines a device register (a location to read/write
* data from)
*/
class Adafruit_BusIO_Register {
public:
Adafruit_BusIO_Register(Adafruit_I2CDevice *i2cdevice, uint16_t reg_addr,
uint8_t width = 1, uint8_t byteorder = LSBFIRST,
uint8_t address_width = 1);
Adafruit_BusIO_Register(Adafruit_SPIDevice *spidevice, uint16_t reg_addr,
Adafruit_BusIO_SPIRegType type, uint8_t width = 1,
uint8_t byteorder = LSBFIRST,
uint8_t address_width = 1);
Adafruit_BusIO_Register(Adafruit_I2CDevice *i2cdevice,
Adafruit_SPIDevice *spidevice,
Adafruit_BusIO_SPIRegType type, uint16_t reg_addr,
uint8_t width = 1, uint8_t byteorder = LSBFIRST,
uint8_t address_width = 1);
Adafruit_BusIO_Register(Adafruit_GenericDevice *genericdevice,
uint16_t reg_addr, uint8_t width = 1,
uint8_t byteorder = LSBFIRST,
uint8_t address_width = 1);
bool read(uint8_t *buffer, uint8_t len);
bool read(uint8_t *value);
bool read(uint16_t *value);
uint32_t read(void);
uint32_t readCached(void);
bool write(uint8_t *buffer, uint8_t len);
bool write(uint32_t value, uint8_t numbytes = 0);
uint8_t width(void);
void setWidth(uint8_t width);
void setAddress(uint16_t address);
void setAddressWidth(uint16_t address_width);
#if !defined(NO_GLOBAL_INSTANCES) && !defined(NO_GLOBAL_SERIAL)
void print(Stream *s = &Serial);
void println(Stream *s = &Serial);
#else
void print(Stream *s);
void println(Stream *s);
#endif
private:
Adafruit_I2CDevice *_i2cdevice;
Adafruit_SPIDevice *_spidevice;
Adafruit_GenericDevice *_genericdevice;
Adafruit_BusIO_SPIRegType _spiregtype;
uint16_t _address;
uint8_t _width, _addrwidth, _byteorder;
uint8_t _buffer[4]; // we won't support anything larger than uint32 for
// non-buffered read
uint32_t _cached = 0;
};
/*!
* @brief The class which defines a slice of bits from within a device register
* (a location to read/write data from)
*/
class Adafruit_BusIO_RegisterBits {
public:
Adafruit_BusIO_RegisterBits(Adafruit_BusIO_Register *reg, uint8_t bits,
uint8_t shift);
bool write(uint32_t value);
uint32_t read(void);
private:
Adafruit_BusIO_Register *_register;
uint8_t _bits, _shift;
};
#endif // SPI exists
#endif // BusIO_Register_h

View File

@@ -0,0 +1,90 @@
/*
Written with help by Claude!
https://claude.ai/chat/335f50b1-3dd8-435e-9139-57ec7ca26a3c (at this time
chats are not shareable :(
*/
#include "Adafruit_GenericDevice.h"
/*!
* @brief Create a Generic device with the provided read/write functions
* @param obj Pointer to object instance
* @param read_func Function pointer for reading raw data
* @param write_func Function pointer for writing raw data
* @param readreg_func Function pointer for reading registers (optional)
* @param writereg_func Function pointer for writing registers (optional) */
Adafruit_GenericDevice::Adafruit_GenericDevice(
void *obj, busio_genericdevice_read_t read_func,
busio_genericdevice_write_t write_func,
busio_genericdevice_readreg_t readreg_func,
busio_genericdevice_writereg_t writereg_func) {
_obj = obj;
_read_func = read_func;
_write_func = write_func;
_readreg_func = readreg_func;
_writereg_func = writereg_func;
_begun = false;
}
/*! @brief Simple begin function (doesn't do much at this time)
@return true always
*/
bool Adafruit_GenericDevice::begin(void) {
_begun = true;
return true;
}
/*!
@brief Marks the GenericDevice as no longer in use.
@note: Since this is a GenericDevice, if you are using this with a Serial
object, this does NOT disable serial communication or release the RX/TX pins.
That must be done manually by calling Serial.end().
*/
void Adafruit_GenericDevice::end(void) { _begun = false; }
/*! @brief Write a buffer of data
@param buffer Pointer to buffer of data to write
@param len Number of bytes to write
@return true if write was successful, otherwise false */
bool Adafruit_GenericDevice::write(const uint8_t *buffer, size_t len) {
if (!_begun)
return false;
return _write_func(_obj, buffer, len);
}
/*! @brief Read data into a buffer
@param buffer Pointer to buffer to read data into
@param len Number of bytes to read
@return true if read was successful, otherwise false */
bool Adafruit_GenericDevice::read(uint8_t *buffer, size_t len) {
if (!_begun)
return false;
return _read_func(_obj, buffer, len);
}
/*! @brief Read from a register location
@param addr_buf Buffer containing register address
@param addrsiz Size of register address in bytes
@param buf Buffer to store read data
@param bufsiz Size of data to read in bytes
@return true if read was successful, otherwise false */
bool Adafruit_GenericDevice::readRegister(uint8_t *addr_buf, uint8_t addrsiz,
uint8_t *buf, uint16_t bufsiz) {
if (!_begun || !_readreg_func)
return false;
return _readreg_func(_obj, addr_buf, addrsiz, buf, bufsiz);
}
/*! @brief Write to a register location
@param addr_buf Buffer containing register address
@param addrsiz Size of register address in bytes
@param buf Buffer containing data to write
@param bufsiz Size of data to write in bytes
@return true if write was successful, otherwise false */
bool Adafruit_GenericDevice::writeRegister(uint8_t *addr_buf, uint8_t addrsiz,
const uint8_t *buf,
uint16_t bufsiz) {
if (!_begun || !_writereg_func)
return false;
return _writereg_func(_obj, addr_buf, addrsiz, buf, bufsiz);
}

View File

@@ -0,0 +1,56 @@
#ifndef ADAFRUIT_GENERICDEVICE_H
#define ADAFRUIT_GENERICDEVICE_H
#include <Arduino.h>
typedef bool (*busio_genericdevice_read_t)(void *obj, uint8_t *buffer,
size_t len);
typedef bool (*busio_genericdevice_write_t)(void *obj, const uint8_t *buffer,
size_t len);
typedef bool (*busio_genericdevice_readreg_t)(void *obj, uint8_t *addr_buf,
uint8_t addrsiz, uint8_t *data,
uint16_t datalen);
typedef bool (*busio_genericdevice_writereg_t)(void *obj, uint8_t *addr_buf,
uint8_t addrsiz,
const uint8_t *data,
uint16_t datalen);
/*!
* @brief Class for communicating with a device via generic read/write functions
*/
class Adafruit_GenericDevice {
public:
Adafruit_GenericDevice(
void *obj, busio_genericdevice_read_t read_func,
busio_genericdevice_write_t write_func,
busio_genericdevice_readreg_t readreg_func = nullptr,
busio_genericdevice_writereg_t writereg_func = nullptr);
bool begin(void);
void end(void);
bool read(uint8_t *buffer, size_t len);
bool write(const uint8_t *buffer, size_t len);
bool readRegister(uint8_t *addr_buf, uint8_t addrsiz, uint8_t *buf,
uint16_t bufsiz);
bool writeRegister(uint8_t *addr_buf, uint8_t addrsiz, const uint8_t *buf,
uint16_t bufsiz);
protected:
/*! @brief Function pointer for reading raw data from the device */
busio_genericdevice_read_t _read_func;
/*! @brief Function pointer for writing raw data to the device */
busio_genericdevice_write_t _write_func;
/*! @brief Function pointer for reading a 'register' from the device */
busio_genericdevice_readreg_t _readreg_func;
/*! @brief Function pointer for writing a 'register' to the device */
busio_genericdevice_writereg_t _writereg_func;
bool _begun; ///< whether we have initialized yet (in case the function needs
///< to do something)
private:
void *_obj; ///< Pointer to object instance
};
#endif // ADAFRUIT_GENERICDEVICE_H

View File

@@ -0,0 +1,320 @@
#include "Adafruit_I2CDevice.h"
// #define DEBUG_SERIAL Serial
/*!
* @brief Create an I2C device at a given address
* @param addr The 7-bit I2C address for the device
* @param theWire The I2C bus to use, defaults to &Wire
*/
Adafruit_I2CDevice::Adafruit_I2CDevice(uint8_t addr, TwoWire *theWire) {
_addr = addr;
_wire = theWire;
_begun = false;
#ifdef ARDUINO_ARCH_SAMD
_maxBufferSize = 250; // as defined in Wire.h's RingBuffer
#elif defined(ESP32)
_maxBufferSize = I2C_BUFFER_LENGTH;
#else
_maxBufferSize = 32;
#endif
}
/*!
* @brief Initializes and does basic address detection
* @param addr_detect Whether we should attempt to detect the I2C address
* with a scan. 99% of sensors/devices don't mind, but once in a while they
* don't respond well to a scan!
* @return True if I2C initialized and a device with the addr found
*/
bool Adafruit_I2CDevice::begin(bool addr_detect) {
_wire->begin();
_begun = true;
if (addr_detect) {
return detected();
}
return true;
}
/*!
* @brief De-initialize device, turn off the Wire interface
*/
void Adafruit_I2CDevice::end(void) {
// Not all port implement Wire::end(), such as
// - ESP8266
// - AVR core without WIRE_HAS_END
// - ESP32: end() is implemented since 2.0.1 which is latest at the moment.
// Temporarily disable for now to give time for user to update.
#if !(defined(ESP8266) || \
(defined(ARDUINO_ARCH_AVR) && !defined(WIRE_HAS_END)) || \
defined(ARDUINO_ARCH_ESP32))
_wire->end();
_begun = false;
#endif
}
/*!
* @brief Scans I2C for the address - note will give a false-positive
* if there's no pullups on I2C
* @return True if I2C initialized and a device with the addr found
*/
bool Adafruit_I2CDevice::detected(void) {
// Init I2C if not done yet
if (!_begun && !begin()) {
return false;
}
// A basic scanner, see if it ACK's
_wire->beginTransmission(_addr);
#ifdef DEBUG_SERIAL
DEBUG_SERIAL.print(F("Address 0x"));
DEBUG_SERIAL.print(_addr, HEX);
#endif
#ifdef ARDUINO_ARCH_MBED
_wire->write(0); // forces a write request instead of a read
#endif
if (_wire->endTransmission() == 0) {
#ifdef DEBUG_SERIAL
DEBUG_SERIAL.println(F(" Detected"));
#endif
return true;
}
#ifdef DEBUG_SERIAL
DEBUG_SERIAL.println(F(" Not detected"));
#endif
return false;
}
/*!
* @brief Write a buffer or two to the I2C device. Cannot be more than
* maxBufferSize() bytes.
* @param buffer Pointer to buffer of data to write. This is const to
* ensure the content of this buffer doesn't change.
* @param len Number of bytes from buffer to write
* @param prefix_buffer Pointer to optional array of data to write before
* buffer. Cannot be more than maxBufferSize() bytes. This is const to
* ensure the content of this buffer doesn't change.
* @param prefix_len Number of bytes from prefix buffer to write
* @param stop Whether to send an I2C STOP signal on write
* @return True if write was successful, otherwise false.
*/
bool Adafruit_I2CDevice::write(const uint8_t *buffer, size_t len, bool stop,
const uint8_t *prefix_buffer,
size_t prefix_len) {
if ((len + prefix_len) > maxBufferSize()) {
// currently not guaranteed to work if more than 32 bytes!
// we will need to find out if some platforms have larger
// I2C buffer sizes :/
#ifdef DEBUG_SERIAL
DEBUG_SERIAL.println(F("\tI2CDevice could not write such a large buffer"));
#endif
return false;
}
_wire->beginTransmission(_addr);
// Write the prefix data (usually an address)
if ((prefix_len != 0) && (prefix_buffer != nullptr)) {
if (_wire->write(prefix_buffer, prefix_len) != prefix_len) {
#ifdef DEBUG_SERIAL
DEBUG_SERIAL.println(F("\tI2CDevice failed to write"));
#endif
return false;
}
}
// Write the data itself
if (_wire->write(buffer, len) != len) {
#ifdef DEBUG_SERIAL
DEBUG_SERIAL.println(F("\tI2CDevice failed to write"));
#endif
return false;
}
#ifdef DEBUG_SERIAL
DEBUG_SERIAL.print(F("\tI2CWRITE @ 0x"));
DEBUG_SERIAL.print(_addr, HEX);
DEBUG_SERIAL.print(F(" :: "));
if ((prefix_len != 0) && (prefix_buffer != nullptr)) {
for (uint16_t i = 0; i < prefix_len; i++) {
DEBUG_SERIAL.print(F("0x"));
DEBUG_SERIAL.print(prefix_buffer[i], HEX);
DEBUG_SERIAL.print(F(", "));
}
}
for (uint16_t i = 0; i < len; i++) {
DEBUG_SERIAL.print(F("0x"));
DEBUG_SERIAL.print(buffer[i], HEX);
DEBUG_SERIAL.print(F(", "));
if (i % 32 == 31) {
DEBUG_SERIAL.println();
}
}
if (stop) {
DEBUG_SERIAL.print("\tSTOP");
}
#endif
if (_wire->endTransmission(stop) == 0) {
#ifdef DEBUG_SERIAL
DEBUG_SERIAL.println();
// DEBUG_SERIAL.println("Sent!");
#endif
return true;
} else {
#ifdef DEBUG_SERIAL
DEBUG_SERIAL.println("\tFailed to send!");
#endif
return false;
}
}
/*!
* @brief Read from I2C into a buffer from the I2C device.
* Cannot be more than maxBufferSize() bytes.
* @param buffer Pointer to buffer of data to read into
* @param len Number of bytes from buffer to read.
* @param stop Whether to send an I2C STOP signal on read
* @return True if read was successful, otherwise false.
*/
bool Adafruit_I2CDevice::read(uint8_t *buffer, size_t len, bool stop) {
size_t pos = 0;
while (pos < len) {
size_t read_len =
((len - pos) > maxBufferSize()) ? maxBufferSize() : (len - pos);
bool read_stop = (pos < (len - read_len)) ? false : stop;
if (!_read(buffer + pos, read_len, read_stop))
return false;
pos += read_len;
}
return true;
}
bool Adafruit_I2CDevice::_read(uint8_t *buffer, size_t len, bool stop) {
#if defined(TinyWireM_h)
size_t recv = _wire->requestFrom((uint8_t)_addr, (uint8_t)len);
#elif defined(ARDUINO_ARCH_MEGAAVR)
size_t recv = _wire->requestFrom(_addr, len, stop);
#else
size_t recv = _wire->requestFrom((uint8_t)_addr, (uint8_t)len, (uint8_t)stop);
#endif
if (recv != len) {
// Not enough data available to fulfill our obligation!
#ifdef DEBUG_SERIAL
DEBUG_SERIAL.print(F("\tI2CDevice did not receive enough data: "));
DEBUG_SERIAL.println(recv);
#endif
return false;
}
for (uint16_t i = 0; i < len; i++) {
buffer[i] = _wire->read();
}
#ifdef DEBUG_SERIAL
DEBUG_SERIAL.print(F("\tI2CREAD @ 0x"));
DEBUG_SERIAL.print(_addr, HEX);
DEBUG_SERIAL.print(F(" :: "));
for (uint16_t i = 0; i < len; i++) {
DEBUG_SERIAL.print(F("0x"));
DEBUG_SERIAL.print(buffer[i], HEX);
DEBUG_SERIAL.print(F(", "));
if (len % 32 == 31) {
DEBUG_SERIAL.println();
}
}
DEBUG_SERIAL.println();
#endif
return true;
}
/*!
* @brief Write some data, then read some data from I2C into another buffer.
* Cannot be more than maxBufferSize() bytes. The buffers can point to
* same/overlapping locations.
* @param write_buffer Pointer to buffer of data to write from
* @param write_len Number of bytes from buffer to write.
* @param read_buffer Pointer to buffer of data to read into.
* @param read_len Number of bytes from buffer to read.
* @param stop Whether to send an I2C STOP signal between the write and read
* @return True if write & read was successful, otherwise false.
*/
bool Adafruit_I2CDevice::write_then_read(const uint8_t *write_buffer,
size_t write_len, uint8_t *read_buffer,
size_t read_len, bool stop) {
if (!write(write_buffer, write_len, stop)) {
return false;
}
return read(read_buffer, read_len);
}
/*!
* @brief Returns the 7-bit address of this device
* @return The 7-bit address of this device
*/
uint8_t Adafruit_I2CDevice::address(void) { return _addr; }
/*!
* @brief Change the I2C clock speed to desired (relies on
* underlying Wire support!
* @param desiredclk The desired I2C SCL frequency
* @return True if this platform supports changing I2C speed.
* Not necessarily that the speed was achieved!
*/
bool Adafruit_I2CDevice::setSpeed(uint32_t desiredclk) {
#if defined(__AVR_ATmega328__) || \
defined(__AVR_ATmega328P__) // fix arduino core set clock
// calculate TWBR correctly
if ((F_CPU / 18) < desiredclk) {
#ifdef DEBUG_SERIAL
Serial.println(F("I2C.setSpeed too high."));
#endif
return false;
}
uint32_t atwbr = ((F_CPU / desiredclk) - 16) / 2;
if (atwbr > 16320) {
#ifdef DEBUG_SERIAL
Serial.println(F("I2C.setSpeed too low."));
#endif
return false;
}
if (atwbr <= 255) {
atwbr /= 1;
TWSR = 0x0;
} else if (atwbr <= 1020) {
atwbr /= 4;
TWSR = 0x1;
} else if (atwbr <= 4080) {
atwbr /= 16;
TWSR = 0x2;
} else { // if (atwbr <= 16320)
atwbr /= 64;
TWSR = 0x3;
}
TWBR = atwbr;
#ifdef DEBUG_SERIAL
Serial.print(F("TWSR prescaler = "));
Serial.println(pow(4, TWSR));
Serial.print(F("TWBR = "));
Serial.println(atwbr);
#endif
return true;
#elif (ARDUINO >= 157) && !defined(ARDUINO_STM32_FEATHER) && \
!defined(TinyWireM_h)
_wire->setClock(desiredclk);
return true;
#else
(void)desiredclk;
return false;
#endif
}

View File

@@ -0,0 +1,36 @@
#ifndef Adafruit_I2CDevice_h
#define Adafruit_I2CDevice_h
#include <Arduino.h>
#include <Wire.h>
///< The class which defines how we will talk to this device over I2C
class Adafruit_I2CDevice {
public:
Adafruit_I2CDevice(uint8_t addr, TwoWire *theWire = &Wire);
uint8_t address(void);
bool begin(bool addr_detect = true);
void end(void);
bool detected(void);
bool read(uint8_t *buffer, size_t len, bool stop = true);
bool write(const uint8_t *buffer, size_t len, bool stop = true,
const uint8_t *prefix_buffer = nullptr, size_t prefix_len = 0);
bool write_then_read(const uint8_t *write_buffer, size_t write_len,
uint8_t *read_buffer, size_t read_len,
bool stop = false);
bool setSpeed(uint32_t desiredclk);
/*! @brief How many bytes we can read in a transaction
* @return The size of the Wire receive/transmit buffer */
size_t maxBufferSize() { return _maxBufferSize; }
private:
uint8_t _addr;
TwoWire *_wire;
bool _begun;
size_t _maxBufferSize;
bool _read(uint8_t *buffer, size_t len, bool stop);
};
#endif // Adafruit_I2CDevice_h

View File

@@ -0,0 +1,10 @@
#ifndef _ADAFRUIT_I2C_REGISTER_H_
#define _ADAFRUIT_I2C_REGISTER_H_
#include <Adafruit_BusIO_Register.h>
#include <Arduino.h>
typedef Adafruit_BusIO_Register Adafruit_I2CRegister;
typedef Adafruit_BusIO_RegisterBits Adafruit_I2CRegisterBits;
#endif

View File

@@ -0,0 +1,512 @@
#include "Adafruit_SPIDevice.h"
// #define DEBUG_SERIAL Serial
#ifdef BUSIO_USE_FAST_PINIO
#define BUSIO_SET_CLOCK_LOW() (*clkPort = *clkPort & ~clkPinMask)
#define BUSIO_SET_CLOCK_HIGH() (*clkPort = *clkPort | clkPinMask)
#define BUSIO_READ_MISO() (*misoPort & misoPinMask)
#define BUSIO_WRITE_MOSI(value) \
do { \
if (value) \
*mosiPort = *mosiPort | mosiPinMask; \
else \
*mosiPort = *mosiPort & ~mosiPinMask; \
} while (0)
#else
#define BUSIO_SET_CLOCK_LOW() digitalWrite(_sck, LOW)
#define BUSIO_SET_CLOCK_HIGH() digitalWrite(_sck, HIGH)
#define BUSIO_READ_MISO() digitalRead(_miso)
#define BUSIO_WRITE_MOSI(value) digitalWrite(_mosi, value)
#endif
/*!
* @brief Create an SPI device with the given CS pin and settings
* @param cspin The arduino pin number to use for chip select
* @param freq The SPI clock frequency to use, defaults to 1MHz
* @param dataOrder The SPI data order to use for bits within each byte,
* defaults to SPI_BITORDER_MSBFIRST
* @param dataMode The SPI mode to use, defaults to SPI_MODE0
* @param theSPI The SPI bus to use, defaults to &theSPI
*/
Adafruit_SPIDevice::Adafruit_SPIDevice(int8_t cspin, uint32_t freq,
BusIOBitOrder dataOrder,
uint8_t dataMode, SPIClass *theSPI) {
#ifdef BUSIO_HAS_HW_SPI
_cs = cspin;
_sck = _mosi = _miso = -1;
_spi = theSPI;
_begun = false;
_spiSetting = new SPISettings(freq, dataOrder, dataMode);
_freq = freq;
_dataOrder = dataOrder;
_dataMode = dataMode;
#else
// unused, but needed to suppress compiler warns
(void)cspin;
(void)freq;
(void)dataOrder;
(void)dataMode;
(void)theSPI;
#endif
}
/*!
* @brief Create an SPI device with the given CS pin and settings
* @param cspin The arduino pin number to use for chip select
* @param sckpin The arduino pin number to use for SCK
* @param misopin The arduino pin number to use for MISO, set to -1 if not
* used
* @param mosipin The arduino pin number to use for MOSI, set to -1 if not
* used
* @param freq The SPI clock frequency to use, defaults to 1MHz
* @param dataOrder The SPI data order to use for bits within each byte,
* defaults to SPI_BITORDER_MSBFIRST
* @param dataMode The SPI mode to use, defaults to SPI_MODE0
*/
Adafruit_SPIDevice::Adafruit_SPIDevice(int8_t cspin, int8_t sckpin,
int8_t misopin, int8_t mosipin,
uint32_t freq, BusIOBitOrder dataOrder,
uint8_t dataMode) {
_cs = cspin;
_sck = sckpin;
_miso = misopin;
_mosi = mosipin;
#ifdef BUSIO_USE_FAST_PINIO
csPort = (BusIO_PortReg *)portOutputRegister(digitalPinToPort(cspin));
csPinMask = digitalPinToBitMask(cspin);
if (mosipin != -1) {
mosiPort = (BusIO_PortReg *)portOutputRegister(digitalPinToPort(mosipin));
mosiPinMask = digitalPinToBitMask(mosipin);
}
if (misopin != -1) {
misoPort = (BusIO_PortReg *)portInputRegister(digitalPinToPort(misopin));
misoPinMask = digitalPinToBitMask(misopin);
}
clkPort = (BusIO_PortReg *)portOutputRegister(digitalPinToPort(sckpin));
clkPinMask = digitalPinToBitMask(sckpin);
#endif
_freq = freq;
_dataOrder = dataOrder;
_dataMode = dataMode;
_begun = false;
}
/*!
* @brief Release memory allocated in constructors
*/
Adafruit_SPIDevice::~Adafruit_SPIDevice() {
if (_spiSetting)
delete _spiSetting;
}
/*!
* @brief Initializes SPI bus and sets CS pin high
* @return Always returns true because there's no way to test success of SPI
* init
*/
bool Adafruit_SPIDevice::begin(void) {
if (_cs != -1) {
pinMode(_cs, OUTPUT);
digitalWrite(_cs, HIGH);
}
if (_spi) { // hardware SPI
#ifdef BUSIO_HAS_HW_SPI
_spi->begin();
#endif
} else {
pinMode(_sck, OUTPUT);
if ((_dataMode == SPI_MODE0) || (_dataMode == SPI_MODE1)) {
// idle low on mode 0 and 1
digitalWrite(_sck, LOW);
} else {
// idle high on mode 2 or 3
digitalWrite(_sck, HIGH);
}
if (_mosi != -1) {
pinMode(_mosi, OUTPUT);
digitalWrite(_mosi, HIGH);
}
if (_miso != -1) {
pinMode(_miso, INPUT);
}
}
_begun = true;
return true;
}
/*!
* @brief Transfer (send/receive) a buffer over hard/soft SPI, without
* transaction management
* @param buffer The buffer to send and receive at the same time
* @param len The number of bytes to transfer
*/
void Adafruit_SPIDevice::transfer(uint8_t *buffer, size_t len) {
//
// HARDWARE SPI
//
if (_spi) {
#ifdef BUSIO_HAS_HW_SPI
#if defined(SPARK)
_spi->transfer(buffer, buffer, len, nullptr);
#elif defined(STM32)
for (size_t i = 0; i < len; i++) {
_spi->transfer(buffer[i]);
}
#else
_spi->transfer(buffer, len);
#endif
return;
#endif
}
//
// SOFTWARE SPI
//
uint8_t startbit;
if (_dataOrder == SPI_BITORDER_LSBFIRST) {
startbit = 0x1;
} else {
startbit = 0x80;
}
bool towrite, lastmosi = !(buffer[0] & startbit);
uint8_t bitdelay_us = (1000000 / _freq) / 2;
for (size_t i = 0; i < len; i++) {
uint8_t reply = 0;
uint8_t send = buffer[i];
/*
Serial.print("\tSending software SPI byte 0x");
Serial.print(send, HEX);
Serial.print(" -> 0x");
*/
// Serial.print(send, HEX);
for (uint8_t b = startbit; b != 0;
b = (_dataOrder == SPI_BITORDER_LSBFIRST) ? b << 1 : b >> 1) {
if (bitdelay_us) {
delayMicroseconds(bitdelay_us);
}
if (_dataMode == SPI_MODE0 || _dataMode == SPI_MODE2) {
towrite = send & b;
if ((_mosi != -1) && (lastmosi != towrite)) {
BUSIO_WRITE_MOSI(towrite);
lastmosi = towrite;
}
BUSIO_SET_CLOCK_HIGH();
if (bitdelay_us) {
delayMicroseconds(bitdelay_us);
}
if (_miso != -1) {
if (BUSIO_READ_MISO())
reply |= b;
}
BUSIO_SET_CLOCK_LOW();
} else if (_dataMode == SPI_MODE3) {
if (_mosi != -1) { // transmit on falling edge
BUSIO_WRITE_MOSI(send & b);
}
BUSIO_SET_CLOCK_LOW();
if (bitdelay_us) {
delayMicroseconds(bitdelay_us);
}
BUSIO_SET_CLOCK_HIGH();
if (bitdelay_us) {
delayMicroseconds(bitdelay_us);
}
if (_miso != -1) { // read on rising edge
if (BUSIO_READ_MISO()) {
reply |= b;
}
}
} else { // || _dataMode == SPI_MODE1)
BUSIO_SET_CLOCK_HIGH();
if (bitdelay_us) {
delayMicroseconds(bitdelay_us);
}
if (_mosi != -1) {
BUSIO_WRITE_MOSI(send & b);
}
BUSIO_SET_CLOCK_LOW();
if (_miso != -1) {
if (BUSIO_READ_MISO()) {
reply |= b;
}
}
}
}
if (_miso != -1) {
buffer[i] = reply;
}
}
return;
}
/*!
* @brief Transfer (send/receive) one byte over hard/soft SPI, without
* transaction management
* @param send The byte to send
* @return The byte received while transmitting
*/
uint8_t Adafruit_SPIDevice::transfer(uint8_t send) {
uint8_t data = send;
transfer(&data, 1);
return data;
}
/*!
* @brief Manually begin a transaction (calls beginTransaction if hardware
* SPI)
*/
void Adafruit_SPIDevice::beginTransaction(void) {
if (_spi) {
#ifdef BUSIO_HAS_HW_SPI
_spi->beginTransaction(*_spiSetting);
#endif
}
}
/*!
* @brief Manually end a transaction (calls endTransaction if hardware SPI)
*/
void Adafruit_SPIDevice::endTransaction(void) {
if (_spi) {
#ifdef BUSIO_HAS_HW_SPI
_spi->endTransaction();
#endif
}
}
/*!
* @brief Assert/Deassert the CS pin if it is defined
* @param value The state the CS is set to
*/
void Adafruit_SPIDevice::setChipSelect(int value) {
if (_cs != -1) {
digitalWrite(_cs, value);
}
}
/*!
* @brief Write a buffer or two to the SPI device, with transaction
* management.
* @brief Manually begin a transaction (calls beginTransaction if hardware
* SPI) with asserting the CS pin
*/
void Adafruit_SPIDevice::beginTransactionWithAssertingCS() {
beginTransaction();
setChipSelect(LOW);
}
/*!
* @brief Manually end a transaction (calls endTransaction if hardware SPI)
* with deasserting the CS pin
*/
void Adafruit_SPIDevice::endTransactionWithDeassertingCS() {
setChipSelect(HIGH);
endTransaction();
}
/*!
* @brief Write a buffer or two to the SPI device, with transaction
* management.
* @param buffer Pointer to buffer of data to write
* @param len Number of bytes from buffer to write
* @param prefix_buffer Pointer to optional array of data to write before
* buffer.
* @param prefix_len Number of bytes from prefix buffer to write
* @return Always returns true because there's no way to test success of SPI
* writes
*/
bool Adafruit_SPIDevice::write(const uint8_t *buffer, size_t len,
const uint8_t *prefix_buffer,
size_t prefix_len) {
beginTransactionWithAssertingCS();
// do the writing
#if defined(ARDUINO_ARCH_ESP32)
if (_spi) {
if (prefix_len > 0) {
_spi->transferBytes((uint8_t *)prefix_buffer, nullptr, prefix_len);
}
if (len > 0) {
_spi->transferBytes((uint8_t *)buffer, nullptr, len);
}
} else
#endif
{
for (size_t i = 0; i < prefix_len; i++) {
transfer(prefix_buffer[i]);
}
for (size_t i = 0; i < len; i++) {
transfer(buffer[i]);
}
}
endTransactionWithDeassertingCS();
#ifdef DEBUG_SERIAL
DEBUG_SERIAL.print(F("\tSPIDevice Wrote: "));
if ((prefix_len != 0) && (prefix_buffer != nullptr)) {
for (uint16_t i = 0; i < prefix_len; i++) {
DEBUG_SERIAL.print(F("0x"));
DEBUG_SERIAL.print(prefix_buffer[i], HEX);
DEBUG_SERIAL.print(F(", "));
}
}
for (uint16_t i = 0; i < len; i++) {
DEBUG_SERIAL.print(F("0x"));
DEBUG_SERIAL.print(buffer[i], HEX);
DEBUG_SERIAL.print(F(", "));
if (i % 32 == 31) {
DEBUG_SERIAL.println();
}
}
DEBUG_SERIAL.println();
#endif
return true;
}
/*!
* @brief Read from SPI into a buffer from the SPI device, with transaction
* management.
* @param buffer Pointer to buffer of data to read into
* @param len Number of bytes from buffer to read.
* @param sendvalue The 8-bits of data to write when doing the data read,
* defaults to 0xFF
* @return Always returns true because there's no way to test success of SPI
* writes
*/
bool Adafruit_SPIDevice::read(uint8_t *buffer, size_t len, uint8_t sendvalue) {
memset(buffer, sendvalue, len); // clear out existing buffer
beginTransactionWithAssertingCS();
transfer(buffer, len);
endTransactionWithDeassertingCS();
#ifdef DEBUG_SERIAL
DEBUG_SERIAL.print(F("\tSPIDevice Read: "));
for (uint16_t i = 0; i < len; i++) {
DEBUG_SERIAL.print(F("0x"));
DEBUG_SERIAL.print(buffer[i], HEX);
DEBUG_SERIAL.print(F(", "));
if (len % 32 == 31) {
DEBUG_SERIAL.println();
}
}
DEBUG_SERIAL.println();
#endif
return true;
}
/*!
* @brief Write some data, then read some data from SPI into another buffer,
* with transaction management. The buffers can point to same/overlapping
* locations. This does not transmit-receive at the same time!
* @param write_buffer Pointer to buffer of data to write from
* @param write_len Number of bytes from buffer to write.
* @param read_buffer Pointer to buffer of data to read into.
* @param read_len Number of bytes from buffer to read.
* @param sendvalue The 8-bits of data to write when doing the data read,
* defaults to 0xFF
* @return Always returns true because there's no way to test success of SPI
* writes
*/
bool Adafruit_SPIDevice::write_then_read(const uint8_t *write_buffer,
size_t write_len, uint8_t *read_buffer,
size_t read_len, uint8_t sendvalue) {
beginTransactionWithAssertingCS();
// do the writing
#if defined(ARDUINO_ARCH_ESP32)
if (_spi) {
if (write_len > 0) {
_spi->transferBytes((uint8_t *)write_buffer, nullptr, write_len);
}
} else
#endif
{
for (size_t i = 0; i < write_len; i++) {
transfer(write_buffer[i]);
}
}
#ifdef DEBUG_SERIAL
DEBUG_SERIAL.print(F("\tSPIDevice Wrote: "));
for (uint16_t i = 0; i < write_len; i++) {
DEBUG_SERIAL.print(F("0x"));
DEBUG_SERIAL.print(write_buffer[i], HEX);
DEBUG_SERIAL.print(F(", "));
if (write_len % 32 == 31) {
DEBUG_SERIAL.println();
}
}
DEBUG_SERIAL.println();
#endif
// do the reading
for (size_t i = 0; i < read_len; i++) {
read_buffer[i] = transfer(sendvalue);
}
#ifdef DEBUG_SERIAL
DEBUG_SERIAL.print(F("\tSPIDevice Read: "));
for (uint16_t i = 0; i < read_len; i++) {
DEBUG_SERIAL.print(F("0x"));
DEBUG_SERIAL.print(read_buffer[i], HEX);
DEBUG_SERIAL.print(F(", "));
if (read_len % 32 == 31) {
DEBUG_SERIAL.println();
}
}
DEBUG_SERIAL.println();
#endif
endTransactionWithDeassertingCS();
return true;
}
/*!
* @brief Write some data and read some data at the same time from SPI
* into the same buffer, with transaction management. This is basicaly a wrapper
* for transfer() with CS-pin and transaction management. This /does/
* transmit-receive at the same time!
* @param buffer Pointer to buffer of data to write/read to/from
* @param len Number of bytes from buffer to write/read.
* @return Always returns true because there's no way to test success of SPI
* writes
*/
bool Adafruit_SPIDevice::write_and_read(uint8_t *buffer, size_t len) {
beginTransactionWithAssertingCS();
transfer(buffer, len);
endTransactionWithDeassertingCS();
return true;
}

View File

@@ -0,0 +1,149 @@
#ifndef Adafruit_SPIDevice_h
#define Adafruit_SPIDevice_h
#include <Arduino.h>
#if !defined(SPI_INTERFACES_COUNT) || \
(defined(SPI_INTERFACES_COUNT) && (SPI_INTERFACES_COUNT > 0))
// HW SPI available
#include <SPI.h>
#define BUSIO_HAS_HW_SPI
#else
// SW SPI ONLY
enum { SPI_MODE0, SPI_MODE1, SPI_MODE2, SPI_MODE3 };
typedef uint8_t SPIClass;
#endif
// some modern SPI definitions don't have BitOrder enum
#if (defined(__AVR__) && !defined(ARDUINO_ARCH_MEGAAVR)) || \
defined(ESP8266) || defined(TEENSYDUINO) || defined(SPARK) || \
defined(ARDUINO_ARCH_SPRESENSE) || defined(MEGATINYCORE) || \
defined(DXCORE) || defined(ARDUINO_AVR_ATmega4809) || \
defined(ARDUINO_AVR_ATmega4808) || defined(ARDUINO_AVR_ATmega3209) || \
defined(ARDUINO_AVR_ATmega3208) || defined(ARDUINO_AVR_ATmega1609) || \
defined(ARDUINO_AVR_ATmega1608) || defined(ARDUINO_AVR_ATmega809) || \
defined(ARDUINO_AVR_ATmega808) || defined(ARDUINO_ARCH_ARC32) || \
defined(ARDUINO_ARCH_XMC)
typedef enum _BitOrder {
SPI_BITORDER_MSBFIRST = MSBFIRST,
SPI_BITORDER_LSBFIRST = LSBFIRST,
} BusIOBitOrder;
#elif defined(ESP32) || defined(__ASR6501__) || defined(__ASR6502__)
// some modern SPI definitions don't have BitOrder enum and have different SPI
// mode defines
typedef enum _BitOrder {
SPI_BITORDER_MSBFIRST = SPI_MSBFIRST,
SPI_BITORDER_LSBFIRST = SPI_LSBFIRST,
} BusIOBitOrder;
#else
// Some platforms have a BitOrder enum but its named MSBFIRST/LSBFIRST
#define SPI_BITORDER_MSBFIRST MSBFIRST
#define SPI_BITORDER_LSBFIRST LSBFIRST
typedef BitOrder BusIOBitOrder;
#endif
#if defined(__IMXRT1062__) // Teensy 4.x
// *Warning* I disabled the usage of FAST_PINIO as the set/clear operations
// used in the cpp file are not atomic and can effect multiple IO pins
// and if an interrupt happens in between the time the code reads the register
// and writes out the updated value, that changes one or more other IO pins
// on that same IO port, those change will be clobbered when the updated
// values are written back. A fast version can be implemented that uses the
// ports set and clear registers which are atomic.
// typedef volatile uint32_t BusIO_PortReg;
// typedef uint32_t BusIO_PortMask;
// #define BUSIO_USE_FAST_PINIO
#elif defined(__MBED__) || defined(__ZEPHYR__)
// Boards based on RTOS cores like mbed or Zephyr are not going to expose the
// low level registers needed for fast pin manipulation
#undef BUSIO_USE_FAST_PINIO
#elif defined(ARDUINO_ARCH_XMC)
#undef BUSIO_USE_FAST_PINIO
#elif defined(__AVR__) || defined(TEENSYDUINO)
typedef volatile uint8_t BusIO_PortReg;
typedef uint8_t BusIO_PortMask;
#define BUSIO_USE_FAST_PINIO
#elif defined(ESP8266) || defined(ESP32) || defined(__SAM3X8E__) || \
defined(ARDUINO_ARCH_SAMD)
typedef volatile uint32_t BusIO_PortReg;
typedef uint32_t BusIO_PortMask;
#define BUSIO_USE_FAST_PINIO
#elif (defined(__arm__) || defined(ARDUINO_FEATHER52)) && \
!defined(ARDUINO_ARCH_RP2040) && !defined(ARDUINO_SILABS) && \
!defined(ARDUINO_UNOR4_MINIMA) && !defined(ARDUINO_UNOR4_WIFI) && \
!defined(PORTDUINO)
typedef volatile uint32_t BusIO_PortReg;
typedef uint32_t BusIO_PortMask;
#if !defined(__ASR6501__) && !defined(__ASR6502__)
#define BUSIO_USE_FAST_PINIO
#endif
#else
#undef BUSIO_USE_FAST_PINIO
#endif
/**! The class which defines how we will talk to this device over SPI **/
class Adafruit_SPIDevice {
public:
#ifdef BUSIO_HAS_HW_SPI
Adafruit_SPIDevice(int8_t cspin, uint32_t freq = 1000000,
BusIOBitOrder dataOrder = SPI_BITORDER_MSBFIRST,
uint8_t dataMode = SPI_MODE0, SPIClass *theSPI = &SPI);
#else
Adafruit_SPIDevice(int8_t cspin, uint32_t freq = 1000000,
BusIOBitOrder dataOrder = SPI_BITORDER_MSBFIRST,
uint8_t dataMode = SPI_MODE0, SPIClass *theSPI = nullptr);
#endif
Adafruit_SPIDevice(int8_t cspin, int8_t sck, int8_t miso, int8_t mosi,
uint32_t freq = 1000000,
BusIOBitOrder dataOrder = SPI_BITORDER_MSBFIRST,
uint8_t dataMode = SPI_MODE0);
~Adafruit_SPIDevice();
bool begin(void);
bool read(uint8_t *buffer, size_t len, uint8_t sendvalue = 0xFF);
bool write(const uint8_t *buffer, size_t len,
const uint8_t *prefix_buffer = nullptr, size_t prefix_len = 0);
bool write_then_read(const uint8_t *write_buffer, size_t write_len,
uint8_t *read_buffer, size_t read_len,
uint8_t sendvalue = 0xFF);
bool write_and_read(uint8_t *buffer, size_t len);
uint8_t transfer(uint8_t send);
void transfer(uint8_t *buffer, size_t len);
void beginTransaction(void);
void endTransaction(void);
void beginTransactionWithAssertingCS();
void endTransactionWithDeassertingCS();
private:
#ifdef BUSIO_HAS_HW_SPI
SPIClass *_spi = nullptr;
SPISettings *_spiSetting = nullptr;
#else
uint8_t *_spi = nullptr;
uint8_t *_spiSetting = nullptr;
#endif
uint32_t _freq;
BusIOBitOrder _dataOrder;
uint8_t _dataMode;
void setChipSelect(int value);
int8_t _cs, _sck, _mosi, _miso;
#ifdef BUSIO_USE_FAST_PINIO
BusIO_PortReg *mosiPort, *clkPort, *misoPort, *csPort;
BusIO_PortMask mosiPinMask, misoPinMask, clkPinMask, csPinMask;
#endif
bool _begun;
};
#endif // Adafruit_SPIDevice_h

View File

@@ -0,0 +1,11 @@
# Adafruit Bus IO Library
# https://github.com/adafruit/Adafruit_BusIO
# MIT License
cmake_minimum_required(VERSION 3.5)
idf_component_register(SRCS "Adafruit_I2CDevice.cpp" "Adafruit_BusIO_Register.cpp" "Adafruit_SPIDevice.cpp" "Adafruit_GenericDevice.cpp"
INCLUDE_DIRS "."
REQUIRES arduino-esp32)
project(Adafruit_BusIO)

View File

@@ -0,0 +1,21 @@
The MIT License (MIT)
Copyright (c) 2017 Adafruit Industries
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.

View File

@@ -0,0 +1,8 @@
# Adafruit Bus IO Library [![Build Status](https://github.com/adafruit/Adafruit_BusIO/workflows/Arduino%20Library%20CI/badge.svg)](https://github.com/adafruit/Adafruit_BusIO/actions)
This is a helper library to abstract away I2C, SPI, and 'generic transport' (e.g. UART) transactions and registers
Adafruit invests time and resources providing this open source code, please support Adafruit and open-source hardware by purchasing products from Adafruit!
MIT license, all text above must be included in any redistribution

View File

@@ -0,0 +1 @@
COMPONENT_ADD_INCLUDEDIRS = .

View File

@@ -0,0 +1,219 @@
/*
Advanced example of using bstracted transport for reading and writing
register data from a UART-based device such as a TMC2209
Written with help by Claude!
https://claude.ai/chat/335f50b1-3dd8-435e-9139-57ec7ca26a3c (at this time
chats are not shareable :(
*/
#include "Adafruit_BusIO_Register.h"
#include "Adafruit_GenericDevice.h"
// Debugging macros
#define DEBUG_SERIAL Serial
#ifdef DEBUG_SERIAL
#define DEBUG_PRINT(x) DEBUG_SERIAL.print(x)
#define DEBUG_PRINTLN(x) DEBUG_SERIAL.println(x)
#define DEBUG_PRINT_HEX(x) \
do { \
if (x < 0x10) \
DEBUG_SERIAL.print('0'); \
DEBUG_SERIAL.print(x, HEX); \
DEBUG_SERIAL.print(' '); \
} while (0)
#else
#define DEBUG_PRINT(x)
#define DEBUG_PRINTLN(x)
#define DEBUG_PRINT_HEX(x)
#endif
#define TMC2209_IOIN 0x06
class TMC2209_UART {
private:
Stream *_uart_stream;
uint8_t _addr;
static bool uart_read(void *thiz, uint8_t *buffer, size_t len) {
TMC2209_UART *dev = (TMC2209_UART *)thiz;
uint16_t timeout = 100;
while (dev->_uart_stream->available() < len && timeout--) {
delay(1);
}
if (timeout == 0) {
DEBUG_PRINTLN("Read timeout!");
return false;
}
DEBUG_PRINT("Reading: ");
for (size_t i = 0; i < len; i++) {
buffer[i] = dev->_uart_stream->read();
DEBUG_PRINT_HEX(buffer[i]);
}
DEBUG_PRINTLN("");
return true;
}
static bool uart_write(void *thiz, const uint8_t *buffer, size_t len) {
TMC2209_UART *dev = (TMC2209_UART *)thiz;
DEBUG_PRINT("Writing: ");
for (size_t i = 0; i < len; i++) {
DEBUG_PRINT_HEX(buffer[i]);
}
DEBUG_PRINTLN("");
dev->_uart_stream->write(buffer, len);
return true;
}
static bool uart_readreg(void *thiz, uint8_t *addr_buf, uint8_t addrsiz,
uint8_t *data, uint16_t datalen) {
TMC2209_UART *dev = (TMC2209_UART *)thiz;
while (dev->_uart_stream->available())
dev->_uart_stream->read();
uint8_t packet[4] = {0x05, uint8_t(dev->_addr << 1), addr_buf[0], 0x00};
packet[3] = calcCRC(packet, 3);
if (!uart_write(thiz, packet, 4))
return false;
// Read back echo
uint8_t echo[4];
if (!uart_read(thiz, echo, 4))
return false;
// Verify echo
for (uint8_t i = 0; i < 4; i++) {
if (echo[i] != packet[i]) {
DEBUG_PRINTLN("Echo mismatch");
return false;
}
}
uint8_t response[8]; // sync + 0xFF + reg + 4 data bytes + CRC
if (!uart_read(thiz, response, 8))
return false;
// Verify response
if (response[0] != 0x05) {
DEBUG_PRINTLN("Invalid sync byte");
return false;
}
if (response[1] != 0xFF) {
DEBUG_PRINTLN("Invalid reply address");
return false;
}
if (response[2] != addr_buf[0]) {
DEBUG_PRINTLN("Register mismatch");
return false;
}
uint8_t crc = calcCRC(response, 7);
if (crc != response[7]) {
DEBUG_PRINTLN("CRC mismatch");
return false;
}
memcpy(data, &response[3], 4);
return true;
}
static bool uart_writereg(void *thiz, uint8_t *addr_buf, uint8_t addrsiz,
const uint8_t *data, uint16_t datalen) {
TMC2209_UART *dev = (TMC2209_UART *)thiz;
while (dev->_uart_stream->available())
dev->_uart_stream->read();
uint8_t packet[8] = {0x05,
uint8_t(dev->_addr << 1),
uint8_t(addr_buf[0] | 0x80),
data[0],
data[1],
data[2],
data[3],
0x00};
packet[7] = calcCRC(packet, 7);
if (!uart_write(thiz, packet, 8))
return false;
uint8_t echo[8];
if (!uart_read(thiz, echo, 8))
return false;
for (uint8_t i = 0; i < 8; i++) {
if (echo[i] != packet[i]) {
DEBUG_PRINTLN("Write echo mismatch");
return false;
}
}
return true;
}
static uint8_t calcCRC(uint8_t *data, uint8_t length) {
uint8_t crc = 0;
for (uint8_t i = 0; i < length; i++) {
uint8_t currentByte = data[i];
for (uint8_t j = 0; j < 8; j++) {
if ((crc >> 7) ^ (currentByte & 0x01)) {
crc = (crc << 1) ^ 0x07;
} else {
crc = crc << 1;
}
currentByte = currentByte >> 1;
}
}
return crc;
}
public:
TMC2209_UART(Stream *serial, uint8_t addr)
: _uart_stream(serial), _addr(addr) {}
Adafruit_GenericDevice *createDevice() {
return new Adafruit_GenericDevice(this, uart_read, uart_write, uart_readreg,
uart_writereg);
}
};
void setup() {
Serial.begin(115200);
while (!Serial)
;
delay(100);
Serial.println("TMC2209 Generic Device register read/write test!");
Serial1.begin(115200);
TMC2209_UART uart(&Serial1, 0);
Adafruit_GenericDevice *device = uart.createDevice();
device->begin();
// Create register object for IOIN
Adafruit_BusIO_Register ioin_reg(device,
TMC2209_IOIN, // device and register address
4, // width = 4 bytes
MSBFIRST, // byte order
1); // address width = 1 byte
Serial.print("IOIN = 0x");
Serial.println(ioin_reg.read(), HEX);
// Create RegisterBits for VERSION field (bits 31:24)
Adafruit_BusIO_RegisterBits version_bits(
&ioin_reg, 8, 24); // 8 bits wide, starting at bit 24
Serial.println("Reading VERSION...");
uint8_t version = version_bits.read();
Serial.print("VERSION = 0x");
Serial.println(version, HEX);
}
void loop() { delay(1000); }

View File

@@ -0,0 +1,98 @@
/*
Abstracted transport for reading and writing data from a UART-based
device such as a TMC2209
Written with help by Claude!
https://claude.ai/chat/335f50b1-3dd8-435e-9139-57ec7ca26a3c (at this time
chats are not shareable :(
*/
#include "Adafruit_GenericDevice.h"
/**
* Basic UART device class that demonstrates using GenericDevice with a Stream
* interface. This example shows how to wrap a Stream (like HardwareSerial or
* SoftwareSerial) with read/write callbacks that can be used by BusIO's
* register functions.
*/
class UARTDevice {
public:
UARTDevice(Stream *serial) : _serial(serial) {}
// Static callback for writing data to UART
// Called by GenericDevice when data needs to be sent
static bool uart_write(void *thiz, const uint8_t *buffer, size_t len) {
UARTDevice *dev = (UARTDevice *)thiz;
dev->_serial->write(buffer, len);
return true;
}
// Static callback for reading data from UART
// Includes timeout and will return false if not enough data available
static bool uart_read(void *thiz, uint8_t *buffer, size_t len) {
UARTDevice *dev = (UARTDevice *)thiz;
uint16_t timeout = 100;
while (dev->_serial->available() < len && timeout--) {
delay(1);
}
if (timeout == 0) {
return false;
}
for (size_t i = 0; i < len; i++) {
buffer[i] = dev->_serial->read();
}
return true;
}
// Create a GenericDevice instance using our callbacks
Adafruit_GenericDevice *createDevice() {
return new Adafruit_GenericDevice(this, uart_read, uart_write);
}
private:
Stream *_serial; // Underlying Stream instance (HardwareSerial, etc)
};
void setup() {
Serial.begin(115200);
while (!Serial)
;
delay(100);
Serial.println("Generic Device test!");
// Initialize UART for device communication
Serial1.begin(115200);
// Create UART wrapper and BusIO device
UARTDevice uart(&Serial1);
Adafruit_GenericDevice *device = uart.createDevice();
device->begin();
// Test write/read cycle
uint8_t write_buf[4] = {0x5, 0x0, 0x0, 0x48};
uint8_t read_buf[8];
Serial.println("Writing data...");
if (!device->write(write_buf, 4)) {
Serial.println("Write failed!");
return;
}
Serial.println("Reading response...");
if (!device->read(read_buf, 8)) {
Serial.println("Read failed!");
return;
}
// Print response bytes
Serial.print("Got response: ");
for (int i = 0; i < 8; i++) {
Serial.print("0x");
Serial.print(read_buf[i], HEX);
Serial.print(" ");
}
Serial.println();
}
void loop() { delay(1000); }

View File

@@ -0,0 +1,22 @@
#include <Adafruit_I2CDevice.h>
Adafruit_I2CDevice i2c_dev = Adafruit_I2CDevice(0x10);
void setup() {
while (!Serial) {
delay(10);
}
Serial.begin(115200);
Serial.println("I2C address detection test");
if (!i2c_dev.begin()) {
Serial.print("Did not find device at 0x");
Serial.println(i2c_dev.address(), HEX);
while (1)
;
}
Serial.print("Device found on address 0x");
Serial.println(i2c_dev.address(), HEX);
}
void loop() {}

View File

@@ -0,0 +1,45 @@
#include <Adafruit_I2CDevice.h>
#define I2C_ADDRESS 0x60
Adafruit_I2CDevice i2c_dev = Adafruit_I2CDevice(I2C_ADDRESS);
void setup() {
while (!Serial) {
delay(10);
}
Serial.begin(115200);
Serial.println("I2C device read and write test");
if (!i2c_dev.begin()) {
Serial.print("Did not find device at 0x");
Serial.println(i2c_dev.address(), HEX);
while (1)
;
}
Serial.print("Device found on address 0x");
Serial.println(i2c_dev.address(), HEX);
uint8_t buffer[32];
// Try to read 32 bytes
i2c_dev.read(buffer, 32);
Serial.print("Read: ");
for (uint8_t i = 0; i < 32; i++) {
Serial.print("0x");
Serial.print(buffer[i], HEX);
Serial.print(", ");
}
Serial.println();
// read a register by writing first, then reading
buffer[0] = 0x0C; // we'll reuse the same buffer
i2c_dev.write_then_read(buffer, 1, buffer, 2, false);
Serial.print("Write then Read: ");
for (uint8_t i = 0; i < 2; i++) {
Serial.print("0x");
Serial.print(buffer[i], HEX);
Serial.print(", ");
}
Serial.println();
}
void loop() {}

View File

@@ -0,0 +1,43 @@
#include <Adafruit_BusIO_Register.h>
#include <Adafruit_I2CDevice.h>
#define I2C_ADDRESS 0x60
Adafruit_I2CDevice i2c_dev = Adafruit_I2CDevice(I2C_ADDRESS);
void setup() {
while (!Serial) {
delay(10);
}
Serial.begin(115200);
Serial.println("I2C device register test");
if (!i2c_dev.begin()) {
Serial.print("Did not find device at 0x");
Serial.println(i2c_dev.address(), HEX);
while (1)
;
}
Serial.print("Device found on address 0x");
Serial.println(i2c_dev.address(), HEX);
Adafruit_BusIO_Register id_reg =
Adafruit_BusIO_Register(&i2c_dev, 0x0C, 2, LSBFIRST);
uint16_t id;
id_reg.read(&id);
Serial.print("ID register = 0x");
Serial.println(id, HEX);
Adafruit_BusIO_Register thresh_reg =
Adafruit_BusIO_Register(&i2c_dev, 0x01, 2, LSBFIRST);
uint16_t thresh;
thresh_reg.read(&thresh);
Serial.print("Initial threshold register = 0x");
Serial.println(thresh, HEX);
thresh_reg.write(~thresh);
Serial.print("Post threshold register = 0x");
Serial.println(thresh_reg.read(), HEX);
}
void loop() {}

View File

@@ -0,0 +1,40 @@
#include <Adafruit_BusIO_Register.h>
// Define which interface to use by setting the unused interface to NULL!
#define SPIDEVICE_CS 10
Adafruit_SPIDevice *spi_dev = NULL; // new Adafruit_SPIDevice(SPIDEVICE_CS);
#define I2C_ADDRESS 0x5D
Adafruit_I2CDevice *i2c_dev = new Adafruit_I2CDevice(I2C_ADDRESS);
void setup() {
while (!Serial) {
delay(10);
}
Serial.begin(115200);
Serial.println("I2C or SPI device register test");
if (spi_dev && !spi_dev->begin()) {
Serial.println("Could not initialize SPI device");
}
if (i2c_dev) {
if (i2c_dev->begin()) {
Serial.print("Device found on I2C address 0x");
Serial.println(i2c_dev->address(), HEX);
} else {
Serial.print("Did not find I2C device at 0x");
Serial.println(i2c_dev->address(), HEX);
}
}
Adafruit_BusIO_Register id_reg =
Adafruit_BusIO_Register(i2c_dev, spi_dev, ADDRBIT8_HIGH_TOREAD, 0x0F);
uint8_t id = 0;
id_reg.read(&id);
Serial.print("ID register = 0x");
Serial.println(id, HEX);
}
void loop() {}

View File

@@ -0,0 +1,35 @@
#include <Adafruit_SPIDevice.h>
#define SPIDEVICE_CS 10
Adafruit_SPIDevice spi_dev =
Adafruit_SPIDevice(SPIDEVICE_CS, 100000, SPI_BITORDER_MSBFIRST, SPI_MODE1);
// Adafruit_SPIDevice spi_dev = Adafruit_SPIDevice(SPIDEVICE_CS, 13, 12, 11,
// 100000, SPI_BITORDER_MSBFIRST, SPI_MODE1);
void setup() {
while (!Serial) {
delay(10);
}
Serial.begin(115200);
Serial.println("SPI device mode test");
if (!spi_dev.begin()) {
Serial.println("Could not initialize SPI device");
while (1)
;
}
}
void loop() {
Serial.println("\n\nTransfer test");
for (uint16_t x = 0; x <= 0xFF; x++) {
uint8_t i = x;
Serial.print("0x");
Serial.print(i, HEX);
spi_dev.read(&i, 1, i);
Serial.print("/");
Serial.print(i, HEX);
Serial.print(", ");
delay(25);
}
}

View File

@@ -0,0 +1,43 @@
#include <Adafruit_SPIDevice.h>
#define SPIDEVICE_CS 10
Adafruit_SPIDevice spi_dev = Adafruit_SPIDevice(SPIDEVICE_CS);
void setup() {
while (!Serial) {
delay(10);
}
Serial.begin(115200);
Serial.println("SPI device read and write test");
if (!spi_dev.begin()) {
Serial.println("Could not initialize SPI device");
while (1)
;
}
uint8_t buffer[32];
// Try to read 32 bytes
spi_dev.read(buffer, 32);
Serial.print("Read: ");
for (uint8_t i = 0; i < 32; i++) {
Serial.print("0x");
Serial.print(buffer[i], HEX);
Serial.print(", ");
}
Serial.println();
// read a register by writing first, then reading
buffer[0] = 0x8F; // we'll reuse the same buffer
spi_dev.write_then_read(buffer, 1, buffer, 2, false);
Serial.print("Write then Read: ");
for (uint8_t i = 0; i < 2; i++) {
Serial.print("0x");
Serial.print(buffer[i], HEX);
Serial.print(", ");
}
Serial.println();
}
void loop() {}

View File

@@ -0,0 +1,268 @@
/***************************************************
This is an example for how to use Adafruit_BusIO_RegisterBits from
Adafruit_BusIO library.
Designed specifically to work with the Adafruit RTD Sensor
----> https://www.adafruit.com/products/3328
uisng a MAX31865 RTD-to-Digital Converter
----> https://datasheets.maximintegrated.com/en/ds/MAX31865.pdf
This sensor uses SPI to communicate, 4 pins are required to
interface.
A fifth pin helps to detect when a new conversion is ready.
Adafruit invests time and resources providing this open source code,
please support Adafruit and open-source hardware by purchasing
products from Adafruit!
Example written (2020/3) by Andreas Hardtung/AnHard.
BSD license, all text above must be included in any redistribution
****************************************************/
#include <Adafruit_BusIO_Register.h>
#include <Adafruit_SPIDevice.h>
#define MAX31865_SPI_SPEED (5000000)
#define MAX31865_SPI_BITORDER (SPI_BITORDER_MSBFIRST)
#define MAX31865_SPI_MODE (SPI_MODE1)
#define MAX31865_SPI_CS (10)
#define MAX31865_READY_PIN (2)
Adafruit_SPIDevice spi_dev = Adafruit_SPIDevice(
MAX31865_SPI_CS, MAX31865_SPI_SPEED, MAX31865_SPI_BITORDER,
MAX31865_SPI_MODE, &SPI); // Hardware SPI
// Adafruit_SPIDevice spi_dev = Adafruit_SPIDevice( MAX31865_SPI_CS, 13, 12, 11,
// MAX31865_SPI_SPEED, MAX31865_SPI_BITORDER, MAX31865_SPI_MODE); // Software
// SPI
// MAX31865 chip related
// *********************************************************************************************
Adafruit_BusIO_Register config_reg =
Adafruit_BusIO_Register(&spi_dev, 0x00, ADDRBIT8_HIGH_TOWRITE, 1, MSBFIRST);
Adafruit_BusIO_RegisterBits bias_bit =
Adafruit_BusIO_RegisterBits(&config_reg, 1, 7);
Adafruit_BusIO_RegisterBits auto_bit =
Adafruit_BusIO_RegisterBits(&config_reg, 1, 6);
Adafruit_BusIO_RegisterBits oneS_bit =
Adafruit_BusIO_RegisterBits(&config_reg, 1, 5);
Adafruit_BusIO_RegisterBits wire_bit =
Adafruit_BusIO_RegisterBits(&config_reg, 1, 4);
Adafruit_BusIO_RegisterBits faultT_bits =
Adafruit_BusIO_RegisterBits(&config_reg, 2, 2);
Adafruit_BusIO_RegisterBits faultR_bit =
Adafruit_BusIO_RegisterBits(&config_reg, 1, 1);
Adafruit_BusIO_RegisterBits fi50hz_bit =
Adafruit_BusIO_RegisterBits(&config_reg, 1, 0);
Adafruit_BusIO_Register rRatio_reg =
Adafruit_BusIO_Register(&spi_dev, 0x01, ADDRBIT8_HIGH_TOWRITE, 2, MSBFIRST);
Adafruit_BusIO_RegisterBits rRatio_bits =
Adafruit_BusIO_RegisterBits(&rRatio_reg, 15, 1);
Adafruit_BusIO_RegisterBits fault_bit =
Adafruit_BusIO_RegisterBits(&rRatio_reg, 1, 0);
Adafruit_BusIO_Register maxRratio_reg =
Adafruit_BusIO_Register(&spi_dev, 0x03, ADDRBIT8_HIGH_TOWRITE, 2, MSBFIRST);
Adafruit_BusIO_RegisterBits maxRratio_bits =
Adafruit_BusIO_RegisterBits(&maxRratio_reg, 15, 1);
Adafruit_BusIO_Register minRratio_reg =
Adafruit_BusIO_Register(&spi_dev, 0x05, ADDRBIT8_HIGH_TOWRITE, 2, MSBFIRST);
Adafruit_BusIO_RegisterBits minRratio_bits =
Adafruit_BusIO_RegisterBits(&minRratio_reg, 15, 1);
Adafruit_BusIO_Register fault_reg =
Adafruit_BusIO_Register(&spi_dev, 0x07, ADDRBIT8_HIGH_TOWRITE, 1, MSBFIRST);
Adafruit_BusIO_RegisterBits range_high_fault_bit =
Adafruit_BusIO_RegisterBits(&fault_reg, 1, 7);
Adafruit_BusIO_RegisterBits range_low_fault_bit =
Adafruit_BusIO_RegisterBits(&fault_reg, 1, 6);
Adafruit_BusIO_RegisterBits refin_high_fault_bit =
Adafruit_BusIO_RegisterBits(&fault_reg, 1, 5);
Adafruit_BusIO_RegisterBits refin_low_fault_bit =
Adafruit_BusIO_RegisterBits(&fault_reg, 1, 4);
Adafruit_BusIO_RegisterBits rtdin_low_fault_bit =
Adafruit_BusIO_RegisterBits(&fault_reg, 1, 3);
Adafruit_BusIO_RegisterBits voltage_fault_bit =
Adafruit_BusIO_RegisterBits(&fault_reg, 1, 2);
// Print the details of the configuration register.
void printConfig(void) {
Serial.print("BIAS: ");
if (bias_bit.read())
Serial.print("ON");
else
Serial.print("OFF");
Serial.print(", AUTO: ");
if (auto_bit.read())
Serial.print("ON");
else
Serial.print("OFF");
Serial.print(", ONES: ");
if (oneS_bit.read())
Serial.print("ON");
else
Serial.print("OFF");
Serial.print(", WIRE: ");
if (wire_bit.read())
Serial.print("3");
else
Serial.print("2/4");
Serial.print(", FAULTCLEAR: ");
if (faultR_bit.read())
Serial.print("ON");
else
Serial.print("OFF");
Serial.print(", ");
if (fi50hz_bit.read())
Serial.print("50HZ");
else
Serial.print("60HZ");
Serial.println();
}
// Check and print faults. Then clear them.
void checkFaults(void) {
if (fault_bit.read()) {
Serial.print("MAX: ");
Serial.println(maxRratio_bits.read());
Serial.print("VAL: ");
Serial.println(rRatio_bits.read());
Serial.print("MIN: ");
Serial.println(minRratio_bits.read());
if (range_high_fault_bit.read())
Serial.println("Range high fault");
if (range_low_fault_bit.read())
Serial.println("Range low fault");
if (refin_high_fault_bit.read())
Serial.println("REFIN high fault");
if (refin_low_fault_bit.read())
Serial.println("REFIN low fault");
if (rtdin_low_fault_bit.read())
Serial.println("RTDIN low fault");
if (voltage_fault_bit.read())
Serial.println("Voltage fault");
faultR_bit.write(1); // clear fault
}
}
void setup() {
#if (MAX31865_1_READY_PIN != -1)
pinMode(MAX31865_READY_PIN, INPUT_PULLUP);
#endif
while (!Serial) {
delay(10);
}
Serial.begin(115200);
Serial.println("SPI Adafruit_BusIO_RegisterBits test on MAX31865");
if (!spi_dev.begin()) {
Serial.println("Could not initialize SPI device");
while (1)
;
}
// Set up for automode 50Hz. We don't care about selfheating. We want the
// highest possible sampling rate.
auto_bit.write(0); // Don't switch filtermode while auto_mode is on.
fi50hz_bit.write(1); // Set filter to 50Hz mode.
faultR_bit.write(1); // Clear faults.
bias_bit.write(1); // In automode we want to have the bias current always on.
delay(5); // Wait until bias current settles down.
// 10.5 time constants of the input RC network is required.
// 10ms worst case for 10kω reference resistor and a 0.1µF capacitor
// across the RTD inputs. Adafruit Module has 0.1µF and only
// 430/4300ω So here 0.43/4.3ms
auto_bit.write(
1); // Now we can set automode. Automatically starting first conversion.
// Test the READY_PIN
#if (defined(MAX31865_READY_PIN) && (MAX31865_READY_PIN != -1))
int i = 0;
while (digitalRead(MAX31865_READY_PIN) && i++ <= 100) {
delay(1);
}
if (i >= 100) {
Serial.print("ERROR: Max31865 Pin detection does not work. PIN:");
Serial.println(MAX31865_READY_PIN);
}
#else
delay(100);
#endif
// Set ratio range.
// Setting the temperatures would need some more calculation - not related to
// Adafruit_BusIO_RegisterBits.
uint16_t ratio = rRatio_bits.read();
maxRratio_bits.write((ratio < 0x8fffu - 1000u) ? ratio + 1000u : 0x8fffu);
minRratio_bits.write((ratio > 1000u) ? ratio - 1000u : 0u);
printConfig();
checkFaults();
}
void loop() {
#if (defined(MAX31865_READY_PIN) && (MAX31865_1_READY_PIN != -1))
// Is conversion ready?
if (!digitalRead(MAX31865_READY_PIN))
#else
// Warant conversion is ready.
delay(21); // 21ms for 50Hz-mode. 19ms in 60Hz-mode.
#endif
{
// Read ratio, calculate temperature, scale, filter and print.
Serial.println(rRatio2C(rRatio_bits.read()) * 100.0f,
0); // Temperature scaled by 100
// Check, print, clear faults.
checkFaults();
}
// Do something else.
// delay(15000);
}
// Module/Sensor related. Here Adafruit PT100 module with a 2_Wire PT100 Class C
// *****************************
float rRatio2C(uint16_t ratio) {
// A simple linear conversion.
const float R0 = 100.0f;
const float Rref = 430.0f;
const float alphaPT = 0.003850f;
const float ADCmax = (1u << 15) - 1.0f;
const float rscale = Rref / ADCmax;
// Measured temperature in boiling water 101.08°C with factor a = 1 and b = 0.
// Rref and MAX at about 22±2°C. Measured temperature in ice/water bath 0.76°C
// with factor a = 1 and b = 0. Rref and MAX at about 22±2°C.
// const float a = 1.0f / (alphaPT * R0);
const float a = (100.0f / 101.08f) / (alphaPT * R0);
// const float b = 0.0f; // 101.08
const float b = -0.76f; // 100.32 > 101.08
return filterRing(((ratio * rscale) - R0) * a + b);
}
// General purpose
// *********************************************************************************************
#define RINGLENGTH 250
float filterRing(float newVal) {
static float ring[RINGLENGTH] = {0.0};
static uint8_t ringIndex = 0;
static bool ringFull = false;
if (ringIndex == RINGLENGTH) {
ringFull = true;
ringIndex = 0;
}
ring[ringIndex] = newVal;
uint8_t loopEnd = (ringFull) ? RINGLENGTH : ringIndex + 1;
float ringSum = 0.0f;
for (uint8_t i = 0; i < loopEnd; i++)
ringSum += ring[i];
ringIndex++;
return ringSum / loopEnd;
}

View File

@@ -0,0 +1,40 @@
#include <Adafruit_BusIO_Register.h>
#include <Adafruit_SPIDevice.h>
#define SPIDEVICE_CS 10
Adafruit_SPIDevice spi_dev = Adafruit_SPIDevice(SPIDEVICE_CS);
void setup() {
while (!Serial) {
delay(10);
}
Serial.begin(115200);
Serial.println("SPI device register test");
if (!spi_dev.begin()) {
Serial.println("Could not initialize SPI device");
while (1)
;
}
Adafruit_BusIO_Register id_reg =
Adafruit_BusIO_Register(&spi_dev, 0x0F, ADDRBIT8_HIGH_TOREAD);
uint8_t id = 0;
id_reg.read(&id);
Serial.print("ID register = 0x");
Serial.println(id, HEX);
Adafruit_BusIO_Register thresh_reg = Adafruit_BusIO_Register(
&spi_dev, 0x0C, ADDRBIT8_HIGH_TOREAD, 2, LSBFIRST);
uint16_t thresh = 0;
thresh_reg.read(&thresh);
Serial.print("Initial threshold register = 0x");
Serial.println(thresh, HEX);
thresh_reg.write(~thresh);
Serial.print("Post threshold register = 0x");
Serial.println(thresh_reg.read(), HEX);
}
void loop() {}

View File

@@ -0,0 +1,9 @@
name=Adafruit BusIO
version=1.17.4
author=Adafruit
maintainer=Adafruit <info@adafruit.com>
sentence=This is a library for abstracting away UART, I2C and SPI interfacing
paragraph=This is a library for abstracting away UART, I2C and SPI interfacing
category=Signal Input/Output
url=https://github.com/adafruit/Adafruit_BusIO
architectures=*

View File

@@ -0,0 +1 @@
{"type": "library", "name": "ArduinoJson", "version": "7.4.3", "spec": {"owner": "bblanchon", "id": 64, "name": "ArduinoJson", "requirements": null, "uri": null}}

View File

@@ -0,0 +1,5 @@
// ArduinoJson - https://arduinojson.org
// Copyright © 2014-2026, Benoit BLANCHON
// MIT License
#include "src/ArduinoJson.h"

View File

@@ -0,0 +1,10 @@
The MIT License (MIT)
---------------------
Copyright © 2014-2026, Benoit BLANCHON
Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the “Software”), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED “AS IS”, WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.

View File

@@ -0,0 +1,153 @@
<p align="center">
<a href="https://arduinojson.org/"><img alt="ArduinoJson" src="https://arduinojson.org/images/logo.svg" width="200" /></a>
</p>
---
[![GitHub Workflow Status](https://img.shields.io/github/actions/workflow/status/bblanchon/ArduinoJson/ci.yml?branch=7.x&logo=github)](https://github.com/bblanchon/ArduinoJson/actions?query=workflow%3A%22Continuous+Integration%22+branch%3A7.x)
[![Continuous Integration](https://ci.appveyor.com/api/projects/status/m7s53wav1l0abssg/branch/7.x?svg=true)](https://ci.appveyor.com/project/bblanchon/arduinojson/branch/7.x)
[![Fuzzing Status](https://oss-fuzz-build-logs.storage.googleapis.com/badges/arduinojson.svg)](https://bugs.chromium.org/p/oss-fuzz/issues/list?sort=-opened&can=1&q=proj:arduinojson)
[![Coveralls branch](https://img.shields.io/coveralls/github/bblanchon/ArduinoJson/7.x?logo=coveralls)](https://coveralls.io/github/bblanchon/ArduinoJson?branch=7.x)
[![GitHub stars](https://img.shields.io/github/stars/bblanchon/ArduinoJson?style=flat&logo=github&color=orange)](https://github.com/bblanchon/ArduinoJson/stargazers)
[![GitHub Sponsors](https://img.shields.io/github/sponsors/bblanchon?logo=github&color=orange)](https://github.com/sponsors/bblanchon)
ArduinoJson is a C++ JSON library for Arduino and IoT (Internet Of Things).
## Features
* [JSON deserialization](https://arduinojson.org/v7/api/json/deserializejson/)
* [Optionally decodes UTF-16 escape sequences to UTF-8](https://arduinojson.org/v7/api/config/decode_unicode/)
* [Optionally supports comments in the input](https://arduinojson.org/v7/api/config/enable_comments/)
* [Optionally filters the input to keep only desired values](https://arduinojson.org/v7/api/json/deserializejson/#filtering)
* Supports single quotes as a string delimiter
* Compatible with [NDJSON](http://ndjson.org/) and [JSON Lines](https://jsonlines.org/)
* [JSON serialization](https://arduinojson.org/v7/api/json/serializejson/)
* [Can write to a buffer or a stream](https://arduinojson.org/v7/api/json/serializejson/)
* [Optionally indents the document (prettified JSON)](https://arduinojson.org/v7/api/json/serializejsonpretty/)
* [MessagePack serialization](https://arduinojson.org/v7/api/msgpack/serializemsgpack/)
* [MessagePack deserialization](https://arduinojson.org/v7/api/msgpack/deserializemsgpack/)
* Efficient
* [Twice smaller than the "official" Arduino_JSON library](https://arduinojson.org/2019/11/19/arduinojson-vs-arduino_json/)
* [Almost 10% faster than the "official" Arduino_JSON library](https://arduinojson.org/2019/11/19/arduinojson-vs-arduino_json/)
* [Consumes roughly 10% less RAM than the "official" Arduino_JSON library](https://arduinojson.org/2019/11/19/arduinojson-vs-arduino_json/)
* [Deduplicates strings](https://arduinojson.org/news/2020/08/01/version-6-16-0/)
* Versatile
* Supports [custom allocators (to use external RAM chip, for example)](https://arduinojson.org/v7/how-to/use-external-ram-on-esp32/)
* Supports [`String`](https://arduinojson.org/v7/api/config/enable_arduino_string/), [`std::string`](https://arduinojson.org/v7/api/config/enable_std_string/), and [`std::string_view`](https://arduinojson.org/v7/api/config/enable_string_view/)
* Supports [`Stream`](https://arduinojson.org/v7/api/config/enable_arduino_stream/) and [`std::istream`/`std::ostream`](https://arduinojson.org/v7/api/config/enable_std_stream/)
* Supports [Flash strings](https://arduinojson.org/v7/api/config/enable_progmem/)
* Supports [custom readers](https://arduinojson.org/v7/api/json/deserializejson/#custom-reader) and [custom writers](https://arduinojson.org/v7/api/json/serializejson/#custom-writer)
* Supports [custom converters](https://arduinojson.org/news/2021/05/04/version-6-18-0/)
* Portable
* Usable on any C++ project (not limited to Arduino)
* Compatible with C++11, C++14 and C++17
* Support for C++98/C++03 available on [ArduinoJson 6.20.x](https://github.com/bblanchon/ArduinoJson/tree/6.20.x)
* Zero warnings with `-Wall -Wextra -pedantic` and `/W4`
* [Header-only library](https://en.wikipedia.org/wiki/Header-only)
* Works with virtually any board
* Arduino boards: [Uno](https://amzn.to/38aL2ik), [Due](https://amzn.to/36YkWi2), [Micro](https://amzn.to/35WkdwG), [Nano](https://amzn.to/2QTvwRX), [Mega](https://amzn.to/36XWhuf), [Yun](https://amzn.to/30odURc), [Leonardo](https://amzn.to/36XWjlR)...
* Espressif chips: [ESP8266](https://amzn.to/36YluV8), [ESP32](https://amzn.to/2G4pRCB)
* Lolin (WeMos) boards: [D1 mini](https://amzn.to/2QUpz7q), [D1 Mini Pro](https://amzn.to/36UsGSs)...
* Teensy boards: [4.0](https://amzn.to/30ljXGq), [3.2](https://amzn.to/2FT0EuC), [2.0](https://amzn.to/2QXUMXj)
* Particle boards: [Argon](https://amzn.to/2FQHa9X), [Boron](https://amzn.to/36WgLUd), [Electron](https://amzn.to/30vEc4k), [Photon](https://amzn.to/387F9Cd)...
* Texas Instruments boards: [MSP430](https://amzn.to/30nJWgg)...
* Soft cores: [Nios II](https://en.wikipedia.org/wiki/Nios_II)...
* Tested on all major development environments
* [Arduino IDE](https://www.arduino.cc/en/Main/Software)
* [Atmel Studio](http://www.atmel.com/microsite/atmel-studio/)
* [Atollic TrueSTUDIO](https://atollic.com/truestudio/)
* [Energia](http://energia.nu/)
* [IAR Embedded Workbench](https://www.iar.com/iar-embedded-workbench/)
* [Keil uVision](http://www.keil.com/)
* [MPLAB X IDE](http://www.microchip.com/mplab/mplab-x-ide)
* [Particle](https://www.particle.io/)
* [PlatformIO](http://platformio.org/)
* [Sloeber plugin for Eclipse](https://eclipse.baeyens.it/)
* [Visual Micro](http://www.visualmicro.com/)
* [Visual Studio](https://www.visualstudio.com/)
* [Even works with online compilers like wandbox.org](https://wandbox.org/permlink/RlZSKy17DjJ6HcdN)
* [CMake friendly](https://arduinojson.org/v7/how-to/use-arduinojson-with-cmake/)
* Well designed
* [Elegant API](http://arduinojson.org/v7/example/)
* [Thread-safe](https://en.wikipedia.org/wiki/Thread_safety)
* Self-contained (no external dependency)
* `const` friendly
* [`for` friendly](https://arduinojson.org/v7/api/jsonobject/begin_end/)
* [TMP friendly](https://en.wikipedia.org/wiki/Template_metaprogramming)
* Handles [integer overflows](https://arduinojson.org/v7/api/jsonvariant/as/#integer-overflows)
* Well tested
* [Unit test coverage close to 100%](https://coveralls.io/github/bblanchon/ArduinoJson?branch=7.x)
* Continuously tested on
* [Visual Studio 2017, 2019, 2022](https://ci.appveyor.com/project/bblanchon/arduinojson/branch/7.x)
* [GCC 4.8, 5, 6, 7, 8, 9, 10, 11, 12](https://github.com/bblanchon/ArduinoJson/actions?query=workflow%3A%22Continuous+Integration%22)
* [Clang 7 to 19](https://github.com/bblanchon/ArduinoJson/actions?query=workflow%3A%22Continuous+Integration%22)
* [Continuously fuzzed with Google OSS Fuzz](https://bugs.chromium.org/p/oss-fuzz/issues/list?sort=-opened&can=1&q=proj:arduinojson)
* Passes all default checks of [clang-tidy](https://releases.llvm.org/10.0.0/tools/clang/tools/extra/docs/clang-tidy/)
* Well documented
* [Tutorials](https://arduinojson.org/v7/doc/deserialization/)
* [Examples](https://arduinojson.org/v7/example/)
* [How-tos](https://arduinojson.org/v7/example/)
* [FAQ](https://arduinojson.org/v7/faq/)
* [Troubleshooter](https://arduinojson.org/v7/troubleshooter/)
* [Book](https://arduinojson.org/book/)
* [Changelog](CHANGELOG.md)
* Vibrant user community
* Most popular of all Arduino libraries on [GitHub](https://github.com/search?o=desc&q=arduino+library&s=stars&type=Repositories)
* [Used in hundreds of projects](https://www.hackster.io/search?i=projects&q=arduinojson)
* [Responsive support](https://github.com/bblanchon/ArduinoJson/issues?q=is%3Aissue+is%3Aclosed)
## Quickstart
### Deserialization
Here is a program that parses a JSON document with ArduinoJson.
```c++
const char* json = "{\"sensor\":\"gps\",\"time\":1351824120,\"data\":[48.756080,2.302038]}";
JsonDocument doc;
deserializeJson(doc, json);
const char* sensor = doc["sensor"];
long time = doc["time"];
double latitude = doc["data"][0];
double longitude = doc["data"][1];
```
See the [tutorial on arduinojson.org](https://arduinojson.org/v7/doc/deserialization/)
### Serialization
Here is a program that generates a JSON document with ArduinoJson:
```c++
JsonDocument doc;
doc["sensor"] = "gps";
doc["time"] = 1351824120;
doc["data"][0] = 48.756080;
doc["data"][1] = 2.302038;
serializeJson(doc, Serial);
// This prints:
// {"sensor":"gps","time":1351824120,"data":[48.756080,2.302038]}
```
See the [tutorial on arduinojson.org](https://arduinojson.org/v7/doc/serialization/)
## Sponsors
ArduinoJson is thankful to its sponsors. Please give them a visit; they deserve it!
<p>
<a href="https://github.com/1technophile" rel="sponsored">
<img alt="1technophile" src="https://avatars.githubusercontent.com/u/12672732?s=40&v=4">
</a>
<a href="https://github.com/LArkema" rel="sponsored">
<img alt="LArkema" src="https://avatars.githubusercontent.com/u/38381313?s=40&v=4">
</a>
</p>
If you run a commercial project that embeds ArduinoJson, think about [sponsoring the library's development](https://github.com/sponsors/bblanchon): it ensures the code that your products rely on stays actively maintained. It can also give your project some exposure to the makers' community.
If you are an individual user and want to support the development (or give a sign of appreciation), consider purchasing the book [Mastering ArduinoJson](https://arduinojson.org/book/)&nbsp;❤, or simply [cast a star](https://github.com/bblanchon/ArduinoJson/stargazers)&nbsp;⭐.

View File

@@ -0,0 +1,152 @@
// ArduinoJson - https://arduinojson.org
// Copyright © 2014-2026, Benoit BLANCHON
// MIT License
//
// This example shows how to store your project configuration in a file.
// It uses the SD library but can be easily modified for any other file-system.
//
// The file contains a JSON document with the following content:
// {
// "hostname": "examples.com",
// "port": 2731
// }
//
// To run this program, you need an SD card connected to the SPI bus as follows:
// * MOSI <-> pin 11
// * MISO <-> pin 12
// * CLK <-> pin 13
// * CS <-> pin 4
//
// https://arduinojson.org/v7/example/config/
#include <ArduinoJson.h>
#include <SD.h>
#include <SPI.h>
// Our configuration structure.
struct Config {
char hostname[64];
int port;
};
const char* filename = "/config.txt"; // <- SD library uses 8.3 filenames
Config config; // <- global configuration object
// Loads the configuration from a file
void loadConfiguration(const char* filename, Config& config) {
// Open file for reading
File file = SD.open(filename);
// Allocate a temporary JsonDocument
JsonDocument doc;
// Deserialize the JSON document
DeserializationError error = deserializeJson(doc, file);
if (error)
Serial.println(F("Failed to read file, using default configuration"));
// Copy values from the JsonDocument to the Config
config.port = doc["port"] | 2731;
strlcpy(config.hostname, // <- destination
doc["hostname"] | "example.com", // <- source
sizeof(config.hostname)); // <- destination's capacity
// Close the file (Curiously, File's destructor doesn't close the file)
file.close();
}
// Saves the configuration to a file
void saveConfiguration(const char* filename, const Config& config) {
// Delete existing file, otherwise the configuration is appended to the file
SD.remove(filename);
// Open file for writing
File file = SD.open(filename, FILE_WRITE);
if (!file) {
Serial.println(F("Failed to create file"));
return;
}
// Allocate a temporary JsonDocument
JsonDocument doc;
// Set the values in the document
doc["hostname"] = config.hostname;
doc["port"] = config.port;
// Serialize JSON to file
if (serializeJson(doc, file) == 0) {
Serial.println(F("Failed to write to file"));
}
// Close the file
file.close();
}
// Prints the content of a file to the Serial
void printFile(const char* filename) {
// Open file for reading
File file = SD.open(filename);
if (!file) {
Serial.println(F("Failed to read file"));
return;
}
// Extract each characters by one by one
while (file.available()) {
Serial.print((char)file.read());
}
Serial.println();
// Close the file
file.close();
}
void setup() {
// Initialize serial port
Serial.begin(9600);
while (!Serial)
continue;
// Initialize SD library
const int chipSelect = 4;
while (!SD.begin(chipSelect)) {
Serial.println(F("Failed to initialize SD library"));
delay(1000);
}
// Should load default config if run for the first time
Serial.println(F("Loading configuration..."));
loadConfiguration(filename, config);
// Create configuration file
Serial.println(F("Saving configuration..."));
saveConfiguration(filename, config);
// Dump config file
Serial.println(F("Print config file..."));
printFile(filename);
}
void loop() {
// not used in this example
}
// Performance issue?
// ------------------
//
// File is an unbuffered stream, which is not optimal for ArduinoJson.
// See: https://arduinojson.org/v7/how-to/improve-speed/
// See also
// --------
//
// https://arduinojson.org/ contains the documentation for all the functions
// used above. It also includes an FAQ that will help you solve any
// serialization or deserialization problem.
//
// The book "Mastering ArduinoJson" contains a case study of a project that has
// a complex configuration with nested members.
// Contrary to this example, the project in the book uses the SPIFFS filesystem.
// Learn more at https://arduinojson.org/book/
// Use the coupon code TWENTY for a 20% discount ❤❤❤❤❤

View File

@@ -0,0 +1,64 @@
// ArduinoJson - https://arduinojson.org
// Copyright © 2014-2026, Benoit BLANCHON
// MIT License
//
// This example shows how to use DeserializationOption::Filter
//
// https://arduinojson.org/v7/example/filter/
#include <ArduinoJson.h>
void setup() {
// Initialize serial port
Serial.begin(9600);
while (!Serial)
continue;
// The huge input: an extract from OpenWeatherMap response
auto input_json = F(
"{\"cod\":\"200\",\"message\":0,\"list\":[{\"dt\":1581498000,\"main\":{"
"\"temp\":3.23,\"feels_like\":-3.63,\"temp_min\":3.23,\"temp_max\":4.62,"
"\"pressure\":1014,\"sea_level\":1014,\"grnd_level\":1010,\"humidity\":"
"58,\"temp_kf\":-1.39},\"weather\":[{\"id\":800,\"main\":\"Clear\","
"\"description\":\"clear "
"sky\",\"icon\":\"01d\"}],\"clouds\":{\"all\":0},\"wind\":{\"speed\":6."
"19,\"deg\":266},\"sys\":{\"pod\":\"d\"},\"dt_txt\":\"2020-02-12 "
"09:00:00\"},{\"dt\":1581508800,\"main\":{\"temp\":6.09,\"feels_like\":-"
"1.07,\"temp_min\":6.09,\"temp_max\":7.13,\"pressure\":1015,\"sea_"
"level\":1015,\"grnd_level\":1011,\"humidity\":48,\"temp_kf\":-1.04},"
"\"weather\":[{\"id\":800,\"main\":\"Clear\",\"description\":\"clear "
"sky\",\"icon\":\"01d\"}],\"clouds\":{\"all\":9},\"wind\":{\"speed\":6."
"64,\"deg\":268},\"sys\":{\"pod\":\"d\"},\"dt_txt\":\"2020-02-12 "
"12:00:00\"}],\"city\":{\"id\":2643743,\"name\":\"London\",\"coord\":{"
"\"lat\":51.5085,\"lon\":-0.1257},\"country\":\"GB\",\"population\":"
"1000000,\"timezone\":0,\"sunrise\":1581492085,\"sunset\":1581527294}}");
// The filter: it contains "true" for each value we want to keep
JsonDocument filter;
filter["list"][0]["dt"] = true;
filter["list"][0]["main"]["temp"] = true;
// Deserialize the document
JsonDocument doc;
deserializeJson(doc, input_json, DeserializationOption::Filter(filter));
// Print the result
serializeJsonPretty(doc, Serial);
}
void loop() {
// not used in this example
}
// See also
// --------
//
// https://arduinojson.org/ contains the documentation for all the functions
// used above. It also includes an FAQ that will help you solve any
// deserialization problem.
//
// The book "Mastering ArduinoJson" contains a tutorial on deserialization.
// It begins with a simple example, like the one above, and then adds more
// features like deserializing directly from a file or an HTTP request.
// Learn more at https://arduinojson.org/book/
// Use the coupon code TWENTY for a 20% discount ❤❤❤❤❤

View File

@@ -0,0 +1,65 @@
// ArduinoJson - https://arduinojson.org
// Copyright © 2014-2026, Benoit BLANCHON
// MIT License
//
// This example shows how to generate a JSON document with ArduinoJson.
//
// https://arduinojson.org/v7/example/generator/
#include <ArduinoJson.h>
void setup() {
// Initialize Serial port
Serial.begin(9600);
while (!Serial)
continue;
// Allocate the JSON document
JsonDocument doc;
// Add values in the document
doc["sensor"] = "gps";
doc["time"] = 1351824120;
// Add an array
JsonArray data = doc["data"].to<JsonArray>();
data.add(48.756080);
data.add(2.302038);
// Generate the minified JSON and send it to the Serial port
serializeJson(doc, Serial);
// The above line prints:
// {"sensor":"gps","time":1351824120,"data":[48.756080,2.302038]}
// Start a new line
Serial.println();
// Generate the prettified JSON and send it to the Serial port
serializeJsonPretty(doc, Serial);
// The above line prints:
// {
// "sensor": "gps",
// "time": 1351824120,
// "data": [
// 48.756080,
// 2.302038
// ]
// }
}
void loop() {
// not used in this example
}
// See also
// --------
//
// https://arduinojson.org/ contains the documentation for all the functions
// used above. It also includes an FAQ that will help you solve any
// serialization problem.
//
// The book "Mastering ArduinoJson" contains a tutorial on serialization.
// It begins with a simple example, like the one above, and then adds more
// features like serializing directly to a file or an HTTP request.
// Learn more at https://arduinojson.org/book/
// Use the coupon code TWENTY for a 20% discount ❤❤❤❤❤

View File

@@ -0,0 +1,125 @@
// ArduinoJson - https://arduinojson.org
// Copyright © 2014-2026, Benoit BLANCHON
// MIT License
//
// This example shows how to parse a JSON document in an HTTP response.
// It uses the Ethernet library, but can be easily adapted for Wifi.
//
// It performs a GET resquest on https://arduinojson.org/example.json
// Here is the expected response:
// {
// "sensor": "gps",
// "time": 1351824120,
// "data": [
// 48.756080,
// 2.302038
// ]
// }
//
// https://arduinojson.org/v7/example/http-client/
#include <ArduinoJson.h>
#include <Ethernet.h>
#include <SPI.h>
void setup() {
// Initialize Serial port
Serial.begin(9600);
while (!Serial)
continue;
// Initialize Ethernet library
byte mac[] = {0xDE, 0xAD, 0xBE, 0xEF, 0xFE, 0xED};
if (!Ethernet.begin(mac)) {
Serial.println(F("Failed to configure Ethernet"));
return;
}
delay(1000);
Serial.println(F("Connecting..."));
// Connect to HTTP server
EthernetClient client;
client.setTimeout(10000);
if (!client.connect("arduinojson.org", 80)) {
Serial.println(F("Connection failed"));
return;
}
Serial.println(F("Connected!"));
// Send HTTP request
client.println(F("GET /example.json HTTP/1.0"));
client.println(F("Host: arduinojson.org"));
client.println(F("Connection: close"));
if (client.println() == 0) {
Serial.println(F("Failed to send request"));
client.stop();
return;
}
// Check HTTP status
char status[32] = {0};
client.readBytesUntil('\r', status, sizeof(status));
// It should be "HTTP/1.0 200 OK" or "HTTP/1.1 200 OK"
if (strcmp(status + 9, "200 OK") != 0) {
Serial.print(F("Unexpected response: "));
Serial.println(status);
client.stop();
return;
}
// Skip HTTP headers
char endOfHeaders[] = "\r\n\r\n";
if (!client.find(endOfHeaders)) {
Serial.println(F("Invalid response"));
client.stop();
return;
}
// Allocate the JSON document
JsonDocument doc;
// Parse JSON object
DeserializationError error = deserializeJson(doc, client);
if (error) {
Serial.print(F("deserializeJson() failed: "));
Serial.println(error.f_str());
client.stop();
return;
}
// Extract values
Serial.println(F("Response:"));
Serial.println(doc["sensor"].as<const char*>());
Serial.println(doc["time"].as<long>());
Serial.println(doc["data"][0].as<float>(), 6);
Serial.println(doc["data"][1].as<float>(), 6);
// Disconnect
client.stop();
}
void loop() {
// not used in this example
}
// Performance issue?
// ------------------
//
// EthernetClient is an unbuffered stream, which is not optimal for ArduinoJson.
// See: https://arduinojson.org/v7/how-to/improve-speed/
// See also
// --------
//
// https://arduinojson.org/ contains the documentation for all the functions
// used above. It also includes an FAQ that will help you solve any
// serialization problem.
//
// The book "Mastering ArduinoJson" contains a tutorial on deserialization
// showing how to parse the response from GitHub's API. In the last chapter,
// it shows how to parse the huge documents from OpenWeatherMap
// and Reddit.
// Learn more at https://arduinojson.org/book/
// Use the coupon code TWENTY for a 20% discount ❤❤❤❤❤

Some files were not shown because too many files have changed in this diff Show More