This commit is contained in:
2025-11-28 18:26:32 -06:00
commit 8bb08d2819
21 changed files with 3309 additions and 0 deletions

5
.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

9
.vscode/extensions.json vendored Normal file
View File

@@ -0,0 +1,9 @@
{
"recommendations": [
"pioarduino.pioarduino-ide",
"platformio.platformio-ide"
],
"unwantedRecommendations": [
"ms-vscode.cpptools-extension-pack"
]
}

5
.vscode/settings.json vendored Normal file
View File

@@ -0,0 +1,5 @@
{
"files.associations": {
"nimbledevice.h": "c"
}
}

3
CMakeLists.txt Normal file
View File

@@ -0,0 +1,3 @@
cmake_minimum_required(VERSION 3.16.0)
include($ENV{IDF_PATH}/tools/cmake/project.cmake)
project(Blinds_XIAO)

Submodule components/esp-nimble-cpp added at 25af28bcad

8
dependencies.lock Normal file
View File

@@ -0,0 +1,8 @@
dependencies:
idf:
source:
type: idf
version: 5.5.1
manifest_hash: 24b4d087511378b31aec46ab95615da067c81c6477aaa4515b222e977e29004a
target: esp32c6
version: 2.0.0

102
include/BLE.cpp Normal file
View File

@@ -0,0 +1,102 @@
#include "BLE.hpp"
#include "NimBLEDevice.h"
#include "WiFi.hpp"
bool flag_scan_requested = false;
std::string SSID = "";
std::string password = "";
bool SSIDGiven = false;
bool passGiven = false;
std::string token = "";
bool tokenGiven = false;
// Global pointers to characteristics for notification support
NimBLECharacteristic* ssidListChar = nullptr;
NimBLECharacteristic* connectConfirmChar = nullptr;
NimBLEAdvertising* initBLE() {
NimBLEDevice::init("BlindMaster-C6");
// Optional: Boost power for better range (ESP32-C6 supports up to +20dBm)
NimBLEDevice::setPower(ESP_PWR_LVL_P9);
// Set security
NimBLEDevice::setSecurityAuth(false, false, true); // bonding=false, mitm=false, sc=true (Secure Connections)
NimBLEDevice::setSecurityIOCap(BLE_HS_IO_NO_INPUT_OUTPUT); // No input/output capability
NimBLEServer *pServer = NimBLEDevice::createServer();
pServer->setCallbacks(new MyServerCallbacks());
pServer->advertiseOnDisconnect(true); // Automatically restart advertising on disconnect
NimBLEService *pService = pServer->createService("181C");
// Create all characteristics with callbacks
MyCharCallbacks* charCallbacks = new MyCharCallbacks();
// 0x0000 - SSID List (READ + NOTIFY)
ssidListChar = pService->createCharacteristic(
"0000",
NIMBLE_PROPERTY::READ | NIMBLE_PROPERTY::NOTIFY
);
ssidListChar->createDescriptor("2902"); // Add BLE2902 descriptor for notifications
// 0x0001 - SSID (WRITE)
NimBLECharacteristic *ssidChar = pService->createCharacteristic(
"0001",
NIMBLE_PROPERTY::WRITE
);
ssidChar->setCallbacks(charCallbacks);
// 0x0002 - Password (WRITE)
NimBLECharacteristic *passChar = pService->createCharacteristic(
"0002",
NIMBLE_PROPERTY::WRITE
);
passChar->setCallbacks(charCallbacks);
// 0x0003 - Token (WRITE)
NimBLECharacteristic *tokenChar = pService->createCharacteristic(
"0003",
NIMBLE_PROPERTY::WRITE
);
tokenChar->setCallbacks(charCallbacks);
// 0x0004 - SSID Refresh (WRITE)
NimBLECharacteristic *ssidRefreshChar = pService->createCharacteristic(
"0004",
NIMBLE_PROPERTY::WRITE
);
ssidRefreshChar->setCallbacks(charCallbacks);
// 0x0005 - Connect Confirmation (READ + NOTIFY)
connectConfirmChar = pService->createCharacteristic(
"0005",
NIMBLE_PROPERTY::READ | NIMBLE_PROPERTY::NOTIFY
);
connectConfirmChar->createDescriptor("2902"); // Add BLE2902 descriptor for notifications
// Start
pService->start();
NimBLEAdvertising *pAdvertising = NimBLEDevice::getAdvertising();
pAdvertising->addServiceUUID("181C");
pAdvertising->setName("BlindMaster-C6");
pAdvertising->enableScanResponse(true);
pAdvertising->setPreferredParams(0x06, 0x12); // Connection interval preferences
pAdvertising->start();
printf("BLE Started. Waiting...\n");
flag_scan_requested = true;
return pAdvertising;
}
void BLEtick(NimBLEAdvertising* pAdvertising) {
if(flag_scan_requested) {
flag_scan_requested = false;
printf("Scanning WiFi...\n");
scanAndUpdateSSIDList();
}
}

