Browse Source

init working

pull/478/head
golfi200 3 years ago
parent
commit
6cfe9db28b
  1. 8
      tools/nano/AhoyUL/.gitignore
  2. 39
      tools/nano/AhoyUL/include/README
  3. 46
      tools/nano/AhoyUL/lib/README
  4. 51
      tools/nano/AhoyUL/platformio.ini
  5. 9
      tools/nano/AhoyUL/src/AhoyUL.code-workspace
  6. 164
      tools/nano/AhoyUL/src/CircularBuffer.h
  7. 76
      tools/nano/AhoyUL/src/config.h
  8. 38
      tools/nano/AhoyUL/src/crc.cpp
  9. 23
      tools/nano/AhoyUL/src/crc.h
  10. 198
      tools/nano/AhoyUL/src/dbg.h
  11. 154
      tools/nano/AhoyUL/src/defines.h
  12. 265
      tools/nano/AhoyUL/src/hmDefines.h
  13. 549
      tools/nano/AhoyUL/src/hmRadio.h
  14. 834
      tools/nano/AhoyUL/src/main.cpp
  15. 233
      tools/nano/AhoyUL/src/utils_serial.h
  16. 11
      tools/nano/AhoyUL/test/README

8
tools/nano/AhoyUL/.gitignore

@ -0,0 +1,8 @@
.pio
.vscode/.browse.c_cpp.db*
.vscode/c_cpp_properties.json
.vscode/launch.json
.vscode/ipch
.log
config_override.h

39
tools/nano/AhoyUL/include/README

@ -0,0 +1,39 @@
This directory is intended for project header files.
A header file is a file containing C declarations and macro definitions
to be shared between several project source files. You request the use of a
header file in your project source file (C, C++, etc) located in `src` folder
by including it, with the C preprocessing directive `#include'.
```src/main.c
#include "header.h"
int main (void)
{
...
}
```
Including a header file produces the same results as copying the header file
into each source file that needs it. Such copying would be time-consuming
and error-prone. With a header file, the related declarations appear
in only one place. If they need to be changed, they can be changed in one
place, and programs that include the header file will automatically use the
new version when next recompiled. The header file eliminates the labor of
finding and changing all the copies as well as the risk that a failure to
find one copy will result in inconsistencies within a program.
In C, the usual convention is to give header files names that end with `.h'.
It is most portable to use only letters, digits, dashes, and underscores in
header file names, and at most one dot.
Read more about using header files in official GCC documentation:
* Include Syntax
* Include Operation
* Once-Only Headers
* Computed Includes
https://gcc.gnu.org/onlinedocs/cpp/Header-Files.html

46
tools/nano/AhoyUL/lib/README

@ -0,0 +1,46 @@
This directory is intended for project specific (private) libraries.
PlatformIO will compile them to static libraries and link into executable file.
The source code of each library should be placed in a an own separate directory
("lib/your_library_name/[here are source files]").
For example, see a structure of the following two libraries `Foo` and `Bar`:
|--lib
| |
| |--Bar
| | |--docs
| | |--examples
| | |--src
| | |- Bar.c
| | |- Bar.h
| | |- library.json (optional, custom build options, etc) https://docs.platformio.org/page/librarymanager/config.html
| |
| |--Foo
| | |- Foo.c
| | |- Foo.h
| |
| |- README --> THIS FILE
|
|- platformio.ini
|--src
|- main.c
and a contents of `src/main.c`:
```
#include <Foo.h>
#include <Bar.h>
int main (void)
{
...
}
```
PlatformIO Library Dependency Finder will find automatically dependent
libraries scanning project source files.
More information about PlatformIO Library Dependency Finder
- https://docs.platformio.org/page/librarymanager/ldf.html

51
tools/nano/AhoyUL/platformio.ini

@ -0,0 +1,51 @@
; 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:nanoatmega328]
platform = atmelavr
board = nanoatmega328new
framework = arduino
upload_speed = 115200
;monitor_speed = 57600
; change microcontroller
board_build.mcu = atmega328p
; change MCU frequency
;board_build.f_cpu = 16000000L
monitor_speed = 57600
lib_deps =
nrf24/RF24@^1.4.5
monitor_filters =
send_on_enter
;default ; Remove typical terminal control codes from input
time ; Add timestamp with milliseconds for each new line
log2file ; Log data to a file “./*-monitor.log” located in the current working directory
[env:esp8266-release]
platform = espressif8266
board = esp12e
framework = arduino
board_build.f_cpu = 80000000L
build_flags = -D RELEASE
monitor_speed = 57600
lib_deps =
nrf24/RF24
monitor_filters =
send_on_enter
;default ; Remove typical terminal control codes from input
time ; Add timestamp with milliseconds for each new line
log2file ; Log data to a file “./*-monitor.log” located in the current working directory

9
tools/nano/AhoyUL/src/AhoyUL.code-workspace

@ -0,0 +1,9 @@
{
"folders": [
{
"name": "AUL",
"path": ".."
}
],
"settings": {}
}

164
tools/nano/AhoyUL/src/CircularBuffer.h

@ -0,0 +1,164 @@
/*
CircularBuffer - An Arduino circular buffering library for arbitrary types.
Created by Ivo Pullens, Emmission, 2014 -- www.emmission.nl
This library is free software; you can redistribute it and/or
modify it under the terms of the GNU Lesser General Public
License as published by the Free Software Foundation; either
version 2.1 of the License, or (at your option) any later version.
This library 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
Lesser General Public License for more details.
You should have received a copy of the GNU Lesser General Public
License along with this library; if not, write to the Free Software
Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
*/
#ifndef CircularBuffer_h
#define CircularBuffer_h
#ifdef ESP8266
#define DISABLE_IRQ noInterrupts()
#define RESTORE_IRQ interrupts()
#elif defined(ESP32)
#define DISABLE_IRQ noInterrupts()
#define RESTORE_IRQ interrupts()
#else
#define DISABLE_IRQ \
uint8_t sreg = SREG; \
cli();
#define RESTORE_IRQ \
SREG = sreg;
#endif
template <class BUFFERTYPE, uint8_t BUFFERSIZE>
class CircularBuffer {
typedef BUFFERTYPE BufferType;
BufferType Buffer[BUFFERSIZE];
public:
CircularBuffer() : m_buff(Buffer) {
m_size = BUFFERSIZE;
clear();
}
/** Clear all entries in the circular buffer. */
void clear(void)
{
m_front = 0;
m_fill = 0;
}
/** Test if the circular buffer is empty */
inline bool empty(void) const
{
return !m_fill;
}
/** Return the number of records stored in the buffer */
inline uint8_t available(void) const
{
return m_fill;
}
/** Test if the circular buffer is full */
inline bool full(void) const
{
return m_fill == m_size;
}
inline uint8_t getFill(void) const {
return m_fill;
}
/** Aquire record on front of the buffer, for writing.
* After filling the record, it has to be pushed to actually
* add it to the buffer.
* @return Pointer to record, or NULL when buffer is full.
*/
BUFFERTYPE* getFront(void) const
{
DISABLE_IRQ;
BUFFERTYPE* f = NULL;
if (!full())
f = get(m_front);
RESTORE_IRQ;
return f;
}
/** Push record to front of the buffer
* @param record Record to push. If record was aquired previously (using getFront) its
* data will not be copied as it is already present in the buffer.
* @return True, when record was pushed successfully.
*/
bool pushFront(BUFFERTYPE* record)
{
bool ok = false;
DISABLE_IRQ;
if (!full())
{
BUFFERTYPE* f = get(m_front);
if (f != record)
*f = *record;
m_front = (m_front+1) % m_size;
m_fill++;
ok = true;
}
RESTORE_IRQ;
return ok;
}
/** Aquire record on back of the buffer, for reading.
* After reading the record, it has to be pop'ed to actually
* remove it from the buffer.
* @return Pointer to record, or NULL when buffer is empty.
*/
BUFFERTYPE* getBack(void) const
{
BUFFERTYPE* b = NULL;
DISABLE_IRQ;
if (!empty())
b = get(back());
RESTORE_IRQ;
return b;
}
/** Remove record from back of the buffer.
* @return True, when record was pop'ed successfully.
*/
bool popBack(void)
{
bool ok = false;
DISABLE_IRQ;
if (!empty())
{
m_fill--;
ok = true;
}
RESTORE_IRQ;
return ok;
}
protected:
inline BUFFERTYPE * get(const uint8_t idx) const
{
return &(m_buff[idx]);
}
inline uint8_t back(void) const
{
return (m_front - m_fill + m_size) % m_size;
}
uint8_t m_size; // Total number of records that can be stored in the buffer.
BUFFERTYPE* const m_buff;
volatile uint8_t m_front; // Index of front element (not pushed yet).
volatile uint8_t m_fill; // Amount of records currently pushed.
};
#endif // CircularBuffer_h

76
tools/nano/AhoyUL/src/config.h

@ -0,0 +1,76 @@
//-----------------------------------------------------------------------------
// 2022 Ahoy, https://www.mikrocontroller.net/topic/525778
// Creative Commons - http://creativecommons.org/licenses/by-nc-sa/3.0/de/
//-----------------------------------------------------------------------------
#ifndef __CONFIG_H__
#define __CONFIG_H__
#include <stdint.h>
//-------------------------------------
// CONFIGURATION - COMPILE TIME
//-------------------------------------
#if defined(ESP8266)
// for esp8266 environment
#define DEF_DEVICE_NAME "\n\n +++ AHOY-UL ESP8266 +++"
#define DEF_RF24_CS_PIN 15
#define DEF_RF24_CE_PIN 2
#define DEF_RF24_IRQ_PIN 0
#else
// default pinout (GPIO Number) for Arduino Nano 328p
#define DEF_DEVICE_NAME "\n\n +++ AHOY-UL A-NANO +++"
#define DEF_RF24_CE_PIN (4)
#define DEF_RF24_CS_PIN (10)
#define DEF_RF24_IRQ_PIN (3)
#endif
// default radio ID
#define DTU_RADIO_ID ((uint64_t)0x1234567801ULL)
// real inverter ID is taken from config_override.h at the bottom of this file
#define IV1_RADIO_ID ((uint64_t) 0x114144332211ULL) // 0x1141 is for HM800, lowerb4bytes must be filled with real ID from INV-plate
// default NRF24 power, possible values (0 - 3)
#define DEF_AMPLIFIERPOWER 2
// number of packets hold in buffer
#define PACKET_BUFFER_SIZE 7
// number of configurable inverters
#define MAX_NUM_INVERTERS 1
// default serial interval
#define SERIAL_INTERVAL 5
// default send interval
#define SEND_INTERVAL 60 //send interval if Rx OK
#define SEND_INTERVAL_MIN 10 //send interval if no RX (used initial sync or signal loss), todo: shall be disabled over night
// maximum human readable inverter name length
#define MAX_NAME_LENGTH 16
// maximum buffer length of packet received / sent to RF24 module
#define MAX_RF_PAYLOAD_SIZE 32
// maximum total payload buffers (must be greater than the number of received frame fragments)
#define MAX_PAYLOAD_ENTRIES 8
// maximum requests for retransmits per payload (per inverter)
#define DEF_MAX_RETRANS_PER_PYLD 10
// number of seconds since last successful response, before inverter is marked inactive
#define INACT_THRES_SEC 300
// threshold of minimum power on which the inverter is marked as inactive
#define INACT_PWR_THRESH 3
#if __has_include("config_override.h")
#include "config_override.h"
#endif
#endif /*__CONFIG_H__*/

38
tools/nano/AhoyUL/src/crc.cpp

@ -0,0 +1,38 @@
//-----------------------------------------------------------------------------
// 2022 Ahoy, https://www.mikrocontroller.net/topic/525778
// Creative Commons - http://creativecommons.org/licenses/by-nc-sa/3.0/de/
//-----------------------------------------------------------------------------
#include "crc.h"
namespace Hoymiles {
uint8_t crc8(uint8_t buf[], uint8_t len) {
uint8_t crc = CRC8_INIT;
for(uint8_t i = 0; i < len; i++) {
crc ^= buf[i];
for(uint8_t b = 0; b < 8; b ++) {
crc = (crc << 1) ^ ((crc & 0x80) ? CRC8_POLY : 0x00);
}
yield();
}
return crc;
}
uint16_t crc16(uint8_t buf[], uint8_t len, uint16_t start) {
uint16_t crc = start;
uint8_t shift = 0;
for(uint8_t i = 0; i < len; i ++) {
crc = crc ^ buf[i];
for(uint8_t bit = 0; bit < 8; bit ++) {
shift = (crc & 0x0001);
crc = crc >> 1;
if(shift != 0)
crc = crc ^ CRC16_MODBUS_POLYNOM;
}
yield();
}
return crc;
}
} // namespace Hoymiles

23
tools/nano/AhoyUL/src/crc.h

@ -0,0 +1,23 @@
//-----------------------------------------------------------------------------
// 2022 Ahoy, https://www.mikrocontroller.net/topic/525778
// Creative Commons - http://creativecommons.org/licenses/by-nc-sa/3.0/de/
//-----------------------------------------------------------------------------
#ifndef __CRC_H__
#define __CRC_H__
#include <stdint.h>
#include "Arduino.h"
#define CRC8_INIT 0x00
#define CRC8_POLY 0x01
#define CRC16_MODBUS_POLYNOM 0xA001
namespace Hoymiles {
uint8_t crc8(uint8_t buf[], uint8_t len);
uint16_t crc16(uint8_t buf[], uint8_t len, uint16_t start = 0xffff);
}
#endif /*__CRC_H__*/

198
tools/nano/AhoyUL/src/dbg.h

@ -0,0 +1,198 @@
//-----------------------------------------------------------------------------
// 2022 Ahoy, https://www.mikrocontroller.net/topic/525778
// Creative Commons - http://creativecommons.org/licenses/by-nc-sa/3.0/de/
//-----------------------------------------------------------------------------
#ifndef __DBG_H__
#define __DBG_H__
#if defined(ESP32) && defined(F)
#undef F
#define F(sl) (sl)
#endif
//-----------------------------------------------------------------------------
// available levels
#define DBG_ERROR 1
#define DBG_WARN 2
#define DBG_INFO 3
#define DBG_DEBUG 4
#define DBG_VERBOSE 5
//-----------------------------------------------------------------------------
// globally used level
#define DEBUG_LEVEL DBG_INFO //adapt DEBUG_LEVEL to your needs
#ifdef ARDUINO
#include "Arduino.h"
#endif
#ifdef NDEBUG
#define DBGPRINT(str)
#define DBGPRINTLN(str)
#else
#ifdef ARDUINO
#ifndef DSERIAL
#define DSERIAL Serial
#endif
template <class T>
inline void DBGPRINT(T str) { DSERIAL.print(str); }
template <class T>
inline void DBGPRINTLN(T str) { DBGPRINT(str); DBGPRINT(F("\r\n")); }
inline void DHEX(uint8_t b) {
if( b<0x10 ) DSERIAL.print('0');
DSERIAL.print(b,HEX);
}
inline void DHEX(uint16_t b) {
if( b<0x10 ) DSERIAL.print(F("000"));
else if( b<0x100 ) DSERIAL.print(F("00"));
else if( b<0x1000 ) DSERIAL.print(F("0"));
DSERIAL.print(b,HEX);
}
inline void DHEX(uint32_t b) {
if( b<0x10 ) DSERIAL.print(F("0000000"));
else if( b<0x100 ) DSERIAL.print(F("000000"));
else if( b<0x1000 ) DSERIAL.print(F("00000"));
else if( b<0x10000 ) DSERIAL.print(F("0000"));
else if( b<0x100000 ) DSERIAL.print(F("000"));
else if( b<0x1000000 ) DSERIAL.print(F("00"));
else if( b<0x10000000 ) DSERIAL.print(F("0"));
DSERIAL.print(b,HEX);
}
#endif
#endif
#if DEBUG_LEVEL >= DBG_ERROR
#define PERR(str) DBGPRINT(F("\nE: ")); DBGPRINT(str);
#define PERRLN(str) DBGPRINT(F("\nE: ")); DBGPRINTLN(str);
#define _PERR(str) DBGPRINT(str);
#define _PERRLN(str) DBGPRINTLN(str);
#define _PERRHEX(str) DHEX(str);
#else
//no output
#define PERR(str)
#define PERRLN(str)
#define _PERR(str)
#define _PERRLN(str)
#define _PERRHEX(str)
#endif
#if DEBUG_LEVEL >= DBG_WARN
#define PWARN(str) DBGPRINT(F("\nW: ")); DBGPRINT(str);
#define PWARNLN(str) DBGPRINT(F("\nW: ")); DBGPRINTLN(str);
#define _PWARN(str) DBGPRINT(str);
#define _PWARNLN(str) DBGPRINTLN(str);
#define _PWARNHEX(str) DHEX(str);
#else
//no output
#define PWARN(str)
#define PWARNLN(str)
#define _PWARN(str)
#define _PWARNLN(str)
#define _PWARNHEX(str)
#endif
#if DEBUG_LEVEL >= DBG_INFO
#define PINFO(str) DBGPRINT(F("\nI: ")); DBGPRINT(str);
#define PINFOLN(str) DBGPRINT(F("\nI: ")); DBGPRINTLN(str);
#define _PINFO(str) DBGPRINT(str);
#define _PINFOLN(str) DBGPRINTLN(str);
#define _PINFOHEX(str) DHEX(str);
#else
//no output
#define PINFO(str)
#define PINFOLN(str)
#define _PINFO(str)
#define _PINFOLN(str)
#define _PINFOHEX(str)
#endif
#if DEBUG_LEVEL >= DBG_DEBUG
#define PDBG(str) DBGPRINT(F("\nD: ")); DBGPRINT(str);
#define PDBGLN(str) DBGPRINT(F("D: ")); DBGPRINTLN(str);
#define _PDBG(str) DBGPRINT(str);
#define _PDBGLN(str) DBGPRINTLN(str);
#define _PDBGHEX(str) DHEX(str);
#else
//no output
#define PDBG(str)
#define PDBGLN(str)
#define _PDBG(str)
#define _PDBGLN(str)
#define _PDBGHEX(str)
#endif
#if DEBUG_LEVEL >= DBG_VERBOSE
#define PVERB(str) DBGPRINT(F("\nV: ")); DBGPRINT(str);
#define PVERBLN(str) DBGPRINT(F("\nV: ")); DBGPRINTLN(str);
#define _PVERB(str) DBGPRINT(str);
#define _PVERBLN(str) DBGPRINTLN(str);
#define _PVERBHEX(str) DHEX(str);
#else
#define PVERB(str)
#define PVERBLN(str)
#define _PVERB(str)
#define _PVERBLN(str)
#define _PVERBHEX(str)
#endif
//external methods
#define DPRINT(level, str) ({\
switch(level) {\
case DBG_ERROR: PERR(str); break; \
case DBG_WARN: PWARN(str); break; \
case DBG_INFO: PINFO(str); break; \
case DBG_DEBUG: PDBG(str); break; \
default: PVERB(str); break; \
}\
})
#define DPRINTLN(level, str) ({\
switch(level) {\
case DBG_ERROR: PERRLN(str); break; \
case DBG_WARN: PWARNLN(str); break; \
case DBG_INFO: PINFOLN(str); break; \
case DBG_DEBUG: PDBGLN(str); break; \
default: PVERBLN(str); break; \
}\
})
#define _DPRINT(level, str) ({\
switch(level) {\
case DBG_ERROR: _PERR(str); break; \
case DBG_WARN: _PWARN(str); break; \
case DBG_INFO: _PINFO(str); break; \
case DBG_DEBUG: _PDBG(str); break; \
default: _PVERB(str); break; \
}\
})
#define _DPRINTLN(level, str) ({\
switch(level) {\
case DBG_ERROR: _PERRLN(str); break; \
case DBG_WARN: _PWARNLN(str); break; \
case DBG_INFO: _PINFOLN(str); break; \
case DBG_DEBUG: _PDBGLN(str); break; \
default: _PVERBLN(str); break; \
}\
})
//2022-10-30: added hex output
#define _DPRINTHEX(level, str) ({\
switch(level) {\
case DBG_ERROR: _PERRHEX(str); break; \
case DBG_WARN: _PWARNHEX(str); break; \
case DBG_INFO: _PINFOHEX(str); break; \
case DBG_DEBUG: _PDBGHEX(str); break; \
default: _PVERBHEX(str); break; \
}\
})
#endif /*__DBG_H__*/

154
tools/nano/AhoyUL/src/defines.h

@ -0,0 +1,154 @@
//-----------------------------------------------------------------------------
// 2022 Ahoy, https://www.mikrocontroller.net/topic/525778
// Creative Commons - http://creativecommons.org/licenses/by-nc-sa/3.0/de/
//-----------------------------------------------------------------------------
//2022: mb some adaptation for Arduinio Nano version
#ifndef __DEFINES_H__
#define __DEFINES_H__
#include "config.h"
//-------------------------------------
// VERSION
//-------------------------------------
#define VERSION_MAJOR 0
#define VERSION_MINOR 5
#define VERSION_PATCH 17+
//-------------------------------------
typedef struct {
uint8_t rfch;
uint8_t plen;
uint8_t data[MAX_RF_PAYLOAD_SIZE];
} packet_t;
typedef struct {
uint8_t txId;
uint8_t invId[5]; // 5byte inverter address field is used
uint16_t invType; // 2bytes inverter type header e.g. 0x1141 for hoymiles 2 channel type
volatile uint32_t ts; //timestamp in millisec
uint8_t data[MAX_PAYLOAD_ENTRIES][MAX_RF_PAYLOAD_SIZE];
uint8_t len[MAX_PAYLOAD_ENTRIES];
bool receive; //indicates that at least one packet was received and stored --> trigger retransmission of missing packets
uint8_t maxPackId; //indicates the last fragment id was received and defines the end of payload data packet
uint8_t retransmits;
bool requested; //indicates the sending requst
uint8_t rxChIdx; //mb 2022-10-30: added to keep the last rx channel index for the evaluated payload. I think its needed to handle different inverter simultaniously, which can work on different frequencies.
bool isMACPacket;
} invPayload_t;
typedef enum {
InverterDevInform_Simple = 0, // 0x00
InverterDevInform_All = 1, // 0x01
GridOnProFilePara = 2, // 0x02
HardWareConfig = 3, // 0x03
SimpleCalibrationPara = 4, // 0x04
SystemConfigPara = 5, // 0x05
RealTimeRunData_Debug = 11, // 0x0b
RealTimeRunData_Reality = 12, // 0x0c
RealTimeRunData_A_Phase = 13, // 0x0d
RealTimeRunData_B_Phase = 14, // 0x0e
RealTimeRunData_C_Phase = 15, // 0x0f
AlarmData = 17, // 0x11, Alarm data - all unsent alarms
AlarmUpdate = 18, // 0x12, Alarm data - all pending alarms
RecordData = 19, // 0x13
InternalData = 20, // 0x14
GetLossRate = 21, // 0x15
GetSelfCheckState = 30, // 0x1e
InitDataState = 0xff
} InfoCmdType;
typedef enum {
TurnOn = 0, // 0x00
TurnOff = 1, // 0x01
Restart = 2, // 0x02
Lock = 3, // 0x03
Unlock = 4, // 0x04
ActivePowerContr = 11, // 0x0b
ReactivePowerContr = 12, // 0x0c
PFSet = 13, // 0x0d
CleanState_LockAndAlarm = 20, // 0x14
SelfInspection = 40, // 0x28, self-inspection of grid-connected protection files
Init = 0xff
} DevControlCmdType;
typedef enum { // ToDo: to be verified by field tests
NoPowerLimit = 0xffff, // ahoy internal value, no hoymiles value!
AbsolutNonPersistent = 0UL, // 0x0000
RelativNonPersistent = 1UL, // 0x0001
AbsolutPersistent = 256UL, // 0x0100
RelativPersistent = 257UL // 0x0101
} PowerLimitControlType;
// minimum serial interval
#define MIN_SERIAL_INTERVAL 5
// minimum send interval
#define MIN_SEND_INTERVAL 15
#define INV_ADDR_LEN MAX_NUM_INVERTERS * 8 // uint64_t
#define INV_NAME_LEN MAX_NUM_INVERTERS * MAX_NAME_LENGTH // char[]
#define INV_CH_CH_PWR_LEN MAX_NUM_INVERTERS * 2 * 4 // uint16_t (4 channels)
#define INV_CH_CH_NAME_LEN MAX_NUM_INVERTERS * MAX_NAME_LENGTH * 4 // (4 channels)
#define INV_INTERVAL_LEN 2 // uint16_t
#define INV_MAX_RTRY_LEN 1 // uint8_t
#define INV_PWR_LIM_LEN MAX_NUM_INVERTERS * 2 // uint16_t
#define PINOUT_LEN 3 // 3 pins: CS, CE, IRQ
#define RF24_AMP_PWR_LEN 1
#define SER_ENABLE_LEN 1 // uint8_t
#define SER_DEBUG_LEN 1 // uint8_t
#define SER_INTERVAL_LEN 2 // uint16_t
typedef struct {
// nrf24
uint16_t sendInterval;
uint8_t maxRetransPerPyld;
uint8_t pinCs;
uint8_t pinCe;
uint8_t pinIrq;
uint8_t amplifierPower;
// serial
uint16_t serialInterval;
bool serialShowIv;
bool serialDebug;
} /*__attribute__((__packed__))*/ config_t;
#define ADDR_CFG ADDR_START_SETTINGS
#define ADDR_CFG_INVERTER ADDR_CFG + CFG_LEN
#define ADDR_INV_ADDR ADDR_CFG_INVERTER
#define ADDR_INV_NAME ADDR_INV_ADDR + INV_ADDR_LEN
#define ADDR_INV_CH_PWR ADDR_INV_NAME + INV_NAME_LEN
#define ADDR_INV_CH_NAME ADDR_INV_CH_PWR + INV_CH_CH_PWR_LEN
#define ADDR_INV_INTERVAL ADDR_INV_CH_NAME + INV_CH_CH_NAME_LEN
#define ADDR_INV_MAX_RTRY ADDR_INV_INTERVAL + INV_INTERVAL_LEN
#define ADDR_INV_PWR_LIM ADDR_INV_MAX_RTRY + INV_MAX_RTRY_LEN
#define ADDR_INV_PWR_LIM_CON ADDR_INV_PWR_LIM + INV_PWR_LIM_LEN
#define ADDR_NEXT ADDR_INV_PWR_LIM_CON + INV_PWR_LIM_LEN
#define ADDR_SETTINGS_CRC ADDR_NEXT + 2
#if(ADDR_SETTINGS_CRC <= ADDR_NEXT)
#pragma error "address overlap! (ADDR_SETTINGS_CRC="+ ADDR_SETTINGS_CRC +", ADDR_NEXT="+ ADDR_NEXT +")"
#endif
#if(ADDR_SETTINGS_CRC >= 4096 - CRC_LEN)
#pragma error "EEPROM size exceeded! (ADDR_SETTINGS_CRC="+ ADDR_SETTINGS_CRC +", CRC_LEN="+ CRC_LEN +")"
#pragma error "Configure less inverters? (MAX_NUM_INVERTERS=" + MAX_NUM_INVERTERS +")"
#endif
#endif /*__DEFINES_H__*/

265
tools/nano/AhoyUL/src/hmDefines.h

@ -0,0 +1,265 @@
//-----------------------------------------------------------------------------
// 2022 Ahoy, https://github.com/lumpapu/ahoy
// Creative Commons - http://creativecommons.org/licenses/by-nc-sa/3.0/de/
//-----------------------------------------------------------------------------
//2022 mb using PROGMEM for lage char* arrays, used and tested for HM800 decoding (2channel), no calculation yet
#ifndef __HM_DEFINES_H__
#define __HM_DEFINES_H__
#include "dbg.h"
union serial_u {
uint64_t u64;
uint8_t b[8];
};
// units
enum {UNIT_V = 0, UNIT_A, UNIT_W, UNIT_WH, UNIT_KWH, UNIT_HZ, UNIT_C, UNIT_PCT, UNIT_VAR, UNIT_NONE};
//const char* const units[] = {" V", " A", " W", " Wh", " kWh", " Hz", " °C", " %", " var", ""};
const char PGM_units[][5] PROGMEM {" V", " A", " W", " Wh", " kWh", " Hz", " °C", " %", " var", ""};
// field types
enum {FLD_UDC = 0, FLD_IDC, FLD_PDC, FLD_YD, FLD_YW, FLD_YT,
FLD_UAC, FLD_IAC, FLD_PAC, FLD_F, FLD_T, FLD_PF, FLD_EFF,
FLD_IRR, FLD_Q, FLD_EVT, FLD_FW_VERSION, FLD_FW_BUILD_YEAR,
FLD_FW_BUILD_MONTH_DAY, FLD_FW_BUILD_HOUR_MINUTE, FLD_HW_ID,
FLD_ACT_ACTIVE_PWR_LIMIT, /*FLD_ACT_REACTIVE_PWR_LIMIT, FLD_ACT_PF,*/ FLD_LAST_ALARM_CODE};
//PGM Flash memory usage instead of RAM for ARDUINO NANO, idea given by Nick Gammon great webpage http://gammon.com.au/progmem
const char PGM_fields[/*NUM ELEMENTS*/][25] PROGMEM {
{"U_DC"},
{"I_DC"},
{"P_DC"},
{"YieldDay"},
{"YieldWeek"},
{"YieldTotal"},
{"U_AC"},
{"I_AC"},
{"P_AC"},
{"F_AC"},
{"Temp"},
{"PF_AC"},
{"Efficiency"},
{"Irradiation"},
{"Q_AC"},
{"ALARM_MES_ID"},
{"FWVersion"},
{"FWBuildYear"},
{"FWBuildMonthDay"},
{"FWBuildHourMinute"},
{"HWPartId"},
{"active PowerLimit"},
/* {"reactive PowerLimit"},
{"Powerfactor"}*/
{"LastAlarmCode"},
};
// const char* const fields[] = {"U_DC", "I_DC", "P_DC", "YieldDay", "YieldWeek", "YieldTotal",
// "U_AC", "I_AC", "P_AC", "F_AC", "Temp", "PF_AC", "Efficiency", "Irradiation","Q_AC",
// "ALARM_MES_ID","FWVersion","FWBuildYear","FWBuildMonthDay","FWBuildHourMinute","HWPartId",
// "active PowerLimit", /*"reactive PowerLimit","Powerfactor",*/ "LastAlarmCode"};
const char* const notAvail = "n/a";
// mqtt discovery device classes
enum {DEVICE_CLS_NONE = 0, DEVICE_CLS_CURRENT, DEVICE_CLS_ENERGY, DEVICE_CLS_PWR, DEVICE_CLS_VOLTAGE, DEVICE_CLS_FREQ, DEVICE_CLS_TEMP};
const char* const deviceClasses[] = {0, "current", "energy", "power", "voltage", "frequency", "temperature"};
enum {STATE_CLS_NONE = 0, STATE_CLS_MEASUREMENT, STATE_CLS_TOTAL_INCREASING};
const char* const stateClasses[] = {0, "measurement", "total_increasing"};
typedef struct {
uint8_t fieldId; // field id
uint8_t deviceClsId; // device class
uint8_t stateClsId; // state class
} byteAssign_fieldDeviceClass;
const byteAssign_fieldDeviceClass deviceFieldAssignment[] = {
{FLD_UDC, DEVICE_CLS_VOLTAGE, STATE_CLS_MEASUREMENT},
{FLD_IDC, DEVICE_CLS_CURRENT, STATE_CLS_MEASUREMENT},
{FLD_PDC, DEVICE_CLS_PWR, STATE_CLS_MEASUREMENT},
{FLD_YD, DEVICE_CLS_ENERGY, STATE_CLS_TOTAL_INCREASING},
{FLD_YW, DEVICE_CLS_ENERGY, STATE_CLS_TOTAL_INCREASING},
{FLD_YT, DEVICE_CLS_ENERGY, STATE_CLS_TOTAL_INCREASING},
{FLD_UAC, DEVICE_CLS_VOLTAGE, STATE_CLS_MEASUREMENT},
{FLD_IAC, DEVICE_CLS_CURRENT, STATE_CLS_MEASUREMENT},
{FLD_PAC, DEVICE_CLS_PWR, STATE_CLS_MEASUREMENT},
{FLD_F, DEVICE_CLS_FREQ, STATE_CLS_NONE},
{FLD_T, DEVICE_CLS_TEMP, STATE_CLS_MEASUREMENT},
{FLD_PF, DEVICE_CLS_NONE, STATE_CLS_NONE},
{FLD_EFF, DEVICE_CLS_NONE, STATE_CLS_NONE},
{FLD_IRR, DEVICE_CLS_NONE, STATE_CLS_NONE}
};
#define DEVICE_CLS_ASSIGN_LIST_LEN (sizeof(deviceFieldAssignment) / sizeof(byteAssign_fieldDeviceClass))
// indices to calculation functions, defined in hmInverter.h
enum {CALC_YT_CH0 = 0, CALC_YD_CH0, CALC_UDC_CH, CALC_PDC_CH0, CALC_EFF_CH0, CALC_IRR_CH};
enum {CMD_CALC = 0xffff};
// CH0 is default channel (freq, ac, temp)
enum {CH0 = 0, CH1, CH2, CH3, CH4};
enum {INV_TYPE_1CH = 0, INV_TYPE_2CH, INV_TYPE_4CH};
typedef struct {
uint8_t fieldId; // field id
uint8_t unitId; // uint id
uint8_t ch; // channel 0 - 4
uint8_t start; // pos of first byte in buffer
uint8_t num; // number of bytes in buffer
uint16_t div; // divisor / calc command
} byteAssign_t;
/**
* indices are built for the buffer starting with cmd-id in first byte
* (complete payload in buffer)
* */
//-------------------------------------
// HM-Series
//-------------------------------------
const byteAssign_t InfoAssignment[] = {
{ FLD_FW_VERSION, UNIT_NONE, CH0, 0, 2, 1 },
{ FLD_FW_BUILD_YEAR, UNIT_NONE, CH0, 2, 2, 1 },
{ FLD_FW_BUILD_MONTH_DAY, UNIT_NONE, CH0, 4, 2, 1 },
{ FLD_FW_BUILD_HOUR_MINUTE, UNIT_NONE, CH0, 6, 2, 1 },
{ FLD_HW_ID, UNIT_NONE, CH0, 8, 2, 1 }
};
#define HMINFO_LIST_LEN (sizeof(InfoAssignment) / sizeof(byteAssign_t))
#define HMINFO_PAYLOAD_LEN 14
const byteAssign_t SystemConfigParaAssignment[] = {
{ FLD_ACT_ACTIVE_PWR_LIMIT, UNIT_PCT, CH0, 2, 2, 10 }/*,
{ FLD_ACT_REACTIVE_PWR_LIMIT, UNIT_PCT, CH0, 4, 2, 10 },
{ FLD_ACT_PF, UNIT_NONE, CH0, 6, 2, 1000 }*/
};
#define HMSYSTEM_LIST_LEN (sizeof(SystemConfigParaAssignment) / sizeof(byteAssign_t))
#define HMSYSTEM_PAYLOAD_LEN 14
const byteAssign_t AlarmDataAssignment[] = {
{ FLD_LAST_ALARM_CODE, UNIT_NONE, CH0, 0, 2, 1 }
};
#define HMALARMDATA_LIST_LEN (sizeof(AlarmDataAssignment) / sizeof(byteAssign_t))
#define HMALARMDATA_PAYLOAD_LEN 0 // 0: means check is off
//-------------------------------------
// HM300, HM350, HM400
//-------------------------------------
const byteAssign_t hm1chAssignment[] = {
{ FLD_UDC, UNIT_V, CH1, 2, 2, 10 },
{ FLD_IDC, UNIT_A, CH1, 4, 2, 100 },
{ FLD_PDC, UNIT_W, CH1, 6, 2, 10 },
{ FLD_YD, UNIT_WH, CH1, 12, 2, 1 },
{ FLD_YT, UNIT_KWH, CH1, 8, 4, 1000 },
{ FLD_IRR, UNIT_PCT, CH1, CALC_IRR_CH, CH1, CMD_CALC },
{ FLD_UAC, UNIT_V, CH0, 14, 2, 10 },
{ FLD_IAC, UNIT_A, CH0, 22, 2, 100 },
{ FLD_PAC, UNIT_W, CH0, 18, 2, 10 },
{ FLD_Q, UNIT_VAR, CH0, 20, 2, 10 },
{ FLD_F, UNIT_HZ, CH0, 16, 2, 100 },
{ FLD_PF, UNIT_NONE, CH0, 24, 2, 1000 },
{ FLD_T, UNIT_C, CH0, 26, 2, 10 },
{ FLD_EVT, UNIT_NONE, CH0, 28, 2, 1 },
{ FLD_YD, UNIT_WH, CH0, CALC_YD_CH0, 0, CMD_CALC },
{ FLD_YT, UNIT_KWH, CH0, CALC_YT_CH0, 0, CMD_CALC },
{ FLD_PDC, UNIT_W, CH0, CALC_PDC_CH0, 0, CMD_CALC },
{ FLD_EFF, UNIT_PCT, CH0, CALC_EFF_CH0, 0, CMD_CALC }
};
#define HM1CH_LIST_LEN (sizeof(hm1chAssignment) / sizeof(byteAssign_t))
#define HM1CH_PAYLOAD_LEN 30
//-------------------------------------
// HM600, HM700, HM800
//-------------------------------------
const byteAssign_t hm2chAssignment[] = {
{ FLD_UDC, UNIT_V, CH1, 2, 2, 10 },
{ FLD_IDC, UNIT_A, CH1, 4, 2, 100 },
{ FLD_PDC, UNIT_W, CH1, 6, 2, 10 },
{ FLD_YD, UNIT_WH, CH1, 22, 2, 1 },
{ FLD_YT, UNIT_KWH, CH1, 14, 4, 1000 },
{ FLD_IRR, UNIT_PCT, CH1, CALC_IRR_CH, CH1, CMD_CALC },
{ FLD_UDC, UNIT_V, CH2, 8, 2, 10 },
{ FLD_IDC, UNIT_A, CH2, 10, 2, 100 },
{ FLD_PDC, UNIT_W, CH2, 12, 2, 10 },
{ FLD_YD, UNIT_WH, CH2, 24, 2, 1 },
{ FLD_YT, UNIT_KWH, CH2, 18, 4, 1000 },
{ FLD_IRR, UNIT_PCT, CH2, CALC_IRR_CH, CH2, CMD_CALC },
{ FLD_UAC, UNIT_V, CH0, 26, 2, 10 },
{ FLD_IAC, UNIT_A, CH0, 34, 2, 100 },
{ FLD_PAC, UNIT_W, CH0, 30, 2, 10 },
{ FLD_Q, UNIT_VAR, CH0, 32, 2, 10 },
{ FLD_F, UNIT_HZ, CH0, 28, 2, 100 },
{ FLD_PF, UNIT_NONE, CH0, 36, 2, 1000 },
{ FLD_T, UNIT_C, CH0, 38, 2, 10 },
{ FLD_EVT, UNIT_NONE, CH0, 40, 2, 1 },
{ FLD_YD, UNIT_WH, CH0, CALC_YD_CH0, 0, CMD_CALC },
{ FLD_YT, UNIT_KWH, CH0, CALC_YT_CH0, 0, CMD_CALC },
{ FLD_PDC, UNIT_W, CH0, CALC_PDC_CH0, 0, CMD_CALC },
{ FLD_EFF, UNIT_PCT, CH0, CALC_EFF_CH0, 0, CMD_CALC }
};
#define HM2CH_LIST_LEN (sizeof(hm2chAssignment) / sizeof(byteAssign_t))
#define HM2CH_PAYLOAD_LEN 42
//-------------------------------------
// HM1200, HM1500
//-------------------------------------
const byteAssign_t hm4chAssignment[] = {
{ FLD_UDC, UNIT_V, CH1, 2, 2, 10 },
{ FLD_IDC, UNIT_A, CH1, 4, 2, 100 },
{ FLD_PDC, UNIT_W, CH1, 8, 2, 10 },
{ FLD_YD, UNIT_WH, CH1, 20, 2, 1 },
{ FLD_YT, UNIT_KWH, CH1, 12, 4, 1000 },
{ FLD_IRR, UNIT_PCT, CH1, CALC_IRR_CH, CH1, CMD_CALC },
{ FLD_UDC, UNIT_V, CH2, CALC_UDC_CH, CH1, CMD_CALC },
{ FLD_IDC, UNIT_A, CH2, 6, 2, 100 },
{ FLD_PDC, UNIT_W, CH2, 10, 2, 10 },
{ FLD_YD, UNIT_WH, CH2, 22, 2, 1 },
{ FLD_YT, UNIT_KWH, CH2, 16, 4, 1000 },
{ FLD_IRR, UNIT_PCT, CH2, CALC_IRR_CH, CH2, CMD_CALC },
{ FLD_UDC, UNIT_V, CH3, 24, 2, 10 },
{ FLD_IDC, UNIT_A, CH3, 26, 2, 100 },
{ FLD_PDC, UNIT_W, CH3, 30, 2, 10 },
{ FLD_YD, UNIT_WH, CH3, 42, 2, 1 },
{ FLD_YT, UNIT_KWH, CH3, 34, 4, 1000 },
{ FLD_IRR, UNIT_PCT, CH3, CALC_IRR_CH, CH3, CMD_CALC },
{ FLD_UDC, UNIT_V, CH4, CALC_UDC_CH, CH3, CMD_CALC },
{ FLD_IDC, UNIT_A, CH4, 28, 2, 100 },
{ FLD_PDC, UNIT_W, CH4, 32, 2, 10 },
{ FLD_YD, UNIT_WH, CH4, 44, 2, 1 },
{ FLD_YT, UNIT_KWH, CH4, 38, 4, 1000 },
{ FLD_IRR, UNIT_PCT, CH4, CALC_IRR_CH, CH4, CMD_CALC },
{ FLD_UAC, UNIT_V, CH0, 46, 2, 10 },
{ FLD_IAC, UNIT_A, CH0, 54, 2, 100 },
{ FLD_PAC, UNIT_W, CH0, 50, 2, 10 },
{ FLD_Q, UNIT_VAR, CH0, 52, 2, 10 },
{ FLD_F, UNIT_HZ, CH0, 48, 2, 100 },
{ FLD_PF, UNIT_NONE, CH0, 56, 2, 1000 },
{ FLD_T, UNIT_C, CH0, 58, 2, 10 },
{ FLD_EVT, UNIT_NONE, CH0, 60, 2, 1 },
{ FLD_YD, UNIT_WH, CH0, CALC_YD_CH0, 0, CMD_CALC },
{ FLD_YT, UNIT_KWH, CH0, CALC_YT_CH0, 0, CMD_CALC },
{ FLD_PDC, UNIT_W, CH0, CALC_PDC_CH0, 0, CMD_CALC },
{ FLD_EFF, UNIT_PCT, CH0, CALC_EFF_CH0, 0, CMD_CALC }
};
#define HM4CH_LIST_LEN (sizeof(hm4chAssignment) / sizeof(byteAssign_t))
#define HM4CH_PAYLOAD_LEN 62
#endif /*__HM_DEFINES_H__*/

