Commit 861d604a authored by schneider's avatar schneider

Merge branch 'schneider/default-main-master' into 'master'

Default main app selector

See merge request card10/firmware!375
parents 6e823291 9b737f6a
......@@ -40,4 +40,6 @@ Option name Type Description
``execute_elf`` Boolean Allow running of binary :ref:`l0dables`. These files can be nefarious, so this option is off by default.
--------------- ---------- -----------
``timezone`` String Timezone for card10; must be of format ``[+-]HHMM``. Examples: ``+0800``, ``-0200``
--------------- ---------- -----------
``default_app`` String Full path to the exectutable file of the default application. If this option is not set,``apps/analog_clock/__init__.py`` is used.
=============== ========== ===========
......@@ -62,6 +62,8 @@ class ColorExample(rst.Directive):
color = self.arguments[0]
html_text = '<div style="width: 30px;height: 30px;background: {};border: black 1px solid;border-radius: 15px;"></div>'
return [nodes.raw("", html_text.format(color), format="html")]
# }}}
# -- Options for HTML output ------------------------------------------------- {{{
......@@ -112,6 +114,7 @@ autodoc_mock_imports = [
"sys_display",
"sys_leds",
"sys_max30001",
"sys_config",
"ucollections",
"urandom",
"utime",
......
......@@ -27,6 +27,7 @@ Last but not least, if you want to start hacking the lower-level firmware, the
pycardium/max30001
pycardium/buttons
pycardium/color
pycardium/config
pycardium/display
pycardium/gpio
pycardium/leds
......
``config`` - Configuration
==========================
The ``config`` module provides functions to interact with card10's
configuration file (``card10.cfg``).
.. automodule:: config
:members:
......@@ -145,6 +145,7 @@ typedef _Bool bool;
#define API_CONFIG_GET_STRING 0x130
#define API_CONFIG_GET_INTEGER 0x131
#define API_CONFIG_GET_BOOLEAN 0x132
#define API_CONFIG_SET_STRING 0x133
/* clang-format on */
typedef uint32_t api_int_id_t;
......@@ -2060,5 +2061,21 @@ API(API_CONFIG_GET_BOOLEAN, int epic_config_get_boolean(const char *key, bool *v
API(API_CONFIG_GET_STRING, int epic_config_get_string(const char *key, char *buf, size_t buf_len));
/**
* Write a string to the configuration file.
*
* :param char* key: Name of the option to write
* :param char* value: The value to write
* :return: `0` on success or a negative value if an error occured. Possivle
* errors:
*
* - ``-EINVAL``: Parameters out of range
* - ``-ENOENT``: Key already exists but can not be read
* - ``-EIO`` : Unspecified I/O error
* - Any fopen/fread/fwrite/fclose related error code
*
* .. versionadded:: 1.16
*/
API(API_CONFIG_SET_STRING, int epic_config_set_string(const char *key, const char *value));
#endif /* _EPICARDIUM_H */
......@@ -7,9 +7,11 @@
#include <stdbool.h>
#include <ctype.h>
#include <string.h>
#include <strings.h>
#include <stdlib.h>
#include <unistd.h>
#include <stddef.h>
#include <stdio.h>
#define MAX_LINE_LENGTH 80
#define KEYS_PER_BLOCK 16
......@@ -125,25 +127,39 @@ static void add_config_pair(
slot->value_offset = value_offset;
}
static void trim(char *str)
{
char *start = str;
while (*start && !isgraph(*start))
start++;
if (strlen(start) > 0) {
char *end = start + strlen(start) - 1;
while (*end && !isgraph(*end))
end--;
end[1] = 0;
}
memmove(str, start, strlen(start) + 1);
}
// parses one line of the config file
static void
parse_line(char *line, char *eol, int line_number, size_t line_offset)
static void parse_line(char *line, int line_number, size_t line_offset)
{
char *line_start = line;
char *line_start = line;
char *value_start = strchr(line, '=') + 1;
//skip leading whitespace
while (*line && isspace((int)*line))
++line;
trim(line);
char *key = line;
if (*key == '#') {
//printf(line);
if (*line == '#') {
//skip comments
return;
}
char *eq = strchr(line, '=');
if (!eq) {
if (*key) {
if (*line) {
LOG_WARN(
"card10.cfg",
"line %d: syntax error",
......@@ -152,26 +168,19 @@ parse_line(char *line, char *eol, int line_number, size_t line_offset)
}
return;
}
*eq = 0;
char *key = line;
trim(key);
char *e_key = eq - 1;
//skip trailing whitespace in key
while (e_key > key && isspace((int)*e_key))
--e_key;
e_key[1] = '\0';
if (*key == '\0') {
LOG_WARN("card10.cfg", "line %d: empty key", line_number);
return;
}
char *value = eq + 1;
//skip leading whitespace
while (*value && isspace((int)*value))
++value;
char *e_val = eol - 1;
//skip trailing whitespace
while (e_val > value && isspace((int)*e_val))
--e_val;
trim(value);
if (*value == '\0') {
LOG_WARN(
"card10.cfg",
......@@ -182,21 +191,44 @@ parse_line(char *line, char *eol, int line_number, size_t line_offset)
return;
}
size_t value_offset = value - line_start + line_offset;
size_t value_offset = value_start - line_start + line_offset;
add_config_pair(key, value, line_number, value_offset);
}
// convert windows line endings to unix line endings.
// we don't care about the extra empty lines
static void convert_crlf_to_lflf(char *buf, int n)
typedef struct {
int line_number;
int file_offset;
int line_start;
char line[MAX_LINE_LENGTH + 1];
int line_length;
} parser_state;
int parse_character(char c, parser_state *s)
{
while (n--) {
if (*buf == '\r') {
*buf = '\n';
if (c != '\r' && c != '\n') {
if (s->line_length == MAX_LINE_LENGTH) {
LOG_WARN(
"card10.cfg",
"line:%d: too long - aborting",
s->line_number
);
return -1;
}
s->line[s->line_length++] = c;
} else {
s->line[s->line_length] = 0;
//printf("New line: %s (%d %d)\n", s->line, s->line_number, s->line_start);
parse_line(s->line, s->line_number, s->line_start);
s->line_length = 0;
s->line_start = s->file_offset + 1;
if (c == '\n') {
s->line_number++;
}
buf++;
}
s->file_offset++;
return 0;
}
// parses the entire config file
......@@ -213,77 +245,21 @@ void load_config(void)
);
return;
}
char buf[MAX_LINE_LENGTH + 1];
int line_number = 0;
size_t file_offset = 0;
char buf[128];
int nread;
parser_state s;
memset(&s, 0, sizeof(s));
s.line_number = 1;
do {
nread = epic_file_read(fd, buf, MAX_LINE_LENGTH);
convert_crlf_to_lflf(buf, nread);
if (nread < MAX_LINE_LENGTH) {
//add fake EOL to ensure termination
buf[nread++] = '\n';
nread = epic_file_read(fd, buf, sizeof(buf));
int i;
for (i = 0; i < nread; i++) {
parse_character(buf[i], &s);
}
//zero-terminate buffer
buf[nread] = '\0';
char *line = buf;
char *eol = NULL;
int last_eol = 0;
while (line) {
//line points one character past the last (if any) '\n' hence '- 1'
last_eol = line - buf - 1;
eol = strchr(line, '\n');
++line_number;
if (eol) {
*eol = '\0';
parse_line(line, eol, line_number, file_offset);
file_offset += eol - line + 1;
line = eol + 1;
continue;
}
if (line == buf) {
//line did not fit into buf
LOG_WARN(
"card10.cfg",
"line:%d: too long - aborting",
line_number
);
return;
}
int seek_back = last_eol - nread;
} while (nread == sizeof(buf));
parse_character('\n', &s);
LOG_DEBUG(
"card10.cfg",
"nread, last_eol, seek_back: %d,%d,%d",
nread,
last_eol,
seek_back
);
assert(seek_back <= 0);
if (!seek_back) {
break;
}
int rc = epic_file_seek(fd, seek_back, SEEK_CUR);
if (rc < 0) {
LOG_ERR("card10.cfg", "seek failed, aborting");
return;
}
char newline;
rc = epic_file_read(fd, &newline, 1);
if (rc < 0 || (newline != '\n' && newline != '\r')) {
LOG_ERR("card10.cfg", "read failed, aborting");
LOG_DEBUG(
"card10.cfg",
"read failed at read-back of newline: rc: %d read: %d",
rc,
(int)newline
);
return;
}
break;
}
} while (nread == MAX_LINE_LENGTH);
epic_file_close(fd);
}
......@@ -355,10 +331,11 @@ int epic_config_get_string(const char *key, char *buf, size_t buf_len)
return -ENOENT;
}
char *eol = strchr(buf, '\n');
if (eol) {
*eol = '\0';
}
char *end = buf;
while (*end && !iscntrl(*end))
end++;
*end = 0;
trim(buf);
return 0;
}
......@@ -389,10 +366,10 @@ int epic_config_get_boolean(const char *key, bool *value)
char buf[MAX_LINE_LENGTH];
epic_config_get_string(key, buf, MAX_LINE_LENGTH);
if (!strcmp(buf, "true")) {
if (!strcasecmp(buf, "true")) {
*value = true;
return 0;
} else if (!strcmp(buf, "false")) {
} else if (!strcasecmp(buf, "false")) {
*value = false;
return 0;
}
......@@ -411,3 +388,256 @@ bool config_get_boolean_with_default(const char *key, bool default_value)
return value;
}
}
int epic_config_set_string(const char *key, const char *value_in)
{
char value[MAX_LINE_LENGTH + 1];
if (strlen(key) > MAX_LINE_LENGTH) {
return -EINVAL;
}
/* TODO: Change interface of trim to take the buffer and size directly */
if (strlen(value_in) > MAX_LINE_LENGTH) {
return -EINVAL;
}
strcpy(value, value_in);
trim(value);
if (snprintf(NULL, 0, "\n%s = %s\n", key, value) > MAX_LINE_LENGTH) {
return -EINVAL;
}
/* Check if key is sane. No control characters, spaces, equal signs or pounds allowed */
for (size_t i = 0; i < strlen(key); i++) {
char c = key[i];
if (!isgraph(c) || c == '=' || c == '#') {
return -EINVAL;
}
}
/* Check if value is sane. No control characters allowed */
for (size_t i = 0; i < strlen(value); i++) {
char c = value[i];
if (!isprint(c)) {
return -EINVAL;
}
}
config_slot *slot = find_config_slot(key);
bool present = slot && slot->value_offset;
int ret = 0;
if (!present) {
/* Easy case: We simply add the new option at the
* end of the file. */
char buf[MAX_LINE_LENGTH];
/* Leading new line because I'm lazy */
ret = snprintf(buf, sizeof(buf), "\n%s = %s\n", key, value);
if (ret < 0 || ret >= (int)sizeof(buf)) {
return -EINVAL;
}
int fd = epic_file_open("card10.cfg", "a");
if (fd < 0) {
LOG_DEBUG(
"card10.cfg",
"open for appending failed: %s (%d)",
strerror(-fd),
fd
);
return fd;
}
int write_ret = epic_file_write(fd, buf, strlen(buf));
if (write_ret < 0) {
LOG_DEBUG(
"card10.cfg",
"writing failed: %s (%d)",
strerror(-write_ret),
write_ret
);
}
if (write_ret < (int)strlen(buf)) {
LOG_DEBUG(
"card10.cfg",
"writing failed to write all bytes (%d of %d)",
write_ret,
strlen(buf)
);
}
ret = epic_file_close(fd);
if (ret < 0) {
LOG_DEBUG(
"card10.cfg",
"close failed: %s (%d)",
strerror(-ret),
ret
);
}
if (write_ret < 0) {
ret = write_ret;
}
if (ret < 0) {
goto out;
}
if (write_ret < (int)strlen(buf)) {
LOG_DEBUG(
"card10.cfg",
"writing failed to write all bytes (%d of %d)",
write_ret,
strlen(buf)
);
ret = -EIO;
goto out;
}
} else {
/* Complex case: The value is already somewhere in the file.
* We do not want to lose existing formatting or comments.
* Solution: Copy parts of the file, insert new value, copy
* rest, rename.
*/
char buf[MAX_LINE_LENGTH + 1];
int fd1 = -1;
int fd2 = -1;
ret = epic_config_get_string(key, buf, sizeof(buf));
size_t nread = read_config_offset(
slot->value_offset, buf, sizeof(buf)
);
if (nread == 0) {
LOG_DEBUG("card10.cfg", "could not read old value", );
goto complex_out;
}
char *end = buf;
while (*end && (!iscntrl(*end) || isblank(*end)))
end++;
*end = 0;
int old_len = strlen(buf);
fd1 = epic_file_open("card10.cfg", "r");
if (fd1 < 0) {
LOG_DEBUG(
"card10.cfg",
"open for read failed: %s (%d)",
strerror(-fd1),
fd1
);
ret = fd1;
goto complex_out;
}
fd2 = epic_file_open("card10.nfg", "w");
if (fd2 < 0) {
LOG_DEBUG(
"card10.nfg",
"open for writing failed: %s (%d)",
strerror(-fd2),
fd2
);
ret = fd2;
goto complex_out;
}
/* Copy over slot->value_offset bytes */
int i = slot->value_offset;
while (i > 0) {
int n = i > (int)sizeof(buf) ? (int)sizeof(buf) : i;
ret = epic_file_read(fd1, buf, n);
if (ret < 0) {
LOG_DEBUG(
"card10.cfg",
"read failed: rc: %d",
ret
);
goto complex_out;
}
int ret2 = epic_file_write(fd2, buf, ret);
if (ret2 < 0) {
ret = ret2;
LOG_DEBUG(
"card10.nfg",
"write failed: rc: %d",
ret
);
goto complex_out;
}
i -= ret;
}
/* Insert new value into the new file */
ret = epic_file_write(fd2, value, strlen(value));
if (ret < 0) {
LOG_DEBUG("card10.nfg", "write failed: rc: %d", ret);
goto complex_out;
}
/* Skip the old value inside the old file */
epic_file_seek(fd1, old_len, SEEK_CUR);
/* Copy the rest of the old file to the new file */
while (true) {
int ret = epic_file_read(fd1, buf, sizeof(buf));
if (ret == 0) {
break;
}
if (ret < 0) {
LOG_DEBUG(
"card10.cfg",
"read failed: rc: %d",
ret
);
goto complex_out;
}
int ret2 = epic_file_write(fd2, buf, ret);
if (ret2 < 0) {
ret = ret2;
LOG_DEBUG(
"card10.nfg",
"write failed: rc: %d",
ret
);
goto complex_out;
}
if (ret < (int)sizeof(buf)) {
break;
}
}
complex_out:
if (fd1 >= 0) {
epic_file_close(fd1);
}
if (fd2 >= 0) {
int ret2 = epic_file_close(fd2);
if (ret >= 0) {
ret = ret2;
}
}
if (ret >= 0) {
epic_file_unlink("card10.cfg");
epic_file_rename("card10.nfg", "card10.cfg");
}
}
out:
/* Reload config so the new key or the changed value is available */
load_config();
return ret < 0 ? ret : 0;
}
......@@ -12,6 +12,7 @@
#include "epicardium.h"
#include "modules/filesystem.h"
#include "modules/config.h"
#include "usb/cdcacm.h"
#include "usb/mass_storage.h"
......@@ -139,6 +140,7 @@ int epic_usb_shutdown(void)
esb_deinit();
if (s_fsDetached) {
fatfs_attach();
load_config();
}
return 0;
}
......@@ -155,6 +157,7 @@ int epic_usb_cdcacm(void)
esb_deinit();
if (s_fsDetached) {
fatfs_attach();
load_config();
}
return esb_init(&s_cfg_cdcacm);
}
......
......@@ -3,7 +3,13 @@ import os
def main():
# Try loading analog clock
default_app = "apps/analog_clock/__init__.py"
try:
import config