Marco Zonca
Published © GPL3+

Arduino MCU network (up to 10 together for a bigger project)

Keyboard and display for an interactivity console, a DOS for SD, an updater, a sensor section. A project I am still working on...

AdvancedWork in progress208
Arduino MCU network (up to 10 together for a bigger project)

Things used in this project

Story

Read more

Schematics

Fritzing schematic diagram

Code

CLOCK Arduino code

Arduino
/* Arduino Net-P (i2c processors network) by Zonca Marco 2020
 * 'netMyNAME' ("CLOCK   ") max 8 char, 'netMyID' (p9) max 2 char, 
 *  address='netMyAddress' (0x16) max 0xFF, 'netMyPx' (9)
 * 
 * implemented commands (* = to do): HELP | ?, dht, gettemp, gettime, settime, red, green
 *
 */
 
#include <Wire.h>
#include <ds3231.h>  // RTC real time clock
#include <dht.h>

const int netMyPx = 9; // netMyPx 0-9
const int netMyAddress = 0x16;  // i2c address 0x16=HEX 22=DEC
const char *netMyID = "p9";  // netMyID
const char *netMyNAME = "CLOCK   ";  // netMyNAME
const int GreenLedPin=8;
const int RedLedPin=9;
const int DHTPin=14;
const int ResetPin=17;  // A3 put low for reset

char _CR[2]="";  // CR
char _BEL[2]="";  // BEL
char _ENQ[2]="";  // ENQ
char _ACK[2]="";  // ACK
char _SPACE[2]="";  // SPACE
char _MYADDR[2]="";  // netMyAddress

char lineString[65]="";
char recCommand[65]="";
byte recAddress=0;
byte recType=0;
char data[2]="";
bool isNetDataWaiting=false;
bool isKnownCommand = false;
bool isRedOn=false;
bool isGreenOn=false;
bool isBusy=false;

struct ts t; // RTC
dht DHT;  // HG + Temp sensor

void setup() {
  //Serial.begin(38400);
  Wire.begin(netMyAddress);
  Wire.onReceive(receiveEvent);  // i2c event
  Wire.onRequest(netWhoIsEvent);  // i2c whois
  _CR[0] = 13;  // CR
  _BEL[0] = 7;  // BEL
  _ENQ[0] = 5;  // ENQ
  _ACK[0] = 6;  // ACK
  _SPACE[0] = 32;  // SPACE
  _MYADDR[0] = netMyAddress;
  pinMode(GreenLedPin, OUTPUT);
  pinMode(RedLedPin, OUTPUT);
  digitalWrite(RedLedPin,LOW);
  digitalWrite(GreenLedPin,LOW);
  DS3231_init(DS3231_INTCN);  // RTC
  DHT.read11(DHTPin);  // DHT
}  // end setup()

void loop() {
  if (isNetDataWaiting == true) {
    execNetCommand();
  }
}  // end loop()

void execNetCommand() {  // executes received command
  isBusy=true;
  char park[65]="";
  char command[65]="";
  if (recType == _BEL[0]) {  // char(7) = BEL command type
    isKnownCommand=false;
    if (s_compare(recCommand,"")==0) {  // empty command just do nothing
      isKnownCommand=true;
      prompt();
    }
    s_substring(park,sizeof(park),recCommand,sizeof(recCommand),0,3);  // red
    if (s_compare(park,"red ") == 0) {
      s_substring(command,sizeof(command),recCommand,sizeof(recCommand),4,strlen(recCommand)-1);
      if (strcmp(command, "on") == 0 || strcmp(command, "off") == 0 || strcmp(command, "?") == 0) {
        isKnownCommand=true;
        ledred(command);
      }
    }
    s_substring(park,sizeof(park),recCommand,sizeof(recCommand),0,5);  // green
    if (s_compare(park,"green ") == 0) {
      s_substring(command,sizeof(command),recCommand,sizeof(recCommand),6,strlen(recCommand)-1);
      if (strcmp(command, "on") == 0 || strcmp(command, "off") == 0 || strcmp(command, "?") == 0) {
        isKnownCommand=true;
        ledgreen(command);
      }
    }
    s_substring(park,sizeof(park),recCommand,sizeof(recCommand),0,4);  // reset
    if (s_compare(park,"reset") == 0) {
      isKnownCommand=true;
      reset();
    }
    s_substring(park,sizeof(park),recCommand,sizeof(recCommand),0,2);  // dht
    if (s_compare(park,"dht") == 0) {
      isKnownCommand=true;
      dht();
    }
    s_substring(park,sizeof(park),recCommand,sizeof(recCommand),0,6);  // gettemp
    if (s_compare(park,"gettemp") == 0) {
      isKnownCommand=true;
      gettemp();
    }
    s_substring(park,sizeof(park),recCommand,sizeof(recCommand),0,6);  // gettime
    if (s_compare(park,"gettime") == 0) {
      isKnownCommand=true;
      gettime();
    }
    s_substring(park,sizeof(park),recCommand,sizeof(recCommand),0,7);  // settime
    if (s_compare(park,"settime ") == 0) {
      s_substring(command,sizeof(command),recCommand,sizeof(recCommand),8,strlen(recCommand)-1);
      if (strlen(command) == 19) {
        isKnownCommand=true;
        settime(command);
      }
    }
    s_substring(park,sizeof(park),recCommand,sizeof(recCommand),0,0);  // ?
    if (s_compare(park,"?") == 0) {
      isKnownCommand=true;
      help();
    }
    if (isKnownCommand == false) {
      s_assign(lineString,"unknown cmd",sizeof(lineString));
      dispLine();
      prompt();
    }
  }  // endif recType
  s_clear(recCommand,sizeof(recCommand));
  recType=0;
  recAddress=0;
  isNetDataWaiting=false;
  isBusy=false;
}  // end execNetCommand()

void reset() {  // ------------------------------------------------------------ RESET
  analogWrite(ResetPin,LOW);
}

void help() {  // ---------------------------------------------- HELP
  s_assign(lineString, "red <on off ?>", sizeof(lineString));
  dispLine();
  s_assign(lineString, "green <on off ?>", sizeof(lineString));
  dispLine();
  s_assign(lineString, "dht", sizeof(lineString));
  dispLine();
  s_assign(lineString, "gettemp", sizeof(lineString));
  dispLine();
  s_assign(lineString, "gettime", sizeof(lineString));
  dispLine();
  s_assign(lineString, "settime <DD/MM/YYYY HH:MM:SS>", sizeof(lineString));
  dispLine();
  s_assign(lineString, "reset", sizeof(lineString));
  dispLine();
  sprintf(lineString,"(free=%d)",s_freemem());
  dispLine();
  prompt();
}  // end help()

void ledred(const char *_s) {  // ---------------------------------------------- RED
  if (strcmp(_s, "on")==0) {
    digitalWrite(RedLedPin,HIGH);     
    isRedOn=true;
  }
  if (strcmp(_s, "off")==0) {
    digitalWrite(RedLedPin,LOW);
    isRedOn=false;
  }
  if (isRedOn) {
    sprintf(lineString, "%s","ON");
  } else {
    sprintf(lineString, "%s","OFF");
  }
  dispLine();
  prompt();
}  // end ledred()

void ledgreen(const char *_s) {  // ---------------------------------------------- GREEN
  if (strcmp(_s, "on")==0) {
    digitalWrite(GreenLedPin,HIGH);     
    isGreenOn=true;
  }
  if (strcmp(_s, "off")==0) {
    digitalWrite(GreenLedPin,LOW);
    isGreenOn=false;
  }
  if (isGreenOn) {
    sprintf(lineString, "%s","ON");
  } else {
    sprintf(lineString, "%s","OFF");
  }
  dispLine();
  prompt();
}  // end ledgreen()

void dht() {  // ---------------------------------------------- DHT
  float temp=0;
  int iintt=0;
  int idect=0;
  int iinth=0;
  int idech=0;
  DHT.read11(DHTPin);  // DHT
  temp = DHT.temperature; // read DHT temp C
  iintt=temp;
  idect=float((temp-iintt)*100);
  temp = DHT.humidity; // read DHT humidity
  iinth=temp;
  idech=float((temp-iinth)*100);
  sprintf (lineString, "%d.%dC %d.%d", iintt, idect, iinth, idech);
  s_concat(lineString,"%",sizeof(lineString));
  dispLine();
  prompt();
}  // end gettemp()

void gettemp() {  // ---------------------------------------------- GETTEMP
  float temp=0;
  int iint=0;
  int idec=0;
  temp = DS3231_get_treg(); // read RTC temp C
  iint=temp;
  idec=float((temp-iint)*100);
  sprintf (lineString, "%d.%dC", iint, idec);
  dispLine();
  prompt();
}  // end gettemp()

void gettime() {  // ---------------------------------------------- GETTIME
  DS3231_get(&t);  // read RTC date & time
  sprintf(lineString, "%02d/%02d/%04d %02d:%02d:%02d", t.mday, t.mon, t.year, t.hour, t.min, t.sec);
  dispLine();
  prompt();
}  // end gettime()

void settime(const char* _s) {  // ---------------------------------------------- SETTIME
  char park[5]="";
  s_substring(park, sizeof(park), _s, strlen(_s), 0,1);
  t.mday=atoi(park);
  s_substring(park, sizeof(park), _s, strlen(_s), 3,4);
  t.mon=atoi(park);
  s_substring(park, sizeof(park), _s, strlen(_s), 6,9);
  t.year=atoi(park);
  s_substring(park, sizeof(park), _s, strlen(_s), 11,12);
  t.hour=atoi(park);
  s_substring(park, sizeof(park), _s, strlen(_s), 14,15);
  t.min=atoi(park);
  s_substring(park, sizeof(park), _s, strlen(_s), 17,18);
  t.sec=atoi(park);
  DS3231_set(t);  // write RTC
  gettime();  // answer with new time
}  // end settime()


