<?xml version="1.0" encoding="UTF-8"?><rss xmlns:dc="http://purl.org/dc/elements/1.1/" xmlns:content="http://purl.org/rss/1.0/modules/content/" xmlns:atom="http://www.w3.org/2005/Atom" version="2.0" xmlns:media="http://search.yahoo.com/mrss/"><channel><title><![CDATA[Idling away retirement]]></title><description><![CDATA[Wasting time on computers]]></description><link>http://technicals.two-drifters.co.uk/</link><image><url>http://technicals.two-drifters.co.uk/favicon.png</url><title>Idling away retirement</title><link>http://technicals.two-drifters.co.uk/</link></image><generator>Ghost 5.38</generator><lastBuildDate>Thu, 09 Apr 2026 05:43:38 GMT</lastBuildDate><atom:link href="http://technicals.two-drifters.co.uk/rss/" rel="self" type="application/rss+xml"/><ttl>60</ttl><item><title><![CDATA[Wordle Pangrams]]></title><description><![CDATA[
]]></description><link>http://technicals.two-drifters.co.uk/wordle-pangrams/</link><guid isPermaLink="false">640f1a3a478d1d0ef02dafe2</guid><dc:creator><![CDATA[Brian Jones]]></dc:creator><pubDate>Sat, 12 Nov 2022 17:48:34 GMT</pubDate><media:content url="http://technicals.two-drifters.co.uk/content/images/2022/11/Screenshot-from-2022-11-12-18-51-04.png" medium="image"/><content:encoded><![CDATA[<img src="http://technicals.two-drifters.co.uk/content/images/2022/11/Screenshot-from-2022-11-12-18-51-04.png" alt="Wordle Pangrams"><p></p><p>It all started with my friend Tim watching this video by Matt Parker</p><figure class="kg-card kg-embed-card"><iframe width="200" height="113" src="https://www.youtube.com/embed/_-AfhLQfb6w?feature=oembed" frameborder="0" allow="accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture" allowfullscreen title="Can you find: five five-letter words with twenty-five unique letters?"></iframe></figure><p>The problem Matt was trying to solve was to find 5 5-letter words (as in the Wordle dictionary) that between them contained 25 different letters of the alphabet, i.e. none of the five words had any letters in common.</p><p>I sat and thought for a while and came up with - 24 letters with WALTZ, FJORD, NYMPH, QUICK, and BEGS - close but no cigar, as the saying goes. Time to write some code. I&apos;d not seen the video at the time so didn&apos;t know that Matt had written some code that took almost two months to run. I set myself a target of under an hour (on my 12 year old first gen i7 desktop).</p><p>The problem space is huge, there are 12972 words in the original, pre-NY Times and so there are 12972*12971*12970*12969*12968/120 different combinations - that&apos;s 3,058,571,387,612,503,040 which might take more than an hour (more than a year, more than a decade...) to work out and check all of them. </p><p>One way to reduce that is to ignore all the words that have two or more of the same letter in them - they automatically exclude themselves from the solution. Removing them reduces the list size to a mere 8322 words. But we can go one stage further and remove all the anagrams of the remaining words (remembering them so if any are in the solution we can add in the extra anagram versions of the solution). Amazingly that drops the word count to 5182.</p><p>Now that seems like a huge reduction, over 50% but simply checking every combination would require 5182*5181*5180*5179*5178/120 =310,190,561,767,076,760 which is still a HUGE number. Checking every combination simply won&apos;t work.</p><p>I spent a long time thinking about how to loop over the combinations breaking out of the loops immediately I found a fail, but my brain simply wasn&apos;t big enough to work out the complex code. Even worse, a natural solution might involve recursion. To be honest I stalled, but a chance conversation down the pub with another friend, Charlie and he sent me a list of solutions the following day - done using a spreadsheet! Amazing!</p><p>Then I had an idea, rather than stop iterating every time there was a clash, why not filter the iterations so that clashes could never happen. So the first iteration I would iterate all the way through the entire 5182 words but for each word (<em>word1</em>, say) I would then build a list of all words that didn&apos;t have any common letters and iterate through this (<em>word2</em>) say to build a list of <em>word3</em> letters and if this existed I&apos;d build a list of word 4 letters (this list wouldn&apos;t happen too often and would likely be quite small). If I did have a <em>word4</em> list (i.e. words that had no letters in common with <em>word1</em>, <em>word2</em>, <em>word3</em>, and <em>word4</em> then I&apos;d try and build a list of any words that also had nothing in common with any of these. The rare occasion this <em>word5</em> list actually succeeded then <em>word1</em>, <em>word2</em>, <em>word3</em>, <em>word4,</em> and any <em>word5</em>s would be the solution to the problem. </p><p>This approach is very Pythonic too as Python&apos;s list comprehension makes building the filtered lists pretty trivial. There were some further optimisations, the main one being that if I&apos;m some way down that 5182 long list of <em>word1</em> words I need only start building the <em>word2</em> list from the next word (e.g. if I&apos;m at 100 words down the original 5182 word list for <em>word1</em>, I can start filtering the word 2 list at word 101). Why? Lets take the 20th word as an example. If I compare the 100th word in the list as <em>word1</em> and the 20th word in the list as <em>word2</em> that&apos;s exactly the same as comparing the 20th word in the list as <em>word1</em> with the 100th word in the list as <em>word2</em> - which I&apos;ve already done. There&apos;s also a very minor tweak that means I can cut the <em>word1</em> list 4 words (i.e. at 5178) before the end, the <em>word2</em> list 3 words before the end, etc.</p><p>Assuming the filtering is fast (it is) then the number of iterations shrinks dramatically. That list of <em>word2 </em>words is generally a fraction of the original 5182 and the <em>word3</em> list is much, much, smaller. If any <em>word4</em>s exist at all they will be pretty scarce and, on the 10 occasions we actually have a list of <em>word5</em>s then it is a list of length 1!<em> &#xA0;</em>The problem with this approach is that, since the filtered word lists are being built dynamically it is not possible to calculate their length in advance, and so work out how many iterations the code might take. So I had to temporarily add some counts and was somewhat amazed that this approach takes just 63,515,889 - a relatively small number, especially when compared to 3,729,483,737,997,660,720</p><p>A real life example, I set the debugger to break at word 2000 out of the 5182 words, which happened to be &apos;carts&apos;. So the second list of words will be all the words from position 2001 to the end of the list (i.e. 2182 of them) but filtered to exclude any with any of the letters &apos;c&apos;, &apos;a&apos;, &apos;r&apos;, &apos;t&apos;, or &apos;s&apos; - this reduces that 2182 to just 413 words to check. The first of these happened to be at the first position in the 413 words list and was &#xA0;&apos;defog&apos;. So now we check the 412 words in the list from position 2 to the end - but exclude any with letters in &apos;defog&apos; (we don&apos;t need to exclude any &apos;carts&apos; letters this time as they&apos;ve already been filtered). Filtering the 412 words leads to just 27 words in the third list to check, and the first 7 of these meant that no further 4th words could be found, but the 8th in the list &apos;jumby&apos; did give a couple of options for word 4 - &apos;plink&apos; and &apos;whilk&apos;. But neither of those lead to a fifth word and solution. </p><p>The next thing was to optimise the code, even just 63 and a half million comparisons of 5 digit strings might take some time. So I converted each of those unique lettered, anagrams removed words to 26 bit binary number - simply if there was an &apos;<em>a</em>&apos; in the word then bit 25 would be set, a &apos;<em>b</em>&apos; meant bit 24 ... and a &apos;<em>z</em>&apos; bit 0. Now working with these, comparing two is simply a Boolean AND and if that result is 0 the binary numbers have no bits in common, i.e. the original words have no letters in common - Boolean ANDs tend to be very fast computer operations.</p><p>Here&apos;s the algorithm:</p><!--kg-card-begin: markdown--><pre><code class="language-Python">    answers = []
    # Get first candidate word (bword0) by interating through the entire list
    # Note -  don&apos;t need last 4 words, they will be checked in the later iterations
    for i1, bword1 in enumerate(binwords1[:-4]): # Binwords1 is the entire list of words
        # For each word in the forst word list build the second word list,
        # starting at the current position in the list + 1
        # but filtering out all words that have any letters in common with bword1
        # and then iterate through this list of second words (bword2)
        binwords2 = [bw for bw in binwords1[i1 + 1:] if not (bw &amp; bword1)]
        if binwords2:  # The code works without checking that the list exists but the check optimises the code slightly!
            for i2, bword2 in enumerate(binwords2[:-3]): # Again, last 3 words will be checked later
                # Build a third word list in a similar fashion and iterate through
                # Note since the list binword2 is filtered not to contain bits common with bword1 we need only check ...
                # commonality with bword2 (i.e. no need to accumulate a bitmap for all previous words to check against)
                binwords3 = [bw for bw in binwords2[i2 + 1:] if not (bw &amp; bword2)]
                if binwords3:
                    for i3, bword3 in enumerate(binwords3[:-2]):
                        # Now try to build a fourth wordlist of words with letters not in words1, 2, and 3
                        binwords4 = [bw for bw in binwords3[i3 + 1:] if not (bw &amp; bword3)]
                        if binwords4:
                            for i4, bword4 in enumerate(binwords4[:-1]):
                                # We have 4 words - try and build a list of 5th words that don&apos;t have letter matches
                                # If this list is non-empty we have solutions!
                                binwords5 = [bw for bw in binwords4[i4 + 1:] if not (bw &amp; bword4)]
                                if binwords5:
                                    for bword5 in binwords5:
                                        answers.append([bword1, bword2, bword3, bword4, bword5])

    return answers # List of all the possible Wordle Pangrams (excluding anagrams)
</code></pre>
<!--kg-card-end: markdown--><p>The above code certainly shows the power of Python list comprehension and slicing in the one-liner for building the filtered lists.</p><!--kg-card-begin: markdown--><pre><code class="language-Python">binwords2 = [bw for bw in binwords1[i1 + 1:] if not (bw &amp; bword1)]
</code></pre>
<!--kg-card-end: markdown--><p>And that&apos;s about it. There&apos;s obviously some code to remove words with duplicate letters - but again Python helps :</p><pre><code class="language-Python">if len(set(aword)) == len(aword)</code></pre><p>is a simple check for words of all unique letters and, of course, converting the words to binary numbers makes detecting anagrams as simple as checking the binary number is unique. There&apos;s also some horrible kludge code to scan the solutions checking if any of the words are anagrams and, if so, creating new, alternative solutions. &#xA0; Believe it or not, one of the words in one of the solutions actually has an anagram!</p><p>Oh, if you are interested - here&apos;s the complete list of Wordle Pangrams:</p><pre><code>fjord, nymph, waltz, gucks, vibex
fjord, chunk, waltz, gymps, vibex
prick, glent, jumby, vozhd, waqfs
brick, jumpy, glent, vozhd, waqfs
jumpy, bling, treck, vozhd, waqfs
bemix, clunk, grypt, vozhd, waqfs
blunk, cimex, grypt, vozhd, waqfs
brung, cylix, kempt, vozhd, waqfs
brung, xylic, kempt, vozhd, waqfs
clipt, jumby, kreng, vozhd, waqfs
jumby, pling, treck, vozhd, waqfs
</code></pre><p>Oh, yes, I also forgot - how long does this code take? Well on my 12 year old i7 it takes 2 mins 20 secs - I beat my hour target by a margin that surprised even me. It&apos;s almost a minute longer on my MacBook Air which shows my aging Linux desktop is still a pretty good development machine!</p><p></p><p>Here&apos;s the complete code:</p><pre><code class="language-Python">import WordleDict
import string
import time


def time_usage(func):
    &quot;&quot;&quot;
    Simple decorator function to calculate and print time taken for any function
    &quot;&quot;&quot;

    def wrapper(*args, **kwargs):
        begin_ts = time.time()
        retval = func(*args, **kwargs)
        end_ts = time.time()
        print(F&quot;Function - {func.__name__}: Elapsed time: {(end_ts - begin_ts)}&quot;)
        return retval

    return wrapper


def binariseword(word):
    &quot;&quot;&quot;
    Convert any word into 26-bit bitmap a=bit25 set, b= bit24 et .. z = bit 0 set
    NOTE - although this was written for 5 char Wordle words, it will work withwords of any length and either ...
    Upper or Lowr case, or a mix thereof

    :param word: A character string containing alphabetic only characters
    :return bitmap: the binary representation of the word
    &quot;&quot;&quot;

    bitmap = 0
    for letter in word.lower():
        bit = 1 &lt;&lt; string.ascii_lowercase.index(letter)  # Set the bit depending on position of letter in alphabet
        bitmap = bitmap | bit  # and accumulate into a bitmap of all letters in the word
    return bitmap


# @time_usage
def uniqueletters_noanagrams_bin(wordlist):
    &quot;&quot;&quot;
    Filter a list of words removing any words that contain duplicate letters and or anagrams
    Also convert any such words to a binary bitmap equivalent (see binarise word)
    :param wordlist: list of character string words
    :return bwdict: A dictionary of with the filtered binarised words as keys with the char equivalent values
    :return anagdict: A dictionary of binarised words with an array of anagrams - the word as in bwdict is the first
                      element in this array - this is only created if there are actually anagrams
    &quot;&quot;&quot;

    # Should be separate functions but for a large wordlist combining into one saves an iteration
    bwdict = {}
    anagdict = {}
    bitset = set()
    for word in wordlist:
        # Is the set of unique letters in the word the same length as the number of letters in the word?
        if len(set(word)) == len(word):  # If so the word doesn&apos;t have duplicate letters
            bitmap = binariseword(word)  # so convert the word to its binary bitmap equivalent
            # Does the set of bitmaps so far contain this bitmap
            if bitmap not in bitset:  # No - so it is not an anagram of an existing word (which would have same bitmap)
                bwdict[bitmap] = word
                bitset.add(bitmap)  # and the the new binarised word into the set of all such so far
            else:  # Yes - we already have this bitmap so the word is an anagram of an existing word
                if bitmap not in anagdict:  # Do we already have other anagrams of the word?
                    anagdict[bitmap] = [bwdict[bitmap],
                                        word]  # No - create a dictionary entry for the original word ...
                else:  # Yes - add it to the existling list of anagrams                  ... and its newly found anagram
                    anagdict[bitmap].append(word)
    return bwdict, anagdict


