Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Binary file added images/TxLight_16x16.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added images/TxShock_16x16.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added images/TxSound_16x16.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added images/TxVibrate_16x16.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
1,809 changes: 1,485 additions & 324 deletions openshock.c

Large diffs are not rendered by default.

4 changes: 2 additions & 2 deletions protocols.h
Original file line number Diff line number Diff line change
Expand Up @@ -27,8 +27,8 @@ typedef struct {
uint16_t low_us;
} OokPulse;

// Maximum pulse buffer size across all protocols (CaiXianlin is largest at 44)
#define OPENSHOCK_MAX_PULSES 48
// Maximum pulse buffer size (dual TX concatenates two packets + gap; CaiXianlin is 44 each)
#define OPENSHOCK_MAX_PULSES 160

// Decoded shocker command (result of RX decoding).
typedef struct {
Expand Down
56 changes: 56 additions & 0 deletions settings.c
Original file line number Diff line number Diff line change
@@ -0,0 +1,56 @@
#include "settings.h"

#include <furi.h>
#include <flipper_format/flipper_format.h>
#include <storage/storage.h>

#include <string.h>

#define SETTINGS_PATH APP_DATA_PATH("openshock_settings.cfg")
#define SETTINGS_TYPE "OpenShock Settings"
#define SETTINGS_VER 1u

void openshock_settings_load(bool* transmit_vertical_ui_out) {
*transmit_vertical_ui_out = false;

Storage* storage = furi_record_open(RECORD_STORAGE);
FlipperFormat* ff = flipper_format_file_alloc(storage);
do {
if(!flipper_format_file_open_existing(ff, SETTINGS_PATH)) break;

FuriString* hdr = furi_string_alloc();
uint32_t ver = 0;
if(!flipper_format_read_header(ff, hdr, &ver)) {
furi_string_free(hdr);
break;
}
if(strcmp(furi_string_get_cstr(hdr), SETTINGS_TYPE) != 0 || ver != SETTINGS_VER) {
furi_string_free(hdr);
break;
}
furi_string_free(hdr);

uint32_t v = 0;
if(flipper_format_read_uint32(ff, "TransmitVertical", &v, 1)) {
*transmit_vertical_ui_out = (v != 0);
}
} while(false);
flipper_format_file_close(ff);
flipper_format_free(ff);
furi_record_close(RECORD_STORAGE);
}

void openshock_settings_save(bool transmit_vertical_ui) {
Storage* storage = furi_record_open(RECORD_STORAGE);

FlipperFormat* ff = flipper_format_file_alloc(storage);
do {
if(!flipper_format_file_open_always(ff, SETTINGS_PATH)) break;
if(!flipper_format_write_header_cstr(ff, SETTINGS_TYPE, SETTINGS_VER)) break;
uint32_t v = transmit_vertical_ui ? 1u : 0u;
if(!flipper_format_write_uint32(ff, "TransmitVertical", &v, 1)) break;
} while(false);
flipper_format_file_close(ff);
flipper_format_free(ff);
furi_record_close(RECORD_STORAGE);
}
6 changes: 6 additions & 0 deletions settings.h
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
#pragma once

#include <stdbool.h>

void openshock_settings_load(bool* transmit_vertical_ui_out);
void openshock_settings_save(bool transmit_vertical_ui);
98 changes: 89 additions & 9 deletions storage.c
Original file line number Diff line number Diff line change
@@ -1,7 +1,5 @@
#include "storage.h"

#include <furi.h>
#include <storage/storage.h>
#include <flipper_format/flipper_format.h>

#include <stdio.h>
Expand All @@ -10,33 +8,81 @@
#define FILE_TYPE "OpenShock Shocker"
#define FILE_VERSION 1

bool openshock_shocker_save(const char* name, ShockerModel model, uint16_t id, uint8_t channel) {
bool openshock_shocker_unique_stem(ShockerModel model, uint16_t id, char* stem_out, size_t stem_sz) {
Storage* storage = furi_record_open(RECORD_STORAGE);
storage_simply_mkdir(storage, OPENSHOCK_SAVE_DIR);

const char* model_name = openshock_model_name(model);
bool ok = false;

for(unsigned suffix = 0; suffix < 1000; suffix++) {
char candidate[OPENSHOCK_NAME_MAX_LEN];
if(suffix == 0) {
snprintf(candidate, sizeof(candidate), "%s_%u", model_name, id);
} else {
snprintf(candidate, sizeof(candidate), "%s_%u_%u", model_name, id, suffix);
}

char path[OPENSHOCK_PATH_MAX_LEN];
snprintf(path, sizeof(path), "%s/%s.shk", OPENSHOCK_SAVE_DIR, candidate);

FileInfo info;
FS_Error err = storage_common_stat(storage, path, &info);
if(err != FSE_OK) {
snprintf(stem_out, stem_sz, "%s", candidate);
ok = true;
break;
}
}

furi_record_close(RECORD_STORAGE);
return ok;
}

bool openshock_shocker_write(
const char* stem,
ShockerModel model,
uint16_t id,
uint8_t channel,
uint8_t sync_group,
const char* replace_path) {
if(!stem || stem[0] == '\0') return false;

if(sync_group > 9) sync_group = 9;

Storage* storage = furi_record_open(RECORD_STORAGE);
storage_simply_mkdir(storage, OPENSHOCK_SAVE_DIR);

char path[OPENSHOCK_PATH_MAX_LEN];
snprintf(path, sizeof(path), "%s/%s.shk", OPENSHOCK_SAVE_DIR, name);
snprintf(path, sizeof(path), "%s/%s.shk", OPENSHOCK_SAVE_DIR, stem);

FlipperFormat* ff = flipper_format_file_alloc(storage);
bool ok = false;

do {
if(!flipper_format_file_open_always(ff, path)) break;
if(!flipper_format_write_header_cstr(ff, FILE_TYPE, FILE_VERSION)) break;
if(!flipper_format_write_string_cstr(ff, "Name", name)) break;
if(!flipper_format_write_string_cstr(ff, "Name", stem)) break;
if(!flipper_format_write_string_cstr(ff, "Model", openshock_model_name(model))) break;

uint32_t val;
val = id;
if(!flipper_format_write_uint32(ff, "ID", &val, 1)) break;
val = channel;
if(!flipper_format_write_uint32(ff, "Channel", &val, 1)) break;
val = sync_group;
if(!flipper_format_write_uint32(ff, "Sync", &val, 1)) break;

ok = true;
} while(false);

flipper_format_file_close(ff);
flipper_format_free(ff);

if(ok && replace_path && strcmp(replace_path, path) != 0) {
storage_common_remove(storage, replace_path);
}

furi_record_close(RECORD_STORAGE);
return ok;
}
Expand All @@ -53,7 +99,7 @@ static bool parse_model(const char* str, ShockerModel* out) {

static bool load_one(Storage* storage, const char* path, SavedShocker* out) {
FlipperFormat* ff = flipper_format_file_alloc(storage);
bool ok = false;
bool load_ok = false;

do {
if(!flipper_format_file_open_existing(ff, path)) break;
Expand Down Expand Up @@ -92,13 +138,33 @@ static bool load_one(Storage* storage, const char* path, SavedShocker* out) {
if(!flipper_format_read_uint32(ff, "Channel", &val, 1)) break;
out->channel = (uint8_t)val;

out->sync_group = 0;
if(flipper_format_read_uint32(ff, "Sync", &val, 1)) {
if(val <= 9) out->sync_group = (uint8_t)val;
}

snprintf(out->filename, sizeof(out->filename), "%s", path);
ok = true;
load_ok = true;
} while(false);

flipper_format_file_close(ff);
flipper_format_free(ff);
return ok;
return load_ok;
}

static void saved_shocker_sort_by_name(SavedShocker* list, size_t count) {
for(size_t i = 1; i < count; i++) {
SavedShocker key = list[i];
size_t j = i;
while(j > 0) {
int r = strcmp(list[j - 1].name, key.name);
if(r == 0) r = strcmp(list[j - 1].filename, key.filename);
if(r <= 0) break;
list[j] = list[j - 1];
j--;
}
list[j] = key;
}
}

size_t openshock_shocker_list(SavedShocker* list, size_t max_count) {
Expand All @@ -118,7 +184,6 @@ size_t openshock_shocker_list(SavedShocker* list, size_t max_count) {
while(count < max_count && storage_dir_read(dir, &info, name, sizeof(name))) {
if(info.flags & FSF_DIRECTORY) continue;

// Only .shk files
size_t len = strlen(name);
if(len < 5 || strcmp(name + len - 4, ".shk") != 0) continue;

Expand All @@ -132,6 +197,10 @@ size_t openshock_shocker_list(SavedShocker* list, size_t max_count) {
storage_dir_close(dir);
}

if(count > 1) {
saved_shocker_sort_by_name(list, count);
}

storage_file_free(dir);
furi_record_close(RECORD_STORAGE);
return count;
Expand All @@ -143,3 +212,14 @@ bool openshock_shocker_delete(const char* path) {
furi_record_close(RECORD_STORAGE);
return err == FSE_OK;
}

bool openshock_stem_exists(const char* stem) {
if(!stem || !stem[0]) return false;
char path[OPENSHOCK_PATH_MAX_LEN];
snprintf(path, sizeof(path), "%s/%s.shk", OPENSHOCK_SAVE_DIR, stem);
Storage* storage = furi_record_open(RECORD_STORAGE);
FileInfo info;
FS_Error err = storage_common_stat(storage, path, &info);
furi_record_close(RECORD_STORAGE);
return err == FSE_OK;
}
21 changes: 16 additions & 5 deletions storage.h
Original file line number Diff line number Diff line change
@@ -1,5 +1,7 @@
#pragma once

#include <furi.h>
#include <storage/storage.h>
#include "protocols.h"
#include <stdbool.h>

Expand All @@ -9,18 +11,27 @@
#define OPENSHOCK_PATH_MAX_LEN 128

typedef struct {
char filename[OPENSHOCK_PATH_MAX_LEN]; // Full path
char filename[OPENSHOCK_PATH_MAX_LEN];
char name[OPENSHOCK_NAME_MAX_LEN];
ShockerModel model;
uint16_t shocker_id;
uint8_t channel;
uint8_t sync_group;
} SavedShocker;

// Save a shocker config. Name is used for the filename and stored in the file.
bool openshock_shocker_save(const char* name, ShockerModel model, uint16_t id, uint8_t channel);
// replace_path: remove after successful write when renaming to a different file.
bool openshock_shocker_write(
const char* stem,
ShockerModel model,
uint16_t id,
uint8_t channel,
uint8_t sync_group,
const char* replace_path);

bool openshock_shocker_unique_stem(ShockerModel model, uint16_t id, char* stem_out, size_t stem_sz);

// Load the list of saved shockers. Returns number loaded.
size_t openshock_shocker_list(SavedShocker* list, size_t max_count);

// Delete a saved shocker by its full path.
bool openshock_shocker_delete(const char* path);

bool openshock_stem_exists(const char* stem);