void prompt() {  // prompt, ready for commands
  sprintf(lineString,"%d>\r",netMyPx);
  dispLine();
}  // end prompt()

void dispLine() {  // print information
  s_concat(lineString, _CR, sizeof(lineString));
  txBackToSender();  // tx
  s_clear(lineString, sizeof(lineString));
  delay(100);
}  // end dispLine()

void receiveEvent(int howMany) {  // i2c event incoming requests
  if (isBusy==false) {
    int counter=0;
    s_clear(recCommand, sizeof(recCommand));  // sender command
    recAddress=0;  // sender address
    recType=0;  // sender cmd type
    if (howMany == 0) {  // ignores empty requests
      isNetDataWaiting=false;
    } else {
      while (Wire.available()) {
        data[0]=Wire.read();
        if (counter==0) { recType = data[0]; } // 1st char = cmd type
        if (counter==1) { recAddress = data[0]; } // 2nd char = sender address
        if (counter >1) { // other chars = command
          s_concat(recCommand, data, sizeof(recCommand)); 
        }
        counter++;
      }
      isNetDataWaiting=true;
    }
  }//endisBusy
}  // end receiveEvent()

void netWhoIsEvent() {  // i2c event: 11 bytes = 1=char(5) ENQ, 2-9=netMyNAME 10=netMyPx 11=netMyAddress
  if (isBusy==false) {
    char ccommand[65]="";
    char park[3]="";
    s_assign(ccommand, _ENQ, sizeof(ccommand));  // ENQ
    s_concat(ccommand, netMyNAME, sizeof(ccommand));  // netMyNAME
    s_concat(ccommand, itoa(netMyPx,park,10), sizeof(ccommand));  // netMyPx
    s_concat(ccommand, _MYADDR, sizeof(ccommand));  // netMyAddress
    Wire.write(ccommand);
  }
}  // end netWhoIsEvent()

void txBackToSender() {  // tx back to sender
  char ccommand[65]="";
  s_assign(ccommand, _ACK, sizeof(ccommand));  // ACK=answer data
  s_concat(ccommand, _MYADDR, sizeof(ccommand));  // netMyAddress
  s_concat(ccommand, lineString, sizeof(ccommand));  // data
  Wire.beginTransmission (recAddress);
  Wire.write (ccommand);
  Wire.endTransmission (true);
}  // end txBackToSender()

//------------------------------------------------------------------------- CLEAR
void s_clear(char *dest_source_string, const int dest_sizeof) {  // fills-up with NUL=chr(0)
  memset(dest_source_string, 0, dest_sizeof);
}  // end s_clear()

//------------------------------------------------------------------------- ASSIGN
bool s_assign(char *dest_string, const char *source_string, const int dest_sizeof) {  // copies source to dest
  int _LenS=strlen(source_string);  // how many bytes
  if (_LenS > (dest_sizeof - 1)) {
    return true;  // err 1
  } else {
    strcpy(dest_string, source_string);
    return false;  // ok 0
  }
}  // end s_assign()

//------------------------------------------------------------------------- SUBSTRING
bool s_substring(char *dest_string, const int dest_sizeof, const char *source_string, 
 const int source_sizeof, const int source_from, const int source_to) {  // copies source(from, to) to dest
  if ((source_from < 0) || (source_to < source_from) || ((source_to - source_from + 1) > (dest_sizeof - 1)) 
    || (source_to >= (source_sizeof-1)) || ((source_to - source_from + 1) > (strlen(source_string)))) {
    dest_string[0]=0;  // NUL
    return true;  // err 1
  } else {
    int _Count=0;
    for (int i=source_from;i<(source_to+1);i++) {
      dest_string[_Count]=source_string[i];
      _Count++;
    }
    dest_string[_Count]=0;  // ends with NUL
    return false;  // ok 0
  }
}  // end s_substring()

//------------------------------------------------------------------------- CONCAT
bool s_concat(char *dest_string, const char *source_string, const int dest_sizeof) {  // append source to dest
  int _LenS=strlen(source_string);  // how many bytes source
  int _LenD=strlen(dest_string);  // how many bytes dest
  if ((_LenS + _LenD) > (dest_sizeof - 1)) {
    return true;  // err 1
  } else {
    strcat(dest_string, source_string);
    return false;  // ok 0
  }
}  // end s_concat()

//------------------------------------------------------------------------- COMPARE
bool s_compare(const char *dest_string, const char *source_string) {  // compares source with dest
  int _LenS=strlen(source_string);  // how many bytes source
  int _LenD=strlen(dest_string);  // how many bytes dest
  if (_LenS != _LenD) {  // different length
    return true;  // are different 1
  } else {
    if (strcmp(dest_string, source_string) == 0) {
      return false;  // are the same 0
    } else {
      return true;  // are different 1
    }    
  }
}  // end s_compare()

//------------------------------------------------------------------------- FREEMEM
int s_freemem() {
  extern int __heap_start,*__brkval;
  int v;
  return (int)&v - (__brkval == 0  ? (int)&__heap_start : (int) __brkval); 
}  // end s_freemem()

UPDATER Arduino code

Arduino
 
/* Arduino Net-P (i2c processors network) by Zonca Marco 2020
 * 'netMyNAME' ("UPDATER ") max 8 char, 'netMyID' (p8) max 2 char, 
 *  address='netMyAddress' (0x15) max 0xFF, 'netMyPx' (8)
 * 
 * implemented commands (* = to do): ?, reset
 *
 */
 
#include <Wire.h>

const int netMyPx = 8; // netMyPx 0-9
const int netMyAddress = 0x15;  // i2c address 0x15=HEX 21=DEC
const char *netMyID = "p8";  // netMyID
const char *netMyNAME = "UPDATER ";  // netMyNAME
const int ResetPin=17;  // A3 put low for reset

char _CR[2]="";  // CR
char _BEL[2]="";  // BEL
char _ENQ[2]="";  // ENQ
char _ACK[2]="";  // ACK
char _SPACE[2]="";  // SPACE
char _MYADDR[2]="";  // netMyAddress

char lineString[65]="";
char recCommand[65]="";
byte recAddress=0;
byte recType=0;
char data[2]="";
bool isNetDataWaiting=false;
bool isKnownCommand = false;
bool isBusy=false;


void setup() {
  //Serial.begin(38400);
  Wire.begin(netMyAddress);
  Wire.onReceive(receiveEvent);  // i2c event
  Wire.onRequest(netWhoIsEvent);  // i2c whois
  _CR[0] = 13;  // CR
  _BEL[0] = 7;  // BEL
  _ENQ[0] = 5;  // ENQ
  _ACK[0] = 6;  // ACK
  _SPACE[0] = 32;  // SPACE
  _MYADDR[0] = netMyAddress;
}  // end setup()


void loop() {
  if (isNetDataWaiting == true) {
    execNetCommand();
  }
}  // end loop()


void execNetCommand() {  // executes received command
  isBusy=true;
  char park[65]="";
  if (recType == _BEL[0]) {  // char(7) = BEL command type
    isKnownCommand=false;
    if (s_compare(recCommand,"")==0) {  // empty command just do nothing
      isKnownCommand=true;
      prompt();
    }
    s_substring(park,sizeof(park),recCommand,sizeof(recCommand),0,4);  // reset
    if (s_compare(park,"reset") == 0) {
      isKnownCommand=true;
      reset();
    }
    s_substring(park,sizeof(park),recCommand,sizeof(recCommand),0,0);  // ?
    if (s_compare(park,"?") == 0) {
      isKnownCommand=true;
      sdhelp();
    }
    if (isKnownCommand == false) {
      s_assign(lineString,"unknown command",sizeof(lineString));
      dispLine();
      prompt();
    }
  }  // endif recType
  s_clear(recCommand,sizeof(recCommand));
  recType=0;
  recAddress=0;
  isNetDataWaiting=false;
  isBusy=false;
}  // end execNetCommand()


void reset() {  // ------------------------------------------------------------ RESET
  analogWrite(ResetPin,LOW);
}


void sdhelp() {  // ---------------------------------------------- HELP
  s_assign(lineString, "reset", sizeof(lineString));
  dispLine();
  sprintf(lineString,"(free=%d)",s_freemem());
  dispLine();
  prompt();
}  // end help()


void prompt() {  // prompt, ready for commands
  sprintf(lineString,"%d>",netMyPx);
  dispLine();
}  // end prompt()


void dispLine() {  // print information
  s_concat(lineString, _CR, sizeof(lineString));
  txBackToSender();  // tx
  s_clear(lineString, sizeof(lineString));
  delay(100);
}  // end dispLine()


void receiveEvent(int howMany) {  // i2c event incoming requests
  if (isBusy==false) {
    int counter=0;
    s_clear(recCommand, sizeof(recCommand));  // sender command
    recAddress=0;  // sender address
    recType=0;  // sender cmd type
    if (howMany == 0) {  // ignores empty requests
      isNetDataWaiting=false;
    } else {
      while (Wire.available()) {
        data[0]=Wire.read();
        if (counter==0) { recType = data[0]; } // 1st char = cmd type
        if (counter==1) { recAddress = data[0]; } // 2nd char = sender address
        if (counter >1) { // other chars = command
          s_concat(recCommand, data, sizeof(recCommand)); 
        }
        counter++;
      }
      isNetDataWaiting=true;
    }
  }//endisBusy
}  // end receiveEvent()