77
include/BLE.hpp Normal file
View File

@@ -0,0 +1,77 @@
#ifndef BLE_H
#define BLE_H
#include "NimBLEDevice.h"
extern bool flag_scan_requested;
extern std::string SSID;
extern std::string password;
extern bool SSIDGiven;
extern bool passGiven;
extern std::string token;
extern bool tokenGiven;
// Global pointers to characteristics for notification support
extern NimBLECharacteristic* ssidListChar;
extern NimBLECharacteristic* connectConfirmChar;
class MyServerCallbacks : public NimBLEServerCallbacks {
void onConnect(NimBLEServer* pServer, NimBLEConnInfo& connInfo) {
printf("Client connected\n");
};
void onDisconnect(NimBLEServer* pServer, NimBLEConnInfo& connInfo, int reason) {
printf("Client disconnected - reason: %d\n", reason);
// Advertising will restart automatically
}
};
class MyCharCallbacks : public NimBLECharacteristicCallbacks {
void onWrite(NimBLECharacteristic* pChar, NimBLEConnInfo& connInfo) {
std::string val = pChar->getValue();
std::string uuidStr = pChar->getUUID().toString();
printf("onWrite called! UUID: %s, Value length: %d\n", uuidStr.c_str(), val.length());
// Check which characteristic was written to
if (uuidStr.find("0001") != std::string::npos) {
// SSID characteristic
if (val.length() > 0) {
printf("Received SSID: %s\n", val.c_str());
SSID = val;
SSIDGiven = true;
}
}
else if (uuidStr.find("0002") != std::string::npos) {
// Password characteristic
if (val.length() > 0) {
printf("Received Password: %s\n", val.c_str());
passGiven = true;
password = val;
}
}
else if (uuidStr.find("0003") != std::string::npos) {
// Token characteristic
if (val.length() > 0) {
printf("Received Token: %s\n", val.c_str());
tokenGiven = true;
token = val;
}
}
else if (uuidStr.find("0004") != std::string::npos) {
// Refresh characteristic
printf("Refresh Requested\n");
flag_scan_requested = true;
}
else {
printf("Unknown UUID: %s\n", uuidStr.c_str());
}
}
};
NimBLEAdvertising* initBLE();
void BLEtick(NimBLEAdvertising* pAdvertising);
#endif

37
include/README Normal file
View File

