<?xml version="1.0" encoding="UTF-8"?><rss xmlns:dc="http://purl.org/dc/elements/1.1/" xmlns:content="http://purl.org/rss/1.0/modules/content/" xmlns:atom="http://www.w3.org/2005/Atom" version="2.0" xmlns:media="http://search.yahoo.com/mrss/"><channel><title><![CDATA[Earth Data Labs]]></title><description><![CDATA[Sensing our World in real-time]]></description><link>https://eadalabs.com/</link><image><url>https://eadalabs.com/favicon.png</url><title>Earth Data Labs</title><link>https://eadalabs.com/</link></image><generator>Ghost 3.41</generator><lastBuildDate>Fri, 13 Mar 2026 21:55:49 GMT</lastBuildDate><atom:link href="https://eadalabs.com/rss/" rel="self" type="application/rss+xml"/><ttl>60</ttl><item><title><![CDATA[LoRaWAN Sessions: Why ABP can be more complex than OTAA]]></title><description><![CDATA[<p>The most common way for LoRaWAN devices to establish sessions is to use the Over-The-Air Activation, aka "OTAA".  It uses a join-accept handshake to negotiate session keys with the network. It also does reset the session parameters such as the frame counter (<a href="https://learn.semtech.com/mod/book/view.php?id=173&amp;chapterid=132">FCnt</a>), the data rate configuration (<a href="https://www.thethingsnetwork.org/docs/lorawan/adaptive-data-rate/">ADR</a>) or the</p>]]></description><link>https://eadalabs.com/lorawan-session-abp-vs-otta/</link><guid isPermaLink="false">66fec83912a59c76022859b4</guid><dc:creator><![CDATA[Ron J]]></dc:creator><pubDate>Fri, 04 Oct 2024 11:31:23 GMT</pubDate><media:content url="https://images.unsplash.com/photo-1585355850093-f39d4c1ef733?crop=entropy&amp;cs=tinysrgb&amp;fit=max&amp;fm=jpg&amp;ixid=M3wxMTc3M3wwfDF8c2VhcmNofDF8fHByb3RvY29sfGVufDB8fHx8MTcyODA0MzMyNHww&amp;ixlib=rb-4.0.3&amp;q=80&amp;w=2000" medium="image"/><content:encoded><![CDATA[<img src="https://images.unsplash.com/photo-1585355850093-f39d4c1ef733?crop=entropy&cs=tinysrgb&fit=max&fm=jpg&ixid=M3wxMTc3M3wwfDF8c2VhcmNofDF8fHByb3RvY29sfGVufDB8fHx8MTcyODA0MzMyNHww&ixlib=rb-4.0.3&q=80&w=2000" alt="LoRaWAN Sessions: Why ABP can be more complex than OTAA"><p>The most common way for LoRaWAN devices to establish sessions is to use the Over-The-Air Activation, aka "OTAA".  It uses a join-accept handshake to negotiate session keys with the network. It also does reset the session parameters such as the frame counter (<a href="https://learn.semtech.com/mod/book/view.php?id=173&amp;chapterid=132">FCnt</a>), the data rate configuration (<a href="https://www.thethingsnetwork.org/docs/lorawan/adaptive-data-rate/">ADR</a>) or the network Id (<a href="https://resources.lora-alliance.org/faq/lorawan-netid-faq">NetId</a>).</p><p>On the other hand, devices have the possibility to use the Activation By Personalization, aka "ABP" for joining the network. At high level, ABP can be seen as a pre-agreed and frozen set of configuration parameters, meaning that there is no need for the initial handshake. However, ABP is not state-less. It is still possible to dynamically change the configure. The TTN console provides an option for reseting the session ABP devices. </p><figure class="kg-card kg-image-card"><img src="https://eadalabs.com/content/images/2024/10/image-7.png" class="kg-image" alt="LoRaWAN Sessions: Why ABP can be more complex than OTAA" srcset="https://eadalabs.com/content/images/size/w600/2024/10/image-7.png 600w, https://eadalabs.com/content/images/size/w1000/2024/10/image-7.png 1000w, https://eadalabs.com/content/images/size/w1600/2024/10/image-7.png 1600w, https://eadalabs.com/content/images/2024/10/image-7.png 1786w" sizes="(min-width: 720px) 720px"></figure><p>This is the description provided by TTN:</p><blockquote>Resetting the session context and MAC state will reset the end device to its initial (factory) state. This includes resetting the frame counters and any other persisted MAC setting on the end device. [...]. Activation-by-personalization (ABP) end devices will only reset the MAC state, while preserving up/downlink queues."</blockquote><p>There are many fields defined in the <a href="https://www.thethingsindustries.com/docs/api/reference/grpc/end_device/#message:MACState">MAC state</a>, and here a few of the relevant ones:</p><!--kg-card-begin: markdown--><table>
<thead>
<tr>
<th>field</th>
<th>description</th>
</tr>
</thead>
<tbody>
<tr>
<td>last adr change <code>fcntup</code></td>
<td>Frame counter of uplink, which confirmed the last ADR parameter change.</td>
</tr>
<tr>
<td>last dev status <code>fcntup</code></td>
<td>Frame counter value of last uplink containing DevStatusAns.</td>
</tr>
<tr>
<td>MAC parameters</td>
<td><a href="https://www.thethingsindustries.com/docs/api/reference/grpc/end_device/#message:MACParameters">Parameters</a> such as EIRP, Tx Power, delay, ...</td>
</tr>
</tbody>
</table>
<!--kg-card-end: markdown--><p>There is also a definition for the <a href="https://www.thethingsindustries.com/docs/api/reference/grpc/end_device/#message:Session">Session state</a>:</p><!--kg-card-begin: markdown--><table>
<thead>
<tr>
<th>field</th>
<th>description</th>
</tr>
</thead>
<tbody>
<tr>
<td>last use uplink <code>fcntup</code></td>
<td>Last uplink frame counter value used. Network Server only. Application Server assumes the Network Server checked it.</td>
</tr>
<tr>
<td>last network downlink <code>n_fcntdown</code></td>
<td>Last network downlink frame counter value used. Network Server only.</td>
</tr>
<tr>
<td>last app download <code>a_fcntdown</code></td>
<td>Last application downlink frame counter value used. Application Server only.</td>
</tr>
<tr>
<td>last confirmed downlink <code>conf_fcntdown</code></td>
<td>Frame counter of the last confirmed downlink message sent. Network Server only.</td>
</tr>
</tbody>
</table>
<!--kg-card-end: markdown--><p>Our focus in the post are on the <strong>frame counters </strong>(<code>fcnt</code>). The reason is that, when restarting the device with ABP, the device needs to remember the last frame counter. But, is it enough? What about other counters, such as downlink counters, and other MAC parameters? </p><p>In the case of OTAA, when a the device restarts from a blank memory, then the session is automatically reset after re-joining the network. This means that the device only need to store in a "permanent storage" the keys and nonce. Other parameters such as the frame counter and session keys can be kept in the "battery powered storage", eg the RTC memory. If the RTC memory gets flushed, (eg if the battery is removed or flat), then the OTAA enabled device can get fresh new paramters by re-joining the network.  But for ABP, this is not the case, and frame counters <strong>must</strong> be kept in the permanent storage. In practice, this is not an issue, but one may want to limit flash wearing by having to write into the flash each time a packet is sent or received.</p><h1 id="practical-implementation-radiolib">Practical Implementation: RadioLib </h1><p>Let's dig into the <a href="https://github.com/jgromes/RadioLib">RadioLib</a> LoRaWAN implementation to better understand how sessions are restored. The function is <code>LoRaWANNode::setBufferSession(uint8_t* buffer)</code>, and located in File "<a href="https://github.com/jgromes/RadioLib/blob/6e665702419f8baa4740cbd1f05c844dd34364d9/src/protocols/LoRaWAN/LoRaWAN.cpp#L466">src/protocols/LoRaWAN/LoRaWAN.cpp</a>".</p><p></p><h2 id="restoring-sessions">Restoring Sessions</h2><p>The first part of the code check the integrity of the buffer.</p><pre><code class="language-c++">// the Nonces buffer holds a checksum signature - compare this to the signature that is in the session buffer
uint16_t signatureNonces = LoRaWANNode::ntoh&lt;uint16_t&gt;(&amp;this-&gt;bufferNonces[NONCES_SIGNATURE]);
uint16_t signatureInSession = LoRaWANNode::ntoh&lt;uint16_t&gt;(&amp;session[SESSION_NONCES_SIGNATURE]);
if(signatureNonces != signatureInSession) {
  RADIOLIB_DEBUG_PROTOCOL_PRINTLN("The Session buffer (%04x) does not match the Nonces buffer (%04x)", signatureInSession, signatureNonces);
  return(RADIOLIB_ERR_SESSION_DISCARDED);
}</code></pre><p>Then gets the value of the device Id, Network ID,  sessions keys. This code is actually designed of OTAA, since for ABP, those parameters are all frozen.</p><pre><code class="language-c++">// pull all authentication keys from persistent storage
this-&gt;devAddr = LoRaWANNode::ntoh&lt;uint32_t&gt;(&amp;session[SESSION_DEV_ADDR]);
memcpy(this-&gt;appSKey,     &amp;session[SESSION_APP_SKEY],      RADIOLIB_AES128_BLOCK_SIZE);
memcpy(this-&gt;nwkSEncKey,  &amp;session[SESSION_NWK_SENC_KEY],  RADIOLIB_AES128_BLOCK_SIZE);
memcpy(this-&gt;fNwkSIntKey, &amp;session[SESSION_FNWK_SINT_KEY], RADIOLIB_AES128_BLOCK_SIZE);
memcpy(this-&gt;sNwkSIntKey, &amp;session[SESSION_SNWK_SINT_KEY], RADIOLIB_AES128_BLOCK_SIZE);</code></pre><p>The code then restores the various frame counters, as well as network Id and revision (eg LoraWan 1.1 or  1.0.x). In case of ABP, the network Id and revision is frozen, while other parameters are dynamic.</p><pre><code class="language-c++">// restore session parameters
this-&gt;rev          = ntoh&lt;uint8_t&gt;(&amp;session[SESSION_VERSION]);
RADIOLIB_DEBUG_PROTOCOL_PRINTLN("LoRaWAN session: v1.%d", this-&gt;rev);
this-&gt;homeNetId    = ntoh(&amp;session[SESSION_HOMENET_ID]);
this-&gt;aFCntDown    = ntoh(&amp;session[SESSION_A_FCNT_DOWN]);
this-&gt;nFCntDown    = ntoh(&amp;session[SESSION_N_FCNT_DOWN]);
this-&gt;confFCntUp   = ntoh(&amp;session[SESSION_CONF_FCNT_UP]);
this-&gt;confFCntDown = ntoh(&amp;session[SESSION_CONF_FCNT_DOWN]);
this-&gt;adrFCnt      = ntoh(&amp;session[SESSION_ADR_FCNT]);
this-&gt;fCntUp       = ntoh(&amp;session[SESSION_FCNT_UP]);</code></pre><p>The rest of the code is used to restore the MAC state and parameters:</p><pre><code>uint8_t cid; // Command ID
uint8_t cLen = 0; // Command Length
uint8_t cOcts[14] = { 0 }; // Command options buffer

// setup the default channels
if(this-&gt;band-&gt;bandType == BAND_DYNAMIC) {
  this-&gt;selectChannelPlanDyn();
} else { ... }

// for dynamic bands,  additional channels must be restored per-channel
if(this-&gt;band-&gt;bandType == BAND_DYNAMIC) { ... }

// restore the state - ADR needs special care, other is straight default
cid = MAC_LINK_ADR;
cLen = 14; // special internal ADR command
memcpy(cOcts, &amp;session[SESSION_LINK_ADR], cLen);
(void)execMacCommand(cid, cOcts, cLen);

uint8_t cids[6] = {
  MAC_DUTY_CYCLE,          MAC_RX_PARAM_SETUP, 
  MAC_RX_TIMING_SETUP,     MAC_TX_PARAM_SETUP,
  MAC_ADR_PARAM_SETUP,     MAC_REJOIN_PARAM_SETUP
};
uint16_t locs[6] = {
  SESSION_DUTY_CYCLE,      SESSION_RX_PARAM_SETUP,
  SESSION_RX_TIMING_SETUP, SESSION_TX_PARAM_SETUP,
  SESSION_ADR_PARAM_SETUP, SESSION_REJOIN_PARAM_SETUP
};

for(uint8_t i = 0; i &lt; 6; i++) {
  (void)this-&gt;getMacLen(cids[i], &amp;cLen, DOWNLINK);
  memcpy(cOcts, &amp;session[locs[i]], cLen);
  (void)execMacCommand(cids[i], cOcts, cLen);
}

// set the available channels
uint16_t chMask = LoRaWANNode::ntoh&lt;uint32_t&gt;(&amp;session[SESSION_AVAILABLE_CHANNELS]);
this-&gt;setAvailableChannels(chMask);

// copy uplink MAC command queue back in place
memcpy(this-&gt;fOptsUp, &amp;session[SESSION_MAC_QUEUE], FHDR_FOPTS_MAX_LEN);
memcpy(&amp;this-&gt;fOptsUpLen, &amp;session[SESSION_MAC_QUEUE_LEN], 1);</code></pre><p>This last part of the code looks a bit barbarian, but what it does in practice is only to call the following MAC commands:</p><ul><li>LINK_ADR (<em><em>LinkADRReq</em>, 0x3): Sets the</em> data rate, transmit power, repetition rate or channel.</li><li>DUTY_CYCLE (<em><em>DutyCycleReq</em>, 0x4): S</em>ets the maximum aggregated transmit duty-cycle of a device</li><li>RX_PARAM_SETUP (<em><em>RXParamSetupReq</em>, 0x5): </em>Sets the reception slots parameters</li><li>RX_TIMING_SETUP ( <em><em>RXTimingSetupReq</em>, 0x8): </em>Sets the timing of the of the reception slots</li><li>TX_PARAM_SETUP (<em><em>TxParamSetupReq</em>, 0x9): </em>Sets the maximum allowed dwell time and Max EIRP of end-device, based on local regulations</li><li>ADR_PARAM_SETUP (<em>ADRParamSetupReq, 0xc</em>): <em>Sets</em> the <em>limit</em> and <em>delay</em> parameters defining the ADR back-off algorithm.</li><li>REJOIN_PARAM_SETUP (<em>RejoinParamSetupReq, 0xf</em>): With this command, the network may request the device to periodically send a <em>RejoinReq Type 0</em> message with a custom periodicity defined as a time or a number of uplinks</li><li>NEW_CHANNEL, DL_CHANNEL: In case of dynamic band configuration (which is the case of EU868/IN865/AS923/KR920 but not US915/AU915). Sets sets the center frequency of the new channel and the range of uplink data rates usable on this channel.</li></ul><p>Voila, restoring a session seems simpler now. But, let's also have a looks at the code for initializing a session from scratch. The function is <code>void LoRaWANNode::<a href="https://github.com/jgromes/RadioLib/blob/6e665702419f8baa4740cbd1f05c844dd34364d9/src/protocols/LoRaWAN/LoRaWAN.cpp#L304">createSession</a>(uint16_t lwMode, uint8_t initialDr)</code> from the same <a href="https://github.com/jgromes/RadioLib/blob/6e665702419f8baa4740cbd1f05c844dd34364d9/src/protocols/LoRaWAN/LoRaWAN.cpp#L304">LoRaWAN.cpp </a>file. </p><h2 id="creating-new-sessions">Creating New Sessions</h2><p>The structure is similar to the restore function. First it takes care of the bands:</p><pre><code class="language-c++">this-&gt;clearSession();

// setup JoinRequest uplink/downlink frequencies and datarates
if (this-&gt;band-&gt;bandType == RADIOLIB_LORAWAN_BAND_DYNAMIC) {... }

// on fixed bands, the first OTAA uplink (JoinRequest) is sent on fixed datarate
if (this-&gt;band-&gt;bandType == RADIOLIB_LORAWAN_BAND_FIXED &amp;&amp; lwMode == RADIOLIB_LORAWAN_MODE_OTAA) { ... }
else { ... }
</code></pre><p>And then executes the necessary MAC commands:</p><pre><code class="language-c++">uint8_t cOcts[5]; // 5 = maximum downlink payload length
uint8_t cid = RADIOLIB_LORAWAN_MAC_LINK_ADR;
uint8_t cLen = 1;       // only apply Dr/Tx field
cOcts[0] = (drUp &lt;&lt; 4); // set uplink datarate
cOcts[0] |= 0;          // default to max Tx Power
(void)execMacCommand(cid, cOcts, cLen);

cid = RADIOLIB_LORAWAN_MAC_DUTY_CYCLE;
this-&gt;getMacLen(cid, &amp;cLen, RADIOLIB_LORAWAN_DOWNLINK);
uint8_t maxDCyclePower = 0;
switch (this-&gt;band-&gt;dutyCycle)
{
case (3600):  maxDCyclePower = 10; break;
case (36000): maxDCyclePower = 7;  break;
}
cOcts[0] = maxDCyclePower;
(void)execMacCommand(cid, cOcts, cLen);

cid = RADIOLIB_LORAWAN_MAC_RX_PARAM_SETUP;
(void)this-&gt;getMacLen(cid, &amp;cLen, RADIOLIB_LORAWAN_DOWNLINK);
cOcts[0] = (RADIOLIB_LORAWAN_RX1_DR_OFFSET &lt;&lt; 4);
cOcts[0] |= this-&gt;channels[RADIOLIB_LORAWAN_DIR_RX2].dr; // may be set by user, otherwise band's default upon initialization
LoRaWANNode::hton&lt;uint32_t&gt;(&amp;cOcts[1], this-&gt;channels[RADIOLIB_LORAWAN_DIR_RX2].freq, 3);
(void)execMacCommand(cid, cOcts, cLen);

cid = RADIOLIB_LORAWAN_MAC_RX_TIMING_SETUP;
(void)this-&gt;getMacLen(cid, &amp;cLen, RADIOLIB_LORAWAN_DOWNLINK);
cOcts[0] = (RADIOLIB_LORAWAN_RECEIVE_DELAY_1_MS / 1000);
(void)execMacCommand(cid, cOcts, cLen);

cid = RADIOLIB_LORAWAN_MAC_TX_PARAM_SETUP;
(void)this-&gt;getMacLen(cid, &amp;cLen, RADIOLIB_LORAWAN_DOWNLINK);
cOcts[0] = (this-&gt;band-&gt;dwellTimeDn &gt; 0 ? 1 : 0) &lt;&lt; 5;
cOcts[0] |= (this-&gt;band-&gt;dwellTimeUp &gt; 0 ? 1 : 0) &lt;&lt; 4;
uint8_t maxEIRPRaw;
switch (this-&gt;band-&gt;powerMax)
{
case (12):  maxEIRPRaw = 2; break;
case (14):  maxEIRPRaw = 4; break;
...
}
cOcts[0] |= maxEIRPRaw;
(void)execMacCommand(cid, cOcts, cLen);

cid = RADIOLIB_LORAWAN_MAC_ADR_PARAM_SETUP;
(void)this-&gt;getMacLen(cid, &amp;cLen, RADIOLIB_LORAWAN_DOWNLINK);
cOcts[0] = (RADIOLIB_LORAWAN_ADR_ACK_LIMIT_EXP &lt;&lt; 4);
cOcts[0] |= RADIOLIB_LORAWAN_ADR_ACK_DELAY_EXP;
(void)execMacCommand(cid, cOcts, cLen);

