Skip to content
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

adding manual WC calibration. #449

Open
wants to merge 7 commits into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
22 changes: 22 additions & 0 deletions fs_src/index.html
Original file line number Diff line number Diff line change
Expand Up @@ -544,6 +544,27 @@ <h1 id="head">Window</h1>
<label>Calibration:</label>
<span id="cal"></span>
</div>
<div class="form-control">
<label>Manual Calibration:</label>
<label class="switch">
<input type="checkbox" id="man_cal">
<span class="slider round"></span>
</label>
<div id="man_cal_warning">
<ion-icon name="warning"
style="vertical-align: text-bottom; font-size: 24px; color: goldenrod;"></ion-icon> Caution
</div>
</div>
<div id="man_cal_settings">
<div class="form-control">
<label>Movement Time:</label>
<input type="text" id="move_time_ms" style="width: 4em; min-width: 2em;"> ms
</div>
<div class="form-control">
<label>Limit Time:</label>
<input type="text" id="move_time_limit_pos_ms" style="width: 4em; min-width: 2em;"> ms
</div>
</div>
<div class="form-control">
<label>Input Mode:</label>
<select id="in_mode">
Expand Down Expand Up @@ -730,5 +751,6 @@ <h1 id="head">RGB</h1>
</script>
<script src="script.js">
</script>
<script src="https://unpkg.com/[email protected]/dist/ionicons.js"></script>
</body>
</html>
16 changes: 15 additions & 1 deletion fs_src/script.js
Original file line number Diff line number Diff line change
Expand Up @@ -410,6 +410,9 @@ function wcSetConfig(c, cfg, spinner) {
cfg = {
name: name,
in_mode: parseInt(el(c, "in_mode").value),
man_cal: el(c, "man_cal").checked,
move_time_ms: parseInt(el(c, "move_time_ms").value),
move_time_limit_pos_ms: parseInt(el(c, "move_time_limit_pos_ms").value),
swap_inputs: el(c, "swap_inputs").checked,
swap_outputs: el(c, "swap_outputs").checked,
};
Expand Down Expand Up @@ -685,10 +688,21 @@ function updateComponent(cd) {
setValueIfNotModified(el(c, "name"), cd.name);
updateInnerText(el(c, "state"), cd.state_str);
selectIfNotModified(el(c, "in_mode"), cd.in_mode);
checkIfNotModified(el(c, "man_cal"), cd.man_cal);
checkIfNotModified(el(c, "swap_inputs"), cd.swap_inputs);
checkIfNotModified(el(c, "swap_outputs"), cd.swap_outputs);
if(el(c, "man_cal").checked) {
el(c, "man_cal_warning").style.display = "inline";
el(c, "man_cal_settings").style.display = "block";
}
else {
el(c, "man_cal_warning").style.display = "none";
el(c, "man_cal_settings").style.display = "none";
}
setValueIfNotModified(el(c, "move_time_ms"), cd.move_time_ms);
setValueIfNotModified(el(c, "move_time_limit_pos_ms"), cd.move_time_limit_pos_ms);
let posText, calText;
if (cd.cal_done == 1) {
if (cd.cal_done == 1 || cd.man_cal == 1) {
if (cd.cur_pos != cd.tgt_pos) {
posText = `${cd.cur_pos} -> ${cd.tgt_pos}`;
} else {
Expand Down
2 changes: 2 additions & 0 deletions mos.yml
Original file line number Diff line number Diff line change
Expand Up @@ -69,9 +69,11 @@ config_schema:
- ["wc.swap_inputs", "b", false, {title: "Swap inputs (2 - open, 1 - close)"}]
- ["wc.swap_outputs", "b", false, {title: "Swap outputs (2 - open, 1 - close)"}]
- ["wc.calibrated", "b", false, {title: "Calibration done"}]
- ["wc.man_cal", "b", false, {title: "Manual Calibration active"}]
- ["wc.idle_power_thr", "d", 10.0, {title: "Power consumption threshold for motor idle detection"}]
- ["wc.move_power", "d", 0.0, {title: "Power consumption during movement, watts"}]
- ["wc.move_time_ms", "i", 0, {title: "Move time, in millseconds"}]
- ["wc.move_time_limit_pos_ms", "i", 0, {title: "Move time to limit position, in milliseconds"}]
- ["wc.max_ramp_up_time_ms", "i", 5000, {title: "Maximum ramp up time, in millseconds"}]
- ["wc.current_pos", "d", 0.0, {title: "Last position, percent: 0 - fully closed, 100 - fully open."}]

Expand Down
150 changes: 114 additions & 36 deletions src/shelly_hap_window_covering.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -37,7 +37,8 @@ WindowCovering::WindowCovering(int id, Input *in0, Input *in1, Output *out0,
state_timer_(std::bind(&WindowCovering::RunOnce, this)),
cur_pos_(cfg_->current_pos),
tgt_pos_(cfg_->current_pos),
move_ms_per_pct_(cfg_->move_time_ms / 100.0) {
move_ms_per_pct_(cfg_->move_time_ms / 100.0),
move_limit_ms_per_pct_(cfg_->move_time_limit_pos_ms / 100.0) {
if (!cfg_->swap_inputs) {
in_open_ = in0;
in_close_ = in1;
Expand Down Expand Up @@ -168,6 +169,10 @@ Status WindowCovering::Init() {
if (cfg_->calibrated) {
LOG(LL_INFO, ("WC %d: mp %.2f, mt_ms %d, cur_pos %.2f", id(),
cfg_->move_power, cfg_->move_time_ms, cur_pos_));
} else if (cfg_->man_cal) {
LOG(LL_INFO, ("WC %d: mp %.2f, mt_ms %d, mtlpos_ms %d, cur_pos %.2f", id(),
cfg_->move_power, cfg_->move_time_ms,
cfg_->move_time_limit_pos_ms, cur_pos_));
} else {
LOG(LL_INFO, ("WC %d: not calibrated", id()));
}
Expand Down Expand Up @@ -196,23 +201,28 @@ StatusOr<std::string> WindowCovering::GetInfoJSON() const {
return mgos::JSONPrintStringf(
"{id: %d, type: %d, name: %Q, "
"in_mode: %d, swap_inputs: %B, swap_outputs: %B, "
"cal_done: %B, move_time_ms: %d, move_power: %d, "
"cal_done: %B, man_cal: %B, move_time_ms: %d, "
"move_time_limit_pos_ms: %d, move_power: %d, "
"state: %d, state_str: %Q, cur_pos: %d, tgt_pos: %d}",
id(), type(), cfg_->name, cfg_->in_mode, cfg_->swap_inputs,
cfg_->swap_outputs, cfg_->calibrated, cfg_->move_time_ms,
(int) cfg_->move_power, (int) state_, StateStr(state_), (int) cur_pos_,
(int) tgt_pos_);
cfg_->swap_outputs, cfg_->calibrated, cfg_->man_cal, cfg_->move_time_ms,
cfg_->move_time_limit_pos_ms, (int) cfg_->move_power, (int) state_,
StateStr(state_), (int) cur_pos_, (int) tgt_pos_);
}

Status WindowCovering::SetConfig(const std::string &config_json,
bool *restart_required) {
struct mgos_config_wc cfg = *cfg_;
cfg.name = nullptr;
int in_mode = -1;
int8_t swap_inputs = -1, swap_outputs = -1;
int in_mode = -1, move_time_ms = -1, move_time_limit_pos_ms = -1;
int8_t man_cal = -1, swap_inputs = -1, swap_outputs = -1;
json_scanf(config_json.c_str(), config_json.size(),
"{name: %Q, in_mode: %d, swap_inputs: %B, swap_outputs: %B}",
&cfg.name, &in_mode, &swap_inputs, &swap_outputs);
"{name: %Q, in_mode: %d, "
"man_cal: %B, move_time_ms: %d, "
"move_time_limit_pos_ms: %d, "
"swap_inputs: %B, swap_outputs: %B}",
&cfg.name, &in_mode, &man_cal, &move_time_ms,
&move_time_limit_pos_ms, &swap_inputs, &swap_outputs);
mgos::ScopedCPtr name_owner((void *) cfg.name);
// Validate.
if (cfg.name != nullptr && strlen(cfg.name) > 64) {
Expand All @@ -231,6 +241,22 @@ Status WindowCovering::SetConfig(const std::string &config_json,
cfg_->in_mode = in_mode;
*restart_required = true;
}
if (man_cal != -1 && man_cal != cfg_->man_cal) {
cfg_->man_cal = man_cal;
if (man_cal && cfg_->calibrated) {
cfg_->calibrated = false;
}
*restart_required = true;
}
if (move_time_ms != -1 && move_time_ms != cfg_->move_time_ms) {
cfg_->move_time_ms = move_time_ms;
*restart_required = true;
}
if (move_time_limit_pos_ms != -1 &&
move_time_limit_pos_ms != cfg_->move_time_limit_pos_ms) {
cfg_->move_time_limit_pos_ms = move_time_limit_pos_ms;
*restart_required = true;
}
if (swap_inputs != -1 && swap_inputs != cfg_->swap_inputs) {
cfg_->swap_inputs = swap_inputs;
*restart_required = true;
Expand Down Expand Up @@ -298,6 +324,8 @@ const char *WindowCovering::StateStr(State state) {
return "rampup";
case State::kMoving:
return "moving";
case State::kMovingToFullPos:
return "moveToFullPos";
case State::kStop:
return "stop";
case State::kStopping:
Expand Down Expand Up @@ -397,7 +425,7 @@ void WindowCovering::HAPSetTgtPos(float value) {
WindowCovering::Direction WindowCovering::GetDesiredMoveDirection() {
if (tgt_pos_ == kNotSet) return Direction::kNone;
float pos_diff = tgt_pos_ - cur_pos_;
if (!cfg_->calibrated || std::abs(pos_diff) < 0.5) {
if (!(cfg_->calibrated || cfg_->man_cal) || std::abs(pos_diff) < 0.5) {
return Direction::kNone;
}
return (pos_diff > 0 ? Direction::kOpen : Direction::kClose);
Expand All @@ -406,7 +434,7 @@ WindowCovering::Direction WindowCovering::GetDesiredMoveDirection() {
void WindowCovering::Move(Direction dir) {
const char *ss = StateStr(state_);
bool want_open = false, want_close = false;
if (cfg_->calibrated) {
if (cfg_->calibrated || cfg_->man_cal) {
switch (dir) {
case Direction::kNone:
break;
Expand All @@ -424,6 +452,37 @@ void WindowCovering::Move(Direction dir) {
moving_dir_ = dir;
}

bool WindowCovering::MeasurePowerAndCheckForPMError(float &power) {
auto *pm = (moving_dir_ == Direction::kOpen ? pm_open_ : pm_close_);
auto pmv = pm->GetPowerW();
if (pmv.ok()) {
power = pmv.ValueOrDie();
} else {
LOG(LL_ERROR, ("PM error"));
tgt_state_ = State::kError;
SetInternalState(State::kStop);
return true;
}
return false;
}

bool WindowCovering::CheckForObstacle(float power) {
float too_much_power = cfg_->move_power * 2.5;
int too_long_time = cfg_->move_time_ms * 1.5;
int moving_time_ms = (mgos_uptime_micros() - begin_) / 1000;

if (power > cfg_->idle_power_thr &&
(power > too_much_power || moving_time_ms > too_long_time)) {
obstruction_detected_ = true;
obst_char_->RaiseEvent();
LOG(LL_ERROR, ("Obstruction: p = %.2f t = %d", power, moving_time_ms));
tgt_state_ = State::kError;
SetInternalState(State::kStop);
return true;
}
return false;
}

void WindowCovering::RunOnce() {
const char *ss = StateStr(state_);
if (state_ != State::kIdle) {
Expand Down Expand Up @@ -502,6 +561,7 @@ void WindowCovering::RunOnce() {
cfg_->move_time_ms = move_time_ms;
cfg_->move_power = move_power;
move_ms_per_pct_ = cfg_->move_time_ms / 100.0;
move_limit_ms_per_pct_ = cfg_->move_time_limit_pos_ms / 100.0;
SetInternalState(State::kPostCal1);
} else {
p_sum_ += p1;
Expand Down Expand Up @@ -547,7 +607,7 @@ void WindowCovering::RunOnce() {
break;
}
LOG(LL_INFO, ("P = %.2f -> %.2f", p, cfg_->move_power));
if (p >= cfg_->move_power * 0.75) {
if (cfg_->man_cal || p >= cfg_->move_power * 0.75) {
SetInternalState(State::kMoving);
break;
}
Expand All @@ -560,42 +620,37 @@ void WindowCovering::RunOnce() {
break;
}
case State::kMoving: {
bool moving_to_limit_pos =
((tgt_pos_ == kFullyOpen && moving_dir_ == Direction::kOpen) ||
(tgt_pos_ == kFullyClosed && moving_dir_ == Direction::kClose));
int moving_time_ms = (mgos_uptime_micros() - begin_) / 1000;

float pos_diff = moving_time_ms / move_ms_per_pct_;

float new_cur_pos =
(moving_dir_ == Direction::kOpen ? move_start_pos_ + pos_diff
: move_start_pos_ - pos_diff);
auto *pm = (moving_dir_ == Direction::kOpen ? pm_open_ : pm_close_);
auto pmv = pm->GetPowerW();

float p = -1;
if (pmv.ok()) {
p = pmv.ValueOrDie();
} else {
LOG(LL_ERROR, ("PM error"));
tgt_state_ = State::kError;
SetInternalState(State::kStop);
if (MeasurePowerAndCheckForPMError(p)) {
break;
}

SetCurPos(new_cur_pos, p);
float too_much_power = cfg_->move_power * 2.5;
int too_long_time = cfg_->move_time_ms * 1.5;
if (p > cfg_->idle_power_thr &&
(p > too_much_power || moving_time_ms > too_long_time)) {
obstruction_detected_ = true;
obst_char_->RaiseEvent();
LOG(LL_ERROR, ("Obstruction: p = %.2f t = %d", p, moving_time_ms));
tgt_state_ = State::kError;
SetInternalState(State::kStop);

if (CheckForObstacle(p)) {
break;
}

Direction want_move_dir = GetDesiredMoveDirection();
bool reverse =
(want_move_dir != moving_dir_ && want_move_dir != Direction::kNone);
// If moving to one of the limit positions, keep moving
// until no current is flowing.
if (((tgt_pos_ == kFullyOpen && moving_dir_ == Direction::kOpen) ||
(tgt_pos_ == kFullyClosed && moving_dir_ == Direction::kClose)) &&
!reverse) {
// until no current is flowing. But, if manually calibrated
// move `move_to_end_time_ms` instead of `move_time_ms`, in order
// to really reach the limit (Caution: this works for motors, that
// stop at their limit positions by themselves, only).
if (moving_to_limit_pos && !reverse && !cfg_->man_cal) {
LOG_EVERY_N(LL_INFO, 8, ("Moving to %d, p %.2f", (int) tgt_pos_, p));
if (p > cfg_->idle_power_thr || (mgos_uptime_micros() - begin_ <
cfg_->max_ramp_up_time_ms * 1000)) {
Expand All @@ -609,8 +664,13 @@ void WindowCovering::RunOnce() {
} else if (want_move_dir == moving_dir_) {
// Still moving.
break;
} else if (cfg_->man_cal) {
LOG(LL_INFO, ("Moving to full pos"));
// move the extra time in order to move to full position
SetInternalState(State::kMovingToFullPos);
break;
} else {
// We stoped moving. Reconcile target position with current,
// We stopped moving. Reconcile target position with current,
// pretend we wanted to be exactly where we ended up.
if (std::abs(tgt_pos_ - cur_pos_) < 1) {
SetTgtPos(cur_pos_, "fixup");
Expand All @@ -620,6 +680,24 @@ void WindowCovering::RunOnce() {
SetInternalState(State::kStop);
break;
}
case State::kMovingToFullPos: {
int moving_time_ms = (mgos_uptime_micros() - begin_) / 1000;
int move_time_limit_pos_ms = cfg_->move_time_limit_pos_ms;

LOG(LL_INFO, ("Moving to full pos for %d, elapsed %d",
(int) move_time_limit_pos_ms, moving_time_ms));

float p = -1;
if (MeasurePowerAndCheckForPMError(p) || CheckForObstacle(p)) {
break;
}

if (moving_time_ms >= cfg_->move_time_limit_pos_ms) {
Move(Direction::kNone); // Stop moving immediately to minimize error.
SetInternalState(State::kStop);
}
break;
}
case State::kStop: {
Move(Direction::kNone);
SaveState();
Expand Down Expand Up @@ -648,7 +726,7 @@ void WindowCovering::RunOnce() {

void WindowCovering::HandleInputEvent01(Direction dir, Input::Event ev,
bool state) {
if (!cfg_->calibrated) {
if (!(cfg_->calibrated || cfg_->man_cal)) {
HandleInputEventNotCalibrated();
return;
}
Expand Down Expand Up @@ -677,7 +755,7 @@ void WindowCovering::HandleInputEvent01(Direction dir, Input::Event ev,
}

void WindowCovering::HandleInputEvent2(Input::Event ev, bool state) {
if (!cfg_->calibrated) {
if (!(cfg_->calibrated || cfg_->man_cal)) {
HandleInputEventNotCalibrated();
return;
}
Expand Down
10 changes: 8 additions & 2 deletions src/shelly_hap_window_covering.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -73,8 +73,9 @@ class WindowCovering : public Component, public mgos::hap::Service {
kMove = 20,
kRampUp = 22,
kMoving = 23,
kStop = 24,
kStopping = 25,
kMovingToFullPos = 24,
kStop = 25,
kStopping = 26,
// Error states
kError = 100,
};
Expand Down Expand Up @@ -106,6 +107,10 @@ class WindowCovering : public Component, public mgos::hap::Service {
Direction GetDesiredMoveDirection();
void Move(Direction dir);

bool MeasurePowerAndCheckForPMError(float &power);

bool CheckForObstacle(float power);

void RunOnce();

void HandleInputEvent01(Direction dir, Input::Event ev, bool state);
Expand Down Expand Up @@ -138,6 +143,7 @@ class WindowCovering : public Component, public mgos::hap::Service {
int64_t begin_ = 0;
float move_start_pos_ = 0;
float move_ms_per_pct_ = 0;
float move_limit_ms_per_pct_ = 0;
bool obstruction_detected_ = false;
int64_t last_hap_set_tgt_pos_ = 0;
Direction moving_dir_ = Direction::kNone;
Expand Down