Просмотр исходного кода

esp32: system variable API

To replace the (ab)use of environment variables, with all the
associated problems with a type-aware random access API.
H. Peter Anvin 1 год назад
Родитель
Сommit
a7e199b2b5
6 измененных файлов с 621 добавлено и 1 удалено
  1. 2 0
      esp32/.gitignore
  2. 5 1
      esp32/Makefile
  3. 356 0
      esp32/max80/sysvar.c
  4. 74 0
      esp32/max80/sysvar_common.h
  5. 144 0
      esp32/sysvars.pl
  6. 40 0
      esp32/sysvars.vars

+ 2 - 0
esp32/.gitignore

@@ -10,3 +10,5 @@ cache/
 *.elf
 *.map
 www/version
+max80/sysvars.h
+max80/sysvars.c

+ 5 - 1
esp32/Makefile

@@ -2,10 +2,11 @@ MAKEFLAGS    += -R -r
 
 ARDUINO_CLI   = arduino-cli
 ZIP	      = zip
+PERL	      = perl
 
 SKETCH	      = max80
 TARGET	      = output/$(SKETCH).ino.bin
-GENFILES      = www.zip
+GENFILES      = www.zip $(SKETCH)/sysvars.c $(SKETCH)/sysvars.h
 WWW	      = www
 PORT	     ?= /dev/ttyACM0
 
@@ -35,6 +36,9 @@ $(TARGET): $(shell find $(SKETCH) -type f) $(GENFILES)
 	cd $(SKETCH) && \
 		$(ARDUINO_CLI) compile $(ARDUINO_OPTS)
 
+$(SKETCH)/%.c $(SKETCH)/%.h: %.vars sysvars.pl
+	$(PERL) sysvars.pl $< $(SKETCH)/$*.h $(SKETCH)/$*.c
+
 .PHONY: zip
 zip: zipexclude
 	mkdir -p zip

+ 356 - 0
esp32/max80/sysvar.c

@@ -0,0 +1,356 @@
+#include "sysvars.h"
+
+#include <stdlib.h>
+#include <string.h>
+#include <stdio.h>
+#include <time.h>
+
+static const char *sysvar_nulltype_tostr(sysvar_t from, char *buf)
+{
+    (void)from;
+    (void)buf;
+    return NULL;
+}
+
+static bool sysvar_nulltype_fromstr(sysvar_t *to, const char *from)
+{
+    (void)to;
+    (void)from;
+    return false;
+}
+
+const struct sysvar_ops sysvar_nulltype_ops = {
+    .tostr = sysvar_nulltype_tostr,
+    .fromstr = sysvar_nulltype_fromstr
+};
+
+static const char *sysvar_bool_tostr(sysvar_t from, char *buf)
+{
+    buf[0] = '0' + from.v_bool;
+    buf[1] = '\0';
+    return buf;
+}
+
+static bool sysvar_bool_fromstr(sysvar_t *to, const char *from)
+{
+    char c = from[0] | 0x20;
+    switch (c) {
+    case '1':
+    case 't':
+    case 'y':
+    case 'j':
+    case 's':
+	to->v_bool = true;
+	return true;
+    case '0':
+    case 'f':
+    case 'n':
+	to->v_bool = false;
+	return true;
+    default:
+	return false;
+    }
+}
+
+const struct sysvar_ops sysvar_bool_ops = {
+    .tostr = sysvar_bool_tostr,
+    .fromstr = sysvar_bool_fromstr,
+    .buflen = BOOL_BUFLEN
+};
+
+static const char *sysvar_int_tostr(sysvar_t from, char *buf)
+{
+    snprintf(buf, INT_BUFLEN, "%"PRId32, from.v_int);
+    return buf;
+}
+
+static bool sysvar_int_fromstr(sysvar_t *to, const char *from)
+{
+    char *ep;
+    long v = strtol(from, &ep, 0);
+    if (ep == from || *ep)
+	return false;
+    to->v_int = v;
+    return true;
+}
+
+const struct sysvar_ops sysvar_int_ops = {
+    .tostr = sysvar_int_tostr,
+    .fromstr = sysvar_int_fromstr,
+    .buflen = INT_BUFLEN
+};
+
+static const char *sysvar_uint_tostr(sysvar_t from, char *buf)
+{
+    snprintf(buf, UINT_BUFLEN, "%"PRIu32, from.v_uint);
+    return buf;
+}
+
+static bool sysvar_uint_fromstr(sysvar_t *to, const char *from)
+{
+    char *ep;
+    unsigned long v = strtoul(from, &ep, 0);
+    if (ep == from || *ep)
+	return false;
+    to->v_uint = v;
+    return true;
+}
+
+const struct sysvar_ops sysvar_uint_ops = {
+    .tostr = sysvar_uint_tostr,
+    .fromstr = sysvar_uint_fromstr,
+    .buflen = UINT_BUFLEN
+};
+
+static const char *sysvar_string_tostr(sysvar_t from, char *buf)
+{
+    (void)buf;
+    return from.v_string;
+}
+
+static bool sysvar_string_fromstr(sysvar_t *to, const char *from)
+{
+    char *ns;
+
+    if (!from) {
+	ns = NULL;
+    } else {
+	ns = strdup(from);
+	if (!ns)
+	    return false;
+    }
+    to->v_string = ns;
+    return true;
+}
+
+static bool sysvar_string_set(sysvar_t *to, sysvar_t from)
+{
+    return sysvar_string_fromstr(to, from.v_string);
+}
+
+const struct sysvar_ops sysvar_string_ops = {
+    .set = sysvar_string_set,
+    .tostr = sysvar_string_tostr,
+    .fromstr = sysvar_string_fromstr,
+    .is_ptr = true
+};
+
+static void sysvar_tz_update(sysvar_t val, bool isset)
+{
+    if (isset)
+	setenv("TZ", val.v_tz, 1);
+    else
+	unsetenv("TZ");
+    tzset();
+}
+
+const struct sysvar_ops sysvar_tz_ops = {
+    .set = sysvar_string_set,
+    .tostr = sysvar_string_tostr,
+    .fromstr = sysvar_string_fromstr,
+    .update = sysvar_tz_update,
+    .is_ptr = true
+};
+
+static const char *sysvar_ip_tostr(sysvar_t from, char *buf)
+{
+    union ip_bytes {
+	uint8_t b[4];
+	uint32_t l;
+    } ip;
+
+    ip.l = from.v_ip;
+    snprintf(buf, IP_BUFLEN, "%u.%u.%u.%u", ip.b[0], ip.b[1], ip.b[2], ip.b[3]);
+    return buf;
+}
+
+static bool sysvar_ip_fromstr(sysvar_t *to, const char *str)
+{
+    union ip_bytes {
+	uint8_t b[4];
+	uint32_t l;
+    } ip;
+
+    ip.l = 0;
+    for (int i = 0; i < 4; i++) {
+	char *ep;
+	unsigned long v = strtoul(str, &ep, 10);
+	if (ep == str || *ep != (i == 3) ? '\0' : '.' || v > 255)
+	    return false;
+	str = ep + 1;
+	ip.b[i] = v;
+    }
+
+    to->v_ip = ip.l;
+    return true;
+}
+
+const struct sysvar_ops sysvar_ip_ops = {
+    .tostr = sysvar_ip_tostr,
+    .fromstr = sysvar_ip_fromstr,
+    .buflen = IP_BUFLEN
+};
+
+static bool sysvar_mac_set(sysvar_t *to, sysvar_t from)
+{
+    uint8_t *buf;
+
+    if (!from.v_mac) {
+	buf = NULL;
+    } else {
+	buf = malloc(6);
+	if (!buf)
+	    return false;
+
+	memcpy(buf, from.v_mac, 6);
+    }
+    to->v_mac = buf;
+    return true;
+}
+
+static const char *sysvar_mac_tostr(sysvar_t from, char *buf)
+{
+    const uint8_t *m = from.v_mac;
+    if (!m)
+	return NULL;
+
+    snprintf(buf, MAC_BUFLEN, "%x:%x:%x:%x:%x:%x",
+	     m[0], m[1], m[2], m[3], m[4], m[5]);
+    return buf;
+}
+
+static bool sysvar_mac_fromstr(sysvar_t *to, const char *str)
+{
+    sysvar_t from;
+    uint8_t m[6];
+
+    if (!str) {
+	from.v_mac = NULL;
+    } else {
+	for (int i = 0; i < 6; i++) {
+	    char *ep;
+	    unsigned long v = strtoul(str, &ep, 16);
+	    if (ep == str || *ep != (i == 5) ? '\0' : ':' || v > 255)
+		return false;
+	    str = ep + 1;
+	    m[i] = v;
+	}
+
+	from.v_mac = m;
+    }
+
+    return sysvar_mac_set(to, from);
+}
+
+const struct sysvar_ops sysvar_mac_ops = {
+    .set = sysvar_mac_set,
+    .tostr = sysvar_mac_tostr,
+    .fromstr = sysvar_mac_fromstr,
+    .buflen = MAC_BUFLEN,
+    .is_ptr = true
+};
+
+/* --- Generic getters/setters --- */
+
+sysvar_t getvar(size_t var)
+{
+    if (var >= (size_t)sysvar_count)
+	return sysvar_defval[sysvar_null];
+
+    return sysvar_val[var];
+}
+
+static bool do_setvar(size_t var, sysvar_t val, bool is_set)
+{
+    const struct sysvar_ops *type = sysvar_type[var];
+    sysvar_t *to = &sysvar_val[var];
+    void *free_ptr = NULL;
+
+    if (type->is_ptr)
+	free_ptr = to->v_ptr;
+
+    if (!type->set || (type->is_ptr && !val.v_ptr)) {
+	sysvar_val[var] = val;
+    } else {
+	if (!type->set(to, val))
+	    return false;
+    }
+
+    if (free_ptr)
+	free(free_ptr);
+
+    sysvar_isset[var] = is_set;
+    if (type->update)
+	type->update(*to, is_set);
+
+    return true;
+}
+
+bool setvar(size_t var, sysvar_t val)
+{
+    if (var >= (size_t)sysvar_count)
+	return false;
+
+    return do_setvar(var, val, true);
+}
+
+bool unsetvar(size_t var)
+{
+    if (var >= (size_t)sysvar_count)
+	return false;
+
+    return do_setvar(var, sysvar_defval[var], false);
+}
+
+/* --- Getters/setters converting to/from strings --- */
+
+const char *getvar_tostr(size_t var)
+{
+    static char buf[SYSVAR_BUFLEN];
+    return getvar_tostr_r(var, buf);
+}
+
+const char *getvar_tostr_r(size_t var, char *buf)
+{
+    if (var >= (size_t)sysvar_count || !sysvar_isset[var])
+	return NULL;
+
+    const struct sysvar_ops *type = sysvar_type[var];
+
+    /* A tostr method is required */
+    return type->tostr(sysvar_val[var], buf);
+}
+
+bool setvar_fromstr(size_t var, const char *str)
+{
+    if (var >= (size_t)sysvar_count)
+	return NULL;
+
+    if (!str)
+	return unsetvar(var);
+
+    const struct sysvar_ops *type = sysvar_type[var];
+    sysvar_t *to = &sysvar_val[var];
+    void *free_ptr = NULL;
+
+    if (type->is_ptr)
+	free_ptr = to->v_ptr;
+
+    /* A fromstr method is required */
+    if (!type->fromstr(to, str))
+	return false;
+
+    if (free_ptr)
+	free(free_ptr);
+
+    sysvar_isset[var] = true;
+    return true;
+}
+
+/* --- Initialization/reset to defaults --- */
+
+void sysvar_reset(void)
+{
+    for (size_t i = sysvar_null; i < (size_t)sysvar_count; i++)
+	unsetvar(i);
+}

