Device: Add disk formatting

pull/76/merge
Yasser Yassine 2025-07-22 20:02:06 +00:00 committed by Kyle Schwarz
parent d70defbf8a
commit 1e5e714f5b
20 changed files with 351 additions and 7 deletions

View File

@ -292,6 +292,7 @@ set(SRC_FILES
disk/plasiondiskreaddriver.cpp
disk/extextractordiskreaddriver.cpp
disk/fat.cpp
disk/diskdetails.cpp
disk/vsa/vsa.cpp
disk/vsa/vsa02.cpp
disk/vsa/vsa03.cpp

View File

@ -124,6 +124,10 @@ static constexpr const char* LIN_SETTINGS_NOT_AVAILABLE = "LIN settings are not
static constexpr const char* MODE_NOT_FOUND = "The mode was not found.";
static constexpr const char* GPTP_NOT_SUPPORTED = "GPTP clock synchronization is not supported on this device.";
static constexpr const char* SETTING_NOT_AVAILABLE = "Requested a setting that is not available on this device";
static constexpr const char* DISK_FORMAT_NOT_SUPPORTED = "Disk formatting is not supported on this device.";
static constexpr const char* DISK_FORMAT_INVALID_COUNT = "Disk format config disk count is mismatched with device disk count.";
// Transport Errors
static constexpr const char* FAILED_TO_READ = "A read operation failed.";
@ -371,6 +375,10 @@ const char* APIEvent::DescriptionForType(Type type) {
return SEND_TO_ERROR;
case Type::GPTPNotSupported:
return GPTP_NOT_SUPPORTED;
case Type::DiskFormatNotSupported:
return DISK_FORMAT_NOT_SUPPORTED;
case Type::DiskFormatInvalidCount:
return DISK_FORMAT_INVALID_COUNT;
// FTD3XX
case Type::FTOK:

View File

@ -36,6 +36,8 @@ void init_event(pybind11::module_& m) {
.value("SettingsLengthError", APIEvent::Type::SettingsLengthError)
.value("SettingsChecksumError", APIEvent::Type::SettingsChecksumError)
.value("SettingsNotAvailable", APIEvent::Type::SettingsNotAvailable)
.value("DiskFormatNotSupported", APIEvent::Type::DiskFormatNotSupported)
.value("DiskFormatInvalidCount", APIEvent::Type::DiskFormatInvalidCount)
.value("SettingsReadOnly", APIEvent::Type::SettingsReadOnly)
.value("CANSettingsNotAvailable", APIEvent::Type::CANSettingsNotAvailable)
.value("CANFDSettingsNotAvailable", APIEvent::Type::CANFDSettingsNotAvailable)

View File

@ -324,6 +324,17 @@ bool Decoder::decode(std::shared_ptr<Message>& result, const std::shared_ptr<Pac
result = GPTPStatus::DecodeToMessage(packet->data, report);
return true;
}
case ExtendedCommand::GetDiskDetails:
case ExtendedCommand::DiskFormatProgress: {
std::vector<uint8_t> responseBody(
packet->data.begin() + sizeof(ExtendedResponseMessage::ResponseHeader),
packet->data.end()
);
auto response = std::make_shared<ExtendedResponseMessage>(resp.command);
response->data = std::move(responseBody);
result = response;
return true;
}
default:
// No defined handler, treat this as a RawMessage
break;

View File

@ -5,6 +5,7 @@
#include "icsneo/device/device.h"
#include "icsneo/device/extensions/deviceextension.h"
#include "icsneo/disk/fat.h"
#include "icsneo/communication/message/filter/extendedresponsefilter.h"
#ifdef _MSC_VER
#pragma warning(disable : 4996) // STL time functions
@ -3487,3 +3488,129 @@ bool Device::enableNetworkCommunication(bool enable) {
}
return true;
}
bool Device::formatDisk(const DiskDetails& config, const DiskFormatProgress& handler, std::chrono::milliseconds interval) {
#pragma pack(push, 2)
struct DiskFormatProgressResponse {
uint16_t state;
uint64_t sectorsRemaining;
};
#pragma pack(pop)
auto diskCount = getDiskCount();
if(!diskCount) {
report(APIEvent::Type::DiskFormatNotSupported, APIEvent::Severity::Error);
return false;
}
if(config.disks.size() != diskCount) {
report(APIEvent::Type::DiskFormatInvalidCount, APIEvent::Severity::Error);
return false;
}
std::vector<uint8_t> payload = DiskDetails::Encode(config);
if(!com->sendCommand(ExtendedCommand::DiskFormatStart, payload)) {
return false;
}
uint64_t sectorsFormatted = 0;
uint64_t sectorsTotal = 0;
uint16_t lastState = 1;
do {
std::shared_ptr<Message> response = com->waitForMessageSync(
[this](){
return com->sendCommand(ExtendedCommand::DiskFormatProgress, {});
},
std::make_shared<ExtendedResponseFilter>(ExtendedCommand::DiskFormatProgress),
std::chrono::milliseconds(200)
);
if(!response) {
report(APIEvent::Type::NoDeviceResponse, APIEvent::Severity::Error);
return false;
}
auto extResponse = std::dynamic_pointer_cast<ExtendedResponseMessage>(response);
if(!extResponse) {
// Should never happen
return false;
}
if(extResponse->data.size() < sizeof(DiskFormatProgressResponse)) {
report(APIEvent::Type::BufferInsufficient, APIEvent::Severity::Error);
return false;
}
auto* progress = reinterpret_cast<DiskFormatProgressResponse*>(extResponse->data.data());
lastState = progress->state;
if(sectorsTotal == 0) {
sectorsTotal = progress->sectorsRemaining;
} else {
sectorsFormatted = sectorsTotal - progress->sectorsRemaining;
if(handler) {
auto directive = handler(sectorsFormatted, sectorsTotal);
if(directive == DiskFormatDirective::Stop) {
return com->sendCommand(ExtendedCommand::DiskFormatCancel);
}
}
}
std::this_thread::sleep_for(interval);
} while(lastState);
return true;
}
bool Device::forceDiskConfigUpdate(const DiskDetails& config) {
auto diskCount = getDiskCount();
if(!diskCount) {
report(APIEvent::Type::DiskFormatNotSupported, APIEvent::Severity::Error);
return false;
}
if(config.disks.size() != diskCount) {
report(APIEvent::Type::DiskFormatInvalidCount, APIEvent::Severity::Error);
return false;
}
return com->sendCommand(ExtendedCommand::DiskFormatUpdate, DiskDetails::Encode(config));
}
std::shared_ptr<DiskDetails> Device::getDiskDetails(std::chrono::milliseconds timeout) {
if(!supportsDiskFormatting()) {
report(APIEvent::Type::DiskFormatNotSupported, APIEvent::Severity::Error);
return nullptr;
}
if(!isOpen()) {
report(APIEvent::Type::DeviceCurrentlyClosed, APIEvent::Severity::Error);
return nullptr;
}
std::shared_ptr<Message> response = com->waitForMessageSync(
[this](){
return com->sendCommand(ExtendedCommand::GetDiskDetails, {});
},
std::make_shared<ExtendedResponseFilter>(ExtendedCommand::GetDiskDetails),
timeout
);
if(!response) {
report(APIEvent::Type::NoDeviceResponse, APIEvent::Severity::Error);
return nullptr;
}
auto extResponse = std::dynamic_pointer_cast<ExtendedResponseMessage>(response);
if(!extResponse) {
// Should never happen
return nullptr;
}
return DiskDetails::Decode(extResponse->data, getDiskCount(), report);
}

View File

@ -0,0 +1,81 @@
#include <icsneo/disk/diskdetails.h>
#include <icsneo/device/idevicesettings.h>
using namespace icsneo;
#pragma pack(push, 2)
struct DISK_STATUS {
uint16_t status;
uint64_t sectors;
uint32_t bytesPerSector;
};
struct DISK_DETAILS {
DISK_SETTINGS settings;
uint16_t options;
DISK_STATUS status[12];
};
#pragma pack(pop)
static constexpr uint8_t DISK_FORMAT_FAT32 = 0x01u;
static constexpr uint8_t DISK_STATUS_PRESENT = 0x01u;
static constexpr uint8_t DISK_STATUS_INITIALIZED = 0x02u;
static constexpr uint16_t DISK_DETAILS_FULL_FORMAT = 0x01u;
std::vector<uint8_t> DiskDetails::Encode(const DiskDetails& config) {
std::vector<uint8_t> result(sizeof(DISK_DETAILS), 0);
DISK_DETAILS* details = reinterpret_cast<DISK_DETAILS*>(result.data());
details->settings.disk_layout = static_cast<uint8_t>(config.layout);
details->settings.disk_format = DISK_FORMAT_FAT32; // Always FAT32
details->options = config.fullFormat ? DISK_DETAILS_FULL_FORMAT : 0;
uint32_t& enables = details->settings.disk_enables;
enables = 0u;
for(size_t i = 0; i < config.disks.size(); i++) {
auto& disk = config.disks[i];
details->status[i].sectors = disk.sectors;
details->status[i].bytesPerSector = disk.bytesPerSector;
uint16_t& status = details->status[i].status;
status = 0u;
if(disk.initialized) {
status |= DISK_STATUS_INITIALIZED;
}
if(disk.present) {
status |= DISK_STATUS_PRESENT;
}
if(disk.formatted) {
enables |= (1u << i);
}
}
return result;
}
std::shared_ptr<DiskDetails> DiskDetails::Decode(const std::vector<uint8_t>& bytes, size_t diskCount, device_eventhandler_t report) {
if(bytes.size() < sizeof(DISK_DETAILS)) {
report(APIEvent::Type::BufferInsufficient, APIEvent::Severity::Error);
return nullptr;
}
auto result = std::make_shared<DiskDetails>();
const DISK_DETAILS* details = reinterpret_cast<const DISK_DETAILS*>(bytes.data());
result->layout = static_cast<DiskLayout>(details->settings.disk_layout);
result->fullFormat = details->options & DISK_DETAILS_FULL_FORMAT;
result->disks.reserve(diskCount);
for(size_t i = 0; i < diskCount; i++) {
auto& disk = details->status[i];
result->disks.emplace_back();
auto& info = result->disks.back();
info.present = static_cast<bool>(disk.status & DISK_STATUS_PRESENT);
info.initialized = static_cast<bool>(disk.status & DISK_STATUS_INITIALIZED);
info.formatted = static_cast<bool>(details->settings.disk_enables & (1u << i));
info.sectors = disk.sectors;
info.bytesPerSector = disk.bytesPerSector;
}
return result;
}

View File

@ -113,6 +113,8 @@ public:
AppErrorParsingFailed = 0x2055,
GPTPNotSupported = 0x2056,
SettingNotAvaiableDevice = 0x2057,
DiskFormatNotSupported = 0x2058,
DiskFormatInvalidCount = 0x2059,
// Transport Events
FailedToRead = 0x3000,

View File

@ -10,11 +10,12 @@ namespace icsneo {
class ExtendedResponseMessage : public Message {
public:
ExtendedResponseMessage(ExtendedCommand cmd, ExtendedResponse resp)
ExtendedResponseMessage(ExtendedCommand cmd, ExtendedResponse resp = ExtendedResponse::OK)
: Message(Message::Type::ExtendedResponse), command(cmd), response(resp) {}
const ExtendedCommand command;
const ExtendedResponse response;
std::vector<uint8_t> data;
#pragma pack(push, 2)
struct ResponseHeader {

View File

@ -15,20 +15,36 @@ namespace icsneo {
class ExtendedResponseFilter : public MessageFilter {
public:
ExtendedResponseFilter(icsneo::ExtendedResponse resp) : MessageFilter(Message::Type::ExtendedResponse), response(resp) {}
ExtendedResponseFilter(icsneo::ExtendedCommand cmd) : MessageFilter(Message::Type::ExtendedResponse), command(cmd), response(std::nullopt) {}
ExtendedResponseFilter(icsneo::ExtendedResponse resp) : MessageFilter(Message::Type::ExtendedResponse), command(std::nullopt), response(resp) {}
ExtendedResponseFilter(icsneo::ExtendedCommand cmd, icsneo::ExtendedResponse resp)
: MessageFilter(Message::Type::ExtendedResponse), command(cmd), response(resp) {}
bool match(const std::shared_ptr<Message>& message) const override {
if(!MessageFilter::match(message)) {
return false;
}
const auto respMsg = std::static_pointer_cast<ExtendedResponseMessage>(message);
return respMsg && matchResponse(respMsg->response);
return respMsg && matchResponse(respMsg->command, respMsg->response);
}
private:
icsneo::ExtendedResponse response;
bool matchResponse(icsneo::ExtendedResponse resp) const {
return response == resp;
std::optional<icsneo::ExtendedCommand> command = std::nullopt;
std::optional<icsneo::ExtendedResponse> response = std::nullopt;
bool matchResponse(icsneo::ExtendedCommand cmd, icsneo::ExtendedResponse resp) const {
if(command) {
if(*command != cmd) {
return false;
}
}
if(response) {
if(*response != resp) {
return false;
}
}
return true;
}
};

View File

@ -26,6 +26,7 @@
#include "icsneo/disk/diskreaddriver.h"
#include "icsneo/disk/diskwritedriver.h"
#include "icsneo/disk/nulldiskdriver.h"
#include "icsneo/disk/diskdetails.h"
#include "icsneo/communication/communication.h"
#include "icsneo/communication/packetizer.h"
#include "icsneo/communication/encoder.h"
@ -637,6 +638,22 @@ public:
bool unsubscribeLiveData(const LiveDataHandle& handle);
bool clearAllLiveData();
bool setValueLiveData(std::shared_ptr<LiveDataSetValueMessage> message);
enum class DiskFormatDirective : uint8_t {
Continue,
Stop
};
using DiskFormatProgress = std::function<DiskFormatDirective(uint64_t sectorsFormatted, uint64_t sectorsTotal)>;
bool formatDisk(
const DiskDetails& config,
const DiskFormatProgress& handler = {},
std::chrono::milliseconds interval = std::chrono::milliseconds(500) /** Send updates at an interval of 500ms */
);
bool forceDiskConfigUpdate(const DiskDetails& config); // Forces a disk layout and enables change without formatting
std::shared_ptr<DiskDetails> getDiskDetails(std::chrono::milliseconds timeout = std::chrono::milliseconds(100));
virtual size_t getDiskCount() const { return 0; }
bool supportsDiskFormatting() const { return getDiskCount() != 0; }
// VSA Read functions

View File

@ -924,7 +924,8 @@ public:
template<typename T> T getStructure() const { return *getStructurePointer<T>(); }
template<typename T> bool applyStructure(const T& newStructure);
const size_t& getSize() const { return structSize; }
size_t getSize() const { return structSize; }
size_t getSerialSize() const { return settings.size(); }
// if settings are disabled for this device. always false unless constructed null
bool disabled = false;

View File

@ -70,6 +70,10 @@ protected:
}
bool supportsGPTP() const override { return true; }
size_t getDiskCount() const override {
return 2;
}
};

View File

@ -141,6 +141,10 @@ protected:
bool supportsEraseMemory() const override {
return true;
}
size_t getDiskCount() const override {
return 1;
}
};
}

View File

@ -91,6 +91,10 @@ protected:
return true;
}
size_t getDiskCount() const override {
return 2;
}
};
}

