Browse Source

update automode cmds

pull/478/head
golfi200 3 years ago
parent
commit
05bb16a3bb
  1. 8
      .gitignore
  2. 151
      tools/nano/AhoyUL/FHEM/ahoyUL.pm
  3. 168
      tools/nano/AhoyUL/_libs2/com_handling.py
  4. 129
      tools/nano/AhoyUL/_libs2/file_handling.py
  5. 118
      tools/nano/AhoyUL/hm_terminal.py
  6. 14
      tools/nano/AhoyUL/src/config.h
  7. 7
      tools/nano/AhoyUL/src/hmDefines.h
  8. 6
      tools/nano/AhoyUL/src/hmRadio.h
  9. 580
      tools/nano/AhoyUL/src/main.cpp
  10. 461
      tools/nano/AhoyUL/src/utils_serial.h

8
.gitignore

@ -22,3 +22,11 @@ tools/esp8266/.vscode/extensions.json
.vscode
tools/esp8266/platformio-device-monitor-*.log
tools/esp8266/html/h/*
*.zip
/hoymiles_doc/*
*.pyc
/tools/nano/AhoyUL/log/*.*
*todo
/tools/nano/AhoyUL/_libs2/HoymilesMAC.py
/tools/nano/AhoyUL/myTest_app.py_old.py
/tools/nano/AhoyUL/myTest_app.py

151
tools/nano/AhoyUL/FHEM/ahoyUL.pm

@ -0,0 +1,151 @@
package main;
use strict;
use warnings;
use DevIo; # load DevIo.pm if not already loaded
# called upon loading the module MY_MODULE
sub AHOYUL_Initialize($)
{
my ($hash) = @_;
$hash->{DefFn} = "AHOYUL_Define";
$hash->{UndefFn} = "AHOYUL_Undef";
$hash->{SetFn} = "AHOYUL_Set";
$hash->{ReadFn} = "AHOYUL_Read";
$hash->{ReadyFn} = "AHOYUL_Ready";
$hash->{ParseFn} = "AHOYUL_Parse";
}
# called when a new definition is created (by hand or from configuration read on FHEM startup)
sub AHOYUL_Define($$)
{
my ($hash, $def) = @_;
my @a = split("[ \t]+", $def);
my $name = $a[0];
# $a[1] is always equals the module name "MY_MODULE"
# first argument is a serial device (e.g. "/dev/ttyUSB0@57600,8,N,1")
my $dev = $a[2];
return "no device given" unless($dev);
# close connection if maybe open (on definition modify)
DevIo_CloseDev($hash) if(DevIo_IsOpen($hash));
# add a default baud rate (9600), if not given by user
$dev .= '@57600,8,N,1' if(not $dev =~ m/\@\d+$/);
# set the device to open
$hash->{DeviceName} = $dev;
# open connection with custom init function
my $ret = DevIo_OpenDev($hash, 0, "AHOYUL_Init");
return undef;
}
# called when definition is undefined
# (config reload, shutdown or delete of definition)
sub AHOYUL_Undef($$)
{
my ($hash, $name) = @_;
# close the connection
DevIo_CloseDev($hash);
return undef;
}
# called repeatedly if device disappeared
sub AHOYUL_Ready($)
{
my ($hash) = @_;
# try to reopen the connection in case the connection is lost
return DevIo_OpenDev($hash, 1, "AHOYUL_Init");
}
# called when data was received
sub AHOYUL_Read($)
{
my ($hash) = @_;
my $name = $hash->{NAME};
# read the available data
my $buf = DevIo_SimpleRead($hash);
# stop processing if no data is available (device disconnected)
return if(!defined($buf));
Log3 $name, 5, "AHOYUL ($name) - received: $buf";
#
# do something with $buf, e.g. generate readings, send answers via DevIo_SimpleWrite(), ...
#
}
# called if set command is executed
sub AHOYUL_Set($$@)
{
my ($hash, $name, $params) = @_;
my @a = split("[ \t]+", $params);
$cmd = $params[0]
my $usage = "unknown argument $cmd, choose one of statusRequest:noArg on:noArg off:noArg";
# get command overview from ahoy-nano device
if($cmd eq "?")
{
#todo
DevIo_SimpleWrite($hash, "?\r\n", 2);
}
elsif($cmd eq "a")
{
#todo handle automode and send command to ahoy-nano via cmd a[[:<period_sec>]:<12 digit inverter id>:]
DevIo_SimpleWrite($hash, "a:{$params[1]}:{$params[2]}:\r\n", 2);
}
elsif($cmd eq "c")
{
#todo
#DevIo_SimpleWrite($hash, "off\r\n", 2);
}
elsif($cmd eq "d")
{
#todo
#DevIo_SimpleWrite($hash, "off\r\n", 2);
}
elsif($cmd eq "i")
{
#todo
#DevIo_SimpleWrite($hash, "off\r\n", 2);
}
elsif($cmd eq "s")
{
#todo
#DevIo_SimpleWrite($hash, "off\r\n", 2);
}
else
{
return $usage;
}
}
# will be executed upon successful connection establishment (see DevIo_OpenDev())
sub AHOYUL_Init($)
{
my ($hash) = @_;
# send init to device, here e.g. enable automode to send DevInfoReq (0x15 ... 0x0B ....) every 120sec and enable simple decoding in ahoy-nano
DevIo_SimpleWrite($hash, "a120:::::d1:\r\n", 2);
return undef;
}
1;

168
tools/nano/AhoyUL/_libs2/com_handling.py

@ -0,0 +1,168 @@
#handles at-commands send and response evaluation, a thread can catch the URCs of the module
import os
import sys
import time
import datetime
import threading
import binascii
import serial.tools.list_ports
import _libs2.file_handling as fh
#some global variables
__version_info__ = ('2022', '11', '13')
__version_string__ = '%40s' % ('hm inverter handling version: ' + '-'.join(__version_info__))
lck2 = threading.Lock() # used for AT-commands to access serial port
com_stop = True
promt = '/> '
respo = '<< '
sendi = '>> '
def get_version():
return __hm_handling_version__
# async reading thread of comport data (e.g. for URCs)
class AsyncComRead(threading.Thread):
def __init__(self, _com, _addTimestamp, _debug):
# calling superclass init
threading.Thread.__init__(self)
self.com = _com
self.addTS = _addTimestamp
self.debug = _debug
self.data = ''
def run(self):
global com_stop
if self.debug: print('\n +++ Thread: com reader started', end='', flush=True)
urc_millis = millis_since(0)
urc_ln = True
while not com_stop:
with lck2:
urc = []
while self.com.in_waiting:
line = self.com.readline().decode('ascii','ignore')
line = line.replace('\n','').replace('\r','')
if len(line) > 2: # filter out empty lines
if self.addTS:
urc.append('\n%s%s%s' % (timestamp(0), respo, line))
else:
urc.append('\n%s%s' % (respo, line))
#todo: return values into callback function
if not self.com.is_open or com_stop: break
if urc:
fh.my_print('\n%s' % ( (' '.join(urc)).replace('\n ', '\n') ))
urc_millis = millis_since(0)
urc_ln = False
if (not urc_ln) and (millis_since(urc_millis) >= 3000):
urc_ln = True
print('\n%s' % (promt,), end = '')
time.sleep(0.1)
if self.debug: print('\n +++ Thread: com reader stopped', end='', flush=True)
#def parse_payload(_self, _data):
# opens the com-port and starts the Async com reading thread
def openCom_startComReadThread(_tp, _com, _debug):
global com_stop
#start urc com logging thread
if not _com.is_open: _com.open()
_tp = AsyncComRead(_com, True, _debug)
_tp.setDaemon = True
com_stop = False
_tp.start()
return _tp
# stops the com-reading thread and closes com-port
def stopComReadThread_closeCom(_tp, _com):
global com_stop
com_stop = True
_tp.join(1.1)
if _com.is_open: _com.close()
return com_stop
# opens com-port if the given port-string exists and starts the Async com-port reading thread
def check_port_and_openCom(_tp, _com, _portstr, MATCH_sec, TIMEOUT_sec, _DEBUG):
if (check_wait_port(_portstr, MATCH_sec, TIMEOUT_sec, _DEBUG) == 0):
fh.my_print('\n +++ Port enumerated and detected: ' + _portstr.lower())
# reopen port
_tp = openCom_startComReadThread(_tp, _com, _DEBUG)
else:
fh.my_print('\n +++ No USB com-port detected again --> end')
_tp = None
return _tp
# simply list the available com-ports of the system
def list_ports():
portlist = serial.tools.list_ports.comports()
fh.my_print('\n +++ detected ports:')
for element in portlist:
fh.my_print(' %s' % (str(element.device)))
## simple Terminal to enter and display cmds, resonses are read by urc thread
def simple_terminal(_com, _debug):
next = True
while(next):
time.sleep(1.0)
_cmd_in = input('\n%s'%(promt,))
fh.my_print('\n%s%s' % (promt, _cmd_in, ))
if "start" in _cmd_in:
#end terminal and return true
return True
elif "kill" in _cmd_in:
#ends terminal and returns false
return False
elif '_time' in _cmd_in or '_tnow' in _cmd_in:
_uartstr = 't{:d}'.format(int(time.time()))
_com.write( _uartstr.encode('ascii') )
fh.my_print('\n%s%s%s' % (timestamp(0), sendi, _uartstr, ))
continue
_uartstr = _cmd_in.encode('ascii')
_com.write( ('%s%s' % (sendi, _uartstr, )).encode('ascii') )
fh.my_print('\n%s%s%s' % (timestamp(0), sendi, _uartstr, ))
return True
# calculates the time in milli-seconds from a given start time
def millis_since(thisstart):
return int(round(time.time() * 1000) - thisstart)
# different timestamp formats
def timestamp(format):
if(format==0):
return ('%s: ' % (datetime.datetime.now().strftime("%Y-%m-%d_%H:%M:%S.%f")[:-3]))
elif format==1:
return str(time.time()) + ": "
elif format==2:
#for file name extention
return datetime.datetime.now().strftime("%Y-%m-%d_%H%M_")
elif format==3:
#for file name extention with seconds
return datetime.datetime.now().strftime("%Y-%m-%d_%H%M%S_")
elif format==4:
return datetime.datetime.now().strftime("%H%M%S.%f")
else:
return datetime.datetime.now().strftime("%y/%m/%d,%H:%M:%S+01")

129
tools/nano/AhoyUL/_libs2/file_handling.py

@ -0,0 +1,129 @@
# has a thread for periodic log file writing and some generic file handling
# my_print function write into global variable to be stored in log file
import os
import sys
import time
import datetime
import threading
import binascii
#some global variables
__version_info__ = ('2022', '11', '13')
__version_string__ = 'file handling version: ' + '-'.join(__version_info__)
lck = threading.Lock() # used for log_sum writing
log_sum = 'logstart... '
log_stop = True
def get_version():
return __version_string__
def set_log_stop(_state):
global log_stop
log_stop = _state
def get_log_stop():
return log_stop
def my_print(out):
global lck
global log_sum
print(out, end='', flush=True)
with lck:
log_sum += out
# async writing thread of log file data
class AsyncWrite(threading.Thread):
def __init__(self, _logfile, _debug):
# calling superclass init
threading.Thread.__init__(self)
self.file = _logfile
self.debug = _debug
def run(self):
global lck
global log_sum
global log_stop
log_stop = False
if self.debug: print('\n +++ Thread: file writer started', end='', flush=True)
while not log_stop:
with lck:
if len(log_sum) > 2000:
if self.debug: print('\n +++ writing to logfile, ' + str(len(log_sum)) + ' bytes', end='', flush=True)
self.file.write(log_sum.replace('\r','').replace('\n\n','\n'))
log_sum = ''
time.sleep(2.0)
#write remaining data to log
with lck:
if self.debug: print('\n +++ writing to logfile remaining, ' + str(len(log_sum)) + ' bytes', end='', flush=True)
self.file.write(log_sum.replace('\r','').replace('\n\n','\n'))
log_sum = ''
self.file.flush() #needed to finally write all data into file after stop
self.file.close()
if self.debug: print('\n +++ Thread: file writer stopped and file closed', end='', flush=True)
def start_logging_thread_open_file(_dirname, _filename, _mode, _DEBUG):
global log_stop
#open file
_logfile = open_file_makedir(_dirname, _filename, _mode, _DEBUG)
if _DEBUG: my_print('\n +++ generate log file: ' + str(_dirname + '/' + _filename))
#start logging thread
_tf = AsyncWrite(_logfile, _DEBUG)
_tf.setDaemon = True
log_stop = False
_tf.start()
return _tf
def stop_logging_thread_close_file(_tf, _DEBUG):
global log_stop
log_stop = True
if _DEBUG: my_print('\n +++ log_stop is: ' + str(log_stop))
if _tf: _tf.join(2.1)
## extracts the pathname and filename from fiven input string and returns two strings
def get_dir_file_name(_pathfile, _DEBUG):
_dir_name = './'
_file_name = ''
if len(_pathfile) > 0:
#if '/' not in _pathfile or '\\' not in _pathfile:
#_pathfile = './' + _pathfile
_dir_name, _file_name = os.path.split(_pathfile)
if len(_dir_name) <= 0:
_dir_name = './'
else:
my_print("\n +++ no file name, exit ")
sys.exit()
if _DEBUG:
my_print("\n +++ get_dir_file_name: " + str(_dir_name + ' / ' + _file_name))
return _dir_name,_file_name
## handling of file opening for read and write in a given directory, directory will be created if not existing
## it returns the file connection pointer
def open_file_makedir(_dir_name, _file_name, _mode, _DEBUG):
if _DEBUG:
my_print("\n +++ open_file_makedir: " + str(_dir_name + ' / ' + _file_name))
if len(_dir_name) > 0:
os.makedirs(_dir_name, exist_ok=True)
if _DEBUG:
my_print("\n +++ open file: " + str(_dir_name + ' / ' + _file_name) + " mode: " + _mode)
_file = open(_dir_name + '/' + _file_name, _mode)
return _file

118
tools/nano/AhoyUL/hm_terminal.py

@ -0,0 +1,118 @@
#works for python 3.7.3
import serial
import os
import argparse
import time
import datetime
import binascii
import threading
import sys
from binascii import hexlify
# my own libs
import _libs2.com_handling as cph
import _libs2.file_handling as fh
# 2022-02-11: init mb
#some global variables
__version_info__ = ('2022', '11', '13')
__version__ = 'app version: ' + '-'.join(__version_info__)
DEBUG = False
############################################################################################################################
# here the program starts
def main():
global DEBUG
global com_stop
ret = 0
err = 0
starttime = cph.millis_since(0)
parser = argparse.ArgumentParser(description='simple Hoymiles Terminal')
parser.add_argument('-p', type=str, default='/dev/ttyUSB0', help='Serial port')
parser.add_argument('-b', type=int, default=57600, help='baudrate of the rf-module')
parser.add_argument('-d', default=False, help='use parameter to print additional debug info', action='store_true')
parser.add_argument('-v', '--version', action='version', version="%(prog)s (" + __version__ + ")")
parser.add_argument('-l', type=str, default='', help='log-file path/name')
parser.add_argument('-i', default=True, help='have a simple AT-command line', action='store_true')
args = parser.parse_args()
DEBUG = args.d
#used later for Threads
tp = None
tf = None
com = None
logfile = None
#shows the used lib versions
fh.my_print('\n +++ starting AhoyUL Terminal ...')
fh.my_print('\n +++ %40s' % (__version__))
fh.my_print('\n +++ %40s' % (cph.__version_string__))
fh.my_print('\n +++ %40s' % (fh.__version_string__))
if DEBUG: fh.my_print('\n' + str(args))
#output file logging
com_port_name = (args.p).replace('/dev/','_').replace('/','_') #for Linux, no matter on Windows
if len(args.l) > 0:
tmp1, tmp2 = fh.get_dir_file_name(args.l, DEBUG)
logfile_name = cph.timestamp(3) + tmp2
tf = fh.start_logging_thread_open_file(tmp1, logfile_name, 'w+', DEBUG)
else:
# default path of logfile, always logging
tmp1, tmp2 = fh.get_dir_file_name('log/default_{0:s}.log'.format(com_port_name), DEBUG)
logfile_name = cph.timestamp(3) + tmp2
tf = fh.start_logging_thread_open_file(tmp1, logfile_name, 'w+', DEBUG)
com = serial.Serial(args.p, args.b, timeout=0.2, rtscts=False, dsrdtr=False)
com.rts = True
com.dtr = True
fh.my_print("\n +++ serial port is open: %s, baud: %d, rtscts: %s, rts: %s" % (com.portstr,com.baudrate,com.rtscts,com.rts))
#start urc com logging thread
tp = cph.openCom_startComReadThread(tp, com, DEBUG)
try:
# do init AT here
#start small AT-command terminal
if args.i == True:
result = cph.simple_terminal(com, DEBUG)
if result == False:
starttime_sec = cph.millis_since(0) / 1000
return -1
# todo: do some automated scripting and data sending to the AhoyUL board
#wait remeining data
TO_sec = 5
fh.my_print('\n +++ wait remaining data for %d sec' % (TO_sec,))
time.sleep(TO_sec)
except KeyboardInterrupt:
fh.my_print('\n +++ keyboard interrupt, end ...')
finally:
fh.my_print('\n +++ end, wait until finshed\n')
#cleanup
#stop comport reader thread
if tp: cph.stopComReadThread_closeCom(tp, com)
# if logging to file was enabled
#if len(args.l) > 0:
if tf: fh.stop_logging_thread_close_file(tf, DEBUG)
if __name__ == "__main__":
main()

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

@ -26,13 +26,15 @@
#define DEF_RF24_IRQ_PIN (3)
#endif
#define DEF_VERSION "\n version 2022-12-01 21:45"
// 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
#define IV1_RADIO_ID ((uint64_t) 0x114144332211ULL) // 0x1141 is type-id for HM800, lower 4bytes must be filled with real ID from INV-plate
// default NRF24 power, possible values (0 - 3)
#define DEF_AMPLIFIERPOWER 2
@ -41,7 +43,7 @@
#define PACKET_BUFFER_SIZE 7
// number of configurable inverters
#define MAX_NUM_INVERTERS 1
#define MAX_NUM_INVERTERS 2
// default serial interval
#define SERIAL_INTERVAL 5
@ -57,7 +59,7 @@
#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
#define MAX_PAYLOAD_ENTRIES 5
// maximum requests for retransmits per payload (per inverter)
#define DEF_MAX_RETRANS_PER_PYLD 10
@ -69,6 +71,12 @@
#define INACT_PWR_THRESH 3
//will be used for serial utils buffers
#define MAX_SERBYTES 120 //serial input buffer length of this app
#define MAX_STRING_LEN 50 //max length of output string at once
#define MAX_SER_PARAM 5 //max num of serial cmd parameter
#if __has_include("config_override.h")
#include "config_override.h"
#endif

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

@ -20,7 +20,7 @@ union serial_u {
// 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", ""};
const char PGM_units[][6] PROGMEM {" V ", " A ", " W ", " Wh ", " kWh ", " Hz ", " °C ", " % ", " var ", " "}; //one hidden byte needed at the end for '\0'
// field types
enum {FLD_UDC = 0, FLD_IDC, FLD_PDC, FLD_YD, FLD_YW, FLD_YT,
@ -29,7 +29,7 @@ enum {FLD_UDC = 0, FLD_IDC, FLD_PDC, FLD_YD, FLD_YW, FLD_YT,
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
//PGM Flash memory usage instead of RAM for ARDUINO NANO, idea given by Nick Gammon's great webpage http://gammon.com.au/progmem
const char PGM_fields[/*NUM ELEMENTS*/][25] PROGMEM {
{"U_DC"},
{"I_DC"},
@ -262,4 +262,7 @@ const byteAssign_t hm4chAssignment[] = {
#define HM4CH_PAYLOAD_LEN 62
#endif /*__HM_DEFINES_H__*/

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

@ -217,7 +217,7 @@ class HmRadio {
}
void sendControlPacket(uint8_t *_radio_id, uint8_t cmd, uint16_t *data) {
DPRINTLN(DBG_VERBOSE, F("hmRadio:sendControlPacket"));
//DPRINTLN(DBG_INFO, 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
@ -435,7 +435,9 @@ class HmRadio {
mNrf24.printPrettyDetails();
}
// todo: scan all channel for 1bit rdp value (1 == >=-64dBm, 0 == <-64dBm)
/**
* scans all channel for 1bit rdp value (1 == >=-64dBm, 0 == <-64dBm)
*/
void scanRF(void) {
bool _rdp = 0;
DISABLE_IRQ;

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

@ -10,8 +10,8 @@
// 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.)
// - mac-mode: -> The hoymiles specific REQUEST messages must be given as input via serial-IF (@57600baud) smac-packet
// <- The full sorted RESPONSE is given to the serial-IF with as rmac-packet (to be used with python, fhem, etc.)
//
#include <Arduino.h>
@ -19,10 +19,6 @@
#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"
@ -45,7 +41,7 @@ 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 getInvIX(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);
@ -53,8 +49,8 @@ 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 bool out_uart_smac_resp(invPayload_t *);
static uint8_t collect_and_return_userPayload(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);
@ -64,18 +60,18 @@ static config_t mConfig;
static BufferType packet_Buffer;
// static char mVersion[12];
static volatile uint32_t mTimestamp;
static volatile uint32_t mTimestamp; // used to count the seconds
// static uint16_t mSendTicker;
// static uint8_t mSendLastIvId;
static invPayload_t mPayload[MAX_NUM_INVERTERS];
static invPayload_t mPayload[MAX_NUM_INVERTERS]; // that is the central storage of data related to all registered inverters
// static uint32_t mRxFailed;
// static uint32_t mRxSuccess;
// static uint32_t mFrameCnt;
// static uint8_t mLastPacketId;
// serial
static volatile uint16_t mSerialTicker;
static volatile uint16_t mSerialTicker; // could be used for serial output control
// timer
// static uint32_t mTicker;
@ -84,55 +80,93 @@ static volatile uint16_t mSerialTicker;
// 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;
// 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};
static const char VERSION[] PROGMEM = {DEF_VERSION};
#define USER_PAYLOAD_MAXLEN 128
static uint8_t user_payload[USER_PAYLOAD_MAXLEN]; // used for simple decoding and output only
#define USER_PAYLOAD_MAXLEN 80 // user_payload buffer size, so far a max output length is 62 for 4ch Inverter
static uint8_t user_payload[USER_PAYLOAD_MAXLEN]; // buffer used for simple decoding and output only, one inverter 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
static SerialUtils utSer; // class reference of serial utils
static char inSer; // one char input-buffer for most simple uart-cmds
static char *m_strout_p; // pointer of output buffer
static char **mParams; // pointer to char arrays used for input buffer str tokenizer output, array must get initialized with physical space!!!
//static char* mParam[MAX_SER_PARAM]; // optionally: array of pointers to char*
static packet_t rfTX_packet; // structure of one received radio packet
static uint8_t rxch; // keeps the current RX channel
///////////////////////////////////////////////////////////////////
// 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 m_inv_ix = 0;
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 sendNow = false;
static bool doDecode = true;
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 uint8_t tmp81 = 0;
static uint32_t min_SEND_SYNC_msec = MIN_SEND_INTERVAL_ms;
static bool iv_devControlReq = false; //this is one kind of requests, see devControlCmds
static uint8_t iv_devControlCmd = ActivePowerContr; //for now the only devControlCmd
static uint16_t iv_powerLimit[2]; // limit power output
/**
*
*
* setup()
*
***********************************************************************************************/
void setup() {
// Serial.begin(115200);
Serial.begin(57600);
printf_begin();
printf_begin(); // I guess, need for printf(), tocheck with F() and PROGMEM
Serial.flush();
Serial.print(P(NAME));
Serial.print(F("\ncompiled "));
Serial.print(P(COMPILE_DATE));
Serial.print(F(" "));
Serial.print(P(COMPILE_TIME));
Serial.print(P(VERSION)); Serial.print(F(" /compiled ")); 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);
DPRINT(DBG_INFO, F("freeRAM "));
_DPRINT(DBG_INFO, availableMemory());
delay(100);
hmRadio.setup(&mConfig, &packet_Buffer);
attachInterrupt(digitalPinToInterrupt(DEF_RF24_IRQ_PIN), handleIntr, FALLING);
utSer.setup(MAX_SERBYTES, MAX_STRING_LEN, MAX_SER_PARAM);
m_strout_p = utSer.getStrOutBuf(); // points to valid output buffer address
// 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
swap_bytes(&mPayload[0].invId[1], (uint32_t)IV1_RADIO_ID); // high byte is at lowest index
mPayload[0].invType = (uint16_t)(IV1_RADIO_ID >> 32); // keep just upper 6 and 5th byte (e.g.0x1141) of interter plate id for type
// hmRadio.dumpBuf("\nsetup InvID ", &mPayload[0].invId[0], 5, DBG_DEBUG);
@ -141,36 +175,21 @@ void setup() {
// todo: load Inverter decoder depending on InvID
} // end setup()
m_inv_ix = 0; //first inverter index
// 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;
//global var for all inverter
iv_powerLimit[1] = AbsolutNonPersistent;
iv_powerLimit[0] = 0xFFFF; //unlimited
} // end setup()
/////////////////////////////////////////////////////////////////////////
///////////////////////////////////////////////////////////////////////
/**
*
*
* loop()
*
**********************************************************************************************************************/
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) {
@ -186,22 +205,112 @@ void loop() {
} // end if
}
// query serial-IF for some control and data to be send via RF (cmd: sMAC:chXX:... see eval_uart_smac_cmd() format)
// set and query serial-IF with some commands, in non-automode mainly cmd sMAC:chXX:... see format in src
if (Serial.available()) {
// wait char
inSer = Serial.read();
delay(10);
delay(2);
switch (inSer) {
case (char)'a': {
// enable automode with REQ polling interval via a10 => 10sec, a100 => 100sec or other range 5....3600sec
//extend automode with polling period in sec, inverter ix and id, all numerial value are optional from the back
//cmd: a[[[:<poll_sec>]:<invIX>]:<12digit invID>:] e.g. a:120:1:081511223344:, also polling only a:120: and polling and inv_ix via a:120:1
automode = true;
m_inv_ix = 0;
uint8_t invIX = 0;
uint16_t invType = 0x4711;
uint8_t invID5[5] = {0x01, 0x44, 0x33, 0x22, 0x11};
mParams = utSer.getParamsBuf();
tmp8 = utSer.read_uart_cmd_param(mParams);
if (tmp8 > 0) {
//get polling interval from first parameter
tmp81 = strlen(&mParams[0][0]);
if (tmp81 > 0 && tmp81 < 5) {
polling_inv_msec = 1000 * utSer.uart_eval_decimal_val(F("auto poll sec "), &mParams[0][0], 5, 5, 3600, 1);
}
if (tmp8 > 2) {
//get inverter index and inverter ID from parameter 2 and 3
if (utSer.uart_cmd_add_inverter_parsing( &mParams[1], MAX_SER_PARAM, &invIX, &invType, &invID5[0])) {
// write to inverter structure at given index
mPayload[invIX].invType = invType;
memcpy(mPayload[invIX].invId, invID5, 5);
m_inv_ix = invIX;
DPRINT(DBG_INFO, F(" OK"));
// todo: save inverter list to eeprom depending on 4th parameter (e.g ":eep:" )
} //end if()
} else if (tmp8 > 1) {
//try to get and set the inverter-index onyl
tmp81 = (uint8_t) (utSer.uart_eval_decimal_val(NULL, &mParams[1][0], tmp8, 0, MAX_NUM_INVERTERS, 1));
if (mPayload[tmp81].invType != 0x0000) {
m_inv_ix = tmp81;
DPRINT(DBG_INFO, F(" inv_ix "));
_DPRINT(DBG_INFO, m_inv_ix);
DPRINT(DBG_INFO, F(" OK"));
} else {
DPRINT(DBG_INFO, F(" ERR"));
} //end if-else
}//end if(tmp8 > 2)-else
} //end if(tmp8 > 0)
break;
} // end case a
case (char)'d': {
// trigger decoding, enable periodic decoding via "d1" and disable via "d0"
Serial.print(F("\nd"));
// simple decoding, can only handle the current active inverter index, maybe erased if new data arrive, switch other inverter via interter indexing in automode settings
decodePayload(TX_REQ_INFO + 0x80, user_payload, 42, user_pl_ts, utSer.mStrOutBuf, MAX_STRING_LEN, mPayload[m_inv_ix].invType);
sread_len = utSer.serBlockRead_ms(utSer.mSerBuffer);
doDecode = (bool)utSer.uart_eval_decimal_val(F("decoding "), utSer.mSerBuffer, sread_len, 0, 255, 1);
break;
} // end case d
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';
// 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
//todo: not yet tested much
if (automode == true) {
automode = false;
sread_len = serReadBytes_ms(ser_buffer, MAX_SERBYTES, 2000);
if (eval_uart_smac_cmd(ser_buffer, sread_len, &rfTX_packet, &rxch)) {
DPRINT(DBG_INFO, F("automode ")); _DPRINT(DBG_INFO, automode);
}
mParams = utSer.getParamsBuf();
tmp8 = utSer.read_uart_cmd_param(mParams);
if (tmp8 > 0) {
if (strstr(&mParams[0][0], "mac")) {
if (utSer.uart_cmd_smac_request_parser(mParams, tmp8, &rfTX_packet, &rxch)) {
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
m_inv_ix = getInvIX(mPayload, MAX_NUM_INVERTERS, &rfTX_packet.data[0]);
if (m_inv_ix == 0xFF) {
DPRINT(DBG_DEBUG, F("inv_id no match"));
m_inv_ix = MAX_NUM_INVERTERS - 1; //use last possition
}
DPRINT(DBG_DEBUG, F("m_inv_ix ")); _DPRINT(DBG_DEBUG, m_inv_ix);
payload_used[m_inv_ix] = !resetPayload(&mPayload[m_inv_ix]);
mPayload[m_inv_ix].isMACPacket = true; // MAC must be enabled to show the full MAC packet, no need for user_payload only
mPayload[m_inv_ix].receive = false;
hmRadio.sendPacket_raw(&mPayload[0].invId[0], &rfTX_packet, rxch); // 2022-10-30: byte array transfer working
mPayload[m_inv_ix].requested = true;
mPayload[m_inv_ix].invType = 0x1111; //used as dummy type, decode works external only with known type
}//end if(utSer.uart_cmd_smac_request_parser(...))
} // end if(mac)
} // end if(tmp8)
/*
sread_len = utSer.serBlockRead_ms(utSer.mSerBuffer);
if (utSer.eval_uart_smac_request(utSer.mSerBuffer, sread_len, &rfTX_packet, &rxch)) {
// send on Tx channel and receive on Rx channel
if (rxch == 0) {
// if rxchannel not given, then set automatically
@ -210,81 +319,165 @@ void loop() {
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;
m_inv_ix = getInvIX(mPayload, MAX_NUM_INVERTERS, &rfTX_packet.data[0]);
if (m_inv_ix != 0xFF) {
if (m_inv_ix < MAX_NUM_INVERTERS) {
}
DPRINT(DBG_DEBUG, F("match, m_inv_ix "));
_DPRINT(DBG_DEBUG, m_inv_ix);
payload_used[m_inv_ix] = !resetPayload(&mPayload[m_inv_ix]);
mPayload[m_inv_ix].isMACPacket = true; // MAC must be enabled to show the full MAC packet, no need for user_payload only
mPayload[m_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;
mPayload[m_inv_ix].requested = true;
} else {
// no matching inverter, do nothing
inv_ix = 0;
}
m_inv_ix = 0;
}
} // end if
*/
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)'i': {
// inverter handling cmds for automode
uint8_t invIX = 0;
uint16_t invType = 0x4711;
uint8_t invID5[5] = {0x01, 0x44, 0x33, 0x22, 0x11};
// cmd tokens will be written in mParams-pointer array
mParams = utSer.getParamsBuf();
tmp8 = utSer.read_uart_cmd_param(mParams);
if (tmp8 > 0) {
if (strstr(&mParams[0][0], "add")) {
// cmd to add a new inverter at index possition
// /> invreg:01:1144 11223344: - register new inverter at index 01 and plate id
//
if (utSer.uart_cmd_add_inverter_parsing(++mParams, tmp8, &invIX, &invType, &invID5[0])) {
// write to inverter structure at given index
mPayload[invIX].invType = invType;
memcpy(mPayload[invIX].invId, invID5, 5);
// todo: extend inverter list to eeprom
}
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;
} else if (strstr(&mParams[0][0], "lst")) {
// match cmd to list inverter which are registered
// /> invlist: -- list content of all index possitions
//
Serial.print(F("\ninv List:"));
for (uint8_t x = 0; x < MAX_NUM_INVERTERS; x++) {
snprintf_P(utSer.getStrOutBuf(), MAX_STRING_LEN, PSTR("\n %d: %04X "), x, mPayload[x].invType);
Serial.print(utSer.getStrOutBuf());
utSer.print_bytes(mPayload[x].invId, 5, " ", true);
} // end for()
} else if (strstr(&mParams[0][0], "del")) {
// cmd to delete inverter from inverter list via given index
// /> invdel:01: -- delete index 01 from list
//
if (utSer.uart_cmd_del_inverter_parsing(mParams, tmp8, &invIX)) {
mPayload[invIX].invType = 0x0000;
}
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);
} else {
// no cmd match
}
}
break;
} // end case a
} // end case i
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);
case (char)'c': {
// todo: scan all channels for 1bit RDP (RSSI) value and print result
// Serial.print(F("\nc OK "));
mParams = utSer.getParamsBuf();
tmp8 = utSer.read_uart_cmd_param(mParams);
if (tmp8 > 0) {
tmp81 = (uint8_t) (utSer.uart_eval_decimal_val(F("scan "), &mParams[0][0], 5, 1, 255, 1));
while (tmp81--) {
hmRadio.scanRF();
if (Serial.available()) {
// wait char
inSer = Serial.read();
if (inSer >= '\n') break; //from while()
}//end if
}//end while()
}
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);
// enable/disable show MACmessages via "m0" or "m1"
Serial.print(F("\nm"));
sread_len = utSer.serBlockRead_ms(utSer.mSerBuffer);
showMAC = (bool)utSer.uart_eval_decimal_val(F("showMAC "), utSer.mSerBuffer, sread_len, 0, 255, 1);
break;
} // end case m
case (char)'i': {
// query current radio information
DPRINT(DBG_INFO, F("i OK"));
case (char)'r': {
mParams = utSer.getParamsBuf();
tmp8 = utSer.read_uart_cmd_param(mParams,2);
if (strstr(&mParams[0][0], "xch")) {
// sets rx channel via e.g via cmd rxch:63:
rxch = utSer.uart_eval_decimal_val(F("rx chan "), &mParams[1][0], 3, 0, 125, 1); //use first parameter after cmd
} else {
// query current radio information via "r"
hmRadio.print_radio_details();
}//end if-else()
break;
}
case (char)'p': {
//set or query power limit from inverter, parameter must be separated by ":"
//todo: extend para-check to reactive powerlimit "%var" and "var"
mParams = utSer.getParamsBuf();
tmp8 = utSer.read_uart_cmd_param(mParams,3);
if (strstr(&mParams[0][0], "?")) {
// cmd:/ "p?"
// query power setting
//not sure there is a query request
DPRINT(DBG_DEBUG, F("query: not yet, break"));
break;
} else if (strstr(&mParams[0][0], "%")) {
//make non persistent settings only
// set relative power
iv_powerLimit[0] = (uint16_t) utSer.uart_eval_decimal_val(F("Prel "), &mParams[0][0], 4, 0, 100, 1);
iv_powerLimit[1] = RelativNonPersistent;
} if (strstr(&mParams[0][0], "w")) {
//make non persistent settings only
// set absolute power
iv_powerLimit[0] = (uint16_t) utSer.uart_eval_decimal_val(F("Pabs "), &mParams[0][0], 5, 0, 65000, 1);
iv_powerLimit[1] = AbsolutNonPersistent;
}//end if-else()
//if (!iv_devControlReq) {
iv_devControlReq = true; //defines type of message, not that something is pending
sendNow = true;
//}
break;
}//end p
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);
//ther is no need of exact timing, it must only increase (change) in REQ_INFO_CMDs
sread_len = utSer.serBlockRead_ms(utSer.getInBuf());
mTimestamp = utSer.uart_eval_decimal_val(F("time set "), utSer.getInBuf(), sread_len, 10 * 3600, 0xFFFFFFFF, 1);
break;
} // end case t
case (char)'?':{
Serial.print(F("\ncmds: a, c, d, iadd, idel, ilst, m, p, s, rxch, t, ?"));
break;
} //end case '?'
} // end switch-case
} // end if serial...
// automode RF-Tx-trigger
if (automode) {
// slow down the sending if no Rx for long time
@ -294,18 +487,26 @@ void loop() {
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) ) {
if (millis() - timer2_millis >= min_SEND_SYNC_msec && ((millis() - lastRx_millis > polling_inv_msec) || millis() < 60000 || sendNow)) {
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;
payload_used[m_inv_ix] = !resetPayload(&mPayload[m_inv_ix]);
mPayload[m_inv_ix].isMACPacket = true;
mPayload[m_inv_ix].receive = false;
mPayload[m_inv_ix].requested = true; // assume the previous sending is finished because of scheduled timing, therefore no check if still true
if (iv_devControlReq) {
//send devControlReq
hmRadio.sendControlPacket(&mPayload[m_inv_ix].invId[0], iv_devControlCmd, iv_powerLimit);
iv_devControlReq = false; //todo: use timer that it is not send to quick (e.g. max once per 5sec)
sendNow = false;
} else {
//send regular info request
hmRadio.sendTimePacket(&mPayload[m_inv_ix].invId[0], 0x0B, mTimestamp, 0x0000);
}
// RESTORE_IRQ;
}
}
@ -313,7 +514,7 @@ void loop() {
// 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)
// eval RF-Rx raw data (one single entry per loop to keep receiving new messages from one inverter m_inv_ix (inverter domain is set in nrf24+ via invID)
if (!packet_Buffer.empty()) {
packet_t *p;
p = packet_Buffer.getBack();
@ -329,10 +530,10 @@ void loop() {
// 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)) {
// no need to get the m_inv_ix, because only desired inverter will answer, payload buffer is CRC protected
// m_inv_ix = getInvID(mPayload, MAX_NUM_INVERTERS, &p->data[0]);
if (m_inv_ix >= 0 && m_inv_ix < MAX_NUM_INVERTERS) {
if (copyPacket2Payload(&mPayload[m_inv_ix], m_inv_ix, p, mPayload[m_inv_ix].isMACPacket)) {
lastRx_millis = millis();
}
}
@ -345,21 +546,22 @@ void loop() {
// 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) {
if ((millis() - timer2_millis >= 500) && packet_Buffer.empty() && mPayload[m_inv_ix].requested && mPayload[m_inv_ix].receive) {
// process payload some sec after last sending
if (false == payload_used[inv_ix]) {
if (processPayload(&mPayload[inv_ix], &mConfig, true)) {
if (false == payload_used[m_inv_ix]) {
if (processPayload(&mPayload[m_inv_ix], &mConfig, true)) {
// data valid and complete
if (mPayload[inv_ix].isMACPacket && showMAC) {
returnMACPackets(&mPayload[inv_ix]);
if (mPayload[m_inv_ix].isMACPacket && showMAC) {
out_uart_smac_resp(&mPayload[m_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;
payload_used[m_inv_ix] = true;
tmp8 = collect_and_return_userPayload(&mPayload[m_inv_ix], &user_payload[0], USER_PAYLOAD_MAXLEN);
user_pl_ts = mPayload[m_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);
decodePayload(mPayload[m_inv_ix].txId, &user_payload[0], tmp8, user_pl_ts, m_strout_p, MAX_STRING_LEN, mPayload[m_inv_ix].invType);
}
}
}
@ -404,9 +606,6 @@ static void loadDefaultConfig(config_t *_mconfig) {
_mconfig->serialDebug = true;
}
// free RAM check for debugging. SRAM for ATmega328p = 2048Kb.
static int availableMemory() {
// Use 1024 with ATmega168
@ -432,27 +631,31 @@ static uint32_t swap_bytes(uint32_t _v) {
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;
}
// 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
* gets the payload index that matches to the received ID or the first empty payload structure possition,
* in case index exeeds _pMAX then 0xFF is returned
*/
static uint8_t getInvID(invPayload_t *_payload, uint8_t _pMAX, uint8_t *rcv_data) {
static uint8_t getInvIX(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;
// if (check_array(&_payload->invId[1], &rcv_data[1], 4)) return i;
if (memcmp(&_payload->invId[1], &rcv_data[1], 4) == 0)
return i;
else if (memcmp(&_payload->invId[1], "\0\0\0\0", 4) == 0)
return i;
_payload++;
}
return (uint8_t)0xFF;
@ -468,7 +671,6 @@ static uint8_t getNumInv(invPayload_t *_payload, uint8_t _pMAX) {
return i;
}
////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
//
// low level payload packet handling
@ -479,7 +681,7 @@ static uint8_t getNumInv(invPayload_t *_payload, uint8_t _pMAX) {
* clearing of the current inverter data structure, except the ID and typedef
*/
static bool resetPayload(invPayload_t *_payload) {
// static uint8_t inv_ix;
// static uint8_t m_inv_ix;
uint8_t backup_inv_id[5];
uint16_t backup_inv_type;
DPRINT(DBG_VERBOSE, F("resetPayload invid "));
@ -557,7 +759,6 @@ static bool copyPacket2Payload(invPayload_t *_payload, uint8_t _invx, packet_t *
return _res;
} // end savePayload()
/**
* saves copies one received payload-packet into inverter memory structure
*/
@ -565,8 +766,8 @@ static bool savePayloadfragment(invPayload_t *_payload, packet_t *_p, uint8_t _p
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;
memcpy(&_payload->data[(_pid & 0x7F) - 1][0], &_p->data[_ix], _p->plen - _ix);
_payload->len[(_pid & 0x7F) - 1] = _p->plen - _ix;
_payload->rxChIdx = _p->rfch;
_payload->ts = millis();
_payload->txId = _p->data[0];
@ -587,7 +788,6 @@ static bool savePayloadfragment(invPayload_t *_payload, packet_t *_p, uint8_t _p
return _res;
} // end savePayload fragment
/**
* process the received payload and handles retransmission request for one inverter
*/
@ -627,7 +827,6 @@ static bool processPayload(invPayload_t *_payload, config_t *_mconfig, bool retr
} // end processPayload()
/**
* checks the paypload of all received packet-fragments via CRC16 and length
* returns:
@ -659,16 +858,16 @@ static uint8_t checkPayload(invPayload_t *_payload) {
// 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);
DPRINT(DBG_DEBUG, F(" checkPL "));
_DPRINT(DBG_DEBUG, _payload->len[i] - ixpl - 1);
_DPRINT(DBG_DEBUG, F("B "));
hmRadio.dumpBuf(DBG_DEBUG, NULL, &_payload->data[i][ixpl], _payload->len[i] - ixpl - 1);
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]);
crc = Hoymiles::crc16(&_payload->data[i][ixpl], _payload->len[i] - ixpl - 3, crc);
crcRcv = (_payload->data[i][_payload->len[i] - 3] << 8) | (_payload->data[i][_payload->len[i] - 2]);
} else
crc = Hoymiles::crc16(&_payload->data[i][ixpl], _payload->len[i] - ixpl, crc);
crc = Hoymiles::crc16(&_payload->data[i][ixpl], _payload->len[i] - ixpl - 1, crc);
} else {
// entry in range with len == zero
//--> request retransmit with this index
@ -681,7 +880,6 @@ static uint8_t checkPayload(invPayload_t *_payload) {
DPRINT(DBG_DEBUG, F(" checkPL -> CRC16 OK"));
return (uint8_t)0x00;
} else {
if ((_payload->maxPackId > 0) && (i >= _payload->maxPackId)) {
DPRINT(DBG_ERROR, F(" crc "));
@ -700,24 +898,28 @@ static uint8_t checkPayload(invPayload_t *_payload) {
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++) {
static bool out_uart_smac_resp(invPayload_t *_payload) {
Serial.print(F("\nrMAC:ch"));
if (_payload->rxChIdx < 10) Serial.print(F(" "));
if (_payload->rxChIdx < 10) Serial.print(F("0"));
Serial.print(_payload->rxChIdx);
Serial.print(F(": "));
Serial.print(F(":{"));
for (uint8_t i = 0; i < (_payload->maxPackId); i++) {
hmRadio.dumpBuf(NULL, &_payload->data[i][0], _payload->len[i]);
if (i != _payload->maxPackId - 1)
Serial.println(F(":"));
else
Serial.print(F("}:"));
} // end for()
Serial.print(F(":rt"));
Serial.print(F("rt"));
Serial.print(_payload->retransmits);
Serial.print(F(":"));
return true;
@ -726,19 +928,21 @@ static bool returnMACPackets(invPayload_t *_payload) {
/**
* output of pure user payload message
*/
static uint8_t returnPayload(invPayload_t *_payload, uint8_t *_user_payload, uint8_t _ulen) {
static uint8_t collect_and_return_userPayload(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;
volatile uint8_t _offs, _ixpl;
_offs = 0;
_ixpl = 0;
if (_payload->isMACPacket) {
_ixpl = 10; // index of position of payload start after pid
} // end if()
if (_payload->maxPackId < 1) return 0xff; //false, assume no valid data in payload
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);
_offs += (_payload->len[i] - _ixpl - 1);
} // end for()
_offs -= 2;
@ -750,7 +954,6 @@ static uint8_t returnPayload(invPayload_t *_payload, uint8_t *_user_payload, uin
return _offs;
} // end if returnPaylout
/**
* simple decoding of 2ch HM-inverter only
*/
@ -759,35 +962,43 @@ static void decodePayload(uint8_t _cmd, uint8_t *_user_payload, uint8_t _ulen, u
byteAssign_t _bp;
volatile uint8_t _x;
volatile uint8_t _end;
volatile uint8_t _dot_val;
volatile float _fval;
volatile uint8_t _tmp8 = 0xff;
//_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 !!!!
//snprintf_P(_strout, _strlen, PSTR("\nHM800/%04Xxxxxxxxx/"), _invtype);
//Serial.print(_strout);
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);
if (_tmp8 != _bp.ch) {
snprintf_P(_strout, _strlen, PSTR("\nHM800/%04Xxxxxxxxx/ch%02d "), _invtype, _bp.ch);
Serial.print(_strout);
//snprintf_P(_strout, _strlen, PSTR("ch%02d/"), _bp.ch);
//Serial.print(_strout);
}
_tmp8 = _bp.ch;
// 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
// strncpy_P(_strout, (PGM_P)pgm_read_word(&(PGM_fields[_bp.fieldId])), _strlen); // this read from PROGMEM array into RAM, works for A-Nano, not for esp8266
// snprintf_P(_strout, );
if (CMD_CALC != _bp.div && _bp.div != 0) {
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];
@ -796,16 +1007,17 @@ static void decodePayload(uint8_t _cmd, uint8_t *_user_payload, uint8_t _ulen, u
if (_bp.unitId == UNIT_NONE) {
Serial.print(_val);
Serial.print(F(" "));
continue;
}
Serial.print(_fval,2); //arduino nano does not sprintf.. float values, but print() does
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"));
// Serial.print(F("not yet"));
}
} // end for()
} else {
@ -814,9 +1026,6 @@ static void decodePayload(uint8_t _cmd, uint8_t *_user_payload, uint8_t _ulen, u
}
}
///////////////////////////////////////////////////////////
//
// Interrupt handling
@ -831,4 +1040,3 @@ static void handleIntr(void) {
}
#endif
///////////////////////////////////////////////////////////

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

@ -2,32 +2,53 @@
// 2022 mb for AHOY-UL (Arduino Nano, USB light IF)
// todo: make class
#include <Arduino.h>
#include <stdint.h>
#include <stdio.h>
#include <Arduino.h>
#include "config.h"
#include "dbg.h"
class SerialUtils {
public:
SerialUtils() {
// some member vars here
}
//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);
~SerialUtils() {}
char *mSerBuffer;
char *mTokens[MAX_SER_PARAM]; // this pointer array is used to tokenize the mSerBuffer, the data are kept in serBuffer
char *mStrOutBuf;
uint8_t *mByteBuffer;
#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;
void setup(uint8_t mMAX_IN_CHAR = MAX_SERBYTES, uint8_t mMAX_OUT_CHAR = MAX_STRING_LEN, uint8_t mNUM_CMD_PARA = MAX_SER_PARAM) {
// mMAX_IN_CHAR = MAX_IN_CHAR;
// mMAX_OUT_CHAR = MAX_OUT_CHAR;
// mNUM_CMD_PARA = NUM_CMD_PARA;
// allocate serial buffer space
mSerBuffer = new char[mMAX_IN_CHAR + 1]; // one extra byte for \0 termination
mByteBuffer = new uint8_t[mMAX_IN_CHAR / 2];
mStrOutBuf = new char[mMAX_OUT_CHAR + 1];
// char pointer array for string token
//mTokens = new char*[mNUM_CMD_PARA];
mSerBuffer[mMAX_IN_CHAR] = '\0';
mStrOutBuf[mMAX_OUT_CHAR] = '\0';
}
char *getStrOutBuf() {
return mStrOutBuf;
}
char *getInBuf() {
return mSerBuffer;
}
char **getParamsBuf() {
return mTokens;
}
/******************************************************************************************************
* serial read function with timeout
@ -44,7 +65,10 @@ static int serReadUntil(char _ch, char *_str, int _len, uint16_t _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);
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"));
}
@ -52,13 +76,11 @@ static int serReadUntil(char _ch, char *_str, int _len, uint16_t _TIMEOUT) {
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;
int _ir = 0;
int _iw = 0;
// _ir = 0;
// _iw = 0;
while (_str[_ir] && _ir < _len) {
if (_str[_ir] != _chrm) {
_str[_iw++] = _str[_ir];
@ -93,8 +115,6 @@ static void c_lower(char* _str, int _len) {
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
@ -120,114 +140,349 @@ static uint16_t x2b(char high, char low) {
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;
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;
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
* also 57600baud works with atmega 8Mhz as well
*/
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';
static int serBlockRead_ms(char *buf, size_t _lenMAX = MAX_SERBYTES, size_t _TIMEOUT_ms = 100) {
volatile size_t _rlen;
volatile size_t _len;
volatile size_t _backupTO = Serial.getTimeout();
Serial.setTimeout(_TIMEOUT_ms);
// fast block reading works for max buffer size e.g. 120bytes @ 57600baud
// if (Serial.available()) { //no need to wait for available
_rlen = Serial.readBytes(buf, _lenMAX);
//}
buf[_rlen] = '\0';
Serial.setTimeout(_backupTO);
return _len;
return _rlen;
} // 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
*
*
*
*
*
* eval the serial command smac<txchannel>:<data>:rc<rxchannel>:, e.g. smac: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
*
* !!! _serdata is changed by strtok(), it's unsafe to be used afterwards in another parsing function !!!
*/
// static boolean eval_uart_smac_request(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);
// 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_INFO, F("smac_txch "));
// _DPRINT(DBG_DEBUG, packet->rfch);
// p = strtok(NULL, ":");
// // next section
// DPRINT(DBG_INFO, 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_INFO, F("smac_rxch "));
// _DPRINT(DBG_DEBUG, *rxch);
// return true;
// } else {
// DPRINT(DBG_ERROR, F("smac ERROR"));
// return false;
// } // end if()
// } // end eval_uart_cmd()
/**
*
*
*
* takes numerical value of char* serdata with some checks and factor, define MIN and MAX value as response
*/
static uint32_t uart_eval_decimal_val(const __FlashStringHelper *_info, char *_serdata, uint8_t _len, uint16_t inMIN, uint32_t inMAX, int outFACTOR) {
volatile uint32_t _tmp32 = 0L;
DISABLE_IRQ;
if ((_len > 0) && (_len < 12)) {
//_serdata[_len] = '\0';
_tmp32 = atol(_serdata);
if (_tmp32 < inMIN) {
_tmp32 = inMIN;
} else if (_tmp32 > inMAX) {
_tmp32 = inMAX;
}
_tmp32 = outFACTOR * _tmp32;
}
RESTORE_IRQ;
if (_info != NULL) {
DPRINT(DBG_INFO, _info);
_DPRINT(DBG_INFO, _tmp32);
}
return _tmp32;
} // end eval_simple_cmd()
/**
*
*
*
* generic uart command parsing
* return number of char* with start address of each token, usually first is the cmd, _tok** only referes to addresses of mSerBuffer,
*/
static boolean eval_uart_smac_cmd(char *_serdata, uint8_t _slen, packet_t *packet, uint8_t *rxch) {
char *p = NULL;
char *m = NULL;
uint8_t read_uart_cmd_param(char **_tok, const uint8_t _numTok = MAX_SER_PARAM) {
char *_p = NULL;
char *_m = NULL;
volatile uint8_t _plen;
volatile uint8_t _rlen;
bool _res = false;
_rlen = serBlockRead_ms(mSerBuffer);
_rlen = c_remove(' ', mSerBuffer, _rlen);
c_replace('\n', '\0', mSerBuffer, _rlen);
c_replace('\r', '\0', mSerBuffer, _rlen);
c_lower(mSerBuffer, _rlen);
_p = strtok(mSerBuffer, ":");
// // check cmdstr header
// _m = strstr(_p, _matchstr);
// if (_m == NULL) {
// DPRINT(DBG_ERROR, F("wrong cmd "));
// return 0xff;
// }
volatile uint8_t _i = 0;
for (_i = 0; _i < _numTok; _i++) {
if (_p) {
_tok[_i] = _p;
DPRINT(DBG_DEBUG, F("para"));
_DPRINT(DBG_DEBUG, _i);
_DPRINT(DBG_DEBUG, F(" "));
_DPRINT(DBG_DEBUG, _tok[_i]);
} else {
break;
}
_p = strtok(NULL, ":");
} // end for()
return _i;
}// end read_uart_cmd_param()
if(_slen<10) {
DPRINT(DBG_ERROR, F("slen low")); _DPRINT(DBG_ERROR, _slen); _DPRINT(DBG_ERROR, F(" ERROR"));
/**
*
*
*
* parses the uart smac command parameter and writes into the mac-packet sending structure
*/
static boolean uart_cmd_smac_request_parser(char **_cmd_params, uint8_t _clen, packet_t *packet, uint8_t *rxch) {
//example cmd "smac:ch03:958180....:rc40:"
if (_clen < 2) {
DPRINT(DBG_ERROR, F("clen low"));
_DPRINT(DBG_ERROR, _clen);
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) {
//parse tx channel
char* m;
m = strstr(_cmd_params[1], "ch");
if(!m) {
DPRINT(DBG_ERROR, F("no chan"));
return false;
}
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));
packet->rfch = atoi(m); //sending channel for request
//copy the bytes-string into byte array
uint8_t _i = 0;
for (_i = 0; _i < strlen(p); _i += 2) {
packet->data[_i / 2] = (uint8_t)x2b(&p[_i]);
for (_i = 0; _i < strlen(_cmd_params[2]); _i += 2) {
packet->data[_i / 2] = (uint8_t)x2b(&_cmd_params[2][_i]);
} // end for()
packet->plen = _i / 2;
//eval rx channel input
p = strtok(NULL, ":");
m = strstr(p, "rc");
// parse rx channel input
*rxch = 0;
if (_clen == 3) {
m = strstr(_cmd_params[3], "rc");
if(m) {
m += 2;
*rxch = atoi(m);
} else {
*rxch = 0;
} // end if(m)
} // end if()
DPRINT(DBG_DEBUG, F("smac_rxch ")); _DPRINT(DBG_DEBUG, *rxch);
DPRINT(DBG_INFO, F("rxch "));
_DPRINT(DBG_DEBUG, *rxch);
return true;
}// end uart_cmd_smac_request_parser()
/**
*
*
* parsing values of adds inverter cmd, preprare to the list of registered inverters
*
*/
bool uart_cmd_add_inverter_parsing(char **_cmd_params, uint8_t _nump, uint8_t *_invIX_p, uint16_t *_invType_p, uint8_t *invID5) {
// volatile uint8_t _plen;
// volatile uint8_t _rlen;
bool _res = false;
char strtmp5[5] = {'0', '0', '0', '0', '\0'};
if (_nump != 0xFF && _nump > 1) {
DPRINT(DBG_INFO, _cmd_params[0]); // para1, used as index
DPRINT(DBG_INFO, _cmd_params[1]); // para2, the full inverter id of the plate
if (strlen(&(_cmd_params[0][0])) > 0) {
*_invIX_p = (uint8_t)atoi(_cmd_params[0]); //*invIX_p must point to value storage outside of function!!!!!
//_rlen = *_invIX_p;
DPRINT(DBG_INFO, F(" InvIx "));
_DPRINT(DBG_INFO, *_invIX_p);
delay(10);
if ((*_invIX_p) < MAX_NUM_INVERTERS ) {
if (strlen(&_cmd_params[1][0]) == 12) {
memcpy(strtmp5, &_cmd_params[1][0], 4); // copy first 4 char into strtmp5
*_invType_p = strtol(strtmp5, NULL, 16);
DPRINT(DBG_INFO, F(" InvType "));
if (DBG_INFO) {
sprintf(mStrOutBuf, "0x%04X", *_invType_p);
Serial.print(mStrOutBuf);
}
delay(10);
invID5[0] = 0x01; // lowest byte val is used for nrf24 pipe id of 5byte addr, not visible, but needed for nrf24 addr
for (volatile uint8_t i = 1; i < 5; i++) {
memcpy(strtmp5, &(_cmd_params[1][2 * i + 2]), 2); // copy two digits used for one byte
strtmp5[2] = '\0'; // str termination
invID5[i] = strtol(strtmp5, NULL, 16); // convert 2char to one byte
}
DPRINT(DBG_INFO, F(" InvID5 "));
if (DBG_INFO) {
print_bytes(&invID5[0], 5, "", true);
}
_res = true;
} else {
DPRINT(DBG_ERROR, F("smac ERROR"));
return false;
}//end if()
}//end eval_uart_cmd()
DPRINT(DBG_ERROR, F(" invID len fail"));
delay(10);
} // end if strlen(token[1])
} else {
DPRINT(DBG_ERROR, F(" invIx high"));
delay(10);
} // end if(invIX)
}
}
return _res;
} // end uart_inverter_add()
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;
/**
*
*
*/
static bool uart_cmd_del_inverter_parsing(char **_cmd_params, uint8_t _nump, uint8_t *_invIX_p) {
bool _res = false;
if (_nump != 0xFF && _nump > 0) {
DPRINT(DBG_INFO, _cmd_params[1]); // para1, index0 is cmd itself
if (strlen(_cmd_params[1]) > 0) {
*_invIX_p = (uint8_t)atoi(_cmd_params[1]); //*invIX_p must point to value storage outside of function, like mSerBuffer !!!!!
//_rlen = *_invIX_p;
if ((*_invIX_p) < MAX_NUM_INVERTERS) {
DPRINT(DBG_INFO, F(" InvIx del "));
_DPRINT(DBG_INFO, *_invIX_p);
_res = true;
}
} // end if(strlen(_cmd_params[1]))
} // end if(_plen...)
return _res;
} // end uart_inverter_del_check()
DISABLE_IRQ;
_tmp32 = inMIN * outFACTOR;
if ((_len > 1) && (_len < 12)) {
_serdata[_len] = '\0';
_tmp32 = atol(_serdata);
if ((_tmp32 <= inMIN) && (_tmp32 >= inMAX)) {
_tmp32 = inMIN;
////////////////////////////////////
// output functions
/**
*
*
*
*
* prints the byte array either as 3 digit uint8_t or 2digit hex string
*/
void print_bytes(uint8_t *_buf, uint8_t _blen, const char *_sep, bool _asHex) {
volatile uint8_t _i = 0;
if (_asHex) {
Serial.print(F("hex "));
}
_tmp32 = outFACTOR * _tmp32;
while (_i < _blen) {
if (_asHex) {
sprintf(mStrOutBuf, "%02X%s", _buf[_i], _sep);
Serial.print(mStrOutBuf);
} else {
sprintf(mStrOutBuf, "%03d%s", _buf[_i], _sep);
Serial.print(mStrOutBuf);
}
RESTORE_IRQ;
DPRINT(DBG_INFO, _info);
_DPRINT(DBG_INFO,_tmp32);
DPRINT(DBG_INFO, F("OK"));
return _tmp32;
} //end eval_simple_cmd()
_i++;
} // end while()
} // end print_bytes
private:
};

Loading…
Cancel
Save