Jekyll2021-07-10T10:28:38+00:00https://odd-one-out.serek.eu/Odd One OutMy blog about geeky projectsPoul Serekhttps://Ox3.serek.eu/about/Executing AT commands from OpenWRT webinterface2020-10-31T00:00:00+00:002020-10-31T14:36:06+00:00https://odd-one-out.serek.eu/code/execute-at-commands-openwrt-webinterface
<p>I wanted a quick way of executing AT commands to my 4G modem in my OpenWRT router and reading the results. To do this I installed</p>
<div class="language-terminal highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="gp">root@OpenWrt:~#</span> opkg update
<span class="gp">root@OpenWrt:~#</span> opkg <span class="nb">install </span>coreutils-stty
</code></pre></div></div>
<p>Then I created this script, execAT.sh, and added to OpenWRT:</p>
<div class="highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="c">#!/bin/sh</span>
<span class="c"># First parameter: Device</span>
<span class="c"># Second parameter: AT command to execute</span>
<span class="c"># Example sh execAT.sh ttyUSB2 at!gstatus?</span>
<span class="c"># set up modem device to translate outgoing \n into \r\n</span>
<span class="nb">stty</span> <span class="nt">-F</span> /dev/<span class="nv">$1</span> 9600 <span class="nt">-echo</span> igncr icanon onlcr
<span class="c"># Open modem for reading and writing</span>
<span class="nb">exec </span>5</dev/<span class="nv">$1</span>
<span class="nb">exec </span>6>/dev/<span class="nv">$1</span>
<span class="nb">echo</span> <span class="nv">$2</span> <span class="o">></span>&6
<span class="c"># Remove the echo and the blank line</span>
<span class="nb">read</span> <&5
<span class="nb">read</span> <&5
<span class="nv">FLAG</span><span class="o">=</span><span class="s2">"GO"</span>
<span class="nv">ZeroCounter</span><span class="o">=</span>0
<span class="c">#READING FROM SERIAL</span>
<span class="k">while</span> <span class="o">[</span> <span class="s2">"</span><span class="k">${</span><span class="nv">FLAG</span><span class="k">}</span><span class="s2">"</span> <span class="o">==</span> <span class="s2">"GO"</span> <span class="o">]</span><span class="p">;</span> <span class="k">do
</span><span class="nb">read</span> <span class="nt">-t</span> 1 RESPONSE <&5
<span class="nb">echo</span> <span class="nt">-e</span> <span class="s2">"</span><span class="nv">$RESPONSE</span><span class="s2">"</span>
<span class="k">if</span> <span class="o">[</span> <span class="s2">"</span><span class="k">${#</span><span class="nv">RESPONSE</span><span class="k">}</span><span class="s2">"</span> <span class="nt">-eq</span> 0 <span class="o">]</span><span class="p">;</span>
<span class="k">then
</span><span class="nv">ZeroCounter</span><span class="o">=</span><span class="k">$((</span>ZeroCounter+1<span class="k">))</span>
<span class="k">else
</span><span class="nv">ZeroCounter</span><span class="o">=</span>0 <span class="c"># Reset counter</span>
<span class="k">fi</span>
<span class="c"># If we get and OK then quit; or we we get an empty response 3 times in a row</span>
<span class="k">if</span> <span class="o">[</span> <span class="s2">"</span><span class="k">${</span><span class="nv">RESPONSE</span><span class="k">}</span><span class="s2">"</span> <span class="o">==</span> <span class="s2">"OK"</span> <span class="o">]</span> <span class="o">||</span> <span class="o">[</span> <span class="s2">"</span><span class="k">${</span><span class="nv">ZeroCounter</span><span class="k">}</span><span class="s2">"</span> <span class="o">==</span> <span class="s2">"3"</span> <span class="o">]</span><span class="p">;</span>
<span class="k">then
</span><span class="nv">FLAG</span><span class="o">=</span><span class="s2">"EXIT"</span>
<span class="k">fi
done</span>
<span class="c"># Close the connections</span>
<span class="nb">exec </span>5<&-
<span class="nb">exec </span>6>&-
</code></pre></div></div>
<p>I tested it from the commandline first:</p>
<div class="highlighter-rouge"><div class="highlight"><pre class="highlight"><code>root@OpenWrt:~# chmod +x execAT.sh
root@OpenWrt:~# sh execAT.sh ttyUSB2 ati
Model: EM7455
Revision: SWI9X30C_02.32.11.00 r8042 CARMD-EV-FRMWR2 2019/05/15 21:52:20
MEID: 35399007042761
IMEI: 353990070427617
IMEI SV: 19
FSN: LF611310230210
+GCAP: +CGSM
OK
</code></pre></div></div>
<p>Then I add a plugin to execute shell scripts from the user interface and read the results:</p>
<div class="highlighter-rouge"><div class="highlight"><pre class="highlight"><code>root@OpenWrt:~# opkg install luci-app-commands
</code></pre></div></div>
<p>And then I just configure the command in OpewWRT and can execute it directly from the interface and read the response:</p>
<figure>
<noscript><img src="/assets/images/execute-at-commands-openwrt-webinterface.png" /></noscript>
<img src="data:image/gif;base64,R0lGODlhAQABAAAAACH5BAEKAAEALAAAAAABAAEAAAICTAEAOw==" data-src="/assets/images/execute-at-commands-openwrt-webinterface.png" alt="" class="lazyload fade-in" />
</figure>
<p><a href="https://odd-one-out.serek.eu/code/execute-at-commands-openwrt-webinterface/" rel="nofollow">Executing AT commands from OpenWRT webinterface</a> was originally published on Odd One Out.</p>
Poul Serekhttps://Ox3.serek.eu/about/Executing AT commands from OpenWRT webinterfaceWireless charging a Kindle Paperwhite2019-01-05T11:28:08+00:002019-01-04T23:00:00+00:00https://odd-one-out.serek.eu/projects/wireless-charging-kindle-paperwhite
<p><img src="/assets/images/wireless-charging-kindle-paperwhite-feature.jpg" alt="" /></p>
<p>In my quest to fully eliminate any micro-usb devices I stumbled upon my old Kindle Paperwhite which is one of my last devices to use micro-usb. A neat little trick if you are using a case with the Kindle is to purchase a wireless charging module to add between the Kindle and the case.</p>
<figure>
<noscript><img src="/assets/images/wireless-charging-kindle-paperwhite-receiver.jpg" /></noscript>
<img src="data:image/gif;base64,R0lGODlhAQABAAAAACH5BAEKAAEALAAAAAABAAEAAAICTAEAOw==" data-src="/assets/images/wireless-charging-kindle-paperwhite-receiver.jpg" alt="" class="lazyload fade-in" />
<figcaption>A micro-usb wireless receiver</figcaption>
</figure>
<p>For just 13 USD for the wireless charging receiver and another 14 USD for the wireless charger it works great! It does make the Kindle a tiny bit more bulky, but nothing I notice using it day to day.</p>
<figure>
<noscript><img src="/assets/images/wireless-charging-kindle-paperwhite-case.jpg" /></noscript>
<img src="data:image/gif;base64,R0lGODlhAQABAAAAACH5BAEKAAEALAAAAAABAAEAAAICTAEAOw==" data-src="/assets/images/wireless-charging-kindle-paperwhite-case.jpg" alt="" class="lazyload fade-in" />
<figcaption>A micro-usb wireless receiver attached to the Kindle Paperwhite</figcaption>
</figure>
<p>It takes a few tries the first time to place the Kindle correctly on the wireless charging pad since it is not completely center. A tip - use the micro-usb connector as a point to center it.</p>
<figure>
<noscript><img src="/assets/images/wireless-charging-kindle-paperwhite-receiver-attached.jpg" /></noscript>
<img src="data:image/gif;base64,R0lGODlhAQABAAAAACH5BAEKAAEALAAAAAABAAEAAAICTAEAOw==" data-src="/assets/images/wireless-charging-kindle-paperwhite-receiver-attached.jpg" alt="" class="lazyload fade-in" />
<figcaption>A micro-usb wireless receiver attached to the Kindle Paperwhite. As it is not completely center it takes a few tries the first time charging it.</figcaption>
</figure>
<h2 id="parts">Parts</h2>
<!-- Affiliate disclamer removed because of GDPR and removal of affiliate links -->
<div class="notice"><h3>Disclaimer</h3>This post contains links to Amazon where I get a small commission if you purchase anything after clicking on these links - at no extra cost to you! But only if you have explicitly consented to this. I have purchased all the mentioned products myself and I only link to products that I believe are the best for my readers. If you want to help out even more, take a look <a href="/support/">here</a>.</div>
<ul>
<li><a href="https://www.amazon.com/gp/product/B01DLYF0Q0/" rel="nofollow" data-amazon-asin="[us][ca][uk][de][es][it][fr]B01DLYF0Q0">Nillkin Wireless Charger Receiver</a></li>
<li><a href="https://www.amazon.com/gp/product/B0756Z8X82/" rel="nofollow" data-amazon-asin="[us][ca][uk][de][es][it][fr]B0756Z8X82">Anker Power port wireless 5 pad</a></li>
<li>Any 1 amp USB charger (using a 0.5 amp charger failing to charge it through the case)</li>
</ul>
<p>This trick should also work on phones or similar devices that use a case.</p>
<p><a href="https://odd-one-out.serek.eu/projects/wireless-charging-kindle-paperwhite/" rel="nofollow">Wireless charging a Kindle Paperwhite</a> was originally published on Odd One Out.</p>
Poul Serekhttps://Ox3.serek.eu/about/Removing the need to old micro-usb cables by retrofitting wireless charging to my old Kindle Paperwhite e-ink reader!Server idling at 9 watt2018-09-23T00:00:00+00:002018-09-23T22:24:00+00:00https://odd-one-out.serek.eu/projects/low-power-server
<p><img src="/assets/images/low-power-server-feature.jpg" alt="" /></p>
<p>I was on the lookout for a proper low power server, which meant</p>
<ul>
<li>Consuming less than 10 watts idle</li>
<li>Silent</li>
<li>ECC memory</li>
<li>Several PCIe expansion slots</li>
<li>Relatively small</li>
</ul>
<p>I did own a Kaby lake Intel NUC which was passively cooled, but while the idle power consumption was great at around 6 watts, it lacked room for expansion (only a single M.2 and SATA slot, no PCIe slots) and did not support ECC memory. The idle temperatures was also too high, my M.2 hard disk was around 38 degrees Celsius idle with a room temperature of 25 degrees.</p>
<p>I ended up with a small micro-atx motherboard with a case not much larger than the board itself.</p>
<figure>
<noscript><img src="/assets/images/low-power-server-case.jpg" /></noscript>
<img src="data:image/gif;base64,R0lGODlhAQABAAAAACH5BAEKAAEALAAAAAABAAEAAAICTAEAOw==" data-src="/assets/images/low-power-server-case.jpg" alt="" class="lazyload fade-in" />
<figcaption>The Cooltek C2 case is a small case with plenty of room for a large silent CPU cooler.</figcaption>
</figure>
<p>The setup consists of</p>
<!-- Affiliate disclamer removed because of GDPR and removal of affiliate links -->
<div class="notice"><h3>Disclaimer</h3>This post contains links to Amazon where I get a small commission if you purchase anything after clicking on these links - at no extra cost to you! But only if you have explicitly consented to this. I have purchased all the mentioned products myself and I only link to products that I believe are the best for my readers. If you want to help out even more, take a look <a href="/support/">here</a>.</div>
<ul>
<li><a href="http://www.fujitsu.com/global/products/computing/peripheral/mainboards/extended-lifecycle-main/pmod-177972.html">Fujitsu D3417-B2 motherboard</a></li>
<li><a href="https://www.amazon.de/dp/B07BG6BMY7/" rel="nofollow" data-amazon-asin="[uk][de][es][it][fr]B07BG6BMY7[us][ca]">A single Samsung 16 GB ECC memory module (M391A2K43BB1-CRC)</a></li>
<li><a href="https://www.amazon.com/dp/B01NADEVZI/" rel="nofollow" data-amazon-asin="[us][ca][uk][de][es][it][fr]B01NADEVZI">Intel Pentium G4600</a></li>
<li><a href="https://www.amazon.com/dp/B00XUVGLEU/" rel="nofollow" data-amazon-asin="[us][ca][uk][de][es][it][fr]B00XUVGLEU">Noctua NH-D15S CPU cooler</a> with an included 140mm fan</li>
<li><a href="https://www.amazon.com/dp/B00AED7XFI/" rel="nofollow" data-amazon-asin="[us][ca][uk][de][es][it][fr]B00AED7XFI">Noctua NF-A15 PWM 150mm case fan</a></li>
<li><a href="https://www.aliexpress.com/item/Jonsbo-C2-Black-C2BK-HTPC-ITX-Mini-computer-case-in-aluminum-support-3-5-HDD-USB3/32718835069.html" rel="nofollow">Cooltek C2 case</a></li>
<li><a href="https://www.amazon.com/dp/B005TWE6B8/" rel="nofollow" data-amazon-asin="[us][ca][uk][de][es][it][fr]B005TWE6B8">PicoPSU 160XT</a> power supply</li>
<li><a href="https://www.amazon.com/dp/B005TWE6B8/" rel="nofollow" data-amazon-asin="[us][ca][uk][de][es][it][fr]B001W3UYLY">60 watt power brick</a></li>
<li>3 M.2 SATA hard disks (single Transcend MTS400 32GB boot disk and two <a href="https://www.amazon.com/dp/B01L80DH1Y/" rel="nofollow" data-amazon-asin="[us][ca]][uk][de][es][it][fr]B01L80DH1Y">1 terabyte Crucial MX300</a></li>
<li>Two <a href="https://www.amazon.com/dp/B073RHHYCM/" rel="nofollow" data-amazon-asin="[us][ca]][uk][de][es][it][fr]B073RHHYCM">Ekwaterblocks EK-RAM M.2 Nvme Heatsink Kit Black</a></li>
<li><a href="https://www.amazon.com/dp/B01LZQGZ95/" rel="nofollow" data-amazon-asin="[us][ca]][uk][de][es][it][fr]B01LZQGZ95">IOCrest 3.5” 2.5” HDD / SSD Mounting Bracket for PCI Slot</a></li>
<li><a href="https://www.amazon.de/dp/B00PIGON4U/" rel="nofollow" data-amazon-asin="[uk][de][es][it][fr]B00PIGON4U[us][ca]B00J4UYAXK">DELOCK Converter 4x SATA 7pin > 4x M.2 NGFF</a></li>
</ul>
<p>Which resulted in a server which is silent and consumes only 8-9 watts idle with 3 hard disks running Ubuntu 16.04.3 server and hosting 10 LXD containers. The only thing connected to the server is the power brick and an ethernet cable for the network. No mouse, keyboard or monitor since these consume some additional power. Some details of the build:</p>
<h2 id="power-consumption">Power consumption</h2>
<p>The PicoPSU with a low wattage power brick does the trick at maintaining a good power efficiency at very low power consumption. When switching to a 192 watt power brick the idle power is around 11-12 watts so the choice of power brick does matter. I have a feeling that it is possible to find an even more efficient combination than I have.</p>
<h2 id="heat">Heat</h2>
<p>The the 140mm and 150mm fans does a very good job at keeping the whole setup cool. The case fan at the bottom draws air inside the case and directs it directly at the hard disks which have added heatsinks.</p>
<figure>
<noscript><img src="/assets/images/low-power-server-hard-disks.jpg" /></noscript>
<img src="data:image/gif;base64,R0lGODlhAQABAAAAACH5BAEKAAEALAAAAAABAAEAAAICTAEAOw==" data-src="/assets/images/low-power-server-hard-disks.jpg" alt="" class="lazyload fade-in" />
<figcaption>3.5 inch SATA adapter capable of mounting 4 M.2 hard disks.</figcaption>
</figure>
<p>The massive CPU cooler takes up half the space in the small case and does a good job at keeping the CPU cool. The hard disks stays at around 33 degrees Celsius when idle and the CPU at 30 degrees.</p>
<figure>
<noscript><img src="/assets/images/low-power-server-cooler.jpg" /></noscript>
<img src="data:image/gif;base64,R0lGODlhAQABAAAAACH5BAEKAAEALAAAAAABAAEAAAICTAEAOw==" data-src="/assets/images/low-power-server-cooler.jpg" alt="" class="lazyload fade-in" />
<figcaption>Massive cooler that barely fits - and only because I use an external power supply.A low profile cooler and a regular ATX power supply could have worked here instead, but I went for a more silent build.</figcaption>
</figure>
<p>It should be possible to disconnect one of the fans, maybe even both, for an even lower idle power consumption, but I prefer to have some additional cooling.</p>
<h2 id="noise">Noise</h2>
<p>With two large fans and a massive CPU cooler, the whole setup is dead silent. I need to put my ear right next to the case to hear a hint of noise. I have been running this setup for 3 months without and it is still silent!</p>
<h2 id="size">Size</h2>
<figure>
<noscript><img src="/assets/images/low-power-server-case.jpg" /></noscript>
<img src="data:image/gif;base64,R0lGODlhAQABAAAAACH5BAEKAAEALAAAAAABAAEAAAICTAEAOw==" data-src="/assets/images/low-power-server-case.jpg" alt="" class="lazyload fade-in" />
<figcaption>The Cooltek C2 case is a small case with plenty of room for a large silent CPU cooler.</figcaption>
</figure>
<p>The case is quite small, but not near as small as the Intel NUC systems. It is however small enough to be hidden away in a closet or shelf - in my case in a small wooden beer crate.</p>
<h2 id="expansion">Expansion</h2>
<p>The case is a very tight fit with room for 2 additional PCIe slots since the third is taken up with the hard disk adapter.</p>
<figure>
<noscript><img src="/assets/images/low-power-server-pcie.jpg" /></noscript>
<img src="data:image/gif;base64,R0lGODlhAQABAAAAACH5BAEKAAEALAAAAAABAAEAAAICTAEAOw==" data-src="/assets/images/low-power-server-pcie.jpg" alt="" class="lazyload fade-in" />
<figcaption>PCI mounting bracket for hard disks. The M.2 hard disks are mounted upside down to get the full cooling from the bottom fan.</figcaption>
</figure>
<p>The adapter is strictly not needed since I could mount it on the case front or side panel with some double-sided tape / glue. I could have gotten away with a smaller CPU cooler to free up some internal space, but prefer better cooling.</p>
<p>I have since replaced the PCI mounting bracket and DELOCK Converter to two <a href="https://www.amazon.com/dp/B01IR05DLK/" rel="nofollow" data-amazon-asin="[us][ca][uk][de][es][it][fr]B01IR05DLK">StarTech 3-port M.2 PCI adapter</a>.</p>
<figure>
<noscript><img src="/assets/images/low-power-server-pci-adapters-inside.jpg" /></noscript>
<img src="data:image/gif;base64,R0lGODlhAQABAAAAACH5BAEKAAEALAAAAAABAAEAAAICTAEAOw==" data-src="/assets/images/low-power-server-pci-adapters-inside.jpg" alt="" class="lazyload fade-in" />
<figcaption>Two 3-port M.2 PCI adapters makes for a cleaner and more versatile setup</figcaption>
</figure>
<p>Much more compact setup and added benefit of a net gain of two additional PCI M.2 slots. Does add a bit of flashing lights in the rear!</p>
<figure>
<noscript><img src="/assets/images/low-power-server-pci-adapters.jpg" /></noscript>
<img src="data:image/gif;base64,R0lGODlhAQABAAAAACH5BAEKAAEALAAAAAABAAEAAAICTAEAOw==" data-src="/assets/images/low-power-server-pci-adapters.jpg" alt="" class="lazyload fade-in" />
<figcaption>Two 3-port M.2 PCI adapters seen from outside the case</figcaption>
</figure>
<h2 id="conclusion">Conclusion</h2>
<p>Been running this setup for months with no issues and the average power consumption measured from the wall socket has been 9 watts - not bad!</p>
<p><a href="https://odd-one-out.serek.eu/projects/low-power-server/" rel="nofollow">Server idling at 9 watt</a> was originally published on Odd One Out.</p>
Poul Serekhttps://Ox3.serek.eu/about/A proper server supporting ECC memory and 3 PCIe slots, without the noise, heat issues and high power consumption. Idling at only 8-9 watt - measured from the power outlet!Fahrer Panel Bags review2017-09-08T00:00:00+00:002017-09-08T08:29:30+00:00https://odd-one-out.serek.eu/reviews/fahrer-berlin-panel-bags-review
<p><img src="/assets/images/fahrer-berlin-panel-bags-review-feature.jpg" alt="" /></p>
<p>I was lucky enough to get the Bullitt Panel Bags at a reduced price from <a href="http://www.fahrer-berlin.de" rel="nofollow">fahrer-berlin.de</a>. It is a set of bags that sits in an otherwise unused space behind the main cargo area of the Danish Bullitt cargo bike.</p>
<figure>
<noscript><img src="/assets/images/fahrer-berlin-panel-bags-review-bags.jpg" /></noscript>
<img src="data:image/gif;base64,R0lGODlhAQABAAAAACH5BAEKAAEALAAAAAABAAEAAAICTAEAOw==" data-src="/assets/images/fahrer-berlin-panel-bags-review-bags.jpg" alt="" class="lazyload fade-in" />
<figcaption>Fahrer Panel Bag - Two practical bags for the Danish Bullitt cargo bike</figcaption>
</figure>
<p>The bags are waterproof and rather large. I was surprised on how much stuff I could carry with room to spare.</p>
<figure>
<noscript><img src="/assets/images/fahrer-berlin-panel-bags-review-Content.jpg" /></noscript>
<img src="data:image/gif;base64,R0lGODlhAQABAAAAACH5BAEKAAEALAAAAAABAAEAAAICTAEAOw==" data-src="/assets/images/fahrer-berlin-panel-bags-review-Content.jpg" alt="" class="lazyload fade-in" />
</figure>
<p>The bicycle pump and <a href="https://www.amazon.com/dp/B001SMSUNI/" rel="nofollow" data-amazon-asin="[us][ca][uk][de][es][it][fr]B001SMSUNI">Kryptonite New York Noose Chain 1275 and Evolution Disc Lock</a> takes up the room in one pocket and the rest is in the other pocket.</p>
<p>In the front inside the main cargo area there is a large mesh pocket which is practical for storing flat items or empty shopping bags.</p>
<figure>
<noscript><img src="/assets/images/fahrer-berlin-panel-bags-review-bags-mesh-pocket.jpg" /></noscript>
<img src="data:image/gif;base64,R0lGODlhAQABAAAAACH5BAEKAAEALAAAAAABAAEAAAICTAEAOw==" data-src="/assets/images/fahrer-berlin-panel-bags-review-bags-mesh-pocket.jpg" alt="" class="lazyload fade-in" />
<figcaption>Mesh pocket in the main cargo area</figcaption>
</figure>
<p>One might wonder why I need two bags on a cargo bike with plenty of storage in the main cargo area. The truth is I like it tidy and a clean main cargo area. The other thing I noticed is when I left my Kryptonite New York Noose 1275 loose in the main cargo area, the whole chain rattled loudly when riding over an uneven surface. When left in one of the small panel bags, I heard nothing. Each bag also have two straps to tighten the bag to keep everything in place and rattle free.</p>
<figure>
<noscript><img src="/assets/images/fahrer-berlin-panel-bags-review-bags-velcro.jpg" /></noscript>
<img src="data:image/gif;base64,R0lGODlhAQABAAAAACH5BAEKAAEALAAAAAABAAEAAAICTAEAOw==" data-src="/assets/images/fahrer-berlin-panel-bags-review-bags-velcro.jpg" alt="" class="lazyload fade-in" />
<figcaption>Velcro strap to keep items in place</figcaption>
</figure>
<p>Installation was straightforward and easy using the supplied instructions. I stopped only once to consider if I really had to tighten two screws through the front mesh pocket… and I did!</p>
<figure class="gallery-2-col">
<a href="/assets/images/fahrer-berlin-panel-bags-review-install-01.jpg"><noscript><img src="/assets/images/fahrer-berlin-panel-bags-review-install-01.jpg" /></noscript><img src="data:image/gif;base64,R0lGODlhAQABAAAAACH5BAEKAAEALAAAAAABAAEAAAICTAEAOw==" data-src="/assets/images/fahrer-berlin-panel-bags-review-install-01.jpg" alt="To mount the panel bag..." class="lazyload fade-in" /></a>
<a href="/assets/images/fahrer-berlin-panel-bags-review-install-02.jpg"><noscript><img src="/assets/images/fahrer-berlin-panel-bags-review-install-02.jpg" /></noscript><img src="data:image/gif;base64,R0lGODlhAQABAAAAACH5BAEKAAEALAAAAAABAAEAAAICTAEAOw==" data-src="/assets/images/fahrer-berlin-panel-bags-review-install-02.jpg" alt="... one needs to use a screwdriver through the mesh pocket." class="lazyload fade-in" /></a>
<figcaption>To mount the panel bag one needs to use a screwdriver through the mesh pocket.</figcaption>
</figure>
<p>It should be compatible with the HOOD cargo bag which I review earlier <a href="/reviews/bullitt-hood-001-review/">here</a> and it is fully compatible with the <a href="http://shop.larryvsharry.com/shop/accessories/bbx-side-panel-kit-race-green.html" rel="nofollow">BBX Side Panel Kit</a> from Harry vs Larry.</p>
<figure>
<noscript><img src="/assets/images/fahrer-berlin-panel-bags-review-compatible-with-side-kit.jpg" /></noscript>
<img src="data:image/gif;base64,R0lGODlhAQABAAAAACH5BAEKAAEALAAAAAABAAEAAAICTAEAOw==" data-src="/assets/images/fahrer-berlin-panel-bags-review-compatible-with-side-kit.jpg" alt="" class="lazyload fade-in" />
<figcaption>The Panel Bags are compatible with the BBX Side Panel Kit from Harry vs Larry</figcaption>
</figure>
<p>With the <a href="http://shop.larryvsharry.com/shop/accessories/childseat.html" rel="nofollow">foldable seat</a> it is still possible to use the panel bags, but without access to the front mesh pockets and without using the bottom screws.</p>
<figure class="gallery-2-col">
<a href="/assets/images/fahrer-berlin-panel-bags-review-seat-01.jpg"><noscript><img src="/assets/images/fahrer-berlin-panel-bags-review-seat-01.jpg" /></noscript><img src="data:image/gif;base64,R0lGODlhAQABAAAAACH5BAEKAAEALAAAAAABAAEAAAICTAEAOw==" data-src="/assets/images/fahrer-berlin-panel-bags-review-seat-01.jpg" alt="The lower mounting screw of the foldable seat is too big to go through the from mesh pocket. The solution is to not use this for the panel bags." class="lazyload fade-in" /></a>
<a href="/assets/images/fahrer-berlin-panel-bags-review-seat-02.jpg"><noscript><img src="/assets/images/fahrer-berlin-panel-bags-review-seat-02.jpg" /></noscript><img src="data:image/gif;base64,R0lGODlhAQABAAAAACH5BAEKAAEALAAAAAABAAEAAAICTAEAOw==" data-src="/assets/images/fahrer-berlin-panel-bags-review-seat-02.jpg" alt="The missing bottom screws does not affect the usability of the panel bags." class="lazyload fade-in" /></a>
<figcaption>The lower mounting screw of the foldable seat is too big to go through the from mesh pocket. The solution is to not use this for the panel bags. The missing bottom screws does not affect the usability of the panel bags.</figcaption>
</figure>
<p>To sum it up, it is the best purchase I have made this year for my cargo bike! 129 euro might be too steep for some, but for that price you get a two practical weatherproof panel bags and a mesh pocket in front. Compared to other accessories for the Bullitt cargo bike, that is in the lower end of the price scale.</p>
<h2 id="pros">Pros</h2>
<ul>
<li>Practical large bags</li>
<li>Weather proof</li>
<li>Takes up otherwise unused space on the cargo bike</li>
<li>Handy mesh pocket</li>
</ul>
<h2 id="cons">Cons</h2>
<ul>
<li>Price</li>
<li>Minor confusion during installation</li>
<li>Less than optimal compatibility with the foldable seat</li>
</ul>
<div class="btn--group">
<a href="https://www.fahrer-berlin.de/en/bullit/panel-bags/panel-bags/a-208/" class="btn" rel="nofollow">Buy from Fahrer-berlin.de</a>
<a href="http://shop.larryvsharry.com/shop/accessories/fahrer-panel-bags.html" class="btn" rel="nofollow">Buy from Larry vs Harry</a>
</div>
<p><a href="https://odd-one-out.serek.eu/reviews/fahrer-berlin-panel-bags-review/" rel="nofollow">Fahrer Panel Bags review</a> was originally published on Odd One Out.</p>
Poul Serekhttps://Ox3.serek.eu/about/Fahrer Panel Bags review - they should come as standard on any Bullitt cargo bike!Using OpenVPN client with Ubuntu 16.04 server2017-05-31T00:00:00+00:002017-05-31T14:36:06+00:00https://odd-one-out.serek.eu/code/linux-openvpn-ubuntu-nordvpn
<p>The post will show you how to setup a headless linux server using Ubuntu 16.04 LTS and only allowing outgoing connections using a secure VPN <sup id="fnref:vpn"><a href="#fn:vpn" class="footnote">1</a></sup> connection with OpenVPN. If the VPN connection fails, no traffic is leaked. I will be using NordVPN as an example VPN provider since it is what I use myself, but any VPN provider with OpenVPN profiles should work.</p>
<h2 id="vpn-setup">VPN setup</h2>
<p>First we install the OpenVPN client and required dependencies as described at NordVPN <a href="https://nordvpn.com/tutorials/linux/openvpn/">guide</a>:</p>
<div class="language-terminal highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="gp">$</span><span class="nb">sudo </span>apt <span class="nb">install </span>openvpn unzip ca-certificates
</code></pre></div></div>
<p>Next we download and unzip the OpenVPN configuration files:</p>
<div class="language-terminal highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="gp">$</span><span class="nb">cd</span> /etc/openvpn
<span class="gp">$</span><span class="nb">sudo </span>wget https://nordvpn.com/api/files/zip
<span class="gp">$</span><span class="nb">sudo </span>unzip zip
<span class="gp">$</span><span class="nb">sudo rm </span>zip
</code></pre></div></div>
<p>Now we can connect to a server. To see a list of all servers available, do a <code class="highlighter-rouge">ls -al</code> from <code class="highlighter-rouge">/etc/openvpn</code>. Choose one of these files, e.g.</p>
<div class="language-terminal highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="gp">$</span><span class="nb">sudo </span>openvpn at1.nordvpn.com.udp1194.ovpn
</code></pre></div></div>
<p>And enter your login credentials. You can test that you are in Austria (AT) from another shell using:</p>
<div class="language-terminal highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="gp">$</span>curl ipinfo.io/country
<span class="go">AT
</span></code></pre></div></div>
<p>And when disconnecting the OpenVPN from the first shell (just use <code class="highlighter-rouge">CTRL + C</code>) and rerunning the above command you should get your origin country.</p>
<h2 id="firewall">Firewall</h2>
<p>Next we make sure we can only use the VPN internet connection. If you are doing this over SSH remember to do a <code class="highlighter-rouge">sudo ufw allow 22</code> to prevent being locked out.</p>
<div class="language-terminal highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="gp">$</span><span class="nb">sudo </span>apt <span class="nb">install </span>ufw
<span class="gp">$</span><span class="nb">sudo </span>ufw default deny incoming
<span class="gp">$</span><span class="nb">sudo </span>ufw default deny outgoing
<span class="gp">$</span><span class="nb">sudo </span>ufw allow out 1194/udp
<span class="gp">$</span><span class="nb">sudo </span>ufw allow out on tun0
<span class="gp">$</span><span class="nb">sudo </span>ufw <span class="nb">enable</span>
</code></pre></div></div>
<p>The above will prevent all incoming and outgoing connections except for <code class="highlighter-rouge">tun0</code> which is the VPN and port 1194 so we can connect to the VPN. Notice that I do not allow VPN connections on port 443 since I might accidentally connect to websites without VPN.</p>
<p>We also make sure to use NordVPN’s DNS servers as described <a href="https://support.nordvpn.com/hc/en-us/articles/208083995-DNS-servers">here</a>.</p>
<div class="language-terminal highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="gp">$</span><span class="nb">sudo </span>nano /etc/resolvconf/resolv.conf.d/base
</code></pre></div></div>
<p>And add the following in the end of the file:</p>
<div class="language-shell highlighter-rouge"><div class="highlight"><pre class="highlight"><code>nameserver 78.46.223.24
nameserver 162.242.211.137
</code></pre></div></div>
<p>We now test the connection without being on the VPN which prevents data-connections and DNS lookups.</p>
<div class="language-terminal highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="gp">$</span>ping google.com <span class="nt">-c</span> 1
<span class="go">ping: unknown host google.com
</span><span class="gp">$</span>curl ipinfo.io/country
<span class="go">curl: (6) Could not resolve host: ipinfo.io
</span></code></pre></div></div>
<p>And with the VPN we are able to</p>
<div class="language-terminal highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="gp">$</span>ping google.com <span class="nt">-c</span> 1
<span class="go">PING google.com (172.217.21.206) 56(84) bytes of data.
64 bytes from fra16s12-in-f206.1e100.net (172.217.21.206): icmp_seq=1 ttl=57 time=34.3 ms
--- google.com ping statistics ---
1 packets transmitted, 1 received, 0% packet loss, time 0ms
rtt min/avg/max/mdev = 34.301/34.301/34.301/0.000 ms
</span><span class="gp">$</span>curl ipinfo.io/country
<span class="go">CH
</span></code></pre></div></div>
<h2 id="autostart-vpn">Autostart VPN</h2>
<p>First we save our credentials in a file. The information is stored as cleartext so be sure to secure it. Replace <code class="highlighter-rouge">Username</code> and <code class="highlighter-rouge">Password</code> with your own information.</p>
<div class="language-terminal highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="gp">$</span><span class="nb">sudo printf</span> <span class="s2">"Username</span><span class="se">\n</span><span class="s2">Password"</span> <span class="o">></span> /etc/openvpn/NordVPN_credentials
</code></pre></div></div>
<p>Next we modify the OpenVPN files to use the credentials from the file.</p>
<div class="language-terminal highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="gp">$</span><span class="nb">sudo sed</span> <span class="nt">-i</span> <span class="nt">--</span> <span class="s1">'s/auth-user-pass.*/auth-user-pass \/etc\/openvpn\/NordVPN_credentials/g'</span> /etc/openvpn/<span class="k">*</span>
</code></pre></div></div>
<p>And finally we create a cronjob to autostart the VPN client on boot, replace <code class="highlighter-rouge">at1.nordvpn.com.udp1194.ovpn</code> with whatever configuration file you want to use.</p>
<div class="language-terminal highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="gp">$</span><span class="o">(</span>crontab <span class="nt">-u</span> root <span class="nt">-l</span><span class="p">;</span> <span class="nb">echo</span> <span class="s2">"@reboot sleep 10 && /usr/sbin/openvpn /etc/openvpn/at1.nordvpn.com.udp1194.ovpn"</span> <span class="o">)</span> | crontab <span class="nt">-u</span> root -
</code></pre></div></div>
<p>Restart the server and you should automatically use the VPN connection.</p>
<div class="footnotes">
<ol>
<li id="fn:vpn">
<p>A <a href="https://en.wikipedia.org/wiki/Virtual_private_network">virtual private network</a> (VPN) extends a private network across a public network, and enables users to send and receive data across shared or public networks as if their computing devices were directly connected to the private network. <a href="#fnref:vpn" class="reversefootnote">↩</a></p>
</li>
</ol>
</div>
<p><a href="https://odd-one-out.serek.eu/code/linux-openvpn-ubuntu-nordvpn/" rel="nofollow">Using OpenVPN client with Ubuntu 16.04 server</a> was originally published on Odd One Out.</p>
Poul Serekhttps://Ox3.serek.eu/about/Using OpenVPN client with Ubuntu 16.04 serverLet’s Encrypt DNS challenge with acme.sh and CloudFlare2017-04-20T00:00:00+00:002017-04-20T19:16:24+00:00https://odd-one-out.serek.eu/code/lets-encrypt-dns-challenge-cloudflare-acme-sh
<p>I wrote a small blog <a href="/code/free-ssl-certificate-lets-encrypt/">post</a> about getting free SSL certificates using Let’s Encrypt. It required outside access for the validations process to work. But now I needed SSL certificates for my local services without public access, this turned out to be very easy using <a href="https://github.com/Neilpang/acme.sh">acme.sh</a> DNS challenge and CloudFlare DNS.</p>
<p>First we install it. Notice that I do this as root.</p>
<div class="language-terminal highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="gp">#</span>curl https://get.acme.sh | sh
</code></pre></div></div>
<p>Then we export two variables needed for the CloudFlare DNS challenge to work. Replace <code class="highlighter-rouge">your@mail.com</code> and <code class="highlighter-rouge">edfgdfgdfgd</code> with your own values from CloudFlare.</p>
<div class="language-terminal highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="gp">#</span><span class="nb">export </span><span class="nv">CF_Key</span><span class="o">=</span><span class="s2">"edfgdfgdfgd"</span>
<span class="gp">#</span><span class="nb">export </span><span class="nv">CF_Email</span><span class="o">=</span><span class="s2">"your@mail.com"</span>
</code></pre></div></div>
<p>Finally we request the certificate. Replace <code class="highlighter-rouge">yourdomain.com</code> with your own domain.</p>
<div class="language-terminal highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="gp">#</span><span class="nb">cd</span> ~/.acme.sh
<span class="gp">#</span>./acme.sh <span class="nt">--issue</span> <span class="nt">--dns</span> dns_cf <span class="nt">-d</span> yourdomain.com
</code></pre></div></div>
<p>This also sets up a cronjob to automatically renew the certificate, you can do an <code class="highlighter-rouge">crontab -e</code> to see it.
Now that we have a certificate, we can use the same script to install it to a webserver, e.g. NGINX. Again, replace <code class="highlighter-rouge">yourdomain.com</code> with your own domain and make sure the <code class="highlighter-rouge">key-file</code> and <code class="highlighter-rouge">fullchain-file</code> matches that of your NGINX configuration.</p>
<div class="language-terminal highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="gp">#</span>./acme.sh <span class="nt">--install-cert</span> <span class="nt">-d</span> yourdomain.com <span class="se">\</span>
<span class="nt">--cert-file</span> /etc/nginx/ssl/yourdomain.com.cer <span class="se">\</span>
<span class="nt">--key-file</span> /etc/nginx/ssl/yourdomain.com.key <span class="se">\</span>
<span class="nt">--fullchain-file</span> /etc/nginx/ssl/fullchain.cer <span class="se">\</span>
<span class="nt">--reloadcmd</span> <span class="s2">"service nginx force-reload"</span>
</code></pre></div></div>
<p>Thats it! Now you have an automatically renewable SSL certificate that works on local networks that are not accessible from the internet.</p>
<p><a href="https://odd-one-out.serek.eu/code/lets-encrypt-dns-challenge-cloudflare-acme-sh/" rel="nofollow">Let’s Encrypt DNS challenge with acme.sh and CloudFlare</a> was originally published on Odd One Out.</p>
Poul Serekhttps://Ox3.serek.eu/about/Get signed SSL certificates using Let's Encrypt. Using DNS challenge with the `acme.sh` script as proof of ownership you do not even need to expose a server to the public internet!Ubuntu Canonical Livepatch Service2017-04-13T00:00:00+00:002017-04-13T11:03:31+00:00https://odd-one-out.serek.eu/code/ubuntu-canonical-livepatch-service
<p>Since I am running several machines with LXD <sup id="fnref:lxd"><a href="#fn:lxd" class="footnote">1</a></sup> containers, it is extra important to keep the host kernel up-to-date with security patches since every container uses the host kernel. Using Canonicals Livepatch Service enables live patching without a reboot / restart for free for up to 3 machines.</p>
<div class="notice warning no_toc_section">
<p><strong>Notice</strong>: Once a livepatch passes Ubuntu’s internal test, it is rolled out on a canary testing basis, first to a tiny percentage of the Ubuntu Community users of the Canonical Livepatch Service - the free tier of this service. If you do not wan’t the risk of ending up being a guinea pig you need to pay for the service.</p>
</div>
<p>For this to work you need:</p>
<ul>
<li>Fully updated 64-bit Ubuntu 16.04 LTS (Xenial) running kernel 4.4 (GA) <sup id="fnref:kernel"><a href="#fn:kernel" class="footnote">2</a></sup> and not the optional HWE kernel which is at 4.8</li>
<li>A free Ubuntu One account</li>
</ul>
<p>To install and enable livepatching simply do:</p>
<ol>
<li>Go to <a href="https://ubuntu.com/livepatch">https://ubuntu.com/livepatch</a> and select “Ubuntu User” and click “Get your Livepatch token” to retrieve your livepatch token</li>
<li>In the terminal install the service
<div class="language-terminal highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="gp">$</span><span class="nb">sudo </span>snap <span class="nb">install </span>canonical-livepatch
</code></pre></div> </div>
</li>
</ol>
<ol start="3">
<li>Enable the service with the token retrieved from step 1
<div class="language-terminal highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="gp">$</span><span class="nb">sudo </span>canonical-livepatch <span class="nb">enable</span> <span class="s2">"Livepatch token from step 1"</span>
</code></pre></div> </div>
</li>
</ol>
<ol start="4">
<li>Check if the service is running
<div class="language-terminal highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="gp">$</span>canonical-livepatch status <span class="nt">--verbose</span>
<span class="go">client-version: "7.21"
machine-id: 432b7728d2c94336325f494158288c1b
machine-token: ec2a887cc4ff40edbfaa590cd73f9266
architecture: x86_64
cpu-model: QEMU Virtual CPU version (cpu64-rhel6)
last-check: 2017-04-13T12:56:36.992+02:00
boot-time: 2017-04-13T12:56:21+02:00
uptime: 2m43s
status:
- kernel: 4.4.0-72.93-generic
running: true
livepatch:
checkState: checked
patchState: nothing-to-apply
version: ""
fixes: ""
</span></code></pre></div> </div>
</li>
</ol>
<div class="footnotes">
<ol>
<li id="fn:lxd">
<p>LXD (pronounced lex-dee) is a container based hypervisor that runs unmodified Linux guest operating systems with VM-style operations at higher speed and density than a full blown traditional hypervisor like VMWare and KVM. Take a look at my posts <a href="/code/lxd-2-0-container-hypervisor/">here</a> and <a href="/code/latest-stable-lxd-ubuntu-16-04-lts/">here</a> for more info. <a href="#fnref:lxd" class="reversefootnote">↩</a></p>
</li>
<li id="fn:kernel">
<p>The general availability (GA) kernel is based on the generic kernel that originally ships with a new Ubuntu version. New hardware gets released all the time and if an Ubuntu host is running an older kernel then that hardware likely won’t be supported by it. Ubuntu’s response to this is to backport more recent kernels. Doing this effectively enables more hardware. Hence, HWE is an acronym for HardWare Enablement. (<a href="https://docs.ubuntu.com/maas/2.1/en/installconfig-nodes-ubuntu-kernels">Source</a>) <a href="#fnref:kernel" class="reversefootnote">↩</a></p>
</li>
</ol>
</div>
<p><a href="https://odd-one-out.serek.eu/code/ubuntu-canonical-livepatch-service/" rel="nofollow">Ubuntu Canonical Livepatch Service</a> was originally published on Odd One Out.</p>
Poul Serekhttps://Ox3.serek.eu/about/Ubuntu Canonical Livepatch Service - free livepatching of running kernels without a reboot.Amazon AJAX Javascript Link Localiser2017-02-02T00:00:00+00:002017-02-02T20:19:19+00:00https://odd-one-out.serek.eu/code/amazon-ajax-javascript-link-localiser
<p>I recently <a href="/code/wordpress-caching-static-html-cloudflare/">wrote</a> about how a cache everything on this site on CloudFlare - including the HTML pages. This does not play nice with my geo-aware cache setup which I used to generate specific pages with localised links depending which country my visitor came from. To get around this problem I have written some Javascript to be able to localise links and through an AJAX <sup id="fnref:ajax"><a href="#fn:ajax" class="footnote">1</a></sup> call get the visitors location. It works by</p>
<ol>
<li>Getting the country code from the visitors IP address using AJAX using freegeoip.net <sup id="fnref:freegeoip"><a href="#fn:freegeoip" class="footnote">2</a></sup>. This makes it cache friendly</li>
<li>The country code is mapped to which Amazon store it should redirect to - including which affiliate tag to use. This can be configured / changed.</li>
<li>The amazon affiliate link is localised as specified in the <code class="highlighter-rouge">data-amazon-asin</code> attribute on the link itself.</li>
</ol>
<h2 id="demo">Demo</h2>
<p>Since we are talking about actual working amazon affiliate links below, I will present my disclaimer first!
<!-- Affiliate disclamer removed because of GDPR and removal of affiliate links --></p>
<div class="notice"><h3>Disclaimer</h3>This post contains links to Amazon where I get a small commission if you purchase anything after clicking on these links - at no extra cost to you! But only if you have explicitly consented to this. I have purchased all the mentioned products myself and I only link to products that I believe are the best for my readers. If you want to help out even more, take a look <a href="/support/">here</a>.</div>
<div class="notice warning no_toc_section">
<p>My script will generate real Amazon affiliate links. When clicking on these links Amazon links you consent to installing an Amazon affiliate cookie on your computer. For this demo you do not need to click on the actual links, you can just hover over the link to see the changed link or inspect it using your browser. If you want to withdraw your consent, just delete the cookies from this site using your browser.</p>
</div>
<p>Now lets use an example link to Amazon:</p>
<figure class="highlight"><pre><code class="language-terminal" data-lang="terminal"><span class="go">https://www.amazon.com/dp/B0084A7PI8/?tag=serek-eu-us-20</span></code></pre></figure>
<p>With an data-amazon-asin attribute:</p>
<figure class="highlight"><pre><code class="language-terminal" data-lang="terminal"><span class="go">data-amazon-asin="[us][es]B0084A7PI8[uk][de]B00JGFDKBQ[it][fr]B00PQC72ZS[ca]us"</span></code></pre></figure>
<p>That results in the following clickable link:<br />
<a href="https://www.amazon.com/dp/B0084A7PI8/?tag=serek-eu-us-20" target="_blank" rel="nofollow" data-amazon-asin="[us][es]B0084A7PI8[uk][de]B00JGFDKBQ[it][fr]B00PQC72ZS[ca]us">Test link to Amazon</a></p>
<p>The link should already be localised depending on the country you arrived from. The buttons below will simulate that you arrive from another country by setting the cookie with a different country and re-running the script. Then the link above should be changed / localised. You can use the developer tools on your browser to see the changes in the HTML or just click on the link.</p>
<div class="btn--group">
<a class="btn" href="#" onclick="setCookie('geo_country_code','IT'); localiseLinks(); return false;">Italy (IT)</a>
<a class="btn" href="#" onclick="setCookie('geo_country_code','US'); localiseLinks(); return false;">United States (US)</a>
<a class="btn" href="#" onclick="setCookie('geo_country_code','UK'); localiseLinks(); return false;">United Kingdom (UK)</a>
</div>
<div class="btn--group">
<a class="btn" href="#" onclick="setCookie('geo_country_code','CA'); localiseLinks(); return false;">Canada (CA)</a>
<a class="btn" href="#" onclick="setCookie('geo_country_code','ES'); localiseLinks(); return false;">Spain (ES)</a>
<a class="btn" href="#" onclick="setCookie('geo_country_code','DE'); localiseLinks(); return false;">Germany (DE)</a>
<a class="btn" href="#" onclick="setCookie('geo_country_code','FR'); localiseLinks(); return false;">France (FR)</a>
</div>
<p>The expected results are</p>
<ul>
<li>US and ES will get a localised link to <code class="highlighter-rouge">B0084A7PI8</code></li>
<li>UK and DE will get a localised link to <code class="highlighter-rouge">B00JGFDKBQ</code></li>
<li>IT and FR will get a localised link to <code class="highlighter-rouge">B00PQC72ZS</code></li>
<li>CA will get an US link to <code class="highlighter-rouge">B0084A7PI8</code></li>
</ul>
<p>Notice that all the above countries have an Amazon store. To redirect countries without Amazon stores we could do the following:</p>
<figure class="highlight"><pre><code class="language-terminal" data-lang="terminal"><span class="go">data-amazon-asin="[us][es]B0084A7PI8[uk][de]B00JGFDKBQ[it][fr]B00PQC72ZS[ca][AT][BE][DK][FI][NL][NO][PL][SE][LI][LU][PT][AD]us"</span></code></pre></figure>
<p>Which would redirect a bunch of countries to the US Amazon affiliate store, but it is cumbersome to write each time. Therefore it is possible define the mapping of countries to other countries in this way:</p>
<figure class="highlight"><pre><code class="language-javascript" data-lang="javascript"><span class="kd">var</span> <span class="nx">amazonAffiliateRegions</span> <span class="o">=</span> <span class="p">{</span>
<span class="s2">"DE"</span><span class="p">:</span><span class="s2">"DE"</span><span class="p">,</span> <span class="c1">//Germany</span>
<span class="s2">"AT"</span><span class="p">:</span><span class="s2">"DE"</span><span class="p">,</span> <span class="c1">//Austria</span>
<span class="s2">"BE"</span><span class="p">:</span><span class="s2">"DE"</span><span class="p">,</span> <span class="c1">//Belgium</span>
<span class="s2">"DK"</span><span class="p">:</span><span class="s2">"DE"</span><span class="p">,</span> <span class="c1">//Denmark</span>
<span class="s2">"FI"</span><span class="p">:</span><span class="s2">"DE"</span><span class="p">,</span> <span class="c1">//Finland</span>
<span class="s2">"NL"</span><span class="p">:</span><span class="s2">"DE"</span><span class="p">,</span> <span class="c1">//Netherlands</span>
<span class="s2">"NO"</span><span class="p">:</span><span class="s2">"DE"</span><span class="p">,</span> <span class="c1">//Norway</span>
<span class="s2">"PL"</span><span class="p">:</span><span class="s2">"DE"</span><span class="p">,</span> <span class="c1">//Poland</span>
<span class="s2">"SE"</span><span class="p">:</span><span class="s2">"DE"</span><span class="p">,</span> <span class="c1">//Sweden</span>
<span class="s2">"LI"</span><span class="p">:</span><span class="s2">"DE"</span><span class="p">,</span> <span class="c1">//Liechtenstein</span>
<span class="s2">"LU"</span><span class="p">:</span><span class="s2">"DE"</span><span class="p">,</span> <span class="c1">//Luxembourg</span>
<span class="s2">"ES"</span><span class="p">:</span><span class="s2">"ES"</span><span class="p">,</span> <span class="c1">//Spain</span>
<span class="s2">"PT"</span><span class="p">:</span><span class="s2">"ES"</span><span class="p">,</span> <span class="c1">//Portugal</span>
<span class="s2">"AD"</span><span class="p">:</span><span class="s2">"ES"</span><span class="p">,</span> <span class="c1">//Andorra</span>
<span class="s2">"GB"</span><span class="p">:</span><span class="s2">"UK"</span><span class="p">,</span> <span class="c1">//United Kingdom</span>
<span class="s2">"UK"</span><span class="p">:</span><span class="s2">"UK"</span><span class="p">,</span> <span class="c1">//United Kingdom (dummy, used since I use UK and not GB)</span>
<span class="s2">"IE"</span><span class="p">:</span><span class="s2">"UK"</span><span class="p">,</span> <span class="c1">//Ireland</span>
<span class="s2">"IM"</span><span class="p">:</span><span class="s2">"UK"</span><span class="p">,</span> <span class="c1">//Isle of Man</span>
<span class="s2">"IT"</span><span class="p">:</span><span class="s2">"IT"</span><span class="p">,</span> <span class="c1">//Italy</span>
<span class="s2">"VA"</span><span class="p">:</span><span class="s2">"IT"</span><span class="p">,</span> <span class="c1">//Holy See (Vatican City State)</span>
<span class="s2">"FR"</span><span class="p">:</span><span class="s2">"FR"</span><span class="p">,</span> <span class="c1">//France</span>
<span class="s2">"CA"</span><span class="p">:</span><span class="s2">"CA"</span><span class="p">,</span> <span class="c1">//Canada</span>
<span class="s2">"US"</span><span class="p">:</span><span class="s2">"US"</span><span class="p">,</span> <span class="c1">//United States</span>
<span class="s2">"CN"</span><span class="p">:</span><span class="s2">"CN"</span><span class="p">,</span> <span class="c1">//China</span>
<span class="s2">"BR"</span><span class="p">:</span><span class="s2">"BR"</span><span class="p">,</span> <span class="c1">//Brazil</span>
<span class="s2">"IN"</span><span class="p">:</span><span class="s2">"IN"</span><span class="p">,</span> <span class="c1">//India</span>
<span class="s2">"MX"</span><span class="p">:</span><span class="s2">"MX"</span><span class="p">,</span> <span class="c1">//Mexico</span>
<span class="s2">"DEFAULT"</span><span class="p">:</span><span class="s2">"US"</span> <span class="c1">//If no match found above, use this country as default</span>
<span class="p">};</span></code></pre></figure>
<p>The above is the default mapping. Lets try to play again with some different countries. We use the same links as before:</p>
<figure class="highlight"><pre><code class="language-terminal" data-lang="terminal"><span class="go">https://www.amazon.com/dp/B0084A7PI8/?tag=serek-eu-us-20</span></code></pre></figure>
<p>With an data-amazon-asin attribute:</p>
<figure class="highlight"><pre><code class="language-terminal" data-lang="terminal"><span class="go">data-amazon-asin="[us][es]B0084A7PI8[uk][de]B00JGFDKBQ[it][fr]B00PQC72ZS[ca]us"</span></code></pre></figure>
<p>That results in the following clickable link:<br />
<a href="https://www.amazon.com/dp/B0084A7PI8/?tag=serek-eu-us-20" target="_blank" rel="nofollow" data-amazon-asin="[us][es]B0084A7PI8[uk][de]B00JGFDKBQ[it][fr]B00PQC72ZS[ca]us">Test link to Amazon</a></p>
<div class="btn--group">
<a class="btn" href="#" onclick="setCookie('geo_country_code','VA'); localiseLinks(); return false;">Vatican City (VA)</a>
<a class="btn" href="#" onclick="setCookie('geo_country_code','PL'); localiseLinks(); return false;">Poland (PL)</a>
<a class="btn" href="#" onclick="setCookie('geo_country_code','CN'); localiseLinks(); return false;">China (CN)</a>
</div>
<div class="btn--group">
<a class="btn" href="#" onclick="setCookie('geo_country_code','PT'); localiseLinks(); return false;">Portugal (PT)</a>
<a class="btn" href="#" onclick="setCookie('geo_country_code','NO'); localiseLinks(); return false;">Norway (NO)</a>
<a class="btn" href="#" onclick="setCookie('geo_country_code','RU'); localiseLinks(); return false;">Russia (RU)</a>
</div>
<p>The expected results are</p>
<ul>
<li>VA will get an IT link to <code class="highlighter-rouge">B00PQC72ZS</code></li>
<li>PL and NO get an DE link to <code class="highlighter-rouge">B00JGFDKBQ</code></li>
<li>PT will get an ES link to <code class="highlighter-rouge">B0084A7PI8</code></li>
<li>CN will <em>not</em> change the link since it cannot find the <code class="highlighter-rouge">[cn]</code> tag in the <code class="highlighter-rouge">data-attribute-asin</code>, but it is defined in <code class="highlighter-rouge">amazonAffiliateRegions</code>. If you try to refresh the page, it should return to the original <code class="highlighter-rouge">.com</code> link when the page is loaded. <strong>UPDATE 11-02-2017:</strong> On my site I have changed CN to point at US so it will show the US link</li>
<li>RU will get an US link (default) to <code class="highlighter-rouge">B0084A7PI8</code> since it cannot be found in <code class="highlighter-rouge">amazonAffiliateRegions</code></li>
</ul>
<h2 id="installation">Installation</h2>
<h3 id="wordpress">WordPress</h3>
<p>I have made two simple WordPress plugins - one for getting and storing the country code in a cookie. The second reads the country code form the cookie and transforms the links.</p>
<ol>
<li>Go into your WordPress plugin folder, e.g. <code class="highlighter-rouge">/var/www/html/wordpress/wp-content/plugins</code></li>
<li>git clone <a href="https://github.com/WordPress-plugins-serek/geo-ip">https://github.com/WordPress-plugins-serek/geo-ip</a></li>
<li>git clone <a href="https://github.com/WordPress-plugins-serek/javascript-amazon-affiliate-link-localiser">https://github.com/WordPress-plugins-serek/javascript-amazon-affiliate-link-localiser</a></li>
<li>Log into WordPress and activate the above plugins</li>
</ol>
<h3 id="custom">Custom</h3>
<p>If you don’t have WordPress or just want to integrate the files manually do the following:</p>
<ol>
<li>Download the file <a href="https://raw.githubusercontent.com/WordPress-plugins-serek/javascript-amazon-affiliate-link-localiser/master/assets/amazonAjaxLinkLocaliser.js">amazonAjaxLinkLocaliser.js</a></li>
<li>Download the file <a href="https://raw.githubusercontent.com/WordPress-plugins-serek/geo-ip/master/assets/geoip.js">geoip.js</a></li>
<li>Add the following to your site (replace <code class="highlighter-rouge">yoursite.com</code> with the correct path to the files)</li>
</ol>
<figure class="highlight"><pre><code class="language-html" data-lang="html"><span class="nt"><script </span><span class="na">src=</span><span class="s">"//yoursite.com/geoip.js"</span> <span class="na">defer</span><span class="nt">></script></span>
<span class="nt"><script </span><span class="na">src=</span><span class="s">"//yoursite.com/amazonAjaxLinkLocaliser.js"</span> <span class="na">defer</span><span class="nt">></script></span></code></pre></figure>
<h2 id="configuration">Configuration</h2>
<p>Edit the <code class="highlighter-rouge">amazonAjaxLinkLocaliser.js</code> file and add your own Amazon affiliate IDs in <code class="highlighter-rouge">amazonAffiliateTags</code></p>
<figure class="highlight"><pre><code class="language-javascript" data-lang="javascript"><span class="kd">var</span> <span class="nx">amazonAffiliateTags</span> <span class="o">=</span> <span class="p">{</span>
<span class="s2">"US"</span><span class="p">:</span><span class="s2">"serek-eu-us-20"</span><span class="p">,</span>
<span class="s2">"CA"</span><span class="p">:</span><span class="s2">"serek-eu-ca-20"</span><span class="p">,</span>
<span class="s2">"DE"</span><span class="p">:</span><span class="s2">"serek-eu-de-21"</span><span class="p">,</span>
<span class="s2">"UK"</span><span class="p">:</span><span class="s2">"serek-eu-uk-21"</span><span class="p">,</span>
<span class="s2">"ES"</span><span class="p">:</span><span class="s2">"serek-eu-es-21"</span><span class="p">,</span>
<span class="s2">"IT"</span><span class="p">:</span><span class="s2">"serek-eu-it-21"</span><span class="p">,</span>
<span class="s2">"FR"</span><span class="p">:</span><span class="s2">"serek-eu-fr-21"</span><span class="p">,</span>
<span class="s2">"CN"</span><span class="p">:</span><span class="s2">"serek-eu-cn-23"</span><span class="p">,</span>
<span class="s2">"JP"</span><span class="p">:</span><span class="s2">""</span><span class="p">,</span>
<span class="s2">"MX"</span><span class="p">:</span><span class="s2">""</span><span class="p">,</span>
<span class="s2">"IN"</span><span class="p">:</span><span class="s2">""</span><span class="p">,</span>
<span class="s2">"BR"</span><span class="p">:</span><span class="s2">""</span>
<span class="p">};</span></code></pre></figure>
<p>Notice that if you do not supply a Amazon affiliate tag for a store that you have configured, I will insert my own tag.
Next you might want to tweak the mapping of countries to Amazon stores:</p>
<figure class="highlight"><pre><code class="language-javascript" data-lang="javascript"><span class="kd">var</span> <span class="nx">amazonAffiliateRegions</span> <span class="o">=</span> <span class="p">{</span>
<span class="s2">"DE"</span><span class="p">:</span><span class="s2">"DE"</span><span class="p">,</span> <span class="c1">//Germany</span>
<span class="s2">"AT"</span><span class="p">:</span><span class="s2">"DE"</span><span class="p">,</span> <span class="c1">//Austria</span>
<span class="s2">"BE"</span><span class="p">:</span><span class="s2">"DE"</span><span class="p">,</span> <span class="c1">//Belgium</span>
<span class="s2">"DK"</span><span class="p">:</span><span class="s2">"DE"</span><span class="p">,</span> <span class="c1">//Denmark</span>
<span class="s2">"FI"</span><span class="p">:</span><span class="s2">"DE"</span><span class="p">,</span> <span class="c1">//Finland</span>
<span class="s2">"NL"</span><span class="p">:</span><span class="s2">"DE"</span><span class="p">,</span> <span class="c1">//Netherlands</span>
<span class="s2">"NO"</span><span class="p">:</span><span class="s2">"DE"</span><span class="p">,</span> <span class="c1">//Norway</span>
<span class="s2">"PL"</span><span class="p">:</span><span class="s2">"DE"</span><span class="p">,</span> <span class="c1">//Poland</span>
<span class="s2">"SE"</span><span class="p">:</span><span class="s2">"DE"</span><span class="p">,</span> <span class="c1">//Sweden</span>
<span class="s2">"LI"</span><span class="p">:</span><span class="s2">"DE"</span><span class="p">,</span> <span class="c1">//Liechtenstein</span>
<span class="s2">"LU"</span><span class="p">:</span><span class="s2">"DE"</span><span class="p">,</span> <span class="c1">//Luxembourg</span>
<span class="s2">"ES"</span><span class="p">:</span><span class="s2">"ES"</span><span class="p">,</span> <span class="c1">//Spain</span>
<span class="s2">"PT"</span><span class="p">:</span><span class="s2">"ES"</span><span class="p">,</span> <span class="c1">//Portugal</span>
<span class="s2">"AD"</span><span class="p">:</span><span class="s2">"ES"</span><span class="p">,</span> <span class="c1">//Andorra</span>
<span class="s2">"GB"</span><span class="p">:</span><span class="s2">"UK"</span><span class="p">,</span> <span class="c1">//United Kingdom</span>
<span class="s2">"UK"</span><span class="p">:</span><span class="s2">"UK"</span><span class="p">,</span> <span class="c1">//United Kingdom (dummy, used since I use UK and not GB)</span>
<span class="s2">"IE"</span><span class="p">:</span><span class="s2">"UK"</span><span class="p">,</span> <span class="c1">//Ireland</span>
<span class="s2">"IM"</span><span class="p">:</span><span class="s2">"UK"</span><span class="p">,</span> <span class="c1">//Isle of Man</span>
<span class="s2">"IT"</span><span class="p">:</span><span class="s2">"IT"</span><span class="p">,</span> <span class="c1">//Italy</span>
<span class="s2">"VA"</span><span class="p">:</span><span class="s2">"IT"</span><span class="p">,</span> <span class="c1">//Holy See (Vatican City State)</span>
<span class="s2">"FR"</span><span class="p">:</span><span class="s2">"FR"</span><span class="p">,</span> <span class="c1">//France</span>
<span class="s2">"CA"</span><span class="p">:</span><span class="s2">"CA"</span><span class="p">,</span> <span class="c1">//Canada</span>
<span class="s2">"US"</span><span class="p">:</span><span class="s2">"US"</span><span class="p">,</span> <span class="c1">//United States</span>
<span class="s2">"CN"</span><span class="p">:</span><span class="s2">"CN"</span><span class="p">,</span> <span class="c1">//China</span>
<span class="s2">"BR"</span><span class="p">:</span><span class="s2">"BR"</span><span class="p">,</span> <span class="c1">//Brazil</span>
<span class="s2">"IN"</span><span class="p">:</span><span class="s2">"IN"</span><span class="p">,</span> <span class="c1">//India</span>
<span class="s2">"MX"</span><span class="p">:</span><span class="s2">"MX"</span><span class="p">,</span> <span class="c1">//Mexico</span>
<span class="s2">"DEFAULT"</span><span class="p">:</span><span class="s2">"US"</span> <span class="c1">//If no match found above, use this country as default</span>
<span class="p">};</span></code></pre></figure>
<p>The most important configuration here is the default country. All the countries on the right hand side is the ones you should use in the <code class="highlighter-rouge">data-amazon-asin</code> attribute, e.g. <code class="highlighter-rouge">[de][es][uk][it][fr][ca][us][cn][br][in][mx]</code>. A full list of country codes can be found <a href="http://dev.maxmind.com/geoip/legacy/codes/iso3166/">here</a>.
You can also tweak the mapping of Amazon stores to their url endings, but this should not be needed:</p>
<figure class="highlight"><pre><code class="language-javascript" data-lang="javascript"><span class="kd">var</span> <span class="nx">amazonAffiliateTLDs</span> <span class="o">=</span> <span class="p">{</span>
<span class="s2">"DE"</span><span class="p">:</span><span class="s2">"de"</span><span class="p">,</span>
<span class="s2">"UK"</span><span class="p">:</span><span class="s2">"co.uk"</span><span class="p">,</span>
<span class="s2">"ES"</span><span class="p">:</span><span class="s2">"es"</span><span class="p">,</span>
<span class="s2">"IT"</span><span class="p">:</span><span class="s2">"it"</span><span class="p">,</span>
<span class="s2">"FR"</span><span class="p">:</span><span class="s2">"fr"</span><span class="p">,</span>
<span class="s2">"CA"</span><span class="p">:</span><span class="s2">"ca"</span><span class="p">,</span>
<span class="s2">"US"</span><span class="p">:</span><span class="s2">"com"</span><span class="p">,</span>
<span class="s2">"CN"</span><span class="p">:</span><span class="s2">"cn"</span><span class="p">,</span>
<span class="s2">"JP"</span><span class="p">:</span><span class="s2">"co.jp"</span><span class="p">,</span>
<span class="s2">"MX"</span><span class="p">:</span><span class="s2">"com.mx"</span><span class="p">,</span>
<span class="s2">"IN"</span><span class="p">:</span><span class="s2">"in"</span><span class="p">,</span>
<span class="s2">"BR"</span><span class="p">:</span><span class="s2">"com.br"</span>
<span class="p">};</span></code></pre></figure>
<p>Lastly there is a setting that defines if the script should try to localise a link if it is already is localised to the target region:</p>
<figure class="highlight"><pre><code class="language-javascript" data-lang="javascript"><span class="kd">var</span> <span class="nx">doNotLocaliseLinksThatMatchRegionAlready</span> <span class="o">=</span> <span class="kc">false</span><span class="p">;</span></code></pre></figure>
<p>I use it since I embed link to the <code class="highlighter-rouge">.com</code> Amazon store with my affiliate links already. No need to waste resources localising these again.</p>
<h2 id="next-action">Next action</h2>
<ul>
<li>Unit test that tests all the variants for 404 and makes sure the item is available for purchase</li>
<li>Switch from cookie to local storage</li>
<li>Wordpress plugin to automatically generate the <code class="highlighter-rouge">data-amazon-asin</code> attribute or automatically localise links according to certain rules and mappings. These services already exists, but I need more flexibility in configuring mappings</li>
</ul>
<div class="footnotes">
<ol>
<li id="fn:ajax">
<p>Asynchronous JavaScript and XML (AJAX) is a set of Web development techniques using many Web technologies on the client side to create asynchronous Web applications. Ajax allows for Web pages, and by extension Web applications, to change content dynamically without the need to reload the entire page <a href="#fnref:ajax" class="reversefootnote">↩</a></p>
</li>
<li id="fn:freegeoip">
<p>A free and open source public HTTP API to search the geolocation of IP addresses. It uses a database of IP addresses that are associated to cities along with other relevant information like time zone, latitude and longitude. You’re allowed up to 15,000 queries per hour by default. Once this limit is reached, all of your requests will result in HTTP 403, forbidden, until your quota is cleared. Alternatively it can be downloaded and installed locally to bypass these limits. <a href="#fnref:freegeoip" class="reversefootnote">↩</a></p>
</li>
</ol>
</div>
<p><a href="https://odd-one-out.serek.eu/code/amazon-ajax-javascript-link-localiser/" rel="nofollow">Amazon AJAX Javascript Link Localiser</a> was originally published on Odd One Out.</p>
Poul Serekhttps://Ox3.serek.eu/about/An Amazon Link Localiser that is cache friendly and written in plain old JavaScript. Manually define which Amazon ASIN product code gets used for each country on each link and get the visitors location using IP lookup.Turbocharging WordPress: Caching static HTML with CloudFlare for free2017-01-21T00:00:00+00:002017-01-21T19:41:16+00:00https://odd-one-out.serek.eu/code/wordpress-caching-static-html-cloudflare
<p><img src="/assets/images/wordpress-caching-static-html-cloudflare-feature.png" alt="" /></p>
<p>I already use CloudFlare as a CDN (content delivery network) to cache my static resources and serve them closer to the visitor. CloudFlare also automatically serves content using HTTP/2 and even allows one to use <a href="/code/http2-server-push-nginx-cloudflare-wordpress/">Server Push</a> feature to push resources before the browser even has received the HTML page. The last step is to cache everything at CloudFlare - the HTML pages.</p>
<h2 id="setup">Setup</h2>
<p>Disable your existing CDN if you have one and setup CloudFlare CDN as described in my post <a href="/code/http2-server-push-nginx-cloudflare-wordpress/">here</a>. I am using the free tier which limits us to 3 Page Rules to allow us to play with static HTML caching. First we setup a Page Rule to prevent caching of the admin pages of WordPress. Secondly we create a rule to prevent caching of preview pages. Lastly we create a rules to cache everything else. Replace <code class="highlighter-rouge">odd-one-out.serek.eu</code> with your own domain.</p>
<figure>
<noscript><img src="/assets/images/wordpress-caching-static-html-cloudflare-feature.png" /></noscript>
<img src="data:image/gif;base64,R0lGODlhAQABAAAAACH5BAEKAAEALAAAAAABAAEAAAICTAEAOw==" data-src="/assets/images/wordpress-caching-static-html-cloudflare-feature.png" alt="" class="lazyload fade-in" />
<figcaption>Using CloudFlares free tier, we leverage the 3 Page Rules to enable static HTML caching of WordPress pages, except admin and preview pages</figcaption>
</figure>
<p>You can tweak the above Page Rules to your liking, but the most important part in the first two rules are to set <code class="highlighter-rouge">Cache Level</code> to <code class="highlighter-rouge">Bypass</code> and the last rule to set <code class="highlighter-rouge">Cache Level</code> to <code class="highlighter-rouge">Cache Everything</code>. I also set the <code class="highlighter-rouge">Browser Cache TTL</code> to the lowest I can and the <code class="highlighter-rouge">Edge Cache TTL</code> to the highest.</p>
<p>One last problem is that the login page to WordPress is cached, to solve this I use the <a href="https://wordpress.org/plugins/rename-wp-login/">Rename wp-login.php</a> plugin as recommended by <a href="https://blog.thirdechelon.org/2015/05/cloudflare-page-rules-for-wordpress-caching/">this</a> post. Make sure the login is renamed to something that begins with <code class="highlighter-rouge">wp-admin</code> to match the first Page Rule, e.g. <code class="highlighter-rouge">wp-admin-mylogin</code>.</p>
<h2 id="result">Result</h2>
<p>The results are amazing, my average page load was around 1.5 seconds before this change, now I am around 0.5 seconds!</p>
<figure>
<noscript><img src="/assets/images/wordpress-caching-static-html-cloudflare-GTMetrix-pageload.png" /></noscript>
<img src="data:image/gif;base64,R0lGODlhAQABAAAAACH5BAEKAAEALAAAAAABAAEAAAICTAEAOw==" data-src="/assets/images/wordpress-caching-static-html-cloudflare-GTMetrix-pageload.png" alt="" class="lazyload fade-in" />
<figcaption>GTMetrix pageload after static HTML cache using CloudFlare</figcaption>
</figure>
<p>My server load has also been reduced dramatically since almost no requests ever hit my server again. In fact, I had to change my <a href="/code/free-wordpress-jetpack-monitor-replacement/">server monitor url</a> to include <code class="highlighter-rouge">?preview=true</code> in the request URL since I was receiving a cached version of my page. I did not even notice that my server had been down for several days! But then neither did my visitors.</p>
<h2 id="limitations">Limitations</h2>
<p>There are a few limitations to this approach:</p>
<ol>
<li>When updating / changing pages and posts in WordPress I need to manually clear the cache in CloudFlare. There are WordPress plugins to clear the CloudFlare cache automatically, but when I only post once or twice a month it is not a problem</li>
<li>I am currently using a geolocation aware cache to serve different content to my visitors based on their location. This does not work perfectly with this type of caching since <a href="https://www.cloudflare.com/network/">CloudFlares POP’s</a> (Point Of Presence) will cache the first request made be the nearest visitor - which could be a different country then the one the POP resides! Then this cached copy will be wrongly served to the next visitor. The way around this is to serve content dynamically, e.g. retrieve the location / country of the visitor using AJAX and modifying the page with javascript.</li>
</ol>
<p><a href="https://odd-one-out.serek.eu/code/wordpress-caching-static-html-cloudflare/" rel="nofollow">Turbocharging WordPress: Caching static HTML with CloudFlare for free</a> was originally published on Odd One Out.</p>
Poul Serekhttps://Ox3.serek.eu/about/Caching static WordPress HTML with CloudFlare for free. Faster pageload for served HTML.Latest stable LXD on Ubuntu 16.04 LTS2016-12-05T00:00:00+00:002016-12-05T19:37:59+00:00https://odd-one-out.serek.eu/code/latest-stable-lxd-ubuntu-16-04-lts
<p><img src="/assets/images/latest-stable-lxd-ubuntu-16-04-lts-feature.png" alt="" /></p>
<p>I have previously a small guide on how to get started with LXD 2.0 <a href="/code/lxd-2-0-container-hypervisor/">here</a>. This however install LXD 2.0.5 and the current stable version is 2.6.2 at the time of writing. The whole setup is on a KVM VPS at <a href="https://clientarea.ramnode.com/aff.php" rel="nofollow">RamNode</a>. Notice that OpenVZ VPS does not support LXD as the kernel is too old.</p>
<h2 id="installation-of-lxd-host">Installation of LXD host</h2>
<p>Lets add a repository to the newest stable LXD.</p>
<figure class="highlight"><pre><code class="language-terminal" data-lang="terminal"><span class="gp">$</span><span class="nb">sudo </span>add-apt-repository ppa:ubuntu-lxc/lxd-stable
<span class="go"> This PPA contains the latest stable release of LXD as well as the latest stable version of any of its dependencies.
More info: https://launchpad.net/~ubuntu-lxc/+archive/ubuntu/lxd-stable
Press [ENTER] to continue or ctrl-c or cancel adding it
gpg: keyring `/tmp/tmp9eaz0v8s/secring.gpg' created
gpg: keyring `/tmp/tmp9eaz0v8s/pubring.gpg' created
gpg: requesting key 7635B973 from hkp server keycenter.ubuntu.com
gpg: /tmp/tmp9eaz0v8s/trustdb.gpg: trustdb created
gpg: key 7635B973: public key "Launchpad PPA for Ubuntu LXC team" imported
gpg: Total number processed: 1
gpg: imported: 1 (RSA: 1)
OK</span></code></pre></figure>
<p>We then update and upgrade to get the latest stable version of LXD.</p>
<figure class="highlight"><pre><code class="language-terminal" data-lang="terminal"><span class="gp">$</span><span class="nb">sudo </span>apt-get update
<span class="gp">$</span><span class="nb">sudo </span>apt-get dist-upgrade</code></pre></figure>
<p>LXD should already be installed on Ubuntu 16.04.1, but if not execute the following. You can skip <code class="highlighter-rouge">zfsutils-linux</code> if you will not use ZFS.</p>
<figure class="highlight"><pre><code class="language-terminal" data-lang="terminal"><span class="gp">$</span><span class="nb">sudo </span>apt-get <span class="nb">install </span>lxd zfsutils-linux</code></pre></figure>
<p>Now everything is handled in the LXD configuration, even the LXD bridge. Replace <code class="highlighter-rouge">somepassword</code> with your own password. I choose only to have an IPv4 network, not IPv6. Replace <code class="highlighter-rouge">/dev/vda3</code> with your own device / partition for the ZFS storage.</p>
<div class="notice danger no_toc_section">
<p><strong>Warning!</strong> Notice that I create a new ZFS pool that wipes the chosen partition / device. Choose dir as the storage backend or a ZFS loop device (answer no to "Would you like to use an existing block device (yes/no)?") if you do not wish to wipe a partition / device and to prevent accidental data loss.</p>
</div>
<figure class="highlight"><pre><code class="language-terminal" data-lang="terminal"><span class="gp">$</span><span class="nb">sudo </span>lxd init
<span class="go">Name of the storage backend to use (dir or zfs) [default=zfs]:
Create a new ZFS pool (yes/no) [default=yes]?
Name of the new ZFS pool [default=lxd]:
Would you like to use an existing block device (yes/no) [default=no]? yes
Path to the existing block device: /dev/vda3
Would you like LXD to be available over the network (yes/no) [default=no]? yes
Address to bind LXD to (not including port) [default=all]:
Port to bind LXD to [default=8443]:
Trust password for new clients: somepassword
Again: somepassword
Would you like stale cached images to be updated automatically (yes/no) [default=yes]?
Do you want to configure the LXD bridge (yes/no) [default=yes]?
What should the new bridge be called [default=lxdbr0]?
What IPv4 subnet should be used (CIDR notation, ◼auto◼ or ◼none◼) [default=auto]? 10.0.0.1/24
Would you like LXD to NAT IPv4 traffic on your bridge? [default=yes]?
What IPv6 subnet should be used (CIDR notation, ◼auto◼ or ◼none◼) [default=auto]? none
LXD has been successfully configured.</span></code></pre></figure>
<p>We confirm the ZFS pool has been created</p>
<figure class="highlight"><pre><code class="language-terminal" data-lang="terminal"><span class="gp">$</span><span class="nb">sudo </span>zpool list lxd
<span class="go">NAME SIZE ALLOC FREE EXPANDSZ FRAG CAP DEDUP HEALTH ALTROOT
lxd 6.81G 90K 6.81G - 0% 0% 1.00x ONLINE -</span></code></pre></figure>
<h2 id="setup-a-container">Setup a container</h2>
<p>Lets create an Ubuntu container</p>
<figure class="highlight"><pre><code class="language-terminal" data-lang="terminal"><span class="gp">$</span>lxc launch ubuntu: test-container</code></pre></figure>
<p>After it is done we confirm that the container is running</p>
<figure class="highlight"><pre><code class="language-terminal" data-lang="terminal"><span class="gp">$</span>lxc list
<span class="go"> +-----------------+---------+------------------------+------+------------+-----------+
| NAME | STATE | IPV4 | IPV6 | TYPE | SNAPSHOTS |
+-----------------+---------+------------------------+------+------------+-----------+
| test-container | RUNNING | 10.0.0.218 (eth0) | | PERSISTENT | 0 |
+-----------------+---------+------------------------+------+------------+-----------+</span></code></pre></figure>
<p>We can now enter the container</p>
<figure class="highlight"><pre><code class="language-terminal" data-lang="terminal"><span class="gp">$</span>lxc <span class="nb">exec </span>test-container bash</code></pre></figure>
<p>Which will enter the container as root. Here we can as an example setup NGINX</p>
<figure class="highlight"><pre><code class="language-terminal" data-lang="terminal"><span class="gp">root@test-container:~#</span>apt-get <span class="nb">install </span>nginx</code></pre></figure>
<p>Then exit the container</p>
<figure class="highlight"><pre><code class="language-terminal" data-lang="terminal"><span class="gp">root@test-container:~#</span><span class="nb">exit</span></code></pre></figure>
<p>Which drops us back to the host. Here we can now access the webserver we just installed on the container</p>
<figure class="highlight"><pre><code class="language-terminal" data-lang="terminal"><span class="gp">$</span>wget http://10.0.0.218
<span class="go">--2016-10-22 23:20:15-- http://10.0.0.228/
Connecting to 10.0.0.228:80... connected.
HTTP request sent, awaiting response... 200 OK
Length: 632 [text/html]
Saving to: ◼index.html◼
</span><span class="gp">index.html 100%[==============================></span><span class="o">]</span> 632 <span class="nt">--</span>.-KB/s
<span class="go">
2016-10-22 23:20:16 (41.0 MB/s) - ◼index.html◼ saved [632/632]</span></code></pre></figure>
<p>Lastly I wanted a static ip address for my containers. This is now very easy using LXD 2.3 or higher.</p>
<figure class="highlight"><pre><code class="language-terminal" data-lang="terminal"><span class="gp">$</span>lxc stop test-container
<span class="gp">$</span>lxc network attach lxdbr0 test-container eth0
<span class="gp">$</span>lxc config device <span class="nb">set </span>test-container eth0 ipv4.address 10.0.0.7
<span class="gp">$</span>lxc start test-container
<span class="gp">$</span>lxc list
<span class="go"> +-----------------+---------+------------------------+------+------------+-----------+
| NAME | STATE | IPV4 | IPV6 | TYPE | SNAPSHOTS |
+-----------------+---------+------------------------+------+------------+-----------+
| test-container | RUNNING | 10.0.0.7 (eth0) | | PERSISTENT | 0 |
+-----------------+---------+------------------------+------+------------+-----------+</span></code></pre></figure>
<p><a href="https://odd-one-out.serek.eu/code/latest-stable-lxd-ubuntu-16-04-lts/" rel="nofollow">Latest stable LXD on Ubuntu 16.04 LTS</a> was originally published on Odd One Out.</p>
Poul Serekhttps://Ox3.serek.eu/about/Install and configure the latest stable LXD on Ubuntu 16.04 LTSMigrate OpenVZ container to another host without node access2016-11-11T00:00:00+00:002016-11-10T22:04:15+00:00https://odd-one-out.serek.eu/code/migrate-openvz-container-without-node-host-access
<p>Today I received a notice that all Crissic.net OpenVZ VPS customers are cancelled 30 days from now so I needed to figure out fast how to migrate my OpenVZ container to another hosting company. Lucky for me, the process took less than 10 minutes without access to the OpenVZ node. My setup is:</p>
<ul>
<li>OpenVZ container (Ubuntu 14.04.5 LTS)</li>
<li>CloudFlare DNS and HTTP proxy</li>
<li>SSH access to the OpenVZ container</li>
</ul>
<h2 id="prepare-new-container">Prepare new container</h2>
<ol>
<li>I created a new OpenVZ container at <a href="https://clientarea.ramnode.com/aff.php" rel="nofollow">RamNode</a> making sure to match the operating system.</li>
<li>Log in through SSH and make sure the system is fully updated
<div class="language-terminal highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="gp">$</span><span class="nb">sudo </span>apt-get update <span class="o">&&</span> <span class="nb">sudo </span>apt-get upgrade <span class="nt">-y</span>
</code></pre></div> </div>
</li>
</ol>
<h2 id="migrate-container">Migrate container</h2>
<ol>
<li>Log into the OpenVZ container you want to migrate</li>
<li>Make sure to update the container
<div class="language-terminal highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="gp">$</span><span class="nb">sudo </span>apt-get update <span class="o">&&</span> <span class="nb">sudo </span>apt-get upgrade <span class="nt">-y</span>
</code></pre></div> </div>
</li>
<li>Using rsync we transfer the content of the container to the destination container. Make sure to replace <code class="highlighter-rouge">root@168.235.90.201</code> with your own information
<div class="language-terminal highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="gp">$</span><span class="nb">sudo </span>rsync <span class="nt">-aAXv</span> <span class="nt">--exclude</span><span class="o">={</span><span class="s2">"/dev/*"</span>,<span class="s2">"/proc/*"</span>,<span class="s2">"/sys/*"</span>,<span class="s2">"/tmp/*"</span>,<span class="s2">"/run/*"</span>,<span class="s2">"/mnt/*"</span>,<span class="s2">"/media/*"</span>,<span class="s2">"/lost+found"</span><span class="o">}</span> / root@168.235.90.201:/
</code></pre></div> </div>
</li>
<li>When the transfer is complete, SSH into the new OpenVZ container. You might get the following warning because of the transfer.
<div class="language-terminal highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="gp">$</span>ssh root@168.235.90.201
<span class="go">@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@
@ WARNING: REMOTE HOST IDENTIFICATION HAS CHANGED! @
@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@
IT IS POSSIBLE THAT SOMEONE IS DOING SOMETHING NASTY!
Someone could be eavesdropping on you right now (man-in-the-middle attack)!
It is also possible that a host key has just been changed.
The fingerprint for the ECDSA key sent by the remote host is
SHA256:OzqhEWM2LyrsboJtVN/zSAT5jLY9jTALPoTakFJdyoc.
Please contact your system administrator.
Add correct host key in /Users/newUser/.ssh/known_hosts to get rid of this message.
Offending ECDSA key in /Users/newUser/.ssh/known_hosts:15
ECDSA host key for 168.235.90.201 has changed and you have requested strict checking.
Host key verification failed.
</span></code></pre></div> </div>
</li>
</ol>
<p>If so, edit the <code class="highlighter-rouge">/Users/newUser/.shh/known_hosts</code> file and remove the line containing <code class="highlighter-rouge">168.235.90.201</code> and try to SSH again.</p>
<ol>
<li>We reboot the container
<div class="language-terminal highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="gp">$</span><span class="nb">sudo </span>reboot
</code></pre></div> </div>
</li>
<li>Now we just change the DNS settings and the new container is accessible.</li>
</ol>
<figure>
<noscript><img src="/assets/images/migrate-openvz-container-without-node-host-access-CloudFlare-DNS-settings.png" /></noscript>
<img src="data:image/gif;base64,R0lGODlhAQABAAAAACH5BAEKAAEALAAAAAABAAEAAAICTAEAOw==" data-src="/assets/images/migrate-openvz-container-without-node-host-access-CloudFlare-DNS-settings.png" alt="" class="lazyload fade-in" />
<figcaption>CloudFlare DNS settings</figcaption>
</figure>
<h2 id="conclusion">Conclusion</h2>
<p>It really only took 10 minutes to migrate the whole setup to <a href="https://clientarea.ramnode.com/aff.php" rel="nofollow">RamNode</a>. It took me another 30 minutes to confirm everything was working because I really did not believe it was that easy.</p>
<p><a href="https://odd-one-out.serek.eu/code/migrate-openvz-container-without-node-host-access/" rel="nofollow">Migrate OpenVZ container to another host without node access</a> was originally published on Odd One Out.</p>
Poul Serekhttps://Ox3.serek.eu/about/Migrate OpenVZ container to another host without node accessGetting started with LXD 2.0 container hypervisor2016-10-23T00:00:00+00:002016-10-23T15:36:45+00:00https://odd-one-out.serek.eu/code/lxd-2-0-container-hypervisor
<p><img src="/assets/images/lxd-2-0-container-hypervisor-feature.png" alt="" /></p>
<p>For the past few years I have been migrating between different <a href="https://en.wikipedia.org/wiki/OpenVZ" rel="nofollow">OpenVZ</a> <a href="https://en.wikipedia.org/wiki/Virtual_private_server" rel="nofollow">VPS</a> providers and each time it has been a hassle to migrate and upgrade my virtual machines between them. Then I stumbled upon <a href="https://www.ubuntu.com/cloud/lxd" rel="nofollow">LXD</a> which in combination with a <a href="https://en.wikipedia.org/wiki/Kernel-based_Virtual_Machine" rel="nofollow">KVM</a> VPS seems like a perfect fit for running multiple containers on a single KVM VPS and migrating them to and from others LXD hosts with a single command! Notice that using OpenVZ VPS’es is not possible since the kernel is too old. For this post I used <a href="https://www.virtualbox.org" rel="nofollow">VirtualBox</a>, but plan on migrating the whole setup to a KVM VPS at <a href="https://clientarea.ramnode.com/aff.php" rel="nofollow">RamNode</a> which I highly recommend.</p>
<div class="notice info no_toc_section">
<p>Check out my post <a href="/code/latest-stable-lxd-ubuntu-16-04-lts/">here</a> for installing the latest stable version of LXD on Ubuntu 16.04</p>
</div>
<h2 id="installation-of-lxd-host">Installation of LXD host</h2>
<p>Start by installing a copy of the newest Ubuntu server on VirtualBox, I used Ubuntu 16.04.1 LTS. This already comes with LXD which just need to be configured. I configured LXD with the default values except saying yes to making LXD available over the network. Replace <code class="highlighter-rouge">somepassword</code> with your own password.</p>
<figure class="highlight"><pre><code class="language-terminal" data-lang="terminal"><span class="gp">$</span><span class="nb">sudo </span>lxd init
<span class="go">Name of the storage backend to use (dir or zfs) [default=dir]: dir
Would you like LXD to be available over the network (yes/no) [default=no]? yes
Address to bind LXD to (not including port) [default=all]: all
Port to bind LXD to [default=8443]: 8443
Trust password for new clients: somepassword
Again: somepassword
Do you want to configure the LXD bridge (yes/no) [default=yes]? yes</span></code></pre></figure>
<p>This will open the bridge configurator. I used the default values.</p>
<figure>
<noscript><img src="/assets/images/lxd-2-0-container-hypervisor-LXD_Bridge_01.png" /></noscript>
<img src="data:image/gif;base64,R0lGODlhAQABAAAAACH5BAEKAAEALAAAAAABAAEAAAICTAEAOw==" data-src="/assets/images/lxd-2-0-container-hypervisor-LXD_Bridge_01.png" alt="" class="lazyload fade-in" />
<figcaption>01: LXD bridge configuration</figcaption>
</figure>
<figure>
<noscript><img src="/assets/images/lxd-2-0-container-hypervisor-LXD_Bridge_02.png" /></noscript>
<img src="data:image/gif;base64,R0lGODlhAQABAAAAACH5BAEKAAEALAAAAAABAAEAAAICTAEAOw==" data-src="/assets/images/lxd-2-0-container-hypervisor-LXD_Bridge_02.png" alt="" class="lazyload fade-in" />
<figcaption>02: Bridge name</figcaption>
</figure>
<figure>
<noscript><img src="/assets/images/lxd-2-0-container-hypervisor-LXD_Bridge_03.png" /></noscript>
<img src="data:image/gif;base64,R0lGODlhAQABAAAAACH5BAEKAAEALAAAAAABAAEAAAICTAEAOw==" data-src="/assets/images/lxd-2-0-container-hypervisor-LXD_Bridge_03.png" alt="" class="lazyload fade-in" />
<figcaption>03: IPv4 subnet</figcaption>
</figure>
<figure>
<noscript><img src="/assets/images/lxd-2-0-container-hypervisor-LXD_Bridge_04.png" /></noscript>
<img src="data:image/gif;base64,R0lGODlhAQABAAAAACH5BAEKAAEALAAAAAABAAEAAAICTAEAOw==" data-src="/assets/images/lxd-2-0-container-hypervisor-LXD_Bridge_04.png" alt="" class="lazyload fade-in" />
<figcaption>04: IPv4 address</figcaption>
</figure>
<figure>
<noscript><img src="/assets/images/lxd-2-0-container-hypervisor-LXD_Bridge_05.png" /></noscript>
<img src="data:image/gif;base64,R0lGODlhAQABAAAAACH5BAEKAAEALAAAAAABAAEAAAICTAEAOw==" data-src="/assets/images/lxd-2-0-container-hypervisor-LXD_Bridge_05.png" alt="" class="lazyload fade-in" />
<figcaption>05: IPv4 CIDR mask</figcaption>
</figure>
<figure>
<noscript><img src="/assets/images/lxd-2-0-container-hypervisor-LXD_Bridge_06.png" /></noscript>
<img src="data:image/gif;base64,R0lGODlhAQABAAAAACH5BAEKAAEALAAAAAABAAEAAAICTAEAOw==" data-src="/assets/images/lxd-2-0-container-hypervisor-LXD_Bridge_06.png" alt="" class="lazyload fade-in" />
<figcaption>06: First DHCP address</figcaption>
</figure>
<figure>
<noscript><img src="/assets/images/lxd-2-0-container-hypervisor-LXD_Bridge_07.png" /></noscript>
<img src="data:image/gif;base64,R0lGODlhAQABAAAAACH5BAEKAAEALAAAAAABAAEAAAICTAEAOw==" data-src="/assets/images/lxd-2-0-container-hypervisor-LXD_Bridge_07.png" alt="" class="lazyload fade-in" />
<figcaption>07: Last DHCP address</figcaption>
</figure>
<figure>
<noscript><img src="/assets/images/lxd-2-0-container-hypervisor-LXD_Bridge_08.png" /></noscript>
<img src="data:image/gif;base64,R0lGODlhAQABAAAAACH5BAEKAAEALAAAAAABAAEAAAICTAEAOw==" data-src="/assets/images/lxd-2-0-container-hypervisor-LXD_Bridge_08.png" alt="" class="lazyload fade-in" />
<figcaption>08: Max number of DHCP clients</figcaption>
</figure>
<figure>
<noscript><img src="/assets/images/lxd-2-0-container-hypervisor-LXD_Bridge_09.png" /></noscript>
<img src="data:image/gif;base64,R0lGODlhAQABAAAAACH5BAEKAAEALAAAAAABAAEAAAICTAEAOw==" data-src="/assets/images/lxd-2-0-container-hypervisor-LXD_Bridge_09.png" alt="" class="lazyload fade-in" />
<figcaption>09: NAT IPv4 traffic</figcaption>
</figure>
<figure>
<noscript><img src="/assets/images/lxd-2-0-container-hypervisor-LXD_Bridge_10.png" /></noscript>
<img src="data:image/gif;base64,R0lGODlhAQABAAAAACH5BAEKAAEALAAAAAABAAEAAAICTAEAOw==" data-src="/assets/images/lxd-2-0-container-hypervisor-LXD_Bridge_10.png" alt="" class="lazyload fade-in" />
<figcaption>10: IPv6 subnet</figcaption>
</figure>
<p>This should end with a <code class="highlighter-rouge">LXD has been successfully configured</code> message.</p>
<h2 id="setup-a-container">Setup a container</h2>
<p>Lets create an Ubuntu container</p>
<figure class="highlight"><pre><code class="language-terminal" data-lang="terminal"><span class="gp">$</span>lxc launch ubuntu: test-container</code></pre></figure>
<p>After it is done we confirm that the container is running</p>
<figure class="highlight"><pre><code class="language-terminal" data-lang="terminal"><span class="gp">$</span>lxc list
<span class="go"> +-----------------+---------+------------------------+------+------------+-----------+
| NAME | STATE | IPV4 | IPV6 | TYPE | SNAPSHOTS |
+-----------------+---------+------------------------+------+------------+-----------+
| test-container | RUNNING | 10.0.0.218 (eth0) | | PERSISTENT | 0 |
+-----------------+---------+------------------------+------+------------+-----------+</span></code></pre></figure>
<p>That’s it! We can now enter the container</p>
<figure class="highlight"><pre><code class="language-terminal" data-lang="terminal"><span class="gp">$</span>lxc <span class="nb">exec </span>test-container bash</code></pre></figure>
<p>Which will enter the container as root</p>
<figure class="highlight"><pre><code class="language-terminal" data-lang="terminal"><span class="gp">root@test-container:~#</span></code></pre></figure>
<p>Here we can as an example setup NGINX</p>
<figure class="highlight"><pre><code class="language-terminal" data-lang="terminal"><span class="gp">root@test-container:~#</span>apt-get <span class="nb">install </span>nginx</code></pre></figure>
<p>Then exit the container</p>
<figure class="highlight"><pre><code class="language-terminal" data-lang="terminal"><span class="gp">root@test-container:~#</span><span class="nb">exit</span></code></pre></figure>
<p>Which drops us back to the host. Here we can now access the webserver we just installed on the container</p>
<figure class="highlight"><pre><code class="language-terminal" data-lang="terminal"><span class="gp">$</span>wget http://10.0.0.218
<span class="go">--2016-10-22 23:20:15-- http://10.0.0.228/
Connecting to 10.0.0.228:80... connected.
HTTP request sent, awaiting response... 200 OK
Length: 632 [text/html]
Saving to: ◼index.html◼
</span><span class="gp">index.html 100%[==============================></span><span class="o">]</span> 632 <span class="nt">--</span>.-KB/s
<span class="go">
2016-10-22 23:20:16 (41.0 MB/s) - ◼index.html◼ saved [632/632]</span></code></pre></figure>
<p>Lastly I wanted a static ip address for my containers</p>
<figure class="highlight"><pre><code class="language-terminal" data-lang="terminal"><span class="gp">$</span><span class="nb">sudo </span>nano /etc/default/lxd-bridge</code></pre></figure>
<p>Find the line <code class="highlighter-rouge">LXD_CONFILE</code> and change it to <code class="highlighter-rouge">LXD_CONFILE="/etc/default/lxd_dnsmasq.conf"</code> and save the file. Next we create the <code class="highlighter-rouge">lxd_dnsmasq.conf</code> file which will contain our static ip mapping</p>
<figure class="highlight"><pre><code class="language-terminal" data-lang="terminal"><span class="gp">$</span><span class="nb">sudo </span>nano /etc/default/lxd_dnsmasq.conf</code></pre></figure>
<p>For each container add a line <code class="highlighter-rouge">dhcp-host=container_name,ip_address</code>, for our container we add the following in the beginning of the file <code class="highlighter-rouge">dhcp-host=test-container,10.0.0.60</code> and save the file.
Now stop the container</p>
<figure class="highlight"><pre><code class="language-terminal" data-lang="terminal"><span class="gp">$</span>lxc stop test-container</code></pre></figure>
<p>Restart the LXD bridge</p>
<figure class="highlight"><pre><code class="language-terminal" data-lang="terminal"><span class="gp">$</span><span class="nb">sudo </span>service lxd-bridge stop <span class="o">&&</span> <span class="nb">sudo </span>service lxd-bridge start</code></pre></figure>
<p>And start the container again</p>
<figure class="highlight"><pre><code class="language-terminal" data-lang="terminal"><span class="gp">$</span>lxc start test-container</code></pre></figure>
<p>Now this container will always use ip 10.0.0.60</p>
<figure class="highlight"><pre><code class="language-terminal" data-lang="terminal"><span class="gp">$</span>lxc list
<span class="go"> +-----------------+---------+------------------------+------+------------+-----------+
| NAME | STATE | IPV4 | IPV6 | TYPE | SNAPSHOTS |
+-----------------+---------+------------------------+------+------------+-----------+
| test-container | RUNNING | 10.0.0.60 (eth0) | | PERSISTENT | 0 |
+-----------------+---------+------------------------+------+------------+-----------+</span></code></pre></figure>
<h2 id="migrate-containers-between-lxd-hosts">Migrate containers between LXD hosts</h2>
<p>Lets assume that this LXD host we have created has the ip of 192.168.1.20. And lets assume we have another LXD host at ip 192.168.1.30 (if you used VirtualBox for this guide you can just clone the whole setup and change the ip). We want to migrate the <code class="highlighter-rouge">test-container</code> from 192.168.1.20 to 192.168.1.30.</p>
<h3 id="both-lxd-hosts">Both LXD hosts</h3>
<p>First we install <code class="highlighter-rouge">criu</code> on both LXD hosts.</p>
<figure class="highlight"><pre><code class="language-terminal" data-lang="terminal"><span class="gp">$</span><span class="nb">sudo </span>apt-get <span class="nb">install </span>criu</code></pre></figure>
<h3 id="192168130-host">192.168.1.30 host</h3>
<p>Then we add a reference to 192.168.1.20 from 192.168.1.30. Replace <code class="highlighter-rouge">somepassword</code> with your own password from the beginning of this post.</p>
<figure class="highlight"><pre><code class="language-terminal" data-lang="terminal"><span class="gp">$</span>lxc remote add host1 192.168.1.20:8443
<span class="go">Certificate fingerprint:
a4056e1b82fed6ebca4447c93926003efe3273903df79b131c4c315350812fff
ok (y/n)? y
</span><span class="gp">Admin password for host1: #</span> somepassword
<span class="go">Client certificate stored at server: host1</span></code></pre></figure>
<p>We can now move or copy <code class="highlighter-rouge">test-container</code> to 192.168.1.30</p>
<figure class="highlight"><pre><code class="language-terminal" data-lang="terminal"><span class="gp">$</span>lxc copy host1:test-container test-container</code></pre></figure>
<p>This can take a long time if the container is large or the connection is slow. For me it takes less than a minute, but there is no progress indicator at the moment.
Lastly we check if the new container is transferred.</p>
<figure class="highlight"><pre><code class="language-terminal" data-lang="terminal"><span class="gp">$</span>lxc list
<span class="go"> +-----------------+---------+------------------------+------+------------+-----------+
| NAME | STATE | IPV4 | IPV6 | TYPE | SNAPSHOTS |
+-----------------+---------+------------------------+------+------------+-----------+
| test-container | RUNNING | 10.0.0.60 (eth0) | | PERSISTENT | 0 |
+-----------------+---------+------------------------+------+------------+-----------+</span></code></pre></figure>
<p>Since we cloned this host from 192.168.1.20, it also remembers our configuration that a container named <code class="highlighter-rouge">test-container</code> will get the ip 10.0.0.60. Notice that a copy will stop the original container. To prevent this we can take a snapshot first and then transfer the snapshot.</p>
<figure class="highlight"><pre><code class="language-terminal" data-lang="terminal"><span class="gp">$</span>lxc snapshot host1:test-container current
<span class="gp">$</span>lxc copy host1:test-container/current test-container
<span class="gp">$</span>lxc start test-container</code></pre></figure>
<p>Lastly one can play around with live migration, but I choose not to do that yet since it is an experimental feature in LXD 2.0, but take a look at <a href="https://www.stgraber.org/2016/04/25/lxd-2-0-live-migration-912/">this</a> post for more information.</p>
<p><a href="https://odd-one-out.serek.eu/code/lxd-2-0-container-hypervisor/" rel="nofollow">Getting started with LXD 2.0 container hypervisor</a> was originally published on Odd One Out.</p>
Poul Serekhttps://Ox3.serek.eu/about/Getting started with LXD 2.0 container hypervisorHTTP/2 Server Push with NGINX, CloudFlare and WordPress2016-08-10T00:00:00+00:002016-08-10T20:19:02+00:00https://odd-one-out.serek.eu/code/http2-server-push-nginx-cloudflare-wordpress
<p><img src="/assets/images/http2-server-push-nginx-cloudflare-wordpress-http2-server-feature.png" alt="" /></p>
<p>With CloudFlare <a href="https://blog.cloudflare.com/announcing-support-for-http-2-server-push-2/">supporting</a> HTTP/2 and Server Push, we can leverage that by using CloudFlare as a CDN even though NGINX does not support Server Push.</p>
<h2 id="setup-cloudflare">Setup CloudFlare</h2>
<ol>
<li>Login / create an account at <a href="https://www.cloudflare.com">CloudFlare.com</a></li>
<li>Make sure you use CloudFlares nameservers as your primary nameservers. Your domain must resolve using CloudFlares DNS server</li>
<li>Go to DNS settings and create an A record pointing to your WordPress domain, e.g. blog.example.com</li>
<li>Make sure to choose “DNS and HTTP proxy (CDN)” and not “DNS only”. Every requests to blog.example.com will go through CloudFlare which does its CDN magic. The added bonus is that everything runs under the same address so you save a DNS lookup to e.g. cdn.blog.example.com</li>
</ol>
<h2 id="setup-wordpress">Setup WordPress</h2>
<ol>
<li>Make sure to disable your current CDN if using any. Since all traffic goes through CloudFlare now they will now automatically serve your static resources via their CDN network. I am using WP Super Cache for WordPress and it was as simple as unselecting a checkbox.</li>
</ol>
<figure>
<noscript><img src="/assets/images/http2-server-push-nginx-cloudflare-wordpress-wp-super-cache-cdn.png" /></noscript>
<img src="data:image/gif;base64,R0lGODlhAQABAAAAACH5BAEKAAEALAAAAAABAAEAAAICTAEAOw==" data-src="/assets/images/http2-server-push-nginx-cloudflare-wordpress-wp-super-cache-cdn.png" alt="" class="lazyload fade-in" />
<figcaption>Be sure to disable your traditional CDN. Example shown for WP Super Cache for WordPress.</figcaption>
</figure>
<h2 id="setup-nginx">Setup NGINX</h2>
<ol>
<li>NGINX needs to send a link header for each static resource to preload. Identify these using Chromes built-in developer tools.</li>
</ol>
<figure>
<noscript><img src="/assets/images/http2-server-push-nginx-cloudflare-wordpress-http2-server-push-none.png" /></noscript>
<img src="data:image/gif;base64,R0lGODlhAQABAAAAACH5BAEKAAEALAAAAAABAAEAAAICTAEAOw==" data-src="/assets/images/http2-server-push-nginx-cloudflare-wordpress-http2-server-push-none.png" alt="" class="lazyload fade-in" />
<figcaption>Using Chrome to identify a websites static resources</figcaption>
</figure>
<ol start="2">
<li>For each identified static resource to preload, add an add_header statement to your NGINX server block. Examples are given below for scripts, stylesheets and images.
<div class="language-terminal highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="gp">add_header link "</wp-includes/js/jquery/jquery.js></span><span class="p">;</span> <span class="nv">rel</span><span class="o">=</span>preload<span class="p">;</span> <span class="nv">as</span><span class="o">=</span>script<span class="s2">";
</span><span class="gp">add_header link "</wp-content/themes/twentyfifteen/genericons/genericons.css></span><span class="s2">; rel=preload; as=styles"</span><span class="p">;</span>
<span class="gp">add_header link "</wp-content/uploads/201604/image.jpg></span><span class="p">;</span> <span class="nv">rel</span><span class="o">=</span>preload<span class="p">;</span> <span class="nv">as</span><span class="o">=</span>image<span class="s2">";
</span></code></pre></div> </div>
</li>
<li>Restart NGINX, e.g. on Ubuntu type <code class="highlighter-rouge">sudo service nginx restart</code></li>
</ol>
<p>Notice that I could not get my fonts preloaded, if you have a working example of preloading a woff2 font, please leave a comment.</p>
<h2 id="test">Test</h2>
<p>The best way to confirm everything is working is downloading and installing <a href="https://www.google.com/chrome/browser/canary.html">Google Chrome Canary</a>, the newest bleeding edge Chrome browser. Open developer tools, check <code class="highlighter-rouge">disable cache</code> and enter your website url. You should now see <code class="highlighter-rouge">Push / ...</code> in the <code class="highlighter-rouge">Initiator</code> column for each preloaded resource. If it does work try clearing your browser cache.</p>
<figure>
<noscript><img src="/assets/images/http2-server-push-nginx-cloudflare-wordpress-http2-server-push.png" /></noscript>
<img src="data:image/gif;base64,R0lGODlhAQABAAAAACH5BAEKAAEALAAAAAABAAEAAAICTAEAOw==" data-src="/assets/images/http2-server-push-nginx-cloudflare-wordpress-http2-server-push.png" alt="" class="lazyload fade-in" />
<figcaption>Example of resources delivered using HTTP/2 Server Push. Notice the difference compared to non Sever Push resources. It cuts about 100 ms from the timeline.</figcaption>
</figure>
<p>For me I see about 100 ms faster load of the resources I preload, longer if the network is slow. It might not look like much, but with a pageload around 1 second, that is still 10% faster pageload if you can do this for every resource. Or you might just have a single resource or two that takes much longer to load that you could Server Push. If you look closer at the header for the first line, the HTML document, you will see a <code class="highlighter-rouge">f-h2-pushed</code> header. This header confirms that CloudFlare took all the Link headers we send and pushed them out to the browser, e.g. example from my site:</p>
<div class="language-terminal highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="gp">cf-h2-pushed: </wp-includes/js/jquery/jquery.js></span>,</wp-content/themes/twenty...genericons/genericons.css>,</wp-content/themes/twentyfifteen-child/style.css>,</wp-content/themes/twentyfifteen/style.css>,</wp-includes/js/jquery/jquery-migrate.min.js>,</wp-content/custom/analytics.js>,</wp-content/themes/twenty...js/skip-link-focus-fix.js>,</wp-includes/js/comment-reply.min.js>,</wp-content/themes/twentyfifteen/js/functions.js>,</wp-includes/js/jquery/jquery.form.min.js>,</wp-includes/js/wp-embed.min.js>
</code></pre></div></div>
<p>If you see a link header for each resource you want to preload, CloudFlare did not do its magic.</p>
<h2 id="conclusion">Conclusion</h2>
<p>An easy way to get a bit faster pageload if you can do this for all your resources or just the one really slow loading resource. It does require that the resources are hosted under your own domain so no external resources and that you use CloudFlare as a DNS and CDN. For now it works best on static resources you know each pageload needs, but for more dynamic content like images it works poorly. There are WordPress plugins for adding the preload tag for images, scripts and stylesheets, these however do not work when serving static html version of PHP pages since the PHP processor is never hit, only the first time. For me that means I need to disable WP Super Cache or just add the headers manually. Googles PageSpeed module for NGINX might at some point be able to handle it dynamic content better.</p>
<p><a href="https://odd-one-out.serek.eu/code/http2-server-push-nginx-cloudflare-wordpress/" rel="nofollow">HTTP/2 Server Push with NGINX, CloudFlare and WordPress</a> was originally published on Odd One Out.</p>
Poul Serekhttps://Ox3.serek.eu/about/HTTP/2 Server Push with NGINX, CloudFlare and WordPressFree email forward with Mailgun.com2016-08-06T00:00:00+00:002016-08-06T15:45:26+00:00https://odd-one-out.serek.eu/code/free-email-forward-mailgun-gmail
<p><img src="/assets/images/free-email-forward-mailgun-gmail-feature.png" alt="" /></p>
<p>I recently started moving away from my Danish Domain and DNS provider GratisDNS which charges me about $7.5 per year just for an email forward service which I use to be able to send and receive emails as serek.eu from my gmail account. This can be done for free using <a href="https://mailgun.com" rel="nofollow">Mailgun.com</a>! This includes unlimited incoming mails and 10.000 outgoing mails per month which is more than enough for my needs.</p>
<p>This guide shows how to</p>
<ul>
<li>Forward incoming emails to any email address using you own domain (e.g. <code class="highlighter-rouge">user@example.com</code> –> <code class="highlighter-rouge">user@gmail.com</code>)</li>
<li>Send emails from gmail as <code class="highlighter-rouge">user@example.com</code></li>
</ul>
<p>Replace <code class="highlighter-rouge">example.com</code> and all emails with your own.</p>
<h2 id="initial-setup">Initial setup</h2>
<ol>
<li>Create an account at <a href="https://mailgun.com" rel="nofollow">Mailgun.com</a>.</li>
<li>Login to Mailgun.com and click <code class="highlighter-rouge">Add new domain</code> under <code class="highlighter-rouge">Domains</code>. Ignore the recommendation to use a subdomain, just add your base domain, e.g. <code class="highlighter-rouge">example.com</code>.</li>
<li>After adding the domain successfully you will be shown a <code class="highlighter-rouge">Now Follow These Steps To Verify Your Domain</code> page. Follow the instructions to add the two TXT records, two MX records and the CNAME entry to your DNS settings. Remember to remove existing MX settings for the chosen domain that to prevent conflicts.</li>
<li>Confirm that the DNS works by going to <code class="highlighter-rouge">Domains</code> –> Click on your domain –> Click <code class="highlighter-rouge">Domain Verification & DNS</code> and lastly click <code class="highlighter-rouge">Check DNS Records Now</code>. It might take a day or two for everything to confirm depending on your existing DNS / TTL settings.</li>
</ol>
<h2 id="setup-incoming-emails">Setup incoming emails</h2>
<ol>
<li>In Mailgun.com go to <code class="highlighter-rouge">Routes</code> and click <code class="highlighter-rouge">Create route</code></li>
<li>Configure a mail forward as follows:
<ul>
<li>Expression Type –> <code class="highlighter-rouge">Match recipient</code></li>
<li>Recipient –> <code class="highlighter-rouge">user@example.com</code></li>
<li>Action –> Check <code class="highlighter-rouge">Forward</code> and enter the email to forward to, e.g. <code class="highlighter-rouge">user@gmail.com</code></li>
<li>Priority –> Set to <code class="highlighter-rouge">10</code></li>
<li>Description –> Add the same as in <code class="highlighter-rouge">Recipient</code></li>
<li>Click <code class="highlighter-rouge">Create route</code></li>
</ul>
</li>
<li>Repeat step 2 for each email forward you want.</li>
</ol>
<figure>
<noscript><img src="/assets/images/free-email-forward-mailgun-gmail-feature.png" /></noscript>
<img src="data:image/gif;base64,R0lGODlhAQABAAAAACH5BAEKAAEALAAAAAABAAEAAAICTAEAOw==" data-src="/assets/images/free-email-forward-mailgun-gmail-feature.png" alt="" class="lazyload fade-in" />
<figcaption>Example of Mailgun.com routes for email forward</figcaption>
</figure>
<ol start="4">
<li>(Optional) Create a route to catch every mail not caught using <code class="highlighter-rouge">Match recipient</code>. This will ensure that every possible recipient using <code class="highlighter-rouge">@example.com</code> will be caught and forwarded.
<ul>
<li>Expression Type –> <code class="highlighter-rouge">Catch All</code></li>
<li>Action –> Check <code class="highlighter-rouge">Forward</code> and enter the email to forward to, e.g. <code class="highlighter-rouge">user@gmail.com</code></li>
<li>Priority –> Set to <code class="highlighter-rouge">99</code></li>
<li>Description –> <code class="highlighter-rouge">Catch all mailforward</code></li>
<li>Click <code class="highlighter-rouge">Create route</code></li>
</ul>
</li>
</ol>
<p>Thats it, you can now receive emails on <code class="highlighter-rouge">@example.com</code>.</p>
<h2 id="setup-outgoing-emails">Setup outgoing emails</h2>
<ol>
<li>Log into gmail</li>
<li>Go to <code class="highlighter-rouge">Settings</code> –> <code class="highlighter-rouge">Accounts and Import</code></li>
<li>Go to section <code class="highlighter-rouge">Send mail as</code> and click <code class="highlighter-rouge">Add another email address that you own</code></li>
<li>Enter an <code class="highlighter-rouge">@example.com</code> email address and click <code class="highlighter-rouge">Next Step</code></li>
<li>Configure the form with information from Mailgun.com –> <code class="highlighter-rouge">Domains</code> –> <code class="highlighter-rouge">example.com</code>
<ul>
<li>SMTP Server –> <code class="highlighter-rouge">SMTP Hostname</code> from Mailgun</li>
<li>Port –> <code class="highlighter-rouge">587</code></li>
<li>Username –> <code class="highlighter-rouge">Default SMTP Login</code> from Mailgun</li>
<li>Password –> <code class="highlighter-rouge">Default Password</code> from Mailgun</li>
<li>Check <code class="highlighter-rouge">Secured connection using TLS</code></li>
<li>Click <code class="highlighter-rouge">Save Changes</code></li>
</ul>
</li>
</ol>
<figure>
<noscript><img src="/assets/images/free-email-forward-mailgun-gmail-send-as-settings.png" /></noscript>
<img src="data:image/gif;base64,R0lGODlhAQABAAAAACH5BAEKAAEALAAAAAABAAEAAAICTAEAOw==" data-src="/assets/images/free-email-forward-mailgun-gmail-send-as-settings.png" alt="" class="lazyload fade-in" />
<figcaption>Example of gmail send as settings using Mailgun.com SMTP settings</figcaption>
</figure>
<ol start="6">
<li>Repeat step 3 - 5 for each email you would like to be able to send as</li>
<li>Go back to <code class="highlighter-rouge">Settings</code> –> <code class="highlighter-rouge">Accounts and Import</code> –> <code class="highlighter-rouge">Send mail as</code>. Choose which email is the default to use when sending mails and which email to use when replying</li>
</ol>
<p>Thats it! When composing a new email from gmail you now get a dropdownlist in the <code class="highlighter-rouge">From</code> field where you can choose who to send as.</p>
<figure>
<noscript><img src="/assets/images/free-email-forward-mailgun-gmail-send-mail-as.png" /></noscript>
<img src="data:image/gif;base64,R0lGODlhAQABAAAAACH5BAEKAAEALAAAAAABAAEAAAICTAEAOw==" data-src="/assets/images/free-email-forward-mailgun-gmail-send-mail-as.png" alt="" class="lazyload fade-in" />
<figcaption>Send email from gmail using your own domain</figcaption>
</figure>
<p><a href="https://odd-one-out.serek.eu/code/free-email-forward-mailgun-gmail/" rel="nofollow">Free email forward with Mailgun.com</a> was originally published on Odd One Out.</p>
Poul Serekhttps://Ox3.serek.eu/about/Free email forward with Mailgun.comHOOD 001 cargo bag review2016-05-23T00:00:00+00:002016-05-22T22:31:45+00:00https://odd-one-out.serek.eu/reviews/bullitt-hood-001-review
<p><img src="/assets/images/bullitt-hood-001-review-feature.jpg" alt="" /></p>
<p>I was lucky enough to get the Bullitt Hood 001 at a reduced price from <a href="http://www.fahrer-berlin.de">fahrer-berlin.de</a>. This is basically a permanently attached weatherproof bag for the Bullitt cargo bike that can be collapsed when not needed. The Hood 002 is the same bag, just in red. The cargo bag is attached as a backplate using four screws. When the Hood 001 cargo bag is attached and collapsed, it only takes up 3 centimetres of space and keeps most of the cargo area free.</p>
<figure>
<noscript><img src="/assets/images/bullitt-hood-001-review-hood-closed.jpg" /></noscript>
<img src="data:image/gif;base64,R0lGODlhAQABAAAAACH5BAEKAAEALAAAAAABAAEAAAICTAEAOw==" data-src="/assets/images/bullitt-hood-001-review-hood-closed.jpg" alt="" class="lazyload fade-in" />
<figcaption>Hood 001 cargo bag in closed condition. It takes up about 3 cm when closed</figcaption>
</figure>
<p>The Bullitt is designed around the standard Danish moving box, which is not standard at all. Depending in the thickness of the cardboard, these fluctuate in size and I have seen lengths of 60, 67, and 70 centimetres. The 3 centimetres the Hood 001 adds is therefore enough to prevent using the largest Danish moving box which at 70 centimetres. However, most moving boxes are only 67 centimetres long or less and these fit perfectly.</p>
<figure>
<noscript><img src="/assets/images/bullitt-hood-001-review-hood-movingbox.jpg" /></noscript>
<img src="data:image/gif;base64,R0lGODlhAQABAAAAACH5BAEKAAEALAAAAAABAAEAAAICTAEAOw==" data-src="/assets/images/bullitt-hood-001-review-hood-movingbox.jpg" alt="" class="lazyload fade-in" />
<figcaption>The Bullitt is designed for the Danish moving box, but the Bullitt adds 3 cm when closed and this means that the there is not enough room for the largest moving box at 70 centimetres. Moving boxes of 67 centimetres or below should fit just fine.</figcaption>
</figure>
<p>One can always store the moving box the other way, but then the Bullitt is no longer slim. To expand the cargo bag, one just have to unzip the bag and using a simple strap attach it around the front of the Bullit cargobike.</p>
<figure>
<noscript><img src="/assets/images/bullitt-hood-001-review-hood-strap.jpg" /></noscript>
<img src="data:image/gif;base64,R0lGODlhAQABAAAAACH5BAEKAAEALAAAAAABAAEAAAICTAEAOw==" data-src="/assets/images/bullitt-hood-001-review-hood-strap.jpg" alt="" class="lazyload fade-in" />
<figcaption>The Hood 001 cargo bag is secured to the front using a simple strap around the honeycomb board</figcaption>
</figure>
<p>This does not take that long once you have tried it a few times, the video below is my fifth try. It takes about a minute to expand or collapse the bag.</p>
<div class="responsive-video-container"><iframe width="640" height="360" data-src="https://www.youtube-nocookie.com/embed/tPfm1cy7SnQ?showinfo=0" frameborder="0" webkitallowfullscreen="" mozallowfullscreen="" allowfullscreen="" class="lazyload"></iframe></div>
<p>The bag is not a 100% snug fit on my bike, there is a small gap between the bag and the front of the bike. This is not a practical problem, it would just look better with a snug fit. I have asked Fahrer-berlin.de why this gap exists and they say you need the gap to retighten the bag. The bag slightly expands with the time of use and when it is wet so the fit will be better over time. Lastly the new versions of these bags have a smaller gap from the start.</p>
<figure>
<noscript><img src="/assets/images/bullitt-hood-001-review-hood-gap.jpg" /></noscript>
<img src="data:image/gif;base64,R0lGODlhAQABAAAAACH5BAEKAAEALAAAAAABAAEAAAICTAEAOw==" data-src="/assets/images/bullitt-hood-001-review-hood-gap.jpg" alt="" class="lazyload fade-in" />
<figcaption>The cargobag is not a perfect fit, there is a small gap between the bag and the honeycomb board</figcaption>
</figure>
<p>The top can also be rolled up on the side to have an open bag.</p>
<figure>
<noscript><img src="/assets/images/bullitt-hood-001-review-hood-open.jpg" /></noscript>
<img src="data:image/gif;base64,R0lGODlhAQABAAAAACH5BAEKAAEALAAAAAABAAEAAAICTAEAOw==" data-src="/assets/images/bullitt-hood-001-review-hood-open.jpg" alt="" class="lazyload fade-in" />
<figcaption>The top of the Hood 001 can be rolled up on the side.</figcaption>
</figure>
<p>A minor detail is the mesh pocket on the back panel for storage and four small rings for securing content using straps (not included). The straps and minor content can be left in the bag even when in a collapsed state.</p>
<figure>
<noscript><img src="/assets/images/bullitt-hood-001-review-hood-hooks-for-straps.jpg" /></noscript>
<img src="data:image/gif;base64,R0lGODlhAQABAAAAACH5BAEKAAEALAAAAAABAAEAAAICTAEAOw==" data-src="/assets/images/bullitt-hood-001-review-hood-hooks-for-straps.jpg" alt="" class="lazyload fade-in" />
<figcaption>Four small hooks can be used for securing content with straps.</figcaption>
</figure>
<p>Ultimately, I really like this bag and have used it more than I thought I would. I am sure that the <a href="http://shop.larryvsharry.com/shop/accessories/bbx-side-panel-kit-race-green.html">BBX Side Panel Kit</a> from Harry vs Larry is more durable and in some ways more practical, but the Hood 001 is 65 euro cheaper and more flexible. In a collapsed state I have transported odd sized cargo that would have been difficult with the side kit and I have used the cargo area as a chair when taking a break since there are no side panels in the way. After I switched to max length of 67 centimetres moving boxes I could also fit these in the bike. The Hood 001 is also half the weight compared to the BBX Side Panel Kit, 2 kg vs 3.8 kg. Like all Bullitt accessories, my biggest issue with this bag is the price. At 289 euro it is expensive, but the only other alternative is the <a href="http://shop.larryvsharry.com/shop/accessories/bbx-side-panel-kit-race-green.html">BBX Side Panel Kit</a> at 354 euro.</p>
<h2 id="pros">Pros</h2>
<ul>
<li>Lightweight</li>
<li>Weatherproof</li>
<li>Easy and fast to expand and collapse the bag</li>
<li>Small back pocket inside the bag and rings to secure content</li>
<li>Still able to transport odd size stuff in a collapsed state</li>
</ul>
<h2 id="cons">Cons</h2>
<ul>
<li>Unable to fit very large (70cm) moving boxes when in closed state</li>
<li>The price is quite high for what it is</li>
<li>Not as durable as the BBX Side Panel Kit</li>
<li>There is a small gap between the bag and the front of the bike</li>
</ul>
<div class="btn--group">
<a href="http://www.fahrer-berlin.de/en/bullit/hood/hood-schwarz/a-207/" class="btn" rel="nofollow">Buy from Fahrer-berlin.de</a>
<a href="http://shop.larryvsharry.com/shop/accessories/fahrer-hood.html" class="btn" rel="nofollow">Buy from Larry vs Harry</a>
</div>
<p><a href="https://odd-one-out.serek.eu/reviews/bullitt-hood-001-review/" rel="nofollow">HOOD 001 cargo bag review</a> was originally published on Odd One Out.</p>
Poul Serekhttps://Ox3.serek.eu/about/HOOD 001 cargo bag reviewESP8266 NodeMCU - OLED display using SPI2016-04-16T00:00:00+00:002016-04-16T21:19:17+00:00https://odd-one-out.serek.eu/projects/esp8266-nodemcu-oled-display-spi
<p><img src="/assets/images/esp8266-nodemcu-oled-display-spi-feature.jpg" alt="" /></p>
<p>I bought a $4 1 inch OLED SPI display that I wanted to use with my ESP8266 development board. By using an online <a href="http://nodemcu-build.com">service</a> to create a streamlined NodeMCU firmware this was a breeze!</p>
<h2 id="nodemcu-with-the-correct-modules">NodeMCU with the correct modules</h2>
<p>Flash the ESP8266 with a version of NodeMCU that contains the following modules:</p>
<ul>
<li>bit</li>
<li>SPI</li>
<li>U8G</li>
</ul>
<p>Follow my post <a href="/projects/esp8266-nodemcu-dht22-custom-modules-firmware/">here</a> on how to built a custom NodeMCU firmware the easy way!</p>
<figure>
<noscript><img src="/assets/images/esp8266-nodemcu-oled-display-spi-modules.png" /></noscript>
<img src="data:image/gif;base64,R0lGODlhAQABAAAAACH5BAEKAAEALAAAAAABAAEAAAICTAEAOw==" data-src="/assets/images/esp8266-nodemcu-oled-display-spi-modules.png" alt="" class="lazyload fade-in" />
<figcaption>Custom NodeMCU build for OLED SPI displays</figcaption>
</figure>
<p>If you need help flashing the firmware you can take a look at my guide <a href="/projects/esp8266-development-kit-nodemcu-firmware-update-os-x/">here</a>.</p>
<h2 id="parts">Parts</h2>
<p>If you don’t mind waiting a few weeks I would recommend buying from AliExpress as I have done for the lowest price and free shipping, even to Europe! Amazon, even with Amazon Prime is just a bit too expensive.</p>
<!-- Affiliate disclamer removed because of GDPR and removal of affiliate links -->
<div class="notice"><h3>Disclaimer</h3>This post contains links to Amazon where I get a small commission if you purchase anything after clicking on these links - at no extra cost to you! But only if you have explicitly consented to this. I have purchased all the mentioned products myself and I only link to products that I believe are the best for my readers. If you want to help out even more, take a look <a href="/support/">here</a>.</div>
<table>
<thead>
<tr>
<th>Part</th>
<th>Ali Express</th>
<th>Amazon</th>
</tr>
</thead>
<tbody>
<tr>
<td>1x <a href="https://www.amazon.com/dp/B010O1G1ES/" rel="nofollow" data-amazon-asin="[us]B010O1G1ES[ca]B019FBLEYU[uk]B010N1SPRK[de]B0182JOWOK[es][it][fr]">ESP8266 development board</a></td>
<td><a href="https://www.aliexpress.com/item/V2-4M-4FLASH-NodeMcu-Lua-WIFI-Networking-development-board-Based-ESP8266/32448662166.html" rel="nofollow">$4.2</a></td>
<td><a href="https://www.amazon.com/dp/B010O1G1ES/" rel="nofollow" data-amazon-asin="[us]B010O1G1ES[ca]B019FBLEYU[uk]B010N1SPRK[de]B0182JOWOK[es][it][fr]">$9</a></td>
</tr>
<tr>
<td>1x <a href="https://www.amazon.com/dp/B01KFSXMR4/" rel="nofollow" data-amazon-asin="[us]B01KFSXMR4[uk][it][fr]B01KUF1CDS[de][es]B010B1UPDE">0.96 inch 128X64 OLED SPI module</a></td>
<td><a href="https://www.aliexpress.com/item/0-96-blue-0-96-inch-OLED-module-New-128X64-OLED-LCD-LED-Display-Module-For/32595649930.html" rel="nofollow">$4.1</a></td>
<td><a href="https://www.amazon.com/dp/B01KFSXMR4/" rel="nofollow" data-amazon-asin="[us]B01KFSXMR4[uk][it][fr]B01KUF1CDS[de][es]B010B1UPDE">$10.7</a></td>
</tr>
<tr>
<td>1x <a href="https://www.amazon.com/dp/B0084A7PI8/" rel="nofollow" data-amazon-asin="[us][ca][es]B0084A7PI8[uk][de]B00JGFDKBQ[it][fr]B00PQC72ZS">breadboard</a></td>
<td><a href="https://www.aliexpress.com/item/1pcs-Quality-mini-bread-board-breadboard-8-5CM-x-5-5CM-400-holes-For-expansion-arduino/1906352269.html" el="nofollow">$1.2</a></td>
<td><a href="https://www.amazon.com/dp/B0084A7PI8/" rel="nofollow" data-amazon-asin="[us][ca][es]B0084A7PI8[uk][de]B00JGFDKBQ[it][fr]B00PQC72ZS">$5.2</a></td>
</tr>
<tr>
<td>1x <a href="https://www.amazon.com/dp/B00B5RJF1M/" rel="nofollow" data-amazon-asin="[us]B00B5RJF1M[ca]B0087ZDSV8[uk]B004S0XA1O[de]B00IYUWT2A[es][it][fr]">10K resistor</a></td>
<td><a href="https://www.aliexpress.com/item/100pcs-10k-ohm-1-4W-10k-Metal-Film-Resistor-10kohm-0-25W-1-ROHS/32577051768.html" rel="nofollow">$0.7</a></td>
<td><a href="https://www.amazon.com/dp/B00B5RJF1M/" rel="nofollow" data-amazon-asin="[us]B00B5RJF1M[ca]B0087ZDSV8[uk]B004S0XA1O[de]B00IYUWT2A[es][it][fr]">$4.9</a></td>
</tr>
<tr>
<td><a href="https://www.amazon.com/dp/B014JOV4TI/" rel="nofollow" data-amazon-asin="[us]B014JOV4TI[ca]B0002H7AIQ[uk]B01B7M5S6K[de]B01B4HO30K[es]B01GQOJY7I[it]B01GZ2LP82[fr]B01IX7WMAM">Various breadboard wires</a></td>
<td><a href="https://www.aliexpress.com/af/breadboard%25252dwires.html?SearchText=breadboard+wires&blanktest=0&origin=n&jump=afs" rel="nofollow">$2.6</a></td>
<td><a href="https://www.amazon.com/dp/B014JOV4TI/" rel="nofollow" data-amazon-asin="[us]B014JOV4TI[ca]B0002H7AIQ[uk]B01B7M5S6K[de]B01B4HO30K[es]B01GQOJY7I[it]B01GZ2LP82[fr]B01IX7WMAM">$5.9</a></td>
</tr>
<tr>
<td><strong>Total</strong></td>
<td>$12.8</td>
<td>$35.7</td>
</tr>
</tbody>
</table>
<h2 id="hardware-setup">Hardware setup</h2>
<figure>
<noscript><img src="/assets/images/esp8266-nodemcu-oled-display-spi-fritzing.png" /></noscript>
<img src="data:image/gif;base64,R0lGODlhAQABAAAAACH5BAEKAAEALAAAAAABAAEAAAICTAEAOw==" data-src="/assets/images/esp8266-nodemcu-oled-display-spi-fritzing.png" alt="" class="lazyload fade-in" />
<figcaption>Connecting an inexpensive OLED display using SPI</figcaption>
</figure>
<h2 id="code">Code</h2>
<p>To get started I cheated a bit and used the test code from the U8G library from Github <a href="https://github.com/nodemcu/nodemcu-firmware/tree/master/lua_examples/u8glib">here</a>. I downloaded the file <a href="https://github.com/nodemcu/nodemcu-firmware/blob/master/lua_examples/u8glib/u8g_graphics_test.lua">u8g_graphics_test.lua</a> and changed line 158 to</p>
<div class="language-lua highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="c1">--init_i2c_display()</span>
</code></pre></div></div>
<p>and line 159 to</p>
<div class="language-lua highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="n">init_spi_display</span><span class="p">()</span>
</code></pre></div></div>
<p>Then I renamed the file to <code class="highlighter-rouge">init.lua</code> and uploaded the file to the ESP8266. The modified file can also be copied from here:</p>
<figure class="highlight"><pre><code class="language-lua" data-lang="lua"><table class="rouge-table"><tbody><tr><td class="gutter gl"><pre class="lineno">1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
</pre></td><td class="code"><pre><span class="c1">-- ***************************************************************************</span>
<span class="c1">-- Graphics Test</span>
<span class="c1">--</span>
<span class="c1">-- This script executes several features of u8glib to test their Lua bindings.</span>
<span class="c1">--</span>
<span class="c1">-- Note: It is prepared for SSD1306-based displays. Select your connectivity</span>
<span class="c1">-- type by calling either init_i2c_display() or init_spi_display() at</span>
<span class="c1">-- the bottom of this file.</span>
<span class="c1">--</span>
<span class="c1">-- ***************************************************************************</span>
<span class="c1">-- setup I2c and connect display</span>
<span class="k">function</span> <span class="nf">init_i2c_display</span><span class="p">()</span>
<span class="c1">-- SDA and SCL can be assigned freely to available GPIOs</span>
<span class="kd">local</span> <span class="n">sda</span> <span class="o">=</span> <span class="mi">5</span> <span class="c1">-- GPIO14</span>
<span class="kd">local</span> <span class="n">scl</span> <span class="o">=</span> <span class="mi">6</span> <span class="c1">-- GPIO12</span>
<span class="kd">local</span> <span class="n">sla</span> <span class="o">=</span> <span class="mh">0x3c</span>
<span class="n">i2c</span><span class="p">.</span><span class="n">setup</span><span class="p">(</span><span class="mi">0</span><span class="p">,</span> <span class="n">sda</span><span class="p">,</span> <span class="n">scl</span><span class="p">,</span> <span class="n">i2c</span><span class="p">.</span><span class="n">SLOW</span><span class="p">)</span>
<span class="n">disp</span> <span class="o">=</span> <span class="n">u8g</span><span class="p">.</span><span class="n">ssd1306_128x64_i2c</span><span class="p">(</span><span class="n">sla</span><span class="p">)</span>
<span class="k">end</span>
<span class="c1">-- setup SPI and connect display</span>
<span class="k">function</span> <span class="nf">init_spi_display</span><span class="p">()</span>
<span class="c1">-- Hardware SPI CLK = GPIO14</span>
<span class="c1">-- Hardware SPI MOSI = GPIO13</span>
<span class="c1">-- Hardware SPI MISO = GPIO12 (not used)</span>
<span class="c1">-- CS, D/C, and RES can be assigned freely to available GPIOs</span>
<span class="kd">local</span> <span class="n">cs</span> <span class="o">=</span> <span class="mi">8</span> <span class="c1">-- GPIO15, pull-down 10k to GND</span>
<span class="kd">local</span> <span class="n">dc</span> <span class="o">=</span> <span class="mi">4</span> <span class="c1">-- GPIO2</span>
<span class="kd">local</span> <span class="n">res</span> <span class="o">=</span> <span class="mi">0</span> <span class="c1">-- GPIO16</span>
<span class="n">spi</span><span class="p">.</span><span class="n">setup</span><span class="p">(</span><span class="mi">1</span><span class="p">,</span> <span class="n">spi</span><span class="p">.</span><span class="n">MASTER</span><span class="p">,</span> <span class="n">spi</span><span class="p">.</span><span class="n">CPOL_LOW</span><span class="p">,</span> <span class="n">spi</span><span class="p">.</span><span class="n">CPHA_LOW</span><span class="p">,</span> <span class="mi">8</span><span class="p">,</span> <span class="mi">8</span><span class="p">)</span>
<span class="n">disp</span> <span class="o">=</span> <span class="n">u8g</span><span class="p">.</span><span class="n">ssd1306_128x64_hw_spi</span><span class="p">(</span><span class="n">cs</span><span class="p">,</span> <span class="n">dc</span><span class="p">,</span> <span class="n">res</span><span class="p">)</span>
<span class="k">end</span>
<span class="c1">-- graphic test components</span>
<span class="k">function</span> <span class="nf">prepare</span><span class="p">()</span>
<span class="n">disp</span><span class="p">:</span><span class="n">setFont</span><span class="p">(</span><span class="n">u8g</span><span class="p">.</span><span class="n">font_6x10</span><span class="p">)</span>
<span class="n">disp</span><span class="p">:</span><span class="n">setFontRefHeightExtendedText</span><span class="p">()</span>
<span class="n">disp</span><span class="p">:</span><span class="n">setDefaultForegroundColor</span><span class="p">()</span>
<span class="n">disp</span><span class="p">:</span><span class="n">setFontPosTop</span><span class="p">()</span>
<span class="k">end</span>
<span class="k">function</span> <span class="nf">box_frame</span><span class="p">(</span><span class="n">a</span><span class="p">)</span>
<span class="n">disp</span><span class="p">:</span><span class="n">drawStr</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="s2">"drawBox"</span><span class="p">)</span>
<span class="n">disp</span><span class="p">:</span><span class="n">drawBox</span><span class="p">(</span><span class="mi">5</span><span class="p">,</span> <span class="mi">10</span><span class="p">,</span> <span class="mi">20</span><span class="p">,</span> <span class="mi">10</span><span class="p">)</span>
<span class="n">disp</span><span class="p">:</span><span class="n">drawBox</span><span class="p">(</span><span class="mi">10</span><span class="o">+</span><span class="n">a</span><span class="p">,</span> <span class="mi">15</span><span class="p">,</span> <span class="mi">30</span><span class="p">,</span> <span class="mi">7</span><span class="p">)</span>
<span class="n">disp</span><span class="p">:</span><span class="n">drawStr</span><span class="p">(</span><span class="mi">0</span><span class="p">,</span> <span class="mi">30</span><span class="p">,</span> <span class="s2">"drawFrame"</span><span class="p">)</span>
<span class="n">disp</span><span class="p">:</span><span class="n">drawFrame</span><span class="p">(</span><span class="mi">5</span><span class="p">,</span> <span class="mi">10</span><span class="o">+</span><span class="mi">30</span><span class="p">,</span> <span class="mi">20</span><span class="p">,</span> <span class="mi">10</span><span class="p">)</span>
<span class="n">disp</span><span class="p">:</span><span class="n">drawFrame</span><span class="p">(</span><span class="mi">10</span><span class="o">+</span><span class="n">a</span><span class="p">,</span> <span class="mi">15</span><span class="o">+</span><span class="mi">30</span><span class="p">,</span> <span class="mi">30</span><span class="p">,</span> <span class="mi">7</span><span class="p">)</span>
<span class="k">end</span>
<span class="k">function</span> <span class="nf">disc_circle</span><span class="p">(</span><span class="n">a</span><span class="p">)</span>
<span class="n">disp</span><span class="p">:</span><span class="n">drawStr</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="s2">"drawDisc"</span><span class="p">)</span>
<span class="n">disp</span><span class="p">:</span><span class="n">drawDisc</span><span class="p">(</span><span class="mi">10</span><span class="p">,</span> <span class="mi">18</span><span class="p">,</span> <span class="mi">9</span><span class="p">)</span>
<span class="n">disp</span><span class="p">:</span><span class="n">drawDisc</span><span class="p">(</span><span class="mi">24</span><span class="o">+</span><span class="n">a</span><span class="p">,</span> <span class="mi">16</span><span class="p">,</span> <span class="mi">7</span><span class="p">)</span>
<span class="n">disp</span><span class="p">:</span><span class="n">drawStr</span><span class="p">(</span><span class="mi">0</span><span class="p">,</span> <span class="mi">30</span><span class="p">,</span> <span class="s2">"drawCircle"</span><span class="p">)</span>
<span class="n">disp</span><span class="p">:</span><span class="n">drawCircle</span><span class="p">(</span><span class="mi">10</span><span class="p">,</span> <span class="mi">18</span><span class="o">+</span><span class="mi">30</span><span class="p">,</span> <span class="mi">9</span><span class="p">)</span>
<span class="n">disp</span><span class="p">:</span><span class="n">drawCircle</span><span class="p">(</span><span class="mi">24</span><span class="o">+</span><span class="n">a</span><span class="p">,</span> <span class="mi">16</span><span class="o">+</span><span class="mi">30</span><span class="p">,</span> <span class="mi">7</span><span class="p">)</span>
<span class="k">end</span>
<span class="k">function</span> <span class="nf">r_frame</span><span class="p">(</span><span class="n">a</span><span class="p">)</span>
<span class="n">disp</span><span class="p">:</span><span class="n">drawStr</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="s2">"drawRFrame/Box"</span><span class="p">)</span>
<span class="n">disp</span><span class="p">:</span><span class="n">drawRFrame</span><span class="p">(</span><span class="mi">5</span><span class="p">,</span> <span class="mi">10</span><span class="p">,</span> <span class="mi">40</span><span class="p">,</span> <span class="mi">30</span><span class="p">,</span> <span class="n">a</span><span class="o">+</span><span class="mi">1</span><span class="p">)</span>
<span class="n">disp</span><span class="p">:</span><span class="n">drawRBox</span><span class="p">(</span><span class="mi">50</span><span class="p">,</span> <span class="mi">10</span><span class="p">,</span> <span class="mi">25</span><span class="p">,</span> <span class="mi">40</span><span class="p">,</span> <span class="n">a</span><span class="o">+</span><span class="mi">1</span><span class="p">)</span>
<span class="k">end</span>
<span class="k">function</span> <span class="nf">stringtest</span><span class="p">(</span><span class="n">a</span><span class="p">)</span>
<span class="n">disp</span><span class="p">:</span><span class="n">drawStr</span><span class="p">(</span><span class="mi">30</span><span class="o">+</span><span class="n">a</span><span class="p">,</span> <span class="mi">31</span><span class="p">,</span> <span class="s2">" 0"</span><span class="p">)</span>
<span class="n">disp</span><span class="p">:</span><span class="n">drawStr90</span><span class="p">(</span><span class="mi">30</span><span class="p">,</span> <span class="mi">31</span><span class="o">+</span><span class="n">a</span><span class="p">,</span> <span class="s2">" 90"</span><span class="p">)</span>
<span class="n">disp</span><span class="p">:</span><span class="n">drawStr180</span><span class="p">(</span><span class="mi">30</span><span class="o">-</span><span class="n">a</span><span class="p">,</span> <span class="mi">31</span><span class="p">,</span> <span class="s2">" 180"</span><span class="p">)</span>
<span class="n">disp</span><span class="p">:</span><span class="n">drawStr270</span><span class="p">(</span><span class="mi">30</span><span class="p">,</span> <span class="mi">31</span><span class="o">-</span><span class="n">a</span><span class="p">,</span> <span class="s2">" 270"</span><span class="p">)</span>
<span class="k">end</span>
<span class="k">function</span> <span class="nf">line</span><span class="p">(</span><span class="n">a</span><span class="p">)</span>
<span class="n">disp</span><span class="p">:</span><span class="n">drawStr</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="s2">"drawLine"</span><span class="p">)</span>
<span class="n">disp</span><span class="p">:</span><span class="n">drawLine</span><span class="p">(</span><span class="mi">7</span><span class="o">+</span><span class="n">a</span><span class="p">,</span> <span class="mi">10</span><span class="p">,</span> <span class="mi">40</span><span class="p">,</span> <span class="mi">55</span><span class="p">)</span>
<span class="n">disp</span><span class="p">:</span><span class="n">drawLine</span><span class="p">(</span><span class="mi">7</span><span class="o">+</span><span class="n">a</span><span class="o">*</span><span class="mi">2</span><span class="p">,</span> <span class="mi">10</span><span class="p">,</span> <span class="mi">60</span><span class="p">,</span> <span class="mi">55</span><span class="p">)</span>
<span class="n">disp</span><span class="p">:</span><span class="n">drawLine</span><span class="p">(</span><span class="mi">7</span><span class="o">+</span><span class="n">a</span><span class="o">*</span><span class="mi">3</span><span class="p">,</span> <span class="mi">10</span><span class="p">,</span> <span class="mi">80</span><span class="p">,</span> <span class="mi">55</span><span class="p">)</span>
<span class="n">disp</span><span class="p">:</span><span class="n">drawLine</span><span class="p">(</span><span class="mi">7</span><span class="o">+</span><span class="n">a</span><span class="o">*</span><span class="mi">4</span><span class="p">,</span> <span class="mi">10</span><span class="p">,</span> <span class="mi">100</span><span class="p">,</span> <span class="mi">55</span><span class="p">)</span>
<span class="k">end</span>
<span class="k">function</span> <span class="nf">triangle</span><span class="p">(</span><span class="n">a</span><span class="p">)</span>
<span class="kd">local</span> <span class="n">offset</span> <span class="o">=</span> <span class="n">a</span>
<span class="n">disp</span><span class="p">:</span><span class="n">drawStr</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="s2">"drawTriangle"</span><span class="p">)</span>
<span class="n">disp</span><span class="p">:</span><span class="n">drawTriangle</span><span class="p">(</span><span class="mi">14</span><span class="p">,</span><span class="mi">7</span><span class="p">,</span> <span class="mi">45</span><span class="p">,</span><span class="mi">30</span><span class="p">,</span> <span class="mi">10</span><span class="p">,</span><span class="mi">40</span><span class="p">)</span>
<span class="n">disp</span><span class="p">:</span><span class="n">drawTriangle</span><span class="p">(</span><span class="mi">14</span><span class="o">+</span><span class="n">offset</span><span class="p">,</span><span class="mi">7</span><span class="o">-</span><span class="n">offset</span><span class="p">,</span> <span class="mi">45</span><span class="o">+</span><span class="n">offset</span><span class="p">,</span><span class="mi">30</span><span class="o">-</span><span class="n">offset</span><span class="p">,</span> <span class="mi">57</span><span class="o">+</span><span class="n">offset</span><span class="p">,</span><span class="mi">10</span><span class="o">-</span><span class="n">offset</span><span class="p">)</span>
<span class="n">disp</span><span class="p">:</span><span class="n">drawTriangle</span><span class="p">(</span><span class="mi">57</span><span class="o">+</span><span class="n">offset</span><span class="o">*</span><span class="mi">2</span><span class="p">,</span><span class="mi">10</span><span class="p">,</span> <span class="mi">45</span><span class="o">+</span><span class="n">offset</span><span class="o">*</span><span class="mi">2</span><span class="p">,</span><span class="mi">30</span><span class="p">,</span> <span class="mi">86</span><span class="o">+</span><span class="n">offset</span><span class="o">*</span><span class="mi">2</span><span class="p">,</span><span class="mi">53</span><span class="p">)</span>
<span class="n">disp</span><span class="p">:</span><span class="n">drawTriangle</span><span class="p">(</span><span class="mi">10</span><span class="o">+</span><span class="n">offset</span><span class="p">,</span><span class="mi">40</span><span class="o">+</span><span class="n">offset</span><span class="p">,</span> <span class="mi">45</span><span class="o">+</span><span class="n">offset</span><span class="p">,</span><span class="mi">30</span><span class="o">+</span><span class="n">offset</span><span class="p">,</span> <span class="mi">86</span><span class="o">+</span><span class="n">offset</span><span class="p">,</span><span class="mi">53</span><span class="o">+</span><span class="n">offset</span><span class="p">)</span>
<span class="k">end</span>
<span class="k">function</span> <span class="nf">ascii_1</span><span class="p">()</span>
<span class="kd">local</span> <span class="n">x</span><span class="p">,</span> <span class="n">y</span><span class="p">,</span> <span class="n">s</span>
<span class="n">disp</span><span class="p">:</span><span class="n">drawStr</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="s2">"ASCII page 1"</span><span class="p">)</span>
<span class="k">for</span> <span class="n">y</span> <span class="o">=</span> <span class="mi">0</span><span class="p">,</span> <span class="mi">5</span><span class="p">,</span> <span class="mi">1</span> <span class="k">do</span>
<span class="k">for</span> <span class="n">x</span> <span class="o">=</span> <span class="mi">0</span><span class="p">,</span> <span class="mi">15</span><span class="p">,</span> <span class="mi">1</span> <span class="k">do</span>
<span class="n">s</span> <span class="o">=</span> <span class="n">y</span><span class="o">*</span><span class="mi">16</span> <span class="o">+</span> <span class="n">x</span> <span class="o">+</span> <span class="mi">32</span>
<span class="n">disp</span><span class="p">:</span><span class="n">drawStr</span><span class="p">(</span><span class="n">x</span><span class="o">*</span><span class="mi">7</span><span class="p">,</span> <span class="n">y</span><span class="o">*</span><span class="mi">10</span><span class="o">+</span><span class="mi">10</span><span class="p">,</span> <span class="nb">string.char</span><span class="p">(</span><span class="n">s</span><span class="p">))</span>
<span class="k">end</span>
<span class="k">end</span>
<span class="k">end</span>
<span class="k">function</span> <span class="nf">extra_page</span><span class="p">(</span><span class="n">a</span><span class="p">)</span>
<span class="n">disp</span><span class="p">:</span><span class="n">drawStr</span><span class="p">(</span><span class="mi">0</span><span class="p">,</span> <span class="mi">12</span><span class="p">,</span> <span class="s2">"setScale2x2"</span><span class="p">)</span>
<span class="n">disp</span><span class="p">:</span><span class="n">setScale2x2</span><span class="p">()</span>
<span class="n">disp</span><span class="p">:</span><span class="n">drawStr</span><span class="p">(</span><span class="mi">0</span><span class="p">,</span> <span class="mi">6</span><span class="o">+</span><span class="n">a</span><span class="p">,</span> <span class="s2">"setScale2x2"</span><span class="p">)</span>
<span class="n">disp</span><span class="p">:</span><span class="n">undoScale</span><span class="p">()</span>
<span class="k">end</span>
<span class="c1">-- the draw() routine</span>
<span class="k">function</span> <span class="nf">draw</span><span class="p">(</span><span class="n">draw_state</span><span class="p">)</span>
<span class="kd">local</span> <span class="n">component</span> <span class="o">=</span> <span class="n">bit</span><span class="p">.</span><span class="n">rshift</span><span class="p">(</span><span class="n">draw_state</span><span class="p">,</span> <span class="mi">3</span><span class="p">)</span>
<span class="n">prepare</span><span class="p">()</span>
<span class="k">if</span> <span class="p">(</span><span class="n">component</span> <span class="o">==</span> <span class="mi">0</span><span class="p">)</span> <span class="k">then</span>
<span class="n">box_frame</span><span class="p">(</span><span class="n">bit</span><span class="p">.</span><span class="n">band</span><span class="p">(</span><span class="n">draw_state</span><span class="p">,</span> <span class="mi">7</span><span class="p">))</span>
<span class="k">elseif</span> <span class="p">(</span><span class="n">component</span> <span class="o">==</span> <span class="mi">1</span><span class="p">)</span> <span class="k">then</span>
<span class="n">disc_circle</span><span class="p">(</span><span class="n">bit</span><span class="p">.</span><span class="n">band</span><span class="p">(</span><span class="n">draw_state</span><span class="p">,</span> <span class="mi">7</span><span class="p">))</span>
<span class="k">elseif</span> <span class="p">(</span><span class="n">component</span> <span class="o">==</span> <span class="mi">2</span><span class="p">)</span> <span class="k">then</span>
<span class="n">r_frame</span><span class="p">(</span><span class="n">bit</span><span class="p">.</span><span class="n">band</span><span class="p">(</span><span class="n">draw_state</span><span class="p">,</span> <span class="mi">7</span><span class="p">))</span>
<span class="k">elseif</span> <span class="p">(</span><span class="n">component</span> <span class="o">==</span> <span class="mi">3</span><span class="p">)</span> <span class="k">then</span>
<span class="n">stringtest</span><span class="p">(</span><span class="n">bit</span><span class="p">.</span><span class="n">band</span><span class="p">(</span><span class="n">draw_state</span><span class="p">,</span> <span class="mi">7</span><span class="p">))</span>
<span class="k">elseif</span> <span class="p">(</span><span class="n">component</span> <span class="o">==</span> <span class="mi">4</span><span class="p">)</span> <span class="k">then</span>
<span class="n">line</span><span class="p">(</span><span class="n">bit</span><span class="p">.</span><span class="n">band</span><span class="p">(</span><span class="n">draw_state</span><span class="p">,</span> <span class="mi">7</span><span class="p">))</span>
<span class="k">elseif</span> <span class="p">(</span><span class="n">component</span> <span class="o">==</span> <span class="mi">5</span><span class="p">)</span> <span class="k">then</span>
<span class="n">triangle</span><span class="p">(</span><span class="n">bit</span><span class="p">.</span><span class="n">band</span><span class="p">(</span><span class="n">draw_state</span><span class="p">,</span> <span class="mi">7</span><span class="p">))</span>
<span class="k">elseif</span> <span class="p">(</span><span class="n">component</span> <span class="o">==</span> <span class="mi">6</span><span class="p">)</span> <span class="k">then</span>
<span class="n">ascii_1</span><span class="p">()</span>
<span class="k">elseif</span> <span class="p">(</span><span class="n">component</span> <span class="o">==</span> <span class="mi">7</span><span class="p">)</span> <span class="k">then</span>
<span class="n">extra_page</span><span class="p">(</span><span class="n">bit</span><span class="p">.</span><span class="n">band</span><span class="p">(</span><span class="n">draw_state</span><span class="p">,</span> <span class="mi">7</span><span class="p">))</span>
<span class="k">end</span>
<span class="k">end</span>
<span class="k">function</span> <span class="nf">graphics_test</span><span class="p">()</span>
<span class="n">disp</span><span class="p">:</span><span class="n">firstPage</span><span class="p">()</span>
<span class="k">repeat</span>
<span class="n">draw</span><span class="p">(</span><span class="n">draw_state</span><span class="p">)</span>
<span class="k">until</span> <span class="n">disp</span><span class="p">:</span><span class="n">nextPage</span><span class="p">()</span> <span class="o">==</span> <span class="kc">false</span>
<span class="k">if</span> <span class="p">(</span><span class="n">draw_state</span> <span class="o"><=</span> <span class="mi">7</span> <span class="o">+</span> <span class="mi">8</span><span class="o">*</span><span class="mi">8</span><span class="p">)</span> <span class="k">then</span>
<span class="n">draw_state</span> <span class="o">=</span> <span class="n">draw_state</span> <span class="o">+</span> <span class="mi">1</span>
<span class="k">else</span>
<span class="nb">print</span><span class="p">(</span><span class="s2">"--- Restarting Graphics Test ---"</span><span class="p">)</span>
<span class="n">draw_state</span> <span class="o">=</span> <span class="mi">0</span>
<span class="k">end</span>
<span class="nb">print</span><span class="p">(</span><span class="s2">"Heap: "</span> <span class="o">..</span> <span class="n">node</span><span class="p">.</span><span class="n">heap</span><span class="p">())</span>
<span class="c1">-- retrigger timer to give room for system housekeeping</span>
<span class="n">tmr</span><span class="p">.</span><span class="n">start</span><span class="p">(</span><span class="mi">0</span><span class="p">)</span>
<span class="k">end</span>
<span class="n">draw_state</span> <span class="o">=</span> <span class="mi">0</span>
<span class="c1">--init_i2c_display()</span>
<span class="n">init_spi_display</span><span class="p">()</span>
<span class="c1">-- set up timer 0 with short interval, will be retriggered in graphics_test()</span>
<span class="n">tmr</span><span class="p">.</span><span class="n">register</span><span class="p">(</span><span class="mi">0</span><span class="p">,</span> <span class="mi">100</span><span class="p">,</span> <span class="n">tmr</span><span class="p">.</span><span class="n">ALARM_SEMI</span><span class="p">,</span> <span class="k">function</span><span class="p">()</span> <span class="n">graphics_test</span><span class="p">()</span> <span class="k">end</span><span class="p">)</span>
<span class="nb">print</span><span class="p">(</span><span class="s2">"--- Starting Graphics Test ---"</span><span class="p">)</span>
<span class="n">tmr</span><span class="p">.</span><span class="n">start</span><span class="p">(</span><span class="mi">0</span><span class="p">)</span></pre></td></tr></tbody></table></code></pre></figure>
<h2 id="result">Result</h2>
<figure>
<noscript><img src="/assets/images/esp8266-nodemcu-oled-display-spi-final.jpg" /></noscript>
<img src="data:image/gif;base64,R0lGODlhAQABAAAAACH5BAEKAAEALAAAAAABAAEAAAICTAEAOw==" data-src="/assets/images/esp8266-nodemcu-oled-display-spi-final.jpg" alt="" class="lazyload fade-in" />
<figcaption>Running the U8G test program</figcaption>
</figure>
<div class="responsive-video-container"><iframe width="640" height="360" data-src="https://www.youtube-nocookie.com/embed/1RrZqFnIecY?showinfo=0" frameborder="0" webkitallowfullscreen="" mozallowfullscreen="" allowfullscreen="" class="lazyload"></iframe></div>
<p>Everything works and from here it is just a matter on using the U8G library to show what you need. Take a look at the NodeMCU U8G documentation <a href="https://nodemcu.readthedocs.org/en/dev/en/modules/u8g/">here</a>.</p>
<p><a href="https://odd-one-out.serek.eu/projects/esp8266-nodemcu-oled-display-spi/" rel="nofollow">ESP8266 NodeMCU - OLED display using SPI</a> was originally published on Odd One Out.</p>
Poul Serekhttps://Ox3.serek.eu/about/ESP8266 NodeMCU - OLED display using SPIESP8266 Controlling an IHC wireless light switch2016-04-10T00:00:00+00:002016-04-09T22:00:47+00:00https://odd-one-out.serek.eu/projects/esp8266-nodemcu-ihc-wireless-webserver-light-switch
<p><img src="/assets/images/esp8266-nodemcu-ihc-wireless-webserver-light-switch-feature.jpg" alt="" /></p>
<p>Recently I installed several intelligent IHC Wireless lightswitches and power outlets from Lauritz Knudsen. They can be programmed so that any switch can wirelessly control any other switch or power outlet. I wanted to control these switches using my phone and was left with three options</p>
<ol>
<li>Buy an IHC Wireless Controller for around $750 and try to integrate an ESP8266 to control the switches</li>
<li>Try to hack the proprietary IHC Wireless protocol and send the commands directly to the switches using an ESP8266 and a radio transceiver module</li>
<li>Modify an IHC Wireless switch so that an ESP8266 can control the buttons directly</li>
</ol>
<p>Option 1 is too expensive for what I want to do and as far as I know nobody has been able to hack the protocol in option 2. So that left option 3!
I bought a battery powered IHC Wireless switch with a total of 7 buttons. Each lightswitch has one on and one off button. The last button is used to program the switch. This was the cheapest option at around $75. Next I took the switch apart and saw it was a simple matter to activate the 7 small buttons.</p>
<figure class="gallery-3-col">
<a href="/assets/images/esp8266-nodemcu-ihc-wireless-webserver-light-switch-front.jpg"><noscript><img src="/assets/images/esp8266-nodemcu-ihc-wireless-webserver-light-switch-front.jpg" /></noscript><img src="data:image/gif;base64,R0lGODlhAQABAAAAACH5BAEKAAEALAAAAAABAAEAAAICTAEAOw==" data-src="/assets/images/esp8266-nodemcu-ihc-wireless-webserver-light-switch-front.jpg" alt="IHC Wireless battery powered switch" class="lazyload fade-in" /></a>
<a href="/assets/images/esp8266-nodemcu-ihc-wireless-webserver-light-switch-without-button-panels.jpg"><noscript><img src="/assets/images/esp8266-nodemcu-ihc-wireless-webserver-light-switch-without-button-panels.jpg" /></noscript><img src="data:image/gif;base64,R0lGODlhAQABAAAAACH5BAEKAAEALAAAAAABAAEAAAICTAEAOw==" data-src="/assets/images/esp8266-nodemcu-ihc-wireless-webserver-light-switch-without-button-panels.jpg" alt="IHC Wireless battery powered switch with the button panels taken off" class="lazyload fade-in" /></a>
<a href="/assets/images/esp8266-nodemcu-ihc-wireless-webserver-light-switch-internal.jpg"><noscript><img src="/assets/images/esp8266-nodemcu-ihc-wireless-webserver-light-switch-internal.jpg" /></noscript><img src="data:image/gif;base64,R0lGODlhAQABAAAAACH5BAEKAAEALAAAAAABAAEAAAICTAEAOw==" data-src="/assets/images/esp8266-nodemcu-ihc-wireless-webserver-light-switch-internal.jpg" alt="IHC Wireless battery powered switch with the front panel off" class="lazyload fade-in" /></a>
<figcaption>IHC Wireless battery powered switch, here shown fully assembled, with the button panels of and with the front cover off</figcaption>
</figure>
<p>Next I hooked up and ESP8266 development board, a few BC547B transistors, 10K resistors and wires and that was it for the hardware setup.</p>
<figure class="gallery-2-col">
<a href="/assets/images/esp8266-nodemcu-ihc-wireless-webserver-light-switch-working-prototype.jpg"><noscript><img src="/assets/images/esp8266-nodemcu-ihc-wireless-webserver-light-switch-working-prototype.jpg" /></noscript><img src="data:image/gif;base64,R0lGODlhAQABAAAAACH5BAEKAAEALAAAAAABAAEAAAICTAEAOw==" data-src="/assets/images/esp8266-nodemcu-ihc-wireless-webserver-light-switch-working-prototype.jpg" alt="ESP8266 development board controlling button 1 and 2 of the IHC Wireless switch" class="lazyload fade-in" /></a>
<a href="/assets/images/esp8266-nodemcu-ihc-wireless-webserver-light-switch-working-prototype-closeup.jpg"><noscript><img src="/assets/images/esp8266-nodemcu-ihc-wireless-webserver-light-switch-working-prototype-closeup.jpg" /></noscript><img src="data:image/gif;base64,R0lGODlhAQABAAAAACH5BAEKAAEALAAAAAABAAEAAAICTAEAOw==" data-src="/assets/images/esp8266-nodemcu-ihc-wireless-webserver-light-switch-working-prototype-closeup.jpg" alt="ESP8266 development board controlling button 1 and 2 of the IHC Wireless switch – closeup" class="lazyload fade-in" /></a>
<figcaption>ESP8266 development board controlling button 1 and 2 of the IHC Wireless switch</figcaption>
</figure>
<p>Notice that I have only hooked up two buttons, but the webserver supports up to 7 buttons</p>
<h2 id="parts">Parts</h2>
<p>If you don’t mind waiting a few weeks I would recommend buying from AliExpress as I have done for the lowest price and free shipping, even to Europe! I cannot recommend Amazon for parts like these since they are very expensive even with free shipping from Amazon Prime. I have created a table to compare the prices so that people can make up their own mind.</p>
<!-- Affiliate disclamer removed because of GDPR and removal of affiliate links -->
<div class="notice"><h3>Disclaimer</h3>This post contains links to Amazon where I get a small commission if you purchase anything after clicking on these links - at no extra cost to you! But only if you have explicitly consented to this. I have purchased all the mentioned products myself and I only link to products that I believe are the best for my readers. If you want to help out even more, take a look <a href="/support/">here</a>.</div>
<table>
<thead>
<tr>
<th>Part</th>
<th>Ali Express</th>
<th>Amazon</th>
</tr>
</thead>
<tbody>
<tr>
<td>1x <a href="https://www.amazon.com/dp/B010O1G1ES/" rel="nofollow" data-amazon-asin="[us]B010O1G1ES[ca]B019FBLEYU[uk]B010N1SPRK[de]B0182JOWOK[es][it][fr]">ESP8266 development board</a></td>
<td><a href="https://www.aliexpress.com/item/V2-4M-4FLASH-NodeMcu-Lua-WIFI-Networking-development-board-Based-ESP8266/32448662166.html" rel="nofollow">$4.2</a></td>
<td><a href="https://www.amazon.com/dp/B010O1G1ES/" rel="nofollow" data-amazon-asin="[us]B010O1G1ES[ca]B019FBLEYU[uk]B010N1SPRK[de]B0182JOWOK[es][it][fr]">$9</a></td>
</tr>
<tr>
<td>1x <a href="http://www.wattoo.dk/lk-ihc-wireless-batteritryk-lk-fuga-6-slutte-hvid-1092001540" rel="nofollow">IHC Wireless switch</a></td>
<td>N/A</td>
<td>N/A</td>
</tr>
<tr>
<td>1x <a href="https://www.amazon.com/dp/B0084A7PI8/" rel="nofollow" data-amazon-asin="[us][ca][es]B0084A7PI8[uk][de]B00JGFDKBQ[it][fr]B00PQC72ZS">breadboard</a></td>
<td><a href="https://www.aliexpress.com/item/1pcs-Quality-mini-bread-board-breadboard-8-5CM-x-5-5CM-400-holes-For-expansion-arduino/1906352269.html" rel="nofollow">$1.2</a></td>
<td><a href="https://www.amazon.com/dp/B0084A7PI8/" rel="nofollow" data-amazon-asin="[us][ca][es]B0084A7PI8[uk][de]B00JGFDKBQ[it][fr]B00PQC72ZS">$5.2</a></td>
</tr>
<tr>
<td>1-7x <a href="https://www.amazon.com/dp/B00B5RJF1M/" rel="nofollow" data-amazon-asin="[us]B00B5RJF1M[ca]B0087ZDSV8[uk]B004S0XA1O[de]B00IYUWT2A[es][it][fr]">10K resistor</a> (one for each button you want to control)</td>
<td><a href="https://www.aliexpress.com/item/100pcs-10k-ohm-1-4W-10k-Metal-Film-Resistor-10kohm-0-25W-1-ROHS/32577051768.html" rel="nofollow">$0.7</a></td>
<td><a href="https://www.amazon.com/dp/B00B5RJF1M/" rel="nofollow" data-amazon-asin="[us]B00B5RJF1M[ca]B0087ZDSV8[uk]B004S0XA1O[de]B00IYUWT2A[es][it][fr]">$4.9</a></td>
</tr>
<tr>
<td>1-7x <a href="https://www.amazon.com/dp/B00CHTOVU2/" rel="nofollow" data-amazon-asin="[us]B00CHTOVU2[ca]B01BT93MY6[uk]B00UB2YS5U[de][it][fr]B00Q6WQ14K[es]B01M3R5GE8">BC547B transistor</a> (one for each button you want to control)</td>
<td><a href="https://www.aliexpress.com/item/FREE-SHIPPING-50PCS-BC547B-BC547-TO-92-TO92-DIP-NPN-general-purpose-transistors/32328944159.html" rel="nofollow">$1.1</a></td>
<td><a href="https://www.amazon.com/dp/B00CHTOVU2/" rel="nofollow" data-amazon-asin="[us]B00CHTOVU2[ca]B01BT93MY6[uk]B00UB2YS5U[de][it][fr]B00Q6WQ14K[es]B01M3R5GE8">$5.5</a></td>
</tr>
<tr>
<td><a href="https://www.amazon.com/dp/B014JOV4TI/" rel="nofollow" data-amazon-asin="[us]B014JOV4TI[ca]B0002H7AIQ[uk]B01B7M5S6K[de]B01B4HO30K[es]B01GQOJY7I[it]B01GZ2LP82[fr]B01IX7WMAM">Various breadboard wires</a></td>
<td><a href="https://www.aliexpress.com/af/breadboard%25252dwires.html?SearchText=breadboard+wires&blanktest=0&origin=n&jump=afs" rel="nofollow">$2.6</a></td>
<td><a href="https://www.amazon.com/dp/B014JOV4TI/" rel="nofollow" data-amazon-asin="[us]B014JOV4TI[ca]B0002H7AIQ[uk]B01B7M5S6K[de]B01B4HO30K[es]B01GQOJY7I[it]B01GZ2LP82[fr]B01IX7WMAM">$5.9</a></td>
</tr>
<tr>
<td><strong>Total</strong></td>
<td>$84.8</td>
<td>$105.5</td>
</tr>
<tr>
<td><strong>Total without IHC Wireless switch</strong></td>
<td>$9.8</td>
<td>$30.5</td>
</tr>
</tbody>
</table>
<h2 id="hardware-setup">Hardware setup</h2>
<figure>
<noscript><img src="/assets/images/esp8266-nodemcu-ihc-wireless-webserver-light-switch-fritzing.png" /></noscript>
<img src="data:image/gif;base64,R0lGODlhAQABAAAAACH5BAEKAAEALAAAAAABAAEAAAICTAEAOw==" data-src="/assets/images/esp8266-nodemcu-ihc-wireless-webserver-light-switch-fritzing.png" alt="" class="lazyload fade-in" />
<figcaption>Hardware setup</figcaption>
</figure>
<p>The two buttons connected by the yellow wires are from the IHC Wireless switch. Other than that the setup is straight forward.</p>
<h2 id="code">Code</h2>
<p>Upload the following code as <code class="highlighter-rouge">init.lua</code> to your ESP8266. If you don’t know how, take a look at my post <a href="/projects/esp8266-nodemcu-getting-started-hello-world/">here</a>. The most important parameters to configure are:</p>
<ul>
<li><code class="highlighter-rouge">switch*_pin</code>: Map up to 7 switches to the GPIO of the ESP8266</li>
<li><code class="highlighter-rouge">wifi_SSID</code>: Wifi name</li>
<li><code class="highlighter-rouge">wifi_password</code>: Wifi password</li>
<li><code class="highlighter-rouge">client_*</code>: Optionally set a static ip address, netmask and gateway. If <code class="highlighter-rouge">client_ip</code> is not set, DHCP will be used</li>
</ul>
<figure class="highlight"><pre><code class="language-lua" data-lang="lua"><table class="rouge-table"><tbody><tr><td class="gutter gl"><pre class="lineno">1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
</pre></td><td class="code"><pre><span class="c1">-- Webserver to control several on / off switches.</span>
<span class="c1">-- Idea inspired by: https://www.youtube.com/watch?v=DPVmGG4xifU</span>
<span class="c1">-- HTML inspired by: https://github.com/mrkale/NodeMCU-WifiDoubleSwitch</span>
<span class="c1">-- Config</span>
<span class="n">switch1_pin</span> <span class="o">=</span> <span class="mi">2</span> <span class="c1">-- Room 1 On</span>
<span class="n">switch2_pin</span> <span class="o">=</span> <span class="mi">1</span> <span class="c1">-- Room 1 Off</span>
<span class="n">switch3_pin</span> <span class="o">=</span> <span class="mi">3</span> <span class="c1">-- Room 2 On</span>
<span class="n">switch4_pin</span> <span class="o">=</span> <span class="mi">3</span> <span class="c1">-- Room 2 Off</span>
<span class="n">switch5_pin</span> <span class="o">=</span> <span class="mi">3</span> <span class="c1">-- Room 3 On</span>
<span class="n">switch6_pin</span> <span class="o">=</span> <span class="mi">3</span> <span class="c1">-- Room 3 Off</span>
<span class="n">switch7_pin</span> <span class="o">=</span> <span class="mi">3</span> <span class="c1">-- Programmable switch</span>
<span class="c1">--- WIFI ---</span>
<span class="n">wifi_SSID</span> <span class="o">=</span> <span class="s2">"wifi_name"</span>
<span class="n">wifi_password</span> <span class="o">=</span> <span class="s2">"wifi_password"</span>
<span class="c1">-- wifi.PHYMODE_B 802.11b, More range, Low Transfer rate, More current draw</span>
<span class="c1">-- wifi.PHYMODE_G 802.11g, Medium range, Medium transfer rate, Medium current draw</span>
<span class="c1">-- wifi.PHYMODE_N 802.11n, Least range, Fast transfer rate, Least current draw</span>
<span class="n">wifi_signal_mode</span> <span class="o">=</span> <span class="n">wifi</span><span class="p">.</span><span class="n">PHYMODE_N</span>
<span class="c1">-- If the settings below are filled out then the module connects</span>
<span class="c1">-- using a static ip address which is faster than DHCP and</span>
<span class="c1">-- better for battery life. Blank "" will use DHCP.</span>
<span class="c1">-- My own tests show around 1-2 seconds with static ip</span>
<span class="c1">-- and 4+ seconds for DHCP</span>
<span class="n">client_ip</span><span class="o">=</span><span class="s2">"192.168.1.111"</span>
<span class="n">client_netmask</span><span class="o">=</span><span class="s2">"255.255.255.0"</span>
<span class="n">client_gateway</span><span class="o">=</span><span class="s2">"192.168.1.1"</span>
<span class="c1">-- Connect to the wifi network</span>
<span class="n">wifi</span><span class="p">.</span><span class="n">setmode</span><span class="p">(</span><span class="n">wifi</span><span class="p">.</span><span class="n">STATION</span><span class="p">)</span>
<span class="n">wifi</span><span class="p">.</span><span class="n">setphymode</span><span class="p">(</span><span class="n">wifi_signal_mode</span><span class="p">)</span>
<span class="n">wifi</span><span class="p">.</span><span class="n">sta</span><span class="p">.</span><span class="n">config</span><span class="p">(</span><span class="n">wifi_SSID</span><span class="p">,</span> <span class="n">wifi_password</span><span class="p">)</span>
<span class="n">wifi</span><span class="p">.</span><span class="n">sta</span><span class="p">.</span><span class="n">connect</span><span class="p">()</span>
<span class="k">if</span> <span class="n">client_ip</span> <span class="o">~=</span> <span class="s2">""</span> <span class="k">then</span>
<span class="n">wifi</span><span class="p">.</span><span class="n">sta</span><span class="p">.</span><span class="n">setip</span><span class="p">({</span><span class="n">ip</span><span class="o">=</span><span class="n">client_ip</span><span class="p">,</span><span class="n">netmask</span><span class="o">=</span><span class="n">client_netmask</span><span class="p">,</span><span class="n">gateway</span><span class="o">=</span><span class="n">client_gateway</span><span class="p">})</span>
<span class="k">end</span>
<span class="c1">-- Connect</span>
<span class="n">tmr</span><span class="p">.</span><span class="n">alarm</span><span class="p">(</span><span class="mi">0</span><span class="p">,</span> <span class="mi">1000</span><span class="p">,</span> <span class="mi">1</span><span class="p">,</span> <span class="k">function</span><span class="p">()</span>
<span class="k">if</span> <span class="n">wifi</span><span class="p">.</span><span class="n">sta</span><span class="p">.</span><span class="n">getip</span><span class="p">()</span> <span class="o">==</span> <span class="kc">nil</span> <span class="k">then</span>
<span class="nb">print</span><span class="p">(</span><span class="s2">"Connecting to AP...\n"</span><span class="p">)</span>
<span class="k">else</span>
<span class="n">ip</span><span class="p">,</span> <span class="n">nm</span><span class="p">,</span> <span class="n">gw</span><span class="o">=</span><span class="n">wifi</span><span class="p">.</span><span class="n">sta</span><span class="p">.</span><span class="n">getip</span><span class="p">()</span>
<span class="nb">print</span><span class="p">(</span><span class="s2">"IP address: "</span><span class="p">,</span><span class="n">ip</span><span class="p">)</span>
<span class="n">tmr</span><span class="p">.</span><span class="n">stop</span><span class="p">(</span><span class="mi">0</span><span class="p">)</span>
<span class="n">gpio</span><span class="p">.</span><span class="n">mode</span><span class="p">(</span><span class="n">switch1_pin</span><span class="p">,</span> <span class="n">gpio</span><span class="p">.</span><span class="n">OUTPUT</span><span class="p">)</span>
<span class="n">gpio</span><span class="p">.</span><span class="n">mode</span><span class="p">(</span><span class="n">switch2_pin</span><span class="p">,</span> <span class="n">gpio</span><span class="p">.</span><span class="n">OUTPUT</span><span class="p">)</span>
<span class="n">gpio</span><span class="p">.</span><span class="n">mode</span><span class="p">(</span><span class="n">switch3_pin</span><span class="p">,</span> <span class="n">gpio</span><span class="p">.</span><span class="n">OUTPUT</span><span class="p">)</span>
<span class="n">gpio</span><span class="p">.</span><span class="n">mode</span><span class="p">(</span><span class="n">switch4_pin</span><span class="p">,</span> <span class="n">gpio</span><span class="p">.</span><span class="n">OUTPUT</span><span class="p">)</span>
<span class="n">gpio</span><span class="p">.</span><span class="n">mode</span><span class="p">(</span><span class="n">switch5_pin</span><span class="p">,</span> <span class="n">gpio</span><span class="p">.</span><span class="n">OUTPUT</span><span class="p">)</span>
<span class="n">gpio</span><span class="p">.</span><span class="n">mode</span><span class="p">(</span><span class="n">switch6_pin</span><span class="p">,</span> <span class="n">gpio</span><span class="p">.</span><span class="n">OUTPUT</span><span class="p">)</span>
<span class="n">gpio</span><span class="p">.</span><span class="n">mode</span><span class="p">(</span><span class="n">switch7_pin</span><span class="p">,</span> <span class="n">gpio</span><span class="p">.</span><span class="n">OUTPUT</span><span class="p">)</span>
<span class="n">tmr</span><span class="p">.</span><span class="n">start</span><span class="p">(</span><span class="mi">1</span><span class="p">)</span>
<span class="k">end</span>
<span class="k">end</span><span class="p">)</span>
<span class="n">tmr</span><span class="p">.</span><span class="n">alarm</span><span class="p">(</span><span class="mi">1</span><span class="p">,</span> <span class="mi">100</span><span class="p">,</span> <span class="mi">1</span><span class="p">,</span> <span class="k">function</span><span class="p">()</span>
<span class="n">gpio</span><span class="p">.</span><span class="n">write</span><span class="p">(</span><span class="n">switch1_pin</span><span class="p">,</span> <span class="n">gpio</span><span class="p">.</span><span class="n">LOW</span><span class="p">)</span>
<span class="n">gpio</span><span class="p">.</span><span class="n">write</span><span class="p">(</span><span class="n">switch2_pin</span><span class="p">,</span> <span class="n">gpio</span><span class="p">.</span><span class="n">LOW</span><span class="p">)</span>
<span class="n">gpio</span><span class="p">.</span><span class="n">write</span><span class="p">(</span><span class="n">switch3_pin</span><span class="p">,</span> <span class="n">gpio</span><span class="p">.</span><span class="n">LOW</span><span class="p">)</span>
<span class="n">gpio</span><span class="p">.</span><span class="n">write</span><span class="p">(</span><span class="n">switch4_pin</span><span class="p">,</span> <span class="n">gpio</span><span class="p">.</span><span class="n">LOW</span><span class="p">)</span>
<span class="n">gpio</span><span class="p">.</span><span class="n">write</span><span class="p">(</span><span class="n">switch5_pin</span><span class="p">,</span> <span class="n">gpio</span><span class="p">.</span><span class="n">LOW</span><span class="p">)</span>
<span class="n">gpio</span><span class="p">.</span><span class="n">write</span><span class="p">(</span><span class="n">switch6_pin</span><span class="p">,</span> <span class="n">gpio</span><span class="p">.</span><span class="n">LOW</span><span class="p">)</span>
<span class="n">gpio</span><span class="p">.</span><span class="n">write</span><span class="p">(</span><span class="n">switch7_pin</span><span class="p">,</span> <span class="n">gpio</span><span class="p">.</span><span class="n">LOW</span><span class="p">)</span>
<span class="n">tmr</span><span class="p">.</span><span class="n">stop</span><span class="p">(</span><span class="mi">1</span><span class="p">)</span>
<span class="k">end</span><span class="p">)</span>
<span class="c1">-- Start a simple http server</span>
<span class="n">srv</span><span class="o">=</span><span class="n">net</span><span class="p">.</span><span class="n">createServer</span><span class="p">(</span><span class="n">net</span><span class="p">.</span><span class="n">TCP</span><span class="p">)</span>
<span class="n">srv</span><span class="p">:</span><span class="n">listen</span><span class="p">(</span><span class="mi">80</span><span class="p">,</span><span class="k">function</span><span class="p">(</span><span class="n">conn</span><span class="p">)</span>
<span class="n">conn</span><span class="p">:</span><span class="n">on</span><span class="p">(</span><span class="s2">"receive"</span><span class="p">,</span><span class="k">function</span><span class="p">(</span><span class="n">conn</span><span class="p">,</span><span class="n">request</span><span class="p">)</span>
<span class="c1">-- https://github.com/marcoskirsch/nodemcu-httpserver/issues/36#issuecomment-167442461</span>
<span class="c1">-- Some browsers send the POST data in multiple chunks, like Safari on OS X and IOS</span>
<span class="c1">-- Collect data packets until the size of HTTP body meets the Content-Length stated in header</span>
<span class="c1">-- This fixed issues where the webpage was rendered twice after posting data to the server in some browsers</span>
<span class="k">if</span> <span class="n">request</span><span class="p">:</span><span class="n">find</span><span class="p">(</span><span class="s2">"Content%-Length:"</span><span class="p">)</span> <span class="ow">or</span> <span class="n">bBodyMissing</span> <span class="k">then</span>
<span class="k">if</span> <span class="n">fullPayload</span> <span class="k">then</span> <span class="n">fullPayload</span> <span class="o">=</span> <span class="n">fullPayload</span> <span class="o">..</span> <span class="n">request</span> <span class="k">else</span> <span class="n">fullPayload</span> <span class="o">=</span> <span class="n">request</span> <span class="k">end</span>
<span class="k">if</span> <span class="p">(</span><span class="nb">tonumber</span><span class="p">(</span><span class="nb">string.match</span><span class="p">(</span><span class="n">fullPayload</span><span class="p">,</span> <span class="s2">"%d+"</span><span class="p">,</span> <span class="n">fullPayload</span><span class="p">:</span><span class="n">find</span><span class="p">(</span><span class="s2">"Content%-Length:"</span><span class="p">)</span><span class="o">+</span><span class="mi">16</span><span class="p">))</span> <span class="o">></span> <span class="o">#</span><span class="n">fullPayload</span><span class="p">:</span><span class="n">sub</span><span class="p">(</span><span class="n">fullPayload</span><span class="p">:</span><span class="n">find</span><span class="p">(</span><span class="s2">"</span><span class="se">\r\n\r\n</span><span class="s2">"</span><span class="p">,</span> <span class="mi">1</span><span class="p">,</span> <span class="kc">true</span><span class="p">)</span><span class="o">+</span><span class="mi">4</span><span class="p">,</span> <span class="o">#</span><span class="n">fullPayload</span><span class="p">))</span> <span class="k">then</span>
<span class="n">bBodyMissing</span> <span class="o">=</span> <span class="kc">true</span>
<span class="k">return</span>
<span class="k">else</span>
<span class="nb">print</span><span class="p">(</span><span class="s2">"HTTP packet assembled! size: "</span><span class="o">..#</span><span class="n">fullPayload</span><span class="p">)</span>
<span class="n">request</span> <span class="o">=</span> <span class="n">fullPayload</span>
<span class="n">fullPayload</span><span class="p">,</span> <span class="n">bBodyMissing</span> <span class="o">=</span> <span class="kc">nil</span>
<span class="k">end</span>
<span class="k">end</span>
<span class="c1">-- Handle POST request - switching on and off the various switches</span>
<span class="n">_</span><span class="p">,</span> <span class="n">j</span> <span class="o">=</span> <span class="nb">string.find</span><span class="p">(</span><span class="n">request</span><span class="p">,</span> <span class="s1">'switch='</span><span class="p">)</span>
<span class="k">if</span> <span class="n">j</span> <span class="o">~=</span> <span class="kc">nil</span> <span class="k">then</span>
<span class="n">command</span> <span class="o">=</span> <span class="nb">string.sub</span><span class="p">(</span><span class="n">request</span><span class="p">,</span> <span class="n">j</span> <span class="o">+</span> <span class="mi">1</span><span class="p">)</span>
<span class="nb">print</span><span class="p">(</span><span class="s2">"Switch "</span><span class="o">..</span><span class="n">command</span><span class="o">..</span><span class="s2">" click"</span><span class="p">)</span>
<span class="n">gpio</span><span class="p">.</span><span class="n">write</span><span class="p">(</span><span class="nb">tonumber</span><span class="p">(</span><span class="n">command</span><span class="p">),</span> <span class="n">gpio</span><span class="p">.</span><span class="n">HIGH</span><span class="p">)</span>
<span class="n">tmr</span><span class="p">.</span><span class="n">start</span><span class="p">(</span><span class="mi">1</span><span class="p">)</span>
<span class="k">end</span>
<span class="c1">-- Reponse HTTP headers</span>
<span class="n">conn</span><span class="p">:</span><span class="n">send</span><span class="p">(</span><span class="s2">"HTTP/1.1 200 OK\r\n"</span><span class="p">)</span>
<span class="n">conn</span><span class="p">:</span><span class="n">send</span><span class="p">(</span><span class="s2">"Content-Type: text/html\r\n"</span><span class="p">)</span>
<span class="n">conn</span><span class="p">:</span><span class="n">send</span><span class="p">(</span><span class="s2">"Connection: keep-alive\r\n\r\n"</span><span class="p">)</span>
<span class="c1">-- HTML response</span>
<span class="n">conn</span><span class="p">:</span><span class="n">send</span><span class="p">(</span><span class="s1">'<!DOCTYPE html>'</span><span class="p">)</span>
<span class="n">conn</span><span class="p">:</span><span class="n">send</span><span class="p">(</span><span class="s1">'<html lang="en">'</span><span class="p">)</span>
<span class="n">conn</span><span class="p">:</span><span class="n">send</span><span class="p">(</span><span class="s1">' <head>'</span><span class="p">)</span>
<span class="n">conn</span><span class="p">:</span><span class="n">send</span><span class="p">(</span><span class="s1">' <meta charset="utf-8" />'</span><span class="p">)</span>
<span class="n">conn</span><span class="p">:</span><span class="n">send</span><span class="p">(</span><span class="s1">' <title>IHC Wireless</title>'</span><span class="p">)</span>
<span class="n">conn</span><span class="p">:</span><span class="n">send</span><span class="p">(</span><span class="s1">' <meta http-equiv="X-UA-Compatible" content="IE=edge">'</span><span class="p">)</span>
<span class="n">conn</span><span class="p">:</span><span class="n">send</span><span class="p">(</span><span class="s1">' <meta name="viewport" content="width=device-width, initial-scale=1">'</span><span class="p">)</span>
<span class="n">conn</span><span class="p">:</span><span class="n">send</span><span class="p">(</span><span class="s1">' <link rel="stylesheet" href="https://maxcdn.bootstrapcdn.com/bootstrap/3.3.6/css/bootstrap.min.css">'</span><span class="p">)</span>
<span class="n">conn</span><span class="p">:</span><span class="n">send</span><span class="p">(</span><span class="s1">' </head>'</span><span class="p">)</span>
<span class="n">conn</span><span class="p">:</span><span class="n">send</span><span class="p">(</span><span class="s1">' <body>'</span><span class="p">)</span>
<span class="n">conn</span><span class="p">:</span><span class="n">send</span><span class="p">(</span><span class="s1">' <h1 class="hidden-xs text-center">IHC Wireless</h1>'</span><span class="p">)</span>
<span class="n">conn</span><span class="p">:</span><span class="n">send</span><span class="p">(</span><span class="s1">' <form method="post">'</span><span class="p">)</span>
<span class="n">conn</span><span class="p">:</span><span class="n">send</span><span class="p">(</span><span class="s1">' <div class="container">'</span><span class="p">)</span>
<span class="n">conn</span><span class="p">:</span><span class="n">send</span><span class="p">(</span><span class="s1">' <div class="row">'</span><span class="p">)</span>
<span class="n">conn</span><span class="p">:</span><span class="n">send</span><span class="p">(</span><span class="s1">' <div class="col-sm-4 col-sm-offset-0">'</span><span class="p">)</span>
<span class="n">conn</span><span class="p">:</span><span class="n">send</span><span class="p">(</span><span class="s1">' <h3 class="text-primary text-center">Kitchen</h3>'</span><span class="p">)</span>
<span class="n">conn</span><span class="p">:</span><span class="n">send</span><span class="p">(</span><span class="s1">' <button class="btn btn-block btn-lg btn-success" role="button" type="submit" name="switch" value="'</span><span class="o">..</span><span class="n">switch1_pin</span><span class="o">..</span><span class="s1">'">On</>'</span><span class="p">)</span>
<span class="n">conn</span><span class="p">:</span><span class="n">send</span><span class="p">(</span><span class="s1">' <button class="btn btn-block btn-lg btn-danger" role="button" type="submit" name="switch" value="'</span><span class="o">..</span><span class="n">switch2_pin</span><span class="o">..</span><span class="s1">'">Off</>'</span><span class="p">)</span>
<span class="n">conn</span><span class="p">:</span><span class="n">send</span><span class="p">(</span><span class="s1">' </div>'</span><span class="p">)</span>
<span class="n">conn</span><span class="p">:</span><span class="n">send</span><span class="p">(</span><span class="s1">' <div class="col-sm-4 col-sm-offset-0">'</span><span class="p">)</span>
<span class="n">conn</span><span class="p">:</span><span class="n">send</span><span class="p">(</span><span class="s1">' <h3 class="text-primary text-center">Living room</h3>'</span><span class="p">)</span>
<span class="n">conn</span><span class="p">:</span><span class="n">send</span><span class="p">(</span><span class="s1">' <button class="btn btn-block btn-lg btn-success" role="button" type="submit" name="switch" value="'</span><span class="o">..</span><span class="n">switch3_pin</span><span class="o">..</span><span class="s1">'">On</>'</span><span class="p">)</span>
<span class="n">conn</span><span class="p">:</span><span class="n">send</span><span class="p">(</span><span class="s1">' <button class="btn btn-block btn-lg btn-danger" role="button" type="submit" name="switch" value="'</span><span class="o">..</span><span class="n">switch4_pin</span><span class="o">..</span><span class="s1">'">Off</>'</span><span class="p">)</span>
<span class="n">conn</span><span class="p">:</span><span class="n">send</span><span class="p">(</span><span class="s1">' </div>'</span><span class="p">)</span>
<span class="n">conn</span><span class="p">:</span><span class="n">send</span><span class="p">(</span><span class="s1">' <div class="col-sm-4 col-sm-offset-0">'</span><span class="p">)</span>
<span class="n">conn</span><span class="p">:</span><span class="n">send</span><span class="p">(</span><span class="s1">' <h3 class="text-primary text-center">Bedroom</h3>'</span><span class="p">)</span>
<span class="n">conn</span><span class="p">:</span><span class="n">send</span><span class="p">(</span><span class="s1">' <button class="btn btn-block btn-lg btn-success" role="button" type="submit" name="switch" value="'</span><span class="o">..</span><span class="n">switch5_pin</span><span class="o">..</span><span class="s1">'">On</>'</span><span class="p">)</span>
<span class="n">conn</span><span class="p">:</span><span class="n">send</span><span class="p">(</span><span class="s1">' <button class="btn btn-block btn-lg btn-danger" role="button" type="submit" name="switch" value="'</span><span class="o">..</span><span class="n">switch6_pin</span><span class="o">..</span><span class="s1">'">Off</>'</span><span class="p">)</span>
<span class="n">conn</span><span class="p">:</span><span class="n">send</span><span class="p">(</span><span class="s1">' </div>'</span><span class="p">)</span>
<span class="n">conn</span><span class="p">:</span><span class="n">send</span><span class="p">(</span><span class="s1">' </div>'</span><span class="p">)</span>
<span class="n">conn</span><span class="p">:</span><span class="n">send</span><span class="p">(</span><span class="s1">' <div class="row">'</span><span class="p">)</span>
<span class="n">conn</span><span class="p">:</span><span class="n">send</span><span class="p">(</span><span class="s1">' <div class="col-sm-4 col-sm-offset-4">'</span><span class="p">)</span>
<span class="n">conn</span><span class="p">:</span><span class="n">send</span><span class="p">(</span><span class="s1">' <h3 class="text-primary text-center">Setup</h3>'</span><span class="p">)</span>
<span class="n">conn</span><span class="p">:</span><span class="n">send</span><span class="p">(</span><span class="s1">' <button class="btn btn-block btn-lg btn-warning" role="button" type="submit" name="switch" value="'</span><span class="o">..</span><span class="n">switch7_pin</span><span class="o">..</span><span class="s1">'">Program</>'</span><span class="p">)</span>
<span class="n">conn</span><span class="p">:</span><span class="n">send</span><span class="p">(</span><span class="s1">' </div>'</span><span class="p">)</span>
<span class="n">conn</span><span class="p">:</span><span class="n">send</span><span class="p">(</span><span class="s1">' </div>'</span><span class="p">)</span>
<span class="n">conn</span><span class="p">:</span><span class="n">send</span><span class="p">(</span><span class="s1">' </div>'</span><span class="p">)</span>
<span class="n">conn</span><span class="p">:</span><span class="n">send</span><span class="p">(</span><span class="s1">' </form>'</span><span class="p">)</span>
<span class="n">conn</span><span class="p">:</span><span class="n">send</span><span class="p">(</span><span class="s1">' </body>'</span><span class="p">)</span>
<span class="n">conn</span><span class="p">:</span><span class="n">send</span><span class="p">(</span><span class="s1">'</html>'</span><span class="p">)</span>
<span class="k">end</span><span class="p">)</span>
<span class="n">conn</span><span class="p">:</span><span class="n">on</span><span class="p">(</span><span class="s2">"sent"</span><span class="p">,</span><span class="k">function</span><span class="p">(</span><span class="n">conn</span><span class="p">)</span>
<span class="n">conn</span><span class="p">:</span><span class="n">close</span><span class="p">()</span>
<span class="k">end</span><span class="p">)</span>
<span class="k">end</span><span class="p">)</span></pre></td></tr></tbody></table></code></pre></figure>
<h2 id="results">Results</h2>
<p>With all connected and up and running, the browser should be accessible from the IP address specified in <code class="highlighter-rouge">client_ip</code> or assigned from DHCP. The site is fully responsive so it looks nice on any device.</p>
<figure>
<noscript><img src="/assets/images/esp8266-nodemcu-ihc-wireless-webserver-light-switch-desktop-browser.png" /></noscript>
<img src="data:image/gif;base64,R0lGODlhAQABAAAAACH5BAEKAAEALAAAAAABAAEAAAICTAEAOw==" data-src="/assets/images/esp8266-nodemcu-ihc-wireless-webserver-light-switch-desktop-browser.png" alt="" class="lazyload fade-in" />
<figcaption>Desktop browser</figcaption>
</figure>
<figure class="gallery-2-col center">
<noscript><img src="/assets/images/esp8266-nodemcu-ihc-wireless-webserver-light-switch-mobile-browser.png" /></noscript>
<img src="data:image/gif;base64,R0lGODlhAQABAAAAACH5BAEKAAEALAAAAAABAAEAAAICTAEAOw==" data-src="/assets/images/esp8266-nodemcu-ihc-wireless-webserver-light-switch-mobile-browser.png" alt="" class="lazyload fade-in" />
<figcaption>Mobile device</figcaption>
</figure>
<p>And the most important part, it actually works!</p>
<div class="responsive-video-container"><iframe width="640" height="360" data-src="https://www.youtube-nocookie.com/embed/2Lpl3N411eA?showinfo=0" frameborder="0" webkitallowfullscreen="" mozallowfullscreen="" allowfullscreen="" class="lazyload"></iframe></div>
<p>Connecting to the ESP8266 using the serial ports shows the following output:</p>
<figure class="highlight"><pre><code class="language-terminal" data-lang="terminal"><span class="go">NodeMCU custom build by frightanic.com
branch: dev
commit: 093a895980fbd4ab8b3ebedcd6efe36e26419887
SSL: true
modules: node,file,gpio,wifi,net,tmr,adc,mqtt,dht
built on: 2015-10-13 18:26
powered by Lua 5.1.4
</span><span class="gp">></span> IP address: 192.168.1.111
<span class="go">Switch 1 click
Switch 2 click</span></code></pre></figure>
<h2 id="whats-next">Whats next?</h2>
<ul>
<li>Hook up the remaining buttons to the ESP8266</li>
<li>Solder all the wires and hide the whole thing in a case</li>
<li>Power the IHC Wireless switch using the ESP8266. It currently uses an 3V CR2022 coin cell battery and the ESP8266 development board uses 3.3V so it should be possible</li>
</ul>
<p><a href="https://odd-one-out.serek.eu/projects/esp8266-nodemcu-ihc-wireless-webserver-light-switch/" rel="nofollow">ESP8266 Controlling an IHC wireless light switch</a> was originally published on Odd One Out.</p>
Poul Serekhttps://Ox3.serek.eu/about/ESP8266 Controlling an IHC wireless light switchWP Product Review custom shortcode2016-04-02T00:00:00+00:002016-04-02T19:45:05+00:00https://odd-one-out.serek.eu/code/wp-product-review-custom-shortcode
<p><img src="/assets/images/wp-product-review-custom-shortcode-feature.png" alt="" /></p>
<p>I am preparing to write reviews on my blog and stumbled on the <a href="https://wordpress.org/plugins/wp-product-review/">WP Product Review</a> plugin for Wordpress which seemed like a perfect fit. However, when I inserted the review box at the end of my posts, it was placed below my social share buttons.</p>
<figure>
<noscript><img src="/assets/images/wp-product-review-custom-shortcode-before-links.png" /></noscript>
<img src="data:image/gif;base64,R0lGODlhAQABAAAAACH5BAEKAAEALAAAAAABAAEAAAICTAEAOw==" data-src="/assets/images/wp-product-review-custom-shortcode-before-links.png" alt="" class="lazyload fade-in" />
<figcaption>WP Product Review inserted after the social share buttons. This is not what I want!</figcaption>
</figure>
<p>To place the box above my social share links I have two options:</p>
<ol>
<li>Purchase the <a href="http://themeisle.com/plugins/wppr-single-review-shortcode/">single review shortcode addon</a> for 20 USD or get the <a href="http://themeisle.com/plugins/wp-product-review-pro-add-on/">pro edition</a> for 75 USD to get the shortcode functionality. This shortcode can be used at the very end of my post to insert the review box before my social links, e.g. <code class="highlighter-rouge">[P_REVIEW post_id=3067 visual='full']</code></li>
<li>Use PHP just before my social links get inserted, e.g., <code class="highlighter-rouge"><span class="cp"><?php</span> <span class="k">echo</span> <span class="nx">cwppos_show_review</span><span class="p">(</span><span class="s1">'postid'</span><span class="p">);</span> <span class="cp">?></span></code></li>
</ol>
<p>I didn’t feel like paying 20 USD just to get the shortcode functionality so I used option two to create a custom shortcode for WP Product Review. This is just some basic functionality to place the review box where I want, it does not offer the full functionality of the purchased add-on / pro version.</p>
<h2 id="custom-shortcode">Custom shortcode</h2>
<p>Add this code to your <code class="highlighter-rouge">functions.php</code> file in your child theme:</p>
<figure class="highlight"><pre><code class="language-php" data-lang="php"><table class="rouge-table"><tbody><tr><td class="gutter gl"><pre class="lineno">1
2
3
4
5
6
7
8
9
10
11
12
13
</pre></td><td class="code"><pre><span class="k">function</span> <span class="nf">product_review_shortcode</span><span class="p">(</span> <span class="nv">$atts</span> <span class="p">)</span> <span class="p">{</span>
<span class="nb">extract</span><span class="p">(</span> <span class="nx">shortcode_atts</span><span class="p">(</span>
<span class="k">array</span><span class="p">(</span>
<span class="s1">'post_id'</span> <span class="o">=></span> <span class="nx">get_the_ID</span><span class="p">(),</span>
<span class="p">),</span> <span class="nv">$atts</span> <span class="p">)</span>
<span class="p">);</span>
<span class="k">if</span> <span class="p">(</span><span class="nb">is_numeric</span><span class="p">(</span><span class="nv">$post_id</span><span class="p">))</span> <span class="p">{</span>
<span class="k">return</span> <span class="nx">cwppos_show_review</span><span class="p">(</span><span class="nv">$post_id</span><span class="p">);</span>
<span class="p">}</span> <span class="k">else</span> <span class="p">{</span>
<span class="k">return</span> <span class="s2">"Post_id is not a number: "</span><span class="o">.</span><span class="nv">$post_id</span><span class="p">;</span>
<span class="p">}</span>
<span class="p">}</span>
<span class="nx">add_shortcode</span><span class="p">(</span> <span class="s1">'product_review'</span><span class="p">,</span> <span class="s1">'product_review_shortcode'</span> <span class="p">);</span></pre></td></tr></tbody></table></code></pre></figure>
<p>Then configure WP Product Review to place the review box “Manually”. You can now use the shortcode the following ways:</p>
<h3 id="display-the-review-box-using-the-current-post-as-post_id">Display the review box using the current post as post_id</h3>
<div class="language-php highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="p">[[</span><span class="nx">product_review</span><span class="p">]]</span>
</code></pre></div></div>
<p>If the post is not configured as a WP Product Review, it will not show anything.</p>
<h3 id="display-the-review-box-using-a-specific-post-id">Display the review box using a specific post id</h3>
<div class="language-php highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="p">[[</span><span class="nx">product_review</span> <span class="nx">post_id</span><span class="o">=</span><span class="s1">'1511'</span><span class="p">]]</span>
</code></pre></div></div>
<p>If the id does not point to a valid WP Product Review post it will not show anything. If the ID is not a number, e.g., “123r”, then it will write a small “Invalid id” message.</p>
<h2 id="custom-shortcode-v20">Custom shortcode v2.0</h2>
<p>One last issue I had was that I use Amazon Link shortcodes to generate my affiliate links to Amazon. WP Product Review only supports static links for the optional “buy” buttons at the bottom of the review box. Hence I updated my shortcode function again:</p>
<figure class="highlight"><pre><code class="language-php" data-lang="php"><table class="rouge-table"><tbody><tr><td class="gutter gl"><pre class="lineno">1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
</pre></td><td class="code"><pre><span class="k">function</span> <span class="nf">product_review_shortcode</span><span class="p">(</span> <span class="nv">$atts</span> <span class="p">)</span> <span class="p">{</span>
<span class="nb">extract</span><span class="p">(</span> <span class="nx">shortcode_atts</span><span class="p">(</span>
<span class="k">array</span><span class="p">(</span>
<span class="s1">'post_id'</span> <span class="o">=></span> <span class="nx">get_the_ID</span><span class="p">(),</span>
<span class="s1">'aff_link_content1'</span> <span class="o">=></span> <span class="s1">''</span><span class="p">,</span>
<span class="s1">'aff_link_content2'</span> <span class="o">=></span> <span class="s1">''</span><span class="p">,</span>
<span class="p">),</span> <span class="nv">$atts</span> <span class="p">)</span>
<span class="p">);</span>
<span class="nv">$content</span> <span class="o">=</span> <span class="s1">''</span><span class="p">;</span>
<span class="k">if</span> <span class="p">(</span><span class="nb">is_numeric</span><span class="p">(</span><span class="nv">$post_id</span><span class="p">))</span> <span class="p">{</span>
<span class="nv">$content</span> <span class="o">=</span> <span class="nx">cwppos_show_review</span><span class="p">(</span><span class="nv">$post_id</span><span class="p">);</span>
<span class="p">}</span> <span class="k">else</span> <span class="p">{</span>
<span class="k">return</span> <span class="s2">"Post_id is not a number: "</span><span class="o">.</span><span class="nv">$post_id</span><span class="p">;</span>
<span class="p">}</span>
<span class="nv">$aff_link_content1</span> <span class="o">=</span> <span class="nb">str_replace</span><span class="p">(</span><span class="s1">'{'</span><span class="p">,</span> <span class="s1">'['</span><span class="p">,</span> <span class="nv">$aff_link_content1</span><span class="p">);</span>
<span class="nv">$aff_link_content1</span> <span class="o">=</span> <span class="nb">str_replace</span><span class="p">(</span><span class="s1">'}'</span><span class="p">,</span> <span class="s1">']'</span><span class="p">,</span> <span class="nv">$aff_link_content1</span><span class="p">);</span>
<span class="nv">$aff_link_content2</span> <span class="o">=</span> <span class="nb">str_replace</span><span class="p">(</span><span class="s1">'{'</span><span class="p">,</span> <span class="s1">'['</span><span class="p">,</span> <span class="nv">$aff_link_content2</span><span class="p">);</span>
<span class="nv">$aff_link_content2</span> <span class="o">=</span> <span class="nb">str_replace</span><span class="p">(</span><span class="s1">'}'</span><span class="p">,</span> <span class="s1">']'</span><span class="p">,</span> <span class="nv">$aff_link_content2</span><span class="p">);</span>
<span class="nv">$cssClass</span> <span class="o">=</span> <span class="s2">"affiliate-button"</span><span class="p">;</span>
<span class="k">if</span><span class="p">(</span><span class="nv">$aff_link_content1</span> <span class="o">&&</span> <span class="nv">$aff_link_content2</span><span class="p">){</span>
<span class="nv">$cssClass</span> <span class="o">.=</span> <span class="s2">"2 affiliate-button"</span><span class="p">;</span>
<span class="p">}</span>
<span class="nv">$content</span> <span class="o">.=</span> <span class="s2">"<div style='width: 100%; overflow: hidden;'>"</span><span class="p">;</span>
<span class="k">if</span><span class="p">(</span><span class="nv">$aff_link_content1</span><span class="p">){</span>
<span class="nv">$content</span> <span class="o">.=</span> <span class="s2">"<div class='"</span><span class="o">.</span><span class="nv">$cssClass</span><span class="o">.</span><span class="s2">"'>"</span><span class="o">.</span><span class="nv">$aff_link_content1</span><span class="o">.</span><span class="s2">"</div>"</span><span class="p">;</span>
<span class="p">}</span>
<span class="k">if</span><span class="p">(</span><span class="nv">$aff_link_content2</span><span class="p">){</span>
<span class="nv">$content</span> <span class="o">.=</span> <span class="s2">"<div class='"</span><span class="o">.</span><span class="nv">$cssClass</span><span class="o">.</span><span class="s2">"'>"</span><span class="o">.</span><span class="nv">$aff_link_content2</span><span class="o">.</span><span class="s2">"</div>"</span><span class="p">;</span>
<span class="p">}</span>
<span class="nv">$content</span> <span class="o">.=</span> <span class="s2">"</div>"</span><span class="p">;</span>
<span class="k">return</span> <span class="nv">$content</span><span class="p">;</span>
<span class="p">}</span>
<span class="nx">add_shortcode</span><span class="p">(</span> <span class="s1">'product_review'</span><span class="p">,</span> <span class="s1">'product_review_shortcode'</span> <span class="p">);</span></pre></td></tr></tbody></table></code></pre></figure>
<p>This adds an additional option to generate up to two “buy” button below the review box. You should disable the built-in ones for every review box where you use my custom shortcode to generate these.</p>
<h3 id="display-the-review-box-using-the-current-post-as-post_id-and-a-single-button">Display the review box using the current post as post_id and a single button</h3>
<div class="language-php highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="p">[[</span><span class="nx">product_review</span> <span class="nx">aff_link_content1</span><span class="o">=</span><span class="s2">"{amazon text='Buy from Amazon'&asin</span><span class="si">{</span><span class="nv">us}=B009BUBF9K</span><span class="si">}</span><span class="s2">"</span><span class="p">]]</span>
</code></pre></div></div>
<p>The shortcode <code class="highlighter-rouge">[Amazon text='Buy from Amazon'&asin[us]=B009BUBF9K]</code> is used to generate the link for the button below the review box. Notice that you need to replace all [] brackets in <code class="highlighter-rouge">aff_link_content1</code> with curly brackets, the function will replace them again into normal brackets.</p>
<h3 id="display-the-review-box-using-the-current-post-as-post_id-and-two-buttons">Display the review box using the current post as post_id and two buttons</h3>
<div class="language-php highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="p">[[</span><span class="nx">product_review</span> <span class="nx">aff_link_content1</span><span class="o">=</span><span class="s2">"{amazon text='Buy from Amazon'&asin</span><span class="si">{</span><span class="nv">us}=B009BUBF9K</span><span class="si">}</span><span class="s2">"</span> <span class="nx">aff_link_content1</span><span class="o">=</span><span class="s2">"<a href="</span><span class="nx">http</span><span class="o">://</span><span class="nx">ebay</span><span class="o">.</span><span class="nx">com</span><span class="s2">" rel="</span><span class="nx">nofollow</span><span class="s2">">Buy from Ebay</a>"</span><span class="p">]]</span>
</code></pre></div></div>
<p>The same as above, only now we add a second buy button below using plain old html.</p>
<h2 id="result">Result</h2>
<p>The end result is that the review box is now placed correctly and the “Buy from Amazon” button is created using Amazon Link.</p>
<figure>
<noscript><img src="/assets/images/wp-product-review-custom-shortcode-after-links.png" /></noscript>
<img src="data:image/gif;base64,R0lGODlhAQABAAAAACH5BAEKAAEALAAAAAABAAEAAAICTAEAOw==" data-src="/assets/images/wp-product-review-custom-shortcode-after-links.png" alt="" class="lazyload fade-in" />
<figcaption>WP Product Review inserted before the social share buttons. The \</figcaption>
</figure>
<p>Leave a comment if you have any better and simpler ideas!</p>
<p><a href="https://odd-one-out.serek.eu/code/wp-product-review-custom-shortcode/" rel="nofollow">WP Product Review custom shortcode</a> was originally published on Odd One Out.</p>
Poul Serekhttps://Ox3.serek.eu/about/WP Product Review custom shortcodeNonda USB-C to USB 3.0 Mini Adapter review2016-04-01T00:00:00+00:002016-04-01T20:42:26+00:00https://odd-one-out.serek.eu/reviews/nonda-usb-c-usb-3-0-mini-adapter-review
<p><img src="/assets/images/nonda-usb-c-usb-3-0-mini-adapter-review-feature.jpg" alt="" /></p>
<p>I was lucky enough to get a review sample from <a href="http://www.nonda.co" rel="nofollow">Nonda</a> of their tiny USB-C USB 3.0 adapter, which was very lucky since I was in dire need of one when I recently bought the new retina MacBook with only a single USB-C port. While I am still waiting for their <a href="https://www.nonda.co/products/hub-mini-usb-c-hub-for-apple-macbook-12-inch">Hub+</a> solution I am using these inexpensive adapter to convert my old USB-A cables to USB-C.</p>
<!-- Affiliate disclamer removed because of GDPR and removal of affiliate links -->
<div class="notice"><h3>Disclaimer</h3>This post contains links to Amazon where I get a small commission if you purchase anything after clicking on these links - at no extra cost to you! But only if you have explicitly consented to this. I have purchased all the mentioned products myself and I only link to products that I believe are the best for my readers. If you want to help out even more, take a look <a href="/support/">here</a>.</div>
<figure>
<noscript><img src="/assets/images/nonda-usb-c-usb-3-0-mini-adapter-review-closeup.jpg" /></noscript>
<img src="data:image/gif;base64,R0lGODlhAQABAAAAACH5BAEKAAEALAAAAAABAAEAAAICTAEAOw==" data-src="/assets/images/nonda-usb-c-usb-3-0-mini-adapter-review-closeup.jpg" alt="" class="lazyload fade-in" />
<figcaption>Nonda USB-C to USB 3.0 Mini Adapter closeup, attached to a standard USB-A type cable</figcaption>
</figure>
<p>The size and premium feel of the adapter is excellent compared to the original <a href="https://www.amazon.com/dp/B00VU2OID2/" rel="nofollow" data-amazon-asin="[us]B00VU2OID2[de][uk]B00VUKLYCM">Apple USB-C to USB Adapter</a> which I am using now.</p>
<figure>
<noscript><img src="/assets/images/nonda-usb-c-usb-3-0-mini-adapter-review-vs-apple.jpg" /></noscript>
<img src="data:image/gif;base64,R0lGODlhAQABAAAAACH5BAEKAAEALAAAAAABAAEAAAICTAEAOw==" data-src="/assets/images/nonda-usb-c-usb-3-0-mini-adapter-review-vs-apple.jpg" alt="" class="lazyload fade-in" />
<figcaption>Nonda USB-C to USB 3.0 Mini Adapter compared to Apples adapter</figcaption>
</figure>
<p>Both adapters feel solidly built, but the aluminium of the Nonda adapter just feels more of a premium product. The size is also great as it does not take up much space in my coat pocket and does not get entangled in my earphone cables like my original Apple adapter did a few times.</p>
<figure>
<noscript><img src="/assets/images/nonda-usb-c-usb-3-0-mini-adapter-review-indicator-light-up.jpg" /></noscript>
<img src="data:image/gif;base64,R0lGODlhAQABAAAAACH5BAEKAAEALAAAAAABAAEAAAICTAEAOw==" data-src="/assets/images/nonda-usb-c-usb-3-0-mini-adapter-review-indicator-light-up.jpg" alt="" class="lazyload fade-in" />
<figcaption>Indicator light facing up</figcaption>
</figure>
<p>The adapter has an indicator light that is always on as long as there is power in the USB port. While I was very quick to dismiss this feature as a gimmick and found it annoying at first, I have now found it a nice reminder that a cable is attached to the computer so I remember to take it out. If the light is too much the adapter can be turned around so the indicator faces downward.</p>
<figure>
<noscript><img src="/assets/images/nonda-usb-c-usb-3-0-mini-adapter-review-indicator-light-down.jpg" /></noscript>
<img src="data:image/gif;base64,R0lGODlhAQABAAAAACH5BAEKAAEALAAAAAABAAEAAAICTAEAOw==" data-src="/assets/images/nonda-usb-c-usb-3-0-mini-adapter-review-indicator-light-down.jpg" alt="" class="lazyload fade-in" />
<figcaption>Indicator light facing down</figcaption>
</figure>
<p>This might be the only potential issue with the adapter I could find, you have to remember to turn the adapter the correct way when plugging it in to get a consistent indicator light experience. If you are like me and don’t really care it is not an issue, either way there is enough light for me to notice that a cable is plugged in.
When plugging the adapter into the USB-C, you need to make sure that you plug it all the way in, you will hear a click. The indicator light will light up before the adapter is fully inserted so don’t let that fool you.</p>
<figure>
<noscript><img src="/assets/images/nonda-usb-c-usb-3-0-mini-adapter-review-not-fully-inserted.jpg" /></noscript>
<img src="data:image/gif;base64,R0lGODlhAQABAAAAACH5BAEKAAEALAAAAAABAAEAAAICTAEAOw==" data-src="/assets/images/nonda-usb-c-usb-3-0-mini-adapter-review-not-fully-inserted.jpg" alt="" class="lazyload fade-in" />
<figcaption>The adapter is not fully inserted, there is a small gap between the laptop and the adapter</figcaption>
</figure>
<figure>
<noscript><img src="/assets/images/nonda-usb-c-usb-3-0-mini-adapter-review-fully-inserted.jpg" /></noscript>
<img src="data:image/gif;base64,R0lGODlhAQABAAAAACH5BAEKAAEALAAAAAABAAEAAAICTAEAOw==" data-src="/assets/images/nonda-usb-c-usb-3-0-mini-adapter-review-fully-inserted.jpg" alt="" class="lazyload fade-in" />
<figcaption>The adapter is fully inserted, there is no gap between the laptop and adapter</figcaption>
</figure>
<p>The adapter comes in 3 colours, space grey, gold and classic silver. While the colours do not matter much to me, it does look great which matching aluminium next to my space grey retina MacBook.</p>
<figure>
<noscript><img src="/assets/images/nonda-usb-c-usb-3-0-mini-adapter-review-3-colors.jpg" /></noscript>
<img src="data:image/gif;base64,R0lGODlhAQABAAAAACH5BAEKAAEALAAAAAABAAEAAAICTAEAOw==" data-src="/assets/images/nonda-usb-c-usb-3-0-mini-adapter-review-3-colors.jpg" alt="" class="lazyload fade-in" />
<figcaption>3 colours available to mach the possible 12 inch Retina MacBook colours: Space gray, gold and silver</figcaption>
</figure>
<p>For only 10 USD compared to 19 USD for the Apple adapter, it is a bargain!</p>
<h2 id="pros">Pros</h2>
<ul>
<li>Small and portable</li>
<li>Inexpensive</li>
<li>Indicator light</li>
<li>Premium feel & quality</li>
</ul>
<h2 id="cons">Cons</h2>
<ul>
<li>Indicator light only on one side</li>
</ul>
<p><a href="https://www.amazon.com/dp/B015Z7XGLW/" class="btn" rel="nofollow" data-amazon-asin="{de}{uk}{us}{es}{it}{fr}{ca}B015Z7XGLW">Buy from Amazon</a></p>
<p><a href="https://odd-one-out.serek.eu/reviews/nonda-usb-c-usb-3-0-mini-adapter-review/" rel="nofollow">Nonda USB-C to USB 3.0 Mini Adapter review</a> was originally published on Odd One Out.</p>
Poul Serekhttps://Ox3.serek.eu/about/Nonda USB-C to USB 3.0 Mini Adapter reviewESP8266 NodeMCU - DHT22 sensor and ThingSpeak2016-03-19T00:00:00+00:002016-03-19T18:01:02+00:00https://odd-one-out.serek.eu/projects/esp8266-nodemcu-dht22-thingspeak
<p><img src="/assets/images/esp8266-nodemcu-dht22-thingspeak-feature.png" alt="" /></p>
<p>I have previously <a href="/projects/esp8266-nodemcu-dht22-mqtt-deep-sleep/">written</a> about pushing temperature and humidity readings from an ESP8266 to a MQTT server using a DHT22 sensor. Later I <a href="/projects/esp8266-nodemcu-dht22-custom-modules-firmware/">leveraged</a> NodeMCU’s built-in DHT22 library by using an online <a href="http://nodemcu-build.com">service</a> to create a streamlined NodeMCU firmware fit for my needs. However, a reader asked me about pushing data to <a href="http://thingspeak.com">ThingSpeak.com</a> instead and here is the reply.</p>
<h2 id="hardware">Hardware</h2>
<p>Follow my post <a href="/projects/esp8266-nodemcu-dht22-mqtt-deep-sleep/">here</a> on how to connect all the parts.</p>
<h2 id="nodemcu-firmware">NodeMCU firmware</h2>
<p>Follow my post <a href="/projects/esp8266-nodemcu-dht22-custom-modules-firmware/">here</a> on how to built a custom NodeMCU firmware and remember to check the DHT library. If you need help flashing the firmware you can take a look at my guide <a href="/proejcts/esp8266-development-kit-nodemcu-firmware-update-os-x/">here</a>.</p>
<figure>
<noscript><img src="/assets/images/esp8266-nodemcu-dht22-thingspeak-nodemcu-build.com-DHT.png" /></noscript>
<img src="data:image/gif;base64,R0lGODlhAQABAAAAACH5BAEKAAEALAAAAAABAAEAAAICTAEAOw==" data-src="/assets/images/esp8266-nodemcu-dht22-thingspeak-nodemcu-build.com-DHT.png" alt="" class="lazyload fade-in" />
<figcaption>Nodemcu-build.com default values and DHT selected</figcaption>
</figure>
<h2 id="software">Software</h2>
<p>Instead of the test code described in my post <a href="/projects/esp8266-nodemcu-dht22-custom-modules-firmware/">here</a>, use the one below. Configure the following variables before uploading to the ESP8266:</p>
<ul>
<li><code class="highlighter-rouge">thingspeak_channel_api_write_key</code></li>
<li><code class="highlighter-rouge">thingspeak_temperature_field_name</code></li>
<li><code class="highlighter-rouge">thingspeak_humidity_field_name</code></li>
<li><code class="highlighter-rouge">wifi_SSID</code></li>
<li><code class="highlighter-rouge">wifi_password</code></li>
</ul>
<p>And I recommend to use a static IP address for faster connection times, just set the <code class="highlighter-rouge">client_*</code> variables.</p>
<figure class="highlight"><pre><code class="language-lua" data-lang="lua"><table class="rouge-table"><tbody><tr><td class="gutter gl"><pre class="lineno">1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
</pre></td><td class="code"><pre><span class="c1">-- Thingspeak connect script with deep sleep</span>
<span class="c1">-- Remember to connect GPIO16 and RST to enable deep sleep</span>
<span class="c1">-- TODO: Log error codes to server</span>
<span class="c1">--############</span>
<span class="c1">--# Settings #</span>
<span class="c1">--############</span>
<span class="c1">--- Thingspeak ---</span>
<span class="n">thingspeak_channel_api_write_key</span> <span class="o">=</span> <span class="s2">"LU1TXYV15GBVTDHU"</span>
<span class="n">thingspeak_temperature_field_name</span> <span class="o">=</span> <span class="s2">"field1"</span>
<span class="n">thingspeak_humidity_field_name</span> <span class="o">=</span> <span class="s2">"field2"</span>
<span class="c1">--- WIFI ---</span>
<span class="n">wifi_SSID</span> <span class="o">=</span> <span class="s2">"wifi-Name"</span>
<span class="n">wifi_password</span> <span class="o">=</span> <span class="s2">"wifi-Password"</span>
<span class="c1">-- wifi.PHYMODE_B 802.11b, More range, Low Transfer rate, More current draw</span>
<span class="c1">-- wifi.PHYMODE_G 802.11g, Medium range, Medium transfer rate, Medium current draw</span>
<span class="c1">-- wifi.PHYMODE_N 802.11n, Least range, Fast transfer rate, Least current draw</span>
<span class="n">wifi_signal_mode</span> <span class="o">=</span> <span class="n">wifi</span><span class="p">.</span><span class="n">PHYMODE_N</span>
<span class="c1">-- If the settings below are filled out then the module connects</span>
<span class="c1">-- using a static ip address which is faster than DHCP and</span>
<span class="c1">-- better for battery life. Blank "" will use DHCP.</span>
<span class="c1">-- My own tests show around 1-2 seconds with static ip</span>
<span class="c1">-- and 4+ seconds for DHCP</span>
<span class="n">client_ip</span><span class="o">=</span><span class="s2">""</span>
<span class="n">client_netmask</span><span class="o">=</span><span class="s2">""</span>
<span class="n">client_gateway</span><span class="o">=</span><span class="s2">""</span>
<span class="c1">--- INTERVAL ---</span>
<span class="c1">-- In milliseconds. Remember that the sensor reading,</span>
<span class="c1">-- reboot and wifi reconnect takes a few seconds</span>
<span class="n">time_between_sensor_readings</span> <span class="o">=</span> <span class="mi">60000</span>
<span class="c1">--################</span>
<span class="c1">--# END settings #</span>
<span class="c1">--################</span>
<span class="n">temperature</span> <span class="o">=</span> <span class="mi">0</span>
<span class="n">humidity</span> <span class="o">=</span> <span class="mi">0</span>
<span class="c1">-- Connect to the wifi network</span>
<span class="n">wifi</span><span class="p">.</span><span class="n">setmode</span><span class="p">(</span><span class="n">wifi</span><span class="p">.</span><span class="n">STATION</span><span class="p">)</span>
<span class="n">wifi</span><span class="p">.</span><span class="n">setphymode</span><span class="p">(</span><span class="n">wifi_signal_mode</span><span class="p">)</span>
<span class="n">wifi</span><span class="p">.</span><span class="n">sta</span><span class="p">.</span><span class="n">config</span><span class="p">(</span><span class="n">wifi_SSID</span><span class="p">,</span> <span class="n">wifi_password</span><span class="p">)</span>
<span class="n">wifi</span><span class="p">.</span><span class="n">sta</span><span class="p">.</span><span class="n">connect</span><span class="p">()</span>
<span class="k">if</span> <span class="n">client_ip</span> <span class="o">~=</span> <span class="s2">""</span> <span class="k">then</span>
<span class="n">wifi</span><span class="p">.</span><span class="n">sta</span><span class="p">.</span><span class="n">setip</span><span class="p">({</span><span class="n">ip</span><span class="o">=</span><span class="n">client_ip</span><span class="p">,</span><span class="n">netmask</span><span class="o">=</span><span class="n">client_netmask</span><span class="p">,</span><span class="n">gateway</span><span class="o">=</span><span class="n">client_gateway</span><span class="p">})</span>
<span class="k">end</span>
<span class="c1">-- DHT22 sensor logic</span>
<span class="k">function</span> <span class="nf">get_sensor_Data</span><span class="p">()</span>
<span class="n">dht</span><span class="o">=</span><span class="nb">require</span><span class="p">(</span><span class="s2">"dht"</span><span class="p">)</span>
<span class="n">status</span><span class="p">,</span><span class="n">temp</span><span class="p">,</span><span class="n">humi</span><span class="p">,</span><span class="n">temp_decimial</span><span class="p">,</span><span class="n">humi_decimial</span> <span class="o">=</span> <span class="n">dht</span><span class="p">.</span><span class="n">read</span><span class="p">(</span><span class="mi">2</span><span class="p">)</span>
<span class="k">if</span><span class="p">(</span> <span class="n">status</span> <span class="o">==</span> <span class="n">dht</span><span class="p">.</span><span class="n">OK</span> <span class="p">)</span> <span class="k">then</span>
<span class="c1">-- Prevent "0.-2 deg C" or "-2.-6" </span>
<span class="n">temperature</span> <span class="o">=</span> <span class="n">temp</span><span class="o">..</span><span class="s2">"."</span><span class="o">..</span><span class="p">(</span><span class="nb">math.abs</span><span class="p">(</span><span class="n">temp_decimial</span><span class="p">)</span><span class="o">/</span><span class="mi">100</span><span class="p">)</span>
<span class="n">humidity</span> <span class="o">=</span> <span class="n">humi</span><span class="o">..</span><span class="s2">"."</span><span class="o">..</span><span class="p">(</span><span class="nb">math.abs</span><span class="p">(</span><span class="n">humi_decimial</span><span class="p">)</span><span class="o">/</span><span class="mi">100</span><span class="p">)</span>
<span class="c1">-- If temp is zero and temp_decimal is negative, then add "-" to the temperature string</span>
<span class="k">if</span><span class="p">(</span><span class="n">temp</span> <span class="o">==</span> <span class="mi">0</span> <span class="ow">and</span> <span class="n">temp_decimial</span><span class="o"><</span><span class="mi">0</span><span class="p">)</span> <span class="k">then</span>
<span class="n">temperature</span> <span class="o">=</span> <span class="s2">"-"</span><span class="o">..</span><span class="n">temperature</span>
<span class="k">end</span>
<span class="nb">print</span><span class="p">(</span><span class="s2">"Temperature: "</span><span class="o">..</span><span class="n">temperature</span><span class="o">..</span><span class="s2">" deg C"</span><span class="p">)</span>
<span class="nb">print</span><span class="p">(</span><span class="s2">"Humidity: "</span><span class="o">..</span><span class="n">humidity</span><span class="o">..</span><span class="s2">"%"</span><span class="p">)</span>
<span class="k">elseif</span><span class="p">(</span> <span class="n">status</span> <span class="o">==</span> <span class="n">dht</span><span class="p">.</span><span class="n">ERROR_CHECKSUM</span> <span class="p">)</span> <span class="k">then</span>
<span class="nb">print</span><span class="p">(</span> <span class="s2">"DHT Checksum error"</span> <span class="p">)</span>
<span class="n">temperature</span><span class="o">=-</span><span class="mi">1</span> <span class="c1">--TEST</span>
<span class="k">elseif</span><span class="p">(</span> <span class="n">status</span> <span class="o">==</span> <span class="n">dht</span><span class="p">.</span><span class="n">ERROR_TIMEOUT</span> <span class="p">)</span> <span class="k">then</span>
<span class="nb">print</span><span class="p">(</span> <span class="s2">"DHT Time out"</span> <span class="p">)</span>
<span class="n">temperature</span><span class="o">=-</span><span class="mi">2</span> <span class="c1">--TEST</span>
<span class="k">end</span>
<span class="c1">-- Release module</span>
<span class="n">dht</span><span class="o">=</span><span class="kc">nil</span>
<span class="nb">package.loaded</span><span class="p">[</span><span class="s2">"dht"</span><span class="p">]</span><span class="o">=</span><span class="kc">nil</span>
<span class="k">end</span>
<span class="k">function</span> <span class="nf">loop</span><span class="p">()</span>
<span class="k">if</span> <span class="n">wifi</span><span class="p">.</span><span class="n">sta</span><span class="p">.</span><span class="n">status</span><span class="p">()</span> <span class="o">==</span> <span class="mi">5</span> <span class="k">then</span>
<span class="c1">-- Stop the loop</span>
<span class="n">tmr</span><span class="p">.</span><span class="n">stop</span><span class="p">(</span><span class="mi">0</span><span class="p">)</span>
<span class="n">con</span> <span class="o">=</span> <span class="kc">nil</span>
<span class="n">con</span> <span class="o">=</span> <span class="n">net</span><span class="p">.</span><span class="n">createConnection</span><span class="p">(</span><span class="n">net</span><span class="p">.</span><span class="n">TCP</span><span class="p">,</span> <span class="mi">0</span><span class="p">)</span>
<span class="n">con</span><span class="p">:</span><span class="n">on</span><span class="p">(</span><span class="s2">"receive"</span><span class="p">,</span> <span class="k">function</span><span class="p">(</span><span class="n">con</span><span class="p">,</span> <span class="n">payloadout</span><span class="p">)</span>
<span class="k">if</span> <span class="p">(</span><span class="nb">string.find</span><span class="p">(</span><span class="n">payloadout</span><span class="p">,</span> <span class="s2">"Status: 200 OK"</span><span class="p">)</span> <span class="o">~=</span> <span class="kc">nil</span><span class="p">)</span> <span class="k">then</span>
<span class="nb">print</span><span class="p">(</span><span class="s2">"Posted OK to ThingSpeak"</span><span class="p">);</span>
<span class="k">end</span>
<span class="k">end</span><span class="p">)</span>
<span class="n">con</span><span class="p">:</span><span class="n">on</span><span class="p">(</span><span class="s2">"connection"</span><span class="p">,</span> <span class="k">function</span><span class="p">(</span><span class="n">con</span><span class="p">,</span> <span class="n">payloadout</span><span class="p">)</span>
<span class="c1">-- Get sensor data</span>
<span class="n">get_sensor_Data</span><span class="p">()</span>
<span class="c1">-- Post data to Thingspeak</span>
<span class="n">con</span><span class="p">:</span><span class="n">send</span><span class="p">(</span>
<span class="s2">"POST /update?api_key="</span> <span class="o">..</span> <span class="n">thingspeak_channel_api_write_key</span> <span class="o">..</span>
<span class="s2">"&field1="</span> <span class="o">..</span> <span class="n">temperature</span> <span class="o">..</span>
<span class="s2">"&field2="</span> <span class="o">..</span> <span class="n">humidity</span> <span class="o">..</span>
<span class="s2">" HTTP/1.1\r\n"</span> <span class="o">..</span>
<span class="s2">"Host: api.thingspeak.com\r\n"</span> <span class="o">..</span>
<span class="s2">"Connection: close\r\n"</span> <span class="o">..</span>
<span class="s2">"Accept: */*\r\n"</span> <span class="o">..</span>
<span class="s2">"User-Agent: Mozilla/4.0 (compatible; esp8266 Lua; Windows NT 5.1)\r\n"</span> <span class="o">..</span>
<span class="s2">"</span><span class="se">\r\n</span><span class="s2">"</span><span class="p">)</span>
<span class="k">end</span><span class="p">)</span>
<span class="n">con</span><span class="p">:</span><span class="n">on</span><span class="p">(</span><span class="s2">"disconnection"</span><span class="p">,</span> <span class="k">function</span><span class="p">(</span><span class="n">con</span><span class="p">,</span> <span class="n">payloadout</span><span class="p">)</span>
<span class="n">con</span><span class="p">:</span><span class="n">close</span><span class="p">();</span>
<span class="nb">collectgarbage</span><span class="p">();</span>
<span class="nb">print</span><span class="p">(</span><span class="s2">"Going to deep sleep for "</span><span class="o">..</span><span class="p">(</span><span class="n">time_between_sensor_readings</span><span class="o">/</span><span class="mi">1000</span><span class="p">)</span><span class="o">..</span><span class="s2">" seconds"</span><span class="p">)</span>
<span class="n">node</span><span class="p">.</span><span class="n">dsleep</span><span class="p">(</span><span class="n">time_between_sensor_readings</span><span class="o">*</span><span class="mi">1000</span><span class="p">)</span>
<span class="k">end</span><span class="p">)</span>
<span class="c1">-- Connect to Thingspeak</span>
<span class="n">con</span><span class="p">:</span><span class="n">connect</span><span class="p">(</span><span class="mi">80</span><span class="p">,</span><span class="s1">'api.thingspeak.com'</span><span class="p">)</span>
<span class="k">else</span>
<span class="nb">print</span><span class="p">(</span><span class="s2">"Connecting..."</span><span class="p">)</span>
<span class="k">end</span>
<span class="k">end</span>
<span class="n">tmr</span><span class="p">.</span><span class="n">alarm</span><span class="p">(</span><span class="mi">0</span><span class="p">,</span> <span class="mi">100</span><span class="p">,</span> <span class="mi">1</span><span class="p">,</span> <span class="k">function</span><span class="p">()</span> <span class="n">loop</span><span class="p">()</span> <span class="k">end</span><span class="p">)</span>
<span class="c1">-- Watchdog loop, will force deep sleep the operation somehow takes too long</span>
<span class="n">tmr</span><span class="p">.</span><span class="n">alarm</span><span class="p">(</span><span class="mi">1</span><span class="p">,</span><span class="mi">4000</span><span class="p">,</span><span class="mi">1</span><span class="p">,</span><span class="k">function</span><span class="p">()</span> <span class="n">node</span><span class="p">.</span><span class="n">dsleep</span><span class="p">(</span><span class="n">time_between_sensor_readings</span><span class="o">*</span><span class="mi">1000</span><span class="p">)</span> <span class="k">end</span><span class="p">)</span></pre></td></tr></tbody></table></code></pre></figure>
<h2 id="result">Result</h2>
<p>If everything went well you should see the following repeated every 60 seconds on your ESP8266:</p>
<figure class="highlight"><pre><code class="language-terminal" data-lang="terminal"><span class="go">NodeMCU custom build by frightanic.com
branch: dev
commit: 093a895980fbd4ab8b3ebedcd6efe36e26419887
SSL: true
modules: node,file,gpio,wifi,net,tmr,adc,dht
built on: 2015-10-13 18:26
powered by Lua 5.1.4
</span><span class="gp">></span> Connecting...
<span class="go">Temperature: 23.2 deg C
Humidity: 39.1%
Posted OK to ThingSpeak
Going to deep sleep for 60 seconds</span></code></pre></figure>
<p>The results should now show up right away at your ThingSpeak channel.</p>
<figure>
<noscript><img src="/assets/images/esp8266-nodemcu-dht22-thingspeak-feature.png" /></noscript>
<img src="data:image/gif;base64,R0lGODlhAQABAAAAACH5BAEKAAEALAAAAAABAAEAAAICTAEAOw==" data-src="/assets/images/esp8266-nodemcu-dht22-thingspeak-feature.png" alt="" class="lazyload fade-in" />
<figcaption>Thingspeak with temperature and humidity data from and ESP8266 using a DHT22 sensor</figcaption>
</figure>
<p><a href="https://odd-one-out.serek.eu/projects/esp8266-nodemcu-dht22-thingspeak/" rel="nofollow">ESP8266 NodeMCU - DHT22 sensor and ThingSpeak</a> was originally published on Odd One Out.</p>
Poul Serekhttps://Ox3.serek.eu/about/ESP8266 NodeMCU - DHT22 sensor and ThingSpeakBullitt cargo bike with solar panels2016-03-13T00:00:00+00:002016-03-13T20:30:13+00:00https://odd-one-out.serek.eu/projects/bullitt-cargo-bike-voltaic-solar-panels
<p><img src="/assets/images/bullitt-cargo-bike-voltaic-solar-panels-Voltaic-Arc-20W-Fuse-10W-feature.jpg" alt="" /></p>
<p>I finally became a proper citizen of Copenhagen, Denmark… I bought a cargo bicycle! Now this isn’t any old cargo bike, it is a new take on the Danish Long John from 1923. Introducing the <a href="http://larryvsharry.com">Bullitt</a> from Larry vs Harry with a load capacity of 180 kilo, driver included. Now with all that room and capacity it is time to take my geeky projects outside and enjoy the weather!</p>
<figure>
<noscript><img src="/assets/images/bullitt-cargo-bike-voltaic-solar-panels-Bullitt.jpg" /></noscript>
<img src="data:image/gif;base64,R0lGODlhAQABAAAAACH5BAEKAAEALAAAAAABAAEAAAICTAEAOw==" data-src="/assets/images/bullitt-cargo-bike-voltaic-solar-panels-Bullitt.jpg" alt="" class="lazyload fade-in" />
<figcaption>My Bullitt cargo bike in Copenhagen, loaded up with a few Voltaic solar panels.</figcaption>
</figure>
<!-- Affiliate disclamer removed because of GDPR and removal of affiliate links -->
<div class="notice"><h3>Disclaimer</h3>This post contains links to Amazon where I get a small commission if you purchase anything after clicking on these links - at no extra cost to you! But only if you have explicitly consented to this. I have purchased all the mentioned products myself and I only link to products that I believe are the best for my readers. If you want to help out even more, take a look <a href="/support/">here</a>.</div>
<p>One thing I needed was a renewable source of energy so I bought a front wheel dynamo, the <a href="https://www.amazon.com/dp/B007M817QE/" rel="nofollow" data-amazon-asin="[de]B015HBDJ6I[uk]B007HIJTSC[us]B007M817QE[ca]B002J9CCUG">Shimano DH-S501 Alfine Dynamo Disc Hub</a>, which generates about 3 watts of power at 6v AC. This can output about 0.5 amps at 5v DC using an adapter to charge USB devices, but it is a bit on the low side. It also introduces some drag as you can see from the video, but nothing I can feel when using the bike.</p>
<div class="responsive-video-container"><iframe width="640" height="360" data-src="https://www.youtube-nocookie.com/embed/u-06EIBJo3Y?showinfo=0" frameborder="0" webkitallowfullscreen="" mozallowfullscreen="" allowfullscreen="" class="lazyload"></iframe></div>
<p>So I decided to mount my Voltaic solar panels for a total of 30 watts of pure solar power!</p>
<figure>
<noscript><img src="/assets/images/bullitt-cargo-bike-voltaic-solar-panels-Voltaic-Arc-20W-Fuse-10W.jpg" /></noscript>
<img src="data:image/gif;base64,R0lGODlhAQABAAAAACH5BAEKAAEALAAAAAABAAEAAAICTAEAOw==" data-src="/assets/images/bullitt-cargo-bike-voltaic-solar-panels-Voltaic-Arc-20W-Fuse-10W.jpg" alt="" class="lazyload fade-in" />
<figcaption>Bullitt cargo bike with Voltaic Arc 20W and Fuse 10W solar panels mounted. The charging is done using a 16.000 mAh Voltaic V60 battery.</figcaption>
</figure>
<p>The setup consists of</p>
<ul>
<li><a href="https://www.amazon.ca/dp/B006L4F2P2/" rel="nofollow" data-amazon-asin="[us]ca[de][uk][es][it][fr][ca]B006L4F2P2">Voltaic Fuse 10W solar panel</a>. This also doubles up as a bag with easy access to the battery, adapters and cables for charging</li>
<li><a href="https://www.amazon.com/dp/B002W9ICWA/" rel="nofollow" data-amazon-asin="[us]B002W9ICWA">Voltaic Arc 20W foldable solar panel</a></li>
<li><a href="https://www.amazon.com/dp/B006L4IP3I/" rel="nofollow" data-amazon-asin="[de][us]B006L4IP3I">Voltaic V60 16.000 mAh battery</a>. You might want to get the newer <a href="https://www.amazon.ca/dp/B00ISH4WEW/" rel="nofollow" data-amazon-asin="[us]ca[uk][ca]B00ISH4WEW">V72 battery</a> which has a capacity of 20.000 mAh.</li>
<li><a href="http://www.voltaicsystems.com/f5521-f3511" rel="nofollow">Voltaic V60 barrel adapter</a></li>
<li><a href="http://www.voltaicsystems.com/laptop-5525m-wire" rel="nofollow">Voltaic 5.5x2.5mm laptop cable</a></li>
</ul>
<p>The battery is connected to the Fuse 10W and the Arc 20W supplies extra power by connecting it to the “Solar Panel Output” on the Fuse 10W. To do this I needed to use one of the included adapters to bridge the V60 barrel adapter with the Arc 20W and then the 5.5x2.5mm wire to connect the V60 barrel adapter to the Fuse 10W. Check out <a href="http://www.voltaicsystems.com/blog/double-power-on-your-solar-charger/">this</a> guide for more information on how to do this.
The above setup works nice for dedicated trips to the beach or a long bike ride where I need a lot of power. But there are a few drawbacks</p>
<ul>
<li>You cannot leave your bike out with all that gear, it will get stolen! So it does take about 5 min. to mount / unmount the setup</li>
<li>The V60 lithium ion battery does not perform too well in the harsh and cold Danish winter. I might look into replacing it with a deep cycle lead acid battery that should perform better in all weather</li>
<li>The angles of the solar panels are less than optimal</li>
<li>The setup has to be adjusted when loading and offloading cargo</li>
</ul>
<p>The next project is to change the setup so both the front hub dynamo and the solar panels charge the same battery and mounting smaller solar panels permanently on the bike.</p>
<p><a href="https://odd-one-out.serek.eu/projects/bullitt-cargo-bike-voltaic-solar-panels/" rel="nofollow">Bullitt cargo bike with solar panels</a> was originally published on Odd One Out.</p>
Poul Serekhttps://Ox3.serek.eu/about/Bullitt cargo bike with solar panelsFree SSL certificates with Let’s Encrypt2016-02-21T00:00:00+00:002016-02-21T20:12:29+00:00https://odd-one-out.serek.eu/code/free-ssl-certificate-lets-encrypt
<p><a href="https://letsencrypt.org">Let’s Encrypt</a> is a free, automated, and open certificate authority which is currently in open beta. Translation: Get free SSL certificates without all the hassle of creation and renewal! This is backed by major players like Mozilla, Facebook, Chrome and Cisco.
Even if you don’t use SSL on your website at the moment, you might be soon because current implementations of HTTP/2 requires it. This blog is already running over HTTP/2 as is my content delivery network <a href="https://www.keycdn.com">KeyCDN</a>. KeyCDN also uses Let’s Encrypt to automatically enable <a href="https://www.keycdn.com/blog/free-ssl-certificates/">free certificates for your CDN domain</a>, e.g. https://cdn.domain.com.
There are other ways of getting free certificates like <a href="https://www.startcomca.com" rel="nofollow">StartSSL.com</a>, but it is a cumbersome process to renew these certificate and create new ones. I will show how you can get quickly up and running and automatically renew the certificates. This post uses NGINX running on Ubuntu 14.04 as an example, but any other webserver and operating system should be able to get a similar solution working.
There a few limitations compared to traditionally issued SSL certificates.</p>
<ul>
<li>Certificates are only domain validated. All other information contained in the certificate is not validated.</li>
<li>Certificates are valid for 90 days. They must be renewed periodically</li>
</ul>
<p>Unless you are running an internet store where trust is very important, this should not be a problem.</p>
<h2 id="setup-lets-encrypt">Setup Let’s Encrypt</h2>
<p>Login to the webserver and download Let’s Encrypt</p>
<figure class="highlight"><pre><code class="language-terminal" data-lang="terminal"><span class="gp">$</span><span class="nb">sudo </span>apt-get <span class="nt">-y</span> <span class="nb">install </span>git
<span class="gp">$</span><span class="nb">cd</span> /opt
<span class="gp">$</span>git clone https://github.com/letsencrypt/letsencrypt</code></pre></figure>
<h2 id="obtain-a-certificate">Obtain a certificate</h2>
<p>In order to obtain a certificate you need to prove you have control of the webserver that your domain points to. There are several ways to do this, but I found the easiest way to allow Let’s Encrypt to store a file temporarily on my webserver in order to validate access to the webserver, a method called webroot. Use the following server block and replace line 13-15 with your own rules. In all the examples in this post replace <code class="highlighter-rouge">www.example.com</code> and <code class="highlighter-rouge">some@email.com</code> with your own information. Since I do not use http (port 80) for anything else, I redirect all traffic to https in this example. The path root <code class="highlighter-rouge">/var/www/html/lets_encrypt</code> is dedicated to Let’s Encrypt.</p>
<figure class="highlight"><pre><code class="language-nginx" data-lang="nginx"><table class="rouge-table"><tbody><tr><td class="gutter gl"><pre class="lineno">1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
</pre></td><td class="code"><pre><span class="k">server</span> <span class="p">{</span>
<span class="kn">listen</span> <span class="mi">80</span><span class="p">;</span>
<span class="kn">server_name</span> <span class="s">www.example.com</span><span class="p">;</span>
<span class="kn">location</span> <span class="p">~</span> <span class="sr">/.well-known</span> <span class="p">{</span>
<span class="kn">allow</span> <span class="s">all</span><span class="p">;</span>
<span class="kn">default_type</span> <span class="s">"text/plain"</span><span class="p">;</span>
<span class="kn">root</span> <span class="n">/var/www/html/lets_encrypt</span><span class="p">;</span>
<span class="p">}</span>
<span class="c1"># Insert your own rules here for you non https server.
</span> <span class="c1"># I choose to redirect all traffic to https
</span> <span class="kn">location</span> <span class="n">/</span> <span class="p">{</span>
<span class="kn">return</span> <span class="mi">301</span> <span class="s">https://</span><span class="nv">$server_name$request_uri</span><span class="p">;</span>
<span class="p">}</span>
<span class="p">}</span></pre></td></tr></tbody></table></code></pre></figure>
<p>Now lets create a certificate using the letsencrypt-auto script. Run this without sudo as the script will ask for permission if needed.</p>
<div class="notice danger no_toc_section">
<p><strong>Warning!</strong> There is a limit to how often you can request and renew a certificate at Let’s Encrypt. Use the <code class="highlighter-rouge">--test-cert</code> parameter when testing.</p>
</div>
<figure class="highlight"><pre><code class="language-terminal" data-lang="terminal"><span class="gp">$</span>/opt/letsencrypt/letsencrypt-auto certonly <span class="nt">--agree-tos</span> <span class="nt">-a</span> webroot <span class="nt">-w</span> /var/www/html/lets_encrypt <span class="nt">-d</span> www.example.com <span class="nt">--rsa-key-size</span> 4096 <span class="nt">--email</span> some@email.com
<span class="go">Updating letsencrypt and virtual environment dependencies......
Requesting root privileges to run with virtualenv: sudo /home/myuser/.local/share/letsencrypt/bin/letsencrypt certonly --agree-tos -a webroot -w /var/www/html/lets_encrypt -d www.example.com --rsa-key-size 4096 --email some@email.com
IMPORTANT NOTES:
- Congratulations! Your certificate and chain have been saved at
/etc/letsencrypt/live/www.example.com/fullchain.pem. Your cert
will expire on 2016-05-15. To obtain a new version of the
certificate in the future, simply run Let's Encrypt again.
- If you like Let's Encrypt, please consider supporting our work by:
Donating to ISRG / Let's Encrypt: https://letsencrypt.org/donate
Donating to EFF: https://eff.org/donate-le</span></code></pre></figure>
<p>If everything went well you should see an output similar to the above. The most recent certificate is always located at <code class="highlighter-rouge">/etc/letsencrypt/live/www.example.com/</code> and contains 4 files:</p>
<ul>
<li><code class="highlighter-rouge">cert.pem</code></li>
<li><code class="highlighter-rouge">chain.pem</code></li>
<li><code class="highlighter-rouge">fullchain.pem</code></li>
<li><code class="highlighter-rouge">privkey.pem</code></li>
</ul>
<p>You are now ready to use the certificate.</p>
<h2 id="configure-nginx">Configure NGINX</h2>
<p>Now we create a test site using SSL using our new certificate:</p>
<figure class="highlight"><pre><code class="language-nginx" data-lang="nginx"><table class="rouge-table"><tbody><tr><td class="gutter gl"><pre class="lineno">1
2
3
4
5
6
7
8
9
10
11
</pre></td><td class="code"><pre><span class="k">server</span> <span class="p">{</span>
<span class="kn">listen</span> <span class="mi">443</span> <span class="s">ssl</span><span class="p">;</span>
<span class="kn">server_name</span> <span class="s">www.example.com</span><span class="p">;</span>
<span class="kn">ssl_certificate</span> <span class="n">/etc/letsencrypt/live/www.example.com/fullchain.pem</span><span class="p">;</span>
<span class="kn">ssl_certificate_key</span> <span class="n">/etc/letsencrypt/live/www.example.com/privkey.pem</span><span class="p">;</span>
<span class="kn">ssl</span> <span class="no">on</span><span class="p">;</span>
<span class="kn">ssl_protocols</span> <span class="s">TLSv1</span> <span class="s">TLSv1.1</span> <span class="s">TLSv1.2</span><span class="p">;</span>
<span class="kn">ssl_prefer_server_ciphers</span> <span class="no">on</span><span class="p">;</span>
<span class="kn">ssl_ciphers</span> <span class="s">‘ECDHE-ECDSA-CHACHA20-POLY1305:ECDHE-RSA-CHACHA20-POLY1305:ECDHE-ECDSA-AES128-GCM-SHA256:ECDHE-RSA-AES128-GCM-SHA256:ECDHE-ECDSA-AES256-GCM-SHA384:ECDHE-RSA-AES256-GCM-SHA384:DHE-RSA-AES128-GCM-SHA256:DHE-RSA-AES256-GCM-SHA384:ECDHE-ECDSA-AES128-SHA256:ECDHE-RSA-AES128-SHA256:ECDHE-ECDSA-AES128-SHA:ECDHE-RSA-AES256-SHA384:ECDHE-RSA-AES128-SHA:ECDHE-ECDSA-AES256-SHA384:ECDHE-ECDSA-AES256-SHA:ECDHE-RSA-AES256-SHA:DHE-RSA-AES128-SHA256:DHE-RSA-AES128-SHA:DHE-RSA-AES256-SHA256:DHE-RSA-AES256-SHA:ECDHE-ECDSA-DES-CBC3-SHA:ECDHE-RSA-DES-CBC3-SHA:EDH-RSA-DES-CBC3-SHA:AES128-GCM-SHA256:AES256-GCM-SHA384:AES128-SHA256:AES256-SHA256:AES128-SHA:AES256-SHA:DES-CBC3-SHA:!DSS’</span><span class="p">;</span>
<span class="p">}</span></pre></td></tr></tbody></table></code></pre></figure>
<p>Restart NGINX and you should have a site up and running!</p>
<figure class="highlight"><pre><code class="language-terminal" data-lang="terminal"><span class="gp">$</span><span class="nb">sudo </span>service nginx restart</code></pre></figure>
<p>Confirm that the site is using the new certificate from Let’s Encrypt in a browser.</p>
<figure>
<noscript><img src="/assets/images/free-ssl-certificate-lets-encrypt-LetsEncrypt.jpg" /></noscript>
<img src="data:image/gif;base64,R0lGODlhAQABAAAAACH5BAEKAAEALAAAAAABAAEAAAICTAEAOw==" data-src="/assets/images/free-ssl-certificate-lets-encrypt-LetsEncrypt.jpg" alt="" class="lazyload fade-in" />
<figcaption>My blog with a Let’s Encrypt SSL certificate</figcaption>
</figure>
<h2 id="configure-automatic-certificate-renewal">Configure automatic certificate renewal</h2>
<p>Renewing a certificate is exactly the same command as creating a new one except there is an extra parameter <code class="highlighter-rouge">--renew-by-default</code>:</p>
<figure class="highlight"><pre><code class="language-terminal" data-lang="terminal"><span class="gp">$</span>/opt/letsencrypt/letsencrypt-auto certonly <span class="nt">--renew-by-default</span> <span class="nt">--agree-tos</span> <span class="nt">-a</span> webroot <span class="nt">-w</span> /var/www/html/lets_encrypt <span class="nt">-d</span> www.example.com <span class="nt">--rsa-key-size</span> 4096 <span class="nt">--email</span> some@email.com</code></pre></figure>
<p>Lets wrap this up in a cron job that runs monthly:</p>
<figure class="highlight"><pre><code class="language-terminal" data-lang="terminal"><span class="gp">$</span><span class="nb">sudo </span>crontab <span class="nt">-e</span></code></pre></figure>
<p>And add the following entry at the end of the file</p>
<figure class="highlight"><pre><code class="language-terminal" data-lang="terminal"><span class="gp">@monthly (/opt/letsencrypt/letsencrypt-auto certonly --renew-by-default --agree-tos -a webroot -w /var/www/html/lets_encrypt --d www.example.com --rsa-key-size 4096 --email some@email.com && service nginx restart) ></span><span class="o">></span> /var/log/letsencrypt.log</code></pre></figure>
<p>Notice that I restart NGINX when a reload should be enough. Reloading NGINX did not work for me, I needed to restart the NGINX server before the updated certificate worked.</p>
<p><a href="https://odd-one-out.serek.eu/code/free-ssl-certificate-lets-encrypt/" rel="nofollow">Free SSL certificates with Let’s Encrypt</a> was originally published on Odd One Out.</p>
Poul Serekhttps://Ox3.serek.eu/about/Free SSL certificates with Let's EncryptPrism code highlighter in WordPress without a plugin2016-02-14T00:00:00+00:002016-02-14T18:57:43+00:00https://odd-one-out.serek.eu/code/prism-wordpress-without-plugin
<p><img src="/assets/images/prism-wordpress-without-plugin-feature.png" alt="" /></p>
<p>I was looking for a lightweight code syntax highlighter for WordPress that only loaded when needed on the page. I found <a href="http://prismjs.com">Prism</a> which fitted my needs. There are a few WordPress plugins for Prism, but I felt that it was not needed and too bloated. Here is what I did</p>
<ol>
<li>Download a minified version of Prism from <a href="http://prismjs.com/download.html">here</a>. You can always re-download this later if you change your mind on which themes, languages and plugins you want. Only choose what you really need to keep the files small.</li>
</ol>
<figure>
<noscript><img src="/assets/images/prism-wordpress-without-plugin-customise-Prism.png" /></noscript>
<img src="data:image/gif;base64,R0lGODlhAQABAAAAACH5BAEKAAEALAAAAAABAAEAAAICTAEAOw==" data-src="/assets/images/prism-wordpress-without-plugin-customise-Prism.png" alt="" class="lazyload fade-in" />
<figcaption>Customise Prism and choose only what you need</figcaption>
</figure>
<ol start="2">
<li>Store the downloaded Prism css and javascript in your theme folder (e.g. <code class="highlighter-rouge">wp-content/themes/your_theme_here</code>). I highly recommend that you use a <a href="https://codex.wordpress.org/Child_Themes">child theme</a> so updates to the main theme does not overwrite your changes. In this example I store the files as <code class="highlighter-rouge">/css/prism.css</code> and <code class="highlighter-rouge">/js/prism.js</code> in my theme folder</li>
<li>Edit you <code class="highlighter-rouge">wp-content/themes/your_theme_here/functions.php</code> file and add the code below. Remember to change the paths to the css and javascript file to where you stored it. The function makes sure that the Prism css and javascript is registered in WordPress and that it is only used if the <code class="highlighter-rouge"><code class="language-</code> text is detected on a page, post, archive page, category page or the frontpage with a list of recent posts. If you do not use Prism it will not be downloaded by the client!</li>
</ol>
<figure class="highlight"><pre><code class="language-php" data-lang="php"><table class="rouge-table"><tbody><tr><td class="gutter gl"><pre class="lineno">1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
</pre></td><td class="code"><pre><span class="k">function</span> <span class="nf">add_prism</span><span class="p">()</span> <span class="p">{</span>
<span class="nx">wp_register_style</span><span class="p">(</span><span class="s1">'prismCSS'</span><span class="p">,</span> <span class="nx">get_stylesheet_directory_uri</span><span class="p">()</span> <span class="o">.</span> <span class="s1">'/css/prism.css'</span><span class="p">);</span>
<span class="nx">wp_register_script</span><span class="p">(</span><span class="s1">'prismJS'</span><span class="p">,</span> <span class="nx">get_stylesheet_directory_uri</span><span class="p">()</span> <span class="o">.</span> <span class="s1">'/js/prism.js'</span><span class="p">);</span>
<span class="k">global</span> <span class="nv">$post</span><span class="p">,</span> <span class="nv">$wp_query</span><span class="p">;</span>
<span class="nv">$post_contents</span> <span class="o">=</span> <span class="s1">''</span><span class="p">;</span>
<span class="k">if</span> <span class="p">(</span> <span class="nx">is_singular</span><span class="p">()</span> <span class="p">)</span> <span class="p">{</span>
<span class="nv">$post_contents</span> <span class="o">=</span> <span class="nv">$post</span><span class="o">-></span><span class="na">post_content</span><span class="p">;</span>
<span class="p">}</span> <span class="k">elseif</span> <span class="p">(</span> <span class="nx">is_archive</span><span class="p">()</span> <span class="o">||</span> <span class="p">(</span><span class="nx">is_front_page</span><span class="p">()</span> <span class="o">&&</span> <span class="nx">is_home</span><span class="p">()))</span> <span class="p">{</span>
<span class="nv">$post_ids</span> <span class="o">=</span> <span class="nx">wp_list_pluck</span><span class="p">(</span> <span class="nv">$wp_query</span><span class="o">-></span><span class="na">posts</span><span class="p">,</span> <span class="s1">'ID'</span> <span class="p">);</span>
<span class="k">foreach</span> <span class="p">(</span> <span class="nv">$post_ids</span> <span class="k">as</span> <span class="nv">$post_id</span> <span class="p">)</span> <span class="p">{</span>
<span class="nv">$post_contents</span> <span class="o">.=</span> <span class="nx">get_post_field</span><span class="p">(</span> <span class="s1">'post_content'</span><span class="p">,</span> <span class="nv">$post_id</span> <span class="p">);</span>
<span class="p">}</span>
<span class="p">}</span>
<span class="k">if</span> <span class="p">(</span> <span class="nb">strpos</span><span class="p">(</span> <span class="nv">$post_contents</span><span class="p">,</span> <span class="s1">'<code class="language-'</span> <span class="p">)</span> <span class="o">!==</span> <span class="kc">false</span> <span class="p">)</span> <span class="p">{</span>
<span class="nx">wp_enqueue_style</span><span class="p">(</span><span class="s1">'prismCSS'</span><span class="p">);</span>
<span class="nx">wp_enqueue_script</span><span class="p">(</span><span class="s1">'prismJS'</span><span class="p">);</span>
<span class="p">}</span>
<span class="p">}</span>
<span class="nx">add_action</span><span class="p">(</span><span class="s1">'wp_enqueue_scripts'</span><span class="p">,</span> <span class="s1">'add_prism'</span><span class="p">);</span></pre></td></tr></tbody></table></code></pre></figure>
<ol start="4">
<li>Start using the Prism syntax highlighter! Take a look at the main <a href="http://prismjs.com">Prism</a> page or even inspect the source of this page. Remember to flush your WordPress cache if using a caching plugin.</li>
</ol>
<p>To add or remove themes, languages or plugins from Prism open the <code class="highlighter-rouge">prism.css</code> file and copy the url from the comment. This url will load the Prism webpage with all your settings checked. Mine was</p>
<p><a href="http://prismjs.com/download.html?themes=prism&languages=markup+css+clike+javascript+aspnet+bash+csharp+ruby+json+lua+php&plugins=line-numbers+autolinker+show-language+command-line">http://prismjs.com/download.html?themes=prism&languages=markup+css+clike+javascript+aspnet+bash+csharp+ruby+json+lua+php&plugins=line-numbers+autolinker+show-language+command-line</a></p>
<p>Now change whatever you like and redo step 1 and 2</p>
<p><a href="https://odd-one-out.serek.eu/code/prism-wordpress-without-plugin/" rel="nofollow">Prism code highlighter in WordPress without a plugin</a> was originally published on Odd One Out.</p>
Poul Serekhttps://Ox3.serek.eu/about/Prism code highlighter in WordPress without a pluginWindows 10 eGPU setup with Optimus!2016-01-31T00:00:00+00:002016-01-30T22:43:35+00:00https://odd-one-out.serek.eu/projects/windows-10-egpu-setup-optimus
<p><img src="/assets/images/windows-10-egpu-setup-optimus-feature.jpg" alt="" /></p>
<p>Last year I did a Thunderbolt 2 enabled external GPU setup using Windows 8.1. I did not upgrade right away when Windows 10 came out because Optimus was <a href="https://www.techinferno.com/index.php?/forums/topic/8918-egpu-window-10-optimus-mode-problem/">broken</a> which meant I could not use the internal laptop screen. Now NVIDIA has finally fixed the problem in the latest 361.75 driver as described in the release highlights:</p>
<blockquote>
<p>Beta support on GeForce GTX GPUs for external graphics over Thunderbolt 3</p>
</blockquote>
<p>I will be starting from scratch so this post assumes you already have</p>
<ul>
<li>A clean copy of Windows 10 installed and fully updated. You can try to upgrade from an older version of Windows, but this did not work for me.</li>
<li>Windows 10 is installed as EFI / UEFI. Check <a href="http://www.thewindowsclub.com/check-if-uefi-or-bios">this</a> post to see if you are running BIOS or UEFI/EFI</li>
<li>A similar hardware setup used in my post <a href="/projects/thunderbolt-2-egpu-setup-using-akitio-thunder2/">here</a>. I use
<ul>
<li><a href="https://www.amazon.com/dp/B00LTAUTHE/" rel="nofollow" data-amazon-asin="[us][ca]B00LTAUTHE[uk]B00OQPWE72[de][es][it][fr]B00NQ23TCU">AKiTiO Thunder2 PCIe box</a></li>
<li><a href="http://www.moddiy.com/products/PCI%252dExpress-PCI%252dE-8X-to-16X-Riser-Card-Flexible-Ribbon-Extender-Cable-w%7B47%7DMolex-%252b-Solid-Capacitor.html">PCI-Express PCI-E 8X to 16X Riser Card Flexible Ribbon Extender Cable w/Molex</a></li>
<li><a href="https://www.amazon.com/dp/B00NVODXR4/" rel="nofollow" data-amazon-asin="[uk]B00NSXYEQW[us]B00NVODXR4[de][es][it][fr][ca]B00NSXYEQW">EVGA GeForce GTX 970 Superclocked ACX 2.0</a></li>
</ul>
</li>
<li>A MacBook without a discrete graphics card. I use a MacBook Pro late 2013</li>
</ul>
<h2 id="driver-installation">Driver installation</h2>
<ol>
<li>Turn the laptop and eGPU off</li>
<li>Plug in the thunderbolt cable from the eGPU setup to the laptop</li>
<li>Power on the eGPU setup</li>
<li>Power on the laptop and boot into Windows 10 (hold down the ALT key to get to the boot menu)</li>
<li>Confirm that the graphics card is detected in Device Manager</li>
</ol>
<figure>
<noscript><img src="/assets/images/windows-10-egpu-setup-optimus-Device-manager-basic-adapter.jpg" /></noscript>
<img src="data:image/gif;base64,R0lGODlhAQABAAAAACH5BAEKAAEALAAAAAABAAEAAAICTAEAOw==" data-src="/assets/images/windows-10-egpu-setup-optimus-Device-manager-basic-adapter.jpg" alt="" class="lazyload fade-in" />
<figcaption>External graphics card detected in device manager before driver installation</figcaption>
</figure>
<ol start="6">
<li>Install NVIDIA driver (use 361.75 or later)</li>
<li>Reboot into Windows 10 (hold down ALT key for boot menu if needed)</li>
<li>The internal and external graphics card should now both be visible without any errors in Device Manager</li>
</ol>
<figure>
<noscript><img src="/assets/images/windows-10-egpu-setup-optimus-Device-manager-NVIDIA.jpg" /></noscript>
<img src="data:image/gif;base64,R0lGODlhAQABAAAAACH5BAEKAAEALAAAAAABAAEAAAICTAEAOw==" data-src="/assets/images/windows-10-egpu-setup-optimus-Device-manager-NVIDIA.jpg" alt="" class="lazyload fade-in" />
<figcaption>External card now correctly detected with the NVIDIA driver installed</figcaption>
</figure>
<ol start="9">
<li>Confirm that the eGPU is working by doing a benchmark, I used <a href="http://www.geeks3d.com/gputest/">Geeks3D GpuTest</a></li>
</ol>
<figure>
<noscript><img src="/assets/images/windows-10-egpu-setup-optimus-Success.jpg" /></noscript>
<img src="data:image/gif;base64,R0lGODlhAQABAAAAACH5BAEKAAEALAAAAAABAAEAAAICTAEAOw==" data-src="/assets/images/windows-10-egpu-setup-optimus-Success.jpg" alt="" class="lazyload fade-in" />
<figcaption>GPU benchmark under Windows 10 using the internal screen (Optimus)</figcaption>
</figure>
<h2 id="usage">Usage</h2>
<p>After the driver installation I do the following every time I need to use the setup:</p>
<ol>
<li>Make sure the laptop and eGPU setup is powered off and disconnected</li>
<li>Connect the laptop to the eGPU system using the Thunderbolt cable</li>
<li>Turn on the laptop and boot into the boot menu by holding down the ALT key</li>
<li>Turn on the eGPU system</li>
<li>Wait a few seconds (I can usually hear the graphics card fans starting) and then continue booting into Windows 10</li>
</ol>
<p>The above steps might be overkill, but for now I have successfully started the setup with no problems many times in a row.</p>
<h2 id="troubleshooting">Troubleshooting</h2>
<ul>
<li>If you boot into a blank screen you probably have the internal screen disabled. Do a full shutdown in Windows 10, disconnect the eGPU setup, boot into Windows 10 without the eGPU, shutdown again and follow the steps described in the section “Usage”. To shutdown with a blank screen you could try the following:
<ul>
<li>If you have access to an external monitor, connect this to the external graphics card and you should now see the login screen for Windows 10. Shutdown Windows 10 from here</li>
<li>If you don’t have access to an external monitor and want to do a proper shutdown, this trick worked for me. If it does not work try it again. When the blue light of the AKiTiO Thunder2 is off then you know the laptop is turned off.
<ul>
<li>Press ENTER</li>
<li>Type your Windows 10 password</li>
<li>Press ENTER</li>
<li>Wait a 10 seconds for Windows to start</li>
<li>Press CMD + R</li>
<li>Type “shutdown -s -t 0”</li>
<li>Press ENTER</li>
</ul>
</li>
</ul>
</li>
<li>If the blue light on the AKiTiO box does not light up when powering on the setup, check that the PCIe riser is firmly attached to the box and the GPU. Also make sure that the thunderbolt port is working (try using it with another device)</li>
<li>If you get an error 12 on the eGPU, try using a DSDT override as described in my previous post <a href="/projects/thunderbolt-2-egpu-built-around-sonnet-echo-express-se-ii-and-pe4l">here</a></li>
</ul>
<h2 id="help-and-support">Help and support</h2>
<p>There is a large eGPU community out there. For the best help and support please visit these sites:</p>
<ul>
<li><a href="https://www.techinferno.com/index.php?/forums/forum/83-diy-e-gpu-projects/">TechInferno</a>: Perhaps the biggest source of info on DIY eGPU setups. This is the first place to look for answers</li>
<li><a href="https://egpu.io/diy-egpu-setup-1-30-nando4/">DIY eGPU setup</a>: Software created by Nando4 which make can make otherwise impossible eGPU combinations work!</li>
</ul>
<p><a href="https://odd-one-out.serek.eu/projects/windows-10-egpu-setup-optimus/" rel="nofollow">Windows 10 eGPU setup with Optimus!</a> was originally published on Odd One Out.</p>
Poul Serekhttps://Ox3.serek.eu/about/Windows 10 eGPU setup with Optimus!Free WordPress Jetpack monitor replacement2016-01-28T00:00:00+00:002016-01-27T22:32:32+00:00https://odd-one-out.serek.eu/code/free-wordpress-jetpack-monitor-replacement
<p><img src="/assets/images/free-wordpress-jetpack-monitor-replacement-feature.png" alt="" /></p>
<p>I am slowly moving away from <a href="http://jetpack.me">Jetpack</a>, a WordPress plugin that bundles many smaller plugins. It is too bulky and I don’t need many of the features it offers. One feature I do need is Monitor that checks if my site is up and running every 5 minutes and notifies me by email when it is down or up. The same functionality can be had for free using <a href="http://uptimerobot.com">uptimerobot.com</a>:</p>
<ol>
<li>Sign up for a free account at <a href="http://uptimerobot.com">uptimerobot.com</a> and activate the account</li>
<li>Add a new HTTP(s) monitor for you website and remember to select your email under “Alert Contacts To Notify”</li>
</ol>
<figure>
<noscript><img src="/assets/images/free-wordpress-jetpack-monitor-replacement-Uptimerobot.com-dashboard.png" /></noscript>
<img src="data:image/gif;base64,R0lGODlhAQABAAAAACH5BAEKAAEALAAAAAABAAEAAAICTAEAOw==" data-src="/assets/images/free-wordpress-jetpack-monitor-replacement-Uptimerobot.com-dashboard.png" alt="" class="lazyload fade-in" />
<figcaption>Uptimerobot.com monitor dashboard</figcaption>
</figure>
<p>Thats it! You now get the same 5 minute interval checking as Jetpacks Monitor service. In addition you get a nice simple dashboard to keep track of up and downtime.</p>
<p>I noticed you can be notified by SMS, but I did not have the option from my service provider to use the email to SMS option in uptimerobot.com and I did not want to pay for the Pro SMS feature they provide. I fixed this by a simple <a href="https://ifttt.com/recipes">IFTTT</a> recipe that listens on the RSS feed uptimerobot.com exposes for my monitor.</p>
<ol>
<li>Create an IFTTT account</li>
<li>Login to IFTTT</li>
<li>Create a SMS channel with the phone number you want to receive the notification</li>
<li>Create a new recipe
<ol>
<li>For “this” choose the Feed channel, choose “new feed item”, enter the RSS url from uptimerobot.com –> “My settings” –> “RSS Notifications” and click “create trigger”</li>
<li>For “that” choose the SMS channel, choose “Send me an SMS” and click “create action”</li>
<li>Finally click “create recipe”</li>
</ol>
</li>
</ol>
<p>IFTTT now listens for changes in the uptimerobot.com RSS feed for your monitor and sends an SMS for free every time anything happens.</p>
<p><a href="https://odd-one-out.serek.eu/code/free-wordpress-jetpack-monitor-replacement/" rel="nofollow">Free WordPress Jetpack monitor replacement</a> was originally published on Odd One Out.</p>
Poul Serekhttps://Ox3.serek.eu/about/Free WordPress Jetpack monitor replacement