View File

@ -75,6 +75,10 @@ protected:
bool supportsEraseMemory() const override {
return true;
}
size_t getDiskCount() const override {
return 2;
}
};
}

View File

@ -54,6 +54,10 @@ protected:
bool supportsEraseMemory() const override {
return true;
}
size_t getDiskCount() const override {
return 1;
}
};
}

View File

@ -107,6 +107,10 @@ protected:
std::optional<MemoryAddress> getCoreminiStartAddressSD() const override {
return 0;
}
size_t getDiskCount() const override {
return 1;
}
};
}

View File

@ -120,6 +120,10 @@ protected:
std::optional<MemoryAddress> getCoreminiStartAddressSD() const override {
return 0;
}
size_t getDiskCount() const override {
return 1;
}
};
}

View File

@ -137,6 +137,11 @@ namespace icsneo
{
return 0;
}
size_t getDiskCount() const override
{
return 1;
}
};
}

View File

@ -0,0 +1,44 @@
#ifndef __DISKDETAILS_H__
#define __DISKDETAILS_H__
#ifdef __cplusplus
#include <cstdint>
#include <vector>
#include <icsneo/api/eventmanager.h>
namespace icsneo {
enum class DiskLayout : uint8_t {
Spanned = 0,
RAID0 = 1
};
struct DiskInfo {
bool present; // Disk is connected
bool initialized; // Disk is initialized
bool formatted; // Disk is formatted
uint64_t sectors;
uint32_t bytesPerSector;
uint64_t size() const {
return sectors * bytesPerSector;
}
};
struct DiskDetails {
DiskLayout layout;
bool fullFormat;
std::vector<DiskInfo> disks;
static std::vector<uint8_t> Encode(const DiskDetails& config);
static std::shared_ptr<DiskDetails> Decode(const std::vector<uint8_t>& bytes, size_t diskCount, device_eventhandler_t report);
};
} // namespace icsneo
#endif // __cplusplus
#endif