Last active
November 30, 2023 17:09
-
-
Save miceno/8de76ef7b473e9806008c7390ddb7e20 to your computer and use it in GitHub Desktop.
Firmware for a SnapCap controller using ESP8266 and WiFi
This file contains 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 - 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