Jekyll2023-09-01T03:35:46+00:00https://blog.kylemanna.com/feed.xmlBootloader BlogKyle Manna's personal blog covering embedded systems, Linux, entrepreneurship and other musings.Kyle MannaFriendlyElec NanoPi R5S as PTP Grandmaster Clock with GNSS/GPS Discipline2022-09-25T00:00:00+00:002022-09-25T00:00:00+00:00https://blog.kylemanna.com/hardware/nanopi-r5s-as-ptp-grandmaster-clock-with-gnss-gps-discipline<h2 id="the-dream">The Dream</h2>
<p>I’ve wanted to build a low-cost open source Linux system that could function as a PTP grandmaster for a while now. This should be pretty doable with IEEE 1588 readily available (if you know where to look) and low-cost GPS receivers with reasonable quality 1PPS outputs.</p>
<h2 id="the-search-for-the-perfect-linux-ptp--gnss-platform">The Search for the Perfect Linux PTP + GNSS Platform</h2>
<p>Hard requirements:</p>
<ul>
<li>At least one IEEE 1588 supported Ethernet port with some semblance of PTP clock support in the Linux driver.</li>
<li>GPIOs that can trigger IRQs on the SoC to accurately time stamp the PPS signal.</li>
<li>Accessible SoC UART (no USB or RS-232).</li>
<li>Functional enclosure.</li>
<li>Easy to purchase.</li>
<li>Access to SoC datasheets / reference manuals to fix drivers.</li>
</ul>
<p>Nice to haves:</p>
<ul>
<li>Board Schematics.</li>
<li>Accessible source code.</li>
<li>GPIO also routed to a hardware timer capture port for more accuracy.</li>
<li>On-board eMMC. I hate dealing with MicroSD cards outside of development.</li>
</ul>
<h2 id="runners-up">Runners Up</h2>
<ul>
<li><a href="https://www.tq-group.com/en/products/tq-embedded/qoriq-layerscape/lboxls1028a/">LBoxLS1028A</a> - This seems like a superior PTP focused product due to the 4-port TSN (Time Sensitive Networking) 1 Gbps switch, but this is hard to buy.</li>
<li><a href="https://www.changwang.com/product/406.html">ChangWang CW-6000 routers</a> - There are many versions of these routers an they all come with i226 Ethernet controllers now that should work great for PTP and the CPU has tons of power relative to the other runners up. The problem here is that it’s difficult to get the 1PPS signal into the processor. There is a <code class="language-plaintext highlighter-rouge">JCOM1</code> RS-232 port (ideally I want 3.3V TTL for GNSS module interfacing), but <a href="https://forums.servethehome.com/index.php?threads/topton-jasper-lake-quad-i225v-mini-pc-report.36699/page-51#post-349259">some online discussions suggest the port doesn’t work</a> (or the person doesn’t know what they’re doing). I’d like to try this if I could convince myself that there’s a low latency and low jitter path for the 1PPS signal.</li>
<li><a href="https://www.seeedstudio.com/Odyssey-Blue-J4125-128GB-p-4921.html">OYDSSEY Blue J4125</a> - Harder to buy, at least in stock, expensive, unclear 1PPS integration, but seems plausible. Uses i211 1 Gbps Ethernet controllers which work well for PTP. Unclear how the GPIOs work with respect to the Intel processor.</li>
<li><a href="https://www.st.com/en/evaluation-tools/stm32mp157d-dk1.html">STM32MP157D-DK1</a> - Out of stock, missing case, out of stock.</li>
<li><a href="https://wiki.seeedstudio.com/ODYSSEY-STM32MP157C/">ODYSSEY – STM32MP157C</a> - Out of stock and missing enclosure. But has low cost CPU with PTP support.</li>
<li><a href="https://wiki.banana-pi.org/Banana_Pi_BPI-W3">Banana Pi BPI-W3</a> - Interesting RK3588 platform, but would rather have the NanoPi R5S form factor and lower power CPU.</li>
<li>Rockchip RK3228 and RK3228 platforms - Don’t seem to support IEEE 1588 on the integrated Ethernet GMAC. Seems the RK3566/RK3568 is unique.</li>
</ul>
<h2 id="winner-friendlyelec-nanopi-r5s">Winner: FriendlyElec NanoPi R5S</h2>
<p>One of the most compelling features ot he NanoPi R5S is that it’s easy to buy from many vendors (I ordered from Amazon.com) and comes with an enclosure. Once you receive it’s ready to go as a router with no other messing around.</p>
<p><img src="https://i.imgur.com/G35TLkD.jpg" alt="NanoPI R5S internals" /></p>
<ul>
<li><a href="http://wiki.friendlyelec.com/wiki/index.php/NanoPi_R5S">FriendlyElec Wiki Page</a></li>
<li><a href="https://www.friendlyelec.com/index.php?route=product/product&product_id=287">FriendlyElec Product Page</a></li>
</ul>
<p>Let’s walk through some of the features.</p>
<h3 id="processor-rockchip-rk3568">Processor: Rockchip RK3568</h3>
<p><img src="https://i.imgur.com/XCwHKFo.png" alt="RK3568 block diagram" /></p>
<p>The RK3568 is focused around 4x<a href="https://developer.arm.com/Processors/Cortex-A55">ARM Cortex-A55</a> cores.</p>
<h4 id="rk3568-1-gbps-ethernet-controller">RK3568 1 Gbps Ethernet Controller</h4>
<p>For Ethernet, the RK3568 processor has two integrated Ethernet MACs (<code class="language-plaintext highlighter-rouge">GMAC</code>) that support IEEE 1588-2002 (version 1) and IEEE 1588-2008 (version 2) and works with PTP over UDP (layer 3) and over Ethernet (layer 2). The GMAC also supports Jumbo frames (up to 9018 bytes, 9022 for tagged VLAN packets). In addition to this seems to support frames up to 16kB which I’ve never seen or used and definitely don’t have hardware to use with it.</p>
<p>Initial testing shows that <code class="language-plaintext highlighter-rouge">linuxptp</code> and the 5.10 Linux kernel “just work” out of the box on FriendlyWrt and was able to synchronize with another device on my network. Driver and hardware support can be verified by reviewing the kernel boot logs:</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code># dmesg | grep -e PTP -e 1588
[ 26.384696] rk_gmac-dwmac fe2a0000.ethernet eth0: IEEE 1588-2008 Advanced Timestamp supported
[ 26.384960] rk_gmac-dwmac fe2a0000.ethernet eth0: registered PTP clock
</code></pre></div></div>
<p>At minimum the “LAN” port will function using the integrated Ethernet controller.</p>
<h3 id="ethernet-controller-2x-25-gbps-realtek-rtl8125bg">Ethernet Controller: 2x 2.5 Gbps Realtek RTL8125BG</h3>
<p>These <a href="https://www.realtek.com/en/products/communications-network-ics/item/rtl8125bg-s-cg">Realtek RTL8125</a> are pretty ubiquitous for low-cost Ethernet PCIe controllers and don’t seem to have the issues some of the early Intel i225 controllers have (fixed with rev 3 and newly release i226). I’ve been using one of these (rev 05) integrated in to my workstation motherboard for well over years without issue on Arch Linux with the mainline kernel driver.</p>
<p>I was surprised to learn that these Etherent controllers also support IEEE 1588-2002, IEEE 1588-2008, and IEEE 802.1AS. Howver, the mainline kernel driver doesn’t support it. It does appear that that the <a href="https://www.realtek.com/en/component/zoo/category/network-interface-controllers-10-100-1000m-gigabit-ethernet-pci-express-software">Realtek vendor out-of-tree driver</a> (version 9.009.02 at time of writing) does seem to support PTP. I haven’t verified this just yet.</p>
<h3 id="expansion-header-gpio--uart-access">Expansion Header: GPIO + UART access</h3>
<p>There’s a 12-pin FPC connector that exposes 3x integrated UARTs, GPIOS, and PWM capture pins. With minimal device tree updates this easily supports UART5 via <code class="language-plaintext highlighter-rouge">/dev/ttyS5</code> and <code class="language-plaintext highlighter-rouge">GPIO3_C5</code> / <code class="language-plaintext highlighter-rouge">gpio-117</code> works with the 1PPS input in IRQ mode.</p>
<h3 id="enclosure">Enclosure</h3>
<p>The enclosure feels high quality and serves well as a heatsink. It’s readily accessible and often easier to buy the NanoPi R5S with the case then without.</p>
<p>Furthermore, there’s unused cut-out for a SMA WiFi antenna adapter on the back. I hope to use this for a GNSS antenna once I figure out how to tuck the GNSS module into the case.</p>
<h2 id="first-look">First Look</h2>
<p>Ships with FriendlyWrt 21.02. Out of the box <code class="language-plaintext highlighter-rouge">linuxptp</code> works largely out of the box.</p>
<p>I built FriendlyWrt 22.03 so I could incorporate the <code class="language-plaintext highlighter-rouge">UART5</code> and 1PPS input device tree changes without much hassle using the directions and an MicroSDXC card.</p>
<p>After some initial wiring with a 12-pin FPC breakout out board, I was able to get <code class="language-plaintext highlighter-rouge">gpsd</code> to read a UBLOX NEO-M8N gps module. I then fed that to <code class="language-plaintext highlighter-rouge">chrony</code> which verified the time was pretty close to right. Finally, I configured <code class="language-plaintext highlighter-rouge">chrony</code> to read the 1PPS signal (<code class="language-plaintext highlighter-rouge">/dev/pps1</code> in my case) to precisely tune the clock after setting the time.</p>
<p>Hardware timestamping info:</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>root@FriendlyWrt:~# ethtool -T eth0
Time stamping parameters for eth0:
Capabilities:
hardware-transmit (SOF_TIMESTAMPING_TX_HARDWARE)
software-transmit (SOF_TIMESTAMPING_TX_SOFTWARE)
hardware-receive (SOF_TIMESTAMPING_RX_HARDWARE)
software-receive (SOF_TIMESTAMPING_RX_SOFTWARE)
software-system-clock (SOF_TIMESTAMPING_SOFTWARE)
hardware-raw-clock (SOF_TIMESTAMPING_RAW_HARDWARE)
PTP Hardware Clock: 0
Hardware Transmit Timestamp Modes:
off (HWTSTAMP_TX_OFF)
on (HWTSTAMP_TX_ON)
Hardware Receive Filter Modes:
none (HWTSTAMP_FILTER_NONE)
all (HWTSTAMP_FILTER_ALL)
ptpv1-l4-event (HWTSTAMP_FILTER_PTP_V1_L4_EVENT)
ptpv1-l4-sync (HWTSTAMP_FILTER_PTP_V1_L4_SYNC)
ptpv1-l4-delay-req (HWTSTAMP_FILTER_PTP_V1_L4_DELAY_REQ)
ptpv2-l4-event (HWTSTAMP_FILTER_PTP_V2_L4_EVENT)
ptpv2-l4-sync (HWTSTAMP_FILTER_PTP_V2_L4_SYNC)
ptpv2-l4-delay-req (HWTSTAMP_FILTER_PTP_V2_L4_DELAY_REQ)
ptpv2-event (HWTSTAMP_FILTER_PTP_V2_EVENT)
ptpv2-sync (HWTSTAMP_FILTER_PTP_V2_SYNC)
ptpv2-delay-req (HWTSTAMP_FILTER_PTP_V2_DELAY_REQ)
root@FriendlyWrt:~# ethtool -T eth1
Time stamping parameters for eth1:
Capabilities:
software-transmit (SOF_TIMESTAMPING_TX_SOFTWARE)
software-receive (SOF_TIMESTAMPING_RX_SOFTWARE)
software-system-clock (SOF_TIMESTAMPING_SOFTWARE)
PTP Hardware Clock: none
Hardware Transmit Timestamp Modes: none
Hardware Receive Filter Modes: none
root@FriendlyWrt:~# ethtool -T eth2
Time stamping parameters for eth2:
Capabilities:
software-transmit (SOF_TIMESTAMPING_TX_SOFTWARE)
software-receive (SOF_TIMESTAMPING_RX_SOFTWARE)
software-system-clock (SOF_TIMESTAMPING_SOFTWARE)
PTP Hardware Clock: none
Hardware Transmit Timestamp Modes: none
Hardware Receive Filter Modes: none
</code></pre></div></div>
<p>Chrony timing stats comparing time to public NTP servers:</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>root@FriendlyWrt:~# chronyc tracking
Reference ID : 50505300 (PPS)
Stratum : 1
Ref time (UTC) : Thu Sep 29 04:52:20 2022
System time : 0.000000314 seconds slow of NTP time
Last offset : -0.000000304 seconds
RMS offset : 0.000000536 seconds
Frequency : 29.929 ppm fast
Residual freq : -0.000 ppm
Skew : 0.025 ppm
Root delay : 0.000000001 seconds
Root dispersion : 0.000016748 seconds
Update interval : 16.0 seconds
Leap status : Normal
</code></pre></div></div>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>root@FriendlyWrt:~# chronyc -n sources -v
.-- Source mode '^' = server, '=' = peer, '#' = local clock.
/ .- Source state '*' = current best, '+' = combined, '-' = not combined,
| / 'x' = may be in error, '~' = too variable, '?' = unusable.
|| .- xxxx [ yyyy ] +/- zzzz
|| Reachability register (octal) -. | xxxx = adjusted offset,
|| Log2(Polling interval) --. | | yyyy = measured offset,
|| \ | | zzzz = estimated error.
|| | | \
MS Name/IP address Stratum Poll Reach LastRx Last sample
===============================================================================
#- GPS 0 4 377 23 -8931us[-8931us] +/- 51ms
#* PPS 0 4 377 22 +91ns[ +110ns] +/- 840ns
^- 2600:c05:3010:50:47::1 2 10 377 172 +609us[ +609us] +/- 59ms
^- 72.30.35.88 2 10 377 444 +2263us[+2263us] +/- 24ms
^- 2604:2dc0:101:200::b9d 2 10 377 481 +1199us[+1199us] +/- 71ms
^- 44.4.53.4 2 11 377 26m +5413us[+5449us] +/- 59ms
</code></pre></div></div>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>root@FriendlyWrt:~# chronyc -n sourcestats -v
.- Number of sample points in measurement set.
/ .- Number of residual runs with same sign.
| / .- Length of measurement set (time).
| | / .- Est. clock freq error (ppm).
| | | / .- Est. error in freq.
| | | | / .- Est. offset.
| | | | | | On the -.
| | | | | | samples. \
| | | | | | |
Name/IP Address NP NR Span Frequency Freq Skew Offset Std Dev
==============================================================================
GPS 6 3 81 -55.987 208.159 -14ms 1630us
PPS 7 4 95 -0.015 0.082 -143ns 999ns
2600:c05:3010:50:47::1 6 5 86m +0.083 0.404 +429us 222us
72.30.35.88 7 5 103m -0.007 0.133 +1987us 108us
2604:2dc0:101:200::b9d 14 9 224m -0.188 0.057 +897us 223us
44.4.53.4 10 7 309m -0.265 0.274 +5891us 1061us
</code></pre></div></div>
<p>Some PPS testing testing using <code class="language-plaintext highlighter-rouge">ppswatch</code></p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>root@FriendlyWrt:~# ppswatch -a /dev/pps1
trying PPS source "/dev/pps1"
found PPS source "/dev/pps1"
timestamp: 1664427237, sequence: 163286, offset: -5414
timestamp: 1664427238, sequence: 163287, offset: 2930
timestamp: 1664427239, sequence: 163288, offset: -2725
timestamp: 1664427240, sequence: 163289, offset: 3870
timestamp: 1664427241, sequence: 163290, offset: -3535
timestamp: 1664427242, sequence: 163291, offset: 1602
timestamp: 1664427243, sequence: 163292, offset: -2012
timestamp: 1664427244, sequence: 163293, offset: 2252
timestamp: 1664427245, sequence: 163294, offset: 3604
timestamp: 1664427246, sequence: 163295, offset: -3794
timestamp: 1664427247, sequence: 163296, offset: -3901
timestamp: 1664427248, sequence: 163297, offset: -4883
timestamp: 1664427249, sequence: 163298, offset: 3177
timestamp: 1664427250, sequence: 163299, offset: -3638
timestamp: 1664427251, sequence: 163300, offset: -2287
timestamp: 1664427252, sequence: 163301, offset: 2565
timestamp: 1664427253, sequence: 163302, offset: 2750
timestamp: 1664427254, sequence: 163303, offset: 3227
timestamp: 1664427255, sequence: 163304, offset: 2828
timestamp: 1664427256, sequence: 163305, offset: 3597
timestamp: 1664427257, sequence: 163306, offset: -5259
timestamp: 1664427258, sequence: 163307, offset: 1634
timestamp: 1664427259, sequence: 163308, offset: -4014
timestamp: 1664427260, sequence: 163309, offset: 2881
timestamp: 1664427261, sequence: 163310, offset: -4222
timestamp: 1664427262, sequence: 163311, offset: 3842
^C
Total number of PPS signals: 26
Maximum divergence: 5414
Mean value: -189.423
Standard deviation: 3460.59
</code></pre></div></div>
<h2 id="next-steps">Next Steps</h2>
<h3 id="gnss-improvements">GNSS Improvements</h3>
<p>I need to use a proper GNSS antenna that can be well placed outside. Currently I’m using a quadcopter GNSS module with integrated ceramic antenna and placed it in the window. With a different GNSS module with a SMA connector I’ll be able to place the antenna outside for better signal. Currently the signal drops out as the GNSS satellites come and go. I’ve ordered a cheap (possibly fake) UBLOX NEO-M8N from somewhere in China with a SMA and U.FL connector. In a perfect world I’ll be able to tuck this + the FPC wiring in the enclosure and run a U.FL to SMA cable to the plugged WiFi SMA cutout in the enclosure.</p>
<h3 id="enable-ptp-support-on-the-rtl8125">Enable PTP support on the RTL8125</h3>
<p>Need to dig in to the out-of-tree kernel driver and see why it doesn’t show up as PTP hardware clock.</p>
<h3 id="add-in-ptp-grandmaster-support-on-all-ports">Add in PTP grandmaster support on all ports</h3>
<p>The <code class="language-plaintext highlighter-rouge">linuxptp</code> daemon in OpenWrt works well enough on a single port, but I’d make to make it work on all ports. The struggle here is that typically I’d bridge the interfaces and be done, but PTP doesn’t really work with bridges. Need to think about what’s a sensible network setup where I can keep this as a “simple” timing device and not turn in to a complicated router.</p>
<h3 id="record-time-statistics">Record time statistics</h3>
<p>Recording the chrony statistics in something like InfluxDB would be idea, but not sure if that’d work well on an embedded device. Then display a dashboard in Grafana to track performance metrics:</p>
<ol>
<li>SNR for each satellite seen</li>
<li>Corrected oscillator error in ppm over time</li>
<li>1PPS jitter/offset</li>
</ol>
<h3 id="better-pps-timestamping">Better PPS Timestamping</h3>
<p>It’s unclear to me why there’s so much delay and jitter in the PPS timestamping. Likely this is just the latency of the kernel servicing the IRQ up to the resolution of the main system clock (which I need to look-up, 24 MHz?).</p>
<p>First steps are to turn off dynamic ticks and use the performance <code class="language-plaintext highlighter-rouge">cpufreq</code> governor.</p>
<p>Beyond that, then using the PWM capture unit (<code class="language-plaintext highlighter-rouge">PWM15</code>) would be much better. Seems that the fastest I can drive the PWM peripheral is 24 MHz which means only about 41.67ns of resolution, but this seems better then the ±3000ns observed with the PPS test above with no notuning.</p>
<p>I verified the <code class="language-plaintext highlighter-rouge">PWM15</code> output works, but the capture and compare unit reports “not implemented” during cursory exploration.</p>
<h3 id="package-up-friendlywrt-to-just-work">Package up FriendlyWrt to just work</h3>
<p>Assemble a FriendlyWrt image that just works out of the box for this application that incorporates all the changes above.</p>
<h3 id="gnss-pcb-integration">GNSS PCB integration</h3>
<p>Create a simple PCB to integrate a UBLOX NEO-M8N (or better) that easily interface with the FPC expansion connector. Ideally this would be easily tucked within the enclosure and expose a SMA connector at the WiFi antenna cut-out near the USB-C connector.</p>Kyle MannaCheap, ubiquitous, and feature rich RK3568 SBC with PTP and PPS hardware supportDanger: AVJONE NC318 WiFi Smart Outlet is an Electrocution and Fire Hazard2021-01-18T00:00:00+00:002021-01-18T00:00:00+00:00https://blog.kylemanna.com/hardware/avjone-nc318-wifi-smart-outlet-power-plug-danger-electrocution-and-fire-hazard<h2 id="danger">Danger</h2>
<p><strong>tl;dr; Do not buy this! At minimum this will damage devices connected to it. At worst it could hurt
somebody or start a fire.</strong></p>
<p>It appears the USB shield and USB ground is directly connected to AC mains line voltage. This means the
outer shell of any USB connected cable is connected to mains voltage which is 120 VAC in the United
States. The cable can electrocute you if you touch the other end (any metallic part exposed of any USB
cable) and are even slightly grounded (i.e. feet on damp concrete floor).</p>
<p>If you’re lucky, it will only destroy inanimate objects on the path to ground when connected to any other
properly designed device. This happened to me. Read below for the story.</p>
<h2 id="update-2021-01-31">Update 2021-01-31</h2>
<p>A few weeks after posting this I was contacted by Willi Hilgers from Germany who had seen this post and
measured his version of the outlet to confirm the same problem!</p>
<p>See photo of the German version from Willi Hilgers below:</p>
<p><img src="https://i.imgur.com/OqLEFRe.jpg" alt="Photo of similar outlet showing 230VAC on USB shielding!" /></p>
<p>This confirms my suspicions and dismisses my concerns that my modification caused this issue.
Thanks for sharing! I can rest better knowing I didn’t directly cause this safety hazard, but still am
disappointed I didn’t measure this out of the box. Live and learn.</p>
<p>Original post follow.</p>
<h2 id="perhaps-i-caused-this">Perhaps I Caused This?</h2>
<p>In an <a href="/hardware/avjone-nc318-wifi-smart-outlet-power-plug-with-tasmota-and-usb-power/">earlier post</a> I
described a way to modify the circuit to control the high side USB MOSFET that supplies 5V to the USB
connector from the microcontroller.</p>
<p>There’s a chance that I misunderstood something in the circuit and bridged high voltage side to low
voltage side and created this dangerous situation. In reviewing my notes I’m confident that my
understanding was and still is correct. If I was wrong I’d expect the circuit to have instantly smoked or
for the re-worked wire to show damage from the short circuit situation that was created and descrbied in
the following section.</p>
<p>The only way to learn more would be to find a new un-modified circuit to confirm this by making the same
measurements shown below with the device fully assembled.</p>
<p>Unfortuantely, these outlets are no longer listed on Amazon. On the product page I ordered from is now a
similar shaped outlet without the USB ports. Perhaps the discontinuation of this product is the most
confirmation of an original design flaw I’ll ever get.</p>
<p>I’d like to get closure to know with certainty whether I misunderstood something and made the device
dangerous or if it was designed dangerous and failed to check this while investigating it in my earlier
post. Eitherway, something to be learned here is to be careful with mains AC powered items.</p>
<h2 id="learning-the-hard-way">Learning the Hard Way</h2>
<p>I’ve been working on a project to build a
<a href="https://en.wikipedia.org/wiki/Consumer_Electronics_Control">HDMI-CEC</a> and
<a href="https://en.wikipedia.org/wiki/HDMI#HDMI_Ethernet_and_Audio_Return_Channel">HDMI-ARC</a> digital audio
amplifier to replace my overkill, power hungry, and buggy AVR for my stereo TV setup.</p>
<p>The prototype matured to a stage where it was ready to be tested with my TV. The TV and USB devices in
this setup were connected to this dangerous outlet.</p>
<p>See the block diagram below for an overview of how things were connected:</p>
<p><img src="https://i.imgur.com/eIGFkbC.png" alt="Prototype block diagram" /></p>
<p>The short circuit occurred the moment I attempted to connect the USB cable between my Khadas VIM3L (which
was plugged in and running) and the Khadas Tone DAC which was connected to the Topping MX3 audio
amplifier via RCA cables (MX3 was powered and running).</p>
<p>This USB cable, which is expected to have nothing more then 5V on it, sparked upon touching the outer
housing of the USB connector of the DAC. This instantly flipped the 15A circuit breaker. At this moment
I was shocked as to how a 5V USB connector (expecting 5W at most) could flip a 1800W circuit breaker.</p>
<p>Aware that something just went <em>very wrong</em> I made sure to take a mental note of what I was doing and
what was connected. Disconnected all the devices in the block diagram and reset the circuit breaker.</p>
<h2 id="diagnosing-the-design-flaw">Diagnosing the Design Flaw</h2>
<p>Working backwards (in a now much safer, more controlled environment since this was clearly hazardous)
from the USB connector I quickly learned that 120V was present on the connector.</p>
<p>First observation is that the USB shield is directly connected to the AC mains hot line with less then 1Ω
of resistance. <strong>This is clearly designed wrong</strong>. The shield should be connected to ground or at
worst case neutral. This is how every other device is designed when not fully isolated.</p>
<p>The safety protection of the USB outlet is effectively shorted to the hot line of the AC outlet. This is
the opposite of safe.</p>
<h3 id="resistance-between-usb-shield-and-ac-outlet">Resistance between USB shield and AC outlet</h3>
<p><img src="https://i.imgur.com/qYWcrko.jpg" alt="Short between USB shield and AC Hot" /></p>
<p>Quick measurement shows that there’s only 0.35Ω of resistance between AC Hot and the shield of the USB
connector. I’d hope this would be at least 1MΩ.</p>
<p><img src="https://i.imgur.com/rFe7oHb.jpg" alt="1 MΩ between USB shield and AC Neutral" /></p>
<p>The resistance of the others shows that neutral line is 1 MΩ of resistance to the USB shield.</p>
<p><img src="https://i.imgur.com/6OC7qRH.jpg" alt="Infinite resistance between USB shield and AC Ground" /></p>
<p>AC Ground is effectively not connected to the USB shield.</p>
<h3 id="resistance-between-usb-powerground-and-ac-outlet">Resistance between USB power/ground and AC outlet</h3>
<p>Using a USB charging cable with a magnetic end made it easier to probe +VBUS and GND on the cable. The
outer ring is attached to USB GND and the inner pin is USB VBUS.</p>
<p><img src="https://i.imgur.com/v5oeo48.jpg" alt="Short between USB GND and AC Hot" /></p>
<p>Short (less then 1Ω) between AC Hot and USB GND.</p>
<p><img src="https://i.imgur.com/2Oktw5W.jpg" alt="340Ω from AC Hot to USB VBUS" /></p>
<p>There was 340Ω of resistance from AC Hot to USB VBUS.</p>
<p><img src="https://i.imgur.com/NhmNNzJ.jpg" alt="1MΩ from USB VBUS to AC Neutral" /></p>
<p>1MΩ from USB VBUS to AC Neutral.</p>
<h3 id="live-dangerously">Live Dangerously</h3>
<p>I confirmed the resistance measurements by measuring voltage while the dangerous switch is plugged in.
This was probably not a good idea in hindsight based on the proximity of fingers to the now known high
voltage design flaw.</p>
<p><img src="https://i.imgur.com/8cI3sCr.jpg" alt="0V from USB GND to AC Hot" /></p>
<p>0V from USB GND to AC Hot. This means the line is at 120 VAC (relative to Neutral) as there’s no voltage difference.</p>
<p><img src="https://i.imgur.com/Leh1zK2.jpg" alt="120 VAC from USB GND to AC Neutral" /></p>
<p>120 VAC from USB GND to AC Neutral. This is very bad and unsafe. I probably shouldn’t be so close to
touching this.</p>
<p><img src="https://i.imgur.com/OcMRuhp.jpg" alt="Not even ground is safe" /></p>
<p>USB GND to AC Ground is also 120 VAC. Again very dangerous.</p>
<h2 id="collateral-damage">Collateral Damage</h2>
<p>The collateral damage from this innocent setup was:</p>
<ul>
<li>
<p>Topping MX3 has two blown ferrite and shorted capacitor at minimum preventing it from working. I
assume these ferrites connected the RCA ground to the ground from the AC-DC power supply which in turn
connects ground to the AC Neutral line. This was the weakest link in the inadvertent short.</p>
<p><img src="https://i.imgur.com/wcQfjVK.jpg" alt="Damage to Topping MX3" /></p>
<p>I think I can repair this poor amplifier.</p>
</li>
<li>JBL Link 10 which was an innocent (yet historically flaky) bystander in all this stopped working,
drained it’s battery and hasn’t powered up or charged since. No dangerous currents flowed near this
thing. At worst it experienced a voltage transient. I assume this device was just poorly designed.</li>
<li>Magnetic USB connector used to make the connection (RIP).</li>
</ul>
<p>Surprisingly, the sensitive Khadas VIM3L single board computer and Khadas Tone Board DAC survived
unscathed (as far as I can tell) despite each experiencing instantaneous current of roughly 15A traverse
their ground paths.</p>
<h2 id="conclusion">Conclusion</h2>
<p>This AVJONE NC318 WiFi Smart Outlet is bad news, avoid it.</p>
<p>There’s a chance that my earlier modification caused an unexpected side effect of bridging low voltage
to high voltage in my post about <a href="/hardware/avjone-nc318-wifi-smart-outlet-power-plug-with-tasmota-and-usb-power/">flashing Tasmota and modifying it to control the USB
ports</a>. However
unlikely I think that is, I can’t say with certainty.</p>
<p>I fear I’ll never know, and if they’re no longer on sale hopefully nobody else has to find out either.</p>Kyle MannaDo not use this device, it's dangerousAVJONE NC318 WiFi Smart Outlet Power Plug with Tasmota and 2xUSB Ports2020-12-13T00:00:00+00:002020-12-13T00:00:00+00:00https://blog.kylemanna.com/hardware/avjone-nc318-wifi-smart-outlet-power-plug-with-tasmota-and-usb-power<h2 id="update-january-2021">Update January 2021:</h2>
<p><strong>Do not buy this and definitely don’t modify it as originally described below. It’s dangerous as it appears the circuit is wired backwards with USB ground referenced to the AC Hot Line.</strong></p>
<p>Read the update here: <a href="/hardware/avjone-nc318-wifi-smart-outlet-power-plug-danger-electrocution-and-fire-hazard/">Danger: AVJONE NC318 WiFi Smart Outlet is an Electrocution and Fire Hazard</a></p>
<h2 id="highlights">Highlights</h2>
<p><a href="https://smile.amazon.com/Wifi-Smart-Plug-AVJONE-Premium-Power/dp/B07LGTXK87?">AVJONE NC318 Smart WiFi
Outlet</a> is a unique
connected WiFi smart outlet as it has two USB ports. The USB ports are handy for powering nearby widgets. By
default the outlet and the USB ports are switched on and off together.</p>
<p><img src="https://i.imgur.com/HumrTzf.jpg" alt="AVJONE NC318 marketing image" /></p>
<p>The outlet is built around the <code class="language-plaintext highlighter-rouge">PSC-B67-GL</code> module which includes a Espressif <code class="language-plaintext highlighter-rouge">ESP8266</code> and Chipsea
<code class="language-plaintext highlighter-rouge">CSE7759B</code> for energy monitoring. This Chipsea module tends to be more accurate out of the box as it uses
a UART to report the data as opposed to a frequency signal other devices use.</p>
<p>The USB ports are rated for 2.1A max combined at 5V. I’m slightly skeptical that it can sustain this but
didn’t test it. This design is nice as it leverages a single 5V power supply for both the ESP8266 and an
external device.</p>
<p>The device has two LEDs (one is controlled by software) and a single button.</p>
<h2 id="teardown-and-internals">Teardown and Internals</h2>
<p>Opening the smart outlet is as simple as removing 4 screws from the back. No guessing and popping or
breaking plastic clips.</p>
<p>The design and build quality of the outlet leaves quite a bit to be desired. I hope the manufacturer
takes some steps to improve it as it’s a in another world compared to things like the UL certified Sonoff
S31 on the power side and the <code class="language-plaintext highlighter-rouge">PSC-B67-GL</code> module doesn’t have an RF shield as most <code class="language-plaintext highlighter-rouge">ESP8266</code> modules do.
It does have a chip antenna which is harder to screw up based on how hurried and unpolished this board
looks.</p>
<p><img src="https://i.imgur.com/lFhFCZK.jpg" alt="AVJONE NC318 PCB front" />
<img src="https://i.imgur.com/0RNefPg.jpg" alt="AVJONE NC318 PCB back" /></p>
<h2 id="tasmota">Tasmota</h2>
<p><a href="https://tasmota.github.io/docs/Getting-Started/#flashing">Flashing with Tasmota</a> is pretty straight
forward once the module pins are identified, and jumper wires soldered on. See the image below for
labels of the essential pins. I tested on Tasmota v9.1.0.</p>
<p><img src="https://i.imgur.com/bXzhLnh.jpg" alt="PSC-B67-GL module pin labels" /></p>
<p>The <code class="language-plaintext highlighter-rouge">RX</code> pin (from the perspective of the <code class="language-plaintext highlighter-rouge">ESP8266</code>) is shared with the Chipsea <code class="language-plaintext highlighter-rouge">CSE7759B</code> using a series
resistor to allow UART programming of the <code class="language-plaintext highlighter-rouge">ESP8266</code> to drive the line for programming. This confused me
for a while as I thought I had <code class="language-plaintext highlighter-rouge">TX</code> and <code class="language-plaintext highlighter-rouge">RX</code> backwards. The bit rate to the <code class="language-plaintext highlighter-rouge">CSE7759B</code> is very low and
the signal is attenuated as a result of this resistor should you get stuck and hook up an oscilloscope.</p>
<p>The template is based off the Sonoff S31 as it uses the same <code class="language-plaintext highlighter-rouge">ESP8266</code> and <code class="language-plaintext highlighter-rouge">CSE7759B</code>. I’m sure other
variations work as well.</p>
<div class="language-json highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="p">{</span><span class="nl">"NAME"</span><span class="p">:</span><span class="s2">"AVJONE NC318"</span><span class="p">,</span><span class="nl">"GPIO"</span><span class="p">:[</span><span class="mi">32</span><span class="p">,</span><span class="mi">3072</span><span class="p">,</span><span class="mi">225</span><span class="p">,</span><span class="mi">3104</span><span class="p">,</span><span class="mi">0</span><span class="p">,</span><span class="mi">0</span><span class="p">,</span><span class="mi">0</span><span class="p">,</span><span class="mi">0</span><span class="p">,</span><span class="mi">224</span><span class="p">,</span><span class="mi">321</span><span class="p">,</span><span class="mi">0</span><span class="p">,</span><span class="mi">0</span><span class="p">,</span><span class="mi">0</span><span class="p">,</span><span class="mi">0</span><span class="p">],</span><span class="nl">"FLAG"</span><span class="p">:</span><span class="mi">0</span><span class="p">,</span><span class="nl">"BASE"</span><span class="p">:</span><span class="mi">41</span><span class="p">}</span><span class="w">
</span></code></pre></div></div>
<p><img src="https://i.imgur.com/RDau2Sy.png" alt="Tasmota template live on the device" /></p>
<p>This should be enough to make the outlet work.</p>
<p><strong>Note</strong>:<code class="language-plaintext highlighter-rouge">Relay 2</code> and <code class="language-plaintext highlighter-rouge">Ledi_i 2</code> are to enable USB control after re-work. Without the re-work they will
not serve any purpose.</p>
<h2 id="rework-to-support-usb-power-control">Rework to Support USB Power Control</h2>
<p>The PCB can be re-worked by adding a simple jumper wire and re-using the existing resistor to allow <code class="language-plaintext highlighter-rouge">GPIO2</code> to
independently control what appears to be a P-channel MOSFET that controls the flow of power to the USB
Type-A connector ports. The 5V power supply is always on as it powers the <code class="language-plaintext highlighter-rouge">ESP8266</code>.</p>
<p>The signal path should be <code class="language-plaintext highlighter-rouge">ESP8266 GPIO2</code> -> <code class="language-plaintext highlighter-rouge">PSC-B67-GL</code> module -> re-used resistor -> re-work wire -> <code class="language-plaintext highlighter-rouge">R36</code>/<code class="language-plaintext highlighter-rouge">R37</code> pad -> P-channel MOSFET gate.</p>
<p>Steps to re-work:</p>
<ol>
<li>Remove the resistor on <code class="language-plaintext highlighter-rouge">R36</code> or <code class="language-plaintext highlighter-rouge">R37</code>, save the resistor for use later.</li>
<li>Connect a jumper wire to one or both of the pads as shown in the picture below.</li>
<li>Connect the resistor to module as shown so it will be in series with the re-work wire.</li>
<li>Attach the remaining end of the wire to the re-located series resistor.</li>
</ol>
<p><img src="https://i.imgur.com/RqThj1z.jpg" alt="AVJONE NC318 USB control rework front" />
<img src="https://i.imgur.com/oIxLFqM.jpg" alt="AVJONE NC318 USB control rework back" /></p>
<p>At this point the Tasmota tempalte shared above should toggle the MOSFET using the <code class="language-plaintext highlighter-rouge">Relay 2</code> control in
Tasmota.</p>
<h2 id="final-steps">Final Steps</h2>
<p>As always, <a href="https://tasmota.github.io/docs/Power-Monitoring-Calibration/">perform Tasmota
calibration</a>. I use a 40W incandescent
light bulb and a Fluke 187.</p>
<p>After this is complete I use this device for control control with MQTT and Home Assistant and logging
with InfluxDB and Grafana.</p>
<p><img src="https://i.imgur.com/rzoc86Z.png" alt="AVJONE NC318 Tasmota Web UI" /></p>Kyle MannaFlash Tasmota to a AVJONE Smart WiFi Outlet and modify to control USB outputSniffer Air Quality (AQI) Monitor using ESP32 + PMSA003 + BME6802020-09-20T00:00:00+00:002020-09-20T00:00:00+00:00https://blog.kylemanna.com/hardware/sniffer-air-quality-monitor-aqi-using-esp32-pmsa003-bme680<h2 id="california-wildfires-and-my-home">California Wildfires and My Home</h2>
<p>The San Francisco Bay Area has been blanketed with wildfire smoke from record setting wildfires creating
strange phenomena such as turning the sky an <a href="https://www.cnn.com/2020/09/09/weather/california-orange-skies-wildfires-photos-trnd/index.html">orange-red for a
day</a>.
In response to an ever rising <a href="https://en.wikipedia.org/wiki/Air_quality_index">air quality index (AQI, where higher value is less
healthy)</a> I’ve been forced to stay indoors and abandon my
normal bike rides, runs, and motorcycle adventures. 😞</p>
<p>With nothing better to do and curious about the air quality in my home I’ve been watching <a href="https://www.purpleair.com/map?opt=1/mAQI/a10/cC0#9.22/37.7226/-122.1704">PurpleAir’s
local sensor data in my
neighborhood</a>. PurpleAir is a
company that sells <a href="https://www2.purpleair.com/collections/air-quality-sensors">consumer grade air quality
sensors</a> for about $250 and then publishes
the data to their site giving localized information.</p>
<p>AirNow.gov has <a href="https://fire.airnow.gov/?lat=37.7552896&lng=-122.38848000000002&zoom=10">launched a site</a> that combines regulatory grade sensors and PurpleAir sensors to give a better picture. The <a href="https://ww3.arb.ca.gov/qaweb/site.php?s_arb_code=90306">SF regulatory sensor</a> is located in Potrero Hill and has <a href="https://www.arb.ca.gov/aqmis2/display.php?report=SITE31D&site=2373&monitor=-&year=2020&mon=09&day=20&param=PM25&units=001&statistic=HVAL&ptype=aqd&o3switch=new&hours=all">data available via CSV</a> for those interested, but the data is not as easy to ingest as PurpleAir’s API and lags behind up to an hour.</p>
<p>All that said, there’s plenty of data about outdoor air quality, but not a good way to measure and track
the indoor quality at my home or better yet determine if my Winix P150 air purifier helps the situation.</p>
<p>There must be a way to collect this data, save it to InfluxDB, and plot it with Grafana with a little
hardware.</p>
<h2 id="exploring-hardware-sensors">Exploring Hardware Sensors</h2>
<p>The core of assembling some hardware to measure air quality is to measure the concentration of
particulate matter in the air measuring smaller then roughly 2.5 μm in diameter. This is commonly
referred to as <a href="https://www.epa.gov/pm-pollution/particulate-matter-pm-basics">“PM2.5”</a> and the
concentration is a measure of mass with the units of μg/m³. This is then converted to the <a href="https://en.wikipedia.org/wiki/Air_quality_index">Air
Quality Index (“AQI”)</a> everyone references.</p>
<p>To measure the PM2.5 concentration there are may techniques ranging from costly to cheap. Regulatory
sensors use methods such as as <a href="https://en.wikipedia.org/wiki/Beta_attenuation_monitoring">beta attenuation
monitoring</a>, but hosting a radioactive source
seems dangerous and cost prohibitive for my passing interest in monitoring AQI. More accessible sensors
use a principle of <a href="https://en.wikipedia.org/wiki/Dynamic_light_scattering">dynamic light scattering</a> to
measure the size of PM1, PM2.5, and PM10 particles.</p>
<p>The PurpleAir sensors use two <a href="http://www.plantower.com/en/content/?108.html">PlanTower PMS5003 sensors</a>
for what I assume is fault tolerance (apparently not good enough as the PurpleAir sensor at UCSF seems to
be failing accordingly to the data and sporadic readings). There have been some tests (<a href="http://www.aqmd.gov/docs/default-source/aq-spec/field-evaluations/purple-air-pa-ii---field-evaluation.pdf?sfvrsn=4">field
evaluation</a>
and <a href="http://www.aqmd.gov/docs/default-source/aq-spec/laboratory-evaluations/purple-air-pa-ii---lab-evaluation.pdf?sfvrsn=4">laboratory
evaluation</a>)
showing that the sensor performs pretty well, especially for the cost. Turns out there are a few
generations of the PlanTower products starting with the PMS3003, newer versions PMS7003, PMS1003 and with
PMSA003 being the newest.</p>
<p>The PMSA003 sensors can be bought on AliExpress for less then $20 each and roughly <a href="https://smile.amazon.com/dp/B082B8R29B">$35 via Amazon third party
sellers</a>. This is a very approachable price compared to what I feared.</p>
<p>I selected a TTGO ESP32 T-Display module as it has an ESP32 micro controller (with integrated WiFi for
those not familiar), a USB-C connector (it’s 2020 after all), and a small 1.14” LCD display. All this
for between $8 on AliExpress and ~<a href="https://smile.amazon.com/dp/B07XQ5G279">$13 through Amazon</a>.</p>
<p>Finally, I selected a <a href="https://www.bosch-sensortec.com/products/environmental-sensors/gas-sensors-bme680/">Bosch
BME680</a> to measure
some interesting environmental parameters including temperature, humidity, pressure, and <a href="https://www.epa.gov/indoor-air-quality-iaq/volatile-organic-compounds-impact-indoor-air-quality">volatile
organic
compounds</a>.
The VOC sensor is novel and returns a “gas resistance” measurement using a 320°C hot plate within the
sensor, but I struggle to make much sense of the output. Seems that higher resistance is lower VOC
concentration. There’s a driver from Bosch on <a href="https://github.com/BoschSensortec/BME680_driver">Github</a>,
but I haven’t looked any closer at it. This sensor is kind of expensive when compared to the BME280
where the only difference appears to be the absence of the gas resistance sensor which has limited
utility for my use.</p>
<h2 id="software-options">Software Options</h2>
<p>The sensor emits data using a 9600 bps UART at regular intervals and seems to require no actual
configuration to start emitting data.</p>
<p>For platforms to build on, I want to use an ESP8266/ESP32 so that I can leverage platforms like <a href="https://tasmota.github.io/docs/">Tasmota</a> (which
I’ve used in the past for IoT things) and <a href="https://esphome.io/index.html">ESPHome</a> (which I’ve been looking for an excuse to use).</p>
<p>Tasmota has a basic driver for the
<a href="https://github.com/arendst/Tasmota/blob/master/tasmota/xsns_18_pms5003.ino">PMS3003, PMS5003, and PMS7003 sensor</a>. I didn’t dig any deeper then this as Tasmota primarily targets ESP8266 platform and I wanted to use an ESP32 as it’s more capable for only a small price increase.</p>
<p>I learned that the ESPHome has support for the <a href="https://esphome.io/components/sensor/pmsx003.html">PMSX003
platform</a> and my search halted there as ESPHome also
has great ESP32 support.</p>
<p>Starting in ESPHome v1.15.0 <a href="https://github.com/esphome/esphome/pull/918">support for the ST7789V LCD controller was
added</a>.</p>
<h2 id="prototype">Prototype</h2>
<p>To start I wired up everything by hand. It was a mess. It was fragile and would break if you looked at it
wrong. But, it worked!</p>
<p><img src="https://i.imgur.com/hG1x8ta.jpg" alt="Sniffer prototype mess" /></p>
<p>The ESPHome software was amazingly easy to use. I did nothing more then fire up a Docker container to
compile the software and to then flash the device over USB for the first time. All from my web browser, I
have yet to directly touch a compiler for this project. Amazing.</p>
<p>Subsequent updates to re-flash the ESP32 were handled over the air and I never had to plug the device
back in to my computer.</p>
<p>All the device application specific parts of my project are implemented in YAML. Some of YAML ends up as
C++ lambda functions to do the heavy lifting. The platform abstracts this away and it’s easy to forget
it’s actually compiled to C++ at the end of the process.</p>
<p>YAML itself is awkward to use for things like inline lambda functions but ESPHome platform is feature
rich enough to justify the clumsy nature of YAML syntax for these things.</p>
<h2 id="kicad-schematic-and-pcb">KiCad Schematic and PCB</h2>
<p>To take the project to the next level I spent a few hours and put together a KiCad schematic and PCB
design to replace the wires and create some structure. The PCB mounts the TTGO ESP32 T-Display module on
top with the PMSA003 particulate matter sensor and BME680 are on the back side mostly hidden from view as
it sits on my dresser.</p>
<p><img src="https://i.imgur.com/3xxddGh.jpg" alt="Sniffer PCB Rendering Front" />
<img src="https://i.imgur.com/XGgeu2g.jpg" alt="Sniffer PCB Rendering Back" /></p>
<p>The PCB is extremely simple but has some components on it for flexibility if I needed it. There are
solder jumpers to allow me to easily cut a trace and re-work something if needed, some decoupling
capacitors to smooth out the power supply (again only if needed) and finally some high side P channel
MOSFET that would allow me to turn off the various sensors if I wanted to make this battery powered and
needed more power management.</p>
<p>As of writing, I haven’t used any of the extra features. The PCB is a glorified wire replacement back
plane just connecting pins and hold parts in place. Only work necessary to assemble the PCB is to solder
2.54mm and 1.27mm pin headers using the headers included with the respected modules, no extra pieces
needed.</p>
<p>The design is available on <a href="https://github.com/kylemanna/sniffer">Github with the project affectionately named
“Sniffer”</a>.</p>
<h2 id="pcbway">PCBWay</h2>
<p>I’ve used PCBWay in the past to manufacture hundreds and hundreds of boards in the past. They have an
amazingly smooth online submission process and lots of options to balance PCB technology and cost.</p>
<p>For this project I paid $5 for the 10x boards and $18 for DHL shipping. I designed and ordered the board
on Friday and the PCBs were in my hand the next Friday. Faster then expected delivery of a custom design
from across the world during a pandemic.</p>
<p>The gerbers used for the design are on
<a href="https://github.com/kylemanna/sniffer/tree/master/kicad/gerbers">Github</a> and can also be uploaded to the
PCB manufacturer of your choice. I tried OSH Park and the renderings looked correct. I didn’t select
OSH Park as I’d get fewer boards for more money with a slower delivery time. The OSH Park boards
had some better properties like ENIG vs HASL plating.</p>
<p>That said, if anyone wants to get PCBs made, checkout the <a href="https://www.pcbway.com/project/shareproject/Sniffer_Air_Quality_Monitor.html">PCBWay shared project
page</a> that will let you
click “Add to cart” and order ‘em with no fuss.</p>
<p>If you do order from PCB Way, check if they offer the <a href="https://www.pcbway.com/project/gifts_detail/TTGO_T_Display_ESP32_WiFi_and_Bluetooth_Module_Development_Board_For_Arduino_1_14_Inch_LCD.html">TTGO ESP32 T-Display
module</a>
from the gift shop as they did when I ordered my boards for $8/module.</p>
<p>If you want to order from PCBWay please use my <a href="https://www.pcbway.com/setinvite.aspx?inviteid=3549">referral code to save
$5</a> which I think might make the first PCB order for
you free (before shipping). Someone please let me know in the comments if this is true!</p>
<p><img src="https://i.imgur.com/djgrSj6.jpg" alt="Sniffer PCBs unpopulated" /></p>
<h2 id="assembly">Assembly</h2>
<p>The PCB is easily assembled by doing nothing more than soldering the 1.27mm and 2.54 pin headers and
connectors included with each of the components. No additional parts needed to assemble the Sniffer
electrically.</p>
<p>To finalize the mechanical assembly some fasteners or low profile double side tape (poster tape worked
well) for me to reduce the strain of the PMSA003 on the 2x5 1.27mm header.</p>
<p>I tried some fine pitch M2x5.5mm screws I had on hand to secure the PMSA003, but one hole is too shallow
(screw is too long) and the other isn’t quite right. Clearly fine pitch isn’t the right move for
threading into soft plastic.</p>
<p>I ordered some M2x4mm and M2x6mm coarse pitch screws hoping they work better.</p>
<p><img src="https://i.imgur.com/NaeZ17w.jpg" alt="Sniffer PCB Assembled Front" />
<img src="https://i.imgur.com/77KQXdF.jpg" alt="Sniffer PCB Assembled Back" />
<img src="https://i.imgur.com/Fa8yZ4f.jpg" alt="Sniffer PCB Assembled Front Vertical" />
<img src="https://i.imgur.com/m3SkfM2.jpg" alt="Sniffer PCB Assembled Back Sensor View" />
<img src="https://i.imgur.com/5UzjO6i.jpg" alt="Sniffer PCB Assembled Back PMSA003 Removed" /></p>
<h2 id="validation">Validation</h2>
<p>To validate the accuracy of the sensor I placed it outside and logged the data for several hours and
compared it to nearby PurpleAir sensors.</p>
<p>Here’s the plot of the data in Grafana, the Sniffer is <code class="language-plaintext highlighter-rouge">sniffer0_pm_2_5_aqi</code>.</p>
<p><img src="https://i.imgur.com/5KR35hx.png" alt="Grafana AQI plot of sniffer vs nearby sensors" /></p>
<p>The sensor data seems to be within about 1% of the other sensors in the area.</p>
<p>Grafana dashboard with the BME680 parameters:</p>
<p><img src="https://i.imgur.com/fBRGtVl.png" alt="Grafana with BME680 data" /></p>
<h2 id="whats-next">What’s Next</h2>
<p>Next steps are to build more and give them to some friends. To remove the dependency on WiFi and
HomeAssistant I’ll explore updating ESPHome to drop the HomeAssistant connection for a time source and
use NTP instead. This is just a matter of modifying YAML files on the project repo.</p>
<p>I’ll probably drop the BME680 in favor of the BME280. There’s a BME280 module that is pin compatible
with the Sniffer design. Would love it if someone could demonstrate a more useful use-case for the VOC
gas resistance sensor. Seems this would be most useful in an industrial setting where strong solvents
are present, but my home isn’t subject to those chemicals.</p>
<p>On the next boards I’m going to use 2.54mm pitch machined round pin headers and sockets to hold the
components in place so I can swap them for future testing. The first build uses the included square
stamped pin headers and were soldered in place.</p>
<p>A friend mentioned they’d look at making a 3D printable enclousre to hold it, so that’d be cool.</p>
<p>Others have asked for an assembly tutorial, maybe I’ll put together a follow-up post if there’s more
interest.</p>
<p>Unrelated to the hardware, I want to ingest the CARB government PM2.5 data stream and log that to
InfluxDB and Grafana to have a more authoritative data source.</p>
<h2 id="conclusion">Conclusion</h2>
<p>The PMSA003 works as advertised and is close enough to the nearby sensors to give me enough confidence in
its readings to evaluate the air within my home. My Winix P150 air purifier makes a notable difference
on the two highest speeds and doesn’t do much at lower speeds.</p>
<p>ESP32 is a pretty awesome chip (price + features) and is enabled by an equally amazing ESPHome ecosystem.</p>Kyle MannaMonitor indoor or outdoor air quality index (AQI) with a SnifferAnker Soundcore Liberty Neo Earbud Headphone Review2019-08-04T00:00:00+00:002019-08-04T00:00:00+00:00https://blog.kylemanna.com/electronics/anker-soundcore-liberty-neo-earbud-headphone-review<h2 id="the-search-for-the-ultimate-single-earbud-headphone-set">The Search for the Ultimate Single Earbud Headphone Set</h2>
<h3 id="hello">Hello</h3>
<p>Last October I bought the <a href="https://amzn.to/31rTiHp">Anker Soundcore Liberty Lite Headphones</a> (replaced by the <a href="https://amzn.to/2yPFyKh">Liberty Neo</a>) to wear while biking to work and motorcycling.</p>
<p>I wanted headphones that could operate in single earbud mode under my helmet while commuting: I like to be able to wear just one earbud to listen to music and podcasts and the occasional Google Maps instruction, while keeping the other ear open and available to listen to all the excitement of the road.</p>
<h3 id="fit">Fit</h3>
<p>These headphones come with four different sizes of silicone “EarTips” (the soft bit that fits into the ear canal) and four different sizes of “WingTips” (little “wings” that secure the earbud rotationally in the ear). I found that just using the medium-sized EarTip without any of the WingTips fit my ears perfectly.</p>
<p>The headphone stayed in my ear while running and didn’t interfere with my bicycle helmet. They were also low-enough profile that they seamlessly fit under my <a href="https://amzn.to/31fsV7e">Shoei RF1200 Motorcycle Helmet</a>. Although both could fit under my motorcycle helmet, I only ever wore the right earbud while also wearing the helmet.</p>
<h3 id="sound">Sound</h3>
<p>They sounded better than I expected, and they certainly beat the wind noise on the highway. The silicone EarTips seal well in my ears and this works great for attenuating a fair amount of noise.</p>
<h3 id="use">Use</h3>
<p>The earbuds had one button on each ear. Right earbud button functionality:</p>
<ul>
<li>Short press to play/pause</li>
<li>Medium press to skip forward a track</li>
<li>Long press to power off</li>
<li>Extremely long press to enter pairing mode</li>
</ul>
<p>Left earbud button did the same, except it skipped backward a track because English and other PIE languages and cultures conceptualizes time as flowing from <a href="http://lera.ucsd.edu/papers/language-time.pdf">left</a> <a href="http://groups.psych.northwestern.edu/gentner/papers/Gentner01.pdf">to</a> <a href="http://www.casasanto.com/papers/Casasanto&Boroditsky_2008.pdf">right</a>.</p>
<p>Volume was controlled by the phone, in my case a <a href="https://amzn.to/2KjzYGB">Google Pixel 3</a> worked great.</p>
<p>The microphone worked quite well for phone conversations, despite the fact that the microphone was in my ear. This was a pleasant surprise when I accidentally answered a call with the earbud headphones on.</p>
<p>I almost never power them off, as it was kind of clumsy compared to just returning them to the charging case which automatically powered them down.</p>
<h3 id="goodbye">Goodbye</h3>
<p>Unfortunately, I lost them at the airport.</p>
<h2 id="enter-anker-soundcore-liberty-neo">Enter Anker Soundcore Liberty Neo</h2>
<p>I was quite sad to discover they hadn’t survived my trip and immediately looked to replace them. Anker Soundcore replaced them with the <a href="https://amzn.to/2yPFyKh">Anker Soundcore Liberty Neo</a> earbud headphones. These are pretty much indistinguishable from my previous version, except for the charging case (Why is it glossy now?! It always looks smudged.) and the “secure fit EarWings” have little rubber blanks installed, which have also fallen off.</p>
<p>But, they work just as well. Maybe even better, but not noticeably different to me.</p>
<h2 id="in-a-nutshell">In a Nutshell</h2>
<p>If you want to listen to music, podcasts, or directions with a single earbud that doesn’t interfere with a bicycle or motorcycle helmet get the <a href="https://amzn.to/2yPFyKh">Anker Soundcore Liberty Neo</a> headphones.</p>Kyle MannaThe quest for the ultimate helmet-compatible single earbud headphonesCooSpo Heart Rate Monitor (HRM) Fitness Tracker Review2019-07-21T00:00:00+00:002019-07-21T00:00:00+00:00https://blog.kylemanna.com/fitness/coospo-heart-rate-monitor-fitness-tracker-review<h2 id="garmin-products-live-short-lives">Garmin Products Live Short Lives</h2>
<p>My first Garmin product was a <a href="https://amzn.to/2JZ9Qiv">Forerunner 305</a> and I purchased it in 2009 when I bought my first road bike. The adhesive holding the face and backshell failed (blame Texas heat) causing the face to separate from the body. I was able to re-epoxy it one or twice, but each time the buttons became harder to use as a result of epoxy going places it shouldn’t. Somewhere along the way the charging contacts became increasingly corroded from sweat (also blame Texas heat) and I was able repair them, but it was finicky to charge for the rest of its life. Finally, the watch entered retirement in 2014 as expected when the battery degraded to the point it wouldn’t support my longer weekend rides. The included <a href="https://amzn.to/2YqohFX">Garmin HRM1 (010-10997-00)</a> outlasted the watch needing only a few battery replacements.</p>
<p>Next up was the <a href="https://amzn.to/2JW5IQA">Forerunner 310XT</a>. This watch had improved on all my previous complaints: improved button feel, improved GPS signal reception, and a revised more comfortable HRM, and an improved (still clumsy) ANT+ synchronization USB dongle. Five years of progress yielded a better product. However, this watch’s life was abruptly terminated in 2017 by a silly failure: the plastic wristband pin boss failed making it impossible to retain the wristband. The later generation <a href="https://amzn.to/2LywJfZ">Garmin HRM3 (010-10997-07)</a> outlasted the watch it was sold with needing only a battery replacement or two.</p>
<p>Currently I own a <a href="https://amzn.to/2JTLCX2">Forerunner 920XT</a>. This watch like the watch before it improved on features again: the buttons were even better, the watch now seamlessly synchronizes with Garmin Connect using BLE to my phone and sometimes WiFi, and the wristband bosses are substantially larger. So far this watch has been pretty painless with the exception of some WiFI issues (easily overcome by letting it use BLE to upload via my phone).</p>
<p>The latest <a href="https://amzn.to/2JTLCX2">Forerunner 920XT</a> ships with a <a href="https://amzn.to/2SwKBbb">Garmin HRM4-Run (010-10997-12)</a> which has the nicest band by far. But, that’s where the fun ends. This thing is a train wreck.</p>
<h2 id="garmin-heartrate-monitors">Garmin Heartrate Monitors</h2>
<p>The <a href="https://amzn.to/2SwKBbb">Garmin HRM4-Run</a> included with the Forerunner 920XT died in less then 2 years. It appeared as if the battery was dead, but even after replacing the battery the device would only work for a few workout sessions before killing the new battery. It seems as if sweat has entered the device and corroded something that now conducts and discharges the battery prematurely.</p>
<p>But I can’t be the only one with this problem, right? Nope, many Amazon reviews have the same issue:
<img src="https://i.imgur.com/NA5UK28.png" alt="Sad Amazon Customer Review #1" />
<img src="https://i.imgur.com/L2I0zvq.png" alt="Sad Amazon Customer Review #2" />
<img src="https://i.imgur.com/ULXV32b.png" alt="Sad Amazon Customer Review #3" />
<img src="https://i.imgur.com/XiLT76m.png" alt="Sad Amazon Customer Review #4" /></p>
<p>Right.</p>
<p>Good thing I have the old <a href="https://amzn.to/2LywJfZ">Garmin HRM3</a> from my <a href="https://amzn.to/2JW5IQA">Forerunner 310XT</a>, right? Well that lasted a few months before flat out dying as well. Two Garmin HRMs down in only a few months after years of service.</p>
<p>Should I dig up the oldest, longest running <a href="https://amzn.to/2YqohFX">Garmin HRM1</a> that’s nearly a decade old, give Garmin more money for a replacement, or try something else?</p>
<p>Let’s try something else.</p>
<h2 id="enter-coospo">Enter CooSpo</h2>
<p>I got the inspiration to try something else when I was reminded that any <a href="https://en.wikipedia.org/wiki/ANT_(network)">ANT+</a> HRM would work. After some poking around on Amazon I stumbled on the <a href="https://amzn.to/2XWHXBP">CooSpo Fitness Tracker from Amazon for less then $40</a> which incorporates ANT+ and BLE. The reviews suggested that the device would work with Garmin devices and the BLE would work directly with my phone should I want it. I took the plunge and ordered a device with no obvious model number on the product page.</p>
<p><img src="https://i.imgur.com/fx0gtMz.jpg" alt="CooSpo HRM808S with chestband" /></p>
<p>Features:</p>
<ul>
<li>ANT+ works fine with my Garmin Forerunner 920XT</li>
<li>BLE works with Strava and various applications on my Android Google Pixel 3</li>
<li>LED illuminates and beeps when I put the HRM on signaling it’s working</li>
</ul>
<p>Appears to be called “HRM808S” according to the “Model Number String” in the BLE profile for Device Information with firmware version “v5.0.19”.</p>
<p>Screenshots of BLE info:
<img src="https://i.imgur.com/Asv9rXV.png" alt="BLE Screen #1" />
<img src="https://i.imgur.com/ntwbN2h.png" alt="BLE Screen #2" />
<img src="https://i.imgur.com/ykxaDlG.png" alt="BLE Screen #3" /></p>
<h2 id="certification">Certification</h2>
<p>I could’t find any FCC ID markings on the device. Also couldn’t find them “CooSpo” <a href="https://www.thisisant.com/directory">listed in the ANT directory</a>. This seemed odd.</p>
<p>A representative at the company quickly replied when contacted via the email address on their website saying that the FCC ID is under their contract manufacturer’s name “Shenzhen Fitcare Electronics Co., Ltd”. I was able to find a <a href="https://fccid.io/2ACN7-HRM803S/Test-Report/Test-Report-DTS-4061228.pdf">document</a> mentioning the “HRM808S” as an additional model of the “HRM803S” which has FCC ID <code class="language-plaintext highlighter-rouge">2ACN7-HRM803S</code>. Looks like it passed the certification process without issue.</p>
<p>Digging deeper, there is a “Fitcare Electronics” listing in the ANT directory, but not this model. The CooSpo representative said that ANT is slow to update their directory, so maybe it will show up there soon enough?</p>
<p>The CooSpo HRM808S device looks very similar to the <a href="http://www.fitcare.cn/product/detail/36.html">“HRM812” on Fitcare’s website</a>.</p>
<h3 id="outstanding-questions">Outstanding Questions</h3>
<p>What’s the actual model of this product? There’s no product / model number on the <a href="https://amzn.to/2XWHXBP">Amazon Product Page</a> and there’s nothing on <a href="http://www.coospo.com/">CooSpo’s own website</a> at the time of writing (July 2019). Searching Google for “HRM808S” or “H808” yields nothing. This is a ghost product that works amazingly well.</p>
<p>Will it last? I’ll be sure to update this page with tears when it dies, hopefully it lasts more then 2 year unlike my Garmin HRM4.</p>
<p>Even if it only lasts 1 year, it’s still a better value then a replacement Garmin HRM4-Run.</p>Kyle MannaOnly because Garmin can't build a HRM that lastsInstalling pfSense on Google Cloud Platform2017-12-09T00:00:00+00:002017-12-09T00:00:00+00:00https://blog.kylemanna.com/cloud/pfsense-on-google-cloud<p>The goal of this guide is to get a simple pfSense server setup and running the cloud. My primary interest is to use it as a VPN server for IPsec and OpenVPN clients.</p>
<h2 id="step-1---prepare-an-image">Step 1 - Prepare an Image</h2>
<p>First the pfSense distribution image needs to be converted to something that can be used on Google Cloud. This is more convoluted then it needs to be but, but sit tight!</p>
<p>I’d recommend doing this on a Google Cloud host or the cloud console to make the upload and download quick.</p>
<p>Download the image, decompress and move it to the required file:</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>curl https://nyifiles.pfsense.org/mirror/downloads/pfSense-CE-memstick-serial-2.4.2-RELEASE-amd64.img.gz | gunzip > disk.raw
</code></pre></div></div>
<p>Tar the file back up in a <a href="https://cloud.google.com/compute/docs/images/import-existing-image">format Google Cloud expects</a>:</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>tar -Sczf pfSense-CE-memstick-serial-2.4.2-RELEASE-amd64.img.tar.gz disk.raw
</code></pre></div></div>
<h2 id="step-2---upload-disk-image-to-google-cloud-storage-bucket">Step 2 - Upload Disk Image to Google Cloud Storage Bucket</h2>
<p>Using Google Cloud shell:</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>gsutil cp pfSense-CE-memstick-serial-2.4.2-RELEASE-amd64.img.tar.gz gs://YOUR_BUCKET
</code></pre></div></div>
<h2 id="step-3---create-a-new-image">Step 3 - Create a New Image</h2>
<p>Navigate to <a href="https://console.cloud.google.com">GCP Console</a> -> <a href="https://console.cloud.google.com/compute">Compute Engine</a> -> <a href="https://console.cloud.google.com/compute/imagesAdd">Create an image</a>.</p>
<ul>
<li>Name: <code class="language-plaintext highlighter-rouge">pfsense-242-installer</code></li>
<li>Source: “Cloud Storage file” and specify the path to <code class="language-plaintext highlighter-rouge">YOUR_BUCKET/pfSense-CE-memstick-serial-2.4.2-RELEASE-amd64.img.tar.gz</code></li>
</ul>
<h2 id="step-4---create-a-new-instance-for-the-installer">Step 4 - Create a New Instance for the Installer</h2>
<p>In this step a new instance will be created using the installer image to install pfSense on to a second disk.</p>
<p>Navigate to <a href="https://console.cloud.google.com">GCP Console</a> -> <a href="https://console.cloud.google.com/compute">Compute Engine</a> -> <a href="https://console.cloud.google.com/compute/instancesAdd">Create an instance</a>.</p>
<ul>
<li>Name: <code class="language-plaintext highlighter-rouge">pfsense-install-1</code></li>
<li>Boot disk: <code class="language-plaintext highlighter-rouge">pfsense-242-installer</code></li>
<li>Create a additional disk using the advanced drop down.
<ul>
<li>Name: <code class="language-plaintext highlighter-rouge">pfsense-242-clean-install</code></li>
<li>Source Type: None (blank disk)</li>
<li>Size: 20GB</li>
</ul>
</li>
</ul>
<p>Start the instance and wait for it to complete start-up.</p>
<h2 id="step-5---enable-and-connect-to-installer-serial-console">Step 5 - Enable and Connect to Installer Serial Console</h2>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>gcloud compute instances add-metadata --project=$PROJ --zone=$ZONE --metadata=serial-port-enable=1 pfsense-install-1
gcloud compute connect-to-serial-port --project=$PROJ --zone=$ZONE pfsense-install-1
</code></pre></div></div>
<h2 id="step-6---follow-default-install-and-shutdown-install-instance">Step 6 - Follow Default Install and Shutdown Install Instance</h2>
<p>The defaults are acceptable.</p>
<p>Stop the instance when the install is complete instead of rebooting. I typically stop the VM using the Google Cloud Console the last step of the installer when it asks to reboot.</p>
<h2 id="step-7---create-snapshot-of-the-newly-installed-disk">Step 7 - Create Snapshot of the Newly Installed Disk</h2>
<p>Navigate to <a href="https://console.cloud.google.com">GCP Console</a> -> <a href="https://console.cloud.google.com/compute">Compute Engine</a> -> <a href="https://console.cloud.google.com/compute/disks">Disk</a>.</p>
<p>Create a snapshot of the <code class="language-plaintext highlighter-rouge">pfsense-242-clean-install</code> so that it can be used as a boot disk for our final instance.</p>
<ul>
<li>Name: <code class="language-plaintext highlighter-rouge">pfsense-242-image</code></li>
</ul>
<h2 id="step-8---create-and-launch-new-instance-using-snapshot">Step 8 - Create and Launch New Instance Using Snapshot</h2>
<p>Navigate to <a href="https://console.cloud.google.com">GCP Console</a> -> <a href="https://console.cloud.google.com/compute">Compute Engine</a> -> <a href="https://console.cloud.google.com/compute/instancesAdd">Create an instance</a>.</p>
<ul>
<li>Name: <code class="language-plaintext highlighter-rouge">pfsense-1</code></li>
<li>Machine type: Pick something applicable to the work load.</li>
<li>Boot disk:
<ul>
<li>Snapshots: <code class="language-plaintext highlighter-rouge">pfsense-242-image</code></li>
<li>Size: 20GB (or something applicable for work load, typically only needed for logging).</li>
</ul>
</li>
<li>Under the advanced drop down -> networking tag create a <code class="language-plaintext highlighter-rouge">pfsense</code> Networking tag. This aides firewall configuration.</li>
</ul>
<p>Start the instance and wait for it to complete start-up.</p>
<h2 id="step-9---enable-and-connect-to-serial-console-for-initial-configuration">Step 9 - Enable and Connect to Serial Console For Initial Configuration</h2>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>gcloud compute instances add-metadata --project=$PROJ --zone=$ZONE --metadata=serial-port-enable=1 pfsense-1
gcloud compute connect-to-serial-port --project=$PROJ --zone=$ZONE pfsense-1
</code></pre></div></div>
<p>Follow the typical pfSense console configuration steps, defaults are pretty close to correct.</p>
<h2 id="step-10---fix-some-setting-to-work-with-google-cloud-platform">Step 10 - Fix Some Setting to Work with Google Cloud Platform</h2>
<p>Run a few commands to enable access to the on the WAN interface.</p>
<h3 id="temporarily-set-the-mtu-to-work-with-google-cloud">Temporarily set the MTU to work with Google Cloud</h3>
<p>Use the serial console to enter the shell by typing <em>8</em>. You should be greeted by the standard pfSense shell.</p>
<p>Google Cloud needs an <a href="https://cloud.google.com/vpn/docs/concepts/advanced#maximum_transfer_unit_mtu_considerations">MTU of 1460 or lower</a> due to administrative overhead in the Google network. Skipping this step will result in all kinds of bewildering networking issues.</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>ifconfig vtnet0 mtu 1460
</code></pre></div></div>
<p>This is temporary, but will get fixed permanently later in the WebUI later.</p>
<h3 id="disable-the-webui-referrer-check">Disable the WebUI referrer check</h3>
<p>Due to the way the GCP internal IPs work pfSense will throw an error. Fix this on the serial console shell with:</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>pfSsh.php playback disablereferercheck
</code></pre></div></div>
<h3 id="enable-ssh-daemon">Enable SSH Daemon</h3>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>pfSsh.php playback enablesshd
</code></pre></div></div>
<h3 id="disable-the-firewall">Disable the firewall</h3>
<p>Disable the firewall so that the SSH can be accessed and configured:</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>pfctl -d
</code></pre></div></div>
<p>Yes, this is a massive hole, I assume you know what you are doing. This will get re-enabled after the WebUI configuration.</p>
<h2 id="step-11---create-a-cloud-firewall-rule-to-allow-ssh-to-the-vm-instance">Step 11 - Create a Cloud Firewall Rule to Allow SSH to the VM Instance</h2>
<p>Navigate to <a href="https://console.cloud.google.com">GCP Console</a> -> <a href="https://console.cloud.google.com/networking/">VPC Network</a> -> <a href="https://console.cloud.google.com/networking/firewalls/add">Create a firewall rule</a>.</p>
<ul>
<li>Name: <code class="language-plaintext highlighter-rouge">allow-pfsense</code></li>
<li>Target tags: <code class="language-plaintext highlighter-rouge">pfsense</code> (from VM Instance creation)</li>
<li>Source IP ranges: <code class="language-plaintext highlighter-rouge">0.0.0.0/0</code></li>
<li>Specified protocols and ports: <code class="language-plaintext highlighter-rouge">tcp:22</code></li>
</ul>
<p>This will expose the ssh server in the pfSense instance to the Internet. Ensure good passwords are set later or better yet, only public-private keys are used.</p>
<p>Create the firewall rule and give it some time to propagate to the cloud firewalls.</p>
<h2 id="step-12---complete-the-webui-wizard-using-ssh-port-forwarding">Step 12 - Complete the WebUI Wizard using SSH port forwarding</h2>
<p>Setup port forwarding and open a ssh session. Default username is <strong>admin</strong> and default password is <strong>pfsense</strong>.</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>ssh admin@EXTERNAL_IP -L 8443:localhost:443
</code></pre></div></div>
<p>Connect to the pfSense WebUI @ <a href="https://localhost:8443/"><code class="language-plaintext highlighter-rouge">https://localhost:8443/</code></a>.</p>
<p>Accept the security exception, and complete the install wizard. Make sure the <strong>MTU of the WAN interface is set to <code class="language-plaintext highlighter-rouge">1460</code></strong> to work with Google Cloud.</p>
<p>When the installer concludes it will re-enable the firewall, you may need to run <code class="language-plaintext highlighter-rouge">pfctl -d</code> again from the serial console.</p>
<h2 id="step-13---reconnect-to-the-webui-and-finish">Step 13 - Reconnect to the WebUI and Finish</h2>
<h3 id="enable-and-configure-sshd-on-wan-interface">Enable and Configure sshd on WAN Interface</h3>
<p>I expose Secure Shell publicly and disable password logins to keep the system locked down:</p>
<ul>
<li>Add your Secure Shell public key to the admin or a new user
<ul>
<li>System -> User Manager -> Users -> <a href="https://localhost:8443/system_usermanager.php?act=edit&userid=0">Admin</a></li>
<li>Paste your SSH public key in the Authorized SSH Keys field.</li>
<li>Save</li>
</ul>
</li>
<li>Navigate to Firewall -> Rules -> <a href="https://localhost:8443/firewall_rules.php?if=wan">WAN</a>
<ul>
<li>Create a rule to allow ssh after we disable the anti-lockout rule.
<ul>
<li>Action: <code class="language-plaintext highlighter-rouge">Pass</code></li>
<li>Interface: <code class="language-plaintext highlighter-rouge">WAN</code></li>
<li>Protocol: <code class="language-plaintext highlighter-rouge">TCP</code></li>
<li>Source: <code class="language-plaintext highlighter-rouge">any</code></li>
<li>Destination: <code class="language-plaintext highlighter-rouge">WAN Address</code></li>
<li>Destination Port Range: <code class="language-plaintext highlighter-rouge">SSH (22)</code></li>
<li>Description: <code class="language-plaintext highlighter-rouge">Allow public SSH access to pfSense</code></li>
</ul>
</li>
<li>Save</li>
<li>Apply Changes</li>
</ul>
</li>
<li>Configure Secure Shell
<ul>
<li>Navigate to System -> Advanced -> <a href="https://localhost:8443/system_advanced_admin.php">Admin Access</a></li>
<li>Check: <code class="language-plaintext highlighter-rouge">Disable webConfigurator anti-lockout rule</code> as it exposes the WebUI (use SSH to forward the WebUI) to the Internet</li>
<li>Check: <code class="language-plaintext highlighter-rouge">Enable Secure Shell</code></li>
<li>Check: <code class="language-plaintext highlighter-rouge">Disable password login for Secure Shell (RSA/DSA key only)</code></li>
<li>Save</li>
<li>Re-connect the SSH port forwarding session as it likely exited and broke access to the WebUI when saved.</li>
</ul>
</li>
</ul>
<h3 id="accessing-the-webui-via-ssh">Accessing the WebUI via ssh</h3>
<p>I typically access the WebUI over ssh to keep the system secure:</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>ssh admin@PUBLIC_IP -fNL 8443:localhost:443
</code></pre></div></div>
<p>Then navigate to <a href="https://localhost:8443/"><code class="language-plaintext highlighter-rouge">https://localhost:8443/</code></a>. An adversary would have to find a hole in the SSH server or my private key to begin attacking the system.</p>
<p>You could expose the WebUI to the entire Internet, but <a href="https://www.netgate.com/blog/securely-managing-web-administered-devices.html">understand the risks</a>.</p>
<h2 id="wrapping-up">Wrapping Up</h2>
<ul>
<li>Delete the original <code class="language-plaintext highlighter-rouge">pfsense-install-1</code> installer VM instance, it’s not needed after the install snapshot has been created.</li>
<li>Verify the firewall is re-enabled and comes up correctly after reboot</li>
<li>Celebrate.</li>
<li>Get down to doing the real pfSense work.</li>
</ul>Kyle MannaHow to create a pfSense Instance using Google Compute EngineForce YouTube to Stream H.264/AVC with h264ify2017-09-10T00:00:00+00:002017-09-10T00:00:00+00:00https://blog.kylemanna.com/media/force-youtube-to-stream-h264-avc-with-h264ify<h2 id="youtube-streams-vp9-by-default">YouTube Streams VP9 by Default</h2>
<p>YouTube will stream videos in WebM format whenever it can. The WebM format selects VP9 for video encoding and Opus for audio encoding. These are open standards and are great, but they require CPU decoding and that’s not acceptable given my <a href="/media/video-acceleration-with-vp9-and-h265/">recent efforts</a> to leverage hardware video acceleration on my AMD RX 470 GPU.</p>
<p>You can verify this first hand by loading up a <a href="https://www.youtube.com/watch?v=aqz-KE-bpKQ&index=10&list=PL6B3937A5D230E335">high resolution video</a>, right-clicking the video while playing and enabling “Stats for Nerds”. It should look something like the following:</p>
<p><img src="https://i.imgur.com/4a7rVis.png" alt="Big Buck Bunny Playback Screenshot with Nerd Stats Enabled" /></p>
<p>At some point I expect software support to be added to enable VP9 on the RX 470 since the <a href="https://en.wikipedia.org/wiki/Unified_Video_Decoder#UVD_6">hardware appears to support it since UVD 6.3</a>. Until then, I want to downgrade to H264 (YouTube seems to refuse to support H265/HEVC) to leverage hardware decoding.</p>
<h2 id="enter-h264ify-for-chrome">Enter h264ify for Chrome</h2>
<p>The <a href="https://github.com/erkserkserks/h264ify">h264ify extension for Chrome</a> disables VP8/VP9 support to force the use of other codecs, most often H264. Install the extension and visit <a href="https://youtube.com/html5">youtube.com/html5</a> to verify missing support for WebM VP8 and WebM VP9.</p>
<p>Return to the video you tried earlier and verify that it is now using the H264/AVC codec and you should observe considerably lower CPU utilization.</p>
<p><img src="https://i.imgur.com/yI8m0Xp.png" alt="Big Buck Bunny Playback Screenshot with Nerd Stats Enabled and h264ify installed" /></p>
<h2 id="side-effects">Side Effects</h2>
<p>It appears that many of the 4k content on YouTube is just not available in H264/AVC format. This is unfortunate, but given the choice of higher CPU utilization for resolution I often won’t see (unless watching fullscreen), I’ll take the reduced CPU utilization.</p>
<h2 id="conclusion">Conclusion</h2>
<p>I’ll continue to use h264ify for better performance and reduced CPU utilization at the expense of lower resolution at times until hardware decoding support for VP9 is added to VA-API for my AMD RX 470.</p>Kyle MannaForce YouTube + Chrome to stream encodings that can be decoded by my Radeon RX 470Video Acceleration with VP9 and H.265/HEVC2017-08-30T00:00:00+00:002017-08-30T00:00:00+00:00https://blog.kylemanna.com/media/video-acceleration-with-vp9-and-h265<h2 id="motivation-for-transcoding-videos">Motivation For Transcoding Videos</h2>
<p>I have a collection of old videos from years ago that are encoded with all sorts of codecs but primarily MPEG2 and H.265/MPEG4 AVC. They were generated at different times and different places by different devices like old old cell phones to Go Pros. Today they spend most of their time occupying space on my harddrive. The primary motivation is to reduce disk consumption for what is largely data at rest without sacrificing playback performance or image quality.</p>
<p>Overall goals:</p>
<ul>
<li>Reduce file size</li>
<li>Maintain same visual quality which is subjective to my eye</li>
<li>Ensure that the videos can be played back with hardware accelerated decoders</li>
<li>No concern for encoding time</li>
</ul>
<p>To achieve the goals I’ll walk you through my codec selection process:</p>
<ol>
<li>Research what the common hardware I have is capable of</li>
<li>Verify that the software libraries actually support the hardware decoding</li>
<li>Evaluate hardware encoding support provided by Intel Quick Sync vs ffmpeg.</li>
<li>Transcode some videos</li>
<li>Review video quality, transcode time, etc</li>
<li>Repeat steps 3-5 until satisfied with encoder and decoder performance.</li>
</ol>
<h2 id="constraints">Constraints</h2>
<p>There are two constraints: available codecs and available hardware.</p>
<h3 id="codecs">Codecs</h3>
<p>The codec list for consideration:</p>
<ul>
<li><a href="https://en.wikipedia.org/wiki/High_Efficiency_Video_Coding">H.265 / HEVC</a> is a proprietary yet popular video codec aiming to reduce the bitrate to half about half that of H.264. It has patent licensing terms that require hardware manufacturer to pay per hardware device royalties for hardware codecs.</li>
<li><a href="https://en.wikipedia.org/wiki/VP9">VP9</a> is positioned as a competitor for H.265 developed by Google with no patent licensing restrictions.</li>
<li><a href="https://en.wikipedia.org/wiki/AOMedia_Video_1">AV1</a> is the forthcoming successor to VP9 also without patent licensing restrictions.</li>
</ul>
<h3 id="hardware">Hardware</h3>
<p>My current computers are a Dell Precision 5510 with an <a href="http://ark.intel.com/products/89608/Intel-Xeon-Processor-E3-1505M-v5-8M-Cache-2_80-GHz">Intel E3-1505M v5</a> and a desktop with an <a href="https://ark.intel.com/products/97129/Intel-Core-i7-7700K-Processor-8M-Cache-up-to-4_50-GHz">Intel i7-7700k</a>. Both feature Intel’s fixed function video acceleration hardware called <a href="https://en.wikipedia.org/wiki/Intel_Quick_Sync_Video">Intel Quick Sync</a>, but the Skylake processor only has H265 decoding on the integrated processor while the Kaby Lake processor support hardware encode and decode of H.265 and VP9.</p>
<p>The desktop PC has a <a href="http://www.amd.com/en-us/products/graphics/radeon-rx-series/radeon-rx-470">AMD Radeon RX 470</a> as the primary graphics adapter. The <a href="https://en.wikipedia.org/wiki/Unified_Video_Decoder#UVD_6">Unified Video Decoder v6.3</a> supports H.265 and VP9 (more on this later) as well.</p>
<p>None of the hardware has any support AV1, and is eliminated as a consideration. The software codec is still under heavy development and hardware support likely won’t follow for for at least 2 years.</p>
<h2 id="adventures-enabling-hardware-decoding">Adventures Enabling Hardware Decoding</h2>
<p>Did some testing with mpv on my desktop and laptop. VA-API seems to be the better way to go and was able to leverage my AMD RX 470 or Intel Kaby Lake Quick Sync and Intel Skylake Quick sync for video decoding. HEVC was supported by all my computers, but VP9 support was missing on the Skylake laptop.</p>
<p>This quickly ruled out VP9 support. HEVC support seems much more accessible these days. I assume VP9 and later AV1 will catch-up. Until then, my hardware is going H.265/HEVC route.</p>
<p>For reference, the simplest way to evaluate what <code class="language-plaintext highlighter-rouge">mpv</code> is using for video decoding is with the <code class="language-plaintext highlighter-rouge">mpv --msg-level=vd=debug</code></p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>$ mpv --msg-level=vd=debug Sintel.2010.1080p.mkv
Playing: Sintel.2010.1080p.mkv
(+) Video --vid=1 (h264 1920x818 24.000fps)
(+) Audio --aid=1 --alang=eng 'AC3 5.1 @ 640 Kbps' (ac3 6ch 48000Hz)
Subs --sid=1 --slang=ger (subrip)
Subs --sid=2 --slang=eng (subrip)
Subs --sid=3 --slang=spa (subrip)
Subs --sid=4 --slang=fre (subrip)
Subs --sid=5 --slang=ita (subrip)
Subs --sid=6 --slang=dut (subrip)
Subs --sid=7 --slang=pol (subrip)
Subs --sid=8 --slang=por (subrip)
Subs --sid=9 --slang=rus (subrip)
Subs --sid=10 --slang=vie (subrip)
[vd] Container reported FPS: 24.000002
[vd] Codec list:
[vd] h264 - H.264 / AVC / MPEG-4 AVC / MPEG-4 part 10
[vd] h264_cuvid (h264) - Nvidia CUVID H264 decoder
[vd] Opening video decoder h264
[vd] Probing 'vaapi'...
[vd] Trying hardware decoding.
[vd] Selected video codec: h264 (H.264 / AVC / MPEG-4 AVC / MPEG-4 part 10)
AO: [pulse] 48000Hz 5.1(side) 6ch float
[vd] Pixel formats supported by decoder: vdpau vaapi_vld yuv420p
[vd] Codec profile: High (0x64)
[vd] Requesting pixfmt 'vaapi_vld' from decoder.
Using hardware decoding (vaapi).
[vd] Decoder format: 1920x818 [0:1] vaapi[nv12] bt.709/auto/auto/limited CL=mpeg2/4/h264
[vd] Using container aspect ratio.
VO: [vaapi] 1920x818 vaapi[nv12]
AV: 00:00:02 / 00:14:48 (0%) A-V: 0.000
[vd] Uninit video.
</code></pre></div></div>
<p>For more debugging query VA-API with <code class="language-plaintext highlighter-rouge">vainfo</code>:</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>$ vainfo
libva info: VA-API version 0.40.0
libva info: va_getDriverName() returns 0
libva info: Trying to open /usr/lib/dri/radeonsi_drv_video.so
libva info: Found init function __vaDriverInit_0_40
libva info: va_openDriver() returns 0
vainfo: VA-API version: 0.40 (libva )
vainfo: Driver version: mesa gallium vaapi
vainfo: Supported profile and entrypoints
VAProfileMPEG2Simple : VAEntrypointVLD
VAProfileMPEG2Main : VAEntrypointVLD
VAProfileVC1Simple : VAEntrypointVLD
VAProfileVC1Main : VAEntrypointVLD
VAProfileVC1Advanced : VAEntrypointVLD
VAProfileH264ConstrainedBaseline: VAEntrypointVLD
VAProfileH264ConstrainedBaseline: VAEntrypointEncSlice
VAProfileH264Main : VAEntrypointVLD
VAProfileH264Main : VAEntrypointEncSlice
VAProfileH264High : VAEntrypointVLD
VAProfileH264High : VAEntrypointEncSlice
VAProfileHEVCMain : VAEntrypointVLD
VAProfileHEVCMain10 : VAEntrypointVLD
VAProfileNone : VAEntrypointVideoProc
</code></pre></div></div>
<p>And the more verbose vdpau method:</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>$ vdpauinfo
display: :1 screen: 0
API version: 1
Information string: G3DVL VDPAU Driver Shared Library version 1.0
Video surface:
name width height types
-------------------------------------------
420 16384 16384 NV12 YV12
422 16384 16384 UYVY YUYV
444 16384 16384 Y8U8V8A8 V8U8Y8A8
Decoder capabilities:
name level macbs width height
----------------------------------------------------
MPEG1 --- not supported ---
MPEG2_SIMPLE 3 65536 4096 4096
MPEG2_MAIN 3 65536 4096 4096
H264_BASELINE 52 65536 4096 4096
H264_MAIN 52 65536 4096 4096
H264_HIGH 52 65536 4096 4096
VC1_SIMPLE 1 65536 4096 4096
VC1_MAIN 2 65536 4096 4096
VC1_ADVANCED 4 65536 4096 4096
MPEG4_PART2_SP 3 65536 4096 4096
MPEG4_PART2_ASP 5 65536 4096 4096
DIVX4_QMOBILE --- not supported ---
DIVX4_MOBILE --- not supported ---
DIVX4_HOME_THEATER --- not supported ---
DIVX4_HD_1080P --- not supported ---
DIVX5_QMOBILE --- not supported ---
DIVX5_MOBILE --- not supported ---
DIVX5_HOME_THEATER --- not supported ---
DIVX5_HD_1080P --- not supported ---
H264_CONSTRAINED_BASELINE 0 65536 4096 4096
H264_EXTENDED --- not supported ---
H264_PROGRESSIVE_HIGH --- not supported ---
H264_CONSTRAINED_HIGH --- not supported ---
H264_HIGH_444_PREDICTIVE --- not supported ---
HEVC_MAIN 186 65536 4096 4096
HEVC_MAIN_10 186 65536 4096 4096
HEVC_MAIN_STILL --- not supported ---
HEVC_MAIN_12 --- not supported ---
HEVC_MAIN_444 --- not supported ---
Output surface:
name width height nat types
----------------------------------------------------
B8G8R8A8 16384 16384 y NV12 YV12 UYVY YUYV Y8U8V8A8 V8U8Y8A8 A8I8 I8A8
R8G8B8A8 16384 16384 y NV12 YV12 UYVY YUYV Y8U8V8A8 V8U8Y8A8 A8I8 I8A8
R10G10B10A2 16384 16384 y NV12 YV12 UYVY YUYV Y8U8V8A8 V8U8Y8A8 A8I8 I8A8
B10G10R10A2 16384 16384 y NV12 YV12 UYVY YUYV Y8U8V8A8 V8U8Y8A8 A8I8 I8A8
Bitmap surface:
name width height
------------------------------
B8G8R8A8 16384 16384
R8G8B8A8 16384 16384
R10G10B10A2 16384 16384
B10G10R10A2 16384 16384
A8 16384 16384
Video mixer:
feature name sup
------------------------------------
DEINTERLACE_TEMPORAL y
DEINTERLACE_TEMPORAL_SPATIAL -
INVERSE_TELECINE -
NOISE_REDUCTION y
SHARPNESS y
LUMA_KEY y
HIGH QUALITY SCALING - L1 y
HIGH QUALITY SCALING - L2 -
HIGH QUALITY SCALING - L3 -
HIGH QUALITY SCALING - L4 -
HIGH QUALITY SCALING - L5 -
HIGH QUALITY SCALING - L6 -
HIGH QUALITY SCALING - L7 -
HIGH QUALITY SCALING - L8 -
HIGH QUALITY SCALING - L9 -
parameter name sup min max
-----------------------------------------------------
VIDEO_SURFACE_WIDTH y 48 4096
VIDEO_SURFACE_HEIGHT y 48 4096
CHROMA_TYPE y
LAYERS y 0 4
attribute name sup min max
-----------------------------------------------------
BACKGROUND_COLOR y
CSC_MATRIX y
NOISE_REDUCTION_LEVEL y 0.00 1.00
SHARPNESS_LEVEL y -1.00 1.00
LUMA_KEY_MIN_LUMA y
LUMA_KEY_MAX_LUMA y
</code></pre></div></div>
<p>After some experimentation my <code class="language-plaintext highlighter-rouge">~/.config/mpv/mpv.conf</code> for both the RX 470 machine + Skylake laptop was:</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>hwdec=vaapi
vo=vaapi
msg-level=vd=debug
</code></pre></div></div>
<p>After tweaking <code class="language-plaintext highlighter-rouge">mpv</code> I confirmed that Chromium was leveraging VA-API where possible. But it was not by default. This was resolved with the <a href="https://aur.archlinux.org/packages/chromium-vaapi/">chromium-vappi AUR package</a> on Arch Linux and enabling the <code class="language-plaintext highlighter-rouge">chrome://flags/#enable-accelerated-video</code> flag.</p>
<p><img src="https://i.imgur.com/cIN7yIZ.png" alt="Chromium Flags page" title="Chromium Flags page" /></p>
<p><img src="https://i.imgur.com/uomO8uC.png" alt="Chromium GPU page" title="Chromium GPU page" /></p>
<p>Test playback with a CPU process monitor open and find a 4k 60FPS YouTube Video. I think I saw CPU drop from something like 100% (8 cores = 800%) to sub 20% with video acceleration enabled.</p>
<p>With a little bit of leg work I had both Chromium and <code class="language-plaintext highlighter-rouge">mpv</code> using hardware accelerated H.265/HEVC decoding on all my machines.</p>
<h2 id="encoding">Encoding</h2>
<p>This is a very deep topic after a number of experiments that would take to long to detail here and aren’t exhaustive enough to draw a real conclusion I settled on a <code class="language-plaintext highlighter-rouge">ffmpeg</code> command line below for re-encoding videos (week later still running…)</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>crf=26
preset=medium
/usr/bin/time -v ffmpeg \
-vaapi_device /dev/dri/renderD129 -hwaccel vaapi"
-i "${src}" \
-vcodec hevc -crf ${crf} -preset ${preset} \
-acodec copy \
-threads 7 \
-y -f matroska \
-benchmark \
"${src}.crf${crf}.${preset}.mkv"
</code></pre></div></div>
<p>I could spend weeks researching this and tweaking it more, but hardware decoding from the Intel Kaby Lake processor and software encoding using 7 cores produced the best image quality and smallest file size.</p>
<p>I chose single pass encoding as it seems to run considerably faster then 2 pass encoding. The only upside to two pass encoding that I could discern (above all the noise on the Internet) from first hand experience was that it would only allow me have an exact file size. Since I’m not burning my videos to fixed size media like DVDs or Blu-Ray I see no value in 2 pass encoding.</p>
<p>Intel QuickSync yielded approximately 2.5x faster encoding support, but the resulting file was about 30% bigger with a little more image noise. I’m sure I could fix this, but didn’t have the patience to dig any deeper. The command line I used was approximately the following:</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>/usr/bin/time -v ffmpeg \
-hwaccel vaapi -i "${src}" \
-vaapi_device /dev/dri/renderD129 -vf 'format=nv12,hwupload' \
-vcodec hevc_vaapi -crf ${crf} \
-acodec copy \
-threads 8 -y -f matroska \
"${src}.mkv"
</code></pre></div></div>
<p>A brief experiment with VP9 encoding just to satisfied my curiosity resulted in larger files. Some quick Google searches for VP9 support showed that it wasn’t up to speed with modern H.265/HEVC implementations and matched my observatoins. Rumor is that Google’s AV1 codec will close the gap in codec performance with H.265/HEVC, so this is something I’ll look for when I buy a GPU again in maybe 2 years. I don’t see any value in pursuing VP9 as it produces larger file sizes at the same image quality and has poor software+hardware support.</p>
<h2 id="conclusion">Conclusion</h2>
<p>It appears that taking some time to re-encode your MPEG4 or H.264 videos will slash your video file size at least in half or save you 30% at worst. Modern hardware decoding makes it trivial to decode these videos without taking a CPU or power hit.</p>Kyle MannaExperiments with Intel Quick Sync and ffmeg to re-encode old videosMy Favorite Linux Terminal Font: Google Noto Mono2017-05-27T00:00:00+00:002017-05-27T00:00:00+00:00https://blog.kylemanna.com/cli/favorite-linux-terminal-font-google-noto-mono<h2 id="monospace-before-and-after-4k-uhd">Monospace Before and after 4K UHD</h2>
<p>For years I’ve spent the better part of my day staring at terminals for my day to day work. I’ve used <a href="https://en.wikipedia.org/wiki/GNOME_Terminal">GNOME Terminal</a> with <a href="http://terminus-font.sourceforge.net/">Terminus font</a> for years without issue on 1600x1200 and 1080p displays.</p>
<p>Recently I bought two <a href="http://www.lg.com/us/monitors/lg-27UD58-B-4k-uhd-led-monitor">LG 27UD58 monitors</a> for my desktop and swapped out my laptop display with the 4K UHD display offered by Dell as UHD slowly stole my heart with its sharpness and increased screen real estate.</p>
<p>Immediately the ability to scale the UI in GNOME became important and the Terminus font quickly looked odd and felt uncomfortable. I could never find a font size that felt natural like Terminus did for years before UHD.</p>
<p>I dug around the web reading various blog posts about monospace fonts, but none of the popular fonts quite felt right. I always installed <a href="https://www.google.com/get/noto/">Google’s Noto fonts</a> to handle international characters on <a href="https://www.archlinux.org/packages/extra/any/noto-fonts/">Arch Linux</a> but never gave it much more thought for anything beyond web page rendering.</p>
<h2 id="enter-google-noto-mono-regular">Enter Google Noto Mono Regular</h2>
<p>One day while wrestling with the awkward font sizes in GNOME terminal I stumbled on <a href="https://www.google.com/get/noto/#mono-mono">Noto Mono</a> and instantly fell in love. I can’t remember if I was on my desktop or laptop when I first discovered the font, but I quickly reviewed the font on the other machine and was delighted. The font looked amazing on both machines at size 11. Not too tall, not too wide and nothing weird.</p>
<p>After a few months of running this font, I figured it’s now my turn to share with the world so this underrated font gets some more attention. That said, <a href="https://www.google.com/get/noto/#mono-mono">go check it out</a>.</p>
<h2 id="screenshots">Screenshots</h2>
<p>To try and demonstrate the differences of the font, I setup a series of GNOME terminals with a simple directory listing of the Linux kernel that I had lying around on both my machines in the default terminal size of 80x24 for comparison.</p>
<p>The comparison isn’t representative of how I normally use my computer, but highlights the differences between the fonts on the same screen. Typically I tile windows like the web browser and data sheets and dedicate one monitor to a full screen tmux session on dual display setups or a virtual desktop when using my laptop and within tmux I tile the panes to my heart’s content.</p>
<p>That said, this isn’t about terminal productivity setups, it’s about monospace fonts. So, see below for font setups on my desktop and laptop.</p>
<h3 id="dell-precision-5510-uhd-display">Dell Precision 5510 UHD Display</h3>
<p><a href="https://i.imgur.com/aJ9awXn.png"><img src="https://i.imgur.com/aJ9awXn.png" alt="Dell Precision 5510 UHD font comparison" title="laptop font comparison" /></a></p>
<ul>
<li>Top left: Noto Mono 11</li>
<li>Top right: Terminus 12</li>
<li>Bottom left: Terminus 11</li>
</ul>
<p>On the laptop Terminus felt too “tall” or too small. I could never get the balance right. The difference based on font size of the <code class="language-plaintext highlighter-rouge">❯❯❯</code> characters in my terminal also bothered me. Noto Mono is better on all fronts except it lacks the “slash” through the zero character for disambiguation with “O” character (<code class="language-plaintext highlighter-rouge">0</code> vs <code class="language-plaintext highlighter-rouge">O</code>). But, typically my brain fills seamlessly fills in the context when reading, so it doesn’t bother me much.</p>
<h3 id="lg-27ud58-uhd-display">LG 27UD58 UHD Display</h3>
<p><a href="https://i.imgur.com/2tVJu7R.png"><img src="https://i.imgur.com/2tVJu7R.png" alt="LG 27UD58 UHD Display font comparison" title="desktop font comparison" /></a></p>
<ul>
<li>Top left: Noto Mono 11</li>
<li>Bottom left: Terminus 13</li>
<li>Top right: Terminus 11</li>
<li>Bottom right: Terminus 12</li>
</ul>
<p>Similar comments as the laptop, but on the desktop but Noto Mono 11 has a little more width to the fonts making them easier to read. Also, note how dramatically the different the font sizes are with Terminus. On the laptop and desktop Noto Mono looks perfect at size 11, but Terminus needs to be about size 11.5 on the laptop and size 13 on the desktop. Why the size discrepancy if I have the desktop scaled to about the same visual settings for everything else? Well, it probably has to do with Terminus being a “fixed width bitmap font” and doesn’t scale well. I don’t know much about fonts, but it sounds like it was designed to be pixel perfect on certain displays, but with the proliferation of 4K UHD this isn’t quite necessary or appropriate anymore. That’s just my guess though, I’m not a font guy.</p>
<h2 id="favorite-font">Favorite Font</h2>
<p>I don’t consider myself a font obsessed person and my subjective requirements are simple: I want a font that is easy to read and not distracting on my desktop or laptop with simple licensing (for easy distribution). Terminus was that font for years, but does not work well with UHD and desktop scaling. Google’s Noto Mono font now fits the bill. Looking back on it, I may have used Noto Mono in place of Terminus years ago had it existed and I merely discovered it first.</p>Kyle MannaTerminus font doesn't quite look right with desktop scaling and 4K UHD displays.