     return;
   Serial.read();  // Consume the copy msg;
   if (cmd.op == SET_PERIOD)  {
-    uint32_t new_period = kBrightnessToTime[cmd.value];
     switch (cmd.channel) {
-      case 0:
-        ssr1_setpoint_value = cmd.value;
-        ssr1_setpoint_us = new_period;
-        break;
-      case 1:
-        ssr2_setpoint_us = new_period;
-        ssr2_setpoint_value = cmd.value;
-        break;
-      case 2:
-        ssr3_setpoint_us = new_period;
-        ssr3_setpoint_value = cmd.value;
-        break;
-      case 3:
-        ssr4_setpoint_value = cmd.value;
-        ssr4_setpoint_us = new_period;
-        break;
-    }
-  } else if (cmd.op == SET_SMOOTHING) {
-    uint32_t new_smoothing = ((uint32_t) (cmd.value + 1)) << 2ul;
+      case 0: ch1.set_brightness(cmd.value); break;
+      case 1: ch2.set_brightness(cmd.value); break;
+      case 2: ch3.set_brightness(cmd.value); break;
+      case 3: ch4.set_brightness(cmd.value); break;
+    }
+  } else /* cmd.op == SET_SMOOTHING */ {
     switch (cmd.channel) {
-      case 0: ssr1_smoothing_us = new_smoothing; break;
-      case 1: ssr2_smoothing_us = new_smoothing; break;
-      case 2: ssr3_smoothing_us = new_smoothing; break;
-      case 3: ssr4_smoothing_us = new_smoothing; break;
+      case 0: ch1.set_duty_cycle_smoothing_rate(cmd.value); break;
+      case 1: ch2.set_duty_cycle_smoothing_rate(cmd.value); break;
+      case 2: ch3.set_duty_cycle_smoothing_rate(cmd.value); break;
+      case 3: ch4.set_duty_cycle_smoothing_rate(cmd.value); break;
     }
   }
   led2_state = led2_state ? LOW : HIGH;
   digitalWrite(LED2, led2_state);
 }
 
-void hm10_beacon_send_current_setpoints() {
+void hm10_beacon_send_current_brightness() {
   char buf[9];
-  sprintf(buf, "%02x%02x%02x%02x", ssr1_setpoint_value, ssr2_setpoint_value, ssr3_setpoint_value,ssr4_setpoint_value);
+  sprintf(buf,
+          "%02x%02x%02x%02x",
+          ch1.brightness(),
+          ch2.brightness(),
+          ch3.brightness(),
+          ch4.brightness());
   buf[9] = '\0';
   Serial.write(buf, sizeof(buf));
 }
 
-void StoreConfigToEEPROMIfChanged() {
-  if (!eeprom_is_ready())
-    return;
-  EEPROM.update(0, ssr1_setpoint_value);
-
-  if (!eeprom_is_ready())
-    return;
-  EEPROM.update(1, ssr2_setpoint_value);
-
-  if (!eeprom_is_ready())
-    return;
-  EEPROM.update(2, ssr3_setpoint_value);
-
-  if (!eeprom_is_ready())
-    return;
-  EEPROM.update(3, ssr4_setpoint_value);
-}
-
-void LoadConfigFromEEPROM() {
-  ssr1_setpoint_value = max(EEPROM.read(0), 31);
-  ssr2_setpoint_value = max(EEPROM.read(1), 31);
-  ssr3_setpoint_value = max(EEPROM.read(2), 31);
-  ssr4_setpoint_value = max(EEPROM.read(3), 31);
-
-  ssr1_period_us = kBrightnessToTime[ssr1_setpoint_value];
-  ssr2_period_us = kBrightnessToTime[ssr2_setpoint_value];
-  ssr3_period_us = kBrightnessToTime[ssr3_setpoint_value];
-  ssr4_period_us = kBrightnessToTime[ssr4_setpoint_value];
-
-  ssr1_setpoint_us = ssr1_period_us;
-  ssr2_setpoint_us = ssr2_period_us;
-  ssr3_setpoint_us = ssr3_period_us;
-  ssr4_setpoint_us = ssr4_period_us;
-}
-

  pinMode(LED2, OUTPUT);
  digitalWrite(LED2, LOW);

  pinMode(SSR1_PIN, OUTPUT);
  digitalWrite(SSR1_PIN, SSR_OFF);
  pinMode(SSR2_PIN, OUTPUT);
  digitalWrite(SSR2_PIN, SSR_OFF);
  pinMode(SSR3_PIN, OUTPUT);
  digitalWrite(SSR3_PIN, SSR_OFF);
  pinMode(SSR4_PIN, OUTPUT);
  digitalWrite(SSR4_PIN, SSR_OFF);
  pinMode(PIN_ZCROSS, INPUT);

  LoadConfigFromEEPROM();

  last_zerocross_isr_time = 0;
  last_zerocross_time = 0;

  ssr1_smoothing_us = INITIAL_SMOOTHING_US;
  ssr2_smoothing_us = INITIAL_SMOOTHING_US;
  ssr3_smoothing_us = INITIAL_SMOOTHING_US;
  ssr4_smoothing_us = INITIAL_SMOOTHING_US;

  ssr1_trigger_time = -1;
  ssr2_trigger_time = -1;
  ssr3_trigger_time = -1;
  ssr4_trigger_time = -1;

  Serial.begin(9600);
  initialize_hm10_beacon();
  attachInterrupt(digitalPinToInterrupt(PIN_ZCROSS), isr_zerocross, RISING);
}

