Code import

This commit is contained in:
2017-12-15 23:52:14 +01:00
commit 56a935cdd0
103 changed files with 14510 additions and 0 deletions
+136
View File
@@ -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;
}
}
}
+80
View File
@@ -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
+236
View File
@@ -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;
}
+66
View File
@@ -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
+57
View File
@@ -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;
}
+50
View File
@@ -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
+47
View File
@@ -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"
};
// ----------------------------------------------------
+118
View File
@@ -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
+12
View File
@@ -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"
+579
View File
@@ -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);
}
+128
View File
@@ -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