arduino-shutters icon indicating copy to clipboard operation
arduino-shutters copied to clipboard

Multi-shutter example

Open phretor opened this issue 6 years ago • 16 comments

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.

phretor avatar Apr 29 '18 19:04 phretor

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.

ludodoucet avatar Jun 04 '18 18:06 ludodoucet

Hello ludodoucet, what kind of hardware do you use for your project ?

Gis70 avatar Jun 06 '18 19:06 Gis70

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...

ludodoucet avatar Jun 14 '18 00:06 ludodoucet

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 😉

marvinroger avatar Jul 13 '18 08:07 marvinroger

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 shuttersOperationHandler(1, 1) (shutter should go UP, despite 0 is the set point).

#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:

Initializing shutter 1
reading 20 bytes from /shutter-state-1
Opening: /shutter-state-1
shutter 1 state = '00008246337392649056��?'
shuttersWriteStateHandler(1, 20)
Opening: /shutter-state-1
State of shutter 1 written to /shutter-state-1 (20 bytes written)
shutter 1 state = '00008246337392649056'
Shutter 1 initialized!
Initializing shutter 2
reading 20 bytes from /shutter-state-2
Opening: /shutter-state-2
shutter 2 state = '00008246337392640896��?'
shuttersWriteStateHandler(2, 20)
Opening: /shutter-state-2
State of shutter 2 written to /shutter-state-2 (20 bytes written)
shutter 2 state = '00008246337392640896'
Shutter 2 initialized!
💡 Firmware awesome-velux (1.1.1)
🔌 Booting into normal mode 🔌
{} Stored configuration
  • Hardware device ID: 6001940ea484
  • Device ID: studio
  • Name: Studio
  • Device Stats Interval: 60 sec
  • Wi-Fi:
    ◦ SSID: ***********
    ◦ Password not shown
  • MQTT:
    ◦ Host: mqtt
    ◦ Port: 1883
    ◦ Base topic: homie/
    ◦ Auth? no
  • OTA:
    ◦ Enabled? yes
  • Custom settings:
    ◦ interval: 10 (set)
    ◦ push_release_delay: 4 (set)
↕ Attempting to connect to Wi-Fi...
✔ Wi-Fi connected, IP: 192.168.42.241
Triggering WIFI_CONNECTED event...
↕ Attempting to connect to MQTT...
Sending initial information...
✔ MQTT ready
Triggering MQTT_READY event...
Calling setup function...
setup handler completed
〽 Sending statistics...
  • Wi-Fi signal quality: 100%
  • Uptime: 1s
shuttersOperationHandler(1, 1)
Shutter index = 0 / PIN index = 0
Shutters 1 going UP (relay pin: 16)
Enqueued 16
Dequeue 16
clickButton(16)
pushButton(16)
Node strip not registered
Node strip not registered
Node strip not registered
Node strip not registered
Node blind not registered
Node blind not registered
shuttersCommandHandler(1, CLOSE)
shuttersLevelHandler(1, 0)
releaseButton(16)
Temp: 24.77°C
Humidity: 49.11%
Pressure: 1000.82hPa
Altitude: 103.99m
Shutter 1 is at 255%
Shutter 2 is at 0%
Shutter 1 is NONE (255%)
Shutter 2 is CLOSED (0%)
Temp: 24.77°C
Humidity: 49.12%
Pressure: 1000.84hPa
Altitude: 103.80m
Shutter 1 is at 255%
Shutter 2 is at 0%
Shutter 1 is NONE (255%)
Shutter 2 is CLOSED (0%)
Temp: 24.77°C
Humidity: 49.17%
Pressure: 1000.89hPa
Altitude: 103.39m
Shutter 1 is at 255%
Shutter 2 is at 0%
Shutter 1 is NONE (255%)
Shutter 2 is CLOSED (0%)
shuttersOperationHandler(1, 3)
Shutter index = 0 / PIN index = 2
Shutters 1 HALTing (relay pin: 5)
Enqueued 5
shuttersWriteStateHandler(1, 20)
Opening: /shutter-state-1
State of shutter 1 written to /shutter-state-1 (20 bytes written)
shutter 1 state = '00008246337392640896'
Shutter 1 level 0 reached
Dequeue 5
clickButton(5)
pushButton(5)
releaseButton(5)
shuttersWriteStateHandler(1, 20)
Opening: /shutter-state-1
State of shutter 1 written to /shutter-state-1 (20 bytes written)
shutter 1 state = '00008246337392649056'
shuttersOperationHandler(1, 1)
Shutter index = 0 / PIN index = 0
Shutters 1 going UP (relay pin: 16)
Enqueued 16
Dequeue 16
clickButton(16)
pushButton(16)
Temp: 24.77°C
Humidity: 49.13%
Pressure: 1000.85hPa
Altitude: 103.76m
Shutter 1 is at 0%
Shutter 2 is at 0%
Shutter 1 is CLOSED (0%)
Shutter 2 is CLOSED (0%)
releaseButton(16)
Shutter 1 level 255 reached
shuttersOperationHandler(1, 1)
Shutter index = 0 / PIN index = 0
Shutters 1 going UP (relay pin: 16)
Enqueued 16
Dequeue 16
clickButton(16)
pushButton(16)
releaseButton(16)
Temp: 24.78°C
Humidity: 49.17%
Pressure: 1000.80hPa
Altitude: 104.15m
Shutter 1 is at 255%
Shutter 2 is at 0%
Shutter 1 is NONE (255%)
Shutter 2 is CLOSED (0%)
Temp: 24.78°C
Humidity: 49.14%
Pressure: 1000.87hPa
Altitude: 103.69m
Shutter 1 is at 255%
Shutter 2 is at 0%
Shutter 1 is NONE (255%)
Shutter 2 is CLOSED (0%)
〽 Sending statistics...
  • Wi-Fi signal quality: 100%
  • Uptime: 61s
Temp: 24.78°C
Humidity: 49.13%
Pressure: 1000.86hPa
Altitude: 103.66m
Shutter 1 is at 255%
Shutter 2 is at 0%
Shutter 1 is NONE (255%)
Shutter 2 is CLOSED (0%)
shuttersOperationHandler(1, 3)
Shutter index = 0 / PIN index = 2
Shutters 1 HALTing (relay pin: 5)
Enqueued 5
shuttersWriteStateHandler(1, 20)
Opening: /shutter-state-1
State of shutter 1 written to /shutter-state-1 (20 bytes written)
shutter 1 state = '00008246337392640896'
Shutter 1 level 0 reached
Dequeue 5
clickButton(5)
pushButton(5)

[...]

phretor avatar Oct 03 '18 22:10 phretor

Hi, @phretor, it can be a long shot (I'm not familiar with Homie framework), but can it be, that your buttons definitions

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 */
};

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 avatar Oct 04 '18 05:10 kluszczyn

@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 shuttersOperationHandler could be triggered by glitches in the I2C bus?

phretor avatar Oct 11 '18 09:10 phretor

Hi, Yes, it is my initial suspect, that i2c communication triggers all the time shutter. When I was dealing with initial version of shutter library, value 255 was indicating that rollershutter was not calibrated, eg full cycle open or close was not performed to set 0 and 100% 'boundaries'. I can't confirm if this is the case here, but assuming that i2c triggers all the time shutter, it has no chance to make calibration cycle.

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 avatar Oct 11 '18 16:10 kluszczyn

@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 /shutter-state-1 and /shutter-state-2 respectively. I notice, however, that for shutter 1, the state keeps on changing between 00008246337392649056 and 00008246337392640896, which I think is the culprit.

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!

phretor avatar Oct 12 '18 21:10 phretor

Thanks for sharing and congratulations for tracing down issue! Having multi-shutter sketch as a part of library will help others to write their own project easily.

kluszczyn avatar Oct 15 '18 16:10 kluszczyn

@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.

phretor avatar Oct 15 '18 16:10 phretor

Hi, @phretor, it can be a long shot (I'm not familiar with Homie framework), but can it be, that your buttons definitions

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 */
};

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).

hello @kluszczyn you setup is similar to mine... can you share your code? thank you

vitucciog avatar Feb 01 '19 20:02 vitucciog

@kluszczyn>

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).

Hi. Any chance you would share your code to see how multi-shutters are implemented ?

hary66 avatar Jun 09 '21 18:06 hary66

Hi there! It's been a long time and in the meantime I've switched to other framework (esphome.io), so I do not have working instance of the shared below code and will not be able to help.

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
It was based on the MySensors framework + Arduino Mega 2560. It is really messy, with mixed comments in Polish/English - you've been warned ;) - but in the nutshell it follows idea shared by library author https://github.com/marvinroger/arduino-shutters/issues/20#issuecomment-404766713 - @hary66, @vitucciog I really recommend to start with this + example and adapt it to yours needs. Good luck!

kluszczyn avatar Jun 10 '21 17:06 kluszczyn

@kluszczyn Thanks very much for responding. Did you find a working multi shutters library in esphome.io to replace @marvinroger 's library which work with percentage opening ?

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.

hary66 avatar Jun 10 '21 18:06 hary66

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 restoreState call

marvinroger avatar Jun 10 '21 22:06 marvinroger