#define ADJUST_SETPOINT(SSR) \
  do { \
    if (SSR##_period_us > SSR##_setpoint_us) { \
      uint32_t delta = SSR##_period_us - SSR##_setpoint_us; \
      SSR##_period_us -= min(delta, SSR##_smoothing_us); \
    } else if (SSR##_period_us < SSR##_setpoint_us) { \
      uint32_t delta = SSR##_setpoint_us - SSR##_period_us; \
      SSR##_period_us += min(delta, SSR##_smoothing_us); \
    }  \
  } while(0)


void loop()
{
  if (last_zerocross_isr_time != last_zerocross_time) {
    last_zerocross_time = last_zerocross_isr_time;
    ADJUST_SETPOINT(ssr1);
    ADJUST_SETPOINT(ssr2);
    ADJUST_SETPOINT(ssr3);
    ADJUST_SETPOINT(ssr4);
    ssr1_trigger_time = last_zerocross_time + ssr1_period_us;
    ssr2_trigger_time = last_zerocross_time + ssr2_period_us;
    ssr3_trigger_time = last_zerocross_time + ssr3_period_us;
    ssr4_trigger_time = last_zerocross_time + ssr4_period_us;

    ++num_cycles;
    digitalWrite(LED1, (num_cycles & 0x10) ? HIGH : LOW);
    if (num_cycles == 600)  {  // Every ~1 minute
      StoreConfigToEEPROMIfChanged();
      num_cycles = 0;
    } else if ((num_cycles & 63) == 32) {  // Every ~3.2 seconds.
      hm10_beacon_send_current_setpoints();
    }
  }

  uint32_t now = micros();
  if (now >= ssr1_trigger_time) {
    digitalWrite(SSR1_PIN, SSR_ON);
    ssr1_trigger_time = -1;
  }
  if (now >= ssr2_trigger_time) {
    digitalWrite(SSR2_PIN, SSR_ON);
    ssr2_trigger_time = -1;
  }
  if (now >= ssr3_trigger_time) {
    digitalWrite(SSR3_PIN, SSR_ON);
    ssr3_trigger_time = -1;
  }
  if (now >= ssr4_trigger_time) {
    digitalWrite(SSR4_PIN, SSR_ON);
    ssr4_trigger_time = -1;
  }

  hm10_beacon_receive();
}


void isr_zerocross() {
  digitalWrite(SSR1_PIN, SSR_OFF);
  digitalWrite(SSR2_PIN, SSR_OFF);
  digitalWrite(SSR3_PIN, SSR_OFF);
  digitalWrite(SSR4_PIN, SSR_OFF);
  last_zerocross_isr_time = micros();
}


void initialize_hm10_beacon() {
  for (;;) {

#define SSR2_PIN A1
#define SSR3_PIN A2
#define SSR4_PIN A3
#define INITIAL_SMOOTHING_US 2000

// Maps a brigthness value [0-31] to a duty-cycle value in the range
// [0-10000] ms (for 50 Hz mains), keeping into account the energy
// distribution of the mains sinewave.
const uint16_t kBrightnessDutyCycleMap[] = {
  11000, 8365, 7986, 7661, 7369, 7100, 6848, 6608,
  6377, 6155, 5938, 5725, 5516, 5309, 5103, 4897,
  4691, 4484, 4275, 4062, 3845, 3623, 3392, 3152,
  2900, 2631, 2339, 2014, 1635, 1150, 700, 0
};

// Data type for the messages exhanged via Bluetooth.
enum op_type_t { SET_PERIOD = 0, SET_SMOOTHING = 1 };
union cmd_t {
  byte raw;
  struct {
    byte value : 5;
    byte channel : 2;
    op_type_t op : 1;
  };
};

template<unsigned int PIN>
class DimmerChannel {
  public:
    void initialize() {
      pinMode(PIN, OUTPUT);
      digitalWrite(PIN, LOW);
      next_trigger_time_ = -1;
      brightness_ = EEPROM.read(PIN);
      if (brightness_ > 31)
        brightness_ = 0;
      target_duty_cycle_us_ = kBrightnessDutyCycleMap[brightness_];
      current_duty_cycle_us_ = kBrightnessDutyCycleMap[0];
      duty_cycle_smoothing_rate_us_ = INITIAL_SMOOTHING_US;
    }

    void on_zero_cross(const uint32_t& last_zerocross_time) {
      // Perform smoothing adjustement to converge towards the
      // |target_duty_cycle_us| set-point.
      if (current_duty_cycle_us_ > target_duty_cycle_us_) {
        uint16_t delta = current_duty_cycle_us_ - target_duty_cycle_us_;
        current_duty_cycle_us_ -= min(delta, duty_cycle_smoothing_rate_us_);
      } else if (current_duty_cycle_us_ < target_duty_cycle_us_) {
        uint16_t delta = target_duty_cycle_us_ - current_duty_cycle_us_;
        current_duty_cycle_us_ += min(delta, duty_cycle_smoothing_rate_us_);
      }

      // Calculate the absolute time for the next SSR trigger.
      next_trigger_time_ = last_zerocross_time + current_duty_cycle_us_;
    }

    void store_brightness_in_eeprom() {
      // Bail out if the EEPROM is busy and the write would block due to a prior
      // pending write. We cannot stall the SSR triggers for ~3ms (which is the
      // time that an EEPROM write cycle takes) as it would generate a visible
      // glitch.
      if (!eeprom_is_ready())
        return;
      EEPROM.update(PIN, brightness_);
    }

    void trigger_ssr_if_time(const uint32_t& now) {
      if (now >= next_trigger_time_) {
        digitalWrite(PIN, HIGH);
        next_trigger_time_ = -1;
      }
    }

    inline void reset_ssr() {
      digitalWrite(PIN, LOW);
    }

    inline uint8_t brightness() const {
       return brightness_;
    }

    void set_brightness(uint8_t value) {
      brightness_ = min(value, 31);
      target_duty_cycle_us_ = kBrightnessDutyCycleMap[brightness_];
    }

    inline uint16_t duty_cycle_smoothing_rate_us() const {
       return duty_cycle_smoothing_rate_us_;
    }

    void set_duty_cycle_smoothing_rate(uint8_t value) {
      duty_cycle_smoothing_rate_us_ = ((uint16_t) (value + 1)) << 2ul;
    }

  private:
    // Absolute time (w.r.t. micros()) for the next SSR trigger.
    uint32_t next_trigger_time_;

    // At every mains cycle |current_duty_cycle_us| is adjusted of at most
    // +- |duty_cycle_smoothing_rate_us| in order to converge towards
    // |target_duty_cycle_us|.
    uint16_t duty_cycle_smoothing_rate_us_;
    uint16_t current_duty_cycle_us_;

    // |target_duty_cycle_us| is |brightness| after the logarithmic
    // transformation to get the equivalent duty cycle [0-10.000] ms
    // in the mains sinewave.
    uint16_t target_duty_cycle_us_;
    uint8_t brightness_;
};

volatile uint32_t last_zerocross_isr_time;
volatile uint8_t zerocross_isr_flag;
uint16_t num_cycles;
uint8_t led2_state;
DimmerChannel<SSR1_PIN> ch1;
DimmerChannel<SSR2_PIN> ch2;
DimmerChannel<SSR3_PIN> ch3;
DimmerChannel<SSR4_PIN> ch4;

void setup()
{
