-
Notifications
You must be signed in to change notification settings - Fork 17
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Multi-shutter example #20
Comments
https://github.com/ludodoucet/4Shutters-ArduinoMega/blob/master/src/Railduino_4Volets.cpp an sample with 4 shutters (and MQTT features) don't use the actual lib's folder, it is out to date. |
Hello ludodoucet, what kind of hardware do you use for your project ? |
http://www.sedtronic.cz/en_about-module-railduino-v1.3,78.html here https://github.com/ludodoucet/4Shutters-ArduinoMega/blob/master/src/Railduino_4Volets.cpp the code is in beta, because i have any bugs ( my wife don't like this bugs) with the new lib, if you what the stable code ask me... |
Hi everyone, Sorry for the late response, but to handle multiple shutters from the same callback, it's pretty simple. Let's take the example code: Shutters shutters1;
Shutters shutters2;
void shuttersOperationHandler(Shutters* s, ShuttersOperation operation) {
if (s == &shutters1) {
// this callback was called from the shutters1
} else if (s == &shutters2) {
// this callback was called from the shutters2
}
switch (operation) {
case ShuttersOperation::UP:
Serial.println("Shutters going up.");
// TODO: Implement the code for the shutters to go up
break;
case ShuttersOperation::DOWN:
Serial.println("Shutters going down.");
// TODO: Implement the code for the shutters to go down
break;
case ShuttersOperation::HALT:
Serial.println("Shutters halting.");
// TODO: Implement the code for the shutters to halt
break;
}
} That's it 😉 |
Thanks @marvinroger. I've finally came up with a complete multi-shutter example. The code handles a temp/hum/press sensor too, which doesn't really matter for this example (just ignore it). The issue I'm facing is that shutter 1 never reaches 0. It keeps on bouncing between 0 and 255 (NONE) for no real reason: as you can see from the log, right after reading from the stored state, the level is correctly reported as zero. However, right after it, the loop calls #include <Shutters.h>
#include <FS.h>
#include <Homie.h>
#include <Wire.h>
#include <Adafruit_Sensor.h>
#include <Adafruit_BME280.h>
#include <cppQueue.h>
#include <Ticker.h>
#define IMPLEMENTATION FIFO
#define BME_ADDR1 0x76
#define BME_ADDR2 0x77
#define SEALEVELPRESSURE_HPA (1013.25)
#define SHUTTER_STATE_FILE_PREFIX "/shutter-state"
Ticker shutter_level_ticker;
Ticker shutter_state_ticker;
Ticker sensor_ticker;
const unsigned char NUMBER_OF_BUTTONS = 3;
const unsigned char NUMBER_OF_SHUTTERS = 2;
const unsigned long upCourseTime = 30 * 1000;
const unsigned long downCourseTime = 45 * 1000;
const float calibrationRatio = 0.1;
// Shutter command handling
unsigned long last_click = 0;
unsigned long click_delay = 1000;
unsigned char clicking = 0;
Queue q(sizeof(unsigned char), 10, IMPLEMENTATION);
// FS
bool disk = false;
Adafruit_BME280 bme;
HomieNode shutterNode("shutter", "shutter");
HomieNode tempNode("temperature", "degrees");
HomieNode humidityNode("humidity", "relative");
HomieNode pressureNode("pressure", "hectopascals");
HomieNode altitudeNode("altitude", "meters");
/*
* Right blind (1)
* ===========
* Relay 1 == right, up
* Relay 3 == right, down
* Relay 2 == right, stop
*/
/* Left blind (2)
* ==========
* Relay 4 == left, up
* Relay 6 == left, down
* Relay 5 == left, stop
*/
/*
From: https://github.com/esp8266/Arduino/issues/584
static const uint8_t D0 = 16;
static const uint8_t D1 = 5;
static const uint8_t D2 = 4;
static const uint8_t D3 = 0;
static const uint8_t D4 = 2;
static const uint8_t D5 = 14;
static const uint8_t D6 = 12;
static const uint8_t D7 = 13;
static const uint8_t D8 = 15;
static const uint8_t D9 = 3;
static const uint8_t D10 = 1;
*/
/*
From: https://github.com/marvinroger/arduino-shutters/blob/master/src/ShuttersOperation.hpp
enum class ShuttersOperation : uint8_t {
UP = 1,
DOWN = 2,
HALT = 3
};
*/
unsigned char shutterOpToPin[NUMBER_OF_SHUTTERS][NUMBER_OF_BUTTONS] = {
{D0, D2, D1}, /* 16, 4, 5 */ /* UP, DOWN, HALT */
{D5, D7, D6} /* 14, 13, 12 */ /* UP, DOWN, HALT */
};
Shutters s1;
Shutters s2;
Shutters* shutterArray[NUMBER_OF_SHUTTERS] = { &s1, &s2 };
// custom settings
HomieSetting<long> intervalSetting(
"interval", "How often should this device send data (in seconds)");
HomieSetting<long> pushReleaseSetting(
"push_release_delay", "How long should a button remain pressed (in seconds)");
void sendSensorData() {
float temp(NAN), hum(NAN), pres(NAN), alt(NAN);
temp = bme.readTemperature();
pres = bme.readPressure() / 100.0F;
alt = bme.readAltitude(SEALEVELPRESSURE_HPA);
hum = bme.readHumidity();
Homie.getLogger() << "Temp: " << temp << "°C" << endl;
tempNode.setProperty("degrees").send(String(temp));
Homie.getLogger() << "Humidity: " << hum << "%" << endl;
humidityNode.setProperty("relative").send(String(hum));
Homie.getLogger() << "Pressure: " << pres << "hPa" << endl;
pressureNode.setProperty("hectopascals").send(String(pres));
Homie.getLogger() << "Altitude: " << alt << "m" << endl;
altitudeNode.setProperty("meters").send(String(alt));
}
void sendShutterLevel() {
for (size_t si = 1; si <= NUMBER_OF_SHUTTERS; si++) {
Shutters* s = resolveShutter(si);
uint8_t level = (*s).getCurrentLevel();
Homie.getLogger() << "Shutter " << si << " is at " << level << "%" << endl;
shutterNode.setProperty("level").setRange(si).send(String(level));
}
}
void sendShutterState() {
for (size_t si = 1; si <= NUMBER_OF_SHUTTERS; si++) {
Shutters* s = resolveShutter(si);
uint8_t level = (*s).getCurrentLevel();
String state = "UNKNOWN";
if (level == 0)
state = "CLOSED";
else if (level <= 100)
state = "OPEN";
else if (level == 255)
state = "NONE";
Homie.getLogger() << "Shutter " << si << " is " << state << " (" << level << "%)" << endl;
shutterNode.setProperty("state").setRange(si).send(state);
}
}
bool shuttersCommandHandler(const HomieRange& range, const String& value) {
Homie.getLogger() << "shuttersCommandHandler(" << range.index << ", " << value << ")" << endl;
if (value == "STOP" || value == "stop") {
Shutters* shut = resolveShutter(range.index);
(*shut).stop();
return true;
}
if (value == "OPEN" || value == "open")
return shuttersLevelHandler(range, "100");
if (value == "CLOSE" || value == "close")
return shuttersLevelHandler(range, "0");
return false;
}
bool shuttersLevelHandler(const HomieRange& range, const String& value) {
Homie.getLogger() << "shuttersLevelHandler(" << range.index << ", " << value << ")" << endl;
for (byte i = 0; i < value.length(); i++)
if (isDigit(value.charAt(i)) == false)
return false;
const unsigned long numericValue = value.toInt();
if (numericValue > 100) return false;
// wanted value is valid
Shutters* shut = resolveShutter(range.index);
if ((*shut).isIdle() &&
numericValue == (*shut).getCurrentLevel())
return true; // nothing to do
Homie.setIdle(false);
(*shut).setLevel(numericValue);
return true;
}
void resetButtons() {
for (size_t i = 0; i < NUMBER_OF_SHUTTERS; i++)
for (size_t j = 0; j < NUMBER_OF_BUTTONS; j++) {
pinMode(shutterOpToPin[i][j], OUTPUT);
digitalWrite(shutterOpToPin[i][j], HIGH);
}
}
void pushButton(const unsigned char pin) {
Homie.getLogger() << "pushButton(" << pin << ")" << endl;
digitalWrite(pin, LOW);
}
void releaseButton(const unsigned char pin) {
Homie.getLogger() << "releaseButton(" << pin << ")" << endl;
digitalWrite(pin, HIGH);
clicking = 0;
}
void clickButton(const unsigned char pin) {
Homie.getLogger() << "clickButton(" << pin << ")" << endl;
pushButton(pin);
//timer.once(pushReleaseSetting.get(), releaseButton, pin);
delay(pushReleaseSetting.get() * 1000);
releaseButton(pin);
}
void clickButtonAsync(const unsigned char pin) {
if (q.isFull()) return; // too many clicks enqueued already
Homie.getLogger() << "Enqueued " << pin << endl;
q.push(&pin);
}
void clickButtonLoop() {
if (
millis() - last_click >= click_delay && // if prev click is far enough in the past
!(q.isEmpty()) && // we have enqueued clicks
clicking == 0) // not clicking any other pin already
{
last_click = millis();
q.pop(&clicking);
Homie.getLogger() << "Dequeue " << clicking << endl;
if (clicking != 0) clickButton(clicking);
}
}
void onShuttersLevelReached(Shutters* s, byte level) {
int si = getShutterIndex(s);
Homie.getLogger() << "Shutter " << si << " level " << level << " reached" << endl;
if ((*s).isIdle()) Homie.setIdle(true); // if idle, we've reached our target
if (Homie.isConnected())
shutterNode.setProperty("level").setRange(si).send(String(level));
}
int getShutterIndex(Shutters* s) {
for (size_t i = 1; i <= NUMBER_OF_SHUTTERS; i++)
if (s == &s1)
return 1;
if (s == &s2)
return 2;
return -1;
}
Shutters* resolveShutter(int index) {
return shutterArray[index-1];
}
void shuttersOperationHandler(Shutters* s, ShuttersOperation operation) {
int si = getShutterIndex(s);
if (si == -1) return;
const int pin_index = ((unsigned int)operation)-1;
const int shutter_index = si-1;
Homie.getLogger() << "shuttersOperationHandler(" << si << ", "
<< (unsigned int)operation << ")" << endl;
Homie.getLogger() << "Shutter index = " << shutter_index << " / "
<< "PIN index = " << pin_index << endl;
unsigned char pin = shutterOpToPin[shutter_index][pin_index];
switch (operation) {
case ShuttersOperation::UP:
Homie.getLogger() << "Shutters " << si << " going UP (relay pin: " << pin << ")" << endl;
break;
case ShuttersOperation::DOWN:
Homie.getLogger() << "Shutters " << si << " going DOWN (relay pin: " << pin << ")" << endl;
break;
case ShuttersOperation::HALT:
Homie.getLogger() << "Shutters " << si << " HALTing (relay pin: " << pin << ")" << endl;
break;
default: return;
}
if (!pin) return;
clickButtonAsync(pin);
}
void shuttersReadState(Shutters* shutters, char* dest, byte length) {
int si = getShutterIndex(shutters);
char fpath[sizeof(SHUTTER_STATE_FILE_PREFIX) + 2];
snprintf(fpath, sizeof(SHUTTER_STATE_FILE_PREFIX) + 2, "%s-%d", SHUTTER_STATE_FILE_PREFIX, si);
Homie.getLogger() << "reading " << length << " bytes from " << fpath << endl;
if (!disk) {
Homie.getLogger() << "FS is not ready" << endl;
return;
}
Homie.getLogger() << "Opening: " << fpath << endl;
File f = SPIFFS.open(fpath, "r");
if (!f) {
Homie.getLogger() << "Error opening " << fpath << " in read mode" << endl;
for (size_t i = 0; i < length; i++) dest[i] = 0;
return;
}
f.read((uint8_t*)dest, length);
f.close();
Homie.getLogger() << "shutter " << si << " state = '" << dest << "'" << endl;
}
void shuttersWriteStateHandler(Shutters* shutters, const char* state, byte length) {
int si = getShutterIndex(shutters);
Homie.getLogger() << "shuttersWriteStateHandler(" << si << ", " << length << ")" << endl;
char fpath[sizeof(SHUTTER_STATE_FILE_PREFIX) + 2];
snprintf(fpath, sizeof(SHUTTER_STATE_FILE_PREFIX) + 2, "%s-%d", SHUTTER_STATE_FILE_PREFIX, si);
if (!disk) {
Homie.getLogger() << "FS is not ready" << endl;
return;
}
Homie.getLogger() << "Opening: " << fpath << endl;
File f = SPIFFS.open(fpath, "w");
if (!f) {
Homie.getLogger() << "Error opening " << fpath << " in write mode" << endl;
return;
}
length = f.write((uint8_t*)state, length);
f.close();
Homie.getLogger() << "State of shutter " << si << " written to "
<< fpath << " (" << length << " bytes written)" << endl;
Homie.getLogger() << "shutter " << si << " state = '" << state << "'" << endl;
}
void loopHandler() {
clickButtonLoop();
s1.loop();
s2.loop();
}
void setupHandler() {
tempNode.setProperty("unit").send("c");
humidityNode.setProperty("unit").send("%");
pressureNode.setProperty("unit").send("hPa");
altitudeNode.setProperty("unit").send("m");
sensor_ticker.attach(intervalSetting.get(), sendSensorData);
shutter_level_ticker.attach(intervalSetting.get(), sendShutterLevel);
shutter_state_ticker.attach(intervalSetting.get(), sendShutterState);
Homie.getLogger() << "setup handler completed" << endl;
}
void setup() {
resetButtons();
Serial.begin(115200);
while (!Serial) {
; // wait for serial port to connect. Needed for Leonardo only
}
// FS
disk = SPIFFS.begin();
// BME280
// ------
//
// D3 = SDA
// D4 = SCL
Wire.begin(D3, D4);
if (!bme.begin(BME_ADDR1)) {
Homie.getLogger() << "Could not find a valid BME280 sensor at " <<
BME_ADDR1 << endl;
}
Homie_setFirmware("awesome-velux", "1.1.1");
delay(100);
Homie.disableLedFeedback();
intervalSetting.setDefaultValue(10).setValidator([] (long candidate) {
return (candidate >= 1) && (candidate <= 3600);
});
pushReleaseSetting.setDefaultValue(4).setValidator([] (long candidate) {
return (candidate >= 1) && (candidate <= 30);
});
shutterNode
.advertiseRange("level", 1, NUMBER_OF_SHUTTERS)
.settable(shuttersLevelHandler);
shutterNode
.advertiseRange("command", 1, NUMBER_OF_SHUTTERS)
.settable(shuttersCommandHandler);
shutterNode
.advertiseRange("state", 1, NUMBER_OF_SHUTTERS);
tempNode.advertise("unit");
tempNode.advertise("degrees");
humidityNode.advertise("unit");
humidityNode.advertise("relative");
pressureNode.advertise("unit");
pressureNode.advertise("hectopascals");
altitudeNode.advertise("unit");
altitudeNode.advertise("meters");
// initialize shutters
for (size_t i = 1; i <= NUMBER_OF_SHUTTERS; i++) {
Homie.getLogger() << "Initializing shutter " << i << endl;
Shutters* shut = resolveShutter(i);
char storedShuttersState[(*shut).getStateLength()];
shuttersReadState(shut, storedShuttersState, (*shut).getStateLength());
(*shut)
.setOperationHandler(shuttersOperationHandler)
.setWriteStateHandler(shuttersWriteStateHandler)
.restoreState(storedShuttersState)
.setCourseTime(upCourseTime, downCourseTime)
.onLevelReached(onShuttersLevelReached)
.begin()
.setLevel(0);
Homie.getLogger() << "Shutter " << i << " initialized!" << endl;
}
Homie
.setLoopFunction(loopHandler)
.setSetupFunction(setupHandler);
delay(100);
Homie.setup();
}
void loop() {
Homie.loop();
} And here is the log:
|
Hi,
interferes with I2C bus (pins 4,5 in standard esp config)? I can confirm, that multi-shutters setup based on @marvinroger example is working without problems in my case (11 shutters, arduino mega, mysensors framework for communication). |
@kluszczyn good point, and thanks for confirming that the example code works. However, I think it doesn't have to do with the actuation. I mean, the actuation (HIGH/LOW on pins) is correct. What's not correctly stored is the position, as it's always getting back to 255. Do you think that the call to |
Hi, Other issue can be with writing/reading shutter state. If you writing to diffrent memory area you are reading then shutter behaves like no calibrated. You have one channel working, so it is validating your code. Now you have to just find diff and fix it ;) Good luck and let us know results! |
@kluszczyn it has to be something else, because I've tried to swap the button definition from: unsigned char shutterOpToPin[NUMBER_OF_SHUTTERS][NUMBER_OF_BUTTONS] = {
{D0, D2, D1}, /* 16, 4, 5 */ /* UP, DOWN, HALT */
{D5, D7, D6} /* 14, 13, 12 */ /* UP, DOWN, HALT */
}; to unsigned char shutterOpToPin[NUMBER_OF_SHUTTERS][NUMBER_OF_BUTTONS] = {
{D5, D7, D6}, /* 14, 13, 12 */ /* UP, DOWN, HALT */
{D0, D2, D1} /* 16, 4, 5 */ /* UP, DOWN, HALT */
}; if it where a glitch caused by the I2C pins, I'd expect shutter 2 (not ) to go from 0 to 255 and then 0 all the time. Instead, I'm seeing the same exact behavior. About your point on how data is stored, I use SPIFFS on Digging deeper, I found out that the file for shutter 1 wasn't being written correctly. So I've implemented a handler to "forget" the state of a shutter (i.e., delete the file) and reboot. From thereinafter it started behaving as expected. I'm going to close this and open a pull request with my example. Thanks! |
Thanks for sharing and congratulations for tracing down issue! |
@kluszczyn I think I've screamed "victory" too early - that's why I haven't pushed anything so far. I've noticed that I can't reliably reset the library so I have to do more tests. I've switched (back) to EEPROM for persistency, after ruling out that SPIFFS wasn't the root cause. I'll post something as soon as I get it working again. |
hello @kluszczyn you setup is similar to mine... can you share your code? thank you |
Hi. |
Hi there! Here is the multi-shutter code I've found from the past time (code was using lib ver. version=3.0.0-beta.4) - https://gist.github.com/kluszczyn/3affffd9b7bad928dd644356ab7fb715 |
@kluszczyn The given example instantiate only 1 shutter ! I really don't see how to instantiate multiple shutters with their own up and down command, their own up /downCourseTime etc ... I really not a coder and need very detailed example to be able to understand how to use others code. A shame this library doesn't seem to be used and commented a lot in different forum. |
Hi @hary66 ! You can instantiate 2 shutters, sharing the same "handlers". Each handler receives as its first parameter the pointer of the shutters instance the handler should take action on. For example: Shutters shutters1;
Shutters shutters2;
void shuttersOperationHandler(Shutters* s, ShuttersOperation operation) {
switch (operation) {
case ShuttersOperation::UP:
if (s == &shutters1) {
// this callback was called from the shutters1
Serial.println("Shutters 1 going up.");
} else if (s == &shutters2) {
// this callback was called from the shutters2
Serial.println("Shutters 2 going up.");
}
break;
case ShuttersOperation::DOWN:
if (s == &shutters1) {
// this callback was called from the shutters1
Serial.println("Shutters 1 going down.");
} else if (s == &shutters2) {
// this callback was called from the shutters2
Serial.println("Shutters 2 going down.");
}
break;
case ShuttersOperation::HALT:
if (s == &shutters1) {
// this callback was called from the shutters1
Serial.println("Shutters 1 halting.");
} else if (s == &shutters2) {
// this callback was called from the shutters2
Serial.println("Shutters 2 halting.");
}
break;
}
} Also, don't forget to update the writeStateHandler to write to a different EEPROM offset depending on the shutters (e.g. your shutters 1 can write on bytes 0 to 19, and your shutters 2 on bytes 20 to 39). Same thing for the |
I've looked at the code and the example, but I can't figure out how a multi-shutter scenario is supposed to be implemented.
Of course I'm free to do whatever I want using callbacks, but I'd like to understand better whether the API supports handling (de)multiplexing (from)to multiple shutters.
The text was updated successfully, but these errors were encountered: