/* This is a simple example to show the steps and one possible way of * automatically scanning for and connecting to a device to receive * notification data from the device. * * It works with the esp32 sketch included at the bottom of this source file. */ using UnityEngine; using UnityEngine.UI; public class StartingExample : MonoBehaviour { public string DeviceName = "ledbtn"; public string ServiceUUID = "A9E90000-194C-4523-A473-5FDF36AA4D20"; public string LedUUID = "A9E90001-194C-4523-A473-5FDF36AA4D20"; public string ButtonUUID = "A9E90002-194C-4523-A473-5FDF36AA4D20"; enum States { None, Scan, ScanRSSI, ReadRSSI, Connect, RequestMTU, Subscribe, Unsubscribe, Disconnect, } private bool _connected = false; private float _timeout = 0f; private States _state = States.None; private string _deviceAddress; private bool _foundButtonUUID = false; private bool _foundLedUUID = false; private bool _rssiOnly = false; private int _rssi = 0; public Text StatusText; public Text ButtonPositionText; private string StatusMessage { set { BluetoothLEHardwareInterface.Log(value); StatusText.text = value; } } void Reset() { _connected = false; _timeout = 0f; _state = States.None; _deviceAddress = null; _foundButtonUUID = false; _foundLedUUID = false; _rssi = 0; } void SetState(States newState, float timeout) { _state = newState; _timeout = timeout; } void StartProcess() { Reset(); BluetoothLEHardwareInterface.Initialize(true, false, () => { SetState(States.Scan, 0.1f); }, (error) => { StatusMessage = "Error during initialize: " + error; }); } // Use this for initialization void Start() { StartProcess(); } private void ProcessButton(byte[] bytes) { if (bytes[0] == 0x00) ButtonPositionText.text = "Not Pushed"; else ButtonPositionText.text = "Pushed"; } // Update is called once per frame void Update() { if (_timeout > 0f) { _timeout -= Time.deltaTime; if (_timeout <= 0f) { _timeout = 0f; switch (_state) { case States.None: break; case States.Scan: StatusMessage = "Scanning for " + DeviceName; BluetoothLEHardwareInterface.ScanForPeripheralsWithServices(null, (address, name) => { // if your device does not advertise the rssi and manufacturer specific data // then you must use this callback because the next callback only gets called // if you have manufacturer specific data if (!_rssiOnly) { if (name.Contains(DeviceName)) { StatusMessage = "Found " + name; // found a device with the name we want // this example does not deal with finding more than one _deviceAddress = address; SetState(States.Connect, 0.5f); } } }, (address, name, rssi, bytes) => { // use this one if the device responses with manufacturer specific data and the rssi if (name.Contains(DeviceName)) { StatusMessage = "Found " + name; if (_rssiOnly) { _rssi = rssi; } else { // found a device with the name we want // this example does not deal with finding more than one _deviceAddress = address; SetState(States.Connect, 0.5f); } } }, _rssiOnly); // this last setting allows RFduino to send RSSI without having manufacturer data if (_rssiOnly) SetState(States.ScanRSSI, 0.5f); break; case States.ScanRSSI: break; case States.ReadRSSI: StatusMessage = $"Call Read RSSI"; BluetoothLEHardwareInterface.ReadRSSI(_deviceAddress, (address, rssi) => { StatusMessage = $"Read RSSI: {rssi}"; }); SetState(States.ReadRSSI, 2f); break; case States.Connect: StatusMessage = "Connecting..."; // set these flags _foundButtonUUID = false; _foundLedUUID = false; // note that the first parameter is the address, not the name. I have not fixed this because // of backwards compatiblity. // also note that I am note using the first 2 callbacks. If you are not looking for specific characteristics you can use one of // the first 2, but keep in mind that the device will enumerate everything and so you will want to have a timeout // large enough that it will be finished enumerating before you try to subscribe or do any other operations. BluetoothLEHardwareInterface.ConnectToPeripheral(_deviceAddress, null, null, (address, serviceUUID, characteristicUUID) => { StatusMessage = "Connected..."; BluetoothLEHardwareInterface.StopScan(); if (IsEqual(serviceUUID, ServiceUUID)) { StatusMessage = "Found Service UUID"; _foundButtonUUID = _foundButtonUUID || IsEqual(characteristicUUID, ButtonUUID); _foundLedUUID = _foundLedUUID || IsEqual(characteristicUUID, LedUUID); // if we have found both characteristics that we are waiting for // set the state. make sure there is enough timeout that if the // device is still enumerating other characteristics it finishes // before we try to subscribe if (_foundButtonUUID && _foundLedUUID) { _connected = true; SetState(States.RequestMTU, 2f); } } }); break; case States.RequestMTU: StatusMessage = "Requesting MTU"; BluetoothLEHardwareInterface.RequestMtu(_deviceAddress, 185, (address, newMTU) => { StatusMessage = "MTU set to " + newMTU.ToString(); SetState(States.Subscribe, 0.1f); }); break; case States.Subscribe: StatusMessage = "Subscribing to characteristics..."; BluetoothLEHardwareInterface.SubscribeCharacteristicWithDeviceAddress(_deviceAddress, ServiceUUID, ButtonUUID, (notifyAddress, notifyCharacteristic) => { StatusMessage = "Waiting for user action (1)..."; _state = States.None; // read the initial state of the button BluetoothLEHardwareInterface.ReadCharacteristic(_deviceAddress, ServiceUUID, ButtonUUID, (characteristic, bytes) => { ProcessButton(bytes); }); SetState(States.ReadRSSI, 1f); }, (address, characteristicUUID, bytes) => { if (_state != States.None) { // some devices do not properly send the notification state change which calls // the lambda just above this one so in those cases we don't have a great way to // set the state other than waiting until we actually got some data back. // The esp32 sends the notification above, but if yuor device doesn't you would have // to send data like pressing the button on the esp32 as the sketch for this demo // would then send data to trigger this. StatusMessage = "Waiting for user action (2)..."; SetState(States.ReadRSSI, 1f); } // we received some data from the device ProcessButton(bytes); }); break; case States.Unsubscribe: BluetoothLEHardwareInterface.UnSubscribeCharacteristic(_deviceAddress, ServiceUUID, ButtonUUID, null); SetState(States.Disconnect, 4f); break; case States.Disconnect: StatusMessage = "Commanded disconnect."; if (_connected) { BluetoothLEHardwareInterface.DisconnectPeripheral(_deviceAddress, (address) => { StatusMessage = "Device disconnected"; BluetoothLEHardwareInterface.DeInitialize(() => { _connected = false; _state = States.None; }); }); } else { BluetoothLEHardwareInterface.DeInitialize(() => { _state = States.None; }); } break; } } } } private bool ledON = false; public void OnLED() { ledON = !ledON; if (ledON) { SendByte((byte)0x01); } else { SendByte((byte)0x00); } } string FullUUID(string uuid) { string fullUUID = uuid; if (fullUUID.Length == 4) fullUUID = "0000" + uuid + "-0000-1000-8000-00805f9b34fb"; return fullUUID; } bool IsEqual(string uuid1, string uuid2) { if (uuid1.Length == 4) uuid1 = FullUUID(uuid1); if (uuid2.Length == 4) uuid2 = FullUUID(uuid2); return (uuid1.ToUpper().Equals(uuid2.ToUpper())); } void SendByte(byte value) { byte[] data = { value }; BluetoothLEHardwareInterface.WriteCharacteristic(_deviceAddress, ServiceUUID, LedUUID, data, data.Length, true, (characteristicUUID) => { BluetoothLEHardwareInterface.Log("Write Succeeded"); }); } } /* COPY FROM BELOW THIS LINE >>> // This sketch is a companion to the Bluetooth LE for iOS, tvOS and Android plugin for Unity3D. // It is the hardware side of the StartingExample. // The URL to the asset on the asset store is: // https://assetstore.unity.com/packages/tools/network/bluetooth-le-for-ios-tvos-and-android-26661 // This sketch simply advertises as ledbtn and has a single service with 2 characteristics. // The ledUUID characteristic is used to turn the LED on and off. Writing 0 turns it off and 1 turns it on. // The buttonUUID characteristic can be read or subscribe to. When the button is down the characteristic // value is 1. When the button is up the value is 0. // This sketch was written for the Hiletgo ESP32 dev board found here: // https://www.amazon.com/HiLetgo-ESP-WROOM-32-Development-Microcontroller-Integrated/dp/B0718T232Z/ref=sr_1_3?keywords=hiletgo&qid=1570209988&sr=8-3 // Many other ESP32 devices will work fine. #include "BLEDevice.h" #include "BLE2902.h" // pin 2 on the Hiletgo // (can be turned on/off from the iPhone app) const uint32_t led = 2; // pin 5 on the RGB shield is button 1 // (button press will be shown on the iPhone app) const uint32_t button = 0; static BLEUUID serviceUUID("A9E90000-194C-4523-A473-5FDF36AA4D20"); static BLEUUID ledUUID("A9E90001-194C-4523-A473-5FDF36AA4D20"); static BLEUUID buttonUUID("A9E90002-194C-4523-A473-5FDF36AA4D20"); bool deviceConnected = false; bool oldDeviceConnected = false; bool lastButtonState = false; BLEServer* pServer = 0; BLECharacteristic* pCharacteristicCommand = 0; BLECharacteristic* pCharacteristicData = 0; class BTServerCallbacks : public BLEServerCallbacks { void onConnect(BLEServer* pServer) { Serial.println("Connected..."); deviceConnected = true; }; void onDisconnect(BLEServer* pServer) { Serial.println("Disconnected..."); deviceConnected = false; // don't leave the led on if they disconnect digitalWrite(led, LOW); } }; class BTCallbacks : public BLECharacteristicCallbacks { void onRead(BLECharacteristic* pCharacteristic) { } void onWrite(BLECharacteristic* pCharacteristic) { uint8_t* data = pCharacteristic->getData(); int len = pCharacteristic->getValue().empty() ? 0 : pCharacteristic->getValue().length(); if (len > 0) { // if the first byte is 0x01 / on / true if (data[0] == 0x01) digitalWrite(led, HIGH); else digitalWrite(led, LOW); } } }; // debounce time (in ms) int debounce_time = 10; // maximum debounce timeout (in ms) int debounce_timeout = 100; void BluetoothStartAdvertising() { if (pServer != 0) { BLEAdvertising* pAdvertising = pServer->getAdvertising(); pAdvertising->start(); } } void BluetoothStopAdvertising() { if (pServer != 0) { BLEAdvertising* pAdvertising = pServer->getAdvertising(); pAdvertising->stop(); } } void setup() { Serial.begin(115200); // led turned on/off from the iPhone app pinMode(led, OUTPUT); // button press will be shown on the iPhone app) pinMode(button, INPUT); BLEDevice::init("ledbtn"); // BLEDevice::setCustomGattsHandler(my_gatts_event_handler); // BLEDevice::setCustomGattcHandler(my_gattc_event_handler); pServer = BLEDevice::createServer(); BLEService* pService = pServer->createService(serviceUUID); pServer->setCallbacks(new BTServerCallbacks()); pCharacteristicCommand = pService->createCharacteristic( buttonUUID, BLECharacteristic::PROPERTY_READ | BLECharacteristic::PROPERTY_WRITE | BLECharacteristic::PROPERTY_NOTIFY); pCharacteristicCommand->setCallbacks(new BTCallbacks()); pCharacteristicCommand->setValue(""); pCharacteristicCommand->addDescriptor(new BLE2902()); pCharacteristicData = pService->createCharacteristic( ledUUID, BLECharacteristic::PROPERTY_READ | BLECharacteristic::PROPERTY_WRITE | BLECharacteristic::PROPERTY_NOTIFY); pCharacteristicData->setCallbacks(new BTCallbacks()); pCharacteristicData->setValue(""); pCharacteristicData->addDescriptor(new BLE2902()); pService->start(); BluetoothStartAdvertising(); } void loop() { if (pServer != 0) { // disconnecting if (!deviceConnected && oldDeviceConnected) { delay(500); // give the bluetooth stack the chance to get things ready pServer->startAdvertising(); // restart advertising Serial.println("start advertising"); oldDeviceConnected = deviceConnected; } // connecting if (deviceConnected && !oldDeviceConnected) { oldDeviceConnected = deviceConnected; } uint8_t buttonState = digitalRead(button); if (deviceConnected && pCharacteristicCommand != 0 && buttonState != lastButtonState) { lastButtonState = buttonState; uint8_t packet[1]; packet[0] = buttonState == HIGH ? 0x00 : 0x01; pCharacteristicCommand->setValue(packet, 1); pCharacteristicCommand->notify(); } } } <<< COPY TO ABOVE THIS LINE */