Merge pull request #756 from geekbozu/BLE_FS
BLE FS Using adafruits Simple (not fast) BLE FS Api
This commit is contained in:
commit
bccd77d5c9
|
@ -487,6 +487,7 @@ list(APPEND SOURCE_FILES
|
||||||
components/ble/NavigationService.cpp
|
components/ble/NavigationService.cpp
|
||||||
displayapp/fonts/lv_font_navi_80.c
|
displayapp/fonts/lv_font_navi_80.c
|
||||||
components/ble/BatteryInformationService.cpp
|
components/ble/BatteryInformationService.cpp
|
||||||
|
components/ble/FSService.cpp
|
||||||
components/ble/ImmediateAlertService.cpp
|
components/ble/ImmediateAlertService.cpp
|
||||||
components/ble/ServiceDiscovery.cpp
|
components/ble/ServiceDiscovery.cpp
|
||||||
components/ble/HeartRateService.cpp
|
components/ble/HeartRateService.cpp
|
||||||
|
@ -557,6 +558,7 @@ list(APPEND RECOVERY_SOURCE_FILES
|
||||||
components/ble/MusicService.cpp
|
components/ble/MusicService.cpp
|
||||||
components/ble/weather/WeatherService.cpp
|
components/ble/weather/WeatherService.cpp
|
||||||
components/ble/BatteryInformationService.cpp
|
components/ble/BatteryInformationService.cpp
|
||||||
|
components/ble/FSService.cpp
|
||||||
components/ble/ImmediateAlertService.cpp
|
components/ble/ImmediateAlertService.cpp
|
||||||
components/ble/ServiceDiscovery.cpp
|
components/ble/ServiceDiscovery.cpp
|
||||||
components/ble/NavigationService.cpp
|
components/ble/NavigationService.cpp
|
||||||
|
@ -669,6 +671,7 @@ set(INCLUDE_FILES
|
||||||
components/ble/DfuService.h
|
components/ble/DfuService.h
|
||||||
components/firmwarevalidator/FirmwareValidator.h
|
components/firmwarevalidator/FirmwareValidator.h
|
||||||
components/ble/BatteryInformationService.h
|
components/ble/BatteryInformationService.h
|
||||||
|
components/ble/FSService.h
|
||||||
components/ble/ImmediateAlertService.h
|
components/ble/ImmediateAlertService.h
|
||||||
components/ble/ServiceDiscovery.h
|
components/ble/ServiceDiscovery.h
|
||||||
components/ble/BleClient.h
|
components/ble/BleClient.h
|
||||||
|
|
330
src/components/ble/FSService.cpp
Normal file
330
src/components/ble/FSService.cpp
Normal file
|
@ -0,0 +1,330 @@
|
||||||
|
#include <nrf_log.h>
|
||||||
|
#include "FSService.h"
|
||||||
|
#include "components/ble/BleController.h"
|
||||||
|
#include "systemtask/SystemTask.h"
|
||||||
|
|
||||||
|
using namespace Pinetime::Controllers;
|
||||||
|
|
||||||
|
constexpr ble_uuid16_t FSService::fsServiceUuid;
|
||||||
|
constexpr ble_uuid128_t FSService::fsVersionUuid;
|
||||||
|
constexpr ble_uuid128_t FSService::fsTransferUuid;
|
||||||
|
|
||||||
|
int FSServiceCallback(uint16_t conn_handle, uint16_t attr_handle, struct ble_gatt_access_ctxt* ctxt, void* arg) {
|
||||||
|
auto* fsService = static_cast<FSService*>(arg);
|
||||||
|
return fsService->OnFSServiceRequested(conn_handle, attr_handle, ctxt);
|
||||||
|
}
|
||||||
|
|
||||||
|
FSService::FSService(Pinetime::System::SystemTask& systemTask, Pinetime::Controllers::FS& fs)
|
||||||
|
: systemTask {systemTask},
|
||||||
|
fs {fs},
|
||||||
|
characteristicDefinition {{.uuid = &fsVersionUuid.u,
|
||||||
|
.access_cb = FSServiceCallback,
|
||||||
|
.arg = this,
|
||||||
|
.flags = BLE_GATT_CHR_F_READ,
|
||||||
|
.val_handle = &versionCharacteristicHandle},
|
||||||
|
{
|
||||||
|
.uuid = &fsTransferUuid.u,
|
||||||
|
.access_cb = FSServiceCallback,
|
||||||
|
.arg = this,
|
||||||
|
.flags = BLE_GATT_CHR_F_WRITE | BLE_GATT_CHR_F_READ | BLE_GATT_CHR_F_NOTIFY,
|
||||||
|
.val_handle = &transferCharacteristicHandle,
|
||||||
|
},
|
||||||
|
{0}},
|
||||||
|
serviceDefinition {
|
||||||
|
{/* Device Information Service */
|
||||||
|
.type = BLE_GATT_SVC_TYPE_PRIMARY,
|
||||||
|
.uuid = &fsServiceUuid.u,
|
||||||
|
.characteristics = characteristicDefinition},
|
||||||
|
{0},
|
||||||
|
} {
|
||||||
|
}
|
||||||
|
|
||||||
|
void FSService::Init() {
|
||||||
|
int res = 0;
|
||||||
|
res = ble_gatts_count_cfg(serviceDefinition);
|
||||||
|
ASSERT(res == 0);
|
||||||
|
|
||||||
|
res = ble_gatts_add_svcs(serviceDefinition);
|
||||||
|
ASSERT(res == 0);
|
||||||
|
}
|
||||||
|
|
||||||
|
int FSService::OnFSServiceRequested(uint16_t connectionHandle, uint16_t attributeHandle, ble_gatt_access_ctxt* context) {
|
||||||
|
if (attributeHandle == versionCharacteristicHandle) {
|
||||||
|
NRF_LOG_INFO("FS_S : handle = %d", versionCharacteristicHandle);
|
||||||
|
int res = os_mbuf_append(context->om, &fsVersion, sizeof(fsVersion));
|
||||||
|
return (res == 0) ? 0 : BLE_ATT_ERR_INSUFFICIENT_RES;
|
||||||
|
}
|
||||||
|
if (attributeHandle == transferCharacteristicHandle) {
|
||||||
|
return FSCommandHandler(connectionHandle, context->om);
|
||||||
|
}
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
int FSService::FSCommandHandler(uint16_t connectionHandle, os_mbuf* om) {
|
||||||
|
auto command = static_cast<commands>(om->om_data[0]);
|
||||||
|
NRF_LOG_INFO("[FS_S] -> FSCommandHandler Command %d", command);
|
||||||
|
// Just always make sure we are awake...
|
||||||
|
systemTask.PushMessage(Pinetime::System::Messages::StartFileTransfer);
|
||||||
|
vTaskDelay(10);
|
||||||
|
while (systemTask.IsSleeping()) {
|
||||||
|
vTaskDelay(100); // 50ms
|
||||||
|
}
|
||||||
|
lfs_dir_t dir = {0};
|
||||||
|
lfs_info info = {0};
|
||||||
|
lfs_file f = {0};
|
||||||
|
switch (command) {
|
||||||
|
case commands::READ: {
|
||||||
|
NRF_LOG_INFO("[FS_S] -> Read");
|
||||||
|
auto* header = (ReadHeader*) om->om_data;
|
||||||
|
uint16_t plen = header->pathlen;
|
||||||
|
if (plen > maxpathlen) { //> counts for null term
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
|
memcpy(filepath, header->pathstr, plen);
|
||||||
|
filepath[plen] = 0; // Copy and null teminate string
|
||||||
|
ReadResponse resp;
|
||||||
|
os_mbuf* om;
|
||||||
|
resp.command = commands::READ_DATA;
|
||||||
|
resp.status = 0x01;
|
||||||
|
resp.chunkoff = header->chunkoff;
|
||||||
|
int res = fs.Stat(filepath, &info);
|
||||||
|
if (res == LFS_ERR_NOENT && info.type != LFS_TYPE_DIR) {
|
||||||
|
resp.status = (int8_t) res;
|
||||||
|
resp.chunklen = 0;
|
||||||
|
resp.totallen = 0;
|
||||||
|
om = ble_hs_mbuf_from_flat(&resp, sizeof(ReadResponse));
|
||||||
|
} else {
|
||||||
|
resp.chunklen = std::min(header->chunksize, info.size); // TODO add mtu somehow
|
||||||
|
resp.totallen = info.size;
|
||||||
|
fs.FileOpen(&f, filepath, LFS_O_RDONLY);
|
||||||
|
fs.FileSeek(&f, header->chunkoff);
|
||||||
|
uint8_t fileData[resp.chunklen] = {0};
|
||||||
|
resp.chunklen = fs.FileRead(&f, fileData, resp.chunklen);
|
||||||
|
om = ble_hs_mbuf_from_flat(&resp, sizeof(ReadResponse));
|
||||||
|
os_mbuf_append(om, fileData, resp.chunklen);
|
||||||
|
fs.FileClose(&f);
|
||||||
|
}
|
||||||
|
|
||||||
|
ble_gattc_notify_custom(connectionHandle, transferCharacteristicHandle, om);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
case commands::READ_PACING: {
|
||||||
|
NRF_LOG_INFO("[FS_S] -> Readpacing");
|
||||||
|
auto* header = (ReadHeader*) om->om_data;
|
||||||
|
ReadResponse resp;
|
||||||
|
resp.command = commands::READ_DATA;
|
||||||
|
resp.status = 0x01;
|
||||||
|
resp.chunkoff = header->chunkoff;
|
||||||
|
int res = fs.Stat(filepath, &info);
|
||||||
|
if (res == LFS_ERR_NOENT && info.type != LFS_TYPE_DIR) {
|
||||||
|
resp.status = (int8_t) res;
|
||||||
|
resp.chunklen = 0;
|
||||||
|
resp.totallen = 0;
|
||||||
|
} else {
|
||||||
|
resp.chunklen = std::min(header->chunksize, info.size); // TODO add mtu somehow
|
||||||
|
resp.totallen = info.size;
|
||||||
|
fs.FileOpen(&f, filepath, LFS_O_RDONLY);
|
||||||
|
fs.FileSeek(&f, header->chunkoff);
|
||||||
|
}
|
||||||
|
os_mbuf* om;
|
||||||
|
if (resp.chunklen > 0) {
|
||||||
|
uint8_t fileData[resp.chunklen] = {0};
|
||||||
|
resp.chunklen = fs.FileRead(&f, fileData, resp.chunklen);
|
||||||
|
om = ble_hs_mbuf_from_flat(&resp, sizeof(ReadResponse));
|
||||||
|
os_mbuf_append(om, fileData, resp.chunklen);
|
||||||
|
} else {
|
||||||
|
resp.chunklen = 0;
|
||||||
|
om = ble_hs_mbuf_from_flat(&resp, sizeof(ReadResponse));
|
||||||
|
}
|
||||||
|
fs.FileClose(&f);
|
||||||
|
ble_gattc_notify_custom(connectionHandle, transferCharacteristicHandle, om);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
case commands::WRITE: {
|
||||||
|
NRF_LOG_INFO("[FS_S] -> Write");
|
||||||
|
auto* header = (WriteHeader*) om->om_data;
|
||||||
|
uint16_t plen = header->pathlen;
|
||||||
|
if (plen > maxpathlen) { //> counts for null term
|
||||||
|
return -1; // TODO make this actually return a BLE notif
|
||||||
|
}
|
||||||
|
memcpy(filepath, header->pathstr, plen);
|
||||||
|
filepath[plen] = 0; // Copy and null teminate string
|
||||||
|
fileSize = header->totalSize;
|
||||||
|
WriteResponse resp;
|
||||||
|
resp.command = commands::WRITE_PACING;
|
||||||
|
resp.offset = header->offset;
|
||||||
|
resp.modTime = 0;
|
||||||
|
|
||||||
|
int res = fs.FileOpen(&f, filepath, LFS_O_RDWR | LFS_O_CREAT);
|
||||||
|
if (res == 0) {
|
||||||
|
fs.FileClose(&f);
|
||||||
|
resp.status = (res == 0) ? 0x01 : (int8_t) res;
|
||||||
|
}
|
||||||
|
resp.freespace = std::min(fs.getSize() - (fs.GetFSSize() * fs.getBlockSize()), fileSize - header->offset);
|
||||||
|
auto* om = ble_hs_mbuf_from_flat(&resp, sizeof(WriteResponse));
|
||||||
|
ble_gattc_notify_custom(connectionHandle, transferCharacteristicHandle, om);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
case commands::WRITE_DATA: {
|
||||||
|
NRF_LOG_INFO("[FS_S] -> WriteData");
|
||||||
|
auto* header = (WritePacing*) om->om_data;
|
||||||
|
WriteResponse resp;
|
||||||
|
resp.command = commands::WRITE_PACING;
|
||||||
|
resp.offset = header->offset;
|
||||||
|
int res = 0;
|
||||||
|
|
||||||
|
if (!(res = fs.FileOpen(&f, filepath, LFS_O_RDWR | LFS_O_CREAT))) {
|
||||||
|
if ((res = fs.FileSeek(&f, header->offset)) >= 0) {
|
||||||
|
res = fs.FileWrite(&f, header->data, header->dataSize);
|
||||||
|
}
|
||||||
|
fs.FileClose(&f);
|
||||||
|
}
|
||||||
|
if (res < 0) {
|
||||||
|
resp.status = (int8_t) res;
|
||||||
|
}
|
||||||
|
resp.freespace = std::min(fs.getSize() - (fs.GetFSSize() * fs.getBlockSize()), fileSize - header->offset);
|
||||||
|
auto* om = ble_hs_mbuf_from_flat(&resp, sizeof(WriteResponse));
|
||||||
|
ble_gattc_notify_custom(connectionHandle, transferCharacteristicHandle, om);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
case commands::DELETE: {
|
||||||
|
NRF_LOG_INFO("[FS_S] -> Delete");
|
||||||
|
auto* header = (DelHeader*) om->om_data;
|
||||||
|
uint16_t plen = header->pathlen;
|
||||||
|
char path[plen + 1] = {0};
|
||||||
|
memcpy(path, header->pathstr, plen);
|
||||||
|
path[plen] = 0; // Copy and null teminate string
|
||||||
|
DelResponse resp {};
|
||||||
|
resp.command = commands::DELETE_STATUS;
|
||||||
|
int res = fs.FileDelete(path);
|
||||||
|
resp.status = (res == 0) ? 0x01 : (int8_t) res;
|
||||||
|
auto* om = ble_hs_mbuf_from_flat(&resp, sizeof(DelResponse));
|
||||||
|
ble_gattc_notify_custom(connectionHandle, transferCharacteristicHandle, om);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
case commands::MKDIR: {
|
||||||
|
NRF_LOG_INFO("[FS_S] -> MKDir");
|
||||||
|
auto* header = (MKDirHeader*) om->om_data;
|
||||||
|
uint16_t plen = header->pathlen;
|
||||||
|
char path[plen + 1] = {0};
|
||||||
|
memcpy(path, header->pathstr, plen);
|
||||||
|
path[plen] = 0; // Copy and null teminate string
|
||||||
|
MKDirResponse resp {};
|
||||||
|
resp.command = commands::MKDIR_STATUS;
|
||||||
|
resp.modification_time = 0;
|
||||||
|
int res = fs.DirCreate(path);
|
||||||
|
resp.status = (res == 0) ? 0x01 : (int8_t) res;
|
||||||
|
auto* om = ble_hs_mbuf_from_flat(&resp, sizeof(MKDirResponse));
|
||||||
|
ble_gattc_notify_custom(connectionHandle, transferCharacteristicHandle, om);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
case commands::LISTDIR: {
|
||||||
|
NRF_LOG_INFO("[FS_S] -> ListDir");
|
||||||
|
ListDirHeader* header = (ListDirHeader*) om->om_data;
|
||||||
|
uint16_t plen = header->pathlen;
|
||||||
|
char path[plen + 1] = {0};
|
||||||
|
path[plen] = 0; // Copy and null teminate string
|
||||||
|
memcpy(path, header->pathstr, plen);
|
||||||
|
|
||||||
|
ListDirResponse resp {};
|
||||||
|
|
||||||
|
resp.command = commands::LISTDIR_ENTRY;
|
||||||
|
resp.status = 0x01;
|
||||||
|
resp.totalentries = 0;
|
||||||
|
resp.entry = 0;
|
||||||
|
resp.modification_time = 0;
|
||||||
|
int res = fs.DirOpen(path, &dir);
|
||||||
|
if (res != 0) {
|
||||||
|
resp.status = (int8_t) res;
|
||||||
|
auto* om = ble_hs_mbuf_from_flat(&resp, sizeof(ListDirResponse));
|
||||||
|
ble_gattc_notify_custom(connectionHandle, transferCharacteristicHandle, om);
|
||||||
|
break;
|
||||||
|
};
|
||||||
|
while (fs.DirRead(&dir, &info)) {
|
||||||
|
resp.totalentries++;
|
||||||
|
}
|
||||||
|
fs.DirRewind(&dir);
|
||||||
|
while (true) {
|
||||||
|
res = fs.DirRead(&dir, &info);
|
||||||
|
if (res <= 0) {
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
switch (info.type) {
|
||||||
|
case LFS_TYPE_REG: {
|
||||||
|
resp.flags = 0;
|
||||||
|
resp.file_size = info.size;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
case LFS_TYPE_DIR: {
|
||||||
|
resp.flags = 1;
|
||||||
|
resp.file_size = 0;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// strcpy(resp.path, info.name);
|
||||||
|
resp.path_length = strlen(info.name);
|
||||||
|
auto* om = ble_hs_mbuf_from_flat(&resp, sizeof(ListDirResponse));
|
||||||
|
os_mbuf_append(om, info.name, resp.path_length);
|
||||||
|
ble_gattc_notify_custom(connectionHandle, transferCharacteristicHandle, om);
|
||||||
|
/*
|
||||||
|
* Todo Figure out how to know when the previous Notify was TX'd
|
||||||
|
* For now just delay 100ms to make sure that the data went out...
|
||||||
|
*/
|
||||||
|
vTaskDelay(100); // Allow stuff to actually go out over the BLE conn
|
||||||
|
resp.entry++;
|
||||||
|
}
|
||||||
|
assert(fs.DirClose(&dir) == 0);
|
||||||
|
resp.file_size = 0;
|
||||||
|
resp.path_length = 0;
|
||||||
|
resp.flags = 0;
|
||||||
|
auto* om = ble_hs_mbuf_from_flat(&resp, sizeof(ListDirResponse));
|
||||||
|
ble_gattc_notify_custom(connectionHandle, transferCharacteristicHandle, om);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
case commands::MOVE: {
|
||||||
|
NRF_LOG_INFO("[FS_S] -> Move");
|
||||||
|
MoveHeader* header = (MoveHeader*) om->om_data;
|
||||||
|
uint16_t plen = header->OldPathLength;
|
||||||
|
// Null Terminate string
|
||||||
|
header->pathstr[plen] = 0;
|
||||||
|
char path[header->NewPathLength + 1] = {0};
|
||||||
|
memcpy(path, &header->pathstr[plen + 1], header->NewPathLength);
|
||||||
|
path[header->NewPathLength] = 0; // Copy and null teminate string
|
||||||
|
MoveResponse resp {};
|
||||||
|
resp.command = commands::MOVE_STATUS;
|
||||||
|
int8_t res = (int8_t) fs.Rename(header->pathstr, path);
|
||||||
|
resp.status = (res == 0) ? 1 : res;
|
||||||
|
auto* om = ble_hs_mbuf_from_flat(&resp, sizeof(MoveResponse));
|
||||||
|
ble_gattc_notify_custom(connectionHandle, transferCharacteristicHandle, om);
|
||||||
|
}
|
||||||
|
default:
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
NRF_LOG_INFO("[FS_S] -> done ");
|
||||||
|
systemTask.PushMessage(Pinetime::System::Messages::StopFileTransfer);
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Loads resp with file data given a valid filepath header and resp
|
||||||
|
void FSService::prepareReadDataResp(ReadHeader* header, ReadResponse* resp) {
|
||||||
|
// uint16_t plen = header->pathlen;
|
||||||
|
resp->command = commands::READ_DATA;
|
||||||
|
resp->chunkoff = header->chunkoff;
|
||||||
|
resp->status = 0x01;
|
||||||
|
struct lfs_info info = {};
|
||||||
|
int res = fs.Stat(filepath, &info);
|
||||||
|
if (res == LFS_ERR_NOENT && info.type != LFS_TYPE_DIR) {
|
||||||
|
resp->status = 0x03;
|
||||||
|
resp->chunklen = 0;
|
||||||
|
resp->totallen = 0;
|
||||||
|
} else {
|
||||||
|
lfs_file f;
|
||||||
|
resp->chunklen = std::min(header->chunksize, info.size);
|
||||||
|
resp->totallen = info.size;
|
||||||
|
fs.FileOpen(&f, filepath, LFS_O_RDONLY);
|
||||||
|
fs.FileSeek(&f, header->chunkoff);
|
||||||
|
resp->chunklen = fs.FileRead(&f, resp->chunk, resp->chunklen);
|
||||||
|
fs.FileClose(&f);
|
||||||
|
}
|
||||||
|
}
|
191
src/components/ble/FSService.h
Normal file
191
src/components/ble/FSService.h
Normal file
|
@ -0,0 +1,191 @@
|
||||||
|
#pragma once
|
||||||
|
#define min // workaround: nimble's min/max macros conflict with libstdc++
|
||||||
|
#define max
|
||||||
|
#include <host/ble_gap.h>
|
||||||
|
#undef max
|
||||||
|
#undef min
|
||||||
|
|
||||||
|
#include "components/fs/FS.h"
|
||||||
|
|
||||||
|
namespace Pinetime {
|
||||||
|
namespace System {
|
||||||
|
class SystemTask;
|
||||||
|
}
|
||||||
|
namespace Controllers {
|
||||||
|
class Ble;
|
||||||
|
class FSService {
|
||||||
|
public:
|
||||||
|
FSService(Pinetime::System::SystemTask& systemTask, Pinetime::Controllers::FS& fs);
|
||||||
|
void Init();
|
||||||
|
|
||||||
|
int OnFSServiceRequested(uint16_t connectionHandle, uint16_t attributeHandle, ble_gatt_access_ctxt* context);
|
||||||
|
void NotifyFSRaw(uint16_t connectionHandle);
|
||||||
|
|
||||||
|
private:
|
||||||
|
Pinetime::System::SystemTask& systemTask;
|
||||||
|
Pinetime::Controllers::FS& fs;
|
||||||
|
static constexpr uint16_t FSServiceId {0xFEBB};
|
||||||
|
static constexpr uint16_t fsVersionId {0x0100};
|
||||||
|
static constexpr uint16_t fsTransferId {0x0200};
|
||||||
|
uint16_t fsVersion = {0x0004};
|
||||||
|
static constexpr uint16_t maxpathlen = 256;
|
||||||
|
static constexpr ble_uuid16_t fsServiceUuid {
|
||||||
|
.u {.type = BLE_UUID_TYPE_16},
|
||||||
|
.value = {0xFEBB}}; // {0x72, 0x65, 0x66, 0x73, 0x6e, 0x61, 0x72, 0x54, 0x65, 0x6c, 0x69, 0x46, 0xBB, 0xFE, 0xAF, 0xAD}};
|
||||||
|
|
||||||
|
static constexpr ble_uuid128_t fsVersionUuid {
|
||||||
|
.u {.type = BLE_UUID_TYPE_128},
|
||||||
|
.value = {0x72, 0x65, 0x66, 0x73, 0x6e, 0x61, 0x72, 0x54, 0x65, 0x6c, 0x69, 0x46, 0x00, 0x01, 0xAF, 0xAD}};
|
||||||
|
|
||||||
|
static constexpr ble_uuid128_t fsTransferUuid {
|
||||||
|
.u {.type = BLE_UUID_TYPE_128},
|
||||||
|
.value = {0x72, 0x65, 0x66, 0x73, 0x6e, 0x61, 0x72, 0x54, 0x65, 0x6c, 0x69, 0x46, 0x00, 0x02, 0xAF, 0xAD}};
|
||||||
|
|
||||||
|
struct ble_gatt_chr_def characteristicDefinition[3];
|
||||||
|
struct ble_gatt_svc_def serviceDefinition[2];
|
||||||
|
uint16_t versionCharacteristicHandle;
|
||||||
|
uint16_t transferCharacteristicHandle;
|
||||||
|
|
||||||
|
enum class commands : uint8_t {
|
||||||
|
INVALID = 0x00,
|
||||||
|
READ = 0x10,
|
||||||
|
READ_DATA = 0x11,
|
||||||
|
READ_PACING = 0x12,
|
||||||
|
WRITE = 0x20,
|
||||||
|
WRITE_PACING = 0x21,
|
||||||
|
WRITE_DATA = 0x22,
|
||||||
|
DELETE = 0x30,
|
||||||
|
DELETE_STATUS = 0x31,
|
||||||
|
MKDIR = 0x40,
|
||||||
|
MKDIR_STATUS = 0x41,
|
||||||
|
LISTDIR = 0x50,
|
||||||
|
LISTDIR_ENTRY = 0x51,
|
||||||
|
MOVE = 0x60,
|
||||||
|
MOVE_STATUS = 0x61
|
||||||
|
};
|
||||||
|
enum class FSState : uint8_t {
|
||||||
|
IDLE = 0x00,
|
||||||
|
READ = 0x01,
|
||||||
|
WRITE = 0x02,
|
||||||
|
};
|
||||||
|
FSState state;
|
||||||
|
char filepath[maxpathlen]; // TODO ..ugh fixed filepath len
|
||||||
|
int fileSize;
|
||||||
|
using ReadHeader = struct __attribute__((packed)) {
|
||||||
|
commands command;
|
||||||
|
uint8_t padding;
|
||||||
|
uint16_t pathlen;
|
||||||
|
uint32_t chunkoff;
|
||||||
|
uint32_t chunksize;
|
||||||
|
char pathstr[];
|
||||||
|
};
|
||||||
|
|
||||||
|
using ReadResponse = struct __attribute__((packed)) {
|
||||||
|
commands command;
|
||||||
|
uint8_t status;
|
||||||
|
uint16_t padding;
|
||||||
|
uint32_t chunkoff;
|
||||||
|
uint32_t totallen;
|
||||||
|
uint32_t chunklen;
|
||||||
|
uint8_t chunk[];
|
||||||
|
};
|
||||||
|
using ReadPacing = struct __attribute__((packed)) {
|
||||||
|
commands command;
|
||||||
|
uint8_t status;
|
||||||
|
uint16_t padding;
|
||||||
|
uint32_t chunkoff;
|
||||||
|
uint32_t chunksize;
|
||||||
|
};
|
||||||
|
|
||||||
|
using WriteHeader = struct __attribute__((packed)) {
|
||||||
|
commands command;
|
||||||
|
uint8_t padding;
|
||||||
|
uint16_t pathlen;
|
||||||
|
uint32_t offset;
|
||||||
|
uint64_t modTime;
|
||||||
|
uint32_t totalSize;
|
||||||
|
char pathstr[];
|
||||||
|
};
|
||||||
|
|
||||||
|
using WriteResponse = struct __attribute__((packed)) {
|
||||||
|
commands command;
|
||||||
|
uint8_t status;
|
||||||
|
uint16_t padding;
|
||||||
|
uint32_t offset;
|
||||||
|
uint64_t modTime;
|
||||||
|
uint32_t freespace;
|
||||||
|
};
|
||||||
|
|
||||||
|
using WritePacing = struct __attribute__((packed)) {
|
||||||
|
commands command;
|
||||||
|
uint8_t status;
|
||||||
|
uint16_t padding;
|
||||||
|
uint32_t offset;
|
||||||
|
uint32_t dataSize;
|
||||||
|
uint8_t data[];
|
||||||
|
};
|
||||||
|
using ListDirHeader = struct __attribute__((packed)) {
|
||||||
|
commands command;
|
||||||
|
uint8_t padding;
|
||||||
|
uint16_t pathlen;
|
||||||
|
char pathstr[];
|
||||||
|
};
|
||||||
|
|
||||||
|
using ListDirResponse = struct __attribute__((packed)) {
|
||||||
|
commands command;
|
||||||
|
uint8_t status;
|
||||||
|
uint16_t path_length;
|
||||||
|
uint32_t entry;
|
||||||
|
uint32_t totalentries;
|
||||||
|
uint32_t flags;
|
||||||
|
uint64_t modification_time;
|
||||||
|
uint32_t file_size;
|
||||||
|
char path[];
|
||||||
|
};
|
||||||
|
|
||||||
|
using MKDirHeader = struct __attribute__((packed)) {
|
||||||
|
commands command;
|
||||||
|
uint8_t padding;
|
||||||
|
uint16_t pathlen;
|
||||||
|
uint32_t padding2;
|
||||||
|
uint64_t time;
|
||||||
|
char pathstr[];
|
||||||
|
};
|
||||||
|
|
||||||
|
using MKDirResponse = struct __attribute__((packed)) {
|
||||||
|
commands command;
|
||||||
|
uint8_t status;
|
||||||
|
uint32_t padding1;
|
||||||
|
uint16_t padding2;
|
||||||
|
uint64_t modification_time;
|
||||||
|
};
|
||||||
|
|
||||||
|
using DelHeader = struct __attribute__((packed)) {
|
||||||
|
commands command;
|
||||||
|
uint8_t padding;
|
||||||
|
uint16_t pathlen;
|
||||||
|
char pathstr[];
|
||||||
|
};
|
||||||
|
|
||||||
|
using DelResponse = struct __attribute__((packed)) {
|
||||||
|
commands command;
|
||||||
|
uint8_t status;
|
||||||
|
};
|
||||||
|
using MoveHeader = struct __attribute__((packed)) {
|
||||||
|
commands command;
|
||||||
|
uint8_t padding;
|
||||||
|
uint16_t OldPathLength;
|
||||||
|
uint16_t NewPathLength;
|
||||||
|
char pathstr[];
|
||||||
|
};
|
||||||
|
|
||||||
|
using MoveResponse = struct __attribute__((packed)) {
|
||||||
|
commands command;
|
||||||
|
uint8_t status;
|
||||||
|
};
|
||||||
|
|
||||||
|
int FSCommandHandler(uint16_t connectionHandle, os_mbuf* om);
|
||||||
|
void prepareReadDataResp(ReadHeader* header, ReadResponse* resp);
|
||||||
|
};
|
||||||
|
}
|
||||||
|
}
|
|
@ -30,7 +30,7 @@ NimbleController::NimbleController(Pinetime::System::SystemTask& systemTask,
|
||||||
Pinetime::Drivers::SpiNorFlash& spiNorFlash,
|
Pinetime::Drivers::SpiNorFlash& spiNorFlash,
|
||||||
Controllers::HeartRateController& heartRateController,
|
Controllers::HeartRateController& heartRateController,
|
||||||
Controllers::MotionController& motionController,
|
Controllers::MotionController& motionController,
|
||||||
Pinetime::Controllers::FS& fs)
|
Controllers::FS& fs)
|
||||||
: systemTask {systemTask},
|
: systemTask {systemTask},
|
||||||
bleController {bleController},
|
bleController {bleController},
|
||||||
dateTimeController {dateTimeController},
|
dateTimeController {dateTimeController},
|
||||||
|
@ -50,6 +50,7 @@ NimbleController::NimbleController(Pinetime::System::SystemTask& systemTask,
|
||||||
immediateAlertService {systemTask, notificationManager},
|
immediateAlertService {systemTask, notificationManager},
|
||||||
heartRateService {systemTask, heartRateController},
|
heartRateService {systemTask, heartRateController},
|
||||||
motionService {systemTask, motionController},
|
motionService {systemTask, motionController},
|
||||||
|
fsService {systemTask, fs},
|
||||||
serviceDiscovery({¤tTimeClient, &alertNotificationClient}) {
|
serviceDiscovery({¤tTimeClient, &alertNotificationClient}) {
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -97,6 +98,7 @@ void NimbleController::Init() {
|
||||||
immediateAlertService.Init();
|
immediateAlertService.Init();
|
||||||
heartRateService.Init();
|
heartRateService.Init();
|
||||||
motionService.Init();
|
motionService.Init();
|
||||||
|
fsService.Init();
|
||||||
|
|
||||||
int rc;
|
int rc;
|
||||||
rc = ble_hs_util_ensure_addr(0);
|
rc = ble_hs_util_ensure_addr(0);
|
||||||
|
|
|
@ -22,6 +22,7 @@
|
||||||
#include "components/ble/MotionService.h"
|
#include "components/ble/MotionService.h"
|
||||||
#include "components/ble/weather/WeatherService.h"
|
#include "components/ble/weather/WeatherService.h"
|
||||||
#include "components/fs/FS.h"
|
#include "components/fs/FS.h"
|
||||||
|
#include "components/ble/FSService.h"
|
||||||
|
|
||||||
namespace Pinetime {
|
namespace Pinetime {
|
||||||
namespace Drivers {
|
namespace Drivers {
|
||||||
|
@ -110,6 +111,7 @@ namespace Pinetime {
|
||||||
HeartRateService heartRateService;
|
HeartRateService heartRateService;
|
||||||
MotionService motionService;
|
MotionService motionService;
|
||||||
ServiceDiscovery serviceDiscovery;
|
ServiceDiscovery serviceDiscovery;
|
||||||
|
FSService fsService;
|
||||||
|
|
||||||
uint8_t addrType;
|
uint8_t addrType;
|
||||||
uint16_t connectionHandle = BLE_HS_CONN_HANDLE_NONE;
|
uint16_t connectionHandle = BLE_HS_CONN_HANDLE_NONE;
|
||||||
|
|
|
@ -5,29 +5,28 @@
|
||||||
|
|
||||||
using namespace Pinetime::Controllers;
|
using namespace Pinetime::Controllers;
|
||||||
|
|
||||||
FS::FS(Pinetime::Drivers::SpiNorFlash& driver) :
|
FS::FS(Pinetime::Drivers::SpiNorFlash& driver)
|
||||||
flashDriver{ driver },
|
: flashDriver {driver},
|
||||||
lfsConfig{
|
lfsConfig {
|
||||||
.context = this,
|
.context = this,
|
||||||
.read = SectorRead,
|
.read = SectorRead,
|
||||||
.prog = SectorProg,
|
.prog = SectorProg,
|
||||||
.erase = SectorErase,
|
.erase = SectorErase,
|
||||||
.sync = SectorSync,
|
.sync = SectorSync,
|
||||||
|
|
||||||
.read_size = 16,
|
.read_size = 16,
|
||||||
.prog_size = 8,
|
.prog_size = 8,
|
||||||
.block_size = blockSize,
|
.block_size = blockSize,
|
||||||
.block_count = size / blockSize,
|
.block_count = size / blockSize,
|
||||||
.block_cycles = 1000u,
|
.block_cycles = 1000u,
|
||||||
|
|
||||||
.cache_size = 16,
|
.cache_size = 16,
|
||||||
.lookahead_size = 16,
|
.lookahead_size = 16,
|
||||||
|
|
||||||
.name_max = 50,
|
|
||||||
.attr_max = 50,
|
|
||||||
}
|
|
||||||
{ }
|
|
||||||
|
|
||||||
|
.name_max = 50,
|
||||||
|
.attr_max = 50,
|
||||||
|
} {
|
||||||
|
}
|
||||||
|
|
||||||
void FS::Init() {
|
void FS::Init() {
|
||||||
|
|
||||||
|
@ -48,7 +47,6 @@ void FS::Init() {
|
||||||
VerifyResource();
|
VerifyResource();
|
||||||
LVGLFileSystemInit();
|
LVGLFileSystemInit();
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
void FS::VerifyResource() {
|
void FS::VerifyResource() {
|
||||||
|
@ -56,7 +54,7 @@ void FS::VerifyResource() {
|
||||||
resourcesValid = true;
|
resourcesValid = true;
|
||||||
}
|
}
|
||||||
|
|
||||||
int FS::FileOpen(lfs_file_t* file_p, const char* fileName, const int flags) {
|
int FS::FileOpen(lfs_file_t* file_p, const char* fileName, const int flags) {
|
||||||
return lfs_file_open(&lfs, file_p, fileName, flags);
|
return lfs_file_open(&lfs, file_p, fileName, flags);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -80,27 +78,31 @@ int FS::FileDelete(const char* fileName) {
|
||||||
return lfs_remove(&lfs, fileName);
|
return lfs_remove(&lfs, fileName);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
int FS::DirOpen(const char* path, lfs_dir_t* lfs_dir) {
|
||||||
|
return lfs_dir_open(&lfs, lfs_dir, path);
|
||||||
|
}
|
||||||
|
|
||||||
|
int FS::DirClose(lfs_dir_t* lfs_dir) {
|
||||||
|
return lfs_dir_close(&lfs, lfs_dir);
|
||||||
|
}
|
||||||
|
|
||||||
|
int FS::DirRead(lfs_dir_t* dir, lfs_info* info) {
|
||||||
|
return lfs_dir_read(&lfs, dir, info);
|
||||||
|
}
|
||||||
|
int FS::DirRewind(lfs_dir_t* dir) {
|
||||||
|
return lfs_dir_rewind(&lfs, dir);
|
||||||
|
}
|
||||||
int FS::DirCreate(const char* path) {
|
int FS::DirCreate(const char* path) {
|
||||||
return lfs_mkdir(&lfs, path);
|
return lfs_mkdir(&lfs, path);
|
||||||
}
|
}
|
||||||
|
int FS::Rename(const char* oldPath, const char* newPath){
|
||||||
// Delete directory and all files inside
|
return lfs_rename(&lfs,oldPath,newPath);
|
||||||
int FS::DirDelete(const char* path) {
|
}
|
||||||
|
int FS::Stat(const char* path, lfs_info* info) {
|
||||||
lfs_dir_t lfs_dir;
|
return lfs_stat(&lfs, path, info);
|
||||||
lfs_info entryInfo;
|
}
|
||||||
|
lfs_ssize_t FS::GetFSSize() {
|
||||||
int err;
|
return lfs_fs_size(&lfs);
|
||||||
err = lfs_dir_open(&lfs, &lfs_dir, path);
|
|
||||||
if (err) {
|
|
||||||
return err;
|
|
||||||
}
|
|
||||||
while (lfs_dir_read(&lfs, &lfs_dir, &entryInfo)) {
|
|
||||||
lfs_remove(&lfs, entryInfo.name);
|
|
||||||
}
|
|
||||||
lfs_dir_close(&lfs, &lfs_dir);
|
|
||||||
return LFS_ERR_OK;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/*
|
/*
|
||||||
|
@ -141,17 +143,17 @@ int FS::SectorRead(const struct lfs_config* c, lfs_block_t block, lfs_off_t off,
|
||||||
|
|
||||||
namespace {
|
namespace {
|
||||||
lv_fs_res_t lvglOpen(lv_fs_drv_t* drv, void* file_p, const char* path, lv_fs_mode_t mode) {
|
lv_fs_res_t lvglOpen(lv_fs_drv_t* drv, void* file_p, const char* path, lv_fs_mode_t mode) {
|
||||||
|
|
||||||
lfs_file_t* file = static_cast<lfs_file_t*>(file_p);
|
lfs_file_t* file = static_cast<lfs_file_t*>(file_p);
|
||||||
FS* filesys = static_cast<FS*>(drv->user_data);
|
FS* filesys = static_cast<FS*>(drv->user_data);
|
||||||
filesys->FileOpen(file, path, LFS_O_RDONLY);
|
int res = filesys->FileOpen(file, path, LFS_O_RDONLY);
|
||||||
|
if (res == 0) {
|
||||||
if (file->type == 0) {
|
if (file->type == 0) {
|
||||||
return LV_FS_RES_FS_ERR;
|
return LV_FS_RES_FS_ERR;
|
||||||
}
|
} else {
|
||||||
else {
|
return LV_FS_RES_OK;
|
||||||
return LV_FS_RES_OK;
|
}
|
||||||
}
|
}
|
||||||
|
return LV_FS_RES_NOT_EX;
|
||||||
}
|
}
|
||||||
|
|
||||||
lv_fs_res_t lvglClose(lv_fs_drv_t* drv, void* file_p) {
|
lv_fs_res_t lvglClose(lv_fs_drv_t* drv, void* file_p) {
|
||||||
|
@ -193,5 +195,4 @@ void FS::LVGLFileSystemInit() {
|
||||||
fs_drv.user_data = this;
|
fs_drv.user_data = this;
|
||||||
|
|
||||||
lv_fs_drv_register(&fs_drv);
|
lv_fs_drv_register(&fs_drv);
|
||||||
|
|
||||||
}
|
}
|
|
@ -21,37 +21,49 @@ namespace Pinetime {
|
||||||
|
|
||||||
int FileDelete(const char* fileName);
|
int FileDelete(const char* fileName);
|
||||||
|
|
||||||
|
int DirOpen(const char* path, lfs_dir_t* lfs_dir);
|
||||||
|
int DirClose(lfs_dir_t* lfs_dir);
|
||||||
|
int DirRead(lfs_dir_t* dir, lfs_info* info);
|
||||||
|
int DirRewind(lfs_dir_t* dir);
|
||||||
int DirCreate(const char* path);
|
int DirCreate(const char* path);
|
||||||
int DirDelete(const char* path);
|
|
||||||
|
|
||||||
|
lfs_ssize_t GetFSSize();
|
||||||
|
int Rename(const char* oldPath, const char* newPath);
|
||||||
|
int Stat(const char* path, lfs_info* info);
|
||||||
void VerifyResource();
|
void VerifyResource();
|
||||||
|
|
||||||
private:
|
static size_t getSize() {
|
||||||
|
return size;
|
||||||
|
}
|
||||||
|
static size_t getBlockSize() {
|
||||||
|
return blockSize;
|
||||||
|
}
|
||||||
|
|
||||||
|
private:
|
||||||
Pinetime::Drivers::SpiNorFlash& flashDriver;
|
Pinetime::Drivers::SpiNorFlash& flashDriver;
|
||||||
|
|
||||||
/*
|
/*
|
||||||
* External Flash MAP (4 MBytes)
|
* External Flash MAP (4 MBytes)
|
||||||
*
|
*
|
||||||
* 0x000000 +---------------------------------------+
|
* 0x000000 +---------------------------------------+
|
||||||
* | Bootloader Assets |
|
* | Bootloader Assets |
|
||||||
* | 256 KBytes |
|
* | 256 KBytes |
|
||||||
* | |
|
* | |
|
||||||
* 0x040000 +---------------------------------------+
|
* 0x040000 +---------------------------------------+
|
||||||
* | OTA |
|
* | OTA |
|
||||||
* | 464 KBytes |
|
* | 464 KBytes |
|
||||||
* | |
|
* | |
|
||||||
* | |
|
* | |
|
||||||
* | |
|
* | |
|
||||||
* 0x0B4000 +---------------------------------------+
|
* 0x0B4000 +---------------------------------------+
|
||||||
* | File System |
|
* | File System |
|
||||||
* | |
|
* | |
|
||||||
* | |
|
* | |
|
||||||
* | |
|
* | |
|
||||||
* | |
|
* | |
|
||||||
* 0x400000 +---------------------------------------+
|
* 0x400000 +---------------------------------------+
|
||||||
*
|
*
|
||||||
*/
|
*/
|
||||||
static constexpr size_t startAddress = 0x0B4000;
|
static constexpr size_t startAddress = 0x0B4000;
|
||||||
static constexpr size_t size = 0x34C000;
|
static constexpr size_t size = 0x34C000;
|
||||||
static constexpr size_t blockSize = 4096;
|
static constexpr size_t blockSize = 4096;
|
||||||
|
@ -65,7 +77,6 @@ namespace Pinetime {
|
||||||
static int SectorErase(const struct lfs_config* c, lfs_block_t block);
|
static int SectorErase(const struct lfs_config* c, lfs_block_t block);
|
||||||
static int SectorProg(const struct lfs_config* c, lfs_block_t block, lfs_off_t off, const void* buffer, lfs_size_t size);
|
static int SectorProg(const struct lfs_config* c, lfs_block_t block, lfs_off_t off, const void* buffer, lfs_size_t size);
|
||||||
static int SectorRead(const struct lfs_config* c, lfs_block_t block, lfs_off_t off, void* buffer, lfs_size_t size);
|
static int SectorRead(const struct lfs_config* c, lfs_block_t block, lfs_off_t off, void* buffer, lfs_size_t size);
|
||||||
|
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -27,6 +27,8 @@ namespace Pinetime {
|
||||||
StopRinging,
|
StopRinging,
|
||||||
MeasureBatteryTimerExpired,
|
MeasureBatteryTimerExpired,
|
||||||
BatteryPercentageUpdated,
|
BatteryPercentageUpdated,
|
||||||
|
StartFileTransfer,
|
||||||
|
StopFileTransfer,
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -342,6 +342,19 @@ void SystemTask::Work() {
|
||||||
doNotGoToSleep = false;
|
doNotGoToSleep = false;
|
||||||
xTimerStart(dimTimer, 0);
|
xTimerStart(dimTimer, 0);
|
||||||
break;
|
break;
|
||||||
|
case Messages::StartFileTransfer:
|
||||||
|
NRF_LOG_INFO("[systemtask] FS Started");
|
||||||
|
doNotGoToSleep = true;
|
||||||
|
if (isSleeping && !isWakingUp)
|
||||||
|
GoToRunning();
|
||||||
|
//TODO add intent of fs access icon or something
|
||||||
|
break;
|
||||||
|
case Messages::StopFileTransfer:
|
||||||
|
NRF_LOG_INFO("[systemtask] FS Stopped");
|
||||||
|
doNotGoToSleep = false;
|
||||||
|
xTimerStart(dimTimer, 0);
|
||||||
|
//TODO add intent of fs access icon or something
|
||||||
|
break;
|
||||||
case Messages::OnTouchEvent:
|
case Messages::OnTouchEvent:
|
||||||
if (touchHandler.GetNewTouchInfo()) {
|
if (touchHandler.GetNewTouchInfo()) {
|
||||||
touchHandler.UpdateLvglTouchPoint();
|
touchHandler.UpdateLvglTouchPoint();
|
||||||
|
|
Loading…
Reference in a new issue