#include "SoilMoisture.h" #include namespace trygvis { namespace sensor { using namespace trygvis::bluetooth; using std::to_string; using std::bitset; Uuid trygvis_io_base_uuid = {0x32, 0xd0, 0x00, 0x00, 0x03, 0x5d, 0x59, 0xc5, 0x70, 0xd3, 0xbc, 0x8e, 0x4a, 0x1f, 0xd8, 0x3f}; Uuid bluetooth_base_uuid = {0x00, 0x00, 0, 0, 0x00, 0x00, 0x10, 0x00, 0x80, 0x00, 0x00, 0x80, 0x5f, 0x9b, 0x34, 0xfb}; const Uuid soil_moisture_service = makeUuid(trygvis_io_base_uuid, 0x00, 0x10); const Uuid soil_moisture_characteristic = makeUuid(trygvis_io_base_uuid, 0x00, 0x11); const Uuid temperature_characteristic = makeUuid(bluetooth_base_uuid, 0x2a, 0x1e); const Uuid light_characteristic = makeUuid(trygvis_io_base_uuid, 0x00, 0x12); static void createGetSensorCount(ByteBuffer &buffer) { buffer .write8(static_cast(sm_cmd_code::SM_CMD_GET_SENSOR_COUNT)); } static void createGetValue(ByteBuffer &buffer, uint8_t sensor) { buffer .write8(static_cast(sm_cmd_code::SM_CMD_GET_VALUE)) .write16le(sensor); } static void createSetWarningValue(ByteBuffer &buffer, uint8_t sensor, uint16_t warning_value) { buffer .write8(static_cast(sm_cmd_code::SM_CMD_SET_WARNING_VALUE)) .write8(sensor) .write16le(warning_value); } static void createSetSensorName(ByteBuffer &buffer, uint8_t sensor, string name) { buffer .write8(static_cast(sm_cmd_code::SM_CMD_SET_SENSOR_NAME)) .write8(sensor) .write(reinterpret_cast(name.c_str()), name.length()); } static void createGetSensorName(ByteBuffer &buffer, uint8_t sensor) { buffer .write8(static_cast(sm_cmd_code::SM_CMD_GET_SENSOR_NAME)).write8(sensor); } static void createSetUpdateInterval(ByteBuffer buffer, uint8_t sensor, uint8_t interval_in_seconds) { buffer .write8(static_cast(sm_cmd_code::SM_CMD_SET_UPDATE_INTERVAL)) .write8(sensor) .write8(interval_in_seconds); } SoilMoisture SoilMoisture::create(BluetoothGattPtr gatt) { gatt->discoverServices(); auto s = gatt->findService(soil_moisture_service); if (!s) { throw runtime_error("The device is missing the soil moisture service"); } auto &service = *s; auto c = service->findCharacteristic(soil_moisture_characteristic); if (!c) { throw runtime_error("The device is missing the soil moisture characteristic"); } auto temperature = service->findCharacteristic(temperature_characteristic); auto light = service->findCharacteristic(light_characteristic); return SoilMoisture(gatt, service, c.operator*(), temperature, light); } SoilMoisture::SoilMoisture(const BluetoothGattPtr &gatt, const BluetoothGattServicePtr &s, const BluetoothGattCharacteristicPtr &soilMoistureCharacteristic, const o temperatureCharacteristic, const o lightCharacteristic) : gatt(gatt), s(s), soilMoistureCharacteristic(soilMoistureCharacteristic), temperatureCharacteristic(temperatureCharacteristic), lightCharacteristic(lightCharacteristic) {} void SoilMoisture::writeAndRead(const BluetoothGattCharacteristicPtr &c, ByteBuffer &buffer) { buffer.setCursor(0); uint8_t expectedCode = buffer.peek8(0); gatt->writeValue(c, buffer); gatt->readValue(c, buffer); if (buffer.getSize() < 1) { throw runtime_error("Unexpected number of bytes read: " + to_string(buffer.getSize())); } uint8_t actualCode = buffer.read8(); if (actualCode != expectedCode) { throw runtime_error("Unexpected response code: " + to_string(actualCode) + ", expected " + to_string(expectedCode)); } } uint8_t SoilMoisture::getSensorCount() { StaticByteBuffer<100> buffer; createGetSensorCount(buffer); writeAndRead(soilMoistureCharacteristic, buffer); return buffer.read8(); } uint16_t SoilMoisture::getValue(uint8_t sensor) { StaticByteBuffer<100> buffer; createGetValue(buffer, sensor); writeAndRead(soilMoistureCharacteristic, buffer); buffer.read8(); // sensor index return buffer.read16le(); } string SoilMoisture::getName(uint8_t sensor) { StaticByteBuffer<100> buffer; createGetSensorName(buffer, sensor); writeAndRead(soilMoistureCharacteristic, buffer); size_t bytesLeft = buffer.getBytesLeft(); uint8_t bytes[bytesLeft]; buffer.copyTo(bytes, bytesLeft); if (bytesLeft == 0 || bytesLeft < (bytes[0] + 1)) { throw runtime_error("Bad response from device. buffer size: " + to_string(bytesLeft) + ", buffer[0]=" + to_string((int) bytes[0])); } return string((const char *) &bytes[1], bytes[0]); } bool SoilMoisture::hasTemperatureSensor() { return (bool) temperatureCharacteristic; } /** * https://developer.bluetooth.org/gatt/characteristics/Pages/CharacteristicViewer.aspx?u=org.bluetooth.characteristic.intermediate_temperature.xml */ o SoilMoisture::readTemperature() { if (!temperatureCharacteristic) { return o(); } StaticByteBuffer<100> responseBytes; gatt->readValue(*temperatureCharacteristic, responseBytes); if (responseBytes.getSize() < 2) { throw runtime_error("Unexpected number of bytes read: " + to_string(responseBytes.getSize())); } bitset<8> b0 = responseBytes.read8(); bool is_fahrenheit = b0.test(0); bool has_timestamp = b0.test(1); bool has_type = b0.test(2); double temperature = responseBytes.readFLOAT(); return o(temperature); } void SoilMoisture::setLight(uint8_t light, uint8_t value) { if (!lightCharacteristic) { return; } StaticByteBuffer<100> responseBytes; gatt->readValue(*lightCharacteristic, responseBytes); if (responseBytes.getSize() < 2) { throw runtime_error("Unexpected number of bytes read: " + to_string(responseBytes.getSize())); } bitset<8> b0 = responseBytes.read8(); } } // namespace sensor } // namespace trygvis