void netWhoIsEvent() {  // i2c event: 11 bytes = 1=char(5) ENQ, 2-9=netMyNAME 10=netMyPx 11=netMyAddress
  if (isBusy==false) {
    char ccommand[65]="";
    char park[3]="";
    s_concat(ccommand, _ENQ, sizeof(ccommand));  // ENQ
    s_concat(ccommand, netMyNAME, sizeof(ccommand));  // netMyNAME
    s_concat(ccommand, itoa(netMyPx,park,10), sizeof(ccommand));  // netMyPx
    s_concat(ccommand, _MYADDR, sizeof(ccommand));  // netMyAddress
    Wire.write(ccommand);
  }
}  // end netWhoIsEvent()


void txBackToSender() {  // tx back to sender
  char ccommand[65]="";
  s_assign(ccommand, _ACK, sizeof(ccommand));  // ACK=answer data
  s_concat(ccommand, _MYADDR, sizeof(ccommand));  // netMyAddress
  s_concat(ccommand, lineString, sizeof(ccommand));  // data
  Wire.beginTransmission (recAddress);
  Wire.write (ccommand);
  Wire.endTransmission (true);
}  // end txBackToSender()


//------------------------------------------------------------------------- CLEAR
void s_clear(char *dest_source_string, const int dest_sizeof) {  // fills-up with NUL=chr(0)
  memset(dest_source_string, 0, dest_sizeof);
}  // end s_clear()

//------------------------------------------------------------------------- ASSIGN
bool s_assign(char *dest_string, const char *source_string, const int dest_sizeof) {  // copies source to dest
  int _LenS=strlen(source_string);  // how many bytes
  if (_LenS > (dest_sizeof - 1)) {
    return true;  // err 1
  } else {
    strcpy(dest_string, source_string);
    return false;  // ok 0
  }
}  // end s_assign()

//------------------------------------------------------------------------- SUBSTRING
bool s_substring(char *dest_string, const int dest_sizeof, const char *source_string, 
 const int source_sizeof, const int source_from, const int source_to) {  // copies source(from, to) to dest
  if ((source_from < 0) || (source_to < source_from) || ((source_to - source_from + 1) > (dest_sizeof - 1)) 
    || (source_to >= (source_sizeof-1)) || ((source_to - source_from + 1) > (strlen(source_string)))) {
    dest_string[0]=0;  // NUL
    return true;  // err 1
  } else {
    int _Count=0;
    for (int i=source_from;i<(source_to+1);i++) {
      dest_string[_Count]=source_string[i];
      _Count++;
    }
    dest_string[_Count]=0;  // ends with NUL
    return false;  // ok 0
  }
}  // end s_substring()

//------------------------------------------------------------------------- CONCAT
bool s_concat(char *dest_string, const char *source_string, const int dest_sizeof) {  // append source to dest
  int _LenS=strlen(source_string);  // how many bytes source
  int _LenD=strlen(dest_string);  // how many bytes dest
  if ((_LenS + _LenD) > (dest_sizeof - 1)) {
    return true;  // err 1
  } else {
    strcat(dest_string, source_string);
    return false;  // ok 0
  }
}  // end s_concat()

//------------------------------------------------------------------------- COMPARE
bool s_compare(const char *dest_string, const char *source_string) {  // compares source with dest
  int _LenS=strlen(source_string);  // how many bytes source
  int _LenD=strlen(dest_string);  // how many bytes dest
  if (_LenS != _LenD) {  // different length
    return true;  // are different 1
  } else {
    if (strcmp(dest_string, source_string) == 0) {
      return false;  // are the same 0
    } else {
      return true;  // are different 1
    }    
  }
}  // end s_compare()


//------------------------------------------------------------------------- FREEMEM
int s_freemem() {
  extern int __heap_start,*__brkval;
  int v;
  return (int)&v - (__brkval == 0  ? (int)&__heap_start : (int) __brkval); 
}  // end s_freemem()

CONSOLE Arduino code

Arduino
/* Arduino Net-P (i2c processors network) by Zonca Marco 2020
 * 'netMyNAME' ("CONSOLE ") max 8 char, 'netMyID' (p0) max p9, address='netMyAddress' max 0xFF, 'netMyPx' (0)
 * 
 * implemented commands (* = to do): scan, ckpower, ?, <pxname> <cmd>, reset
 * 
 * scan:      scan [inquiry i2c bus,
 *            i.e. scan
 *            returns a list of active addresses on the bus in form "DDD (0xEE)"...
 *            ends with "'n' found"]
 * ckpower:   ckpower [inquiry power situation
 *            i.e. ckpower
 *            returns "v3=3.3 v5=5.0 vraw=7.0-12.0"]
 * ?:         ? [ask for the list of commands
 *            i.e. ?
 *            returns a list of available commands on board]
 * reset:     reset [force the MPU to reset
 *            i.e. reset]
 *            
 * <pxname> <cmd>: [sends <cmd> to the processor <pxname>           
 *            i.e. SD dir /
 *            returns result of <cmd> from processor <pxname>]
 *            
 */
 
#include <Adafruit_GFX_AS.h>     // Core graphics library customized
#include <Adafruit_ILI9341_AS.h> // Hardware-specific library customized
#include <SPI.h>
#include <SoftwareSerial.h>
#include <Wire.h>
#include <NewTone.h>
#include <PS2Keyboard.h>

// tft pins used for the MINI PRO, we must use hardware SPI
#define _sclk 13
#define _miso 12 // Not used
#define _mosi 11
#define _cs 10
#define _rst 8
#define _dc  9

// Must use hardware SPI for speed
Adafruit_ILI9341_AS tft = Adafruit_ILI9341_AS(_cs, _dc, _rst);

// The scrolling area must be a integral multiple of TEXT_HEIGHT
#define TEXT_HEIGHT 16 // Height of text to be printed and scrolled
#define BOT_FIXED_AREA 0 // Number of lines in bottom fixed area (lines counted from bottom of screen)
#define TOP_FIXED_AREA 16 // Number of lines in top fixed area (lines counted from top of screen)

#define btTX 4  // to "TX-O" pin of bluetooth terminal
#define btRX 5  // to "RX-I" pin of bluetooth terminal
#define btSPEED 38400  // communication speed both serial/keyboard and bluetooth

SoftwareSerial bluetooth(btTX, btRX);  // bluetooth terminal TX+RX
PS2Keyboard keyboard;

// The initial y coordinate of the top of the scrolling area
uint16_t yStart = TOP_FIXED_AREA;
// yArea must be a integral multiple of TEXT_HEIGHT
uint16_t yArea = 320-TOP_FIXED_AREA-BOT_FIXED_AREA;
// The initial y coordinate of the top of the bottom text line
uint16_t yDraw = 320 - BOT_FIXED_AREA - TEXT_HEIGHT;

// Keep track of the drawing x coordinate
uint16_t xPos = 0;
// For the byte we read from the serial port and display
char data[2] = "";

// We have to blank the top line each time the display is scrolled, but this takes up to 13 milliseconds
// for a full width line, meanwhile the serial buffer may be filling... and overflowing
// We can speed up scrolling of short text lines by just blanking the character we drew
// We keep all the strings pixel lengths to optimise the speed of the top line blanking
int blank[19]; 

// other staff
char lineString[65]="";
char command[65]="";
char recCommand[65]="";
byte recAddress=0;
byte recType=0;
char park[65]="";
char netRegisterNames[10][10]={};

bool displayAlsoOnSerial = false;
bool isKnownCommand = false;
bool isNetDataWaiting = false;
bool isBusy=false;
byte netRegisterAddrs[10] = {};
unsigned long prevSCANBUSmillis=0;

const int buzzerPin = 7;
const int vin3Pin = 14;
const int vin5Pin = 15;
const int vin12Pin = 16;
const int netMyPx = 0; // netMyPx 0-9
const int netMyAddress = 0x0a;  // i2c address
const char *netMyID = "p0";  // netMyID
const char *netMyNAME = "CONSOLE ";  // netMyNAME
const int ps2DataPin = 2;
const int ps2IRQpin =  3;
const int SCANBUSinterval = 30000;
const int ResetPin=17;  // A3 put low for reset

char _CR[2]="";  // CR
char _BEL[2]="";  // BEL
char _ENQ[2]="";  // ENQ
char _ACK[2]="";  // ACK
char _SPACE[2]="";  // SPACE
char _MYADDR[2]="";  // netMyAddress

void setup() {
  //Serial.begin(btSPEED);  // serial/keyboard RX only
  bluetooth.begin(btSPEED);  // bluetooth terminal TX/RX
  keyboard.begin(ps2DataPin, ps2IRQpin);
  Wire.begin(netMyAddress);
  Wire.onReceive(receiveEvent);  // i2c event

  _CR[0] = 13;  // CR
  _BEL[0] = 7;  // BEL
  _ENQ[0] = 5;  // ENQ
  _ACK[0] = 6;  // ACK
  _SPACE[0] = 32;  // SPACE
  _MYADDR[0] = netMyAddress;

  netRegisterAddrs[0]=netMyAddress;
  s_trimall(park,netMyNAME,sizeof(park));
  s_assign(netRegisterNames[0],park,sizeof(netRegisterNames[0]));  // 0 it is me

  // Setup the TFT display
  tft.init();
  tft.setRotation(0);
  tft.fillScreen(ILI9341_BLACK);

  // Setup scroll area
  setupScrollArea(TOP_FIXED_AREA, BOT_FIXED_AREA);

  // Top banner
  tft.setTextColor(ILI9341_BLACK, ILI9341_GREEN);
  tft.fillRect(0,0,240,16, ILI9341_GREEN);
  s_assign(park," Net-P Console ",sizeof(park));
  tft.drawCentreString(park,120,0,2);

  // Change colour for scrolling zone
  tft.setTextColor(ILI9341_WHITE, ILI9341_BLACK);

  // Zero the array
  for (byte i = 0; i<18; i++) blank[i]=0;

  delay(2000);  // lets other Px to start before "scanbus"
  scanbus(false);
  sprintf(lineString,"%s\r","Ready");
  dispLine();
  NewTone(buzzerPin,200,50);
  prompt();
}