cid = RADIOLIB_LORAWAN_MAC_REJOIN_PARAM_SETUP;
(void)this-&gt;getMacLen(cid, &amp;cLen, RADIOLIB_LORAWAN_DOWNLINK);
cOcts[0] = (RADIOLIB_LORAWAN_REJOIN_MAX_TIME_N &lt;&lt; 4);
cOcts[0] |= RADIOLIB_LORAWAN_REJOIN_MAX_COUNT_N;
(void)execMacCommand(cid, cOcts, cLen);</code></pre><p>The MAC commands are actually, and unsurprisingly, the same as for the restore function.</p><h1 id="abp-sessions">ABP Sessions</h1><p>So, what about ABP sessions? What should actually be restored compared to OTAA. Well, just about every single MAC parameter, since it defines, for example, how much time the gateway has for transmitting a downlink to the device, and on which channel. If the gateway does not talk to the device on the same channels and at the same time, then that won't work. And in this case, only an ADR back-off to the initial setting, or a MAC reset in the TTN console can help to restore the connection. This is probably one of significant drawback from ABP compared to OTAA, since using OTAA, the device can dynamically reset the MAC state just by rejoining the network, while for ABP, the device must wait for the server to ADR back-off. </p><p>One question remains, about the session parameters. The above discussion is mainly about MAC parameters, which we now know must be restored according to the session. But what about the frame counters? What happens if there are not restored properly, can the server still receive the frames from the device? The answer is no - and to make it worse, this is a common issue with TTN/ABP, referring to the discussion "<a href="https://www.thethingsnetwork.org/forum/t/abp-device-packets-in-gateway-view-but-not-arriving-at-application/63047">ABP device packets in gateway view but not arriving at application</a>". And when looking at if "<a href="https://www.thethingsnetwork.org/forum/t/is-there-a-way-to-monitor-why-the-network-server-drops-certain-frames/62789">there is a way to monitor why the network server drops certain frames</a>", then answer is pretty straightforward:</p><blockquote>Question: Is there a way to monitor on the LNS why it is rejecting those frames ?</blockquote><blockquote>Answer: For ABP the classic is the frame counters. In this situation it’s unlikely to be as they are incrementing. So the refined answer is no, there are no further logs for us to see. If you want to write your own stack, how about doing it against a copy of TTS OS on a local server, then you can poke under the hood as much as you like.</blockquote><p>In short, if there is an issue with ABP, you are on your own. And, while using the reset MAC state in the TTN console is "acceptable" for developpement configuration, it is definitely not for a production environment. And as for the "advice" to write one's own TTN stack, that's honestly the worst recommendation.</p><p>Of course, one may wonder why this is important, since restoring the full MAC and Session states using the <code>setBufferSession</code> should be enough to restore the frame counters? Well, in actual fact,  that sometime works, but quite often, the devices gets into the "seen on the gateway but not in the app" state. And when this happens, the only solution that works is to reset the MAC state. Definitelty not a suitable solution for production.</p><h1 id="conclusion">Conclusion</h1><p>It will take a bit more time to find out how to properly restore ABP session without getting in the "seen on the gateway but not in the app" issue, and I will write later about the possible approaches and solutions. </p><p>Meanwhile, using OTAA is the correct solution. And on a side topic, one question to investigate is why OTAA only needs to 2 way handshake, while TCP needs <a href="https://www.pixelstech.net/article/1727412048-Why-TCP-needs-3-handshakes">3 handshakes</a>. Something for a yet another future post!</p><p></p>]]></content:encoded></item><item><title><![CDATA[The cost of LoRaWAN: frame encoding efficiency]]></title><description><![CDATA[<p>This is a quick post to document one of the question I had about LoRaWAN, namely what is the overhead for sending 1 byte of useful application payload when using LoRaWAN, versus using LoRa directly.</p><figure class="kg-card kg-image-card kg-card-hascaption"><img src="https://eadalabs.com/content/images/2024/10/image-1.png" class="kg-image" alt srcset="https://eadalabs.com/content/images/size/w600/2024/10/image-1.png 600w, https://eadalabs.com/content/images/size/w1000/2024/10/image-1.png 1000w, https://eadalabs.com/content/images/size/w1600/2024/10/image-1.png 1600w, https://eadalabs.com/content/images/2024/10/image-1.png 1790w" sizes="(min-width: 720px) 720px"><figcaption>LoRaWAN frame payload format</figcaption></figure><p>In the above diagram "8b" refers to 8 bits, while "8B"</p>]]></description><link>https://eadalabs.com/lorawan-overhead/</link><guid isPermaLink="false">66fe474512a59c7602285860</guid><category><![CDATA[lora]]></category><category><![CDATA[lorawan]]></category><category><![CDATA[payload]]></category><category><![CDATA[frame format]]></category><category><![CDATA[encoding]]></category><category><![CDATA[overhead]]></category><dc:creator><![CDATA[Ron J]]></dc:creator><pubDate>Thu, 03 Oct 2024 10:20:50 GMT</pubDate><media:content url="https://images.unsplash.com/photo-1566217558289-2bc2fb1641cd?crop=entropy&amp;cs=tinysrgb&amp;fit=max&amp;fm=jpg&amp;ixid=M3wxMTc3M3wwfDF8c2VhcmNofDEyfHxhbnRlbm5hfGVufDB8fHx8MTcyNzk0NDA0OHww&amp;ixlib=rb-4.0.3&amp;q=80&amp;w=2000" medium="image"/><content:encoded><![CDATA[<img src="https://images.unsplash.com/photo-1566217558289-2bc2fb1641cd?crop=entropy&cs=tinysrgb&fit=max&fm=jpg&ixid=M3wxMTc3M3wwfDF8c2VhcmNofDEyfHxhbnRlbm5hfGVufDB8fHx8MTcyNzk0NDA0OHww&ixlib=rb-4.0.3&q=80&w=2000" alt="The cost of LoRaWAN: frame encoding efficiency"><p>This is a quick post to document one of the question I had about LoRaWAN, namely what is the overhead for sending 1 byte of useful application payload when using LoRaWAN, versus using LoRa directly.</p><figure class="kg-card kg-image-card kg-card-hascaption"><img src="https://eadalabs.com/content/images/2024/10/image-1.png" class="kg-image" alt="The cost of LoRaWAN: frame encoding efficiency" srcset="https://eadalabs.com/content/images/size/w600/2024/10/image-1.png 600w, https://eadalabs.com/content/images/size/w1000/2024/10/image-1.png 1000w, https://eadalabs.com/content/images/size/w1600/2024/10/image-1.png 1600w, https://eadalabs.com/content/images/2024/10/image-1.png 1790w" sizes="(min-width: 720px) 720px"><figcaption>LoRaWAN frame payload format</figcaption></figure><p>In the above diagram "8b" refers to 8 bits, while "8B" refers to 8 bytes. </p><h1 id="lorawan-encoding">LoRaWAN encoding</h1><p>Assuming that the "App Layer" is 1 byte, the amount of extra bits needed for encoding the frame from the LoRaWAN device to the LoRaWAN gateway consists of the following items:</p><p>LoRaWAN layer:</p><ul><li>4 bytes for the device address  (inside the header) - used for the cloud to identify the emitting device.</li><li>2 bytes for the frame counter (inside the header) - used for packet loss detection (and rejoin with OTTA), as well as to prevent replay attacks.</li><li>1 byte of control bits (inside the header), used for adaptive data rate (ADR) and acknowledge request control.</li><li>1 byte for the  port - which can be any value from 1 to 223; the other values beeing reserved for protocol payload handling. </li></ul><p>Medium  Access control (<a href="https://en.wikipedia.org/wiki/Medium_access_control">MAC</a>) layer:</p><ul><li>1 byte for the header - which includes the frame type, and a version number known as the major. The most basic header is defined as an uplink data message, with a major equal to zero.</li><li>4 bytes for the message integrity code, aka "MIC". The MIC is not a <a href="https://www.thethingsnetwork.org/forum/t/mic-vs-checksum/25806">CRC</a> (which instead, sits at the physical layer level). Instead the MIC is used for <a href="https://en.wikipedia.org/wiki/Message_authentication_code">authentication</a>. It is the computed as the  <a href="https://www.rfc-editor.org/rfc/rfc4493.html">AES-CMAC</a> crypto based on the network session key (<em><em>NwkSKey</em><strong>) </strong></em> over the <a href="https://learn.semtech.com/mod/book/view.php?id=173&amp;chapterid=131">MAC header and payload</a>.</li></ul><p>So, this is 13 bytes overhead needed to transmit a single 1 byte useful payload.  And considering that out of those 13 bytes, 10 are used for addressing, authentication, and sequence control, this is actually a pretty efficient  protocol encoding implementation.</p><h1 id="lora-encoding">LoRa encoding</h1><p>Let's continue the analysis of with the LoRa physical frame. </p><p>The overhead when using the LoRa physical frame encoding consists of those additional bits:</p><ul><li>8 bytes for the preamble. The preamble, which is needed for the LoRa receive to start detecting a frame, can vary in length.  For LoRaWAN, it is 8 bytes. Other modulation, such as <a href="https://essay.utwente.nl/96205/1/Lopez_BA_EEMCS.pdf">GSFK</a>, uses 5 bytes. </li><li>2.5 bytes for the header - I could not find any explicit description for this header payload, expect from this fascinating <a href="https://dl.acm.org/doi/pdf/10.1145/3546869">reverse engineering</a> <a href="https://dl.acm.org/doi/pdf/10.1145/3546869">(</a>section 4.6)</li><li>2 bytes for the CRC.</li></ul><p>Actually, there are two transmitions modes for the physical layer: Explicit and Implicit. The later implicit mode assumes that the payload length, CR, and CRC are known in advance by both the sender and receiver, in which case the CRC and header are not transmitted, thus saving 4.5 bytes. However, while the implicit mode can work for specific frames with known length, it is not suitable to generic frames when ADR is used, since the coding rate can vary dynamically. </p><p>So, assuming an explicit physical frame, it takes 25.5 bytes overhead to transfer one byte of useful application data.</p><h1 id="conclusion">Conclusion</h1><p>The graph below shows the actual encoding efficiency, measured for the 3 different encoding. </p><figure class="kg-card kg-image-card kg-card-hascaption"><img src="https://eadalabs.com/content/images/2024/10/image-5.png" class="kg-image" alt="The cost of LoRaWAN: frame encoding efficiency" srcset="https://eadalabs.com/content/images/size/w600/2024/10/image-5.png 600w, https://eadalabs.com/content/images/2024/10/image-5.png 841w" sizes="(min-width: 720px) 720px"><figcaption>LoRaWAN frame encoding efficiency</figcaption></figure><p>The efficiency is defined as PL/(O+PL), where PL = is the useful application payload, and O the Overhead.</p><p>As a conclusion, sending 1 byte of useful application data has an efficiency of 3.8% when using LoRaWAN. And to achieve 80% encoding efficiency (eg overhead length smaller than one fifth of the application payload), LoRaWAN application need to send at least 100 bytes.</p>]]></content:encoded></item><item><title><![CDATA[Getting started with OpenXR and Viulux VR headset]]></title><description><![CDATA[<p>During the past few weeks I have been experimenting with Virtual Reality, and more precisely with the OpenXR standard. My initial objective is simple:  to enhance one of the existing <a href="https://eadalabs.com/a-visual-study-of-air-pollution-forecasting/">atmospheric simulation</a> to a 3D model and visualise it using a VR headset. </p><p>The initial thinking was to use a</p>]]></description><link>https://eadalabs.com/getting-started-with-openxr/</link><guid isPermaLink="false">62d50ec013353b475e3ab822</guid><category><![CDATA[OpenXR]]></category><category><![CDATA[Viulux]]></category><category><![CDATA[OpenHMD]]></category><category><![CDATA[Oculus]]></category><category><![CDATA[Rift]]></category><category><![CDATA[Monado]]></category><category><![CDATA[VR]]></category><category><![CDATA[XR]]></category><dc:creator><![CDATA[Ron J]]></dc:creator><pubDate>Mon, 18 Jul 2022 14:05:00 GMT</pubDate><media:content url="https://images.unsplash.com/photo-1650963310446-011fc6a28367?crop=entropy&amp;cs=tinysrgb&amp;fit=max&amp;fm=jpg&amp;ixid=MnwxMTc3M3wwfDF8c2VhcmNofDY3fHwzZHxlbnwwfHx8fDE2NTgxODg0NTI&amp;ixlib=rb-1.2.1&amp;q=80&amp;w=2000" medium="image"/><content:encoded><![CDATA[<img src="https://images.unsplash.com/photo-1650963310446-011fc6a28367?crop=entropy&cs=tinysrgb&fit=max&fm=jpg&ixid=MnwxMTc3M3wwfDF8c2VhcmNofDY3fHwzZHxlbnwwfHx8fDE2NTgxODg0NTI&ixlib=rb-1.2.1&q=80&w=2000" alt="Getting started with OpenXR and Viulux VR headset"><p>During the past few weeks I have been experimenting with Virtual Reality, and more precisely with the OpenXR standard. My initial objective is simple:  to enhance one of the existing <a href="https://eadalabs.com/a-visual-study-of-air-pollution-forecasting/">atmospheric simulation</a> to a 3D model and visualise it using a VR headset. </p><p>The initial thinking was to use a headset like the Oculus Quest. Unfortunately, that does not really work, and I soon realised that the best option was to use a tethered headset: That is, where the 3D/VR engine runs directly on a Linux server, and where the headset is just used as a simple display and orientation sensor. </p><p> While looking for an affordable VR headset (aka HMD, for Head Mounted Device), I stumbled on the Viulux V9,  which can be bought for $60 on <a href="https://item.taobao.com/item.htm?spm=a1z09.2.0.0.66e92e8dDOTG45&amp;id=660548444713">Taobao</a>. </p><figure class="kg-card kg-image-card kg-card-hascaption"><img src="https://eadalabs.com/content/images/2022/07/image-3.png" class="kg-image" alt="Getting started with OpenXR and Viulux VR headset" srcset="https://eadalabs.com/content/images/size/w600/2022/07/image-3.png 600w, https://eadalabs.com/content/images/2022/07/image-3.png 702w"><figcaption>Viulux V9 HMD (Head Mounted Device), aka VR headset</figcaption></figure><p>This HMD has a quite impressive <a href="https://fccid.io/2AGQ9-VIULUXV9/User-Manual/User-Manual-3315190">spec</a> considering its cost: 2*1440*1440 displays with a 120 Hz refresh rate, 9 DOF orientational sensor, and extension for external SLAM sensors like  NOLA. So, I just bought one from the Taobao shop.</p><p></p><h1 id="inside-the-viulux-v9-hardware">Inside the Viulux V9 hardware</h1><p></p><p>Opening the Viulux V9 does not require a screw driver: Just need to remove the lid which is clipped into the headset.</p><figure class="kg-card kg-image-card"><img src="https://eadalabs.com/content/images/2022/07/viuxlux-front.jpg" class="kg-image" alt="Getting started with OpenXR and Viulux VR headset" srcset="https://eadalabs.com/content/images/size/w600/2022/07/viuxlux-front.jpg 600w, https://eadalabs.com/content/images/2022/07/viuxlux-front.jpg 1000w" sizes="(min-width: 720px) 720px"></figure><figure class="kg-card kg-image-card"><img src="https://eadalabs.com/content/images/2022/07/viuxlux-front-inside.jpg" class="kg-image" alt="Getting started with OpenXR and Viulux VR headset" srcset="https://eadalabs.com/content/images/size/w600/2022/07/viuxlux-front-inside.jpg 600w, https://eadalabs.com/content/images/2022/07/viuxlux-front-inside.jpg 1000w" sizes="(min-width: 720px) 720px"></figure><p></p><p>The inner PCB is not overly complex: It is composed of DP to MIPI display IC,  two STM IC, and an Eprom, most likely to store the display information.</p><figure class="kg-card kg-image-card"><img src="https://eadalabs.com/content/images/2022/07/viuxlux-inside.jpg" class="kg-image" alt="Getting started with OpenXR and Viulux VR headset" srcset="https://eadalabs.com/content/images/size/w600/2022/07/viuxlux-inside.jpg 600w, https://eadalabs.com/content/images/2022/07/viuxlux-inside.jpg 1000w" sizes="(min-width: 720px) 720px"></figure><p>The display IC is the <a href="https://toshiba.semicon-storage.com/us/semiconductor/product/interface-bridge-ics-for-mobile-peripheral-devices/display-interface-bridge-ics/detail.TC358860XBG.html">TC358860</a> from Toshiba and is just used to "convert the Embedded Display Port (eDPTM) video stream into an MIPI® DSI stream". The two other ICs are STM32, and it is not quite clear why the board needs two of them. Most likely, one is dedicated just to handle the real-time sensor data, and the other to handle the USB connection and other controls (audio volume, etc). </p><p>The orientational sensors are located on the other side of the PCB. It consist of a 6 DOF Accelerometer, Gyroscope (<a href="https://www.sparkfun.com/products/retired/11234">MPU 6000</a>) and 3DOF Compass (<a href="https://www.adafruit.com/product/1746">HMC58883L</a>).</p><figure class="kg-card kg-image-card"><img src="https://eadalabs.com/content/images/2022/07/viuxlux-inside-rear.jpg" class="kg-image" alt="Getting started with OpenXR and Viulux VR headset" srcset="https://eadalabs.com/content/images/size/w600/2022/07/viuxlux-inside-rear.jpg 600w, https://eadalabs.com/content/images/2022/07/viuxlux-inside-rear.jpg 1000w" sizes="(min-width: 720px) 720px"></figure><p> Voila, that's it for the hardware. Now, let's have a look at what it takes from the software side to get the Viulux V9 to work.</p><h1 id="openxr-aka-the-ar-vr-standard">OpenXR, aka the AR/VR standard</h1><p>When it comes to Virtual Reality, the standard is called <a href="https://www.khronos.org/OpenXR/">OpenXR</a>. At first glance, it can look quite complex, but to make thing simple, one can consider OpenXR as a <em>compositor</em>, which helps a 3D rendering application to compose the frames needed for the two displays, by providing to the application the scene orientation, position and timing. Open XR also has an extensive set of API for handling auxiliary devices (eg gears), and relative spaces positioning, but we can skip this part for now.</p><figure class="kg-card kg-image-card"><img src="https://eadalabs.com/content/images/2022/07/image-5.png" class="kg-image" alt="Getting started with OpenXR and Viulux VR headset" srcset="https://eadalabs.com/content/images/size/w600/2022/07/image-5.png 600w, https://eadalabs.com/content/images/size/w1000/2022/07/image-5.png 1000w, https://eadalabs.com/content/images/2022/07/image-5.png 1282w" sizes="(min-width: 720px) 720px"></figure><h2 id="monado-the-openxr-runtime">Monado, the OpenXR runtime</h2><p>To get started with getting the VR application to run on the Viulux HMD, one first need an OpenXR runtime which implements the above mentioned compositor. The very good news is that  the team at <a href="https://collabora.com">Collabora</a> together with <a href="https://www.khronos.org/">Khronos</a> have released an open source version on the OpenXR runtime called <a href="https://monado.freedesktop.org/">Monado</a>, which does support many headset. The bad news is that the Viulux HMD does not work out of the box, but fortunately, it is an easy thing to fix.</p><p></p><h2 id="getting-monado-to-support-viulux">Getting Monado to support Viulux</h2><p>The Viulux V9 HMD has been designed to be a drop-in replacement for the Oculus Rift HMD, meaning that both USB and Video data should be inter-exchangeable. When plugin the USB  cable in, one can see the following device (using <code>lsusb</code>)</p><pre><code>Bus 001 Device 046: ID 2833:0001 Oculus VR, Inc. Rift Developer Kit 1
Device Descriptor:
  bLength                18
  bDescriptorType         1
  bcdUSB               2.00
  bDeviceClass            0
  bDeviceSubClass         0
  bDeviceProtocol         0
  bMaxPacketSize0        64
  idVendor           0x2833 Oculus VR, Inc.
  idProduct          0x0001 Rift Developer Kit 1
  bcdDevice            0.01
  iManufacturer           1 Inlife 3D, Inc.
  iProduct                2 Tracker DK
  iSerial                 3 AAAAAAAAAAAA
  bNumConfigurations      1</code></pre><p>It does present itself as an Oculus Rift, with a manufacturer defined as "<em>Inline 3D, Inc.</em>" which is the actual Viulux manufacturer name. </p><p>The way Monado handles the HMD is by using the OpenHMD project, which implements the driver for many headsets, and specifically  the Oculus Rift one. </p><h2 id="introducing-openhmd">Introducing OpenHMD</h2><p>OpenHMD supports many head mounted devices (HMD). The way it detects HMD is mainly by scanning the USB devices and matching against a specific Product name. In the case of the Oculus rift it is done in the <a href="https://github.com/OpenHMD/OpenHMD/blob/dfac0203376552c5274976c42f0757b31310c483/src/drv_oculus_rift/rift.c#L1084">OpenHMD/ src/ drv_oculus_rift/ rift.c</a><strong> </strong>file:</p><pre><code class="language-C++">static void get_device_list(ohmd_driver* driver, ohmd_device_list* list)
{
	// enumerate HID devices and add any Rifts found to the device list

	rift_devices rd[RIFT_ID_COUNT] = {
		{ "Rift (DK1)", OCULUS_VR_INC_ID, 0x0001,	-1, REV_DK1 },
		{ "Rift (DK2)", OCULUS_VR_INC_ID, 0x0021,	-1, REV_DK2 },
		{ "Rift (DK2)", OCULUS_VR_INC_ID, 0x2021,	-1, REV_DK2 },
        ...
	};

	for(int i = 0; i &lt; RIFT_ID_COUNT; i++){
		struct hid_device_info* devs = hid_enumerate(rd[i].company, rd[i].id);
		struct hid_device_info* cur_dev = devs;

		while (cur_dev) {
			// We need to check the manufacturer because other companies (eg: VR-Tek)
			// are reusing the Oculus DK1 USB ID for their own HMDs
			if(ohmd_wstring_match(cur_dev-&gt;manufacturer_string, L"Oculus VR, Inc.") &amp;&amp;
			   (rd[i].iface == -1 || cur_dev-&gt;interface_number == rd[i].iface)) {
				int id = 0;
				ohmd_device_desc* desc = &amp;list-&gt;devices[list-&gt;num_devices++];</code></pre><p>The issue in the above detection code is that it explicitly checks for manufacturer. The reason is that other companies (like VR-Tek, or Inlife in our case) use the same <code>idProduct</code> and <code>idVendor</code>, but have different distortion and aberration  parameters. So, to avoid using the wrong parameters for non Oculus manufactured HMD, OpenHMD is explicitly disabling those devices.</p><p>There are many solutions to this issue, and one straightforward is to create a new ID for those non Oculus manufactured devices, and check agains the manufacturer name. This, IMO, is more efficient that just duplicating the rift driver and make it a Viulux copy, since 99% of the code is reused between Viulux and Rift. The change proposal can be see from this <a href="https://github.com/OpenHMD/OpenHMD/commit/01021c426340662e182941776bbd26105a9a5e76">commit</a></p><p></p><h2 id="all-fine-let-s-run-the-app-then">All fine, let's run the app then</h2><p>With the previous change, the Viulux headset is automatically detected by OpenHMD. By default, OpenHMD will create a new window (using SDL) on the main screen, and to active the Viulux display, one needs to "move" the window on the Viulux display. Using fedora, this can be done by right clicking on the window and selecting the "Move to the monitor on the (...|right)".  </p><figure class="kg-card kg-image-card"><img src="https://eadalabs.com/content/images/2022/07/screen02-1.png" class="kg-image" alt="Getting started with OpenXR and Viulux VR headset" srcset="https://eadalabs.com/content/images/size/w600/2022/07/screen02-1.png 600w, https://eadalabs.com/content/images/2022/07/screen02-1.png 887w" sizes="(min-width: 720px) 720px"></figure><p></p><p>Voila, that's all you need to get OpenXR to run on the Viulux HMD. Next, Just move the headset and checkout the scene!</p><figure class="kg-card kg-image-card"><img src="https://eadalabs.com/content/images/2022/07/screencast-01.gif" class="kg-image" alt="Getting started with OpenXR and Viulux VR headset"></figure><p></p><h1 id="next-steps">Next Steps</h1><p>That was a quick post to get familiar with OpenXR, Monado and OpenHMD. </p><p>Next step is to port the exiting 2D <a href="https://eadalabs.com/a-visual-study-of-air-pollution-forecasting/">atmospheric simulation</a> to a 3D simulating running using OpenXR. I will describe this experiment in the next post.</p>]]></content:encoded></item><item><title><![CDATA[When IPv6 routing goes wrong]]></title><description><![CDATA[<p></p><p>I have been using VPS from Linode for more than 10 years, located in several locations world-wide, and except from  Network maintenance or seldom DDOS attacks, I have never experienced any direct networking issue. Until last month, when I ran into an interesting  routing issue between two VMs located in</p>]]></description><link>https://eadalabs.com/when-ipv6-routing-goes-wrong/</link><guid isPermaLink="false">6219a8947aebff6a8aa9480b</guid><dc:creator><![CDATA[Ron J]]></dc:creator><pubDate>Fri, 18 Mar 2022 11:02:25 GMT</pubDate><media:content url="https://images.unsplash.com/photo-1606765962248-7ff407b51667?crop=entropy&amp;cs=tinysrgb&amp;fit=max&amp;fm=jpg&amp;ixid=MnwxMTc3M3wwfDF8c2VhcmNofDE1fHxpbnRlcm5ldCUyMHJvdXRlfGVufDB8fHx8MTY0NzU5NDE0Mw&amp;ixlib=rb-1.2.1&amp;q=80&amp;w=2000" medium="image"/><content:encoded><![CDATA[<img src="https://images.unsplash.com/photo-1606765962248-7ff407b51667?crop=entropy&cs=tinysrgb&fit=max&fm=jpg&ixid=MnwxMTc3M3wwfDF8c2VhcmNofDE1fHxpbnRlcm5ldCUyMHJvdXRlfGVufDB8fHx8MTY0NzU5NDE0Mw&ixlib=rb-1.2.1&q=80&w=2000" alt="When IPv6 routing goes wrong"><p></p><p>I have been using VPS from Linode for more than 10 years, located in several locations world-wide, and except from  Network maintenance or seldom DDOS attacks, I have never experienced any direct networking issue. Until last month, when I ran into an interesting  routing issue between two VMs located in two data centres:  Tokyo, Japan and  London, UK.</p><p>It all started on February 23rd with alerts from  a micro-service used to synchronise the data between a server in Tokyo and another one in London. After a quick investigation, it became clear that the issue was related to an abnormal network latency when connecting to the HTTPs API  endpoint on the London server from the Tokyo service: the HTTP connection could be established, but getting the result from the API, which would usually take 100ms, would then take more than 30 seconds. </p><h2 id="micro-service-connectivity">Micro-service connectivity </h2><p>Looking further at the Rest API micro-service logs from the London-based server did not give more clue, so the natural next step was to check the connectivity from the Tokyo server down to the London server. A simple <code>curl -vvv</code> gave all the needed information</p><pre><code class="language-bash"># curl "http://xxx.members.linode.com/service/sync" -vvv
* About to connect() to xxx.members.linode.com port 80 (#0)
*   Trying 2a01:7e00::1234:5678:9abc:def0...
* Connected to xxx.members.linode.com (2a01:7e00::1234:5678:9abc:def0) port 80 (#0)
* Connection timed out
*   Trying 139.123.123.123...
* Connected to xxx.members.linode.com (139.123.123.123) port 80 (#0)
&gt; GET /service/sync HTTP/1.1
&gt; User-Agent: curl/a.b.c
&gt; Host: xxx.members.linode.com
...</code></pre><p>What became clear is that, since the London server had both IPv4 and IPv6 advertised on the DNS, the curl command tried both IPv4 and IPv6, starting with IPv6. Where it went wrong is that the IPv6 connection failed, and only after a  30 seconds curl defaulted back to IPv4. Fortunately, the IPv4 connection worked fine, and the micro-service did provide the correct API result - excluding an issue from the micro-service</p><p>As an immediate action to keep the system running, the curl (fetch) command was first updated to enforce IPv4 use, and then the AAA entry from the DNS was remove. </p><p>That worked fine, yet, that  did not explain why the IPv6 connectivity failed, especially since there was not system update on any of the servers for the past 7 days. I then decided to run a few more test to find out the root cause, and make sure that this situation would not happen again.</p><h1 id="understanding-connectivity">Understanding connectivity</h1><figure class="kg-card kg-image-card kg-card-hascaption"><img src="https://eadalabs.com/content/images/2022/03/image-4.png" class="kg-image" alt="When IPv6 routing goes wrong" srcset="https://eadalabs.com/content/images/size/w600/2022/03/image-4.png 600w, https://eadalabs.com/content/images/size/w1000/2022/03/image-4.png 1000w, https://eadalabs.com/content/images/2022/03/image-4.png 1520w" sizes="(min-width: 720px) 720px"><figcaption>snapshot from https://stefansundin.github.io/traceroute-mapper/</figcaption></figure><h2 id="layer-3-connectivity-aka-ip-v6-">Layer 3 Connectivity - aka IP(v6)</h2><p>When it comes to checking the network connectivity between two servers, the common approach is to use ping and trace-route. Somewhat, those are effective tools, but slightly outdated in 2022. Nowadays, a better tool, which combines both ping and trace route, as well as many more features is <a href="http://www.bitwizard.nl/mtr/">MTR</a>, aka Multi Trace Route.</p><pre><code class="language-bash">mtr -6rwbzc 100  2a01:7e00::f03c:....
Start: Fri Feb 25 11:19:39 2022
HOST: Tokyo                Loss%   Snt   Last   Avg  Best  Wrst StDev
  1. 2400:8902::4255:....   0.0%   100    1.1   1.3   0.8   8.9   1.1
  2. 2400:8902:f::1         0.0%   100    0.6   1.6   0.4  37.4   4.8
  3. 2400:8902:5::1         0.0%   100    7.6   1.4   0.4  26.3   3.4
  4. 2001:678:34c:6c::1     0.0%   100  103.5 104.2 103.0 137.7   4.7
  5. 2600:3c01:3333:5::2    0.0%   100  103.1 103.7 103.0 115.4   1.9
  6. 2001:678:34c:44::2     0.0%   100  178.9 179.7 178.8 201.0   2.9
  7. 2600:3c04:15:5::1      0.0%   100  178.9 179.4 178.8 197.8   2.4
  8. 2001:678:34c:49::2     0.0%   100  259.4 254.2 252.8 293.5   5.4
  9. 2a01:7e00:7777:18::2   0.0%   100  253.9 253.8 253.5 258.0   0.6
 10. 2a01:7e00::f03c:....   2.0%   100  252.9 253.2 252.9 262.9   1.0</code></pre><p>The MTR result did not show anything abnormal, expect from a 2% drop on the London VM hosting the micro-service. This drop is annoying and I will come back on this issue later. But for now, it does not explain why the curl command completely failed, since with the 2% drop, the curl command would still be able to go through. In other words, only a drop close to 100%  would justify why the curl command does not work.</p><h2 id="layer-4-connectivity-aka-tcp-udp-and-icmp-">Layer 4 Connectivity - aka TCP, UDP and ICMP.</h2><p>The less know part of ping and trace-route is that they essentially work using <a href="https://wikipedia.org/wiki/Internet_Control_Message_Protocol">ICMP</a> (aka Internet Control Message Protocol), a special Layer4 protocol for checking IP connectivity. And in my case, ICMP does not represent the ground truth: The HTTPs micro-service is exposed over TCP (or UDP for QUIC/HTTP3), meaning that when connecting using CURL to the micro-service, the real protocol that need to be checked is TCP and not ICMP. </p><p>Maybe that sounds counter intuitive: If the connectivity can be established using ICMP, why couldn't it using TCP or UDP? MTR has actually a <a href="https://github.com/traviscross/mtr/blob/ec42ba61f77654e8397e6496095634585f90b26d/man/mtr.8.in#L504">statement</a> about this issue.  But before digging further into the details of the L4 connectivity, let's have a quick look at the MTR results using UDP and TCP:</p><pre><code class="language-bash">mtr -6rwbzc 100 --udp 2a01:7e00::f03c:...
HOST: Tokyo                Loss%   Snt   Last   Avg  Best  Wrst StDev
  1. 2400:8902::fa66:....   0.0%   100    0.9   1.6   0.7  22.4   2.4
     2400:8902::4255:....
  2. 2400:8902:d::1         0.0%   100   19.4   1.6   0.3  25.1   3.9
  3. 2001:678:34c:6c::1     0.0%   100    0.9  54.4   0.4 131.8  51.9
     2400:8902:5::1
  4. 2600:3c01:3333:5::2    0.0%   100  103.0 103.3 102.9 115.5   1.5
     2001:678:34c:6c::1
  5. 2001:678:34c:44::2     0.0%   100  158.7 130.8 102.9 161.2  27.8
     2600:3c01:3333:5::2
  6. 2001:678:34c:44::2     0.0%   100  158.9 159.4 158.6 175.0   1.9
     2600:3c04:15:5::1
  7. 2001:678:34c:49::2     0.0%   100  169.3 194.5 158.7 246.2  36.7
     2600:3c04:15:5::1
  8. 2a01:7e00:7777:18::2   0.0%   100  232.8 233.9 232.7 244.6   2.0
     2001:678:34c:49::2
  9. 2a01:7e00::f03c:....   0.0%   100  233.4 233.3 232.7 239.3   0.8
     2a01:7e00:7777:18::2</code></pre><p>At first glance, the UDP MTR does not show anything abnormal, at least as far as drop is concerned. The only annoying part is that the routes do not seem to be stable, and since MTR is sorting by hop, each hop can be associated to several IPs. For instance <code><a href="https://dnslytics.com/ipv6/2a01:7e00:7777:18::2">2a01:7e00:7777:18::2</a></code>  which seems to be the Linode UK front router just before the VM can be seen on hop 8 and 9. Yet, I do not really explain why if this IP can be seen on the last hop, there is not another hop with the actual VM IP.</p><pre><code class="language-bash">mtr -6rwbzc 100 --tcp 2a01:7e00::f03c:...
HOST: Tokyo                Loss%   Snt   Last   Avg  Best  Wrst StDev
  1.2400:8902::4255:....    0.0%   100    0.9   1.1   0.7   2.5   0.2
  2.2400:8902:f::1          0.0%   100    0.5   1.5   0.4  39.9   4.5
  3.2400:8902:5::1          0.0%   100  106.4  59.8   0.4 149.1  53.9
    2001:678:34c:6c::1
  4.2600:3c01:3333:5::2     0.0%   100  106.5 108.5 106.3 144.9   5.9
    2001:678:34c:6c::1
  5.2600:3c01:3333:5::2     0.0%   100  162.2 137.6 106.3 194.8  28.2
    2001:678:34c:44::2
  6.2001:678:34c:44::2      0.0%   100  162.3 162.7 162.1 179.3   1.8
    2600:3c04:15:5::1
  7.2600:3c04:15:5::1       0.0%   100  162.2 193.5 162.2 246.8  36.9
    2001:678:34c:49::2
  8.2001:678:34c:49::2      0.0%   100  236.8 237.4 236.1 251.2   2.1
    2a01:7e00:7777:18::2
  9.2a01:7e00:7777:18::2   48.0%   100  236.9 237.1 236.8 237.9   0.0
 10.???                    100.0   100    0.0   0.0   0.0   0.0   0.0</code></pre><p>This second trace, this time using TCP, is spot on: It clearly shows a complete (100%) drop on the last IP, which is the London-based VM IP. </p><p>So, well.... ICMP and UDP working fine, but not TCP? And only for IPv6? To add on the weirdness of the situation, I checked the connectivity to the London VM from another VM located in Tokyo, and it did not show any issue. This is definitely and interesting situation worth digging out.</p><h2 id="layer-2-connectivity-aka-frames-">Layer 2 Connectivity - aka frames.</h2><p>The way MTR TCP connectivity testing works is by establishing TCP sessions (aka <a href="https://github.com/traviscross/mtr/blob/852e5617fbf331cf292723702161f0ac9afe257c/packet/construct_unix.c#L457">connect</a>) while at the same time setting the socket in non-blocking mode as well as setting the IP<a href="https://github.com/traviscross/mtr/blob/852e5617fbf331cf292723702161f0ac9afe257c/packet/construct_unix.c#L365"> TTL</a> . When the TTL is big enough, the server will respond with a SYN-ACK, while if the TTL is too low, an ICMP message will be sent back.</p><figure class="kg-card kg-image-card kg-card-hascaption"><img src="https://eadalabs.com/content/images/2022/03/image-5.png" class="kg-image" alt="When IPv6 routing goes wrong" srcset="https://eadalabs.com/content/images/size/w600/2022/03/image-5.png 600w, https://eadalabs.com/content/images/size/w1000/2022/03/image-5.png 1000w, https://eadalabs.com/content/images/size/w1600/2022/03/image-5.png 1600w, https://eadalabs.com/content/images/2022/03/image-5.png 2326w" sizes="(min-width: 720px) 720px"><figcaption>From https://www.mdpi.com/2076-3417/6/11/358/htm</figcaption></figure><p>In order to check the TCP SYN/SYN-ACK handshake, I used <code>netcat</code>: One on the London server, used in listen mode, and the other on the Tokyo server, establishing connections. At the same time, I used tcpdump to capture the raw frames. </p><p>The London based server did show the SYN and SYN-ACK response packets.</p><pre><code class="language-bash">#London &gt; sudo tcpdump -c 10 ip6 host 2400:8902::f03c:.... -n
02:45:10.545147 IP6 2400:8902::f03c:.....54118 &gt; 2a01:7e00::f03c:.....krb524: Flags [S], seq 1241850342, win 64800, options [mss 1440,sackOK,TS val 3582020984 ecr 0,nop,wscale 7], length 0
02:45:10.545222 IP6 2a01:7e00::f03c:.....krb524 &gt; 2400:8902::f03c:.....54118: Flags [S.], seq 4136409442, ack 1241850343, win 64260, options [mss 1440,sackOK,TS val 4039886398 ecr 3582020984,nop,wscale 7], length 0</code></pre><p>While the Tokyo based server did not show any of the SYN-ACK response.</p><pre><code class="language-bash">#Tokyo&gt; sudo tcpdump -c 10 ip6 host 2a01:7e00::f03c:....
11:45:10.418211 IP6 2400:8902::f03c:.....54118 &gt; 2a01:7e00::f03c:.....krb524: Flags [S], seq 1241850342, win 64800, options [mss 1440,sackOK,TS val 3582020984 ecr 0,nop,wscale 7], length 0
11:45:11.434915 IP6 2400:8902::f03c:.....54118 &gt; 2a01:7e00::f03c:.....krb524: Flags [S], seq 1241850342, win 64800, options [mss 1440,sackOK,TS val 3582022001 ecr 0,nop,wscale 7], length 0
</code></pre><p>This meant that the London server did properly receive the traffic, and for some reason, this traffic would never get back to the London server. Since this only happened for TCP packets, the next  question  was wether the server in London could actually establish TCP connections with the server in Tokyo. </p><pre><code class="language-bash">#London &gt; mtr --tcp -r -c 10 2400:8902::f03c:...
Start: Sat Feb 26 04:27:05 2022
HOST: London                Loss%   Snt   Last   Avg  Best  Wrst StDev
  1.|-- 2a01:7e00::208:....  0.0%    10    1.1   1.3   1.1   2.0   0.0
  2.|-- 2a01:7e00:7777:20::1 0.0%    10    3.0   3.8   0.5  15.1   4.6
  3.|-- 2a01:7e00:7777:5::1  0.0%    10    0.5  16.1   0.5  74.5  30.8
  4.|-- 2600:3c04:15:5::2    0.0%    10   76.4  74.8  74.6  76.4   0.5
  5.|-- 2600:3c04:15:5::2    0.0%    10  130.3  97.0  74.6 130.4  28.7
  6.|-- 2600:3c01:3333:5::1  0.0%    10  130.6 131.6 130.3 138.4   2.4
  7.|-- 2001:678:34c:6c::2   0.0%    10  136.3 175.8 130.3 239.8  53.5
  8.|-- 2001:678:34c:6c::2   0.0%    10  236.5 236.8 236.4 237.5   0.0
  9.|-- 2400:8902::f03c:.... 0.0%    10  239.0 263.9 237.1 336.7  42.7</code></pre><p>Unfortunately, the connection in that direction did work fine. If it had not, that could have indicated an issue with the router not properly forwarding packets, for instance because of a corrupted cache (as described in <a href="https://www.microsoft.com/en-us/research/wp-content/uploads/2016/08/everflow-sigcomm15.pdf">everflow</a>). But no, that was not the case.</p><h2 id="layer-2-neighbour-connectivity">Layer 2 - Neighbour Connectivity</h2><p>After scratching my head for a few more hours, I decided to reach out to the Linode team who suggested to flush the ARP cache using <code>ip neigh flush all</code> and checking the results using <code>ip -6 neigh show</code>. </p><p>At first glance, having to flush the ARP entries is somewhat a bit irrational in the context of this problem: ARP is only about resolving the next-hop mac address. So, if the traffic can get through UDP, that would mean that the next hop is was properly resolved. In that case, why couldn't the same next-hop work too with TCP?</p><p>I decided anyways to follow the suggestion from the Linode team, and, to my surprise, there was actually a lot of FAILED entries in the ARP table (FAILED indicates that the system could not be reached, while STALE indicates that the connection hasn't been recently verified):</p><pre><code class="language-bash">#London &gt; ip -6 neigh show
fe80::1 dev eth0 lladdr 00:05:xx:xx:xx:xx router REACHABLE
fe80::bace:f6ff:fexx:4aa6 dev eth0 lladdr b8:ce:xx:xx:xx:xx STALE
fe80::8678:acff:fexx:21cc dev eth0 lladdr 84:78:xx:xx:xx:xx router STALE
fe80::bace:f6ff:fexx:5a56 dev eth0  router FAILED
fe80::bace:f6ff:fexx:4b66 dev eth0  router FAILED
fe80::063f:72ff:fexx:5af2 dev eth0  FAILED
fe80::063f:72ff:fexx:53f2 dev eth0  FAILED
fe80::bace:f6ff:fexx:5ee6 dev eth0  FAILED
fe80::bace:f6ff:fexx:4a66 dev eth0  FAILED</code></pre><p>Flushing did not fix anything. But checking the ARP table triggered the idea to alos check the ip routing table, and check  if any of those failed IP could be in the routing table.  The result, which can be obtained using <code>ip -6 r</code> came as a big surprise:</p><pre><code class="language-bash">#London &gt; ip -6 r
fe80::/64 dev eth0 proto kernel metric 100 pref medium
fe80::/64 dev eth0 proto kernel metric 256 pref medium
fe80::/64 dev tailscale0 proto kernel metric 256 pref medium
default proto ra metric 100
	nexthop via fe80::1 dev eth0 weight 1
	nexthop via fe80::4094 dev eth0 weight 1
	nexthop via fe80::063f:72ff:fexx:52f2 dev eth0 weight 1
	nexthop via fe80::bace:f6ff:fexx:4a66 dev eth0 weight 1
	nexthop via fe80::bace:f6ff:fexx:42c6 dev eth0 weight 1
	nexthop via fe80::bace:f6ff:fexx:5ee6 dev eth0 weight 1
	nexthop via fe80::bace:f6ff:fexx:42e6 dev eth0 weight 1
	nexthop via fe80::bace:f6ff:fexx:5a56 dev eth0 weight 1
	nexthop via fe80::063f:72ff:fexx:53f2 dev eth0 weight 1
	nexthop via fe80::bace:f6ff:fexx:4b66 dev eth0 weight 1
	...
</code></pre><p>There are many questions from the routing output. The first and most obvious is why is there so many IP listed as the next-hop? Usually VMs report only <code>fe80::1</code> or <code>fe80::4096</code> as the next hop, but not this specific server, which listed more than 30 next hop IPs for the same <code>eth0</code> network interface. </p><p>Second question, why did most of those next hop IPs were actually those marked as failed in the ARP entries. The good news is that it could explain the issue with TCP and IPv6: Provided the IP routing kernel would "stick" the TCP flow (identified by a directional source-destination IP) to a given failed next-hop, that would explain why some flow would fail, but not other.</p><p>At this point, I still do not have the answers to the those two questions, and quick a google search did give any outstanding explanation. It seems that the only way is to dig into the kernel code, in a similar way as this <a href="https://vincent.bernat.ch/en/blog/2017-ipv6-route-lookup-linux">excellent article</a> on IPv6 routing performance. I will keep this for a later post. </p><h2 id="layer-1-physical-connectivity">Layer 1 - Physical Connectivity</h2><p>Meanwhile, there is actually another  much more relevant question:  in a data-center, how many peers neighbours can a VM have? I was somewhat naïve, thinking that all the VM traffic would go through a single Router (VTEP). </p><p>To verify the assumption, I first ran tcpdump to extract the MAC address and sort them by frequency. The result came again as a big surprise:</p><pre><code> #London &gt; sudo tcpdump -c 10000  -nnS -e | awk '{ print($2);print($4); }' | grep -v Flags | sed 's/,//g' | sort | uniq -c | sort -n
  10000 f2:3c:91:xx:xx:01
   9367 f2:3c:91:xx:xx:02
    307 00:00:0c:9f:f0:19
     51 00:05:73:a0:0f:ff
     ...</code></pre><p> The first two address <code>f2:3c:91:xx:xx:xx</code> are mac addresses of VMs hosted in the London data-center. Having direct VM L2 connectivity is ok provided that VxLAN is used, which ought to be the case (it is actually quite difficult to find any information from Linode DC topology). </p><figure class="kg-card kg-image-card"><img src="https://eadalabs.com/content/images/2022/03/image-6.png" class="kg-image" alt="When IPv6 routing goes wrong" srcset="https://eadalabs.com/content/images/size/w600/2022/03/image-6.png 600w, https://eadalabs.com/content/images/size/w1000/2022/03/image-6.png 1000w, https://eadalabs.com/content/images/size/w1600/2022/03/image-6.png 1600w, https://eadalabs.com/content/images/2022/03/image-6.png 1885w" sizes="(min-width: 720px) 720px"></figure><p>What is interesting are the other IP addresses. Could they be from different VTEPs, which seems to be a <a href="https://sivasankar.org/2018/2312/vmware-nsx-dmz-anywhere-detailed-design-guide/">recommended</a> load-balancing design? To know the answer, the traffic needs to be classified by mac and IP flows. I implemented a simple golang app (<a href="https://gist.github.com/ronanj/627a6931ad4c0e24f8c225ef79c9bf0f">gist</a>) to classify the packets, and here is the result:</p><!--kg-card-begin: markdown--><table>
<thead>
<tr>
<th>IP</th>
<th>MAC</th>
</tr>
</thead>
<tbody>
<tr>
<td>ipv4-public-linode-tokyo-1</td>
<td>&gt;00:00:0c:9f:f0:19</td>
</tr>
<tr>
<td>ipv4-public-linode-tokyo-2</td>
<td>&gt;00:00:0c:9f:f0:19</td>
</tr>
<tr>
<td>ipv4-public-linode-london-2</td>
<td>&gt;00:00:0c:9f:f0:19</td>
</tr>
<tr>
<td><strong>ipv6</strong>-public-linode-tokyo-2</td>
<td>&gt;00:05:73:a0:0f:ff</td>
</tr>
<tr>
<td><strong>ipv6</strong>-public-linode-tokyo-1</td>
<td>&gt;00:05:73:a0:0f:ff &gt;00:05:73:a0:0f:fe</td>
</tr>
<tr>
<td><strong>ipv6</strong>-public-digitalocean-uk</td>
<td>&gt;00:05:73:a0:0f:ff</td>
</tr>
<tr>
<td><strong>ipv6</strong>-public-digitalocean-de</td>
<td>&gt;00:05:73:a0:0f:fe</td>
</tr>
<tr>
<td>ipv4-<strong>private</strong>-linode-london-2</td>
<td>&gt;f2:3c:91:37:xx:xx</td>
</tr>
<tr>
<td>ipv4-<strong>private</strong>-linode-london-3</td>
<td>&gt;f2:3c:91:a1:xx:xx</td>
</tr>
<tr>
<td>ipv4-<strong>private</strong>-linode-london-1</td>
<td>&gt;f2:3c:92:a1:xx:xx</td>
</tr>
</tbody>
</table>
<!--kg-card-end: markdown--><p>It shows that all private IP address are using their own Mac address, which is the expected behaviour for VxLAN. It also shows that IPv6 and IPv4 are using different next-hop, which is not a problem in itself. And  finally, it  shows that the connection to Tokyo-1 server is using two next-hops MAC addresses, which, at first glance, seems suspicious. </p><p>A TCP-dump helped to confirm that the two next-hops seems to be distributed among packets based on the <a href="https://networkengineering.stackexchange.com/questions/28723/usage-of-flow-label-in-ipv6-header">flow label</a>.</p><!--kg-card-begin: markdown--><blockquote>
<p>Packet 1 &amp; 2, using flow label <code>774398</code>:</p>
</blockquote>
<pre><code>- Layer 1 (14 bytes) = SrcMAC=f2:3c:91:..:..:.. DstMAC=00:05:73:a0:0f:fe EthernetType=IPv6 Length=0}
- Layer 2 (40 bytes) = IPv6	FlowLabel=774398 Length=32 NextHeader=TCP HopLimit=64 SrcIP=2a01:7e00::f03c:... DstIP=2400:8902::f03c:...

- Layer 1 (14 bytes) = SrcMAC=f2:3c:91:..:..:.. DstMAC=00:05:73:a0:0f:fe EthernetType=IPv6 Length=0}
- Layer 2 (40 bytes) = IPv6	FlowLabel=774398 Length=32 NextHeader=TCP HopLimit=64 SrcIP=2a01:7e00::f03c:... DstIP=2400:8902::f03c:...
</code></pre>
<blockquote>
<p>Packet 3&amp;4, using flow label <code>106397</code></p>
</blockquote>
<pre><code>- Layer 1 (14 bytes) = SrcMAC=f2:3c:91:..:..:.. DstMAC=00:05:73:a0:0f:ff EthernetType=IPv6 Length=0}
- Layer 2 (40 bytes) = IPv6	FlowLabel=106397 Length=32 NextHeader=TCP HopLimit=64 SrcIP=2a01:7e00::f03c:... DstIP=2400:8902::f03c:...

- Layer 1 (14 bytes) = SrcMAC=f2:3c:91:..:..:.. DstMAC=00:05:73:a0:0f:ff EthernetType=IPv6 Length=0}
- Layer 2 (40 bytes) = IPv6	FlowLabel=106397 Length=394 NextHeader=TCP HopLimit=64 SrcIP=2a01:7e00::f03c:... DstIP=2400:8902::f03c:...
</code></pre>
<!--kg-card-end: markdown--><p>To understand the reason for those two hops, we need to get back to the ARP entries. Here is what <code>ip -6 neigh show</code> shows for the two MAC addresses:</p><!--kg-card-begin: markdown--><table>
<thead>
<tr>
<th>IP</th>
<th>dev</th>
<th>lladr</th>
<th>State</th>
</tr>
</thead>
<tbody>
<tr>
<td>fe80::4094</td>
<td>eth0</td>
<td><code>00:05:73:a0:0f:fe</code></td>
<td>router REACHABLE</td>
</tr>
<tr>
<td>fe80::1</td>
<td>eth0</td>
<td><code>00:05:73:a0:0f:ff</code></td>
<td>router REACHABLE</td>
</tr>
</tbody>
</table>
<!--kg-card-end: markdown--><p>The two IPs are both <a href="https://en.wikipedia.org/wiki/Link-local_address">link-local</a> IPv6 addresses, and <code>fe80::1</code> is the default <a href="https://blogs.infoblox.com/ipv6-coe/fe80-1-is-a-perfectly-valid-ipv6-default-gateway-address/">IPv6 gateway</a>, while <code>fe80::4094</code> is actually a <em>random</em> link local address. Considering that the two mac address differ only by 1 bit, it should be ok to assume that they come from the same network device having two MAC addresses, maybe used in LAG mode. In any case, since those two macs are valid and reachable next-hops, it is totally acceptable for the Kernel to use any of those for IP routing.</p><p>The more interesting question is why does this only happen for a single remote VM, which, coincidentally, is also located in the Tokyo data-centre. To answer this question, I will need to deep dive into the Linux kernel, with the hint that it has something to do with flow-label. I will keep this for a later post. </p><p>For now, this multi next-hop routing behaviour is maybe not a bad news, because it could definitely explain the reason why the IPv6 connectivity got initially lost from the two VM in London and Tokyo.</p><h1 id="conclusion">Conclusion</h1><p>I would have liked to be able to do even more post-mortem analysis, but unfortunately, the failing server in London got an automated upgrade to migrate the external disk from SSD to NVMe, which required a reboot. </p><p>The somewhat good news is that the problem got fixed after the reboot. Yet, in terms of <em>devops</em>, that is <strong>not</strong> a good news, because having to reboot a server should always remain an exceptional situation. But given that the issue seems to have been caused by an IPv6 routing corruption, and that flushing the ARP entries did not solve the issue, there was not much alternative left but reboot.</p><p>Going forward, there is one thing to remember when handling connectivity issue: always check the IP routing table! This should actually be automated and periodically checked by monitoring tools.  </p><p></p><p> </p><p> </p><p></p><p></p>]]></content:encoded></item><item><title><![CDATA[A new look at SQLite]]></title><description><![CDATA[<p>Probably like many, i used to be looking at SQLite as a nice tool for working on  dev or staging apps, and Postgres or MySQL as <em>the</em> solution for production environment. Until I read the excellent <a href="https://apenwarr.ca/log/20211229">article</a> from Avery Pennarun, where SQLite is described as an open source tool with</p>]]></description><link>https://eadalabs.com/a-new-look-at-sqlite/</link><guid isPermaLink="false">61f359127aebff6a8aa945de</guid><dc:creator><![CDATA[Ron J]]></dc:creator><pubDate>Fri, 28 Jan 2022 05:04:45 GMT</pubDate><media:content url="https://images.unsplash.com/photo-1483736762161-1d107f3c78e1?crop=entropy&amp;cs=tinysrgb&amp;fit=max&amp;fm=jpg&amp;ixid=MnwxMTc3M3wwfDF8c2VhcmNofDJ8fGRhdGFiYXNlfGVufDB8fHx8MTY0MzMzODY3OQ&amp;ixlib=rb-1.2.1&amp;q=80&amp;w=2000" medium="image"/><content:encoded><![CDATA[<img src="https://images.unsplash.com/photo-1483736762161-1d107f3c78e1?crop=entropy&cs=tinysrgb&fit=max&fm=jpg&ixid=MnwxMTc3M3wwfDF8c2VhcmNofDJ8fGRhdGFiYXNlfGVufDB8fHx8MTY0MzMzODY3OQ&ixlib=rb-1.2.1&q=80&w=2000" alt="A new look at SQLite"><p>Probably like many, i used to be looking at SQLite as a nice tool for working on  dev or staging apps, and Postgres or MySQL as <em>the</em> solution for production environment. Until I read the excellent <a href="https://apenwarr.ca/log/20211229">article</a> from Avery Pennarun, where SQLite is described as an open source tool with "... <em>quality levels too high to be rational for the market to provide</em>".</p><p>Actually, why couldn't SQLite be used for production environment? What if my preconceived idea that SQLite is "too light to be performant" wasn't true? What if SQLite could outperform PGX or MySQL? By performance, I mean both read and write operations per second, but also the disk space needed to store the actual data. </p><h1 id="context">Context</h1><p>This study is done in the context of the Time-Series database (TSDB) used to store data from <a href="https://eadalabs.com/a-new-look-at-sqlite/waqi.info">waqi.info</a> and <a href="https://eadalabs.com/a-new-look-at-sqlite/aqicn.org/here/">aqicn.org</a> - There is a lot of data, and 99% of it is time-series data, which is currently carefully stored on a dual PGX MySQL cluster managed by a custom framework called ATSDB. </p><p>This ATSDB framework makes time-series storage automated, handling not only transparent table time slicing, but also string-to-index mapping, data streaming,  data re-compression, L1 and L2 caching, as well as backup, plus a few more  extras, all, of course, done in a completely automated way. </p><p>As we are now in the process of adding full support for PGX to ATSDB, the question that popped-up is, why couldn't we also add support SQLite? To get a rational answer, it was decided to first do a few benchmarks.</p><h1 id="benchmarking">Benchmarking</h1><p>In order to perform the benchmark, we used 2 tables type. The first one contains 2 columns, of type string, indexed to respectively 4 and 1 byte.  The second table contains an additional 6 columns of type int encoded in 4 bytes. Note that both table contain an implicit 4 bytes timestamp, automatically added by ATSDB for time-sliced tables.</p><pre><code class="language-go">type Table2Columns struct {
	Sensor string `atsdb:"map:uint32"`
	Status string `atsdb:"map:uint8"`
}

