Skip to content

Instantly share code, notes, and snippets.

@miceno
Created March 3, 2025 18:24
Show Gist options
  • Save miceno/16ee6619edd8db849ac7ad7be8800d39 to your computer and use it in GitHub Desktop.
Save miceno/16ee6619edd8db849ac7ad7be8800d39 to your computer and use it in GitHub Desktop.
Firmware for a SnapCap controller using ESP8266 and Serial port
/*
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