549
tools/nano/AhoyUL/src/hmRadio.h

@ -0,0 +1,549 @@
//-----------------------------------------------------------------------------
// 2022 Ahoy, https://www.mikrocontroller.net/topic/525778
// Creative Commons - http://creativecommons.org/licenses/by-nc-sa/3.0/de/
//-----------------------------------------------------------------------------
// 2022 mb modified for AHOY-UL (Hoymiles Arduino Nano, USB light IF)
// - RF handling and function sendPacket_raw() without automatic channel increment
// - loop() saves new packet fragments only to save space
#ifndef __RADIO_H__
#define __RADIO_H__
#include <RF24.h>
#include <RF24_config.h>
#include <SPI.h>
#include "CircularBuffer.h"
#include "config.h"
#include "crc.h"
#include "dbg.h"
#define DEFAULT_RECV_CHANNEL 3
#define SPI_SPEED 1000000
#define DUMMY_RADIO_ID ((uint64_t)0xDEADBEEF01ULL)
#define RF_CHANNELS 5
#define RF_LOOP_CNT 300
// used in main.cpp for response catching and data requests
#define TX_REQ_INFO 0X15
#define TX_REQ_DEVCONTROL 0x51
#define ALL_FRAMES 0x80
#define SINGLE_FRAME 0x81
const char *const rf24AmpPowerNames[] = {"MIN", "LOW", "HIGH", "MAX"};
//-----------------------------------------------------------------------------
// MACROS
//-----------------------------------------------------------------------------
#define CP_U32_LittleEndian(buf, v) ({ \
uint8_t *b = buf; \
b[0] = ((v >> 24) & 0xff); \
b[1] = ((v >> 16) & 0xff); \
b[2] = ((v >> 8) & 0xff); \
b[3] = ((v)&0xff); \
})
#define CP_U32_BigEndian(buf, v) ({ \
uint8_t *b = buf; \
b[3] = ((v >> 24) & 0xff); \
b[2] = ((v >> 16) & 0xff); \
b[1] = ((v >> 8) & 0xff); \
b[0] = ((v)&0xff); \
})
#define BIT_CNT(x) ((x) << 3)
//-----------------------------------------------------------------------------
// HM Radio class
//-----------------------------------------------------------------------------
template <uint8_t CE_PIN, uint8_t CS_PIN, class BUFFER, uint64_t DTU_ID = DTU_RADIO_ID>
class HmRadio {
uint8_t mTxCh;
uint8_t mRxCh; // only dummy use
uint8_t mTxChIdx;
uint8_t mRfChLst[RF_CHANNELS];
uint8_t mRxChIdx;
uint16_t mRxLoopCnt;
RF24 mNrf24;
// RF24 mNrf24(CE_PIN, CS_PIN, SPI_SPEED);
BUFFER *mBufCtrl;
uint8_t mTxBuf[MAX_RF_PAYLOAD_SIZE];
DevControlCmdType DevControlCmd;
volatile bool mIrqRcvd;
uint8_t last_rxdata[2];
public:
HmRadio() : mNrf24(CE_PIN, CS_PIN, SPI_SPEED) {
// HmRadio() : mNrf24(CE_PIN, CS_PIN) {
// Depending on the program, the module can work on 2403, 2423, 2440, 2461 or 2475MHz.
// Channel List 2403, 2423, 2440, 2461, 2475MHz
mRfChLst[0] = 03;
mRfChLst[1] = 23;
mRfChLst[2] = 40;
mRfChLst[3] = 61;
mRfChLst[4] = 75;
mTxChIdx = 2;
mRxChIdx = 4;
mRxLoopCnt = RF_LOOP_CNT;
mSendCnt = 0;
mSerialDebug = true;
mIrqRcvd = true;
}
~HmRadio() {}
void setup(config_t *config, BUFFER *ctrl) {
DPRINT(DBG_VERBOSE, F("hmRadio.h: setup(CE_PIN: "));
_DPRINT(DBG_VERBOSE, config->pinCe);
_DPRINT(DBG_VERBOSE, F(", CS_PIN: "));
_DPRINT(DBG_VERBOSE, config->pinCs);
_DPRINT(DBG_VERBOSE, F(", SPI_SPEED: "));
_DPRINT(DBG_VERBOSE, SPI_SPEED);
_DPRINTLN(DBG_VERBOSE, F(")"));
delay(100);
pinMode(config->pinIrq, INPUT_PULLUP);
mBufCtrl = ctrl;
// mNrf24.begin(config->pinCe, config->pinCs);
mNrf24.begin();
mNrf24.setRetries(0, 0);
mNrf24.setChannel(DEFAULT_RECV_CHANNEL);
mNrf24.setDataRate(RF24_250KBPS);
mNrf24.setCRCLength(RF24_CRC_16);
mNrf24.setAutoAck(false);
mNrf24.setPayloadSize(MAX_RF_PAYLOAD_SIZE);
mNrf24.setAddressWidth(5);
mNrf24.openReadingPipe(1, DTU_RADIO_ID);
mNrf24.enableDynamicPayloads();
// enable only receiving interrupts
mNrf24.maskIRQ(true, true, false);
DPRINT(DBG_VERBOSE, F("RF24 Amp Pwr: RF24_PA_"));
_DPRINT(DBG_VERBOSE, rf24AmpPowerNames[config->amplifierPower]);
mNrf24.setPALevel(config->amplifierPower & 0x03);
mNrf24.startListening();
if (DEBUG_LEVEL >= DBG_VERBOSE) {
DPRINTLN(DBG_VERBOSE, F("Radio Config: "));
mNrf24.printPrettyDetails();
}
mTxCh = setDefaultChannels();
mRxCh = 0; // only dummy
if (!mNrf24.isChipConnected()) {
DPRINT(DBG_WARN, F("WARNING! your NRF24 module can't be reached, check the wiring"));
}
}
/**
* writes to circular packet_t buffer on Rx interrupt data
*/
bool loop(void) {
volatile bool _status;
volatile uint8_t dlen;
_status = false;
DISABLE_IRQ;
if (mIrqRcvd) {
mIrqRcvd = false;
bool tx_ok, tx_fail, rx_ready;
mNrf24.whatHappened(tx_ok, tx_fail, rx_ready); // resets the IRQ pin to HIGH
RESTORE_IRQ;
uint8_t pipe; //, len;
packet_t *p;
while (mNrf24.available(&pipe)) {
DPRINT(DBG_DEBUG, F("RxAvail "));
_DPRINT(DBG_DEBUG, pipe);
if (!mBufCtrl->full()) {
_status = true;
p = mBufCtrl->getFront(); // init pointer address with circular buffer space
p->rfch = mRxCh;
p->plen = mNrf24.getPayloadSize();
// p->plen = mNrf24.getDynamicPayloadSize(); //is not the real RF payload fragment (packet) length, calculate later
if (p->plen < 0) {
DPRINT(DBG_ERROR, F("Rx plen false"));
mNrf24.flush_rx(); // drop the packet and leave
return false;
} else if (p->plen > MAX_RF_PAYLOAD_SIZE) {
p->plen = MAX_RF_PAYLOAD_SIZE;
} // end if-else
mNrf24.read(p->data, p->plen);
// only store new packets, try to get the CRC part only for matching
// PACKET_BUFFER_SIZE can be reduced to max number of expected packets, e.g. 6...8 for 4 channel inverter (payload of 62 bytes)
dlen = p->data[0]; // get decoded data len
dlen = dlen >> 2;
if (dlen < MAX_RF_PAYLOAD_SIZE) {
if (p->data[dlen - 2] == last_rxdata[0] && p->data[dlen - 1] == last_rxdata[1]) {
_DPRINT(DBG_DEBUG, F("-> skip"));
continue;
} // if()
_DPRINT(DBG_DEBUG, F("-> take"));
last_rxdata[0] = p->data[dlen - 2];
last_rxdata[1] = p->data[dlen - 1];
} // end if()
mBufCtrl->pushFront(p);
} else
break;
} // end while()
DPRINT(DBG_DEBUG, F("Rx buf "));
_DPRINT(DBG_DEBUG, mBufCtrl->available());
mNrf24.flush_rx(); // drop the packet
}
RESTORE_IRQ;
return _status;
} // end loop()
void handleIntr(void) {
DPRINT(DBG_DEBUG, F(" i"));
mIrqRcvd = true;
}
uint8_t setDefaultChannels(void) {
// DPRINTLN(DBG_VERBOSE, F("hmRadio.h:setDefaultChannels"));
mTxChIdx = 2; // Start TX with 40
mRxChIdx = 4; // Start RX with 75
return mRfChLst[mTxChIdx];
}
void sendControlPacket(uint8_t *_radio_id, uint8_t cmd, uint16_t *data) {
DPRINTLN(DBG_VERBOSE, F("hmRadio:sendControlPacket"));
sendCmdPacket(_radio_id, TX_REQ_DEVCONTROL, ALL_FRAMES, false); // 0x80 implementation as original DTU code
int cnt = 0;
mTxBuf[10] = cmd; // cmd --> 0x0b => Type_ActivePowerContr, 0 on, 1 off, 2 restart, 12 reactive power, 13 power factor
mTxBuf[10 + (++cnt)] = 0x00;
if (cmd >= ActivePowerContr && cmd <= PFSet) {
mTxBuf[10 + (++cnt)] = ((data[0] * 10) >> 8) & 0xff; // power limit
mTxBuf[10 + (++cnt)] = ((data[0] * 10)) & 0xff; // power limit
mTxBuf[10 + (++cnt)] = ((data[1]) >> 8) & 0xff; // setting for persistens handlings
mTxBuf[10 + (++cnt)] = ((data[1])) & 0xff; // setting for persistens handling
}
// crc control data
uint16_t crc = Hoymiles::crc16(&mTxBuf[10], cnt + 1);
mTxBuf[10 + (++cnt)] = (crc >> 8) & 0xff;
mTxBuf[10 + (++cnt)] = (crc)&0xff;
// crc over all
cnt += 1;
mTxBuf[10 + cnt] = Hoymiles::crc8(mTxBuf, 10 + cnt);
sendPacket(_radio_id, mTxBuf, 10 + (++cnt), true);
}
void sendTimePacket(uint8_t *_radio_id, uint8_t cmd, uint32_t ts, uint16_t alarmMesId) {
// DPRINTLN(DBG_VERBOSE, F("hmRadio.h:sendTimePacket"));
sendCmdPacket(_radio_id, TX_REQ_INFO, ALL_FRAMES, false);
mTxBuf[10] = cmd; // cid
mTxBuf[11] = 0x00;
CP_U32_LittleEndian(&mTxBuf[12], ts); //?? adapt for atmega/nano
// dumpBuf(" ", &mTxBuf[12], 4);
if (cmd == RealTimeRunData_Debug || cmd == AlarmData) {
mTxBuf[18] = (alarmMesId >> 8) & 0xff;
mTxBuf[19] = (alarmMesId)&0xff;
} else {
mTxBuf[18] = 0x00;
mTxBuf[19] = 0x00;
}
uint16_t crc = Hoymiles::crc16(&mTxBuf[10], 14);
mTxBuf[24] = (crc >> 8) & 0xff;
mTxBuf[25] = (crc)&0xff;
mTxBuf[26] = Hoymiles::crc8(mTxBuf, 26);
sendPacket(_radio_id, mTxBuf, 27, true);
}
void sendCmdPacket(uint8_t *_radio_id, uint8_t mid, uint8_t pid, bool calcCrc = true) {
// DPRINTLN(DBG_VERBOSE, F("hmRadio.h:sendCmdPacket"));
memset(mTxBuf, 0, MAX_RF_PAYLOAD_SIZE);
mTxBuf[0] = mid; // message id
// CP_U32_BigEndian(&mTxBuf[1], (invId >> 8)); // this must match with the given invID format, big endian is needed because the id from the plate is entered big endian
memcpy(&mTxBuf[1], &_radio_id[1], 4); // copy 4bytes of radio_id to right possition at txbuffer
CP_U32_BigEndian(&mTxBuf[5], (DTU_ID >> 8)); // needed on Arduino nano
mTxBuf[9] = pid;
if (calcCrc) {
mTxBuf[10] = Hoymiles::crc8(mTxBuf, 10);
sendPacket(_radio_id, mTxBuf, 11, false);
}
}
bool checkPaketCrc(uint8_t buf[], uint8_t *len, uint8_t rxCh) {
// DPRINTLN(DBG_VERBOSE, F("hmRadio.h:checkPaketCrc"));
// will be returned by pointer value
*len = (buf[0] >> 2);
// DPRINT(DBG_INFO, String(F("RX crc_in ")) + String(*len) + String(F("B Ch")) + String(rxCh) + String(F(" | ")));
// dumpBuf(NULL, buf, *len, DBG_DEBUG);
if (*len > (MAX_RF_PAYLOAD_SIZE - 2))
*len = MAX_RF_PAYLOAD_SIZE - 2;
for (uint8_t i = 1; i < (*len + 1); i++) {
buf[i - 1] = (buf[i] << 1) | (buf[i + 1] >> 7);
}
// DPRINT(DBG_INFO, String(F("RX crc_out ")) + String(*len) + String(F("B Ch")) + String(rxCh) + String(F(" | ")));
// dumpBuf(NULL, buf, *len, DBG_DEBUG);
volatile uint8_t crc = Hoymiles::crc8(buf, *len - 1);
volatile bool valid = (crc == buf[*len - 1]);
DPRINT(DBG_DEBUG, F("RX crc-valid "));
_DPRINT(DBG_DEBUG, valid);
return valid;
}
bool switchRxCh(uint16_t addLoop = 0) {
DPRINT(DBG_DEBUG, F("hmRadio.h:switchRxCh: try"));
// DPRINTLN(DBG_VERBOSE, F("R"));
mRxLoopCnt += addLoop;
if (mRxLoopCnt != 0) {
mRxLoopCnt--;
DISABLE_IRQ;
mNrf24.stopListening();
mNrf24.setChannel(getRxNxtChannel());
mNrf24.startListening();
RESTORE_IRQ;
}
return (0 == mRxLoopCnt); // receive finished
}
/**
* Hex string output of uint8_t array
*/
void dumpBuf(const char *info, uint8_t buf[], uint8_t len) {
if (NULL != info) DBGPRINT(info);
for (uint8_t i = 0; i < len; i++) {
DHEX(buf[i]);
if (i % 10 == 0) DBGPRINT(" ");
} // end for()
}
/**
* Hex output with debug-flag dependancy
*/
void dumpBuf(int _thisdebug, const char *info, uint8_t buf[], uint8_t len) {
// DPRINTLN(DBG_VERBOSE, F("hmRadio.h:dumpBuf"));
if (_thisdebug <= DEBUG_LEVEL) {
dumpBuf(info, buf, len);
} // end if(debug)
// DBGPRINTLN("");
}
bool isChipConnected(void) {
// DPRINTLN(DBG_VERBOSE, F("hmRadio.h:isChipConnected"));
return mNrf24.isChipConnected();
}
uint32_t mSendCnt;
bool mSerialDebug;
/**
* send the buf content to the tx-channel, the rx-channel is switched to two higher index
*/
uint8_t sendPacket_raw(uint8_t *_radio_id, packet_t *_p, uint8_t _rxch) {
// void sendPacket_raw(uint64_t _radio_id64, packet_t *_p, uint8_t _rxch) {
// DPRINT(DBG_DEBUG, F("sendPacket_raw"));
uint8_t _arc = 0;
DPRINT(DBG_INFO, F("TXraw Ch"));
if (_p->rfch) _DPRINT(DBG_INFO, F("0"));
_DPRINT(DBG_INFO, _p->rfch);
_DPRINT(DBG_INFO, F(" "));
_DPRINT(DBG_INFO, _p->plen);
_DPRINT(DBG_INFO, F("B | "));
dumpBuf(DBG_INFO, NULL, _p->data, _p->plen);
DISABLE_IRQ;
mNrf24.stopListening();
mNrf24.setChannel(_p->rfch);
// mTxCh = getTxNxtChannel(); // prepare Tx channel for next packet
DPRINT(DBG_DEBUG, F("RF24 addr "));
dumpBuf(DBG_DEBUG, NULL, _radio_id, 5);
delay(25); // wait serial data sending before RF transmission, otherwise missing first RF packet
/*
if(mSerialDebug) {
DPRINT(DBG_INFO, "TX iv-ID 0x");
DHEX((uint32_t)(_radio_id64>>32)); Serial.print(F(" ")); DHEX((uint32_t)(_radio_id64));
}
*/
// mNrf24.openWritingPipe(_radio_id64); // 2022-10-17: mb, addr taken from buf (must be transformed to big endian)
mNrf24.openWritingPipe(_radio_id); // 2022-10-17: mb, addr taken from _p->data must be transformed to big endian,
mNrf24.setCRCLength(RF24_CRC_16);
mNrf24.enableDynamicPayloads();
mNrf24.setAutoAck(true);
mNrf24.setRetries(3, 15); // 3*250us and 15 loops -> 11.25ms
mNrf24.write(_p->data, _p->plen);
//_arc = mNrf24.getARC();
// Try to avoid zero payload acks (has no effect)
mNrf24.openWritingPipe(DUMMY_RADIO_ID); // TODO: why dummy radio id?, deprecated
// mNrf24.setChannel(getRxChannel(_p->rfch)); // switch to Rx channel that matches to Tx channel
setRxChanIdx(getChanIdx(_rxch));
mNrf24.setChannel(_rxch); // switch to Rx channel
mNrf24.setAutoAck(false);
mNrf24.setRetries(0, 0);
mNrf24.disableDynamicPayloads();
mNrf24.setCRCLength(RF24_CRC_DISABLED);
mNrf24.startListening();
RESTORE_IRQ;
_DPRINT(DBG_INFO, F(" ->ARC ")); _DPRINT(DBG_INFO, _arc);
DPRINT(DBG_INFO, "RX Ch");
if (_rxch < 10) _DPRINT(DBG_INFO, F("0"));
_DPRINT(DBG_INFO, _rxch);
_DPRINT(DBG_INFO, " wait");
return _arc;
}
uint8_t getRxChannel(uint8_t _txch) {
uint8_t ix;
ix = getChanIdx(_txch);
ix = (ix + 2) % RF_CHANNELS;
return mRfChLst[mRxChIdx];
}
uint8_t getRxChan() {
return mRfChLst[mRxChIdx];
}
uint8_t getChanIdx(uint8_t _channel) {
// uses global channel list
static uint8_t ix;
for (ix = 0; ix < RF_CHANNELS; ix++) {
if (_channel == mRfChLst[ix]) break;
}
return ix;
}
void setRxChanIdx(uint8_t ix) {
mRxChIdx = ix;
}
void print_radio_details() {
mNrf24.printPrettyDetails();
}
// todo: scan all channel for 1bit rdp value (1 == >=-64dBm, 0 == <-64dBm)
void scanRF(void) {
bool _rdp = 0;
DISABLE_IRQ;
DPRINT(DBG_INFO, F("RDP[0...124] "));
for (uint8_t _ch = 0; _ch < 125; _ch++) {
if (_ch % 10 == 0) _DPRINT(DBG_INFO, " ");
mNrf24.setChannel(_ch);
mNrf24.startListening();
_DPRINT(DBG_INFO, mNrf24.testRPD());
}
RESTORE_IRQ;
}
private:
/**
* That is the actual send function
*/
bool sendPacket(uint8_t *_radio_id, uint8_t buf[], uint8_t len, bool clear = false, bool doSend = true) {
// DPRINT(DBG_DEBUG, F("hmRadio.h:sendPacket"));
//bool _tx_ok = 0; // could also query the num of tx retries as uplink quality indication, no lib interface seen yet
uint8_t _arc = 0;
// DPRINTLN(DBG_VERBOSE, "sent packet: #" + String(mSendCnt));
// dumpBuf("SEN ", buf, len);
if (mSerialDebug) {
DPRINT(DBG_INFO, F("TX Ch"));
if (mRfChLst[mTxChIdx] < 10) _DPRINT(DBG_INFO, F("0"));
_DPRINT(DBG_INFO, mRfChLst[mTxChIdx]);
_DPRINT(DBG_INFO, F(" "));
_DPRINT(DBG_INFO, len);
_DPRINT(DBG_INFO, F("B | "));
dumpBuf(DBG_INFO, NULL, buf, len);
DPRINT(DBG_VERBOSE, F("TX iv-ID 0x"));
dumpBuf(DBG_VERBOSE, NULL, _radio_id, 5);
// DPRINT(DBG_INFO, "TX iv-ID 0x");
// DHEX((uint32_t)(invId>>32)); Serial.print(F(" ")); DHEX((uint32_t)(invId));
delay(20); // wait serial data sending before RF transmission, otherwise missing first RF packet
}
DISABLE_IRQ;
mNrf24.stopListening();
if (clear)
mRxLoopCnt = RF_LOOP_CNT;
mNrf24.setChannel(mRfChLst[mTxChIdx]);
mTxCh = getTxNxtChannel(); // prepare Tx channel for next packet
// mNrf24.openWritingPipe(invId); // TODO: deprecated, !!!! invID must be given in big endian uint8_t*
mNrf24.openWritingPipe(_radio_id);
mNrf24.setCRCLength(RF24_CRC_16);
mNrf24.enableDynamicPayloads();
mNrf24.setAutoAck(true);
mNrf24.setRetries(3, 15); // 3*250us and 15 loops -> 11.25ms, I guess Hoymiles has disabled autoack, thus always max loops send
if (doSend) mNrf24.write(buf, len); // only send in case of send-flag true, _tx_ok seems to be always false
//_arc = mNrf24.getARC(); // is always 15, hoymiles receiver might have autoack=false
// Try to avoid zero payload acks (has no effect)
mNrf24.openWritingPipe(DUMMY_RADIO_ID); // TODO: why dummy radio id?, deprecated
// mRxChIdx = 0;
mNrf24.setChannel(mRfChLst[mRxChIdx]); // switch to Rx channel that matches to Tx channel
mRxCh = mRfChLst[mRxChIdx];
mNrf24.setAutoAck(false);
mNrf24.setRetries(0, 0);
mNrf24.disableDynamicPayloads();
mNrf24.setCRCLength(RF24_CRC_DISABLED);
mNrf24.startListening();
RESTORE_IRQ;
if (mSerialDebug) {
//_DPRINT(DBG_INFO, F(" ->ARC ")); _DPRINT(DBG_INFO, _arc);
DPRINT(DBG_VERBOSE, "RX Ch");
if (mRfChLst[mRxChIdx] < 10) _DPRINT(DBG_VERBOSE, F("0"));
_DPRINT(DBG_VERBOSE, mRfChLst[mRxChIdx]);
_DPRINT(DBG_VERBOSE, F(" wait"));
getRxNxtChannel(); // prepare Rx channel for next packet
}
mSendCnt++;
return true;
}
uint8_t getTxNxtChannel(void) {
if (++mTxChIdx >= RF_CHANNELS)
mTxChIdx = 0;
// DPRINT(DBG_DEBUG, F("next TX Ch"));
//_DPRINT(DBG_DEBUG, mRfChLst[mTxChIdx]);
return mRfChLst[mTxChIdx];
}
uint8_t getRxNxtChannel(void) {
if (++mRxChIdx >= RF_CHANNELS)
mRxChIdx = 0;
// PRINT(DBG_DEBUG, F("next RX Ch"));
//_DPRINT(DBG_DEBUG, mRfChLst[mRxChIdx]);
return mRfChLst[mRxChIdx];
}
void setChanIdx(uint8_t _txch) {
mTxChIdx = getChanIdx(_txch);
mRxChIdx = (mTxChIdx + 2) % RF_CHANNELS;
}
uint8_t getRxChannel(void) {
return mRxCh;
}
}; // end class
#endif /*__RADIO_H__*/

