HomeSpan / HomeSpanReferenceSketches

References Sketches for HomeSpan Devices
MIT License
10 stars 1 forks source link

In the thermostat example, we can use the BLE Xiaomi thermometer as a temperature sensor instead of the local temperature sensor. #1

Open Kzekih opened 1 year ago

Kzekih commented 1 year ago

Hello everyone. I've been trying to get the Homespan thermostat example to work with the xiaomi thermometer, but I haven't been able to. I want to use bluetooth temperature sensor instead of local temperature sensor. I'm running the thermostat example I bought the screen shot of the Xiaomi mi thermometer, but I couldn't use the values ​​of the Atc thermometer instead of a kind of DummyTempSensor. Homekit values ​​22 C. I am very glad for your help. I've been trying for 24 hours and I couldn't succeed.

HomeSpan commented 1 year ago

I've not used bluetooth on the ESP32 so unfortunately can't advise on that portion of the project. Are you able to read the thermometer properly using bluetooth but without HomeSpan? In other words, is the issue related to incorporating readings you have successfully taken into HomeSpan, or is it with reading the thermometer via bluetooth?

Note, for greater visibility you may wish to post questions on the main HomeSpan issues and discussions pages instead (this repository is simply a subset of the main HomeSpan docs/examples).

Kzekih commented 1 year ago

Thanks for your interest. I establish a homkit connection via Homspan, and a card with a Wemoos ESP32 Oled Bluetooth and battery is provided. See the information of the Xiaomi Ble thermometer with your card. So far everything is normal. Homspan sends temperature information to Homekit as Duumy Tem Sensor, 22 degrees C. Instead of installing the Dummy Temp Sensor, which I could not do, he explained the temperature information of the xiaomi Atc Thermometer, which is already running on the same card. However, I still couldn't. I keep making mistakes. I would like to ask you, how can I use the temperature sensor of the xiaomi thermometer instead of the Dummy Temp Sensor in the easiest way. We don't have a problem on the Bluetooth side because I'm already reading on the screen and serial output.

For example: Dummy Temp Sensor = (miThermometer.data[i].temperature / 100.0);

I will be grateful if you could help me. It should be simple actually, but for some reason I couldn't make it.

Kzekih commented 1 year ago

Also, if you have an example of a Homespan Thermostat that works with a real temperature sensor (DHT , DHT22...), we would appreciate it if you could send it to me. Maybe we can work it that way. kzekih@gmail.com

HomeSpan commented 1 year ago

I have an example of a real temperature sensor that uses the I2C bus: https://github.com/HomeSpan/TempSensorI2C. DHT22 and similar sensors require special timing protocols and can be quite unreliable in certain instances. I generally use I2C components whenever available since they all use a standard bus and the ESP32 has a robust library (called Wire) to handle all I2C communications.

Kzekih commented 1 year ago

Hello again, I haven't been successful for a long time, because I couldn't do it, I'm trying to solve it. Also thanks for your help. I examined the example you sent, but no matter what I did, I could not change it to BLE and communicate. An idea came to my mind, I can print the temperature of the ATC Thermometer on the oled screen and see it in the serial output. I can add @c to the beginning and read the heat denominator in the serial output. I wonder if we can't pretend to send the user temperature value from serial.

Serial Out : Sensor 0: a4:c1:38:b2:c7:32 25.50°C 52.00% 3.539V 100% -79dBm

@c 25.50

Sensor 0: a4:c1:38:b2:c7:32 25.50°C 52.00% 3.539V 100% -85dBm

@c 25.50

HomeSpan commented 1 year ago

I'm not sure I fully understand what's working and what's not working. From above, it seems that you are able to read the thermometer on the ESP32. Is that correct? If so, what code are you running to produce the "Serial Out" information above? Did you add that code to the example TempSensorI2C sketch, replacing the code that reads from the I2C sensor? If you are able to run a HomeSpan sketch at the same time you can read your sensor via BLE, what exactly is not working?

Also, can you confirm that you can run any of the HomeSpan sketches, such as Example 1, without any modification, and the it pairs to HomeKit and operates as expected. Making sure you can do this is a first step to then adding in your BLE sensor.

Kzekih commented 1 year ago

