diff --git a/doc/nobra.markdown b/doc/nobra.markdown new file mode 100644 index 00000000..9aeb79ee --- /dev/null +++ b/doc/nobra.markdown @@ -0,0 +1,19 @@ +# Nobra Integration + +Basic integration for Nobra devices is included with the Edge-O-Matic-3000. + +### Setup +1. Turn on your Nobra device. +2. A green light indicates 'AzureFang' is enabled. +3. If the light is not green, press the outer two buttons on the control box at the same time and power cycle the device. +4. Turn the main control knob so that the Nobra device is vibrating slightly. +5. With your EOM3k, push the knob and select "Network Settings > Bluetooth Pair". +6. Select "NobraControl" to pair. +7. The EOM3k is now controlling your Nobra device. + +### Configuration +The Nobra's main intensity knob essentially takes over the EOM3k's clamping of motor speed. This is why step 4 in Setup suggests to turn the intensity up until it's vibrating slightly - otherwise, it can appear the 'AzureFang' connection is not working. + +Put another way, you want to configure your EOM3k's "Motor Max Speed" to be the highest it will go (255). This lets you fully adjust the intensity on the NobraControl device. + +So the basic procedure after the 'AzureFang' connection is established is to enter manual mode, turn the EOM3k to max, and configure the NobraControl knobs to a suitable maximum intensity. Then you are ready for the automated modes. diff --git a/include/drivers/Nobra.h b/include/drivers/Nobra.h new file mode 100644 index 00000000..d7c21585 --- /dev/null +++ b/include/drivers/Nobra.h @@ -0,0 +1,29 @@ +#ifndef __drivers_Nobra_h +#define __drivers_Nobra_h + +#include "drivers/Device.h" + +#define CMD_MAX_LEN 40 + +namespace BluetoothDriver { + class Nobra : public Device { + public: + Nobra(const char *name, NimBLEClient *client, NimBLERemoteCharacteristic *remote) : + Device(name, client), remote(remote) {}; + + static Device* detect(NimBLEAdvertisedDevice *device, NimBLEClient *client, NimBLERemoteService *service); + + bool setSpeed(uint8_t speed) override; + + protected: + char mapSpeed(uint8_t speed); + bool send(const char *cmd); + bool sendf(const char *fmt, ...); + + private: + NimBLERemoteCharacteristic *remote; + char lastSentSpeed; + }; +} + +#endif \ No newline at end of file diff --git a/src/BluetoothDriver.cpp b/src/BluetoothDriver.cpp index 52cf6bc0..840bb5b0 100644 --- a/src/BluetoothDriver.cpp +++ b/src/BluetoothDriver.cpp @@ -1,5 +1,6 @@ #include "BluetoothDriver.h" #include "drivers/Lovense.h" +#include "drivers/Nobra.h" static const char *TAG = "BluetoothDriver"; @@ -9,7 +10,8 @@ namespace BluetoothDriver { * driver's detect routine. Thanks! */ static const DeviceDetectionCallback DEVICE_DRIVERS[] = { - Lovense::detect, + Nobra::detect, + Lovense::detect }; void registerDevice(Device *device) { @@ -97,8 +99,11 @@ namespace BluetoothDriver { return nullptr; } - client->connect(device); - + if (!client->connect(device)) { + ESP_LOGE(TAG, "Failed to connect to device (%s).", device->toString().c_str()); + return nullptr; + } + BLEUUID serviceUUID = device->getServiceUUID(); if (serviceUUID.toString() == "") { ESP_LOGE(TAG, "No serviceUUID advertised."); diff --git a/src/BluetoothServer.cpp b/src/BluetoothServer.cpp index 692f9855..d151d125 100644 --- a/src/BluetoothServer.cpp +++ b/src/BluetoothServer.cpp @@ -22,10 +22,8 @@ void BluetoothServer::disconnect() { void BluetoothServer::begin() { Serial.println("BLEDevice::init"); NimBLEDevice::init(Config.bt_display_name); - Serial.println("Create server"); - this->server = BLEDevice::createServer(); - + this->server = NimBLEDevice::createServer(); Serial.println("Create service"); this->service = this->server->createService(SERVICE_UUID); diff --git a/src/drivers/Nobra.cpp b/src/drivers/Nobra.cpp new file mode 100644 index 00000000..67d3c213 --- /dev/null +++ b/src/drivers/Nobra.cpp @@ -0,0 +1,73 @@ +#include "drivers/Nobra.h" +#include + +using namespace BluetoothDriver; + +static const char* TAG = "BluetoothDriver::Nobra"; + +char Nobra::mapSpeed(uint8_t speed) { + const uint8_t nobraSpeedResolution = 15; + float proportion = float(speed) / 255.0; + uint8_t nobraSpeed = round(nobraSpeedResolution * proportion); + if (nobraSpeed == 0) { + return 'p'; + } + char output = 'a' - 1 + nobraSpeed; + return output; +} + +bool Nobra::setSpeed(uint8_t speed) { + char speedCommand = mapSpeed(speed); + + bool isShiftingGears = lastSentSpeed != speedCommand; + if (!isShiftingGears) { + return false; + } + + return this->sendf("%c", speedCommand); +} + +bool Nobra::send(const char* cmd) { + if (!this->isConnected()) { + ESP_LOGE(TAG, "Client was disconnected."); + return false; + } + + std::string cmdStr(cmd); + if (!this->remote->writeValue(cmdStr, false)) { + ESP_LOGE(TAG, "Write failed, disconnect."); + this->disconnect(); + return false; + } + + ESP_LOGD(TAG, "- remote = %s", this->remote->getUUID().toString().c_str()); + ESP_LOGI(TAG, "Send command: \"%s\"", cmd); + return true; +} + +bool Nobra::sendf(const char* fmt, ...) { + char cmd[CMD_MAX_LEN + 1] = ""; + va_list args; + va_start(args, fmt); + vsniprintf(cmd, CMD_MAX_LEN, fmt, args); + va_end(args); + return send(cmd); +} + +Device* Nobra::detect(NimBLEAdvertisedDevice* device, NimBLEClient* client, NimBLERemoteService* service) { + std::vector *chars = service->getCharacteristics(true); + NimBLERemoteCharacteristic *writeChar = nullptr; + + for (NimBLERemoteCharacteristic* c : *chars) { + if (c->canWriteNoResponse()) { + writeChar = c; + } + } + const char *deviceName = device->getName().c_str(); + bool writeCharacteristicFound = writeChar != nullptr; + bool looksLikeNobra = std::string(deviceName).find("NobraControl") != std::string::npos; + if (writeCharacteristicFound && looksLikeNobra) { + Nobra *nobraDevice = new Nobra(deviceName, client, writeChar); + return (BluetoothDriver::Device *)nobraDevice; + } +}