Browse Source

ESP8266 & ESP32; optimized rf24 rx for HM-300

pull/1080/head
no name 2 years ago
parent
commit
b2bcc7024d
  1. 6
      src/CHANGES.md
  2. 29
      src/app.cpp
  3. 10
      src/config/config.h
  4. 1
      src/defines.h
  5. 5
      src/hm/hmPayload.h
  6. 14
      src/hm/hmRadio.h
  7. 38
      src/hm/hmSystem.h
  8. 2
      src/hm/miPayload.h
  9. 20
      src/platformio.ini
  10. 48
      src/plugins/SML_OBIS_Parser.cpp
  11. 2
      src/plugins/SML_OBIS_Parser.h

6
src/CHANGES.md

@ -6,13 +6,15 @@ Changelog for ahoy-all-in-one compared to 0.6.9 of the main project
- show max solar ac/dc power - show max solar ac/dc power
- improved radio retransmit (complete retransmit if nothing was received, but only when inverter ought to be active) for the hm series) - improved radio retransmit (complete retransmit if nothing was received, but only when inverter ought to be active) for the hm series)
- Heuristic for choosing the best send channel (of 5 possible) helps reducing retransmits - Heuristic for choosing the best send channel (of 5 possible) helps reducing retransmits
- optimized receiving handler for HM Inverters with 1 DC input (reduced frequency hopping and reduced answer timeout)
- shortcut radio traces a little bit - shortcut radio traces a little bit
DRAWBACKS: DRAWBACKS:
- MQTT Source is commented out (except 1 var which is used for other purpose as well) - ESP8266: MQTT Source is commented out (except 1 var which is used for other purpose as well)
- only up to 2 Inverters are supported (was: 10) - ESP8266: only up to 2 Inverters are supported (was: 10), for ESP32: 16
- RX/TX of UART0 is used for serial interface to IR sensor. Of course you cannot operate a display that uses RX/TX pins of UART0, simultaneously. And unplug serial connection bevor updating via USB (see also below) - RX/TX of UART0 is used for serial interface to IR sensor. Of course you cannot operate a display that uses RX/TX pins of UART0, simultaneously. And unplug serial connection bevor updating via USB (see also below)
- Due to a non-matching licence model of the charting lib certain parts of visualization.html are commented out. See comments there. - Due to a non-matching licence model of the charting lib certain parts of visualization.html are commented out. See comments there.
- reduced platforms: only 8266 and ESP32 S3 (opendtufusionv1) currently
But: Currently there is enough heap available for stable operation on a ESP8266 plattform (WEMOS D1 R1). So adjust the number of inverters and enable MQTT to your needs and see if the AHOY-DTU is still stable in operation with your hw plattform. But: Currently there is enough heap available for stable operation on a ESP8266 plattform (WEMOS D1 R1). So adjust the number of inverters and enable MQTT to your needs and see if the AHOY-DTU is still stable in operation with your hw plattform.
To update firmware via USB, unplug serial connection to IR sensor first. Surprisingly during normal operation it seems that one can use a fully connected USB cable (for power supply). But I'm not sure if this works for all hardware plattforms. To update firmware via USB, unplug serial connection to IR sensor first. Surprisingly during normal operation it seems that one can use a fully connected USB cable (for power supply). But I'm not sure if this works for all hardware plattforms.

29
src/app.cpp