void loop(void) {
  if ((prevSCANBUSmillis+SCANBUSinterval) < millis()) {  // plug & play 'px'
    scanbus(false);
    prevSCANBUSmillis=millis();
  }
  if (bluetooth.available() > 0) { // one character from bluetooth terminal if any
    data[0]=bluetooth.read();
    if (displayAlsoOnSerial==true) {  // usually false
      //Serial.print((char)data[0]);  // to serial/keyboard
      if (data[0] == '\r') {  // cr=cr+lf
        //Serial.print('\n');
      }
    }
    tft.setTextColor(ILI9341_WHITE, ILI9341_BLACK);
    dispData();  // send to display
    evaluateCommands();  // send to evaluate commands
  }
  if (keyboard.available()) { // one character from ps2 serial keyboard if any
    data[0] = keyboard.read();
    tft.setTextColor(ILI9341_WHITE, ILI9341_BLACK);
    bluetooth.print((char)data[0]);  // send to bluetooth terminal
    dispData();  // send to display
    evaluateCommands();  // send to evaluate commands
  }
  if (isNetDataWaiting==true) {
    execNetCommand();
  }
}  // end loop()


void evaluateCommands() {  // evaluate commands
  byte x=0;
  byte ln=0;
  char cmd[65]="";
  if (data[0] == 13) {  // CR (eol) pressed
    isKnownCommand=false;
    if (s_compare(command,"")==0) {  // empty command just do nothing
      isKnownCommand=true;
      prompt();
    }
    s_substring(park,sizeof(park),command,sizeof(command),0,4);  // reset
    if (s_compare(park,"reset") == 0) {
      isKnownCommand=true;
      reset();
    }
    s_substring(park,sizeof(park),command,sizeof(command),0,3);  // scanbus
    if (s_compare(park,"scan") == 0) {
      isKnownCommand=true;
      scanbus(true);  // true=displays results, false=mute
    }
    s_substring(park,sizeof(park),command,sizeof(command),0,6);  // checkpower
    if (s_compare(park,"ckpower") == 0) {
      isKnownCommand=true;
      checkpower();
    }
    s_substring(park,sizeof(park),command,sizeof(command),0,0);  // ?
    if (s_compare(park,"?") == 0) {
      isKnownCommand=true;
      help();
    }
    for (x=0;x<10;x++) {  // search processor's name for command
      if (x!=netMyPx && netRegisterNames[x][0] != 0) {  // not me, not unknown
        s_assign(park,netRegisterNames[x],sizeof(park));
        s_concat(park," ",sizeof(park));
        ln=strlen(park);
        s_substring(cmd,sizeof(cmd),command,sizeof(command),0,(ln-1));
        if (s_compare(cmd,park) == 0) {  // known processor name
          isKnownCommand=true;
          s_substring(cmd,sizeof(cmd),command,sizeof(command),ln,strlen(command)-1);  // clean command for target processor
          askCommandOnPx(_BEL[0], x, netRegisterAddrs[x], cmd);  // 7=BEL=cmdtype
        }
      }
    }
    if (isKnownCommand == false) {
      sprintf(lineString,"%s\r","unknown cmd");
      dispLine();
      prompt();
    }
    s_clear(command,sizeof(command));
  }
  if (data[0] > 31 && data[0] < 127) {  // only visible ASCII chars (127=DEL)
    s_concat(command,data,sizeof(command));
  }
}  // end evaluateCommands()


void dispData() {  // display single character at a time
  if (data[0] == _CR[0] || xPos>231) {
    xPos = 0;
    yDraw = scroll_line(); // It takes about 13ms to scroll 16 pixel lines
  }
  if (data[0] > 31 && data[0] < 127) {  // only visible ASCII chars (127=DEL)
    xPos += tft.drawChar(data[0],xPos,yDraw,2);
    blank[(18+(yStart-TOP_FIXED_AREA)/TEXT_HEIGHT)%19]=xPos; // Keep a record of line lengths
  }
}  // end dispData()


void terminalData() {  // send to terminal, serial and bluetooth, single char at a time
  if (displayAlsoOnSerial==true) {  // usually false
    //Serial.print((char)data[0]);  // to serial/keyboard
    if (data[0] == _CR[0]) {  // cr=cr+lf
      //Serial.print('\n');
    }
  }
  bluetooth.print((char)data[0]);  // send to bluetooth terminal
}  // end terminalData()


void dispLine() {  // prepare single char by single char
  byte i=0;  //       for display and serial and bluetooth terminal (for internal generated text)
  for (i=0; i<strlen(lineString); i++) {
  data[0]=lineString[i];
    tft.setTextColor(ILI9341_GREEN, ILI9341_BLACK);
    dispData();
    terminalData();
  }
  s_clear(lineString, sizeof(lineString));
}  // end dispString()


void help() {  // provide a list of available commands
  sprintf(lineString,"%s\r","scan");
  dispLine();
  sprintf(lineString,"%s\r","ckpower");
  dispLine();
  sprintf(lineString,"%s\r","<pxname> <cmd>");
  dispLine();
  sprintf(lineString,"%s\r","reset");
  dispLine();
  sprintf(lineString,"(free=%d)\r",s_freemem());
  dispLine();
  prompt();
}  // end help()

void prompt() {  // prompt, ready for commands
  sprintf(lineString,"%d>\r",netMyPx);
  dispLine();
}  // end prompt()


void checkpower() {  // calculate voltages onboard 3v, 5v and raw (12v)
  float n=0;         // sprintf does not work with float
  float n1=0;        // then I print separate int.dec parts of numbers
  int v3i=0;
  int v3d=0;
  int v5i=0;
  int v5d=0;
  int vrawi=0;
  int vrawd=0;

  n = analogRead(vin3Pin);  // v3
  n1=(((6.60 * n) / 1023.00));
  n=(n1 + ((n1 * 1.0) /100));  // arbitrary correction in %)
  v3i=n;
  v3d=float((n-v3i)*100);

  n = analogRead(vin5Pin);  // v5
  n1=(((6.60 * n) / 1023.00));
  n=(n1 + ((n1 * 1.0) /100));  // arbitrary correction in %)
  v5i=n;
  v5d=float((n-v5i)*100);

  n = analogRead(vin12Pin);  // vraw
  n1=(((13.2 * n) / 1023.00));
  n=(n1 + ((n1 * 3.0) /100));  // arbitrary correction in %)
  vrawi=n;
  vrawd=float((n-vrawi)*100);

  sprintf(lineString, "v3=%d.%02d v5=%d.%02d vraw=%d.%02d\r",v3i,v3d,v5i,v5d,vrawi,vrawd);
  dispLine();
  prompt();
}  // end checkpower


void reset() {  // ------------------------------------------------------------ RESET
  analogWrite(ResetPin,LOW);
}


void scanbus(bool isInteractive) {  // scans i2c bus for active addresses
  byte count = 0;                   // true=interactive and force refresh, false=mute and ask for new only
  byte i=0;
  int px=0;
  if (isInteractive==true) {  // shows it and clear Register -> force refresh
    sprintf(lineString,"%s\r","Scanning...");
    dispLine();
    for (px=0;px<10;px++) {  // clear Register
      netRegisterAddrs[px] = 0;
      s_clear(netRegisterNames[px],sizeof(netRegisterNames[px]));
    }
  }
  for (i = 8; i < 128; i++) {  // addresses 0-7 are reserved (7 bits address)
    Wire.beginTransmission (i);
    delay (10);
    if (Wire.endTransmission (true) == 0 || i==netMyAddress) {  // it blocks here if no i2c available! put pullup resistors and check if a device is blocking to GND pins
      sprintf(lineString,"%3d (0x%02X) ",i,i);
      if (i==netMyAddress) {
        netRegisterAddrs[netMyPx] = netMyAddress;
        s_trimall(netRegisterNames[netMyPx],netMyNAME,sizeof(netRegisterNames[netMyPx]));
        s_concat(lineString,netMyID,sizeof(lineString));
        s_concat(lineString," ",sizeof(lineString));
        s_concat(lineString,netRegisterNames[netMyPx],sizeof(lineString));
        s_concat(lineString," [me]",sizeof(lineString));
      } else {
        px=whois(int(i));  // check address
        if (px < 10) {  // 0-9 is a "p", 99 is unknown
          sprintf(park,"p%d %s",px,netRegisterNames[px]);
          s_concat(lineString,park,sizeof(lineString));
        }
      }
      s_concat(lineString,_CR,sizeof(lineString));
      if (isInteractive==true) { dispLine(); };  // shows it
      count++;
    }
  }
  if (isInteractive==true) {  // shows it
    sprintf(lineString,"%d found\r",count);
    dispLine();
    prompt();
  }
}  // end scanbus()


