[HomeKit] Connect your Photon to HomeKit without homebridge

First of all I would like to add like many others; what an amazing project!
This add a whole interface to an already very cool setup of sensors.

Right away I intergrated your example and i had it up and running in a matter of minutes - very intuitive.
On that node I can add that I have tested this with both the Argon and the Xenon, connected through a Mesh Network.

I am looking at integrating this with my weather station. I am using this shield:

The shield return quite a bit of values. Therefore, I am not sure how to go about to get all the following variables (and how to map them):

  • Temp
  • Humi
  • WindG
  • WindA
  • Pressure

Any chance you can point me in the right direction?

Best regards
Mikkel

So I’m trying to integrate this code with some relay control code from NCD.io. The attempt is in the homekit branch here:

https://github.com/djb-rh/gate-controller

For some reason, though, it’s grabbing new IP addresses every few seconds and dumping a lot of events about it in the Particle cloud:

And the serial output has a lot of this:

0000075202 [homekit] INFO: writeData responseLen:36
0000075203 [homekit] INFO: Client removed.
0000075251 [homekit] INFO: Client connected.
0000075252 [homekit] INFO: Request Message read length: 159
0000075253 [homekit] INFO: Max connections reached, sending response to 10.0.1.150 data: HTTP/1.1 503 Service Unavailable


0000075254 [homekit] INFO: writeData responseLen:36
0000075255 [homekit] INFO: Client removed.
0000075935 [homekit] INFO: Bonjour advertised
0000076939 [homekit] INFO: Bonjour advertised
0000077943 [homekit] INFO: Bonjour advertised
0000078947 [homekit] INFO: Bonjour advertised
0000079951 [homekit] INFO: Bonjour advertised
0000080955 [homekit] INFO: Bonjour advertised
0000081959 [homekit] INFO: Bonjour advertised
0000082963 [homekit] INFO: Bonjour advertised
0000083967 [homekit] INFO: Bonjour advertised
0000084884 [comm.protocol] TRACE: Reply recieved: type=2, code=0
0000084884 [comm.protocol] TRACE: message id 2795 complete with code 0.00
0000084885 [comm.protocol] TRACE: rcv'd message type=13
0000084972 [homekit] INFO: Bonjour advertised
0000085976 [homekit] INFO: Bonjour advertised
0000086980 [homekit] INFO: Bonjour advertised
0000087984 [homekit] INFO: Bonjour advertised
0000088988 [homekit] INFO: Bonjour advertised
0000089132 [homekit] INFO: Client connected.
0000089133 [homekit] INFO: Request Message read length: 159
0000089134 [homekit] INFO: Max connections reached, sending response to 10.0.1.89 data: HTTP/1.1 503 Service Unavailable


0000089135 [homekit] INFO: writeData responseLen:36
0000089136 [homekit] INFO: Client removed.
0000089214 [homekit] INFO: Client connected.
0000089222 [homekit] INFO: Request Message read length: 159
0000089223 [homekit] INFO: Max connections reached, sending response to 10.0.1.89 data: HTTP/1.1 503 Service Unavailable


0000089224 [homekit] INFO: writeData responseLen:36
0000089225 [homekit] INFO: Client removed.
0000089989 [homekit] INFO: Bonjour advertised
0000090993 [homekit] INFO: Bonjour advertised
0000091997 [homekit] INFO: Bonjour advertised
0000093001 [homekit] INFO: Bonjour advertised
0000094005 [homekit] INFO: Bonjour advertised
0000095009 [homekit] INFO: Bonjour advertised
0000096013 [homekit] INFO: Bonjour advertised

Anything obvious as to what’s going on here? It seems to sort of work so far, at least on the HomeKit side. Getting it to do what it’s supposed to do on the Particle Cloud side isn’t quite there yet, but it’s hard to troubleshoot if it’s spitting out this much to the Particle cloud since I fear getting throttled while investigating.

–Donnie

Quick followup: Decided it made sense to rule out our code, so I installed and compiled and tested the HAP example relayswitch code. I hooked up a 3.3V relay to D0 on a dev board and flashed it. It’s actually worse in how much it’s spewing to the Particle Cloud. 4-10 messages per IP change, and the IP changes happening even faster.

I have a very very active network of Apple devices. Looks like that’s a problem with this code? I’d guess we have at any given time as many as 8 iPhones, 6 iPads, and a dozen Macs on the same network along with several ATVs and many many other HK devices.

–Donnie

Alright, more in the stream of consciousness. I am being dumb in for some reason thinking the IPs I was seeing in the data column of the cloud event log was the IP of my device. That’s just the IP of some HK device that this thing is hearing from. And my network has a LOT of HK traffic, so this is bad in terms of how much it’s spewing to the cloud.

So, what I think needs to happen is we need to wrap the Particle.publish functions (most of them, anyway) in Particle-HAP with some way to turn them on and off. In my ideal world I think it makes sense to actually be able to dynamically turn them on and off, so maybe we use a runtime flag and have that be something you can set with the iOS app via Particle.function()? Because I could see one of these being kind of useful to debug generic HomeKit problems, but if I’m not debugging I’d like to turn them off completely.

–Donnie

Your GitHub link doesn’t work for me.

1 Like

I just verified the link and it works for me. Not sure what could be up.

That said, I’m relatively certain now it’s not my code anyway. There’s a github issue where someone else complains about the serial traffic and the 503 “errors” and the author (our OP) responds that these are completely normal in a HK network, so all that serial traffic is normal (at first glance it looks kind of bad, but everything is still working properly even with those 503’s). I was confused about the origin of the Particle Cloud messages and from what I can tell they are also normal, although just somewhat excessive traffic when you put this code on a VERY active Apple/HomeKit network. Apparently they are no big deal if you just have a small amount of HK going on, but I have a lot. Way more than the average person. I’ve already looked at the code generating them and there isn’t any way to easily turn them off or down, so I think the lingering question is how to best “fix” that. I think those messages could be very useful in debugging HK specific problems, especially since HK provides so little helpful information when something fails, so I don’t want them to completely go away.

I can fix that, and then submit my changes via github, but wanted to leave this dangling with the OP and see if he has a specific way he’d like to change it, wants to do it himself, or doesn’t care. If left to me, my plan is to just wrap the excessive publishes with a flag that would be off by default and could be turned on via the cloud or the app by a function.

For anyone who cares, all my code does is take what’s basically the NCD.io code that they provide alongside their “IoT relay boards” and adds some extra Particle Cloud stuff (they advertise these as “IFTTT compatible” which is achieved very simply by having a Particle.function that can turn the relays on and off, but provide no state info or anything else) and add some features. One set of features is state output because I have other Photon devices that can monitor and control my relay boards (which control gates on my property), and now I’m adding HomeKit support to the gate controller side.

—Donnie

Nope, just this


Is this maybe a private repo?
When I go to your GitHub repo list there are only four repositories listed.

As usual, you are correct. It is public now. Sorry for that. Also, make sure you look at the “homekit” branch. And the offending publish statements are in the Particle-HAP library itself.

–Donnie

Hi, to clarify your logs.

At first publishing events to particle cloud is just for debugging purposes, it’s not needed. If you want, you can remove it and create a merge request. I will merge it. IP addresses are the addresses of clients, which wants to connect to Photon, so it’s the IPs of iPhones, iPads, etc.

Second - 503 “error”.
Photon in HomeKits acts as a HTTP Server and your iPhones, iPads are HTTP clients. Photon has a limited amount of open sockets (i believe its 10). There’s a maximum active connections in HomeKit (less than 10. i.e. 8). Problem is that all of your iPhone, iPads in network keeps the connection opened all the time, but HomeKit can use only 8 connections in total. 9th socket connections is used only for accepting new connections, sending 503 (Busy) and closing the connection. 10th socket connection is used for particle cloud connection.

Assuming you have 20 more iOS devices in your network, so first 8 devices are accepted and 9th and others are constantly rejected with 503 http code. This is correct behaviour according to Apple HomeKit documentation. It’s not a problem, because if you want to control HomeKit accessory with the 9th (victim) iOS device, it will at first contact one of the 8 connected devices and that one will send the command. It’s complicated to explain, but it works.