Hello again, I finally made it. It works smoothly. It was an interesting project. Like the Xiaomi Thermometer user, it reports the temperature via serial2. @c <28.00> etc. I am adding the code. Thank you for your help.

`

include "HomeSpan.h"

include

define MIN_TEMP 0 // minimum allowed temperature in celsius

define MAX_TEMP 40 // maximum allowed temperature in celsius

include

ifdef U8X8_HAVE_HW_I2C

include

endif

include "ATC_MiThermometer.h"

define HEATING_RELAY 2

define RXD2 16

define TXD2 17

const int scanTime = 5; // BLE scan time in seconds //static BLEAddress *knownBLEAddresses; //static boolean doConnect = false; //static boolean connected = false;

// List of known sensors' BLE addresses std::vector knownBLEAddresses = { "a4:c1:38:b2:c7:32" };

ATC_MiThermometer miThermometer(knownBLEAddresses);

U8G2_SSD1306_128X64_NONAME_F_SW_I2C u8g2(U8G2_R0, /SCL/ 4, /SDA/ 5, /RESET/ -1);

define WIFI_SSID "iphone"

define WIFI_PASSWORD "12345678"

struct DummyTempSensor { static float temp;

DummyTempSensor(float t) { temp = t; new SpanUserCommand('f', " - set the temperature, where temp is in degrees F", [](const char buf) { temp = (atof(buf + 1) - 32.0) / 1.8; }); new SpanUserCommand('c', " - set the temperature, where temp is in degrees C", [](const char buf) { temp = atof(buf + 1); }); }

float read() { return (temp); } };

float DummyTempSensor::temp;

struct Reference_Thermostat : Service::Thermostat {

// Create characteristics, set initial values, and set storage in NVS to true

Characteristic::CurrentHeatingCoolingState currentState{ 0, true }; Characteristic::TargetHeatingCoolingState targetState{ 0, true }; Characteristic::CurrentTemperature currentTemp{ 22, true }; Characteristic::TargetTemperature targetTemp{ 22, true }; Characteristic::CurrentRelativeHumidity currentHumidity{ 50, true }; Characteristic::TargetRelativeHumidity targetHumidity{ 50, true }; Characteristic::HeatingThresholdTemperature heatingThreshold{ 22, true }; Characteristic::CoolingThresholdTemperature coolingThreshold{ 22, true }; Characteristic::TemperatureDisplayUnits displayUnits{ 0, true }; // this is for changing the display on the actual thermostat (if any), NOT in the Home App

DummyTempSensor tempSensor{ 22 }; // instantiate a dummy temperature sensor with initial temp=22 degrees C

Reference_Thermostat() : Service::Thermostat() { Serial.printf("\n Creating HomeSpan Thermostat\n");

currentTemp.setRange(MIN_TEMP, MAX_TEMP);  // set all ranges the same to make sure Home App displays them correctly on the same dial
targetTemp.setRange(MIN_TEMP, MAX_TEMP);
heatingThreshold.setRange(MIN_TEMP, MAX_TEMP);
coolingThreshold.setRange(MIN_TEMP, MAX_TEMP);

}

boolean update() override {

if (targetState.updated()) {
  switch (targetState.getNewVal()) {
    case 0:
      Serial.printf("Thermostat turning OFF\n");
      break;
    case 1:
      Serial.printf("Thermostat set to HEAT at %s\n", temp2String(targetTemp.getVal<float>()).c_str());
      break;
    case 2:
      Serial.printf("Thermostat set to COOL at %s\n", temp2String(targetTemp.getVal<float>()).c_str());
      break;
    case 3:
      Serial.printf("Thermostat set to AUTO from %s to %s\n", temp2String(heatingThreshold.getVal<float>()).c_str(), temp2String(coolingThreshold.getVal<float>()).c_str());
      break;
  }
}

if (heatingThreshold.updated() || coolingThreshold.updated())
  Serial.printf("Temperature range changed to %s to %s\n", temp2String(heatingThreshold.getNewVal<float>()).c_str(), temp2String(coolingThreshold.getNewVal<float>()).c_str());

else if (targetTemp.updated())
  Serial.printf("Temperature target changed to %s\n", temp2String(targetTemp.getNewVal<float>()).c_str());

if (displayUnits.updated())
  Serial.printf("Display Units changed to %c\n", displayUnits.getNewVal() ? 'F' : 'C');

if (targetHumidity.updated())
  Serial.printf("Humidity target changed to %d%%\n", targetHumidity.getNewVal());

return (true);

}

// Here's where all the main logic exists to turn on/off heating/cooling by comparing the current temperature to the Thermostat's settings

void loop() override {

float temp = tempSensor.read();  // read temperature sensor (which in this example is just a dummy sensor)

if (temp < MIN_TEMP)  // limit value to stay between MIN_TEMP and MAX_TEMP
  temp = MIN_TEMP;
if (temp > MAX_TEMP)
  temp = MAX_TEMP;

if (currentTemp.timeVal() > 5000 && fabs(currentTemp.getVal<float>() - temp) > 0.25) {  // if it's been more than 5 seconds since last update, and temperature has changed
  currentTemp.setVal(temp);
  Serial.printf("Current Temperature is now %s.\n", temp2String(currentTemp.getNewVal<float>()).c_str());
}

switch (targetState.getVal()) {

  case 0:
    if (currentState.getVal() != 0) {
      Serial.printf("Thermostat OFF\n");
      currentState.setVal(0);
    }
    break;

  case 1:
    if (currentTemp.getVal<float>() < targetTemp.getVal<float>() && currentState.getVal() != 1) {
      Serial.printf("Turning HEAT ON\n");
      currentState.setVal(1);
    } else if (currentTemp.getVal<float>() >= targetTemp.getVal<float>() && currentState.getVal() == 1) {
      Serial.printf("Turning HEAT OFF\n");
      currentState.setVal(0);
    } else if (currentState.getVal() == 2) {
      Serial.printf("Turning COOL OFF\n");
      currentState.setVal(0);
    }
    break;

  case 2:
    if (currentTemp.getVal<float>() > targetTemp.getVal<float>() && currentState.getVal() != 2) {
      Serial.printf("Turning COOL ON\n");
      currentState.setVal(2);
    } else if (currentTemp.getVal<float>() <= targetTemp.getVal<float>() && currentState.getVal() == 2) {
      Serial.printf("Turning COOL OFF\n");
      currentState.setVal(0);
    } else if (currentState.getVal() == 1) {
      Serial.printf("Turning HEAT OFF\n");
      currentState.setVal(0);
    }
    break;

  case 3:
    if (currentTemp.getVal<float>() < heatingThreshold.getVal<float>() && currentState.getVal() != 1) {
      Serial.printf("Turning HEAT ON\n");
      currentState.setVal(1);
    } else if (currentTemp.getVal<float>() >= heatingThreshold.getVal<float>() && currentState.getVal() == 1) {
      Serial.printf("Turning HEAT OFF\n");
      currentState.setVal(0);
    }

    if (currentTemp.getVal<float>() > coolingThreshold.getVal<float>() && currentState.getVal() != 2) {
      Serial.printf("Turning COOL ON\n");
      currentState.setVal(2);
    } else if (currentTemp.getVal<float>() <= coolingThreshold.getVal<float>() && currentState.getVal() == 2) {
      Serial.printf("Turning COOL OFF\n");
      currentState.setVal(0);
    }
    break;
}

}

// This "helper" function makes it easy to display temperatures on the serial monitor in either F or C depending on TemperatureDisplayUnits

String temp2String(float temp) { String t = displayUnits.getVal() ? String(round(temp * 1.8 + 32.0)) : String(temp); t += displayUnits.getVal() ? " F" : " C"; return (t); } };

void drawOLED() { miThermometer.getData(scanTime);

unsigned found = miThermometer.getData(scanTime);

for (int i = 0; i < miThermometer.data.size(); i++) { if (miThermometer.data[i].valid) u8g2.setFont(u8g2_font_logisoso54_tf); u8g2.setCursor(0, 60); u8g2.print(miThermometer.data[i].temperature / 100.0); u8g2.sendBuffer(); } } //drawOLED()

void scanBLE() {

// miThermometer.begin(); miThermometer.resetData(); miThermometer.getData(scanTime);

unsigned found = miThermometer.getData(scanTime);

for (int i = 0; i < miThermometer.data.size(); i++) { if (miThermometer.data[i].valid) { Serial.println(); Serial.printf("Sensor %d: %s\n", i, knownBLEAddresses[i].c_str()); Serial.printf("%.2f°C\n", miThermometer.data[i].temperature / 100.0); Serial.printf("%.2f%%\n", miThermometer.data[i].humidity / 100.0); Serial.printf("%.3fV\n", miThermometer.data[i].batt_voltage / 1000.0); Serial.printf("%d%%\n", miThermometer.data[i].batt_level); Serial.printf("%ddBm\n", miThermometer.data[i].rssi); Serial.println(); delay(1000); Serial2.printf("@c %.2f \r\n", miThermometer.data[i].temperature / 100.0) + String(RX); delay(1000); } }

while (Serial2.available()) { Serial.print(char(Serial2.read())); //Serial2.println("@c %.2f \r\n", miThermometer.data[i].temperature / 100.0) + String(RX); } }

void setup() {

Serial.begin(115200);

Serial2.begin(115200, SERIAL_8N1, RXD2, TXD2); Serial2.println("Serial Txd is on pin: " + String(TX)); Serial2.println("Serial Rxd is on pin: " + String(RX));

u8g2.begin();

pinMode(HEATING_RELAY, OUTPUT);

if defined(WIFI_SSID) && defined(WIFI_PASSWORD)

// if Wi-Fi credentials are defined, supply // them here, otherwise set them using the HomeSpan CLI homeSpan.setWifiCredentials(WIFI_SSID, WIFI_PASSWORD);

endif

homeSpan.begin(Category::Thermostats, "HomeSpan Thermostat");

new SpanAccessory(); new Service::AccessoryInformation(); new Characteristic::Identify();

new Reference_Thermostat();

miThermometer.begin(); scanBLE();

drawOLED(); }

void loop() {

homeSpan.poll(); }`

HomeSpan commented 1 year ago

Thanks for the update and good news. Really glad that you got it all working.

Kzekih commented 1 year ago

Hello again guys. BLE execution protection was limited to WiFi. There have been escapes. I accept the code. I am sharing the latest version. Good luck.

`

include "HomeSpan.h"

include

define MIN_TEMP 0 // minimum allowed temperature in celsius

define MAX_TEMP 40 // maximum allowed temperature in celsius

////////////////////////////////////////////////////////////////////////

include

ifdef U8X8_HAVE_HW_I2C

include

endif

include "ATC_MiThermometer.h"

define HEATING_RELAY 16

define RXD2 15

define TXD2 17

const int HOMESPAN_STATUS_PIN = 2; const int HOMESPAN_CONTROL_PIN = 32;

const int scanTime = 5; // BLE scan time in seconds

// List of known sensors' BLE addresses std::vector knownBLEAddresses = { "a4:c1:38:b2:c7:32" };

ATC_MiThermometer miThermometer(knownBLEAddresses);

U8G2_SSD1306_128X64_NONAME_F_SW_I2C u8g2(U8G2_R0, /SCL/ 4, /SDA/ 5, /RESET/ -1);

define WIFI_SSID "iphone"

define WIFI_PASSWORD "12345678"

struct DummyTempSensor { static float temp;

DummyTempSensor(float t) { temp = t; new SpanUserCommand('f', " - set the temperature, where temp is in degrees F", [](const char buf) { temp = (atof(buf + 1) - 32.0) / 1.8; }); new SpanUserCommand('c', " - set the temperature, where temp is in degrees C", [](const char buf) { temp = atof(buf + 1); }); }

float read() { return (temp); } };

float DummyTempSensor::temp;

struct Reference_Thermostat : Service::Thermostat {

// Create characteristics, set initial values, and set storage in NVS to true

Characteristic::CurrentHeatingCoolingState currentState{ 0, true }; Characteristic::TargetHeatingCoolingState targetState{ 0, true }; Characteristic::CurrentTemperature currentTemp{ 22, true }; Characteristic::TargetTemperature targetTemp{ 22, true }; Characteristic::CurrentRelativeHumidity currentHumidity{ 50, true }; Characteristic::TargetRelativeHumidity targetHumidity{ 50, true }; Characteristic::HeatingThresholdTemperature heatingThreshold{ 22, true }; Characteristic::CoolingThresholdTemperature coolingThreshold{ 22, true }; Characteristic::TemperatureDisplayUnits displayUnits{ 0, true }; // this is for changing the display on the actual thermostat (if any), NOT in the Home App

DummyTempSensor tempSensor{ 22 }; // instantiate a dummy temperature sensor with initial temp=22 degrees C

Reference_Thermostat() : Service::Thermostat() { Serial.printf("\n Creating HomeSpan Thermostat\n");

currentTemp.setRange(MIN_TEMP, MAX_TEMP);  // set all ranges the same to make sure Home App displays them correctly on the same dial
targetTemp.setRange(MIN_TEMP, MAX_TEMP);
heatingThreshold.setRange(MIN_TEMP, MAX_TEMP);
coolingThreshold.setRange(MIN_TEMP, MAX_TEMP);

}

boolean update() override {

if (targetState.updated()) {
  switch (targetState.getNewVal()) {
    case 0:
      Serial.printf("Thermostat turning OFF\n");
      digitalWrite(HEATING_RELAY, LOW);
      break;
    case 1:
      Serial.printf("Thermostat set to HEAT at %s\n", temp2String(targetTemp.getVal<float>()).c_str());
      break;
    case 2:
      Serial.printf("Thermostat set to COOL at %s\n", temp2String(targetTemp.getVal<float>()).c_str());
      break;
    case 3:
      Serial.printf("Thermostat set to AUTO from %s to %s\n", temp2String(heatingThreshold.getVal<float>()).c_str(), temp2String(coolingThreshold.getVal<float>()).c_str());
      break;
  }
}

if (heatingThreshold.updated() || coolingThreshold.updated())
  Serial.printf("Temperature range changed to %s to %s\n", temp2String(heatingThreshold.getNewVal<float>()).c_str(), temp2String(coolingThreshold.getNewVal<float>()).c_str());

else if (targetTemp.updated())
  Serial.printf("Temperature target changed to %s\n", temp2String(targetTemp.getNewVal<float>()).c_str());

if (displayUnits.updated())
  Serial.printf("Display Units changed to %c\n", displayUnits.getNewVal() ? 'F' : 'C');

if (targetHumidity.updated())
  Serial.printf("Humidity target changed to %d%%\n", targetHumidity.getNewVal());

return (true);

}

// Here's where all the main logic exists to turn on/off heating/cooling by comparing the current temperature to the Thermostat's settings

void loop() override {

float temp = tempSensor.read();  // read temperature sensor (which in this example is just a dummy sensor)

if (temp < MIN_TEMP)  // limit value to stay between MIN_TEMP and MAX_TEMP
  temp = MIN_TEMP;
if (temp > MAX_TEMP)
  temp = MAX_TEMP;

if (currentTemp.timeVal() > 5000 && fabs(currentTemp.getVal<float>() - temp) > 0.25) {  // if it's been more than 5 seconds since last update, and temperature has changed
  currentTemp.setVal(temp);
  Serial.printf("Current Temperature is now %s.\n", temp2String(currentTemp.getNewVal<float>()).c_str());
}

switch (targetState.getVal()) {

  case 0:
    if (currentState.getVal() != 0) {
      Serial.printf("Thermostat OFF\n");
      digitalWrite(HEATING_RELAY, LOW);
      currentState.setVal(0);
    }
    break;

  case 1:
    if (currentTemp.getVal<float>() < targetTemp.getVal<float>() && currentState.getVal() != 1) {
      Serial.printf("Turning HEAT ON\n");
      digitalWrite(HEATING_RELAY, HIGH);
      currentState.setVal(1);
    } else if (currentTemp.getVal<float>() >= targetTemp.getVal<float>() && currentState.getVal() == 1) {
      Serial.printf("Turning HEAT OFF\n");
      digitalWrite(HEATING_RELAY, LOW);
      currentState.setVal(0);
    } else if (currentState.getVal() == 2) {
      Serial.printf("Turning COOL OFF\n");

      currentState.setVal(0);
    }
    break;

  case 2:
    if (currentTemp.getVal<float>() > targetTemp.getVal<float>() && currentState.getVal() != 2) {
      Serial.printf("Turning COOL ON\n");
      currentState.setVal(2);
    } else if (currentTemp.getVal<float>() <= targetTemp.getVal<float>() && currentState.getVal() == 2) {
      Serial.printf("Turning COOL OFF\n");
      currentState.setVal(0);
    } else if (currentState.getVal() == 1) {
      Serial.printf("Turning HEAT OFF\n");
      digitalWrite(HEATING_RELAY, LOW);
      currentState.setVal(0);
    }
    break;

  case 3:
    if (currentTemp.getVal<float>() < heatingThreshold.getVal<float>() && currentState.getVal() != 1) {
      Serial.printf("Turning HEAT ON\n");
      digitalWrite(HEATING_RELAY, HIGH);
      currentState.setVal(1);
    } else if (currentTemp.getVal<float>() >= heatingThreshold.getVal<float>() && currentState.getVal() == 1) {
      Serial.printf("Turning HEAT OFF\n");
      digitalWrite(HEATING_RELAY, LOW);
      currentState.setVal(0);
    }

    if (currentTemp.getVal<float>() > coolingThreshold.getVal<float>() && currentState.getVal() != 2) {
      Serial.printf("Turning COOL ON\n");
      digitalWrite(HEATING_RELAY, LOW);
      currentState.setVal(2);
    } else if (currentTemp.getVal<float>() <= coolingThreshold.getVal<float>() && currentState.getVal() == 2) {
      Serial.printf("Turning COOL OFF\n");
      currentState.setVal(0);
    }
    break;
}

}

// This "helper" function makes it easy to display temperatures on the serial monitor in either F or C depending on TemperatureDisplayUnits

String temp2String(float temp) { String t = displayUnits.getVal() ? String(round(temp * 1.8 + 32.0)) : String(temp); t += displayUnits.getVal() ? " F" : " C"; return (t); } };

void scanBLE() { // miThermometer.begin(); miThermometer.resetData(); miThermometer.getData(scanTime);

unsigned found = miThermometer.getData(scanTime);

for (int i = 0; i < miThermometer.data.size(); i++) { if (miThermometer.data[i].valid) { Serial.println(); Serial.printf("Sensor %d: %s\n", i, knownBLEAddresses[i].c_str()); Serial.printf("%.2f°C\n", miThermometer.data[i].temperature / 100.0); Serial.printf("%.2f%%\n", miThermometer.data[i].humidity / 100.0); Serial.printf("%.3fV\n", miThermometer.data[i].batt_voltage / 1000.0); Serial.printf("%d%%\n", miThermometer.data[i].batt_level); Serial.printf("%ddBm\n", miThermometer.data[i].rssi); Serial.println(); delay(1000); Serial2.printf("@c %.2f \r\n", miThermometer.data[i].temperature / 100.0) + String(RX); delay(1000); u8g2.setFont(u8g2_font_logisoso54_tf); u8g2.setCursor(0, 60); u8g2.print(miThermometer.data[i].temperature / 100.0); u8g2.sendBuffer(); } }

while (Serial2.available()) { Serial.print(char(Serial2.read())); //Serial2.println("@c %.2f \r\n", miThermometer.data[i].temperature / 100.0) + String(RX); } }

void setup() {

Serial.begin(115200);

Serial2.begin(115200, SERIAL_8N1, RXD2, TXD2); Serial2.println("Serial Txd is on pin: " + String(TX)); Serial2.println("Serial Rxd is on pin: " + String(RX));

homeSpan.setStatusPin(HOMESPAN_STATUS_PIN); homeSpan.setControlPin(HOMESPAN_CONTROL_PIN);

u8g2.begin();

pinMode(HEATING_RELAY, OUTPUT);

if defined(WIFI_SSID) && defined(WIFI_PASSWORD)

// if Wi-Fi credentials are defined, supply // them here, otherwise set them using the HomeSpan CLI homeSpan.setWifiCredentials(WIFI_SSID, WIFI_PASSWORD);

endif

homeSpan.begin(Category::Thermostats, "HomeSpan Thermostat");

new SpanAccessory(); new Service::AccessoryInformation(); new Characteristic::Identify();

new Reference_Thermostat();

miThermometer.begin(); homeSpan.autoPoll(); }

void loop() { scanBLE(); } `