Skip to content

Instantly share code, notes, and snippets.

@philharlow
Created December 24, 2022 04:32
Show Gist options
  • Save philharlow/15892ba692e528348d1220a8a3a21597 to your computer and use it in GitHub Desktop.
Save philharlow/15892ba692e528348d1220a8a3a21597 to your computer and use it in GitHub Desktop.
#include "ESPHASDevice.h"
#include <Encoder.h>
#include <AccelStepper.h>
const String DEVICETYPE = "curtainopener"; // lowercase
const String VERSION = "v0.35";
const String EEPROMVERSION = "v0.10";
ESPHASDevice device;
EEPROMInt* currentPosition = device.AddSettingInt("currentPosition", 0);
EEPROMInt* openPosition = device.AddSettingInt("openPosition", 400);
EEPROMInt* stepsToEncoder = device.AddSettingInt("stepsToEncoder", 40);
EEPROMInt* maxSpeed = device.AddSettingInt("maxSpeed", 5000);
EEPROMInt* acceleration = device.AddSettingInt("acceleration", 3000);
EEPROMInt* minPulseWidth = device.AddSettingInt("minPulseWidth", 1);
EEPROMInt* quickUpDownThreshold = device.AddSettingInt("quickUpDownThreshold", 300);
EEPROMInt* programmingThreshold = device.AddSettingInt("programmingThreshold", 1000);
EEPROMInt* stallThreshold = device.AddSettingInt("stallThreshold", 5);
#define enablePin D0
#define dirPin D6
#define stepPin D7
// Update me!
#define openButtonPin D4
#define closeButtonPin D5
#define ledPin D8
Encoder myEnc(D1, D2);
AccelStepper stepper(1, stepPin, dirPin);
bool motorEnabled = false;
int motorCooldown = 0;
int targetPosition = 0;
int saveSettingsIn = 0;
int startingPositionOffset = 0;
int encoderPos = 0;
int lastEncoderPos = encoderPos;
bool inProgrammingMode = false;
int singleButtonHeldStartedAt = 0;
int bothButtonsHeldStartedAt = 0;
bool waitingForDepress = false;
int lastTugPosition = 0;
int unsetPosition = -100000;
int position1 = unsetPosition;
int position2 = unsetPosition;
int stalledTicks = 0;
//int connectingBlinkInterval = 3;
int programmingModeBlinkInterval = 5;
long ledCount = 0;
void updateLED(uint64_t now = 0)
{
bool ledOn = false;
if (inProgrammingMode)
{
ledOn = waitingForDepress || ledCount >= programmingModeBlinkInterval;
if (ledCount++ > programmingModeBlinkInterval * 2)
ledCount = 0;
} else {
ledOn = motorEnabled;
ledCount = 0;
}
analogWrite(ledPin, ledOn ? 100 : 0);
}
void sendStatus() {
int openPerc = 100 * clampf((float)targetPosition/(float)openPosition->Value, 0, 1);
device.publish(device.deviceName + "/open/status", openPerc > 0 ? "1" : "0");
device.publish(device.deviceName + "/brightness/status", String(openPerc));
}
void move(int delta, bool constrain = true)
{
int32 position = currentPosition->Value + delta;
if (constrain)
position = clamp(position, 0, openPosition->Value);
if (currentPosition->Value == position || targetPosition == position)
return;
delta = position - currentPosition->Value;
if (motorEnabled == false)
stepper.moveTo(stepper.currentPosition() + delta * stepsToEncoder->Value);
else
stepper.setTarget(stepper.currentPosition() + delta * stepsToEncoder->Value);
targetPosition = position;
motorEnabled = true;
stalledTicks = 0;
}
void setTargetPosition(int position, bool constrain = true)
{
if (constrain)
position = clamp(position, 0, openPosition->Value);
Serial.println("setTargetPosition position: " + String(position) + " currentPosition->Value: " + String(currentPosition->Value) + " targetPosition: " + String(targetPosition));
if (currentPosition->Value == position || targetPosition == position)
return;
targetPosition = position;
motorEnabled = true;
stalledTicks = 0;
Serial.println("setTargetPosition: " + String(targetPosition));
int delta = position - currentPosition->Value;
stepper.moveTo(stepper.currentPosition() + delta * stepsToEncoder->Value);
sendStatus();
}
void onMQTTConnected() {
sendStatus();
}
void onSettingsChanged() {
Serial.println("onSettingsChanged");
stepper.setMinPulseWidth(minPulseWidth->Value);
stepper.setMaxSpeed(maxSpeed->Value);
stepper.setAcceleration(acceleration->Value);
// Dont move on startup
//setTargetPosition(currentPosition->Value);
}
void setup() {
Serial.begin(115200);
pinMode(enablePin, OUTPUT);
digitalWrite(enablePin, HIGH);
pinMode(dirPin, OUTPUT);
pinMode(stepPin, OUTPUT);
pinMode(openButtonPin, INPUT_PULLUP);
pinMode(closeButtonPin, INPUT_PULLUP);
pinMode(ledPin, OUTPUT);
updateLED();
device.init(DEVICETYPE, VERSION, EEPROMVERSION);
device.subscribe(device.deviceName + "/open", handleOpen);
device.subscribe(device.deviceName + "/brightness", handleBrightness);
device.subscribe(device.deviceName + "/close", handleClose);
device.createTimer(100, true, stallCheck);
device.createTimer(100, true, updateLED);
device.onSettingsChanged = onSettingsChanged;
device.onMQTTConnected = onMQTTConnected;
delay(100);
bool openButtonPressed = digitalRead(openButtonPin) == LOW;
bool closeButtonPressed = digitalRead(closeButtonPin) == LOW;
if (openButtonPressed && closeButtonPressed)
device.resetSettings();
Serial.println("starting up: open: " + String(openButtonPressed ? "true" : "false") + String(" closed: ") + String(closeButtonPressed ? "true" : "false"));
//device.subscribe(DEVICETYPE + "/" + device.deviceName + "/" + displayTopic, handleDisplay);
encoderPos = 0;
startingPositionOffset = currentPosition->Value;
targetPosition = currentPosition->Value;
lastTugPosition = currentPosition->Value;
Serial.println("starting up: startingPositionOffset: " + String(startingPositionOffset));
//stepper.setEnablePin(enablePin);
onSettingsChanged();
stepper.setPinsInverted(true,true);
}
void disableMotor() {
Serial.println("motor disabled");
motorEnabled = false;
stalledTicks = 0;
targetPosition = currentPosition->Value;
lastTugPosition = currentPosition->Value;
motorCooldown = 10;
}
int clamp(int input, int minV, int maxV)
{
return max(minV, min(maxV, input));
}
float clampf(float input, float minV, float maxV)
{
return max(minV, min(maxV, input));
}
void handleOpen(String topic, String payload) {
payload.toLowerCase();
bool boolVal = payload == "1" || payload == "on";
float pos = boolVal ? 1 : 0;
setOpenPercentage(pos);
}
void handleBrightness(String topic, String payload) {
payload.toLowerCase();
float floatVal = clampf(payload.toInt(), 0, 100) / 100.0f;
setOpenPercentage(floatVal);
}
void handleClose(String topic, String payload) {
setTargetPosition(0);
}
// every 250 ms
void stallCheck(uint64_t now) {
int moveThreshold = 2;//_max(3, abs(stepper.speed() * 0.002f));
int32 delta = encoderPos - lastEncoderPos;
bool opening = targetPosition > currentPosition->Value;
bool moved = opening ? delta >= moveThreshold : delta <= -moveThreshold;
//Serial.println("delta > threshold: " + String(delta) + " > " + String(moveThreshold));
if (inProgrammingMode == false) {
// motor is on
if (motorEnabled) {
if (moved) {
stalledTicks = 0;
//Serial.println("Moving!!");
} else {
stalledTicks++;
Serial.println("Stalling!! " + String(stalledTicks));
if (stalledTicks > stallThreshold->Value) {
Serial.println("Stall detected!!");
disableMotor();
motorCooldown = 50;
stepper.stop();
}
}
} else { // motors is off
bool moved = abs(delta) >= moveThreshold;
if (moved && motorCooldown == 0) {
//Serial.println("moved encoderPos: " + String(encoderPos) + " currentPosition->Value" + String(currentPosition->Value));
int tugAmount = currentPosition->Value - lastTugPosition;
int tugThreshold = 3;
if (abs(tugAmount) >= tugThreshold) {
if (tugAmount > 0) {
Serial.println("opening from tug");
setOpenPercentage(1);
} else {
Serial.println("closing from tug");
setOpenPercentage(0);
}
lastTugPosition = currentPosition->Value;
}
}
}
}
if (moved)
lastEncoderPos = encoderPos;
if (motorCooldown > 0) {
motorCooldown--;
if (motorCooldown == 0)
lastTugPosition = currentPosition->Value;
}
}
void loop()
{
encoderPos = myEnc.read();
currentPosition->Value = encoderPos + startingPositionOffset;
stepper.run();
device.CanDoBlockingWork = motorEnabled == false;
device.loop();
handleButtons();
if (motorEnabled && stepper.distanceToGo() == 0) {
Serial.println("Reached end! currentPosition->Value: " + String(currentPosition->Value) + " stepper.currentPosition()/stepsToEncoder " + String(stepper.currentPosition() / stepsToEncoder->Value));
disableMotor();
}
if (inProgrammingMode)
saveSettingsIn = 0;
else if (motorEnabled) {
saveSettingsIn = 100000;
}
digitalWrite(enablePin, motorEnabled ? LOW :HIGH);
if (saveSettingsIn > 0)
{
saveSettingsIn--;
if (saveSettingsIn == 0) {
sendStatus();
device.SaveSettings();
}
}
}
bool wasOpenButtonPressed = false;
bool wasCloseButtonPressed = false;
void handleButtons() {
unsigned long now = millis();
bool openButtonPressed = digitalRead(openButtonPin) == LOW;
bool closeButtonPressed = digitalRead(closeButtonPin) == LOW;
int pressedCount = openButtonPressed + closeButtonPressed;
if (pressedCount == 1) {
bothButtonsHeldStartedAt = 0;
if (singleButtonHeldStartedAt == 0) {
singleButtonHeldStartedAt = now;
//Serial.println("down " + String(openButtonPressed ? "open" : "close"));
}
unsigned long delta = now - singleButtonHeldStartedAt;
unsigned long threshold = quickUpDownThreshold->Value;
//Serial.println("pressed single button for: " + String(delta) + " threshold: " + String(threshold));
bool buttonHeld = delta >= threshold;
// Held, manually move
if (buttonHeld && waitingForDepress == false) {
int adjustedDelta = _max(delta - threshold, 0);
//Serial.println("delta: " + String(delta) + " adjustedDelta: " + String(adjustedDelta));
adjustedDelta /= 50;
int moveAmount = clamp(adjustedDelta, 5, 50);
//Serial.println("held or programming. adjustedDelta: " + String(adjustedDelta) + " moveAmount: " + String(moveAmount));
if (openButtonPressed)
move(moveAmount, inProgrammingMode == false);
if (closeButtonPressed)
move(-moveAmount, inProgrammingMode == false);
}
}
else if (pressedCount == 2)
{
stepper.stop();
singleButtonHeldStartedAt = 0;
if (bothButtonsHeldStartedAt == 0) {
bothButtonsHeldStartedAt = now;
}
unsigned long delta = now - bothButtonsHeldStartedAt;
if (delta > programmingThreshold->Value && waitingForDepress == false) {
waitingForDepress = true;
if (inProgrammingMode) {
Serial.println("programming mode tap");
// save first pos
if (position1 == unsetPosition)
{
Serial.print("Setting position 1 to: ");
Serial.println(currentPosition->Value);
position1 = encoderPos;// settings.currentPosition->Value;
}
else // second pos
{
Serial.print("Setting position 2 to: ");
Serial.println(currentPosition->Value);
position2 = encoderPos;// settings.currentPosition->Value;
// settings.currentPosition->Value = encoderPos + startingPositionOffset;
// now closed, pos1 = top/open
if (position1 > position2)
{
openPosition->Value = position1 - position2;
currentPosition->Value = 0;
}
// now open, pos2 = top/open
else
{
openPosition->Value = position2 - position1;
currentPosition->Value = openPosition->Value;
}
startingPositionOffset = currentPosition->Value - encoderPos;
targetPosition = currentPosition->Value;
lastTugPosition = currentPosition->Value;
Serial.print("Saving new settings! openPosition: ");
Serial.println(openPosition->Value);
inProgrammingMode = false;
device.SaveSettings();
}
} else {
inProgrammingMode = true;
position1 = unsetPosition;
position2 = unsetPosition;
Serial.println("starting programming mode");
}
bothButtonsHeldStartedAt = 0;
}
}
else {
// On release, check for short press
if (inProgrammingMode == false && singleButtonHeldStartedAt > 0) {
unsigned long delta = now - singleButtonHeldStartedAt;
unsigned long threshold = quickUpDownThreshold->Value;
//Serial.println("pressed single button for: " + String(delta) + " threshold: " + String(threshold));
bool buttonHeld = delta >= threshold;
// Held, manually move
if (buttonHeld == false && waitingForDepress == false) {
if (wasOpenButtonPressed) {
Serial.println("opening from button");
setOpenPercentage(1);
}
if (wasCloseButtonPressed) {
Serial.println("closing from button");
setOpenPercentage(0);
}
}
}
singleButtonHeldStartedAt = 0;
bothButtonsHeldStartedAt = 0;
waitingForDepress = false;
}
wasOpenButtonPressed = openButtonPressed;
wasCloseButtonPressed = closeButtonPressed;
}
void setOpenPercentage(float percentage)
{
int newTarget = percentage * (float)openPosition->Value;
newTarget = clamp(newTarget, 0, openPosition->Value);
if (currentPosition->Value < 0 && newTarget == 0)
return;
if (currentPosition->Value > openPosition->Value && newTarget == openPosition->Value)
return;
setTargetPosition(newTarget);
//Serial.print("newTarget: ");
//Serial.println(newTarget);
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment