Skip to content

Commit

Permalink
Add HAP doorbell input type (#495)
Browse files Browse the repository at this point in the history
* Add HAP doorbell input type

* Refactor out SSW into stateless_switch_base so that both doorbell and
  ssw inherit from the base class

Signed-off-by: Timothy Langer <[email protected]>
  • Loading branch information
zeevox authored Feb 14, 2021
1 parent 7aea56c commit 20f6ce0
Show file tree
Hide file tree
Showing 11 changed files with 355 additions and 183 deletions.
3 changes: 3 additions & 0 deletions fs_src/index.html
Original file line number Diff line number Diff line change
Expand Up @@ -354,6 +354,7 @@ <h1 id="head">Input</h1>
<option id="type_7" value="7">Motion Sensor</option>
<option id="type_8" value="8">Occupancy Sensor</option>
<option id="type_9" value="9">Contact Sensor</option>
<option id="type_10" value="10">Doorbell</option>
</select>
</div>
<div class="form-control">
Expand Down Expand Up @@ -403,6 +404,7 @@ <h1 id="head">Disabled Input</h1>
<option id="type_7" value="7">Motion Sensor</option>
<option id="type_8" value="8">Occupancy Sensor</option>
<option id="type_9" value="9">Contact Sensor</option>
<option id="type_10" value="10">Doorbell</option>
</select>
</div>
<div class="form-control">
Expand Down Expand Up @@ -430,6 +432,7 @@ <h1 id="head">Sensor</h1>
<option id="type_7" value="7">Motion Sensor</option>
<option id="type_8" value="8">Occupancy Sensor</option>
<option id="type_9" value="9">Contact Sensor</option>
<option id="type_10" value="10">Doorbell</option>
</select>
</div>
<div class="form-control">
Expand Down
6 changes: 6 additions & 0 deletions fs_src/script.js
Original file line number Diff line number Diff line change
Expand Up @@ -421,12 +421,15 @@ function findOrAddContainer(cd) {
case 7: // Motion Sensor.
case 8: // Occupancy Sensor.
case 9: // Contact Sensor.
case 10: // Doorbell
c = el("sensor_template").cloneNode(true);
c.id = elId;
el(c, "save_btn").onclick = function () {
mosSetConfig(c);
};
break;
default:
console.log(`Unhandled component type: ${cd.type}`);
}
if (c) {
c.style.display = "block";
Expand Down Expand Up @@ -566,6 +569,7 @@ function updateComponent(cd) {
case 7: // Motion Sensor
case 8: // Occupancy Sensor
case 9: // Contact Sensor
case 10: // Doorbell
var headText = `Input ${cd.id}`;
if (cd.name) headText += ` (${cd.name})`;
el(c, "head").innerText = headText;
Expand All @@ -582,6 +586,8 @@ function updateComponent(cd) {
}
el(c, "status").innerText = statusText;
break;
default:
console.log(`Unhandled component type: ${cd.type}`);
}
c.data = cd;
}
Expand Down
2 changes: 2 additions & 0 deletions src/shelly_common.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -35,6 +35,7 @@
#define SHELLY_HAP_AID_BASE_OCCUPANCY_SENSOR 0x700
#define SHELLY_HAP_AID_BASE_CONTACT_SENSOR 0x800
#define SHELLY_HAP_AID_BASE_VALVE 0x900
#define SHELLY_HAP_AID_BASE_DOORBELL 0xa00

#define SHELLY_HAP_IID_BASE_SWITCH 0x100
#define SHELLY_HAP_IID_STEP_SWITCH 4
Expand All @@ -54,6 +55,7 @@
#define SHELLY_HAP_IID_STEP_SENSOR 0x10
#define SHELLY_HAP_IID_BASE_VALVE 0xa00
#define SHELLY_HAP_IID_STEP_VALVE 0x10
#define SHELLY_HAP_IID_BASE_DOORBELL 0xb00

namespace shelly {

Expand Down
1 change: 1 addition & 0 deletions src/shelly_component.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -34,6 +34,7 @@ class Component {
kMotionSensor = 7,
kOccupancySensor = 8,
kContactSensor = 9,
kDoorbell = 10,
kMax,
};

Expand Down
40 changes: 40 additions & 0 deletions src/shelly_hap_doorbell.cpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,40 @@
/*
* Copyright (c) Shelly-HomeKit Contributors
* All rights reserved
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/

#include "shelly_hap_doorbell.hpp"

#include "HAPUUID.h"

namespace shelly {
namespace hap {

const HAPUUID kHAPServiceType_Doorbell = HAPUUIDCreateAppleDefined(0x121);

Doorbell::Doorbell(int id, Input *in, struct mgos_config_in_ssw *cfg)
: StatelessSwitchBase(id, in, cfg, SHELLY_HAP_IID_BASE_DOORBELL,
&kHAPServiceType_Doorbell, "service.doorbell") {
}

Doorbell::~Doorbell() {
}

Component::Type Doorbell::type() const {
return Type::kDoorbell;
}

} // namespace hap
} // namespace shelly
34 changes: 34 additions & 0 deletions src/shelly_hap_doorbell.hpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
/*
* Copyright (c) Shelly-HomeKit Contributors
* All rights reserved
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/

#pragma once
#include "shelly_hap_stateless_switch_base.hpp"

namespace shelly {
namespace hap {

class Doorbell : public StatelessSwitchBase {
public:
Doorbell(int id, Input *in, struct mgos_config_in_ssw *cfg);
virtual ~Doorbell();

// Component interface impl.
Type type() const override;
};

} // namespace hap
} // namespace shelly
11 changes: 11 additions & 0 deletions src/shelly_hap_input.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@
#include "mgos_hap.h"

#include "shelly_hap_contact_sensor.hpp"
#include "shelly_hap_doorbell.hpp"
#include "shelly_hap_motion_sensor.hpp"
#include "shelly_hap_occupancy_sensor.hpp"
#include "shelly_hap_stateless_switch.hpp"
Expand Down Expand Up @@ -132,6 +133,13 @@ Status ShellyInput::Init() {
s_ = cs;
break;
}
case Type::kDoorbell: {
auto *db = new hap::Doorbell(id(), in_,
(struct mgos_config_in_ssw *) &cfg_->ssw);
c_.reset(db);
s_ = db;
break;
}
default: {
return mgos::Errorf(STATUS_INVALID_ARGUMENT, "Invalid type %d",
(int) initial_type_);
Expand Down Expand Up @@ -195,6 +203,8 @@ uint16_t ShellyInput::GetAIDBase() const {
return SHELLY_HAP_AID_BASE_OCCUPANCY_SENSOR;
case Type::kContactSensor:
return SHELLY_HAP_AID_BASE_CONTACT_SENSOR;
case Type::kDoorbell:
return SHELLY_HAP_AID_BASE_DOORBELL;
default:
return 0;
}
Expand All @@ -212,6 +222,7 @@ bool ShellyInput::IsValidType(int type) {
case (int) Type::kMotionSensor:
case (int) Type::kOccupancySensor:
case (int) Type::kContactSensor:
case (int) Type::kDoorbell:
return true;
}
return false;
Expand Down
143 changes: 4 additions & 139 deletions src/shelly_hap_stateless_switch.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -17,158 +17,23 @@

#include "shelly_hap_stateless_switch.hpp"

#include "mgos.hpp"
#include "mgos_hap.hpp"

namespace shelly {
namespace hap {

StatelessSwitch::StatelessSwitch(int id, Input *in,
struct mgos_config_in_ssw *cfg)
: Component(id),
Service(
// IDs used to start at 0, preserve compat.
(SHELLY_HAP_IID_BASE_STATELESS_SWITCH +
(SHELLY_HAP_IID_STEP_STATELESS_SWITCH * (id - 1))),
: StatelessSwitchBase(
id, in, cfg, SHELLY_HAP_IID_BASE_STATELESS_SWITCH,
&kHAPServiceType_StatelessProgrammableSwitch,
kHAPServiceDebugDescription_StatelessProgrammableSwitch),
in_(in),
cfg_(cfg) {
kHAPServiceDebugDescription_StatelessProgrammableSwitch) {
}

StatelessSwitch::~StatelessSwitch() {
in_->RemoveHandler(handler_id_);
}

Component::Type StatelessSwitch::type() const {
return Type::kStatelessSwitch;
}

std::string StatelessSwitch::name() const {
return cfg_->name;
}

Status StatelessSwitch::Init() {
if (in_ == nullptr) {
return mgos::Errorf(STATUS_INVALID_ARGUMENT, "input is required");
}
uint16_t iid = svc_.iid + 1;
// Name
AddNameChar(iid++, cfg_->name);
// Programmable Switch Event
AddChar(new mgos::hap::UInt8Characteristic(
iid++, &kHAPCharacteristicType_ProgrammableSwitchEvent, 0, 2, 1,
[this](HAPAccessoryServerRef *, const HAPUInt8CharacteristicReadRequest *,
uint8_t *value) {
if (last_ev_ts_ == 0) return kHAPError_InvalidState;
*value = last_ev_;
return kHAPError_None;
},
true /* supports_notification */, nullptr /* write_handler */,
kHAPCharacteristicDebugDescription_ProgrammableSwitchEvent));

