Chore: initial commit

This commit is contained in:
Dorian Zedler 2023-01-13 17:28:25 +01:00
parent a39897195f
commit 87c316f5ef
Signed by: dorian
GPG key ID: 989DE36109AFA354
20 changed files with 778 additions and 0 deletions

3
.gitmodules vendored Normal file
View file

@ -0,0 +1,3 @@
[submodule "app/QBluetoothLeUart"]
path = app/QBluetoothLeUart
url = https://itsblue.dev/itsblue-development/QBluetoothLeUart.git

74
app/.gitignore vendored Normal file
View file

@ -0,0 +1,74 @@
# This file is used to ignore files which are generated
# ----------------------------------------------------------------------------
*~
*.autosave
*.a
*.core
*.moc
*.o
*.obj
*.orig
*.rej
*.so
*.so.*
*_pch.h.cpp
*_resource.rc
*.qm
.#*
*.*#
core
!core/
tags
.DS_Store
.directory
*.debug
Makefile*
*.prl
*.app
moc_*.cpp
ui_*.h
qrc_*.cpp
Thumbs.db
*.res
*.rc
/.qmake.cache
/.qmake.stash
# qtcreator generated files
*.pro.user*
CMakeLists.txt.user*
# xemacs temporary files
*.flc
# Vim temporary files
.*.swp
# Visual Studio generated files
*.ib_pdb_index
*.idb
*.ilk
*.pdb
*.sln
*.suo
*.vcproj
*vcproj.*.*.user
*.ncb
*.sdf
*.opensdf
*.vcxproj
*vcxproj.*
# MinGW generated files
*.Debug
*.Release
# Python byte code
*.pyc
# Binaries
# --------
*.dll
*.exe

24
app/BirdLamp.pro Normal file
View file

@ -0,0 +1,24 @@
QT += quick
# You can make your code fail to compile if it uses deprecated APIs.
# In order to do so, uncomment the following line.
#DEFINES += QT_DISABLE_DEPRECATED_BEFORE=0x060000 # disables all the APIs deprecated before Qt 6.0.0
SOURCES += \
main.cpp
RESOURCES += qml.qrc
# Additional import path used to resolve QML modules in Qt Creator's code model
QML_IMPORT_PATH =
# Additional import path used to resolve QML modules just for Qt Quick Designer
QML_DESIGNER_IMPORT_PATH =
# Default rules for deployment.
qnx: target.path = /tmp/$${TARGET}/bin
else: unix:!android: target.path = /opt/$${TARGET}/bin
!isEmpty(target.path): INSTALLS += target
CONFIG += QBluetoothLeUart_QML
include($$PWD/QBluetoothLeUart/QBluetoothLeUart.pri)

10
app/BluetoothOffPage.qml Normal file
View file

@ -0,0 +1,10 @@
import QtQuick 2.15
import QtQuick.Controls 2.15
Page {
id: root
Label {
text: "Please turn Bluetooth on"
}
}

5
app/ColorPicker.qml Normal file
View file

@ -0,0 +1,5 @@
import QtQuick 2.0
Item {
}

73
app/ControllerPage.qml Normal file
View file

@ -0,0 +1,73 @@
import QtQuick 2.15
import QtQuick.Controls 2.15
import QtQuick.Controls.Material 2.15
Page {
id: root
anchors.fill: parent
property int lampState: 0
onLampStateChanged: {
console.log(lampState.toString(16))
sendBufferTimer.start()
}
Timer {
id: sendBufferTimer
interval: 500
running: false
repeat: false
onTriggered: {
ble.sendData("s:" + lampState.toString(16))
}
}
Column {
anchors.centerIn: parent
Switch {
checked: app.remoteState >> 24 === 1
onCheckedChanged: {
let newState = lampState & 16777215
lampState = newState | ((checked ? 1:0) << 24)
}
}
Slider {
id: red
Material.accent: Material.Red
from: 0
to: 255
value: app.remoteState >> 16 & 255
onValueChanged: {
let newColor = lampState & 16842751
lampState = newColor | (value << 16)
}
}
Slider {
id: green
Material.accent: Material.Green
from: 0
to: 255
value: app.remoteState >> 8 & 255
onValueChanged: {
let newColor = lampState & 33489151
lampState = newColor | (value << 8)
}
}
Slider {
id: blue
Material.accent: Material.Blue
from: 0
to: 255
value: app.remoteState & 255
onValueChanged: {
let newColor = lampState & 33554176
lampState = newColor | (value)
}
}
}
}

