Skip to content
Snippets Groups Projects

Arduino networking code

Merged Kyrre Ness Sjobaek requested to merge arduinoServer into master
Compare and
4 files
+ 742
0
Compare changes
  • Side-by-side
  • Inline
Files
4
+ 556
0
/*
* Server for controling 4D robot for moving samples in/out of the beam at CLEAR.
* Access via telnet-like interface at port 23.
*
* Example for accessing over text interface:
* nc 192.168.1.31 23
* Netcat (nc) is the best client: it is a clean TCP client,
* which makes no attempts to negotiate anything.
*
* Kyrre Ness Sjobak, 24 April 2021
* University of Oslo / CERN
*
* Inspired by PositionGaugeServer, also in use at CLEAR.
*/
#include <Arduino.h>
#include <SPI.h>
#include <Ethernet.h>
#include <OneWire.h>
#include <DallasTemperature.h>
#include <Servo.h>
//This file holds all the configurations
// to be modified for deployment
#include "4DrobotServer_config.h"
// ***** GLOBAL VARIABLES ***********************
EthernetServer telnet_socket(telnet_port);
EthernetClient telnet_connection[telnet_numConnections];
//Buffers & buffCount (number of elements = idx of first free element)
char telnet_buff[telnet_numConnections][telnet_bufflen];
size_t telnet_buffCount[telnet_numConnections];
char serial_buff[telnet_bufflen];
size_t serial_buffCount;
char output_buff[output_bufflen];
size_t output_buffCount;
//Temperature sensor access & buffers
OneWire temp_oneWire(temp_oneWire_pin);
DallasTemperature temp_sensors(&temp_oneWire);
float temp_data[temp_numSensors]; //[degC]
unsigned long temp_update_prev = 0; //[ms]
//Grabber servo
Servo grabber_servo;
bool grabber_go = false;
int grabber_pos = grabber_closed; //Assumed initial position
int grabber_goto = grabber_closed;
// ***** CODE ***********************************
void setup() {
//Serial setup, for debugging
Serial.begin(9600);
// wait for serial port to connect. Needed for native USB port only
int serialCounter=0;
while (!Serial) {
//However only wait for 1 second before giving up,
// so that it can work also when not connected to USB
delay(100);
serialCounter++;
if (serialCounter > 10) break;
}
Serial.println();
Serial.println("4DrobotServer initializing...");
//Network setup
Serial.println("Starting networking...");
#ifdef USE_DHCP
Ethernet.begin(mac);
#else
Ethernet.begin(mac, ip, myDns, gateway, subnet);
#endif
// Check for Ethernet hardware present
if (Ethernet.hardwareStatus() == EthernetNoHardware) {
Serial.println("Ethernet shield was not found. Sorry, can't run without hardware. :(");
while (true) {
// Do nothing, no point running without Ethernet hardware
// TODO: Not actually true, we could also run with just serial...
delay(1);
}
}
while(true)
if (Ethernet.linkStatus() == LinkOFF) {
Serial.println("Ethernet cable is not connected");
}
else {
Serial.println("Ethernet cable connected / status unknown");
break;
}
byte buffMAC[6];
Serial.print("MAC address: ");
Ethernet.MACAddress(buffMAC);
for (byte octet=0; octet<6;octet++) {
Serial.print(buffMAC[octet],HEX);
if(octet < 5) {
Serial.print(":");
}
}
Serial.println();
Serial.print("IP address: "); Serial.println(Ethernet.localIP());
Serial.print("DNS address: "); Serial.println(Ethernet.dnsServerIP());
Serial.print("Gateway address: "); Serial.println(Ethernet.gatewayIP());
Serial.print("Subnet: "); Serial.println(Ethernet.subnetMask());
//Server setup
telnet_socket.begin();
for (uint8_t i=0; i<telnet_numConnections; i++) {
memset(telnet_buff[i], '\0', sizeof(telnet_buff[i]));
telnet_buffCount[i] = 0;
}
memset(serial_buff, '\0', sizeof(serial_buff));
serial_buffCount = 0;
memset(output_buff, '\0', sizeof(output_buff));
output_buffCount = 0;
//Initialize temperature sensors to something nonsensical
for (size_t i=0; i<temp_numSensors; i++) {
temp_data[i] = -500.0f; //(it's float not Farenheit)
}
//Serial is ready for input!
Serial.println("$");
}
void loop() {
//Run all the "programs" in order
//delay(1000);
temperatures_update();
telnet_server();
servo_control();
// ** Housekeeping **
#ifdef USE_DHCP
DHCPhousekeeping();
#endif
}
void temperatures_update() {
//Program for refreshing the temperatures
//Poll temperature sensors no faster than the given interval
//If too short, some sensors may heat up and give inaccurate results
unsigned long thisUpdateTime = millis();
if (not (thisUpdateTime - temp_update_prev >= temp_update_interval)) {
//Not yet
return;
}
temp_update_prev = thisUpdateTime;
//Refresh temp_data
#ifndef DUMMY_TEMP
temp_sensors.requestTemperatures();
for (size_t i=0; i<temp_numSensors; i++) {
temp_data[i] = temp_sensors.getTempCByIndex(i);
}
#else
for (size_t i=0; i< temp_numSensors; i++) {
temp_data[i] = random(-1000L,15000L)/100.0f;
}
#endif
}
void servo_control() {
//Program for controlling the servomotor for the grabber
if (grabber_go) {
for(int i=grabber_pos; i != grabber_goto; (grabber_pos < grabber_goto) ? i++ : i--) {
#ifndef DUMMY_SERVO
grabber_servo.write(i);
#else
Serial.print("Grabber to: ");
Serial.println(i);
#endif
delay(grabber_stepWait);
}
grabber_pos = grabber_goto;
grabber_go = false;
bufferWrite(output_buff, sizeof(output_buff), output_buffCount, F("< GRABBER GO FINISHED\n"));
}
}
void telnet_server() {
// Program for running the telnet- and serial communication
//1. Check for new connections
while (true) {
//Loop in case there are multiple new connections
EthernetClient newConnection = telnet_socket.accept();
if (newConnection) {
//Find a new connection slot
for (size_t i=0; i<telnet_numConnections; i++) {
if (!telnet_connection[i]) {
telnet_connection[i] = newConnection;
bufferWrite(output_buff, sizeof(output_buff), output_buffCount, F("< INFO CONN MAKE: "));
IPAddress IP = telnet_connection[i].remoteIP();
char IP_buff[16];
snprintf(IP_buff, sizeof(IP_buff), "%d.%d.%d.%d", IP[0],IP[1],IP[2],IP[3]);
bufferWrite(output_buff, sizeof(output_buff), output_buffCount, IP_buff);
snprintf(IP_buff, sizeof(IP_buff), " ON #%-2d\n", i);
bufferWrite(output_buff, sizeof(output_buff), output_buffCount, IP_buff);
telnet_connection[i].println("$");
break;
}
}
}
else {
break;
}
}
//2. Check for incoming data (telnet & serial) and buffer it
for (size_t i = 0; i < telnet_numConnections; i++) {
while (telnet_connection[i] &&
telnet_connection[i].connected() &&
telnet_connection[i].available() > 0 ) {
bufferWrite(telnet_buff[i], sizeof(telnet_buff[i]), telnet_buffCount[i], telnet_connection[i].read());
}
}
while (Serial.available() > 0) {
bufferWrite(serial_buff, sizeof(serial_buff), serial_buffCount, Serial.read());
}
//3. Feed the clients & serial with the content of the output buffer
if(output_buffCount) {
for (size_t i = 0; i < telnet_numConnections; i++) {
if (telnet_connection[i] &&
telnet_connection[i].connected() ) {
telnet_connection[i].print(output_buff);
}
}
Serial.print(output_buff);
memset(output_buff, '\0', sizeof(output_buff));
output_buffCount = 0;
}
//4. Check for telnet disconnects & handle
for (size_t i = 0; i < telnet_numConnections; i++) {
if ( telnet_connection[i] &&
!telnet_connection[i].connected() ) {
bufferWrite(output_buff, sizeof(output_buff), output_buffCount, F("< INFO CONN BREAK: "));
IPAddress IP = telnet_connection[i].remoteIP();
char IP_buff[16];
snprintf(IP_buff, sizeof(IP_buff), "%d.%d.%d.%d", IP[0],IP[1],IP[2],IP[3]);
bufferWrite(output_buff, sizeof(output_buff), output_buffCount, IP_buff);
snprintf(IP_buff, sizeof(IP_buff), " ON #%-2d\n", i);
bufferWrite(output_buff, sizeof(output_buff), output_buffCount, IP_buff);
telnet_connection[i].stop();
memset(telnet_buff[i],'\0',sizeof(telnet_buff[i]));
telnet_buffCount[i]=0;
}
}
//5. Parse input buffers & notify that we are ready for more
for (size_t i = 0; i < telnet_numConnections; i++) {
input_parser(telnet_buff[i], sizeof(telnet_buff[i]), telnet_buffCount[i], &(telnet_connection[i]));
}
input_parser(serial_buff, sizeof(serial_buff), serial_buffCount, &(Serial));
}
void input_parser(char* in, const size_t buffSize, size_t& buffCount, Stream* originatingStream) {
// Attempt to parse the commands in the buffer.
// It will only attempt to parse commands that end with a '\n', '\r', or a combination thereof
//1. Sanity-check the buffer size
// (Note: Last entry is always a \0, buffSize cannot point to it)
if (buffCount == buffSize-1) {
originatingStream->println(F("@ INFO BUFFER FULL"));
originatingStream->println(F("@ INFO BUFFER RESET"));
memset(in,'\0', buffSize);
buffCount=0;
// Ready for more
originatingStream->println("$");
return;
}
//2. Look for whole lines & parse each of them in order
char line_buff[buffSize];
memset(line_buff,'\0', sizeof(line_buff));
size_t line_next = 0; //Index of the first valid character
bool lastchar_was_newline = true;
bool gotCommand = false; //Print a `$` when done, after having run commands
for (size_t i = 0; i<buffCount; i++) {
if(in[i] == '\0') {
//Sanity check: There should be no NULL before the end of the buffer `in`
originatingStream->println(F("@ INFO BUFFER NULL"));
originatingStream->println(F("@ INFO BUFFER RESET"));
memset(line_buff,'\0', sizeof(line_buff));
memset(in,'\0', buffSize);
line_next = 0;
buffCount = 0;
// Ready for more
originatingStream->println("$");
return;
}
else if (lastchar_was_newline) {
//Skip repeated newlines, and place line_next correctly
line_next = i;
if(in[i] != '\n' and in[i] != '\r') {
//Double newline or newline at start
lastchar_was_newline = false;
}
}
else if (in[i] == '\n' or in[i] == '\r') {
//Newline not following start or another newline
memcpy(line_buff, in+line_next, i-line_next);
line_buff[i-line_next]='\0';
parse_line(line_buff);
lastchar_was_newline = true;
//Note: line_next is valid before lastchar_was_newline is false
line_next = i;
memset(line_buff,'\0', sizeof(line_buff));
gotCommand = true;
}
//Else: Just keep counting chars
}
//3. Prepare buffers for next round
if (lastchar_was_newline && buffCount > 0) {
// All data was parsed; reset buffer and continue
if(not in[line_next+1] == '\0') {
//Internal error!
originatingStream->println(F("@ INFO BUFFER UNTERMINATED"));
originatingStream->println(F("@ INFO BUFFER RESET"));
//TODO:
// This may indicate a serious bug (array overrun)
// consider resetting the CPU!
}
//All treated, let's reset
memset(line_buff,'\0', sizeof(line_buff));
memset(in,'\0', buffSize);
buffCount = 0;
line_next = 0;
// Ready for more
originatingStream->println("$");
}
else if (not lastchar_was_newline && line_next>0) {
// Shift remaining content of `in` to start of `in`
size_t j = 0;
for(size_t i = line_next; i < buffCount; i++) {
in[j] = in[i];
j += 1;
}
memset(in+j,'\0', buffSize-j);
buffCount = j;
}
}
void parse_line(char* line_buff) {
bufferWrite(output_buff, sizeof(output_buff), output_buffCount, "> ");
bufferWrite(output_buff, sizeof(output_buff), output_buffCount, line_buff);
bufferWrite(output_buff, sizeof(output_buff), output_buffCount, '\n');
size_t len = strlen(line_buff);
if (len >= 4 and strncmp(line_buff, "HELP", 4)==0) {
bufferWrite(output_buff, sizeof(output_buff), output_buffCount, F("< USAGE:\n"));
bufferWrite(output_buff, sizeof(output_buff), output_buffCount, F("< 'HELP' : Get help\n"));
bufferWrite(output_buff, sizeof(output_buff), output_buffCount, F("< 'TEMP' : Get temperature(s)\n"));
bufferWrite(output_buff, sizeof(output_buff), output_buffCount, F("< 'GRABBER STATUS' : Get grabber status\n"));
bufferWrite(output_buff, sizeof(output_buff), output_buffCount, F("< 'GRABBER POS int' : Goto the given position (integer)\n"));
bufferWrite(output_buff, sizeof(output_buff), output_buffCount, F("< 'GRABBER CLOSE' : Close the grabber\n"));
bufferWrite(output_buff, sizeof(output_buff), output_buffCount, F("< 'GRABBER OPEN' : Open the grabber\n"));
}
else if (len >= 4 and strncmp(line_buff, "TEMP", 4)==0) {
for (size_t i=0; i<temp_numSensors; i++) {
char buff[23];
float f = temp_data[i];
int tmp_int = (int) (f*100);
int tmp_dec = abs(tmp_int-tmp_int/100*100);
tmp_int = tmp_int/100;
snprintf(buff, sizeof(buff), "< TEMP #%03d %+04d.%02d C\n", i, tmp_int,tmp_dec);
bufferWrite(output_buff, sizeof(output_buff), output_buffCount, buff);
}
}
else if (len >= 7 and strncmp(line_buff, "GRABBER", 7)==0) {
//Note: Not writing out the sub-options to save RAM
if (len >= 14 and strncmp(line_buff+7, " STATUS", 7)==0) { //'GRABBER STATUS'
bufferWrite(output_buff, sizeof(output_buff), output_buffCount, F("< POS = "));
char buff[5];
snprintf(buff,sizeof(buff), "%3d\n", grabber_pos);
bufferWrite(output_buff, sizeof(output_buff), output_buffCount, buff);
}
else if (len >= 13 and strncmp(line_buff+7, " CLOSE", 6)==0) { //'GRABBER CLOSE'
if(grabber_go) {
bufferWrite(output_buff, sizeof(output_buff), output_buffCount, F("< ERROR GRABBER ALREADY SET"));
}
grabber_goto = grabber_closed;
grabber_go = true;
}
else if (len >= 12 and strncmp(line_buff+7, " OPEN", 5)==0) { //'GRABBER OPEN'
if(grabber_go) {
bufferWrite(output_buff, sizeof(output_buff), output_buffCount, F("< ERROR GRABBER ALREADY SET"));
}
grabber_goto = grabber_open;
grabber_go = true;
}
else {
bufferWrite(output_buff, sizeof(output_buff), output_buffCount, F("< ERROR GRABBER COMMAND NOT RECOGNIZED\n"));
}
}
else {
bufferWrite(output_buff, sizeof(output_buff), output_buffCount, F("< ERROR COMMAND NOT RECOGNIZED\n"));
}
}
int bufferWrite(char* buff, const size_t buffSize, size_t& buffCount, const char* in) {
/* Add a zero-terminated string to a buffer,
* checking for buffer overruns and
* (SIDE EFFECT!) incrementing the buffCount.
* If the buffer will overrun, truncate the input string.
* Return number of chars written, or return <0 on error.
* If buffer overrun, chars may still be written -> positive return.
*/
size_t len = strlen(in);
int ret = snprintf(buff+buffCount, buffSize-buffCount, in);
buffCount += ret;
return ret;
}
int bufferWrite(char* buff, const size_t buffSize, size_t& buffCount, const char in) {
/* Add a single char to a buffer,
* checking that there is space for it and
* (SIDE EFFECT!) incrementing the buffCount.
* Returns the number of chars written (0 or 1).
*/
if (buffCount == buffSize-1) {
//Pointing at terminating \0,
// no more space!
return 0;
}
else {
buff[buffCount] = in;
buffCount++;
return 1;
}
}
int bufferWrite(char* buff, const size_t buffSize, size_t& buffCount, const __FlashStringHelper *ifsh) {
//Copy flash contents into buffer, otherwise functionally identical to the const char* version.
// Used to save RAM from big hardcoded strings.
//
// Based on ArduinoCore-avr/cores/arduino/Print.cpp->Print::Print(const __FlashStringHelper *ifsh)
// If it doesn't work (future arch), try removing the 'F()' around the strings and comment out this function.
PGM_P p = reinterpret_cast<PGM_P>(ifsh);
size_t n = 0;
while (1) {
char c = pgm_read_byte(p++);
if (c == 0) break;
if (bufferWrite(buff, buffSize, buffCount, c)) n++;
else break;
}
return n;
}
#ifdef USE_DHCP
void DHCPhousekeeping() {
switch (Ethernet.maintain()) {
case 1:
//renewed fail
Serial.println(F("Error: renewed fail"));
break;
case 2:
//renewed success
Serial.println(F("Renewed success"));
//print your local IP address:
Serial.print(F("My IP address: "));
Serial.println(Ethernet.localIP());
break;
case 3:
//rebind fail
Serial.println(F("Error: rebind fail"));
break;
case 4:
//rebind success
Serial.println(F("Rebind success"));
//print your local IP address:
Serial.print(F("My IP address: "));
Serial.println(Ethernet.localIP());
break;
default:
//nothing happened
break;
}
}
#endif
Loading