void askCommandOnPx(const int cmdtype, const int px, const int addrs, const char *cmd) {  // ask target processor to execute a command
  char ccommand[65]="";
  s_assign(ccommand, _BEL, sizeof(ccommand));  // BEL
  s_concat(ccommand, _MYADDR, sizeof(ccommand));  // netMyAddress
  s_concat(ccommand, cmd, sizeof(ccommand));  // cmd
  prompt();
  Wire.beginTransmission (addrs);  // ask
  Wire.write(ccommand);
  Wire.endTransmission (true);
}  // end asksd()


int whois(int address) {  // ask name+ID on i2c net, expecting char(5) ENQ as 1st byte
  byte i=0;
  char xtype=0;
  int xid=0;
  char xaddr=0;
  char park1[65]="";
  s_clear(park,sizeof(park));
  s_clear(park1,sizeof(park1));
  Wire.requestFrom(address, 11);  // send request, dest address + nr of bytes
  delay(10);
  i=0;
  while(Wire.available()) {  // read answer
    data[0]=Wire.read();
    i++;
    if (i==1) { xtype=data[0]; };  // byte 1 = answer type must be char(5) ENQ
    if (i>1 && i<10) {  // bytes 2-9 = name
      park[i-2] = data[0]; 
    }
    if (i==10) { xid=atoi(data); };  // byte 10=id
    if (i==11) { xaddr=data[0]; };  // byte 11=address
  }
  if ((xtype == _ENQ[0]) && (int(xaddr) == address) && (xid < 10)) {  // must be a "p"= 0-9, and 10th byte = address, or unknown/wrong answer
    netRegisterAddrs[int(xid)] = xaddr;  // mem id and address
    s_trimall(park1,park,sizeof(park1));
    s_assign(netRegisterNames[int(xid)],park1,sizeof(netRegisterNames[int(xid)]));
    return xid;  // recognized address as "p"
  } else {
    return 99;  // unknown address or answer
  }
}  // end whois()


void execNetCommand() {  // executes received command
  isBusy=true;
  s_clear(park, sizeof(park));
  s_clear(command, sizeof(command));
  if (recType == _BEL[0]) {  // char(7) = BEL command type ---------------- BEL=incoming request
    isKnownCommand=false;
    if (s_compare(recCommand,"")==0) {  // empty command just do nothing
      isKnownCommand=true;
      prompt();
    }
    s_substring(park,sizeof(park),recCommand,sizeof(recCommand),0,0);  // ?
    if (s_compare(park,"?") == 0) {
      isKnownCommand=true;
      help();
    }
    if (isKnownCommand == false) {
      s_assign(lineString,"unknown cmd",sizeof(lineString));
      dispLine();
      prompt();
    }
  }  // endif recType=BEL
  if (recType == _ACK[0]) {  // char(6) = ACK command type ---------------- ACK=incoming answer
    s_assign(lineString,recCommand,sizeof(lineString));
    dispLine();
  }  // endif recType=ACK
  s_clear(recCommand,sizeof(recCommand));
  recType=0;
  recAddress=0;
  isNetDataWaiting=false;
  isBusy=false;
}  // end execNetCommand()

void receiveEvent(int howMany) {  // i2c event incoming requests
  if (isBusy==false) {
    int counter=0;
    s_clear(recCommand, sizeof(recCommand));  // sender command
    recAddress=0;  // sender address
    recType=0;  // sender cmd type
    if (howMany == 0) {  // ignores empty requests
      isNetDataWaiting=false;
    } else {
      while (Wire.available()) {
        data[0]=Wire.read();
        if (counter==0) { recType = data[0]; } // 1st char = cmd type
        if (counter==1) { recAddress = data[0]; } // 2nd char = sender address
        if (counter >1) { // other chars = command
          s_concat(recCommand, data, sizeof(recCommand)); 
        }
        counter++;
      }
      isNetDataWaiting=true;
    }
  }//endisBusy
}  // end receiveEvent()


//------------------------------------------------------------------------- CLEAR
void s_clear(char *dest_source_string, const int dest_sizeof) {  // fills-up with NUL=chr(0)
  memset(dest_source_string, 0, dest_sizeof);
}  // end s_clear()

//------------------------------------------------------------------------- ASSIGN
bool s_assign(char *dest_string, const char *source_string, const int dest_sizeof) {  // copies source to dest
  int _LenS=strlen(source_string);  // how many bytes
  if (_LenS > (dest_sizeof - 1)) {
    return true;  // err 1
  } else {
    strcpy(dest_string, source_string);
    return false;  // ok 0
  }
}  // end s_assign()

//------------------------------------------------------------------------- SUBSTRING
bool s_substring(char *dest_string, const int dest_sizeof, const char *source_string, 
 const int source_sizeof, const int source_from, const int source_to) {  // copies source(from, to) to dest
  if ((source_from < 0) || (source_to < source_from) || ((source_to - source_from + 1) > (dest_sizeof - 1)) 
    || (source_to >= (source_sizeof-1)) || ((source_to - source_from + 1) > (strlen(source_string)))) {
    dest_string[0]=0;  // NUL
    return true;  // err 1
  } else {
    int _Count=0;
    for (int i=source_from;i<(source_to+1);i++) {
      dest_string[_Count]=source_string[i];
      _Count++;
    }
    dest_string[_Count]=0;  // ends with NUL
    return false;  // ok 0
  }
}  // end s_substring()

//------------------------------------------------------------------------- CONCAT
bool s_concat(char *dest_string, const char *source_string, const int dest_sizeof) {  // append source to dest
  int _LenS=strlen(source_string);  // how many bytes source
  int _LenD=strlen(dest_string);  // how many bytes dest
  if ((_LenS + _LenD) > (dest_sizeof - 1)) {
    return true;  // err 1
  } else {
    strcat(dest_string, source_string);
    return false;  // ok 0
  }
}  // end s_concat()

//------------------------------------------------------------------------- COMPARE
bool s_compare(const char *dest_string, const char *source_string) {  // compares source with dest
  int _LenS=strlen(source_string);  // how many bytes source
  int _LenD=strlen(dest_string);  // how many bytes dest
  if (_LenS != _LenD) {  // different length
    return true;  // are different 1
  } else {
    if (strcmp(dest_string, source_string) == 0) {
      return false;  // are the same 0
    } else {
      return true;  // are different 1
    }    
  }
}  // end s_compare()

//------------------------------------------------------------------------- TRIMALL
bool s_trimall(char *dest_string, const char *source_string, const int dest_sizeof) {  // eliminate all 'spaces' from source to dest
  int _LenS=strlen(source_string);  // how many bytes max
  if (_LenS > (dest_sizeof - 1)) {
    return true;  // err 1
  } else {
    int _Count=0;
    for (int i=0;i<(_LenS);i++) {
      if (source_string[i] != _SPACE[0]){
        dest_string[_Count]=source_string[i];
        _Count++;
      }
    }
    dest_string[_Count]=0;  // ends with NUL
    return false;  // ok 0
  }
}  // end s_trimall()

//------------------------------------------------------------------------- FREEMEM
int s_freemem() {
  extern int __heap_start,*__brkval;
  int v;
  return (int)&v - (__brkval == 0  ? (int)&__heap_start : (int) __brkval); 
}  // end s_freemem()



// ##############################################################################################
// Call this function to scroll the display one text line
// ##############################################################################################
int scroll_line() {
  int yTemp = yStart; // Store the old yStart, this is where we draw the next line
  // Use the record of line lengths to optimise the rectangle size we need to erase the top line
  tft.fillRect(0,yStart,blank[(yStart-TOP_FIXED_AREA)/TEXT_HEIGHT],TEXT_HEIGHT, ILI9341_BLACK);

  // Change the top of the scroll area
  yStart+=TEXT_HEIGHT;
  // The value must wrap around as the screen memory is a circular buffer
  if (yStart >= 320 - BOT_FIXED_AREA) yStart = TOP_FIXED_AREA + (yStart - 320 + BOT_FIXED_AREA);
  // Now we can scroll the display
  scrollAddress(yStart);
  return  yTemp;
}

// ##############################################################################################
// Setup a portion of the screen for vertical scrolling
// ##############################################################################################
// We are using a hardware feature of the display, so we can only scroll in portrait orientation
void setupScrollArea(uint16_t TFA, uint16_t BFA) {
  tft.writecommand(ILI9341_VSCRDEF); // Vertical scroll definition
  tft.writedata(TFA >> 8);
  tft.writedata(TFA);
  tft.writedata((320-TFA-BFA)>>8);
  tft.writedata(320-TFA-BFA);
  tft.writedata(BFA >> 8);
  tft.writedata(BFA);
}

// ##############################################################################################
// Setup the vertical scrolling start address
// ##############################################################################################
void scrollAddress(uint16_t VSP) {
  tft.writecommand(ILI9341_VSCRSADD); // Vertical scrolling start address
  tft.writedata(VSP>>8);
  tft.writedata(VSP);
}

SD Arduino code

Arduino
/* Arduino Net-P (i2c processors network) by Zonca Marco 2020
 * 'netMyNAME' ("SD      ") max 8 char, 'netMyID' (p1) max 2 char, 
 *  address='netMyAddress' max 0xFF, 'netMyPx' (1)
 * 
 * implemented commands (* = to do): mr, mw
 *          dir, mkdir, rmdir, del, type, size, rename*, reset, ?
 *            
 * (m... commands work on \MEMORY\Px\ directory, plain text file 'cr'+'lf' terminating line/record)
 * (sd...  commands work on SD memory card)
 * 
 * ?:         ? [ask for help
 *            i.e. ?
 *            returns a list of available commands on board and free memory]
 *            
 * mw/mwa:    mw <FileName>=<Value> [writes/modifies Value in file name
 *            i.e. mw temp01=24 (FileName=Value)
 *            returns "written"] (mwa will append Value to the file)
 * mr/mra:    mr <FileName> [reads Value from corrisponding file name
 *            i.e. mr temp01 (FileName is temp01, Value is 24)
 *            returns 'Value' or "not found"] (mra will read multilines Value from the file)
 * 
 * reset:     reset [resets MPU
 *            i.e. reset]
 * dir:       dir <path> [reads file names starting from 'path'
 *            i.e. dir /]
 *            returns a list of file 'names' and 'size'... ends with 'n found']
 * mkdir:     mkdir <dirname> [build a new directory (and also relative subdirectories)          
 *            i.e. mkdir /music, sdmkdir /logic/params/binary
 *            returns "built" or "error"]
 * rmdir:     rmdir <dirname> [delete a dir
 *            i.e. rmdir /music
 *            returns "removed" or "error or not empty" ]
 * rename:    rename <OldFileName> <NewFileName> [renames a file
 *            i.e. rename myfile.txt bestfile.txt
 *            returns "renamed" or "not found"]
 * del:       del <FileName> [delete a file
 *            i.e. del myfile.txt
 *            returns "deleted" or "not found"]
 * type:      type <FileName> <Mode> [reads a file and displays in mode 0=CHR, 1=BIN, 2=HEX
 *            i.e. type myfile.txt CHR
 *            returns the content of the file ... and filesize 'n' and bytes red 'n' at the end]      
 * eeupl:     eeupl <Filename> [read a file and upload it to the EEPROM, write and verify all data
 *            i.e. eeupl /mysketch.hex
 *            returns upload, written, verified...]
 *            
 * SD card attached to SPI bus as follows:
 * ---------------------------------------
 *  MOSI - pin 11
 *  MISO - pin 12
 *  CLK  - pin 13
 *  CS   - pin 10 see 'chipSelect' below
 */
 
#include <Wire.h>
#include <SdFat.h>

const int netMyPx = 1; // netMyPx 0-9
const int netMyAddress = 0x0b;  // i2c address 0x0b=HEX 11=DEC
const char *netMyID = "p1";  // netMyID
const char *netMyNAME = "SD      ";  // netMyNAME 8 bytes
const char *memDIR = "/MEMORY/";  // memory directory
const char *logDIR = "/LOG/";  // log directory
const int chipSelect = 10;  // CS
const int eprom1addr=80;  // 1st eprom address (80, 81, etc. (DEC))
const int eprom1pgsz=16;  // 1st eprom memory page size (16, 128, etc.)
const long eprom1size=256;  // 1st eprom size (258, 1024, etc.)
const int ResetPin=17;  // A3 put low for reset

char _CR[2]="";  // CR
char _LF[2]="";  // LF
char _BEL[2]="";  // BEL
char _ENQ[2]="";  // ENQ
char _ACK[2]="";  // ACK
char _NUL[2]="";  // NUL
char _SPACE[2]="";  // SPACE
char _MYADDR[2]="";  // netMyAddress

char lineString[65]="";
char recCommand[65]="";
char data[2]="";
byte recAddress=0;
byte recType=0;
bool isNetDataWaiting=false;
bool isKnownCommand=false;
bool sdOpened=false;
bool isBusy=false;

SdFat sd;

void setup() {
//  Serial.begin(38400);
  Wire.begin(netMyAddress);
  Wire.onReceive(receiveEvent);  // i2c event
  Wire.onRequest(netWhoIsEvent);  // i2c whois
  _CR[0] = 13;  // CR
  _LF[0] = 10;  // LF
  _BEL[0] = 7;  // BEL
  _ENQ[0] = 5;  // ENQ
  _ACK[0] = 6;  // ACK
  _NUL[0] = 0;  // NUL
  _SPACE[0] = 32;  // SPACE
  _MYADDR[0] = netMyAddress;
  if (!sd.begin(chipSelect,SPI_FULL_SPEED)) {  // in case try SPI_HALF_SPEED
    sdOpened=false;
  } else {
    sdOpened=true;
  }
}  // end setup()

void loop() {
  if (isNetDataWaiting == true) {
    execNetCommand();
  }
}  // end loop()


void sdreset() {  // ------------------------------------------------------------ RESET
  analogWrite(ResetPin,LOW);
}

void sddelete(const char *_s) {  // ---------------------------------------------- DELETE
  if (!sd.exists(_s)) {  // check if file does exist
    s_assign(lineString,"not found!",sizeof(lineString));
    dispLine();
  } else {
    if (!sd.remove(_s)) {  // remove file
      s_assign(lineString,"error!",sizeof(lineString));
      dispLine();
    } else {
      s_assign(lineString,"deleted",sizeof(lineString));
      dispLine();
    }
  }
  prompt();
}  // end sddelete()


void sdeeupload(const char *_s) {  // ------------------------------------------- EEUPLOAD
  File file;
  long count=0;
  long uc=0;
  byte err=0;
  byte EE=0;
  long fsize=0;
  bool isDone=false;
  byte EErr=0;

  while (isDone==false) {
    if (!sd.exists(_s)) {  // check if name exists  // ---------------- preliminary checks
      s_assign(lineString,"nm not found",sizeof(lineString));
      dispLine();
      isDone=true;
      break;
    }
    file=sd.open(_s);
    if (!file.isOpen()) {
      s_assign(lineString,"nm open!",sizeof(lineString));
      dispLine();
      isDone=true;
      break;
    }
    fsize=file.fileSize();
    if (fsize > (eprom1size-16-1)) {  // must be less then size-header-NUL
      s_assign(lineString,"too big!",sizeof(lineString));
      dispLine();
      file.close();
      isDone=true;
      break;
    }
    sprintf(lineString,"(%ld) upload...",fsize);  // ----------------------------- write
    dispLine();
    while (file.available()) {
      data[0]=file.read();
      EE=data[0];
      uc=count+16;
      writeEEPROM (eprom1addr, uc, EE, &EErr);
      if (EErr!=0) { 
        sprintf(lineString,"error (%d) wr EE!",EErr);
        dispLine();
        file.close();
        isDone=true;
        break; //break this while, later the main while
      }
      count++;
    }//endwhile
    file.close();
    if (isDone==true) {break;}  // breaks main while
    
    EE=_NUL[0];  // --------------------------------------------- write finish with a NUL
    uc=count+16;  // count already+1
    writeEEPROM (eprom1addr, uc, EE, &EErr);
    if (EErr!=0) { 
      sprintf(lineString,"error (%d) NUL EE!",EErr);
      dispLine();
      isDone=true;
      break;
    }
    sprintf(lineString,"(%ld) written...",count);
    dispLine();

    file=sd.open(_s);  // -------------------------------------------------------------- verify
    if (!file.isOpen()) {
      s_assign(lineString,"error opn nm!",sizeof(lineString));
      dispLine();
      isDone=true;
      break;
    }
    count=0;
    while (file.available()) {
      data[0]=file.read();
      uc=count+16;
      EE=readEEPROM (eprom1addr, uc, &EErr);
      if (EErr!=0) { 
        sprintf(lineString,"error (%d) rd EE!",EErr);
        dispLine();
        file.close();
        isDone=true;
        break;
      }
      if (byte(data[0])!=EE) { 
        sprintf(lineString,"diff vrf (%ld) in EE!",count);
        dispLine();
        file.close();
        isDone=true;
        break; //break this while, later the main while
      }
      count++;
    }//endwhile
    file.close();
    if (isDone==true) { break; }  // breaks main while
        
    uc=count+16;  // ----------------------------------------------- verify finish NUL
    EE=readEEPROM (eprom1addr, uc, &EErr);
    if (EErr!=0) { 
      sprintf(lineString,"error (%d) vrf EE!",EErr);
      dispLine();
      isDone=true;
      break;
    } else {
      if (EE!=byte(_NUL[0])) { 
        sprintf(lineString,"(%ld) not NUL EE!",uc);
        dispLine();
        isDone=true;
        break;
      }
    }
    sprintf(lineString,"(%ld) verified",count);
    dispLine();
    isDone=true;
  }//whileisDone
  prompt();
}  // end sdeeupload()


void sdmr(const char *_s, const boolean _all) {  // ---------------------------------------- MR / MRA
  byte cnt=0;
  if (!sd.exists(_s)) {  // check if name exists
    s_assign(lineString,"not found",sizeof(lineString));
    dispLine();
  } else {
    File file;
    file=sd.open(_s);
    if (!file.isOpen()) {
      s_assign(lineString,"open!",sizeof(lineString));
      dispLine();
    }
    s_clear(lineString,sizeof(lineString));
    while (file.available()) {
      data[0]=file.read();
      if (data[0]!=_CR[0] && data[0]!=_LF[0]) {
        s_concat(lineString,data,sizeof(lineString));
        cnt++;
      }
      if (cnt > (sizeof(lineString)-1)) {
        s_assign(lineString,"too long!",sizeof(lineString));
        dispLine();
        break;
      }
      if (_all==true && data[0]==_LF[0]) {  // multiline-log reading MRA
        cnt += 2;  // include CR+LF counting
        dispLine();
      }
    }
    file.close();
    if (_all==false) { dispLine(); }
    sprintf(lineString,"(%d) bytes",cnt);
    dispLine();
  }
  prompt();
}  // end sdmw()