15
app/PermissionPage.qml Normal file
View file

@ -0,0 +1,15 @@
import QtQuick 2.15
import QtQuick.Controls 2.15
Page {
id: root
Column {
Label {
text: "Please grant bluetooth permission"
}
Button {
onClicked: ble.requestLocationPermission()
}
}
}

1
app/QBluetoothLeUart Submodule

@ -0,0 +1 @@
Subproject commit b6ff6d1e0ce6d05761761daf27343a3dd13fb1d0

28
app/main.cpp Normal file
View file

@ -0,0 +1,28 @@
#include <QGuiApplication>
#include <QQmlApplicationEngine>
#include <qbluetoothleuartclient.h>
#include <QQuickStyle>
int main(int argc, char *argv[])
{
#if QT_VERSION < QT_VERSION_CHECK(6, 0, 0)
QCoreApplication::setAttribute(Qt::AA_EnableHighDpiScaling);
#endif
QGuiApplication app(argc, argv);
QBluetoothLeUartClient::init();
QQuickStyle::setStyle("Material");
QQmlApplicationEngine engine;
const QUrl url(QStringLiteral("qrc:/main.qml"));
QObject::connect(&engine, &QQmlApplicationEngine::objectCreated,
&app, [url](QObject *obj, const QUrl &objUrl) {
if (!obj && url == objUrl)
QCoreApplication::exit(-1);
}, Qt::QueuedConnection);
engine.load(url);
return app.exec();
}

101
app/main.qml Normal file
View file

@ -0,0 +1,101 @@
import QtQuick 2.15
import QtQuick.Controls 2.15
import QtQuick.Window 2.15
import de.itsblue.bluetoothleuart 1.0
Window {
width: 640
height: 480
visible: true
title: qsTr("Hello World")
Page {
id: app
anchors.fill: parent
property int remoteState: -1
property bool foundLamp: false
QBluetoothLeUart {
id: ble
Component.onCompleted: {
ble.startScanningForDevices()
}
onFoundNewDevice: {
console.log("Found a device: name: " + device.name + " address: " + device.address)
if(device.address === "08:B6:1F:38:17:16") {
app.foundLamp = true
ble.stopScanningForDevices()
ble.connectToDevice(device)
}
}
onConnectedToDevice: {
console.log("Connected")
ble.sendData("s")
}
onDataReceived: {
console.log("Data received: " + data)
if(data.startsWith("s:")) {
app.remoteState = parseInt(data.slice(2), 16)
}
}
onStateChanged: {
console.log("state", state)
switch(state) {
case QBluetoothLeUart.ScanFinished:
case QBluetoothLeUart.Idle:
if(!app.foundLamp) ble.startScanningForDevices()
break;
case QBluetoothLeUart.Connecting:
app.foundLamp = false
break;
default:
break;
}
}
}
Loader {
anchors.fill: parent
sourceComponent:
ble.state === QBluetoothLeUart.Connected && app.remoteState !== -1 ? controllerComponent :
ble.state === QBluetoothLeUart.LocationPermissionDenied ? permissionComponent:
ble.state === QBluetoothLeUart.AdapterTurnedOff ? bluetoothOffComponent:
loadingComponent
}
Component {
id: loadingComponent
Page {
anchors.fill: parent
BusyIndicator {
anchors.centerIn: parent
}
}
}
Component {
id: controllerComponent
ControllerPage {
}
}
Component {
id: permissionComponent
PermissionPage {
}
}
Component {
id: bluetoothOffComponent
BluetoothOffPage {
}
}
}
}

9
app/qml.qrc Normal file
View file

@ -0,0 +1,9 @@
<RCC>
<qresource prefix="/">
<file>main.qml</file>
<file>ControllerPage.qml</file>
<file>ColorPicker.qml</file>
<file>PermissionPage.qml</file>
<file>BluetoothOffPage.qml</file>
</qresource>
</RCC>

5
firmware/.gitignore vendored Normal file
View file