Cool, thanks. We’ll submit a patch in the next couple days.

–Donnie

1 Like

Cool project here! I'm trying to understand it enough to create a thermostat but I don't understand how the information in the HomeKit Accessory Protocol Specification document relates to what is in the code. For instance the BME280TemperatureHumiditySensor includes currentTemperature and currentHumidity by in the homekit documentation it is called current relative humidity. How do homekit characteristics match up in the code? I just don't know get how this works. I know I need to define a thermostat device and give it the require characteristics but I'm struggling to follow the examples. Any help for this newbie would be greatly appreciated.

Found what I was looking for! The HKAccessory.h file has all the device characteristics and their mapping to the homekit documentation is a hex value. Now to see if I can modify one of the examples to make a thermostat with my lacking skills.

2 Likes

I think I'm close to getting my thermostat to work but something is not quite right. When I add the device with the ios home app it ends up saying the device could not be added. It has been 2.5yrs since anyone posted on this topic but if anyone out there that is smarter than me could take a look at this code and spot what is wrong please do. I know there is some unused code still in here from the example I started with but I was ignoring it for now thinking it does not matter.

#include "ThermostatAccessoryBase.h"


#include "HKConnection.h"
#include "HKLog.h"


#include <set>


#include <Particle.h>

void ThermostatAccessoryBase::Identify(bool oldValue, bool newValue, HKConnection *sender) {
    hkLog.info("Start Identify\n");
}

std::string ThermostatAccessoryBase::getCurrentMode (HKConnection *sender) {
    return format("%d",1);
}


std::string ThermostatAccessoryBase::getTargetMode (HKConnection *sender) {
    return format("%d",1);
}

void ThermostatAccessoryBase::setTargetMode (int oldValue, int newValue, HKConnection *sender) {
    newValue;
}

std::string ThermostatAccessoryBase::getCurrentTemperature (HKConnection *sender) {
    return format("%.1f", 71.1);
}


std::string ThermostatAccessoryBase::getTargetTemperature (HKConnection *sender) {
    return format("%.1f", 72.2);
}

void ThermostatAccessoryBase::setTargetTemperature (float oldValue, float newValue, HKConnection *sender) {
    newValue;
}

std::string ThermostatAccessoryBase::getTemperatureUnit (HKConnection *sender) {
    return format("%d",1);
}

void ThermostatAccessoryBase::setTemperatureUnit (int oldValue, int newValue, HKConnection *sender) {
    newValue;
}

void ThermostatAccessoryBase::initAccessorySet() {
    Accessory *ThermostatAcc1 = new Accessory();

    //Add thermostat
    AccessorySet *accSet = &AccessorySet::getInstance();
    addInfoServiceToAccessory(ThermostatAcc1, "Thermostat name", "Vendor name", "Model name", "1","1.0.0", std::bind(&ThermostatAccessoryBase::Identify, this, std::placeholders::_1, std::placeholders::_2,std::placeholders::_3));
    accSet->addAccessory(ThermostatAcc1);

    Service *ThermostatService1 = new Service(serviceType_thermostat);
    ThermostatAcc1->addService(ThermostatService1);

    intCharacteristics *currentModeStateChar = new intCharacteristics(charType_currentHeatCoolMode, permission_read|permission_notify, 0, 2, 1, unit_none);
    currentModeStateChar->perUserQuery = std::bind(&ThermostatAccessoryBase::getCurrentMode, this, std::placeholders::_1);
    ThermostatAcc1->addCharacteristics(ThermostatService1, currentModeStateChar);

    intCharacteristics *targetModeStateChar = new intCharacteristics(charType_targetHeatCoolMode, permission_read|permission_write|permission_notify, 0, 3, 1, unit_none);
    targetModeStateChar->perUserQuery = std::bind(&ThermostatAccessoryBase::getTargetMode, this, std::placeholders::_1);
    targetModeStateChar->valueChangeFunctionCall = std::bind(&ThermostatAccessoryBase::setTargetMode, this, std::placeholders::_1, std::placeholders::_2,std::placeholders::_3);
    ThermostatAcc1->addCharacteristics(ThermostatService1, targetModeStateChar);

    floatCharacteristics *currentTemperatureChar = new floatCharacteristics(charType_currentTemperature, permission_read|permission_notify, 0, 100, 0.1, unit_celsius);
    currentTemperatureChar->perUserQuery = std::bind(&ThermostatAccessoryBase::getCurrentTemperature, this, std::placeholders::_1);
    ThermostatAcc1->addCharacteristics(ThermostatService1, currentTemperatureChar);

    floatCharacteristics *targetTemperatureChar = new floatCharacteristics(charType_targetTemperature, permission_read|permission_write|permission_notify, 0, 38, 0.1, unit_celsius);
    targetTemperatureChar->perUserQuery = std::bind(&ThermostatAccessoryBase::getTargetTemperature, this, std::placeholders::_1);
    targetTemperatureChar->valueChangeFunctionCall = std::bind(&ThermostatAccessoryBase::setTargetTemperature, this, std::placeholders::_1, std::placeholders::_2,std::placeholders::_3);
    ThermostatAcc1->addCharacteristics(ThermostatService1, targetTemperatureChar);

    intCharacteristics *temperatureUnitChar = new intCharacteristics(charType_temperatureUnit, permission_read, 0, 1, 1, unit_none);
    temperatureUnitChar->perUserQuery = std::bind(&ThermostatAccessoryBase::getTemperatureUnit, this, std::placeholders::_1);
    ThermostatAcc1->addCharacteristics(ThermostatService1, temperatureUnitChar);

};

