I have an unused TV aerial rotator and would be good to put it, along with an motorized elevation control, for some satellite work.
Googling around for some Arduino controller code I found from fellow ham YO3RAK at: https://racov.ro/index.php/2020/12/09/arduino-based-antenna-rotator-part3-software-tracking-update/
and:
https://create.arduino.cc/projecthub/viorelracoviteanu/antenna-rotator-controller-compatible-with-tracking-software-48f9cd#code
a nice bit of a project.
Compiled it along with some LCD library changes and noted that was not fully working with rotctld (Hamlib 1.2.15.3) and gredict (1.3) on Linux. Long story short I ended up starting my own code, half way trough discovered what looks like a different implementation or EasyComm II protocol on hamlib in Linux depending on version.
At some point trough my code I reverted to do changes on YO3RAK code to accommodate my setup, a mix of hamlib 1.2.15.3 and 3.1 along with gpredict 1.3 and 2.0-4. Now works fine on both implementations in Linux.
Since I've seen some posts with similar issues here's what I got while testing regarding behaviour:
hamlib 1.2.15:
Query position: "!"
Return: "TM0 AZ<AZIM>0.0 EL<ELEVATION>0.0"
See "0.0" appended, that sends 10x more angle, else gpredict will receive angle/10
hamlib 3.1:
Query position: "AZ EL ."
Return: "AZ<AZIM>.0 EL<ELEVATION>.0"
Here only appending the decimal point and works correctly on gpredict 2.0-4.
To start the process, besides having the the Arduino rotator controller you need to start rotctld similar to:
rotctld -m 202 -r /dev/ttyUSB0 -s 9600 -T 192.168.0.14 -t 4533
where 192.168.0.14 will be your computer IP address, rotctld will listen on port 4533 for rotator commands from gpredict. /dev/ttyUSB0 will be the virtual USB port of the Arduino/rotator controller device. So, in simple terms, rotctld connects to the Arduino and listens on IP commands that gpredict sends to control the rotator.
Some images of gpredict and rotctld in action along wiht the Arduino code:
To configure gpredict will be similar to this:
To connect to the rotator, this way:
I still don't have the hardware, will update when done.
The code bellow, since blogspot breaks a bit on the formating send me and email if you need a file with the code, file is: original_ant_rotator_easycom2_aug2021_mod_ct2gqv_v3.ino
/// code start
/* AZ/EL Antenna Rotator controller for Arduino - DC motors
* ========================================================
* Uses EasyComm protocol for computer - Tracking Software
* Manual command by means of two rotary encoders AZ - EL
*
* Viorel Racoviteannu
* https://www.youtube.com/channel/UCiRLZX0bV9rS04BGAyUf-fA
* https://racov.ro
* YO3RAK@gmail.com
*
* I cannot take any responsibility for missuse of this code
* or any kind of damage it may occur from using this code.
*
* dec 2020 v2 - improved serial comm stability
* january 2021 - improved near target dead-zone, for which antenna won't move
* apr 2021 - improved serial comm stability
* jun 2021 - error proportional power for tracking movement
* aug 2021 - faster USB update, cold switching Az/El direction, small optimizations in the code
*
* Mods by CT2GQV bellow
* 2021-10-31 added code to accept AZ EL as query since Hamlib 3.1 sends it, while Hamlib 1.2.15.3 sends the "!"
* 2021-10-30 Original code moded to acept the "!" from Hamlib 1.2.15.3 using gpredict in 1.3 Linux
* to include parsing of rotctld commands from Linux via gpredict.
* gpredict will connect to "rotator" (rotctld) with IP 192.168.0.14 (example)
* and rotctld will listen to IP 192.168.0.14 and send commands for the arduino rotator
* controler (from YO3RAK with mod CT2GQV) * via serial USB
* ex: rotctld -m 202 -r /dev/ttyUSB0 -s 9600 -T 192.168.0.14 -t 4533.
* ex: rotctld -m 202 -r /dev/<arduino usb> -s 9600 -T <your IP address to listen> -t <port to listen>
*
*
*/
#include <Wire.h> // Library for I2C communication
#include <LiquidCrystal_I2C.h> // Library for LCD
// Wiring: SDA pin is connected to A4 and SCL pin to A5.
// Connect to LCD via I2C, default address 0x27 (A0-A2 not jumpered)
// LiquidCrystal_I2C lcd(0x27, 16, 2); // address, chars, rows.
// CT2GQV mod
LiquidCrystal_I2C lcd(0x27,2,1,0,4,5,6,7); // 0x27 is the default I2C bus address (this could be different for some modules)
// how much serial data we expect from rotctld before a newline
const unsigned int MAX_INPUT = 14; // "AZ360.0 EL90.0", that is: 14 char's
int soft_az,soft_el ; // to hold the azimuth and elevation values sent from the gpredict via rotctld software
// end mod CT2GQV
// declaring custom symbol for up/down arrow
byte DownArrow[8] = {
B00000,
B00100,
B00100,
B00100,
B10101,
B01110,
B00100,
B00000
};
byte UpArrow[8] = {
B00000,
B00100,
B01110,
B10101,
B00100,
B00100,
B00100,
B00000
};
/***********************************THIS IS WHERE YOU REALY TWEAK THE ANTENNA MOVEMENT***************/
// ANTENNA potentiometers CALIBRATION
int AzMin = 1; //begining of the potentiometer
int AzMax = 1023; //end of the potentiometer
int ElMin = 1;
int ElMax = 1023;
// Allowed error for which antennna won't move. Minimum 1 degree
int AzErr = 9;
int ElErr = 4;
// Angle difference where soft stop begins
int Amax = 25; //azimuth
int Emax = 15; //elevation
// min and max power for motors, percents;
int PwAzMin = 40; //minimum power for which the motor doesn't stall and starts under load
int PwAzMax = 100; //full power for the fastest speed
int PwElMin = 30;
int PwElMax = 100;
int PwAz = 0; //calculated power to be transmitted to motor (percents);
int PwEl = 0;
/***************************************************************************************************/
// Azim encoder variables
enum AzPinAssignments {
AzEncoderPinA = 2, // encoder right
AzEncoderPinB = 3, // encoder left
AzClearButton = 4}; // encoder push
unsigned int lastReportedPos = 1; // change management
static boolean rotating = false; // debounce management
// interrupt service routine vars
boolean A_set = false;
boolean B_set = false;
//Elev encoder variables
enum ElPinAssignments{
ElEncoderPinA = 6, // encoder right
ElEncoderPinB = 5, // encoder left
ElClearButton = 7}; // encoder push
int aState;
int aLastState;
// other variables
int AzPotPin = A0; // select the input pin for the azim. potentiometer
int AzRotPin = 12; // select the out pin for rotation direction
int AzPWMPin = 11; // select the out pin for azimuth PWM command
int TruAzim = 0; // calculated real azimuth value
int ComAzim = 0; // commanded azimuth value
int OldTruAzim = 0; // to store previous azimuth value
int OldComAzim = 0;
char AzDir; // symbol for azim rot display
int AzEncBut = 1; // variable to toggle with encoder push button
int ElPotPin = A1; // select the input pin for the elev. potentiometer
int ElRotPin = 13; // select the out pin for elevation rotation direction
int ElPWMPin = 10; // select the out pin for elevation rotation PWM command
int TruElev = 0; // calculated real elevation value
int ComElev = 0; // commanded elevation value
int OldTruElev = 0; // to store previous elevation value
int OldComElev = 0;
char ElDir; // symbol for elev. rot display
// flags for AZ, EL tolerances
bool AzStop = false;
bool ElStop = false;
int ElUp = 0; // 1 = Elevation Dn, 0 = Elevation STOP, 2 = Elevation Up
//averaging loop
const int numReadings = 25;
int readIndex = 0; // the index of the current reading
int azimuth[numReadings]; // the readings from the analog input
int elevation[numReadings];
int totalAz = 0; // the running total
int totalEl = 0;
// variables for serial comm
String Azimuth = "";
String Elevation = "";
String ComputerRead;
String ComputerWrite;
bool AZser = false;
bool ELser = false;
bool ANTser = false;
/*************** END VARIABLE DECLARATION ************/
void setup() {
Serial.begin(9600);
Serial.setTimeout(50); // miliseconds to wait for USB sata. Default 1000
// Initiate the LCD:
// lcd.begin(16,2);
// lcd.init();
// lcd.backlight();
// LCD initialization
lcd.begin(16, 2); // LCD set for 16 by 2 display
lcd.setBacklightPin(3,POSITIVE); // (BL, BL_POL)
lcd.setBacklight(HIGH); // LCD backlight turned ON
lcd.clear();
//creating custom symbol for up/dwn arrow
lcd.createChar(1, DownArrow);
lcd.createChar(2, UpArrow);
// pin declaration
pinMode(AzRotPin, OUTPUT); //declaring azim. rotation direction Pin as OUTPUT
pinMode(AzPWMPin, OUTPUT); //declaring azimuth PWM command Pin as OUTPUT
pinMode(ElRotPin, OUTPUT); //declaring elev. rotation direction Pin as OUTPUT
pinMode(ElPWMPin, OUTPUT);
pinMode(AzPotPin, INPUT);
pinMode(ElPotPin, INPUT);
pinMode(AzEncoderPinA, INPUT);
pinMode(AzEncoderPinB, INPUT);
pinMode(AzClearButton, INPUT);
pinMode(ElEncoderPinA, INPUT);
pinMode(ElEncoderPinB, INPUT);
pinMode(ElClearButton, INPUT);
// AzEncoder pin on interrupt 0 (pin A)
attachInterrupt(0, doEncoderA, CHANGE);
// AzEncoder pin on interrupt 1 (pin B)
attachInterrupt(1, doEncoderB, CHANGE);
// Reads the initial state of the ElEncoderPinA
aLastState = digitalRead(ElEncoderPinA);
// write on display name and version
lcd.setCursor(0, 0); // Set the cursor on the first column first row.(counting starts at 0!)
lcd.print("EasyCom AntRotor"); // display "..."
lcd.setCursor(0, 1); // Set the cursor on the first column the second row
// changed to reflect mod by CT2GQV
lcd.print("*YO3RAK/CT2GQV*");
delay(2000); // keep for 2 seconds
// display Azim. and Elev. values
lcd.setCursor(0, 0);
lcd.print("Azm.---" + String(char(223)) + "=Cd.---" + String(char(223))); // char(223) is degree symbol
lcd.setCursor(0, 1);
lcd.print("Elv. --" + String(char(223)) + "=Cd. --" + String(char(223)));
/* initialization of the averaging loop */
// this is to set azim-command the same value as real, not to jerk the antenna at start-up
TruAzim = (map(analogRead(AzPotPin), AzMin, AzMax, 0, 359)); // azimuth value 0-359
if (TruAzim<0) {TruAzim=0;}
if (TruAzim>359) {TruAzim=359;} // keep values between limits
TruElev = (map(analogRead(ElPotPin), ElMin, ElMax, 0, 90)); // elev value 0-90
if (TruElev<0) {TruElev=0;}
if (TruElev>90) {TruElev=90;} // keep values between limits
// initialize all the readings
for (int thisReading = 0; thisReading < numReadings; thisReading++) {
azimuth[thisReading] = 0;
elevation[thisReading] = 0;
}
ComAzim = TruAzim;
ComElev = TruElev;
OldTruAzim = TruAzim;
OldComAzim = ComAzim;
OldTruElev = TruElev;
OldComElev = TruElev;
DisplAzim(TruAzim, 4,0);
DisplAzim(ComAzim,12,0);
DisplElev(TruElev, 5,1);
DisplElev(ComElev,13,1);
}
// end SETUP
// here to process incoming serial data after a terminator received
void process_data (const char * data) // ct2gqv
{
char data1[3]="", data2[3]=""; // to hold azimuth and elevation sent by gpredict/rotctld
int j = 0, k = 0 ; // for counters to insert in data1 and data2
int select = 1; // holding which data to insert the character
// for AZxxx ELxxx command but will also try to parse something like "AZ EL." so we don't do it if data[3] == 'E'
if (data[0] == 'A' && data[1] == 'Z' && data[3] != 'E'){ // we wait for the AZ command, it comes similar to AZ3599.0 EL900.0
for (int i = 0; i < MAX_INPUT; i++)
{
char x = data[i];
if (x == ' ') {select = 2;}; // we are done, we had the first space and means we are on to the second value for azimuth
if (x == '.') {select = 3;}; // we have a dot so we don't parse the data1 until we have the previous wich is a spce then we do data2
if (isdigit(x) == 0)
{
// do nothing only if needed to get AZ or EL string's
}
else // it's a number
{
if (select == 1) { // holds the azimuth characters until the dot
data1[k] = x;
k++; };
if (select == 2) { // holds the elevation
data2[j] = x;
j++; };
}
}; // end of if loop to separate AZ and EL from the rotator command software
soft_az = atoi(data1);
soft_el = atoi(data2);
// we need to send now the data to the original code
ComAzim = soft_az;
ComElev = soft_el;
};
// for "AZ EL ." command of query position on gpredict 2.0-4 since it's diferent from gpredict 1.3 (we only care about knowing if "AZ EL" was received)
if (data[0] == 'A' && data[1] == 'Z' && data[3] == 'E' && data[4] == 'L'){
// removed the "TM0" (ComputerWrite = "TM0 AZ...") on the ComputerWrite string for gpredict 2.0-4 , also on gpredict 2.0-4 the x10 of the el/az looks fixed so removed
// the adding of "0.0" to the sent position and elevation, now only decimal dedgrees is send ".0"
ComputerWrite = "AZ"+String(TruAzim)+".0 EL"+String(TruElev)+".0"; // need to be x10 (add 0.0) or gpredict 1.3 will read divide by 10 (might be rotctld causing it also).
Serial.println(ComputerWrite);
};
// for "SA SE ." command of stop azimuth and stop postion.... blank for now.. need to study betther the code
if (data[0] == 'S' && data[1] == 'A' && data[3] == 'S' && data[4] == 'E'){
// how should we stop here ? and should we report something ? Should we apply breaks on rotator ?
// for now we do nothing.....to be continued...
};
} // end void process_data
void processIncomingByte (const byte inByte) // CT2GQV
{
static char input_line [MAX_INPUT];
static unsigned int input_pos = 0;
switch (inByte)
{
// for gpredict 1.3 w report the position here, there is also a factor of 10 that needs to be send, so it's "0.0" apended
// this is on Hamlib 1.2.15.3 but diferent on Hamlib 3.1 where it sends "AZ EL"
case '!': // rotctld when sending "!" is asking rotator position so let's send it
ComputerWrite = "TM0 AZ"+String(TruAzim)+"0.0 EL"+String(TruElev)+"0.0"; // need to be x10 (add 0.0) or gpredict 1.3 will read divide by 10 (might be rotctld causing it also).
Serial.println(ComputerWrite);
break;
case '\n': // end of text
input_line [input_pos] = 0; // terminating null byte
// terminator reached! process input_line here ...
process_data (input_line);
// reset buffer for next time
input_pos = 0;
break;
case '\r': // discard carriage return
break;
default:
// keep adding if not full ... allow for terminating null byte
if (input_pos < (MAX_INPUT - 1))
input_line [input_pos++] = inByte;
break;
} // end of switch
} // end of processIncomingByte
void loop() {
/************** FYI, this loop repeats 500 times per second !!! **************/
// AZIMUTH/ELEVATION AVERAGING LOOP
// subtract the oldest value
totalAz = totalAz - azimuth[readIndex];
totalEl = totalEl - elevation[readIndex];
// read from the sensor:
azimuth[readIndex] = (map(analogRead(AzPotPin), AzMin, AzMax, 0, 359));
elevation[readIndex] = (map(analogRead(ElPotPin), ElMin, ElMax, 0, 90));
// add the reading to the total:
totalAz = totalAz + azimuth[readIndex];
totalEl = totalEl + elevation[readIndex];
// do the average
TruAzim = totalAz / numReadings;
TruElev = totalEl / numReadings;
// keep values between limits
if (TruAzim<0) {TruAzim=0;}
if (TruAzim>359) {TruAzim=359;}
if (TruElev<0) {TruElev=0;}
if (TruElev>90) {TruElev=90;}
// advance to the next position in the array:
readIndex = readIndex + 1;
// if we're at the end of the array, wrap around to the beginning:
if (readIndex >= numReadings) {readIndex = 0;}
// this is to read the command from encoder
ReadAzimEncoder();
ReadElevEncoder();
// original code
// if (Serial.available()) {SerComm();} // read USB data
while (Serial.available () > 0) // CT2GQV
processIncomingByte (Serial.read ());
// update antenna position display only if value change
if ((millis()%500)<10){ // not to flicker the display
if (OldTruAzim!=TruAzim) {
DisplAzim(TruAzim,4,0);
OldTruAzim = TruAzim;
}
if (OldTruElev!=TruElev) {
DisplElev(TruElev,5,1);
OldTruElev = TruElev;
}
}
// update target position display only if value change
if (OldComAzim != ComAzim) {
DisplAzim(ComAzim,12,0);
OldComAzim = ComAzim;
}
if (OldComElev != ComElev) {
DisplElev(ComElev,13,1);
OldComElev = ComElev;
}
// this is to rotate in azimuth
if (TruAzim == ComAzim) { // if equal, stop moving
AzStop = true;
analogWrite(AzPWMPin, 0);
lcd.setCursor(8, 0);
lcd.print("=");
}
else if ((abs(TruAzim - ComAzim)<=AzErr)&&(AzStop == false)) { // if in tolerance, but it wasn't an equal, rotate
AzimRotate();}
else if (abs(TruAzim - ComAzim)>AzErr){ // if target is off tolerance
AzStop = false; // it's not equal
AzimRotate(); // rotate
}
// this is to rotate in elevation
if (TruElev == ComElev) { // if equal, stop moving
ElStop = true;
analogWrite(ElPWMPin, 0);
lcd.setCursor(8, 1);
lcd.print("=");
ElUp = 0; // flag for elevation STOP
}
else if ((abs(TruElev - ComElev)<=ElErr)&&(ElStop == false)) { // if in tolerance, but it wasn't an equal, rotate
ElevRotate();}
else if (abs(TruElev - ComElev)>ElErr){ // if target is off tolerance
ElStop = false; // it's not equal
ElevRotate(); // rotate
}
// this is to interpret Az encoder x10 multiplication
while (AzEncBut == 10) { // while toggled to x10
analogWrite(AzPWMPin, 0); // STOP antenna rotation
analogWrite(ElPWMPin, 0);
lcd.setCursor(8, 0);
lcd.print("*");
ReadAzimEncoder();
if (OldComAzim != ComAzim){ // update display only if numbers change
DisplAzim(ComAzim, 12, 0);
OldComAzim = ComAzim;
}
delay (100);
}
}
// end main LOOP
//____________________________________________________
// ___________procedures definitions__________________
void DisplAzim(int x, int y, int z) {
char displayString[7] = "";
sprintf(displayString, "%03d", x); //outputs a fixed lenght number (3 integer)
lcd.setCursor(y, z); // for no leading zeros "__7" use "%3d"
lcd.print(displayString);
// ************** FOR CALIBRATION PURPOSES **************
// Serial.print ("Az ");
// Serial.println (analogRead(AzPotPin));
}
void DisplElev(int x, int y, int z){
char displayString[7] = "";
sprintf(displayString, "%02d", x); //outputs a fixed lenght number (2 integer)
lcd.setCursor(y, z); // for no leading zeros "_7" use "%2d"
lcd.print(displayString);
// ************** FOR CALIBRATION PURPOSES **************
// Serial.print ("El ");
// Serial.println (analogRead(ElPotPin));
}
void ReadElevEncoder() {
aState = digitalRead(ElEncoderPinA); // Reads the "current" state of the ElEncoderPinA
// If the previous and the current state of the ElEncoderPinA are different, that means a Pulse has occured
if (aState != aLastState){
// If the ElEncoderPinB state is different to the ElEncoderPinA state, that means the encoder is rotating clockwise
if (digitalRead(ElEncoderPinB) != aState) { ComElev ++;}
else { ComElev --;}
if (ComElev <0) {ComElev = 0;}
if (ComElev >90) {ComElev = 90;}
}
aLastState = aState; // Updates the previous state of the ElEncoderPinA with the current state
}
void ReadAzimEncoder() {
rotating = true; // reset the debouncer
if (lastReportedPos != ComAzim) {
lastReportedPos = ComAzim;
}
delay(10);
if (digitalRead(AzClearButton) == LOW ) { // if encoder switch depressed
delay (250); // debounce switch
if (AzEncBut == 1){
AzEncBut = 10;
ComAzim = int(ComAzim/10)*10; // ComAzim in 10deg. steps
}
else {
AzEncBut = 1;
}
}
} //end ReadAzimEncoder()
// Interrupt on A changing state
void doEncoderA() {
// debounce
if ( rotating ) delay (1); // wait a little until the bouncing is done
// Test transition, did things really change?
if ( digitalRead(AzEncoderPinA) != A_set ) { // debounce once more
A_set = !A_set;
// adjust counter + if A leads B
if ( A_set && !B_set )
ComAzim += AzEncBut;
ComAzim = ((ComAzim + 360) % 360); // encoderPos between 0 and 359 deg.
rotating = false; // no more debouncing until loop() hits again
}
}
// Interrupt on B changing state, same as A above
void doEncoderB() {
if ( rotating ) delay (1);
if ( digitalRead(AzEncoderPinB) != B_set ) {
B_set = !B_set;
// adjust counter - 1 if B leads A
if ( B_set && !A_set )
ComAzim -= AzEncBut;
ComAzim = ((ComAzim + 360) % 360); // encoderPos between 0 and 359 deg.
rotating = false;
}
}
void AzimRotate() {
if ((ComAzim-TruAzim) > (TruAzim-ComAzim)) { // this to determine direction of rotation
// cold switching - stop motor before changing direction - to protect mechanic and electric parts
if (AzDir == char(127)) { // if previously rotating in the oposite direction
analogWrite(AzPWMPin, 0); // STOP the motor
delay(400); // pre-switch delay
digitalWrite(AzRotPin, LOW); // deactivate rotation pin - rotate right
delay(200); // post-switch delay
}
else { // same directin, no Stop, no delay
digitalWrite(AzRotPin, LOW); // deactivate rotation pin - rotate right
}
AzDir = char(126); // "->"
}
else {
if (AzDir == char(126)) { // if previously rotating in the oposite direction
analogWrite(AzPWMPin, 0); // STOP the motor
delay(400); // pre-switch delay
digitalWrite(AzRotPin, HIGH); // activate rotation pin - rotate left
delay(200); // post-switch delay
}
else { // same directin, no Stop, no delay
digitalWrite(AzRotPin, HIGH); // activate rotation pin - rotate left
}
AzDir = char(127); // "<-"
}
lcd.setCursor(8, 0);
lcd.print(String(AzDir));
// this activates azim PWM pin proportional with angle error
PwAz = PwAzMin + round((abs(ComAzim-TruAzim))*(PwAzMax-PwAzMin)/(Amax-AzErr)); //formula which outputs a power proportional with angle difference
if (PwAz > 100) {PwAz = 100;}
analogWrite(AzPWMPin, round(2.55*PwAz)); // activate Azim drive PWM pin
}
void ElevRotate() {
// this to determine direction of rotation
if ((ComElev-TruElev) > (TruElev-ComElev)) {
if (ElUp == 1) { // if previously rotating in the oposite direction
analogWrite(ElPWMPin, 0); // STOP the motor
delay(400); // pre-switch delay
digitalWrite(ElRotPin, LOW); // deactivate rotation pin - rotate UP
delay(200); // post-switch delay
}
else { // same directin, no Stop, no delay
digitalWrite(ElRotPin, LOW); // deactivate rotation pin - rotate UP
}
lcd.setCursor(8, 1);
lcd.write(2); // arrow up
ElUp = 2;
}
else {
if (ElUp == 2) { // if previously rotating in the oposite direction
analogWrite(ElPWMPin, 0); // STOP the motor
delay(400); // pre-switch delay
digitalWrite(ElRotPin, HIGH); // deactivate rotation pin - rotate UP
delay(200); // post-switch delay
}
else { // same directin, no Stop, no delay
digitalWrite(ElRotPin, HIGH); // deactivate rotation pin - rotate UP
}
lcd.setCursor(8, 1);
lcd.write(1); // arrow down
ElUp = 1;
}
// this activates elev PWM pin proportional with angle error
PwEl = PwElMin + round((abs(ComElev-TruElev))*(PwElMax-PwElMin)/(Emax-ElErr)); //formula which outputs a power proportional with angle difference
if (PwEl > 100) {PwEl = 100;}
analogWrite(ElPWMPin, round(2.55*PwEl)); // activate Elev drive PWM pin
}
/* removed by CT2GQV
void SerComm() {
// initialize readings
ComputerRead = "";
Azimuth = "";
Elevation = "";
while(Serial.available()) {
ComputerRead= Serial.readString(); // read the incoming data as string
// Serial.println(ComputerRead); // echo the reception for testing purposes
}
// looking for command <AZxxx.x>
for (int i = 0; i <= ComputerRead.length(); i++) {
if ((ComputerRead.charAt(i) == 'A')&&(ComputerRead.charAt(i+1) == 'Z')){ // if read AZ
for (int j = i+2; j <= ComputerRead.length(); j++) {
if (isDigit(ComputerRead.charAt(j))) { // if the character is number
Azimuth = Azimuth + ComputerRead.charAt(j);
}
else {break;}
}
}
}
// looking for command <ELxxx.x>
for (int i = 0; i <= (ComputerRead.length()-2); i++) {
if ((ComputerRead.charAt(i) == 'E')&&(ComputerRead.charAt(i+1) == 'L')){ // if read EL
if ((ComputerRead.charAt(i+2)) == '-') {
ComElev = 0; // if elevation negative
break;
}
for (int j = i+2; j <= ComputerRead.length(); j++) {
if (isDigit(ComputerRead.charAt(j))) { // if the character is number
Elevation = Elevation + ComputerRead.charAt(j);
}
else {break;}
}
}
}
// if <AZxx> received
if (Azimuth != ""){
ComAzim = Azimuth.toInt();
ComAzim = ComAzim%360; // keeping values between limits(for trackers with more than 360 deg. rotation)
}
// if <ELxx> received
if (Elevation != ""){
ComElev = Elevation.toInt();
if (ComElev>180) { ComElev = 0;}
if (ComElev>90) { //if received more than 90deg. (for trackers with 180deg. elevation)
ComElev = 180-ComElev; //keep below 90deg.
ComAzim = (ComAzim+180)%360; //and rotate the antenna on the back
}
}
// looking for <AZ EL> interogation for antenna position
for (int i = 0; i <= (ComputerRead.length()-4); i++) {
if ((ComputerRead.charAt(i) == 'A')&&(ComputerRead.charAt(i+1) == 'Z')&&(ComputerRead.charAt(i+3) == 'E')&&(ComputerRead.charAt(i+4) == 'L')){
// send back the antenna position <+xxx.x xx.x>
ComputerWrite = "+"+String(TruAzim)+".0 "+String(TruElev)+".0";
Serial.println(ComputerWrite);
}
}
}
// end SerComm()
*/
/// code end
Have a nice day!