Completed Example 13
Fully documented Garage Door Opener and motorized Window Shade examples.
This commit is contained in:
parent
6c5a5835e6
commit
7fd21f2bed
|
|
@ -5,6 +5,8 @@
|
||||||
// ------------------------------------------------ //
|
// ------------------------------------------------ //
|
||||||
// //
|
// //
|
||||||
// Example 13: Target States and Current States //
|
// Example 13: Target States and Current States //
|
||||||
|
// * implementing a Garage Door Opener //
|
||||||
|
// * implementing a motorized Window Shade //
|
||||||
// //
|
// //
|
||||||
////////////////////////////////////////////////////////////
|
////////////////////////////////////////////////////////////
|
||||||
|
|
||||||
|
|
@ -14,6 +16,37 @@
|
||||||
|
|
||||||
void setup() {
|
void setup() {
|
||||||
|
|
||||||
|
// In Example 12 we saw how to implement the loop() method for a Service to continuously monitor our device and periodically report
|
||||||
|
// changes in one or more Characteristics back to HomeKit using setVal() and timeVal(). In that example we implemented passive sensors
|
||||||
|
// that operated independently and required no input from the user, which meant we did not need to implement any update() methods.
|
||||||
|
|
||||||
|
// In this Example 13 we demonstrate the simultaneous use of both the update() and loop() methods by implementing two new Services:
|
||||||
|
// a Garage Door Opener and a motorized Window Shade. Both examples showcase HomeKit's Target-State/Current-State framework.
|
||||||
|
// For physical devices that take time to operate (such as closing a door), HomeKit Services typically use:
|
||||||
|
|
||||||
|
// * one Characteristic that HomeKit sets via update() requests to HomeSpan, and that represent a desired target state,
|
||||||
|
// such as opened, closed, or in some cases a percentage opened or closed; and
|
||||||
|
|
||||||
|
// * one read-only Characteristic that HomeSpan use to track the current state of the device in the loop() method, as well as
|
||||||
|
// report back changes to HomeKit using setVal().
|
||||||
|
|
||||||
|
// Not all HomeKit Services utilize the same Characteristics to define target and current states. Some Services use Characteristics
|
||||||
|
// that are specific to that one Service, whereas others use more generic Characteristics. The common theme seems to be that HomeKit
|
||||||
|
// guesses the actions a device is taking, and updates it tile's icon accordingly, by comparing the value of the target state
|
||||||
|
// Characteristic it sets, and the current state Characteristic it receives in the form of Event Notifications. When they are the same,
|
||||||
|
// HomeKit assumes the physical device has reached the required position. When they differ, HomeKit assumes something will be opening,
|
||||||
|
// closing, raising, lowering, etc. The details of this process for each Service is outlined in the HAP documentation, but beware
|
||||||
|
// the document is not always up to date with the lastest version of the HomeKit application. Sometimes a little experimenting and a lot
|
||||||
|
// of trial and error is required to fully understand how each Service responds to different combinations of Characteristic values.
|
||||||
|
|
||||||
|
// As always, we won't be connecting our ESP32 to an actual garage door or window shade but will instead simulate their responses and
|
||||||
|
// actions for illustrative purposes. In some ways the code is more complicated because of the need to simulate values - it might be
|
||||||
|
// easier if we actually were connecting to a garage door or window shade!
|
||||||
|
|
||||||
|
// Fully commented code for both of our derived Services can be found in DEV_DoorsWindows.h. These examples do not introduce any new
|
||||||
|
// HomeSpan functions or features. Rather we are combining everything learned so far into two reasonably complex Services. You may
|
||||||
|
// want to reference the HAP documentation for these two parent Services to fully understand the meaning of the different value settings
|
||||||
|
// for each of the Services' Characteristics.
|
||||||
|
|
||||||
Serial.begin(115200);
|
Serial.begin(115200);
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -5,15 +5,15 @@
|
||||||
|
|
||||||
struct DEV_GarageDoor : Service::GarageDoorOpener { // A Garage Door Opener
|
struct DEV_GarageDoor : Service::GarageDoorOpener { // A Garage Door Opener
|
||||||
|
|
||||||
SpanCharacteristic *current;
|
SpanCharacteristic *current; // reference to the Current Door State Characteristic (specific to Garage Door Openers)
|
||||||
SpanCharacteristic *target;
|
SpanCharacteristic *target; // reference to the Target Door State Characteristic (specific to Garage Door Openers)
|
||||||
SpanCharacteristic *obstruction;
|
SpanCharacteristic *obstruction; // reference to the Obstruction Detected Characteristic (specific to Garage Door Openers)
|
||||||
|
|
||||||
DEV_GarageDoor(ServiceType sType=ServiceType::Regular) : Service::GarageDoorOpener(sType){ // constructor() method
|
DEV_GarageDoor(ServiceType sType=ServiceType::Regular) : Service::GarageDoorOpener(sType){ // constructor() method
|
||||||
|
|
||||||
current=new Characteristic::CurrentDoorState(1);
|
current=new Characteristic::CurrentDoorState(1); // initial value of 1 means closed
|
||||||
target=new Characteristic::TargetDoorState(1);
|
target=new Characteristic::TargetDoorState(1); // initial value of 1 means closed
|
||||||
obstruction=new Characteristic::ObstructionDetected(false);
|
obstruction=new Characteristic::ObstructionDetected(false); // initial value of false means NO obstruction is detected
|
||||||
|
|
||||||
Serial.print("Configuring Garage Door Opener"); // initialization message
|
Serial.print("Configuring Garage Door Opener"); // initialization message
|
||||||
Serial.print("\n");
|
Serial.print("\n");
|
||||||
|
|
@ -22,14 +22,16 @@ struct DEV_GarageDoor : Service::GarageDoorOpener { // A Garage Door Opener
|
||||||
|
|
||||||
StatusCode update(){ // update() method
|
StatusCode update(){ // update() method
|
||||||
|
|
||||||
if(target->getNewVal()==0){
|
// see HAP Documentation for details on what each value represents
|
||||||
|
|
||||||
|
if(target->getNewVal()==0){ // if the target-state value is set to 0, HomeKit is requesting the door to be in open position
|
||||||
LOG1("Opening Garage Door\n");
|
LOG1("Opening Garage Door\n");
|
||||||
current->setVal(2);
|
current->setVal(2); // set the current-state value to 2, which means "opening"
|
||||||
obstruction->setVal(false);
|
obstruction->setVal(false); // clear any prior obstruction detection
|
||||||
} else {
|
} else {
|
||||||
LOG1("Closing Garage Door\n");
|
LOG1("Closing Garage Door\n"); // else the target-state value is set to 1, and HomeKit is requesting the door to be in the closed position
|
||||||
current->setVal(3);
|
current->setVal(3); // set the current-state value to 3, which means "closing"
|
||||||
obstruction->setVal(false);
|
obstruction->setVal(false); // clear any prior obstruction detection
|
||||||
}
|
}
|
||||||
|
|
||||||
return(StatusCode::OK); // return OK status code
|
return(StatusCode::OK); // return OK status code
|
||||||
|
|
@ -38,20 +40,23 @@ struct DEV_GarageDoor : Service::GarageDoorOpener { // A Garage Door Opener
|
||||||
|
|
||||||
void loop(){ // loop() method
|
void loop(){ // loop() method
|
||||||
|
|
||||||
if(current->getVal()==target->getVal())
|
if(current->getVal()==target->getVal()) // if current-state matches target-state there is nothing do -- exit loop()
|
||||||
return;
|
return;
|
||||||
|
|
||||||
if(current->getVal()==3 && random(100000)==0){
|
if(current->getVal()==3 && random(100000)==0){ // here we simulate a random obstruction, but only if the door is closing (not opening)
|
||||||
current->setVal(4);
|
current->setVal(4); // if our simulated obstruction is triggered, set the curent-state to 4, which means "stopped"
|
||||||
obstruction->setVal(true);
|
obstruction->setVal(true); // and set obstruction-detected to true
|
||||||
LOG1("Garage Door Obstruction Detected!\n");
|
LOG1("Garage Door Obstruction Detected!\n");
|
||||||
}
|
}
|
||||||
|
|
||||||
if(current->getVal()==4)
|
if(current->getVal()==4) // if the current-state is stopped, there is nothing more to do - exit loop()
|
||||||
return;
|
return;
|
||||||
|
|
||||||
if(target->timeVal()>5000)
|
// This last bit of code only gets called if the door is in a state that represents actively opening or actively closing.
|
||||||
current->setVal(target->getVal());
|
// If there is an obstruction, the door is "stopped" and won't start again until the HomeKit Controller requests a new open or close action
|
||||||
|
|
||||||
|
if(target->timeVal()>5000) // simulate a garage door that takes 5 seconds to operate by monitoring time since target-state was last modified
|
||||||
|
current->setVal(target->getVal()); // set the current-state to the target-state
|
||||||
|
|
||||||
} // loop
|
} // loop
|
||||||
|
|
||||||
|
|
@ -61,16 +66,16 @@ struct DEV_GarageDoor : Service::GarageDoorOpener { // A Garage Door Opener
|
||||||
|
|
||||||
struct DEV_WindowShade : Service::WindowCovering { // A motorized Window Shade with Hold Feature
|
struct DEV_WindowShade : Service::WindowCovering { // A motorized Window Shade with Hold Feature
|
||||||
|
|
||||||
SpanCharacteristic *current;
|
SpanCharacteristic *current; // reference to a "generic" Current Position Characteristic (used by a variety of different Service)
|
||||||
SpanCharacteristic *target;
|
SpanCharacteristic *target; // reference to a "generic" Target Position Characteristic (used by a variety of different Service)
|
||||||
|
|
||||||
DEV_WindowShade(ServiceType sType=ServiceType::Regular) : Service::WindowCovering(sType){ // constructor() method
|
DEV_WindowShade(ServiceType sType=ServiceType::Regular) : Service::WindowCovering(sType){ // constructor() method
|
||||||
|
|
||||||
current=new Characteristic::CurrentPosition(0);
|
current=new Characteristic::CurrentPosition(0); // Windows Shades have positions that range from 0 (fully lowered) to 100 (fully raised)
|
||||||
new SpanRange(0,100,10);
|
new SpanRange(0,100,10); // set the allowable current-position range to 0-100 IN STEPS of 10
|
||||||
|
|
||||||
target=new Characteristic::TargetPosition(0);
|
target=new Characteristic::TargetPosition(0); // Windows Shades have positions that range from 0 (fully lowered) to 100 (fully raised)
|
||||||
new SpanRange(0,100,10);
|
new SpanRange(0,100,10); // set the allowable target-position range to 0-100 IN STEPS of 10
|
||||||
|
|
||||||
Serial.print("Configuring Motorized Window Shade"); // initialization message
|
Serial.print("Configuring Motorized Window Shade"); // initialization message
|
||||||
Serial.print("\n");
|
Serial.print("\n");
|
||||||
|
|
@ -79,11 +84,20 @@ struct DEV_WindowShade : Service::WindowCovering { // A motorized Window Sha
|
||||||
|
|
||||||
StatusCode update(){ // update() method
|
StatusCode update(){ // update() method
|
||||||
|
|
||||||
if(target->getNewVal()>current->getVal()){
|
// The logic below is based on how HomeKit appears to operate in practice, which is NOT consistent with
|
||||||
LOG1("Raising Shade\n");
|
// HAP documentation. In that document HomeKit seems to support fully opening or fully closing a window shade, with
|
||||||
|
// an optional control to HOLD the window shade at a given in-between position while it is moving.
|
||||||
|
|
||||||
|
// In practice, HomeKit does not appear to implement any form of a HOLD control button, even if you instantiate that
|
||||||
|
// Characteristic. Instead, HomeKit provides a full slider control, similar to the brightness control for a lightbulb,
|
||||||
|
// that allows you to set the exact position of the window shade from 0-100%. This obviates the need to any sort of HOLD button.
|
||||||
|
// The resulting logic is also very simple:
|
||||||
|
|
||||||
|
if(target->getNewVal()>current->getVal()){ // if the target-position requested is greater than the current-position, simply log a "raise" message
|
||||||
|
LOG1("Raising Shade\n"); // ** there is nothing more to do - HomeKit keeps track of the current-position so knows raising is required
|
||||||
} else
|
} else
|
||||||
if(target->getNewVal()<current->getVal()){
|
if(target->getNewVal()<current->getVal()){ // if the target-position requested is less than the current-position, simply log a "raise" message
|
||||||
LOG1("Lowering Shade\n");
|
LOG1("Lowering Shade\n"); // ** there is nothing more to do - HomeKit keeps track of the current-position so knows lowering is required
|
||||||
}
|
}
|
||||||
|
|
||||||
return(StatusCode::OK); // return OK status code
|
return(StatusCode::OK); // return OK status code
|
||||||
|
|
@ -92,15 +106,21 @@ struct DEV_WindowShade : Service::WindowCovering { // A motorized Window Sha
|
||||||
|
|
||||||
void loop(){ // loop() method
|
void loop(){ // loop() method
|
||||||
|
|
||||||
if(current->timeVal()>1000){
|
// Here we simulate a window shade that moves 10% higher or lower every 1 second as it seeks to reach its target-position
|
||||||
if(target->getVal()>current->getVal()){
|
|
||||||
|
if(current->timeVal()>1000){ // if 1 second has elapsed since the current-position was last modified
|
||||||
|
if(target->getVal()>current->getVal()){ // increase the current-position by 10 if the target-position is greater than the current-position
|
||||||
current->setVal(current->getVal()+10);
|
current->setVal(current->getVal()+10);
|
||||||
} else
|
} else
|
||||||
if(target->getVal()<current->getVal()){
|
if(target->getVal()<current->getVal()){ // else decrease the current-position by 10 if the target-position is less than the current-position
|
||||||
current->setVal(current->getVal()-10);
|
current->setVal(current->getVal()-10);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Note we do NOTHING if target-positon and current-position is the same - HomeKit will detect this and adjust its tile icon accordingly.
|
||||||
|
// Unlike the Garage Door Service above, we do not need to set any Characteristic telling HomeKit the shade is actually raising, lowering, or stopped.
|
||||||
|
// HomeKit figures this out automatically, which is very good, though unfortunately inconsistent with the HAP Documentation.
|
||||||
|
|
||||||
} // loop
|
} // loop
|
||||||
|
|
||||||
};
|
};
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue