/*
|
|
|
|
SENSOR MODULE
|
|
|
|
Copyright (C) 2016-2019 by Xose Pérez <xose dot perez at gmail dot com>
|
|
Copyright (C) 2020 by Maxim Prokhorov <prokhorov dot max at outlook dot com>
|
|
|
|
*/
|
|
|
|
#pragma once
|
|
|
|
#include <Arduino.h>
|
|
|
|
#include <chrono>
|
|
#include <cstddef>
|
|
#include <cstdint>
|
|
#include <ratio>
|
|
|
|
#include <ArduinoJson.h>
|
|
|
|
#include "system.h"
|
|
|
|
namespace espurna {
|
|
namespace sensor {
|
|
|
|
enum class Unit : int {
|
|
Min_,
|
|
None,
|
|
Celcius,
|
|
Farenheit,
|
|
Kelvin,
|
|
Percentage,
|
|
Hectopascal,
|
|
Ampere,
|
|
Volt,
|
|
Voltampere,
|
|
Kilovoltampere,
|
|
VoltampereReactive,
|
|
KilovoltampereReactive,
|
|
Watt,
|
|
Kilowatt,
|
|
WattSecond,
|
|
Joule = WattSecond,
|
|
KilowattHour,
|
|
PartsPerMillion,
|
|
Ohm,
|
|
MicrogrammPerCubicMeter, // The concentration of an air pollutant
|
|
MilligrammPerCubicMeter, //
|
|
Lux,
|
|
UltravioletIndex, // "measurement of the strength of sunburn-producing ultraviolet (UV) radiation at a particular place and time"
|
|
// (XXX: Not a unit. Distinguish from None and specify decimals)
|
|
CountsPerMinute, // Unit of local dose rate (Geiger counting)
|
|
MicrosievertPerHour, // 2nd unit of local dose rate (Geiger counting)
|
|
Meter,
|
|
Hertz,
|
|
Ph
|
|
};
|
|
|
|
struct Watts {
|
|
using Type = double;
|
|
using Ratio = std::ratio<1>;
|
|
Type value;
|
|
};
|
|
|
|
struct WattSeconds {
|
|
using Type = uint32_t;
|
|
using Ratio = std::ratio_multiply<
|
|
Watts::Ratio,
|
|
espurna::duration::Seconds::period>;
|
|
Type value { 0 };
|
|
|
|
WattSeconds() = default;
|
|
|
|
constexpr explicit WattSeconds(Type value) :
|
|
value(value)
|
|
{}
|
|
|
|
constexpr explicit WattSeconds(float value) :
|
|
value(static_cast<Type>(value))
|
|
{}
|
|
|
|
constexpr explicit WattSeconds(double value) :
|
|
value(static_cast<Type>(value))
|
|
{}
|
|
|
|
constexpr WattSeconds(Watts watts, espurna::duration::Seconds seconds) :
|
|
value(static_cast<Type>(watts.value * seconds.count()))
|
|
{}
|
|
};
|
|
|
|
struct WattHours {
|
|
using Type = uint32_t;
|
|
using Ratio = std::ratio_multiply<
|
|
Watts::Ratio,
|
|
espurna::duration::Hours::period>;
|
|
Type value { 0 };
|
|
|
|
WattHours() = default;
|
|
explicit WattHours(Type value) :
|
|
value(value)
|
|
{}
|
|
|
|
WattHours(Watts watts, espurna::duration::Hours hours) :
|
|
value(static_cast<Type>(watts.value * hours.count()))
|
|
{}
|
|
};
|
|
|
|
struct Kilowatts {
|
|
using Type = double;
|
|
using Ratio = std::ratio<1000>;
|
|
Type value;
|
|
};
|
|
|
|
struct KilowattHours {
|
|
using Type = uint32_t;
|
|
using Ratio = std::ratio_multiply<
|
|
Kilowatts::Ratio,
|
|
espurna::duration::Hours::period>;
|
|
Type value { 0 };
|
|
|
|
KilowattHours() = default;
|
|
explicit KilowattHours(Type value) :
|
|
value(value)
|
|
{}
|
|
|
|
KilowattHours(Kilowatts kilowatts, espurna::duration::Hours hours) :
|
|
value(static_cast<Type>(kilowatts.value * hours.count()))
|
|
{}
|
|
};
|
|
|
|
template <typename To, typename From,
|
|
typename Divide = std::ratio_divide<typename From::Ratio, typename To::Ratio>>
|
|
struct Convert {
|
|
static To from(From from) {
|
|
return To(from.value
|
|
* static_cast<typename To::Type>(Divide::num)
|
|
/ static_cast<typename To::Type>(Divide::den));
|
|
}
|
|
};
|
|
|
|
struct Energy {
|
|
struct Pair {
|
|
KilowattHours kwh;
|
|
WattSeconds ws;
|
|
};
|
|
|
|
static constexpr auto WattSecondsMax =
|
|
WattSeconds::Type(KilowattHours::Ratio::num);
|
|
|
|
Energy() = default;
|
|
Energy(const Energy&) = default;
|
|
Energy(Energy&&) = default;
|
|
|
|
// energy always consists of kwh + ws pair
|
|
explicit Energy(Pair);
|
|
|
|
// prefer integral types
|
|
explicit Energy(WattSeconds);
|
|
explicit Energy(WattHours);
|
|
explicit Energy(KilowattHours kwh) :
|
|
_kwh(kwh)
|
|
{}
|
|
|
|
// special case for kwh input
|
|
explicit Energy(double);
|
|
|
|
Energy& operator=(const Energy&) = default;
|
|
Energy& operator=(Energy&&) = default;
|
|
|
|
// sets internal counters to zero
|
|
void reset();
|
|
|
|
// check whether we have *any* energy recorded. Can be zero:
|
|
// - on cold boot
|
|
// - on overflow
|
|
// - when we call `reset()`
|
|
explicit operator bool() const;
|
|
|
|
// allow generic math operation when dealing with energy delta
|
|
Energy& operator+=(const Energy&);
|
|
|
|
// most sensor implementations handle energy in joules / watt-second
|
|
Energy& operator+=(WattSeconds);
|
|
Energy operator+(WattSeconds);
|
|
|
|
// numeric representation as kWh
|
|
double asDouble() const;
|
|
|
|
// API representation as `<kWh>+<Ws>`
|
|
String asString() const;
|
|
|
|
// represent internal value as watt seconds / joules
|
|
// **will rollover** when exceeding WattSeconds::Type capacity
|
|
// (when kWh value is greater or equal to `maximum of Type / Ws per kWh`)
|
|
WattSeconds asWattSeconds() const;
|
|
|
|
// we are storing a kind-of integral and fractional parts
|
|
// using watt-second to avoid loosing precision, we don't expect these to be accessed directly
|
|
Pair pair() const {
|
|
return Pair {
|
|
.kwh = _kwh,
|
|
.ws = _ws,
|
|
};
|
|
}
|
|
|
|
private:
|
|
KilowattHours _kwh;
|
|
WattSeconds _ws;
|
|
};
|
|
|
|
// '.value' is set to 'Value::Unknown' when index is out of bounds
|
|
// '.value' is undefined when either reading or report hadn't happened yet
|
|
struct Value {
|
|
static constexpr double Unknown {
|
|
std::numeric_limits<double>::quiet_NaN() };
|
|
|
|
unsigned char type;
|
|
unsigned char index;
|
|
|
|
Unit units;
|
|
unsigned char decimals;
|
|
String topic;
|
|
|
|
double value;
|
|
String repr;
|
|
|
|
explicit operator bool() const;
|
|
};
|
|
|
|
struct Info {
|
|
unsigned char type;
|
|
unsigned char index;
|
|
|
|
Unit units;
|
|
unsigned char decimals;
|
|
|
|
String topic;
|
|
String description;
|
|
};
|
|
|
|
} // namespace sensor
|
|
} // namespace espurna
|
|
|
|
//--------------------------------------------------------------------------------
|
|
|
|
String magnitudeTypeTopic(unsigned char type);
|
|
String magnitudeUnitsName(espurna::sensor::Unit);
|
|
|
|
using MagnitudeReadHandler = void(*)(const espurna::sensor::Value&);
|
|
|
|
// Executes 'handler(value)' every time sensor reading happens
|
|
// (depends on read interval and won't happen in case sensor returns an error)
|
|
void sensorOnMagnitudeRead(MagnitudeReadHandler handler);
|
|
|
|
// Executes 'handler(value)' every time sensor report happens
|
|
// (depends on report counter of sensor reads and conditions like min / max delta)
|
|
void sensorOnMagnitudeReport(MagnitudeReadHandler handler);
|
|
|
|
// Amount of registered sensor magnitudes aka measurements
|
|
size_t magnitudeCount();
|
|
|
|
// Base magnitude info. Will contain `.type = MAGNITUDE_NONE` when index is out of bounds
|
|
espurna::sensor::Info magnitudeInfo(unsigned char index);
|
|
|
|
// type of magnitude at index; returns MAGNITUDE_NONE when index is out of bounds
|
|
unsigned char magnitudeType(unsigned char index);
|
|
|
|
// returns global index of magnitudes of the same type
|
|
unsigned char magnitudeIndex(unsigned char index);
|
|
|
|
String magnitudeTopic(unsigned char index);
|
|
|
|
// Get either last or reported reading; repends on the real-time setting
|
|
espurna::sensor::Value magnitudeValue(unsigned char index);
|
|
|
|
// Retrieves last sensor reading of the magnitude at index
|
|
espurna::sensor::Value magnitudeReadValue(unsigned char index);
|
|
|
|
// Retrieves last reported value of the magnitude at index
|
|
espurna::sensor::Value magnitudeReportValue(unsigned char index);
|
|
|
|
using SensorWebSocketMagnitudesCallback = void(*)(JsonArray&, size_t);
|
|
void sensorWebSocketMagnitudes(JsonObject& root, const char* prefix, SensorWebSocketMagnitudesCallback);
|
|
|
|
espurna::StringView sensorList();
|
|
void sensorSetup();
|