Wednesday, 30 December 2015

Congratulations, Microsoft, you finally made an unusable operating system

All you Linux-lovers out there are probably sniggering at this and saying that Microsoft have been producing unusable operating systems for years. But people who have a slavish love for Linux and the open-source movement (99% of which boils down to "I don't want to pay for my operating system and resent paying for anything when an alternative version is available for free, even if it's a bit shitty") simply hate Microsoft for being Microsoft.

It sort of makes sense. After all, I have a deep-rooted hatred of all things Apple. It's not quite as sentimental as the hatred a lot of open-source evangelists have for Microsoft - it's based on a belief that Apple have genuinely influenced the entire industry enough to take it backwards. Power PCs of today can barely browse websites similar to the ones we were building at the turn of the century that could run easily on 200Mhz single-core computers. But that's another argument.


This is about Microsoft.
For years - decades even - I've been a fan.
Sure their core products - Windows and Office - became unnecessarily bloated and the licence restrictions were - at times - painful (although I don't know anyone who hasn't successfully "borrowed" a works copy of Office and got it running on their home computer). But they always produced software that allowed you to get the job done easily (ok, Windows ME doesn't count, and Windows Vista was a car-crash, but if you followed the Windows upgrade rule - ever other version is shit, avoid - you were good!)

Despite all the ribbing that we give to Steve, I still think that Windows XP remains, to date, the best version of Windows. It's fast and requires just a modest computer to run. Plug-n-play works well and things "just work".

Of course, Linux users will argue that this has never been the case. But then again, if you're a dedicated Linux user, you really are in no position to criticise Windows for failing to automagically recognise and install hardware drivers. If you're installing anything even as simple as an HP LaserJet on Linux, you'll need to give yourself a good four hours and plenty of time to get your Google-fu up to speed to find out exactly which version of which driver works for your specific installation of Linux! But that's another argument.


This about Microsoft.
And the sad fact is, Windows 10 is downright unusable.
Upgrading to Windows 7 from XP was a jolt (did anyone go XP to Vista and not immediately reverse their decision?). But Windows 7 was actually a decent operating system. It was just Windows XP but with a few things in a few different places. Oh, and an annoying driver-management system - but we all just turned that off anyway didn't we?

Compared to XP, Windows 8 was a bit of a stinker.
But that's only because it looked and felt so different.
If you accepted that your start-bar was now a massive, sideways scrolling, full-screen affair, and the font was larger for a lot of messages, it wasn't actually that much different to Windows 7. For a few legacy apps, you might have to "run as administrator" but there wasn't an awful lot you couldn't do with Windows 8 that you could with Windows XP.

But Windows 10?
Windows 10 is a steaming pile of dog crap.
In all my years of using Microsoft products - from the original Windows in monochrome back in about 1992, through Windows 3.11, resisting - then finally embracing - the massive change that was Windows 95, through 98 and the mistake that was ME, to XP, I've never ever upgraded and then "gone back".

Even XP to Windows 7 had me tempted, but I gave seven a go and learned to love it. I fully embraced Windows 8 when everyone around me was "upgrading" back to Windows 7. I've never - yet - reverted back to an earlier version of Windows.

That doesn't mean the familiar ding-dong-dingle-dong of my old XP-based CNC machine doesn't fill me with warm nostalgia - I still love the old operating systems. I've just uninstalled a newer version of Windows to "go backwards".

Except now.
For Xmas I got a NAS and after backing up all my important stuff, it's time to say goodbye and good riddance to Windows 10. I'm wiping my laptop and restoring it to it's factory settings, complete with Windows 8. Everything just worked so much faster. And stuff didn't lock up as often. And my internet connection wasn't slowed down, as shit like this downloaded in the background


Seriously, Microsoft, live content cluttering up the place where I usually look for shortcuts and apps? Did you learn nothing from "active desktop" back in 1999?

The standard OS dialogues weren't blurry and out of focus, because it incorrectly recognises my touch-screen PC as a tablet device and rescales everything to 101%.



It booted up quicker (although even the totally awesome XP was pretty slow at booting up after a few months of use and Windows-update-rot had set in - the newest version is almost a parody of itself, it takes so long to boot up). It went into sleep mode and didn't require a full power-cycle to come out of snooze. You could choose whether updating was convenient or postpone/cancel it.

And I've never - in over 25 years of using Windows - lost files and/or documents as the result of a Windows upgrade. Except last week. When Windows 10 notified me that it had failed to update and promptly deleted everything I'd saved on the desktop (I save lot of work-in-progress on the desktop when I'm not yet done with it).


So goodbye Windows 10.
I really want to say "and f@*k off and don't ever darken my door again". But that means I'd have to either learn to use a Mac, or get friendly with Linux. And neither of those is particularly appealing either.

So my big gesture, to an over-bloated, arrogant, out-of-touch multi-national conglomerate like Microsoft is to throw away a product I never actually had to pay for, and install one I did. Nice one, Microsoft. You make shit software. Well at least I'm off your upgrade-cycle now. Just like XP, I'll stick with the old versions for as long as I can get away with it.

Then, there's a very real possibility that my next OS won't be Windows at all.





Saturday, 26 December 2015

Papastache Kitchen Sink Bundle

Christmas has been a rather stressful event this year.
Originally it was going to be the entire family, all gathered around for the first time in years (if not decades!). I have three sisters, and we were all going to meet up in North Wales - all the extended family, partners, husbands and wives, along with the kids would get together and meet up and be at the same place at the same time for the first time.

Then as the big day got closer, things started to fall apart.
One sister - who works in a care home - got called into work on Xmas Day. Another - who lives out in Ireland - couldn't get over this year (despite me offering to have the flights paid for, using the matched betting scam). Then the other couldn't make it because of family commitments on her husband's side.

Despite this, I'd already started out for the mountains, and was determined to deliver my vanful of gifts. Xmas Eve, I had a breakdown (apparently a weak battery with a damaged cell can get overcooked by a 350-mile drive journey). So at 4pm on Xmas Eve, just as all the shops were closing, the roads were emptying and everyone headed home, it looked like I was going to have to abandon my big old ugly Transit van some 50 miles from where I was staying and I'd spend the rest of the evening trying to get back; Steve Martin and John Candy from Planes Trains and Automobiles sprang to mind on more than one occasion! The best I could have hoped for was to find a warm bed to sleep in, and to spend Christmas Day watching Songs of Praise and The Snowman in a TravelLodge/Premier Inn.

Then - like some really bad Christmas plot-line from the cheesiest of UK soap operas (let's say Doctors) - the breakdown recovery guy went above and beyond all duty. Not only did he get us up and running again (although, to be honest, this involved nothing more than a bump-start off the back of his truck) but insisted we followed him back to his yard, he opened up the workshop (complete with oil-stained Xmas lights and two rather sad looking strings of tinsel), spent an hour or more diagnosing the fault and fixed us up so it wouldn't happen again. We were mobile again for the big day!

Xmas Day was - of course - amazing.
We tore up and down the A55 along the North Wales coast, visiting family, ate too much, exchanged presents. And generally agreed that - like a really bad Yuletide movie - it'd been worth it all in the long run and that the true spirit of Christmas was not in the buying and wrapping of presents, but in having a hellish journey 48 hours before, to arrive just in time to see friends and family on the big day.

So Christmas was a triumph.
And - like all those corny 80s re-runs showing on the Xmas24 digital channel - while all this was going on, somewhere - perhaps even on the other side of the world (although it might have been in the North Pole) some crazy Christmas Elves were busy working their magic to see that we (or at least, I) would be rewarded for all our endeavours.

After what eventually turned out to be a great day, just before retiring to bed, I checked my emails.
And there was the best Christmas present of all (sorry, Dave, it even beat your Minions socks from Poundland that were slightly too tight a fit) - an email from none other than Brett Papa, saying I'd won the Papastache Kitchen Sink Bundle!



For all kinds of reasons, this year was a great Christmas!

Wednesday, 16 December 2015

(Semi) automating Betfair

Now listen.
Don't too many of you guys do this - too many people automating matched bets will only mean that the bookies get wise to it, and clamp down, making their odds far too low (against the betting markets) to deny anyone a chance of finding a good match for backing and laying the same horse/football result/tennis player!

But a few people have asked for the source code, so here goes:
When you look at a market in Betfair, you should notice part of  the URL



Simply enter this as a parameter into your own show_market.php file (so your final URL will look something like http://myserver.com/betfair/show_market.php?market=123456)

Here's the show_market.php page

<?php
     $market="1.122075620";
     if(isset($_GET['market'])){
          $market=trim($_GET['market']);
     }
?>
<html>
<head>

<script src="js/jquery.min.js"></script>
<script src="js/show_market.js"></script>

<style>
     body, tr, td { font-family: trebuchet, tahoma, arial, sans; font-size:10pt;}
     td { padding-left: 8px; padding-right: 8px; }

     .lay_3 { background-color:#ADD8E6; color:black; text-align:center; width:38px; }
     .lay_2 { background-color:#D1EBF3; color:black; text-align:center; width:38px; }
     .lay_1 { background-color:#E9F5F9; color:black; text-align:center; width:38px; }

     .back_1 { background-color:#FFC0CB; color:black; text-align:center; width:38px; }
     .back_2 { background-color:#FFDDE3; color:black; text-align:center; width:38px; }
     .back_3 { background-color:#FFF1F3; color:black; text-align:center; width:38px; }

     .odds { font-size:100%; font-weight:bold; }
     .value { font-size:75%; font-weight:normal; }

     .odds_box { border:1px solid silver; width:40px; text-align:right; padding-right:4px; }
</style>

</head>
<body>

<table id="prices" border='1'>
<tbody>
<tr>
     <th>Selection</th><th>Name</th>
     <th colspan="3">Back</th>
     <th colspan="3">Lay</th>
     <th>Bookies price</th>
     <th>Actions</th>
</tr>

</tbody>
</table>

<script defer>
var market="<?php echo($market);?>";
refreshOdds();
</script>



</body>
</html>

You'll need the jQuery library, and this show_market.js page


function refreshOdds(){
     // get the odds from the server
     
     $.get("bet_market_js.php?market="+market, function(data, status){
      if(data.indexOf("arket not found")>0){
      alert("Market not found");
      }else if(data.indexOf("url error")>0){
      alert("Data accessing betfair API");

      }else{
populateOdds(data);
      }
     });
     
}

function populateOdds(s){
     var warning_threshold=0.15;
     var d=s.split("\n");
     var run_count=parseInt(d[0]);

     if(!$('#runners').length){
          // create the divs
          for(var i=1; i<=run_count; i++){
               var s='<tr><td id="id_'+i+'"></td><td id="name_'+i+'"></td>';
               for(var j=1; j<=3; j++){
                    s=s+'<td id="lay_'+i+'_'+j+'" class="lay_'+j+'"></td>';
               }
               for(var j=1; j<=3; j++){
                    s=s+'<td id="back_'+i+'_'+j+'" class="back_'+j+'"></td>';
               }
               s=s+'<td id="odds_'+i+'" align="right"><input type="text" class="odds_box" id="txt_odds_'+i+'" /></td>';
               s=s+'<td id="actions_'+i+'"></td>';
               s=s+'</tr>';
               $('#prices > tbody:last-child').append(s);
          }

          // add a footer
          var s='<tr><td colspan="9" id="runners">Runners: '+run_count+'</td></tr>';
          $('#prices > tbody:last-child').append(s);

          $(".odds_box").blur(function() {
               var k=$(this).val();
               k=parseFloat(k);
               if(isNaN(k)){
                    $(this).val('');
               }else{
                    k=k*100;
                    k=Math.floor(k);
                    k=k/100;
                    $(this).val(k.toFixed(2));
               }
          });

     }

     for(var i=1; i<=run_count; i++){
          // now populate the divs with the data
          var e=d[i].split("|");
          $('#id_'+i).text(e[0]);
          $('#name_'+i).text(e[1]);

          if(e.length<4){
               // non-runner: do nothing
               $('#txt_odds_'+i).hide();
          }else{

               var bookies_price=parseFloat($('#txt_odds_'+i).val());
               if(isNaN(bookies_price)){ bookies_price=0;}


               // 57.28@3.9,47.89@3.85,67.09@3.8,
               var f=e[2].split(",");
               for(var j=0; j<f.length; j++){
                    var t=f[j].split("@");
                    var odds=parseFloat(t[1]);
                    var val=parseFloat(t[0]);
     
                    if(isNaN(odds)){ odds="-";}
                    if(isNaN(val)) { val="-";} else { val=Math.floor(val); }

                    var p="<span class='odds'>"+odds+"</span><br/><span class='value'>£"+val+"</span>";
                    $('#lay_'+i+'_'+(3-j)).html(p);

                    if(j==0){
                         if( bookies_price>0 && (bookies_price+warning_threshold) >= odds){
                              $('#name_'+i).css("color","red");
                         }else{
                              $('#name_'+i).css("color","black");
                         }
                    }
                    
               }

               var f=e[3].split(",");
               for(var j=0; j<f.length; j++){
                    var t=f[j].split("@");
                    var odds=parseFloat(t[1]);
                    var val=parseFloat(t[0]);
     
                    if(isNaN(odds)){ odds="-";}
                    if(isNaN(val)) { val="-";} else { val=Math.floor(val); }

                    var p="<span class='odds'>"+odds+"</span><br/><span class='value'>£"+val+"</span>";
                    $('#back_'+i+'_'+(j+1)).html(p);

                    if(j==0){
                         if( bookies_price>0 && (bookies_price+warning_threshold) >= odds){
                              if( bookies_price>0 && (bookies_price) > odds){
                                   $('#name_'+i).css("background-color","#FF0000");
                                   $('#name_'+i).css("color","white");
                              }else{
                                   $('#name_'+i).css("background-color","#FFDDE3");
                                   $('#name_'+i).css("color","red");
                              }
                         }else{
                              $('#name_'+i).css("background-color","white");
                         }
                    }

               }
          }

     }

     setTimeout(refreshOdds, 1000);

}

This javascript function in turn calls a php page using AJAX to update the odds every second. You can, if you really must, change this to not less than 5 times a second. But just think about the battering your sever is going to take (as it is a pass-through server) as well as the Betfair server. In truth, because it's only a semi-automated approach (you still need to put the bets on manually) even refreshing every second is probably a bit overkill.

<?php

header("Expires: on, 01 Jan 1970 00:00:00 GMT");
header("Last-Modified: " . gmdate("D, d M Y H:i:s") . " GMT");
header("Cache-Control: no-store, no-cache, must-revalidate");
header("Cache-Control: post-check=0, pre-check=0", false);
header("Pragma: no-cache");

$market="1.122075620";
if(isset($_GET['market'])){ $market=trim($_GET['market']); }


function sportsApingRequest($appKey, $sessionToken, $operation, $params) {
     $ch = curl_init();
     curl_setopt($ch, CURLOPT_URL, "https://api.betfair.com/exchange/betting/json-rpc/v1");
     curl_setopt($ch, CURLOPT_POST, 1);
     curl_setopt($ch, CURLOPT_RETURNTRANSFER, 1);
     curl_setopt($ch, CURLOPT_HTTPHEADER, array('Expect:',
          'X-Application: ' . $appKey,
          'X-Authentication: ' . $sessionToken,
          'Accept: application/json',
          'Content-Type: application/json'
     ));

     $postData = '[{ "jsonrpc": "2.0", "method": "SportsAPING/v1.0/' . $operation . '", "params" :' . $params . ', "id": 1}]';
     curl_setopt($ch, CURLOPT_POSTFIELDS, $postData);
     $response = json_decode(curl_exec($ch));
     curl_close($ch);

     if (isset($response[0]->error)) {
          echo 'Call to api-ng failed: ' . "\n";
          echo 'Response: ' . json_encode($response);
          exit(-1);
     } else {
          return $response;
     }
}

function getAllEventTypes($appKey, $sessionToken) {
     $jsonResponse = sportsApingRequest($appKey, $sessionToken, 'listEventTypes', '{"filter":{}}');
     return $jsonResponse[0]->result;
}

function extractHorseRacingEventTypeId($allEventTypes) {
     foreach ($allEventTypes as $eventType) {
          if ($eventType->eventType->name == 'Horse Racing') {
               return $eventType->eventType->id;
          }
     }
}

function getMarketBook($appKey, $sessionToken, $marketId) {
     $params = '{"marketIds":["' . $marketId . '"], "priceProjection":{"priceData":["EX_BEST_OFFERS"]}}';
     $jsonResponse = sportsApingRequest($appKey, $sessionToken, 'listMarketBook', $params);
     return $jsonResponse[0]->result[0];
}

function getMarketCatalogue($appKey, $sessionToken, $m_id, $eventTypeId) {
     $params = '{"filter":{"eventTypeIds":["' . $eventTypeId . '"],
          "marketIds":["'.$m_id.'"],
          "marketCountries":["GB"],
          "marketTypeCodes":["WIN"],
          "marketStartTime":{"from":"' . date('c') . '"}},
          "sort":"FIRST_TO_START",
          "maxResults":"1",
          "marketProjection":["RUNNER_DESCRIPTION"]}';

     $jsonResponse = sportsApingRequest($appKey, $sessionToken, 'listMarketCatalogue', $params);
     return $jsonResponse[0]->result[0];
}

function availablePrices($selectionId, $marketBook) {
     $t="";

     // Get selection
     foreach ($marketBook->runners as $runner) {
          if ($runner->selectionId == $selectionId) break;
     }
          
     foreach ($runner->ex->availableToBack as $availableToBack){
          echo $availableToBack->size . "@" . $availableToBack->price . ",";
          $t.= $availableToBack->size . "@" . $availableToBack->price . ",";
     }

     echo "|";
     $t.="|";

     foreach ($runner->ex->availableToLay as $availableToLay){
          echo $availableToLay->size . "@" . $availableToLay->price . ",";
          $t.= $availableToLay->size . "@" . $availableToLay->price . ",";
     }

     return($t);
}

function getACookie(){
     
     $loginEndpoint= "https://identitysso.betfair.com/api/login";     
     $cookie = "";
     
     $username = "YOUR_LOGIN_HERE";
     $password = "YOUR_PASSWORD_HERE";
     
     $login = "true";
     $redirectmethod = "POST";
     $product = "home.betfair.int";
     $url = "https://www.betfair.com/";

     $fields = array
          (
               'username' => urlencode($username),
               'password' => urlencode($password),
               'login' => urlencode($login),
               'redirectmethod' => urlencode($redirectmethod),
               'product' => urlencode($product),
               'url' => urlencode($url)
          );

     //open connection
     $ch = curl_init($loginEndpoint);
     //url-ify the data for the POST
     $counter = 0;
     $fields_string = "&";
     
     foreach($fields as $key=>$value) {
          if ($counter > 0) {
               $fields_string .= '&';
          }
          $fields_string .= $key.'='.$value;
          $counter++;
     }

     rtrim($fields_string,'&');

     curl_setopt($ch, CURLOPT_URL, $loginEndpoint);
     curl_setopt($ch, CURLOPT_SSL_VERIFYPEER, false);
     curl_setopt($ch, CURLOPT_POST, true);
     curl_setopt($ch, CURLOPT_POSTFIELDS,$fields_string);
     curl_setopt($ch, CURLOPT_HEADER, true); // DO RETURN HTTP HEADERS
     curl_setopt($ch, CURLOPT_RETURNTRANSFER, true); // DO RETURN THE CONTENTS OF THE CALL

     //execute post

     $result = curl_exec($ch);

     if($result == false) {
           echo 'Curl error: ' . curl_error($ch);
     } else {
          $temp = explode(";", $result);
          $result = $temp[0];
               
          $end = strlen($result);
          $start = strpos($result, 'ssoid=');
          $start = $start + 6;
          
          $cookie = substr($result, $start, $end);
     }
     curl_close($ch);     
     return $cookie;
}

// ------------- here's where the magic happens ----------------

$appKey="YOUR_APP_KEY";
$sessionToken="";
if(isset($_COOKIE['session'])){ $sessionToken=trim($_COOKIE['session']); }

if(!$sessionToken){
     ob_start();
     $sessionToken = getACookie();
     ob_end_clean();
}

setcookie('session', $sessionToken, time() + 900, "/"); // 900 = 60 sec * 15 mins

// get the id for horse racing events
$horseRacingEventTypeId=extractHorseRacingEventTypeId(getAllEventTypes($appKey, $sessionToken));      // horse racing events are ID 7

// provide a market ID
$market_id="1.122075620";
if($market){ $market_id=$market; }

// get the market catalogue for this market
$catalogue=getMarketCatalogue($appKey, $sessionToken, $market_id, $horseRacingEventTypeId);

if($catalogue){
     
     // get the market data for this market
     $nextMarketBook=getMarketBook($appKey, $sessionToken, $market_id);
     $num_runners=$nextMarketBook->numberOfRunners;
     echo("".$num_runners."\n");

     foreach ($catalogue->runners as $runner) {

          $s_id=$runner->selectionId;
          $sRunner=trim($runner->runnerName);

      echo "S:" . $s_id . "|" . $sRunner . "|";
     
          foreach ($nextMarketBook->runners as $runner) {
               $sel_id=$runner->selectionId;
               if(trim($sel_id)==trim($s_id)){
                    $status=$runner->status;
                    if(trim($status)=="ACTIVE"){
                         $prices=availablePrices($sel_id, $nextMarketBook);
                     echo $prices;                                                  
                    }
                    echo "\n";
               }               
          }          
     }


}else{
     echo("Market not found");
}


?>

You'll need to modify the php above, entering your betfair username, betfair password and betfair APP key. If you haven't already got an appKey, you'll need to log into your Betfair account and do a bit of poking around until you find their API section. There are some really easy-to-follow step-by-step instructions for generating and testing your app key.

That's it really.
Most of the betfair integration code was a straight copy-and-paste job from their website. I simply took the bits I needed and left the stuff I didn't. It may not be the most elegant way of integrating with their website (the bit where I get the ID for horse racing seems a bit convoluted) but it should be enough to get you started.

On my to-do list (though probably not until the new year now) is

  • one-click placing of a lay bet (the Betfair API allows to you place bets with them programatically)
  • parsing bookies websites to get the latest horse odds for any given race (though having looked at some of the obfusicated javascript on the William Hill website, I'm not sure that this will be possible without building some kind of browser screen-scraping plugin!)
  • menu system to select a race from my own page without having to find the market id in betfair first
Just like this blog post, the code is a bit rough-and-ready.
It was thrown together to get a working solution, not to be particularly elegant. Rather like this blog post - very little thought went into the planning, it was more like "this needs doing quickly, so let's just get it done and get it live".

Hope this helps those of you who were asking for the source code!


Tuesday, 15 December 2015

Guitar fingerboard PCBs arrived

3pcb.com rushed through our order (although we did pay for the 24 hour processing!) and this morning took receipt of x15 big long PCBs.


They look great and at just 0.6mm thick, aren't going to add much bulk to our guitar fingerboards


They sit on our 25.5" scale guitar neck just perfectly.


The width is consistent all the way along the neck length and even the bottom curve is pretty close to the shape of the neck pocket.


The PCBs were deliberately designed with elongated pads, so that they could be easily accessible either with a hot-air gun, or a soldering iron tip, even with the SMT component in place.


While we're going to build a jig and CNC some patterns when it comes to building the final guitar, we couldn't wait to get started and stick some RGB LEDs down on our PCBs


Using a ruler and nothing more sophisticated than double-sided tape to hold the LEDs in place, we created our first line of LEDs. With the LEDs stuck in place, actually soldering them down was a quick and easy job, using a soldering iron and a reel of chunky 1mm solder. After just a few minutes of soldering, we had a partially working board. We stopped soldering and tested our boards every four or five rows of lights, just to help with debugging (a missed or dry solder joint could easily stop all LEDs after it from working properly).

I took about 20 minutes - allowing for breaks, cups of tea and multi-step debugging - to get soldered up to the fifteenth fret.


When lighting up all fifteen rows of lights, we're drawing more than the 500mA that our USB power will give us (and the LEDs start flickering all kinds of crazy colours) but this was easily fixed by using a dedicated power supply to power the entire board.

Now we have a working 15-fret LED matrix, it's time to get that firmware updated to actually display useful patterns. Speaking from experience, knowing just box one of the pentatonic scale is only ever going to get you so far........







Monday, 14 December 2015

This week, I've mostly been doing....

....matched betting.
It's a quick and easy way of getting some cash in, for Xmas. The concept is quite straight-forward but it's one of those deals that sounds almost too good to be true.

After all, we all know that if you gamble, you'll lose your money, then your friends and family, then your home... and when you're living on the streets drinking rainwater from out of your shoes, you'll lose the shirt off your back. And then they'll take your shoes.

Make no mistake gambling is bad. Very bad.
Many, many years ago I wrote a football database and with a few friends we committed to each putting £5 into a pot every week and seeing if we could get one over on the bookies. In six months, we'd doubled our money. That was great! But then, as often happens, the lure of easy money makes you want to win more, and do it more often, and do it more quickly.

And that's when gambling really hurts. Because eventually, with big stakes, trying to hit big money, there comes a run of losses that just wipes out everything. Luckily I've never had it happen to me, but I've seen a few people get into trouble through it (my friends and I took our profits after six months and promised never to gamble again!)

But matched betting is different. Even though, it still took nearly two months of Matt persuading me that it was 100% risk free, before I took the plunge and gave it a go. Here's how it works:

Online bookmakers offer free bets to new customers.
You need to place a bet with the bookie (as a new customer) to be given the free bet. Sometimes you only get a free bet if your first bet loses. But that's ok. Believe it or not, we actually want our bets to lose!

And that's where the key to this counter-intuitive swindle lies - we want to lose at the bookies.
But we don't want to lose overall.

Then you take the free bet, and turn it into cash that you can withdraw (and spend on stuff like a few pints down the pub, or a meal out-  or do lots of free bet offers, save the cash and buy a laser cutter!)

So we sign up as a new customer at the bookies and put a bet on (say, Arsenal to win) at the maximum price they're offering a free bet for (after all, there's no point taking only £10 if they're offering up to £50). That means sometimes putting £50 on the result of a football match.

That can be pretty terrifying.
There's no way I could afford to throw away £50 if Arsenal let me down. So I head over to Betfair and "lay" Arsenal to win. What this means is that for the length of this one match, I'm acting like a bookie myself, offering my own odds for Arsenal to win. If someone takes my bet, and Arsenal win, I have to pay out. But  - here's the clever bit - if Arsenal don't win (i.e. they lose or it's a draw) I get paid £50 from Betfair. So if Arsenal win, I get some winnings at the bookie and have to pay out on Betfair. If Arsenal lose, I lose my money at the bookies, but get the same value in winnings at Betfair.

So I offer as much money as necessary and at odds as close as I can to the original bookmaker, on Betfair, for Arsenal to not win. Now, whatever the result of the game, the winnings from one bet cover the loses on the other. (actually, unless the lay odds are less than the original bookie's odds - something which can happen, but it's very rare - there's a tiny loss involved, because Betfair charge a 5% commission).

Let's say, for example, I've put £50 at evens on Arsenal to win at honestdavesbookies.com. As a new customer, he's offering a free bet to the same value if my first bet loses. Now I head over to Betfair and I offer to accept £50 at evens for anyone wanting to back Arsenal (actually, I'd probably offer 21/20 or something just a little bit more than the original bookies, but let's keep things simple). To place this bet at Betfair, I need to put £50 up for offer (the taker gets their original £50 stake but I need to pay their winnings at 1/1).

You can imagine that at higher odds, you need to "lay" more on Betfair (a £50 lay bet at 4/1 on Betfair requires 4x£50 = £200 to be put up for offer - this is called the "liability").

Once both bets are placed, there's nothing much more to do. There's no point watching the game and cheering Arsenal on, because you've also backed them to lose (well, to not win which includes the draw as an outcome). Similarly, if you've backed a horse, there's no point tuning in to Channel 4 racing to scream at it to get over the line, because - on Betfair - you've effectively backed every other horse in the race to win as well! Before the event starts, you already know the (financial) outcome. So there's nothing to get excited about - no euphoria when you win and no despair when you lose. It's just another event happening in an already busy day. And that's the difference between matched betting and gambling.

Nothing is ever at risk.
Whatever the outcome of the event (be it football, horse racing or whatever) you neither win nor lose (except, perhaps, a small known loss because of commission). As Matt put it "you give some money out, some guys kick a football around, and when they're done, you get your money back". (of course this is a simplification as it doesn't account for any difference in odds, nor the commission on Befair if the bet loses at the bookies, and wins at Betfair). But you get the idea.

So what's the point?
Remember Honest Dave?
He was offering us a free bet up to the value our first stake. That's why we put £50 and not a tenner on Arsenal. As it's the 2015 season at the time of writing, you probably already know the outcome - Arsenal, indeed, fail to win (despite the odds suggesting they would) and Honest Dave takes the £50. He doesn't need to know we backed the opposite result on Betfair!

In Betfair, our bet for Arsenal to not win comes good, and covers the loss paid to Honest Dave.

Now Dave thinks we're ripe for the picking - a "mug punter" who throws money on their favourite football team. So - just like a drug dealer offering a free gateway drug to get you hooked - he says "go on, have another go. Here's your £50 back to try again".

Cheers Dave.
So we take his £50 and repeat the process.
We already know that whatever the outcome, our stake money either appears at the bookies as winnings or - if the original bet loses - in our Betfair account (since we backed the bet to lose).
The important difference this second time around is that we don't actually have any losses to cover if the bet loses at the bookies.

If the free bet wins at the bookies, Dave keeps the £50 stake (normally, when betting at the bookies, you get your stake back - but if it's offered as a free bet, the bookie keeps it) but he does pay out the winnings. The "lay bet" at Betfair is calculated such that it always costs less (if it loses) than the bookie will pay out in winnings.

If the free bet loses at the bookies, well, there's no real harm done. Dave offered you £50 and simply took it back. But over at Betfair, our "lay bet" has come good. We have more cash than we started with, despite "losing" the bet.

And that's how matched betting works. It's heavily reliant on Betfair.
And it's heavily reliant on finding matches between bookmakers odds and Betfair "lay stakes".
And finding these matches can be slow and laborious (even when using a website like oddsmonkey.com, they're not always bang up to date). So what we decided we needed was a way of (semi) automating things.

The Betfair website is simple and easy to use, but to match odds against bookmakers requires a lot of flicking between screens


Luckily, Betfair offers a completely free (for personal use) API for programatically accessing their betting exchange. Which means we can build some software to do a lot of the work for us. For nerds who know how to program, and who are interested in free money, this is perfect!

The best time to find "good matches" between the bookies website(s) and Betfair is in the ten minutes or so before the event begins. This is when all the "betting traders" are doing their thing, trying to make money from mug punters and other bookmakers, who have been offering different odds on the same event. Prices on Betfair change far more frequently than they do on bookmakers websites.

What we decided we needed was a website where we could be automatically notified when a horse (or football team, or whatever) reached a specific price on the Betfair exchange. This allows us to pre-calculate what we think an acceptable loss for a matched-bet would be, and work out how much, and at what odds, we should be prepared to "lay" a bet on Betfair, in order to accept an offer from an online bookmaker.

Using the Betfair API, we were able to quickly produce a webpage that displayed the current prices on the Betfair exchange.


And a quick bit of javascript later, and we're able to not only enter the current "back price" at the online bookies, but have the display automatically update when "good matches" become available.


Here I've set my "warning threshold" to 0.1 which means that when the Betfair exchange lay price is just 0.1 above the bookies prices, I'll be given a visual cue. 


Here I've placed a bet at 4.5 (in fractional format, 7/2) on Amenable, and laid the bet off with Betfair for £25.28 at 4.5 as well (the early indicator on our web page gave us time to flick over to the Betfair website and place the bet while the exchange odds were very favourable, and within the few seconds before William Hill reduced their odds offered). 

Whatever the outcome of this race, it's set to lose me 98p. There's no point getting excited about the race - whichever horse wins, I know I'm going to be 98p down. So while the race is running, I'm off messing about with guitars and board games and soldering and stuff. The actual race is of no importance now - I just need to wait for the race to complete.

Once the race has been run - and irrespective of the outcome - I get a free bet, to the value of £25. So while I know I'm 98p down after the first race, I can expect to get about 75% of the value of the free bet, using matched betting (you'll need to Google "matched betting" if you want a full explanation of why this is).

So I repeat the process, only this time, using the £25 free bet stake the Mr Hill so kindly offered. "Big Storm Coming" looks like a good horse to use the free bet on. Not that I know anything about his form, whether he's a stallion or a nag. I know nothing about the horse. What I do see is that the numbers look good.


I put my free £25 on Big Storm Coming on the William Hill website and head over to Betfair.


Normally I try to find matches where the back offer and lay odds are as close together as possible. But because I'm a bit impatient, and don't want to sit glued to the computer screen until the race actually runs, I take the 7.8 odds on "Big Storm Coming" on Betfair.

(this odds calculator is available at oddsmoney.com but matched betting websites all over the 'net carry similar versions which pretty much do the same thing)

I lay £20.97 at odds of 7.8 and I know that - whatever the outcome of the race - I'll be £19.90 up after it. That's all there is to it. The race isn't for a couple of hours yet; had I waited, I'm sure I could have got a closer match (and taken more from the free bet offer). But nearly £20 for a couple of minutes clicking about isn't a bad return!

Rinse and repeat a few times with different bookmakers and that's Xmas Dinner paid for this year!

Saturday, 12 December 2015

Dragonmeet Earl's Court

Last weekend, a few of us went along to the annual Dragonmeet exhibition at Earl's Court in London. Last year was very interesting, with a load of tabletop and board games being demostrated, loads of miniatures companies, tabletop terrain and so on, as well as the usual mix of new releases, a play-testing area and guest speakers.

We were expecting more of the same this time around, but left feeling, well, a little disappointed.


There were some beautifully painted miniatures


and a few people playing tabletop wargames

(for players who spend so long painting their miniatures and terrain, to make everything look so lifelike and impressive, the whole effect is ruined when they're actually put into play, and the tabletop is strewn with rules, cards, dice and other distracting paraphernalia)

but on the whole there didn't feel like there was a particularly large tabletop presence.


There were a few interesting people - like this guy doing live fantasy art drawings in pen and ink - but speakers were few and far between. One thing that was in abundance was card/deck-building games. And play testing. Loads of games being play tested.

I couldn't help but wonder if the relative success of Kickstarter has steered too many people into making their own games! Card games are obviously cheap to make and offer a high return (Exploding Kittens anyone?) and it showed in the offerings at Dragonmeet. There must have been about five deck-building games for every one other type of game at the show. And the play-testing room was filled with people handing out scraps of paper and inkjet-printed punched-out cards, trying to find the next Exploding Kittens.

But all-in-all, the excitement and enthusiasm of last year seemed to be noticeably lacking. Maybe it's because some of the "big boys" like 4Ground and Mantic had already had success last year with their earlier Kickstarter campaigns, they didn't feel the need to put much effort into this one? It was only after reviewing the traders list in the programme that we actually noticed Matic had been there - they certainly didn't have much of a presence this year! 4Ground were completely absent.

Dragonmeet was promoted this year as "...one of the most vibrant tabletop gaming conventions and the largest of it's kind in London".

It was certainly a large space.
But it wasn't particularly vibrant.
And there was very little tabletop gaming going on at all!

These days, it feels like the nerd club blog is just one wak-wak-oops report after another! Come on Dragonmeet, stop obsessing about Kickstarter - that's not the only place where the tabletop action is.

Wednesday, 9 December 2015

Real wood embedded electronic guitar lights

It started out as a suggestion when we very first began this project, over a month ago - why not just drill holes in an existing fingerboard and put a PCB behind it?

Well, after numerous attempts at removing the fingerboard, we never quite managed to get it off and keep the whole thing in one piece, let alone in a re-usable, playable condition.

Then Nick got chatting to those lovely people at www.aiersiguitar.com who said that, for a minimum order of ten guitars, they could supply kits with the fingerboards detached from the necks (they simply skip the step where they glue them on, during manufacture!)

Well, ten guitars is a lot, even for a bunch of nerds (who, let's be honest, only two or three are actually interested in playing guitar anyway). But, never one to miss an opportunity, I said we should got for it, and sell any extras online; who knows, there may even be a market for full-colour RGB light-up guitars.....

Anyway, the order is in, and some time in early 2016, we can expect our first batch of 10 guitar kits. The PCBs have already been made at www.3pcb.com and are winging there way over here - hopefully in plenty time before a few of us have to go away for Xmas.

So we thought we'd see how butchering the existing fingerboard might look, rather than casting resin fretboards, just to compare the two.


Using a scrap of fingerboard removed with the bandsaw, we lined up the LEDs on one of our test sectoins and drilled some holes (using a 1100W hammer drill and a 3mm metal drill bit - no messing about doing things properly with a pillar drill and dedicated wood-working equipment!)

The finish is terrible, but gives us an idea about how it might look. The result was - surprisingly - better than we'd expected.


When viewed full on, the lights are quite bright and - as you can see in this photo - they shine out quite clearly. But when you're playing guitar, you're not looking at the fretboard from 90 degrees, immediately in front of it - you tend to look down and along the neck...


The photo above fails to demonstrate just how bright and vibrant the colours actually are. They look far better than we could have hoped. And even from a very shallow viewing angle, it's not only very easy to see which LEDs are lit up, but because of the way the light reflects off the inside of the drilled holes, each colour is bright, vibrant, and distinct.

So if we're going to try flogging a few of these guitars, it might be worthwhile being able to offer either the "synthetic" resin finish (which looks awesome) or "real wood" (which feels nice).

Fretting a resin fingerboard

So far, we've created our resin fingerboard, checked it's the correct size/scale (it is) so now it's time to get it fretted and see if it's actually playable!

At first we thought we could "repurpose" the frets from the original neck from our guitar kit

The specialist fret-pulling tool has a ground surface, allowing the jaws to get right under the fret wire without ripping or causing the (rosewood?) fingerboard to splinter

The frets came out easily enough. But in doing so, the "radius" (slight curve to make them sit nicely along the curved surface of the fingerboard) got straightened out


So even after removing all the frets are carefully keeping them in order, all we managed to do was end up with a load of knackered guitar frets. It makes sense - usually when a proper luthier is removing frets from a guitar, it's because they're worn or need replacing; so if they get damaged, they're only going to end up discarded anyway.


With the frets removed, we had to take the fingerboard off the guitar neck. We'd already tried the household iron trick on this; whether it's the type of glue used in cheap Chinese knock-offs, or magic fret-wire that refuses to transfer heat, we've no idea. But we knew that heat wouldn't be the answer. So we set about hacking it off with a chisel.

The glue held so fast that not even a chisel could prise it off - it simply splintered the wood into little pieces! So we put the neck onto the BuildBrighton bandsaw and sliced it clean off.


After slicing the most part of the fingerboard off the neck, a bit of filing and sanding got the neck surface about as flat and smooth as we could hope


And after mixing some two-part epoxy resin (Araldite) we glued each of the previously placed frets in place. With the glue dry, we cut each fret to length


And using our fancy multi-angled fret file, made sure that there was no nasty overhang where the frets met the edge of the resin fingerboard


We quickly learned the correct way to use the multi-angled file to make the sides of the frets flush with the fingerboard...
...and bevelled the sides of the frets to a nice, consistent, 35 degree angle

(this isn't a photo of us actually doing the filing - it's a photo from Google Images - but it demonstrates the correct way to use the file to bevel the frets)

The fingerboard still needs a little work, but we've managed to get the frets flush to the sides of the resin, and there's very little, or no, snagging as you run your thumb up the side of the fretboard.


 With the fingerboard finished, we next need to actually fit it to the neck, build the guitar and give it a whirl!

Guitar RGB fretboard PCBs

We've got our PCB designed and although we're 99.9% certain it's done right, we've had too many instances of ordering factory-made PCBs, only to discover a fault (missing ground connections are classic) or think of something that we should have added to the original design - sometimes even just an hour or so after "pulling the trigger" on an order from China.

So we're always keen to make up at least one, complete, finished, homebrew PCB of our own design before getting multiples ordered (because it always works out cheaper to order ten or so PCBs to spread the cost of the carriage, than just one or two).

Our guitar PCBs are quite long, thin boards. There's too much "black" in them to use press-n-peel (any design with a lot of fill in it causes the press-n-peel to slide around when all the toner is hot and melted) so we sprayed some copper boards with car paint, and used the "laser etching" method


Normally we use white spirit to wipe down the PCBs after "laser etching" but before dunking in the ferric chloride to etch the exposed copper. Doing so removes the thin layer of vapour that sometimes settles after the laser has finished etching. There was no white spirit to hand, but we found that regular household cleaner worked just as well.

It took us three pieces of 160mm x 100mm "eurocard" sized copper clad board to create the full PCB

(in our final design, we're only actually going up to the 17th fret with the LEDs, but the PCB will be the same size as the entire length of the guitar neck, to make it easier to get consistent thickness when the board is cast)

Now the BB laser didn't exactly do a great job of separating some of the tracks that we'd drawn closer together (particularly in some of the higher frets, where things start to get a little bit cramped and squashed together). So we had to tidy up some of the tracks, using a craft knife and the pointed end of our multimeter, to ensure that there were no little bits of copper creating unwanted bridges between tracks.


Because we've had trouble with running tracks under these fiddly RGB LEDs in the past, we thought it prudent to use some solder resist on these boards. Our solder resist paint is UV curable, so some LEDs from an earlier project were just the job


Maybe our UV LEDs were a little fierce, but some of the solder resist over the pads has since cured rock hard. The idea is to cover the pads with a transparency with a black mask over each of the pads - the UV light shouldn't penetrate these areas, leaving the paint wet, while the rest of it cures.
Once done, the idea is to wash off the un-cured paint and leave the copper of the pads exposed.

In another wak-wak-oops moment, we managed to cure the solder resist - but somehow leave a lot of the pads covered with now-cured resist paint too! We had to scrape away sections of solder resist before soldering the LEDs on:


After creating three separate sections, debugging proved particularly difficult. It took a lot of work to get each section to work in its entirely - all the way along the length of the whole section. Which meant that when we combined the three sections and the lights only made it half-way up the first section, we couldn't be sure where the problem lay!

While each of these sections lights up entirely when plugged into the controller individually, when daisy-chained together, only half of the first board would light up. Weird huh?

Could it be that the three sections were not joined together properly? Or that there's a fault or a short on the data line somewhere? Or maybe a bad or dry solder joint where the resist hasn't been cleared off the pads properly? A fault on the first section would be enough to stop every subsequent LED working - so when the lights on sections two and three aren't working, it's actually quite difficult to know where to start!

After a lot of soldering, re-soldering, unsoldering and replacing multiple LEDs, we figured that the PCBs were simply not of good enough quality to be reliable for testing.

Only one thing for it - the one thing we said we'd never do after so many botched PCBs in the past - get some ordered, and just hope they work when they get here!

Before pulling the trigger on the order, we checked and double-checked our PCB layout with the 25.5" scale fingerboard.


Although it's a tiny discrepency, by the time we reach the 21st fret, the fret markings on the PCB are about 1.5mm off where they should be. It's not the end of the world - those fret lines are only guides for placing the LEDs. And, looking at the design, they'd still be ok - each LED is still slightly "north" of where the fretwire will go across the fingerboard. But it might still be worth bunching things up a little bit before committing to getting a dozen or more full-sized (and relatively expensive) PCBs manufactured!

Although close, the fret wire will not actually obscure any part of the LEDs, even if we left our final design as drawn. Still, better safe than sorry - this design had to undergo three or four revisions before we were happy to  send it out.




A few clicks later and we've got the PCBs on order. No doubt, Steve will be smiling to himself - he suggested we do this about three weeks ago! While we're waiting for them to arrive, we'll get on with making a few jigs to help populate and solder them...

Tuesday, 8 December 2015

Communicating between two (or more) Arduino / PIC / AVR chips in a noisy environment

Every now and again, in the middle of something else, you have to take a break and work on something completely unrelated. Sometimes, someone asks for some advice on the very thing you're currently working on. That's exactly what happened when Mike got in touch about a project he was working on that involves talking to up to 30 AVR chips (actually, Arduino-style ATMega328s) from a master controller. While initially it seemed quite straight-forward, Mike's project involves each chip being on a motor controller board - so lots of noisy emf and back emf as the motors start, stop and reverse.

To complicate matters, each chip lives in a separate, repeatable, stand-alone module, which may or may not be rotating, depending on the state of the motor it's controlling. So connections between the modules is being done using slip-rings.

So as well as the electro-magnetic noise generated by the motors, there's also the possibility of noise from the (data) lines passing through the slip-rings too. This is going to call for a well-thought-out communications protocol, and plenty of "smoothing" capacitors!

Of course, we're going to be putting plenty of de-coupling or smoothing capacitors on the power supplies of all our chips (in fact, a simple test, trying to drive the motor without decoupling caps on the supply to the AVR chip made it go haywire - intermittently resetting, locking up, and failing to exit looping routines).

But we're also going to have to think about smoothing our data/comms cables too.
Rather than a single-wire, timing based system (like UART/serial) we're going to use a two-wire clock-and-data system, more like I2C. This should prove more robust than a timing-based protocol, in the event of a "crackle" in the line just at the point that we're reading a logical one or a zero from the wire.

We're going to assume that our data lines are (electrically) very noisy. This is always the safest position to work from. Let's quickly review how I2C works:

The master device pulses a clock line up and down. The slave device monitors the clock line for  a change in state (for example, a low-to-high transition, or a high-to-low, whichever you prefer). When this change in transition occurs, the slave devices looks at the data line to see if it is high (logical one) or low (logical zero).



This is all well and good, in a perfect environment. But what if we add some extraneous noise to the line? What if our clock line was particularly noisy?



And even if the clock line were pefectly "clean", a noisy data line would result in the wrong bit values being transmitted


So we need to be sure that our SCK and SDA lines are free from noise. The easiest way to achieve this is a simple capacitor from the pin to ground. The capacitor will "filter out" any noise (on either line) so where there is "crackle" (a very rapid low-to-high or high-to-low rise/fall in the signal) the capacitor will "fill in the gaps" and remove the noise.

The downside to this simplistic approach is that we also lose the sharp falling edges of our clock and data lines. Instead of abruptly falling to zero, the clock line, for example, will decay to zero over time:


How a clock line might look, with a smoothing capacitor to ground on the SCK pin, while the SDA pin has no smoothing applied.


While the decay has been exaggerated for illustrative purposes, it should already be clear that when we introduce capacitors on the clock (and data) lines, we need to increase the delay between clock pulses, to allow enough time for the smoothing capacitor(s) to fully discharge. Just as the clock line decays to zero, if we add smoothing capacitors to the data line also, we can expect those to decay to zero too. This means that we also need to make sure that the delay between clock pulses is long enough to allow both capacitors to discharge fully, to ensure accurate values are read back.

As a result, our data transmission rate is going to be much slower than it might be, with a direct, single wire connecting the SDA and SCK lines together.

How much decay (and therefore how wide the gap between pulses needs to be) is determined by the size of the capacitor. A large capacitor would mean a very long decay time (and very long pulse widths would be required). A very small capacitor would mean very short decay times, so we can use shorter pulse widths.

As with most things, when applying electronics to the real world, there's a trade-off between getting the best compromise for the job. We want our pulse widths to be short, to allow faster communications. But we also want to ensure that the capacitor values are not so small (and the decay times so tiny) that they fail to "fill in the gaps" should there be any crackle or noise in the lines. The best way to find the ideal capacitor values? Hook up some I2C comms lines and try some different values!


The capacitor values need to be very small so as to smooth, but not muddy the signal. But - as with most non-digital/analogue electronics - it's a fine balancing act: too small and they will be discharging so quickly as to be barely noticeable. While this may actually be fine for our purpose, they don't really offer much protection against a really noisy line, that may have lots of large gaps in the signal, all very close together.

By fiddling about, trying different capacitors and different clock times, we settled on 50nF/0.05uF and a clock time of 2ms on, 2ms off.

This means each pulse width is 4ms, to we can send 50 bits (5 bytes) per second. This is much slower than many other data protocols, but comes with the security of knowing that any noise in the line (either through electro-magnetic noise from the motors, or because of the connections between slip-rings) isn't going to disrupt our data signals.

For good measure, we're also going to implement a simple checksum at the end of each message. This will be the usual XOR-sum as the last byte in the data packet. So if we're sending two bytes of data, we XOR them together and send it as the third byte. On the receiving end, after receiving two bytes of data, we XOR them together and compare this result to the third byte. If there is a match, we know the message is valid. If not, we assume the message is corrupt, and ignore.

How we handle "corrupt" or ignored messages is for another day - at least we've tried to ensure that our signal lines are as "clean" as they can be, given that we already know we're working in a very "noisy" environment.