@ -22,7 +22,11 @@ void app::setup() {
Electricity meter sends SML telegrams via IR interface (9600,8,n,1) without being asked (typical behaviour). Electricity meter sends SML telegrams via IR interface (9600,8,n,1) without being asked (typical behaviour).
An IR sensor is connected to the UART0 of AHOY DTU. Connected pins: GND-GND, 3V3-VCC, RX-RX, TX-TX. An IR sensor is connected to the UART0 of AHOY DTU. Connected pins: GND-GND, 3V3-VCC, RX-RX, TX-TX.
*/ */
#ifdef ESP32
Serial.begin(9600, SERIAL_8N1, RX, -1);
#else
Serial.begin(9600, SERIAL_8N1, SERIAL_RX_ONLY); Serial.begin(9600, SERIAL_8N1, SERIAL_RX_ONLY);
#endif
#else #else
Serial.begin(115200); Serial.begin(115200);
#endif #endif
@ -129,8 +133,8 @@ void app::loopStandard(void) {
DBGPRINT(F(" | ")); DBGPRINT(F(" | "));
mSys.Radio.dumpBuf(p->packet, p->len); mSys.Radio.dumpBuf(p->packet, p->len);
#else #else
DPRINTLN(DBG_INFO, "RX (Ch " + String (p->ch) + "), Delay " + String (p->delay) + DPRINTLN(DBG_INFO, "RX (Ch " + String (p->ch) + "), " +
" us, " + String (p->len) + " Bytes"); String (p->len) + " Bytes");
#endif #endif
} }
@ -512,9 +516,29 @@ void app::check_hist_file (File file)
//----------------------------------------------------------------------------- //-----------------------------------------------------------------------------
void app::show_history (String path) void app::show_history (String path)
{ {
#ifdef ESP32
File dir = LittleFS.open (path);
File file;
#else
Dir dir = LittleFS.openDir (path); Dir dir = LittleFS.openDir (path);
#endif
DPRINTLN (DBG_INFO, "Enter Dir: " + path); DPRINTLN (DBG_INFO, "Enter Dir: " + path);
#ifdef ESP32
if (dir) {
while ((file = dir.openNextFile())) {
if (file.isDirectory()) {
show_history ((char *)file.name());
close (file);
} else {
DPRINTLN (DBG_INFO, "file " + String((char *)file.name()) +
", Size: " + String (file.size()));
check_hist_file (file); // closes file
}
}
dir.close();
}
#else
while (dir.next()) { while (dir.next()) {
if (dir.isDirectory ()) { if (dir.isDirectory ()) {
show_history (path + "/" + dir.fileName()); show_history (path + "/" + dir.fileName());
@ -524,5 +548,6 @@ void app::show_history (String path)
check_hist_file (dir.openFile ("r")); check_hist_file (dir.openFile ("r"));
} }
} }
#endif
DPRINTLN (DBG_INFO, "Leave Dir: " + path); DPRINTLN (DBG_INFO, "Leave Dir: " + path);
} }

10
src/config/config.h

@ -72,7 +72,11 @@
#define PACKET_BUFFER_SIZE 30 #define PACKET_BUFFER_SIZE 30
// number of configurable inverters (increase if you need, and if there is enough heap available for stable operation) // number of configurable inverters (increase if you need, and if there is enough heap available for stable operation)
#if defined(ESP32)
#define MAX_NUM_INVERTERS 16
#else
#define MAX_NUM_INVERTERS 2 #define MAX_NUM_INVERTERS 2
#endif
// default serial interval // default serial interval
#define SERIAL_INTERVAL 5 #define SERIAL_INTERVAL 5
@ -114,8 +118,12 @@
#define AHOY_CHARTDATA_WITH_GRID_HDR "Time, AC Power, Grid Power" #define AHOY_CHARTDATA_WITH_GRID_HDR "Time, AC Power, Grid Power"
#define AHOY_CHARTDATA_HDR "Time, AC Power" #define AHOY_CHARTDATA_HDR "Time, AC Power"
#if defined(ESP32)
#define AHOY_MQTT_SUPPORT
#else
// save RAM
// #define AHOY_MQTT_SUPPORT // #define AHOY_MQTT_SUPPORT
#endif
// default mqtt interval // default mqtt interval
#define MQTT_INTERVAL 90 #define MQTT_INTERVAL 90

1
src/defines.h

@ -19,7 +19,6 @@
typedef struct { typedef struct {
uint8_t ch; uint8_t ch;
uint8_t len; uint8_t len;
long delay; // micros since preceeding tx_fail irq and this rx_ready irq
uint8_t packet[MAX_RF_PAYLOAD_SIZE]; uint8_t packet[MAX_RF_PAYLOAD_SIZE];
} packet_t; } packet_t;

5
src/hm/hmPayload.h

@ -248,13 +248,14 @@ class HmPayload {
if (!mPayload[iv->id].complete) { if (!mPayload[iv->id].complete) {
bool crcPass, pyldComplete; bool crcPass, pyldComplete;
uint8 Fragments; uint8_t Fragments;
crcPass = build(iv->id, &pyldComplete, &Fragments); crcPass = build(iv->id, &pyldComplete, &Fragments);
// evaluate quality of send channel with rcv params // evaluate quality of send channel with rcv params
if (retransmit) { if (retransmit && mPayload[iv->id].requested && (mPayload[iv->id].retransmits < mMaxRetrans)) {
iv->evalTxChanQuality (crcPass, mPayload[iv->id].retransmits, iv->evalTxChanQuality (crcPass, mPayload[iv->id].retransmits,
Fragments, mPayload[iv->id].lastFragments); Fragments, mPayload[iv->id].lastFragments);
DPRINT_IVID(DBG_INFO, iv->id);
DPRINT (DBG_INFO, "Quality: "); DPRINT (DBG_INFO, "Quality: ");
iv->dumpTxChanQuality(); iv->dumpTxChanQuality();
DBGPRINTLN(""); DBGPRINTLN("");

14
src/hm/hmRadio.h

@ -202,10 +202,6 @@ class HmRadio {
} }
void handleIntr(void) { void handleIntr(void) {
if (mRfIrqIndex < MAX_PAYLOAD_ENTRIES) {
// it is allowed to call micros() inside irq handler to read the time (but check for unexpected restart reasons)
mRfIrqTime[mRfIrqIndex++] = micros(); // remember Time of tx_fail and rx_readies
}
mIrqRcvd = true; mIrqRcvd = true;
} }
@ -345,9 +341,6 @@ class HmRadio {
packet_t p; packet_t p;
p.ch = mRxChannels[mRxChIdx]; p.ch = mRxChannels[mRxChIdx];
p.len = len; p.len = len;
if (mRfIrqIndex) {
p.delay = mRfIrqTime[mRfIrqIndex - 1] - mRfIrqTime[0];
}
mNrf24.read(p.packet, len); mNrf24.read(p.packet, len);
if (p.packet[0] != 0x00) { if (p.packet[0] != 0x00) {
mBufCtrl.push(p); mBufCtrl.push(p);
@ -416,7 +409,6 @@ class HmRadio {
mNrf24.stopListening(); mNrf24.stopListening();
mNrf24.setChannel(rf24ChLst[mTxChIdx]); mNrf24.setChannel(rf24ChLst[mTxChIdx]);
mNrf24.openWritingPipe(reinterpret_cast<uint8_t*>(&invId)); mNrf24.openWritingPipe(reinterpret_cast<uint8_t*>(&invId));
mRfIrqIndex = 0;
mNrf24.startWrite(mTxBuf, len, false); // false = request ACK response mNrf24.startWrite(mTxBuf, len, false); // false = request ACK response
if(isRetransmit) if(isRetransmit)
@ -427,8 +419,6 @@ class HmRadio {
volatile bool mIrqRcvd; volatile bool mIrqRcvd;
uint64_t DTU_RADIO_ID; uint64_t DTU_RADIO_ID;
volatile uint8_t mRfIrqIndex;
volatile long mRfIrqTime[1 + MAX_PAYLOAD_ENTRIES];
SPIClass* mSpi; SPIClass* mSpi;
RF24 mNrf24; RF24 mNrf24;
@ -437,8 +427,8 @@ class HmRadio {
uint8_t mRxChIdx; // cur index in mRxChannels uint8_t mRxChIdx; // cur index in mRxChannels
uint8_t *mRxChannels; // rx channel to be used; depends on inverter and previous tx channel uint8_t *mRxChannels; // rx channel to be used; depends on inverter and previous tx channel
uint8_t mMaxRxChannels; // actual size of mRxChannels; depends on inverter and previous tx channel uint8_t mMaxRxChannels; // actual size of mRxChannels; depends on inverter and previous tx channel
uint32 mRxAnswerTmo; // max wait time in millis for answers of inverter uint32_t mRxAnswerTmo; // max wait time in millis for answers of inverter
uint32 mRxChanTmo; // max wait time in micros for a rx channel uint32_t mRxChanTmo; // max wait time in micros for a rx channel
}; };
#endif /*__RADIO_H__*/ #endif /*__RADIO_H__*/

38
src/hm/hmSystem.h

@ -25,7 +25,7 @@ class HmSystem {
Radio.setup(); Radio.setup();
} }
void setup(uint32 *timestamp, uint8_t ampPwr, uint8_t irqPin, uint8_t cePin, uint8_t csPin, uint8_t sclkPin, uint8_t mosiPin, uint8_t misoPin) { void setup(uint32_t *timestamp, uint8_t ampPwr, uint8_t irqPin, uint8_t cePin, uint8_t csPin, uint8_t sclkPin, uint8_t mosiPin, uint8_t misoPin) {
mTimestamp = timestamp; mTimestamp = timestamp;
mNumInv = 0; mNumInv = 0;
Radio.setup(ampPwr, irqPin, cePin, csPin, sclkPin, mosiPin, misoPin); Radio.setup(ampPwr, irqPin, cePin, csPin, sclkPin, mosiPin, misoPin);
@ -147,15 +147,48 @@ class HmSystem {
} }
if ((time_today = *mTimestamp)) { if ((time_today = *mTimestamp)) {
#ifdef ESP32
File ac_power_dir;
File file;
#else
Dir ac_power_dir; Dir ac_power_dir;
#endif
char cur_file_name[sizeof (AC_FORMAT_FILE_NAME)]; char cur_file_name[sizeof (AC_FORMAT_FILE_NAME)];
time_today = gTimezone.toLocal (time_today); time_today = gTimezone.toLocal (time_today);
snprintf (cur_file_name, sizeof (cur_file_name), AC_FORMAT_FILE_NAME, snprintf (cur_file_name, sizeof (cur_file_name), AC_FORMAT_FILE_NAME,
day(time_today), month(time_today), year(time_today)); day(time_today), month(time_today), year(time_today));
ac_power_dir = LittleFS.openDir (AC_POWER_PATH);
/* design: no dataserver, cleanup old history */ /* design: no dataserver, cleanup old history */
#ifdef ESP32
if ((ac_power_dir = LittleFS.open (AC_POWER_PATH))) {
while ((file = ac_power_dir.openNextFile())) {
const char *fullName = file.name();
char *name;
if ((name = strrchr (fullName, '/')) && name[1]) {
name++;
} else {
name = (char *)fullName;
}
if (strcmp (name, cur_file_name)) {
char *path = strdup (fullName);
file.close ();
if (path) {
DPRINTLN (DBG_INFO, "Remove file " + String (name) +
", Size: " + String (ac_power_dir.size()));
LittleFS.remove (path);
free (path);
}
} else {
file.close();
}
}
ac_power_dir.close();
}
#else
ac_power_dir = LittleFS.openDir (AC_POWER_PATH);
while (ac_power_dir.next()) { while (ac_power_dir.next()) {
if (ac_power_dir.fileName() != cur_file_name) { if (ac_power_dir.fileName() != cur_file_name) {
DPRINTLN (DBG_INFO, "Remove file " + ac_power_dir.fileName() + DPRINTLN (DBG_INFO, "Remove file " + ac_power_dir.fileName() +
@ -163,6 +196,7 @@ class HmSystem {
LittleFS.remove (AC_POWER_PATH "/" + ac_power_dir.fileName()); LittleFS.remove (AC_POWER_PATH "/" + ac_power_dir.fileName());
} }
} }
#endif
} else { } else {
DPRINTLN (DBG_WARN, "cleanup_history, no time yet"); DPRINTLN (DBG_WARN, "cleanup_history, no time yet");
} }

2
src/hm/miPayload.h

@ -435,7 +435,7 @@ const byteAssign_t InfoAssignment[] = {
crcPass = build(iv->id, &pyldComplete); crcPass = build(iv->id, &pyldComplete);
// evaluate quality of send channel with rcv params // evaluate quality of send channel with rcv params
if (retransmit) { if (retransmit && mPayload[iv->id].requested && (mPayload[iv->id].retransmits < mMaxRetrans)) {
iv->evalTxChanQuality (crcPass, mPayload[iv->id].retransmits, iv->evalTxChanQuality (crcPass, mPayload[iv->id].retransmits,
mPayload[iv->id].fragments, mPayload[iv->id].lastFragments); mPayload[iv->id].fragments, mPayload[iv->id].lastFragments);
DPRINT (DBG_INFO, "Quality: "); DPRINT (DBG_INFO, "Quality: ");

20
src/platformio.ini

@ -13,9 +13,6 @@ src_dir = .
include_dir = . include_dir = .
[env] [env]
board = d1_mini
board_build.f_cpu = 80000000L
platform=https://github.com/platformio/platform-espressif8266.git@^4.2.0
framework = arduino framework = arduino
board_build.filesystem = littlefs board_build.filesystem = littlefs
upload_speed = 921600 upload_speed = 921600
@ -34,5 +31,22 @@ lib_deps =
zinggjm/GxEPD2 @ ^1.5.0 zinggjm/GxEPD2 @ ^1.5.0
[env:esp8266-release] [env:esp8266-release]
platform = espressif8266
board = esp12e
board_build.f_cpu = 80000000L
build_flags = -D RELEASE build_flags = -D RELEASE
monitor_filters = esp8266_exception_decoder monitor_filters = esp8266_exception_decoder
[env:opendtufusionv1-release]
platform = espressif32@>=6.1.0
board = esp32-s3-devkitc-1
upload_protocol = esp-builtin
upload_speed = 115200
debug_tool = esp-builtin
debug_speed = 12000
build_flags = -D RELEASE -std=gnu++14
build_unflags = -std=gnu++11
monitor_filters =
;default ; Remove typical terminal control codes from input
time ; Add timestamp with milliseconds for each new line
;log2file ; Log data to a file “platformio-device-monitor-*.log” located in the current working directory

48
src/plugins/SML_OBIS_Parser.cpp

@ -1,7 +1,9 @@
#include <string.h> #include <string.h>
#include <stdlib.h> #include <stdlib.h>
#include <HardwareSerial.h> #include <HardwareSerial.h>
#ifndef ESP32
#include <user_interface.h> #include <user_interface.h>
#endif
#include "../utils/dbg.h" #include "../utils/dbg.h"
#include "../utils/scheduler.h" #include "../utils/scheduler.h"
#include "../config/settings.h" #include "../config/settings.h"
@ -86,9 +88,9 @@ static uint16_t cur_sml_list_layer;
static unsigned char sml_list_layer_entries [SML_MAX_LIST_LAYER]; static unsigned char sml_list_layer_entries [SML_MAX_LIST_LAYER];
static unsigned char sml_serial_buf[SML_MAX_SERIAL_BUF]; static unsigned char sml_serial_buf[SML_MAX_SERIAL_BUF];
static unsigned char *cur_serial_buf = sml_serial_buf; static unsigned char *cur_serial_buf = sml_serial_buf;
static uint16 sml_serial_len = 0; static uint16_t sml_serial_len = 0;
static uint16 sml_skip_len = 0; static uint16_t sml_skip_len = 0;
static uint32 sml_message = SML_MSG_NONE; static uint32_t sml_message = SML_MSG_NONE;
static obis_state_t obis_state = OBIS_ST_NONE; static obis_state_t obis_state = OBIS_ST_NONE;
static int obis_power_all_scale, obis_power_all_value; static int obis_power_all_scale, obis_power_all_value;
/* design: max 16 bit fuer aktuelle Powerwerte */ /* design: max 16 bit fuer aktuelle Powerwerte */
@ -306,14 +308,47 @@ void sml_cleanup_history ()
obis_cur_pac_index = 0; obis_cur_pac_index = 0;
obis_pac_sum = 0; obis_pac_sum = 0;
if ((time_today = *obis_timestamp)) { if ((time_today = *obis_timestamp)) {
#ifdef ESP32
File grid_power_dir;
File file;
#else
Dir grid_power_dir; Dir grid_power_dir;
#endif
char cur_file_name[sizeof (SML_OBIS_FORMAT_FILE_NAME)]; char cur_file_name[sizeof (SML_OBIS_FORMAT_FILE_NAME)];
time_today = gTimezone.toLocal (time_today); time_today = gTimezone.toLocal (time_today);
snprintf (cur_file_name, sizeof (cur_file_name), SML_OBIS_FORMAT_FILE_NAME, snprintf (cur_file_name, sizeof (cur_file_name), SML_OBIS_FORMAT_FILE_NAME,
day(time_today), month (time_today), year (time_today)); day(time_today), month (time_today), year (time_today));
grid_power_dir = LittleFS.openDir (SML_OBIS_GRID_POWER_PATH);
/* design: no dataserver, cleanup old history */ /* design: no dataserver, cleanup old history */
#ifdef ESP32
grid_power_dir = LittleFS.open (SML_OBIS_GRID_POWER_PATH);
if (grid_power_dir) {
while ((file = grid_power_dir.openNextFile())) {
const char *fullName = file.name();
char *name;
if ((name = strrchr (fullName, '/')) && name[1]) {
name++;
} else {
name = (char *)fullName;
}
if (strcmp (name, cur_file_name)) {
char *path = strdup (fullName);
file.close ();
if (path) {
DPRINTLN (DBG_INFO, "Remove file " + String(name) +
", Size: " + String (grid_power_dir.size()));
LittleFS.remove (path);
free (path);
}
} else {
file.close();
}
}
grid_power_dir.close();
}
#else
grid_power_dir = LittleFS.openDir (SML_OBIS_GRID_POWER_PATH);
while (grid_power_dir.next()) { while (grid_power_dir.next()) {
if (grid_power_dir.fileName() != cur_file_name) { if (grid_power_dir.fileName() != cur_file_name) {
@ -322,6 +357,7 @@ void sml_cleanup_history ()
LittleFS.remove (SML_OBIS_GRID_POWER_PATH "/" + grid_power_dir.fileName()); LittleFS.remove (SML_OBIS_GRID_POWER_PATH "/" + grid_power_dir.fileName());
} }
} }
#endif
} else { } else {
DPRINTLN (DBG_WARN, "sml_cleanup_history, no time yet"); DPRINTLN (DBG_WARN, "sml_cleanup_history, no time yet");
} }
@ -627,7 +663,7 @@ bool sml_get_list_entries (uint16_t layer)
unsigned char entry_len; unsigned char entry_len;
// Acc. to Spec there might be len_info > 2. But does this happen in real life? // Acc. to Spec there might be len_info > 2. But does this happen in real life?
// Also: an info_len > 2 could be due to corrupt data. So better break. // Also: an info_len > 2 could be due to corrupt data. So better break.
uint16 len_info = (*cur_serial_buf & SML_EXT_LENGTH) ? 2 : 1; uint16_t len_info = (*cur_serial_buf & SML_EXT_LENGTH) ? 2 : 1;
if (sml_serial_len < len_info) { if (sml_serial_len < len_info) {
if (cur_serial_buf > sml_serial_buf) { if (cur_serial_buf > sml_serial_buf) {
@ -795,7 +831,7 @@ bool sml_get_list_entries (uint16_t layer)
} }
//----------------------------------------------------------------------------- //-----------------------------------------------------------------------------
uint16_t sml_parse_stream (uint16 len) uint16_t sml_parse_stream (uint16_t len)
{ {
bool parse_continue; bool parse_continue;
uint16_t serial_read; uint16_t serial_read;

2
src/plugins/SML_OBIS_Parser.h

@ -13,7 +13,7 @@ void sml_close_hist (File file);
int sml_find_hist_power (File file, uint16_t index); int sml_find_hist_power (File file, uint16_t index);
int16_t sml_get_obis_pac (); int16_t sml_get_obis_pac ();
int16_t sml_get_obis_pac_average (); int16_t sml_get_obis_pac_average ();
uint16_t sml_parse_stream (uint16 len); uint16_t sml_parse_stream (uint16_t len);
void sml_set_trace_obis (bool trace_flag); void sml_set_trace_obis (bool trace_flag);
void sml_loop(); void sml_loop();

Loading…
Cancel
Save