type Table8Columns struct {
	Sensor   string `atsdb:"map:uint32"`
	Status   string `atsdb:"map:uint8"`
	Count1   int
	Count2   int
	Count3   int
	Count4   int
	Count5   int
	Count6   int
}</code></pre><h2 id="write-speed">Write Speed</h2><p>The test consist in <strong>inserting</strong> as fast as possible data during a period of 1 minute, using either a MySQL backend, or a SQLite backend. The test platform is a 4 cores  Intel i5-7200U CPU running at 2.50GHz. </p><p>Here are the results for the insertion speed:</p><!--kg-card-begin: markdown--><table>
<thead>
<tr>
<th>Backend</th>
<th>2 columns</th>
<th>8 columns</th>
</tr>
</thead>
<tbody>
<tr>
<td>PGX</td>
<td>19,459 inserts/sec</td>
<td>17,433 inserts/sec</td>
</tr>
<tr>
<td>MySQL</td>
<td>17,189 inserts/sec</td>
<td>15,511 inserts/sec</td>
</tr>
<tr>
<td>SQLite</td>
<td>187,539 inserts/sec</td>
<td>111,118 inserts/sec</td>
</tr>
</tbody>
</table>
<!--kg-card-end: markdown--><p>Wow, I seriously did not expect this kind of result. SQLite is up to 10 times faster than MySQL and PGX, and this even without any specific optimisation. </p><h2 id="read-speed">Read Speed</h2><p>Next is the read-back speed. This can be important when having to preload caches that require to get, for instance, the past 8 hours of stored data.  </p><!--kg-card-begin: markdown--><table>
<thead>
<tr>
<th>Backend</th>
<th>2 columns</th>
<th>8 columns</th>
</tr>
</thead>
<tbody>
<tr>
<td>PGX</td>
<td>790,506 read/sec</td>
<td>402,582 read/sec</td>
</tr>
<tr>
<td>MySQL</td>
<td>782,317 read/sec</td>
<td>326,740 read/sec</td>
</tr>
<tr>
<td>SQLite</td>
<td>478,080 read/sec</td>
<td>207,321 read/sec</td>
</tr>
</tbody>
</table>
<!--kg-card-end: markdown--><p>This time PGX wins, but only by a factor of 2. And SQLite is already able to achieve almost half million reads/second, which is definitely enough for our use case. Further more, the ATSDB support streaming, so when having to read really huge amount of data way back in time, data is always progressively extracted. So, this SQLite read-back performance is definitely enough.</p><h2 id="storage-efficiency">Storage Efficiency</h2><p>Last, but not the least, we need to look at the disk usage, since when accumulating data for more than 15 years, no one wants to end-up having to pay an exponentially increasing bill to cloud providers for data-store. In other, the smaller the data store on disk, i.e. the lower the entropy, the better.  </p><p>The result below are obtained by taking the table size divided by the number of entries stored in the table. In the case of SQLite, it is the file size, while for MySQL, it is both the data and index size obtained from the <code>information schema</code> metadata. As for PGX, it is the <code>pg_total_relation_size (oid)</code> from the <code>pg_class c</code> </p><!--kg-card-begin: markdown--><table>
<thead>
<tr>
<th>Backend</th>
<th>2 columns</th>
<th>8 columns</th>
</tr>
</thead>
<tbody>
<tr>
<td>PGX</td>
<td>44.5 bytes per entry</td>
<td>68.5 bytes per entry</td>
</tr>
<tr>
<td>MySQL</td>
<td>39.8 bytes per entry</td>
<td>71.2 bytes per entry</td>
</tr>
<tr>
<td>SQLite</td>
<td>14.1 bytes per entry</td>
<td>44.6 bytes per entry</td>
</tr>
</tbody>
</table>
<!--kg-card-end: markdown--><p>Remember thats ATSDB adds an implicit 4 bytes for the timestamp. So, for the 2 column version, the minimum entropy is 4 (timestamp)+4 (sensor value)+1(status value), i..e 9 bytes per entry, and for the 8 column version, it requires an additional 6*4 bytes, eg a total of 35 bytes. Based, on those number, we can get the DB engine storage overhead, and the lower the better:</p><!--kg-card-begin: markdown--><table>
<thead>
<tr>
<th>Backend</th>
<th>2 columns</th>
<th>8 columns</th>
</tr>
</thead>
<tbody>
<tr>
<td>PGX</td>
<td>394% overhead</td>
<td>96% overhead</td>
</tr>
<tr>
<td>MySQL</td>
<td>342% overhead</td>
<td>103% overhead</td>
</tr>
<tr>
<td>SQLite</td>
<td>57% overhead</td>
<td>27% overhead</td>
</tr>
</tbody>
</table>
<!--kg-card-end: markdown--><p>Yet, to be precise, MySQL has a wonderful feature that allows to change the storage engine. Most commonly, InnoDB is used, but actually, MyISAM is so much more performant in terms of storage efficiency, at the cost of slower inserts speed. So, to be exact, we need to evaluate the storage efficiency comparing both MyISAM and InnoDB against SQLite:</p><!--kg-card-begin: markdown--><table>
<thead>
<tr>
<th>Backend</th>
<th>2 columns (bytes/entry)</th>
<th>2 columns (overhead)</th>
<th>8 columns (bytes/ entry)</th>
<th>8 columns (overhead)</th>
</tr>
</thead>
<tbody>
<tr>
<td>SQLite</td>
<td>14.1</td>
<td>57%</td>
<td>44.6</td>
<td>27%</td>
</tr>
<tr>
<td>MySQL (MyISAM)</td>
<td>10.1</td>
<td>12%</td>
<td>35.1</td>
<td>0%</td>
</tr>
</tbody>
</table>
<!--kg-card-end: markdown--><p>Using MyISAM, MySQL can over perform SQLite, and that makes it a better choice for archiving warm data, eg tables that at mostly read and seldom updated. But for live data, eg tables where most operations are about inserting or updating, and where reads are handling in memory using L1 cache, then SQLite is definitely a very good choice. </p><p>As for PGX, I do not have enough knowledge about its internal storage structure to say wether it can be optimised, but this <a href="https://ketansingh.me/posts/how-postgres-stores-rows/">post</a> should be a good starting link. I'll blog about that in a later post.</p><h1 id="conclusion">Conclusion</h1><p>The results speaks for it, and I must admit I was completely biased and wrong: SQLite is not a "light DB",  it is an over performing database! </p><p>And when taking into account how easy it is to deploy an SQLite database compared to deploying MySQL or PGX, this makes SQLite the preferred data-store choice for many applications. </p>]]></content:encoded></item><item><title><![CDATA[ESP32G Smart Ethernet Gateway]]></title><description><![CDATA[<p>I recently stumbled on the AI Thinker's "ESP32-G"  WiFi+BLE+Ethernet smart gateway because of its cost and form factor.</p><p>The gateway is available at 98CNY (~15 USD) on <a href="https://item.taobao.com/item.htm?id=633468623184">Tabao</a>, which makes it even more attractive than the low-cost TpLink <a href="https://openwrt.org/toh/tp-link/tl-wr703n">WRT54GL</a> routers, considering the vibrant Espressif  eco-system.</p><figure class="kg-card kg-image-card"><img src="https://eadalabs.com/content/images/2022/01/image.png" class="kg-image" alt="ESP32-G Smart Wifi, Ethernet, Bluetooth Gateway" srcset="https://eadalabs.com/content/images/size/w600/2022/01/image.png 600w, https://eadalabs.com/content/images/size/w1000/2022/01/image.png 1000w, https://eadalabs.com/content/images/size/w1600/2022/01/image.png 1600w, https://eadalabs.com/content/images/2022/01/image.png 1988w" sizes="(min-width: 720px) 720px"></figure><p> The primary objective</p>]]></description><link>https://eadalabs.com/esp32g-smart-ethernet-gateway/</link><guid isPermaLink="false">61d8fe2b7aebff6a8aa944eb</guid><category><![CDATA[esp32]]></category><category><![CDATA[ethernet]]></category><category><![CDATA[esp32-g]]></category><category><![CDATA[bridge]]></category><category><![CDATA[gateway]]></category><category><![CDATA[esp-idf]]></category><dc:creator><![CDATA[Ron J]]></dc:creator><pubDate>Sat, 08 Jan 2022 03:45:38 GMT</pubDate><media:content url="https://images.unsplash.com/photo-1627660080110-20045fd3875d?crop=entropy&amp;cs=tinysrgb&amp;fit=max&amp;fm=jpg&amp;ixid=MnwxMTc3M3wwfDF8c2VhcmNofDEwNXx8Ymx1ZXByaW50fGVufDB8fHx8MTY0MTYzMDA3Nw&amp;ixlib=rb-1.2.1&amp;q=80&amp;w=2000" medium="image"/><content:encoded><![CDATA[<img src="https://images.unsplash.com/photo-1627660080110-20045fd3875d?crop=entropy&cs=tinysrgb&fit=max&fm=jpg&ixid=MnwxMTc3M3wwfDF8c2VhcmNofDEwNXx8Ymx1ZXByaW50fGVufDB8fHx8MTY0MTYzMDA3Nw&ixlib=rb-1.2.1&q=80&w=2000" alt="ESP32G Smart Ethernet Gateway"><p>I recently stumbled on the AI Thinker's "ESP32-G"  WiFi+BLE+Ethernet smart gateway because of its cost and form factor.</p><p>The gateway is available at 98CNY (~15 USD) on <a href="https://item.taobao.com/item.htm?id=633468623184">Tabao</a>, which makes it even more attractive than the low-cost TpLink <a href="https://openwrt.org/toh/tp-link/tl-wr703n">WRT54GL</a> routers, considering the vibrant Espressif  eco-system.</p><figure class="kg-card kg-image-card"><img src="https://eadalabs.com/content/images/2022/01/image.png" class="kg-image" alt="ESP32G Smart Ethernet Gateway" srcset="https://eadalabs.com/content/images/size/w600/2022/01/image.png 600w, https://eadalabs.com/content/images/size/w1000/2022/01/image.png 1000w, https://eadalabs.com/content/images/size/w1600/2022/01/image.png 1600w, https://eadalabs.com/content/images/2022/01/image.png 1988w" sizes="(min-width: 720px) 720px"></figure><p> The primary objective of this gateway is to allow external Bluetooth or Wifi-mesh devices to connect to the LAN. The  Software  running on the gateway even comes with an MQTT client used to forward the message from the IOT devices connected to the gateway (see the <a href="https://docs.ai-thinker.com/_media/esp32-g_gateway_user_manual.pdf">doc</a> for more info).</p><p>Anyways, regardless of how nice the preloaded SW is, the real question is can one flash the gateway with a custom software... and the answer is of course yes! But, first, let's have a look inside the box:</p><figure class="kg-card kg-image-card kg-card-hascaption"><img src="https://eadalabs.com/content/images/2022/01/image-1.png" class="kg-image" alt="ESP32G Smart Ethernet Gateway" srcset="https://eadalabs.com/content/images/size/w600/2022/01/image-1.png 600w, https://eadalabs.com/content/images/2022/01/image-1.png 970w" sizes="(min-width: 720px) 720px"><figcaption>Inside the ESP32-G</figcaption></figure><p>It's actually quite simple, which is not a surprise considering the price. The Ethernet controller is an LAN8720, the same as the one used in the WT32-ETH01. There is also a fully wired CH340 Serial TTL converter which allows to flash the gateway only using the USB port. And the gateway also exposes 6 GPIOs which can be used to add extra sensor or modules. Cool! </p><p>In the previous post, I have been using the WT32-ETH01 configuration to drive the Ethernet LAN. Let's see how the configuration changes:</p><figure class="kg-card kg-image-card kg-card-hascaption"><img src="https://eadalabs.com/content/images/2022/01/image-2.png" class="kg-image" alt="ESP32G Smart Ethernet Gateway" srcset="https://eadalabs.com/content/images/size/w600/2022/01/image-2.png 600w, https://eadalabs.com/content/images/size/w1000/2022/01/image-2.png 1000w, https://eadalabs.com/content/images/size/w1600/2022/01/image-2.png 1600w, https://eadalabs.com/content/images/2022/01/image-2.png 1860w" sizes="(min-width: 720px) 720px"><figcaption>ESP32-G pinout and LAN8720 wiring</figcaption></figure><p>The main difference with the WT32-ETH01 is that there is no external oscillator  to drive the LAN8720, so the clock needs to be provided by the ESP32, which it does via the GPIO 17. The other difference is the Reset (aka Power) PIN, which is using GPIO5 on the ESP32-G, while it was GPIO16 on the WT32-ETH01:</p><!--kg-card-begin: markdown--><table>
<thead>
<tr>
<th>Configuration</th>
<th>ESP32-G</th>
<th>WT32-ETH01</th>
</tr>
</thead>
<tbody>
<tr>
<td>Power (Reset)</td>
<td>5</td>
<td>16</td>
</tr>
<tr>
<td>Phy Address</td>
<td>1</td>
<td>1</td>
</tr>
<tr>
<td>MDIO</td>
<td>18</td>
<td>18</td>
</tr>
<tr>
<td>MDC</td>
<td>23</td>
<td>23</td>
</tr>
<tr>
<td>Clock Mode</td>
<td>OUT</td>
<td>IN</td>
</tr>
<tr>
<td>Clock GPIO</td>
<td>17</td>
<td>0</td>
</tr>
</tbody>
</table>
<!--kg-card-end: markdown--><p>The following initialisation code does work fine:</p><pre><code class="language-C">#define ETH_TYPE ETH_PHY_LAN8720
#define ETH_POWER_PIN 5
#define ETH_MDC_PIN 23
#define ETH_MDIO_PIN 18
#define ETH_ADDR 1

void initialize_ethernet(void)
{
    eth_mac_config_t mac_config = ETH_MAC_DEFAULT_CONFIG();
    eth_phy_config_t phy_config = ETH_PHY_DEFAULT_CONFIG();
    phy_config.phy_addr = ETH_ADDR;
    phy_config.reset_gpio_num = ETH_POWER_PIN;

    mac_config.smi_mdc_gpio_num = ETH_MDC_PIN;
    mac_config.smi_mdio_gpio_num = ETH_MDIO_PIN;

    mac_config.clock_config.rmii.clock_mode = EMAC_CLK_OUT;
    mac_config.clock_config.rmii.clock_gpio = EMAC_CLK_OUT_180_GPIO;

    esp_eth_mac_t *mac = esp_eth_mac_new_esp32(&amp;mac_config);
    esp_eth_phy_t *phy = esp_eth_phy_new_lan8720(&amp;phy_config);

    esp_eth_config_t config = ETH_DEFAULT_CONFIG(mac, phy);
    ESP_ERROR_CHECK(esp_eth_driver_install(&amp;config, &amp;s_eth_handle));
}</code></pre><p>Et voila, that's all what is needed to start using this cool ESP32-Gateway. In my case, I have already added an extra $3 GPS module. </p><figure class="kg-card kg-image-card"><img src="https://eadalabs.com/content/images/2022/01/image-4.png" class="kg-image" alt="ESP32G Smart Ethernet Gateway" srcset="https://eadalabs.com/content/images/size/w600/2022/01/image-4.png 600w, https://eadalabs.com/content/images/size/w1000/2022/01/image-4.png 1000w, https://eadalabs.com/content/images/size/w1600/2022/01/image-4.png 1600w, https://eadalabs.com/content/images/2022/01/image-4.png 1776w" sizes="(min-width: 720px) 720px"></figure><p>In the next post, I'll be looking at enabling the WIFI mesh capability.</p>]]></content:encoded></item><item><title><![CDATA[WT32-ETH0 performance analysis using IPerf]]></title><description><![CDATA[<p>In the previous posts, we have been looking at enabling NuttX on the ESP32 based WT32-ETH0 module. In this post, we'll be looking at assessing the performance on the ethernet port, and verify if the ESP32 can really drive traffic up to 100MB/s. </p><h1 id="configuration">Configuration</h1><p>The easiest way to get</p>]]></description><link>https://eadalabs.com/esp32-nuttx-performance-analysis-using-iperf/</link><guid isPermaLink="false">605fd2ff76f397346a7f27c6</guid><dc:creator><![CDATA[Ron J]]></dc:creator><pubDate>Mon, 29 Mar 2021 01:59:42 GMT</pubDate><media:content url="https://eadalabs.com/content/images/2021/03/1000px-World_Speed_Limits.png" medium="image"/><content:encoded><![CDATA[<img src="https://eadalabs.com/content/images/2021/03/1000px-World_Speed_Limits.png" alt="WT32-ETH0 performance analysis using IPerf"><p>In the previous posts, we have been looking at enabling NuttX on the ESP32 based WT32-ETH0 module. In this post, we'll be looking at assessing the performance on the ethernet port, and verify if the ESP32 can really drive traffic up to 100MB/s. </p><h1 id="configuration">Configuration</h1><p>The easiest way to get a quick performance assessment of a network device is to use IPerf. Using NuttX, it's so simple- one just need to enable the <code>iperf example</code> under the network utilities within the apps.</p><pre><code>Application Configuration  ---&gt;
    Network Utilities  ---&gt;
        [*] iperf example
        (eth0) Wi-Fi Network device</code></pre><p>Note that since we will be testing the ethernet port,  the "Wi-Fi network device" is changed to <code>eth0</code>. </p><p>Also, since performance in critical for this test, we'll get rid of the network traces (aka <code>ninfo</code>) which could seriously impact the performance:</p><pre><code class="language-bash"> Build Setup  ---&gt;
   Debug Options  ---&gt;
     [] Network Informational Output</code></pre><p>After flashing, the iperf app is available from the Nutt shell:</p><pre><code class="language-bash">nsh&gt; ?
Builtin Apps:
  dhcpd        dhcpd_stop   nsh          ping6        sh
  dhcpd_start  iperf        ping         renew        wapi</code></pre><h1 id="initial-testing">Initial Testing</h1><p>So, let's run <em>iperf</em> then - but first with the UDP mode. On the server (the OpenWRT router in my case), this command is used:</p><pre><code>iperf -s -p 5471 -i 1 -w 416K -u</code></pre><p>And on the ESP32, the <em>iperf</em> client, this command is used:</p><pre><code class="language-bash">iperf -c 192.168.1.1 -p 5471 -i 1 -u</code></pre><p>And that's the result since from the server:</p><pre><code class="language-bash">[  3] local 192.168.1.1 port 5471 connected with 192.168.1.183 port 6184
[ ID] Interval       Transfer     Bandwidth        Jitter   Lost/Total Datagrams
[  3]  0.0- 1.0 sec  3.85 MBytes  32.3 Mbits/sec   0.250 ms    1/ 4035
[  3]  1.0- 2.0 sec  3.84 MBytes  32.2 Mbits/sec   0.248 ms    0/ 4022
[  3]  2.0- 3.0 sec  3.84 MBytes  32.2 Mbits/sec   0.248 ms    0/ 4022
[  3]  3.0- 4.0 sec  3.84 MBytes  32.2 Mbits/sec   0.248 ms    0/ 4023
[  3]  4.0- 5.0 sec  3.84 MBytes  32.2 Mbits/sec   0.247 ms    0/ 4022
[  3]  5.0- 6.0 sec  3.84 MBytes  32.2 Mbits/sec   0.248 ms    0/ 4023
[  3]  6.0- 7.0 sec  3.84 MBytes  32.2 Mbits/sec   0.248 ms    0/ 4023
[  3]  7.0- 8.0 sec  3.84 MBytes  32.2 Mbits/sec   0.249 ms    0/ 4022
[  3]  8.0- 9.0 sec  3.84 MBytes  32.2 Mbits/sec   0.248 ms    0/ 4023
[  3]  9.0-10.0 sec  3.84 MBytes  32.2 Mbits/sec   0.248 ms    0/ 4023
[  3] 10.0-11.0 sec  3.84 MBytes  32.2 Mbits/sec   0.249 ms    0/ 4023
[  3] 11.0-12.0 sec  3.84 MBytes  32.2 Mbits/sec   0.248 ms    0/ 4022
[  3] 12.0-13.0 sec  3.84 MBytes  32.2 Mbits/sec   0.249 ms    0/ 4023
[  3] 13.0-14.0 sec  3.84 MBytes  32.2 Mbits/sec   0.248 ms    0/ 4023
</code></pre><p>What we are expecting is up to 100Mb/s, and we get 1/3 if it out of the box. Not bad! But let's try to find if there any configuration which could be updated to improve the performance - before taking this initial result as granted.</p><h1 id="reverse-engineering-the-performance">Reverse Engineering the performance</h1><h2 id="inside-the-iperf-client">Inside the iPerf client</h2><p>The <a href="https://github.com/apache/incubator-nuttx-apps/blob/f3828ccbca3e45319d804cd5c3f91a4764de68c8/netutils/iperf/iperf.c#L446">code</a> used for the iperf client in UDP mode is quite simple. Here is its pseudo code:</p><pre><code>static int iperf_run_udp_client(void)
{
  sockfd = socket(AF_INET, SOCK_DGRAM, IPPROTO_UDP);
  setsockopt(sockfd, SOL_SOCKET, SO_REUSEADDR, &amp;opt, sizeof(opt));

  addr.sin_family = AF_INET;
  addr.sin_port = htons(s_iperf_ctrl.cfg.dport);
  addr.sin_addr.s_addr = s_iperf_ctrl.cfg.dip;

  buffer = s_iperf_ctrl.buffer;
  udp = (struct iperf_udp_pkt_t *)buffer;
  want_send = s_iperf_ctrl.buffer_len;

  while (!s_iperf_ctrl.finish)
    {
      actual_send = sendto(sockfd, buffer, want_send, 0,
                           (struct sockaddr *)&amp;addr, sizeof(addr));

      if (actual_send == want_send)
        {
          s_iperf_ctrl.total_len += actual_send;
        }
      else
        {
         .... handle the error ...
         }
    }
}</code></pre><p>So, just call "sendto" as fast as possible! What we have here is a standard producer consumer, where the producer is the above code calling the blocking <code>sendto</code> function, and the consumer the ethernet device consuming data via its DMA interface. </p><h2 id="inside-the-esp32-hardware">Inside the ESP32 hardware</h2><p>The way the DMA interface to the PHY works is explained in the diagram below (based on spec sheet for the <a href="https://www.espressif.com/sites/default/files/documentation/esp32_technical_reference_manual_en.pdf">ESP32</a> and <a href="https://ip.cadence.com/uploads/1099/TIP_PB_Xtensa_lx7_FINAL-pdf">LX7</a>). </p><figure class="kg-card kg-image-card"><img src="https://eadalabs.com/content/images/2021/03/image-4.png" class="kg-image" alt="WT32-ETH0 performance analysis using IPerf" srcset="https://eadalabs.com/content/images/size/w600/2021/03/image-4.png 600w, https://eadalabs.com/content/images/size/w1000/2021/03/image-4.png 1000w, https://eadalabs.com/content/images/size/w1600/2021/03/image-4.png 1600w, https://eadalabs.com/content/images/2021/03/image-4.png 1880w" sizes="(min-width: 720px) 720px"></figure><p>Note that in the case of the WT32-ETH01, the PHY is a <a href="http://ww1.microchip.com/downloads/en/DeviceDoc/00002165B.pdf">LAN8720A</a> from microchip, but there could be any other kind of PHY up to 100Mb/s.</p><p>From the ESP32 spec sheet in the <a href="https://www.espressif.com/sites/default/files/documentation/esp32_technical_reference_manual_en.pdf">Ethernet DMA Features</a></p><blockquote>The DMA has independent Transmit and Receive engines (...) space. The Transmit engine transfers data from the system memory to the device port (MTL), while the Receive engine transmits data from the device port to the system memory. The controller uses descriptors to efficiently move data from source to destination with minimal Host CPU intervention. The DMA is designed for packet-oriented data transmission, such as frames in Ethernet. The controller can be programmed to interrupt the Host CPU for normal situations, such as the completion of frame transmission or reception, or when errors occur</blockquote><p>So, all the ESP32 processor should have to do is to prepare the frames and insert them into the DMA ring controller. Assuming we want to achieve 100Mb/s with 1250 bytes frames, that would require having to prepare 10K packets per second, i.e. 100 microseconds per packet.  Since we are using UDP, which is quite lightweight in terms of protocol overhead - only having to compute checksums, that should not be a problem.</p><h2 id="inside-the-nuttx-udp-stack">Inside the NuttX UDP stack</h2><p>The call to <code>sendto</code> in the iperf client, eventually leads to the following calls:</p><pre><code>sendto-&gt; psock_sendto -&gt; inet_sendto -&gt; psock_udp_sendto </code></pre><p> The <code>psock_udp_sendto</code> comes is two flavors: The unbuffered one, and the buffered one. By default, the unbuffered one is used, so let's check this one. Here is its pseudo code:</p><pre><code class="language-C++">ssize_t psock_udp_sendto(FAR struct socket *psock, FAR const void *buf,
 size_t len, int flags, FAR const struct sockaddr *to, socklen_t tolen)
{
  FAR struct udp_conn_s *conn;
  struct sendto_s state;
  int ret;

  /* Get the underlying the UDP connection structure.  */
  conn = (FAR struct udp_conn_s *)psock-&gt;s_conn;

  /* Assure the the IPv4 destination address maps to a valid MAC 
   * address in the ARP table.
   */
  if (psock-&gt;s_domain == PF_INET)
    {
      FAR const struct sockaddr_in *into = 
        (FAR const struct sockaddr_in *)to;
      in_addr_t destipaddr = into-&gt;sin_addr.s_addr;

      /* Make sure that the IP address mapping is in the ARP table */
      ret = arp_send(destipaddr);
    }

  /* Initialize the state structure.  This is done with the network
   * locked because we don't want anything to happen until we are
   * ready. */

  net_lock();
  memset(&amp;state, 0, sizeof(struct sendto_s));

  /* This semaphore is used for signaling and, hence, should not have
   * priority inheritance enabled. */

  nxsem_init(&amp;state.st_sem, 0, 0);
  nxsem_set_protocol(&amp;state.st_sem, SEM_PRIO_NONE);
  state.st_buflen = len;
  state.st_buffer = buf;
  state.st_sock = psock;

  /* Get the device that will handle the remote packet transfers */
  state.st_dev = udp_find_raddr_device(conn);

  /* Set up the callback in the connection */
  state.st_cb = udp_callback_alloc(state.st_dev, conn);
  state.st_cb-&gt;flags   = (UDP_POLL | NETDEV_DOWN);
  state.st_cb-&gt;priv    = (FAR void *)&amp;state;
  state.st_cb-&gt;event   = sendto_eventhandler;

  /* Notify the device driver of the availability of TX data */
  netdev_txnotify_dev(state.st_dev);

  /* Wait for either the receive to complete or for an error/timeout to
   * occur. NOTES:  net_timedwait will also terminate if a signal
   * is received. */
  ret = net_timedwait(&amp;state.st_sem, _SO_TIMEOUT(psock-&gt;s_sndtimeo));

  /* Make sure that no further events are processed */
  udp_callback_free(state.st_dev, conn, state.st_cb);

  /* Release the semaphore */
  nxsem_destroy(&amp;state.st_sem);

  /* Unlock the network and return the result of the sendto() 
   * operation */
  net_unlock();
  return ret;
}</code></pre><p>The call to the next layer is done via the <code>netdev_txnotify_dev</code> which calls the driver <code>emac_txavail</code> callback. There are two things that could be of a concern in this code:</p><p>1- Systematic call to arp_send each time a UDP frame needs to be sent. Fortunately, the arp code is pretty optimised with an ARP cache, so the ARP frame will only be sent once. Even with this cache, it is still possible to optimize the overhead due the <code>arp_send</code> call by replacing <code>arp_send(destipaddr)</code> with <code>arp_lookup(destipaddr)?0:arp_send(destipaddr);</code>, but that does not give more than 3Mb/s improvement - so nothing serious to be considered right now.</p><p>2- Systematic call to <code>net_lock</code> and <code>net_unlock</code> while waiting for the frame to be sent. This means that, provided this function is the bottleneck in the UDP performance, it would not possible to use concurrent clients to improve the performance since each client would be sequentially scheduled.</p><h2 id="inside-the-nuttx-network-driver">Inside the NuttX network driver</h2><p>The two functions in <code>psock_udp_sendto</code> which trigger the actual frame transmission:</p><pre><code>  netdev_txnotify_dev(state.st_dev);
  net_timedwait(&amp;state.st_sem, _SO_TIMEOUT(psock-&gt;s_sndtimeo));
</code></pre><p>The first one (<code>netdev_txnotify_dev</code>) lets the driver know that some TX work is pending - what happens in practice is that the driver notifies the kernel worker queue (via <code>work_queue</code>) that works has to be done - and this work will be executed on the specific kernel worker task. There are two tasks, a low and high priority one, and the network is using the low priority one.  This <a href="https://github.com/apache/incubator-nuttx/blob/e699b6f85f8c1b023b4bcabaf2cca14567fbea97/arch/xtensa/src/esp32/esp32_emac.c#L161">comment</a> in the NuttX code explains why using the high priority queue for the network task is wrong:</p><blockquote>NOTE:  However, the network should NEVER run on the high priority work queue!  That queue is intended only to service short back end interrupt processing that never suspends.  Suspending the high priority work queue may bring the system to its knees!</blockquote><p>The second function (<code>net_timedwait</code>) waits for the first one to be scheduled and executed. What happens behind the scene is a smart "net_lock" manipulation allowing the TX worker, also waiting on the lock, to be executed "synchronously":</p><figure class="kg-card kg-image-card"><img src="https://eadalabs.com/content/images/2021/03/image-5.png" class="kg-image" alt="WT32-ETH0 performance analysis using IPerf" srcset="https://eadalabs.com/content/images/size/w600/2021/03/image-5.png 600w, https://eadalabs.com/content/images/size/w1000/2021/03/image-5.png 1000w, https://eadalabs.com/content/images/2021/03/image-5.png 1504w" sizes="(min-width: 720px) 720px"></figure><p>What the above diagram does not explain is the complexity of the <code>devif_timer</code> - which actually calls the callback <code>sendto_eventhandler</code> defined in the <code>psock_udp_sendto</code> function, and then calls the <code>emac_txpoll</code> which eventually starts the DMA via <code>emac_transmit</code>. </p><figure class="kg-card kg-image-card"><img src="https://eadalabs.com/content/images/2021/03/image-6.png" class="kg-image" alt="WT32-ETH0 performance analysis using IPerf" srcset="https://eadalabs.com/content/images/size/w600/2021/03/image-6.png 600w, https://eadalabs.com/content/images/size/w1000/2021/03/image-6.png 1000w, https://eadalabs.com/content/images/2021/03/image-6.png 1416w" sizes="(min-width: 720px) 720px"></figure><p>There is a small detail in the <code>net_timedwait</code> function worth commenting about: It is that it disables both interrupts (via <code><a href="https://github.com/apache/incubator-nuttx/blob/e699b6f85f8c1b023b4bcabaf2cca14567fbea97/sched/irq/irq_csection.c#L159">enter_critical_section</a></code> ) and the addition of any new task in the to be scheduled list (via <code><a href="https://github.com/apache/incubator-nuttx/blob/e699b6f85f8c1b023b4bcabaf2cca14567fbea97/sched/sched/sched_lock.c#L118">sched_lock</a></code>) . Then, when the semaphore is actually locked (via <code>nxsem_wait</code>), the next likely to be scheduled task in the  kernel worker. This way, the scheduling overhead should be limited.</p><h1 id="conclusions">Conclusions</h1><p>That was a quick deep dive into the NuttX network architecture, but an interesting one to learn how the network "full-stack" is  actually implemented. Yet, there are so many details which were not covered, but there is a real feeling of a solid architecture behind NuttX, and that's something very positive if one would decide to capitalise on NuttX for developing a large application eco-system.</p><p>Back to the performance, we did not quite reach the 100Mb/s. So, that will be the objective for the next post - where we will try to implement one of those <a href="https://trex-tgn.cisco.com/">dumb</a> packet blaster by directly bypassing the network stack and inserting frames directly into the DMA queue. We'll also  be looking at the options for micro-performance analysis - to measure the actual time spent by functions - for instance using the ESP32 <code>RSR</code> instruction which provides up to 4 nanosecond accuracy, or with the <a href="https://docs.espressif.com/projects/esp-idf/en/latest/esp32/api-reference/system/perfmon.html">performance monitor</a> from IDF.</p><p></p><p></p><p>Image credits: <a href="https://en.wikipedia.org/wiki/Speed_limits_by_country">https://en.wikipedia.org/wiki/Speed_limits_by_country</a></p>]]></content:encoded></item><item><title><![CDATA[Getting IPv6 working with NuttX from Day One]]></title><description><![CDATA[<p>It's already 2021: IPv6 has been created  more than 20 years ago, and nowadays more than one third of the traffic on the Internet is using IPv6. Yet, in the previous 3 post about enabling NuttX on the ESP32, I made a big mistake by forgetting to enable IPv6! </p><p>So,</p>]]></description><link>https://eadalabs.com/ipv6-from-day-1/</link><guid isPermaLink="false">605813f076f397346a7f2587</guid><dc:creator><![CDATA[Ron J]]></dc:creator><pubDate>Tue, 23 Mar 2021 02:39:34 GMT</pubDate><media:content url="https://eadalabs.com/content/images/2021/03/urban-railway-system.jpeg" medium="image"/><content:encoded><![CDATA[<img src="https://eadalabs.com/content/images/2021/03/urban-railway-system.jpeg" alt="Getting IPv6 working with NuttX from Day One"><p>It's already 2021: IPv6 has been created  more than 20 years ago, and nowadays more than one third of the traffic on the Internet is using IPv6. Yet, in the previous 3 post about enabling NuttX on the ESP32, I made a big mistake by forgetting to enable IPv6! </p><p>So, in this post, we'll try to get an IPv6 configuration working. At the same time, we will try get rid of the IPv4 NAT which was required for the bridge setup, and which would not be necessary any more using <a href="https://en.wikipedia.org/wiki/Prefix_delegation">IPv6 prefix delegation</a>.</p><h1 id="configuration">Configuration</h1><p>Enabling IPv6 is as simple as:</p><pre><code>Networking Support  ---&gt;
   Internet Protocol Selection  ---&gt;
      [*] IPv6</code></pre><p>But of course, after all the features enabled in the past 3 post, the probability the the compilation works the first time is quite small. And indeed, here is the error:</p><pre><code>xtensa-esp32-elf-ld: nuttxspace/nuttx/staging/libarch.a(esp32_wlan.o): in function `wlan_ifup':
esp32_wlan.c:(.text.wlan_ifup+0x45): undefined reference to `winfo'
make[1]: *** [nuttx] Error 1</code></pre><p>Fortunately, that is just a <a href="https://github.com/apache/incubator-nuttx/blob/e03218ab716c99f335761f8f583eab6f9a181a99/arch/xtensa/src/esp32/esp32_wlan.c#L1188">typo issue</a> with a function incorrectly named <code>winfo</code> while it should <code>ninfo</code> . So, after patching the file, compilation works fine</p><h1 id="run-time">Run Time</h1><p>After booting, the networking configuration is still not quite right:</p><pre><code>nsh&gt; ifconfig
eth0    Link encap:Ethernet HWaddr a8:03:2a:a1:2c:c4 at UP
        inet addr:192.168.1.182 DRaddr:192.168.1.1 Mask:255.255.255.0
        inet6 addr: fc00::2/112
        inet6 DRaddr: fc00::1/112</code></pre><h2 id="static-ip-address-configuration">Static IP address configuration</h2><p>Before getting into the advanced IPv6 address configuration using DHCPv6 or SLAAC, let's first try instead to use a predefined IPv6 address. To make sue the configuration tool is working, let's just use the same IPv6 configuration  as the default one:</p><pre><code>ifconfig wlan0 fc00::1 inet6 gateway fc00::2</code></pre><p>That works fine. However, when trying to input longer IPv6 address, the NSH console fails to accept longer inputs... That's yet another configuration which should be changed, to allow "longer command lines"..</p><pre><code>Application Configuration  ---&gt;
    NSH Library  ---&gt;
        Command Line Configuration  ---&gt;
        	(200) Max command line length</code></pre><p>After this change, it is possible to configure the IPv6 address:</p><pre><code>nsh&gt; ifconfig eth0 2888:8201:2848:abcd:0123:0123:0123:d58e inet6 gateway 2888:8201:2848:abcd::1

nsh&gt; ifconfig
eth0    Link encap:Ethernet HWaddr a8:03:2a:a1:2c:c5 at UP
        inet addr:192.168.1.183 DRaddr:192.168.1.1 Mask:255.255.255.0
        inet6 addr: 2408:8206:2640:a833:4e5:c85d:bf9:d58e/64
        inet6 DRaddr: 2408:8206:2640:a833::1/64
</code></pre><h2 id="ping6-application">Ping6 application</h2><p>The default ping NSH command does not support IPv6. To enable <code>ping6</code>, the following needs to be added:</p><pre><code>Networking Support  ---&gt;
   ICMPv6 Networking Support  ---&gt;
      [*] Enable ICMPv6 networking
Application Configuration  ---&gt;
   System Libraries and NSH Add-Ons  ---&gt;
   	  [*] ICMPv6 'ping6' command (NEW)</code></pre><p>Voila, after fixing another small compilation bug due to an <a href="https://github.com/apache/incubator-nuttx/blob/6a6ad96066fd1cad48af41316ea00de466060804/arch/xtensa/src/esp32/esp32_wlan.c#L1214">unreferenced function</a> we are now ready to ping <code>openwrt.com</code> , aka 2600:3c02:1::2d4f:f40e:</p><pre><code>nsh &gt; ping6 2600:3c02:1::2d4f:f40e
psock_socket: ERROR: socket address family unsupported: domain:10, type:2, protocol:58
socket: ERROR: psock_socket() failed: -106
ERROR: socket() failed: 106
</code></pre><h2 id="icmpv6-socket-support">ICMPv6 socket support</h2><p>Well... that did not work as expected! It fails on domain (<em>family</em>)= <code>PF_INET6</code>, type = <code>SOCK_DGRAM</code> and protocol = <code>IPPROTO_ICMP6</code>. The missing link is the <code>NET_ICMPv6_SOCKET</code> configuration which needs to be enabled too</p><pre><code>Networking Support  ---&gt;
   ICMPv6 Networking Support  ---&gt;
      [*]   IPPROTO_ICMP6 socket support
      [*]   Solicit destination addresses</code></pre><p>We also enable the "<em>solicit destination addresses</em>", which covers the ICMPv6 Neighbour Discovery (ND) protocol, for the solicitation packet. That's the same as ARP, but for IPv6. Without it, the stack would not be able to resolve the ethernet address associated to the IPv6 gateway.</p><pre><code>nsh&gt; ping6 2600:3c02:1::2d4f:f40e
sendto_request: Outgoing ICMPv6 packet length: 105 (65)
neighbor_dumpentry: Entry found: 2408:8206:2640:a833:0000:0000:0000:0001
neighbor_ethernet_out: Outgoing IPv6 Packet length: 119 (65)
emac_transmit: d_buf=0x3ffb2cb6 d_len=119
emac_recvframe: RX bytes 119
icmpv6_poll_eventhandler: flags: 0002
icmpv6_datahandler: Buffered 94 bytes
icmpv6_readahead: Received 65 bytes (of 94)
56 bytes from 2600:3c02:1::2d4f:f40e icmp_seq=0 time=410 ms
</code></pre><h2 id="dnsv6-support">DNSv6 support</h2><p>Let's try to ping6 with the domain name instead of the direct IP address:</p><pre><code>nsh&gt; ping6 openwrt.com
udp_send: Outgoing UDP packet length: 57
udp_readahead: Received 57 bytes (of 74)
dns_recv_response: ID 37905
dns_recv_response: Query 128
dns_recv_response: Error 0
dns_recv_response: Num questions 1, answers 1, authrr 0, extrarr 0
dns_recv_response: Question: type=001c, class=0001
dns_parse_name: Compressed answer
dns_recv_response: Answer: type=001c, class=0001, ttl=0034ab, length=0010
dns_recv_response: IPv6 address: 0000:0000:0100:0000:0200:3c00:0000:2600
...
udp_readahead: Received 45 bytes (of 62)
dns_recv_response: ID 46525
dns_recv_response: Query 128
dns_recv_response: Error 0
dns_recv_response: Num questions 1, answers 1, authrr 0, extrarr 0
dns_recv_response: Question: type=0001, class=0001
dns_parse_name: Compressed answer
dns_recv_response: Answer: type=0001, class=0001, ttl=0034cf, length=0004
dns_recv_response: IPv4 address: 72.52.179.175
PING6 2600

sendto_request: Outgoing ICMPv6 packet length: 105 (65)
icmpv6_readahead: Received 65 bytes (of 94)
56 bytes from 2600:3c02:1::2d4f:f40e icmp_seq=0 time=450 ms
</code></pre><p>Good news, all works fine, so no special configuration needed here.</p><h1 id="let-s-slaac-">Let's SLAAC ...</h1><p>When it comes to IPv6, the common approach is to use <a href="https://www.networkacademy.io/ccna/ipv6/stateless-address-autoconfiguration-slaac">SLAAC</a> rather than DHCPv6. The main advantage of SLAAC is its simplicity - it requires to have ICMPv6 (which we have already confirmed to work), and support for two Neighbour discovery (ND) packets: Router discovery and advertisement  (RD/RA).</p><p>Looking at the NuttX code, one can find the needed <a href="https://github.com/apache/incubator-nuttx/blob/6ff11d8c7659f908597103d33f6b338b3124557c/net/icmpv6/icmpv6_rsolicit.c#L75">code</a> to handle router solicitations:</p><pre><code>/****************************************************************************
 * Name: icmpv6_rsolicit
 *
 * Description:
 *   Set up to send an ICMPv6 Router Solicitation message.  This version
 *   is for a standalone solicitation.  If formats:
 *
 *   - The IPv6 header
 *   - The ICMPv6 Neighbor Router Message
 *
 *   The device IP address should have been set to the link local address
 *   prior to calling this function.
 *
****************************************************************************/

void icmpv6_rsolicit(FAR struct net_driver_s *dev);</code></pre><p>This code is only available when activating the configuration is <code>NET_ICMPv6_AUTOCONF</code> .</p><pre><code>Networking Support  ---&gt;
   ICMPv6 Networking Support  ---&gt;
       [*]   ICMPv6 auto-configuration</code></pre><p>Compilation works fine, but after flashing the new firmware and sending the interface UP using the <code>ifconfig eth0 up</code> , nothing happens - and only IPv4 address is obtained via the DHCPv4. </p><p>The missing link is the <a href="https://github.com/apache/incubator-nuttx-apps/blob/3bf2f317169f07bcd6f83022e46fc18b01aec9be/netutils/netlib/netlib_autoconfig.c#L58">netlib_icmpv6_autoconfiguration</a> function, which is responsible for sending the <a href="https://github.com/apache/incubator-nuttx/blob/6ff11d8c7659f908597103d33f6b338b3124557c/net/netdev/netdev_ioctl.c#L870"><code>SIOCIFAUTOCONF</code></a> ioctl to the driver, which will in turn call the <a href="https://github.com/apache/incubator-nuttx/blob/6ff11d8c7659f908597103d33f6b338b3124557c/net/icmpv6/icmpv6_autoconfig.c#L268">icmpv6_autoconfig</a> function, and then call our icmpv6_rsolicit. The problem is that this <a href="https://github.com/apache/incubator-nuttx-apps/blob/3bf2f317169f07bcd6f83022e46fc18b01aec9be/netutils/netlib/netlib_autoconfig.c#L58">netlib_icmpv6_autoconfiguration</a> function is only called during the netlib <a href="https://github.com/apache/incubator-nuttx-apps/blob/3bf2f317169f07bcd6f83022e46fc18b01aec9be/netutils/netinit/netinit.c#L620">initialisation</a> process (aka <em>netinit</em>), but not during the <a href="https://github.com/apache/incubator-nuttx-apps/blob/3bf2f317169f07bcd6f83022e46fc18b01aec9be/nshlib/nsh_netcmds.c#L568">nsh net commands</a>, especially the <code>ifconfig eth0 up</code> one . A quick fix consists is adding this line before:</p><pre><code class="language-diff">git diff nshlib/nsh_netcmds.c                                    08:49:40
diff --git a/nshlib/nsh_netcmds.c b/nshlib/nsh_netcmds.c
index d4633e10..1e4605c4 100644
--- a/nshlib/nsh_netcmds.c
+++ b/nshlib/nsh_netcmds.c
@@ -918,6 +918,12 @@ int cmd_ifconfig(FAR struct nsh_vtbl_s *vtbl, int argc, char **argv)
 #endif /* CONFIG_NET_IPv4 */
 #endif /* CONFIG_NETINIT_DHCPC || CONFIG_NETINIT_DNS */

+#ifdef CONFIG_NET_ICMPv6_AUTOCONF
+  /* Perform ICMPv6 auto-configuration */
+  netlib_icmpv6_autoconfiguration(ifname);
+#endif
+
+
 #if defined(CONFIG_NETINIT_DHCPC)
   /* Get the MAC address of the NIC */
</code></pre><p>Voila, after this fix, the <code>ifconfig eth0 up</code> will successfully acquire it's global IPv6 via SLAAC:</p><pre><code>nsh&gt; ifconfig eth0 up
icmpv6_autoconfig: Auto-configuring eth0
icmpv6_autoconfig: lladdr=fe80:0000:0000:0000:aa03:2aff:fea1:2cc5
icmpv6_solicit: Outgoing ICMPv6 Neighbor Solicitation length: 72 (32)
icmpv6_solicit: Outgoing ICMPv6 Neighbor Solicitation length: 72 (32)
icmpv6_solicit: Outgoing ICMPv6 Neighbor Solicitation length: 72 (32)
icmpv6_solicit: Outgoing ICMPv6 Neighbor Solicitation length: 72 (32)
icmpv6_solicit: Outgoing ICMPv6 Neighbor Solicitation length: 72 (32)
icmpv6_router_eventhandler: flags: 6000 sent: 0
icmpv6_rsolicit: Outgoing ICMPv6 Router Solicitation length: 56 (16)
icmpv6_rwait: Waiting...
icmpv6_rnotify: Notified
icmpv6_setaddresses: preflen=64 netmask=ffff:ffff:ffff:ffff:0000:0000:0000:0000
icmpv6_setaddresses: prefix=2408:8206:2640:a833:0000:0000:0000:0000
icmpv6_setaddresses: IP address=2408:8206:2640:a833:aa03:2aff:fea1:2cc5
icmpv6_setaddresses: DR address=fe80:0000:0000:0000:ee41:18ff:fe0c:53dc
icmpv6_rwait_cancel: Canceling...
icmpv6_autoconfig: Timed out... retrying 1
icmpv6_router_eventhandler: flags: 6000 sent: 0
icmpv6_rsolicit: Outgoing ICMPv6 Router Solicitation length: 56 (16)
icmpv6_rnotify: Notified
icmpv6_setaddresses: preflen=64 netmask=ffff:ffff:ffff:ffff:0000:0000:0000:0000
icmpv6_setaddresses: prefix=2408:8206:2640:a833:0000:0000:0000:0000
icmpv6_setaddresses: IP address=2408:8206:2640:a833:aa03:2aff:fea1:2cc5
icmpv6_setaddresses: DR address=fe80:0000:0000:0000:ee41:18ff:fe0c:53dc
icmpv6_rwait: Waiting...
icmpv6_rwait_cancel: Canceling...

</code></pre><p>Voila, it's not fully working :-)</p><pre><code>nsh&gt; ifconfig
eth0    Link encap:Ethernet HWaddr a8:03:2a:a1:2c:c5 at UP
        inet addr:192.168.1.183 DRaddr:192.168.1.1 Mask:255.255.255.0
        inet6 addr: 2408:8206:2640:a833:aa03:2aff:fea1:2cc5/64
        inet6 DRaddr: fe80::ee41:18ff:fe0c:53dc/64</code></pre><h1 id="conclusion">Conclusion</h1><p>That's was not a straightforward configuration, and there are quite a few patches needed for the NuttX source code to compile, but fortunately, IPv6 is now working natively on our ESP32 hardware running NuttX. The natural next step would be to even remove the IPv4 configuration and only get an IPv6 network, but we'll keep this configuration for a later post.</p><p>In the next post, we'll be looking at enabling the DHCPv6 server and prefix delegation, as a way to remove the need for the IPv4 NAT in our ETH-WIFI bridge setup. </p><p>There are also now quite  a few fixes that should be pushed upstream, so I'll make a PR and update this post when the PR is ready.</p><hr><p>Photo credits:  <a href="https://www.flickr.com/photos/anniemole/313981428/in/photostream/">flickr.com/photos/anniemole/313981428/in/photostream/</a></p><p></p><p></p>]]></content:encoded></item><item><title><![CDATA[Esp32, NuttX, and bridged networking]]></title><description><![CDATA[<p> In this blog post, we will be looking at the NuttX configuration needed for bridged networking from the WIFI interface to the Ethernet interface, using an ESP32 based WT32-ETH01. We'll also be looking at the routing options, and the possibility to route via tunnels. </p><p>Note that this is follow-up from</p>]]></description><link>https://eadalabs.com/esp32-nuttx-and-bridged-networking/</link><guid isPermaLink="false">604c201576f397346a7f22e9</guid><dc:creator><![CDATA[Ron J]]></dc:creator><pubDate>Sun, 14 Mar 2021 23:00:00 GMT</pubDate><media:content url="https://eadalabs.com/content/images/2021/03/bridge-building-competition.jpg" medium="image"/><content:encoded><![CDATA[<img src="https://eadalabs.com/content/images/2021/03/bridge-building-competition.jpg" alt="Esp32, NuttX, and bridged networking"><p> In this blog post, we will be looking at the NuttX configuration needed for bridged networking from the WIFI interface to the Ethernet interface, using an ESP32 based WT32-ETH01. We'll also be looking at the routing options, and the possibility to route via tunnels. </p><p>Note that this is follow-up from the <a href="https://eadalabs.com/nuttx-with-esp32-and-ethernet/">previous post</a> about enabling a dual ETH+WIFI networking stack on the WT32-ETH01.</p><h1 id="bridge-as-a-nuttx-application">Bridge as a NuttX application</h1><p>So, let's start with the bridge example provided in the <a href="https://github.com/apache/incubator-nuttx-apps/tree/master/examples/bridge">NuttX apps</a>. It contains two implementation files, one for the hosts, and one for the bridge. The intention is to drive the traffic in this way:</p><figure class="kg-card kg-image-card"><img src="https://eadalabs.com/content/images/2021/03/image.png" class="kg-image" alt="Esp32, NuttX, and bridged networking" srcset="https://eadalabs.com/content/images/size/w600/2021/03/image.png 600w, https://eadalabs.com/content/images/size/w1000/2021/03/image.png 1000w, https://eadalabs.com/content/images/2021/03/image.png 1308w" sizes="(min-width: 720px) 720px"></figure><p>The bridge works by creating two tasks ( using <code>task_create</code> ). Each task if getting it's IP address by <a href="https://github.com/apache/incubator-nuttx-apps/blob/6f75c1b3d61edb126469ff70554dbf41466bfb4c/examples/bridge/bridge_main.c#L269">invoking</a> the DHCP client (so, the bridge could work with a single network interface). It then <a href="https://github.com/apache/incubator-nuttx-apps/blob/6f75c1b3d61edb126469ff70554dbf41466bfb4c/examples/bridge/bridge_main.c#L403">listens</a> on an UDP socket, on a given port, and for each packet received, it will forward it to the outgoing IP address. Two tasks are needed as each task is handling unidirectional traffic. </p><p>Unfortunately, the bridge example is not of any help. For a bridge to work, we should be able to intercept any kind of traffic, at IP level, and forward it at IP level. The standard way to do that is to use raw sockets, but let's first have a look at <a href="https://github.com/adamdunkels/uip">uIP</a> networking stack, in case it would already have such bridge capability.</p><h1 id="bridge-as-a-nuttx-system-service">Bridge as a NuttX System Service</h1><p>Looking at the NuttX net folder, one can notice the <a href="https://github.com/apache/incubator-nuttx/tree/master/net/ipforward">ipforward</a> service, described as the "L2 forwarding service". </p><h2 id="l2-forwarding">L2 forwarding</h2><p>That's exactly what we need - so, let's enable it with this configuration:</p><pre><code class="language-bash">Networking Support  ---&gt;
   Internet Protocol Selection  ---&gt;
       [*] Enable L2 forwarding</code></pre><p>By default, the services has a table 4 forwarding <a href="https://github.com/apache/incubator-nuttx/blob/9523d4bea40e26152af47716e35b04d60b57176e/net/ipforward/ipfwd_alloc.c#L69">entries</a>, which get populated dynamically as incoming  packet come in. When the stack receives a packet with a destination IP address which does not match the interface IP address, it willl call the <code>ipv4_forward</code> function, described as:</p><blockquote>This function is called from ipv4_input when a packet is received that is not destined for us.  In this case, the packet may need to be forwarded to another device (or sent back out the same device) depending configuration, routing table information, and the IPv4 networks served by various network devices.</blockquote><p>The <code>ipv4_forward</code> function will first try to find the forwarding interface, using <code>netdev_findby_ripv4addr</code>  </p><pre><code class="language-C">int ipv4_forward(FAR struct net_driver_s *dev, FAR struct ipv4_hdr_s *ipv4)
{
  in_addr_t destipaddr;
  in_addr_t srcipaddr;
  FAR struct net_driver_s *fwddev;
  int ret;

  /* Search for a device that can forward this packet. */

  destipaddr = net_ip4addr_conv32(ipv4-&gt;destipaddr);
  srcipaddr  = net_ip4addr_conv32(ipv4-&gt;srcipaddr);

  fwddev     = netdev_findby_ripv4addr(srcipaddr, destipaddr);
  if (fwddev == NULL)
    {
      nwarn("WARNING: Not routable\n");
      return (ssize_t)-ENETUNREACH;
    }</code></pre><h2 id="routing-table">Routing Table</h2><p>From the <code>netdev_findby_ripv4addr</code> one can notice that there is a <code>CONFIG_NET_ROUTE</code> configuration used for the routing table. It is not enabled by default, so let's activate it under:</p><pre><code class="language-bash">Networking Support  ---&gt;
	Routing Table Configuration  ---&gt;
    	[*] Routing table support</code></pre><p>It also comes with a default of 4 routes, but note that routes are handled at network prefix level, while forwarding entries are at IP level. So, if a route handles a /16 prefix, you could have up to 64K  IP to forward.</p><p>Unfortunately, the compilation does not work the first time, complaining about  missing <code>RTF_XXX</code> definitions.</p><pre><code class="language-C++">src/network.c: In function 'wapi_act_route_gw':
src/network.c:141:17: error: 'RTF_UP' undeclared (first use in this function); did you mean 'IFF_UP'?
   rt.rt_flags = RTF_UP | RTF_GATEWAY;
                 ^~~~~~
                 IFF_UP
src/network.c:141:17: note: each undeclared identifier is reported only once for each function it appears in
src/network.c:141:26: error: 'RTF_GATEWAY' undeclared (first use in this function)
   rt.rt_flags = RTF_UP | RTF_GATEWAY;
                          ^~~~~~~~~~~
src/network.c:144:22: error: 'RTF_HOST' undeclared (first use in this function)
       rt.rt_flags |= RTF_HOST;</code></pre><p>The issue is actually from the WAPI wireless configuration shell that was enabled for the WIFI configuration. The good news is that it tells us that WAPI can be used to inspect and configure the routes.  The bad news is that it seems we will have to manually patch the code for the compilation to work.</p><p>The reason for the failure is that WAPI wants to ioctl <code>SIOCADDRT</code> and <code>SIOCDELRT</code> to configure the routing entries. So, let's have a look at how the net routing component handles those two ioctl. That's done in the <a href="https://github.com/apache/incubator-nuttx/blob/9523d4bea40e26152af47716e35b04d60b57176e/net/netdev/netdev_ioctl.c#L1373">netdev_ioctl.c</a> file:</p><pre><code class="language-C++">#ifdef CONFIG_NET_ROUTE
static int netdev_rt_ioctl(FAR struct socket *psock, int cmd,  FAR struct rtentry *rtentry)
{
  switch (cmd)
    {
      case SIOCADDRT:  /* Add an entry to the routing table */
          return ioctl_add_ipv4route(rtentry);
          break;
          ...
      case SIOCDELRT:  /* Add an entry to the routing table */
          return ioctl_del_ipv4route(rtentry);
          break;
}
#endif</code></pre><p>Good news, both <code>ioctl_del_ipv4route</code> and <code>ioctl_add_ipv4route</code> ignore the <code>rt_flag</code>  which WAPI was trying to set, so we can safely comment out the line <code>rt.rt_flags = RTF_UP | RTF_GATEWAY;</code> in WAPI <a href="https://github.com/apache/incubator-nuttx-apps/blob/6f75c1b3d61edb126469ff70554dbf41466bfb4c/wireless/wapi/src/network.c#L141">network.c</a>. Those <a href="https://www.unix.com/man-page/freebsd/9/rtentry/">flags</a> are used to indicate if the routing entry is a host or a gateway - and we'll figure out later if this matters to us.</p><h2 id="run-time-route-configuration">Run-time route configuration</h2><p>Now that the compilation is working, let's try to configure the routes via WAPI. Here are the logs which  can be seen from the NuttShell:</p><pre><code class="language-bash">NuttShell (NSH) NuttX-10.0.1
nsh&gt; ipv4_forward: WARNING: Packet forwarding to same device not supported (srcIP=192.168.1.203 dstIP=224.0.0.251)
udp_input: WARNING: No listener on UDP port 5353</code></pre><p>The message <em>packet forwarding to same device not supported</em>  tells us that a packet has been received from "192.168.1.203" and aimed for "224.0.0.251", which is a multicast IP for the <a href="https://stackoverflow.com/questions/12483717/what-is-the-multicast-doing-on-224-0-0-251">mDNS</a> service running on port 5353. This protocol is not enabled by default, hence the "<em>no listener</em>" warning log. </p><p>So, let's try to check the routes now with a <code>route</code> command:</p><pre><code class="language-bash">nsh&gt; route
SEQ   TARGET          NETMASK         ROUTER
   1. 0.0.0.0         0.0.0.0         192.168.1.1</code></pre><p>The netmask <code>0</code> corresponds to a /32 CIDR, meaning that all IP addresses will be considered for this route. As for the router <code>192.168.1.1</code>, this is the openwrt router, and this is exactly the expected IP. So, first, let's try to add a route</p><pre><code class="language-bash">nsh&gt; addroute 8.8.8.8/32 192.168.1.1
nsh&gt; route
SEQ   TARGET          NETMASK         ROUTER
   1. 0.0.0.0         0.0.0.0         192.168.1.1
   2. 8.8.8.8         255.255.255.255 192.168.1.1</code></pre><p>Then let's try to ping the newly added destination (8.8.8.8). It works fine because the gateway is properly defined.</p><pre><code class="language-bash">nsh&gt; ping 8.8.8.8
PING 8.8.8.8 56 bytes of data
56 bytes from 8.8.8.8: icmp_seq=1 time=190 ms</code></pre><p>Now, let's try to add a route to a non existing gateway. Ping should fail with a "no route available" - not because it can not find the route, but because it can not find a way to reach the gateway (to be precise, because the gateway in on the same subnet as the ESP, the ESP will try to ARP the gateway, and since no one replies, it will fail to send the ICMP packet).</p><pre><code class="language-bash">nsh&gt; addroute 4.4.4.4/32 192.168.1.2
nsh&gt; route
SEQ   TARGET          NETMASK         ROUTER
   1. 0.0.0.0         0.0.0.0         192.168.1.1
   2. 8.8.8.8         255.255.255.255 192.168.1.1
   3. 4.4.4.4         255.255.255.255 192.168.1.2
nsh&gt; ping 4.4.4.4
PING 4.4.4.4 56 bytes of data
arp_send_eventhandler: flags: 2000 sent: 0
arp_send: ERROR: arp_wait failed: -116
...
icmp_sendto: ERROR: Not reachable
</code></pre><h2 id="bridge-configuration">Bridge Configuration</h2><p>Now that routes are working, we just need to slight change the ESP configuration, from a Wifi Station to a WIFI Access point, which the Mac will connect to, as described in the diagram below:</p><figure class="kg-card kg-image-card"><img src="https://eadalabs.com/content/images/2021/03/image-1.png" class="kg-image" alt="Esp32, NuttX, and bridged networking" srcset="https://eadalabs.com/content/images/size/w600/2021/03/image-1.png 600w, https://eadalabs.com/content/images/size/w1000/2021/03/image-1.png 1000w, https://eadalabs.com/content/images/2021/03/image-1.png 1440w" sizes="(min-width: 720px) 720px"></figure><p>There are a few configurations which need to be changed, and the first is to add a DHCP server so that the MAC can get an IP address when connecting to the WT32 Access Point (AP):</p><pre><code class="language-bash">Application Configuration  ---&gt; 
    Network Utilities  ---&gt;
        [*] DHCP server</code></pre><p>The second is to turn the WIFI from a Station to an AP. Let's try to do that with WAPI:</p><pre><code class="language-bash">nsh&gt; wapi mode wlan0 WAPI_MODE_MASTER</code></pre><p>Unfortunately, that does not work, because... well, the underling <code>SIOCSIWMODE</code> is not <a href="https://github.com/apache/incubator-nuttx/blob/59a5d038426de9609d386d2ea49f2ed9700bf69c/arch/xtensa/src/esp32/esp32_wlan.c#L1363">implemented</a> in the current esp32 driver. Never mind, let's try the reverse bridge, where the Mac is connected via Ethernet:</p><figure class="kg-card kg-image-card"><img src="https://eadalabs.com/content/images/2021/03/image-2.png" class="kg-image" alt="Esp32, NuttX, and bridged networking" srcset="https://eadalabs.com/content/images/size/w600/2021/03/image-2.png 600w, https://eadalabs.com/content/images/size/w1000/2021/03/image-2.png 1000w, https://eadalabs.com/content/images/2021/03/image-2.png 1438w" sizes="(min-width: 720px) 720px"></figure><p>For this configuration, there should only be a need for the DHCP server.  So, after flashing the new firmware configuration, and connecting the Mac to the ESP via the ethernet cable, I could see the following logs:</p><pre><code>udp_input: WARNING: No listener on UDP port 67</code></pre><p>That just tells us that the DHCP Server is not started. Looking at the code, the missing link seems that we also need to enable the server from the app config. </p><pre><code class="language-bash">Application Configuration  ---&gt;
  Examples  ---&gt;
    [*] DHCP server example</code></pre><p>Voila, let's reflash and start the app from the Nutt Shell:</p><pre><code>nsh&gt; dhcpd_start eth0
nsh&gt; ifconfig
eth0    Link encap:Ethernet HWaddr a8:03:2a:a1:2c:c5 at UP
        inet addr:10.0.0.1 DRaddr:10.0.0.1 Mask:255.255.255.0

wlan0   Link encap:Ethernet HWaddr a8:03:2a:a1:2c:c4 at UP
        inet addr:192.168.1.182 DRaddr:192.168.1.1 Mask:255.255.255.0</code></pre><p>Bingo, it works - the <code>eth0</code> interface is having its own subnet. A quick check on the Mac confirms that all works fine :-)</p><pre><code class="language-bahs">⋊&gt; ~/P/m/n/nuttx on master ⨯ ifconfig en5 flags=8863&lt;UP,BROADCAST,SMART,RUNNING,SIMPLEX,MULTICAST&gt; mtu 1500
	options=4&lt;VLAN_MTU&gt;
	inet6 fe80::8ca:dbd1:a602:81d6%en5 prefixlen 64 secured scopeid 0xf
	inet 10.0.0.2 netmask 0xffffff00 broadcast 10.0.0.255
	nd6 options=201&lt;PERFORMNUD,DAD&gt;
	media: autoselect (100baseTX &lt;full-duplex&gt;)
	status: active</code></pre><p>Last, step, let's try to ping from the Mac and see if it gets through the ESP - for that to work, we should first ensure that the route is properly set on the ESP:</p><pre><code>nsh&gt; addroute default 192.168.1.1 wlan0
nsh&gt; route
SEQ   TARGET          NETMASK         ROUTER
   1. 0.0.0.0         0.0.0.0         192.168.1.1</code></pre><p>The first attempt to <code>ping 8.8.8.8</code> on the mac resulted in a Request timeout. So dumping the the traffic on the OpenWRT router showed and frames were correctly forwarded:</p><pre><code>root@OpenWrt:~# tcpdump ether host A8:03:2A:A1:2C:C4 -nS
tcpdump: verbose output suppressed, use -v or -vv for full protocol decode
listening on br-lan, link-type EN10MB (Ethernet), capture size 262144 bytes
04:03:59.040947 IP 10.0.0.2 &gt; 8.8.8.8: ICMP echo request, id 19945, seq 9, length 64
04:04:00.040736 IP 10.0.0.2 &gt; 8.8.8.8: ICMP echo request, id 19945, seq 10, length 64</code></pre><p>Only problem, no response... At first glance, it looks like a routing issue on the OpenWRT, which needs to add a route for the 10.x.x.x subnet to the ESP gateway</p><pre><code>root@OpenWrt:~# route add -net 10.0.0.0/24 gateway 192.168.1.182</code></pre><p>After that, it finally works:</p><pre><code>PING 8.8.8.8 (8.8.8.8): 56 data bytes
64 bytes from 8.8.8.8: icmp_seq=0 ttl=108 time=293.731 ms
64 bytes from 8.8.8.8: icmp_seq=1 ttl=108 time=352.465 ms
64 bytes from 8.8.8.8: icmp_seq=2 ttl=108 time=334.467 ms</code></pre><p>While, technically, we could say the routing is working, actually, this last step is not a desired one. What should happen is that the ESP should <a href="https://en.wikipedia.org/wiki/Network_address_translation">NAT</a> the IP address from the Mac; that's a standard process for routers. </p><p>I am not sure of the reason why NuttX does not NAT the outgoing packet - there does not seems to be any trace of "NAT" in the <code>net</code> source code, and ip forward service does not seems to be handling the NAT, but only decrementing the TTL in the IP header. Let's keep this parked fro now, and we'll come back to this NAT configuration issue in a later blog post.</p><h2 id="tunnelling-traffic">Tunnelling Traffic</h2><p>Now that routes are working, the next logical step is to tunnel part of the traffic.   The standard way in Posix is to create TAP/TUN virtual interfaces, and fortunately for us, NuttX supports such interfaces. But let's keep this investigation for the next blog.</p><p></p><h1 id="conclusion">Conclusion</h1><p>Obviously, the more we dig into NuttX and the more complex it becomes. But at the same time, since NuttX is a Posix compatible system, we can refer to all the online information about Posix to troubleshoot our configuration. That makes it not only much easier, but also much more interesting as all our configuration learnings can be applied to standard Linux too.</p><p>Next step, we will be checking the Ethernet driver performance, to check if we can really reach 100Mb/s, as well as the IP forwarding performance. We'll also be looking at setting up the TAP/TUN interface for the tunnelling.</p><figure class="kg-card kg-gallery-card kg-width-wide kg-card-hascaption"><div class="kg-gallery-container"><div class="kg-gallery-row"><div class="kg-gallery-image"><img src="https://eadalabs.com/content/images/2021/03/diy-vinci-bridge.png" width="2000" height="943" alt="Esp32, NuttX, and bridged networking" srcset="https://eadalabs.com/content/images/size/w600/2021/03/diy-vinci-bridge.png 600w, https://eadalabs.com/content/images/size/w1000/2021/03/diy-vinci-bridge.png 1000w, https://eadalabs.com/content/images/size/w1600/2021/03/diy-vinci-bridge.png 1600w, https://eadalabs.com/content/images/2021/03/diy-vinci-bridge.png 2002w" sizes="(min-width: 1200px) 1200px"></div></div></div><figcaption>a Da Vinci self-supporting bridge - It's a bit like NuttX and ESP: Robust but unstable for beginners</figcaption></figure><p></p><p></p><p> </p>]]></content:encoded></item><item><title><![CDATA[Esp32, NuttX, and Ethernet on a WT32-ETH01]]></title><description><![CDATA[<p>This is a follow-up from the previous post about <a href="https://eadalabs.com/nuttx-esp32-macos/">getting started with ESP32 and NuttX</a>. This time, we'll be looking at trying to enable the Ethernet module on the LAN8720-based <a href="http://www.wireless-tag.com/portfolio/wt32-eth01/">WT32-ETH01</a>, and use NuttX micro-IP networking stack as a dual interface stack.</p><p>So, let's get started with the ethernet configuration</p>]]></description><link>https://eadalabs.com/nuttx-with-esp32-and-ethernet/</link><guid isPermaLink="false">6043464276f397346a7f21f4</guid><dc:creator><![CDATA[Ron J]]></dc:creator><pubDate>Sat, 06 Mar 2021 10:08:02 GMT</pubDate><media:content url="https://eadalabs.com/content/images/2021/03/ethernet-switch.jpg" medium="image"/><content:encoded><![CDATA[<img src="https://eadalabs.com/content/images/2021/03/ethernet-switch.jpg" alt="Esp32, NuttX, and Ethernet on a WT32-ETH01"><p>This is a follow-up from the previous post about <a href="https://eadalabs.com/nuttx-esp32-macos/">getting started with ESP32 and NuttX</a>. This time, we'll be looking at trying to enable the Ethernet module on the LAN8720-based <a href="http://www.wireless-tag.com/portfolio/wt32-eth01/">WT32-ETH01</a>, and use NuttX micro-IP networking stack as a dual interface stack.</p><p>So, let's get started with the ethernet configuration first.</p><h1 id="configuring-the-ethernet-peripheral">Configuring the Ethernet peripheral</h1><p>Looking at the NuttX <a href="https://github.com/apache/incubator-nuttx/blob/master/drivers/net/Kconfig">configuration</a> for network drivers, one can notice that there is a flag for the 8720. So let's try to enable it from the menu config:</p><pre><code>Device Drivers  ---&gt;
	Network Device/PHY Support  ----
    		*** External Ethernet MAC Device Support ***</code></pre><p>Unfortunately, there is nothing under <code>External Ethernet MAC Device Support</code> , and that's where one should enable the LAN8720. So looking a bit further into the config, I noticed that for the external ethernet to be supported, the <code>ARCH_HAVE_PHY</code> <em>config</em> should be enabled. </p><p>Unfortunately, that's not the case in any of the ESP32 default config, and there is no trace of a <code>ARCH_HAVE_PHY</code> config in the <a href="https://github.com/apache/incubator-nuttx/blob/master/arch/xtensa/src/esp32/Kconfig">ESP KConfig</a>.  But looking at the specific ESP32 config, I noticed that ETH could be enabled by selecting <code>Ethernet MAC</code> under the peripheral selection.</p><pre><code>System Type  ---&gt;
    ESP32 Peripheral Selection  ---&gt;
        Ethernet MAC
    Ethernet configuration  ---&gt;
        (9) RX description number
        (8) TX description number
        (23) MDC Pin
        (18) MDIO Pin
        (5) Reset PHY Pin --&gt;&gt;&gt; Change to 16
        (1) PHY address     
</code></pre><p>The default configuration, when compared to the indication <a href="https://github.com/arendst/Tasmota/issues/9496#issuecomment-750812635">here</a> is correct, at least for MDC and MDIO. </p><pre><code>mdc_pin: GPIO23
mdio_pin: GPIO18
clk_mode: GPIO0_IN
phy_addr: 1
power_pin: GPIO16</code></pre><p>However, the <em><a href="https://github.com/apache/incubator-nuttx/blob/ed0a1b724b5f152f8a1ffb83af992f52e3baabe3/arch/xtensa/src/esp32/esp32_emac.c#L515">reset phy pin</a></em>, which is referred as a <em><a href="https://github.com/espressif/arduino-esp32/blob/f7fc8ab37714efc1e00ef640a3e4f51ca647fac5/libraries/WiFi/src/ETH.cpp#L46">power pin</a></em> in esp-idf, is wrong- it should be  16 and not the default 5. </p><p>As concerns the <em>clock mode</em>, it is handled by this <a href="https://github.com/espressif/esp-idf/blob/0bfff0b25a9bf9495edf29dc455a114e60104434/components/ethernet/emac_main.c#L1117">code</a> in the esp-idf: </p><pre><code class="language-c">    if (emac_config.clock_mode != ETH_CLOCK_GPIO0_IN) {
#if CONFIG_SPIRAM_SUPPORT
        // make sure Ethernet won't have conflict with PSRAM
        if (emac_config.clock_mode &gt;= ETH_CLOCK_GPIO16_OUT) {
            if (esp_spiram_is_initialized()) {
                ESP_LOGE(TAG, "GPIO16 and GPIO17 are occupied by PSRAM, please switch to ETH_CLOCK_GPIO_IN or ETH_CLOCK_GPIO_OUT mode");
                ret = ESP_FAIL;
                goto _verify_err;
            } else {
                ESP_LOGW(TAG, "Using GPIO16/17 to output Ethernet RMII clock, make sure you don't have PSRAM on board");
            }
        }
#endif
        // 50 MHz = 40MHz * (6 + 4) / (2 * (2 + 2) = 400MHz / 8
        rtc_clk_apll_enable(1, 0, 0, 6, 2);
        REG_SET_FIELD(EMAC_EX_CLKOUT_CONF_REG, EMAC_EX_CLK_OUT_H_DIV_NUM, 0);
        REG_SET_FIELD(EMAC_EX_CLKOUT_CONF_REG, EMAC_EX_CLK_OUT_DIV_NUM, 0);

        if (emac_config.clock_mode == ETH_CLOCK_GPIO0_OUT) {
            PIN_FUNC_SELECT(PERIPHS_IO_MUX_GPIO0_U, FUNC_GPIO0_CLK_OUT1);
            REG_WRITE(PIN_CTRL, 6);
            ESP_LOGD(TAG, "EMAC 50MHz clock output on GPIO0");
        } else if (emac_config.clock_mode == ETH_CLOCK_GPIO16_OUT) {
            PIN_FUNC_SELECT(PERIPHS_IO_MUX_GPIO16_U, FUNC_GPIO16_EMAC_CLK_OUT);
            ESP_LOGD(TAG, "EMAC 50MHz clock output on GPIO16");
        } else if (emac_config.clock_mode == ETH_CLOCK_GPIO17_OUT) {
            PIN_FUNC_SELECT(PERIPHS_IO_MUX_GPIO17_U, FUNC_GPIO17_EMAC_CLK_OUT_180);
            ESP_LOGD(TAG, "EMAC 50MHz inverted clock output on GPIO17");
        }
    }</code></pre><p>There is no such thing yet int he NuttX version, but fortunately for us, the NuttX SW assumes that the <a href="https://github.com/apache/incubator-nuttx/blob/ed0a1b724b5f152f8a1ffb83af992f52e3baabe3/arch/xtensa/src/esp32/esp32_emac.c#L137">clock mode is GPIO0</a>.</p><p>So, voila, the configuration looks good - with the exception of the power pin - and so we are ready to compile the SW.</p><h1 id="compilation">Compilation</h1><p>Well, after all the struggles met during the first phase, for enabling WIFI, expecting that the compilation would work without any error the first time would have be utopia. And indeed, here is what the compiler said:</p><pre><code class="language-C"> CC:  chip/esp32_emac.c
chip/esp32_emac.c: In function 'phy_enable_interrupt':
chip/esp32_emac.c:1243:38: error: 'MII_INT_REG' undeclared (first use in this function); did you mean 'MISC_REG'?
   ret = emac_read_phy(EMAC_PHY_ADDR, MII_INT_REG, &amp;regval);
                                      ^~~~~~~~~~~
                                      MISC_REG
chip/esp32_emac.c:1243:38: note: each undeclared identifier is reported only once for each function it appears in
chip/esp32_emac.c:1243:52: error: 'regval' undeclared (first use in this function); did you mean 'sigval'?
   ret = emac_read_phy(EMAC_PHY_ADDR, MII_INT_REG, &amp;regval);
                                                    ^~~~~~
                                                    sigval
chip/esp32_emac.c:1249:39: error: 'MII_INT_CLREN' undeclared (first use in this function)
                            (regval &amp; ~MII_INT_CLREN) | MII_INT_SETEN);
                                       ^~~~~~~~~~~~~