void sdmw(const char *_s, const char *_v, const boolean _append) {  // -------------------------- MW / MWA
  if (_append==false && sd.exists(_s)) {  // check if name exists then delete it (MW only)
    if (!sd.remove(_s)) {
      s_assign(lineString,"rm error!",sizeof(lineString));
      dispLine();
    }
  }
  SdFile file(_s, O_WRONLY | O_CREAT | O_APPEND);
  if (!file.isOpen()) {
    s_assign(lineString,"opening!",sizeof(lineString));
    dispLine();
  } else {
    file.println(_v);
    file.close();
    if (_append==false) {
      s_assign(lineString,"written",sizeof(lineString));
    } else {
      s_assign(lineString,"added",sizeof(lineString));
    }
    dispLine();
  }
  prompt();
}  // end sdmw()

void sddir(const char *_s) {  // ----------------------------- DIR
  int fcount=0;
  int dcount=0;
  long fsize=0;
  SdFile root;
  SdFile file;
  bool isDone=false;
  char fname[14]="";
  while (isDone==false) {
    if (!root.open(_s)) {  // dir starting from 'path'
      s_assign(lineString,"not found!",sizeof(lineString));
      dispLine();
      isDone=true;
      break;
    }
    while (file.openNext (&root, O_RDONLY)) {
      if (!file.isHidden()) {
        if (file.isDir()) {
          file.getName(fname,13);
          s_assign(lineString,fname,sizeof(lineString));
          s_concat(lineString," <dir>",sizeof(lineString));
          dispLine();
          dcount++;
        }
        if (file.isFile()) {
          file.getName(fname,13);
          fsize=file.fileSize();
          s_assign(lineString,fname,sizeof(lineString));
          s_concat(lineString," (",sizeof(lineString));
          sprintf(fname,"%ld",fsize);
          s_concat(lineString,fname,sizeof(lineString));
          s_concat(lineString,")",sizeof(lineString));
          dispLine();
          fcount++; 
        }
      }//end !file.hidden
      file.close();
    } //end whileOpenNext
    root.close();
    s_assign(lineString,"(dir=",sizeof(lineString));
    sprintf(fname,"%d",dcount);
    s_concat(lineString,fname,sizeof(lineString));
    s_concat(lineString," file=",sizeof(lineString));
    sprintf(fname,"%d",fcount);
    s_concat(lineString,fname,sizeof(lineString));
    s_concat(lineString,")",sizeof(lineString));
    dispLine();
    isDone=true;
  }//end while isDone
  prompt();
}  // end sddir()


void sdhelp() {  // ---------------------------------------------- HELP
  sprintf(lineString,"%s","dir <path>");
  dispLine();
  sprintf(lineString,"%s","mr|mra <nm>");
  dispLine();
  sprintf(lineString,"%s","mw|mwa <nm>=<val>");
  dispLine();
  sprintf(lineString,"%s","del <nm>");
  dispLine();
  sprintf(lineString,"%s","eeupl <nm>");
  dispLine();
  sprintf(lineString,"%s","reset");
  dispLine();
  sprintf(lineString,"(free=%d)",s_freemem());
  dispLine();
  prompt();
}  // end help()

void prompt() {  // prompt, ready for commands
  sprintf(lineString,"%d>\r",netMyPx);
  dispLine();
}  // end prompt()

void dispLine() {  // print information
  s_concat(lineString, _CR, sizeof(lineString));
  txBackToSender();  // tx
  s_clear(lineString, sizeof(lineString));
  delay(100);
}  // end dispLine()

void receiveEvent(int howMany) {  // i2c event incoming requests
  if (isBusy==false) {
    int counter=0;
    s_clear(recCommand, sizeof(recCommand));  // sender command
    recAddress=0;  // sender address
    recType=0;  // sender cmd type
    if (howMany == 0) {  // ignores empty requests
      isNetDataWaiting=false;
    } else {
      while (Wire.available()) {
        data[0]=Wire.read();
        if (counter==0) { recType = data[0]; } // 1st char = cmd type
        if (counter==1) { recAddress = data[0]; } // 2nd char = sender address
        if (counter >1) { // other chars = command
          s_concat(recCommand, data, sizeof(recCommand)); 
        }
        counter++;
      }
      isNetDataWaiting=true;
    }
  }//endifbusy
}  // end receiveEvent()

void netWhoIsEvent() {  // i2c event: 11 bytes = 1=char(5) ENQ, 2-9=netMyNAME 10=netMyPx 11=netMyAddress
  if (isBusy==false) {
    char ccommand[65]="";
    char park[3]="";
    s_concat(ccommand, _ENQ, sizeof(ccommand));  // ENQ=answer whois
    s_concat(ccommand, netMyNAME, sizeof(ccommand));  // netMyNAME
    s_concat(ccommand, itoa(netMyPx,park,10), sizeof(ccommand));  // netMyPx
    s_concat(ccommand, _MYADDR, sizeof(ccommand));  // netMyAddress
    Wire.write(ccommand);
  }
}  // end netWhoIsEvent()


void txBackToSender() {  // tx back to sender
  char ccommand[65]="";
  s_assign(ccommand, _ACK, sizeof(ccommand));  // ACK=answer data
  s_concat(ccommand, _MYADDR, sizeof(ccommand));  // netMyAddress
  s_concat(ccommand, lineString, sizeof(ccommand));  // data
  Wire.beginTransmission (recAddress);
  Wire.write (ccommand);
  Wire.endTransmission (true);
}  // end txBackToSender()


void execNetCommand() {  // executes received command
  isBusy=true;
  char park[65]="";
  char command[65]="";
  int x=0;
  int its=-1;
  if (recType == _BEL[0]) {  // char(7) = BEL command type
    isKnownCommand=false;
    if (s_compare(recCommand,"")==0) {  // empty command just do nothing
      isKnownCommand=true;
      prompt();
    }
    s_substring(park,sizeof(park),recCommand,sizeof(recCommand),0,0);  // ?
    if (s_compare(park,"?") == 0) {
      isKnownCommand=true;
      sdhelp();
    }
    s_substring(park,sizeof(park),recCommand,sizeof(recCommand),0,4);  // reset
    if (s_compare(park,"reset") == 0) {
      isKnownCommand=true;
      sdreset();
    }
    s_substring(park,sizeof(park),recCommand,sizeof(recCommand),0,2);  // mw
    if (s_compare(park,"mw ") == 0) {
      for (x=0; x<strlen(recCommand);x++) {  // search for "="
        s_substring(data,sizeof(data),recCommand,sizeof(recCommand),x,x);
        if (strcmp(data,"=") == 0) {
          its=x;
        }
      }
      if (its >= 0) {
        s_substring(park,sizeof(park),recCommand,sizeof(recCommand),3,its-1);  // filename
        s_assign(command,memDIR,sizeof(command));  // memDIR
        s_concat(command,park,sizeof(command));  // memDIR+filename
        s_substring(park,sizeof(park),recCommand,sizeof(recCommand),its+1,strlen(recCommand)-1);  // value
        isKnownCommand=true;
        sdmw(command,park,false);
      }
    }
    s_substring(park,sizeof(park),recCommand,sizeof(recCommand),0,3);  // mwa (append)
    if (s_compare(park,"mwa ") == 0) {
      for (x=0; x<strlen(recCommand);x++) {  // search for "="
        s_substring(data,sizeof(data),recCommand,sizeof(recCommand),x,x);
        if (strcmp(data,"=") == 0) {
          its=x;
        }
      }
      if (its >= 0) {
        s_substring(park,sizeof(park),recCommand,sizeof(recCommand),4,its-1);  // filename
        s_assign(command,logDIR,sizeof(command));  // logDIR
        s_concat(command,park,sizeof(command));  // logDIR+filename
        s_substring(park,sizeof(park),recCommand,sizeof(recCommand),its+1,strlen(recCommand)-1);  // value
        isKnownCommand=true;
        sdmw(command,park,true);
      }
    }
    s_substring(park,sizeof(park),recCommand,sizeof(recCommand),0,2);  // mr
    if (s_compare(park,"mr ") == 0) {
      isKnownCommand=true;
      s_substring(park,sizeof(park),recCommand,sizeof(recCommand),3,strlen(recCommand)-1);  // filename
      s_assign(command,memDIR,sizeof(command));  // memDIR
      s_concat(command,park,sizeof(command));  // memDIR+filename
      sdmr(command,false);
    }
    s_substring(park,sizeof(park),recCommand,sizeof(recCommand),0,3);  // mra (multiline/log)
    if (s_compare(park,"mra ") == 0) {
      isKnownCommand=true;
      s_substring(park,sizeof(park),recCommand,sizeof(recCommand),4,strlen(recCommand)-1);  // filename
      s_assign(command,logDIR,sizeof(command));  // logDIR
      s_concat(command,park,sizeof(command));  // logDIR+filename
      sdmr(command,true);
    }
    s_substring(park,sizeof(park),recCommand,sizeof(recCommand),0,3);  // del
    if (s_compare(park,"del ") == 0) {
      isKnownCommand=true;
      s_substring(command,sizeof(command),recCommand,sizeof(recCommand),4,strlen(recCommand)-1);
      sddelete(command);
    }
    s_substring(park,sizeof(park),recCommand,sizeof(recCommand),0,5);  // eeupload
    if (s_compare(park,"eeupl ") == 0) {
      isKnownCommand=true;
      s_substring(command,sizeof(command),recCommand,sizeof(recCommand),6,strlen(recCommand)-1);
      sdeeupload(command);
    }
    s_substring(park,sizeof(park),recCommand,sizeof(recCommand),0,3);  // dir file
    if (s_compare(park,"dir ") == 0) {
      isKnownCommand=true;
      s_substring(command,sizeof(command),recCommand,sizeof(recCommand),4,strlen(recCommand)-1);
      sddir(command);
    }
    if (isKnownCommand == false) {
      s_assign(lineString,"unknown command",sizeof(lineString));
      dispLine();
      prompt();
    }
  }  // endif recType
  s_clear(recCommand,sizeof(recCommand));
  recType=0;
  recAddress=0;
  isNetDataWaiting=false;
  isBusy=false;
}  // end execNetCommand()