@@ -0,0 +1,37 @@
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 convention is to give header files names that end with `.h'.
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

78
include/WiFi.cpp Normal file
View File

@@ -0,0 +1,78 @@
#include "WiFi.hpp"
#include "esp_wifi.h"
#include "esp_event.h"
#include "cJSON.h" // Native replacement for ArduinoJson
#include "BLE.hpp"
void init_wifi() {
// 1. Init Network Interface
ESP_ERROR_CHECK(esp_netif_init());
ESP_ERROR_CHECK(esp_event_loop_create_default());
esp_netif_create_default_wifi_sta();
// 2. Init WiFi Driver
wifi_init_config_t cfg = WIFI_INIT_CONFIG_DEFAULT();
ESP_ERROR_CHECK(esp_wifi_init(&cfg));
// 3. Set Mode to Station (Client)
ESP_ERROR_CHECK(esp_wifi_set_mode(WIFI_MODE_STA));
ESP_ERROR_CHECK(esp_wifi_start());
}
void scanAndUpdateSSIDList() {
printf("Starting WiFi Scan...\n");
// 1. Start Scan (Blocking Mode = true)
// In blocking mode, this function waits here until scan is done (~2 seconds)
wifi_scan_config_t scan_config = {
.ssid = NULL,
.bssid = NULL,
.channel = 0,
.show_hidden = false
};
esp_err_t err = esp_wifi_scan_start(&scan_config, true);
if (err != ESP_OK) {
printf("Scan failed!\n");
return;
}
// 2. Get the results
uint16_t ap_count = 0;
esp_wifi_scan_get_ap_num(&ap_count);
// Limit to 10 networks to save RAM/BLE MTU space
if (ap_count > 10) ap_count = 10;
wifi_ap_record_t *ap_list = (wifi_ap_record_t *)malloc(sizeof(wifi_ap_record_t) * ap_count);
ESP_ERROR_CHECK(esp_wifi_scan_get_ap_records(&ap_count, ap_list));
// 3. Build JSON using cJSON
cJSON *root = cJSON_CreateArray();
for (int i = 0; i < ap_count; i++) {
cJSON *item = cJSON_CreateObject();
// ESP-IDF stores SSID as uint8_t, cast to char*
cJSON_AddStringToObject(item, "ssid", (char *)ap_list[i].ssid);
cJSON_AddNumberToObject(item, "rssi", ap_list[i].rssi);
// Add encryption type if you want (optional)
cJSON_AddNumberToObject(item, "auth", ap_list[i].authmode);
cJSON_AddItemToArray(root, item);
}
// 4. Convert to String
char *json_string = cJSON_PrintUnformatted(root); // Compact JSON
printf("JSON: %s\n", json_string);
// 5. Update BLE
if (ssidListChar != nullptr) {
ssidListChar->setValue(std::string(json_string));
ssidListChar->notify();
}
// 6. Cleanup Memory (CRITICAL in C++)
free(ap_list);
cJSON_Delete(root); // This deletes all children (items) too
free(json_string); // cJSON_Print allocates memory, you must free it
}

7
include/WiFi.hpp Normal file
View File

@@ -0,0 +1,7 @@
#ifndef WIFI_H
#define WIFI_H
void init_wifi();
void scanAndUpdateSSIDList();
#endif

38
include/defines.h Normal file
View File

@@ -0,0 +1,38 @@
#ifndef DEFINES_H
#define DEFINES_H
#define ccwSpeed 6500
#define cwSpeed 3300
#define offSpeed 4900
#define ccwMax 10
#define cwMax 0
#define getMovingCW(port) ((movingCW & (1 << port)) >> port)
#define setMovingCW(port) (movingCW |= (1 << port))
#define clearMovingCW(port) (movingCW &= ~(1 << port))
#define getMovingCCW(port) ((movingCCW & (1 << port)) >> port)
#define setMovingCCW(port) (movingCCW |= (1 << port))
#define clearMovingCCW(port) (movingCCW &= ~(1 << port))
#define getCalibCW(port) ((calibCW & (1 << port)) >> port)
#define setCalibCW(port) (calibCW |= (1 << port))
#define clearCalibCW(port) (calibCW &= ~(1 << port))
#define getCalibCCW(port) ((calibCCW & (1 << port)) >> port)
#define setCalibCCW(port) (calibCCW |= (1 << port))
#define clearCalibCCW(port) (calibCCW &= ~(1 << port))
#define getCalibDone(port) ((calibDone & (1 << port)) >> port)
#define setCalibDone(port) (calibDone |= (1 << port))
#define clearCalibDone(port) (calibDone &= ~(1 << port))
#define getBlocked(port) ((blocked & (1 << port)) >> port)
#define setBlocked(port) (blocked |= (1 << port))
#define clearBlocked(port) (blocked &= ~(1 << port))
#define getPos10(port) ((movingCCW & (1 << (port + 4))) >> (port + 4))
#define setPos10(port) (movingCCW |= (1 << (port + 4)))
#define clearPos10(port) (movingCCW &= ~(1 << (port + 4)))
#define getPos0(port) ((movingCW & (1 << (port + 4))) >> (port + 4))
#define setPos0(port) (movingCW |= (1 << (port + 4)))
#define clearPos0(port) (movingCW &= ~(1 << (port + 4)))
#define prefNameCalibs "periph_info_"
#endif

30
include/pwm.c Normal file
View File

@@ -0,0 +1,30 @@
#include "driver/ledc.h"
#include "defines.h"
void init_servo_PWM(void) {
ledc_timer_config_t ledc_timer = {
.speed_mode = LEDC_LOW_SPEED_MODE,
.timer_num = LEDC_TIMER_0,
.duty_resolution = LEDC_TIMER_16_BIT,
.freq_hz = 50,
.clk_cfg = LEDC_AUTO_CLK
};
ESP_ERROR_CHECK(ledc_timer_config(&ledc_timer));
ledc_channel_config_t ledc_channel = {
.speed_mode = LEDC_LOW_SPEED_MODE,
.channel = LEDC_CHANNEL_0,
.timer_sel = LEDC_TIMER_0,
.intr_type = LEDC_INTR_DISABLE,
.gpio_num = 0, // Using pin D0 for servo.
.duty = offSpeed, // Start off
.hpoint = 0
};
ESP_ERROR_CHECK(ledc_channel_config(&ledc_channel));
}
void set_servo_speed(int channel, uint32_t duty) {
// duty input should be between 0 and 65535
ledc_set_duty(LEDC_LOW_SPEED_MODE, (ledc_channel_t)channel, duty);
ledc_update_duty(LEDC_LOW_SPEED_MODE, (ledc_channel_t)channel);
}

7
include/pwm.h Normal file
View File

@@ -0,0 +1,7 @@
#ifndef PWM_H
#define PWM_H
void init_servo_PWM(void);
void set_servo_speed(int channel, int speed);
#endif

46
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 the executable file.
The source code of each library should be placed in a separate directory
("lib/your_library_name/[Code]").
For example, see the structure of the following example libraries `Foo` and `Bar`:
|--lib
| |
| |--Bar
| | |--docs
| | |--examples
| | |--src
| | |- Bar.c
| | |- Bar.h
| | |- library.json (optional. for 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
Example contents of `src/main.c` using Foo and Bar:
```
#include <Foo.h>
#include <Bar.h>
int main (void)
{
...
}
```
The PlatformIO Library Dependency Finder will find automatically dependent
libraries by scanning project source files.
More information about PlatformIO Library Dependency Finder
- https://docs.platformio.org/page/librarymanager/ldf.html

4
partitions.csv Normal file
View File

@@ -0,0 +1,4 @@
# Name, Type, SubType, Offset, Size, Flags
nvs, data, nvs, 0x9000, 0x6000,
phy_init, data, phy, 0xf000, 0x1000,
factory, app, factory, 0x10000, 0x3F0000,
1 # Name Type SubType Offset Size Flags
2 nvs data nvs 0x9000 0x6000
3 phy_init data phy 0xf000 0x1000
4 factory app factory 0x10000 0x3F0000

15
platformio.ini Normal file
View File

@@ -0,0 +1,15 @@
; 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:seeed_xiao_esp32c6]
platform = espressif32
board = seeed_xiao_esp32c6
framework = espidf
board_build.partitions = partitions.csv

2793
sdkconfig.seeed_xiao_esp32c6 Normal file

File diff suppressed because it is too large Load Diff

8
src/CMakeLists.txt Normal file
View File

@@ -0,0 +1,8 @@
# This file was automatically generated for projects
# without default 'CMakeLists.txt' file.
FILE(GLOB_RECURSE app_sources ${CMAKE_SOURCE_DIR}/src/*.* ${CMAKE_SOURCE_DIR}/include/*.cpp)
idf_component_register(SRCS ${app_sources}
INCLUDE_DIRS "."
REQUIRES nvs_flash esp-nimble-cpp)

25
src/main.cpp Normal file
View File

@@ -0,0 +1,25 @@
#include <driver/gptimer.h>
#include "pwm.h"
#include "defines.h"
#include "nvs_flash.h"
#include "NimBLEDevice.h"
#include "BLE.hpp"
#include "WiFi.hpp"
extern "C" void app_main() {
esp_err_t ret = nvs_flash_init();
// 2. If NVS is full or corrupt (common after flashing new code), erase and retry
if (ret == ESP_ERR_NVS_NO_FREE_PAGES || ret == ESP_ERR_NVS_NEW_VERSION_FOUND) {
ESP_ERROR_CHECK(nvs_flash_erase());
ret = nvs_flash_init();
}
ESP_ERROR_CHECK(ret);
init_wifi();
NimBLEAdvertising* pAdv = initBLE();
while (1) {
BLEtick(pAdv);
vTaskDelay(pdMS_TO_TICKS(10));
}
}

11
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