@ -0,0 +1,5 @@
.pio
.vscode/.browse.c_cpp.db*
.vscode/c_cpp_properties.json
.vscode/launch.json
.vscode/ipch

10
firmware/.vscode/extensions.json vendored Normal file
View file

@ -0,0 +1,10 @@
{
// See http://go.microsoft.com/fwlink/?LinkId=827846
// for the documentation about the extensions.json format
"recommendations": [
"platformio.platformio-ide"
],
"unwantedRecommendations": [
"ms-vscode.cpptools-extension-pack"
]
}

View file

@ -0,0 +1,102 @@
#ifndef BLUETOOTH_LE_UART_SERVER
#define BLUETOOTH_LE_UART_SERVER
#include <BLEDevice.h>
#include <BLEServer.h>
#include <BLEUtils.h>
#include <BLE2902.h>
#include <Arduino.h>
class BluetoothLeUartServerCallbacks;
/*!
* \brief The BluetoothLeUartServer class can be used to connect to another Bluetooth LE device (like an Android phone) effordlessly.
*
* Example use
* \code
* #include <Arduino.h>
*
* BluetoothLeUartServer* server;
*
* class MyCallbacks : public BluetoothLeUartServerCallbacks
* {
* virtual void onDeviceConnectedChanged(bool deviceConnected)
* {
* Serial.println("Device connected changed");
* };
* virtual void onDataReceived(String data)
* {
* Serial.println("Got some data: " + data);
* };
* };
*
* void setup()
* {
* server = new BluetoothLeUartServer("LedDisplay1", "6e400001-b5a3-f393-e0a9-e50e24dcca9e", "6e400002-b5a3-f393-e0a9-e50e24dcca9e", "6e400003-b5a3-f393-e0a9-e50e24dcca9e");
* server->setCallbacks(new MyCallbacks());
* }
*
* void loop()
* {
* if(server->getDeviceConnected())
* server->sendData("PING");
* delay(1000);
* }
* \endcode
*/
class BluetoothLeUartServer : protected BLEServerCallbacks, protected BLECharacteristicCallbacks
{
public:
explicit BluetoothLeUartServer(String deviceName, const char uartServiceUUID[36], const char rxUUID[36], const char txUUID[36]);
// befriend for callbacks
friend class BLEServer;
friend class BLECharacteristic;
// public functions
void setCallbacks(BluetoothLeUartServerCallbacks *callbacks);
void sendData(String data);
bool getDeviceConnected();
bool disconnectCurrentDevice();
String getDeviceAddress();
protected:
// callbacks for BLEServer
void onConnect(BLEServer *pServer, esp_ble_gatts_cb_param_t *param) override;
void onDisconnect(BLEServer *pServer) override;
// callback for BLECharacteristic
void onWrite(BLECharacteristic *rxCharacteristic) override;
private:
// service and characteristic UUIDs
const char *uartServiceUUID;
const char *rxUUID;
const char *txUUID;
esp_gatt_perm_t permissions;
// BLE Objects
BLEServer *bleServer;
BLEService *bleService;
BLECharacteristic *txCharacteristic;
BLECharacteristic *rxCharacteristic;
// helpers
bool deviceConnected;
uint16_t deviceConnectionId;
BluetoothLeUartServerCallbacks *callbacks;
};
class BluetoothLeUartServerCallbacks
{
public:
virtual void onDeviceConnectedChanged(bool deviceConnected);
virtual void onDataReceived(String data);
};
#endif // BLUETOOTH_LE_UART_SERVER

39
firmware/include/README Normal file
View file

@ -0,0 +1,39 @@
This directory is intended for project header files.
A header file is a file containing C declarations and macro definitions
to be shared between several project source files. You request the use of a
header file in your project source file (C, C++, etc) located in `src` folder
by including it, with the C preprocessing directive `#include'.
```src/main.c
#include "header.h"
int main (void)
{
...
}
```
Including a header file produces the same results as copying the header file
into each source file that needs it. Such copying would be time-consuming
and error-prone. With a header file, the related declarations appear
in only one place. If they need to be changed, they can be changed in one
place, and programs that include the header file will automatically use the
new version when next recompiled. The header file eliminates the labor of
finding and changing all the copies as well as the risk that a failure to
find one copy will result in inconsistencies within a program.
In C, the usual convention is to give header files names that end with `.h'.
It is most portable to use only letters, digits, dashes, and underscores in
header file names, and at most one dot.
Read more about using header files in official GCC documentation:
* Include Syntax
* Include Operation
* Once-Only Headers
* Computed Includes
https://gcc.gnu.org/onlinedocs/cpp/Header-Files.html