handler_id_ = in_->AddHandler(
std::bind(&StatelessSwitch::InputEventHandler, this, _1, _2));

return Status::OK();
}

StatusOr<std::string> StatelessSwitch::GetInfo() const {
double last_ev_age = -1;
if (last_ev_ts_ > 0) {
last_ev_age = mgos_uptime() - last_ev_ts_;
}
return mgos::SPrintf("st:%d m:%d lea: %.3f", in_->GetState(), cfg_->in_mode,
last_ev_age);
}

StatusOr<std::string> StatelessSwitch::GetInfoJSON() const {
double last_ev_age = -1;
if (last_ev_ts_ > 0) {
last_ev_age = mgos_uptime() - last_ev_ts_;
}
return mgos::JSONPrintStringf(
"{id: %d, type: %d, name: %Q, in_mode: %d, "
"last_ev: %d, last_ev_age: %.3f}",
id(), type(), (cfg_->name ? cfg_->name : ""), cfg_->in_mode, last_ev_,
last_ev_age);
}

Status StatelessSwitch::SetConfig(const std::string &config_json,
bool *restart_required) {
char *name = nullptr;
int in_mode = -1;
json_scanf(config_json.c_str(), config_json.size(), "{name: %Q, in_mode: %d}",
&name, &in_mode);
mgos::ScopedCPtr name_owner(name);
// Validation.
if (name != nullptr && strlen(name) > 64) {
return mgos::Errorf(STATUS_INVALID_ARGUMENT, "invalid %s",
"name (too long, max 64)");
}
if (in_mode < 0 || in_mode > 2) {
return mgos::Errorf(STATUS_INVALID_ARGUMENT, "invalid %s", "in_mode");
}
// Now copy over.
if (name != nullptr && strcmp(name, cfg_->name) != 0) {
mgos_conf_set_str(&cfg_->name, name);
*restart_required = true;
}
cfg_->in_mode = in_mode;
return Status::OK();
}

