commit
835d49adc5
|
|
@ -0,0 +1,221 @@
|
|||
/*********************************************************************************
|
||||
* MIT License
|
||||
*
|
||||
* Copyright (c) 2021 Gregg E. Berman
|
||||
*
|
||||
* https://github.com/HomeSpan/HomeSpan
|
||||
*
|
||||
* Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
* of this software and associated documentation files (the "Software"), to deal
|
||||
* in the Software without restriction, including without limitation the rights
|
||||
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
* copies of the Software, and to permit persons to whom the Software is
|
||||
* furnished to do so, subject to the following conditions:
|
||||
*
|
||||
* The above copyright notice and this permission notice shall be included in all
|
||||
* copies or substantial portions of the Software.
|
||||
*
|
||||
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||
* SOFTWARE.
|
||||
*
|
||||
********************************************************************************/
|
||||
|
||||
// HomeSpan Television Service Example
|
||||
|
||||
// Covers all Characteristics of the Television Service that appear to
|
||||
// be supported in the iOS 15 version of the Home App. Note these Services
|
||||
// are not documented by Apple and are not officially part HAP-R2.
|
||||
//
|
||||
// For Service::Television():
|
||||
//
|
||||
// * Characteristic::Active()
|
||||
// * Characteristic::ConfiguredName()
|
||||
// * Characteristic::ActiveIdentifier()
|
||||
// * Characteristic::RemoteKey()
|
||||
// * Characteristic::PowerModeSelection()
|
||||
//
|
||||
// For Service::InputSource():
|
||||
//
|
||||
// * Characteristic::ConfiguredName()
|
||||
// * Characteristic::ConfiguredNameStatic() // a HomeSpan-specific variation of ConfiguredName()
|
||||
// * Characteristic::Identifier()
|
||||
// * Characteristic::IsConfigured()
|
||||
// * Characteristic::CurrentVisibilityState()
|
||||
// * Characteristic::TargetVisibilityState()
|
||||
|
||||
// NOTE: This example is only designed to demonstrate how Television Services and Characteristics
|
||||
// appear in the Home App, and what they each control. To keep things simple, actions for the
|
||||
// Input Sources have NOT been implemented in the code below. For example, the code below does not include
|
||||
// any logic to update CurrentVisibilityState when the TargetVisibilityState checkboxes are clicked.
|
||||
|
||||
#include "HomeSpan.h"
|
||||
|
||||
struct HomeSpanTV : Service::Television {
|
||||
|
||||
SpanCharacteristic *active = new Characteristic::Active(0); // TV On/Off (set to Off at start-up)
|
||||
SpanCharacteristic *activeID = new Characteristic::ActiveIdentifier(3); // Sets HDMI 3 on start-up
|
||||
SpanCharacteristic *remoteKey = new Characteristic::RemoteKey(); // Used to receive button presses from the Remote Control widget
|
||||
SpanCharacteristic *settingsKey = new Characteristic::PowerModeSelection(); // Adds "View TV Setting" option to Selection Screen
|
||||
|
||||
HomeSpanTV(const char *name) : Service::Television() {
|
||||
new Characteristic::ConfiguredName(name); // Name of TV
|
||||
Serial.printf("Configured TV: %s\n",name);
|
||||
}
|
||||
|
||||
boolean update() override {
|
||||
|
||||
if(active->updated()){
|
||||
Serial.printf("Set TV Power to: %s\n",active->getNewVal()?"ON":"OFF");
|
||||
}
|
||||
|
||||
if(activeID->updated()){
|
||||
Serial.printf("Set Input Source to HDMI-%d\n",activeID->getNewVal());
|
||||
}
|
||||
|
||||
if(settingsKey->updated()){
|
||||
Serial.printf("Received request to \"View TV Settings\"\n");
|
||||
}
|
||||
|
||||
if(remoteKey->updated()){
|
||||
Serial.printf("Remote Control key pressed: ");
|
||||
switch(remoteKey->getNewVal()){
|
||||
case 4:
|
||||
Serial.printf("UP ARROW\n");
|
||||
break;
|
||||
case 5:
|
||||
Serial.printf("DOWN ARROW\n");
|
||||
break;
|
||||
case 6:
|
||||
Serial.printf("LEFT ARROW\n");
|
||||
break;
|
||||
case 7:
|
||||
Serial.printf("RIGHT ARROW\n");
|
||||
break;
|
||||
case 8:
|
||||
Serial.printf("SELECT\n");
|
||||
break;
|
||||
case 9:
|
||||
Serial.printf("BACK\n");
|
||||
break;
|
||||
case 11:
|
||||
Serial.printf("PLAY/PAUSE\n");
|
||||
break;
|
||||
case 15:
|
||||
Serial.printf("INFO\n");
|
||||
break;
|
||||
default:
|
||||
Serial.print("UNKNOWN KEY\n");
|
||||
}
|
||||
}
|
||||
|
||||
return(true);
|
||||
}
|
||||
};
|
||||
|
||||
///////////////////////////////
|
||||
|
||||
void setup() {
|
||||
|
||||
Serial.begin(115200);
|
||||
|
||||
homeSpan.begin(Category::Television,"HomeSpan Television");
|
||||
|
||||
new SpanAccessory();
|
||||
new Service::AccessoryInformation();
|
||||
new Characteristic::Name("HomeSpan TV");
|
||||
new Characteristic::Manufacturer("HomeSpan");
|
||||
new Characteristic::SerialNumber("123-ABC");
|
||||
new Characteristic::Model("HomeSpan");
|
||||
new Characteristic::FirmwareRevision("0.1");
|
||||
new Characteristic::Identify();
|
||||
|
||||
new Service::HAPProtocolInformation();
|
||||
new Characteristic::Version("1.1.0");
|
||||
|
||||
// Below we define 10 different InputSource Services using different combinations
|
||||
// of Characteristics to demonstrate how they interact and appear to the user in the Home App
|
||||
|
||||
SpanService *hdmi1 = new Service::InputSource(); // Source included in Selection List, but excluded from Settings Screen
|
||||
new Characteristic::ConfiguredName("HDMI 1");
|
||||
new Characteristic::Identifier(1);
|
||||
|
||||
SpanService *hdmi2 = new Service::InputSource();
|
||||
new Characteristic::ConfiguredName("HDMI 2");
|
||||
new Characteristic::Identifier(2);
|
||||
new Characteristic::IsConfigured(0); // Source excluded from both the Selection List and the Settings Screen
|
||||
|
||||
SpanService *hdmi3 = new Service::InputSource();
|
||||
new Characteristic::ConfiguredName("HDMI 3");
|
||||
new Characteristic::Identifier(3);
|
||||
new Characteristic::IsConfigured(1); // Source included in both the Selection List and the Settings Screen
|
||||
|
||||
SpanService *hdmi4 = new Service::InputSource();
|
||||
new Characteristic::ConfiguredName("HDMI 4");
|
||||
new Characteristic::Identifier(4);
|
||||
new Characteristic::IsConfigured(1); // Source included in the Settings Screen...
|
||||
new Characteristic::CurrentVisibilityState(1); // ...but excluded from the Selection List
|
||||
|
||||
SpanService *hdmi5 = new Service::InputSource();
|
||||
new Characteristic::ConfiguredName("HDMI 5");
|
||||
new Characteristic::Identifier(5);
|
||||
new Characteristic::IsConfigured(1); // Source included in the Settings Screen...
|
||||
new Characteristic::CurrentVisibilityState(0); // ...and included in the Selection List
|
||||
|
||||
SpanService *hdmi6 = new Service::InputSource();
|
||||
new Characteristic::ConfiguredName("HDMI 6");
|
||||
new Characteristic::Identifier(6);
|
||||
new Characteristic::IsConfigured(0); // Source excluded from both the Selection List and the Settings Screen
|
||||
new Characteristic::CurrentVisibilityState(0); // If IsConfigured(0) is specified, CurrentVisibilityState() has no effect
|
||||
|
||||
SpanService *hdmi7 = new Service::InputSource();
|
||||
new Characteristic::ConfiguredName("HDMI 7");
|
||||
new Characteristic::Identifier(7);
|
||||
new Characteristic::IsConfigured(1); // Source included in the Settings Screen...
|
||||
new Characteristic::CurrentVisibilityState(0); // ...and included in the Selection List...
|
||||
new Characteristic::TargetVisibilityState(0); // ...and a "checked" checkbox is provided on the Settings Screen that can be used to toggle CurrentVisibilityState()
|
||||
|
||||
SpanService *hdmi8 = new Service::InputSource();
|
||||
new Characteristic::ConfiguredName("HDMI 8");
|
||||
new Characteristic::Identifier(8);
|
||||
new Characteristic::IsConfigured(1); // Source included in the Settings Screen...
|
||||
new Characteristic::CurrentVisibilityState(1); // ...but excluded from the Selection List...
|
||||
new Characteristic::TargetVisibilityState(1); // ...and an "un-checked" checkbox is provided on the Settings Screen that can be used to toggle CurrentVisibilityState()
|
||||
|
||||
SpanService *hdmi9 = new Service::InputSource();
|
||||
new Characteristic::ConfiguredName("HDMI 9");
|
||||
new Characteristic::IsConfigured(1); // Source included in the Settings Screen...
|
||||
new Characteristic::CurrentVisibilityState(0); // ...but without an Identifier() set, the Source is excluded from the Selection List regardless of CurrentVisibilityState(0)
|
||||
new Characteristic::TargetVisibilityState(0);
|
||||
|
||||
SpanService *hdmi10 = new Service::InputSource();
|
||||
new Characteristic::ConfiguredNameStatic("HDMI 10"); // Source Name is static and cannot be edited in Settings Screen
|
||||
new Characteristic::Identifier(10);
|
||||
new Characteristic::IsConfigured(1); // Source included in the Settings Screen...
|
||||
new Characteristic::CurrentVisibilityState(0); // ...and included in the Selection List...
|
||||
new Characteristic::TargetVisibilityState(0); // ...and a "checked" checkbox is provided on the Settings Screen that can be used to toggle CurrentVisibilityState()
|
||||
|
||||
(new HomeSpanTV("Test TV")) // Define a Television Service. Must link in InputSources!
|
||||
->addLink(hdmi1)
|
||||
->addLink(hdmi2)
|
||||
->addLink(hdmi3)
|
||||
->addLink(hdmi4)
|
||||
->addLink(hdmi5)
|
||||
->addLink(hdmi6)
|
||||
->addLink(hdmi7)
|
||||
->addLink(hdmi8)
|
||||
->addLink(hdmi9)
|
||||
->addLink(hdmi10)
|
||||
;
|
||||
|
||||
}
|
||||
|
||||
///////////////////////////////
|
||||
|
||||
void loop() {
|
||||
homeSpan.poll();
|
||||
}
|
||||
|
|
@ -35,6 +35,7 @@ The table below provides a list of all HomeSpan Categories.
|
|||
* VideoDoorbells
|
||||
* Windows
|
||||
* WindowCoverings
|
||||
* Television
|
||||
|
||||
Note that the HomeKit primarily uses the Accessory Category of a device for determining the icon to show in the Home App when a device is being paired. Apart from this purely cosmetic function, the Category assigned to a device does not in any way limit which Services or Characteristics can be implemented on that device.
|
||||
|
||||
|
|
|
|||
|
|
@ -80,11 +80,11 @@ The ESP32 has an on-chip signal-generator peripheral designed to drive an RF or
|
|||
|
||||
`#include "extras/RFControl.h"`
|
||||
|
||||
### *RFControl(int pin)*
|
||||
### *RFControl(int pin, boolean refClock=true)*
|
||||
|
||||
Creating an instance of this **class** initializes the RF/IR signal generator and specifies the ESP32 *pin* to output the signal. You may create more than one instance of this class if driving more than one RF/IR transmitter (each connected to different *pin*), subject to the following limitations: ESP32 - 8 instances; ESP32-S2 - 4 instances; ESP32-C3 - 2 instances.
|
||||
Creating an instance of this **class** initializes the RF/IR signal generator and specifies the ESP32 *pin* to output the signal. You may create more than one instance of this class if driving more than one RF/IR transmitter (each connected to different *pin*), subject to the following limitations: ESP32 - 8 instances; ESP32-S2 - 4 instances; ESP32-C3 - 2 instances. The optional parameter *refClock* is more fully described further below under the `start()` method.
|
||||
|
||||
Signals are defined as a sequence of HIGH and LOW phases that together form a pulse train where you specify the duration, in *ticks*, of each HIGH and LOW phase, shown respectively as H1-H4 and L1-L4 in the diagram below.
|
||||
Signals are defined as a sequence of HIGH and LOW phases that together form a pulse train where you specify the duration, in *ticks*, of each HIGH and LOW phase, shown respectively as H1-H4 and L1-L4 in the following diagram:
|
||||
|
||||

|
||||
|
||||
|
|
@ -105,20 +105,34 @@ Details of each function are as follows:
|
|||
|
||||
* clears the pulse train memory structure of a specific instance of RFControl
|
||||
|
||||
* `void phase(uint16_t numTicks, uint8_t phase)`
|
||||
* `void phase(uint32_t numTicks, uint8_t phase)`
|
||||
|
||||
* appends either a HIGH or LOW phase to the pulse train memory buffer for a specific instance of RFControl
|
||||
|
||||
* *numTicks* - the duration, in *ticks* of the pulse phase. Allowable range is 0-32767 ticks. Requests to add a pulse with *numTicks* outside this range are ignored, but raise non-fatal warning message
|
||||
* *numTicks* - the duration, in *ticks* of the pulse phase. Durations of greater than 32767 ticks allowed (the system automatically creates repeated pulses of a maximum of 32767 ticks each until the specified duration of *numTicks* is reached)
|
||||
|
||||
* *phase* - set to 0 to create a LOW phase; set to 1 (or any non-zero number) to create a HIGH phase
|
||||
|
||||
* repeated phases of the same type (e.g. HIGH followed by another HIGH) are permitted and result in a single HIGH or LOW phase with a duration equal to the sum of the *numTicks* specified for each repeated phase (this is helpful when generating Manchester-encoded signals)
|
||||
|
||||
* `void add(uint16_t onTime, uint16_t offTime)`
|
||||
* `void add(uint32_t onTime, uint32_t offTime)`
|
||||
|
||||
* appends a single HIGH/LOW pulse with duration *onTime* followed by *offTime* to the pulse train of a specific instance of RFControl. This is functionally equivalent to calling `phase(onTime,HIGH);` followed by `phase(offTime,LOW);` as defined above
|
||||
|
||||
* `void enableCarrier(uint32_t freq, float duty=0.5)`
|
||||
|
||||
* enables modulation of the pulse train with a "square" carrier wave. In practice this is only used for IR signals (not RF)
|
||||
|
||||
* *freq* - the frequency, in Hz, of the carrier wave. If freq=0, carrier wave is disabled
|
||||
|
||||
* *duty* - the duty cycle of the carrier wave, from 0-1. Default is 0.5 if not specified
|
||||
|
||||
* RFControl will report an error if the combination of the specified frequency and duty cycle is outside the range of supported configurations
|
||||
|
||||
* `void disableCarrier()`
|
||||
|
||||
* disables the carrier wave. Equivalent to `enableCarrier(0);`
|
||||
|
||||
* `void start(uint8_t _numCycles, uint8_t tickTime)`
|
||||
* `void start(uint32_t *data, int nData, uint8_t nCycles, uint8_t tickTime)`
|
||||
|
||||
|
|
@ -126,7 +140,7 @@ Details of each function are as follows:
|
|||
|
||||
* *numCycles* - the total number of times to transmit the pulse train (i.e. a value of 3 means the pulse train will be transmitted once, followed by 2 additional re-transmissions). This is an optional argument with a default of 1 if not specified.
|
||||
|
||||
* *tickTime* - the duration, in **microseconds**, of a *tick*. This is an optional argument with a default of 1𝛍s if not specified. Valid range is 1-255𝛍s, or set to 0 for 256𝛍s
|
||||
* *tickTime* - the duration, in ***clock units***, of a *tick*. This is an optional argument with a default of 1 *clock unit* if not specified. Valid range is 1-255 *clock units*, or set to 0 for 256 *clock units*. The duration of a *clock unit* is determined by the *refClock* parameter (the second, optional argument, in the RFControl constructor described above). If *refClock* is set to true (the default), RFControl uses the ESP32's 1 MHz Reference Clock for timing so that each *clock unit* equals 1𝛍s. If *refClock* is set to false, RFControl uses the ESP32's faster 80 MHz APB Clock so that each *clock unit* equals 0.0125𝛍s (1/80 of microsecond)
|
||||
|
||||
* To aid in the creation of a pulse train stored in an external array of 32-bit words, RFControl includes the macro *RF_PULSE(highTicks,lowTicks)* that returns a properly-formatted 32-bit value representing a single HIGH/LOW pulse of duration *highTicks* followed by *lowTicks*. This is basically an analog to the `add()` function. For example, the following code snippet shows two ways of creating and transmitting the same 3-pulse pulse-train --- the only difference being that one uses the internal memory structure of RFControl, and the second uses an external array:
|
||||
|
||||
|
|
@ -146,7 +160,7 @@ rf.start(pulseTrain,3,4,1000); // start transmission using the same parameters
|
|||
|
||||
### Example RFControl Sketch
|
||||
|
||||
Below is a complete sketch that produces two different pulse trains with the signal output linked to the ESP32 device's built-in LED (rather than an RF or IR transmitter). For illustrative purposes the tick duration has been set to a very long 100𝛍s, and pulse times range from of 1000-10,000 ticks, so that the individual pulses are easily discernable on the LED. Note this example sketch is also available in the Arduino IDE under [*File → Examples → HomeSpan → Other Examples → RemoteControl*](https://github.com/HomeSpan/HomeSpan/tree/dev/Other%20Examples/RemoteControl).
|
||||
Below is a complete sketch that produces two different pulse trains with the signal output linked to the ESP32 device's built-in LED (rather than an RF or IR transmitter). For illustrative purposes the tick duration has been set to a very long 100𝛍s, and pulse times range from of 1000-10,000 ticks, so that the individual pulses are easily discernable on the LED. Note this example sketch is also available in the Arduino IDE under [*File → Examples → HomeSpan → Other Examples → RemoteControl*](../Other%20Examples/RemoteControl).
|
||||
|
||||
```C++
|
||||
/* HomeSpan Remote Control Example */
|
||||
|
|
|
|||
|
|
@ -32,9 +32,9 @@
|
|||
* *sketch* - the version number of the sketch, as specified with `homeSpan.setSketchVersion(const char *)`, or *n/a* if no version was specified
|
||||
* *ota* - either *yes* or *no* depending on whether OTA was enabled for the sketch using the method `homeSpan.enableOTA()`
|
||||
|
||||
#### Where are the Television Services?
|
||||
#### Does HomeSpan support Television Services?
|
||||
|
||||
* Though HomeKit now supports Television controls, at the time of this posting Apple has not publicly released any specifications, SDKs, or ADKs, related to HAP Television Services or any HAP Television Characteristics. It appears that for now these Services are meant only for commercial use. Support for HAP Television Services and Characteristics will be added to HomeSpan as soon as Apple makes the specifications publicly available in some form or another.
|
||||
* Yes. Though undocumented by Apple and not officially part of HAP-R2, HomeSpan supports HomeKit Television controls. See [Television Services](../docs/TVServices.md) for details.
|
||||
|
||||
#### Can you use HomeSpan with an Ethernet connection instead of a WiFi connection?
|
||||
|
||||
|
|
|
|||
|
|
@ -41,34 +41,13 @@ HomeSpan is fully compatible with both Versions 1 and 2 of the [Arduino-ESP32 Bo
|
|||
* Launch the WiFi Access Point
|
||||
* A standalone, detailed End-User Guide
|
||||
|
||||
## ❗Latest Update - HomeSpan 1.4.0 (10/9/2021)
|
||||
## ❗Latest Update - HomeSpan 1.4.1 (10/31/2021)
|
||||
|
||||
**HomeSpan is now fully compatible with Version 2.0.0 of the Arduino-ESP32 Board Manager and will run on the following Espressif chips:**
|
||||
* **ESP32**
|
||||
* **ESP32-S2**
|
||||
* **ESP32-C3**
|
||||
* **Television Services and Characteristics have been added to HomeSpan!** See [HomeSpan Television Services](https://github.com/HomeSpan/HomeSpan/blob/master/docs/TVServices.md) for complete details
|
||||
|
||||
HomeSpan also maintains full backwards-compatability with Version 1.0.6 of the Arduino-ESP Board Manager should you need to revert to that version. This has been a complicated update! Please report any bugs or issues found when using Version 2 of the Arduino-ESP32.
|
||||
* **The RFControl library has been updated to allow for the generation of a modulating carrier wave suitable for controlling an infrared LED.** This allows you to create HomeKit-enabled TV remote controls with HomeSpan. See [HomeSpan Projects](https://github.com/topics/homespan) for some real-world examples!
|
||||
|
||||
Also included in HomeSpan 1.4.0 are the following new features and enhancements:
|
||||
|
||||
* **The PWM library has been upgraded**
|
||||
* users can specify a custom PWM frequency for each instance of LedPin() (subject to chip-specific resource limitations)
|
||||
* users can set the duty cycle of an LedPin() as a decimal floating number (e.g., 34.56), instead of just an integer
|
||||
* the library automatically sets the duty resolution to the maximum allowable value for a chosen PWM frequency
|
||||
* the library optimzizes the distribution of multiple LedPin() and ServoPin() instances to ensure all available PWM channels are used
|
||||
|
||||
* **The RFControl library has been upgraded**
|
||||
* the library allows for the transmisison of arbitrary-length pulse trains
|
||||
* users can pre-load pulse trains into one or more arbitrary-length arrays of 32-bit words for on-demand transmission as needed
|
||||
|
||||
* Users can now **limit the selection choices** of certain Characteristics (such as the Target State for a Security System) in the Home App using a new method, `setValidValues()`
|
||||
|
||||
* **The Status LED and Control Button are now optional components that are ignored unless specifically enabled**
|
||||
* because HomeSpan now runs on chips with many different pin configurations, HomeSpan's use of preset pin numbers for the Status LED and Control Button is likely to conflict with many devices
|
||||
* the default behavior for HomeSpan has been changed to ignore all logic related to the Status LED and Control Button
|
||||
* to enable the Status LED you must specify the pin to which your LED is attached using the usual method `homeSpan.setStatusPin(pin)`
|
||||
* to enable the Control Button you must specify the pin to which your Control Button is attached using the usual method `homeSpan.setControlPin(pin)`
|
||||
* **User-defined Custom Characteristics can be added to HomeSpan with a new macro.** See the [HomeSpan API](https://github.com/HomeSpan/HomeSpan/blob/master/docs/Reference.md#define-custom_charnameuuidpermsformatdefaultvalueminvaluemaxvaluestaticrange) for details (for *advanced* users only)
|
||||
|
||||
See [Releases](https://github.com/HomeSpan/HomeSpan/releases) for details on all changes included in this update.
|
||||
|
||||
|
|
@ -87,6 +66,7 @@ HomeSpan includes the following documentation:
|
|||
* [HomeSpan QR Codes](https://github.com/HomeSpan/HomeSpan/blob/master/docs/QRCodes.md) - create and use QR Codes for pairing HomeSpan devices
|
||||
* [HomeSpan OTA](https://github.com/HomeSpan/HomeSpan/blob/master/docs/OTA.md) - update your sketches Over-the-Air directly from the Arduino IDE without a serial connection
|
||||
* [HomeSpan Extras](https://github.com/HomeSpan/HomeSpan/blob/master/docs/Extras.md) - integrated access to the ESP32's on-chip LED, Servo Motor, and Remote Control peripherals!
|
||||
* [HomeSpan Television Services](https://github.com/HomeSpan/HomeSpan/blob/master/docs/TVServices.md) - how to use HomeKit's undocumented Television Services and Characteristics
|
||||
* [HomeSpan Projects](https://github.com/topics/homespan) - real-world applications of the HomeSpan Library
|
||||
* [HomeSpan FAQ](https://github.com/HomeSpan/HomeSpan/blob/master/docs/FAQ.md) - answers to frequently-asked questions
|
||||
|
||||
|
|
|
|||
|
|
@ -266,13 +266,42 @@ To invoke this command from the CLI, preface the single-letter name *c* with '@'
|
|||
|
||||
To create more than one user-defined command, simply create multiple instances of SpanUserCommand, each with its own single-letter name. Note that re-using the same single-letter name in an instance of SpanUserCommand over-rides any previous instances using that same letter.
|
||||
|
||||
## *#define REQUIRED VERSION(major,minor,patch)*
|
||||
## User Macros
|
||||
|
||||
### *#define REQUIRED VERSION(major,minor,patch)*
|
||||
|
||||
If REQUIRED is defined in the main sketch prior to including the HomeSpan library with `#include "HomeSpan.h"`, HomeSpan will throw a compile-time error unless the version of the library included is equal to, or later than, the version specified using the VERSION macro. Example:
|
||||
|
||||
```C++
|
||||
#define REQUIRED VERISON(2,1,3) // throws a compile-time error unless HomeSpan library used is version 2.1.3 or later
|
||||
```
|
||||
### *#define CUSTOM_CHAR(name,uuid,perms,format,defaultValue,minValue,maxValue,staticRange)*
|
||||
|
||||
Creates a custom Characteristic that can be added to any Service. Custom Characteristics are generally ignored by the Home App but may be used by other third-party applications (such as Eve for HomeKit). Parameters are as follows (note that quotes should NOT be used in any of the string parameters):
|
||||
|
||||
* *name* - the name of the custom Characteristic. This will be added to the Characteristic namespace so that it is accessed the same as any HomeSpan Characteristic
|
||||
* *uuid* - the UUID of the Characteristic as defined by the manufacturer. Must be *exactly* 36 characters in the form XXXXXXXX-XXXX-XXXX-XXXX-XXXXXXXXXXXX, where *X* represent a valid hexidecimal digit. Leading zeros are required if needed as described more fully in HAP-R2 Section 6.6.1
|
||||
* *perms* - additive list of permissions as described in HAP-R2 Table 6-4. Valid values are PR, PW, EV, AA, TW, HD, and WR
|
||||
* *format* - specifies the format of the Characteristic value, as described in HAP-R2 Table 6-5. Valid value are BOOL, UINT8, UINT16, UNIT32, UINT64, INT, FLOAT, and STRING. Note that the HomeSpan does not presently support the TLV8 or DATA formats
|
||||
* *defaultValue* - specifies the default value of the Characteristic if not defined during instantiation
|
||||
* *minValue* - specifies the default minimum range for a valid value, which may be able to be overriden by a call to `setRange()`
|
||||
* *minValue* - specifies the default minimum range for a valid value, which may be able to be overriden by a call to `setRange()`
|
||||
* *staticRange* - set to *true* if *minValue* and *maxValue* are static and cannot be overridden with a call to `setRange()`. Set to *false* if calls to `setRange()` are allowed
|
||||
|
||||
As an example, the following creates a custom Characteristic named "Voltage" with a UUID code that is recognized by Eve for HomeKit. The parameters show that the Characteristic is read-only (PR) and notifications are enabled (EV). The default range of allowed values is 0-240, with a default of 120. The range *can* be overridden by subsequent calls to `setRange()`:
|
||||
|
||||
```C++
|
||||
CUSTOM_CHAR(Voltage, E863F10A-079E-48FF-8F27-9C2605A29F52, PR+EV, UINT16, 120, 0, 240, false);
|
||||
...
|
||||
new Service::LightBulb();
|
||||
new Characteristic::Name("Low-Voltage Lamp");
|
||||
new Characteristic::On(0);
|
||||
new Characteristic::Brightness(50);
|
||||
new Characteristic::Voltage(12); // adds Voltage Characteristics and sets initial value to 12 volts
|
||||
```
|
||||
|
||||
Note that Custom Characteristics must be created prior to calling `homeSpan.begin()`
|
||||
|
||||
---
|
||||
|
||||
#### Deprecated functions (available for backwards compatibility with older sketches):
|
||||
|
|
|
|||
|
|
@ -40,6 +40,7 @@ Additionally, when first starting up, HomeSpan begins by validating the device's
|
|||
| HeaterCooler | Active<br>CurrentTemperature<br>CurrentHeaterCoolerState<br>TargetHeaterCoolerState | Name<br>RotationSpeed<br>TemperatureDisplayUnits<br>SwingMode<br>CoolingThresholdTemperature<br>HeatingThresholdTemperature<br>LockPhysicalControls |
|
||||
| HumidifierDehumidifier | Active<br>CurrentRelativeHumidity<br>CurrentHumidifierDehumidifierState<br>TargetHumidifierDehumidifierState | Name<br>RelativeHumidityDehumidifierThreshold<br>RelativeHumidityHumidifierThreshold<br>RotationSpeed<br>SwingMode<br>WaterLevel<br>LockPhysicalControls |
|
||||
| HumiditySensor | CurrentRelativeHumidity | Name<br>StatusActive<br>StatusFault<br>StatusTampered<br>StatusLowBattery |
|
||||
| InputSource | Identifier | ConfiguredName<br>IsConfigured<br>CurrentVisibilityState<br>TargetVisibilityState |
|
||||
| IrrigationSystem | Active<br>ProgramMode<br>InUse | RemainingDuration<br>Name<br>StatusFault |
|
||||
| LeakSensor | LeakDetected | Name<br>StatusActive<br>StatusFault<br>StatusTampered<br>StatusLowBattery |
|
||||
| LightBulb | On | Brightness<br>Hue<br>Name<br>Saturation<br>ColorTemperature |
|
||||
|
|
@ -56,6 +57,7 @@ Additionally, when first starting up, HomeSpan begins by validating the device's
|
|||
| Speaker | Mute | Name<br>Volume |
|
||||
| StatelessProgrammableSwitch | ProgrammableSwitchEvent | Name<br>ServiceLabelIndex |
|
||||
| Switch | On | Name |
|
||||
| Television | Active | ConfiguredName<br>ActiveIdentifier<br>RemoteKey<br>PowerModeSelection |
|
||||
| TemperatureSensor | CurrentTemperature | Name<br>StatusActive<br>StatusFault<br>StatusTampered<br>StatusLowBattery |
|
||||
| Thermostat | CurrentHeatingCoolingState<br>TargetHeatingCoolingState<br>CurrentTemperature<br>TargetTemperature<br>TemperatureDisplayUnits | CoolingThresholdTemperature<br>CurrentRelativeHumidity<br>HeatingThresholdTemperature<br>Name<br>TargetRelativeHumidity |
|
||||
| Valve | Active<br>InUse<br>ValveType | SetDuration<br>RemainingDuration<br>IsConfigured<br>ServiceLabelIndex<br>StatusFault<br>Name |
|
||||
|
|
@ -68,6 +70,7 @@ Additionally, when first starting up, HomeSpan begins by validating the device's
|
|||
|Characteristic|Type|Default
|
||||
|---|---|---|
|
||||
|Active|uint8_t|0|
|
||||
|ActiveIdentifier|uint32_t|0|
|
||||
|AirQuality|uint8_t|0|
|
||||
|BatteryLevel|uint8_t|0|
|
||||
|Brightness|int|0|
|
||||
|
|
@ -80,6 +83,7 @@ Additionally, when first starting up, HomeSpan begins by validating the device's
|
|||
|ChargingState|uint8_t|0|
|
||||
|CoolingThresholdTemperature|double|10|
|
||||
|ColorTemperature|uint32_t|50|
|
||||
|ConfiguredName|char \*|"unnamed"|
|
||||
|ContactSensorState|uint8_t|1|
|
||||
|CurrentAmbientLightLevel|double|1|
|
||||
|CurrentHorizontalTiltAngle|int|0|
|
||||
|
|
@ -95,6 +99,7 @@ Additionally, when first starting up, HomeSpan begins by validating the device's
|
|||
|CurrentRelativeHumidity|double|0|
|
||||
|CurrentTemperature|double|0|
|
||||
|CurrentTiltAngle|int|0|
|
||||
|CurrentVisibilityState|uint8_t|0|
|
||||
|FilterLifeLevel|double|0|
|
||||
|FilterChangeIndication|uint8_t|0|
|
||||
|FirmwareRevision|char \*|"1.0.0"|
|
||||
|
|
@ -102,6 +107,7 @@ Additionally, when first starting up, HomeSpan begins by validating the device's
|
|||
|HeatingThresholdTemperature|double|16|
|
||||
|HoldPosition|boolean|false|
|
||||
|Hue|double|0|
|
||||
|Identifier|uint32_t|0|
|
||||
|Identify|boolean|false|
|
||||
|InUse|uint8_t|0|
|
||||
|IsConfigured|uint8_t|0|
|
||||
|
|
@ -123,11 +129,13 @@ Additionally, when first starting up, HomeSpan begins by validating the device's
|
|||
|OzoneDensity|double|0|
|
||||
|PM10Density|double|0|
|
||||
|PositionState|uint8_t|2|
|
||||
|PowerModeSelection|uint8_t|0|
|
||||
|ProgramMode|uint8_t|0|
|
||||
|ProgrammableSwitchEvent|uint8_t|0|
|
||||
|RelativeHumidityDehumidifierThreshold|double|50|
|
||||
|RelativeHumidityHumidifierThreshold|double|50|
|
||||
|RemainingDuration|uint32_t|60|
|
||||
|RemoteKey|uint8_t|0|
|
||||
|ResetFilterIndication|uint8_t|0|
|
||||
|RotationDirection|int|0|
|
||||
|RotationSpeed|double|0|
|
||||
|
|
@ -161,6 +169,7 @@ Additionally, when first starting up, HomeSpan begins by validating the device's
|
|||
|TargetTemperature|double|16|
|
||||
|TemperatureDisplayUnits|uint8_t|0|
|
||||
|TargetVerticalTiltAngle|int|0|
|
||||
|TargetVisibilityState|uint8_t|0|
|
||||
|ValveType|uint8_t|0|
|
||||
|Version|char \*|"1.0.0"|
|
||||
|VOCDensity|double|0|
|
||||
|
|
|
|||
|
|
@ -0,0 +1,74 @@
|
|||
# Television Services and Characteristics
|
||||
|
||||
HomeSpan includes a number of undocumented Television Services and Characteristics that are not part of HAP-R2. Though the UUID and specifications for each Television Service and Characteristic have been identified by the broader HomeKit community, it is only by trial-and-error that proper usage can be determined. This page documents the results of experimenting with the different Television Services and Characteristics within HomeSpan using the Home App provided in iOS 15.1. This documentaiton should be considered reliable, but Apple can of course change the behavior of such Service and Characteristics within the Home App at any time and without notice.
|
||||
|
||||
### `Category::Television`
|
||||
|
||||
Use `Category::Television` as the category in `homeSpan.begin()` to specify a Television Accessory. This causes the Home App to display an TV icon in the Accessory Tile. However, this only seems to work for the first Accessory implemented. If you create a device with multiple Television Accessories, or place a Television Accessory behind a Bridge Accessory, the icon for the TV Tile converts to a generic HomeKit symbol.
|
||||
|
||||
### `Service::Television()`
|
||||
|
||||
Use `Service::Television()` as the Service to create a Television Accessory Tile. It support two primary Characteristics:
|
||||
|
||||
* `Characteristic::Active()` - this HAP-R2 standard Characteristic it used to turn the TV on or off. It is a required Characteristic
|
||||
|
||||
* `Characteristic::ConfiguredName()` - this is an optional, TV-specific version of `Characteristic::Name()` that seems to be the only way to set the default name of the TV. Unlike all other HomeKit Services, the Home App ignores any names specified with `Characeteristic::Name()` when used with `Service::Television()`
|
||||
|
||||
Based on the above, the following code snippet defines a simple TV Accessory with a basic on/off switch:
|
||||
|
||||
```C++
|
||||
new Service::Television();
|
||||
new Characteristic::Active(0); // set power to OFF at start-up
|
||||
new Characteristic::ConfiguredName("Sony TV"); // optional Characteristic to set name of TV
|
||||
```
|
||||
More advanced control of a TV can enabled with two other optional Characteristics:
|
||||
|
||||
* `Characteristic::RemoteKey()` - this write-only numerical Characteristic enables HomeSpan to read button presses from the Remote Control widget on an iPhone that can be found under the Control Center. This widget is normally used to control Apple TVs, but it seems any Television Accessory created per above can also be operated from the Remote Control widget. The layout of the widget (which cannot be modified) includes 4 arrows, a central select button, a play/pause button, a large "back" button, and an "info" button. When a "key" is pressed, the Home App sends an update to `Characteristic::RemoteKey()` that can be read by HomeSpan using the usual `update()` method. Values are as follows:
|
||||
|
||||
* 4 = up arrow
|
||||
* 5 = down arrow
|
||||
* 6 = left arrow
|
||||
* 7 = right arrow
|
||||
* 8 = center select button
|
||||
* 9 = back button
|
||||
* 11 = play/pause button
|
||||
* 15 = info button
|
||||
|
||||
* `Characteristic::PowerModeSelection()` - this write-only Characteristic causes the text "View TV Settings" to appear in the Home App under the Settings page for a TV Accessory. When this text is pressed, the Home App sends an update with value=0 to `Characteristic::PowerModeSelection()` that can be read by HomeSpan using the usual `update()` method
|
||||
|
||||
* `Characteristic::ActiveIdentifier()` - this numerical Characteristic is used to control the input source for the TV (e.g. HDMI-1, HDMI-2, Netflix, etc.). It is only used when input sources are defined and linked using `Service::InputSource()` (see below), in which case it is a *required* Characteristic
|
||||
|
||||
### `Service::InputSource()`
|
||||
|
||||
Use `Service::InputSource()` to create a new input source selection for the TV, such as HDMI-1, HDMI-2, Netflix, etc. The use of `Service::InputSource()` is optional - it is perfectly okay to create a Television Service without the ability to select different Input Sources. However, if used, each Input Source Service added should be defined in the *same* Accessory as the Television Service to which it applies, and ***must*** be linked to that Television Service using `addLink()`. The Home App behaves unexpectedly if it finds any Input Source Services that are not linked to a Television Service.
|
||||
|
||||
Input Sources can appear in two places within the Home App. The first is in the Input Source "Selector" that is shown below the On/Off power button when you open the controls for a TV (i.e. long-press the Accessory Tile). This is how you change the Input Source for the TV. The second place that Input Sources appear is on the Settings page for a TV Accessory. This is where you can change the name of an Input Source, as well as configure whether to include or exclude a particular Input Source from the Input Source Selector.
|
||||
|
||||
The overall idea is that your sketch should implement a TV Accessory containing a full list of all potential inputs, using names that match the labels on the TV, such as "HDMI 1", "Component 1", "HDMI 2", etc. If your TV Remote has dedicated buttons for Netflix, HBO Max, Amazon Prime, etc. you can add these to the list as well. Once this generic list is created, you can then rename and enable each Input Source directly from within the Home App. For example you might rename "HDMI 1" to "Comcast Cable", and "HDMI 2" to "Sony Blue-Ray". If you have nothing connected to the "Component 1", you can exclude it from the Input Source Selector. This makes it easy to configure and re-configure your TV Input Sources without ever having to change or update your HomeSpan sketch.
|
||||
|
||||
All of this is accomplished by using a combination of some, or all, of the following Characteristics:
|
||||
|
||||
* `Characteristic::ConfiguredName()` - similar to how its used when applied to `Service::Television()`, this Characteristic allows you set the default name for an Input Source. Note that if you change the name of an Input Source in the Home App, an update will be sent to HomeSpan with the new name for you to use in your sketch if needed. This is very different from the usual `Characteristic::Name()` used for many other Services, and for which name changes performed in the Home App are never communicated back to the Accessory
|
||||
|
||||
* `Characteristic::Identifier()` - this numerical Characteristic sets an ID for each Input Source. Any unsigned 32-bit number can be used as an ID, provided it is *unique* and not used by any other Input Source in the same TV Service. When you use the Input Source Selector in the Home App to choose a particular Input Soure, the `Characteristic::ActiveIdentifier()` from the Television Service (see above) will be updated with a value that matches the ID corresponding to the chosen Input Source. Within HomeSpan you simply use the `update()` method to determine when `Characteristic::ActiveIdentifer()` is updated, and, based on its value, which Input Source was chosen. HomeKit does not seem to require `Characteristic::Identifier()` be defined for an Input Source. However, if it not set, the Home App will not allow it to be displayed as a choice in the Input Source Selector, which defeats the purpose of creating an Input Source!
|
||||
|
||||
* `Characteristic::IsConfigured()` - this Characteristic determines whether an Input Source is allowed to appear as a choice in the Input Source Selector of the Home App. If IsConfigured() is defined and set to 0, the Input Source will appear in the Settings page, but it will be excluded as a choice from the Input Source Selector. If IsConfigured() is defined and set to 1, the Input Source will appear in the Settings page, and will also be included as a choice in the Input Source Selector. If `Characteristic::IsConfigured()` is not defined for an Input Source, that source will still appear as a choice in the Input Source Selector, but it will *not* appear in the list of Input Sources found on the Settings page. This means you will not be able to rename the Input Source from the Home App, nor toggle it as an allowable choice in the Input Selector (see below)
|
||||
|
||||
* `Characteristic::CurrentVisibilityState()` and `Characteristic::TargetVisibilityState()` - these two Characteristics work in tandem much like any current-state/target-state pair. When these are defined for an Input Source, a checkbox toggle appears next to the name of the Input Source on the Settings page, provided `Characteristic::IsConfigured()` has also been defined. Clicking the checkbox causes the Home App to toggle the TargetVisibilityState between 0 to 1, where 0 ironically means the checkbox is *checked*, and 1 means it is *unchecked* (the reverse of what you might expect!). If you read this update in HomeSpan you can then use `setVal()` to change the CurrentVisibiltyState() to match the TargetVisibilityState(). Setting CurrentVisibilityState() to 0 means the Input Source appears as a choice in the Input Source Selector. Setting CurrentVisibilityState() to 1 means it does not appear as a selection. Note these features only operate if an ID has been set for the Input Source with `Characteristic::Identifier()`, and IsConfigured() has been defined and set to 1
|
||||
|
||||
### Examples
|
||||
|
||||
Please see [*File → Examples → HomeSpan → Other Examples → Television*](../Other%20Examples/Television) for a complete worked example demonstrating the effects of using different combinations of the above Characteristics. Also, don't forget to check out the [HomeSpan Projects](https://github.com/topics/homespan) page for some real-world examples of TV sketches and controllers.
|
||||
|
||||
|
||||
### Credits
|
||||
|
||||
Much thanks to @unreality for the PR to include Television codes and associated functionality!
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
---
|
||||
|
||||
[↩️](README.md) Back to the Welcome page
|
||||
|
|
@ -1,5 +1,5 @@
|
|||
name=HomeSpan
|
||||
version=1.4.0
|
||||
version=1.4.1
|
||||
author=Gregg <homespan@icloud.com>
|
||||
maintainer=Gregg <homespan@icloud.com>
|
||||
sentence=A robust and extremely easy-to-use HomeKit implementation for the Espressif ESP32 running on the Arduino IDE.
|
||||
|
|
|
|||
|
|
@ -53,6 +53,17 @@ enum FORMAT { // HAP Table 6-5
|
|||
|
||||
///////////////////////////////
|
||||
|
||||
typedef boolean BOOL_t;
|
||||
typedef uint8_t UINT8_t;
|
||||
typedef uint16_t UINT16_t;
|
||||
typedef uint32_t UINT32_t;
|
||||
typedef uint64_t UINT64_t;
|
||||
typedef int32_t INT_t;
|
||||
typedef double FLOAT_t;
|
||||
typedef char * STRING_t;
|
||||
|
||||
///////////////////////////////
|
||||
|
||||
struct HapChar {
|
||||
const char *type;
|
||||
const char *hapName;
|
||||
|
|
@ -68,6 +79,7 @@ struct HapChar {
|
|||
struct HapCharacteristics {
|
||||
|
||||
HAPCHAR( Active, B0, PW+PR+EV, UINT8, true );
|
||||
HAPCHAR( ActiveIdentifier, E7, PW+PR+EV, UINT32, true );
|
||||
HAPCHAR( AirQuality, 95, PR+EV, UINT8, true );
|
||||
HAPCHAR( BatteryLevel, 68, PR+EV, UINT8, false );
|
||||
HAPCHAR( Brightness, 8, PR+PW+EV, INT, false );
|
||||
|
|
@ -78,8 +90,11 @@ struct HapCharacteristics {
|
|||
HAPCHAR( CarbonDioxidePeakLevel, 94, PR+EV, FLOAT, false );
|
||||
HAPCHAR( CarbonMonoxideDetected, 69, PR+EV, UINT8, true );
|
||||
HAPCHAR( ChargingState, 8F, PR+EV, UINT8, true );
|
||||
HAPCHAR( ClosedCaptions, DD, PW+PR+EV, UINT8, true );
|
||||
HAPCHAR( CoolingThresholdTemperature, D, PR+PW+EV, FLOAT, false );
|
||||
HAPCHAR( ColorTemperature, CE, PR+PW+EV, UINT32, false );
|
||||
HAPCHAR( ConfiguredName, E3, PW+PR+EV, STRING, false );
|
||||
HAPCHAR( ConfiguredNameStatic, E3, PR+EV, STRING, false );
|
||||
HAPCHAR( ContactSensorState, 6A, PR+EV, UINT8, true );
|
||||
HAPCHAR( CurrentAmbientLightLevel, 6B, PR+EV, FLOAT, false );
|
||||
HAPCHAR( CurrentHorizontalTiltAngle, 6C, PR+EV, INT, false );
|
||||
|
|
@ -92,9 +107,11 @@ struct HapCharacteristics {
|
|||
HAPCHAR( CurrentFanState, AF, PR+EV, UINT8, true );
|
||||
HAPCHAR( CurrentHeatingCoolingState, F, PR+EV, UINT8, true );
|
||||
HAPCHAR( CurrentHeaterCoolerState, B1, PR+EV, UINT8, true );
|
||||
HAPCHAR( CurrentMediaState, E0, PR+EV, UINT8, true );
|
||||
HAPCHAR( CurrentRelativeHumidity, 10, PR+EV, FLOAT, false );
|
||||
HAPCHAR( CurrentTemperature, 11, PR+EV, FLOAT, false );
|
||||
HAPCHAR( CurrentTiltAngle, C1, PR+EV, INT, false );
|
||||
HAPCHAR( CurrentVisibilityState, 135, PR+EV, UINT8, true );
|
||||
HAPCHAR( FilterLifeLevel, AB, PR+EV, FLOAT, false );
|
||||
HAPCHAR( FilterChangeIndication, AC, PR+EV, UINT8, true );
|
||||
HAPCHAR( FirmwareRevision, 52, PR, STRING, true );
|
||||
|
|
@ -103,6 +120,9 @@ struct HapCharacteristics {
|
|||
HAPCHAR( HoldPosition, 6F, PW, BOOL, true );
|
||||
HAPCHAR( Hue, 13, PR+PW+EV, FLOAT, false );
|
||||
HAPCHAR( Identify, 14, PW, BOOL, true );
|
||||
HAPCHAR( Identifier, E6, PR, UINT32, true );
|
||||
HAPCHAR( InputDeviceType, DC, PR+EV, UINT8, true );
|
||||
HAPCHAR( InputSourceType, DB, PR+EV, UINT8, true );
|
||||
HAPCHAR( InUse, D2, PR+EV, UINT8, true );
|
||||
HAPCHAR( IsConfigured, D6, PR+EV, UINT8, true );
|
||||
HAPCHAR( LeakDetected, 70, PR+EV, UINT8, true );
|
||||
|
|
@ -122,12 +142,15 @@ struct HapCharacteristics {
|
|||
HAPCHAR( On, 25, PR+PW+EV, BOOL, true );
|
||||
HAPCHAR( OzoneDensity, C3, PR+EV, FLOAT, false );
|
||||
HAPCHAR( PM10Density, C7, PR+EV, FLOAT, false );
|
||||
HAPCHAR( PictureMode, E2, PW+PR+EV, UINT8, true );
|
||||
HAPCHAR( PositionState, 72, PR+EV, UINT8, true );
|
||||
HAPCHAR( PowerModeSelection, DF, PW, UINT8, true );
|
||||
HAPCHAR( ProgramMode, D1, PR+EV, UINT8, true );
|
||||
HAPCHAR( ProgrammableSwitchEvent, 73, PR+EV+NV, UINT8, true );
|
||||
HAPCHAR( RelativeHumidityDehumidifierThreshold, C9, PR+PW+EV, FLOAT, false );
|
||||
HAPCHAR( RelativeHumidityHumidifierThreshold, CA, PR+PW+EV, FLOAT, false );
|
||||
HAPCHAR( RemainingDuration, D4, PR+EV, UINT32, false );
|
||||
HAPCHAR( RemoteKey, E1, PW, UINT8, true );
|
||||
HAPCHAR( ResetFilterIndication, AD, PW, UINT8, true );
|
||||
HAPCHAR( RotationDirection, 28, PR+PW+EV, INT, true );
|
||||
HAPCHAR( RotationSpeed, 29, PR+PW+EV, FLOAT, false );
|
||||
|
|
@ -139,6 +162,7 @@ struct HapCharacteristics {
|
|||
HAPCHAR( ServiceLabelIndex, CB, PR, UINT8, true );
|
||||
HAPCHAR( ServiceLabelNamespace, CD, PR, UINT8, true );
|
||||
HAPCHAR( SlatType, C0, PR, UINT8, true );
|
||||
HAPCHAR( SleepDiscoveryMode, E8, PR+EV, UINT8, true );
|
||||
HAPCHAR( SmokeDetected, 76, PR+EV, UINT8, true );
|
||||
HAPCHAR( StatusActive, 75, PR+EV, BOOL, true );
|
||||
HAPCHAR( StatusFault, 77, PR+EV, UINT8, true );
|
||||
|
|
@ -157,15 +181,20 @@ struct HapCharacteristics {
|
|||
HAPCHAR( TargetPosition, 7C, PW+PR+EV, UINT8, false );
|
||||
HAPCHAR( TargetDoorState, 32, PW+PR+EV, UINT8, true );
|
||||
HAPCHAR( TargetHeatingCoolingState, 33, PW+PR+EV, UINT8, true );
|
||||
HAPCHAR( TargetMediaState, 137, PW+PR+EV, UINT8, true );
|
||||
HAPCHAR( TargetRelativeHumidity, 34, PW+PR+EV, FLOAT, false );
|
||||
HAPCHAR( TargetTemperature, 35, PW+PR+EV, FLOAT, false );
|
||||
HAPCHAR( TargetVisibilityState, 134, PW+PR+EV, UINT8, true );
|
||||
HAPCHAR( TemperatureDisplayUnits, 36, PW+PR+EV, UINT8, true );
|
||||
HAPCHAR( TargetVerticalTiltAngle, 7D, PW+PR+EV, INT, false );
|
||||
HAPCHAR( ValveType, D5, PR+EV, UINT8, true );
|
||||
HAPCHAR( Version, 37, PR, STRING, true );
|
||||
HAPCHAR( VOCDensity, C8, PR+EV, FLOAT, false );
|
||||
HAPCHAR( Volume, 119, PW+PR+EV, UINT8, false );
|
||||
HAPCHAR( VolumeControlType, E9, PR+EV, UINT8, true );
|
||||
HAPCHAR( VolumeSelector, EA, PR+EV, UINT8, true );
|
||||
HAPCHAR( WaterLevel, B5, PR+EV, FLOAT, false );
|
||||
|
||||
};
|
||||
|
||||
extern HapCharacteristics hapChars;
|
||||
|
|
|
|||
|
|
@ -157,6 +157,8 @@ void Span::poll() {
|
|||
homeSpan.Accessories.back()->validate();
|
||||
}
|
||||
|
||||
checkRanges();
|
||||
|
||||
if(nWarnings>0){
|
||||
configLog+="\n*** CAUTION: There " + String((nWarnings>1?"are ":"is ")) + String(nWarnings) + " WARNING" + (nWarnings>1?"S":"") + " associated with this configuration that may lead to the device becoming non-responsive, or operating in an unexpected manner. ***\n";
|
||||
}
|
||||
|
|
@ -1137,7 +1139,7 @@ int Span::updateCharacteristics(char *buf, SpanBuf *pObj){
|
|||
pObj[nObj].iid=atoi(t3);
|
||||
okay|=2;
|
||||
} else
|
||||
if(!strcmp(t2,"value") && (t3=strtok_r(t1,"}[]:, \"\t\n\r",&p2))){
|
||||
if(!strcmp(t2,"value") && (t3=strtok_r(t1,"}[]:,\"",&p2))){
|
||||
pObj[nObj].val=t3;
|
||||
okay|=4;
|
||||
} else
|
||||
|
|
@ -1206,14 +1208,17 @@ int Span::updateCharacteristics(char *buf, SpanBuf *pObj){
|
|||
LOG1(" iid=");
|
||||
LOG1(pObj[j].characteristic->iid);
|
||||
if(status==StatusCode::OK){ // if status is okay
|
||||
pObj[j].characteristic->value=pObj[j].characteristic->newValue; // update characteristic value with new value
|
||||
pObj[j].characteristic->uvSet(pObj[j].characteristic->value,pObj[j].characteristic->newValue); // update characteristic value with new value
|
||||
if(pObj[j].characteristic->nvsKey){ // if storage key found
|
||||
if(pObj[j].characteristic->format != FORMAT::STRING)
|
||||
nvs_set_blob(charNVS,pObj[j].characteristic->nvsKey,&(pObj[j].characteristic->value),sizeof(pObj[j].characteristic->value)); // store data
|
||||
else
|
||||
nvs_set_str(charNVS,pObj[j].characteristic->nvsKey,pObj[j].characteristic->value.STRING); // store data
|
||||
nvs_commit(charNVS);
|
||||
}
|
||||
LOG1(" (okay)\n");
|
||||
} else { // if status not okay
|
||||
pObj[j].characteristic->newValue=pObj[j].characteristic->value; // replace characteristic new value with original value
|
||||
pObj[j].characteristic->uvSet(pObj[j].characteristic->newValue,pObj[j].characteristic->value); // replace characteristic new value with original value
|
||||
LOG1(" (failed)\n");
|
||||
}
|
||||
pObj[j].characteristic->isUpdated=false; // reset isUpdated flag for characteristic
|
||||
|
|
@ -1344,6 +1349,37 @@ int Span::sprintfAttributes(char **ids, int numIDs, int flags, char *cBuf){
|
|||
return(nChars);
|
||||
}
|
||||
|
||||
///////////////////////////////
|
||||
|
||||
void Span::checkRanges(){
|
||||
|
||||
boolean okay=true;
|
||||
homeSpan.configLog+="\nRange Check:";
|
||||
|
||||
for(int i=0;i<Accessories.size();i++){
|
||||
for(int j=0;j<Accessories[i]->Services.size();j++){
|
||||
for(int k=0;k<Accessories[i]->Services[j]->Characteristics.size();k++){
|
||||
SpanCharacteristic *chr=Accessories[i]->Services[j]->Characteristics[k];
|
||||
|
||||
if(chr->format!=STRING && (chr->uvGet<double>(chr->value) < chr->uvGet<double>(chr->minValue) || chr->uvGet<double>(chr->value) > chr->uvGet<double>(chr->maxValue))){
|
||||
char c[256];
|
||||
sprintf(c,"\n \u2718 Characteristic %s with AID=%d, IID=%d: Initial value of %lg is out of range [%llg,%llg]",
|
||||
chr->hapName,chr->aid,chr->iid,chr->uvGet<double>(chr->value),chr->uvGet<double>(chr->minValue),chr->uvGet<double>(chr->maxValue));
|
||||
if(okay)
|
||||
homeSpan.configLog+="\n";
|
||||
homeSpan.configLog+=c;
|
||||
homeSpan.nWarnings++;
|
||||
okay=false;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if(okay)
|
||||
homeSpan.configLog+=" No Warnings";
|
||||
homeSpan.configLog+="\n\n";
|
||||
}
|
||||
|
||||
///////////////////////////////
|
||||
// SpanAccessory //
|
||||
///////////////////////////////
|
||||
|
|
@ -1408,18 +1444,6 @@ void SpanAccessory::validate(){
|
|||
foundProtocol=true;
|
||||
else if(aid==1) // this is an Accessory with aid=1, but it has more than just AccessoryInfo and HAPProtocolInformation. So...
|
||||
homeSpan.isBridge=false; // ...this is not a bridge device
|
||||
|
||||
for(int j=0;j<Services[i]->Characteristics.size();j++){ // check that initial values are all in range of mix/max (which may have been modified by setRange)
|
||||
SpanCharacteristic *chr=Services[i]->Characteristics[j];
|
||||
|
||||
if(chr->format!=STRING && (chr->uvGet<double>(chr->value) < chr->uvGet<double>(chr->minValue) || chr->uvGet<double>(chr->value) > chr->uvGet<double>(chr->maxValue))){
|
||||
char c[256];
|
||||
sprintf(c," \u2718 Characteristic %s with IID=%d *** WARNING: Initial value of %lg is out of range [%llg,%llg]. ***\n",
|
||||
chr->hapName,chr->iid,chr->uvGet<double>(chr->value),chr->uvGet<double>(chr->minValue),chr->uvGet<double>(chr->maxValue));
|
||||
homeSpan.configLog+=c;
|
||||
homeSpan.nWarnings++;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if(!foundInfo){
|
||||
|
|
@ -1476,7 +1500,7 @@ SpanService::SpanService(const char *type, const char *hapName){
|
|||
homeSpan.Accessories.back()->Services.push_back(this);
|
||||
iid=++(homeSpan.Accessories.back()->iidCount);
|
||||
|
||||
homeSpan.configLog+=": IID=" + String(iid) + ", UUID=0x" + String(type);
|
||||
homeSpan.configLog+=": IID=" + String(iid) + ", UUID=\"" + String(type) + "\"";
|
||||
|
||||
if(!strcmp(this->type,"3E") && iid!=1){
|
||||
homeSpan.configLog+=" *** ERROR! The AccessoryInformation Service must be defined before any other Services in an Accessory. ***";
|
||||
|
|
@ -1727,6 +1751,10 @@ StatusCode SpanCharacteristic::loadUpdate(char *val, char *ev){
|
|||
return(StatusCode::InvalidValue);
|
||||
break;
|
||||
|
||||
case STRING:
|
||||
uvSet(newValue,(const char *)val);
|
||||
break;
|
||||
|
||||
default:
|
||||
break;
|
||||
|
||||
|
|
|
|||
124
src/HomeSpan.h
124
src/HomeSpan.h
|
|
@ -156,6 +156,7 @@ struct Span{
|
|||
void checkConnect(); // check WiFi connection; connect if needed
|
||||
void commandMode(); // allows user to control and reset HomeSpan settings with the control button
|
||||
void processSerialCommand(const char *c); // process command 'c' (typically from readSerial, though can be called with any 'c')
|
||||
void checkRanges(); // checks values of all Characteristics to ensure they are each within range
|
||||
|
||||
int sprintfAttributes(char *cBuf); // prints Attributes JSON database into buf, unless buf=NULL; return number of characters printed, excluding null terminator, even if buf=NULL
|
||||
void prettyPrint(char *buf, int nsp=2); // print arbitrary JSON from buf to serial monitor, formatted with indentions of 'nsp' spaces
|
||||
|
|
@ -239,14 +240,14 @@ struct SpanCharacteristic{
|
|||
|
||||
|
||||
union UVal {
|
||||
boolean BOOL;
|
||||
uint8_t UINT8;
|
||||
uint16_t UINT16;
|
||||
uint32_t UINT32;
|
||||
uint64_t UINT64;
|
||||
int32_t INT;
|
||||
double FLOAT;
|
||||
const char *STRING;
|
||||
BOOL_t BOOL;
|
||||
UINT8_t UINT8;
|
||||
UINT16_t UINT16;
|
||||
UINT32_t UINT32;
|
||||
UINT64_t UINT64;
|
||||
INT_t INT;
|
||||
FLOAT_t FLOAT;
|
||||
STRING_t STRING = NULL;
|
||||
};
|
||||
|
||||
int iid=0; // Instance ID (HAP Table 6-3)
|
||||
|
|
@ -307,8 +308,16 @@ struct SpanCharacteristic{
|
|||
return(String()); // included to prevent compiler warnings
|
||||
}
|
||||
|
||||
void uvSet(UVal &dest, UVal &src){
|
||||
if(format==FORMAT::STRING)
|
||||
uvSet(dest,(const char *)src.STRING);
|
||||
else
|
||||
dest=src;
|
||||
}
|
||||
|
||||
void uvSet(UVal &u, const char *val){
|
||||
u.STRING=val;
|
||||
u.STRING = (char *)realloc(u.STRING, strlen(val) + 1);
|
||||
strcpy(u.STRING, val);
|
||||
}
|
||||
|
||||
template <typename T> void uvSet(UVal &u, T val){
|
||||
|
|
@ -354,9 +363,6 @@ struct SpanCharacteristic{
|
|||
return((T) u.UINT64);
|
||||
case FORMAT::FLOAT:
|
||||
return((T) u.FLOAT);
|
||||
case FORMAT::STRING:
|
||||
Serial.print("\n*** WARNING: Can't use getVal() or getNewVal() with string Characteristics.\n\n");
|
||||
return(0);
|
||||
}
|
||||
return(0); // included to prevent compiler warnings
|
||||
}
|
||||
|
|
@ -364,7 +370,7 @@ struct SpanCharacteristic{
|
|||
template <typename A, typename B, typename S=int> SpanCharacteristic *setRange(A min, B max, S step=0){
|
||||
|
||||
char c[256];
|
||||
homeSpan.configLog+=String(" \u2b0c Set Range for ") + String(hapName) + " with IID=" + String(iid);
|
||||
homeSpan.configLog+=String(" \u2b0c Set Range for ") + String(hapName) + " with AID=" + String(aid) + ", IID=" + String(iid);
|
||||
|
||||
if(customRange){
|
||||
sprintf(c," *** ERROR! Range already set for this Characteristic! ***\n");
|
||||
|
|
@ -394,12 +400,7 @@ struct SpanCharacteristic{
|
|||
template <typename T, typename A=boolean, typename B=boolean> void init(T val, boolean nvsStore, A min=0, B max=1){
|
||||
|
||||
int nvsFlag=0;
|
||||
|
||||
uvSet(value,val);
|
||||
uvSet(newValue,val);
|
||||
uvSet(minValue,min);
|
||||
uvSet(maxValue,max);
|
||||
uvSet(stepValue,0);
|
||||
|
||||
if(nvsStore){
|
||||
nvsKey=(char *)malloc(16);
|
||||
|
|
@ -408,9 +409,9 @@ struct SpanCharacteristic{
|
|||
sprintf(nvsKey,"%04X%08X%03X",t,aid,iid&0xFFF);
|
||||
size_t len;
|
||||
|
||||
if(format != FORMAT::STRING){
|
||||
if(!nvs_get_blob(homeSpan.charNVS,nvsKey,NULL,&len)){
|
||||
nvs_get_blob(homeSpan.charNVS,nvsKey,&value,&len);
|
||||
newValue=value;
|
||||
nvsFlag=2;
|
||||
}
|
||||
else {
|
||||
|
|
@ -418,18 +419,44 @@ struct SpanCharacteristic{
|
|||
nvs_commit(homeSpan.charNVS); // commit to NVS
|
||||
nvsFlag=1;
|
||||
}
|
||||
} else {
|
||||
if(!nvs_get_str(homeSpan.charNVS,nvsKey,NULL,&len)){
|
||||
char c[len];
|
||||
nvs_get_str(homeSpan.charNVS,nvsKey,c,&len);
|
||||
uvSet(value,(const char *)c);
|
||||
nvsFlag=2;
|
||||
}
|
||||
else {
|
||||
nvs_set_str(homeSpan.charNVS,nvsKey,value.STRING); // store string data
|
||||
nvs_commit(homeSpan.charNVS); // commit to NVS
|
||||
nvsFlag=1;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
homeSpan.configLog+="(" + uvPrint(value) + ")" + ": IID=" + String(iid) + ", UUID=0x" + String(type);
|
||||
if(format!=STRING && format!=BOOL)
|
||||
homeSpan.configLog+= " Range=[" + String(uvPrint(minValue)) + "," + String(uvPrint(maxValue)) + "]";
|
||||
uvSet(newValue,value);
|
||||
|
||||
if(format != FORMAT::STRING) {
|
||||
uvSet(minValue,min);
|
||||
uvSet(maxValue,max);
|
||||
uvSet(stepValue,0);
|
||||
}
|
||||
|
||||
int x=0;
|
||||
sscanf(type,"%*8[0-9a-fA-F]-%*4[0-9a-fA-F]-%*4[0-9a-fA-F]-%*4[0-9a-fA-F]-%*12[0-9a-fA-F]%n",&x);
|
||||
|
||||
boolean isCustom=(strlen(type)==36 && x==36);
|
||||
|
||||
homeSpan.configLog+="(" + uvPrint(value) + ")" + ": IID=" + String(iid) + ", " + (isCustom?"Custom-":"") + "UUID=\"" + String(type) + "\"";
|
||||
if(format!=FORMAT::STRING && format!=FORMAT::BOOL)
|
||||
homeSpan.configLog+= ", Range=[" + String(uvPrint(minValue)) + "," + String(uvPrint(maxValue)) + "]";
|
||||
|
||||
if(nvsFlag==2)
|
||||
homeSpan.configLog+=" (restored)";
|
||||
else if(nvsFlag==1)
|
||||
homeSpan.configLog+=" (storing)";
|
||||
|
||||
boolean valid=false;
|
||||
boolean valid=isCustom;
|
||||
|
||||
for(int i=0; !valid && i<homeSpan.Accessories.back()->Services.back()->req.size(); i++)
|
||||
valid=!strcmp(type,homeSpan.Accessories.back()->Services.back()->req[i]->type);
|
||||
|
|
@ -438,8 +465,8 @@ struct SpanCharacteristic{
|
|||
valid=!strcmp(type,homeSpan.Accessories.back()->Services.back()->opt[i]->type);
|
||||
|
||||
if(!valid){
|
||||
homeSpan.configLog+=" *** ERROR! Service does not support this Characteristic. ***";
|
||||
homeSpan.nFatalErrors++;
|
||||
homeSpan.configLog+=" *** WARNING! Service does not support this Characteristic. ***";
|
||||
homeSpan.nWarnings++;
|
||||
}
|
||||
|
||||
boolean repeated=false;
|
||||
|
|
@ -458,6 +485,7 @@ struct SpanCharacteristic{
|
|||
|
||||
} // init()
|
||||
|
||||
|
||||
template <class T=int> T getVal(){
|
||||
return(uvGet<T>(value));
|
||||
}
|
||||
|
|
@ -466,10 +494,50 @@ struct SpanCharacteristic{
|
|||
return(uvGet<T>(newValue));
|
||||
}
|
||||
|
||||
char *getString(){
|
||||
if(format == FORMAT::STRING)
|
||||
return value.STRING;
|
||||
|
||||
return NULL;
|
||||
}
|
||||
|
||||
char *getNewString(){
|
||||
if(format == FORMAT::STRING)
|
||||
return newValue.STRING;
|
||||
|
||||
return NULL;
|
||||
}
|
||||
|
||||
void setString(const char *val){
|
||||
|
||||
if((perms & EV) == 0){
|
||||
Serial.printf("\n*** WARNING: Attempt to update Characteristic::%s with setVal() ignored. No NOTIFICATION permission on this characteristic\n\n",hapName);
|
||||
return;
|
||||
}
|
||||
|
||||
uvSet(value,val);
|
||||
uvSet(newValue,value);
|
||||
|
||||
updateTime=homeSpan.snapTime;
|
||||
|
||||
SpanBuf sb; // create SpanBuf object
|
||||
sb.characteristic=this; // set characteristic
|
||||
sb.status=StatusCode::OK; // set status
|
||||
char dummy[]="";
|
||||
sb.val=dummy; // set dummy "val" so that sprintfNotify knows to consider this "update"
|
||||
homeSpan.Notifications.push_back(sb); // store SpanBuf in Notifications vector
|
||||
|
||||
if(nvsKey){
|
||||
nvs_set_str(homeSpan.charNVS,nvsKey,value.STRING); // store data
|
||||
nvs_commit(homeSpan.charNVS);
|
||||
}
|
||||
|
||||
} // setString()
|
||||
|
||||
template <typename T> void setVal(T val){
|
||||
|
||||
if(format==STRING){
|
||||
Serial.printf("\n*** WARNING: Attempt to update Characteristic::%s(\"%s\") with setVal() ignored. Can't update STRING Characteristics once they are initialized!\n\n",hapName,value.STRING);
|
||||
if((perms & EV) == 0){
|
||||
Serial.printf("\n*** WARNING: Attempt to update Characteristic::%s with setVal() ignored. No NOTIFICATION permission on this characteristic\n\n",hapName);
|
||||
return;
|
||||
}
|
||||
|
||||
|
|
@ -479,7 +547,7 @@ struct SpanCharacteristic{
|
|||
}
|
||||
|
||||
uvSet(value,val);
|
||||
uvSet(newValue,val);
|
||||
uvSet(newValue,value);
|
||||
|
||||
updateTime=homeSpan.snapTime;
|
||||
|
||||
|
|
|
|||
|
|
@ -36,7 +36,7 @@
|
|||
|
||||
#define HS_MAJOR 1
|
||||
#define HS_MINOR 4
|
||||
#define HS_PATCH 0
|
||||
#define HS_PATCH 1
|
||||
|
||||
#define STRINGIFY(x) _STR(x)
|
||||
#define _STR(x) #x
|
||||
|
|
@ -130,5 +130,6 @@ enum class Category {
|
|||
Dehumidifiers=23,
|
||||
Sprinklers=28,
|
||||
Faucets=29,
|
||||
ShowerSystems=30
|
||||
ShowerSystems=30,
|
||||
Television=31
|
||||
};
|
||||
|
|
|
|||
44
src/Span.h
44
src/Span.h
|
|
@ -199,12 +199,19 @@ namespace Service {
|
|||
OPT(StatusLowBattery);
|
||||
}};
|
||||
|
||||
struct InputSource : SpanService { InputSource() : SpanService{"D9","InputSource"}{
|
||||
OPT(ConfiguredName);
|
||||
OPT(IsConfigured);
|
||||
REQ(Identifier);
|
||||
OPT(CurrentVisibilityState);
|
||||
OPT(TargetVisibilityState);
|
||||
}};
|
||||
|
||||
struct IrrigationSystem : SpanService { IrrigationSystem() : SpanService{"CF","IrrigationSystem"}{
|
||||
REQ(Active);
|
||||
REQ(ProgramMode);
|
||||
REQ(InUse);
|
||||
OPT(RemainingDuration);
|
||||
OPT(Name);
|
||||
OPT(StatusFault);
|
||||
}};
|
||||
|
||||
|
|
@ -319,6 +326,14 @@ namespace Service {
|
|||
OPT(Name);
|
||||
}};
|
||||
|
||||
struct Television : SpanService { Television() : SpanService{"D8","Television"}{
|
||||
REQ(Active);
|
||||
OPT(ConfiguredName);
|
||||
OPT(ActiveIdentifier);
|
||||
OPT(RemoteKey);
|
||||
OPT(PowerModeSelection);
|
||||
}};
|
||||
|
||||
struct TemperatureSensor : SpanService { TemperatureSensor() : SpanService{"8A","TemperatureSensor"}{
|
||||
REQ(CurrentTemperature);
|
||||
OPT(Name);
|
||||
|
|
@ -389,6 +404,7 @@ namespace Service {
|
|||
namespace Characteristic {
|
||||
|
||||
CREATE_CHAR(uint8_t,Active,0,0,1);
|
||||
CREATE_CHAR(uint32_t,ActiveIdentifier,0,0,255);
|
||||
CREATE_CHAR(uint8_t,AirQuality,0,0,5);
|
||||
CREATE_CHAR(uint8_t,BatteryLevel,0,0,100);
|
||||
CREATE_CHAR(int,Brightness,0,0,100);
|
||||
|
|
@ -399,20 +415,25 @@ namespace Characteristic {
|
|||
CREATE_CHAR(double,CarbonDioxidePeakLevel,0,0,100000);
|
||||
CREATE_CHAR(uint8_t,CarbonDioxideDetected,0,0,1);
|
||||
CREATE_CHAR(uint8_t,ChargingState,0,0,2);
|
||||
CREATE_CHAR(uint8_t,ClosedCaptions,0,0,1);
|
||||
CREATE_CHAR(double,CoolingThresholdTemperature,10,10,35);
|
||||
CREATE_CHAR(uint32_t,ColorTemperature,200,140,500);
|
||||
CREATE_CHAR(uint8_t,ContactSensorState,1,0,1);
|
||||
CREATE_CHAR(const char *,ConfiguredName,"unnamed",0,1);
|
||||
CREATE_CHAR(const char *,ConfiguredNameStatic,"unnamed",0,1);
|
||||
CREATE_CHAR(double,CurrentAmbientLightLevel,1,0.0001,100000);
|
||||
CREATE_CHAR(int,CurrentHorizontalTiltAngle,0,-90,90);
|
||||
CREATE_CHAR(uint8_t,CurrentAirPurifierState,1,0,2);
|
||||
CREATE_CHAR(uint8_t,CurrentSlatState,0,0,2);
|
||||
CREATE_CHAR(uint8_t,CurrentPosition,0,0,100);
|
||||
CREATE_CHAR(int,CurrentVerticalTiltAngle,0,-90,90);
|
||||
CREATE_CHAR(uint8_t,CurrentVisibilityState,0,0,1);
|
||||
CREATE_CHAR(uint8_t,CurrentHumidifierDehumidifierState,1,0,3);
|
||||
CREATE_CHAR(uint8_t,CurrentDoorState,1,0,4);
|
||||
CREATE_CHAR(uint8_t,CurrentFanState,1,0,2);
|
||||
CREATE_CHAR(uint8_t,CurrentHeatingCoolingState,0,0,2);
|
||||
CREATE_CHAR(uint8_t,CurrentHeaterCoolerState,1,0,3);
|
||||
CREATE_CHAR(uint8_t,CurrentMediaState,0,0,5);
|
||||
CREATE_CHAR(double,CurrentRelativeHumidity,0,0,100);
|
||||
CREATE_CHAR(double,CurrentTemperature,0,0,100);
|
||||
CREATE_CHAR(int,CurrentTiltAngle,0,-90,90);
|
||||
|
|
@ -424,6 +445,9 @@ namespace Characteristic {
|
|||
CREATE_CHAR(boolean,HoldPosition,false,0,1);
|
||||
CREATE_CHAR(double,Hue,0,0,360);
|
||||
CREATE_CHAR(boolean,Identify,false,0,1);
|
||||
CREATE_CHAR(uint32_t,Identifier,0,0,255);
|
||||
CREATE_CHAR(uint8_t,InputDeviceType,0,0,6);
|
||||
CREATE_CHAR(uint8_t,InputSourceType,0,0,10);
|
||||
CREATE_CHAR(uint8_t,InUse,0,0,1);
|
||||
CREATE_CHAR(uint8_t,IsConfigured,0,0,1);
|
||||
CREATE_CHAR(uint8_t,LeakDetected,0,0,1);
|
||||
|
|
@ -442,13 +466,16 @@ namespace Characteristic {
|
|||
CREATE_CHAR(boolean,OutletInUse,false,0,1);
|
||||
CREATE_CHAR(boolean,On,false,0,1);
|
||||
CREATE_CHAR(double,OzoneDensity,0,0,1000);
|
||||
CREATE_CHAR(uint8_t,PictureMode,0,0,13);
|
||||
CREATE_CHAR(double,PM10Density,0,0,1000);
|
||||
CREATE_CHAR(uint8_t,PositionState,2,0,2);
|
||||
CREATE_CHAR(uint8_t,PowerModeSelection,0,0,1);
|
||||
CREATE_CHAR(uint8_t,ProgramMode,0,0,2);
|
||||
CREATE_CHAR(uint8_t,ProgrammableSwitchEvent,0,0,2);
|
||||
CREATE_CHAR(double,RelativeHumidityDehumidifierThreshold,50,0,100);
|
||||
CREATE_CHAR(double,RelativeHumidityHumidifierThreshold,50,0,100);
|
||||
CREATE_CHAR(uint32_t,RemainingDuration,60,0,3600);
|
||||
CREATE_CHAR(uint8_t,RemoteKey,0,0,16);
|
||||
CREATE_CHAR(uint8_t,ResetFilterIndication,0,1,1);
|
||||
CREATE_CHAR(int,RotationDirection,0,0,1);
|
||||
CREATE_CHAR(double,RotationSpeed,0,0,100);
|
||||
|
|
@ -460,6 +487,7 @@ namespace Characteristic {
|
|||
CREATE_CHAR(uint8_t,ServiceLabelIndex,1,1,255);
|
||||
CREATE_CHAR(uint8_t,ServiceLabelNamespace,1,0,1);
|
||||
CREATE_CHAR(uint8_t,SlatType,0,0,1);
|
||||
CREATE_CHAR(uint8_t,SleepDiscoveryMode,0,0,1);
|
||||
CREATE_CHAR(uint8_t,SmokeDetected,0,0,1);
|
||||
CREATE_CHAR(boolean,StatusActive,true,0,1);
|
||||
CREATE_CHAR(uint8_t,StatusFault,0,0,1);
|
||||
|
|
@ -478,14 +506,28 @@ namespace Characteristic {
|
|||
CREATE_CHAR(uint8_t,TargetPosition,0,0,100);
|
||||
CREATE_CHAR(uint8_t,TargetDoorState,1,0,1);
|
||||
CREATE_CHAR(uint8_t,TargetHeatingCoolingState,0,0,3);
|
||||
CREATE_CHAR(uint8_t,TargetMediaState,0,0,2);
|
||||
CREATE_CHAR(double,TargetRelativeHumidity,0,0,100);
|
||||
CREATE_CHAR(double,TargetTemperature,16,10,38);
|
||||
CREATE_CHAR(uint8_t,TargetVisibilityState,0,0,1);
|
||||
CREATE_CHAR(uint8_t,TemperatureDisplayUnits,0,0,1);
|
||||
CREATE_CHAR(int,TargetVerticalTiltAngle,0,-90,90);
|
||||
CREATE_CHAR(uint8_t,ValveType,0,0,3);
|
||||
CREATE_CHAR(const char *,Version,"1.0.0",0,1);
|
||||
CREATE_CHAR(double,VOCDensity,0,0,1000);
|
||||
CREATE_CHAR(uint8_t,Volume,0,0,100);
|
||||
CREATE_CHAR(uint8_t,VolumeControlType,0,0,3);
|
||||
CREATE_CHAR(uint8_t,VolumeSelector,0,0,1);
|
||||
CREATE_CHAR(double,WaterLevel,0,0,100);
|
||||
|
||||
}
|
||||
|
||||
//////////////////////////////////////////
|
||||
// MACRO TO ADD CUSTOM CHARACTERISTICS //
|
||||
//////////////////////////////////////////
|
||||
|
||||
#define CUSTOM_CHAR(NAME,UUID,PERMISISONS,FORMAT,DEFVAL,MINVAL,MAXVAL,STATIC_RANGE) \
|
||||
HapChar _CUSTOM_##NAME {#UUID,#NAME,(PERMS)(PERMISISONS),FORMAT,STATIC_RANGE}; \
|
||||
namespace Characteristic { struct NAME : SpanCharacteristic { NAME(FORMAT##_t val=DEFVAL, boolean nvsStore=false) : SpanCharacteristic {&_CUSTOM_##NAME} { init(val,nvsStore,(FORMAT##_t)MINVAL,(FORMAT##_t)MAXVAL); } }; }
|
||||
|
||||
|
||||
|
|
|
|||
|
|
@ -97,12 +97,12 @@ PushButton::PushButton(int pin){
|
|||
|
||||
void PushButton::init(int pin){
|
||||
|
||||
this->pin=pin;
|
||||
if(pin<0)
|
||||
return;
|
||||
|
||||
status=0;
|
||||
doubleCheck=false;
|
||||
this->pin=pin;
|
||||
pinMode(pin, INPUT_PULLUP);
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -6,7 +6,7 @@
|
|||
|
||||
///////////////////
|
||||
|
||||
RFControl::RFControl(uint8_t pin){
|
||||
RFControl::RFControl(uint8_t pin, boolean refClock){
|
||||
|
||||
#ifdef CONFIG_IDF_TARGET_ESP32C3
|
||||
if(nChannels==RMT_CHANNEL_MAX/2){
|
||||
|
|
@ -34,10 +34,13 @@ RFControl::RFControl(uint8_t pin){
|
|||
rmt_config(config);
|
||||
rmt_driver_install(config->channel,0,0);
|
||||
|
||||
// Below we set the base clock to 1 MHz so tick-units are in microseconds (before CLK_DIV)
|
||||
// If specified, set the base clock to 1 MHz so tick-units are in microseconds (before any CLK_DIV is applied), otherwise default will be 80 MHz APB clock
|
||||
|
||||
this->refClock=refClock;
|
||||
|
||||
if(refClock)
|
||||
#ifdef CONFIG_IDF_TARGET_ESP32C3
|
||||
REG_SET_FIELD(RMT_SYS_CONF_REG,RMT_SCLK_DIV_NUM,80); // ESP32-C3 does not have a 1 MHz REF Tick Clock, but allows the 80 MHz APB clock to be scaled by an additional RMT-specific divider
|
||||
REG_SET_FIELD(RMT_SYS_CONF_REG,RMT_SCLK_DIV_NUM,79); // ESP32-C3 does not have a 1 MHz REF Tick Clock, but allows the 80 MHz APB clock to be scaled by an additional RMT-specific divider
|
||||
#else
|
||||
rmt_set_source_clk(config->channel,RMT_BASECLK_REF); // use 1 MHz REF Tick Clock for ESP32 and ESP32-S2
|
||||
#endif
|
||||
|
|
@ -69,11 +72,12 @@ void RFControl::start(uint32_t *data, int nData, uint8_t nCycles, uint8_t tickTi
|
|||
|
||||
void RFControl::clear(){
|
||||
data.clear();
|
||||
lowWord=true;
|
||||
}
|
||||
|
||||
///////////////////
|
||||
|
||||
void RFControl::add(uint16_t onTime, uint16_t offTime){
|
||||
void RFControl::add(uint32_t onTime, uint32_t offTime){
|
||||
|
||||
phase(onTime,HIGH);
|
||||
phase(offTime,LOW);
|
||||
|
|
@ -81,9 +85,11 @@ void RFControl::add(uint16_t onTime, uint16_t offTime){
|
|||
|
||||
///////////////////
|
||||
|
||||
void RFControl::phase(uint16_t nTicks, uint8_t phase){
|
||||
void RFControl::phase(uint32_t nTicks, uint8_t phase){
|
||||
|
||||
uint32_t ticks=nTicks&0x7FFF;
|
||||
while(nTicks>0){ // create as many repeated phases as needed to accomodate duration of nTicks
|
||||
uint32_t ticks=nTicks>0x7FFF?0x7FFF:nTicks;
|
||||
nTicks-=ticks;
|
||||
|
||||
if(lowWord)
|
||||
data.push_back(ticks | (phase?(1<<15):0));
|
||||
|
|
@ -91,6 +97,43 @@ void RFControl::phase(uint16_t nTicks, uint8_t phase){
|
|||
data.back()|=ticks<<16 | (phase?(1<<31):0);
|
||||
|
||||
lowWord=!lowWord;
|
||||
}
|
||||
}
|
||||
|
||||
///////////////////
|
||||
|
||||
void RFControl::enableCarrier(uint32_t freq, float duty){
|
||||
|
||||
if(duty<0)
|
||||
duty=0;
|
||||
if(duty>1)
|
||||
duty=1;
|
||||
|
||||
if(freq>0){
|
||||
float period=1.0e6/freq*(refClock?1:80);
|
||||
uint32_t highTime=period*duty+0.5;
|
||||
uint32_t lowTime=period*(1.0-duty)+0.5;
|
||||
|
||||
if(highTime>0xFFFF || lowTime>0xFFFF){
|
||||
Serial.printf("\n*** ERROR: Can't enable carrier frequency=%d Hz for RF Control pin=%d, duty=%0.2f. Frequency is too low!\n\n",freq,config->gpio_num,duty);
|
||||
return;
|
||||
}
|
||||
|
||||
if(highTime==0){
|
||||
Serial.printf("\n*** ERROR: Can't enable carrier frequency=%d Hz for RF Control pin=%d, duty=%0.2f. Duty is too low or frequency is too high!\n\n",freq,config->gpio_num,duty);
|
||||
return;
|
||||
}
|
||||
|
||||
if(lowTime==0){
|
||||
Serial.printf("\n*** ERROR: Can't enable carrier frequency=%d Hz for RF Control pin=%d, duty=%0.2f. Duty is too high or frequency is too high!\n\n",freq,config->gpio_num,duty);
|
||||
return;
|
||||
}
|
||||
|
||||
// Serial.printf("%d %g %d %d\n",freq,period,highTime,lowTime);
|
||||
rmt_set_tx_carrier(config->channel,true,highTime,lowTime,RMT_CARRIER_LEVEL_HIGH);
|
||||
} else {
|
||||
rmt_set_tx_carrier(config->channel,false,0,0,RMT_CARRIER_LEVEL_HIGH);
|
||||
}
|
||||
}
|
||||
|
||||
///////////////////
|
||||
|
|
|
|||
|
|
@ -13,17 +13,20 @@ class RFControl {
|
|||
rmt_config_t *config=NULL;
|
||||
vector<uint32_t> data;
|
||||
boolean lowWord=true;
|
||||
boolean refClock;
|
||||
static uint8_t nChannels;
|
||||
|
||||
public:
|
||||
RFControl(uint8_t pin); // creates transmitter on pin
|
||||
RFControl(uint8_t pin, boolean refClock=true); // creates transmitter on pin, using 1-MHz Ref Tick clock
|
||||
|
||||
void start(uint32_t *data, int nData, uint8_t nCycles=1, uint8_t tickTime=1); // starts transmission of pulses from specified data pointer, repeated for numCycles, where each tick in pulse is tickTime microseconds long
|
||||
void start(uint8_t nCycles=1, uint8_t tickTime=1); // starts transmission of pulses from internal data structure, repeated for numCycles, where each tick in pulse is tickTime microseconds long
|
||||
|
||||
void clear(); // clears transmitter memory
|
||||
void add(uint16_t onTime, uint16_t offTime); // adds pulse of onTime ticks HIGH followed by offTime ticks LOW
|
||||
void phase(uint16_t nTicks, uint8_t phase); // adds either a HIGH phase or LOW phase lasting numTicks ticks
|
||||
void add(uint32_t onTime, uint32_t offTime); // adds pulse of onTime ticks HIGH followed by offTime ticks LOW
|
||||
void phase(uint32_t nTicks, uint8_t phase); // adds either a HIGH phase or LOW phase lasting numTicks ticks
|
||||
void enableCarrier(uint32_t freq, float duty=0.5); // enables carrier wave if freq>0, else disables carrier wave; duty is a fraction from 0-1
|
||||
void disableCarrier(){enableCarrier(0);} // disables carrier wave
|
||||
};
|
||||
|
||||
// Helper macro for creating your own storage of uint32_t data array elements - used with first variation of start() above
|
||||
|
|
|
|||
|
|
@ -2,30 +2,44 @@
|
|||
|
||||
#include "RFControl.h" // include RF Control Library
|
||||
|
||||
#define PRONTO_N 0.241246
|
||||
|
||||
uint16_t pronto[]={0000,0x006D,0x0000,0x0022,0x00AC,0x00AC,0x0015,0x0040,0x0015,0x0040,0x0015,0x0040,0x0015,0x0015,0x0015,0x0015,0x0015,0x0015,0x0015,0x0015,0x0015,0x0015,0x0015,0x0040,0x0015,0x0040,0x0015,0x0040,0x0015,0x0015,0x0015,0x0015,0x0015,0x0015,0x0015,0x0015,0x0015,0x0015,0x0015,0x0040,0x0015,0x0015,0x0015,0x0015,0x0015,0x0040,0x0015,0x0040,0x0015,0x0015,0x0015,0x0015,0x0015,0x0040,0x0015,0x0015,0x0015,0x0040,0x0015,0x0040,0x0015,0x0015,0x0015,0x0015,0x0015,0x0040,0x0015,0x0040,0x0015,0x0015,0x0015,0x0689};
|
||||
//uint16_t pronto[]={0000,0x006D,0x0000,0x0022,0x00AC,0x00AC,0x0017,0x003E,0x0017,0x003E,0x0017,0x003E,0x0017,0x0013,0x0017,0x0013,0x0017,0x0013,0x0017,0x0013,0x0017,0x0013,0x0017,0x003E,0x0017,0x003E,0x0017,0x003E,0x0017,0x0013,0x0017,0x0013,0x0017,0x0013,0x0017,0x0013,0x0017,0x0013,0x0017,0x0013,0x0017,0x0013,0x0017,0x0013,0x0017,0x003E,0x0017,0x003E,0x0017,0x0013,0x0017,0x0013,0x0017,0x003E,0x0017,0x003E,0x0018,0x003E,0x0017,0x003E,0x0017,0x0013,0x0017,0x0013,0x0017,0x003E,0x0017,0x003E,0x0017,0x0013,0x0017,0x0746};
|
||||
//uint16_t pronto[]={0000,0x0067,0x0000,0x000d,0x0060,0x0018,0x0018,0x0018,0x0030,0x0018,0x0030,0x0018,0x0030,0x0018,0x0018,0x0018,0x0030,0x0018,0x0018,0x0018,0x0030,0x0018,0x0018,0x0018,0x0018,0x0018,0x0018,0x0018,0x0018,0x03f6};
|
||||
|
||||
uint32_t data[100];
|
||||
|
||||
void setup() {
|
||||
|
||||
Serial.begin(115200); // start the Serial interface
|
||||
Serial.flush();
|
||||
delay(1000); // wait for interface to flush
|
||||
|
||||
Serial.println("\n\nHomeSpan RF Transmitter Example");
|
||||
Serial.println("\n\nHomeSpan RF Transmitter Example\n\n");
|
||||
|
||||
RFControl rf(18); // create an instance of RFControl with signal output to pin 6
|
||||
RFControl rf(17);
|
||||
rf.enableCarrier(38000,0.5);
|
||||
|
||||
#define NPOINTS 3
|
||||
uint32_t code = 0xE0E019E6; // OFF
|
||||
// uint32_t code = 0xE0E09966; // ON
|
||||
|
||||
uint32_t data[NPOINTS];
|
||||
int unit=563;
|
||||
|
||||
for(int i=0;i<NPOINTS;i++){
|
||||
if(i<NPOINTS-1)
|
||||
data[i]=RF_PULSE(1000,9000);
|
||||
else
|
||||
data[i]=RF_PULSE(1000,30000);
|
||||
rf.add(4500,4500);
|
||||
|
||||
for(int i=31;i>=0;i--){
|
||||
rf.add(unit,unit*((code&(1<<i))?3:1));
|
||||
Serial.print((code&(1<<i))?1:0);
|
||||
if(!(i%8))
|
||||
Serial.print(" ");
|
||||
}
|
||||
|
||||
rf.start(data,NPOINTS,2,100);
|
||||
rf.add(unit,45000);
|
||||
|
||||
Serial.println("End Example");
|
||||
rf.start(2);
|
||||
|
||||
Serial.println("Done!");
|
||||
|
||||
} // end of setup()
|
||||
|
||||
|
|
|
|||
28
src/src.ino
28
src/src.ino
|
|
@ -4,6 +4,8 @@
|
|||
|
||||
#include "HomeSpan.h"
|
||||
|
||||
CUSTOM_CHAR(CustomActive, E863F10A-079E-48FF-8F27-9C2605A29F52, PR+EV, UINT16, 0, 0, 4800, false);
|
||||
|
||||
void setup() {
|
||||
|
||||
Serial.begin(115200);
|
||||
|
|
@ -43,13 +45,35 @@ void setup() {
|
|||
|
||||
new Service::LightBulb();
|
||||
new Characteristic::On(0);
|
||||
new Characteristic::Brightness();
|
||||
new Characteristic::CustomActive(1200);
|
||||
new Characteristic::Brightness(50);
|
||||
new Characteristic::Name("Light 1");
|
||||
new Characteristic::ColorTemperature();
|
||||
new Characteristic::Active();
|
||||
new Service::LightBulb();
|
||||
new Characteristic::On(0,true);
|
||||
(new Characteristic::Brightness(50,false))->setRange(10,100,5);
|
||||
new Characteristic::Name("Light 2");
|
||||
|
||||
new SpanAccessory(); // Begin by creating a new Accessory using SpanAccessory(), which takes no arguments
|
||||
|
||||
new Service::AccessoryInformation(); // HAP requires every Accessory to implement an AccessoryInformation Service, which has 6 required Characteristics
|
||||
new Characteristic::Name("HomeSpan Test"); // Name of the Accessory, which shows up on the HomeKit "tiles", and should be unique across Accessories
|
||||
new Characteristic::Manufacturer("HomeSpan"); // Manufacturer of the Accessory (arbitrary text string, and can be the same for every Accessory)
|
||||
new Characteristic::SerialNumber("HSL-123"); // Serial Number of the Accessory (arbitrary text string, and can be the same for every Accessory)
|
||||
new Characteristic::Model("HSL Test"); // Model of the Accessory (arbitrary text string, and can be the same for every Accessory)
|
||||
new Characteristic::FirmwareRevision(HOMESPAN_VERSION); // Firmware of the Accessory (arbitrary text string, and can be the same for every Accessory)
|
||||
new Characteristic::Identify(); // Create the required Identify
|
||||
|
||||
new Service::HAPProtocolInformation(); // Create the HAP Protcol Information Service
|
||||
new Characteristic::Version("1.1.0"); // Set the Version Characteristic to "1.1.0" as required by HAP
|
||||
|
||||
new Service::LightBulb();
|
||||
new Characteristic::On(0,true);
|
||||
(new Characteristic::Brightness(50,true))->setRange(10,100,5);
|
||||
new Characteristic::Name("Light 2");
|
||||
new Characteristic::Name("Light 3");
|
||||
new Characteristic::TargetPosition();
|
||||
new Characteristic::OzoneDensity();
|
||||
|
||||
} // end of setup()
|
||||
|
||||
|
|
|
|||
Loading…
Reference in New Issue