// Arduino sketch which outputs PWM for a set of channels based on serial input.
#include <WiFi.h>
#include <ESPmDNS.h>
#include <WiFiClient.h>
//#include "/Users/jmzorko/work/circusautomatic/hardware/espressif/esp32/tools/sdk/include/soc/soc/rtc.h"
//#include "/home/fleeky/Arduino/hardware/espressif/esp32/tools/sdk/include/freertos/freertos/task.h"
//#include "/home/cayuga/.arduino15/packages/esp32/hardware/esp32/1.0.1/tools/sdk/include/soc/soc/rtc.h"
#include "/home/fleeky/Arduino/hardware/espressif/esp32/tools/sdk/include/soc/soc/rtc.h"
const char* ssid = "prsm space";
const char* password = "";
// TCP server at port 80 will respond to HTTP requests
WiFiServer server(1337);
const int timeout = 30;
// Serial commands accepted:
// - pwm <pwm1> <pwm2> ..., where <pwmN> is 0-65535 and N is up to NumChannels
// - pwm <index1>:<pwm1> <index2>:<pwm2> ..., where <indexN> is 1-based index into the PWMPins list
// - plevel <level>, change the amount of response text
//
// NOTE: initHRPWM and myHRWrite are set specifically to work on MEGAS. It could be tuned to work on
// unos.
#include <SC.h>
#include <PrintLevel.h>
#define INVERT_HIGH_AND_LOW
#define RED_MAX_PWM 65534 // protective cap for red diodes (lower voltage tolerance)
#define MAX_PWM 65535 // full 16-bit range; LEDC handles 65535 via duty=max+1 special case
constexpr int NumPeriods = 1; // number of steps in fade
constexpr int PeriodLength = 10; // sleep for this many ms
//////////////////////////////////////////////////////////////////////////////////////
int freq = 1000; // 20kHz at 16-bit is impossible on ESP32 LEDC (80MHz/2^16 ≈ 1220 Hz max); 1kHz keeps full 16-bit precision
int res = 16;
int steps = 1;
int defaultlight = 4000;
int maxfreq = 40000;
int ledChannel = 0;
int ledChannel1 = 1;
int ledChannel2 = 2;
int ledChannel3 = 3;
int ledChannel4 = 4;
int ledChannel5 = 5;
int ledChannel6 = 6;
int ledChannel7 = 7;
int ledChannel8 = 8;
int ledChannel9 = 9;
int ledChannel10 = 10;
int ledChannel11 = 11;
const int PWMPins[] = {32, 33, 25, 26, 27, 14, 12, 13, 2, 23, 22, 21};
const int NumPWMPins = sizeof(PWMPins) / sizeof(*PWMPins);
int PinCurrentValues[NumPWMPins] = {0};
// this flag will invert PWM output (65535 - PWM), for active-low devices
const char *MsgPWMTupleFormatError = "Error: takes up to 6 arguments between 0 and 255";
/////////////////////////////////////////////////////////////////////////////////////////
// TCP client tracking + diagnostic helpers
// Works around arduino-esp32 issues #1527 (connected() lies after FIN) and #4773
// (server.available() can leave stale clients in place). See setup()/loop() below.
/////////////////////////////////////////////////////////////////////////////////////////
WiFiClient LastClient;
unsigned long lastClientByteMs = 0;
const unsigned long CLIENT_IDLE_TIMEOUT_MS = 300000; // 5 min — drop a dead session
// When true, command handlers also write GOT/OK/ERR markers to the active telnet
// client so you can see whether commands are reaching the parser. Toggle at runtime
// with "verbose 0" or "verbose 1".
bool diagVerbose = true;
void diagPrint(const char *s) {
Serial.print(s);
if (diagVerbose && LastClient && LastClient.connected()) LastClient.print(s);
}
void diagPrintln(const char *s) {
Serial.println(s);
if (diagVerbose && LastClient && LastClient.connected()) LastClient.println(s);
}
void diagPrintln(long v) {
Serial.println(v);
if (diagVerbose && LastClient && LastClient.connected()) LastClient.println(v);
}
/////////////////////////////////////////////////////////////////////////////////////////
// Command Manager and forward declarations for commands accessible from text interface
/////////////////////////////////////////////////////////////////////////////////////////
struct IDTuple {
int id;
long value;
};
int parseID(const char *arg);
int parseListOfIDs(int *outIDs, int maxIDs);
void cmdUnrecognized(const char *cmd);
void cmdSetFrequency();
void cmdPWMPins();
void cmdPWMPins2();
void cmdSetPrintLevel();
void cmdServo();
void cmdVerbose();
SerialCommand::Entry CommandsList[] = {
{"plevel", cmdSetPrintLevel},
{"freq", cmdSetFrequency},
{"pwm", cmdPWMPins},
{"pwm2", cmdPWMPins2},
{"s", cmdServo},
{"verbose", cmdVerbose},
{NULL, NULL}
};
SerialCommand CmdMgr(CommandsList, cmdUnrecognized);
/////////////////////////////////////////////////////////////////////////////////////////////
// helpers
/////////////////////////////////////////////////////////////////////////////////////////////
unsigned long invertPWM(unsigned long c) {
#ifdef INVERT_HIGH_AND_LOW
c = 65535 - c;
#endif
return c;
}
void myHRWrite(int pin, unsigned value) {
//printAlways("setting pin to value: ");
//printAlways(pin);
//printAlways(" ");
//printlnAlways(value);
value = invertPWM(value);
switch (pin) {
case 32: ledcWrite(ledChannel, value); break;
case 33: ledcWrite(ledChannel1, value); break;
case 25: ledcWrite(ledChannel2, value); break;
case 26: ledcWrite(ledChannel3, value); break;
case 27: ledcWrite(ledChannel4, value); break;
case 14: ledcWrite(ledChannel5, value); break;
case 12: ledcWrite(ledChannel6, value); break;
case 13: ledcWrite(ledChannel7, value); break;
case 2: ledcWrite(ledChannel8, value); break;
case 23: ledcWrite(ledChannel9, value); break;
case 22: ledcWrite(ledChannel10, value); break;
case 21: ledcWrite(ledChannel11, value); break;
default: printlnError("invalid pin");
}
}
void updateFrequency() {
ledcSetup(ledChannel, freq, res);
ledcAttachPin(32, ledChannel);
myHRWrite(PWMPins[0], 0);
ledcSetup(ledChannel1, freq, res);
ledcAttachPin(33, ledChannel);
myHRWrite(PWMPins[1], 0);
ledcSetup(ledChannel2, freq, res);
ledcAttachPin(25, ledChannel);
myHRWrite(PWMPins[2], 0);
ledcSetup(ledChannel3, freq, res);
ledcSetup(ledChannel4, freq, res);
ledcSetup(ledChannel5, freq, res);
ledcSetup(ledChannel6, freq, res);
ledcSetup(ledChannel7, freq, res);
ledcSetup(ledChannel8, freq, res);
ledcSetup(ledChannel9, freq, res);
ledcSetup(ledChannel10, freq, res);
ledcSetup(ledChannel11, freq, res);
ledcAttachPin(32, ledChannel);
ledcAttachPin(33, ledChannel1);
ledcAttachPin(25, ledChannel2);
ledcAttachPin(26, ledChannel3);
ledcAttachPin(27, ledChannel4);
ledcAttachPin(14, ledChannel5);
ledcAttachPin(12, ledChannel6);
ledcAttachPin(13, ledChannel7);
ledcAttachPin(2, ledChannel8);
ledcAttachPin(23, ledChannel9);
ledcAttachPin(22, ledChannel10);
ledcAttachPin(21, ledChannel11);
}
void ledsetup() {
updateFrequency();
// Pins that work 27,26,25 23,22,21 32,33,14 12,13,2
//for(uint8_t i=0; i < 12; i++)
// ledcWrite(channel, dutycycle)
// For 8-bit resolution duty cycle is 0 - 255
// ledcWrite(ledChannel, 0); // test high output of all leds in sequence
// ledcWrite(ledChannel1, 0); // test high output of all leds in sequence
// ledcWrite(ledChannel2, 0); // test high output of all leds in sequence
// ledcWrite(ledChannel3, 0); // test high output of all leds in sequence
// ledcWrite(ledChannel4, 0); // test high output of all leds in sequence
// ledcWrite(ledChannel5, 0); // test high output of all leds in sequence
// ledcWrite(ledChannel6, 0); // test high output of all leds in sequence
// ledcWrite(ledChannel7, 0); // test high output of all leds in sequence
// ledcWrite(ledChannel8, 0); // test high output of all leds in sequence
// ledcWrite(ledChannel9, 0); // test high output of all leds in sequence
// ledcWrite(ledChannel10, 0); // test high output of all leds in sequence
// ledcWrite(ledChannel11, 0); // test high output of all leds in sequence
}
// Takes a string of an integer (numeric chars only). Returns the integer on success.
// Prints error and returns 0 if there is a parse error or the ID is out of range.
int parseID(const char *arg) {
char *end;
int id = strtol(arg, &end, 10);
if (*end != '\0' || id < 1 || id > 253) {
printlnError("Error: ID must be between 1 and 253");
return 0;
}
return id;
}
// Takes a string of an integer (numeric chars only). Returns the integer on success.
// Prints error and returns -1 if there is a parse error or the PWM value is out of range.
long parsePWM(const char *arg, long maxPWM) {
char *end;
long v = strtol(arg, &end, 10);
if (*end != '\0' || v < 0 || v > maxPWM) {
printlnError("Error: PWM value must be between 0 and ");
printlnError(maxPWM);
return -1;
}
return v;
}
void cmdUnrecognized(const char *cmd) {
printlnError("unrecognized command");
diagPrintln("ERR unrecognized");
}
// if no argument, prints the frequency
// if one argument, sets the frequency and then prints it
void cmdSetFrequency() {
diagPrintln("GOT freq");
if(char *arg = CmdMgr.next()) {
if(CmdMgr.next()) {
printlnError("Error: takes 0 or 1 arguments");
diagPrintln("ERR freq too many args");
return;
}
char *end;
double v = strtod(arg, &end);
if(*end != '\0' || v < 1 || v > maxfreq) {
printlnError("Error: speed is in angle-units per second; between 1 and 1000");
diagPrintln("ERR freq out of range");
return;
}
freq = round(v);
}
updateFrequency();
printAck("frequency: ");
printlnAck(freq);
diagPrint("OK freq ");
diagPrintln((long)freq);
}
void cmdPWMPins2() {
diagPrintln("GOT pwm2");
const char *SetPWM2UsageMsg = "Error: takes up to 13 arguments between 0 and 65535 (except argument 3's max is 45000).";
long channelValues[NumPWMPins];
int count = 0;
char *arg = CmdMgr.next();
if (arg == NULL) {
printlnError("Error: no arguments");
diagPrintln("ERR pwm2 no args");
return;
}
long numPeriods = parsePWM(arg, 100);
arg = CmdMgr.next();
long periodLength = parsePWM(arg, 100);
arg = CmdMgr.next();
do {
int index;
long value;
if (count >= NumPWMPins) {
printlnError(SetPWM2UsageMsg);
diagPrintln("ERR pwm2 too many args");
return;
}
char *end;
index = count;
value = parsePWM(arg, count % 3 == 2 ? RED_MAX_PWM : MAX_PWM);
if (value < 0) {
printlnError(SetPWM2UsageMsg);
diagPrintln("ERR pwm2 bad arg");
// value = 4000;
return;
}
//printInfo("Set pin ");
//printInfo(PWMPins[index]);
//printInfo(" to ");
//printlnInfo(value);
channelValues[index] = value;
count++;
} while (arg = CmdMgr.next());
// interpolate over a period of time
const TickType_t xDelay = periodLength / portTICK_PERIOD_MS;
float frameDelay = 9.14; // 9.14ms - length of time between frames at 60fps
long beginTime = micros();
long endTime = beginTime + 9140;
for (long period = 1; period < 1+numPeriods; period++) {
//long beginTime = millis();
float fraction = period / (float)numPeriods;
for (int i = 0; i < count; i++) {
unsigned long v = PinCurrentValues[i] + (channelValues[i] - PinCurrentValues[i]) * fraction;
//printlnAlways(v);
myHRWrite(PWMPins[i], v);
}
//long endTime = millis();
//printlnAlways(endTime - beginTime);
//vTaskDelay(xDelay);
}
for (int i = 0; i < count; i++) {
unsigned long v = channelValues[i];
PinCurrentValues[i] = v;
myHRWrite(PWMPins[i], v);
}
//printAck("OK set ");
//printAck(count);
//printlnAck(" pins");
diagPrint("OK pwm2 ");
diagPrintln((long)count);
}
// expects space-delimited ints 0-65535, or space delimited <index>:<pwm> pairs
void cmdPWMPins() {
diagPrintln("GOT pwm");
const char *SetPWMUsageMsg = "Error: takes up to 12 arguments between 0 and 65535 (except argument 3's max is 45000).";
long channelValues[NumPWMPins];
int count = 0;
char *arg = CmdMgr.next();
if (arg == NULL) {
printlnError("Error: no arguments");
diagPrintln("ERR pwm no args");
return;
}
do {
int index;
long value;
if (count >= NumPWMPins) {
printlnError(SetPWMUsageMsg);
diagPrintln("ERR pwm too many args");
return;
}
char *end;
index = count;
value = parsePWM(arg, count % 3 == 2 ? RED_MAX_PWM : MAX_PWM);
if (value < 0) {
printlnError(SetPWMUsageMsg);
diagPrintln("ERR pwm bad arg");
// value = 4000;
return;
}
printInfo("Set pin ");
printInfo(PWMPins[index]);
printInfo(" to ");
printlnInfo(value);
channelValues[index] = value;
count++;
} while (arg = CmdMgr.next());
// interpolate over a period of time
const TickType_t xDelay = PeriodLength / portTICK_PERIOD_MS;
for (int period = 1; period < 1+NumPeriods; period++) {
float fraction = period / (float)NumPeriods;
for (int i = 0; i < count; i++) {
unsigned long v = PinCurrentValues[i] + (channelValues[i] - PinCurrentValues[i]) * fraction;
//printlnAlways(v);
myHRWrite(PWMPins[i], v);
}
//vTaskDelay(xDelay);
}
for (int i = 0; i < count; i++) {
unsigned long v = channelValues[i];
PinCurrentValues[i] = v;
myHRWrite(PWMPins[i], v);
}
printAck("OK set ");
printAck(count);
printlnAck(" pins");
diagPrint("OK pwm ");
diagPrintln((long)count);
}
void cmdSetPrintLevel() {
diagPrintln("GOT plevel");
if (char *arg = CmdMgr.next()) {
if (CmdMgr.next()) {
printlnAlways("Error: takes 0 or 1 arguments");
diagPrintln("ERR plevel too many args");
return;
}
if (!PrintLevel::set(arg)) {
PrintLevel::printErrorString();
diagPrintln("ERR plevel bad arg");
return;
}
}
printAck("print level ");
printlnAck(PrintLevel::toString());
diagPrint("OK plevel ");
diagPrintln(PrintLevel::toString());
}
void cmdServo() {
diagPrintln("GOT s");
Serial.print("Got Servo shit");
diagPrintln("OK s");
}
// verbose 0 | verbose 1 — toggle telnet diagnostic markers
void cmdVerbose() {
if (char *arg = CmdMgr.next()) {
diagVerbose = (atoi(arg) != 0);
}
// Force-print this one to telnet so user knows the toggle took effect
Serial.print("verbose ");
Serial.println(diagVerbose ? "1" : "0");
if (LastClient && LastClient.connected()) {
LastClient.print("verbose ");
LastClient.println(diagVerbose ? "1" : "0");
}
}
///////////////////////////////////////////////////////////////////////////////////////////////////////////
// setup & loop
///////////////////////////////////////////////////////////////////////////////////////////////////////////
void setup() {
//rtc_clk_cpu_freq_set(RTC_CPU_FREQ_240M);
ledsetup();
for (int i=0; i < NumPWMPins; i++){
myHRWrite(PWMPins[i], 0);
PinCurrentValues[i] = 0;
delay(10);
}
Serial.begin(115200);
WiFi.begin(ssid, password);
WiFi.setSleep(false); // forum-recommended stability fix for persistent TCP servers
Serial.println("");
// Wait for connection
int i = 0;
while (WiFi.status() != WL_CONNECTED ) {
if (i < timeout) {
delay(500);
Serial.print(".");
i++;
}
else {
Serial.println(" ");
break;
}
}
if (WiFi.status() == WL_CONNECTED ) {
Serial.println("");
Serial.print("Connected to ");
Serial.println(ssid);
Serial.print("IP address: ");
Serial.println(WiFi.localIP());
// Set up mDNS responder:
// - first argument is the domain name, in this example
// the fully-qualified domain name is "esp8266.local"
// - second argument is the IP address to advertise
// we send our IP address on the WiFi network
if (!MDNS.begin("studiolight1")) {
Serial.println("Error setting up MDNS responder!");
while (1) {
delay(1000);
}
}
Serial.println("mDNS responder started");
// Start TCP (HTTP) server
server.begin();
Serial.println("TCP server started");
// Add service to MDNS-SD
MDNS.addService("telnet", "tcp", 23);
}
Serial.println("Initial LED Setup");
// INSERT HERE
{
const char *cmd =
"pwm 60000 60000 30000 60000 60000 30000 60000 60000 30000 60000 60000 30000";
for (const char *p = cmd; *p; p++) {
CmdMgr.handleChar(*p);
}
CmdMgr.handleChar('\n'); // finalize the command
}
}
void loop() {
CmdMgr.readSerial();
// Accept a new TCP client if one is pending. If we already have one, stop()
// it first so we don't leak the socket — arduino-esp32 #1527 means connected()
// can lie about old sessions long after they've actually ended.
WiFiClient newClient = server.available();
if (newClient) {
if (LastClient) {
LastClient.stop();
}
LastClient = newClient;
lastClientByteMs = millis();
Serial.println("New Client.");
LastClient.println("studiolight1 ready");
}
if (LastClient && LastClient.connected()) {
while (LastClient.available()) {
char c = LastClient.read();
CmdMgr.handleChar(c);
Serial.write(c);
lastClientByteMs = millis();
}
// Idle-timeout: forcibly drop sessions that have gone silent. Protects
// against half-open TCP where connected() never returns false.
if (millis() - lastClientByteMs > CLIENT_IDLE_TIMEOUT_MS) {
Serial.println("Client idle — dropping");
LastClient.stop();
LastClient = WiFiClient();
}
} else if (LastClient) {
// connected() finally became false — clean up.
LastClient.stop();
LastClient = WiFiClient();
}
}