chip/esp32_emac.c:1249:56: error: 'MII_INT_SETEN' undeclared (first use in this function); did you mean 'MII_MSR_ESTATEN'?
                            (regval &amp; ~MII_INT_CLREN) | MII_INT_SETEN);
                                                        ^~~~~~~~~~~~~
                                                        MII_MSR_ESTATEN
chip/esp32_emac.c:1240:12: warning: unused variable 'phyval' [-Wunused-variable]
   uint16_t phyval;
            ^~~~~~
chip/esp32_emac.c: In function 'emac_ioctl':
chip/esp32_emac.c:186:24: warning: initialization of 'struct esp32_emacmac_s *' from incompatible pointer type 'struct esp32_emac_s *' [-Wincompatible-pointer-types]
 #define NET2PRIV(_dev) ((struct esp32_emac_s *)(_dev)-&gt;d_private)
                        ^
chip/esp32_emac.c:2098:38: note: in expansion of macro 'NET2PRIV'
   FAR struct esp32_emacmac_s *priv = NET2PRIV(dev);
                                      ^~~~~~~~
chip/esp32_emac.c:2111:17: warning: implicit declaration of function 'phy_notify_subscribe' [-Wimplicit-function-declaration]
           ret = phy_notify_subscribe(dev-&gt;d_ifname, req-&gt;pid, &amp;req-&gt;event);
                 ^~~~~~~~~~~~~~~~~~~~
