From b82c27f64554a891bd6ccbb1a5064b4582f1a7e9 Mon Sep 17 00:00:00 2001 From: TD-er Date: Sun, 19 Apr 2026 23:09:12 +0200 Subject: [PATCH] [RMII Eth] Fix setting clock mode + document NW config import/export --- docs/source/Network/_Network.rst | 179 ++++++++++++++++++ .../net/DataTypes/EthernetParameters.h | 24 ++- .../DataTypes/EthernetParameters_ESP32.cpp | 4 + .../DataTypes/EthernetParameters_ESP32P4.cpp | 61 +++--- .../net/Helpers/NWPlugin_import_export.cpp | 10 +- .../NW003_data_struct_ETH_RMII.cpp | 19 +- .../NW004_data_struct_ETH_SPI.cpp | 14 -- .../NW005_data_struct_PPP_modem.cpp | 2 +- 8 files changed, 251 insertions(+), 62 deletions(-) diff --git a/docs/source/Network/_Network.rst b/docs/source/Network/_Network.rst index d0b586a74..85e689b68 100644 --- a/docs/source/Network/_Network.rst +++ b/docs/source/Network/_Network.rst @@ -115,3 +115,182 @@ Enable IPv6 (ESP32 only) Checking this checkbox, will allow the network device to use IPv6. + + +ESP-Hosted-MCU +============== + +Recently Espressif introduced a new concept along with the ESP32-P4. +These ESP32-P4 chips do not have any RF hardware present, so they don't support WiFi, Bluetooth or 802.15.4 (Zigbee) + +This means boards with an ESP32-P4 and WiFi support do have a secondary Esp module present, like an ESP32-C6. +This secondary module does run the "ESP-Hosted-MCU" firmware. +In theory it should be possible to run just about any combination of ESP32-xx with an ESP32-P4, however currently only ESP32-C6 is supported even though there are ESP32-P4 boards with an ESP32-C5 being sold. + +The protocol used between the processor running ESPEasy and the secondary ESP can change over time, so it is important to keep both firmware versions in sync. + +ESPEasy does show information on both firmware/protocol versions on the WiFi Station config page. + +For example: + +.. code-block:: + + ESP-Host Fw Version: 2.12.3 + ESP-Hosted-MCU Fw Version: 2.12.3 + ESP-Hosted-MCU Chip: ESP32-C6 + MAC: E4:B3:23:A9:3F:68 + +It has been made relatively easy to update the secondary ESP using the ``wifiotahostedmcu`` command. + +This does not require any extra parameters. It only needs a direct connection to the internet. +Either via the WiFi module itself, or via Ethernet. + +This will then try to download the matching firmware version from GitHub. + + +Export/Import Network Parameters +================================ + +.. note:: + This is working only on ESP32 as extra network interfaces are only supported on ESP32 builds of ESPEasy. + +In order to simplify initial setup of a newly flashed module, it is possible to have a JSON string to enter via the ESPEasy console. + +* ``networkexportconfig,N`` with N being the network adapter index. +* ``networkexportconfig,N,'{....}'`` with N being the network adapter index and the second parameter the literal JSON string as output from the ``networkexportconfig`` command. + +.. note:: + N.B. the 2nd parameter must be wrapped in quotes which are not used in the JSON string, like single quote or back-ticks. Also the braces must be included. + + +Examples +-------- + +Export of network config on a `Waveshare ESP32-P4-NANO `_ RMII Ethernet adapter set as network index 3: + +.. code-block:: none + + networkexportconfig,3 + {"nwpluginID":3,"enabled":"true","route_prio":150,"fallback":"false","sn_block":"false","start_delay":1000,"en_ipv6":"false","Index":0,"phytype":1,"phyaddr":1,"MDC":31,"MDIO":52,"pwr":51,"clock":1} + +To import this exact example: + +.. code-block:: none + + networkimportconfig,3,`{"nwpluginID":3,"enabled":"true","route_prio":150,"fallback":"false","sn_block":"false","start_delay":1000,"en_ipv6":"false","Index":0,"phytype":1,"phyaddr":1,"MDC":31,"MDIO":52,"pwr":51,"clock":1}` + + +.. note:: + When a parameter is not present in the JSON when importing, the default will be used. + Also when a parameter is not present in the stored config (applies to default values), the key/value pair will not appear in the exported JSON. + +Allowed Parameters +------------------ + + +.. csv-table:: Allowed import/export keywords per network plugin. + :header: "Key","Value Type","Description","WiFi STA","WiFi AP","RMII Ethernet","SPI Ethernet","PPP " + :widths: 15, 10, 20, 10, 10, 10, 10, 10 + + "nwpluginID","int","ESPEasy NWPlugin ID","1","2","3","4","5 " + "enabled","bool","","✔","✔","✔","✔","✔" + "route_prio","int","See “Route Priority”","✔ (100 default)","✔ (10 default)","✔ (50 default)","✔ (50 default)","✔ (20 default)" + "fallback","bool","See “Fallback Interface”","✔","✔","✔","✔","✔" + "sn_block","bool","See “Block Web Access”","✔","✔","✔","✔","✔" + "start_delay","int","See “Delay Startup”","✔","✔","✔","✔","✔" + "en_ipv6","bool","See “Enable IPv6”","✔","✔","✔","✔","✔" + "Index","int","Network ‘Nr’ on Network tab","1","2","✔","✔","✔" + "phytype","int","Selected chip, see below","","","✔","✔","✔" + "phyaddr","int","Ethernet PHY Address","","","✔","✔","" + "MDC","int","GPIO pin MDC","","","✔","","" + "MDIO","int","GPIO pin MDIO","","","✔","","" + "pwr","int","GPIO pin Power","","","✔","","" + "clock","int","Clock Mode","","","✔","","" + "CS","int","GPIO pin CS","","","","✔","" + "IRQ","int","GPIO pin IRQ","","","","✔","" + "RST","int","GPIO pin Reset","","","","✔","✔" + "ethspibus","int","SPI bus (0 or 1)","","","","✔","" + "serPort","int","ESPEasySerial port enum","","","","","✔" + "RX","int","GPIO pin RX","","","","","✔" + "TX","int","GPIO pin TX","","","","","✔" + "RTS","int","GPIO pin RTS","","","","","✔" + "CTS","int","GPIO pin CTS","","","","","✔" + "DTR","int","GPIO pin DTR","","","","","✔" + "rst_act_low","bool","Reset Active Low","","","","","✔" + "rst_delay","int","Delay after reset in ms","","","","","✔" + "baudrate","int","Baudrate","","","","","✔ (115200 default)" + "apn","string","Access Point Name (APN)","","","","","✔" + "pin","string","PIN of SIM card","","","","","✔" + "IP","string","IP address","✔","✔","✔","✔","✔" + "gw","string","Gateway IP address","✔","✔","✔","✔","✔" + "sn","string","Subnet mask","✔","✔","✔","✔","✔" + "DNS","string","DNS server IP address","✔","✔","✔","✔","✔" + +Phy Types +--------- + +RMII Ethernet: + +* LAN8720 = 0 +* TLK110 = 1 +* RTL8201 = 2 +* JL1101 = 3 +* DP83848 = 4 +* KSZ8041 = 5 +* KSZ8081 = 6 +* LAN867X = 7 + + +SPI Ethernet: + +* DM9051 = 10 +* W5500 = 11 +* KSZ8851 = 12 + +PPP: + +* generic = 1 +* SIM7600 = 2 +* SIM7070 = 3 +* SIM7000 = 4 +* BG96 = 5 +* SIM800 = 6 + + +Clock Mode +---------- + +For ESP32-classic, the clock mode for RMII Ethernet has these options: + +* Ext_crystal_osc = 0 +* Int_50MHz_GPIO_0 = 1 +* Int_50MHz_GPIO_16 = 2 +* Int_50MHz_GPIO_17_inv = 3 + + +For ESP32-P4, the clock mode is a bit simpler as the clock pin can only be GPIO 32 / 44 / 50 (default) +and there are only 2 modes, either external crystal or clock generated by the ESP. + +* Default = 0 +* Ext_crystal (GPIO-50) = 1 +* Int_50MHz (GPIO-50) = 2 + +And the not yet supported other clock GPIO pins: + +* Ext_crystal (GPIO-32) = 129 ``(1 | (32 << 2))`` +* Int_50MHz (GPIO-32) = 130 ``(2 | (32 << 2))`` +* Ext_crystal (GPIO-44) = 177 ``(1 | (44 << 2))`` +* Int_50MHz (GPIO-44) = 178 ``(2 | (44 << 2))`` + +.. note:: Currently only GPIO-50 is supported, but later the other GPIO pins could be used and then these fixed values should be used. + + +M5Stack ESP32-S3 Atom with W5500 AtomPoE SPI Ethernet +^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + +Make sure SPI is configured with CLK: GPIO-5, MISO: GPIO-7, MOSI: GPIO-8 + +.. code-block:: none + + networkimportconfig,3,'{"nwpluginID":4,"enabled":"true","route_prio":150,"sn_block":"false","start_delay":1000,"en_ipv6":"true","phytype":11,"phyaddr":0,"CS":6,"IRQ":-1,"RST":-1}' + diff --git a/src/ESPEasy/net/DataTypes/EthernetParameters.h b/src/ESPEasy/net/DataTypes/EthernetParameters.h index 11b43836d..9a5f212b7 100644 --- a/src/ESPEasy/net/DataTypes/EthernetParameters.h +++ b/src/ESPEasy/net/DataTypes/EthernetParameters.h @@ -19,14 +19,16 @@ enum class EthClockMode_t : uint8_t { Ext_crystal = 1, Int_50MHz = 2, - /* - Ext_crystal_GPIO_32 = (1 | (32 << 2)), - Ext_crystal_GPIO_44 = (1 | (44 << 2)), - Ext_crystal_GPIO_50 = (1 | (50 << 2)), - Int_50MHz_GPIO_32 = (2 | (32 << 2)), - Int_50MHz_GPIO_44 = (2 | (44 << 2)), - Int_50MHz_GPIO_50 = (2 | (50 << 2)), - */ + + // Ext_crystal_GPIO_32 = (1 | (32 << 2)), + // Ext_crystal_GPIO_44 = (1 | (44 << 2)), + + // Ext_crystal_GPIO_50 = (1 | (50 << 2)), + // Int_50MHz_GPIO_32 = (2 | (32 << 2)), + // Int_50MHz_GPIO_44 = (2 | (44 << 2)), + + // Int_50MHz_GPIO_50 = (2 | (50 << 2)), + }; @@ -46,6 +48,8 @@ enum class EthClockMode_t : uint8_t { bool isValid(EthClockMode_t clockMode); +bool isExternalCrystal(EthClockMode_t clockMode); + const __FlashStringHelper* toString(EthClockMode_t clockMode); bool isGpioUsedInETHClockMode(EthClockMode_t clockMode, @@ -64,9 +68,9 @@ enum class EthPhyType_t : uint8_t { DP83848 = 4, KSZ8041 = 5, KSZ8081 = 6, -# if ETH_PHY_LAN867X_SUPPORTED +# if ETH_PHY_LAN867X_SUPPORTED LAN867X = 7, -# endif +# endif # endif // if CONFIG_ETH_USE_ESP32_EMAC && FEATURE_ETHERNET # if ESP_IDF_VERSION_MAJOR >= 5 # if CONFIG_ETH_SPI_ETHERNET_DM9051 diff --git a/src/ESPEasy/net/DataTypes/EthernetParameters_ESP32.cpp b/src/ESPEasy/net/DataTypes/EthernetParameters_ESP32.cpp index 5fd4e17e8..8a5dc980f 100644 --- a/src/ESPEasy/net/DataTypes/EthernetParameters_ESP32.cpp +++ b/src/ESPEasy/net/DataTypes/EthernetParameters_ESP32.cpp @@ -20,6 +20,10 @@ bool isValid(EthClockMode_t clockMode) { return false; } +bool isExternalCrystal(EthClockMode_t clockMode) { + return clockMode == EthClockMode_t::Ext_crystal_osc; +} + bool isGpioUsedInETHClockMode(EthClockMode_t clockMode, int8_t gpio) { if (((clockMode == EthClockMode_t::Int_50MHz_GPIO_0) && (gpio == 0)) || diff --git a/src/ESPEasy/net/DataTypes/EthernetParameters_ESP32P4.cpp b/src/ESPEasy/net/DataTypes/EthernetParameters_ESP32P4.cpp index b8800c8e5..7b70bd0dd 100644 --- a/src/ESPEasy/net/DataTypes/EthernetParameters_ESP32P4.cpp +++ b/src/ESPEasy/net/DataTypes/EthernetParameters_ESP32P4.cpp @@ -14,14 +14,14 @@ bool isValid(EthClockMode_t clockMode) { case EthClockMode_t::Default: case EthClockMode_t::Ext_crystal: case EthClockMode_t::Int_50MHz: - /* - case EthClockMode_t::Ext_crystal_GPIO_32: - case EthClockMode_t::Ext_crystal_GPIO_44: - case EthClockMode_t::Ext_crystal_GPIO_50: - case EthClockMode_t::Int_50MHz_GPIO_32: - case EthClockMode_t::Int_50MHz_GPIO_44: - case EthClockMode_t::Int_50MHz_GPIO_50: - */ + + // case EthClockMode_t::Ext_crystal_GPIO_32: + // case EthClockMode_t::Ext_crystal_GPIO_44: + // case EthClockMode_t::Ext_crystal_GPIO_50: + // case EthClockMode_t::Int_50MHz_GPIO_32: + // case EthClockMode_t::Int_50MHz_GPIO_44: + // case EthClockMode_t::Int_50MHz_GPIO_50: + return true; // Do not use default: as this allows the compiler to detect any missing cases. @@ -29,6 +29,13 @@ bool isValid(EthClockMode_t clockMode) { return false; } +bool isExternalCrystal(EthClockMode_t clockMode) { + return clockMode == EthClockMode_t::Ext_crystal; + + // || clockMode == EthClockMode_t::Ext_crystal_GPIO_32 + // || clockMode == EthClockMode_t::Ext_crystal_GPIO_44; +} + bool isGpioUsedInETHClockMode(EthClockMode_t clockMode, int8_t gpio) { switch (clockMode) @@ -37,14 +44,15 @@ bool isGpioUsedInETHClockMode(EthClockMode_t clockMode, case EthClockMode_t::Ext_crystal: case EthClockMode_t::Int_50MHz: return gpio == ETH_RMII_CLK; - /* - case EthClockMode_t::Int_50MHz_GPIO_32: - case EthClockMode_t::Ext_crystal_GPIO_32: return (gpio == 32); - case EthClockMode_t::Int_50MHz_GPIO_44: - case EthClockMode_t::Ext_crystal_GPIO_44: return (gpio == 44); - case EthClockMode_t::Int_50MHz_GPIO_50: - case EthClockMode_t::Ext_crystal_GPIO_50: return (gpio == 50); - */ + + // case EthClockMode_t::Int_50MHz_GPIO_32: + // case EthClockMode_t::Ext_crystal_GPIO_32: return gpio == 32; + // case EthClockMode_t::Int_50MHz_GPIO_44: + // case EthClockMode_t::Ext_crystal_GPIO_44: return gpio == 44; + + // case EthClockMode_t::Int_50MHz_GPIO_50: + // case EthClockMode_t::Ext_crystal_GPIO_50: return (gpio == 50); + } return false; } @@ -53,17 +61,18 @@ const __FlashStringHelper* toString(EthClockMode_t clockMode) { switch (clockMode) { case EthClockMode_t::Default: return F("default"); - case EthClockMode_t::Ext_crystal: return F("External crystal oscillator"); - case EthClockMode_t::Int_50MHz: return F("50MHz APLL Output"); + case EthClockMode_t::Ext_crystal: return F("Ext. crystal on GPIO-50"); + case EthClockMode_t::Int_50MHz: return F("50MHz APLL Output GPIO-50"); - /* - case EthClockMode_t::Ext_crystal_GPIO_32: return F("External crystal oscillator on GPIO-32"); - case EthClockMode_t::Ext_crystal_GPIO_44: return F("External crystal oscillator on GPIO-44"); - case EthClockMode_t::Ext_crystal_GPIO_50: return F("External crystal oscillator on GPIO-50"); - case EthClockMode_t::Int_50MHz_GPIO_32: return F("50MHz APLL Output on GPIO-32"); - case EthClockMode_t::Int_50MHz_GPIO_44: return F("50MHz APLL Output on GPIO-44"); - case EthClockMode_t::Int_50MHz_GPIO_50: return F("50MHz APLL Output on GPIO-50"); - */ + + // case EthClockMode_t::Ext_crystal_GPIO_32: return F("Ext. crystal on GPIO-32"); + // case EthClockMode_t::Ext_crystal_GPIO_44: return F("Ext. crystal on GPIO-44"); + + // case EthClockMode_t::Ext_crystal_GPIO_50: return F("Ext. crystal on GPIO-50"); + // case EthClockMode_t::Int_50MHz_GPIO_32: return F("50MHz APLL Output GPIO-32"); + // case EthClockMode_t::Int_50MHz_GPIO_44: return F("50MHz APLL Output GPIO-44"); + + // case EthClockMode_t::Int_50MHz_GPIO_50: return F("50MHz APLL Output GPIO-50"); // Do not use default: as this allows the compiler to detect any missing cases. } diff --git a/src/ESPEasy/net/Helpers/NWPlugin_import_export.cpp b/src/ESPEasy/net/Helpers/NWPlugin_import_export.cpp index d2f283621..ca8992962 100644 --- a/src/ESPEasy/net/Helpers/NWPlugin_import_export.cpp +++ b/src/ESPEasy/net/Helpers/NWPlugin_import_export.cpp @@ -134,6 +134,7 @@ String NWPlugin_import_export::exportConfig( child->write({ F("nwpluginID"), nwpluginID.value }); child->write({ F("enabled"), Settings.getNetworkEnabled(networkIndex) }); child->write({ F("route_prio"), Settings.getRoutePrio_for_network(networkIndex) }); + child->write({ F("fallback"), Settings.getNetworkInterface_isFallback(networkIndex) }); child->write({ F("sn_block"), Settings.getNetworkInterfaceSubnetBlockClientIP(networkIndex) }); child->write({ F("start_delay"), Settings.getNetworkInterfaceStartupDelay(networkIndex) }); @@ -201,6 +202,7 @@ String NWPlugin_import_export::importConfig( const String non_kvs_keys[] = { F("enabled"), F("route_prio"), + F("fallback"), F("sn_block"), F("start_delay") # if FEATURE_USE_IPV6 @@ -221,12 +223,14 @@ String NWPlugin_import_export::importConfig( break; case 1: Settings.setRoutePrio_for_network(networkIndex, value.toInt()); break; - case 2: Settings.setNetworkInterfaceSubnetBlockClientIP(networkIndex, bool_val); + case 2: Settings.setNetworkInterface_isFallback(networkIndex, bool_val); break; - case 3: Settings.setNetworkInterfaceStartupDelay(networkIndex, value.toInt()); + case 3: Settings.setNetworkInterfaceSubnetBlockClientIP(networkIndex, bool_val); + break; + case 4: Settings.setNetworkInterfaceStartupDelay(networkIndex, value.toInt()); break; # if FEATURE_USE_IPV6 - case 4: Settings.setNetworkEnabled_IPv6(networkIndex, bool_val); + case 5: Settings.setNetworkEnabled_IPv6(networkIndex, bool_val); break; # endif // if FEATURE_USE_IPV6 } diff --git a/src/ESPEasy/net/NWPluginStructs/NW003_data_struct_ETH_RMII.cpp b/src/ESPEasy/net/NWPluginStructs/NW003_data_struct_ETH_RMII.cpp index 6d33b0a86..91c22fc14 100644 --- a/src/ESPEasy/net/NWPluginStructs/NW003_data_struct_ETH_RMII.cpp +++ b/src/ESPEasy/net/NWPluginStructs/NW003_data_struct_ETH_RMII.cpp @@ -276,11 +276,19 @@ void NW003_data_struct_ETH_RMII::webform_load(EventStruct *event) // toString(EthClockMode_t::Default), toString(EthClockMode_t::Ext_crystal), toString(EthClockMode_t::Int_50MHz) +// , toString(EthClockMode_t::Ext_crystal_GPIO_32) +// , toString(EthClockMode_t::Int_50MHz_GPIO_32) +// , toString(EthClockMode_t::Ext_crystal_GPIO_44) +// , toString(EthClockMode_t::Int_50MHz_GPIO_44) }; const int indices[] = { // static_cast(EthClockMode_t::Default), static_cast(EthClockMode_t::Ext_crystal), static_cast(EthClockMode_t::Int_50MHz) +// ,static_cast(EthClockMode_t::Ext_crystal_GPIO_32) +// ,static_cast(EthClockMode_t::Int_50MHz_GPIO_32) +// ,static_cast(EthClockMode_t::Ext_crystal_GPIO_44) +// ,static_cast(EthClockMode_t::Int_50MHz_GPIO_44) }; # endif // if CONFIG_IDF_TARGET_ESP32P4 const FormSelectorOptions selector( @@ -439,13 +447,7 @@ void NW003_data_struct_ETH_RMII::ethPower(bool enable) if (!enable) { const EthClockMode_t ETH_ClockMode = static_cast(_kvs->getValueAsInt(NW003_KEY_CLOCK_MODE)); - # if CONFIG_IDF_TARGET_ESP32P4 - const bool isExternalCrystal = ETH_ClockMode == EthClockMode_t::Ext_crystal; - # else - const bool isExternalCrystal = ETH_ClockMode == EthClockMode_t::Ext_crystal_osc; - # endif // if CONFIG_IDF_TARGET_ESP32P4 - - if (isExternalCrystal) { + if (isExternalCrystal(ETH_ClockMode)) { delay(600); // Give some time to discharge any capacitors // Delay is needed to make sure no clock signal remains present which may cause the ESP to boot into flash mode. } @@ -545,7 +547,8 @@ bool NW003_data_struct_ETH_RMII::ETHConnectRelaxed() { mdcPin, mdioPin, powerPin, - (eth_clock_mode_t)Settings.ETH_Clock_Mode); + (eth_clock_mode_t)ETH_ClockMode); + // TODO TD-er: When we can set the clock GPIO pin on ESP32-P4, this should also be matched to the Espressif eth_clock_mode_t enum. } if (success) { diff --git a/src/ESPEasy/net/NWPluginStructs/NW004_data_struct_ETH_SPI.cpp b/src/ESPEasy/net/NWPluginStructs/NW004_data_struct_ETH_SPI.cpp index 44c12810c..26ed19296 100644 --- a/src/ESPEasy/net/NWPluginStructs/NW004_data_struct_ETH_SPI.cpp +++ b/src/ESPEasy/net/NWPluginStructs/NW004_data_struct_ETH_SPI.cpp @@ -348,20 +348,6 @@ void NW004_data_struct_ETH_SPI::ethResetGPIOpins() { gpio_reset_pin(GPIO_NUM_27); // EMAC_RX_CRS_DV - hardcoded # endif // if CONFIG_ETH_USE_ESP32_EMAC && FEATURE_ETHERNET - /* - switch (Settings.ETH_Clock_Mode) { - case EthClockMode_t::Ext_crystal_osc: // ETH_CLOCK_GPIO0_IN - case EthClockMode_t::Int_50MHz_GPIO_0: // ETH_CLOCK_GPIO0_OUT - gpio_reset_pin(GPIO_NUM_0); - break; - case EthClockMode_t::Int_50MHz_GPIO_16: // ETH_CLOCK_GPIO16_OUT - gpio_reset_pin(GPIO_NUM_16); - break; - case EthClockMode_t::Int_50MHz_GPIO_17_inv: // ETH_CLOCK_GPIO17_OUT - gpio_reset_pin(GPIO_NUM_17); - break; - } - */ delay(1); } diff --git a/src/ESPEasy/net/NWPluginStructs/NW005_data_struct_PPP_modem.cpp b/src/ESPEasy/net/NWPluginStructs/NW005_data_struct_PPP_modem.cpp index f868381a4..091dc4757 100644 --- a/src/ESPEasy/net/NWPluginStructs/NW005_data_struct_PPP_modem.cpp +++ b/src/ESPEasy/net/NWPluginStructs/NW005_data_struct_PPP_modem.cpp @@ -75,7 +75,7 @@ const __FlashStringHelper * NW005_data_struct_PPP_modem::getLabelString(uint32_t return displayString ? F("Baud rate") : F("baudrate"); case NW005_KEY_FLOWCTRL: return displayString ? F("Flow Control") : F("flowctrl"); - case NW005_KEY_MODEM_MODEL: return displayString ? F("Modem Model") : F("mmodel"); + case NW005_KEY_MODEM_MODEL: return displayString ? F("Modem Model") : F("phytype"); case NW005_KEY_APN: storageType = KVS_StorageType::Enum::string_type; return displayString ? F("Access Point Name (APN)") : F("apn");