46
firmware/lib/README Normal file
View file

@ -0,0 +1,46 @@
This directory is intended for project specific (private) libraries.
PlatformIO will compile them to static libraries and link into executable file.
The source code of each library should be placed in a an own separate directory
("lib/your_library_name/[here are source files]").
For example, see a structure of the following two libraries `Foo` and `Bar`:
|--lib
| |
| |--Bar
| | |--docs
| | |--examples
| | |--src
| | |- Bar.c
| | |- Bar.h
| | |- library.json (optional, custom build options, etc) https://docs.platformio.org/page/librarymanager/config.html
| |
| |--Foo
| | |- Foo.c
| | |- Foo.h
| |
| |- README --> THIS FILE
|
|- platformio.ini
|--src
|- main.c
and a contents of `src/main.c`:
```
#include <Foo.h>
#include <Bar.h>
int main (void)
{
...
}
```
PlatformIO Library Dependency Finder will find automatically dependent
libraries scanning project source files.
More information about PlatformIO Library Dependency Finder
- https://docs.platformio.org/page/librarymanager/ldf.html

18
firmware/platformio.ini Normal file
View file

@ -0,0 +1,18 @@
; PlatformIO Project Configuration File
;
; Build options: build flags, source filter
; Upload options: custom upload port, speed and extra flags
; Library options: dependencies, extra library storages
; Advanced options: extra scripting
;
; Please visit documentation for the other options and examples
; https://docs.platformio.org/page/projectconf.html
[env:esp32dev]
platform = espressif32
board = esp32dev
framework = arduino
monitor_filters = esp32_exception_decoder
monitor_speed = 115200
lib_deps =
adafruit/Adafruit NeoPixel@^1.10.7

View file

@ -0,0 +1,112 @@
#include "BluetoothLeUartServer.h"
BluetoothLeUartServer::BluetoothLeUartServer(String deviceName, const char uartServiceUUID[36], const char rxUUID[36], const char txUUID[36])
{
this->uartServiceUUID = uartServiceUUID;
this->rxUUID = rxUUID;
this->txUUID = txUUID;
this->callbacks = nullptr;
this->deviceConnected = false;
this->deviceConnectionId = -1;
// Create the BLE Device
BLEDevice::init(deviceName.c_str()); // Give it a name
// Create the BLE Server
bleServer = BLEDevice::createServer();
bleServer->setCallbacks(this);
// Create the BLE Service
bleService = bleServer->createService(this->uartServiceUUID);
// Create a BLE Characteristic
txCharacteristic = bleService->createCharacteristic(
this->txUUID,
BLECharacteristic::PROPERTY_NOTIFY);
txCharacteristic->addDescriptor(new BLE2902());
rxCharacteristic = bleService->createCharacteristic(
this->rxUUID,
BLECharacteristic::PROPERTY_READ |
BLECharacteristic::PROPERTY_WRITE);
rxCharacteristic->setCallbacks(this);
// Start the service
bleService->start();
// Start advertising
bleServer->getAdvertising()->addServiceUUID(bleService->getUUID());
bleServer->getAdvertising()->setScanResponse(true);
bleServer->getAdvertising()->setMinPreferred(0x06); // functions that help with iPhone connections issue
bleServer->getAdvertising()->setMinPreferred(0x12);
bleServer->getAdvertising()->start();
}
void BluetoothLeUartServer::setCallbacks(BluetoothLeUartServerCallbacks *callbacks)
{
this->callbacks = callbacks;
}
void BluetoothLeUartServer::sendData(String data)
{
//txCharacteristic->setValue(data.c_str());
//Serial.printf("String is : '%s' with length of %d\n", data.c_str(), data.length() );
txCharacteristic->setValue((uint8_t*)data.c_str(),data.length());
txCharacteristic->notify();
}
bool BluetoothLeUartServer::getDeviceConnected()
{
return this->deviceConnected;
}
bool BluetoothLeUartServer::disconnectCurrentDevice() {
if(!this->getDeviceConnected())
return false;
this->bleServer->disconnect(this->deviceConnectionId);
return true;
}
String BluetoothLeUartServer::getDeviceAddress() {
String address = BLEDevice::getAddress().toString().c_str();
address.toUpperCase();
return address;
}
void BluetoothLeUartServer::onConnect(BLEServer* pServer, esp_ble_gatts_cb_param_t *param)
{
// only allow one device
if(this->deviceConnected)
return this->bleServer->disconnect(param->connect.conn_id);
this->deviceConnected = true;
this->deviceConnectionId = param->connect.conn_id;
if (this->callbacks != nullptr)
{
this->callbacks->onDeviceConnectedChanged(this->deviceConnected);
}
}
void BluetoothLeUartServer::onDisconnect(BLEServer *pServer)
{
this->deviceConnected = false;
this->deviceConnectionId = -1;
bleServer->getAdvertising()->start();
Serial.println("Restart advertising after disconnect ...");
if (this->callbacks != nullptr)
this->callbacks->onDeviceConnectedChanged(this->deviceConnected);
}
void BluetoothLeUartServer::onWrite(BLECharacteristic *rxCharacteristic)
{
if (this->callbacks != nullptr)
this->callbacks->onDataReceived(rxCharacteristic->getValue().c_str());
}