chip/esp32_emac.c:2116:21: error: too many arguments to function 'phy_enable_interrupt'
               ret = phy_enable_interrupt(priv);
                     ^~~~~~~~~~~~~~~~~~~~
chip/esp32_emac.c:1238:12: note: declared here
 static int phy_enable_interrupt(void)
            ^~~~~~~~~~~~~~~~~~~~
make[1]: *** [esp32_emac.o] Error 1
make: *** [arch/xtensa/src/libarch.a] Error 2</code></pre><p>Something is obviously wrong in the ESP code here. For example, from the <a href="https://github.com/apache/incubator-nuttx/blob/ed0a1b724b5f152f8a1ffb83af992f52e3baabe3/arch/xtensa/src/esp32/esp32_emac.c#L1238">esp32_emac.c</a>, one can see that there is a conflict in the signature of the <code>phy_enable_interrupt</code> function, which is declared without any argument.</p><pre><code class="language-C">#if defined(CONFIG_NETDEV_PHY_IOCTL) &amp;&amp; defined(CONFIG_ARCH_PHY_INTERRUPT)
static int phy_enable_interrupt(void)
{
  ...
}
#endif</code></pre><p>But later called, in the same file, with an extra <em>priv</em> argument.</p><pre><code class="language-C">static int emac_ioctl(struct net_driver_s *dev, int cmd, unsigned long arg)
{
    ...
#ifdef CONFIG_NETDEV_PHY_IOCTL
#ifdef CONFIG_ARCH_PHY_INTERRUPT
	...
    ret = phy_enable_interrupt(priv);
    ...
#endif</code></pre><p>Most likely, the ESP team did not try yet to verify the ethernet driver with the <code>CONFIG_NETDEV_PHY_IOCTL</code> configuration. </p><p>So, after disabling the IO control under <code>Networking support-&gt;Network Device Operations-&gt;Enable PHY ioctl</code> , bingo!, the compilation is working :-)</p><h1 id="starting-the-ethernet-driver">Starting the Ethernet driver</h1><pre><code>make download ESPTOOL_PORT=/dev/cu.SLAB_USBtoUART ESPTOOL_BAUD=115200 ESPTOOL_BINDIR={$NUTTX_SPACE}/esp-bins</code></pre><p>After a successful flashing, of course,  no trace of the Ethernet driver initialisation... Looking deeper into the code, the issue is conflict on the <code>NETDEV_LATEINIT</code> config - which <a href="https://github.com/apache/incubator-nuttx/blob/da2f9f1357ed451b7cf85a1f1a0a34f2834468bf/arch/xtensa/src/esp32/esp32_emac.c#L2264">must</a> be disabled for ethernet:</p><pre><code class="language-C">#if !defined(CONFIG_NETDEV_LATEINIT)
void up_netinitialize(void)
{
  esp32_emac_init();
}
#endif</code></pre><p> So, let's disable it under <code>Networking Support -&gt;  Link layer support -&gt; Late driver initialization</code> (I wonder why I enabled it in first place). After that, the interface is finally visible:</p><pre><code class="language-shell">nsh&gt; ifconfig
eth0    Link encap:Ethernet HWaddr b4:e6:2d:95:b1:05 at DOWN
        inet addr:0.0.0.0 DRaddr:0.0.0.0 Mask:0.0.0.0

wlan0   Link encap:Ethernet HWaddr b4:e6:2d:95:b1:05 at UP
        inet addr:192.168.1.218 DRaddr:192.168.1.1 Mask:255.255.255.0</code></pre><p>Both interface share the same ethernet address. This will definitely confuse the router, which both WIFI and ETH are connected to, but let's try anyways. Next step is to get the interface up using  the <code>ifup eth0</code> command.</p><pre><code>nsh&gt; ifup eth0
netdev_ifr_ioctl: cmd: 1818
emac_init_phy: PHY register 0x0 is: 0x3100
emac_init_phy: PHY register 0x2 is: 0x0007
emac_init_phy: PHY register 0x3 is: 0xc0f1
emac_wait_linkup: PHY register 0x1 is: 0x782d
ifup eth0...OK</code></pre><p>Good news, it works, and frames can be received on the ethernet port. But patience, the IP address is still not resolved. </p><p>One of the reason the DHCP doe not work for ETH0 is the conflict on the mac addresses. The way the ethernet driver gets its address is using this code:</p><pre><code class="language-C">static int emac_read_mac(uint8_t *mac)
{
  uint32_t regval[2];
  uint8_t *data = (uint8_t *)regval;
  uint8_t crc;
  int i;

  /* The MAC address in register is from high byte to low byte */

  regval[0] = getreg32(MAC_ADDR0_REG);
  regval[1] = getreg32(MAC_ADDR1_REG);

  crc = data[6];
  for (i = 0; i &lt; 6; i++)
    {
      mac[i] = data[5 - i];
    }

  if (crc != esp_crc8(mac, 6))
    {
      nerr("ERROR: Failed to check MAC address CRC\n");

      return -EINVAL;
    }

  return 0;
}</code></pre><p>There is nothing wrong with this function, expect that the WIFI adapter uses the same "copy pasted" function. There's definitely room for a clean network architecture here. But anyways, let's hack a <code>mac[5]+=1</code> just after the <em>CRC</em> check to ensure the mac is unique.</p><pre><code>nsh&gt; ifconfig
eth0    Link encap:Ethernet HWaddr a8:03:2a:62:5a:89 at DOWN
        inet addr:0.0.0.0 DRaddr:0.0.0.0 Mask:0.0.0.0

wlan0   Link encap:Ethernet HWaddr a8:03:2a:62:5a:88 at UP
        inet addr:192.168.1.111 DRaddr:192.168.1.1 Mask:255.255.255.0
        
nsh&gt; ifup eth0
netdev_ifr_ioctl: cmd: 1818
emac_init_gpio: emac_init_gpio
emac_config: emac_config: got EMAC_SR_E
emac_read_mac: emac_read_mac -&gt; a8:03:2a:62:5a:89
emac_config: emac_config: macaddr=a8:03:2a:62:5a:8a
emac_init_phy: PHY register 0x0 is: 0x3100
emac_init_phy: PHY register 0x2 is: 0x0007
emac_init_phy: PHY register 0x3 is: 0xc0f1
emac_wait_linkup: emac_wait_linkup:
emac_wait_linkup: PHY register 0x1 is: 0x782d
emac_init_dma: emac_init_dma
emac_start: emac_start
ifup eth0...OK

nsh&gt; ifconfig eth0 up
cmd_ifconfig: Host IP: up
...
dhcpc_request: Received ACK
dhcpc_request: Got IP address 192.168.1.112
dhcpc_request: Got netmask 255.255.255.0
dhcpc_request: Got DNS server 192.168.1.1
dhcpc_request: Got default router 192.168.1.1
dhcpc_request: Lease expires in 43200 seconds
...

nsh&gt; ifconfig
eth0    Link encap:Ethernet HWaddr a8:03:2a:62:5a:89 at UP
        inet addr:192.168.1.112 DRaddr:192.168.1.1 Mask:255.255.255.0

wlan0   Link encap:Ethernet HWaddr a8:03:2a:62:5a:88 at UP
        inet addr:192.168.1.111 DRaddr:192.168.1.1 Mask:255.255.255.0</code></pre><p> Et voila, it looks like it's working :-) </p><h1 id="verifying-the-dual-network-stack">Verifying the dual network stack </h1><p>But well, as usual, something went wrong. Pinging the <code>wlan0</code> worked, but not the <code>eth0</code> (no response). Let's have a look at the logs, when do a ping from the mac:</p><pre><code>PING 192.168.1.112 (192.168.1.112): 56 data bytes
Request timeout for icmp_seq 0
Request timeout for icmp_seq 1
Request timeout for icmp_seq 2</code></pre><p>The corresponding log shows a </p><pre><code>wlan_rxpoll: ARP frame</code></pre><p>Good news, the ARP frame is receive, bad news, it is received by the WIFI driver and not the ETH driver.  Why would this happen? Because the router thinks the IP <code>192.168.1.112</code> is located on the wifi interface. And for this to happen, that would mean that the DHCP request has been done for WIFI. So, let's look at how the DHCP client actually works:</p><pre><code>nsh&gt; ifconfig eth0 up
dhcpc_open: MAC: a8:03:2a:62:5a:89
dhcpc_request: Broadcast DISCOVER
udp_callback: flags: 0010
sendto_eventhandler: flags: 0010
udp_send: UDP payload: 256 (0) bytes
udp_send: Outgoing UDP packet length: 284
udp_callback: flags: 0010
wlan_rxpoll: IPv4 frame
udp_callback: flags: 0002
udp_eventhandler: flags: 0002
udp_recvfrom_newdata: Received 300 bytes (of 300)
udp_eventhandler: UDP done
dhcpc_request: Received OFFER from c0a80101
...</code></pre><p>The only hint is the <code>wlan_rxpoll: IPv4 frame</code> - that just tells us that the frame is received over WIFI. Since we have a dual interface system, the solution in posix consists in "binding to device" the socket used for the DHCP client. So, let's have a look at the <a href="https://github.com/apache/incubator-nuttx-apps/blob/d4259acc156bc193b0581e3ce332d223537954bb/netutils/dhcpc/dhcpc.c#L474">NuttX DHCP Client</a>:</p><pre><code>FAR void *dhcpc_open(FAR const char *interface, FAR const void *macaddr,
                     int maclen)
{
      ...

      /* Create a UDP socket */
      pdhcpc-&gt;sockfd = socket(PF_INET, SOCK_DGRAM, 0);

       ...

#ifdef CONFIG_NET_UDP_BINDTODEVICE
      /* Bind socket to interface, because UDP packets have to be sent 
       * to the broadcast address at a moment when it is not possible 
       * to decide the target network device using the local or 
       * remote address (which is, by definition and purpose of 
       * DHCP, undefined yet).
       */

      ret = setsockopt(pdhcpc-&gt;sockfd, IPPROTO_UDP, UDP_BINDTODEVICE,
                       pdhcpc-&gt;interface, strlen(pdhcpc-&gt;interface));
       ...
#endif

</code></pre><p>Of course, the <code>NET_UDP_BINDTODEVICE</code> config is not enabled by default. So let's try again after enabling under <code>Networking Support &gt; UDP Networking &gt; UDP Bind-to-device support</code>.</p><p>Bingo, it's working :-)</p><pre><code>nsh&gt; ifconfig eth0 up
dhcpc_open: MAC: a8:03:2a:62:5a:89
dhcpc_request: Broadcast DISCOVER
emac_txavail_work: ifup: 1
udp_callback: flags: 0010
sendto_eventhandler: flags: 0010
udp_send: UDP payload: 256 (0) bytes
udp_send: Outgoing UDP packet length: 284
emac_transmit: d_buf=0x3ffb6dec d_len=298
udp_callback: flags: 0010
emac_recvframe: RX bytes 342
emac_rx_interrupt_work: IPv4 frame
udp_callback: flags: 0002
net_dataevent: No receive on connection
udp_datahandler: Buffered 300 bytes
udp_readahead: Received 300 bytes (of 317)
dhcpc_request: Received OFFER from c0a80101</code></pre><p>Ping is also working :-)</p><pre><code class="language-bash">⋊&gt; ~/P/m/n/nuttx on master ⨯ ping 192.168.1.112
PING 192.168.1.112 (192.168.1.112): 56 data bytes
64 bytes from 192.168.1.112: icmp_seq=0 ttl=64 time=5.438 ms
64 bytes from 192.168.1.112: icmp_seq=1 ttl=64 time=5.306 ms
64 bytes from 192.168.1.112: icmp_seq=2 ttl=64 time=5.176 ms
64 bytes from 192.168.1.112: icmp_seq=3 ttl=64 time=4.650 ms
64 bytes from 192.168.1.112: icmp_seq=4 ttl=64 time=5.147 ms
64 bytes from 192.168.1.112: icmp_seq=5 ttl=64 time=5.436 ms</code></pre><p>And NuttX log also shows a correct <em>icmp input</em> message after the <em>emac_rx interrupt</em> message.</p><pre><code> nsh&gt; emac_recvframe: RX bytes 98
emac_rx_interrupt_work: IPv4 frame
icmp_input: Outgoing ICMP packet length: 84 (84)
emac_transmit: d_buf=0x3ffb3e5c d_len=98
</code></pre><h1 id="conclusion">Conclusion</h1><p>What I really like with NuttX is this feeling of Posix - like, when one has a dual stack, the solution is to bind to the device, and for NuttX, that's the exact same solution - a no brainer which brings a lot of advantages when talking about portability. </p><p>The downside is that the ESP32 port is still far from being stable, but let's hope for the best and wait for Espressif to implement a nice network driver architecture in the short future.</p><p>Next step,  will be a deep dive into the NuttX dual stack micro-IP architecture, and how to enable the bridging mode. That will be the focus on the next blog.</p><figure class="kg-card kg-gallery-card kg-width-wide kg-card-hascaption"><div class="kg-gallery-container"><div class="kg-gallery-row"><div class="kg-gallery-image"><img src="https://eadalabs.com/content/images/2021/03/nuttx-on-wt32-eth0.jpg" width="961" height="605" alt="Esp32, NuttX, and Ethernet on a WT32-ETH01" srcset="https://eadalabs.com/content/images/size/w600/2021/03/nuttx-on-wt32-eth0.jpg 600w, https://eadalabs.com/content/images/2021/03/nuttx-on-wt32-eth0.jpg 961w" sizes="(min-width: 720px) 720px"></div></div></div><figcaption>The WT32-ETH01 used for this experiment</figcaption></figure>]]></content:encoded></item><item><title><![CDATA[Getting started with NuttX and Esp32 on MacOS]]></title><description><![CDATA[<!--kg-card-begin: markdown--><p>In this post, we will be looking at getting an ESP32 working with NuttX and WIFI networking enabled, using a Mac as the development platform. The ESP hardware is a LOLIN32, but any dev-kit C ESP32 hardware will work fine.</p>
<p>The inspiration for this post is based on Sara Monteiro's</p>]]></description><link>https://eadalabs.com/nuttx-esp32-macos/</link><guid isPermaLink="false">603dc687098a6941c452d838</guid><category><![CDATA[esp32]]></category><category><![CDATA[nuttx]]></category><category><![CDATA[iot]]></category><dc:creator><![CDATA[Ron J]]></dc:creator><pubDate>Sun, 28 Feb 2021 02:54:46 GMT</pubDate><media:content url="https://eadalabs.com/content/images/2021/02/nuttx-esp32-3.png" medium="image"/><content:encoded><![CDATA[<!--kg-card-begin: markdown--><img src="https://eadalabs.com/content/images/2021/02/nuttx-esp32-3.png" alt="Getting started with NuttX and Esp32 on MacOS"><p>In this post, we will be looking at getting an ESP32 working with NuttX and WIFI networking enabled, using a Mac as the development platform. The ESP hardware is a LOLIN32, but any dev-kit C ESP32 hardware will work fine.</p>
<p>The inspiration for this post is based on Sara Monteiro's <a href="https://medium.com/the-esp-journal/getting-started-with-esp32-and-nuttx-fd3e1a3d182c">nuttx+esp32 getting started article</a> article, but adapted for MacOS and extended to support WIFI networking configuration.</p>
<h1 id="environmentsetup">Environment Setup</h1>
<p>For the sake of simplicity, I will be using a environment variable to point of the folder used for this project. I am also using <a href="https://fishshell.com/">fish</a> as the default shell.</p>
<pre><code class="language-bash">export NUTTX_SPACE=(realpath ~/projects/iot/nuttxspace/)
</code></pre>
<h2 id="downloadnuttx">Download NuttX</h2>
<p>First step is to checkout the source NuttX code:</p>
<pre><code class="language-bash">cd $NUTTX_SPACE
git clone https://bitbucket.org/nuttx/tools.git
git clone https://github.com/apache/incubator-nuttx.git nuttx
git clone https://github.com/apache/incubator-nuttx-apps.git apps
</code></pre>
<p>Build <a href="https://www.kernel.org/doc/html/latest/kbuild/index.html">kconfig</a> configuration tool.</p>
<pre><code class="language-bash">cd $NUTTX_SPACE/tools/kconfig-frontends
./configure --enable-mconf
make
make install
</code></pre>
<h2 id="bootloader">Bootloader</h2>
<pre><code class="language-bash">mkdir {$NUTTX_SPACE}/esp-bins
curl -L &quot;https://github.com/espressif/esp-nuttx-bootloader/releases/download/latest/bootloader-esp32.bin&quot; -o $NUTTX_SPACE/esp-bins/bootloader-esp32.bin
curl -L &quot;https://github.com/espressif/esp-nuttx-bootloader/releases/download/latest/partition-table-esp32.bin&quot; -o $NUTTX_SPACE/esp-bins/partition-table-esp32.bin
</code></pre>
<p>Alternatively, building your own version of the boot-loader can be done quite easily, provided you have docker installed.</p>
<pre><code class="language-bash">git clone https://github.com/espressif/esp-nuttx-bootloader.git {$NUTTX_SPACE}/esp-bootloader
docker run --rm -v {$NUTTX_SPACE}/esp-bootloader:/work -w /work espressif/idf:release-v4.3 ./build.sh
</code></pre>
<p>If all works fine, you should be able to see the built files in the out_ folder:</p>
<pre><code class="language-bash">ls -la {$NUTTX_SPACE}/out/
drwxr-xr-x   8 ron  staff    256 28 Feb 10:19 ./
drwxr-xr-x  14 ron  staff    448 28 Feb 10:18 ../
-rw-r--r--   1 ron  staff  23824 28 Feb 10:17 bootloader-esp32.bin
-rw-r--r--   1 ron  staff  18528 28 Feb 10:19 bootloader-esp32c3.bin
-rw-r--r--   1 ron  staff   3072 28 Feb 10:17 partition-table-esp32.bin
-rw-r--r--   1 ron  staff   3072 28 Feb 10:19 partition-table-esp32c3.bin
-rw-r--r--   1 ron  staff  36748 28 Feb 10:17 sdkconfig-esp32
-rw-r--r--   1 ron  staff  33935 28 Feb 10:19 sdkconfig-esp32c3
</code></pre>
<h2 id="espidf">ESP-IDF</h2>
<p>Assuming that you have already installed the ESP-IDF, you should be able to</p>
<pre><code class="language-bash">export IDF_PATH=(realpath ~/projects/iot/esp-idf/)
. $IDF_PATH/export.fish
</code></pre>
<hr>
<h1 id="buildingtheapp">Building the app</h1>
<h2 id="appconfiguration">App Configuration</h2>
<p>Generate the kernel/app configuration for the ESP32 platform.</p>
<pre><code class="language-bash">cd {$NUTTX_SPACE}/nuttx
./tools/configure.sh esp32-devkitc:nsh
</code></pre>
<p>This will create the file <code>.config</code> which contains all the necessary flags for ESP32. For example, this is the generated config for the devkit-C:</p>
<pre><code class="language-bash">CONFIG_ARCH_XTENSA=y
CONFIG_ARCH=&quot;xtensa&quot;
CONFIG_ARCH_CHIP=&quot;esp32&quot;
CONFIG_ARCH_BOARD=&quot;esp32-devkitc&quot;
CONFIG_ARCH_CHIP_ESP32=y
CONFIG_ARCH_FAMILY_LX6=y
CONFIG_XTENSA_CP_INITSET=0x0001
CONFIG_XTENSA_DUMPBT_ON_ASSERT=y
CONFIG_XTENSA_BTDEPTH=50

#
# ESP32 Configuration Options
#
CONFIG_ARCH_CHIP_ESP32WROVER=y
CONFIG_ESP32_DUAL_CPU=y
CONFIG_ESP32_FLASH_4M=y
CONFIG_ESP32_PSRAM_8M=y
CONFIG_ESP32_ESP32DXWDXX=y
CONFIG_ESP32_DEFAULT_CPU_FREQ_240=y
CONFIG_ESP32_DEFAULT_CPU_FREQ_MHZ=240
</code></pre>
<h2 id="build">Build</h2>
<pre><code class="language-bash">make
</code></pre>
<p>If all goes fine, you should be able to see this at the end of the compilation</p>
<pre><code class="language-bash">AR (create): libboard.a   esp32_boot.o esp32_bringup.o esp32_appinit.o
LD: nuttx
CP: nuttx.hex
CP: nuttx.bin
MKIMAGE: ESP32 binary
esptool.py --chip esp32 elf2image --flash_mode dio --flash_size &quot;4MB&quot; -o nuttx.bin nuttx
esptool.py v3.1-dev
Generated: nuttx.bin (ESP32 compatible)
</code></pre>
<h2 id="flash">Flash</h2>
<p>I am using a Wemos LOLIN32 1.0</p>
<pre><code class="language-bash">make download ESPTOOL_PORT=/dev/cu.SLAB_USBtoUART ESPTOOL_BAUD=115200 ESPTOOL_BINDIR={$NUTTX_SPACE}/esp-bins
</code></pre>
<pre><code class="language-bash">Connecting........_
Chip is ESP32-D0WDQ6 (revision 1)
Features: WiFi, BT, Dual Core, 240MHz, VRef calibration in efuse, Coding Scheme None
Crystal is 40MHz
MAC: b4:e6:2d:95:b1:05
Uploading stub...
Running stub...
Stub running...
Configuring flash size...
Compressed 23824 bytes to 14851...
Wrote 23824 bytes (14851 compressed) at 0x00001000 in 1.6 seconds (effective 118.2 kbit/s)...
Hash of data verified.
Compressed 3072 bytes to 69...
Wrote 3072 bytes (69 compressed) at 0x00008000 in 0.1 seconds (effective 454.4 kbit/s)...
Hash of data verified.
Compressed 124896 bytes to 52034...
Wrote 124896 bytes (52034 compressed) at 0x00010000 in 4.8 seconds (effective 207.1 kbit/s)...
Hash of data verified.

Leaving...
Hard resetting via RTS pin...
</code></pre>
<h2 id="connectingtonuttxshell">Connecting to NuttX shell</h2>
<pre><code class="language-bash">screen /dev/cu.SLAB_USBtoUART 115200
</code></pre>
<p>Just write <code>help</code> and you'll should see this:</p>
<pre><code class="language-bash">help usage:  help [-v] [&lt;cmd&gt;]

  .         cd        echo      hexdump   mh        rm        time      xd
  [         cp        exec      kill      mount     rmdir     true
  ?         cmp       exit      ls        mv        set       uname
  basename  dirname   false     mb        mw        sleep     umount
  break     dd        free      mkdir     ps        source    unset
  cat       df        help      mkrd      pwd       test      usleep

Builtin Apps:
  nsh  sh
nsh&gt;
</code></pre>
<p>Voila, that's it for the basic config. Next step is to enable the WIFI connectivity.</p>
<hr>
<h1 id="enablingwifinetworking">Enabling WIFI Networking</h1>
<p>NuttX uses the <a href="https://en.wikipedia.org/wiki/UIP_(micro_IP)">uIP</a> networking stack, unlike ESP-IDF which uses <a href="https://en.wikipedia.org/wiki/LwIP">LWiP</a>.</p>
<h2 id="basicnetworkconfig">Basic Network config</h2>
<p>Enabling WIFI can be done by configuring the nuttx app:</p>
<pre><code class="language-bash">make -C {$NUTTX_SPACE}/nuttx distclean
{$NUTTX_SPACE}/nuttx/tools/configure.sh esp32-devkitc:nsh
make -C {$NUTTX_SPACE}/nuttx menuconfig
</code></pre>
<p>Go to <code>Networking Support</code> and enable it, as well as</p>
<pre><code>    * Networking Support: yes
        * Link layer support
            * Late driver initialization: yes
    * System Type
        * ESP32 Peripheral Selection
            * Wireless: yes
    * RTOS Features
        * Work queue support
            * Generic work notifier
            * High priority (kernel) worker thread
        * Pthread Options
            *  Enable mutex types
    * Device Drivers
        * Wireless Device Support
            * IEEE 802.11 Device Support
</code></pre>
<p>To make it easier to debug, I also enabled the traces from:</p>
<pre><code>    * Build Setup
        * Debug Options
            * Enable Error Output
            * Enable Debug Features
            * Network Debug Features
            * Wireless Debug Features
</code></pre>
<p>After flashing the, the following logs can be seen:</p>
<pre><code class="language-bash">I (266) boot: Disabling RNG early entropy source...
esp32_rng_initialize: Initializing RNG
esp32_net_initialize: B4:E6:2D:95:B1:05
netdev_register: Registered MAC: b4:e6:2d:95:b1:05 as dev: wlan0
I (16) wifi:wifi driver task: 5, prio:253, stack:3584, core=0
I (18) wifi:wifi firmware version: 3cc2254
I (18) wifi:wifi certification version: v7.0
I (18) wifi:config NVS flash: disabled
I (22) wifi:config nano formating: disabled
I (26) wifi:Init data frame dynamic rx buffer num: 32
I (31) wifi:Init management frame dynamic rx buffer num: 32
I (36) wifi:Init management short buffer num: 32
I (40) wifi:Init dynamic tx buffer num: 32
I (44) wifi:Init static rx buffer size: 1600
I (48) wifi:Init static rx buffer num: 10
I (52) wifi:Init dynamic rx buffer num: 32
netdev_ifr_ioctl: cmd: 1794
tcp_callback: flags: 0040
netdev_ifr_ioctl: cmd: 1796
tcp_callback: flags: 0040
netdev_ifr_ioctl: cmd: 1800
tcp_callback: flags: 0040
netdev_ifr_ioctl: cmd: 1818
wlan_ifup: Bringing up: 10.0.0.2
tcp_callback: flags: 0040

NuttShell (NSH) NuttX-10.0.1
nsh&gt; ifconfig
wlan0   Link encap:Ethernet HWaddr b4:e6:2d:95:b1:05 at UP
        inet addr:10.0.0.2 DRaddr:10.0.0.1 Mask:255.255.255.0
</code></pre>
<h2 id="accessingwapi">Accessing WAPI</h2>
<p><a href="https://github.com/vy/wapi">WAPI</a> is a lightweight wrapper for iwconfig, wlanconfig, ifconfig, and route commands, and that's the command we need to use to scan the av available access points.</p>
<p>The first attempt resulted in a failure due to the missing <code>ioctl</code> support.</p>
<pre><code class="language-bash">nsh&gt; wapi scan wlan0
netdev_ifr_ioctl: cmd: 35608
ioctl(SIOCSIWSCAN): 25
ERROR: Process command (scan) failed.
</code></pre>
<p>After enabling the Wireless IOCTL</p>
<pre><code>    * Networking support
        * Network Device Operations
            * Enable Wireless ioctl()
</code></pre>
<p>Unfortunately, at the time of writing (Feb. 2021), one can notice that from the <a href="https://github.com/apache/incubator-nuttx/blob/59a5d038426de9609d386d2ea49f2ed9700bf69c/arch/xtensa/src/esp32/esp32_wlan.c#L1306">esp32_wlan.c</a>, the scan is not currently supported. So, the only possiblity is to connect manually:</p>
<pre><code class="language-bash">wapi psk wlan0 access_point_password 0
wapi essid wlan0 access_point_ssid 0
</code></pre>
<p>From my access point running OpenWRT, I could notice that the ESP32 was connected, and was allocated an IP address. However, from the NSH CLI, the IP address remmained unchanged:</p>
<pre><code class="language-bash">nsh&gt; ifconfig
wlan0   Link encap:Ethernet HWaddr b4:e6:2d:95:b1:05 at UP
        inet addr:10.0.0.2 DRaddr:10.0.0.1 Mask:255.255.255.0
</code></pre>
<p>Also, pinging the device from my Mac laptop to the IP address mentionned on the OpenWRT router, would result in frames beeing dropped (or unanswered) since uIP did not get the correct IP config.</p>
<pre><code class="language-bash">nsh&gt; wlan_rxpoll: ARP frame
arp_arpin: ARP request for IP da01a8c0
</code></pre>
<h2 id="enablingdhcpsupport">Enabling DHCP support</h2>
<p>To enable the DHCP client:</p>
<pre><code>    * Networking Support
        * UDP Networking
            * UDP Networking
            * UDP broadcast Rx support
        * Socket Support
            * Socket options
    * Library Routines
        * NETDB Support
            * DNS Name resolution
    * Application
        * System Libraries and NSH Add-Ons
            * DHCP Address Renewal (NEW)
        * Wireless Libraries and NSH Add-Ons
            * IEEE 802.11 Configuration Library
                * IEEE 802.11 Command Line Tool
</code></pre>
<p>After that, and refering to <a href="https://esp32.com/viewtopic.php?f=2&amp;t=19481">this post on esp32.com</a>, it is possible to enable the DHCP for the network initlization using:</p>
<pre><code>    * Application
        * Network Utilities
            * DHCP client
            * Network initialization
                * IP Address Configuration
                    * Use DHCP to get IP address
                    * Router IPv4 address: 0xc0a80101
</code></pre>
<p>It is quite weird to have to configure the Router IPv4 address, and further more having to do it using hexadecimal (<em>0xc0a80101</em> in my case), but well, that's the only way to get things working.</p>
<p>Unfortunately, that was not enough. After trying to setup the SSID and passkey from WAPI, it always ended-up with errors:</p>
<pre><code class="language-bash">NuttShell (NSH) NuttX-10.0.1
nsh&gt; wapi psk wlan0 my-ap-password 1
netdev_ifr_ioctl: cmd: 35636
nsh&gt; wapi essid wlan0 my-ap-ssid 1
netdev_ifr_ioctl: cmd: 35610
phy_version: 4500, 0cd6843, Sep 17 2020, 15:37:07, 0, 2
wifi_set_intr: cpu_no=0, intr_source=0, intr_num=0, intr_prio=1I (6220) wifi:mode : sta (b4:e6:2d:95:b1:05)
I (6221) wifi:enable tsf
esp_event_post: Event: base=WIFI_EVENT id=2 data=0 data_size=0 ticks=4294967295
I (6231) wifi:Set ps type: 0

I (6597) wifi:new:&lt;3,0&gt;, old:&lt;1,0&gt;, ap:&lt;255,255&gt;, sta:&lt;3,0&gt;, prof:1
I (7249) wifi:state: init -&gt; auth (b0)
I (7256) wifi:state: auth -&gt; assoc (0)
I (7262) wifi:state: assoc -&gt; run (10)
I (7276) wifi:connected with my-ap-ssid, aid = 2, channel 3, BW20, bssid = ee:41:18:0c:53:dd
I (7277) wifi:security: WPA2-PSK, phy: bgn, rssi: -29
I (7278) wifi:pm start, type: 0

esp_event_post: Event: base=WIFI_EVENT id=4 data=0x3ffe5bb0 data_size=44 ticks=4294967295
nsh&gt; I (7301) wifi:AP's beacon interval = 102400 us, DTIM period = 2
</code></pre>
<p>The only way to overcome this issue was to set the SSID and passkey directly in the menu config, under <code>Application -&gt; Network Utilities -&gt; Network initialization -&gt; WAPI Configuration (SSID / Passprhase)</code>. And fortunately, after that, it was possible to the the correct IP address:</p>
<pre><code class="language-bash">nsh&gt; ifconfig
wlan0   Link encap:Ethernet HWaddr b4:e6:2d:95:b1:05 at UP
        inet addr:192.168.1.218 DRaddr:192.168.1.1 Mask:255.255.255.0
</code></pre>
<h2 id="pingingtheinternet">Pinging the Internet</h2>
<p>Unfortunately, even after having the correct IP condiguration, PING would still not work, failing with <code>socket address family unsupported</code>.</p>
<pre><code class="language-bash">nsh&gt; ping baidu.com
dns_recv_response: ID 36690
dns_recv_response: Query 128
dns_recv_response: Error 0
dns_recv_response: Num questions 1, answers 2, authrr 0, extrarr 0
dns_recv_response: Question: type=0001, class=0001
dns_parse_name: Compressed answer
dns_recv_response: Answer: type=0001, class=0001, ttl=0000bf, length=0004
dns_recv_response: IPv4 address: 220.181.38.148
dns_parse_name: Compressed answer
dns_recv_response: Answer: type=0001, class=0001, ttl=0000bf, length=0004
dns_recv_response: IPv4 address: 39.156.69.79
psock_socket: ERROR: socket address family unsupported: 2
socket: ERROR: psock_socket() failed: -106
</code></pre>
<p>The missing link was to enable <code>IPPROTO_ICMP socket support</code>  under <code>Networking Support -&gt; ICMP Networking Support</code></p>
<pre><code class="language-bash">NuttShell (NSH) NuttX-10.0.1
nsh&gt;
nsh&gt; ping baidu.com
sendto_eventhandler: Send ICMP request
sendto_request: Outgoing ICMP packet length: 84 (84)
icmp_poll_eventhandler: flags: 0002
icmp_datahandler: Buffered 81 bytes
icmp_readahead: Received 64 bytes (of 81)
56 bytes from 39.156.69.79: icmp_seq=0 time=40 ms
</code></pre>
<p>Voila, finally, it's working :-)</p>
<p>Also, just in case, I could see some random failures during the ping: <code>up_assert: Assertion failed at file:mm_heap/mm_free.c line: 170 task: ping</code></p>
<hr>
<h1 id="conclusions">Conclusions</h1>
<p>NuttX is definitely a promising solution - especially considering the eco-system that is forming arround it. However, at this stage the ESP32 support is quite limited. But the good news is that Espressif seems to be proactively adding support for their chip, so let's hope that within a few weeks WIFI - and other drivers - will be completely supported.</p>
<p><a href="https://eadalabs.com/nuttx-with-esp32-and-ethernet/">Next step</a> is to try to enable Ethernet on the <a href="http://www.wireless-tag.com/wp-content/uploads/2020/08/WT32-ETH01%E8%A7%84%E6%A0%BC%E4%B9%A6V1.2EN%EF%BC%88%E8%8B%B1%E6%96%87%EF%BC%89.pdf">W32-ETH01</a></p>
<p><img src="https://eadalabs.com/content/images/2021/03/w32-eth01.png" alt="Getting started with NuttX and Esp32 on MacOS"></p>
<!--kg-card-end: markdown-->]]></content:encoded></item><item><title><![CDATA[Benchmarking Mysql, Postgres, MongoDB performance in NoSQL mode  using Golang]]></title><description><![CDATA[<!--kg-card-begin: markdown--><p>When it comes to storing significantly large volumes of time-series data, with several thouthands rows added every second, one of the first questions which comes to one's mind is wether traditional databases such as MySql, Postgres or Sqlite can handle those data volume? Or whether it is needed to use</p>]]></description><link>https://eadalabs.com/benchmarking-mysql-postgres-and-mongodb-performance-in-nosql-mode/</link><guid isPermaLink="false">603dc687098a6941c452d837</guid><category><![CDATA[mysql]]></category><category><![CDATA[postgress]]></category><category><![CDATA[nosql]]></category><dc:creator><![CDATA[Ron J]]></dc:creator><pubDate>Thu, 08 Mar 2018 09:00:00 GMT</pubDate><media:content url="https://images.unsplash.com/photo-1465447142348-e9952c393450?ixlib=rb-0.3.5&amp;q=80&amp;fm=jpg&amp;crop=entropy&amp;cs=tinysrgb&amp;w=1080&amp;fit=max&amp;ixid=eyJhcHBfaWQiOjExNzczfQ&amp;s=30c2837564064f98a3a3b79633cfcaed" medium="image"/><content:encoded><![CDATA[<!--kg-card-begin: markdown--><img src="https://images.unsplash.com/photo-1465447142348-e9952c393450?ixlib=rb-0.3.5&q=80&fm=jpg&crop=entropy&cs=tinysrgb&w=1080&fit=max&ixid=eyJhcHBfaWQiOjExNzczfQ&s=30c2837564064f98a3a3b79633cfcaed" alt="Benchmarking Mysql, Postgres, MongoDB performance in NoSQL mode  using Golang"><p>When it comes to storing significantly large volumes of time-series data, with several thouthands rows added every second, one of the first questions which comes to one's mind is wether traditional databases such as MySql, Postgres or Sqlite can handle those data volume? Or whether it is needed to use dedicated platforms and architectures such as Hadoop, MongoDB, Click house or influxDB.</p>
<p>The code associated to this post is availaible on github: <a href="https://github.com/atsdb/db-perf-test">https://github.com/atsdb/db-perf-test</a></p>
<h1 id="introduction">Introduction</h1>
<h2 id="benchmarksrelevantmeasurements">Benchmarks-relevant measurements</h2>
<p>There are several aspects to answering this question. First, in terms of performance of inserting new data, but also in terms of reading back from the database to find specific data. If the assumption is that the incoming volume of data is in the order of magnitude of several kilo inserts per second, then the natural question is wether traditional databases like MySQL or Postgres can do it? And the next question is, if they can handle 1K rps, then how what is the maximum speed they can ingest data? 10K, 50K, 100K?</p>
<p>The second aspect is related to the size of the data stored by the database engine. For an application that would collect just 1K rows per second, where one row is defined using 20 bytes (5 times 4 bytes integers), how long would it take to fill a 200GB disk? Assuming that the database stores the data without any extra overhead, then that would take <code>(1000*20)*3600*24 / 180.(1024^3)</code> = 110 days. But in practice, the overhead from the database storage engine can be quite fat, or even very fat... so that the disk would just could fill-up in 1 month or even less.</p>
<p>The third aspect, less important but still critical for assessing the various database, in the the performance of the database server in terms of CPU usage and memory usage. For instance, for a database that would be able to ingest 10K row/sec, how much CPU would the database use? 10%, 50% or 100%? In the following part of this article, we will be looking at the CPU usage, CPU load and system memory usage.</p>
<h2 id="performingthetestusingnosqlschema">Performing the test using NoSQL schema.</h2>
<p>There can be a lot of buzz around the NoSQL meaning, so what is refers to in this article, in the usage of database tables without any kind of jointure, and where one table is used to store has much rows as possible (up to 100 millions or more).</p>
<p>There are 3 kinds of table schema used for the test:</p>
<ul>
<li><strong>slim table</strong>: contains 2 columns of type integer (4 bytes), without any index.</li>
<li><strong>light table with key and index</strong>: This table contains two more table compared to the first table; one used as auto-inc index, and the other one as non-primary index. This is useful to test the index performance for each database engine.</li>
<li><strong>large table with key</strong>: It contains 10 rows, the first is the primary-index, the second one is 200 characters (variable), and the last 8 integers. This last table is useful to asses the performance of fat table.</li>
</ul>
<p>Example for the first slim table without index:</p>
<pre><code>CREATE TABLE `test-slim-table` (
  timestamp int(11) DEFAULT NULL,
  value int(11) DEFAULT NULL,
) ENGINE=InnoDB DEFAULT CHARSET=utf8;
</code></pre>
<p>Example for the second light table with index:</p>
<pre><code>CREATE TABLE `test-light-table-index` (
  index int(10) unsigned NOT NULL AUTO_INCREMENT,
  timestamp int(11) DEFAULT NULL,
  val1 smallint(6) DEFAULT NULL,
  val2 smallint(6) DEFAULT NULL,
  PRIMARY KEY (idx),
  KEY col1 (col1)
) ENGINE=InnoDB DEFAULT CHARSET=utf8;
</code></pre>
<h1 id="testingstrategyandscenarios">Testing strategy and scenarios</h1>
<p>In order to maximize the speed at which database can ingest rows, three different strategies are defined:</p>
<h2 id="naveinserts">Naïve inserts</h2>
<p>The naive insert works by first doing <code>prepare</code> on an insert query, and then calling <code>exec</code> on the prepared query as fast as possible, using random data for each column.  The insert query looks like:</p>
<pre><code>insert into test-light-table-index (index,timestamp,val1,val2) (0,4384932083409,87,39804)
</code></pre>
<p>Using go, the naive insert is implemented using the those functions:</p>
<pre><code class="language-go">func (db *DB) Prepare(query string) (*Stmt, error)
func (db *DB) Exec(query string, args ...interface{}) (Result, error)
</code></pre>
<h2 id="transactionbasedinserts">Transaction based inserts</h2>
<p>The drawback of the first naive method is that the database have to keep the ACID promise for each row inserted, thus <em>typically</em> having to flush the data to disk and recompute the index for each <code>exec</code>. A better way is to use a transaction, and insert the rows into the transaction which then gets a <code>commit</code> every second. The advantage is that the ACID promise only needs to be kept at commit time, thus reducing the disk flushing and index computation overhead.</p>
<p>Using go, the transaction can be handled easily using the two functions:</p>
<pre><code class="language-go">func (db *DB) Begin() (*Tx, error)
func (tx *Tx) Commit() error
</code></pre>
<p>Note that the <code>prepare</code> statement needs to be <em>re-prepared</em> after each new transaction is created.</p>
<h2 id="shardsinserts">Shards inserts</h2>
<p>The last strategy consists is <em>splitting</em> the incoming data into several schema-identical tables.  The way the data is split is not the critical for this article, assuming that the art of find well-balanced hash algorithm is business specific. The most important is wether the usage of table shards can improve the performance, and if yes, what is the optimal number of tables to be created.</p>
<p>For this scenarios, the <em>transaction based insert</em> is used to each table, and the test consist is running several transaction based insert in parallel. Only the overall insert performance is being looked at, i.e only the sum of all tables inserts/second will be investigated.</p>
<h1 id="preliminaryresult">Preliminary result</h1>
<p>Test are running for 10 minutes, on a Azure A8 instance (28GB RAM). All the numbers below are express in rows/seconds, and represent the overall rps (i.e. total number of inserted rows divided by test duration).</p>
<p>Database configuration is off-the-shell, except for the following MySQL / InnoDB tunings:</p>
<ul>
<li><code>innodb_buffer_pool_size</code>: 11GB</li>
<li><code>innodb_file_per_table</code>: 1</li>
</ul>
<h2 id="insertspersecondperformance">Inserts per Second performance</h2>
<p>Note that MySQL/MyISAM <em>transaction insert</em> result is the same as the <em>naive insert</em> result since MyISAM does not support transactions.</p>
<table style="border:3px solid black;width:100%">
<thead><tr><th>Database Engine</th><th>naive 
insert</th><th>transaction insert</th><th>shard insert</th></tr></thead>
<tr><td colspan="4" style="text-align:center;background-color:#eef;">Table Schema: Slim
</td></tr><tr><td>MySQL myISAM</td><td>18,104</td><td>18,104</td><td>58,897
</td></tr><tr><td>MySQL InnoDB</td><td>731</td><td>9,107</td><td>53,399
</td></tr><tr><td>Postgres    </td><td>2,120</td><td>10,147</td><td>63,481
</td></tr><tr><td colspan="4" style="text-align:center;background-color:#eef;">Table Schema: Light with index
</td></tr><tr><td>MySQL myISAM    </td><td>12,317      </td><td>12,317</td><td>26,026
</td></tr><tr><td>MySQL InnoDB    </td><td>660</td><td>6,815   </td><td>23,415
</td></tr><tr><td>Postgres    </td><td>1,607   </td><td>3,845   </td><td>30,022
</td></tr><tr><td colspan="4" style="text-align:center;background-color:#eef;">Table Schema: Large
</td></tr><tr><td>MySQL myISAM    </td><td>14,377      </td><td>14,377</td><td>24,786
</td></tr><tr><td>MySQL InnoDB    </td><td>674</td><td>6,060   </td><td>17,350
</td></tr><tr><td>Postgres    </td><td>1,745</td><td>10,147</td><td>24,019
</td></tr></table> 
<p>Data for MongoDB, as well as Click-House, Sqlite and Influx DB will be added in a later post</p>
<h2 id="diskusageperformance">Disk usage performance</h2>
<p>Numbers below are expressed in average size in bytes per row (including data and index).</p>
<table style="border:3px solid black;width:100%">
<thead><tr><th>Database Engine</th><th>Slim</th><th>Light w. Index</th><th>Large</th></tr></thead>
<tr><td>MyISAM</td><td>9</td><td>37</td><td>244
</td></tr><tr><td>InnoDB</td><td>37</td><td>62</td><td>328
</td></tr><tr><td>Postgress</td><td>36</td><td>44</td><td>318
</td></tr></table>
<p>MyISAM is outstandanding compared to the other. It stores the data in an optimal way, and yet performs better in terms of write/seconds.</p>
<!--kg-card-end: markdown-->]]></content:encoded></item><item><title><![CDATA[Gridded Population of the World]]></title><description><![CDATA[<!--kg-card-begin: markdown--><p>The <a href="https://sedac.ciesin.columbia.edu/data/collection/gpw-v4">Gridded Population of the World</a>, also known as GPW, can be used as a way to estimate the amount of anthropogenic pollution.</p>
<p>The following animation is applied to Northern India, and based on the GPW version 4 combined with the real-time wind forecast from data the <a href="https://www.ncdc.noaa.gov/data-access/model-data/model-datasets/global-forcast-system-gfs">Global Forecast System</a></p>]]></description><link>https://eadalabs.com/gridded-population-of-the-world/</link><guid isPermaLink="false">603dc687098a6941c452d836</guid><dc:creator><![CDATA[Ron J]]></dc:creator><pubDate>Sun, 13 Mar 2016 13:46:14 GMT</pubDate><media:content url="https://images.unsplash.com/photo-1479837524808-8bfbd8b0ce8d?ixlib=rb-0.3.5&amp;q=80&amp;fm=jpg&amp;crop=entropy&amp;cs=tinysrgb&amp;w=1080&amp;fit=max&amp;s=bc8c66e4529e6cdeb157c7e4644deb86" medium="image"/><content:encoded><![CDATA[<!--kg-card-begin: markdown--><img src="https://images.unsplash.com/photo-1479837524808-8bfbd8b0ce8d?ixlib=rb-0.3.5&q=80&fm=jpg&crop=entropy&cs=tinysrgb&w=1080&fit=max&s=bc8c66e4529e6cdeb157c7e4644deb86" alt="Gridded Population of the World"><p>The <a href="https://sedac.ciesin.columbia.edu/data/collection/gpw-v4">Gridded Population of the World</a>, also known as GPW, can be used as a way to estimate the amount of anthropogenic pollution.</p>
<p>The following animation is applied to Northern India, and based on the GPW version 4 combined with the real-time wind forecast from data the <a href="https://www.ncdc.noaa.gov/data-access/model-data/model-datasets/global-forcast-system-gfs">Global Forecast System</a>.</p>
<script>
function resizeFrame() {}
document.write('<iframe id="aqi-forecast" src="https://aqicn.org/aqicn/view/faq/snippets/using-gpw-in-northern-india/?_='+(new Date().getTime())+'" scrolling="no" style="width:100%;height:800px;border:none;" onLoad="resizeFrame();">></iframe>');
</script>
<p>The full explanation available is available from <a href="https://aqicn.org/faq/2016-02-28/air-quality-forecasting-in-northern-india/">aqicn.org/faq/2016-02-28/air-quality-forecasting-in-northern-india/</a></p>
<!--kg-card-end: markdown-->]]></content:encoded></item><item><title><![CDATA[A visual study of air pollution Forecasting]]></title><description><![CDATA[<!--kg-card-begin: markdown--><p>We have been writing quite a few times about the influence of wind on air pollution, and how strong winds (or, to be more precise, strong ventilation) can help to clean the air in a very short time. But we never had the opportunity to create on a dynamic visualization</p>]]></description><link>https://eadalabs.com/a-visual-study-of-air-pollution-forecasting/</link><guid isPermaLink="false">603dc687098a6941c452d835</guid><dc:creator><![CDATA[Ron J]]></dc:creator><pubDate>Fri, 20 Nov 2015 11:43:02 GMT</pubDate><media:content url="https://images.unsplash.com/photo-1495401220594-550313c3046b?ixlib=rb-0.3.5&amp;q=80&amp;fm=jpg&amp;crop=entropy&amp;cs=tinysrgb&amp;w=1080&amp;fit=max&amp;s=0fa76484e942f3c6dee95e86f80a16cd" medium="image"/><content:encoded><![CDATA[<!--kg-card-begin: markdown--><img src="https://images.unsplash.com/photo-1495401220594-550313c3046b?ixlib=rb-0.3.5&q=80&fm=jpg&crop=entropy&cs=tinysrgb&w=1080&fit=max&s=0fa76484e942f3c6dee95e86f80a16cd" alt="A visual study of air pollution Forecasting"><p>We have been writing quite a few times about the influence of wind on air pollution, and how strong winds (or, to be more precise, strong ventilation) can help to clean the air in a very short time. But we never had the opportunity to create on a dynamic visualization of this phenomenon, so this is what this article is about.</p>
<script>
document.write('<iframe id="aqi-forecast" src="//aqicn.org/aqicn/view/faq/snippets/air-quality-forecasting/?_='+(new Date().getTime())+'" scrolling="no" style="width:100%;height:800px;border:none;" ></iframe>');
</script>
<p>The full explanation available from this link: <a href="http://aqicn.org/faq/2015-11-05/a-visual-study-of-wind-impact-on-pm25-concentration/">http://aqicn.org/faq/2015-11-05/a-visual-study-of-wind-impact-on-pm25-concentration/</a></p>
<!--kg-card-end: markdown-->]]></content:encoded></item><item><title><![CDATA[Real-time volcano ash forecast]]></title><description><![CDATA[<!--kg-card-begin: markdown--><p>We have been recently working with the Quito Ambiente team for forecasting in real-time the volcano ash from Quito's 3 closest Volcanos.</p>
<p>The full project explanation is available <a href="http://aqicn.org/faq/2015-06-08/wind-and-air-pollution-forecast-in-quito/">from this link</a>. The current result is:</p>
<iframe id="quito-ash-forecast" src="https://aqicn.org/aqicn/view/faq/snippets/quito-wind-and-eruption-forecast/" scrolling="no" width="100%" height="800" style="border:1px solid black;"></iframe>
<!--kg-card-end: markdown-->]]></description><link>https://eadalabs.com/real-time-volcano-ash-forecast/</link><guid isPermaLink="false">603dc687098a6941c452d834</guid><dc:creator><![CDATA[Ron J]]></dc:creator><pubDate>Thu, 18 Jun 2015 06:47:00 GMT</pubDate><media:content url="https://images.unsplash.com/photo-1506467493604-25d7861a6703?ixlib=rb-0.3.5&amp;q=80&amp;fm=jpg&amp;crop=entropy&amp;cs=tinysrgb&amp;w=1080&amp;fit=max&amp;s=0b993dce173955673b94a4d7512d645a" medium="image"/><content:encoded><![CDATA[<!--kg-card-begin: markdown--><img src="https://images.unsplash.com/photo-1506467493604-25d7861a6703?ixlib=rb-0.3.5&q=80&fm=jpg&crop=entropy&cs=tinysrgb&w=1080&fit=max&s=0b993dce173955673b94a4d7512d645a" alt="Real-time volcano ash forecast"><p>We have been recently working with the Quito Ambiente team for forecasting in real-time the volcano ash from Quito's 3 closest Volcanos.</p>
<p>The full project explanation is available <a href="http://aqicn.org/faq/2015-06-08/wind-and-air-pollution-forecast-in-quito/">from this link</a>. The current result is:</p>
<iframe id="quito-ash-forecast" src="https://aqicn.org/aqicn/view/faq/snippets/quito-wind-and-eruption-forecast/" scrolling="no" width="100%" height="800" style="border:1px solid black;"></iframe>
<!--kg-card-end: markdown-->]]></content:encoded></item></channel></rss>