Status StatelessSwitch::SetState(const std::string &state_json) {
(void) state_json;
return Status::UNIMPLEMENTED();
}

void StatelessSwitch::InputEventHandler(Input::Event ev, bool state) {
const auto in_mode = static_cast<InMode>(cfg_->in_mode);
switch (in_mode) {
// In momentary input mode we translate input events to HAP events directly.
case InMode::kMomentary:
switch (ev) {
case Input::Event::kSingle:
RaiseEvent(
kHAPCharacteristicValue_ProgrammableSwitchEvent_SinglePress);
break;
case Input::Event::kDouble:
RaiseEvent(
kHAPCharacteristicValue_ProgrammableSwitchEvent_DoublePress);
break;
case Input::Event::kLong:
RaiseEvent(kHAPCharacteristicValue_ProgrammableSwitchEvent_LongPress);
break;
case Input::Event::kChange:
case Input::Event::kReset:
case Input::Event::kMax:
// Ignore.
break;
}
break;
// In toggle switch input mode we translate changes to HAP events.
case InMode::kToggleShort:
case InMode::kToggleShortLong:
if (ev != Input::Event::kChange) break;
if (in_mode == InMode::kToggleShortLong && !state) {
RaiseEvent(kHAPCharacteristicValue_ProgrammableSwitchEvent_DoublePress);
} else {
RaiseEvent(kHAPCharacteristicValue_ProgrammableSwitchEvent_SinglePress);
}
break;
}
}

void StatelessSwitch::RaiseEvent(uint8_t ev) {
last_ev_ = ev;
last_ev_ts_ = mgos_uptime();
LOG(LL_INFO, ("Input %d: HAP event (mode %d): %d", id(), cfg_->in_mode, ev));
// May happen during init, we don't want to raise events until initialized.
if (handler_id_ != Input::kInvalidHandlerID) {
chars_[1]->RaiseEvent();
}
}

} // namespace hap
} // namespace shelly
} // namespace shelly
Loading

0 comments on commit 20f6ce0

Please sign in to comment.