Code import
This commit is contained in:
@@ -0,0 +1,136 @@
|
||||
//
|
||||
// Created by MightyPork on 2017/11/24.
|
||||
//
|
||||
|
||||
#include "platform.h"
|
||||
#include "unit.h"
|
||||
#include "resources.h"
|
||||
|
||||
static bool rsc_initialized = false;
|
||||
|
||||
// This takes quite a lot of space, we could use u8 and IDs instead if needed
|
||||
struct resouce_slot {
|
||||
const char *name;
|
||||
Unit *owner;
|
||||
} __attribute__((packed));
|
||||
|
||||
static struct resouce_slot resources[R_RESOURCE_COUNT];
|
||||
|
||||
// here are the resource names for better debugging (could also be removed if absolutely necessary)
|
||||
const char *const rsc_names[] = {
|
||||
#define X(res_name) #res_name,
|
||||
XX_RESOURCES
|
||||
#undef X
|
||||
};
|
||||
|
||||
/**
|
||||
* Initialize the resources registry
|
||||
*/
|
||||
void rsc_init_registry(void)
|
||||
{
|
||||
for (int i = 0; i < R_RESOURCE_COUNT; i++) {
|
||||
resources[i].owner = &UNIT_PLATFORM;
|
||||
}
|
||||
rsc_initialized = true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Claim a resource for a unit
|
||||
*
|
||||
* @param unit - claiming unit
|
||||
* @param rsc - resource to claim
|
||||
* @return true on successful claim
|
||||
*/
|
||||
bool rsc_claim(Unit *unit, Resource rsc)
|
||||
{
|
||||
assert_param(rsc_initialized);
|
||||
assert_param(rsc > R_NONE && rsc < R_RESOURCE_COUNT);
|
||||
assert_param(unit != NULL);
|
||||
|
||||
if (resources[rsc].owner) {
|
||||
//TODO properly report to user
|
||||
dbg("ERROR!! Unit %s failed to claim resource %s, already held by %s!",
|
||||
unit->name, rsc_names[rsc], resources[rsc].owner->name);
|
||||
|
||||
unit->status = E_RESOURCE_NOT_AVAILABLE;
|
||||
return false;
|
||||
}
|
||||
|
||||
resources[rsc].owner = unit;
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Claim a range of resources for a unit (useful for GPIO)
|
||||
*
|
||||
* @param unit - claiming unit
|
||||
* @param rsc0 - first resource to claim
|
||||
* @param rsc1 - last resource to claim
|
||||
* @return true on complete claim, false if any failed (none are claimed in that case)
|
||||
*/
|
||||
bool rsc_claim_range(Unit *unit, Resource rsc0, Resource rsc1)
|
||||
{
|
||||
assert_param(rsc_initialized);
|
||||
assert_param(rsc0 > R_NONE && rsc0 < R_RESOURCE_COUNT);
|
||||
assert_param(rsc1 > R_NONE && rsc1 < R_RESOURCE_COUNT);
|
||||
assert_param(unit != NULL);
|
||||
|
||||
for (int i = rsc0; i <= rsc1; i++) {
|
||||
if (!rsc_claim(unit, (Resource) i)) return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Free a resource for other use
|
||||
*
|
||||
* @param unit - owning unit; if not null, free only resources claimed by this unit
|
||||
* @param rsc - resource to free
|
||||
*/
|
||||
void rsc_free(Unit *unit, Resource rsc)
|
||||
{
|
||||
assert_param(rsc_initialized);
|
||||
assert_param(rsc > R_NONE && rsc < R_RESOURCE_COUNT);
|
||||
|
||||
if (unit == NULL || resources[rsc].owner == unit) {
|
||||
resources[rsc].owner = NULL;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Free a range of resources (useful for GPIO)
|
||||
*
|
||||
* @param unit - owning unit; if not null, free only resources claimed by this unit
|
||||
* @param rsc0 - first resource to free
|
||||
* @param rsc1 - last resource to free
|
||||
*/
|
||||
void rsc_free_range(Unit *unit, Resource rsc0, Resource rsc1)
|
||||
{
|
||||
assert_param(rsc_initialized);
|
||||
assert_param(rsc0 > R_NONE && rsc0 < R_RESOURCE_COUNT);
|
||||
assert_param(rsc1 > R_NONE && rsc1 < R_RESOURCE_COUNT);
|
||||
|
||||
for (int i = rsc0; i <= rsc1; i++) {
|
||||
if (unit == NULL || resources[i].owner == unit) {
|
||||
resources[i].owner = NULL;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Tear down a unit - release all resources owned by the unit
|
||||
*
|
||||
* @param unit - unit to tear down; free only resources claimed by this unit
|
||||
*/
|
||||
void rsc_teardown(Unit *unit)
|
||||
{
|
||||
assert_param(rsc_initialized);
|
||||
assert_param(unit != NULL);
|
||||
|
||||
for (int i = R_NONE+1; i < R_RESOURCE_COUNT; i++) {
|
||||
if (resources[i].owner == unit) {
|
||||
resources[i].owner = NULL;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,80 @@
|
||||
//
|
||||
// Created by MightyPork on 2017/11/24.
|
||||
//
|
||||
|
||||
#ifndef GEX_RESOURCES_H
|
||||
#define GEX_RESOURCES_H
|
||||
|
||||
#include "platform.h"
|
||||
#include "unit.h"
|
||||
|
||||
#define CHECK_SUC() do { if (!suc) return false; } while (0)
|
||||
|
||||
// X macro: Resource name,
|
||||
#define XX_RESOURCES \
|
||||
X(NONE) \
|
||||
X(PA0) X(PA1) X(PA2) X(PA3) X(PA4) X(PA5) X(PA6) X(PA7) \
|
||||
X(PA8) X(PA9) X(PA10) X(PA11) X(PA12) X(PA13) X(PA14) X(PA15) \
|
||||
X(PB0) X(PB1) X(PB2) X(PB3) X(PB4) X(PB5) X(PB6) X(PB7) \
|
||||
X(PB8) X(PB9) X(PB10) X(PB11) X(PB12) X(PB13) X(PB14) X(PB15) \
|
||||
X(PC0) X(PC1) X(PC2) X(PC3) X(PC4) X(PC5) X(PC6) X(PC7) \
|
||||
X(PC8) X(PC9) X(PC10) X(PC11) X(PC12) X(PC13) X(PC14) X(PC15) \
|
||||
X(PD0) X(PD1) X(PD2) X(PD3) X(PD4) X(PD5) X(PD6) X(PD7) \
|
||||
X(PD8) X(PD9) X(PD10) X(PD11) X(PD12) X(PD13) X(PD14) X(PD15) \
|
||||
X(PE0) X(PE1) X(PE2) X(PE3) X(PE4) X(PE5) X(PE6) X(PE7) \
|
||||
X(PE8) X(PE9) X(PE10) X(PE11) X(PE12) X(PE13) X(PE14) X(PE15) \
|
||||
X(SPI1) X(SPI2) X(SPI3) \
|
||||
X(I2C1) X(I2C2) X(I2C3) \
|
||||
X(I2S1) X(I2S2) X(I2S3) \
|
||||
X(ADC1) X(ADC2) X(ADC3) X(ADC4) \
|
||||
X(DAC1) X(DAC2) \
|
||||
X(USART1) X(USART2) X(USART3) X(USART4) X(USART5) X(USART6) \
|
||||
X(TIM1) X(TIM2) X(TIM3) X(TIM4) X(TIM5) \
|
||||
X(TIM6) X(TIM7) X(TIM8) X(TIM9) X(TIM10) X(TIM11) X(TIM12) X(TIM13) X(TIM14) \
|
||||
X(TIM15) X(TIM16) X(TIM17) \
|
||||
X(DMA1) X(DMA2) \
|
||||
X(RNG) X(LCD)
|
||||
|
||||
// GPIOs are allocated whenever the pin is needed
|
||||
// (e.g. when used for SPI, the R_SPI resource as well as the corresponding R_GPIO resources must be claimed)
|
||||
|
||||
// Peripheral blocks (IPs) - not all chips have all blocks, usually the 1 and 2 are present as a minimum, if any.
|
||||
// It doesn't really make sense to expose multiple instances of buses that support addressing
|
||||
|
||||
// ADCs - some more advanced chips support differential input mode on some (not all!) inputs
|
||||
// Usually only one or two instances are present
|
||||
|
||||
// DAC - often only one is present, or none.
|
||||
|
||||
// UARTs
|
||||
// - 1 and 2 are present universally, 2 is connected to VCOM on Nucleo/Discovery boards, good for debug messages
|
||||
// 4 and 5 don't support synchronous mode.
|
||||
|
||||
// Timers
|
||||
// - some support quadrature input, probably all support external clock / gating / clock-out/PWM generation
|
||||
// Not all chips have all timers and not all timers are equal.
|
||||
|
||||
// DMA - Direct memory access lines - TODO split those to channels, they can be used separately
|
||||
|
||||
// The resource registry will be pre-loaded with platform-specific config of which blocks are available - the rest will be "pre-claimed"
|
||||
// (i.e. unavailable to functional modules)
|
||||
|
||||
typedef enum hw_resource Resource;
|
||||
|
||||
enum hw_resource {
|
||||
#define X(res_name) R_##res_name,
|
||||
XX_RESOURCES
|
||||
#undef X
|
||||
R_RESOURCE_COUNT
|
||||
};
|
||||
|
||||
void rsc_init_registry(void);
|
||||
|
||||
bool rsc_claim(Unit *unit, Resource rsc);
|
||||
bool rsc_claim_range(Unit *unit, Resource rsc0, Resource rsc1);
|
||||
void rsc_teardown(Unit *unit);
|
||||
|
||||
void rsc_free(Unit *unit, Resource rsc);
|
||||
void rsc_free_range(Unit *unit, Resource rsc0, Resource rsc1);
|
||||
|
||||
#endif //GEX_RESOURCES_H
|
||||
@@ -0,0 +1,236 @@
|
||||
//
|
||||
// Created by MightyPork on 2017/11/26.
|
||||
//
|
||||
|
||||
#include "platform.h"
|
||||
#include "settings.h"
|
||||
#include "unit_registry.h"
|
||||
#include "system_settings.h"
|
||||
#include "utils/str_utils.h"
|
||||
|
||||
// This is the first entry in a valid config.
|
||||
// Change with each breaking change to force config reset.
|
||||
#define CONFIG_MARKER 0xA55C
|
||||
|
||||
void settings_load(void)
|
||||
{
|
||||
dbg("Loading settings");
|
||||
|
||||
uint8_t *buffer = (uint8_t *) SETTINGS_FLASH_ADDR;
|
||||
|
||||
PayloadParser pp = pp_start(buffer, SETTINGS_BLOCK_SIZE, NULL);
|
||||
|
||||
// Check the integrity marker
|
||||
if (pp_u16(&pp) != CONFIG_MARKER) {
|
||||
dbg("Config not valid!");
|
||||
// Save for next run
|
||||
settings_save();
|
||||
return;
|
||||
}
|
||||
|
||||
// System section
|
||||
if (!systemsettings_load(&pp)) {
|
||||
dbg("!! System settings failed to load");
|
||||
return;
|
||||
}
|
||||
|
||||
if (!ureg_load_units(&pp)) {
|
||||
dbg("!! Unit settings failed to load");
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
#define SAVE_BUF_SIZE 256
|
||||
static uint8_t save_buffer[SAVE_BUF_SIZE];
|
||||
static uint32_t save_addr;
|
||||
|
||||
#if DEBUG_FLASH_WRITE
|
||||
#define fls_printf(fmt, ...) dbg(fmt, ##__VA_ARGS__)
|
||||
#else
|
||||
#define fls_printf(fmt, ...) do {} while (0)
|
||||
#endif
|
||||
|
||||
/**
|
||||
* Flush the save buffer to flash, moving leftovers from uneven half-words
|
||||
* to the beginning and adjusting the CWPack curent pointer accordingly.
|
||||
*
|
||||
* @param ctx - pack context
|
||||
* @param final - if true, flush uneven leftovers; else move them to the beginning and keep for next call.
|
||||
*/
|
||||
static void savebuf_flush(PayloadBuilder *pb, bool final)
|
||||
{
|
||||
// TODO this might be buggy, was not tested cross-boundary yet
|
||||
// TODO remove those printf's after verifying correctness
|
||||
|
||||
uint32_t bytes = (uint32_t) pb_length(pb);
|
||||
|
||||
// Dump what we're flushing
|
||||
fls_printf("Flush: ");
|
||||
for (uint32_t i = 0; i < bytes; i++) {
|
||||
fls_printf("%02X ", save_buffer[i]);
|
||||
}
|
||||
fls_printf("\r\n");
|
||||
|
||||
uint32_t halfwords = bytes >> 1;
|
||||
uint32_t remain = bytes & 1; // how many bytes won't be programmed
|
||||
|
||||
fls_printf("Halfwords: %d, Remain: %d, last? %d\r\n", (int)halfwords, (int)remain, final);
|
||||
|
||||
uint16_t *hwbuf = (void*) &save_buffer[0];
|
||||
|
||||
for (; halfwords > 0; halfwords--) {
|
||||
uint16_t hword = *hwbuf++;
|
||||
|
||||
fls_printf("%04X ", hword);
|
||||
|
||||
HAL_StatusTypeDef res = HAL_FLASH_Program(FLASH_TYPEPROGRAM_HALFWORD, save_addr, hword);
|
||||
assert_param(HAL_OK == res);
|
||||
save_addr += 2; // advance
|
||||
}
|
||||
|
||||
// rewind the context buffer
|
||||
pb->current = pb->start;
|
||||
|
||||
if (remain) {
|
||||
// We have an odd byte left to write
|
||||
if (final) {
|
||||
// We're done writing, this is the last call. Append the last byte to flash.
|
||||
uint16_t hword = save_buffer[bytes-1];
|
||||
fls_printf("& %02X ", hword);
|
||||
|
||||
HAL_StatusTypeDef res = HAL_FLASH_Program(FLASH_TYPEPROGRAM_HALFWORD, save_addr, hword);
|
||||
assert_param(HAL_OK == res);
|
||||
} else {
|
||||
// Move the leftover to the beginning of the buffer for next call.
|
||||
save_buffer[0] = save_buffer[bytes-1];
|
||||
pb->current++;
|
||||
}
|
||||
}
|
||||
fls_printf("\r\n");
|
||||
}
|
||||
|
||||
/**
|
||||
* Save buffer overflow handler.
|
||||
* This should flush whatever is in the buffer and let CWPack continue
|
||||
*
|
||||
* @param pb - buffer
|
||||
* @param more - how many more bytes are needed (this is meant for realloc / buffer expanding)
|
||||
* @return - success code
|
||||
*/
|
||||
static bool savebuf_ovhandler(PayloadBuilder *pb, uint32_t more)
|
||||
{
|
||||
if (more > SAVE_BUF_SIZE) return false;
|
||||
savebuf_flush(pb, false);
|
||||
return true;
|
||||
}
|
||||
|
||||
// Save settings to flash
|
||||
void settings_save(void)
|
||||
{
|
||||
HAL_StatusTypeDef hst;
|
||||
PayloadBuilder pb = pb_start(save_buffer, SAVE_BUF_SIZE, savebuf_ovhandler);
|
||||
|
||||
save_addr = SETTINGS_FLASH_ADDR;
|
||||
|
||||
fls_printf("--- Starting flash write... ---\r\n");
|
||||
hst = HAL_FLASH_Unlock();
|
||||
assert_param(hst == HAL_OK);
|
||||
{
|
||||
fls_printf("ERASE flash pages for settings storage...\r\n");
|
||||
// We have to first erase the pages
|
||||
FLASH_EraseInitTypeDef erase;
|
||||
erase.Banks = FLASH_BANK_1; // TODO ?????
|
||||
erase.NbPages = SETTINGS_BLOCK_SIZE/FLASH_PAGE_SIZE;
|
||||
erase.PageAddress = SETTINGS_FLASH_ADDR;
|
||||
erase.TypeErase = FLASH_TYPEERASE_PAGES;
|
||||
uint32_t pgerror = 0;
|
||||
hst = HAL_FLASHEx_Erase(&erase, &pgerror);
|
||||
assert_param(pgerror == 0xFFFFFFFFU);
|
||||
assert_param(hst == HAL_OK);
|
||||
|
||||
// and now we can start writing...
|
||||
|
||||
fls_printf("Beginning settings collect\r\n");
|
||||
|
||||
// Marker that this is a valid save
|
||||
pb_u16(&pb, CONFIG_MARKER);
|
||||
fls_printf("Saving system settings\r\n");
|
||||
systemsettings_save(&pb);
|
||||
fls_printf("Saving units\r\n");
|
||||
ureg_save_units(&pb);
|
||||
|
||||
fls_printf("Final flush\r\n");
|
||||
savebuf_flush(&pb, true);
|
||||
}
|
||||
fls_printf("Locking flash...\r\n");
|
||||
hst = HAL_FLASH_Lock();
|
||||
assert_param(hst == HAL_OK);
|
||||
fls_printf("--- Flash done ---\r\n");
|
||||
}
|
||||
|
||||
/**
|
||||
* Write system settings to INI (without section)
|
||||
*/
|
||||
void settings_write_ini(IniWriter *iw)
|
||||
{
|
||||
// File header
|
||||
iw_comment(iw, "CONFIG.INI");
|
||||
iw_comment(iw, "Changes are applied on file save and can be immediately tested and verified.");
|
||||
iw_comment(iw, "To persist to flash, replace the LOCK jumper before disconnecting from USB."); // TODO the jumper...
|
||||
|
||||
systemsettings_write_ini(iw);
|
||||
iw_newline(iw);
|
||||
|
||||
ureg_export_combined(iw);
|
||||
}
|
||||
|
||||
|
||||
void settings_read_ini_begin(void)
|
||||
{
|
||||
SystemSettings.modified = true;
|
||||
|
||||
// load defaults
|
||||
systemsettings_init();
|
||||
ureg_remove_all_units();
|
||||
}
|
||||
|
||||
void settings_read_ini(const char *restrict section, const char *restrict key, const char *restrict value)
|
||||
{
|
||||
// dbg("[%s] %s = %s", section, key, value);
|
||||
|
||||
if (streq(section, "SYSTEM")) {
|
||||
// system is always at the top
|
||||
systemsettings_read_ini(key, value);
|
||||
}
|
||||
else if (streq(section, "UNITS")) {
|
||||
// this will always come before individual units config
|
||||
// install or tear down units as described by the config
|
||||
ureg_instantiate_by_ini(key, value);
|
||||
} else {
|
||||
// not a standard section, may be some unit config
|
||||
// all unit sections contain the colon character [TYPE:NAME]
|
||||
const char *nameptr = strchr(section, ':');
|
||||
if (nameptr) {
|
||||
ureg_read_unit_ini(nameptr+1, key, value);
|
||||
} else {
|
||||
dbg("! Bad config key: [%s] %s = %s", section, key, value);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void settings_read_ini_end(void)
|
||||
{
|
||||
if (!ureg_finalize_all_init()) {
|
||||
dbg("Some units failed to init!!");
|
||||
}
|
||||
}
|
||||
|
||||
uint32_t settings_get_ini_len(void)
|
||||
{
|
||||
// this writer is configured to skip everything, so each written byte will decrement the skip count
|
||||
IniWriter iw = iw_init(NULL, 0xFFFFFFFF, 1);
|
||||
settings_write_ini(&iw);
|
||||
// now we just check how many bytes were skipped
|
||||
return 0xFFFFFFFF - iw.skip;
|
||||
}
|
||||
@@ -0,0 +1,66 @@
|
||||
//
|
||||
// Created by MightyPork on 2017/11/26.
|
||||
//
|
||||
|
||||
#ifndef GEX_SETTINGS_H
|
||||
#define GEX_SETTINGS_H
|
||||
|
||||
#include "platform.h"
|
||||
#include "utils/ini_writer.h"
|
||||
|
||||
/**
|
||||
* Load settings from flash (system settings + units).
|
||||
* Unit registry must be already initialized.
|
||||
*
|
||||
* This should happen only once, during the boot sequence.
|
||||
*/
|
||||
void settings_load(void);
|
||||
|
||||
/**
|
||||
* Save all settings to flash.
|
||||
* This may be called multiple times after user changes the config file.
|
||||
*/
|
||||
void settings_save(void);
|
||||
|
||||
|
||||
/**
|
||||
* Call this before any of the ini read stuff
|
||||
*
|
||||
* Resets everything to defaults so we have a clean start.
|
||||
*
|
||||
* NOTE: Should the file be received only partially, this may corrupt the settings.
|
||||
* For this reason we don't commit it to flash immediately but require user to replace
|
||||
* the LOCK jumper before unplugging the device. (TODO implement the LOCK jumper and this feature!!)
|
||||
*/
|
||||
void settings_read_ini_begin(void);
|
||||
|
||||
/**
|
||||
* Load settings from INI kv pair.
|
||||
*/
|
||||
void settings_read_ini(const char *restrict section, const char *restrict key, const char *restrict value);
|
||||
|
||||
/**
|
||||
* Call this before any of the ini read stuff
|
||||
*
|
||||
* Resets everything to defaults so we have a clean start.
|
||||
*
|
||||
* NOTE: Should the file be received only partially, this may corrupt the settings.
|
||||
* For this reason we don't commit it to flash immediately but require user to replace
|
||||
* the LOCK jumper before unplugging the device. (TODO implement the LOCK jumper and this feature!!)
|
||||
*/
|
||||
void settings_read_ini_end(void);
|
||||
|
||||
/**
|
||||
* Write all settings to a iniwriter
|
||||
* @param iw - writer handle
|
||||
*/
|
||||
void settings_write_ini(IniWriter *iw);
|
||||
|
||||
/**
|
||||
* Get total settings len (caution: this is expensive, works by dummy-printing everything)
|
||||
*
|
||||
* @return bytes
|
||||
*/
|
||||
uint32_t settings_get_ini_len(void);
|
||||
|
||||
#endif //GEX_SETTINGS_H
|
||||
@@ -0,0 +1,57 @@
|
||||
//
|
||||
// Created by MightyPork on 2017/12/02.
|
||||
//
|
||||
|
||||
#include "platform.h"
|
||||
#include "utils/str_utils.h"
|
||||
#include "system_settings.h"
|
||||
|
||||
struct system_settings SystemSettings;
|
||||
|
||||
void systemsettings_init(void)
|
||||
{
|
||||
SystemSettings.visible_vcom = true;
|
||||
SystemSettings.editable = false; // This will be loaded in platform init based on the LOCK pin
|
||||
SystemSettings.modified = false;
|
||||
}
|
||||
|
||||
// to binary
|
||||
void systemsettings_save(PayloadBuilder *pb)
|
||||
{
|
||||
pb_char(pb, 'S');
|
||||
pb_bool(pb, SystemSettings.visible_vcom);
|
||||
}
|
||||
|
||||
// from binary
|
||||
bool systemsettings_load(PayloadParser *pp)
|
||||
{
|
||||
if (pp_char(pp) != 'S') return false;
|
||||
SystemSettings.visible_vcom = pp_bool(pp);
|
||||
|
||||
return pp->ok;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Write system settings to INI (without section)
|
||||
*/
|
||||
void systemsettings_write_ini(IniWriter *iw)
|
||||
{
|
||||
iw_section(iw, "SYSTEM");
|
||||
iw_comment(iw, "Expose the comm. channel as a virtual comport (Y, N)");
|
||||
iw_entry(iw, "expose_vcom", str_yn(SystemSettings.visible_vcom));
|
||||
}
|
||||
|
||||
/**
|
||||
* Load system settings from INI kv pair
|
||||
*/
|
||||
bool systemsettings_read_ini(const char *restrict key, const char *restrict value)
|
||||
{
|
||||
bool suc = true;
|
||||
if (streq(key, "expose_vcom")) {
|
||||
bool yn = str_parse_yn(value, &suc);
|
||||
if (suc) SystemSettings.visible_vcom = yn;
|
||||
}
|
||||
|
||||
return suc;
|
||||
}
|
||||
@@ -0,0 +1,50 @@
|
||||
//
|
||||
// Created by MightyPork on 2017/12/02.
|
||||
//
|
||||
|
||||
#ifndef GEX_SYSTEM_SETTINGS_H
|
||||
#define GEX_SYSTEM_SETTINGS_H
|
||||
|
||||
#include "platform.h"
|
||||
#include "utils/ini_writer.h"
|
||||
#include "utils/payload_parser.h"
|
||||
#include "utils/payload_builder.h"
|
||||
|
||||
struct system_settings {
|
||||
bool visible_vcom;
|
||||
|
||||
// Support flags put here for scoping, but not atcually part of the persistent settings
|
||||
volatile bool editable; //!< True if we booted with the LOCK jumper removed
|
||||
volatile bool modified; //!< True if user did any change to the settings (checked when the LOCK jumper is replaced)
|
||||
};
|
||||
|
||||
extern struct system_settings SystemSettings;
|
||||
|
||||
/**
|
||||
* Load defaults
|
||||
*/
|
||||
void systemsettings_init(void);
|
||||
|
||||
/**
|
||||
* Write system settings to the pack context
|
||||
*/
|
||||
void systemsettings_save(PayloadBuilder *pb);
|
||||
|
||||
/**
|
||||
* Load system settings from the unpack context
|
||||
*/
|
||||
bool systemsettings_load(PayloadParser *pp);
|
||||
|
||||
/**
|
||||
* Write system settings to INI
|
||||
*/
|
||||
void systemsettings_write_ini(IniWriter *iw);
|
||||
|
||||
/**
|
||||
* Load system settings from INI kv pair
|
||||
*
|
||||
* @return true on success
|
||||
*/
|
||||
bool systemsettings_read_ini(const char *restrict key, const char *restrict value);
|
||||
|
||||
#endif //GEX_SYSTEM_SETTINGS_H
|
||||
@@ -0,0 +1,47 @@
|
||||
//
|
||||
// Created by MightyPork on 2017/11/24.
|
||||
//
|
||||
|
||||
#include "platform.h"
|
||||
#include "unit.h"
|
||||
#include "resources.h"
|
||||
|
||||
// Abort partly inited unit
|
||||
void clean_failed_unit(Unit *unit)
|
||||
{
|
||||
if (unit == NULL) return;
|
||||
|
||||
dbg("!! Init of [%s] failed!", unit->name);
|
||||
|
||||
// Free if it looks like it might've been allocated
|
||||
if (isDynAlloc(unit->data)) {
|
||||
dbg("Freeing allocated unit data");
|
||||
free(unit->data);
|
||||
unit->data = NULL;
|
||||
}
|
||||
if (isDynAlloc(unit->name)) {
|
||||
dbg("Freeing allocated name");
|
||||
free((void *) unit->name);
|
||||
unit->name = NULL;
|
||||
}
|
||||
|
||||
dbg("Releasing any held resources");
|
||||
// Release any already claimed resources
|
||||
rsc_teardown(unit);
|
||||
}
|
||||
|
||||
// ----------------------------------------------------
|
||||
|
||||
// system unit is used to claim peripherals on behalf of the system (e.g. HAL tick source)
|
||||
Unit UNIT_SYSTEM = {
|
||||
.name = "SYSTEM"
|
||||
};
|
||||
|
||||
// ----------------------------------------------------
|
||||
|
||||
// platform unit is used to claim peripherals not present on the current platform
|
||||
Unit UNIT_PLATFORM = {
|
||||
.name = "PLATFORM"
|
||||
};
|
||||
|
||||
// ----------------------------------------------------
|
||||
@@ -0,0 +1,118 @@
|
||||
//
|
||||
// Created by MightyPork on 2017/11/24.
|
||||
//
|
||||
|
||||
#ifndef GEX_UNIT_H
|
||||
#define GEX_UNIT_H
|
||||
|
||||
#include "platform.h"
|
||||
#include <TinyFrame.h>
|
||||
#include "utils/ini_writer.h"
|
||||
#include "utils/payload_builder.h"
|
||||
#include "utils/payload_parser.h"
|
||||
|
||||
typedef struct unit Unit;
|
||||
typedef struct unit_driver UnitDriver;
|
||||
|
||||
struct unit {
|
||||
const UnitDriver *driver;
|
||||
|
||||
/** Unit name (used in error messages) */
|
||||
const char *name;
|
||||
|
||||
/**
|
||||
* Storage for arbitrary unit data (allocated in 'preInit' and freed in 'deInit' or when init/load fails)
|
||||
*/
|
||||
void *data;
|
||||
|
||||
/** Unit init status */
|
||||
error_t status;
|
||||
|
||||
/** Unit call sign for messages */
|
||||
uint8_t callsign;
|
||||
};
|
||||
|
||||
/**
|
||||
* Unit instance - statically or dynamically allocated (depends whether it's system or user unit)
|
||||
*/
|
||||
struct unit_driver {
|
||||
/** Driver ID */
|
||||
const char *name;
|
||||
|
||||
/** Unit type description (for use in comments) */
|
||||
const char *description;
|
||||
|
||||
/**
|
||||
* Pre-init: allocate data object, init defaults
|
||||
*/
|
||||
bool (*preInit)(Unit *unit);
|
||||
|
||||
/**
|
||||
* Load settings from binary storage, parse and store them in the data object.
|
||||
* Don't do any validation, that's left for the init() function
|
||||
*
|
||||
* @param pp - parser
|
||||
*/
|
||||
void (*cfgLoadBinary)(Unit *unit, PayloadParser *pp);
|
||||
|
||||
/**
|
||||
* Write settings to binary storage.
|
||||
*
|
||||
* @param pb - builder
|
||||
*/
|
||||
void (*cfgWriteBinary)(Unit *unit, PayloadBuilder *pb);
|
||||
|
||||
/**
|
||||
* Load settings from a INI file.
|
||||
* Name has already been parsed and assigned.
|
||||
* This function is called repeatedly as kv-pairs are encountered in the stream.
|
||||
*
|
||||
* @param key - key from the INI file
|
||||
* @param value - value from the ini file; strings have already removed quotes and replaced escape sequences with ASCII as needed
|
||||
*/
|
||||
bool (*cfgLoadIni)(Unit *unit, const char *key, const char *value);
|
||||
|
||||
/**
|
||||
* Export settings to a INI file.
|
||||
* Capacity will likely be 512 bytes, do not waste space!
|
||||
*
|
||||
* @param buffer - destination buffer
|
||||
* @param capacity - buffer size
|
||||
* @return nubmer of bytes used
|
||||
*/
|
||||
void (*cfgWriteIni)(Unit *unit, IniWriter *iw);
|
||||
|
||||
/**
|
||||
* Finalize the init sequence, validate settings, enable peripherals and prepare for operation
|
||||
*/
|
||||
bool (*init)(Unit *unit);
|
||||
|
||||
/**
|
||||
* De-initialize the unit: de-init peripheral, free resources, free data object...
|
||||
* This is called when disabling all units in order to reload new config.
|
||||
*/
|
||||
void (*deInit)(Unit *unit);
|
||||
|
||||
/**
|
||||
* Handle an incoming request. Return true if command was OK.
|
||||
*/
|
||||
bool (*handleRequest)(Unit *unit, TF_ID frame_id, uint8_t command, PayloadParser *pp);
|
||||
};
|
||||
|
||||
/**
|
||||
* De-init a partially initialized unit (before 'init' succeeds)
|
||||
* This releases all held resources and frees the *data,
|
||||
* if it looks like it has been dynamically allocated.
|
||||
*
|
||||
* Does NOT free the unit struct itself
|
||||
*
|
||||
* @param unit - unit to discard
|
||||
*/
|
||||
void clean_failed_unit(Unit *unit);
|
||||
|
||||
/** Marks peripherals claimed by the system */
|
||||
extern Unit UNIT_SYSTEM;
|
||||
/** Marks peripherals not available on the platform */
|
||||
extern Unit UNIT_PLATFORM;
|
||||
|
||||
#endif //GEX_UNIT_H
|
||||
@@ -0,0 +1,12 @@
|
||||
//
|
||||
// Created by MightyPork on 2017/12/09.
|
||||
//
|
||||
|
||||
#include "platform.h"
|
||||
#include "unit.h"
|
||||
#include "pin_utils.h"
|
||||
#include "resources.h"
|
||||
#include "utils/str_utils.h"
|
||||
#include "utils/malloc_safe.h"
|
||||
#include "payload_builder.h"
|
||||
#include "payload_parser.h"
|
||||
@@ -0,0 +1,579 @@
|
||||
//
|
||||
// Created by MightyPork on 2017/11/26.
|
||||
//
|
||||
|
||||
#include "platform.h"
|
||||
#include "utils/avrlibc.h"
|
||||
#include "comm/messages.h"
|
||||
#include "utils/ini_writer.h"
|
||||
#include "utils/str_utils.h"
|
||||
#include "utils/malloc_safe.h"
|
||||
#include "unit_registry.h"
|
||||
#include "resources.h"
|
||||
|
||||
// ** Unit repository **
|
||||
|
||||
typedef struct ureg_entry UregEntry;
|
||||
typedef struct ulist_entry UlistEntry;
|
||||
|
||||
struct ureg_entry {
|
||||
const UnitDriver *driver;
|
||||
UregEntry *next;
|
||||
};
|
||||
|
||||
UregEntry *ureg_head = NULL;
|
||||
UregEntry *ureg_tail = NULL;
|
||||
|
||||
// ---
|
||||
|
||||
struct ulist_entry {
|
||||
Unit unit;
|
||||
UlistEntry *next;
|
||||
};
|
||||
|
||||
UlistEntry *ulist_head = NULL;
|
||||
UlistEntry *ulist_tail = NULL;
|
||||
|
||||
// ---
|
||||
|
||||
void ureg_add_type(const UnitDriver *driver)
|
||||
{
|
||||
assert_param(driver != NULL);
|
||||
assert_param(driver->description != NULL);
|
||||
assert_param(driver->name != NULL);
|
||||
assert_param(driver->preInit != NULL);
|
||||
assert_param(driver->cfgLoadBinary != NULL);
|
||||
assert_param(driver->cfgLoadIni != NULL);
|
||||
assert_param(driver->cfgWriteBinary != NULL);
|
||||
assert_param(driver->cfgWriteIni != NULL);
|
||||
assert_param(driver->init != NULL);
|
||||
assert_param(driver->deInit != NULL);
|
||||
assert_param(driver->handleRequest != NULL);
|
||||
|
||||
UregEntry *re = malloc_s(sizeof(UregEntry));
|
||||
re->driver = driver;
|
||||
re->next = NULL;
|
||||
|
||||
if (ureg_head == NULL) {
|
||||
ureg_head = re;
|
||||
} else {
|
||||
ureg_tail->next = re;
|
||||
}
|
||||
ureg_tail = re;
|
||||
}
|
||||
|
||||
/** Free unit in a list entry (do not free the list entry itself!) */
|
||||
static void free_le_unit(UlistEntry *le)
|
||||
{
|
||||
Unit *const pUnit = &le->unit;
|
||||
|
||||
pUnit->driver->deInit(pUnit);
|
||||
// Name is not expected to be freed by the deInit() function
|
||||
// - was alloc'd in the settings load loop
|
||||
if (isDynAlloc(pUnit->name)) {
|
||||
dbg("Freeing allocated name");
|
||||
free((void *) pUnit->name);
|
||||
pUnit->name = NULL;
|
||||
}
|
||||
}
|
||||
|
||||
/** Remove a unit and update links appropriately */
|
||||
static void remove_unit_from_list(UlistEntry *restrict le, UlistEntry *restrict parent)
|
||||
{
|
||||
if (parent == NULL) {
|
||||
ulist_head = le->next;
|
||||
} else {
|
||||
parent->next = le->next;
|
||||
}
|
||||
|
||||
// Fix tail potentially pointing to the removed entry
|
||||
if (ulist_tail == le) {
|
||||
ulist_tail = parent;
|
||||
}
|
||||
}
|
||||
|
||||
/** Add unit to the list, updating references as needed */
|
||||
static void add_unit_to_list(UlistEntry *le)
|
||||
{
|
||||
// Attach to the list
|
||||
if (ulist_head == NULL) {
|
||||
ulist_head = le;
|
||||
} else {
|
||||
ulist_tail->next = le;
|
||||
}
|
||||
ulist_tail = le;
|
||||
}
|
||||
|
||||
/** Find a unit in the list */
|
||||
static UlistEntry *find_unit(const Unit *unit, UlistEntry **pParent)
|
||||
{
|
||||
UlistEntry *le = ulist_head;
|
||||
UlistEntry *parent = NULL;
|
||||
while (le != NULL) {
|
||||
if (&le->unit == unit) {
|
||||
if (pParent != NULL) {
|
||||
*pParent = parent;
|
||||
}
|
||||
return le;
|
||||
}
|
||||
|
||||
parent = le;
|
||||
le = le->next;
|
||||
}
|
||||
|
||||
dbg("!! Unit was not found in registry");
|
||||
*pParent = NULL;
|
||||
return NULL;
|
||||
}
|
||||
|
||||
// create a unit instance (not yet loading or initing - just pre-init)
|
||||
Unit *ureg_instantiate(const char *driver_name)
|
||||
{
|
||||
bool suc = true;
|
||||
|
||||
dbg("Creating unit of type %s", driver_name);
|
||||
|
||||
// Find type in the repository
|
||||
UregEntry *re = ureg_head;
|
||||
while (re != NULL) {
|
||||
if (streq(re->driver->name, driver_name)) {
|
||||
// Create new list entry
|
||||
UlistEntry *le = malloc_ck(sizeof(UlistEntry), &suc);
|
||||
CHECK_SUC();
|
||||
|
||||
le->next = NULL;
|
||||
|
||||
Unit *pUnit = &le->unit;
|
||||
pUnit->driver = re->driver;
|
||||
pUnit->status = E_LOADING;
|
||||
pUnit->data = NULL;
|
||||
pUnit->callsign = 0;
|
||||
|
||||
suc = pUnit->driver->preInit(pUnit);
|
||||
if (!suc) {
|
||||
// tear down what we already allocated and abort
|
||||
|
||||
// If it failed this early, the only plausible explanation is failed malloc,
|
||||
// in which case the data structure is not populated and keeping the
|
||||
// broken unit doesn't serve any purpose. Just ditch it...
|
||||
|
||||
dbg("!! Unit failed to pre-init!");
|
||||
clean_failed_unit(pUnit);
|
||||
free(le);
|
||||
return NULL;
|
||||
}
|
||||
|
||||
add_unit_to_list(le);
|
||||
return pUnit;
|
||||
}
|
||||
re = re->next;
|
||||
}
|
||||
|
||||
dbg("!! Did not find unit type %s", driver_name);
|
||||
return NULL;
|
||||
}
|
||||
|
||||
// remove before init()
|
||||
void ureg_clean_failed(Unit *unit)
|
||||
{
|
||||
dbg("Cleaning failed unit from registry");
|
||||
|
||||
UlistEntry *le;
|
||||
UlistEntry *parent;
|
||||
le = find_unit(unit, &parent);
|
||||
if (!le) return;
|
||||
|
||||
clean_failed_unit(&le->unit);
|
||||
|
||||
remove_unit_from_list(le, parent);
|
||||
|
||||
free(le);
|
||||
}
|
||||
|
||||
// remove after successful init()
|
||||
void ureg_remove_unit(Unit *unit)
|
||||
{
|
||||
dbg("Cleaning & removing unit from registry");
|
||||
|
||||
UlistEntry *le;
|
||||
UlistEntry *parent;
|
||||
le = find_unit(unit, &parent);
|
||||
if (!le) return;
|
||||
|
||||
free_le_unit(le);
|
||||
|
||||
remove_unit_from_list(le, parent);
|
||||
free(le);
|
||||
}
|
||||
|
||||
void ureg_save_units(PayloadBuilder *pb)
|
||||
{
|
||||
assert_param(pb->ok);
|
||||
|
||||
uint32_t count = ureg_get_num_units();
|
||||
|
||||
pb_char(pb, 'U');
|
||||
pb_u16(pb, (uint16_t) count);
|
||||
|
||||
UlistEntry *le = ulist_head;
|
||||
while (le != NULL) {
|
||||
Unit *const pUnit = &le->unit;
|
||||
pb_char(pb, 'u');
|
||||
pb_string(pb, pUnit->driver->name);
|
||||
pb_string(pb, pUnit->name);
|
||||
pb_u8(pb, pUnit->callsign);
|
||||
|
||||
// Now all the rest, unit-specific
|
||||
pUnit->driver->cfgWriteBinary(pUnit, pb);
|
||||
assert_param(pb->ok);
|
||||
le = le->next;
|
||||
}
|
||||
}
|
||||
|
||||
bool ureg_load_units(PayloadParser *pp)
|
||||
{
|
||||
bool suc;
|
||||
char typebuf[16];
|
||||
|
||||
assert_param(pp->ok);
|
||||
|
||||
if (pp_char(pp) != 'U') return false;
|
||||
uint16_t unit_count = pp_u16(pp);
|
||||
|
||||
for (uint32_t j = 0; j < unit_count; j++) {
|
||||
// We're now unpacking a single unit
|
||||
|
||||
// Marker that this is a unit - it could get out of alignment if structure changed
|
||||
if (pp_char(pp) != 'u') return false;
|
||||
|
||||
// TYPE
|
||||
pp_string(pp, typebuf, 16);
|
||||
Unit *const pUnit = ureg_instantiate(typebuf);
|
||||
assert_param(pUnit);
|
||||
|
||||
// NAME
|
||||
pp_string(pp, typebuf, 16);
|
||||
pUnit->name = strdup(typebuf);
|
||||
assert_param(pUnit->name);
|
||||
|
||||
// CALLSIGN
|
||||
pUnit->callsign = pp_u8(pp);
|
||||
assert_param(pUnit->callsign != 0);
|
||||
|
||||
// Load the rest of the unit
|
||||
pUnit->driver->cfgLoadBinary(pUnit, pp);
|
||||
assert_param(pp->ok);
|
||||
|
||||
suc = pUnit->driver->init(pUnit); // finalize the load and init the unit
|
||||
if (pUnit->status == E_LOADING) {
|
||||
pUnit->status = suc ? E_SUCCESS : E_BAD_CONFIG;
|
||||
}
|
||||
|
||||
// XXX we want to keep the failed unit to preserve settings and for error reporting
|
||||
// if (!suc) {
|
||||
// // Discard, remove from registry
|
||||
// ureg_clean_failed(unit);
|
||||
// }
|
||||
}
|
||||
|
||||
return pp->ok;
|
||||
}
|
||||
|
||||
|
||||
void ureg_remove_all_units(void)
|
||||
{
|
||||
UlistEntry *le = ulist_head;
|
||||
UlistEntry *next;
|
||||
while (le != NULL) {
|
||||
next = le->next;
|
||||
|
||||
free_le_unit(le);
|
||||
free(le);
|
||||
|
||||
le = next;
|
||||
}
|
||||
|
||||
ulist_head = ulist_tail = NULL;
|
||||
}
|
||||
|
||||
|
||||
bool ureg_instantiate_by_ini(const char *restrict driver_name, const char *restrict names)
|
||||
{
|
||||
UregEntry *re = ureg_head;
|
||||
while (re != NULL) {
|
||||
if (streq(re->driver->name, driver_name)) {
|
||||
const char *p = names;
|
||||
while (p != NULL) { // we use this to indicate we're done
|
||||
// skip leading whitespace (assume there's never whitespace before a comma)
|
||||
while (*p == ' ' || *p == '\t') p++;
|
||||
if (*p == 0) break; // out of characters
|
||||
|
||||
const char *delim = strchr(p, ',');
|
||||
char *name = NULL;
|
||||
if (delim != NULL) {
|
||||
// not last
|
||||
name = strndup(p, delim - p);
|
||||
p = delim + 1;
|
||||
} else {
|
||||
// last name
|
||||
name = strdup(p);
|
||||
p = NULL; // quit after this loop ends
|
||||
}
|
||||
assert_param(name);
|
||||
Unit *pUnit = ureg_instantiate(driver_name);
|
||||
if (!pUnit) {
|
||||
free(name);
|
||||
return false;
|
||||
}
|
||||
|
||||
pUnit->name = name;
|
||||
|
||||
// don't init yet - leave that for when we're done with the INI
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
re = re->next;
|
||||
}
|
||||
|
||||
dbg("! ureg instantiate - bad type");
|
||||
return false;
|
||||
}
|
||||
|
||||
bool ureg_read_unit_ini(const char *restrict name,
|
||||
const char *restrict key,
|
||||
const char *restrict value)
|
||||
{
|
||||
UlistEntry *li = ulist_head;
|
||||
while (li != NULL) {
|
||||
if (streq(li->unit.name, name)) {
|
||||
Unit *const pUnit = &li->unit;
|
||||
|
||||
if (streq(key, "CALLSIGN")) {
|
||||
// handled separately from unit data
|
||||
pUnit->callsign = (uint8_t) avr_atoi(value);
|
||||
return true;
|
||||
} else {
|
||||
return pUnit->driver->cfgLoadIni(pUnit, key, value);
|
||||
}
|
||||
}
|
||||
|
||||
li = li->next;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
bool ureg_finalize_all_init(void)
|
||||
{
|
||||
dbg("Finalizing units init...");
|
||||
bool suc = true;
|
||||
UlistEntry *li = ulist_head;
|
||||
uint8_t callsign = 1;
|
||||
while (li != NULL) {
|
||||
Unit *const pUnit = &li->unit;
|
||||
|
||||
bool s = pUnit->driver->init(pUnit);
|
||||
if (!s) {
|
||||
dbg("!!!! error initing unit %s", pUnit->name);
|
||||
if (pUnit->status == E_LOADING) {
|
||||
// assume it's a config error if not otherwise specified
|
||||
pUnit->status = E_BAD_CONFIG;
|
||||
}
|
||||
} else {
|
||||
pUnit->status = E_SUCCESS;
|
||||
}
|
||||
|
||||
// try to assign unique callsigns
|
||||
if (pUnit->callsign == 0) {
|
||||
pUnit->callsign = callsign++;
|
||||
} else {
|
||||
if (pUnit->callsign >= callsign) {
|
||||
callsign = (uint8_t) (pUnit->callsign + 1);
|
||||
}
|
||||
}
|
||||
|
||||
suc &= s;
|
||||
li = li->next;
|
||||
}
|
||||
return suc;
|
||||
}
|
||||
|
||||
static void export_unit_do(UlistEntry *li, IniWriter *iw)
|
||||
{
|
||||
Unit *const pUnit = &li->unit;
|
||||
|
||||
iw_section(iw, "%s:%s", pUnit->driver->name, pUnit->name);
|
||||
iw_comment(iw, ">> Status: %s", error_get_string(pUnit->status));
|
||||
iw_newline(iw);
|
||||
iw_comment(iw, "Address for control messages (1-255)");
|
||||
iw_entry(iw, "CALLSIGN", "%d", pUnit->callsign);
|
||||
|
||||
pUnit->driver->cfgWriteIni(pUnit, iw);
|
||||
iw_newline(iw);
|
||||
}
|
||||
|
||||
// unit to INI
|
||||
void ureg_export_unit(uint32_t index, IniWriter *iw)
|
||||
{
|
||||
UlistEntry *li = ulist_head;
|
||||
uint32_t count = 0;
|
||||
while (li != NULL) {
|
||||
if (count == index) {
|
||||
export_unit_do(li, iw);
|
||||
return;
|
||||
}
|
||||
|
||||
count++;
|
||||
li = li->next;
|
||||
}
|
||||
}
|
||||
|
||||
// unit to INI
|
||||
void ureg_export_combined(IniWriter *iw)
|
||||
{
|
||||
UlistEntry *li;
|
||||
UregEntry *re;
|
||||
|
||||
// Unit list
|
||||
iw_section(iw, "UNITS");
|
||||
iw_comment(iw, "Here is a list of all unit types supported by the current firmware.");
|
||||
iw_comment(iw, "To manage units, simply add/remove their comma-separated names next to");
|
||||
iw_comment(iw, "the desired unit type. Reload the file and the corresponding unit");
|
||||
iw_comment(iw, "sections should appear below, ready to configure.");
|
||||
|
||||
// This could certainly be done in some more efficient way ...
|
||||
re = ureg_head;
|
||||
while (re != NULL) {
|
||||
// Should produce something like:
|
||||
|
||||
// # Description string here
|
||||
// TYPE_ID=NAME1,NAME2
|
||||
//
|
||||
|
||||
const UnitDriver *const pDriver = re->driver;
|
||||
|
||||
iw_newline(iw);
|
||||
iw_comment(iw, pDriver->description);
|
||||
iw_string(iw, pDriver->name);
|
||||
iw_string(iw, "=");
|
||||
|
||||
li = ulist_head;
|
||||
uint32_t count = 0;
|
||||
while (li != NULL) {
|
||||
Unit *const pUnit = &li->unit;
|
||||
if (streq(pUnit->driver->name, pDriver->name)) {
|
||||
if (count > 0) iw_string(iw, ",");
|
||||
iw_string(iw, pUnit->name);
|
||||
count++;
|
||||
}
|
||||
li = li->next;
|
||||
}
|
||||
re = re->next;
|
||||
iw_newline(iw);
|
||||
}
|
||||
iw_newline(iw); // space before the unit sections
|
||||
|
||||
// Now we dump all the units
|
||||
li = ulist_head;
|
||||
while (li != NULL) {
|
||||
export_unit_do(li, iw);
|
||||
li = li->next;
|
||||
}
|
||||
}
|
||||
|
||||
// count units
|
||||
uint32_t ureg_get_num_units(void)
|
||||
{
|
||||
// TODO keep this in a variable
|
||||
UlistEntry *li = ulist_head;
|
||||
uint32_t count = 0;
|
||||
while (li != NULL) {
|
||||
count++;
|
||||
li = li->next;
|
||||
}
|
||||
|
||||
return count;
|
||||
}
|
||||
|
||||
static void job_nosuch_unit(Job *job)
|
||||
{
|
||||
tf_respond_snprintf(MSG_ERROR, job->frame_id, "NO UNIT @ %"PRIu32, job->d32);
|
||||
}
|
||||
|
||||
/** Deliver message to it's destination unit */
|
||||
void ureg_deliver_unit_request(TF_Msg *msg)
|
||||
{
|
||||
PayloadParser pp = pp_start(msg->data, msg->len, NULL);
|
||||
uint8_t callsign = pp_u8(&pp);
|
||||
uint8_t command = pp_u8(&pp);
|
||||
|
||||
// highest bit indicates user wants an extra confirmation on success
|
||||
bool confirmed = (bool) (command & 0x80);
|
||||
command &= 0x7F;
|
||||
|
||||
if (!pp.ok) { dbg("!! pp not OK!"); }
|
||||
|
||||
if (callsign == 0 || !pp.ok) {
|
||||
sched_respond_malformed_cmd(msg->frame_id);
|
||||
return;
|
||||
}
|
||||
|
||||
UlistEntry *li = ulist_head;
|
||||
while (li != NULL) {
|
||||
Unit *const pUnit = &li->unit;
|
||||
if (pUnit->callsign == callsign) {
|
||||
bool ok = pUnit->driver->handleRequest(pUnit, msg->frame_id, command, &pp);
|
||||
if (ok && confirmed) {
|
||||
sched_respond_suc(msg->frame_id);
|
||||
}
|
||||
return;
|
||||
}
|
||||
li = li->next;
|
||||
}
|
||||
|
||||
// Not found
|
||||
Job job = {
|
||||
.cb = job_nosuch_unit,
|
||||
.frame_id = msg->frame_id,
|
||||
.d32 = callsign
|
||||
};
|
||||
scheduleJob(&job, TSK_SCHED_LOW);
|
||||
}
|
||||
|
||||
|
||||
void ureg_report_active_units(TF_ID frame_id)
|
||||
{
|
||||
// count bytes needed
|
||||
uint32_t needed = 1; //
|
||||
|
||||
UlistEntry *li = ulist_head;
|
||||
uint32_t count = 0;
|
||||
while (li != NULL) {
|
||||
count++;
|
||||
needed += strlen(li->unit.name)+1;
|
||||
li = li->next;
|
||||
}
|
||||
needed += count;
|
||||
|
||||
bool suc = true;
|
||||
uint8_t *buff = malloc_ck(needed, &suc);
|
||||
if (!suc) { tf_respond_str(MSG_ERROR, frame_id, "OUT OF MEMORY"); return; }
|
||||
|
||||
{
|
||||
PayloadBuilder pb = pb_start(buff, needed, NULL);
|
||||
pb_u8(&pb, (uint8_t) count); // assume we don't have more than 255
|
||||
|
||||
li = ulist_head;
|
||||
while (li != NULL) {
|
||||
pb_u8(&pb, li->unit.callsign);
|
||||
pb_string(&pb, li->unit.name);
|
||||
li = li->next;
|
||||
}
|
||||
|
||||
assert_param(pb.ok);
|
||||
|
||||
tf_respond_buf(MSG_SUCCESS, frame_id, buff, needed);
|
||||
}
|
||||
|
||||
free(buff);
|
||||
}
|
||||
@@ -0,0 +1,128 @@
|
||||
//
|
||||
// Created by MightyPork on 2017/11/26.
|
||||
//
|
||||
|
||||
#ifndef GEX_UNIT_REGISTRY_H
|
||||
#define GEX_UNIT_REGISTRY_H
|
||||
|
||||
#include <TinyFrame/TinyFrame.h>
|
||||
#include "platform.h"
|
||||
#include "unit.h"
|
||||
|
||||
/**
|
||||
* Add instantiable unit type to the registry
|
||||
*
|
||||
* @param driver - unit template, will be shallowly cloned for new instances
|
||||
*/
|
||||
void ureg_add_type(const UnitDriver *driver);
|
||||
|
||||
/**
|
||||
* Create an instance of a unit type. The unit is added to the unit list.
|
||||
*
|
||||
* @param driver_name - unit type, same as given when registering the type. CAN BE ON STACK! Not stored.
|
||||
* @return the unit, or NULL on failure
|
||||
*/
|
||||
Unit *ureg_instantiate(const char *driver_name);
|
||||
|
||||
/**
|
||||
* Clean a unit previously obtained by 'ureg_instantiate' that
|
||||
* failed to properly load or init. This tears it down and removes it from the unit list.
|
||||
*
|
||||
* @param unit - unit to remove
|
||||
*/
|
||||
void ureg_clean_failed(Unit *unit);
|
||||
|
||||
/**
|
||||
* Safely delete a unit instance (releasing memory and reosurces, removing it from the list)
|
||||
*
|
||||
* @param unit - unit to remove
|
||||
*/
|
||||
void ureg_remove_unit(Unit *unit);
|
||||
|
||||
/**
|
||||
* De-init and remove all units
|
||||
*/
|
||||
void ureg_remove_all_units(void);
|
||||
|
||||
/**
|
||||
* Save all units to a binary buffer
|
||||
* @param ctx
|
||||
*/
|
||||
void ureg_save_units(PayloadBuilder *pb);
|
||||
|
||||
/**
|
||||
* Load units from the binary format
|
||||
* @param ctx
|
||||
*/
|
||||
bool ureg_load_units(PayloadParser *pp);
|
||||
|
||||
/**
|
||||
* Export unit as INI to a buffer.
|
||||
*
|
||||
* @param index - unit index
|
||||
* @param iw - iniwriter instance to use
|
||||
* @return real number of bytes used (should end with a newline)
|
||||
*/
|
||||
void ureg_export_unit(uint32_t index, IniWriter *iw);
|
||||
|
||||
/**
|
||||
* Export everything to INI
|
||||
*
|
||||
* @param iw
|
||||
*/
|
||||
void ureg_export_combined(IniWriter *iw);
|
||||
|
||||
/**
|
||||
* Get number of instantiated units
|
||||
*
|
||||
* @return nr of units
|
||||
*/
|
||||
uint32_t ureg_get_num_units(void);
|
||||
|
||||
/**
|
||||
* Instantiate a unit by INI
|
||||
*
|
||||
* This is called for lines inside the [UNITS] section, e.g. PIN=LED1, BUTTON
|
||||
*
|
||||
* @param driver_name - unit type ID
|
||||
* @param names - names string, comma separated (may have whitespace after commas)
|
||||
* @return all OK
|
||||
*/
|
||||
bool ureg_instantiate_by_ini(const char *restrict driver_name, const char *restrict names);
|
||||
|
||||
/**
|
||||
* Load a single INI line to a unit.
|
||||
*
|
||||
* @param name - unit name (for look-up)
|
||||
* @param key - property key
|
||||
* @param value - value to set as string
|
||||
* @return success
|
||||
*/
|
||||
bool ureg_read_unit_ini(const char *restrict name,
|
||||
const char *restrict key,
|
||||
const char *restrict value);
|
||||
|
||||
/**
|
||||
* Run init() for all unit instances.
|
||||
*
|
||||
* @return all OK
|
||||
*/
|
||||
bool ureg_finalize_all_init(void);
|
||||
|
||||
/**
|
||||
* Deliver a TinyFrame message to it's designed destination.
|
||||
* Unit is identified by the data first byte which is the "call sign"
|
||||
*
|
||||
* @param msg - message to deliver
|
||||
* @return true if delivered
|
||||
*/
|
||||
void ureg_deliver_unit_request(TF_Msg *msg);
|
||||
|
||||
/**
|
||||
* Report all unit callsigns and names to TF master
|
||||
*
|
||||
* @param frame_id - original message ID
|
||||
*/
|
||||
void ureg_report_active_units(TF_ID frame_id);
|
||||
|
||||
#endif //GEX_UNIT_REGISTRY_H
|
||||
Reference in New Issue
Block a user