Created
March 3, 2025 18:24
-
-
Save miceno/16ee6619edd8db849ac7ad7be8800d39 to your computer and use it in GitHub Desktop.
Firmware for a SnapCap controller using ESP8266 and Serial port
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
/* | |
What: LEDLightBoxAlnitak - PC controlled lightbox implmented using the | |
Alnitak (Flip-Flat/Flat-Man) command set found here: | |
https://optec.us/resources/catalog/alnitak/pdf/Alnitak_GenericCommandsR4.pdf | |
Responses don't include the deviceId, since the implementation of the | |
INDI::SnapCap driver requires that responses do not use it. | |
Who: | |
Created By: Jared Wellman - [email protected] | |
Updated by: Orestes Sanchez-Benavente - [email protected] | |
When: | |
Last modified: 2013/May/05 | |
Last modified: 2024/Mar/03 | |
Typical usage on the command prompt: | |
Send : >O000\n //open | |
Send : >o000\n //force open | |
Send : >C000\n //close | |
Send : >c000\n //force close | |
Send : >P000\n //ping | |
Send : >S000\n //request state | |
Receive : *S000\n //returned state | |
Return : *SMLC\n | |
M = motor status( 0 stopped, 1 running) | |
L = light status( 0 off, 1 on) | |
C = Cover Status( 0 moving, 1 open, 2 closed) | |
Send : >B128\n //set brightness 128 | |
Receive : *B128\n //confirming brightness set to 128 | |
Send : >J000\n //get brightness | |
Receive : *B128\n //brightness value of 128 (assuming as set from above) | |
Send : >L000\n //turn light on (uses set brightness value) | |
Receive : *L000\n //confirms light turned on | |
Send : >D000\n //turn light off (brightness value should not be changed) | |
Receive : *D000\n //confirms light turned off. | |
Send : >V000\n // Get firmware version. | |
Receive : *V103\n // return firmware version. | |
Send : >M000\n //get position of motor | |
Receive : *MPPP\n //PPP = position of motor | |
Send : >NPPP\n //move position of motor | |
Receive : *NPPP\n //PPP = new position of motor | |
*/ | |
#include <Servo.h> | |
#include <HardwareSerial.h> | |
#include "Arduino_DebugUtils.h" | |
#define DEFAULT_LOG_LEVEL DBG_DEBUG | |
// If there is difficulty getting the INDI driver to connect, need to establish that the | |
// WiFi network is working and that the Arduino is connected. Running the Arduino IDE and | |
// using its serial monitor can be helpful to see any diagnostic messages. | |
// INIT_DELAY_SECS can be defined to wait a number of seconds after starting the Arduino | |
// to have time to get the serial monitor display started and see network startup messages. | |
#define INIT_DELAY_SECS 0 | |
#define FIRMWARE_VERSION 105 // Firmware version. | |
#define MAX_BAUD_RATE 74880 | |
/* | |
Servo configuration | |
*/ | |
#define POSITION_CLOSED 0 // Position of servo when shutter is closed. | |
#define POSITION_OPENED 180 // Going from 0 to 180 will move the servo for its full range of movement. \ | |
// If it's a 270 degree servo you'll get 270 when you write(180). \ | |
// If it's a 360 degree servo you'll get 360 when you write(180). | |
#define MAX_SERVO_DEGREES 270 // Max value of servo movement. | |
// #define MIN_SERVO_TIME 300 // Minimum pulse of the servo in miliseconds | |
// #define MAX_SERVO_TIME 2550 // Maximum pulse of the servo in miliseconds | |
#define MIN_SERVO_TIME 300 // Minimum pulse of the servo in miliseconds | |
#define MAX_SERVO_TIME 2550 // Maximum pulse of the servo in miliseconds | |
#define MIDDLE_SERVO_TIME 1500 // Middle pulse of the servo in miliseconds | |
/* SG90S | |
#define MIN_SERVO_TIME 300 // Minimum pulse of the servo in miliseconds | |
#define MAX_SERVO_TIME 2550 // Maximum pulse of the servo in miliseconds | |
#define MIDDLE_SERVO_TIME 1500 // Middle pulse of the servo in miliseconds | |
*/ | |
/* | |
Light configuration | |
*/ | |
#define MAX_ANALOG_VALUE 255 // Max value of analog range | |
#define MAX_BRIGHTNESS_VALUE 255 // Max value of light brightness in INDI::LightBoxInterface | |
/* | |
Network and communications | |
*/ | |
#define NETWORK_WAIT_TIME 15 // Time to wait for incoming network data. | |
#define SERVO_WAIT_TIME 0 // Time to wait for servo to reach set position. | |
#define MAX_RECEIVED_LENGTH 20 // Max length of the receive buffer. | |
enum devices { | |
FLAT_MAN_L = 10, // Flat-Man_XL | |
FLAT_MAN_XL = 15, // Flat-Man_L | |
FLAT_MAN = 19, // Flat-Man | |
FLIP_DUST = 98, // Flip-Mask/Remote Dust Cover | |
FLIP_FLAT = 99 // Flip-Flat | |
}; | |
enum motorStatuses { | |
MS_STOPPED = 0, | |
MS_RUNNING | |
}; | |
enum lightStatuses { | |
LS_OFF = 0, | |
LS_ON | |
}; | |
enum shutterStatuses { | |
SS_UNKNOWN = 0, // ie not open or closed...could be moving | |
SS_OPENED = 1, | |
SS_CLOSED = 2 | |
}; | |
/* | |
Device setup | |
*/ | |
int deviceId = FLIP_FLAT; | |
/* | |
Network | |
*/ | |
HardwareSerial &client = Serial; // Connection to return data back to the indi driver | |
/* | |
Status variables | |
*/ | |
Servo capServo; // create servo object to control a servo | |
int pos = 0; // variable to store the current position of the servo | |
int brightness = 0; // variable to store current light brightness | |
volatile int ledPin = LED_BUILTIN; // the pin that the LED is attached to, needs to be a PWM pin. | |
volatile int motorPin = D7; // the pin that the motor is attached to, needs to be a PWM pin. | |
int motorStatus = MS_STOPPED; | |
int lightStatus = LS_OFF; | |
int coverStatus = SS_CLOSED; | |
boolean indiConnected = false; // Driver has connected to local network | |
/* | |
Setup and initialization | |
*/ | |
void setupDebug() { | |
Debug.timestampOn(); | |
Debug.formatTimestampOn(); | |
Debug.newlineOn(); | |
Debug.setDebugLevel(DEFAULT_LOG_LEVEL); | |
} | |
void setupSerial() { | |
// initialize the serial communication: | |
Serial.begin(MAX_BAUD_RATE); | |
} | |
void initHardware() { | |
// Set the maximum range for analog pins. | |
analogWriteRange(MAX_ANALOG_VALUE); | |
// initialize the ledPin as an output, using pull-down. | |
analogWriteMode(ledPin, OUTPUT_OPEN_DRAIN, true); | |
// Switch the light off. | |
setLight(0); | |
// Set the timings according to the spec of your servo motor. | |
// capServo.attach(motorPin, 500, 2500, 1500); | |
// capServo.attach(motorPin, MIN_SERVO_TIME, MAX_SERVO_TIME, MIDDLE_SERVO_TIME); | |
capServo.attach(motorPin, MIN_SERVO_TIME, MAX_SERVO_TIME); | |
delay(100); | |
// Closed position | |
// Uncomment the following line if you need the servo to always move to close | |
// when initializing the microcontroller. | |
// In some cases, you would prefer to do not move it, for example, | |
// if you switch the microcontroller off to save power when using a battery. | |
// capServo.write(POSITION_CLOSED); | |
} | |
void setup() { | |
setupSerial(); | |
setupDebug(); | |
initHardware(); | |
} | |
/* | |
Utilities | |
*/ | |
void setLight(int brightness) { | |
analogWrite(ledPin, map(brightness, 0, MAX_BRIGHTNESS_VALUE, MAX_ANALOG_VALUE, 0)); | |
} | |
void sendResult(const char* message) { | |
DEBUG_DEBUG("sending %s;", message); | |
client.println(message); | |
} | |
int motorPosition() { | |
int result = capServo.read(); | |
int degrees = map(result, 0, POSITION_OPENED, 0, MAX_SERVO_DEGREES); | |
return degrees; | |
} | |
int moveServoFast(int startPosition, int endPosition, int waitTime) { | |
capServo.write(endPosition); | |
pos = endPosition; | |
delay(waitTime); | |
return pos; | |
} | |
int moveServoBySteps(int startPosition, int endPosition, int waitTime) { | |
if (startPosition > endPosition) { | |
// Reverse motion. | |
// Move the motor | |
for (pos = startPosition; pos >= endPosition; pos -= 1) { | |
// move in 1 degree increments | |
capServo.write(pos); // we give the server a command to turn to the position specified in the 'pos' variable | |
delay(waitTime); // wait until the servo rotor reaches the specified position | |
} | |
} else { | |
// Forward motion. | |
// Move the motor | |
for (pos = startPosition; pos <= endPosition; pos += 1) { | |
// move in 1 degree increments | |
capServo.write(pos); // we give the server a command to turn to the position specified in the 'pos' variable | |
delay(waitTime); // wait until the servo rotor reaches the specified position | |
} | |
} | |
return pos; | |
} | |
void SetShutter(int val) { | |
if (val == SS_OPENED && coverStatus != SS_OPENED) { | |
DEBUG_INFO("Opening the servo"); | |
coverStatus = SS_OPENED; | |
moveServoFast(POSITION_CLOSED, POSITION_OPENED, SERVO_WAIT_TIME); | |
// moveServoBySteps(POSITION_CLOSED, POSITION_OPENED, SERVO_WAIT_TIME); | |
} else if (val == SS_CLOSED && coverStatus != SS_CLOSED) { | |
DEBUG_INFO("Closing the servo"); | |
coverStatus = SS_CLOSED; | |
moveServoFast(POSITION_OPENED, POSITION_CLOSED, SERVO_WAIT_TIME); | |
// moveServoBySteps(POSITION_OPENED, POSITION_CLOSED, SERVO_WAIT_TIME); | |
} else { | |
DEBUG_INFO("Moving servo to %d", val); | |
pos = val; | |
// Actually handle this case | |
capServo.write(pos); // give the command to go to the position that is written in the 'pos' variable | |
delay(SERVO_WAIT_TIME); // wait until the servo reaches the specified position | |
} | |
} | |
void handleSerial() { | |
if (client.available() > 0) // all incoming communications are fixed length at 6 bytes including the \n | |
{ | |
char* cmd; | |
char* data; | |
char temp[10]; | |
int len = 0; | |
char str[MAX_RECEIVED_LENGTH]; | |
memset(str, 0, MAX_RECEIVED_LENGTH); | |
// I don't personally like using the \n as a command character for reading. | |
// but that's how the command set is. | |
client.readBytesUntil('\n', str, MAX_RECEIVED_LENGTH); | |
cmd = str + 1; | |
data = str + 2; | |
// useful for debugging to make sure your commands came through and are parsed correctly. | |
if (true) { | |
DEBUG_DEBUG("cmd = >%c%s;", *cmd, data); | |
} | |
switch (*cmd) { | |
/* | |
Ping device | |
Request: >P000\n | |
Return : *Pii000\n | |
ii = deviceId | |
*/ | |
case 'P': | |
sprintf(temp, "*P%02dOOO", deviceId); | |
sendResult(temp); | |
break; | |
/* | |
Open shutter | |
Request: >O000\n | |
Return : *O000\n | |
ii = deviceId | |
This command is only supported on the Flip-Flat! | |
*/ | |
case 'O': | |
case 'o': | |
sprintf(temp, "*%c000", *cmd, deviceId); | |
sendResult(temp); | |
SetShutter(SS_OPENED); | |
break; | |
/* | |
Close shutter | |
Request: >C000\n | |
Return : *C000\n | |
ii = deviceId | |
This command is only supported on the Flip-Flat! | |
*/ | |
case 'C': | |
case 'c': | |
sprintf(temp, "*%c000", *cmd, deviceId); | |
sendResult(temp); | |
SetShutter(SS_CLOSED); | |
break; | |
/* | |
Turn light on | |
Request: >L000\n | |
Return : *L000\n | |
This response MUST not include the device ID | |
id = deviceId | |
*/ | |
case 'L': | |
sprintf(temp, "*L000", deviceId); | |
sendResult(temp); | |
lightStatus = LS_ON; | |
setLight(brightness); | |
break; | |
/* | |
Turn light off | |
Request: >D000\n | |
Return : *D000\n | |
This response MUST not include the device ID | |
id = deviceId | |
*/ | |
case 'D': | |
sprintf(temp, "*D000", deviceId); | |
sendResult(temp); | |
lightStatus = LS_OFF; | |
setLight(0); | |
break; | |
/* | |
Set brightness | |
Request: >Bxxx\n | |
xxx = brightness value from 000-255 | |
Return : *Byyy\n | |
ii = deviceId | |
yyy = value that brightness was set from 000-255 | |
*/ | |
case 'B': | |
brightness = atoi(data); | |
sprintf(temp, "*B%03d", brightness); | |
sendResult(temp); | |
if (lightStatus == LS_ON) { | |
setLight(brightness); | |
} | |
break; | |
/* | |
Get brightness | |
Request: >J000\n | |
Return : *Jyyy\n | |
ii = deviceId | |
yyy = current brightness value from 000-255 | |
*/ | |
case 'J': | |
sprintf(temp, "*J%03d", brightness); | |
sendResult(temp); | |
break; | |
/* | |
Get device status: | |
Request: >S000\n | |
Return : *SMLC\n | |
ii = deviceId | |
M = motor status( 0 stopped, 1 running) | |
L = light status( 0 off, 1 on) | |
C = Cover Status( 0 moving, 1 open, 2 closed) | |
*/ | |
case 'S': | |
sprintf(temp, "*S%d%d%d", motorStatus, lightStatus, coverStatus); | |
sendResult(temp); | |
break; | |
/* | |
Get firmware version | |
Request: >V000\n | |
Return : *Vvvv\n | |
vvv = version | |
*/ | |
case 'V': // get firmware version | |
sprintf(temp, "*V%03d", FIRMWARE_VERSION); | |
sendResult(temp); | |
break; | |
/* | |
Get position of motor | |
Request: >M000\n | |
Return : *MPPP\n | |
PPP = position of motor (0-180) | |
*/ | |
case 'M': // get motor position | |
sprintf(temp, "*M%03d", motorPosition()); | |
sendResult(temp); | |
break; | |
/* | |
Set position of motor | |
Request: >NPPP\n | |
Return : *NPPP\n | |
PPP = position of motor (0-360) | |
*/ | |
case 'N': // set motor position | |
pos = map(atoi(data), 0, MAX_SERVO_DEGREES, 0, POSITION_OPENED); | |
sprintf(temp, "*N%03d", pos); | |
sendResult(temp); | |
SetShutter(pos); | |
break; | |
/* | |
Abort: this is not implemented, but the driver may send it. | |
Request: >A000\n | |
Return : *A000\n | |
*/ | |
case 'A': // set motor position | |
sendResult("*A000"); | |
break; | |
/** | |
* UNKNOW command | |
*/ | |
default: | |
sprintf(temp, "cmd = >%c%s;", *cmd, data); | |
sendResult("Unknown command: "); | |
sendResult(temp); | |
break; | |
} | |
while (client.available() > 0) | |
client.read(); | |
} | |
delay(NETWORK_WAIT_TIME); | |
} | |
void loop() { | |
handleSerial(); | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment