Skip to content

0.7.0

Choose a tag to compare

@soosp soosp released this 26 Aug 20:35
· 18 commits to master since this release

Fixed

  • loadCfg() could restore a value the last save did not contain. saveCfg()
    removes the key of an empty string rather than storing a blank, and
    Preferences::getString()/getBytes() leave the destination untouched when a
    key is absent — so clearing the hostname, SSID, password, MAC or MQTT broker
    address, saving, and then loading back into the same object handed the old
    value straight back, and the store read as agreeing with it. The affected
    fields are now reset to their defaults before they are read, in
    NetworkProfile, WiFiProfile and MqttProfile alike. The numeric fields
    were never affected: they pass a default to the getter, which does the same
    job. Neither was a load into a freshly constructed profile, which is the usual
    boot path — this only ever bit a reload into a populated object, such as a
    configuration page reverting its edits.

    MqttProfile::_doLoad() additionally passed the current value as each
    getter's default, which had the same effect on the bools and the port; it now
    passes a default-constructed MqttConfig's.

  • WiFiProfile(const WiFiConfig&) dropped cfg.txPower. The constructor applied
    the SSID and the password but left the transmit power at the platform default,
    while setConfig(WiFiConfig) applied all three — so the same struct produced
    two different profiles depending on whether it arrived through the constructor
    or the setter. It is now validated and stored like the other two, and ignored
    the same way when invalid, since a constructor cannot report a rejection.

  • WiFiProfile::_doLoad() could load a TX power of NAN.
    Preferences::getFloat() answers a missing key with NAN unless given a
    default, and the call passed none — so a namespace written before txpwr
    existed came back with a poisoned _txPower, after which every
    isValidTxPower() comparison against it was false and the radio never got a
    usable value. It now falls back to DEFAULT_WIFI_TX_POWER_dBm, and does the
    same for a stored value outside what this build accepts: the range is not the
    same on both platforms (ESP32 discrete levels up to 19.5 dBm, ESP8266
    continuous up to 20.5), so a namespace carried between them, or a build whose
    WIFI_PROFILE_DEFAULT_WIFI_TX_POWER changed, can hold one the radio would
    refuse. The rest of the profile still loads and loadCfg() still returns
    true; only the TX power is replaced.

Added

  • NtpProfile::clearCfg() and MqttProfile::clearCfg(), matching
    NetworkProfile's. Both classes could write a namespace and read it back but
    not erase it, which left a factory reset unable to finish the job: a device
    wiped of its network configuration still came back holding the time servers it
    synchronised from and — worse — the broker's address and the credentials for
    it. MqttProfile leaves the password out of toJson() precisely because a
    backup file travels, so leaving it in flash through a reset was the same
    concern answered two different ways.

    Same contract as NetworkProfile::clearCfg(), including that it clears the
    store rather than the object: the instance keeps what it holds until something
    loads over it.

  • NetworkProfile::configCheckName(), naming a ConfigCheck value for logs and
    configuration UIs. checkConfig() exists so a caller can report which field
    was refused rather than only that something was, but with no name for the
    value every caller had to write the same nine-case switch — so in practice
    callers reported nothing, or skipped the check. The string is flash-resident,
    costing no RAM on AVR, and names the field rather than the remedy: a UI knows
    better than this library how to phrase advice to its user.

  • NtpProfile — the NTP servers as a device-level setting, with getServer() /
    setServer(), checkConfig(), saveCfg() / loadCfg() and toJson(). A
    device has one clock, so the servers it synchronises from belong to the device,
    not to whichever interface is up: keeping a copy in every NetworkProfile meant
    the same pool.ntp.org typed twice, stored twice, and carried twice in the
    largest document the portal serves. Where interfaces really do reach different
    time servers, two NtpProfile instances say so plainly.

  • NtpProfile::importLegacy() reads servers written by an earlier version, which
    stored them per interface under ntp0..ntpN in the network profile's own
    namespace. Without it an upgraded device would come back with no time servers
    and no indication why. Scheduled for removal in 1.0.0.

  • detail/ntp_config.h holds NTP_PROFILE_SERVER_COUNT on its own, because two
    callers ask different things of it: NtpProfile.h asks how many servers it
    holds and refuses to be included when the answer is none, while NetworkManager
    asks whether to compile its NTP support and must be able to ask that when the
    answer is no.

  • NTP_PROFILE_ENABLED decides whether NTP exists in the build;
    NTP_PROFILE_SERVER_COUNT says how many servers a profile holds, and is now a
    count in its own right (1..3) rather than a switch that also happened to be a
    number. Zero no longer disables anything and is refused with a message naming
    the replacement — a profile holding no servers was never a coherent state, which
    is why the class had to error out on it. Defaults: NTP off on AVR (the switch
    also guards a UDP SNTP client there, not just a buffer), three servers on ESP32,
    one everywhere else. Code sizes buffers from NtpProfile::SERVER_COUNT rather
    than the macro.

  • NTP_PROFILE_SERVER_COUNT replaces NETWORK_PROFILE_NTP_SERVER_COUNT; the old
    name is reported by a #warning rather than ignored, since a build still
    passing it would silently get the default count.

  • json_cat_esc() in detail/json_tools.h — appends a string escaped, without
    a scratch buffer. A fully escaped host name needs six bytes per character, and
    a temporary that size has no business on an ESP8266 stack, least of all inside
    an async request handler. MqttProfile::toJson() uses it.

Changed

  • NtpProfile::setConfig() and MqttProfile::setConfig() validate what they are
    given, through their own checkConfig(), and store nothing unless it passes.
    Both classes already had the check written; it simply was not wired in, so the
    same object accepted through setConfig() what it refused through
    setServer(). A stored address the resolver will never accept is a clock that
    silently never syncs, or a broker never reached — the kind of failure that
    looks like a network fault.

    The three profile families now answer the same way: setConfig() refuses an
    invalid struct everywhere. checkConfig() here is virtual, so unlike
    NetworkProfile's static isValidConfig(), a subclass's own rules apply
    through it automatically.

    What still passes is unchanged where it matters: an NtpProfile with every
    slot empty is a device told to take its time from DHCP, and a disabled
    MqttProfile keeps whatever half-filled form the user switched off. The
    portal pattern — stage a config, check it, keep it — works as before; it just
    no longer matters whether the check was remembered.

    This is a tightening. A struct that setConfig() used to swallow is now
    refused, and a getConfig() → edit → setConfig() round-trip of something
    loaded from an older or hand-edited namespace can fail where it did not.
    loadCfg() and importLegacy() deliberately stay permissive: the check
    belongs where a new value arrives, not where an existing one is recovered.

  • NtpProfile and MqttProfile gained configCheckName(), matching
    NetworkProfile's. Each class has its own ConfigCheck enum, so one shared
    function could not have covered them; without their own, a caller reporting
    which field was refused had to write the switch that the third class had
    been given. Now that setConfig() can refuse, the name is what makes the
    refusal reportable.

  • detail/prefs_tools.h names AVR instead of catching it with #else, and an
    unsupported architecture now fails at the include with a message saying what a
    port would need. EEPROMPreferences.h declares its class unconditionally and
    guards only its .cpp, so the #else branch compiled on a fourth
    architecture and then failed at link time with undefined reference to EEPROMPreferences::begin — a long way from the cause. NetworkProfile.h
    already used the narrower guard; the two now agree, and the message follows
    the one WiFiProfile.h has carried for the same situation.

  • ESP8266: isValidTxPower() now also requires the value to sit on the 0.25 dBm
    grid, not merely inside
    [MIN_WIFI_TX_POWER_dBm, MAX_WIFI_TX_POWER_dBm]. setOutputPower()
    truncates to the grid on its way to the radio, so a value between two steps
    was accepted, stored and reported back as something the radio never produced —
    the same silent step-down the ESP32 branch already existed to catch. The two
    platforms now ask the same question about the grid and differ only where they
    genuinely do: ESP32 additionally accepts just the dozen discrete levels its
    radio offers.

    API.md already documented ESP8266 as accepting any multiple of 0.25 dBm,
    so this brings the code to the documentation rather than the other way round.
    The prose above that table, which said every value in range was accepted,
    contradicted it and has been corrected.

    This narrows what is accepted. A stored TX power off the grid now fails
    validation, and loadCfg() replaces it with DEFAULT_WIFI_TX_POWER_dBm as it
    does for any other unacceptable value.

  • toJson() prints txpwr with two decimals instead of %f's six
    ("txpwr":19.50). Two are exact for the 0.25 dBm grid now enforced on both
    platforms, and the other four were always zeros. One would not do: 13.25 would
    come back as 13.2, and ESP8266 offers the whole grid, not only the halves
    ESP32's level set happens to use.

Removed

  • The library's two using Preferences = EEPROMPreferences; declarations, one in
    NetworkProfile.h and one in detail/prefs_tools.h. EEPROMPreferences.h has
    declared the alias itself since 0.2.0, so both were redundant — and the one in
    NetworkProfile.h was doubly so, sitting below an include of prefs_tools.h
    that had already brought it in.

    Redeclaring an alias to the same type is legal, which is why this went
    unnoticed, but it overrode EEPROMPREFERENCES_NO_PREFERENCES_ALIAS — and that
    macro is a working extension point, not just a collision escape. An application
    can define it, alias Preferences to its own EEPROMPreferences-compatible type
    (a different EEPROM offset, for instance), include this library afterwards, and
    the profile classes will store through the application's type. Our copies made
    the macro look available while quietly cancelling it.

  • The NTP fields are gone from NetworkProfile: getNtp(), setNtp(),
    isConfiguredNtp(), the ntp[] array in NetworkConfig, NTP_SERVER_COUNT,
    ConfigCheck::BadNtp, and the NETWORK_PROFILE_*_NTP_SERVERS macros. Use
    NtpProfile; importLegacy() reads what an earlier version stored.

    Each profile carried its own copy — around 780 bytes for three servers — so two
    interfaces meant the same pool.ntp.org stored twice and free to drift. What
    is
    saved is not only the RAM: the largest document the portal serves shrinks with
    it, and there is one place to look when the time is wrong.

Documentation

  • Comments that explained the code by what it used to be are reworded to explain
    it by what it must do. A reader who never saw the earlier version cannot check
    a sentence like "would have restored the old one" against anything; the same
    point stated as a consequence — "without the reset, loading back into the same
    object would hand the old one straight back" — needs no such memory. Affects
    _doLoad() in NetworkProfile, WiFiProfile and MqttProfile, and
    MqttProfile::setConfig(). The development history itself belongs here, in
    this file.
  • The superseded way of giving interfaces different NTP servers is corrected in
    API.md, README.md and the NtpConfig example. All three said to keep two
    profiles and swap them on the manager from its CONNECTED handler; the
    binding now lives on the adapter, because the manager settles the DHCP
    option-42 question before it starts an interface and no event has said which
    interface is taking over that early. The change was made in NetworkManager,
    so these were left behind.
  • README.md's memory table said an NtpProfile is charged once for the device
    rather than per interface. One instance is the usual case and the reason the
    servers moved out of the network profiles, but per-interface instances are now
    a supported choice and cost accordingly.
  • NtpProfile::getServer() said it returns false for a short buffer. It
    truncates with snprintf() and reports true, which is what the accessor above
    it in NetworkManager already documented. The doxygen now says so, and says
    why it matters: a truncated host name is a different server, not a partial
    answer.
  • API.md still listed NTP among the fields NetworkProfile::setConfig() and
    getConfig() carry, and claimed NTP entries were validated there. They moved
    to NtpProfile.
  • library.properties described the library as WiFiProfile and EthProfile with
    NTP among their fields. It names all four classes now.
  • Comments and documents left behind by the NTP move and by earlier reworks are
    corrected. NetworkConfig's note described its address fields as strings
    validated as IP-or-FQDN, which was true only while they held NTP servers; the
    default constructor's summary read "the hostname servers are empty", a sentence
    left half-edited when "and NTP" came out of it; and JSON_LEN's sizing still
    accounted for three NTP servers per profile, putting the worst case at ~1150
    bytes when the real figure is now 482 (266 for an EthProfile). The constant
    stays at 1200 — the headroom is for fields a subclass adds through _doJson(),
    which are counted against the same buffer — but it now says so instead of
    implying the space is spoken for.
  • The AVR persistence story is told as it is. The class header still said
    saveCfg()/loadCfg() delegate to user-supplied callbacks and clearCfg()
    claimed to be a no-op returning false there; all three have gone through
    EEPROMPreferences for some time, and clear() works. The SaveCallback and
    LoadCallback typedefs those sentences referred to are removed — they were
    declared, never used, and never called by anything.
  • NTP_PROFILE_SERVER_COUNT is no longer described as disabling NTP when set to
    zero. It is a count (1..3); zero is refused by a #error naming
    NTP_PROFILE_ENABLED, which is the switch. API.md and README.md said
    otherwise, and the CHANGELOG carried two entries for this macro that
    contradicted each other — one describing the current behaviour, one still
    claiming zero compiles the class away. The stale half is gone, as is a third
    entry restating the ESP8266 default in terms of the obsolete
    NETWORK_PROFILE_NTP_SERVER_COUNT name and of NTP servers "per profile".
  • API.md spelled ConfigSource as ACTIVE/DEFAULT; the enum is
    ACTIVE/FACTORY, which the surrounding prose already used. The DNS range
    was given as 1–2, but ESP32 allows 3.
  • Doxygen blocks that had drifted onto the wrong declaration are moved back.
    NetworkProfile::isValidConfig() and MqttProfile::checkConfig() each had
    their description sitting immediately above the deleted template overload
    that follows them, so Doxygen attached it there and the real function was
    documented by nothing.
  • README and API.md follow the split: the memory section now charges the servers
    once to NtpProfile rather than per interface, and states plainly that
    declaring no instance costs nothing. HOST_FQDN_LEN is called out as the most
    effective knob, being multiplied by every profile and every NTP slot.

Examples

  • NtpConfig rewritten for NtpProfile: the old one exercised setNtp(),
    getNtp() and isConfiguredNtp() on a NetworkProfile, none of which exist
    any more, so it could not compile. It now covers the class's whole surface,
    including importLegacy() for an upgraded device and the two-instance case for
    interfaces that really do reach different servers.
  • BasicUsage and BasicEthUsage no longer write cfg.ntp[], which is gone.