// reading eeprom
byte readEEPROM (const int _disk, const unsigned int _addr, byte *EErr ) {
  *EErr=0;
  byte _rxbyte=0;
  Wire.beginTransmission(_disk);
  Wire.write(_addr);
  if (Wire.endTransmission() == 0) {
    Wire.requestFrom(_disk,1);
    if (Wire.available()) {
      _rxbyte = Wire.read();
    } else {
       *EErr = 1; //"READ no data available"
    }
  } else {
    *EErr = 2; //"READ eTX error"
  }
  Wire.endTransmission (true);
  return _rxbyte;
}

// writing eeprom
void writeEEPROM (const int _disk, const unsigned int _addr, byte _txbyte, byte *EErr) {
  *EErr=0;
  Wire.beginTransmission(_disk);
  Wire.write(_addr);
  Wire.write(_txbyte);
  if (Wire.endTransmission() != 0) {
    *EErr = 3; //"WRITING eTX error"
  }
  Wire.endTransmission (true);
  delay(5); 
}


//------------------------------------------------------------------------- CLEAR
void s_clear(char *dest_source_string, const int dest_sizeof) {  // fills-up with NUL=chr(0)
  memset(dest_source_string, 0, dest_sizeof);
}  // end s_clear()

//------------------------------------------------------------------------- ASSIGN
bool s_assign(char *dest_string, const char *source_string, const int dest_sizeof) {  // copies source to dest
  int _LenS=strlen(source_string);  // how many bytes
  if (_LenS > (dest_sizeof - 1)) {
    return true;  // err 1
  } else {
    strcpy(dest_string, source_string);
    return false;  // ok 0
  }
}  // end s_assign()

//------------------------------------------------------------------------- SUBSTRING
bool s_substring(char *dest_string, const int dest_sizeof, const char *source_string, 
 const int source_sizeof, const int source_from, const int source_to) {  // copies source(from, to) to dest
  if ((source_from < 0) || (source_to < source_from) || ((source_to - source_from + 1) > (dest_sizeof - 1)) 
    || (source_to >= (source_sizeof-1)) || ((source_to - source_from + 1) > (strlen(source_string)))) {
    dest_string[0]=0;  // NUL
    return true;  // err 1
  } else {
    int _Count=0;
    for (int i=source_from;i<(source_to+1);i++) {
      dest_string[_Count]=source_string[i];
      _Count++;
    }
    dest_string[_Count]=0;  // ends with NUL
    return false;  // ok 0
  }
}  // end s_substring()

//------------------------------------------------------------------------- CONCAT
bool s_concat(char *dest_string, const char *source_string, const int dest_sizeof) {  // append source to dest
  int _LenS=strlen(source_string);  // how many bytes source
  int _LenD=strlen(dest_string);  // how many bytes dest
  if ((_LenS + _LenD) > (dest_sizeof - 1)) {
    return true;  // err 1
  } else {
    strcat(dest_string, source_string);
    return false;  // ok 0
  }
}  // end s_concat()

//------------------------------------------------------------------------- COMPARE
bool s_compare(const char *dest_string, const char *source_string) {  // compares source with dest
  int _LenS=strlen(source_string);  // how many bytes source
  int _LenD=strlen(dest_string);  // how many bytes dest
  if (_LenS != _LenD) {  // different length
    return true;  // are different 1
  } else {
    if (strcmp(dest_string, source_string) == 0) {
      return false;  // are the same 0
    } else {
      return true;  // are different 1
    }    
  }
}  // end s_compare()

//------------------------------------------------------------------------- FREEMEM
int s_freemem() {
  extern int __heap_start,*__brkval;
  int v;
  return (int)&v - (__brkval == 0  ? (int)&__heap_start : (int) __brkval); 
}  // end s_freemem()



//*************************************************************************************

/*

void sdtype(String filename, byte mode) {  // ----------------------------- TYPE
  char data=0;  //                     sdtype, displays the content of a file
  int count=0;  //                     mode 0=CHAR 1=BIN 2=HEX
  byte vert=0;
  bool isAlreadyAtNL=true;
  String STemp="";
  STemp.reserve(64);
  File SDfile;
  if (!SD.exists(filename)) {  // check if file does exist
    lineString="SD file not found!";
    dispLine(lineString);
    } else {
      SDfile = SD.open(filename);  // open file
      while(SDfile.available()) {  // read all file, byte by byte
        data = (SDfile.read());
        switch (mode) {
          case 0: // CHAR
            lineString=String(data);
            dispChar(lineString);
            break;
          case 1: // BIN
            STemp="00000000"+String(data,BIN);  // fill with non significant bits
            lineString=STemp.substring(STemp.length()-9,STemp.length())+" ";
            dispChar(lineString);
            vert ++;
            if (vert == 3) {
              dispLine("");  // newline
              isAlreadyAtNL=true;
              vert=0;
            } else {
              isAlreadyAtNL=false;
            }
            break;
          case 2: // HEX
            STemp="00"+String(data,HEX)+" ";  // fill with non significant 0
            lineString=STemp.substring(STemp.length()-3,STemp.length())+" ";
            dispChar(lineString);
            vert ++;
            if (vert == 8) {
              dispLine("");  // newline
              isAlreadyAtNL=true;
              vert=0;
            } else {
              isAlreadyAtNL=false;
            }
            break;
        }
        count ++;
      }  //end while
      if (isAlreadyAtNL==false) {dispLine("");};  // empty line at the end
      lineString="file size ("+String(SDfile.size(),DEC)+"), bytes read ("+String(count,DEC)+")";  // file size
      dispLine(lineString);
      SDfile.close();
    }
  prompt();
}  // end sdtype()


    if (recCommand.substring(0,4) == "type") {
      isKnownCommand=true;
      command=recCommand.substring(5,recCommand.length()-4);
      if (recCommand.substring(recCommand.length()-3,recCommand.length())=="CHR") { x=0; };
      if (recCommand.substring(recCommand.length()-3,recCommand.length())=="BIN") { x=1; };
      if (recCommand.substring(recCommand.length()-3,recCommand.length())=="HEX") { x=2; };
      sdtype(command,x);
    }






void sdfilesize(const char *_s) {  // ------------------------------------------- FILESIZE
  File file;
  bool isDone=false;
  long fsize=0;
  while (isDone==false) {
    if (!sd.exists(_s)) {  // check if name exists
      s_assign(lineString,"nm not found",sizeof(lineString));
      dispLine();
      isDone=true;
      break;
    }
    file=sd.open(_s);
    if (!file.isOpen()) {
      s_assign(lineString,"nm open!",sizeof(lineString));
      dispLine();
      isDone=true;
      break;
    }
    fsize=file.fileSize();
    sprintf(lineString,"size=%ld",fsize);
    dispLine();
    isDone=true;
  }//endwhile
  prompt();
}

    s_substring(park,sizeof(park),recCommand,sizeof(recCommand),0,4);  // size
    if (s_compare(park,"size ") == 0) {
      isKnownCommand=true;
      s_substring(command,sizeof(command),recCommand,sizeof(recCommand),5,strlen(recCommand)-1);
      sdfilesize(command);
    }


void sdrmdir(const char *_s) {  // ----------------------------- RMDIR
  if (!sd.exists(_s)) {  // check if dir exists
    s_assign(lineString,"not found!",sizeof(lineString));
    dispLine();
  } else {
    if (sd.rmdir(_s)) {  // deletes dir
      s_assign(lineString,"removed",sizeof(lineString));
      dispLine();
    } else {
      s_assign(lineString,"not empty!",sizeof(lineString));
      dispLine();
    }
  }
  prompt();
}  // end sdrmdir()


void sdmkdir(const char *_s) {  // ----------------------------- MKDIR
  if (sd.exists(_s)) {  // check if dir already exists
    s_assign(lineString,"nm exists!",sizeof(lineString));
    dispLine();
  } else {
    if (sd.mkdir(_s)) {  // builds dir
      s_assign(lineString,"made",sizeof(lineString));
      dispLine();
    } else {
      s_assign(lineString,"nm error!",sizeof(lineString));
      dispLine();
    }
  }
  prompt();
}  // end sdmkdir()

    s_substring(park,sizeof(park),recCommand,sizeof(recCommand),0,5);  // mkdir
    if (s_compare(park,"mkdir ") == 0) {
      isKnownCommand=true;
      s_substring(command,sizeof(command),recCommand,sizeof(recCommand),6,strlen(recCommand)-1);
      sdmkdir(command);
    }
    s_substring(park,sizeof(park),recCommand,sizeof(recCommand),0,5);  // rmdir
    if (s_compare(park,"rmdir ") == 0) {
      isKnownCommand=true;
      s_substring(command,sizeof(command),recCommand,sizeof(recCommand),6,strlen(recCommand)-1);
      sdrmdir(command);
    }



*/

Credits

Marco Zonca
17 projects • 50 followers
"From an early age I learned to not use pointers"
Contact

Comments

Please log in or sign up to comment.