834
tools/nano/AhoyUL/src/main.cpp

@ -0,0 +1,834 @@
//-----------------------------------------------------------------------------
// 2022 Ahoy, https://www.mikrocontroller.net/topic/525778
// Creative Commons - http://creativecommons.org/licenses/by-nc-sa/3.0/de/
//-----------------------------------------------------------------------------
//
// 2022: mb, AHOY-src modified for AHOY-UL (Hoymiles, USB light IF) for Arduino Nano (also works on ESP8266 without WIFI/BT)
// The HW is same as described in AHOY project.
// The Hoymiles inverter (e.g. HM800) is accessed via NRF24L01+.
// The interface is USB2serial for input and output.
// There are two modes of operation:
// - automode: one REQUEST message is polled periodically and decoded payload is given by serial-IF (@57600baud), some comfig inputs possible
// - smac-mode: -> The hoymiles specific REQUEST messages must be given as input via serial-IF (@57600baud)
// <- The full sorted RESPONSE is given to the serial-IF with as smac-packet (to be used with python, fhem, etc.)
//
#include <Arduino.h>
#include <printf.h>
#include <stdint.h>
#include <stdio.h>
#include <RF24.h>
#include <RF24_config.h>
#include <SPI.h>
#include "CircularBuffer.h"
#include "config.h"
#include "dbg.h"
#include "defines.h"
#include "hmDefines.h"
#include "hmRadio.h"
#include "utils_serial.h"
typedef CircularBuffer<packet_t, PACKET_BUFFER_SIZE> BufferType;
typedef HmRadio<DEF_RF24_CE_PIN, DEF_RF24_CS_PIN, BufferType> RadioType;
// typedef Inverter<float> InverterType;
// typedef HmSystem<RadioType, BufferType, MAX_NUM_INVERTERS, InverterType> HmSystemType;
// declaration of functions
static void resetSystem(void);
static void loadDefaultConfig(config_t *_mconfig);
// utility function
static int availableMemory(void);
static void swap_bytes(uint8_t *, uint32_t);
static uint32_t swap_bytes(uint32_t);
static bool check_array(uint8_t *, uint8_t *, uint8_t);
// low level packet payload and inverter handling
static uint8_t getInvID(invPayload_t *, uint8_t, uint8_t *);
static uint8_t getNumInv(invPayload_t *, uint8_t);
static bool copyPacket2Payload(invPayload_t *, uint8_t, packet_t *, bool);
static bool savePayloadfragment(invPayload_t *, packet_t *, uint8_t, uint8_t);
static bool processPayload(invPayload_t *, config_t *, bool);
static uint8_t checkPayload(invPayload_t *);
static bool resetPayload(invPayload_t *);
// output and decoding
static bool returnMACPackets(invPayload_t *);
static uint8_t returnPayload(invPayload_t *, uint8_t *, uint8_t);
static void decodePayload(uint8_t, uint8_t *, uint8_t, uint32_t, char*, uint8_t, uint16_t);
//interrupt handler
static void handleIntr(void);
// sysConfig_t mSysConfig;
static config_t mConfig;
static BufferType packet_Buffer;
// static char mVersion[12];
static volatile uint32_t mTimestamp;
// static uint16_t mSendTicker;
// static uint8_t mSendLastIvId;
static invPayload_t mPayload[MAX_NUM_INVERTERS];
// static uint32_t mRxFailed;
// static uint32_t mRxSuccess;
// static uint32_t mFrameCnt;
// static uint8_t mLastPacketId;
// serial
static volatile uint16_t mSerialTicker;
// timer
// static uint32_t mTicker;
// static uint32_t mRxTicker;
// static HmSystemType *mSys;
static RadioType hmRadio;
// static uint8_t radio_id[5]; //todo: use the mPayload[].id field ,this defines the radio-id (domain) of the rf24 transmission, will be derived from inverter id
static uint64_t radio_id64 = 0ULL;
#define P(x) (__FlashStringHelper *)(x) // PROGMEM-Makro for variables
static const char COMPILE_DATE[] PROGMEM = {__DATE__};
static const char COMPILE_TIME[] PROGMEM = {__TIME__};
static const char NAME[] PROGMEM = {DEF_DEVICE_NAME};
#define USER_PAYLOAD_MAXLEN 128
static uint8_t user_payload[USER_PAYLOAD_MAXLEN]; // used for simple decoding and output only
static uint32_t user_pl_ts = 0;
#define MAX_STRING_LEN 51
static char strout[MAX_STRING_LEN]; //global string buffer for sprintf, snprintf, snprintf_P outputs
///////////////////////////////////////////////////////////////////
void setup() {
// Serial.begin(115200);
Serial.begin(57600);
printf_begin();
Serial.flush();
Serial.print(P(NAME));
Serial.print(F("\ncompiled "));
Serial.print(P(COMPILE_DATE));
Serial.print(F(" "));
Serial.print(P(COMPILE_TIME));
mSerialTicker = 0xffff;
resetSystem(); // reset allocated mPayload buffer
loadDefaultConfig(&mConfig); // fills the mConfig parameter with values
strout[MAX_STRING_LEN-1] = '\0'; //string termination
// todo: loadEEconfig() from Flash, like Inverter-ID, power setting
// mSys = new HmSystemType();
// mSys->setup(&mConfig);
DPRINT(DBG_INFO, F("freeRAM ")); _DPRINT(DBG_INFO,availableMemory());
delay(2000);
hmRadio.setup(&mConfig, &packet_Buffer);
attachInterrupt(digitalPinToInterrupt(DEF_RF24_IRQ_PIN), handleIntr, FALLING);
// prepare radio domain ID
// radio_id[0] = (uint8_t) 0x01;
// swap_bytes( &radio_id[1], (uint32_t)IV1_RADIO_ID );
// assign inverter ID to the payload structure
mPayload[0].invId[0] = (uint8_t)0x01;
swap_bytes(&mPayload[0].invId[1], (uint32_t)IV1_RADIO_ID);
mPayload[0].invType = (uint16_t)(IV1_RADIO_ID >> 32); //keep just upper 6 and 5th byte (e.g.0x1141) of interter plate id
// hmRadio.dumpBuf("\nsetup InvID ", &mPayload[0].invId[0], 5, DBG_DEBUG);
// alternativly radio-id
//radio_id64 = (uint64_t)(swap_bytes((uint32_t)IV1_RADIO_ID)) << 8 | 0x01;
// todo: load Inverter decoder depending on InvID
} // end setup()
// volatile static uint32_t current_millis = 0;
static volatile uint32_t timer1_millis = 0L; // general loop timer
static volatile uint32_t timer2_millis = 0L; // send Request timer
static volatile uint32_t lastRx_millis = 0L;
#define ONE_SECOND (1000L)
#define ONE_MINUTE (60L * ONE_SECOND)
#define QUARTER_HOUR (15L * ONE_MINUTE)
#define SEND_INTERVAL_ms (ONE_SECOND * SEND_INTERVAL)
#define MIN_SEND_INTERVAL_ms (ONE_SECOND * MIN_SEND_INTERVAL)
static uint8_t c_loop = 0;
static volatile int sread_len = 0; // UART read length
// static volatile bool rxRdy = false; //will be set true on first receive packet during sending interval, reset to false before sending
static uint8_t inv_ix;
static bool payload_used[MAX_NUM_INVERTERS];
// static bool saveMACPacket = false; // when true the the whole MAC packet with address is kept, remove the MAC for CRC calc
static bool automode = true;
static bool doDecode = false;
static bool showMAC = true;
static volatile uint32_t polling_inv_msec = SEND_INTERVAL_ms;
static volatile uint16_t tmp16 = 0;
static uint8_t tmp8 = 0;
static uint32_t min_SEND_SYNC_msec = MIN_SEND_INTERVAL_ms;
/////////////////////////////////////////////////////////////////////////
///////////////////////////////////////////////////////////////////////
void loop() {
// the inverter id/ix I shall be handled in a loop if more than one inverter is registered for periodic polling
inv_ix = 0;
// counting the time reference of seconds
if (millis() - timer1_millis >= ONE_SECOND) {
timer1_millis = millis();
mTimestamp += 1;
c_loop++;
if (c_loop > 15) {
c_loop = 0;
// DPRINT(DBG_VERBOSE, F("loop().."));
DPRINT(DBG_DEBUG, millis());
_DPRINT(DBG_DEBUG, F(" loop:freeRAM "));
_DPRINT(DBG_DEBUG, availableMemory());
} // end if
}
// query serial-IF for some control and data to be send via RF (cmd: sMAC:chXX:... see eval_uart_smac_cmd() format)
if (Serial.available()) {
// wait char
inSer = Serial.read();
delay(10);
switch (inSer) {
case (char)'s': {
// sending smac commands which are send to the inverter, same format as from inverter, see details in funct. eval_uart_smac_cmd(...)
//e.g. "smac:ch03:958180....:rc40:" -- ch03 for Tx channel, <raw data with crc8>, rc40 for rx channel 40, if rx channel is left then default tx_ix+2
DPRINT(DBG_INFO, F("s OK")); // OK needed only for my simple python at-terminal
static packet_t rfTX_packet;
static uint8_t rxch;
ser_buffer[0] = '\0';
automode = false;
sread_len = serReadBytes_ms(ser_buffer, MAX_SERBYTES, 2000);
if (eval_uart_smac_cmd(ser_buffer, sread_len, &rfTX_packet, &rxch)) {
// send on Tx channel and receive on Rx channel
if (rxch == 0) {
// if rxchannel not given, then set automatically
rxch = hmRadio.getRxChannel(rfTX_packet.rfch);
}
hmRadio.setRxChanIdx(hmRadio.getChanIdx(rxch));
// compare inv-id from packet data with all registerd inv-id of the payload_t struct array
inv_ix = getInvID(mPayload, MAX_NUM_INVERTERS, &rfTX_packet.data[0]);
if (inv_ix != 0xFF) {
DPRINT(DBG_DEBUG, F("match, inv_ix "));
_DPRINT(DBG_DEBUG, inv_ix);
payload_used[inv_ix] = !resetPayload(&mPayload[inv_ix]);
mPayload[inv_ix].isMACPacket = true; //MAC must be enabled to show the full MAC packet, no need for user_payload only
mPayload[inv_ix].receive = false;
hmRadio.sendPacket_raw(&mPayload[0].invId[0], &rfTX_packet, rxch); // 2022-10-30: byte array transfer working
mPayload[inv_ix].requested = true;
} else {
// no matching inverter, do nothing
inv_ix = 0;
}
}
break;
} // end case s
case (char)'c': {
// todo: scan all channels for 1bit RSSI value and print result
Serial.print(F("\nc OK "));
hmRadio.scanRF();
break;
}
case (char)'z': {
// todo: register new Inverter ID in payload_t array via "z:<5bytes>:<crc>:", save to eeprom
// todo: query via z1? for first inverter
break;
}
case (char)'a': {
//enable automode with REQ polling interval via a10 => 10sec, a100 => 100sec or other range 5....3600sec
automode = true;
sread_len = serReadBytes_ms(ser_buffer, MAX_SERBYTES, 2000);
polling_inv_msec = eval_uart_decimal_val("auto polling msec ", ser_buffer, sread_len, 5, 3600, ONE_SECOND);
break;
} // end case a
case (char)'d': {
// trigger decoding, enable periodic decoding via "d1" and disable via "d0"
Serial.print(F("\nd OK "));
// simple decoding
decodePayload(TX_REQ_INFO + 0x80, &user_payload[0], 42, user_pl_ts, &strout[0], MAX_STRING_LEN, mPayload[inv_ix].invType);
sread_len = serReadBytes_ms(ser_buffer, MAX_SERBYTES, 2000);
doDecode = (bool)eval_uart_decimal_val("decoding ", ser_buffer, sread_len, 0, 255, 1);
break;
} // end case d
case (char)'m': {
// enable/disable show MACmessages via "m0" or "d1"
Serial.print(F("\nm OK "));
sread_len = serReadBytes_ms(ser_buffer, MAX_SERBYTES, 2000);
showMAC = (bool)eval_uart_decimal_val("showMAC ", ser_buffer, sread_len, 0, 255, 1);
break;
} // end case m
case (char)'i': {
// query current radio information
DPRINT(DBG_INFO, F("i OK"));
hmRadio.print_radio_details();
break;
}
case (char)'t': {
// set the time sec since Jan-01 1970 (UNIX epoch time) as decimal String value e.g. "t1612345678:" for Feb-03 2021 9:47:58
// timestamp is only used for sending packet timer, but not for the timing of Tx/Rx scheduling etc...
sread_len = serReadBytes_ms(ser_buffer, MAX_SERBYTES, 2000);
mTimestamp = eval_uart_decimal_val("time set ", ser_buffer, sread_len, 12 * 3600, 0xFFFFFFFF, 1);
break;
} // end case t
} // end switch-case
} // end if serial...
// automode RF-Tx-trigger
if (automode) {
//slow down the sending if no Rx for long time
if (millis() - lastRx_millis > QUARTER_HOUR) {
min_SEND_SYNC_msec = QUARTER_HOUR;
} else {
min_SEND_SYNC_msec = MIN_SEND_INTERVAL_ms;
}
// normal sending request interval
// todo: add queue of cmds or schedule simple device control request for power limit values
if ( millis() - timer2_millis >= min_SEND_SYNC_msec && ((millis() - lastRx_millis > polling_inv_msec) || millis() < 60000) ) {
timer2_millis = millis();
// DISABLE_IRQ;
// todo: handle different inverter via inv_id
payload_used[inv_ix] = !resetPayload(&mPayload[inv_ix]);
mPayload[inv_ix].isMACPacket = true;
mPayload[inv_ix].receive = false;
hmRadio.sendTimePacket(&mPayload[inv_ix].invId[0], 0x0B, mTimestamp, 0x0000);
mPayload[inv_ix].requested = true;
// RESTORE_IRQ;
}
}
// RF-Rx-loop raw reading
// receives rf data and writes data into circular buffer (packet_Buffer)
hmRadio.loop();
// eval RF-Rx raw data (one single entry per loop to keep receiving new messages from one inverter (inverter domain is set via invID)
if (!packet_Buffer.empty()) {
packet_t *p;
p = packet_Buffer.getBack();
if (hmRadio.checkPaketCrc(&p->data[0], &p->plen, p->rfch)) {
// process buffer only on first occurrence
DPRINT(DBG_INFO, F("RX Ch"));
if (p->rfch < 10) _DPRINT(DBG_INFO, F("0"));
_DPRINT(DBG_INFO, p->rfch);
_DPRINT(DBG_INFO, F(" "));
_DPRINT(DBG_INFO, p->plen);
_DPRINT(DBG_INFO, F("B | "));
hmRadio.dumpBuf(DBG_INFO, NULL, p->data, p->plen);
// mFrameCnt++;
if (p->plen) {
// no need to get the inv_ix, because only desired inverter will answer, payload buffer is CRC protected
// inv_ix = getInvID(mPayload, MAX_NUM_INVERTERS, &p->data[0]);
if (inv_ix >= 0 && inv_ix < MAX_NUM_INVERTERS) {
if (copyPacket2Payload(&mPayload[inv_ix], inv_ix, p, mPayload[inv_ix].isMACPacket)) {
lastRx_millis = millis();
}
}
} // end if(plen)
} // end if(checkPacketCRC)
packet_Buffer.popBack(); // remove last entry after eval and print
DPRINT(DBG_DEBUG, F("Records in p_B "));
_DPRINT(DBG_DEBUG, packet_Buffer.available());
} // end if()
// handle output of data if ready
// after min 500msec and all packets shall be copied successfully to mPayload structure, run only if requested and some data received after REQ-trigger
if ((millis() - timer2_millis >= 500) && packet_Buffer.empty() && mPayload[inv_ix].requested && mPayload[inv_ix].receive) {
// process payload some sec after last sending
if (false == payload_used[inv_ix]) {
if (processPayload(&mPayload[inv_ix], &mConfig, true)) {
// data valid and complete
if (mPayload[inv_ix].isMACPacket && showMAC) {
returnMACPackets(&mPayload[inv_ix]);
}
payload_used[inv_ix] = true;
tmp8 = returnPayload(&mPayload[inv_ix], &user_payload[0], USER_PAYLOAD_MAXLEN);
user_pl_ts = mPayload[inv_ix].ts;
if (tmp8 == 42 && doDecode) {
decodePayload(mPayload[inv_ix].txId, &user_payload[0], tmp8, user_pl_ts, &strout[0], MAX_STRING_LEN, mPayload[inv_ix].invType);
}
}
}
}
} // end loop()
//-----------------------------------------------------------------------------
//-----------------------------------------------------------------------------
static void resetSystem(void) {
mTimestamp = 1665740000; // sec since 1970 Jan-01, is Oct-14 2022 ~09:33
// mTicker = 0;
// mRxTicker = 0;
// mSendLastIvId = 0;
memset(mPayload, 0, (MAX_NUM_INVERTERS * sizeof(invPayload_t)));
// mRxFailed = 0;
// mRxSuccess = 0;
// mFrameCnt = 0;
// mLastPacketId = 0x00;
// mSerialTicker = 0xffff;
}
//-----------------------------------------------------------------------------
static void loadDefaultConfig(config_t *_mconfig) {
DPRINT(DBG_VERBOSE, F("loadDefaultCfg .."));
// memset(&mSysConfig, 0, sizeof(sysConfig_t));
memset(_mconfig, 0, sizeof(config_t));
// snprintf(mVersion, 12, "%d.%d.%d", VERSION_MAJOR, VERSION_MINOR, VERSION_PATCH);
// snprintf(mSysConfig.deviceName, DEVNAME_LEN, "%s", DEF_DEVICE_NAME);
// nrf24
_mconfig->sendInterval = SEND_INTERVAL;
_mconfig->maxRetransPerPyld = DEF_MAX_RETRANS_PER_PYLD;
_mconfig->pinCs = DEF_RF24_CS_PIN;
_mconfig->pinCe = DEF_RF24_CE_PIN;
_mconfig->pinIrq = DEF_RF24_IRQ_PIN;
_mconfig->amplifierPower = DEF_AMPLIFIERPOWER & 0x03;
// serial
_mconfig->serialInterval = SERIAL_INTERVAL;
_mconfig->serialShowIv = true;
_mconfig->serialDebug = true;
}
// free RAM check for debugging. SRAM for ATmega328p = 2048Kb.
static int availableMemory() {
// Use 1024 with ATmega168
volatile int size = 2048;
byte *buf;
while ((buf = (byte *)malloc(--size)) == NULL)
;
free(buf);
return size;
}
static void swap_bytes(uint8_t *_buf, uint32_t _v) {
_buf[0] = ((_v >> 24) & 0xff);
_buf[1] = ((_v >> 16) & 0xff);
_buf[2] = ((_v >> 8) & 0xff);
_buf[3] = ((_v)&0xff);
return;
}
static uint32_t swap_bytes(uint32_t _v) {
volatile uint32_t _res;
_res = ((_v >> 24) & 0x000000ff) | ((_v >> 8) & 0x0000ff00) | ((_v << 8) & 0x00ff0000) | ((_v << 24) & 0xff000000);
return _res;
}
////////////////// handle payload function, maybe later as own class //////////////////
/**
* compares the two arrays and returns true when equal
*/
static bool check_array(uint8_t *invID, uint8_t *rcvID, uint8_t _len) {
uint8_t i;
for (i = 0; i < _len; i++) {
if (invID[i] != rcvID[i]) return false;
}
return true;
}
/**
* gets the payload index that matches to the received ID, in case of no match 0xFF is returned
*/
static uint8_t getInvID(invPayload_t *_payload, uint8_t _pMAX, uint8_t *rcv_data) {
uint8_t i;
for (i = 0; i < _pMAX; i++) {
// comparison starts at index 1 of invID, because index zero contains the pipe, only 4 bytes are compared
if (check_array(&_payload->invId[1], &rcv_data[1], 4)) return i;
_payload++;
}
return (uint8_t)0xFF;
}
static uint8_t getNumInv(invPayload_t *_payload, uint8_t _pMAX) {
uint8_t i;
for (i = 0; i < _pMAX; i++) {
// check if invID is not set on first two entries, shall be sufficient for now
if (_payload->invId[1] == 0x00 && _payload->invId[2] == 0x00) break;
_payload++;
}
return i;
}
////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
//
// low level payload packet handling
//
//
/**
* clearing of the current inverter data structure, except the ID and typedef
*/
static bool resetPayload(invPayload_t *_payload) {
// static uint8_t inv_ix;
uint8_t backup_inv_id[5];
uint16_t backup_inv_type;
DPRINT(DBG_VERBOSE, F("resetPayload invid "));
hmRadio.dumpBuf(DBG_VERBOSE, NULL, &_payload->invId[0], 5);
memcpy(backup_inv_id, &_payload->invId[0], 5);
backup_inv_type = _payload->invType;
// nv_ix = getInvID(_payload, _pMAX, *invID);
memset(_payload, 0, sizeof(invPayload_t));
memcpy(&_payload->invId[0], backup_inv_id, 5);
_payload->invType = backup_inv_type;
return true;
}
/**
* copies data (response of REQ_INFO message) from Rx packet buffer into payload structure per inverter
*/
static bool copyPacket2Payload(invPayload_t *_payload, uint8_t _invx, packet_t *_p, bool _keepMAC_packet) {
// check whether inverter ID is registered and save data per registered inverter, but for TX_REQ_INFO only
bool _res;
uint8_t ix;
uint8_t pid;
_res = false;
DPRINT(DBG_DEBUG, F("copyPacket2Payload invx "));
_DPRINT(DBG_DEBUG, _invx);
pid = _p->data[9];
// check first if all RF data shall be kept, e.g. for transfer via uart-if as MAC packets
ix = 10; // start of real payload without address overhead
_payload->isMACPacket = _keepMAC_packet;
if (_keepMAC_packet) {
ix = 0;
}
if ((pid & 0x7F) > MAX_PAYLOAD_ENTRIES) {
DPRINT(DBG_ERROR, F(" high index "));
_DPRINT(DBG_ERROR, pid & 0x7F);
return false;
} // end
// parsing of payload responses, only keeping the real payload data
if (_p->data[0] == (TX_REQ_INFO + 0x80)) {
// eval response from get information command 0x15
DPRINT(DBG_DEBUG, F(" resp getinfo "));
_DPRINT(DBG_DEBUG, F(" pid "));
_DPRINT(DBG_DEBUG, pid & 0x7F);
if (pid == 0x00) {
_DPRINT(DBG_DEBUG, F(" ignore"));
} else {
// store regular fragments only
_res = savePayloadfragment(_payload, _p, pid, ix);
} // end if-else(*pid == 0x00)
} // end if(TX_REQ_INFO +0x80)
// check and save dev control
else if (_p->data[0] == (TX_REQ_DEVCONTROL + 0x80)) {
// response from dev control request
DPRINT(DBG_DEBUG, F(" resp devcontrol"));
switch (_p->data[12]) {
case ActivePowerContr:
_DPRINTLN(DBG_INFO, F(" ActivePowerContr"));
break;
default:
_DPRINTLN(DBG_INFO, F(" default data[12] handling"));
_res = savePayloadfragment(_payload, _p, pid, ix);
break;
} // end switch-case
} else {
// handle all unknown Request response fragments
_res = savePayloadfragment(_payload, _p, pid, ix);
} // end else-if
return _res;
} // end savePayload()
/**
* saves copies one received payload-packet into inverter memory structure
*/
static bool savePayloadfragment(invPayload_t *_payload, packet_t *_p, uint8_t _pid, uint8_t _ix) {
volatile bool _res = false;
if ((_pid & 0x7F) < MAX_PAYLOAD_ENTRIES) {
_res = true;
memcpy(&_payload->data[(_pid & 0x7F) - 1][0], &_p->data[_ix], _p->plen - _ix - 1);
_payload->len[(_pid & 0x7F) - 1] = _p->plen - _ix - 1;
_payload->rxChIdx = _p->rfch;
_payload->ts = millis();
_payload->txId = _p->data[0];
_payload->receive = _res; // indicates that a packet was received at least once per Request iteration
// handle last packet additionally
if ((_pid & 0x80) == 0x80) {
_DPRINT(DBG_DEBUG, F(" fragm.end"));
if ((_pid & 0x7f) > _payload->maxPackId) {
_payload->maxPackId = (_pid & 0x7f); // set maxPackId > 0 as indication that last fragment was detected
_DPRINT(DBG_DEBUG, F(" maxPID "));
_DPRINT(DBG_DEBUG, _payload->maxPackId);
}
} // end if((*pid & 0x80)...)
} else {
_DPRINT(DBG_ERROR, F(" pid out of range"));
} // end if(MAX_PAYLOAD_ENTRIES)
return _res;
} // end savePayload fragment
/**
* process the received payload and handles retransmission request for one inverter
*/
static bool processPayload(invPayload_t *_payload, config_t *_mconfig, bool retransmit) {
// uses gloabal send timer2_millis
// for one inverter only at a given time only, but payload structure array can hold more inverter
DPRINT(DBG_VERBOSE, F("processPayload"));
bool _res;
uint8_t _reqfragment;
_res = 0x00;
//_payload->complete = false;
_reqfragment = checkPayload(_payload);
if (_reqfragment == 0xff) {
return false;
}
if (_reqfragment > 0x80) {
if (retransmit) {
if (_payload->retransmits < _mconfig->maxRetransPerPyld) {
_payload->retransmits++;
DPRINT(DBG_DEBUG, F(" req fragment "));
_DPRINTHEX(DBG_DEBUG, (uint8_t)(_reqfragment & 0x7f));
hmRadio.sendCmdPacket(_payload->invId, TX_REQ_INFO, _reqfragment, true);
timer2_millis = millis(); // set sending timer2
// leaves with false to quickly receive again
// so far the Tx and Rx frequency is handled globally, it shall be individually per inverter ID?
}
}
} else {
// all data valid
_res = true;
//_payload->complete = true;
} // end if-else()
return _res;
} // end processPayload()
/**
* checks the paypload of all received packet-fragments via CRC16 and length
* returns:
* - 0x00 == OK if all fragments received and CRC OK,
* - PID + 0x81 can be used directly to request the first next missing packet
* - 0xFF in case of CRC16 failure and next missing packet would be bigger than payload->maxPacketId
*/
static uint8_t checkPayload(invPayload_t *_payload) {
// DPRINT(DBG_VERBOSE, F("checkPayload "));
volatile uint16_t crc = 0xffff, crcRcv = 0x0000;
uint8_t i;
uint8_t ixpl; // index were payload starts in each MAC packet
if (_payload->maxPackId > MAX_PAYLOAD_ENTRIES) {
_payload->maxPackId = MAX_PAYLOAD_ENTRIES;
}
ixpl = 0;
if (_payload->isMACPacket) {
ixpl = 10;
}
// try to get the next missing packet via len detection
if (_payload->maxPackId == 0) {
for (i = 0; i < MAX_PAYLOAD_ENTRIES; i++) {
if (_payload->len[i] == 0) break;
}
} else {
// check CRC over all entries
for (i = 0; i < _payload->maxPackId; i++) {
if (_payload->len[i] > 0) {
DPRINT(DBG_VERBOSE, F(" checkPL "));
_DPRINT(DBG_VERBOSE, _payload->len[i] - ixpl);
_DPRINT(DBG_VERBOSE, F("B "));
hmRadio.dumpBuf(DBG_VERBOSE, NULL, &_payload->data[i][ixpl], _payload->len[i] - ixpl);
if (i == (_payload->maxPackId - 1)) {
crc = Hoymiles::crc16(&_payload->data[i][ixpl], _payload->len[i] - ixpl - 2, crc);
crcRcv = (_payload->data[i][_payload->len[i] - 2] << 8) | (_payload->data[i][_payload->len[i] - 1]);
} else
crc = Hoymiles::crc16(&_payload->data[i][ixpl], _payload->len[i] - ixpl, crc);
} else {
// entry in range with len == zero
//--> request retransmit with this index
break;
}
}
if (crc == crcRcv) {
// success --> leave here
DPRINT(DBG_DEBUG, F(" checkPL -> CRC16 OK"));
return (uint8_t)0x00;
} else {
if ((_payload->maxPackId > 0) && (i >= _payload->maxPackId)) {
DPRINT(DBG_ERROR, F(" crc "));
_DPRINTHEX(DBG_ERROR, crc);
DPRINT(DBG_ERROR, F(" crcRcv "));
_DPRINTHEX(DBG_ERROR, crcRcv);
// wrong CRC16 over all packets, must actually never happen for correct packets, must be programming bug
DPRINT(DBG_ERROR, F(" cPL wrong req. "));
_DPRINTHEX(DBG_ERROR, (uint8_t)(i + 0x81));
_payload->receive = false; //stop further eval
return (uint8_t)0xFF;
}
} // end if-else
} // end if-else
return (uint8_t)(i + 0x81);
}
//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
//
// output and decoding functions
//
//
/**
* output of sorted packets with MAC-header, all info included
*/
static bool returnMACPackets(invPayload_t *_payload) {
for (uint8_t i = 0; i < (_payload->maxPackId); i++) {
Serial.print(F("\nrMAC:ch"));
if (_payload->rxChIdx < 10) Serial.print(F(" "));
Serial.print(_payload->rxChIdx);
Serial.print(F(": "));
hmRadio.dumpBuf(NULL, &_payload->data[i][0], _payload->len[i]);
} // end for()
Serial.print(F(":rt"));
Serial.print(_payload->retransmits);
Serial.print(F(":"));
return true;
}
/**
* output of pure user payload message
*/
static uint8_t returnPayload(invPayload_t *_payload, uint8_t *_user_payload, uint8_t _ulen) {
// iv->ts = mPayload[iv->id].ts;
memset(_user_payload, 0, _ulen);
static uint8_t _offs, _ixpl, _len;
_offs = 0;
_ixpl = 0;
if (_payload->isMACPacket) {
_ixpl = 10; // index of position of payload start after pid
} // end if()
for (uint8_t i = 0; i < (_payload->maxPackId); i++) {
memcpy(&_user_payload[_offs], &_payload->data[i][_ixpl], (_payload->len[i] - _ixpl - 1));
_offs += (_payload->len[i] - _ixpl);
} // end for()
_offs -= 2;
Serial.print(F("\nrPayload("));
Serial.print(_offs);
Serial.print(F("): "));
hmRadio.dumpBuf(NULL, _user_payload, _offs);
Serial.print(F(":"));
return _offs;
} // end if returnPaylout
/**
* simple decoding of 2ch HM-inverter only
*/
static void decodePayload(uint8_t _cmd, uint8_t *_user_payload, uint8_t _ulen, uint32_t _ts, char* _strout, uint8_t _strlen, uint16_t _invtype) {
volatile uint32_t _val = 0L;
byteAssign_t _bp;
volatile uint8_t _x;
volatile uint8_t _end;
volatile uint8_t _dot_val;
volatile float _fval;
//_str80[80] = '\0';
snprintf_P(_strout, _strlen, PSTR("\ndata age: %d sec"), (millis() - _ts)/1000);
Serial.print(_strout);
snprintf_P(_strout, _strlen, PSTR("\nInvertertype %Xxxxxxxxx "), _invtype);
Serial.print(_strout);
if (_cmd == 0x95 and _ulen == 42) {
//!!! simple HM600/700/800 2ch decoding for cmd=0x95 only !!!!
for (_x = 0; _x < HM2CH_LIST_LEN; _x++) {
// read values from given positions in payload
_bp = hm2chAssignment[_x];
_val = 0L;
_end = _bp.start + _bp.num;
snprintf_P(_strout, _strlen, PSTR("\nHM800/ch%02d/"), _bp.ch);
Serial.print(_strout);
//Serial.print(F("\nHM800/ch"));
//Serial.print(_bp.ch);
//Serial.print(F("/"));
//strncpy_P(_strout, (PGM_P)pgm_read_word(&(PGM_fields[_bp.fieldId])), _strlen); // read from PROGMEM array into RAM, works for A-Nano, not for esp8266
//snprintf_P(_strout, );
strncpy_P(_strout, &(PGM_fields[_bp.fieldId][0]), _strlen);
Serial.print(_strout);
Serial.print(F(": "));
if (CMD_CALC != _bp.div && _bp.div != 0) {
do {
_val <<= 8;
_val |= _user_payload[_bp.start];
} while (++_bp.start != _end);
_fval = _val / (float)_bp.div;
if (_bp.unitId == UNIT_NONE) {
Serial.print(_val);
continue;
}
Serial.print(_fval,2); //arduino nano does not sprintf.. float values, but print() does
//Serial.print(units[_bp.unitId]);
strncpy_P(_strout, &PGM_units[_bp.unitId][0], _strlen);
Serial.print(_strout);
} else {
// do calculations
Serial.print(F("not yet"));
}
} // end for()
} else {
Serial.print(F("NO DECODER "));
Serial.print(_cmd, HEX);
}
}
///////////////////////////////////////////////////////////
//
// Interrupt handling
//
#if defined(ESP8266) || defined(ESP32)
IRAM_ATTR void handleIntr(void) {
hmRadio.handleIntr();
}
#else
static void handleIntr(void) {
hmRadio.handleIntr();
}
#endif
///////////////////////////////////////////////////////////

233
tools/nano/AhoyUL/src/utils_serial.h

@ -0,0 +1,233 @@
//2022 mb for AHOY-UL (Arduino Nano, USB light IF)
//todo: make class
#include <stdint.h>
#include <stdio.h>
#include <Arduino.h>
#include "dbg.h"
//declaration of functions
static int serReadUntil(char, char*, int, uint16_t);
static int c_remove(char, char*, int);
static void c_replace(char, char, char*, int);
//static uint8_t* serGetHex(char*, int8_t*, uint16_t);
//static uint16_t x2b(char, char);
static uint16_t x2b(char *);
static int serReadTo_ms(char, char*, byte, uint16_t);
static int serReadBytes_ms(char *, byte, uint16_t );
static boolean eval_uart_smac_cmd(char*, uint8_t, packet_t*, uint8_t*);
static uint32_t eval_uart_single_val(char*, char*, uint8_t, uint8_t, uint8_t, int);
#define MAX_SERBYTES 150 //max buffer length serial
static char ser_buffer[MAX_SERBYTES+1]; //one extra byte for \0 termination
static uint8_t byte_buffer[MAX_SERBYTES/2];
static char inSer;
static int ser_len;
/******************************************************************************************************
* serial read function with timeout
* With ATMEGA328p it can only detect 64byte at once (serial input buffer size) at the baudrate of 115200baud (~5ms per byte)
*/
static int serReadUntil(char _ch, char *_str, int _len, uint16_t _TIMEOUT) {
static volatile uint16_t _backupTO;
static volatile int thislen;
_backupTO = Serial.getTimeout();
thislen = 0;
Serial.setTimeout(_TIMEOUT);
thislen = Serial.readBytesUntil(_ch, _str, _len); //can only be used unto max serial input buffer of 64bytes
_str[thislen] = '\0'; //terminate char* str with '\0', data are available external now
if( _len > 0 ) {
DPRINT( DBG_DEBUG, F("ser Rx ")); _DPRINT( DBG_DEBUG, _str); _DPRINT( DBG_DEBUG, F(" len ")); _DPRINT( DBG_DEBUG, thislen);
} else {
DPRINT( DBG_DEBUG, F("ser TIMEOUT"));
}
Serial.setTimeout(_backupTO);
return thislen;
}//end serReadUntil()
static int c_remove(char _chrm, char* _str, int _len) {
static int _ir = 0;
static int _iw = 0;
_ir = 0; //! must be initialized here explicitly with zero, instanciation above is only done once !
_iw = 0;
while (_str[_ir] && _ir < _len) {
if (_str[_ir]!=_chrm) {
_str[_iw++] = _str[_ir];
}
_ir++;
}
_str[_iw]='\0';
return _iw;
}//end c_remove()
static void c_replace(char _crpl, char _cnew, char* _str, int _len) {
int _ir = 0;
_ir = 0;
while (_str[_ir] && _ir < _len) {
if (_str[_ir] ==_crpl) {
_str[_ir] = _cnew;
}
_ir++;
}
return;
}//end c_replace
static void c_lower(char* _str, int _len) {
int _ir = 0;
_ir = 0;
while (_str[_ir] && _ir < _len) {
if (_str[_ir] >= 'A' && _str[_ir] < 'Z') {
_str[_ir] = (char) (_str[_ir] + 0x20);
}
_ir++;
}
return;
}//end c_lower
//todo: use strtol(const char *str, char **endptr, int base) instead
/**
* converts 2 digits hex string to uint8_t byte, the high byte is FF in case of non-hex digit
*/
/*
static uint16_t x2b(char high, char low) {
static volatile uint16_t onebyte=0;
onebyte = 0;
if(high >= '0' && high <= '9') onebyte = (uint16_t) high - '0';
else if (high >='A' && high <= 'F') onebyte = (uint16_t) high - 'A' + 10;
else if (high >='a' && high <= 'f') onebyte = (uint16_t) high - 'a' + 10;
else return 0xF000;
onebyte = onebyte << 4;
if(low >= '0' && low <= '9') onebyte |= (uint16_t) low - '0';
else if (low >='A' && low <= 'F') onebyte |= (uint16_t) low - 'A' + 10;
else if (low >='a' && low <= 'f') onebyte |= (uint16_t) low - 'a' + 10;
else return 0x0F00;
return onebyte & 0x00FF;
} //end x2i()
*/
static uint16_t x2b(char *_hexbyte) {
uint16_t onebyte=0;
if(_hexbyte[0] >= '0' && _hexbyte[0] <= '9') onebyte = (uint16_t)_hexbyte[0] - '0';
else if (_hexbyte[0] >='A' && _hexbyte[0] <= 'F') onebyte = (uint16_t) _hexbyte[0] - 'A' + 10;
else if (_hexbyte[0] >='a' && _hexbyte[0] <= 'f') onebyte = (uint16_t) _hexbyte[0] - 'a' + 10;
else return 0xF000;
onebyte = onebyte << 4;
if(_hexbyte[1] >= '0' && _hexbyte[1] <= '9') onebyte |= (uint16_t)_hexbyte[1] - '0';
else if (_hexbyte[1] >='A' && _hexbyte[1] <= 'F') onebyte |= (uint16_t)_hexbyte[1] - 'A' + 10;
else if (_hexbyte[1] >='a' && _hexbyte[1] <= 'f') onebyte |= (uint16_t)_hexbyte[1] - 'a' + 10;
else return 0x0F00;
return onebyte & 0x00FF;
} //end x2i()
/*********************************************************************************************************************************************************************
* faster serial buffer reading function with timeout, value parsing after reading; function works for more than 64byte UART buffer, only for baudrates <= 57600baud
*/
static int serReadBytes_ms(char *buf, byte _lenMAX, uint16_t TIMEOUT_ms) {
volatile uint32_t _startTime = 0L;
volatile uint8_t _thislen;
volatile int _len;
volatile uint16_t _backupTO;
_thislen = 0;
_len = 0;
_backupTO = Serial.getTimeout();
Serial.setTimeout(100);
_startTime = millis();
//fast reading loop of full buffer,
while(Serial.available()) {
_thislen = Serial.readBytes(&buf[_len], _lenMAX - _len);
_len += _thislen;
} //end while()
buf[_len] = '\0';
Serial.setTimeout(_backupTO);
return _len;
}//end serReadBytes_ms()
/**
* eval the serial command smac<txchannel>:<data>:rc<rxchannel>:, e.g. s:ch03:958180....:rc40: and puts it to the packet_t structure
* the data payload is addressed directly to the RF interface with the given channels
*/
static boolean eval_uart_smac_cmd(char *_serdata, uint8_t _slen, packet_t *packet, uint8_t *rxch) {
char *p = NULL;
char *m = NULL;
if(_slen<10) {
DPRINT(DBG_ERROR, F("slen low")); _DPRINT(DBG_ERROR, _slen); _DPRINT(DBG_ERROR, F(" ERROR"));
return false;
}
_slen = c_remove(' ', _serdata, _slen);
c_lower(_serdata, _slen);
p = strtok(_serdata, ":");
m = strstr(p, "mac");
if(m==NULL) return false;
p = strtok(NULL, ":");
m = strstr(p, "ch");
if (m) {
m += 2;
packet->rfch = atoi(m);
DPRINT(DBG_DEBUG, F("smac_txch ")); _DPRINT(DBG_DEBUG, packet->rfch);
p = strtok(NULL, ":");
// next section
DPRINT(DBG_DEBUG, F("smac_data ")); _DPRINT(DBG_DEBUG, p); _DPRINT(DBG_DEBUG, F(", len ")); _DPRINT(DBG_DEBUG, strlen(p));
uint8_t _i = 0;
for (_i = 0; _i < strlen(p); _i += 2) {
packet->data[_i / 2] = (uint8_t)x2b(&p[_i]);
} // end for()
packet->plen = _i / 2;
//eval rx channel input
p = strtok(NULL, ":");
m = strstr(p, "rc");
if (m) {
m += 2;
*rxch = atoi(m);
} else {
*rxch = 0;
}//end if()
DPRINT(DBG_DEBUG, F("smac_rxch ")); _DPRINT(DBG_DEBUG, *rxch);
return true;
} else {
DPRINT(DBG_ERROR, F("smac ERROR"));
return false;
}//end if()
}//end eval_uart_cmd()
static uint32_t eval_uart_decimal_val(const char* _info, char* _serdata, uint8_t _len, uint32_t inMIN, uint32_t inMAX, int outFACTOR) {
volatile uint32_t _tmp32;
DISABLE_IRQ;
_tmp32 = inMIN * outFACTOR;
if ((_len > 1) && (_len < 12)) {
_serdata[_len] = '\0';
_tmp32 = atol(_serdata);
if ((_tmp32 <= inMIN) && (_tmp32 >= inMAX)) {
_tmp32 = inMIN;
}
_tmp32 = outFACTOR * _tmp32;
}
RESTORE_IRQ;
DPRINT(DBG_INFO, _info);
_DPRINT(DBG_INFO,_tmp32);
DPRINT(DBG_INFO, F("OK"));
return _tmp32;
} //end eval_simple_cmd()

11
tools/nano/AhoyUL/test/README

@ -0,0 +1,11 @@
This directory is intended for PlatformIO Test Runner and project tests.
Unit Testing is a software testing method by which individual units of
source code, sets of one or more MCU program modules together with associated
control data, usage procedures, and operating procedures, are tested to
determine whether they are fit for use. Unit testing finds problems early
in the development cycle.
More information about PlatformIO Unit Testing:
- https://docs.platformio.org/en/latest/advanced/unit-testing/index.html
Loading…
Cancel
Save