Skip to content

Instantly share code, notes, and snippets.

@miceno
Last active November 30, 2023 17:09
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save miceno/8de76ef7b473e9806008c7390ddb7e20 to your computer and use it in GitHub Desktop.
Save miceno/8de76ef7b473e9806008c7390ddb7e20 to your computer and use it in GitHub Desktop.
Firmware for a SnapCap controller using ESP8266 and WiFi
/*
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 - jared@mainsequencesoftware.com
Updated by: Orestes Sanchez-Benavente - miceno.atreides@gmail.com
When:
Last modified: 2013/May/05
Last modified: 2023/Nov/08
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 <ESP8266WiFi.h>
#include <WiFiManager.h> // https://github.com/tzapu/WiFiManager
#include <Servo.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 104 // Firmware version.
#define MAX_BAUD_RATE 115200
/*
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 180 // 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 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
*/
#define INTERNET_ADDR 192, 168, 1, 62 // Manual setup of IP address
#define GATEWAY_ADDR 192, 168, 1, 1 // Gateway address
#define SUBNET_ADDR 255, 255, 255, 0 // Subnet address
#define INTERNET_PORT 8888 // Listen on telnet port, match in INDI driver tab \
IPAddress ip(INTERNET_ADDR); // AP local Internet address
IPAddress gw(GATEWAY_ADDR); // AP gateway address
IPAddress subnet(SUBNET_ADDR); // AP subnet address
WiFiServer server(INTERNET_PORT); // Arduino server listening for connections on port specified
WiFiClient client; // 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 = 2; // the pin that the LED is attached to, needs to be a PWM pin.
volatile int motorPin = 5; // 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 setupWifi() {
delay(INIT_DELAY_SECS * 1000); // diagnostic, allow time to get serial monitor displayed
WiFi.mode(WIFI_STA); // explicitly set mode, esp defaults to STA+AP
// it is a good practice to make sure your code sets wifi mode how you want it.
//WiFiManager, Local intialization. Once its business is done, there is no need to keep it around
WiFiManager wm;
// reset settings - wipe stored credentials for testing
// these are stored by the esp library
// wm.resetSettings();
// Automatically connect using saved credentials,
// if connection fails, it starts an access point with the specified name ( "AutoConnectAP"),
// if empty will auto generate SSID, if password is blank it will be anonymous AP (wm.autoConnect())
// then goes into a blocking loop awaiting configuration and will return success result
bool res;
// res = wm.autoConnect(); // auto generated AP name from chipid
// res = wm.autoConnect("AutoConnectAP"); // anonymous ap
res = wm.autoConnect("APsnapcap", "astroberry-snapcap"); // password protected ap
if (!res) {
DEBUG_ERROR("Failed to connect");
// ESP.restart();
} else {
//if you get here you have connected to the WiFi
DEBUG_INFO("connected...yeey :)");
}
printWifiStatus();
DEBUG_INFO("Network online, ready for rolloffino driver connections.");
}
void printWifiStatus() {
String ssid = WiFi.SSID();
int8_t rssi = WiFi.RSSI();
IPAddress ip = WiFi.localIP();
// print the SSID of the network you're attached to:
DEBUG_INFO("\nSSID: %s, IP Address: %s, Signal (RSSI): %d dBm", ssid.c_str(), ip.toString().c_str(), rssi);
}
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 connectWifi() {
// Connect to the WiFi network:
while (WiFi.status() != WL_CONNECTED) {
DEBUG_INFO("Attempting to connect to the network ");
// wait up to 20 seconds to establish the connection
for (int i = 0; i < 20; i++) {
delay(1000);
if (WiFi.status() == WL_CONNECTED)
return;
DEBUG_INFO(".");
}
DEBUG_INFO("Failed to connect to the current configured network.\n"
"Unable to continue without a WiFi network.");
}
}
void reconnectWifi() {
DEBUG_VERBOSE("not connected"); // DEBUG
// if (indiConnected || indiData)
if (indiConnected) {
DEBUG_INFO("Lost the WiFi connection");
}
indiConnected = false;
if (client) {
client.stop();
}
WiFi.disconnect();
WiFi.config(ip, gw, subnet); // Use a fixed WiFi address for the Arduino
connectWifi();
server.begin(); // Start listening
printWifiStatus();
}
void setupServer(){
server.begin(); // Start listening
}
void setup(){
setupSerial();
setupDebug();
initHardware();
setupWifi();
setupServer();
}
/*
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 checkWifiStatus(){
// Check still connected to the wifi network
DEBUG_VERBOSE("wifi loop"); // DEBUG
int wifi_status = WiFi.status();
DEBUG_VERBOSE("wifi status: %d", wifi_status); // DEBUG
if (wifi_status != WL_CONNECTED) {
DEBUG_VERBOSE("reconnecting..."); // DEBUG
reconnectWifi();
}
if (!client) {
client = server.available();
}
if (client.connected()) {
DEBUG_VERBOSE("client.connected"); // DEBUG
if (!indiConnected) {
indiConnected = true;
// indiData = false;
DEBUG_INFO("rolloffino driver connected");
}
} else {
DEBUG_VERBOSE("NOT client.connected"); // DEBUG
if (indiConnected) {
indiConnected = false;
DEBUG_INFO("rolloffino driver disconnected");
}
}
DEBUG_VERBOSE("after client.connected checks"); // DEBUG
}
void handleWifi() {
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() {
checkWifiStatus();
handleWifi();
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment