/* * EMS-ESP - https://github.com/proddy/EMS-ESP * Copyright 2019 Paul Derbyshire * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation, either version 3 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program. If not, see . */ // code written by nomis - https://github.com/nomis #include "sensors.h" MAKE_PSTR(logger_name, "sensors") namespace emsesp { uuid::log::Logger Sensors::logger_{F_(logger_name), uuid::log::Facility::DAEMON}; void Sensors::start() { // copy over values from MQTT so we don't keep on quering the filesystem mqtt_nestedjson_ = Settings().mqtt_nestedjson(); #ifndef EMSESP_STANDALONE bus_.begin(SENSOR_GPIO); #endif } void Sensors::loop() { #ifndef EMSESP_STANDALONE if (state_ == State::IDLE) { if (millis() - last_activity_ >= READ_INTERVAL_MS) { // DEBUG_LOG(F("Read sensor temperature")); if (bus_.reset()) { bus_.skip(); bus_.write(CMD_CONVERT_TEMP); state_ = State::READING; } else { // logger_.err(F("Bus reset failed")); } last_activity_ = millis(); } } else if (state_ == State::READING) { if (temperature_convert_complete()) { // DEBUG_LOG(F("Scanning for sensors")); bus_.reset_search(); found_.clear(); state_ = State::SCANNING; last_activity_ = millis(); } else if (millis() - last_activity_ > READ_TIMEOUT_MS) { // logger_.err(F("Sensor read timeout")); state_ = State::IDLE; last_activity_ = millis(); } } else if (state_ == State::SCANNING) { if (millis() - last_activity_ > SCAN_TIMEOUT_MS) { // logger_.err(F("Sensor scan timeout")); state_ = State::IDLE; last_activity_ = millis(); } else { uint8_t addr[ADDR_LEN] = {0}; if (bus_.search(addr)) { bus_.depower(); if (bus_.crc8(addr, ADDR_LEN - 1) == addr[ADDR_LEN - 1]) { switch (addr[0]) { case TYPE_DS18B20: case TYPE_DS18S20: case TYPE_DS1822: case TYPE_DS1825: found_.emplace_back(addr); found_.back().temperature_c_ = get_temperature_c(addr); // char result[10]; // DEBUG_LOG(F("Temperature of %s = %s"), found_.back().to_string().c_str(), Helpers::render_value(result, found_.back().temperature_c_, 2)); break; default: logger_.err(F("Unknown sensor %s"), Device(addr).to_string().c_str()); break; } } else { logger_.err(F("Invalid sensor %s"), Device(addr).to_string().c_str()); } } else { bus_.depower(); devices_ = std::move(found_); found_.clear(); // DEBUG_LOG(F("Found %zu sensor(s)"), devices_.size()); state_ = State::IDLE; last_activity_ = millis(); } } } #endif } bool Sensors::temperature_convert_complete() { #ifndef EMSESP_STANDALONE return bus_.read_bit() == 1; #else return 1; #endif } #pragma GCC diagnostic push #pragma GCC diagnostic ignored "-Wunused-parameter" float Sensors::get_temperature_c(const uint8_t addr[]) { #ifndef EMSESP_STANDALONE if (!bus_.reset()) { logger_.err(F("Bus reset failed before reading scratchpad from %s"), Device(addr).to_string().c_str()); return NAN; } uint8_t scratchpad[SCRATCHPAD_LEN] = {0}; bus_.select(addr); bus_.write(CMD_READ_SCRATCHPAD); bus_.read_bytes(scratchpad, SCRATCHPAD_LEN); if (!bus_.reset()) { logger_.err(F("Bus reset failed after reading scratchpad from %s"), Device(addr).to_string().c_str()); return NAN; } if (bus_.crc8(scratchpad, SCRATCHPAD_LEN - 1) != scratchpad[SCRATCHPAD_LEN - 1]) { logger_.warning(F("Invalid scratchpad CRC: %02X%02X%02X%02X%02X%02X%02X%02X%02X from device %s"), scratchpad[0], scratchpad[1], scratchpad[2], scratchpad[3], scratchpad[4], scratchpad[5], scratchpad[6], scratchpad[7], scratchpad[8], Device(addr).to_string().c_str()); return NAN; } int16_t raw_value = ((int16_t)scratchpad[SCRATCHPAD_TEMP_MSB] << 8) | scratchpad[SCRATCHPAD_TEMP_LSB]; // Adjust based on device resolution int resolution = 9 + ((scratchpad[SCRATCHPAD_CONFIG] >> 5) & 0x3); switch (resolution) { case 9: raw_value &= ~0x1; break; case 10: raw_value &= ~0x3; break; case 11: raw_value &= ~0x7; break; case 12: break; } return (float)raw_value / 16; #else return NAN; #endif } #pragma GCC diagnostic pop const std::vector Sensors::devices() const { return devices_; } Sensors::Device::Device(const uint8_t addr[]) : id_(((uint64_t)addr[0] << 56) | ((uint64_t)addr[1] << 48) | ((uint64_t)addr[2] << 40) | ((uint64_t)addr[3] << 32) | ((uint64_t)addr[4] << 24) | ((uint64_t)addr[5] << 16) | ((uint64_t)addr[6] << 8) | (uint64_t)addr[7]) { } uint64_t Sensors::Device::id() const { return id_; } std::string Sensors::Device::to_string() const { std::string str(20, '\0'); snprintf_P(&str[0], str.capacity() + 1, PSTR("%02X-%04X-%04X-%04X-%02X"), (unsigned int)(id_ >> 56) & 0xFF, (unsigned int)(id_ >> 40) & 0xFFFF, (unsigned int)(id_ >> 24) & 0xFFFF, (unsigned int)(id_ >> 8) & 0xFFFF, (unsigned int)(id_)&0xFF); return str; } // send all dallas sensor values as a JSON package to MQTT // assumes there are devices void Sensors::publish_values() { uint8_t num_devices = devices_.size(); if (num_devices == 0) { return; } // if we're not using nested JSON, send each sensor out seperately // sensor1, sensor2 etc... // e.g. sensor_1 = {"temp":20.2} if (!mqtt_nestedjson_) { StaticJsonDocument<20> doc; for (const auto & device : devices_) { char s[5]; doc["temp"] = Helpers::render_value(s, device.temperature_c_, 2); char topic[60]; // sensors{1-n} strlcpy(topic, "sensor_", 50); // create topic strlcat(topic, device.to_string().c_str(), 50); Mqtt::publish(topic, doc); doc.clear(); // clear json doc so we can reuse the buffer again } return; } // group all sensors together - https://github.com/proddy/EMS-ESP/issues/327 // https://arduinojson.org/v6/assistant/ // sensors = { // "sensor1":{"id":"28-EA41-9497-0E03-5F","temp":"23.25"}, // "sensor2":{"id":"28-EA41-9497-0E03-5F","temp":"23.25"}, // "sensor3":{"id":"28-EA41-9497-0E03-5F","temp":"23.25"}, // "sensor4":{"id":"28-EA41-9497-0E03-5F","temp":"23.25"} // } // const size_t capacity = num_devices * JSON_OBJECT_SIZE(2) + JSON_OBJECT_SIZE(num_devices); DynamicJsonDocument doc(100 * num_devices); uint8_t i = 1; for (const auto & device : devices_) { char sensorID[10]; // sensor{1-n} strlcpy(sensorID, "sensor", 10); char s[5]; strlcat(sensorID, Helpers::itoa(s, i++), 10); JsonObject dataSensor = doc.createNestedObject(sensorID); dataSensor["id"] = device.to_string(); dataSensor["temp"] = Helpers::render_value(s, device.temperature_c_, 2); } Mqtt::publish("sensors", doc); } } // namespace emsesp