diff --git a/.gitignore b/.gitignore index 9af41f40..4820a171 100644 --- a/.gitignore +++ b/.gitignore @@ -21,4 +21,12 @@ tools/esp8266/.vscode/extensions.json .DS_Store .vscode tools/esp8266/platformio-device-monitor-*.log -tools/esp8266/html/h/* \ No newline at end of file +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 diff --git a/tools/nano/AhoyUL/FHEM/ahoyUL.pm b/tools/nano/AhoyUL/FHEM/ahoyUL.pm new file mode 100644 index 00000000..1b475c8e --- /dev/null +++ b/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[[:]:<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; \ No newline at end of file diff --git a/tools/nano/AhoyUL/_libs2/com_handling.py b/tools/nano/AhoyUL/_libs2/com_handling.py new file mode 100644 index 00000000..ef781d46 --- /dev/null +++ b/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") + diff --git a/tools/nano/AhoyUL/_libs2/file_handling.py b/tools/nano/AhoyUL/_libs2/file_handling.py new file mode 100644 index 00000000..c841d044 --- /dev/null +++ b/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 + diff --git a/tools/nano/AhoyUL/hm_terminal.py b/tools/nano/AhoyUL/hm_terminal.py new file mode 100644 index 00000000..545995d7 --- /dev/null +++ b/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() diff --git a/tools/nano/AhoyUL/src/config.h b/tools/nano/AhoyUL/src/config.h index b258a102..17c696e1 100644 --- a/tools/nano/AhoyUL/src/config.h +++ b/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 diff --git a/tools/nano/AhoyUL/src/hmDefines.h b/tools/nano/AhoyUL/src/hmDefines.h index de139458..5337b9b5 100644 --- a/tools/nano/AhoyUL/src/hmDefines.h +++ b/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__*/ diff --git a/tools/nano/AhoyUL/src/hmRadio.h b/tools/nano/AhoyUL/src/hmRadio.h index a85b47c5..7407dcc4 100644 --- a/tools/nano/AhoyUL/src/hmRadio.h +++ b/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; diff --git a/tools/nano/AhoyUL/src/main.cpp b/tools/nano/AhoyUL/src/main.cpp index a86425b8..cfd448db 100644 --- a/tools/nano/AhoyUL/src/main.cpp +++ b/tools/nano/AhoyUL/src/main.cpp @@ -10,19 +10,15 @@ // 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 #include #include #include -#include -#include -#include - #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,10 +49,10 @@ 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 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); // sysConfig_t mSysConfig; @@ -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,94 +80,117 @@ 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 +#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 - + resetSystem(); // reset allocated mPayload buffer + loadDefaultConfig(&mConfig); // fills the mConfig parameter with values - // 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); // alternativly radio-id - //radio_id64 = (uint64_t)(swap_bytes((uint32_t)IV1_RADIO_ID)) << 8 | 0x01; + // radio_id64 = (uint64_t)(swap_bytes((uint32_t)IV1_RADIO_ID)) << 8 | 0x01; // todo: load Inverter decoder depending on InvID + m_inv_ix = 0; //first inverter index + + //global var for all inverter + iv_powerLimit[1] = AbsolutNonPersistent; + iv_powerLimit[0] = 0xFFFF; //unlimited } // 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; - -///////////////////////////////////////////////////////////////////////// -/////////////////////////////////////////////////////////////////////// +/** + * + * + * 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) { timer1_millis = millis(); @@ -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[[[:]:]:<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, , 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)) { + // e.g. "smac:ch03:958180....:rc40:" -- ch03 for Tx channel, , -- 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; + 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,102 +319,194 @@ 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; - hmRadio.sendPacket_raw(&mPayload[0].invId[0], &rfTX_packet, rxch); // 2022-10-30: byte array transfer working - mPayload[inv_ix].requested = true; + 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[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)'z': { - // todo: register new Inverter ID in payload_t array via "z:<5bytes>::", save to eeprom - // todo: query via z1? for first inverter - 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 + } + + } 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")); - hmRadio.print_radio_details(); + 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 t - } // end switch-case - } // end if serial... + } //end case '?' + + } // end switch-case + } // end if serial... + + // automode RF-Tx-trigger if (automode) { - //slow down the sending if no Rx for long time + // 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) ) { + 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]); - } - payload_used[inv_ix] = true; - tmp8 = returnPayload(&mPayload[inv_ix], &user_payload[0], USER_PAYLOAD_MAXLEN); - user_pl_ts = mPayload[inv_ix].ts; + if (mPayload[m_inv_ix].isMACPacket && showMAC) { + out_uart_smac_resp(&mPayload[m_inv_ix]); + } + 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,20 +759,19 @@ 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 + * 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; + 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]; - _payload->receive = _res; // indicates that a packet was received at least once per Request iteration + _payload->receive = _res; // indicates that a packet was received at least once per Request iteration // handle last packet additionally if ((_pid & 0x80) == 0x80) { @@ -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 ")); @@ -691,8 +889,8 @@ static uint8_t checkPayload(invPayload_t *_payload) { // 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; + _payload->receive = false; // stop further eval + return (uint8_t)0xFF; } } // end if-else } // end if-else @@ -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 + * output of sorted packets with MAC-header, all info included */ -static bool returnMACPackets(invPayload_t *_payload) { +static bool out_uart_smac_resp(invPayload_t *_payload) { + Serial.print(F("\nrMAC:ch")); + if (_payload->rxChIdx < 10) Serial.print(F("0")); + Serial.print(_payload->rxChIdx); + Serial.print(F(":{")); 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]); + 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,44 +954,51 @@ static uint8_t returnPayload(invPayload_t *_payload, uint8_t *_user_payload, uin 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) { +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; - + 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); + snprintf_P(_strout, _strlen, PSTR("\ndata age: %d sec"), (millis() - _ts) / 1000); 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; + 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; - 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(": ")); + // 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); // 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(": ")); + 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(units[_bp.unitId]); + 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,14 +1026,11 @@ static void decodePayload(uint8_t _cmd, uint8_t *_user_payload, uint8_t _ulen, u } } - - - /////////////////////////////////////////////////////////// // // Interrupt handling // -#if defined(ESP8266) || defined(ESP32) +#if defined(ESP8266) || defined(ESP32) IRAM_ATTR void handleIntr(void) { hmRadio.handleIntr(); } @@ -831,4 +1040,3 @@ static void handleIntr(void) { } #endif /////////////////////////////////////////////////////////// - diff --git a/tools/nano/AhoyUL/src/utils_serial.h b/tools/nano/AhoyUL/src/utils_serial.h index 32fa62de..afc05a99 100644 --- a/tools/nano/AhoyUL/src/utils_serial.h +++ b/tools/nano/AhoyUL/src/utils_serial.h @@ -1,233 +1,488 @@ -//2022 mb for AHOY-UL (Arduino Nano, USB light IF) -//todo: make class +// 2022 mb for AHOY-UL (Arduino Nano, USB light IF) +// todo: make class +#include #include #include -#include -#include "dbg.h" +#include "config.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")); +class SerialUtils { + public: + SerialUtils() { + // some member vars here } - Serial.setTimeout(_backupTO); - return thislen; -}//end serReadUntil() + ~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; -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++; + 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'; } - _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++; + + char *getStrOutBuf() { + return mStrOutBuf; } - 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++; + + char *getInBuf() { + return mSerBuffer; } - 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::rc:, 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; + + char **getParamsBuf() { + return mTokens; } - _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) { + /****************************************************************************************************** + * 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) { + int _ir = 0; + int _iw = 0; + // _ir = 0; + // _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 + * also 57600baud works with atmega 8Mhz as well + */ + 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 _rlen; + } // end serReadBytes_ms() + + /** + * + * + * + * + * + * eval the serial command smac::rc:, 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, + */ + 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() + + /** + * + * + * + * 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; + } + + //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"); - if (m) { - m += 2; - *rxch = atoi(m); - } else { - *rxch = 0; - }//end if() - DPRINT(DBG_DEBUG, F("smac_rxch ")); _DPRINT(DBG_DEBUG, *rxch); + + // parse rx channel input + *rxch = 0; + if (_clen == 3) { + m = strstr(_cmd_params[3], "rc"); + if(m) { + m += 2; + *rxch = atoi(m); + } // end if(m) + } // end if() + + DPRINT(DBG_INFO, F("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; + }// 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(" invID len fail")); + delay(10); + } // end if strlen(token[1]) + + } else { + DPRINT(DBG_ERROR, F(" invIx high")); + delay(10); + } // end if(invIX) + } } - _tmp32 = outFACTOR * _tmp32; - } - RESTORE_IRQ; - DPRINT(DBG_INFO, _info); - _DPRINT(DBG_INFO,_tmp32); - DPRINT(DBG_INFO, F("OK")); - return _tmp32; -} //end eval_simple_cmd() + return _res; + } // end uart_inverter_add() + + /** + * + * + */ + 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() + + //////////////////////////////////// + // 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 ")); + } + + 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); + } + _i++; + } // end while() + } // end print_bytes + + private: +};