@time_usage
def get_pangrams(binwords1):
    &quot;&quot;&quot;
    Find all five word panagrams in the list of 5 letter Wordle words (which have been binarised)
            - i.e. a set of words comprising 25 distinct letters (or 25 distinct bits in the binarised version)

    :param binwords: A list of binarised words to search and check for pangrams
    :return answers: A list of 5 entry lists containing binarised words that comprise the pangram
    &quot;&quot;&quot;

    # This module is the core of this code and finding an efficient algorithm is necessary
    # There are 12972 words in the Wordle list (original version pre-NY Times). After filtering out words with ...
    # duplicate letters this is still 5182 unique character strings. So there are are 5182!/5177! or ...
    # 3.7359635e+18 total combinations!!

    # This code reduces the iterations substantially (to just 74582079 iterations)
    # It iterates through the entire list of words and for each word produces a (smaller) filtered list of all ...
    # words which do not share common letters with that word. It then iterates through this list for the second word ...
    # and for each second word it produces a (now much smaller) list of filtered third words, For each word in this ...
    # third list a fourth word list of all words not sharing letters with words1, 2 or 3 is produced ...
    # (if any such exist) and should iterating this produce any list of fifth words we have a solution!

    # The iteration loop for the second word need only start after the current position of the current initial word ...
    # iteration e.g. if we are at iteration 100 in word 0 we can start building the filtered list for the second ...
    # word starting at position 101. This is (again for example) word 10 on the second iteration compared with word ...
    # 100 on the first iteration is the same as word 10 on the first iteration compared with word 100 on the second ...
    # which has already been checked.

    # The code is further optimised by using the binarised equivalent version of the words so a simply Boolean AND ...
    # suffices to check commonality of bits (i.e. letters in the original word)

    answers = []
    # Get first candidate word (bword0) by interating through the entire list
    # Note -  don&apos;t need last 4 words, they will be checked in the later iterations
    for i1, bword1 in enumerate(binwords1[:-4]): # Binwords1 is the entire list of words
        # For each word in the forst word list build the second word list,
        # starting at the current position in the list + 1
        # but filtering out all words that have any letters in common with bword1
        # and then iterate through this list of second words (bword2)
        binwords2 = [bw for bw in binwords1[i1 + 1:] if not (bw &amp; bword1)]
        if binwords2:  # The code works without checking that the list exists but the check optimises the code slightly!
            for i2, bword2 in enumerate(binwords2[:-3]): # Again, last 3 words will be checked later
                # Build a third word list in a similar fashion and iterate through
                # Note since the list binword2 is filtered not to contain bits common with bword1 we need only check ...
                # commonality with bword2 (i.e. no need to accumulate a bitmap for all previous words to check against)
                binwords3 = [bw for bw in binwords2[i2 + 1:] if not (bw &amp; bword2)]
                if binwords3:
                    for i3, bword3 in enumerate(binwords3[:-2]):
                        # Now try to build a fourth wordlist of words with letters not in words1, 2, and 3
                        binwords4 = [bw for bw in binwords3[i3 + 1:] if not (bw &amp; bword3)]
                        if binwords4:
                            for i4, bword4 in enumerate(binwords4[:-1]):
                                # We have 4 words - try and build a list of 5th words that don&apos;t have letter matches
                                # If this list is non-empty we have solutions!
                                binwords5 = [bw for bw in binwords4[i4 + 1:] if not (bw &amp; bword4)]
                                if binwords5:
                                    for bword5 in binwords5:
                                        answers.append([bword1, bword2, bword3, bword4, bword5])

    return answers



# @time_usage
def print_with_angrams(bw_solutions, anagdict):
    &quot;&quot;&quot;
    Print all the pangram solutions including all anagram variants

    :param bw_solutions: A list of lists of 5 letter binarised word pangrams
    :param anagdict: A dictionary mapping the binarised word to all anagrams thereof
    :return NONE:
    &quot;&quot;&quot;
    # This is horrible KLUDGE code. Converting the binarised word lists to words and printing is trivial but ...
    # folding in all anagrams is horrible, especially since more than one word in a solution may have one or more ...
    # anagrams

    solutions = []
    for bw_solution in bw_solutions: # Get a single binary solution
        solution = []
        for bword in bw_solution:
            solution.append(bwdict[bword])  # Convert the binarised word back to its char equivalent
        solutions.append(solution)          # and add the list of char pangrams into a total solutions list
        for bword in bw_solution:
            if bword in anagdict:           # iterate through the binarised words again looking for anagrams
                for i in range(len(anagdict[bword]) - 1): # there may be more than one so need to iterate anagrams
                    oldword = anagdict[bword][i]          # get the original word, or previous anagram
                    newword = anagdict[bword][i + 1]      # and get its newly found anagram
                    for oldsolution in solutions:         # go back through all the existing lists of pangrams
                        if oldword in oldsolution:        # and if the old word is in the list replace it with the new
                            newsolution = list(map(lambda x: x.replace(oldword, newword), oldsolution))
                            solutions.append(newsolution) # dynamically extend the list of solutions so if other ...
                                                          # words in this pangram are anagrammed all combinations ...
                                                          # will be produced.

    for solution in solutions: # Now simply print all the solutions which now includes all anagram variants
        print(&apos;, &apos;.join([word for word in solution]))




# END OF THE FUNCTIONS - MAIN CODE STARTS HERE

if __name__ == &quot;__main__&quot;:
    bwdict, anagdict = uniqueletters_noanagrams_bin(WordleDict.all)  # Filter out anagrams and duplicate letter words
    solutions = get_pangrams(list(bwdict.keys()))  # Use the binary equivalents of the words to find all pangrams
    print_with_angrams(solutions, anagdict)        # Print the solutions along with any anagram variants
</code></pre>]]></content:encoded></item><item><title><![CDATA[What does your browser tell folks? - part 2 - Implementation]]></title><description><![CDATA[<p></p><!--kg-card-begin: markdown--><p>Part 1 showed how to implement a Python Flask server and use WhoIsXMLAPI and the browser user-agent information to find some basic information about the client.</p>
<h3 id="proxyinganexistingproductionservertotheflaskserver">Proxying an existing production server to the flask server</h3>
<p>We now need to make that accessible via the web. Generally this means creating a</p>]]></description><link>http://technicals.two-drifters.co.uk/what-does-your-browser-tell-folks-part-1-implementation/</link><guid isPermaLink="false">640f1a3a478d1d0ef02dafdf</guid><dc:creator><![CDATA[Brian Jones]]></dc:creator><pubDate>Wed, 17 Feb 2021 17:49:45 GMT</pubDate><media:content url="http://technicals.two-drifters.co.uk/content/images/2021/02/4767780.jpg" medium="image"/><content:encoded><![CDATA[<img src="http://technicals.two-drifters.co.uk/content/images/2021/02/4767780.jpg" alt="What does your browser tell folks? - part 2 - Implementation"><p></p><!--kg-card-begin: markdown--><p>Part 1 showed how to implement a Python Flask server and use WhoIsXMLAPI and the browser user-agent information to find some basic information about the client.</p>
<h3 id="proxyinganexistingproductionservertotheflaskserver">Proxying an existing production server to the flask server</h3>
<p>We now need to make that accessible via the web. Generally this means creating a subdomain for the server, requesting SSL certificates etc. However, since I already have a Technicals website (you are reading it at the moment) it will be easier to hang it off this site, and then subdomain registration and SSL registration is already taken care off.<br>
So we need to create a new URL route, /mike on this site and reverse proxy this to the flask server running on 192.168.1.15:5000</p>
<pre><code>    location /mike/ {
        proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
        proxy_set_header X-Forwarded-Proto $scheme;
        proxy_set_header X-Real-IP $remote_addr;
        proxy_set_header Host $http_host;
        proxy_pass http://192.168.1.15:5000/;

    }
</code></pre>
<p>is all that is needed.</p>
<p>Now browsing to<br>
<a href="http://technicals.two-drifters.co.uk/mike">/mike</a><br>
will redirect from the normal Technicals server machine to the flask server.</p>
<p>One further tweak - we are restricted to 1000 WhoIsXMLAPI lookups a month and it would be easy to exceeed that during heavy testing, or if the website fell foul to an attack of any sort, so the flask server expects a parameter on the URL, if it is present and with the correct value then the code will do the WhoIsXMLAPI lookup, if not it will use a saved pre-canned one. This requires no implementation changes as it is handled entirely in the flask but it does mean that to actually get the code to implement a real browser lookup it needs to be called with the parameter - e.g.</p>
<p><a href="http://technicals.two-drifters.co.uk/mike?type=xxxx">/mike?type=xxxx</a></p>
<p>[Oh, if that doesn&apos;t work for you, it&apos;ll be because the parameter isn&apos;t really &apos;type=xxxx&apos; :-) ]</p>
<h3 id="makingtheflaskserviceasystemservice">Making the flask service a system service</h3>
<p>The code works and is now available on the external internet via the URL above but currently it needs to be started manually and will need manual restarting should the 192.168.1.15 machine reboot.</p>
<p>We will create a system service to automatically start the flask server at startup.</p>
<pre><code>sudo nano /etc/systemd/system/internetprobe.service
</code></pre>
<p>and add the following</p>
<pre><code>[Unit]
Description=Demo internet credentials check for Mike Eacott
After=network.target

[Service]
User=pi
WorkingDirectory=/home/pi/internetprobe
ExecStart=/home/pi/internetprobe/security1.py
Restart=always

[Install]
WantedBy=multi-user.target
</code></pre>
<p>We can start it straight away to test it</p>
<pre><code>sudo systemctl daemon-reload
sudo systemctl start internetprobe
</code></pre>
<p>and check it is running</p>
<pre><code>sudo systemctl status internetprobe
</code></pre>
<p>before enabling it so that it will startup a boot time</p>
<pre><code>sudo systemctl enable internetprobe
</code></pre>
<p>That should be it!</p>
<!--kg-card-end: markdown--><p> </p>]]></content:encoded></item><item><title><![CDATA[What does your browser tell folks? - part 1 - Code]]></title><description><![CDATA[<p></p><!--kg-card-begin: markdown--><p>My good friend Mike is a local Cyber champion and often gives talks to various organisations, generally retired folks. His next talk is to be on the subject of Internet Safety and he wanted to demonstrate the sort of information that is available to any website you visit.</p>
<p>For example</p>]]></description><link>http://technicals.two-drifters.co.uk/security/</link><guid isPermaLink="false">640f1a3a478d1d0ef02dafde</guid><dc:creator><![CDATA[Brian Jones]]></dc:creator><pubDate>Fri, 12 Feb 2021 16:08:54 GMT</pubDate><media:content url="http://technicals.two-drifters.co.uk/content/images/2021/02/hacker-3480124_1280.jpg" medium="image"/><content:encoded><![CDATA[<img src="http://technicals.two-drifters.co.uk/content/images/2021/02/hacker-3480124_1280.jpg" alt="What does your browser tell folks? - part 1 - Code"><p></p><!--kg-card-begin: markdown--><p>My good friend Mike is a local Cyber champion and often gives talks to various organisations, generally retired folks. His next talk is to be on the subject of Internet Safety and he wanted to demonstrate the sort of information that is available to any website you visit.</p>
<p>For example - how do sites like this, know about you?<br>
<a href="https://www.whatsmyip.org/more-info-about-you/?ref=idling-away-retirement">https://www.whatsmyip.org/more-info-about-you/</a></p>
<p>So I took up the challenge to see if I could write something to provide similar information and just how simple it might be.</p>
<p>The information comes from 3 separate sources</p>
<ul>
<li>Your Browser header information</li>
<li>The WHOIS entry corresponding to your IP address</li>
<li>A collated database which is possibly updated from sites to whom you have given permission to access your location.</li>
</ul>
<p>The Browser headers contain information about your Browser (obviously), Operating System, machine type and model, and screen size.</p>
<p>The WHOIS entry has the details of your ISP and connection type.</p>
<p>The database entries should be able to make a reasonable stab at your geo-location although my be well out (up to 200km!). I suspect accuracy depends on how many websites, in the past, have been granted access to your location. Also some database repositories appear more accurate than others.</p>
<p>For this exercise I chose WhoisXMLAPI - <a href="https://ip-geolocation.whoisxmlapi.com/api?ref=idling-away-retirement">https://ip-geolocation.whoisxmlapi.com/api</a> because their geolocation of my own IP was fairly accurate and they had a generous free 1000 lookups per month without the need for credit card registration. They also do the IP lookup (otherwise I would need to do a <code>request.remote_addr</code>), the WhoIs lookup, and even do a DNS lookup to list domains registered to this IP.</p>
<p>Let&apos;s look at the Python code - it really is pretty minimal - just get and parse the User-Agent and then call SimpleGeoIP for all the IP, ISP, and geo information. A little bit of code saves wasting Simple GeoIP calls when testing, by reusing a &apos;Blue Peter&apos; call that I made earlier.The results from both the User-Agent and SimpleGeoIp calls (both Python dictionary structures) are then simply passed onto the Jinja2 template presenting the page on the Flask server.</p>
<p>Here&apos;s the ENTIRE Python code:</p>
<pre><code class="language-python">#! /usr/bin/python3
from flask import Flask, request, render_template
from flask_simple_geoip import SimpleGeoIP
from user_agents import parse

app= Flask(__name__)  # define the flask web server
app.config[&quot;GEOIPIFY_API_KEY&quot;] = &quot;xxxxxxxxxxxxxxxxxxxxxxxx&quot; # my API key for SimpleGeoIP

