Простое устройство, безделушка. Но куча установочных параметров. По красивому бы каждый параметр устанавливать/проверять/сохранять в своём модуле. Но это громоздко и много текста. Как альтернатива, заниматься всем этим отдельно. Это компактно - всё в структуре и всё сохраняется за раз. Но, как то, КМК, не аккуратненько? Кто как делает?
параметры каждого модуля (кстати, а что вы называете модулями?) описать структурами типа
struct any_conf_t {
...
};
для каждой такой прописать функцию инициализации со всеми необходимыми проверками, типа
any_conf_t any_conf_init (some_t par_1, some_t par_2, ... ) {
...
return (any_conf_t) ;
}
либо, если надо знать результат инициализации
bool any_conf_init (any_conf_t* conf, some_t par_1, some_t par_2, ... ) {
...
return (bool);
}
Я примерно так предпочитаю. Еще для минимизации проверок иногда полезно использовать enum перечисления, где выбор из небольшого кол-ва значений. Для дальнейшего обсуждения, пожалуй, требуются подробности с вашей стороны
Модуль - это независимая от всего секция. Сегодня он есть, а завтра его нет, что тут объяснять.
Вот, а в то же время, если я делаю всё в куче, получается на всё завязка.
у меня есть назойливая привычка обрамлять такое в классы, даже если экземпляр в программе будет всего один. Использовать потом удобно и границы видно отчетливо. Ну плюс области видимости, хотя это и без классов можно
Еще из области почти бредовой реализации:
enum any_conf_list {
cfg_1,
cfg_2,
cfg_3,
get
};
void func (any_conf_list conf_id, ...) {
static bool value_1 = false;
static int value_2 = 0;
static float value_31 = 0;
static int value_32 = 0;
va_list valist;
if (conf_id == cfg_1) {
va_list valist;
va_start(valist, 1);
value_1 = va_arg(valist, bool);
va_end(valist);
} else if (conf_id == cfg_2) {
va_start(valist, 1);
value_2 = va_arg(valist, int);
va_end(valist);
} else if (conf_id == cfg_3) {
va_start(valist, 2);
value_31 = va_arg(valist, float);
value_32 = va_arg(valist, int);
va_end(valist);
} else if (conf_id == get) {
va_start(valist, 1);
float* res = va_arg(valist, float*);
va_end(valist);
*float = value_1 ? value_2 * value_2 : value_31 + value_32 ;
}
}
функции на неопределенное кол-во аргументов произвольного формата. Можно вот первым параметром определяем вариант взаимодействия, и для него обработчик свой сделать внутри функции. Остальные параметры согласно конкретному формату взаимодействия, а хранятся в static (принцип как у глобальных переменных). Разумеется, беглым взглядом не поймешь даже, в каких вариантах можно использовать функцию и какие параметры передавать
Нет. Хочется максимально просто и понятно. С другой стороны, не хочется и мусолить. Что бы открыл через пол года - год и ага, всё понятно.)
это если функция служит для контроля объекта (задачи), существующего(ей) в единственном экземпляре
конфиг структурой в глобальной области видимости (как в сообщении №2)
struct t_KeyboardValues
{
unsigned short int values[4];
};
struct t_DeviceSettings
{
int iIdent;
t_KeyboardValues KeyboardValues;
time_t tmLastEvent;
unsigned long ulPeriodSec;
unsigned long ulDurationSec;
float fFlowScale;
unsigned long ulWaterDoseMl;
};
#define OFFSET_SETTINGS(MEMBER) (int)(&((struct t_DeviceSettings*)0)->MEMBER)
unsigned long ulDurationS;
m_pEeprom->ReadBuffer(&ulDurationS, OFFSET_SETTINGS(ulDurationSec), sizeof(ulDurationS));
Описывается общая структура данных в EEPROM (только описание, сама нигде полностью не заводится, места не знмиает), потом из любого места программы по имени доступ - автовычисление адреса и размера.
Удобство в том, что добавление нового параметра - просто добавление члена в структуру. Изменение формата тоже (меняешь идентификатор, и при загрузке идет сброс на дефолт по невалидности). Ничего считать по адресам и смещениям не надо.
Это база. Сверху можно накрутить удобные аксессоры к своим параметрам.
Да, так и сделано. Не нравится то, что не могу взять и прикрепить к другому проекту. Потому что на структуру всё завязано. Потому что другие данные в структуре присутствуют. А мне не хотелось бы их видеть.
например, так:
float pref_get_float(const char *key, float default_value);
...
uint32_t pref_get_uint32(const char *key, uint32_t default_value);
...
float param_a = pref_get_float("cool_name.module_a.param_a", 0.1f);
Параметры - в файле настроек, например
так (парсить проще)
preferences.txt:
cool_name.module_b.param_bcd=23
cool_name.module_a.param_a=0.12345
cool_name.module_a.param_b=0x12345678
...
Или так (читаемость лучше, размер меньше, парсить сложнее):
cool_name {
module_a {
param_a=0.12345
param_b=0x12345678
}
module_b {
param_bcd = 23
}
}
Как оно физически хранится - это будет решать функция pref_get_…(). Читать из файла на файловой системе, или из ЕЕПРОМ или еще как.
И портировать легко. И мало
Плюсом такого подхода является то, что ты вообще не заморачиваешся структурами. Поэтому добавлять ноовое поле в файл настроек будет легко.
Структура, или же класс. Если есть различия в проектах, то наследуй от основной и добавляй нужное. В .h (.hpp) файле ты описываешь структуру и основные методы интерфейса (сохранение в nvram автосохранение, обновление состояний и пр.) Если в проекте наследуешь что-то от “скелетной”, то в проект включаешь .h в котором объявляешь наследника со всеми добавками. И в нем прописываешь extern глобальную переменную. Этот хедер идет во все модули проекта, а сам объект объявляешь в основном файле проекта.
Это стандартная схема. Зачем что-то придумывать? “Все уже украдено до нас”.
Влад! Но это всё закручено. А хотелось бы, что бы было по простецки. Что бы любой чел мог бы понять. И даже сам, по прошествии какого то времени.)
Нужно подумать…)
Храните область памяти и тупо меняете тип указателя под любой формат/структуру.
Спойлер
num optionsPositionEEPROMparams { opMagicCode = 0, // код, по которому определяется первый ли это запуск устройства
opDeviceVer, // версия устройства
opDeviceID, // ИД устройства
opDevicePass, // пароль устройства
opGetBatLevel, // периодичность опроса модема - получение уровня заряда батареи
opNormalHTTP, // периодичность отправки HTTP запроса в режиме 1 при нормальном завершении предыдущего сеанса
opHTTPbad, // периодичность отправки HTTP запроса в режиме 1 при ошибочном завершении предыдущего сеанса
opDeviceMode, // режим работы устройства 1...3 (4 ый режим запрещено сохранять)
opMode2period, // периодичность включения температурного датчика и GPS для отправки
opMode3work, // вренмя работы в Режиме 3 в минутах после включения GPS и темп датчика
opResponseONOFF, // включение/отключение СМС ответа на zum/led
opMode4normal, // записываем 1 при установке Режим4 и записываем 0 при команде mode и после отправки аварийных СМС
opCountHTTPbad, // число ошибочных http запросов, прежде чем модем сбрасывать
opReadSMS, // периодичность чтения СМС
opTestCREG, // периодичность уточнения у оператора зарегестрирован ли модем
opRestartGPRS, // периодичность перезапуска GPRS
opGetGPSnormal, // периодичность получения данных GPS
opGetTemp, // периодичность получения температуры
opCountWhitePhones, // число сохраненных телефонов белого списка
opGprsAPN, // APN оператора
opGprsUSR, // USR оператора
opGprsPAS, // PAS оператора
opPowMessage, // 1 если при низком уровне напряжения - СМС уже отправлено
opLastMode, // предыдущий режим, установленный перед сменой
opRINGmode, // режим действия устройства при входящем звонке
opFirstWhitePhone, // первый телефон из белого списка, остальные параметры это последующие телефоны
opSecondWhitePhone
};
void rawDigToParam(int posParam, long digParam) {
long* ptrUL = (long*)massParams[posParam];
*ptrUL = digParam;
}
void rawStrToParam(int posParam, char* strParam) {
int ix = 0;
massParams[posParam][ix] = '\0';
while (*(strParam + ix)) {
massParams[posParam][ix] = *(strParam + ix);
ix++;
}
massParams[posParam][ix] = '\0';
}
Да я понимаю как это всё хранить и как это всё извлекать. Просто, всё кучей или всё по отдельности. Кучей - всё компактно, но нужно учитывать ВСЕ параметры проекта. Тогда как, по отдельности, я могу оперировать с одним параметром и ни о чём не думать. Дилема, понимаешь ли.)
Ни у одного выжившего в эволюционном замесе животного нет универсального набора органов.
Делай вывод.
Я просто делаю ВО ВСЕХ проектах однообразно. У меня есть структура State и объект просто одной буквой “s”. Он доступен во всех модулях программы, потому, что объявлен внешним в главном инклюде.
У него есть привычные методы и поля, которые я использую всегда. Конкретика добавляется путем наследования.
Я написал то, как делаю сам, именно, чтобы не путать и не забывать.
Просто делаю всегда одинаково.
И в любом модуле смотрю что-то типа s.pinNTC1
или вызываю s.saveState()
.
Ты главное делай одинаково всегда. как самому удобно. Сделай один интерфейс и в своем любимом Инклюде один раз его откомментируй до молекулы.
Чому мені, Боже, ти крила не дав?
Я б землю покинув и в небо злітав,
(с) Тарас Шевченко, если не путаю…
// однострочный комментарий
/*
комментарий из произвольного количества строк
*/