+ 74 - 0
esp32/max80/sysvar_common.h

@@ -0,0 +1,74 @@
+#ifndef SYSVAR_COMMON_H
+#define SYSVAR_COMMON_H
+
+#include <stddef.h>
+#include <stdbool.h>
+#include <inttypes.h>
+
+typedef union sysvar_value {
+    bool v_bool;
+    int32_t v_int;
+    uint32_t v_uint;
+    const char *v_string;
+    const char *v_tz;		/* Timezone */
+    uint32_t v_ip;		/* IPv4 address */
+    const uint8_t *v_mac;	/* MAC address */
+    void *v_ptr;
+} sysvar_t;
+
+struct sysvar_ops {
+    bool (*set)(sysvar_t *, sysvar_t);
+    /* bool (*unset)(sysvar_t *); - not used */
+    const char * (*tostr)(sysvar_t, char *);
+    bool (*fromstr)(sysvar_t *, const char *);
+    void (*update)(sysvar_t, bool); /* Called after set or fromstr; bool = isset */
+    size_t buflen;		/* Minimal buffer size for string if needed */
+    bool is_ptr;		/* Type is an allocated pointer */
+};
+
+typedef const struct sysvar_ops *sysvar_type_t;
+
+#define SYSVAR_TYPE(x) (&sysvar_ ## x ## _ops)
+
+/* Buffer size needed to represent some data types */
+#define BOOL_BUFLEN 2
+#define INT_BUFLEN  12		/* 10 digits + sign + null */
+#define UINT_BUFLEN INT_BUFLEN
+#define IP_BUFLEN   (4*4)
+#define MAC_BUFLEN  (3*6)
+
+#define SYSVAR_BUFLEN	(3*6)	/* Maximum */
+
+sysvar_t getvar(size_t var);
+bool setvar(size_t var, sysvar_t val);
+bool unsetvar(size_t var);
+const char *getvar_tostr(size_t var);
+const char *getvar_tostr_r(size_t var, char *buf);
+bool setvar_fromstr(size_t var, const char *str);
+void sysvar_reset(void);
+
+/* Type-specific definitions/getters/setters */
+
+#define SYSVAR_MKTYPE(t,c_type)					\
+    static inline c_type getvar_ ## t (size_t var)		\
+    {								\
+	return getvar(var).v_ ## t ;				\
+    }								\
+    static inline bool setvar_ ## t (size_t var, c_type v)	\
+    {								\
+	sysvar_t vv;						\
+	vv.v_ ## t = v;						\
+	return setvar(var, vv);					\
+    }								\
+    extern const struct sysvar_ops sysvar_ ## t ## _ops
+
+extern const struct sysvar_ops sysvar_nulltype_ops;
+SYSVAR_MKTYPE(bool, bool);
+SYSVAR_MKTYPE(int, int32_t);
+SYSVAR_MKTYPE(uint, uint32_t);
+SYSVAR_MKTYPE(string, const char *);
+SYSVAR_MKTYPE(tz, const char *);
+SYSVAR_MKTYPE(ip, uint32_t);
+SYSVAR_MKTYPE(mac, const uint8_t *);
+
+#endif /* SYSVAR_COMMON_H */