@app.route(&apos;/&apos;) #Define the / (i.e. index) page of the website
def index():
# Add a &quot;type=xxxx&quot; parameter to the URL so that testing doesn&apos;t waste SimpleGeoIP lookups 
    production = True if request.args.get(&apos;type&apos;) == &apos;xxxx&apos; else False
    
    UAHeaders = request.headers.get(&apos;User-Agent&apos;) # Get the HTTP user_agent
    UAparsed = parse(UAHeaders) # Parse it nicely
    
    if production:
        simple_geoip = SimpleGeoIP(app)  # set up the SimpleGeoIp API
        geoip_data = simple_geoip.get_geoip_data() # call SimpleGeoipto get IP info
        runtype = &quot;Production&quot;
    else: # if just testing use precanned information  
        geoip_data = {&apos;ip&apos;: &apos;81.174.xx.xx&apos;, &apos;location&apos;: {&apos;country&apos;: &apos;GB&apos;, &apos;region&apos;: &apos;England&apos;, &apos;city&apos;: &quot;xxxxxxxxxx&quot;, &apos;lat&apos;: xxxxxxxx, &apos;lng&apos;: -1.39905, &apos;postalCode&apos;: &apos;&apos;, &apos;timezone&apos;: &apos;+00:00&apos;, &apos;geonameId&apos;: 7287933}, &apos;domains&apos;: [&apos;two-drifters.co.uk&apos;], &apos;as&apos;: {&apos;asn&apos;: 6871, &apos;name&apos;: &apos;Plusnet plc&apos;, &apos;route&apos;: &apos;81.174.128.0/17&apos;, &apos;domain&apos;: &apos;http://www.plus.net&apos;, &apos;type&apos;: &apos;Cable/DSL/ISP&apos;}, &apos;isp&apos;: &apos;Plusnet&apos;, &apos;connectionType&apos;: &apos;broadband&apos;
        runtype = &quot;Test&quot;

    return render_template(&apos;index.html&apos;,runtype=runtype, ua=UAparsed, geoip=geoip_data) # render the webpage
   
if __name__ == &apos;__main__&apos;:  # main program code
    app.run(host=&apos;192.168.1.15&apos;, debug=True) # simply run our flask webserver app on this machine
</code></pre>
<p><br><br><br>
In the HTML a couple of Javascript functions get the screen and viewport sizes (thanks to Andy Langton for this code)</p>
<pre><code class="language-javascript">&lt;script type=&quot;text/javascript&quot;&gt;
      document.getElementById(&quot;screensize&quot;).innerHTML =
        screen.width + &quot;x&quot; + screen.height;
    &lt;/script&gt;

    &lt;script type=&quot;text/javascript&quot;&gt;
      var wh = getViewportSize();
      var w = wh[0];
      var h = wh[1];

      function getViewportSize() {
        var viewportwidth;
        var viewportheight;
        if (typeof window.innerWidth != &quot;undefined&quot;) {
          (viewportwidth = window.innerWidth),
            (viewportheight = window.innerHeight);
        } else if (
          typeof document.documentElement != &quot;undefined&quot; &amp;&amp;
          typeof document.documentElement.clientWidth != &quot;undefined&quot; &amp;&amp;
          document.documentElement.clientWidth != 0
        ) {
          (viewportwidth = document.documentElement.clientWidth),
            (viewportheight = document.documentElement.clientHeight);
        } else {
          (viewportwidth = document.getElementsByTagName(&quot;body&quot;)[0]
            .clientWidth),
            (viewportheight = document.getElementsByTagName(&quot;body&quot;)[0]
              .clientHeight);
        }
        return [viewportwidth, viewportheight];
      }

      document.getElementById(&quot;viewportsize&quot;).innerHTML = w + &quot;x&quot; + h;
    &lt;/script&gt;
</code></pre>
<p><br><br><br>
And there is a function directly lifted from Here&apos;s developer documentation to display the map - the only difference being that the lat/lon coordinates are passed into the function from the SimpleGeoIP call.</p>
<pre><code class="language-javascript">&lt;html&gt;
  &lt;head&gt;
  ...
     &lt;script
      src=&quot;https://js.api.here.com/v3/3.1/mapsjs-core.js&quot;
      type=&quot;text/javascript&quot;
      charset=&quot;utf-8&quot;
    &gt;&lt;/script&gt;
    &lt;script
      src=&quot;https://js.api.here.com/v3/3.1/mapsjs-service.js&quot;
      type=&quot;text/javascript&quot;
      charset=&quot;utf-8&quot;
    &gt;&lt;/script&gt;
  ...  
  &lt;/head&gt;

  &lt;body style=&quot;margin: 0&quot;&gt;
  ...   
    &lt;script&gt;
        var platform = new H.service.Platform({
          &apos;apikey&apos;: &apos;xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx&apos;
        });

      var lat = {{ geoip[&apos;location&apos;][&apos;lat&apos;]}};
      var long = {{geoip[&apos;location&apos;][&apos;lng&apos;]}};
      var maptypes = platform.createDefaultLayers();
       var map = new H.Map(
          document.getElementById(&apos;mapContainer&apos;),
          maptypes.vector.normal.map,
          {
            zoom: 13,
            center: { lat: lat, lng: long }
          });
    &lt;/script&gt;
  ...  
  &lt;/body&gt;
&lt;/html&gt;
</code></pre>
<p><br><br><br>
The corresponding CSS is very basic and the only tricksy bit as a nice two column formatter.</p>
<p>And that&apos;s it! As long as things are in the correct sub-directories, (templates/ for the html and static/css/ for the CSS) it just works. Running it and browsing to 192.168.1.15:5000 gives a page similar to this:</p>
<p><img src="http://technicals.two-drifters.co.uk/content/images/2021/02/Screenshot-from-2021-02-17-16-55-56.png" alt="What does your browser tell folks? - part 1 - Code" loading="lazy"></p>
<p>That was run via my VPN. My normal home location does actually include my City details, but not my Postcode.</p>
<!--kg-card-end: markdown--><p></p><p></p>]]></content:encoded></item><item><title><![CDATA[Jetson Nano Basics]]></title><description><![CDATA[<p></p><h3 id="basic-install-and-fix-update-problem">Basic Install and fix Update problem</h3><p>One of the problems with the new Jetson Nano 2GB board is that a lot of the tutorials and install guides are outdated and lead to problems. So here&apos;s my take based on Jetpack 4.4.1 Jan 2021.</p><p>Install - simply</p>]]></description><link>http://technicals.two-drifters.co.uk/jetson-nano-basics/</link><guid isPermaLink="false">640f1a3a478d1d0ef02dafdd</guid><dc:creator><![CDATA[Brian Jones]]></dc:creator><pubDate>Fri, 15 Jan 2021 17:44:58 GMT</pubDate><media:content url="http://technicals.two-drifters.co.uk/content/images/2021/01/jetsonnano.jpg" medium="image"/><content:encoded><![CDATA[<img src="http://technicals.two-drifters.co.uk/content/images/2021/01/jetsonnano.jpg" alt="Jetson Nano Basics"><p></p><h3 id="basic-install-and-fix-update-problem">Basic Install and fix Update problem</h3><p>One of the problems with the new Jetson Nano 2GB board is that a lot of the tutorials and install guides are outdated and lead to problems. So here&apos;s my take based on Jetpack 4.4.1 Jan 2021.</p><p>Install - simply download the system image from the Nvidia site, use Balena Etcher to write an SD card on your main machine; boot the Nano and follow the normal Ubuntu install instructions.</p><p>Update with the normal</p><p><code>sudo apt-get update &amp;&amp; sudo apt-get install</code></p><p>DO NOT REBOOT despite being told one is necessary</p><p>Clear the old files</p><p><code>sudo apt-get autoremove</code></p><p>The update process breaks the lightdm greeter screen and if you reboot you will be presented with a blank screen. No problems, you can simply SSH into the Nano and perform the following, but, of course, it is simpler to do this whilst still logged into the Nano, before rebooting.</p><p>Add Nano editor if you don&apos;t like Vim</p><p><code>sudo apt-get install nano</code></p><p>Get the lightdm gtk greeter</p><p><code>sudo apt-get install lightdm-gtk-greeter</code></p><p>and tell lightdm to use this greeter:</p><p><code>sudo nano /etc/lighdm/lightdm.conf.d/50-nvidia.conf</code></p><p>add the following line to the end</p><p><code>greeter-session=lightdm-gtk-greeter</code></p><p>You should now get a login screen on reboot and continue as normal</p><p></p><h3 id="install-microsoft-vs-code">Install Microsoft VS Code</h3><p>Follow this instructions here: <a href="https://www.jetsonhacks.com/2020/11/28/visual-studio-code/?ref=idling-away-retirement">https://www.jetsonhacks.com/2020/11/28/visual-studio-code/</a></p><p>Basically clone the git repository and run </p><p><code>./installVSCode.sh</code></p><p>from within it. </p><p></p><h3 id="fix-opencv-code-completion-and-pylint-errors-in-vs-code">Fix OpenCV code completion and pylint errors in VS Code</h3><p>Big thanks to Paul McWhorter at TopTechBoy who has an excellent &#xA0;set of Jetson Nano, opencv and AI tutorials on Youtube. Beware, though, the earlier episodes were written for previous versions of Jetpack and are out of date - following the instructions on this website is better for Jetpack 4.4.1. BTW Paul, in turn, thanked David Westmorland for coming up with this fix</p><p>Firstly check the location of your python opencv install:</p><p><code>dpkg -L libopencv-python</code></p><p>It is probably at</p><p><code>/usr/lib/python3.6/dist-packages/cv2/python-3.6</code></p><p>Start VS Code and go to preferences with</p><p><code>ctrl-shift-p</code></p><p>Search for </p><p><code>Preferences: Open Settings (JSON)</code></p><p>If you haven&apos;t configured VS Code then this file is probably empty (save a couple of braces), add the following two lines</p><p><code>&quot;python.linting.pylintArgs&quot;: [&quot;--generate-members&quot;, &quot;--extension-pkg-whitelist=cv2&quot;], </code></p><p><code>&quot;python.autoComplete.extraPaths&quot;: [&quot;/usr/lib/python3.6/dist-packages/cv2/python-3.6&quot;]</code></p><p><strong>Note</strong>: The path in the second expression must be the same as the path as shown by that previous dpgk -L command.</p><p>Save that preferences file and exit VS Code. On restart opencv pylint errors should have gone and code completion/prompting should work for opencv</p><p></p><h3 id="install-matplotlib">Install matplotlib</h3><p>I wanted to play with matplotlib so simply installed via</p><p><code>sudo apt-get install python3-matplotlib</code></p><p></p><p></p><p></p><p></p><p></p><p></p>]]></content:encoded></item><item><title><![CDATA[Si5351 adventures]]></title><description><![CDATA[<p>A complicated little beast. 2 PLLs which can be integer (preferable, less jitter) or fractional multiples of the reference crystal oscillator. Max freq for a PLL is 900MHz with max crystal multipliers being in the range 15-90</p><p>After setting the PLL the output clocks are either integer or fractional divisors</p>]]></description><link>http://technicals.two-drifters.co.uk/si5351-adventures/</link><guid isPermaLink="false">640f1a3a478d1d0ef02dafdc</guid><dc:creator><![CDATA[Brian Jones]]></dc:creator><pubDate>Fri, 01 Mar 2019 19:47:10 GMT</pubDate><media:content url="http://technicals.two-drifters.co.uk/content/images/2021/02/2045-00_03ce3670-a303-4f0d-b801-b03b417b9cee_500x.jpg" medium="image"/><content:encoded><![CDATA[<img src="http://technicals.two-drifters.co.uk/content/images/2021/02/2045-00_03ce3670-a303-4f0d-b801-b03b417b9cee_500x.jpg" alt="Si5351 adventures"><p>A complicated little beast. 2 PLLs which can be integer (preferable, less jitter) or fractional multiples of the reference crystal oscillator. Max freq for a PLL is 900MHz with max crystal multipliers being in the range 15-90</p><p>After setting the PLL the output clocks are either integer or fractional divisors of the selected PLL. I believe the range is 4 to 900. If fractional then parameters need to be supplied in the format multiplier, numerator, denominator where numerator and denominator are limited to 20 bits (i.e. 1048575). Fortunately Python makes this pretty straightforward,</p><p>Here&apos;s some simple but crude code to calculate a spot frequency from a given PLL frequency:</p><!--kg-card-begin: markdown--><pre><code class="language-python">from fractions import Fraction

def calc_freq(pll_freq, freq):
    mult,rem = divmod(pll_freq, freq) 
    if mult &lt; 4 or mult &gt;900:
        raise Exception(&quot;Target frequency produced invalid divider for the given PLL frequency&quot;)
    frac=Fraction(rem/freq).limit_denominator(1048575)
    return int(mult), frac.numerator, frac.denominator

def set_freq(clock,pll,freq):
    m,n,d = calc_freq(pll.frequency,freq)
    if d == 1:
        clock.configure_integer(pll, m)
    else:
        clock.configure_fractional(pll,m,n,d )
        
        
 set_freq(si5351.clock_1,si5351.pll_a,9.0e6) #sets clock1 to 9.0MHz       
