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

Add HAP doorbell input type #495

Merged
merged 2 commits into from
Feb 14, 2021
Merged
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
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"
zeevox marked this conversation as resolved.
Show resolved Hide resolved

#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