XeWeCore 3.0.0

XeWeCore is the modular firmware core for ESP32, as one Arduino library: a serial console, a
"$group command" command line, typed NVS storage, small helpers and a module framework. Use it on
its own from the Arduino IDE, or through an xewe-os project (github.com/xewe-labs/xewe-os), whose
tools pull it in together with the ready-made modules.

3.0.0 follows 2.0.1 (2.1.0 was never tagged) and is part of the XeWe OS v3 release set.

NEW

- Settings tables. A module declares its persistent settings once, as a constexpr table of
  xewe::SettingDef rows bound to its members (src/XeWeCore/Settings.h). From that table the core
  loads defaults then NVS values at begin(), validates every value against the row's bounds, and
  provides $<module> set <key> <value>, $<module> get <key> and the key: value lines in status().
  Rows can be marked RESTART or SECRET (a secret prints as ********). A firmware where no module
  declares a table does not link the engine [from doc/os/settings.md; not verified in a link map].
- Schema output. $<module> schema prints the module's settings as JSON Lines; $system schema
  prints a header ({"schema":1,"core":"<version>","device":"<name>","modules":[...]}) and every
  module's rows, including the rows a module adds through schema_extra(). Tools read this instead
  of parsing status text.
- xewe::ListenerSet<Iface, N> (Utils/Listeners.h): a fixed-capacity, heap-free set of listener
  pointers for module-to-module change notifications, with an origin pointer so a listener does not
  get its own change echoed back. remove() during notify() is safe.
- Pin registry xewe::pins (Utils/Pins.h): claim(), release() and owner_of() for GPIO 0-48. A claim
  of a pin held by another owner is refused with a message; a strapping pin (per chip: ESP32, C3,
  C6, S3) is claimed with a warning.
- FlexData type-matched assignment. A JSON value whose type does not match the field (a string
  into a bool, an out-of-range integer, ...) is no longer coerced: the field keeps its value and
  flex_error_handler gets "! <Type>.<field>: expected <kind>, got <kind>". update() and
  from_json_object() return false when anything was rejected. Field presence: has(field) and
  present(); a blob read marks every field present, so stored settings are recognised after a
  restart.
- XEWE_TESTING hooks: building with -DXEWE_TESTING=1 adds the $test command group
  (src/XeWeCore/Testing.{h,cpp}) that the board tests drive. Without the define it adds 0 bytes.
- Smaller additions: build-time device name (XEWE_DEVICE_NAME, or the tools' XeWeBuildInfo.h),
  str::parse_hex_color / to_hex_color, Os::report_error() (queued until begin() when Serial is not
  up yet), and Color.h, String.h, Pins.h and Listeners.h compile on a host with only the standard
  library.

CHANGED (BREAKING)

- Module::get_description() is removed.
- FlexData::update() and from_json_object() return bool instead of void, and mismatched JSON types
  are rejected instead of converted (see above). Code that relied on the conversion gets the
  default value and an error message.
- An input line over 254 characters is dropped whole, with one notice, instead of being split.
- The confirmations of $<module> disable and $system reset are bounded: two attempts of 15 s;
  anything but a clear yes cancels.
- Module registration refuses an id over 15 characters, an id that is already registered
  (case-insensitive), "help", and names that are empty or contain whitespace.
- str::parse_int rejects -1/-0 for unsigned types and 64-bit overflow; validate<T> no longer
  truncates to T; GMT offsets are parsed strictly; split_by_token with an empty separator is defined.
- NVS failures (init, open, write, commit, the automatic erase) are reported through
  Nvs::set_error_handler instead of being silent; erase_all reports a failed erase.
- Naming: no public identifier was renamed in this release. The XeWe naming (XeWeCore, namespace
  xewe, XeWeOs) is the one 2.0.0 introduced when XeWeCore replaced XeWeUtils, XeWeSerial, XeWeCli,
  XeWeNvs and XeWeOS; the board tests now use the tools' XeWeError class.

FIXED (selection)

- A corrupt FlexData blob could make the reader reserve gigabytes and abort (a boot loop when read
  in begin()); the reservation is capped.
- A boot loop when NVS cannot be written on first boot.
- A use-after-free when a command handler adds or removes commands in its own group.
- report_error never cuts a message; the parsed and direct Cli::execute paths agree on commands
  with the same name and different argument counts.

Internals with no API change: one shared formatter for print and tables, allocation-free CLI
matching, table-driven module commands, one NVS read/write core. The ESP32-S3 test image of an
xewe-os project shrank from 1566456 B to 1344696 B across this rewrite and the matching modules
pass [measured on whole project images in the v1 and v3 board campaigns, not on the core alone].

REQUIREMENTS

- ESP32 family with arduino-esp32 3.x. Built and tested with esp32 core 3.3.12 (arduino-cli
  1.5.1); the examples compile for ESP32-C3, C6 and S3.
- ArduinoJson >= 7.0.0 (library.properties depends=). Tested with 7.4.3.

INSTALL

- Arduino IDE: Library Manager, search XeWeCore, Install. arduino-cli: arduino-cli lib install
  XeWeCore. The Library Manager lists a new tag within about a day [Arduino's stated indexing
  interval; not measured].
- xewe-os projects: the tools fetch the core at the ref in xewe.toml.

TESTING

What was tested on which commits, and what was not: releases/3.0.0/testcard.md (attached to this
release).