92
firmware/src/main.cpp Normal file
View file

@ -0,0 +1,92 @@
#include <Arduino.h>
#include <Adafruit_NeoPixel.h>
#include <EEPROM.h>
#include <BluetoothLeUartServer.h>
#define NUMPIXELS 15
Adafruit_NeoPixel pixels(NUMPIXELS, 21, NEO_GRB + NEO_KHZ800);
uint64_t current_state = 0;
uint64_t last_state = 0;
BluetoothLeUartServer *server;
class MyCallbacks : public BluetoothLeUartServerCallbacks
{
virtual void onDeviceConnectedChanged(bool deviceConnected)
{
Serial.println("Device connected changed: " + String(deviceConnected));
};
virtual void onDataReceived(String data)
{
Serial.println("Got some data: " + data);
if (data.startsWith("s:"))
{
data.remove(0, 2);
current_state = strtoul(data.c_str(), NULL, 16);
if (current_state == last_state)
return;
EEPROM.write(0, current_state >> 24);
EEPROM.write(1, current_state >> 16);
EEPROM.write(2, current_state >> 8);
EEPROM.write(3, current_state);
EEPROM.commit();
}
else if (data == "s")
{
server->sendData("s:" + String(current_state, HEX));
}
};
};
void update_color();
void set_color(uint32_t c);
void setup()
{
Serial.begin(115200);
pixels.begin();
server = new BluetoothLeUartServer("Hannis birb lamp", "6e400001-b5a3-f393-e0a9-e50e24dcca9e", "6e400002-b5a3-f393-e0a9-e50e24dcca9e", "6e400003-b5a3-f393-e0a9-e50e24dcca9e");
server->setCallbacks(new MyCallbacks());
EEPROM.begin(4);
current_state = EEPROM.read(0) << 24 | EEPROM.read(1) << 16 | EEPROM.read(2) << 8 | EEPROM.read(3);
last_state = current_state;
update_color();
Serial.println("Setup done");
Serial.println(current_state);
}
void loop()
{
if (current_state != last_state)
{
update_color();
last_state = current_state;
}
delay(100);
}
void update_color()
{
if (current_state >> 24 == 0)
set_color(0);
else
set_color(current_state);
}
void set_color(uint32_t c)
{
for (int i = 0; i < NUMPIXELS; i++)
{
pixels.setPixelColor(i, c);
}
pixels.show();
}

11
firmware/test/README Normal file
View file

@ -0,0 +1,11 @@
This directory is intended for PlatformIO Test Runner and project tests.
Unit Testing is a software testing method by which individual units of
source code, sets of one or more MCU program modules together with associated
control data, usage procedures, and operating procedures, are tested to
determine whether they are fit for use. Unit testing finds problems early
in the development cycle.
More information about PlatformIO Unit Testing:
- https://docs.platformio.org/en/latest/advanced/unit-testing/index.html