wmbusmeters/src/units.h

266 wiersze
10 KiB
C++
Czysty Wina Historia

This file contains ambiguous Unicode characters!

This file contains ambiguous Unicode characters that may be confused with others in your current locale. If your use case is intentional and legitimate, you can safely ignore this warning. Use the Escape button to highlight these characters.

/*
Copyright (C) 2019-2022 Fredrik Öhrström (gpl-3.0-or-later)
This program is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
#ifndef UNITS_H
#define UNITS_H
#include<string>
#include<vector>
// A named quantity has a preferred unit,
// ie Volume has m3 (cubic meters) Energy has kwh, Power has kw.
// We search for quantities in the mbus telegrams instead of
// hard coding a specific unit. This is useful since some
// meters can send either mj or kwh depending on their configuration.
#define LIST_OF_QUANTITIES \
X(Time,Hour) \
X(Length,M) \
X(Mass,KG) \
X(Amperage,Ampere) \
X(Temperature,C) \
X(AmountOfSubstance,MOL) \
X(LuminousIntensity,CD) \
\
X(Energy,KWH) \
X(Reactive_Energy,KVARH) \
X(Apparent_Energy,KVAH) \
X(Power,KW) \
\
X(Volume,M3) \
X(Flow,M3H) \
\
X(Voltage,Volt) \
X(Frequency,HZ) \
X(Pressure,BAR) \
\
X(PointInTime,DateTimeLT) \
\
X(RelativeHumidity,RH) \
X(HCA,HCA) \
X(Text,TXT) \
X(Counter,COUNTER) \
enum class Quantity
{
#define X(quantity,default_unit) quantity,
LIST_OF_QUANTITIES
#undef X
Unknown
};
// A named unit (Unit) is a recurring unit that is useful to represent values
// sent in mbus telegrams. A named unit can be translated into an SIUnit
// which is unnamed and can encode any combination of SI units & scale.
// The SIUnits are used inside formulas. Eventually a successful formula
// calculation will map the final SI Unit to a named unit.
#define LIST_OF_UNITS \
X(Second,s,"s",Time,"second") \
X(M,m,"m",Length,"meter") \
X(KG,kg,"kg",Mass,"kilogram") \
X(Ampere,a,"A",Amperage,"ampere") \
X(K,k,"K",Temperature,"kelvin") \
X(MOL,mol,"mol",AmountOfSubstance,"mole") \
X(CD,cd,"cd",LuminousIntensity,"candela") \
\
X(KWH,kwh,"kWh",Energy,"kilo Watt hour") \
X(MJ,mj,"MJ",Energy,"Mega Joule") \
X(GJ,gj,"GJ",Energy,"Giga Joule") \
X(KVARH,kvarh,"kVARh",Reactive_Energy,"kilo volt amperes reactive hour") \
X(KVAH,kvah,"kVAh",Apparent_Energy,"kilo volt amperes hour") \
X(M3C,m3c,"m³°C",Energy,"cubic meter celsius") \
\
X(KW,kw,"kW",Power,"kilo Watt") \
\
X(M3,m3,"m³",Volume,"cubic meter") \
X(L,l,"l",Volume,"litre") \
X(M3H,m3h,"m³/h",Flow,"cubic meters per hour") \
X(LH,lh,"l/h",Flow,"liters per hour") \
\
X(C,c,"°C",Temperature,"celsius") \
X(F,f,"°F",Temperature,"fahrenheit") \
\
X(Volt,v,"V",Voltage,"volt") \
X(HZ,hz,"Hz",Frequency,"hz") \
X(PA,pa,"pa",Pressure,"pascal") \
X(BAR,bar,"bar",Pressure,"bar") \
\
X(Minute,min,"min",Time,"minute") \
X(Hour,h,"h",Time,"hour") \
X(Day,d,"d",Time,"day") \
X(Month,month,"month",Time,"month") \
X(Year,y,"y",Time,"year") \
X(DateTimeUT,ut,"ut",PointInTime,"unix timestamp") \
X(DateTimeUTC,utc,"utc",PointInTime,"coordinated universal time") \
X(DateTimeLT,lt,"lt",PointInTime,"local time") \
\
X(RH,rh,"RH",RelativeHumidity,"relative humidity") \
X(HCA,hca,"hca",HCA,"heat cost allocation") \
X(TXT,txt,"txt",Text,"text") \
X(COUNTER,counter,"counter",Counter,"counter") \
enum class Unit
{
#define X(cname,lcname,hrname,quantity,explanation) cname,
LIST_OF_UNITS
#undef X
Unknown
};
// The SIUnit is used inside formulas and for conversion between units.
// Any numeric named Unit can be expressed using an SIUnit.
// Most SIUnits do not have a corresponding named Unit.
// However the result of a formula calculation or conversion will
// be eventually converted into a named unit. So even if you
// are allowed to write a formula: 9 kwh * 6 kwh which generates
// the unit kwh² which is not explicitly named above,
// you cannot assign this value to any calculated field since kwh^2
// is not named. However you can do: sqrt(10 kwh * 10 kwh) which
// will generated an SIUnit which is equivalent to kwh.
//
// Other valid formulas are for example:
// energy_kwh = 100 kw * 22 h
// flow_m3h = 100 m3 / 5 h
//
// We can only track units raised to the power of 127 or -128.
// The struct SIExp below is used to track the exponents of the units.
//
struct SIExp
{
SIExp &s(int8_t i) { s_ = i; return *this; }
SIExp &m(int8_t i) { m_ = i; return *this; }
SIExp &kg(int8_t i) { kg_ = i; return *this; }
SIExp &a(int8_t i) { a_ = i; return *this; }
SIExp &mol(int8_t i) { mol_ = i; return *this; }
SIExp &cd(int8_t i) { cd_ = i; return *this; }
SIExp &k(int8_t i) { k_ = i; if (k_ != 0 && (c_ != 0 || f_ != 0)) { invalid_ = true; } return *this; }
SIExp &c(int8_t i) { c_ = i; if (c_ != 0 && (k_ != 0 || f_ != 0)) { invalid_ = true; } return *this; }
SIExp &f(int8_t i) { f_ = i; if (f_ != 0 && (k_ != 0 || c_ != 0)) { invalid_ = true; } return *this; }
int8_t s() const { return s_; }
int8_t m() const { return m_; }
int8_t kg() const { return kg_; }
int8_t a() const { return a_; }
int8_t mol() const { return mol_; }
int8_t cd() const { return cd_; }
int8_t k() const { return k_; }
int8_t c() const { return c_; }
int8_t f() const { return f_; }
SIExp mul(const SIExp &e) const;
SIExp div(const SIExp &e) const;
SIExp sqrt() const;
int8_t safe_add(int8_t a, int8_t b);
int8_t safe_sub(int8_t a, int8_t b);
int8_t safe_div2(int8_t a);
bool operator==(const SIExp &e) const;
std::string str() const;
static SIExp build() { return SIExp(); }
private:
int8_t s_ {};
int8_t m_ {};
int8_t kg_ {};
int8_t a_ {};
int8_t mol_ {};
int8_t cd_ {};
int8_t k_ {};
int8_t c_ {}; // Why c and f here? Because they are offset against K.
int8_t f_ {}; // Since SIUnits are also used for conversions of values, not just unit tracking,
// this means that the offset units (c,f) must be treated as distinct units.
// E.g. if you calculated m3*k (and forget the m3 and k value) then you
// cannot convert this value into m3*c since the offset makes the calculation
// depend on the k value, which we no longer know.
// But kw*h can be translated into kw*s since scaling (*3600) can be done on the
// calculated value without knowing the h value. Therefore we have to
// treat k, c and f as distinct units. I.e. you cannot add m3*k+m3*f+m3*c.
// If exponents have over/underflowed or if multiple of k,c,f are set, then the SIExp is not valid any more!
bool invalid_ = false;
};
struct SIUnit
{
// Transform a double,double,uint64_t into an SIUnit.
// The exp can be created compile time like this: SIUNIT(3.6E6, 0, SI_KG(1)|SI_M(2)|SI_S(-2)) which is kwh.
SIUnit(Quantity q, double scale, SIExp exponents) :
quantity_(q), scale_(scale), exponents_(exponents) {}
// Transform a named unit into an SIUnit.
SIUnit(Unit);
// Parse string like: 3.6E106*kg*m^2*s^-3*a^1 (ie volt)
SIUnit(std::string s);
// Return the known unit that best matches the SIUnit, or Unit::Unknown if none.
Unit asUnit() const ;
// Return the known unit that best matches the SIUnit and the quantity, or Unit::Unknown if none.
Unit asUnit(Quantity q) const;
// Return the quantity that this unit is used for.
Quantity quantity() const { return quantity_; }
// Get the scale.
double scale() const { return scale_; }
// Get the exponents.
const SIExp &exp() const { return exponents_; }
// Return a string like 3.6⁶s⁻²m²kg
std::string str() const;
// Return a detailed string like: kwh[3.6⁶s⁻²m²kg]Energy
std::string info() const;
// Check if the exponents (ie units) are the same.
bool sameExponents(SIUnit &to) const { return exponents_ == to.exponents_; }
// Check if this unit can be converted to the other unit.
bool canConvertTo(const SIUnit &to) const;
// Convert value from this unit to another unit.
double convertTo(double val, const SIUnit &to) const;
// Multiply this unit with another unit.
SIUnit mul(const SIUnit &m) const ;
// Dividethis unit with another unit.
SIUnit div(const SIUnit &m) const ;
// Square root this unit.
SIUnit sqrt() const ;
private:
Quantity quantity_;
double scale_;
SIExp exponents_;
};
bool canConvert(Unit from, Unit to);
double convert(double v, Unit from, Unit to);
Unit whenMultiplied(Unit left, Unit right);
double multiply(double l, Unit left, double r, Unit right);
// Either uppercase KWH or lowercase kwh works here.
Unit toUnit(std::string s);
const SIUnit &toSIUnit(Unit u);
const char *toString(Quantity q);
bool isQuantity(Unit u, Quantity q);
Quantity toQuantity(Unit u);
void assertQuantity(Unit u, Quantity q);
Unit defaultUnitForQuantity(Quantity q);
std::string unitToStringHR(Unit u);
std::string unitToStringLowerCase(Unit u);
std::string unitToStringUpperCase(Unit u);
std::string valueToString(double v, Unit u);
Unit replaceWithConversionUnit(Unit u, std::vector<Unit> cs);
bool extractUnit(const std::string &s, std::string *vname, Unit *u);
#endif