+ 144 - 0
esp32/sysvars.pl

@@ -0,0 +1,144 @@
+#!/usr/bin/perl
+use strict;
+use integer;
+use File::Basename;
+
+sub c_name($) {
+    my($s) = @_;
+
+    $s =~ s/[^A-Za-z0-9_]+/_/g;
+    return $s;
+}
+
+die "Usage: $0 infile hfile cfile\n" unless (scalar @ARGV >= 3);
+
+my($infile, $hfile, $cfile) = @ARGV;
+
+my $err = 0;
+
+open(my $in, '<', $infile) or die "$0: $infile: $!\n";
+my $ns;
+my %vars;
+my %nsprefix;
+
+my $line = 0;
+while (defined(my $l = <$in>)) {
+    $line++;
+    $l =~ s/^\s+//;
+    $l =~ s/\s+$//;
+    next if ($l =~ /^(\#.*)?$/);
+    if ($l =~ /^\@(\w+)(\s+([\w\.]+))?/) {
+	$ns = $1;
+	if (!defined($vars{$ns})) {
+	    $vars{$ns} = {};
+	}
+	$nsprefix{$ns} = $3;
+	next;
+    }
+
+    if (!defined{$ns}) {
+	print STDERR "$0:$infile:$line: variable without namespace\n";
+	$err++;
+	next;
+    }
+
+    my($var,$type,$defval) = split(/\s+/, $l);
+    if (!defined($type)) {
+	print STDERR "$0:$infile:$line: variable $var lacks type\n";
+	$err++;
+	next;
+    }
+    $vars{$ns}->{$var} = [$type, $defval];
+}
+
+close($in);
+
+exit 1 if ($err);
+
+my @nslist = sort keys(%vars);
+my %nsfirst;
+my %nscount;
+my @varname = (undef);
+my @varenum = ('sysvar_null');
+my @vartype = ("\t[sysvar_null] = SYSVAR_TYPE(nulltype),\n");
+my @vardef;
+
+my $cnt = 1;
+foreach my $ns (@nslist) {
+    $nsfirst{$ns} = $cnt;
+    foreach my $var (sort keys(%{$vars{$ns}})) {
+	my($type, $defval) = @{$vars{$ns}->{$var}};
+	push(@varname, $nsprefix{$ns}.$var);
+
+	my $ename = c_name($ns.'.'.$var);
+	push(@varenum, $ename);
+
+	push(@vartype, "\t[$ename] = SYSVAR_TYPE($type),\n");
+	if (defined($defval)) {
+	    # Add mangling here if needed
+	    push(@vardef, "\t[$ename] = { .v_$type = $defval },\n");
+	}
+	$cnt++;
+    }
+    $nscount{$ns} = $cnt - $nsfirst{$ns};
+}
+
+open(my $h, '>', $hfile) or die "$0:$hfile: $!\n";
+my $htag = c_name(uc(basename($hfile)));
+print $h "#ifndef $htag\n";
+print $h "#define $htag\n\n";
+
+print $h "#include \"sysvar_common.h\"\n\n";
+
+print $h "enum sysvar_ns {\n";
+print $h map { "\tsysvar_ns_$_,\n" } @nslist;
+print $h "\tsysvar_nscount\n};\n\n";
+
+print $h "enum sysvars {\n";
+print $h map { "\t$_,\n" } @varenum;
+print $h "\tsysvar_count\n};\n\n";
+
+push(@varenum, 'sysvar_count');
+
+foreach my $ns (@nslist) {
+    print $h "#define sysvar_ns_${ns}_start ", $varenum[$nsfirst{$ns}], "\n";
+    print $h "#define sysvar_ns_${ns}_end   ", $varenum[$nsfirst{$ns}+$nscount{$ns}], "\n";
+    print $h "#define sysvar_ns_${ns}_count ", $nscount{$ns}, "\n";
+}
+
+print $h "\n";
+print $h "extern const char * const sysvar_name[$cnt];\n";
+print $h "extern const sysvar_type_t sysvar_type[$cnt];\n";
+print $h "extern const sysvar_t sysvar_defval[$cnt];\n";
+print $h "extern sysvar_t sysvar_val[$cnt];\n";
+print $h "extern bool sysvar_isset[$cnt];\n";
+
+print $h "\n#endif /* $htag */\n";
+close($h);
+
+open(my $c, '>', $cfile) or die "$0:$cfile: $!\n";
+print $c "#include <stddef.h>\n";
+print $c "#include \"", basename($hfile), "\"\n\n";
+
+print $c "const char * const sysvar_name[$cnt] = {\n";
+print $c "\tNULL,\n";
+foreach my $ns (@nslist) {
+    print $c "\n\t/* ---- $ns ---- */\n";
+    my $v_end = $nsfirst{$ns} + $nscount{$ns};
+    for (my $v = $nsfirst{$ns}; $v < $v_end; $v++) {
+	print $c "\t\"", $varname[$v], "\",\n";
+    }
+}
+print $c "};\n";
+
+print $c "const sysvar_type_t sysvar_type[$cnt] = {\n";
+print $c @vartype;
+print $c "};\n";
+
+print $c "const sysvar_t sysvar_defval[$cnt] = {\n";
+print $c @vardef;
+print $c "};\n";
+
+print $c "sysvar_t sysvar_val[$cnt];\n";
+print $c "bool sysvar_isset[$cnt];\n";
+close($c);

+ 40 - 0
esp32/sysvars.vars

@@ -0,0 +1,40 @@
+@config
+LANG			string "sv"
+TZ			tz "CET-1CEST,M3.5.0,M10.5.0/3"
+abc.hosttype		string "auto"
+hostname		string "max80"
+ip4.dhcp.nodns		bool
+ip4.dhcp.nosntp		bool
+ip4.dns			ip
+mdns.enabled		bool true
+sntp.enabled		bool true
+sntp.server		string "time.max80.abc80.org"
+tzname			string "Europe/Stockholm"
+wifi.psk		string
+wifi.ssid		string
+
+@status
+hostname		string
+max80.fpga		bool
+max80.fw.date		string
+max80.hw.serial		string
+max80.hw.ver		string
+net.ap.clients		int
+net.ap.conn		bool
+net.ap.ip4		ip
+net.ap.ip4.mask		ip
+net.ap.mac		mac
+net.ap.ssid		string
+net.dns.server		ip
+net.eth.conn		bool
+net.eth.ip4		ip
+net.eth.ip4.gw		ip
+net.eth.ip4.mask	ip
+net.sntp.server		ip
+net.sntp.sync		bool
+net.sta.conn		bool
+net.sta.ip4		ip
+net.sta.ip4.gw		ip
+net.sta.ip4.mask	ip
+net.sta.mac		mac
+net.sta.ssid		string