RgbColor ThermostatAccessoryBase::HsvToRgb(HsvColor hsv)
{
    RgbColor rgb;
    unsigned char region, remainder, p, q, t;

    if (hsv.s == 0)
    {
        rgb.r = hsv.v;
        rgb.g = hsv.v;
        rgb.b = hsv.v;
        return rgb;
    }

    region = hsv.h / 43;
    remainder = (hsv.h - (region * 43)) * 6;

    p = (hsv.v * (255 - hsv.s)) >> 8;
    q = (hsv.v * (255 - ((hsv.s * remainder) >> 8))) >> 8;
    t = (hsv.v * (255 - ((hsv.s * (255 - remainder)) >> 8))) >> 8;

    switch (region)
    {
        case 0:
            rgb.r = hsv.v; rgb.g = t; rgb.b = p;
            break;
        case 1:
            rgb.r = q; rgb.g = hsv.v; rgb.b = p;
            break;
        case 2:
            rgb.r = p; rgb.g = hsv.v; rgb.b = t;
            break;
        case 3:
            rgb.r = p; rgb.g = q; rgb.b = hsv.v;
            break;
        case 4:
            rgb.r = t; rgb.g = p; rgb.b = hsv.v;
            break;
        default:
            rgb.r = hsv.v; rgb.g = p; rgb.b = q;
            break;
    }

    return rgb;
}

HsvColor ThermostatAccessoryBase::RgbToHsv(RgbColor rgb)
{
    HsvColor hsv;
    unsigned char rgbMin, rgbMax;

    rgbMin = rgb.r < rgb.g ? (rgb.r < rgb.b ? rgb.r : rgb.b) : (rgb.g < rgb.b ? rgb.g : rgb.b);
    rgbMax = rgb.r > rgb.g ? (rgb.r > rgb.b ? rgb.r : rgb.b) : (rgb.g > rgb.b ? rgb.g : rgb.b);

    hsv.v = rgbMax;
    if (hsv.v == 0)
    {
        hsv.h = 0;
        hsv.s = 0;
        return hsv;
    }

    hsv.s = 255 * long(rgbMax - rgbMin) / hsv.v;
    if (hsv.s == 0)
    {
        hsv.h = 0;
        return hsv;
    }

    if (rgbMax == rgb.r)
        hsv.h = 0 + 43 * (rgb.g - rgb.b) / (rgbMax - rgbMin);
    else if (rgbMax == rgb.g)
        hsv.h = 85 + 43 * (rgb.b - rgb.r) / (rgbMax - rgbMin);
    else
        hsv.h = 171 + 43 * (rgb.r - rgb.g) / (rgbMax - rgbMin);

    return hsv;
}
1 Like