Mirror of espurna firmware for wireless switches and more
You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

302 lines
9.3 KiB

6 years ago
  1. // -----------------------------------------------------------------------------
  2. // V9261F based power monitor
  3. // Copyright (C) 2017-2019 by Xose Pérez <xose dot perez at gmail dot com>
  4. // -----------------------------------------------------------------------------
  5. #if SENSOR_SUPPORT && V9261F_SUPPORT
  6. #pragma once
  7. #include "BaseEmonSensor.h"
  8. #include "../libs/fs_math.h"
  9. class V9261FSensor : public BaseEmonSensor {
  10. public:
  11. // ---------------------------------------------------------------------
  12. // Public
  13. // ---------------------------------------------------------------------
  14. static constexpr Magnitude Magnitudes[] {
  15. MAGNITUDE_CURRENT,
  16. MAGNITUDE_VOLTAGE,
  17. MAGNITUDE_POWER_ACTIVE,
  18. MAGNITUDE_POWER_REACTIVE,
  19. MAGNITUDE_POWER_APPARENT,
  20. MAGNITUDE_POWER_FACTOR,
  21. MAGNITUDE_ENERGY
  22. };
  23. V9261FSensor() :
  24. BaseEmonSensor(Magnitudes)
  25. {}
  26. void setPort(Stream* port) {
  27. _serial = port;
  28. _dirty = true;
  29. }
  30. // ---------------------------------------------------------------------
  31. // Sensor API
  32. // ---------------------------------------------------------------------
  33. unsigned char id() const override {
  34. return SENSOR_V9261F_ID;
  35. }
  36. unsigned char count() const override {
  37. return std::size(Magnitudes);
  38. }
  39. // Initialization method, must be idempotent
  40. void begin() override {
  41. if (!_dirty) return;
  42. _reading = false;
  43. _ready = true;
  44. _dirty = false;
  45. }
  46. // Descriptive name of the sensor
  47. String description() const override {
  48. return F("V9261F");
  49. }
  50. // Address of the sensor (it could be the GPIO or I2C address)
  51. String address(unsigned char) const override {
  52. return String(V9261F_PORT, 10);
  53. }
  54. // Loop-like method, call it in your main loop
  55. void tick() override {
  56. _read();
  57. }
  58. // Type for slot # index
  59. unsigned char type(unsigned char index) const override {
  60. if (index < std::size(Magnitudes)) {
  61. return Magnitudes[index].type;
  62. }
  63. return MAGNITUDE_NONE;
  64. }
  65. // Current value for slot # index
  66. double value(unsigned char index) override {
  67. if (index == 0) return _current;
  68. if (index == 1) return _voltage;
  69. if (index == 2) return _active;
  70. if (index == 3) return _reactive;
  71. if (index == 4) return _apparent;
  72. if (index == 5) return _apparent > 0 ? 100 * _active / _apparent : 100;
  73. if (index == 6) return _energy[0].asDouble();
  74. return 0;
  75. }
  76. double defaultRatio(unsigned char index) const override {
  77. switch (index) {
  78. case 0:
  79. return V9261F_CURRENT_FACTOR;
  80. case 1:
  81. return V9261F_VOLTAGE_FACTOR;
  82. case 2:
  83. return V9261F_POWER_FACTOR;
  84. case 3:
  85. return V9261F_RPOWER_FACTOR;
  86. }
  87. return BaseEmonSensor::DefaultRatio;
  88. }
  89. void setRatio(unsigned char index, double value) override {
  90. if (value > 0.0) {
  91. switch (index) {
  92. case 0:
  93. _current_ratio = value;
  94. break;
  95. case 1:
  96. _voltage_ratio = value;
  97. break;
  98. case 2:
  99. _power_active_ratio = value;
  100. break;
  101. case 3:
  102. _power_reactive_ratio = value;
  103. break;
  104. }
  105. }
  106. }
  107. double getRatio(unsigned char index) const override {
  108. switch (index) {
  109. case 0:
  110. return _current_ratio;
  111. case 1:
  112. return _voltage_ratio;
  113. case 2:
  114. return _power_active_ratio;
  115. case 3:
  116. return _power_reactive_ratio;
  117. }
  118. return BaseEmonSensor::getRatio(index);
  119. }
  120. protected:
  121. // ---------------------------------------------------------------------
  122. // Protected
  123. // ---------------------------------------------------------------------
  124. void _read() {
  125. // we are seeing the data request
  126. if (_state == 0) {
  127. const auto available = _serial->available();
  128. if (available <= 0) {
  129. if (_found && (TimeSource::now() - _timestamp > SyncInterval)) {
  130. _index = 0;
  131. _state = 1;
  132. }
  133. return;
  134. }
  135. consumeAvailable(*_serial);
  136. _found = true;
  137. _timestamp = TimeSource::now();
  138. // ...which we just skip...
  139. } else if (_state == 1) {
  140. _index += consumeAvailable(*_serial);
  141. if (_index++ >= 7) {
  142. _index = 0;
  143. _state = 2;
  144. }
  145. // ...until we receive response...
  146. } else if (_state == 2) {
  147. const auto available = _serial->available();
  148. if (available <= 0) {
  149. return;
  150. }
  151. _index += _serial->read(&_data[_index], std::min(
  152. static_cast<size_t>(available), sizeof(_data)));
  153. if (_index >= 19) {
  154. _timestamp = TimeSource::now();
  155. _state = 3;
  156. }
  157. // validate received data and wait for the next request -> response
  158. // FE1104 25F2420069C1BCFF20670C38C05E4101 B6
  159. // ^^^^^^ - HEAD byte, mask, number of valeus
  160. // ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ - u32 4 times
  161. // ^^ - CRC byte
  162. } else if (_state == 3) {
  163. if (_checksum(&_data[0], &_data[19]) == _data[19]) {
  164. _active = (double) (
  165. (_data[3]) +
  166. (_data[4] << 8) +
  167. (_data[5] << 16) +
  168. (_data[6] << 24)
  169. ) / _power_active_ratio;
  170. _reactive = (double) (
  171. (_data[7]) +
  172. (_data[8] << 8) +
  173. (_data[9] << 16) +
  174. (_data[10] << 24)
  175. ) / _power_reactive_ratio;
  176. _voltage = (double) (
  177. (_data[11]) +
  178. (_data[12] << 8) +
  179. (_data[13] << 16) +
  180. (_data[14] << 24)
  181. ) / _voltage_ratio;
  182. _current = (double) (
  183. (_data[15]) +
  184. (_data[16] << 8) +
  185. (_data[17] << 16) +
  186. (_data[18] << 24)
  187. ) / _current_ratio;
  188. if (_active < 0) _active = 0;
  189. if (_reactive < 0) _reactive = 0;
  190. if (_voltage < 0) _voltage = 0;
  191. if (_current < 0) _current = 0;
  192. _apparent = fs_sqrt(_reactive * _reactive + _active * _active);
  193. const auto now = TimeSource::now();
  194. if (_reading) {
  195. using namespace espurna::sensor;
  196. const auto elapsed = std::chrono::duration_cast<espurna::duration::Seconds>(now - _last_reading);
  197. _energy[0] += WattSeconds(Watts{_active}, elapsed);
  198. }
  199. _reading = true;
  200. _last_reading = now;
  201. }
  202. _timestamp = TimeSource::now();
  203. _index = 0;
  204. _state = 4;
  205. // ... by waiting for a bit
  206. } else if (_state == 4) {
  207. consumeAvailable(*_serial);
  208. if (TimeSource::now() - _timestamp > SyncInterval) {
  209. _state = 1;
  210. }
  211. }
  212. }
  213. static uint8_t _checksum(const uint8_t* begin, const uint8_t* end) {
  214. uint8_t out = 0;
  215. for (auto it = begin; it != end; ++it) {
  216. out += (*it);
  217. }
  218. out = ~out + 0x33;
  219. return out;
  220. }
  221. // ---------------------------------------------------------------------
  222. Stream* _serial { nullptr };
  223. using TimeSource = espurna::time::CoreClock;
  224. static constexpr auto SyncInterval = TimeSource::duration { V9261F_SYNC_INTERVAL };
  225. double _active { 0 };
  226. double _reactive { 0 };
  227. double _voltage { 0 };
  228. double _current { 0 };
  229. double _apparent { 0 };
  230. TimeSource::time_point _last_reading;
  231. TimeSource::time_point _timestamp;
  232. int _state { 0 };
  233. bool _found { false };
  234. bool _reading { false };
  235. uint8_t _data[24] {0};
  236. size_t _index { 0 };
  237. };
  238. #if __cplusplus < 201703L
  239. constexpr BaseSensor::Magnitude V9261FSensor::Magnitudes[];
  240. #endif
  241. #endif // SENSOR_SUPPORT && V9261F_SUPPORT