</code></pre>
<!--kg-card-end: markdown--><p>To divide down further there is an additional R-divide which can be applied to a specific clock, values are multiples of 2 from 1 to 128.</p>]]></content:encoded></item><item><title><![CDATA[Building a Home Automation Pi]]></title><description><![CDATA[<!--kg-card-begin: markdown--><p>So, I&apos;ve been playing around with the Raspberry Pi and home automation devices such as IKEA Tr&#xE5;dfri lights, a TP-Link HS100 switch, my Netatmo smart thermostat and a few ESP8366 devices including various varieties of Sonoff switches. Rather than blunder on I thought it would be</p>]]></description><link>http://technicals.two-drifters.co.uk/building-a-home-automation-pi/</link><guid isPermaLink="false">640f1a3a478d1d0ef02dafd7</guid><dc:creator><![CDATA[Brian Jones]]></dc:creator><pubDate>Tue, 07 Aug 2018 12:16:55 GMT</pubDate><media:content url="http://technicals.two-drifters.co.uk/content/images/2021/02/9fadf6ad91764378436b79176af2-1586047.jpg" medium="image"/><content:encoded><![CDATA[<!--kg-card-begin: markdown--><img src="http://technicals.two-drifters.co.uk/content/images/2021/02/9fadf6ad91764378436b79176af2-1586047.jpg" alt="Building a Home Automation Pi"><p>So, I&apos;ve been playing around with the Raspberry Pi and home automation devices such as IKEA Tr&#xE5;dfri lights, a TP-Link HS100 switch, my Netatmo smart thermostat and a few ESP8366 devices including various varieties of Sonoff switches. Rather than blunder on I thought it would be nice and take stock of what I&apos;ve done and install everything cleanly from scratch.</p>
<p>Most of the pearls of wisdom herein have been taken from this excellent guide: <a href="https://screenzone.eu/arduino-mqtt-sensor-grafana/?ref=idling-away-retirement">https://screenzone.eu/arduino-mqtt-sensor-grafana/</a></p>
<p>Here is the software I want installed just to control the devices:</p>
<ul>
<li>Node-RED</li>
<li>Mosquitto MQTT broker</li>
</ul>
<p>But one of my Sonoff&apos;s is a Sonoff POW which provides real time power measurements and I also want to hook up at least one of my ESP8266 to a DHT22 temperature/humidity sensor and measure temperature real time. So some method of graphing the data over time would be nice. Some more software will be required for this:</p>
<ul>
<li>InfluxDB</li>
<li>Grafana</li>
</ul>
<p>That&apos;s the shopping list for now so let&apos;s get started.</p>
<h2 id="raspian">Raspian</h2>
<p>The Pi will be running purely as a server so I don&apos;t need the graphics interface. But I do need wireless.</p>
<p>So, install Raspian Stretch Lite as normal, add a HDMI connection and keyboard, just for now and login. To add wireless we need to go edit the wpa-supplicant file:</p>
<pre><code>sudo nano /etc/wpa_supplicant/wpa_supplicant.conf
</code></pre>
<p>and add the following lines:<br>
<em>network={<br>
ssid=&quot;myWifiSsid&quot;<br>
psk=&quot;myWifiPassword&quot;<br>
}</em><br>
with the appropriate ssid and password. Note the password is in the clear - we can generate the hashed password but I didn&apos;t know of an easy way of copying it and pasting it into the editor - but once we have SSH running this is easy from another computer so edit raspi-config</p>
<pre><code>sudo raspi-config
</code></pre>
<p>to change the password, expand storage, set the Hostname, and make sure SSH is enabled. And since we are not using graphics we can drop the memory split to use the minimum 16MB for graphics.</p>
<p>Reboot, check wifi is working and remember the IP address. Now we can ssh into the Pi and generate a hashed wifi key</p>
<pre><code>wpa_passphrase &quot;myWifiSsid&quot; &quot;myWifiPassword&quot;
</code></pre>
<p>which will produce output like<br>
<em>network={<br>
ssid=&quot;MyWifiSsid&quot;<br>
#psk=&quot;MyWifiPassword&quot;<br>
psk=ab058a266ce8fe3b5cdc92a6ab6edd35ce13f88797614e21b8af5ba0e4afb784<br>
}</em><br>
so we can re-edit wpa_supplicant.conf and replace the original psk definition which was out wifi password in the clear with the new hashed version.</p>
<p>All that&apos;s left to do is ensure the Pi has all the latest packages:</p>
<pre><code>sudo apt-get update &amp;&amp; sudo-apt-get upgrade
</code></pre>
<h2 id="nodered">Node-RED</h2>
<p>Node-REDd comes pre-installed with Raspberry Pi full install images but not with the Lite versions. The documentation and installation of Node-RED is excellent - check <a href="https://nodered.org/docs/hardware/raspberrypi?ref=idling-away-retirement">https://nodered.org/docs/hardware/raspberrypi</a> and all we need is</p>
<pre><code>bash &lt;(curl -sL https://raw.githubusercontent.com/node-red/raspbian-deb-package/master/resources/update-nodejs-and-nodered)
</code></pre>
<p>Now add Node-Red to systemd so it auto-starts:</p>
<pre><code>sudo systemctl enable nodered.service
</code></pre>
<p>reboot and test. Use any browser on your home LAN and navigate to <em>Pi&apos;s ip address</em>:1880</p>
<h3 id="security">Security</h3>
<p>Before we can edit the <em>~/.node-red/setting.js</em> file we need to generate a hashed password. Install node-red-admin:</p>
<pre><code>sudo npm install -g  node-red-admin
</code></pre>
<p>Now we can generate the password with:</p>
<pre><code>node-red-admin hash-pw
</code></pre>
<p>Enter your desired admin password at the prompt and copy the resultant string.Now edit <em>settings.js</em> file and uncomment the <em>adminAuth</em> section and replace the password with the one you have just copied. Restart node-RED (or reboot) to activate the change.</p>
<h2 id="mqtt">MQTT</h2>
<p>There are a lot of websites explaining that MQTT is broken on Debian Stretch but it looks like the repositories now have a slightly back-level version of MQTT which has all the dependencies met so install is the simple:</p>
<pre><code>sudo apt-get install mosquitto mosquitto-clients
</code></pre>
<p>While we&apos;re installing let&apos;s install Python support for MQTT too, just in case - this is a simple python pip install but ... the Lite version of Raspian doesn&apos;t have python pip installed so firstly</p>
<pre><code>sudo apt-get install python-pip
sudo apt-get install python3-pip
</code></pre>
<p>then we can install paho:</p>
<pre><code>sudo pip install paho-mqtt
</code></pre>
<p>To test MQTT simply subscribe to a topic by:</p>
<pre><code>mosquitto_sub -h localhost -t &quot;test&quot;
</code></pre>
<p>Leave this running and start a different shell (I simply used SSH) and publish something to that <em>test</em> topic:</p>
<pre><code>mosquitto_pub -h localhost -t &quot;test&quot; -m &quot;test msg&quot;
</code></pre>
<p>Back on your subscribe shell the message payload <em>test msg</em> should appear and all is well.</p>
<h3 id="security">Security</h3>
<p>The <a href="https://screenzone.eu/arduino-mqtt-sensor-grafana/?ref=idling-away-retirement">Screenzone Guide</a> suggests it might be a good idea to add some security to MQTT in the form of requiring a Userid and Password to publish or subscribe to messages.</p>
<p>Firstly we create an authorisation file:</p>
<pre><code>sudo nano /etc/mosquitto/conf.d/auth.conf
</code></pre>
<p>and add the lines:<br>
<em>allow_anonymous false<br>
password_file /etc/mosquitto/passwd</em></p>
<p>Then create a user <em>homeauth</em> and supply a password</p>
<pre><code>sudo mosquitto_passwd -c /etc/mosquitto/passwd homeauth
</code></pre>
<p>Restart mosquitto (or reboot) for the changes to take effect:</p>
<pre><code>sudo systemctl restart mosquitto
</code></pre>
<p>Now let&apos;s try our tests again - the simple sub and pub tests above both fail to connect because of an authorisation error - so let&apos;s add authorisation:</p>
<pre><code>mosquitto_sub -h localhost -t &quot;test&quot; -u &quot;homeauth&quot; -P &quot;mysecret&quot;
</code></pre>
<p>and</p>
<pre><code>mosquitto_pub -h localhost -t &quot;test&quot; -m &quot;test msg&quot; -u &quot;homeauth&quot; -P &quot;mysecret&quot;
</code></pre>
<p>and confirm they work as previously.</p>
<h2 id="influxdb">InfluxDB</h2>
<p><a href="https://bentek.fr/influxdb-grafana-raspberry-pi/?ref=idling-away-retirement">https://bentek.fr/influxdb-grafana-raspberry-pi/</a></p>
<pre><code>curl -sL https://repos.influxdata.com/influxdb.key | sudo apt-key add -

echo &quot;deb https://repos.influxdata.com/debian stretch stable&quot; | sudo tee /etc/apt/sources.list.d/influxdb.list

sudo apt-get update

sudo apt-get install influxdb
</code></pre>
<h2 id="grafana">Grafana</h2>
<pre><code>curl https://bintray.com/user/downloadSubjectPublicKey?username=bintray | sudo apt-key add -

echo &quot;deb https://dl.bintray.com/fg2it/deb stretch main&quot; | sudo tee -a /etc/apt/sources.list.d/grafana.list

sudo apt-get update

sudo apt-get install grafana
</code></pre>
<pre><code>sudo nano /etc/grafana/grafana.ini
</code></pre>
<p><em>[server]</em><br>
<em># Protocol (http, https, socket)<br>
protocol = http</em><br>
<em># The ip address to bind to, empty will bind to all interfaces<br>
;httpaddr =</em><br>
<em># The http port  to use<br>
httpport = 3000</em></p>
<pre><code>sudo service grafana-server restart
</code></pre>
<pre><code>sudo systemctl enable grafana-server.service 
</code></pre>
<h2 id="configuringnodered">Configuring Node-Red</h2>
<p>Let&apos;s use a simple example from a Tasmota flashed Sonoff-POW</p>
<p>The MQTT message is:<br>
<code>&quot;{&quot;Time&quot;:&quot;2018-08-07T12:04:27&quot;, &quot;Total&quot;:31.918, &quot;Yesterday&quot;:0.229, &quot;Today&quot;:0.003, &quot;Period&quot;:0, &quot;Power&quot;:1, &quot;Factor&quot;:0.00, &quot;Voltage&quot;:253, &quot;Current&quot;:9.362}&quot;</code></p>
<p>First step is to use the influx CL to create a database<br>
&apos;create database power&apos;</p>
<p>Then a simple function is needed to convert this to a payload into the requisite influx payload format - we are probably only interested in power, current and voltage so simply converting the payload to JSON and taking the appropriate parts works:</p>
<pre><code>node.status({fill:&quot;blue&quot;,shape:&quot;dot&quot;,text: msg.payload});

tmp=JSON.parse(msg.payload);

msg.payload = {
	power: tmp.Power,
	voltage: tmp.Voltage,
	current: tmp.Current,
	sensor:&quot;Sonoff-POW1&quot;,
    };
    
return msg;
</code></pre>
<p>*The first line just output the incoming message payload as status under the function box in Node-Red for debugging<br>
*</p>
<p>Just 3 Node-Red nodes are needed an MQTT output node to pass the published message to the Function node and the output from that is simply passed to an Influxdb input and data is written to the database.</p>
<p>Using the influx CL<br>
<code>use power</code><br>
to select the appropriiate database</p>
<p><code>show measurements</code> should show the measurement name you specified in the Influxdb Node-Red node - in my case it is &quot;Sonoff&quot; so</p>
<p><code>select * from Sonoff</code><br>
shows</p>
<pre><code>time                current power sensor      voltage
----                ------- ----- ------      -------
1533633288497843297 0       0     Sonoff-POW1 254
1533633318505875412 0       0     Sonoff-POW1 254
1533633348715756458 0       0     Sonoff-POW1 255
1533633378731317810 0       0     Sonoff-POW1 254
1533633408891859566 0       0     Sonoff-POW1 254
1533633438910263696 0       0     Sonoff-POW1 254
1533633469053749624 0       0     Sonoff-POW1 254
1533633499139945397 0       0     Sonoff-POW1 254
1533633529244007464 0       0     Sonoff-POW1 254
1533633559250354927 9.44    1     Sonoff-POW1 254
1533633589398467843 9.44    2     Sonoff-POW1 255
1533633619458944619 0       0     Sonoff-POW1 255
1533633649730510602 9.474   1     Sonoff-POW1 256
</code></pre>
<p>*Yes, I didn&apos;t have a load on my Sonoff-POW at the time!&quot;</p>
<!--kg-card-end: markdown-->]]></content:encoded></item><item><title><![CDATA[VPN]]></title><description><![CDATA[<!--kg-card-begin: markdown--><p>The UK Digital Economy Act of 2017 brings into force the strong possibility that the UK government will introduce age verification to certain websites (i.e. porn). This, in itself, is worrying and something I strongly disagree with. Sadly the genie is out of the bottle with internet content. If</p>]]></description><link>http://technicals.two-drifters.co.uk/vpn/</link><guid isPermaLink="false">640f1a3a478d1d0ef02dafd5</guid><category><![CDATA[Security]]></category><dc:creator><![CDATA[Brian Jones]]></dc:creator><pubDate>Sat, 06 Jan 2018 18:20:37 GMT</pubDate><media:content url="https://images.unsplash.com/photo-1477244075012-5cc28286e465?ixlib=rb-0.3.5&amp;q=80&amp;fm=jpg&amp;crop=entropy&amp;cs=tinysrgb&amp;w=1080&amp;fit=max&amp;s=650281f5f6da101b6954a53645bf6501" medium="image"/><content:encoded><![CDATA[<!--kg-card-begin: markdown--><img src="https://images.unsplash.com/photo-1477244075012-5cc28286e465?ixlib=rb-0.3.5&amp;q=80&amp;fm=jpg&amp;crop=entropy&amp;cs=tinysrgb&amp;w=1080&amp;fit=max&amp;s=650281f5f6da101b6954a53645bf6501" alt="VPN"><p>The UK Digital Economy Act of 2017 brings into force the strong possibility that the UK government will introduce age verification to certain websites (i.e. porn). This, in itself, is worrying and something I strongly disagree with. Sadly the genie is out of the bottle with internet content. If working with 8-10 year old kids at Code Club has taught me anything it is the fact that they are much more computer savvy than we might expect; I foresee kids using TOR and the Dark Net as a simple way of circumventing such measures. Driving them underground makes as much sense as criminalising drugs, and so driving people who seek them into the hands of an underworld selling products of dubious prevenance.</p>
<p>But even more worrying is that the act grans Ofcom new powers that will allow them to collect or demand more information from broadband ISPs and mobile operators. Whilst my internet browsing is fairly innocous it is something which I would prefer the option as to what I share and with whom rather than acceeding to a government&apos;s carte blanche spying.</p>
<p>So, time for that VPN I&apos;ve been considering from some years. A quick survey of recommendations suggests that NordVPN is a good candidate for my needs. Even better it has an offer on currently with 3 years service for $99 which works at at a penny or two over &#xA3;2 a month. Even better it has a (not well advertised) 3 day free-trial; no sign-up required, no credit card, just an email address registration.</p>
<p>It comes with applications for Windos, Mac, Android etc but nothing for Linux. I won&apos;t detail the steps here because the NordVPN documentation does a god job but for Linux:</p>
<ol>
<li>Install OpenVPN</li>
<li>Download the NordVPN server configuration server files</li>
<li>Configure Network Manager to use one of the server configs</li>
</ol>
<p>Each step was remarkably simple and up and running in just a couple of minutes. Download speeds drop from 34Mbs to 30Mbs but I can live with that.</p>
<p>An even easier install is the browser plug-in so that geo-location can be switched with a click. Works well, I could listen to the Leonard Cohen tribute concert via Canadian CBC replay - this was rejected yesterday using both Opera&apos;s built-in VPN proxy and the Hola VPN plug-in for Chrome. Also tested was switching to the USA and, sure enough, BBC iPlayer content was no longer available.</p>
<p>Android is a simple app, installed and one click turns on or off the VPN. All is good.</p>
<!--kg-card-end: markdown-->]]></content:encoded></item><item><title><![CDATA[Microbit Game - Snowball Fight]]></title><description><![CDATA[You are going to throw a snowball at your classmates. How many times will YOU get SPLATTED!  This game uses the micro:bit radio component to throw snowballs at your friends.]]></description><link>http://technicals.two-drifters.co.uk/microbit-game-snowball-fight/</link><guid isPermaLink="false">640f1a3a478d1d0ef02dafd3</guid><category><![CDATA[Microbit]]></category><dc:creator><![CDATA[Brian Jones]]></dc:creator><pubDate>Fri, 22 Dec 2017 10:56:08 GMT</pubDate><media:content url="https://images.unsplash.com/photo-1481740953965-c1ee18cf226d?ixlib=rb-0.3.5&amp;q=80&amp;fm=jpg&amp;crop=entropy&amp;cs=tinysrgb&amp;w=1080&amp;fit=max&amp;s=7d1071181d1060adea345f00bf8a2459" medium="image"/><content:encoded><![CDATA[<!--kg-card-begin: markdown--><h1 id="snowballfiiiight">Snowball Fi-i-i-ight!</h1>
<img src="https://images.unsplash.com/photo-1481740953965-c1ee18cf226d?ixlib=rb-0.3.5&amp;q=80&amp;fm=jpg&amp;crop=entropy&amp;cs=tinysrgb&amp;w=1080&amp;fit=max&amp;s=7d1071181d1060adea345f00bf8a2459" alt="Microbit Game - Snowball Fight"><p>You are going to throw a snowball at your classmates. How many times will YOU get SPLATTED!</p>
<h2 id="step1anintroductiontoradioonthemicrobit">Step 1. An introduction to Radio on the micro:bit</h2>
<p>The micro:bit has a radio chip that can be used to send and receive messages. You can set each micro:bit to a &apos;radio group&apos; so that it will only send and receive messages within that group. This is very handy - imagine you and your friend are playing a game on two micro:bits sending messages to each other. If another pair of people are playing the same game then your micro:bits might receive their messages as well and this would spoil the game. But if each group of players is on a different radio group then they will only send and receive messages in their own group and so won&apos;t interfere with nearby players and so, all the games will work.</p>
<p>We are going to take advantage of that by setting every micro:bit to a different radio group and then when we want to send a message to someone else we will temporarily change our group so it matches the person we want to send to and then we can send them a message. That way we know only one person will receive our message.</p>
<h2 id="step2settingupthebasicvariables">Step 2. Setting up the basic variables</h2>
<p>Let&apos;s write some start code that sets up a different number for each player and also says how many people are playing - we&apos;ll need that later.<br>
<img src="http://technicals.two-drifters.co.uk/content/images/2017/12/start1.png" alt="Microbit Game - Snowball Fight" loading="lazy"></p>
<h4 id="stopdidyoujustcopythatcode">STOP - Did you just copy that code?</h4>
<p>Remember we said earlier that we are going to assign each micro:bit to a different radio group. Well, if we all just copied the code above, what has happened? - everyone has set their radio to group 0.</p>
<p>Everyone needs to have a different value for mynumber. So, agree who will be 0, 1, 2 ,3  etc. - the numbers should start with 0 and be in order with no gaps.</p>
<p>Also note the variable:<br>
<img src="http://technicals.two-drifters.co.uk/content/images/2017/12/lastplayer.png" alt="Microbit Game - Snowball Fight" loading="lazy"></p>
<ul>
<li>this holds the number of people in our snowball fight. I&apos;ve guessed there will be 7 or us here today (so the last player will be number 6 - remember we started at 0) If there isn&apos;t 7 players then change this. It should be the same value for everyone.</li>
</ul>
<p>We&apos;ll add a long 5 second pause to make sure we can all check we have the correct player number.<br>
<img src="http://technicals.two-drifters.co.uk/content/images/2017/12/start2.png" alt="Microbit Game - Snowball Fight" loading="lazy"><br>
If we are going to have a snowball fight - someone needs a snowball. We could give everyone a snowball but chaos would happen. So let&apos;s give just one player a snowball to start with so he can throw it to someone else.</p>
<p>We will give it to player 0 and we will also set a variable to say that he currently has the snowball. For everyone else we will tell the code that they don&apos;t have the snowball. We will do this using something called a Boolean variable <em>(named after a Victorian called George Boole who first thought of this sort of thing)</em>. A Boolean variable is just like any other variable but it doesn&apos;t hold a number or a string, it holds either the value True or the value False. So we will make a variable called ihavethesnowball and player 0 will set it to true and everyone else will set it to false.</p>
<p>We could write different code for player 0 to set ihavethesnowball to true. But it is easier if we all have the same code - so let&apos;s test the value of the mynumber variable and make the code to be different for player 0. Let&apos;s also give player 0 a great fat snowball on their screen, and since no-one else has a snowball we&apos;ll clear their screens.</p>
<p>Here&apos;s the complete on start code:<br>
<img src="http://technicals.two-drifters.co.uk/content/images/2017/12/start4.png" alt="Microbit Game - Snowball Fight" loading="lazy"></p>
<p>Remember your code may have a different value of mynumber and if there aren&apos;t 7 of us here today then lastplayer will be different too.</p>
<h2 id="step3throwingthesnowball">Step 3. Throwing the snowball</h2>
<p>We will shake the microbit to throw the snowball. But you can&apos;t throw the snowball if you don&apos;t have it, so we will make the on shake code do something only for the player who currently has the snowball - like this:</p>
<p><img src="http://technicals.two-drifters.co.uk/content/images/2017/12/onshake1.png" alt="Microbit Game - Snowball Fight" loading="lazy"></p>
<h4 id="notedoesthatiftestlookstrange">Note. Does that if test look strange?</h4>
<p><em>Normally when we do an if test we need to have an = or &gt; or similar sign in, like &quot;if score=10&quot; or &quot;if tries &gt; 2&quot;. Because the ihavethesnowball variable is a Boolean variable that can only have the values true or false we could say &quot;if ihavethesnowball = true&quot; here and this would work, in fact it would be exactly the same. But I think the simpler &quot;if ihavethesnowball&quot; makes slightly more sense. What do you think? If you prefer &quot;if ihavethesnowball = true&quot; then you can change the code.</em></p>
<p>We need to decide who to throw it to. For now we can select a random person. This is easy we can just use:<br>
<img src="http://technicals.two-drifters.co.uk/content/images/2017/12/random3.png" alt="Microbit Game - Snowball Fight" loading="lazy"></p>
<p>But there is a problem?</p>
<h4 id="stopcanyouseetheproblem">STOP - Can you see the problem?</h4>
<p>&#xA0;</p>
<p>That code can pick any player - including the player who is throwing the snowball. Now, not only is it stupid to throw the snowball and splat yourself but the micro:bit radio code doesn&apos;t allow messages to go to the player who sent them, so the message will just get lost and the snowball disappear into space.</p>
<p>We need to write some code to pick a random number but make sure it isn&apos;t our own number.</p>
<h4 id="stophowwouldyoudothis">STOP - how would you do this?</h4>
<p>&#xA0;</p>
<p>There are several way but the way we&apos;ll use is to pick a random number and it is our own player number we&apos;ll pick another one and keep doing so until we get a different player.</p>
<p>One way to write this in micro:bit code is to deliberately set the throwto variable to our own number (the very thing we don&apos;t want!) and then loop round picking random numbers until we get one which isn&apos;t our number. Like this:</p>
<p><img src="http://technicals.two-drifters.co.uk/content/images/2017/12/random4.png" alt="Microbit Game - Snowball Fight" loading="lazy"></p>
<p>So we have someone to throw the snowball to (the player number indicated by the variable throwto)</p>
<p>When we are ready to throw the snowball we will change our radio group to match the radio group of the person we are throwing to. Since they are the only other person in that radio group when we send a message only they will get it.</p>
<p>Once we&apos;ve thrown the snowball (that is, sent the message) we will change our radio group back to our own player number.</p>
<p>Also in the message we send we&apos;ll put our own player number, so they know who threw the snowball at them.</p>
<p>Of course, once we have thrown the snowball we don&apos;t have it any more! So we better remove the snowball off our screen and tell our code that we don&apos;t have it.</p>
<p>So the code is going to be like this</p>
<p><img src="http://technicals.two-drifters.co.uk/content/images/2017/12/onshake2.png" alt="Microbit Game - Snowball Fight" loading="lazy"></p>
<p>This is quite complicated so let&apos;s just check what that code does - if we don&apos;t have the snowball it does nothing. Easy! If we do have the snowball we pick another player and switch to their radio group so we can throw the snowball at them (i.e. send them a radio message). Then we set the radio group back to the one we assigned at start. This is important because it now means we are back in a radio group with just us in it, so we are ready for any snowballs being thrown our way in the future. And finally - we don&apos;t have the snowball anymore so we clear the screen and tell the code.</p>
<h2 id="step4gettinghit">Step 4. Getting hit!</h2>
<p>We know we&apos;ve just been hit by the snowball when we get a radio message. Remember the code is designed so we are the only one that will get a message. The message contains the number of the player who threw the snowball but we won&apos;t use that information just yet, we&apos;ll just react to being hit (that is receiving a radio message)</p>
<p>The code is really easy - when we receive a radio message we just plop a big snowball on the screen and tell our code we have the snowball (by setting the variable ihavethesnowball to true). Just to slow things down a little we&apos;ll also add a pause, otherwise the snowball will zip around the group a bit fast.</p>
<p>Here&apos;s the code:<br>
<img src="http://technicals.two-drifters.co.uk/content/images/2017/12/received1.png" alt="Microbit Game - Snowball Fight" loading="lazy"></p>
<h4 id="letshaveasnowballfiiight">Let&apos;s have a SNOWBALL FI-I-IGHT!</h4>
<p>
</p><p>
</p><p>
</p><h2 id="challenge1abettersplat">Challenge 1: A better SPLAT!</h2>
<p>Let&apos;s put some simple animation to look like the snowball is coming to get us. Here&apos;s my code - can you make something that looks better?</p>
<p><img src="http://technicals.two-drifters.co.uk/content/images/2017/12/received2.png" alt="Microbit Game - Snowball Fight" loading="lazy"></p>
<h2 id="challenge2countingthesplats">Challenge 2: Counting the splats!</h2>
<p>Let&apos;s count how many times each of us get hit and as soon as someone gets 6 hits end the game. We just need to count each time we received a snowball.</p>
<p>Let&apos;s do the count code first - when do we know we&apos;ve been splatted?</p>
<p>The answer is every time we receive a radio message - so in the on Radio Received code we can just add one to the count. First we need to set the count to 0 and the best place to do that will be On Start. We will also set a variable maxsplats to say how many splats a player can get before the game ends</p>
<h4 id="notesettingavariablelikemaxsplatsmakestidiercode">Note - setting a variable like maxsplats makes tidier code</h4>
<p><em>We could just test for the number 6 in the code, but if we wanted to make the game longer or shorter we would have to hunt around in the code and find this number and change it. By having a variable with an obvious name right at the top of our On Start code it makes it much easier to change things in the future</em></p>
<p><img src="http://technicals.two-drifters.co.uk/content/images/2017/12/splatct3.png" alt="Microbit Game - Snowball Fight" loading="lazy"><br>
and then just add 1 when we receive a snowball splat</p>
<p><img src="http://technicals.two-drifters.co.uk/content/images/2017/12/splatct2.png" alt="Microbit Game - Snowball Fight" loading="lazy"></p>
<p>So now all we need to is check when splats = maxsplats and end the game.</p>
<h4 id="stopcanyouthinkofaproblemaboutendingthegame">STOP - can you think of a problem about ending the game?</h4>
<p>&#xA0;</p>
<p>It is easy to end the game on our micro:bit, we can just check if we&apos;ve been splatted the maximum number of times and put out a message. Like this:</p>
<p><img src="http://technicals.two-drifters.co.uk/content/images/2017/12/end6.png" alt="Microbit Game - Snowball Fight" loading="lazy"></p>
<p>But how do we tell everyone else to stop?</p>
<p>The only way we can communicate with them is to send them another radio message. Let&apos;s send them an unusual number - like 99, to say the game has ended. We need to send this message to every other radio group. It&apos;s a bit easier than the random code. Can you think what the code is like?</p>
<p>This code will work:</p>
<p><img src="http://technicals.two-drifters.co.uk/content/images/2017/12/end7.png" alt="Microbit Game - Snowball Fight" loading="lazy"></p>
<p>We loop around for all the player numbers and for every number that is not our own we set the radio group to that number and send a 99. So every other player should receive a 99 radio message.</p>
<p>Now we have to write some code to handle that special radio message telling us the game has ended.</p>
<p>We can simple check when we receive a radio message whether it is 99 and so we have to end the game, if it isn&apos;t then it must be an incoming snowball and we get splatted as usual. So our radio receive code now starts like this:</p>
<p><img src="http://technicals.two-drifters.co.uk/content/images/2017/12/end8.png" alt="Microbit Game - Snowball Fight" loading="lazy"></p>
<p>And that&apos;s it!</p>
<p>Not quite - the player who last got a Snowball still has it, and so if he shakes then it will get thrown and the game will continue. This is a nasty code bug which, fortunately is easy to fix. All we need do is tell the player when he gets splatted the maximum number of times that he no longer has the snowball - the game is over and the snowball has melted!</p>
<p>We&apos;ll add that into the end of the radio receive code which will now look like:</p>
<p><img src="http://technicals.two-drifters.co.uk/content/images/2017/12/end9.png" alt="Microbit Game - Snowball Fight" loading="lazy"></p>
<p>Now that really is it?</p>
<h4 id="stopcanyoustillseeapotentialbug">STOP - can you still see a potential bug?</h4>
<p>&#xA0;</p>
<p>What would happen if there were more than 99 players in this game? The snowball could never get thrown from player 99 - if it did then the receiver would get a radio message with the thrower&apos;s number 99 and the code would tell that player it is the end of the game.</p>
<p>In reality we are not likely to play this game with 99 players or more so our code is probably OK  but we need to bear this in mind as a possible problem. Often in computer programs bugs occur because the person who wrote the code though &quot;Oh, this could never happen, so I won&apos;t bother writing a lot of code just in case it does&quot;.</p>
<p>I think we are safe in assuming our Code Club won&apos;t get 99 members so -</p>
<h4 id="letsgoandsnowballfiiight">Let&apos;s go and Snowball Fi-i-i-ght!</h4>
<hr>
<h4 id="completecode">Complete Code</h4>
<p><img src="http://technicals.two-drifters.co.uk/content/images/2017/12/complete1.png" alt="Microbit Game - Snowball Fight" loading="lazy"><br>
<img src="http://technicals.two-drifters.co.uk/content/images/2017/12/complete2.png" alt="Microbit Game - Snowball Fight" loading="lazy"></p>
<h4 id="downloadthecodehere">Download the code here:</h4>
<p><a href="https://makecode.microbit.org/_ccjDyAAtH4kR?ref=idling-away-retirement">https://makecode.microbit.org/_ccjDyAAtH4kR</a></p>
<h4 id="expansionteachersnote">Expansion (Teacher&apos;s note)</h4>
<p>A really nice expansion for this would be to change the randow throw to allow the player with the snowball to point the micro:bit at the person they want to splat! If the players were in a circle and of known compass orientation then the micro:bit could use the compass bearing to know in which direction it was pointing before being shaken and so calculate which player gets splatted!</p>
<!--kg-card-end: markdown-->]]></content:encoded></item><item><title><![CDATA[Microbit Game - Fill It Up]]></title><description><![CDATA[Build a game where you see who is the fastest at 'rolling' an LED round the screen, lighting everywhere it touches.]]></description><link>http://technicals.two-drifters.co.uk/microbit-game-fill-it-up/</link><guid isPermaLink="false">640f1a3a478d1d0ef02dafd2</guid><category><![CDATA[Microbit]]></category><dc:creator><![CDATA[Brian Jones]]></dc:creator><pubDate>Fri, 22 Dec 2017 10:43:06 GMT</pubDate><media:content url="https://images.unsplash.com/photo-1510576241014-9b14488b624a?ixlib=rb-0.3.5&amp;q=80&amp;fm=jpg&amp;crop=entropy&amp;cs=tinysrgb&amp;w=1080&amp;fit=max&amp;s=819ad3aff3851a4086c932d4467e428d" medium="image"/><content:encoded><![CDATA[<!--kg-card-begin: markdown--><h1 id="fillitup">Fill It Up</h1>
<img src="https://images.unsplash.com/photo-1510576241014-9b14488b624a?ixlib=rb-0.3.5&amp;q=80&amp;fm=jpg&amp;crop=entropy&amp;cs=tinysrgb&amp;w=1080&amp;fit=max&amp;s=819ad3aff3851a4086c932d4467e428d" alt="Microbit Game - Fill It Up"><p>You are going to make a game where the object is to light all the LEDs on the Micro:bit in the fastest possible time. One LED will flash and you will tilt the Micro:bit to roll the flashing LED around the screen. Every time it rolls to a new LED the new LED will light it up.</p>
<p>All you have to do is light up all 25 LEDs as fast as possible. Easy?</p>
<h2 id="step1rollinganledaroundthescreen">Step1: Rolling an LED around the screen</h2>
<p>We&apos;re going to use the LED screen coordinate system to control the movement of the LED. The screen has two coordinates: X, running from left to right and Y running from top to bottom. Each starts at 0 so the top left hand corner is X = 0 and Y=0 and the bottom right hand corner is X=4 and Y=4</p>
<p>Let&apos;s set an LED in the middle of the screen. This is where X=2 and Y=2. Since we will be changing these values we need to create 2 variables, let&apos;s call them &apos;cur_x&apos; and &apos;cur_y&apos;.<br>
<em>(&apos;cur&apos; is short for &apos;current&apos; and is often used in variable names to indicate that this is the current value of the variable but it may change)</em><br>
<img src="http://technicals.two-drifters.co.uk/content/images/2017/12/setcurs.png" alt="Microbit Game - Fill It Up" loading="lazy"></p>
<p>That doesn&apos;t do much so let&apos;s light up the LED at that point<br>
<img src="http://technicals.two-drifters.co.uk/content/images/2017/12/plotxy.png" alt="Microbit Game - Fill It Up" loading="lazy"></p>
<p>We can use the accelerometer function of the Micro:bit to detect when the Micro:bit is being tilted. To start with we will just detect movement along the X-axis and light up the next LED when we detect a tilt</p>
<p>We&apos;ll need to do this in a Forever loop. Every time the Micro:bit is tilted to the right the X acceleration will become greater than 0 and so in that case we will increase the value of cur_x so that the next LED to the right lights up. Similarly if the Micro:bit is tilted to the left we need to decrease cur_x so the LED to the left is lit up.<br>
<img src="http://technicals.two-drifters.co.uk/content/images/2017/12/tiltx1.png" alt="Microbit Game - Fill It Up" loading="lazy"></p>
<p>Try it. It&apos;s very sensitive and hard to get just the next LED lit up. Slow it down by adding a pause statement (of say 100ms) after the plot statement in the forever loop</p>
<p>We can do the same thing for moving LEDs up and down, so let&apos;s add similar code for the Y axis - up and down movement<br>
<img src="http://technicals.two-drifters.co.uk/content/images/2017/12/tiltxandy1.png" alt="Microbit Game - Fill It Up" loading="lazy"></p>
<h2 id="step2fixingthebug">Step2:Fixing the bug!</h2>
<p>Can you roll the LED round the screen. I suspect you are probably having difficulty and sometimes the LED just doesn&apos;t seem to be moving.</p>
<h4 id="stopcanyouthinkwhy">STOP - Can you think why?</h4>
<p>&#xA0;</p>
<p>Remember what we said earlier that the screen goes from X=0, Y=0 in the top left corner to X=4, Y=4 at the bottom right corner.</p>
<p>Got it yet? Whenever we detect any acceleration we increase or decrease the value of cur_x or cur_y. So cur_x or cur_y may have values bigger than 4 or less than 0. When we use a plot statement with values outside the screen such as &quot;plot x -2 y 7&quot; then nothing gets plotted. So we only see LEDs being plotted when cur_x and cur_y have values between 0 and 4 inclusive.</p>
<p>To fix this we need to put some test into our code so that if cur_x or cur_y are already 0 or 4 we don&apos;t change them and make them go out of range.</p>
<p>Here&apos;s the code to stop cur_x being &gt;4<br>
<img src="http://technicals.two-drifters.co.uk/content/images/2017/12/limitcurx.png" alt="Microbit Game - Fill It Up" loading="lazy"></p>
<p>Can you write the code to make sure cur_x and cur_y are always within the range 0 to 4?<br>
<img src="http://technicals.two-drifters.co.uk/content/images/2017/12/xylimited.png" alt="Microbit Game - Fill It Up" loading="lazy"></p>
<p>Well, that might be working, it&apos;s hard to tell because we can&apos;t see where we are. Let&apos;s add an unplot so that we can see that we are just moving around the LEDs correctly.<br>
<img src="http://technicals.two-drifters.co.uk/content/images/2017/12/unplot.png" alt="Microbit Game - Fill It Up" loading="lazy"></p>
<p>That&apos;s great - we can move the LED around, just tilting the Micro:bit and it always has a LED lit. However, before we added that unplot we almost had a complete game, the square of LEDs was filling up nicely.</p>
<p>What&apos;s the difference? Well, now the last thing in our Forever loop is an unplot which turns the LED off,  so when we move to a new position the previous LED is turned off. Previously our loop ended with a plot and so the LEDs stayed on. So having the plot at the end of the loop is good. What do you think will happen if we do the unplot first and then the plot?</p>
<p>Let&apos;s try it, adding a delay<br>
<img src="http://technicals.two-drifters.co.uk/content/images/2017/12/unplotplot.png" alt="Microbit Game - Fill It Up" loading="lazy"></p>
<p>Wow - that works! The screen fills up nicely and the current position of the LED we tilt is shown by flashing - perfect. We have a game.</p>
<p>Almost!</p>
<h2 id="step3checkingforgameend">Step 3: Checking for Game End</h2>
<p>How do you think we know when the game ends?</p>
<p>That&apos;s right, all 25 LEDs are lit up. So we need a variable that counts every time we light up an LED and when it reaches 25 we know we have finished.</p>
<p>What happens when we move back over an LED that is already lit? We definitely don&apos;t want to count it again. So we need to make sure we only count LEDs that are unlit before we tilt onto them for the first time whicxh is what makes them become lit</p>
<p>So first of all we need a variable, and it is going to start off with the value 0 so no LEDs are lit. We can put this in our On Start code<br>
<img src="http://technicals.two-drifters.co.uk/content/images/2017/12/OnStartcount.png" alt="Microbit Game - Fill It Up" loading="lazy"><br>
Now after we have just moved (i.e. all those &quot;if acceleration&quot; statements) we need to check if the point we have moved to is unlit and if it is we can add one to our count<br>
<img src="http://technicals.two-drifters.co.uk/content/images/2017/12/Checkpoint.png" alt="Microbit Game - Fill It Up" loading="lazy"></p>
<p>OK so now we know how many LEDs are lit up and we know we want the game to end when 25 LEDs are lit. How are we going to use this? Also can we make the game start again when we&apos;ve finished?</p>
<p>Instead of it starting the game automatically we&apos;ll make it only start when button A is pressed. And instead of a Forever loop we&apos;ll code a While loop that continues for as long as there are LEDs still needing lighting - that is as long as LEDScount is less than 25.</p>
<p><img src="http://technicals.two-drifters.co.uk/content/images/2017/12/OnAFinish.png" alt="Microbit Game - Fill It Up" loading="lazy"></p>
<p>That works. As some as the screen fills up we put put the message &quot;Finished&quot;</p>
<h2 id="step4addingatimer">Step 4: Adding a timer</h2>
<p>Great, now if we could just replace that finished message with a message that says how long it took to fill the screen we would have a good game.</p>
<p>The Micro:bit has a clock (runninngtime) that says how long it has been running (in milliseconds). So we can tell how long we have taken to complete the game by looking at the time when we start the game and the time when we end the game and subtracting.</p>
<p>So at the beginning of the On Button A block before the While loop let&apos;s add another variable to record the starttime.</p>
<p><img src="http://technicals.two-drifters.co.uk/content/images/2017/12/initstart.png" alt="Microbit Game - Fill It Up" loading="lazy"></p>
<p>And at the end of the game we can work out the time taken in milliseconds. If we then divide this by 1000 we get seconds which is better to tell the user. We also need to report this</p>
<p><img src="http://technicals.two-drifters.co.uk/content/images/2017/12/score.png" alt="Microbit Game - Fill It Up" loading="lazy"></p>
<p>That&apos;s it</p>
<p>Almost!</p>
<p>If we press button A we get a new game but the screen is full up so we can&apos;t play. Simple to fix, we just need to a a Clear Screen instruction everytime we start the game.</p>
<p><img src="http://technicals.two-drifters.co.uk/content/images/2017/12/start.png" alt="Microbit Game - Fill It Up" loading="lazy"></p>
<h2 id="step5makingiteasier">Step 5: Making it easier</h2>
<p>The game is very difficult to control. That is because whenever we notice that the Micro:bit&apos;s acceleration is not 0 we move the LED. This makes the game very &apos;twitchy&apos; - it would be a lot less sensitive if we only moved the LED if the accereration was greater than a value bigger than 0 - the bigger the value we choose, the less sensitive the game becomes. Let&apos;s try a value of 50 and so we want to replace all of the code that says <code>if acceleration(mg)x &gt; 0</code> with <code>if acceleration(mg)x &gt; 50</code> and the same for <code>acceleration(mg)y</code>. We also want to replace <code>if acceleration(mg)x &lt; 0</code> with <code>if acceleration(mg)x &lt; -50</code> and again for <code>acceleration(mg)y</code>.</p>
<p>We can easily make these changes, but what happens if we find it is still too sensitive and we want to try a sensitivity of 100 rather than 50? We have to changes the code in lots of places. Whenever we change code in several places there is always a chance we will miss one and so introduce a bug into our code. How can we avoid this?</p>
<p>It is good coding practice to use a variable such as <code>max_accel</code> which we set once and once only in our On Start code and then do tests such as <code>if acceleration(mg)x &gt; max_accel</code>. Now if we want to make a change we just need to change the single line of code where we give <code>max_accel</code> a value and everywhere in the code will use the new value. No chance of missing anywhere and introducing a bug.</p>
<p>So we will set <code>max_accel</code> in oue On Start block and also to allow for the less than tests we will define another variable min_accel as -1*max_accel (i.e. if we set max_accel to 50 then min_accel will be -50, just what we need for the <code>if acceleration(mg)x &lt; ...</code> tests.</p>
<p>Another way of tweaking the speed of the code is to adjust the delay every time we plot and unplot the flashing LED. Currently we have this as a fixed 100 milli-second delay but we might want to explore changing this to make the game easier or harder so let&apos;s also replace those <code>pause(ms) 100</code> lines to test a variable called slowness.</p>
<p>Here&apos;s the On Start code:</p>
<p><img src="http://technicals.two-drifters.co.uk/content/images/2017/12/OnStart.png" alt="Microbit Game - Fill It Up" loading="lazy"></p>
<hr>
<h4 id="completecode">Complete Code</h4>
<p><img src="http://technicals.two-drifters.co.uk/content/images/2017/12/completecode.png" alt="Microbit Game - Fill It Up" loading="lazy"></p>
<h4 id="downloadthecodehere">Download the code here:</h4>
<p><a href="https://makecode.microbit.org/_agRexb6g35sf?ref=idling-away-retirement">https://makecode.microbit.org/_agRexb6g35sf</a></p>
<!--kg-card-end: markdown-->]]></content:encoded></item><item><title><![CDATA[Post-installation tasks]]></title><description><![CDATA[<!--kg-card-begin: markdown--><p>Installing Ghost is <strong>so easy</strong> using ghost-cli and the installer does everything. Well, almost everything. Three things left for me to do on my Technicals blog:</p>
<ol>
<li>Set up mail to allow resetting forgotten passwords</li>
<li>Adding Disqus commenting</li>
<li>Adding Prism code highlighting</li>
</ol>
<h3 id="1settingupmail">1. Setting up mail</h3>
<p>The Ghost documentation covers this</p>]]></description><link>http://technicals.two-drifters.co.uk/post-installation-tasks/</link><guid isPermaLink="false">640f1a3a478d1d0ef02dafd1</guid><category><![CDATA[Ghost]]></category><dc:creator><![CDATA[Brian Jones]]></dc:creator><pubDate>Thu, 21 Dec 2017 18:19:41 GMT</pubDate><media:content url="https://images.unsplash.com/photo-1502911679107-2b54f5c0292c?ixlib=rb-0.3.5&amp;q=80&amp;fm=jpg&amp;crop=entropy&amp;cs=tinysrgb&amp;w=1080&amp;fit=max&amp;s=93d573da5d3d5b4cf6e819fd1d4f29d4" medium="image"/><content:encoded><![CDATA[<!--kg-card-begin: markdown--><img src="https://images.unsplash.com/photo-1502911679107-2b54f5c0292c?ixlib=rb-0.3.5&amp;q=80&amp;fm=jpg&amp;crop=entropy&amp;cs=tinysrgb&amp;w=1080&amp;fit=max&amp;s=93d573da5d3d5b4cf6e819fd1d4f29d4" alt="Post-installation tasks"><p>Installing Ghost is <strong>so easy</strong> using ghost-cli and the installer does everything. Well, almost everything. Three things left for me to do on my Technicals blog:</p>
<ol>
<li>Set up mail to allow resetting forgotten passwords</li>
<li>Adding Disqus commenting</li>
<li>Adding Prism code highlighting</li>
</ol>
<h3 id="1settingupmail">1. Setting up mail</h3>
<p>The Ghost documentation covers this here - <a href="https://docs.ghost.org/v1.0.0/docs/mail-config?ref=idling-away-retirement">https://docs.ghost.org/v1.0.0/docs/mail-config</a></p>
<p>Basically if you have a mailgun account then you simply edit the <strong>config.production.json</strong> <em>(or <strong>config.development.json</strong>, as appropriate)</em> and replace the &quot;mail section&quot; with something like the following</p>
<pre><code>&quot;mail&quot;: {
    &quot;from&quot;: &quot;&apos;Test Ghost Blog&apos; &lt;test@two-drifters.co.uk&gt;&quot;,
    &quot;transport&quot;: &quot;SMTP&quot;,
    &quot;options&quot;: {
        &quot;service&quot;: &quot;Mailgun&quot;,
        &quot;auth&quot;: {
            &quot;user&quot;: &quot;postmaster@mg.two-drifters.co.uk&quot;,
            &quot;pass&quot;: &quot;********************************&quot;
        }
    }
},
</code></pre>
<p>Restart Ghost and head on over to the Labs section of the Ghost admin page to send a test email.</p>
<p>One minor annoyance is that I get such emails flagged with an &apos;unencrypted&apos; icon within Gmail, despite mailgun using TLS security. This seems to be some interaction with GoDaddy&apos;s secureserver.net - I have an open problem with GoDaddy and will report back if and when resolved. It&apos;s more an annoyance than problem.</p>
<h3 id="2addingdisquscommenting">2. Adding Disqus commenting</h3>
<p>If you use the default Casper theme this is already coded but commented out. Navigate to the <strong>content/themes</strong> folder and select your current theme. Then edit <strong>post.hbs</strong> and scroll down to the Disqus section and uncomment it -(the easiest way is simple to move the <code>--}}</code> line from the end of the section to the line before the <code>&lt;section class=&quot;post-full-comments&quot;&gt;</code> line - so just the explanatory documentation is commented out.</p>
<p>There is one further change - locate the line <code>s.src = &apos;https://test-apkdzgmqhj.disqus.com/embed.js&apos;;</code> and replace the test URL identifier with the Disqus identifier for your blog.</p>
<h3 id="3addingprismcodehighlighting">3. Adding Prism code highlighting</h3>
<p>This is only necessary if you want to include code snippets in your blog. This Technicals blog will almost certainly do so. So, head on over to Prism <a href="http://prismjs.com/download.html?ref=idling-away-retirement">http://prismjs.com/download.html</a>, select the theme, languages and extensions you want to enable and download both the javascript and css files.</p>
<p>Now, navigate to your themes <strong>content/themes/</strong> theme <strong>/assets</strong> folder and copy the downloaded javascript file into the <strong>js</strong> folder. Similarly copy the css file into the <strong>css</strong> folder.</p>
<p>To activate Prism you need to edit your theme&apos;s <strong>default.hbs</strong> file and add the following two lines to the appropriate section:</p>
<pre><code class="language-JavaScript">{{! Styles&apos;n&apos;Scripts }}
&lt;link rel=&quot;stylesheet&quot; type=&quot;text/css&quot; href=&quot;{{asset &quot;css/prism.css&quot;}}&quot;/&gt;
</code></pre>
<pre><code class="language-JavaScript">{{!-- jQuery + Fitvids, which makes all video embeds responsive --}}
&lt;script type=&quot;text/javascript&quot; src=&quot;{{asset &quot;js/prism.js&quot;}}&quot;&gt;&lt;/script&gt;
</code></pre>
<p>Yeah, that seems to work!</p>
<!--kg-card-end: markdown-->]]></content:encoded></item><item><title><![CDATA[Installing Ghost 1.x on a Raspberry Pi - A Production Blog]]></title><description><![CDATA[<!--kg-card-begin: markdown--><p>Now we have a development blog let&apos;s turn our thoughts to production. A production blog needs to restart automatically at reboot or in the event of a failure and really ought use SSL.</p>
<p>Colour me impressed - ghost-cli does both of these things out of the box without</p>]]></description><link>http://technicals.two-drifters.co.uk/installing-ghost-1-x-on-a-raspberry-pi-a-production-blog/</link><guid isPermaLink="false">640f1a3a478d1d0ef02dafd0</guid><category><![CDATA[RaspberryPi]]></category><category><![CDATA[Ghost]]></category><dc:creator><![CDATA[Brian Jones]]></dc:creator><pubDate>Thu, 21 Dec 2017 12:36:40 GMT</pubDate><media:content url="https://images.unsplash.com/photo-1476283721796-dd935b062838?ixlib=rb-0.3.5&amp;q=80&amp;fm=jpg&amp;crop=entropy&amp;cs=tinysrgb&amp;w=1080&amp;fit=max&amp;s=08aae4c9d0f72443f904040dededace6" medium="image"/><content:encoded><![CDATA[<!--kg-card-begin: markdown--><img src="https://images.unsplash.com/photo-1476283721796-dd935b062838?ixlib=rb-0.3.5&amp;q=80&amp;fm=jpg&amp;crop=entropy&amp;cs=tinysrgb&amp;w=1080&amp;fit=max&amp;s=08aae4c9d0f72443f904040dededace6" alt="Installing Ghost 1.x on a Raspberry Pi - A Production Blog"><p>Now we have a development blog let&apos;s turn our thoughts to production. A production blog needs to restart automatically at reboot or in the event of a failure and really ought use SSL.</p>
<p>Colour me impressed - ghost-cli does both of these things out of the box without any further work from the installer. It configures systemd to provide automatic failure restarting and it acquires certificates from letsencrypt.org and automatically configures nginx to use these to provide SSL security. This means that post-install configuration of Ghost is now much simpler than the previous 0.x versions.</p>
<p>One decision needs to be made before installation. Do you want ot run with an sqlite3 database or do you need the robustness that mysql offers. Personally I think that a Raspberry Pi is best suited to sqlite3 but if you prefer mysql you will need to install it prior to installing Ghost. I didn&apos;t so don&apos;t have install instructions here.</p>
<p>Additionally, since Ghost is going to set up SSL keys via Letsencrypt then you must have all of the DNS entries set up correctly in advance so that Letsencrypt can find DNS entries for your blog.</p>
<p>To create a production system then follow the steps in the previous post detailing creating a Test, Development Blog except for the final <code>ghost install local</code> step. Of course, if you&apos;ve actually created such a blog on your server already then you don&apos;t need to re-install the software again, just start at the point where you need to create a var/www folder for your new blog.</p>
<p>Once you have the folder, with the revised owner and have navigated to it then you can simply install Ghost with</p>
<pre><code>ghost install --db sqlite3
</code></pre>
<p><em>if you decided to use mysql then you just need <code>ghost install</code></em><br>
<strong>And that&apos;s it</strong></p>
<p>The installation process will warn that you are not running Ubuntu 16.04 but you can simply ignore that warning. It will then install Ghost which takes quite a while and ask do you want nginx, systemd and SSL configured before finally asking whether you want Ghost started. Be aware that generating the keys for SSL <strong>takes a long time</strong>, over 30 minutes on my un-overclocked Raspberry Pi 3. Also be aware that the response from Ghost starting occurs several seconds before Ghost has successfully started - so should you received a <em>502 Bad Gateway</em> error it may just be that we were a little eager in trying out your new system. Wait a few seconds before retrying.</p>
<!--kg-card-end: markdown-->]]></content:encoded></item><item><title><![CDATA[Installing Ghost 1.x on a Raspberry Pi - A Test, Development Blog]]></title><description><![CDATA[<!--kg-card-begin: markdown--><p>This summer Ghost underwent major changes when it moved up to version 1.0. One of these changes was a new command line interface which also installed Ghost itself.</p>
<p>The font of Ghost installation knowledge on a Raspberry Pi is <a href="https://ghostpi.pro/?ref=idling-away-retirement">https://ghostpi.pro</a> who had a post dated August where</p>]]></description><link>http://technicals.two-drifters.co.uk/installing-ghost-1-x-on-a-raspberry-pi/</link><guid isPermaLink="false">640f1a3a478d1d0ef02dafcd</guid><category><![CDATA[RaspberryPi]]></category><category><![CDATA[Ghost]]></category><dc:creator><![CDATA[Brian Jones]]></dc:creator><pubDate>Wed, 13 Dec 2017 17:52:48 GMT</pubDate><media:content url="https://images.unsplash.com/photo-1509558741973-0cd2f6a12a4f?ixlib=rb-0.3.5&amp;q=80&amp;fm=jpg&amp;crop=entropy&amp;cs=tinysrgb&amp;w=1080&amp;fit=max&amp;s=485b36b29181a277d27c5c6e96ecd4f3" medium="image"/><content:encoded><![CDATA[<!--kg-card-begin: markdown--><img src="https://images.unsplash.com/photo-1509558741973-0cd2f6a12a4f?ixlib=rb-0.3.5&amp;q=80&amp;fm=jpg&amp;crop=entropy&amp;cs=tinysrgb&amp;w=1080&amp;fit=max&amp;s=485b36b29181a277d27c5c6e96ecd4f3" alt="Installing Ghost 1.x on a Raspberry Pi - A Test, Development Blog"><p>This summer Ghost underwent major changes when it moved up to version 1.0. One of these changes was a new command line interface which also installed Ghost itself.</p>
<p>The font of Ghost installation knowledge on a Raspberry Pi is <a href="https://ghostpi.pro/?ref=idling-away-retirement">https://ghostpi.pro</a> who had a post dated August where it was suggested that Ghost 1.0 no longer installs on a Raspberry Pi running Raspbian but needs Ubuntu 16.04. I thought I&apos;d explore the problem and see where the errors lay with little hope that I&apos;d be able to fix them - but 4 months is a long time in software and the latest Ghost 1.19.0 (at least) installs on a Raspberry Pi without problem. In fact, the ghost-cli installer makes the whole process even easier than previously.</p>
<p>Let&apos;s do an install from a fresh raspian-stretch-lite image (in my case it just happened to be the image dated 29-11-2017</p>
<p>After installing the image and whilst still on the install computer as opposed to the Raspberry Pi I added an empty file <strong>ssh</strong> to the boot partition to allow for SSH to the Raspberry Pi immediately - so I didn&apos;t need to hook up a monitor and keyboard to the Raspberry Pi. I also edited the file <strong>/etc/dhcpcd.conf</strong> to give the Raspberry Pi a static IP address. Much easier to find it than using dhcp.</p>
<p>Then SSH to the Raspberry Pi, login and make sure all the software is up-to-date</p>
<pre><code>sudo apt-get update &amp;&amp; sudo apt-get upgrade
</code></pre>
<p>and change the default password, reduce the amount of graphics memory and expand the file system using raspi-config</p>
<pre><code>sudo raspi-config
</code></pre>
<p>which needs a reboot to take effect.<br>
&#xA0;</p>
<p>We&apos;ll use Sqlite3 rather then MySQL so need to install it:</p>
<pre><code>sudo apt-get install sqlite3
</code></pre>
<p>&#xA0;</p>
<p>Next, install nginx</p>
<pre><code>sudo apt-get install nginx
</code></pre>
<p>nginx needs a tweak for ghost.<br>
Edit <strong>/etc/nginx/nginx.conf</strong> and uncomment<br>
<code>server_names_has_bucket_size 64;</code></p>
<p>&#xA0;</p>
<p>Now install node.js - 6.9.5 is the recommended version for Ghost.</p>
<pre><code>wget https://nodejs.org/dist/v6.9.5/node-v6.9.5-linux-armv7l.tar.gz
sudo mv node-v6.9.5-linux-armv7l.tar.gz /opt
cd /opt
sudo tar -xzf node-v6.9.5-linux-armv7l.tar.gz
sudo mv node-v6.9.5-linux-armv7l nodejs
sudo rm node-v6.9.5-linux-armv7l.tar.gz
sudo ln -s /opt/nodejs/bin/node /usr/bin/node 
sudo ln -s /opt/nodejs/bin/npm /usr/bin/npm
</code></pre>
<p>&#xA0;</p>
<p>Once node.js is installed we can install ghost-cli</p>
<pre><code>sudo npm i -g ghost-cli@latest
sudo ln -s /opt/nodejs/bin/ghost /usr/bin/ghost
</code></pre>
<p>&#xA0;</p>
<p>We probably don&apos;t need yarn but the node.js install suggested it might be useful so why not install it now</p>
<pre><code>curl -o- -L https://yarnpkg.com/install.sh | bash
</code></pre>
<p>&#xA0;</p>
<p>We&apos;re now ready to install ghost itself, so let&apos;s make the directory for the blog and ensure we have all the permissions we need.</p>
<pre><code>sudo mkdir /var/www/ghost
sudo chown -R pi:pi /var/www/ghost
</code></pre>
<p>&#xA0;</p>
<p>and install ghost in to it - <strong>NOTE</strong> this takes quite a while, be patient!</p>
<pre><code>cd /var/www/ghost
ghost install local
</code></pre>
<p>And that&apos;s it. If you want to access the test Ghost from a different computer on your LAN you need to edit <strong>config.development.json</strong>. Change the first &quot;url&quot; line from localhost to the actual IP-Address of your Raspberry Pi Ghost server and similarly change the &quot;host&quot; line a couple of lines lower to also refer to thesame IP-Address rather than 127.0.0.1</p>
<p>Restart Ghost with</p>
<pre><code>ghost restart
</code></pre>
<p>and you should be able to access yor blog from within your LAN on IP-Address:Port</p>
<!--kg-card-end: markdown-->]]></content:encoded></item><item><title><![CDATA[Raspberry Pi and USB GPS]]></title><description><![CDATA[<!--kg-card-begin: markdown--><p>Just a couple of notes because this was trickier than expected, especially since all my experiences with Arduinos and GPSs have been fairly straightforward.</p>
<p>I bought a cheap USB GPS which turns out to be a Neo-7 - <code>dmesg</code> giving</p>
<pre><code>[   28.770396] usb 1-1.5: New USB device found, idVendor=</code></pre>]]></description><link>http://technicals.two-drifters.co.uk/raspberry-pi-and-usb-gps/</link><guid isPermaLink="false">640f1a3a478d1d0ef02dafcb</guid><category><![CDATA[RaspberryPi]]></category><dc:creator><![CDATA[Brian Jones]]></dc:creator><pubDate>Thu, 27 Apr 2017 13:53:49 GMT</pubDate><media:content url="https://images.unsplash.com/photo-1505778276668-26b3ff7af103?ixlib=rb-0.3.5&amp;q=80&amp;fm=jpg&amp;crop=entropy&amp;cs=tinysrgb&amp;w=1080&amp;fit=max&amp;s=6dd17a06c4acbdf1b65af30fd2538a61" medium="image"/><content:encoded><![CDATA[<!--kg-card-begin: markdown--><img src="https://images.unsplash.com/photo-1505778276668-26b3ff7af103?ixlib=rb-0.3.5&amp;q=80&amp;fm=jpg&amp;crop=entropy&amp;cs=tinysrgb&amp;w=1080&amp;fit=max&amp;s=6dd17a06c4acbdf1b65af30fd2538a61" alt="Raspberry Pi and USB GPS"><p>Just a couple of notes because this was trickier than expected, especially since all my experiences with Arduinos and GPSs have been fairly straightforward.</p>
<p>I bought a cheap USB GPS which turns out to be a Neo-7 - <code>dmesg</code> giving</p>
<pre><code>[   28.770396] usb 1-1.5: New USB device found, idVendor=1546, idProduct=01a7
[   28.770426] usb 1-1.5: New USB device strings: Mfr=1, Product=2, SerialNumber=0
[   28.770443] usb 1-1.5: Product: u-blox 7 - GPS/GNSS Receiver
[   28.770459] usb 1-1.5: Manufacturer: u-blox AG - www.u-blox.com
[   28.771952] cdc_acm 1-1.5:1.0: ttyACM0: USB ACM device
</code></pre>
<p>So, &apos;simples&apos; thinks I: install gpsd and gpsd-clients and then start gpsd with <code>sudo gpsd /dev/ttyACM0 -n -F /var/run/gpsd.sock</code> and running <code>cgps -s</code> will give me my location.</p>
<p>Not so fast there. cgps is giving me no fix and timing out.</p>
<p>First check the gps is working by simply looking at the serial output stream with<br>
<code>cat /dev/ttyACM0</code></p>
<p>Yep that works - there are the NMEA sentences and I can see the valid fix data. SO the problem must be with gpsd.</p>
<p>Long story short, after looking at syslog and realising it must be a socket problem I Googled around and found this page <a href="https://www.raspberrypi.org/forums/viewtopic.php?f=28&amp;3Bt=138711&amp;ref=idling-away-retirement">https://www.raspberrypi.org/forums/viewtopic.php?f=28&amp;t=138711</a> where I owe a big THANK YOU to Per Linderholm for his post suggesting changing /lib/systemd/system/gpsd.socket</p>
<p>Here&apos;s the relevant bit of his post</p>
<blockquote>
<p>I think the problem is in how systemd is configured.</p>
</blockquote>
<blockquote>
<p>Try to change /lib/systemd/system/gpsd.socket<br>
from</p>
</blockquote>
<blockquote>
<p>ListenStream=127.0.0.1:2947</p>
</blockquote>
<blockquote>
<p>which listens to localhost only<br>
to</p>
</blockquote>
<blockquote>
<p>ListenStream=0.0.0.0:2947</p>
</blockquote>
<blockquote>
<p>which listens to &quot;all&quot; , and restart gpsd (or reboot).</p>
</blockquote>
<p>Thanks Per that fixed it and now I get all my location data working nicely with cgps.</p>
<p><strong>Alas, too soon I spoke</strong> Today I decided to rebuild the Raspberry Pi (don&apos;t ask!) and the above steps <strong>failed</strong>. I then remembered that I had previously tried something else from that link - delorian&apos;s hint that the socket needed deleting via:</p>
<pre><code>sudo systemctl stop gpsd.socket
sudo systemctl disable gpsd.socket
</code></pre>
<p>Ah, now looking at syslog I can see gpsd is starting correctly and, yes indeed, cgps works!</p>
<!--kg-card-end: markdown-->]]></content:encoded></item><item><title><![CDATA[Nexus 2012 - a tale of woe]]></title><description><![CDATA[<!--kg-card-begin: markdown--><p>My first tablet was a 1st gen Nexus 7. Probably as good as they got at the time. But then Android 5 came along and the Nexus 7 2012 version became unusable - performance plummeted and it was just too frustrating to attempt anything other than power-off.</p>
<p>I read recently</p>]]></description><link>http://technicals.two-drifters.co.uk/nexus-2012-a-tale-of-woe/</link><guid isPermaLink="false">640f1a3a478d1d0ef02dafc9</guid><category><![CDATA[Android]]></category><dc:creator><![CDATA[Brian Jones]]></dc:creator><pubDate>Wed, 12 Apr 2017 17:38:15 GMT</pubDate><media:content url="https://images.unsplash.com/photo-1501757275-33022c3e8a72?ixlib=rb-0.3.5&amp;q=80&amp;fm=jpg&amp;crop=entropy&amp;cs=tinysrgb&amp;w=1080&amp;fit=max&amp;s=0f1bef76eb405dda22b0c2ffd67e2ea4" medium="image"/><content:encoded><![CDATA[<!--kg-card-begin: markdown--><img src="https://images.unsplash.com/photo-1501757275-33022c3e8a72?ixlib=rb-0.3.5&amp;q=80&amp;fm=jpg&amp;crop=entropy&amp;cs=tinysrgb&amp;w=1080&amp;fit=max&amp;s=0f1bef76eb405dda22b0c2ffd67e2ea4" alt="Nexus 2012 - a tale of woe"><p>My first tablet was a 1st gen Nexus 7. Probably as good as they got at the time. But then Android 5 came along and the Nexus 7 2012 version became unusable - performance plummeted and it was just too frustrating to attempt anything other than power-off.</p>
<p>I read recently that Android 6 and 7 have given it a new lease of life, but, of course, Google abandoned it years ago so any revitalisation would require a manually installed 3rd-party ROM. So let&apos;s do it!</p>
<p>Here&apos;s a checklist:</p>
<ul>
<li>Install and run Kingroot to get root access</li>
<li>Install Root Checker to confirm rooted OK</li>
<li>Install and run SuperSuMe to switch root access from Kingroot to SuperSu (Kingroot has problems with some apps - SuperSu is more reliable but harder to install from scratch)</li>
<li>Install ClockworkRecoveryMod (CWM) to allow booting into Recovery Mode</li>
<li>Download new Android ROM [Android 7.1 (Nougat)] and and appropriate Google Apps (Gapps)</li>
<li>Boot into Recovery Mode</li>
<li>Data reset, clear cache and Dalvik cache and install Android ROM</li>
<li>Reboot/Poweroff and boot back into Recovery</li>
<li>Data reset, clear caches and install Gapps</li>
</ul>
<p>Tablet now in factory clean state and just need to set everything up.</p>
<p>Actually it was all fairly straightforward once Kingroot eventually installed. It took several attempts before I could get it to root. Wasn&apos;t it Einstein who defined <em>&quot;Insanity: doing the same thing over and over again and expecting different results&quot;</em> - yet Kingroot was insane, subsequent runs kept failing differently until after about 5 attempts root was successful.</p>
<p>The hardest part of the exercise was finding all the components - back 3 or 4 years ago the web was awash with Nexus 7 firmware hack sites but today there are broken links and links to out-of-date versions of Kingroot and, in particular, CWM - enough not to give you confidence that anything found is actually likely to work!</p>
<p>Anyway a new Nougat Nexus 7 2012 and, indeed all is sprightly. Happy campers (literally, the tablet is for the campervan).</p>
<p>Not so fast there! After performing flawlessly all weekend we left it without charge and the battery depleted. Now, no matter how long I charged it the device would not power-up. I tried all the tricks: power button for &gt; 30 secs, power + vol down, charge for 156 mins and remove the cable before powering, remove back case, disconnect and reconnect battery - nothing!</p>
<p>Today I dug out my handy-dandy USB voltage and current measurer and started charging the device. It was drawing a charge current of 0.0A - i.e. not charging. Ah, that&apos;s the problem - it won&apos;t power on because the battery is flat and no end of pressing various buttons for eons can possibly wake it up until I can get some juice into it. Tried several cables and eventually one cable showed a charge of 0.44A and after 30 mins there was enough charge to actually attempt power-on, which worked first time without any of the hold for 30 secs palaver. But 0.44A is not a particularly fast charge rate, especially when attached to a 2A 5V PSU. The problem appears to be a worn USB socket - wiggling the cable readily switches back to not charging and there is definitely a lot of play of the plug in the socket.</p>
<p>A quick poke around the web - and yes - this is a very common problem. So common that USB connector assemblies are rife on Ebay for just over &#xA3;3 a time, or if I want to solder I can just buy a compatible connector for half that price.</p>
<p>The saving isn&apos;t worth the effort of fiddly soldering - an assembly is on order. Watch this space.</p>
<!--kg-card-end: markdown-->]]></content:encoded></item><item><title><![CDATA[SSL Redux]]></title><description><![CDATA[<!--kg-card-begin: markdown--><p>Chuffed and flushed with success I tried the new websites and yes typing <a href="http://two.drifters-co.uk/?ref=idling-away-retirement">http://two.drifters-co.uk</a> into Chrome automatically presents the home page as secure HTTPS. All the sub-domains work too.</p>
<p><a href="https://medium.freecodecamp.com/how-to-set-up-a-vpn-in-5-minutes-for-free-and-why-you-urgently-need-one-d5cdba361907?ref=idling-away-retirement">The article that lead me to SSL</a> informed me that Opera had a built-in VPN. So I tend</p>]]></description><link>http://technicals.two-drifters.co.uk/ssl-redux/</link><guid isPermaLink="false">640f1a3a478d1d0ef02dafc8</guid><category><![CDATA[Security]]></category><dc:creator><![CDATA[Brian Jones]]></dc:creator><pubDate>Fri, 07 Apr 2017 08:07:46 GMT</pubDate><media:content url="https://images.unsplash.com/photo-1485761954900-f9a29f318567?ixlib=rb-0.3.5&amp;q=80&amp;fm=jpg&amp;crop=entropy&amp;cs=tinysrgb&amp;w=1080&amp;fit=max&amp;s=dc97111fcc63c91370133ee14975d2b4" medium="image"/><content:encoded><![CDATA[<!--kg-card-begin: markdown--><img src="https://images.unsplash.com/photo-1485761954900-f9a29f318567?ixlib=rb-0.3.5&amp;q=80&amp;fm=jpg&amp;crop=entropy&amp;cs=tinysrgb&amp;w=1080&amp;fit=max&amp;s=dc97111fcc63c91370133ee14975d2b4" alt="SSL Redux"><p>Chuffed and flushed with success I tried the new websites and yes typing <a href="http://two.drifters-co.uk/?ref=idling-away-retirement">http://two.drifters-co.uk</a> into Chrome automatically presents the home page as secure HTTPS. All the sub-domains work too.</p>
<p><a href="https://medium.freecodecamp.com/how-to-set-up-a-vpn-in-5-minutes-for-free-and-why-you-urgently-need-one-d5cdba361907?ref=idling-away-retirement">The article that lead me to SSL</a> informed me that Opera had a built-in VPN. So I tend to use Opera for much of my browsing now. I tried <a href="http://two-drifters.co.uk/?ref=idling-away-retirement">http://two-drifters.co.uk</a> in Opera and, shock, horror Opera deemed it <strong>insecure</strong></p>
<p>Opera checks HTTPS pages to ensure that no internal links are HTTP. Of course, my home page had all my sub-domain links as HTTP relying on the server to redirect to HTTPS. I changed these but still the site was deemed insecure. A little further investigation of the source and there are a couple of links to Cloudflare and Google Fonts that also need changing to HTTPS (because I&apos;m sure Cloudflare and Google will support it) and, at last ...</p>
<p>Opera is Happy.</p>
<!--kg-card-end: markdown-->]]></content:encoded></item></channel></rss>